diff options
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)) + }) + }) |