...

Source file src/cuelabs.dev/go/oci/ociregistry/ocimem/registry.go

Documentation: cuelabs.dev/go/oci/ociregistry/ocimem

     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 ocimem provides a simple in-memory implementation of
    16  // an OCI registry.
    17  package ocimem
    18  
    19  import (
    20  	"fmt"
    21  	"sync"
    22  
    23  	"cuelabs.dev/go/oci/ociregistry"
    24  	"github.com/opencontainers/go-digest"
    25  )
    26  
    27  var _ ociregistry.Interface = (*Registry)(nil)
    28  
    29  type Registry struct {
    30  	*ociregistry.Funcs
    31  	cfg   Config
    32  	mu    sync.Mutex
    33  	repos map[string]*repository
    34  }
    35  
    36  type repository struct {
    37  	tags      map[string]ociregistry.Descriptor
    38  	manifests map[ociregistry.Digest]*blob
    39  	blobs     map[ociregistry.Digest]*blob
    40  	uploads   map[string]*Buffer
    41  }
    42  
    43  type blob struct {
    44  	mediaType string
    45  	data      []byte
    46  	subject   digest.Digest
    47  }
    48  
    49  func (b *blob) descriptor() ociregistry.Descriptor {
    50  	return ociregistry.Descriptor{
    51  		MediaType: b.mediaType,
    52  		Size:      int64(len(b.data)),
    53  		Digest:    digest.FromBytes(b.data),
    54  	}
    55  }
    56  
    57  // TODO (breaking API change) rename NewWithConfig to New
    58  // so we don't have two very similar entry points.
    59  
    60  // New is like NewWithConfig(nil).
    61  func New() *Registry {
    62  	return NewWithConfig(nil)
    63  }
    64  
    65  // NewWithConfig returns a new in-memory [ociregistry.Interface]
    66  // implementation using the given configuration. If
    67  // cfg is nil, it's treated the same as a pointer to the zero [Config] value.
    68  func NewWithConfig(cfg0 *Config) *Registry {
    69  	var cfg Config
    70  	if cfg0 != nil {
    71  		cfg = *cfg0
    72  	}
    73  	return &Registry{
    74  		cfg: cfg,
    75  	}
    76  }
    77  
    78  // Config holds configuration for the registry.
    79  type Config struct {
    80  	// ImmutableTags specifies that tags in the registry cannot
    81  	// be changed. Specifically the following restrictions are enforced:
    82  	// - no removal of tags from a manifest
    83  	// - no pushing of a tag if that tag already exists with a different
    84  	// digest or media type.
    85  	// - no deletion of directly tagged manifests
    86  	// - no deletion of any blob or manifest that a tagged manifest
    87  	// refers to (TODO: not implemented yet)
    88  	ImmutableTags bool
    89  }
    90  
    91  func (r *Registry) repo(repoName string) (*repository, error) {
    92  	if repo, ok := r.repos[repoName]; ok {
    93  		return repo, nil
    94  	}
    95  	return nil, ociregistry.ErrNameUnknown
    96  }
    97  
    98  func (r *Registry) manifestForDigest(repoName string, dig ociregistry.Digest) (*blob, error) {
    99  	repo, err := r.repo(repoName)
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  	b := repo.manifests[dig]
   104  	if b == nil {
   105  		return nil, ociregistry.ErrManifestUnknown
   106  	}
   107  	return b, nil
   108  }
   109  
   110  func (r *Registry) blobForDigest(repoName string, dig ociregistry.Digest) (*blob, error) {
   111  	repo, err := r.repo(repoName)
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  	b := repo.blobs[dig]
   116  	if b == nil {
   117  		return nil, ociregistry.ErrBlobUnknown
   118  	}
   119  	return b, nil
   120  }
   121  
   122  func (r *Registry) makeRepo(repoName string) (*repository, error) {
   123  	if !ociregistry.IsValidRepoName(repoName) {
   124  		return nil, ociregistry.ErrNameInvalid
   125  	}
   126  	if r.repos == nil {
   127  		r.repos = make(map[string]*repository)
   128  	}
   129  	if repo := r.repos[repoName]; repo != nil {
   130  		return repo, nil
   131  	}
   132  	repo := &repository{
   133  		tags:      make(map[string]ociregistry.Descriptor),
   134  		manifests: make(map[digest.Digest]*blob),
   135  		blobs:     make(map[digest.Digest]*blob),
   136  		uploads:   make(map[string]*Buffer),
   137  	}
   138  	r.repos[repoName] = repo
   139  	return repo, nil
   140  }
   141  
   142  // SHA256("")
   143  const emptyHash = "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
   144  
   145  // CheckDescriptor checks that the given descriptor matches the given data or,
   146  // if data is nil, that the descriptor looks sane.
   147  func CheckDescriptor(desc ociregistry.Descriptor, data []byte) error {
   148  	if err := desc.Digest.Validate(); err != nil {
   149  		return fmt.Errorf("invalid digest: %v", err)
   150  	}
   151  	if data != nil {
   152  		if digest.FromBytes(data) != desc.Digest {
   153  			return fmt.Errorf("digest mismatch")
   154  		}
   155  		if desc.Size != int64(len(data)) {
   156  			return fmt.Errorf("size mismatch")
   157  		}
   158  	} else {
   159  		if desc.Size == 0 && desc.Digest != emptyHash {
   160  			return fmt.Errorf("zero sized content with mismatching digest")
   161  		}
   162  	}
   163  	if desc.MediaType == "" {
   164  		return fmt.Errorf("no media type in descriptor")
   165  	}
   166  	return nil
   167  }
   168  

View as plain text