cuelabs.dev/go/oci/ociregistry@v0.0.0-20240906074133-82eb438dd565/ocifilter/sub.go (about) 1 // Copyright 2023 CUE Labs AG 2 // 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 package ocifilter 16 17 import ( 18 "context" 19 "io" 20 "path" 21 "strings" 22 23 "cuelabs.dev/go/oci/ociregistry" 24 "cuelabs.dev/go/oci/ociregistry/ociauth" 25 ) 26 27 // Sub returns r wrapped so that it addresses only 28 // repositories within pathPrefix. 29 // 30 // The prefix must match an entire path element so, 31 // for example, if the prefix is "foo", "foo" and "foo/a" will 32 // be included, but "foobie" will not. 33 // 34 // For example, if r has the following repositories: 35 // 36 // a 37 // a/b/c 38 // a/d 39 // x/p 40 // aa/b 41 // 42 // then Sub(r "a") will return a registry containing the following repositories: 43 // 44 // b/c 45 // d 46 func Sub(r ociregistry.Interface, pathPrefix string) ociregistry.Interface { 47 if pathPrefix == "" { 48 return r 49 } 50 return &subRegistry{ 51 prefix: pathPrefix, 52 r: r, 53 } 54 } 55 56 // TODO adjust any auth scopes in the context as they pass through. 57 58 type subRegistry struct { 59 *ociregistry.Funcs 60 prefix string 61 r ociregistry.Interface 62 } 63 64 func (r *subRegistry) GetBlob(ctx context.Context, repo string, digest ociregistry.Digest) (ociregistry.BlobReader, error) { 65 ctx = r.mapScopes(ctx) 66 return r.r.GetBlob(ctx, r.repo(repo), digest) 67 } 68 69 func (r *subRegistry) GetBlobRange(ctx context.Context, repo string, digest ociregistry.Digest, offset0, offset1 int64) (ociregistry.BlobReader, error) { 70 ctx = r.mapScopes(ctx) 71 return r.r.GetBlobRange(ctx, r.repo(repo), digest, offset0, offset1) 72 } 73 74 func (r *subRegistry) GetManifest(ctx context.Context, repo string, digest ociregistry.Digest) (ociregistry.BlobReader, error) { 75 ctx = r.mapScopes(ctx) 76 return r.r.GetManifest(ctx, r.repo(repo), digest) 77 } 78 79 func (r *subRegistry) GetTag(ctx context.Context, repo string, tagName string) (ociregistry.BlobReader, error) { 80 ctx = r.mapScopes(ctx) 81 return r.r.GetTag(ctx, r.repo(repo), tagName) 82 } 83 84 func (r *subRegistry) ResolveBlob(ctx context.Context, repo string, digest ociregistry.Digest) (ociregistry.Descriptor, error) { 85 ctx = r.mapScopes(ctx) 86 return r.r.ResolveBlob(ctx, r.repo(repo), digest) 87 } 88 89 func (r *subRegistry) ResolveManifest(ctx context.Context, repo string, digest ociregistry.Digest) (ociregistry.Descriptor, error) { 90 ctx = r.mapScopes(ctx) 91 return r.r.ResolveManifest(ctx, r.repo(repo), digest) 92 } 93 94 func (r *subRegistry) ResolveTag(ctx context.Context, repo string, tagName string) (ociregistry.Descriptor, error) { 95 ctx = r.mapScopes(ctx) 96 return r.r.ResolveTag(ctx, r.repo(repo), tagName) 97 } 98 99 func (r *subRegistry) PushBlob(ctx context.Context, repo string, desc ociregistry.Descriptor, rd io.Reader) (ociregistry.Descriptor, error) { 100 ctx = r.mapScopes(ctx) 101 return r.r.PushBlob(ctx, r.repo(repo), desc, rd) 102 } 103 104 func (r *subRegistry) PushBlobChunked(ctx context.Context, repo string, chunkSize int) (ociregistry.BlobWriter, error) { 105 ctx = r.mapScopes(ctx) 106 // Luckily the context spans the entire lifetime of the blob writer (no 107 // BlobWriter methods take a Context argument, so no need 108 // to wrap it. 109 return r.r.PushBlobChunked(ctx, r.repo(repo), chunkSize) 110 } 111 112 func (r *subRegistry) PushBlobChunkedResume(ctx context.Context, repo, id string, offset int64, chunkSize int) (ociregistry.BlobWriter, error) { 113 ctx = r.mapScopes(ctx) 114 return r.r.PushBlobChunkedResume(ctx, r.repo(repo), id, offset, chunkSize) 115 } 116 117 func (r *subRegistry) MountBlob(ctx context.Context, fromRepo, toRepo string, digest ociregistry.Digest) (ociregistry.Descriptor, error) { 118 ctx = r.mapScopes(ctx) 119 return r.r.MountBlob(ctx, r.repo(fromRepo), r.repo(toRepo), digest) 120 } 121 122 func (r *subRegistry) PushManifest(ctx context.Context, repo string, tag string, contents []byte, mediaType string) (ociregistry.Descriptor, error) { 123 ctx = r.mapScopes(ctx) 124 return r.r.PushManifest(ctx, r.repo(repo), tag, contents, mediaType) 125 } 126 127 func (r *subRegistry) DeleteBlob(ctx context.Context, repo string, digest ociregistry.Digest) error { 128 ctx = r.mapScopes(ctx) 129 return r.r.DeleteBlob(ctx, r.repo(repo), digest) 130 } 131 132 func (r *subRegistry) DeleteManifest(ctx context.Context, repo string, digest ociregistry.Digest) error { 133 ctx = r.mapScopes(ctx) 134 return r.r.DeleteManifest(ctx, r.repo(repo), digest) 135 } 136 137 func (r *subRegistry) DeleteTag(ctx context.Context, repo string, name string) error { 138 ctx = r.mapScopes(ctx) 139 return r.r.DeleteTag(ctx, r.repo(repo), name) 140 } 141 142 func (r *subRegistry) Repositories(ctx context.Context, startAfter string) ociregistry.Seq[string] { 143 ctx = r.mapScopes(ctx) 144 p := r.prefix + "/" 145 return func(yield func(string, error) bool) { 146 // TODO(go1.23): for name, err := range r.r.Repositories(ctx) 147 r.r.Repositories(ctx, startAfter)(func(repo string, err error) bool { 148 if err != nil { 149 yield("", err) 150 return false 151 } 152 if p, ok := strings.CutPrefix(repo, p); ok { 153 return yield(p, nil) 154 } 155 return true 156 }) 157 } 158 } 159 160 func (r *subRegistry) Tags(ctx context.Context, repo, startAfter string) ociregistry.Seq[string] { 161 ctx = r.mapScopes(ctx) 162 return r.r.Tags(ctx, r.repo(repo), startAfter) 163 } 164 165 func (r *subRegistry) Referrers(ctx context.Context, repo string, digest ociregistry.Digest, artifactType string) ociregistry.Seq[ociregistry.Descriptor] { 166 ctx = r.mapScopes(ctx) 167 return r.r.Referrers(ctx, r.repo(repo), digest, artifactType) 168 } 169 170 // mapScopes changes any auth scopes in the context so that 171 // they refer to the prefixed names rather than the originals. 172 func (r *subRegistry) mapScopes(ctx context.Context) context.Context { 173 scope := ociauth.ScopeFromContext(ctx) 174 if scope.IsEmpty() { 175 return ctx 176 } 177 // TODO we could potentially provide a Scope constructor 178 // that took an iterator, which could avoid the intermediate 179 // slice allocation. 180 scopes := make([]ociauth.ResourceScope, 0, scope.Len()) 181 scope.Iter()(func(rs ociauth.ResourceScope) bool { 182 if rs.ResourceType == ociauth.TypeRepository { 183 rs.Resource = r.repo(rs.Resource) 184 } 185 scopes = append(scopes, rs) 186 return true 187 }) 188 return ociauth.ContextWithScope(ctx, ociauth.NewScope(scopes...)) 189 } 190 191 func (r *subRegistry) repo(name string) string { 192 if name == "" { 193 // An empty repository name isn't allowed, so keep it 194 // like that so that the underlying registry will reject the 195 // empty name. 196 return "" 197 } 198 return path.Join(r.prefix, name) 199 }