github.com/nikkelma/oras-project_oras-go@v1.1.1-0.20220201001104-a75f6a419090/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 }