mirror of
https://github.com/containers/podman.git
synced 2025-06-17 15:08:08 +08:00
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 <sgrunert@suse.com>
This commit is contained in:
2
API.md
2
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)
|
||||
### <a name="ImageHistory"></a>type ImageHistory
|
||||
|
||||
ImageHistory describes the returned structure from ImageHistory.
|
||||
|
@ -64,6 +64,7 @@ type ImagesValues struct {
|
||||
NoTrunc bool
|
||||
Quiet bool
|
||||
Sort string
|
||||
History bool
|
||||
}
|
||||
|
||||
type EventValues struct {
|
||||
|
@ -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}}<none>{{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)
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -1563,6 +1563,7 @@ _podman_images() {
|
||||
--filter
|
||||
-h
|
||||
--help
|
||||
--history
|
||||
--no-trunc
|
||||
--notruncate
|
||||
-n
|
||||
|
@ -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.
|
||||
|
2
go.mod
2
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
|
||||
|
2
go.sum
2
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=
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
2
vendor/github.com/containers/storage/VERSION
generated
vendored
2
vendor/github.com/containers/storage/VERSION
generated
vendored
@ -1 +1 @@
|
||||
1.14.0
|
||||
1.15.0
|
||||
|
16
vendor/github.com/containers/storage/drivers/copy/copy_linux.go
generated
vendored
16
vendor/github.com/containers/storage/drivers/copy/copy_linux.go
generated
vendored
@ -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
|
||||
|
2
vendor/github.com/containers/storage/drivers/vfs/copy_linux.go
generated
vendored
2
vendor/github.com/containers/storage/drivers/vfs/copy_linux.go
generated
vendored
@ -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)
|
||||
}
|
||||
|
11
vendor/github.com/containers/storage/images.go
generated
vendored
11
vendor/github.com/containers/storage/images.go
generated
vendored
@ -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()
|
||||
|
108
vendor/github.com/containers/storage/images_ffjson.go
generated
vendored
108
vendor/github.com/containers/storage/images_ffjson.go
generated
vendored
@ -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*/
|
||||
|
29
vendor/github.com/containers/storage/pkg/archive/archive.go
generated
vendored
29
vendor/github.com/containers/storage/pkg/archive/archive.go
generated
vendored
@ -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)
|
||||
}
|
||||
|
5
vendor/github.com/containers/storage/pkg/archive/changes.go
generated
vendored
5
vendor/github.com/containers/storage/pkg/archive/changes.go
generated
vendored
@ -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,
|
||||
|
22
vendor/github.com/containers/storage/pkg/archive/changes_linux.go
generated
vendored
22
vendor/github.com/containers/storage/pkg/archive/changes_linux.go
generated
vendored
@ -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
|
||||
}
|
||||
|
41
vendor/github.com/containers/storage/pkg/system/xattrs_linux.go
generated
vendored
41
vendor/github.com/containers/storage/pkg/system/xattrs_linux.go
generated
vendored
@ -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
|
||||
}
|
||||
|
12
vendor/github.com/containers/storage/pkg/system/xattrs_unsupported.go
generated
vendored
12
vendor/github.com/containers/storage/pkg/system/xattrs_unsupported.go
generated
vendored
@ -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
|
||||
}
|
||||
|
12
vendor/github.com/containers/storage/utils.go
generated
vendored
12
vendor/github.com/containers/storage/utils.go
generated
vendored
@ -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 != "" {
|
||||
|
2
vendor/modules.txt
vendored
2
vendor/modules.txt
vendored
@ -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
|
||||
|
Reference in New Issue
Block a user