mirror of https://github.com/containers/podman.git
Merge pull request #10836 from Luap99/diff
podman diff accept two images or containers
This commit is contained in:
commit
895b815188
|
|
@ -1,10 +1,11 @@
|
||||||
package containers
|
package containers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/containers/common/pkg/report"
|
|
||||||
"github.com/containers/podman/v3/cmd/podman/common"
|
"github.com/containers/podman/v3/cmd/podman/common"
|
||||||
|
"github.com/containers/podman/v3/cmd/podman/diff"
|
||||||
"github.com/containers/podman/v3/cmd/podman/registry"
|
"github.com/containers/podman/v3/cmd/podman/registry"
|
||||||
"github.com/containers/podman/v3/cmd/podman/validate"
|
"github.com/containers/podman/v3/cmd/podman/validate"
|
||||||
|
"github.com/containers/podman/v3/libpod/define"
|
||||||
"github.com/containers/podman/v3/pkg/domain/entities"
|
"github.com/containers/podman/v3/pkg/domain/entities"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
@ -13,11 +14,11 @@ import (
|
||||||
var (
|
var (
|
||||||
// podman container _diff_
|
// podman container _diff_
|
||||||
diffCmd = &cobra.Command{
|
diffCmd = &cobra.Command{
|
||||||
Use: "diff [options] CONTAINER",
|
Use: "diff [options] CONTAINER [CONTAINER]",
|
||||||
Args: validate.IDOrLatestArgs,
|
Args: diff.ValidateContainerDiffArgs,
|
||||||
Short: "Inspect changes to the container's file systems",
|
Short: "Inspect changes to the container's file systems",
|
||||||
Long: `Displays changes to the container filesystem's'. The container will be compared to its parent layer.`,
|
Long: `Displays changes to the container filesystem's'. The container will be compared to its parent layer or the second argument when given.`,
|
||||||
RunE: diff,
|
RunE: diffRun,
|
||||||
ValidArgsFunction: common.AutocompleteContainers,
|
ValidArgsFunction: common.AutocompleteContainers,
|
||||||
Example: `podman container diff myCtr
|
Example: `podman container diff myCtr
|
||||||
podman container diff -l --format json myCtr`,
|
podman container diff -l --format json myCtr`,
|
||||||
|
|
@ -33,41 +34,22 @@ func init() {
|
||||||
|
|
||||||
diffOpts = &entities.DiffOptions{}
|
diffOpts = &entities.DiffOptions{}
|
||||||
flags := diffCmd.Flags()
|
flags := diffCmd.Flags()
|
||||||
|
|
||||||
|
// FIXME: Why does this exists? It is not used anywhere.
|
||||||
flags.BoolVar(&diffOpts.Archive, "archive", true, "Save the diff as a tar archive")
|
flags.BoolVar(&diffOpts.Archive, "archive", true, "Save the diff as a tar archive")
|
||||||
_ = flags.MarkHidden("archive")
|
_ = flags.MarkHidden("archive")
|
||||||
|
|
||||||
formatFlagName := "format"
|
formatFlagName := "format"
|
||||||
flags.StringVar(&diffOpts.Format, formatFlagName, "", "Change the output format")
|
flags.StringVar(&diffOpts.Format, formatFlagName, "", "Change the output format (json)")
|
||||||
_ = diffCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(nil))
|
_ = diffCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(nil))
|
||||||
|
|
||||||
validate.AddLatestFlag(diffCmd, &diffOpts.Latest)
|
validate.AddLatestFlag(diffCmd, &diffOpts.Latest)
|
||||||
}
|
}
|
||||||
|
|
||||||
func diff(cmd *cobra.Command, args []string) error {
|
func diffRun(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) == 0 && !diffOpts.Latest {
|
if len(args) == 0 && !diffOpts.Latest {
|
||||||
return errors.New("container must be specified: podman container diff [options [...]] ID-NAME")
|
return errors.New("container must be specified: podman container diff [options [...]] ID-NAME")
|
||||||
}
|
}
|
||||||
|
diffOpts.Type = define.DiffContainer
|
||||||
var id string
|
return diff.Diff(cmd, args, *diffOpts)
|
||||||
if len(args) > 0 {
|
|
||||||
id = args[0]
|
|
||||||
}
|
|
||||||
results, err := registry.ContainerEngine().ContainerDiff(registry.GetContext(), id, *diffOpts)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case report.IsJSON(diffOpts.Format):
|
|
||||||
return common.ChangesToJSON(results)
|
|
||||||
case diffOpts.Format == "":
|
|
||||||
return common.ChangesToTable(results)
|
|
||||||
default:
|
|
||||||
return errors.New("only supported value for '--format' is 'json'")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Diff(cmd *cobra.Command, args []string, options entities.DiffOptions) error {
|
|
||||||
diffOpts = &options
|
|
||||||
return diff(cmd, args)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/containers/podman/v3/cmd/podman/common"
|
"github.com/containers/podman/v3/cmd/podman/common"
|
||||||
"github.com/containers/podman/v3/cmd/podman/containers"
|
"github.com/containers/podman/v3/cmd/podman/diff"
|
||||||
"github.com/containers/podman/v3/cmd/podman/images"
|
|
||||||
"github.com/containers/podman/v3/cmd/podman/registry"
|
"github.com/containers/podman/v3/cmd/podman/registry"
|
||||||
"github.com/containers/podman/v3/cmd/podman/validate"
|
"github.com/containers/podman/v3/cmd/podman/validate"
|
||||||
|
"github.com/containers/podman/v3/libpod/define"
|
||||||
"github.com/containers/podman/v3/pkg/domain/entities"
|
"github.com/containers/podman/v3/pkg/domain/entities"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
@ -16,13 +14,13 @@ import (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// Command: podman _diff_ Object_ID
|
// Command: podman _diff_ Object_ID
|
||||||
diffDescription = `Displays changes on a container or image's filesystem. The container or image will be compared to its parent layer.`
|
diffDescription = `Displays changes on a container or image's filesystem. The container or image will be compared to its parent layer or the second argument when given.`
|
||||||
diffCmd = &cobra.Command{
|
diffCmd = &cobra.Command{
|
||||||
Use: "diff [options] {CONTAINER|IMAGE}",
|
Use: "diff [options] {CONTAINER|IMAGE} [{CONTAINER|IMAGE}]",
|
||||||
Args: validate.IDOrLatestArgs,
|
Args: diff.ValidateContainerDiffArgs,
|
||||||
Short: "Display the changes to the object's file system",
|
Short: "Display the changes to the object's file system",
|
||||||
Long: diffDescription,
|
Long: diffDescription,
|
||||||
RunE: diff,
|
RunE: diffRun,
|
||||||
ValidArgsFunction: common.AutocompleteContainersAndImages,
|
ValidArgsFunction: common.AutocompleteContainersAndImages,
|
||||||
Example: `podman diff imageID
|
Example: `podman diff imageID
|
||||||
podman diff ctrID
|
podman diff ctrID
|
||||||
|
|
@ -37,36 +35,18 @@ func init() {
|
||||||
Command: diffCmd,
|
Command: diffCmd,
|
||||||
})
|
})
|
||||||
flags := diffCmd.Flags()
|
flags := diffCmd.Flags()
|
||||||
|
// FIXME: Why does this exists? It is not used anywhere.
|
||||||
flags.BoolVar(&diffOpts.Archive, "archive", true, "Save the diff as a tar archive")
|
flags.BoolVar(&diffOpts.Archive, "archive", true, "Save the diff as a tar archive")
|
||||||
_ = flags.MarkHidden("archive")
|
_ = flags.MarkHidden("archive")
|
||||||
|
|
||||||
formatFlagName := "format"
|
formatFlagName := "format"
|
||||||
flags.StringVar(&diffOpts.Format, formatFlagName, "", "Change the output format")
|
flags.StringVar(&diffOpts.Format, formatFlagName, "", "Change the output format (json)")
|
||||||
_ = diffCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(nil))
|
_ = diffCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(nil))
|
||||||
|
|
||||||
validate.AddLatestFlag(diffCmd, &diffOpts.Latest)
|
validate.AddLatestFlag(diffCmd, &diffOpts.Latest)
|
||||||
}
|
}
|
||||||
|
|
||||||
func diff(cmd *cobra.Command, args []string) error {
|
func diffRun(cmd *cobra.Command, args []string) error {
|
||||||
// Latest implies looking for a container
|
diffOpts.Type = define.DiffAll
|
||||||
if diffOpts.Latest {
|
return diff.Diff(cmd, args, diffOpts)
|
||||||
return containers.Diff(cmd, args, diffOpts)
|
|
||||||
}
|
|
||||||
|
|
||||||
options := entities.ContainerExistsOptions{
|
|
||||||
External: true,
|
|
||||||
}
|
|
||||||
if found, err := registry.ContainerEngine().ContainerExists(registry.GetContext(), args[0], options); err != nil {
|
|
||||||
return err
|
|
||||||
} else if found.Value {
|
|
||||||
return containers.Diff(cmd, args, diffOpts)
|
|
||||||
}
|
|
||||||
|
|
||||||
if found, err := registry.ImageEngine().Exists(registry.GetContext(), args[0]); err != nil {
|
|
||||||
return err
|
|
||||||
} else if found.Value {
|
|
||||||
return images.Diff(cmd, args, diffOpts)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("%s not found on system", args[0])
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
package diff
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/containers/common/pkg/report"
|
||||||
|
"github.com/containers/podman/v3/cmd/podman/registry"
|
||||||
|
"github.com/containers/podman/v3/pkg/domain/entities"
|
||||||
|
"github.com/docker/docker/pkg/archive"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Diff(cmd *cobra.Command, args []string, options entities.DiffOptions) error {
|
||||||
|
results, err := registry.ContainerEngine().Diff(registry.GetContext(), args, options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case report.IsJSON(options.Format):
|
||||||
|
return changesToJSON(results)
|
||||||
|
case options.Format == "":
|
||||||
|
return changesToTable(results)
|
||||||
|
default:
|
||||||
|
return errors.New("only supported value for '--format' is 'json'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChangesReportJSON struct {
|
||||||
|
Changed []string `json:"changed,omitempty"`
|
||||||
|
Added []string `json:"added,omitempty"`
|
||||||
|
Deleted []string `json:"deleted,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func changesToJSON(diffs *entities.DiffReport) error {
|
||||||
|
body := ChangesReportJSON{}
|
||||||
|
for _, row := range diffs.Changes {
|
||||||
|
switch row.Kind {
|
||||||
|
case archive.ChangeAdd:
|
||||||
|
body.Added = append(body.Added, row.Path)
|
||||||
|
case archive.ChangeDelete:
|
||||||
|
body.Deleted = append(body.Deleted, row.Path)
|
||||||
|
case archive.ChangeModify:
|
||||||
|
body.Changed = append(body.Changed, row.Path)
|
||||||
|
default:
|
||||||
|
return errors.Errorf("output kind %q not recognized", row.Kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pull in configured json library
|
||||||
|
enc := json.NewEncoder(os.Stdout)
|
||||||
|
enc.SetIndent("", " ")
|
||||||
|
return enc.Encode(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func changesToTable(diffs *entities.DiffReport) error {
|
||||||
|
for _, row := range diffs.Changes {
|
||||||
|
fmt.Fprintln(os.Stdout, row.String())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDOrLatestArgs used to validate a nameOrId was provided or the "--latest" flag
|
||||||
|
func ValidateContainerDiffArgs(cmd *cobra.Command, args []string) error {
|
||||||
|
given, _ := cmd.Flags().GetBool("latest")
|
||||||
|
if len(args) > 0 && !given {
|
||||||
|
return cobra.RangeArgs(1, 2)(cmd, args)
|
||||||
|
}
|
||||||
|
if len(args) > 0 && given {
|
||||||
|
return errors.New("--latest and containers cannot be used together")
|
||||||
|
}
|
||||||
|
if len(args) == 0 && !given {
|
||||||
|
return errors.Errorf("%q requires a name, id, or the \"--latest\" flag", cmd.CommandPath())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
package images
|
package images
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/containers/common/pkg/report"
|
|
||||||
"github.com/containers/podman/v3/cmd/podman/common"
|
"github.com/containers/podman/v3/cmd/podman/common"
|
||||||
|
"github.com/containers/podman/v3/cmd/podman/diff"
|
||||||
"github.com/containers/podman/v3/cmd/podman/registry"
|
"github.com/containers/podman/v3/cmd/podman/registry"
|
||||||
|
"github.com/containers/podman/v3/libpod/define"
|
||||||
"github.com/containers/podman/v3/pkg/domain/entities"
|
"github.com/containers/podman/v3/pkg/domain/entities"
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
@ -13,11 +13,11 @@ import (
|
||||||
var (
|
var (
|
||||||
// podman container _inspect_
|
// podman container _inspect_
|
||||||
diffCmd = &cobra.Command{
|
diffCmd = &cobra.Command{
|
||||||
Use: "diff [options] IMAGE",
|
Use: "diff [options] IMAGE [IMAGE]",
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.RangeArgs(1, 2),
|
||||||
Short: "Inspect changes to the image's file systems",
|
Short: "Inspect changes to the image's file systems",
|
||||||
Long: `Displays changes to the image's filesystem. The image will be compared to its parent layer.`,
|
Long: `Displays changes to the image's filesystem. The image will be compared to its parent layer or the second argument when given.`,
|
||||||
RunE: diff,
|
RunE: diffRun,
|
||||||
ValidArgsFunction: common.AutocompleteImages,
|
ValidArgsFunction: common.AutocompleteImages,
|
||||||
Example: `podman image diff myImage
|
Example: `podman image diff myImage
|
||||||
podman image diff --format json redis:alpine`,
|
podman image diff --format json redis:alpine`,
|
||||||
|
|
@ -39,31 +39,11 @@ func diffFlags(flags *pflag.FlagSet) {
|
||||||
_ = flags.MarkDeprecated("archive", "Provided for backwards compatibility, has no impact on output.")
|
_ = flags.MarkDeprecated("archive", "Provided for backwards compatibility, has no impact on output.")
|
||||||
|
|
||||||
formatFlagName := "format"
|
formatFlagName := "format"
|
||||||
flags.StringVar(&diffOpts.Format, formatFlagName, "", "Change the output format")
|
flags.StringVar(&diffOpts.Format, formatFlagName, "", "Change the output format (json)")
|
||||||
_ = diffCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(nil))
|
_ = diffCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func diff(cmd *cobra.Command, args []string) error {
|
func diffRun(cmd *cobra.Command, args []string) error {
|
||||||
if diffOpts.Latest {
|
diffOpts.Type = define.DiffImage
|
||||||
return errors.New("image diff does not support --latest")
|
return diff.Diff(cmd, args, *diffOpts)
|
||||||
}
|
|
||||||
|
|
||||||
results, err := registry.ImageEngine().Diff(registry.GetContext(), args[0], *diffOpts)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case report.IsJSON(diffOpts.Format):
|
|
||||||
return common.ChangesToJSON(results)
|
|
||||||
case diffOpts.Format == "":
|
|
||||||
return common.ChangesToTable(results)
|
|
||||||
default:
|
|
||||||
return errors.New("only supported value for '--format' is 'json'")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Diff(cmd *cobra.Command, args []string, options entities.DiffOptions) error {
|
|
||||||
diffOpts = &options
|
|
||||||
return diff(cmd, args)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
.so man1/podman-diff.1
|
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
% podman-container-diff(1)
|
||||||
|
|
||||||
|
## NAME
|
||||||
|
podman\-container\-diff - Inspect changes on a container's filesystem
|
||||||
|
|
||||||
|
## SYNOPSIS
|
||||||
|
**podman container diff** [*options*] *container* [*container*]
|
||||||
|
|
||||||
|
## DESCRIPTION
|
||||||
|
Displays changes on a container's filesystem. The container will be compared to its parent layer or the second argument when given.
|
||||||
|
|
||||||
|
The output is prefixed with the following symbols:
|
||||||
|
|
||||||
|
| Symbol | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| A | A file or directory was added. |
|
||||||
|
| D | A file or directory was deleted. |
|
||||||
|
| C | A file or directory was changed. |
|
||||||
|
|
||||||
|
## OPTIONS
|
||||||
|
|
||||||
|
#### **--format**
|
||||||
|
|
||||||
|
Alter the output into a different format. The only valid format for **podman container diff** is `json`.
|
||||||
|
|
||||||
|
#### **--latest**, **-l**
|
||||||
|
|
||||||
|
Instead of providing the container name or ID, use the last created container. If you use methods other than Podman
|
||||||
|
to run containers such as CRI-O, the last started container could be from either of those methods. (This option is not available with the remote Podman client)
|
||||||
|
|
||||||
|
## EXAMPLE
|
||||||
|
|
||||||
|
```
|
||||||
|
# podman container diff container1
|
||||||
|
C /usr
|
||||||
|
C /usr/local
|
||||||
|
C /usr/local/bin
|
||||||
|
A /usr/local/bin/docker-entrypoint.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
$ podman container diff --format json container1 container2
|
||||||
|
{
|
||||||
|
"added": [
|
||||||
|
"/test"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## SEE ALSO
|
||||||
|
**[podman(1)](podman.1.md)**, **[podman-container(1)](podman-container.1.md)**
|
||||||
|
|
||||||
|
## HISTORY
|
||||||
|
July 2021, Originally compiled by Paul Holzinger <pholzing@redhat.com>
|
||||||
|
|
@ -19,7 +19,7 @@ The container command allows you to manage containers
|
||||||
| commit | [podman-commit(1)](podman-commit.1.md) | Create new image based on the changed container. |
|
| commit | [podman-commit(1)](podman-commit.1.md) | Create new image based on the changed container. |
|
||||||
| cp | [podman-cp(1)](podman-cp.1.md) | Copy files/folders between a container and the local filesystem. |
|
| cp | [podman-cp(1)](podman-cp.1.md) | Copy files/folders between a container and the local filesystem. |
|
||||||
| create | [podman-create(1)](podman-create.1.md) | Create a new container. |
|
| create | [podman-create(1)](podman-create.1.md) | Create a new container. |
|
||||||
| diff | [podman-diff(1)](podman-diff.1.md) | Inspect changes on a container or image's filesystem. |
|
| diff | [podman-container-diff(1)](podman-container-diff.1.md) | Inspect changes on a container's filesystem |
|
||||||
| exec | [podman-exec(1)](podman-exec.1.md) | Execute a command in a running container. |
|
| exec | [podman-exec(1)](podman-exec.1.md) | Execute a command in a running container. |
|
||||||
| exists | [podman-container-exists(1)](podman-container-exists.1.md) | Check if a container exists in local storage |
|
| exists | [podman-container-exists(1)](podman-container-exists.1.md) | Check if a container exists in local storage |
|
||||||
| export | [podman-export(1)](podman-export.1.md) | Export a container's filesystem contents as a tar archive. |
|
| export | [podman-export(1)](podman-export.1.md) | Export a container's filesystem contents as a tar archive. |
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,24 @@
|
||||||
podman\-diff - Inspect changes on a container or image's filesystem
|
podman\-diff - Inspect changes on a container or image's filesystem
|
||||||
|
|
||||||
## SYNOPSIS
|
## SYNOPSIS
|
||||||
**podman diff** [*options*] *name*
|
**podman diff** [*options*] *container|image* [*container|image*]
|
||||||
|
|
||||||
**podman container diff** [*options*] *name*
|
|
||||||
|
|
||||||
## DESCRIPTION
|
## DESCRIPTION
|
||||||
Displays changes on a container or image's filesystem. The container or image will be compared to its parent layer
|
Displays changes on a container or image's filesystem. The container or image will be compared to its parent layer or the second argument when given.
|
||||||
|
|
||||||
|
The output is prefixed with the following symbols:
|
||||||
|
|
||||||
|
| Symbol | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| A | A file or directory was added. |
|
||||||
|
| D | A file or directory was deleted. |
|
||||||
|
| C | A file or directory was changed. |
|
||||||
|
|
||||||
## OPTIONS
|
## OPTIONS
|
||||||
|
|
||||||
#### **--format**
|
#### **--format**
|
||||||
|
|
||||||
Alter the output into a different format. The only valid format for diff is `json`.
|
Alter the output into a different format. The only valid format for **podman diff** is `json`.
|
||||||
|
|
||||||
#### **--latest**, **-l**
|
#### **--latest**, **-l**
|
||||||
|
|
||||||
|
|
@ -25,15 +31,12 @@ to run containers such as CRI-O, the last started container could be from either
|
||||||
## EXAMPLE
|
## EXAMPLE
|
||||||
|
|
||||||
```
|
```
|
||||||
# podman diff redis:alpine
|
$ podman diff container1
|
||||||
C /usr
|
A /myscript.sh
|
||||||
C /usr/local
|
|
||||||
C /usr/local/bin
|
|
||||||
A /usr/local/bin/docker-entrypoint.sh
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
# podman diff --format json redis:alpine
|
$ podman diff --format json myimage
|
||||||
{
|
{
|
||||||
"changed": [
|
"changed": [
|
||||||
"/usr",
|
"/usr",
|
||||||
|
|
@ -46,8 +49,13 @@ A /usr/local/bin/docker-entrypoint.sh
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
$ podman diff container1 image1
|
||||||
|
A /test
|
||||||
|
```
|
||||||
|
|
||||||
## SEE ALSO
|
## SEE ALSO
|
||||||
podman(1)
|
**[podman(1)](podman.1.md)**, **[podman-container-diff(1)](podman-container-diff.1.md)**, **[podman-image-diff(1)](podman-image-diff.1.md)**
|
||||||
|
|
||||||
## HISTORY
|
## HISTORY
|
||||||
August 2017, Originally compiled by Ryan Cole <rycole@redhat.com>
|
August 2017, Originally compiled by Ryan Cole <rycole@redhat.com>
|
||||||
|
|
|
||||||
|
|
@ -4,21 +4,29 @@
|
||||||
podman-image-diff - Inspect changes on an image's filesystem
|
podman-image-diff - Inspect changes on an image's filesystem
|
||||||
|
|
||||||
## SYNOPSIS
|
## SYNOPSIS
|
||||||
**podman image diff** [*options*] *name*
|
**podman image diff** [*options*] *image* [*image*]
|
||||||
|
|
||||||
## DESCRIPTION
|
## DESCRIPTION
|
||||||
Displays changes on a container or image's filesystem. The container or image will be compared to its parent layer
|
Displays changes on an image's filesystem. The image will be compared to its parent layer or the second argument when given.
|
||||||
|
|
||||||
|
The output is prefixed with the following symbols:
|
||||||
|
|
||||||
|
| Symbol | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| A | A file or directory was added. |
|
||||||
|
| D | A file or directory was deleted. |
|
||||||
|
| C | A file or directory was changed. |
|
||||||
|
|
||||||
## OPTIONS
|
## OPTIONS
|
||||||
|
|
||||||
#### **--format**
|
#### **--format**
|
||||||
|
|
||||||
Alter the output into a different format. The only valid format for diff is `json`.
|
Alter the output into a different format. The only valid format for **podman image diff** is `json`.
|
||||||
|
|
||||||
## EXAMPLE
|
## EXAMPLE
|
||||||
|
|
||||||
```
|
```
|
||||||
# podman diff redis:old redis:alpine
|
$ podman diff redis:old
|
||||||
C /usr
|
C /usr
|
||||||
C /usr/local
|
C /usr/local
|
||||||
C /usr/local/bin
|
C /usr/local/bin
|
||||||
|
|
@ -26,7 +34,7 @@ A /usr/local/bin/docker-entrypoint.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
# podman diff --format json redis:old redis:alpine
|
$ podman diff --format json redis:old redis:alpine
|
||||||
{
|
{
|
||||||
"changed": [
|
"changed": [
|
||||||
"/usr",
|
"/usr",
|
||||||
|
|
|
||||||
|
|
@ -923,7 +923,7 @@ func (c *Container) exportCheckpoint(options ContainerCheckpointOptions) error {
|
||||||
var addToTarFiles []string
|
var addToTarFiles []string
|
||||||
if !options.IgnoreRootfs {
|
if !options.IgnoreRootfs {
|
||||||
// To correctly track deleted files, let's go through the output of 'podman diff'
|
// To correctly track deleted files, let's go through the output of 'podman diff'
|
||||||
rootFsChanges, err := c.runtime.GetDiff("", c.ID())
|
rootFsChanges, err := c.runtime.GetDiff("", c.ID(), define.DiffContainer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "error exporting root file-system diff for %q", c.ID())
|
return errors.Wrapf(err, "error exporting root file-system diff for %q", c.ID())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package define
|
||||||
|
|
||||||
|
// extra type to use as enum
|
||||||
|
type DiffType uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
// only diff containers
|
||||||
|
DiffContainer DiffType = 1 << iota
|
||||||
|
// only diff images
|
||||||
|
DiffImage
|
||||||
|
// diff both containers and images
|
||||||
|
DiffAll DiffType = 0b11111111
|
||||||
|
)
|
||||||
|
|
||||||
|
func (d DiffType) String() string {
|
||||||
|
switch d {
|
||||||
|
case DiffAll:
|
||||||
|
return "all"
|
||||||
|
case DiffContainer:
|
||||||
|
return "container"
|
||||||
|
case DiffImage:
|
||||||
|
return "image"
|
||||||
|
default:
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ package libpod
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/containers/common/libimage"
|
"github.com/containers/common/libimage"
|
||||||
|
"github.com/containers/podman/v3/libpod/define"
|
||||||
"github.com/containers/podman/v3/libpod/layers"
|
"github.com/containers/podman/v3/libpod/layers"
|
||||||
"github.com/containers/storage/pkg/archive"
|
"github.com/containers/storage/pkg/archive"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
@ -21,14 +22,14 @@ var initInodes = map[string]bool{
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDiff returns the differences between the two images, layers, or containers
|
// GetDiff returns the differences between the two images, layers, or containers
|
||||||
func (r *Runtime) GetDiff(from, to string) ([]archive.Change, error) {
|
func (r *Runtime) GetDiff(from, to string, diffType define.DiffType) ([]archive.Change, error) {
|
||||||
toLayer, err := r.getLayerID(to)
|
toLayer, err := r.getLayerID(to, diffType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
fromLayer := ""
|
fromLayer := ""
|
||||||
if from != "" {
|
if from != "" {
|
||||||
fromLayer, err = r.getLayerID(from)
|
fromLayer, err = r.getLayerID(from, diffType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -49,25 +50,30 @@ func (r *Runtime) GetDiff(from, to string) ([]archive.Change, error) {
|
||||||
// GetLayerID gets a full layer id given a full or partial id
|
// GetLayerID gets a full layer id given a full or partial id
|
||||||
// If the id matches a container or image, the id of the top layer is returned
|
// If the id matches a container or image, the id of the top layer is returned
|
||||||
// If the id matches a layer, the top layer id is returned
|
// If the id matches a layer, the top layer id is returned
|
||||||
func (r *Runtime) getLayerID(id string) (string, error) {
|
func (r *Runtime) getLayerID(id string, diffType define.DiffType) (string, error) {
|
||||||
var toLayer string
|
var lastErr error
|
||||||
toImage, _, err := r.libimageRuntime.LookupImage(id, &libimage.LookupImageOptions{IgnorePlatform: true})
|
if diffType&define.DiffImage == define.DiffImage {
|
||||||
if err == nil {
|
toImage, _, err := r.libimageRuntime.LookupImage(id, &libimage.LookupImageOptions{IgnorePlatform: true})
|
||||||
return toImage.TopLayer(), nil
|
if err == nil {
|
||||||
|
return toImage.TopLayer(), nil
|
||||||
|
}
|
||||||
|
lastErr = err
|
||||||
}
|
}
|
||||||
|
|
||||||
targetID, err := r.store.Lookup(id)
|
if diffType&define.DiffContainer == define.DiffContainer {
|
||||||
if err != nil {
|
toCtr, err := r.store.Container(id)
|
||||||
targetID = id
|
if err == nil {
|
||||||
}
|
return toCtr.LayerID, nil
|
||||||
toCtr, err := r.store.Container(targetID)
|
|
||||||
if err != nil {
|
|
||||||
toLayer, err = layers.FullID(r.store, targetID)
|
|
||||||
if err != nil {
|
|
||||||
return "", errors.Errorf("layer, image, or container %s does not exist", id)
|
|
||||||
}
|
}
|
||||||
} else {
|
lastErr = err
|
||||||
toLayer = toCtr.LayerID
|
|
||||||
}
|
}
|
||||||
return toLayer, nil
|
|
||||||
|
if diffType == define.DiffAll {
|
||||||
|
toLayer, err := layers.FullID(r.store, id)
|
||||||
|
if err == nil {
|
||||||
|
return toLayer, nil
|
||||||
|
}
|
||||||
|
lastErr = err
|
||||||
|
}
|
||||||
|
return "", errors.Wrapf(lastErr, "%s not found", id)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,39 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/containers/podman/v3/libpod"
|
"github.com/containers/podman/v3/libpod"
|
||||||
|
"github.com/containers/podman/v3/libpod/define"
|
||||||
"github.com/containers/podman/v3/pkg/api/handlers/utils"
|
"github.com/containers/podman/v3/pkg/api/handlers/utils"
|
||||||
|
"github.com/gorilla/schema"
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Changes(w http.ResponseWriter, r *http.Request) {
|
func Changes(w http.ResponseWriter, r *http.Request) {
|
||||||
|
decoder := r.Context().Value("decoder").(*schema.Decoder)
|
||||||
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
runtime := r.Context().Value("runtime").(*libpod.Runtime)
|
||||||
|
|
||||||
|
query := struct {
|
||||||
|
Parent string `schema:"parent"`
|
||||||
|
DiffType string `schema:"diffType"`
|
||||||
|
}{}
|
||||||
|
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
|
||||||
|
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var diffType define.DiffType
|
||||||
|
switch query.DiffType {
|
||||||
|
case "", "all":
|
||||||
|
diffType = define.DiffAll
|
||||||
|
case "container":
|
||||||
|
diffType = define.DiffContainer
|
||||||
|
case "image":
|
||||||
|
diffType = define.DiffImage
|
||||||
|
default:
|
||||||
|
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Errorf("invalid diffType value %q", query.DiffType))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
id := utils.GetName(r)
|
id := utils.GetName(r)
|
||||||
changes, err := runtime.GetDiff("", id)
|
changes, err := runtime.GetDiff(query.Parent, id, diffType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.InternalServerError(w, err)
|
utils.InternalServerError(w, err)
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -1505,6 +1505,15 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
|
||||||
// type: string
|
// type: string
|
||||||
// required: true
|
// required: true
|
||||||
// description: the name or id of the container
|
// description: the name or id of the container
|
||||||
|
// - in: query
|
||||||
|
// name: parent
|
||||||
|
// type: string
|
||||||
|
// description: specify a second layer which is used to compare against it instead of the parent layer
|
||||||
|
// - in: query
|
||||||
|
// name: diffType
|
||||||
|
// type: string
|
||||||
|
// enum: [all, container, image]
|
||||||
|
// description: select what you want to match, default is all
|
||||||
// responses:
|
// responses:
|
||||||
// 200:
|
// 200:
|
||||||
// description: Array of Changes
|
// description: Array of Changes
|
||||||
|
|
|
||||||
|
|
@ -1286,7 +1286,16 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
|
||||||
// name: name
|
// name: name
|
||||||
// type: string
|
// type: string
|
||||||
// required: true
|
// required: true
|
||||||
// description: the name or id of the container
|
// description: the name or id of the image
|
||||||
|
// - in: query
|
||||||
|
// name: parent
|
||||||
|
// type: string
|
||||||
|
// description: specify a second layer which is used to compare against it instead of the parent layer
|
||||||
|
// - in: query
|
||||||
|
// name: diffType
|
||||||
|
// type: string
|
||||||
|
// enum: [all, container, image]
|
||||||
|
// description: select what you want to match, default is all
|
||||||
// responses:
|
// responses:
|
||||||
// 200:
|
// 200:
|
||||||
// description: Array of Changes
|
// description: Array of Changes
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,16 @@ func Diff(ctx context.Context, nameOrID string, options *DiffOptions) ([]archive
|
||||||
if options == nil {
|
if options == nil {
|
||||||
options = new(DiffOptions)
|
options = new(DiffOptions)
|
||||||
}
|
}
|
||||||
_ = options
|
|
||||||
conn, err := bindings.GetClient(ctx)
|
conn, err := bindings.GetClient(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/changes", nil, nil, nameOrID)
|
params, err := options.ToParams()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/changes", params, nil, nameOrID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,12 @@ type CreateOptions struct{}
|
||||||
|
|
||||||
//go:generate go run ../generator/generator.go DiffOptions
|
//go:generate go run ../generator/generator.go DiffOptions
|
||||||
// DiffOptions are optional options for creating containers
|
// DiffOptions are optional options for creating containers
|
||||||
type DiffOptions struct{}
|
type DiffOptions struct {
|
||||||
|
// By the default diff will compare against the parent layer. Change the Parent if you want to compare against something else.
|
||||||
|
Parent *string
|
||||||
|
// Change the type the backend should match. This can be set to "all", "container" or "image".
|
||||||
|
DiffType *string
|
||||||
|
}
|
||||||
|
|
||||||
//go:generate go run ../generator/generator.go ExecInspectOptions
|
//go:generate go run ../generator/generator.go ExecInspectOptions
|
||||||
// ExecInspectOptions are optional options for inspecting
|
// ExecInspectOptions are optional options for inspecting
|
||||||
|
|
|
||||||
|
|
@ -19,3 +19,35 @@ func (o *DiffOptions) Changed(fieldName string) bool {
|
||||||
func (o *DiffOptions) ToParams() (url.Values, error) {
|
func (o *DiffOptions) ToParams() (url.Values, error) {
|
||||||
return util.ToParams(o)
|
return util.ToParams(o)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithParent
|
||||||
|
func (o *DiffOptions) WithParent(value string) *DiffOptions {
|
||||||
|
v := &value
|
||||||
|
o.Parent = v
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetParent
|
||||||
|
func (o *DiffOptions) GetParent() string {
|
||||||
|
var parent string
|
||||||
|
if o.Parent == nil {
|
||||||
|
return parent
|
||||||
|
}
|
||||||
|
return *o.Parent
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDiffType
|
||||||
|
func (o *DiffOptions) WithDiffType(value string) *DiffOptions {
|
||||||
|
v := &value
|
||||||
|
o.DiffType = v
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDiffType
|
||||||
|
func (o *DiffOptions) GetDiffType() string {
|
||||||
|
var diffType string
|
||||||
|
if o.DiffType == nil {
|
||||||
|
return diffType
|
||||||
|
}
|
||||||
|
return *o.DiffType
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,10 @@ type RemoveOptions struct {
|
||||||
//go:generate go run ../generator/generator.go DiffOptions
|
//go:generate go run ../generator/generator.go DiffOptions
|
||||||
// DiffOptions are optional options image diffs
|
// DiffOptions are optional options image diffs
|
||||||
type DiffOptions struct {
|
type DiffOptions struct {
|
||||||
|
// By the default diff will compare against the parent layer. Change the Parent if you want to compare against something else.
|
||||||
|
Parent *string
|
||||||
|
// Change the type the backend should match. This can be set to "all", "container" or "image".
|
||||||
|
DiffType *string
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:generate go run ../generator/generator.go ListOptions
|
//go:generate go run ../generator/generator.go ListOptions
|
||||||
|
|
|
||||||
|
|
@ -19,3 +19,35 @@ func (o *DiffOptions) Changed(fieldName string) bool {
|
||||||
func (o *DiffOptions) ToParams() (url.Values, error) {
|
func (o *DiffOptions) ToParams() (url.Values, error) {
|
||||||
return util.ToParams(o)
|
return util.ToParams(o)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithParent
|
||||||
|
func (o *DiffOptions) WithParent(value string) *DiffOptions {
|
||||||
|
v := &value
|
||||||
|
o.Parent = v
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetParent
|
||||||
|
func (o *DiffOptions) GetParent() string {
|
||||||
|
var parent string
|
||||||
|
if o.Parent == nil {
|
||||||
|
return parent
|
||||||
|
}
|
||||||
|
return *o.Parent
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDiffType
|
||||||
|
func (o *DiffOptions) WithDiffType(value string) *DiffOptions {
|
||||||
|
v := &value
|
||||||
|
o.DiffType = v
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDiffType
|
||||||
|
func (o *DiffOptions) GetDiffType() string {
|
||||||
|
var diffType string
|
||||||
|
if o.DiffType == nil {
|
||||||
|
return diffType
|
||||||
|
}
|
||||||
|
return *o.DiffType
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ type ContainerEngine interface {
|
||||||
ContainerCopyFromArchive(ctx context.Context, nameOrID, path string, reader io.Reader, options CopyOptions) (ContainerCopyFunc, error)
|
ContainerCopyFromArchive(ctx context.Context, nameOrID, path string, reader io.Reader, options CopyOptions) (ContainerCopyFunc, error)
|
||||||
ContainerCopyToArchive(ctx context.Context, nameOrID string, path string, writer io.Writer) (ContainerCopyFunc, error)
|
ContainerCopyToArchive(ctx context.Context, nameOrID string, path string, writer io.Writer) (ContainerCopyFunc, error)
|
||||||
ContainerCreate(ctx context.Context, s *specgen.SpecGenerator) (*ContainerCreateReport, error)
|
ContainerCreate(ctx context.Context, s *specgen.SpecGenerator) (*ContainerCreateReport, error)
|
||||||
ContainerDiff(ctx context.Context, nameOrID string, options DiffOptions) (*DiffReport, error)
|
|
||||||
ContainerExec(ctx context.Context, nameOrID string, options ExecOptions, streams define.AttachStreams) (int, error)
|
ContainerExec(ctx context.Context, nameOrID string, options ExecOptions, streams define.AttachStreams) (int, error)
|
||||||
ContainerExecDetached(ctx context.Context, nameOrID string, options ExecOptions) (string, error)
|
ContainerExecDetached(ctx context.Context, nameOrID string, options ExecOptions) (string, error)
|
||||||
ContainerExists(ctx context.Context, nameOrID string, options ContainerExistsOptions) (*BoolReport, error)
|
ContainerExists(ctx context.Context, nameOrID string, options ContainerExistsOptions) (*BoolReport, error)
|
||||||
|
|
@ -52,6 +51,7 @@ type ContainerEngine interface {
|
||||||
ContainerUnmount(ctx context.Context, nameOrIDs []string, options ContainerUnmountOptions) ([]*ContainerUnmountReport, error)
|
ContainerUnmount(ctx context.Context, nameOrIDs []string, options ContainerUnmountOptions) ([]*ContainerUnmountReport, error)
|
||||||
ContainerUnpause(ctx context.Context, namesOrIds []string, options PauseUnPauseOptions) ([]*PauseUnpauseReport, error)
|
ContainerUnpause(ctx context.Context, namesOrIds []string, options PauseUnPauseOptions) ([]*PauseUnpauseReport, error)
|
||||||
ContainerWait(ctx context.Context, namesOrIds []string, options WaitOptions) ([]WaitReport, error)
|
ContainerWait(ctx context.Context, namesOrIds []string, options WaitOptions) ([]WaitReport, error)
|
||||||
|
Diff(ctx context.Context, namesOrIds []string, options DiffOptions) (*DiffReport, error)
|
||||||
Events(ctx context.Context, opts EventsOptions) error
|
Events(ctx context.Context, opts EventsOptions) error
|
||||||
GenerateSystemd(ctx context.Context, nameOrID string, opts GenerateSystemdOptions) (*GenerateSystemdReport, error)
|
GenerateSystemd(ctx context.Context, nameOrID string, opts GenerateSystemdOptions) (*GenerateSystemdReport, error)
|
||||||
GenerateKube(ctx context.Context, nameOrIDs []string, opts GenerateKubeOptions) (*GenerateKubeReport, error)
|
GenerateKube(ctx context.Context, nameOrIDs []string, opts GenerateKubeOptions) (*GenerateKubeReport, error)
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import (
|
||||||
type ImageEngine interface {
|
type ImageEngine interface {
|
||||||
Build(ctx context.Context, containerFiles []string, opts BuildOptions) (*BuildReport, error)
|
Build(ctx context.Context, containerFiles []string, opts BuildOptions) (*BuildReport, error)
|
||||||
Config(ctx context.Context) (*config.Config, error)
|
Config(ctx context.Context) (*config.Config, error)
|
||||||
Diff(ctx context.Context, nameOrID string, options DiffOptions) (*DiffReport, error)
|
|
||||||
Exists(ctx context.Context, nameOrID string) (*BoolReport, error)
|
Exists(ctx context.Context, nameOrID string) (*BoolReport, error)
|
||||||
History(ctx context.Context, nameOrID string, opts ImageHistoryOptions) (*ImageHistoryReport, error)
|
History(ctx context.Context, nameOrID string, opts ImageHistoryOptions) (*ImageHistoryReport, error)
|
||||||
Import(ctx context.Context, opts ImageImportOptions) (*ImageImportReport, error)
|
Import(ctx context.Context, opts ImageImportOptions) (*ImageImportReport, error)
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
buildahDefine "github.com/containers/buildah/define"
|
buildahDefine "github.com/containers/buildah/define"
|
||||||
|
"github.com/containers/podman/v3/libpod/define"
|
||||||
"github.com/containers/podman/v3/libpod/events"
|
"github.com/containers/podman/v3/libpod/events"
|
||||||
"github.com/containers/podman/v3/pkg/specgen"
|
"github.com/containers/podman/v3/pkg/specgen"
|
||||||
"github.com/containers/storage/pkg/archive"
|
"github.com/containers/storage/pkg/archive"
|
||||||
|
|
@ -62,9 +63,10 @@ type InspectOptions struct {
|
||||||
|
|
||||||
// All API and CLI diff commands and diff sub-commands use the same options
|
// All API and CLI diff commands and diff sub-commands use the same options
|
||||||
type DiffOptions struct {
|
type DiffOptions struct {
|
||||||
Format string `json:",omitempty"` // CLI only
|
Format string `json:",omitempty"` // CLI only
|
||||||
Latest bool `json:",omitempty"` // API and CLI, only supported by containers
|
Latest bool `json:",omitempty"` // API and CLI, only supported by containers
|
||||||
Archive bool `json:",omitempty"` // CLI only
|
Archive bool `json:",omitempty"` // CLI only
|
||||||
|
Type define.DiffType // Type which should be compared
|
||||||
}
|
}
|
||||||
|
|
||||||
// DiffReport provides changes for object
|
// DiffReport provides changes for object
|
||||||
|
|
|
||||||
|
|
@ -858,16 +858,26 @@ func (ic *ContainerEngine) ContainerListExternal(ctx context.Context) ([]entitie
|
||||||
return ps.GetExternalContainerLists(ic.Libpod)
|
return ps.GetExternalContainerLists(ic.Libpod)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerDiff provides changes to given container
|
// Diff provides changes to given container
|
||||||
func (ic *ContainerEngine) ContainerDiff(ctx context.Context, nameOrID string, opts entities.DiffOptions) (*entities.DiffReport, error) {
|
func (ic *ContainerEngine) Diff(ctx context.Context, namesOrIDs []string, opts entities.DiffOptions) (*entities.DiffReport, error) {
|
||||||
|
var (
|
||||||
|
base string
|
||||||
|
parent string
|
||||||
|
)
|
||||||
if opts.Latest {
|
if opts.Latest {
|
||||||
ctnr, err := ic.Libpod.GetLatestContainer()
|
ctnr, err := ic.Libpod.GetLatestContainer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "unable to get latest container")
|
return nil, errors.Wrap(err, "unable to get latest container")
|
||||||
}
|
}
|
||||||
nameOrID = ctnr.ID()
|
base = ctnr.ID()
|
||||||
}
|
}
|
||||||
changes, err := ic.Libpod.GetDiff("", nameOrID)
|
if len(namesOrIDs) > 0 {
|
||||||
|
base = namesOrIDs[0]
|
||||||
|
if len(namesOrIDs) > 1 {
|
||||||
|
parent = namesOrIDs[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
changes, err := ic.Libpod.GetDiff(parent, base, opts.Type)
|
||||||
return &entities.DiffReport{Changes: changes}, err
|
return &entities.DiffReport{Changes: changes}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -403,14 +403,6 @@ func (ir *ImageEngine) Import(ctx context.Context, options entities.ImageImportO
|
||||||
return &entities.ImageImportReport{Id: imageID}, nil
|
return &entities.ImageImportReport{Id: imageID}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ir *ImageEngine) Diff(_ context.Context, nameOrID string, _ entities.DiffOptions) (*entities.DiffReport, error) {
|
|
||||||
changes, err := ir.Libpod.GetDiff("", nameOrID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &entities.DiffReport{Changes: changes}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.ImageSearchOptions) ([]entities.ImageSearchReport, error) {
|
func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.ImageSearchOptions) ([]entities.ImageSearchReport, error) {
|
||||||
filter, err := libimage.ParseSearchFilter(opts.Filters)
|
filter, err := libimage.ParseSearchFilter(opts.Filters)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -765,8 +765,18 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta
|
||||||
return &report, err
|
return &report, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ic *ContainerEngine) ContainerDiff(ctx context.Context, nameOrID string, _ entities.DiffOptions) (*entities.DiffReport, error) {
|
func (ic *ContainerEngine) Diff(ctx context.Context, namesOrIDs []string, opts entities.DiffOptions) (*entities.DiffReport, error) {
|
||||||
changes, err := containers.Diff(ic.ClientCtx, nameOrID, nil)
|
var base string
|
||||||
|
options := new(containers.DiffOptions).WithDiffType(opts.Type.String())
|
||||||
|
if len(namesOrIDs) > 0 {
|
||||||
|
base = namesOrIDs[0]
|
||||||
|
if len(namesOrIDs) > 1 {
|
||||||
|
options.WithParent(namesOrIDs[1])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, errors.New("no arguments for diff")
|
||||||
|
}
|
||||||
|
changes, err := containers.Diff(ic.ClientCtx, base, options)
|
||||||
return &entities.DiffReport{Changes: changes}, err
|
return &entities.DiffReport{Changes: changes}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -299,16 +299,6 @@ func (ir *ImageEngine) Save(ctx context.Context, nameOrID string, tags []string,
|
||||||
return utils2.UntarToFileSystem(opts.Output, f, nil)
|
return utils2.UntarToFileSystem(opts.Output, f, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Diff reports the changes to the given image
|
|
||||||
func (ir *ImageEngine) Diff(ctx context.Context, nameOrID string, _ entities.DiffOptions) (*entities.DiffReport, error) {
|
|
||||||
options := new(images.DiffOptions)
|
|
||||||
changes, err := images.Diff(ir.ClientCtx, nameOrID, options)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &entities.DiffReport{Changes: changes}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.ImageSearchOptions) ([]entities.ImageSearchReport, error) {
|
func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.ImageSearchOptions) ([]entities.ImageSearchReport, error) {
|
||||||
mappedFilters := make(map[string][]string)
|
mappedFilters := make(map[string][]string)
|
||||||
filters, err := libimage.ParseSearchFilter(opts.Filters)
|
filters, err := libimage.ParseSearchFilter(opts.Filters)
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
package integration
|
package integration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
. "github.com/containers/podman/v3/test/utils"
|
. "github.com/containers/podman/v3/test/utils"
|
||||||
|
"github.com/containers/storage/pkg/stringid"
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
)
|
)
|
||||||
|
|
@ -40,13 +42,6 @@ var _ = Describe("Podman diff", func() {
|
||||||
Expect(len(session.OutputToStringArray())).To(BeNumerically(">", 0))
|
Expect(len(session.OutputToStringArray())).To(BeNumerically(">", 0))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("podman container diff of image", func() {
|
|
||||||
session := podmanTest.Podman([]string{"container", "diff", ALPINE})
|
|
||||||
session.WaitWithDefaultTimeout()
|
|
||||||
Expect(session.ExitCode()).To(Equal(0))
|
|
||||||
Expect(len(session.OutputToStringArray())).To(BeNumerically(">", 0))
|
|
||||||
})
|
|
||||||
|
|
||||||
It("podman diff bogus image", func() {
|
It("podman diff bogus image", func() {
|
||||||
session := podmanTest.Podman([]string{"diff", "1234"})
|
session := podmanTest.Podman([]string{"diff", "1234"})
|
||||||
session.WaitWithDefaultTimeout()
|
session.WaitWithDefaultTimeout()
|
||||||
|
|
@ -84,7 +79,11 @@ var _ = Describe("Podman diff", func() {
|
||||||
session := podmanTest.Podman([]string{"run", "--name", "diff-test", ALPINE, "touch", "/tmp/diff-test"})
|
session := podmanTest.Podman([]string{"run", "--name", "diff-test", ALPINE, "touch", "/tmp/diff-test"})
|
||||||
session.WaitWithDefaultTimeout()
|
session.WaitWithDefaultTimeout()
|
||||||
Expect(session.ExitCode()).To(Equal(0))
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
session = podmanTest.Podman([]string{"diff", "diff-test"})
|
if !IsRemote() {
|
||||||
|
session = podmanTest.Podman([]string{"diff", "-l"})
|
||||||
|
} else {
|
||||||
|
session = podmanTest.Podman([]string{"diff", "diff-test"})
|
||||||
|
}
|
||||||
session.WaitWithDefaultTimeout()
|
session.WaitWithDefaultTimeout()
|
||||||
containerDiff := session.OutputToStringArray()
|
containerDiff := session.OutputToStringArray()
|
||||||
sort.Strings(containerDiff)
|
sort.Strings(containerDiff)
|
||||||
|
|
@ -92,4 +91,101 @@ var _ = Describe("Podman diff", func() {
|
||||||
Expect(session.LineInOutputContains("A /tmp/diff-test")).To(BeTrue())
|
Expect(session.LineInOutputContains("A /tmp/diff-test")).To(BeTrue())
|
||||||
Expect(session.ExitCode()).To(Equal(0))
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("podman image diff", func() {
|
||||||
|
file1 := "/" + stringid.GenerateNonCryptoID()
|
||||||
|
file2 := "/" + stringid.GenerateNonCryptoID()
|
||||||
|
file3 := "/" + stringid.GenerateNonCryptoID()
|
||||||
|
|
||||||
|
// Create container image with the files
|
||||||
|
containerfile := fmt.Sprintf(`
|
||||||
|
FROM %s
|
||||||
|
RUN touch %s
|
||||||
|
RUN touch %s
|
||||||
|
RUN touch %s`, ALPINE, file1, file2, file3)
|
||||||
|
|
||||||
|
image := "podman-diff-test"
|
||||||
|
podmanTest.BuildImage(containerfile, image, "true")
|
||||||
|
|
||||||
|
// build a second image which used as base to compare against
|
||||||
|
// using ALPINE does not work in CI, most likely due the extra vfs.imagestore
|
||||||
|
containerfile = fmt.Sprintf(`
|
||||||
|
FROM %s
|
||||||
|
RUN echo test
|
||||||
|
`, ALPINE)
|
||||||
|
baseImage := "base-image"
|
||||||
|
podmanTest.BuildImage(containerfile, baseImage, "true")
|
||||||
|
|
||||||
|
session := podmanTest.Podman([]string{"image", "diff", image})
|
||||||
|
session.WaitWithDefaultTimeout()
|
||||||
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
|
Expect(len(session.OutputToStringArray())).To(BeNumerically("==", 1))
|
||||||
|
Expect(session.OutputToString()).To(Equal("A " + file3))
|
||||||
|
|
||||||
|
session = podmanTest.Podman([]string{"image", "diff", image, baseImage})
|
||||||
|
session.WaitWithDefaultTimeout()
|
||||||
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
|
Expect(len(session.OutputToStringArray())).To(BeNumerically("==", 4))
|
||||||
|
Expect(session.LineInOutputContains("A " + file1)).To(BeTrue())
|
||||||
|
Expect(session.LineInOutputContains("A " + file2)).To(BeTrue())
|
||||||
|
Expect(session.LineInOutputContains("A " + file3)).To(BeTrue())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("podman image diff of single image", func() {
|
||||||
|
session := podmanTest.Podman([]string{"image", "diff", BB})
|
||||||
|
session.WaitWithDefaultTimeout()
|
||||||
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
|
Expect(len(session.OutputToStringArray())).To(BeNumerically(">", 0))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("podman image diff bogus image", func() {
|
||||||
|
session := podmanTest.Podman([]string{"image", "diff", "1234", ALPINE})
|
||||||
|
session.WaitWithDefaultTimeout()
|
||||||
|
Expect(session.ExitCode()).To(Equal(125))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("podman image diff of the same image", func() {
|
||||||
|
session := podmanTest.Podman([]string{"image", "diff", ALPINE, ALPINE})
|
||||||
|
session.WaitWithDefaultTimeout()
|
||||||
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
|
Expect(len(session.OutputToStringArray())).To(BeNumerically("==", 0))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("podman diff container and image with same name", func() {
|
||||||
|
imagefile := "/" + stringid.GenerateNonCryptoID()
|
||||||
|
confile := "/" + stringid.GenerateNonCryptoID()
|
||||||
|
|
||||||
|
// Create container image with the files
|
||||||
|
containerfile := fmt.Sprintf(`
|
||||||
|
FROM %s
|
||||||
|
RUN touch %s`, ALPINE, imagefile)
|
||||||
|
|
||||||
|
name := "podman-diff-test"
|
||||||
|
podmanTest.BuildImage(containerfile, name, "false")
|
||||||
|
|
||||||
|
session := podmanTest.Podman([]string{"run", "--name", name, ALPINE, "touch", confile})
|
||||||
|
session.WaitWithDefaultTimeout()
|
||||||
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
|
|
||||||
|
// podman diff prefers image over container when they have the same name
|
||||||
|
session = podmanTest.Podman([]string{"diff", name})
|
||||||
|
session.WaitWithDefaultTimeout()
|
||||||
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
|
Expect(len(session.OutputToStringArray())).To(BeNumerically("==", 2))
|
||||||
|
Expect(session.OutputToString()).To(ContainSubstring(imagefile))
|
||||||
|
|
||||||
|
session = podmanTest.Podman([]string{"image", "diff", name})
|
||||||
|
session.WaitWithDefaultTimeout()
|
||||||
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
|
Expect(len(session.OutputToStringArray())).To(BeNumerically("==", 2))
|
||||||
|
Expect(session.OutputToString()).To(ContainSubstring(imagefile))
|
||||||
|
|
||||||
|
// container diff has to show the container
|
||||||
|
session = podmanTest.Podman([]string{"container", "diff", name})
|
||||||
|
session.WaitWithDefaultTimeout()
|
||||||
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
|
Expect(len(session.OutputToStringArray())).To(BeNumerically("==", 2))
|
||||||
|
Expect(session.OutputToString()).To(ContainSubstring(confile))
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue