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>
This commit is contained in:
Paul Holzinger
2021-07-01 15:24:20 +02:00
parent fd1715568b
commit 8f6a0243f4
28 changed files with 513 additions and 173 deletions

View File

@ -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)
}

View File

@ -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)
}

79
cmd/podman/diff/diff.go Normal file
View File

@ -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
}

View File

@ -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)
}

View File

@ -1 +0,0 @@
.so man1/podman-diff.1

View File

@ -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>

View File

@ -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. |

View File

@ -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>

View File

@ -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",

View File

@ -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())
}

26
libpod/define/diff.go Normal file
View File

@ -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"
}
}

View File

@ -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
}
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.DiffContainer == define.DiffContainer {
toCtr, err := r.store.Container(id)
if err == nil {
return toCtr.LayerID, nil
}
} else {
toLayer = toCtr.LayerID
lastErr = err
}
return toLayer, nil
if diffType == define.DiffAll {
toLayer, err := layers.FullID(r.store, id)
if err == nil {
return toLayer, nil
}
lastErr = err
}
return "", errors.Wrapf(lastErr, "%s not found", id)
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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()
}
changes, err := ic.Libpod.GetDiff("", nameOrID)
if len(namesOrIDs) > 0 {
base = namesOrIDs[0]
if len(namesOrIDs) > 1 {
parent = namesOrIDs[1]
}
}
changes, err := ic.Libpod.GetDiff(parent, base, opts.Type)
return &entities.DiffReport{Changes: changes}, err
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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)

View File

@ -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))
})
})