From 63e46cc85cb0a9523e9c48db7a88039e0baeac29 Mon Sep 17 00:00:00 2001 From: Sascha Grunert Date: Tue, 26 Nov 2019 16:08:04 +0100 Subject: Add support for image name history We leverage the containers/storage image history tracking feature to show the previously used image names when running: `podman images --history` Signed-off-by: Sascha Grunert --- API.md | 2 + cmd/podman/cliconfig/config.go | 1 + cmd/podman/images.go | 10 ++ cmd/podman/varlink/io.podman.varlink | 3 +- completions/bash/podman | 1 + docs/source/markdown/podman-images.1.md | 4 + go.mod | 2 +- go.sum | 2 + libpod/image/image.go | 15 +++ pkg/adapter/runtime_remote.go | 67 +++++++------ pkg/varlinkapi/images.go | 2 + test/system/010-images.bats | 15 +++ vendor/github.com/containers/storage/VERSION | 2 +- .../containers/storage/drivers/copy/copy_linux.go | 16 ++- .../containers/storage/drivers/vfs/copy_linux.go | 2 +- vendor/github.com/containers/storage/images.go | 11 +++ .../github.com/containers/storage/images_ffjson.go | 108 +++++++++++++++++++++ .../containers/storage/pkg/archive/archive.go | 29 +++++- .../containers/storage/pkg/archive/changes.go | 5 +- .../storage/pkg/archive/changes_linux.go | 22 ++++- .../containers/storage/pkg/system/xattrs_linux.go | 41 +++++++- .../storage/pkg/system/xattrs_unsupported.go | 12 +++ vendor/github.com/containers/storage/utils.go | 12 +++ vendor/modules.txt | 2 +- 24 files changed, 346 insertions(+), 40 deletions(-) diff --git a/API.md b/API.md index c288e6b28..a02a8cf6f 100755 --- a/API.md +++ b/API.md @@ -1698,6 +1698,8 @@ isParent [bool](https://godoc.org/builtin#bool) topLayer [string](https://godoc.org/builtin#string) readOnly [bool](https://godoc.org/builtin#bool) + +history [[]string](#[]string) ### type ImageHistory ImageHistory describes the returned structure from ImageHistory. diff --git a/cmd/podman/cliconfig/config.go b/cmd/podman/cliconfig/config.go index 27d94f769..a34afa827 100644 --- a/cmd/podman/cliconfig/config.go +++ b/cmd/podman/cliconfig/config.go @@ -64,6 +64,7 @@ type ImagesValues struct { NoTrunc bool Quiet bool Sort string + History bool } type EventValues struct { diff --git a/cmd/podman/images.go b/cmd/podman/images.go index 7d498517c..6b16272f4 100644 --- a/cmd/podman/images.go +++ b/cmd/podman/images.go @@ -32,6 +32,7 @@ type imagesTemplateParams struct { CreatedTime time.Time Size string ReadOnly bool + History string } type imagesJSONParams struct { @@ -42,6 +43,7 @@ type imagesJSONParams struct { Created time.Time `json:"created"` Size *uint64 `json:"size"` ReadOnly bool `json:"readonly"` + History []string `json:"history"` } type imagesOptions struct { @@ -53,6 +55,7 @@ type imagesOptions struct { outputformat string sort string all bool + history bool } // Type declaration and functions for sorting the images output @@ -124,6 +127,7 @@ func imagesInit(command *cliconfig.ImagesValues) { flags.BoolVar(&command.NoTrunc, "no-trunc", false, "Do not truncate output") flags.BoolVarP(&command.Quiet, "quiet", "q", false, "Display only image IDs") flags.StringVar(&command.Sort, "sort", "created", "Sort by created, id, repository, size, or tag") + flags.BoolVarP(&command.History, "history", "", false, "Display the image name history") } @@ -171,6 +175,7 @@ func imagesCmd(c *cliconfig.ImagesValues) error { format: c.Format, sort: c.Sort, all: c.All, + history: c.History, } opts.outputformat = opts.setOutputFormat() @@ -214,6 +219,9 @@ func (i imagesOptions) setOutputFormat() string { format += "{{.Digest}}\t" } format += "{{.ID}}\t{{.Created}}\t{{.Size}}\t" + if i.history { + format += "{{if .History}}{{.History}}{{else}}{{end}}\t" + } return format } @@ -306,6 +314,7 @@ func getImagesTemplateOutput(ctx context.Context, images []*adapter.ContainerIma Created: units.HumanDuration(time.Since(createdTime)) + " ago", Size: sizeStr, ReadOnly: img.IsReadOnly(), + History: strings.Join(img.NamesHistory(), ", "), } imagesOutput = append(imagesOutput, params) if opts.quiet { // Show only one image ID when quiet @@ -336,6 +345,7 @@ func getImagesJSONOutput(ctx context.Context, images []*adapter.ContainerImage) Created: img.Created(), Size: size, ReadOnly: img.IsReadOnly(), + History: img.NamesHistory(), } imagesOutput = append(imagesOutput, params) } diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index 4f810dd53..e76b9627e 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -70,7 +70,8 @@ type Image ( labels: [string]string, isParent: bool, topLayer: string, - readOnly: bool + readOnly: bool, + history: []string ) # ImageHistory describes the returned structure from ImageHistory. diff --git a/completions/bash/podman b/completions/bash/podman index 7b64c2a80..6d145030f 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -1563,6 +1563,7 @@ _podman_images() { --filter -h --help + --history --no-trunc --notruncate -n diff --git a/docs/source/markdown/podman-images.1.md b/docs/source/markdown/podman-images.1.md index 3ac07fc43..21fca1dbd 100644 --- a/docs/source/markdown/podman-images.1.md +++ b/docs/source/markdown/podman-images.1.md @@ -52,6 +52,10 @@ Filter output based on conditions provided Change the default output format. This can be of a supported type like 'json' or a Go template. +**--history** + +Display the history of image names. If an image gets re-tagged or untagged, then the image name history gets prepended (latest image first). This is especially useful when undoing a tag operation or an image does not contain any name because it has been untagged. + **--noheading**, **-n** Omit the table headings from the listing of images. diff --git a/go.mod b/go.mod index 5760f910b..83eebaa9c 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/containers/conmon v2.0.2+incompatible // indirect github.com/containers/image/v5 v5.0.0 github.com/containers/psgo v1.3.2 - github.com/containers/storage v1.14.0 + github.com/containers/storage v1.15.0 github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect github.com/cri-o/ocicni v0.1.1-0.20190920040751-deac903fd99b diff --git a/go.sum b/go.sum index 23091d495..142a23c29 100644 --- a/go.sum +++ b/go.sum @@ -90,6 +90,8 @@ github.com/containers/storage v1.13.5 h1:/SUzGeOP2HDijpF7Yur21Ch6WTZC1BNeZF917CW github.com/containers/storage v1.13.5/go.mod h1:HELz8Sn+UVbPaUZMI8RvIG9doD4y4z6Gtg4k7xdd2ZY= github.com/containers/storage v1.14.0 h1:LbX6WZaDmkXt4DT4xWIg3YXAWd6oA4K9Fi6/KG1xt84= github.com/containers/storage v1.14.0/go.mod h1:qGPsti/qC1xxX+xcpHfiTMT+8ThVE2Jf83wFHHqkDAY= +github.com/containers/storage v1.15.0 h1:QNW7jJ94ccGcAbFIOSMHUAsUxvHceb71ecLye9EDrkk= +github.com/containers/storage v1.15.0/go.mod h1:qGPsti/qC1xxX+xcpHfiTMT+8ThVE2Jf83wFHHqkDAY= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-iptables v0.4.2 h1:KH0EwId05JwWIfb96gWvkiT2cbuOu8ygqUaB+yPAwIg= diff --git a/libpod/image/image.go b/libpod/image/image.go index fa75be44d..129ccd376 100644 --- a/libpod/image/image.go +++ b/libpod/image/image.go @@ -335,6 +335,21 @@ func (i *Image) Names() []string { return i.image.Names } +// NamesHistory returns a string array of names previously associated with the +// image, which may be a mixture of tags and digests +func (i *Image) NamesHistory() []string { + if len(i.image.Names) > 0 && len(i.image.NamesHistory) > 0 && + // We compare the latest (time-referenced) tags for equality and skip + // it in the history if they match to not display them twice. We have + // to compare like this, because `i.image.Names` (latest last) gets + // appended on retag, whereas `i.image.NamesHistory` gets prepended + // (latest first) + i.image.Names[len(i.image.Names)-1] == i.image.NamesHistory[0] { + return i.image.NamesHistory[1:] + } + return i.image.NamesHistory +} + // RepoTags returns a string array of repotags associated with the image func (i *Image) RepoTags() ([]string, error) { var repoTags []string diff --git a/pkg/adapter/runtime_remote.go b/pkg/adapter/runtime_remote.go index ddd4b5271..f9232897c 100644 --- a/pkg/adapter/runtime_remote.go +++ b/pkg/adapter/runtime_remote.go @@ -136,21 +136,22 @@ type ContainerImage struct { } type remoteImage struct { - ID string - Labels map[string]string - RepoTags []string - RepoDigests []string - Parent string - Size int64 - Created time.Time - InputName string - Names []string - Digest digest.Digest - Digests []digest.Digest - isParent bool - Runtime *LocalRuntime - TopLayer string - ReadOnly bool + ID string + Labels map[string]string + RepoTags []string + RepoDigests []string + Parent string + Size int64 + Created time.Time + InputName string + Names []string + Digest digest.Digest + Digests []digest.Digest + isParent bool + Runtime *LocalRuntime + TopLayer string + ReadOnly bool + NamesHistory []string } // Container ... @@ -232,21 +233,22 @@ func imageInListToContainerImage(i iopodman.Image, name string, runtime *LocalRu digests = append(digests, digest.Digest(d)) } ri := remoteImage{ - InputName: name, - ID: i.Id, - Digest: digest.Digest(i.Digest), - Digests: digests, - Labels: i.Labels, - RepoTags: i.RepoTags, - RepoDigests: i.RepoTags, - Parent: i.ParentId, - Size: i.Size, - Created: created, - Names: i.RepoTags, - isParent: i.IsParent, - Runtime: runtime, - TopLayer: i.TopLayer, - ReadOnly: i.ReadOnly, + InputName: name, + ID: i.Id, + Digest: digest.Digest(i.Digest), + Digests: digests, + Labels: i.Labels, + RepoTags: i.RepoTags, + RepoDigests: i.RepoTags, + Parent: i.ParentId, + Size: i.Size, + Created: created, + Names: i.RepoTags, + isParent: i.IsParent, + Runtime: runtime, + TopLayer: i.TopLayer, + ReadOnly: i.ReadOnly, + NamesHistory: i.History, } return &ContainerImage{ri}, nil } @@ -337,6 +339,11 @@ func (ci *ContainerImage) Names() []string { return ci.remoteImage.Names } +// NamesHistory returns a string array of names previously associated with the image +func (ci *ContainerImage) NamesHistory() []string { + return ci.remoteImage.NamesHistory +} + // Created returns the time the image was created func (ci *ContainerImage) Created() time.Time { return ci.remoteImage.Created diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go index c27088805..7abffa42a 100644 --- a/pkg/varlinkapi/images.go +++ b/pkg/varlinkapi/images.go @@ -70,6 +70,7 @@ func (i *LibpodAPI) ListImages(call iopodman.VarlinkCall) error { Labels: labels, IsParent: isParent, ReadOnly: image.IsReadOnly(), + History: image.NamesHistory(), } imageList = append(imageList, i) } @@ -111,6 +112,7 @@ func (i *LibpodAPI) GetImage(call iopodman.VarlinkCall, id string) error { Labels: labels, TopLayer: newImage.TopLayer(), ReadOnly: newImage.IsReadOnly(), + History: newImage.NamesHistory(), } return call.ReplyGetImage(il) } diff --git a/test/system/010-images.bats b/test/system/010-images.bats index 380623078..543876509 100644 --- a/test/system/010-images.bats +++ b/test/system/010-images.bats @@ -44,4 +44,19 @@ size | [0-9]\\\+ } +@test "podman images - history output" { + run_podman images --format json + actual=$(echo $output | jq -r '.[0].history | length') + is "$actual" "0" + + run_podman tag $PODMAN_TEST_IMAGE_REGISTRY/$PODMAN_TEST_IMAGE_USER/$PODMAN_TEST_IMAGE_NAME:$PODMAN_TEST_IMAGE_TAG test-image + run_podman images --format json + actual=$(echo $output | jq -r '.[1].history | length') + is "$actual" "0" + actual=$(echo $output | jq -r '.[0].history | length') + is "$actual" "1" + actual=$(echo $output | jq -r '.[0].history[0]') + is "$actual" "$PODMAN_TEST_IMAGE_REGISTRY/$PODMAN_TEST_IMAGE_USER/$PODMAN_TEST_IMAGE_NAME:$PODMAN_TEST_IMAGE_TAG" +} + # vim: filetype=sh diff --git a/vendor/github.com/containers/storage/VERSION b/vendor/github.com/containers/storage/VERSION index 850e74240..141f2e805 100644 --- a/vendor/github.com/containers/storage/VERSION +++ b/vendor/github.com/containers/storage/VERSION @@ -1 +1 @@ -1.14.0 +1.15.0 diff --git a/vendor/github.com/containers/storage/drivers/copy/copy_linux.go b/vendor/github.com/containers/storage/drivers/copy/copy_linux.go index e52545d5b..c00b9e47d 100644 --- a/vendor/github.com/containers/storage/drivers/copy/copy_linux.go +++ b/vendor/github.com/containers/storage/drivers/copy/copy_linux.go @@ -16,6 +16,7 @@ import ( "io" "os" "path/filepath" + "strings" "syscall" "time" @@ -97,7 +98,7 @@ func legacyCopy(srcFile io.Reader, dstFile io.Writer) error { func copyXattr(srcPath, dstPath, attr string) error { data, err := system.Lgetxattr(srcPath, attr) - if err != nil { + if err != nil && err != unix.EOPNOTSUPP { return err } if data != nil { @@ -271,6 +272,19 @@ func doCopyXattrs(srcPath, dstPath string) error { return err } + xattrs, err := system.Llistxattr(srcPath) + if err != nil && err != unix.EOPNOTSUPP { + return err + } + + for _, key := range xattrs { + if strings.HasPrefix(key, "user.") { + if err := copyXattr(srcPath, dstPath, key); err != nil { + return err + } + } + } + // We need to copy this attribute if it appears in an overlay upper layer, as // this function is used to copy those. It is set by overlay if a directory // is removed and then re-created and should not inherit anything from the diff --git a/vendor/github.com/containers/storage/drivers/vfs/copy_linux.go b/vendor/github.com/containers/storage/drivers/vfs/copy_linux.go index 8137fcf67..bf22a5f6f 100644 --- a/vendor/github.com/containers/storage/drivers/vfs/copy_linux.go +++ b/vendor/github.com/containers/storage/drivers/vfs/copy_linux.go @@ -3,5 +3,5 @@ package vfs import "github.com/containers/storage/drivers/copy" func dirCopy(srcDir, dstDir string) error { - return copy.DirCopy(srcDir, dstDir, copy.Content, false) + return copy.DirCopy(srcDir, dstDir, copy.Content, true) } diff --git a/vendor/github.com/containers/storage/images.go b/vendor/github.com/containers/storage/images.go index 5d6a2e48d..6373ebb41 100644 --- a/vendor/github.com/containers/storage/images.go +++ b/vendor/github.com/containers/storage/images.go @@ -47,6 +47,11 @@ type Image struct { // or canonical references. Names []string `json:"names,omitempty"` + // NamesHistory is an optional set of Names the image had in the past. The + // contained names are free from any duplicates, whereas the newest entry + // is the first one. + NamesHistory []string `json:"names-history,omitempty"` + // TopLayer is the ID of the topmost layer of the image itself, if the // image contains one or more layers. Multiple images can refer to the // same top layer. @@ -155,6 +160,7 @@ func copyImage(i *Image) *Image { Digest: i.Digest, Digests: copyDigestSlice(i.Digests), Names: copyStringSlice(i.Names), + NamesHistory: copyStringSlice(i.NamesHistory), TopLayer: i.TopLayer, MappedTopLayers: copyStringSlice(i.MappedTopLayers), Metadata: i.Metadata, @@ -481,6 +487,10 @@ func (r *imageStore) removeName(image *Image, name string) { image.Names = stringSliceWithoutValue(image.Names, name) } +func (i *Image) addNameToHistory(name string) { + i.NamesHistory = dedupeNames(append([]string{name}, i.NamesHistory...)) +} + func (r *imageStore) SetNames(id string, names []string) error { if !r.IsReadWrite() { return errors.Wrapf(ErrStoreIsReadOnly, "not allowed to change image name assignments at %q", r.imagespath()) @@ -495,6 +505,7 @@ func (r *imageStore) SetNames(id string, names []string) error { r.removeName(otherImage, name) } r.byname[name] = image + image.addNameToHistory(name) } image.Names = names return r.Save() diff --git a/vendor/github.com/containers/storage/images_ffjson.go b/vendor/github.com/containers/storage/images_ffjson.go index 539acfe93..0dde97c18 100644 --- a/vendor/github.com/containers/storage/images_ffjson.go +++ b/vendor/github.com/containers/storage/images_ffjson.go @@ -59,6 +59,22 @@ func (j *Image) MarshalJSONBuf(buf fflib.EncodingBuffer) error { } buf.WriteByte(',') } + if len(j.NamesHistory) != 0 { + buf.WriteString(`"names-history":`) + if j.NamesHistory != nil { + buf.WriteString(`[`) + for i, v := range j.NamesHistory { + if i != 0 { + buf.WriteString(`,`) + } + fflib.WriteJsonString(buf, string(v)) + } + buf.WriteString(`]`) + } else { + buf.WriteString(`null`) + } + buf.WriteByte(',') + } if len(j.TopLayer) != 0 { buf.WriteString(`"layer":`) fflib.WriteJsonString(buf, string(j.TopLayer)) @@ -171,6 +187,8 @@ const ( ffjtImageNames + ffjtImageNamesHistory + ffjtImageTopLayer ffjtImageMappedTopLayers @@ -194,6 +212,8 @@ var ffjKeyImageDigest = []byte("digest") var ffjKeyImageNames = []byte("names") +var ffjKeyImageNamesHistory = []byte("names-history") + var ffjKeyImageTopLayer = []byte("layer") var ffjKeyImageMappedTopLayers = []byte("mapped-layers") @@ -348,6 +368,11 @@ mainparse: currentKey = ffjtImageNames state = fflib.FFParse_want_colon goto mainparse + + } else if bytes.Equal(ffjKeyImageNamesHistory, kn) { + currentKey = ffjtImageNamesHistory + state = fflib.FFParse_want_colon + goto mainparse } } @@ -400,6 +425,12 @@ mainparse: goto mainparse } + if fflib.EqualFoldRight(ffjKeyImageNamesHistory, kn) { + currentKey = ffjtImageNamesHistory + state = fflib.FFParse_want_colon + goto mainparse + } + if fflib.EqualFoldRight(ffjKeyImageNames, kn) { currentKey = ffjtImageNames state = fflib.FFParse_want_colon @@ -444,6 +475,9 @@ mainparse: case ffjtImageNames: goto handle_Names + case ffjtImageNamesHistory: + goto handle_NamesHistory + case ffjtImageTopLayer: goto handle_TopLayer @@ -608,6 +642,80 @@ handle_Names: state = fflib.FFParse_after_value goto mainparse +handle_NamesHistory: + + /* handler: j.NamesHistory type=[]string kind=slice quoted=false*/ + + { + + { + if tok != fflib.FFTok_left_brace && tok != fflib.FFTok_null { + return fs.WrapErr(fmt.Errorf("cannot unmarshal %s into Go value for ", tok)) + } + } + + if tok == fflib.FFTok_null { + j.NamesHistory = nil + } else { + + j.NamesHistory = []string{} + + wantVal := true + + for { + + var tmpJNamesHistory string + + tok = fs.Scan() + if tok == fflib.FFTok_error { + goto tokerror + } + if tok == fflib.FFTok_right_brace { + break + } + + if tok == fflib.FFTok_comma { + if wantVal == true { + // TODO(pquerna): this isn't an ideal error message, this handles + // things like [,,,] as an array value. + return fs.WrapErr(fmt.Errorf("wanted value token, but got token: %v", tok)) + } + continue + } else { + wantVal = true + } + + /* handler: tmpJNamesHistory type=string kind=string quoted=false*/ + + { + + { + if tok != fflib.FFTok_string && tok != fflib.FFTok_null { + return fs.WrapErr(fmt.Errorf("cannot unmarshal %s into Go value for string", tok)) + } + } + + if tok == fflib.FFTok_null { + + } else { + + outBuf := fs.Output.Bytes() + + tmpJNamesHistory = string(string(outBuf)) + + } + } + + j.NamesHistory = append(j.NamesHistory, tmpJNamesHistory) + + wantVal = false + } + } + } + + state = fflib.FFParse_after_value + goto mainparse + handle_TopLayer: /* handler: j.TopLayer type=string kind=string quoted=false*/ diff --git a/vendor/github.com/containers/storage/pkg/archive/archive.go b/vendor/github.com/containers/storage/pkg/archive/archive.go index ba635ae91..d2752ae7c 100644 --- a/vendor/github.com/containers/storage/pkg/archive/archive.go +++ b/vendor/github.com/containers/storage/pkg/archive/archive.go @@ -387,7 +387,10 @@ func fillGo18FileTypeBits(mode int64, fi os.FileInfo) int64 { // ReadSecurityXattrToTarHeader reads security.capability xattr from filesystem // to a tar header func ReadSecurityXattrToTarHeader(path string, hdr *tar.Header) error { - capability, _ := system.Lgetxattr(path, "security.capability") + capability, err := system.Lgetxattr(path, "security.capability") + if err != nil && err != system.EOPNOTSUPP { + return err + } if capability != nil { hdr.Xattrs = make(map[string]string) hdr.Xattrs["security.capability"] = string(capability) @@ -395,6 +398,27 @@ func ReadSecurityXattrToTarHeader(path string, hdr *tar.Header) error { return nil } +// ReadUserXattrToTarHeader reads user.* xattr from filesystem to a tar header +func ReadUserXattrToTarHeader(path string, hdr *tar.Header) error { + xattrs, err := system.Llistxattr(path) + if err != nil && err != system.EOPNOTSUPP { + return err + } + for _, key := range xattrs { + if strings.HasPrefix(key, "user.") { + value, err := system.Lgetxattr(path, key) + if err != nil { + return err + } + if hdr.Xattrs == nil { + hdr.Xattrs = make(map[string]string) + } + hdr.Xattrs[key] = string(value) + } + } + return nil +} + type tarWhiteoutConverter interface { ConvertWrite(*tar.Header, string, os.FileInfo) (*tar.Header, error) ConvertRead(*tar.Header, string) (bool, error) @@ -469,6 +493,9 @@ func (ta *tarAppender) addTarFile(path, name string) error { if err := ReadSecurityXattrToTarHeader(path, hdr); err != nil { return err } + if err := ReadUserXattrToTarHeader(path, hdr); err != nil { + return err + } if ta.CopyPass { copyPassHeader(hdr) } diff --git a/vendor/github.com/containers/storage/pkg/archive/changes.go b/vendor/github.com/containers/storage/pkg/archive/changes.go index d3d6c8f74..3ce396070 100644 --- a/vendor/github.com/containers/storage/pkg/archive/changes.go +++ b/vendor/github.com/containers/storage/pkg/archive/changes.go @@ -8,6 +8,7 @@ import ( "io/ioutil" "os" "path/filepath" + "reflect" "sort" "strings" "syscall" @@ -263,6 +264,7 @@ type FileInfo struct { children map[string]*FileInfo capability []byte added bool + xattrs map[string]string } // LookUp looks up the file information of a file. @@ -331,7 +333,8 @@ func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) { // breaks down is if some code intentionally hides a change by setting // back mtime if statDifferent(oldStat, oldInfo, newStat, info) || - !bytes.Equal(oldChild.capability, newChild.capability) { + !bytes.Equal(oldChild.capability, newChild.capability) || + !reflect.DeepEqual(oldChild.xattrs, newChild.xattrs) { change := Change{ Path: newChild.path(), Kind: ChangeModify, diff --git a/vendor/github.com/containers/storage/pkg/archive/changes_linux.go b/vendor/github.com/containers/storage/pkg/archive/changes_linux.go index dc313d1ab..ceec53ada 100644 --- a/vendor/github.com/containers/storage/pkg/archive/changes_linux.go +++ b/vendor/github.com/containers/storage/pkg/archive/changes_linux.go @@ -6,6 +6,7 @@ import ( "os" "path/filepath" "sort" + "strings" "syscall" "unsafe" @@ -83,7 +84,26 @@ func walkchunk(path string, fi os.FileInfo, dir string, root *FileInfo) error { return err } info.stat = stat - info.capability, _ = system.Lgetxattr(cpath, "security.capability") // lgetxattr(2): fs access + info.capability, err = system.Lgetxattr(cpath, "security.capability") // lgetxattr(2): fs access + if err != nil && err != system.EOPNOTSUPP { + return err + } + xattrs, err := system.Llistxattr(cpath) + if err != nil && err != system.EOPNOTSUPP { + return err + } + for _, key := range xattrs { + if strings.HasPrefix(key, "user.") { + value, err := system.Lgetxattr(cpath, key) + if err != nil { + return err + } + if info.xattrs == nil { + info.xattrs = make(map[string]string) + } + info.xattrs[key] = string(value) + } + } parent.children[info.name] = info return nil } diff --git a/vendor/github.com/containers/storage/pkg/system/xattrs_linux.go b/vendor/github.com/containers/storage/pkg/system/xattrs_linux.go index 98b111be4..0d6cd95e3 100644 --- a/vendor/github.com/containers/storage/pkg/system/xattrs_linux.go +++ b/vendor/github.com/containers/storage/pkg/system/xattrs_linux.go @@ -1,6 +1,16 @@ package system -import "golang.org/x/sys/unix" +import ( + "bytes" + "syscall" + + "golang.org/x/sys/unix" +) + +const ( + // Operation not supported + EOPNOTSUPP syscall.Errno = unix.EOPNOTSUPP +) // Lgetxattr retrieves the value of the extended attribute identified by attr // and associated with the given path in the file system. @@ -27,3 +37,32 @@ func Lgetxattr(path string, attr string) ([]byte, error) { func Lsetxattr(path string, attr string, data []byte, flags int) error { return unix.Lsetxattr(path, attr, data, flags) } + +// Llistxattr lists extended attributes associated with the given path +// in the file system. +func Llistxattr(path string) ([]string, error) { + var dest []byte + + for { + sz, err := unix.Llistxattr(path, dest) + if err != nil { + return nil, err + } + + if sz > len(dest) { + dest = make([]byte, sz) + } else { + dest = dest[:sz] + break + } + } + + var attrs []string + for _, token := range bytes.Split(dest, []byte{0}) { + if len(token) > 0 { + attrs = append(attrs, string(token)) + } + } + + return attrs, nil +} diff --git a/vendor/github.com/containers/storage/pkg/system/xattrs_unsupported.go b/vendor/github.com/containers/storage/pkg/system/xattrs_unsupported.go index 0114f2227..b4cf4e6ca 100644 --- a/vendor/github.com/containers/storage/pkg/system/xattrs_unsupported.go +++ b/vendor/github.com/containers/storage/pkg/system/xattrs_unsupported.go @@ -2,6 +2,13 @@ package system +import "syscall" + +const ( + // Operation not supported + EOPNOTSUPP syscall.Errno = syscall.Errno(0) +) + // Lgetxattr is not supported on platforms other than linux. func Lgetxattr(path string, attr string) ([]byte, error) { return nil, ErrNotSupportedPlatform @@ -11,3 +18,8 @@ func Lgetxattr(path string, attr string) ([]byte, error) { func Lsetxattr(path string, attr string, data []byte, flags int) error { return ErrNotSupportedPlatform } + +// Llistxattr is not supported on platforms other than linux. +func Llistxattr(path string) ([]string, error) { + return nil, ErrNotSupportedPlatform +} diff --git a/vendor/github.com/containers/storage/utils.go b/vendor/github.com/containers/storage/utils.go index 54627731a..5aa7c0851 100644 --- a/vendor/github.com/containers/storage/utils.go +++ b/vendor/github.com/containers/storage/utils.go @@ -70,6 +70,18 @@ func ParseIDMapping(UIDMapSlice, GIDMapSlice []string, subUIDMap, subGIDMap stri // GetRootlessRuntimeDir returns the runtime directory when running as non root func GetRootlessRuntimeDir(rootlessUid int) (string, error) { + path, err := getRootlessRuntimeDir(rootlessUid) + if err != nil { + return "", err + } + path = filepath.Join(path, "containers") + if err := os.MkdirAll(path, 0700); err != nil { + return "", errors.Wrapf(err, "unable to make rootless runtime dir %s", path) + } + return path, nil +} + +func getRootlessRuntimeDir(rootlessUid int) (string, error) { runtimeDir := os.Getenv("XDG_RUNTIME_DIR") if runtimeDir != "" { diff --git a/vendor/modules.txt b/vendor/modules.txt index 8837e7efb..4fb9b9d44 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -125,7 +125,7 @@ github.com/containers/psgo/internal/dev github.com/containers/psgo/internal/host github.com/containers/psgo/internal/proc github.com/containers/psgo/internal/process -# github.com/containers/storage v1.14.0 +# github.com/containers/storage v1.15.0 github.com/containers/storage github.com/containers/storage/drivers github.com/containers/storage/drivers/aufs -- cgit v1.2.3-54-g00ecf