mirror of
https://github.com/rancher/os.git
synced 2025-09-01 14:48:55 +00:00
Bump a few libs to latest tagged versions
This commit is contained in:
31
vendor/github.com/docker/distribution/registry/client/auth/session.go
generated
vendored
31
vendor/github.com/docker/distribution/registry/client/auth/session.go
generated
vendored
@@ -108,6 +108,8 @@ type tokenHandler struct {
|
||||
tokenLock sync.Mutex
|
||||
tokenCache string
|
||||
tokenExpiration time.Time
|
||||
|
||||
additionalScopes map[string]struct{}
|
||||
}
|
||||
|
||||
// tokenScope represents the scope at which a token will be requested.
|
||||
@@ -145,6 +147,7 @@ func newTokenHandler(transport http.RoundTripper, creds CredentialStore, c clock
|
||||
Scope: scope,
|
||||
Actions: actions,
|
||||
},
|
||||
additionalScopes: map[string]struct{}{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,7 +163,15 @@ func (th *tokenHandler) Scheme() string {
|
||||
}
|
||||
|
||||
func (th *tokenHandler) AuthorizeRequest(req *http.Request, params map[string]string) error {
|
||||
if err := th.refreshToken(params); err != nil {
|
||||
var additionalScopes []string
|
||||
if fromParam := req.URL.Query().Get("from"); fromParam != "" {
|
||||
additionalScopes = append(additionalScopes, tokenScope{
|
||||
Resource: "repository",
|
||||
Scope: fromParam,
|
||||
Actions: []string{"pull"},
|
||||
}.String())
|
||||
}
|
||||
if err := th.refreshToken(params, additionalScopes...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -169,11 +180,18 @@ func (th *tokenHandler) AuthorizeRequest(req *http.Request, params map[string]st
|
||||
return nil
|
||||
}
|
||||
|
||||
func (th *tokenHandler) refreshToken(params map[string]string) error {
|
||||
func (th *tokenHandler) refreshToken(params map[string]string, additionalScopes ...string) error {
|
||||
th.tokenLock.Lock()
|
||||
defer th.tokenLock.Unlock()
|
||||
var addedScopes bool
|
||||
for _, scope := range additionalScopes {
|
||||
if _, ok := th.additionalScopes[scope]; !ok {
|
||||
th.additionalScopes[scope] = struct{}{}
|
||||
addedScopes = true
|
||||
}
|
||||
}
|
||||
now := th.clock.Now()
|
||||
if now.After(th.tokenExpiration) {
|
||||
if now.After(th.tokenExpiration) || addedScopes {
|
||||
tr, err := th.fetchToken(params)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -223,6 +241,10 @@ func (th *tokenHandler) fetchToken(params map[string]string) (token *tokenRespon
|
||||
reqParams.Add("scope", scopeField)
|
||||
}
|
||||
|
||||
for scope := range th.additionalScopes {
|
||||
reqParams.Add("scope", scope)
|
||||
}
|
||||
|
||||
if th.creds != nil {
|
||||
username, password := th.creds.Basic(realmURL)
|
||||
if username != "" && password != "" {
|
||||
@@ -240,7 +262,8 @@ func (th *tokenHandler) fetchToken(params map[string]string) (token *tokenRespon
|
||||
defer resp.Body.Close()
|
||||
|
||||
if !client.SuccessStatus(resp.StatusCode) {
|
||||
return nil, fmt.Errorf("token auth attempt for registry: %s request failed with status: %d %s", req.URL, resp.StatusCode, http.StatusText(resp.StatusCode))
|
||||
err := client.HandleErrorResponse(resp)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(resp.Body)
|
||||
|
2
vendor/github.com/docker/distribution/registry/client/blob_writer.go
generated
vendored
2
vendor/github.com/docker/distribution/registry/client/blob_writer.go
generated
vendored
@@ -33,7 +33,7 @@ func (hbu *httpBlobUpload) handleErrorResponse(resp *http.Response) error {
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
return distribution.ErrBlobUploadUnknown
|
||||
}
|
||||
return handleErrorResponse(resp)
|
||||
return HandleErrorResponse(resp)
|
||||
}
|
||||
|
||||
func (hbu *httpBlobUpload) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
|
25
vendor/github.com/docker/distribution/registry/client/errors.go
generated
vendored
25
vendor/github.com/docker/distribution/registry/client/errors.go
generated
vendored
@@ -31,13 +31,26 @@ func (e *UnexpectedHTTPResponseError) Error() string {
|
||||
return fmt.Sprintf("Error parsing HTTP response: %s: %q", e.ParseErr.Error(), string(e.Response))
|
||||
}
|
||||
|
||||
func parseHTTPErrorResponse(r io.Reader) error {
|
||||
func parseHTTPErrorResponse(statusCode int, r io.Reader) error {
|
||||
var errors errcode.Errors
|
||||
body, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// For backward compatibility, handle irregularly formatted
|
||||
// messages that contain a "details" field.
|
||||
var detailsErr struct {
|
||||
Details string `json:"details"`
|
||||
}
|
||||
err = json.Unmarshal(body, &detailsErr)
|
||||
if err == nil && detailsErr.Details != "" {
|
||||
if statusCode == http.StatusUnauthorized {
|
||||
return errcode.ErrorCodeUnauthorized.WithMessage(detailsErr.Details)
|
||||
}
|
||||
return errcode.ErrorCodeUnknown.WithMessage(detailsErr.Details)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(body, &errors); err != nil {
|
||||
return &UnexpectedHTTPResponseError{
|
||||
ParseErr: err,
|
||||
@@ -47,16 +60,20 @@ func parseHTTPErrorResponse(r io.Reader) error {
|
||||
return errors
|
||||
}
|
||||
|
||||
func handleErrorResponse(resp *http.Response) error {
|
||||
// HandleErrorResponse returns error parsed from HTTP response for an
|
||||
// unsuccessful HTTP response code (in the range 400 - 499 inclusive). An
|
||||
// UnexpectedHTTPStatusError returned for response code outside of expected
|
||||
// range.
|
||||
func HandleErrorResponse(resp *http.Response) error {
|
||||
if resp.StatusCode == 401 {
|
||||
err := parseHTTPErrorResponse(resp.Body)
|
||||
err := parseHTTPErrorResponse(resp.StatusCode, resp.Body)
|
||||
if uErr, ok := err.(*UnexpectedHTTPResponseError); ok {
|
||||
return errcode.ErrorCodeUnauthorized.WithDetail(uErr.Response)
|
||||
}
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode >= 400 && resp.StatusCode < 500 {
|
||||
return parseHTTPErrorResponse(resp.Body)
|
||||
return parseHTTPErrorResponse(resp.StatusCode, resp.Body)
|
||||
}
|
||||
return &UnexpectedHTTPStatusError{Status: resp.Status}
|
||||
}
|
||||
|
89
vendor/github.com/docker/distribution/registry/client/errors_test.go
generated
vendored
Normal file
89
vendor/github.com/docker/distribution/registry/client/errors_test.go
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type nopCloser struct {
|
||||
io.Reader
|
||||
}
|
||||
|
||||
func (nopCloser) Close() error { return nil }
|
||||
|
||||
func TestHandleErrorResponse401ValidBody(t *testing.T) {
|
||||
json := "{\"errors\":[{\"code\":\"UNAUTHORIZED\",\"message\":\"action requires authentication\"}]}"
|
||||
response := &http.Response{
|
||||
Status: "401 Unauthorized",
|
||||
StatusCode: 401,
|
||||
Body: nopCloser{bytes.NewBufferString(json)},
|
||||
}
|
||||
err := HandleErrorResponse(response)
|
||||
|
||||
expectedMsg := "unauthorized: action requires authentication"
|
||||
if !strings.Contains(err.Error(), expectedMsg) {
|
||||
t.Errorf("Expected \"%s\", got: \"%s\"", expectedMsg, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleErrorResponse401WithInvalidBody(t *testing.T) {
|
||||
json := "{invalid json}"
|
||||
response := &http.Response{
|
||||
Status: "401 Unauthorized",
|
||||
StatusCode: 401,
|
||||
Body: nopCloser{bytes.NewBufferString(json)},
|
||||
}
|
||||
err := HandleErrorResponse(response)
|
||||
|
||||
expectedMsg := "unauthorized: authentication required"
|
||||
if !strings.Contains(err.Error(), expectedMsg) {
|
||||
t.Errorf("Expected \"%s\", got: \"%s\"", expectedMsg, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleErrorResponseExpectedStatusCode400ValidBody(t *testing.T) {
|
||||
json := "{\"errors\":[{\"code\":\"DIGEST_INVALID\",\"message\":\"provided digest does not match\"}]}"
|
||||
response := &http.Response{
|
||||
Status: "400 Bad Request",
|
||||
StatusCode: 400,
|
||||
Body: nopCloser{bytes.NewBufferString(json)},
|
||||
}
|
||||
err := HandleErrorResponse(response)
|
||||
|
||||
expectedMsg := "digest invalid: provided digest does not match"
|
||||
if !strings.Contains(err.Error(), expectedMsg) {
|
||||
t.Errorf("Expected \"%s\", got: \"%s\"", expectedMsg, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleErrorResponseExpectedStatusCode404InvalidBody(t *testing.T) {
|
||||
json := "{invalid json}"
|
||||
response := &http.Response{
|
||||
Status: "404 Not Found",
|
||||
StatusCode: 404,
|
||||
Body: nopCloser{bytes.NewBufferString(json)},
|
||||
}
|
||||
err := HandleErrorResponse(response)
|
||||
|
||||
expectedMsg := "Error parsing HTTP response: invalid character 'i' looking for beginning of object key string: \"{invalid json}\""
|
||||
if !strings.Contains(err.Error(), expectedMsg) {
|
||||
t.Errorf("Expected \"%s\", got: \"%s\"", expectedMsg, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleErrorResponseUnexpectedStatusCode501(t *testing.T) {
|
||||
response := &http.Response{
|
||||
Status: "501 Not Implemented",
|
||||
StatusCode: 501,
|
||||
Body: nopCloser{bytes.NewBufferString("{\"Error Encountered\" : \"Function not implemented.\"}")},
|
||||
}
|
||||
err := HandleErrorResponse(response)
|
||||
|
||||
expectedMsg := "Received unexpected HTTP status: 501 Not Implemented"
|
||||
if !strings.Contains(err.Error(), expectedMsg) {
|
||||
t.Errorf("Expected \"%s\", got: \"%s\"", expectedMsg, err.Error())
|
||||
}
|
||||
}
|
469
vendor/github.com/docker/distribution/registry/client/repository.go
generated
vendored
469
vendor/github.com/docker/distribution/registry/client/repository.go
generated
vendored
@@ -3,6 +3,7 @@ package client
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -14,7 +15,6 @@ import (
|
||||
"github.com/docker/distribution"
|
||||
"github.com/docker/distribution/context"
|
||||
"github.com/docker/distribution/digest"
|
||||
"github.com/docker/distribution/manifest/schema1"
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/docker/distribution/registry/api/v2"
|
||||
"github.com/docker/distribution/registry/client/transport"
|
||||
@@ -91,18 +91,14 @@ func (r *registry) Repositories(ctx context.Context, entries []string, last stri
|
||||
returnErr = io.EOF
|
||||
}
|
||||
} else {
|
||||
return 0, handleErrorResponse(resp)
|
||||
return 0, HandleErrorResponse(resp)
|
||||
}
|
||||
|
||||
return numFilled, returnErr
|
||||
}
|
||||
|
||||
// NewRepository creates a new Repository for the given repository name and base URL.
|
||||
func NewRepository(ctx context.Context, name, baseURL string, transport http.RoundTripper) (distribution.Repository, error) {
|
||||
if _, err := reference.ParseNamed(name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func NewRepository(ctx context.Context, name reference.Named, baseURL string, transport http.RoundTripper) (distribution.Repository, error) {
|
||||
ub, err := v2.NewURLBuilderFromString(baseURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -125,21 +121,21 @@ type repository struct {
|
||||
client *http.Client
|
||||
ub *v2.URLBuilder
|
||||
context context.Context
|
||||
name string
|
||||
name reference.Named
|
||||
}
|
||||
|
||||
func (r *repository) Name() string {
|
||||
func (r *repository) Name() reference.Named {
|
||||
return r.name
|
||||
}
|
||||
|
||||
func (r *repository) Blobs(ctx context.Context) distribution.BlobStore {
|
||||
statter := &blobStatter{
|
||||
name: r.Name(),
|
||||
name: r.name,
|
||||
ub: r.ub,
|
||||
client: r.client,
|
||||
}
|
||||
return &blobs{
|
||||
name: r.Name(),
|
||||
name: r.name,
|
||||
ub: r.ub,
|
||||
client: r.client,
|
||||
statter: cache.NewCachedBlobStatter(memory.NewInMemoryBlobDescriptorCacheProvider(), statter),
|
||||
@@ -149,81 +145,166 @@ func (r *repository) Blobs(ctx context.Context) distribution.BlobStore {
|
||||
func (r *repository) Manifests(ctx context.Context, options ...distribution.ManifestServiceOption) (distribution.ManifestService, error) {
|
||||
// todo(richardscothern): options should be sent over the wire
|
||||
return &manifests{
|
||||
name: r.Name(),
|
||||
name: r.name,
|
||||
ub: r.ub,
|
||||
client: r.client,
|
||||
etags: make(map[string]string),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *repository) Signatures() distribution.SignatureService {
|
||||
ms, _ := r.Manifests(r.context)
|
||||
return &signatures{
|
||||
manifests: ms,
|
||||
func (r *repository) Tags(ctx context.Context) distribution.TagService {
|
||||
return &tags{
|
||||
client: r.client,
|
||||
ub: r.ub,
|
||||
context: r.context,
|
||||
name: r.Name(),
|
||||
}
|
||||
}
|
||||
|
||||
type signatures struct {
|
||||
manifests distribution.ManifestService
|
||||
// tags implements remote tagging operations.
|
||||
type tags struct {
|
||||
client *http.Client
|
||||
ub *v2.URLBuilder
|
||||
context context.Context
|
||||
name reference.Named
|
||||
}
|
||||
|
||||
func (s *signatures) Get(dgst digest.Digest) ([][]byte, error) {
|
||||
m, err := s.manifests.Get(dgst)
|
||||
// All returns all tags
|
||||
func (t *tags) All(ctx context.Context) ([]string, error) {
|
||||
var tags []string
|
||||
|
||||
u, err := t.ub.BuildTagsURL(t.name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m.Signatures()
|
||||
}
|
||||
|
||||
func (s *signatures) Put(dgst digest.Digest, signatures ...[]byte) error {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
type manifests struct {
|
||||
name string
|
||||
ub *v2.URLBuilder
|
||||
client *http.Client
|
||||
etags map[string]string
|
||||
}
|
||||
|
||||
func (ms *manifests) Tags() ([]string, error) {
|
||||
u, err := ms.ub.BuildTagsURL(ms.name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return tags, err
|
||||
}
|
||||
|
||||
resp, err := ms.client.Get(u)
|
||||
resp, err := t.client.Get(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return tags, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if SuccessStatus(resp.StatusCode) {
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return tags, err
|
||||
}
|
||||
|
||||
tagsResponse := struct {
|
||||
Tags []string `json:"tags"`
|
||||
}{}
|
||||
if err := json.Unmarshal(b, &tagsResponse); err != nil {
|
||||
return nil, err
|
||||
return tags, err
|
||||
}
|
||||
|
||||
return tagsResponse.Tags, nil
|
||||
tags = tagsResponse.Tags
|
||||
return tags, nil
|
||||
}
|
||||
return nil, handleErrorResponse(resp)
|
||||
return tags, HandleErrorResponse(resp)
|
||||
}
|
||||
|
||||
func (ms *manifests) Exists(dgst digest.Digest) (bool, error) {
|
||||
// Call by Tag endpoint since the API uses the same
|
||||
// URL endpoint for tags and digests.
|
||||
return ms.ExistsByTag(dgst.String())
|
||||
func descriptorFromResponse(response *http.Response) (distribution.Descriptor, error) {
|
||||
desc := distribution.Descriptor{}
|
||||
headers := response.Header
|
||||
|
||||
ctHeader := headers.Get("Content-Type")
|
||||
if ctHeader == "" {
|
||||
return distribution.Descriptor{}, errors.New("missing or empty Content-Type header")
|
||||
}
|
||||
desc.MediaType = ctHeader
|
||||
|
||||
digestHeader := headers.Get("Docker-Content-Digest")
|
||||
if digestHeader == "" {
|
||||
bytes, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return distribution.Descriptor{}, err
|
||||
}
|
||||
_, desc, err := distribution.UnmarshalManifest(ctHeader, bytes)
|
||||
if err != nil {
|
||||
return distribution.Descriptor{}, err
|
||||
}
|
||||
return desc, nil
|
||||
}
|
||||
|
||||
dgst, err := digest.ParseDigest(digestHeader)
|
||||
if err != nil {
|
||||
return distribution.Descriptor{}, err
|
||||
}
|
||||
desc.Digest = dgst
|
||||
|
||||
lengthHeader := headers.Get("Content-Length")
|
||||
if lengthHeader == "" {
|
||||
return distribution.Descriptor{}, errors.New("missing or empty Content-Length header")
|
||||
}
|
||||
length, err := strconv.ParseInt(lengthHeader, 10, 64)
|
||||
if err != nil {
|
||||
return distribution.Descriptor{}, err
|
||||
}
|
||||
desc.Size = length
|
||||
|
||||
return desc, nil
|
||||
|
||||
}
|
||||
|
||||
func (ms *manifests) ExistsByTag(tag string) (bool, error) {
|
||||
u, err := ms.ub.BuildManifestURL(ms.name, tag)
|
||||
// Get issues a HEAD request for a Manifest against its named endpoint in order
|
||||
// to construct a descriptor for the tag. If the registry doesn't support HEADing
|
||||
// a manifest, fallback to GET.
|
||||
func (t *tags) Get(ctx context.Context, tag string) (distribution.Descriptor, error) {
|
||||
ref, err := reference.WithTag(t.name, tag)
|
||||
if err != nil {
|
||||
return distribution.Descriptor{}, err
|
||||
}
|
||||
u, err := t.ub.BuildManifestURL(ref)
|
||||
if err != nil {
|
||||
return distribution.Descriptor{}, err
|
||||
}
|
||||
var attempts int
|
||||
resp, err := t.client.Head(u)
|
||||
|
||||
check:
|
||||
if err != nil {
|
||||
return distribution.Descriptor{}, err
|
||||
}
|
||||
|
||||
switch {
|
||||
case resp.StatusCode >= 200 && resp.StatusCode < 400:
|
||||
return descriptorFromResponse(resp)
|
||||
case resp.StatusCode == http.StatusMethodNotAllowed:
|
||||
resp, err = t.client.Get(u)
|
||||
attempts++
|
||||
if attempts > 1 {
|
||||
return distribution.Descriptor{}, err
|
||||
}
|
||||
goto check
|
||||
default:
|
||||
return distribution.Descriptor{}, HandleErrorResponse(resp)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *tags) Lookup(ctx context.Context, digest distribution.Descriptor) ([]string, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (t *tags) Tag(ctx context.Context, tag string, desc distribution.Descriptor) error {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (t *tags) Untag(ctx context.Context, tag string) error {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
type manifests struct {
|
||||
name reference.Named
|
||||
ub *v2.URLBuilder
|
||||
client *http.Client
|
||||
etags map[string]string
|
||||
}
|
||||
|
||||
func (ms *manifests) Exists(ctx context.Context, dgst digest.Digest) (bool, error) {
|
||||
ref, err := reference.WithDigest(ms.name, dgst)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
u, err := ms.ub.BuildManifestURL(ref)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -238,49 +319,75 @@ func (ms *manifests) ExistsByTag(tag string) (bool, error) {
|
||||
} else if resp.StatusCode == http.StatusNotFound {
|
||||
return false, nil
|
||||
}
|
||||
return false, handleErrorResponse(resp)
|
||||
return false, HandleErrorResponse(resp)
|
||||
}
|
||||
|
||||
func (ms *manifests) Get(dgst digest.Digest) (*schema1.SignedManifest, error) {
|
||||
// Call by Tag endpoint since the API uses the same
|
||||
// URL endpoint for tags and digests.
|
||||
return ms.GetByTag(dgst.String())
|
||||
}
|
||||
|
||||
// AddEtagToTag allows a client to supply an eTag to GetByTag which will be
|
||||
// AddEtagToTag allows a client to supply an eTag to Get which will be
|
||||
// used for a conditional HTTP request. If the eTag matches, a nil manifest
|
||||
// and nil error will be returned. etag is automatically quoted when added to
|
||||
// this map.
|
||||
// and ErrManifestNotModified error will be returned. etag is automatically
|
||||
// quoted when added to this map.
|
||||
func AddEtagToTag(tag, etag string) distribution.ManifestServiceOption {
|
||||
return func(ms distribution.ManifestService) error {
|
||||
if ms, ok := ms.(*manifests); ok {
|
||||
ms.etags[tag] = fmt.Sprintf(`"%s"`, etag)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("etag options is a client-only option")
|
||||
}
|
||||
return etagOption{tag, etag}
|
||||
}
|
||||
|
||||
func (ms *manifests) GetByTag(tag string, options ...distribution.ManifestServiceOption) (*schema1.SignedManifest, error) {
|
||||
type etagOption struct{ tag, etag string }
|
||||
|
||||
func (o etagOption) Apply(ms distribution.ManifestService) error {
|
||||
if ms, ok := ms.(*manifests); ok {
|
||||
ms.etags[o.tag] = fmt.Sprintf(`"%s"`, o.etag)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("etag options is a client-only option")
|
||||
}
|
||||
|
||||
func (ms *manifests) Get(ctx context.Context, dgst digest.Digest, options ...distribution.ManifestServiceOption) (distribution.Manifest, error) {
|
||||
var (
|
||||
digestOrTag string
|
||||
ref reference.Named
|
||||
err error
|
||||
)
|
||||
|
||||
for _, option := range options {
|
||||
err := option(ms)
|
||||
if opt, ok := option.(withTagOption); ok {
|
||||
digestOrTag = opt.tag
|
||||
ref, err = reference.WithTag(ms.name, opt.tag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
err := option.Apply(ms)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if digestOrTag == "" {
|
||||
digestOrTag = dgst.String()
|
||||
ref, err = reference.WithDigest(ms.name, dgst)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
u, err := ms.ub.BuildManifestURL(ms.name, tag)
|
||||
u, err := ms.ub.BuildManifestURL(ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, ok := ms.etags[tag]; ok {
|
||||
req.Header.Set("If-None-Match", ms.etags[tag])
|
||||
for _, t := range distribution.ManifestMediaTypes() {
|
||||
req.Header.Add("Accept", t)
|
||||
}
|
||||
|
||||
if _, ok := ms.etags[digestOrTag]; ok {
|
||||
req.Header.Set("If-None-Match", ms.etags[digestOrTag])
|
||||
}
|
||||
|
||||
resp, err := ms.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -289,45 +396,98 @@ func (ms *manifests) GetByTag(tag string, options ...distribution.ManifestServic
|
||||
if resp.StatusCode == http.StatusNotModified {
|
||||
return nil, distribution.ErrManifestNotModified
|
||||
} else if SuccessStatus(resp.StatusCode) {
|
||||
var sm schema1.SignedManifest
|
||||
decoder := json.NewDecoder(resp.Body)
|
||||
mt := resp.Header.Get("Content-Type")
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
|
||||
if err := decoder.Decode(&sm); err != nil {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &sm, nil
|
||||
m, _, err := distribution.UnmarshalManifest(mt, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
return nil, handleErrorResponse(resp)
|
||||
return nil, HandleErrorResponse(resp)
|
||||
}
|
||||
|
||||
func (ms *manifests) Put(m *schema1.SignedManifest) error {
|
||||
manifestURL, err := ms.ub.BuildManifestURL(ms.name, m.Tag)
|
||||
if err != nil {
|
||||
return err
|
||||
// WithTag allows a tag to be passed into Put which enables the client
|
||||
// to build a correct URL.
|
||||
func WithTag(tag string) distribution.ManifestServiceOption {
|
||||
return withTagOption{tag}
|
||||
}
|
||||
|
||||
type withTagOption struct{ tag string }
|
||||
|
||||
func (o withTagOption) Apply(m distribution.ManifestService) error {
|
||||
if _, ok := m.(*manifests); ok {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("withTagOption is a client-only option")
|
||||
}
|
||||
|
||||
// Put puts a manifest. A tag can be specified using an options parameter which uses some shared state to hold the
|
||||
// tag name in order to build the correct upload URL. This state is written and read under a lock.
|
||||
func (ms *manifests) Put(ctx context.Context, m distribution.Manifest, options ...distribution.ManifestServiceOption) (digest.Digest, error) {
|
||||
ref := ms.name
|
||||
|
||||
for _, option := range options {
|
||||
if opt, ok := option.(withTagOption); ok {
|
||||
var err error
|
||||
ref, err = reference.WithTag(ref, opt.tag)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
err := option.Apply(ms)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// todo(richardscothern): do something with options here when they become applicable
|
||||
|
||||
putRequest, err := http.NewRequest("PUT", manifestURL, bytes.NewReader(m.Raw))
|
||||
manifestURL, err := ms.ub.BuildManifestURL(ref)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
|
||||
mediaType, p, err := m.Payload()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
putRequest, err := http.NewRequest("PUT", manifestURL, bytes.NewReader(p))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
putRequest.Header.Set("Content-Type", mediaType)
|
||||
|
||||
resp, err := ms.client.Do(putRequest)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if SuccessStatus(resp.StatusCode) {
|
||||
// TODO(dmcgowan): make use of digest header
|
||||
return nil
|
||||
dgstHeader := resp.Header.Get("Docker-Content-Digest")
|
||||
dgst, err := digest.ParseDigest(dgstHeader)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return dgst, nil
|
||||
}
|
||||
return handleErrorResponse(resp)
|
||||
|
||||
return "", HandleErrorResponse(resp)
|
||||
}
|
||||
|
||||
func (ms *manifests) Delete(dgst digest.Digest) error {
|
||||
u, err := ms.ub.BuildManifestURL(ms.name, dgst.String())
|
||||
func (ms *manifests) Delete(ctx context.Context, dgst digest.Digest) error {
|
||||
ref, err := reference.WithDigest(ms.name, dgst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u, err := ms.ub.BuildManifestURL(ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -345,11 +505,16 @@ func (ms *manifests) Delete(dgst digest.Digest) error {
|
||||
if SuccessStatus(resp.StatusCode) {
|
||||
return nil
|
||||
}
|
||||
return handleErrorResponse(resp)
|
||||
return HandleErrorResponse(resp)
|
||||
}
|
||||
|
||||
// todo(richardscothern): Restore interface and implementation with merge of #1050
|
||||
/*func (ms *manifests) Enumerate(ctx context.Context, manifests []distribution.Manifest, last distribution.Manifest) (n int, err error) {
|
||||
panic("not supported")
|
||||
}*/
|
||||
|
||||
type blobs struct {
|
||||
name string
|
||||
name reference.Named
|
||||
ub *v2.URLBuilder
|
||||
client *http.Client
|
||||
|
||||
@@ -377,11 +542,7 @@ func (bs *blobs) Stat(ctx context.Context, dgst digest.Digest) (distribution.Des
|
||||
}
|
||||
|
||||
func (bs *blobs) Get(ctx context.Context, dgst digest.Digest) ([]byte, error) {
|
||||
desc, err := bs.Stat(ctx, dgst)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reader, err := bs.Open(ctx, desc.Digest)
|
||||
reader, err := bs.Open(ctx, dgst)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -391,17 +552,22 @@ func (bs *blobs) Get(ctx context.Context, dgst digest.Digest) ([]byte, error) {
|
||||
}
|
||||
|
||||
func (bs *blobs) Open(ctx context.Context, dgst digest.Digest) (distribution.ReadSeekCloser, error) {
|
||||
stat, err := bs.statter.Stat(ctx, dgst)
|
||||
ref, err := reference.WithDigest(bs.name, dgst)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
blobURL, err := bs.ub.BuildBlobURL(ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blobURL, err := bs.ub.BuildBlobURL(bs.name, stat.Digest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return transport.NewHTTPReadSeeker(bs.client, blobURL, stat.Size), nil
|
||||
return transport.NewHTTPReadSeeker(bs.client, blobURL,
|
||||
func(resp *http.Response) error {
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
return distribution.ErrBlobUnknown
|
||||
}
|
||||
return HandleErrorResponse(resp)
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (bs *blobs) ServeBlob(ctx context.Context, w http.ResponseWriter, r *http.Request, dgst digest.Digest) error {
|
||||
@@ -431,8 +597,57 @@ func (bs *blobs) Put(ctx context.Context, mediaType string, p []byte) (distribut
|
||||
return writer.Commit(ctx, desc)
|
||||
}
|
||||
|
||||
func (bs *blobs) Create(ctx context.Context) (distribution.BlobWriter, error) {
|
||||
u, err := bs.ub.BuildBlobUploadURL(bs.name)
|
||||
// createOptions is a collection of blob creation modifiers relevant to general
|
||||
// blob storage intended to be configured by the BlobCreateOption.Apply method.
|
||||
type createOptions struct {
|
||||
Mount struct {
|
||||
ShouldMount bool
|
||||
From reference.Canonical
|
||||
}
|
||||
}
|
||||
|
||||
type optionFunc func(interface{}) error
|
||||
|
||||
func (f optionFunc) Apply(v interface{}) error {
|
||||
return f(v)
|
||||
}
|
||||
|
||||
// WithMountFrom returns a BlobCreateOption which designates that the blob should be
|
||||
// mounted from the given canonical reference.
|
||||
func WithMountFrom(ref reference.Canonical) distribution.BlobCreateOption {
|
||||
return optionFunc(func(v interface{}) error {
|
||||
opts, ok := v.(*createOptions)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected options type: %T", v)
|
||||
}
|
||||
|
||||
opts.Mount.ShouldMount = true
|
||||
opts.Mount.From = ref
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (bs *blobs) Create(ctx context.Context, options ...distribution.BlobCreateOption) (distribution.BlobWriter, error) {
|
||||
var opts createOptions
|
||||
|
||||
for _, option := range options {
|
||||
err := option.Apply(&opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var values []url.Values
|
||||
|
||||
if opts.Mount.ShouldMount {
|
||||
values = append(values, url.Values{"from": {opts.Mount.From.Name()}, "mount": {opts.Mount.From.Digest().String()}})
|
||||
}
|
||||
|
||||
u, err := bs.ub.BuildBlobUploadURL(bs.name, values...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := bs.client.Post(u, "", nil)
|
||||
if err != nil {
|
||||
@@ -440,7 +655,14 @@ func (bs *blobs) Create(ctx context.Context) (distribution.BlobWriter, error) {
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if SuccessStatus(resp.StatusCode) {
|
||||
switch resp.StatusCode {
|
||||
case http.StatusCreated:
|
||||
desc, err := bs.statter.Stat(ctx, opts.Mount.From.Digest())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, distribution.ErrBlobMounted{From: opts.Mount.From, Descriptor: desc}
|
||||
case http.StatusAccepted:
|
||||
// TODO(dmcgowan): Check for invalid UUID
|
||||
uuid := resp.Header.Get("Docker-Upload-UUID")
|
||||
location, err := sanitizeLocation(resp.Header.Get("Location"), u)
|
||||
@@ -455,8 +677,9 @@ func (bs *blobs) Create(ctx context.Context) (distribution.BlobWriter, error) {
|
||||
startedAt: time.Now(),
|
||||
location: location,
|
||||
}, nil
|
||||
default:
|
||||
return nil, HandleErrorResponse(resp)
|
||||
}
|
||||
return nil, handleErrorResponse(resp)
|
||||
}
|
||||
|
||||
func (bs *blobs) Resume(ctx context.Context, id string) (distribution.BlobWriter, error) {
|
||||
@@ -468,13 +691,17 @@ func (bs *blobs) Delete(ctx context.Context, dgst digest.Digest) error {
|
||||
}
|
||||
|
||||
type blobStatter struct {
|
||||
name string
|
||||
name reference.Named
|
||||
ub *v2.URLBuilder
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func (bs *blobStatter) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) {
|
||||
u, err := bs.ub.BuildBlobURL(bs.name, dgst)
|
||||
ref, err := reference.WithDigest(bs.name, dgst)
|
||||
if err != nil {
|
||||
return distribution.Descriptor{}, err
|
||||
}
|
||||
u, err := bs.ub.BuildBlobURL(ref)
|
||||
if err != nil {
|
||||
return distribution.Descriptor{}, err
|
||||
}
|
||||
@@ -487,6 +714,10 @@ func (bs *blobStatter) Stat(ctx context.Context, dgst digest.Digest) (distributi
|
||||
|
||||
if SuccessStatus(resp.StatusCode) {
|
||||
lengthHeader := resp.Header.Get("Content-Length")
|
||||
if lengthHeader == "" {
|
||||
return distribution.Descriptor{}, fmt.Errorf("missing content-length header for request: %s", u)
|
||||
}
|
||||
|
||||
length, err := strconv.ParseInt(lengthHeader, 10, 64)
|
||||
if err != nil {
|
||||
return distribution.Descriptor{}, fmt.Errorf("error parsing content-length: %v", err)
|
||||
@@ -500,7 +731,7 @@ func (bs *blobStatter) Stat(ctx context.Context, dgst digest.Digest) (distributi
|
||||
} else if resp.StatusCode == http.StatusNotFound {
|
||||
return distribution.Descriptor{}, distribution.ErrBlobUnknown
|
||||
}
|
||||
return distribution.Descriptor{}, handleErrorResponse(resp)
|
||||
return distribution.Descriptor{}, HandleErrorResponse(resp)
|
||||
}
|
||||
|
||||
func buildCatalogValues(maxEntries int, last string) url.Values {
|
||||
@@ -518,7 +749,11 @@ func buildCatalogValues(maxEntries int, last string) url.Values {
|
||||
}
|
||||
|
||||
func (bs *blobStatter) Clear(ctx context.Context, dgst digest.Digest) error {
|
||||
blobURL, err := bs.ub.BuildBlobURL(bs.name, dgst)
|
||||
ref, err := reference.WithDigest(bs.name, dgst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
blobURL, err := bs.ub.BuildBlobURL(ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -537,7 +772,7 @@ func (bs *blobStatter) Clear(ctx context.Context, dgst digest.Digest) error {
|
||||
if SuccessStatus(resp.StatusCode) {
|
||||
return nil
|
||||
}
|
||||
return handleErrorResponse(resp)
|
||||
return HandleErrorResponse(resp)
|
||||
}
|
||||
|
||||
func (bs *blobStatter) SetDescriptor(ctx context.Context, dgst digest.Digest, desc distribution.Descriptor) error {
|
||||
|
342
vendor/github.com/docker/distribution/registry/client/repository_test.go
generated
vendored
342
vendor/github.com/docker/distribution/registry/client/repository_test.go
generated
vendored
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/docker/distribution/digest"
|
||||
"github.com/docker/distribution/manifest"
|
||||
"github.com/docker/distribution/manifest/schema1"
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/docker/distribution/registry/api/errcode"
|
||||
"github.com/docker/distribution/testutil"
|
||||
"github.com/docker/distribution/uuid"
|
||||
@@ -38,16 +39,10 @@ func newRandomBlob(size int) (digest.Digest, []byte) {
|
||||
panic("unable to read enough bytes")
|
||||
}
|
||||
|
||||
dgst, err := digest.FromBytes(b)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return dgst, b
|
||||
return digest.FromBytes(b), b
|
||||
}
|
||||
|
||||
func addTestFetch(repo string, dgst digest.Digest, content []byte, m *testutil.RequestResponseMap) {
|
||||
|
||||
*m = append(*m, testutil.RequestResponseMapping{
|
||||
Request: testutil.Request{
|
||||
Method: "GET",
|
||||
@@ -103,11 +98,11 @@ func addTestCatalog(route string, content []byte, link string, m *testutil.Reque
|
||||
func TestBlobDelete(t *testing.T) {
|
||||
dgst, _ := newRandomBlob(1024)
|
||||
var m testutil.RequestResponseMap
|
||||
repo := "test.example.com/repo1"
|
||||
repo, _ := reference.ParseNamed("test.example.com/repo1")
|
||||
m = append(m, testutil.RequestResponseMapping{
|
||||
Request: testutil.Request{
|
||||
Method: "DELETE",
|
||||
Route: "/v2/" + repo + "/blobs/" + dgst.String(),
|
||||
Route: "/v2/" + repo.Name() + "/blobs/" + dgst.String(),
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusAccepted,
|
||||
@@ -142,7 +137,8 @@ func TestBlobFetch(t *testing.T) {
|
||||
defer c()
|
||||
|
||||
ctx := context.Background()
|
||||
r, err := NewRepository(ctx, "test.example.com/repo1", e, nil)
|
||||
repo, _ := reference.ParseNamed("test.example.com/repo1")
|
||||
r, err := NewRepository(ctx, repo, e, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -159,6 +155,59 @@ func TestBlobFetch(t *testing.T) {
|
||||
// TODO(dmcgowan): Test for unknown blob case
|
||||
}
|
||||
|
||||
func TestBlobExistsNoContentLength(t *testing.T) {
|
||||
var m testutil.RequestResponseMap
|
||||
|
||||
repo, _ := reference.ParseNamed("biff")
|
||||
dgst, content := newRandomBlob(1024)
|
||||
m = append(m, testutil.RequestResponseMapping{
|
||||
Request: testutil.Request{
|
||||
Method: "GET",
|
||||
Route: "/v2/" + repo.Name() + "/blobs/" + dgst.String(),
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: content,
|
||||
Headers: http.Header(map[string][]string{
|
||||
// "Content-Length": {fmt.Sprint(len(content))},
|
||||
"Last-Modified": {time.Now().Add(-1 * time.Second).Format(time.ANSIC)},
|
||||
}),
|
||||
},
|
||||
})
|
||||
|
||||
m = append(m, testutil.RequestResponseMapping{
|
||||
Request: testutil.Request{
|
||||
Method: "HEAD",
|
||||
Route: "/v2/" + repo.Name() + "/blobs/" + dgst.String(),
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Headers: http.Header(map[string][]string{
|
||||
// "Content-Length": {fmt.Sprint(len(content))},
|
||||
"Last-Modified": {time.Now().Add(-1 * time.Second).Format(time.ANSIC)},
|
||||
}),
|
||||
},
|
||||
})
|
||||
e, c := testServer(m)
|
||||
defer c()
|
||||
|
||||
ctx := context.Background()
|
||||
r, err := NewRepository(ctx, repo, e, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
l := r.Blobs(ctx)
|
||||
|
||||
_, err = l.Stat(ctx, dgst)
|
||||
if err == nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(err.Error(), "missing content-length heade") {
|
||||
t.Fatalf("Expected missing content-length error message")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestBlobExists(t *testing.T) {
|
||||
d1, b1 := newRandomBlob(1024)
|
||||
var m testutil.RequestResponseMap
|
||||
@@ -168,7 +217,8 @@ func TestBlobExists(t *testing.T) {
|
||||
defer c()
|
||||
|
||||
ctx := context.Background()
|
||||
r, err := NewRepository(ctx, "test.example.com/repo1", e, nil)
|
||||
repo, _ := reference.ParseNamed("test.example.com/repo1")
|
||||
r, err := NewRepository(ctx, repo, e, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -199,18 +249,18 @@ func TestBlobUploadChunked(t *testing.T) {
|
||||
b1[512:513],
|
||||
b1[513:1024],
|
||||
}
|
||||
repo := "test.example.com/uploadrepo"
|
||||
repo, _ := reference.ParseNamed("test.example.com/uploadrepo")
|
||||
uuids := []string{uuid.Generate().String()}
|
||||
m = append(m, testutil.RequestResponseMapping{
|
||||
Request: testutil.Request{
|
||||
Method: "POST",
|
||||
Route: "/v2/" + repo + "/blobs/uploads/",
|
||||
Route: "/v2/" + repo.Name() + "/blobs/uploads/",
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusAccepted,
|
||||
Headers: http.Header(map[string][]string{
|
||||
"Content-Length": {"0"},
|
||||
"Location": {"/v2/" + repo + "/blobs/uploads/" + uuids[0]},
|
||||
"Location": {"/v2/" + repo.Name() + "/blobs/uploads/" + uuids[0]},
|
||||
"Docker-Upload-UUID": {uuids[0]},
|
||||
"Range": {"0-0"},
|
||||
}),
|
||||
@@ -223,14 +273,14 @@ func TestBlobUploadChunked(t *testing.T) {
|
||||
m = append(m, testutil.RequestResponseMapping{
|
||||
Request: testutil.Request{
|
||||
Method: "PATCH",
|
||||
Route: "/v2/" + repo + "/blobs/uploads/" + uuids[i],
|
||||
Route: "/v2/" + repo.Name() + "/blobs/uploads/" + uuids[i],
|
||||
Body: chunk,
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusAccepted,
|
||||
Headers: http.Header(map[string][]string{
|
||||
"Content-Length": {"0"},
|
||||
"Location": {"/v2/" + repo + "/blobs/uploads/" + uuids[i+1]},
|
||||
"Location": {"/v2/" + repo.Name() + "/blobs/uploads/" + uuids[i+1]},
|
||||
"Docker-Upload-UUID": {uuids[i+1]},
|
||||
"Range": {fmt.Sprintf("%d-%d", offset, newOffset-1)},
|
||||
}),
|
||||
@@ -241,7 +291,7 @@ func TestBlobUploadChunked(t *testing.T) {
|
||||
m = append(m, testutil.RequestResponseMapping{
|
||||
Request: testutil.Request{
|
||||
Method: "PUT",
|
||||
Route: "/v2/" + repo + "/blobs/uploads/" + uuids[len(uuids)-1],
|
||||
Route: "/v2/" + repo.Name() + "/blobs/uploads/" + uuids[len(uuids)-1],
|
||||
QueryParams: map[string][]string{
|
||||
"digest": {dgst.String()},
|
||||
},
|
||||
@@ -258,7 +308,7 @@ func TestBlobUploadChunked(t *testing.T) {
|
||||
m = append(m, testutil.RequestResponseMapping{
|
||||
Request: testutil.Request{
|
||||
Method: "HEAD",
|
||||
Route: "/v2/" + repo + "/blobs/" + dgst.String(),
|
||||
Route: "/v2/" + repo.Name() + "/blobs/" + dgst.String(),
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
@@ -314,18 +364,18 @@ func TestBlobUploadChunked(t *testing.T) {
|
||||
func TestBlobUploadMonolithic(t *testing.T) {
|
||||
dgst, b1 := newRandomBlob(1024)
|
||||
var m testutil.RequestResponseMap
|
||||
repo := "test.example.com/uploadrepo"
|
||||
repo, _ := reference.ParseNamed("test.example.com/uploadrepo")
|
||||
uploadID := uuid.Generate().String()
|
||||
m = append(m, testutil.RequestResponseMapping{
|
||||
Request: testutil.Request{
|
||||
Method: "POST",
|
||||
Route: "/v2/" + repo + "/blobs/uploads/",
|
||||
Route: "/v2/" + repo.Name() + "/blobs/uploads/",
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusAccepted,
|
||||
Headers: http.Header(map[string][]string{
|
||||
"Content-Length": {"0"},
|
||||
"Location": {"/v2/" + repo + "/blobs/uploads/" + uploadID},
|
||||
"Location": {"/v2/" + repo.Name() + "/blobs/uploads/" + uploadID},
|
||||
"Docker-Upload-UUID": {uploadID},
|
||||
"Range": {"0-0"},
|
||||
}),
|
||||
@@ -334,13 +384,13 @@ func TestBlobUploadMonolithic(t *testing.T) {
|
||||
m = append(m, testutil.RequestResponseMapping{
|
||||
Request: testutil.Request{
|
||||
Method: "PATCH",
|
||||
Route: "/v2/" + repo + "/blobs/uploads/" + uploadID,
|
||||
Route: "/v2/" + repo.Name() + "/blobs/uploads/" + uploadID,
|
||||
Body: b1,
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusAccepted,
|
||||
Headers: http.Header(map[string][]string{
|
||||
"Location": {"/v2/" + repo + "/blobs/uploads/" + uploadID},
|
||||
"Location": {"/v2/" + repo.Name() + "/blobs/uploads/" + uploadID},
|
||||
"Docker-Upload-UUID": {uploadID},
|
||||
"Content-Length": {"0"},
|
||||
"Docker-Content-Digest": {dgst.String()},
|
||||
@@ -351,7 +401,7 @@ func TestBlobUploadMonolithic(t *testing.T) {
|
||||
m = append(m, testutil.RequestResponseMapping{
|
||||
Request: testutil.Request{
|
||||
Method: "PUT",
|
||||
Route: "/v2/" + repo + "/blobs/uploads/" + uploadID,
|
||||
Route: "/v2/" + repo.Name() + "/blobs/uploads/" + uploadID,
|
||||
QueryParams: map[string][]string{
|
||||
"digest": {dgst.String()},
|
||||
},
|
||||
@@ -368,7 +418,7 @@ func TestBlobUploadMonolithic(t *testing.T) {
|
||||
m = append(m, testutil.RequestResponseMapping{
|
||||
Request: testutil.Request{
|
||||
Method: "HEAD",
|
||||
Route: "/v2/" + repo + "/blobs/" + dgst.String(),
|
||||
Route: "/v2/" + repo.Name() + "/blobs/" + dgst.String(),
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
@@ -419,7 +469,72 @@ func TestBlobUploadMonolithic(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func newRandomSchemaV1Manifest(name, tag string, blobCount int) (*schema1.SignedManifest, digest.Digest, []byte) {
|
||||
func TestBlobMount(t *testing.T) {
|
||||
dgst, content := newRandomBlob(1024)
|
||||
var m testutil.RequestResponseMap
|
||||
repo, _ := reference.ParseNamed("test.example.com/uploadrepo")
|
||||
|
||||
sourceRepo, _ := reference.ParseNamed("test.example.com/sourcerepo")
|
||||
canonicalRef, _ := reference.WithDigest(sourceRepo, dgst)
|
||||
|
||||
m = append(m, testutil.RequestResponseMapping{
|
||||
Request: testutil.Request{
|
||||
Method: "POST",
|
||||
Route: "/v2/" + repo.Name() + "/blobs/uploads/",
|
||||
QueryParams: map[string][]string{"from": {sourceRepo.Name()}, "mount": {dgst.String()}},
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusCreated,
|
||||
Headers: http.Header(map[string][]string{
|
||||
"Content-Length": {"0"},
|
||||
"Location": {"/v2/" + repo.Name() + "/blobs/" + dgst.String()},
|
||||
"Docker-Content-Digest": {dgst.String()},
|
||||
}),
|
||||
},
|
||||
})
|
||||
m = append(m, testutil.RequestResponseMapping{
|
||||
Request: testutil.Request{
|
||||
Method: "HEAD",
|
||||
Route: "/v2/" + repo.Name() + "/blobs/" + dgst.String(),
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Headers: http.Header(map[string][]string{
|
||||
"Content-Length": {fmt.Sprint(len(content))},
|
||||
"Last-Modified": {time.Now().Add(-1 * time.Second).Format(time.ANSIC)},
|
||||
}),
|
||||
},
|
||||
})
|
||||
|
||||
e, c := testServer(m)
|
||||
defer c()
|
||||
|
||||
ctx := context.Background()
|
||||
r, err := NewRepository(ctx, repo, e, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
l := r.Blobs(ctx)
|
||||
|
||||
bw, err := l.Create(ctx, WithMountFrom(canonicalRef))
|
||||
if bw != nil {
|
||||
t.Fatalf("Expected blob writer to be nil, was %v", bw)
|
||||
}
|
||||
|
||||
if ebm, ok := err.(distribution.ErrBlobMounted); ok {
|
||||
if ebm.From.Digest() != dgst {
|
||||
t.Fatalf("Unexpected digest: %s, expected %s", ebm.From.Digest(), dgst)
|
||||
}
|
||||
if ebm.From.Name() != sourceRepo.Name() {
|
||||
t.Fatalf("Unexpected from: %s, expected %s", ebm.From.Name(), sourceRepo)
|
||||
}
|
||||
} else {
|
||||
t.Fatalf("Unexpected error: %v, expected an ErrBlobMounted", err)
|
||||
}
|
||||
}
|
||||
|
||||
func newRandomSchemaV1Manifest(name reference.Named, tag string, blobCount int) (*schema1.SignedManifest, digest.Digest, []byte) {
|
||||
blobs := make([]schema1.FSLayer, blobCount)
|
||||
history := make([]schema1.History, blobCount)
|
||||
|
||||
@@ -431,7 +546,7 @@ func newRandomSchemaV1Manifest(name, tag string, blobCount int) (*schema1.Signed
|
||||
}
|
||||
|
||||
m := schema1.Manifest{
|
||||
Name: name,
|
||||
Name: name.String(),
|
||||
Tag: tag,
|
||||
Architecture: "x86",
|
||||
FSLayers: blobs,
|
||||
@@ -451,24 +566,14 @@ func newRandomSchemaV1Manifest(name, tag string, blobCount int) (*schema1.Signed
|
||||
panic(err)
|
||||
}
|
||||
|
||||
p, err := sm.Payload()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
dgst, err := digest.FromBytes(p)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return sm, dgst, p
|
||||
return sm, digest.FromBytes(sm.Canonical), sm.Canonical
|
||||
}
|
||||
|
||||
func addTestManifestWithEtag(repo, reference string, content []byte, m *testutil.RequestResponseMap, dgst string) {
|
||||
actualDigest, _ := digest.FromBytes(content)
|
||||
func addTestManifestWithEtag(repo reference.Named, reference string, content []byte, m *testutil.RequestResponseMap, dgst string) {
|
||||
actualDigest := digest.FromBytes(content)
|
||||
getReqWithEtag := testutil.Request{
|
||||
Method: "GET",
|
||||
Route: "/v2/" + repo + "/manifests/" + reference,
|
||||
Route: "/v2/" + repo.Name() + "/manifests/" + reference,
|
||||
Headers: http.Header(map[string][]string{
|
||||
"If-None-Match": {fmt.Sprintf(`"%s"`, dgst)},
|
||||
}),
|
||||
@@ -482,6 +587,7 @@ func addTestManifestWithEtag(repo, reference string, content []byte, m *testutil
|
||||
Headers: http.Header(map[string][]string{
|
||||
"Content-Length": {"0"},
|
||||
"Last-Modified": {time.Now().Add(-1 * time.Second).Format(time.ANSIC)},
|
||||
"Content-Type": {schema1.MediaTypeSignedManifest},
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
@@ -491,6 +597,7 @@ func addTestManifestWithEtag(repo, reference string, content []byte, m *testutil
|
||||
Headers: http.Header(map[string][]string{
|
||||
"Content-Length": {fmt.Sprint(len(content))},
|
||||
"Last-Modified": {time.Now().Add(-1 * time.Second).Format(time.ANSIC)},
|
||||
"Content-Type": {schema1.MediaTypeSignedManifest},
|
||||
}),
|
||||
}
|
||||
|
||||
@@ -498,11 +605,11 @@ func addTestManifestWithEtag(repo, reference string, content []byte, m *testutil
|
||||
*m = append(*m, testutil.RequestResponseMapping{Request: getReqWithEtag, Response: getRespWithEtag})
|
||||
}
|
||||
|
||||
func addTestManifest(repo, reference string, content []byte, m *testutil.RequestResponseMap) {
|
||||
func addTestManifest(repo reference.Named, reference string, mediatype string, content []byte, m *testutil.RequestResponseMap) {
|
||||
*m = append(*m, testutil.RequestResponseMapping{
|
||||
Request: testutil.Request{
|
||||
Method: "GET",
|
||||
Route: "/v2/" + repo + "/manifests/" + reference,
|
||||
Route: "/v2/" + repo.Name() + "/manifests/" + reference,
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
@@ -510,19 +617,21 @@ func addTestManifest(repo, reference string, content []byte, m *testutil.Request
|
||||
Headers: http.Header(map[string][]string{
|
||||
"Content-Length": {fmt.Sprint(len(content))},
|
||||
"Last-Modified": {time.Now().Add(-1 * time.Second).Format(time.ANSIC)},
|
||||
"Content-Type": {mediatype},
|
||||
}),
|
||||
},
|
||||
})
|
||||
*m = append(*m, testutil.RequestResponseMapping{
|
||||
Request: testutil.Request{
|
||||
Method: "HEAD",
|
||||
Route: "/v2/" + repo + "/manifests/" + reference,
|
||||
Route: "/v2/" + repo.Name() + "/manifests/" + reference,
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Headers: http.Header(map[string][]string{
|
||||
"Content-Length": {fmt.Sprint(len(content))},
|
||||
"Last-Modified": {time.Now().Add(-1 * time.Second).Format(time.ANSIC)},
|
||||
"Content-Type": {mediatype},
|
||||
}),
|
||||
},
|
||||
})
|
||||
@@ -555,12 +664,18 @@ func checkEqualManifest(m1, m2 *schema1.SignedManifest) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestManifestFetch(t *testing.T) {
|
||||
func TestV1ManifestFetch(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
repo := "test.example.com/repo"
|
||||
repo, _ := reference.ParseNamed("test.example.com/repo")
|
||||
m1, dgst, _ := newRandomSchemaV1Manifest(repo, "latest", 6)
|
||||
var m testutil.RequestResponseMap
|
||||
addTestManifest(repo, dgst.String(), m1.Raw, &m)
|
||||
_, pl, err := m1.Payload()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
addTestManifest(repo, dgst.String(), schema1.MediaTypeSignedManifest, pl, &m)
|
||||
addTestManifest(repo, "latest", schema1.MediaTypeSignedManifest, pl, &m)
|
||||
addTestManifest(repo, "badcontenttype", "text/html", pl, &m)
|
||||
|
||||
e, c := testServer(m)
|
||||
defer c()
|
||||
@@ -574,7 +689,7 @@ func TestManifestFetch(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ok, err := ms.Exists(dgst)
|
||||
ok, err := ms.Exists(ctx, dgst)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -582,17 +697,48 @@ func TestManifestFetch(t *testing.T) {
|
||||
t.Fatal("Manifest does not exist")
|
||||
}
|
||||
|
||||
manifest, err := ms.Get(dgst)
|
||||
manifest, err := ms.Get(ctx, dgst)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := checkEqualManifest(manifest, m1); err != nil {
|
||||
v1manifest, ok := manifest.(*schema1.SignedManifest)
|
||||
if !ok {
|
||||
t.Fatalf("Unexpected manifest type from Get: %T", manifest)
|
||||
}
|
||||
|
||||
if err := checkEqualManifest(v1manifest, m1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
manifest, err = ms.Get(ctx, dgst, WithTag("latest"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
v1manifest, ok = manifest.(*schema1.SignedManifest)
|
||||
if !ok {
|
||||
t.Fatalf("Unexpected manifest type from Get: %T", manifest)
|
||||
}
|
||||
|
||||
if err = checkEqualManifest(v1manifest, m1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
manifest, err = ms.Get(ctx, dgst, WithTag("badcontenttype"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
v1manifest, ok = manifest.(*schema1.SignedManifest)
|
||||
if !ok {
|
||||
t.Fatalf("Unexpected manifest type from Get: %T", manifest)
|
||||
}
|
||||
|
||||
if err = checkEqualManifest(v1manifest, m1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestManifestFetchWithEtag(t *testing.T) {
|
||||
repo := "test.example.com/repo/by/tag"
|
||||
repo, _ := reference.ParseNamed("test.example.com/repo/by/tag")
|
||||
_, d1, p1 := newRandomSchemaV1Manifest(repo, "latest", 6)
|
||||
var m testutil.RequestResponseMap
|
||||
addTestManifestWithEtag(repo, "latest", p1, &m, d1.String())
|
||||
@@ -600,31 +746,36 @@ func TestManifestFetchWithEtag(t *testing.T) {
|
||||
e, c := testServer(m)
|
||||
defer c()
|
||||
|
||||
r, err := NewRepository(context.Background(), repo, e, nil)
|
||||
ctx := context.Background()
|
||||
r, err := NewRepository(ctx, repo, e, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
||||
ms, err := r.Manifests(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = ms.GetByTag("latest", AddEtagToTag("latest", d1.String()))
|
||||
clientManifestService, ok := ms.(*manifests)
|
||||
if !ok {
|
||||
panic("wrong type for client manifest service")
|
||||
}
|
||||
_, err = clientManifestService.Get(ctx, d1, WithTag("latest"), AddEtagToTag("latest", d1.String()))
|
||||
if err != distribution.ErrManifestNotModified {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestManifestDelete(t *testing.T) {
|
||||
repo := "test.example.com/repo/delete"
|
||||
repo, _ := reference.ParseNamed("test.example.com/repo/delete")
|
||||
_, dgst1, _ := newRandomSchemaV1Manifest(repo, "latest", 6)
|
||||
_, dgst2, _ := newRandomSchemaV1Manifest(repo, "latest", 6)
|
||||
var m testutil.RequestResponseMap
|
||||
m = append(m, testutil.RequestResponseMapping{
|
||||
Request: testutil.Request{
|
||||
Method: "DELETE",
|
||||
Route: "/v2/" + repo + "/manifests/" + dgst1.String(),
|
||||
Route: "/v2/" + repo.Name() + "/manifests/" + dgst1.String(),
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusAccepted,
|
||||
@@ -647,24 +798,29 @@ func TestManifestDelete(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := ms.Delete(dgst1); err != nil {
|
||||
if err := ms.Delete(ctx, dgst1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := ms.Delete(dgst2); err == nil {
|
||||
if err := ms.Delete(ctx, dgst2); err == nil {
|
||||
t.Fatal("Expected error deleting unknown manifest")
|
||||
}
|
||||
// TODO(dmcgowan): Check for specific unknown error
|
||||
}
|
||||
|
||||
func TestManifestPut(t *testing.T) {
|
||||
repo := "test.example.com/repo/delete"
|
||||
repo, _ := reference.ParseNamed("test.example.com/repo/delete")
|
||||
m1, dgst, _ := newRandomSchemaV1Manifest(repo, "other", 6)
|
||||
|
||||
_, payload, err := m1.Payload()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var m testutil.RequestResponseMap
|
||||
m = append(m, testutil.RequestResponseMapping{
|
||||
Request: testutil.Request{
|
||||
Method: "PUT",
|
||||
Route: "/v2/" + repo + "/manifests/other",
|
||||
Body: m1.Raw,
|
||||
Route: "/v2/" + repo.Name() + "/manifests/other",
|
||||
Body: payload,
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusAccepted,
|
||||
@@ -688,7 +844,7 @@ func TestManifestPut(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := ms.Put(m1); err != nil {
|
||||
if _, err := ms.Put(ctx, m1, WithTag(m1.Tag)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -696,7 +852,7 @@ func TestManifestPut(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestManifestTags(t *testing.T) {
|
||||
repo := "test.example.com/repo/tags/list"
|
||||
repo, _ := reference.ParseNamed("test.example.com/repo/tags/list")
|
||||
tagsList := []byte(strings.TrimSpace(`
|
||||
{
|
||||
"name": "test.example.com/repo/tags/list",
|
||||
@@ -708,21 +864,22 @@ func TestManifestTags(t *testing.T) {
|
||||
}
|
||||
`))
|
||||
var m testutil.RequestResponseMap
|
||||
m = append(m, testutil.RequestResponseMapping{
|
||||
Request: testutil.Request{
|
||||
Method: "GET",
|
||||
Route: "/v2/" + repo + "/tags/list",
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: tagsList,
|
||||
Headers: http.Header(map[string][]string{
|
||||
"Content-Length": {fmt.Sprint(len(tagsList))},
|
||||
"Last-Modified": {time.Now().Add(-1 * time.Second).Format(time.ANSIC)},
|
||||
}),
|
||||
},
|
||||
})
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
m = append(m, testutil.RequestResponseMapping{
|
||||
Request: testutil.Request{
|
||||
Method: "GET",
|
||||
Route: "/v2/" + repo.Name() + "/tags/list",
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: tagsList,
|
||||
Headers: http.Header(map[string][]string{
|
||||
"Content-Length": {fmt.Sprint(len(tagsList))},
|
||||
"Last-Modified": {time.Now().Add(-1 * time.Second).Format(time.ANSIC)},
|
||||
}),
|
||||
},
|
||||
})
|
||||
}
|
||||
e, c := testServer(m)
|
||||
defer c()
|
||||
|
||||
@@ -730,34 +887,41 @@ func TestManifestTags(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
ms, err := r.Manifests(ctx)
|
||||
tagService := r.Tags(ctx)
|
||||
|
||||
tags, err := tagService.All(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tags, err := ms.Tags()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(tags) != 3 {
|
||||
t.Fatalf("Wrong number of tags returned: %d, expected 3", len(tags))
|
||||
}
|
||||
// TODO(dmcgowan): Check array
|
||||
|
||||
expected := map[string]struct{}{
|
||||
"tag1": {},
|
||||
"tag2": {},
|
||||
"funtag": {},
|
||||
}
|
||||
for _, t := range tags {
|
||||
delete(expected, t)
|
||||
}
|
||||
if len(expected) != 0 {
|
||||
t.Fatalf("unexpected tags returned: %v", expected)
|
||||
}
|
||||
// TODO(dmcgowan): Check for error cases
|
||||
}
|
||||
|
||||
func TestManifestUnauthorized(t *testing.T) {
|
||||
repo := "test.example.com/repo"
|
||||
repo, _ := reference.ParseNamed("test.example.com/repo")
|
||||
_, dgst, _ := newRandomSchemaV1Manifest(repo, "latest", 6)
|
||||
var m testutil.RequestResponseMap
|
||||
|
||||
m = append(m, testutil.RequestResponseMapping{
|
||||
Request: testutil.Request{
|
||||
Method: "GET",
|
||||
Route: "/v2/" + repo + "/manifests/" + dgst.String(),
|
||||
Route: "/v2/" + repo.Name() + "/manifests/" + dgst.String(),
|
||||
},
|
||||
Response: testutil.Response{
|
||||
StatusCode: http.StatusUnauthorized,
|
||||
@@ -778,7 +942,7 @@ func TestManifestUnauthorized(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = ms.Get(dgst)
|
||||
_, err = ms.Get(ctx, dgst)
|
||||
if err == nil {
|
||||
t.Fatal("Expected error fetching manifest")
|
||||
}
|
||||
|
84
vendor/github.com/docker/distribution/registry/client/transport/http_reader.go
generated
vendored
84
vendor/github.com/docker/distribution/registry/client/transport/http_reader.go
generated
vendored
@@ -2,11 +2,9 @@ package transport
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
@@ -21,11 +19,11 @@ type ReadSeekCloser interface {
|
||||
// request. When seeking and starting a read from a non-zero offset
|
||||
// the a "Range" header will be added which sets the offset.
|
||||
// TODO(dmcgowan): Move this into a separate utility package
|
||||
func NewHTTPReadSeeker(client *http.Client, url string, size int64) ReadSeekCloser {
|
||||
func NewHTTPReadSeeker(client *http.Client, url string, errorHandler func(*http.Response) error) ReadSeekCloser {
|
||||
return &httpReadSeeker{
|
||||
client: client,
|
||||
url: url,
|
||||
size: size,
|
||||
client: client,
|
||||
url: url,
|
||||
errorHandler: errorHandler,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,12 +31,26 @@ type httpReadSeeker struct {
|
||||
client *http.Client
|
||||
url string
|
||||
|
||||
// errorHandler creates an error from an unsuccessful HTTP response.
|
||||
// This allows the error to be created with the HTTP response body
|
||||
// without leaking the body through a returned error.
|
||||
errorHandler func(*http.Response) error
|
||||
|
||||
size int64
|
||||
|
||||
rc io.ReadCloser // remote read closer
|
||||
brd *bufio.Reader // internal buffered io
|
||||
offset int64
|
||||
err error
|
||||
// rc is the remote read closer.
|
||||
rc io.ReadCloser
|
||||
// brd is a buffer for internal buffered io.
|
||||
brd *bufio.Reader
|
||||
// readerOffset tracks the offset as of the last read.
|
||||
readerOffset int64
|
||||
// seekOffset allows Seek to override the offset. Seek changes
|
||||
// seekOffset instead of changing readOffset directly so that
|
||||
// connection resets can be delayed and possibly avoided if the
|
||||
// seek is undone (i.e. seeking to the end and then back to the
|
||||
// beginning).
|
||||
seekOffset int64
|
||||
err error
|
||||
}
|
||||
|
||||
func (hrs *httpReadSeeker) Read(p []byte) (n int, err error) {
|
||||
@@ -46,16 +58,29 @@ func (hrs *httpReadSeeker) Read(p []byte) (n int, err error) {
|
||||
return 0, hrs.err
|
||||
}
|
||||
|
||||
// If we seeked to a different position, we need to reset the
|
||||
// connection. This logic is here instead of Seek so that if
|
||||
// a seek is undone before the next read, the connection doesn't
|
||||
// need to be closed and reopened. A common example of this is
|
||||
// seeking to the end to determine the length, and then seeking
|
||||
// back to the original position.
|
||||
if hrs.readerOffset != hrs.seekOffset {
|
||||
hrs.reset()
|
||||
}
|
||||
|
||||
hrs.readerOffset = hrs.seekOffset
|
||||
|
||||
rd, err := hrs.reader()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
n, err = rd.Read(p)
|
||||
hrs.offset += int64(n)
|
||||
hrs.seekOffset += int64(n)
|
||||
hrs.readerOffset += int64(n)
|
||||
|
||||
// Simulate io.EOF error if we reach filesize.
|
||||
if err == nil && hrs.offset >= hrs.size {
|
||||
if err == nil && hrs.size >= 0 && hrs.readerOffset >= hrs.size {
|
||||
err = io.EOF
|
||||
}
|
||||
|
||||
@@ -67,13 +92,20 @@ func (hrs *httpReadSeeker) Seek(offset int64, whence int) (int64, error) {
|
||||
return 0, hrs.err
|
||||
}
|
||||
|
||||
var err error
|
||||
newOffset := hrs.offset
|
||||
_, err := hrs.reader()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
newOffset := hrs.seekOffset
|
||||
|
||||
switch whence {
|
||||
case os.SEEK_CUR:
|
||||
newOffset += int64(offset)
|
||||
case os.SEEK_END:
|
||||
if hrs.size < 0 {
|
||||
return 0, errors.New("content length not known")
|
||||
}
|
||||
newOffset = hrs.size + int64(offset)
|
||||
case os.SEEK_SET:
|
||||
newOffset = int64(offset)
|
||||
@@ -82,15 +114,10 @@ func (hrs *httpReadSeeker) Seek(offset int64, whence int) (int64, error) {
|
||||
if newOffset < 0 {
|
||||
err = errors.New("cannot seek to negative position")
|
||||
} else {
|
||||
if hrs.offset != newOffset {
|
||||
hrs.reset()
|
||||
}
|
||||
|
||||
// No problems, set the offset.
|
||||
hrs.offset = newOffset
|
||||
hrs.seekOffset = newOffset
|
||||
}
|
||||
|
||||
return hrs.offset, err
|
||||
return hrs.seekOffset, err
|
||||
}
|
||||
|
||||
func (hrs *httpReadSeeker) Close() error {
|
||||
@@ -130,17 +157,12 @@ func (hrs *httpReadSeeker) reader() (io.Reader, error) {
|
||||
return hrs.brd, nil
|
||||
}
|
||||
|
||||
// If the offset is great than or equal to size, return a empty, noop reader.
|
||||
if hrs.offset >= hrs.size {
|
||||
return ioutil.NopCloser(bytes.NewReader([]byte{})), nil
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", hrs.url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if hrs.offset > 0 {
|
||||
if hrs.readerOffset > 0 {
|
||||
// TODO(stevvooe): Get this working correctly.
|
||||
|
||||
// If we are at different offset, issue a range request from there.
|
||||
@@ -158,8 +180,16 @@ func (hrs *httpReadSeeker) reader() (io.Reader, error) {
|
||||
// import
|
||||
if resp.StatusCode >= 200 && resp.StatusCode <= 399 {
|
||||
hrs.rc = resp.Body
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
hrs.size = resp.ContentLength
|
||||
} else {
|
||||
hrs.size = -1
|
||||
}
|
||||
} else {
|
||||
defer resp.Body.Close()
|
||||
if hrs.errorHandler != nil {
|
||||
return nil, hrs.errorHandler(resp)
|
||||
}
|
||||
return nil, fmt.Errorf("unexpected status resolving reader: %v", resp.Status)
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user