From 60d9cdcc592af1ba0fdd439e998232223c9d3bb5 Mon Sep 17 00:00:00 2001 From: Junjie Gao Date: Fri, 15 Sep 2023 09:20:01 +0800 Subject: [PATCH] bump: update oras-go to v2.3.0 (#347) - Update oras-go to v2.3.0. - Replace oras.Pack() with oras.PackManifest() as it is deprecated in v2.3.0. - Generate an empty config blob manually, as oras.PackManifest() does not generate the config blob with the notation artifact type as the media type. Resolves #346 Signed-off-by: Junjie Gao --------- Signed-off-by: Junjie Gao --- go.mod | 2 +- go.sum | 4 +-- registry/repository.go | 49 ++++++++++++++++++++++++++++++++++--- registry/repository_test.go | 6 +++++ 4 files changed, 55 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 4cb6c29e..7fafa14c 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/veraison/go-cose v1.1.0 golang.org/x/crypto v0.13.0 golang.org/x/mod v0.12.0 - oras.land/oras-go/v2 v2.2.1 + oras.land/oras-go/v2 v2.3.0 ) require ( diff --git a/go.sum b/go.sum index d94f50a8..c8db3179 100644 --- a/go.sum +++ b/go.sum @@ -75,5 +75,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -oras.land/oras-go/v2 v2.2.1 h1:3VJTYqy5KfelEF9c2jo1MLSpr+TM3mX8K42wzZcd6qE= -oras.land/oras-go/v2 v2.2.1/go.mod h1:GeAwLuC4G/JpNwkd+bSZ6SkDMGaaYglt6YK2WvZP7uQ= +oras.land/oras-go/v2 v2.3.0 h1:lqX1aXdN+DAmDTKjiDyvq85cIaI4RkIKp/PghWlAGIU= +oras.land/oras-go/v2 v2.3.0/go.mod h1:GeAwLuC4G/JpNwkd+bSZ6SkDMGaaYglt6YK2WvZP7uQ= diff --git a/registry/repository.go b/registry/repository.go index cb6e20a3..908040f8 100644 --- a/registry/repository.go +++ b/registry/repository.go @@ -14,8 +14,10 @@ package registry import ( + "bytes" "context" "encoding/json" + "errors" "fmt" "os" @@ -24,6 +26,7 @@ import ( "oras.land/oras-go/v2" "oras.land/oras-go/v2/content" "oras.land/oras-go/v2/content/oci" + "oras.land/oras-go/v2/errdef" "oras.land/oras-go/v2/registry" ) @@ -32,6 +35,19 @@ const ( maxManifestSizeLimit = 4 * 1024 * 1024 // 4 MiB ) +var ( + // notationEmptyConfigDesc is the descriptor of an empty notation manifest + // config + // reference: https://github.com/notaryproject/specifications/blob/v1.0.0/specs/signature-specification.md#storage + notationEmptyConfigDesc = ocispec.Descriptor{ + MediaType: ArtifactTypeNotation, + Digest: ocispec.DescriptorEmptyJSON.Digest, + Size: ocispec.DescriptorEmptyJSON.Size, + } + // notationEmptyConfigData is the data of an empty notation manifest config + notationEmptyConfigData = ocispec.DescriptorEmptyJSON.Data +) + // RepositoryOptions provides user options when creating a Repository // it is kept for future extensibility type RepositoryOptions struct{} @@ -187,13 +203,40 @@ func (c *repositoryClient) getSignatureBlobDesc(ctx context.Context, sigManifest // uploadSignatureManifest uploads the signature manifest to the registry func (c *repositoryClient) uploadSignatureManifest(ctx context.Context, subject, blobDesc ocispec.Descriptor, annotations map[string]string) (ocispec.Descriptor, error) { - opts := oras.PackOptions{ + configDesc, err := pushNotationManifestConfig(ctx, c.GraphTarget) + if err != nil { + return ocispec.Descriptor{}, fmt.Errorf("failed to push notation manifest config: %w", err) + } + + opts := oras.PackManifestOptions{ Subject: &subject, ManifestAnnotations: annotations, - PackImageManifest: true, + Layers: []ocispec.Descriptor{blobDesc}, + ConfigDescriptor: &configDesc, + } + + return oras.PackManifest(ctx, c.GraphTarget, oras.PackManifestVersion1_1_RC4, "", opts) +} + +// pushNotationManifestConfig pushes an empty notation manifest config, if it +// doesn't exist. +// +// if the config exists, it returns the descriptor of the config without error. +func pushNotationManifestConfig(ctx context.Context, pusher content.Storage) (ocispec.Descriptor, error) { + // check if the config exists + exists, err := pusher.Exists(ctx, notationEmptyConfigDesc) + if err != nil { + return ocispec.Descriptor{}, fmt.Errorf("unable to verify existence: %s: %s. Details: %w", notationEmptyConfigDesc.Digest.String(), notationEmptyConfigDesc.MediaType, err) + } + if exists { + return notationEmptyConfigDesc, nil } - return oras.Pack(ctx, c.GraphTarget, ArtifactTypeNotation, []ocispec.Descriptor{blobDesc}, opts) + // return nil if the config pushed successfully or it already exists + if err := pusher.Push(ctx, notationEmptyConfigDesc, bytes.NewReader(notationEmptyConfigData)); err != nil && !errors.Is(err, errdef.ErrAlreadyExists) { + return ocispec.Descriptor{}, fmt.Errorf("unable to push: %s: %s. Details: %w", notationEmptyConfigDesc.Digest.String(), notationEmptyConfigDesc.MediaType, err) + } + return notationEmptyConfigDesc, nil } // signatureReferrers returns referrer nodes of desc in target filtered by diff --git a/registry/repository_test.go b/registry/repository_test.go index 38fbcf29..37caab88 100644 --- a/registry/repository_test.go +++ b/registry/repository_test.go @@ -38,6 +38,7 @@ import ( const ( zeroDigest = "sha256:0000000000000000000000000000000000000000000000000000000000000000" + emptyConfigDigest = "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a" validDigest = "6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b" validDigest2 = "1834876dcfb05cb167a5c24953eba58c4ac89b1adf57f28f2f9d09af107ee8f2" invalidDigest = "invaliddigest" @@ -129,6 +130,11 @@ func (c mockRemoteClient) Do(req *http.Request) (*http.Response, error) { "Docker-Content-Digest": {validDigestWithAlgo2}, }, }, nil + case "/v2/test/blobs/" + emptyConfigDigest: + return &http.Response{ + StatusCode: http.StatusNotFound, + Body: io.NopCloser(bytes.NewReader([]byte{})), + }, nil case "/v2/test/manifests/" + invalidDigest: return &http.Response{}, fmt.Errorf(errMsg) case "v2/test/manifest/" + validDigest2: