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  }