From 053088a8c2732c786a41be1d5ca24ce403c09600 Mon Sep 17 00:00:00 2001 From: Aviral Takkar Date: Thu, 19 Aug 2021 00:11:39 -0700 Subject: [PATCH 1/9] add docker image release to ghcr Signed-off-by: Aviral Takkar --- .github/workflows/oras-release.yml | 59 ++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 .github/workflows/oras-release.yml diff --git a/.github/workflows/oras-release.yml b/.github/workflows/oras-release.yml new file mode 100644 index 00000000000..bf95829d7e1 --- /dev/null +++ b/.github/workflows/oras-release.yml @@ -0,0 +1,59 @@ +name: Release oras-project/registry container image + +on: + push: + tags: + - "v[0-9]+.[0-9]+.[0-9]+-alpha" + +jobs: + publish: + name: Build and publish container image + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + env: + REGISTRY: ghcr.io + REPOSITORY: ${{ format('{0}/registry', github.repository_owner) }} + DOCKER_BUILDTAGS: "include_oss include_gcs" + CGO_ENABLED: 1 + GO111MODULE: "auto" + GOPATH: ${{ github.workspace }} + GOOS: linux + COMMIT_RANGE: ${{ github.event_name == 'pull_request' && format('{0}..{1}',github.event.pull_request.base.sha, github.event.pull_request.head.sha) || format('{0}..{1}', github.event.before, github.event.after) }} + + steps: + - name: Get git tag + id: get_git_tag + run: echo ::set-output name=git_tag::${GITHUB_REF#refs/tags/} + + - name: Check out source code + if: ${{ success() }} + uses: actions/checkout@v2 + with: + ref: ${{ steps.get_git_tag.outputs.git_tag }} + + - name: Set docker image tag + env: + GIT_TAG: ${{ steps.get_git_tag.outputs.git_tag }} + id: get_image_tag + run: echo ::set-output name=docker_tag::${GIT_TAG} + + - name: Login to GitHub Container Registry + uses: docker/login-action@v1 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + if: ${{ success() }} + uses: docker/build-push-action@v2 + with: + context: . + file: ./Dockerfile + platforms: linux/amd64 + push: true + tags: | + ${{ env.REGISTRY }}/${{ env.REPOSITORY }}:${{ steps.get_image_tag.outputs.docker_tag }} + ${{ env.REGISTRY }}/${{ env.REPOSITORY }}:latest From 21196a76eda2a85e1dc83fc92670958de3d9c666 Mon Sep 17 00:00:00 2001 From: Aviral Takkar Date: Wed, 9 Sep 2020 05:58:02 -0700 Subject: [PATCH 2/9] add referrers API, ORAS artifact manifest support Signed-off-by: Aviral Takkar --- docs/referrers.md | 235 ++++++++++++++++++ go.mod | 3 + go.sum | 2 + manifest/orasartifact/manifest.go | 111 +++++++++ manifests.go | 21 ++ registry/api/oras/artifacts/v1/descriptors.go | 128 ++++++++++ registry/api/oras/artifacts/v1/doc.go | 3 + registry/api/oras/artifacts/v1/routes.go | 23 ++ registry/api/v2/errors.go | 2 +- registry/client/repository.go | 4 + registry/handlers/app.go | 5 + registry/handlers/referrers.go | 88 +++++++ registry/proxy/proxymanifeststore.go | 4 + registry/proxy/proxymanifeststore_test.go | 5 + registry/storage/manifeststore.go | 61 +++++ .../storage/orasartifactmanifesthandler.go | 123 +++++++++ registry/storage/paths.go | 41 +++ registry/storage/registry.go | 46 ++++ .../oras-project/artifacts-spec/LICENSE | 201 +++++++++++++++ .../artifacts-spec/specs-go/v1/descriptor.go | 40 +++ .../artifacts-spec/specs-go/v1/manifest.go | 35 +++ .../artifacts-spec/specs-go/v1/mediatype.go | 20 ++ vendor/modules.txt | 4 + 23 files changed, 1204 insertions(+), 1 deletion(-) create mode 100644 docs/referrers.md create mode 100644 manifest/orasartifact/manifest.go create mode 100644 registry/api/oras/artifacts/v1/descriptors.go create mode 100644 registry/api/oras/artifacts/v1/doc.go create mode 100644 registry/api/oras/artifacts/v1/routes.go create mode 100644 registry/handlers/referrers.go create mode 100644 registry/storage/orasartifactmanifesthandler.go create mode 100644 vendor/github.com/oras-project/artifacts-spec/LICENSE create mode 100644 vendor/github.com/oras-project/artifacts-spec/specs-go/v1/descriptor.go create mode 100644 vendor/github.com/oras-project/artifacts-spec/specs-go/v1/manifest.go create mode 100644 vendor/github.com/oras-project/artifacts-spec/specs-go/v1/mediatype.go diff --git a/docs/referrers.md b/docs/referrers.md new file mode 100644 index 00000000000..bdc4b42934f --- /dev/null +++ b/docs/referrers.md @@ -0,0 +1,235 @@ +[[__TOC__]] + +# ORAS Artifacts Distribution + +This document describes an experimental prototype that implements the +[ORAS Artifact Manifest](https://github.com/oras-project/artifacts-spec) spec. + +## Usage - Push, Discover, Pull + +The following steps illustrate how ORAS artifacts can be stored and retrieved from a registry. +The artifact in this example is a Notary V2 [signature](https://github.com/notaryproject/nv2/tree/prototype-2/docs/nv2). + +### Prerequisites + +- Local registry prototype instance +- [docker-generate](https://github.com/shizhMSFT/docker-generate) +- [nv2](https://github.com/notaryproject/nv2) +- `curl` +- `jq` + +### Push an image to your registry + +```shell +# Initialize local registry variables +regIp="127.0.0.1" && \ + regPort="5000" && \ + registry="$regIp:$regPort" && \ + repo="busybox" && \ + tag="latest" && \ + image="$repo:$tag" && \ + reference="$registry/$image" + +# Pull an image from docker hub and push to local registry +docker pull $image && \ + docker tag $image $reference && \ + docker push $reference +``` + +### Generate image manifest and sign it + +```shell +# Generate self-signed certificates +openssl req \ + -x509 \ + -sha256 \ + -nodes \ + -newkey rsa:2048 \ + -days 365 \ + -subj "/CN=$regIp/O=example inc/C=IN/ST=Haryana/L=Gurgaon" \ + -addext "subjectAltName=IP:$regIp" \ + -keyout example.key \ + -out example.crt + +# Generate image manifest +manifestFile="manifest-to-sign.json" && \ + docker generate manifest $image > $manifestFile + +# Sign manifest +signatureFile="manifest-signature.jwt" && \ + nv2 sign --method x509 \ + -k example.key \ + -c example.crt \ + -r $reference \ + -o $signatureFile \ + file:$manifestFile +``` + +### Obtain manifest and signature digests + +```shell +manifestDigest="sha256:$(sha256sum $manifestFile | cut -d " " -f 1)" && \ + signatureDigest="sha256:$(sha256sum $signatureFile | cut -d " " -f 1)" +``` + +### Create an Artifact file referencing the manifest that was signed and its signature as blob + +```shell +artifactFile="artifact.json" && \ + artifactMediaType="application/vnd.cncf.oras.artifact.manifest.v1+json" && \ + artifactType="application/vnd.cncf.notary.v2" && \ + signatureMediaType="application/vnd.cncf.notary.signature.v2+jwt" && \ + signatureFileSize=`wc -c < $signatureFile` && \ + manifestMediaType="$(cat $manifestFile | jq -r '.mediaType')" && \ + manifestFileSize=`wc -c < $manifestFile` + +cat < $artifactFile +{ + "mediaType": "$artifactMediaType", + "artifactType": "$artifactType", + "blobs": [ + { + "mediaType": "$signatureMediaType", + "digest": "$signatureDigest", + "size": $signatureFileSize + } + ], + "subjectManifest": { + "mediaType": "$manifestMediaType", + "digest": "$manifestDigest", + "size": $manifestFileSize + } +} +EOF +``` + +### Obtain artifact digest + +```shell +artifactDigest="sha256:$(sha256sum $artifactFile | cut -d " " -f 1)" +``` + +### Push signature and artifact + +```shell +# Initiate blob upload and obtain PUT location +blobPutLocation=`curl -I -X POST -s http://$registry/v2/$repo/blobs/uploads/ | grep "Location: " | sed -e "s/Location: //;s/$/\&digest=$signatureDigest/;s/\r//"` + +# Push signature blob +curl -X PUT -H "Content-Type: application/octet-stream" --data-binary @"$signatureFile" $blobPutLocation + +# Push artifact +curl -X PUT --data-binary @"$artifactFile" -H "Content-Type: $artifactMediaType" "http://$registry/v2/$repo/manifests/$artifactDigest" +``` + +### List referrers + +```shell +# Retrieve referrers +curl -s "http://$registry/oras/artifacts/v1/$repo/manifests/$manifestDigest/referrers?artifactType=$artifactType" | jq +``` + +### Verify signature + +```shell +# Retrieve signature +artifactDigest=`curl -s "http://$registry/oras/artifacts/v1/$repo/manifests/$manifestDigest/referrers?artifactType=$artifactType" | jq -r '.references[0].digest'` && \ + signatureDigest=`curl -s "http://$registry/oras/artifacts/v1/$repo/manifests/$artifactDigest" | jq -r '.blobs[0].digest'` && \ + retrievedSignatureFile="retrieved-signature.json" && \ + curl -s http://$registry/v2/$repo/blobs/$signatureDigest > $retrievedSignatureFile + +# Verify signature +nv2 verify \ + -f $retrievedSignatureFile \ + -c example.crt \ + file:$manifestFile +``` + +## Implementation + +To power the [/referrers](https://github.com/oras-project/artifacts-spec/blob/main/manifest-referrers-api.md) API, the +referrers of a manifest are indexed in the repository store. The following example illustrates the creation of this +index. + +The `nginx:v1` image is already persisted: + +- repository: `nginx` +- digest: `sha256:111ma2d22ae5ef400769fa51c84717264cd1520ac8d93dc071374c1be49a111m` +- tag: `v1.0` + +The repository store layout is represented as: + +```bash + +└── v2 + └── repositories + └── nginx + └── _manifests + └── revisions + └── sha256 + └── 111ma2d22ae5ef400769fa51c84717264cd1520ac8d93dc071374c1be49a111m + └── link +``` + +Push a signature as blob and an ORAS Artifact that contains a blobs property referencing the signature, with the +following properties: + +- digest: `sha256:222ibbf80b44ce6be8234e6ff90a1ac34acbeb826903b02cfa0da11c82cb222i` +- `subjectManifest` digest: `sha256:111ma2d22ae5ef400769fa51c84717264cd1520ac8d93dc071374c1be49a111m` +- `artifactType`: `application/vnd.example.artifact` + +On `PUT`, the artifact appears as a manifest revision. Additionally, an index entry is created under +the subject to facilitate a lookup to the referrer. The index path where the entry is added is +`/ref/`, as shown below. + +``` + +└── v2 + └── repositories + └── nginx + └── _manifests + └── revisions + └── sha256 + ├── 111ma2d22ae5ef400769fa51c84717264cd1520ac8d93dc071374c1be49a111m + │ ├── link + │ └── ref + │ └── digest(application/vnd.example.artifact) + │ └── sha256 + │ └── 222ibbf80b44ce6be8234e6ff90a1ac34acbeb826903b02cfa0da11c82cb222i + │ └── link + └── 222ibbf80b44ce6be8234e6ff90a1ac34acbeb826903b02cfa0da11c82cb222i + └── link +``` + +Push another ORAS artifact with the following properties: + +- digest: `sha256:333ic0c33ebc4a74a0a554c86ac2b28ddf3454a5ad9cf90ea8cea9f9e75c333i` +- `subjectManifest` digest: `sha256:111ma2d22ae5ef400769fa51c84717264cd1520ac8d93dc071374c1be49a111m` +- `artifactType`: `application/vnd.another.example.artifact` + +This results in an addition to the index as shown below. + +``` + +└── v2 + └── repositories + └── nginx + └── _manifests + └── revisions + └── sha256 + ├── 111ma2d22ae5ef400769fa51c84717264cd1520ac8d93dc071374c1be49a111m + │ ├── link + │ └── ref + │ ├── digest(application/vnd.example.artifact) + │ │ └── sha256 + │ │ └── 222ibbf80b44ce6be8234e6ff90a1ac34acbeb826903b02cfa0da11c82cb222i + │ │ └── link + │ └── digest(application/vnd.another.example.artifact) + │ └── sha256 + │ └── 333ic0c33ebc4a74a0a554c86ac2b28ddf3454a5ad9cf90ea8cea9f9e75c333i + │ └── link + ├── 222ibbf80b44ce6be8234e6ff90a1ac34acbeb826903b02cfa0da11c82cb222i + │ └── link + └── 333ic0c33ebc4a74a0a554c86ac2b28ddf3454a5ad9cf90ea8cea9f9e75c333i + └── link +``` diff --git a/go.mod b/go.mod index 89d044f1e56..dc5ea03cc81 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,8 @@ module github.com/distribution/distribution/v3 go 1.15 +replace github.com/oras-project/artifacts-spec => github.com/aviral26/artifacts-spec v0.0.2 + require ( github.com/Azure/azure-sdk-for-go v16.2.1+incompatible github.com/Azure/go-autorest v10.8.1+incompatible // indirect @@ -30,6 +32,7 @@ require ( github.com/ncw/swift v1.0.47 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.0.1 + github.com/oras-project/artifacts-spec v0.0.0-00010101000000-000000000000 github.com/satori/go.uuid v1.2.0 // indirect github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v0.0.3 diff --git a/go.sum b/go.sum index 50b314e7a3f..f2a1020226c 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,8 @@ github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/O github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/aviral26/artifacts-spec v0.0.2 h1:uU5MIRT68TP4l2Elri9WoweDc9f8iSwe0G0ghDCxolQ= +github.com/aviral26/artifacts-spec v0.0.2/go.mod h1:Xch2aLzSwtkhbFFN6LUzTfLtukYvMMdXJ4oZ8O7BOdc= github.com/aws/aws-sdk-go v1.34.9 h1:cUGBW9CVdi0mS7K1hDzxIqTpfeWhpoQiguq81M1tjK0= github.com/aws/aws-sdk-go v1.34.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= diff --git a/manifest/orasartifact/manifest.go b/manifest/orasartifact/manifest.go new file mode 100644 index 00000000000..bdb080c1050 --- /dev/null +++ b/manifest/orasartifact/manifest.go @@ -0,0 +1,111 @@ +package orasartifact + +import ( + "encoding/json" + "errors" + "fmt" + + "github.com/distribution/distribution/v3" + "github.com/opencontainers/go-digest" + v1 "github.com/oras-project/artifacts-spec/specs-go/v1" +) + +func init() { + unmarshalFunc := func(b []byte) (distribution.Manifest, distribution.Descriptor, error) { + d := new(DeserializedManifest) + err := d.UnmarshalJSON(b) + if err != nil { + return nil, distribution.Descriptor{}, err + } + + if d.inner.MediaType != v1.MediaTypeArtifactManifest { + err = fmt.Errorf("if present, mediaType in ORAS artifact manifest should be '%s' not '%s'", + v1.MediaTypeArtifactManifest, d.inner.MediaType) + + return nil, distribution.Descriptor{}, err + } + + dgst := digest.FromBytes(b) + return d, distribution.Descriptor{Digest: dgst, Size: int64(len(b)), MediaType: v1.MediaTypeArtifactManifest}, err + } + err := distribution.RegisterManifestSchema(v1.MediaTypeArtifactManifest, unmarshalFunc) + if err != nil { + panic(fmt.Sprintf("Unable to register ORAS artifact manifest: %s", err)) + } +} + +// Manifest describes ORAS artifact manifests. +type Manifest struct { + inner v1.Manifest +} + +// ArtifactType returns the artifactType of this ORAS artifact. +func (a Manifest) ArtifactType() string { + return a.inner.ArtifactType +} + +// References returns the distribution descriptors for the referenced blobs. +func (a Manifest) References() []distribution.Descriptor { + blobs := make([]distribution.Descriptor, len(a.inner.Blobs)) + for i := range a.inner.Blobs { + blobs[i] = distribution.Descriptor{ + MediaType: a.inner.Blobs[i].MediaType, + Digest: a.inner.Blobs[i].Digest, + Size: a.inner.Blobs[i].Size, + } + } + return blobs +} + +// SubjectManifest returns the the subject manifest this artifact references. +func (a Manifest) SubjectManifest() distribution.Descriptor { + return distribution.Descriptor{ + MediaType: a.inner.SubjectManifest.MediaType, + Digest: a.inner.SubjectManifest.Digest, + Size: a.inner.SubjectManifest.Size, + } +} + +// DeserializedManifest wraps Manifest with a copy of the original JSON data. +type DeserializedManifest struct { + Manifest + + // raw is the raw byte representation of the ORAS artifact. + raw []byte +} + +// UnmarshalJSON populates a new Manifest struct from JSON data. +func (d *DeserializedManifest) UnmarshalJSON(b []byte) error { + d.raw = make([]byte, len(b)) + copy(d.raw, b) + + var man v1.Manifest + if err := json.Unmarshal(d.raw, &man); err != nil { + return err + } + d.inner = man + + return nil +} + +// MarshalJSON returns the raw content. +func (d *DeserializedManifest) MarshalJSON() ([]byte, error) { + if len(d.raw) > 0 { + return d.raw, nil + } + + return nil, errors.New("JSON representation not initialized in DeserializedManifest") +} + +// Payload returns the raw content of the Artifact. The contents can be +// used to calculate the content identifier. +func (d DeserializedManifest) Payload() (string, []byte, error) { + var mediaType string + if d.inner.MediaType == "" { + mediaType = v1.MediaTypeArtifactManifest + } else { + mediaType = d.inner.MediaType + } + + return mediaType, d.raw, nil +} diff --git a/manifests.go b/manifests.go index 8f84a220a97..064f8ea3239 100644 --- a/manifests.go +++ b/manifests.go @@ -47,6 +47,23 @@ type ManifestBuilder interface { AppendReference(dependency Describable) error } +// ArtifactDescriptor describes targeted reference type content. +type ArtifactDescriptor struct { + // MediaType describe the type of the content. All text based formats are + // encoded as utf-8. + MediaType string `json:"mediaType,omitempty"` + + // Size in bytes of content. + Size int64 `json:"size,omitempty"` + + // Digest uniquely identifies the content. A byte stream can be verified + // against this digest. + Digest string `json:"digest,omitempty"` + + // ArtifactType specifies the artifact type of the content. + ArtifactType string `json:"artifactType,omitempty"` +} + // ManifestService describes operations on image manifests. type ManifestService interface { // Exists returns true if the manifest exists. @@ -61,6 +78,10 @@ type ManifestService interface { // Delete removes the manifest specified by the given digest. Deleting // a manifest that doesn't exist will return ErrManifestNotFound Delete(ctx context.Context, dgst digest.Digest) error + + // Referrers returns a collection of manifests which reference the given manifest, + // filtered by artifactType. + Referrers(ctx context.Context, dgst digest.Digest, artifactType string) ([]ArtifactDescriptor, error) } // ManifestEnumerator enables iterating over manifests diff --git a/registry/api/oras/artifacts/v1/descriptors.go b/registry/api/oras/artifacts/v1/descriptors.go new file mode 100644 index 00000000000..2f5841fef54 --- /dev/null +++ b/registry/api/oras/artifacts/v1/descriptors.go @@ -0,0 +1,128 @@ +package v1 + +import ( + "net/http" + + "github.com/distribution/distribution/v3/reference" + v2 "github.com/distribution/distribution/v3/registry/api/v2" +) + +var ( + nameParameterDescriptor = v2.ParameterDescriptor{ + Name: "name", + Type: "string", + Format: reference.NameRegexp.String(), + Required: true, + Description: `Name of the target repository.`, + } + + digestParameterDescriptor = v2.ParameterDescriptor{ + Name: "digest", + Type: "string", + Format: reference.DigestRegexp.String(), + Required: true, + Description: `Digest of the target manifest.`, + } + + hostHeader = v2.ParameterDescriptor{ + Name: "Host", + Type: "string", + Description: "Standard HTTP Host Header. Should be set to the registry host.", + Format: "", + Examples: []string{"registry-1.docker.io"}, + } + + authHeader = v2.ParameterDescriptor{ + Name: "Authorization", + Type: "string", + Description: "An RFC7235 compliant authorization header.", + Format: " ", + Examples: []string{"Bearer dGhpcyBpcyBhIGZha2UgYmVhcmVyIHRva2VuIQ=="}, + } +) + +var routeDescriptors = []v2.RouteDescriptor{ + + { + Name: RouteNameReferrers, + Path: "/oras/artifacts/v1/{name:" + reference.NameRegexp.String() + "}/manifests/{digest:" + reference.DigestRegexp.String() + "}/referrers", + Entity: "Referrers", + Description: "Retrieve information about artifacts that reference this manifest.", + Methods: []v2.MethodDescriptor{ + { + Method: "GET", + Description: "Fetch a list of referrers.", + Requests: []v2.RequestDescriptor{ + { + Name: "Referrers", + Description: "Return a list of all referrers of the given manifest filtered by the given artifact type.", + Headers: []v2.ParameterDescriptor{ + hostHeader, + authHeader, + }, + PathParameters: []v2.ParameterDescriptor{ + nameParameterDescriptor, + digestParameterDescriptor, + }, + QueryParameters: []v2.ParameterDescriptor{ + { + Name: "artifactType", + Type: "query", + Format: "", + Description: `Artifact type of the requested referrers.`, + }, + { + Name: "n", + Type: "query", + Format: "", + Description: `Pagination parameter indicating the requested number of results`, + }, + }, + Successes: []v2.ResponseDescriptor{ + { + StatusCode: http.StatusOK, + Description: "A list of referrers of the given manifest filtered by the given artifact type.", + Headers: []v2.ParameterDescriptor{ + { + Name: "Content-Length", + Type: "integer", + Description: "Length of the JSON response body.", + Format: "", + }, + { + Name: "Content-Type", + Type: "string", + Description: "Type of content in the response body.", + Format: "", + }, + { + Name: "Link", + Type: "string", + Description: "RFC5988 Link header with next relationship. Used for pagination.", + Format: "", + }, + }, + Body: v2.BodyDescriptor{ + ContentType: "application/json", + Format: ` +{ + "references": [ + { + "digest": "", + "mediaType": "", + "artifactType": "", + "size": + }, + ... + ] +} + `, + }, + }, + }, + }, + }, + }, + }, + }, +} diff --git a/registry/api/oras/artifacts/v1/doc.go b/registry/api/oras/artifacts/v1/doc.go new file mode 100644 index 00000000000..da4d2a4ab8b --- /dev/null +++ b/registry/api/oras/artifacts/v1/doc.go @@ -0,0 +1,3 @@ +// Package v1 describes routes, urls and the error codes used in ORAS +// Artifacts V1 API. +package v1 diff --git a/registry/api/oras/artifacts/v1/routes.go b/registry/api/oras/artifacts/v1/routes.go new file mode 100644 index 00000000000..3922876151d --- /dev/null +++ b/registry/api/oras/artifacts/v1/routes.go @@ -0,0 +1,23 @@ +package v1 + +import "github.com/gorilla/mux" + +// The following are definitions of the name under which all V1 routes are +// registered. These symbols can be used to look up a route based on the name. +const ( + RouteNameReferrers = "referrers" +) + +// RouterWithPrefix builds a gorilla router with a configured prefix +// on all routes. +func AddPaths(router *mux.Router) *mux.Router { + if router == nil { + return nil + } + + for _, descriptor := range routeDescriptors { + router.Path(descriptor.Path).Name(descriptor.Name) + } + + return router +} diff --git a/registry/api/v2/errors.go b/registry/api/v2/errors.go index 23a87f8c0b5..4c49daa4bae 100644 --- a/registry/api/v2/errors.go +++ b/registry/api/v2/errors.go @@ -109,7 +109,7 @@ var ( ErrorCodeManifestBlobUnknown = errcode.Register(errGroup, errcode.ErrorDescriptor{ Value: "MANIFEST_BLOB_UNKNOWN", Message: "blob unknown to registry", - Description: `This error may be returned when a manifest blob is + Description: `This error may be returned when a manifest blob is unknown to the registry.`, HTTPStatusCode: http.StatusBadRequest, }) diff --git a/registry/client/repository.go b/registry/client/repository.go index a3379c0a06c..4bcb518a2e6 100644 --- a/registry/client/repository.go +++ b/registry/client/repository.go @@ -397,6 +397,10 @@ type manifests struct { etags map[string]string } +func (ms *manifests) Referrers(_ context.Context, _ digest.Digest, _ string) ([]distribution.ArtifactDescriptor, error) { + return nil, fmt.Errorf("not implemented") +} + func (ms *manifests) Exists(ctx context.Context, dgst digest.Digest) (bool, error) { ref, err := reference.WithDigest(ms.name, dgst) if err != nil { diff --git a/registry/handlers/app.go b/registry/handlers/app.go index 212c79a709f..b1f85a9618d 100644 --- a/registry/handlers/app.go +++ b/registry/handlers/app.go @@ -24,6 +24,7 @@ import ( "github.com/distribution/distribution/v3/notifications" "github.com/distribution/distribution/v3/reference" "github.com/distribution/distribution/v3/registry/api/errcode" + v1 "github.com/distribution/distribution/v3/registry/api/oras/artifacts/v1" v2 "github.com/distribution/distribution/v3/registry/api/v2" "github.com/distribution/distribution/v3/registry/auth" registrymiddleware "github.com/distribution/distribution/v3/registry/middleware/registry" @@ -111,6 +112,10 @@ func NewApp(ctx context.Context, config *configuration.Configuration) *App { app.register(v2.RouteNameBlobUpload, blobUploadDispatcher) app.register(v2.RouteNameBlobUploadChunk, blobUploadDispatcher) + // Register ORAS artifact routes. + app.router = v1.AddPaths(app.router) + app.register(v1.RouteNameReferrers, referrersDispatcher) + // override the storage driver's UA string for registry outbound HTTP requests storageParams := config.Storage.Parameters() if storageParams == nil { diff --git a/registry/handlers/referrers.go b/registry/handlers/referrers.go new file mode 100644 index 00000000000..1712668e2f7 --- /dev/null +++ b/registry/handlers/referrers.go @@ -0,0 +1,88 @@ +package handlers + +import ( + "encoding/json" + "net/http" + + "github.com/distribution/distribution/v3" + "github.com/distribution/distribution/v3/registry/api/errcode" + v2 "github.com/distribution/distribution/v3/registry/api/v2" + dcontext "github.com/distribution/distribution/v3/context" + "github.com/gorilla/handlers" + "github.com/opencontainers/go-digest" +) + +// referrersDispatcher takes the request context and builds the +// appropriate handler for handling manifest referrer requests. +func referrersDispatcher(ctx *Context, r *http.Request) http.Handler { + handler := &referrersHandler{ + Context: ctx, + } + handler.Digest, _ = getDigest(ctx) + + mhandler := handlers.MethodHandler{ + "GET": http.HandlerFunc(handler.Get), + } + + return mhandler +} + +// referrersResponse describes the response body of the referrers API. +type referrersResponse struct { + Referrers []distribution.ArtifactDescriptor `json:"references"` +} + +// referrersHandler handles http operations on manifest referrers. +type referrersHandler struct { + *Context + + // Digest is the target manifest's digest. + Digest digest.Digest +} + +// Get gets the list of artifacts that reference the given manifest filtered by the artifact type +// specified in the request. +func (h *referrersHandler) Get(w http.ResponseWriter, r *http.Request) { + dcontext.GetLogger(h).Debug("Get") + + // This can be empty + artifactType := r.FormValue("artifactType") + + if h.Digest == "" { + h.Errors = append(h.Errors, v2.ErrorCodeManifestUnknown.WithDetail("digest not specified")) + return + } + + ms, err := h.Repository.Manifests(h.Context) + if err != nil { + h.Errors = append(h.Errors, err) + return + } + + referrers, err := ms.Referrers(h.Context, h.Digest, artifactType) + if err != nil { + if _, ok := err.(distribution.ErrManifestUnknownRevision); ok { + h.Errors = append(h.Errors, v2.ErrorCodeManifestUnknown.WithDetail(err)) + } else { + h.Errors = append(h.Errors, errcode.ErrorCodeUnknown.WithDetail(err)) + } + return + } + + if referrers == nil { + referrers = []distribution.ArtifactDescriptor{} + } + + response := referrersResponse{} + + for _, referrer := range referrers { + response.Referrers = append(response.Referrers, referrer) + } + + w.Header().Set("Content-Type", "application/json") + enc := json.NewEncoder(w) + if err = enc.Encode(response); err != nil { + h.Errors = append(h.Errors, errcode.ErrorCodeUnknown.WithDetail(err)) + return + } +} diff --git a/registry/proxy/proxymanifeststore.go b/registry/proxy/proxymanifeststore.go index 0b29386a758..e9d52e9d1df 100644 --- a/registry/proxy/proxymanifeststore.go +++ b/registry/proxy/proxymanifeststore.go @@ -25,6 +25,10 @@ type proxyManifestStore struct { var _ distribution.ManifestService = &proxyManifestStore{} +func (pms proxyManifestStore) Referrers(_ context.Context, _ digest.Digest, _ string) ([]distribution.ArtifactDescriptor, error) { + return nil, distribution.ErrUnsupported +} + func (pms proxyManifestStore) Exists(ctx context.Context, dgst digest.Digest) (bool, error) { exists, err := pms.localManifests.Exists(ctx, dgst) if err != nil { diff --git a/registry/proxy/proxymanifeststore_test.go b/registry/proxy/proxymanifeststore_test.go index 8967e992c57..b0214071ec3 100644 --- a/registry/proxy/proxymanifeststore_test.go +++ b/registry/proxy/proxymanifeststore_test.go @@ -61,6 +61,11 @@ func (sm statsManifest) Put(ctx context.Context, manifest distribution.Manifest, return sm.manifests.Put(ctx, manifest) } +func (sm statsManifest) Referrers(ctx context.Context, dgst digest.Digest, referrerType string) ([]distribution.ArtifactDescriptor, error) { + sm.stats["referrers"]++ + return sm.Referrers(ctx, dgst, referrerType) +} + type mockChallenger struct { sync.Mutex count int diff --git a/registry/storage/manifeststore.go b/registry/storage/manifeststore.go index 0daa92dfe91..4b7a1aad1ed 100644 --- a/registry/storage/manifeststore.go +++ b/registry/storage/manifeststore.go @@ -10,12 +10,20 @@ import ( "github.com/distribution/distribution/v3/manifest" "github.com/distribution/distribution/v3/manifest/manifestlist" "github.com/distribution/distribution/v3/manifest/ocischema" + "github.com/distribution/distribution/v3/manifest/orasartifact" "github.com/distribution/distribution/v3/manifest/schema1" "github.com/distribution/distribution/v3/manifest/schema2" + "github.com/distribution/distribution/v3/registry/storage/driver" "github.com/opencontainers/go-digest" v1 "github.com/opencontainers/image-spec/specs-go/v1" + orasartifactv1 "github.com/oras-project/artifacts-spec/specs-go/v1" ) +// referrersStoreFunc describes a function that returns the referrers store +// for the given manifest of the given artifactType. +// A referrers store provides links to referrer manifests. +type referrersStoreFunc func(ctx context.Context, revision digest.Digest, artifactType string) *linkedBlobStore + // A ManifestHandler gets and puts manifests of a particular type. type ManifestHandler interface { // Unmarshal unmarshals the manifest from a byte slice. @@ -52,6 +60,9 @@ type manifestStore struct { schema2Handler ManifestHandler ocischemaHandler ManifestHandler manifestListHandler ManifestHandler + orasArtifactHandler ManifestHandler + + referrersStore referrersStoreFunc } var _ distribution.ManifestService = &manifestStore{} @@ -121,6 +132,11 @@ func (ms *manifestStore) Get(ctx context.Context, dgst digest.Digest, options .. default: return nil, distribution.ErrManifestVerification{fmt.Errorf("unrecognized manifest content type %s", versioned.MediaType)} } + default: + switch versioned.MediaType { + case orasartifactv1.MediaTypeArtifactManifest: + return ms.orasArtifactHandler.Unmarshal(ctx, dgst, content) + } } return nil, fmt.Errorf("unrecognized manifest schema version %d", versioned.SchemaVersion) @@ -138,11 +154,56 @@ func (ms *manifestStore) Put(ctx context.Context, manifest distribution.Manifest return ms.ocischemaHandler.Put(ctx, manifest, ms.skipDependencyVerification) case *manifestlist.DeserializedManifestList: return ms.manifestListHandler.Put(ctx, manifest, ms.skipDependencyVerification) + case *orasartifact.DeserializedManifest: + return ms.orasArtifactHandler.Put(ctx, manifest, ms.skipDependencyVerification) } return "", fmt.Errorf("unrecognized manifest type %T", manifest) } +// Referrers returns referrer manifests filtered by the given referrerType. +func (ms *manifestStore) Referrers(ctx context.Context, revision digest.Digest, referrerType string) ([]distribution.ArtifactDescriptor, error) { + dcontext.GetLogger(ms.ctx).Debug("(*manifestStore).Referrers") + + var referrers []distribution.ArtifactDescriptor + + err := ms.referrersStore(ctx, revision, referrerType).Enumerate(ctx, func(referrerRevision digest.Digest) error { + man, err := ms.Get(ctx, referrerRevision) + if err != nil { + return err + } + + orasArtifactMan, ok := man.(*orasartifact.DeserializedManifest) + if !ok { + // The PUT handler would guard against this situation. Skip this manifest. + return nil + } + + desc, err := ms.blobStore.Stat(ctx, referrerRevision) + if err != nil { + return err + } + desc.MediaType, _, _ = man.Payload() + referrers = append(referrers, distribution.ArtifactDescriptor{ + MediaType: desc.MediaType, + Size: desc.Size, + Digest: desc.Digest.String(), + ArtifactType: orasArtifactMan.ArtifactType(), + }) + return nil + }) + + if err != nil { + switch err.(type) { + case driver.PathNotFoundError: + return nil, nil + } + return nil, err + } + + return referrers, nil +} + // Delete removes the revision of the specified manifest. func (ms *manifestStore) Delete(ctx context.Context, dgst digest.Digest) error { dcontext.GetLogger(ms.ctx).Debug("(*manifestStore).Delete") diff --git a/registry/storage/orasartifactmanifesthandler.go b/registry/storage/orasartifactmanifesthandler.go new file mode 100644 index 00000000000..b655e25a833 --- /dev/null +++ b/registry/storage/orasartifactmanifesthandler.go @@ -0,0 +1,123 @@ +package storage + +import ( + "context" + "errors" + "fmt" + + "github.com/distribution/distribution/v3" + dcontext "github.com/distribution/distribution/v3/context" + "github.com/distribution/distribution/v3/manifest/orasartifact" + "github.com/opencontainers/go-digest" +) + +// orasArtifactManifestHandler is a ManifestHandler that covers ORAS Artifacts. +type orasArtifactManifestHandler struct { + repository distribution.Repository + blobStore distribution.BlobStore + ctx context.Context + referrersStore referrersStoreFunc +} + +func (oamh *orasArtifactManifestHandler) Unmarshal(ctx context.Context, dgst digest.Digest, content []byte) (distribution.Manifest, error) { + dcontext.GetLogger(oamh.ctx).Debug("(*orasArtifactManifestHandler).Unmarshal") + + dm := &orasartifact.DeserializedManifest{} + if err := dm.UnmarshalJSON(content); err != nil { + return nil, err + } + + return dm, nil +} + +func (ah *orasArtifactManifestHandler) Put(ctx context.Context, man distribution.Manifest, skipDependencyVerification bool) (digest.Digest, error) { + dcontext.GetLogger(ah.ctx).Debug("(*orasArtifactManifestHandler).Put") + + da, ok := man.(*orasartifact.DeserializedManifest) + if !ok { + return "", fmt.Errorf("wrong type put to orasArtifactManifestHandler: %T", man) + } + + if err := ah.verifyManifest(ah.ctx, *da, skipDependencyVerification); err != nil { + return "", err + } + + mt, payload, err := da.Payload() + if err != nil { + return "", err + } + + revision, err := ah.blobStore.Put(ctx, mt, payload) + if err != nil { + dcontext.GetLogger(ctx).Errorf("error putting payload into blobstore: %v", err) + return "", err + } + + err = ah.indexReferrers(ctx, *da, revision.Digest) + if err != nil { + dcontext.GetLogger(ctx).Errorf("error indexing referrers: %v", err) + return "", err + } + + return revision.Digest, nil +} + +// verifyManifest ensures that the manifest content is valid from the +// perspective of the registry. As a policy, the registry only tries to +// store valid content, leaving trust policies of that content up to +// consumers. +func (oamh *orasArtifactManifestHandler) verifyManifest(ctx context.Context, dm orasartifact.DeserializedManifest, skipDependencyVerification bool) error { + var errs distribution.ErrManifestVerification + + if dm.ArtifactType() == "" { + errs = append(errs, distribution.ErrManifestVerification{errors.New("artifactType invalid")}) + } + + if !skipDependencyVerification { + bs := oamh.repository.Blobs(ctx) + + // All references must exist. + for _, blobDesc := range dm.References() { + desc, err := bs.Stat(ctx, blobDesc.Digest) + if err != nil && err != distribution.ErrBlobUnknown { + errs = append(errs, err) + } + if err != nil || desc.Digest == "" { + // On error here, we always append unknown blob errors. + errs = append(errs, distribution.ErrManifestBlobUnknown{Digest: blobDesc.Digest}) + } + } + + ms, err := oamh.repository.Manifests(ctx) + if err != nil { + return err + } + + // Validate subject manifest. + subject := dm.SubjectManifest() + exists, err := ms.Exists(ctx, subject.Digest) + if !exists || err == distribution.ErrBlobUnknown { + errs = append(errs, distribution.ErrManifestBlobUnknown{Digest: subject.Digest}) + } else if err != nil { + errs = append(errs, err) + } + } + + if len(errs) != 0 { + return errs + } + + return nil +} + +// indexReferrers indexes the subject of the given revision in its referrers index store. +func (oamh *orasArtifactManifestHandler) indexReferrers(ctx context.Context, dm orasartifact.DeserializedManifest, revision digest.Digest) error { + artifactType := dm.ArtifactType() + subject := dm.SubjectManifest() + + if err := oamh.referrersStore(ctx, subject.Digest, artifactType).linkBlob(ctx, distribution.Descriptor{Digest: revision}); err != nil { + return err + } + + return nil +} diff --git a/registry/storage/paths.go b/registry/storage/paths.go index 4e9ad0856e8..f14afaa4f4f 100644 --- a/registry/storage/paths.go +++ b/registry/storage/paths.go @@ -74,6 +74,8 @@ const ( // manifestRevisionsPathSpec: /v2/repositories//_manifests/revisions/ // manifestRevisionPathSpec: /v2/repositories//_manifests/revisions/// // manifestRevisionLinkPathSpec: /v2/repositories//_manifests/revisions///link +// referrersPathSpec: /v2/repositories//_manifests/revisions///ref/ +// referrerRevisionLinkPathSpec: /v2/repositories//_manifests/revisions///ref////link // // Tags: // @@ -193,6 +195,25 @@ func pathFor(spec pathSpec) (string, error) { } return path.Join(root, path.Join(components...)), nil + case referrersPathSpec: + revisionPrefix, err := pathFor(manifestRevisionPathSpec{name: v.name, revision: v.revision}) + if err != nil { + return "", err + } + + return path.Join(revisionPrefix, "ref", v.artifactType), nil + case referrerRevisionLinkPathSpec: + metadataPrefix, err := pathFor(referrersPathSpec{name: v.name, revision: v.revision, artifactType: v.artifactType}) + if err != nil { + return "", err + } + + metadataSuffix, err := digestPathComponents(v.referrerRevision, false) + if err != nil { + return "", err + } + + return path.Join(append(append([]string{metadataPrefix}, metadataSuffix...), "link")...), nil case layerLinkPathSpec: components, err := digestPathComponents(v.digest, false) if err != nil { @@ -319,6 +340,26 @@ type manifestTagIndexPathSpec struct { func (manifestTagIndexPathSpec) pathSpec() {} +// referrersPathSpec describes the directory path for manifest referrers of type artifactType. +type referrersPathSpec struct { + name string + revision digest.Digest + artifactType string +} + +func (referrersPathSpec) pathSpec() {} + +// referrerRevisionLinkPathSpec describes the path components required to look +// up the referrer revision link of a manifest revision. +type referrerRevisionLinkPathSpec struct { + name string + revision digest.Digest + artifactType string + referrerRevision digest.Digest +} + +func (referrerRevisionLinkPathSpec) pathSpec() {} + // manifestTagIndexEntryPathSpec contains the entries of the index by revision. type manifestTagIndexEntryPathSpec struct { name string diff --git a/registry/storage/registry.go b/registry/storage/registry.go index 45939083f7a..988dfd37c85 100644 --- a/registry/storage/registry.go +++ b/registry/storage/registry.go @@ -3,12 +3,14 @@ package storage import ( "context" "regexp" + "strings" "github.com/distribution/distribution/v3" "github.com/distribution/distribution/v3/reference" "github.com/distribution/distribution/v3/registry/storage/cache" storagedriver "github.com/distribution/distribution/v3/registry/storage/driver" "github.com/docker/libtrust" + "github.com/opencontainers/go-digest" ) // registry is the top-level implementation of Registry for use in the storage @@ -266,10 +268,48 @@ func (repo *repository) Manifests(ctx context.Context, options ...distribution.M } } + referrersStore := func(ctx context.Context, revision digest.Digest, artifactType string) *linkedBlobStore { + artifactTypeDigest := "" + + if artifactType != "" { + // NOTE (aviral26) + // This might cause a problem on Windows file system because of long file paths. + // A workaround is to run this server using wsl2. + artifactTypeDigest = strings.Split(digest.FromString(artifactType).String(), ":")[1] + } + + return &linkedBlobStore{ + blobStore: repo.blobStore, + repository: repo, + ctx: ctx, + linkPathFns: []linkPathFunc{ + func(name string, referrerRevision digest.Digest) (string, error) { + return pathFor(referrerRevisionLinkPathSpec{ + name: name, + revision: revision, + artifactType: artifactTypeDigest, + referrerRevision: referrerRevision, + }) + }, + }, + linkDirectoryPathSpec: referrersPathSpec{ + name: repo.Named().Name(), + revision: revision, + artifactType: artifactTypeDigest, + }, + blobAccessController: &linkedBlobStatter{ + blobStore: repo.blobStore, + repository: repo, + linkPathFns: []linkPathFunc{manifestRevisionLinkPath}, + }, + } + } + ms := &manifestStore{ ctx: ctx, repository: repo, blobStore: blobStore, + referrersStore: referrersStore, schema1Handler: v1Handler, schema2Handler: &schema2ManifestHandler{ ctx: ctx, @@ -282,6 +322,12 @@ func (repo *repository) Manifests(ctx context.Context, options ...distribution.M repository: repo, blobStore: blobStore, }, + orasArtifactHandler: &orasArtifactManifestHandler{ + ctx: ctx, + repository: repo, + blobStore: blobStore, + referrersStore: referrersStore, + }, ocischemaHandler: &ocischemaManifestHandler{ ctx: ctx, repository: repo, diff --git a/vendor/github.com/oras-project/artifacts-spec/LICENSE b/vendor/github.com/oras-project/artifacts-spec/LICENSE new file mode 100644 index 00000000000..a67d1693894 --- /dev/null +++ b/vendor/github.com/oras-project/artifacts-spec/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2021 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. diff --git a/vendor/github.com/oras-project/artifacts-spec/specs-go/v1/descriptor.go b/vendor/github.com/oras-project/artifacts-spec/specs-go/v1/descriptor.go new file mode 100644 index 00000000000..a4e024088ea --- /dev/null +++ b/vendor/github.com/oras-project/artifacts-spec/specs-go/v1/descriptor.go @@ -0,0 +1,40 @@ +// Copyright 2021 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 v1 + +import digest "github.com/opencontainers/go-digest" + +// Descriptor describes the disposition of targeted content. +type Descriptor struct { + // MediaType is the media type of the object this schema refers to. + MediaType string `json:"mediaType,omitempty"` + + // ArtifactType is the artifact type of the object this schema refers to. + // + // When the descriptor is used for blobs, this property must be empty. + ArtifactType string `json:"artifactType"` + + // Digest is the digest of the targeted content. + Digest digest.Digest `json:"digest"` + + // Size specifies the size in bytes of the blob. + Size int64 `json:"size"` + + // URLs specifies a list of URLs from which this object MAY be downloaded + URLs []string `json:"urls,omitempty"` + + // Annotations contains arbitrary metadata relating to the targeted content. + Annotations map[string]string `json:"annotations,omitempty"` +} diff --git a/vendor/github.com/oras-project/artifacts-spec/specs-go/v1/manifest.go b/vendor/github.com/oras-project/artifacts-spec/specs-go/v1/manifest.go new file mode 100644 index 00000000000..854d0cca935 --- /dev/null +++ b/vendor/github.com/oras-project/artifacts-spec/specs-go/v1/manifest.go @@ -0,0 +1,35 @@ +// Copyright 2021 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 v1 + +// Manifest describes an ORAS artifact. +// This structure provides `application/vnd.oras.artifact.manifest.v1+json` mediatype when marshalled to JSON. +type Manifest struct { + // MediaType is the media type of the object this schema refers to. + MediaType string `json:"mediaType"` + + // ArtifactType is the artifact type of the object this schema refers to. + ArtifactType string `json:"artifactType"` + + // Blobs is a collection of blobs referenced by this manifest. + Blobs []Descriptor `json:"blobs"` + + // SubjectManifest is an optional reference to any existing manifest within the repository. + // When specified, the artifact is said to be dependent upon the referenced subjectManifest. + SubjectManifest Descriptor `json:"subjectManifest"` + + // Annotations contains arbitrary metadata for the artifact manifest. + Annotations map[string]string `json:"annotations,omitempty"` +} diff --git a/vendor/github.com/oras-project/artifacts-spec/specs-go/v1/mediatype.go b/vendor/github.com/oras-project/artifacts-spec/specs-go/v1/mediatype.go new file mode 100644 index 00000000000..52c1732325e --- /dev/null +++ b/vendor/github.com/oras-project/artifacts-spec/specs-go/v1/mediatype.go @@ -0,0 +1,20 @@ +// Copyright 2021 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 v1 + +const ( + // MediaTypeArtifactManifest specifies the media type for an ORAS artifact reference type manifest. + MediaTypeArtifactManifest = "application/vnd.cncf.oras.artifact.manifest.v1+json" +) diff --git a/vendor/modules.txt b/vendor/modules.txt index 25194388b48..d9abb28cb3b 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -139,6 +139,9 @@ github.com/opencontainers/go-digest ## explicit github.com/opencontainers/image-spec/specs-go github.com/opencontainers/image-spec/specs-go/v1 +# github.com/oras-project/artifacts-spec v0.0.0-00010101000000-000000000000 => github.com/aviral26/artifacts-spec v0.0.2 +## explicit +github.com/oras-project/artifacts-spec/specs-go/v1 # github.com/prometheus/client_golang v1.1.0 github.com/prometheus/client_golang/prometheus github.com/prometheus/client_golang/prometheus/internal @@ -243,3 +246,4 @@ gopkg.in/check.v1 # gopkg.in/yaml.v2 v2.4.0 ## explicit gopkg.in/yaml.v2 +# github.com/oras-project/artifacts-spec => github.com/aviral26/artifacts-spec v0.0.2 From 0c7cf29bfcbcadc791463bffef30a694c9de28a7 Mon Sep 17 00:00:00 2001 From: Aviral Takkar Date: Tue, 24 Aug 2021 01:08:17 -0700 Subject: [PATCH 3/9] update readme, add release template Signed-off-by: Aviral Takkar --- README.md | 229 +++++++++++++++++++++++++------------ docs/referrers.md | 140 ----------------------- releases/v0.0.1-alpha.toml | 31 +++++ 3 files changed, 189 insertions(+), 211 deletions(-) create mode 100644 releases/v0.0.1-alpha.toml diff --git a/README.md b/README.md index 3af0093ec49..7af95119682 100644 --- a/README.md +++ b/README.md @@ -1,73 +1,160 @@ # Distribution -The toolset to pack, ship, store, and deliver content. - -This repository's main product is the Open Source Registry implementation -for storing and distributing container images using the -[OCI Distribution Specification](https://github.com/opencontainers/distribution-spec). -The goal of this project is to provide a simple, secure, and scalable base -for building a large scale registry solution or running a simple private registry. -It is a core library for many registry operators including Docker Hub, GitHub Container Registry, -GitLab Container Registry and DigitalOcean Container Registry, as well as the CNCF Harbor -Project, and VMware Harbor Registry. - - - -[![Build Status](https://github.com/distribution/distribution/workflows/CI/badge.svg?branch=main&event=push)](https://github.com/distribution/distribution/actions?query=workflow%3ACI) -[![GoDoc](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/distribution/distribution) -[![License: Apache-2.0](https://img.shields.io/badge/License-Apache--2.0-blue.svg)](LICENSE) -[![codecov](https://codecov.io/gh/distribution/distribution/branch/main/graph/badge.svg)](https://codecov.io/gh/distribution/distribution) -[![FOSSA Status](https://app.fossa.com/api/projects/custom%2B162%2Fgithub.com%2Fdistribution%2Fdistribution.svg?type=shield)](https://app.fossa.com/projects/custom%2B162%2Fgithub.com%2Fdistribution%2Fdistribution?ref=badge_shield) -[![OCI Conformance](https://github.com/distribution/distribution/workflows/conformance/badge.svg)](https://github.com/distribution/distribution/actions?query=workflow%3Aconformance) - -This repository contains the following components: - -|**Component** |Description | -|--------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| **registry** | An implementation of the [OCI Distribution Specification](https://github.com/opencontainers/distribution-spec). | -| **libraries** | A rich set of libraries for interacting with distribution components. Please see [godoc](https://pkg.go.dev/github.com/distribution/distribution) for details. **Note**: The interfaces for these libraries are **unstable**. | -| **documentation** | Docker's full documentation set is available at [docs.docker.com](https://docs.docker.com). This repository [contains the subset](docs/) related just to the registry. | - -### How does this integrate with Docker, containerd, and other OCI client? - -Clients implement against the OCI specification and communicate with the -registry using HTTP. This project contains a client implementation which -is currently in use by Docker, however, it is deprecated for the -[implementation in containerd](https://github.com/containerd/containerd/tree/master/remotes/docker) -and will not support new features. - -### What are the long term goals of the Distribution project? - -The _Distribution_ project has the further long term goal of providing a -secure tool chain for distributing content. The specifications, APIs and tools -should be as useful with Docker as they are without. - -Our goal is to design a professional grade and extensible content distribution -system that allow users to: - -* Enjoy an efficient, secured and reliable way to store, manage, package and - exchange content -* Hack/roll their own on top of healthy open-source components -* Implement their own home made solution through good specs, and solid - extensions mechanism. - -## Contribution - -Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute -issues, fixes, and patches to this project. If you are contributing code, see -the instructions for [building a development environment](BUILDING.md). - -## Communication - -For async communication and long running discussions please use issues and pull requests on the github repo. -This will be the best place to discuss design and implementation. - -For sync communication we have a #distribution channel in the [CNCF Slack](https://slack.cncf.io/) -that everyone is welcome to join and chat about development. - -## Licenses - -The distribution codebase is released under the [Apache 2.0 license](LICENSE). -The README.md file, and files in the "docs" folder are licensed under the -Creative Commons Attribution 4.0 International License. You may obtain a -copy of the license, titled CC-BY-4.0, at http://creativecommons.org/licenses/by/4.0/. +This fork of [distribution/distribution](distribution-distribution) provides +an experimental implementation of [reference types](reference-types). + +Features supported: + +- :heavy_check_mark: PUT ORAS Artifact Manifest +- :heavy_check_mark: GET ORAS Artifact Manifest +- :heavy_check_mark: LIST referrers + - [ ] Pagination support +- [ ] Garbage Collection of reference types + +To power the `/referrers` API, the implementation creates and uses an index. +See [referrers.md](docs/referrers.md) for details. + +## Usage - Push, Discover, Pull + +The following steps illustrate how ORAS artifacts can be stored and retrieved +from a registry. The artifact in this example is a Notary V2 +[signature](signature). + +### Prerequisites + +- Local registry prototype instance +- [docker-generate](https://github.com/shizhMSFT/docker-generate) +- [nv2](https://github.com/notaryproject/nv2) +- `curl` +- `jq` + +### Push an image to your registry + +```shell +# Initialize local registry variables +regIp="127.0.0.1" && \ + regPort="5000" && \ + registry="$regIp:$regPort" && \ + repo="busybox" && \ + tag="latest" && \ + image="$repo:$tag" && \ + reference="$registry/$image" + +# Pull an image from docker hub and push to local registry +docker pull $image && \ + docker tag $image $reference && \ + docker push $reference +``` + +### Generate image manifest and sign it + +```shell +# Generate self-signed certificates +openssl req \ + -x509 \ + -sha256 \ + -nodes \ + -newkey rsa:2048 \ + -days 365 \ + -subj "/CN=$regIp/O=example inc/C=IN/ST=Haryana/L=Gurgaon" \ + -addext "subjectAltName=IP:$regIp" \ + -keyout example.key \ + -out example.crt + +# Generate image manifest +manifestFile="manifest-to-sign.json" && \ + docker generate manifest $image > $manifestFile + +# Sign manifest +signatureFile="manifest-signature.jwt" && \ + nv2 sign --method x509 \ + -k example.key \ + -c example.crt \ + -r $reference \ + -o $signatureFile \ + file:$manifestFile +``` + +### Obtain manifest and signature digests + +```shell +manifestDigest="sha256:$(sha256sum $manifestFile | cut -d " " -f 1)" && \ + signatureDigest="sha256:$(sha256sum $signatureFile | cut -d " " -f 1)" +``` + +### Create an Artifact file referencing the manifest that was signed and its signature as blob + +```shell +artifactFile="artifact.json" && \ + artifactMediaType="application/vnd.cncf.oras.artifact.manifest.v1+json" && \ + artifactType="application/vnd.cncf.notary.v2" && \ + signatureMediaType="application/vnd.cncf.notary.signature.v2+jwt" && \ + signatureFileSize=`wc -c < $signatureFile` && \ + manifestMediaType="$(cat $manifestFile | jq -r '.mediaType')" && \ + manifestFileSize=`wc -c < $manifestFile` + +cat < $artifactFile +{ + "mediaType": "$artifactMediaType", + "artifactType": "$artifactType", + "blobs": [ + { + "mediaType": "$signatureMediaType", + "digest": "$signatureDigest", + "size": $signatureFileSize + } + ], + "subjectManifest": { + "mediaType": "$manifestMediaType", + "digest": "$manifestDigest", + "size": $manifestFileSize + } +} +EOF +``` + +### Obtain artifact digest + +```shell +artifactDigest="sha256:$(sha256sum $artifactFile | cut -d " " -f 1)" +``` + +### Push signature and artifact + +```shell +# Initiate blob upload and obtain PUT location +blobPutLocation=`curl -I -X POST -s http://$registry/v2/$repo/blobs/uploads/ | grep "Location: " | sed -e "s/Location: //;s/$/\&digest=$signatureDigest/;s/\r//"` + +# Push signature blob +curl -X PUT -H "Content-Type: application/octet-stream" --data-binary @"$signatureFile" $blobPutLocation + +# Push artifact +curl -X PUT --data-binary @"$artifactFile" -H "Content-Type: $artifactMediaType" "http://$registry/v2/$repo/manifests/$artifactDigest" +``` + +### List referrers + +```shell +# Retrieve referrers +curl -s "http://$registry/oras/artifacts/v1/$repo/manifests/$manifestDigest/referrers?artifactType=$artifactType" | jq +``` + +### Verify signature + +```shell +# Retrieve signature +artifactDigest=`curl -s "http://$registry/oras/artifacts/v1/$repo/manifests/$manifestDigest/referrers?artifactType=$artifactType" | jq -r '.references[0].digest'` && \ + signatureDigest=`curl -s "http://$registry/oras/artifacts/v1/$repo/manifests/$artifactDigest" | jq -r '.blobs[0].digest'` && \ + retrievedSignatureFile="retrieved-signature.json" && \ + curl -s http://$registry/v2/$repo/blobs/$signatureDigest > $retrievedSignatureFile + +# Verify signature +nv2 verify \ + -f $retrievedSignatureFile \ + -c example.crt \ + file:$manifestFile +``` + +[distribution-distribution]: https://github.com/distribution/distribution +[reference-types]: https://github.com/oras-project/artifacts-spec +[signature]: https://github.com/notaryproject/nv2/tree/prototype-2/docs/nv2 diff --git a/docs/referrers.md b/docs/referrers.md index bdc4b42934f..58ac9e8e2ce 100644 --- a/docs/referrers.md +++ b/docs/referrers.md @@ -5,146 +5,6 @@ This document describes an experimental prototype that implements the [ORAS Artifact Manifest](https://github.com/oras-project/artifacts-spec) spec. -## Usage - Push, Discover, Pull - -The following steps illustrate how ORAS artifacts can be stored and retrieved from a registry. -The artifact in this example is a Notary V2 [signature](https://github.com/notaryproject/nv2/tree/prototype-2/docs/nv2). - -### Prerequisites - -- Local registry prototype instance -- [docker-generate](https://github.com/shizhMSFT/docker-generate) -- [nv2](https://github.com/notaryproject/nv2) -- `curl` -- `jq` - -### Push an image to your registry - -```shell -# Initialize local registry variables -regIp="127.0.0.1" && \ - regPort="5000" && \ - registry="$regIp:$regPort" && \ - repo="busybox" && \ - tag="latest" && \ - image="$repo:$tag" && \ - reference="$registry/$image" - -# Pull an image from docker hub and push to local registry -docker pull $image && \ - docker tag $image $reference && \ - docker push $reference -``` - -### Generate image manifest and sign it - -```shell -# Generate self-signed certificates -openssl req \ - -x509 \ - -sha256 \ - -nodes \ - -newkey rsa:2048 \ - -days 365 \ - -subj "/CN=$regIp/O=example inc/C=IN/ST=Haryana/L=Gurgaon" \ - -addext "subjectAltName=IP:$regIp" \ - -keyout example.key \ - -out example.crt - -# Generate image manifest -manifestFile="manifest-to-sign.json" && \ - docker generate manifest $image > $manifestFile - -# Sign manifest -signatureFile="manifest-signature.jwt" && \ - nv2 sign --method x509 \ - -k example.key \ - -c example.crt \ - -r $reference \ - -o $signatureFile \ - file:$manifestFile -``` - -### Obtain manifest and signature digests - -```shell -manifestDigest="sha256:$(sha256sum $manifestFile | cut -d " " -f 1)" && \ - signatureDigest="sha256:$(sha256sum $signatureFile | cut -d " " -f 1)" -``` - -### Create an Artifact file referencing the manifest that was signed and its signature as blob - -```shell -artifactFile="artifact.json" && \ - artifactMediaType="application/vnd.cncf.oras.artifact.manifest.v1+json" && \ - artifactType="application/vnd.cncf.notary.v2" && \ - signatureMediaType="application/vnd.cncf.notary.signature.v2+jwt" && \ - signatureFileSize=`wc -c < $signatureFile` && \ - manifestMediaType="$(cat $manifestFile | jq -r '.mediaType')" && \ - manifestFileSize=`wc -c < $manifestFile` - -cat < $artifactFile -{ - "mediaType": "$artifactMediaType", - "artifactType": "$artifactType", - "blobs": [ - { - "mediaType": "$signatureMediaType", - "digest": "$signatureDigest", - "size": $signatureFileSize - } - ], - "subjectManifest": { - "mediaType": "$manifestMediaType", - "digest": "$manifestDigest", - "size": $manifestFileSize - } -} -EOF -``` - -### Obtain artifact digest - -```shell -artifactDigest="sha256:$(sha256sum $artifactFile | cut -d " " -f 1)" -``` - -### Push signature and artifact - -```shell -# Initiate blob upload and obtain PUT location -blobPutLocation=`curl -I -X POST -s http://$registry/v2/$repo/blobs/uploads/ | grep "Location: " | sed -e "s/Location: //;s/$/\&digest=$signatureDigest/;s/\r//"` - -# Push signature blob -curl -X PUT -H "Content-Type: application/octet-stream" --data-binary @"$signatureFile" $blobPutLocation - -# Push artifact -curl -X PUT --data-binary @"$artifactFile" -H "Content-Type: $artifactMediaType" "http://$registry/v2/$repo/manifests/$artifactDigest" -``` - -### List referrers - -```shell -# Retrieve referrers -curl -s "http://$registry/oras/artifacts/v1/$repo/manifests/$manifestDigest/referrers?artifactType=$artifactType" | jq -``` - -### Verify signature - -```shell -# Retrieve signature -artifactDigest=`curl -s "http://$registry/oras/artifacts/v1/$repo/manifests/$manifestDigest/referrers?artifactType=$artifactType" | jq -r '.references[0].digest'` && \ - signatureDigest=`curl -s "http://$registry/oras/artifacts/v1/$repo/manifests/$artifactDigest" | jq -r '.blobs[0].digest'` && \ - retrievedSignatureFile="retrieved-signature.json" && \ - curl -s http://$registry/v2/$repo/blobs/$signatureDigest > $retrievedSignatureFile - -# Verify signature -nv2 verify \ - -f $retrievedSignatureFile \ - -c example.crt \ - file:$manifestFile -``` - ## Implementation To power the [/referrers](https://github.com/oras-project/artifacts-spec/blob/main/manifest-referrers-api.md) API, the diff --git a/releases/v0.0.1-alpha.toml b/releases/v0.0.1-alpha.toml new file mode 100644 index 00000000000..6f48fdf4471 --- /dev/null +++ b/releases/v0.0.1-alpha.toml @@ -0,0 +1,31 @@ +# commit to be tagged for new release +commit = "HEAD" + +project_name = "registry" +github_repo = "oras-project/distribution" + +# previous release +previous = "main" + +pre_release = true + +preface = """ +The v0.0.1-alpha registry experimental release provides basic support for phase 1 of +[reference types](phase-1-reference-types). Clients can push and pull manifests +of media type `application/vnd.cncf.oras.artifact.manifest.v1+json` (ORAS +Artifact Manifests) and discover existing reference types using the `/referrers` +[api](referrers-api). + +This release includes the following changes pertaining to reference types: + +* Adds CRUD operations for ORAS Artifact Manifests +* Adds support for `GET /referrers` API + * In this release candidate, pagination is not implemented + * The implementation uses a [reverse index](reverse-index) as an optimization + +The release **does not** implement garbage collection of reference types. + +[phase-1-reference-types]: https://github.com/oras-project/artifacts-spec/blob/main/scenarios.md#phase-1---reference-types +[referrers-api]: https://github.com/oras-project/artifacts-spec/blob/main/manifest-referrers-api.md +[reverse-index]: docs/referrers.md +""" From a6af141fb7ac7b48b65bd1e76d458fff36707018 Mon Sep 17 00:00:00 2001 From: Aviral Takkar Date: Tue, 24 Aug 2021 02:23:40 -0700 Subject: [PATCH 4/9] remove aviral26/artifacts-spec replace Signed-off-by: Aviral Takkar --- go.mod | 4 +--- go.sum | 4 ++-- vendor/modules.txt | 3 +-- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index dc5ea03cc81..15cedff7f13 100644 --- a/go.mod +++ b/go.mod @@ -2,8 +2,6 @@ module github.com/distribution/distribution/v3 go 1.15 -replace github.com/oras-project/artifacts-spec => github.com/aviral26/artifacts-spec v0.0.2 - require ( github.com/Azure/azure-sdk-for-go v16.2.1+incompatible github.com/Azure/go-autorest v10.8.1+incompatible // indirect @@ -32,7 +30,7 @@ require ( github.com/ncw/swift v1.0.47 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.0.1 - github.com/oras-project/artifacts-spec v0.0.0-00010101000000-000000000000 + github.com/oras-project/artifacts-spec v0.0.0-20210820222200-769b88c06428 github.com/satori/go.uuid v1.2.0 // indirect github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v0.0.3 diff --git a/go.sum b/go.sum index f2a1020226c..3072cc9c9eb 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,6 @@ github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/O github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/aviral26/artifacts-spec v0.0.2 h1:uU5MIRT68TP4l2Elri9WoweDc9f8iSwe0G0ghDCxolQ= -github.com/aviral26/artifacts-spec v0.0.2/go.mod h1:Xch2aLzSwtkhbFFN6LUzTfLtukYvMMdXJ4oZ8O7BOdc= github.com/aws/aws-sdk-go v1.34.9 h1:cUGBW9CVdi0mS7K1hDzxIqTpfeWhpoQiguq81M1tjK0= github.com/aws/aws-sdk-go v1.34.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -96,6 +94,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/oras-project/artifacts-spec v0.0.0-20210820222200-769b88c06428 h1:amnau8Czf7c+KCq14179DmXq9tLt0b/tGg+xusz2L2Q= +github.com/oras-project/artifacts-spec v0.0.0-20210820222200-769b88c06428/go.mod h1:Xch2aLzSwtkhbFFN6LUzTfLtukYvMMdXJ4oZ8O7BOdc= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/vendor/modules.txt b/vendor/modules.txt index d9abb28cb3b..a62385b2477 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -139,7 +139,7 @@ github.com/opencontainers/go-digest ## explicit github.com/opencontainers/image-spec/specs-go github.com/opencontainers/image-spec/specs-go/v1 -# github.com/oras-project/artifacts-spec v0.0.0-00010101000000-000000000000 => github.com/aviral26/artifacts-spec v0.0.2 +# github.com/oras-project/artifacts-spec v0.0.0-20210820222200-769b88c06428 ## explicit github.com/oras-project/artifacts-spec/specs-go/v1 # github.com/prometheus/client_golang v1.1.0 @@ -246,4 +246,3 @@ gopkg.in/check.v1 # gopkg.in/yaml.v2 v2.4.0 ## explicit gopkg.in/yaml.v2 -# github.com/oras-project/artifacts-spec => github.com/aviral26/artifacts-spec v0.0.2 From 70b3afde2365183732e62ceea9e2dee65aadd58d Mon Sep 17 00:00:00 2001 From: Aviral Takkar Date: Fri, 27 Aug 2021 06:13:23 -0700 Subject: [PATCH 5/9] subjectManifest -> subject Signed-off-by: Aviral Takkar --- go.mod | 2 +- go.sum | 2 ++ .../oras-project/artifacts-spec/specs-go/v1/manifest.go | 4 ++-- vendor/modules.txt | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 15cedff7f13..6ff57ef56db 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( github.com/ncw/swift v1.0.47 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.0.1 - github.com/oras-project/artifacts-spec v0.0.0-20210820222200-769b88c06428 + github.com/oras-project/artifacts-spec v0.0.0-20210826181006-68f2cefa34a6 github.com/satori/go.uuid v1.2.0 // indirect github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v0.0.3 diff --git a/go.sum b/go.sum index 3072cc9c9eb..740ee4bbe4c 100644 --- a/go.sum +++ b/go.sum @@ -96,6 +96,8 @@ github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVo github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/oras-project/artifacts-spec v0.0.0-20210820222200-769b88c06428 h1:amnau8Czf7c+KCq14179DmXq9tLt0b/tGg+xusz2L2Q= github.com/oras-project/artifacts-spec v0.0.0-20210820222200-769b88c06428/go.mod h1:Xch2aLzSwtkhbFFN6LUzTfLtukYvMMdXJ4oZ8O7BOdc= +github.com/oras-project/artifacts-spec v0.0.0-20210826181006-68f2cefa34a6 h1:BZYj7fsb3kZb2INlysMzTeLZ1ksxwwn4BreryzyI2KM= +github.com/oras-project/artifacts-spec v0.0.0-20210826181006-68f2cefa34a6/go.mod h1:Xch2aLzSwtkhbFFN6LUzTfLtukYvMMdXJ4oZ8O7BOdc= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/vendor/github.com/oras-project/artifacts-spec/specs-go/v1/manifest.go b/vendor/github.com/oras-project/artifacts-spec/specs-go/v1/manifest.go index 854d0cca935..341e7f4f678 100644 --- a/vendor/github.com/oras-project/artifacts-spec/specs-go/v1/manifest.go +++ b/vendor/github.com/oras-project/artifacts-spec/specs-go/v1/manifest.go @@ -27,8 +27,8 @@ type Manifest struct { Blobs []Descriptor `json:"blobs"` // SubjectManifest is an optional reference to any existing manifest within the repository. - // When specified, the artifact is said to be dependent upon the referenced subjectManifest. - SubjectManifest Descriptor `json:"subjectManifest"` + // When specified, the artifact is said to be dependent upon the referenced subject. + SubjectManifest Descriptor `json:"subject"` // Annotations contains arbitrary metadata for the artifact manifest. Annotations map[string]string `json:"annotations,omitempty"` diff --git a/vendor/modules.txt b/vendor/modules.txt index a62385b2477..7ab11096d8a 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -139,7 +139,7 @@ github.com/opencontainers/go-digest ## explicit github.com/opencontainers/image-spec/specs-go github.com/opencontainers/image-spec/specs-go/v1 -# github.com/oras-project/artifacts-spec v0.0.0-20210820222200-769b88c06428 +# github.com/oras-project/artifacts-spec v0.0.0-20210826181006-68f2cefa34a6 ## explicit github.com/oras-project/artifacts-spec/specs-go/v1 # github.com/prometheus/client_golang v1.1.0 From 5478867f9f1d93c01f8938a8090101399ff72237 Mon Sep 17 00:00:00 2001 From: Aviral Takkar Date: Wed, 1 Sep 2021 05:38:12 -0700 Subject: [PATCH 6/9] update artifacts-spec version Signed-off-by: Aviral Takkar --- go.mod | 2 +- go.sum | 6 ++---- .../oras-project/artifacts-spec/specs-go/v1/descriptor.go | 2 +- vendor/modules.txt | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 6ff57ef56db..3a1c01ef743 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( github.com/ncw/swift v1.0.47 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.0.1 - github.com/oras-project/artifacts-spec v0.0.0-20210826181006-68f2cefa34a6 + github.com/oras-project/artifacts-spec v0.0.0-20210830204616-bd9edb3a9b43 github.com/satori/go.uuid v1.2.0 // indirect github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v0.0.3 diff --git a/go.sum b/go.sum index 740ee4bbe4c..74b397bb4c4 100644 --- a/go.sum +++ b/go.sum @@ -94,10 +94,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/oras-project/artifacts-spec v0.0.0-20210820222200-769b88c06428 h1:amnau8Czf7c+KCq14179DmXq9tLt0b/tGg+xusz2L2Q= -github.com/oras-project/artifacts-spec v0.0.0-20210820222200-769b88c06428/go.mod h1:Xch2aLzSwtkhbFFN6LUzTfLtukYvMMdXJ4oZ8O7BOdc= -github.com/oras-project/artifacts-spec v0.0.0-20210826181006-68f2cefa34a6 h1:BZYj7fsb3kZb2INlysMzTeLZ1ksxwwn4BreryzyI2KM= -github.com/oras-project/artifacts-spec v0.0.0-20210826181006-68f2cefa34a6/go.mod h1:Xch2aLzSwtkhbFFN6LUzTfLtukYvMMdXJ4oZ8O7BOdc= +github.com/oras-project/artifacts-spec v0.0.0-20210830204616-bd9edb3a9b43 h1:WDAdGkL9gSREKHLV3eyIWalP9Q7Nf+MTnDvXi1X6xR4= +github.com/oras-project/artifacts-spec v0.0.0-20210830204616-bd9edb3a9b43/go.mod h1:Xch2aLzSwtkhbFFN6LUzTfLtukYvMMdXJ4oZ8O7BOdc= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/vendor/github.com/oras-project/artifacts-spec/specs-go/v1/descriptor.go b/vendor/github.com/oras-project/artifacts-spec/specs-go/v1/descriptor.go index a4e024088ea..dae2accc687 100644 --- a/vendor/github.com/oras-project/artifacts-spec/specs-go/v1/descriptor.go +++ b/vendor/github.com/oras-project/artifacts-spec/specs-go/v1/descriptor.go @@ -24,7 +24,7 @@ type Descriptor struct { // ArtifactType is the artifact type of the object this schema refers to. // // When the descriptor is used for blobs, this property must be empty. - ArtifactType string `json:"artifactType"` + ArtifactType string `json:"artifactType,omitempty"` // Digest is the digest of the targeted content. Digest digest.Digest `json:"digest"` diff --git a/vendor/modules.txt b/vendor/modules.txt index 7ab11096d8a..213d36e4af8 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -139,7 +139,7 @@ github.com/opencontainers/go-digest ## explicit github.com/opencontainers/image-spec/specs-go github.com/opencontainers/image-spec/specs-go/v1 -# github.com/oras-project/artifacts-spec v0.0.0-20210826181006-68f2cefa34a6 +# github.com/oras-project/artifacts-spec v0.0.0-20210830204616-bd9edb3a9b43 ## explicit github.com/oras-project/artifacts-spec/specs-go/v1 # github.com/prometheus/client_golang v1.1.0 From 032cbef5628f09a905f4af1d68c45ac874fc989c Mon Sep 17 00:00:00 2001 From: Aviral Takkar Date: Wed, 1 Sep 2021 06:44:45 -0700 Subject: [PATCH 7/9] remote internal ArtifactDescriptor Signed-off-by: Aviral Takkar --- manifests.go | 20 ++------------------ registry/client/repository.go | 3 ++- registry/handlers/referrers.go | 7 ++++--- registry/proxy/proxymanifeststore.go | 3 ++- registry/proxy/proxymanifeststore_test.go | 3 ++- registry/storage/manifeststore.go | 8 ++++---- 6 files changed, 16 insertions(+), 28 deletions(-) diff --git a/manifests.go b/manifests.go index 064f8ea3239..9bd97b7f711 100644 --- a/manifests.go +++ b/manifests.go @@ -6,6 +6,7 @@ import ( "mime" "github.com/opencontainers/go-digest" + orasartifact "github.com/oras-project/artifacts-spec/specs-go/v1" ) // Manifest represents a registry object specifying a set of @@ -47,23 +48,6 @@ type ManifestBuilder interface { AppendReference(dependency Describable) error } -// ArtifactDescriptor describes targeted reference type content. -type ArtifactDescriptor struct { - // MediaType describe the type of the content. All text based formats are - // encoded as utf-8. - MediaType string `json:"mediaType,omitempty"` - - // Size in bytes of content. - Size int64 `json:"size,omitempty"` - - // Digest uniquely identifies the content. A byte stream can be verified - // against this digest. - Digest string `json:"digest,omitempty"` - - // ArtifactType specifies the artifact type of the content. - ArtifactType string `json:"artifactType,omitempty"` -} - // ManifestService describes operations on image manifests. type ManifestService interface { // Exists returns true if the manifest exists. @@ -81,7 +65,7 @@ type ManifestService interface { // Referrers returns a collection of manifests which reference the given manifest, // filtered by artifactType. - Referrers(ctx context.Context, dgst digest.Digest, artifactType string) ([]ArtifactDescriptor, error) + Referrers(ctx context.Context, dgst digest.Digest, artifactType string) ([]orasartifact.Descriptor, error) } // ManifestEnumerator enables iterating over manifests diff --git a/registry/client/repository.go b/registry/client/repository.go index 4bcb518a2e6..b9b876a6dc8 100644 --- a/registry/client/repository.go +++ b/registry/client/repository.go @@ -21,6 +21,7 @@ import ( "github.com/distribution/distribution/v3/registry/storage/cache" "github.com/distribution/distribution/v3/registry/storage/cache/memory" "github.com/opencontainers/go-digest" + orasartifacts "github.com/oras-project/artifacts-spec/specs-go/v1" ) // Registry provides an interface for calling Repositories, which returns a catalog of repositories. @@ -397,7 +398,7 @@ type manifests struct { etags map[string]string } -func (ms *manifests) Referrers(_ context.Context, _ digest.Digest, _ string) ([]distribution.ArtifactDescriptor, error) { +func (ms *manifests) Referrers(_ context.Context, _ digest.Digest, _ string) ([]orasartifacts.Descriptor, error) { return nil, fmt.Errorf("not implemented") } diff --git a/registry/handlers/referrers.go b/registry/handlers/referrers.go index 1712668e2f7..7011bce05c4 100644 --- a/registry/handlers/referrers.go +++ b/registry/handlers/referrers.go @@ -5,11 +5,12 @@ import ( "net/http" "github.com/distribution/distribution/v3" + dcontext "github.com/distribution/distribution/v3/context" "github.com/distribution/distribution/v3/registry/api/errcode" v2 "github.com/distribution/distribution/v3/registry/api/v2" - dcontext "github.com/distribution/distribution/v3/context" "github.com/gorilla/handlers" "github.com/opencontainers/go-digest" + orasartifacts "github.com/oras-project/artifacts-spec/specs-go/v1" ) // referrersDispatcher takes the request context and builds the @@ -29,7 +30,7 @@ func referrersDispatcher(ctx *Context, r *http.Request) http.Handler { // referrersResponse describes the response body of the referrers API. type referrersResponse struct { - Referrers []distribution.ArtifactDescriptor `json:"references"` + Referrers []orasartifacts.Descriptor `json:"references"` } // referrersHandler handles http operations on manifest referrers. @@ -70,7 +71,7 @@ func (h *referrersHandler) Get(w http.ResponseWriter, r *http.Request) { } if referrers == nil { - referrers = []distribution.ArtifactDescriptor{} + referrers = []orasartifacts.Descriptor{} } response := referrersResponse{} diff --git a/registry/proxy/proxymanifeststore.go b/registry/proxy/proxymanifeststore.go index e9d52e9d1df..6af93dfe290 100644 --- a/registry/proxy/proxymanifeststore.go +++ b/registry/proxy/proxymanifeststore.go @@ -9,6 +9,7 @@ import ( "github.com/distribution/distribution/v3/reference" "github.com/distribution/distribution/v3/registry/proxy/scheduler" "github.com/opencontainers/go-digest" + orasartifacts "github.com/oras-project/artifacts-spec/specs-go/v1" ) // todo(richardscothern): from cache control header or config @@ -25,7 +26,7 @@ type proxyManifestStore struct { var _ distribution.ManifestService = &proxyManifestStore{} -func (pms proxyManifestStore) Referrers(_ context.Context, _ digest.Digest, _ string) ([]distribution.ArtifactDescriptor, error) { +func (pms proxyManifestStore) Referrers(_ context.Context, _ digest.Digest, _ string) ([]orasartifacts.Descriptor, error) { return nil, distribution.ErrUnsupported } diff --git a/registry/proxy/proxymanifeststore_test.go b/registry/proxy/proxymanifeststore_test.go index b0214071ec3..59f9521a2ae 100644 --- a/registry/proxy/proxymanifeststore_test.go +++ b/registry/proxy/proxymanifeststore_test.go @@ -19,6 +19,7 @@ import ( "github.com/distribution/distribution/v3/testutil" "github.com/docker/libtrust" "github.com/opencontainers/go-digest" + orasartifacts "github.com/oras-project/artifacts-spec/specs-go/v1" ) type statsManifest struct { @@ -61,7 +62,7 @@ func (sm statsManifest) Put(ctx context.Context, manifest distribution.Manifest, return sm.manifests.Put(ctx, manifest) } -func (sm statsManifest) Referrers(ctx context.Context, dgst digest.Digest, referrerType string) ([]distribution.ArtifactDescriptor, error) { +func (sm statsManifest) Referrers(ctx context.Context, dgst digest.Digest, referrerType string) ([]orasartifacts.Descriptor, error) { sm.stats["referrers"]++ return sm.Referrers(ctx, dgst, referrerType) } diff --git a/registry/storage/manifeststore.go b/registry/storage/manifeststore.go index 4b7a1aad1ed..a642818686c 100644 --- a/registry/storage/manifeststore.go +++ b/registry/storage/manifeststore.go @@ -162,10 +162,10 @@ func (ms *manifestStore) Put(ctx context.Context, manifest distribution.Manifest } // Referrers returns referrer manifests filtered by the given referrerType. -func (ms *manifestStore) Referrers(ctx context.Context, revision digest.Digest, referrerType string) ([]distribution.ArtifactDescriptor, error) { +func (ms *manifestStore) Referrers(ctx context.Context, revision digest.Digest, referrerType string) ([]orasartifactv1.Descriptor, error) { dcontext.GetLogger(ms.ctx).Debug("(*manifestStore).Referrers") - var referrers []distribution.ArtifactDescriptor + var referrers []orasartifactv1.Descriptor err := ms.referrersStore(ctx, revision, referrerType).Enumerate(ctx, func(referrerRevision digest.Digest) error { man, err := ms.Get(ctx, referrerRevision) @@ -184,10 +184,10 @@ func (ms *manifestStore) Referrers(ctx context.Context, revision digest.Digest, return err } desc.MediaType, _, _ = man.Payload() - referrers = append(referrers, distribution.ArtifactDescriptor{ + referrers = append(referrers, orasartifactv1.Descriptor{ MediaType: desc.MediaType, Size: desc.Size, - Digest: desc.Digest.String(), + Digest: desc.Digest, ArtifactType: orasArtifactMan.ArtifactType(), }) return nil From 592de2fdbccae6747dc0dec7b575225d7ef78139 Mon Sep 17 00:00:00 2001 From: Aviral Takkar Date: Fri, 3 Sep 2021 00:33:25 -0700 Subject: [PATCH 8/9] fix readme: subjectManifest -> subject Signed-off-by: Aviral Takkar --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7af95119682..89a1b2f4d48 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ cat < $artifactFile "size": $signatureFileSize } ], - "subjectManifest": { + "subject": { "mediaType": "$manifestMediaType", "digest": "$manifestDigest", "size": $manifestFileSize From 018d63a146068bc0b7ba3a67f2c45d679f098a4e Mon Sep 17 00:00:00 2001 From: Aviral Takkar Date: Sun, 12 Sep 2021 21:01:08 -0700 Subject: [PATCH 9/9] remove mediaType Signed-off-by: Aviral Takkar --- go.mod | 2 +- go.sum | 4 +- manifest/orasartifact/manifest.go | 30 +++---- registry/storage/manifeststore.go | 8 +- .../storage/orasartifactmanifesthandler.go | 4 +- .../artifacts-spec/specs-go/v1/descriptor.go | 80 +++++++++---------- .../artifacts-spec/specs-go/v1/manifest.go | 67 ++++++++-------- .../artifacts-spec/specs-go/v1/mediatype.go | 40 +++++----- vendor/modules.txt | 2 +- 9 files changed, 110 insertions(+), 127 deletions(-) diff --git a/go.mod b/go.mod index 3a1c01ef743..33382bceb92 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( github.com/ncw/swift v1.0.47 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.0.1 - github.com/oras-project/artifacts-spec v0.0.0-20210830204616-bd9edb3a9b43 + github.com/oras-project/artifacts-spec v0.0.0-20210910233110-813953a626ae github.com/satori/go.uuid v1.2.0 // indirect github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v0.0.3 diff --git a/go.sum b/go.sum index 74b397bb4c4..d65dec4dadf 100644 --- a/go.sum +++ b/go.sum @@ -94,8 +94,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/oras-project/artifacts-spec v0.0.0-20210830204616-bd9edb3a9b43 h1:WDAdGkL9gSREKHLV3eyIWalP9Q7Nf+MTnDvXi1X6xR4= -github.com/oras-project/artifacts-spec v0.0.0-20210830204616-bd9edb3a9b43/go.mod h1:Xch2aLzSwtkhbFFN6LUzTfLtukYvMMdXJ4oZ8O7BOdc= +github.com/oras-project/artifacts-spec v0.0.0-20210910233110-813953a626ae h1:vw1ccIckPxFFx3/wW8SaVLtvQ5viAduN+fuku8KbEfo= +github.com/oras-project/artifacts-spec v0.0.0-20210910233110-813953a626ae/go.mod h1:Xch2aLzSwtkhbFFN6LUzTfLtukYvMMdXJ4oZ8O7BOdc= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/manifest/orasartifact/manifest.go b/manifest/orasartifact/manifest.go index bdb080c1050..d3072d85c61 100644 --- a/manifest/orasartifact/manifest.go +++ b/manifest/orasartifact/manifest.go @@ -18,13 +18,6 @@ func init() { return nil, distribution.Descriptor{}, err } - if d.inner.MediaType != v1.MediaTypeArtifactManifest { - err = fmt.Errorf("if present, mediaType in ORAS artifact manifest should be '%s' not '%s'", - v1.MediaTypeArtifactManifest, d.inner.MediaType) - - return nil, distribution.Descriptor{}, err - } - dgst := digest.FromBytes(b) return d, distribution.Descriptor{Digest: dgst, Size: int64(len(b)), MediaType: v1.MediaTypeArtifactManifest}, err } @@ -57,12 +50,12 @@ func (a Manifest) References() []distribution.Descriptor { return blobs } -// SubjectManifest returns the the subject manifest this artifact references. -func (a Manifest) SubjectManifest() distribution.Descriptor { +// Subject returns the the subject manifest this artifact references. +func (a Manifest) Subject() distribution.Descriptor { return distribution.Descriptor{ - MediaType: a.inner.SubjectManifest.MediaType, - Digest: a.inner.SubjectManifest.Digest, - Size: a.inner.SubjectManifest.Size, + MediaType: a.inner.Subject.MediaType, + Digest: a.inner.Subject.Digest, + Size: a.inner.Subject.Size, } } @@ -83,6 +76,9 @@ func (d *DeserializedManifest) UnmarshalJSON(b []byte) error { if err := json.Unmarshal(d.raw, &man); err != nil { return err } + if man.ArtifactType == "" { + return errors.New("artifactType cannot be empty") + } d.inner = man return nil @@ -100,12 +96,6 @@ func (d *DeserializedManifest) MarshalJSON() ([]byte, error) { // Payload returns the raw content of the Artifact. The contents can be // used to calculate the content identifier. func (d DeserializedManifest) Payload() (string, []byte, error) { - var mediaType string - if d.inner.MediaType == "" { - mediaType = v1.MediaTypeArtifactManifest - } else { - mediaType = d.inner.MediaType - } - - return mediaType, d.raw, nil + // NOTE: This is a hack. The media type should be read from storage. + return v1.MediaTypeArtifactManifest, d.raw, nil } diff --git a/registry/storage/manifeststore.go b/registry/storage/manifeststore.go index a642818686c..ad2f674b93c 100644 --- a/registry/storage/manifeststore.go +++ b/registry/storage/manifeststore.go @@ -133,13 +133,9 @@ func (ms *manifestStore) Get(ctx context.Context, dgst digest.Digest, options .. return nil, distribution.ErrManifestVerification{fmt.Errorf("unrecognized manifest content type %s", versioned.MediaType)} } default: - switch versioned.MediaType { - case orasartifactv1.MediaTypeArtifactManifest: - return ms.orasArtifactHandler.Unmarshal(ctx, dgst, content) - } + // Assume it's an ORAS artifact manifest. + return ms.orasArtifactHandler.Unmarshal(ctx, dgst, content) } - - return nil, fmt.Errorf("unrecognized manifest schema version %d", versioned.SchemaVersion) } func (ms *manifestStore) Put(ctx context.Context, manifest distribution.Manifest, options ...distribution.ManifestServiceOption) (digest.Digest, error) { diff --git a/registry/storage/orasartifactmanifesthandler.go b/registry/storage/orasartifactmanifesthandler.go index b655e25a833..ffb8c65e1ee 100644 --- a/registry/storage/orasartifactmanifesthandler.go +++ b/registry/storage/orasartifactmanifesthandler.go @@ -94,7 +94,7 @@ func (oamh *orasArtifactManifestHandler) verifyManifest(ctx context.Context, dm } // Validate subject manifest. - subject := dm.SubjectManifest() + subject := dm.Subject() exists, err := ms.Exists(ctx, subject.Digest) if !exists || err == distribution.ErrBlobUnknown { errs = append(errs, distribution.ErrManifestBlobUnknown{Digest: subject.Digest}) @@ -113,7 +113,7 @@ func (oamh *orasArtifactManifestHandler) verifyManifest(ctx context.Context, dm // indexReferrers indexes the subject of the given revision in its referrers index store. func (oamh *orasArtifactManifestHandler) indexReferrers(ctx context.Context, dm orasartifact.DeserializedManifest, revision digest.Digest) error { artifactType := dm.ArtifactType() - subject := dm.SubjectManifest() + subject := dm.Subject() if err := oamh.referrersStore(ctx, subject.Digest, artifactType).linkBlob(ctx, distribution.Descriptor{Digest: revision}); err != nil { return err diff --git a/vendor/github.com/oras-project/artifacts-spec/specs-go/v1/descriptor.go b/vendor/github.com/oras-project/artifacts-spec/specs-go/v1/descriptor.go index dae2accc687..7e4edfd0df9 100644 --- a/vendor/github.com/oras-project/artifacts-spec/specs-go/v1/descriptor.go +++ b/vendor/github.com/oras-project/artifacts-spec/specs-go/v1/descriptor.go @@ -1,40 +1,40 @@ -// Copyright 2021 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 v1 - -import digest "github.com/opencontainers/go-digest" - -// Descriptor describes the disposition of targeted content. -type Descriptor struct { - // MediaType is the media type of the object this schema refers to. - MediaType string `json:"mediaType,omitempty"` - - // ArtifactType is the artifact type of the object this schema refers to. - // - // When the descriptor is used for blobs, this property must be empty. - ArtifactType string `json:"artifactType,omitempty"` - - // Digest is the digest of the targeted content. - Digest digest.Digest `json:"digest"` - - // Size specifies the size in bytes of the blob. - Size int64 `json:"size"` - - // URLs specifies a list of URLs from which this object MAY be downloaded - URLs []string `json:"urls,omitempty"` - - // Annotations contains arbitrary metadata relating to the targeted content. - Annotations map[string]string `json:"annotations,omitempty"` -} +// Copyright 2021 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 v1 + +import digest "github.com/opencontainers/go-digest" + +// Descriptor describes the disposition of targeted content. +type Descriptor struct { + // MediaType is the media type of the object this schema refers to. + MediaType string `json:"mediaType,omitempty"` + + // ArtifactType is the artifact type of the object this schema refers to. + // + // When the descriptor is used for blobs, this property must be empty. + ArtifactType string `json:"artifactType,omitempty"` + + // Digest is the digest of the targeted content. + Digest digest.Digest `json:"digest"` + + // Size specifies the size in bytes of the blob. + Size int64 `json:"size"` + + // URLs specifies a list of URLs from which this object MAY be downloaded + URLs []string `json:"urls,omitempty"` + + // Annotations contains arbitrary metadata relating to the targeted content. + Annotations map[string]string `json:"annotations,omitempty"` +} diff --git a/vendor/github.com/oras-project/artifacts-spec/specs-go/v1/manifest.go b/vendor/github.com/oras-project/artifacts-spec/specs-go/v1/manifest.go index 341e7f4f678..67ded906fa1 100644 --- a/vendor/github.com/oras-project/artifacts-spec/specs-go/v1/manifest.go +++ b/vendor/github.com/oras-project/artifacts-spec/specs-go/v1/manifest.go @@ -1,35 +1,32 @@ -// Copyright 2021 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 v1 - -// Manifest describes an ORAS artifact. -// This structure provides `application/vnd.oras.artifact.manifest.v1+json` mediatype when marshalled to JSON. -type Manifest struct { - // MediaType is the media type of the object this schema refers to. - MediaType string `json:"mediaType"` - - // ArtifactType is the artifact type of the object this schema refers to. - ArtifactType string `json:"artifactType"` - - // Blobs is a collection of blobs referenced by this manifest. - Blobs []Descriptor `json:"blobs"` - - // SubjectManifest is an optional reference to any existing manifest within the repository. - // When specified, the artifact is said to be dependent upon the referenced subject. - SubjectManifest Descriptor `json:"subject"` - - // Annotations contains arbitrary metadata for the artifact manifest. - Annotations map[string]string `json:"annotations,omitempty"` -} +// Copyright 2021 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 v1 + +// Manifest describes an ORAS artifact. +// This structure provides `application/vnd.oras.artifact.manifest.v1+json` mediatype when marshalled to JSON. +type Manifest struct { + // ArtifactType is the artifact type of the object this schema refers to. + ArtifactType string `json:"artifactType"` + + // Blobs is a collection of blobs referenced by this manifest. + Blobs []Descriptor `json:"blobs"` + + // Subject is an optional reference to any existing manifest within the repository. + // When specified, the artifact is said to be dependent upon the referenced subject. + Subject Descriptor `json:"subject"` + + // Annotations contains arbitrary metadata for the artifact manifest. + Annotations map[string]string `json:"annotations,omitempty"` +} diff --git a/vendor/github.com/oras-project/artifacts-spec/specs-go/v1/mediatype.go b/vendor/github.com/oras-project/artifacts-spec/specs-go/v1/mediatype.go index 52c1732325e..fd5767f7582 100644 --- a/vendor/github.com/oras-project/artifacts-spec/specs-go/v1/mediatype.go +++ b/vendor/github.com/oras-project/artifacts-spec/specs-go/v1/mediatype.go @@ -1,20 +1,20 @@ -// Copyright 2021 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 v1 - -const ( - // MediaTypeArtifactManifest specifies the media type for an ORAS artifact reference type manifest. - MediaTypeArtifactManifest = "application/vnd.cncf.oras.artifact.manifest.v1+json" -) +// Copyright 2021 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 v1 + +const ( + // MediaTypeArtifactManifest specifies the media type for an ORAS artifact reference type manifest. + MediaTypeArtifactManifest = "application/vnd.cncf.oras.artifact.manifest.v1+json" +) diff --git a/vendor/modules.txt b/vendor/modules.txt index 213d36e4af8..60706f26832 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -139,7 +139,7 @@ github.com/opencontainers/go-digest ## explicit github.com/opencontainers/image-spec/specs-go github.com/opencontainers/image-spec/specs-go/v1 -# github.com/oras-project/artifacts-spec v0.0.0-20210830204616-bd9edb3a9b43 +# github.com/oras-project/artifacts-spec v0.0.0-20210910233110-813953a626ae ## explicit github.com/oras-project/artifacts-spec/specs-go/v1 # github.com/prometheus/client_golang v1.1.0