diff --git a/conformance/00_conformance_suite_test.go b/conformance/00_conformance_suite_test.go index 4e1770b9..f98b9f9d 100644 --- a/conformance/00_conformance_suite_test.go +++ b/conformance/00_conformance_suite_test.go @@ -14,6 +14,7 @@ func TestConformance(t *testing.T) { test02Push() test03ContentDiscovery() test04ContentManagement() + test05Referrers() }) RegisterFailHandler(g.Fail) diff --git a/conformance/05_referrers.go b/conformance/05_referrers.go new file mode 100644 index 00000000..65ddf70a --- /dev/null +++ b/conformance/05_referrers.go @@ -0,0 +1,334 @@ +package conformance + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/bloodorangeio/reggie" + g "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + godigest "github.com/opencontainers/go-digest" +) + +var test05Referrers = func() { + g.Context(titleReferrers, func() { + + g.Context("Setup", func() { + g.Specify("Populate registry with empty JSON blob", func() { + SkipIfDisabled(referrers) + RunOnlyIf(runReferencesSetup) + // validate expected empty JSON blob digest + Expect(emptyJSONDescriptor.Digest).To(Equal(godigest.Digest("sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a"))) + req := client.NewRequest(reggie.POST, "/v2//blobs/uploads/") + resp, err := client.Do(req) + Expect(err).To(BeNil()) + req = client.NewRequest(reggie.PUT, resp.GetRelativeLocation()). + SetQueryParam("digest", emptyJSONDescriptor.Digest.String()). + SetHeader("Content-Type", "application/octet-stream"). + SetHeader("Content-Length", fmt.Sprintf("%d", emptyJSONDescriptor.Size)). + SetBody(emptyJSONBlob) + resp, err = client.Do(req) + Expect(err).To(BeNil()) + Expect(resp.StatusCode()).To(SatisfyAll( + BeNumerically(">=", 200), + BeNumerically("<", 300))) + }) + + g.Specify("Populate registry with reference blob before the image manifest is pushed", func() { + SkipIfDisabled(referrers) + RunOnlyIf(runReferencesSetup) + req := client.NewRequest(reggie.POST, "/v2//blobs/uploads/") + resp, err := client.Do(req) + Expect(err).To(BeNil()) + req = client.NewRequest(reggie.PUT, resp.GetRelativeLocation()). + SetQueryParam("digest", testRefBlobADigest). + SetHeader("Content-Type", "application/octet-stream"). + SetHeader("Content-Length", testRefBlobALength). + SetBody(testRefBlobA) + resp, err = client.Do(req) + Expect(err).To(BeNil()) + Expect(resp.StatusCode()).To(SatisfyAll( + BeNumerically(">=", 200), + BeNumerically("<", 300))) + }) + + g.Specify("Populate registry with test references manifest (config.MediaType = artifactType)", func() { + SkipIfDisabled(referrers) + RunOnlyIf(runReferencesSetup) + req := client.NewRequest(reggie.PUT, "/v2//manifests/", + reggie.WithReference(refsManifestAConfigArtifactDigest)). + SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json"). + SetBody(refsManifestAConfigArtifactContent) + resp, err := client.Do(req) + Expect(err).To(BeNil()) + Expect(resp.StatusCode()).To(SatisfyAll( + BeNumerically(">=", 200), + BeNumerically("<", 300))) + Expect(resp.Header().Get("OCI-Subject")).To(Equal(manifests[4].Digest)) + }) + + g.Specify("Populate registry with test references manifest (ArtifactType, config.MediaType = emptyJSON)", func() { + SkipIfDisabled(referrers) + RunOnlyIf(runReferencesSetup) + req := client.NewRequest(reggie.PUT, "/v2//manifests/", + reggie.WithReference(refsManifestALayerArtifactDigest)). + SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json"). + SetBody(refsManifestALayerArtifactContent) + resp, err := client.Do(req) + Expect(err).To(BeNil()) + Expect(resp.StatusCode()).To(SatisfyAll( + BeNumerically(">=", 200), + BeNumerically("<", 300))) + Expect(resp.Header().Get("OCI-Subject")).To(Equal(manifests[4].Digest)) + }) + + g.Specify("Populate registry with test blob", func() { + SkipIfDisabled(referrers) + RunOnlyIf(runReferencesSetup) + req := client.NewRequest(reggie.POST, "/v2//blobs/uploads/") + resp, err := client.Do(req) + Expect(err).To(BeNil()) + req = client.NewRequest(reggie.PUT, resp.GetRelativeLocation()). + SetQueryParam("digest", configs[4].Digest). + SetHeader("Content-Type", "application/octet-stream"). + SetHeader("Content-Length", configs[4].ContentLength). + SetBody(configs[4].Content) + resp, err = client.Do(req) + Expect(err).To(BeNil()) + Expect(resp.StatusCode()).To(SatisfyAll( + BeNumerically(">=", 200), + BeNumerically("<", 300))) + }) + + g.Specify("Populate registry with test layer", func() { + SkipIfDisabled(referrers) + RunOnlyIf(runReferencesSetup) + req := client.NewRequest(reggie.POST, "/v2//blobs/uploads/") + resp, err := client.Do(req) + Expect(err).To(BeNil()) + req = client.NewRequest(reggie.PUT, resp.GetRelativeLocation()). + SetQueryParam("digest", layerBlobDigest). + SetHeader("Content-Type", "application/octet-stream"). + SetHeader("Content-Length", layerBlobContentLength). + SetBody(layerBlobData) + resp, err = client.Do(req) + Expect(err).To(BeNil()) + Expect(resp.StatusCode()).To(SatisfyAll( + BeNumerically(">=", 200), + BeNumerically("<", 300))) + }) + + g.Specify("Populate registry with test manifest", func() { + SkipIfDisabled(referrers) + RunOnlyIf(runReferencesSetup) + tag := testTagName + req := client.NewRequest(reggie.PUT, "/v2//manifests/", + reggie.WithReference(tag)). + SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json"). + SetBody(manifests[4].Content) + resp, err := client.Do(req) + Expect(err).To(BeNil()) + Expect(resp.StatusCode()).To(SatisfyAll( + BeNumerically(">=", 200), + BeNumerically("<", 300))) + }) + + g.Specify("Populate registry with reference blob after the image manifest is pushed", func() { + SkipIfDisabled(referrers) + RunOnlyIf(runReferencesSetup) + req := client.NewRequest(reggie.POST, "/v2//blobs/uploads/") + resp, err := client.Do(req) + Expect(err).To(BeNil()) + req = client.NewRequest(reggie.PUT, resp.GetRelativeLocation()). + SetQueryParam("digest", testRefBlobBDigest). + SetHeader("Content-Type", "application/octet-stream"). + SetHeader("Content-Length", testRefBlobBLength). + SetBody(testRefBlobB) + resp, err = client.Do(req) + Expect(err).To(BeNil()) + Expect(resp.StatusCode()).To(SatisfyAll( + BeNumerically(">=", 200), + BeNumerically("<", 300))) + }) + + g.Specify("Populate registry with test references manifest (config.MediaType = artifactType)", func() { + SkipIfDisabled(referrers) + RunOnlyIf(runReferencesSetup) + req := client.NewRequest(reggie.PUT, "/v2//manifests/", + reggie.WithReference(refsManifestBConfigArtifactDigest)). + SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json"). + SetBody(refsManifestBConfigArtifactContent) + resp, err := client.Do(req) + Expect(err).To(BeNil()) + Expect(resp.StatusCode()).To(SatisfyAll( + BeNumerically(">=", 200), + BeNumerically("<", 300))) + Expect(resp.Header().Get("OCI-Subject")).To(Equal(manifests[4].Digest)) + }) + + g.Specify("Populate registry with test references manifest (ArtifactType, config.MediaType = emptyJSON)", func() { + SkipIfDisabled(referrers) + RunOnlyIf(runReferencesSetup) + req := client.NewRequest(reggie.PUT, "/v2//manifests/", + reggie.WithReference(refsManifestBLayerArtifactDigest)). + SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json"). + SetBody(refsManifestBLayerArtifactContent) + resp, err := client.Do(req) + Expect(err).To(BeNil()) + Expect(resp.StatusCode()).To(SatisfyAll( + BeNumerically(">=", 200), + BeNumerically("<", 300))) + Expect(resp.Header().Get("OCI-Subject")).To(Equal(manifests[4].Digest)) + }) + }) + + g.Context("Get references", func() { + g.Specify("GET request to nonexistent blob should result in empty 200 response", func() { + SkipIfDisabled(referrers) + req := client.NewRequest(reggie.GET, "/v2//referrers/", + reggie.WithDigest(dummyDigest)) + resp, err := client.Do(req) + Expect(err).To(BeNil()) + Expect(resp.StatusCode()).To(Equal(http.StatusOK)) + + var index Index + err = json.Unmarshal(resp.Body(), &index) + Expect(err).To(BeNil()) + Expect(len(index.Manifests)).To(BeZero()) + }) + + g.Specify("GET request to existing blob should yield 200", func() { + SkipIfDisabled(referrers) + req := client.NewRequest(reggie.GET, "/v2//referrers/", + reggie.WithDigest(manifests[4].Digest)) + resp, err := client.Do(req) + Expect(err).To(BeNil()) + Expect(resp.StatusCode()).To(Equal(http.StatusOK)) + if h := resp.Header().Get("Docker-Content-Digest"); h != "" { + Expect(h).To(Equal(configs[4].Digest)) + } + + var index Index + err = json.Unmarshal(resp.Body(), &index) + Expect(err).To(BeNil()) + Expect(len(index.Manifests)).To(Equal(4)) + Expect(index.Manifests[0].Digest).ToNot(Equal(index.Manifests[1].Digest)) + }) + + g.Specify("GET request to existing blob with filter should yield 200", func() { + SkipIfDisabled(referrers) + req := client.NewRequest(reggie.GET, "/v2//referrers/", + reggie.WithDigest(manifests[4].Digest)). + SetQueryParam("artifactType", testRefArtifactTypeA) + resp, err := client.Do(req) + Expect(err).To(BeNil()) + Expect(resp.StatusCode()).To(Equal(http.StatusOK)) + if h := resp.Header().Get("Docker-Content-Digest"); h != "" { + Expect(h).To(Equal(configs[4].Digest)) + } + + var index Index + err = json.Unmarshal(resp.Body(), &index) + Expect(err).To(BeNil()) + + // also check resp header "OCI-Filters-Applied: artifactType" denoting that an artifactType filter was applied + if resp.Header().Get("OCI-Filters-Applied") != "" { + Expect(len(index.Manifests)).To(Equal(2)) + Expect(resp.Header().Get("OCI-Filters-Applied")).To(Equal(testRefArtifactTypeA)) + } else { + Expect(len(index.Manifests)).To(Equal(4)) + Warn("filtering by artifact-type is not implemented") + } + }) + }) + + g.Context("Teardown", func() { + deleteReq := func(req *reggie.Request) { + SkipIfDisabled(push) + RunOnlyIf(runReferencesSetup) + resp, err := client.Do(req) + Expect(err).To(BeNil()) + Expect(resp.StatusCode()).To(SatisfyAny( + SatisfyAll( + BeNumerically(">=", 200), + BeNumerically("<", 300), + ), + Equal(http.StatusMethodNotAllowed), + )) + } + + if deleteManifestBeforeBlobs { + g.Specify("Delete manifest created in setup", func() { + SkipIfDisabled(referrers) + RunOnlyIf(runReferencesSetup) + req := client.NewRequest(reggie.DELETE, "/v2//manifests/", + reggie.WithReference(refsManifestAConfigArtifactDigest)) + deleteReq(req) + req = client.NewRequest(reggie.DELETE, "/v2//manifests/", + reggie.WithReference(refsManifestALayerArtifactDigest)) + deleteReq(req) + req = client.NewRequest(reggie.DELETE, "/v2//manifests/", reggie.WithDigest(manifests[4].Digest)) + deleteReq(req) + req = client.NewRequest(reggie.DELETE, "/v2//manifests/", + reggie.WithReference(refsManifestBConfigArtifactDigest)) + deleteReq(req) + req = client.NewRequest(reggie.DELETE, "/v2//manifests/", + reggie.WithReference(refsManifestBLayerArtifactDigest)) + deleteReq(req) + }) + } + + g.Specify("Delete config blob created in setup", func() { + SkipIfDisabled(referrers) + RunOnlyIf(runReferencesSetup) + req := client.NewRequest(reggie.DELETE, "/v2//blobs/", reggie.WithDigest(configs[4].Digest)) + deleteReq(req) + }) + + g.Specify("Delete layer blob created in setup", func() { + SkipIfDisabled(referrers) + RunOnlyIf(runReferencesSetup) + req := client.NewRequest(reggie.DELETE, "/v2//blobs/", reggie.WithDigest(layerBlobDigest)) + deleteReq(req) + }) + + g.Specify("Delete reference blob created in setup", func() { + SkipIfDisabled(referrers) + RunOnlyIf(runReferencesSetup) + req := client.NewRequest(reggie.DELETE, "/v2//blobs/", reggie.WithDigest(testRefBlobADigest)) + deleteReq(req) + req = client.NewRequest(reggie.DELETE, "/v2//blobs/", reggie.WithDigest(testRefBlobBDigest)) + deleteReq(req) + }) + + g.Specify("Delete empty JSON blob created in setup", func() { + SkipIfDisabled(referrers) + RunOnlyIf(runReferencesSetup) + req := client.NewRequest(reggie.DELETE, "/v2//blobs/", reggie.WithDigest(emptyJSONDescriptor.Digest.String())) + deleteReq(req) + }) + + if !deleteManifestBeforeBlobs { + g.Specify("Delete manifest created in setup", func() { + SkipIfDisabled(referrers) + RunOnlyIf(runReferencesSetup) + req := client.NewRequest(reggie.DELETE, "/v2//manifests/", + reggie.WithReference(refsManifestAConfigArtifactDigest)) + deleteReq(req) + req = client.NewRequest(reggie.DELETE, "/v2//manifests/", + reggie.WithReference(refsManifestALayerArtifactDigest)) + deleteReq(req) + req = client.NewRequest(reggie.DELETE, "/v2//manifests/", reggie.WithDigest(manifests[4].Digest)) + deleteReq(req) + req = client.NewRequest(reggie.DELETE, "/v2//manifests/", + reggie.WithReference(refsManifestBConfigArtifactDigest)) + deleteReq(req) + req = client.NewRequest(reggie.DELETE, "/v2//manifests/", + reggie.WithReference(refsManifestBLayerArtifactDigest)) + deleteReq(req) + }) + } + }) + }) +} diff --git a/conformance/go.mod b/conformance/go.mod index f81ab142..b62101a2 100644 --- a/conformance/go.mod +++ b/conformance/go.mod @@ -12,8 +12,11 @@ require ( require ( github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/fatih/color v1.15.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-resty/resty/v2 v2.7.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/nxadm/tail v1.4.8 // indirect golang.org/x/net v0.8.0 // indirect diff --git a/conformance/go.sum b/conformance/go.sum index 2d74f145..614d2bdc 100644 --- a/conformance/go.sum +++ b/conformance/go.sum @@ -4,6 +4,8 @@ github.com/bloodorangeio/reggie v0.6.1 h1:rSpfPN8oU9kflRI7aQVjImjhY5meRsXDIXnJQr github.com/bloodorangeio/reggie v0.6.1/go.mod h1:Jkvg7UBdlXVNOlvXU6hgysdtG1XNVCB3B4/k7+PtlfM= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= @@ -28,6 +30,11 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= @@ -87,6 +94,7 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= diff --git a/conformance/image.go b/conformance/image.go index d00fad6a..169d7b4f 100644 --- a/conformance/image.go +++ b/conformance/image.go @@ -14,12 +14,18 @@ type Manifest struct { // SchemaVersion is the image manifest schema that this image follows SchemaVersion int `json:"schemaVersion"` + // ArtifactType specifies the IANA media type of artifact when the manifest is used for an artifact. + ArtifactType string `json:"artifactType,omitempty"` + // Config references a configuration object for a container, by digest. // The referenced configuration object is a JSON blob that the runtime uses to set up the container. Config Descriptor `json:"config"` // Layers is an indexed list of layers referenced by the manifest. Layers []Descriptor `json:"layers"` + + // Subject is an optional link from the image manifest to another manifest forming an association between the image manifest and the other manifest. + Subject *Descriptor `json:"subject,omitempty"` } // Descriptor describes the disposition of targeted content. @@ -71,3 +77,19 @@ type Image struct { // RootFS references the layer content addresses used by the image. RootFS RootFS `json:"rootfs"` } + +// Index references manifests for various platforms. +// This structure provides `application/vnd.oci.image.index.v1+json` mediatype when marshalled to JSON. +type Index struct { + // SchemaVersion is the image manifest schema that this image follows + SchemaVersion int `json:"schemaVersion"` + + // MediaType specifies the type of this document data structure e.g. `application/vnd.oci.image.index.v1+json` + MediaType string `json:"mediaType,omitempty"` + + // Manifests references platform specific manifests. + Manifests []Descriptor `json:"manifests"` + + // Annotations contains arbitrary metadata for the image index. + Annotations map[string]string `json:"annotations,omitempty"` +} diff --git a/conformance/setup.go b/conformance/setup.go index 6395c4b2..fc1725d7 100644 --- a/conformance/setup.go +++ b/conformance/setup.go @@ -10,11 +10,15 @@ import ( "math/big" mathrand "math/rand" "os" + "runtime" "strconv" "github.com/bloodorangeio/reggie" + "github.com/fatih/color" "github.com/google/uuid" g "github.com/onsi/ginkgo" + "github.com/onsi/ginkgo/formatter" + "github.com/onsi/ginkgo/reporters/stenographer/support/go-colorable" godigest "github.com/opencontainers/go-digest" ) @@ -36,6 +40,8 @@ const ( push contentDiscovery contentManagement + referrers + numWorkflows BLOB_UNKNOWN = iota BLOB_UPLOAD_INVALID @@ -72,6 +78,7 @@ const ( envVarDeleteManifestBeforeBlobs = "OCI_DELETE_MANIFEST_BEFORE_BLOBS" envVarCrossmountNamespace = "OCI_CROSSMOUNT_NAMESPACE" envVarAutomaticCrossmount = "OCI_AUTOMATIC_CROSSMOUNT" + envVarReferrers = "OCI_REFERRERS" emptyLayerTestTag = "emptylayer" testTagName = "tagtest0" @@ -80,6 +87,7 @@ const ( titlePush = "Push" titleContentDiscovery = "Content Discovery" titleContentManagement = "Content Management" + titleReferrers = "Referrers" // layerBase64String is a base64 encoding of a simple tarball, obtained like this: // $ echo 'you bothered to find out what was in here. Congratulations!' > test.txt @@ -96,46 +104,66 @@ var ( envVarPush: push, envVarContentDiscovery: contentDiscovery, envVarContentManagement: contentManagement, + envVarReferrers: referrers, } - testBlobA []byte - testBlobALength string - testBlobADigest string - testBlobB []byte - testBlobBDigest string - testBlobBChunk1 []byte - testBlobBChunk1Length string - testBlobBChunk2 []byte - testBlobBChunk2Length string - testBlobBChunk1Range string - testBlobBChunk2Range string - client *reggie.Client - crossmountNamespace string - dummyDigest string - errorCodes []string - invalidManifestContent []byte - layerBlobData []byte - layerBlobDigest string - layerBlobContentLength string - emptyLayerManifestContent []byte - nonexistentManifest string - reportJUnitFilename string - reportHTMLFilename string - httpWriter *httpDebugWriter - testsToRun int - suiteDescription string - runPullSetup bool - runPushSetup bool - runContentDiscoverySetup bool - runContentManagementSetup bool - skipEmptyLayerTest bool - deleteManifestBeforeBlobs bool - runAutomaticCrossmountTest bool - automaticCrossmountEnabled bool - configs []TestBlob - manifests []TestBlob - seed int64 - Version = "unknown" + testBlobA []byte + testBlobALength string + testBlobADigest string + testRefBlobA []byte + testRefBlobALength string + testRefBlobADigest string + testRefArtifactTypeA string + testRefArtifactTypeB string + testRefBlobB []byte + testRefBlobBLength string + testRefBlobBDigest string + testBlobB []byte + testBlobBDigest string + testBlobBChunk1 []byte + testBlobBChunk1Length string + testBlobBChunk2 []byte + testBlobBChunk2Length string + testBlobBChunk1Range string + testBlobBChunk2Range string + client *reggie.Client + crossmountNamespace string + dummyDigest string + errorCodes []string + invalidManifestContent []byte + layerBlobData []byte + layerBlobDigest string + layerBlobContentLength string + emptyLayerManifestContent []byte + refsManifestAConfigArtifactContent []byte + refsManifestAConfigArtifactDigest string + refsManifestALayerArtifactContent []byte + refsManifestALayerArtifactDigest string + refsManifestBConfigArtifactContent []byte + refsManifestBConfigArtifactDigest string + refsManifestBLayerArtifactContent []byte + refsManifestBLayerArtifactDigest string + nonexistentManifest string + reportJUnitFilename string + reportHTMLFilename string + httpWriter *httpDebugWriter + testsToRun int + suiteDescription string + runPullSetup bool + runPushSetup bool + runContentDiscoverySetup bool + runContentManagementSetup bool + runReferencesSetup bool + skipEmptyLayerTest bool + deleteManifestBeforeBlobs bool + runAutomaticCrossmountTest bool + automaticCrossmountEnabled bool + configs []TestBlob + manifests []TestBlob + seed int64 + Version = "unknown" + emptyJSONBlob []byte + emptyJSONDescriptor Descriptor ) func init() { @@ -177,7 +205,7 @@ func init() { client.SetCookieJar(nil) // create a unique config for each workflow category - for i := 0; i < 4; i++ { + for i := 0; i < numWorkflows; i++ { // in order to get a unique blob digest, we create a new author // field for the config on each run. @@ -226,7 +254,7 @@ func init() { }} // create a unique manifest for each workflow category - for i := 0; i < 4; i++ { + for i := 0; i < numWorkflows; i++ { manifest := Manifest{ Config: Descriptor{ MediaType: "application/vnd.oci.image.config.v1+json", @@ -285,6 +313,118 @@ func init() { setupChunkedBlob(42) + // used in referrers test (artifacts with Subject field set) + emptyJSONBlob = []byte("{}") + emptyJSONDescriptor = Descriptor{ + MediaType: "application/vnd.oci.empty.v1+json", + Size: int64(len(emptyJSONBlob)), + Digest: godigest.FromBytes(emptyJSONBlob), + } + + testRefBlobA = []byte("NHL Peanut Butter on my NHL bagel") + testRefBlobALength = strconv.Itoa(len(testRefBlobA)) + testRefBlobADigest = godigest.FromBytes(testRefBlobA).String() + + testRefArtifactTypeA = "application/vnd.nhl.peanut.butter.bagel" + + testRefBlobB = []byte("NBA Strawberry Jam on my NBA croissant") + testRefBlobBLength = strconv.Itoa(len(testRefBlobB)) + testRefBlobBDigest = godigest.FromBytes(testRefBlobB).String() + + testRefArtifactTypeB = "application/vnd.nba.strawberry.jam.croissant" + + // artifact with Subject ref using config.MediaType = artifactType + refsManifestAConfigArtifact := Manifest{ + Config: Descriptor{ + MediaType: testRefArtifactTypeA, + Size: int64(len(testRefBlobA)), + Digest: godigest.FromBytes(testRefBlobA), + }, + Subject: &Descriptor{ + Size: int64(len(manifests[4].Content)), + Digest: godigest.FromBytes(manifests[4].Content), + }, + Layers: []Descriptor{ + emptyJSONDescriptor, + }, + } + + refsManifestAConfigArtifactContent, err = json.MarshalIndent(&refsManifestAConfigArtifact, "", "\t") + if err != nil { + log.Fatal(err) + } + + refsManifestAConfigArtifactDigest = godigest.FromBytes(refsManifestAConfigArtifactContent).String() + + refsManifestBConfigArtifact := Manifest{ + Config: Descriptor{ + MediaType: testRefArtifactTypeB, + Size: int64(len(testRefBlobB)), + Digest: godigest.FromBytes(testRefBlobB), + }, + Subject: &Descriptor{ + Size: int64(len(manifests[4].Content)), + Digest: godigest.FromBytes(manifests[4].Content), + }, + Layers: []Descriptor{ + emptyJSONDescriptor, + }, + } + + refsManifestBConfigArtifactContent, err = json.MarshalIndent(&refsManifestBConfigArtifact, "", "\t") + if err != nil { + log.Fatal(err) + } + + refsManifestBConfigArtifactDigest = godigest.FromBytes(refsManifestBConfigArtifactContent).String() + + // artifact with Subject ref using ArtifactType, config.MediaType = emptyJSON + refsManifestALayerArtifact := Manifest{ + ArtifactType: testRefArtifactTypeA, + Config: emptyJSONDescriptor, + Subject: &Descriptor{ + Size: int64(len(manifests[4].Content)), + Digest: godigest.FromBytes(manifests[4].Content), + }, + Layers: []Descriptor{ + Descriptor{ + MediaType: testRefArtifactTypeA, + Size: int64(len(testRefBlobB)), + Digest: godigest.FromBytes(testRefBlobB), + }, + }, + } + + refsManifestALayerArtifactContent, err = json.MarshalIndent(&refsManifestALayerArtifact, "", "\t") + if err != nil { + log.Fatal(err) + } + + refsManifestALayerArtifactDigest = godigest.FromBytes(refsManifestALayerArtifactContent).String() + + refsManifestBLayerArtifact := Manifest{ + ArtifactType: testRefArtifactTypeB, + Config: emptyJSONDescriptor, + Subject: &Descriptor{ + Size: int64(len(manifests[4].Content)), + Digest: godigest.FromBytes(manifests[4].Content), + }, + Layers: []Descriptor{ + Descriptor{ + MediaType: testRefArtifactTypeB, + Size: int64(len(testRefBlobB)), + Digest: godigest.FromBytes(testRefBlobB), + }, + }, + } + + refsManifestBLayerArtifactContent, err = json.MarshalIndent(&refsManifestBLayerArtifact, "", "\t") + if err != nil { + log.Fatal(err) + } + + refsManifestBLayerArtifactDigest = godigest.FromBytes(refsManifestBLayerArtifactContent).String() + dummyDigest = godigest.FromString("hello world").String() errorCodes = []string{ @@ -309,6 +449,7 @@ func init() { runPushSetup = true runContentDiscoverySetup = true runContentManagementSetup = true + runReferencesSetup = true skipEmptyLayerTest = false deleteManifestBeforeBlobs = false @@ -316,6 +457,7 @@ func init() { os.Getenv(envVarManifestDigest) != "" && os.Getenv(envVarBlobDigest) != "" { runPullSetup = false + runReferencesSetup = false } if os.Getenv(envVarTagList) != "" { @@ -352,6 +494,19 @@ func RunOnlyIfNot(v bool) { } } +func Warn(message string) { + fmt.Fprintf(colorable.NewColorableStderr(), "\n") + // print message + msgcolor := color.New(color.FgMagenta).FprintfFunc() + msgcolor(colorable.NewColorableStderr(), formatter.Fi(2, "\nWARNING: "+message)) + fmt.Fprintf(colorable.NewColorableStderr(), "\n") + // print file:line + _, file, line, _ := runtime.Caller(1) + flcolor := color.New(color.FgWhite).FprintfFunc() + flcolor(colorable.NewColorableStderr(), formatter.Fi(2, "\n")) + flcolor(colorable.NewColorableStderr(), formatter.Fi(2, "%s:%d\n", file, line)) +} + func generateSkipReport() string { buf := new(bytes.Buffer) fmt.Fprintf(buf, "you have skipped this test; if this is an error, check your environment variable settings:\n")