Browse code

vendor docker/distribution d06d6d3b093302c02a93153ac7b06ebc0ffd1793

- fix and add integration tests

Signed-off-by: Antonio Murdaca <runcom@redhat.com>

Antonio Murdaca authored on 2016/03/19 20:19:43
Showing 11 changed files
... ...
@@ -48,7 +48,7 @@ clone git github.com/boltdb/bolt v1.1.0
48 48
 clone git github.com/miekg/dns 75e6e86cc601825c5dbcd4e0c209eab180997cd7
49 49
 
50 50
 # get graph and distribution packages
51
-clone git github.com/docker/distribution db17a23b961978730892e12a0c6051d43a31aab3
51
+clone git github.com/docker/distribution d06d6d3b093302c02a93153ac7b06ebc0ffd1793
52 52
 clone git github.com/vbatts/tar-split v0.9.11
53 53
 
54 54
 # get desired notary commit, might also need to be updated in Dockerfile
... ...
@@ -549,32 +549,61 @@ func (s *DockerSuite) TestPushToCentralRegistryUnauthorized(c *check.C) {
549 549
 	c.Assert(out, checker.Contains, "unauthorized: access to the requested resource is not authorized")
550 550
 }
551 551
 
552
-func (s *DockerRegistryAuthTokenSuite) TestPushTokenServiceUnauthResponse(c *check.C) {
553
-	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
554
-		w.WriteHeader(http.StatusUnauthorized)
552
+func getTestTokenService(status int, body string) *httptest.Server {
553
+	return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
554
+		w.WriteHeader(status)
555 555
 		w.Header().Set("Content-Type", "application/json")
556
-		w.Write([]byte(`{"errors": [{"Code":"UNAUTHORIZED", "message": "a message", "detail": null}]}`))
556
+		w.Write([]byte(body))
557 557
 	}))
558
+}
559
+
560
+func (s *DockerRegistryAuthTokenSuite) TestPushTokenServiceUnauthResponse(c *check.C) {
561
+	ts := getTestTokenService(http.StatusUnauthorized, `{"errors": [{"Code":"UNAUTHORIZED", "message": "a message", "detail": null}]}`)
558 562
 	defer ts.Close()
559 563
 	s.setupRegistryWithTokenService(c, ts.URL)
560 564
 	repoName := fmt.Sprintf("%s/busybox", privateRegistryURL)
561 565
 	dockerCmd(c, "tag", "busybox", repoName)
562 566
 	out, _, err := dockerCmdWithError("push", repoName)
563 567
 	c.Assert(err, check.NotNil, check.Commentf(out))
568
+	c.Assert(out, checker.Not(checker.Contains), "Retrying")
564 569
 	c.Assert(out, checker.Contains, "unauthorized: a message")
565 570
 }
566 571
 
567
-func (s *DockerRegistryAuthTokenSuite) TestPushMisconfiguredTokenServiceResponse(c *check.C) {
568
-	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
569
-		w.WriteHeader(http.StatusUnauthorized)
570
-		w.Header().Set("Content-Type", "application/json")
571
-		// this will make the daemon panics if no check is performed in retryOnError
572
-		w.Write([]byte(`{"error": "unauthorized"}`))
573
-	}))
572
+func (s *DockerRegistryAuthTokenSuite) TestPushMisconfiguredTokenServiceResponseUnauthorized(c *check.C) {
573
+	ts := getTestTokenService(http.StatusUnauthorized, `{"error": "unauthorized"}`)
574
+	defer ts.Close()
575
+	s.setupRegistryWithTokenService(c, ts.URL)
576
+	repoName := fmt.Sprintf("%s/busybox", privateRegistryURL)
577
+	dockerCmd(c, "tag", "busybox", repoName)
578
+	out, _, err := dockerCmdWithError("push", repoName)
579
+	c.Assert(err, check.NotNil, check.Commentf(out))
580
+	c.Assert(out, checker.Not(checker.Contains), "Retrying")
581
+	split := strings.Split(out, "\n")
582
+	c.Assert(split[len(split)-2], check.Equals, "unauthorized: authentication required")
583
+}
584
+
585
+func (s *DockerRegistryAuthTokenSuite) TestPushMisconfiguredTokenServiceResponseError(c *check.C) {
586
+	ts := getTestTokenService(http.StatusInternalServerError, `{"error": "unexpected"}`)
587
+	defer ts.Close()
588
+	s.setupRegistryWithTokenService(c, ts.URL)
589
+	repoName := fmt.Sprintf("%s/busybox", privateRegistryURL)
590
+	dockerCmd(c, "tag", "busybox", repoName)
591
+	out, _, err := dockerCmdWithError("push", repoName)
592
+	c.Assert(err, check.NotNil, check.Commentf(out))
593
+	c.Assert(out, checker.Contains, "Retrying")
594
+	split := strings.Split(out, "\n")
595
+	c.Assert(split[len(split)-2], check.Equals, "received unexpected HTTP status: 500 Internal Server Error")
596
+}
597
+
598
+func (s *DockerRegistryAuthTokenSuite) TestPushMisconfiguredTokenServiceResponseUnparsable(c *check.C) {
599
+	ts := getTestTokenService(http.StatusForbidden, `no way`)
574 600
 	defer ts.Close()
575 601
 	s.setupRegistryWithTokenService(c, ts.URL)
576 602
 	repoName := fmt.Sprintf("%s/busybox", privateRegistryURL)
577 603
 	dockerCmd(c, "tag", "busybox", repoName)
578 604
 	out, _, err := dockerCmdWithError("push", repoName)
579 605
 	c.Assert(err, check.NotNil, check.Commentf(out))
606
+	c.Assert(out, checker.Not(checker.Contains), "Retrying")
607
+	split := strings.Split(out, "\n")
608
+	c.Assert(split[len(split)-2], checker.Contains, "error parsing HTTP 403 response body: ")
580 609
 }
... ...
@@ -76,7 +76,7 @@ Some simple rules to ensure quick merge:
76 76
 You are heavily encouraged to first discuss what you want to do. You can do so on the irc channel, or by opening an issue that clearly describes the use case you want to fulfill, or the problem you are trying to solve.
77 77
 
78 78
 If this is a major new feature, you should then submit a proposal that describes your technical solution and reasoning.
79
-If you did discuss it first, this will likely be greenlighted very fast. It's advisable to address all feedback on this proposal before starting actual work. 
79
+If you did discuss it first, this will likely be greenlighted very fast. It's advisable to address all feedback on this proposal before starting actual work.
80 80
 
81 81
 Then you should submit your implementation, clearly linking to the issue (and possible proposal).
82 82
 
... ...
@@ -90,7 +90,7 @@ It's mandatory to:
90 90
 
91 91
 Complying to these simple rules will greatly accelerate the review process, and will ensure you have a pleasant experience in contributing code to the Registry.
92 92
 
93
-Have a look at a great, successful contribution: the [Ceph driver PR](https://github.com/docker/distribution/pull/443)
93
+Have a look at a great, successful contribution: the [Swift driver PR](https://github.com/docker/distribution/pull/493)
94 94
 
95 95
 ## Coding Style
96 96
 
... ...
@@ -1,12 +1,12 @@
1 1
 FROM golang:1.5.3
2 2
 
3 3
 RUN apt-get update && \
4
-    apt-get install -y librados-dev apache2-utils && \
4
+    apt-get install -y apache2-utils && \
5 5
     rm -rf /var/lib/apt/lists/*
6 6
 
7 7
 ENV DISTRIBUTION_DIR /go/src/github.com/docker/distribution
8 8
 ENV GOPATH $DISTRIBUTION_DIR/Godeps/_workspace:$GOPATH
9
-ENV DOCKER_BUILDTAGS include_rados include_oss include_gcs
9
+ENV DOCKER_BUILDTAGS include_oss include_gcs
10 10
 
11 11
 WORKDIR $DISTRIBUTION_DIR
12 12
 COPY . $DISTRIBUTION_DIR
... ...
@@ -189,9 +189,11 @@ type BlobCreateOption interface {
189 189
 // BlobWriteService.Resume. If supported by the store, a writer can be
190 190
 // recovered with the id.
191 191
 type BlobWriter interface {
192
-	io.WriteSeeker
192
+	io.WriteCloser
193 193
 	io.ReaderFrom
194
-	io.Closer
194
+
195
+	// Size returns the number of bytes written to this blob.
196
+	Size() int64
195 197
 
196 198
 	// ID returns the identifier for this writer. The ID can be used with the
197 199
 	// Blob service to later resume the write.
... ...
@@ -216,9 +218,6 @@ type BlobWriter interface {
216 216
 	// result in a no-op. This allows use of Cancel in a defer statement,
217 217
 	// increasing the assurance that it is correctly called.
218 218
 	Cancel(ctx context.Context) error
219
-
220
-	// Get a reader to the blob being written by this BlobWriter
221
-	Reader() (io.ReadCloser, error)
222 219
 }
223 220
 
224 221
 // BlobService combines the operations to access, read and write blobs. This
... ...
@@ -3,9 +3,6 @@ machine:
3 3
   pre:
4 4
   # Install gvm
5 5
     - bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/1.0.22/binscripts/gvm-installer)
6
-  # Install ceph to test rados driver & create pool
7
-    - sudo -i ~/distribution/contrib/ceph/ci-setup.sh
8
-    - ceph osd pool create docker-distribution 1
9 6
   # Install codecov for coverage
10 7
     - pip install --user codecov
11 8
 
... ...
@@ -19,11 +16,9 @@ machine:
19 19
     BASE_DIR: src/github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME
20 20
   # Trick circle brainflat "no absolute path" behavior
21 21
     BASE_STABLE: ../../../$HOME/.gvm/pkgsets/stable/global/$BASE_DIR
22
-    DOCKER_BUILDTAGS: "include_rados include_oss include_gcs"
22
+    DOCKER_BUILDTAGS: "include_oss include_gcs"
23 23
   # Workaround Circle parsing dumb bugs and/or YAML wonkyness
24 24
     CIRCLE_PAIN: "mode: set"
25
-  # Ceph config
26
-    RADOS_POOL: "docker-distribution"
27 25
 
28 26
   hosts:
29 27
   # Not used yet
... ...
@@ -25,7 +25,7 @@ type Challenge struct {
25 25
 type ChallengeManager interface {
26 26
 	// GetChallenges returns the challenges for the given
27 27
 	// endpoint URL.
28
-	GetChallenges(endpoint string) ([]Challenge, error)
28
+	GetChallenges(endpoint url.URL) ([]Challenge, error)
29 29
 
30 30
 	// AddResponse adds the response to the challenge
31 31
 	// manager. The challenges will be parsed out of
... ...
@@ -48,8 +48,10 @@ func NewSimpleChallengeManager() ChallengeManager {
48 48
 
49 49
 type simpleChallengeManager map[string][]Challenge
50 50
 
51
-func (m simpleChallengeManager) GetChallenges(endpoint string) ([]Challenge, error) {
52
-	challenges := m[endpoint]
51
+func (m simpleChallengeManager) GetChallenges(endpoint url.URL) ([]Challenge, error) {
52
+	endpoint.Host = strings.ToLower(endpoint.Host)
53
+
54
+	challenges := m[endpoint.String()]
53 55
 	return challenges, nil
54 56
 }
55 57
 
... ...
@@ -60,11 +62,10 @@ func (m simpleChallengeManager) AddResponse(resp *http.Response) error {
60 60
 	}
61 61
 	urlCopy := url.URL{
62 62
 		Path:   resp.Request.URL.Path,
63
-		Host:   resp.Request.URL.Host,
63
+		Host:   strings.ToLower(resp.Request.URL.Host),
64 64
 		Scheme: resp.Request.URL.Scheme,
65 65
 	}
66 66
 	m[urlCopy.String()] = challenges
67
-
68 67
 	return nil
69 68
 }
70 69
 
... ...
@@ -15,9 +15,15 @@ import (
15 15
 	"github.com/docker/distribution/registry/client/transport"
16 16
 )
17 17
 
18
-// ErrNoBasicAuthCredentials is returned if a request can't be authorized with
19
-// basic auth due to lack of credentials.
20
-var ErrNoBasicAuthCredentials = errors.New("no basic auth credentials")
18
+var (
19
+	// ErrNoBasicAuthCredentials is returned if a request can't be authorized with
20
+	// basic auth due to lack of credentials.
21
+	ErrNoBasicAuthCredentials = errors.New("no basic auth credentials")
22
+
23
+	// ErrNoToken is returned if a request is successful but the body does not
24
+	// contain an authorization token.
25
+	ErrNoToken = errors.New("authorization server did not include a token in the response")
26
+)
21 27
 
22 28
 const defaultClientID = "registry-client"
23 29
 
... ...
@@ -77,9 +83,7 @@ func (ea *endpointAuthorizer) ModifyRequest(req *http.Request) error {
77 77
 		Path:   req.URL.Path[:v2Root+4],
78 78
 	}
79 79
 
80
-	pingEndpoint := ping.String()
81
-
82
-	challenges, err := ea.challenges.GetChallenges(pingEndpoint)
80
+	challenges, err := ea.challenges.GetChallenges(ping)
83 81
 	if err != nil {
84 82
 		return err
85 83
 	}
... ...
@@ -404,7 +408,7 @@ func (th *tokenHandler) fetchTokenWithBasicAuth(realm *url.URL, service string,
404 404
 	}
405 405
 
406 406
 	if tr.Token == "" {
407
-		return "", time.Time{}, errors.New("authorization server did not include a token in the response")
407
+		return "", time.Time{}, ErrNoToken
408 408
 	}
409 409
 
410 410
 	if tr.ExpiresIn < minimumTokenLifetimeSeconds {
... ...
@@ -6,7 +6,6 @@ import (
6 6
 	"io"
7 7
 	"io/ioutil"
8 8
 	"net/http"
9
-	"os"
10 9
 	"time"
11 10
 
12 11
 	"github.com/docker/distribution"
... ...
@@ -104,21 +103,8 @@ func (hbu *httpBlobUpload) Write(p []byte) (n int, err error) {
104 104
 
105 105
 }
106 106
 
107
-func (hbu *httpBlobUpload) Seek(offset int64, whence int) (int64, error) {
108
-	newOffset := hbu.offset
109
-
110
-	switch whence {
111
-	case os.SEEK_CUR:
112
-		newOffset += int64(offset)
113
-	case os.SEEK_END:
114
-		newOffset += int64(offset)
115
-	case os.SEEK_SET:
116
-		newOffset = int64(offset)
117
-	}
118
-
119
-	hbu.offset = newOffset
120
-
121
-	return hbu.offset, nil
107
+func (hbu *httpBlobUpload) Size() int64 {
108
+	return hbu.offset
122 109
 }
123 110
 
124 111
 func (hbu *httpBlobUpload) ID() string {
... ...
@@ -2,6 +2,7 @@ package client
2 2
 
3 3
 import (
4 4
 	"encoding/json"
5
+	"errors"
5 6
 	"fmt"
6 7
 	"io"
7 8
 	"io/ioutil"
... ...
@@ -10,6 +11,10 @@ import (
10 10
 	"github.com/docker/distribution/registry/api/errcode"
11 11
 )
12 12
 
13
+// ErrNoErrorsInBody is returned when a HTTP response body parses to an empty
14
+// errcode.Errors slice.
15
+var ErrNoErrorsInBody = errors.New("no error details found in HTTP response body")
16
+
13 17
 // UnexpectedHTTPStatusError is returned when an unexpected HTTP status is
14 18
 // returned when making a registry api call.
15 19
 type UnexpectedHTTPStatusError struct {
... ...
@@ -17,18 +22,19 @@ type UnexpectedHTTPStatusError struct {
17 17
 }
18 18
 
19 19
 func (e *UnexpectedHTTPStatusError) Error() string {
20
-	return fmt.Sprintf("Received unexpected HTTP status: %s", e.Status)
20
+	return fmt.Sprintf("received unexpected HTTP status: %s", e.Status)
21 21
 }
22 22
 
23 23
 // UnexpectedHTTPResponseError is returned when an expected HTTP status code
24 24
 // is returned, but the content was unexpected and failed to be parsed.
25 25
 type UnexpectedHTTPResponseError struct {
26
-	ParseErr error
27
-	Response []byte
26
+	ParseErr   error
27
+	StatusCode int
28
+	Response   []byte
28 29
 }
29 30
 
30 31
 func (e *UnexpectedHTTPResponseError) Error() string {
31
-	return fmt.Sprintf("Error parsing HTTP response: %s: %q", e.ParseErr.Error(), string(e.Response))
32
+	return fmt.Sprintf("error parsing HTTP %d response body: %s: %q", e.StatusCode, e.ParseErr.Error(), string(e.Response))
32 33
 }
33 34
 
34 35
 func parseHTTPErrorResponse(statusCode int, r io.Reader) error {
... ...
@@ -53,10 +59,22 @@ func parseHTTPErrorResponse(statusCode int, r io.Reader) error {
53 53
 
54 54
 	if err := json.Unmarshal(body, &errors); err != nil {
55 55
 		return &UnexpectedHTTPResponseError{
56
-			ParseErr: err,
57
-			Response: body,
56
+			ParseErr:   err,
57
+			StatusCode: statusCode,
58
+			Response:   body,
59
+		}
60
+	}
61
+
62
+	if len(errors) == 0 {
63
+		// If there was no error specified in the body, return
64
+		// UnexpectedHTTPResponseError.
65
+		return &UnexpectedHTTPResponseError{
66
+			ParseErr:   ErrNoErrorsInBody,
67
+			StatusCode: statusCode,
68
+			Response:   body,
58 69
 		}
59 70
 	}
71
+
60 72
 	return errors
61 73
 }
62 74
 
... ...
@@ -308,6 +308,7 @@ check:
308 308
 	if err != nil {
309 309
 		return distribution.Descriptor{}, err
310 310
 	}
311
+	defer resp.Body.Close()
311 312
 
312 313
 	switch {
313 314
 	case resp.StatusCode >= 200 && resp.StatusCode < 400: