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  }