1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

vendor docker/distribution d06d6d3b093302c02a93153ac7b06ebc0ffd1793

- fix and add integration tests

Signed-off-by: Antonio Murdaca <runcom@redhat.com>
This commit is contained in:
Antonio Murdaca 2016-03-19 12:19:43 +01:00
parent ea0025a7e1
commit c48439af7f
11 changed files with 94 additions and 61 deletions

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

@ -549,32 +549,61 @@ func (s *DockerSuite) TestPushToCentralRegistryUnauthorized(c *check.C) {
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: ")
} }

View file

@ -76,7 +76,7 @@ Some simple rules to ensure quick merge:
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. 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.
If this is a major new feature, you should then submit a proposal that describes your technical solution and reasoning. If this is a major new feature, you should then submit a proposal that describes your technical solution and reasoning.
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. 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.
Then you should submit your implementation, clearly linking to the issue (and possible proposal). Then you should submit your implementation, clearly linking to the issue (and possible proposal).
@ -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"
) )
// ErrNoBasicAuthCredentials is returned if a request can't be authorized with var (
// basic auth due to lack of credentials. // ErrNoBasicAuthCredentials is returned if a request can't be authorized with
var ErrNoBasicAuthCredentials = errors.New("no basic auth credentials") // basic auth due to lack of 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
Response []byte StatusCode int
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 {
@ -53,10 +59,22 @@ 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,
Response: body, StatusCode: statusCode,
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: