github.com/sealerio/sealer@v0.11.1-0.20240507115618-f4f89c5853ae/pkg/image/save/distributionpkg/proxy/proxyblobstore.go (about) 1 // Copyright © 2021 https://github.com/distribution/distribution 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 proxy 16 17 import ( 18 "context" 19 "io" 20 "net/http" 21 "strconv" 22 "sync" 23 24 "github.com/distribution/distribution/v3" 25 dcontext "github.com/distribution/distribution/v3/context" 26 "github.com/distribution/distribution/v3/reference" 27 "github.com/distribution/distribution/v3/registry/proxy/scheduler" 28 "github.com/opencontainers/go-digest" 29 ) 30 31 type proxyBlobStore struct { 32 localStore distribution.BlobStore 33 remoteStore distribution.BlobService 34 scheduler *scheduler.TTLExpirationScheduler 35 repositoryName reference.Named 36 authChallenger authChallenger 37 } 38 39 var _ distribution.BlobStore = &proxyBlobStore{} 40 41 // inflight tracks currently downloading blobs 42 var inflight = make(map[digest.Digest]struct{}) 43 44 // mu protects inflight 45 var mu sync.Mutex 46 47 func setResponseHeaders(w http.ResponseWriter, length int64, mediaType string, digest digest.Digest) { 48 w.Header().Set("Content-Length", strconv.FormatInt(length, 10)) 49 w.Header().Set("Content-Type", mediaType) 50 w.Header().Set("Docker-Content-Digest", digest.String()) 51 w.Header().Set("Etag", digest.String()) 52 } 53 54 func (pbs *proxyBlobStore) copyContent(ctx context.Context, dgst digest.Digest, writer io.Writer) (distribution.Descriptor, error) { 55 desc, err := pbs.remoteStore.Stat(ctx, dgst) 56 if err != nil { 57 return distribution.Descriptor{}, err 58 } 59 60 if w, ok := writer.(http.ResponseWriter); ok { 61 setResponseHeaders(w, desc.Size, desc.MediaType, dgst) 62 } 63 64 remoteReader, err := pbs.remoteStore.Open(ctx, dgst) 65 if err != nil { 66 return distribution.Descriptor{}, err 67 } 68 69 defer remoteReader.Close() 70 71 _, err = io.CopyN(writer, remoteReader, desc.Size) 72 if err != nil { 73 return distribution.Descriptor{}, err 74 } 75 76 proxyMetrics.BlobPush(uint64(desc.Size)) 77 78 return desc, nil 79 } 80 81 func (pbs *proxyBlobStore) serveLocal(ctx context.Context, w http.ResponseWriter, r *http.Request, dgst digest.Digest) (bool, error) { 82 localDesc, err := pbs.localStore.Stat(ctx, dgst) 83 if err != nil { 84 // Stat can report a zero sized file here if it's checked between creation 85 // and population. Return nil error, and continue 86 return false, err 87 } 88 89 proxyMetrics.BlobPush(uint64(localDesc.Size)) 90 return true, pbs.localStore.ServeBlob(ctx, w, r, dgst) 91 } 92 93 func (pbs *proxyBlobStore) storeLocal(ctx context.Context, dgst digest.Digest) error { 94 defer func() { 95 mu.Lock() 96 delete(inflight, dgst) 97 mu.Unlock() 98 }() 99 100 var desc distribution.Descriptor 101 var err error 102 var bw distribution.BlobWriter 103 104 bw, err = pbs.localStore.Create(ctx) 105 if err != nil { 106 return err 107 } 108 109 desc, err = pbs.copyContent(ctx, dgst, bw) 110 if err != nil { 111 return err 112 } 113 114 _, err = bw.Commit(ctx, desc) 115 if err != nil { 116 return err 117 } 118 119 return nil 120 } 121 122 func (pbs *proxyBlobStore) ServeBlob(ctx context.Context, w http.ResponseWriter, r *http.Request, dgst digest.Digest) error { 123 served, err := pbs.serveLocal(ctx, w, r, dgst) 124 if err != nil { 125 dcontext.GetLogger(ctx).Errorf("Error serving blob from local storage: %s", err.Error()) 126 return err 127 } 128 129 if served { 130 return nil 131 } 132 133 if err := pbs.authChallenger.tryEstablishChallenges(ctx); err != nil { 134 return err 135 } 136 137 mu.Lock() 138 if _, ok := inflight[dgst]; ok { 139 mu.Unlock() 140 _, err := pbs.copyContent(ctx, dgst, w) 141 return err 142 } 143 inflight[dgst] = struct{}{} 144 mu.Unlock() 145 146 go func(dgst digest.Digest) { 147 if err := pbs.storeLocal(ctx, dgst); err != nil { 148 dcontext.GetLogger(ctx).Errorf("Error committing to storage: %s", err.Error()) 149 } 150 151 blobRef, err := reference.WithDigest(pbs.repositoryName, dgst) 152 if err != nil { 153 dcontext.GetLogger(ctx).Errorf("Error creating reference: %s", err) 154 return 155 } 156 157 _ = pbs.scheduler.AddBlob(blobRef, repositoryTTL) 158 }(dgst) 159 160 _, err = pbs.copyContent(ctx, dgst, w) 161 if err != nil { 162 return err 163 } 164 return nil 165 } 166 167 func (pbs *proxyBlobStore) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) { 168 desc, err := pbs.localStore.Stat(ctx, dgst) 169 if err == nil { 170 return desc, nil 171 } 172 173 return distribution.Descriptor{}, err 174 } 175 176 func (pbs *proxyBlobStore) Get(ctx context.Context, dgst digest.Digest) ([]byte, error) { 177 blob, err := pbs.localStore.Get(ctx, dgst) 178 if err == nil { 179 return blob, nil 180 } 181 if err := pbs.authChallenger.tryEstablishChallenges(ctx); err != nil { 182 return []byte{}, err 183 } 184 185 blob, err = pbs.remoteStore.Get(ctx, dgst) 186 if err != nil { 187 return []byte{}, err 188 } 189 190 _, err = pbs.localStore.Put(ctx, "", blob) 191 if err != nil { 192 return []byte{}, err 193 } 194 return blob, nil 195 } 196 197 func (pbs *proxyBlobStore) Open(ctx context.Context, dgst digest.Digest) (distribution.ReadSeekCloser, error) { 198 if err := pbs.authChallenger.tryEstablishChallenges(ctx); err != nil { 199 return nil, err 200 } 201 202 reader, err := pbs.remoteStore.Open(ctx, dgst) 203 if err != nil { 204 return nil, err 205 } 206 return reader, nil 207 } 208 209 func (pbs *proxyBlobStore) Put(ctx context.Context, mediaType string, p []byte) (distribution.Descriptor, error) { 210 desc, err := pbs.localStore.Put(ctx, "", p) 211 if err != nil { 212 return distribution.Descriptor{}, err 213 } 214 return desc, nil 215 } 216 217 // Unsupported functions 218 func (pbs *proxyBlobStore) Create(ctx context.Context, options ...distribution.BlobCreateOption) (distribution.BlobWriter, error) { 219 return pbs.localStore.Create(ctx, options...) 220 } 221 222 func (pbs *proxyBlobStore) Resume(ctx context.Context, id string) (distribution.BlobWriter, error) { 223 return nil, distribution.ErrUnsupported 224 } 225 226 func (pbs *proxyBlobStore) Mount(ctx context.Context, sourceRepo reference.Named, dgst digest.Digest) (distribution.Descriptor, error) { 227 return distribution.Descriptor{}, distribution.ErrUnsupported 228 } 229 230 func (pbs *proxyBlobStore) Delete(ctx context.Context, dgst digest.Digest) error { 231 return distribution.ErrUnsupported 232 }