From 02efb047a3aa42c2b862569e4e767b8118ceae50 Mon Sep 17 00:00:00 2001 From: Dongsu Park Date: Fri, 23 Nov 2018 15:13:39 +0100 Subject: [PATCH 1/4] dist,pkg: add test for uploading layer Add an UploadLayer test. Run the 3 tests sequentially: UploadLayer, PushManifest, PullManifest. Signed-off-by: Dongsu Park --- test/dist/dist_test.go | 76 ++++++++++++++++++++++++++++++++++++------ test/pkg/image/util.go | 20 +++++++++++ 2 files changed, 86 insertions(+), 10 deletions(-) diff --git a/test/dist/dist_test.go b/test/dist/dist_test.go index e540e7db..c2a77ea4 100644 --- a/test/dist/dist_test.go +++ b/test/dist/dist_test.go @@ -18,10 +18,12 @@ import ( "net/http" "os" "path/filepath" + "strings" "testing" "github.com/opencontainers/distribution-spec/test/pkg/auth" distp "github.com/opencontainers/distribution-spec/test/pkg/distp" + "github.com/opencontainers/distribution-spec/test/pkg/image" ) var ( @@ -35,8 +37,6 @@ var ( func init() { homeDir = os.Getenv("HOME") - - regURL = regAuthCtx.RegURL } func TestCheckAPIVersion(t *testing.T) { @@ -64,15 +64,64 @@ func TestCheckAPIVersion(t *testing.T) { } } -func TestPullManifest(t *testing.T) { +func testUploadLayer(t *testing.T) { + regAuthCtx := auth.NewRegAuthContext() + regURL := regAuthCtx.RegURL indexServer := auth.GetIndexServer(regURL) remoteName := filepath.Join(auth.DefaultRepoPrefix, testImageName) - reqPath := filepath.Join(remoteName, "manifests", testRefName) + reqPath := filepath.Join(remoteName, "blobs/uploads", testRefName) + + regAuthCtx.Scope.RemoteName = remoteName + regAuthCtx.Scope.Actions = "push" + + if err := regAuthCtx.PrepareAuth(indexServer); err != nil { + t.Fatalf("failed to prepare auth to %s for %s: %v", indexServer, reqPath, err) + } + + // 1. POST + // Send a POST request without any body specified. + postURL := "https://" + indexServer + "/v2/" + reqPath + + res, err := regAuthCtx.GetResponse(postURL, "POST", nil, []int{http.StatusOK, http.StatusAccepted}) + if err != nil { + t.Fatalf("got an unexpected reply: %v", err) + } + + uuid := res.Header.Get(distp.UploadUuidKey) + + // 2. PATCH + // Generate a 100-byte blob of a randomly generated string. + // Send a PATCH request with the blob. + blob := image.GenRandomBlob(100) + + if _, err := regAuthCtx.GetResponse(postURL, "PATCH", strings.NewReader(blob), + []int{http.StatusOK, http.StatusAccepted}); err != nil { + t.Fatalf("got an unexpected reply: %v", err) + } + // 3. PUT + // Generate a blob's digest, generated as a sha256 checksum of the blob. + // Send a PUT request with a "digest=..." option appended to its URL. + digest := image.GetHash(blob) + putURL := "https://" + indexServer + "/v2/" + reqPath + "/" + uuid + "?digest=" + digest + + if _, err := regAuthCtx.GetResponse(putURL, "PUT", strings.NewReader(blob), + []int{http.StatusCreated}); err != nil { + t.Fatalf("got an unexpected reply: %v", err) + } +} + +func testPushManifest(t *testing.T) { regAuthCtx := auth.NewRegAuthContext() + regURL := regAuthCtx.RegURL + indexServer := auth.GetIndexServer(regURL) + + remoteName := filepath.Join(auth.DefaultRepoPrefix, testImageName) + reqPath := filepath.Join(remoteName, "manifests", testRefName) + regAuthCtx.Scope.RemoteName = remoteName - regAuthCtx.Scope.Actions = "pull" + regAuthCtx.Scope.Actions = "push" if err := regAuthCtx.PrepareAuth(indexServer); err != nil { t.Fatalf("failed to prepare auth to %s for %s: %v", indexServer, reqPath, err) @@ -80,20 +129,21 @@ func TestPullManifest(t *testing.T) { inputURL := "https://" + indexServer + "/v2/" + reqPath - if _, err := regAuthCtx.GetResponse(inputURL, "GET", nil, []int{http.StatusOK}); err != nil { + if _, err := regAuthCtx.GetResponse(inputURL, "PUT", nil, []int{http.StatusCreated}); err != nil { t.Fatalf("got an unexpected reply: %v", err) } } -func TestPushManifest(t *testing.T) { +func testPullManifest(t *testing.T) { + regAuthCtx := auth.NewRegAuthContext() + regURL := regAuthCtx.RegURL indexServer := auth.GetIndexServer(regURL) remoteName := filepath.Join(auth.DefaultRepoPrefix, testImageName) reqPath := filepath.Join(remoteName, "manifests", testRefName) - regAuthCtx := auth.NewRegAuthContext() regAuthCtx.Scope.RemoteName = remoteName - regAuthCtx.Scope.Actions = "push" + regAuthCtx.Scope.Actions = "pull" if err := regAuthCtx.PrepareAuth(indexServer); err != nil { t.Fatalf("failed to prepare auth to %s for %s: %v", indexServer, reqPath, err) @@ -101,7 +151,13 @@ func TestPushManifest(t *testing.T) { inputURL := "https://" + indexServer + "/v2/" + reqPath - if _, err := regAuthCtx.GetResponse(inputURL, "PUT", nil, []int{http.StatusOK}); err != nil { + if _, err := regAuthCtx.GetResponse(inputURL, "GET", nil, []int{http.StatusOK}); err != nil { t.Fatalf("got an unexpected reply: %v", err) } } + +func TestPushPullLayer(t *testing.T) { + testUploadLayer(t) + testPushManifest(t) + testPullManifest(t) +} diff --git a/test/pkg/image/util.go b/test/pkg/image/util.go index dbd5e1c8..7e5b65c2 100644 --- a/test/pkg/image/util.go +++ b/test/pkg/image/util.go @@ -13,3 +13,23 @@ // limitations under the License. package image + +import ( + "crypto/sha256" + "math/rand" + "strconv" +) + +func GenRandomBlob(blobLen int) string { + blob := "" + for i := 0; i < blobLen; i++ { + blob += strconv.Itoa(rand.Intn(9)) + } + return blob +} + +func GetHash(inputStr string) string { + h := sha256.New() + h.Write([]byte(inputStr)) + return string(h.Sum(nil)) +} From 3c2f2d484cbb73bea54cda7370598ee7b2818553 Mon Sep 17 00:00:00 2001 From: Dongsu Park Date: Fri, 30 Nov 2018 15:40:08 +0100 Subject: [PATCH 2/4] dist: add tests for pulling layer/manifest, and deleting Add tests about pulling and deleting layers or manifests, like PullLayer, DeleteLayer, DeleteManifest. Signed-off-by: Dongsu Park --- test/dist/dist_test.go | 107 ++++++++++++++++++++++++++++++++++++++++ test/pkg/distp/distp.go | 1 + 2 files changed, 108 insertions(+) diff --git a/test/dist/dist_test.go b/test/dist/dist_test.go index c2a77ea4..dd20b265 100644 --- a/test/dist/dist_test.go +++ b/test/dist/dist_test.go @@ -15,6 +15,7 @@ package dist import ( + "fmt" "net/http" "os" "path/filepath" @@ -45,6 +46,7 @@ func TestCheckAPIVersion(t *testing.T) { regAuthCtx := auth.NewRegAuthContext() regAuthCtx.Scope.RemoteName = reqPath regAuthCtx.Scope.Actions = "pull" + regURL := regAuthCtx.RegURL indexServer := auth.GetIndexServer(regURL) @@ -64,6 +66,30 @@ func TestCheckAPIVersion(t *testing.T) { } } +func getDigestFromManifest(regURL, testImageName, testRefName string) (string, error) { + indexServer := auth.GetIndexServer(regURL) + + remoteName := filepath.Join(auth.DefaultRepoPrefix, testImageName) + reqPath := filepath.Join(remoteName, "manifests", testRefName) + + regAuthCtx := auth.NewRegAuthContext() + regAuthCtx.Scope.RemoteName = remoteName + regAuthCtx.Scope.Actions = "pull" + + if err := regAuthCtx.PrepareAuth(indexServer); err != nil { + return "", fmt.Errorf("failed to prepare auth to %s for %s: %v", indexServer, reqPath, err) + } + + inputURL := "https://" + indexServer + "/v2/" + reqPath + + res, err := regAuthCtx.GetResponse(inputURL, "HEAD", nil, []int{http.StatusOK}) + if err != nil { + return "", fmt.Errorf("got an unexpected reply: %v", err) + } + + return res.Header.Get(distp.ContentDigest), nil +} + func testUploadLayer(t *testing.T) { regAuthCtx := auth.NewRegAuthContext() regURL := regAuthCtx.RegURL @@ -156,8 +182,89 @@ func testPullManifest(t *testing.T) { } } +func testPullLayer(t *testing.T) { + regAuthCtx := auth.NewRegAuthContext() + regURL := regAuthCtx.RegURL + indexServer := auth.GetIndexServer(regURL) + + remoteName := filepath.Join(auth.DefaultRepoPrefix, testImageName) + + testDigest, err := getDigestFromManifest(regURL, testImageName, testRefName) + if err != nil { + t.Fatalf("failed to get digest from %s: %v", indexServer, err) + } + + reqPath := filepath.Join(remoteName, "blobs", testDigest) + + regAuthCtx.Scope.RemoteName = remoteName + regAuthCtx.Scope.Actions = "pull" + + if err := regAuthCtx.PrepareAuth(indexServer); err != nil { + t.Fatalf("failed to prepare auth to %s for %s: %v", indexServer, reqPath, err) + } + + inputURL := "https://" + indexServer + "/v2/" + reqPath + + if _, err := regAuthCtx.GetResponse(inputURL, "GET", nil, []int{http.StatusOK}); err != nil { + t.Fatalf("got an unexpected reply: %v", err) + } +} + +func testDeleteLayer(t *testing.T) { + regAuthCtx := auth.NewRegAuthContext() + regURL := regAuthCtx.RegURL + indexServer := auth.GetIndexServer(regURL) + + remoteName := filepath.Join(auth.DefaultRepoPrefix, testImageName) + + testDigest, err := getDigestFromManifest(regURL, testImageName, testRefName) + if err != nil { + t.Fatalf("failed to get digest from %s: %v", indexServer, err) + } + + reqPath := filepath.Join(remoteName, "blobs", testDigest) + + regAuthCtx.Scope.RemoteName = remoteName + regAuthCtx.Scope.Actions = "push" + + if err := regAuthCtx.PrepareAuth(indexServer); err != nil { + t.Fatalf("failed to prepare auth to %s for %s: %v", indexServer, reqPath, err) + } + + inputURL := "https://" + indexServer + "/v2/" + reqPath + + if _, err := regAuthCtx.GetResponse(inputURL, "DELETE", nil, []int{http.StatusAccepted}); err != nil { + t.Fatalf("got an unexpected reply: %v", err) + } +} + +func testDeleteManifest(t *testing.T) { + regAuthCtx := auth.NewRegAuthContext() + regURL := regAuthCtx.RegURL + indexServer := auth.GetIndexServer(regURL) + + remoteName := filepath.Join(auth.DefaultRepoPrefix, testImageName) + reqPath := filepath.Join(remoteName, "manifests", testRefName) + + regAuthCtx.Scope.RemoteName = remoteName + regAuthCtx.Scope.Actions = "push" + + if err := regAuthCtx.PrepareAuth(indexServer); err != nil { + t.Fatalf("failed to prepare auth to %s for %s: %v", indexServer, reqPath, err) + } + + inputURL := "https://" + indexServer + "/v2/" + reqPath + + if _, err := regAuthCtx.GetResponse(inputURL, "DELETE", nil, []int{http.StatusAccepted}); err != nil { + t.Fatalf("got an unexpected reply: %v", err) + } +} + func TestPushPullLayer(t *testing.T) { testUploadLayer(t) testPushManifest(t) testPullManifest(t) + testPullLayer(t) + testDeleteLayer(t) + testDeleteManifest(t) } diff --git a/test/pkg/distp/distp.go b/test/pkg/distp/distp.go index a542b046..94e2c598 100644 --- a/test/pkg/distp/distp.go +++ b/test/pkg/distp/distp.go @@ -19,4 +19,5 @@ const ( DistAPIVersionValue string = "registry/2.0" UploadUuidKey string = "Docker-Upload-Uuid" + ContentDigest string = "Docker-Content-Digest" ) From 6903ea9ab5fcf270efdfc5019beaaec87e3a315d Mon Sep 17 00:00:00 2001 From: Dongsu Park Date: Fri, 30 Nov 2018 13:52:26 +0100 Subject: [PATCH 3/4] dist: add tests for listing repos, tags Added tests for listing existing repos and tags of a given manifest. Signed-off-by: Dongsu Park --- test/dist/dist_test.go | 48 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/test/dist/dist_test.go b/test/dist/dist_test.go index dd20b265..cf79e884 100644 --- a/test/dist/dist_test.go +++ b/test/dist/dist_test.go @@ -268,3 +268,51 @@ func TestPushPullLayer(t *testing.T) { testDeleteLayer(t) testDeleteManifest(t) } + +func TestListRepos(t *testing.T) { + reqPath := "_catalog" + + regAuthCtx := auth.NewRegAuthContext() + regURL := regAuthCtx.RegURL + + regAuthCtx.Scope.RemoteName = reqPath + regAuthCtx.Scope.Actions = "pull" + + indexServer := auth.GetIndexServer(regURL) + + // NOTE: it will fail when testing against docker.io, as '/v2/_catalog' endpoint + // will not be supported. + if err := regAuthCtx.PrepareAuth(indexServer); err != nil { + t.Fatalf("failed to prepare auth to %s for %s: %v", indexServer, reqPath, err) + } + + inputURL := "https://" + indexServer + "/v2/" + reqPath + + _, err := regAuthCtx.GetResponse(inputURL, "GET", nil, []int{http.StatusOK}) + if err != nil { + t.Fatalf("got an unexpected reply: %v", err) + } +} + +func TestListTags(t *testing.T) { + regAuthCtx := auth.NewRegAuthContext() + regURL := regAuthCtx.RegURL + + indexServer := auth.GetIndexServer(regURL) + + remoteName := filepath.Join(auth.DefaultRepoPrefix, testImageName) + reqPath := filepath.Join(remoteName, "tags/list") + + regAuthCtx.Scope.RemoteName = remoteName + regAuthCtx.Scope.Actions = "pull" + + if err := regAuthCtx.PrepareAuth(indexServer); err != nil { + t.Fatalf("failed to prepare auth to %s for %s: %v", indexServer, reqPath, err) + } + + inputURL := "https://" + indexServer + "/v2/" + reqPath + + if _, err := regAuthCtx.GetResponse(inputURL, "GET", nil, []int{http.StatusOK}); err != nil { + t.Fatalf("got an unexpected reply: %v", err) + } +} From 6b5c54de2eda60cd8e2778c844879b8d5b4b3926 Mon Sep 17 00:00:00 2001 From: Dongsu Park Date: Wed, 19 Dec 2018 16:06:25 +0100 Subject: [PATCH 4/4] dist: check also for the path /tags Before checking for /tags/list, we should also check if /tags is available. Signed-off-by: Dongsu Park --- test/dist/dist_test.go | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/test/dist/dist_test.go b/test/dist/dist_test.go index cf79e884..ee39bc1b 100644 --- a/test/dist/dist_test.go +++ b/test/dist/dist_test.go @@ -180,6 +180,21 @@ func testPullManifest(t *testing.T) { if _, err := regAuthCtx.GetResponse(inputURL, "GET", nil, []int{http.StatusOK}); err != nil { t.Fatalf("got an unexpected reply: %v", err) } + + reqPath = filepath.Join(remoteName, "tags/list") + + regAuthCtx.Scope.RemoteName = remoteName + regAuthCtx.Scope.Actions = "pull" + + if err := regAuthCtx.PrepareAuth(indexServer); err != nil { + t.Fatalf("failed to prepare auth to %s for %s: %v", indexServer, reqPath, err) + } + + inputURL = "https://" + indexServer + "/v2/" + reqPath + + if _, err := regAuthCtx.GetResponse(inputURL, "GET", nil, []int{http.StatusOK}); err != nil { + t.Fatalf("got an unexpected reply: %v", err) + } } func testPullLayer(t *testing.T) { @@ -301,7 +316,7 @@ func TestListTags(t *testing.T) { indexServer := auth.GetIndexServer(regURL) remoteName := filepath.Join(auth.DefaultRepoPrefix, testImageName) - reqPath := filepath.Join(remoteName, "tags/list") + reqPath := filepath.Join(remoteName, "tags/") regAuthCtx.Scope.RemoteName = remoteName regAuthCtx.Scope.Actions = "pull"