1
0
mirror of https://github.com/rancher/os.git synced 2025-08-31 22:32:14 +00:00

migrate to upstream libcompose in one and a half go

This commit is contained in:
Ivan Mikushin
2015-11-26 17:41:42 +05:00
parent 1d691cd8d6
commit 5a363ab97d
1291 changed files with 40107 additions and 123532 deletions

View File

@@ -10,6 +10,7 @@ import (
"sync"
"time"
"github.com/Sirupsen/logrus"
"github.com/docker/distribution/registry/client"
"github.com/docker/distribution/registry/client/transport"
)
@@ -85,11 +86,24 @@ func (ea *endpointAuthorizer) ModifyRequest(req *http.Request) error {
return nil
}
// This is the minimum duration a token can last (in seconds).
// A token must not live less than 60 seconds because older versions
// of the Docker client didn't read their expiration from the token
// response and assumed 60 seconds. So to remain compatible with
// those implementations, a token must live at least this long.
const minimumTokenLifetimeSeconds = 60
// Private interface for time used by this package to enable tests to provide their own implementation.
type clock interface {
Now() time.Time
}
type tokenHandler struct {
header http.Header
creds CredentialStore
scope tokenScope
transport http.RoundTripper
clock clock
tokenLock sync.Mutex
tokenCache string
@@ -108,12 +122,24 @@ func (ts tokenScope) String() string {
return fmt.Sprintf("%s:%s:%s", ts.Resource, ts.Scope, strings.Join(ts.Actions, ","))
}
// An implementation of clock for providing real time data.
type realClock struct{}
// Now implements clock
func (realClock) Now() time.Time { return time.Now() }
// NewTokenHandler creates a new AuthenicationHandler which supports
// fetching tokens from a remote token server.
func NewTokenHandler(transport http.RoundTripper, creds CredentialStore, scope string, actions ...string) AuthenticationHandler {
return newTokenHandler(transport, creds, realClock{}, scope, actions...)
}
// newTokenHandler exposes the option to provide a clock to manipulate time in unit testing.
func newTokenHandler(transport http.RoundTripper, creds CredentialStore, c clock, scope string, actions ...string) AuthenticationHandler {
return &tokenHandler{
transport: transport,
creds: creds,
clock: c,
scope: tokenScope{
Resource: "repository",
Scope: scope,
@@ -146,40 +172,43 @@ func (th *tokenHandler) AuthorizeRequest(req *http.Request, params map[string]st
func (th *tokenHandler) refreshToken(params map[string]string) error {
th.tokenLock.Lock()
defer th.tokenLock.Unlock()
now := time.Now()
now := th.clock.Now()
if now.After(th.tokenExpiration) {
token, err := th.fetchToken(params)
tr, err := th.fetchToken(params)
if err != nil {
return err
}
th.tokenCache = token
th.tokenExpiration = now.Add(time.Minute)
th.tokenCache = tr.Token
th.tokenExpiration = tr.IssuedAt.Add(time.Duration(tr.ExpiresIn) * time.Second)
}
return nil
}
type tokenResponse struct {
Token string `json:"token"`
Token string `json:"token"`
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
IssuedAt time.Time `json:"issued_at"`
}
func (th *tokenHandler) fetchToken(params map[string]string) (token string, err error) {
func (th *tokenHandler) fetchToken(params map[string]string) (token *tokenResponse, err error) {
//log.Debugf("Getting bearer token with %s for %s", challenge.Parameters, ta.auth.Username)
realm, ok := params["realm"]
if !ok {
return "", errors.New("no realm specified for token auth challenge")
return nil, errors.New("no realm specified for token auth challenge")
}
// TODO(dmcgowan): Handle empty scheme
realmURL, err := url.Parse(realm)
if err != nil {
return "", fmt.Errorf("invalid token auth challenge realm: %s", err)
return nil, fmt.Errorf("invalid token auth challenge realm: %s", err)
}
req, err := http.NewRequest("GET", realmURL.String(), nil)
if err != nil {
return "", err
return nil, err
}
reqParams := req.URL.Query()
@@ -206,26 +235,44 @@ func (th *tokenHandler) fetchToken(params map[string]string) (token string, err
resp, err := th.client().Do(req)
if err != nil {
return "", err
return nil, err
}
defer resp.Body.Close()
if !client.SuccessStatus(resp.StatusCode) {
return "", fmt.Errorf("token auth attempt for registry: %s request failed with status: %d %s", req.URL, resp.StatusCode, http.StatusText(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))
}
decoder := json.NewDecoder(resp.Body)
tr := new(tokenResponse)
if err = decoder.Decode(tr); err != nil {
return "", fmt.Errorf("unable to decode token response: %s", err)
return nil, fmt.Errorf("unable to decode token response: %s", err)
}
// `access_token` is equivalent to `token` and if both are specified
// the choice is undefined. Canonicalize `access_token` by sticking
// things in `token`.
if tr.AccessToken != "" {
tr.Token = tr.AccessToken
}
if tr.Token == "" {
return "", errors.New("authorization server did not include a token in the response")
return nil, errors.New("authorization server did not include a token in the response")
}
return tr.Token, nil
if tr.ExpiresIn < minimumTokenLifetimeSeconds {
logrus.Debugf("Increasing token expiration to: %d seconds", tr.ExpiresIn)
// The default/minimum lifetime.
tr.ExpiresIn = minimumTokenLifetimeSeconds
}
if tr.IssuedAt.IsZero() {
// issued_at is optional in the token response.
tr.IssuedAt = th.clock.Now()
}
return tr, nil
}
type basicHandler struct {

View File

@@ -7,11 +7,20 @@ import (
"net/http/httptest"
"net/url"
"testing"
"time"
"github.com/docker/distribution/registry/client/transport"
"github.com/docker/distribution/testutil"
)
// An implementation of clock for providing fake time data.
type fakeClock struct {
current time.Time
}
// Now implements clock
func (fc *fakeClock) Now() time.Time { return fc.current }
func testServer(rrm testutil.RequestResponseMap) (string, func()) {
h := testutil.NewHandler(rrm)
s := httptest.NewServer(h)
@@ -210,7 +219,7 @@ func TestEndpointAuthorizeTokenBasic(t *testing.T) {
},
Response: testutil.Response{
StatusCode: http.StatusOK,
Body: []byte(`{"token":"statictoken"}`),
Body: []byte(`{"access_token":"statictoken"}`),
},
},
})
@@ -265,6 +274,285 @@ func TestEndpointAuthorizeTokenBasic(t *testing.T) {
}
}
func TestEndpointAuthorizeTokenBasicWithExpiresIn(t *testing.T) {
service := "localhost.localdomain"
repo := "some/fun/registry"
scope := fmt.Sprintf("repository:%s:pull,push", repo)
username := "tokenuser"
password := "superSecretPa$$word"
tokenMap := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
{
Request: testutil.Request{
Method: "GET",
Route: fmt.Sprintf("/token?account=%s&scope=%s&service=%s", username, url.QueryEscape(scope), service),
},
Response: testutil.Response{
StatusCode: http.StatusOK,
Body: []byte(`{"token":"statictoken", "expires_in": 3001}`),
},
},
{
Request: testutil.Request{
Method: "GET",
Route: fmt.Sprintf("/token?account=%s&scope=%s&service=%s", username, url.QueryEscape(scope), service),
},
Response: testutil.Response{
StatusCode: http.StatusOK,
Body: []byte(`{"access_token":"statictoken", "expires_in": 3001}`),
},
},
})
authenicate1 := fmt.Sprintf("Basic realm=localhost")
tokenExchanges := 0
basicCheck := func(a string) bool {
tokenExchanges = tokenExchanges + 1
return a == fmt.Sprintf("Basic %s", basicAuth(username, password))
}
te, tc := testServerWithAuth(tokenMap, authenicate1, basicCheck)
defer tc()
m := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
{
Request: testutil.Request{
Method: "GET",
Route: "/v2/hello",
},
Response: testutil.Response{
StatusCode: http.StatusAccepted,
},
},
{
Request: testutil.Request{
Method: "GET",
Route: "/v2/hello",
},
Response: testutil.Response{
StatusCode: http.StatusAccepted,
},
},
{
Request: testutil.Request{
Method: "GET",
Route: "/v2/hello",
},
Response: testutil.Response{
StatusCode: http.StatusAccepted,
},
},
{
Request: testutil.Request{
Method: "GET",
Route: "/v2/hello",
},
Response: testutil.Response{
StatusCode: http.StatusAccepted,
},
},
{
Request: testutil.Request{
Method: "GET",
Route: "/v2/hello",
},
Response: testutil.Response{
StatusCode: http.StatusAccepted,
},
},
})
authenicate2 := fmt.Sprintf("Bearer realm=%q,service=%q", te+"/token", service)
bearerCheck := func(a string) bool {
return a == "Bearer statictoken"
}
e, c := testServerWithAuth(m, authenicate2, bearerCheck)
defer c()
creds := &testCredentialStore{
username: username,
password: password,
}
challengeManager := NewSimpleChallengeManager()
_, err := ping(challengeManager, e+"/v2/", "")
if err != nil {
t.Fatal(err)
}
clock := &fakeClock{current: time.Now()}
transport1 := transport.NewTransport(nil, NewAuthorizer(challengeManager, newTokenHandler(nil, creds, clock, repo, "pull", "push"), NewBasicHandler(creds)))
client := &http.Client{Transport: transport1}
// First call should result in a token exchange
// Subsequent calls should recycle the token from the first request, until the expiration has lapsed.
timeIncrement := 1000 * time.Second
for i := 0; i < 4; i++ {
req, _ := http.NewRequest("GET", e+"/v2/hello", nil)
resp, err := client.Do(req)
if err != nil {
t.Fatalf("Error sending get request: %s", err)
}
if resp.StatusCode != http.StatusAccepted {
t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted)
}
if tokenExchanges != 1 {
t.Fatalf("Unexpected number of token exchanges, want: 1, got %d (iteration: %d)", tokenExchanges, i)
}
clock.current = clock.current.Add(timeIncrement)
}
// After we've exceeded the expiration, we should see a second token exchange.
req, _ := http.NewRequest("GET", e+"/v2/hello", nil)
resp, err := client.Do(req)
if err != nil {
t.Fatalf("Error sending get request: %s", err)
}
if resp.StatusCode != http.StatusAccepted {
t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted)
}
if tokenExchanges != 2 {
t.Fatalf("Unexpected number of token exchanges, want: 2, got %d", tokenExchanges)
}
}
func TestEndpointAuthorizeTokenBasicWithExpiresInAndIssuedAt(t *testing.T) {
service := "localhost.localdomain"
repo := "some/fun/registry"
scope := fmt.Sprintf("repository:%s:pull,push", repo)
username := "tokenuser"
password := "superSecretPa$$word"
// This test sets things up such that the token was issued one increment
// earlier than its sibling in TestEndpointAuthorizeTokenBasicWithExpiresIn.
// This will mean that the token expires after 3 increments instead of 4.
clock := &fakeClock{current: time.Now()}
timeIncrement := 1000 * time.Second
firstIssuedAt := clock.Now()
clock.current = clock.current.Add(timeIncrement)
secondIssuedAt := clock.current.Add(2 * timeIncrement)
tokenMap := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
{
Request: testutil.Request{
Method: "GET",
Route: fmt.Sprintf("/token?account=%s&scope=%s&service=%s", username, url.QueryEscape(scope), service),
},
Response: testutil.Response{
StatusCode: http.StatusOK,
Body: []byte(`{"token":"statictoken", "issued_at": "` + firstIssuedAt.Format(time.RFC3339Nano) + `", "expires_in": 3001}`),
},
},
{
Request: testutil.Request{
Method: "GET",
Route: fmt.Sprintf("/token?account=%s&scope=%s&service=%s", username, url.QueryEscape(scope), service),
},
Response: testutil.Response{
StatusCode: http.StatusOK,
Body: []byte(`{"access_token":"statictoken", "issued_at": "` + secondIssuedAt.Format(time.RFC3339Nano) + `", "expires_in": 3001}`),
},
},
})
authenicate1 := fmt.Sprintf("Basic realm=localhost")
tokenExchanges := 0
basicCheck := func(a string) bool {
tokenExchanges = tokenExchanges + 1
return a == fmt.Sprintf("Basic %s", basicAuth(username, password))
}
te, tc := testServerWithAuth(tokenMap, authenicate1, basicCheck)
defer tc()
m := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
{
Request: testutil.Request{
Method: "GET",
Route: "/v2/hello",
},
Response: testutil.Response{
StatusCode: http.StatusAccepted,
},
},
{
Request: testutil.Request{
Method: "GET",
Route: "/v2/hello",
},
Response: testutil.Response{
StatusCode: http.StatusAccepted,
},
},
{
Request: testutil.Request{
Method: "GET",
Route: "/v2/hello",
},
Response: testutil.Response{
StatusCode: http.StatusAccepted,
},
},
{
Request: testutil.Request{
Method: "GET",
Route: "/v2/hello",
},
Response: testutil.Response{
StatusCode: http.StatusAccepted,
},
},
})
authenicate2 := fmt.Sprintf("Bearer realm=%q,service=%q", te+"/token", service)
bearerCheck := func(a string) bool {
return a == "Bearer statictoken"
}
e, c := testServerWithAuth(m, authenicate2, bearerCheck)
defer c()
creds := &testCredentialStore{
username: username,
password: password,
}
challengeManager := NewSimpleChallengeManager()
_, err := ping(challengeManager, e+"/v2/", "")
if err != nil {
t.Fatal(err)
}
transport1 := transport.NewTransport(nil, NewAuthorizer(challengeManager, newTokenHandler(nil, creds, clock, repo, "pull", "push"), NewBasicHandler(creds)))
client := &http.Client{Transport: transport1}
// First call should result in a token exchange
// Subsequent calls should recycle the token from the first request, until the expiration has lapsed.
// We shaved one increment off of the equivalent logic in TestEndpointAuthorizeTokenBasicWithExpiresIn
// so this loop should have one fewer iteration.
for i := 0; i < 3; i++ {
req, _ := http.NewRequest("GET", e+"/v2/hello", nil)
resp, err := client.Do(req)
if err != nil {
t.Fatalf("Error sending get request: %s", err)
}
if resp.StatusCode != http.StatusAccepted {
t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted)
}
if tokenExchanges != 1 {
t.Fatalf("Unexpected number of token exchanges, want: 1, got %d (iteration: %d)", tokenExchanges, i)
}
clock.current = clock.current.Add(timeIncrement)
}
// After we've exceeded the expiration, we should see a second token exchange.
req, _ := http.NewRequest("GET", e+"/v2/hello", nil)
resp, err := client.Do(req)
if err != nil {
t.Fatalf("Error sending get request: %s", err)
}
if resp.StatusCode != http.StatusAccepted {
t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted)
}
if tokenExchanges != 2 {
t.Fatalf("Unexpected number of token exchanges, want: 2, got %d", tokenExchanges)
}
}
func TestEndpointAuthorizeBasic(t *testing.T) {
m := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
{

View File

@@ -8,7 +8,6 @@ import (
"net/http"
"github.com/docker/distribution/registry/api/errcode"
"github.com/docker/distribution/registry/api/v2"
)
// UnexpectedHTTPStatusError is returned when an unexpected HTTP status is
@@ -52,7 +51,7 @@ func handleErrorResponse(resp *http.Response) error {
if resp.StatusCode == 401 {
err := parseHTTPErrorResponse(resp.Body)
if uErr, ok := err.(*UnexpectedHTTPResponseError); ok {
return v2.ErrorCodeUnauthorized.WithDetail(uErr.Response)
return errcode.ErrorCodeUnauthorized.WithDetail(uErr.Response)
}
return err
}

View File

@@ -14,7 +14,8 @@ import (
"github.com/docker/distribution"
"github.com/docker/distribution/context"
"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/v2"
"github.com/docker/distribution/registry/client/transport"
"github.com/docker/distribution/registry/storage/cache"
@@ -96,9 +97,9 @@ func (r *registry) Repositories(ctx context.Context, entries []string, last stri
return numFilled, returnErr
}
// NewRepository creates a new Repository for the given repository name and base URL
// 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 := v2.ValidateRepositoryName(name); err != nil {
if _, err := reference.ParseNamed(name); err != nil {
return nil, err
}
@@ -211,8 +212,6 @@ func (ms *manifests) Tags() ([]string, error) {
}
return tagsResponse.Tags, nil
} else if resp.StatusCode == http.StatusNotFound {
return nil, nil
}
return nil, handleErrorResponse(resp)
}
@@ -242,7 +241,7 @@ func (ms *manifests) ExistsByTag(tag string) (bool, error) {
return false, handleErrorResponse(resp)
}
func (ms *manifests) Get(dgst digest.Digest) (*manifest.SignedManifest, error) {
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())
@@ -262,7 +261,7 @@ func AddEtagToTag(tag, etag string) distribution.ManifestServiceOption {
}
}
func (ms *manifests) GetByTag(tag string, options ...distribution.ManifestServiceOption) (*manifest.SignedManifest, error) {
func (ms *manifests) GetByTag(tag string, options ...distribution.ManifestServiceOption) (*schema1.SignedManifest, error) {
for _, option := range options {
err := option(ms)
if err != nil {
@@ -288,9 +287,9 @@ func (ms *manifests) GetByTag(tag string, options ...distribution.ManifestServic
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusNotModified {
return nil, nil
return nil, distribution.ErrManifestNotModified
} else if SuccessStatus(resp.StatusCode) {
var sm manifest.SignedManifest
var sm schema1.SignedManifest
decoder := json.NewDecoder(resp.Body)
if err := decoder.Decode(&sm); err != nil {
@@ -301,7 +300,7 @@ func (ms *manifests) GetByTag(tag string, options ...distribution.ManifestServic
return nil, handleErrorResponse(resp)
}
func (ms *manifests) Put(m *manifest.SignedManifest) error {
func (ms *manifests) Put(m *schema1.SignedManifest) error {
manifestURL, err := ms.ub.BuildManifestURL(ms.name, m.Tag)
if err != nil {
return err
@@ -358,25 +357,18 @@ type blobs struct {
distribution.BlobDeleter
}
func sanitizeLocation(location, source string) (string, error) {
func sanitizeLocation(location, base string) (string, error) {
baseURL, err := url.Parse(base)
if err != nil {
return "", err
}
locationURL, err := url.Parse(location)
if err != nil {
return "", err
}
if locationURL.Scheme == "" {
sourceURL, err := url.Parse(source)
if err != nil {
return "", err
}
locationURL = &url.URL{
Scheme: sourceURL.Scheme,
Host: sourceURL.Host,
Path: location,
}
location = locationURL.String()
}
return location, nil
return baseURL.ResolveReference(locationURL).String(), nil
}
func (bs *blobs) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) {

View File

@@ -3,7 +3,6 @@ package client
import (
"bytes"
"crypto/rand"
"encoding/json"
"fmt"
"io"
"log"
@@ -14,15 +13,15 @@ import (
"testing"
"time"
"github.com/docker/distribution/uuid"
"github.com/docker/distribution"
"github.com/docker/distribution/context"
"github.com/docker/distribution/digest"
"github.com/docker/distribution/manifest"
"github.com/docker/distribution/manifest/schema1"
"github.com/docker/distribution/registry/api/errcode"
"github.com/docker/distribution/registry/api/v2"
"github.com/docker/distribution/testutil"
"github.com/docker/distribution/uuid"
"github.com/docker/libtrust"
)
func testServer(rrm testutil.RequestResponseMap) (string, func()) {
@@ -420,41 +419,49 @@ func TestBlobUploadMonolithic(t *testing.T) {
}
}
func newRandomSchemaV1Manifest(name, tag string, blobCount int) (*manifest.SignedManifest, digest.Digest) {
blobs := make([]manifest.FSLayer, blobCount)
history := make([]manifest.History, blobCount)
func newRandomSchemaV1Manifest(name, tag string, blobCount int) (*schema1.SignedManifest, digest.Digest, []byte) {
blobs := make([]schema1.FSLayer, blobCount)
history := make([]schema1.History, blobCount)
for i := 0; i < blobCount; i++ {
dgst, blob := newRandomBlob((i % 5) * 16)
blobs[i] = manifest.FSLayer{BlobSum: dgst}
history[i] = manifest.History{V1Compatibility: fmt.Sprintf("{\"Hex\": \"%x\"}", blob)}
blobs[i] = schema1.FSLayer{BlobSum: dgst}
history[i] = schema1.History{V1Compatibility: fmt.Sprintf("{\"Hex\": \"%x\"}", blob)}
}
m := &manifest.SignedManifest{
Manifest: manifest.Manifest{
Name: name,
Tag: tag,
Architecture: "x86",
FSLayers: blobs,
History: history,
Versioned: manifest.Versioned{
SchemaVersion: 1,
},
m := schema1.Manifest{
Name: name,
Tag: tag,
Architecture: "x86",
FSLayers: blobs,
History: history,
Versioned: manifest.Versioned{
SchemaVersion: 1,
},
}
manifestBytes, err := json.Marshal(m)
if err != nil {
panic(err)
}
dgst, err := digest.FromBytes(manifestBytes)
pk, err := libtrust.GenerateECP256PrivateKey()
if err != nil {
panic(err)
}
m.Raw = manifestBytes
sm, err := schema1.Sign(&m, pk)
if err != nil {
panic(err)
}
return m, dgst
p, err := sm.Payload()
if err != nil {
panic(err)
}
dgst, err := digest.FromBytes(p)
if err != nil {
panic(err)
}
return sm, dgst, p
}
func addTestManifestWithEtag(repo, reference string, content []byte, m *testutil.RequestResponseMap, dgst string) {
@@ -522,7 +529,7 @@ func addTestManifest(repo, reference string, content []byte, m *testutil.Request
}
func checkEqualManifest(m1, m2 *manifest.SignedManifest) error {
func checkEqualManifest(m1, m2 *schema1.SignedManifest) error {
if m1.Name != m2.Name {
return fmt.Errorf("name does not match %q != %q", m1.Name, m2.Name)
}
@@ -551,7 +558,7 @@ func checkEqualManifest(m1, m2 *manifest.SignedManifest) error {
func TestManifestFetch(t *testing.T) {
ctx := context.Background()
repo := "test.example.com/repo"
m1, dgst := newRandomSchemaV1Manifest(repo, "latest", 6)
m1, dgst, _ := newRandomSchemaV1Manifest(repo, "latest", 6)
var m testutil.RequestResponseMap
addTestManifest(repo, dgst.String(), m1.Raw, &m)
@@ -586,9 +593,9 @@ func TestManifestFetch(t *testing.T) {
func TestManifestFetchWithEtag(t *testing.T) {
repo := "test.example.com/repo/by/tag"
m1, d1 := newRandomSchemaV1Manifest(repo, "latest", 6)
_, d1, p1 := newRandomSchemaV1Manifest(repo, "latest", 6)
var m testutil.RequestResponseMap
addTestManifestWithEtag(repo, "latest", m1.Raw, &m, d1.String())
addTestManifestWithEtag(repo, "latest", p1, &m, d1.String())
e, c := testServer(m)
defer c()
@@ -603,19 +610,16 @@ func TestManifestFetchWithEtag(t *testing.T) {
t.Fatal(err)
}
m2, err := ms.GetByTag("latest", AddEtagToTag("latest", d1.String()))
if err != nil {
_, err = ms.GetByTag("latest", AddEtagToTag("latest", d1.String()))
if err != distribution.ErrManifestNotModified {
t.Fatal(err)
}
if m2 != nil {
t.Fatal("Expected empty manifest for matching etag")
}
}
func TestManifestDelete(t *testing.T) {
repo := "test.example.com/repo/delete"
_, dgst1 := newRandomSchemaV1Manifest(repo, "latest", 6)
_, dgst2 := newRandomSchemaV1Manifest(repo, "latest", 6)
_, dgst1, _ := newRandomSchemaV1Manifest(repo, "latest", 6)
_, dgst2, _ := newRandomSchemaV1Manifest(repo, "latest", 6)
var m testutil.RequestResponseMap
m = append(m, testutil.RequestResponseMapping{
Request: testutil.Request{
@@ -654,7 +658,7 @@ func TestManifestDelete(t *testing.T) {
func TestManifestPut(t *testing.T) {
repo := "test.example.com/repo/delete"
m1, dgst := newRandomSchemaV1Manifest(repo, "other", 6)
m1, dgst, _ := newRandomSchemaV1Manifest(repo, "other", 6)
var m testutil.RequestResponseMap
m = append(m, testutil.RequestResponseMapping{
Request: testutil.Request{
@@ -747,7 +751,7 @@ func TestManifestTags(t *testing.T) {
func TestManifestUnauthorized(t *testing.T) {
repo := "test.example.com/repo"
_, dgst := newRandomSchemaV1Manifest(repo, "latest", 6)
_, dgst, _ := newRandomSchemaV1Manifest(repo, "latest", 6)
var m testutil.RequestResponseMap
m = append(m, testutil.RequestResponseMapping{
@@ -782,10 +786,10 @@ func TestManifestUnauthorized(t *testing.T) {
if !ok {
t.Fatalf("Unexpected error type: %#v", err)
}
if v2Err.Code != v2.ErrorCodeUnauthorized {
if v2Err.Code != errcode.ErrorCodeUnauthorized {
t.Fatalf("Unexpected error code: %s", v2Err.Code.String())
}
if expected := v2.ErrorCodeUnauthorized.Message(); v2Err.Message != expected {
if expected := errcode.ErrorCodeUnauthorized.Message(); v2Err.Message != expected {
t.Fatalf("Unexpected message value: %q, expected %q", v2Err.Message, expected)
}
}
@@ -857,3 +861,49 @@ func TestCatalogInParts(t *testing.T) {
t.Fatalf("Got wrong number of repos")
}
}
func TestSanitizeLocation(t *testing.T) {
for _, testcase := range []struct {
description string
location string
source string
expected string
err error
}{
{
description: "ensure relative location correctly resolved",
location: "/v2/foo/baasdf",
source: "http://blahalaja.com/v1",
expected: "http://blahalaja.com/v2/foo/baasdf",
},
{
description: "ensure parameters are preserved",
location: "/v2/foo/baasdf?_state=asdfasfdasdfasdf&digest=foo",
source: "http://blahalaja.com/v1",
expected: "http://blahalaja.com/v2/foo/baasdf?_state=asdfasfdasdfasdf&digest=foo",
},
{
description: "ensure new hostname overidden",
location: "https://mwhahaha.com/v2/foo/baasdf?_state=asdfasfdasdfasdf",
source: "http://blahalaja.com/v1",
expected: "https://mwhahaha.com/v2/foo/baasdf?_state=asdfasfdasdfasdf",
},
} {
fatalf := func(format string, args ...interface{}) {
t.Fatalf(testcase.description+": "+format, args...)
}
s, err := sanitizeLocation(testcase.location, testcase.source)
if err != testcase.err {
if testcase.err != nil {
fatalf("expected error: %v != %v", err, testcase)
} else {
fatalf("unexpected error sanitizing: %v", err)
}
}
if s != testcase.expected {
fatalf("bad sanitize: %q != %q", s, testcase.expected)
}
}
}