diff --git a/registry/storage/ociartifactmanifesthandler.go b/registry/storage/ociartifactmanifesthandler.go index 287d333163a..37c3d39239f 100644 --- a/registry/storage/ociartifactmanifesthandler.go +++ b/registry/storage/ociartifactmanifesthandler.go @@ -7,15 +7,17 @@ import ( "github.com/distribution/distribution/v3" dcontext "github.com/distribution/distribution/v3/context" "github.com/distribution/distribution/v3/manifest/ociartifact" + "github.com/distribution/distribution/v3/registry/storage/driver" "github.com/opencontainers/go-digest" v1 "github.com/opencontainers/image-spec/specs-go/v1" ) // ociArtifactManifestHandler is a ManifestHandler that covers oci artifact manifests. type ociArtifactManifestHandler struct { - repository distribution.Repository - blobStore distribution.BlobStore - ctx context.Context + repository distribution.Repository + blobStore distribution.BlobStore + ctx context.Context + storageDriver driver.StorageDriver } var _ ManifestHandler = &ociArtifactManifestHandler{} @@ -54,6 +56,11 @@ func (ms *ociArtifactManifestHandler) Put(ctx context.Context, manifest distribu return "", err } + err = ms.indexReferrers(ctx, m, revision.Digest) + if err != nil { + dcontext.GetLogger(ctx).Errorf("error indexing referrers: %v", err) + return "", err + } return revision.Digest, nil } @@ -112,3 +119,20 @@ func (ms *ociArtifactManifestHandler) verifyArtifactManifest(ctx context.Context return nil } + +// indexReferrers indexes the subject of the given revision in its referrers index store. +func (ms *ociArtifactManifestHandler) indexReferrers(ctx context.Context, dm *ociartifact.DeserializedManifest, revision digest.Digest) error { + if dm.Subject == nil { + return nil + } + + // [TODO] We can use artifact type in the link path to support filtering by artifact type + // but need to consider the max path length in different os + subjectRevision := dm.Subject.Digest + + referrersLinkPath, err := pathFor(referrersLinkPathSpec{name: ms.repository.Named().Name(), revision: revision, subjectRevision: subjectRevision}) + if err != nil { + return fmt.Errorf("failed to generate referrers link path for %v", revision) + } + return ms.storageDriver.PutContent(ctx, referrersLinkPath, []byte(revision.String())) +} diff --git a/registry/storage/paths.go b/registry/storage/paths.go index 4e9ad0856e8..cb9e73d126a 100644 --- a/registry/storage/paths.go +++ b/registry/storage/paths.go @@ -23,25 +23,29 @@ const ( // // The path layout in the storage backend is roughly as follows: // -// /v2 -// -> repositories/ -// ->/ -// -> _manifests/ -// revisions -// -> -// -> link -// tags/ -// -> current/link -// -> index -// -> //link -// -> _layers/ -// -// -> _uploads/ -// data -// startedat -// hashstates// -// -> blob/ -// +// /v2 +// -> repositories/ +// ->/ +// -> _manifests/ +// revisions +// -> +// -> link +// tags/ +// -> current/link +// -> index +// -> //link +// -> _layers/ +// +// -> _uploads/ +// data +// startedat +// hashstates// +// -> _referrers/subjects +// -> +// -> /link +// +// -> blob/ +// // // The storage backend layout is broken up into a content-addressable blob // store and repositories. The content-addressable blob store holds most data @@ -71,36 +75,40 @@ const ( // // Manifests: // -// manifestRevisionsPathSpec: /v2/repositories//_manifests/revisions/ -// manifestRevisionPathSpec: /v2/repositories//_manifests/revisions/// -// manifestRevisionLinkPathSpec: /v2/repositories//_manifests/revisions///link +// manifestRevisionsPathSpec: /v2/repositories//_manifests/revisions/ +// manifestRevisionPathSpec: /v2/repositories//_manifests/revisions/// +// manifestRevisionLinkPathSpec: /v2/repositories//_manifests/revisions///link // // Tags: // -// manifestTagsPathSpec: /v2/repositories//_manifests/tags/ -// manifestTagPathSpec: /v2/repositories//_manifests/tags// -// manifestTagCurrentPathSpec: /v2/repositories//_manifests/tags//current/link -// manifestTagIndexPathSpec: /v2/repositories//_manifests/tags//index/ -// manifestTagIndexEntryPathSpec: /v2/repositories//_manifests/tags//index/// -// manifestTagIndexEntryLinkPathSpec: /v2/repositories//_manifests/tags//index///link +// manifestTagsPathSpec: /v2/repositories//_manifests/tags/ +// manifestTagPathSpec: /v2/repositories//_manifests/tags// +// manifestTagCurrentPathSpec: /v2/repositories//_manifests/tags//current/link +// manifestTagIndexPathSpec: /v2/repositories//_manifests/tags//index/ +// manifestTagIndexEntryPathSpec: /v2/repositories//_manifests/tags//index/// +// manifestTagIndexEntryLinkPathSpec: /v2/repositories//_manifests/tags//index///link // -// Blobs: +// Blobs: // -// layerLinkPathSpec: /v2/repositories//_layers///link -// layersPathSpec: /v2/repositories//_layers +// layerLinkPathSpec: /v2/repositories//_layers///link +// layersPathSpec: /v2/repositories//_layers // // Uploads: // -// uploadDataPathSpec: /v2/repositories//_uploads//data -// uploadStartedAtPathSpec: /v2/repositories//_uploads//startedat -// uploadHashStatePathSpec: /v2/repositories//_uploads//hashstates// +// uploadDataPathSpec: /v2/repositories//_uploads//data +// uploadStartedAtPathSpec: /v2/repositories//_uploads//startedat +// uploadHashStatePathSpec: /v2/repositories//_uploads//hashstates// +// +// Referrers: +// +// referrersLinkPathSpec: /v2/repositories//_referrers/subjects/////link // // Blob Store: // // blobsPathSpec: /v2/blobs/ -// blobPathSpec: /v2/blobs/// -// blobDataPathSpec: /v2/blobs////data -// blobMediaTypePathSpec: /v2/blobs////data +// blobPathSpec: /v2/blobs/// +// blobDataPathSpec: /v2/blobs////data +// blobMediaTypePathSpec: /v2/blobs////data // // For more information on the semantic meaning of each path and their // contents, please see the path spec documentation. @@ -242,6 +250,20 @@ func pathFor(spec pathSpec) (string, error) { return path.Join(append(repoPrefix, v.name, "_uploads", v.id, "hashstates", string(v.alg), offset)...), nil case repositoriesRootPathSpec: return path.Join(repoPrefix...), nil + case referrersLinkPathSpec: + + revisionComponents, err := digestPathComponents(v.revision, false) + if err != nil { + return "", err + } + + subjectComponents, err := digestPathComponents(v.subjectRevision, false) + if err != nil { + return "", err + } + referrersRootPath := append(repoPrefix, v.name, "_referrers", "subjects") + referrersComponentPath := append(append(referrersRootPath, revisionComponents...), subjectComponents...) + return path.Join(append(referrersComponentPath, "link")...), nil default: // TODO(sday): This is an internal error. Ensure it doesn't escape (panic?). return "", fmt.Errorf("unknown path spec: %#v", v) @@ -349,11 +371,11 @@ func (layersPathSpec) pathSpec() {} // blob id. The blob link will contain a content addressable blob id reference // into the blob store. The format of the contents is as follows: // -// : +// : // // The following example of the file contents is more illustrative: // -// sha256:96443a84ce518ac22acb2e985eda402b58ac19ce6f91980bde63726a79d80b36 +// sha256:96443a84ce518ac22acb2e985eda402b58ac19ce6f91980bde63726a79d80b36 // // This indicates that there is a blob with the id/digest, calculated via // sha256 that can be fetched from the blob store. @@ -436,16 +458,24 @@ type repositoriesRootPathSpec struct { func (repositoriesRootPathSpec) pathSpec() {} +// referrersLinkPathSpec defines the link path of a referrer. +type referrersLinkPathSpec struct { + name string + revision digest.Digest + subjectRevision digest.Digest +} + +func (referrersLinkPathSpec) pathSpec() {} + // digestPathComponents provides a consistent path breakdown for a given // digest. For a generic digest, it will be as follows: // -// / +// / // // If multilevel is true, the first two bytes of the digest will separate // groups of digest folder. It will be as follows: // -// // -// +// // func digestPathComponents(dgst digest.Digest, multilevel bool) ([]string, error) { if err := dgst.Validate(); err != nil { return nil, err diff --git a/registry/storage/paths_test.go b/registry/storage/paths_test.go index 68fca59e517..c2f1be57f1c 100644 --- a/registry/storage/paths_test.go +++ b/registry/storage/paths_test.go @@ -87,6 +87,13 @@ func TestPathMapper(t *testing.T) { spec: layersPathSpec{name: "foo/bar"}, expected: "/docker/registry/v2/repositories/foo/bar/_layers", }, + { + spec: referrersLinkPathSpec{ + name: "bar", + revision: "sha256:abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789", + subjectRevision: "sha256:6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b"}, + expected: "/docker/registry/v2/repositories/bar/_referrers/subjects/sha256/abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789/sha256/6c3c624b58dbbcd3c0dd82b4c53f04194d1247c6eebdaab7c610cf7d66709b3b/link", + }, } { p, err := pathFor(testcase.spec) if err != nil { diff --git a/registry/storage/registry.go b/registry/storage/registry.go index 679119bf0ec..de316845dbc 100644 --- a/registry/storage/registry.go +++ b/registry/storage/registry.go @@ -289,9 +289,10 @@ func (repo *repository) Manifests(ctx context.Context, options ...distribution.M manifestURLs: repo.registry.manifestURLs, }, ociartifactHandler: &ociArtifactManifestHandler{ - repository: repo, - blobStore: blobStore, - ctx: ctx, + repository: repo, + blobStore: blobStore, + ctx: ctx, + storageDriver: repo.driver, }, }