Skip to content

Commit

Permalink
Add support for docker-archive: to skopeo list-tags
Browse files Browse the repository at this point in the history
Signed-off-by: zhangguanzhang <[email protected]>
  • Loading branch information
zhangguanzhang committed Mar 15, 2022
1 parent 0f70172 commit 7ba56f3
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 27 deletions.
104 changes: 84 additions & 20 deletions cmd/skopeo/list_tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
}

Expand All @@ -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, "", "")
Expand All @@ -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
`,
Expand Down Expand Up @@ -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()
Expand All @@ -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{
Expand Down
54 changes: 48 additions & 6 deletions docs/skopeo-list-tags.1.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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:

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

Expand Down Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion docs/skopeo.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. |
Expand Down
28 changes: 28 additions & 0 deletions systemtest/070-list-tags.bats
Original file line number Diff line number Diff line change
@@ -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
Binary file added systemtest/testdata/docker-two-images.tar.xz
Binary file not shown.

0 comments on commit 7ba56f3

Please sign in to comment.