github.com/dtroyer-salad/og2/v2@v2.0.0-20240412154159-c47231610877/registry/repository.go (about) 1 /* 2 Copyright The ORAS Authors. 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 7 http://www.apache.org/licenses/LICENSE-2.0 8 9 Unless required by applicable law or agreed to in writing, software 10 distributed under the License is distributed on an "AS IS" BASIS, 11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 See the License for the specific language governing permissions and 13 limitations under the License. 14 */ 15 16 package registry 17 18 import ( 19 "context" 20 "encoding/json" 21 "fmt" 22 "io" 23 24 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 25 "oras.land/oras-go/v2/content" 26 "oras.land/oras-go/v2/errdef" 27 "oras.land/oras-go/v2/internal/descriptor" 28 "oras.land/oras-go/v2/internal/spec" 29 ) 30 31 // Repository is an ORAS target and an union of the blob and the manifest CASs. 32 // 33 // As specified by https://docs.docker.com/registry/spec/api/, it is natural to 34 // assume that content.Resolver interface only works for manifests. Tagging a 35 // blob may be resulted in an `ErrUnsupported` error. However, this interface 36 // does not restrict tagging blobs. 37 // 38 // Since a repository is an union of the blob and the manifest CASs, all 39 // operations defined in the `BlobStore` are executed depending on the media 40 // type of the given descriptor accordingly. 41 // 42 // Furthermore, this interface also provides the ability to enforce the 43 // separation of the blob and the manifests CASs. 44 type Repository interface { 45 content.Storage 46 content.Deleter 47 content.TagResolver 48 ReferenceFetcher 49 ReferencePusher 50 ReferrerLister 51 TagLister 52 53 // Blobs provides access to the blob CAS only, which contains config blobs, 54 // layers, and other generic blobs. 55 Blobs() BlobStore 56 57 // Manifests provides access to the manifest CAS only. 58 Manifests() ManifestStore 59 } 60 61 // BlobStore is a CAS with the ability to stat and delete its content. 62 type BlobStore interface { 63 content.Storage 64 content.Deleter 65 content.Resolver 66 ReferenceFetcher 67 } 68 69 // ManifestStore is a CAS with the ability to stat and delete its content. 70 // Besides, ManifestStore provides reference tagging. 71 type ManifestStore interface { 72 BlobStore 73 content.Tagger 74 ReferencePusher 75 } 76 77 // ReferencePusher provides advanced push with the tag service. 78 type ReferencePusher interface { 79 // PushReference pushes the manifest with a reference tag. 80 PushReference(ctx context.Context, expected ocispec.Descriptor, content io.Reader, reference string) error 81 } 82 83 // ReferenceFetcher provides advanced fetch with the tag service. 84 type ReferenceFetcher interface { 85 // FetchReference fetches the content identified by the reference. 86 FetchReference(ctx context.Context, reference string) (ocispec.Descriptor, io.ReadCloser, error) 87 } 88 89 // ReferrerLister provides the Referrers API. 90 // Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#listing-referrers 91 type ReferrerLister interface { 92 Referrers(ctx context.Context, desc ocispec.Descriptor, artifactType string, fn func(referrers []ocispec.Descriptor) error) error 93 } 94 95 // TagLister lists tags by the tag service. 96 type TagLister interface { 97 // Tags lists the tags available in the repository. 98 // Since the returned tag list may be paginated by the underlying 99 // implementation, a function should be passed in to process the paginated 100 // tag list. 101 // 102 // `last` argument is the `last` parameter when invoking the tags API. 103 // If `last` is NOT empty, the entries in the response start after the 104 // tag specified by `last`. Otherwise, the response starts from the top 105 // of the Tags list. 106 // 107 // Note: When implemented by a remote registry, the tags API is called. 108 // However, not all registries supports pagination or conforms the 109 // specification. 110 // 111 // References: 112 // - https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#content-discovery 113 // - https://docs.docker.com/registry/spec/api/#tags 114 // See also `Tags()` in this package. 115 Tags(ctx context.Context, last string, fn func(tags []string) error) error 116 } 117 118 // Mounter allows cross-repository blob mounts. 119 // For backward compatibility reasons, this is not implemented by 120 // BlobStore: use a type assertion to check availability. 121 type Mounter interface { 122 // Mount makes the blob with the given descriptor in fromRepo 123 // available in the repository signified by the receiver. 124 Mount(ctx context.Context, 125 desc ocispec.Descriptor, 126 fromRepo string, 127 getContent func() (io.ReadCloser, error), 128 ) error 129 } 130 131 // Tags lists the tags available in the repository. 132 func Tags(ctx context.Context, repo TagLister) ([]string, error) { 133 var res []string 134 if err := repo.Tags(ctx, "", func(tags []string) error { 135 res = append(res, tags...) 136 return nil 137 }); err != nil { 138 return nil, err 139 } 140 return res, nil 141 } 142 143 // Referrers lists the descriptors of image or artifact manifests directly 144 // referencing the given manifest descriptor. 145 // 146 // Reference: https://github.com/opencontainers/distribution-spec/blob/v1.1.0/spec.md#listing-referrers 147 func Referrers(ctx context.Context, store content.ReadOnlyGraphStorage, desc ocispec.Descriptor, artifactType string) ([]ocispec.Descriptor, error) { 148 if !descriptor.IsManifest(desc) { 149 return nil, fmt.Errorf("the descriptor %v is not a manifest: %w", desc, errdef.ErrUnsupported) 150 } 151 152 var results []ocispec.Descriptor 153 154 // use the Referrer API if it is available 155 if rf, ok := store.(ReferrerLister); ok { 156 if err := rf.Referrers(ctx, desc, artifactType, func(referrers []ocispec.Descriptor) error { 157 results = append(results, referrers...) 158 return nil 159 }); err != nil { 160 return nil, err 161 } 162 return results, nil 163 } 164 165 predecessors, err := store.Predecessors(ctx, desc) 166 if err != nil { 167 return nil, err 168 } 169 for _, node := range predecessors { 170 switch node.MediaType { 171 case ocispec.MediaTypeImageManifest: 172 fetched, err := content.FetchAll(ctx, store, node) 173 if err != nil { 174 return nil, err 175 } 176 var manifest ocispec.Manifest 177 if err := json.Unmarshal(fetched, &manifest); err != nil { 178 return nil, err 179 } 180 if manifest.Subject == nil || !content.Equal(*manifest.Subject, desc) { 181 continue 182 } 183 node.ArtifactType = manifest.ArtifactType 184 if node.ArtifactType == "" { 185 node.ArtifactType = manifest.Config.MediaType 186 } 187 node.Annotations = manifest.Annotations 188 case ocispec.MediaTypeImageIndex: 189 fetched, err := content.FetchAll(ctx, store, node) 190 if err != nil { 191 return nil, err 192 } 193 var index ocispec.Index 194 if err := json.Unmarshal(fetched, &index); err != nil { 195 return nil, err 196 } 197 if index.Subject == nil || !content.Equal(*index.Subject, desc) { 198 continue 199 } 200 node.ArtifactType = index.ArtifactType 201 node.Annotations = index.Annotations 202 case spec.MediaTypeArtifactManifest: 203 fetched, err := content.FetchAll(ctx, store, node) 204 if err != nil { 205 return nil, err 206 } 207 var artifact spec.Artifact 208 if err := json.Unmarshal(fetched, &artifact); err != nil { 209 return nil, err 210 } 211 if artifact.Subject == nil || !content.Equal(*artifact.Subject, desc) { 212 continue 213 } 214 node.ArtifactType = artifact.ArtifactType 215 node.Annotations = artifact.Annotations 216 default: 217 continue 218 } 219 if artifactType == "" || artifactType == node.ArtifactType { 220 // the field artifactType in referrers descriptor is allowed to be empty 221 // https://github.com/opencontainers/distribution-spec/issues/458 222 results = append(results, node) 223 } 224 } 225 return results, nil 226 }