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