github.com/uber/kraken@v0.1.4/lib/dockerregistry/transfer/rw_transferer.go (about) 1 // Copyright (c) 2016-2019 Uber Technologies, Inc. 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 package transfer 15 16 import ( 17 "fmt" 18 "os" 19 20 "github.com/uber/kraken/build-index/tagclient" 21 "github.com/uber/kraken/core" 22 "github.com/uber/kraken/lib/store" 23 "github.com/uber/kraken/origin/blobclient" 24 "github.com/uber/kraken/utils/log" 25 26 "github.com/docker/distribution/uuid" 27 "github.com/uber-go/tally" 28 ) 29 30 // ReadWriteTransferer is a Transferer for proxy. Uploads/downloads blobs via the 31 // local origin cluster, and posts/gets tags via the local build-index. 32 type ReadWriteTransferer struct { 33 stats tally.Scope 34 tags tagclient.Client 35 originCluster blobclient.ClusterClient 36 cas *store.CAStore 37 } 38 39 // NewReadWriteTransferer creates a new ReadWriteTransferer. 40 func NewReadWriteTransferer( 41 stats tally.Scope, 42 tags tagclient.Client, 43 originCluster blobclient.ClusterClient, 44 cas *store.CAStore) *ReadWriteTransferer { 45 46 stats = stats.Tagged(map[string]string{ 47 "module": "rwtransferer", 48 }) 49 50 return &ReadWriteTransferer{stats, tags, originCluster, cas} 51 } 52 53 // Stat returns blob info from origin cluster or local cache. 54 func (t *ReadWriteTransferer) Stat(namespace string, d core.Digest) (*core.BlobInfo, error) { 55 fi, err := t.cas.GetCacheFileStat(d.Hex()) 56 if err != nil { 57 if os.IsNotExist(err) { 58 return t.originStat(namespace, d) 59 } 60 return nil, fmt.Errorf("stat cache file: %s", err) 61 } 62 return core.NewBlobInfo(fi.Size()), nil 63 } 64 65 func (t *ReadWriteTransferer) originStat(namespace string, d core.Digest) (*core.BlobInfo, error) { 66 bi, err := t.originCluster.Stat(namespace, d) 67 if err != nil { 68 // `docker push` stats blobs before uploading them. If the blob is not 69 // found, it will upload it. However if remote blob storage is unavailable, 70 // this will be a 5XX error, and will short-circuit push. We must consider 71 // this class of error to be a 404 to allow pushes to succeed while remote 72 // storage is down (write-back will eventually persist the blobs). 73 if err != blobclient.ErrBlobNotFound { 74 log.With("digest", d).Info("Error stat-ing origin blob: %s", err) 75 } 76 return nil, ErrBlobNotFound 77 } 78 return bi, nil 79 } 80 81 // Download downloads the blob of name into the file store and returns a reader 82 // to the newly downloaded file. 83 func (t *ReadWriteTransferer) Download(namespace string, d core.Digest) (store.FileReader, error) { 84 blob, err := t.cas.GetCacheFileReader(d.Hex()) 85 if err != nil { 86 if os.IsNotExist(err) { 87 return t.downloadFromOrigin(namespace, d) 88 } 89 return nil, fmt.Errorf("get cache file: %s", err) 90 } 91 return blob, nil 92 } 93 94 func (t *ReadWriteTransferer) downloadFromOrigin(namespace string, d core.Digest) (store.FileReader, error) { 95 tmp := fmt.Sprintf("%s.%s", d.Hex(), uuid.Generate().String()) 96 if err := t.cas.CreateUploadFile(tmp, 0); err != nil { 97 return nil, fmt.Errorf("create upload file: %s", err) 98 } 99 w, err := t.cas.GetUploadFileReadWriter(tmp) 100 if err != nil { 101 return nil, fmt.Errorf("get upload writer: %s", err) 102 } 103 defer w.Close() 104 if err := t.originCluster.DownloadBlob(namespace, d, w); err != nil { 105 if err == blobclient.ErrBlobNotFound { 106 return nil, ErrBlobNotFound 107 } 108 return nil, fmt.Errorf("origin: %s", err) 109 } 110 if err := t.cas.MoveUploadFileToCache(tmp, d.Hex()); err != nil && !os.IsExist(err) { 111 return nil, fmt.Errorf("move upload file to cache: %s", err) 112 } 113 blob, err := t.cas.GetCacheFileReader(d.Hex()) 114 if err != nil { 115 return nil, fmt.Errorf("get cache file: %s", err) 116 } 117 return blob, nil 118 } 119 120 // Upload uploads blob to the origin cluster. 121 func (t *ReadWriteTransferer) Upload( 122 namespace string, d core.Digest, blob store.FileReader) error { 123 124 return t.originCluster.UploadBlob(namespace, d, blob) 125 } 126 127 // GetTag returns the manifest digest for tag. 128 func (t *ReadWriteTransferer) GetTag(tag string) (core.Digest, error) { 129 d, err := t.tags.Get(tag) 130 if err != nil { 131 if err == tagclient.ErrTagNotFound { 132 return core.Digest{}, ErrTagNotFound 133 } 134 return core.Digest{}, fmt.Errorf("client get tag: %s", err) 135 } 136 return d, nil 137 } 138 139 // PutTag uploads d as the manifest digest for tag. 140 func (t *ReadWriteTransferer) PutTag(tag string, d core.Digest) error { 141 if err := t.tags.PutAndReplicate(tag, d); err != nil { 142 t.stats.Counter("put_tag_error").Inc(1) 143 return fmt.Errorf("put and replicate tag: %s", err) 144 } 145 return nil 146 } 147 148 // ListTags lists all tags with prefix. 149 func (t *ReadWriteTransferer) ListTags(prefix string) ([]string, error) { 150 return t.tags.List(prefix) 151 }