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..389ba4d6 --- /dev/null +++ b/conformance/05_referrers.go @@ -0,0 +1,306 @@ +package conformance + +import ( + "encoding/json" + "net/http" + + "github.com/bloodorangeio/reggie" + g "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var test05Referrers = func() { + g.Context(titleReferrers, func() { + + g.Context("Setup", func() { + + 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 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) + }) + + 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/image.go b/conformance/image.go index d00fad6a..9d6a889b 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,38 @@ type Image struct { // RootFS references the layer content addresses used by the image. RootFS RootFS `json:"rootfs"` } + +// Artifact describes an artifact manifest. +// This structure provides `application/vnd.oci.artifact.manifest.v1+json` mediatype when marshalled to JSON. +type Artifact struct { + // MediaType is the media type of the object this schema refers to. + MediaType string `json:"mediaType"` + + // ArtifactType is the IANA media type of the artifact this schema refers to. + ArtifactType string `json:"artifactType,omitempty"` + + // Blobs is a collection of blobs referenced by this manifest. + Blobs []Descriptor `json:"blobs,omitempty"` + + // Subject (reference) is an optional link from the artifact to another manifest forming an association between the artifact and the other manifest. + Subject *Descriptor `json:"subject,omitempty"` + + // Annotations contains arbitrary metadata for the artifact manifest. + Annotations map[string]string `json:"annotations,omitempty"` +} + +// 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..5fbfa0a1 100644 --- a/conformance/setup.go +++ b/conformance/setup.go @@ -15,6 +15,8 @@ import ( "github.com/bloodorangeio/reggie" "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 +38,8 @@ const ( push contentDiscovery contentManagement + referrers + numWorkflows BLOB_UNKNOWN = iota BLOB_UPLOAD_INVALID @@ -72,6 +76,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 +85,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 +102,69 @@ 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" + emptyJSONDescriptor = Descriptor{ + MediaType: "application/vnd.oci.empty.v1+json", + Size: 2, + Digest: godigest.FromString("sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a"), + } ) func init() { @@ -177,7 +206,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 +255,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 +314,112 @@ func init() { setupChunkedBlob(42) + // used in referrers test (artifacts with Subject field set) + + 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 +444,7 @@ func init() { runPushSetup = true runContentDiscoverySetup = true runContentManagementSetup = true + runReferencesSetup = true skipEmptyLayerTest = false deleteManifestBeforeBlobs = false @@ -316,6 +452,7 @@ func init() { os.Getenv(envVarManifestDigest) != "" && os.Getenv(envVarBlobDigest) != "" { runPullSetup = false + runReferencesSetup = false } if os.Getenv(envVarTagList) != "" { @@ -352,6 +489,10 @@ func RunOnlyIfNot(v bool) { } } +func Warn(msg string) { + fmt.Fprintln(colorable.NewColorableStderr(), formatter.F("WARNING: "+msg)) +} + 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")