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  }