aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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))
+ })
+
})