-
Notifications
You must be signed in to change notification settings - Fork 788
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #716 from zhill/tag_list
Adds "docker-list-tags" command to list tags
- Loading branch information
Showing
6 changed files
with
311 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"github.com/containers/image/v5/docker" | ||
"github.com/containers/image/v5/docker/reference" | ||
"github.com/containers/image/v5/transports/alltransports" | ||
"github.com/containers/image/v5/types" | ||
"github.com/pkg/errors" | ||
"github.com/urfave/cli" | ||
"strings" | ||
|
||
"io" | ||
) | ||
|
||
// 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 | ||
Tags []string | ||
} | ||
|
||
type tagsOptions struct { | ||
global *globalOptions | ||
image *imageOptions | ||
} | ||
|
||
func tagsCmd(global *globalOptions) cli.Command { | ||
sharedFlags, sharedOpts := sharedImageFlags() | ||
imageFlags, imageOpts := dockerImageFlags(global, sharedOpts, "", "") | ||
|
||
opts := tagsOptions{ | ||
global: global, | ||
image: imageOpts, | ||
} | ||
|
||
return cli.Command{ | ||
Name: "list-tags", | ||
Usage: "List tags in the transport/repository specified by the REPOSITORY-NAME", | ||
Description: ` | ||
Return the list of tags from the transport/repository "REPOSITORY-NAME" | ||
Supported transports: | ||
docker | ||
See skopeo-list-tags(1) section "REPOSITORY NAMES" for the expected format | ||
`, | ||
ArgsUsage: "REPOSITORY-NAME", | ||
Flags: append(sharedFlags, imageFlags...), | ||
Action: commandAction(opts.run), | ||
} | ||
} | ||
|
||
// Customized version of the alltransports.ParseImageName and docker.ParseReference that does not place a default tag in the reference | ||
// Would really love to not have this, but needed to enforce tag-less and digest-less names | ||
func parseDockerRepositoryReference(refString string) (types.ImageReference, error) { | ||
if !strings.HasPrefix(refString, docker.Transport.Name()+"://") { | ||
return nil, errors.Errorf("docker: image reference %s does not start with %s://", refString, docker.Transport.Name()) | ||
} | ||
|
||
parts := strings.SplitN(refString, ":", 2) | ||
if len(parts) != 2 { | ||
return nil, errors.Errorf(`Invalid image name "%s", expected colon-separated transport:reference`, refString) | ||
} | ||
|
||
ref, err := reference.ParseNormalizedNamed(strings.TrimPrefix(parts[1], "//")) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if !reference.IsNameOnly(ref) { | ||
return nil, errors.New(`No tag or digest allowed in reference`) | ||
} | ||
|
||
// Checks ok, now return a reference. This is a hack because the tag listing code expects a full image reference even though the tag is ignored | ||
return docker.NewReference(reference.TagNameOnly(ref)) | ||
} | ||
|
||
// List the tags from a repository contained in the imgRef reference. Any tag value in the reference is ignored | ||
func listDockerTags(ctx context.Context, sys *types.SystemContext, imgRef types.ImageReference) (string, []string, error) { | ||
repositoryName := imgRef.DockerReference().Name() | ||
|
||
tags, err := docker.GetRepositoryTags(ctx, sys, imgRef) | ||
if err != nil { | ||
return ``, nil, fmt.Errorf("Error listing repository tags: %v", err) | ||
} | ||
return repositoryName, tags, nil | ||
} | ||
|
||
func (opts *tagsOptions) run(args []string, stdout io.Writer) (retErr error) { | ||
ctx, cancel := opts.global.commandTimeoutContext() | ||
defer cancel() | ||
|
||
if len(args) != 1 { | ||
return errorShouldDisplayUsage{errors.New("Exactly one non-option argument expected")} | ||
} | ||
|
||
sys, err := opts.image.newSystemContext() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
transport := alltransports.TransportFromImageName(args[0]) | ||
if transport == nil { | ||
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 | ||
} | ||
|
||
repositoryName, tagListing, err := listDockerTags(ctx, sys, imgRef) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
outputData := tagListOutput{ | ||
Repository: repositoryName, | ||
Tags: tagListing, | ||
} | ||
|
||
out, err := json.MarshalIndent(outputData, "", " ") | ||
if err != nil { | ||
return err | ||
} | ||
_, err = fmt.Fprintf(stdout, "%s\n", string(out)) | ||
|
||
return err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package main | ||
|
||
import ( | ||
"github.com/containers/image/v5/transports/alltransports" | ||
"github.com/stretchr/testify/assert" | ||
"testing" | ||
) | ||
|
||
// Tests the kinds of inputs allowed and expected to the command | ||
func TestDockerRepositoryReferenceParser(t *testing.T) { | ||
for _, test := range [][]string{ | ||
{"docker://myhost.com:1000/nginx"}, //no tag | ||
{"docker://myhost.com/nginx"}, //no port or tag | ||
{"docker://somehost.com"}, // Valid default expansion | ||
{"docker://nginx"}, // Valid default expansion | ||
} { | ||
|
||
ref, err := parseDockerRepositoryReference(test[0]) | ||
expected, err := alltransports.ParseImageName(test[0]) | ||
if assert.NoError(t, err, "Could not parse, got error on %v", test[0]) { | ||
assert.Equal(t, expected.DockerReference().Name(), ref.DockerReference().Name(), "Mismatched parse result for input %v", test[0]) | ||
} | ||
} | ||
|
||
for _, test := range [][]string{ | ||
{"oci://somedir"}, | ||
{"dir:/somepath"}, | ||
{"docker-archive:/tmp/dir"}, | ||
{"container-storage:myhost.com/someimage"}, | ||
{"docker-daemon:myhost.com/someimage"}, | ||
{"docker://myhost.com:1000/nginx:foobar:foobar"}, // Invalid repository ref | ||
{"docker://somehost.com:5000/"}, // no repo | ||
{"docker://myhost.com:1000/nginx:latest"}, //tag not allowed | ||
{"docker://myhost.com:1000/nginx@sha256:abcdef1234567890"}, //digest not allowed | ||
} { | ||
_, err := parseDockerRepositoryReference(test[0]) | ||
assert.Error(t, err, test[0]) | ||
} | ||
} | ||
|
||
func TestDockerRepositoryReferenceParserDrift(t *testing.T) { | ||
for _, test := range [][]string{ | ||
{"docker://myhost.com:1000/nginx", "myhost.com:1000/nginx"}, //no tag | ||
{"docker://myhost.com/nginx", "myhost.com/nginx"}, //no port or tag | ||
{"docker://somehost.com", "docker.io/library/somehost.com"}, // Valid default expansion | ||
{"docker://nginx", "docker.io/library/nginx"}, // Valid default expansion | ||
} { | ||
|
||
ref, err := parseDockerRepositoryReference(test[0]) | ||
ref2, err2 := alltransports.ParseImageName(test[0]) | ||
|
||
if assert.NoError(t, err, "Could not parse, got error on %v", test[0]) && assert.NoError(t, err2, "Could not parse with regular parser, got error on %v", test[0]) { | ||
assert.Equal(t, ref.DockerReference().String(), ref2.DockerReference().String(), "Different parsing output for input %v. Repo parse = %v, regular parser = %v", test[0], ref, ref2) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
% skopeo-list-tags(1) | ||
|
||
## NAME | ||
skopeo\-list\-tags - Return a list of tags the transport-specific image repository | ||
|
||
## SYNOPSIS | ||
**skopeo list-tags** _repository-name_ | ||
|
||
Return a list of tags from _repository-name_ in a registry. | ||
|
||
_repository-name_ name of repository to retrieve tag listing from | ||
|
||
**--authfile** _path_ | ||
|
||
Path of the authentication file. Default is ${XDG\_RUNTIME\_DIR}/containers/auth.json, which is set using `podman login`. | ||
If the authorization state is not found there, $HOME/.docker/config.json is checked, which is set using `docker login`. | ||
|
||
**--creds** _username[:password]_ for accessing the registry | ||
|
||
**--cert-dir** _path_ Use certificates at _path_ (\*.crt, \*.cert, \*.key) to connect to the registry | ||
|
||
**--tls-verify** _bool-value_ Require HTTPS and verify certificates when talking to container registries (defaults to true) | ||
|
||
**--no-creds** _bool-value_ Access the registry anonymously. | ||
|
||
## 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. | ||
|
||
This commands refers to repositories using a _transport_`:`_details_ format. The following formats are supported: | ||
|
||
**docker://**_docker-repository-reference_ | ||
A repository in a registry implementing the "Docker Registry HTTP API V2". By default, uses the authorization state in either `$XDG_RUNTIME_DIR/containers/auth.json`, which is set using `(podman login)`. If the authorization state is not found there, `$HOME/.docker/config.json` is checked, which is set using `(docker login)`. | ||
A _docker-repository-reference_ is of the form: **registryhost:port/repositoryname** which is similar to an _image-reference_ but with no tag or digest allowed as the last component (e.g no `:latest` or `@sha256:xyz`) | ||
|
||
Examples of valid docker-repository-references: | ||
"docker.io/myuser/myrepo" | ||
"docker.io/nginx" | ||
"docker.io/library/fedora" | ||
"localhost:5000/myrepository" | ||
|
||
Examples of invalid references: | ||
"docker.io/nginx:latest" | ||
"docker.io/myuser/myimage:v1.0" | ||
"docker.io/myuser/myimage@sha256:f48c4cc192f4c3c6a069cb5cca6d0a9e34d6076ba7c214fd0cc3ca60e0af76bb" | ||
|
||
|
||
## EXAMPLES | ||
|
||
### Docker Transport | ||
To get the list of tags in the "fedora" repository from the docker.io registry (the repository name expands to "library/fedora" per docker transport canonical form): | ||
```sh | ||
$ skopeo list-tags docker://docker.io/fedora | ||
{ | ||
"Repository": "docker.io/library/fedora", | ||
"Tags": [ | ||
"20", | ||
"21", | ||
"22", | ||
"23", | ||
"24", | ||
"25", | ||
"26-modular", | ||
"26", | ||
"27", | ||
"28", | ||
"29", | ||
"30", | ||
"31", | ||
"32", | ||
"branched", | ||
"heisenbug", | ||
"latest", | ||
"modular", | ||
"rawhide" | ||
] | ||
} | ||
|
||
``` | ||
|
||
To list the tags in a local host docker/distribution registry on port 5000, in this case for the "fedora" repository: | ||
|
||
```sh | ||
$ skopeo list-tags docker://localhost:5000/fedora | ||
{ | ||
"Repository": "localhost:5000/fedora", | ||
"Tags": [ | ||
"latest", | ||
"30", | ||
"31" | ||
] | ||
} | ||
|
||
``` | ||
|
||
# SEE ALSO | ||
skopeo(1), podman-login(1), docker-login(1) | ||
|
||
## AUTHORS | ||
|
||
Zach Hill <[email protected]> | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters