github.com/uber/kraken@v0.1.4/lib/backend/registrybackend/blobclient.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 registrybackend 15 16 import ( 17 "errors" 18 "fmt" 19 "io" 20 "net/http" 21 "strconv" 22 23 "github.com/uber/kraken/core" 24 "github.com/uber/kraken/lib/backend" 25 "github.com/uber/kraken/lib/backend/backenderrors" 26 "github.com/uber/kraken/lib/backend/registrybackend/security" 27 "github.com/uber/kraken/utils/httputil" 28 "github.com/uber/kraken/utils/log" 29 yaml "gopkg.in/yaml.v2" 30 ) 31 32 const _registryblob = "registry_blob" 33 34 func init() { 35 backend.Register(_registryblob, &blobClientFactory{}) 36 } 37 38 type blobClientFactory struct{} 39 40 func (f *blobClientFactory) Create( 41 confRaw interface{}, authConfRaw interface{}) (backend.Client, error) { 42 43 confBytes, err := yaml.Marshal(confRaw) 44 if err != nil { 45 return nil, errors.New("marshal hdfs config") 46 } 47 var config Config 48 if err := yaml.Unmarshal(confBytes, &config); err != nil { 49 return nil, errors.New("unmarshal hdfs config") 50 } 51 return NewBlobClient(config) 52 } 53 54 const _layerquery = "http://%s/v2/%s/blobs/sha256:%s" 55 const _manifestquery = "http://%s/v2/%s/manifests/sha256:%s" 56 57 // BlobClient stats and downloads blob from registry. 58 type BlobClient struct { 59 config Config 60 authenticator security.Authenticator 61 } 62 63 // NewBlobClient creates a new BlobClient. 64 func NewBlobClient(config Config) (*BlobClient, error) { 65 config = config.applyDefaults() 66 authenticator, err := security.NewAuthenticator(config.Address, config.Security) 67 if err != nil { 68 return nil, fmt.Errorf("cannot create tag client authenticator: %s", err) 69 } 70 return &BlobClient{ 71 config: config, 72 authenticator: authenticator, 73 }, nil 74 } 75 76 // Stat sends a HEAD request to registry for a blob and returns the blob size. 77 func (c *BlobClient) Stat(namespace, name string) (*core.BlobInfo, error) { 78 opts, err := c.authenticator.Authenticate(namespace) 79 if err != nil { 80 return nil, fmt.Errorf("get security opt: %s", err) 81 } 82 83 info, err := c.statHelper(namespace, name, _layerquery, opts) 84 if err != nil && err == backenderrors.ErrBlobNotFound { 85 // Docker registry does not support querying manifests with blob path. 86 log.Infof("Blob %s unknown to registry. Tring to stat manifest instead", name) 87 info, err = c.statHelper(namespace, name, _manifestquery, opts) 88 } 89 return info, err 90 } 91 92 // Download gets a blob from registry. 93 func (c *BlobClient) Download(namespace, name string, dst io.Writer) error { 94 opts, err := c.authenticator.Authenticate(namespace) 95 if err != nil { 96 return fmt.Errorf("get security opt: %s", err) 97 } 98 99 err = c.downloadHelper(namespace, name, _layerquery, dst, opts) 100 if err != nil && err == backenderrors.ErrBlobNotFound { 101 // Docker registry does not support querying manifests with blob path. 102 log.Infof("Blob %s unknown to registry. Tring to download manifest instead", name) 103 err = c.downloadHelper(namespace, name, _manifestquery, dst, opts) 104 } 105 return err 106 } 107 108 func (c *BlobClient) statHelper(namespace, name, query string, opts []httputil.SendOption) (*core.BlobInfo, error) { 109 URL := fmt.Sprintf(query, c.config.Address, namespace, name) 110 resp, err := httputil.Head( 111 URL, 112 append(opts, httputil.SendAcceptedCodes(http.StatusOK))..., 113 ) 114 if err != nil { 115 if httputil.IsNotFound(err) { 116 return nil, backenderrors.ErrBlobNotFound 117 } 118 return nil, fmt.Errorf("head blob: %s", err) 119 } 120 121 size, err := strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64) 122 if err != nil { 123 return nil, fmt.Errorf("parse blob size: %s", err) 124 } 125 return core.NewBlobInfo(size), nil 126 } 127 128 func (c *BlobClient) downloadHelper(namespace, name, query string, dst io.Writer, opts []httputil.SendOption) error { 129 URL := fmt.Sprintf(query, c.config.Address, namespace, name) 130 resp, err := httputil.Get( 131 URL, 132 append( 133 opts, 134 httputil.SendAcceptedCodes(http.StatusOK), 135 httputil.SendTimeout(c.config.Timeout), 136 )..., 137 ) 138 if err != nil { 139 if httputil.IsNotFound(err) { 140 return backenderrors.ErrBlobNotFound 141 } 142 return fmt.Errorf("get blob: %s", err) 143 } 144 defer resp.Body.Close() 145 146 if _, err := io.Copy(dst, resp.Body); err != nil { 147 return fmt.Errorf("copy: %s", err) 148 } 149 return nil 150 } 151 152 // Upload is not supported as users can push directly to registry. 153 func (c *BlobClient) Upload(namespace, name string, src io.Reader) error { 154 return errors.New("not supported") 155 } 156 157 // List is not supported for blobs. 158 func (c *BlobClient) List(prefix string, opts ...backend.ListOption) (*backend.ListResult, error) { 159 return nil, errors.New("not supported") 160 }