From 7ba56f3f7ace269a575458b80baf5439d880b482 Mon Sep 17 00:00:00 2001 From: zhangguanzhang Date: Wed, 2 Mar 2022 12:48:06 +0800 Subject: [PATCH] Add support for docker-archive: to skopeo list-tags Signed-off-by: zhangguanzhang --- cmd/skopeo/list_tags.go | 104 +++++++++++++++---- docs/skopeo-list-tags.1.md | 54 ++++++++-- docs/skopeo.1.md | 2 +- systemtest/070-list-tags.bats | 28 +++++ systemtest/testdata/docker-two-images.tar.xz | Bin 0 -> 1416 bytes 5 files changed, 161 insertions(+), 27 deletions(-) create mode 100644 systemtest/070-list-tags.bats create mode 100644 systemtest/testdata/docker-two-images.tar.xz diff --git a/cmd/skopeo/list_tags.go b/cmd/skopeo/list_tags.go index 4486429d..1a4d6423 100644 --- a/cmd/skopeo/list_tags.go +++ b/cmd/skopeo/list_tags.go @@ -5,10 +5,12 @@ import ( "encoding/json" "fmt" "io" + "sort" "strings" "github.com/containers/common/pkg/retry" "github.com/containers/image/v5/docker" + "github.com/containers/image/v5/docker/archive" "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/transports/alltransports" "github.com/containers/image/v5/types" @@ -18,7 +20,7 @@ import ( // tagListOutput is the output format of (skopeo list-tags), primarily so that we can format it with a simple json.MarshalIndent. type tagListOutput struct { - Repository string + Repository string `json:",omitempty"` Tags []string } @@ -28,6 +30,21 @@ type tagsOptions struct { retryOpts *retry.RetryOptions } +var transportHandlers = map[string]func(ctx context.Context, sys *types.SystemContext, opts *tagsOptions, userInput string) (repositoryName string, tagListing []string, err error){ + docker.Transport.Name(): listDockerRepoTags, + archive.Transport.Name(): listDockerArchiveTags, +} + +// supportedTransports returns all the supported transports +func supportedTransports(joinStr string) string { + res := make([]string, 0, len(transportHandlers)) + for handlerName := range transportHandlers { + res = append(res, handlerName) + } + sort.Strings(res) + return strings.Join(res, joinStr) +} + func tagsCmd(global *globalOptions) *cobra.Command { sharedFlags, sharedOpts := sharedImageFlags() imageFlags, imageOpts := dockerImageFlags(global, sharedOpts, nil, "", "") @@ -38,13 +55,14 @@ func tagsCmd(global *globalOptions) *cobra.Command { image: imageOpts, retryOpts: retryOpts, } + cmd := &cobra.Command{ - Use: "list-tags [command options] REPOSITORY-NAME", - Short: "List tags in the transport/repository specified by the REPOSITORY-NAME", - Long: `Return the list of tags from the transport/repository "REPOSITORY-NAME" + Use: "list-tags [command options] SOURCE-IMAGE", + Short: "List tags in the transport/repository specified by the SOURCE-IMAGE", + Long: `Return the list of tags from the transport/repository "SOURCE-IMAGE" Supported transports: -docker +` + supportedTransports(" ") + ` See skopeo-list-tags(1) section "REPOSITORY NAMES" for the expected format `, @@ -95,6 +113,58 @@ func listDockerTags(ctx context.Context, sys *types.SystemContext, imgRef types. return repositoryName, tags, nil } +// return the tagLists from a docker repo +func listDockerRepoTags(ctx context.Context, sys *types.SystemContext, opts *tagsOptions, userInput string) (repositoryName string, tagListing []string, err error) { + // Do transport-specific parsing and validation to get an image reference + imgRef, err := parseDockerRepositoryReference(userInput) + if err != nil { + return + } + if err = retry.RetryIfNecessary(ctx, func() error { + repositoryName, tagListing, err = listDockerTags(ctx, sys, imgRef) + return err + }, opts.retryOpts); err != nil { + return + } + return +} + +// return the tagLists from a docker archive file +func listDockerArchiveTags(ctx context.Context, sys *types.SystemContext, opts *tagsOptions, userInput string) (repositoryName string, tagListing []string, err error) { + ref, err := alltransports.ParseImageName(userInput) + if err != nil { + return + } + + tarReader, _, err := archive.NewReaderForReference(sys, ref) + if err != nil { + return + } + defer tarReader.Close() + + imageRefs, err := tarReader.List() + if err != nil { + return + } + + var repoTags []string + for imageIndex, items := range imageRefs { + for _, ref := range items { + repoTags, err = tarReader.ManifestTagsForReference(ref) + if err != nil { + return + } + // handle for each untagged image + if len(repoTags) == 0 { + repoTags = []string{fmt.Sprintf("@%d", imageIndex)} + } + tagListing = append(tagListing, repoTags...) + } + } + + return +} + func (opts *tagsOptions) run(args []string, stdout io.Writer) (retErr error) { ctx, cancel := opts.global.commandTimeoutContext() defer cancel() @@ -113,23 +183,17 @@ func (opts *tagsOptions) run(args []string, stdout io.Writer) (retErr error) { return fmt.Errorf("Invalid %q: does not specify a transport", args[0]) } - if transport.Name() != docker.Transport.Name() { - return fmt.Errorf("Unsupported transport '%v' for tag listing. Only '%v' currently supported", transport.Name(), docker.Transport.Name()) - } - - // Do transport-specific parsing and validation to get an image reference - imgRef, err := parseDockerRepositoryReference(args[0]) - if err != nil { - return err - } - var repositoryName string var tagListing []string - if err = retry.RetryIfNecessary(ctx, func() error { - repositoryName, tagListing, err = listDockerTags(ctx, sys, imgRef) - return err - }, opts.retryOpts); err != nil { - return err + + if val, ok := transportHandlers[transport.Name()]; ok { + repositoryName, tagListing, err = val(ctx, sys, opts, args[0]) + if err != nil { + return err + } + } else { + return fmt.Errorf("Unsupported transport '%s' for tag listing. Only supported: %s", + transport.Name(), supportedTransports(", ")) } outputData := tagListOutput{ diff --git a/docs/skopeo-list-tags.1.md b/docs/skopeo-list-tags.1.md index 88d2fb3d..cfb3736f 100644 --- a/docs/skopeo-list-tags.1.md +++ b/docs/skopeo-list-tags.1.md @@ -1,14 +1,14 @@ % skopeo-list-tags(1) ## NAME -skopeo\-list\-tags - List tags in the transport-specific image repository. +skopeo\-list\-tags - List image names in a transport-specific collection of images. ## SYNOPSIS -**skopeo list-tags** [*options*] _repository-name_ +**skopeo list-tags** [*options*] _source-image_ -Return a list of tags from _repository-name_ in a registry. +Return a list of tags from _source-image_ in a registry or a local docker-archive file. - _repository-name_ name of repository to retrieve tag listing from + _source-image_ name of the repository to retrieve a tag listing from or a local docker-archive file. ## OPTIONS @@ -53,7 +53,7 @@ The password to access the registry. ## REPOSITORY NAMES -Repository names are transport-specific references as each transport may have its own concept of a "repository" and "tags". Currently, only the Docker transport is supported. +Repository names are transport-specific references as each transport may have its own concept of a "repository" and "tags". This commands refers to repositories using a _transport_`:`_details_ format. The following formats are supported: @@ -72,6 +72,8 @@ This commands refers to repositories using a _transport_`:`_details_ format. The "docker.io/myuser/myimage:v1.0" "docker.io/myuser/myimage@sha256:f48c4cc192f4c3c6a069cb5cca6d0a9e34d6076ba7c214fd0cc3ca60e0af76bb" + **docker-archive:path[:docker-reference] + more than one images were stored in a docker save-formatted file. ## EXAMPLES @@ -121,8 +123,48 @@ $ skopeo list-tags docker://localhost:5000/fedora ``` +### Docker-archive Transport + +To list the tags in a local docker-archive file: + +```sh +$ skopeo list-tags docker-archive:/tmp/busybox.tar.gz +{ + "Tags": [ + "busybox:1.28.3" + ] +} +``` + +Also supports more than one tags in an archive: + +```sh +$ skopeo list-tags docker-archive:/tmp/docker-two-images.tar.gz +{ + "Tags": [ + "example.com/empty:latest", + "example.com/empty/but:different" + ] +} +``` + +Will include a source-index entry for each untagged image: + +```sh +$ skopeo list-tags docker-archive:/tmp/four-tags-with-an-untag.tar +{ + "Tags": [ + "image1:tag1", + "image2:tag2", + "@2", + "image4:tag4" + ] +} +``` + + # SEE ALSO -skopeo(1), skopeo-login(1), docker-login(1), containers-auth.json(5) +skopeo(1), skopeo-login(1), docker-login(1), containers-auth.json(5), containers-transports(1) ## AUTHORS diff --git a/docs/skopeo.1.md b/docs/skopeo.1.md index b067d0d5..56741d60 100644 --- a/docs/skopeo.1.md +++ b/docs/skopeo.1.md @@ -102,7 +102,7 @@ Print the version number | [skopeo-copy(1)](skopeo-copy.1.md) | Copy an image (manifest, filesystem layers, signatures) from one location to another. | | [skopeo-delete(1)](skopeo-delete.1.md) | Mark the _image-name_ for later deletion by the registry's garbage collector. | | [skopeo-inspect(1)](skopeo-inspect.1.md) | Return low-level information about _image-name_ in a registry. | -| [skopeo-list-tags(1)](skopeo-list-tags.1.md) | List tags in the transport-specific image repository. | +| [skopeo-list-tags(1)](skopeo-list-tags.1.md) | List image names in a transport-specific collection of images.| | [skopeo-login(1)](skopeo-login.1.md) | Login to a container registry. | | [skopeo-logout(1)](skopeo-logout.1.md) | Logout of a container registry. | | [skopeo-manifest-digest(1)](skopeo-manifest-digest.1.md) | Compute a manifest digest for a manifest-file and write it to standard output. | diff --git a/systemtest/070-list-tags.bats b/systemtest/070-list-tags.bats new file mode 100644 index 00000000..d681c66c --- /dev/null +++ b/systemtest/070-list-tags.bats @@ -0,0 +1,28 @@ +#!/usr/bin/env bats +# +# list-tags tests +# + +load helpers + +# list from registry +@test "list-tags: remote repository on a registry" { + local remote_image=quay.io/libpod/alpine_labels + + run_skopeo list-tags "docker://${remote_image}" + expect_output --substring "quay.io/libpod/alpine_labels" + expect_output --substring "latest" +} + +# list from a local docker-archive file +@test "list-tags: from a docker-archive file" { + local file_name=${TEST_SOURCE_DIR}/testdata/docker-two-images.tar.xz + + run_skopeo list-tags docker-archive:$file_name + expect_output --substring "example.com/empty:latest" + expect_output --substring "example.com/empty/but:different" + +} + + +# vim: filetype=sh diff --git a/systemtest/testdata/docker-two-images.tar.xz b/systemtest/testdata/docker-two-images.tar.xz new file mode 100644 index 0000000000000000000000000000000000000000..148d8a86ba6f245c2fc3240ca2b0c8ebc72b219c GIT binary patch literal 1416 zcmV;31$X-WH+ooF000E$*0e?f03iVu0001VFXf})L;nRvT>u)GLgsN~w9b^4e;~T5 zci!}2p$e^qo2Z_>iB8f9s5$z=_r8VW_|3bj7Bg0L!W*)1qUq_|^dnxknZ`9bJ6dO% zg1b2^8&Oc!F@GRyI=F$DXmR@h(>zOaPGWfqWpGV1(FSQ;Ae&pydAJB9ud>qLh`>aF zU+J8Ynra46(#(GB1VT={3n<=J@cwQ6)yec_U0E^w6?fs;47AJMFqdFvgm3KmBm!hS zOx1(SX+}1LD}W{Mr*IgzEI34aJ4;o5ZmQw@q_7{y5ImshWI`~zvMkDL6!73(@LDld zDf(wfi`q!C7#~{hR9uG4(OKG;1sBkxHzQ^;cEmCuLOl0kS&zkG>uFAWKHwhe?R4~d z%@OrFggABKew(DNgIFYX+Y+SKVUeQ|Bc`2oaCrUZl3xdz2%Ceu7ao1i^9F8!m)U9N zlvdym-RDST-fV^b1LH6Rm;h`{TN#@tmn!9(&0tS_=J~6-+ z3g{VK*X)AN91EWLk}D#8sGa^TVW>-?bCO^;GejRW=@!5&R#4jK6*>7~g`>3zwTPgg z=@A0qPHJnM({)c4GPjbgitlfZG;J#bpd{Ek%5-&OqaG%#`gzra5C7%~v8O7~@Zl1KD&$)@JA32>Qa`(Jy8bur|PjMeP# z4z^o|9lOf4Jf%)8QjWHBemUqB-4KWPLBw+L$eD7JcAzPs!ql8%pyoZx%!c~*+B*uI z_9_J*ZzcTZ6fl-d7~}8XJ~*|X89b8T0X#5RjbN)rmT%eixyw-e)y8CgG4%#hDi7iR zKD)QCmPt?ky?X?1VB%HsphjXMgJxL^K8)iV(qwy;IfKQ3@5! zn8~8m>Y|0<$Y3m*bW1SR-ij7S8$SQ6r(KKBori|W-RcP0l>-7*sp>Q?ifXd8(D{iW z{-8|9&iGUVS^)ivog~S?tVi{ly6ABUdmly=Fl=R`vdz(`E@S1mPQ@^O>M^HyR_eNISQmGU)D^vX z)3jdoUnK+fmfvZA20-#j4m8-1WFQjeC@Kr;0~&&LCaIUcf7`GR|E?5+E4Qq=k6BR_ z?m~HpMO2{|uN~mWG`28aUzDm&qtU-eWYutcqV!dbQH;c`gup)l-#A-=5=qB>l0zr5 zNphZX>d7t%v@>`jl4K9@s}bA2GIJ5D%LA?}GEKN!H9a!;Js5U`;tz=v*>^&q);s!l zV|bv;C2o|Z4ENFt;p3Vaz@+ZDZ#v*$(%%l~3e1+NdLWor)#Ao{g000001X)@$I