Browse code

Do Docker edits so we can use the new distribution code

Signed-off-by: Doug Davis <dug@us.ibm.com>

Doug Davis authored on 2015/07/22 07:54:26
Showing 12 changed files
... ...
@@ -18,6 +18,7 @@ import (
18 18
 	"github.com/docker/docker/trust"
19 19
 	"github.com/docker/docker/utils"
20 20
 	"github.com/docker/libtrust"
21
+	"golang.org/x/net/context"
21 22
 )
22 23
 
23 24
 type v2Puller struct {
... ...
@@ -58,7 +59,13 @@ func (p *v2Puller) pullV2Repository(tag string) (err error) {
58 58
 		taggedName = utils.ImageReference(p.repoInfo.LocalName, tag)
59 59
 	} else {
60 60
 		var err error
61
-		tags, err = p.repo.Manifests().Tags()
61
+
62
+		manSvc, err := p.repo.Manifests(context.Background())
63
+		if err != nil {
64
+			return err
65
+		}
66
+
67
+		tags, err = manSvc.Tags()
62 68
 		if err != nil {
63 69
 			return err
64 70
 		}
... ...
@@ -140,7 +147,7 @@ func (p *v2Puller) download(di *downloadInfo) {
140 140
 		di.err <- err
141 141
 		return
142 142
 	}
143
-	di.size = desc.Length
143
+	di.size = desc.Size
144 144
 
145 145
 	layerDownload, err := blobs.Open(nil, di.digest)
146 146
 	if err != nil {
... ...
@@ -187,7 +194,12 @@ func (p *v2Puller) pullV2Tag(tag, taggedName string) (bool, error) {
187 187
 	logrus.Debugf("Pulling tag from V2 registry: %q", tag)
188 188
 	out := p.config.OutStream
189 189
 
190
-	manifest, err := p.repo.Manifests().GetByTag(tag)
190
+	manSvc, err := p.repo.Manifests(context.Background())
191
+	if err != nil {
192
+		return false, err
193
+	}
194
+
195
+	manifest, err := manSvc.GetByTag(tag)
191 196
 	if err != nil {
192 197
 		return false, err
193 198
 	}
... ...
@@ -16,6 +16,7 @@ import (
16 16
 	"github.com/docker/docker/registry"
17 17
 	"github.com/docker/docker/runconfig"
18 18
 	"github.com/docker/docker/utils"
19
+	"golang.org/x/net/context"
19 20
 )
20 21
 
21 22
 type v2Pusher struct {
... ...
@@ -191,7 +192,11 @@ func (p *v2Pusher) pushV2Tag(tag string) error {
191 191
 		out.Write(p.sf.FormatStatus("", "Digest: %s", manifestDigest))
192 192
 	}
193 193
 
194
-	return p.repo.Manifests().Put(signed)
194
+	manSvc, err := p.repo.Manifests(context.Background())
195
+	if err != nil {
196
+		return err
197
+	}
198
+	return manSvc.Put(signed)
195 199
 }
196 200
 
197 201
 func (p *v2Pusher) pushV2Image(bs distribution.BlobService, img *image.Image) (digest.Digest, error) {
... ...
@@ -35,7 +35,7 @@ clone git github.com/coreos/go-etcd v2.0.0
35 35
 clone git github.com/hashicorp/consul v0.5.2
36 36
 
37 37
 # get graph and distribution packages
38
-clone git github.com/docker/distribution 856638e299eddf01964fa918ac1552d8aa2e22b3
38
+clone git github.com/docker/distribution cd8ff553b6b1911be23dfeabb73e33108bcbf147
39 39
 clone git github.com/vbatts/tar-split v0.9.4
40 40
 
41 41
 clone git github.com/opencontainers/runc v0.0.2 # libcontainer
... ...
@@ -97,32 +97,43 @@ Unless explicitly stated, we follow all coding guidelines from the Go
97 97
 community. While some of these standards may seem arbitrary, they somehow seem
98 98
 to result in a solid, consistent codebase.
99 99
 
100
+It is possible that the code base does not currently comply with these
101
+guidelines. We are not looking for a massive PR that fixes this, since that
102
+goes against the spirit of the guidelines. All new contributions should make a
103
+best effort to clean up and make the code base better than they left it.
104
+Obviously, apply your best judgement. Remember, the goal here is to make the
105
+code base easier for humans to navigate and understand. Always keep that in
106
+mind when nudging others to comply.
107
+
100 108
 The rules:
101 109
 
102 110
 1. All code should be formatted with `gofmt -s`.
103 111
 2. All code should pass the default levels of
104 112
    [`golint`](https://github.com/golang/lint).
105
-3. All code should follow the guidelines covered at
106
-   https://github.com/golang/go/wiki/CodeReviewComments.
113
+3. All code should follow the guidelines covered in [Effective
114
+   Go](http://golang.org/doc/effective_go.html) and [Go Code Review
115
+   Comments](https://github.com/golang/go/wiki/CodeReviewComments).
107 116
 4. Comment the code. Tell us the why, the history and the context.
108 117
 5. Document _all_ declarations and methods, even private ones. Declare
109 118
    expectations, caveats and anything else that may be important. If a type
110 119
    gets exported, having the comments already there will ensure it's ready.
111
-6. Variable name length should be proportional to it's context and no longer.
112
-   noALongVariableNameLikeThisIsNotMoreClearWhenASimpleCommentWouldDo. In
113
-   practice, short methods will have short variable names and globals will
120
+6. Variable name length should be proportional to its context and no longer.
121
+   `noCommaALongVariableNameLikeThisIsNotMoreClearWhenASimpleCommentWouldDo`.
122
+   In practice, short methods will have short variable names and globals will
114 123
    have longer names.
115 124
 7. No underscores in package names. If you need a compound name, step back,
116 125
    and re-examine why you need a compound name. If you still think you need a
117 126
    compound name, lose the underscore.
118 127
 8. No utils or helpers packages. If a function is not general enough to
119
-   warrant it's own package, it has not been written generally enough to be a
128
+   warrant its own package, it has not been written generally enough to be a
120 129
    part of a util package. Just leave it unexported and well-documented.
121
-9. No, we don't need another unit testing framework.
130
+9. All tests should run with `go test` and outside tooling should not be
131
+   required. No, we don't need another unit testing framework. Assertion
132
+   packages are acceptable if they provide _real_ incremental value.
122 133
 10. Even though we call these "rules" above, they are actually just
123 134
     guidelines. Since you've read all the rules, you now know that.
124 135
 
125 136
 If you are having trouble getting into the mood of idiomatic Go, we recommend
126
-reading through [`Effective Go`](http://golang.org/doc/effective_go.html). The
137
+reading through [Effective Go](http://golang.org/doc/effective_go.html). The
127 138
 [Go Blog](http://blog.golang.org/) is also a great resource. Drinking the
128 139
 kool-aid is a lot easier than going thirsty.
... ...
@@ -7,6 +7,8 @@ for storing and distributing Docker images. It supersedes the [docker/docker-
7 7
 registry](https://github.com/docker/docker-registry) project with a new API
8 8
 design, focused around security and performance.
9 9
 
10
+<img src="https://www.docker.com/sites/default/files/oyster-registry-3.png" width=200px/>
11
+
10 12
 This repository contains the following components:
11 13
 
12 14
 |**Component**       |Description                                                                                                                                                                                         |
... ...
@@ -35,6 +35,12 @@ type Namespace interface {
35 35
 	// registry may or may not have the repository but should always return a
36 36
 	// reference.
37 37
 	Repository(ctx context.Context, name string) (Repository, error)
38
+
39
+	// Repositories fills 'repos' with a lexigraphically sorted catalog of repositories
40
+	// up to the size of 'repos' and returns the value 'n' for the number of entries
41
+	// which were filled.  'last' contains an offset in the catalog, and 'err' will be
42
+	// set to io.EOF if there are no more entries to obtain.
43
+	Repositories(ctx context.Context, repos []string, last string) (n int, err error)
38 44
 }
39 45
 
40 46
 // ManifestServiceOption is a function argument for Manifest Service methods
... ...
@@ -106,7 +106,7 @@ func (e Error) ErrorCode() ErrorCode {
106 106
 func (e Error) Error() string {
107 107
 	return fmt.Sprintf("%s: %s",
108 108
 		strings.ToLower(strings.Replace(e.Code.String(), "_", " ", -1)),
109
-		e.Code.Message())
109
+		e.Message)
110 110
 }
111 111
 
112 112
 // WithDetail will return a new Error, based on the current one, but with
... ...
@@ -87,6 +87,30 @@ var (
87 87
 		Format:      "<digest>",
88 88
 	}
89 89
 
90
+	linkHeader = ParameterDescriptor{
91
+		Name:        "Link",
92
+		Type:        "link",
93
+		Description: "RFC5988 compliant rel='next' with URL to next result set, if available",
94
+		Format:      `<<url>?n=<last n value>&last=<last entry from response>>; rel="next"`,
95
+	}
96
+
97
+	paginationParameters = []ParameterDescriptor{
98
+		{
99
+			Name:        "n",
100
+			Type:        "integer",
101
+			Description: "Limit the number of entries in each response. It not present, all entries will be returned.",
102
+			Format:      "<integer>",
103
+			Required:    false,
104
+		},
105
+		{
106
+			Name:        "last",
107
+			Type:        "string",
108
+			Description: "Result set will include values lexically after last.",
109
+			Format:      "<integer>",
110
+			Required:    false,
111
+		},
112
+	}
113
+
90 114
 	unauthorizedResponse = ResponseDescriptor{
91 115
 		Description: "The client does not have access to the repository.",
92 116
 		StatusCode:  http.StatusUnauthorized,
... ...
@@ -269,6 +293,9 @@ type ResponseDescriptor struct {
269 269
 	// Headers covers any headers that may be returned from the response.
270 270
 	Headers []ParameterDescriptor
271 271
 
272
+	// Fields describes any fields that may be present in the response.
273
+	Fields []ParameterDescriptor
274
+
272 275
 	// ErrorCodes enumerates the error codes that may be returned along with
273 276
 	// the response.
274 277
 	ErrorCodes []errcode.ErrorCode
... ...
@@ -427,6 +454,36 @@ var routeDescriptors = []RouteDescriptor{
427 427
 							},
428 428
 						},
429 429
 					},
430
+					{
431
+						Description:     "Return a portion of the tags for the specified repository.",
432
+						PathParameters:  []ParameterDescriptor{nameParameterDescriptor},
433
+						QueryParameters: paginationParameters,
434
+						Successes: []ResponseDescriptor{
435
+							{
436
+								StatusCode:  http.StatusOK,
437
+								Description: "A list of tags for the named repository.",
438
+								Headers: []ParameterDescriptor{
439
+									{
440
+										Name:        "Content-Length",
441
+										Type:        "integer",
442
+										Description: "Length of the JSON response body.",
443
+										Format:      "<length>",
444
+									},
445
+									linkHeader,
446
+								},
447
+								Body: BodyDescriptor{
448
+									ContentType: "application/json; charset=utf-8",
449
+									Format: `{
450
+    "name": <name>,
451
+    "tags": [
452
+        <tag>,
453
+        ...
454
+    ],
455
+}`,
456
+								},
457
+							},
458
+						},
459
+					},
430 460
 				},
431 461
 			},
432 462
 		},
... ...
@@ -1320,6 +1377,76 @@ var routeDescriptors = []RouteDescriptor{
1320 1320
 			},
1321 1321
 		},
1322 1322
 	},
1323
+	{
1324
+		Name:        RouteNameCatalog,
1325
+		Path:        "/v2/_catalog",
1326
+		Entity:      "Catalog",
1327
+		Description: "List a set of available repositories in the local registry cluster. Does not provide any indication of what may be available upstream. Applications can only determine if a repository is available but not if it is not available.",
1328
+		Methods: []MethodDescriptor{
1329
+			{
1330
+				Method:      "GET",
1331
+				Description: "Retrieve a sorted, json list of repositories available in the registry.",
1332
+				Requests: []RequestDescriptor{
1333
+					{
1334
+						Name:        "Catalog Fetch Complete",
1335
+						Description: "Request an unabridged list of repositories available.",
1336
+						Successes: []ResponseDescriptor{
1337
+							{
1338
+								Description: "Returns the unabridged list of repositories as a json response.",
1339
+								StatusCode:  http.StatusOK,
1340
+								Headers: []ParameterDescriptor{
1341
+									{
1342
+										Name:        "Content-Length",
1343
+										Type:        "integer",
1344
+										Description: "Length of the JSON response body.",
1345
+										Format:      "<length>",
1346
+									},
1347
+								},
1348
+								Body: BodyDescriptor{
1349
+									ContentType: "application/json; charset=utf-8",
1350
+									Format: `{
1351
+	"repositories": [
1352
+		<name>,
1353
+		...
1354
+	]
1355
+}`,
1356
+								},
1357
+							},
1358
+						},
1359
+					},
1360
+					{
1361
+						Name:            "Catalog Fetch Paginated",
1362
+						Description:     "Return the specified portion of repositories.",
1363
+						QueryParameters: paginationParameters,
1364
+						Successes: []ResponseDescriptor{
1365
+							{
1366
+								StatusCode: http.StatusOK,
1367
+								Body: BodyDescriptor{
1368
+									ContentType: "application/json; charset=utf-8",
1369
+									Format: `{
1370
+	"repositories": [
1371
+		<name>,
1372
+		...
1373
+	]
1374
+	"next": "<url>?last=<name>&n=<last value of n>"
1375
+}`,
1376
+								},
1377
+								Headers: []ParameterDescriptor{
1378
+									{
1379
+										Name:        "Content-Length",
1380
+										Type:        "integer",
1381
+										Description: "Length of the JSON response body.",
1382
+										Format:      "<length>",
1383
+									},
1384
+									linkHeader,
1385
+								},
1386
+							},
1387
+						},
1388
+					},
1389
+				},
1390
+			},
1391
+		},
1392
+	},
1323 1393
 }
1324 1394
 
1325 1395
 var routeDescriptorsMap map[string]RouteDescriptor
... ...
@@ -11,10 +11,12 @@ const (
11 11
 	RouteNameBlob            = "blob"
12 12
 	RouteNameBlobUpload      = "blob-upload"
13 13
 	RouteNameBlobUploadChunk = "blob-upload-chunk"
14
+	RouteNameCatalog         = "catalog"
14 15
 )
15 16
 
16 17
 var allEndpoints = []string{
17 18
 	RouteNameManifest,
19
+	RouteNameCatalog,
18 20
 	RouteNameTags,
19 21
 	RouteNameBlob,
20 22
 	RouteNameBlobUpload,
... ...
@@ -100,6 +100,18 @@ func (ub *URLBuilder) BuildBaseURL() (string, error) {
100 100
 	return baseURL.String(), nil
101 101
 }
102 102
 
103
+// BuildCatalogURL constructs a url get a catalog of repositories
104
+func (ub *URLBuilder) BuildCatalogURL(values ...url.Values) (string, error) {
105
+	route := ub.cloneRoute(RouteNameCatalog)
106
+
107
+	catalogURL, err := route.URL()
108
+	if err != nil {
109
+		return "", err
110
+	}
111
+
112
+	return appendValuesURL(catalogURL, values...).String(), nil
113
+}
114
+
103 115
 // BuildTagsURL constructs a url to list the tags in the named repository.
104 116
 func (ub *URLBuilder) BuildTagsURL(name string) (string, error) {
105 117
 	route := ub.cloneRoute(RouteNameTags)
... ...
@@ -53,13 +53,6 @@ func handleErrorResponse(resp *http.Response) error {
53 53
 		err := parseHTTPErrorResponse(resp.Body)
54 54
 		if uErr, ok := err.(*UnexpectedHTTPResponseError); ok {
55 55
 			return v2.ErrorCodeUnauthorized.WithDetail(uErr.Response)
56
-			/*
57
-				return &errcode.Error{
58
-					Code:    v2.ErrorCodeUnauthorized,
59
-					Message: v2.ErrorCodeUnauthorized.Message(),
60
-					Detail:  uErr.Response,
61
-				}
62
-			*/
63 56
 		}
64 57
 		return err
65 58
 	}
... ...
@@ -21,6 +21,83 @@ import (
21 21
 	"github.com/docker/distribution/registry/storage/cache/memory"
22 22
 )
23 23
 
24
+// Registry provides an interface for calling Repositories, which returns a catalog of repositories.
25
+type Registry interface {
26
+	Repositories(ctx context.Context, repos []string, last string) (n int, err error)
27
+}
28
+
29
+// NewRegistry creates a registry namespace which can be used to get a listing of repositories
30
+func NewRegistry(ctx context.Context, baseURL string, transport http.RoundTripper) (Registry, error) {
31
+	ub, err := v2.NewURLBuilderFromString(baseURL)
32
+	if err != nil {
33
+		return nil, err
34
+	}
35
+
36
+	client := &http.Client{
37
+		Transport: transport,
38
+		Timeout:   1 * time.Minute,
39
+	}
40
+
41
+	return &registry{
42
+		client:  client,
43
+		ub:      ub,
44
+		context: ctx,
45
+	}, nil
46
+}
47
+
48
+type registry struct {
49
+	client  *http.Client
50
+	ub      *v2.URLBuilder
51
+	context context.Context
52
+}
53
+
54
+// Repositories returns a lexigraphically sorted catalog given a base URL.  The 'entries' slice will be filled up to the size
55
+// of the slice, starting at the value provided in 'last'.  The number of entries will be returned along with io.EOF if there
56
+// are no more entries
57
+func (r *registry) Repositories(ctx context.Context, entries []string, last string) (int, error) {
58
+	var numFilled int
59
+	var returnErr error
60
+
61
+	values := buildCatalogValues(len(entries), last)
62
+	u, err := r.ub.BuildCatalogURL(values)
63
+	if err != nil {
64
+		return 0, err
65
+	}
66
+
67
+	resp, err := r.client.Get(u)
68
+	if err != nil {
69
+		return 0, err
70
+	}
71
+	defer resp.Body.Close()
72
+
73
+	switch resp.StatusCode {
74
+	case http.StatusOK:
75
+		var ctlg struct {
76
+			Repositories []string `json:"repositories"`
77
+		}
78
+		decoder := json.NewDecoder(resp.Body)
79
+
80
+		if err := decoder.Decode(&ctlg); err != nil {
81
+			return 0, err
82
+		}
83
+
84
+		for cnt := range ctlg.Repositories {
85
+			entries[cnt] = ctlg.Repositories[cnt]
86
+		}
87
+		numFilled = len(ctlg.Repositories)
88
+
89
+		link := resp.Header.Get("Link")
90
+		if link == "" {
91
+			returnErr = io.EOF
92
+		}
93
+
94
+	default:
95
+		return 0, handleErrorResponse(resp)
96
+	}
97
+
98
+	return numFilled, returnErr
99
+}
100
+
24 101
 // NewRepository creates a new Repository for the given repository name and base URL
25 102
 func NewRepository(ctx context.Context, name, baseURL string, transport http.RoundTripper) (distribution.Repository, error) {
26 103
 	if err := v2.ValidateRepositoryName(name); err != nil {
... ...
@@ -444,3 +521,17 @@ func (bs *blobStatter) Stat(ctx context.Context, dgst digest.Digest) (distributi
444 444
 		return distribution.Descriptor{}, handleErrorResponse(resp)
445 445
 	}
446 446
 }
447
+
448
+func buildCatalogValues(maxEntries int, last string) url.Values {
449
+	values := url.Values{}
450
+
451
+	if maxEntries > 0 {
452
+		values.Add("n", strconv.Itoa(maxEntries))
453
+	}
454
+
455
+	if last != "" {
456
+		values.Add("last", last)
457
+	}
458
+
459
+	return values
460
+}