github.com/uber/kraken@v0.1.4/lib/backend/registrybackend/tagclient.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 "strings" 23 24 "github.com/uber/kraken/core" 25 "github.com/uber/kraken/lib/backend" 26 "github.com/uber/kraken/lib/backend/backenderrors" 27 "github.com/uber/kraken/lib/backend/registrybackend/security" 28 "github.com/uber/kraken/utils/dockerutil" 29 "github.com/uber/kraken/utils/httputil" 30 yaml "gopkg.in/yaml.v2" 31 ) 32 33 const _registrytag = "registry_tag" 34 35 func init() { 36 backend.Register(_registrytag, &tagClientFactory{}) 37 } 38 39 type tagClientFactory struct{} 40 41 func (f *tagClientFactory) Create( 42 confRaw interface{}, authConfRaw interface{}) (backend.Client, error) { 43 44 confBytes, err := yaml.Marshal(confRaw) 45 if err != nil { 46 return nil, errors.New("marshal hdfs config") 47 } 48 var config Config 49 if err := yaml.Unmarshal(confBytes, &config); err != nil { 50 return nil, errors.New("unmarshal hdfs config") 51 } 52 return NewTagClient(config) 53 } 54 55 const _tagquery = "http://%s/v2/%s/manifests/%s" 56 const _v2ManifestType = "application/vnd.docker.distribution.manifest.v2+json" 57 58 // TagClient stats and downloads tag from registry. 59 type TagClient struct { 60 config Config 61 authenticator security.Authenticator 62 } 63 64 // NewTagClient creates a new TagClient. 65 func NewTagClient(config Config) (*TagClient, error) { 66 config = config.applyDefaults() 67 authenticator, err := security.NewAuthenticator(config.Address, config.Security) 68 if err != nil { 69 return nil, fmt.Errorf("cannot create tag client authenticator: %s", err) 70 } 71 return &TagClient{ 72 config: config, 73 authenticator: authenticator, 74 }, nil 75 } 76 77 // Stat sends a HEAD request to registry for a tag and returns the manifest size. 78 func (c *TagClient) Stat(namespace, name string) (*core.BlobInfo, error) { 79 tokens := strings.Split(name, ":") 80 if len(tokens) != 2 { 81 return nil, fmt.Errorf("invald name %s: must be repo:tag", name) 82 } 83 repo, tag := tokens[0], tokens[1] 84 85 opts, err := c.authenticator.Authenticate(repo) 86 if err != nil { 87 return nil, fmt.Errorf("get security opt: %s", err) 88 } 89 90 URL := fmt.Sprintf(_tagquery, c.config.Address, repo, tag) 91 resp, err := httputil.Head( 92 URL, 93 append( 94 opts, 95 httputil.SendHeaders(map[string]string{"Accept": _v2ManifestType}), 96 httputil.SendAcceptedCodes(http.StatusOK, http.StatusNotFound), 97 )..., 98 ) 99 if err != nil { 100 return nil, fmt.Errorf("check blob exists: %s", err) 101 } 102 103 if resp.StatusCode == http.StatusNotFound { 104 return nil, backenderrors.ErrBlobNotFound 105 } 106 size, err := strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64) 107 if err != nil { 108 return nil, fmt.Errorf("parse blob size: %s", err) 109 } 110 return core.NewBlobInfo(size), nil 111 } 112 113 // Download gets the digest for a tag from registry. 114 func (c *TagClient) Download(namespace, name string, dst io.Writer) error { 115 tokens := strings.Split(name, ":") 116 if len(tokens) != 2 { 117 return fmt.Errorf("invald name %s: must be repo:tag", name) 118 } 119 repo, tag := tokens[0], tokens[1] 120 121 opts, err := c.authenticator.Authenticate(repo) 122 if err != nil { 123 return fmt.Errorf("get security opt: %s", err) 124 } 125 126 URL := fmt.Sprintf(_tagquery, c.config.Address, repo, tag) 127 resp, err := httputil.Get( 128 URL, 129 append( 130 opts, 131 httputil.SendHeaders(map[string]string{"Accept": _v2ManifestType}), 132 httputil.SendAcceptedCodes(http.StatusOK, http.StatusNotFound), 133 )..., 134 ) 135 if err != nil { 136 return fmt.Errorf("check blob exists: %s", err) 137 } 138 defer resp.Body.Close() 139 140 if resp.StatusCode == http.StatusNotFound { 141 return backenderrors.ErrBlobNotFound 142 } 143 144 _, digest, err := dockerutil.ParseManifestV2(resp.Body) 145 if err != nil { 146 return fmt.Errorf("parse manifest v2: %s", err) 147 } 148 if _, err := io.Copy(dst, strings.NewReader(digest.String())); err != nil { 149 return fmt.Errorf("copy: %s", err) 150 } 151 return nil 152 } 153 154 // Upload is not supported as users can push directly to registry. 155 func (c *TagClient) Upload(namespace, name string, src io.Reader) error { 156 return errors.New("not supported") 157 } 158 159 // List is not supported as users can list directly from registry. 160 func (c *TagClient) List(prefix string, opts ...backend.ListOption) (*backend.ListResult, error) { 161 return nil, errors.New("not supported") 162 }