From dea7c10e7d86ed811eab3169ba741646aec8257e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Aug 2023 13:10:11 +0000 Subject: [PATCH] build(deps): bump oras.land/oras-go/v2 from 2.2.1 to 2.3.0 Bumps [oras.land/oras-go/v2](https://github.com/oras-project/oras-go) from 2.2.1 to 2.3.0. - [Release notes](https://github.com/oras-project/oras-go/releases) - [Commits](https://github.com/oras-project/oras-go/compare/v2.2.1...v2.3.0) --- updated-dependencies: - dependency-name: oras.land/oras-go/v2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 +- vendor/modules.txt | 3 +- vendor/oras.land/oras-go/v2/content/graph.go | 19 +- .../oras.land/oras-go/v2/content/oci/oci.go | 10 +- .../oras-go/v2/content/oci/readonlyoci.go | 2 +- .../oras-go/v2/content/oci/readonlystorage.go | 2 +- .../oras-go/v2/content/oci/storage.go | 2 +- .../oras.land/oras-go/v2/content/storage.go | 2 +- vendor/oras.land/oras-go/v2/errdef/errors.go | 1 + .../v2/internal/manifestutil/parser.go | 63 ++++ .../oras-go/v2/internal/platform/platform.go | 11 +- vendor/oras.land/oras-go/v2/pack.go | 328 +++++++++++++++--- .../oras-go/v2/registry/reference.go | 8 +- .../v2/registry/remote/errcode/errors.go | 6 +- .../oras-go/v2/registry/repository.go | 9 +- 16 files changed, 393 insertions(+), 79 deletions(-) create mode 100644 vendor/oras.land/oras-go/v2/internal/manifestutil/parser.go diff --git a/go.mod b/go.mod index d12ecdb96b..eb2d9044d1 100644 --- a/go.mod +++ b/go.mod @@ -42,7 +42,7 @@ require ( google.golang.org/grpc v1.57.0 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c gopkg.in/yaml.v2 v2.4.0 - oras.land/oras-go/v2 v2.2.1 + oras.land/oras-go/v2 v2.3.0 sigs.k8s.io/yaml v1.3.0 ) diff --git a/go.sum b/go.sum index 1aa20bfff0..59cf80af58 100644 --- a/go.sum +++ b/go.sum @@ -640,8 +640,8 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -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= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/vendor/modules.txt b/vendor/modules.txt index 4f4418eb7e..85484ab39e 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -458,7 +458,7 @@ gopkg.in/yaml.v2 # gopkg.in/yaml.v3 v3.0.1 ## explicit gopkg.in/yaml.v3 -# oras.land/oras-go/v2 v2.2.1 +# oras.land/oras-go/v2 v2.3.0 ## explicit; go 1.19 oras.land/oras-go/v2 oras.land/oras-go/v2/content @@ -473,6 +473,7 @@ oras.land/oras-go/v2/internal/fs/tarfs oras.land/oras-go/v2/internal/graph oras.land/oras-go/v2/internal/interfaces oras.land/oras-go/v2/internal/ioutil +oras.land/oras-go/v2/internal/manifestutil oras.land/oras-go/v2/internal/platform oras.land/oras-go/v2/internal/registryutil oras.land/oras-go/v2/internal/resolver diff --git a/vendor/oras.land/oras-go/v2/content/graph.go b/vendor/oras.land/oras-go/v2/content/graph.go index fa2f9efe78..9ae837285e 100644 --- a/vendor/oras.land/oras-go/v2/content/graph.go +++ b/vendor/oras.land/oras-go/v2/content/graph.go @@ -75,18 +75,33 @@ func Successors(ctx context.Context, fetcher Fetcher, node ocispec.Descriptor) ( } nodes = append(nodes, manifest.Config) return append(nodes, manifest.Layers...), nil - case docker.MediaTypeManifestList, ocispec.MediaTypeImageIndex: + case docker.MediaTypeManifestList: content, err := FetchAll(ctx, fetcher, node) if err != nil { return nil, err } - // docker manifest list and oci index are equivalent for successors. + // OCI manifest index schema can be used to marshal docker manifest list var index ocispec.Index if err := json.Unmarshal(content, &index); err != nil { return nil, err } return index.Manifests, nil + case ocispec.MediaTypeImageIndex: + content, err := FetchAll(ctx, fetcher, node) + if err != nil { + return nil, err + } + + var index ocispec.Index + if err := json.Unmarshal(content, &index); err != nil { + return nil, err + } + var nodes []ocispec.Descriptor + if index.Subject != nil { + nodes = append(nodes, *index.Subject) + } + return append(nodes, index.Manifests...), nil case spec.MediaTypeArtifactManifest: content, err := FetchAll(ctx, fetcher, node) if err != nil { diff --git a/vendor/oras.land/oras-go/v2/content/oci/oci.go b/vendor/oras.land/oras-go/v2/content/oci/oci.go index 5b473eeb72..97ba6de594 100644 --- a/vendor/oras.land/oras-go/v2/content/oci/oci.go +++ b/vendor/oras.land/oras-go/v2/content/oci/oci.go @@ -14,7 +14,7 @@ limitations under the License. */ // Package oci provides access to an OCI content store. -// Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc2/image-layout.md +// Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc4/image-layout.md package oci import ( @@ -40,17 +40,17 @@ import ( // ociImageIndexFile is the file name of the index // from the OCI Image Layout Specification. -// Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc2/image-layout.md#indexjson-file +// Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc4/image-layout.md#indexjson-file const ociImageIndexFile = "index.json" // ociBlobsDir is the name of the blobs directory // from the OCI Image Layout Specification. -// Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc2/image-layout.md#content +// Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc4/image-layout.md#content const ociBlobsDir = "blobs" // Store implements `oras.Target`, and represents a content store // based on file system with the OCI-Image layout. -// Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc2/image-layout.md +// Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc4/image-layout.md type Store struct { // AutoSaveIndex controls if the OCI store will automatically save the index // file on each Tag() call. @@ -135,7 +135,7 @@ func (s *Store) Exists(ctx context.Context, target ocispec.Descriptor) (bool, er // Tag tags a descriptor with a reference string. // reference should be a valid tag (e.g. "latest"). -// Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc2/image-layout.md#indexjson-file +// Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc4/image-layout.md#indexjson-file func (s *Store) Tag(ctx context.Context, desc ocispec.Descriptor, reference string) error { if err := validateReference(reference); err != nil { return err diff --git a/vendor/oras.land/oras-go/v2/content/oci/readonlyoci.go b/vendor/oras.land/oras-go/v2/content/oci/readonlyoci.go index f908d16909..eb94f61cd0 100644 --- a/vendor/oras.land/oras-go/v2/content/oci/readonlyoci.go +++ b/vendor/oras.land/oras-go/v2/content/oci/readonlyoci.go @@ -36,7 +36,7 @@ import ( // ReadOnlyStore implements `oras.ReadonlyTarget`, and represents a read-only // content store based on file system with the OCI-Image layout. -// Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc2/image-layout.md +// Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc4/image-layout.md type ReadOnlyStore struct { fsys fs.FS storage content.ReadOnlyStorage diff --git a/vendor/oras.land/oras-go/v2/content/oci/readonlystorage.go b/vendor/oras.land/oras-go/v2/content/oci/readonlystorage.go index 3cab94fac4..b5e53039d9 100644 --- a/vendor/oras.land/oras-go/v2/content/oci/readonlystorage.go +++ b/vendor/oras.land/oras-go/v2/content/oci/readonlystorage.go @@ -31,7 +31,7 @@ import ( // ReadOnlyStorage is a read-only CAS based on file system with the OCI-Image // layout. -// Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc2/image-layout.md +// Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc4/image-layout.md type ReadOnlyStorage struct { fsys fs.FS } diff --git a/vendor/oras.land/oras-go/v2/content/oci/storage.go b/vendor/oras.land/oras-go/v2/content/oci/storage.go index 6b0e90a8c3..9d4d03b3c5 100644 --- a/vendor/oras.land/oras-go/v2/content/oci/storage.go +++ b/vendor/oras.land/oras-go/v2/content/oci/storage.go @@ -42,7 +42,7 @@ var bufPool = sync.Pool{ } // Storage is a CAS based on file system with the OCI-Image layout. -// Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc2/image-layout.md +// Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc4/image-layout.md type Storage struct { *ReadOnlyStorage // root is the root directory of the OCI layout. diff --git a/vendor/oras.land/oras-go/v2/content/storage.go b/vendor/oras.land/oras-go/v2/content/storage.go index 971142cbf9..47c95d8769 100644 --- a/vendor/oras.land/oras-go/v2/content/storage.go +++ b/vendor/oras.land/oras-go/v2/content/storage.go @@ -31,7 +31,7 @@ type Fetcher interface { // Pusher pushes content. type Pusher interface { // Push pushes the content, matching the expected descriptor. - // Reader is perferred to Writer so that the suitable buffer size can be + // Reader is preferred to Writer so that the suitable buffer size can be // chosen by the underlying implementation. Furthermore, the implementation // can also do reflection on the Reader for more advanced I/O optimization. Push(ctx context.Context, expected ocispec.Descriptor, content io.Reader) error diff --git a/vendor/oras.land/oras-go/v2/errdef/errors.go b/vendor/oras.land/oras-go/v2/errdef/errors.go index 030360edb7..7adb44b173 100644 --- a/vendor/oras.land/oras-go/v2/errdef/errors.go +++ b/vendor/oras.land/oras-go/v2/errdef/errors.go @@ -22,6 +22,7 @@ var ( ErrAlreadyExists = errors.New("already exists") ErrInvalidDigest = errors.New("invalid digest") ErrInvalidReference = errors.New("invalid reference") + ErrInvalidMediaType = errors.New("invalid media type") ErrMissingReference = errors.New("missing reference") ErrNotFound = errors.New("not found") ErrSizeExceedsLimit = errors.New("size exceeds limit") diff --git a/vendor/oras.land/oras-go/v2/internal/manifestutil/parser.go b/vendor/oras.land/oras-go/v2/internal/manifestutil/parser.go new file mode 100644 index 0000000000..c904dc690e --- /dev/null +++ b/vendor/oras.land/oras-go/v2/internal/manifestutil/parser.go @@ -0,0 +1,63 @@ +/* +Copyright The ORAS Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package manifestutil + +import ( + "context" + "encoding/json" + + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "oras.land/oras-go/v2/content" + "oras.land/oras-go/v2/internal/docker" +) + +// Config returns the config of desc, if present. +func Config(ctx context.Context, fetcher content.Fetcher, desc ocispec.Descriptor) (*ocispec.Descriptor, error) { + switch desc.MediaType { + case docker.MediaTypeManifest, ocispec.MediaTypeImageManifest: + content, err := content.FetchAll(ctx, fetcher, desc) + if err != nil { + return nil, err + } + // OCI manifest schema can be used to marshal docker manifest + var manifest ocispec.Manifest + if err := json.Unmarshal(content, &manifest); err != nil { + return nil, err + } + return &manifest.Config, nil + default: + return nil, nil + } +} + +// Manifest returns the manifests of desc, if present. +func Manifests(ctx context.Context, fetcher content.Fetcher, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { + switch desc.MediaType { + case docker.MediaTypeManifestList, ocispec.MediaTypeImageIndex: + content, err := content.FetchAll(ctx, fetcher, desc) + if err != nil { + return nil, err + } + // OCI manifest index schema can be used to marshal docker manifest list + var index ocispec.Index + if err := json.Unmarshal(content, &index); err != nil { + return nil, err + } + return index.Manifests, nil + default: + return nil, nil + } +} diff --git a/vendor/oras.land/oras-go/v2/internal/platform/platform.go b/vendor/oras.land/oras-go/v2/internal/platform/platform.go index 38d8d47fd9..e903fe3d5a 100644 --- a/vendor/oras.land/oras-go/v2/internal/platform/platform.go +++ b/vendor/oras.land/oras-go/v2/internal/platform/platform.go @@ -25,6 +25,7 @@ import ( "oras.land/oras-go/v2/content" "oras.land/oras-go/v2/errdef" "oras.land/oras-go/v2/internal/docker" + "oras.land/oras-go/v2/internal/manifestutil" ) // Match checks whether the current platform matches the target platform. @@ -35,7 +36,7 @@ import ( // array of the current platform. // // Note: Variant, OSVersion and OSFeatures are optional fields, will skip -// the comparison if the target platform does not provide specfic value. +// the comparison if the target platform does not provide specific value. func Match(got *ocispec.Platform, want *ocispec.Platform) bool { if got.Architecture != want.Architecture || got.OS != want.OS { return false @@ -77,7 +78,7 @@ func isSubset(a, b []string) bool { func SelectManifest(ctx context.Context, src content.ReadOnlyStorage, root ocispec.Descriptor, p *ocispec.Platform) (ocispec.Descriptor, error) { switch root.MediaType { case docker.MediaTypeManifestList, ocispec.MediaTypeImageIndex: - manifests, err := content.Successors(ctx, src, root) + manifests, err := manifestutil.Manifests(ctx, src, root) if err != nil { return ocispec.Descriptor{}, err } @@ -90,7 +91,8 @@ func SelectManifest(ctx context.Context, src content.ReadOnlyStorage, root ocisp } return ocispec.Descriptor{}, fmt.Errorf("%s: %w: no matching manifest was found in the manifest list", root.Digest, errdef.ErrNotFound) case docker.MediaTypeManifest, ocispec.MediaTypeImageManifest: - descs, err := content.Successors(ctx, src, root) + // config will be non-nil for docker manifest and OCI image manifest + config, err := manifestutil.Config(ctx, src, root) if err != nil { return ocispec.Descriptor{}, err } @@ -99,8 +101,7 @@ func SelectManifest(ctx context.Context, src content.ReadOnlyStorage, root ocisp if root.MediaType == ocispec.MediaTypeImageManifest { configMediaType = ocispec.MediaTypeImageConfig } - - cfgPlatform, err := getPlatformFromConfig(ctx, src, descs[0], configMediaType) + cfgPlatform, err := getPlatformFromConfig(ctx, src, *config, configMediaType) if err != nil { return ocispec.Descriptor{}, err } diff --git a/vendor/oras.land/oras-go/v2/pack.go b/vendor/oras.land/oras-go/v2/pack.go index 872afa518f..08e14e1954 100644 --- a/vendor/oras.land/oras-go/v2/pack.go +++ b/vendor/oras.land/oras-go/v2/pack.go @@ -21,6 +21,7 @@ import ( "encoding/json" "errors" "fmt" + "regexp" "time" specs "github.com/opencontainers/image-spec/specs-go" @@ -31,38 +32,127 @@ import ( ) const ( - // MediaTypeUnknownConfig is the default mediaType used when no - // config media type is specified. + // MediaTypeUnknownConfig is the default config mediaType used + // - for [Pack] when PackOptions.PackImageManifest is true and + // PackOptions.ConfigDescriptor is not specified. + // - for [PackManifest] when packManifestVersion is PackManifestVersion1_0 + // and PackManifestOptions.ConfigDescriptor is not specified. MediaTypeUnknownConfig = "application/vnd.unknown.config.v1+json" - // MediaTypeUnknownArtifact is the default artifactType used when no - // artifact type is specified. + + // MediaTypeUnknownArtifact is the default artifactType used for [Pack] + // when PackOptions.PackImageManifest is false and artifactType is + // not specified. MediaTypeUnknownArtifact = "application/vnd.unknown.artifact.v1" ) -// ErrInvalidDateTimeFormat is returned by Pack() when -// AnnotationArtifactCreated or AnnotationCreated is provided, but its value -// is not in RFC 3339 format. -// Reference: https://www.rfc-editor.org/rfc/rfc3339#section-5.6 -var ErrInvalidDateTimeFormat = errors.New("invalid date and time format") +var ( + // ErrInvalidDateTimeFormat is returned by [Pack] and [PackManifest] when + // AnnotationArtifactCreated or AnnotationCreated is provided, but its value + // is not in RFC 3339 format. + // Reference: https://www.rfc-editor.org/rfc/rfc3339#section-5.6 + ErrInvalidDateTimeFormat = errors.New("invalid date and time format") + + // ErrMissingArtifactType is returned by [PackManifest] when + // packManifestVersion is PackManifestVersion1_1_RC4 and artifactType is + // empty and the config media type is set to + // "application/vnd.oci.empty.v1+json". + ErrMissingArtifactType = errors.New("missing artifact type") +) + +// PackManifestVersion represents the manifest version used for [PackManifest]. +type PackManifestVersion int + +const ( + // PackManifestVersion1_0 represents the OCI Image Manifest defined in + // image-spec v1.0.2. + // Reference: https://github.com/opencontainers/image-spec/blob/v1.0.2/manifest.md + PackManifestVersion1_0 PackManifestVersion = 1 + + // PackManifestVersion1_1_RC4 represents the OCI Image Manifest defined + // in image-spec v1.1.0-rc4. + // Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc4/manifest.md + PackManifestVersion1_1_RC4 PackManifestVersion = 2 +) + +// PackManifestOptions contains optional parameters for [PackManifest]. +type PackManifestOptions struct { + // Subject is the subject of the manifest. + // This option is only valid when PackManifestVersion is + // NOT PackManifestVersion1_0. + Subject *ocispec.Descriptor + + // Layers is the layers of the manifest. + Layers []ocispec.Descriptor + + // ManifestAnnotations is the annotation map of the manifest. + ManifestAnnotations map[string]string + + // ConfigDescriptor is a pointer to the descriptor of the config blob. + // If not nil, ConfigAnnotations will be ignored. + ConfigDescriptor *ocispec.Descriptor + + // ConfigAnnotations is the annotation map of the config descriptor. + // This option is valid only when ConfigDescriptor is nil. + ConfigAnnotations map[string]string +} + +// mediaTypeRegexp checks the format of media types. +// References: +// - https://github.com/opencontainers/image-spec/blob/v1.1.0-rc4/schema/defs-descriptor.json#L7 +// - https://datatracker.ietf.org/doc/html/rfc6838#section-4.2 +var mediaTypeRegexp = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9!#$&-^_.+]{0,126}/[A-Za-z0-9][A-Za-z0-9!#$&-^_.+]{0,126}$`) -// PackOptions contains parameters for [oras.Pack]. +// PackManifest generates an OCI Image Manifest based on the given parameters +// and pushes the packed manifest to a content storage using pusher. The version +// of the manifest to be packed is determined by packManifestVersion +// (Recommended value: PackManifestVersion1_1_RC4). +// +// - If packManifestVersion is [PackManifestVersion1_1_RC4]: +// artifactType MUST NOT be empty unless opts.ConfigDescriptor is specified. +// - If packManifestVersion is [PackManifestVersion1_0]: +// if opts.ConfigDescriptor is nil, artifactType will be used as the +// config media type; if artifactType is empty, +// "application/vnd.unknown.config.v1+json" will be used. +// if opts.ConfigDescriptor is NOT nil, artifactType will be ignored. +// +// artifactType and opts.ConfigDescriptor.MediaType MUST comply with RFC 6838. +// +// If succeeded, returns a descriptor of the packed manifest. +func PackManifest(ctx context.Context, pusher content.Pusher, packManifestVersion PackManifestVersion, artifactType string, opts PackManifestOptions) (ocispec.Descriptor, error) { + switch packManifestVersion { + case PackManifestVersion1_0: + return packManifestV1_0(ctx, pusher, artifactType, opts) + case PackManifestVersion1_1_RC4: + return packManifestV1_1_RC4(ctx, pusher, artifactType, opts) + default: + return ocispec.Descriptor{}, fmt.Errorf("PackManifestVersion(%v): %w", packManifestVersion, errdef.ErrUnsupported) + } +} + +// PackOptions contains optional parameters for [Pack]. +// +// Deprecated: This type is deprecated and not recommended for future use. +// Use [PackManifestOptions] instead. type PackOptions struct { // Subject is the subject of the manifest. Subject *ocispec.Descriptor + // ManifestAnnotations is the annotation map of the manifest. ManifestAnnotations map[string]string - // PackImageManifest controls whether to pack an image manifest or not. - // - If true, pack an image manifest; artifactType will be used as the - // the config descriptor mediaType of the image manifest. - // - If false, pack an artifact manifest. - // Default: false. + // PackImageManifest controls whether to pack an OCI Image Manifest or not. + // - If true, pack an OCI Image Manifest. + // - If false, pack an OCI Artifact Manifest (deprecated). + // + // Default value: false. PackImageManifest bool + // ConfigDescriptor is a pointer to the descriptor of the config blob. // If not nil, artifactType will be implied by the mediaType of the // specified ConfigDescriptor, and ConfigAnnotations will be ignored. // This option is valid only when PackImageManifest is true. ConfigDescriptor *ocispec.Descriptor + // ConfigAnnotations is the annotation map of the config descriptor. // This option is valid only when PackImageManifest is true // and ConfigDescriptor is nil. @@ -74,17 +164,20 @@ type PackOptions struct { // // When opts.PackImageManifest is true, artifactType will be used as the // the config descriptor mediaType of the image manifest. +// // If succeeded, returns a descriptor of the manifest. +// +// Deprecated: This method is deprecated and not recommended for future use. +// Use [PackManifest] instead. func Pack(ctx context.Context, pusher content.Pusher, artifactType string, blobs []ocispec.Descriptor, opts PackOptions) (ocispec.Descriptor, error) { if opts.PackImageManifest { - return packImage(ctx, pusher, artifactType, blobs, opts) + return packManifestV1_1_RC2(ctx, pusher, artifactType, blobs, opts) } return packArtifact(ctx, pusher, artifactType, blobs, opts) } -// packArtifact packs the given blobs, generates an artifact manifest for the -// pack, and pushes it to a content storage. -// If succeeded, returns a descriptor of the manifest. +// packArtifact packs an Artifact manifest as defined in image-spec v1.1.0-rc2. +// Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc2/artifact.md func packArtifact(ctx context.Context, pusher content.Pusher, artifactType string, blobs []ocispec.Descriptor, opts PackOptions) (ocispec.Descriptor, error) { if artifactType == "" { artifactType = MediaTypeUnknownArtifact @@ -101,46 +194,72 @@ func packArtifact(ctx context.Context, pusher content.Pusher, artifactType strin Subject: opts.Subject, Annotations: annotations, } - manifestJSON, err := json.Marshal(manifest) - if err != nil { - return ocispec.Descriptor{}, fmt.Errorf("failed to marshal manifest: %w", err) + return pushManifest(ctx, pusher, manifest, manifest.MediaType, manifest.ArtifactType, manifest.Annotations) +} + +// packManifestV1_0 packs an image manifest defined in image-spec v1.0.2. +// Reference: https://github.com/opencontainers/image-spec/blob/v1.0.2/manifest.md +func packManifestV1_0(ctx context.Context, pusher content.Pusher, artifactType string, opts PackManifestOptions) (ocispec.Descriptor, error) { + if opts.Subject != nil { + return ocispec.Descriptor{}, fmt.Errorf("subject is not supported for manifest version %v: %w", PackManifestVersion1_0, errdef.ErrUnsupported) } - manifestDesc := content.NewDescriptorFromBytes(spec.MediaTypeArtifactManifest, manifestJSON) - // populate ArtifactType and Annotations of the manifest into manifestDesc - manifestDesc.ArtifactType = manifest.ArtifactType - manifestDesc.Annotations = manifest.Annotations - // push manifest - if err := pusher.Push(ctx, manifestDesc, bytes.NewReader(manifestJSON)); err != nil && !errors.Is(err, errdef.ErrAlreadyExists) { - return ocispec.Descriptor{}, fmt.Errorf("failed to push manifest: %w", err) + // prepare config + var configDesc ocispec.Descriptor + if opts.ConfigDescriptor != nil { + if err := validateMediaType(opts.ConfigDescriptor.MediaType); err != nil { + return ocispec.Descriptor{}, fmt.Errorf("invalid config mediaType format: %w", err) + } + configDesc = *opts.ConfigDescriptor + } else { + if artifactType == "" { + artifactType = MediaTypeUnknownConfig + } else if err := validateMediaType(artifactType); err != nil { + return ocispec.Descriptor{}, fmt.Errorf("invalid artifactType format: %w", err) + } + var err error + configDesc, err = pushCustomEmptyConfig(ctx, pusher, artifactType, opts.ConfigAnnotations) + if err != nil { + return ocispec.Descriptor{}, err + } } - return manifestDesc, nil + annotations, err := ensureAnnotationCreated(opts.ManifestAnnotations, ocispec.AnnotationCreated) + if err != nil { + return ocispec.Descriptor{}, err + } + if opts.Layers == nil { + opts.Layers = []ocispec.Descriptor{} // make it an empty array to prevent potential server-side bugs + } + manifest := ocispec.Manifest{ + Versioned: specs.Versioned{ + SchemaVersion: 2, // historical value. does not pertain to OCI or docker version + }, + Config: configDesc, + MediaType: ocispec.MediaTypeImageManifest, + Layers: opts.Layers, + Annotations: annotations, + } + return pushManifest(ctx, pusher, manifest, manifest.MediaType, manifest.Config.MediaType, manifest.Annotations) } -// packImage packs the given blobs, generates an image manifest for the pack, -// and pushes it to a content storage. artifactType will be used as the config -// descriptor mediaType of the image manifest. -// If succeeded, returns a descriptor of the manifest. -func packImage(ctx context.Context, pusher content.Pusher, configMediaType string, layers []ocispec.Descriptor, opts PackOptions) (ocispec.Descriptor, error) { +// packManifestV1_1_RC2 packs an image manifest as defined in image-spec +// v1.1.0-rc2. +// Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc2/manifest.md +func packManifestV1_1_RC2(ctx context.Context, pusher content.Pusher, configMediaType string, layers []ocispec.Descriptor, opts PackOptions) (ocispec.Descriptor, error) { if configMediaType == "" { configMediaType = MediaTypeUnknownConfig } + // prepare config var configDesc ocispec.Descriptor if opts.ConfigDescriptor != nil { configDesc = *opts.ConfigDescriptor } else { - // Use an empty JSON object here, because some registries may not accept - // empty config blob. - // As of September 2022, GAR is known to return 400 on empty blob upload. - // See https://github.com/oras-project/oras-go/issues/294 for details. - configBytes := []byte("{}") - configDesc = content.NewDescriptorFromBytes(configMediaType, configBytes) - configDesc.Annotations = opts.ConfigAnnotations - // push config - if err := pusher.Push(ctx, configDesc, bytes.NewReader(configBytes)); err != nil && !errors.Is(err, errdef.ErrAlreadyExists) { - return ocispec.Descriptor{}, fmt.Errorf("failed to push config: %w", err) + var err error + configDesc, err = pushCustomEmptyConfig(ctx, pusher, configMediaType, opts.ConfigAnnotations) + if err != nil { + return ocispec.Descriptor{}, err } } @@ -161,23 +280,124 @@ func packImage(ctx context.Context, pusher content.Pusher, configMediaType strin Subject: opts.Subject, Annotations: annotations, } + return pushManifest(ctx, pusher, manifest, manifest.MediaType, manifest.Config.MediaType, manifest.Annotations) +} + +// packManifestV1_1_RC4 packs an image manifest defined in image-spec v1.1.0-rc4. +// Reference: https://github.com/opencontainers/image-spec/blob/v1.1.0-rc4/manifest.md#guidelines-for-artifact-usage +func packManifestV1_1_RC4(ctx context.Context, pusher content.Pusher, artifactType string, opts PackManifestOptions) (ocispec.Descriptor, error) { + if artifactType == "" && (opts.ConfigDescriptor == nil || opts.ConfigDescriptor.MediaType == ocispec.MediaTypeEmptyJSON) { + // artifactType MUST be set when config.mediaType is set to the empty value + return ocispec.Descriptor{}, ErrMissingArtifactType + } + if artifactType != "" { + if err := validateMediaType(artifactType); err != nil { + return ocispec.Descriptor{}, fmt.Errorf("invalid artifactType format: %w", err) + } + } + + // prepare config + var emptyBlobExists bool + var configDesc ocispec.Descriptor + if opts.ConfigDescriptor != nil { + if err := validateMediaType(opts.ConfigDescriptor.MediaType); err != nil { + return ocispec.Descriptor{}, fmt.Errorf("invalid config mediaType format: %w", err) + } + configDesc = *opts.ConfigDescriptor + } else { + // use the empty descriptor for config + configDesc = ocispec.DescriptorEmptyJSON + configDesc.Annotations = opts.ConfigAnnotations + configBytes := ocispec.DescriptorEmptyJSON.Data + // push config + if err := pushIfNotExist(ctx, pusher, configDesc, configBytes); err != nil { + return ocispec.Descriptor{}, fmt.Errorf("failed to push config: %w", err) + } + emptyBlobExists = true + } + + annotations, err := ensureAnnotationCreated(opts.ManifestAnnotations, ocispec.AnnotationCreated) + if err != nil { + return ocispec.Descriptor{}, err + } + if len(opts.Layers) == 0 { + // use the empty descriptor as the single layer + layerDesc := ocispec.DescriptorEmptyJSON + layerData := ocispec.DescriptorEmptyJSON.Data + if !emptyBlobExists { + if err := pushIfNotExist(ctx, pusher, layerDesc, layerData); err != nil { + return ocispec.Descriptor{}, fmt.Errorf("failed to push layer: %w", err) + } + } + opts.Layers = []ocispec.Descriptor{layerDesc} + } + + manifest := ocispec.Manifest{ + Versioned: specs.Versioned{ + SchemaVersion: 2, // historical value. does not pertain to OCI or docker version + }, + Config: configDesc, + MediaType: ocispec.MediaTypeImageManifest, + Layers: opts.Layers, + Subject: opts.Subject, + ArtifactType: artifactType, + Annotations: annotations, + } + return pushManifest(ctx, pusher, manifest, manifest.MediaType, manifest.ArtifactType, manifest.Annotations) +} + +// pushIfNotExist pushes data described by desc if it does not exist in the +// target. +func pushIfNotExist(ctx context.Context, pusher content.Pusher, desc ocispec.Descriptor, data []byte) error { + if ros, ok := pusher.(content.ReadOnlyStorage); ok { + exists, err := ros.Exists(ctx, desc) + if err != nil { + return fmt.Errorf("failed to check existence: %s: %s: %w", desc.Digest.String(), desc.MediaType, err) + } + if exists { + return nil + } + } + + if err := pusher.Push(ctx, desc, bytes.NewReader(data)); err != nil && !errors.Is(err, errdef.ErrAlreadyExists) { + return fmt.Errorf("failed to push: %s: %s: %w", desc.Digest.String(), desc.MediaType, err) + } + return nil +} + +// pushManifest marshals manifest into JSON bytes and pushes it. +func pushManifest(ctx context.Context, pusher content.Pusher, manifest any, mediaType string, artifactType string, annotations map[string]string) (ocispec.Descriptor, error) { manifestJSON, err := json.Marshal(manifest) if err != nil { return ocispec.Descriptor{}, fmt.Errorf("failed to marshal manifest: %w", err) } - manifestDesc := content.NewDescriptorFromBytes(ocispec.MediaTypeImageManifest, manifestJSON) + manifestDesc := content.NewDescriptorFromBytes(mediaType, manifestJSON) // populate ArtifactType and Annotations of the manifest into manifestDesc - manifestDesc.ArtifactType = manifest.Config.MediaType - manifestDesc.Annotations = manifest.Annotations - + manifestDesc.ArtifactType = artifactType + manifestDesc.Annotations = annotations // push manifest if err := pusher.Push(ctx, manifestDesc, bytes.NewReader(manifestJSON)); err != nil && !errors.Is(err, errdef.ErrAlreadyExists) { return ocispec.Descriptor{}, fmt.Errorf("failed to push manifest: %w", err) } - return manifestDesc, nil } +// pushCustomEmptyConfig generates and pushes an empty config blob. +func pushCustomEmptyConfig(ctx context.Context, pusher content.Pusher, mediaType string, annotations map[string]string) (ocispec.Descriptor, error) { + // Use an empty JSON object here, because some registries may not accept + // empty config blob. + // As of September 2022, GAR is known to return 400 on empty blob upload. + // See https://github.com/oras-project/oras-go/issues/294 for details. + configBytes := []byte("{}") + configDesc := content.NewDescriptorFromBytes(mediaType, configBytes) + configDesc.Annotations = annotations + // push config + if err := pushIfNotExist(ctx, pusher, configDesc, configBytes); err != nil { + return ocispec.Descriptor{}, fmt.Errorf("failed to push config: %w", err) + } + return configDesc, nil +} + // ensureAnnotationCreated ensures that annotationCreatedKey is in annotations, // and that its value conforms to RFC 3339. Otherwise returns a new annotation // map with annotationCreatedKey created. @@ -201,3 +421,11 @@ func ensureAnnotationCreated(annotations map[string]string, annotationCreatedKey copied[annotationCreatedKey] = now.Format(time.RFC3339) return copied, nil } + +// validateMediaType validates the format of mediaType. +func validateMediaType(mediaType string) error { + if !mediaTypeRegexp.MatchString(mediaType) { + return fmt.Errorf("%s: %w", mediaType, errdef.ErrInvalidMediaType) + } + return nil +} diff --git a/vendor/oras.land/oras-go/v2/registry/reference.go b/vendor/oras.land/oras-go/v2/registry/reference.go index cea579a141..7661a162bd 100644 --- a/vendor/oras.land/oras-go/v2/registry/reference.go +++ b/vendor/oras.land/oras-go/v2/registry/reference.go @@ -31,14 +31,16 @@ var ( // repository name set under OCI distribution spec is a subset of the docker // spec. For maximum compatability, the docker spec is verified client-side. // Further checks are left to the server-side. + // // References: - // - https://github.com/distribution/distribution/blob/v2.7.1/reference/regexp.go#L53 - // - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#pulling-manifests + // - https://github.com/distribution/distribution/blob/v2.7.1/reference/regexp.go#L53 + // - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#pulling-manifests repositoryRegexp = regexp.MustCompile(`^[a-z0-9]+(?:(?:[._]|__|[-]*)[a-z0-9]+)*(?:/[a-z0-9]+(?:(?:[._]|__|[-]*)[a-z0-9]+)*)*$`) // tagRegexp checks the tag name. // The docker and OCI spec have the same regular expression. - // Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#pulling-manifests + // + // Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#pulling-manifests tagRegexp = regexp.MustCompile(`^[\w][\w.-]{0,127}$`) ) diff --git a/vendor/oras.land/oras-go/v2/registry/remote/errcode/errors.go b/vendor/oras.land/oras-go/v2/registry/remote/errcode/errors.go index cf0018a018..fb192aa8a3 100644 --- a/vendor/oras.land/oras-go/v2/registry/remote/errcode/errors.go +++ b/vendor/oras.land/oras-go/v2/registry/remote/errcode/errors.go @@ -24,7 +24,7 @@ import ( ) // References: -// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#error-codes +// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#error-codes // - https://docs.docker.com/registry/spec/api/#errors-2 const ( ErrorCodeBlobUnknown = "BLOB_UNKNOWN" @@ -45,7 +45,7 @@ const ( // Error represents a response inner error returned by the remote // registry. // References: -// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#error-codes +// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#error-codes // - https://docs.docker.com/registry/spec/api/#errors-2 type Error struct { Code string `json:"code"` @@ -73,7 +73,7 @@ func (e Error) Error() string { // Errors represents a list of response inner errors returned by the remote // server. // References: -// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#error-codes +// - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#error-codes // - https://docs.docker.com/registry/spec/api/#errors-2 type Errors []Error diff --git a/vendor/oras.land/oras-go/v2/registry/repository.go b/vendor/oras.land/oras-go/v2/registry/repository.go index 2dd7ff994b..b75b7b8ea4 100644 --- a/vendor/oras.land/oras-go/v2/registry/repository.go +++ b/vendor/oras.land/oras-go/v2/registry/repository.go @@ -82,7 +82,7 @@ type ReferenceFetcher interface { } // ReferrerLister provides the Referrers API. -// Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#listing-referrers +// Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#listing-referrers type ReferrerLister interface { Referrers(ctx context.Context, desc ocispec.Descriptor, artifactType string, fn func(referrers []ocispec.Descriptor) error) error } @@ -93,16 +93,19 @@ type TagLister interface { // Since the returned tag list may be paginated by the underlying // implementation, a function should be passed in to process the paginated // tag list. + // // `last` argument is the `last` parameter when invoking the tags API. // If `last` is NOT empty, the entries in the response start after the // tag specified by `last`. Otherwise, the response starts from the top // of the Tags list. + // // Note: When implemented by a remote registry, the tags API is called. // However, not all registries supports pagination or conforms the // specification. + // // References: - // - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc1/spec.md#content-discovery - // - https://docs.docker.com/registry/spec/api/#tags + // - https://github.com/opencontainers/distribution-spec/blob/v1.1.0-rc3/spec.md#content-discovery + // - https://docs.docker.com/registry/spec/api/#tags // See also `Tags()` in this package. Tags(ctx context.Context, last string, fn func(tags []string) error) error }