From 3532e95a08c1c8aa651377d21b78166c5faefad4 Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Sat, 18 Feb 2017 00:06:00 +1100 Subject: [PATCH] vendor: add _test.go files Signed-off-by: Aleksa Sarai --- .../containers/image/.pullapprove.yml | 9 + .../github.com/containers/image/.travis.yml | 18 + .../github.com/containers/image/MAINTAINERS | 3 + vendor/github.com/containers/image/Makefile | 60 + vendor/github.com/containers/image/README.md | 21 + .../containers/image/copy/copy_test.go | 124 ++ .../containers/image/copy/fixtures/Hello.bz2 | 1 + .../containers/image/copy/fixtures/Hello.gz | 1 + .../image/copy/fixtures/Hello.uncompressed | 1 + .../containers/image/copy/fixtures/Hello.xz | 1 + .../image/directory/directory_test.go | 162 ++ .../directory/directory_transport_test.go | 232 +++ .../directory/explicitfilepath/path_test.go | 173 +++ vendor/github.com/containers/image/doc.go | 29 + .../docker/daemon/daemon_transport_test.go | 228 +++ .../image/docker/docker_client_test.go | 432 ++++++ .../image/docker/docker_image_src_test.go | 24 + .../image/docker/docker_transport_test.go | 196 +++ .../fixtures/registries.d/emptyConfig.yaml | 1 + .../registries.d/internal-example.com.yaml | 14 + .../fixtures/registries.d/internet-user.yaml | 12 + .../fixtures/registries.d/invalid-but.notyaml | 1 + .../containers/image/docker/lookaside_test.go | 277 ++++ .../docker/policyconfiguration/naming_test.go | 79 + .../image/docker/reference/README.md | 2 + .../image/docker/reference/normalize_test.go | 573 +++++++ .../image/docker/reference/reference_test.go | 659 ++++++++ .../image/docker/reference/regexp_test.go | 553 +++++++ .../image/docker/wwwauthenticate_test.go | 45 + .../containers/image/docs/policy.json.md | 267 ++++ .../containers/image/docs/registries.d.md | 124 ++ .../image/image/docker_schema2_test.go | 498 ++++++ .../image/image/fixtures/oci1-config.json | 1 + .../image/image/fixtures/oci1-to-schema2.json | 36 + .../containers/image/image/fixtures/oci1.json | 35 + .../image/image/fixtures/schema2-config.json | 1 + .../image/image/fixtures/schema2-to-oci1.json | 29 + .../schema2-to-schema1-by-docker.json | 116 ++ .../image/image/fixtures/schema2.json | 36 + .../containers/image/image/oci_test.go | 345 +++++ .../manifest/fixtures/non-json.manifest.json | Bin 0 -> 411 bytes .../manifest/fixtures/ociv1.manifest.json | 26 + .../manifest/fixtures/ociv1list.manifest.json | 56 + .../fixtures/unknown-version.manifest.json | 5 + .../manifest/fixtures/v2list.manifest.json | 56 + .../v2s1-invalid-signatures.manifest.json | 11 + .../fixtures/v2s1-unsigned.manifest.json | 28 + .../manifest/fixtures/v2s1.manifest.json | 44 + .../manifest/fixtures/v2s2.manifest.json | 26 + .../fixtures/v2s2nomime.manifest.json | 10 + .../image/manifest/fixtures_info_test.go | 12 + .../image/manifest/manifest_test.go | 125 ++ .../image/oci/layout/oci_dest_test.go | 61 + .../image/oci/layout/oci_transport_test.go | 272 ++++ vendor/github.com/containers/image/oci/oci.go | 1 + .../openshift/openshift_transport_test.go | 125 ++ .../image/pkg/compression/compression_test.go | 86 ++ .../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_test.go | 101 ++ .../image/signature/fixtures/.gitignore | 4 + .../signature/fixtures/corrupt.signature | Bin 0 -> 412 bytes .../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/manifest.json | 27 + .../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-2/signature-2 | Bin 0 -> 425 bytes .../fixtures/dir-img-valid/manifest.json | 1 + .../fixtures/dir-img-valid/signature-1 | Bin 0 -> 427 bytes .../image/signature/fixtures/double.signature | Bin 0 -> 822 bytes .../signature/fixtures/expired.signature | Bin 0 -> 217 bytes .../signature/fixtures/image.manifest.json | 26 + .../image/signature/fixtures/image.signature | Bin 0 -> 411 bytes .../signature/fixtures/invalid-blob.signature | Bin 0 -> 199 bytes .../fixtures/invalid-reference.signature | Bin 0 -> 422 bytes .../fixtures/no-optional-fields.signature | Bin 0 -> 383 bytes .../image/signature/fixtures/policy.json | 96 ++ .../image/signature/fixtures/public-key.gpg | 19 + .../image/signature/fixtures/pubring.gpg | Bin 0 -> 661 bytes .../image/signature/fixtures/secring.gpg | Bin 0 -> 1325 bytes .../image/signature/fixtures/trustdb.gpg | Bin 0 -> 1360 bytes .../signature/fixtures/unknown-key.signature | Bin 0 -> 558 bytes .../fixtures/unsigned-encrypted.signature | Bin 0 -> 233 bytes .../fixtures/unsigned-literal.signature | Bin 0 -> 47 bytes .../v2s1-invalid-signatures.manifest.json | 11 + .../image/signature/fixtures_info_test.go | 14 + .../containers/image/signature/json_test.go | 182 +++ .../image/signature/mechanism_test.go | 205 +++ .../image/signature/policy_config_test.go | 1366 +++++++++++++++++ .../signature/policy_eval_baselayer_test.go | 24 + .../signature/policy_eval_signedby_test.go | 264 ++++ .../signature/policy_eval_simple_test.go | 74 + .../image/signature/policy_eval_test.go | 488 ++++++ .../signature/policy_reference_match_test.go | 365 +++++ .../image/signature/signature_test.go | 377 +++++ .../image/storage/storage_reference_test.go | 97 ++ .../containers/image/storage/storage_test.go | 882 +++++++++++ .../image/storage/storage_transport_test.go | 146 ++ .../image/transports/transports_test.go | 46 + 108 files changed, 11189 insertions(+) create mode 100644 vendor/github.com/containers/image/.pullapprove.yml create mode 100644 vendor/github.com/containers/image/.travis.yml create mode 100644 vendor/github.com/containers/image/MAINTAINERS create mode 100644 vendor/github.com/containers/image/Makefile create mode 100644 vendor/github.com/containers/image/README.md create mode 100644 vendor/github.com/containers/image/copy/copy_test.go create mode 120000 vendor/github.com/containers/image/copy/fixtures/Hello.bz2 create mode 120000 vendor/github.com/containers/image/copy/fixtures/Hello.gz create mode 120000 vendor/github.com/containers/image/copy/fixtures/Hello.uncompressed create mode 120000 vendor/github.com/containers/image/copy/fixtures/Hello.xz create mode 100644 vendor/github.com/containers/image/directory/directory_test.go create mode 100644 vendor/github.com/containers/image/directory/directory_transport_test.go create mode 100644 vendor/github.com/containers/image/directory/explicitfilepath/path_test.go create mode 100644 vendor/github.com/containers/image/doc.go create mode 100644 vendor/github.com/containers/image/docker/daemon/daemon_transport_test.go create mode 100644 vendor/github.com/containers/image/docker/docker_client_test.go create mode 100644 vendor/github.com/containers/image/docker/docker_image_src_test.go create mode 100644 vendor/github.com/containers/image/docker/docker_transport_test.go create mode 100644 vendor/github.com/containers/image/docker/fixtures/registries.d/emptyConfig.yaml create mode 100644 vendor/github.com/containers/image/docker/fixtures/registries.d/internal-example.com.yaml create mode 100644 vendor/github.com/containers/image/docker/fixtures/registries.d/internet-user.yaml create mode 100644 vendor/github.com/containers/image/docker/fixtures/registries.d/invalid-but.notyaml create mode 100644 vendor/github.com/containers/image/docker/lookaside_test.go create mode 100644 vendor/github.com/containers/image/docker/policyconfiguration/naming_test.go create mode 100644 vendor/github.com/containers/image/docker/reference/README.md create mode 100644 vendor/github.com/containers/image/docker/reference/normalize_test.go create mode 100644 vendor/github.com/containers/image/docker/reference/reference_test.go create mode 100644 vendor/github.com/containers/image/docker/reference/regexp_test.go create mode 100644 vendor/github.com/containers/image/docker/wwwauthenticate_test.go create mode 100644 vendor/github.com/containers/image/docs/policy.json.md create mode 100644 vendor/github.com/containers/image/docs/registries.d.md create mode 100644 vendor/github.com/containers/image/image/docker_schema2_test.go create mode 100644 vendor/github.com/containers/image/image/fixtures/oci1-config.json create mode 100644 vendor/github.com/containers/image/image/fixtures/oci1-to-schema2.json create mode 100644 vendor/github.com/containers/image/image/fixtures/oci1.json create mode 100644 vendor/github.com/containers/image/image/fixtures/schema2-config.json create mode 100644 vendor/github.com/containers/image/image/fixtures/schema2-to-oci1.json create mode 100644 vendor/github.com/containers/image/image/fixtures/schema2-to-schema1-by-docker.json create mode 100644 vendor/github.com/containers/image/image/fixtures/schema2.json create mode 100644 vendor/github.com/containers/image/image/oci_test.go create mode 100644 vendor/github.com/containers/image/manifest/fixtures/non-json.manifest.json create mode 100644 vendor/github.com/containers/image/manifest/fixtures/ociv1.manifest.json create mode 100644 vendor/github.com/containers/image/manifest/fixtures/ociv1list.manifest.json create mode 100644 vendor/github.com/containers/image/manifest/fixtures/unknown-version.manifest.json create mode 100644 vendor/github.com/containers/image/manifest/fixtures/v2list.manifest.json create mode 100644 vendor/github.com/containers/image/manifest/fixtures/v2s1-invalid-signatures.manifest.json create mode 100644 vendor/github.com/containers/image/manifest/fixtures/v2s1-unsigned.manifest.json create mode 100644 vendor/github.com/containers/image/manifest/fixtures/v2s1.manifest.json create mode 100644 vendor/github.com/containers/image/manifest/fixtures/v2s2.manifest.json create mode 100644 vendor/github.com/containers/image/manifest/fixtures/v2s2nomime.manifest.json create mode 100644 vendor/github.com/containers/image/manifest/fixtures_info_test.go create mode 100644 vendor/github.com/containers/image/manifest/manifest_test.go create mode 100644 vendor/github.com/containers/image/oci/layout/oci_dest_test.go create mode 100644 vendor/github.com/containers/image/oci/layout/oci_transport_test.go create mode 100644 vendor/github.com/containers/image/oci/oci.go create mode 100644 vendor/github.com/containers/image/openshift/openshift_transport_test.go create mode 100644 vendor/github.com/containers/image/pkg/compression/compression_test.go 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 100644 vendor/github.com/containers/image/signature/docker_test.go create mode 100644 vendor/github.com/containers/image/signature/fixtures/.gitignore create mode 100644 vendor/github.com/containers/image/signature/fixtures/corrupt.signature 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 100644 vendor/github.com/containers/image/signature/fixtures/dir-img-modified-manifest/manifest.json 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 100644 vendor/github.com/containers/image/signature/fixtures/dir-img-valid-2/signature-2 create mode 120000 vendor/github.com/containers/image/signature/fixtures/dir-img-valid/manifest.json create mode 100644 vendor/github.com/containers/image/signature/fixtures/dir-img-valid/signature-1 create mode 100644 vendor/github.com/containers/image/signature/fixtures/double.signature create mode 100644 vendor/github.com/containers/image/signature/fixtures/expired.signature create mode 100644 vendor/github.com/containers/image/signature/fixtures/image.manifest.json create mode 100644 vendor/github.com/containers/image/signature/fixtures/image.signature create mode 100644 vendor/github.com/containers/image/signature/fixtures/invalid-blob.signature create mode 100644 vendor/github.com/containers/image/signature/fixtures/invalid-reference.signature create mode 100644 vendor/github.com/containers/image/signature/fixtures/no-optional-fields.signature create mode 100644 vendor/github.com/containers/image/signature/fixtures/policy.json create mode 100644 vendor/github.com/containers/image/signature/fixtures/public-key.gpg create mode 100644 vendor/github.com/containers/image/signature/fixtures/pubring.gpg create mode 100644 vendor/github.com/containers/image/signature/fixtures/secring.gpg create mode 100644 vendor/github.com/containers/image/signature/fixtures/trustdb.gpg create mode 100644 vendor/github.com/containers/image/signature/fixtures/unknown-key.signature create mode 100644 vendor/github.com/containers/image/signature/fixtures/unsigned-encrypted.signature create mode 100644 vendor/github.com/containers/image/signature/fixtures/unsigned-literal.signature create mode 100644 vendor/github.com/containers/image/signature/fixtures/v2s1-invalid-signatures.manifest.json create mode 100644 vendor/github.com/containers/image/signature/fixtures_info_test.go create mode 100644 vendor/github.com/containers/image/signature/json_test.go create mode 100644 vendor/github.com/containers/image/signature/mechanism_test.go create mode 100644 vendor/github.com/containers/image/signature/policy_config_test.go create mode 100644 vendor/github.com/containers/image/signature/policy_eval_baselayer_test.go create mode 100644 vendor/github.com/containers/image/signature/policy_eval_signedby_test.go create mode 100644 vendor/github.com/containers/image/signature/policy_eval_simple_test.go create mode 100644 vendor/github.com/containers/image/signature/policy_eval_test.go create mode 100644 vendor/github.com/containers/image/signature/policy_reference_match_test.go create mode 100644 vendor/github.com/containers/image/signature/signature_test.go create mode 100644 vendor/github.com/containers/image/storage/storage_reference_test.go create mode 100644 vendor/github.com/containers/image/storage/storage_test.go create mode 100644 vendor/github.com/containers/image/storage/storage_transport_test.go create mode 100644 vendor/github.com/containers/image/transports/transports_test.go diff --git a/vendor/github.com/containers/image/.pullapprove.yml b/vendor/github.com/containers/image/.pullapprove.yml new file mode 100644 index 0000000000..0da2fcfacb --- /dev/null +++ b/vendor/github.com/containers/image/.pullapprove.yml @@ -0,0 +1,9 @@ +approve_by_comment: true +approve_regex: '^(Approved|lgtm|LGTM|:shipit:|:star:|:\+1:|:ship:)' +reject_regex: ^Rejected +reset_on_push: false +reviewers: + teams: + - image-maintainers + name: default + required: 2 diff --git a/vendor/github.com/containers/image/.travis.yml b/vendor/github.com/containers/image/.travis.yml new file mode 100644 index 0000000000..2432a053e5 --- /dev/null +++ b/vendor/github.com/containers/image/.travis.yml @@ -0,0 +1,18 @@ +--- + language: go + sudo: required + notifications: + email: false + go: + - 1.7 + install: make deps + script: make .gitvalidation && make validate && make test && make test-skopeo + dist: trusty + os: + - linux + addons: + apt: + packages: + - btrfs-tools + - libdevmapper-dev + - libgpgme11-dev diff --git a/vendor/github.com/containers/image/MAINTAINERS b/vendor/github.com/containers/image/MAINTAINERS new file mode 100644 index 0000000000..e23cea9b6c --- /dev/null +++ b/vendor/github.com/containers/image/MAINTAINERS @@ -0,0 +1,3 @@ +Antonio Murdaca (@runcom) +Brandon Philips (@philips) +Miloslav Trmac (@mtrmac) diff --git a/vendor/github.com/containers/image/Makefile b/vendor/github.com/containers/image/Makefile new file mode 100644 index 0000000000..0783cfd147 --- /dev/null +++ b/vendor/github.com/containers/image/Makefile @@ -0,0 +1,60 @@ +.PHONY: all deps test validate lint + +# Which github repostiory and branch to use for testing with skopeo +SKOPEO_REPO = projectatomic/skopeo +SKOPEO_BRANCH = master +# Set SUDO=sudo to run container integration tests using sudo. +SUDO = +BUILDTAGS = btrfs_noversion libdm_no_deferred_remove +BUILDFLAGS := -tags "$(BUILDTAGS)" + +all: deps .gitvalidation test validate + +deps: + go get -t $(BUILDFLAGS) ./... + go get -u $(BUILDFLAGS) github.com/golang/lint/golint + go get $(BUILDFLAGS) github.com/vbatts/git-validation + +test: + @go test $(BUILDFLAGS) -cover ./... + +# 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. +# make test-skopeo SKOPEO_REPO=runcom/skopeo-1 SKOPEO_BRANCH=oci-3 SUDO=sudo +# ) is a requirement before merging; note that Travis will only test +# the master branch of the upstream repo. +test-skopeo: + @echo === Testing skopeo build + @export GOPATH=$$(mktemp -d) && \ + 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} && \ + 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)" + +lint: + @out="$$(golint ./...)"; \ + if [ -n "$$(golint ./...)" ]; then \ + echo "$$out"; \ + exit 1; \ + fi + +.PHONY: .gitvalidation + +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) +ifeq ($(TRAVIS),true) + @git-validation -q -run DCO,short-subject,dangling-whitespace +else + @git-validation -q -run DCO,short-subject,dangling-whitespace -range $(EPOCH_TEST_COMMIT)..HEAD +endif diff --git a/vendor/github.com/containers/image/README.md b/vendor/github.com/containers/image/README.md new file mode 100644 index 0000000000..397f475a50 --- /dev/null +++ b/vendor/github.com/containers/image/README.md @@ -0,0 +1,21 @@ +[![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. + +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 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. + +## License + +ASL 2.0 + +## Contact + +- Mailing list: [containers-dev](https://groups.google.com/forum/?hl=en#!forum/containers-dev) +- IRC: #[container-projects](irc://irc.freenode.net:6667/#container-projects) on freenode.net diff --git a/vendor/github.com/containers/image/copy/copy_test.go b/vendor/github.com/containers/image/copy/copy_test.go new file mode 100644 index 0000000000..b98133a88f --- /dev/null +++ b/vendor/github.com/containers/image/copy/copy_test.go @@ -0,0 +1,124 @@ +package copy + +import ( + "bytes" + "io" + "os" + "testing" + "time" + + "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" +) + +func TestNewDigestingReader(t *testing.T) { + // Only the failure cases, success is tested in TestDigestingReaderRead below. + source := bytes.NewReader([]byte("abc")) + for _, input := range []digest.Digest{ + "abc", // Not algo:hexvalue + "crc32:", // Unknown algorithm, empty value + "crc32:012345678", // Unknown algorithm + "sha256:", // Empty value + "sha256:0", // Invalid hex value + "sha256:01", // Invalid length of hex value + } { + _, err := newDigestingReader(source, input) + assert.Error(t, err, input.String()) + } +} + +func TestDigestingReaderRead(t *testing.T) { + cases := []struct { + input []byte + digest digest.Digest + }{ + {[]byte(""), "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, + {[]byte("abc"), "sha256:ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"}, + {make([]byte, 65537, 65537), "sha256:3266304f31be278d06c3bd3eb9aa3e00c59bedec0a890de466568b0b90b0e01f"}, + } + // Valid input + for _, c := range cases { + source := bytes.NewReader(c.input) + reader, err := newDigestingReader(source, c.digest) + require.NoError(t, err, c.digest.String()) + dest := bytes.Buffer{} + n, err := io.Copy(&dest, reader) + assert.NoError(t, err, c.digest.String()) + assert.Equal(t, int64(len(c.input)), n, c.digest.String()) + assert.Equal(t, c.input, dest.Bytes(), c.digest.String()) + assert.False(t, reader.validationFailed, c.digest.String()) + } + // Modified input + for _, c := range cases { + source := bytes.NewReader(bytes.Join([][]byte{c.input, []byte("x")}, nil)) + reader, err := newDigestingReader(source, c.digest) + require.NoError(t, err, c.digest.String()) + dest := bytes.Buffer{} + _, err = io.Copy(&dest, reader) + assert.Error(t, err, c.digest.String()) + assert.True(t, reader.validationFailed) + } +} + +func goDiffIDComputationGoroutineWithTimeout(layerStream io.ReadCloser, decompressor compression.DecompressorFunc) *diffIDResult { + ch := make(chan diffIDResult) + go diffIDComputationGoroutine(ch, layerStream, nil) + timeout := time.After(time.Second) + select { + case res := <-ch: + return &res + case <-timeout: + return nil + } +} + +func TestDiffIDComputationGoroutine(t *testing.T) { + stream, err := os.Open("fixtures/Hello.uncompressed") + require.NoError(t, err) + res := goDiffIDComputationGoroutineWithTimeout(stream, nil) + require.NotNil(t, res) + assert.NoError(t, res.err) + assert.Equal(t, "sha256:185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969", res.digest.String()) + + // Error reading input + reader, writer := io.Pipe() + writer.CloseWithError(errors.New("Expected error reading input in diffIDComputationGoroutine")) + res = goDiffIDComputationGoroutineWithTimeout(reader, nil) + require.NotNil(t, res) + assert.Error(t, res.err) +} + +func TestComputeDiffID(t *testing.T) { + for _, c := range []struct { + filename string + decompressor compression.DecompressorFunc + result digest.Digest + }{ + {"fixtures/Hello.uncompressed", nil, "sha256:185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969"}, + {"fixtures/Hello.gz", nil, "sha256:0bd4409dcd76476a263b8f3221b4ce04eb4686dec40bfdcc2e86a7403de13609"}, + {"fixtures/Hello.gz", compression.GzipDecompressor, "sha256:185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969"}, + } { + stream, err := os.Open(c.filename) + require.NoError(t, err, c.filename) + defer stream.Close() + + diffID, err := computeDiffID(stream, c.decompressor) + require.NoError(t, err, c.filename) + assert.Equal(t, c.result, diffID) + } + + // Error initializing decompression + _, err := computeDiffID(bytes.NewReader([]byte{}), compression.GzipDecompressor) + assert.Error(t, err) + + // Error reading input + reader, writer := io.Pipe() + defer reader.Close() + writer.CloseWithError(errors.New("Expected error reading input in computeDiffID")) + _, err = computeDiffID(reader, nil) + assert.Error(t, err) +} 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 0000000000..fc28d6c9ac --- /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 new file mode 120000 index 0000000000..08aa805fcc --- /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 new file mode 120000 index 0000000000..49b46625d8 --- /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 new file mode 120000 index 0000000000..77bcd85587 --- /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/directory/directory_test.go b/vendor/github.com/containers/image/directory/directory_test.go new file mode 100644 index 0000000000..2693311c63 --- /dev/null +++ b/vendor/github.com/containers/image/directory/directory_test.go @@ -0,0 +1,162 @@ +package directory + +import ( + "bytes" + "io/ioutil" + "os" + "testing" + + "github.com/containers/image/types" + "github.com/opencontainers/go-digest" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDestinationReference(t *testing.T) { + ref, tmpDir := refToTempDir(t) + defer os.RemoveAll(tmpDir) + + dest, err := ref.NewImageDestination(nil) + require.NoError(t, err) + defer dest.Close() + ref2 := dest.Reference() + assert.Equal(t, tmpDir, ref2.StringWithinTransport()) +} + +func TestGetPutManifest(t *testing.T) { + ref, tmpDir := refToTempDir(t) + defer os.RemoveAll(tmpDir) + + man := []byte("test-manifest") + dest, err := ref.NewImageDestination(nil) + require.NoError(t, err) + defer dest.Close() + err = dest.PutManifest(man) + assert.NoError(t, err) + err = dest.Commit() + assert.NoError(t, err) + + src, err := ref.NewImageSource(nil, nil) + require.NoError(t, err) + defer src.Close() + m, mt, err := src.GetManifest() + assert.NoError(t, err) + assert.Equal(t, man, m) + assert.Equal(t, "", mt) +} + +func TestGetPutBlob(t *testing.T) { + ref, tmpDir := refToTempDir(t) + defer os.RemoveAll(tmpDir) + + blob := []byte("test-blob") + dest, err := ref.NewImageDestination(nil) + require.NoError(t, err) + defer dest.Close() + compress := dest.ShouldCompressLayers() + assert.False(t, compress) + info, err := dest.PutBlob(bytes.NewReader(blob), types.BlobInfo{Digest: digest.Digest("sha256:digest-test"), Size: int64(9)}) + assert.NoError(t, err) + err = dest.Commit() + assert.NoError(t, err) + assert.Equal(t, int64(9), info.Size) + assert.Equal(t, digest.FromBytes(blob), info.Digest) + + src, err := ref.NewImageSource(nil, nil) + require.NoError(t, err) + defer src.Close() + rc, size, err := src.GetBlob(info) + assert.NoError(t, err) + defer rc.Close() + b, err := ioutil.ReadAll(rc) + assert.NoError(t, err) + assert.Equal(t, blob, b) + assert.Equal(t, int64(len(blob)), size) +} + +// readerFromFunc allows implementing Reader by any function, e.g. a closure. +type readerFromFunc func([]byte) (int, error) + +func (fn readerFromFunc) Read(p []byte) (int, error) { + return fn(p) +} + +// TestPutBlobDigestFailure simulates behavior on digest verification failure. +func TestPutBlobDigestFailure(t *testing.T) { + const digestErrorString = "Simulated digest error" + const blobDigest = digest.Digest("sha256:test-digest") + + ref, tmpDir := refToTempDir(t) + defer os.RemoveAll(tmpDir) + dirRef, ok := ref.(dirReference) + require.True(t, ok) + blobPath := dirRef.layerPath(blobDigest) + + firstRead := true + reader := readerFromFunc(func(p []byte) (int, error) { + _, err := os.Lstat(blobPath) + require.Error(t, err) + require.True(t, os.IsNotExist(err)) + if firstRead { + if len(p) > 0 { + firstRead = false + } + for i := 0; i < len(p); i++ { + p[i] = 0xAA + } + return len(p), nil + } + return 0, errors.Errorf(digestErrorString) + }) + + dest, err := ref.NewImageDestination(nil) + require.NoError(t, err) + defer dest.Close() + _, err = dest.PutBlob(reader, types.BlobInfo{Digest: blobDigest, Size: -1}) + assert.Error(t, err) + assert.Contains(t, digestErrorString, err.Error()) + err = dest.Commit() + assert.NoError(t, err) + + _, err = os.Lstat(blobPath) + require.Error(t, err) + require.True(t, os.IsNotExist(err)) +} + +func TestGetPutSignatures(t *testing.T) { + ref, tmpDir := refToTempDir(t) + defer os.RemoveAll(tmpDir) + + dest, err := ref.NewImageDestination(nil) + require.NoError(t, err) + defer dest.Close() + signatures := [][]byte{ + []byte("sig1"), + []byte("sig2"), + } + err = dest.SupportsSignatures() + assert.NoError(t, err) + err = dest.PutSignatures(signatures) + assert.NoError(t, err) + err = dest.Commit() + assert.NoError(t, err) + + src, err := ref.NewImageSource(nil, nil) + require.NoError(t, err) + defer src.Close() + sigs, err := src.GetSignatures() + assert.NoError(t, err) + assert.Equal(t, signatures, sigs) +} + +func TestSourceReference(t *testing.T) { + ref, tmpDir := refToTempDir(t) + defer os.RemoveAll(tmpDir) + + src, err := ref.NewImageSource(nil, nil) + require.NoError(t, err) + defer src.Close() + ref2 := src.Reference() + assert.Equal(t, tmpDir, ref2.StringWithinTransport()) +} diff --git a/vendor/github.com/containers/image/directory/directory_transport_test.go b/vendor/github.com/containers/image/directory/directory_transport_test.go new file mode 100644 index 0000000000..1384d6f5b1 --- /dev/null +++ b/vendor/github.com/containers/image/directory/directory_transport_test.go @@ -0,0 +1,232 @@ +package directory + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/containers/image/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestTransportName(t *testing.T) { + assert.Equal(t, "dir", Transport.Name()) +} + +func TestTransportParseReference(t *testing.T) { + testNewReference(t, Transport.ParseReference) +} + +func TestTransportValidatePolicyConfigurationScope(t *testing.T) { + for _, scope := range []string{ + "/etc", + "/this/does/not/exist", + } { + err := Transport.ValidatePolicyConfigurationScope(scope) + assert.NoError(t, err, scope) + } + + for _, scope := range []string{ + "relative/path", + "/double//slashes", + "/has/./dot", + "/has/dot/../dot", + "/trailing/slash/", + "/", + } { + err := Transport.ValidatePolicyConfigurationScope(scope) + assert.Error(t, err, scope) + } +} + +func TestNewReference(t *testing.T) { + testNewReference(t, NewReference) +} + +// testNewReference is a test shared for Transport.ParseReference and NewReference. +func testNewReference(t *testing.T, fn func(string) (types.ImageReference, error)) { + tmpDir, err := ioutil.TempDir("", "dir-transport-test") + require.NoError(t, err) + defer os.RemoveAll(tmpDir) + + for _, path := range []string{ + "/", + "/etc", + tmpDir, + "relativepath", + tmpDir + "/thisdoesnotexist", + } { + ref, err := fn(path) + require.NoError(t, err, path) + dirRef, ok := ref.(dirReference) + require.True(t, ok) + assert.Equal(t, path, dirRef.path, path) + } + + _, err = fn(tmpDir + "/thisparentdoesnotexist/something") + assert.Error(t, err) +} + +// refToTempDir creates a temporary directory and returns a reference to it. +// The caller should +// defer os.RemoveAll(tmpDir) +func refToTempDir(t *testing.T) (ref types.ImageReference, tmpDir string) { + tmpDir, err := ioutil.TempDir("", "dir-transport-test") + require.NoError(t, err) + ref, err = NewReference(tmpDir) + require.NoError(t, err) + return ref, tmpDir +} + +func TestReferenceTransport(t *testing.T) { + ref, tmpDir := refToTempDir(t) + defer os.RemoveAll(tmpDir) + assert.Equal(t, Transport, ref.Transport()) +} + +func TestReferenceStringWithinTransport(t *testing.T) { + ref, tmpDir := refToTempDir(t) + defer os.RemoveAll(tmpDir) + assert.Equal(t, tmpDir, ref.StringWithinTransport()) +} + +func TestReferenceDockerReference(t *testing.T) { + ref, tmpDir := refToTempDir(t) + defer os.RemoveAll(tmpDir) + assert.Nil(t, ref.DockerReference()) +} + +func TestReferencePolicyConfigurationIdentity(t *testing.T) { + ref, tmpDir := refToTempDir(t) + defer os.RemoveAll(tmpDir) + + assert.Equal(t, tmpDir, ref.PolicyConfigurationIdentity()) + // A non-canonical path. Test just one, the various other cases are + // tested in explicitfilepath.ResolvePathToFullyExplicit. + ref, err := NewReference(tmpDir + "/.") + require.NoError(t, err) + assert.Equal(t, tmpDir, ref.PolicyConfigurationIdentity()) + + // "/" as a corner case. + ref, err = NewReference("/") + require.NoError(t, err) + assert.Equal(t, "/", ref.PolicyConfigurationIdentity()) +} + +func TestReferencePolicyConfigurationNamespaces(t *testing.T) { + ref, tmpDir := refToTempDir(t) + defer os.RemoveAll(tmpDir) + // We don't really know enough to make a full equality test here. + ns := ref.PolicyConfigurationNamespaces() + require.NotNil(t, ns) + assert.NotEmpty(t, ns) + assert.Equal(t, filepath.Dir(tmpDir), ns[0]) + + // Test with a known path which should exist. Test just one non-canonical + // path, the various other cases are tested in explicitfilepath.ResolvePathToFullyExplicit. + // + // It would be nice to test a deeper hierarchy, but it is not obvious what + // deeper path is always available in the various distros, AND is not likely + // to contains a symbolic link. + for _, path := range []string{"/etc/skel", "/etc/skel/./."} { + _, err := os.Lstat(path) + require.NoError(t, err) + ref, err := NewReference(path) + require.NoError(t, err) + ns := ref.PolicyConfigurationNamespaces() + require.NotNil(t, ns) + assert.Equal(t, []string{"/etc"}, ns) + } + + // "/" as a corner case. + ref, err := NewReference("/") + require.NoError(t, err) + assert.Equal(t, []string{}, ref.PolicyConfigurationNamespaces()) +} + +func TestReferenceNewImage(t *testing.T) { + ref, tmpDir := refToTempDir(t) + defer os.RemoveAll(tmpDir) + + dest, err := ref.NewImageDestination(nil) + require.NoError(t, err) + defer dest.Close() + mFixture, err := ioutil.ReadFile("../manifest/fixtures/v2s1.manifest.json") + require.NoError(t, err) + err = dest.PutManifest(mFixture) + assert.NoError(t, err) + err = dest.Commit() + assert.NoError(t, err) + + img, err := ref.NewImage(nil) + assert.NoError(t, err) + defer img.Close() +} + +func TestReferenceNewImageNoValidManifest(t *testing.T) { + ref, tmpDir := refToTempDir(t) + defer os.RemoveAll(tmpDir) + + dest, err := ref.NewImageDestination(nil) + require.NoError(t, err) + defer dest.Close() + err = dest.PutManifest([]byte(`{"schemaVersion":1}`)) + assert.NoError(t, err) + err = dest.Commit() + assert.NoError(t, err) + + _, err = ref.NewImage(nil) + assert.Error(t, err) +} + +func TestReferenceNewImageSource(t *testing.T) { + ref, tmpDir := refToTempDir(t) + defer os.RemoveAll(tmpDir) + src, err := ref.NewImageSource(nil, nil) + assert.NoError(t, err) + defer src.Close() +} + +func TestReferenceNewImageDestination(t *testing.T) { + ref, tmpDir := refToTempDir(t) + defer os.RemoveAll(tmpDir) + dest, err := ref.NewImageDestination(nil) + assert.NoError(t, err) + defer dest.Close() +} + +func TestReferenceDeleteImage(t *testing.T) { + ref, tmpDir := refToTempDir(t) + defer os.RemoveAll(tmpDir) + err := ref.DeleteImage(nil) + assert.Error(t, err) +} + +func TestReferenceManifestPath(t *testing.T) { + ref, tmpDir := refToTempDir(t) + defer os.RemoveAll(tmpDir) + dirRef, ok := ref.(dirReference) + require.True(t, ok) + assert.Equal(t, tmpDir+"/manifest.json", dirRef.manifestPath()) +} + +func TestReferenceLayerPath(t *testing.T) { + const hex = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + + ref, tmpDir := refToTempDir(t) + defer os.RemoveAll(tmpDir) + dirRef, ok := ref.(dirReference) + require.True(t, ok) + assert.Equal(t, tmpDir+"/"+hex+".tar", dirRef.layerPath("sha256:"+hex)) +} + +func TestReferenceSignaturePath(t *testing.T) { + ref, tmpDir := refToTempDir(t) + defer os.RemoveAll(tmpDir) + dirRef, ok := ref.(dirReference) + require.True(t, ok) + assert.Equal(t, tmpDir+"/signature-1", dirRef.signaturePath(0)) + assert.Equal(t, tmpDir+"/signature-10", dirRef.signaturePath(9)) +} diff --git a/vendor/github.com/containers/image/directory/explicitfilepath/path_test.go b/vendor/github.com/containers/image/directory/explicitfilepath/path_test.go new file mode 100644 index 0000000000..e45baf0581 --- /dev/null +++ b/vendor/github.com/containers/image/directory/explicitfilepath/path_test.go @@ -0,0 +1,173 @@ +package explicitfilepath + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type pathResolvingTestCase struct { + setup func(*testing.T, string) string + expected string +} + +var testCases = []pathResolvingTestCase{ + { // A straightforward subdirectory hierarchy + func(t *testing.T, top string) string { + err := os.MkdirAll(filepath.Join(top, "dir1/dir2/dir3"), 0755) + require.NoError(t, err) + return "dir1/dir2/dir3" + }, + "dir1/dir2/dir3", + }, + { // Missing component + func(t *testing.T, top string) string { + return "thisismissing/dir2" + }, + "", + }, + { // Symlink on the path + func(t *testing.T, top string) string { + err := os.MkdirAll(filepath.Join(top, "dir1/dir2"), 0755) + require.NoError(t, err) + err = os.Symlink("dir1", filepath.Join(top, "link1")) + require.NoError(t, err) + return "link1/dir2" + }, + "dir1/dir2", + }, + { // Trailing symlink + func(t *testing.T, top string) string { + err := os.MkdirAll(filepath.Join(top, "dir1/dir2"), 0755) + require.NoError(t, err) + err = os.Symlink("dir2", filepath.Join(top, "dir1/link2")) + require.NoError(t, err) + return "dir1/link2" + }, + "dir1/dir2", + }, + { // Symlink pointing nowhere, as a non-final component + func(t *testing.T, top string) string { + err := os.Symlink("thisismissing", filepath.Join(top, "link1")) + require.NoError(t, err) + return "link1/dir2" + }, + "", + }, + { // Trailing symlink pointing nowhere (but note that a missing non-symlink would be accepted) + func(t *testing.T, top string) string { + err := os.Symlink("thisismissing", filepath.Join(top, "link1")) + require.NoError(t, err) + return "link1" + }, + "", + }, + { // Relative components in a path + func(t *testing.T, top string) string { + err := os.MkdirAll(filepath.Join(top, "dir1/dir2/dir3"), 0755) + require.NoError(t, err) + return "dir1/./dir2/../dir2/dir3" + }, + "dir1/dir2/dir3", + }, + { // Trailing relative components + func(t *testing.T, top string) string { + err := os.MkdirAll(filepath.Join(top, "dir1/dir2"), 0755) + require.NoError(t, err) + return "dir1/dir2/.." + }, + "dir1", + }, + { // Relative components in symlink + func(t *testing.T, top string) string { + err := os.MkdirAll(filepath.Join(top, "dir1/dir2"), 0755) + require.NoError(t, err) + err = os.Symlink("../dir1/dir2", filepath.Join(top, "dir1/link2")) + require.NoError(t, err) + return "dir1/link2" + }, + "dir1/dir2", + }, + { // Relative component pointing "into" a symlink + func(t *testing.T, top string) string { + err := os.MkdirAll(filepath.Join(top, "dir1/dir2/dir3"), 0755) + require.NoError(t, err) + err = os.Symlink("dir3", filepath.Join(top, "dir1/dir2/link3")) + require.NoError(t, err) + return "dir1/dir2/link3/../.." + }, + "dir1", + }, + { // Unreadable directory + func(t *testing.T, top string) string { + err := os.MkdirAll(filepath.Join(top, "unreadable/dir2"), 0755) + require.NoError(t, err) + err = os.Chmod(filepath.Join(top, "unreadable"), 000) + require.NoError(t, err) + return "unreadable/dir2" + }, + "", + }, +} + +func testPathsAreSameFile(t *testing.T, path1, path2, description string) { + fi1, err := os.Stat(path1) + require.NoError(t, err) + fi2, err := os.Stat(path2) + require.NoError(t, err) + assert.True(t, os.SameFile(fi1, fi2), description) +} + +func runPathResolvingTestCase(t *testing.T, f func(string) (string, error), c pathResolvingTestCase, suffix string) { + topDir, err := ioutil.TempDir("", "pathResolving") + defer func() { + // Clean up after the "Unreadable directory" case; os.RemoveAll just fails. + _ = os.Chmod(filepath.Join(topDir, "unreadable"), 0755) // Ignore errors, especially if this does not exist. + os.RemoveAll(topDir) + }() + + input := c.setup(t, topDir) + suffix // Do not call filepath.Join() on input, it calls filepath.Clean() internally! + description := fmt.Sprintf("%s vs. %s%s", input, c.expected, suffix) + + fullOutput, err := ResolvePathToFullyExplicit(topDir + "/" + input) + if c.expected == "" { + assert.Error(t, err, description) + } else { + require.NoError(t, err, input) + fullExpected := topDir + "/" + c.expected + suffix + assert.Equal(t, fullExpected, fullOutput) + + // Either the two paths resolve to the same existing file, or to the same name in the same existing parent. + if _, err := os.Lstat(fullExpected); err == nil { + testPathsAreSameFile(t, fullOutput, fullExpected, description) + } else { + require.True(t, os.IsNotExist(err)) + _, err := os.Stat(fullOutput) + require.Error(t, err) + require.True(t, os.IsNotExist(err)) + + parentExpected, fileExpected := filepath.Split(fullExpected) + parentOutput, fileOutput := filepath.Split(fullOutput) + assert.Equal(t, fileExpected, fileOutput) + testPathsAreSameFile(t, parentOutput, parentExpected, description) + } + } +} + +func TestResolvePathToFullyExplicit(t *testing.T) { + for _, c := range testCases { + runPathResolvingTestCase(t, ResolvePathToFullyExplicit, c, "") + runPathResolvingTestCase(t, ResolvePathToFullyExplicit, c, "/trailing") + } +} + +func TestResolveExistingPathToFullyExplicit(t *testing.T) { + for _, c := range testCases { + runPathResolvingTestCase(t, resolveExistingPathToFullyExplicit, c, "") + } +} diff --git a/vendor/github.com/containers/image/doc.go b/vendor/github.com/containers/image/doc.go new file mode 100644 index 0000000000..253a083571 --- /dev/null +++ b/vendor/github.com/containers/image/doc.go @@ -0,0 +1,29 @@ +// Package image provides libraries and commands to interact with containers images. +// +// package main +// +// import ( +// "fmt" +// +// "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)) +// } +// +// TODO(runcom) +package image 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 new file mode 100644 index 0000000000..2a60c6b29c --- /dev/null +++ b/vendor/github.com/containers/image/docker/daemon/daemon_transport_test.go @@ -0,0 +1,228 @@ +package daemon + +import ( + "testing" + + "github.com/containers/image/docker/reference" + "github.com/containers/image/types" + "github.com/opencontainers/go-digest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + sha256digestHex = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + sha256digest = "sha256:" + sha256digestHex +) + +func TestTransportName(t *testing.T) { + assert.Equal(t, "docker-daemon", Transport.Name()) +} + +func TestTransportParseReference(t *testing.T) { + testParseReference(t, Transport.ParseReference) +} + +func TestTransportValidatePolicyConfigurationScope(t *testing.T) { + for _, scope := range []string{ // A semi-representative assortment of values; everything is rejected. + sha256digestHex, + sha256digest, + "docker.io/library/busybox:latest", + "docker.io", + "", + } { + err := Transport.ValidatePolicyConfigurationScope(scope) + assert.Error(t, err, scope) + } +} + +func TestParseReference(t *testing.T) { + testParseReference(t, ParseReference) +} + +// 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, expectedID, expectedRef string }{ + {sha256digest, sha256digest, ""}, // Valid digest format + {"sha512:" + sha256digestHex + sha256digestHex, "", ""}, // Non-digest.Canonical digest + {"sha256:ab", "", ""}, // Invalid digest value (too short) + {sha256digest + "ab", "", ""}, // Invalid digest value (too long) + {"sha256:XX23456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "", ""}, // Invalid digest value + {"UPPERCASEISINVALID", "", ""}, // Invalid reference input + {"busybox", "", ""}, // Missing tag or 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! + // 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 == "" { + assert.Error(t, err, c.input) + } else { + 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 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) + + _, 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) + + _, ok := dockerRef.(reference.Named) + require.True(t, ok, c.input) + assert.Equal(t, c.expectedRef, dockerRef.String(), c.input) + } + } + } +} + +// 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", "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) { + // An ID reference. + id, err := digest.Parse(sha256digest) + require.NoError(t, err) + ref, err := NewReference(id, nil) + require.NoError(t, err) + daemonRef, ok := ref.(daemonReference) + require.True(t, ok) + assert.Equal(t, id, daemonRef.id) + assert.Nil(t, daemonRef.ref) + + // Named references + for _, c := range validNamedReferenceTestCases { + parsed, err := reference.ParseNormalizedNamed(c.input) + require.NoError(t, err) + ref, err := NewReference("", parsed) + require.NoError(t, err, c.input) + daemonRef, ok := ref.(daemonReference) + require.True(t, ok, c.input) + assert.Equal(t, "", daemonRef.id.String()) + require.NotNil(t, daemonRef.ref) + assert.Equal(t, c.dockerRef, daemonRef.ref.String(), c.input) + } + + // Both an ID and a named reference provided + 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.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.ParseNormalizedNamed("busybox:notlatest@" + sha256digest) + require.NoError(t, err) + _, ok = parsed.(reference.Canonical) + require.True(t, ok) + _, ok = parsed.(reference.NamedTagged) + require.True(t, ok) + _, err = NewReference("", parsed) + assert.Error(t, err) +} + +func TestReferenceTransport(t *testing.T) { + ref, err := ParseReference(sha256digest) + require.NoError(t, err) + assert.Equal(t, Transport, ref.Transport()) + + ref, err = ParseReference("busybox:latest") + require.NoError(t, err) + assert.Equal(t, Transport, ref.Transport()) +} + +func TestReferenceStringWithinTransport(t *testing.T) { + ref, err := ParseReference(sha256digest) + require.NoError(t, err) + assert.Equal(t, sha256digest, ref.StringWithinTransport()) + + for _, c := range validNamedReferenceTestCases { + ref, err := ParseReference(c.input) + require.NoError(t, err, c.input) + stringRef := ref.StringWithinTransport() + assert.Equal(t, c.stringWithinTransport, stringRef, c.input) + // Do one more round to verify that the output can be parsed, to an equal value. + ref2, err := Transport.ParseReference(stringRef) + require.NoError(t, err, c.input) + stringRef2 := ref2.StringWithinTransport() + assert.Equal(t, stringRef, stringRef2, c.input) + } +} + +func TestReferenceDockerReference(t *testing.T) { + ref, err := ParseReference(sha256digest) + require.NoError(t, err) + assert.Nil(t, ref.DockerReference()) + + for _, c := range validNamedReferenceTestCases { + ref, err := ParseReference(c.input) + require.NoError(t, err, c.input) + dockerRef := ref.DockerReference() + require.NotNil(t, dockerRef, c.input) + assert.Equal(t, c.dockerRef, dockerRef.String(), c.input) + } +} + +func TestReferencePolicyConfigurationIdentity(t *testing.T) { + ref, err := ParseReference(sha256digest) + require.NoError(t, err) + assert.Equal(t, "", ref.PolicyConfigurationIdentity()) + + for _, c := range validNamedReferenceTestCases { + ref, err := ParseReference(c.input) + require.NoError(t, err, c.input) + assert.Equal(t, "", ref.PolicyConfigurationIdentity(), c.input) + } +} + +func TestReferencePolicyConfigurationNamespaces(t *testing.T) { + ref, err := ParseReference(sha256digest) + require.NoError(t, err) + assert.Empty(t, ref.PolicyConfigurationNamespaces()) + + for _, c := range validNamedReferenceTestCases { + ref, err := ParseReference(c.input) + require.NoError(t, err, c.input) + assert.Empty(t, ref.PolicyConfigurationNamespaces(), c.input) + } +} + +// daemonReference.NewImage, daemonReference.NewImageSource, openshiftReference.NewImageDestination +// untested because just creating the objects immediately connects to the daemon. + +func TestReferenceDeleteImage(t *testing.T) { + ref, err := ParseReference(sha256digest) + require.NoError(t, err) + err = ref.DeleteImage(nil) + assert.Error(t, err) + + for _, c := range validNamedReferenceTestCases { + ref, err := ParseReference(c.input) + require.NoError(t, err, c.input) + err = ref.DeleteImage(nil) + assert.Error(t, err, c.input) + } +} diff --git a/vendor/github.com/containers/image/docker/docker_client_test.go b/vendor/github.com/containers/image/docker/docker_client_test.go new file mode 100644 index 0000000000..937603572c --- /dev/null +++ b/vendor/github.com/containers/image/docker/docker_client_test.go @@ -0,0 +1,432 @@ +package docker + +import ( + "encoding/base64" + "encoding/json" + //"fmt" + "io/ioutil" + "os" + "path/filepath" + "reflect" + "testing" + + "github.com/containers/image/types" + "github.com/containers/storage/pkg/homedir" +) + +func TestGetAuth(t *testing.T) { + origHomeDir := homedir.Get() + tmpDir, err := ioutil.TempDir("", "test_docker_client_get_auth") + if err != nil { + t.Fatal(err) + } + t.Logf("using temporary home directory: %q", tmpDir) + // override homedir + os.Setenv(homedir.Key(), tmpDir) + defer func() { + err := os.RemoveAll(tmpDir) + if err != nil { + t.Logf("failed to cleanup temporary home directory %q: %v", tmpDir, err) + } + os.Setenv(homedir.Key(), origHomeDir) + }() + + configDir := filepath.Join(tmpDir, ".docker") + if err := os.Mkdir(configDir, 0750); err != nil { + t.Fatal(err) + } + configPath := filepath.Join(configDir, "config.json") + + for _, tc := range []struct { + name string + hostname string + authConfig testAuthConfig + expectedUsername string + expectedPassword string + expectedError error + ctx *types.SystemContext + }{ + { + name: "empty hostname", + authConfig: makeTestAuthConfig(testAuthConfigDataMap{"localhost:5000": testAuthConfigData{"bob", "password"}}), + }, + { + name: "no auth config", + hostname: "index.docker.io", + }, + { + name: "match one", + hostname: "example.org", + authConfig: makeTestAuthConfig(testAuthConfigDataMap{"example.org": testAuthConfigData{"joe", "mypass"}}), + expectedUsername: "joe", + expectedPassword: "mypass", + }, + { + name: "match none", + hostname: "registry.example.org", + authConfig: makeTestAuthConfig(testAuthConfigDataMap{"example.org": testAuthConfigData{"joe", "mypass"}}), + }, + { + name: "match docker.io", + hostname: "docker.io", + authConfig: makeTestAuthConfig(testAuthConfigDataMap{ + "example.org": testAuthConfigData{"example", "org"}, + "index.docker.io": testAuthConfigData{"index", "docker.io"}, + "docker.io": testAuthConfigData{"docker", "io"}, + }), + expectedUsername: "docker", + expectedPassword: "io", + }, + { + name: "match docker.io normalized", + hostname: "docker.io", + authConfig: makeTestAuthConfig(testAuthConfigDataMap{ + "example.org": testAuthConfigData{"bob", "pw"}, + "https://index.docker.io/v1": testAuthConfigData{"alice", "wp"}, + }), + expectedUsername: "alice", + expectedPassword: "wp", + }, + { + name: "normalize registry", + hostname: "https://docker.io/v1", + authConfig: makeTestAuthConfig(testAuthConfigDataMap{ + "docker.io": testAuthConfigData{"user", "pw"}, + "localhost:5000": testAuthConfigData{"joe", "pass"}, + }), + expectedUsername: "user", + expectedPassword: "pw", + }, + { + name: "match localhost", + hostname: "http://localhost", + authConfig: makeTestAuthConfig(testAuthConfigDataMap{ + "docker.io": testAuthConfigData{"user", "pw"}, + "localhost": testAuthConfigData{"joe", "pass"}, + "example.com": testAuthConfigData{"alice", "pwd"}, + }), + expectedUsername: "joe", + expectedPassword: "pass", + }, + { + name: "match ip", + hostname: "10.10.3.56:5000", + authConfig: makeTestAuthConfig(testAuthConfigDataMap{ + "10.10.30.45": testAuthConfigData{"user", "pw"}, + "localhost": testAuthConfigData{"joe", "pass"}, + "10.10.3.56": testAuthConfigData{"alice", "pwd"}, + "10.10.3.56:5000": testAuthConfigData{"me", "mine"}, + }), + expectedUsername: "me", + expectedPassword: "mine", + }, + { + name: "match port", + hostname: "https://localhost:5000", + authConfig: makeTestAuthConfig(testAuthConfigDataMap{ + "https://127.0.0.1:5000": testAuthConfigData{"user", "pw"}, + "http://localhost": testAuthConfigData{"joe", "pass"}, + "https://localhost:5001": testAuthConfigData{"alice", "pwd"}, + "localhost:5000": testAuthConfigData{"me", "mine"}, + }), + expectedUsername: "me", + expectedPassword: "mine", + }, + { + name: "use system context", + hostname: "example.org", + authConfig: makeTestAuthConfig(testAuthConfigDataMap{ + "example.org": testAuthConfigData{"user", "pw"}, + }), + expectedUsername: "foo", + expectedPassword: "bar", + ctx: &types.SystemContext{ + DockerAuthConfig: &types.DockerAuthConfig{ + Username: "foo", + Password: "bar", + }, + }, + }, + } { + contents, err := json.MarshalIndent(&tc.authConfig, "", " ") + if err != nil { + t.Errorf("[%s] failed to marshal authConfig: %v", tc.name, err) + continue + } + if err := ioutil.WriteFile(configPath, contents, 0640); err != nil { + t.Errorf("[%s] failed to write file %q: %v", tc.name, configPath, err) + continue + } + + var ctx *types.SystemContext + if tc.ctx != nil { + ctx = tc.ctx + } + username, password, err := getAuth(ctx, tc.hostname) + if err == nil && tc.expectedError != nil { + t.Errorf("[%s] got unexpected non error and username=%q, password=%q", tc.name, username, password) + continue + } + if err != nil && tc.expectedError == nil { + t.Errorf("[%s] got unexpected error: %#+v", tc.name, err) + continue + } + if !reflect.DeepEqual(err, tc.expectedError) { + t.Errorf("[%s] got unexpected error: %#+v != %#+v", tc.name, err, tc.expectedError) + continue + } + + if username != tc.expectedUsername { + t.Errorf("[%s] got unexpected user name: %q != %q", tc.name, username, tc.expectedUsername) + } + if password != tc.expectedPassword { + t.Errorf("[%s] got unexpected user name: %q != %q", tc.name, password, tc.expectedPassword) + } + } +} + +func TestGetAuthFromLegacyFile(t *testing.T) { + origHomeDir := homedir.Get() + tmpDir, err := ioutil.TempDir("", "test_docker_client_get_auth") + if err != nil { + t.Fatal(err) + } + t.Logf("using temporary home directory: %q", tmpDir) + // override homedir + os.Setenv(homedir.Key(), tmpDir) + defer func() { + err := os.RemoveAll(tmpDir) + if err != nil { + t.Logf("failed to cleanup temporary home directory %q: %v", tmpDir, err) + } + os.Setenv(homedir.Key(), origHomeDir) + }() + + configPath := filepath.Join(tmpDir, ".dockercfg") + + for _, tc := range []struct { + name string + hostname string + authConfig testAuthConfig + expectedUsername string + expectedPassword string + expectedError error + }{ + { + name: "normalize registry", + hostname: "https://docker.io/v1", + authConfig: makeTestAuthConfig(testAuthConfigDataMap{ + "docker.io": testAuthConfigData{"user", "pw"}, + "localhost:5000": testAuthConfigData{"joe", "pass"}, + }), + expectedUsername: "user", + expectedPassword: "pw", + }, + { + name: "ignore schema and path", + hostname: "http://index.docker.io/v1", + authConfig: makeTestAuthConfig(testAuthConfigDataMap{ + "docker.io/v2": testAuthConfigData{"user", "pw"}, + "https://localhost/v1": testAuthConfigData{"joe", "pwd"}, + }), + expectedUsername: "user", + expectedPassword: "pw", + }, + } { + contents, err := json.MarshalIndent(&tc.authConfig.Auths, "", " ") + if err != nil { + t.Errorf("[%s] failed to marshal authConfig: %v", tc.name, err) + continue + } + if err := ioutil.WriteFile(configPath, contents, 0640); err != nil { + t.Errorf("[%s] failed to write file %q: %v", tc.name, configPath, err) + continue + } + + username, password, err := getAuth(nil, tc.hostname) + if err == nil && tc.expectedError != nil { + t.Errorf("[%s] got unexpected non error and username=%q, password=%q", tc.name, username, password) + continue + } + if err != nil && tc.expectedError == nil { + t.Errorf("[%s] got unexpected error: %#+v", tc.name, err) + continue + } + if !reflect.DeepEqual(err, tc.expectedError) { + t.Errorf("[%s] got unexpected error: %#+v != %#+v", tc.name, err, tc.expectedError) + continue + } + + if username != tc.expectedUsername { + t.Errorf("[%s] got unexpected user name: %q != %q", tc.name, username, tc.expectedUsername) + } + if password != tc.expectedPassword { + t.Errorf("[%s] got unexpected user name: %q != %q", tc.name, password, tc.expectedPassword) + } + } +} + +func TestGetAuthPreferNewConfig(t *testing.T) { + origHomeDir := homedir.Get() + tmpDir, err := ioutil.TempDir("", "test_docker_client_get_auth") + if err != nil { + t.Fatal(err) + } + t.Logf("using temporary home directory: %q", tmpDir) + // override homedir + os.Setenv(homedir.Key(), tmpDir) + defer func() { + err := os.RemoveAll(tmpDir) + if err != nil { + t.Logf("failed to cleanup temporary home directory %q: %v", tmpDir, err) + } + os.Setenv(homedir.Key(), origHomeDir) + }() + + configDir := filepath.Join(tmpDir, ".docker") + if err := os.Mkdir(configDir, 0750); err != nil { + t.Fatal(err) + } + + for _, data := range []struct { + path string + ac interface{} + }{ + { + filepath.Join(configDir, "config.json"), + makeTestAuthConfig(testAuthConfigDataMap{ + "https://index.docker.io/v1/": testAuthConfigData{"alice", "pass"}, + }), + }, + { + filepath.Join(tmpDir, ".dockercfg"), + makeTestAuthConfig(testAuthConfigDataMap{ + "https://index.docker.io/v1/": testAuthConfigData{"bob", "pw"}, + }).Auths, + }, + } { + contents, err := json.MarshalIndent(&data.ac, "", " ") + if err != nil { + t.Fatalf("failed to marshal authConfig: %v", err) + } + if err := ioutil.WriteFile(data.path, contents, 0640); err != nil { + t.Fatalf("failed to write file %q: %v", data.path, err) + } + } + + username, password, err := getAuth(nil, "index.docker.io") + if err != nil { + t.Fatalf("got unexpected error: %#+v", err) + } + + if username != "alice" { + t.Fatalf("got unexpected user name: %q != %q", username, "alice") + } + if password != "pass" { + t.Fatalf("got unexpected user name: %q != %q", password, "pass") + } +} + +func TestGetAuthFailsOnBadInput(t *testing.T) { + origHomeDir := homedir.Get() + tmpDir, err := ioutil.TempDir("", "test_docker_client_get_auth") + if err != nil { + t.Fatal(err) + } + t.Logf("using temporary home directory: %q", tmpDir) + // override homedir + os.Setenv(homedir.Key(), tmpDir) + defer func() { + err := os.RemoveAll(tmpDir) + if err != nil { + t.Logf("failed to cleanup temporary home directory %q: %v", tmpDir, err) + } + os.Setenv(homedir.Key(), origHomeDir) + }() + + configDir := filepath.Join(tmpDir, ".docker") + if err := os.Mkdir(configDir, 0750); err != nil { + t.Fatal(err) + } + configPath := filepath.Join(configDir, "config.json") + + // no config file present + username, password, err := getAuth(nil, "index.docker.io") + if err != nil { + t.Fatalf("got unexpected error: %#+v", err) + } + if len(username) > 0 || len(password) > 0 { + t.Fatalf("got unexpected not empty username/password: %q/%q", username, password) + } + + if err := ioutil.WriteFile(configPath, []byte("Json rocks! Unless it doesn't."), 0640); err != nil { + t.Fatalf("failed to write file %q: %v", configPath, err) + } + username, password, err = getAuth(nil, "index.docker.io") + if err == nil { + t.Fatalf("got unexpected non-error: username=%q, password=%q", username, password) + } + if _, ok := err.(*json.SyntaxError); !ok { + t.Fatalf("expected os.PathError, not: %#+v", err) + } + + // remove the invalid config file + os.RemoveAll(configPath) + // no config file present + username, password, err = getAuth(nil, "index.docker.io") + if err != nil { + t.Fatalf("got unexpected error: %#+v", err) + } + if len(username) > 0 || len(password) > 0 { + t.Fatalf("got unexpected not empty username/password: %q/%q", username, password) + } + + configPath = filepath.Join(tmpDir, ".dockercfg") + if err := ioutil.WriteFile(configPath, []byte("I'm certainly not a json string."), 0640); err != nil { + t.Fatalf("failed to write file %q: %v", configPath, err) + } + username, password, err = getAuth(nil, "index.docker.io") + if err == nil { + t.Fatalf("got unexpected non-error: username=%q, password=%q", username, password) + } + if _, ok := err.(*json.SyntaxError); !ok { + t.Fatalf("expected os.PathError, not: %#+v", err) + } +} + +type testAuthConfigData struct { + username string + password string +} + +type testAuthConfigDataMap map[string]testAuthConfigData + +type testAuthConfigEntry struct { + Auth string `json:"auth,omitempty"` +} + +type testAuthConfig struct { + Auths map[string]testAuthConfigEntry `json:"auths"` +} + +// encodeAuth creates an auth value from given authConfig data to be stored in auth config file. +// Inspired by github.com/docker/docker/cliconfig/config.go v1.10.3. +func encodeAuth(authConfig *testAuthConfigData) string { + authStr := authConfig.username + ":" + authConfig.password + msg := []byte(authStr) + encoded := make([]byte, base64.StdEncoding.EncodedLen(len(msg))) + base64.StdEncoding.Encode(encoded, msg) + return string(encoded) +} + +func makeTestAuthConfig(authConfigData map[string]testAuthConfigData) testAuthConfig { + ac := testAuthConfig{ + Auths: make(map[string]testAuthConfigEntry), + } + for host, data := range authConfigData { + ac.Auths[host] = testAuthConfigEntry{ + Auth: encodeAuth(&data), + } + } + return ac +} diff --git a/vendor/github.com/containers/image/docker/docker_image_src_test.go b/vendor/github.com/containers/image/docker/docker_image_src_test.go new file mode 100644 index 0000000000..43e262a246 --- /dev/null +++ b/vendor/github.com/containers/image/docker/docker_image_src_test.go @@ -0,0 +1,24 @@ +package docker + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSimplifyContentType(t *testing.T) { + for _, c := range []struct{ input, expected string }{ + {"", ""}, + {"application/json", "application/json"}, + {"application/json;charset=utf-8", "application/json"}, + {"application/json; charset=utf-8", "application/json"}, + {"application/json ; charset=utf-8", "application/json"}, + {"application/json\t;\tcharset=utf-8", "application/json"}, + {"application/json ;charset=utf-8", "application/json"}, + {`application/json; charset="utf-8"`, "application/json"}, + {"completely invalid", ""}, + } { + out := simplifyContentType(c.input) + assert.Equal(t, c.expected, out, c.input) + } +} diff --git a/vendor/github.com/containers/image/docker/docker_transport_test.go b/vendor/github.com/containers/image/docker/docker_transport_test.go new file mode 100644 index 0000000000..98dc7f5861 --- /dev/null +++ b/vendor/github.com/containers/image/docker/docker_transport_test.go @@ -0,0 +1,196 @@ +package docker + +import ( + "testing" + + "github.com/containers/image/docker/reference" + "github.com/containers/image/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + sha256digestHex = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + sha256digest = "@sha256:" + sha256digestHex +) + +func TestTransportName(t *testing.T) { + assert.Equal(t, "docker", Transport.Name()) +} + +func TestTransportParseReference(t *testing.T) { + testParseReference(t, Transport.ParseReference) +} + +func TestTransportValidatePolicyConfigurationScope(t *testing.T) { + for _, scope := range []string{ + "docker.io/library/busybox" + sha256digest, + "docker.io/library/busybox:notlatest", + "docker.io/library/busybox", + "docker.io/library", + "docker.io", + } { + err := Transport.ValidatePolicyConfigurationScope(scope) + assert.NoError(t, err, scope) + } +} + +func TestParseReference(t *testing.T) { + testParseReference(t, ParseReference) +} + +// 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", "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! + // 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 == "" { + assert.Error(t, err, c.input) + } else { + require.NoError(t, err, c.input) + dockerRef, ok := ref.(dockerReference) + require.True(t, ok, c.input) + assert.Equal(t, c.expected, dockerRef.ref.String(), c.input) + } + } +} + +// A common list of reference formats to test for the various ImageReference methods. +var validReferenceTestCases = []struct{ input, dockerRef, stringWithinTransport string }{ + {"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.ParseNormalizedNamed(c.input) + require.NoError(t, err) + ref, err := NewReference(parsed) + require.NoError(t, err, c.input) + dockerRef, ok := ref.(dockerReference) + require.True(t, ok, c.input) + assert.Equal(t, c.dockerRef, dockerRef.ref.String(), c.input) + } + + // Neither a tag nor digest + 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.ParseNormalizedNamed("busybox:notlatest" + sha256digest) + require.NoError(t, err) + _, ok := parsed.(reference.Canonical) + require.True(t, ok) + _, ok = parsed.(reference.NamedTagged) + require.True(t, ok) + _, err = NewReference(parsed) + assert.Error(t, err) +} + +func TestReferenceTransport(t *testing.T) { + ref, err := ParseReference("//busybox") + require.NoError(t, err) + assert.Equal(t, Transport, ref.Transport()) +} + +func TestReferenceStringWithinTransport(t *testing.T) { + for _, c := range validReferenceTestCases { + ref, err := ParseReference("//" + c.input) + require.NoError(t, err, c.input) + stringRef := ref.StringWithinTransport() + assert.Equal(t, c.stringWithinTransport, stringRef, c.input) + // Do one more round to verify that the output can be parsed, to an equal value. + ref2, err := Transport.ParseReference(stringRef) + require.NoError(t, err, c.input) + stringRef2 := ref2.StringWithinTransport() + assert.Equal(t, stringRef, stringRef2, c.input) + } +} + +func TestReferenceDockerReference(t *testing.T) { + for _, c := range validReferenceTestCases { + ref, err := ParseReference("//" + c.input) + require.NoError(t, err, c.input) + dockerRef := ref.DockerReference() + require.NotNil(t, dockerRef, c.input) + assert.Equal(t, c.dockerRef, dockerRef.String(), c.input) + } +} + +func TestReferencePolicyConfigurationIdentity(t *testing.T) { + // Just a smoke test, the substance is tested in policyconfiguration.TestDockerReference. + ref, err := ParseReference("//busybox") + require.NoError(t, err) + assert.Equal(t, "docker.io/library/busybox:latest", ref.PolicyConfigurationIdentity()) +} + +func TestReferencePolicyConfigurationNamespaces(t *testing.T) { + // Just a smoke test, the substance is tested in policyconfiguration.TestDockerReference. + ref, err := ParseReference("//busybox") + require.NoError(t, err) + assert.Equal(t, []string{ + "docker.io/library/busybox", + "docker.io/library", + "docker.io", + }, ref.PolicyConfigurationNamespaces()) +} + +func TestReferenceNewImage(t *testing.T) { + ref, err := ParseReference("//busybox") + require.NoError(t, err) + img, err := ref.NewImage(&types.SystemContext{RegistriesDirPath: "/this/doesnt/exist"}) + assert.NoError(t, err) + defer img.Close() +} + +func TestReferenceNewImageSource(t *testing.T) { + ref, err := ParseReference("//busybox") + require.NoError(t, err) + src, err := ref.NewImageSource(&types.SystemContext{RegistriesDirPath: "/this/doesnt/exist"}, nil) + assert.NoError(t, err) + defer src.Close() +} + +func TestReferenceNewImageDestination(t *testing.T) { + ref, err := ParseReference("//busybox") + require.NoError(t, err) + dest, err := ref.NewImageDestination(&types.SystemContext{RegistriesDirPath: "/this/doesnt/exist"}) + assert.NoError(t, err) + defer dest.Close() +} + +func TestReferenceTagOrDigest(t *testing.T) { + for input, expected := range map[string]string{ + "//busybox:notlatest": "notlatest", + "//busybox" + sha256digest: "sha256:" + sha256digestHex, + } { + ref, err := ParseReference(input) + require.NoError(t, err, input) + dockerRef, ok := ref.(dockerReference) + require.True(t, ok, input) + tod, err := dockerRef.tagOrDigest() + require.NoError(t, err, input) + assert.Equal(t, expected, tod, input) + } + + // Invalid input + ref, err := reference.ParseNormalizedNamed("busybox") + require.NoError(t, err) + dockerRef := dockerReference{ref: ref} + _, err = dockerRef.tagOrDigest() + assert.Error(t, err) +} diff --git a/vendor/github.com/containers/image/docker/fixtures/registries.d/emptyConfig.yaml b/vendor/github.com/containers/image/docker/fixtures/registries.d/emptyConfig.yaml new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/vendor/github.com/containers/image/docker/fixtures/registries.d/emptyConfig.yaml @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/vendor/github.com/containers/image/docker/fixtures/registries.d/internal-example.com.yaml b/vendor/github.com/containers/image/docker/fixtures/registries.d/internal-example.com.yaml new file mode 100644 index 0000000000..526402b107 --- /dev/null +++ b/vendor/github.com/containers/image/docker/fixtures/registries.d/internal-example.com.yaml @@ -0,0 +1,14 @@ +docker: + example.com: + sigstore: https://sigstore.example.com + registry.test.example.com: + sigstore: http://registry.test.example.com/sigstore + registry.test.example.com:8888: + sigstore: http://registry.test.example.com:8889/sigstore + sigstore-staging: https://registry.test.example.com:8889/sigstore/specialAPIserverWhichDoesntExist + localhost: + sigstore: file:///home/mitr/mydevelopment1 + localhost:8080: + sigstore: file:///home/mitr/mydevelopment2 + localhost/invalid/url/test: + sigstore: ":emptyscheme" diff --git a/vendor/github.com/containers/image/docker/fixtures/registries.d/internet-user.yaml b/vendor/github.com/containers/image/docker/fixtures/registries.d/internet-user.yaml new file mode 100644 index 0000000000..53969d2465 --- /dev/null +++ b/vendor/github.com/containers/image/docker/fixtures/registries.d/internet-user.yaml @@ -0,0 +1,12 @@ +default-docker: + sigstore: file:///mnt/companywide/signatures/for/other/repositories +docker: + docker.io/contoso: + sigstore: https://sigstore.contoso.com/fordocker + docker.io/centos: + sigstore: https://sigstore.centos.org/ + docker.io/centos/mybetaprooduct: + sigstore: http://localhost:9999/mybetaWIP/sigstore + sigstore-staging: file:///srv/mybetaWIP/sigstore + docker.io/centos/mybetaproduct:latest: + sigstore: https://sigstore.centos.org/ diff --git a/vendor/github.com/containers/image/docker/fixtures/registries.d/invalid-but.notyaml b/vendor/github.com/containers/image/docker/fixtures/registries.d/invalid-but.notyaml new file mode 100644 index 0000000000..5c34318c21 --- /dev/null +++ b/vendor/github.com/containers/image/docker/fixtures/registries.d/invalid-but.notyaml @@ -0,0 +1 @@ +} diff --git a/vendor/github.com/containers/image/docker/lookaside_test.go b/vendor/github.com/containers/image/docker/lookaside_test.go new file mode 100644 index 0000000000..7cab4b8804 --- /dev/null +++ b/vendor/github.com/containers/image/docker/lookaside_test.go @@ -0,0 +1,277 @@ +package docker + +import ( + "fmt" + "io/ioutil" + "net/url" + "os" + "path/filepath" + "testing" + + "github.com/containers/image/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func dockerRefFromString(t *testing.T, s string) dockerReference { + ref, err := ParseReference(s) + require.NoError(t, err, s) + dockerRef, ok := ref.(dockerReference) + require.True(t, ok, s) + return dockerRef +} + +func TestConfiguredSignatureStorageBase(t *testing.T) { + // Error reading configuration directory (/dev/null is not a directory) + _, err := configuredSignatureStorageBase(&types.SystemContext{RegistriesDirPath: "/dev/null"}, + dockerRefFromString(t, "//busybox"), false) + assert.Error(t, err) + + // No match found + emptyDir, err := ioutil.TempDir("", "empty-dir") + require.NoError(t, err) + defer os.RemoveAll(emptyDir) + base, err := configuredSignatureStorageBase(&types.SystemContext{RegistriesDirPath: emptyDir}, + dockerRefFromString(t, "//this/is/not/in/the:configuration"), false) + assert.NoError(t, err) + assert.Nil(t, base) + + // Invalid URL + _, err = configuredSignatureStorageBase(&types.SystemContext{RegistriesDirPath: "fixtures/registries.d"}, + dockerRefFromString(t, "//localhost/invalid/url/test"), false) + assert.Error(t, err) + + // Success + base, err = configuredSignatureStorageBase(&types.SystemContext{RegistriesDirPath: "fixtures/registries.d"}, + dockerRefFromString(t, "//example.com/my/project"), false) + assert.NoError(t, err) + require.NotNil(t, base) + assert.Equal(t, "https://sigstore.example.com/example.com/my/project", (*url.URL)(base).String()) +} + +func TestRegistriesDirPath(t *testing.T) { + const nondefaultPath = "/this/is/not/the/default/registries.d" + const variableReference = "$HOME" + const rootPrefix = "/root/prefix" + + for _, c := range []struct { + ctx *types.SystemContext + expected string + }{ + // The common case + {nil, systemRegistriesDirPath}, + // There is a context, but it does not override the path. + {&types.SystemContext{}, systemRegistriesDirPath}, + // Path overridden + {&types.SystemContext{RegistriesDirPath: nondefaultPath}, nondefaultPath}, + // Root overridden + { + &types.SystemContext{RootForImplicitAbsolutePaths: rootPrefix}, + filepath.Join(rootPrefix, systemRegistriesDirPath), + }, + // Root and path overrides present simultaneously, + { + &types.SystemContext{ + RootForImplicitAbsolutePaths: rootPrefix, + RegistriesDirPath: nondefaultPath, + }, + nondefaultPath, + }, + // No environment expansion happens in the overridden paths + {&types.SystemContext{RegistriesDirPath: variableReference}, variableReference}, + } { + path := registriesDirPath(c.ctx) + assert.Equal(t, c.expected, path) + } +} + +func TestLoadAndMergeConfig(t *testing.T) { + tmpDir, err := ioutil.TempDir("", "merge-config") + require.NoError(t, err) + defer os.RemoveAll(tmpDir) + + // No registries.d exists + config, err := loadAndMergeConfig(filepath.Join(tmpDir, "thisdoesnotexist")) + require.NoError(t, err) + assert.Equal(t, ®istryConfiguration{Docker: map[string]registryNamespace{}}, config) + + // Empty registries.d directory + emptyDir := filepath.Join(tmpDir, "empty") + err = os.Mkdir(emptyDir, 0755) + require.NoError(t, err) + config, err = loadAndMergeConfig(emptyDir) + require.NoError(t, err) + assert.Equal(t, ®istryConfiguration{Docker: map[string]registryNamespace{}}, config) + + // Unreadable registries.d directory + unreadableDir := filepath.Join(tmpDir, "unreadable") + err = os.Mkdir(unreadableDir, 0000) + require.NoError(t, err) + config, err = loadAndMergeConfig(unreadableDir) + assert.Error(t, err) + + // An unreadable file in a registries.d directory + unreadableFileDir := filepath.Join(tmpDir, "unreadableFile") + err = os.Mkdir(unreadableFileDir, 0755) + require.NoError(t, err) + err = ioutil.WriteFile(filepath.Join(unreadableFileDir, "0.yaml"), []byte("{}"), 0644) + require.NoError(t, err) + err = ioutil.WriteFile(filepath.Join(unreadableFileDir, "1.yaml"), nil, 0000) + require.NoError(t, err) + config, err = loadAndMergeConfig(unreadableFileDir) + assert.Error(t, err) + + // Invalid YAML + invalidYAMLDir := filepath.Join(tmpDir, "invalidYAML") + err = os.Mkdir(invalidYAMLDir, 0755) + require.NoError(t, err) + err = ioutil.WriteFile(filepath.Join(invalidYAMLDir, "0.yaml"), []byte("}"), 0644) + require.NoError(t, err) + config, err = loadAndMergeConfig(invalidYAMLDir) + assert.Error(t, err) + + // Duplicate DefaultDocker + duplicateDefault := filepath.Join(tmpDir, "duplicateDefault") + err = os.Mkdir(duplicateDefault, 0755) + require.NoError(t, err) + err = ioutil.WriteFile(filepath.Join(duplicateDefault, "0.yaml"), + []byte("default-docker:\n sigstore: file:////tmp/something"), 0644) + require.NoError(t, err) + err = ioutil.WriteFile(filepath.Join(duplicateDefault, "1.yaml"), + []byte("default-docker:\n sigstore: file:////tmp/different"), 0644) + require.NoError(t, err) + config, err = loadAndMergeConfig(duplicateDefault) + require.Error(t, err) + assert.Contains(t, err.Error(), "0.yaml") + assert.Contains(t, err.Error(), "1.yaml") + + // Duplicate DefaultDocker + duplicateNS := filepath.Join(tmpDir, "duplicateNS") + err = os.Mkdir(duplicateNS, 0755) + require.NoError(t, err) + err = ioutil.WriteFile(filepath.Join(duplicateNS, "0.yaml"), + []byte("docker:\n example.com:\n sigstore: file:////tmp/something"), 0644) + require.NoError(t, err) + err = ioutil.WriteFile(filepath.Join(duplicateNS, "1.yaml"), + []byte("docker:\n example.com:\n sigstore: file:////tmp/different"), 0644) + require.NoError(t, err) + config, err = loadAndMergeConfig(duplicateNS) + assert.Error(t, err) + assert.Contains(t, err.Error(), "0.yaml") + assert.Contains(t, err.Error(), "1.yaml") + + // A fully worked example, including an empty-dictionary file and a non-.yaml file + config, err = loadAndMergeConfig("fixtures/registries.d") + require.NoError(t, err) + assert.Equal(t, ®istryConfiguration{ + DefaultDocker: ®istryNamespace{SigStore: "file:///mnt/companywide/signatures/for/other/repositories"}, + Docker: map[string]registryNamespace{ + "example.com": {SigStore: "https://sigstore.example.com"}, + "registry.test.example.com": {SigStore: "http://registry.test.example.com/sigstore"}, + "registry.test.example.com:8888": {SigStore: "http://registry.test.example.com:8889/sigstore", SigStoreStaging: "https://registry.test.example.com:8889/sigstore/specialAPIserverWhichDoesntExist"}, + "localhost": {SigStore: "file:///home/mitr/mydevelopment1"}, + "localhost:8080": {SigStore: "file:///home/mitr/mydevelopment2"}, + "localhost/invalid/url/test": {SigStore: ":emptyscheme"}, + "docker.io/contoso": {SigStore: "https://sigstore.contoso.com/fordocker"}, + "docker.io/centos": {SigStore: "https://sigstore.centos.org/"}, + "docker.io/centos/mybetaprooduct": { + SigStore: "http://localhost:9999/mybetaWIP/sigstore", + SigStoreStaging: "file:///srv/mybetaWIP/sigstore", + }, + "docker.io/centos/mybetaproduct:latest": {SigStore: "https://sigstore.centos.org/"}, + }, + }, config) +} + +func TestRegistryConfigurationSignaureTopLevel(t *testing.T) { + config := registryConfiguration{ + DefaultDocker: ®istryNamespace{SigStore: "=default", SigStoreStaging: "=default+w"}, + Docker: map[string]registryNamespace{}, + } + for _, ns := range []string{ + "localhost", + "localhost:5000", + "example.com", + "example.com/ns1", + "example.com/ns1/ns2", + "example.com/ns1/ns2/repo", + "example.com/ns1/ns2/repo:notlatest", + } { + config.Docker[ns] = registryNamespace{SigStore: ns, SigStoreStaging: ns + "+w"} + } + + for _, c := range []struct{ input, expected string }{ + {"example.com/ns1/ns2/repo:notlatest", "example.com/ns1/ns2/repo:notlatest"}, + {"example.com/ns1/ns2/repo:unmatched", "example.com/ns1/ns2/repo"}, + {"example.com/ns1/ns2/notrepo:notlatest", "example.com/ns1/ns2"}, + {"example.com/ns1/notns2/repo:notlatest", "example.com/ns1"}, + {"example.com/notns1/ns2/repo:notlatest", "example.com"}, + {"unknown.example.com/busybox", "=default"}, + {"localhost:5000/busybox", "localhost:5000"}, + {"localhost/busybox", "localhost"}, + {"localhost:9999/busybox", "=default"}, + } { + dr := dockerRefFromString(t, "//"+c.input) + + res := config.signatureTopLevel(dr, false) + assert.Equal(t, c.expected, res, c.input) + res = config.signatureTopLevel(dr, true) // test that forWriting is correctly propagated + assert.Equal(t, c.expected+"+w", res, c.input) + } + + config = registryConfiguration{ + Docker: map[string]registryNamespace{ + "unmatched": {SigStore: "a", SigStoreStaging: "b"}, + }, + } + dr := dockerRefFromString(t, "//thisisnotmatched") + res := config.signatureTopLevel(dr, false) + assert.Equal(t, "", res) + res = config.signatureTopLevel(dr, true) + assert.Equal(t, "", res) +} + +func TestRegistryNamespaceSignatureTopLevel(t *testing.T) { + for _, c := range []struct { + ns registryNamespace + forWriting bool + expected string + }{ + {registryNamespace{SigStoreStaging: "a", SigStore: "b"}, true, "a"}, + {registryNamespace{SigStoreStaging: "a", SigStore: "b"}, false, "b"}, + {registryNamespace{SigStore: "b"}, true, "b"}, + {registryNamespace{SigStore: "b"}, false, "b"}, + {registryNamespace{SigStoreStaging: "a"}, true, "a"}, + {registryNamespace{SigStoreStaging: "a"}, false, ""}, + {registryNamespace{}, true, ""}, + {registryNamespace{}, false, ""}, + } { + res := c.ns.signatureTopLevel(c.forWriting) + assert.Equal(t, c.expected, res, fmt.Sprintf("%#v %v", c.ns, c.forWriting)) + } +} + +func TestSignatureStorageBaseSignatureStorageURL(t *testing.T) { + const md = "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + + assert.True(t, signatureStorageURL(nil, md, 0) == nil) + for _, c := range []struct { + base string + index int + expected string + }{ + {"file:///tmp", 0, "file:///tmp@" + md + "/signature-1"}, + {"file:///tmp", 1, "file:///tmp@" + md + "/signature-2"}, + {"https://localhost:5555/root", 0, "https://localhost:5555/root@" + md + "/signature-1"}, + {"https://localhost:5555/root", 1, "https://localhost:5555/root@" + md + "/signature-2"}, + {"http://localhost:5555/root", 0, "http://localhost:5555/root@" + md + "/signature-1"}, + {"http://localhost:5555/root", 1, "http://localhost:5555/root@" + md + "/signature-2"}, + } { + url, err := url.Parse(c.base) + require.NoError(t, err) + expectedURL, err := url.Parse(c.expected) + require.NoError(t, err) + res := signatureStorageURL(url, md, c.index) + assert.Equal(t, expectedURL, res, c.expected) + } +} diff --git a/vendor/github.com/containers/image/docker/policyconfiguration/naming_test.go b/vendor/github.com/containers/image/docker/policyconfiguration/naming_test.go new file mode 100644 index 0000000000..5998faa81f --- /dev/null +++ b/vendor/github.com/containers/image/docker/policyconfiguration/naming_test.go @@ -0,0 +1,79 @@ +package policyconfiguration + +import ( + "fmt" + "strings" + "testing" + + "github.com/containers/image/docker/reference" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestDockerReference tests DockerReferenceIdentity and DockerReferenceNamespaces simulatenously +// to ensure they are consistent. +func TestDockerReference(t *testing.T) { + sha256Digest := "@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + // Test both that DockerReferenceIdentity returns the expected value (fullName+suffix), + // and that DockerReferenceNamespaces starts with the expected value (fullName), i.e. that the two functions are + // consistent. + for inputName, expectedNS := range map[string][]string{ + "example.com/ns/repo": {"example.com/ns/repo", "example.com/ns", "example.com"}, + "example.com/repo": {"example.com/repo", "example.com"}, + "localhost/ns/repo": {"localhost/ns/repo", "localhost/ns", "localhost"}, + // Note that "localhost" is special here: notlocalhost/repo is parsed as docker.io/notlocalhost.repo: + "localhost/repo": {"localhost/repo", "localhost"}, + "notlocalhost/repo": {"docker.io/notlocalhost/repo", "docker.io/notlocalhost", "docker.io"}, + "docker.io/ns/repo": {"docker.io/ns/repo", "docker.io/ns", "docker.io"}, + "docker.io/library/repo": {"docker.io/library/repo", "docker.io/library", "docker.io"}, + "docker.io/repo": {"docker.io/library/repo", "docker.io/library", "docker.io"}, + "ns/repo": {"docker.io/ns/repo", "docker.io/ns", "docker.io"}, + "library/repo": {"docker.io/library/repo", "docker.io/library", "docker.io"}, + "repo": {"docker.io/library/repo", "docker.io/library", "docker.io"}, + } { + for inputSuffix, mappedSuffix := range map[string]string{ + ":tag": ":tag", + sha256Digest: sha256Digest, + } { + fullInput := inputName + inputSuffix + ref, err := reference.ParseNormalizedNamed(fullInput) + require.NoError(t, err, fullInput) + + identity, err := DockerReferenceIdentity(ref) + require.NoError(t, err, fullInput) + assert.Equal(t, expectedNS[0]+mappedSuffix, identity, fullInput) + + ns := DockerReferenceNamespaces(ref) + require.NotNil(t, ns, fullInput) + require.Len(t, ns, len(expectedNS), fullInput) + moreSpecific := identity + for i := range expectedNS { + assert.Equal(t, ns[i], expectedNS[i], fmt.Sprintf("%s item %d", fullInput, i)) + assert.True(t, strings.HasPrefix(moreSpecific, ns[i])) + moreSpecific = ns[i] + } + } + } +} + +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.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.ParseNormalizedNamed("busybox:notlatest@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") + require.NoError(t, err) + _, ok := parsed.(reference.Canonical) + require.True(t, ok) + _, 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 0000000000..53a88de826 --- /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/normalize_test.go b/vendor/github.com/containers/image/docker/reference/normalize_test.go new file mode 100644 index 0000000000..064ee749c0 --- /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_test.go b/vendor/github.com/containers/image/docker/reference/reference_test.go new file mode 100644 index 0000000000..16b871f987 --- /dev/null +++ b/vendor/github.com/containers/image/docker/reference/reference_test.go @@ -0,0 +1,659 @@ +package reference + +import ( + _ "crypto/sha256" + _ "crypto/sha512" + "encoding/json" + "strconv" + "strings" + "testing" + + "github.com/opencontainers/go-digest" +) + +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() + } + + 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 { + failf("no error parsing name. expected: %s", testcase.err) + } + } +} + +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 { + 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) + } + } +} + +type serializationType struct { + Description string + Field Field +} + +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 _, 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 { + failf("error marshalling: %v", err) + } + t := serializationType{} + + 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) + } + + 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 TestWithTag(t *testing.T) { + testcases := []struct { + name string + digest digest.Digest + tag string + combined string + }{ + { + name: "test.com/foo", + tag: "tag", + combined: "test.com/foo:tag", + }, + { + name: "foo", + tag: "tag2", + combined: "foo:tag2", + }, + { + name: "test.com:8000/foo", + tag: "tag4", + combined: "test.com:8000/foo:tag4", + }, + { + name: "test.com:8000/foo", + tag: "TAG5", + combined: "test.com:8000/foo:TAG5", + }, + { + name: "test.com:8000/foo", + digest: "sha256:1234567890098765432112345667890098765", + tag: "TAG5", + combined: "test.com:8000/foo:TAG5@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.digest != "" { + canonical, err := WithDigest(named, testcase.digest) + if err != nil { + failf("error adding digest") + } + 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 { + failf("error adding tag") + } + named = tagged + } + 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 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, + }, + } + for _, testcase := range testcases { + failf := func(format string, v ...interface{}) { + t.Logf(strconv.Quote(testcase.input)+": "+format, v...) + t.Fail() + } + + 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_test.go b/vendor/github.com/containers/image/docker/reference/regexp_test.go new file mode 100644 index 0000000000..c21263992f --- /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/docker/wwwauthenticate_test.go b/vendor/github.com/containers/image/docker/wwwauthenticate_test.go new file mode 100644 index 0000000000..d11f6fbc96 --- /dev/null +++ b/vendor/github.com/containers/image/docker/wwwauthenticate_test.go @@ -0,0 +1,45 @@ +package docker + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// This is just a smoke test for the common expected header formats, +// by no means comprehensive. +func TestParseValueAndParams(t *testing.T) { + for _, c := range []struct { + input string + scope string + params map[string]string + }{ + { + `Bearer realm="https://auth.docker.io/token",service="registry.docker.io",scope="repository:library/busybox:pull"`, + "bearer", + map[string]string{ + "realm": "https://auth.docker.io/token", + "service": "registry.docker.io", + "scope": "repository:library/busybox:pull", + }, + }, + { + `Bearer realm="https://auth.docker.io/token",service="registry.docker.io",scope="repository:library/busybox:pull,push"`, + "bearer", + map[string]string{ + "realm": "https://auth.docker.io/token", + "service": "registry.docker.io", + "scope": "repository:library/busybox:pull,push", + }, + }, + { + `Bearer realm="http://127.0.0.1:5000/openshift/token"`, + "bearer", + map[string]string{"realm": "http://127.0.0.1:5000/openshift/token"}, + }, + } { + scope, params := parseValueAndParams(c.input) + assert.Equal(t, c.scope, scope, c.input) + assert.Equal(t, c.params, params, c.input) + } +} diff --git a/vendor/github.com/containers/image/docs/policy.json.md b/vendor/github.com/containers/image/docs/policy.json.md new file mode 100644 index 0000000000..2984d38871 --- /dev/null +++ b/vendor/github.com/containers/image/docs/policy.json.md @@ -0,0 +1,267 @@ +% POLICY.JSON(5) policy.json Man Page +% Miloslav Trmač +% September 2016 + +# Signature verification policy file format + +Signature verification policy files are used to specify policy, e.g. trusted keys, +applicable when deciding whether to accept an image, or individual signatures of that image, as valid. + +The default policy is stored (unless overridden at compile-time) at `/etc/containers/policy.json`; +applications performing verification may allow using a different policy instead. + +## Overall structure + +The signature verification policy file, usually called `policy.json`, +uses a JSON format. Unlike some other JSON files, its parsing is fairly strict: +unrecognized, duplicated or otherwise invalid fields cause the entire file, +and usually the entire operation, to be rejected. + +The purpose of the policy file is to define a set of *policy requirements* for a container image, +usually depending on its location (where it is being pulled from) or otherwise defined identity. + +Policy requirements can be defined for: + +- An individual *scope* in a *transport*. + The *transport* values are the same as the transport prefixes when pushing/pulling images (e.g. `docker:`, `atomic:`), + and *scope* values are defined by each transport; see below for more details. + + Usually, a scope can be defined to match a single image, and various prefixes of + such a most specific scope define namespaces of matching images. +- A default policy for a single transport, expressed using an empty string as a scope +- A global default policy. + +If multiple policy requirements match a given image, only the requirements from the most specific match apply, +the more general policy requirements definitions are ignored. + +This is expressed in JSON using the top-level syntax +```js +{ + "default": [/* policy requirements: global default */] + "transports": { + transport_name: { + "": [/* policy requirements: default for transport $transport_name */], + scope_1: [/* policy requirements: default for $scope_1 in $transport_name */], + scope_2: [/*…*/] + /*…*/ + }, + transport_name_2: {/*…*/} + /*…*/ + } +} +``` + +The global `default` set of policy requirements is mandatory; all of the other fields +(`transports` itself, any specific transport, the transport-specific default, etc.) are optional. + + +## Supported transports and their scopes + +### `atomic:` + +The `atomic:` transport refers to images in an Atomic Registry. + +Supported scopes use the form _hostname_[`:`_port_][`/`_namespace_[`/`_imagestream_ [`:`_tag_]]], +i.e. either specifying a complete name of a tagged image, or prefix denoting +a host/namespace/image stream. + +*Note:* The _hostname_ and _port_ refer to the Docker registry host and port (the one used +e.g. for `docker pull`), _not_ to the OpenShift API host and port. + +### `dir:` + +The `dir:` transport refers to images stored in local directories. + +Supported scopes are paths of directories (either containing a single image or +subdirectories possibly containing images). + +*Note:* The paths must be absolute and contain no symlinks. Paths violating these requirements may be silently ignored. + +The top-level scope `"/"` is forbidden; use the transport default scope `""`, +for consistency with other transports. + +### `docker:` + +The `docker:` transport refers to images in a registry implementing the "Docker Registry HTTP API V2". + +Scopes matching individual images are named Docker references *in the fully expanded form*, either +using a tag or digest. For example, `docker.io/library/busybox:latest` (*not* `busybox:latest`). + +More general scopes are prefixes of individual-image scopes, and specify a repository (by omitting the tag or digest), +a repository namespace, or a registry host (by only specifying the host name). + +### `oci:` + +The `oci:` transport refers to images in directories compliant with "Open Container Image Layout Specification". + +Supported scopes use the form _directory_`:`_tag_, and _directory_ referring to +a directory containing one or more tags, or any of the parent directories. + +*Note:* See `dir:` above for semantics and restrictions on the directory paths, they apply to `oci:` equivalently. + +## Policy Requirements + +Using the mechanisms above, a set of policy requirements is looked up. The policy requirements +are represented as a JSON array of individual requirement objects. For an image to be accepted, +*all* of the requirements must be satisfied simulatenously. + +The policy requirements can also be used to decide whether an individual signature is accepted (= is signed by a recognized key of a known author); +in that case some requirements may apply only to some signatures, but each signature must be accepted by *at least one* requirement object. + +The following requirement objects are supported: + +### `insecureAcceptAnything` + +A simple requirement with the following syntax + +```json +{"type":"insecureAcceptAnything"} +``` + +This requirement accepts any image (but note that other requirements in the array still apply). + +When deciding to accept an individual signature, this requirement does not have any effect; it does *not* cause the signature to be accepted, though. + +This is useful primarily for policy scopes where no signature verification is required; +because the array of policy requirements must not be empty, this requirement is used +to represent the lack of requirements explicitly. + +### `reject` + +A simple requirement with the following syntax: + +```json +{"type":"reject"} +``` + +This requirement rejects every image, and every signature. + +### `signedBy` + +This requirement requires an image to be signed with an expected identity, or accepts a signature if it is using an expected identity and key. + +```js +{ + "type": "signedBy", + "keyType": "GPGKeys", /* The only currently supported value */ + "keyPath": "/path/to/local/keyring/file", + "keyData": "base64-encoded-keyring-data", + "signedIdentity": identity_requirement +} +``` + + +Exactly one of `keyPath` and `keyData` must be present, containing a GPG keyring of one or more public keys. Only signatures made by these keys are accepted. + +The `signedIdentity` field, a JSON object, specifies what image identity the signature claims about the image. +One of the following alternatives are supported: + +- The identity in the signature must exactly match the image identity. Note that with this, referencing an image by digest (with a signature claiming a _repository_`:`_tag_ identity) will fail. + + ```json + {"type":"matchExact"} + ``` +- If the image identity carries a tag, the identity in the signature must exactly match; + if the image identity uses a digest reference, the identity in the signature must be in the same repository as the image identity (using any tag). + + (Note that with images identified using digest references, the digest from the reference is validated even before signature verification starts.) + + ```json + {"type":"matchRepoDigestOrExact"} + ``` +- The identity in the signature must be in the same repository as the image identity. This is useful e.g. to pull an image using the `:latest` tag when the image is signed with a tag specifing an exact image version. + + ```json + {"type":"matchRepository"} + ``` +- The identity in the signature must exactly match a specified identity. + This is useful e.g. when locally mirroring images signed using their public identity. + + ```js + { + "type": "exactReference", + "dockerReference": docker_reference_value + } + ``` +- The identity in the signature must be in the same repository as a specified identity. + This combines the properties of `matchRepository` and `exactReference`. + + ```js + { + "type": "exactRepository", + "dockerRepository": docker_repository_value + } + ``` + +If the `signedIdentity` field is missing, it is treated as `matchRepoDigestOrExact`. + +*Note*: `matchExact`, `matchRepoDigestOrExact` and `matchRepository` can be only used if a Docker-like image identity is +provided by the transport. In particular, the `dir:` and `oci:` transports can be only +used with `exactReference` or `exactRepository`. + + + +## Examples + +It is *strongly* recommended to set the `default` policy to `reject`, and then +selectively allow individual transports and scopes as desired. + +### A reasonably locked-down system + +(Note that the `/*`…`*/` comments are not valid in JSON, and must not be used in real policies.) + +```js +{ + "default": [{"type": "reject"}], /* Reject anything not explicitly allowed */ + "transports": { + "docker": { + /* Allow installing images from a specific repository namespace, without cryptographic verification. + This namespace includes images like openshift/hello-openshift and openshift/origin. */ + "docker.io/openshift": [{"type": "insecureAcceptAnything"}], + /* Similarly, allow installing the “official” busybox images. Note how the fully expanded + form, with the explicit /library/, must be used. */ + "docker.io/library/busybox": [{"type": "insecureAcceptAnything"}] + /* Other docker: images use the global default policy and are rejected */ + }, + "dir": { + "": [{"type": "insecureAcceptAnything"}] /* Allow any images originating in local directories */ + }, + "atomic": { + /* The common case: using a known key for a repository or set of repositories */ + "hostname:5000/myns/official": [ + { + "type": "signedBy", + "keyType": "GPGKeys", + "keyPath": "/path/to/official-pubkey.gpg" + } + ], + /* A more complex example, for a repository which contains a mirror of a third-party product, + which must be signed-off by local IT */ + "hostname:5000/vendor/product": [ + { /* Require the image to be signed by the original vendor, using the vendor's repository location. */ + "type": "signedBy", + "keyType": "GPGKeys", + "keyPath": "/path/to/vendor-pubkey.gpg", + "signedIdentity": { + "type": "exactRepository", + "dockerRepository": "vendor-hostname/product/repository" + } + }, + { /* Require the image to _also_ be signed by a local reviewer. */ + "type": "signedBy", + "keyType": "GPGKeys", + "keyPath": "/path/to/reviewer-pubkey.gpg" + } + ] + } + } +} +``` + +### Completely disable security, allow all images, do not trust any signatures + +```json +{ + "default": [{"type": "insecureAcceptAnything"}] +} +``` diff --git a/vendor/github.com/containers/image/docs/registries.d.md b/vendor/github.com/containers/image/docs/registries.d.md new file mode 100644 index 0000000000..424ecf8a40 --- /dev/null +++ b/vendor/github.com/containers/image/docs/registries.d.md @@ -0,0 +1,124 @@ +% REGISTRIES.D(5) Registries.d Man Page +% Miloslav Trmač +% August 2016 +# Registries Configuration Directory + +The registries configuration directory contains configuration for various registries +(servers storing remote container images), and for content stored in them, +so that the configuration does not have to be provided in command-line options over and over for every command, +and so that it can be shared by all users of containers/image. + +By default (unless overridden at compile-time), the registries configuration directory is `/etc/containers/registries.d`; +applications may allow using a different directory instead. + +## Directory Structure + +The directory may contain any number of files with the extension `.yaml`, +each using the YAML format. Other than the mandatory extension, names of the files +don’t matter. + +The contents of these files are merged together; to have a well-defined and easy to understand +behavior, there can be only one configuration section describing a single namespace within a registry +(in particular there can be at most one one `default-docker` section across all files, +and there can be at most one instance of any key under the the `docker` section; +these sections are documented later). + +Thus, it is forbidden to have two conflicting configurations for a single registry or scope, +and it is also forbidden to split a configuration for a single registry or scope across +more than one file (even if they are not semantically in conflict). + +## Registries, Scopes and Search Order + +Each YAML file must contain a “YAML mapping” (key-value pairs). Two top-level keys are defined: + +- `default-docker` is the _configuration section_ (as documented below) + for registries implementing "Docker Registry HTTP API V2". + + This key is optional. + +- `docker` is a mapping, using individual registries implementing "Docker Registry HTTP API V2", + or namespaces and individual images within these registries, as keys; + the value assigned to any such key is a _configuration section_. + + This key is optional. + + Scopes matching individual images are named Docker references *in the fully expanded form*, either + using a tag or digest. For example, `docker.io/library/busybox:latest` (*not* `busybox:latest`). + + More general scopes are prefixes of individual-image scopes, and specify a repository (by omitting the tag or digest), + a repository namespace, or a registry host (and a port if it differs from the default). + + Note that if a registry is accessed using a hostname+port configuration, the port-less hostname + is _not_ used as parent scope. + +When searching for a configuration to apply for an individual container image, only +the configuration for the most-precisely matching scope is used; configuration using +more general scopes is ignored. For example, if _any_ configuration exists for +`docker.io/library/busybox`, the configuration for `docker.io` is ignored +(even if some element of the configuration is defined for `docker.io` and not for `docker.io/library/busybox`). + +## Individual Configuration Sections + +A single configuration section is selected for a container image using the process +described above. The configuration section is a YAML mapping, with the following keys: + +- `sigstore-staging` defines an URL of of the signature storage, used for editing it (adding or deleting signatures). + + This key is optional; if it is missing, `sigstore` below is used. + +- `sigstore` defines an URL of the signature storage. + This URL is used for reading existing signatures, + and if `sigstore-staging` does not exist, also for adding or removing them. + + This key is optional; if it is missing, no signature storage is defined (no signatures + are download along with images, adding new signatures is possible only if `sigstore-staging` is defined). + +## Examples + +### Using Containers from Various Origins + +The following demonstrates how to to consume and run images from various registries and namespaces: + +```yaml +docker: + registry.database-supplier.com: + sigstore: https://sigstore.database-supplier.com + distribution.great-middleware.org: + sigstore: https://security-team.great-middleware.org/sigstore + docker.io/web-framework: + sigstore: https://sigstore.web-framework.io:8080 +``` + +### Developing and Signing Containers, Staging Signatures + +For developers in `example.com`: + +- Consume most container images using the public servers also used by clients. +- Use a separate sigure storage for an container images in a namespace corresponding to the developers' department, with a staging storage used before publishing signatures. +- Craft an individual exception for a single branch a specific developer is working on locally. + +```yaml +docker: + registry.example.com: + sigstore: https://registry-sigstore.example.com + registry.example.com/mydepartment: + sigstore: https://sigstore.mydepartment.example.com + sigstore-staging: file:///mnt/mydepartment/sigstore-staging + registry.example.com/mydepartment/myproject:mybranch: + sigstore: http://localhost:4242/sigstore + sigstore-staging: file:///home/useraccount/webroot/sigstore +``` + +### A Global Default + +If a company publishes its products using a different domain, and different registry hostname for each of them, it is still possible to use a single signature storage server +without listing each domain individually. This is expected to rarely happen, usually only for staging new signatures. + +```yaml +default-docker: + sigstore-staging: file:///mnt/company/common-sigstore-staging +``` + +# AUTHORS + +Miloslav Trmač diff --git a/vendor/github.com/containers/image/image/docker_schema2_test.go b/vendor/github.com/containers/image/image/docker_schema2_test.go new file mode 100644 index 0000000000..473e51e246 --- /dev/null +++ b/vendor/github.com/containers/image/image/docker_schema2_test.go @@ -0,0 +1,498 @@ +package image + +import ( + "bytes" + "encoding/json" + "io" + "io/ioutil" + "path/filepath" + "testing" + "time" + + "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" +) + +// unusedImageSource is used when we don't expect the ImageSource to be used in our tests. +type unusedImageSource struct{} + +func (f unusedImageSource) Reference() types.ImageReference { + panic("Unexpected call to a mock function") +} +func (f unusedImageSource) Close() { + panic("Unexpected call to a mock function") +} +func (f unusedImageSource) GetManifest() ([]byte, string, error) { + panic("Unexpected call to a mock function") +} +func (f unusedImageSource) GetTargetManifest(digest digest.Digest) ([]byte, string, error) { + panic("Unexpected call to a mock function") +} +func (f unusedImageSource) GetBlob(info types.BlobInfo) (io.ReadCloser, int64, error) { + panic("Unexpected call to a mock function") +} +func (f unusedImageSource) GetSignatures() ([][]byte, error) { + panic("Unexpected call to a mock function") +} + +func manifestSchema2FromFixture(t *testing.T, src types.ImageSource, fixture string) genericManifest { + manifest, err := ioutil.ReadFile(filepath.Join("fixtures", fixture)) + require.NoError(t, err) + + m, err := manifestSchema2FromManifest(src, manifest) + require.NoError(t, err) + return m +} + +func manifestSchema2FromComponentsLikeFixture(configBlob []byte) genericManifest { + return manifestSchema2FromComponents(descriptor{ + MediaType: "application/octet-stream", + Size: 5940, + Digest: "sha256:9ca4bda0a6b3727a6ffcc43e981cad0f24e2ec79d338f6ba325b4dfd0756fb8f", + }, nil, configBlob, []descriptor{ + { + MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip", + Digest: "sha256:6a5a5368e0c2d3e5909184fa28ddfd56072e7ff3ee9a945876f7eee5896ef5bb", + Size: 51354364, + }, + { + MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip", + Digest: "sha256:1bbf5d58d24c47512e234a5623474acf65ae00d4d1414272a893204f44cc680c", + Size: 150, + }, + { + MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip", + Digest: "sha256:8f5dc8a4b12c307ac84de90cdd9a7f3915d1be04c9388868ca118831099c67a9", + Size: 11739507, + }, + { + MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip", + Digest: "sha256:bbd6b22eb11afce63cc76f6bc41042d99f10d6024c96b655dafba930b8d25909", + Size: 8841833, + }, + { + MediaType: "application/vnd.docker.image.rootfs.diff.tar.gzip", + Digest: "sha256:960e52ecf8200cbd84e70eb2ad8678f4367e50d14357021872c10fa3fc5935fa", + Size: 291, + }, + }) +} + +func TestManifestSchema2FromManifest(t *testing.T) { + // This just tests that the JSON can be loaded; we test that the parsed + // values are correctly returned in tests for the individual getter methods. + _ = manifestSchema2FromFixture(t, unusedImageSource{}, "schema2.json") + + _, err := manifestSchema2FromManifest(nil, []byte{}) + assert.Error(t, err) +} + +func TestManifestSchema2FromComponents(t *testing.T) { + // This just smoke-tests that the manifest can be created; we test that the parsed + // values are correctly returned in tests for the individual getter methods. + _ = manifestSchema2FromComponentsLikeFixture(nil) +} + +func TestManifestSchema2Serialize(t *testing.T) { + for _, m := range []genericManifest{ + manifestSchema2FromFixture(t, unusedImageSource{}, "schema2.json"), + manifestSchema2FromComponentsLikeFixture(nil), + } { + serialized, err := m.serialize() + require.NoError(t, err) + var contents map[string]interface{} + err = json.Unmarshal(serialized, &contents) + require.NoError(t, err) + + original, err := ioutil.ReadFile("fixtures/schema2.json") + require.NoError(t, err) + var originalContents map[string]interface{} + err = json.Unmarshal(original, &originalContents) + require.NoError(t, err) + + // We would ideally like to compare “serialized” with some transformation of + // “original”, but the ordering of fields in JSON maps is undefined, so this is + // easier. + assert.Equal(t, originalContents, contents) + } +} + +func TestManifestSchema2ManifestMIMEType(t *testing.T) { + for _, m := range []genericManifest{ + manifestSchema2FromFixture(t, unusedImageSource{}, "schema2.json"), + manifestSchema2FromComponentsLikeFixture(nil), + } { + assert.Equal(t, manifest.DockerV2Schema2MediaType, m.manifestMIMEType()) + } +} + +func TestManifestSchema2ConfigInfo(t *testing.T) { + for _, m := range []genericManifest{ + manifestSchema2FromFixture(t, unusedImageSource{}, "schema2.json"), + manifestSchema2FromComponentsLikeFixture(nil), + } { + assert.Equal(t, types.BlobInfo{ + Size: 5940, + Digest: "sha256:9ca4bda0a6b3727a6ffcc43e981cad0f24e2ec79d338f6ba325b4dfd0756fb8f", + }, m.ConfigInfo()) + } +} + +// configBlobImageSource allows testing various GetBlob behaviors in .ConfigBlob() +type configBlobImageSource struct { + unusedImageSource // We inherit almost all of the methods, which just panic() + f func(digest digest.Digest) (io.ReadCloser, int64, error) +} + +func (f configBlobImageSource) GetBlob(info types.BlobInfo) (io.ReadCloser, int64, error) { + if info.Digest.String() != "sha256:9ca4bda0a6b3727a6ffcc43e981cad0f24e2ec79d338f6ba325b4dfd0756fb8f" { + panic("Unexpected digest in GetBlob") + } + return f.f(info.Digest) +} + +func TestManifestSchema2ConfigBlob(t *testing.T) { + realConfigJSON, err := ioutil.ReadFile("fixtures/schema2-config.json") + require.NoError(t, err) + + for _, c := range []struct { + cbISfn func(digest digest.Digest) (io.ReadCloser, int64, error) + blob []byte + }{ + // Success + {func(digest digest.Digest) (io.ReadCloser, int64, error) { + return ioutil.NopCloser(bytes.NewReader(realConfigJSON)), int64(len(realConfigJSON)), nil + }, realConfigJSON}, + // Various kinds of failures + {nil, nil}, + {func(digest digest.Digest) (io.ReadCloser, int64, error) { + return nil, -1, errors.New("Error returned from GetBlob") + }, nil}, + {func(digest digest.Digest) (io.ReadCloser, int64, error) { + reader, writer := io.Pipe() + writer.CloseWithError(errors.New("Expected error reading input in ConfigBlob")) + return reader, 1, nil + }, nil}, + {func(digest digest.Digest) (io.ReadCloser, int64, error) { + nonmatchingJSON := []byte("This does not match ConfigDescriptor.Digest") + return ioutil.NopCloser(bytes.NewReader(nonmatchingJSON)), int64(len(nonmatchingJSON)), nil + }, nil}, + } { + var src types.ImageSource + if c.cbISfn != nil { + src = configBlobImageSource{unusedImageSource{}, c.cbISfn} + } else { + src = nil + } + m := manifestSchema2FromFixture(t, src, "schema2.json") + blob, err := m.ConfigBlob() + if c.blob != nil { + assert.NoError(t, err) + assert.Equal(t, c.blob, blob) + } else { + assert.Error(t, err) + } + } + + // Generally conficBlob should match ConfigInfo; we don’t quite need it to, and this will + // guarantee that the returned object is returning the original contents instead + // of reading an object from elsewhere. + configBlob := []byte("config blob which does not match ConfigInfo") + // This just tests that the manifest can be created; we test that the parsed + // values are correctly returned in tests for the individual getter methods. + m := manifestSchema2FromComponentsLikeFixture(configBlob) + cb, err := m.ConfigBlob() + require.NoError(t, err) + assert.Equal(t, configBlob, cb) +} + +func TestManifestSchema2LayerInfo(t *testing.T) { + for _, m := range []genericManifest{ + manifestSchema2FromFixture(t, unusedImageSource{}, "schema2.json"), + manifestSchema2FromComponentsLikeFixture(nil), + } { + assert.Equal(t, []types.BlobInfo{ + { + Digest: "sha256:6a5a5368e0c2d3e5909184fa28ddfd56072e7ff3ee9a945876f7eee5896ef5bb", + Size: 51354364, + }, + { + Digest: "sha256:1bbf5d58d24c47512e234a5623474acf65ae00d4d1414272a893204f44cc680c", + Size: 150, + }, + { + Digest: "sha256:8f5dc8a4b12c307ac84de90cdd9a7f3915d1be04c9388868ca118831099c67a9", + Size: 11739507, + }, + { + Digest: "sha256:bbd6b22eb11afce63cc76f6bc41042d99f10d6024c96b655dafba930b8d25909", + Size: 8841833, + }, + { + Digest: "sha256:960e52ecf8200cbd84e70eb2ad8678f4367e50d14357021872c10fa3fc5935fa", + Size: 291, + }, + }, m.LayerInfos()) + } +} + +func TestManifestSchema2ImageInspectInfo(t *testing.T) { + configJSON, err := ioutil.ReadFile("fixtures/schema2-config.json") + require.NoError(t, err) + + m := manifestSchema2FromComponentsLikeFixture(configJSON) + ii, err := m.imageInspectInfo() + require.NoError(t, err) + assert.Equal(t, types.ImageInspectInfo{ + Tag: "", + Created: time.Date(2016, 9, 23, 23, 20, 45, 789764590, time.UTC), + DockerVersion: "1.12.1", + Labels: map[string]string{}, + Architecture: "amd64", + Os: "linux", + Layers: nil, + }, *ii) + + // nil configBlob will trigger an error in m.ConfigBlob() + m = manifestSchema2FromComponentsLikeFixture(nil) + _, err = m.imageInspectInfo() + assert.Error(t, err) + + m = manifestSchema2FromComponentsLikeFixture([]byte("invalid JSON")) + _, err = m.imageInspectInfo() + assert.Error(t, err) +} + +func TestManifestSchema2UpdatedImageNeedsLayerDiffIDs(t *testing.T) { + for _, m := range []genericManifest{ + manifestSchema2FromFixture(t, unusedImageSource{}, "schema2.json"), + manifestSchema2FromComponentsLikeFixture(nil), + } { + assert.False(t, m.UpdatedImageNeedsLayerDiffIDs(types.ManifestUpdateOptions{ + ManifestMIMEType: manifest.DockerV2Schema1SignedMediaType, + })) + } +} + +// schema2ImageSource is plausible enough for schema conversions in manifestSchema2.UpdatedImage() to work. +type schema2ImageSource struct { + configBlobImageSource + ref reference.Named +} + +func (s2is *schema2ImageSource) Reference() types.ImageReference { + return refImageReferenceMock{s2is.ref} +} + +// refImageReferenceMock is a mock of types.ImageReference which returns itself in DockerReference. +type refImageReferenceMock struct{ reference.Named } + +func (ref refImageReferenceMock) Transport() types.ImageTransport { + panic("unexpected call to a mock function") +} +func (ref refImageReferenceMock) StringWithinTransport() string { + panic("unexpected call to a mock function") +} +func (ref refImageReferenceMock) DockerReference() reference.Named { + return ref.Named +} +func (ref refImageReferenceMock) PolicyConfigurationIdentity() string { + panic("unexpected call to a mock function") +} +func (ref refImageReferenceMock) PolicyConfigurationNamespaces() []string { + panic("unexpected call to a mock function") +} +func (ref refImageReferenceMock) NewImage(ctx *types.SystemContext) (types.Image, error) { + panic("unexpected call to a mock function") +} +func (ref refImageReferenceMock) NewImageSource(ctx *types.SystemContext, requestedManifestMIMETypes []string) (types.ImageSource, error) { + panic("unexpected call to a mock function") +} +func (ref refImageReferenceMock) NewImageDestination(ctx *types.SystemContext) (types.ImageDestination, error) { + panic("unexpected call to a mock function") +} +func (ref refImageReferenceMock) DeleteImage(ctx *types.SystemContext) error { + panic("unexpected call to a mock function") +} + +func newSchema2ImageSource(t *testing.T, dockerRef string) *schema2ImageSource { + realConfigJSON, err := ioutil.ReadFile("fixtures/schema2-config.json") + require.NoError(t, err) + + ref, err := reference.ParseNormalizedNamed(dockerRef) + require.NoError(t, err) + + return &schema2ImageSource{ + configBlobImageSource: configBlobImageSource{ + f: func(digest digest.Digest) (io.ReadCloser, int64, error) { + return ioutil.NopCloser(bytes.NewReader(realConfigJSON)), int64(len(realConfigJSON)), nil + }, + }, + ref: ref, + } +} + +type memoryImageDest struct { + ref reference.Named + storedBlobs map[digest.Digest][]byte +} + +func (d *memoryImageDest) Reference() types.ImageReference { + return refImageReferenceMock{d.ref} +} +func (d *memoryImageDest) Close() { + panic("Unexpected call to a mock function") +} +func (d *memoryImageDest) SupportedManifestMIMETypes() []string { + panic("Unexpected call to a mock function") +} +func (d *memoryImageDest) SupportsSignatures() error { + panic("Unexpected call to a mock function") +} +func (d *memoryImageDest) ShouldCompressLayers() bool { + panic("Unexpected call to a mock function") +} +func (d *memoryImageDest) AcceptsForeignLayerURLs() bool { + panic("Unexpected call to a mock function") +} +func (d *memoryImageDest) PutBlob(stream io.Reader, inputInfo types.BlobInfo) (types.BlobInfo, error) { + if d.storedBlobs == nil { + d.storedBlobs = make(map[digest.Digest][]byte) + } + if inputInfo.Digest.String() == "" { + panic("inputInfo.Digest unexpectedly empty") + } + contents, err := ioutil.ReadAll(stream) + if err != nil { + return types.BlobInfo{}, err + } + d.storedBlobs[inputInfo.Digest] = contents + return types.BlobInfo{Digest: inputInfo.Digest, Size: int64(len(contents))}, nil +} +func (d *memoryImageDest) HasBlob(inputInfo types.BlobInfo) (bool, int64, error) { + panic("Unexpected call to a mock function") +} +func (d *memoryImageDest) ReapplyBlob(inputInfo types.BlobInfo) (types.BlobInfo, error) { + panic("Unexpected call to a mock function") +} +func (d *memoryImageDest) PutManifest([]byte) error { + panic("Unexpected call to a mock function") +} +func (d *memoryImageDest) PutSignatures(signatures [][]byte) error { + panic("Unexpected call to a mock function") +} +func (d *memoryImageDest) Commit() error { + panic("Unexpected call to a mock function") +} + +func TestManifestSchema2UpdatedImage(t *testing.T) { + originalSrc := newSchema2ImageSource(t, "httpd:latest") + original := manifestSchema2FromFixture(t, originalSrc, "schema2.json") + + // LayerInfos: + layerInfos := append(original.LayerInfos()[1:], original.LayerInfos()[0]) + res, err := original.UpdatedImage(types.ManifestUpdateOptions{ + LayerInfos: layerInfos, + }) + require.NoError(t, err) + assert.Equal(t, layerInfos, res.LayerInfos()) + _, err = original.UpdatedImage(types.ManifestUpdateOptions{ + LayerInfos: append(layerInfos, layerInfos[0]), + }) + assert.Error(t, err) + + // ManifestMIMEType: + // Only smoke-test the valid conversions, detailed tests are below. (This also verifies that “original” is not affected.) + for _, mime := range []string{ + manifest.DockerV2Schema1MediaType, + manifest.DockerV2Schema1SignedMediaType, + } { + _, err = original.UpdatedImage(types.ManifestUpdateOptions{ + ManifestMIMEType: mime, + InformationOnly: types.ManifestUpdateInformation{ + Destination: &memoryImageDest{ref: originalSrc.ref}, + }, + }) + assert.NoError(t, err, mime) + } + for _, mime := range []string{ + manifest.DockerV2Schema2MediaType, // This indicates a confused caller, not a no-op + "this is invalid", + } { + _, err = original.UpdatedImage(types.ManifestUpdateOptions{ + ManifestMIMEType: mime, + }) + assert.Error(t, err, mime) + } + + // m hasn’t been changed: + m2 := manifestSchema2FromFixture(t, originalSrc, "schema2.json") + typedOriginal, ok := original.(*manifestSchema2) + require.True(t, ok) + typedM2, ok := m2.(*manifestSchema2) + require.True(t, ok) + assert.Equal(t, *typedM2, *typedOriginal) +} + +func TestConvertToManifestOCI(t *testing.T) { + originalSrc := newSchema2ImageSource(t, "httpd-copy:latest") + original := manifestSchema2FromFixture(t, originalSrc, "schema2.json") + res, err := original.UpdatedImage(types.ManifestUpdateOptions{ + ManifestMIMEType: imgspecv1.MediaTypeImageManifest, + }) + require.NoError(t, err) + + convertedJSON, mt, err := res.Manifest() + require.NoError(t, err) + assert.Equal(t, imgspecv1.MediaTypeImageManifest, mt) + + byHandJSON, err := ioutil.ReadFile("fixtures/schema2-to-oci1.json") + require.NoError(t, err) + var converted, byHand map[string]interface{} + err = json.Unmarshal(byHandJSON, &byHand) + require.NoError(t, err) + err = json.Unmarshal(convertedJSON, &converted) + require.NoError(t, err) + assert.Equal(t, byHand, converted) +} + +func TestConvertToManifestSchema1(t *testing.T) { + originalSrc := newSchema2ImageSource(t, "httpd-copy:latest") + original := manifestSchema2FromFixture(t, originalSrc, "schema2.json") + memoryDest := &memoryImageDest{ref: originalSrc.ref} + res, err := original.UpdatedImage(types.ManifestUpdateOptions{ + ManifestMIMEType: manifest.DockerV2Schema1SignedMediaType, + InformationOnly: types.ManifestUpdateInformation{ + Destination: memoryDest, + }, + }) + require.NoError(t, err) + + convertedJSON, mt, err := res.Manifest() + require.NoError(t, err) + assert.Equal(t, manifest.DockerV2Schema1SignedMediaType, mt) + + // byDockerJSON is the result of asking the Docker Hub for a schema1 manifest, + // except that we have replaced "name" to verify that the ref from + // memoryDest, not from originalSrc, is used. + byDockerJSON, err := ioutil.ReadFile("fixtures/schema2-to-schema1-by-docker.json") + require.NoError(t, err) + var converted, byDocker map[string]interface{} + err = json.Unmarshal(byDockerJSON, &byDocker) + require.NoError(t, err) + err = json.Unmarshal(convertedJSON, &converted) + require.NoError(t, err) + delete(byDocker, "signatures") + delete(converted, "signatures") + assert.Equal(t, byDocker, converted) + + assert.Equal(t, gzippedEmptyLayer, memoryDest.storedBlobs[gzippedEmptyLayerDigest]) + + // FIXME? Test also the various failure cases, if only to see that we don't crash? +} diff --git a/vendor/github.com/containers/image/image/fixtures/oci1-config.json b/vendor/github.com/containers/image/image/fixtures/oci1-config.json new file mode 100644 index 0000000000..f49230ea77 --- /dev/null +++ b/vendor/github.com/containers/image/image/fixtures/oci1-config.json @@ -0,0 +1 @@ +{"architecture":"amd64","config":{"Hostname":"383850eeb47b","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"ExposedPorts":{"80/tcp":{}},"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/apache2/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","HTTPD_PREFIX=/usr/local/apache2","HTTPD_VERSION=2.4.23","HTTPD_SHA1=5101be34ac4a509b245adb70a56690a84fcc4e7f","HTTPD_BZ2_URL=https://www.apache.org/dyn/closer.cgi?action=download\u0026filename=httpd/httpd-2.4.23.tar.bz2","HTTPD_ASC_URL=https://www.apache.org/dist/httpd/httpd-2.4.23.tar.bz2.asc"],"Cmd":["httpd-foreground"],"ArgsEscaped":true,"Image":"sha256:4f83530449c67c1ed8fca72583c5b92fdf446010990028c362a381e55dd84afd","Volumes":null,"WorkingDir":"/usr/local/apache2","Entrypoint":null,"OnBuild":[],"Labels":{}},"container":"8825acde1b009729807e4b70a65a89399dd8da8e53be9216b9aaabaff4339f69","container_config":{"Hostname":"383850eeb47b","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"ExposedPorts":{"80/tcp":{}},"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/apache2/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","HTTPD_PREFIX=/usr/local/apache2","HTTPD_VERSION=2.4.23","HTTPD_SHA1=5101be34ac4a509b245adb70a56690a84fcc4e7f","HTTPD_BZ2_URL=https://www.apache.org/dyn/closer.cgi?action=download\u0026filename=httpd/httpd-2.4.23.tar.bz2","HTTPD_ASC_URL=https://www.apache.org/dist/httpd/httpd-2.4.23.tar.bz2.asc"],"Cmd":["/bin/sh","-c","#(nop) ","CMD [\"httpd-foreground\"]"],"ArgsEscaped":true,"Image":"sha256:4f83530449c67c1ed8fca72583c5b92fdf446010990028c362a381e55dd84afd","Volumes":null,"WorkingDir":"/usr/local/apache2","Entrypoint":null,"OnBuild":[],"Labels":{}},"created":"2016-09-23T23:20:45.78976459Z","docker_version":"1.12.1","history":[{"created":"2016-09-23T18:08:50.537223822Z","created_by":"/bin/sh -c #(nop) ADD file:c6c23585ab140b0b320d4e99bc1b0eb544c9e96c24d90fec5e069a6d57d335ca in / "},{"created":"2016-09-23T18:08:51.133779867Z","created_by":"/bin/sh -c #(nop) CMD [\"/bin/bash\"]","empty_layer":true},{"created":"2016-09-23T19:16:40.725768956Z","created_by":"/bin/sh -c #(nop) ENV HTTPD_PREFIX=/usr/local/apache2","empty_layer":true},{"created":"2016-09-23T19:16:41.037788416Z","created_by":"/bin/sh -c #(nop) ENV PATH=/usr/local/apache2/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","empty_layer":true},{"created":"2016-09-23T19:16:41.990121202Z","created_by":"/bin/sh -c mkdir -p \"$HTTPD_PREFIX\" \t\u0026\u0026 chown www-data:www-data \"$HTTPD_PREFIX\""},{"created":"2016-09-23T19:16:42.339911155Z","created_by":"/bin/sh -c #(nop) WORKDIR /usr/local/apache2","empty_layer":true},{"created":"2016-09-23T19:16:54.948461741Z","created_by":"/bin/sh -c apt-get update \t\u0026\u0026 apt-get install -y --no-install-recommends \t\tlibapr1 \t\tlibaprutil1 \t\tlibaprutil1-ldap \t\tlibapr1-dev \t\tlibaprutil1-dev \t\tlibpcre++0 \t\tlibssl1.0.0 \t\u0026\u0026 rm -r /var/lib/apt/lists/*"},{"created":"2016-09-23T19:16:55.321573403Z","created_by":"/bin/sh -c #(nop) ENV HTTPD_VERSION=2.4.23","empty_layer":true},{"created":"2016-09-23T19:16:55.629947307Z","created_by":"/bin/sh -c #(nop) ENV HTTPD_SHA1=5101be34ac4a509b245adb70a56690a84fcc4e7f","empty_layer":true},{"created":"2016-09-23T23:19:03.705796801Z","created_by":"/bin/sh -c #(nop) ENV HTTPD_BZ2_URL=https://www.apache.org/dyn/closer.cgi?action=download\u0026filename=httpd/httpd-2.4.23.tar.bz2","empty_layer":true},{"created":"2016-09-23T23:19:04.009782822Z","created_by":"/bin/sh -c #(nop) ENV HTTPD_ASC_URL=https://www.apache.org/dist/httpd/httpd-2.4.23.tar.bz2.asc","empty_layer":true},{"created":"2016-09-23T23:20:44.585743332Z","created_by":"/bin/sh -c set -x \t\u0026\u0026 buildDeps=' \t\tbzip2 \t\tca-certificates \t\tgcc \t\tlibpcre++-dev \t\tlibssl-dev \t\tmake \t\twget \t' \t\u0026\u0026 apt-get update \t\u0026\u0026 apt-get install -y --no-install-recommends $buildDeps \t\u0026\u0026 rm -r /var/lib/apt/lists/* \t\t\u0026\u0026 wget -O httpd.tar.bz2 \"$HTTPD_BZ2_URL\" \t\u0026\u0026 echo \"$HTTPD_SHA1 *httpd.tar.bz2\" | sha1sum -c - \t\u0026\u0026 wget -O httpd.tar.bz2.asc \"$HTTPD_ASC_URL\" \t\u0026\u0026 export GNUPGHOME=\"$(mktemp -d)\" \t\u0026\u0026 gpg --keyserver ha.pool.sks-keyservers.net --recv-keys A93D62ECC3C8EA12DB220EC934EA76E6791485A8 \t\u0026\u0026 gpg --batch --verify httpd.tar.bz2.asc httpd.tar.bz2 \t\u0026\u0026 rm -r \"$GNUPGHOME\" httpd.tar.bz2.asc \t\t\u0026\u0026 mkdir -p src \t\u0026\u0026 tar -xvf httpd.tar.bz2 -C src --strip-components=1 \t\u0026\u0026 rm httpd.tar.bz2 \t\u0026\u0026 cd src \t\t\u0026\u0026 ./configure \t\t--prefix=\"$HTTPD_PREFIX\" \t\t--enable-mods-shared=reallyall \t\u0026\u0026 make -j\"$(nproc)\" \t\u0026\u0026 make install \t\t\u0026\u0026 cd .. \t\u0026\u0026 rm -r src \t\t\u0026\u0026 sed -ri \t\t-e 's!^(\\s*CustomLog)\\s+\\S+!\\1 /proc/self/fd/1!g' \t\t-e 's!^(\\s*ErrorLog)\\s+\\S+!\\1 /proc/self/fd/2!g' \t\t\"$HTTPD_PREFIX/conf/httpd.conf\" \t\t\u0026\u0026 apt-get purge -y --auto-remove $buildDeps"},{"created":"2016-09-23T23:20:45.127455562Z","created_by":"/bin/sh -c #(nop) COPY file:761e313354b918b6cd7ea99975a4f6b53ff5381ba689bab2984aec4dab597215 in /usr/local/bin/ "},{"created":"2016-09-23T23:20:45.453934921Z","created_by":"/bin/sh -c #(nop) EXPOSE 80/tcp","empty_layer":true},{"created":"2016-09-23T23:20:45.78976459Z","created_by":"/bin/sh -c #(nop) CMD [\"httpd-foreground\"]","empty_layer":true}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:142a601d97936307e75220c35dde0348971a9584c21e7cb42e1f7004005432ab","sha256:90fcc66ad3be9f1757f954b750deb37032f208428aa12599fcb02182b9065a9c","sha256:5a8624bb7e76d1e6829f9c64c43185e02bc07f97a2189eb048609a8914e72c56","sha256:d349ff6b3afc6a2800054768c82bfbf4289c9aa5da55c1290f802943dcd4d1e9","sha256:8c064bb1f60e84fa8cc6079b6d2e76e0423389fd6aeb7e497dfdae5e05b2b25b"]}} \ No newline at end of file diff --git a/vendor/github.com/containers/image/image/fixtures/oci1-to-schema2.json b/vendor/github.com/containers/image/image/fixtures/oci1-to-schema2.json new file mode 100644 index 0000000000..8861521ec2 --- /dev/null +++ b/vendor/github.com/containers/image/image/fixtures/oci1-to-schema2.json @@ -0,0 +1,36 @@ +{ + "schemaVersion": 2, + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "config": { + "mediaType": "application/vnd.docker.container.image.v1+json", + "size": 5940, + "digest": "sha256:9ca4bda0a6b3727a6ffcc43e981cad0f24e2ec79d338f6ba325b4dfd0756fb8f" + }, + "layers": [ + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 51354364, + "digest": "sha256:6a5a5368e0c2d3e5909184fa28ddfd56072e7ff3ee9a945876f7eee5896ef5bb" + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 150, + "digest": "sha256:1bbf5d58d24c47512e234a5623474acf65ae00d4d1414272a893204f44cc680c" + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 11739507, + "digest": "sha256:8f5dc8a4b12c307ac84de90cdd9a7f3915d1be04c9388868ca118831099c67a9" + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 8841833, + "digest": "sha256:bbd6b22eb11afce63cc76f6bc41042d99f10d6024c96b655dafba930b8d25909" + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 291, + "digest": "sha256:960e52ecf8200cbd84e70eb2ad8678f4367e50d14357021872c10fa3fc5935fa" + } + ] +} diff --git a/vendor/github.com/containers/image/image/fixtures/oci1.json b/vendor/github.com/containers/image/image/fixtures/oci1.json new file mode 100644 index 0000000000..d26561d820 --- /dev/null +++ b/vendor/github.com/containers/image/image/fixtures/oci1.json @@ -0,0 +1,35 @@ +{ + "schemaVersion": 2, + "config": { + "mediaType": "application/vnd.oci.image.config.v1+json", + "size": 5940, + "digest": "sha256:9ca4bda0a6b3727a6ffcc43e981cad0f24e2ec79d338f6ba325b4dfd0756fb8f" + }, + "layers": [ + { + "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", + "size": 51354364, + "digest": "sha256:6a5a5368e0c2d3e5909184fa28ddfd56072e7ff3ee9a945876f7eee5896ef5bb" + }, + { + "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", + "size": 150, + "digest": "sha256:1bbf5d58d24c47512e234a5623474acf65ae00d4d1414272a893204f44cc680c" + }, + { + "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", + "size": 11739507, + "digest": "sha256:8f5dc8a4b12c307ac84de90cdd9a7f3915d1be04c9388868ca118831099c67a9" + }, + { + "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", + "size": 8841833, + "digest": "sha256:bbd6b22eb11afce63cc76f6bc41042d99f10d6024c96b655dafba930b8d25909" + }, + { + "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", + "size": 291, + "digest": "sha256:960e52ecf8200cbd84e70eb2ad8678f4367e50d14357021872c10fa3fc5935fa" + } + ] +} diff --git a/vendor/github.com/containers/image/image/fixtures/schema2-config.json b/vendor/github.com/containers/image/image/fixtures/schema2-config.json new file mode 100644 index 0000000000..f49230ea77 --- /dev/null +++ b/vendor/github.com/containers/image/image/fixtures/schema2-config.json @@ -0,0 +1 @@ +{"architecture":"amd64","config":{"Hostname":"383850eeb47b","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"ExposedPorts":{"80/tcp":{}},"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/apache2/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","HTTPD_PREFIX=/usr/local/apache2","HTTPD_VERSION=2.4.23","HTTPD_SHA1=5101be34ac4a509b245adb70a56690a84fcc4e7f","HTTPD_BZ2_URL=https://www.apache.org/dyn/closer.cgi?action=download\u0026filename=httpd/httpd-2.4.23.tar.bz2","HTTPD_ASC_URL=https://www.apache.org/dist/httpd/httpd-2.4.23.tar.bz2.asc"],"Cmd":["httpd-foreground"],"ArgsEscaped":true,"Image":"sha256:4f83530449c67c1ed8fca72583c5b92fdf446010990028c362a381e55dd84afd","Volumes":null,"WorkingDir":"/usr/local/apache2","Entrypoint":null,"OnBuild":[],"Labels":{}},"container":"8825acde1b009729807e4b70a65a89399dd8da8e53be9216b9aaabaff4339f69","container_config":{"Hostname":"383850eeb47b","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"ExposedPorts":{"80/tcp":{}},"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/apache2/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","HTTPD_PREFIX=/usr/local/apache2","HTTPD_VERSION=2.4.23","HTTPD_SHA1=5101be34ac4a509b245adb70a56690a84fcc4e7f","HTTPD_BZ2_URL=https://www.apache.org/dyn/closer.cgi?action=download\u0026filename=httpd/httpd-2.4.23.tar.bz2","HTTPD_ASC_URL=https://www.apache.org/dist/httpd/httpd-2.4.23.tar.bz2.asc"],"Cmd":["/bin/sh","-c","#(nop) ","CMD [\"httpd-foreground\"]"],"ArgsEscaped":true,"Image":"sha256:4f83530449c67c1ed8fca72583c5b92fdf446010990028c362a381e55dd84afd","Volumes":null,"WorkingDir":"/usr/local/apache2","Entrypoint":null,"OnBuild":[],"Labels":{}},"created":"2016-09-23T23:20:45.78976459Z","docker_version":"1.12.1","history":[{"created":"2016-09-23T18:08:50.537223822Z","created_by":"/bin/sh -c #(nop) ADD file:c6c23585ab140b0b320d4e99bc1b0eb544c9e96c24d90fec5e069a6d57d335ca in / "},{"created":"2016-09-23T18:08:51.133779867Z","created_by":"/bin/sh -c #(nop) CMD [\"/bin/bash\"]","empty_layer":true},{"created":"2016-09-23T19:16:40.725768956Z","created_by":"/bin/sh -c #(nop) ENV HTTPD_PREFIX=/usr/local/apache2","empty_layer":true},{"created":"2016-09-23T19:16:41.037788416Z","created_by":"/bin/sh -c #(nop) ENV PATH=/usr/local/apache2/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","empty_layer":true},{"created":"2016-09-23T19:16:41.990121202Z","created_by":"/bin/sh -c mkdir -p \"$HTTPD_PREFIX\" \t\u0026\u0026 chown www-data:www-data \"$HTTPD_PREFIX\""},{"created":"2016-09-23T19:16:42.339911155Z","created_by":"/bin/sh -c #(nop) WORKDIR /usr/local/apache2","empty_layer":true},{"created":"2016-09-23T19:16:54.948461741Z","created_by":"/bin/sh -c apt-get update \t\u0026\u0026 apt-get install -y --no-install-recommends \t\tlibapr1 \t\tlibaprutil1 \t\tlibaprutil1-ldap \t\tlibapr1-dev \t\tlibaprutil1-dev \t\tlibpcre++0 \t\tlibssl1.0.0 \t\u0026\u0026 rm -r /var/lib/apt/lists/*"},{"created":"2016-09-23T19:16:55.321573403Z","created_by":"/bin/sh -c #(nop) ENV HTTPD_VERSION=2.4.23","empty_layer":true},{"created":"2016-09-23T19:16:55.629947307Z","created_by":"/bin/sh -c #(nop) ENV HTTPD_SHA1=5101be34ac4a509b245adb70a56690a84fcc4e7f","empty_layer":true},{"created":"2016-09-23T23:19:03.705796801Z","created_by":"/bin/sh -c #(nop) ENV HTTPD_BZ2_URL=https://www.apache.org/dyn/closer.cgi?action=download\u0026filename=httpd/httpd-2.4.23.tar.bz2","empty_layer":true},{"created":"2016-09-23T23:19:04.009782822Z","created_by":"/bin/sh -c #(nop) ENV HTTPD_ASC_URL=https://www.apache.org/dist/httpd/httpd-2.4.23.tar.bz2.asc","empty_layer":true},{"created":"2016-09-23T23:20:44.585743332Z","created_by":"/bin/sh -c set -x \t\u0026\u0026 buildDeps=' \t\tbzip2 \t\tca-certificates \t\tgcc \t\tlibpcre++-dev \t\tlibssl-dev \t\tmake \t\twget \t' \t\u0026\u0026 apt-get update \t\u0026\u0026 apt-get install -y --no-install-recommends $buildDeps \t\u0026\u0026 rm -r /var/lib/apt/lists/* \t\t\u0026\u0026 wget -O httpd.tar.bz2 \"$HTTPD_BZ2_URL\" \t\u0026\u0026 echo \"$HTTPD_SHA1 *httpd.tar.bz2\" | sha1sum -c - \t\u0026\u0026 wget -O httpd.tar.bz2.asc \"$HTTPD_ASC_URL\" \t\u0026\u0026 export GNUPGHOME=\"$(mktemp -d)\" \t\u0026\u0026 gpg --keyserver ha.pool.sks-keyservers.net --recv-keys A93D62ECC3C8EA12DB220EC934EA76E6791485A8 \t\u0026\u0026 gpg --batch --verify httpd.tar.bz2.asc httpd.tar.bz2 \t\u0026\u0026 rm -r \"$GNUPGHOME\" httpd.tar.bz2.asc \t\t\u0026\u0026 mkdir -p src \t\u0026\u0026 tar -xvf httpd.tar.bz2 -C src --strip-components=1 \t\u0026\u0026 rm httpd.tar.bz2 \t\u0026\u0026 cd src \t\t\u0026\u0026 ./configure \t\t--prefix=\"$HTTPD_PREFIX\" \t\t--enable-mods-shared=reallyall \t\u0026\u0026 make -j\"$(nproc)\" \t\u0026\u0026 make install \t\t\u0026\u0026 cd .. \t\u0026\u0026 rm -r src \t\t\u0026\u0026 sed -ri \t\t-e 's!^(\\s*CustomLog)\\s+\\S+!\\1 /proc/self/fd/1!g' \t\t-e 's!^(\\s*ErrorLog)\\s+\\S+!\\1 /proc/self/fd/2!g' \t\t\"$HTTPD_PREFIX/conf/httpd.conf\" \t\t\u0026\u0026 apt-get purge -y --auto-remove $buildDeps"},{"created":"2016-09-23T23:20:45.127455562Z","created_by":"/bin/sh -c #(nop) COPY file:761e313354b918b6cd7ea99975a4f6b53ff5381ba689bab2984aec4dab597215 in /usr/local/bin/ "},{"created":"2016-09-23T23:20:45.453934921Z","created_by":"/bin/sh -c #(nop) EXPOSE 80/tcp","empty_layer":true},{"created":"2016-09-23T23:20:45.78976459Z","created_by":"/bin/sh -c #(nop) CMD [\"httpd-foreground\"]","empty_layer":true}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:142a601d97936307e75220c35dde0348971a9584c21e7cb42e1f7004005432ab","sha256:90fcc66ad3be9f1757f954b750deb37032f208428aa12599fcb02182b9065a9c","sha256:5a8624bb7e76d1e6829f9c64c43185e02bc07f97a2189eb048609a8914e72c56","sha256:d349ff6b3afc6a2800054768c82bfbf4289c9aa5da55c1290f802943dcd4d1e9","sha256:8c064bb1f60e84fa8cc6079b6d2e76e0423389fd6aeb7e497dfdae5e05b2b25b"]}} \ No newline at end of file diff --git a/vendor/github.com/containers/image/image/fixtures/schema2-to-oci1.json b/vendor/github.com/containers/image/image/fixtures/schema2-to-oci1.json new file mode 100644 index 0000000000..cd081b7d1d --- /dev/null +++ b/vendor/github.com/containers/image/image/fixtures/schema2-to-oci1.json @@ -0,0 +1,29 @@ +{ + "schemaVersion": 2, + "config": { + "mediaType": "application/vnd.oci.image.config.v1+json", + "size": 4651, + "digest": "sha256:a13a0762ab7bed51a1b49adec0a702b1cd99294fd460a025b465bcfb7b152745" + }, + "layers": [{ + "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", + "size": 51354364, + "digest": "sha256:6a5a5368e0c2d3e5909184fa28ddfd56072e7ff3ee9a945876f7eee5896ef5bb" + }, { + "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", + "size": 150, + "digest": "sha256:1bbf5d58d24c47512e234a5623474acf65ae00d4d1414272a893204f44cc680c" + }, { + "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", + "size": 11739507, + "digest": "sha256:8f5dc8a4b12c307ac84de90cdd9a7f3915d1be04c9388868ca118831099c67a9" + }, { + "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", + "size": 8841833, + "digest": "sha256:bbd6b22eb11afce63cc76f6bc41042d99f10d6024c96b655dafba930b8d25909" + }, { + "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip", + "size": 291, + "digest": "sha256:960e52ecf8200cbd84e70eb2ad8678f4367e50d14357021872c10fa3fc5935fa" + }] +} diff --git a/vendor/github.com/containers/image/image/fixtures/schema2-to-schema1-by-docker.json b/vendor/github.com/containers/image/image/fixtures/schema2-to-schema1-by-docker.json new file mode 100644 index 0000000000..494450d9fd --- /dev/null +++ b/vendor/github.com/containers/image/image/fixtures/schema2-to-schema1-by-docker.json @@ -0,0 +1,116 @@ +{ + "schemaVersion": 1, + "name": "library/httpd-copy", + "tag": "latest", + "architecture": "amd64", + "fsLayers": [ + { + "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4" + }, + { + "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4" + }, + { + "blobSum": "sha256:960e52ecf8200cbd84e70eb2ad8678f4367e50d14357021872c10fa3fc5935fa" + }, + { + "blobSum": "sha256:bbd6b22eb11afce63cc76f6bc41042d99f10d6024c96b655dafba930b8d25909" + }, + { + "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4" + }, + { + "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4" + }, + { + "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4" + }, + { + "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4" + }, + { + "blobSum": "sha256:8f5dc8a4b12c307ac84de90cdd9a7f3915d1be04c9388868ca118831099c67a9" + }, + { + "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4" + }, + { + "blobSum": "sha256:1bbf5d58d24c47512e234a5623474acf65ae00d4d1414272a893204f44cc680c" + }, + { + "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4" + }, + { + "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4" + }, + { + "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4" + }, + { + "blobSum": "sha256:6a5a5368e0c2d3e5909184fa28ddfd56072e7ff3ee9a945876f7eee5896ef5bb" + } + ], + "history": [ + { + "v1Compatibility": "{\"architecture\":\"amd64\",\"config\":{\"Hostname\":\"383850eeb47b\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"ExposedPorts\":{\"80/tcp\":{}},\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/apache2/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"HTTPD_PREFIX=/usr/local/apache2\",\"HTTPD_VERSION=2.4.23\",\"HTTPD_SHA1=5101be34ac4a509b245adb70a56690a84fcc4e7f\",\"HTTPD_BZ2_URL=https://www.apache.org/dyn/closer.cgi?action=download\\u0026filename=httpd/httpd-2.4.23.tar.bz2\",\"HTTPD_ASC_URL=https://www.apache.org/dist/httpd/httpd-2.4.23.tar.bz2.asc\"],\"Cmd\":[\"httpd-foreground\"],\"ArgsEscaped\":true,\"Image\":\"sha256:4f83530449c67c1ed8fca72583c5b92fdf446010990028c362a381e55dd84afd\",\"Volumes\":null,\"WorkingDir\":\"/usr/local/apache2\",\"Entrypoint\":null,\"OnBuild\":[],\"Labels\":{}},\"container\":\"8825acde1b009729807e4b70a65a89399dd8da8e53be9216b9aaabaff4339f69\",\"container_config\":{\"Hostname\":\"383850eeb47b\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"ExposedPorts\":{\"80/tcp\":{}},\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[\"PATH=/usr/local/apache2/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\"HTTPD_PREFIX=/usr/local/apache2\",\"HTTPD_VERSION=2.4.23\",\"HTTPD_SHA1=5101be34ac4a509b245adb70a56690a84fcc4e7f\",\"HTTPD_BZ2_URL=https://www.apache.org/dyn/closer.cgi?action=download\\u0026filename=httpd/httpd-2.4.23.tar.bz2\",\"HTTPD_ASC_URL=https://www.apache.org/dist/httpd/httpd-2.4.23.tar.bz2.asc\"],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) \",\"CMD [\\\"httpd-foreground\\\"]\"],\"ArgsEscaped\":true,\"Image\":\"sha256:4f83530449c67c1ed8fca72583c5b92fdf446010990028c362a381e55dd84afd\",\"Volumes\":null,\"WorkingDir\":\"/usr/local/apache2\",\"Entrypoint\":null,\"OnBuild\":[],\"Labels\":{}},\"created\":\"2016-09-23T23:20:45.78976459Z\",\"docker_version\":\"1.12.1\",\"id\":\"dca7323f9c839837493199d63263083d94f5eb1796d7bd04ca8374c4e9d3749a\",\"os\":\"linux\",\"parent\":\"1b750729af47c9a802c8d14b0d327d3ad5ecdce5ae773ac728a0263315b914f4\",\"throwaway\":true}" + }, + { + "v1Compatibility": "{\"id\":\"1b750729af47c9a802c8d14b0d327d3ad5ecdce5ae773ac728a0263315b914f4\",\"parent\":\"3ef2f186f8b0a2fd2d95f5a1f1cd213f5fb0a6e51b0a8dfbe2ec7003a788ff9a\",\"created\":\"2016-09-23T23:20:45.453934921Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) EXPOSE 80/tcp\"]},\"throwaway\":true}" + }, + { + "v1Compatibility": "{\"id\":\"3ef2f186f8b0a2fd2d95f5a1f1cd213f5fb0a6e51b0a8dfbe2ec7003a788ff9a\",\"parent\":\"dbbb5c772ba968f675ebdb1968a2fbcf3cf53c0c85ff4e3329619e3735c811e6\",\"created\":\"2016-09-23T23:20:45.127455562Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) COPY file:761e313354b918b6cd7ea99975a4f6b53ff5381ba689bab2984aec4dab597215 in /usr/local/bin/ \"]}}" + }, + { + "v1Compatibility": "{\"id\":\"dbbb5c772ba968f675ebdb1968a2fbcf3cf53c0c85ff4e3329619e3735c811e6\",\"parent\":\"d264ded964bb52f78c8905c9e6c5f2b8526ef33f371981f0651f3fb0164ad4a7\",\"created\":\"2016-09-23T23:20:44.585743332Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c set -x \\t\\u0026\\u0026 buildDeps=' \\t\\tbzip2 \\t\\tca-certificates \\t\\tgcc \\t\\tlibpcre++-dev \\t\\tlibssl-dev \\t\\tmake \\t\\twget \\t' \\t\\u0026\\u0026 apt-get update \\t\\u0026\\u0026 apt-get install -y --no-install-recommends $buildDeps \\t\\u0026\\u0026 rm -r /var/lib/apt/lists/* \\t\\t\\u0026\\u0026 wget -O httpd.tar.bz2 \\\"$HTTPD_BZ2_URL\\\" \\t\\u0026\\u0026 echo \\\"$HTTPD_SHA1 *httpd.tar.bz2\\\" | sha1sum -c - \\t\\u0026\\u0026 wget -O httpd.tar.bz2.asc \\\"$HTTPD_ASC_URL\\\" \\t\\u0026\\u0026 export GNUPGHOME=\\\"$(mktemp -d)\\\" \\t\\u0026\\u0026 gpg --keyserver ha.pool.sks-keyservers.net --recv-keys A93D62ECC3C8EA12DB220EC934EA76E6791485A8 \\t\\u0026\\u0026 gpg --batch --verify httpd.tar.bz2.asc httpd.tar.bz2 \\t\\u0026\\u0026 rm -r \\\"$GNUPGHOME\\\" httpd.tar.bz2.asc \\t\\t\\u0026\\u0026 mkdir -p src \\t\\u0026\\u0026 tar -xvf httpd.tar.bz2 -C src --strip-components=1 \\t\\u0026\\u0026 rm httpd.tar.bz2 \\t\\u0026\\u0026 cd src \\t\\t\\u0026\\u0026 ./configure \\t\\t--prefix=\\\"$HTTPD_PREFIX\\\" \\t\\t--enable-mods-shared=reallyall \\t\\u0026\\u0026 make -j\\\"$(nproc)\\\" \\t\\u0026\\u0026 make install \\t\\t\\u0026\\u0026 cd .. \\t\\u0026\\u0026 rm -r src \\t\\t\\u0026\\u0026 sed -ri \\t\\t-e 's!^(\\\\s*CustomLog)\\\\s+\\\\S+!\\\\1 /proc/self/fd/1!g' \\t\\t-e 's!^(\\\\s*ErrorLog)\\\\s+\\\\S+!\\\\1 /proc/self/fd/2!g' \\t\\t\\\"$HTTPD_PREFIX/conf/httpd.conf\\\" \\t\\t\\u0026\\u0026 apt-get purge -y --auto-remove $buildDeps\"]}}" + }, + { + "v1Compatibility": "{\"id\":\"d264ded964bb52f78c8905c9e6c5f2b8526ef33f371981f0651f3fb0164ad4a7\",\"parent\":\"fd6f8d569a8a6d2a95f797494ab3cee7a47693dde647210b236a141f76b5c5fd\",\"created\":\"2016-09-23T23:19:04.009782822Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) ENV HTTPD_ASC_URL=https://www.apache.org/dist/httpd/httpd-2.4.23.tar.bz2.asc\"]},\"throwaway\":true}" + }, + { + "v1Compatibility": "{\"id\":\"fd6f8d569a8a6d2a95f797494ab3cee7a47693dde647210b236a141f76b5c5fd\",\"parent\":\"5e2578d171daa47c0eeb55e592b4e3bd28a0946a75baed58e4d4dd315c5d5780\",\"created\":\"2016-09-23T23:19:03.705796801Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) ENV HTTPD_BZ2_URL=https://www.apache.org/dyn/closer.cgi?action=download\\u0026filename=httpd/httpd-2.4.23.tar.bz2\"]},\"throwaway\":true}" + }, + { + "v1Compatibility": "{\"id\":\"5e2578d171daa47c0eeb55e592b4e3bd28a0946a75baed58e4d4dd315c5d5780\",\"parent\":\"1912159ee5bea8d7fde49b85012f90c47bceb3f09e4082b112b1f06a3f339c53\",\"created\":\"2016-09-23T19:16:55.629947307Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) ENV HTTPD_SHA1=5101be34ac4a509b245adb70a56690a84fcc4e7f\"]},\"throwaway\":true}" + }, + { + "v1Compatibility": "{\"id\":\"1912159ee5bea8d7fde49b85012f90c47bceb3f09e4082b112b1f06a3f339c53\",\"parent\":\"3bfb089ca9d4bb73a9016e44a2c6f908b701f97704433305c419f75e8559d8a2\",\"created\":\"2016-09-23T19:16:55.321573403Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) ENV HTTPD_VERSION=2.4.23\"]},\"throwaway\":true}" + }, + { + "v1Compatibility": "{\"id\":\"3bfb089ca9d4bb73a9016e44a2c6f908b701f97704433305c419f75e8559d8a2\",\"parent\":\"ae1ece73de4d0365c8b8ab45ba0bf6b1efa4213c16a4903b89341b704d101c3c\",\"created\":\"2016-09-23T19:16:54.948461741Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c apt-get update \\t\\u0026\\u0026 apt-get install -y --no-install-recommends \\t\\tlibapr1 \\t\\tlibaprutil1 \\t\\tlibaprutil1-ldap \\t\\tlibapr1-dev \\t\\tlibaprutil1-dev \\t\\tlibpcre++0 \\t\\tlibssl1.0.0 \\t\\u0026\\u0026 rm -r /var/lib/apt/lists/*\"]}}" + }, + { + "v1Compatibility": "{\"id\":\"ae1ece73de4d0365c8b8ab45ba0bf6b1efa4213c16a4903b89341b704d101c3c\",\"parent\":\"bffbcb416f40e0bd3ebae202403587bfd41829cd1e0d538b66f29adce40c6408\",\"created\":\"2016-09-23T19:16:42.339911155Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) WORKDIR /usr/local/apache2\"]},\"throwaway\":true}" + }, + { + "v1Compatibility": "{\"id\":\"bffbcb416f40e0bd3ebae202403587bfd41829cd1e0d538b66f29adce40c6408\",\"parent\":\"7b27731a3363efcb6b0520962d544471745aae15664920dffe690b4fdb410d80\",\"created\":\"2016-09-23T19:16:41.990121202Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c mkdir -p \\\"$HTTPD_PREFIX\\\" \\t\\u0026\\u0026 chown www-data:www-data \\\"$HTTPD_PREFIX\\\"\"]}}" + }, + { + "v1Compatibility": "{\"id\":\"7b27731a3363efcb6b0520962d544471745aae15664920dffe690b4fdb410d80\",\"parent\":\"57a0a421f1acbc1fe6b88b32d3d1c3c0388ff1958b97f95dd0e3a599b810499b\",\"created\":\"2016-09-23T19:16:41.037788416Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) ENV PATH=/usr/local/apache2/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"]},\"throwaway\":true}" + }, + { + "v1Compatibility": "{\"id\":\"57a0a421f1acbc1fe6b88b32d3d1c3c0388ff1958b97f95dd0e3a599b810499b\",\"parent\":\"faeaf6fdfdcbb18d68c12db9683a02428bab83962a493de88b4c7b1ec941db8f\",\"created\":\"2016-09-23T19:16:40.725768956Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) ENV HTTPD_PREFIX=/usr/local/apache2\"]},\"throwaway\":true}" + }, + { + "v1Compatibility": "{\"id\":\"faeaf6fdfdcbb18d68c12db9683a02428bab83962a493de88b4c7b1ec941db8f\",\"parent\":\"d0c4f1eb7dc8f4dae2b45fe5c0cf4cfc70e5be85d933f5f5f4deb59f134fb520\",\"created\":\"2016-09-23T18:08:51.133779867Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) CMD [\\\"/bin/bash\\\"]\"]},\"throwaway\":true}" + }, + { + "v1Compatibility": "{\"id\":\"d0c4f1eb7dc8f4dae2b45fe5c0cf4cfc70e5be85d933f5f5f4deb59f134fb520\",\"created\":\"2016-09-23T18:08:50.537223822Z\",\"container_config\":{\"Cmd\":[\"/bin/sh -c #(nop) ADD file:c6c23585ab140b0b320d4e99bc1b0eb544c9e96c24d90fec5e069a6d57d335ca in / \"]}}" + } + ], + "signatures": [ + { + "header": { + "jwk": { + "crv": "P-256", + "kid": "6QVR:5NTY:VIHC:W6IU:XYIN:CTKT:OG5R:XEEG:Z6XJ:2623:YCBP:36MA", + "kty": "EC", + "x": "NAGHj6-IdNonuFoxlqJnNMjcrCCE1CBoq2r_1NDci68", + "y": "Kocqgj_Ey5J-wLXTjkuqLC-HjciAnWxsBEziAOTvSPc" + }, + "alg": "ES256" + }, + "signature": "2MN5k06i8xkJhD5ay4yxAFK7tsZk58UznAZONxDplvQ5lZwbRS162OeBDjCb0Hk0IDyrLXtAfBDlY2Gzf6jrpw", + "protected": "eyJmb3JtYXRMZW5ndGgiOjEwODk1LCJmb3JtYXRUYWlsIjoiQ24wIiwidGltZSI6IjIwMTYtMTAtMTRUMTY6MTI6MDlaIn0" + } + ] +} diff --git a/vendor/github.com/containers/image/image/fixtures/schema2.json b/vendor/github.com/containers/image/image/fixtures/schema2.json new file mode 100644 index 0000000000..8df4c0daf8 --- /dev/null +++ b/vendor/github.com/containers/image/image/fixtures/schema2.json @@ -0,0 +1,36 @@ +{ + "schemaVersion": 2, + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "config": { + "mediaType": "application/octet-stream", + "size": 5940, + "digest": "sha256:9ca4bda0a6b3727a6ffcc43e981cad0f24e2ec79d338f6ba325b4dfd0756fb8f" + }, + "layers": [ + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 51354364, + "digest": "sha256:6a5a5368e0c2d3e5909184fa28ddfd56072e7ff3ee9a945876f7eee5896ef5bb" + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 150, + "digest": "sha256:1bbf5d58d24c47512e234a5623474acf65ae00d4d1414272a893204f44cc680c" + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 11739507, + "digest": "sha256:8f5dc8a4b12c307ac84de90cdd9a7f3915d1be04c9388868ca118831099c67a9" + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 8841833, + "digest": "sha256:bbd6b22eb11afce63cc76f6bc41042d99f10d6024c96b655dafba930b8d25909" + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 291, + "digest": "sha256:960e52ecf8200cbd84e70eb2ad8678f4367e50d14357021872c10fa3fc5935fa" + } + ] +} \ No newline at end of file diff --git a/vendor/github.com/containers/image/image/oci_test.go b/vendor/github.com/containers/image/image/oci_test.go new file mode 100644 index 0000000000..c51b3ae774 --- /dev/null +++ b/vendor/github.com/containers/image/image/oci_test.go @@ -0,0 +1,345 @@ +package image + +import ( + "bytes" + "encoding/json" + "io" + "io/ioutil" + "path/filepath" + "testing" + "time" + + "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" +) + +func manifestOCI1FromFixture(t *testing.T, src types.ImageSource, fixture string) genericManifest { + manifest, err := ioutil.ReadFile(filepath.Join("fixtures", fixture)) + require.NoError(t, err) + + m, err := manifestOCI1FromManifest(src, manifest) + require.NoError(t, err) + return m +} + +func manifestOCI1FromComponentsLikeFixture(configBlob []byte) genericManifest { + return manifestOCI1FromComponents(descriptor{ + MediaType: imgspecv1.MediaTypeImageConfig, + Size: 5940, + Digest: "sha256:9ca4bda0a6b3727a6ffcc43e981cad0f24e2ec79d338f6ba325b4dfd0756fb8f", + }, nil, configBlob, []descriptor{ + { + MediaType: imgspecv1.MediaTypeImageLayerGzip, + Digest: "sha256:6a5a5368e0c2d3e5909184fa28ddfd56072e7ff3ee9a945876f7eee5896ef5bb", + Size: 51354364, + }, + { + MediaType: imgspecv1.MediaTypeImageLayerGzip, + Digest: "sha256:1bbf5d58d24c47512e234a5623474acf65ae00d4d1414272a893204f44cc680c", + Size: 150, + }, + { + MediaType: imgspecv1.MediaTypeImageLayerGzip, + Digest: "sha256:8f5dc8a4b12c307ac84de90cdd9a7f3915d1be04c9388868ca118831099c67a9", + Size: 11739507, + }, + { + MediaType: imgspecv1.MediaTypeImageLayerGzip, + Digest: "sha256:bbd6b22eb11afce63cc76f6bc41042d99f10d6024c96b655dafba930b8d25909", + Size: 8841833, + }, + { + MediaType: imgspecv1.MediaTypeImageLayerGzip, + Digest: "sha256:960e52ecf8200cbd84e70eb2ad8678f4367e50d14357021872c10fa3fc5935fa", + Size: 291, + }, + }) +} + +func TestManifestOCI1FromManifest(t *testing.T) { + // This just tests that the JSON can be loaded; we test that the parsed + // values are correctly returned in tests for the individual getter methods. + _ = manifestOCI1FromFixture(t, unusedImageSource{}, "oci1.json") + + _, err := manifestOCI1FromManifest(nil, []byte{}) + assert.Error(t, err) +} + +func TestManifestOCI1FromComponents(t *testing.T) { + // This just smoke-tests that the manifest can be created; we test that the parsed + // values are correctly returned in tests for the individual getter methods. + _ = manifestOCI1FromComponentsLikeFixture(nil) +} + +func TestManifestOCI1Serialize(t *testing.T) { + for _, m := range []genericManifest{ + manifestOCI1FromFixture(t, unusedImageSource{}, "oci1.json"), + manifestOCI1FromComponentsLikeFixture(nil), + } { + serialized, err := m.serialize() + require.NoError(t, err) + var contents map[string]interface{} + err = json.Unmarshal(serialized, &contents) + require.NoError(t, err) + + original, err := ioutil.ReadFile("fixtures/oci1.json") + require.NoError(t, err) + var originalContents map[string]interface{} + err = json.Unmarshal(original, &originalContents) + require.NoError(t, err) + + // We would ideally like to compare “serialized” with some transformation of + // “original”, but the ordering of fields in JSON maps is undefined, so this is + // easier. + assert.Equal(t, originalContents, contents) + } +} + +func TestManifestOCI1ManifestMIMEType(t *testing.T) { + for _, m := range []genericManifest{ + manifestOCI1FromFixture(t, unusedImageSource{}, "oci1.json"), + manifestOCI1FromComponentsLikeFixture(nil), + } { + assert.Equal(t, imgspecv1.MediaTypeImageManifest, m.manifestMIMEType()) + } +} + +func TestManifestOCI1ConfigInfo(t *testing.T) { + for _, m := range []genericManifest{ + manifestOCI1FromFixture(t, unusedImageSource{}, "oci1.json"), + manifestOCI1FromComponentsLikeFixture(nil), + } { + assert.Equal(t, types.BlobInfo{ + Size: 5940, + Digest: "sha256:9ca4bda0a6b3727a6ffcc43e981cad0f24e2ec79d338f6ba325b4dfd0756fb8f", + }, m.ConfigInfo()) + } +} + +func TestManifestOCI1ConfigBlob(t *testing.T) { + realConfigJSON, err := ioutil.ReadFile("fixtures/oci1-config.json") + require.NoError(t, err) + + for _, c := range []struct { + cbISfn func(digest digest.Digest) (io.ReadCloser, int64, error) + blob []byte + }{ + // Success + {func(digest digest.Digest) (io.ReadCloser, int64, error) { + return ioutil.NopCloser(bytes.NewReader(realConfigJSON)), int64(len(realConfigJSON)), nil + }, realConfigJSON}, + // Various kinds of failures + {nil, nil}, + {func(digest digest.Digest) (io.ReadCloser, int64, error) { + return nil, -1, errors.New("Error returned from GetBlob") + }, nil}, + {func(digest digest.Digest) (io.ReadCloser, int64, error) { + reader, writer := io.Pipe() + writer.CloseWithError(errors.New("Expected error reading input in ConfigBlob")) + return reader, 1, nil + }, nil}, + {func(digest digest.Digest) (io.ReadCloser, int64, error) { + nonmatchingJSON := []byte("This does not match ConfigDescriptor.Digest") + return ioutil.NopCloser(bytes.NewReader(nonmatchingJSON)), int64(len(nonmatchingJSON)), nil + }, nil}, + } { + var src types.ImageSource + if c.cbISfn != nil { + src = configBlobImageSource{unusedImageSource{}, c.cbISfn} + } else { + src = nil + } + m := manifestOCI1FromFixture(t, src, "oci1.json") + blob, err := m.ConfigBlob() + if c.blob != nil { + assert.NoError(t, err) + assert.Equal(t, c.blob, blob) + } else { + assert.Error(t, err) + } + } + + // Generally conficBlob should match ConfigInfo; we don’t quite need it to, and this will + // guarantee that the returned object is returning the original contents instead + // of reading an object from elsewhere. + configBlob := []byte("config blob which does not match ConfigInfo") + // This just tests that the manifest can be created; we test that the parsed + // values are correctly returned in tests for the individual getter methods. + m := manifestOCI1FromComponentsLikeFixture(configBlob) + cb, err := m.ConfigBlob() + require.NoError(t, err) + assert.Equal(t, configBlob, cb) +} + +func TestManifestOCI1LayerInfo(t *testing.T) { + for _, m := range []genericManifest{ + manifestOCI1FromFixture(t, unusedImageSource{}, "oci1.json"), + manifestOCI1FromComponentsLikeFixture(nil), + } { + assert.Equal(t, []types.BlobInfo{ + { + Digest: "sha256:6a5a5368e0c2d3e5909184fa28ddfd56072e7ff3ee9a945876f7eee5896ef5bb", + Size: 51354364, + }, + { + Digest: "sha256:1bbf5d58d24c47512e234a5623474acf65ae00d4d1414272a893204f44cc680c", + Size: 150, + }, + { + Digest: "sha256:8f5dc8a4b12c307ac84de90cdd9a7f3915d1be04c9388868ca118831099c67a9", + Size: 11739507, + }, + { + Digest: "sha256:bbd6b22eb11afce63cc76f6bc41042d99f10d6024c96b655dafba930b8d25909", + Size: 8841833, + }, + { + Digest: "sha256:960e52ecf8200cbd84e70eb2ad8678f4367e50d14357021872c10fa3fc5935fa", + Size: 291, + }, + }, m.LayerInfos()) + } +} + +func TestManifestOCI1ImageInspectInfo(t *testing.T) { + configJSON, err := ioutil.ReadFile("fixtures/oci1-config.json") + require.NoError(t, err) + + m := manifestOCI1FromComponentsLikeFixture(configJSON) + ii, err := m.imageInspectInfo() + require.NoError(t, err) + assert.Equal(t, types.ImageInspectInfo{ + Tag: "", + Created: time.Date(2016, 9, 23, 23, 20, 45, 789764590, time.UTC), + DockerVersion: "1.12.1", + Labels: map[string]string{}, + Architecture: "amd64", + Os: "linux", + Layers: nil, + }, *ii) + + // nil configBlob will trigger an error in m.ConfigBlob() + m = manifestOCI1FromComponentsLikeFixture(nil) + _, err = m.imageInspectInfo() + assert.Error(t, err) + + m = manifestOCI1FromComponentsLikeFixture([]byte("invalid JSON")) + _, err = m.imageInspectInfo() + assert.Error(t, err) +} + +func TestManifestOCI1UpdatedImageNeedsLayerDiffIDs(t *testing.T) { + for _, m := range []genericManifest{ + manifestOCI1FromFixture(t, unusedImageSource{}, "oci1.json"), + manifestOCI1FromComponentsLikeFixture(nil), + } { + assert.False(t, m.UpdatedImageNeedsLayerDiffIDs(types.ManifestUpdateOptions{ + ManifestMIMEType: manifest.DockerV2Schema2MediaType, + })) + } +} + +// oci1ImageSource is plausible enough for schema conversions in manifestOCI1.UpdatedImage() to work. +type oci1ImageSource struct { + configBlobImageSource + ref reference.Named +} + +func (OCIis *oci1ImageSource) Reference() types.ImageReference { + return refImageReferenceMock{OCIis.ref} +} + +func newOCI1ImageSource(t *testing.T, dockerRef string) *oci1ImageSource { + realConfigJSON, err := ioutil.ReadFile("fixtures/oci1-config.json") + require.NoError(t, err) + + ref, err := reference.ParseNormalizedNamed(dockerRef) + require.NoError(t, err) + + return &oci1ImageSource{ + configBlobImageSource: configBlobImageSource{ + f: func(digest digest.Digest) (io.ReadCloser, int64, error) { + return ioutil.NopCloser(bytes.NewReader(realConfigJSON)), int64(len(realConfigJSON)), nil + }, + }, + ref: ref, + } +} + +func TestManifestOCI1UpdatedImage(t *testing.T) { + originalSrc := newOCI1ImageSource(t, "httpd:latest") + original := manifestOCI1FromFixture(t, originalSrc, "oci1.json") + + // LayerInfos: + layerInfos := append(original.LayerInfos()[1:], original.LayerInfos()[0]) + res, err := original.UpdatedImage(types.ManifestUpdateOptions{ + LayerInfos: layerInfos, + }) + require.NoError(t, err) + assert.Equal(t, layerInfos, res.LayerInfos()) + _, err = original.UpdatedImage(types.ManifestUpdateOptions{ + LayerInfos: append(layerInfos, layerInfos[0]), + }) + assert.Error(t, err) + + // ManifestMIMEType: + // Only smoke-test the valid conversions, detailed tests are below. (This also verifies that “original” is not affected.) + for _, mime := range []string{ + manifest.DockerV2Schema2MediaType, + } { + _, err = original.UpdatedImage(types.ManifestUpdateOptions{ + ManifestMIMEType: mime, + InformationOnly: types.ManifestUpdateInformation{ + Destination: &memoryImageDest{ref: originalSrc.ref}, + }, + }) + assert.NoError(t, err, mime) + } + for _, mime := range []string{ + imgspecv1.MediaTypeImageManifest, // This indicates a confused caller, not a no-op. + "this is invalid", + } { + _, err = original.UpdatedImage(types.ManifestUpdateOptions{ + ManifestMIMEType: mime, + }) + assert.Error(t, err, mime) + } + + // m hasn’t been changed: + m2 := manifestOCI1FromFixture(t, originalSrc, "oci1.json") + typedOriginal, ok := original.(*manifestOCI1) + require.True(t, ok) + typedM2, ok := m2.(*manifestOCI1) + require.True(t, ok) + assert.Equal(t, *typedM2, *typedOriginal) +} + +func TestConvertToManifestSchema2(t *testing.T) { + originalSrc := newOCI1ImageSource(t, "httpd-copy:latest") + original := manifestOCI1FromFixture(t, originalSrc, "oci1.json") + res, err := original.UpdatedImage(types.ManifestUpdateOptions{ + ManifestMIMEType: manifest.DockerV2Schema2MediaType, + }) + require.NoError(t, err) + + convertedJSON, mt, err := res.Manifest() + require.NoError(t, err) + assert.Equal(t, manifest.DockerV2Schema2MediaType, mt) + + byHandJSON, err := ioutil.ReadFile("fixtures/oci1-to-schema2.json") + require.NoError(t, err) + var converted, byHand map[string]interface{} + err = json.Unmarshal(byHandJSON, &byHand) + require.NoError(t, err) + err = json.Unmarshal(convertedJSON, &converted) + require.NoError(t, err) + assert.Equal(t, byHand, converted) + + // FIXME? Test also the various failure cases, if only to see that we don't crash? +} diff --git a/vendor/github.com/containers/image/manifest/fixtures/non-json.manifest.json b/vendor/github.com/containers/image/manifest/fixtures/non-json.manifest.json new file mode 100644 index 0000000000000000000000000000000000000000..f89272127575245341ca24b85edd44d8f4f215ef GIT binary patch literal 411 zcmV;M0c8H80h_?f%)r5TyXccd_m-R!jHeF-Br$}&`th(@DY+=KBr`cNN6D&MDKjNC zuOzdi62wl)PtHy)(k)6&OD#&xOHNg?QYuL;F3HSG*UwGN%S;2Zm1=dAGIJBtQ<2oe z}j0{s#Oiay_j4cf;j4YE=l2TKXEzB)V6U~!S6AjIb z%q%Sp(^3-642)9^QVa}}Oj6C$%nVG7%u`KGL6((N7J%H5SdyQcnXHhUpI4HYnU`9m zP@I{bmsnC-lnPRmUr>^np9k?!a#1Q!aS=j~63~#$T%f}ea|@KL3{6ZejEpVK4UKDS zJLa%3Fmf=ku`+?eg@KDx02W$NSD63DeycibxIdt0URPzmL;uWzVE5D4c7)8*<(8R~ zSn@1YEb+oJ!Kl77mX)6yrv!YrV9zQ$mmANnr}X;z%NDZ?-VUoWIyq-Ti3+v#79^%&%*SuC7PcG4elSu2an1YHV9wlEtw+YEQ+i Fxd5g!#>fBw literal 0 HcmV?d00001 diff --git a/vendor/github.com/containers/image/manifest/fixtures/ociv1.manifest.json b/vendor/github.com/containers/image/manifest/fixtures/ociv1.manifest.json new file mode 100644 index 0000000000..ce098423cc --- /dev/null +++ b/vendor/github.com/containers/image/manifest/fixtures/ociv1.manifest.json @@ -0,0 +1,26 @@ +{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "config": { + "mediaType": "application/vnd.oci.image.serialization.config.v1+json", + "size": 7023, + "digest": "sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7" + }, + "layers": [ + { + "mediaType": "application/vnd.oci.image.serialization.rootfs.tar.gzip", + "size": 32654, + "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f" + }, + { + "mediaType": "application/vnd.oci.image.serialization.rootfs.tar.gzip", + "size": 16724, + "digest": "sha256:3c3a4604a545cdc127456d94e421cd355bca5b528f4a9c1905b15da2eb4a4c6b" + }, + { + "mediaType": "application/vnd.oci.image.serialization.rootfs.tar.gzip", + "size": 73109, + "digest": "sha256:ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736" + } + ] +} \ No newline at end of file diff --git a/vendor/github.com/containers/image/manifest/fixtures/ociv1list.manifest.json b/vendor/github.com/containers/image/manifest/fixtures/ociv1list.manifest.json new file mode 100644 index 0000000000..2c334b6ee0 --- /dev/null +++ b/vendor/github.com/containers/image/manifest/fixtures/ociv1list.manifest.json @@ -0,0 +1,56 @@ +{ + "schemaVersion": 2, + "mediaType": "application/vnd.oci.image.manifest.list.v1+json", + "manifests": [ + { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "size": 2094, + "digest": "sha256:7820f9a86d4ad15a2c4f0c0e5479298df2aa7c2f6871288e2ef8546f3e7b6783", + "platform": { + "architecture": "ppc64le", + "os": "linux" + } + }, + { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "size": 1922, + "digest": "sha256:ae1b0e06e8ade3a11267564a26e750585ba2259c0ecab59ab165ad1af41d1bdd", + "platform": { + "architecture": "amd64", + "os": "linux", + "features": [ + "sse" + ] + } + }, + { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "size": 2084, + "digest": "sha256:e4c0df75810b953d6717b8f8f28298d73870e8aa2a0d5e77b8391f16fdfbbbe2", + "platform": { + "architecture": "s390x", + "os": "linux" + } + }, + { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "size": 2084, + "digest": "sha256:07ebe243465ef4a667b78154ae6c3ea46fdb1582936aac3ac899ea311a701b40", + "platform": { + "architecture": "arm", + "os": "linux", + "variant": "armv7" + } + }, + { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "size": 2090, + "digest": "sha256:fb2fc0707b86dafa9959fe3d29e66af8787aee4d9a23581714be65db4265ad8a", + "platform": { + "architecture": "arm64", + "os": "linux", + "variant": "armv8" + } + } + ] +} diff --git a/vendor/github.com/containers/image/manifest/fixtures/unknown-version.manifest.json b/vendor/github.com/containers/image/manifest/fixtures/unknown-version.manifest.json new file mode 100644 index 0000000000..b0f34b631c --- /dev/null +++ b/vendor/github.com/containers/image/manifest/fixtures/unknown-version.manifest.json @@ -0,0 +1,5 @@ +{ + "schemaVersion": 99999, + "name": "mitr/noversion-nonsense", + "tag": "latest" +} diff --git a/vendor/github.com/containers/image/manifest/fixtures/v2list.manifest.json b/vendor/github.com/containers/image/manifest/fixtures/v2list.manifest.json new file mode 100644 index 0000000000..1bf9896e04 --- /dev/null +++ b/vendor/github.com/containers/image/manifest/fixtures/v2list.manifest.json @@ -0,0 +1,56 @@ +{ + "schemaVersion": 2, + "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json", + "manifests": [ + { + "mediaType": "application/vnd.docker.distribution.manifest.v1+json", + "size": 2094, + "digest": "sha256:7820f9a86d4ad15a2c4f0c0e5479298df2aa7c2f6871288e2ef8546f3e7b6783", + "platform": { + "architecture": "ppc64le", + "os": "linux" + } + }, + { + "mediaType": "application/vnd.docker.distribution.manifest.v1+json", + "size": 1922, + "digest": "sha256:ae1b0e06e8ade3a11267564a26e750585ba2259c0ecab59ab165ad1af41d1bdd", + "platform": { + "architecture": "amd64", + "os": "linux", + "features": [ + "sse" + ] + } + }, + { + "mediaType": "application/vnd.docker.distribution.manifest.v1+json", + "size": 2084, + "digest": "sha256:e4c0df75810b953d6717b8f8f28298d73870e8aa2a0d5e77b8391f16fdfbbbe2", + "platform": { + "architecture": "s390x", + "os": "linux" + } + }, + { + "mediaType": "application/vnd.docker.distribution.manifest.v1+json", + "size": 2084, + "digest": "sha256:07ebe243465ef4a667b78154ae6c3ea46fdb1582936aac3ac899ea311a701b40", + "platform": { + "architecture": "arm", + "os": "linux", + "variant": "armv7" + } + }, + { + "mediaType": "application/vnd.docker.distribution.manifest.v1+json", + "size": 2090, + "digest": "sha256:fb2fc0707b86dafa9959fe3d29e66af8787aee4d9a23581714be65db4265ad8a", + "platform": { + "architecture": "arm64", + "os": "linux", + "variant": "armv8" + } + } + ] +} diff --git a/vendor/github.com/containers/image/manifest/fixtures/v2s1-invalid-signatures.manifest.json b/vendor/github.com/containers/image/manifest/fixtures/v2s1-invalid-signatures.manifest.json new file mode 100644 index 0000000000..8dfefd4e1b --- /dev/null +++ b/vendor/github.com/containers/image/manifest/fixtures/v2s1-invalid-signatures.manifest.json @@ -0,0 +1,11 @@ +{ + "schemaVersion": 1, + "name": "mitr/buxybox", + "tag": "latest", + "architecture": "amd64", + "fsLayers": [ + ], + "history": [ + ], + "signatures": 1 +} diff --git a/vendor/github.com/containers/image/manifest/fixtures/v2s1-unsigned.manifest.json b/vendor/github.com/containers/image/manifest/fixtures/v2s1-unsigned.manifest.json new file mode 100644 index 0000000000..77cd6674fd --- /dev/null +++ b/vendor/github.com/containers/image/manifest/fixtures/v2s1-unsigned.manifest.json @@ -0,0 +1,28 @@ +{ + "schemaVersion": 1, + "name": "mitr/buxybox", + "tag": "latest", + "architecture": "amd64", + "fsLayers": [ + { + "blobSum": "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef" + }, + { + "blobSum": "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef" + }, + { + "blobSum": "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef" + } + ], + "history": [ + { + "v1Compatibility": "{\"id\":\"f1b5eb0a1215f663765d509b6cdf3841bc2bcff0922346abb943d1342d469a97\",\"parent\":\"594075be8d003f784074cc639d970d1fa091a8197850baaae5052c01564ac535\",\"created\":\"2016-03-03T11:29:44.222098366Z\",\"container\":\"c0924f5b281a1992127d0afc065e59548ded8880b08aea4debd56d4497acb17a\",\"container_config\":{\"Hostname\":\"56f0fe1dfc95\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"ExposedPorts\":null,\"PublishService\":\"\",\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) LABEL Checksum=4fef81d30f31f9213c642881357e6662846a0f884c2366c13ebad807b4031368 ./tests/test-images/Dockerfile.2\"],\"Image\":\"594075be8d003f784074cc639d970d1fa091a8197850baaae5052c01564ac535\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{\"Checksum\":\"4fef81d30f31f9213c642881357e6662846a0f884c2366c13ebad807b4031368 ./tests/test-images/Dockerfile.2\",\"Name\":\"atomic-test-2\"}},\"docker_version\":\"1.8.2-fc22\",\"author\":\"\\\"William Temple \\u003cwtemple at redhat dot com\\u003e\\\"\",\"config\":{\"Hostname\":\"56f0fe1dfc95\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"ExposedPorts\":null,\"PublishService\":\"\",\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":null,\"Image\":\"594075be8d003f784074cc639d970d1fa091a8197850baaae5052c01564ac535\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{\"Checksum\":\"4fef81d30f31f9213c642881357e6662846a0f884c2366c13ebad807b4031368 ./tests/test-images/Dockerfile.2\",\"Name\":\"atomic-test-2\"}},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n" + }, + { + "v1Compatibility": "{\"id\":\"594075be8d003f784074cc639d970d1fa091a8197850baaae5052c01564ac535\",\"parent\":\"03dfa1cd1abe452bc2b69b8eb2362fa6beebc20893e65437906318954f6276d4\",\"created\":\"2016-03-03T11:29:38.563048924Z\",\"container\":\"fd4cf54dcd239fbae9bdade9db48e41880b436d27cb5313f60952a46ab04deff\",\"container_config\":{\"Hostname\":\"56f0fe1dfc95\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"ExposedPorts\":null,\"PublishService\":\"\",\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) LABEL Name=atomic-test-2\"],\"Image\":\"03dfa1cd1abe452bc2b69b8eb2362fa6beebc20893e65437906318954f6276d4\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{\"Name\":\"atomic-test-2\"}},\"docker_version\":\"1.8.2-fc22\",\"author\":\"\\\"William Temple \\u003cwtemple at redhat dot com\\u003e\\\"\",\"config\":{\"Hostname\":\"56f0fe1dfc95\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"ExposedPorts\":null,\"PublishService\":\"\",\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":null,\"Image\":\"03dfa1cd1abe452bc2b69b8eb2362fa6beebc20893e65437906318954f6276d4\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{\"Name\":\"atomic-test-2\"}},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n" + }, + { + "v1Compatibility": "{\"id\":\"03dfa1cd1abe452bc2b69b8eb2362fa6beebc20893e65437906318954f6276d4\",\"created\":\"2016-03-03T11:29:32.948089874Z\",\"container\":\"56f0fe1dfc95755dd6cda10f7215c9937a8d9c6348d079c581a261fd4c2f3a5f\",\"container_config\":{\"Hostname\":\"56f0fe1dfc95\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"ExposedPorts\":null,\"PublishService\":\"\",\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) MAINTAINER \\\"William Temple \\u003cwtemple at redhat dot com\\u003e\\\"\"],\"Image\":\"\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":null},\"docker_version\":\"1.8.2-fc22\",\"author\":\"\\\"William Temple \\u003cwtemple at redhat dot com\\u003e\\\"\",\"config\":{\"Hostname\":\"56f0fe1dfc95\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"ExposedPorts\":null,\"PublishService\":\"\",\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":null,\"Image\":\"\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n" + } + ] +} \ No newline at end of file diff --git a/vendor/github.com/containers/image/manifest/fixtures/v2s1.manifest.json b/vendor/github.com/containers/image/manifest/fixtures/v2s1.manifest.json new file mode 100644 index 0000000000..d384e3e6e8 --- /dev/null +++ b/vendor/github.com/containers/image/manifest/fixtures/v2s1.manifest.json @@ -0,0 +1,44 @@ +{ + "schemaVersion": 1, + "name": "mitr/buxybox", + "tag": "latest", + "architecture": "amd64", + "fsLayers": [ + { + "blobSum": "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef" + }, + { + "blobSum": "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef" + }, + { + "blobSum": "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef" + } + ], + "history": [ + { + "v1Compatibility": "{\"id\":\"f1b5eb0a1215f663765d509b6cdf3841bc2bcff0922346abb943d1342d469a97\",\"parent\":\"594075be8d003f784074cc639d970d1fa091a8197850baaae5052c01564ac535\",\"created\":\"2016-03-03T11:29:44.222098366Z\",\"container\":\"c0924f5b281a1992127d0afc065e59548ded8880b08aea4debd56d4497acb17a\",\"container_config\":{\"Hostname\":\"56f0fe1dfc95\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"ExposedPorts\":null,\"PublishService\":\"\",\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) LABEL Checksum=4fef81d30f31f9213c642881357e6662846a0f884c2366c13ebad807b4031368 ./tests/test-images/Dockerfile.2\"],\"Image\":\"594075be8d003f784074cc639d970d1fa091a8197850baaae5052c01564ac535\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{\"Checksum\":\"4fef81d30f31f9213c642881357e6662846a0f884c2366c13ebad807b4031368 ./tests/test-images/Dockerfile.2\",\"Name\":\"atomic-test-2\"}},\"docker_version\":\"1.8.2-fc22\",\"author\":\"\\\"William Temple \\u003cwtemple at redhat dot com\\u003e\\\"\",\"config\":{\"Hostname\":\"56f0fe1dfc95\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"ExposedPorts\":null,\"PublishService\":\"\",\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":null,\"Image\":\"594075be8d003f784074cc639d970d1fa091a8197850baaae5052c01564ac535\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{\"Checksum\":\"4fef81d30f31f9213c642881357e6662846a0f884c2366c13ebad807b4031368 ./tests/test-images/Dockerfile.2\",\"Name\":\"atomic-test-2\"}},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n" + }, + { + "v1Compatibility": "{\"id\":\"594075be8d003f784074cc639d970d1fa091a8197850baaae5052c01564ac535\",\"parent\":\"03dfa1cd1abe452bc2b69b8eb2362fa6beebc20893e65437906318954f6276d4\",\"created\":\"2016-03-03T11:29:38.563048924Z\",\"container\":\"fd4cf54dcd239fbae9bdade9db48e41880b436d27cb5313f60952a46ab04deff\",\"container_config\":{\"Hostname\":\"56f0fe1dfc95\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"ExposedPorts\":null,\"PublishService\":\"\",\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) LABEL Name=atomic-test-2\"],\"Image\":\"03dfa1cd1abe452bc2b69b8eb2362fa6beebc20893e65437906318954f6276d4\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{\"Name\":\"atomic-test-2\"}},\"docker_version\":\"1.8.2-fc22\",\"author\":\"\\\"William Temple \\u003cwtemple at redhat dot com\\u003e\\\"\",\"config\":{\"Hostname\":\"56f0fe1dfc95\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"ExposedPorts\":null,\"PublishService\":\"\",\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":null,\"Image\":\"03dfa1cd1abe452bc2b69b8eb2362fa6beebc20893e65437906318954f6276d4\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{\"Name\":\"atomic-test-2\"}},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n" + }, + { + "v1Compatibility": "{\"id\":\"03dfa1cd1abe452bc2b69b8eb2362fa6beebc20893e65437906318954f6276d4\",\"created\":\"2016-03-03T11:29:32.948089874Z\",\"container\":\"56f0fe1dfc95755dd6cda10f7215c9937a8d9c6348d079c581a261fd4c2f3a5f\",\"container_config\":{\"Hostname\":\"56f0fe1dfc95\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"ExposedPorts\":null,\"PublishService\":\"\",\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) MAINTAINER \\\"William Temple \\u003cwtemple at redhat dot com\\u003e\\\"\"],\"Image\":\"\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":null},\"docker_version\":\"1.8.2-fc22\",\"author\":\"\\\"William Temple \\u003cwtemple at redhat dot com\\u003e\\\"\",\"config\":{\"Hostname\":\"56f0fe1dfc95\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"ExposedPorts\":null,\"PublishService\":\"\",\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":null,\"Image\":\"\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":null},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":0}\n" + } + ], + "signatures": [ + { + "header": { + "jwk": { + "crv": "P-256", + "kid": "OZ45:U3IG:TDOI:PMBD:NGP2:LDIW:II2U:PSBI:MMCZ:YZUP:TUUO:XPZT", + "kty": "EC", + "x": "ReC5c0J9tgXSdUL4_xzEt5RsD8kFt2wWSgJcpAcOQx8", + "y": "3sBGEqQ3ZMeqPKwQBAadN2toOUEASha18xa0WwsDF-M" + }, + "alg": "ES256" + }, + "signature": "dV1paJ3Ck1Ph4FcEhg_frjqxdlGdI6-ywRamk6CvMOcaOEUdCWCpCPQeBQpD2N6tGjkoG1BbstkFNflllfenCw", + "protected": "eyJmb3JtYXRMZW5ndGgiOjU0NzgsImZvcm1hdFRhaWwiOiJDbjAiLCJ0aW1lIjoiMjAxNi0wNC0xOFQyMDo1NDo0MloifQ" + } + ] +} \ No newline at end of file diff --git a/vendor/github.com/containers/image/manifest/fixtures/v2s2.manifest.json b/vendor/github.com/containers/image/manifest/fixtures/v2s2.manifest.json new file mode 100644 index 0000000000..198da23f92 --- /dev/null +++ b/vendor/github.com/containers/image/manifest/fixtures/v2s2.manifest.json @@ -0,0 +1,26 @@ +{ + "schemaVersion": 2, + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "config": { + "mediaType": "application/vnd.docker.container.image.v1+json", + "size": 7023, + "digest": "sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7" + }, + "layers": [ + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 32654, + "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f" + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 16724, + "digest": "sha256:3c3a4604a545cdc127456d94e421cd355bca5b528f4a9c1905b15da2eb4a4c6b" + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 73109, + "digest": "sha256:ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736" + } + ] +} \ No newline at end of file diff --git a/vendor/github.com/containers/image/manifest/fixtures/v2s2nomime.manifest.json b/vendor/github.com/containers/image/manifest/fixtures/v2s2nomime.manifest.json new file mode 100644 index 0000000000..a0b06c233b --- /dev/null +++ b/vendor/github.com/containers/image/manifest/fixtures/v2s2nomime.manifest.json @@ -0,0 +1,10 @@ +{ + "schemaVersion": 2, + "config": { + "mediaType": "application/vnd.docker.container.image.v1+json", + "size": 7023, + "digest": "sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7" + }, + "layers": [ + ] +} diff --git a/vendor/github.com/containers/image/manifest/fixtures_info_test.go b/vendor/github.com/containers/image/manifest/fixtures_info_test.go new file mode 100644 index 0000000000..2607125be9 --- /dev/null +++ b/vendor/github.com/containers/image/manifest/fixtures_info_test.go @@ -0,0 +1,12 @@ +package manifest + +import "github.com/opencontainers/go-digest" + +const ( + // TestV2S2ManifestDigest is the Docker manifest digest of "v2s2.manifest.json" + TestDockerV2S2ManifestDigest = digest.Digest("sha256:20bf21ed457b390829cdbeec8795a7bea1626991fda603e0d01b4e7f60427e55") + // TestV2S1ManifestDigest is the Docker manifest digest of "v2s1.manifest.json" + TestDockerV2S1ManifestDigest = digest.Digest("sha256:077594da70fc17ec2c93cfa4e6ed1fcc26992851fb2c71861338aaf4aa9e41b1") + // TestV2S1UnsignedManifestDigest is the Docker manifest digest of "v2s1unsigned.manifest.json" + TestDockerV2S1UnsignedManifestDigest = digest.Digest("sha256:077594da70fc17ec2c93cfa4e6ed1fcc26992851fb2c71861338aaf4aa9e41b1") +) diff --git a/vendor/github.com/containers/image/manifest/manifest_test.go b/vendor/github.com/containers/image/manifest/manifest_test.go new file mode 100644 index 0000000000..78da71a292 --- /dev/null +++ b/vendor/github.com/containers/image/manifest/manifest_test.go @@ -0,0 +1,125 @@ +package manifest + +import ( + "io/ioutil" + "path/filepath" + "testing" + + "github.com/docker/libtrust" + "github.com/opencontainers/go-digest" + imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + digestSha256EmptyTar = "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" +) + +func TestGuessMIMEType(t *testing.T) { + cases := []struct { + path string + mimeType string + }{ + {"ociv1.manifest.json", imgspecv1.MediaTypeImageManifest}, + {"ociv1list.manifest.json", imgspecv1.MediaTypeImageManifestList}, + {"v2s2.manifest.json", DockerV2Schema2MediaType}, + {"v2list.manifest.json", DockerV2ListMediaType}, + {"v2s1.manifest.json", DockerV2Schema1SignedMediaType}, + {"v2s1-unsigned.manifest.json", DockerV2Schema1MediaType}, + {"v2s1-invalid-signatures.manifest.json", DockerV2Schema1SignedMediaType}, + {"v2s2nomime.manifest.json", DockerV2Schema2MediaType}, // It is unclear whether this one is legal, but we should guess v2s2 if anything at all. + {"unknown-version.manifest.json", ""}, + {"non-json.manifest.json", ""}, // Not a manifest (nor JSON) at all + } + + for _, c := range cases { + manifest, err := ioutil.ReadFile(filepath.Join("fixtures", c.path)) + require.NoError(t, err) + mimeType := GuessMIMEType(manifest) + assert.Equal(t, c.mimeType, mimeType, c.path) + } +} + +func TestDigest(t *testing.T) { + cases := []struct { + path string + expectedDigest digest.Digest + }{ + {"v2s2.manifest.json", TestDockerV2S2ManifestDigest}, + {"v2s1.manifest.json", TestDockerV2S1ManifestDigest}, + {"v2s1-unsigned.manifest.json", TestDockerV2S1UnsignedManifestDigest}, + } + for _, c := range cases { + manifest, err := ioutil.ReadFile(filepath.Join("fixtures", c.path)) + require.NoError(t, err) + actualDigest, err := Digest(manifest) + require.NoError(t, err) + assert.Equal(t, c.expectedDigest, actualDigest) + } + + manifest, err := ioutil.ReadFile("fixtures/v2s1-invalid-signatures.manifest.json") + require.NoError(t, err) + actualDigest, err := Digest(manifest) + assert.Error(t, err) + + actualDigest, err = Digest([]byte{}) + require.NoError(t, err) + assert.Equal(t, digest.Digest(digestSha256EmptyTar), actualDigest) +} + +func TestMatchesDigest(t *testing.T) { + cases := []struct { + path string + expectedDigest digest.Digest + result bool + }{ + // Success + {"v2s2.manifest.json", TestDockerV2S2ManifestDigest, true}, + {"v2s1.manifest.json", TestDockerV2S1ManifestDigest, true}, + // No match (switched s1/s2) + {"v2s2.manifest.json", TestDockerV2S1ManifestDigest, false}, + {"v2s1.manifest.json", TestDockerV2S2ManifestDigest, false}, + // Unrecognized algorithm + {"v2s2.manifest.json", digest.Digest("md5:2872f31c5c1f62a694fbd20c1e85257c"), false}, + // Mangled format + {"v2s2.manifest.json", digest.Digest(TestDockerV2S2ManifestDigest.String() + "abc"), false}, + {"v2s2.manifest.json", digest.Digest(TestDockerV2S2ManifestDigest.String()[:20]), false}, + {"v2s2.manifest.json", digest.Digest(""), false}, + } + for _, c := range cases { + manifest, err := ioutil.ReadFile(filepath.Join("fixtures", c.path)) + require.NoError(t, err) + res, err := MatchesDigest(manifest, c.expectedDigest) + require.NoError(t, err) + assert.Equal(t, c.result, res) + } + + manifest, err := ioutil.ReadFile("fixtures/v2s1-invalid-signatures.manifest.json") + require.NoError(t, err) + // Even a correct SHA256 hash is rejected if we can't strip the JSON signature. + res, err := MatchesDigest(manifest, digest.FromBytes(manifest)) + assert.False(t, res) + assert.Error(t, err) + + res, err = MatchesDigest([]byte{}, digest.Digest(digestSha256EmptyTar)) + assert.True(t, res) + assert.NoError(t, err) +} + +func TestAddDummyV2S1Signature(t *testing.T) { + manifest, err := ioutil.ReadFile("fixtures/v2s1-unsigned.manifest.json") + require.NoError(t, err) + + signedManifest, err := AddDummyV2S1Signature(manifest) + require.NoError(t, err) + + sig, err := libtrust.ParsePrettySignature(signedManifest, "signatures") + require.NoError(t, err) + signaturePayload, err := sig.Payload() + require.NoError(t, err) + assert.Equal(t, manifest, signaturePayload) + + _, err = AddDummyV2S1Signature([]byte("}this is invalid JSON")) + assert.Error(t, err) +} diff --git a/vendor/github.com/containers/image/oci/layout/oci_dest_test.go b/vendor/github.com/containers/image/oci/layout/oci_dest_test.go new file mode 100644 index 0000000000..9767f94f3a --- /dev/null +++ b/vendor/github.com/containers/image/oci/layout/oci_dest_test.go @@ -0,0 +1,61 @@ +package layout + +import ( + "os" + "testing" + + "github.com/containers/image/types" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// readerFromFunc allows implementing Reader by any function, e.g. a closure. +type readerFromFunc func([]byte) (int, error) + +func (fn readerFromFunc) Read(p []byte) (int, error) { + return fn(p) +} + +// TestPutBlobDigestFailure simulates behavior on digest verification failure. +func TestPutBlobDigestFailure(t *testing.T) { + const digestErrorString = "Simulated digest error" + const blobDigest = "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f" + + ref, tmpDir := refToTempOCI(t) + defer os.RemoveAll(tmpDir) + dirRef, ok := ref.(ociReference) + require.True(t, ok) + blobPath, err := dirRef.blobPath(blobDigest) + assert.NoError(t, err) + + firstRead := true + reader := readerFromFunc(func(p []byte) (int, error) { + _, err := os.Lstat(blobPath) + require.Error(t, err) + require.True(t, os.IsNotExist(err)) + if firstRead { + if len(p) > 0 { + firstRead = false + } + for i := 0; i < len(p); i++ { + p[i] = 0xAA + } + return len(p), nil + } + return 0, errors.Errorf(digestErrorString) + }) + + dest, err := ref.NewImageDestination(nil) + require.NoError(t, err) + defer dest.Close() + _, err = dest.PutBlob(reader, types.BlobInfo{Digest: blobDigest, Size: -1}) + assert.Error(t, err) + assert.Contains(t, digestErrorString, err.Error()) + err = dest.Commit() + assert.NoError(t, err) + + _, err = os.Lstat(blobPath) + require.Error(t, err) + require.True(t, os.IsNotExist(err)) +} diff --git a/vendor/github.com/containers/image/oci/layout/oci_transport_test.go b/vendor/github.com/containers/image/oci/layout/oci_transport_test.go new file mode 100644 index 0000000000..eeefd2cef5 --- /dev/null +++ b/vendor/github.com/containers/image/oci/layout/oci_transport_test.go @@ -0,0 +1,272 @@ +package layout + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/containers/image/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestTransportName(t *testing.T) { + assert.Equal(t, "oci", Transport.Name()) +} + +func TestTransportParseReference(t *testing.T) { + testParseReference(t, Transport.ParseReference) +} + +func TestTransportValidatePolicyConfigurationScope(t *testing.T) { + for _, scope := range []string{ + "/etc", + "/etc:notlatest", + "/this/does/not/exist", + "/this/does/not/exist:notlatest", + "/:strangecornercase", + } { + err := Transport.ValidatePolicyConfigurationScope(scope) + assert.NoError(t, err, scope) + } + + for _, scope := range []string{ + "relative/path", + "/", + "/double//slashes", + "/has/./dot", + "/has/dot/../dot", + "/trailing/slash/", + "/etc:invalid'tag!value@", + "/path:with/colons", + "/path:with/colons/and:tag", + } { + err := Transport.ValidatePolicyConfigurationScope(scope) + assert.Error(t, err, scope) + } +} + +func TestParseReference(t *testing.T) { + testParseReference(t, ParseReference) +} + +// testParseReference is a test shared for Transport.ParseReference and ParseReference. +func testParseReference(t *testing.T, fn func(string) (types.ImageReference, error)) { + tmpDir, err := ioutil.TempDir("", "oci-transport-test") + require.NoError(t, err) + defer os.RemoveAll(tmpDir) + + for _, path := range []string{ + "/", + "/etc", + tmpDir, + "relativepath", + tmpDir + "/thisdoesnotexist", + } { + for _, tag := range []struct{ suffix, tag string }{ + {":notlatest", "notlatest"}, + {"", "latest"}, + } { + input := path + tag.suffix + ref, err := fn(input) + require.NoError(t, err, input) + ociRef, ok := ref.(ociReference) + require.True(t, ok) + assert.Equal(t, path, ociRef.dir, input) + assert.Equal(t, tag.tag, ociRef.tag, input) + } + } + + _, err = fn(tmpDir + "/with:multiple:colons:and:tag") + assert.Error(t, err) + + _, err = fn(tmpDir + ":invalid'tag!value@") + assert.Error(t, err) +} + +func TestNewReference(t *testing.T) { + const tagValue = "tagValue" + + tmpDir, err := ioutil.TempDir("", "oci-transport-test") + require.NoError(t, err) + defer os.RemoveAll(tmpDir) + + ref, err := NewReference(tmpDir, tagValue) + require.NoError(t, err) + ociRef, ok := ref.(ociReference) + require.True(t, ok) + assert.Equal(t, tmpDir, ociRef.dir) + assert.Equal(t, tagValue, ociRef.tag) + + _, err = NewReference(tmpDir+"/thisparentdoesnotexist/something", tagValue) + assert.Error(t, err) + + _, err = NewReference(tmpDir+"/has:colon", tagValue) + assert.Error(t, err) + + _, err = NewReference(tmpDir, "invalid'tag!value@") + assert.Error(t, err) +} + +// refToTempOCI creates a temporary directory and returns an reference to it. +// The caller should +// defer os.RemoveAll(tmpDir) +func refToTempOCI(t *testing.T) (ref types.ImageReference, tmpDir string) { + tmpDir, err := ioutil.TempDir("", "oci-transport-test") + require.NoError(t, err) + ref, err = NewReference(tmpDir, "tagValue") + require.NoError(t, err) + return ref, tmpDir +} + +func TestReferenceTransport(t *testing.T) { + ref, tmpDir := refToTempOCI(t) + defer os.RemoveAll(tmpDir) + assert.Equal(t, Transport, ref.Transport()) +} + +func TestReferenceStringWithinTransport(t *testing.T) { + tmpDir, err := ioutil.TempDir("", "oci-transport-test") + require.NoError(t, err) + defer os.RemoveAll(tmpDir) + + for _, c := range []struct{ input, result string }{ + {"/dir1:notlatest", "/dir1:notlatest"}, // Explicit tag + {"/dir2", "/dir2:latest"}, // Default tag + } { + ref, err := ParseReference(tmpDir + c.input) + require.NoError(t, err, c.input) + stringRef := ref.StringWithinTransport() + assert.Equal(t, tmpDir+c.result, stringRef, c.input) + // Do one more round to verify that the output can be parsed, to an equal value. + ref2, err := Transport.ParseReference(stringRef) + require.NoError(t, err, c.input) + stringRef2 := ref2.StringWithinTransport() + assert.Equal(t, stringRef, stringRef2, c.input) + } +} + +func TestReferenceDockerReference(t *testing.T) { + ref, tmpDir := refToTempOCI(t) + defer os.RemoveAll(tmpDir) + assert.Nil(t, ref.DockerReference()) +} + +func TestReferencePolicyConfigurationIdentity(t *testing.T) { + ref, tmpDir := refToTempOCI(t) + defer os.RemoveAll(tmpDir) + + assert.Equal(t, tmpDir+":tagValue", ref.PolicyConfigurationIdentity()) + // A non-canonical path. Test just one, the various other cases are + // tested in explicitfilepath.ResolvePathToFullyExplicit. + ref, err := NewReference(tmpDir+"/.", "tag2") + require.NoError(t, err) + assert.Equal(t, tmpDir+":tag2", ref.PolicyConfigurationIdentity()) + + // "/" as a corner case. + ref, err = NewReference("/", "tag3") + require.NoError(t, err) + assert.Equal(t, "/:tag3", ref.PolicyConfigurationIdentity()) +} + +func TestReferencePolicyConfigurationNamespaces(t *testing.T) { + ref, tmpDir := refToTempOCI(t) + defer os.RemoveAll(tmpDir) + // We don't really know enough to make a full equality test here. + ns := ref.PolicyConfigurationNamespaces() + require.NotNil(t, ns) + assert.True(t, len(ns) >= 2) + assert.Equal(t, tmpDir, ns[0]) + assert.Equal(t, filepath.Dir(tmpDir), ns[1]) + + // Test with a known path which should exist. Test just one non-canonical + // path, the various other cases are tested in explicitfilepath.ResolvePathToFullyExplicit. + // + // It would be nice to test a deeper hierarchy, but it is not obvious what + // deeper path is always available in the various distros, AND is not likely + // to contains a symbolic link. + for _, path := range []string{"/etc/skel", "/etc/skel/./."} { + _, err := os.Lstat(path) + require.NoError(t, err) + ref, err := NewReference(path, "sometag") + require.NoError(t, err) + ns := ref.PolicyConfigurationNamespaces() + require.NotNil(t, ns) + assert.Equal(t, []string{"/etc/skel", "/etc"}, ns) + } + + // "/" as a corner case. + ref, err := NewReference("/", "tag3") + require.NoError(t, err) + assert.Equal(t, []string{}, ref.PolicyConfigurationNamespaces()) +} + +func TestReferenceNewImage(t *testing.T) { + ref, tmpDir := refToTempOCI(t) + defer os.RemoveAll(tmpDir) + _, err := ref.NewImage(nil) + assert.Error(t, err) +} + +func TestReferenceNewImageSource(t *testing.T) { + ref, tmpDir := refToTempOCI(t) + defer os.RemoveAll(tmpDir) + _, err := ref.NewImageSource(nil, nil) + assert.NoError(t, err) +} + +func TestReferenceNewImageDestination(t *testing.T) { + ref, tmpDir := refToTempOCI(t) + defer os.RemoveAll(tmpDir) + dest, err := ref.NewImageDestination(nil) + assert.NoError(t, err) + defer dest.Close() +} + +func TestReferenceDeleteImage(t *testing.T) { + ref, tmpDir := refToTempOCI(t) + defer os.RemoveAll(tmpDir) + err := ref.DeleteImage(nil) + assert.Error(t, err) +} + +func TestReferenceOCILayoutPath(t *testing.T) { + ref, tmpDir := refToTempOCI(t) + defer os.RemoveAll(tmpDir) + ociRef, ok := ref.(ociReference) + require.True(t, ok) + assert.Equal(t, tmpDir+"/oci-layout", ociRef.ociLayoutPath()) +} + +func TestReferenceBlobPath(t *testing.T) { + const hex = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + + ref, tmpDir := refToTempOCI(t) + defer os.RemoveAll(tmpDir) + ociRef, ok := ref.(ociReference) + require.True(t, ok) + bp, err := ociRef.blobPath("sha256:" + hex) + assert.NoError(t, err) + assert.Equal(t, tmpDir+"/blobs/sha256/"+hex, bp) +} + +func TestReferenceBlobPathInvalid(t *testing.T) { + const hex = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + + ref, tmpDir := refToTempOCI(t) + defer os.RemoveAll(tmpDir) + ociRef, ok := ref.(ociReference) + require.True(t, ok) + _, err := ociRef.blobPath(hex) + assert.Error(t, err) + assert.Contains(t, err.Error(), "unexpected digest reference "+hex) +} + +func TestReferenceDescriptorPath(t *testing.T) { + ref, tmpDir := refToTempOCI(t) + defer os.RemoveAll(tmpDir) + ociRef, ok := ref.(ociReference) + require.True(t, ok) + assert.Equal(t, tmpDir+"/refs/notlatest", ociRef.descriptorPath("notlatest")) +} diff --git a/vendor/github.com/containers/image/oci/oci.go b/vendor/github.com/containers/image/oci/oci.go new file mode 100644 index 0000000000..03607d3288 --- /dev/null +++ b/vendor/github.com/containers/image/oci/oci.go @@ -0,0 +1 @@ +package oci diff --git a/vendor/github.com/containers/image/openshift/openshift_transport_test.go b/vendor/github.com/containers/image/openshift/openshift_transport_test.go new file mode 100644 index 0000000000..5c589c1923 --- /dev/null +++ b/vendor/github.com/containers/image/openshift/openshift_transport_test.go @@ -0,0 +1,125 @@ +package openshift + +import ( + "testing" + + "github.com/containers/image/docker/reference" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + sha256digestHex = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + sha256digest = "@sha256:" + sha256digestHex +) + +func TestTransportName(t *testing.T) { + assert.Equal(t, "atomic", Transport.Name()) +} + +func TestTransportValidatePolicyConfigurationScope(t *testing.T) { + for _, scope := range []string{ + "registry.example.com/ns/stream" + sha256digest, + "registry.example.com/ns/stream:notlatest", + "registry.example.com/ns/stream", + "registry.example.com/ns", + "registry.example.com", + } { + err := Transport.ValidatePolicyConfigurationScope(scope) + assert.NoError(t, err, scope) + } + + for _, scope := range []string{ + "registry.example.com/too/deep/hierarchy", + "registry.example.com/ns/stream:tag1:tag2", + } { + err := Transport.ValidatePolicyConfigurationScope(scope) + assert.Error(t, err, scope) + } +} + +func TestNewReference(t *testing.T) { + // too many ns + 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.ParseNormalizedNamed("registry.example.com/ns/stream:tag") + require.NoError(t, err) + tagged, ok = r.(reference.NamedTagged) + require.True(t, ok) + _, err = NewReference(tagged) + assert.NoError(t, err) +} + +func TestParseReference(t *testing.T) { + // Success + ref, err := ParseReference("registry.example.com:8443/ns/stream:notlatest") + require.NoError(t, err) + osRef, ok := ref.(openshiftReference) + require.True(t, ok) + 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", reference.Domain(osRef.dockerReference)) + + // Components creating an invalid Docker Reference name + _, err = ParseReference("registry.example.com/ns/UPPERCASEISINVALID:notlatest") + assert.Error(t, err) + + _, err = ParseReference("registry.example.com/ns/stream:invalid!tag@value=") + assert.Error(t, err) +} + +func TestReferenceDockerReference(t *testing.T) { + ref, err := ParseReference("registry.example.com:8443/ns/stream:notlatest") + require.NoError(t, err) + dockerRef := ref.DockerReference() + require.NotNil(t, dockerRef) + assert.Equal(t, "registry.example.com:8443/ns/stream:notlatest", dockerRef.String()) +} + +func TestReferenceTransport(t *testing.T) { + ref, err := ParseReference("registry.example.com:8443/ns/stream:notlatest") + require.NoError(t, err) + assert.Equal(t, Transport, ref.Transport()) +} + +func TestReferenceStringWithinTransport(t *testing.T) { + ref, err := ParseReference("registry.example.com:8443/ns/stream:notlatest") + require.NoError(t, err) + assert.Equal(t, "registry.example.com:8443/ns/stream:notlatest", ref.StringWithinTransport()) + // We should do one more round to verify that the output can be parsed, to an equal value, + // but that is untested because it depends on per-user configuration. +} + +func TestReferencePolicyConfigurationIdentity(t *testing.T) { + // Just a smoke test, the substance is tested in policyconfiguration.TestDockerReference. + ref, err := ParseReference("registry.example.com:8443/ns/stream:notlatest") + require.NoError(t, err) + assert.Equal(t, "registry.example.com:8443/ns/stream:notlatest", ref.PolicyConfigurationIdentity()) +} + +func TestReferencePolicyConfigurationNamespaces(t *testing.T) { + // Just a smoke test, the substance is tested in policyconfiguration.TestDockerReference. + ref, err := ParseReference("registry.example.com:8443/ns/stream:notlatest") + require.NoError(t, err) + assert.Equal(t, []string{ + "registry.example.com:8443/ns/stream", + "registry.example.com:8443/ns", + "registry.example.com:8443", + }, ref.PolicyConfigurationNamespaces()) +} + +// openshiftReference.NewImage, openshiftReference.NewImageSource, openshiftReference.NewImageDestination untested because they depend +// on per-user configuration when initializing httpClient. + +func TestReferenceDeleteImage(t *testing.T) { + ref, err := ParseReference("registry.example.com:8443/ns/stream:notlatest") + require.NoError(t, err) + err = ref.DeleteImage(nil) + assert.Error(t, err) +} diff --git a/vendor/github.com/containers/image/pkg/compression/compression_test.go b/vendor/github.com/containers/image/pkg/compression/compression_test.go new file mode 100644 index 0000000000..2dd429317a --- /dev/null +++ b/vendor/github.com/containers/image/pkg/compression/compression_test.go @@ -0,0 +1,86 @@ +package compression + +import ( + "bytes" + "io" + "io/ioutil" + "os" + "testing" + + "github.com/pkg/errors" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDetectCompression(t *testing.T) { + cases := []struct { + filename string + unimplemented bool + }{ + {"fixtures/Hello.uncompressed", false}, + {"fixtures/Hello.gz", false}, + {"fixtures/Hello.bz2", false}, + {"fixtures/Hello.xz", true}, + } + + // The original stream is preserved. + for _, c := range cases { + originalContents, err := ioutil.ReadFile(c.filename) + require.NoError(t, err, c.filename) + + stream, err := os.Open(c.filename) + require.NoError(t, err, c.filename) + defer stream.Close() + + _, updatedStream, err := DetectCompression(stream) + require.NoError(t, err, c.filename) + + updatedContents, err := ioutil.ReadAll(updatedStream) + require.NoError(t, err, c.filename) + assert.Equal(t, originalContents, updatedContents, c.filename) + } + + // The correct decompressor is chosen, and the result is as expected. + for _, c := range cases { + stream, err := os.Open(c.filename) + require.NoError(t, err, c.filename) + defer stream.Close() + + decompressor, updatedStream, err := DetectCompression(stream) + require.NoError(t, err, c.filename) + + var uncompressedStream io.Reader + switch { + case decompressor == nil: + uncompressedStream = updatedStream + case c.unimplemented: + _, err := decompressor(updatedStream) + assert.Error(t, err) + continue + default: + s, err := decompressor(updatedStream) + require.NoError(t, err) + uncompressedStream = s + } + + uncompressedContents, err := ioutil.ReadAll(uncompressedStream) + require.NoError(t, err, c.filename) + assert.Equal(t, []byte("Hello"), uncompressedContents, c.filename) + } + + // Empty input is handled reasonably. + decompressor, updatedStream, err := DetectCompression(bytes.NewReader([]byte{})) + require.NoError(t, err) + assert.Nil(t, decompressor) + updatedContents, err := ioutil.ReadAll(updatedStream) + require.NoError(t, err) + assert.Equal(t, []byte{}, updatedContents) + + // Error reading input + reader, writer := io.Pipe() + defer reader.Close() + 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 0000000000..5ab2f8a432 --- /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_test.go b/vendor/github.com/containers/image/signature/docker_test.go new file mode 100644 index 0000000000..6d2f0b3d71 --- /dev/null +++ b/vendor/github.com/containers/image/signature/docker_test.go @@ -0,0 +1,101 @@ +package signature + +import ( + "io/ioutil" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSignDockerManifest(t *testing.T) { + mech, err := newGPGSigningMechanismInDirectory(testGPGHomeDirectory) + require.NoError(t, err) + manifest, err := ioutil.ReadFile("fixtures/image.manifest.json") + require.NoError(t, err) + + // Successful signing + signature, err := SignDockerManifest(manifest, TestImageSignatureReference, mech, TestKeyFingerprint) + require.NoError(t, err) + + verified, err := VerifyDockerManifestSignature(signature, manifest, TestImageSignatureReference, mech, TestKeyFingerprint) + assert.NoError(t, err) + assert.Equal(t, TestImageSignatureReference, verified.DockerReference) + assert.Equal(t, TestImageManifestDigest, verified.DockerManifestDigest) + + // Error computing Docker manifest + invalidManifest, err := ioutil.ReadFile("fixtures/v2s1-invalid-signatures.manifest.json") + require.NoError(t, err) + _, err = SignDockerManifest(invalidManifest, TestImageSignatureReference, mech, TestKeyFingerprint) + assert.Error(t, err) + + // Error creating blob to sign + _, err = SignDockerManifest(manifest, "", mech, TestKeyFingerprint) + assert.Error(t, err) + + // Error signing + _, err = SignDockerManifest(manifest, TestImageSignatureReference, mech, "this fingerprint doesn't exist") + assert.Error(t, err) +} + +func TestVerifyDockerManifestSignature(t *testing.T) { + mech, err := newGPGSigningMechanismInDirectory(testGPGHomeDirectory) + require.NoError(t, err) + manifest, err := ioutil.ReadFile("fixtures/image.manifest.json") + require.NoError(t, err) + signature, err := ioutil.ReadFile("fixtures/image.signature") + require.NoError(t, err) + + // Successful verification + sig, err := VerifyDockerManifestSignature(signature, manifest, TestImageSignatureReference, mech, TestKeyFingerprint) + require.NoError(t, err) + assert.Equal(t, TestImageSignatureReference, sig.DockerReference) + assert.Equal(t, TestImageManifestDigest, sig.DockerManifestDigest) + + // Verification using a different canonicalization of TestImageSignatureReference + sig, err = VerifyDockerManifestSignature(signature, manifest, "docker.io/"+TestImageSignatureReference, mech, TestKeyFingerprint) + require.NoError(t, err) + assert.Equal(t, TestImageSignatureReference, sig.DockerReference) + assert.Equal(t, TestImageManifestDigest, sig.DockerManifestDigest) + + // For extra paranoia, test that we return nil data on error. + + // Invalid docker reference on input + sig, err = VerifyDockerManifestSignature(signature, manifest, "UPPERCASEISINVALID", mech, TestKeyFingerprint) + assert.Error(t, err) + assert.Nil(t, sig) + + // Error computing Docker manifest + invalidManifest, err := ioutil.ReadFile("fixtures/v2s1-invalid-signatures.manifest.json") + require.NoError(t, err) + sig, err = VerifyDockerManifestSignature(signature, invalidManifest, TestImageSignatureReference, mech, TestKeyFingerprint) + assert.Error(t, err) + assert.Nil(t, sig) + + // Error verifying signature + corruptSignature, err := ioutil.ReadFile("fixtures/corrupt.signature") + sig, err = VerifyDockerManifestSignature(corruptSignature, manifest, TestImageSignatureReference, mech, TestKeyFingerprint) + assert.Error(t, err) + assert.Nil(t, sig) + + // Key fingerprint mismatch + sig, err = VerifyDockerManifestSignature(signature, manifest, TestImageSignatureReference, mech, "unexpected fingerprint") + assert.Error(t, err) + assert.Nil(t, sig) + + // Invalid reference in the signature + invalidReferenceSignature, err := ioutil.ReadFile("fixtures/invalid-reference.signature") + sig, err = VerifyDockerManifestSignature(invalidReferenceSignature, manifest, TestImageSignatureReference, mech, TestKeyFingerprint) + assert.Error(t, err) + assert.Nil(t, sig) + + // Docker reference mismatch + sig, err = VerifyDockerManifestSignature(signature, manifest, "example.com/doesnt/match", mech, TestKeyFingerprint) + assert.Error(t, err) + assert.Nil(t, sig) + + // Docker manifest digest mismatch + sig, err = VerifyDockerManifestSignature(signature, []byte("unexpected manifest"), TestImageSignatureReference, mech, TestKeyFingerprint) + assert.Error(t, err) + assert.Nil(t, sig) +} diff --git a/vendor/github.com/containers/image/signature/fixtures/.gitignore b/vendor/github.com/containers/image/signature/fixtures/.gitignore new file mode 100644 index 0000000000..3730cb25a6 --- /dev/null +++ b/vendor/github.com/containers/image/signature/fixtures/.gitignore @@ -0,0 +1,4 @@ +/*.gpg~ +/.gpg-v21-migrated +/private-keys-v1.d +/random_seed diff --git a/vendor/github.com/containers/image/signature/fixtures/corrupt.signature b/vendor/github.com/containers/image/signature/fixtures/corrupt.signature new file mode 100644 index 0000000000000000000000000000000000000000..95c29087125f0b84a7dc03ad65f2e6b2db4f996f GIT binary patch literal 412 zcmV;N0b~B70h_?f%)r5TyXccd_m-R!jHeF-Br$}&`th(@DY+=KBr`cNN6D&MDKjNC zuOzdi62wl)PtHy)(k)6&OD#&xOHNg?QYuL;F3HSG*UwGN%S;2Zm1=dAGIJBtQ<2oe z}j0{s#Oiay_j4cf;j4YE=l2TKXEzB)V6U~!S6AjIb z%q%Sp(^3-642)9^QVa}}Oj6C$%nVG7%u`KGL6((N7J%H5SdyQcnXHhUpI4HYnU`9m zP@I{bmsnC-lnPRmUr>^np9k?!a#1Q!aS=j~63~#$T%f}ea|@KL3{6ZejEpVK4UKDS zJLa%3Fmf=ku`+?eg@KDx02W$NSD63DeycibxIdt0URPzmL;uWzVE5D4c7)8*<(8R~ zSn@1YEb+oJ!Kl77mX)6yrv!YrV9zQ$mmANnr}X;z%NDZ?-VUoWIyq-Ti3+v#79^%&%*SuC7PcG4elSu2almYHV9wlEtw+YEQ+i Gxc~}fWX4wj literal 0 HcmV?d00001 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 0000000000..3dee14b4a8 --- /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 0000000000..f010fd4c41 --- /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 0000000000..ff7d2ffadf --- /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 0000000000..b27cdc4585 --- /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 0000000000..f010fd4c41 --- /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/manifest.json b/vendor/github.com/containers/image/signature/fixtures/dir-img-modified-manifest/manifest.json new file mode 100644 index 0000000000..82fde3811e --- /dev/null +++ b/vendor/github.com/containers/image/signature/fixtures/dir-img-modified-manifest/manifest.json @@ -0,0 +1,27 @@ +{ + "schemaVersion": 2, + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "config": { + "mediaType": "application/vnd.docker.container.image.v1+json", + "size": 7023, + "digest": "sha256:b5b2b2c507a0944348e0303114d8d93aaaa081732b86451d9bce1f432a537bc7" + }, + "layers": [ + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 32654, + "digest": "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f" + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 16724, + "digest": "sha256:3c3a4604a545cdc127456d94e421cd355bca5b528f4a9c1905b15da2eb4a4c6b" + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 73109, + "digest": "sha256:ec4b8955958665577945c89419d1af06b5f7636b4ac3da7f12184802ad867736" + } + ], + "extra": "this manifest has been modified" +} 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 0000000000..f010fd4c41 --- /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 0000000000..f010fd4c41 --- /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 0000000000..ff7d2ffadf --- /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 0000000000..ff7d2ffadf --- /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 0000000000..f010fd4c41 --- /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-2/signature-2 b/vendor/github.com/containers/image/signature/fixtures/dir-img-valid-2/signature-2 new file mode 100644 index 0000000000000000000000000000000000000000..dbba8f422811e20ff5077fb33bed1c7b0c49484c GIT binary patch literal 425 zcmV;a0apH_0h_?f%)r5TyXccd_m-R!jHeGICNYHjEHbWEN-oMQ$xKenQL?I5%1lYk zE6FUW1hG@{le1Hcbc<5cQj1dal2eteluA;IOEUA)^>Y*RGSh%;tDHoTkW#IVQf6*q zdMc7Om=fKT%yf_vE2ZL$L?cr(DuwBQr}&!?ct{GXvvPgA@b9B$HJ0G&2JeBlA>KQ;=mPl?5Q@B$njoW+p2n=jW9q zX6B_9DHLa>=Ovbu7Nvp|=I25Dm0XkxR9uA6W1wfKXK1XOl3Jz&G%hn2=)}a_ z0wpU$6Eh$*FfcW&t?ih@!obMEz{bi13Md9HP61f(DZgc5SRvv3>p{5-e{}20e`Y_P zE4`Nw^4q4zQny`lU4iZWtgzB)=G`B(-b`J^858E^%~GoIY*RGSh%;tDHoTkW#IVQf6*q zdMc7Om=fKT%yf_vE2ZL$L?cr(DuwBQr}&!?ct{GXvvPgA@b9B$HJ0G&2JeBlA>KQ;=mPl?5Q@B$njoW+p2n=jW9q zX6B_9DHLa>=Ovbu7Nvp|=I25Dm0XkxR9uA6W1wfKXK1XOl3Jz&G%hn2=)}a_ z0wpU$6EhQIOAAX=liJ#jIV=o}91Lu%OrU^b;Nld31z)%@^M8+d`p;)&aHfmy<#WsS zIK8RVeU8%X-WmB$J1dW|Oj*WaDl(;Svh=AfLN^vEid+Zb!ad9RSyT*L_Wt^~Axd+n#%2{`OGEdQtD-cGRZZ8c zY+fRC#P6j4IX~S?x?aBCx?X3!{X#bdX?g1Eojl`x{k-p)(>~0bre>8+F)9|m96a&L z;U^`*MwcDE)=cR#7V7mmyQYU{(xo1r;9wu0vy*#td3AbpCI?Rn3ZATcM%P#P?BpO} z-m^VtbaZ(y2ML|!=?Ufy;^CR>6Lj|S86KaWGr_*Tj?<=wh&9}q7%^%7?3rsO%$XlP zX~xXyQA+~m%$zqpA}DGKSJ0IBfJrmwvwsXI3e*ZH6m;qrx-j8P%!0#0#oSZY@c8=Z z_VnnUV7!3<(if#Ea$AFo?&mA%7$L3&(x>VD_{XPOee zU%p<}a^}+0X)`BBe9{Tk`FP0a;EH|5)27asUd~dpJ>k5T^>pd;>z04{_3O0QW}bU| ztBoEn>Zx9Hf4Xh)PPH@lAOBYkDxWps_g~hl+s)puzWXM8{4#y3r=f|LB^X42wsjX0S#L0Dz;7o&W#< literal 0 HcmV?d00001 diff --git a/vendor/github.com/containers/image/signature/fixtures/expired.signature b/vendor/github.com/containers/image/signature/fixtures/expired.signature new file mode 100644 index 0000000000000000000000000000000000000000..c609c37927a230b5ce693cbfef12137ebc16f49a GIT binary patch literal 217 zcmV;~04D#V0h_?f%)rEWyXccd_m-R!jHeZo7{Xrto)eOhS*%c;nVy$eQd*R%0OX`r z6l4~qrs#2XEMj3`WMbf9WdbQ=ZDwF#U}WIp6o8pAH;jd$|8-gKw%b`J-*V)0-rN5B zT~+Oz_niNhpAx@yfJ^i1j+&{_#doA)0$hB)hjdpwZTz5NwrhWSz}$o;BV)1W+KVn9 zpZaaCOj|?b>}#cWZvOK;`p}j0{s#Oiay_j4cf;j4YE=l2TKXEzB)V6U~!S6AjIb z%q%Sp(^3-642)9^QVa}}Oj6C$%nVG7%u`KGL6((N7J%H5SdyQcnXHhUpI4HYnU`9m zP@I{bmsnC-lnPRmUr>^np9k?!a#1Q!aS=j~63~#$T%f}ea|@KL3{6ZejEpVK4UKDS zJLa%3Fmf=ku`+?eg@KDx02W$NSD63DeycibxIdt0URPzmL;uWzVE5D4c7)8*<(8R~ zSn@1YEb+oJ!Kl77mX)6yrv!YrV9zQ$mmANnr}X;z%NDZ?-VUoWIyq-Ti3+v#79^%&%*SuC7PcG4elSu2an1YHV9wlEtw+YEQ+i Fxd5g!#>fBw literal 0 HcmV?d00001 diff --git a/vendor/github.com/containers/image/signature/fixtures/invalid-blob.signature b/vendor/github.com/containers/image/signature/fixtures/invalid-blob.signature new file mode 100644 index 0000000000000000000000000000000000000000..c8db18cac28c8c57b861f62b99830c18ef4aa510 GIT binary patch literal 199 zcmV;&0671n0h_?f%)rEWyXccd_m-R!jHktu7{XqCz8jK}S*!p=dHE#@Ucvr;Tpe>* z7#Nut*jSlB(hOXj0x&(domm)~uf34Xx%_Uu&vOQcL&v4q*3C02`tm35SJUO5C5rF& zIj##8W#4&Q)!MzxvqdWO$_G_(C#9-2mzP);3!F`4@{d&57HePHC#dnRBt?|tho57H z-tRP{Lu(FSiinLae#_Tpzo$ZCYF~L+Y2O8---Y(EA>C#3m)r?oB)8|Y)$B^PCuWF{x(C|Ol2Wu~O& zm1LGwg4ikf$=Rtzx<#pJsYR)I$*D?KN}&M(u0hU@!LFXco_=ADKAtX0wK__fxryni zNIG&8^D@&?i%WDXZSvW2;& zX`*>jYNDZ;k(s5XVOmO}nSpVtL5hK4l1Zw0nwf!#k$I}ADaf*t$^wu}5=-)PGm{mP z^Ycm)GxJi56pAy`^Abx+i&8;~@(W5b^Yb8HN-jzTDlS6kQ34v0nG1ASVs3$wm7$5L zg^{tPxuJ1wEmy}J76wKp1~yhEP|z@NaSFf!%wQq&|8$Q=T^&K+AJ_UH3HK}x-1sWU zqsKo#@`~v*(akr+t~AADy$M>hRG>mo{QS>(8x;N!@|JG#K6YN1oAEe7pDL$5KP}Q|CQxEdf?iT z>oJuvx30at8&fN@>6W#1n%~38%QyP#Cj0Gwyw4%JuSg;F_u*r=s>SCly6E}jRFg&1 zFWq%aXOwoQ<}TcQdH!|7wT=^6-&tGS@;)RI9^v5X_^{!T;H`fyix$@Xe1Ei|_|g>9 dKT&fXzc>`SoomwQP+E4bv`?bx;PvLU_W&nc%{2f3 literal 0 HcmV?d00001 diff --git a/vendor/github.com/containers/image/signature/fixtures/policy.json b/vendor/github.com/containers/image/signature/fixtures/policy.json new file mode 100644 index 0000000000..8e39e5c626 --- /dev/null +++ b/vendor/github.com/containers/image/signature/fixtures/policy.json @@ -0,0 +1,96 @@ +{ + "default": [ + { + "type": "reject" + } + ], + "transports": { + "dir": { + "": [ + { + "type": "insecureAcceptAnything" + } + ] + }, + "docker": { + "example.com/playground": [ + { + "type": "insecureAcceptAnything" + } + ], + "example.com/production": [ + { + "type": "signedBy", + "keyType": "GPGKeys", + "keyPath": "/keys/employee-gpg-keyring" + } + ], + "example.com/hardened": [ + { + "type": "signedBy", + "keyType": "GPGKeys", + "keyPath": "/keys/employee-gpg-keyring", + "signedIdentity": { + "type": "matchRepository" + } + }, + { + "type": "signedBy", + "keyType": "signedByGPGKeys", + "keyPath": "/keys/public-key-signing-gpg-keyring", + "signedIdentity": { + "type": "matchExact" + } + }, + { + "type": "signedBaseLayer", + "baseLayerIdentity": { + "type": "exactRepository", + "dockerRepository": "registry.access.redhat.com/rhel7/rhel" + } + } + ], + "example.com/hardened-x509": [ + { + "type": "signedBy", + "keyType": "X509Certificates", + "keyPath": "/keys/employee-cert-file", + "signedIdentity": { + "type": "matchRepository" + } + }, + { + "type": "signedBy", + "keyType": "signedByX509CAs", + "keyPath": "/keys/public-key-signing-ca-file" + } + ], + "registry.access.redhat.com": [ + { + "type": "signedBy", + "keyType": "signedByGPGKeys", + "keyPath": "/keys/RH-key-signing-key-gpg-keyring", + "signedIdentity": { + "type": "matchRepoDigestOrExact" + } + } + ], + "bogus/key-data-example": [ + { + "type": "signedBy", + "keyType": "signedByGPGKeys", + "keyData": "bm9uc2Vuc2U=" + } + ], + "bogus/signed-identity-example": [ + { + "type": "signedBaseLayer", + "baseLayerIdentity": { + "type": "exactReference", + "dockerReference": "registry.access.redhat.com/rhel7/rhel:latest" + } + } + ] + } + } +} \ No newline at end of file diff --git a/vendor/github.com/containers/image/signature/fixtures/public-key.gpg b/vendor/github.com/containers/image/signature/fixtures/public-key.gpg new file mode 100644 index 0000000000..46901d58db --- /dev/null +++ b/vendor/github.com/containers/image/signature/fixtures/public-key.gpg @@ -0,0 +1,19 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1 + +mI0EVurzqQEEAL3qkFq4K2URtSWVDYnQUNA9HdM9sqS2eAWfqUFMrkD5f+oN+LBL +tPyaE5GNLA0vXY7nHAM2TeM8ijZ/eMP17Raj64JL8GhCymL3wn2jNvb9XaF0R0s6 +H0IaRPPu45A3SnxLwm4Orc/9Z7/UxtYjKSg9xOaTiVPzJgaf5Vm4J4ApABEBAAG0 +EnNrb3BlbyB0ZXN0aW5nIGtleYi4BBMBAgAiBQJW6vOpAhsDBgsJCAcDAgYVCAIJ +CgsEFgIDAQIeAQIXgAAKCRDbcvIYi7RsyBbOBACgJFiKDlQ1UyvsNmGqJ7D0OpbS +1OppJlradKgZXyfahFswhFI+7ZREvELLHbinq3dBy5cLXRWzQKdJZNHknSN5Tjf2 +0ipVBQuqpcBo+dnKiG4zH6fhTri7yeTZksIDfsqlI6FXDOdKLUSnahagEBn4yU+x +jHPvZk5SuuZv56A45biNBFbq86kBBADIC/9CsAlOmRALuYUmkhcqEjuFwn3wKz2d +IBjzgvro7zcVNNCgxQfMEjcUsvEh5cx13G3QQHcwOKy3M6Bv6VMhfZjd+1P1el4P +0fJS8GFmhWRBknMN8jFsgyohQeouQ798RFFv94KszfStNnr/ae8oao5URmoUXSCa +/MdUxn0YKwARAQABiJ8EGAECAAkFAlbq86kCGwwACgkQ23LyGIu0bMjUywQAq0dn +lUpDNSoLTcpNWuVvHQ7c/qmnE4TyiSLiRiAywdEWA6gMiyhUUucuGsEhMFP1WX1k +UNwArZ6UG7BDOUsvngP7jKGNqyUOQrq1s/r8D+0MrJGOWErGLlfttO2WeoijECkI +5qm8cXzAra3Xf/Z3VjxYTKSnNu37LtZkakdTdYE= +=tJAt +-----END PGP PUBLIC KEY BLOCK----- diff --git a/vendor/github.com/containers/image/signature/fixtures/pubring.gpg b/vendor/github.com/containers/image/signature/fixtures/pubring.gpg new file mode 100644 index 0000000000000000000000000000000000000000..2d922b42d8820a545a3326e3d6c9268ea68f1743 GIT binary patch literal 661 zcmbQy%M$kL^GZe*hP|&QMD5T{72K*imACUkzy({`%eI@AY^z|MztYiXox{)iSG+$q zcyIYLOL$_h4zGS}-*XveGvCKHU1s$ahrhlRTl~7o`$LA)sig0RY8RV*`y0Ek#NFFU z-bu>k^Sj3r%)M&759RT#J^wd-|CM9clr=SMk35^)8T?s|ZT{299qJ9541$adj9Y|? zv-1m5^A$=`i%T-|(iO5(D?4_u2s1JC9X zB*(}k-oU`cDR8^!lSKEHoD*W_SQr+lM0D|mmJ8j_fn<>D(vsG=9xR#K0>!I2Y+O~5QBtAF&dhy;|)a1f~qwHsd z%tbbRRD61-^iJ*thjIgpHQS9BhUn6T%0`4%Zoi{~#gR`Z#{ygU2JqvqRtMWPR+PeAIAO5#IYbN$ZcpcLVf4k-F sw5pE90-7ApR_-aRIk0x^_4;q+VKxyyOO~6x{jGN`CCfdyv=J0$0QSHl5C8xG literal 0 HcmV?d00001 diff --git a/vendor/github.com/containers/image/signature/fixtures/secring.gpg b/vendor/github.com/containers/image/signature/fixtures/secring.gpg new file mode 100644 index 0000000000000000000000000000000000000000..36cf0f7db274dd9b56ba91c194d5350734e08428 GIT binary patch literal 1325 zcmV+|1=9MJ0oVjq>hq}q1OUD2kXpDaWf8R{l?{o|P|!Uc(>=1Jws-}fsX2^_ z4fwE2wEUVAk&P@3FI|r390N8@<2;Hse|W?7?G~f!f=lpdLds(I!hNGQ_WfOL^X}u2H%fdOmNU!X%tQ<$bc*q|40Egb1Igsfi{2?M}wiUfaS1WRqe*@a`sDu0)qX% zmUDY2#OvRC&YzJYaSUn5cYqevqeomDMN0)I65gO2%>|jK_hE)r`1EUH50xnzjGSQ3 zep63hUy60oF_BzEjbe!-8?qw!b4oNZ@X!Xx5% zXy_2FDSaN^9WmvOCH|27rkRs=-|{Rbd$6lHZV1^C0nNZ~0s!e@=<~WT_ngHa(M*DW z<(S4GE@uu`kF2=Jyx5__8p=|h%u~YX_J$0B1lGXEdWH&5cIQY3%K)FdOPK$pS-og+H|NHUnkmxTQG!DKJAo5yh6(zxTmXkLCcp5T@|xHr%7bdiCR!UKNFr6ZwN4ChKMM5k&Npb#1O$xpG2 zbMIzOQo81E=b$*{umS)8odMVcR_gPq0R#ZZ3;#l}2~L?13%P|Rk{2oxJB7l1@GCu? zAQ4P<{nVTo$g~@2!5Gv)8j;Xuy(the7-6U)VjFq>3)sv<7K` zHY0rt*UIprVE@!WXYT2+%r>;N+0`y>y2ly-(0(E&0PyW8X4y_~T}gec@nK4x)2(~< zvj#YS-0G@ia=v!H|t5~K?_(!@&dcH>~Zofu&P0LY_}Go-=2=87?8)!Z=iJ_2#_ zm%RLP7t5Q8Pbb&JS(Hj5kZQ`eo7v9=M@lAg)ru@Gizzjv zg)^e!P2=GL0OBIl)QcIO<~UT(dTc!~4}i8s2CmIHs~185vcC={MLw<;Pw^K9Vara( z`8LSbO(t#m6gdjp3+W1%-D%+9(4e-6p9B~I0ssjG0#@qtsRA1e0162Z+j8<4i?nRW z)XM|_t4C**N<%d&3r)&RTIFvY4&45!rxS$oi6Y`gATq(x76YgZizrl5=Pnw-Auv<* zS$$+s+yJeflpC-^IZH2|1N)4jjjJUNLb|oH`uq>=46KokSW3n&SM9XzmU@Vz5Ge@e jsl0J~z^$#WZgBm}o4jK?>)JO{#9){NIGc;HQueI5|Ym@0ykJ&R@HCYlGDDsHF;bmYbOT2oe zB5>x8H_pqJH(RcK>NbBTLLSEAVUTSy_;z;Nf^@MbA_s35eUj+jl5>I$BCQBh#lV2w KJg7Q^G6nz^0WVts literal 0 HcmV?d00001 diff --git a/vendor/github.com/containers/image/signature/fixtures/unknown-key.signature b/vendor/github.com/containers/image/signature/fixtures/unknown-key.signature new file mode 100644 index 0000000000000000000000000000000000000000..2277b130dab415c59a76679a5ad1b1d252e98908 GIT binary patch literal 558 zcmV+}0@3}W0h_?f%)r6;)IC>F;6ep6`5C#FEmYRFI(=Id~ zsyD2^7m{SL`O>k)B`-?0L@ZL}y()d};O`xMqGhb%F57k~E|suqk3T$<)gj}0*ap{D zP2IHKDMlR5Gxub_Kl@<)%f}1!o6TNIx?F!IJ5E z){OaV0yD&Jzxz99M$RSs71wGNzAxRsP|#lH=4m$Oh*Jw+sOCSFsqK$otN&uzd8ao0 zYvO%%bnhRht0={SYdwML-yJ0YG;ONuD_Xf!KAlG7wQmoPY{nudiT3 zitH@^x*Qi~$Bde0*;&WC`}5t4+40=~UWb8bMH{i|y1PS81No)7GO6==#dEcBP0&^q zsh5OecAzki)oL)koMHZO;!>|R34K{)ePC``zD`b4a j(M%(Tt)&E2LGYsEP3!`lNoKD27}FAGJhGjce;*_DYeaC- literal 0 HcmV?d00001 diff --git a/vendor/github.com/containers/image/signature/fixtures/unsigned-literal.signature b/vendor/github.com/containers/image/signature/fixtures/unsigned-literal.signature new file mode 100644 index 0000000000000000000000000000000000000000..9b660cb214e36d8ccfc5347153902aa8f64d91b8 GIT binary patch literal 47 zcmV+~0MP%V0Xwag#1QuC!}O4h%wmP&lA_GKbOj(QFTX?~F}b8PF(;=|p*S-=FEvGv F3jjf96tDmQ literal 0 HcmV?d00001 diff --git a/vendor/github.com/containers/image/signature/fixtures/v2s1-invalid-signatures.manifest.json b/vendor/github.com/containers/image/signature/fixtures/v2s1-invalid-signatures.manifest.json new file mode 100644 index 0000000000..8dfefd4e1b --- /dev/null +++ b/vendor/github.com/containers/image/signature/fixtures/v2s1-invalid-signatures.manifest.json @@ -0,0 +1,11 @@ +{ + "schemaVersion": 1, + "name": "mitr/buxybox", + "tag": "latest", + "architecture": "amd64", + "fsLayers": [ + ], + "history": [ + ], + "signatures": 1 +} diff --git a/vendor/github.com/containers/image/signature/fixtures_info_test.go b/vendor/github.com/containers/image/signature/fixtures_info_test.go new file mode 100644 index 0000000000..a44a6b7364 --- /dev/null +++ b/vendor/github.com/containers/image/signature/fixtures_info_test.go @@ -0,0 +1,14 @@ +package signature + +import "github.com/opencontainers/go-digest" + +const ( + // TestImageManifestDigest is the Docker manifest digest of "image.manifest.json" + TestImageManifestDigest = digest.Digest("sha256:20bf21ed457b390829cdbeec8795a7bea1626991fda603e0d01b4e7f60427e55") + // TestImageSignatureReference is the Docker image reference signed in "image.signature" + TestImageSignatureReference = "testing/manifest" + // TestKeyFingerprint is the fingerprint of the private key in this directory. + TestKeyFingerprint = "1D8230F6CDB6A06716E414C1DB72F2188BB46CC8" + // TestKeyShortID is the short ID of the private key in this directory. + TestKeyShortID = "DB72F2188BB46CC8" +) diff --git a/vendor/github.com/containers/image/signature/json_test.go b/vendor/github.com/containers/image/signature/json_test.go new file mode 100644 index 0000000000..fe63a1cd1e --- /dev/null +++ b/vendor/github.com/containers/image/signature/json_test.go @@ -0,0 +1,182 @@ +package signature + +import ( + "encoding/json" + "fmt" + "math" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type mSI map[string]interface{} // To minimize typing the long name + +// A short-hand way to get a JSON object field value or panic. No error handling done, we know +// what we are working with, a panic in a test is good enough, and fitting test cases on a single line +// is a priority. +func x(m mSI, fields ...string) mSI { + for _, field := range fields { + // Not .(mSI) because type assertion of an unnamed type to a named type always fails (the types + // are not "identical"), but the assignment is fine because they are "assignable". + m = m[field].(map[string]interface{}) + } + return m +} + +func TestValidateExactMapKeys(t *testing.T) { + // Empty map and keys + err := validateExactMapKeys(mSI{}) + assert.NoError(t, err) + + // Success + err = validateExactMapKeys(mSI{"a": nil, "b": 1}, "b", "a") + assert.NoError(t, err) + + // Extra map keys + err = validateExactMapKeys(mSI{"a": nil, "b": 1}, "a") + assert.Error(t, err) + + // Extra expected keys + err = validateExactMapKeys(mSI{"a": 1}, "b", "a") + assert.Error(t, err) + + // Unexpected key values + err = validateExactMapKeys(mSI{"a": 1}, "b") + assert.Error(t, err) +} + +func TestInt64Field(t *testing.T) { + // Field not found + _, err := int64Field(mSI{"a": "x"}, "b") + assert.Error(t, err) + + // Field has a wrong type + _, err = int64Field(mSI{"a": "string"}, "a") + assert.Error(t, err) + + for _, value := range []float64{ + 0.5, // Fractional input + math.Inf(1), // Infinity + math.NaN(), // NaN + } { + _, err = int64Field(mSI{"a": value}, "a") + assert.Error(t, err, fmt.Sprintf("%f", value)) + } + + // Success + // The float64 type has 53 bits of effective precision, so ±1FFFFFFFFFFFFF is the + // range of integer values which can all be represented exactly (beyond that, + // some are representable if they are divisible by a high enough power of 2, + // but most are not). + for _, value := range []int64{0, 1, -1, 0x1FFFFFFFFFFFFF, -0x1FFFFFFFFFFFFF} { + testName := fmt.Sprintf("%d", value) + v, err := int64Field(mSI{"a": float64(value), "b": nil}, "a") + require.NoError(t, err, testName) + assert.Equal(t, value, v, testName) + } +} + +func TestMapField(t *testing.T) { + // Field not found + _, err := mapField(mSI{"a": mSI{}}, "b") + assert.Error(t, err) + + // Field has a wrong type + _, err = mapField(mSI{"a": 1}, "a") + assert.Error(t, err) + + // Success + // FIXME? We can't use mSI as the type of child, that type apparently can't be converted to the raw map type. + child := map[string]interface{}{"b": mSI{}} + m, err := mapField(mSI{"a": child, "b": nil}, "a") + require.NoError(t, err) + assert.Equal(t, child, m) +} + +func TestStringField(t *testing.T) { + // Field not found + _, err := stringField(mSI{"a": "x"}, "b") + assert.Error(t, err) + + // Field has a wrong type + _, err = stringField(mSI{"a": 1}, "a") + assert.Error(t, err) + + // Success + s, err := stringField(mSI{"a": "x", "b": nil}, "a") + require.NoError(t, err) + assert.Equal(t, "x", s) +} + +// implementsUnmarshalJSON is a minimalistic type used to detect that +// paranoidUnmarshalJSONObject uses the json.Unmarshaler interface of resolved +// pointers. +type implementsUnmarshalJSON bool + +// Compile-time check that Policy implements json.Unmarshaler. +var _ json.Unmarshaler = (*implementsUnmarshalJSON)(nil) + +func (dest *implementsUnmarshalJSON) UnmarshalJSON(data []byte) error { + _ = data // We don't care, not really. + *dest = true // Mark handler as called + return nil +} + +func TestParanoidUnmarshalJSONObject(t *testing.T) { + type testStruct struct { + A string + B int + } + ts := testStruct{} + var unmarshalJSONCalled implementsUnmarshalJSON + tsResolver := func(key string) interface{} { + switch key { + case "a": + return &ts.A + case "b": + return &ts.B + case "implementsUnmarshalJSON": + return &unmarshalJSONCalled + default: + return nil + } + } + + // Empty object + ts = testStruct{} + err := paranoidUnmarshalJSONObject([]byte(`{}`), tsResolver) + require.NoError(t, err) + assert.Equal(t, testStruct{}, ts) + + // Success + ts = testStruct{} + err = paranoidUnmarshalJSONObject([]byte(`{"a":"x", "b":2}`), tsResolver) + require.NoError(t, err) + assert.Equal(t, testStruct{A: "x", B: 2}, ts) + + // json.Unamarshaler is used for decoding values + ts = testStruct{} + unmarshalJSONCalled = implementsUnmarshalJSON(false) + err = paranoidUnmarshalJSONObject([]byte(`{"implementsUnmarshalJSON":true}`), tsResolver) + require.NoError(t, err) + assert.Equal(t, unmarshalJSONCalled, implementsUnmarshalJSON(true)) + + // Various kinds of invalid input + for _, input := range []string{ + ``, // Empty input + `&`, // Entirely invalid JSON + `1`, // Not an object + `{&}`, // Invalid key JSON + `{1:1}`, // Key not a string + `{"b":1, "b":1}`, // Duplicate key + `{"thisdoesnotexist":1}`, // Key rejected by resolver + `{"a":&}`, // Invalid value JSON + `{"a":1}`, // Type mismatch + `{"a":"value"}{}`, // Extra data after object + } { + ts = testStruct{} + err := paranoidUnmarshalJSONObject([]byte(input), tsResolver) + assert.Error(t, err, input) + } +} diff --git a/vendor/github.com/containers/image/signature/mechanism_test.go b/vendor/github.com/containers/image/signature/mechanism_test.go new file mode 100644 index 0000000000..5aee945faa --- /dev/null +++ b/vendor/github.com/containers/image/signature/mechanism_test.go @@ -0,0 +1,205 @@ +package signature + +import ( + "bytes" + "io/ioutil" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + testGPGHomeDirectory = "./fixtures" +) + +func TestNewGPGSigningMechanism(t *testing.T) { + // A dumb test just for code coverage. We test more with newGPGSigningMechanismInDirectory(). + _, err := NewGPGSigningMechanism() + assert.NoError(t, err) +} + +func TestNewGPGSigningMechanismInDirectory(t *testing.T) { + // A dumb test just for code coverage. + _, err := newGPGSigningMechanismInDirectory(testGPGHomeDirectory) + assert.NoError(t, err) + // The various GPG failure cases are not obviously easy to reach. +} + +func TestGPGSigningMechanismImportKeysFromBytes(t *testing.T) { + testDir, err := ioutil.TempDir("", "gpg-import-keys") + require.NoError(t, err) + defer os.RemoveAll(testDir) + + mech, err := newGPGSigningMechanismInDirectory(testDir) + require.NoError(t, err) + + // Try validating a signature when the key is unknown. + signature, err := ioutil.ReadFile("./fixtures/invalid-blob.signature") + require.NoError(t, err) + content, signingFingerprint, err := mech.Verify(signature) + require.Error(t, err) + + // Successful import + keyBlob, err := ioutil.ReadFile("./fixtures/public-key.gpg") + require.NoError(t, err) + keyIdentities, err := mech.ImportKeysFromBytes(keyBlob) + require.NoError(t, err) + assert.Equal(t, []string{TestKeyFingerprint}, keyIdentities) + + // After import, the signature should validate. + content, signingFingerprint, err = mech.Verify(signature) + require.NoError(t, err) + assert.Equal(t, []byte("This is not JSON\n"), content) + assert.Equal(t, TestKeyFingerprint, signingFingerprint) + + // Two keys: just concatenate the valid input twice. + keyIdentities, err = mech.ImportKeysFromBytes(bytes.Join([][]byte{keyBlob, keyBlob}, nil)) + require.NoError(t, err) + assert.Equal(t, []string{TestKeyFingerprint, TestKeyFingerprint}, keyIdentities) + + // Invalid input: This is accepted anyway by GPG, just returns no keys. + keyIdentities, err = mech.ImportKeysFromBytes([]byte("This is invalid")) + require.NoError(t, err) + assert.Equal(t, []string{}, keyIdentities) + // The various GPG/GPGME failures cases are not obviously easy to reach. +} + +func TestGPGSigningMechanismSign(t *testing.T) { + mech, err := newGPGSigningMechanismInDirectory(testGPGHomeDirectory) + require.NoError(t, err) + + // Successful signing + content := []byte("content") + signature, err := mech.Sign(content, TestKeyFingerprint) + require.NoError(t, err) + + signedContent, signingFingerprint, err := mech.Verify(signature) + require.NoError(t, err) + assert.EqualValues(t, content, signedContent) + assert.Equal(t, TestKeyFingerprint, signingFingerprint) + + // Error signing + _, err = mech.Sign(content, "this fingerprint doesn't exist") + assert.Error(t, err) + // The various GPG/GPGME failures cases are not obviously easy to reach. +} + +func assertSigningError(t *testing.T, content []byte, fingerprint string, err error) { + assert.Error(t, err) + assert.Nil(t, content) + assert.Empty(t, fingerprint) +} + +func TestGPGSigningMechanismVerify(t *testing.T) { + mech, err := newGPGSigningMechanismInDirectory(testGPGHomeDirectory) + require.NoError(t, err) + + // Successful verification + signature, err := ioutil.ReadFile("./fixtures/invalid-blob.signature") + require.NoError(t, err) + content, signingFingerprint, err := mech.Verify(signature) + require.NoError(t, err) + assert.Equal(t, []byte("This is not JSON\n"), content) + assert.Equal(t, TestKeyFingerprint, signingFingerprint) + + // For extra paranoia, test that we return nil data on error. + + // Completely invalid signature. + content, signingFingerprint, err = mech.Verify([]byte{}) + assertSigningError(t, content, signingFingerprint, err) + + content, signingFingerprint, err = mech.Verify([]byte("invalid signature")) + assertSigningError(t, content, signingFingerprint, err) + + // Literal packet, not a signature + signature, err = ioutil.ReadFile("./fixtures/unsigned-literal.signature") + require.NoError(t, err) + content, signingFingerprint, err = mech.Verify(signature) + assertSigningError(t, content, signingFingerprint, err) + + // Encrypted data, not a signature. + signature, err = ioutil.ReadFile("./fixtures/unsigned-encrypted.signature") + require.NoError(t, err) + content, signingFingerprint, err = mech.Verify(signature) + assertSigningError(t, content, signingFingerprint, err) + + // FIXME? Is there a way to create a multi-signature so that gpgme_op_verify returns multiple signatures? + + // Expired signature + signature, err = ioutil.ReadFile("./fixtures/expired.signature") + require.NoError(t, err) + content, signingFingerprint, err = mech.Verify(signature) + assertSigningError(t, content, signingFingerprint, err) + + // Corrupt signature + signature, err = ioutil.ReadFile("./fixtures/corrupt.signature") + require.NoError(t, err) + content, signingFingerprint, err = mech.Verify(signature) + assertSigningError(t, content, signingFingerprint, err) + + // Valid signature with an unknown key + signature, err = ioutil.ReadFile("./fixtures/unknown-key.signature") + require.NoError(t, err) + content, signingFingerprint, err = mech.Verify(signature) + assertSigningError(t, content, signingFingerprint, err) + + // The various GPG/GPGME failures cases are not obviously easy to reach. +} + +func TestGPGSigningMechanismUntrustedSignatureContents(t *testing.T) { + mech, err := newGPGSigningMechanismInDirectory(testGPGHomeDirectory) + require.NoError(t, err) + + // A valid signature + signature, err := ioutil.ReadFile("./fixtures/invalid-blob.signature") + require.NoError(t, err) + content, shortKeyID, err := mech.UntrustedSignatureContents(signature) + require.NoError(t, err) + assert.Equal(t, []byte("This is not JSON\n"), content) + assert.Equal(t, TestKeyShortID, shortKeyID) + + // Completely invalid signature. + _, _, err = mech.UntrustedSignatureContents([]byte{}) + assert.Error(t, err) + + _, _, err = mech.UntrustedSignatureContents([]byte("invalid signature")) + assert.Error(t, err) + + // Literal packet, not a signature + signature, err = ioutil.ReadFile("./fixtures/unsigned-literal.signature") + require.NoError(t, err) + content, shortKeyID, err = mech.UntrustedSignatureContents(signature) + assert.Error(t, err) + + // Encrypted data, not a signature. + signature, err = ioutil.ReadFile("./fixtures/unsigned-encrypted.signature") + require.NoError(t, err) + content, shortKeyID, err = mech.UntrustedSignatureContents(signature) + assert.Error(t, err) + + // Expired signature + signature, err = ioutil.ReadFile("./fixtures/expired.signature") + require.NoError(t, err) + content, shortKeyID, err = mech.UntrustedSignatureContents(signature) + require.NoError(t, err) + assert.Equal(t, []byte("This signature is expired.\n"), content) + assert.Equal(t, TestKeyShortID, shortKeyID) + + // Corrupt signature + signature, err = ioutil.ReadFile("./fixtures/corrupt.signature") + require.NoError(t, err) + content, shortKeyID, err = mech.UntrustedSignatureContents(signature) + require.NoError(t, err) + assert.Equal(t, []byte(`{"critical":{"identity":{"docker-reference":"testing/manifest"},"image":{"docker-manifest-digest":"sha256:20bf21ed457b390829cdbeec8795a7bea1626991fda603e0d01b4e7f60427e55"},"type":"atomic container signature"},"optional":{"creator":"atomic ","timestamp":1458239713}}`), content) + assert.Equal(t, TestKeyShortID, shortKeyID) + + // Valid signature with an unknown key + signature, err = ioutil.ReadFile("./fixtures/unknown-key.signature") + require.NoError(t, err) + content, shortKeyID, err = mech.UntrustedSignatureContents(signature) + require.NoError(t, err) + assert.Equal(t, []byte(`{"critical":{"identity":{"docker-reference":"testing/manifest"},"image":{"docker-manifest-digest":"sha256:20bf21ed457b390829cdbeec8795a7bea1626991fda603e0d01b4e7f60427e55"},"type":"atomic container signature"},"optional":{"creator":"atomic 0.1.13-dev","timestamp":1464633474}}`), content) + assert.Equal(t, "E5476D1110D07803", shortKeyID) +} diff --git a/vendor/github.com/containers/image/signature/policy_config_test.go b/vendor/github.com/containers/image/signature/policy_config_test.go new file mode 100644 index 0000000000..c7b3278f39 --- /dev/null +++ b/vendor/github.com/containers/image/signature/policy_config_test.go @@ -0,0 +1,1366 @@ +package signature + +import ( + "bytes" + "encoding/json" + "io/ioutil" + "path/filepath" + "testing" + + "github.com/containers/image/directory" + "github.com/containers/image/docker" + "github.com/containers/image/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// policyFixtureContents is a data structure equal to the contents of "fixtures/policy.json" +var policyFixtureContents = &Policy{ + Default: PolicyRequirements{NewPRReject()}, + Transports: map[string]PolicyTransportScopes{ + "dir": { + "": PolicyRequirements{NewPRInsecureAcceptAnything()}, + }, + "docker": { + "example.com/playground": { + NewPRInsecureAcceptAnything(), + }, + "example.com/production": { + xNewPRSignedByKeyPath(SBKeyTypeGPGKeys, + "/keys/employee-gpg-keyring", + NewPRMMatchRepoDigestOrExact()), + }, + "example.com/hardened": { + xNewPRSignedByKeyPath(SBKeyTypeGPGKeys, + "/keys/employee-gpg-keyring", + NewPRMMatchRepository()), + xNewPRSignedByKeyPath(SBKeyTypeSignedByGPGKeys, + "/keys/public-key-signing-gpg-keyring", + NewPRMMatchExact()), + xNewPRSignedBaseLayer(xNewPRMExactRepository("registry.access.redhat.com/rhel7/rhel")), + }, + "example.com/hardened-x509": { + xNewPRSignedByKeyPath(SBKeyTypeX509Certificates, + "/keys/employee-cert-file", + NewPRMMatchRepository()), + xNewPRSignedByKeyPath(SBKeyTypeSignedByX509CAs, + "/keys/public-key-signing-ca-file", + NewPRMMatchRepoDigestOrExact()), + }, + "registry.access.redhat.com": { + xNewPRSignedByKeyPath(SBKeyTypeSignedByGPGKeys, + "/keys/RH-key-signing-key-gpg-keyring", + NewPRMMatchRepoDigestOrExact()), + }, + "bogus/key-data-example": { + xNewPRSignedByKeyData(SBKeyTypeSignedByGPGKeys, + []byte("nonsense"), + NewPRMMatchRepoDigestOrExact()), + }, + "bogus/signed-identity-example": { + xNewPRSignedBaseLayer(xNewPRMExactReference("registry.access.redhat.com/rhel7/rhel:latest")), + }, + }, + }, +} + +func TestDefaultPolicy(t *testing.T) { + // We can't test the actual systemDefaultPolicyPath, so override. + // TestDefaultPolicyPath below tests that we handle the overrides and defaults + // correctly. + + // Success + policy, err := DefaultPolicy(&types.SystemContext{SignaturePolicyPath: "./fixtures/policy.json"}) + require.NoError(t, err) + assert.Equal(t, policyFixtureContents, policy) + + for _, path := range []string{ + "/this/doesnt/exist", // Error reading file + "/dev/null", // A failure case; most are tested in the individual method unit tests. + } { + policy, err := DefaultPolicy(&types.SystemContext{SignaturePolicyPath: path}) + assert.Error(t, err) + assert.Nil(t, policy) + } +} + +func TestDefaultPolicyPath(t *testing.T) { + + const nondefaultPath = "/this/is/not/the/default/path.json" + const variableReference = "$HOME" + const rootPrefix = "/root/prefix" + + for _, c := range []struct { + ctx *types.SystemContext + expected string + }{ + // The common case + {nil, systemDefaultPolicyPath}, + // There is a context, but it does not override the path. + {&types.SystemContext{}, systemDefaultPolicyPath}, + // Path overridden + {&types.SystemContext{SignaturePolicyPath: nondefaultPath}, nondefaultPath}, + // Root overridden + { + &types.SystemContext{RootForImplicitAbsolutePaths: rootPrefix}, + filepath.Join(rootPrefix, systemDefaultPolicyPath), + }, + // Root and path overrides present simultaneously, + { + &types.SystemContext{ + RootForImplicitAbsolutePaths: rootPrefix, + SignaturePolicyPath: nondefaultPath, + }, + nondefaultPath, + }, + // No environment expansion happens in the overridden paths + {&types.SystemContext{SignaturePolicyPath: variableReference}, variableReference}, + } { + path := defaultPolicyPath(c.ctx) + assert.Equal(t, c.expected, path) + } +} + +func TestNewPolicyFromFile(t *testing.T) { + // Success + policy, err := NewPolicyFromFile("./fixtures/policy.json") + require.NoError(t, err) + assert.Equal(t, policyFixtureContents, policy) + + // Error reading file + _, err = NewPolicyFromFile("/this/doesnt/exist") + assert.Error(t, err) + + // A failure case; most are tested in the individual method unit tests. + _, err = NewPolicyFromFile("/dev/null") + require.Error(t, err) + assert.IsType(t, InvalidPolicyFormatError(""), err) +} + +func TestNewPolicyFromBytes(t *testing.T) { + // Success + bytes, err := ioutil.ReadFile("./fixtures/policy.json") + require.NoError(t, err) + policy, err := NewPolicyFromBytes(bytes) + require.NoError(t, err) + assert.Equal(t, policyFixtureContents, policy) + + // A failure case; most are tested in the individual method unit tests. + _, err = NewPolicyFromBytes([]byte("")) + require.Error(t, err) + assert.IsType(t, InvalidPolicyFormatError(""), err) +} + +// FIXME? There is quite a bit of duplication below. Factor some of it out? + +// testInvalidJSONInput verifies that obviously invalid input is rejected for dest. +func testInvalidJSONInput(t *testing.T, dest json.Unmarshaler) { + // Invalid input. Note that json.Unmarshal is guaranteed to validate input before calling our + // UnmarshalJSON implementation; so test that first, then test our error handling for completeness. + err := json.Unmarshal([]byte("&"), dest) + assert.Error(t, err) + err = dest.UnmarshalJSON([]byte("&")) + assert.Error(t, err) + + // Not an object/array/string + err = json.Unmarshal([]byte("1"), dest) + assert.Error(t, err) +} + +// addExtraJSONMember adds adds an additional member "$name": $extra, +// possibly with a duplicate name, to encoded. +// Errors, if any, are reported through t. +func addExtraJSONMember(t *testing.T, encoded []byte, name string, extra interface{}) []byte { + extraJSON, err := json.Marshal(extra) + require.NoError(t, err) + + require.True(t, bytes.HasSuffix(encoded, []byte("}"))) + preservedLen := len(encoded) - 1 + + return bytes.Join([][]byte{encoded[:preservedLen], []byte(`,"`), []byte(name), []byte(`":`), extraJSON, []byte("}")}, nil) +} + +func TestInvalidPolicyFormatError(t *testing.T) { + // A stupid test just to keep code coverage + s := "test" + err := InvalidPolicyFormatError(s) + assert.Equal(t, s, err.Error()) +} + +// Return the result of modifying validJSON with fn and unmarshaling it into *p +func tryUnmarshalModifiedPolicy(t *testing.T, p *Policy, validJSON []byte, modifyFn func(mSI)) error { + var tmp mSI + err := json.Unmarshal(validJSON, &tmp) + require.NoError(t, err) + + modifyFn(tmp) + + testJSON, err := json.Marshal(tmp) + require.NoError(t, err) + + *p = Policy{} + return json.Unmarshal(testJSON, p) +} + +// xNewPRSignedByKeyPath is like NewPRSignedByKeyPath, except it must not fail. +func xNewPRSignedByKeyPath(keyType sbKeyType, keyPath string, signedIdentity PolicyReferenceMatch) PolicyRequirement { + pr, err := NewPRSignedByKeyPath(keyType, keyPath, signedIdentity) + if err != nil { + panic("xNewPRSignedByKeyPath failed") + } + return pr +} + +// xNewPRSignedByKeyData is like NewPRSignedByKeyData, except it must not fail. +func xNewPRSignedByKeyData(keyType sbKeyType, keyData []byte, signedIdentity PolicyReferenceMatch) PolicyRequirement { + pr, err := NewPRSignedByKeyData(keyType, keyData, signedIdentity) + if err != nil { + panic("xNewPRSignedByKeyData failed") + } + return pr +} + +func TestPolicyUnmarshalJSON(t *testing.T) { + var p Policy + + testInvalidJSONInput(t, &p) + + // Start with a valid JSON. + validPolicy := Policy{ + Default: []PolicyRequirement{ + xNewPRSignedByKeyData(SBKeyTypeGPGKeys, []byte("abc"), NewPRMMatchRepoDigestOrExact()), + }, + Transports: map[string]PolicyTransportScopes{ + "docker": { + "docker.io/library/busybox": []PolicyRequirement{ + xNewPRSignedByKeyData(SBKeyTypeGPGKeys, []byte("def"), NewPRMMatchRepoDigestOrExact()), + }, + "registry.access.redhat.com": []PolicyRequirement{ + xNewPRSignedByKeyData(SBKeyTypeSignedByGPGKeys, []byte("RH"), NewPRMMatchRepository()), + }, + }, + "atomic": { + "registry.access.redhat.com/rhel7": []PolicyRequirement{ + xNewPRSignedByKeyData(SBKeyTypeSignedByGPGKeys, []byte("RHatomic"), NewPRMMatchRepository()), + }, + }, + }, + } + validJSON, err := json.Marshal(validPolicy) + require.NoError(t, err) + + // Success + p = Policy{} + err = json.Unmarshal(validJSON, &p) + require.NoError(t, err) + assert.Equal(t, validPolicy, p) + + // Various ways to corrupt the JSON + breakFns := []func(mSI){ + // The "default" field is missing + func(v mSI) { delete(v, "default") }, + // Extra top-level sub-object + func(v mSI) { v["unexpected"] = 1 }, + // "default" not an array + func(v mSI) { v["default"] = 1 }, + func(v mSI) { v["default"] = mSI{} }, + // "transports" not an object + func(v mSI) { v["transports"] = 1 }, + 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) + assert.Error(t, err) + } + + // Duplicated fields + for _, field := range []string{"default", "transports"} { + var tmp mSI + err := json.Unmarshal(validJSON, &tmp) + require.NoError(t, err) + + testJSON := addExtraJSONMember(t, validJSON, field, tmp[field]) + + p = Policy{} + err = json.Unmarshal(testJSON, &p) + assert.Error(t, err) + } + + // Various allowed modifications to the policy + allowedModificationFns := []func(mSI){ + // Delete the map of specific policies + func(v mSI) { delete(v, "specific") }, + // Use an empty map of transport-specific scopes + func(v mSI) { v["transports"] = map[string]PolicyTransportScopes{} }, + } + for _, fn := range allowedModificationFns { + err = tryUnmarshalModifiedPolicy(t, &p, validJSON, fn) + require.NoError(t, err) + } +} + +func TestPolicyTransportScopesUnmarshalJSON(t *testing.T) { + var pts PolicyTransportScopes + + // Start with a valid JSON. + validPTS := PolicyTransportScopes{ + "": []PolicyRequirement{ + xNewPRSignedByKeyData(SBKeyTypeGPGKeys, []byte("global"), NewPRMMatchRepoDigestOrExact()), + }, + } + validJSON, err := json.Marshal(validPTS) + require.NoError(t, err) + + // Nothing can be unmarshaled directly into PolicyTransportScopes + pts = PolicyTransportScopes{} + err = json.Unmarshal(validJSON, &pts) + assert.Error(t, err) +} + +// Return the result of modifying validJSON with fn and unmarshaling it into *pts +// using transport. +func tryUnmarshalModifiedPTS(t *testing.T, pts *PolicyTransportScopes, transport types.ImageTransport, + validJSON []byte, modifyFn func(mSI)) error { + var tmp mSI + err := json.Unmarshal(validJSON, &tmp) + require.NoError(t, err) + + modifyFn(tmp) + + testJSON, err := json.Marshal(tmp) + require.NoError(t, err) + + *pts = PolicyTransportScopes{} + dest := policyTransportScopesWithTransport{ + transport: transport, + dest: pts, + } + return json.Unmarshal(testJSON, &dest) +} + +func TestPolicyTransportScopesWithTransportUnmarshalJSON(t *testing.T) { + var pts PolicyTransportScopes + + dest := policyTransportScopesWithTransport{ + transport: docker.Transport, + dest: &pts, + } + testInvalidJSONInput(t, &dest) + + // Start with a valid JSON. + validPTS := PolicyTransportScopes{ + "docker.io/library/busybox": []PolicyRequirement{ + xNewPRSignedByKeyData(SBKeyTypeGPGKeys, []byte("def"), NewPRMMatchRepoDigestOrExact()), + }, + "registry.access.redhat.com": []PolicyRequirement{ + xNewPRSignedByKeyData(SBKeyTypeSignedByGPGKeys, []byte("RH"), NewPRMMatchRepository()), + }, + "": []PolicyRequirement{ + xNewPRSignedByKeyData(SBKeyTypeGPGKeys, []byte("global"), NewPRMMatchRepoDigestOrExact()), + }, + } + validJSON, err := json.Marshal(validPTS) + require.NoError(t, err) + + // Success + pts = PolicyTransportScopes{} + dest = policyTransportScopesWithTransport{ + transport: docker.Transport, + dest: &pts, + } + err = json.Unmarshal(validJSON, &dest) + require.NoError(t, err) + assert.Equal(t, validPTS, pts) + + // Various ways to corrupt the JSON + breakFns := []func(mSI){ + // A scope is not an array + func(v mSI) { v["docker.io/library/busybox"] = 1 }, + func(v mSI) { v["docker.io/library/busybox"] = mSI{} }, + func(v mSI) { v[""] = 1 }, + func(v mSI) { v[""] = mSI{} }, + // A scope is an invalid PolicyRequirements + func(v mSI) { v["docker.io/library/busybox"] = PolicyRequirements{} }, + func(v mSI) { v[""] = PolicyRequirements{} }, + } + for _, fn := range breakFns { + err = tryUnmarshalModifiedPTS(t, &pts, docker.Transport, validJSON, fn) + assert.Error(t, err) + } + + // Duplicated fields + for _, field := range []string{"docker.io/library/busybox", ""} { + var tmp mSI + err := json.Unmarshal(validJSON, &tmp) + require.NoError(t, err) + + testJSON := addExtraJSONMember(t, validJSON, field, tmp[field]) + + pts = PolicyTransportScopes{} + dest := policyTransportScopesWithTransport{ + transport: docker.Transport, + dest: &pts, + } + err = json.Unmarshal(testJSON, &dest) + assert.Error(t, err) + } + + // Scope rejected by transport the Docker scopes we use as valid are rejected by directory.Transport + // as relative paths. + err = tryUnmarshalModifiedPTS(t, &pts, directory.Transport, validJSON, + func(v mSI) {}) + assert.Error(t, err) + + // Various allowed modifications to the policy + allowedModificationFns := []func(mSI){ + // The "" scope is missing + func(v mSI) { delete(v, "") }, + // The policy is completely empty + func(v mSI) { + for key := range v { + delete(v, key) + } + }, + } + for _, fn := range allowedModificationFns { + err = tryUnmarshalModifiedPTS(t, &pts, docker.Transport, validJSON, fn) + require.NoError(t, err) + } +} + +func TestPolicyRequirementsUnmarshalJSON(t *testing.T) { + var reqs PolicyRequirements + + testInvalidJSONInput(t, &reqs) + + // Start with a valid JSON. + validReqs := PolicyRequirements{ + xNewPRSignedByKeyData(SBKeyTypeGPGKeys, []byte("def"), NewPRMMatchRepoDigestOrExact()), + xNewPRSignedByKeyData(SBKeyTypeSignedByGPGKeys, []byte("RH"), NewPRMMatchRepository()), + } + validJSON, err := json.Marshal(validReqs) + require.NoError(t, err) + + // Success + reqs = PolicyRequirements{} + err = json.Unmarshal(validJSON, &reqs) + require.NoError(t, err) + assert.Equal(t, validReqs, reqs) + + for _, invalid := range [][]interface{}{ + // No requirements + {}, + // A member is not an object + {1}, + // A member has an invalid type + {prSignedBy{prCommon: prCommon{Type: "this is invalid"}}}, + // A member has a valid type but invalid contents + {prSignedBy{ + prCommon: prCommon{Type: prTypeSignedBy}, + KeyType: "this is invalid", + }}, + } { + testJSON, err := json.Marshal(invalid) + require.NoError(t, err) + + reqs = PolicyRequirements{} + err = json.Unmarshal(testJSON, &reqs) + assert.Error(t, err, string(testJSON)) + } +} + +func TestNewPolicyRequirementFromJSON(t *testing.T) { + // Sample success. Others tested in the individual PolicyRequirement.UnmarshalJSON implementations. + validReq := NewPRInsecureAcceptAnything() + validJSON, err := json.Marshal(validReq) + require.NoError(t, err) + req, err := newPolicyRequirementFromJSON(validJSON) + require.NoError(t, err) + assert.Equal(t, validReq, req) + + // Invalid + for _, invalid := range []interface{}{ + // Not an object + 1, + // Missing type + prCommon{}, + // Invalid type + prCommon{Type: "this is invalid"}, + // Valid type but invalid contents + prSignedBy{ + prCommon: prCommon{Type: prTypeSignedBy}, + KeyType: "this is invalid", + }, + } { + testJSON, err := json.Marshal(invalid) + require.NoError(t, err) + + _, err = newPolicyRequirementFromJSON(testJSON) + assert.Error(t, err, string(testJSON)) + } +} + +func TestNewPRInsecureAcceptAnything(t *testing.T) { + _pr := NewPRInsecureAcceptAnything() + pr, ok := _pr.(*prInsecureAcceptAnything) + require.True(t, ok) + assert.Equal(t, &prInsecureAcceptAnything{prCommon{prTypeInsecureAcceptAnything}}, pr) +} + +func TestPRInsecureAcceptAnythingUnmarshalJSON(t *testing.T) { + var pr prInsecureAcceptAnything + + testInvalidJSONInput(t, &pr) + + // Start with a valid JSON. + validPR := NewPRInsecureAcceptAnything() + validJSON, err := json.Marshal(validPR) + require.NoError(t, err) + + // Success + pr = prInsecureAcceptAnything{} + err = json.Unmarshal(validJSON, &pr) + require.NoError(t, err) + assert.Equal(t, validPR, &pr) + + // newPolicyRequirementFromJSON recognizes this type + _pr, err := newPolicyRequirementFromJSON(validJSON) + require.NoError(t, err) + assert.Equal(t, validPR, _pr) + + for _, invalid := range []mSI{ + // Missing "type" field + {}, + // Wrong "type" field + {"type": 1}, + {"type": "this is invalid"}, + // Extra fields + { + "type": string(prTypeInsecureAcceptAnything), + "unknown": "foo", + }, + } { + testJSON, err := json.Marshal(invalid) + require.NoError(t, err) + + pr = prInsecureAcceptAnything{} + err = json.Unmarshal(testJSON, &pr) + assert.Error(t, err, string(testJSON)) + } + + // Duplicated fields + for _, field := range []string{"type"} { + var tmp mSI + err := json.Unmarshal(validJSON, &tmp) + require.NoError(t, err) + + testJSON := addExtraJSONMember(t, validJSON, field, tmp[field]) + + pr = prInsecureAcceptAnything{} + err = json.Unmarshal(testJSON, &pr) + assert.Error(t, err) + } +} + +func TestNewPRReject(t *testing.T) { + _pr := NewPRReject() + pr, ok := _pr.(*prReject) + require.True(t, ok) + assert.Equal(t, &prReject{prCommon{prTypeReject}}, pr) +} + +func TestPRRejectUnmarshalJSON(t *testing.T) { + var pr prReject + + testInvalidJSONInput(t, &pr) + + // Start with a valid JSON. + validPR := NewPRReject() + validJSON, err := json.Marshal(validPR) + require.NoError(t, err) + + // Success + pr = prReject{} + err = json.Unmarshal(validJSON, &pr) + require.NoError(t, err) + assert.Equal(t, validPR, &pr) + + // newPolicyRequirementFromJSON recognizes this type + _pr, err := newPolicyRequirementFromJSON(validJSON) + require.NoError(t, err) + assert.Equal(t, validPR, _pr) + + for _, invalid := range []mSI{ + // Missing "type" field + {}, + // Wrong "type" field + {"type": 1}, + {"type": "this is invalid"}, + // Extra fields + { + "type": string(prTypeReject), + "unknown": "foo", + }, + } { + testJSON, err := json.Marshal(invalid) + require.NoError(t, err) + + pr = prReject{} + err = json.Unmarshal(testJSON, &pr) + assert.Error(t, err, string(testJSON)) + } + + // Duplicated fields + for _, field := range []string{"type"} { + var tmp mSI + err := json.Unmarshal(validJSON, &tmp) + require.NoError(t, err) + + testJSON := addExtraJSONMember(t, validJSON, field, tmp[field]) + + pr = prReject{} + err = json.Unmarshal(testJSON, &pr) + assert.Error(t, err) + } +} + +func TestNewPRSignedBy(t *testing.T) { + const testPath = "/foo/bar" + testData := []byte("abc") + testIdentity := NewPRMMatchRepoDigestOrExact() + + // Success + pr, err := newPRSignedBy(SBKeyTypeGPGKeys, testPath, nil, testIdentity) + require.NoError(t, err) + assert.Equal(t, &prSignedBy{ + prCommon: prCommon{prTypeSignedBy}, + KeyType: SBKeyTypeGPGKeys, + KeyPath: testPath, + KeyData: nil, + SignedIdentity: testIdentity, + }, pr) + pr, err = newPRSignedBy(SBKeyTypeGPGKeys, "", testData, testIdentity) + require.NoError(t, err) + assert.Equal(t, &prSignedBy{ + prCommon: prCommon{prTypeSignedBy}, + KeyType: SBKeyTypeGPGKeys, + KeyPath: "", + KeyData: testData, + SignedIdentity: testIdentity, + }, pr) + + // Invalid keyType + pr, err = newPRSignedBy(sbKeyType(""), testPath, nil, testIdentity) + assert.Error(t, err) + pr, err = newPRSignedBy(sbKeyType("this is invalid"), testPath, nil, testIdentity) + assert.Error(t, err) + + // Both keyPath and keyData specified + pr, err = newPRSignedBy(SBKeyTypeGPGKeys, testPath, testData, testIdentity) + assert.Error(t, err) + + // Invalid signedIdentity + pr, err = newPRSignedBy(SBKeyTypeGPGKeys, testPath, nil, nil) + assert.Error(t, err) +} + +func TestNewPRSignedByKeyPath(t *testing.T) { + const testPath = "/foo/bar" + _pr, err := NewPRSignedByKeyPath(SBKeyTypeGPGKeys, testPath, NewPRMMatchRepoDigestOrExact()) + require.NoError(t, err) + pr, ok := _pr.(*prSignedBy) + require.True(t, ok) + assert.Equal(t, testPath, pr.KeyPath) + // Failure cases tested in TestNewPRSignedBy. +} + +func TestNewPRSignedByKeyData(t *testing.T) { + testData := []byte("abc") + _pr, err := NewPRSignedByKeyData(SBKeyTypeGPGKeys, testData, NewPRMMatchRepoDigestOrExact()) + require.NoError(t, err) + pr, ok := _pr.(*prSignedBy) + require.True(t, ok) + assert.Equal(t, testData, pr.KeyData) + // Failure cases tested in TestNewPRSignedBy. +} + +// Return the result of modifying vaoidJSON with fn and unmarshalingit into *pr +func tryUnmarshalModifiedSignedBy(t *testing.T, pr *prSignedBy, validJSON []byte, modifyFn func(mSI)) error { + var tmp mSI + err := json.Unmarshal(validJSON, &tmp) + require.NoError(t, err) + + modifyFn(tmp) + + testJSON, err := json.Marshal(tmp) + require.NoError(t, err) + + *pr = prSignedBy{} + return json.Unmarshal(testJSON, &pr) +} + +func TestPRSignedByUnmarshalJSON(t *testing.T) { + var pr prSignedBy + + testInvalidJSONInput(t, &pr) + + // Start with a valid JSON. + validPR, err := NewPRSignedByKeyData(SBKeyTypeGPGKeys, []byte("abc"), NewPRMMatchRepoDigestOrExact()) + require.NoError(t, err) + validJSON, err := json.Marshal(validPR) + require.NoError(t, err) + + // Success with KeyData + pr = prSignedBy{} + err = json.Unmarshal(validJSON, &pr) + require.NoError(t, err) + assert.Equal(t, validPR, &pr) + + // Success with KeyPath + kpPR, err := NewPRSignedByKeyPath(SBKeyTypeGPGKeys, "/foo/bar", NewPRMMatchRepoDigestOrExact()) + require.NoError(t, err) + testJSON, err := json.Marshal(kpPR) + require.NoError(t, err) + pr = prSignedBy{} + err = json.Unmarshal(testJSON, &pr) + require.NoError(t, err) + assert.Equal(t, kpPR, &pr) + + // newPolicyRequirementFromJSON recognizes this type + _pr, err := newPolicyRequirementFromJSON(validJSON) + require.NoError(t, err) + assert.Equal(t, validPR, _pr) + + // Various ways to corrupt the JSON + breakFns := []func(mSI){ + // The "type" field is missing + func(v mSI) { delete(v, "type") }, + // Wrong "type" field + func(v mSI) { v["type"] = 1 }, + func(v mSI) { v["type"] = "this is invalid" }, + // Extra top-level sub-object + func(v mSI) { v["unexpected"] = 1 }, + // The "keyType" field is missing + func(v mSI) { delete(v, "keyType") }, + // Invalid "keyType" field + func(v mSI) { v["keyType"] = "this is invalid" }, + // Both "keyPath" and "keyData" is missing + func(v mSI) { delete(v, "keyData") }, + // Both "keyPath" and "keyData" is present + func(v mSI) { v["keyPath"] = "/foo/bar" }, + // Invalid "keyPath" field + func(v mSI) { delete(v, "keyData"); v["keyPath"] = 1 }, + func(v mSI) { v["type"] = "this is invalid" }, + // Invalid "keyData" field + func(v mSI) { v["keyData"] = 1 }, + func(v mSI) { v["keyData"] = "this is invalid base64" }, + // Invalid "signedIdentity" field + func(v mSI) { v["signedIdentity"] = "this is invalid" }, + // "signedIdentity" an explicit nil + func(v mSI) { v["signedIdentity"] = nil }, + } + for _, fn := range breakFns { + err = tryUnmarshalModifiedSignedBy(t, &pr, validJSON, fn) + assert.Error(t, err, string(testJSON)) + } + + // Duplicated fields + for _, field := range []string{"type", "keyType", "keyData", "signedIdentity"} { + var tmp mSI + err := json.Unmarshal(validJSON, &tmp) + require.NoError(t, err) + + testJSON := addExtraJSONMember(t, validJSON, field, tmp[field]) + + pr = prSignedBy{} + err = json.Unmarshal(testJSON, &pr) + assert.Error(t, err) + } + // Handle "keyPath", which is not in validJSON, specially + pathPR, err := NewPRSignedByKeyPath(SBKeyTypeGPGKeys, "/foo/bar", NewPRMMatchRepoDigestOrExact()) + require.NoError(t, err) + testJSON, err = json.Marshal(pathPR) + require.NoError(t, err) + testJSON = addExtraJSONMember(t, testJSON, "keyPath", pr.KeyPath) + pr = prSignedBy{} + err = json.Unmarshal(testJSON, &pr) + assert.Error(t, err) + + // Various allowed modifications to the requirement + allowedModificationFns := []func(mSI){ + // Delete the signedIdentity field + func(v mSI) { delete(v, "signedIdentity") }, + } + for _, fn := range allowedModificationFns { + err = tryUnmarshalModifiedSignedBy(t, &pr, validJSON, fn) + require.NoError(t, err) + } + + // Various ways to set signedIdentity to the default value + signedIdentityDefaultFns := []func(mSI){ + // Set signedIdentity to the default explicitly + func(v mSI) { v["signedIdentity"] = NewPRMMatchRepoDigestOrExact() }, + // Delete the signedIdentity field + func(v mSI) { delete(v, "signedIdentity") }, + } + for _, fn := range signedIdentityDefaultFns { + err = tryUnmarshalModifiedSignedBy(t, &pr, validJSON, fn) + require.NoError(t, err) + assert.Equal(t, NewPRMMatchRepoDigestOrExact(), pr.SignedIdentity) + } +} + +func TestSBKeyTypeIsValid(t *testing.T) { + // Valid values + for _, s := range []sbKeyType{ + SBKeyTypeGPGKeys, + SBKeyTypeSignedByGPGKeys, + SBKeyTypeX509Certificates, + SBKeyTypeSignedByX509CAs, + } { + assert.True(t, s.IsValid()) + } + + // Invalid values + for _, s := range []string{"", "this is invalid"} { + assert.False(t, sbKeyType(s).IsValid()) + } +} + +func TestSBKeyTypeUnmarshalJSON(t *testing.T) { + var kt sbKeyType + + testInvalidJSONInput(t, &kt) + + // Valid values. + for _, v := range []sbKeyType{ + SBKeyTypeGPGKeys, + SBKeyTypeSignedByGPGKeys, + SBKeyTypeX509Certificates, + SBKeyTypeSignedByX509CAs, + } { + kt = sbKeyType("") + err := json.Unmarshal([]byte(`"`+string(v)+`"`), &kt) + assert.NoError(t, err) + } + + // Invalid values + kt = sbKeyType("") + err := json.Unmarshal([]byte(`""`), &kt) + assert.Error(t, err) + + kt = sbKeyType("") + err = json.Unmarshal([]byte(`"this is invalid"`), &kt) + assert.Error(t, err) +} + +// NewPRSignedBaseLayer is like NewPRSignedBaseLayer, except it must not fail. +func xNewPRSignedBaseLayer(baseLayerIdentity PolicyReferenceMatch) PolicyRequirement { + pr, err := NewPRSignedBaseLayer(baseLayerIdentity) + if err != nil { + panic("xNewPRSignedBaseLayer failed") + } + return pr +} + +func TestNewPRSignedBaseLayer(t *testing.T) { + testBLI := NewPRMMatchExact() + + // Success + _pr, err := NewPRSignedBaseLayer(testBLI) + require.NoError(t, err) + pr, ok := _pr.(*prSignedBaseLayer) + require.True(t, ok) + assert.Equal(t, &prSignedBaseLayer{ + prCommon: prCommon{prTypeSignedBaseLayer}, + BaseLayerIdentity: testBLI, + }, pr) + + // Invalid baseLayerIdentity + _, err = NewPRSignedBaseLayer(nil) + assert.Error(t, err) +} + +func TestPRSignedBaseLayerUnmarshalJSON(t *testing.T) { + var pr prSignedBaseLayer + + testInvalidJSONInput(t, &pr) + + // Start with a valid JSON. + baseIdentity, err := NewPRMExactReference("registry.access.redhat.com/rhel7/rhel:7.2.3") + require.NoError(t, err) + validPR, err := NewPRSignedBaseLayer(baseIdentity) + require.NoError(t, err) + validJSON, err := json.Marshal(validPR) + require.NoError(t, err) + + // Success + pr = prSignedBaseLayer{} + err = json.Unmarshal(validJSON, &pr) + require.NoError(t, err) + assert.Equal(t, validPR, &pr) + + // newPolicyRequirementFromJSON recognizes this type + _pr, err := newPolicyRequirementFromJSON(validJSON) + require.NoError(t, err) + assert.Equal(t, validPR, _pr) + + // Various ways to corrupt the JSON + breakFns := []func(mSI){ + // The "type" field is missing + func(v mSI) { delete(v, "type") }, + // Wrong "type" field + func(v mSI) { v["type"] = 1 }, + func(v mSI) { v["type"] = "this is invalid" }, + // Extra top-level sub-object + func(v mSI) { v["unexpected"] = 1 }, + // The "baseLayerIdentity" field is missing + func(v mSI) { delete(v, "baseLayerIdentity") }, + // Invalid "baseLayerIdentity" field + func(v mSI) { v["baseLayerIdentity"] = "this is invalid" }, + // Invalid "baseLayerIdentity" an explicit nil + func(v mSI) { v["baseLayerIdentity"] = nil }, + } + for _, fn := range breakFns { + var tmp mSI + err := json.Unmarshal(validJSON, &tmp) + require.NoError(t, err) + + fn(tmp) + + testJSON, err := json.Marshal(tmp) + require.NoError(t, err) + + pr = prSignedBaseLayer{} + err = json.Unmarshal(testJSON, &pr) + assert.Error(t, err) + } + + // Duplicated fields + for _, field := range []string{"type", "baseLayerIdentity"} { + var tmp mSI + err := json.Unmarshal(validJSON, &tmp) + require.NoError(t, err) + + testJSON := addExtraJSONMember(t, validJSON, field, tmp[field]) + + pr = prSignedBaseLayer{} + err = json.Unmarshal(testJSON, &pr) + assert.Error(t, err) + } +} + +func TestNewPolicyReferenceMatchFromJSON(t *testing.T) { + // Sample success. Others tested in the individual PolicyReferenceMatch.UnmarshalJSON implementations. + validPRM := NewPRMMatchRepoDigestOrExact() + validJSON, err := json.Marshal(validPRM) + require.NoError(t, err) + prm, err := newPolicyReferenceMatchFromJSON(validJSON) + require.NoError(t, err) + assert.Equal(t, validPRM, prm) + + // Invalid + for _, invalid := range []interface{}{ + // Not an object + 1, + // Missing type + prmCommon{}, + // Invalid type + prmCommon{Type: "this is invalid"}, + // Valid type but invalid contents + prmExactReference{ + prmCommon: prmCommon{Type: prmTypeExactReference}, + DockerReference: "", + }, + } { + testJSON, err := json.Marshal(invalid) + require.NoError(t, err) + + _, err = newPolicyReferenceMatchFromJSON(testJSON) + assert.Error(t, err, string(testJSON)) + } +} + +func TestNewPRMMatchExact(t *testing.T) { + _prm := NewPRMMatchExact() + prm, ok := _prm.(*prmMatchExact) + require.True(t, ok) + assert.Equal(t, &prmMatchExact{prmCommon{prmTypeMatchExact}}, prm) +} + +func TestPRMMatchExactUnmarshalJSON(t *testing.T) { + var prm prmMatchExact + + testInvalidJSONInput(t, &prm) + + // Start with a valid JSON. + validPR := NewPRMMatchExact() + validJSON, err := json.Marshal(validPR) + require.NoError(t, err) + + // Success + prm = prmMatchExact{} + err = json.Unmarshal(validJSON, &prm) + require.NoError(t, err) + assert.Equal(t, validPR, &prm) + + // newPolicyReferenceMatchFromJSON recognizes this type + _pr, err := newPolicyReferenceMatchFromJSON(validJSON) + require.NoError(t, err) + assert.Equal(t, validPR, _pr) + + for _, invalid := range []mSI{ + // Missing "type" field + {}, + // Wrong "type" field + {"type": 1}, + {"type": "this is invalid"}, + // Extra fields + { + "type": string(prmTypeMatchExact), + "unknown": "foo", + }, + } { + testJSON, err := json.Marshal(invalid) + require.NoError(t, err) + + prm = prmMatchExact{} + err = json.Unmarshal(testJSON, &prm) + assert.Error(t, err, string(testJSON)) + } + + // Duplicated fields + for _, field := range []string{"type"} { + var tmp mSI + err := json.Unmarshal(validJSON, &tmp) + require.NoError(t, err) + + testJSON := addExtraJSONMember(t, validJSON, field, tmp[field]) + + prm = prmMatchExact{} + err = json.Unmarshal(testJSON, &prm) + assert.Error(t, err) + } +} + +func TestNewPRMMatchRepoDigestOrExact(t *testing.T) { + _prm := NewPRMMatchRepoDigestOrExact() + prm, ok := _prm.(*prmMatchRepoDigestOrExact) + require.True(t, ok) + assert.Equal(t, &prmMatchRepoDigestOrExact{prmCommon{prmTypeMatchRepoDigestOrExact}}, prm) +} + +func TestPRMMatchRepoDigestOrExactUnmarshalJSON(t *testing.T) { + var prm prmMatchRepoDigestOrExact + + testInvalidJSONInput(t, &prm) + + // Start with a valid JSON. + validPR := NewPRMMatchRepoDigestOrExact() + validJSON, err := json.Marshal(validPR) + require.NoError(t, err) + + // Success + prm = prmMatchRepoDigestOrExact{} + err = json.Unmarshal(validJSON, &prm) + require.NoError(t, err) + assert.Equal(t, validPR, &prm) + + // newPolicyReferenceMatchFromJSON recognizes this type + _pr, err := newPolicyReferenceMatchFromJSON(validJSON) + require.NoError(t, err) + assert.Equal(t, validPR, _pr) + + for _, invalid := range []mSI{ + // Missing "type" field + {}, + // Wrong "type" field + {"type": 1}, + {"type": "this is invalid"}, + // Extra fields + { + "type": string(prmTypeMatchRepoDigestOrExact), + "unknown": "foo", + }, + } { + testJSON, err := json.Marshal(invalid) + require.NoError(t, err) + + prm = prmMatchRepoDigestOrExact{} + err = json.Unmarshal(testJSON, &prm) + assert.Error(t, err, string(testJSON)) + } + + // Duplicated fields + for _, field := range []string{"type"} { + var tmp mSI + err := json.Unmarshal(validJSON, &tmp) + require.NoError(t, err) + + testJSON := addExtraJSONMember(t, validJSON, field, tmp[field]) + + prm = prmMatchRepoDigestOrExact{} + err = json.Unmarshal(testJSON, &prm) + assert.Error(t, err) + } +} + +func TestNewPRMMatchRepository(t *testing.T) { + _prm := NewPRMMatchRepository() + prm, ok := _prm.(*prmMatchRepository) + require.True(t, ok) + assert.Equal(t, &prmMatchRepository{prmCommon{prmTypeMatchRepository}}, prm) +} + +func TestPRMMatchRepositoryUnmarshalJSON(t *testing.T) { + var prm prmMatchRepository + + testInvalidJSONInput(t, &prm) + + // Start with a valid JSON. + validPR := NewPRMMatchRepository() + validJSON, err := json.Marshal(validPR) + require.NoError(t, err) + + // Success + prm = prmMatchRepository{} + err = json.Unmarshal(validJSON, &prm) + require.NoError(t, err) + assert.Equal(t, validPR, &prm) + + // newPolicyReferenceMatchFromJSON recognizes this type + _pr, err := newPolicyReferenceMatchFromJSON(validJSON) + require.NoError(t, err) + assert.Equal(t, validPR, _pr) + + for _, invalid := range []mSI{ + // Missing "type" field + {}, + // Wrong "type" field + {"type": 1}, + {"type": "this is invalid"}, + // Extra fields + { + "type": string(prmTypeMatchRepository), + "unknown": "foo", + }, + } { + testJSON, err := json.Marshal(invalid) + require.NoError(t, err) + + prm = prmMatchRepository{} + err = json.Unmarshal(testJSON, &prm) + assert.Error(t, err, string(testJSON)) + } + + // Duplicated fields + for _, field := range []string{"type"} { + var tmp mSI + err := json.Unmarshal(validJSON, &tmp) + require.NoError(t, err) + + testJSON := addExtraJSONMember(t, validJSON, field, tmp[field]) + + prm = prmMatchRepository{} + err = json.Unmarshal(testJSON, &prm) + assert.Error(t, err) + } +} + +// xNewPRMExactReference is like NewPRMExactReference, except it must not fail. +func xNewPRMExactReference(dockerReference string) PolicyReferenceMatch { + pr, err := NewPRMExactReference(dockerReference) + if err != nil { + panic("xNewPRMExactReference failed") + } + return pr +} + +func TestNewPRMExactReference(t *testing.T) { + const testDR = "library/busybox:latest" + + // Success + _prm, err := NewPRMExactReference(testDR) + require.NoError(t, err) + prm, ok := _prm.(*prmExactReference) + require.True(t, ok) + assert.Equal(t, &prmExactReference{ + prmCommon: prmCommon{prmTypeExactReference}, + DockerReference: testDR, + }, prm) + + // Invalid dockerReference + _, err = NewPRMExactReference("") + assert.Error(t, err) + // Uppercase is invalid in Docker reference components. + _, err = NewPRMExactReference("INVALIDUPPERCASE:latest") + assert.Error(t, err) + // Missing tag + _, err = NewPRMExactReference("library/busybox") + assert.Error(t, err) +} + +func TestPRMExactReferenceUnmarshalJSON(t *testing.T) { + var prm prmExactReference + + testInvalidJSONInput(t, &prm) + + // Start with a valid JSON. + validPRM, err := NewPRMExactReference("library/buxybox:latest") + require.NoError(t, err) + validJSON, err := json.Marshal(validPRM) + require.NoError(t, err) + + // Success + prm = prmExactReference{} + err = json.Unmarshal(validJSON, &prm) + require.NoError(t, err) + assert.Equal(t, validPRM, &prm) + + // newPolicyReferenceMatchFromJSON recognizes this type + _prm, err := newPolicyReferenceMatchFromJSON(validJSON) + require.NoError(t, err) + assert.Equal(t, validPRM, _prm) + + // Various ways to corrupt the JSON + breakFns := []func(mSI){ + // The "type" field is missing + func(v mSI) { delete(v, "type") }, + // Wrong "type" field + func(v mSI) { v["type"] = 1 }, + func(v mSI) { v["type"] = "this is invalid" }, + // Extra top-level sub-object + func(v mSI) { v["unexpected"] = 1 }, + // The "dockerReference" field is missing + func(v mSI) { delete(v, "dockerReference") }, + // Invalid "dockerReference" field + func(v mSI) { v["dockerReference"] = 1 }, + } + for _, fn := range breakFns { + var tmp mSI + err := json.Unmarshal(validJSON, &tmp) + require.NoError(t, err) + + fn(tmp) + + testJSON, err := json.Marshal(tmp) + require.NoError(t, err) + + prm = prmExactReference{} + err = json.Unmarshal(testJSON, &prm) + assert.Error(t, err) + } + + // Duplicated fields + for _, field := range []string{"type", "baseLayerIdentity"} { + var tmp mSI + err := json.Unmarshal(validJSON, &tmp) + require.NoError(t, err) + + testJSON := addExtraJSONMember(t, validJSON, field, tmp[field]) + + prm = prmExactReference{} + err = json.Unmarshal(testJSON, &prm) + assert.Error(t, err) + } +} + +// xNewPRMExactRepository is like NewPRMExactRepository, except it must not fail. +func xNewPRMExactRepository(dockerRepository string) PolicyReferenceMatch { + pr, err := NewPRMExactRepository(dockerRepository) + if err != nil { + panic("xNewPRMExactRepository failed") + } + return pr +} + +func TestNewPRMExactRepository(t *testing.T) { + const testDR = "library/busybox:latest" + + // Success + _prm, err := NewPRMExactRepository(testDR) + require.NoError(t, err) + prm, ok := _prm.(*prmExactRepository) + require.True(t, ok) + assert.Equal(t, &prmExactRepository{ + prmCommon: prmCommon{prmTypeExactRepository}, + DockerRepository: testDR, + }, prm) + + // Invalid dockerRepository + _, err = NewPRMExactRepository("") + assert.Error(t, err) + // Uppercase is invalid in Docker reference components. + _, err = NewPRMExactRepository("INVALIDUPPERCASE") + assert.Error(t, err) +} + +func TestPRMExactRepositoryUnmarshalJSON(t *testing.T) { + var prm prmExactRepository + + testInvalidJSONInput(t, &prm) + + // Start with a valid JSON. + validPRM, err := NewPRMExactRepository("library/buxybox:latest") + require.NoError(t, err) + validJSON, err := json.Marshal(validPRM) + require.NoError(t, err) + + // Success + prm = prmExactRepository{} + err = json.Unmarshal(validJSON, &prm) + require.NoError(t, err) + assert.Equal(t, validPRM, &prm) + + // newPolicyReferenceMatchFromJSON recognizes this type + _prm, err := newPolicyReferenceMatchFromJSON(validJSON) + require.NoError(t, err) + assert.Equal(t, validPRM, _prm) + + // Various ways to corrupt the JSON + breakFns := []func(mSI){ + // The "type" field is missing + func(v mSI) { delete(v, "type") }, + // Wrong "type" field + func(v mSI) { v["type"] = 1 }, + func(v mSI) { v["type"] = "this is invalid" }, + // Extra top-level sub-object + func(v mSI) { v["unexpected"] = 1 }, + // The "dockerRepository" field is missing + func(v mSI) { delete(v, "dockerRepository") }, + // Invalid "dockerRepository" field + func(v mSI) { v["dockerRepository"] = 1 }, + } + for _, fn := range breakFns { + var tmp mSI + err := json.Unmarshal(validJSON, &tmp) + require.NoError(t, err) + + fn(tmp) + + testJSON, err := json.Marshal(tmp) + require.NoError(t, err) + + prm = prmExactRepository{} + err = json.Unmarshal(testJSON, &prm) + assert.Error(t, err) + } + + // Duplicated fields + for _, field := range []string{"type", "baseLayerIdentity"} { + var tmp mSI + err := json.Unmarshal(validJSON, &tmp) + require.NoError(t, err) + + testJSON := addExtraJSONMember(t, validJSON, field, tmp[field]) + + prm = prmExactRepository{} + err = json.Unmarshal(testJSON, &prm) + assert.Error(t, err) + } +} diff --git a/vendor/github.com/containers/image/signature/policy_eval_baselayer_test.go b/vendor/github.com/containers/image/signature/policy_eval_baselayer_test.go new file mode 100644 index 0000000000..937cb928f0 --- /dev/null +++ b/vendor/github.com/containers/image/signature/policy_eval_baselayer_test.go @@ -0,0 +1,24 @@ +package signature + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestPRSignedBaseLayerIsSignatureAuthorAccepted(t *testing.T) { + pr, err := NewPRSignedBaseLayer(NewPRMMatchRepository()) + require.NoError(t, err) + // Pass nil pointers to, kind of, test that the return value does not depend on the parameters. + sar, parsedSig, err := pr.isSignatureAuthorAccepted(nil, nil) + assertSARUnknown(t, sar, parsedSig, err) +} + +func TestPRSignedBaseLayerIsRunningImageAllowed(t *testing.T) { + // This will obviously need to change after signedBaseLayer is implemented. + pr, err := NewPRSignedBaseLayer(NewPRMMatchRepository()) + require.NoError(t, err) + // Pass a nil pointer to, kind of, test that the return value does not depend on the image. + res, err := pr.isRunningImageAllowed(nil) + assertRunningRejectedPolicyRequirement(t, res, err) +} 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 new file mode 100644 index 0000000000..19086fcf5b --- /dev/null +++ b/vendor/github.com/containers/image/signature/policy_eval_signedby_test.go @@ -0,0 +1,264 @@ +package signature + +import ( + "io/ioutil" + "os" + "path" + "testing" + + "github.com/containers/image/directory" + "github.com/containers/image/docker/reference" + "github.com/containers/image/image" + "github.com/containers/image/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// 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.ParseNormalizedNamed(dockerReference) + require.NoError(t, err) + return dirImageMockWithRef(t, dir, refImageReferenceMock{ref}) +} + +// dirImageMockWithRef returns a types.UnparsedImage for a directory, claiming a specified ref. +// The caller must call .Close() on the returned UnparsedImage. +func dirImageMockWithRef(t *testing.T, dir string, ref types.ImageReference) types.UnparsedImage { + srcRef, err := directory.NewReference(dir) + require.NoError(t, err) + src, err := srcRef.NewImageSource(nil, nil) + require.NoError(t, err) + return image.UnparsedFromSource(&dirImageSourceMock{ + ImageSource: src, + ref: ref, + }) +} + +// dirImageSourceMock inherits dirImageSource, but overrides its Reference method. +type dirImageSourceMock struct { + types.ImageSource + ref types.ImageReference +} + +func (d *dirImageSourceMock) Reference() types.ImageReference { + return d.ref +} + +func TestPRSignedByIsSignatureAuthorAccepted(t *testing.T) { + ktGPG := SBKeyTypeGPGKeys + prm := NewPRMMatchExact() + testImage := dirImageMock(t, "fixtures/dir-img-valid", "testing/manifest:latest") + defer testImage.Close() + testImageSig, err := ioutil.ReadFile("fixtures/dir-img-valid/signature-1") + require.NoError(t, err) + + // Successful validation, with KeyData and KeyPath + pr, err := NewPRSignedByKeyPath(ktGPG, "fixtures/public-key.gpg", prm) + require.NoError(t, err) + sar, parsedSig, err := pr.isSignatureAuthorAccepted(testImage, testImageSig) + assertSARAccepted(t, sar, parsedSig, err, Signature{ + DockerManifestDigest: TestImageManifestDigest, + DockerReference: "testing/manifest:latest", + }) + + keyData, err := ioutil.ReadFile("fixtures/public-key.gpg") + require.NoError(t, err) + pr, err = NewPRSignedByKeyData(ktGPG, keyData, prm) + require.NoError(t, err) + sar, parsedSig, err = pr.isSignatureAuthorAccepted(testImage, testImageSig) + assertSARAccepted(t, sar, parsedSig, err, Signature{ + DockerManifestDigest: TestImageManifestDigest, + DockerReference: "testing/manifest:latest", + }) + + // Unimplemented and invalid KeyType values + for _, keyType := range []sbKeyType{SBKeyTypeSignedByGPGKeys, + SBKeyTypeX509Certificates, + SBKeyTypeSignedByX509CAs, + sbKeyType("This is invalid"), + } { + // Do not use NewPRSignedByKeyData, because it would reject invalid values. + pr := &prSignedBy{ + KeyType: keyType, + KeyData: []byte("abc"), + SignedIdentity: prm, + } + // Pass nil pointers to, kind of, test that the return value does not depend on the parameters. + sar, parsedSig, err := pr.isSignatureAuthorAccepted(nil, nil) + assertSARRejected(t, sar, parsedSig, err) + } + + // Both KeyPath and KeyData set. Do not use NewPRSignedBy*, because it would reject this. + prSB := &prSignedBy{ + KeyType: ktGPG, + KeyPath: "/foo/bar", + KeyData: []byte("abc"), + SignedIdentity: prm, + } + // Pass nil pointers to, kind of, test that the return value does not depend on the parameters. + sar, parsedSig, err = prSB.isSignatureAuthorAccepted(nil, nil) + assertSARRejected(t, sar, parsedSig, err) + + // Invalid KeyPath + pr, err = NewPRSignedByKeyPath(ktGPG, "/this/does/not/exist", prm) + require.NoError(t, err) + // Pass nil pointers to, kind of, test that the return value does not depend on the parameters. + sar, parsedSig, err = pr.isSignatureAuthorAccepted(nil, nil) + assertSARRejected(t, sar, parsedSig, err) + + // Errors initializing the temporary GPG directory and mechanism are not obviously easy to reach. + + // KeyData has no public keys. + pr, err = NewPRSignedByKeyData(ktGPG, []byte{}, prm) + require.NoError(t, err) + // Pass nil pointers to, kind of, test that the return value does not depend on the parameters. + sar, parsedSig, err = pr.isSignatureAuthorAccepted(nil, nil) + assertSARRejectedPolicyRequirement(t, sar, parsedSig, err) + + // A signature which does not GPG verify + pr, err = NewPRSignedByKeyPath(ktGPG, "fixtures/public-key.gpg", prm) + require.NoError(t, err) + // Pass a nil pointer to, kind of, test that the return value does not depend on the image parmater.. + sar, parsedSig, err = pr.isSignatureAuthorAccepted(nil, []byte("invalid signature")) + assertSARRejected(t, sar, parsedSig, err) + + // A valid signature using an unknown key. + // (This is (currently?) rejected through the "mech.Verify fails" path, not the "!identityFound" path, + // because we use a temporary directory and only import the trusted keys.) + pr, err = NewPRSignedByKeyPath(ktGPG, "fixtures/public-key.gpg", prm) + require.NoError(t, err) + sig, err := ioutil.ReadFile("fixtures/unknown-key.signature") + require.NoError(t, err) + // Pass a nil pointer to, kind of, test that the return value does not depend on the image parmater.. + sar, parsedSig, err = pr.isSignatureAuthorAccepted(nil, sig) + assertSARRejected(t, sar, parsedSig, err) + + // A valid signature of an invalid JSON. + pr, err = NewPRSignedByKeyPath(ktGPG, "fixtures/public-key.gpg", prm) + require.NoError(t, err) + sig, err = ioutil.ReadFile("fixtures/invalid-blob.signature") + require.NoError(t, err) + // Pass a nil pointer to, kind of, test that the return value does not depend on the image parmater.. + sar, parsedSig, err = pr.isSignatureAuthorAccepted(nil, sig) + assertSARRejected(t, sar, parsedSig, err) + assert.IsType(t, InvalidSignatureError{}, err) + + // A valid signature with a rejected identity. + nonmatchingPRM, err := NewPRMExactReference("this/doesnt:match") + require.NoError(t, err) + pr, err = NewPRSignedByKeyPath(ktGPG, "fixtures/public-key.gpg", nonmatchingPRM) + require.NoError(t, err) + sar, parsedSig, err = pr.isSignatureAuthorAccepted(testImage, testImageSig) + assertSARRejectedPolicyRequirement(t, sar, parsedSig, err) + + // Error reading image manifest + image := dirImageMock(t, "fixtures/dir-img-no-manifest", "testing/manifest:latest") + defer image.Close() + sig, err = ioutil.ReadFile("fixtures/dir-img-no-manifest/signature-1") + require.NoError(t, err) + pr, err = NewPRSignedByKeyPath(ktGPG, "fixtures/public-key.gpg", prm) + require.NoError(t, err) + sar, parsedSig, err = pr.isSignatureAuthorAccepted(image, sig) + assertSARRejected(t, sar, parsedSig, err) + + // Error computing manifest digest + image = dirImageMock(t, "fixtures/dir-img-manifest-digest-error", "testing/manifest:latest") + defer image.Close() + sig, err = ioutil.ReadFile("fixtures/dir-img-manifest-digest-error/signature-1") + require.NoError(t, err) + pr, err = NewPRSignedByKeyPath(ktGPG, "fixtures/public-key.gpg", prm) + require.NoError(t, err) + sar, parsedSig, err = pr.isSignatureAuthorAccepted(image, sig) + assertSARRejected(t, sar, parsedSig, err) + + // A valid signature with a non-matching manifest + image = dirImageMock(t, "fixtures/dir-img-modified-manifest", "testing/manifest:latest") + defer image.Close() + sig, err = ioutil.ReadFile("fixtures/dir-img-modified-manifest/signature-1") + require.NoError(t, err) + pr, err = NewPRSignedByKeyPath(ktGPG, "fixtures/public-key.gpg", prm) + require.NoError(t, err) + sar, parsedSig, err = pr.isSignatureAuthorAccepted(image, sig) + assertSARRejectedPolicyRequirement(t, sar, parsedSig, err) +} + +// createInvalidSigDir creates a directory suitable for dirImageMock, in which image.Signatures() +// fails. +// The caller should eventually call os.RemoveAll on the returned path. +func createInvalidSigDir(t *testing.T) string { + dir, err := ioutil.TempDir("", "skopeo-test-unreadable-signature") + require.NoError(t, err) + err = ioutil.WriteFile(path.Join(dir, "manifest.json"), []byte("{}"), 0644) + require.NoError(t, err) + // Creating a 000-permissions file would work for unprivileged accounts, but root (in particular, + // in the Docker container we use for testing) would still have access. So, create a symlink + // pointing to itself, to cause an ELOOP. (Note that a symlink pointing to a nonexistent file would be treated + // just like a nonexistent signature file, and not an error.) + err = os.Symlink("signature-1", path.Join(dir, "signature-1")) + require.NoError(t, err) + return dir +} + +func TestPRSignedByIsRunningImageAllowed(t *testing.T) { + ktGPG := SBKeyTypeGPGKeys + prm := NewPRMMatchExact() + + // A simple success case: single valid signature. + image := dirImageMock(t, "fixtures/dir-img-valid", "testing/manifest:latest") + defer image.Close() + pr, err := NewPRSignedByKeyPath(ktGPG, "fixtures/public-key.gpg", prm) + require.NoError(t, err) + allowed, err := pr.isRunningImageAllowed(image) + assertRunningAllowed(t, allowed, err) + + // Error reading signatures + invalidSigDir := createInvalidSigDir(t) + defer os.RemoveAll(invalidSigDir) + image = dirImageMock(t, invalidSigDir, "testing/manifest:latest") + defer image.Close() + pr, err = NewPRSignedByKeyPath(ktGPG, "fixtures/public-key.gpg", prm) + require.NoError(t, err) + allowed, err = pr.isRunningImageAllowed(image) + assertRunningRejected(t, allowed, err) + + // No signatures + image = dirImageMock(t, "fixtures/dir-img-unsigned", "testing/manifest:latest") + defer image.Close() + pr, err = NewPRSignedByKeyPath(ktGPG, "fixtures/public-key.gpg", prm) + require.NoError(t, err) + allowed, err = pr.isRunningImageAllowed(image) + assertRunningRejectedPolicyRequirement(t, allowed, err) + + // 1 invalid signature: use dir-img-valid, but a non-matching Docker reference + image = dirImageMock(t, "fixtures/dir-img-valid", "testing/manifest:notlatest") + defer image.Close() + pr, err = NewPRSignedByKeyPath(ktGPG, "fixtures/public-key.gpg", prm) + require.NoError(t, err) + allowed, err = pr.isRunningImageAllowed(image) + assertRunningRejectedPolicyRequirement(t, allowed, err) + + // 2 valid signatures + image = dirImageMock(t, "fixtures/dir-img-valid-2", "testing/manifest:latest") + defer image.Close() + pr, err = NewPRSignedByKeyPath(ktGPG, "fixtures/public-key.gpg", prm) + require.NoError(t, err) + allowed, err = pr.isRunningImageAllowed(image) + assertRunningAllowed(t, allowed, err) + + // One invalid, one valid signature (in this order) + image = dirImageMock(t, "fixtures/dir-img-mixed", "testing/manifest:latest") + defer image.Close() + pr, err = NewPRSignedByKeyPath(ktGPG, "fixtures/public-key.gpg", prm) + require.NoError(t, err) + allowed, err = pr.isRunningImageAllowed(image) + assertRunningAllowed(t, allowed, err) + + // 2 invalid signatures: use dir-img-valid-2, but a non-matching Docker reference + image = dirImageMock(t, "fixtures/dir-img-valid-2", "testing/manifest:notlatest") + defer image.Close() + pr, err = NewPRSignedByKeyPath(ktGPG, "fixtures/public-key.gpg", prm) + require.NoError(t, err) + allowed, err = pr.isRunningImageAllowed(image) + assertRunningRejectedPolicyRequirement(t, allowed, err) +} diff --git a/vendor/github.com/containers/image/signature/policy_eval_simple_test.go b/vendor/github.com/containers/image/signature/policy_eval_simple_test.go new file mode 100644 index 0000000000..aae4b6a8b8 --- /dev/null +++ b/vendor/github.com/containers/image/signature/policy_eval_simple_test.go @@ -0,0 +1,74 @@ +package signature + +import ( + "testing" + + "github.com/containers/image/docker/reference" + "github.com/containers/image/types" +) + +// nameOnlyImageMock is a mock of types.UnparsedImage which only allows transports.ImageName to work +type nameOnlyImageMock struct { + forbiddenImageMock +} + +func (nameOnlyImageMock) Reference() types.ImageReference { + return nameOnlyImageReferenceMock("== StringWithinTransport mock") +} + +// nameOnlyImageReferenceMock is a mock of types.ImageReference which only allows transports.ImageName to work, returning self. +type nameOnlyImageReferenceMock string + +func (ref nameOnlyImageReferenceMock) Transport() types.ImageTransport { + return nameImageTransportMock("== Transport mock") +} +func (ref nameOnlyImageReferenceMock) StringWithinTransport() string { + return string(ref) +} +func (ref nameOnlyImageReferenceMock) DockerReference() reference.Named { + panic("unexpected call to a mock function") +} +func (ref nameOnlyImageReferenceMock) PolicyConfigurationIdentity() string { + panic("unexpected call to a mock function") +} +func (ref nameOnlyImageReferenceMock) PolicyConfigurationNamespaces() []string { + panic("unexpected call to a mock function") +} +func (ref nameOnlyImageReferenceMock) NewImage(ctx *types.SystemContext) (types.Image, error) { + panic("unexpected call to a mock function") +} +func (ref nameOnlyImageReferenceMock) NewImageSource(ctx *types.SystemContext, requestedManifestMIMETypes []string) (types.ImageSource, error) { + panic("unexpected call to a mock function") +} +func (ref nameOnlyImageReferenceMock) NewImageDestination(ctx *types.SystemContext) (types.ImageDestination, error) { + panic("unexpected call to a mock function") +} +func (ref nameOnlyImageReferenceMock) DeleteImage(ctx *types.SystemContext) error { + panic("unexpected call to a mock function") +} + +func TestPRInsecureAcceptAnythingIsSignatureAuthorAccepted(t *testing.T) { + pr := NewPRInsecureAcceptAnything() + // Pass nil signature to, kind of, test that the return value does not depend on it. + sar, parsedSig, err := pr.isSignatureAuthorAccepted(nameOnlyImageMock{}, nil) + assertSARUnknown(t, sar, parsedSig, err) +} + +func TestPRInsecureAcceptAnythingIsRunningImageAllowed(t *testing.T) { + pr := NewPRInsecureAcceptAnything() + res, err := pr.isRunningImageAllowed(nameOnlyImageMock{}) + assertRunningAllowed(t, res, err) +} + +func TestPRRejectIsSignatureAuthorAccepted(t *testing.T) { + pr := NewPRReject() + // Pass nil signature to, kind of, test that the return value does not depend on it. + sar, parsedSig, err := pr.isSignatureAuthorAccepted(nameOnlyImageMock{}, nil) + assertSARRejectedPolicyRequirement(t, sar, parsedSig, err) +} + +func TestPRRejectIsRunningImageAllowed(t *testing.T) { + pr := NewPRReject() + res, err := pr.isRunningImageAllowed(nameOnlyImageMock{}) + assertRunningRejectedPolicyRequirement(t, res, err) +} diff --git a/vendor/github.com/containers/image/signature/policy_eval_test.go b/vendor/github.com/containers/image/signature/policy_eval_test.go new file mode 100644 index 0000000000..785d7900b5 --- /dev/null +++ b/vendor/github.com/containers/image/signature/policy_eval_test.go @@ -0,0 +1,488 @@ +package signature + +import ( + "fmt" + "os" + "testing" + + "github.com/containers/image/docker/policyconfiguration" + "github.com/containers/image/docker/reference" + "github.com/containers/image/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPolicyRequirementError(t *testing.T) { + // A stupid test just to keep code coverage + s := "test" + err := PolicyRequirementError(s) + assert.Equal(t, s, err.Error()) +} + +func TestPolicyContextChangeState(t *testing.T) { + pc, err := NewPolicyContext(&Policy{Default: PolicyRequirements{NewPRReject()}}) + require.NoError(t, err) + defer pc.Destroy() + + require.Equal(t, pcReady, pc.state) + err = pc.changeState(pcReady, pcInUse) + require.NoError(t, err) + + err = pc.changeState(pcReady, pcInUse) + require.Error(t, err) + + // Return state to pcReady to allow pc.Destroy to clean up. + err = pc.changeState(pcInUse, pcReady) + require.NoError(t, err) +} + +func TestPolicyContextNewDestroy(t *testing.T) { + pc, err := NewPolicyContext(&Policy{Default: PolicyRequirements{NewPRReject()}}) + require.NoError(t, err) + assert.Equal(t, pcReady, pc.state) + + err = pc.Destroy() + require.NoError(t, err) + assert.Equal(t, pcDestroyed, pc.state) + + // Trying to destroy when not pcReady + pc, err = NewPolicyContext(&Policy{Default: PolicyRequirements{NewPRReject()}}) + require.NoError(t, err) + err = pc.changeState(pcReady, pcInUse) + require.NoError(t, err) + err = pc.Destroy() + require.Error(t, err) + assert.Equal(t, pcInUse, pc.state) // The state, and hopefully nothing else, has changed. + + err = pc.changeState(pcInUse, pcReady) + require.NoError(t, err) + err = pc.Destroy() + assert.NoError(t, err) +} + +// pcImageReferenceMock is a mock of types.ImageReference which returns itself in DockerReference +// and handles PolicyConfigurationIdentity and PolicyConfigurationReference consistently. +type pcImageReferenceMock struct { + transportName string + ref reference.Named +} + +func (ref pcImageReferenceMock) Transport() types.ImageTransport { + return nameImageTransportMock(ref.transportName) +} +func (ref pcImageReferenceMock) StringWithinTransport() string { + // We use this in error messages, so sadly we must return something. + return "== StringWithinTransport mock" +} +func (ref pcImageReferenceMock) DockerReference() reference.Named { + return ref.ref +} +func (ref pcImageReferenceMock) PolicyConfigurationIdentity() string { + res, err := policyconfiguration.DockerReferenceIdentity(ref.ref) + if res == "" || err != nil { + panic(fmt.Sprintf("Internal inconsistency: policyconfiguration.DockerReferenceIdentity returned %#v, %v", res, err)) + } + return res +} +func (ref pcImageReferenceMock) PolicyConfigurationNamespaces() []string { + if ref.ref == nil { + panic("unexpected call to a mock function") + } + return policyconfiguration.DockerReferenceNamespaces(ref.ref) +} +func (ref pcImageReferenceMock) NewImage(ctx *types.SystemContext) (types.Image, error) { + panic("unexpected call to a mock function") +} +func (ref pcImageReferenceMock) NewImageSource(ctx *types.SystemContext, requestedManifestMIMETypes []string) (types.ImageSource, error) { + panic("unexpected call to a mock function") +} +func (ref pcImageReferenceMock) NewImageDestination(ctx *types.SystemContext) (types.ImageDestination, error) { + panic("unexpected call to a mock function") +} +func (ref pcImageReferenceMock) DeleteImage(ctx *types.SystemContext) error { + panic("unexpected call to a mock function") +} + +func TestPolicyContextRequirementsForImageRef(t *testing.T) { + ktGPG := SBKeyTypeGPGKeys + prm := NewPRMMatchRepoDigestOrExact() + + policy := &Policy{ + Default: PolicyRequirements{NewPRReject()}, + Transports: map[string]PolicyTransportScopes{}, + } + // Just put _something_ into the PolicyTransportScopes map for the keys we care about, and make it pairwise + // distinct so that we can compare the values and show them when debugging the tests. + for _, t := range []struct{ transport, scope string }{ + {"docker", ""}, + {"docker", "unmatched"}, + {"docker", "deep.com"}, + {"docker", "deep.com/n1"}, + {"docker", "deep.com/n1/n2"}, + {"docker", "deep.com/n1/n2/n3"}, + {"docker", "deep.com/n1/n2/n3/repo"}, + {"docker", "deep.com/n1/n2/n3/repo:tag2"}, + {"atomic", "unmatched"}, + } { + if _, ok := policy.Transports[t.transport]; !ok { + policy.Transports[t.transport] = PolicyTransportScopes{} + } + policy.Transports[t.transport][t.scope] = PolicyRequirements{xNewPRSignedByKeyData(ktGPG, []byte(t.transport+t.scope), prm)} + } + + pc, err := NewPolicyContext(policy) + require.NoError(t, err) + + for _, c := range []struct{ inputTransport, input, matchedTransport, matched string }{ + // Full match + {"docker", "deep.com/n1/n2/n3/repo:tag2", "docker", "deep.com/n1/n2/n3/repo:tag2"}, + // Namespace matches + {"docker", "deep.com/n1/n2/n3/repo:nottag2", "docker", "deep.com/n1/n2/n3/repo"}, + {"docker", "deep.com/n1/n2/n3/notrepo:tag2", "docker", "deep.com/n1/n2/n3"}, + {"docker", "deep.com/n1/n2/notn3/repo:tag2", "docker", "deep.com/n1/n2"}, + {"docker", "deep.com/n1/notn2/n3/repo:tag2", "docker", "deep.com/n1"}, + // Host name match + {"docker", "deep.com/notn1/n2/n3/repo:tag2", "docker", "deep.com"}, + // Default + {"docker", "this.doesnt/match:anything", "docker", ""}, + // No match within a matched transport which doesn't have a "" scope + {"atomic", "this.doesnt/match:anything", "", ""}, + // No configuration available for this transport at all + {"dir", "what/ever", "", ""}, // "what/ever" is not a valid scope for the real "dir" transport, but we only need it to be a valid reference.Named. + } { + var expected PolicyRequirements + if c.matchedTransport != "" { + e, ok := policy.Transports[c.matchedTransport][c.matched] + require.True(t, ok, fmt.Sprintf("case %s:%s: expected reqs not found", c.inputTransport, c.input)) + expected = e + } else { + expected = policy.Default + } + + 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]) + // Do not use assert.Equal, which would do a deep contents comparison; we want to compare + // the pointers. Also, == does not work on slices; so test that the slices start at the + // same element and have the same length. + assert.True(t, &(reqs[0]) == &(expected[0]), comment) + assert.True(t, len(reqs) == len(expected), comment) + } +} + +// 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.ParseNormalizedNamed(dockerReference) + require.NoError(t, err) + return dirImageMockWithRef(t, dir, pcImageReferenceMock{"docker", ref}) +} + +func TestPolicyContextGetSignaturesWithAcceptedAuthor(t *testing.T) { + expectedSig := &Signature{ + DockerManifestDigest: TestImageManifestDigest, + DockerReference: "testing/manifest:latest", + } + + pc, err := NewPolicyContext(&Policy{ + Default: PolicyRequirements{NewPRReject()}, + Transports: map[string]PolicyTransportScopes{ + "docker": { + "docker.io/testing/manifest:latest": { + xNewPRSignedByKeyPath(SBKeyTypeGPGKeys, "fixtures/public-key.gpg", NewPRMMatchExact()), + }, + "docker.io/testing/manifest:twoAccepts": { + xNewPRSignedByKeyPath(SBKeyTypeGPGKeys, "fixtures/public-key.gpg", NewPRMMatchRepository()), + xNewPRSignedByKeyPath(SBKeyTypeGPGKeys, "fixtures/public-key.gpg", NewPRMMatchRepository()), + }, + "docker.io/testing/manifest:acceptReject": { + xNewPRSignedByKeyPath(SBKeyTypeGPGKeys, "fixtures/public-key.gpg", NewPRMMatchRepository()), + NewPRReject(), + }, + "docker.io/testing/manifest:acceptUnknown": { + xNewPRSignedByKeyPath(SBKeyTypeGPGKeys, "fixtures/public-key.gpg", NewPRMMatchRepository()), + xNewPRSignedBaseLayer(NewPRMMatchRepository()), + }, + "docker.io/testing/manifest:rejectUnknown": { + NewPRReject(), + xNewPRSignedBaseLayer(NewPRMMatchRepository()), + }, + "docker.io/testing/manifest:unknown": { + xNewPRSignedBaseLayer(NewPRMMatchRepository()), + }, + "docker.io/testing/manifest:unknown2": { + NewPRInsecureAcceptAnything(), + }, + "docker.io/testing/manifest:invalidEmptyRequirements": {}, + }, + }, + }) + require.NoError(t, err) + defer pc.Destroy() + + // Success + img := pcImageMock(t, "fixtures/dir-img-valid", "testing/manifest:latest") + defer img.Close() + sigs, err := pc.GetSignaturesWithAcceptedAuthor(img) + require.NoError(t, err) + assert.Equal(t, []*Signature{expectedSig}, sigs) + + // Two signatures + // FIXME? Use really different signatures for this? + img = pcImageMock(t, "fixtures/dir-img-valid-2", "testing/manifest:latest") + defer img.Close() + sigs, err = pc.GetSignaturesWithAcceptedAuthor(img) + require.NoError(t, err) + assert.Equal(t, []*Signature{expectedSig, expectedSig}, sigs) + + // No signatures + img = pcImageMock(t, "fixtures/dir-img-unsigned", "testing/manifest:latest") + defer img.Close() + sigs, err = pc.GetSignaturesWithAcceptedAuthor(img) + require.NoError(t, err) + assert.Empty(t, sigs) + + // Only invalid signatures + img = pcImageMock(t, "fixtures/dir-img-modified-manifest", "testing/manifest:latest") + defer img.Close() + sigs, err = pc.GetSignaturesWithAcceptedAuthor(img) + require.NoError(t, err) + assert.Empty(t, sigs) + + // 1 invalid, 1 valid signature (in this order) + img = pcImageMock(t, "fixtures/dir-img-mixed", "testing/manifest:latest") + defer img.Close() + sigs, err = pc.GetSignaturesWithAcceptedAuthor(img) + require.NoError(t, err) + assert.Equal(t, []*Signature{expectedSig}, sigs) + + // Two sarAccepted results for one signature + img = pcImageMock(t, "fixtures/dir-img-valid", "testing/manifest:twoAccepts") + defer img.Close() + sigs, err = pc.GetSignaturesWithAcceptedAuthor(img) + require.NoError(t, err) + assert.Equal(t, []*Signature{expectedSig}, sigs) + + // sarAccepted+sarRejected for a signature + img = pcImageMock(t, "fixtures/dir-img-valid", "testing/manifest:acceptReject") + defer img.Close() + sigs, err = pc.GetSignaturesWithAcceptedAuthor(img) + require.NoError(t, err) + assert.Empty(t, sigs) + + // sarAccepted+sarUnknown for a signature + img = pcImageMock(t, "fixtures/dir-img-valid", "testing/manifest:acceptUnknown") + defer img.Close() + sigs, err = pc.GetSignaturesWithAcceptedAuthor(img) + require.NoError(t, err) + assert.Equal(t, []*Signature{expectedSig}, sigs) + + // sarRejected+sarUnknown for a signature + img = pcImageMock(t, "fixtures/dir-img-valid", "testing/manifest:rejectUnknown") + defer img.Close() + sigs, err = pc.GetSignaturesWithAcceptedAuthor(img) + require.NoError(t, err) + assert.Empty(t, sigs) + + // sarUnknown only + img = pcImageMock(t, "fixtures/dir-img-valid", "testing/manifest:unknown") + defer img.Close() + sigs, err = pc.GetSignaturesWithAcceptedAuthor(img) + require.NoError(t, err) + assert.Empty(t, sigs) + + img = pcImageMock(t, "fixtures/dir-img-valid", "testing/manifest:unknown2") + defer img.Close() + sigs, err = pc.GetSignaturesWithAcceptedAuthor(img) + require.NoError(t, err) + assert.Empty(t, sigs) + + // Empty list of requirements (invalid) + img = pcImageMock(t, "fixtures/dir-img-valid", "testing/manifest:invalidEmptyRequirements") + defer img.Close() + sigs, err = pc.GetSignaturesWithAcceptedAuthor(img) + require.NoError(t, err) + assert.Empty(t, sigs) + + // Failures: Make sure we return nil sigs. + + // Unexpected state (context already destroyed) + destroyedPC, err := NewPolicyContext(pc.Policy) + require.NoError(t, err) + err = destroyedPC.Destroy() + require.NoError(t, err) + img = pcImageMock(t, "fixtures/dir-img-valid", "testing/manifest:latest") + defer img.Close() + sigs, err = destroyedPC.GetSignaturesWithAcceptedAuthor(img) + assert.Error(t, err) + assert.Nil(t, sigs) + // Not testing the pcInUse->pcReady transition, that would require custom PolicyRequirement + // implementations meddling with the state, or threads. This is for catching trivial programmer + // mistakes only, anyway. + + // Error reading signatures. + invalidSigDir := createInvalidSigDir(t) + defer os.RemoveAll(invalidSigDir) + img = pcImageMock(t, invalidSigDir, "testing/manifest:latest") + defer img.Close() + sigs, err = pc.GetSignaturesWithAcceptedAuthor(img) + assert.Error(t, err) + assert.Nil(t, sigs) +} + +func TestPolicyContextIsRunningImageAllowed(t *testing.T) { + pc, err := NewPolicyContext(&Policy{ + Default: PolicyRequirements{NewPRReject()}, + Transports: map[string]PolicyTransportScopes{ + "docker": { + "docker.io/testing/manifest:latest": { + xNewPRSignedByKeyPath(SBKeyTypeGPGKeys, "fixtures/public-key.gpg", NewPRMMatchExact()), + }, + "docker.io/testing/manifest:twoAllows": { + xNewPRSignedByKeyPath(SBKeyTypeGPGKeys, "fixtures/public-key.gpg", NewPRMMatchRepository()), + xNewPRSignedByKeyPath(SBKeyTypeGPGKeys, "fixtures/public-key.gpg", NewPRMMatchRepository()), + }, + "docker.io/testing/manifest:allowDeny": { + xNewPRSignedByKeyPath(SBKeyTypeGPGKeys, "fixtures/public-key.gpg", NewPRMMatchRepository()), + NewPRReject(), + }, + "docker.io/testing/manifest:reject": { + NewPRReject(), + }, + "docker.io/testing/manifest:acceptAnything": { + NewPRInsecureAcceptAnything(), + }, + "docker.io/testing/manifest:invalidEmptyRequirements": {}, + }, + }, + }) + require.NoError(t, err) + defer pc.Destroy() + + // Success + img := pcImageMock(t, "fixtures/dir-img-valid", "testing/manifest:latest") + defer img.Close() + res, err := pc.IsRunningImageAllowed(img) + assertRunningAllowed(t, res, err) + + // Two signatures + // FIXME? Use really different signatures for this? + img = pcImageMock(t, "fixtures/dir-img-valid-2", "testing/manifest:latest") + defer img.Close() + res, err = pc.IsRunningImageAllowed(img) + assertRunningAllowed(t, res, err) + + // No signatures + img = pcImageMock(t, "fixtures/dir-img-unsigned", "testing/manifest:latest") + defer img.Close() + res, err = pc.IsRunningImageAllowed(img) + assertRunningRejectedPolicyRequirement(t, res, err) + + // Only invalid signatures + img = pcImageMock(t, "fixtures/dir-img-modified-manifest", "testing/manifest:latest") + defer img.Close() + res, err = pc.IsRunningImageAllowed(img) + assertRunningRejectedPolicyRequirement(t, res, err) + + // 1 invalid, 1 valid signature (in this order) + img = pcImageMock(t, "fixtures/dir-img-mixed", "testing/manifest:latest") + defer img.Close() + res, err = pc.IsRunningImageAllowed(img) + assertRunningAllowed(t, res, err) + + // Two allowed results + img = pcImageMock(t, "fixtures/dir-img-mixed", "testing/manifest:twoAllows") + defer img.Close() + res, err = pc.IsRunningImageAllowed(img) + assertRunningAllowed(t, res, err) + + // Allow + deny results + img = pcImageMock(t, "fixtures/dir-img-mixed", "testing/manifest:allowDeny") + defer img.Close() + res, err = pc.IsRunningImageAllowed(img) + assertRunningRejectedPolicyRequirement(t, res, err) + + // prReject works + img = pcImageMock(t, "fixtures/dir-img-mixed", "testing/manifest:reject") + defer img.Close() + res, err = pc.IsRunningImageAllowed(img) + assertRunningRejectedPolicyRequirement(t, res, err) + + // prInsecureAcceptAnything works + img = pcImageMock(t, "fixtures/dir-img-mixed", "testing/manifest:acceptAnything") + defer img.Close() + res, err = pc.IsRunningImageAllowed(img) + assertRunningAllowed(t, res, err) + + // Empty list of requirements (invalid) + img = pcImageMock(t, "fixtures/dir-img-valid", "testing/manifest:invalidEmptyRequirements") + defer img.Close() + res, err = pc.IsRunningImageAllowed(img) + assertRunningRejectedPolicyRequirement(t, res, err) + + // Unexpected state (context already destroyed) + destroyedPC, err := NewPolicyContext(pc.Policy) + require.NoError(t, err) + err = destroyedPC.Destroy() + require.NoError(t, err) + img = pcImageMock(t, "fixtures/dir-img-valid", "testing/manifest:latest") + defer img.Close() + res, err = destroyedPC.IsRunningImageAllowed(img) + assertRunningRejected(t, res, err) + // Not testing the pcInUse->pcReady transition, that would require custom PolicyRequirement + // implementations meddling with the state, or threads. This is for catching trivial programmer + // mistakes only, anyway. +} + +// Helpers for validating PolicyRequirement.isSignatureAuthorAccepted results: + +// assertSARRejected verifies that isSignatureAuthorAccepted returns a consistent sarRejected result +// with the expected signature. +func assertSARAccepted(t *testing.T, sar signatureAcceptanceResult, parsedSig *Signature, err error, expectedSig Signature) { + assert.Equal(t, sarAccepted, sar) + assert.Equal(t, &expectedSig, parsedSig) + assert.NoError(t, err) +} + +// assertSARRejected verifies that isSignatureAuthorAccepted returns a consistent sarRejected result. +func assertSARRejected(t *testing.T, sar signatureAcceptanceResult, parsedSig *Signature, err error) { + assert.Equal(t, sarRejected, sar) + assert.Nil(t, parsedSig) + assert.Error(t, err) +} + +// assertSARRejectedPolicyRequiremnt verifies that isSignatureAuthorAccepted returns a consistent sarRejected resul, +// and that the returned error is a PolicyRequirementError.. +func assertSARRejectedPolicyRequirement(t *testing.T, sar signatureAcceptanceResult, parsedSig *Signature, err error) { + assertSARRejected(t, sar, parsedSig, err) + assert.IsType(t, PolicyRequirementError(""), err) +} + +// assertSARRejected verifies that isSignatureAuthorAccepted returns a consistent sarUnknown result. +func assertSARUnknown(t *testing.T, sar signatureAcceptanceResult, parsedSig *Signature, err error) { + assert.Equal(t, sarUnknown, sar) + assert.Nil(t, parsedSig) + assert.NoError(t, err) +} + +// Helpers for validating PolicyRequirement.isRunningImageAllowed results: + +// assertRunningAllowed verifies that isRunningImageAllowed returns a consistent true result +func assertRunningAllowed(t *testing.T, allowed bool, err error) { + assert.Equal(t, true, allowed) + assert.NoError(t, err) +} + +// assertRunningRejected verifies that isRunningImageAllowed returns a consistent false result +func assertRunningRejected(t *testing.T, allowed bool, err error) { + assert.Equal(t, false, allowed) + assert.Error(t, err) +} + +// assertRunningRejectedPolicyRequirement verifies that isRunningImageAllowed returns a consistent false result +// and that the returned error is a PolicyRequirementError. +func assertRunningRejectedPolicyRequirement(t *testing.T, allowed bool, err error) { + assertRunningRejected(t, allowed, err) + assert.IsType(t, PolicyRequirementError(""), 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 new file mode 100644 index 0000000000..295e8339a0 --- /dev/null +++ b/vendor/github.com/containers/image/signature/policy_reference_match_test.go @@ -0,0 +1,365 @@ +package signature + +import ( + "fmt" + "testing" + + "github.com/containers/image/docker/reference" + "github.com/containers/image/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + fullRHELRef = "registry.access.redhat.com/rhel7/rhel:7.2.3" + untaggedRHELRef = "registry.access.redhat.com/rhel7/rhel" + digestSuffix = "@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + digestSuffixOther = "@sha256:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +) + +func TestParseImageAndDockerReference(t *testing.T) { + const ( + ok1 = "busybox" + ok2 = fullRHELRef + bad1 = "UPPERCASE_IS_INVALID_IN_DOCKER_REFERENCES" + bad2 = "" + ) + // Success + ref, err := reference.ParseNormalizedNamed(ok1) + require.NoError(t, err) + r1, r2, err := parseImageAndDockerReference(refImageMock{ref}, ok2) + require.NoError(t, err) + assert.Equal(t, ok1, reference.FamiliarString(r1)) + assert.Equal(t, ok2, reference.FamiliarString(r2)) + + // Unidentified images are rejected. + _, _, err = parseImageAndDockerReference(refImageMock{nil}, ok2) + require.Error(t, err) + assert.IsType(t, PolicyRequirementError(""), err) + + // Failures + for _, refs := range [][]string{ + {bad1, ok2}, + {ok1, bad2}, + {bad1, bad2}, + } { + ref, err := reference.ParseNormalizedNamed(refs[0]) + if err == nil { + _, _, err := parseImageAndDockerReference(refImageMock{ref}, refs[1]) + assert.Error(t, err) + } + } +} + +// refImageMock is a mock of types.UnparsedImage which returns itself in Reference().DockerReference. +type refImageMock struct{ reference.Named } + +func (ref refImageMock) Reference() types.ImageReference { + return refImageReferenceMock{ref.Named} +} +func (ref refImageMock) Close() { + panic("unexpected call to a mock function") +} +func (ref refImageMock) Manifest() ([]byte, string, error) { + panic("unexpected call to a mock function") +} +func (ref refImageMock) Signatures() ([][]byte, error) { + panic("unexpected call to a mock function") +} + +// refImageReferenceMock is a mock of types.ImageReference which returns itself in DockerReference. +type refImageReferenceMock struct{ reference.Named } + +func (ref refImageReferenceMock) Transport() types.ImageTransport { + // 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") + } + panic("unexpected call to a mock function") +} +func (ref refImageReferenceMock) StringWithinTransport() string { + // 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. + if ref.Named == nil { + return "== StringWithinTransport for an image with no Docker support" + } + panic("unexpected call to a mock function") +} +func (ref refImageReferenceMock) DockerReference() reference.Named { + return ref.Named +} +func (ref refImageReferenceMock) PolicyConfigurationIdentity() string { + panic("unexpected call to a mock function") +} +func (ref refImageReferenceMock) PolicyConfigurationNamespaces() []string { + panic("unexpected call to a mock function") +} +func (ref refImageReferenceMock) NewImage(ctx *types.SystemContext) (types.Image, error) { + panic("unexpected call to a mock function") +} +func (ref refImageReferenceMock) NewImageSource(ctx *types.SystemContext, requestedManifestMIMETypes []string) (types.ImageSource, error) { + panic("unexpected call to a mock function") +} +func (ref refImageReferenceMock) NewImageDestination(ctx *types.SystemContext) (types.ImageDestination, error) { + panic("unexpected call to a mock function") +} +func (ref refImageReferenceMock) DeleteImage(ctx *types.SystemContext) error { + panic("unexpected call to a mock function") +} + +// nameImageTransportMock is a mock of types.ImageTransport which returns itself in Name. +type nameImageTransportMock string + +func (name nameImageTransportMock) Name() string { + return string(name) +} +func (name nameImageTransportMock) ParseReference(reference string) (types.ImageReference, error) { + panic("unexpected call to a mock function") +} +func (name nameImageTransportMock) ValidatePolicyConfigurationScope(scope string) error { + panic("unexpected call to a mock function") +} + +type prmSymmetricTableTest struct { + refA, refB string + result bool +} + +// Test cases for exact reference match. The behavior is supposed to be symmetric. +var prmExactMatchTestTable = []prmSymmetricTableTest{ + // Success, simple matches + {"busybox:latest", "busybox:latest", true}, + {fullRHELRef, fullRHELRef, true}, + {"busybox" + digestSuffix, "busybox" + digestSuffix, true}, // NOTE: This is not documented; signing digests is not recommended at this time. + // Non-canonical reference format is canonicalized + {"library/busybox:latest", "busybox:latest", true}, + {"docker.io/library/busybox:latest", "busybox:latest", true}, + {"library/busybox" + digestSuffix, "busybox" + digestSuffix, true}, + // Mismatch + {"busybox:latest", "busybox:notlatest", false}, + {"busybox:latest", "notbusybox:latest", false}, + {"busybox:latest", "hostname/library/busybox:notlatest", false}, + {"hostname/library/busybox:latest", "busybox:notlatest", false}, + {"busybox:latest", fullRHELRef, false}, + {"busybox" + digestSuffix, "notbusybox" + digestSuffix, false}, + {"busybox:latest", "busybox" + digestSuffix, false}, + {"busybox" + digestSuffix, "busybox" + digestSuffixOther, false}, + // NameOnly references + {"busybox", "busybox:latest", false}, + {"busybox", "busybox" + digestSuffix, false}, + {"busybox", "busybox", false}, + // 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, false}, + {"busybox:latest" + digestSuffix, "busybox" + digestSuffix, false}, + {"busybox:latest" + digestSuffix, "busybox:latest", false}, + // Invalid format + {"UPPERCASE_IS_INVALID_IN_DOCKER_REFERENCES", "busybox:latest", false}, + {"", "UPPERCASE_IS_INVALID_IN_DOCKER_REFERENCES", false}, + // Even if they are exactly equal, invalid values are rejected. + {"INVALID", "INVALID", false}, +} + +// Test cases for repository-only reference match. The behavior is supposed to be symmetric. +var prmRepositoryMatchTestTable = []prmSymmetricTableTest{ + // Success, simple matches + {"busybox:latest", "busybox:latest", true}, + {fullRHELRef, fullRHELRef, true}, + {"busybox" + digestSuffix, "busybox" + digestSuffix, true}, // NOTE: This is not documented; signing digests is not recommended at this time. + // Non-canonical reference format is canonicalized + {"library/busybox:latest", "busybox:latest", true}, + {"docker.io/library/busybox:latest", "busybox:latest", true}, + {"library/busybox" + digestSuffix, "busybox" + digestSuffix, true}, + // The same as above, but with mismatching tags + {"busybox:latest", "busybox:notlatest", true}, + {fullRHELRef + "tagsuffix", fullRHELRef, true}, + {"library/busybox:latest", "busybox:notlatest", true}, + {"busybox:latest", "library/busybox:notlatest", true}, + {"docker.io/library/busybox:notlatest", "busybox:latest", true}, + {"busybox:notlatest", "docker.io/library/busybox:latest", true}, + {"busybox:latest", "busybox" + digestSuffix, true}, + {"busybox" + digestSuffix, "busybox" + digestSuffixOther, true}, // Even this is accepted here. (This could more reasonably happen with two different digest algorithms.) + // The same as above, but with defaulted tags (should not actually happen) + {"busybox", "busybox:notlatest", true}, + {fullRHELRef, untaggedRHELRef, true}, + {"busybox", "busybox" + digestSuffix, true}, + {"library/busybox", "busybox", true}, + {"docker.io/library/busybox", "busybox", true}, + // Mismatch + {"busybox:latest", "notbusybox:latest", false}, + {"hostname/library/busybox:latest", "busybox:notlatest", false}, + {"busybox:latest", fullRHELRef, false}, + {"busybox" + digestSuffix, "notbusybox" + digestSuffix, false}, + // 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}, + {"busybox:latest" + digestSuffix, "busybox" + digestSuffix, true}, + {"busybox:latest" + digestSuffix, "busybox:latest", true}, + // Invalid format + {"UPPERCASE_IS_INVALID_IN_DOCKER_REFERENCES", "busybox:latest", false}, + {"", "UPPERCASE_IS_INVALID_IN_DOCKER_REFERENCES", false}, + // Even if they are exactly equal, invalid values are rejected. + {"INVALID", "INVALID", false}, +} + +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.ParseNormalizedNamed can not happen in practice. + parsedImageRef, err := reference.ParseNormalizedNamed(imageRef) + if err != nil { + return + } + res := prm.matchesDockerReference(refImageMock{parsedImageRef}, sigRef) + assert.Equal(t, result, res, fmt.Sprintf("%s vs. %s", imageRef, sigRef)) +} + +func TestPRMMatchExactMatchesDockerReference(t *testing.T) { + prm := NewPRMMatchExact() + for _, test := range prmExactMatchTestTable { + testImageAndSig(t, prm, test.refA, test.refB, test.result) + testImageAndSig(t, prm, test.refB, test.refA, test.result) + } + // Even if they are signed with an empty string as a reference, unidentified images are rejected. + res := prm.matchesDockerReference(refImageMock{nil}, "") + assert.False(t, res, `unidentified vs. ""`) +} + +func TestPMMMatchRepoDigestOrExactMatchesDockerReference(t *testing.T) { + prm := NewPRMMatchRepoDigestOrExact() + + // prmMatchRepoDigestOrExact is a middle ground between prmMatchExact and prmMatchRepository: + // It accepts anything prmMatchExact accepts,… + for _, test := range prmExactMatchTestTable { + if test.result == true { + testImageAndSig(t, prm, test.refA, test.refB, test.result) + testImageAndSig(t, prm, test.refB, test.refA, test.result) + } + } + // … and it rejects everything prmMatchRepository rejects. + for _, test := range prmRepositoryMatchTestTable { + if test.result == false { + testImageAndSig(t, prm, test.refA, test.refB, test.result) + testImageAndSig(t, prm, test.refB, test.refA, test.result) + } + } + + // The other cases, possibly assymetrical: + for _, test := range []struct { + imageRef, sigRef string + result bool + }{ + // Tag mismatch + {"busybox:latest", "busybox:notlatest", false}, + {fullRHELRef + "tagsuffix", fullRHELRef, false}, + {"library/busybox:latest", "busybox:notlatest", false}, + {"busybox:latest", "library/busybox:notlatest", false}, + {"docker.io/library/busybox:notlatest", "busybox:latest", false}, + {"busybox:notlatest", "docker.io/library/busybox:latest", false}, + // NameOnly references + {"busybox", "busybox:latest", false}, + {"busybox:latest", "busybox", false}, + {"busybox", "busybox" + digestSuffix, false}, + {"busybox" + digestSuffix, "busybox", false}, + {fullRHELRef, untaggedRHELRef, false}, + {"busybox", "busybox", false}, + // Tag references only accept signatures with matching tags. + {"busybox:latest", "busybox" + digestSuffix, false}, + // 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: 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, false}, + {"busybox:latest" + digestSuffix, "busybox:notlatest" + digestSuffixOther, false}, + } { + testImageAndSig(t, prm, test.imageRef, test.sigRef, test.result) + } +} + +func TestPRMMatchRepositoryMatchesDockerReference(t *testing.T) { + prm := NewPRMMatchRepository() + for _, test := range prmRepositoryMatchTestTable { + testImageAndSig(t, prm, test.refA, test.refB, test.result) + testImageAndSig(t, prm, test.refB, test.refA, test.result) + } + // Even if they are signed with an empty string as a reference, unidentified images are rejected. + res := prm.matchesDockerReference(refImageMock{nil}, "") + assert.False(t, res, `unidentified vs. ""`) +} + +func TestParseDockerReferences(t *testing.T) { + const ( + ok1 = "busybox" + ok2 = fullRHELRef + bad1 = "UPPERCASE_IS_INVALID_IN_DOCKER_REFERENCES" + bad2 = "" + ) + + // Success + r1, r2, err := parseDockerReferences(ok1, ok2) + require.NoError(t, err) + assert.Equal(t, ok1, reference.FamiliarString(r1)) + assert.Equal(t, ok2, reference.FamiliarString(r2)) + + // Failures + for _, refs := range [][]string{ + {bad1, ok2}, + {ok1, bad2}, + {bad1, bad2}, + } { + _, _, err := parseDockerReferences(refs[0], refs[1]) + assert.Error(t, err) + } +} + +// forbiddenImageMock is a mock of types.UnparsedImage which ensures Reference is not called +type forbiddenImageMock struct{} + +func (ref forbiddenImageMock) Reference() types.ImageReference { + panic("unexpected call to a mock function") +} +func (ref forbiddenImageMock) Close() { + panic("unexpected call to a mock function") +} +func (ref forbiddenImageMock) Manifest() ([]byte, string, error) { + panic("unexpected call to a mock function") +} +func (ref forbiddenImageMock) Signatures() ([][]byte, error) { + panic("unexpected call to a mock function") +} + +func testExactPRMAndSig(t *testing.T, prmFactory func(string) PolicyReferenceMatch, imageRef, sigRef string, result bool) { + prm := prmFactory(imageRef) + res := prm.matchesDockerReference(forbiddenImageMock{}, sigRef) + assert.Equal(t, result, res, fmt.Sprintf("%s vs. %s", imageRef, sigRef)) +} + +func prmExactReferenceFactory(ref string) PolicyReferenceMatch { + // Do not use NewPRMExactReference, we want to also test the case with an invalid DockerReference, + // even though NewPRMExactReference should never let it happen. + return &prmExactReference{DockerReference: ref} +} + +func TestPRMExactReferenceMatchesDockerReference(t *testing.T) { + for _, test := range prmExactMatchTestTable { + testExactPRMAndSig(t, prmExactReferenceFactory, test.refA, test.refB, test.result) + testExactPRMAndSig(t, prmExactReferenceFactory, test.refB, test.refA, test.result) + } +} + +func prmExactRepositoryFactory(ref string) PolicyReferenceMatch { + // Do not use NewPRMExactRepository, we want to also test the case with an invalid DockerReference, + // even though NewPRMExactRepository should never let it happen. + return &prmExactRepository{DockerRepository: ref} +} + +func TestPRMExactRepositoryMatchesDockerReference(t *testing.T) { + for _, test := range prmRepositoryMatchTestTable { + testExactPRMAndSig(t, prmExactRepositoryFactory, test.refA, test.refB, test.result) + testExactPRMAndSig(t, prmExactRepositoryFactory, test.refB, test.refA, test.result) + } +} diff --git a/vendor/github.com/containers/image/signature/signature_test.go b/vendor/github.com/containers/image/signature/signature_test.go new file mode 100644 index 0000000000..3ddcffe574 --- /dev/null +++ b/vendor/github.com/containers/image/signature/signature_test.go @@ -0,0 +1,377 @@ +package signature + +import ( + "encoding/json" + "io/ioutil" + "testing" + "time" + + "github.com/containers/image/version" + "github.com/opencontainers/go-digest" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestInvalidSignatureError(t *testing.T) { + // A stupid test just to keep code coverage + s := "test" + err := InvalidSignatureError{msg: s} + assert.Equal(t, s, err.Error()) +} + +func TestNewUntrustedSignature(t *testing.T) { + timeBefore := time.Now() + sig := newUntrustedSignature(TestImageManifestDigest, TestImageSignatureReference) + assert.Equal(t, TestImageManifestDigest, sig.UntrustedDockerManifestDigest) + assert.Equal(t, TestImageSignatureReference, sig.UntrustedDockerReference) + require.NotNil(t, sig.UntrustedCreatorID) + assert.Equal(t, "atomic "+version.Version, *sig.UntrustedCreatorID) + require.NotNil(t, sig.UntrustedTimestamp) + timeAfter := time.Now() + assert.True(t, timeBefore.Unix() <= *sig.UntrustedTimestamp) + assert.True(t, *sig.UntrustedTimestamp <= timeAfter.Unix()) +} + +func TestMarshalJSON(t *testing.T) { + // Empty string values + s := newUntrustedSignature("", "_") + _, err := s.MarshalJSON() + assert.Error(t, err) + s = newUntrustedSignature("_", "") + _, err = s.MarshalJSON() + assert.Error(t, err) + + // Success + // Use intermediate variables for these values so that we can take their addresses. + creatorID := "CREATOR" + timestamp := int64(1484683104) + for _, c := range []struct { + input untrustedSignature + expected string + }{ + { + untrustedSignature{ + UntrustedDockerManifestDigest: "digest!@#", + UntrustedDockerReference: "reference#@!", + UntrustedCreatorID: &creatorID, + UntrustedTimestamp: ×tamp, + }, + "{\"critical\":{\"identity\":{\"docker-reference\":\"reference#@!\"},\"image\":{\"docker-manifest-digest\":\"digest!@#\"},\"type\":\"atomic container signature\"},\"optional\":{\"creator\":\"CREATOR\",\"timestamp\":1484683104}}", + }, + { + untrustedSignature{ + UntrustedDockerManifestDigest: "digest!@#", + UntrustedDockerReference: "reference#@!", + }, + "{\"critical\":{\"identity\":{\"docker-reference\":\"reference#@!\"},\"image\":{\"docker-manifest-digest\":\"digest!@#\"},\"type\":\"atomic container signature\"},\"optional\":{}}", + }, + } { + marshaled, err := c.input.MarshalJSON() + require.NoError(t, err) + assert.Equal(t, []byte(c.expected), marshaled) + + // Also call MarshalJSON through the JSON package. + marshaled, err = json.Marshal(c.input) + assert.NoError(t, err) + assert.Equal(t, []byte(c.expected), marshaled) + } +} + +// Return the result of modifying validJSON with fn and unmarshaling it into *sig +func tryUnmarshalModifiedSignature(t *testing.T, sig *untrustedSignature, validJSON []byte, modifyFn func(mSI)) error { + var tmp mSI + err := json.Unmarshal(validJSON, &tmp) + require.NoError(t, err) + + modifyFn(tmp) + + testJSON, err := json.Marshal(tmp) + require.NoError(t, err) + + *sig = untrustedSignature{} + return json.Unmarshal(testJSON, sig) +} + +func TestUnmarshalJSON(t *testing.T) { + var s untrustedSignature + // Invalid input. Note that json.Unmarshal is guaranteed to validate input before calling our + // UnmarshalJSON implementation; so test that first, then test our error handling for completeness. + err := json.Unmarshal([]byte("&"), &s) + assert.Error(t, err) + err = s.UnmarshalJSON([]byte("&")) + assert.Error(t, err) + + // Not an object + err = json.Unmarshal([]byte("1"), &s) + assert.Error(t, err) + + // Start with a valid JSON. + validSig := newUntrustedSignature("digest!@#", "reference#@!") + validJSON, err := validSig.MarshalJSON() + require.NoError(t, err) + + // Success + s = untrustedSignature{} + err = json.Unmarshal(validJSON, &s) + require.NoError(t, err) + assert.Equal(t, validSig, s) + + // Various ways to corrupt the JSON + breakFns := []func(mSI){ + // A top-level field is missing + func(v mSI) { delete(v, "critical") }, + func(v mSI) { delete(v, "optional") }, + // Extra top-level sub-object + func(v mSI) { v["unexpected"] = 1 }, + // "critical" not an object + func(v mSI) { v["critical"] = 1 }, + // "optional" not an object + func(v mSI) { v["optional"] = 1 }, + // A field of "critical" is missing + func(v mSI) { delete(x(v, "critical"), "type") }, + func(v mSI) { delete(x(v, "critical"), "image") }, + func(v mSI) { delete(x(v, "critical"), "identity") }, + // Extra field of "critical" + func(v mSI) { x(v, "critical")["unexpected"] = 1 }, + // Invalid "type" + func(v mSI) { x(v, "critical")["type"] = 1 }, + func(v mSI) { x(v, "critical")["type"] = "unexpected" }, + // Invalid "image" object + func(v mSI) { x(v, "critical")["image"] = 1 }, + func(v mSI) { delete(x(v, "critical", "image"), "docker-manifest-digest") }, + func(v mSI) { x(v, "critical", "image")["unexpected"] = 1 }, + // Invalid "docker-manifest-digest" + func(v mSI) { x(v, "critical", "image")["docker-manifest-digest"] = 1 }, + // Invalid "identity" object + func(v mSI) { x(v, "critical")["identity"] = 1 }, + func(v mSI) { delete(x(v, "critical", "identity"), "docker-reference") }, + func(v mSI) { x(v, "critical", "identity")["unexpected"] = 1 }, + // Invalid "docker-reference" + func(v mSI) { x(v, "critical", "identity")["docker-reference"] = 1 }, + // Invalid "creator" + func(v mSI) { x(v, "optional")["creator"] = 1 }, + // Invalid "timestamp" + func(v mSI) { x(v, "optional")["timestamp"] = "unexpected" }, + } + for _, fn := range breakFns { + err = tryUnmarshalModifiedSignature(t, &s, validJSON, fn) + assert.Error(t, err) + } + + // Modifications to unrecognized fields in "optional" are allowed and ignored + allowedModificationFns := []func(mSI){ + // Add an optional field + func(v mSI) { x(v, "optional")["unexpected"] = 1 }, + } + for _, fn := range allowedModificationFns { + err = tryUnmarshalModifiedSignature(t, &s, validJSON, fn) + require.NoError(t, err) + assert.Equal(t, validSig, s) + } + + // Optional fields can be missing + validSig = untrustedSignature{ + UntrustedDockerManifestDigest: "digest!@#", + UntrustedDockerReference: "reference#@!", + UntrustedCreatorID: nil, + UntrustedTimestamp: nil, + } + validJSON, err = validSig.MarshalJSON() + require.NoError(t, err) + s = untrustedSignature{} + err = json.Unmarshal(validJSON, &s) + require.NoError(t, err) + assert.Equal(t, validSig, s) +} + +func TestSign(t *testing.T) { + mech, err := newGPGSigningMechanismInDirectory(testGPGHomeDirectory) + require.NoError(t, err) + + sig := newUntrustedSignature("digest!@#", "reference#@!") + + // Successful signing + signature, err := sig.sign(mech, TestKeyFingerprint) + require.NoError(t, err) + + verified, err := verifyAndExtractSignature(mech, signature, signatureAcceptanceRules{ + validateKeyIdentity: func(keyIdentity string) error { + if keyIdentity != TestKeyFingerprint { + return errors.Errorf("Unexpected keyIdentity") + } + return nil + }, + validateSignedDockerReference: func(signedDockerReference string) error { + if signedDockerReference != sig.UntrustedDockerReference { + return errors.Errorf("Unexpected signedDockerReference") + } + return nil + }, + validateSignedDockerManifestDigest: func(signedDockerManifestDigest digest.Digest) error { + if signedDockerManifestDigest != sig.UntrustedDockerManifestDigest { + return errors.Errorf("Unexpected signedDockerManifestDigest") + } + return nil + }, + }) + require.NoError(t, err) + + assert.Equal(t, sig.UntrustedDockerManifestDigest, verified.DockerManifestDigest) + assert.Equal(t, sig.UntrustedDockerReference, verified.DockerReference) + + // Error creating blob to sign + _, err = untrustedSignature{}.sign(mech, TestKeyFingerprint) + assert.Error(t, err) + + // Error signing + _, err = sig.sign(mech, "this fingerprint doesn't exist") + assert.Error(t, err) +} + +func TestVerifyAndExtractSignature(t *testing.T) { + mech, err := newGPGSigningMechanismInDirectory(testGPGHomeDirectory) + require.NoError(t, err) + + type triple struct { + keyIdentity string + signedDockerReference string + signedDockerManifestDigest digest.Digest + } + var wanted, recorded triple + // recordingRules are a plausible signatureAcceptanceRules implementations, but equally + // importantly record that we are passing the correct values to the rule callbacks. + recordingRules := signatureAcceptanceRules{ + validateKeyIdentity: func(keyIdentity string) error { + recorded.keyIdentity = keyIdentity + if keyIdentity != wanted.keyIdentity { + return errors.Errorf("keyIdentity mismatch") + } + return nil + }, + validateSignedDockerReference: func(signedDockerReference string) error { + recorded.signedDockerReference = signedDockerReference + if signedDockerReference != wanted.signedDockerReference { + return errors.Errorf("signedDockerReference mismatch") + } + return nil + }, + validateSignedDockerManifestDigest: func(signedDockerManifestDigest digest.Digest) error { + recorded.signedDockerManifestDigest = signedDockerManifestDigest + if signedDockerManifestDigest != wanted.signedDockerManifestDigest { + return errors.Errorf("signedDockerManifestDigest mismatch") + } + return nil + }, + } + + signature, err := ioutil.ReadFile("./fixtures/image.signature") + require.NoError(t, err) + signatureData := triple{ + keyIdentity: TestKeyFingerprint, + signedDockerReference: TestImageSignatureReference, + signedDockerManifestDigest: TestImageManifestDigest, + } + + // Successful verification + wanted = signatureData + recorded = triple{} + sig, err := verifyAndExtractSignature(mech, signature, recordingRules) + require.NoError(t, err) + assert.Equal(t, TestImageSignatureReference, sig.DockerReference) + assert.Equal(t, TestImageManifestDigest, sig.DockerManifestDigest) + assert.Equal(t, signatureData, recorded) + + // For extra paranoia, test that we return a nil signature object on error. + + // Completely invalid signature. + recorded = triple{} + sig, err = verifyAndExtractSignature(mech, []byte{}, recordingRules) + assert.Error(t, err) + assert.Nil(t, sig) + assert.Equal(t, triple{}, recorded) + + recorded = triple{} + sig, err = verifyAndExtractSignature(mech, []byte("invalid signature"), recordingRules) + assert.Error(t, err) + assert.Nil(t, sig) + assert.Equal(t, triple{}, recorded) + + // Valid signature of non-JSON: asked for keyIdentity, only + invalidBlobSignature, err := ioutil.ReadFile("./fixtures/invalid-blob.signature") + require.NoError(t, err) + recorded = triple{} + sig, err = verifyAndExtractSignature(mech, invalidBlobSignature, recordingRules) + assert.Error(t, err) + assert.Nil(t, sig) + assert.Equal(t, triple{keyIdentity: signatureData.keyIdentity}, recorded) + + // Valid signature with a wrong key: asked for keyIdentity, only + wanted = signatureData + wanted.keyIdentity = "unexpected fingerprint" + recorded = triple{} + sig, err = verifyAndExtractSignature(mech, signature, recordingRules) + assert.Error(t, err) + assert.Nil(t, sig) + assert.Equal(t, triple{keyIdentity: signatureData.keyIdentity}, recorded) + + // Valid signature with a wrong manifest digest: asked for keyIdentity and signedDockerManifestDigest + wanted = signatureData + wanted.signedDockerManifestDigest = "invalid digest" + recorded = triple{} + sig, err = verifyAndExtractSignature(mech, signature, recordingRules) + assert.Error(t, err) + assert.Nil(t, sig) + assert.Equal(t, triple{ + keyIdentity: signatureData.keyIdentity, + signedDockerManifestDigest: signatureData.signedDockerManifestDigest, + }, recorded) + + // Valid signature with a wrong image reference + wanted = signatureData + wanted.signedDockerReference = "unexpected docker reference" + recorded = triple{} + sig, err = verifyAndExtractSignature(mech, signature, recordingRules) + assert.Error(t, err) + assert.Nil(t, sig) + assert.Equal(t, signatureData, recorded) +} + +func TestGetUntrustedSignatureInformationWithoutVerifying(t *testing.T) { + signature, err := ioutil.ReadFile("./fixtures/image.signature") + require.NoError(t, err) + // Successful parsing, all optional fields present + info, err := GetUntrustedSignatureInformationWithoutVerifying(signature) + require.NoError(t, err) + assert.Equal(t, TestImageSignatureReference, info.UntrustedDockerReference) + assert.Equal(t, TestImageManifestDigest, info.UntrustedDockerManifestDigest) + assert.NotNil(t, info.UntrustedCreatorID) + assert.Equal(t, "atomic ", *info.UntrustedCreatorID) + assert.NotNil(t, info.UntrustedTimestamp) + assert.Equal(t, time.Unix(1458239713, 0), *info.UntrustedTimestamp) + assert.Equal(t, TestKeyShortID, info.UntrustedShortKeyIdentifier) + // Successful parsing, no optional fields present + signature, err = ioutil.ReadFile("./fixtures/no-optional-fields.signature") + require.NoError(t, err) + // Successful parsing + info, err = GetUntrustedSignatureInformationWithoutVerifying(signature) + require.NoError(t, err) + assert.Equal(t, TestImageSignatureReference, info.UntrustedDockerReference) + assert.Equal(t, TestImageManifestDigest, info.UntrustedDockerManifestDigest) + assert.Nil(t, info.UntrustedCreatorID) + assert.Nil(t, info.UntrustedTimestamp) + assert.Equal(t, TestKeyShortID, info.UntrustedShortKeyIdentifier) + + // Completely invalid signature. + _, err = GetUntrustedSignatureInformationWithoutVerifying([]byte{}) + assert.Error(t, err) + + _, err = GetUntrustedSignatureInformationWithoutVerifying([]byte("invalid signature")) + assert.Error(t, err) + + // Valid signature of non-JSON + invalidBlobSignature, err := ioutil.ReadFile("./fixtures/invalid-blob.signature") + require.NoError(t, err) + _, err = GetUntrustedSignatureInformationWithoutVerifying(invalidBlobSignature) + assert.Error(t, err) +} diff --git a/vendor/github.com/containers/image/storage/storage_reference_test.go b/vendor/github.com/containers/image/storage/storage_reference_test.go new file mode 100644 index 0000000000..ee4613414d --- /dev/null +++ b/vendor/github.com/containers/image/storage/storage_reference_test.go @@ -0,0 +1,97 @@ +package storage + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestStorageReferenceTransport(t *testing.T) { + newStore(t) + ref, err := Transport.ParseReference("busybox") + require.NoError(t, err) + transport := ref.Transport() + st, ok := transport.(*storageTransport) + require.True(t, ok) + assert.Equal(t, *(Transport.(*storageTransport)), *st) +} + +func TestStorageReferenceDockerReference(t *testing.T) { + ref, err := Transport.ParseReference("busybox") + require.NoError(t, err) + dr := ref.DockerReference() + require.NotNil(t, dr) + assert.Equal(t, "docker.io/library/busybox:latest", dr.String()) + + ref, err = Transport.ParseReference("@" + sha256digestHex) + require.NoError(t, err) + + dr = ref.DockerReference() + assert.Nil(t, dr) +} + +// A common list of reference formats to test for the various ImageReference methods. +var validReferenceTestCases = []struct { + input, canonical string + namespaces []string +}{ + { + "busybox", "docker.io/library/busybox:latest", + []string{"docker.io/library/busybox", "docker.io/library", "docker.io"}, + }, + { + "example.com/myns/ns2/busybox:notlatest", "example.com/myns/ns2/busybox:notlatest", + []string{"example.com/myns/ns2/busybox", "example.com/myns/ns2", "example.com/myns", "example.com"}, + }, + { + "@" + sha256digestHex, "@" + sha256digestHex, + []string{}, + }, + { + "busybox@" + sha256digestHex, "docker.io/library/busybox:latest@" + sha256digestHex, + []string{"docker.io/library/busybox:latest", "docker.io/library/busybox", "docker.io/library", "docker.io"}, + }, +} + +func TestStorageReferenceStringWithinTransport(t *testing.T) { + store := newStore(t) + storeSpec := fmt.Sprintf("[%s@%s]", store.GetGraphDriverName(), store.GetGraphRoot()) + + for _, c := range validReferenceTestCases { + ref, err := Transport.ParseReference(c.input) + require.NoError(t, err, c.input) + assert.Equal(t, storeSpec+c.canonical, ref.StringWithinTransport(), c.input) + } +} + +func TestStorageReferencePolicyConfigurationIdentity(t *testing.T) { + store := newStore(t) + storeSpec := fmt.Sprintf("[%s@%s]", store.GetGraphDriverName(), store.GetGraphRoot()) + + for _, c := range validReferenceTestCases { + ref, err := Transport.ParseReference(c.input) + require.NoError(t, err, c.input) + assert.Equal(t, storeSpec+c.canonical, ref.PolicyConfigurationIdentity(), c.input) + } +} + +func TestStorageReferencePolicyConfigurationNamespaces(t *testing.T) { + store := newStore(t) + storeSpec := fmt.Sprintf("[%s@%s]", store.GetGraphDriverName(), store.GetGraphRoot()) + + for _, c := range validReferenceTestCases { + ref, err := Transport.ParseReference(c.input) + require.NoError(t, err, c.input) + expectedNS := []string{} + for _, ns := range c.namespaces { + expectedNS = append(expectedNS, storeSpec+ns) + } + expectedNS = append(expectedNS, storeSpec) + expectedNS = append(expectedNS, fmt.Sprintf("[%s]", store.GetGraphRoot())) + assert.Equal(t, expectedNS, ref.PolicyConfigurationNamespaces()) + } +} + +// NewImage, NewImageSource, NewImageDestination, DeleteImage tested in storage_test.go diff --git a/vendor/github.com/containers/image/storage/storage_test.go b/vendor/github.com/containers/image/storage/storage_test.go new file mode 100644 index 0000000000..6d17728d24 --- /dev/null +++ b/vendor/github.com/containers/image/storage/storage_test.go @@ -0,0 +1,882 @@ +package storage + +import ( + "archive/tar" + "bytes" + "crypto/rand" + "crypto/sha256" + "flag" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/Sirupsen/logrus" + "github.com/containers/image/types" + "github.com/containers/storage/pkg/archive" + "github.com/containers/storage/pkg/idtools" + "github.com/containers/storage/pkg/ioutils" + "github.com/containers/storage/pkg/reexec" + "github.com/containers/storage/storage" + ddigest "github.com/opencontainers/go-digest" +) + +var ( + _imgd types.ImageDestination = &storageImageDestination{} + _imgs types.ImageSource = &storageImageSource{} + _ref types.ImageReference = &storageReference{} + _transport types.ImageTransport = &storageTransport{} + topwd = "" +) + +const ( + layerSize = 12345 +) + +func TestMain(m *testing.M) { + if reexec.Init() { + return + } + wd, err := ioutil.TempDir("", "test.") + if err != nil { + os.Exit(1) + } + topwd = wd + debug := false + flag.BoolVar(&debug, "debug", false, "print debug statements") + flag.Parse() + if debug { + logrus.SetLevel(logrus.DebugLevel) + } + code := m.Run() + os.RemoveAll(wd) + os.Exit(code) +} + +func newStore(t *testing.T) storage.Store { + wd, err := ioutil.TempDir(topwd, "test.") + if err != nil { + t.Fatal(err) + } + err = os.MkdirAll(wd, 0700) + if err != nil { + t.Fatal(err) + } + run := filepath.Join(wd, "run") + root := filepath.Join(wd, "root") + uidmap := []idtools.IDMap{{ + ContainerID: 0, + HostID: os.Getuid(), + Size: 1, + }} + gidmap := []idtools.IDMap{{ + ContainerID: 0, + HostID: os.Getgid(), + Size: 1, + }} + store, err := storage.GetStore(storage.StoreOptions{ + RunRoot: run, + GraphRoot: root, + GraphDriverName: "vfs", + GraphDriverOptions: []string{}, + UIDMap: uidmap, + GIDMap: gidmap, + }) + if err != nil { + t.Fatal(err) + } + Transport.SetStore(store) + return store +} + +func TestParse(t *testing.T) { + store := newStore(t) + + ref, err := Transport.ParseReference("test") + if err != nil { + t.Fatalf("ParseReference(%q) returned error %v", "test", err) + } + if ref == nil { + t.Fatalf("ParseReference returned nil reference") + } + + ref, err = Transport.ParseStoreReference(store, "test") + if err != nil { + t.Fatalf("ParseStoreReference(%q) returned error %v", "test", err) + } + if ref == nil { + t.Fatalf("ParseStoreReference(%q) returned nil reference", "test") + } + + strRef := ref.StringWithinTransport() + ref, err = Transport.ParseReference(strRef) + if err != nil { + t.Fatalf("ParseReference(%q) returned error: %v", strRef, err) + } + if ref == nil { + t.Fatalf("ParseReference(%q) returned nil reference", strRef) + } + + transport := storageTransport{ + store: store, + } + _references := []storageReference{ + { + name: ref.(*storageReference).name, + reference: verboseName(ref.(*storageReference).name), + id: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + transport: transport, + }, + { + name: ref.(*storageReference).name, + reference: verboseName(ref.(*storageReference).name), + transport: transport, + }, + { + id: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + transport: transport, + }, + { + name: ref.DockerReference(), + reference: verboseName(ref.DockerReference()), + transport: transport, + }, + } + for _, reference := range _references { + s := reference.StringWithinTransport() + ref, err := Transport.ParseStoreReference(store, s) + if err != nil { + t.Fatalf("ParseReference(%q) returned error: %v", strRef, err) + } + if ref.id != reference.id { + t.Fatalf("ParseReference(%q) failed to extract ID", s) + } + if ref.reference != reference.reference { + t.Fatalf("ParseReference(%q) failed to extract reference (%q!=%q)", s, ref.reference, reference.reference) + } + } +} + +func systemContext() *types.SystemContext { + return &types.SystemContext{} +} + +func makeLayer(t *testing.T, compression archive.Compression) (ddigest.Digest, int64, int64, []byte) { + var cwriter io.WriteCloser + var uncompressed *ioutils.WriteCounter + var twriter *tar.Writer + preader, pwriter := io.Pipe() + tbuffer := bytes.Buffer{} + if compression != archive.Uncompressed { + compressor, err := archive.CompressStream(pwriter, compression) + if err != nil { + t.Fatalf("Error compressing layer: %v", err) + } + cwriter = compressor + uncompressed = ioutils.NewWriteCounter(cwriter) + } else { + uncompressed = ioutils.NewWriteCounter(pwriter) + } + twriter = tar.NewWriter(uncompressed) + buf := make([]byte, layerSize) + n, err := rand.Read(buf) + if err != nil { + t.Fatalf("Error reading tar data: %v", err) + } + if n != len(buf) { + t.Fatalf("Short read reading tar data: %d < %d", n, len(buf)) + } + for i := 1024; i < 2048; i++ { + buf[i] = 0 + } + go func() { + defer pwriter.Close() + if cwriter != nil { + defer cwriter.Close() + } + defer twriter.Close() + err := twriter.WriteHeader(&tar.Header{ + Name: "/random-single-file", + Mode: 0600, + Size: int64(len(buf)), + ModTime: time.Now(), + AccessTime: time.Now(), + ChangeTime: time.Now(), + Typeflag: tar.TypeReg, + }) + if err != nil { + t.Fatalf("Error writing tar header: %v", err) + } + n, err := twriter.Write(buf) + if err != nil { + t.Fatalf("Error writing tar header: %v", err) + } + if n != len(buf) { + t.Fatalf("Short write writing tar header: %d < %d", n, len(buf)) + } + }() + _, err = io.Copy(&tbuffer, preader) + if err != nil { + t.Fatalf("Error reading layer tar: %v", err) + } + sum := ddigest.SHA256.FromBytes(tbuffer.Bytes()) + return sum, uncompressed.Count, int64(tbuffer.Len()), tbuffer.Bytes() +} + +func TestWriteRead(t *testing.T) { + if os.Geteuid() != 0 { + t.Skip("TestWriteRead requires root privileges") + } + + config := `{"config":{"labels":{}},"created":"2006-01-02T15:04:05Z"}` + sum := ddigest.SHA256.FromBytes([]byte(config)) + configInfo := types.BlobInfo{ + Digest: sum, + Size: int64(len(config)), + } + manifests := []string{ + //`{ + // "schemaVersion": 2, + // "mediaType": "application/vnd.oci.image.manifest.v1+json", + // "config": { + // "mediaType": "application/vnd.oci.image.serialization.config.v1+json", + // "size": %cs, + // "digest": "%ch" + // }, + // "layers": [ + // { + // "mediaType": "application/vnd.oci.image.serialization.rootfs.tar.gzip", + // "digest": "%lh", + // "size": %ls + // } + // ] + //}`, + `{ + "schemaVersion": 1, + "name": "test", + "tag": "latest", + "architecture": "amd64", + "fsLayers": [ + { + "blobSum": "%lh" + } + ], + "history": [ + { + "v1Compatibility": "{\"id\":\"%li\",\"created\":\"2016-03-03T11:29:44.222098366Z\",\"container\":\"\",\"container_config\":{\"Hostname\":\"56f0fe1dfc95\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"ExposedPorts\":null,\"PublishService\":\"\",\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":[\"/bin/sh\"],\"Image\":\"\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{}},\"docker_version\":\"1.8.2-fc22\",\"author\":\"\\\"William Temple \\u003cwtemple at redhat dot com\\u003e\\\"\",\"config\":{\"Hostname\":\"56f0fe1dfc95\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"ExposedPorts\":null,\"PublishService\":\"\",\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":null,\"Cmd\":null,\"Image\":\"\",\"Volumes\":null,\"VolumeDriver\":\"\",\"WorkingDir\":\"\",\"Entrypoint\":null,\"NetworkDisabled\":false,\"MacAddress\":\"\",\"OnBuild\":null,\"Labels\":{}},\"architecture\":\"amd64\",\"os\":\"linux\",\"Size\":%ls}" + } + ] + }`, + `{ + "schemaVersion": 2, + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "config": { + "mediaType": "application/vnd.docker.container.image.v1+json", + "size": %cs, + "digest": "%ch" + }, + "layers": [ + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "digest": "%lh", + "size": %ls + } + ] + }`, + } + signatures := [][]byte{ + []byte("Signature A"), + []byte("Signature B"), + } + newStore(t) + ref, err := Transport.ParseReference("test") + if err != nil { + t.Fatalf("ParseReference(%q) returned error %v", "test", err) + } + if ref == nil { + t.Fatalf("ParseReference returned nil reference") + } + + for _, manifestFmt := range manifests { + dest, err := ref.NewImageDestination(systemContext()) + if err != nil { + t.Fatalf("NewImageDestination(%q) returned error %v", ref.StringWithinTransport(), err) + } + if dest == nil { + t.Fatalf("NewImageDestination(%q) returned no destination", ref.StringWithinTransport()) + } + if dest.Reference().StringWithinTransport() != ref.StringWithinTransport() { + t.Fatalf("NewImageDestination(%q) changed the reference to %q", ref.StringWithinTransport(), dest.Reference().StringWithinTransport()) + } + t.Logf("supported manifest MIME types: %v", dest.SupportedManifestMIMETypes()) + if err := dest.SupportsSignatures(); err != nil { + t.Fatalf("Destination image doesn't support signatures: %v", err) + } + t.Logf("compress layers: %v", dest.ShouldCompressLayers()) + compression := archive.Uncompressed + if dest.ShouldCompressLayers() { + compression = archive.Gzip + } + digest, decompressedSize, size, blob := makeLayer(t, compression) + if _, err := dest.PutBlob(bytes.NewBuffer(blob), types.BlobInfo{ + Size: size, + Digest: digest, + }); err != nil { + t.Fatalf("Error saving randomly-generated layer to destination: %v", err) + } + t.Logf("Wrote randomly-generated layer %q (%d/%d bytes) to destination", digest, size, decompressedSize) + if _, err := dest.PutBlob(bytes.NewBufferString(config), configInfo); err != nil { + t.Fatalf("Error saving config to destination: %v", err) + } + manifest := strings.Replace(manifestFmt, "%lh", digest.String(), -1) + manifest = strings.Replace(manifest, "%ch", configInfo.Digest.String(), -1) + manifest = strings.Replace(manifest, "%ls", fmt.Sprintf("%d", size), -1) + manifest = strings.Replace(manifest, "%cs", fmt.Sprintf("%d", configInfo.Size), -1) + li := digest.Hex() + manifest = strings.Replace(manifest, "%li", li, -1) + manifest = strings.Replace(manifest, "%ci", sum.Hex(), -1) + t.Logf("this manifest is %q", manifest) + if err := dest.PutManifest([]byte(manifest)); err != nil { + t.Fatalf("Error saving manifest to destination: %v", err) + } + if err := dest.PutSignatures(signatures); err != nil { + t.Fatalf("Error saving signatures to destination: %v", err) + } + if err := dest.Commit(); err != nil { + t.Fatalf("Error committing changes to destination: %v", err) + } + dest.Close() + + img, err := ref.NewImage(systemContext()) + if err != nil { + t.Fatalf("NewImage(%q) returned error %v", ref.StringWithinTransport(), err) + } + imageConfigInfo := img.ConfigInfo() + if imageConfigInfo.Digest != "" { + blob, err := img.ConfigBlob() + if err != nil { + t.Fatalf("image %q claimed there was a config blob, but couldn't produce it: %v", ref.StringWithinTransport(), err) + } + sum := ddigest.SHA256.FromBytes(blob) + if sum != configInfo.Digest { + t.Fatalf("image config blob digest for %q doesn't match", ref.StringWithinTransport()) + } + if int64(len(blob)) != configInfo.Size { + t.Fatalf("image config size for %q changed from %d to %d", ref.StringWithinTransport(), configInfo.Size, len(blob)) + } + } + layerInfos := img.LayerInfos() + if layerInfos == nil { + t.Fatalf("image for %q returned empty layer list", ref.StringWithinTransport()) + } + imageInfo, err := img.Inspect() + if err != nil { + t.Fatalf("Inspect(%q) returned error %v", ref.StringWithinTransport(), err) + } + if imageInfo.Created.IsZero() { + t.Fatalf("Image %q claims to have been created at time 0", ref.StringWithinTransport()) + } + + src, err := ref.NewImageSource(systemContext(), []string{}) + if err != nil { + t.Fatalf("NewImageSource(%q) returned error %v", ref.StringWithinTransport(), err) + } + if src == nil { + t.Fatalf("NewImageSource(%q) returned no source", ref.StringWithinTransport()) + } + if src.Reference().StringWithinTransport() != ref.StringWithinTransport() { + // As long as it's only the addition of an ID suffix, that's okay. + if !strings.HasPrefix(src.Reference().StringWithinTransport(), ref.StringWithinTransport()+"@") { + t.Fatalf("NewImageSource(%q) changed the reference to %q", ref.StringWithinTransport(), src.Reference().StringWithinTransport()) + } + } + retrievedManifest, manifestType, err := src.GetManifest() + if err != nil { + t.Fatalf("GetManifest(%q) returned error %v", ref.StringWithinTransport(), err) + } + t.Logf("this manifest's type appears to be %q", manifestType) + if string(retrievedManifest) != manifest { + t.Fatalf("NewImageSource(%q) changed the manifest: %q was %q", ref.StringWithinTransport(), string(retrievedManifest), manifest) + } + sum = ddigest.SHA256.FromBytes([]byte(manifest)) + _, _, err = src.GetTargetManifest(sum) + if err == nil { + t.Fatalf("GetTargetManifest(%q) is supposed to fail", ref.StringWithinTransport()) + } + sigs, err := src.GetSignatures() + if err != nil { + t.Fatalf("GetSignatures(%q) returned error %v", ref.StringWithinTransport(), err) + } + if len(sigs) < len(signatures) { + t.Fatalf("Lost %d signatures", len(signatures)-len(sigs)) + } + if len(sigs) > len(signatures) { + t.Fatalf("Gained %d signatures", len(sigs)-len(signatures)) + } + for i := range sigs { + if bytes.Compare(sigs[i], signatures[i]) != 0 { + t.Fatalf("Signature %d was corrupted", i) + } + } + for _, layerInfo := range layerInfos { + buf := bytes.Buffer{} + layer, size, err := src.GetBlob(layerInfo) + if err != nil { + t.Fatalf("Error reading layer %q from %q", layerInfo.Digest, ref.StringWithinTransport()) + } + t.Logf("Decompressing blob %q, blob size = %d, layerInfo.Size = %d bytes", layerInfo.Digest, size, layerInfo.Size) + hasher := sha256.New() + compressed := ioutils.NewWriteCounter(hasher) + countedLayer := io.TeeReader(layer, compressed) + decompressed, err := archive.DecompressStream(countedLayer) + if err != nil { + t.Fatalf("Error decompressing layer %q from %q", layerInfo.Digest, ref.StringWithinTransport()) + } + n, err := io.Copy(&buf, decompressed) + if layerInfo.Size >= 0 && compressed.Count != layerInfo.Size { + t.Fatalf("Blob size is different than expected: %d != %d, read %d", compressed.Count, layerInfo.Size, n) + } + if size >= 0 && compressed.Count != size { + t.Fatalf("Blob size mismatch: %d != %d, read %d", compressed.Count, size, n) + } + sum := hasher.Sum(nil) + if ddigest.NewDigestFromBytes(ddigest.SHA256, sum) != layerInfo.Digest { + t.Fatalf("Layer blob digest for %q doesn't match", ref.StringWithinTransport()) + } + } + src.Close() + img.Close() + err = ref.DeleteImage(systemContext()) + if err != nil { + t.Fatalf("DeleteImage(%q) returned error %v", ref.StringWithinTransport(), err) + } + } +} + +func TestDuplicateName(t *testing.T) { + if os.Geteuid() != 0 { + t.Skip("TestDuplicateName requires root privileges") + } + + newStore(t) + + ref, err := Transport.ParseReference("test") + if err != nil { + t.Fatalf("ParseReference(%q) returned error %v", "test", err) + } + if ref == nil { + t.Fatalf("ParseReference returned nil reference") + } + + dest, err := ref.NewImageDestination(systemContext()) + if err != nil { + t.Fatalf("NewImageDestination(%q, first pass) returned error %v", ref.StringWithinTransport(), err) + } + if dest == nil { + t.Fatalf("NewImageDestination(%q, first pass) returned no destination", ref.StringWithinTransport()) + } + digest, _, size, blob := makeLayer(t, archive.Uncompressed) + if _, err := dest.PutBlob(bytes.NewBuffer(blob), types.BlobInfo{ + Size: size, + Digest: digest, + }); err != nil { + t.Fatalf("Error saving randomly-generated layer to destination, first pass: %v", err) + } + if err := dest.Commit(); err != nil { + t.Fatalf("Error committing changes to destination, first pass: %v", err) + } + dest.Close() + + dest, err = ref.NewImageDestination(systemContext()) + if err != nil { + t.Fatalf("NewImageDestination(%q, second pass) returned error %v", ref.StringWithinTransport(), err) + } + if dest == nil { + t.Fatalf("NewImageDestination(%q, second pass) returned no destination", ref.StringWithinTransport()) + } + if _, err := dest.PutBlob(bytes.NewBuffer(blob), types.BlobInfo{ + Size: int64(size), + Digest: digest, + }); err != nil { + t.Fatalf("Error saving randomly-generated layer to destination, second pass: %v", err) + } + if err := dest.Commit(); err != nil { + t.Fatalf("Error committing changes to destination, second pass: %v", err) + } + dest.Close() +} + +func TestDuplicateID(t *testing.T) { + if os.Geteuid() != 0 { + t.Skip("TestDuplicateID requires root privileges") + } + + newStore(t) + + ref, err := Transport.ParseReference("@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + if err != nil { + t.Fatalf("ParseReference(%q) returned error %v", "test", err) + } + if ref == nil { + t.Fatalf("ParseReference returned nil reference") + } + + dest, err := ref.NewImageDestination(systemContext()) + if err != nil { + t.Fatalf("NewImageDestination(%q, first pass) returned error %v", ref.StringWithinTransport(), err) + } + if dest == nil { + t.Fatalf("NewImageDestination(%q, first pass) returned no destination", ref.StringWithinTransport()) + } + digest, _, size, blob := makeLayer(t, archive.Gzip) + if _, err := dest.PutBlob(bytes.NewBuffer(blob), types.BlobInfo{ + Size: size, + Digest: digest, + }); err != nil { + t.Fatalf("Error saving randomly-generated layer to destination, first pass: %v", err) + } + if err := dest.Commit(); err != nil { + t.Fatalf("Error committing changes to destination, first pass: %v", err) + } + dest.Close() + + dest, err = ref.NewImageDestination(systemContext()) + if err != nil { + t.Fatalf("NewImageDestination(%q, second pass) returned error %v", ref.StringWithinTransport(), err) + } + if dest == nil { + t.Fatalf("NewImageDestination(%q, second pass) returned no destination", ref.StringWithinTransport()) + } + if _, err := dest.PutBlob(bytes.NewBuffer(blob), types.BlobInfo{ + Size: int64(size), + Digest: digest, + }); err != nil { + t.Fatalf("Error saving randomly-generated layer to destination, second pass: %v", err) + } + if err := dest.Commit(); err != storage.ErrDuplicateID { + if err != nil { + t.Fatalf("Wrong error committing changes to destination, second pass: %v", err) + } + t.Fatalf("Incorrectly succeeded committing changes to destination, second pass: %v", err) + } + dest.Close() +} + +func TestDuplicateNameID(t *testing.T) { + if os.Geteuid() != 0 { + t.Skip("TestDuplicateNameID requires root privileges") + } + + newStore(t) + + ref, err := Transport.ParseReference("test@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + if err != nil { + t.Fatalf("ParseReference(%q) returned error %v", "test", err) + } + if ref == nil { + t.Fatalf("ParseReference returned nil reference") + } + + dest, err := ref.NewImageDestination(systemContext()) + if err != nil { + t.Fatalf("NewImageDestination(%q, first pass) returned error %v", ref.StringWithinTransport(), err) + } + if dest == nil { + t.Fatalf("NewImageDestination(%q, first pass) returned no destination", ref.StringWithinTransport()) + } + digest, _, size, blob := makeLayer(t, archive.Gzip) + if _, err := dest.PutBlob(bytes.NewBuffer(blob), types.BlobInfo{ + Size: size, + Digest: digest, + }); err != nil { + t.Fatalf("Error saving randomly-generated layer to destination, first pass: %v", err) + } + if err := dest.Commit(); err != nil { + t.Fatalf("Error committing changes to destination, first pass: %v", err) + } + dest.Close() + + dest, err = ref.NewImageDestination(systemContext()) + if err != nil { + t.Fatalf("NewImageDestination(%q, second pass) returned error %v", ref.StringWithinTransport(), err) + } + if dest == nil { + t.Fatalf("NewImageDestination(%q, second pass) returned no destination", ref.StringWithinTransport()) + } + if _, err := dest.PutBlob(bytes.NewBuffer(blob), types.BlobInfo{ + Size: int64(size), + Digest: digest, + }); err != nil { + t.Fatalf("Error saving randomly-generated layer to destination, second pass: %v", err) + } + if err := dest.Commit(); err != storage.ErrDuplicateID { + if err != nil { + t.Fatalf("Wrong error committing changes to destination, second pass: %v", err) + } + t.Fatalf("Incorrectly succeeded committing changes to destination, second pass: %v", err) + } + dest.Close() +} + +func TestNamespaces(t *testing.T) { + newStore(t) + + ref, err := Transport.ParseReference("test@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + if err != nil { + t.Fatalf("ParseReference(%q) returned error %v", "test", err) + } + if ref == nil { + t.Fatalf("ParseReference returned nil reference") + } + + namespaces := ref.PolicyConfigurationNamespaces() + for _, namespace := range namespaces { + t.Logf("namespace: %q", namespace) + err = Transport.ValidatePolicyConfigurationScope(namespace) + if ref == nil { + t.Fatalf("ValidatePolicyConfigurationScope(%q) returned error: %v", namespace, err) + } + } + namespace := ref.StringWithinTransport() + t.Logf("ref: %q", namespace) + err = Transport.ValidatePolicyConfigurationScope(namespace) + if err != nil { + t.Fatalf("ValidatePolicyConfigurationScope(%q) returned error: %v", namespace, err) + } + for _, namespace := range []string{ + "@beefee", + ":miracle", + ":miracle@beefee", + "@beefee:miracle", + } { + t.Logf("invalid ref: %q", namespace) + err = Transport.ValidatePolicyConfigurationScope(namespace) + if err == nil { + t.Fatalf("ValidatePolicyConfigurationScope(%q) should have failed", namespace) + } + } +} + +func TestSize(t *testing.T) { + if os.Geteuid() != 0 { + t.Skip("TestSize requires root privileges") + } + + config := `{"config":{"labels":{}},"created":"2006-01-02T15:04:05Z"}` + sum := ddigest.SHA256.FromBytes([]byte(config)) + configInfo := types.BlobInfo{ + Digest: sum, + Size: int64(len(config)), + } + + newStore(t) + + ref, err := Transport.ParseReference("test") + if err != nil { + t.Fatalf("ParseReference(%q) returned error %v", "test", err) + } + if ref == nil { + t.Fatalf("ParseReference returned nil reference") + } + + dest, err := ref.NewImageDestination(systemContext()) + if err != nil { + t.Fatalf("NewImageDestination(%q) returned error %v", ref.StringWithinTransport(), err) + } + if dest == nil { + t.Fatalf("NewImageDestination(%q) returned no destination", ref.StringWithinTransport()) + } + digest1, _, size1, blob := makeLayer(t, archive.Gzip) + if _, err := dest.PutBlob(bytes.NewBuffer(blob), types.BlobInfo{ + Size: size1, + Digest: digest1, + }); err != nil { + t.Fatalf("Error saving randomly-generated layer 1 to destination: %v", err) + } + digest2, _, size2, blob := makeLayer(t, archive.Gzip) + if _, err := dest.PutBlob(bytes.NewBuffer(blob), types.BlobInfo{ + Size: size2, + Digest: digest2, + }); err != nil { + t.Fatalf("Error saving randomly-generated layer 2 to destination: %v", err) + } + manifest := fmt.Sprintf(` + { + "schemaVersion": 2, + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "config": { + "mediaType": "application/vnd.docker.container.image.v1+json", + "size": %d, + "digest": "%s" + }, + "layers": [ + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "digest": "%s", + "size": %d + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "digest": "%s", + "size": %d + } + ] + } + `, configInfo.Size, configInfo.Digest, digest1, size1, digest2, size2) + if err := dest.PutManifest([]byte(manifest)); err != nil { + t.Fatalf("Error storing manifest to destination: %v", err) + } + if err := dest.Commit(); err != nil { + t.Fatalf("Error committing changes to destination: %v", err) + } + dest.Close() + + img, err := ref.NewImage(systemContext()) + if err != nil { + t.Fatalf("NewImage(%q) returned error %v", ref.StringWithinTransport(), err) + } + usize, err := img.Size() + if usize == -1 || err != nil { + t.Fatalf("Error calculating image size: %v", err) + } + if int(usize) != layerSize*2+len(manifest) { + t.Fatalf("Unexpected image size: %d != %d + %d + %d", usize, layerSize, layerSize, len(manifest)) + } + img.Close() +} + +func TestDuplicateBlob(t *testing.T) { + if os.Geteuid() != 0 { + t.Skip("TestDuplicateBlob requires root privileges") + } + + config := `{"config":{"labels":{}},"created":"2006-01-02T15:04:05Z"}` + sum := ddigest.SHA256.FromBytes([]byte(config)) + configInfo := types.BlobInfo{ + Digest: sum, + Size: int64(len(config)), + } + + newStore(t) + + ref, err := Transport.ParseReference("test") + if err != nil { + t.Fatalf("ParseReference(%q) returned error %v", "test", err) + } + if ref == nil { + t.Fatalf("ParseReference returned nil reference") + } + + dest, err := ref.NewImageDestination(systemContext()) + if err != nil { + t.Fatalf("NewImageDestination(%q) returned error %v", ref.StringWithinTransport(), err) + } + if dest == nil { + t.Fatalf("NewImageDestination(%q) returned no destination", ref.StringWithinTransport()) + } + digest1, _, size1, blob1 := makeLayer(t, archive.Gzip) + if _, err := dest.PutBlob(bytes.NewBuffer(blob1), types.BlobInfo{ + Size: size1, + Digest: digest1, + }); err != nil { + t.Fatalf("Error saving randomly-generated layer 1 to destination (first copy): %v", err) + } + digest2, _, size2, blob2 := makeLayer(t, archive.Gzip) + if _, err := dest.PutBlob(bytes.NewBuffer(blob2), types.BlobInfo{ + Size: size2, + Digest: digest2, + }); err != nil { + t.Fatalf("Error saving randomly-generated layer 2 to destination (first copy): %v", err) + } + if _, err := dest.PutBlob(bytes.NewBuffer(blob1), types.BlobInfo{ + Size: size1, + Digest: digest1, + }); err != nil { + t.Fatalf("Error saving randomly-generated layer 1 to destination (second copy): %v", err) + } + if _, err := dest.PutBlob(bytes.NewBuffer(blob2), types.BlobInfo{ + Size: size2, + Digest: digest2, + }); err != nil { + t.Fatalf("Error saving randomly-generated layer 2 to destination (second copy): %v", err) + } + manifest := fmt.Sprintf(` + { + "schemaVersion": 2, + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "config": { + "mediaType": "application/vnd.docker.container.image.v1+json", + "size": %d, + "digest": "%s" + }, + "layers": [ + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "digest": "%s", + "size": %d + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "digest": "%s", + "size": %d + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "digest": "%s", + "size": %d + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "digest": "%s", + "size": %d + } + ] + } + `, configInfo.Size, configInfo.Digest, digest1, size1, digest2, size2, digest1, size1, digest2, size2) + if err := dest.PutManifest([]byte(manifest)); err != nil { + t.Fatalf("Error storing manifest to destination: %v", err) + } + if err := dest.Commit(); err != nil { + t.Fatalf("Error committing changes to destination: %v", err) + } + dest.Close() + + img, err := ref.NewImage(systemContext()) + if err != nil { + t.Fatalf("NewImage(%q) returned error %v", ref.StringWithinTransport(), err) + } + src, err := ref.NewImageSource(systemContext(), nil) + if err != nil { + t.Fatalf("NewImageSource(%q) returned error %v", ref.StringWithinTransport(), err) + } + source, ok := src.(*storageImageSource) + if !ok { + t.Fatalf("ImageSource is not a storage image") + } + layers := []string{} + for _, layerInfo := range img.LayerInfos() { + rc, _, layerID, err := source.getBlobAndLayerID(layerInfo) + if err != nil { + t.Fatalf("getBlobAndLayerID(%q) returned error %v", layerInfo.Digest, err) + } + io.Copy(ioutil.Discard, rc) + rc.Close() + layers = append(layers, layerID) + } + if len(layers) != 4 { + t.Fatalf("Incorrect number of layers: %d", len(layers)) + } + for i, layerID := range layers { + for j, otherID := range layers { + if i != j && layerID == otherID { + t.Fatalf("Layer IDs are not unique: %v", layers) + } + } + } + src.Close() + img.Close() +} diff --git a/vendor/github.com/containers/image/storage/storage_transport_test.go b/vendor/github.com/containers/image/storage/storage_transport_test.go new file mode 100644 index 0000000000..3fddff5ccc --- /dev/null +++ b/vendor/github.com/containers/image/storage/storage_transport_test.go @@ -0,0 +1,146 @@ +package storage + +import ( + "fmt" + "testing" + + "github.com/containers/image/docker/reference" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + sha256digestHex = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +) + +func TestTransportName(t *testing.T) { + assert.Equal(t, "containers-storage", Transport.Name()) +} + +func TestTransportParseStoreReference(t *testing.T) { + for _, c := range []struct{ input, expectedRef, expectedID string }{ + {"", "", ""}, // Empty input + // Handling of the store prefix + // FIXME? Should we be silently discarding input like this? + {"[unterminated", "", ""}, // Unterminated store specifier + {"[garbage]busybox", "docker.io/library/busybox:latest", ""}, // Store specifier is overridden by the store we pass to ParseStoreReference + + {"UPPERCASEISINVALID", "", ""}, // Invalid single-component name + {"sha256:" + sha256digestHex, "docker.io/library/sha256:" + sha256digestHex, ""}, // Valid single-component name; the hex part is not an ID unless it has a "@" prefix + {sha256digestHex, "", ""}, // Invalid single-component ID; not an ID without a "@" prefix, so it's parsed as a name, but names aren't allowed to look like IDs + {"@" + sha256digestHex, "", sha256digestHex}, // Valid single-component ID + {"sha256:ab", "docker.io/library/sha256:ab", ""}, // Valid single-component name, explicit tag + {"busybox", "docker.io/library/busybox:latest", ""}, // Valid single-component name, implicit tag + {"busybox:notlatest", "docker.io/library/busybox:notlatest", ""}, // Valid single-component name, explicit tag + {"docker.io/library/busybox:notlatest", "docker.io/library/busybox:notlatest", ""}, // Valid single-component name, everything explicit + + {"UPPERCASEISINVALID@" + sha256digestHex, "", ""}, // Invalid name in name@ID + {"busybox@ab", "", ""}, // Invalid ID in name@ID + {"busybox@", "", ""}, // Empty ID in name@ID + {"busybox@sha256:" + sha256digestHex, "", ""}, // This (a digested docker/docker reference format) is also invalid, since it's an invalid ID in name@ID + {"@" + sha256digestHex, "", sha256digestHex}, // Valid two-component name, with ID only + {"busybox@" + sha256digestHex, "docker.io/library/busybox:latest", sha256digestHex}, // Valid two-component name, implicit tag + {"busybox:notlatest@" + sha256digestHex, "docker.io/library/busybox:notlatest", sha256digestHex}, // Valid two-component name, explicit tag + {"docker.io/library/busybox:notlatest@" + sha256digestHex, "docker.io/library/busybox:notlatest", sha256digestHex}, // Valid two-component name, everything explicit + } { + storageRef, err := Transport.ParseStoreReference(Transport.(*storageTransport).store, c.input) + if c.expectedRef == "" && c.expectedID == "" { + assert.Error(t, err, c.input) + } else { + require.NoError(t, err, c.input) + assert.Equal(t, *(Transport.(*storageTransport)), storageRef.transport, c.input) + assert.Equal(t, c.expectedRef, storageRef.reference, c.input) + assert.Equal(t, c.expectedID, storageRef.id, c.input) + if c.expectedRef == "" { + assert.Nil(t, storageRef.name, c.input) + } else { + 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()) + } + } + } +} + +func TestTransportParseReference(t *testing.T) { + store := newStore(t) + driver := store.GetGraphDriverName() + root := store.GetGraphRoot() + + for _, c := range []struct{ prefix, expectedDriver, expectedRoot string }{ + {"", driver, root}, // Implicit store location prefix + {"[unterminated", "", ""}, // Unterminated store specifier + {"[]", "", ""}, // Empty store specifier + {"[relative/path]", "", ""}, // Non-absolute graph root path + {"[" + driver + "@relative/path]", "", ""}, // Non-absolute graph root path + {"[thisisunknown@" + root + "suffix2]", "", ""}, // Unknown graph driver + + // The next two could be valid, but aren't enough to allow GetStore() to locate a matching + // store, since the reference can't specify a RunRoot. Without one, GetStore() tries to + // match the GraphRoot (possibly combined with the driver name) against a Store that was + // previously opened using GetStore(), and we haven't done that. + // Future versions of the storage library will probably make this easier for locations that + // are shared, by caching the rest of the information inside the graph root so that it can + // be looked up later, but since this is a per-test temporary location, that won't help here. + //{"[" + root + "suffix1]", driver, root + "suffix1"}, // A valid root path + //{"[" + driver + "@" + root + "suffix3]", driver, root + "suffix3"}, // A valid root@graph pair + } { + ref, err := Transport.ParseReference(c.prefix + "busybox") + if c.expectedDriver == "" { + assert.Error(t, err, c.prefix) + } else { + require.NoError(t, err, c.prefix) + storageRef, ok := ref.(*storageReference) + require.True(t, ok, c.prefix) + assert.Equal(t, c.expectedDriver, storageRef.transport.store.GetGraphDriverName(), c.prefix) + assert.Equal(t, c.expectedRoot, storageRef.transport.store.GetGraphRoot(), c.prefix) + } + } +} + +func TestTransportValidatePolicyConfigurationScope(t *testing.T) { + store := newStore(t) + driver := store.GetGraphDriverName() + root := store.GetGraphRoot() + storeSpec := fmt.Sprintf("[%s@%s]", driver, root) // As computed in PolicyConfigurationNamespaces + + // Valid inputs + for _, scope := range []string{ + "[" + root + "suffix1]", // driverlessStoreSpec in PolicyConfigurationNamespaces + "[" + driver + "@" + root + "suffix3]", // storeSpec in PolicyConfigurationNamespaces + storeSpec + "sha256:ab", // Valid single-component name, explicit tag + storeSpec + "sha256:" + sha256digestHex, // Valid single-component ID with a longer explicit tag + storeSpec + "busybox", // Valid single-component name, implicit tag; NOTE that this non-canonical form would be interpreted as a scope for host busybox + storeSpec + "busybox:notlatest", // Valid single-component name, explicit tag; NOTE that this non-canonical form would be interpreted as a scope for host busybox + storeSpec + "docker.io/library/busybox:notlatest", // Valid single-component name, everything explicit + storeSpec + "busybox@" + sha256digestHex, // Valid two-component name, implicit tag; NOTE that this non-canonical form would be interpreted as a scope for host busybox (and never match) + storeSpec + "busybox:notlatest@" + sha256digestHex, // Valid two-component name, explicit tag; NOTE that this non-canonical form would be interpreted as a scope for host busybox (and never match) + storeSpec + "docker.io/library/busybox:notlatest@" + sha256digestHex, // Valid two-component name, everything explicit + } { + err := Transport.ValidatePolicyConfigurationScope(scope) + assert.NoError(t, err, scope) + } + + // Invalid inputs + for _, scope := range []string{ + "busybox", // Unprefixed reference + "[unterminated", // Unterminated store specifier + "[]", // Empty store specifier + "[relative/path]", // Non-absolute graph root path + "[" + driver + "@relative/path]", // Non-absolute graph root path + // "[thisisunknown@" + root + "suffix2]", // Unknown graph driver FIXME: validate against storage.ListGraphDrivers() once that's available + storeSpec + sha256digestHex, // Almost a valid single-component name, but rejected because it looks like an ID that's missing its "@" prefix + storeSpec + "@", // An incomplete two-component name + storeSpec + "@" + sha256digestHex, // A valid two-component name, but ID-only, so not a valid scope + + storeSpec + "UPPERCASEISINVALID", // Invalid single-component name + storeSpec + "UPPERCASEISINVALID@" + sha256digestHex, // Invalid name in name@ID + storeSpec + "busybox@ab", // Invalid ID in name@ID + storeSpec + "busybox@", // Empty ID in name@ID + storeSpec + "busybox@sha256:" + sha256digestHex, // This (in a digested docker/docker reference format) is also invalid; this can't actually be matched by a storageReference.PolicyConfigurationIdentity, so it should be rejected + } { + err := Transport.ValidatePolicyConfigurationScope(scope) + assert.Error(t, err, scope) + } +} diff --git a/vendor/github.com/containers/image/transports/transports_test.go b/vendor/github.com/containers/image/transports/transports_test.go new file mode 100644 index 0000000000..2daec0ddfa --- /dev/null +++ b/vendor/github.com/containers/image/transports/transports_test.go @@ -0,0 +1,46 @@ +package transports + +import ( + "testing" + + "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. + for _, name := range []string{ + "", // Empty + "busybox", // No transport name + ":busybox", // Empty transport name + "docker:", // Empty transport reference + } { + _, err := ParseImageName(name) + assert.Error(t, err, name) + } +} + +// A table-driven test summarizing the various transports' behavior. +func TestImageNameHandling(t *testing.T) { + for _, c := range []struct{ transport, input, roundtrip string }{ + {"dir", "/etc", "/etc"}, + {"docker", "//busybox", "//busybox:latest"}, + {"docker", "//busybox:notlatest", "//busybox:notlatest"}, // This also tests handling of multiple ":" characters + {"docker-daemon", "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"}, + {"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. + } { + fullInput := c.transport + ":" + c.input + ref, err := ParseImageName(fullInput) + require.NoError(t, err, fullInput) + s := ImageName(ref) + assert.Equal(t, c.transport+":"+c.roundtrip, s, fullInput) + } +}