Merge pull request #21283 from runcom/update-test

vendor docker/distribution d06d6d3b09
This commit is contained in:
Alexander Morozov 2016-03-21 09:46:28 -07:00
commit a05fdd6806
12 changed files with 110 additions and 62 deletions

View File

@ -93,7 +93,8 @@ func retryOnError(err error) error {
return xfer.DoNotRetry{Err: err} return xfer.DoNotRetry{Err: err}
} }
case *url.Error: case *url.Error:
if v.Err == auth.ErrNoBasicAuthCredentials { switch v.Err {
case auth.ErrNoBasicAuthCredentials, auth.ErrNoToken:
return xfer.DoNotRetry{Err: v.Err} return xfer.DoNotRetry{Err: v.Err}
} }
return retryOnError(v.Err) return retryOnError(v.Err)

View File

@ -48,7 +48,7 @@ clone git github.com/boltdb/bolt v1.1.0
clone git github.com/miekg/dns 75e6e86cc601825c5dbcd4e0c209eab180997cd7 clone git github.com/miekg/dns 75e6e86cc601825c5dbcd4e0c209eab180997cd7
# get graph and distribution packages # get graph and distribution packages
clone git github.com/docker/distribution db17a23b961978730892e12a0c6051d43a31aab3 clone git github.com/docker/distribution d06d6d3b093302c02a93153ac7b06ebc0ffd1793
clone git github.com/vbatts/tar-split v0.9.11 clone git github.com/vbatts/tar-split v0.9.11
# get desired notary commit, might also need to be updated in Dockerfile # get desired notary commit, might also need to be updated in Dockerfile

View File

@ -546,35 +546,78 @@ func (s *DockerSuite) TestPushToCentralRegistryUnauthorized(c *check.C) {
dockerCmd(c, "tag", "busybox", repoName) dockerCmd(c, "tag", "busybox", repoName)
out, _, err := dockerCmdWithError("push", repoName) out, _, err := dockerCmdWithError("push", repoName)
c.Assert(err, check.NotNil, check.Commentf(out)) c.Assert(err, check.NotNil, check.Commentf(out))
c.Assert(out, check.Not(checker.Contains), "Retrying")
c.Assert(out, checker.Contains, "unauthorized: access to the requested resource is not authorized") c.Assert(out, checker.Contains, "unauthorized: access to the requested resource is not authorized")
} }
func (s *DockerRegistryAuthTokenSuite) TestPushTokenServiceUnauthResponse(c *check.C) { func getTestTokenService(status int, body string) *httptest.Server {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusUnauthorized) w.WriteHeader(status)
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"errors": [{"Code":"UNAUTHORIZED", "message": "a message", "detail": null}]}`)) w.Write([]byte(body))
})) }))
}
func (s *DockerRegistryAuthTokenSuite) TestPushTokenServiceUnauthResponse(c *check.C) {
ts := getTestTokenService(http.StatusUnauthorized, `{"errors": [{"Code":"UNAUTHORIZED", "message": "a message", "detail": null}]}`)
defer ts.Close() defer ts.Close()
s.setupRegistryWithTokenService(c, ts.URL) s.setupRegistryWithTokenService(c, ts.URL)
repoName := fmt.Sprintf("%s/busybox", privateRegistryURL) repoName := fmt.Sprintf("%s/busybox", privateRegistryURL)
dockerCmd(c, "tag", "busybox", repoName) dockerCmd(c, "tag", "busybox", repoName)
out, _, err := dockerCmdWithError("push", repoName) out, _, err := dockerCmdWithError("push", repoName)
c.Assert(err, check.NotNil, check.Commentf(out)) c.Assert(err, check.NotNil, check.Commentf(out))
c.Assert(out, checker.Not(checker.Contains), "Retrying")
c.Assert(out, checker.Contains, "unauthorized: a message") c.Assert(out, checker.Contains, "unauthorized: a message")
} }
func (s *DockerRegistryAuthTokenSuite) TestPushMisconfiguredTokenServiceResponse(c *check.C) { func (s *DockerRegistryAuthTokenSuite) TestPushMisconfiguredTokenServiceResponseUnauthorized(c *check.C) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ts := getTestTokenService(http.StatusUnauthorized, `{"error": "unauthorized"}`)
w.WriteHeader(http.StatusUnauthorized)
w.Header().Set("Content-Type", "application/json")
// this will make the daemon panics if no check is performed in retryOnError
w.Write([]byte(`{"error": "unauthorized"}`))
}))
defer ts.Close() defer ts.Close()
s.setupRegistryWithTokenService(c, ts.URL) s.setupRegistryWithTokenService(c, ts.URL)
repoName := fmt.Sprintf("%s/busybox", privateRegistryURL) repoName := fmt.Sprintf("%s/busybox", privateRegistryURL)
dockerCmd(c, "tag", "busybox", repoName) dockerCmd(c, "tag", "busybox", repoName)
out, _, err := dockerCmdWithError("push", repoName) out, _, err := dockerCmdWithError("push", repoName)
c.Assert(err, check.NotNil, check.Commentf(out)) c.Assert(err, check.NotNil, check.Commentf(out))
c.Assert(out, checker.Not(checker.Contains), "Retrying")
split := strings.Split(out, "\n")
c.Assert(split[len(split)-2], check.Equals, "unauthorized: authentication required")
}
func (s *DockerRegistryAuthTokenSuite) TestPushMisconfiguredTokenServiceResponseError(c *check.C) {
ts := getTestTokenService(http.StatusInternalServerError, `{"error": "unexpected"}`)
defer ts.Close()
s.setupRegistryWithTokenService(c, ts.URL)
repoName := fmt.Sprintf("%s/busybox", privateRegistryURL)
dockerCmd(c, "tag", "busybox", repoName)
out, _, err := dockerCmdWithError("push", repoName)
c.Assert(err, check.NotNil, check.Commentf(out))
c.Assert(out, checker.Contains, "Retrying")
split := strings.Split(out, "\n")
c.Assert(split[len(split)-2], check.Equals, "received unexpected HTTP status: 500 Internal Server Error")
}
func (s *DockerRegistryAuthTokenSuite) TestPushMisconfiguredTokenServiceResponseUnparsable(c *check.C) {
ts := getTestTokenService(http.StatusForbidden, `no way`)
defer ts.Close()
s.setupRegistryWithTokenService(c, ts.URL)
repoName := fmt.Sprintf("%s/busybox", privateRegistryURL)
dockerCmd(c, "tag", "busybox", repoName)
out, _, err := dockerCmdWithError("push", repoName)
c.Assert(err, check.NotNil, check.Commentf(out))
c.Assert(out, checker.Not(checker.Contains), "Retrying")
split := strings.Split(out, "\n")
c.Assert(split[len(split)-2], checker.Contains, "error parsing HTTP 403 response body: ")
}
func (s *DockerRegistryAuthTokenSuite) TestPushMisconfiguredTokenServiceResponseNoToken(c *check.C) {
ts := getTestTokenService(http.StatusOK, `{"something": "wrong"}`)
defer ts.Close()
s.setupRegistryWithTokenService(c, ts.URL)
repoName := fmt.Sprintf("%s/busybox", privateRegistryURL)
dockerCmd(c, "tag", "busybox", repoName)
out, _, err := dockerCmdWithError("push", repoName)
c.Assert(err, check.NotNil, check.Commentf(out))
c.Assert(out, checker.Not(checker.Contains), "Retrying")
split := strings.Split(out, "\n")
c.Assert(split[len(split)-2], check.Equals, "authorization server did not include a token in the response")
} }

View File

@ -90,7 +90,7 @@ It's mandatory to:
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. 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.
Have a look at a great, successful contribution: the [Ceph driver PR](https://github.com/docker/distribution/pull/443) Have a look at a great, successful contribution: the [Swift driver PR](https://github.com/docker/distribution/pull/493)
## Coding Style ## Coding Style

View File

@ -1,12 +1,12 @@
FROM golang:1.5.3 FROM golang:1.5.3
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y librados-dev apache2-utils && \ apt-get install -y apache2-utils && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
ENV DISTRIBUTION_DIR /go/src/github.com/docker/distribution ENV DISTRIBUTION_DIR /go/src/github.com/docker/distribution
ENV GOPATH $DISTRIBUTION_DIR/Godeps/_workspace:$GOPATH ENV GOPATH $DISTRIBUTION_DIR/Godeps/_workspace:$GOPATH
ENV DOCKER_BUILDTAGS include_rados include_oss include_gcs ENV DOCKER_BUILDTAGS include_oss include_gcs
WORKDIR $DISTRIBUTION_DIR WORKDIR $DISTRIBUTION_DIR
COPY . $DISTRIBUTION_DIR COPY . $DISTRIBUTION_DIR

View File

@ -189,9 +189,11 @@ type BlobCreateOption interface {
// BlobWriteService.Resume. If supported by the store, a writer can be // BlobWriteService.Resume. If supported by the store, a writer can be
// recovered with the id. // recovered with the id.
type BlobWriter interface { type BlobWriter interface {
io.WriteSeeker io.WriteCloser
io.ReaderFrom io.ReaderFrom
io.Closer
// Size returns the number of bytes written to this blob.
Size() int64
// ID returns the identifier for this writer. The ID can be used with the // ID returns the identifier for this writer. The ID can be used with the
// Blob service to later resume the write. // Blob service to later resume the write.
@ -216,9 +218,6 @@ type BlobWriter interface {
// result in a no-op. This allows use of Cancel in a defer statement, // result in a no-op. This allows use of Cancel in a defer statement,
// increasing the assurance that it is correctly called. // increasing the assurance that it is correctly called.
Cancel(ctx context.Context) error Cancel(ctx context.Context) error
// Get a reader to the blob being written by this BlobWriter
Reader() (io.ReadCloser, error)
} }
// BlobService combines the operations to access, read and write blobs. This // BlobService combines the operations to access, read and write blobs. This

View File

@ -3,9 +3,6 @@ machine:
pre: pre:
# Install gvm # Install gvm
- bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/1.0.22/binscripts/gvm-installer) - bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/1.0.22/binscripts/gvm-installer)
# Install ceph to test rados driver & create pool
- sudo -i ~/distribution/contrib/ceph/ci-setup.sh
- ceph osd pool create docker-distribution 1
# Install codecov for coverage # Install codecov for coverage
- pip install --user codecov - pip install --user codecov
@ -19,11 +16,9 @@ machine:
BASE_DIR: src/github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME BASE_DIR: src/github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME
# Trick circle brainflat "no absolute path" behavior # Trick circle brainflat "no absolute path" behavior
BASE_STABLE: ../../../$HOME/.gvm/pkgsets/stable/global/$BASE_DIR BASE_STABLE: ../../../$HOME/.gvm/pkgsets/stable/global/$BASE_DIR
DOCKER_BUILDTAGS: "include_rados include_oss include_gcs" DOCKER_BUILDTAGS: "include_oss include_gcs"
# Workaround Circle parsing dumb bugs and/or YAML wonkyness # Workaround Circle parsing dumb bugs and/or YAML wonkyness
CIRCLE_PAIN: "mode: set" CIRCLE_PAIN: "mode: set"
# Ceph config
RADOS_POOL: "docker-distribution"
hosts: hosts:
# Not used yet # Not used yet

View File

@ -25,7 +25,7 @@ type Challenge struct {
type ChallengeManager interface { type ChallengeManager interface {
// GetChallenges returns the challenges for the given // GetChallenges returns the challenges for the given
// endpoint URL. // endpoint URL.
GetChallenges(endpoint string) ([]Challenge, error) GetChallenges(endpoint url.URL) ([]Challenge, error)
// AddResponse adds the response to the challenge // AddResponse adds the response to the challenge
// manager. The challenges will be parsed out of // manager. The challenges will be parsed out of
@ -48,8 +48,10 @@ func NewSimpleChallengeManager() ChallengeManager {
type simpleChallengeManager map[string][]Challenge type simpleChallengeManager map[string][]Challenge
func (m simpleChallengeManager) GetChallenges(endpoint string) ([]Challenge, error) { func (m simpleChallengeManager) GetChallenges(endpoint url.URL) ([]Challenge, error) {
challenges := m[endpoint] endpoint.Host = strings.ToLower(endpoint.Host)
challenges := m[endpoint.String()]
return challenges, nil return challenges, nil
} }
@ -60,11 +62,10 @@ func (m simpleChallengeManager) AddResponse(resp *http.Response) error {
} }
urlCopy := url.URL{ urlCopy := url.URL{
Path: resp.Request.URL.Path, Path: resp.Request.URL.Path,
Host: resp.Request.URL.Host, Host: strings.ToLower(resp.Request.URL.Host),
Scheme: resp.Request.URL.Scheme, Scheme: resp.Request.URL.Scheme,
} }
m[urlCopy.String()] = challenges m[urlCopy.String()] = challenges
return nil return nil
} }

View File

@ -15,9 +15,15 @@ import (
"github.com/docker/distribution/registry/client/transport" "github.com/docker/distribution/registry/client/transport"
) )
var (
// ErrNoBasicAuthCredentials is returned if a request can't be authorized with // ErrNoBasicAuthCredentials is returned if a request can't be authorized with
// basic auth due to lack of credentials. // basic auth due to lack of credentials.
var ErrNoBasicAuthCredentials = errors.New("no basic auth credentials") ErrNoBasicAuthCredentials = errors.New("no basic auth credentials")
// ErrNoToken is returned if a request is successful but the body does not
// contain an authorization token.
ErrNoToken = errors.New("authorization server did not include a token in the response")
)
const defaultClientID = "registry-client" const defaultClientID = "registry-client"
@ -77,9 +83,7 @@ func (ea *endpointAuthorizer) ModifyRequest(req *http.Request) error {
Path: req.URL.Path[:v2Root+4], Path: req.URL.Path[:v2Root+4],
} }
pingEndpoint := ping.String() challenges, err := ea.challenges.GetChallenges(ping)
challenges, err := ea.challenges.GetChallenges(pingEndpoint)
if err != nil { if err != nil {
return err return err
} }
@ -404,7 +408,7 @@ func (th *tokenHandler) fetchTokenWithBasicAuth(realm *url.URL, service string,
} }
if tr.Token == "" { if tr.Token == "" {
return "", time.Time{}, errors.New("authorization server did not include a token in the response") return "", time.Time{}, ErrNoToken
} }
if tr.ExpiresIn < minimumTokenLifetimeSeconds { if tr.ExpiresIn < minimumTokenLifetimeSeconds {

View File

@ -6,7 +6,6 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"os"
"time" "time"
"github.com/docker/distribution" "github.com/docker/distribution"
@ -104,21 +103,8 @@ func (hbu *httpBlobUpload) Write(p []byte) (n int, err error) {
} }
func (hbu *httpBlobUpload) Seek(offset int64, whence int) (int64, error) { func (hbu *httpBlobUpload) Size() int64 {
newOffset := hbu.offset return hbu.offset
switch whence {
case os.SEEK_CUR:
newOffset += int64(offset)
case os.SEEK_END:
newOffset += int64(offset)
case os.SEEK_SET:
newOffset = int64(offset)
}
hbu.offset = newOffset
return hbu.offset, nil
} }
func (hbu *httpBlobUpload) ID() string { func (hbu *httpBlobUpload) ID() string {

View File

@ -2,6 +2,7 @@ package client
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
@ -10,6 +11,10 @@ import (
"github.com/docker/distribution/registry/api/errcode" "github.com/docker/distribution/registry/api/errcode"
) )
// ErrNoErrorsInBody is returned when a HTTP response body parses to an empty
// errcode.Errors slice.
var ErrNoErrorsInBody = errors.New("no error details found in HTTP response body")
// UnexpectedHTTPStatusError is returned when an unexpected HTTP status is // UnexpectedHTTPStatusError is returned when an unexpected HTTP status is
// returned when making a registry api call. // returned when making a registry api call.
type UnexpectedHTTPStatusError struct { type UnexpectedHTTPStatusError struct {
@ -17,18 +22,19 @@ type UnexpectedHTTPStatusError struct {
} }
func (e *UnexpectedHTTPStatusError) Error() string { func (e *UnexpectedHTTPStatusError) Error() string {
return fmt.Sprintf("Received unexpected HTTP status: %s", e.Status) return fmt.Sprintf("received unexpected HTTP status: %s", e.Status)
} }
// UnexpectedHTTPResponseError is returned when an expected HTTP status code // UnexpectedHTTPResponseError is returned when an expected HTTP status code
// is returned, but the content was unexpected and failed to be parsed. // is returned, but the content was unexpected and failed to be parsed.
type UnexpectedHTTPResponseError struct { type UnexpectedHTTPResponseError struct {
ParseErr error ParseErr error
StatusCode int
Response []byte Response []byte
} }
func (e *UnexpectedHTTPResponseError) Error() string { func (e *UnexpectedHTTPResponseError) Error() string {
return fmt.Sprintf("Error parsing HTTP response: %s: %q", e.ParseErr.Error(), string(e.Response)) return fmt.Sprintf("error parsing HTTP %d response body: %s: %q", e.StatusCode, e.ParseErr.Error(), string(e.Response))
} }
func parseHTTPErrorResponse(statusCode int, r io.Reader) error { func parseHTTPErrorResponse(statusCode int, r io.Reader) error {
@ -54,9 +60,21 @@ func parseHTTPErrorResponse(statusCode int, r io.Reader) error {
if err := json.Unmarshal(body, &errors); err != nil { if err := json.Unmarshal(body, &errors); err != nil {
return &UnexpectedHTTPResponseError{ return &UnexpectedHTTPResponseError{
ParseErr: err, ParseErr: err,
StatusCode: statusCode,
Response: body, Response: body,
} }
} }
if len(errors) == 0 {
// If there was no error specified in the body, return
// UnexpectedHTTPResponseError.
return &UnexpectedHTTPResponseError{
ParseErr: ErrNoErrorsInBody,
StatusCode: statusCode,
Response: body,
}
}
return errors return errors
} }

View File

@ -308,6 +308,7 @@ check:
if err != nil { if err != nil {
return distribution.Descriptor{}, err return distribution.Descriptor{}, err
} }
defer resp.Body.Close()
switch { switch {
case resp.StatusCode >= 200 && resp.StatusCode < 400: case resp.StatusCode >= 200 && resp.StatusCode < 400: