Compare commits

...

25 commits

Author SHA1 Message Date
Milos Gajdos
3fe707de5c
chore: fix typos returned in some errors (#4414)
Some checks failed
FOSSA License Scanning / scan-license (push) Failing after 4s
Scorecards supply-chain security / Scorecards analysis (push) Failing after 9s
e2e / run-e2e-test (push) Failing after 27s
build / test (1.21.12, test-cloud-storage) (push) Failing after 8s
dockerhub-readme / update (push) Failing after 23s
validate / validate (validate-git) (push) Failing after 6s
build / test (1.22.5, test-coverage) (push) Failing after 6s
conformance / run-conformance-test (push) Failing after 29s
docs / build (push) Failing after 30s
docs / deploy (push) Has been skipped
e2e / run-e2e-test-s3-storage (push) Failing after 54s
validate / validate (validate-vendor) (push) Failing after 1m47s
CodeQL / Analyze (go) (push) Failing after 1m44s
build / test (1.22.5, test-cloud-storage) (push) Failing after 1m46s
validate / validate (lint) (push) Failing after 2m0s
build / test (1.21.12, test-coverage) (push) Failing after 12m41s
build / build (push) Has been cancelled
2024-07-22 09:13:30 +01:00
Milos Gajdos
0c4d622374
build(deps): bump ossf/scorecard-action from 2.3.1 to 2.3.3 (#4416) 2024-07-22 09:13:06 +01:00
dependabot[bot]
f072af9573
build(deps): bump ossf/scorecard-action from 2.3.1 to 2.3.3
Bumps [ossf/scorecard-action](https://github.com/ossf/scorecard-action) from 2.3.1 to 2.3.3.
- [Release notes](https://github.com/ossf/scorecard-action/releases)
- [Changelog](https://github.com/ossf/scorecard-action/blob/main/RELEASE.md)
- [Commits](0864cf1902...dc50aa9510)

---
updated-dependencies:
- dependency-name: ossf/scorecard-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-22 01:17:16 +00:00
Milos Gajdos
91eda593ef
chore: fix typos returned in some errors
Signed-off-by: Milos Gajdos <milosthegajdos@gmail.com>
2024-07-21 10:12:15 +01:00
Milos Gajdos
21f3291612
build(deps): bump docker/bake-action from 4 to 5 (#4410) 2024-07-20 07:56:23 +01:00
Milos Gajdos
fde4b7d664
build(deps): bump softprops/action-gh-release from 1 to 2 (#4407) 2024-07-19 16:10:24 +01:00
Milos Gajdos
2577121fa8
fix nil pointer in s3 list api (#4412) 2024-07-19 16:02:49 +01:00
Jan-Otto Kröpke
8619a11f73
fix nil pointer in s3 list api
Signed-off-by: Jan-Otto Kröpke <github@jkroepke.de>
2024-07-19 15:12:54 +02:00
dependabot[bot]
d4f611dfab
build(deps): bump docker/bake-action from 4 to 5
Bumps [docker/bake-action](https://github.com/docker/bake-action) from 4 to 5.
- [Release notes](https://github.com/docker/bake-action/releases)
- [Commits](https://github.com/docker/bake-action/compare/v4...v5)

---
updated-dependencies:
- dependency-name: docker/bake-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-19 09:48:22 +00:00
dependabot[bot]
3fe99ca2c0
build(deps): bump softprops/action-gh-release from 1 to 2
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 1 to 2.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/v1...v2)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-19 09:47:57 +00:00
Milos Gajdos
252619876a
fix logic for handling regionEndpoint (#4341) 2024-07-18 22:56:58 +01:00
Milos Gajdos
33b657b5ae
deprecate Versioned in favor of oci.Versioned (#3887) 2024-07-18 19:44:14 +01:00
Sebastiaan van Stijn
1e89cf780c
deprecate Versioned in favor of oci.Versioned
Update the Manifest types to use the oci implementation of the Versioned
struct.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-18 18:38:32 +02:00
Milos Gajdos
ed46691519
ci:bump Go version (#4402) 2024-07-18 14:59:27 +01:00
Wang Yan
e0503319b2
manifest: slight cleanup of init / registration (#4403) 2024-07-18 19:53:48 +08:00
Milos Gajdos
753d64b677
S3 driver: Attempt HeadObject on Stat first, fail over to List (#4401) 2024-07-17 10:25:16 +01:00
Milos Gajdos
a18cc8a656
S3 driver: Attempt HeadObject on Stat first, fail over to List
Stat always calls ListObjects when stat-ing S3 key.
Unfortauntely ListObjects is not a free call - both in terms of egress
and actual AWS costs (likely because of the egress).

This changes the behaviour of Stat such that we always attempt the
HeadObject call first and only ever fall through to ListObjects if the
HeadObject returns an AWS API error.

Note, that the official docs mention that the only error returned by
HEAD is NoSuchKey; experiments show that this is demonstrably wrong and
the AWS docs are simply outdated at the time of this commit.

HeadObject actually returns the following errors:
* NotFound: if the queried key does not exist
* NotFound: if the queried key contains subkeys i.e. it's a prefix
* BucketRegionError: if the bucket does not exist
* Forbidden: if Head operation is not allows via IAM/ACLs

Co-authored-by: Cory Snider <corhere@gmail.com>
Co-authored-by: Sebastiaan van Stijn <github@gone.nl>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
Signed-off-by: Milos Gajdos <milosthegajdos@gmail.com>
2024-07-17 10:16:54 +01:00
Milos Gajdos
54cf4165d4
Descriptor: do not implement Describable interface (#3886) 2024-07-16 14:42:15 +01:00
Sebastiaan van Stijn
3d0239ac6f
manifest: slight cleanup of init / registration
Change the marshal-funcs to a regular function instead of definining
as part of an init and remove some intermediate variables.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-16 12:02:28 +02:00
Sebastiaan van Stijn
f1c8c41408
Descriptor: do not implement Describable interface
Commit cb6f002350 implemented a generic
Manifest interface to represent manifests in the registry and remove
references to schema specific manifests.

As part of this refactor, the Describable interface was introduced,
which allowed for a single ManifestBuilder interface to handle both
schema1 and schema2 manifests. Implementations of Describable are
generally objects which can be described, not simply descriptors, but
for convenience, this interface was also implemented on Descriptor in
2ff77c00ba.

This interface served its purpose, but no longer needed for most cases;
schema2 (and OCI) descriptors do not need this method, making it only
needed for `schema1.Reference`, which is now deprecated.

Requiring this interface to be implemented limits interoperability
between distribution's Descriptor and the OCI Descriptor types, which
are identical in every other way, except for the presence of the
Describable interface.

This patch:

- Removes the `Descriptor.Descriptor()` method (no longer implementing
  the `Describable` interface).
- Updates ManifestBuilder interface and implementations to accept either
- Updates ManifestBuilder interface and implementations to accept a
  `Descriptor`.

After this patch, the caller is responsible for changing a describable
type into a descriptor;

    builder.AppendReference(describable.Descriptor())

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-16 11:30:53 +02:00
Cory Snider
671184e910
Remove ManifestBuilder interface
Defining an interface on the implementer side is generally not best
practice in Go code. There is no code in the distribution module which
consumes a ManifestBuilder value so there is no need to define the
interface in the distribution module. Export the concrete
ManifestBuilder types and modify the constructors to return concrete
values.

Co-authored-by: Sebastiaan van Stijn <github@gone.nl>
Signed-off-by: Cory Snider <csnider@mirantis.com>
2024-07-16 11:16:06 +02:00
Milos Gajdos
c345425ff5
ci:bump Go version
Signed-off-by: Milos Gajdos <milosthegajdos@gmail.com>
2024-07-13 19:24:26 +01:00
Milos Gajdos
f22dd61860
vendor: github.com/opencontainers/image-spec v1.1.0 (#3889) 2024-07-11 08:27:33 +01:00
Sebastiaan van Stijn
9ba7340601
vendor: github.com/opencontainers/image-spec v1.1.0
full diff: https://github.com/opencontainers/image-spec/compare/v1.0.2...v1.1.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-10 14:58:09 -05:00
Ankur Kothiwal
eb6123f5ed fix logic for handling regionEndpoint
With the current logic we only verifies the region and return if it's
empty; we were not validating the regionEndpoint parameter.

Signed-off-by: Ankur Kothiwal <ankur.kothiwal@cern.com>
2024-05-07 17:03:12 +02:00
49 changed files with 440 additions and 314 deletions

View file

@ -27,8 +27,8 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
go: go:
- 1.21.8 - 1.21.12
- 1.22.1 - 1.22.5
target: target:
- test-coverage - test-coverage
- test-cloud-storage - test-cloud-storage
@ -112,7 +112,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- -
name: Build artifacts name: Build artifacts
uses: docker/bake-action@v4 uses: docker/bake-action@v5
with: with:
targets: artifact-all targets: artifact-all
- -
@ -140,7 +140,7 @@ jobs:
if-no-files-found: error if-no-files-found: error
- -
name: Build image name: Build image
uses: docker/bake-action@v4 uses: docker/bake-action@v5
with: with:
files: | files: |
./docker-bake.hcl ./docker-bake.hcl
@ -149,7 +149,7 @@ jobs:
push: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') }} push: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') }}
- -
name: GitHub Release name: GitHub Release
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/') if: startsWith(github.ref, 'refs/tags/')
with: with:
draft: true draft: true

View file

@ -22,7 +22,7 @@ jobs:
fetch-depth: 0 fetch-depth: 0
- -
name: Build image name: Build image
uses: docker/bake-action@v4 uses: docker/bake-action@v5
with: with:
targets: image-local targets: image-local
- -

View file

@ -30,7 +30,7 @@ jobs:
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: Build docs - name: Build docs
uses: docker/bake-action@v4 uses: docker/bake-action@v5
with: with:
files: | files: |
docker-bake.hcl docker-bake.hcl

View file

@ -25,7 +25,7 @@ jobs:
fetch-depth: 0 fetch-depth: 0
- -
name: Build image name: Build image
uses: docker/bake-action@v4 uses: docker/bake-action@v5
with: with:
targets: image-local targets: image-local
- -

View file

@ -27,7 +27,7 @@ jobs:
persist-credentials: false persist-credentials: false
- name: "Run analysis" - name: "Run analysis"
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # tag=v2.3.1 uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # tag=v2.3.3
with: with:
results_file: results.sarif results_file: results.sarif
results_format: sarif results_format: sarif

View file

@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1 # syntax=docker/dockerfile:1
ARG GO_VERSION=1.22.4 ARG GO_VERSION=1.22.5
ARG ALPINE_VERSION=3.20 ARG ALPINE_VERSION=3.20
ARG XX_VERSION=1.2.1 ARG XX_VERSION=1.2.1

View file

@ -85,15 +85,6 @@ type Descriptor struct {
// depend on the simplicity of this type. // depend on the simplicity of this type.
} }
// Descriptor returns the descriptor, to make it satisfy the Describable
// interface. Note that implementations of Describable are generally objects
// which can be described, not simply descriptors; this exception is in place
// to make it more convenient to pass actual descriptors to functions that
// expect Describable objects.
func (d Descriptor) Descriptor() Descriptor {
return d
}
// BlobStatter makes blob descriptors available by digest. The service may // BlobStatter makes blob descriptors available by digest. The service may
// provide a descriptor of a different digest if the provided digest is not // provide a descriptor of a different digest if the provided digest is not
// canonical. // canonical.

View file

@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1 # syntax=docker/dockerfile:1
ARG GO_VERSION=1.22.4 ARG GO_VERSION=1.22.5
ARG ALPINE_VERSION=3.20 ARG ALPINE_VERSION=3.20
FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS base FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS base

View file

@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1 # syntax=docker/dockerfile:1
ARG GO_VERSION=1.22.4 ARG GO_VERSION=1.22.5
ARG ALPINE_VERSION=3.20 ARG ALPINE_VERSION=3.20
FROM alpine:${ALPINE_VERSION} AS base FROM alpine:${ALPINE_VERSION} AS base

View file

@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1 # syntax=docker/dockerfile:1
ARG GO_VERSION=1.22.4 ARG GO_VERSION=1.22.5
ARG ALPINE_VERSION=3.20 ARG ALPINE_VERSION=3.20
ARG GOLANGCI_LINT_VERSION=v1.59.1 ARG GOLANGCI_LINT_VERSION=v1.59.1
ARG BUILDTAGS="" ARG BUILDTAGS=""

View file

@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1 # syntax=docker/dockerfile:1
ARG GO_VERSION=1.22.4 ARG GO_VERSION=1.22.5
ARG ALPINE_VERSION=3.20 ARG ALPINE_VERSION=3.20
ARG MODOUTDATED_VERSION=v0.8.0 ARG MODOUTDATED_VERSION=v0.8.0

4
go.mod
View file

@ -1,6 +1,6 @@
module github.com/distribution/distribution/v3 module github.com/distribution/distribution/v3
go 1.22.4 go 1.22.5
require ( require (
cloud.google.com/go/storage v1.30.1 cloud.google.com/go/storage v1.30.1
@ -22,7 +22,7 @@ require (
github.com/klauspost/compress v1.17.4 github.com/klauspost/compress v1.17.4
github.com/mitchellh/mapstructure v1.5.0 github.com/mitchellh/mapstructure v1.5.0
github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.0.2 github.com/opencontainers/image-spec v1.1.0
github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 github.com/redis/go-redis/extra/redisotel/v9 v9.0.5
github.com/redis/go-redis/v9 v9.1.0 github.com/redis/go-redis/v9 v9.1.0
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3

4
go.sum
View file

@ -183,8 +183,8 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=

View file

@ -18,13 +18,13 @@ import (
"github.com/distribution/distribution/v3" "github.com/distribution/distribution/v3"
"github.com/distribution/distribution/v3/internal/dcontext" "github.com/distribution/distribution/v3/internal/dcontext"
"github.com/distribution/distribution/v3/manifest"
"github.com/distribution/distribution/v3/manifest/ocischema" "github.com/distribution/distribution/v3/manifest/ocischema"
"github.com/distribution/distribution/v3/registry/api/errcode" "github.com/distribution/distribution/v3/registry/api/errcode"
"github.com/distribution/distribution/v3/testutil" "github.com/distribution/distribution/v3/testutil"
"github.com/distribution/reference" "github.com/distribution/reference"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go"
v1 "github.com/opencontainers/image-spec/specs-go/v1" v1 "github.com/opencontainers/image-spec/specs-go/v1"
) )
@ -928,10 +928,8 @@ func newRandomOCIManifest(t *testing.T, blobCount int) (*ocischema.Manifest, dig
} }
m := ocischema.Manifest{ m := ocischema.Manifest{
Versioned: manifest.Versioned{ Versioned: specs.Versioned{SchemaVersion: 2},
SchemaVersion: 2, MediaType: v1.MediaTypeImageManifest,
MediaType: v1.MediaTypeImageManifest,
},
Config: distribution.Descriptor{ Config: distribution.Descriptor{
Digest: "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b", Digest: "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b",
Size: 123, Size: 123,

View file

@ -8,6 +8,7 @@ import (
"github.com/distribution/distribution/v3" "github.com/distribution/distribution/v3"
"github.com/distribution/distribution/v3/manifest" "github.com/distribution/distribution/v3/manifest"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go"
v1 "github.com/opencontainers/image-spec/specs-go/v1" v1 "github.com/opencontainers/image-spec/specs-go/v1"
) )
@ -18,35 +19,39 @@ const (
// SchemaVersion provides a pre-initialized version structure for this // SchemaVersion provides a pre-initialized version structure for this
// packages version of the manifest. // packages version of the manifest.
//
// Deprecated: use [specs.Versioned] and set MediaType on the manifest
// to [MediaTypeManifestList].
//
//nolint:staticcheck // ignore SA1019: manifest.Versioned is deprecated:
var SchemaVersion = manifest.Versioned{ var SchemaVersion = manifest.Versioned{
SchemaVersion: 2, SchemaVersion: 2,
MediaType: MediaTypeManifestList, MediaType: MediaTypeManifestList,
} }
func init() { func init() {
manifestListFunc := func(b []byte) (distribution.Manifest, distribution.Descriptor, error) { if err := distribution.RegisterManifestSchema(MediaTypeManifestList, unmarshalManifestList); err != nil {
m := new(DeserializedManifestList)
err := m.UnmarshalJSON(b)
if err != nil {
return nil, distribution.Descriptor{}, err
}
if m.MediaType != MediaTypeManifestList {
err = fmt.Errorf("mediaType in manifest list should be '%s' not '%s'",
MediaTypeManifestList, m.MediaType)
return nil, distribution.Descriptor{}, err
}
dgst := digest.FromBytes(b)
return m, distribution.Descriptor{Digest: dgst, Size: int64(len(b)), MediaType: MediaTypeManifestList}, err
}
err := distribution.RegisterManifestSchema(MediaTypeManifestList, manifestListFunc)
if err != nil {
panic(fmt.Sprintf("Unable to register manifest: %s", err)) panic(fmt.Sprintf("Unable to register manifest: %s", err))
} }
} }
func unmarshalManifestList(b []byte) (distribution.Manifest, distribution.Descriptor, error) {
m := &DeserializedManifestList{}
if err := m.UnmarshalJSON(b); err != nil {
return nil, distribution.Descriptor{}, err
}
if m.MediaType != MediaTypeManifestList {
return nil, distribution.Descriptor{}, fmt.Errorf("mediaType in manifest list should be '%s' not '%s'", MediaTypeManifestList, m.MediaType)
}
return m, distribution.Descriptor{
Digest: digest.FromBytes(b),
Size: int64(len(b)),
MediaType: MediaTypeManifestList,
}, nil
}
// PlatformSpec specifies a platform where a particular image manifest is // PlatformSpec specifies a platform where a particular image manifest is
// applicable. // applicable.
type PlatformSpec struct { type PlatformSpec struct {
@ -85,7 +90,10 @@ type ManifestDescriptor struct {
// ManifestList references manifests for various platforms. // ManifestList references manifests for various platforms.
type ManifestList struct { type ManifestList struct {
manifest.Versioned specs.Versioned
// MediaType is the media type of this schema.
MediaType string `json:"mediaType,omitempty"`
// Manifests references a list of manifests // Manifests references a list of manifests
Manifests []ManifestDescriptor `json:"manifests"` Manifests []ManifestDescriptor `json:"manifests"`
@ -128,10 +136,8 @@ func FromDescriptors(descriptors []ManifestDescriptor) (*DeserializedManifestLis
// fromDescriptorsWithMediaType is for testing purposes, it's useful to be able to specify the media type explicitly // fromDescriptorsWithMediaType is for testing purposes, it's useful to be able to specify the media type explicitly
func fromDescriptorsWithMediaType(descriptors []ManifestDescriptor, mediaType string) (*DeserializedManifestList, error) { func fromDescriptorsWithMediaType(descriptors []ManifestDescriptor, mediaType string) (*DeserializedManifestList, error) {
m := ManifestList{ m := ManifestList{
Versioned: manifest.Versioned{ Versioned: specs.Versioned{SchemaVersion: 2},
SchemaVersion: SchemaVersion.SchemaVersion, MediaType: mediaType,
MediaType: mediaType,
},
} }
m.Manifests = make([]ManifestDescriptor, len(descriptors)) m.Manifests = make([]ManifestDescriptor, len(descriptors))
@ -176,7 +182,14 @@ func (m *DeserializedManifestList) MarshalJSON() ([]byte, error) {
// Payload returns the raw content of the manifest list. The contents can be // Payload returns the raw content of the manifest list. The contents can be
// used to calculate the content identifier. // used to calculate the content identifier.
func (m DeserializedManifestList) Payload() (string, []byte, error) { func (m DeserializedManifestList) Payload() (string, []byte, error) {
return m.MediaType, m.canonical, nil var mediaType string
if m.MediaType == "" {
mediaType = v1.MediaTypeImageIndex
} else {
mediaType = m.MediaType
}
return mediaType, m.canonical, nil
} }
// validateManifestList returns an error if the byte slice is invalid JSON or if it // validateManifestList returns an error if the byte slice is invalid JSON or if it

View file

@ -5,8 +5,8 @@ import (
"errors" "errors"
"github.com/distribution/distribution/v3" "github.com/distribution/distribution/v3"
"github.com/distribution/distribution/v3/manifest"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go"
v1 "github.com/opencontainers/image-spec/specs-go/v1" v1 "github.com/opencontainers/image-spec/specs-go/v1"
) )
@ -32,7 +32,7 @@ type Builder struct {
// NewManifestBuilder is used to build new manifests for the current schema // NewManifestBuilder is used to build new manifests for the current schema
// version. It takes a BlobService so it can publish the configuration blob // version. It takes a BlobService so it can publish the configuration blob
// as part of the Build process, and annotations. // as part of the Build process, and annotations.
func NewManifestBuilder(bs distribution.BlobService, configJSON []byte, annotations map[string]string) distribution.ManifestBuilder { func NewManifestBuilder(bs distribution.BlobService, configJSON []byte, annotations map[string]string) *Builder {
mb := &Builder{ mb := &Builder{
bs: bs, bs: bs,
configJSON: make([]byte, len(configJSON)), configJSON: make([]byte, len(configJSON)),
@ -58,10 +58,8 @@ func (mb *Builder) SetMediaType(mediaType string) error {
// Build produces a final manifest from the given references. // Build produces a final manifest from the given references.
func (mb *Builder) Build(ctx context.Context) (distribution.Manifest, error) { func (mb *Builder) Build(ctx context.Context) (distribution.Manifest, error) {
m := Manifest{ m := Manifest{
Versioned: manifest.Versioned{ Versioned: specs.Versioned{SchemaVersion: 2},
SchemaVersion: 2, MediaType: mb.mediaType,
MediaType: mb.mediaType,
},
Layers: make([]distribution.Descriptor, len(mb.layers)), Layers: make([]distribution.Descriptor, len(mb.layers)),
Annotations: mb.annotations, Annotations: mb.annotations,
} }
@ -96,8 +94,8 @@ func (mb *Builder) Build(ctx context.Context) (distribution.Manifest, error) {
} }
// AppendReference adds a reference to the current ManifestBuilder. // AppendReference adds a reference to the current ManifestBuilder.
func (mb *Builder) AppendReference(d distribution.Describable) error { func (mb *Builder) AppendReference(ref distribution.Descriptor) error {
mb.layers = append(mb.layers, d.Descriptor()) mb.layers = append(mb.layers, ref)
return nil return nil
} }

View file

@ -8,51 +8,56 @@ import (
"github.com/distribution/distribution/v3" "github.com/distribution/distribution/v3"
"github.com/distribution/distribution/v3/manifest" "github.com/distribution/distribution/v3/manifest"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go"
v1 "github.com/opencontainers/image-spec/specs-go/v1" v1 "github.com/opencontainers/image-spec/specs-go/v1"
) )
// IndexSchemaVersion provides a pre-initialized version structure for OCI Image // IndexSchemaVersion provides a pre-initialized version structure for OCI Image
// Indices. // Indices.
//
// Deprecated: use [specs.Versioned] and set MediaType on the manifest
// to [v1.MediaTypeImageIndex].
//
//nolint:staticcheck // ignore SA1019: manifest.Versioned is deprecated:
var IndexSchemaVersion = manifest.Versioned{ var IndexSchemaVersion = manifest.Versioned{
SchemaVersion: 2, SchemaVersion: 2,
MediaType: v1.MediaTypeImageIndex, MediaType: v1.MediaTypeImageIndex,
} }
func init() { func init() {
imageIndexFunc := func(b []byte) (distribution.Manifest, distribution.Descriptor, error) { if err := distribution.RegisterManifestSchema(v1.MediaTypeImageIndex, unmarshalImageIndex); err != nil {
if err := validateIndex(b); err != nil {
return nil, distribution.Descriptor{}, err
}
m := new(DeserializedImageIndex)
err := m.UnmarshalJSON(b)
if err != nil {
return nil, distribution.Descriptor{}, err
}
if m.MediaType != "" && m.MediaType != v1.MediaTypeImageIndex {
err = fmt.Errorf("if present, mediaType in image index should be '%s' not '%s'",
v1.MediaTypeImageIndex, m.MediaType)
return nil, distribution.Descriptor{}, err
}
dgst := digest.FromBytes(b)
return m, distribution.Descriptor{
MediaType: v1.MediaTypeImageIndex,
Digest: dgst,
Size: int64(len(b)),
Annotations: m.Annotations,
}, err
}
err := distribution.RegisterManifestSchema(v1.MediaTypeImageIndex, imageIndexFunc)
if err != nil {
panic(fmt.Sprintf("Unable to register OCI Image Index: %s", err)) panic(fmt.Sprintf("Unable to register OCI Image Index: %s", err))
} }
} }
func unmarshalImageIndex(b []byte) (distribution.Manifest, distribution.Descriptor, error) {
if err := validateIndex(b); err != nil {
return nil, distribution.Descriptor{}, err
}
m := &DeserializedImageIndex{}
if err := m.UnmarshalJSON(b); err != nil {
return nil, distribution.Descriptor{}, err
}
if m.MediaType != "" && m.MediaType != v1.MediaTypeImageIndex {
return nil, distribution.Descriptor{}, fmt.Errorf("if present, mediaType in image index should be '%s' not '%s'", v1.MediaTypeImageIndex, m.MediaType)
}
return m, distribution.Descriptor{
MediaType: v1.MediaTypeImageIndex,
Digest: digest.FromBytes(b),
Size: int64(len(b)),
Annotations: m.Annotations,
}, nil
}
// ImageIndex references manifests for various platforms. // ImageIndex references manifests for various platforms.
type ImageIndex struct { type ImageIndex struct {
manifest.Versioned specs.Versioned
// MediaType is the media type of this schema.
MediaType string `json:"mediaType,omitempty"`
// Manifests references a list of manifests // Manifests references a list of manifests
Manifests []distribution.Descriptor `json:"manifests"` Manifests []distribution.Descriptor `json:"manifests"`
@ -88,10 +93,8 @@ func FromDescriptors(descriptors []distribution.Descriptor, annotations map[stri
// fromDescriptorsWithMediaType is for testing purposes, it's useful to be able to specify the media type explicitly // fromDescriptorsWithMediaType is for testing purposes, it's useful to be able to specify the media type explicitly
func fromDescriptorsWithMediaType(descriptors []distribution.Descriptor, annotations map[string]string, mediaType string) (_ *DeserializedImageIndex, err error) { func fromDescriptorsWithMediaType(descriptors []distribution.Descriptor, annotations map[string]string, mediaType string) (_ *DeserializedImageIndex, err error) {
m := ImageIndex{ m := ImageIndex{
Versioned: manifest.Versioned{ Versioned: specs.Versioned{SchemaVersion: 2},
SchemaVersion: IndexSchemaVersion.SchemaVersion, MediaType: mediaType,
MediaType: mediaType,
},
Annotations: annotations, Annotations: annotations,
} }

View file

@ -8,44 +8,52 @@ import (
"github.com/distribution/distribution/v3" "github.com/distribution/distribution/v3"
"github.com/distribution/distribution/v3/manifest" "github.com/distribution/distribution/v3/manifest"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go"
v1 "github.com/opencontainers/image-spec/specs-go/v1" v1 "github.com/opencontainers/image-spec/specs-go/v1"
) )
// SchemaVersion provides a pre-initialized version structure for OCI Image // SchemaVersion provides a pre-initialized version structure for OCI Image
// Manifests // Manifests.
//
// Deprecated: use [specs.Versioned] and set MediaType on the manifest
// to [v1.MediaTypeImageManifest].
//
//nolint:staticcheck // ignore SA1019: manifest.Versioned is deprecated:
var SchemaVersion = manifest.Versioned{ var SchemaVersion = manifest.Versioned{
SchemaVersion: 2, SchemaVersion: 2,
MediaType: v1.MediaTypeImageManifest, MediaType: v1.MediaTypeImageManifest,
} }
func init() { func init() {
ocischemaFunc := func(b []byte) (distribution.Manifest, distribution.Descriptor, error) { if err := distribution.RegisterManifestSchema(v1.MediaTypeImageManifest, unmarshalOCISchema); err != nil {
if err := validateManifest(b); err != nil {
return nil, distribution.Descriptor{}, err
}
m := new(DeserializedManifest)
err := m.UnmarshalJSON(b)
if err != nil {
return nil, distribution.Descriptor{}, err
}
dgst := digest.FromBytes(b)
return m, distribution.Descriptor{
MediaType: v1.MediaTypeImageManifest,
Digest: dgst,
Size: int64(len(b)),
Annotations: m.Annotations,
}, err
}
err := distribution.RegisterManifestSchema(v1.MediaTypeImageManifest, ocischemaFunc)
if err != nil {
panic(fmt.Sprintf("Unable to register manifest: %s", err)) panic(fmt.Sprintf("Unable to register manifest: %s", err))
} }
} }
func unmarshalOCISchema(b []byte) (distribution.Manifest, distribution.Descriptor, error) {
if err := validateManifest(b); err != nil {
return nil, distribution.Descriptor{}, err
}
m := &DeserializedManifest{}
if err := m.UnmarshalJSON(b); err != nil {
return nil, distribution.Descriptor{}, err
}
return m, distribution.Descriptor{
MediaType: v1.MediaTypeImageManifest,
Digest: digest.FromBytes(b),
Size: int64(len(b)),
Annotations: m.Annotations,
}, nil
}
// Manifest defines a ocischema manifest. // Manifest defines a ocischema manifest.
type Manifest struct { type Manifest struct {
manifest.Versioned specs.Versioned
// MediaType is the media type of this schema.
MediaType string `json:"mediaType,omitempty"`
// Config references the image configuration as a blob. // Config references the image configuration as a blob.
Config distribution.Descriptor `json:"config"` Config distribution.Descriptor `json:"config"`
@ -125,7 +133,7 @@ func (m *DeserializedManifest) MarshalJSON() ([]byte, error) {
// Payload returns the raw content of the manifest. The contents can be used to // Payload returns the raw content of the manifest. The contents can be used to
// calculate the content identifier. // calculate the content identifier.
func (m DeserializedManifest) Payload() (string, []byte, error) { func (m *DeserializedManifest) Payload() (string, []byte, error) {
return v1.MediaTypeImageManifest, m.canonical, nil return v1.MediaTypeImageManifest, m.canonical, nil
} }

View file

@ -7,8 +7,8 @@ import (
"testing" "testing"
"github.com/distribution/distribution/v3" "github.com/distribution/distribution/v3"
"github.com/distribution/distribution/v3/manifest"
"github.com/distribution/distribution/v3/manifest/manifestlist" "github.com/distribution/distribution/v3/manifest/manifestlist"
"github.com/opencontainers/image-spec/specs-go"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
v1 "github.com/opencontainers/image-spec/specs-go/v1" v1 "github.com/opencontainers/image-spec/specs-go/v1"
@ -42,10 +42,8 @@ const expectedManifestSerialization = `{
func makeTestManifest(mediaType string) Manifest { func makeTestManifest(mediaType string) Manifest {
return Manifest{ return Manifest{
Versioned: manifest.Versioned{ Versioned: specs.Versioned{SchemaVersion: 2},
SchemaVersion: 2, MediaType: mediaType,
MediaType: mediaType,
},
Config: distribution.Descriptor{ Config: distribution.Descriptor{
MediaType: v1.MediaTypeImageConfig, MediaType: v1.MediaTypeImageConfig,
Digest: "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b", Digest: "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b",

View file

@ -4,10 +4,11 @@ import (
"context" "context"
"github.com/distribution/distribution/v3" "github.com/distribution/distribution/v3"
"github.com/opencontainers/image-spec/specs-go"
) )
// builder is a type for constructing manifests. // Builder is a type for constructing manifests.
type builder struct { type Builder struct {
// configDescriptor is used to describe configuration // configDescriptor is used to describe configuration
configDescriptor distribution.Descriptor configDescriptor distribution.Descriptor
@ -22,8 +23,8 @@ type builder struct {
// NewManifestBuilder is used to build new manifests for the current schema // NewManifestBuilder is used to build new manifests for the current schema
// version. It takes a BlobService so it can publish the configuration blob // version. It takes a BlobService so it can publish the configuration blob
// as part of the Build process. // as part of the Build process.
func NewManifestBuilder(configDescriptor distribution.Descriptor, configJSON []byte) distribution.ManifestBuilder { func NewManifestBuilder(configDescriptor distribution.Descriptor, configJSON []byte) *Builder {
mb := &builder{ mb := &Builder{
configDescriptor: configDescriptor, configDescriptor: configDescriptor,
configJSON: make([]byte, len(configJSON)), configJSON: make([]byte, len(configJSON)),
} }
@ -33,9 +34,10 @@ func NewManifestBuilder(configDescriptor distribution.Descriptor, configJSON []b
} }
// Build produces a final manifest from the given references. // Build produces a final manifest from the given references.
func (mb *builder) Build(ctx context.Context) (distribution.Manifest, error) { func (mb *Builder) Build(ctx context.Context) (distribution.Manifest, error) {
m := Manifest{ m := Manifest{
Versioned: SchemaVersion, Versioned: specs.Versioned{SchemaVersion: defaultSchemaVersion},
MediaType: defaultMediaType,
Layers: make([]distribution.Descriptor, len(mb.dependencies)), Layers: make([]distribution.Descriptor, len(mb.dependencies)),
} }
copy(m.Layers, mb.dependencies) copy(m.Layers, mb.dependencies)
@ -46,12 +48,12 @@ func (mb *builder) Build(ctx context.Context) (distribution.Manifest, error) {
} }
// AppendReference adds a reference to the current ManifestBuilder. // AppendReference adds a reference to the current ManifestBuilder.
func (mb *builder) AppendReference(d distribution.Describable) error { func (mb *Builder) AppendReference(ref distribution.Descriptor) error {
mb.dependencies = append(mb.dependencies, d.Descriptor()) mb.dependencies = append(mb.dependencies, ref)
return nil return nil
} }
// References returns the current references added to this builder. // References returns the current references added to this builder.
func (mb *builder) References() []distribution.Descriptor { func (mb *Builder) References() []distribution.Descriptor {
return mb.dependencies return mb.dependencies
} }

View file

@ -8,6 +8,7 @@ import (
"github.com/distribution/distribution/v3" "github.com/distribution/distribution/v3"
"github.com/distribution/distribution/v3/manifest" "github.com/distribution/distribution/v3/manifest"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go"
) )
const ( const (
@ -33,33 +34,48 @@ const (
MediaTypeUncompressedLayer = "application/vnd.docker.image.rootfs.diff.tar" MediaTypeUncompressedLayer = "application/vnd.docker.image.rootfs.diff.tar"
) )
const (
defaultSchemaVersion = 2
defaultMediaType = MediaTypeManifest
)
// SchemaVersion provides a pre-initialized version structure for this // SchemaVersion provides a pre-initialized version structure for this
// packages version of the manifest. // packages version of the manifest.
//
// Deprecated: use [specs.Versioned] and set MediaType on the manifest
// to [MediaTypeManifest].
//
//nolint:staticcheck // ignore SA1019: manifest.Versioned is deprecated:
var SchemaVersion = manifest.Versioned{ var SchemaVersion = manifest.Versioned{
SchemaVersion: 2, SchemaVersion: defaultSchemaVersion,
MediaType: MediaTypeManifest, MediaType: defaultMediaType,
} }
func init() { func init() {
schema2Func := func(b []byte) (distribution.Manifest, distribution.Descriptor, error) { if err := distribution.RegisterManifestSchema(defaultMediaType, unmarshalSchema2); err != nil {
m := new(DeserializedManifest)
err := m.UnmarshalJSON(b)
if err != nil {
return nil, distribution.Descriptor{}, err
}
dgst := digest.FromBytes(b)
return m, distribution.Descriptor{Digest: dgst, Size: int64(len(b)), MediaType: MediaTypeManifest}, err
}
err := distribution.RegisterManifestSchema(MediaTypeManifest, schema2Func)
if err != nil {
panic(fmt.Sprintf("Unable to register manifest: %s", err)) panic(fmt.Sprintf("Unable to register manifest: %s", err))
} }
} }
func unmarshalSchema2(b []byte) (distribution.Manifest, distribution.Descriptor, error) {
m := &DeserializedManifest{}
if err := m.UnmarshalJSON(b); err != nil {
return nil, distribution.Descriptor{}, err
}
return m, distribution.Descriptor{
Digest: digest.FromBytes(b),
Size: int64(len(b)),
MediaType: defaultMediaType,
}, nil
}
// Manifest defines a schema2 manifest. // Manifest defines a schema2 manifest.
type Manifest struct { type Manifest struct {
manifest.Versioned specs.Versioned
// MediaType is the media type of this schema.
MediaType string `json:"mediaType,omitempty"`
// Config references the image configuration as a blob. // Config references the image configuration as a blob.
Config distribution.Descriptor `json:"config"` Config distribution.Descriptor `json:"config"`
@ -114,9 +130,8 @@ func (m *DeserializedManifest) UnmarshalJSON(b []byte) error {
return err return err
} }
if mfst.MediaType != MediaTypeManifest { if mfst.MediaType != defaultMediaType {
return fmt.Errorf("mediaType in manifest should be '%s' not '%s'", return fmt.Errorf("mediaType in manifest should be '%s' not '%s'", defaultMediaType, mfst.MediaType)
MediaTypeManifest, mfst.MediaType)
} }
m.Manifest = mfst m.Manifest = mfst

View file

@ -7,7 +7,7 @@ import (
"testing" "testing"
"github.com/distribution/distribution/v3" "github.com/distribution/distribution/v3"
"github.com/distribution/distribution/v3/manifest" "github.com/opencontainers/image-spec/specs-go"
) )
const expectedManifestSerialization = `{ const expectedManifestSerialization = `{
@ -29,10 +29,8 @@ const expectedManifestSerialization = `{
func makeTestManifest(mediaType string) Manifest { func makeTestManifest(mediaType string) Manifest {
return Manifest{ return Manifest{
Versioned: manifest.Versioned{ Versioned: specs.Versioned{SchemaVersion: 2},
SchemaVersion: 2, MediaType: mediaType,
MediaType: mediaType,
},
Config: distribution.Descriptor{ Config: distribution.Descriptor{
MediaType: MediaTypeImageConfig, MediaType: MediaTypeImageConfig,
Digest: "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b", Digest: "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b",

View file

@ -3,6 +3,8 @@ package manifest
// Versioned provides a struct with the manifest schemaVersion and mediaType. // Versioned provides a struct with the manifest schemaVersion and mediaType.
// Incoming content with unknown schema version can be decoded against this // Incoming content with unknown schema version can be decoded against this
// struct to check the version. // struct to check the version.
//
// Deprecated: use [specs.Versioned] and set MediaType on the Manifest itself.
type Versioned struct { type Versioned struct {
// SchemaVersion is the image manifest schema that this image follows // SchemaVersion is the image manifest schema that this image follows
SchemaVersion int `json:"schemaVersion"` SchemaVersion int `json:"schemaVersion"`

View file

@ -26,27 +26,6 @@ type Manifest interface {
Payload() (mediaType string, payload []byte, err error) Payload() (mediaType string, payload []byte, err error)
} }
// ManifestBuilder creates a manifest allowing one to include dependencies.
// Instances can be obtained from a version-specific manifest package. Manifest
// specific data is passed into the function which creates the builder.
type ManifestBuilder interface {
// Build creates the manifest from his builder.
Build(ctx context.Context) (Manifest, error)
// References returns a list of objects which have been added to this
// builder. The dependencies are returned in the order they were added,
// which should be from base to head.
References() []Descriptor
// AppendReference includes the given object in the manifest after any
// existing dependencies. If the add fails, such as when adding an
// unsupported dependency, an error may be returned.
//
// The destination of the reference is dependent on the manifest type and
// the dependency type.
AppendReference(dependency Describable) error
}
// ManifestService describes operations on manifests. // ManifestService describes operations on manifests.
type ManifestService interface { type ManifestService interface {
// Exists returns true if the manifest exists. // Exists returns true if the manifest exists.
@ -69,8 +48,12 @@ type ManifestEnumerator interface {
Enumerate(ctx context.Context, ingester func(digest.Digest) error) error Enumerate(ctx context.Context, ingester func(digest.Digest) error) error
} }
// Describable is an interface for descriptors // Describable is an interface for descriptors.
//
// Implementations of Describable are generally objects which can be
// described, not simply descriptors.
type Describable interface { type Describable interface {
// Descriptor returns the descriptor.
Descriptor() Descriptor Descriptor() Descriptor
} }

View file

@ -4,13 +4,13 @@ import (
"testing" "testing"
"github.com/distribution/distribution/v3" "github.com/distribution/distribution/v3"
"github.com/distribution/distribution/v3/manifest"
"github.com/distribution/distribution/v3/manifest/schema2" "github.com/distribution/distribution/v3/manifest/schema2"
v2 "github.com/distribution/distribution/v3/registry/api/v2" v2 "github.com/distribution/distribution/v3/registry/api/v2"
"github.com/distribution/reference" "github.com/distribution/reference"
events "github.com/docker/go-events" events "github.com/docker/go-events"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go"
v1 "github.com/opencontainers/image-spec/specs-go/v1" v1 "github.com/opencontainers/image-spec/specs-go/v1"
) )
@ -29,7 +29,6 @@ var (
} }
request = RequestRecord{} request = RequestRecord{}
tag = "latest" tag = "latest"
ociMediaType = v1.MediaTypeImageManifest
artifactType = "application/vnd.example.sbom.v1" artifactType = "application/vnd.example.sbom.v1"
cfg = distribution.Descriptor{ cfg = distribution.Descriptor{
MediaType: artifactType, MediaType: artifactType,
@ -143,14 +142,13 @@ func TestEventBridgeRepoDeleted(t *testing.T) {
} }
func createTestEnv(t *testing.T, fn testSinkFn) Listener { func createTestEnv(t *testing.T, fn testSinkFn) Listener {
manifest := schema2.Manifest{ mfst := schema2.Manifest{
Versioned: manifest.Versioned{ Versioned: specs.Versioned{SchemaVersion: 2},
MediaType: ociMediaType, MediaType: v1.MediaTypeImageManifest,
}, Config: cfg,
Config: cfg,
} }
deserializedManifest, err := schema2.FromStruct(manifest) deserializedManifest, err := schema2.FromStruct(mfst)
if err != nil { if err != nil {
t.Fatalf("creating OCI manifest: %v", err) t.Fatalf("creating OCI manifest: %v", err)
} }

View file

@ -14,6 +14,7 @@ import (
"github.com/distribution/distribution/v3/testutil" "github.com/distribution/distribution/v3/testutil"
"github.com/distribution/reference" "github.com/distribution/reference"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go"
) )
func TestListener(t *testing.T) { func TestListener(t *testing.T) {
@ -143,7 +144,8 @@ func checkTestRepository(t *testing.T, repository distribution.Repository, remov
} }
m := schema2.Manifest{ m := schema2.Manifest{
Versioned: schema2.SchemaVersion, Versioned: specs.Versioned{SchemaVersion: 2},
MediaType: schema2.MediaTypeManifest,
Config: distribution.Descriptor{ Config: distribution.Descriptor{
MediaType: "foo/bar", MediaType: "foo/bar",
Digest: configDgst, Digest: configDgst,

View file

@ -21,7 +21,7 @@ import (
// init handles registering the token auth backend. // init handles registering the token auth backend.
func init() { func init() {
if err := auth.Register("token", auth.InitFunc(newAccessController)); err != nil { if err := auth.Register("token", auth.InitFunc(newAccessController)); err != nil {
logrus.Errorf("tailed to register token auth: %v", err) logrus.Errorf("failed to register token auth: %v", err)
} }
} }

View file

@ -21,7 +21,6 @@ import (
"github.com/distribution/distribution/v3" "github.com/distribution/distribution/v3"
"github.com/distribution/distribution/v3/configuration" "github.com/distribution/distribution/v3/configuration"
"github.com/distribution/distribution/v3/manifest"
"github.com/distribution/distribution/v3/manifest/manifestlist" "github.com/distribution/distribution/v3/manifest/manifestlist"
"github.com/distribution/distribution/v3/manifest/schema2" "github.com/distribution/distribution/v3/manifest/schema2"
"github.com/distribution/distribution/v3/registry/api/errcode" "github.com/distribution/distribution/v3/registry/api/errcode"
@ -33,6 +32,7 @@ import (
"github.com/distribution/reference" "github.com/distribution/reference"
"github.com/gorilla/handlers" "github.com/gorilla/handlers"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go"
) )
var headerConfig = http.Header{ var headerConfig = http.Header{
@ -1577,10 +1577,8 @@ func testManifestAPISchema2(t *testing.T, env *testEnv, imageName reference.Name
// -------------------------------- // --------------------------------
// Attempt to push manifest with missing config and missing layers // Attempt to push manifest with missing config and missing layers
manifest := &schema2.Manifest{ manifest := &schema2.Manifest{
Versioned: manifest.Versioned{ Versioned: specs.Versioned{SchemaVersion: 2},
SchemaVersion: 2, MediaType: schema2.MediaTypeManifest,
MediaType: schema2.MediaTypeManifest,
},
Config: distribution.Descriptor{ Config: distribution.Descriptor{
Digest: "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b", Digest: "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b",
Size: 3253, Size: 3253,
@ -1900,10 +1898,8 @@ func testManifestAPIManifestList(t *testing.T, env *testEnv, args manifestArgs)
// -------------------------------- // --------------------------------
// Attempt to push manifest list that refers to an unknown manifest // Attempt to push manifest list that refers to an unknown manifest
manifestList := &manifestlist.ManifestList{ manifestList := &manifestlist.ManifestList{
Versioned: manifest.Versioned{ Versioned: specs.Versioned{SchemaVersion: 2},
SchemaVersion: 2, MediaType: manifestlist.MediaTypeManifestList,
MediaType: manifestlist.MediaTypeManifestList,
},
Manifests: []manifestlist.ManifestDescriptor{ Manifests: []manifestlist.ManifestDescriptor{
{ {
Descriptor: distribution.Descriptor{ Descriptor: distribution.Descriptor{
@ -2639,10 +2635,8 @@ func createRepository(env *testEnv, t *testing.T, imageName string, tag string)
} }
manifest := &schema2.Manifest{ manifest := &schema2.Manifest{
Versioned: manifest.Versioned{ Versioned: specs.Versioned{SchemaVersion: 2},
SchemaVersion: 2, MediaType: schema2.MediaTypeManifest,
MediaType: schema2.MediaTypeManifest,
},
Config: distribution.Descriptor{ Config: distribution.Descriptor{
Digest: "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b", Digest: "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b",
Size: 3253, Size: 3253,
@ -2736,10 +2730,8 @@ func TestRegistryAsCacheMutationAPIs(t *testing.T) {
// Manifest upload // Manifest upload
manifest := &schema2.Manifest{ manifest := &schema2.Manifest{
Versioned: manifest.Versioned{ Versioned: specs.Versioned{SchemaVersion: 2},
SchemaVersion: 2, MediaType: schema2.MediaTypeManifest,
MediaType: schema2.MediaTypeManifest,
},
Config: distribution.Descriptor{ Config: distribution.Descriptor{
Digest: "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b", Digest: "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b",
Size: 3253, Size: 3253,

View file

@ -17,6 +17,7 @@ import (
"github.com/distribution/distribution/v3/testutil" "github.com/distribution/distribution/v3/testutil"
"github.com/distribution/reference" "github.com/distribution/reference"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go"
) )
type statsManifest struct { type statsManifest struct {
@ -153,7 +154,8 @@ func populateRepo(ctx context.Context, t *testing.T, repository distribution.Rep
} }
m := schema2.Manifest{ m := schema2.Manifest{
Versioned: schema2.SchemaVersion, Versioned: specs.Versioned{SchemaVersion: 2},
MediaType: schema2.MediaTypeManifest,
Config: distribution.Descriptor{ Config: distribution.Descriptor{
MediaType: "foo/bar", MediaType: "foo/bar",
Digest: configDigest, Digest: configDigest,

View file

@ -14,7 +14,7 @@ import (
func init() { func init() {
if err := storagemiddleware.Register("redirect", newRedirectStorageMiddleware); err != nil { if err := storagemiddleware.Register("redirect", newRedirectStorageMiddleware); err != nil {
logrus.Errorf("tailed to register redirect storage middleware: %v", err) logrus.Errorf("failed to register redirect storage middleware: %v", err)
} }
} }

View file

@ -14,7 +14,7 @@ import (
func init() { func init() {
if err := storagemiddleware.Register("rewrite", newRewriteStorageMiddleware); err != nil { if err := storagemiddleware.Register("rewrite", newRewriteStorageMiddleware); err != nil {
logrus.Errorf("tailed to register redirect storage middleware: %v", err) logrus.Errorf("failed to register redirect storage middleware: %v", err)
} }
} }

View file

@ -223,12 +223,13 @@ func FromParameters(ctx context.Context, parameters map[string]interface{}) (*Dr
} }
regionName := parameters["region"] regionName := parameters["region"]
if regionName == nil || fmt.Sprint(regionName) == "" {
return nil, fmt.Errorf("no region parameter provided")
}
region := fmt.Sprint(regionName) region := fmt.Sprint(regionName)
// Don't check the region value if a custom endpoint is provided. // Don't check the region value if a custom endpoint is provided.
if regionEndpoint == "" { if regionEndpoint == "" {
if regionName == nil || region == "" {
return nil, fmt.Errorf("no region parameter provided")
}
if _, ok := validRegions[region]; !ok { if _, ok := validRegions[region]; !ok {
return nil, fmt.Errorf("invalid region provided: %v", region) return nil, fmt.Errorf("invalid region provided: %v", region)
} }
@ -763,37 +764,76 @@ func (d *driver) Writer(ctx context.Context, path string, appendMode bool) (stor
return nil, storagedriver.PathNotFoundError{Path: path} return nil, storagedriver.PathNotFoundError{Path: path}
} }
// Stat retrieves the FileInfo for the given path, including the current size func (d *driver) statHead(ctx context.Context, path string) (*storagedriver.FileInfoFields, error) {
// in bytes and the creation time. resp, err := d.S3.HeadObjectWithContext(ctx, &s3.HeadObjectInput{
func (d *driver) Stat(ctx context.Context, path string) (storagedriver.FileInfo, error) { Bucket: aws.String(d.Bucket),
Key: aws.String(d.s3Path(path)),
})
if err != nil {
return nil, err
}
return &storagedriver.FileInfoFields{
Path: path,
IsDir: false,
Size: *resp.ContentLength,
ModTime: *resp.LastModified,
}, nil
}
func (d *driver) statList(ctx context.Context, path string) (*storagedriver.FileInfoFields, error) {
s3Path := d.s3Path(path)
resp, err := d.S3.ListObjectsV2WithContext(ctx, &s3.ListObjectsV2Input{ resp, err := d.S3.ListObjectsV2WithContext(ctx, &s3.ListObjectsV2Input{
Bucket: aws.String(d.Bucket), Bucket: aws.String(d.Bucket),
Prefix: aws.String(d.s3Path(path)), Prefix: aws.String(s3Path),
MaxKeys: aws.Int64(1), MaxKeys: aws.Int64(1),
}) })
if err != nil { if err != nil {
return nil, err return nil, err
} }
fi := storagedriver.FileInfoFields{
Path: path,
}
if len(resp.Contents) == 1 { if len(resp.Contents) == 1 {
if *resp.Contents[0].Key != d.s3Path(path) { if *resp.Contents[0].Key != s3Path {
fi.IsDir = true return &storagedriver.FileInfoFields{
} else { Path: path,
fi.IsDir = false IsDir: true,
fi.Size = *resp.Contents[0].Size }, nil
fi.ModTime = *resp.Contents[0].LastModified
} }
} else if len(resp.CommonPrefixes) == 1 { return &storagedriver.FileInfoFields{
fi.IsDir = true Path: path,
} else { Size: *resp.Contents[0].Size,
return nil, storagedriver.PathNotFoundError{Path: path} ModTime: *resp.Contents[0].LastModified,
}, nil
} }
if len(resp.CommonPrefixes) == 1 {
return &storagedriver.FileInfoFields{
Path: path,
IsDir: true,
}, nil
}
return nil, storagedriver.PathNotFoundError{Path: path}
}
return storagedriver.FileInfoInternal{FileInfoFields: fi}, nil // Stat retrieves the FileInfo for the given path, including the current size
// in bytes and the creation time.
func (d *driver) Stat(ctx context.Context, path string) (storagedriver.FileInfo, error) {
fi, err := d.statHead(ctx, path)
if err != nil {
// For AWS errors, we fail over to ListObjects:
// Though the official docs https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadObject.html#API_HeadObject_Errors
// are slightly outdated, the HeadObject actually returns NotFound error
// if querying a key which doesn't exist or a key which has nested keys
// and Forbidden if IAM/ACL permissions do not allow Head but allow List.
var awsErr awserr.Error
if errors.As(err, &awsErr) {
fi, err := d.statList(ctx, path)
if err != nil {
return nil, parseError(path, err)
}
return storagedriver.FileInfoInternal{FileInfoFields: *fi}, nil
}
// For non-AWS errors, return the error directly
return nil, err
}
return storagedriver.FileInfoInternal{FileInfoFields: *fi}, nil
} }
// List returns a list of the objects that are direct descendants of the given path. // List returns a list of the objects that are direct descendants of the given path.
@ -834,20 +874,20 @@ func (d *driver) List(ctx context.Context, opath string) ([]string, error) {
directories = append(directories, strings.Replace(commonPrefix[0:len(commonPrefix)-1], d.s3Path(""), prefix, 1)) directories = append(directories, strings.Replace(commonPrefix[0:len(commonPrefix)-1], d.s3Path(""), prefix, 1))
} }
if *resp.IsTruncated { if resp.IsTruncated == nil || !*resp.IsTruncated {
resp, err = d.S3.ListObjectsV2WithContext(ctx, &s3.ListObjectsV2Input{
Bucket: aws.String(d.Bucket),
Prefix: aws.String(d.s3Path(path)),
Delimiter: aws.String("/"),
MaxKeys: aws.Int64(listMax),
ContinuationToken: resp.NextContinuationToken,
})
if err != nil {
return nil, err
}
} else {
break break
} }
resp, err = d.S3.ListObjectsV2WithContext(ctx, &s3.ListObjectsV2Input{
Bucket: aws.String(d.Bucket),
Prefix: aws.String(d.s3Path(path)),
Delimiter: aws.String("/"),
MaxKeys: aws.Int64(listMax),
ContinuationToken: resp.NextContinuationToken,
})
if err != nil {
return nil, err
}
} }
if opath != "/" { if opath != "/" {

View file

@ -35,17 +35,17 @@ func (ms *manifestListHandler) Unmarshal(ctx context.Context, dgst digest.Digest
func (ms *manifestListHandler) Put(ctx context.Context, manifestList distribution.Manifest, skipDependencyVerification bool) (digest.Digest, error) { func (ms *manifestListHandler) Put(ctx context.Context, manifestList distribution.Manifest, skipDependencyVerification bool) (digest.Digest, error) {
dcontext.GetLogger(ms.ctx).Debug("(*manifestListHandler).Put") dcontext.GetLogger(ms.ctx).Debug("(*manifestListHandler).Put")
var schemaVersion, expectedSchemaVersion int var schemaVersion int
switch m := manifestList.(type) { switch m := manifestList.(type) {
case *manifestlist.DeserializedManifestList: case *manifestlist.DeserializedManifestList:
expectedSchemaVersion = manifestlist.SchemaVersion.SchemaVersion
schemaVersion = m.SchemaVersion schemaVersion = m.SchemaVersion
case *ocischema.DeserializedImageIndex: case *ocischema.DeserializedImageIndex:
expectedSchemaVersion = ocischema.IndexSchemaVersion.SchemaVersion
schemaVersion = m.SchemaVersion schemaVersion = m.SchemaVersion
default: default:
return "", fmt.Errorf("wrong type put to manifestListHandler: %T", manifestList) return "", fmt.Errorf("wrong type put to manifestListHandler: %T", manifestList)
} }
const expectedSchemaVersion = 2
if schemaVersion != expectedSchemaVersion { if schemaVersion != expectedSchemaVersion {
return "", fmt.Errorf("unrecognized manifest list schema version %d, expected %d", schemaVersion, expectedSchemaVersion) return "", fmt.Errorf("unrecognized manifest list schema version %d, expected %d", schemaVersion, expectedSchemaVersion)
} }

View file

@ -7,11 +7,11 @@ import (
"github.com/distribution/distribution/v3" "github.com/distribution/distribution/v3"
"github.com/distribution/distribution/v3/internal/dcontext" "github.com/distribution/distribution/v3/internal/dcontext"
"github.com/distribution/distribution/v3/manifest"
"github.com/distribution/distribution/v3/manifest/manifestlist" "github.com/distribution/distribution/v3/manifest/manifestlist"
"github.com/distribution/distribution/v3/manifest/ocischema" "github.com/distribution/distribution/v3/manifest/ocischema"
"github.com/distribution/distribution/v3/manifest/schema2" "github.com/distribution/distribution/v3/manifest/schema2"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go"
v1 "github.com/opencontainers/image-spec/specs-go/v1" v1 "github.com/opencontainers/image-spec/specs-go/v1"
) )
@ -88,7 +88,13 @@ func (ms *manifestStore) Get(ctx context.Context, dgst digest.Digest, options ..
return nil, err return nil, err
} }
var versioned manifest.Versioned // versioned is a minimal representation of a manifest with version and mediatype.
var versioned struct {
specs.Versioned
// MediaType is the media type of this schema.
MediaType string `json:"mediaType,omitempty"`
}
if err = json.Unmarshal(content, &versioned); err != nil { if err = json.Unmarshal(content, &versioned); err != nil {
return nil, err return nil, err
} }

View file

@ -9,7 +9,6 @@ import (
"testing" "testing"
"github.com/distribution/distribution/v3" "github.com/distribution/distribution/v3"
"github.com/distribution/distribution/v3/manifest"
"github.com/distribution/distribution/v3/manifest/manifestlist" "github.com/distribution/distribution/v3/manifest/manifestlist"
"github.com/distribution/distribution/v3/manifest/ocischema" "github.com/distribution/distribution/v3/manifest/ocischema"
"github.com/distribution/distribution/v3/manifest/schema2" "github.com/distribution/distribution/v3/manifest/schema2"
@ -19,6 +18,7 @@ import (
"github.com/distribution/distribution/v3/testutil" "github.com/distribution/distribution/v3/testutil"
"github.com/distribution/reference" "github.com/distribution/reference"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go"
v1 "github.com/opencontainers/image-spec/specs-go/v1" v1 "github.com/opencontainers/image-spec/specs-go/v1"
) )
@ -94,10 +94,8 @@ func testManifestStorage(t *testing.T, options ...RegistryOption) {
builder := schema2.NewManifestBuilder(d, sampleConfig) builder := schema2.NewManifestBuilder(d, sampleConfig)
m := &schema2.Manifest{ m := &schema2.Manifest{
Versioned: manifest.Versioned{ Versioned: specs.Versioned{SchemaVersion: 2},
SchemaVersion: 2, MediaType: schema2.MediaTypeManifest,
MediaType: schema2.MediaTypeManifest,
},
Config: distribution.Descriptor{ Config: distribution.Descriptor{
Digest: digest.FromBytes(sampleConfig), Digest: digest.FromBytes(sampleConfig),
Size: int64(len(sampleConfig)), Size: int64(len(sampleConfig)),
@ -393,7 +391,7 @@ func testOCIManifestStorage(t *testing.T, testname string, includeMediaTypes boo
t.Fatalf("%s: unexpected MediaType for result, %s", testname, fetchedManifest.MediaType) t.Fatalf("%s: unexpected MediaType for result, %s", testname, fetchedManifest.MediaType)
} }
if fetchedManifest.SchemaVersion != ocischema.SchemaVersion.SchemaVersion { if fetchedManifest.SchemaVersion != 2 {
t.Fatalf("%s: unexpected schema version for result, %d", testname, fetchedManifest.SchemaVersion) t.Fatalf("%s: unexpected schema version for result, %d", testname, fetchedManifest.SchemaVersion)
} }
@ -595,7 +593,7 @@ func TestIndexManifestStorageWithSelectivePlatforms(t *testing.T) {
// createRandomImage builds an image manifest and store it and its layers in the registry // createRandomImage builds an image manifest and store it and its layers in the registry
func createRandomImage(t *testing.T, testname string, imageMediaType string, blobStore distribution.BlobStore) (distribution.Manifest, error) { func createRandomImage(t *testing.T, testname string, imageMediaType string, blobStore distribution.BlobStore) (distribution.Manifest, error) {
builder := ocischema.NewManifestBuilder(blobStore, []byte{}, map[string]string{}) builder := ocischema.NewManifestBuilder(blobStore, []byte{}, map[string]string{})
err := builder.(*ocischema.Builder).SetMediaType(imageMediaType) err := builder.SetMediaType(imageMediaType)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View file

@ -88,7 +88,7 @@ func (ms *ocischemaManifestHandler) verifyManifest(ctx context.Context, mnfst oc
} }
switch descriptor.MediaType { switch descriptor.MediaType {
case v1.MediaTypeImageLayer, v1.MediaTypeImageLayerGzip, v1.MediaTypeImageLayerNonDistributable, v1.MediaTypeImageLayerNonDistributableGzip: case v1.MediaTypeImageLayer, v1.MediaTypeImageLayerGzip, v1.MediaTypeImageLayerNonDistributable, v1.MediaTypeImageLayerNonDistributableGzip: //nolint:staticcheck // ignore A1019: v1.MediaTypeImageLayerNonDistributable is deprecated: Non-distributable layers are deprecated, and not recommended for future use.
allow := ms.manifestURLs.allow allow := ms.manifestURLs.allow
deny := ms.manifestURLs.deny deny := ms.manifestURLs.deny
for _, u := range descriptor.URLs { for _, u := range descriptor.URLs {

View file

@ -7,10 +7,10 @@ import (
"testing" "testing"
"github.com/distribution/distribution/v3" "github.com/distribution/distribution/v3"
"github.com/distribution/distribution/v3/manifest"
"github.com/distribution/distribution/v3/manifest/ocischema" "github.com/distribution/distribution/v3/manifest/ocischema"
"github.com/distribution/distribution/v3/registry/storage/driver/inmemory" "github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go"
v1 "github.com/opencontainers/image-spec/specs-go/v1" v1 "github.com/opencontainers/image-spec/specs-go/v1"
) )
@ -36,7 +36,7 @@ func TestVerifyOCIManifestNonDistributableLayer(t *testing.T) {
nonDistributableLayer := distribution.Descriptor{ nonDistributableLayer := distribution.Descriptor{
Digest: "sha256:463435349086340864309863409683460843608348608934092322395278926a", Digest: "sha256:463435349086340864309863409683460843608348608934092322395278926a",
Size: 6323, Size: 6323,
MediaType: v1.MediaTypeImageLayerNonDistributableGzip, MediaType: v1.MediaTypeImageLayerNonDistributableGzip, //nolint:staticcheck // ignore A1019: v1.MediaTypeImageLayerNonDistributableGzip is deprecated: Non-distributable layers are deprecated, and not recommended for future use
} }
emptyLayer := distribution.Descriptor{ emptyLayer := distribution.Descriptor{
@ -49,11 +49,9 @@ func TestVerifyOCIManifestNonDistributableLayer(t *testing.T) {
} }
template := ocischema.Manifest{ template := ocischema.Manifest{
Versioned: manifest.Versioned{ Versioned: specs.Versioned{SchemaVersion: 2},
SchemaVersion: 2, MediaType: v1.MediaTypeImageManifest,
MediaType: v1.MediaTypeImageManifest, Config: config,
},
Config: config,
} }
type testcase struct { type testcase struct {
@ -189,10 +187,8 @@ func TestVerifyOCIManifestBlobLayerAndConfig(t *testing.T) {
} }
template := ocischema.Manifest{ template := ocischema.Manifest{
Versioned: manifest.Versioned{ Versioned: specs.Versioned{SchemaVersion: 2},
SchemaVersion: 2, MediaType: v1.MediaTypeImageManifest,
MediaType: v1.MediaTypeImageManifest,
},
} }
checkFn := func(m ocischema.Manifest, rerr error) { checkFn := func(m ocischema.Manifest, rerr error) {

View file

@ -7,10 +7,10 @@ import (
"github.com/distribution/distribution/v3" "github.com/distribution/distribution/v3"
"github.com/distribution/distribution/v3/internal/dcontext" "github.com/distribution/distribution/v3/internal/dcontext"
"github.com/distribution/distribution/v3/manifest"
"github.com/distribution/distribution/v3/manifest/schema2" "github.com/distribution/distribution/v3/manifest/schema2"
"github.com/distribution/distribution/v3/registry/storage/driver/inmemory" "github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go"
) )
func TestVerifyManifestForeignLayer(t *testing.T) { func TestVerifyManifestForeignLayer(t *testing.T) {
@ -45,11 +45,9 @@ func TestVerifyManifestForeignLayer(t *testing.T) {
} }
template := schema2.Manifest{ template := schema2.Manifest{
Versioned: manifest.Versioned{ Versioned: specs.Versioned{SchemaVersion: 2},
SchemaVersion: 2, MediaType: schema2.MediaTypeManifest,
MediaType: schema2.MediaTypeManifest, Config: config,
},
Config: config,
} }
type testcase struct { type testcase struct {
@ -176,10 +174,8 @@ func TestVerifyManifestBlobLayerAndConfig(t *testing.T) {
} }
template := schema2.Manifest{ template := schema2.Manifest{
Versioned: manifest.Versioned{ Versioned: specs.Versioned{SchemaVersion: 2},
SchemaVersion: 2, MediaType: schema2.MediaTypeManifest,
MediaType: schema2.MediaTypeManifest,
},
} }
checkFn := func(m schema2.Manifest, rerr error) { checkFn := func(m schema2.Manifest, rerr error) {

View file

@ -6,11 +6,11 @@ import (
"testing" "testing"
"github.com/distribution/distribution/v3" "github.com/distribution/distribution/v3"
"github.com/distribution/distribution/v3/manifest"
"github.com/distribution/distribution/v3/manifest/schema2" "github.com/distribution/distribution/v3/manifest/schema2"
"github.com/distribution/distribution/v3/registry/storage/driver/inmemory" "github.com/distribution/distribution/v3/registry/storage/driver/inmemory"
"github.com/distribution/reference" "github.com/distribution/reference"
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go"
) )
type tagsTestEnv struct { type tagsTestEnv struct {
@ -243,10 +243,8 @@ func TestTagIndexes(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
m := schema2.Manifest{ m := schema2.Manifest{
Versioned: manifest.Versioned{ Versioned: specs.Versioned{SchemaVersion: 2},
SchemaVersion: 2, MediaType: schema2.MediaTypeManifest,
MediaType: schema2.MediaTypeManifest,
},
Config: distribution.Descriptor{ Config: distribution.Descriptor{
Digest: conf.Digest, Digest: conf.Digest,
Size: 1, Size: 1,

View file

@ -1,7 +1,6 @@
package testutil package testutil
import ( import (
"context"
"fmt" "fmt"
"github.com/distribution/distribution/v3" "github.com/distribution/distribution/v3"
@ -51,7 +50,18 @@ func MakeSchema2Manifest(repository distribution.Repository, digests []digest.Di
return nil, fmt.Errorf("unexpected error storing content in blobstore: %v", err) return nil, fmt.Errorf("unexpected error storing content in blobstore: %v", err)
} }
builder := schema2.NewManifestBuilder(d, configJSON) builder := schema2.NewManifestBuilder(d, configJSON)
return makeManifest(ctx, builder, digests) for _, dgst := range digests {
if err := builder.AppendReference(distribution.Descriptor{Digest: dgst}); err != nil {
return nil, fmt.Errorf("unexpected error building schema2 manifest: %v", err)
}
}
mfst, err := builder.Build(ctx)
if err != nil {
return nil, fmt.Errorf("unexpected error generating schema2 manifest: %v", err)
}
return mfst, nil
} }
func MakeOCIManifest(repository distribution.Repository, digests []digest.Digest) (distribution.Manifest, error) { func MakeOCIManifest(repository distribution.Repository, digests []digest.Digest) (distribution.Manifest, error) {
@ -61,19 +71,15 @@ func MakeOCIManifest(repository distribution.Repository, digests []digest.Digest
var configJSON []byte var configJSON []byte
builder := ocischema.NewManifestBuilder(blobStore, configJSON, make(map[string]string)) builder := ocischema.NewManifestBuilder(blobStore, configJSON, make(map[string]string))
return makeManifest(ctx, builder, digests) for _, dgst := range digests {
} if err := builder.AppendReference(distribution.Descriptor{Digest: dgst}); err != nil {
return nil, fmt.Errorf("unexpected error building OCI manifest: %v", err)
func makeManifest(ctx context.Context, builder distribution.ManifestBuilder, digests []digest.Digest) (distribution.Manifest, error) {
for _, digest := range digests {
if err := builder.AppendReference(distribution.Descriptor{Digest: digest}); err != nil {
return nil, fmt.Errorf("unexpected error building manifest: %v", err)
} }
} }
mfst, err := builder.Build(ctx) mfst, err := builder.Build(ctx)
if err != nil { if err != nil {
return nil, fmt.Errorf("unexpected error generating manifest: %v", err) return nil, fmt.Errorf("unexpected error generating OCI manifest: %v", err)
} }
return mfst, nil return mfst, nil

View file

@ -53,4 +53,10 @@ const (
// AnnotationDescription is the annotation key for the human-readable description of the software packaged in the image. // AnnotationDescription is the annotation key for the human-readable description of the software packaged in the image.
AnnotationDescription = "org.opencontainers.image.description" AnnotationDescription = "org.opencontainers.image.description"
// AnnotationBaseImageDigest is the annotation key for the digest of the image's base image.
AnnotationBaseImageDigest = "org.opencontainers.image.base.digest"
// AnnotationBaseImageName is the annotation key for the image reference of the image's base image.
AnnotationBaseImageName = "org.opencontainers.image.base.name"
) )

View file

@ -48,6 +48,17 @@ type ImageConfig struct {
// StopSignal contains the system call signal that will be sent to the container to exit. // StopSignal contains the system call signal that will be sent to the container to exit.
StopSignal string `json:"StopSignal,omitempty"` StopSignal string `json:"StopSignal,omitempty"`
// ArgsEscaped
//
// Deprecated: This field is present only for legacy compatibility with
// Docker and should not be used by new image builders. It is used by Docker
// for Windows images to indicate that the `Entrypoint` or `Cmd` or both,
// contains only a single element array, that is a pre-escaped, and combined
// into a single string `CommandLine`. If `true` the value in `Entrypoint` or
// `Cmd` should be used as-is to avoid double escaping.
// https://github.com/opencontainers/image-spec/pull/892
ArgsEscaped bool `json:"ArgsEscaped,omitempty"`
} }
// RootFS describes a layer content addresses // RootFS describes a layer content addresses
@ -86,11 +97,8 @@ type Image struct {
// Author defines the name and/or email address of the person or entity which created and is responsible for maintaining the image. // Author defines the name and/or email address of the person or entity which created and is responsible for maintaining the image.
Author string `json:"author,omitempty"` Author string `json:"author,omitempty"`
// Architecture is the CPU architecture which the binaries in this image are built to run on. // Platform describes the platform which the image in the manifest runs on.
Architecture string `json:"architecture"` Platform
// OS is the name of the operating system which the image is built to run on.
OS string `json:"os"`
// Config defines the execution parameters which should be used as a base when running a container using the image. // Config defines the execution parameters which should be used as a base when running a container using the image.
Config ImageConfig `json:"config,omitempty"` Config ImageConfig `json:"config,omitempty"`

View file

@ -1,4 +1,4 @@
// Copyright 2016 The Linux Foundation // Copyright 2016-2022 The Linux Foundation
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -21,7 +21,7 @@ import digest "github.com/opencontainers/go-digest"
// when marshalled to JSON. // when marshalled to JSON.
type Descriptor struct { type Descriptor struct {
// MediaType is the media type of the object this schema refers to. // MediaType is the media type of the object this schema refers to.
MediaType string `json:"mediaType,omitempty"` MediaType string `json:"mediaType"`
// Digest is the digest of the targeted content. // Digest is the digest of the targeted content.
Digest digest.Digest `json:"digest"` Digest digest.Digest `json:"digest"`
@ -35,16 +35,24 @@ type Descriptor struct {
// Annotations contains arbitrary metadata relating to the targeted content. // Annotations contains arbitrary metadata relating to the targeted content.
Annotations map[string]string `json:"annotations,omitempty"` Annotations map[string]string `json:"annotations,omitempty"`
// Data is an embedding of the targeted content. This is encoded as a base64
// string when marshalled to JSON (automatically, by encoding/json). If
// present, Data can be used directly to avoid fetching the targeted content.
Data []byte `json:"data,omitempty"`
// Platform describes the platform which the image in the manifest runs on. // Platform describes the platform which the image in the manifest runs on.
// //
// This should only be used when referring to a manifest. // This should only be used when referring to a manifest.
Platform *Platform `json:"platform,omitempty"` Platform *Platform `json:"platform,omitempty"`
// ArtifactType is the IANA media type of this artifact.
ArtifactType string `json:"artifactType,omitempty"`
} }
// Platform describes the platform which the image in the manifest runs on. // Platform describes the platform which the image in the manifest runs on.
type Platform struct { type Platform struct {
// Architecture field specifies the CPU architecture, for example // Architecture field specifies the CPU architecture, for example
// `amd64` or `ppc64`. // `amd64` or `ppc64le`.
Architecture string `json:"architecture"` Architecture string `json:"architecture"`
// OS specifies the operating system, for example `linux` or `windows`. // OS specifies the operating system, for example `linux` or `windows`.
@ -62,3 +70,11 @@ type Platform struct {
// example `v7` to specify ARMv7 when architecture is `arm`. // example `v7` to specify ARMv7 when architecture is `arm`.
Variant string `json:"variant,omitempty"` Variant string `json:"variant,omitempty"`
} }
// DescriptorEmptyJSON is the descriptor of a blob with content of `{}`.
var DescriptorEmptyJSON = Descriptor{
MediaType: MediaTypeEmptyJSON,
Digest: `sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a`,
Size: 2,
Data: []byte(`{}`),
}

View file

@ -21,12 +21,18 @@ import "github.com/opencontainers/image-spec/specs-go"
type Index struct { type Index struct {
specs.Versioned specs.Versioned
// MediaType specificies the type of this document data structure e.g. `application/vnd.oci.image.index.v1+json` // MediaType specifies the type of this document data structure e.g. `application/vnd.oci.image.index.v1+json`
MediaType string `json:"mediaType,omitempty"` MediaType string `json:"mediaType,omitempty"`
// ArtifactType specifies the IANA media type of artifact when the manifest is used for an artifact.
ArtifactType string `json:"artifactType,omitempty"`
// Manifests references platform specific manifests. // Manifests references platform specific manifests.
Manifests []Descriptor `json:"manifests"` Manifests []Descriptor `json:"manifests"`
// 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"`
// Annotations contains arbitrary metadata for the image index. // Annotations contains arbitrary metadata for the image index.
Annotations map[string]string `json:"annotations,omitempty"` Annotations map[string]string `json:"annotations,omitempty"`
} }

View file

@ -15,10 +15,14 @@
package v1 package v1
const ( const (
// ImageLayoutFile is the file name of oci image layout file // ImageLayoutFile is the file name containing ImageLayout in an OCI Image Layout
ImageLayoutFile = "oci-layout" ImageLayoutFile = "oci-layout"
// ImageLayoutVersion is the version of ImageLayout // ImageLayoutVersion is the version of ImageLayout
ImageLayoutVersion = "1.0.0" ImageLayoutVersion = "1.0.0"
// ImageIndexFile is the file name of the entry point for references and descriptors in an OCI Image Layout
ImageIndexFile = "index.json"
// ImageBlobsDir is the directory name containing content addressable blobs in an OCI Image Layout
ImageBlobsDir = "blobs"
) )
// ImageLayout is the structure in the "oci-layout" file, found in the root // ImageLayout is the structure in the "oci-layout" file, found in the root

View file

@ -1,4 +1,4 @@
// Copyright 2016 The Linux Foundation // Copyright 2016-2022 The Linux Foundation
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -20,9 +20,12 @@ import "github.com/opencontainers/image-spec/specs-go"
type Manifest struct { type Manifest struct {
specs.Versioned specs.Versioned
// MediaType specificies the type of this document data structure e.g. `application/vnd.oci.image.manifest.v1+json` // MediaType specifies the type of this document data structure e.g. `application/vnd.oci.image.manifest.v1+json`
MediaType string `json:"mediaType,omitempty"` MediaType string `json:"mediaType,omitempty"`
// 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. // 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. // The referenced configuration object is a JSON blob that the runtime uses to set up the container.
Config Descriptor `json:"config"` Config Descriptor `json:"config"`
@ -30,6 +33,9 @@ type Manifest struct {
// Layers is an indexed list of layers referenced by the manifest. // Layers is an indexed list of layers referenced by the manifest.
Layers []Descriptor `json:"layers"` 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"`
// Annotations contains arbitrary metadata for the image manifest. // Annotations contains arbitrary metadata for the image manifest.
Annotations map[string]string `json:"annotations,omitempty"` Annotations map[string]string `json:"annotations,omitempty"`
} }

View file

@ -21,12 +21,20 @@ const (
// MediaTypeLayoutHeader specifies the media type for the oci-layout. // MediaTypeLayoutHeader specifies the media type for the oci-layout.
MediaTypeLayoutHeader = "application/vnd.oci.layout.header.v1+json" MediaTypeLayoutHeader = "application/vnd.oci.layout.header.v1+json"
// MediaTypeImageManifest specifies the media type for an image manifest.
MediaTypeImageManifest = "application/vnd.oci.image.manifest.v1+json"
// MediaTypeImageIndex specifies the media type for an image index. // MediaTypeImageIndex specifies the media type for an image index.
MediaTypeImageIndex = "application/vnd.oci.image.index.v1+json" MediaTypeImageIndex = "application/vnd.oci.image.index.v1+json"
// MediaTypeImageManifest specifies the media type for an image manifest.
MediaTypeImageManifest = "application/vnd.oci.image.manifest.v1+json"
// MediaTypeImageConfig specifies the media type for the image configuration.
MediaTypeImageConfig = "application/vnd.oci.image.config.v1+json"
// MediaTypeEmptyJSON specifies the media type for an unused blob containing the value "{}".
MediaTypeEmptyJSON = "application/vnd.oci.empty.v1+json"
)
const (
// MediaTypeImageLayer is the media type used for layers referenced by the manifest. // MediaTypeImageLayer is the media type used for layers referenced by the manifest.
MediaTypeImageLayer = "application/vnd.oci.image.layer.v1.tar" MediaTypeImageLayer = "application/vnd.oci.image.layer.v1.tar"
@ -34,15 +42,44 @@ const (
// referenced by the manifest. // referenced by the manifest.
MediaTypeImageLayerGzip = "application/vnd.oci.image.layer.v1.tar+gzip" MediaTypeImageLayerGzip = "application/vnd.oci.image.layer.v1.tar+gzip"
// MediaTypeImageLayerZstd is the media type used for zstd compressed
// layers referenced by the manifest.
MediaTypeImageLayerZstd = "application/vnd.oci.image.layer.v1.tar+zstd"
)
// Non-distributable layer media-types.
//
// Deprecated: Non-distributable layers are deprecated, and not recommended
// for future use. Implementations SHOULD NOT produce new non-distributable
// layers.
// https://github.com/opencontainers/image-spec/pull/965
const (
// MediaTypeImageLayerNonDistributable is the media type for layers referenced by // MediaTypeImageLayerNonDistributable is the media type for layers referenced by
// the manifest but with distribution restrictions. // the manifest but with distribution restrictions.
//
// Deprecated: Non-distributable layers are deprecated, and not recommended
// for future use. Implementations SHOULD NOT produce new non-distributable
// layers.
// https://github.com/opencontainers/image-spec/pull/965
MediaTypeImageLayerNonDistributable = "application/vnd.oci.image.layer.nondistributable.v1.tar" MediaTypeImageLayerNonDistributable = "application/vnd.oci.image.layer.nondistributable.v1.tar"
// MediaTypeImageLayerNonDistributableGzip is the media type for // MediaTypeImageLayerNonDistributableGzip is the media type for
// gzipped layers referenced by the manifest but with distribution // gzipped layers referenced by the manifest but with distribution
// restrictions. // restrictions.
//
// Deprecated: Non-distributable layers are deprecated, and not recommended
// for future use. Implementations SHOULD NOT produce new non-distributable
// layers.
// https://github.com/opencontainers/image-spec/pull/965
MediaTypeImageLayerNonDistributableGzip = "application/vnd.oci.image.layer.nondistributable.v1.tar+gzip" MediaTypeImageLayerNonDistributableGzip = "application/vnd.oci.image.layer.nondistributable.v1.tar+gzip"
// MediaTypeImageConfig specifies the media type for the image configuration. // MediaTypeImageLayerNonDistributableZstd is the media type for zstd
MediaTypeImageConfig = "application/vnd.oci.image.config.v1+json" // compressed layers referenced by the manifest but with distribution
// restrictions.
//
// Deprecated: Non-distributable layers are deprecated, and not recommended
// for future use. Implementations SHOULD NOT produce new non-distributable
// layers.
// https://github.com/opencontainers/image-spec/pull/965
MediaTypeImageLayerNonDistributableZstd = "application/vnd.oci.image.layer.nondistributable.v1.tar+zstd"
) )

View file

@ -20,9 +20,9 @@ const (
// VersionMajor is for an API incompatible changes // VersionMajor is for an API incompatible changes
VersionMajor = 1 VersionMajor = 1
// VersionMinor is for functionality in a backwards-compatible manner // VersionMinor is for functionality in a backwards-compatible manner
VersionMinor = 0 VersionMinor = 1
// VersionPatch is for backwards-compatible bug fixes // VersionPatch is for backwards-compatible bug fixes
VersionPatch = 2 VersionPatch = 0
// VersionDev indicates development branch. Releases will be empty string. // VersionDev indicates development branch. Releases will be empty string.
VersionDev = "" VersionDev = ""

4
vendor/modules.txt vendored
View file

@ -295,8 +295,8 @@ github.com/mitchellh/mapstructure
## explicit; go 1.13 ## explicit; go 1.13
github.com/opencontainers/go-digest github.com/opencontainers/go-digest
github.com/opencontainers/go-digest/digestset github.com/opencontainers/go-digest/digestset
# github.com/opencontainers/image-spec v1.0.2 # github.com/opencontainers/image-spec v1.1.0
## explicit ## explicit; go 1.18
github.com/opencontainers/image-spec/specs-go github.com/opencontainers/image-spec/specs-go
github.com/opencontainers/image-spec/specs-go/v1 github.com/opencontainers/image-spec/specs-go/v1
# github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c # github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c