oras.land/oras-go@v1.2.5/pkg/oras/store.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 oras
    17  
    18  import (
    19  	"context"
    20  	"io"
    21  	"io/ioutil"
    22  	"time"
    23  
    24  	"github.com/containerd/containerd/content"
    25  	"github.com/containerd/containerd/errdefs"
    26  	"github.com/containerd/containerd/remotes"
    27  	"github.com/opencontainers/go-digest"
    28  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    29  	"golang.org/x/sync/errgroup"
    30  
    31  	orascontent "oras.land/oras-go/pkg/content"
    32  )
    33  
    34  type hybridStore struct {
    35  	cache            *orascontent.Memory
    36  	cachedMediaTypes []string
    37  	cacheOnly        bool
    38  	provider         content.Provider
    39  	ingester         content.Ingester
    40  }
    41  
    42  func newHybridStoreFromPusher(pusher remotes.Pusher, cachedMediaTypes []string, cacheOnly bool) *hybridStore {
    43  	// construct an ingester from a pusher
    44  	ingester := pusherIngester{
    45  		pusher: pusher,
    46  	}
    47  	return &hybridStore{
    48  		cache:            orascontent.NewMemory(),
    49  		cachedMediaTypes: cachedMediaTypes,
    50  		ingester:         ingester,
    51  		cacheOnly:        cacheOnly,
    52  	}
    53  }
    54  
    55  func (s *hybridStore) Set(desc ocispec.Descriptor, content []byte) {
    56  	s.cache.Set(desc, content)
    57  }
    58  
    59  func (s *hybridStore) Fetch(ctx context.Context, desc ocispec.Descriptor) (io.ReadCloser, error) {
    60  	reader, err := s.cache.Fetch(ctx, desc)
    61  	if err == nil {
    62  		return reader, err
    63  	}
    64  	if s.provider != nil {
    65  		rat, err := s.provider.ReaderAt(ctx, desc)
    66  		return ioutil.NopCloser(orascontent.NewReaderAtWrapper(rat)), err
    67  	}
    68  	return nil, err
    69  }
    70  
    71  func (s *hybridStore) Push(ctx context.Context, desc ocispec.Descriptor) (content.Writer, error) {
    72  	return s.Writer(ctx, content.WithDescriptor(desc))
    73  }
    74  
    75  // Writer begins or resumes the active writer identified by desc
    76  func (s *hybridStore) Writer(ctx context.Context, opts ...content.WriterOpt) (content.Writer, error) {
    77  	var wOpts content.WriterOpts
    78  	for _, opt := range opts {
    79  		if err := opt(&wOpts); err != nil {
    80  			return nil, err
    81  		}
    82  	}
    83  
    84  	if isAllowedMediaType(wOpts.Desc.MediaType, s.cachedMediaTypes...) || s.ingester == nil {
    85  		pusher, err := s.cache.Pusher(ctx, "")
    86  		if err != nil {
    87  			return nil, err
    88  		}
    89  		cacheWriter, err := pusher.Push(ctx, wOpts.Desc)
    90  		if err != nil {
    91  			return nil, err
    92  		}
    93  		// if we cache it only, do not pass it through
    94  		if s.cacheOnly {
    95  			return cacheWriter, nil
    96  		}
    97  		ingesterWriter, err := s.ingester.Writer(ctx, opts...)
    98  		switch {
    99  		case err == nil:
   100  			return newTeeWriter(wOpts.Desc, cacheWriter, ingesterWriter), nil
   101  		case errdefs.IsAlreadyExists(err):
   102  			return cacheWriter, nil
   103  		}
   104  		return nil, err
   105  	}
   106  	return s.ingester.Writer(ctx, opts...)
   107  }
   108  
   109  // teeWriter tees the content to one or more content.Writer
   110  type teeWriter struct {
   111  	writers  []content.Writer
   112  	digester digest.Digester
   113  	status   content.Status
   114  }
   115  
   116  func newTeeWriter(desc ocispec.Descriptor, writers ...content.Writer) *teeWriter {
   117  	now := time.Now()
   118  	return &teeWriter{
   119  		writers:  writers,
   120  		digester: digest.Canonical.Digester(),
   121  		status: content.Status{
   122  			Total:     desc.Size,
   123  			StartedAt: now,
   124  			UpdatedAt: now,
   125  		},
   126  	}
   127  }
   128  
   129  func (t *teeWriter) Close() error {
   130  	g := new(errgroup.Group)
   131  	for _, w := range t.writers {
   132  		w := w // closure issues, see https://golang.org/doc/faq#closures_and_goroutines
   133  		g.Go(func() error {
   134  			return w.Close()
   135  		})
   136  	}
   137  	return g.Wait()
   138  }
   139  
   140  func (t *teeWriter) Write(p []byte) (n int, err error) {
   141  	g := new(errgroup.Group)
   142  	for _, w := range t.writers {
   143  		w := w // closure issues, see https://golang.org/doc/faq#closures_and_goroutines
   144  		g.Go(func() error {
   145  			n, err := w.Write(p[:])
   146  			if err != nil {
   147  				return err
   148  			}
   149  			if n != len(p) {
   150  				return io.ErrShortWrite
   151  			}
   152  			return nil
   153  		})
   154  	}
   155  	err = g.Wait()
   156  	n = len(p)
   157  	if err != nil {
   158  		return n, err
   159  	}
   160  	_, _ = t.digester.Hash().Write(p[:n])
   161  	t.status.Offset += int64(len(p))
   162  	t.status.UpdatedAt = time.Now()
   163  
   164  	return n, nil
   165  }
   166  
   167  // Digest may return empty digest or panics until committed.
   168  func (t *teeWriter) Digest() digest.Digest {
   169  	return t.digester.Digest()
   170  }
   171  
   172  func (t *teeWriter) Commit(ctx context.Context, size int64, expected digest.Digest, opts ...content.Opt) error {
   173  	g := new(errgroup.Group)
   174  	for _, w := range t.writers {
   175  		w := w // closure issues, see https://golang.org/doc/faq#closures_and_goroutines
   176  		g.Go(func() error {
   177  			return w.Commit(ctx, size, expected, opts...)
   178  		})
   179  	}
   180  	return g.Wait()
   181  }
   182  
   183  // Status returns the current state of write
   184  func (t *teeWriter) Status() (content.Status, error) {
   185  	return t.status, nil
   186  }
   187  
   188  // Truncate updates the size of the target blob
   189  func (t *teeWriter) Truncate(size int64) error {
   190  	g := new(errgroup.Group)
   191  	for _, w := range t.writers {
   192  		w := w // closure issues, see https://golang.org/doc/faq#closures_and_goroutines
   193  		g.Go(func() error {
   194  			return w.Truncate(size)
   195  		})
   196  	}
   197  	return g.Wait()
   198  }
   199  
   200  // pusherIngester simple wrapper to get an ingester from a pusher
   201  type pusherIngester struct {
   202  	pusher remotes.Pusher
   203  }
   204  
   205  func (p pusherIngester) Writer(ctx context.Context, opts ...content.WriterOpt) (content.Writer, error) {
   206  	var wOpts content.WriterOpts
   207  	for _, opt := range opts {
   208  		if err := opt(&wOpts); err != nil {
   209  			return nil, err
   210  		}
   211  	}
   212  	return p.pusher.Push(ctx, wOpts.Desc)
   213  }