forked from TrueCloudLab/distribution
Add API unit testing for schema2 manifest
Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
This commit is contained in:
parent
f14c6a4814
commit
66a33baa36
2 changed files with 383 additions and 44 deletions
|
@ -18,11 +18,13 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/distribution"
|
||||||
"github.com/docker/distribution/configuration"
|
"github.com/docker/distribution/configuration"
|
||||||
"github.com/docker/distribution/context"
|
"github.com/docker/distribution/context"
|
||||||
"github.com/docker/distribution/digest"
|
"github.com/docker/distribution/digest"
|
||||||
"github.com/docker/distribution/manifest"
|
"github.com/docker/distribution/manifest"
|
||||||
"github.com/docker/distribution/manifest/schema1"
|
"github.com/docker/distribution/manifest/schema1"
|
||||||
|
"github.com/docker/distribution/manifest/schema2"
|
||||||
"github.com/docker/distribution/registry/api/errcode"
|
"github.com/docker/distribution/registry/api/errcode"
|
||||||
"github.com/docker/distribution/registry/api/v2"
|
"github.com/docker/distribution/registry/api/v2"
|
||||||
_ "github.com/docker/distribution/registry/storage/driver/inmemory"
|
_ "github.com/docker/distribution/registry/storage/driver/inmemory"
|
||||||
|
@ -690,48 +692,40 @@ func httpDelete(url string) (*http.Response, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type manifestArgs struct {
|
type manifestArgs struct {
|
||||||
imageName string
|
imageName string
|
||||||
signedManifest *schema1.SignedManifest
|
mediaType string
|
||||||
dgst digest.Digest
|
manifest distribution.Manifest
|
||||||
}
|
dgst digest.Digest
|
||||||
|
|
||||||
func makeManifestArgs(t *testing.T) manifestArgs {
|
|
||||||
args := manifestArgs{
|
|
||||||
imageName: "foo/bar",
|
|
||||||
}
|
|
||||||
|
|
||||||
return args
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestManifestAPI(t *testing.T) {
|
func TestManifestAPI(t *testing.T) {
|
||||||
deleteEnabled := false
|
deleteEnabled := false
|
||||||
env := newTestEnv(t, deleteEnabled)
|
env := newTestEnv(t, deleteEnabled)
|
||||||
args := makeManifestArgs(t)
|
testManifestAPISchema1(t, env, "foo/schema1")
|
||||||
testManifestAPI(t, env, args)
|
testManifestAPISchema2(t, env, "foo/schema2")
|
||||||
|
|
||||||
deleteEnabled = true
|
deleteEnabled = true
|
||||||
env = newTestEnv(t, deleteEnabled)
|
env = newTestEnv(t, deleteEnabled)
|
||||||
args = makeManifestArgs(t)
|
testManifestAPISchema1(t, env, "foo/schema1")
|
||||||
testManifestAPI(t, env, args)
|
testManifestAPISchema2(t, env, "foo/schema2")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestManifestDelete(t *testing.T) {
|
func TestManifestDelete(t *testing.T) {
|
||||||
deleteEnabled := true
|
deleteEnabled := true
|
||||||
env := newTestEnv(t, deleteEnabled)
|
env := newTestEnv(t, deleteEnabled)
|
||||||
args := makeManifestArgs(t)
|
schema1Args := testManifestAPISchema1(t, env, "foo/schema1")
|
||||||
env, args = testManifestAPI(t, env, args)
|
testManifestDelete(t, env, schema1Args)
|
||||||
testManifestDelete(t, env, args)
|
schema2Args := testManifestAPISchema2(t, env, "foo/schema2")
|
||||||
|
testManifestDelete(t, env, schema2Args)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestManifestDeleteDisabled(t *testing.T) {
|
func TestManifestDeleteDisabled(t *testing.T) {
|
||||||
deleteEnabled := false
|
deleteEnabled := false
|
||||||
env := newTestEnv(t, deleteEnabled)
|
env := newTestEnv(t, deleteEnabled)
|
||||||
args := makeManifestArgs(t)
|
testManifestDeleteDisabled(t, env, "foo/schema1")
|
||||||
testManifestDeleteDisabled(t, env, args)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func testManifestDeleteDisabled(t *testing.T, env *testEnv, args manifestArgs) *testEnv {
|
func testManifestDeleteDisabled(t *testing.T, env *testEnv, imageName string) {
|
||||||
imageName := args.imageName
|
|
||||||
manifestURL, err := env.builder.BuildManifestURL(imageName, digest.DigestSha256EmptyTar)
|
manifestURL, err := env.builder.BuildManifestURL(imageName, digest.DigestSha256EmptyTar)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error getting manifest url: %v", err)
|
t.Fatalf("unexpected error getting manifest url: %v", err)
|
||||||
|
@ -744,12 +738,11 @@ func testManifestDeleteDisabled(t *testing.T, env *testEnv, args manifestArgs) *
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
checkResponse(t, "status of disabled delete of manifest", resp, http.StatusMethodNotAllowed)
|
checkResponse(t, "status of disabled delete of manifest", resp, http.StatusMethodNotAllowed)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func testManifestAPI(t *testing.T, env *testEnv, args manifestArgs) (*testEnv, manifestArgs) {
|
func testManifestAPISchema1(t *testing.T, env *testEnv, imageName string) manifestArgs {
|
||||||
imageName := args.imageName
|
|
||||||
tag := "thetag"
|
tag := "thetag"
|
||||||
|
args := manifestArgs{imageName: imageName}
|
||||||
|
|
||||||
manifestURL, err := env.builder.BuildManifestURL(imageName, tag)
|
manifestURL, err := env.builder.BuildManifestURL(imageName, tag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -808,10 +801,10 @@ func testManifestAPI(t *testing.T, env *testEnv, args manifestArgs) (*testEnv, m
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
resp = putManifest(t, "putting unsigned manifest", manifestURL, unsignedManifest)
|
resp = putManifest(t, "putting unsigned manifest", manifestURL, "", unsignedManifest)
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
checkResponse(t, "putting unsigned manifest", resp, http.StatusBadRequest)
|
checkResponse(t, "putting unsigned manifest", resp, http.StatusBadRequest)
|
||||||
_, p, counts := checkBodyHasErrorCodes(t, "getting unknown manifest tags", resp, v2.ErrorCodeManifestInvalid)
|
_, p, counts := checkBodyHasErrorCodes(t, "putting unsigned manifest", resp, v2.ErrorCodeManifestInvalid)
|
||||||
|
|
||||||
expectedCounts := map[errcode.ErrorCode]int{
|
expectedCounts := map[errcode.ErrorCode]int{
|
||||||
v2.ErrorCodeManifestInvalid: 1,
|
v2.ErrorCodeManifestInvalid: 1,
|
||||||
|
@ -827,7 +820,7 @@ func testManifestAPI(t *testing.T, env *testEnv, args manifestArgs) (*testEnv, m
|
||||||
t.Fatalf("error signing manifest: %v", err)
|
t.Fatalf("error signing manifest: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp = putManifest(t, "putting signed manifest with errors", manifestURL, sm)
|
resp = putManifest(t, "putting signed manifest with errors", manifestURL, "", sm)
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
checkResponse(t, "putting signed manifest with errors", resp, http.StatusBadRequest)
|
checkResponse(t, "putting signed manifest with errors", resp, http.StatusBadRequest)
|
||||||
_, p, counts = checkBodyHasErrorCodes(t, "putting signed manifest with errors", resp,
|
_, p, counts = checkBodyHasErrorCodes(t, "putting signed manifest with errors", resp,
|
||||||
|
@ -872,13 +865,13 @@ func testManifestAPI(t *testing.T, env *testEnv, args manifestArgs) (*testEnv, m
|
||||||
}
|
}
|
||||||
|
|
||||||
dgst := digest.FromBytes(signedManifest.Canonical)
|
dgst := digest.FromBytes(signedManifest.Canonical)
|
||||||
args.signedManifest = signedManifest
|
args.manifest = signedManifest
|
||||||
args.dgst = dgst
|
args.dgst = dgst
|
||||||
|
|
||||||
manifestDigestURL, err := env.builder.BuildManifestURL(imageName, dgst.String())
|
manifestDigestURL, err := env.builder.BuildManifestURL(imageName, dgst.String())
|
||||||
checkErr(t, err, "building manifest url")
|
checkErr(t, err, "building manifest url")
|
||||||
|
|
||||||
resp = putManifest(t, "putting signed manifest no error", manifestURL, signedManifest)
|
resp = putManifest(t, "putting signed manifest no error", manifestURL, "", signedManifest)
|
||||||
checkResponse(t, "putting signed manifest no error", resp, http.StatusCreated)
|
checkResponse(t, "putting signed manifest no error", resp, http.StatusCreated)
|
||||||
checkHeaders(t, resp, http.Header{
|
checkHeaders(t, resp, http.Header{
|
||||||
"Location": []string{manifestDigestURL},
|
"Location": []string{manifestDigestURL},
|
||||||
|
@ -887,7 +880,7 @@ func testManifestAPI(t *testing.T, env *testEnv, args manifestArgs) (*testEnv, m
|
||||||
|
|
||||||
// --------------------
|
// --------------------
|
||||||
// Push by digest -- should get same result
|
// Push by digest -- should get same result
|
||||||
resp = putManifest(t, "putting signed manifest", manifestDigestURL, signedManifest)
|
resp = putManifest(t, "putting signed manifest", manifestDigestURL, "", signedManifest)
|
||||||
checkResponse(t, "putting signed manifest", resp, http.StatusCreated)
|
checkResponse(t, "putting signed manifest", resp, http.StatusCreated)
|
||||||
checkHeaders(t, resp, http.Header{
|
checkHeaders(t, resp, http.Header{
|
||||||
"Location": []string{manifestDigestURL},
|
"Location": []string{manifestDigestURL},
|
||||||
|
@ -958,7 +951,7 @@ func testManifestAPI(t *testing.T, env *testEnv, args manifestArgs) (*testEnv, m
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resp = putManifest(t, "re-putting signed manifest", manifestDigestURL, sm2)
|
resp = putManifest(t, "re-putting signed manifest", manifestDigestURL, "", sm2)
|
||||||
checkResponse(t, "re-putting signed manifest", resp, http.StatusCreated)
|
checkResponse(t, "re-putting signed manifest", resp, http.StatusCreated)
|
||||||
|
|
||||||
resp, err = http.Get(manifestDigestURL)
|
resp, err = http.Get(manifestDigestURL)
|
||||||
|
@ -1020,8 +1013,7 @@ func testManifestAPI(t *testing.T, env *testEnv, args manifestArgs) (*testEnv, m
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
// Check that we get an unknown repository error when asking for tags
|
checkResponse(t, "getting tags", resp, http.StatusOK)
|
||||||
checkResponse(t, "getting unknown manifest tags", resp, http.StatusOK)
|
|
||||||
dec = json.NewDecoder(resp.Body)
|
dec = json.NewDecoder(resp.Body)
|
||||||
|
|
||||||
var tagsResponse tagsAPIResponse
|
var tagsResponse tagsAPIResponse
|
||||||
|
@ -1052,16 +1044,359 @@ func testManifestAPI(t *testing.T, env *testEnv, args manifestArgs) (*testEnv, m
|
||||||
t.Fatalf("error signing manifest")
|
t.Fatalf("error signing manifest")
|
||||||
}
|
}
|
||||||
|
|
||||||
resp = putManifest(t, "putting invalid signed manifest", manifestDigestURL, invalidSigned)
|
resp = putManifest(t, "putting invalid signed manifest", manifestDigestURL, "", invalidSigned)
|
||||||
checkResponse(t, "putting invalid signed manifest", resp, http.StatusBadRequest)
|
checkResponse(t, "putting invalid signed manifest", resp, http.StatusBadRequest)
|
||||||
|
|
||||||
return env, args
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
func testManifestAPISchema2(t *testing.T, env *testEnv, imageName string) manifestArgs {
|
||||||
|
tag := "schema2tag"
|
||||||
|
args := manifestArgs{
|
||||||
|
imageName: imageName,
|
||||||
|
mediaType: schema2.MediaTypeManifest,
|
||||||
|
}
|
||||||
|
|
||||||
|
manifestURL, err := env.builder.BuildManifestURL(imageName, tag)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error getting manifest url: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------
|
||||||
|
// Attempt to fetch the manifest
|
||||||
|
resp, err := http.Get(manifestURL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error getting manifest: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
checkResponse(t, "getting non-existent manifest", resp, http.StatusNotFound)
|
||||||
|
checkBodyHasErrorCodes(t, "getting non-existent manifest", resp, v2.ErrorCodeManifestUnknown)
|
||||||
|
|
||||||
|
tagsURL, err := env.builder.BuildTagsURL(imageName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error building tags url: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err = http.Get(tagsURL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error getting unknown tags: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// Check that we get an unknown repository error when asking for tags
|
||||||
|
checkResponse(t, "getting unknown manifest tags", resp, http.StatusNotFound)
|
||||||
|
checkBodyHasErrorCodes(t, "getting unknown manifest tags", resp, v2.ErrorCodeNameUnknown)
|
||||||
|
|
||||||
|
// --------------------------------
|
||||||
|
// Attempt to push manifest with missing config and missing layers
|
||||||
|
manifest := &schema2.Manifest{
|
||||||
|
Versioned: manifest.Versioned{
|
||||||
|
SchemaVersion: 2,
|
||||||
|
},
|
||||||
|
MediaType: schema2.MediaTypeManifest,
|
||||||
|
Config: distribution.Descriptor{
|
||||||
|
Digest: "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b",
|
||||||
|
Size: 3253,
|
||||||
|
MediaType: schema2.MediaTypeConfig,
|
||||||
|
},
|
||||||
|
Layers: []distribution.Descriptor{
|
||||||
|
{
|
||||||
|
Digest: "sha256:463434349086340864309863409683460843608348608934092322395278926a",
|
||||||
|
Size: 6323,
|
||||||
|
MediaType: schema2.MediaTypeLayer,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Digest: "sha256:630923423623623423352523525237238023652897356239852383652aaaaaaa",
|
||||||
|
Size: 6863,
|
||||||
|
MediaType: schema2.MediaTypeLayer,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resp = putManifest(t, "putting missing config manifest", manifestURL, schema2.MediaTypeManifest, manifest)
|
||||||
|
defer resp.Body.Close()
|
||||||
|
checkResponse(t, "putting missing config manifest", resp, http.StatusBadRequest)
|
||||||
|
_, p, counts := checkBodyHasErrorCodes(t, "putting missing config manifest", resp, v2.ErrorCodeManifestBlobUnknown)
|
||||||
|
|
||||||
|
expectedCounts := map[errcode.ErrorCode]int{
|
||||||
|
v2.ErrorCodeManifestBlobUnknown: 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(counts, expectedCounts) {
|
||||||
|
t.Fatalf("unexpected number of error codes encountered: %v\n!=\n%v\n---\n%s", counts, expectedCounts, string(p))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push a config, and reference it in the manifest
|
||||||
|
sampleConfig := []byte(`{
|
||||||
|
"architecture": "amd64",
|
||||||
|
"history": [
|
||||||
|
{
|
||||||
|
"created": "2015-10-31T22:22:54.690851953Z",
|
||||||
|
"created_by": "/bin/sh -c #(nop) ADD file:a3bc1e842b69636f9df5256c49c5374fb4eef1e281fe3f282c65fb853ee171c5 in /"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"created": "2015-10-31T22:22:55.613815829Z",
|
||||||
|
"created_by": "/bin/sh -c #(nop) CMD [\"sh\"]"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rootfs": {
|
||||||
|
"diff_ids": [
|
||||||
|
"sha256:c6f988f4874bb0add23a778f753c65efe992244e148a1d2ec2a8b664fb66bbd1",
|
||||||
|
"sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
|
||||||
|
],
|
||||||
|
"type": "layers"
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
sampleConfigDigest := digest.FromBytes(sampleConfig)
|
||||||
|
|
||||||
|
uploadURLBase, _ := startPushLayer(t, env.builder, imageName)
|
||||||
|
pushLayer(t, env.builder, imageName, sampleConfigDigest, uploadURLBase, bytes.NewReader(sampleConfig))
|
||||||
|
manifest.Config.Digest = sampleConfigDigest
|
||||||
|
manifest.Config.Size = int64(len(sampleConfig))
|
||||||
|
|
||||||
|
// The manifest should still be invalid, because its layer doesnt exist
|
||||||
|
resp = putManifest(t, "putting missing layer manifest", manifestURL, schema2.MediaTypeManifest, manifest)
|
||||||
|
defer resp.Body.Close()
|
||||||
|
checkResponse(t, "putting missing layer manifest", resp, http.StatusBadRequest)
|
||||||
|
_, p, counts = checkBodyHasErrorCodes(t, "getting unknown manifest tags", resp, v2.ErrorCodeManifestBlobUnknown)
|
||||||
|
|
||||||
|
expectedCounts = map[errcode.ErrorCode]int{
|
||||||
|
v2.ErrorCodeManifestBlobUnknown: 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(counts, expectedCounts) {
|
||||||
|
t.Fatalf("unexpected number of error codes encountered: %v\n!=\n%v\n---\n%s", counts, expectedCounts, string(p))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push 2 random layers
|
||||||
|
expectedLayers := make(map[digest.Digest]io.ReadSeeker)
|
||||||
|
|
||||||
|
for i := range manifest.Layers {
|
||||||
|
rs, dgstStr, err := testutil.CreateRandomTarFile()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error creating random layer %d: %v", i, err)
|
||||||
|
}
|
||||||
|
dgst := digest.Digest(dgstStr)
|
||||||
|
|
||||||
|
expectedLayers[dgst] = rs
|
||||||
|
manifest.Layers[i].Digest = dgst
|
||||||
|
|
||||||
|
uploadURLBase, _ := startPushLayer(t, env.builder, imageName)
|
||||||
|
pushLayer(t, env.builder, imageName, dgst, uploadURLBase, rs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------
|
||||||
|
// Push the manifest with all layers pushed.
|
||||||
|
deserializedManifest, err := schema2.FromStruct(*manifest)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("could not create DeserializedManifest: %v", err)
|
||||||
|
}
|
||||||
|
_, canonical, err := deserializedManifest.Payload()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("could not get manifest payload: %v", err)
|
||||||
|
}
|
||||||
|
dgst := digest.FromBytes(canonical)
|
||||||
|
args.dgst = dgst
|
||||||
|
args.manifest = deserializedManifest
|
||||||
|
|
||||||
|
manifestDigestURL, err := env.builder.BuildManifestURL(imageName, dgst.String())
|
||||||
|
checkErr(t, err, "building manifest url")
|
||||||
|
|
||||||
|
resp = putManifest(t, "putting manifest no error", manifestURL, schema2.MediaTypeManifest, manifest)
|
||||||
|
checkResponse(t, "putting manifest no error", resp, http.StatusCreated)
|
||||||
|
checkHeaders(t, resp, http.Header{
|
||||||
|
"Location": []string{manifestDigestURL},
|
||||||
|
"Docker-Content-Digest": []string{dgst.String()},
|
||||||
|
})
|
||||||
|
|
||||||
|
// --------------------
|
||||||
|
// Push by digest -- should get same result
|
||||||
|
resp = putManifest(t, "putting manifest by digest", manifestDigestURL, schema2.MediaTypeManifest, manifest)
|
||||||
|
checkResponse(t, "putting manifest by digest", resp, http.StatusCreated)
|
||||||
|
checkHeaders(t, resp, http.Header{
|
||||||
|
"Location": []string{manifestDigestURL},
|
||||||
|
"Docker-Content-Digest": []string{dgst.String()},
|
||||||
|
})
|
||||||
|
|
||||||
|
// ------------------
|
||||||
|
// Fetch by tag name
|
||||||
|
req, err := http.NewRequest("GET", manifestURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error constructing request: %s", err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Accept", schema2.MediaTypeManifest)
|
||||||
|
resp, err = http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error fetching manifest: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
checkResponse(t, "fetching uploaded manifest", resp, http.StatusOK)
|
||||||
|
checkHeaders(t, resp, http.Header{
|
||||||
|
"Docker-Content-Digest": []string{dgst.String()},
|
||||||
|
"ETag": []string{fmt.Sprintf(`"%s"`, dgst)},
|
||||||
|
})
|
||||||
|
|
||||||
|
var fetchedManifest schema2.DeserializedManifest
|
||||||
|
dec := json.NewDecoder(resp.Body)
|
||||||
|
|
||||||
|
if err := dec.Decode(&fetchedManifest); err != nil {
|
||||||
|
t.Fatalf("error decoding fetched manifest: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, fetchedCanonical, err := fetchedManifest.Payload()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error getting manifest payload: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(fetchedCanonical, canonical) {
|
||||||
|
t.Fatalf("manifests do not match")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------
|
||||||
|
// Fetch by digest
|
||||||
|
req, err = http.NewRequest("GET", manifestDigestURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error constructing request: %s", err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Accept", schema2.MediaTypeManifest)
|
||||||
|
resp, err = http.DefaultClient.Do(req)
|
||||||
|
checkErr(t, err, "fetching manifest by digest")
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
checkResponse(t, "fetching uploaded manifest", resp, http.StatusOK)
|
||||||
|
checkHeaders(t, resp, http.Header{
|
||||||
|
"Docker-Content-Digest": []string{dgst.String()},
|
||||||
|
"ETag": []string{fmt.Sprintf(`"%s"`, dgst)},
|
||||||
|
})
|
||||||
|
|
||||||
|
var fetchedManifestByDigest schema2.DeserializedManifest
|
||||||
|
dec = json.NewDecoder(resp.Body)
|
||||||
|
if err := dec.Decode(&fetchedManifestByDigest); err != nil {
|
||||||
|
t.Fatalf("error decoding fetched manifest: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, fetchedCanonical, err = fetchedManifest.Payload()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error getting manifest payload: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(fetchedCanonical, canonical) {
|
||||||
|
t.Fatalf("manifests do not match")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get by name with etag, gives 304
|
||||||
|
etag := resp.Header.Get("Etag")
|
||||||
|
req, err = http.NewRequest("GET", manifestURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error constructing request: %s", err)
|
||||||
|
}
|
||||||
|
req.Header.Set("If-None-Match", etag)
|
||||||
|
resp, err = http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error constructing request: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkResponse(t, "fetching manifest by name with etag", resp, http.StatusNotModified)
|
||||||
|
|
||||||
|
// Get by digest with etag, gives 304
|
||||||
|
req, err = http.NewRequest("GET", manifestDigestURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error constructing request: %s", err)
|
||||||
|
}
|
||||||
|
req.Header.Set("If-None-Match", etag)
|
||||||
|
resp, err = http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error constructing request: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
checkResponse(t, "fetching manifest by dgst with etag", resp, http.StatusNotModified)
|
||||||
|
|
||||||
|
// Ensure that the tag is listed.
|
||||||
|
resp, err = http.Get(tagsURL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error getting unknown tags: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
checkResponse(t, "getting unknown manifest tags", resp, http.StatusOK)
|
||||||
|
dec = json.NewDecoder(resp.Body)
|
||||||
|
|
||||||
|
var tagsResponse tagsAPIResponse
|
||||||
|
|
||||||
|
if err := dec.Decode(&tagsResponse); err != nil {
|
||||||
|
t.Fatalf("unexpected error decoding error response: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tagsResponse.Name != imageName {
|
||||||
|
t.Fatalf("tags name should match image name: %v != %v", tagsResponse.Name, imageName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(tagsResponse.Tags) != 1 {
|
||||||
|
t.Fatalf("expected some tags in response: %v", tagsResponse.Tags)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tagsResponse.Tags[0] != tag {
|
||||||
|
t.Fatalf("tag not as expected: %q != %q", tagsResponse.Tags[0], tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------
|
||||||
|
// Fetch as a schema1 manifest
|
||||||
|
resp, err = http.Get(manifestURL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error fetching manifest as schema1: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
checkResponse(t, "fetching uploaded manifest as schema1", resp, http.StatusOK)
|
||||||
|
checkHeaders(t, resp, http.Header{
|
||||||
|
"Docker-Content-Digest": []string{dgst.String()},
|
||||||
|
"ETag": []string{fmt.Sprintf(`"%s"`, dgst)},
|
||||||
|
})
|
||||||
|
|
||||||
|
var fetchedSchema1Manifest schema1.SignedManifest
|
||||||
|
dec = json.NewDecoder(resp.Body)
|
||||||
|
|
||||||
|
if err := dec.Decode(&fetchedSchema1Manifest); err != nil {
|
||||||
|
t.Fatalf("error decoding fetched schema1 manifest: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fetchedSchema1Manifest.Manifest.SchemaVersion != 1 {
|
||||||
|
t.Fatal("wrong schema version")
|
||||||
|
}
|
||||||
|
if fetchedSchema1Manifest.Architecture != "amd64" {
|
||||||
|
t.Fatal("wrong architecture")
|
||||||
|
}
|
||||||
|
if fetchedSchema1Manifest.Name != imageName {
|
||||||
|
t.Fatal("wrong image name")
|
||||||
|
}
|
||||||
|
if fetchedSchema1Manifest.Tag != tag {
|
||||||
|
t.Fatal("wrong tag")
|
||||||
|
}
|
||||||
|
if len(fetchedSchema1Manifest.FSLayers) != 2 {
|
||||||
|
t.Fatal("wrong number of FSLayers")
|
||||||
|
}
|
||||||
|
for i := range manifest.Layers {
|
||||||
|
if fetchedSchema1Manifest.FSLayers[i].BlobSum != manifest.Layers[len(manifest.Layers)-i-1].Digest {
|
||||||
|
t.Fatalf("blob digest mismatch in schema1 manifest for layer %d", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(fetchedSchema1Manifest.History) != 2 {
|
||||||
|
t.Fatal("wrong number of History entries")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't check V1Compatibility fields becuase we're using randomly-generated
|
||||||
|
// layers.
|
||||||
|
|
||||||
|
return args
|
||||||
}
|
}
|
||||||
|
|
||||||
func testManifestDelete(t *testing.T, env *testEnv, args manifestArgs) {
|
func testManifestDelete(t *testing.T, env *testEnv, args manifestArgs) {
|
||||||
imageName := args.imageName
|
imageName := args.imageName
|
||||||
dgst := args.dgst
|
dgst := args.dgst
|
||||||
signedManifest := args.signedManifest
|
manifest := args.manifest
|
||||||
manifestDigestURL, err := env.builder.BuildManifestURL(imageName, dgst.String())
|
manifestDigestURL, err := env.builder.BuildManifestURL(imageName, dgst.String())
|
||||||
// ---------------
|
// ---------------
|
||||||
// Delete by digest
|
// Delete by digest
|
||||||
|
@ -1090,8 +1425,8 @@ func testManifestDelete(t *testing.T, env *testEnv, args manifestArgs) {
|
||||||
|
|
||||||
// --------------------
|
// --------------------
|
||||||
// Re-upload manifest by digest
|
// Re-upload manifest by digest
|
||||||
resp = putManifest(t, "putting signed manifest", manifestDigestURL, signedManifest)
|
resp = putManifest(t, "putting manifest", manifestDigestURL, args.mediaType, manifest)
|
||||||
checkResponse(t, "putting signed manifest", resp, http.StatusCreated)
|
checkResponse(t, "putting manifest", resp, http.StatusCreated)
|
||||||
checkHeaders(t, resp, http.Header{
|
checkHeaders(t, resp, http.Header{
|
||||||
"Location": []string{manifestDigestURL},
|
"Location": []string{manifestDigestURL},
|
||||||
"Docker-Content-Digest": []string{dgst.String()},
|
"Docker-Content-Digest": []string{dgst.String()},
|
||||||
|
@ -1183,7 +1518,7 @@ func newTestEnvWithConfig(t *testing.T, config *configuration.Configuration) *te
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func putManifest(t *testing.T, msg, url string, v interface{}) *http.Response {
|
func putManifest(t *testing.T, msg, url, contentType string, v interface{}) *http.Response {
|
||||||
var body []byte
|
var body []byte
|
||||||
|
|
||||||
if sm, ok := v.(*schema1.SignedManifest); ok {
|
if sm, ok := v.(*schema1.SignedManifest); ok {
|
||||||
|
@ -1205,6 +1540,10 @@ func putManifest(t *testing.T, msg, url string, v interface{}) *http.Response {
|
||||||
t.Fatalf("error creating request for %s: %v", msg, err)
|
t.Fatalf("error creating request for %s: %v", msg, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if contentType != "" {
|
||||||
|
req.Header.Set("Content-Type", contentType)
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := http.DefaultClient.Do(req)
|
resp, err := http.DefaultClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error doing put request while %s: %v", msg, err)
|
t.Fatalf("error doing put request while %s: %v", msg, err)
|
||||||
|
@ -1532,7 +1871,7 @@ func createRepository(env *testEnv, t *testing.T, imageName string, tag string)
|
||||||
location, err := env.builder.BuildManifestURL(imageName, dgst.String())
|
location, err := env.builder.BuildManifestURL(imageName, dgst.String())
|
||||||
checkErr(t, err, "building location URL")
|
checkErr(t, err, "building location URL")
|
||||||
|
|
||||||
resp := putManifest(t, "putting signed manifest", manifestDigestURL, signedManifest)
|
resp := putManifest(t, "putting signed manifest", manifestDigestURL, "", signedManifest)
|
||||||
checkResponse(t, "putting signed manifest", resp, http.StatusCreated)
|
checkResponse(t, "putting signed manifest", resp, http.StatusCreated)
|
||||||
checkHeaders(t, resp, http.Header{
|
checkHeaders(t, resp, http.Header{
|
||||||
"Location": []string{location},
|
"Location": []string{location},
|
||||||
|
@ -1570,7 +1909,7 @@ func TestRegistryAsCacheMutationAPIs(t *testing.T) {
|
||||||
t.Fatalf("error signing manifest: %v", err)
|
t.Fatalf("error signing manifest: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := putManifest(t, "putting unsigned manifest", manifestURL, sm)
|
resp := putManifest(t, "putting unsigned manifest", manifestURL, "", sm)
|
||||||
checkResponse(t, "putting signed manifest to cache", resp, errcode.ErrorCodeUnsupported.Descriptor().HTTPStatusCode)
|
checkResponse(t, "putting signed manifest to cache", resp, errcode.ErrorCodeUnsupported.Descriptor().HTTPStatusCode)
|
||||||
|
|
||||||
// Manifest Delete
|
// Manifest Delete
|
||||||
|
|
|
@ -86,7 +86,7 @@ func (imh *imageManifestHandler) GetImageManifest(w http.ResponseWriter, r *http
|
||||||
// Only rewrite schema2 manifests when they are being fetched by tag.
|
// Only rewrite schema2 manifests when they are being fetched by tag.
|
||||||
// If they are being fetched by digest, we can't return something not
|
// If they are being fetched by digest, we can't return something not
|
||||||
// matching the digest.
|
// matching the digest.
|
||||||
if _, isSchema2 := manifest.(*schema2.DeserializedManifest); imh.Tag != "" && isSchema2 {
|
if schema2Manifest, isSchema2 := manifest.(*schema2.DeserializedManifest); imh.Tag != "" && isSchema2 {
|
||||||
supportsSchema2 := false
|
supportsSchema2 := false
|
||||||
if acceptHeaders, ok := r.Header["Accept"]; ok {
|
if acceptHeaders, ok := r.Header["Accept"]; ok {
|
||||||
for _, mediaType := range acceptHeaders {
|
for _, mediaType := range acceptHeaders {
|
||||||
|
@ -101,7 +101,7 @@ func (imh *imageManifestHandler) GetImageManifest(w http.ResponseWriter, r *http
|
||||||
// Rewrite manifest in schema1 format
|
// Rewrite manifest in schema1 format
|
||||||
ctxu.GetLogger(imh).Infof("rewriting manifest %s in schema1 format to support old client", imh.Digest.String())
|
ctxu.GetLogger(imh).Infof("rewriting manifest %s in schema1 format to support old client", imh.Digest.String())
|
||||||
|
|
||||||
targetDescriptor := manifest.Target()
|
targetDescriptor := schema2Manifest.Target()
|
||||||
blobs := imh.Repository.Blobs(imh)
|
blobs := imh.Repository.Blobs(imh)
|
||||||
configJSON, err := blobs.Get(imh, targetDescriptor.Digest)
|
configJSON, err := blobs.Get(imh, targetDescriptor.Digest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
Loading…
Reference in a new issue