cuelabs.dev/go/oci/ociregistry@v0.0.0-20240906074133-82eb438dd565/ocifilter/immutable.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 implements "filter" functions that wrap or combine ociregistry 16 // implementations in different ways. 17 package ocifilter 18 19 import ( 20 "context" 21 "fmt" 22 23 "cuelabs.dev/go/oci/ociregistry" 24 "github.com/opencontainers/go-digest" 25 ) 26 27 // Immutable returns a registry wrap r but only allows content to be 28 // added but not changed once added: nothing can be deleted and tags 29 // can't be changed. 30 func Immutable(r ociregistry.Interface) ociregistry.Interface { 31 return immutable{r} 32 } 33 34 type immutable struct { 35 ociregistry.Interface 36 } 37 38 func (r immutable) PushManifest(ctx context.Context, repo string, tag string, contents []byte, mediaType string) (ociregistry.Descriptor, error) { 39 if tag == "" { 40 return r.Interface.PushManifest(ctx, repo, tag, contents, mediaType) 41 } 42 dig := digest.FromBytes(contents) 43 44 if desc, err := r.ResolveTag(ctx, repo, tag); err == nil { 45 if desc.Digest == dig { 46 // We're trying to push exactly the same content. That's OK. 47 return desc, nil 48 } 49 return ociregistry.Descriptor{}, fmt.Errorf("this store is immutable: %w", ociregistry.ErrDenied) 50 } 51 desc, err := r.Interface.PushManifest(ctx, repo, tag, contents, mediaType) 52 if err != nil { 53 return ociregistry.Descriptor{}, err 54 } 55 // We've pushed the tag but someone else might also have pushed it at the same time. 56 // UNFORTUNATELY if there was a race, then there's a small window in time where 57 // some client might have seen the tag change underfoot. 58 desc, err = r.ResolveTag(ctx, repo, tag) 59 if err != nil { 60 return ociregistry.Descriptor{}, fmt.Errorf("cannot resolve tag that's just been pushed: %v", err) 61 } 62 if desc.Digest != dig { 63 // We lost the race. 64 return ociregistry.Descriptor{}, fmt.Errorf("this store is immutable: %w", ociregistry.ErrDenied) 65 } 66 return desc, nil 67 } 68 69 func (r immutable) DeleteBlob(ctx context.Context, repo string, digest ociregistry.Digest) error { 70 return ociregistry.ErrDenied 71 } 72 73 func (r immutable) DeleteManifest(ctx context.Context, repo string, digest ociregistry.Digest) error { 74 return ociregistry.ErrDenied 75 } 76 77 func (r immutable) DeleteTag(ctx context.Context, repo string, name string) error { 78 return ociregistry.ErrDenied 79 }