|
|
|
@@ -26,6 +26,7 @@ import (
|
|
|
|
|
"github.com/docker/distribution/manifest/manifestlist"
|
|
|
|
|
"github.com/docker/distribution/manifest/schema1"
|
|
|
|
|
"github.com/docker/distribution/manifest/schema2"
|
|
|
|
|
"github.com/docker/distribution/reference"
|
|
|
|
|
"github.com/docker/distribution/registry/api/errcode"
|
|
|
|
|
"github.com/docker/distribution/registry/api/v2"
|
|
|
|
|
_ "github.com/docker/distribution/registry/storage/driver/inmemory"
|
|
|
|
@@ -251,7 +252,7 @@ func TestURLPrefix(t *testing.T) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type blobArgs struct {
|
|
|
|
|
imageName string
|
|
|
|
|
imageName reference.Named
|
|
|
|
|
layerFile io.ReadSeeker
|
|
|
|
|
layerDigest digest.Digest
|
|
|
|
|
}
|
|
|
|
@@ -263,10 +264,10 @@ func makeBlobArgs(t *testing.T) blobArgs {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
args := blobArgs{
|
|
|
|
|
imageName: "foo/bar",
|
|
|
|
|
layerFile: layerFile,
|
|
|
|
|
layerDigest: layerDigest,
|
|
|
|
|
}
|
|
|
|
|
args.imageName, _ = reference.ParseNamed("foo/bar")
|
|
|
|
|
return args
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -609,7 +610,7 @@ func testBlobDelete(t *testing.T, env *testEnv, args blobArgs) {
|
|
|
|
|
func TestDeleteDisabled(t *testing.T) {
|
|
|
|
|
env := newTestEnv(t, false)
|
|
|
|
|
|
|
|
|
|
imageName := "foo/bar"
|
|
|
|
|
imageName, _ := reference.ParseNamed("foo/bar")
|
|
|
|
|
// "build" our layer file
|
|
|
|
|
layerFile, layerDigest, err := testutil.CreateRandomTarFile()
|
|
|
|
|
if err != nil {
|
|
|
|
@@ -634,7 +635,7 @@ func TestDeleteDisabled(t *testing.T) {
|
|
|
|
|
func TestDeleteReadOnly(t *testing.T) {
|
|
|
|
|
env := newTestEnv(t, true)
|
|
|
|
|
|
|
|
|
|
imageName := "foo/bar"
|
|
|
|
|
imageName, _ := reference.ParseNamed("foo/bar")
|
|
|
|
|
// "build" our layer file
|
|
|
|
|
layerFile, layerDigest, err := testutil.CreateRandomTarFile()
|
|
|
|
|
if err != nil {
|
|
|
|
@@ -662,7 +663,7 @@ func TestStartPushReadOnly(t *testing.T) {
|
|
|
|
|
env := newTestEnv(t, true)
|
|
|
|
|
env.app.readOnly = true
|
|
|
|
|
|
|
|
|
|
imageName := "foo/bar"
|
|
|
|
|
imageName, _ := reference.ParseNamed("foo/bar")
|
|
|
|
|
|
|
|
|
|
layerUploadURL, err := env.builder.BuildBlobUploadURL(imageName)
|
|
|
|
|
if err != nil {
|
|
|
|
@@ -693,42 +694,49 @@ func httpDelete(url string) (*http.Response, error) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type manifestArgs struct {
|
|
|
|
|
imageName string
|
|
|
|
|
imageName reference.Named
|
|
|
|
|
mediaType string
|
|
|
|
|
manifest distribution.Manifest
|
|
|
|
|
dgst digest.Digest
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestManifestAPI(t *testing.T) {
|
|
|
|
|
schema1Repo, _ := reference.ParseNamed("foo/schema1")
|
|
|
|
|
schema2Repo, _ := reference.ParseNamed("foo/schema2")
|
|
|
|
|
|
|
|
|
|
deleteEnabled := false
|
|
|
|
|
env := newTestEnv(t, deleteEnabled)
|
|
|
|
|
testManifestAPISchema1(t, env, "foo/schema1")
|
|
|
|
|
schema2Args := testManifestAPISchema2(t, env, "foo/schema2")
|
|
|
|
|
testManifestAPISchema1(t, env, schema1Repo)
|
|
|
|
|
schema2Args := testManifestAPISchema2(t, env, schema2Repo)
|
|
|
|
|
testManifestAPIManifestList(t, env, schema2Args)
|
|
|
|
|
|
|
|
|
|
deleteEnabled = true
|
|
|
|
|
env = newTestEnv(t, deleteEnabled)
|
|
|
|
|
testManifestAPISchema1(t, env, "foo/schema1")
|
|
|
|
|
schema2Args = testManifestAPISchema2(t, env, "foo/schema2")
|
|
|
|
|
testManifestAPISchema1(t, env, schema1Repo)
|
|
|
|
|
schema2Args = testManifestAPISchema2(t, env, schema2Repo)
|
|
|
|
|
testManifestAPIManifestList(t, env, schema2Args)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestManifestDelete(t *testing.T) {
|
|
|
|
|
schema1Repo, _ := reference.ParseNamed("foo/schema1")
|
|
|
|
|
schema2Repo, _ := reference.ParseNamed("foo/schema2")
|
|
|
|
|
|
|
|
|
|
deleteEnabled := true
|
|
|
|
|
env := newTestEnv(t, deleteEnabled)
|
|
|
|
|
schema1Args := testManifestAPISchema1(t, env, "foo/schema1")
|
|
|
|
|
schema1Args := testManifestAPISchema1(t, env, schema1Repo)
|
|
|
|
|
testManifestDelete(t, env, schema1Args)
|
|
|
|
|
schema2Args := testManifestAPISchema2(t, env, "foo/schema2")
|
|
|
|
|
schema2Args := testManifestAPISchema2(t, env, schema2Repo)
|
|
|
|
|
testManifestDelete(t, env, schema2Args)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestManifestDeleteDisabled(t *testing.T) {
|
|
|
|
|
schema1Repo, _ := reference.ParseNamed("foo/schema1")
|
|
|
|
|
deleteEnabled := false
|
|
|
|
|
env := newTestEnv(t, deleteEnabled)
|
|
|
|
|
testManifestDeleteDisabled(t, env, "foo/schema1")
|
|
|
|
|
testManifestDeleteDisabled(t, env, schema1Repo)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testManifestDeleteDisabled(t *testing.T, env *testEnv, imageName string) {
|
|
|
|
|
func testManifestDeleteDisabled(t *testing.T, env *testEnv, imageName reference.Named) {
|
|
|
|
|
manifestURL, err := env.builder.BuildManifestURL(imageName, digest.DigestSha256EmptyTar)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("unexpected error getting manifest url: %v", err)
|
|
|
|
@@ -743,7 +751,7 @@ func testManifestDeleteDisabled(t *testing.T, env *testEnv, imageName string) {
|
|
|
|
|
checkResponse(t, "status of disabled delete of manifest", resp, http.StatusMethodNotAllowed)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testManifestAPISchema1(t *testing.T, env *testEnv, imageName string) manifestArgs {
|
|
|
|
|
func testManifestAPISchema1(t *testing.T, env *testEnv, imageName reference.Named) manifestArgs {
|
|
|
|
|
tag := "thetag"
|
|
|
|
|
args := manifestArgs{imageName: imageName}
|
|
|
|
|
|
|
|
|
@@ -784,7 +792,7 @@ func testManifestAPISchema1(t *testing.T, env *testEnv, imageName string) manife
|
|
|
|
|
Versioned: manifest.Versioned{
|
|
|
|
|
SchemaVersion: 1,
|
|
|
|
|
},
|
|
|
|
|
Name: imageName,
|
|
|
|
|
Name: imageName.Name(),
|
|
|
|
|
Tag: tag,
|
|
|
|
|
FSLayers: []schema1.FSLayer{
|
|
|
|
|
{
|
|
|
|
@@ -1032,8 +1040,8 @@ func testManifestAPISchema1(t *testing.T, env *testEnv, imageName string) manife
|
|
|
|
|
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 tagsResponse.Name != imageName.Name() {
|
|
|
|
|
t.Fatalf("tags name should match image name: %v != %v", tagsResponse.Name, imageName.Name())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(tagsResponse.Tags) != 1 {
|
|
|
|
@@ -1060,7 +1068,7 @@ func testManifestAPISchema1(t *testing.T, env *testEnv, imageName string) manife
|
|
|
|
|
return args
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testManifestAPISchema2(t *testing.T, env *testEnv, imageName string) manifestArgs {
|
|
|
|
|
func testManifestAPISchema2(t *testing.T, env *testEnv, imageName reference.Named) manifestArgs {
|
|
|
|
|
tag := "schema2tag"
|
|
|
|
|
args := manifestArgs{
|
|
|
|
|
imageName: imageName,
|
|
|
|
@@ -1340,7 +1348,7 @@ func testManifestAPISchema2(t *testing.T, env *testEnv, imageName string) manife
|
|
|
|
|
t.Fatalf("unexpected error decoding error response: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if tagsResponse.Name != imageName {
|
|
|
|
|
if tagsResponse.Name != imageName.Name() {
|
|
|
|
|
t.Fatalf("tags name should match image name: %v != %v", tagsResponse.Name, imageName)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -1379,7 +1387,7 @@ func testManifestAPISchema2(t *testing.T, env *testEnv, imageName string) manife
|
|
|
|
|
if fetchedSchema1Manifest.Architecture != "amd64" {
|
|
|
|
|
t.Fatal("wrong architecture")
|
|
|
|
|
}
|
|
|
|
|
if fetchedSchema1Manifest.Name != imageName {
|
|
|
|
|
if fetchedSchema1Manifest.Name != imageName.Name() {
|
|
|
|
|
t.Fatal("wrong image name")
|
|
|
|
|
}
|
|
|
|
|
if fetchedSchema1Manifest.Tag != tag {
|
|
|
|
@@ -1602,7 +1610,7 @@ func testManifestAPIManifestList(t *testing.T, env *testEnv, args manifestArgs)
|
|
|
|
|
if fetchedSchema1Manifest.Architecture != "amd64" {
|
|
|
|
|
t.Fatal("wrong architecture")
|
|
|
|
|
}
|
|
|
|
|
if fetchedSchema1Manifest.Name != imageName {
|
|
|
|
|
if fetchedSchema1Manifest.Name != imageName.Name() {
|
|
|
|
|
t.Fatal("wrong image name")
|
|
|
|
|
}
|
|
|
|
|
if fetchedSchema1Manifest.Tag != tag {
|
|
|
|
@@ -1715,7 +1723,7 @@ func testManifestDelete(t *testing.T, env *testEnv, args manifestArgs) {
|
|
|
|
|
t.Fatalf("unexpected error decoding error response: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if tagsResponse.Name != imageName {
|
|
|
|
|
if tagsResponse.Name != imageName.Name() {
|
|
|
|
|
t.Fatalf("tags name should match image name: %v != %v", tagsResponse.Name, imageName)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -1749,7 +1757,7 @@ func testManifestDelete(t *testing.T, env *testEnv, args manifestArgs) {
|
|
|
|
|
t.Fatalf("unexpected error decoding error response: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if tagsResponse.Name != imageName {
|
|
|
|
|
if tagsResponse.Name != imageName.Name() {
|
|
|
|
|
t.Fatalf("tags name should match image name: %v != %v", tagsResponse.Name, imageName)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -1863,7 +1871,7 @@ func putManifest(t *testing.T, msg, url, contentType string, v interface{}) *htt
|
|
|
|
|
return resp
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func startPushLayer(t *testing.T, ub *v2.URLBuilder, name string) (location string, uuid string) {
|
|
|
|
|
func startPushLayer(t *testing.T, ub *v2.URLBuilder, name reference.Named) (location string, uuid string) {
|
|
|
|
|
layerUploadURL, err := ub.BuildBlobUploadURL(name)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("unexpected error building layer upload url: %v", err)
|
|
|
|
@@ -1875,7 +1883,7 @@ func startPushLayer(t *testing.T, ub *v2.URLBuilder, name string) (location stri
|
|
|
|
|
}
|
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
|
|
checkResponse(t, fmt.Sprintf("pushing starting layer push %v", name), resp, http.StatusAccepted)
|
|
|
|
|
checkResponse(t, fmt.Sprintf("pushing starting layer push %v", name.String()), resp, http.StatusAccepted)
|
|
|
|
|
|
|
|
|
|
u, err := url.Parse(resp.Header.Get("Location"))
|
|
|
|
|
if err != nil {
|
|
|
|
@@ -1894,7 +1902,7 @@ func startPushLayer(t *testing.T, ub *v2.URLBuilder, name string) (location stri
|
|
|
|
|
|
|
|
|
|
// doPushLayer pushes the layer content returning the url on success returning
|
|
|
|
|
// the response. If you're only expecting a successful response, use pushLayer.
|
|
|
|
|
func doPushLayer(t *testing.T, ub *v2.URLBuilder, name string, dgst digest.Digest, uploadURLBase string, body io.Reader) (*http.Response, error) {
|
|
|
|
|
func doPushLayer(t *testing.T, ub *v2.URLBuilder, name reference.Named, dgst digest.Digest, uploadURLBase string, body io.Reader) (*http.Response, error) {
|
|
|
|
|
u, err := url.Parse(uploadURLBase)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("unexpected error parsing pushLayer url: %v", err)
|
|
|
|
@@ -1918,7 +1926,7 @@ func doPushLayer(t *testing.T, ub *v2.URLBuilder, name string, dgst digest.Diges
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// pushLayer pushes the layer content returning the url on success.
|
|
|
|
|
func pushLayer(t *testing.T, ub *v2.URLBuilder, name string, dgst digest.Digest, uploadURLBase string, body io.Reader) string {
|
|
|
|
|
func pushLayer(t *testing.T, ub *v2.URLBuilder, name reference.Named, dgst digest.Digest, uploadURLBase string, body io.Reader) string {
|
|
|
|
|
digester := digest.Canonical.New()
|
|
|
|
|
|
|
|
|
|
resp, err := doPushLayer(t, ub, name, dgst, uploadURLBase, io.TeeReader(body, digester.Hash()))
|
|
|
|
@@ -1949,7 +1957,7 @@ func pushLayer(t *testing.T, ub *v2.URLBuilder, name string, dgst digest.Digest,
|
|
|
|
|
return resp.Header.Get("Location")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func finishUpload(t *testing.T, ub *v2.URLBuilder, name string, uploadURLBase string, dgst digest.Digest) string {
|
|
|
|
|
func finishUpload(t *testing.T, ub *v2.URLBuilder, name reference.Named, uploadURLBase string, dgst digest.Digest) string {
|
|
|
|
|
resp, err := doPushLayer(t, ub, name, dgst, uploadURLBase, nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("unexpected error doing push layer request: %v", err)
|
|
|
|
@@ -1997,7 +2005,7 @@ func doPushChunk(t *testing.T, uploadURLBase string, body io.Reader) (*http.Resp
|
|
|
|
|
return resp, digester.Digest(), err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func pushChunk(t *testing.T, ub *v2.URLBuilder, name string, uploadURLBase string, body io.Reader, length int64) (string, digest.Digest) {
|
|
|
|
|
func pushChunk(t *testing.T, ub *v2.URLBuilder, name reference.Named, uploadURLBase string, body io.Reader, length int64) (string, digest.Digest) {
|
|
|
|
|
resp, dgst, err := doPushChunk(t, uploadURLBase, body)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("unexpected error doing push layer request: %v", err)
|
|
|
|
@@ -2133,6 +2141,11 @@ func checkErr(t *testing.T, err error, msg string) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func createRepository(env *testEnv, t *testing.T, imageName string, tag string) digest.Digest {
|
|
|
|
|
imageNameRef, err := reference.ParseNamed(imageName)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("unable to parse reference: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unsignedManifest := &schema1.Manifest{
|
|
|
|
|
Versioned: manifest.Versioned{
|
|
|
|
|
SchemaVersion: 1,
|
|
|
|
@@ -2164,8 +2177,8 @@ func createRepository(env *testEnv, t *testing.T, imageName string, tag string)
|
|
|
|
|
expectedLayers[dgst] = rs
|
|
|
|
|
unsignedManifest.FSLayers[i].BlobSum = dgst
|
|
|
|
|
|
|
|
|
|
uploadURLBase, _ := startPushLayer(t, env.builder, imageName)
|
|
|
|
|
pushLayer(t, env.builder, imageName, dgst, uploadURLBase, rs)
|
|
|
|
|
uploadURLBase, _ := startPushLayer(t, env.builder, imageNameRef)
|
|
|
|
|
pushLayer(t, env.builder, imageNameRef, dgst, uploadURLBase, rs)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
signedManifest, err := schema1.Sign(unsignedManifest, env.pk)
|
|
|
|
@@ -2176,10 +2189,10 @@ func createRepository(env *testEnv, t *testing.T, imageName string, tag string)
|
|
|
|
|
dgst := digest.FromBytes(signedManifest.Canonical)
|
|
|
|
|
|
|
|
|
|
// Create this repository by tag to ensure the tag mapping is made in the registry
|
|
|
|
|
manifestDigestURL, err := env.builder.BuildManifestURL(imageName, tag)
|
|
|
|
|
manifestDigestURL, err := env.builder.BuildManifestURL(imageNameRef, tag)
|
|
|
|
|
checkErr(t, err, "building manifest url")
|
|
|
|
|
|
|
|
|
|
location, err := env.builder.BuildManifestURL(imageName, dgst.String())
|
|
|
|
|
location, err := env.builder.BuildManifestURL(imageNameRef, dgst.String())
|
|
|
|
|
checkErr(t, err, "building location URL")
|
|
|
|
|
|
|
|
|
|
resp := putManifest(t, "putting signed manifest", manifestDigestURL, "", signedManifest)
|
|
|
|
@@ -2197,7 +2210,7 @@ func TestRegistryAsCacheMutationAPIs(t *testing.T) {
|
|
|
|
|
deleteEnabled := true
|
|
|
|
|
env := newTestEnvMirror(t, deleteEnabled)
|
|
|
|
|
|
|
|
|
|
imageName := "foo/bar"
|
|
|
|
|
imageName, _ := reference.ParseNamed("foo/bar")
|
|
|
|
|
tag := "latest"
|
|
|
|
|
manifestURL, err := env.builder.BuildManifestURL(imageName, tag)
|
|
|
|
|
if err != nil {
|
|
|
|
@@ -2209,7 +2222,7 @@ func TestRegistryAsCacheMutationAPIs(t *testing.T) {
|
|
|
|
|
Versioned: manifest.Versioned{
|
|
|
|
|
SchemaVersion: 1,
|
|
|
|
|
},
|
|
|
|
|
Name: imageName,
|
|
|
|
|
Name: imageName.Name(),
|
|
|
|
|
Tag: tag,
|
|
|
|
|
FSLayers: []schema1.FSLayer{},
|
|
|
|
|
History: []schema1.History{},
|
|
|
|
@@ -2284,12 +2297,12 @@ func TestProxyManifestGetByTag(t *testing.T) {
|
|
|
|
|
}
|
|
|
|
|
truthConfig.HTTP.Headers = headerConfig
|
|
|
|
|
|
|
|
|
|
imageName := "foo/bar"
|
|
|
|
|
imageName, _ := reference.ParseNamed("foo/bar")
|
|
|
|
|
tag := "latest"
|
|
|
|
|
|
|
|
|
|
truthEnv := newTestEnvWithConfig(t, &truthConfig)
|
|
|
|
|
// create a repository in the truth registry
|
|
|
|
|
dgst := createRepository(truthEnv, t, imageName, tag)
|
|
|
|
|
dgst := createRepository(truthEnv, t, imageName.Name(), tag)
|
|
|
|
|
|
|
|
|
|
proxyConfig := configuration.Configuration{
|
|
|
|
|
Storage: configuration.Storage{
|
|
|
|
@@ -2322,7 +2335,7 @@ func TestProxyManifestGetByTag(t *testing.T) {
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Create another manifest in the remote with the same image/tag pair
|
|
|
|
|
newDigest := createRepository(truthEnv, t, imageName, tag)
|
|
|
|
|
newDigest := createRepository(truthEnv, t, imageName.Name(), tag)
|
|
|
|
|
if dgst == newDigest {
|
|
|
|
|
t.Fatalf("non-random test data")
|
|
|
|
|
}
|
|
|
|
|