From 54c176e336591df6b88029a258ccc02948324a94 Mon Sep 17 00:00:00 2001 From: Andrew Pilloud Date: Mon, 13 Mar 2017 08:16:03 -0700 Subject: [PATCH 1/2] storage: Support latest containers/image Signed-off-by: Andrew Pilloud --- pkg/storage/image.go | 12 ++++++------ pkg/storage/runtime.go | 6 +++--- test/copyimg/copyimg.go | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/pkg/storage/image.go b/pkg/storage/image.go index 3c338cfc..4901d3e1 100644 --- a/pkg/storage/image.go +++ b/pkg/storage/image.go @@ -4,7 +4,7 @@ import ( "github.com/containers/image/copy" "github.com/containers/image/signature" istorage "github.com/containers/image/storage" - "github.com/containers/image/transports" + "github.com/containers/image/transports/alltransports" "github.com/containers/image/types" "github.com/containers/storage/storage" ) @@ -64,7 +64,7 @@ func (svc *imageService) ListImages(filter string) ([]ImageResult, error) { } func (svc *imageService) ImageStatus(systemContext *types.SystemContext, nameOrID string) (*ImageResult, error) { - ref, err := transports.ParseImageName(nameOrID) + ref, err := alltransports.ParseImageName(nameOrID) if err != nil { ref2, err2 := istorage.Transport.ParseStoreReference(svc.store, "@"+nameOrID) if err2 != nil { @@ -118,12 +118,12 @@ func (svc *imageService) PullImage(systemContext *types.SystemContext, imageName if options == nil { options = ©.Options{} } - srcRef, err := transports.ParseImageName(imageName) + srcRef, err := alltransports.ParseImageName(imageName) if err != nil { if svc.defaultTransport == "" { return nil, err } - srcRef2, err2 := transports.ParseImageName(svc.defaultTransport + imageName) + srcRef2, err2 := alltransports.ParseImageName(svc.defaultTransport + imageName) if err2 != nil { return nil, err } @@ -131,7 +131,7 @@ func (svc *imageService) PullImage(systemContext *types.SystemContext, imageName } dest := imageName if srcRef.DockerReference() != nil { - dest = srcRef.DockerReference().FullName() + dest = srcRef.DockerReference().Name() } destRef, err := istorage.Transport.ParseStoreReference(svc.store, dest) if err != nil { @@ -157,7 +157,7 @@ func (svc *imageService) PullImage(systemContext *types.SystemContext, imageName } func (svc *imageService) RemoveImage(systemContext *types.SystemContext, nameOrID string) error { - ref, err := transports.ParseImageName(nameOrID) + ref, err := alltransports.ParseImageName(nameOrID) if err != nil { ref2, err2 := istorage.Transport.ParseStoreReference(svc.store, "@"+nameOrID) if err2 != nil { diff --git a/pkg/storage/runtime.go b/pkg/storage/runtime.go index abf7bf46..b2102abd 100644 --- a/pkg/storage/runtime.go +++ b/pkg/storage/runtime.go @@ -9,7 +9,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/containers/image/copy" istorage "github.com/containers/image/storage" - "github.com/containers/image/transports" + "github.com/containers/image/transports/alltransports" "github.com/containers/image/types" "github.com/containers/storage/storage" "github.com/opencontainers/image-spec/specs-go/v1" @@ -162,9 +162,9 @@ func (r *runtimeService) createContainerOrPodSandbox(systemContext *types.System ref, err := istorage.Transport.ParseStoreReference(r.image.GetStore(), imageName) if err != nil { // Maybe it's some other transport's copy of the image? - otherRef, err2 := transports.ParseImageName(imageName) + otherRef, err2 := alltransports.ParseImageName(imageName) if err2 == nil && otherRef.DockerReference() != nil { - ref, err = istorage.Transport.ParseStoreReference(r.image.GetStore(), otherRef.DockerReference().FullName()) + ref, err = istorage.Transport.ParseStoreReference(r.image.GetStore(), otherRef.DockerReference().Name()) } if err != nil { // Maybe the image ID is sufficient? diff --git a/test/copyimg/copyimg.go b/test/copyimg/copyimg.go index 9ea952a5..aa260521 100644 --- a/test/copyimg/copyimg.go +++ b/test/copyimg/copyimg.go @@ -7,7 +7,7 @@ import ( "github.com/containers/image/copy" "github.com/containers/image/signature" "github.com/containers/image/storage" - "github.com/containers/image/transports" + "github.com/containers/image/transports/alltransports" "github.com/containers/image/types" "github.com/containers/storage/pkg/reexec" sstorage "github.com/containers/storage/storage" @@ -137,7 +137,7 @@ func main() { options := ©.Options{} if importFrom != "" { - importRef, err = transports.ParseImageName(importFrom) + importRef, err = alltransports.ParseImageName(importFrom) if err != nil { logrus.Errorf("error parsing image name %v: %v", importFrom, err) os.Exit(1) @@ -145,7 +145,7 @@ func main() { } if exportTo != "" { - exportRef, err = transports.ParseImageName(exportTo) + exportRef, err = alltransports.ParseImageName(exportTo) if err != nil { logrus.Errorf("error parsing image name %v: %v", exportTo, err) os.Exit(1) From de9995d5f0d9b3b9c41d8f1ff1ef32191a2b6839 Mon Sep 17 00:00:00 2001 From: Andrew Pilloud Date: Mon, 13 Mar 2017 09:33:17 -0700 Subject: [PATCH 2/2] dep: Update containers/image to 1d7e25b91705e4d1cddb5396baf112caeb1119f3 Signed-off-by: Andrew Pilloud --- lock.json | 6 +- vendor/github.com/containers/image/.gitignore | 2 + .../github.com/containers/image/.travis.yml | 3 +- vendor/github.com/containers/image/Makefile | 40 +- vendor/github.com/containers/image/README.md | 31 +- .../github.com/containers/image/copy/copy.go | 72 +- .../containers/image/copy/copy_test.go | 9 +- .../containers/image/copy/fixtures/Hello.bz2 | Bin 43 -> 40 bytes .../containers/image/copy/fixtures/Hello.gz | Bin 25 -> 39 bytes .../image/copy/fixtures/Hello.uncompressed | 2 +- .../containers/image/copy/fixtures/Hello.xz | Bin 64 -> 39 bytes .../containers/image/copy/progress_reader.go | 28 + .../image/directory/directory_dest.go | 3 +- .../image/directory/directory_src.go | 3 +- .../image/directory/directory_transport.go | 5 + vendor/github.com/containers/image/doc.go | 42 +- .../image/docker/daemon/daemon_dest.go | 8 +- .../image/docker/daemon/daemon_src.go | 49 +- .../image/docker/daemon/daemon_transport.go | 20 +- .../docker/daemon/daemon_transport_test.go | 56 +- .../containers/image/docker/docker_client.go | 7 +- .../containers/image/docker/docker_image.go | 5 +- .../image/docker/docker_image_dest.go | 41 +- .../image/docker/docker_image_src.go | 14 +- .../image/docker/docker_transport.go | 18 +- .../image/docker/docker_transport_test.go | 50 +- .../containers/image/docker/lookaside.go | 2 +- .../docker/policyconfiguration/naming.go | 13 +- .../docker/policyconfiguration/naming_test.go | 28 +- .../image/docker/reference/README.md | 2 + .../containers/image/docker/reference/doc.go | 6 - .../image/docker/reference/helpers.go | 42 + .../image/docker/reference/normalize.go | 152 ++++ .../image/docker/reference/normalize_test.go | 573 ++++++++++++ .../image/docker/reference/reference.go | 520 +++++++---- .../image/docker/reference/reference_test.go | 823 +++++++++++++----- .../image/docker/reference/regexp.go | 143 +++ .../image/docker/reference/regexp_test.go | 553 ++++++++++++ .../containers/image/image/docker_schema1.go | 2 +- .../image/image/docker_schema2_test.go | 9 +- .../containers/image/image/memory.go | 3 +- .../containers/image/image/oci_test.go | 5 +- .../containers/image/image/unparsed.go | 7 +- .../containers/image/oci/layout/oci_dest.go | 3 +- .../containers/image/oci/layout/oci_src.go | 12 +- .../image/oci/layout/oci_transport.go | 5 + .../containers/image/openshift/openshift.go | 17 +- .../image/openshift/openshift_transport.go | 16 +- .../openshift/openshift_transport_test.go | 6 +- .../{copy => pkg/compression}/compression.go | 33 +- .../compression}/compression_test.go | 12 +- .../image/pkg/compression/fixtures/Hello.bz2 | Bin 0 -> 43 bytes .../image/pkg/compression/fixtures/Hello.gz | Bin 0 -> 25 bytes .../compression/fixtures/Hello.uncompressed | 1 + .../image/pkg/compression/fixtures/Hello.xz | Bin 0 -> 64 bytes .../containers/image/signature/docker.go | 4 +- .../image/signature/fixtures/.gitignore | 2 + .../manifest.json | 1 + .../dir-img-manifest-digest-error/signature-1 | 1 + .../fixtures/dir-img-mixed/manifest.json | 1 + .../fixtures/dir-img-mixed/signature-1 | 1 + .../fixtures/dir-img-mixed/signature-2 | 1 + .../dir-img-modified-manifest/signature-1 | 1 + .../fixtures/dir-img-no-manifest/signature-1 | 1 + .../fixtures/dir-img-unsigned/manifest.json | 1 + .../fixtures/dir-img-valid-2/manifest.json | 1 + .../fixtures/dir-img-valid-2/signature-1 | 1 + .../fixtures/dir-img-valid/manifest.json | 1 + .../image/signature/policy_config.go | 17 +- .../image/signature/policy_config_test.go | 10 +- .../signature/policy_eval_signedby_test.go | 2 +- .../image/signature/policy_eval_test.go | 37 +- .../image/signature/policy_reference_match.go | 6 +- .../signature/policy_reference_match_test.go | 45 +- .../containers/image/storage/storage_image.go | 6 +- .../image/storage/storage_reference.go | 2 +- .../image/storage/storage_reference_test.go | 2 +- .../image/storage/storage_transport.go | 19 +- .../image/storage/storage_transport_test.go | 2 +- .../transports/alltransports/alltransports.go | 31 + .../alltransports_test.go} | 11 +- .../containers/image/transports/transports.go | 83 +- .../containers/image/types/types.go | 16 +- .../github.com/containers/image/vendor.conf | 31 + 84 files changed, 3091 insertions(+), 748 deletions(-) create mode 100644 vendor/github.com/containers/image/.gitignore mode change 100644 => 120000 vendor/github.com/containers/image/copy/fixtures/Hello.bz2 mode change 100644 => 120000 vendor/github.com/containers/image/copy/fixtures/Hello.gz mode change 100644 => 120000 vendor/github.com/containers/image/copy/fixtures/Hello.uncompressed mode change 100644 => 120000 vendor/github.com/containers/image/copy/fixtures/Hello.xz create mode 100644 vendor/github.com/containers/image/copy/progress_reader.go create mode 100644 vendor/github.com/containers/image/docker/reference/README.md delete mode 100644 vendor/github.com/containers/image/docker/reference/doc.go create mode 100644 vendor/github.com/containers/image/docker/reference/helpers.go create mode 100644 vendor/github.com/containers/image/docker/reference/normalize.go create mode 100644 vendor/github.com/containers/image/docker/reference/normalize_test.go create mode 100644 vendor/github.com/containers/image/docker/reference/regexp.go create mode 100644 vendor/github.com/containers/image/docker/reference/regexp_test.go rename vendor/github.com/containers/image/{copy => pkg/compression}/compression.go (59%) rename vendor/github.com/containers/image/{copy => pkg/compression}/compression_test.go (88%) create mode 100644 vendor/github.com/containers/image/pkg/compression/fixtures/Hello.bz2 create mode 100644 vendor/github.com/containers/image/pkg/compression/fixtures/Hello.gz create mode 100644 vendor/github.com/containers/image/pkg/compression/fixtures/Hello.uncompressed create mode 100644 vendor/github.com/containers/image/pkg/compression/fixtures/Hello.xz create mode 120000 vendor/github.com/containers/image/signature/fixtures/dir-img-manifest-digest-error/manifest.json create mode 120000 vendor/github.com/containers/image/signature/fixtures/dir-img-manifest-digest-error/signature-1 create mode 120000 vendor/github.com/containers/image/signature/fixtures/dir-img-mixed/manifest.json create mode 120000 vendor/github.com/containers/image/signature/fixtures/dir-img-mixed/signature-1 create mode 120000 vendor/github.com/containers/image/signature/fixtures/dir-img-mixed/signature-2 create mode 120000 vendor/github.com/containers/image/signature/fixtures/dir-img-modified-manifest/signature-1 create mode 120000 vendor/github.com/containers/image/signature/fixtures/dir-img-no-manifest/signature-1 create mode 120000 vendor/github.com/containers/image/signature/fixtures/dir-img-unsigned/manifest.json create mode 120000 vendor/github.com/containers/image/signature/fixtures/dir-img-valid-2/manifest.json create mode 120000 vendor/github.com/containers/image/signature/fixtures/dir-img-valid-2/signature-1 create mode 120000 vendor/github.com/containers/image/signature/fixtures/dir-img-valid/manifest.json create mode 100644 vendor/github.com/containers/image/transports/alltransports/alltransports.go rename vendor/github.com/containers/image/transports/{transports_test.go => alltransports/alltransports_test.go} (86%) create mode 100644 vendor/github.com/containers/image/vendor.conf diff --git a/lock.json b/lock.json index 27d1d5d9..a20e9088 100644 --- a/lock.json +++ b/lock.json @@ -1,5 +1,5 @@ { - "memo": "43d56ce99d6232de6146fa891dafc690d66b05555ce6587759f39d037e37c84a", + "memo": "f9f05813d58aa8fce2eed8aae7f05fd12d1f2965afb0fea7ed0ead9a70836e53", "projects": [ { "name": "github.com/BurntSushi/toml", @@ -50,7 +50,7 @@ { "name": "github.com/containers/image", "branch": "master", - "revision": "1c202c5d85d2ee531acb1e91740144410066d19e", + "revision": "1d7e25b91705e4d1cddb5396baf112caeb1119f3", "packages": [ "copy", "directory", @@ -63,9 +63,11 @@ "manifest", "oci/layout", "openshift", + "pkg/compression", "signature", "storage", "transports", + "transports/alltransports", "types", "version" ] diff --git a/vendor/github.com/containers/image/.gitignore b/vendor/github.com/containers/image/.gitignore new file mode 100644 index 00000000..aa951758 --- /dev/null +++ b/vendor/github.com/containers/image/.gitignore @@ -0,0 +1,2 @@ +vendor +tools.timestamp diff --git a/vendor/github.com/containers/image/.travis.yml b/vendor/github.com/containers/image/.travis.yml index 2432a053..7653bc41 100644 --- a/vendor/github.com/containers/image/.travis.yml +++ b/vendor/github.com/containers/image/.travis.yml @@ -5,8 +5,7 @@ email: false go: - 1.7 - install: make deps - script: make .gitvalidation && make validate && make test && make test-skopeo + script: make tools .gitvalidation validate test test-skopeo dist: trusty os: - linux diff --git a/vendor/github.com/containers/image/Makefile b/vendor/github.com/containers/image/Makefile index 0783cfd1..4ea77ec5 100644 --- a/vendor/github.com/containers/image/Makefile +++ b/vendor/github.com/containers/image/Makefile @@ -1,4 +1,4 @@ -.PHONY: all deps test validate lint +.PHONY: all tools test validate lint # Which github repostiory and branch to use for testing with skopeo SKOPEO_REPO = projectatomic/skopeo @@ -8,15 +8,27 @@ SUDO = BUILDTAGS = btrfs_noversion libdm_no_deferred_remove BUILDFLAGS := -tags "$(BUILDTAGS)" -all: deps .gitvalidation test validate +PACKAGES := $(shell go list ./... | grep -v github.com/containers/image/vendor) -deps: - go get -t $(BUILDFLAGS) ./... - go get -u $(BUILDFLAGS) github.com/golang/lint/golint - go get $(BUILDFLAGS) github.com/vbatts/git-validation +all: tools .gitvalidation test validate -test: - @go test $(BUILDFLAGS) -cover ./... +tools: tools.timestamp + +tools.timestamp: Makefile + @go get -u $(BUILDFLAGS) github.com/golang/lint/golint + @go get $(BUILDFLAGS) github.com/vbatts/git-validation + @go get -u github.com/rancher/trash + @touch tools.timestamp + +vendor: tools.timestamp vendor.conf + @trash + @touch vendor + +clean: + rm -rf vendor tools.timestamp + +test: vendor + @go test $(BUILDFLAGS) -cover $(PACKAGES) # This is not run as part of (make all), but Travis CI does run this. # Demonstarting a working version of skopeo (possibly with modified SKOPEO_REPO/SKOPEO_BRANCH, e.g. @@ -29,19 +41,19 @@ test-skopeo: skopeo_path=$${GOPATH}/src/github.com/projectatomic/skopeo && \ vendor_path=$${skopeo_path}/vendor/github.com/containers/image && \ git clone -b $(SKOPEO_BRANCH) https://github.com/$(SKOPEO_REPO) $${skopeo_path} && \ - rm -rf $${vendor_path} && cp -r . $${vendor_path} && \ + rm -rf $${vendor_path} && cp -r . $${vendor_path} && rm -rf $${vendor_path}/vendor && \ cd $${skopeo_path} && \ make BUILDTAGS="$(BUILDTAGS)" binary-local test-all-local && \ $(SUDO) make check && \ rm -rf $${skopeo_path} validate: lint - @go vet ./... - @test -z "$$(gofmt -s -l . | tee /dev/stderr)" + @go vet $(PACKAGES) + @test -z "$$(gofmt -s -l . | grep -ve '^vendor' | tee /dev/stderr)" lint: - @out="$$(golint ./...)"; \ - if [ -n "$$(golint ./...)" ]; then \ + @out="$$(golint $(PACKAGES))"; \ + if [ -n "$$out" ]; then \ echo "$$out"; \ exit 1; \ fi @@ -52,7 +64,7 @@ EPOCH_TEST_COMMIT ?= e68e0e1110e64f906f9b482e548f17d73e02e6b1 # When this is running in travis, it will only check the travis commit range .gitvalidation: - @which git-validation > /dev/null 2>/dev/null || (echo "ERROR: git-validation not found. Consider 'make deps' target" && false) + @which git-validation > /dev/null 2>/dev/null || (echo "ERROR: git-validation not found. Consider 'make clean && make tools'" && false) ifeq ($(TRAVIS),true) @git-validation -q -run DCO,short-subject,dangling-whitespace else diff --git a/vendor/github.com/containers/image/README.md b/vendor/github.com/containers/image/README.md index 397f475a..429755c0 100644 --- a/vendor/github.com/containers/image/README.md +++ b/vendor/github.com/containers/image/README.md @@ -1,15 +1,36 @@ [![GoDoc](https://godoc.org/github.com/containers/image?status.svg)](https://godoc.org/github.com/containers/image) [![Build Status](https://travis-ci.org/containers/image.svg?branch=master)](https://travis-ci.org/containers/image) = -`image` is a set of Go libraries aimed at working in various way with containers' images and container image registries. +`image` is a set of Go libraries aimed at working in various way with +containers' images and container image registries. -The containers/image library allows application to pull and push images from container image registries, like the upstream docker registry. It also implements "simple image signing". +The containers/image library allows application to pull and push images from +container image registries, like the upstream docker registry. It also +implements "simple image signing". -The containers/image library also allows you to inspect a repository on a container registry without pulling down the image. This means it fetches the repository's manifest and it is able to show you a `docker inspect`-like json output about a whole repository or a tag. This library, in contrast to `docker inspect`, helps you gather useful information about a repository or a tag without requiring you to run `docker pull`. +The containers/image library also allows you to inspect a repository on a +container registry without pulling down the image. This means it fetches the +repository's manifest and it is able to show you a `docker inspect`-like json +output about a whole repository or a tag. This library, in contrast to `docker +inspect`, helps you gather useful information about a repository or a tag +without requiring you to run `docker pull`. -The containers/image library also allows you to translate from one image format to another, for example docker container images to OCI images. It also allows you to copy container images between various registries, possibly converting them as necessary, and to sign and verify images. +The containers/image library also allows you to translate from one image format +to another, for example docker container images to OCI images. It also allows +you to copy container images between various registries, possibly converting +them as necessary, and to sign and verify images. -The [skopeo](https://github.com/projectatomic/skopeo) tool uses the containers/image library and takes advantage of its many features. +The [skopeo](https://github.com/projectatomic/skopeo) tool uses the +containers/image library and takes advantage of its many features. + +## Dependencies + +Dependencies that this library prefers will not be found in the `vendor` +directory. This is so you can make well-informed decisions about which +libraries you should use with this package in your own projects. + +What this project tests against dependencies-wise is located +[here](https://github.com/containers/image/blob/master/vendor.conf). ## License diff --git a/vendor/github.com/containers/image/copy/copy.go b/vendor/github.com/containers/image/copy/copy.go index d27e634c..45fe8afd 100644 --- a/vendor/github.com/containers/image/copy/copy.go +++ b/vendor/github.com/containers/image/copy/copy.go @@ -7,12 +7,14 @@ import ( "io" "io/ioutil" "reflect" + "time" pb "gopkg.in/cheggaaa/pb.v1" "github.com/Sirupsen/logrus" "github.com/containers/image/image" "github.com/containers/image/manifest" + "github.com/containers/image/pkg/compression" "github.com/containers/image/signature" "github.com/containers/image/transports" "github.com/containers/image/types" @@ -45,6 +47,8 @@ type imageCopier struct { diffIDsAreNeeded bool canModifyManifest bool reportWriter io.Writer + progressInterval time.Duration + progress chan types.ProgressProperties } // newDigestingReader returns an io.Reader implementation with contents of source, which will eventually return a non-EOF error @@ -92,14 +96,28 @@ type Options struct { ReportWriter io.Writer SourceCtx *types.SystemContext DestinationCtx *types.SystemContext + ProgressInterval time.Duration // time to wait between reports to signal the progress channel + Progress chan types.ProgressProperties // Reported to when ProgressInterval has arrived for a single artifact+offset. } -// Image copies image from srcRef to destRef, using policyContext to validate source image admissibility. -func Image(policyContext *signature.PolicyContext, destRef, srcRef types.ImageReference, options *Options) error { +// Image copies image from srcRef to destRef, using policyContext to validate +// source image admissibility. +func Image(policyContext *signature.PolicyContext, destRef, srcRef types.ImageReference, options *Options) (retErr error) { + // NOTE this function uses an output parameter for the error return value. + // Setting this and returning is the ideal way to return an error. + // + // the defers in this routine will wrap the error return with its own errors + // which can be valuable context in the middle of a multi-streamed copy. + if options == nil { + options = &Options{} + } + reportWriter := ioutil.Discard - if options != nil && options.ReportWriter != nil { + + if options.ReportWriter != nil { reportWriter = options.ReportWriter } + writeReport := func(f string, a ...interface{}) { fmt.Fprintf(reportWriter, f, a...) } @@ -108,7 +126,12 @@ func Image(policyContext *signature.PolicyContext, destRef, srcRef types.ImageRe if err != nil { return errors.Wrapf(err, "Error initializing destination %s", transports.ImageName(destRef)) } - defer dest.Close() + defer func() { + if err := dest.Close(); err != nil { + retErr = errors.Wrapf(retErr, " (dest: %v)", err) + } + }() + destSupportedManifestMIMETypes := dest.SupportedManifestMIMETypes() rawSource, err := srcRef.NewImageSource(options.SourceCtx, destSupportedManifestMIMETypes) @@ -118,7 +141,9 @@ func Image(policyContext *signature.PolicyContext, destRef, srcRef types.ImageRe unparsedImage := image.UnparsedFromSource(rawSource) defer func() { if unparsedImage != nil { - unparsedImage.Close() + if err := unparsedImage.Close(); err != nil { + retErr = errors.Wrapf(retErr, " (unparsed: %v)", err) + } } }() @@ -131,14 +156,18 @@ func Image(policyContext *signature.PolicyContext, destRef, srcRef types.ImageRe return errors.Wrapf(err, "Error initializing image from source %s", transports.ImageName(srcRef)) } unparsedImage = nil - defer src.Close() + defer func() { + if err := src.Close(); err != nil { + retErr = errors.Wrapf(retErr, " (source: %v)", err) + } + }() if src.IsMultiImage() { return errors.Errorf("can not copy %s: manifest contains multiple images", transports.ImageName(srcRef)) } var sigs [][]byte - if options != nil && options.RemoveSignatures { + if options.RemoveSignatures { sigs = [][]byte{} } else { writeReport("Getting image source signatures\n") @@ -173,6 +202,8 @@ func Image(policyContext *signature.PolicyContext, destRef, srcRef types.ImageRe diffIDsAreNeeded: src.UpdatedImageNeedsLayerDiffIDs(manifestUpdates), canModifyManifest: canModifyManifest, reportWriter: reportWriter, + progressInterval: options.ProgressInterval, + progress: options.Progress, } if err := ic.copyLayers(); err != nil { @@ -199,7 +230,7 @@ func Image(policyContext *signature.PolicyContext, destRef, srcRef types.ImageRe return err } - if options != nil && options.SignBy != "" { + if options.SignBy != "" { mech, err := signature.NewGPGSigningMechanism() if err != nil { return errors.Wrap(err, "Error initializing GPG") @@ -370,7 +401,7 @@ func (ic *imageCopier) copyLayer(srcInfo types.BlobInfo) (types.BlobInfo, digest // and returns a complete blobInfo of the copied blob and perhaps a <-chan diffIDResult if diffIDIsNeeded, to be read by the caller. func (ic *imageCopier) copyLayerFromStream(srcStream io.Reader, srcInfo types.BlobInfo, diffIDIsNeeded bool) (types.BlobInfo, <-chan diffIDResult, error) { - var getDiffIDRecorder func(decompressorFunc) io.Writer // = nil + var getDiffIDRecorder func(compression.DecompressorFunc) io.Writer // = nil var diffIDChan chan diffIDResult err := errors.New("Internal error: unexpected panic in copyLayer") // For pipeWriter.CloseWithError below @@ -381,7 +412,7 @@ func (ic *imageCopier) copyLayerFromStream(srcStream io.Reader, srcInfo types.Bl pipeWriter.CloseWithError(err) // CloseWithError(nil) is equivalent to Close() }() - getDiffIDRecorder = func(decompressor decompressorFunc) io.Writer { + getDiffIDRecorder = func(decompressor compression.DecompressorFunc) io.Writer { // If this fails, e.g. because we have exited and due to pipeWriter.CloseWithError() above further // reading from the pipe has failed, we don’t really care. // We only read from diffIDChan if the rest of the flow has succeeded, and when we do read from it, @@ -399,7 +430,7 @@ func (ic *imageCopier) copyLayerFromStream(srcStream io.Reader, srcInfo types.Bl } // diffIDComputationGoroutine reads all input from layerStream, uncompresses using decompressor if necessary, and sends its digest, and status, if any, to dest. -func diffIDComputationGoroutine(dest chan<- diffIDResult, layerStream io.ReadCloser, decompressor decompressorFunc) { +func diffIDComputationGoroutine(dest chan<- diffIDResult, layerStream io.ReadCloser, decompressor compression.DecompressorFunc) { result := diffIDResult{ digest: "", err: errors.New("Internal error: unexpected panic in diffIDComputationGoroutine"), @@ -411,7 +442,7 @@ func diffIDComputationGoroutine(dest chan<- diffIDResult, layerStream io.ReadClo } // computeDiffID reads all input from layerStream, uncompresses it using decompressor if necessary, and returns its digest. -func computeDiffID(stream io.Reader, decompressor decompressorFunc) (digest.Digest, error) { +func computeDiffID(stream io.Reader, decompressor compression.DecompressorFunc) (digest.Digest, error) { if decompressor != nil { s, err := decompressor(stream) if err != nil { @@ -428,7 +459,7 @@ func computeDiffID(stream io.Reader, decompressor decompressorFunc) (digest.Dige // perhaps compressing it if canCompress, // and returns a complete blobInfo of the copied blob. func (ic *imageCopier) copyBlobFromStream(srcStream io.Reader, srcInfo types.BlobInfo, - getOriginalLayerCopyWriter func(decompressor decompressorFunc) io.Writer, + getOriginalLayerCopyWriter func(decompressor compression.DecompressorFunc) io.Writer, canCompress bool) (types.BlobInfo, error) { // The copying happens through a pipeline of connected io.Readers. // === Input: srcStream @@ -446,8 +477,8 @@ func (ic *imageCopier) copyBlobFromStream(srcStream io.Reader, srcInfo types.Blo var destStream io.Reader = digestingReader // === Detect compression of the input stream. - // This requires us to “peek ahead” into the stream to read the initial part, which requires us to chain through another io.Reader returned by detectCompression. - decompressor, destStream, err := detectCompression(destStream) // We could skip this in some cases, but let's keep the code path uniform + // This requires us to “peek ahead” into the stream to read the initial part, which requires us to chain through another io.Reader returned by DetectCompression. + decompressor, destStream, err := compression.DetectCompression(destStream) // We could skip this in some cases, but let's keep the code path uniform if err != nil { return types.BlobInfo{}, errors.Wrapf(err, "Error reading blob %s", srcInfo.Digest) } @@ -489,6 +520,17 @@ func (ic *imageCopier) copyBlobFromStream(srcStream io.Reader, srcInfo types.Blo inputInfo.Size = -1 } + // === Report progress using the ic.progress channel, if required. + if ic.progress != nil && ic.progressInterval > 0 { + destStream = &progressReader{ + source: destStream, + channel: ic.progress, + interval: ic.progressInterval, + artifact: srcInfo, + lastTime: time.Now(), + } + } + // === Finally, send the layer stream to dest. uploadedInfo, err := ic.dest.PutBlob(destStream, inputInfo) if err != nil { diff --git a/vendor/github.com/containers/image/copy/copy_test.go b/vendor/github.com/containers/image/copy/copy_test.go index feb580dd..b98133a8 100644 --- a/vendor/github.com/containers/image/copy/copy_test.go +++ b/vendor/github.com/containers/image/copy/copy_test.go @@ -9,6 +9,7 @@ import ( "github.com/pkg/errors" + "github.com/containers/image/pkg/compression" "github.com/opencontainers/go-digest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -63,7 +64,7 @@ func TestDigestingReaderRead(t *testing.T) { } } -func goDiffIDComputationGoroutineWithTimeout(layerStream io.ReadCloser, decompressor decompressorFunc) *diffIDResult { +func goDiffIDComputationGoroutineWithTimeout(layerStream io.ReadCloser, decompressor compression.DecompressorFunc) *diffIDResult { ch := make(chan diffIDResult) go diffIDComputationGoroutine(ch, layerStream, nil) timeout := time.After(time.Second) @@ -94,12 +95,12 @@ func TestDiffIDComputationGoroutine(t *testing.T) { func TestComputeDiffID(t *testing.T) { for _, c := range []struct { filename string - decompressor decompressorFunc + decompressor compression.DecompressorFunc result digest.Digest }{ {"fixtures/Hello.uncompressed", nil, "sha256:185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969"}, {"fixtures/Hello.gz", nil, "sha256:0bd4409dcd76476a263b8f3221b4ce04eb4686dec40bfdcc2e86a7403de13609"}, - {"fixtures/Hello.gz", gzipDecompressor, "sha256:185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969"}, + {"fixtures/Hello.gz", compression.GzipDecompressor, "sha256:185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969"}, } { stream, err := os.Open(c.filename) require.NoError(t, err, c.filename) @@ -111,7 +112,7 @@ func TestComputeDiffID(t *testing.T) { } // Error initializing decompression - _, err := computeDiffID(bytes.NewReader([]byte{}), gzipDecompressor) + _, err := computeDiffID(bytes.NewReader([]byte{}), compression.GzipDecompressor) assert.Error(t, err) // Error reading input diff --git a/vendor/github.com/containers/image/copy/fixtures/Hello.bz2 b/vendor/github.com/containers/image/copy/fixtures/Hello.bz2 deleted file mode 100644 index e822f5e5e9e170abc384cc72e161a5ed1548ca22..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43 ycmZ>Y%CIzaj8qGblnP0i#K6G7%D~{j#Ik@vaaM-0uds3VPNg{-9=uvco(uru9tt-A diff --git a/vendor/github.com/containers/image/copy/fixtures/Hello.bz2 b/vendor/github.com/containers/image/copy/fixtures/Hello.bz2 new file mode 120000 index 00000000..fc28d6c9 --- /dev/null +++ b/vendor/github.com/containers/image/copy/fixtures/Hello.bz2 @@ -0,0 +1 @@ +../../pkg/compression/fixtures/Hello.bz2 \ No newline at end of file diff --git a/vendor/github.com/containers/image/copy/fixtures/Hello.gz b/vendor/github.com/containers/image/copy/fixtures/Hello.gz deleted file mode 100644 index 22c895b7d178a03e0dc601f450ec5f2158f4866b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25 hcmb2|=3pq;{y&_7`LoB_lPB33nmR9jXJueu004p>39JAB diff --git a/vendor/github.com/containers/image/copy/fixtures/Hello.gz b/vendor/github.com/containers/image/copy/fixtures/Hello.gz new file mode 120000 index 00000000..08aa805f --- /dev/null +++ b/vendor/github.com/containers/image/copy/fixtures/Hello.gz @@ -0,0 +1 @@ +../../pkg/compression/fixtures/Hello.gz \ No newline at end of file diff --git a/vendor/github.com/containers/image/copy/fixtures/Hello.uncompressed b/vendor/github.com/containers/image/copy/fixtures/Hello.uncompressed deleted file mode 100644 index 5ab2f8a4..00000000 --- a/vendor/github.com/containers/image/copy/fixtures/Hello.uncompressed +++ /dev/null @@ -1 +0,0 @@ -Hello \ No newline at end of file diff --git a/vendor/github.com/containers/image/copy/fixtures/Hello.uncompressed b/vendor/github.com/containers/image/copy/fixtures/Hello.uncompressed new file mode 120000 index 00000000..49b46625 --- /dev/null +++ b/vendor/github.com/containers/image/copy/fixtures/Hello.uncompressed @@ -0,0 +1 @@ +../../pkg/compression/fixtures/Hello.uncompressed \ No newline at end of file diff --git a/vendor/github.com/containers/image/copy/fixtures/Hello.xz b/vendor/github.com/containers/image/copy/fixtures/Hello.xz deleted file mode 100644 index 6e9b0b6648fbe8cc20d6dcb1921ddbed0069210b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 64 zcmexsUKJ6=z`*kC+7>q^21Q0O1_p)_{ill=8CX10b8_;5T!s^Cs!v$QoDXDRlx5wa R+pu1K+vi$FkOWI)6aakT6B_^k diff --git a/vendor/github.com/containers/image/copy/fixtures/Hello.xz b/vendor/github.com/containers/image/copy/fixtures/Hello.xz new file mode 120000 index 00000000..77bcd855 --- /dev/null +++ b/vendor/github.com/containers/image/copy/fixtures/Hello.xz @@ -0,0 +1 @@ +../../pkg/compression/fixtures/Hello.xz \ No newline at end of file diff --git a/vendor/github.com/containers/image/copy/progress_reader.go b/vendor/github.com/containers/image/copy/progress_reader.go new file mode 100644 index 00000000..b670ee59 --- /dev/null +++ b/vendor/github.com/containers/image/copy/progress_reader.go @@ -0,0 +1,28 @@ +package copy + +import ( + "io" + "time" + + "github.com/containers/image/types" +) + +// progressReader is a reader that reports its progress on an interval. +type progressReader struct { + source io.Reader + channel chan types.ProgressProperties + interval time.Duration + artifact types.BlobInfo + lastTime time.Time + offset uint64 +} + +func (r *progressReader) Read(p []byte) (int, error) { + n, err := r.source.Read(p) + r.offset += uint64(n) + if time.Since(r.lastTime) > r.interval { + r.channel <- types.ProgressProperties{Artifact: r.artifact, Offset: r.offset} + r.lastTime = time.Now() + } + return n, err +} diff --git a/vendor/github.com/containers/image/directory/directory_dest.go b/vendor/github.com/containers/image/directory/directory_dest.go index e0b0fe45..c310d662 100644 --- a/vendor/github.com/containers/image/directory/directory_dest.go +++ b/vendor/github.com/containers/image/directory/directory_dest.go @@ -26,7 +26,8 @@ func (d *dirImageDestination) Reference() types.ImageReference { } // Close removes resources associated with an initialized ImageDestination, if any. -func (d *dirImageDestination) Close() { +func (d *dirImageDestination) Close() error { + return nil } func (d *dirImageDestination) SupportedManifestMIMETypes() []string { diff --git a/vendor/github.com/containers/image/directory/directory_src.go b/vendor/github.com/containers/image/directory/directory_src.go index 8053c189..d432fb72 100644 --- a/vendor/github.com/containers/image/directory/directory_src.go +++ b/vendor/github.com/containers/image/directory/directory_src.go @@ -28,7 +28,8 @@ func (s *dirImageSource) Reference() types.ImageReference { } // Close removes resources associated with an initialized ImageSource, if any. -func (s *dirImageSource) Close() { +func (s *dirImageSource) Close() error { + return nil } // GetManifest returns the image's manifest along with its MIME type (which may be empty when it can't be determined but the manifest is available). diff --git a/vendor/github.com/containers/image/directory/directory_transport.go b/vendor/github.com/containers/image/directory/directory_transport.go index 89d2565a..34f74289 100644 --- a/vendor/github.com/containers/image/directory/directory_transport.go +++ b/vendor/github.com/containers/image/directory/directory_transport.go @@ -10,10 +10,15 @@ import ( "github.com/containers/image/directory/explicitfilepath" "github.com/containers/image/docker/reference" "github.com/containers/image/image" + "github.com/containers/image/transports" "github.com/containers/image/types" "github.com/opencontainers/go-digest" ) +func init() { + transports.Register(Transport) +} + // Transport is an ImageTransport for directory paths. var Transport = dirTransport{} diff --git a/vendor/github.com/containers/image/doc.go b/vendor/github.com/containers/image/doc.go index c486c35b..253a0835 100644 --- a/vendor/github.com/containers/image/doc.go +++ b/vendor/github.com/containers/image/doc.go @@ -1,29 +1,29 @@ // Package image provides libraries and commands to interact with containers images. // -// package main +// package main // -// import ( -// "fmt" +// import ( +// "fmt" // -// "github.com/containers/image/docker" -// ) +// "github.com/containers/image/docker" +// ) // -// func main() { -// ref, err := docker.ParseReference("fedora") -// if err != nil { -// panic(err) -// } -// img, err := ref.NewImage(nil) -// if err != nil { -// panic(err) -// } -// defer img.Close() -// b, _, err := img.Manifest() -// if err != nil { -// panic(err) -// } -// fmt.Printf("%s", string(b)) -// } +// func main() { +// ref, err := docker.ParseReference("//fedora") +// if err != nil { +// panic(err) +// } +// img, err := ref.NewImage(nil) +// if err != nil { +// panic(err) +// } +// defer img.Close() +// b, _, err := img.Manifest() +// if err != nil { +// panic(err) +// } +// fmt.Printf("%s", string(b)) +// } // // TODO(runcom) package image diff --git a/vendor/github.com/containers/image/docker/daemon/daemon_dest.go b/vendor/github.com/containers/image/docker/daemon/daemon_dest.go index 816a6226..f8ba97af 100644 --- a/vendor/github.com/containers/image/docker/daemon/daemon_dest.go +++ b/vendor/github.com/containers/image/docker/daemon/daemon_dest.go @@ -91,7 +91,7 @@ func imageLoadGoroutine(ctx context.Context, c *client.Client, reader *io.PipeRe } // Close removes resources associated with an initialized ImageDestination, if any. -func (d *daemonImageDestination) Close() { +func (d *daemonImageDestination) Close() error { if !d.committed { logrus.Debugf("docker-daemon: Closing tar stream to abort loading") // In principle, goroutineCancel() should abort the HTTP request and stop the process from continuing. @@ -107,10 +107,10 @@ func (d *daemonImageDestination) Close() { d.writer.CloseWithError(errors.New("Aborting upload, daemonImageDestination closed without a previous .Commit()")) } d.goroutineCancel() + + return nil } -// Reference returns the reference used to set up this destination. Note that this should directly correspond to user's intent, -// e.g. it should use the public hostname instead of the result of resolving CNAMEs or following redirects. func (d *daemonImageDestination) Reference() types.ImageReference { return d.ref } @@ -230,7 +230,7 @@ func (d *daemonImageDestination) PutManifest(m []byte) error { // a hostname-qualified reference. // See https://github.com/containers/image/issues/72 for a more detailed // analysis and explanation. - refString := fmt.Sprintf("%s:%s", d.namedTaggedRef.FullName(), d.namedTaggedRef.Tag()) + refString := fmt.Sprintf("%s:%s", d.namedTaggedRef.Name(), d.namedTaggedRef.Tag()) items := []manifestItem{{ Config: man.Config.Digest.String(), diff --git a/vendor/github.com/containers/image/docker/daemon/daemon_src.go b/vendor/github.com/containers/image/docker/daemon/daemon_src.go index 6b4aec8b..256fd2da 100644 --- a/vendor/github.com/containers/image/docker/daemon/daemon_src.go +++ b/vendor/github.com/containers/image/docker/daemon/daemon_src.go @@ -10,6 +10,7 @@ import ( "path" "github.com/containers/image/manifest" + "github.com/containers/image/pkg/compression" "github.com/containers/image/types" "github.com/docker/docker/client" "github.com/opencontainers/go-digest" @@ -91,8 +92,8 @@ func (s *daemonImageSource) Reference() types.ImageReference { } // Close removes resources associated with an initialized ImageSource, if any. -func (s *daemonImageSource) Close() { - _ = os.Remove(s.tarCopyPath) +func (s *daemonImageSource) Close() error { + return os.Remove(s.tarCopyPath) } // tarReadCloser is a way to close the backing file of a tar.Reader when the user no longer needs the tar component. @@ -334,6 +335,18 @@ func (s *daemonImageSource) GetTargetManifest(digest digest.Digest) ([]byte, str return nil, "", errors.Errorf(`Manifest lists are not supported by "docker-daemon:"`) } +type readCloseWrapper struct { + io.Reader + closeFunc func() error +} + +func (r readCloseWrapper) Close() error { + if r.closeFunc != nil { + return r.closeFunc() + } + return nil +} + // GetBlob returns a stream for the specified blob, and the blob’s size (or -1 if unknown). func (s *daemonImageSource) GetBlob(info types.BlobInfo) (io.ReadCloser, int64, error) { if err := s.ensureCachedDataIsPresent(); err != nil { @@ -349,7 +362,37 @@ func (s *daemonImageSource) GetBlob(info types.BlobInfo) (io.ReadCloser, int64, if err != nil { return nil, 0, err } - return stream, li.size, nil + + // In order to handle the fact that digests != diffIDs (and thus that a + // caller which is trying to verify the blob will run into problems), + // we need to decompress blobs. This is a bit ugly, but it's a + // consequence of making everything addressable by their DiffID rather + // than by their digest... + // + // In particular, because the v2s2 manifest being generated uses + // DiffIDs, any caller of GetBlob is going to be asking for DiffIDs of + // layers not their _actual_ digest. The result is that copy/... will + // be verifing a "digest" which is not the actual layer's digest (but + // is instead the DiffID). + + decompressFunc, reader, err := compression.DetectCompression(stream) + if err != nil { + return nil, 0, errors.Wrapf(err, "Detecting compression in blob %s", info.Digest) + } + + if decompressFunc != nil { + reader, err = decompressFunc(reader) + if err != nil { + return nil, 0, errors.Wrapf(err, "Decompressing blob %s stream", info.Digest) + } + } + + newStream := readCloseWrapper{ + Reader: reader, + closeFunc: stream.Close, + } + + return newStream, li.size, nil } return nil, 0, errors.Errorf("Unknown blob %s", info.Digest) diff --git a/vendor/github.com/containers/image/docker/daemon/daemon_transport.go b/vendor/github.com/containers/image/docker/daemon/daemon_transport.go index c8e40aed..41ccd1f1 100644 --- a/vendor/github.com/containers/image/docker/daemon/daemon_transport.go +++ b/vendor/github.com/containers/image/docker/daemon/daemon_transport.go @@ -5,10 +5,15 @@ import ( "github.com/containers/image/docker/reference" "github.com/containers/image/image" + "github.com/containers/image/transports" "github.com/containers/image/types" "github.com/opencontainers/go-digest" ) +func init() { + transports.Register(Transport) +} + // Transport is an ImageTransport for images managed by a local Docker daemon. var Transport = daemonTransport{} @@ -46,11 +51,11 @@ type daemonReference struct { // ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an ImageReference. func ParseReference(refString string) (types.ImageReference, error) { - // This is intended to be compatible with reference.ParseIDOrReference, but more strict about refusing some of the ambiguous cases. + // This is intended to be compatible with reference.ParseAnyReference, but more strict about refusing some of the ambiguous cases. // In particular, this rejects unprefixed digest values (64 hex chars), and sha256 digest prefixes (sha256:fewer-than-64-hex-chars). // digest:hexstring is structurally the same as a reponame:tag (meaning docker.io/library/reponame:tag). - // reference.ParseIDOrReference interprets such strings as digests. + // reference.ParseAnyReference interprets such strings as digests. if dgst, err := digest.Parse(refString); err == nil { // The daemon explicitly refuses to tag images with a reponame equal to digest.Canonical - but _only_ this digest name. // Other digest references are ambiguous, so refuse them. @@ -60,11 +65,11 @@ func ParseReference(refString string) (types.ImageReference, error) { return NewReference(dgst, nil) } - ref, err := reference.ParseNamed(refString) // This also rejects unprefixed digest values + ref, err := reference.ParseNormalizedNamed(refString) // This also rejects unprefixed digest values if err != nil { return nil, err } - if ref.Name() == digest.Canonical.String() { + if reference.FamiliarName(ref) == digest.Canonical.String() { return nil, errors.Errorf("Invalid docker-daemon: reference %s: The %s repository name is reserved for (non-shortened) digest references", refString, digest.Canonical) } return NewReference("", ref) @@ -77,10 +82,11 @@ func NewReference(id digest.Digest, ref reference.Named) (types.ImageReference, } if ref != nil { if reference.IsNameOnly(ref) { - return nil, errors.Errorf("docker-daemon: reference %s has neither a tag nor a digest", ref.String()) + return nil, errors.Errorf("docker-daemon: reference %s has neither a tag nor a digest", reference.FamiliarString(ref)) } // A github.com/distribution/reference value can have a tag and a digest at the same time! - // docker/reference does not handle that, so fail. + // Most versions of docker/reference do not handle that (ignoring the tag), so reject such input. + // This MAY be accepted in the future. _, isTagged := ref.(reference.NamedTagged) _, isDigested := ref.(reference.Canonical) if isTagged && isDigested { @@ -108,7 +114,7 @@ func (ref daemonReference) StringWithinTransport() string { case ref.id != "": return ref.id.String() case ref.ref != nil: - return ref.ref.String() + return reference.FamiliarString(ref.ref) default: // Coverage: Should never happen, NewReference above should refuse such values. panic("Internal inconsistency: daemonReference has empty id and nil ref") } diff --git a/vendor/github.com/containers/image/docker/daemon/daemon_transport_test.go b/vendor/github.com/containers/image/docker/daemon/daemon_transport_test.go index 68ad2553..2a60c6b2 100644 --- a/vendor/github.com/containers/image/docker/daemon/daemon_transport_test.go +++ b/vendor/github.com/containers/image/docker/daemon/daemon_transport_test.go @@ -50,15 +50,12 @@ func testParseReference(t *testing.T, fn func(string) (types.ImageReference, err {"sha256:XX23456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "", ""}, // Invalid digest value {"UPPERCASEISINVALID", "", ""}, // Invalid reference input {"busybox", "", ""}, // Missing tag or digest - {"busybox:latest", "", "busybox:latest"}, // Explicit tag - {"busybox@" + sha256digest, "", "busybox@" + sha256digest}, // Explicit digest + {"busybox:latest", "", "docker.io/library/busybox:latest"}, // Explicit tag + {"busybox@" + sha256digest, "", "docker.io/library/busybox@" + sha256digest}, // Explicit digest // A github.com/distribution/reference value can have a tag and a digest at the same time! - // github.com/docker/reference handles that by dropping the tag. That is not obviously the - // right thing to do, but it is at least reasonable, so test that we keep behaving reasonably. - // This test case should not be construed to make this an API promise. - // FIXME? Instead work extra hard to reject such input? - {"busybox:latest@" + sha256digest, "", "busybox@" + sha256digest}, // Both tag and digest - {"docker.io/library/busybox:latest", "", "busybox:latest"}, // All implied values explicitly specified + // Most versions of docker/reference do not handle that (ignoring the tag), so we reject such input. + {"busybox:latest@" + sha256digest, "", ""}, // Both tag and digest + {"docker.io/library/busybox:latest", "", "docker.io/library/busybox:latest"}, // All implied values explicitly specified } { ref, err := fn(c.input) if c.expectedID == "" && c.expectedRef == "" { @@ -67,43 +64,37 @@ func testParseReference(t *testing.T, fn func(string) (types.ImageReference, err require.NoError(t, err, c.input) daemonRef, ok := ref.(daemonReference) require.True(t, ok, c.input) - // If we don't reject the input, the interpretation must be consistent for reference.ParseIDOrReference - dockerID, dockerRef, err := reference.ParseIDOrReference(c.input) + // If we don't reject the input, the interpretation must be consistent with reference.ParseAnyReference + dockerRef, err := reference.ParseAnyReference(c.input) require.NoError(t, err, c.input) if c.expectedRef == "" { assert.Equal(t, c.expectedID, daemonRef.id.String(), c.input) assert.Nil(t, daemonRef.ref, c.input) - assert.Equal(t, c.expectedID, dockerID.String(), c.input) - assert.Nil(t, dockerRef, c.input) + _, ok := dockerRef.(reference.Digested) + require.True(t, ok, c.input) + assert.Equal(t, c.expectedID, dockerRef.String(), c.input) } else { assert.Equal(t, "", daemonRef.id.String(), c.input) require.NotNil(t, daemonRef.ref, c.input) assert.Equal(t, c.expectedRef, daemonRef.ref.String(), c.input) - assert.Equal(t, "", dockerID.String(), c.input) - require.NotNil(t, dockerRef, c.input) + _, ok := dockerRef.(reference.Named) + require.True(t, ok, c.input) assert.Equal(t, c.expectedRef, dockerRef.String(), c.input) } } } } -// refWithTagAndDigest is a reference.NamedTagged and reference.Canonical at the same time. -type refWithTagAndDigest struct{ reference.Canonical } - -func (ref refWithTagAndDigest) Tag() string { - return "notLatest" -} - // A common list of reference formats to test for the various ImageReference methods. // (For IDs it is much simpler, we simply use them unmodified) var validNamedReferenceTestCases = []struct{ input, dockerRef, stringWithinTransport string }{ - {"busybox:notlatest", "busybox:notlatest", "busybox:notlatest"}, // Explicit tag - {"busybox" + sha256digest, "busybox" + sha256digest, "busybox" + sha256digest}, // Explicit digest - {"docker.io/library/busybox:latest", "busybox:latest", "busybox:latest"}, // All implied values explicitly specified - {"example.com/ns/foo:bar", "example.com/ns/foo:bar", "example.com/ns/foo:bar"}, // All values explicitly specified + {"busybox:notlatest", "docker.io/library/busybox:notlatest", "busybox:notlatest"}, // Explicit tag + {"busybox" + sha256digest, "docker.io/library/busybox" + sha256digest, "busybox" + sha256digest}, // Explicit digest + {"docker.io/library/busybox:latest", "docker.io/library/busybox:latest", "busybox:latest"}, // All implied values explicitly specified + {"example.com/ns/foo:bar", "example.com/ns/foo:bar", "example.com/ns/foo:bar"}, // All values explicitly specified } func TestNewReference(t *testing.T) { @@ -119,7 +110,7 @@ func TestNewReference(t *testing.T) { // Named references for _, c := range validNamedReferenceTestCases { - parsed, err := reference.ParseNamed(c.input) + parsed, err := reference.ParseNormalizedNamed(c.input) require.NoError(t, err) ref, err := NewReference("", parsed) require.NoError(t, err, c.input) @@ -131,24 +122,25 @@ func TestNewReference(t *testing.T) { } // Both an ID and a named reference provided - parsed, err := reference.ParseNamed("busybox:latest") + parsed, err := reference.ParseNormalizedNamed("busybox:latest") require.NoError(t, err) _, err = NewReference(id, parsed) assert.Error(t, err) // A reference with neither a tag nor digest - parsed, err = reference.ParseNamed("busybox") + parsed, err = reference.ParseNormalizedNamed("busybox") require.NoError(t, err) _, err = NewReference("", parsed) assert.Error(t, err) // A github.com/distribution/reference value can have a tag and a digest at the same time! - parsed, err = reference.ParseNamed("busybox@" + sha256digest) + parsed, err = reference.ParseNormalizedNamed("busybox:notlatest@" + sha256digest) require.NoError(t, err) - refDigested, ok := parsed.(reference.Canonical) + _, ok = parsed.(reference.Canonical) require.True(t, ok) - tagDigestRef := refWithTagAndDigest{refDigested} - _, err = NewReference("", tagDigestRef) + _, ok = parsed.(reference.NamedTagged) + require.True(t, ok) + _, err = NewReference("", parsed) assert.Error(t, err) } diff --git a/vendor/github.com/containers/image/docker/docker_client.go b/vendor/github.com/containers/image/docker/docker_client.go index 0605cad2..4e9fe575 100644 --- a/vendor/github.com/containers/image/docker/docker_client.go +++ b/vendor/github.com/containers/image/docker/docker_client.go @@ -15,6 +15,7 @@ import ( "time" "github.com/Sirupsen/logrus" + "github.com/containers/image/docker/reference" "github.com/containers/image/types" "github.com/containers/storage/pkg/homedir" "github.com/docker/go-connections/sockets" @@ -164,11 +165,11 @@ func hasFile(files []os.FileInfo, name string) bool { // newDockerClient returns a new dockerClient instance for refHostname (a host a specified in the Docker image reference, not canonicalized to dockerRegistry) // “write” specifies whether the client will be used for "write" access (in particular passed to lookaside.go:toplevelFromSection) func newDockerClient(ctx *types.SystemContext, ref dockerReference, write bool, actions string) (*dockerClient, error) { - registry := ref.ref.Hostname() + registry := reference.Domain(ref.ref) if registry == dockerHostname { registry = dockerRegistry } - username, password, err := getAuth(ctx, ref.ref.Hostname()) + username, password, err := getAuth(ctx, reference.Domain(ref.ref)) if err != nil { return nil, err } @@ -202,7 +203,7 @@ func newDockerClient(ctx *types.SystemContext, ref dockerReference, write bool, signatureBase: sigBase, scope: authScope{ actions: actions, - remoteName: ref.ref.RemoteName(), + remoteName: reference.Path(ref.ref), }, }, nil } diff --git a/vendor/github.com/containers/image/docker/docker_image.go b/vendor/github.com/containers/image/docker/docker_image.go index ce769c0a..2bea7eb6 100644 --- a/vendor/github.com/containers/image/docker/docker_image.go +++ b/vendor/github.com/containers/image/docker/docker_image.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" + "github.com/containers/image/docker/reference" "github.com/containers/image/image" "github.com/containers/image/types" "github.com/pkg/errors" @@ -34,12 +35,12 @@ func newImage(ctx *types.SystemContext, ref dockerReference) (types.Image, error // SourceRefFullName returns a fully expanded name for the repository this image is in. func (i *Image) SourceRefFullName() string { - return i.src.ref.ref.FullName() + return i.src.ref.ref.Name() } // GetRepositoryTags list all tags available in the repository. Note that this has no connection with the tag(s) used for this specific image, if any. func (i *Image) GetRepositoryTags() ([]string, error) { - url := fmt.Sprintf(tagsURL, i.src.ref.ref.RemoteName()) + url := fmt.Sprintf(tagsURL, reference.Path(i.src.ref.ref)) res, err := i.src.c.makeRequest("GET", url, nil, nil) if err != nil { return nil, err diff --git a/vendor/github.com/containers/image/docker/docker_image_dest.go b/vendor/github.com/containers/image/docker/docker_image_dest.go index 78ccc27a..0d1b0234 100644 --- a/vendor/github.com/containers/image/docker/docker_image_dest.go +++ b/vendor/github.com/containers/image/docker/docker_image_dest.go @@ -11,6 +11,7 @@ import ( "path/filepath" "github.com/Sirupsen/logrus" + "github.com/containers/image/docker/reference" "github.com/containers/image/manifest" "github.com/containers/image/types" "github.com/opencontainers/go-digest" @@ -58,7 +59,8 @@ func (d *dockerImageDestination) Reference() types.ImageReference { } // Close removes resources associated with an initialized ImageDestination, if any. -func (d *dockerImageDestination) Close() { +func (d *dockerImageDestination) Close() error { + return nil } func (d *dockerImageDestination) SupportedManifestMIMETypes() []string { @@ -98,31 +100,18 @@ func (c *sizeCounter) Write(p []byte) (n int, err error) { // If stream.Read() at any time, ESPECIALLY at end of input, returns an error, PutBlob MUST 1) fail, and 2) delete any data stored so far. func (d *dockerImageDestination) PutBlob(stream io.Reader, inputInfo types.BlobInfo) (types.BlobInfo, error) { if inputInfo.Digest.String() != "" { - checkURL := fmt.Sprintf(blobsURL, d.ref.ref.RemoteName(), inputInfo.Digest.String()) - - logrus.Debugf("Checking %s", checkURL) - res, err := d.c.makeRequest("HEAD", checkURL, nil, nil) - if err != nil { + haveBlob, size, err := d.HasBlob(inputInfo) + if err != nil && err != types.ErrBlobNotFound { return types.BlobInfo{}, err } - defer res.Body.Close() - switch res.StatusCode { - case http.StatusOK: - logrus.Debugf("... already exists, not uploading") - return types.BlobInfo{Digest: inputInfo.Digest, Size: getBlobSize(res)}, nil - case http.StatusUnauthorized: - logrus.Debugf("... not authorized") - return types.BlobInfo{}, errors.Errorf("not authorized to read from destination repository %s", d.ref.ref.RemoteName()) - case http.StatusNotFound: - // noop - default: - return types.BlobInfo{}, errors.Errorf("failed to read from destination repository %s: %v", d.ref.ref.RemoteName(), http.StatusText(res.StatusCode)) + // Now err == nil || err == types.ErrBlobNotFound + if err == nil && haveBlob { + return types.BlobInfo{Digest: inputInfo.Digest, Size: size}, nil } - logrus.Debugf("... failed, status %d", res.StatusCode) } // FIXME? Chunked upload, progress reporting, etc. - uploadURL := fmt.Sprintf(blobUploadURL, d.ref.ref.RemoteName()) + uploadURL := fmt.Sprintf(blobUploadURL, reference.Path(d.ref.ref)) logrus.Debugf("Uploading %s", uploadURL) res, err := d.c.makeRequest("POST", uploadURL, nil, nil) if err != nil { @@ -178,7 +167,7 @@ func (d *dockerImageDestination) HasBlob(info types.BlobInfo) (bool, int64, erro if info.Digest == "" { return false, -1, errors.Errorf(`"Can not check for a blob with unknown digest`) } - checkURL := fmt.Sprintf(blobsURL, d.ref.ref.RemoteName(), info.Digest.String()) + checkURL := fmt.Sprintf(blobsURL, reference.Path(d.ref.ref), info.Digest.String()) logrus.Debugf("Checking %s", checkURL) res, err := d.c.makeRequest("HEAD", checkURL, nil, nil) @@ -192,15 +181,13 @@ func (d *dockerImageDestination) HasBlob(info types.BlobInfo) (bool, int64, erro return true, getBlobSize(res), nil case http.StatusUnauthorized: logrus.Debugf("... not authorized") - return false, -1, errors.Errorf("not authorized to read from destination repository %s", d.ref.ref.RemoteName()) + return false, -1, errors.Errorf("not authorized to read from destination repository %s", reference.Path(d.ref.ref)) case http.StatusNotFound: logrus.Debugf("... not present") return false, -1, types.ErrBlobNotFound default: - logrus.Errorf("failed to read from destination repository %s: %v", d.ref.ref.RemoteName(), http.StatusText(res.StatusCode)) + return false, -1, errors.Errorf("failed to read from destination repository %s: %v", reference.Path(d.ref.ref), http.StatusText(res.StatusCode)) } - logrus.Debugf("... failed, status %d, ignoring", res.StatusCode) - return false, -1, types.ErrBlobNotFound } func (d *dockerImageDestination) ReapplyBlob(info types.BlobInfo) (types.BlobInfo, error) { @@ -214,11 +201,11 @@ func (d *dockerImageDestination) PutManifest(m []byte) error { } d.manifestDigest = digest - reference, err := d.ref.tagOrDigest() + refTail, err := d.ref.tagOrDigest() if err != nil { return err } - url := fmt.Sprintf(manifestURL, d.ref.ref.RemoteName(), reference) + url := fmt.Sprintf(manifestURL, reference.Path(d.ref.ref), refTail) headers := map[string][]string{} mimeType := manifest.GuessMIMEType(m) diff --git a/vendor/github.com/containers/image/docker/docker_image_src.go b/vendor/github.com/containers/image/docker/docker_image_src.go index f87a5fc2..d84c31af 100644 --- a/vendor/github.com/containers/image/docker/docker_image_src.go +++ b/vendor/github.com/containers/image/docker/docker_image_src.go @@ -11,6 +11,7 @@ import ( "strconv" "github.com/Sirupsen/logrus" + "github.com/containers/image/docker/reference" "github.com/containers/image/manifest" "github.com/containers/image/types" "github.com/docker/distribution/registry/client" @@ -64,7 +65,8 @@ func (s *dockerImageSource) Reference() types.ImageReference { } // Close removes resources associated with an initialized ImageSource, if any. -func (s *dockerImageSource) Close() { +func (s *dockerImageSource) Close() error { + return nil } // simplifyContentType drops parameters from a HTTP media type (see https://tools.ietf.org/html/rfc7231#section-3.1.1.1) @@ -91,7 +93,7 @@ func (s *dockerImageSource) GetManifest() ([]byte, string, error) { } func (s *dockerImageSource) fetchManifest(tagOrDigest string) ([]byte, string, error) { - url := fmt.Sprintf(manifestURL, s.ref.ref.RemoteName(), tagOrDigest) + url := fmt.Sprintf(manifestURL, reference.Path(s.ref.ref), tagOrDigest) headers := make(map[string][]string) headers["Accept"] = s.requestedManifestMIMETypes res, err := s.c.makeRequest("GET", url, headers, nil) @@ -177,7 +179,7 @@ func (s *dockerImageSource) GetBlob(info types.BlobInfo) (io.ReadCloser, int64, return s.getExternalBlob(info.URLs) } - url := fmt.Sprintf(blobsURL, s.ref.ref.RemoteName(), info.Digest.String()) + url := fmt.Sprintf(blobsURL, reference.Path(s.ref.ref), info.Digest.String()) logrus.Debugf("Downloading %s", url) res, err := s.c.makeRequest("GET", url, nil, nil) if err != nil { @@ -271,11 +273,11 @@ func deleteImage(ctx *types.SystemContext, ref dockerReference) error { headers := make(map[string][]string) headers["Accept"] = []string{manifest.DockerV2Schema2MediaType} - reference, err := ref.tagOrDigest() + refTail, err := ref.tagOrDigest() if err != nil { return err } - getURL := fmt.Sprintf(manifestURL, ref.ref.RemoteName(), reference) + getURL := fmt.Sprintf(manifestURL, reference.Path(ref.ref), refTail) get, err := c.makeRequest("GET", getURL, headers, nil) if err != nil { return err @@ -294,7 +296,7 @@ func deleteImage(ctx *types.SystemContext, ref dockerReference) error { } digest := get.Header.Get("Docker-Content-Digest") - deleteURL := fmt.Sprintf(manifestURL, ref.ref.RemoteName(), digest) + deleteURL := fmt.Sprintf(manifestURL, reference.Path(ref.ref), digest) // When retrieving the digest from a registry >= 2.3 use the following header: // "Accept": "application/vnd.docker.distribution.manifest.v2+json" diff --git a/vendor/github.com/containers/image/docker/docker_transport.go b/vendor/github.com/containers/image/docker/docker_transport.go index 00d0b7c9..15d68e99 100644 --- a/vendor/github.com/containers/image/docker/docker_transport.go +++ b/vendor/github.com/containers/image/docker/docker_transport.go @@ -6,10 +6,15 @@ import ( "github.com/containers/image/docker/policyconfiguration" "github.com/containers/image/docker/reference" + "github.com/containers/image/transports" "github.com/containers/image/types" "github.com/pkg/errors" ) +func init() { + transports.Register(Transport) +} + // Transport is an ImageTransport for Docker registry-hosted images. var Transport = dockerTransport{} @@ -45,21 +50,22 @@ func ParseReference(refString string) (types.ImageReference, error) { if !strings.HasPrefix(refString, "//") { return nil, errors.Errorf("docker: image reference %s does not start with //", refString) } - ref, err := reference.ParseNamed(strings.TrimPrefix(refString, "//")) + ref, err := reference.ParseNormalizedNamed(strings.TrimPrefix(refString, "//")) if err != nil { return nil, err } - ref = reference.WithDefaultTag(ref) + ref = reference.TagNameOnly(ref) return NewReference(ref) } // NewReference returns a Docker reference for a named reference. The reference must satisfy !reference.IsNameOnly(). func NewReference(ref reference.Named) (types.ImageReference, error) { if reference.IsNameOnly(ref) { - return nil, errors.Errorf("Docker reference %s has neither a tag nor a digest", ref.String()) + return nil, errors.Errorf("Docker reference %s has neither a tag nor a digest", reference.FamiliarString(ref)) } // A github.com/distribution/reference value can have a tag and a digest at the same time! - // docker/reference does not handle that, so fail. + // The docker/distribution API does not really support that (we can’t ask for an image with a specific + // tag and digest), so fail. This MAY be accepted in the future. // (Even if it were supported, the semantics of policy namespaces are unclear - should we drop // the tag or the digest first?) _, isTagged := ref.(reference.NamedTagged) @@ -82,7 +88,7 @@ func (ref dockerReference) Transport() types.ImageTransport { // e.g. default attribute values omitted by the user may be filled in in the return value, or vice versa. // WARNING: Do not use the return value in the UI to describe an image, it does not contain the Transport().Name() prefix. func (ref dockerReference) StringWithinTransport() string { - return "//" + ref.ref.String() + return "//" + reference.FamiliarString(ref.ref) } // DockerReference returns a Docker reference associated with this reference @@ -152,5 +158,5 @@ func (ref dockerReference) tagOrDigest() (string, error) { return ref.Tag(), nil } // This should not happen, NewReference above refuses reference.IsNameOnly values. - return "", errors.Errorf("Internal inconsistency: Reference %s unexpectedly has neither a digest nor a tag", ref.ref.String()) + return "", errors.Errorf("Internal inconsistency: Reference %s unexpectedly has neither a digest nor a tag", reference.FamiliarString(ref.ref)) } diff --git a/vendor/github.com/containers/image/docker/docker_transport_test.go b/vendor/github.com/containers/image/docker/docker_transport_test.go index c6c83623..98dc7f58 100644 --- a/vendor/github.com/containers/image/docker/docker_transport_test.go +++ b/vendor/github.com/containers/image/docker/docker_transport_test.go @@ -42,18 +42,16 @@ func TestParseReference(t *testing.T) { // testParseReference is a test shared for Transport.ParseReference and ParseReference. func testParseReference(t *testing.T, fn func(string) (types.ImageReference, error)) { for _, c := range []struct{ input, expected string }{ - {"busybox", ""}, // Missing // prefix - {"//busybox:notlatest", "busybox:notlatest"}, // Explicit tag - {"//busybox" + sha256digest, "busybox" + sha256digest}, // Explicit digest - {"//busybox", "busybox:latest"}, // Default tag + {"busybox", ""}, // Missing // prefix + {"//busybox:notlatest", "docker.io/library/busybox:notlatest"}, // Explicit tag + {"//busybox" + sha256digest, "docker.io/library/busybox" + sha256digest}, // Explicit digest + {"//busybox", "docker.io/library/busybox:latest"}, // Default tag // A github.com/distribution/reference value can have a tag and a digest at the same time! - // github.com/docker/reference handles that by dropping the tag. That is not obviously the - // right thing to do, but it is at least reasonable, so test that we keep behaving reasonably. - // This test case should not be construed to make this an API promise. - // FIXME? Instead work extra hard to reject such input? - {"//busybox:latest" + sha256digest, "busybox" + sha256digest}, // Both tag and digest - {"//docker.io/library/busybox:latest", "busybox:latest"}, // All implied values explicitly specified - {"//UPPERCASEISINVALID", ""}, // Invalid input + // The docker/distribution API does not really support that (we can’t ask for an image with a specific + // tag and digest), so fail. This MAY be accepted in the future. + {"//busybox:latest" + sha256digest, ""}, // Both tag and digest + {"//docker.io/library/busybox:latest", "docker.io/library/busybox:latest"}, // All implied values explicitly specified + {"//UPPERCASEISINVALID", ""}, // Invalid input } { ref, err := fn(c.input) if c.expected == "" { @@ -67,24 +65,17 @@ func testParseReference(t *testing.T, fn func(string) (types.ImageReference, err } } -// refWithTagAndDigest is a reference.NamedTagged and reference.Canonical at the same time. -type refWithTagAndDigest struct{ reference.Canonical } - -func (ref refWithTagAndDigest) Tag() string { - return "notLatest" -} - // A common list of reference formats to test for the various ImageReference methods. var validReferenceTestCases = []struct{ input, dockerRef, stringWithinTransport string }{ - {"busybox:notlatest", "busybox:notlatest", "//busybox:notlatest"}, // Explicit tag - {"busybox" + sha256digest, "busybox" + sha256digest, "//busybox" + sha256digest}, // Explicit digest - {"docker.io/library/busybox:latest", "busybox:latest", "//busybox:latest"}, // All implied values explicitly specified - {"example.com/ns/foo:bar", "example.com/ns/foo:bar", "//example.com/ns/foo:bar"}, // All values explicitly specified + {"busybox:notlatest", "docker.io/library/busybox:notlatest", "//busybox:notlatest"}, // Explicit tag + {"busybox" + sha256digest, "docker.io/library/busybox" + sha256digest, "//busybox" + sha256digest}, // Explicit digest + {"docker.io/library/busybox:latest", "docker.io/library/busybox:latest", "//busybox:latest"}, // All implied values explicitly specified + {"example.com/ns/foo:bar", "example.com/ns/foo:bar", "//example.com/ns/foo:bar"}, // All values explicitly specified } func TestNewReference(t *testing.T) { for _, c := range validReferenceTestCases { - parsed, err := reference.ParseNamed(c.input) + parsed, err := reference.ParseNormalizedNamed(c.input) require.NoError(t, err) ref, err := NewReference(parsed) require.NoError(t, err, c.input) @@ -94,18 +85,19 @@ func TestNewReference(t *testing.T) { } // Neither a tag nor digest - parsed, err := reference.ParseNamed("busybox") + parsed, err := reference.ParseNormalizedNamed("busybox") require.NoError(t, err) _, err = NewReference(parsed) assert.Error(t, err) // A github.com/distribution/reference value can have a tag and a digest at the same time! - parsed, err = reference.ParseNamed("busybox" + sha256digest) + parsed, err = reference.ParseNormalizedNamed("busybox:notlatest" + sha256digest) require.NoError(t, err) - refDigested, ok := parsed.(reference.Canonical) + _, ok := parsed.(reference.Canonical) require.True(t, ok) - tagDigestRef := refWithTagAndDigest{refDigested} - _, err = NewReference(tagDigestRef) + _, ok = parsed.(reference.NamedTagged) + require.True(t, ok) + _, err = NewReference(parsed) assert.Error(t, err) } @@ -196,7 +188,7 @@ func TestReferenceTagOrDigest(t *testing.T) { } // Invalid input - ref, err := reference.ParseNamed("busybox") + ref, err := reference.ParseNormalizedNamed("busybox") require.NoError(t, err) dockerRef := dockerReference{ref: ref} _, err = dockerRef.tagOrDigest() diff --git a/vendor/github.com/containers/image/docker/lookaside.go b/vendor/github.com/containers/image/docker/lookaside.go index e8f3a5be..8896b758 100644 --- a/vendor/github.com/containers/image/docker/lookaside.go +++ b/vendor/github.com/containers/image/docker/lookaside.go @@ -64,7 +64,7 @@ func configuredSignatureStorageBase(ctx *types.SystemContext, ref dockerReferenc return nil, errors.Wrapf(err, "Invalid signature storage URL %s", topLevel) } // FIXME? Restrict to explicitly supported schemes? - repo := ref.ref.FullName() // Note that this is without a tag or digest. + repo := ref.ref.Name() // Note that this is without a tag or digest. if path.Clean(repo) != repo { // Coverage: This should not be reachable because /./ and /../ components are not valid in docker references return nil, errors.Errorf("Unexpected path elements in Docker reference %s for signature storage", ref.ref.String()) } diff --git a/vendor/github.com/containers/image/docker/policyconfiguration/naming.go b/vendor/github.com/containers/image/docker/policyconfiguration/naming.go index a40fa380..31bbb544 100644 --- a/vendor/github.com/containers/image/docker/policyconfiguration/naming.go +++ b/vendor/github.com/containers/image/docker/policyconfiguration/naming.go @@ -3,23 +3,22 @@ package policyconfiguration import ( "strings" - "github.com/pkg/errors" - "github.com/containers/image/docker/reference" + "github.com/pkg/errors" ) // DockerReferenceIdentity returns a string representation of the reference, suitable for policy lookup, // as a backend for ImageReference.PolicyConfigurationIdentity. // The reference must satisfy !reference.IsNameOnly(). func DockerReferenceIdentity(ref reference.Named) (string, error) { - res := ref.FullName() + res := ref.Name() tagged, isTagged := ref.(reference.NamedTagged) digested, isDigested := ref.(reference.Canonical) switch { - case isTagged && isDigested: // This should not happen, docker/reference.ParseNamed drops the tag. - return "", errors.Errorf("Unexpected Docker reference %s with both a name and a digest", ref.String()) + case isTagged && isDigested: // Note that this CAN actually happen. + return "", errors.Errorf("Unexpected Docker reference %s with both a name and a digest", reference.FamiliarString(ref)) case !isTagged && !isDigested: // This should not happen, the caller is expected to ensure !reference.IsNameOnly() - return "", errors.Errorf("Internal inconsistency: Docker reference %s with neither a tag nor a digest", ref.String()) + return "", errors.Errorf("Internal inconsistency: Docker reference %s with neither a tag nor a digest", reference.FamiliarString(ref)) case isTagged: res = res + ":" + tagged.Tag() case isDigested: @@ -43,7 +42,7 @@ func DockerReferenceNamespaces(ref reference.Named) []string { // ref.FullName() == ref.Hostname() + "/" + ref.RemoteName(), so the last // iteration matches the host name (for any namespace). res := []string{} - name := ref.FullName() + name := ref.Name() for { res = append(res, name) diff --git a/vendor/github.com/containers/image/docker/policyconfiguration/naming_test.go b/vendor/github.com/containers/image/docker/policyconfiguration/naming_test.go index 0269db95..5998faa8 100644 --- a/vendor/github.com/containers/image/docker/policyconfiguration/naming_test.go +++ b/vendor/github.com/containers/image/docker/policyconfiguration/naming_test.go @@ -1,11 +1,10 @@ package policyconfiguration import ( + "fmt" "strings" "testing" - "fmt" - "github.com/containers/image/docker/reference" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -35,14 +34,9 @@ func TestDockerReference(t *testing.T) { for inputSuffix, mappedSuffix := range map[string]string{ ":tag": ":tag", sha256Digest: sha256Digest, - // A github.com/distribution/reference value can have a tag and a digest at the same time! - // github.com/docker/reference handles that by dropping the tag. That is not obviously the - // right thing to do, but it is at least reasonable, so test that we keep behaving reasonably. - // This test case should not be construed to make this an API promise. - ":tag" + sha256Digest: sha256Digest, } { fullInput := inputName + inputSuffix - ref, err := reference.ParseNamed(fullInput) + ref, err := reference.ParseNormalizedNamed(fullInput) require.NoError(t, err, fullInput) identity, err := DockerReferenceIdentity(ref) @@ -62,30 +56,24 @@ func TestDockerReference(t *testing.T) { } } -// refWithTagAndDigest is a reference.NamedTagged and reference.Canonical at the same time. -type refWithTagAndDigest struct{ reference.Canonical } - -func (ref refWithTagAndDigest) Tag() string { - return "notLatest" -} - func TestDockerReferenceIdentity(t *testing.T) { // TestDockerReference above has tested the core of the functionality, this tests only the failure cases. // Neither a tag nor digest - parsed, err := reference.ParseNamed("busybox") + parsed, err := reference.ParseNormalizedNamed("busybox") require.NoError(t, err) id, err := DockerReferenceIdentity(parsed) assert.Equal(t, "", id) assert.Error(t, err) // A github.com/distribution/reference value can have a tag and a digest at the same time! - parsed, err = reference.ParseNamed("busybox@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") + parsed, err = reference.ParseNormalizedNamed("busybox:notlatest@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") require.NoError(t, err) - refDigested, ok := parsed.(reference.Canonical) + _, ok := parsed.(reference.Canonical) require.True(t, ok) - tagDigestRef := refWithTagAndDigest{refDigested} - id, err = DockerReferenceIdentity(tagDigestRef) + _, ok = parsed.(reference.NamedTagged) + require.True(t, ok) + id, err = DockerReferenceIdentity(parsed) assert.Equal(t, "", id) assert.Error(t, err) } diff --git a/vendor/github.com/containers/image/docker/reference/README.md b/vendor/github.com/containers/image/docker/reference/README.md new file mode 100644 index 00000000..53a88de8 --- /dev/null +++ b/vendor/github.com/containers/image/docker/reference/README.md @@ -0,0 +1,2 @@ +This is a copy of github.com/docker/distribution/reference as of commit fb0bebc4b64e3881cc52a2478d749845ed76d2a8, +except that ParseAnyReferenceWithSet has been removed to drop the dependency on github.com/docker/distribution/digestset. \ No newline at end of file diff --git a/vendor/github.com/containers/image/docker/reference/doc.go b/vendor/github.com/containers/image/docker/reference/doc.go deleted file mode 100644 index a75ea749..00000000 --- a/vendor/github.com/containers/image/docker/reference/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Package reference is a fork of the upstream docker/docker/reference package. -// The package is forked because we need consistency especially when storing and -// checking signatures (RH patches break this consistency because they modify -// docker/docker/reference as part of a patch carried in projectatomic/docker). -// The version of this package is v1.12.1 from upstream, update as necessary. -package reference diff --git a/vendor/github.com/containers/image/docker/reference/helpers.go b/vendor/github.com/containers/image/docker/reference/helpers.go new file mode 100644 index 00000000..978df7ea --- /dev/null +++ b/vendor/github.com/containers/image/docker/reference/helpers.go @@ -0,0 +1,42 @@ +package reference + +import "path" + +// IsNameOnly returns true if reference only contains a repo name. +func IsNameOnly(ref Named) bool { + if _, ok := ref.(NamedTagged); ok { + return false + } + if _, ok := ref.(Canonical); ok { + return false + } + return true +} + +// FamiliarName returns the familiar name string +// for the given named, familiarizing if needed. +func FamiliarName(ref Named) string { + if nn, ok := ref.(normalizedNamed); ok { + return nn.Familiar().Name() + } + return ref.Name() +} + +// FamiliarString returns the familiar string representation +// for the given reference, familiarizing if needed. +func FamiliarString(ref Reference) string { + if nn, ok := ref.(normalizedNamed); ok { + return nn.Familiar().String() + } + return ref.String() +} + +// FamiliarMatch reports whether ref matches the specified pattern. +// See https://godoc.org/path#Match for supported patterns. +func FamiliarMatch(pattern string, ref Reference) (bool, error) { + matched, err := path.Match(pattern, FamiliarString(ref)) + if namedRef, isNamed := ref.(Named); isNamed && !matched { + matched, _ = path.Match(pattern, FamiliarName(namedRef)) + } + return matched, err +} diff --git a/vendor/github.com/containers/image/docker/reference/normalize.go b/vendor/github.com/containers/image/docker/reference/normalize.go new file mode 100644 index 00000000..fcc436a3 --- /dev/null +++ b/vendor/github.com/containers/image/docker/reference/normalize.go @@ -0,0 +1,152 @@ +package reference + +import ( + "errors" + "fmt" + "strings" + + "github.com/opencontainers/go-digest" +) + +var ( + legacyDefaultDomain = "index.docker.io" + defaultDomain = "docker.io" + officialRepoName = "library" + defaultTag = "latest" +) + +// normalizedNamed represents a name which has been +// normalized and has a familiar form. A familiar name +// is what is used in Docker UI. An example normalized +// name is "docker.io/library/ubuntu" and corresponding +// familiar name of "ubuntu". +type normalizedNamed interface { + Named + Familiar() Named +} + +// ParseNormalizedNamed parses a string into a named reference +// transforming a familiar name from Docker UI to a fully +// qualified reference. If the value may be an identifier +// use ParseAnyReference. +func ParseNormalizedNamed(s string) (Named, error) { + if ok := anchoredIdentifierRegexp.MatchString(s); ok { + return nil, fmt.Errorf("invalid repository name (%s), cannot specify 64-byte hexadecimal strings", s) + } + domain, remainder := splitDockerDomain(s) + var remoteName string + if tagSep := strings.IndexRune(remainder, ':'); tagSep > -1 { + remoteName = remainder[:tagSep] + } else { + remoteName = remainder + } + if strings.ToLower(remoteName) != remoteName { + return nil, errors.New("invalid reference format: repository name must be lowercase") + } + + ref, err := Parse(domain + "/" + remainder) + if err != nil { + return nil, err + } + named, isNamed := ref.(Named) + if !isNamed { + return nil, fmt.Errorf("reference %s has no name", ref.String()) + } + return named, nil +} + +// splitDockerDomain splits a repository name to domain and remotename string. +// If no valid domain is found, the default domain is used. Repository name +// needs to be already validated before. +func splitDockerDomain(name string) (domain, remainder string) { + i := strings.IndexRune(name, '/') + if i == -1 || (!strings.ContainsAny(name[:i], ".:") && name[:i] != "localhost") { + domain, remainder = defaultDomain, name + } else { + domain, remainder = name[:i], name[i+1:] + } + if domain == legacyDefaultDomain { + domain = defaultDomain + } + if domain == defaultDomain && !strings.ContainsRune(remainder, '/') { + remainder = officialRepoName + "/" + remainder + } + return +} + +// familiarizeName returns a shortened version of the name familiar +// to to the Docker UI. Familiar names have the default domain +// "docker.io" and "library/" repository prefix removed. +// For example, "docker.io/library/redis" will have the familiar +// name "redis" and "docker.io/dmcgowan/myapp" will be "dmcgowan/myapp". +// Returns a familiarized named only reference. +func familiarizeName(named namedRepository) repository { + repo := repository{ + domain: named.Domain(), + path: named.Path(), + } + + if repo.domain == defaultDomain { + repo.domain = "" + // Handle official repositories which have the pattern "library/" + if split := strings.Split(repo.path, "/"); len(split) == 2 && split[0] == officialRepoName { + repo.path = split[1] + } + } + return repo +} + +func (r reference) Familiar() Named { + return reference{ + namedRepository: familiarizeName(r.namedRepository), + tag: r.tag, + digest: r.digest, + } +} + +func (r repository) Familiar() Named { + return familiarizeName(r) +} + +func (t taggedReference) Familiar() Named { + return taggedReference{ + namedRepository: familiarizeName(t.namedRepository), + tag: t.tag, + } +} + +func (c canonicalReference) Familiar() Named { + return canonicalReference{ + namedRepository: familiarizeName(c.namedRepository), + digest: c.digest, + } +} + +// TagNameOnly adds the default tag "latest" to a reference if it only has +// a repo name. +func TagNameOnly(ref Named) Named { + if IsNameOnly(ref) { + namedTagged, err := WithTag(ref, defaultTag) + if err != nil { + // Default tag must be valid, to create a NamedTagged + // type with non-validated input the WithTag function + // should be used instead + panic(err) + } + return namedTagged + } + return ref +} + +// ParseAnyReference parses a reference string as a possible identifier, +// full digest, or familiar name. +func ParseAnyReference(ref string) (Reference, error) { + if ok := anchoredIdentifierRegexp.MatchString(ref); ok { + return digestReference("sha256:" + ref), nil + } + if dgst, err := digest.Parse(ref); err == nil { + return digestReference(dgst), nil + } + + return ParseNormalizedNamed(ref) +} diff --git a/vendor/github.com/containers/image/docker/reference/normalize_test.go b/vendor/github.com/containers/image/docker/reference/normalize_test.go new file mode 100644 index 00000000..064ee749 --- /dev/null +++ b/vendor/github.com/containers/image/docker/reference/normalize_test.go @@ -0,0 +1,573 @@ +package reference + +import ( + "strconv" + "testing" + + "github.com/opencontainers/go-digest" +) + +func TestValidateReferenceName(t *testing.T) { + validRepoNames := []string{ + "docker/docker", + "library/debian", + "debian", + "docker.io/docker/docker", + "docker.io/library/debian", + "docker.io/debian", + "index.docker.io/docker/docker", + "index.docker.io/library/debian", + "index.docker.io/debian", + "127.0.0.1:5000/docker/docker", + "127.0.0.1:5000/library/debian", + "127.0.0.1:5000/debian", + "thisisthesongthatneverendsitgoesonandonandonthisisthesongthatnev", + + // This test case was moved from invalid to valid since it is valid input + // when specified with a hostname, it removes the ambiguity from about + // whether the value is an identifier or repository name + "docker.io/1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a", + } + invalidRepoNames := []string{ + "https://github.com/docker/docker", + "docker/Docker", + "-docker", + "-docker/docker", + "-docker.io/docker/docker", + "docker///docker", + "docker.io/docker/Docker", + "docker.io/docker///docker", + "1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a", + } + + for _, name := range invalidRepoNames { + _, err := ParseNormalizedNamed(name) + if err == nil { + t.Fatalf("Expected invalid repo name for %q", name) + } + } + + for _, name := range validRepoNames { + _, err := ParseNormalizedNamed(name) + if err != nil { + t.Fatalf("Error parsing repo name %s, got: %q", name, err) + } + } +} + +func TestValidateRemoteName(t *testing.T) { + validRepositoryNames := []string{ + // Sanity check. + "docker/docker", + + // Allow 64-character non-hexadecimal names (hexadecimal names are forbidden). + "thisisthesongthatneverendsitgoesonandonandonthisisthesongthatnev", + + // Allow embedded hyphens. + "docker-rules/docker", + + // Allow multiple hyphens as well. + "docker---rules/docker", + + //Username doc and image name docker being tested. + "doc/docker", + + // single character names are now allowed. + "d/docker", + "jess/t", + + // Consecutive underscores. + "dock__er/docker", + } + for _, repositoryName := range validRepositoryNames { + _, err := ParseNormalizedNamed(repositoryName) + if err != nil { + t.Errorf("Repository name should be valid: %v. Error: %v", repositoryName, err) + } + } + + invalidRepositoryNames := []string{ + // Disallow capital letters. + "docker/Docker", + + // Only allow one slash. + "docker///docker", + + // Disallow 64-character hexadecimal. + "1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a", + + // Disallow leading and trailing hyphens in namespace. + "-docker/docker", + "docker-/docker", + "-docker-/docker", + + // Don't allow underscores everywhere (as opposed to hyphens). + "____/____", + + "_docker/_docker", + + // Disallow consecutive periods. + "dock..er/docker", + "dock_.er/docker", + "dock-.er/docker", + + // No repository. + "docker/", + + //namespace too long + "this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255/docker", + } + for _, repositoryName := range invalidRepositoryNames { + if _, err := ParseNormalizedNamed(repositoryName); err == nil { + t.Errorf("Repository name should be invalid: %v", repositoryName) + } + } +} + +func TestParseRepositoryInfo(t *testing.T) { + type tcase struct { + RemoteName, FamiliarName, FullName, AmbiguousName, Domain string + } + + tcases := []tcase{ + { + RemoteName: "fooo/bar", + FamiliarName: "fooo/bar", + FullName: "docker.io/fooo/bar", + AmbiguousName: "index.docker.io/fooo/bar", + Domain: "docker.io", + }, + { + RemoteName: "library/ubuntu", + FamiliarName: "ubuntu", + FullName: "docker.io/library/ubuntu", + AmbiguousName: "library/ubuntu", + Domain: "docker.io", + }, + { + RemoteName: "nonlibrary/ubuntu", + FamiliarName: "nonlibrary/ubuntu", + FullName: "docker.io/nonlibrary/ubuntu", + AmbiguousName: "", + Domain: "docker.io", + }, + { + RemoteName: "other/library", + FamiliarName: "other/library", + FullName: "docker.io/other/library", + AmbiguousName: "", + Domain: "docker.io", + }, + { + RemoteName: "private/moonbase", + FamiliarName: "127.0.0.1:8000/private/moonbase", + FullName: "127.0.0.1:8000/private/moonbase", + AmbiguousName: "", + Domain: "127.0.0.1:8000", + }, + { + RemoteName: "privatebase", + FamiliarName: "127.0.0.1:8000/privatebase", + FullName: "127.0.0.1:8000/privatebase", + AmbiguousName: "", + Domain: "127.0.0.1:8000", + }, + { + RemoteName: "private/moonbase", + FamiliarName: "example.com/private/moonbase", + FullName: "example.com/private/moonbase", + AmbiguousName: "", + Domain: "example.com", + }, + { + RemoteName: "privatebase", + FamiliarName: "example.com/privatebase", + FullName: "example.com/privatebase", + AmbiguousName: "", + Domain: "example.com", + }, + { + RemoteName: "private/moonbase", + FamiliarName: "example.com:8000/private/moonbase", + FullName: "example.com:8000/private/moonbase", + AmbiguousName: "", + Domain: "example.com:8000", + }, + { + RemoteName: "privatebasee", + FamiliarName: "example.com:8000/privatebasee", + FullName: "example.com:8000/privatebasee", + AmbiguousName: "", + Domain: "example.com:8000", + }, + { + RemoteName: "library/ubuntu-12.04-base", + FamiliarName: "ubuntu-12.04-base", + FullName: "docker.io/library/ubuntu-12.04-base", + AmbiguousName: "index.docker.io/library/ubuntu-12.04-base", + Domain: "docker.io", + }, + { + RemoteName: "library/foo", + FamiliarName: "foo", + FullName: "docker.io/library/foo", + AmbiguousName: "docker.io/foo", + Domain: "docker.io", + }, + { + RemoteName: "library/foo/bar", + FamiliarName: "library/foo/bar", + FullName: "docker.io/library/foo/bar", + AmbiguousName: "", + Domain: "docker.io", + }, + { + RemoteName: "store/foo/bar", + FamiliarName: "store/foo/bar", + FullName: "docker.io/store/foo/bar", + AmbiguousName: "", + Domain: "docker.io", + }, + } + + for _, tcase := range tcases { + refStrings := []string{tcase.FamiliarName, tcase.FullName} + if tcase.AmbiguousName != "" { + refStrings = append(refStrings, tcase.AmbiguousName) + } + + var refs []Named + for _, r := range refStrings { + named, err := ParseNormalizedNamed(r) + if err != nil { + t.Fatal(err) + } + refs = append(refs, named) + } + + for _, r := range refs { + if expected, actual := tcase.FamiliarName, FamiliarName(r); expected != actual { + t.Fatalf("Invalid normalized reference for %q. Expected %q, got %q", r, expected, actual) + } + if expected, actual := tcase.FullName, r.String(); expected != actual { + t.Fatalf("Invalid canonical reference for %q. Expected %q, got %q", r, expected, actual) + } + if expected, actual := tcase.Domain, Domain(r); expected != actual { + t.Fatalf("Invalid domain for %q. Expected %q, got %q", r, expected, actual) + } + if expected, actual := tcase.RemoteName, Path(r); expected != actual { + t.Fatalf("Invalid remoteName for %q. Expected %q, got %q", r, expected, actual) + } + + } + } +} + +func TestParseReferenceWithTagAndDigest(t *testing.T) { + shortRef := "busybox:latest@sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa" + ref, err := ParseNormalizedNamed(shortRef) + if err != nil { + t.Fatal(err) + } + if expected, actual := "docker.io/library/"+shortRef, ref.String(); actual != expected { + t.Fatalf("Invalid parsed reference for %q: expected %q, got %q", ref, expected, actual) + } + + if _, isTagged := ref.(NamedTagged); !isTagged { + t.Fatalf("Reference from %q should support tag", ref) + } + if _, isCanonical := ref.(Canonical); !isCanonical { + t.Fatalf("Reference from %q should support digest", ref) + } + if expected, actual := shortRef, FamiliarString(ref); actual != expected { + t.Fatalf("Invalid parsed reference for %q: expected %q, got %q", ref, expected, actual) + } +} + +func TestInvalidReferenceComponents(t *testing.T) { + if _, err := ParseNormalizedNamed("-foo"); err == nil { + t.Fatal("Expected WithName to detect invalid name") + } + ref, err := ParseNormalizedNamed("busybox") + if err != nil { + t.Fatal(err) + } + if _, err := WithTag(ref, "-foo"); err == nil { + t.Fatal("Expected WithName to detect invalid tag") + } + if _, err := WithDigest(ref, digest.Digest("foo")); err == nil { + t.Fatal("Expected WithDigest to detect invalid digest") + } +} + +func equalReference(r1, r2 Reference) bool { + switch v1 := r1.(type) { + case digestReference: + if v2, ok := r2.(digestReference); ok { + return v1 == v2 + } + case repository: + if v2, ok := r2.(repository); ok { + return v1 == v2 + } + case taggedReference: + if v2, ok := r2.(taggedReference); ok { + return v1 == v2 + } + case canonicalReference: + if v2, ok := r2.(canonicalReference); ok { + return v1 == v2 + } + case reference: + if v2, ok := r2.(reference); ok { + return v1 == v2 + } + } + return false +} + +func TestParseAnyReference(t *testing.T) { + tcases := []struct { + Reference string + Equivalent string + Expected Reference + }{ + { + Reference: "redis", + Equivalent: "docker.io/library/redis", + }, + { + Reference: "redis:latest", + Equivalent: "docker.io/library/redis:latest", + }, + { + Reference: "docker.io/library/redis:latest", + Equivalent: "docker.io/library/redis:latest", + }, + { + Reference: "redis@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c", + Equivalent: "docker.io/library/redis@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c", + }, + { + Reference: "docker.io/library/redis@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c", + Equivalent: "docker.io/library/redis@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c", + }, + { + Reference: "dmcgowan/myapp", + Equivalent: "docker.io/dmcgowan/myapp", + }, + { + Reference: "dmcgowan/myapp:latest", + Equivalent: "docker.io/dmcgowan/myapp:latest", + }, + { + Reference: "docker.io/mcgowan/myapp:latest", + Equivalent: "docker.io/mcgowan/myapp:latest", + }, + { + Reference: "dmcgowan/myapp@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c", + Equivalent: "docker.io/dmcgowan/myapp@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c", + }, + { + Reference: "docker.io/dmcgowan/myapp@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c", + Equivalent: "docker.io/dmcgowan/myapp@sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c", + }, + { + Reference: "dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c", + Expected: digestReference("sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"), + Equivalent: "sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c", + }, + { + Reference: "sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c", + Expected: digestReference("sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c"), + Equivalent: "sha256:dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9c", + }, + { + Reference: "dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9", + Equivalent: "docker.io/library/dbcc1c35ac38df41fd2f5e4130b32ffdb93ebae8b3dbe638c23575912276fc9", + }, + } + + for _, tcase := range tcases { + var ref Reference + var err error + ref, err = ParseAnyReference(tcase.Reference) + if err != nil { + t.Fatalf("Error parsing reference %s: %v", tcase.Reference, err) + } + if ref.String() != tcase.Equivalent { + t.Fatalf("Unexpected string: %s, expected %s", ref.String(), tcase.Equivalent) + } + + expected := tcase.Expected + if expected == nil { + expected, err = Parse(tcase.Equivalent) + if err != nil { + t.Fatalf("Error parsing reference %s: %v", tcase.Equivalent, err) + } + } + if !equalReference(ref, expected) { + t.Errorf("Unexpected reference %#v, expected %#v", ref, expected) + } + } +} + +func TestNormalizedSplitHostname(t *testing.T) { + testcases := []struct { + input string + domain string + name string + }{ + { + input: "test.com/foo", + domain: "test.com", + name: "foo", + }, + { + input: "test_com/foo", + domain: "docker.io", + name: "test_com/foo", + }, + { + input: "docker/migrator", + domain: "docker.io", + name: "docker/migrator", + }, + { + input: "test.com:8080/foo", + domain: "test.com:8080", + name: "foo", + }, + { + input: "test-com:8080/foo", + domain: "test-com:8080", + name: "foo", + }, + { + input: "foo", + domain: "docker.io", + name: "library/foo", + }, + { + input: "xn--n3h.com/foo", + domain: "xn--n3h.com", + name: "foo", + }, + { + input: "xn--n3h.com:18080/foo", + domain: "xn--n3h.com:18080", + name: "foo", + }, + { + input: "docker.io/foo", + domain: "docker.io", + name: "library/foo", + }, + { + input: "docker.io/library/foo", + domain: "docker.io", + name: "library/foo", + }, + { + input: "docker.io/library/foo/bar", + domain: "docker.io", + name: "library/foo/bar", + }, + } + for _, testcase := range testcases { + failf := func(format string, v ...interface{}) { + t.Logf(strconv.Quote(testcase.input)+": "+format, v...) + t.Fail() + } + + named, err := ParseNormalizedNamed(testcase.input) + if err != nil { + failf("error parsing name: %s", err) + } + domain, name := SplitHostname(named) + if domain != testcase.domain { + failf("unexpected domain: got %q, expected %q", domain, testcase.domain) + } + if name != testcase.name { + failf("unexpected name: got %q, expected %q", name, testcase.name) + } + } +} + +func TestMatchError(t *testing.T) { + named, err := ParseAnyReference("foo") + if err != nil { + t.Fatal(err) + } + _, err = FamiliarMatch("[-x]", named) + if err == nil { + t.Fatalf("expected an error, got nothing") + } +} + +func TestMatch(t *testing.T) { + matchCases := []struct { + reference string + pattern string + expected bool + }{ + { + reference: "foo", + pattern: "foo/**/ba[rz]", + expected: false, + }, + { + reference: "foo/any/bat", + pattern: "foo/**/ba[rz]", + expected: false, + }, + { + reference: "foo/a/bar", + pattern: "foo/**/ba[rz]", + expected: true, + }, + { + reference: "foo/b/baz", + pattern: "foo/**/ba[rz]", + expected: true, + }, + { + reference: "foo/c/baz:tag", + pattern: "foo/**/ba[rz]", + expected: true, + }, + { + reference: "foo/c/baz:tag", + pattern: "foo/*/baz:tag", + expected: true, + }, + { + reference: "foo/c/baz:tag", + pattern: "foo/c/baz:tag", + expected: true, + }, + { + reference: "example.com/foo/c/baz:tag", + pattern: "*/foo/c/baz", + expected: true, + }, + { + reference: "example.com/foo/c/baz:tag", + pattern: "example.com/foo/c/baz", + expected: true, + }, + } + for _, c := range matchCases { + named, err := ParseAnyReference(c.reference) + if err != nil { + t.Fatal(err) + } + actual, err := FamiliarMatch(c.pattern, named) + if err != nil { + t.Fatal(err) + } + if actual != c.expected { + t.Fatalf("expected %s match %s to be %v, was %v", c.reference, c.pattern, c.expected, actual) + } + } +} diff --git a/vendor/github.com/containers/image/docker/reference/reference.go b/vendor/github.com/containers/image/docker/reference/reference.go index 38c30e2d..fd3510e9 100644 --- a/vendor/github.com/containers/image/docker/reference/reference.go +++ b/vendor/github.com/containers/image/docker/reference/reference.go @@ -1,41 +1,120 @@ +// Package reference provides a general type to represent any way of referencing images within the registry. +// Its main purpose is to abstract tags and digests (content-addressable hash). +// +// Grammar +// +// reference := name [ ":" tag ] [ "@" digest ] +// name := [domain '/'] path-component ['/' path-component]* +// domain := domain-component ['.' domain-component]* [':' port-number] +// domain-component := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/ +// port-number := /[0-9]+/ +// path-component := alpha-numeric [separator alpha-numeric]* +// alpha-numeric := /[a-z0-9]+/ +// separator := /[_.]|__|[-]*/ +// +// tag := /[\w][\w.-]{0,127}/ +// +// digest := digest-algorithm ":" digest-hex +// digest-algorithm := digest-algorithm-component [ digest-algorithm-separator digest-algorithm-component ] +// digest-algorithm-separator := /[+.-_]/ +// digest-algorithm-component := /[A-Za-z][A-Za-z0-9]*/ +// digest-hex := /[0-9a-fA-F]{32,}/ ; At least 128 bit digest value +// +// identifier := /[a-f0-9]{64}/ +// short-identifier := /[a-f0-9]{6,64}/ package reference import ( - "regexp" + "errors" + "fmt" "strings" - // "opencontainers/go-digest" requires us to load the algorithms that we - // want to use into the binary (it calls .Available). - _ "crypto/sha256" - - distreference "github.com/docker/distribution/reference" "github.com/opencontainers/go-digest" - "github.com/pkg/errors" ) const ( - // DefaultTag defines the default tag used when performing images related actions and no tag or digest is specified - DefaultTag = "latest" - // DefaultHostname is the default built-in hostname - DefaultHostname = "docker.io" - // LegacyDefaultHostname is automatically converted to DefaultHostname - LegacyDefaultHostname = "index.docker.io" - // DefaultRepoPrefix is the prefix used for default repositories in default host - DefaultRepoPrefix = "library/" + // NameTotalLengthMax is the maximum total number of characters in a repository name. + NameTotalLengthMax = 255 ) +var ( + // ErrReferenceInvalidFormat represents an error while trying to parse a string as a reference. + ErrReferenceInvalidFormat = errors.New("invalid reference format") + + // ErrTagInvalidFormat represents an error while trying to parse a string as a tag. + ErrTagInvalidFormat = errors.New("invalid tag format") + + // ErrDigestInvalidFormat represents an error while trying to parse a string as a tag. + ErrDigestInvalidFormat = errors.New("invalid digest format") + + // ErrNameContainsUppercase is returned for invalid repository names that contain uppercase characters. + ErrNameContainsUppercase = errors.New("repository name must be lowercase") + + // ErrNameEmpty is returned for empty, invalid repository names. + ErrNameEmpty = errors.New("repository name must have at least one component") + + // ErrNameTooLong is returned when a repository name is longer than NameTotalLengthMax. + ErrNameTooLong = fmt.Errorf("repository name must not be more than %v characters", NameTotalLengthMax) + + // ErrNameNotCanonical is returned when a name is not canonical. + ErrNameNotCanonical = errors.New("repository name must be canonical") +) + +// Reference is an opaque object reference identifier that may include +// modifiers such as a hostname, name, tag, and digest. +type Reference interface { + // String returns the full reference + String() string +} + +// Field provides a wrapper type for resolving correct reference types when +// working with encoding. +type Field struct { + reference Reference +} + +// AsField wraps a reference in a Field for encoding. +func AsField(reference Reference) Field { + return Field{reference} +} + +// Reference unwraps the reference type from the field to +// return the Reference object. This object should be +// of the appropriate type to further check for different +// reference types. +func (f Field) Reference() Reference { + return f.reference +} + +// MarshalText serializes the field to byte text which +// is the string of the reference. +func (f Field) MarshalText() (p []byte, err error) { + return []byte(f.reference.String()), nil +} + +// UnmarshalText parses text bytes by invoking the +// reference parser to ensure the appropriately +// typed reference object is wrapped by field. +func (f *Field) UnmarshalText(p []byte) error { + r, err := Parse(string(p)) + if err != nil { + return err + } + + f.reference = r + return nil +} + // Named is an object with a full name type Named interface { - // Name returns normalized repository name, like "ubuntu". + Reference Name() string - // String returns full reference, like "ubuntu@sha256:abcdef..." - String() string - // FullName returns full repository name with hostname, like "docker.io/library/ubuntu" - FullName() string - // Hostname returns hostname for the reference, like "docker.io" - Hostname() string - // RemoteName returns the repository component of the full name, like "library/ubuntu" - RemoteName() string +} + +// Tagged is an object which has a tag +type Tagged interface { + Reference + Tag() string } // NamedTagged is an object including a name and tag. @@ -44,174 +123,311 @@ type NamedTagged interface { Tag() string } +// Digested is an object which has a digest +// in which it can be referenced by +type Digested interface { + Reference + Digest() digest.Digest +} + // Canonical reference is an object with a fully unique -// name including a name with hostname and digest +// name including a name with domain and digest type Canonical interface { Named Digest() digest.Digest } -// ParseNamed parses s and returns a syntactically valid reference implementing -// the Named interface. The reference must have a name, otherwise an error is -// returned. +// namedRepository is a reference to a repository with a name. +// A namedRepository has both domain and path components. +type namedRepository interface { + Named + Domain() string + Path() string +} + +// Domain returns the domain part of the Named reference +func Domain(named Named) string { + if r, ok := named.(namedRepository); ok { + return r.Domain() + } + domain, _ := splitDomain(named.Name()) + return domain +} + +// Path returns the name without the domain part of the Named reference +func Path(named Named) (name string) { + if r, ok := named.(namedRepository); ok { + return r.Path() + } + _, path := splitDomain(named.Name()) + return path +} + +func splitDomain(name string) (string, string) { + match := anchoredNameRegexp.FindStringSubmatch(name) + if len(match) != 3 { + return "", name + } + return match[1], match[2] +} + +// SplitHostname splits a named reference into a +// hostname and name string. If no valid hostname is +// found, the hostname is empty and the full value +// is returned as name +// DEPRECATED: Use Domain or Path +func SplitHostname(named Named) (string, string) { + if r, ok := named.(namedRepository); ok { + return r.Domain(), r.Path() + } + return splitDomain(named.Name()) +} + +// Parse parses s and returns a syntactically valid Reference. // If an error was encountered it is returned, along with a nil Reference. -func ParseNamed(s string) (Named, error) { - named, err := distreference.ParseNormalizedNamed(s) - if err != nil { - return nil, errors.Wrapf(err, "Error parsing reference: %q is not a valid repository/tag", s) +// NOTE: Parse will not handle short digests. +func Parse(s string) (Reference, error) { + matches := ReferenceRegexp.FindStringSubmatch(s) + if matches == nil { + if s == "" { + return nil, ErrNameEmpty + } + if ReferenceRegexp.FindStringSubmatch(strings.ToLower(s)) != nil { + return nil, ErrNameContainsUppercase + } + return nil, ErrReferenceInvalidFormat } - r, err := WithName(named.Name()) - if err != nil { - return nil, err + + if len(matches[1]) > NameTotalLengthMax { + return nil, ErrNameTooLong } - if canonical, isCanonical := named.(distreference.Canonical); isCanonical { - r, err := distreference.WithDigest(r, canonical.Digest()) + + var repo repository + + nameMatch := anchoredNameRegexp.FindStringSubmatch(matches[1]) + if nameMatch != nil && len(nameMatch) == 3 { + repo.domain = nameMatch[1] + repo.path = nameMatch[2] + } else { + repo.domain = "" + repo.path = matches[1] + } + + ref := reference{ + namedRepository: repo, + tag: matches[2], + } + if matches[3] != "" { + var err error + ref.digest, err = digest.Parse(matches[3]) if err != nil { return nil, err } - return &canonicalRef{namedRef{r}}, nil } - if tagged, isTagged := named.(distreference.NamedTagged); isTagged { - return WithTag(r, tagged.Tag()) + + r := getBestReferenceType(ref) + if r == nil { + return nil, ErrNameEmpty } + return r, nil } +// ParseNamed parses s and returns a syntactically valid reference implementing +// the Named interface. The reference must have a name and be in the canonical +// form, otherwise an error is returned. +// If an error was encountered it is returned, along with a nil Reference. +// NOTE: ParseNamed will not handle short digests. +func ParseNamed(s string) (Named, error) { + named, err := ParseNormalizedNamed(s) + if err != nil { + return nil, err + } + if named.String() != s { + return nil, ErrNameNotCanonical + } + return named, nil +} + // WithName returns a named object representing the given string. If the input // is invalid ErrReferenceInvalidFormat will be returned. func WithName(name string) (Named, error) { - name, err := normalize(name) - if err != nil { - return nil, err + if len(name) > NameTotalLengthMax { + return nil, ErrNameTooLong } - if err := validateName(name); err != nil { - return nil, err + + match := anchoredNameRegexp.FindStringSubmatch(name) + if match == nil || len(match) != 3 { + return nil, ErrReferenceInvalidFormat } - r, err := distreference.WithName(name) - if err != nil { - return nil, err - } - return &namedRef{r}, nil + return repository{ + domain: match[1], + path: match[2], + }, nil } // WithTag combines the name from "name" and the tag from "tag" to form a // reference incorporating both the name and the tag. func WithTag(name Named, tag string) (NamedTagged, error) { - r, err := distreference.WithTag(name, tag) - if err != nil { - return nil, err + if !anchoredTagRegexp.MatchString(tag) { + return nil, ErrTagInvalidFormat } - return &taggedRef{namedRef{r}}, nil -} - -type namedRef struct { - distreference.Named -} -type taggedRef struct { - namedRef -} -type canonicalRef struct { - namedRef -} - -func (r *namedRef) FullName() string { - hostname, remoteName := splitHostname(r.Name()) - return hostname + "/" + remoteName -} -func (r *namedRef) Hostname() string { - hostname, _ := splitHostname(r.Name()) - return hostname -} -func (r *namedRef) RemoteName() string { - _, remoteName := splitHostname(r.Name()) - return remoteName -} -func (r *taggedRef) Tag() string { - return r.namedRef.Named.(distreference.NamedTagged).Tag() -} -func (r *canonicalRef) Digest() digest.Digest { - return digest.Digest(r.namedRef.Named.(distreference.Canonical).Digest()) -} - -// WithDefaultTag adds a default tag to a reference if it only has a repo name. -func WithDefaultTag(ref Named) Named { - if IsNameOnly(ref) { - ref, _ = WithTag(ref, DefaultTag) + var repo repository + if r, ok := name.(namedRepository); ok { + repo.domain = r.Domain() + repo.path = r.Path() + } else { + repo.path = name.Name() } + if canonical, ok := name.(Canonical); ok { + return reference{ + namedRepository: repo, + tag: tag, + digest: canonical.Digest(), + }, nil + } + return taggedReference{ + namedRepository: repo, + tag: tag, + }, nil +} + +// WithDigest combines the name from "name" and the digest from "digest" to form +// a reference incorporating both the name and the digest. +func WithDigest(name Named, digest digest.Digest) (Canonical, error) { + if !anchoredDigestRegexp.MatchString(digest.String()) { + return nil, ErrDigestInvalidFormat + } + var repo repository + if r, ok := name.(namedRepository); ok { + repo.domain = r.Domain() + repo.path = r.Path() + } else { + repo.path = name.Name() + } + if tagged, ok := name.(Tagged); ok { + return reference{ + namedRepository: repo, + tag: tagged.Tag(), + digest: digest, + }, nil + } + return canonicalReference{ + namedRepository: repo, + digest: digest, + }, nil +} + +// TrimNamed removes any tag or digest from the named reference. +func TrimNamed(ref Named) Named { + domain, path := SplitHostname(ref) + return repository{ + domain: domain, + path: path, + } +} + +func getBestReferenceType(ref reference) Reference { + if ref.Name() == "" { + // Allow digest only references + if ref.digest != "" { + return digestReference(ref.digest) + } + return nil + } + if ref.tag == "" { + if ref.digest != "" { + return canonicalReference{ + namedRepository: ref.namedRepository, + digest: ref.digest, + } + } + return ref.namedRepository + } + if ref.digest == "" { + return taggedReference{ + namedRepository: ref.namedRepository, + tag: ref.tag, + } + } + return ref } -// IsNameOnly returns true if reference only contains a repo name. -func IsNameOnly(ref Named) bool { - if _, ok := ref.(NamedTagged); ok { - return false - } - if _, ok := ref.(Canonical); ok { - return false - } - return true +type reference struct { + namedRepository + tag string + digest digest.Digest } -// ParseIDOrReference parses string for an image ID or a reference. ID can be -// without a default prefix. -func ParseIDOrReference(idOrRef string) (digest.Digest, Named, error) { - if err := validateID(idOrRef); err == nil { - idOrRef = "sha256:" + idOrRef - } - if dgst, err := digest.Parse(idOrRef); err == nil { - return dgst, nil, nil - } - ref, err := ParseNamed(idOrRef) - return "", ref, err +func (r reference) String() string { + return r.Name() + ":" + r.tag + "@" + r.digest.String() } -// splitHostname splits a repository name to hostname and remotename string. -// If no valid hostname is found, the default hostname is used. Repository name -// needs to be already validated before. -func splitHostname(name string) (hostname, remoteName string) { - i := strings.IndexRune(name, '/') - if i == -1 || (!strings.ContainsAny(name[:i], ".:") && name[:i] != "localhost") { - hostname, remoteName = DefaultHostname, name - } else { - hostname, remoteName = name[:i], name[i+1:] - } - if hostname == LegacyDefaultHostname { - hostname = DefaultHostname - } - if hostname == DefaultHostname && !strings.ContainsRune(remoteName, '/') { - remoteName = DefaultRepoPrefix + remoteName - } - return +func (r reference) Tag() string { + return r.tag } -// normalize returns a repository name in its normalized form, meaning it -// will not contain default hostname nor library/ prefix for official images. -func normalize(name string) (string, error) { - host, remoteName := splitHostname(name) - if strings.ToLower(remoteName) != remoteName { - return "", errors.New("invalid reference format: repository name must be lowercase") - } - if host == DefaultHostname { - if strings.HasPrefix(remoteName, DefaultRepoPrefix) { - return strings.TrimPrefix(remoteName, DefaultRepoPrefix), nil - } - return remoteName, nil - } - return name, nil +func (r reference) Digest() digest.Digest { + return r.digest } -var validHex = regexp.MustCompile(`^([a-f0-9]{64})$`) - -func validateID(id string) error { - if ok := validHex.MatchString(id); !ok { - return errors.Errorf("image ID %q is invalid", id) - } - return nil +type repository struct { + domain string + path string } -func validateName(name string) error { - if err := validateID(name); err == nil { - return errors.Errorf("Invalid repository name (%s), cannot specify 64-byte hexadecimal strings", name) - } - return nil +func (r repository) String() string { + return r.Name() +} + +func (r repository) Name() string { + if r.domain == "" { + return r.path + } + return r.domain + "/" + r.path +} + +func (r repository) Domain() string { + return r.domain +} + +func (r repository) Path() string { + return r.path +} + +type digestReference digest.Digest + +func (d digestReference) String() string { + return digest.Digest(d).String() +} + +func (d digestReference) Digest() digest.Digest { + return digest.Digest(d) +} + +type taggedReference struct { + namedRepository + tag string +} + +func (t taggedReference) String() string { + return t.Name() + ":" + t.tag +} + +func (t taggedReference) Tag() string { + return t.tag +} + +type canonicalReference struct { + namedRepository + digest digest.Digest +} + +func (c canonicalReference) String() string { + return c.Name() + "@" + c.digest.String() +} + +func (c canonicalReference) Digest() digest.Digest { + return c.digest } diff --git a/vendor/github.com/containers/image/docker/reference/reference_test.go b/vendor/github.com/containers/image/docker/reference/reference_test.go index 79aa829f..16b871f9 100644 --- a/vendor/github.com/containers/image/docker/reference/reference_test.go +++ b/vendor/github.com/containers/image/docker/reference/reference_test.go @@ -1,272 +1,659 @@ package reference import ( + _ "crypto/sha256" + _ "crypto/sha512" + "encoding/json" + "strconv" + "strings" "testing" - _ "crypto/sha256" + "github.com/opencontainers/go-digest" ) -func TestValidateReferenceName(t *testing.T) { - validRepoNames := []string{ - "docker/docker", - "library/debian", - "debian", - "docker.io/docker/docker", - "docker.io/library/debian", - "docker.io/debian", - "index.docker.io/docker/docker", - "index.docker.io/library/debian", - "index.docker.io/debian", - "127.0.0.1:5000/docker/docker", - "127.0.0.1:5000/library/debian", - "127.0.0.1:5000/debian", - "thisisthesongthatneverendsitgoesonandonandonthisisthesongthatnev", - } - invalidRepoNames := []string{ - "https://github.com/docker/docker", - "docker/Docker", - "-docker", - "-docker/docker", - "-docker.io/docker/docker", - "docker///docker", - "docker.io/docker/Docker", - "docker.io/docker///docker", - "1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a", - "docker.io/1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a", +func TestReferenceParse(t *testing.T) { + // referenceTestcases is a unified set of testcases for + // testing the parsing of references + referenceTestcases := []struct { + // input is the repository name or name component testcase + input string + // err is the error expected from Parse, or nil + err error + // repository is the string representation for the reference + repository string + // domain is the domain expected in the reference + domain string + // tag is the tag for the reference + tag string + // digest is the digest for the reference (enforces digest reference) + digest string + }{ + { + input: "test_com", + repository: "test_com", + }, + { + input: "test.com:tag", + repository: "test.com", + tag: "tag", + }, + { + input: "test.com:5000", + repository: "test.com", + tag: "5000", + }, + { + input: "test.com/repo:tag", + domain: "test.com", + repository: "test.com/repo", + tag: "tag", + }, + { + input: "test:5000/repo", + domain: "test:5000", + repository: "test:5000/repo", + }, + { + input: "test:5000/repo:tag", + domain: "test:5000", + repository: "test:5000/repo", + tag: "tag", + }, + { + input: "test:5000/repo@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + domain: "test:5000", + repository: "test:5000/repo", + digest: "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + }, + { + input: "test:5000/repo:tag@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + domain: "test:5000", + repository: "test:5000/repo", + tag: "tag", + digest: "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + }, + { + input: "test:5000/repo", + domain: "test:5000", + repository: "test:5000/repo", + }, + { + input: "", + err: ErrNameEmpty, + }, + { + input: ":justtag", + err: ErrReferenceInvalidFormat, + }, + { + input: "@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + err: ErrReferenceInvalidFormat, + }, + { + input: "repo@sha256:ffffffffffffffffffffffffffffffffff", + err: digest.ErrDigestInvalidLength, + }, + { + input: "validname@invaliddigest:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + err: digest.ErrDigestUnsupported, + }, + { + input: "Uppercase:tag", + err: ErrNameContainsUppercase, + }, + // FIXME "Uppercase" is incorrectly handled as a domain-name here, therefore passes. + // See https://github.com/docker/distribution/pull/1778, and https://github.com/docker/docker/pull/20175 + //{ + // input: "Uppercase/lowercase:tag", + // err: ErrNameContainsUppercase, + //}, + { + input: "test:5000/Uppercase/lowercase:tag", + err: ErrNameContainsUppercase, + }, + { + input: "lowercase:Uppercase", + repository: "lowercase", + tag: "Uppercase", + }, + { + input: strings.Repeat("a/", 128) + "a:tag", + err: ErrNameTooLong, + }, + { + input: strings.Repeat("a/", 127) + "a:tag-puts-this-over-max", + domain: "a", + repository: strings.Repeat("a/", 127) + "a", + tag: "tag-puts-this-over-max", + }, + { + input: "aa/asdf$$^/aa", + err: ErrReferenceInvalidFormat, + }, + { + input: "sub-dom1.foo.com/bar/baz/quux", + domain: "sub-dom1.foo.com", + repository: "sub-dom1.foo.com/bar/baz/quux", + }, + { + input: "sub-dom1.foo.com/bar/baz/quux:some-long-tag", + domain: "sub-dom1.foo.com", + repository: "sub-dom1.foo.com/bar/baz/quux", + tag: "some-long-tag", + }, + { + input: "b.gcr.io/test.example.com/my-app:test.example.com", + domain: "b.gcr.io", + repository: "b.gcr.io/test.example.com/my-app", + tag: "test.example.com", + }, + { + input: "xn--n3h.com/myimage:xn--n3h.com", // ☃.com in punycode + domain: "xn--n3h.com", + repository: "xn--n3h.com/myimage", + tag: "xn--n3h.com", + }, + { + input: "xn--7o8h.com/myimage:xn--7o8h.com@sha512:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", // 🐳.com in punycode + domain: "xn--7o8h.com", + repository: "xn--7o8h.com/myimage", + tag: "xn--7o8h.com", + digest: "sha512:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + }, + { + input: "foo_bar.com:8080", + repository: "foo_bar.com", + tag: "8080", + }, + { + input: "foo/foo_bar.com:8080", + domain: "foo", + repository: "foo/foo_bar.com", + tag: "8080", + }, } + for _, testcase := range referenceTestcases { + failf := func(format string, v ...interface{}) { + t.Logf(strconv.Quote(testcase.input)+": "+format, v...) + t.Fail() + } - for _, name := range invalidRepoNames { - _, err := ParseNamed(name) + repo, err := Parse(testcase.input) + if testcase.err != nil { + if err == nil { + failf("missing expected error: %v", testcase.err) + } else if testcase.err != err { + failf("mismatched error: got %v, expected %v", err, testcase.err) + } + continue + } else if err != nil { + failf("unexpected parse error: %v", err) + continue + } + if repo.String() != testcase.input { + failf("mismatched repo: got %q, expected %q", repo.String(), testcase.input) + } + + if named, ok := repo.(Named); ok { + if named.Name() != testcase.repository { + failf("unexpected repository: got %q, expected %q", named.Name(), testcase.repository) + } + domain, _ := SplitHostname(named) + if domain != testcase.domain { + failf("unexpected domain: got %q, expected %q", domain, testcase.domain) + } + } else if testcase.repository != "" || testcase.domain != "" { + failf("expected named type, got %T", repo) + } + + tagged, ok := repo.(Tagged) + if testcase.tag != "" { + if ok { + if tagged.Tag() != testcase.tag { + failf("unexpected tag: got %q, expected %q", tagged.Tag(), testcase.tag) + } + } else { + failf("expected tagged type, got %T", repo) + } + } else if ok { + failf("unexpected tagged type") + } + + digested, ok := repo.(Digested) + if testcase.digest != "" { + if ok { + if digested.Digest().String() != testcase.digest { + failf("unexpected digest: got %q, expected %q", digested.Digest().String(), testcase.digest) + } + } else { + failf("expected digested type, got %T", repo) + } + } else if ok { + failf("unexpected digested type") + } + + } +} + +// TestWithNameFailure tests cases where WithName should fail. Cases where it +// should succeed are covered by TestSplitHostname, below. +func TestWithNameFailure(t *testing.T) { + testcases := []struct { + input string + err error + }{ + { + input: "", + err: ErrNameEmpty, + }, + { + input: ":justtag", + err: ErrReferenceInvalidFormat, + }, + { + input: "@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + err: ErrReferenceInvalidFormat, + }, + { + input: "validname@invaliddigest:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + err: ErrReferenceInvalidFormat, + }, + { + input: strings.Repeat("a/", 128) + "a:tag", + err: ErrNameTooLong, + }, + { + input: "aa/asdf$$^/aa", + err: ErrReferenceInvalidFormat, + }, + } + for _, testcase := range testcases { + failf := func(format string, v ...interface{}) { + t.Logf(strconv.Quote(testcase.input)+": "+format, v...) + t.Fail() + } + + _, err := WithName(testcase.input) if err == nil { - t.Fatalf("Expected invalid repo name for %q", name) + failf("no error parsing name. expected: %s", testcase.err) } } +} - for _, name := range validRepoNames { - _, err := ParseNamed(name) +func TestSplitHostname(t *testing.T) { + testcases := []struct { + input string + domain string + name string + }{ + { + input: "test.com/foo", + domain: "test.com", + name: "foo", + }, + { + input: "test_com/foo", + domain: "", + name: "test_com/foo", + }, + { + input: "test:8080/foo", + domain: "test:8080", + name: "foo", + }, + { + input: "test.com:8080/foo", + domain: "test.com:8080", + name: "foo", + }, + { + input: "test-com:8080/foo", + domain: "test-com:8080", + name: "foo", + }, + { + input: "xn--n3h.com:18080/foo", + domain: "xn--n3h.com:18080", + name: "foo", + }, + } + for _, testcase := range testcases { + failf := func(format string, v ...interface{}) { + t.Logf(strconv.Quote(testcase.input)+": "+format, v...) + t.Fail() + } + + named, err := WithName(testcase.input) if err != nil { - t.Fatalf("Error parsing repo name %s, got: %q", name, err) + failf("error parsing name: %s", err) + } + domain, name := SplitHostname(named) + if domain != testcase.domain { + failf("unexpected domain: got %q, expected %q", domain, testcase.domain) + } + if name != testcase.name { + failf("unexpected name: got %q, expected %q", name, testcase.name) } } } -func TestValidateRemoteName(t *testing.T) { - validRepositoryNames := []string{ - // Sanity check. - "docker/docker", +type serializationType struct { + Description string + Field Field +} - // Allow 64-character non-hexadecimal names (hexadecimal names are forbidden). - "thisisthesongthatneverendsitgoesonandonandonthisisthesongthatnev", - - // Allow embedded hyphens. - "docker-rules/docker", - - // Allow multiple hyphens as well. - "docker---rules/docker", - - //Username doc and image name docker being tested. - "doc/docker", - - // single character names are now allowed. - "d/docker", - "jess/t", - - // Consecutive underscores. - "dock__er/docker", +func TestSerialization(t *testing.T) { + testcases := []struct { + description string + input string + name string + tag string + digest string + err error + }{ + { + description: "empty value", + err: ErrNameEmpty, + }, + { + description: "just a name", + input: "example.com:8000/named", + name: "example.com:8000/named", + }, + { + description: "name with a tag", + input: "example.com:8000/named:tagged", + name: "example.com:8000/named", + tag: "tagged", + }, + { + description: "name with digest", + input: "other.com/named@sha256:1234567890098765432112345667890098765432112345667890098765432112", + name: "other.com/named", + digest: "sha256:1234567890098765432112345667890098765432112345667890098765432112", + }, } - for _, repositoryName := range validRepositoryNames { - _, err := ParseNamed(repositoryName) + for _, testcase := range testcases { + failf := func(format string, v ...interface{}) { + t.Logf(strconv.Quote(testcase.input)+": "+format, v...) + t.Fail() + } + + m := map[string]string{ + "Description": testcase.description, + "Field": testcase.input, + } + b, err := json.Marshal(m) if err != nil { - t.Errorf("Repository name should be valid: %v. Error: %v", repositoryName, err) + failf("error marshalling: %v", err) } - } + t := serializationType{} - invalidRepositoryNames := []string{ - // Disallow capital letters. - "docker/Docker", + if err := json.Unmarshal(b, &t); err != nil { + if testcase.err == nil { + failf("error unmarshalling: %v", err) + } + if err != testcase.err { + failf("wrong error, expected %v, got %v", testcase.err, err) + } - // Only allow one slash. - "docker///docker", - - // Disallow 64-character hexadecimal. - "1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a", - - // Disallow leading and trailing hyphens in namespace. - "-docker/docker", - "docker-/docker", - "-docker-/docker", - - // Don't allow underscores everywhere (as opposed to hyphens). - "____/____", - - "_docker/_docker", - - // Disallow consecutive periods. - "dock..er/docker", - "dock_.er/docker", - "dock-.er/docker", - - // No repository. - "docker/", - - //namespace too long - "this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255_this_is_not_a_valid_namespace_because_its_lenth_is_greater_than_255/docker", - } - for _, repositoryName := range invalidRepositoryNames { - if _, err := ParseNamed(repositoryName); err == nil { - t.Errorf("Repository name should be invalid: %v", repositoryName) + continue + } else if testcase.err != nil { + failf("expected error unmarshalling: %v", testcase.err) } + + if t.Description != testcase.description { + failf("wrong description, expected %q, got %q", testcase.description, t.Description) + } + + ref := t.Field.Reference() + + if named, ok := ref.(Named); ok { + if named.Name() != testcase.name { + failf("unexpected repository: got %q, expected %q", named.Name(), testcase.name) + } + } else if testcase.name != "" { + failf("expected named type, got %T", ref) + } + + tagged, ok := ref.(Tagged) + if testcase.tag != "" { + if ok { + if tagged.Tag() != testcase.tag { + failf("unexpected tag: got %q, expected %q", tagged.Tag(), testcase.tag) + } + } else { + failf("expected tagged type, got %T", ref) + } + } else if ok { + failf("unexpected tagged type") + } + + digested, ok := ref.(Digested) + if testcase.digest != "" { + if ok { + if digested.Digest().String() != testcase.digest { + failf("unexpected digest: got %q, expected %q", digested.Digest().String(), testcase.digest) + } + } else { + failf("expected digested type, got %T", ref) + } + } else if ok { + failf("unexpected digested type") + } + + t = serializationType{ + Description: testcase.description, + Field: AsField(ref), + } + + b2, err := json.Marshal(t) + if err != nil { + failf("error marshing serialization type: %v", err) + } + + if string(b) != string(b2) { + failf("unexpected serialized value: expected %q, got %q", string(b), string(b2)) + } + + // Ensure t.Field is not implementing "Reference" directly, getting + // around the Reference type system + var fieldInterface interface{} = t.Field + if _, ok := fieldInterface.(Reference); ok { + failf("field should not implement Reference interface") + } + } } -func TestParseRepositoryInfo(t *testing.T) { - type tcase struct { - RemoteName, NormalizedName, FullName, AmbiguousName, Hostname string - } - - tcases := []tcase{ +func TestWithTag(t *testing.T) { + testcases := []struct { + name string + digest digest.Digest + tag string + combined string + }{ { - RemoteName: "fooo/bar", - NormalizedName: "fooo/bar", - FullName: "docker.io/fooo/bar", - AmbiguousName: "index.docker.io/fooo/bar", - Hostname: "docker.io", + name: "test.com/foo", + tag: "tag", + combined: "test.com/foo:tag", }, { - RemoteName: "library/ubuntu", - NormalizedName: "ubuntu", - FullName: "docker.io/library/ubuntu", - AmbiguousName: "library/ubuntu", - Hostname: "docker.io", + name: "foo", + tag: "tag2", + combined: "foo:tag2", }, { - RemoteName: "nonlibrary/ubuntu", - NormalizedName: "nonlibrary/ubuntu", - FullName: "docker.io/nonlibrary/ubuntu", - AmbiguousName: "", - Hostname: "docker.io", + name: "test.com:8000/foo", + tag: "tag4", + combined: "test.com:8000/foo:tag4", }, { - RemoteName: "other/library", - NormalizedName: "other/library", - FullName: "docker.io/other/library", - AmbiguousName: "", - Hostname: "docker.io", + name: "test.com:8000/foo", + tag: "TAG5", + combined: "test.com:8000/foo:TAG5", }, { - RemoteName: "private/moonbase", - NormalizedName: "127.0.0.1:8000/private/moonbase", - FullName: "127.0.0.1:8000/private/moonbase", - AmbiguousName: "", - Hostname: "127.0.0.1:8000", - }, - { - RemoteName: "privatebase", - NormalizedName: "127.0.0.1:8000/privatebase", - FullName: "127.0.0.1:8000/privatebase", - AmbiguousName: "", - Hostname: "127.0.0.1:8000", - }, - { - RemoteName: "private/moonbase", - NormalizedName: "example.com/private/moonbase", - FullName: "example.com/private/moonbase", - AmbiguousName: "", - Hostname: "example.com", - }, - { - RemoteName: "privatebase", - NormalizedName: "example.com/privatebase", - FullName: "example.com/privatebase", - AmbiguousName: "", - Hostname: "example.com", - }, - { - RemoteName: "private/moonbase", - NormalizedName: "example.com:8000/private/moonbase", - FullName: "example.com:8000/private/moonbase", - AmbiguousName: "", - Hostname: "example.com:8000", - }, - { - RemoteName: "privatebasee", - NormalizedName: "example.com:8000/privatebasee", - FullName: "example.com:8000/privatebasee", - AmbiguousName: "", - Hostname: "example.com:8000", - }, - { - RemoteName: "library/ubuntu-12.04-base", - NormalizedName: "ubuntu-12.04-base", - FullName: "docker.io/library/ubuntu-12.04-base", - AmbiguousName: "index.docker.io/library/ubuntu-12.04-base", - Hostname: "docker.io", + name: "test.com:8000/foo", + digest: "sha256:1234567890098765432112345667890098765", + tag: "TAG5", + combined: "test.com:8000/foo:TAG5@sha256:1234567890098765432112345667890098765", }, } - - for _, tcase := range tcases { - refStrings := []string{tcase.NormalizedName, tcase.FullName} - if tcase.AmbiguousName != "" { - refStrings = append(refStrings, tcase.AmbiguousName) + for _, testcase := range testcases { + failf := func(format string, v ...interface{}) { + t.Logf(strconv.Quote(testcase.name)+": "+format, v...) + t.Fail() } - var refs []Named - for _, r := range refStrings { - named, err := ParseNamed(r) + named, err := WithName(testcase.name) + if err != nil { + failf("error parsing name: %s", err) + } + if testcase.digest != "" { + canonical, err := WithDigest(named, testcase.digest) if err != nil { - t.Fatal(err) + failf("error adding digest") } - refs = append(refs, named) - named, err = WithName(r) + named = canonical + } + + tagged, err := WithTag(named, testcase.tag) + if err != nil { + failf("WithTag failed: %s", err) + } + if tagged.String() != testcase.combined { + failf("unexpected: got %q, expected %q", tagged.String(), testcase.combined) + } + } +} + +func TestWithDigest(t *testing.T) { + testcases := []struct { + name string + digest digest.Digest + tag string + combined string + }{ + { + name: "test.com/foo", + digest: "sha256:1234567890098765432112345667890098765", + combined: "test.com/foo@sha256:1234567890098765432112345667890098765", + }, + { + name: "foo", + digest: "sha256:1234567890098765432112345667890098765", + combined: "foo@sha256:1234567890098765432112345667890098765", + }, + { + name: "test.com:8000/foo", + digest: "sha256:1234567890098765432112345667890098765", + combined: "test.com:8000/foo@sha256:1234567890098765432112345667890098765", + }, + { + name: "test.com:8000/foo", + digest: "sha256:1234567890098765432112345667890098765", + tag: "latest", + combined: "test.com:8000/foo:latest@sha256:1234567890098765432112345667890098765", + }, + } + for _, testcase := range testcases { + failf := func(format string, v ...interface{}) { + t.Logf(strconv.Quote(testcase.name)+": "+format, v...) + t.Fail() + } + + named, err := WithName(testcase.name) + if err != nil { + failf("error parsing name: %s", err) + } + if testcase.tag != "" { + tagged, err := WithTag(named, testcase.tag) if err != nil { - t.Fatal(err) + failf("error adding tag") } - refs = append(refs, named) + named = tagged } - - for _, r := range refs { - if expected, actual := tcase.NormalizedName, r.Name(); expected != actual { - t.Fatalf("Invalid normalized reference for %q. Expected %q, got %q", r, expected, actual) - } - if expected, actual := tcase.FullName, r.FullName(); expected != actual { - t.Fatalf("Invalid normalized reference for %q. Expected %q, got %q", r, expected, actual) - } - if expected, actual := tcase.Hostname, r.Hostname(); expected != actual { - t.Fatalf("Invalid hostname for %q. Expected %q, got %q", r, expected, actual) - } - if expected, actual := tcase.RemoteName, r.RemoteName(); expected != actual { - t.Fatalf("Invalid remoteName for %q. Expected %q, got %q", r, expected, actual) - } - + digested, err := WithDigest(named, testcase.digest) + if err != nil { + failf("WithDigest failed: %s", err) + } + if digested.String() != testcase.combined { + failf("unexpected: got %q, expected %q", digested.String(), testcase.combined) } } } -func TestParseReferenceWithTagAndDigest(t *testing.T) { - ref, err := ParseNamed("busybox:latest@sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa") - if err != nil { - t.Fatal(err) +func TestParseNamed(t *testing.T) { + testcases := []struct { + input string + domain string + name string + err error + }{ + { + input: "test.com/foo", + domain: "test.com", + name: "foo", + }, + { + input: "test:8080/foo", + domain: "test:8080", + name: "foo", + }, + { + input: "test_com/foo", + err: ErrNameNotCanonical, + }, + { + input: "test.com", + err: ErrNameNotCanonical, + }, + { + input: "foo", + err: ErrNameNotCanonical, + }, + { + input: "library/foo", + err: ErrNameNotCanonical, + }, + { + input: "docker.io/library/foo", + domain: "docker.io", + name: "library/foo", + }, + // Ambiguous case, parser will add "library/" to foo + { + input: "docker.io/foo", + err: ErrNameNotCanonical, + }, } - if _, isTagged := ref.(NamedTagged); isTagged { - t.Fatalf("Reference from %q should not support tag", ref) - } - if _, isCanonical := ref.(Canonical); !isCanonical { - t.Fatalf("Reference from %q should not support digest", ref) - } - if expected, actual := "busybox@sha256:86e0e091d0da6bde2456dbb48306f3956bbeb2eae1b5b9a43045843f69fe4aaa", ref.String(); actual != expected { - t.Fatalf("Invalid parsed reference for %q: expected %q, got %q", ref, expected, actual) - } -} + for _, testcase := range testcases { + failf := func(format string, v ...interface{}) { + t.Logf(strconv.Quote(testcase.input)+": "+format, v...) + t.Fail() + } -func TestInvalidReferenceComponents(t *testing.T) { - if _, err := WithName("-foo"); err == nil { - t.Fatal("Expected WithName to detect invalid name") - } - ref, err := WithName("busybox") - if err != nil { - t.Fatal(err) - } - if _, err := WithTag(ref, "-foo"); err == nil { - t.Fatal("Expected WithName to detect invalid tag") + named, err := ParseNamed(testcase.input) + if err != nil && testcase.err == nil { + failf("error parsing name: %s", err) + continue + } else if err == nil && testcase.err != nil { + failf("parsing succeded: expected error %v", testcase.err) + continue + } else if err != testcase.err { + failf("unexpected error %v, expected %v", err, testcase.err) + continue + } else if err != nil { + continue + } + + domain, name := SplitHostname(named) + if domain != testcase.domain { + failf("unexpected domain: got %q, expected %q", domain, testcase.domain) + } + if name != testcase.name { + failf("unexpected name: got %q, expected %q", name, testcase.name) + } } } diff --git a/vendor/github.com/containers/image/docker/reference/regexp.go b/vendor/github.com/containers/image/docker/reference/regexp.go new file mode 100644 index 00000000..405e995d --- /dev/null +++ b/vendor/github.com/containers/image/docker/reference/regexp.go @@ -0,0 +1,143 @@ +package reference + +import "regexp" + +var ( + // alphaNumericRegexp defines the alpha numeric atom, typically a + // component of names. This only allows lower case characters and digits. + alphaNumericRegexp = match(`[a-z0-9]+`) + + // separatorRegexp defines the separators allowed to be embedded in name + // components. This allow one period, one or two underscore and multiple + // dashes. + separatorRegexp = match(`(?:[._]|__|[-]*)`) + + // nameComponentRegexp restricts registry path component names to start + // with at least one letter or number, with following parts able to be + // separated by one period, one or two underscore and multiple dashes. + nameComponentRegexp = expression( + alphaNumericRegexp, + optional(repeated(separatorRegexp, alphaNumericRegexp))) + + // domainComponentRegexp restricts the registry domain component of a + // repository name to start with a component as defined by domainRegexp + // and followed by an optional port. + domainComponentRegexp = match(`(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`) + + // domainRegexp defines the structure of potential domain components + // that may be part of image names. This is purposely a subset of what is + // allowed by DNS to ensure backwards compatibility with Docker image + // names. + domainRegexp = expression( + domainComponentRegexp, + optional(repeated(literal(`.`), domainComponentRegexp)), + optional(literal(`:`), match(`[0-9]+`))) + + // TagRegexp matches valid tag names. From docker/docker:graph/tags.go. + TagRegexp = match(`[\w][\w.-]{0,127}`) + + // anchoredTagRegexp matches valid tag names, anchored at the start and + // end of the matched string. + anchoredTagRegexp = anchored(TagRegexp) + + // DigestRegexp matches valid digests. + DigestRegexp = match(`[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,}`) + + // anchoredDigestRegexp matches valid digests, anchored at the start and + // end of the matched string. + anchoredDigestRegexp = anchored(DigestRegexp) + + // NameRegexp is the format for the name component of references. The + // regexp has capturing groups for the domain and name part omitting + // the separating forward slash from either. + NameRegexp = expression( + optional(domainRegexp, literal(`/`)), + nameComponentRegexp, + optional(repeated(literal(`/`), nameComponentRegexp))) + + // anchoredNameRegexp is used to parse a name value, capturing the + // domain and trailing components. + anchoredNameRegexp = anchored( + optional(capture(domainRegexp), literal(`/`)), + capture(nameComponentRegexp, + optional(repeated(literal(`/`), nameComponentRegexp)))) + + // ReferenceRegexp is the full supported format of a reference. The regexp + // is anchored and has capturing groups for name, tag, and digest + // components. + ReferenceRegexp = anchored(capture(NameRegexp), + optional(literal(":"), capture(TagRegexp)), + optional(literal("@"), capture(DigestRegexp))) + + // IdentifierRegexp is the format for string identifier used as a + // content addressable identifier using sha256. These identifiers + // are like digests without the algorithm, since sha256 is used. + IdentifierRegexp = match(`([a-f0-9]{64})`) + + // ShortIdentifierRegexp is the format used to represent a prefix + // of an identifier. A prefix may be used to match a sha256 identifier + // within a list of trusted identifiers. + ShortIdentifierRegexp = match(`([a-f0-9]{6,64})`) + + // anchoredIdentifierRegexp is used to check or match an + // identifier value, anchored at start and end of string. + anchoredIdentifierRegexp = anchored(IdentifierRegexp) + + // anchoredShortIdentifierRegexp is used to check if a value + // is a possible identifier prefix, anchored at start and end + // of string. + anchoredShortIdentifierRegexp = anchored(ShortIdentifierRegexp) +) + +// match compiles the string to a regular expression. +var match = regexp.MustCompile + +// literal compiles s into a literal regular expression, escaping any regexp +// reserved characters. +func literal(s string) *regexp.Regexp { + re := match(regexp.QuoteMeta(s)) + + if _, complete := re.LiteralPrefix(); !complete { + panic("must be a literal") + } + + return re +} + +// expression defines a full expression, where each regular expression must +// follow the previous. +func expression(res ...*regexp.Regexp) *regexp.Regexp { + var s string + for _, re := range res { + s += re.String() + } + + return match(s) +} + +// optional wraps the expression in a non-capturing group and makes the +// production optional. +func optional(res ...*regexp.Regexp) *regexp.Regexp { + return match(group(expression(res...)).String() + `?`) +} + +// repeated wraps the regexp in a non-capturing group to get one or more +// matches. +func repeated(res ...*regexp.Regexp) *regexp.Regexp { + return match(group(expression(res...)).String() + `+`) +} + +// group wraps the regexp in a non-capturing group. +func group(res ...*regexp.Regexp) *regexp.Regexp { + return match(`(?:` + expression(res...).String() + `)`) +} + +// capture wraps the expression in a capturing group. +func capture(res ...*regexp.Regexp) *regexp.Regexp { + return match(`(` + expression(res...).String() + `)`) +} + +// anchored anchors the regular expression by adding start and end delimiters. +func anchored(res ...*regexp.Regexp) *regexp.Regexp { + return match(`^` + expression(res...).String() + `$`) +} diff --git a/vendor/github.com/containers/image/docker/reference/regexp_test.go b/vendor/github.com/containers/image/docker/reference/regexp_test.go new file mode 100644 index 00000000..c2126399 --- /dev/null +++ b/vendor/github.com/containers/image/docker/reference/regexp_test.go @@ -0,0 +1,553 @@ +package reference + +import ( + "regexp" + "strings" + "testing" +) + +type regexpMatch struct { + input string + match bool + subs []string +} + +func checkRegexp(t *testing.T, r *regexp.Regexp, m regexpMatch) { + matches := r.FindStringSubmatch(m.input) + if m.match && matches != nil { + if len(matches) != (r.NumSubexp()+1) || matches[0] != m.input { + t.Fatalf("Bad match result %#v for %q", matches, m.input) + } + if len(matches) < (len(m.subs) + 1) { + t.Errorf("Expected %d sub matches, only have %d for %q", len(m.subs), len(matches)-1, m.input) + } + for i := range m.subs { + if m.subs[i] != matches[i+1] { + t.Errorf("Unexpected submatch %d: %q, expected %q for %q", i+1, matches[i+1], m.subs[i], m.input) + } + } + } else if m.match { + t.Errorf("Expected match for %q", m.input) + } else if matches != nil { + t.Errorf("Unexpected match for %q", m.input) + } +} + +func TestDomainRegexp(t *testing.T) { + hostcases := []regexpMatch{ + { + input: "test.com", + match: true, + }, + { + input: "test.com:10304", + match: true, + }, + { + input: "test.com:http", + match: false, + }, + { + input: "localhost", + match: true, + }, + { + input: "localhost:8080", + match: true, + }, + { + input: "a", + match: true, + }, + { + input: "a.b", + match: true, + }, + { + input: "ab.cd.com", + match: true, + }, + { + input: "a-b.com", + match: true, + }, + { + input: "-ab.com", + match: false, + }, + { + input: "ab-.com", + match: false, + }, + { + input: "ab.c-om", + match: true, + }, + { + input: "ab.-com", + match: false, + }, + { + input: "ab.com-", + match: false, + }, + { + input: "0101.com", + match: true, // TODO(dmcgowan): valid if this should be allowed + }, + { + input: "001a.com", + match: true, + }, + { + input: "b.gbc.io:443", + match: true, + }, + { + input: "b.gbc.io", + match: true, + }, + { + input: "xn--n3h.com", // ☃.com in punycode + match: true, + }, + { + input: "Asdf.com", // uppercase character + match: true, + }, + } + r := regexp.MustCompile(`^` + domainRegexp.String() + `$`) + for i := range hostcases { + checkRegexp(t, r, hostcases[i]) + } +} + +func TestFullNameRegexp(t *testing.T) { + if anchoredNameRegexp.NumSubexp() != 2 { + t.Fatalf("anchored name regexp should have two submatches: %v, %v != 2", + anchoredNameRegexp, anchoredNameRegexp.NumSubexp()) + } + + testcases := []regexpMatch{ + { + input: "", + match: false, + }, + { + input: "short", + match: true, + subs: []string{"", "short"}, + }, + { + input: "simple/name", + match: true, + subs: []string{"simple", "name"}, + }, + { + input: "library/ubuntu", + match: true, + subs: []string{"library", "ubuntu"}, + }, + { + input: "docker/stevvooe/app", + match: true, + subs: []string{"docker", "stevvooe/app"}, + }, + { + input: "aa/aa/aa/aa/aa/aa/aa/aa/aa/bb/bb/bb/bb/bb/bb", + match: true, + subs: []string{"aa", "aa/aa/aa/aa/aa/aa/aa/aa/bb/bb/bb/bb/bb/bb"}, + }, + { + input: "aa/aa/bb/bb/bb", + match: true, + subs: []string{"aa", "aa/bb/bb/bb"}, + }, + { + input: "a/a/a/a", + match: true, + subs: []string{"a", "a/a/a"}, + }, + { + input: "a/a/a/a/", + match: false, + }, + { + input: "a//a/a", + match: false, + }, + { + input: "a", + match: true, + subs: []string{"", "a"}, + }, + { + input: "a/aa", + match: true, + subs: []string{"a", "aa"}, + }, + { + input: "a/aa/a", + match: true, + subs: []string{"a", "aa/a"}, + }, + { + input: "foo.com", + match: true, + subs: []string{"", "foo.com"}, + }, + { + input: "foo.com/", + match: false, + }, + { + input: "foo.com:8080/bar", + match: true, + subs: []string{"foo.com:8080", "bar"}, + }, + { + input: "foo.com:http/bar", + match: false, + }, + { + input: "foo.com/bar", + match: true, + subs: []string{"foo.com", "bar"}, + }, + { + input: "foo.com/bar/baz", + match: true, + subs: []string{"foo.com", "bar/baz"}, + }, + { + input: "localhost:8080/bar", + match: true, + subs: []string{"localhost:8080", "bar"}, + }, + { + input: "sub-dom1.foo.com/bar/baz/quux", + match: true, + subs: []string{"sub-dom1.foo.com", "bar/baz/quux"}, + }, + { + input: "blog.foo.com/bar/baz", + match: true, + subs: []string{"blog.foo.com", "bar/baz"}, + }, + { + input: "a^a", + match: false, + }, + { + input: "aa/asdf$$^/aa", + match: false, + }, + { + input: "asdf$$^/aa", + match: false, + }, + { + input: "aa-a/a", + match: true, + subs: []string{"aa-a", "a"}, + }, + { + input: strings.Repeat("a/", 128) + "a", + match: true, + subs: []string{"a", strings.Repeat("a/", 127) + "a"}, + }, + { + input: "a-/a/a/a", + match: false, + }, + { + input: "foo.com/a-/a/a", + match: false, + }, + { + input: "-foo/bar", + match: false, + }, + { + input: "foo/bar-", + match: false, + }, + { + input: "foo-/bar", + match: false, + }, + { + input: "foo/-bar", + match: false, + }, + { + input: "_foo/bar", + match: false, + }, + { + input: "foo_bar", + match: true, + subs: []string{"", "foo_bar"}, + }, + { + input: "foo_bar.com", + match: true, + subs: []string{"", "foo_bar.com"}, + }, + { + input: "foo_bar.com:8080", + match: false, + }, + { + input: "foo_bar.com:8080/app", + match: false, + }, + { + input: "foo.com/foo_bar", + match: true, + subs: []string{"foo.com", "foo_bar"}, + }, + { + input: "____/____", + match: false, + }, + { + input: "_docker/_docker", + match: false, + }, + { + input: "docker_/docker_", + match: false, + }, + { + input: "b.gcr.io/test.example.com/my-app", + match: true, + subs: []string{"b.gcr.io", "test.example.com/my-app"}, + }, + { + input: "xn--n3h.com/myimage", // ☃.com in punycode + match: true, + subs: []string{"xn--n3h.com", "myimage"}, + }, + { + input: "xn--7o8h.com/myimage", // 🐳.com in punycode + match: true, + subs: []string{"xn--7o8h.com", "myimage"}, + }, + { + input: "example.com/xn--7o8h.com/myimage", // 🐳.com in punycode + match: true, + subs: []string{"example.com", "xn--7o8h.com/myimage"}, + }, + { + input: "example.com/some_separator__underscore/myimage", + match: true, + subs: []string{"example.com", "some_separator__underscore/myimage"}, + }, + { + input: "example.com/__underscore/myimage", + match: false, + }, + { + input: "example.com/..dots/myimage", + match: false, + }, + { + input: "example.com/.dots/myimage", + match: false, + }, + { + input: "example.com/nodouble..dots/myimage", + match: false, + }, + { + input: "example.com/nodouble..dots/myimage", + match: false, + }, + { + input: "docker./docker", + match: false, + }, + { + input: ".docker/docker", + match: false, + }, + { + input: "docker-/docker", + match: false, + }, + { + input: "-docker/docker", + match: false, + }, + { + input: "do..cker/docker", + match: false, + }, + { + input: "do__cker:8080/docker", + match: false, + }, + { + input: "do__cker/docker", + match: true, + subs: []string{"", "do__cker/docker"}, + }, + { + input: "b.gcr.io/test.example.com/my-app", + match: true, + subs: []string{"b.gcr.io", "test.example.com/my-app"}, + }, + { + input: "registry.io/foo/project--id.module--name.ver---sion--name", + match: true, + subs: []string{"registry.io", "foo/project--id.module--name.ver---sion--name"}, + }, + { + input: "Asdf.com/foo/bar", // uppercase character in hostname + match: true, + }, + { + input: "Foo/FarB", // uppercase characters in remote name + match: false, + }, + } + for i := range testcases { + checkRegexp(t, anchoredNameRegexp, testcases[i]) + } +} + +func TestReferenceRegexp(t *testing.T) { + if ReferenceRegexp.NumSubexp() != 3 { + t.Fatalf("anchored name regexp should have three submatches: %v, %v != 3", + ReferenceRegexp, ReferenceRegexp.NumSubexp()) + } + + testcases := []regexpMatch{ + { + input: "registry.com:8080/myapp:tag", + match: true, + subs: []string{"registry.com:8080/myapp", "tag", ""}, + }, + { + input: "registry.com:8080/myapp@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912", + match: true, + subs: []string{"registry.com:8080/myapp", "", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"}, + }, + { + input: "registry.com:8080/myapp:tag2@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912", + match: true, + subs: []string{"registry.com:8080/myapp", "tag2", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"}, + }, + { + input: "registry.com:8080/myapp@sha256:badbadbadbad", + match: false, + }, + { + input: "registry.com:8080/myapp:invalid~tag", + match: false, + }, + { + input: "bad_hostname.com:8080/myapp:tag", + match: false, + }, + { + input:// localhost treated as name, missing tag with 8080 as tag + "localhost:8080@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912", + match: true, + subs: []string{"localhost", "8080", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"}, + }, + { + input: "localhost:8080/name@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912", + match: true, + subs: []string{"localhost:8080/name", "", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"}, + }, + { + input: "localhost:http/name@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912", + match: false, + }, + { + // localhost will be treated as an image name without a host + input: "localhost@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912", + match: true, + subs: []string{"localhost", "", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"}, + }, + { + input: "registry.com:8080/myapp@bad", + match: false, + }, + { + input: "registry.com:8080/myapp@2bad", + match: false, // TODO(dmcgowan): Support this as valid + }, + } + + for i := range testcases { + checkRegexp(t, ReferenceRegexp, testcases[i]) + } + +} + +func TestIdentifierRegexp(t *testing.T) { + fullCases := []regexpMatch{ + { + input: "da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf9821", + match: true, + }, + { + input: "7EC43B381E5AEFE6E04EFB0B3F0693FF2A4A50652D64AEC573905F2DB5889A1C", + match: false, + }, + { + input: "da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf", + match: false, + }, + { + input: "sha256:da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf9821", + match: false, + }, + { + input: "da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf98218482", + match: false, + }, + } + + shortCases := []regexpMatch{ + { + input: "da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf9821", + match: true, + }, + { + input: "7EC43B381E5AEFE6E04EFB0B3F0693FF2A4A50652D64AEC573905F2DB5889A1C", + match: false, + }, + { + input: "da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf", + match: true, + }, + { + input: "sha256:da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf9821", + match: false, + }, + { + input: "da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf98218482", + match: false, + }, + { + input: "da304", + match: false, + }, + { + input: "da304e", + match: true, + }, + } + + for i := range fullCases { + checkRegexp(t, anchoredIdentifierRegexp, fullCases[i]) + } + + for i := range shortCases { + checkRegexp(t, anchoredShortIdentifierRegexp, shortCases[i]) + } +} diff --git a/vendor/github.com/containers/image/image/docker_schema1.go b/vendor/github.com/containers/image/image/docker_schema1.go index dce81a14..7d6de1a6 100644 --- a/vendor/github.com/containers/image/image/docker_schema1.go +++ b/vendor/github.com/containers/image/image/docker_schema1.go @@ -72,7 +72,7 @@ func manifestSchema1FromManifest(manifest []byte) (genericManifest, error) { func manifestSchema1FromComponents(ref reference.Named, fsLayers []fsLayersSchema1, history []historySchema1, architecture string) genericManifest { var name, tag string if ref != nil { // Well, what to do if it _is_ nil? Most consumers actually don't use these fields nowadays, so we might as well try not supplying them. - name = ref.RemoteName() + name = reference.Path(ref) if tagged, ok := ref.(reference.NamedTagged); ok { tag = tagged.Tag() } diff --git a/vendor/github.com/containers/image/image/docker_schema2_test.go b/vendor/github.com/containers/image/image/docker_schema2_test.go index 52ef3089..9d1ab7ee 100644 --- a/vendor/github.com/containers/image/image/docker_schema2_test.go +++ b/vendor/github.com/containers/image/image/docker_schema2_test.go @@ -9,13 +9,12 @@ import ( "testing" "time" - "github.com/pkg/errors" - "github.com/containers/image/docker/reference" "github.com/containers/image/manifest" "github.com/containers/image/types" "github.com/opencontainers/go-digest" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -26,7 +25,7 @@ type unusedImageSource struct{} func (f unusedImageSource) Reference() types.ImageReference { panic("Unexpected call to a mock function") } -func (f unusedImageSource) Close() { +func (f unusedImageSource) Close() error { panic("Unexpected call to a mock function") } func (f unusedImageSource) GetManifest() ([]byte, string, error) { @@ -326,7 +325,7 @@ func newSchema2ImageSource(t *testing.T, dockerRef string) *schema2ImageSource { realConfigJSON, err := ioutil.ReadFile("fixtures/schema2-config.json") require.NoError(t, err) - ref, err := reference.ParseNamed(dockerRef) + ref, err := reference.ParseNormalizedNamed(dockerRef) require.NoError(t, err) return &schema2ImageSource{ @@ -347,7 +346,7 @@ type memoryImageDest struct { func (d *memoryImageDest) Reference() types.ImageReference { return refImageReferenceMock{d.ref} } -func (d *memoryImageDest) Close() { +func (d *memoryImageDest) Close() error { panic("Unexpected call to a mock function") } func (d *memoryImageDest) SupportedManifestMIMETypes() []string { diff --git a/vendor/github.com/containers/image/image/memory.go b/vendor/github.com/containers/image/image/memory.go index 1a3faa02..304274d1 100644 --- a/vendor/github.com/containers/image/image/memory.go +++ b/vendor/github.com/containers/image/image/memory.go @@ -32,7 +32,8 @@ func (i *memoryImage) Reference() types.ImageReference { } // Close removes resources associated with an initialized UnparsedImage, if any. -func (i *memoryImage) Close() { +func (i *memoryImage) Close() error { + return nil } // Size returns the size of the image as stored, if known, or -1 if not. diff --git a/vendor/github.com/containers/image/image/oci_test.go b/vendor/github.com/containers/image/image/oci_test.go index ac74fbec..c51b3ae7 100644 --- a/vendor/github.com/containers/image/image/oci_test.go +++ b/vendor/github.com/containers/image/image/oci_test.go @@ -9,13 +9,12 @@ import ( "testing" "time" - "github.com/pkg/errors" - "github.com/containers/image/docker/reference" "github.com/containers/image/manifest" "github.com/containers/image/types" "github.com/opencontainers/go-digest" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -260,7 +259,7 @@ func newOCI1ImageSource(t *testing.T, dockerRef string) *oci1ImageSource { realConfigJSON, err := ioutil.ReadFile("fixtures/oci1-config.json") require.NoError(t, err) - ref, err := reference.ParseNamed(dockerRef) + ref, err := reference.ParseNormalizedNamed(dockerRef) require.NoError(t, err) return &oci1ImageSource{ diff --git a/vendor/github.com/containers/image/image/unparsed.go b/vendor/github.com/containers/image/image/unparsed.go index 1e1ee0b5..446652e7 100644 --- a/vendor/github.com/containers/image/image/unparsed.go +++ b/vendor/github.com/containers/image/image/unparsed.go @@ -4,6 +4,7 @@ import ( "github.com/containers/image/docker/reference" "github.com/containers/image/manifest" "github.com/containers/image/types" + "github.com/opencontainers/go-digest" "github.com/pkg/errors" ) @@ -35,8 +36,8 @@ func (i *UnparsedImage) Reference() types.ImageReference { } // Close removes resources associated with an initialized UnparsedImage, if any. -func (i *UnparsedImage) Close() { - i.src.Close() +func (i *UnparsedImage) Close() error { + return i.src.Close() } // Manifest is like ImageSource.GetManifest, but the result is cached; it is OK to call this however often you need. @@ -52,7 +53,7 @@ func (i *UnparsedImage) Manifest() ([]byte, string, error) { ref := i.Reference().DockerReference() if ref != nil { if canonical, ok := ref.(reference.Canonical); ok { - digest := canonical.Digest() + digest := digest.Digest(canonical.Digest()) matches, err := manifest.MatchesDigest(m, digest) if err != nil { return nil, "", errors.Wrap(err, "Error computing manifest digest") diff --git a/vendor/github.com/containers/image/oci/layout/oci_dest.go b/vendor/github.com/containers/image/oci/layout/oci_dest.go index 06ae40ff..b665f87d 100644 --- a/vendor/github.com/containers/image/oci/layout/oci_dest.go +++ b/vendor/github.com/containers/image/oci/layout/oci_dest.go @@ -31,7 +31,8 @@ func (d *ociImageDestination) Reference() types.ImageReference { } // Close removes resources associated with an initialized ImageDestination, if any. -func (d *ociImageDestination) Close() { +func (d *ociImageDestination) Close() error { + return nil } func (d *ociImageDestination) SupportedManifestMIMETypes() []string { diff --git a/vendor/github.com/containers/image/oci/layout/oci_src.go b/vendor/github.com/containers/image/oci/layout/oci_src.go index 1b148d27..6ae47c26 100644 --- a/vendor/github.com/containers/image/oci/layout/oci_src.go +++ b/vendor/github.com/containers/image/oci/layout/oci_src.go @@ -6,7 +6,6 @@ import ( "io/ioutil" "os" - "github.com/containers/image/manifest" "github.com/containers/image/types" "github.com/opencontainers/go-digest" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" @@ -27,7 +26,8 @@ func (s *ociImageSource) Reference() types.ImageReference { } // Close removes resources associated with an initialized ImageSource, if any. -func (s *ociImageSource) Close() { +func (s *ociImageSource) Close() error { + return nil } // GetManifest returns the image's manifest along with its MIME type (which may be empty when it can't be determined but the manifest is available). @@ -54,7 +54,7 @@ func (s *ociImageSource) GetManifest() ([]byte, string, error) { return nil, "", err } - return m, manifest.GuessMIMEType(m), nil + return m, desc.MediaType, nil } func (s *ociImageSource) GetTargetManifest(digest digest.Digest) ([]byte, string, error) { @@ -68,7 +68,11 @@ func (s *ociImageSource) GetTargetManifest(digest digest.Digest) ([]byte, string return nil, "", err } - return m, manifest.GuessMIMEType(m), nil + // XXX: GetTargetManifest means that we don't have the context of what + // mediaType the manifest has. In OCI this means that we don't know + // what reference it came from, so we just *assume* that its + // MediaTypeImageManifest. + return m, imgspecv1.MediaTypeImageManifest, nil } // GetBlob returns a stream for the specified blob, and the blob's size. diff --git a/vendor/github.com/containers/image/oci/layout/oci_transport.go b/vendor/github.com/containers/image/oci/layout/oci_transport.go index 734af87c..007798b4 100644 --- a/vendor/github.com/containers/image/oci/layout/oci_transport.go +++ b/vendor/github.com/containers/image/oci/layout/oci_transport.go @@ -9,11 +9,16 @@ import ( "github.com/containers/image/directory/explicitfilepath" "github.com/containers/image/docker/reference" "github.com/containers/image/image" + "github.com/containers/image/transports" "github.com/containers/image/types" "github.com/opencontainers/go-digest" "github.com/pkg/errors" ) +func init() { + transports.Register(Transport) +} + // Transport is an ImageTransport for OCI directories. var Transport = ociTransport{} diff --git a/vendor/github.com/containers/image/openshift/openshift.go b/vendor/github.com/containers/image/openshift/openshift.go index 1fc0e24c..9ab905e2 100644 --- a/vendor/github.com/containers/image/openshift/openshift.go +++ b/vendor/github.com/containers/image/openshift/openshift.go @@ -13,6 +13,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/containers/image/docker" + "github.com/containers/image/docker/reference" "github.com/containers/image/manifest" "github.com/containers/image/types" "github.com/containers/image/version" @@ -153,7 +154,7 @@ func (c *openshiftClient) convertDockerImageReference(ref string) (string, error if len(parts) != 2 { return "", errors.Errorf("Invalid format of docker reference %s: missing '/'", ref) } - return c.ref.dockerReference.Hostname() + "/" + parts[1], nil + return reference.Domain(c.ref.dockerReference) + "/" + parts[1], nil } type openshiftImageSource struct { @@ -190,11 +191,15 @@ func (s *openshiftImageSource) Reference() types.ImageReference { } // Close removes resources associated with an initialized ImageSource, if any. -func (s *openshiftImageSource) Close() { +func (s *openshiftImageSource) Close() error { if s.docker != nil { - s.docker.Close() + err := s.docker.Close() s.docker = nil + + return err } + + return nil } func (s *openshiftImageSource) GetTargetManifest(digest digest.Digest) ([]byte, string, error) { @@ -305,7 +310,7 @@ func newImageDestination(ctx *types.SystemContext, ref openshiftReference) (type // FIXME: Should this always use a digest, not a tag? Uploading to Docker by tag requires the tag _inside_ the manifest to match, // i.e. a single signed image cannot be available under multiple tags. But with types.ImageDestination, we don't know // the manifest digest at this point. - dockerRefString := fmt.Sprintf("//%s/%s/%s:%s", client.ref.dockerReference.Hostname(), client.ref.namespace, client.ref.stream, client.ref.dockerReference.Tag()) + dockerRefString := fmt.Sprintf("//%s/%s/%s:%s", reference.Domain(client.ref.dockerReference), client.ref.namespace, client.ref.stream, client.ref.dockerReference.Tag()) dockerRef, err := docker.ParseReference(dockerRefString) if err != nil { return nil, err @@ -328,8 +333,8 @@ func (d *openshiftImageDestination) Reference() types.ImageReference { } // Close removes resources associated with an initialized ImageDestination, if any. -func (d *openshiftImageDestination) Close() { - d.docker.Close() +func (d *openshiftImageDestination) Close() error { + return d.docker.Close() } func (d *openshiftImageDestination) SupportedManifestMIMETypes() []string { diff --git a/vendor/github.com/containers/image/openshift/openshift_transport.go b/vendor/github.com/containers/image/openshift/openshift_transport.go index ef92a7cd..108e1102 100644 --- a/vendor/github.com/containers/image/openshift/openshift_transport.go +++ b/vendor/github.com/containers/image/openshift/openshift_transport.go @@ -8,10 +8,15 @@ import ( "github.com/containers/image/docker/policyconfiguration" "github.com/containers/image/docker/reference" genericImage "github.com/containers/image/image" + "github.com/containers/image/transports" "github.com/containers/image/types" "github.com/pkg/errors" ) +func init() { + transports.Register(Transport) +} + // Transport is an ImageTransport for OpenShift registry-hosted images. var Transport = openshiftTransport{} @@ -51,22 +56,23 @@ type openshiftReference struct { // ParseReference converts a string, which should not start with the ImageTransport.Name prefix, into an OpenShift ImageReference. func ParseReference(ref string) (types.ImageReference, error) { - r, err := reference.ParseNamed(ref) + r, err := reference.ParseNormalizedNamed(ref) if err != nil { return nil, errors.Wrapf(err, "failed to parse image reference %q", ref) } tagged, ok := r.(reference.NamedTagged) if !ok { - return nil, errors.Errorf("invalid image reference %s, %#v", ref, r) + return nil, errors.Errorf("invalid image reference %s, expected format: 'hostname/namespace/stream:tag'", ref) } return NewReference(tagged) } // NewReference returns an OpenShift reference for a reference.NamedTagged func NewReference(dockerRef reference.NamedTagged) (types.ImageReference, error) { - r := strings.SplitN(dockerRef.RemoteName(), "/", 3) + r := strings.SplitN(reference.Path(dockerRef), "/", 3) if len(r) != 2 { - return nil, errors.Errorf("invalid image reference %s", dockerRef.String()) + return nil, errors.Errorf("invalid image reference: %s, expected format: 'hostname/namespace/stream:tag'", + reference.FamiliarString(dockerRef)) } return openshiftReference{ namespace: r[0], @@ -85,7 +91,7 @@ func (ref openshiftReference) Transport() types.ImageTransport { // e.g. default attribute values omitted by the user may be filled in in the return value, or vice versa. // WARNING: Do not use the return value in the UI to describe an image, it does not contain the Transport().Name() prefix. func (ref openshiftReference) StringWithinTransport() string { - return ref.dockerReference.String() + return reference.FamiliarString(ref.dockerReference) } // DockerReference returns a Docker reference associated with this reference diff --git a/vendor/github.com/containers/image/openshift/openshift_transport_test.go b/vendor/github.com/containers/image/openshift/openshift_transport_test.go index 46904ff3..5c589c19 100644 --- a/vendor/github.com/containers/image/openshift/openshift_transport_test.go +++ b/vendor/github.com/containers/image/openshift/openshift_transport_test.go @@ -40,14 +40,14 @@ func TestTransportValidatePolicyConfigurationScope(t *testing.T) { func TestNewReference(t *testing.T) { // too many ns - r, err := reference.ParseNamed("registry.example.com/ns1/ns2/ns3/stream:tag") + r, err := reference.ParseNormalizedNamed("registry.example.com/ns1/ns2/ns3/stream:tag") require.NoError(t, err) tagged, ok := r.(reference.NamedTagged) require.True(t, ok) _, err = NewReference(tagged) assert.Error(t, err) - r, err = reference.ParseNamed("registry.example.com/ns/stream:tag") + r, err = reference.ParseNormalizedNamed("registry.example.com/ns/stream:tag") require.NoError(t, err) tagged, ok = r.(reference.NamedTagged) require.True(t, ok) @@ -64,7 +64,7 @@ func TestParseReference(t *testing.T) { assert.Equal(t, "ns", osRef.namespace) assert.Equal(t, "stream", osRef.stream) assert.Equal(t, "notlatest", osRef.dockerReference.Tag()) - assert.Equal(t, "registry.example.com:8443", osRef.dockerReference.Hostname()) + assert.Equal(t, "registry.example.com:8443", reference.Domain(osRef.dockerReference)) // Components creating an invalid Docker Reference name _, err = ParseReference("registry.example.com/ns/UPPERCASEISINVALID:notlatest") diff --git a/vendor/github.com/containers/image/copy/compression.go b/vendor/github.com/containers/image/pkg/compression/compression.go similarity index 59% rename from vendor/github.com/containers/image/copy/compression.go rename to vendor/github.com/containers/image/pkg/compression/compression.go index 03514b48..c114ded6 100644 --- a/vendor/github.com/containers/image/copy/compression.go +++ b/vendor/github.com/containers/image/pkg/compression/compression.go @@ -1,4 +1,4 @@ -package copy +package compression import ( "bytes" @@ -11,32 +11,37 @@ import ( "github.com/Sirupsen/logrus" ) -// decompressorFunc, given a compressed stream, returns the decompressed stream. -type decompressorFunc func(io.Reader) (io.Reader, error) +// DecompressorFunc returns the decompressed stream, given a compressed stream. +type DecompressorFunc func(io.Reader) (io.Reader, error) -func gzipDecompressor(r io.Reader) (io.Reader, error) { +// GzipDecompressor is a DecompressorFunc for the gzip compression algorithm. +func GzipDecompressor(r io.Reader) (io.Reader, error) { return gzip.NewReader(r) } -func bzip2Decompressor(r io.Reader) (io.Reader, error) { + +// Bzip2Decompressor is a DecompressorFunc for the bzip2 compression algorithm. +func Bzip2Decompressor(r io.Reader) (io.Reader, error) { return bzip2.NewReader(r), nil } -func xzDecompressor(r io.Reader) (io.Reader, error) { + +// XzDecompressor is a DecompressorFunc for the xz compression algorithm. +func XzDecompressor(r io.Reader) (io.Reader, error) { return nil, errors.New("Decompressing xz streams is not supported") } -// compressionAlgos is an internal implementation detail of detectCompression +// compressionAlgos is an internal implementation detail of DetectCompression var compressionAlgos = map[string]struct { prefix []byte - decompressor decompressorFunc + decompressor DecompressorFunc }{ - "gzip": {[]byte{0x1F, 0x8B, 0x08}, gzipDecompressor}, // gzip (RFC 1952) - "bzip2": {[]byte{0x42, 0x5A, 0x68}, bzip2Decompressor}, // bzip2 (decompress.c:BZ2_decompress) - "xz": {[]byte{0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00}, xzDecompressor}, // xz (/usr/share/doc/xz/xz-file-format.txt) + "gzip": {[]byte{0x1F, 0x8B, 0x08}, GzipDecompressor}, // gzip (RFC 1952) + "bzip2": {[]byte{0x42, 0x5A, 0x68}, Bzip2Decompressor}, // bzip2 (decompress.c:BZ2_decompress) + "xz": {[]byte{0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00}, XzDecompressor}, // xz (/usr/share/doc/xz/xz-file-format.txt) } -// detectCompression returns a decompressorFunc if the input is recognized as a compressed format, nil otherwise. +// DetectCompression returns a DecompressorFunc if the input is recognized as a compressed format, nil otherwise. // Because it consumes the start of input, other consumers must use the returned io.Reader instead to also read from the beginning. -func detectCompression(input io.Reader) (decompressorFunc, io.Reader, error) { +func DetectCompression(input io.Reader) (DecompressorFunc, io.Reader, error) { buffer := [8]byte{} n, err := io.ReadAtLeast(input, buffer[:], len(buffer)) @@ -46,7 +51,7 @@ func detectCompression(input io.Reader) (decompressorFunc, io.Reader, error) { return nil, nil, err } - var decompressor decompressorFunc + var decompressor DecompressorFunc for name, algo := range compressionAlgos { if bytes.HasPrefix(buffer[:n], algo.prefix) { logrus.Debugf("Detected compression format %s", name) diff --git a/vendor/github.com/containers/image/copy/compression_test.go b/vendor/github.com/containers/image/pkg/compression/compression_test.go similarity index 88% rename from vendor/github.com/containers/image/copy/compression_test.go rename to vendor/github.com/containers/image/pkg/compression/compression_test.go index 9475a8b7..2dd42931 100644 --- a/vendor/github.com/containers/image/copy/compression_test.go +++ b/vendor/github.com/containers/image/pkg/compression/compression_test.go @@ -1,4 +1,4 @@ -package copy +package compression import ( "bytes" @@ -33,7 +33,7 @@ func TestDetectCompression(t *testing.T) { require.NoError(t, err, c.filename) defer stream.Close() - _, updatedStream, err := detectCompression(stream) + _, updatedStream, err := DetectCompression(stream) require.NoError(t, err, c.filename) updatedContents, err := ioutil.ReadAll(updatedStream) @@ -47,7 +47,7 @@ func TestDetectCompression(t *testing.T) { require.NoError(t, err, c.filename) defer stream.Close() - decompressor, updatedStream, err := detectCompression(stream) + decompressor, updatedStream, err := DetectCompression(stream) require.NoError(t, err, c.filename) var uncompressedStream io.Reader @@ -70,7 +70,7 @@ func TestDetectCompression(t *testing.T) { } // Empty input is handled reasonably. - decompressor, updatedStream, err := detectCompression(bytes.NewReader([]byte{})) + decompressor, updatedStream, err := DetectCompression(bytes.NewReader([]byte{})) require.NoError(t, err) assert.Nil(t, decompressor) updatedContents, err := ioutil.ReadAll(updatedStream) @@ -80,7 +80,7 @@ func TestDetectCompression(t *testing.T) { // Error reading input reader, writer := io.Pipe() defer reader.Close() - writer.CloseWithError(errors.New("Expected error reading input in detectCompression")) - _, _, err = detectCompression(reader) + writer.CloseWithError(errors.New("Expected error reading input in DetectCompression")) + _, _, err = DetectCompression(reader) assert.Error(t, err) } diff --git a/vendor/github.com/containers/image/pkg/compression/fixtures/Hello.bz2 b/vendor/github.com/containers/image/pkg/compression/fixtures/Hello.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..e822f5e5e9e170abc384cc72e161a5ed1548ca22 GIT binary patch literal 43 ycmZ>Y%CIzaj8qGblnP0i#K6G7%D~{j#Ik@vaaM-0uds3VPNg{-9=uvco(uru9tt-A literal 0 HcmV?d00001 diff --git a/vendor/github.com/containers/image/pkg/compression/fixtures/Hello.gz b/vendor/github.com/containers/image/pkg/compression/fixtures/Hello.gz new file mode 100644 index 0000000000000000000000000000000000000000..22c895b7d178a03e0dc601f450ec5f2158f4866b GIT binary patch literal 25 hcmb2|=3pq;{y&_7`LoB_lPB33nmR9jXJueu004p>39JAB literal 0 HcmV?d00001 diff --git a/vendor/github.com/containers/image/pkg/compression/fixtures/Hello.uncompressed b/vendor/github.com/containers/image/pkg/compression/fixtures/Hello.uncompressed new file mode 100644 index 00000000..5ab2f8a4 --- /dev/null +++ b/vendor/github.com/containers/image/pkg/compression/fixtures/Hello.uncompressed @@ -0,0 +1 @@ +Hello \ No newline at end of file diff --git a/vendor/github.com/containers/image/pkg/compression/fixtures/Hello.xz b/vendor/github.com/containers/image/pkg/compression/fixtures/Hello.xz new file mode 100644 index 0000000000000000000000000000000000000000..6e9b0b6648fbe8cc20d6dcb1921ddbed0069210b GIT binary patch literal 64 zcmexsUKJ6=z`*kC+7>q^21Q0O1_p)_{ill=8CX10b8_;5T!s^Cs!v$QoDXDRlx5wa R+pu1K+vi$FkOWI)6aakT6B_^k literal 0 HcmV?d00001 diff --git a/vendor/github.com/containers/image/signature/docker.go b/vendor/github.com/containers/image/signature/docker.go index 901a225a..16eb3f79 100644 --- a/vendor/github.com/containers/image/signature/docker.go +++ b/vendor/github.com/containers/image/signature/docker.go @@ -25,7 +25,7 @@ func SignDockerManifest(m []byte, dockerReference string, mech SigningMechanism, // using mech. func VerifyDockerManifestSignature(unverifiedSignature, unverifiedManifest []byte, expectedDockerReference string, mech SigningMechanism, expectedKeyIdentity string) (*Signature, error) { - expectedRef, err := reference.ParseNamed(expectedDockerReference) + expectedRef, err := reference.ParseNormalizedNamed(expectedDockerReference) if err != nil { return nil, err } @@ -37,7 +37,7 @@ func VerifyDockerManifestSignature(unverifiedSignature, unverifiedManifest []byt return nil }, validateSignedDockerReference: func(signedDockerReference string) error { - signedRef, err := reference.ParseNamed(signedDockerReference) + signedRef, err := reference.ParseNormalizedNamed(signedDockerReference) if err != nil { return InvalidSignatureError{msg: fmt.Sprintf("Invalid docker reference %s in signature", signedDockerReference)} } diff --git a/vendor/github.com/containers/image/signature/fixtures/.gitignore b/vendor/github.com/containers/image/signature/fixtures/.gitignore index 3730cb25..2772b97f 100644 --- a/vendor/github.com/containers/image/signature/fixtures/.gitignore +++ b/vendor/github.com/containers/image/signature/fixtures/.gitignore @@ -2,3 +2,5 @@ /.gpg-v21-migrated /private-keys-v1.d /random_seed +/gnupg_spawn_agent_sentinel.lock +/.#* diff --git a/vendor/github.com/containers/image/signature/fixtures/dir-img-manifest-digest-error/manifest.json b/vendor/github.com/containers/image/signature/fixtures/dir-img-manifest-digest-error/manifest.json new file mode 120000 index 00000000..3dee14b4 --- /dev/null +++ b/vendor/github.com/containers/image/signature/fixtures/dir-img-manifest-digest-error/manifest.json @@ -0,0 +1 @@ +../v2s1-invalid-signatures.manifest.json \ No newline at end of file diff --git a/vendor/github.com/containers/image/signature/fixtures/dir-img-manifest-digest-error/signature-1 b/vendor/github.com/containers/image/signature/fixtures/dir-img-manifest-digest-error/signature-1 new file mode 120000 index 00000000..f010fd4c --- /dev/null +++ b/vendor/github.com/containers/image/signature/fixtures/dir-img-manifest-digest-error/signature-1 @@ -0,0 +1 @@ +../dir-img-valid/signature-1 \ No newline at end of file diff --git a/vendor/github.com/containers/image/signature/fixtures/dir-img-mixed/manifest.json b/vendor/github.com/containers/image/signature/fixtures/dir-img-mixed/manifest.json new file mode 120000 index 00000000..ff7d2ffa --- /dev/null +++ b/vendor/github.com/containers/image/signature/fixtures/dir-img-mixed/manifest.json @@ -0,0 +1 @@ +../dir-img-valid/manifest.json \ No newline at end of file diff --git a/vendor/github.com/containers/image/signature/fixtures/dir-img-mixed/signature-1 b/vendor/github.com/containers/image/signature/fixtures/dir-img-mixed/signature-1 new file mode 120000 index 00000000..b27cdc45 --- /dev/null +++ b/vendor/github.com/containers/image/signature/fixtures/dir-img-mixed/signature-1 @@ -0,0 +1 @@ +../invalid-blob.signature \ No newline at end of file diff --git a/vendor/github.com/containers/image/signature/fixtures/dir-img-mixed/signature-2 b/vendor/github.com/containers/image/signature/fixtures/dir-img-mixed/signature-2 new file mode 120000 index 00000000..f010fd4c --- /dev/null +++ b/vendor/github.com/containers/image/signature/fixtures/dir-img-mixed/signature-2 @@ -0,0 +1 @@ +../dir-img-valid/signature-1 \ No newline at end of file diff --git a/vendor/github.com/containers/image/signature/fixtures/dir-img-modified-manifest/signature-1 b/vendor/github.com/containers/image/signature/fixtures/dir-img-modified-manifest/signature-1 new file mode 120000 index 00000000..f010fd4c --- /dev/null +++ b/vendor/github.com/containers/image/signature/fixtures/dir-img-modified-manifest/signature-1 @@ -0,0 +1 @@ +../dir-img-valid/signature-1 \ No newline at end of file diff --git a/vendor/github.com/containers/image/signature/fixtures/dir-img-no-manifest/signature-1 b/vendor/github.com/containers/image/signature/fixtures/dir-img-no-manifest/signature-1 new file mode 120000 index 00000000..f010fd4c --- /dev/null +++ b/vendor/github.com/containers/image/signature/fixtures/dir-img-no-manifest/signature-1 @@ -0,0 +1 @@ +../dir-img-valid/signature-1 \ No newline at end of file diff --git a/vendor/github.com/containers/image/signature/fixtures/dir-img-unsigned/manifest.json b/vendor/github.com/containers/image/signature/fixtures/dir-img-unsigned/manifest.json new file mode 120000 index 00000000..ff7d2ffa --- /dev/null +++ b/vendor/github.com/containers/image/signature/fixtures/dir-img-unsigned/manifest.json @@ -0,0 +1 @@ +../dir-img-valid/manifest.json \ No newline at end of file diff --git a/vendor/github.com/containers/image/signature/fixtures/dir-img-valid-2/manifest.json b/vendor/github.com/containers/image/signature/fixtures/dir-img-valid-2/manifest.json new file mode 120000 index 00000000..ff7d2ffa --- /dev/null +++ b/vendor/github.com/containers/image/signature/fixtures/dir-img-valid-2/manifest.json @@ -0,0 +1 @@ +../dir-img-valid/manifest.json \ No newline at end of file diff --git a/vendor/github.com/containers/image/signature/fixtures/dir-img-valid-2/signature-1 b/vendor/github.com/containers/image/signature/fixtures/dir-img-valid-2/signature-1 new file mode 120000 index 00000000..f010fd4c --- /dev/null +++ b/vendor/github.com/containers/image/signature/fixtures/dir-img-valid-2/signature-1 @@ -0,0 +1 @@ +../dir-img-valid/signature-1 \ No newline at end of file diff --git a/vendor/github.com/containers/image/signature/fixtures/dir-img-valid/manifest.json b/vendor/github.com/containers/image/signature/fixtures/dir-img-valid/manifest.json new file mode 120000 index 00000000..c5bd2543 --- /dev/null +++ b/vendor/github.com/containers/image/signature/fixtures/dir-img-valid/manifest.json @@ -0,0 +1 @@ +../image.manifest.json \ No newline at end of file diff --git a/vendor/github.com/containers/image/signature/policy_config.go b/vendor/github.com/containers/image/signature/policy_config.go index e4525795..e4c93ed5 100644 --- a/vendor/github.com/containers/image/signature/policy_config.go +++ b/vendor/github.com/containers/image/signature/policy_config.go @@ -19,11 +19,10 @@ import ( "io/ioutil" "path/filepath" - "github.com/pkg/errors" - "github.com/containers/image/docker/reference" "github.com/containers/image/transports" "github.com/containers/image/types" + "github.com/pkg/errors" ) // systemDefaultPolicyPath is the policy path used for DefaultPolicy(). @@ -123,10 +122,8 @@ func (m *policyTransportsMap) UnmarshalJSON(data []byte) error { // So, use a temporary map of pointers-to-slices and convert. tmpMap := map[string]*PolicyTransportScopes{} if err := paranoidUnmarshalJSONObject(data, func(key string) interface{} { - transport, ok := transports.KnownTransports[key] - if !ok { - return nil - } + // transport can be nil + transport := transports.Get(key) // paranoidUnmarshalJSONObject detects key duplication for us, check just to be safe. if _, ok := tmpMap[key]; ok { return nil @@ -156,7 +153,7 @@ func (m *PolicyTransportScopes) UnmarshalJSON(data []byte) error { } // policyTransportScopesWithTransport is a way to unmarshal a PolicyTransportScopes -// while validating using a specific ImageTransport. +// while validating using a specific ImageTransport if not nil. type policyTransportScopesWithTransport struct { transport types.ImageTransport dest *PolicyTransportScopes @@ -175,7 +172,7 @@ func (m *policyTransportScopesWithTransport) UnmarshalJSON(data []byte) error { if _, ok := tmpMap[key]; ok { return nil } - if key != "" { + if key != "" && m.transport != nil { if err := m.transport.ValidatePolicyConfigurationScope(key); err != nil { return nil } @@ -634,7 +631,7 @@ func (prm *prmMatchRepository) UnmarshalJSON(data []byte) error { // newPRMExactReference is NewPRMExactReference, except it resturns the private type. func newPRMExactReference(dockerReference string) (*prmExactReference, error) { - ref, err := reference.ParseNamed(dockerReference) + ref, err := reference.ParseNormalizedNamed(dockerReference) if err != nil { return nil, InvalidPolicyFormatError(fmt.Sprintf("Invalid format of dockerReference %s: %s", dockerReference, err.Error())) } @@ -686,7 +683,7 @@ func (prm *prmExactReference) UnmarshalJSON(data []byte) error { // newPRMExactRepository is NewPRMExactRepository, except it resturns the private type. func newPRMExactRepository(dockerRepository string) (*prmExactRepository, error) { - if _, err := reference.ParseNamed(dockerRepository); err != nil { + if _, err := reference.ParseNormalizedNamed(dockerRepository); err != nil { return nil, InvalidPolicyFormatError(fmt.Sprintf("Invalid format of dockerRepository %s: %s", dockerRepository, err.Error())) } return &prmExactRepository{ diff --git a/vendor/github.com/containers/image/signature/policy_config_test.go b/vendor/github.com/containers/image/signature/policy_config_test.go index c7b3278f..12cb832a 100644 --- a/vendor/github.com/containers/image/signature/policy_config_test.go +++ b/vendor/github.com/containers/image/signature/policy_config_test.go @@ -9,6 +9,8 @@ import ( "github.com/containers/image/directory" "github.com/containers/image/docker" + // this import is needed where we use the "atomic" transport in TestPolicyUnmarshalJSON + _ "github.com/containers/image/openshift" "github.com/containers/image/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -244,6 +246,11 @@ func TestPolicyUnmarshalJSON(t *testing.T) { xNewPRSignedByKeyData(SBKeyTypeSignedByGPGKeys, []byte("RHatomic"), NewPRMMatchRepository()), }, }, + "unknown": { + "registry.access.redhat.com/rhel7": []PolicyRequirement{ + xNewPRSignedByKeyData(SBKeyTypeSignedByGPGKeys, []byte("RHatomic"), NewPRMMatchRepository()), + }, + }, }, } validJSON, err := json.Marshal(validPolicy) @@ -269,9 +276,6 @@ func TestPolicyUnmarshalJSON(t *testing.T) { func(v mSI) { v["transports"] = []string{} }, // "default" is an invalid PolicyRequirements func(v mSI) { v["default"] = PolicyRequirements{} }, - // A key in "transports" is an invalid transport name - func(v mSI) { x(v, "transports")["this is unknown"] = x(v, "transports")["docker"] }, - func(v mSI) { x(v, "transports")[""] = x(v, "transports")["docker"] }, } for _, fn := range breakFns { err = tryUnmarshalModifiedPolicy(t, &p, validJSON, fn) diff --git a/vendor/github.com/containers/image/signature/policy_eval_signedby_test.go b/vendor/github.com/containers/image/signature/policy_eval_signedby_test.go index d21ee9c1..19086fcf 100644 --- a/vendor/github.com/containers/image/signature/policy_eval_signedby_test.go +++ b/vendor/github.com/containers/image/signature/policy_eval_signedby_test.go @@ -17,7 +17,7 @@ import ( // dirImageMock returns a types.UnparsedImage for a directory, claiming a specified dockerReference. // The caller must call .Close() on the returned UnparsedImage. func dirImageMock(t *testing.T, dir, dockerReference string) types.UnparsedImage { - ref, err := reference.ParseNamed(dockerReference) + ref, err := reference.ParseNormalizedNamed(dockerReference) require.NoError(t, err) return dirImageMockWithRef(t, dir, refImageReferenceMock{ref}) } diff --git a/vendor/github.com/containers/image/signature/policy_eval_test.go b/vendor/github.com/containers/image/signature/policy_eval_test.go index c0bfe1a3..7cfcb3fb 100644 --- a/vendor/github.com/containers/image/signature/policy_eval_test.go +++ b/vendor/github.com/containers/image/signature/policy_eval_test.go @@ -5,8 +5,10 @@ import ( "os" "testing" + "github.com/containers/image/docker" "github.com/containers/image/docker/policyconfiguration" "github.com/containers/image/docker/reference" + "github.com/containers/image/transports" "github.com/containers/image/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -103,6 +105,37 @@ func (ref pcImageReferenceMock) DeleteImage(ctx *types.SystemContext) error { panic("unexpected call to a mock function") } +func TestPolicyContextRequirementsForImageRefNotRegisteredTransport(t *testing.T) { + transports.Delete("docker") + assert.Nil(t, transports.Get("docker")) + + defer func() { + assert.Nil(t, transports.Get("docker")) + transports.Register(docker.Transport) + assert.NotNil(t, transports.Get("docker")) + }() + + pr := []PolicyRequirement{ + xNewPRSignedByKeyData(SBKeyTypeSignedByGPGKeys, []byte("RH"), NewPRMMatchRepository()), + } + policy := &Policy{ + Default: PolicyRequirements{NewPRReject()}, + Transports: map[string]PolicyTransportScopes{ + "docker": { + "registry.access.redhat.com": pr, + }, + }, + } + pc, err := NewPolicyContext(policy) + require.NoError(t, err) + ref, err := reference.ParseNormalizedNamed("registry.access.redhat.com/rhel7:latest") + require.NoError(t, err) + reqs := pc.requirementsForImageRef(pcImageReferenceMock{"docker", ref}) + assert.True(t, &(reqs[0]) == &(pr[0])) + assert.True(t, len(reqs) == len(pr)) + +} + func TestPolicyContextRequirementsForImageRef(t *testing.T) { ktGPG := SBKeyTypeGPGKeys prm := NewPRMMatchRepoDigestOrExact() @@ -159,7 +192,7 @@ func TestPolicyContextRequirementsForImageRef(t *testing.T) { expected = policy.Default } - ref, err := reference.ParseNamed(c.input) + ref, err := reference.ParseNormalizedNamed(c.input) require.NoError(t, err) reqs := pc.requirementsForImageRef(pcImageReferenceMock{c.inputTransport, ref}) comment := fmt.Sprintf("case %s:%s: %#v", c.inputTransport, c.input, reqs[0]) @@ -174,7 +207,7 @@ func TestPolicyContextRequirementsForImageRef(t *testing.T) { // pcImageMock returns a types.UnparsedImage for a directory, claiming a specified dockerReference and implementing PolicyConfigurationIdentity/PolicyConfigurationNamespaces. // The caller must call .Close() on the returned Image. func pcImageMock(t *testing.T, dir, dockerReference string) types.UnparsedImage { - ref, err := reference.ParseNamed(dockerReference) + ref, err := reference.ParseNormalizedNamed(dockerReference) require.NoError(t, err) return dirImageMockWithRef(t, dir, pcImageReferenceMock{"docker", ref}) } diff --git a/vendor/github.com/containers/image/signature/policy_reference_match.go b/vendor/github.com/containers/image/signature/policy_reference_match.go index ced51e6e..a8dad677 100644 --- a/vendor/github.com/containers/image/signature/policy_reference_match.go +++ b/vendor/github.com/containers/image/signature/policy_reference_match.go @@ -17,7 +17,7 @@ func parseImageAndDockerReference(image types.UnparsedImage, s2 string) (referen return nil, nil, PolicyRequirementError(fmt.Sprintf("Docker reference match attempted on image %s with no known Docker reference identity", transports.ImageName(image.Reference()))) } - r2, err := reference.ParseNamed(s2) + r2, err := reference.ParseNormalizedNamed(s2) if err != nil { return nil, nil, err } @@ -69,11 +69,11 @@ func (prm *prmMatchRepository) matchesDockerReference(image types.UnparsedImage, // parseDockerReferences converts two reference strings into parsed entities, failing on any error func parseDockerReferences(s1, s2 string) (reference.Named, reference.Named, error) { - r1, err := reference.ParseNamed(s1) + r1, err := reference.ParseNormalizedNamed(s1) if err != nil { return nil, nil, err } - r2, err := reference.ParseNamed(s2) + r2, err := reference.ParseNormalizedNamed(s2) if err != nil { return nil, nil, err } diff --git a/vendor/github.com/containers/image/signature/policy_reference_match_test.go b/vendor/github.com/containers/image/signature/policy_reference_match_test.go index 25541821..da934f3d 100644 --- a/vendor/github.com/containers/image/signature/policy_reference_match_test.go +++ b/vendor/github.com/containers/image/signature/policy_reference_match_test.go @@ -6,7 +6,6 @@ import ( "github.com/containers/image/docker/reference" "github.com/containers/image/types" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -26,12 +25,12 @@ func TestParseImageAndDockerReference(t *testing.T) { bad2 = "" ) // Success - ref, err := reference.ParseNamed(ok1) + ref, err := reference.ParseNormalizedNamed(ok1) require.NoError(t, err) r1, r2, err := parseImageAndDockerReference(refImageMock{ref}, ok2) require.NoError(t, err) - assert.Equal(t, ok1, r1.String()) - assert.Equal(t, ok2, r2.String()) + assert.Equal(t, ok1, reference.FamiliarString(r1)) + assert.Equal(t, ok2, reference.FamiliarString(r2)) // Unidentified images are rejected. _, _, err = parseImageAndDockerReference(refImageMock{nil}, ok2) @@ -44,7 +43,7 @@ func TestParseImageAndDockerReference(t *testing.T) { {ok1, bad2}, {bad1, bad2}, } { - ref, err := reference.ParseNamed(refs[0]) + ref, err := reference.ParseNormalizedNamed(refs[0]) if err == nil { _, _, err := parseImageAndDockerReference(refImageMock{ref}, refs[1]) assert.Error(t, err) @@ -58,7 +57,7 @@ type refImageMock struct{ reference.Named } func (ref refImageMock) Reference() types.ImageReference { return refImageReferenceMock{ref.Named} } -func (ref refImageMock) Close() { +func (ref refImageMock) Close() error { panic("unexpected call to a mock function") } func (ref refImageMock) Manifest() ([]byte, string, error) { @@ -72,7 +71,7 @@ func (ref refImageMock) Signatures() ([][]byte, error) { type refImageReferenceMock struct{ reference.Named } func (ref refImageReferenceMock) Transport() types.ImageTransport { - // We use this in error messages, so sadly we must return something. But right now we do so only when DockerReference is nil, so restrict to that. + // We use this in error messages, so sady we must return something. But right now we do so only when DockerReference is nil, so restrict to that. if ref.Named == nil { return nameImageTransportMock("== Transport mock") } @@ -148,14 +147,12 @@ var prmExactMatchTestTable = []prmSymmetricTableTest{ {"busybox", "busybox:latest", false}, {"busybox", "busybox" + digestSuffix, false}, {"busybox", "busybox", false}, - // References with both tags and digests: `reference.WithName` essentially drops the tag. - // This is not _particularly_ desirable but it is the semantics used throughout containers/image; at least, with the digest it is clear which image the reference means, - // even if the tag may reflect a different user intent. + // References with both tags and digests: We match them exactly (requiring BOTH to match) // NOTE: Again, this is not documented behavior; the recommendation is to sign tags, not digests, and then tag-and-digest references won’t match the signed identity. {"busybox:latest" + digestSuffix, "busybox:latest" + digestSuffix, true}, {"busybox:latest" + digestSuffix, "busybox:latest" + digestSuffixOther, false}, - {"busybox:latest" + digestSuffix, "busybox:notlatest" + digestSuffix, true}, // Ugly. Do not rely on this. - {"busybox:latest" + digestSuffix, "busybox" + digestSuffix, true}, // Ugly. Do not rely on this. + {"busybox:latest" + digestSuffix, "busybox:notlatest" + digestSuffix, false}, + {"busybox:latest" + digestSuffix, "busybox" + digestSuffix, false}, {"busybox:latest" + digestSuffix, "busybox:latest", false}, // Invalid format {"UPPERCASE_IS_INVALID_IN_DOCKER_REFERENCES", "busybox:latest", false}, @@ -194,7 +191,7 @@ var prmRepositoryMatchTestTable = []prmSymmetricTableTest{ {"hostname/library/busybox:latest", "busybox:notlatest", false}, {"busybox:latest", fullRHELRef, false}, {"busybox" + digestSuffix, "notbusybox" + digestSuffix, false}, - // References with both tags and digests: `reference.WithName` essentially drops the tag, and we ignore both anyway. + // References with both tags and digests: We ignore both anyway. {"busybox:latest" + digestSuffix, "busybox:latest" + digestSuffix, true}, {"busybox:latest" + digestSuffix, "busybox:latest" + digestSuffixOther, true}, {"busybox:latest" + digestSuffix, "busybox:notlatest" + digestSuffix, true}, @@ -209,8 +206,8 @@ var prmRepositoryMatchTestTable = []prmSymmetricTableTest{ func testImageAndSig(t *testing.T, prm PolicyReferenceMatch, imageRef, sigRef string, result bool) { // This assumes that all ways to obtain a reference.Named perform equivalent validation, - // and therefore values refused by reference.ParseNamed can not happen in practice. - parsedImageRef, err := reference.ParseNamed(imageRef) + // and therefore values refused by reference.ParseNormalizedNamed can not happen in practice. + parsedImageRef, err := reference.ParseNormalizedNamed(imageRef) if err != nil { return } @@ -272,14 +269,12 @@ func TestPMMMatchRepoDigestOrExactMatchesDockerReference(t *testing.T) { // Digest references accept any signature with matching repository. {"busybox" + digestSuffix, "busybox:latest", true}, {"busybox" + digestSuffix, "busybox" + digestSuffixOther, true}, // Even this is accepted here. (This could more reasonably happen with two different digest algorithms.) - // References with both tags and digests: `reference.WithName` essentially drops the tag. - // This is not _particularly_ desirable but it is the semantics used throughout containers/image; at least, with the digest it is clear which image the reference means, - // even if the tag may reflect a different user intent. - {"busybox:latest" + digestSuffix, "busybox:latest", true}, - {"busybox:latest" + digestSuffix, "busybox:notlatest", true}, + // References with both tags and digests: We match them exactly (requiring BOTH to match). + {"busybox:latest" + digestSuffix, "busybox:latest", false}, + {"busybox:latest" + digestSuffix, "busybox:notlatest", false}, {"busybox:latest", "busybox:latest" + digestSuffix, false}, - {"busybox:latest" + digestSuffix, "busybox:latest" + digestSuffixOther, true}, // Even this is accepted here. (This could more reasonably happen with two different digest algorithms.) - {"busybox:latest" + digestSuffix, "busybox:notlatest" + digestSuffixOther, true}, // Ugly. Do not rely on this. + {"busybox:latest" + digestSuffix, "busybox:latest" + digestSuffixOther, false}, + {"busybox:latest" + digestSuffix, "busybox:notlatest" + digestSuffixOther, false}, } { testImageAndSig(t, prm, test.imageRef, test.sigRef, test.result) } @@ -307,8 +302,8 @@ func TestParseDockerReferences(t *testing.T) { // Success r1, r2, err := parseDockerReferences(ok1, ok2) require.NoError(t, err) - assert.Equal(t, ok1, r1.String()) - assert.Equal(t, ok2, r2.String()) + assert.Equal(t, ok1, reference.FamiliarString(r1)) + assert.Equal(t, ok2, reference.FamiliarString(r2)) // Failures for _, refs := range [][]string{ @@ -327,7 +322,7 @@ type forbiddenImageMock struct{} func (ref forbiddenImageMock) Reference() types.ImageReference { panic("unexpected call to a mock function") } -func (ref forbiddenImageMock) Close() { +func (ref forbiddenImageMock) Close() error { panic("unexpected call to a mock function") } func (ref forbiddenImageMock) Manifest() ([]byte, string, error) { diff --git a/vendor/github.com/containers/image/storage/storage_image.go b/vendor/github.com/containers/image/storage/storage_image.go index fb7fe86f..7930cb32 100644 --- a/vendor/github.com/containers/image/storage/storage_image.go +++ b/vendor/github.com/containers/image/storage/storage_image.go @@ -118,10 +118,12 @@ func (s storageImageDestination) Reference() types.ImageReference { return s.imageRef } -func (s storageImageSource) Close() { +func (s storageImageSource) Close() error { + return nil } -func (s storageImageDestination) Close() { +func (s storageImageDestination) Close() error { + return nil } func (s storageImageDestination) ShouldCompressLayers() bool { diff --git a/vendor/github.com/containers/image/storage/storage_reference.go b/vendor/github.com/containers/image/storage/storage_reference.go index 13413df1..bee753f4 100644 --- a/vendor/github.com/containers/image/storage/storage_reference.go +++ b/vendor/github.com/containers/image/storage/storage_reference.go @@ -87,7 +87,7 @@ func (s storageReference) PolicyConfigurationNamespaces() []string { // The reference without the ID is also a valid namespace. namespaces = append(namespaces, storeSpec+s.reference) } - components := strings.Split(s.name.FullName(), "/") + components := strings.Split(s.name.Name(), "/") for len(components) > 0 { namespaces = append(namespaces, storeSpec+strings.Join(components, "/")) components = components[:len(components)-1] diff --git a/vendor/github.com/containers/image/storage/storage_reference_test.go b/vendor/github.com/containers/image/storage/storage_reference_test.go index 687d1005..ee461341 100644 --- a/vendor/github.com/containers/image/storage/storage_reference_test.go +++ b/vendor/github.com/containers/image/storage/storage_reference_test.go @@ -23,7 +23,7 @@ func TestStorageReferenceDockerReference(t *testing.T) { require.NoError(t, err) dr := ref.DockerReference() require.NotNil(t, dr) - assert.Equal(t, "busybox:latest", dr.String()) + assert.Equal(t, "docker.io/library/busybox:latest", dr.String()) ref, err = Transport.ParseReference("@" + sha256digestHex) require.NoError(t, err) diff --git a/vendor/github.com/containers/image/storage/storage_transport.go b/vendor/github.com/containers/image/storage/storage_transport.go index 661df103..e9982175 100644 --- a/vendor/github.com/containers/image/storage/storage_transport.go +++ b/vendor/github.com/containers/image/storage/storage_transport.go @@ -9,12 +9,17 @@ import ( "github.com/Sirupsen/logrus" "github.com/containers/image/docker/reference" + "github.com/containers/image/transports" "github.com/containers/image/types" "github.com/containers/storage/storage" "github.com/opencontainers/go-digest" ddigest "github.com/opencontainers/go-digest" ) +func init() { + transports.Register(Transport) +} + var ( // Transport is an ImageTransport that uses either a default // storage.Store or one that's it's explicitly told to use. @@ -83,14 +88,14 @@ func (s storageTransport) ParseStoreReference(store storage.Store, ref string) ( refInfo := strings.SplitN(ref, "@", 2) if len(refInfo) == 1 { // A name. - name, err = reference.ParseNamed(refInfo[0]) + name, err = reference.ParseNormalizedNamed(refInfo[0]) if err != nil { return nil, err } } else if len(refInfo) == 2 { // An ID, possibly preceded by a name. if refInfo[0] != "" { - name, err = reference.ParseNamed(refInfo[0]) + name, err = reference.ParseNormalizedNamed(refInfo[0]) if err != nil { return nil, err } @@ -111,7 +116,7 @@ func (s storageTransport) ParseStoreReference(store storage.Store, ref string) ( } refname := "" if name != nil { - name = reference.WithDefaultTag(name) + name = reference.TagNameOnly(name) refname = verboseName(name) } if refname == "" { @@ -257,12 +262,12 @@ func (s storageTransport) ValidatePolicyConfigurationScope(scope string) error { // that are just bare IDs. scopeInfo := strings.SplitN(scope, "@", 2) if len(scopeInfo) == 1 && scopeInfo[0] != "" { - _, err := reference.ParseNamed(scopeInfo[0]) + _, err := reference.ParseNormalizedNamed(scopeInfo[0]) if err != nil { return err } } else if len(scopeInfo) == 2 && scopeInfo[0] != "" && scopeInfo[1] != "" { - _, err := reference.ParseNamed(scopeInfo[0]) + _, err := reference.ParseNormalizedNamed(scopeInfo[0]) if err != nil { return err } @@ -277,10 +282,10 @@ func (s storageTransport) ValidatePolicyConfigurationScope(scope string) error { } func verboseName(name reference.Named) string { - name = reference.WithDefaultTag(name) + name = reference.TagNameOnly(name) tag := "" if tagged, ok := name.(reference.NamedTagged); ok { tag = tagged.Tag() } - return name.FullName() + ":" + tag + return name.Name() + ":" + tag } diff --git a/vendor/github.com/containers/image/storage/storage_transport_test.go b/vendor/github.com/containers/image/storage/storage_transport_test.go index 2ca7a657..3fddff5c 100644 --- a/vendor/github.com/containers/image/storage/storage_transport_test.go +++ b/vendor/github.com/containers/image/storage/storage_transport_test.go @@ -54,7 +54,7 @@ func TestTransportParseStoreReference(t *testing.T) { if c.expectedRef == "" { assert.Nil(t, storageRef.name, c.input) } else { - dockerRef, err := reference.ParseNamed(c.expectedRef) + dockerRef, err := reference.ParseNormalizedNamed(c.expectedRef) require.NoError(t, err) require.NotNil(t, storageRef.name, c.input) assert.Equal(t, dockerRef.String(), storageRef.name.String()) diff --git a/vendor/github.com/containers/image/transports/alltransports/alltransports.go b/vendor/github.com/containers/image/transports/alltransports/alltransports.go new file mode 100644 index 00000000..eb5ddcd9 --- /dev/null +++ b/vendor/github.com/containers/image/transports/alltransports/alltransports.go @@ -0,0 +1,31 @@ +package alltransports + +import ( + "strings" + + // register all known transports + // NOTE: Make sure docs/policy.json.md is updated when adding or updating + // a transport. + _ "github.com/containers/image/directory" + _ "github.com/containers/image/docker" + _ "github.com/containers/image/docker/daemon" + _ "github.com/containers/image/oci/layout" + _ "github.com/containers/image/openshift" + _ "github.com/containers/image/storage" + "github.com/containers/image/transports" + "github.com/containers/image/types" + "github.com/pkg/errors" +) + +// ParseImageName converts a URL-like image name to a types.ImageReference. +func ParseImageName(imgName string) (types.ImageReference, error) { + parts := strings.SplitN(imgName, ":", 2) + if len(parts) != 2 { + return nil, errors.Errorf(`Invalid image name "%s", expected colon-separated transport:reference`, imgName) + } + transport := transports.Get(parts[0]) + if transport == nil { + return nil, errors.Errorf(`Invalid image name "%s", unknown transport "%s"`, imgName, parts[0]) + } + return transport.ParseReference(parts[1]) +} diff --git a/vendor/github.com/containers/image/transports/transports_test.go b/vendor/github.com/containers/image/transports/alltransports/alltransports_test.go similarity index 86% rename from vendor/github.com/containers/image/transports/transports_test.go rename to vendor/github.com/containers/image/transports/alltransports/alltransports_test.go index 2daec0dd..8401d5d9 100644 --- a/vendor/github.com/containers/image/transports/transports_test.go +++ b/vendor/github.com/containers/image/transports/alltransports/alltransports_test.go @@ -1,17 +1,13 @@ -package transports +package alltransports import ( "testing" + "github.com/containers/image/transports" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestKnownTransports(t *testing.T) { - assert.NotNil(t, KnownTransports) // Ensure that the initialization has actually been run - assert.True(t, len(KnownTransports) > 1) -} - func TestParseImageName(t *testing.T) { // This primarily tests error handling, TestImageNameHandling is a table-driven // test for the expected values. @@ -36,11 +32,12 @@ func TestImageNameHandling(t *testing.T) { {"docker-daemon", "busybox:latest", "busybox:latest"}, {"oci", "/etc:sometag", "/etc:sometag"}, // "atomic" not tested here because it depends on per-user configuration for the default cluster. + // "containers-storage" not tested here because it needs to initialize various directories on the fs. } { fullInput := c.transport + ":" + c.input ref, err := ParseImageName(fullInput) require.NoError(t, err, fullInput) - s := ImageName(ref) + s := transports.ImageName(ref) assert.Equal(t, c.transport+":"+c.roundtrip, s, fullInput) } } diff --git a/vendor/github.com/containers/image/transports/transports.go b/vendor/github.com/containers/image/transports/transports.go index 04826c04..03969be7 100644 --- a/vendor/github.com/containers/image/transports/transports.go +++ b/vendor/github.com/containers/image/transports/transports.go @@ -2,52 +2,61 @@ package transports import ( "fmt" - "strings" + "sync" - "github.com/containers/image/directory" - "github.com/containers/image/docker" - "github.com/containers/image/docker/daemon" - ociLayout "github.com/containers/image/oci/layout" - "github.com/containers/image/openshift" - "github.com/containers/image/storage" "github.com/containers/image/types" - "github.com/pkg/errors" ) -// KnownTransports is a registry of known ImageTransport instances. -var KnownTransports map[string]types.ImageTransport +// knownTransports is a registry of known ImageTransport instances. +type knownTransports struct { + transports map[string]types.ImageTransport + mu sync.Mutex +} + +func (kt *knownTransports) Get(k string) types.ImageTransport { + kt.mu.Lock() + t := kt.transports[k] + kt.mu.Unlock() + return t +} + +func (kt *knownTransports) Remove(k string) { + kt.mu.Lock() + delete(kt.transports, k) + kt.mu.Unlock() +} + +func (kt *knownTransports) Add(t types.ImageTransport) { + kt.mu.Lock() + defer kt.mu.Unlock() + name := t.Name() + if t := kt.transports[name]; t != nil { + panic(fmt.Sprintf("Duplicate image transport name %s", name)) + } + kt.transports[name] = t +} + +var kt *knownTransports func init() { - KnownTransports = make(map[string]types.ImageTransport) - // NOTE: Make sure docs/policy.json.md is updated when adding or updating - // a transport. - for _, t := range []types.ImageTransport{ - directory.Transport, - docker.Transport, - daemon.Transport, - ociLayout.Transport, - openshift.Transport, - storage.Transport, - } { - name := t.Name() - if _, ok := KnownTransports[name]; ok { - panic(fmt.Sprintf("Duplicate image transport name %s", name)) - } - KnownTransports[name] = t + kt = &knownTransports{ + transports: make(map[string]types.ImageTransport), } } -// ParseImageName converts a URL-like image name to a types.ImageReference. -func ParseImageName(imgName string) (types.ImageReference, error) { - parts := strings.SplitN(imgName, ":", 2) - if len(parts) != 2 { - return nil, errors.Errorf(`Invalid image name "%s", expected colon-separated transport:reference`, imgName) - } - transport, ok := KnownTransports[parts[0]] - if !ok { - return nil, errors.Errorf(`Invalid image name "%s", unknown transport "%s"`, imgName, parts[0]) - } - return transport.ParseReference(parts[1]) +// Get returns the transport specified by name or nil when unavailable. +func Get(name string) types.ImageTransport { + return kt.Get(name) +} + +// Delete deletes a transport from the registered transports. +func Delete(name string) { + kt.Remove(name) +} + +// Register registers a transport. +func Register(t types.ImageTransport) { + kt.Add(t) } // ImageName converts a types.ImageReference into an URL-like image name, which MUST be such that diff --git a/vendor/github.com/containers/image/types/types.go b/vendor/github.com/containers/image/types/types.go index 517388a0..e114a95e 100644 --- a/vendor/github.com/containers/image/types/types.go +++ b/vendor/github.com/containers/image/types/types.go @@ -4,10 +4,9 @@ import ( "io" "time" - "github.com/pkg/errors" - "github.com/containers/image/docker/reference" "github.com/opencontainers/go-digest" + "github.com/pkg/errors" ) // ImageTransport is a top-level namespace for ways to to store/load an image. @@ -111,7 +110,7 @@ type ImageSource interface { // (not as the image itself, or its underlying storage, claims). This can be used e.g. to determine which public keys are trusted for this image. Reference() ImageReference // Close removes resources associated with an initialized ImageSource, if any. - Close() + Close() error // GetManifest returns the image's manifest along with its MIME type (which may be empty when it can't be determined but the manifest is available). // It may use a remote (= slow) service. GetManifest() ([]byte, string, error) @@ -139,7 +138,7 @@ type ImageDestination interface { // e.g. it should use the public hostname instead of the result of resolving CNAMEs or following redirects. Reference() ImageReference // Close removes resources associated with an initialized ImageDestination, if any. - Close() + Close() error // SupportedManifestMIMETypes tells which manifest mime types the destination supports // If an empty slice or nil it's returned, then any mime type can be tried to upload @@ -185,7 +184,7 @@ type UnparsedImage interface { // (not as the image itself, or its underlying storage, claims). This can be used e.g. to determine which public keys are trusted for this image. Reference() ImageReference // Close removes resources associated with an initialized UnparsedImage, if any. - Close() + Close() error // Manifest is like ImageSource.GetManifest, but the result is cached; it is OK to call this however often you need. Manifest() ([]byte, string, error) // Signatures is like ImageSource.GetSignatures, but the result is cached; it is OK to call this however often you need. @@ -295,6 +294,13 @@ type SystemContext struct { DockerDisableV1Ping bool } +// ProgressProperties is used to pass information from the copy code to a monitor which +// can use the real-time information to produce output or react to changes. +type ProgressProperties struct { + Artifact BlobInfo + Offset uint64 +} + var ( // ErrBlobNotFound can be returned by an ImageDestination's HasBlob() method ErrBlobNotFound = errors.New("no such blob present") diff --git a/vendor/github.com/containers/image/vendor.conf b/vendor/github.com/containers/image/vendor.conf new file mode 100644 index 00000000..454aabbf --- /dev/null +++ b/vendor/github.com/containers/image/vendor.conf @@ -0,0 +1,31 @@ +github.com/Sirupsen/logrus 7f4b1adc791766938c29457bed0703fb9134421a +github.com/containers/storage 5cbbc6bafb45bd7ef10486b673deb3b81bb3b787 +github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76 +github.com/docker/distribution df5327f76fb6468b84a87771e361762b8be23fdb +github.com/docker/docker 75843d36aa5c3eaade50da005f9e0ff2602f3d5e +github.com/docker/go-connections 7da10c8c50cad14494ec818dcdfb6506265c0086 +github.com/docker/go-units 0dadbb0345b35ec7ef35e228dabb8de89a65bf52 +github.com/docker/libtrust aabc10ec26b754e797f9028f4589c5b7bd90dc20 +github.com/ghodss/yaml 04f313413ffd65ce25f2541bfd2b2ceec5c0908c +github.com/gorilla/context 08b5f424b9271eedf6f9f0ce86cb9396ed337a42 +github.com/gorilla/mux 94e7d24fd285520f3d12ae998f7fdd6b5393d453 +github.com/imdario/mergo 50d4dbd4eb0e84778abe37cefef140271d96fade +github.com/mattn/go-runewidth 14207d285c6c197daabb5c9793d63e7af9ab2d50 +github.com/mattn/go-shellwords 005a0944d84452842197c2108bd9168ced206f78 +github.com/mistifyio/go-zfs c0224de804d438efd11ea6e52ada8014537d6062 +github.com/mtrmac/gpgme b2432428689ca58c2b8e8dea9449d3295cf96fc9 +github.com/opencontainers/go-digest aa2ec055abd10d26d539eb630a92241b781ce4bc +github.com/opencontainers/image-spec v1.0.0-rc4 +github.com/opencontainers/runc 6b1d0e76f239ffb435445e5ae316d2676c07c6e3 +github.com/pborman/uuid 1b00554d822231195d1babd97ff4a781231955c9 +github.com/pkg/errors 248dadf4e9068a0b3e79f02ed0a610d935de5302 +github.com/pmezard/go-difflib 792786c7400a136282c1664665ae0a8db921c6c2 +github.com/stretchr/testify 4d4bfba8f1d1027c4fdbe371823030df51419987 +github.com/vbatts/tar-split bd4c5d64c3e9297f410025a3b1bd0c58f659e721 +golang.org/x/crypto 453249f01cfeb54c3d549ddb75ff152ca243f9d8 +golang.org/x/net 6b27048ae5e6ad1ef927e72e437531493de612fe +golang.org/x/sys 075e574b89e4c2d22f2286a7e2b919519c6f3547 +gopkg.in/cheggaaa/pb.v1 d7e6ca3010b6f084d8056847f55d7f572f180678 +gopkg.in/yaml.v2 a3f3340b5840cee44f372bddb5880fcbc419b46a +k8s.io/client-go bcde30fb7eaed76fd98a36b4120321b94995ffb6 +github.com/xeipuuv/gojsonschema master