aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Holzinger <pholzing@redhat.com>2021-07-01 15:24:20 +0200
committerPaul Holzinger <pholzing@redhat.com>2021-07-02 17:11:56 +0200
commit8f6a0243f4b7f861a116c0dba5967b3cfe23d61f (patch)
tree4525ea01079f4f61104e34b5f19b0a823c17f6a0
parentfd1715568b7c14451dcf2581c385c8d3e307d30e (diff)
downloadpodman-8f6a0243f4b7f861a116c0dba5967b3cfe23d61f.tar.gz
podman-8f6a0243f4b7f861a116c0dba5967b3cfe23d61f.tar.bz2
podman-8f6a0243f4b7f861a116c0dba5967b3cfe23d61f.zip
podman diff accept two images or containers
First, make podman diff accept optionally a second argument. This allows the user to specify a second image/container to compare the first with. If it is not set the parent layer will be used as before. Second, podman container diff should only use containers and podman image diff should only use images. Previously, podman container diff would use the image when both an image and container with this name exists. To make this work two new parameters have been added to the api. If they are not used the previous behaviour is used. The same applies to the bindings. Fixes #10649 Signed-off-by: Paul Holzinger <pholzing@redhat.com>
-rw-r--r--cmd/podman/containers/diff.go42
-rw-r--r--cmd/podman/diff.go42
-rw-r--r--cmd/podman/diff/diff.go79
-rw-r--r--cmd/podman/images/diff.go40
-rw-r--r--docs/source/markdown/links/podman-container-diff.11
-rw-r--r--docs/source/markdown/podman-container-diff.1.md54
-rw-r--r--docs/source/markdown/podman-container.1.md2
-rw-r--r--docs/source/markdown/podman-diff.1.md32
-rw-r--r--docs/source/markdown/podman-image-diff.1.md18
-rw-r--r--libpod/container_internal_linux.go2
-rw-r--r--libpod/define/diff.go26
-rw-r--r--libpod/diff.go44
-rw-r--r--pkg/api/handlers/compat/changes.go27
-rw-r--r--pkg/api/server/register_containers.go9
-rw-r--r--pkg/api/server/register_images.go11
-rw-r--r--pkg/bindings/containers/diff.go7
-rw-r--r--pkg/bindings/containers/types.go7
-rw-r--r--pkg/bindings/containers/types_diff_options.go32
-rw-r--r--pkg/bindings/images/types.go4
-rw-r--r--pkg/bindings/images/types_diff_options.go32
-rw-r--r--pkg/domain/entities/engine_container.go2
-rw-r--r--pkg/domain/entities/engine_image.go1
-rw-r--r--pkg/domain/entities/types.go8
-rw-r--r--pkg/domain/infra/abi/containers.go18
-rw-r--r--pkg/domain/infra/abi/images.go8
-rw-r--r--pkg/domain/infra/tunnel/containers.go14
-rw-r--r--pkg/domain/infra/tunnel/images.go10
-rw-r--r--test/e2e/diff_test.go112
28 files changed, 512 insertions, 172 deletions
diff --git a/cmd/podman/containers/diff.go b/cmd/podman/containers/diff.go
index 0eee85825..7463e96ad 100644
--- a/cmd/podman/containers/diff.go
+++ b/cmd/podman/containers/diff.go
@@ -1,10 +1,11 @@
package containers
import (
- "github.com/containers/common/pkg/report"
"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/validate"
+ "github.com/containers/podman/v3/libpod/define"
"github.com/containers/podman/v3/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@@ -13,11 +14,11 @@ import (
var (
// podman container _diff_
diffCmd = &cobra.Command{
- Use: "diff [options] CONTAINER",
- Args: validate.IDOrLatestArgs,
+ Use: "diff [options] CONTAINER [CONTAINER]",
+ Args: diff.ValidateContainerDiffArgs,
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.`,
- RunE: diff,
+ Long: `Displays changes to the container filesystem's'. The container will be compared to its parent layer or the second argument when given.`,
+ RunE: diffRun,
ValidArgsFunction: common.AutocompleteContainers,
Example: `podman container diff myCtr
podman container diff -l --format json myCtr`,
@@ -33,41 +34,22 @@ func init() {
diffOpts = &entities.DiffOptions{}
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.MarkHidden("archive")
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))
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 {
return errors.New("container must be specified: podman container diff [options [...]] ID-NAME")
}
-
- var id string
- 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)
+ diffOpts.Type = define.DiffContainer
+ return diff.Diff(cmd, args, *diffOpts)
}
diff --git a/cmd/podman/diff.go b/cmd/podman/diff.go
index e2a3cd727..c592b6a22 100644
--- a/cmd/podman/diff.go
+++ b/cmd/podman/diff.go
@@ -1,13 +1,11 @@
package main
import (
- "fmt"
-
"github.com/containers/podman/v3/cmd/podman/common"
- "github.com/containers/podman/v3/cmd/podman/containers"
- "github.com/containers/podman/v3/cmd/podman/images"
+ "github.com/containers/podman/v3/cmd/podman/diff"
"github.com/containers/podman/v3/cmd/podman/registry"
"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/spf13/cobra"
)
@@ -16,13 +14,13 @@ import (
var (
// 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{
- Use: "diff [options] {CONTAINER|IMAGE}",
- Args: validate.IDOrLatestArgs,
+ Use: "diff [options] {CONTAINER|IMAGE} [{CONTAINER|IMAGE}]",
+ Args: diff.ValidateContainerDiffArgs,
Short: "Display the changes to the object's file system",
Long: diffDescription,
- RunE: diff,
+ RunE: diffRun,
ValidArgsFunction: common.AutocompleteContainersAndImages,
Example: `podman diff imageID
podman diff ctrID
@@ -37,36 +35,18 @@ func init() {
Command: diffCmd,
})
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.MarkHidden("archive")
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))
validate.AddLatestFlag(diffCmd, &diffOpts.Latest)
}
-func diff(cmd *cobra.Command, args []string) error {
- // Latest implies looking for a container
- if diffOpts.Latest {
- 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])
+func diffRun(cmd *cobra.Command, args []string) error {
+ diffOpts.Type = define.DiffAll
+ return diff.Diff(cmd, args, diffOpts)
}
diff --git a/cmd/podman/diff/diff.go b/cmd/podman/diff/diff.go
new file mode 100644
index 000000000..81bbb6c43
--- /dev/null
+++ b/cmd/podman/diff/diff.go
@@ -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
+}
diff --git a/cmd/podman/images/diff.go b/cmd/podman/images/diff.go
index 2e883d7ae..825aab362 100644
--- a/cmd/podman/images/diff.go
+++ b/cmd/podman/images/diff.go
@@ -1,11 +1,11 @@
package images
import (
- "github.com/containers/common/pkg/report"
"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/libpod/define"
"github.com/containers/podman/v3/pkg/domain/entities"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
@@ -13,11 +13,11 @@ import (
var (
// podman container _inspect_
diffCmd = &cobra.Command{
- Use: "diff [options] IMAGE",
- Args: cobra.ExactArgs(1),
+ Use: "diff [options] IMAGE [IMAGE]",
+ Args: cobra.RangeArgs(1, 2),
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.`,
- RunE: diff,
+ Long: `Displays changes to the image's filesystem. The image will be compared to its parent layer or the second argument when given.`,
+ RunE: diffRun,
ValidArgsFunction: common.AutocompleteImages,
Example: `podman image diff myImage
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.")
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))
}
-func diff(cmd *cobra.Command, args []string) error {
- if diffOpts.Latest {
- return errors.New("image diff does not support --latest")
- }
-
- 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)
+func diffRun(cmd *cobra.Command, args []string) error {
+ diffOpts.Type = define.DiffImage
+ return diff.Diff(cmd, args, *diffOpts)
}
diff --git a/docs/source/markdown/links/podman-container-diff.1 b/docs/source/markdown/links/podman-container-diff.1
deleted file mode 100644
index ac4881f98..000000000
--- a/docs/source/markdown/links/podman-container-diff.1
+++ /dev/null
@@ -1 +0,0 @@
-.so man1/podman-diff.1
diff --git a/docs/source/markdown/podman-container-diff.1.md b/docs/source/markdown/podman-container-diff.1.md
new file mode 100644
index 000000000..89a749fbd
--- /dev/null
+++ b/docs/source/markdown/podman-container-diff.1.md
@@ -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>
diff --git a/docs/source/markdown/podman-container.1.md b/docs/source/markdown/podman-container.1.md
index e85d69c59..e69c5a170 100644
--- a/docs/source/markdown/podman-container.1.md
+++ b/docs/source/markdown/podman-container.1.md
@@ -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. |
| 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. |
-| 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. |
| 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. |
diff --git a/docs/source/markdown/podman-diff.1.md b/docs/source/markdown/podman-diff.1.md
index dbab2d4db..fd574abb1 100644
--- a/docs/source/markdown/podman-diff.1.md
+++ b/docs/source/markdown/podman-diff.1.md
@@ -4,18 +4,24 @@
podman\-diff - Inspect changes on a container or image's filesystem
## SYNOPSIS
-**podman diff** [*options*] *name*
-
-**podman container diff** [*options*] *name*
+**podman diff** [*options*] *container|image* [*container|image*]
## 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
#### **--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**
@@ -25,15 +31,12 @@ to run containers such as CRI-O, the last started container could be from either
## EXAMPLE
```
-# podman diff redis:alpine
-C /usr
-C /usr/local
-C /usr/local/bin
-A /usr/local/bin/docker-entrypoint.sh
+$ podman diff container1
+A /myscript.sh
```
```
-# podman diff --format json redis:alpine
+$ podman diff --format json myimage
{
"changed": [
"/usr",
@@ -46,8 +49,13 @@ A /usr/local/bin/docker-entrypoint.sh
}
```
+```
+$ podman diff container1 image1
+A /test
+```
+
## 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
August 2017, Originally compiled by Ryan Cole <rycole@redhat.com>
diff --git a/docs/source/markdown/podman-image-diff.1.md b/docs/source/markdown/podman-image-diff.1.md
index 9b1dfa45a..933cd0f9a 100644
--- a/docs/source/markdown/podman-image-diff.1.md
+++ b/docs/source/markdown/podman-image-diff.1.md
@@ -4,21 +4,29 @@
podman-image-diff - Inspect changes on an image's filesystem
## SYNOPSIS
-**podman image diff** [*options*] *name*
+**podman image diff** [*options*] *image* [*image*]
## 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
#### **--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
```
-# podman diff redis:old redis:alpine
+$ podman diff redis:old
C /usr
C /usr/local
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": [
"/usr",
diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go
index 25db3cff0..850af235f 100644
--- a/libpod/container_internal_linux.go
+++ b/libpod/container_internal_linux.go
@@ -923,7 +923,7 @@ func (c *Container) exportCheckpoint(options ContainerCheckpointOptions) error {
var addToTarFiles []string
if !options.IgnoreRootfs {
// 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 {
return errors.Wrapf(err, "error exporting root file-system diff for %q", c.ID())
}
diff --git a/libpod/define/diff.go b/libpod/define/diff.go
new file mode 100644
index 000000000..ee492eb3a
--- /dev/null
+++ b/libpod/define/diff.go
@@ -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"
+ }
+}
diff --git a/libpod/diff.go b/libpod/diff.go
index c5a53478b..5bd162e7b 100644
--- a/libpod/diff.go
+++ b/libpod/diff.go
@@ -2,6 +2,7 @@ package libpod
import (
"github.com/containers/common/libimage"
+ "github.com/containers/podman/v3/libpod/define"
"github.com/containers/podman/v3/libpod/layers"
"github.com/containers/storage/pkg/archive"
"github.com/pkg/errors"
@@ -21,14 +22,14 @@ var initInodes = map[string]bool{
}
// GetDiff returns the differences between the two images, layers, or containers
-func (r *Runtime) GetDiff(from, to string) ([]archive.Change, error) {
- toLayer, err := r.getLayerID(to)
+func (r *Runtime) GetDiff(from, to string, diffType define.DiffType) ([]archive.Change, error) {
+ toLayer, err := r.getLayerID(to, diffType)
if err != nil {
return nil, err
}
fromLayer := ""
if from != "" {
- fromLayer, err = r.getLayerID(from)
+ fromLayer, err = r.getLayerID(from, diffType)
if err != nil {
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
// 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
-func (r *Runtime) getLayerID(id string) (string, error) {
- var toLayer string
- toImage, _, err := r.libimageRuntime.LookupImage(id, &libimage.LookupImageOptions{IgnorePlatform: true})
- if err == nil {
- return toImage.TopLayer(), nil
+func (r *Runtime) getLayerID(id string, diffType define.DiffType) (string, error) {
+ var lastErr error
+ if diffType&define.DiffImage == define.DiffImage {
+ toImage, _, err := r.libimageRuntime.LookupImage(id, &libimage.LookupImageOptions{IgnorePlatform: true})
+ if err == nil {
+ return toImage.TopLayer(), nil
+ }
+ lastErr = err
}
- targetID, err := r.store.Lookup(id)
- if err != nil {
- targetID = id
+ if diffType&define.DiffContainer == define.DiffContainer {
+ toCtr, err := r.store.Container(id)
+ if err == nil {
+ return toCtr.LayerID, nil
+ }
+ lastErr = err
}
- 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)
+
+ if diffType == define.DiffAll {
+ toLayer, err := layers.FullID(r.store, id)
+ if err == nil {
+ return toLayer, nil
}
- } else {
- toLayer = toCtr.LayerID
+ lastErr = err
}
- return toLayer, nil
+ return "", errors.Wrapf(lastErr, "%s not found", id)
}
diff --git a/pkg/api/handlers/compat/changes.go b/pkg/api/handlers/compat/changes.go
index c442abbf9..dfe656755 100644
--- a/pkg/api/handlers/compat/changes.go
+++ b/pkg/api/handlers/compat/changes.go
@@ -4,14 +4,39 @@ import (
"net/http"
"github.com/containers/podman/v3/libpod"
+ "github.com/containers/podman/v3/libpod/define"
"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) {
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
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)
- changes, err := runtime.GetDiff("", id)
+ changes, err := runtime.GetDiff(query.Parent, id, diffType)
if err != nil {
utils.InternalServerError(w, err)
return
diff --git a/pkg/api/server/register_containers.go b/pkg/api/server/register_containers.go
index 88ebb4df5..50e059ecc 100644
--- a/pkg/api/server/register_containers.go
+++ b/pkg/api/server/register_containers.go
@@ -1505,6 +1505,15 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
// type: string
// required: true
// 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:
// 200:
// description: Array of Changes
diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go
index 3410c53cd..2103c093c 100644
--- a/pkg/api/server/register_images.go
+++ b/pkg/api/server/register_images.go
@@ -1286,7 +1286,16 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// name: name
// type: string
// 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:
// 200:
// description: Array of Changes
diff --git a/pkg/bindings/containers/diff.go b/pkg/bindings/containers/diff.go
index 0d0516044..7d20ae530 100644
--- a/pkg/bindings/containers/diff.go
+++ b/pkg/bindings/containers/diff.go
@@ -13,13 +13,16 @@ func Diff(ctx context.Context, nameOrID string, options *DiffOptions) ([]archive
if options == nil {
options = new(DiffOptions)
}
- _ = options
conn, err := bindings.GetClient(ctx)
if err != nil {
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 {
return nil, err
}
diff --git a/pkg/bindings/containers/types.go b/pkg/bindings/containers/types.go
index 0d22c32f8..80f382926 100644
--- a/pkg/bindings/containers/types.go
+++ b/pkg/bindings/containers/types.go
@@ -70,7 +70,12 @@ type CreateOptions struct{}
//go:generate go run ../generator/generator.go DiffOptions
// 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
// ExecInspectOptions are optional options for inspecting
diff --git a/pkg/bindings/containers/types_diff_options.go b/pkg/bindings/containers/types_diff_options.go
index ed356335d..e92594d39 100644
--- a/pkg/bindings/containers/types_diff_options.go
+++ b/pkg/bindings/containers/types_diff_options.go
@@ -19,3 +19,35 @@ func (o *DiffOptions) Changed(fieldName string) bool {
func (o *DiffOptions) ToParams() (url.Values, error) {
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
+}
diff --git a/pkg/bindings/images/types.go b/pkg/bindings/images/types.go
index 0aa75a81e..801f5ed96 100644
--- a/pkg/bindings/images/types.go
+++ b/pkg/bindings/images/types.go
@@ -16,6 +16,10 @@ type RemoveOptions struct {
//go:generate go run ../generator/generator.go DiffOptions
// DiffOptions are optional options image diffs
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
diff --git a/pkg/bindings/images/types_diff_options.go b/pkg/bindings/images/types_diff_options.go
index f15a9a696..5492323f6 100644
--- a/pkg/bindings/images/types_diff_options.go
+++ b/pkg/bindings/images/types_diff_options.go
@@ -19,3 +19,35 @@ func (o *DiffOptions) Changed(fieldName string) bool {
func (o *DiffOptions) ToParams() (url.Values, error) {
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
+}
diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go
index 4cd3cfd2a..ec17d7ce3 100644
--- a/pkg/domain/entities/engine_container.go
+++ b/pkg/domain/entities/engine_container.go
@@ -23,7 +23,6 @@ type ContainerEngine interface {
ContainerCopyFromArchive(ctx context.Context, nameOrID string, path string, reader io.Reader) (ContainerCopyFunc, error)
ContainerCopyToArchive(ctx context.Context, nameOrID string, path string, writer io.Writer) (ContainerCopyFunc, 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)
ContainerExecDetached(ctx context.Context, nameOrID string, options ExecOptions) (string, 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)
ContainerUnpause(ctx context.Context, namesOrIds []string, options PauseUnPauseOptions) ([]*PauseUnpauseReport, 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
GenerateSystemd(ctx context.Context, nameOrID string, opts GenerateSystemdOptions) (*GenerateSystemdReport, error)
GenerateKube(ctx context.Context, nameOrIDs []string, opts GenerateKubeOptions) (*GenerateKubeReport, error)
diff --git a/pkg/domain/entities/engine_image.go b/pkg/domain/entities/engine_image.go
index 1b2de5d5e..b0f9ae408 100644
--- a/pkg/domain/entities/engine_image.go
+++ b/pkg/domain/entities/engine_image.go
@@ -10,7 +10,6 @@ import (
type ImageEngine interface {
Build(ctx context.Context, containerFiles []string, opts BuildOptions) (*BuildReport, 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)
History(ctx context.Context, nameOrID string, opts ImageHistoryOptions) (*ImageHistoryReport, error)
Import(ctx context.Context, opts ImageImportOptions) (*ImageImportReport, error)
diff --git a/pkg/domain/entities/types.go b/pkg/domain/entities/types.go
index 02e374111..9e25b7bf8 100644
--- a/pkg/domain/entities/types.go
+++ b/pkg/domain/entities/types.go
@@ -4,6 +4,7 @@ import (
"net"
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/pkg/specgen"
"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
type DiffOptions struct {
- Format string `json:",omitempty"` // CLI only
- Latest bool `json:",omitempty"` // API and CLI, only supported by containers
- Archive bool `json:",omitempty"` // CLI only
+ Format string `json:",omitempty"` // CLI only
+ Latest bool `json:",omitempty"` // API and CLI, only supported by containers
+ Archive bool `json:",omitempty"` // CLI only
+ Type define.DiffType // Type which should be compared
}
// DiffReport provides changes for object
diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go
index 47fa553ce..2c5300ccb 100644
--- a/pkg/domain/infra/abi/containers.go
+++ b/pkg/domain/infra/abi/containers.go
@@ -858,16 +858,26 @@ func (ic *ContainerEngine) ContainerListExternal(ctx context.Context) ([]entitie
return ps.GetExternalContainerLists(ic.Libpod)
}
-// ContainerDiff provides changes to given container
-func (ic *ContainerEngine) ContainerDiff(ctx context.Context, nameOrID string, opts entities.DiffOptions) (*entities.DiffReport, error) {
+// Diff provides changes to given container
+func (ic *ContainerEngine) Diff(ctx context.Context, namesOrIDs []string, opts entities.DiffOptions) (*entities.DiffReport, error) {
+ var (
+ base string
+ parent string
+ )
if opts.Latest {
ctnr, err := ic.Libpod.GetLatestContainer()
if err != nil {
return nil, errors.Wrap(err, "unable to get latest container")
}
- nameOrID = ctnr.ID()
+ base = ctnr.ID()
+ }
+ if len(namesOrIDs) > 0 {
+ base = namesOrIDs[0]
+ if len(namesOrIDs) > 1 {
+ parent = namesOrIDs[1]
+ }
}
- changes, err := ic.Libpod.GetDiff("", nameOrID)
+ changes, err := ic.Libpod.GetDiff(parent, base, opts.Type)
return &entities.DiffReport{Changes: changes}, err
}
diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go
index 5992181d3..6d1acb590 100644
--- a/pkg/domain/infra/abi/images.go
+++ b/pkg/domain/infra/abi/images.go
@@ -403,14 +403,6 @@ func (ir *ImageEngine) Import(ctx context.Context, options entities.ImageImportO
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) {
filter, err := libimage.ParseSearchFilter(opts.Filters)
if err != nil {
diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go
index ccebfffa4..1bee521cf 100644
--- a/pkg/domain/infra/tunnel/containers.go
+++ b/pkg/domain/infra/tunnel/containers.go
@@ -765,8 +765,18 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta
return &report, err
}
-func (ic *ContainerEngine) ContainerDiff(ctx context.Context, nameOrID string, _ entities.DiffOptions) (*entities.DiffReport, error) {
- changes, err := containers.Diff(ic.ClientCtx, nameOrID, nil)
+func (ic *ContainerEngine) Diff(ctx context.Context, namesOrIDs []string, opts entities.DiffOptions) (*entities.DiffReport, error) {
+ 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
}
diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go
index 42027a2dc..db4e14aba 100644
--- a/pkg/domain/infra/tunnel/images.go
+++ b/pkg/domain/infra/tunnel/images.go
@@ -299,16 +299,6 @@ func (ir *ImageEngine) Save(ctx context.Context, nameOrID string, tags []string,
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) {
mappedFilters := make(map[string][]string)
filters, err := libimage.ParseSearchFilter(opts.Filters)
diff --git a/test/e2e/diff_test.go b/test/e2e/diff_test.go
index 4370a2127..cf955772b 100644
--- a/test/e2e/diff_test.go
+++ b/test/e2e/diff_test.go
@@ -1,10 +1,12 @@
package integration
import (
+ "fmt"
"os"
"sort"
. "github.com/containers/podman/v3/test/utils"
+ "github.com/containers/storage/pkg/stringid"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
@@ -40,13 +42,6 @@ var _ = Describe("Podman diff", func() {
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() {
session := podmanTest.Podman([]string{"diff", "1234"})
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.WaitWithDefaultTimeout()
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()
containerDiff := session.OutputToStringArray()
sort.Strings(containerDiff)
@@ -92,4 +91,101 @@ var _ = Describe("Podman diff", func() {
Expect(session.LineInOutputContains("A /tmp/diff-test")).To(BeTrue())
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))
+ })
+
})