mirror of
https://github.com/containers/podman.git
synced 2025-08-06 03:19:52 +08:00
images: distinguish between tags and digests
Generate an image's RepoDigests list using all applicable digests, and refrain from outputting a digest in the tag column of the "images" output. Signed-off-by: Nalin Dahyabhai <nalin@redhat.com>
This commit is contained in:
@ -206,9 +206,9 @@ func (i imagesOptions) setOutputFormat() string {
|
||||
if i.quiet {
|
||||
return formats.IDString
|
||||
}
|
||||
format := "table {{.Repository}}\t{{.Tag}}\t"
|
||||
format := "table {{.Repository}}\t{{if .Tag}}{{.Tag}}{{else}}<none>{{end}}\t"
|
||||
if i.noHeading {
|
||||
format = "{{.Repository}}\t{{.Tag}}\t"
|
||||
format = "{{.Repository}}\t{{if .Tag}}{{.Tag}}{{else}}<none>{{end}}\t"
|
||||
}
|
||||
if i.digests {
|
||||
format += "{{.Digest}}\t"
|
||||
@ -270,7 +270,7 @@ func getImagesTemplateOutput(ctx context.Context, images []*adapter.ContainerIma
|
||||
imageID = shortID(img.ID())
|
||||
}
|
||||
|
||||
// get all specified repo:tag pairs and print them separately
|
||||
// get all specified repo:tag and repo@digest pairs and print them separately
|
||||
repopairs, err := image.ReposToMap(img.Names())
|
||||
if err != nil {
|
||||
logrus.Errorf("error finding tag/digest for %s", img.ID())
|
||||
@ -287,11 +287,16 @@ func getImagesTemplateOutput(ctx context.Context, images []*adapter.ContainerIma
|
||||
lastNumIdx := strings.LastIndexFunc(sizeStr, unicode.IsNumber)
|
||||
sizeStr = sizeStr[:lastNumIdx+1] + " " + sizeStr[lastNumIdx+1:]
|
||||
}
|
||||
var imageDigest digest.Digest
|
||||
if len(tag) == 71 && strings.HasPrefix(tag, "sha256:") {
|
||||
imageDigest = digest.Digest(tag)
|
||||
tag = ""
|
||||
}
|
||||
params := imagesTemplateParams{
|
||||
Repository: repo,
|
||||
Tag: tag,
|
||||
ID: imageID,
|
||||
Digest: img.Digest(),
|
||||
Digest: imageDigest,
|
||||
Digests: img.Digests(),
|
||||
CreatedTime: createdTime,
|
||||
Created: units.HumanDuration(time.Since(createdTime)) + " ago",
|
||||
@ -302,7 +307,6 @@ func getImagesTemplateOutput(ctx context.Context, images []*adapter.ContainerIma
|
||||
if opts.quiet { // Show only one image ID when quiet
|
||||
break outer
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
@ -324,29 +325,54 @@ func (i *Image) Manifest(ctx context.Context) ([]byte, string, error) {
|
||||
return imgRef.Manifest(ctx)
|
||||
}
|
||||
|
||||
// Names returns a string array of names associated with the image
|
||||
// Names returns a string array of names associated with the image, which may be a mixture of tags and digests
|
||||
func (i *Image) Names() []string {
|
||||
return i.image.Names
|
||||
}
|
||||
|
||||
// RepoDigests returns a string array of repodigests associated with the image
|
||||
func (i *Image) RepoDigests() ([]string, error) {
|
||||
var repoDigests []string
|
||||
imageDigest := i.Digest()
|
||||
|
||||
// RepoTags returns a string array of repotags associated with the image
|
||||
func (i *Image) RepoTags() ([]string, error) {
|
||||
var repoTags []string
|
||||
for _, name := range i.Names() {
|
||||
named, err := reference.ParseNormalizedNamed(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
canonical, err := reference.WithDigest(reference.TrimNamed(named), imageDigest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if tagged, isTagged := named.(reference.NamedTagged); isTagged {
|
||||
repoTags = append(repoTags, tagged.String())
|
||||
}
|
||||
|
||||
repoDigests = append(repoDigests, canonical.String())
|
||||
}
|
||||
return repoTags, nil
|
||||
}
|
||||
|
||||
// RepoDigests returns a string array of repodigests associated with the image
|
||||
func (i *Image) RepoDigests() ([]string, error) {
|
||||
var repoDigests []string
|
||||
added := make(map[string]struct{})
|
||||
|
||||
for _, name := range i.Names() {
|
||||
for _, imageDigest := range append(i.Digests(), i.Digest()) {
|
||||
if imageDigest == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
named, err := reference.ParseNormalizedNamed(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
canonical, err := reference.WithDigest(reference.TrimNamed(named), imageDigest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, alreadyInList := added[canonical.String()]; !alreadyInList {
|
||||
repoDigests = append(repoDigests, canonical.String())
|
||||
added[canonical.String()] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Strings(repoDigests)
|
||||
return repoDigests, nil
|
||||
}
|
||||
|
||||
@ -944,6 +970,11 @@ func (i *Image) Inspect(ctx context.Context) (*inspect.ImageData, error) {
|
||||
size = int64(*usize)
|
||||
}
|
||||
|
||||
repoTags, err := i.RepoTags()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repoDigests, err := i.RepoDigests()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -965,7 +996,7 @@ func (i *Image) Inspect(ctx context.Context) (*inspect.ImageData, error) {
|
||||
|
||||
data := &inspect.ImageData{
|
||||
ID: i.ID(),
|
||||
RepoTags: i.Names(),
|
||||
RepoTags: repoTags,
|
||||
RepoDigests: repoDigests,
|
||||
Comment: comment,
|
||||
Created: ociv1Img.Created,
|
||||
|
@ -247,6 +247,19 @@ func TestImage_RepoDigests(t *testing.T) {
|
||||
}
|
||||
|
||||
assert.Equal(t, test.expected, actual)
|
||||
|
||||
image = &Image{
|
||||
image: &storage.Image{
|
||||
Names: test.names,
|
||||
Digests: []digest.Digest{dgst},
|
||||
},
|
||||
}
|
||||
actual, err = image.RepoDigests()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, test.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -87,18 +87,18 @@ func hasTransport(image string) bool {
|
||||
}
|
||||
|
||||
// ReposToMap parses the specified repotags and returns a map with repositories
|
||||
// as keys and the corresponding arrays of tags as values.
|
||||
func ReposToMap(repotags []string) (map[string][]string, error) {
|
||||
// map format is repo -> tag
|
||||
// as keys and the corresponding arrays of tags or digests-as-strings as values.
|
||||
func ReposToMap(names []string) (map[string][]string, error) {
|
||||
// map format is repo -> []tag-or-digest
|
||||
repos := make(map[string][]string)
|
||||
for _, repo := range repotags {
|
||||
for _, name := range names {
|
||||
var repository, tag string
|
||||
if len(repo) > 0 {
|
||||
named, err := reference.ParseNormalizedNamed(repo)
|
||||
repository = named.Name()
|
||||
if len(name) > 0 {
|
||||
named, err := reference.ParseNormalizedNamed(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
repository = named.Name()
|
||||
if ref, ok := named.(reference.NamedTagged); ok {
|
||||
tag = ref.Tag()
|
||||
} else if ref, ok := named.(reference.Canonical); ok {
|
||||
|
Reference in New Issue
Block a user