github.com/fanux/shipyard@v0.0.0-20161009071005-6515ce223235/registry/v1/registry.go (about) 1 package v1 2 3 import ( 4 "bytes" 5 "crypto/tls" 6 "encoding/json" 7 "errors" 8 "fmt" 9 "io/ioutil" 10 "net" 11 "net/http" 12 "net/url" 13 "path" 14 "strings" 15 "time" 16 ) 17 18 var ( 19 ErrNotFound = errors.New("Not found") 20 defaultHTTPTimeout = 30 * time.Second 21 ) 22 23 type RegistryClient struct { 24 URL *url.URL 25 tlsConfig *tls.Config 26 httpClient *http.Client 27 } 28 29 type Repo struct { 30 Namespace string 31 Repository string 32 } 33 34 func parseRepo(repo string) Repo { 35 namespace := "library" 36 r := repo 37 38 if strings.Index(repo, "/") != -1 { 39 parts := strings.Split(repo, "/") 40 namespace = parts[0] 41 r = path.Join(parts[1:]...) 42 } 43 44 return Repo{ 45 Namespace: namespace, 46 Repository: r, 47 } 48 } 49 50 func newHTTPClient(u *url.URL, tlsConfig *tls.Config, timeout time.Duration) *http.Client { 51 httpTransport := &http.Transport{ 52 TLSClientConfig: tlsConfig, 53 } 54 55 httpTransport.Dial = func(proto, addr string) (net.Conn, error) { 56 return net.DialTimeout(proto, addr, timeout) 57 } 58 return &http.Client{Transport: httpTransport} 59 } 60 61 func NewRegistryClient(registryUrl string, tlsConfig *tls.Config) (*RegistryClient, error) { 62 u, err := url.Parse(registryUrl) 63 if err != nil { 64 return nil, err 65 } 66 httpClient := newHTTPClient(u, tlsConfig, defaultHTTPTimeout) 67 return &RegistryClient{ 68 URL: u, 69 httpClient: httpClient, 70 tlsConfig: tlsConfig, 71 }, nil 72 } 73 74 func (client *RegistryClient) doRequest(method string, path string, body []byte, headers map[string]string) ([]byte, error) { 75 b := bytes.NewBuffer(body) 76 77 req, err := http.NewRequest(method, client.URL.String()+"/v1"+path, b) 78 if err != nil { 79 return nil, err 80 } 81 82 req.Header.Add("Content-Type", "application/json") 83 if headers != nil { 84 for header, value := range headers { 85 req.Header.Add(header, value) 86 } 87 } 88 89 resp, err := client.httpClient.Do(req) 90 if err != nil { 91 if !strings.Contains(err.Error(), "connection refused") && client.tlsConfig == nil { 92 return nil, fmt.Errorf("%v. Are you trying to connect to a TLS-enabled daemon without TLS?", err) 93 } 94 return nil, err 95 } 96 97 defer resp.Body.Close() 98 99 data, err := ioutil.ReadAll(resp.Body) 100 if err != nil { 101 return nil, err 102 } 103 104 if resp.StatusCode == 404 { 105 return nil, ErrNotFound 106 } 107 108 if resp.StatusCode >= 400 { 109 return nil, Error{StatusCode: resp.StatusCode, Status: resp.Status, msg: string(data)} 110 } 111 112 return data, nil 113 } 114 115 func (client *RegistryClient) Search(query string, page int, numResults int) (*SearchResult, error) { 116 if numResults < 1 { 117 numResults = 100 118 } 119 uri := fmt.Sprintf("/search?q=%s&n=%d&page=%d", query, numResults, page) 120 data, err := client.doRequest("GET", uri, nil, nil) 121 if err != nil { 122 return nil, err 123 } 124 125 res := &SearchResult{} 126 if err := json.Unmarshal(data, &res); err != nil { 127 return nil, err 128 } 129 130 // convert the simple results to rich Repository results 131 repos := []*Repository{} 132 for _, repo := range res.Results { 133 r, err := client.Repository(repo.Name) 134 if err != nil { 135 return nil, err 136 } 137 138 repos = append(repos, r) 139 } 140 141 res.Results = repos 142 143 return res, nil 144 } 145 146 func (client *RegistryClient) DeleteRepository(repo string) error { 147 r := parseRepo(repo) 148 uri := fmt.Sprintf("/repositories/%s/%s/", r.Namespace, r.Repository) 149 if _, err := client.doRequest("DELETE", uri, nil, nil); err != nil { 150 return err 151 } 152 153 return nil 154 } 155 156 func (client *RegistryClient) DeleteTag(repo string, tag string) error { 157 r := parseRepo(repo) 158 uri := fmt.Sprintf("/repositories/%s/%s/tags/%s", r.Namespace, r.Repository, tag) 159 if _, err := client.doRequest("DELETE", uri, nil, nil); err != nil { 160 return err 161 } 162 163 return nil 164 } 165 166 func (client *RegistryClient) Layer(id string) (*Layer, error) { 167 uri := fmt.Sprintf("/images/%s/json", id) 168 data, err := client.doRequest("GET", uri, nil, nil) 169 if err != nil { 170 return nil, err 171 } 172 173 layer := &Layer{} 174 if err := json.Unmarshal(data, &layer); err != nil { 175 return nil, err 176 } 177 178 return layer, nil 179 } 180 181 func (client *RegistryClient) Repository(name string) (*Repository, error) { 182 r := parseRepo(name) 183 uri := fmt.Sprintf("/repositories/%s/%s/tags", r.Namespace, r.Repository) 184 repoTags := map[string]string{} 185 186 data, err := client.doRequest("GET", uri, nil, nil) 187 if err != nil { 188 return nil, err 189 } 190 191 if err := json.Unmarshal(data, &repoTags); err != nil { 192 return nil, err 193 } 194 195 layers := []Layer{} 196 tags := []Tag{} 197 size := int64(0) 198 199 for n, id := range repoTags { 200 uri := fmt.Sprintf("/images/%s/json", id) 201 layer := &Layer{} 202 203 data, err := client.doRequest("GET", uri, nil, nil) 204 if err != nil { 205 return nil, err 206 } 207 208 if err := json.Unmarshal(data, &layer); err != nil { 209 return nil, err 210 } 211 212 uri = fmt.Sprintf("/images/%s/ancestry", id) 213 214 ancestry := []string{} 215 216 data, err = client.doRequest("GET", uri, nil, nil) 217 if err != nil { 218 return nil, err 219 } 220 221 if err = json.Unmarshal(data, &ancestry); err != nil { 222 return nil, err 223 } 224 225 tag := &Tag{ 226 ID: id, 227 Name: n, 228 } 229 230 tags = append(tags, *tag) 231 layer.Ancestry = ancestry 232 233 layers = append(layers, *layer) 234 235 // parse ancestor layers 236 for _, i := range ancestry { 237 uri = fmt.Sprintf("/images/%s/json", i) 238 l := &Layer{} 239 240 data, err = client.doRequest("GET", uri, nil, nil) 241 if err != nil { 242 return nil, err 243 } 244 245 if err = json.Unmarshal(data, &l); err != nil { 246 return nil, err 247 } 248 size += l.Size 249 layers = append(layers, *l) 250 } 251 } 252 var repoSize int64 253 if int64(len(tags)) > 0 { 254 repoSize = int64(size) / int64(len(tags)) 255 } 256 257 return &Repository{ 258 Name: path.Join(r.Namespace, r.Repository), 259 Namespace: r.Namespace, 260 Repository: r.Repository, 261 Tags: tags, 262 Layers: layers, 263 Size: repoSize, 264 }, nil 265 }