From a57f08574979db8d64746eb57c4390eccb2ee787 Mon Sep 17 00:00:00 2001 From: Ramkumar Chinchani Date: Mon, 23 Dec 2019 22:32:52 -0800 Subject: [PATCH] compliance: cleanup compliance test code zot ci/cd tests are too stict, so separate and relax them for compliance tests. Location header is set in some cases, but some clarification is needed in URL construction. Fix some incorrect compliance tests. --- pkg/api/controller.go | 1 + pkg/api/regexp.go | 2 +- pkg/api/routes.go | 2 +- pkg/compliance/config.go | 3 +- pkg/compliance/v1_0_0/check.go | 197 ++++++++++++++++++++------------- 5 files changed, 124 insertions(+), 81 deletions(-) diff --git a/pkg/api/controller.go b/pkg/api/controller.go index 3a679705..6098ed92 100644 --- a/pkg/api/controller.go +++ b/pkg/api/controller.go @@ -42,6 +42,7 @@ func (c *Controller) Run() error { handlers.PrintRecoveryStack(false))) c.Router = engine + c.Router.UseEncodedPath() _ = NewRouteHandler(c) c.ImageStore = storage.NewImageStore(c.Config.Storage.RootDirectory, c.Log) diff --git a/pkg/api/regexp.go b/pkg/api/regexp.go index e2df041e..45b9a86b 100644 --- a/pkg/api/regexp.go +++ b/pkg/api/regexp.go @@ -6,7 +6,7 @@ import "regexp" var ( // alphaNumericRegexp defines the alpha numeric atom, typically a // component of names. This only allows lower case characters and digits. - alphaNumericRegexp = match(`[a-z0-9]+`) + alphaNumericRegexp = match(`[a-zA-Z0-9]+`) // separatorRegexp defines the separators allowed to be embedded in name // components. This allow one period, one or two underscore and multiple diff --git a/pkg/api/routes.go b/pkg/api/routes.go index 99bb69af..0f7e6433 100644 --- a/pkg/api/routes.go +++ b/pkg/api/routes.go @@ -838,7 +838,7 @@ func (rh *RouteHandler) DeleteBlobUpload(w http.ResponseWriter, r *http.Request) return } - w.WriteHeader(http.StatusOK) + w.WriteHeader(http.StatusNoContent) } type RepositoryList struct { diff --git a/pkg/compliance/config.go b/pkg/compliance/config.go index 721d7312..8b17140e 100644 --- a/pkg/compliance/config.go +++ b/pkg/compliance/config.go @@ -5,8 +5,9 @@ type Config struct { Port string Version string OutputJSON bool + Compliance bool } func NewConfig() *Config { - return &Config{} + return &Config{Compliance: true} } diff --git a/pkg/compliance/v1_0_0/check.go b/pkg/compliance/v1_0_0/check.go index 2458ea1f..cc5cf31b 100644 --- a/pkg/compliance/v1_0_0/check.go +++ b/pkg/compliance/v1_0_0/check.go @@ -19,6 +19,20 @@ import ( "gopkg.in/resty.v1" ) +func Location(baseURL string, resp *resty.Response, config *compliance.Config) string { + // For some API responses, the Location header is set and is supposed to + // indicate an opaque value. However, it is not clear if this value is an + // absolute URL (https://server:port/v2/...) or just a path (/v2/...) + // zot implements the latter as per the spec, but some registries appear to + // return the former - this needs to be clarified + loc := resp.Header().Get("Location") + if config.Compliance { + return loc + } + + return baseURL + loc +} + func CheckWorkflows(t *testing.T, config *compliance.Config) { if config == nil || config.Address == "" || config.Port == "" { panic("insufficient config") @@ -70,140 +84,152 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) { So(resp.StatusCode(), ShouldEqual, 200) So(resp.String(), ShouldNotBeEmpty) r := resp.Result().(*api.RepositoryList) - So(len(r.Repositories), ShouldBeGreaterThan, 0) - So(r.Repositories[0], ShouldEqual, "a/b/c/d") - So(r.Repositories[1], ShouldEqual, "z") + if !config.Compliance { + // stricter check for zot ci/cd + So(len(r.Repositories), ShouldBeGreaterThan, 0) + So(r.Repositories[0], ShouldEqual, "a/b/c/d") + So(r.Repositories[1], ShouldEqual, "z") + } }) Convey("Get images in a repository", func() { Print("\nGet images in a repository") // non-existent repository should fail - resp, err := resty.R().Get(baseURL + "/v2/repo/tags/list") + resp, err := resty.R().Get(baseURL + "/v2/repo1/tags/list") So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 404) So(resp.String(), ShouldNotBeEmpty) // after newly created upload should succeed - resp, err = resty.R().Post(baseURL + "/v2/repo/blobs/uploads/") + resp, err = resty.R().Post(baseURL + "/v2/repo1/blobs/uploads/") So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 202) - resp, err = resty.R().Get(baseURL + "/v2/repo/tags/list") + resp, err = resty.R().Get(baseURL + "/v2/repo1/tags/list") So(err, ShouldBeNil) - So(resp.StatusCode(), ShouldEqual, 200) - So(resp.String(), ShouldNotBeEmpty) + if !config.Compliance { + // stricter check for zot ci/cd + So(resp.StatusCode(), ShouldEqual, 200) + So(resp.String(), ShouldNotBeEmpty) + } }) Convey("Monolithic blob upload", func() { Print("\nMonolithic blob upload") - resp, err := resty.R().Post(baseURL + "/v2/repo/blobs/uploads/") + resp, err := resty.R().Post(baseURL + "/v2/repo2/blobs/uploads/") So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 202) - loc := resp.Header().Get("Location") + loc := Location(baseURL, resp, config) So(loc, ShouldNotBeEmpty) - resp, err = resty.R().Get(baseURL + loc) + resp, err = resty.R().Get(loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 204) - resp, err = resty.R().Get(baseURL + "/v2/repo/tags/list") + resp, err = resty.R().Get(baseURL + "/v2/repo2/tags/list") So(err, ShouldBeNil) - So(resp.StatusCode(), ShouldEqual, 200) - So(resp.String(), ShouldNotBeEmpty) + if !config.Compliance { + // stricter check for zot ci/cd + So(resp.StatusCode(), ShouldEqual, 200) + So(resp.String(), ShouldNotBeEmpty) + } // without a "?digest=<>" should fail content := []byte("this is a blob") digest := godigest.FromBytes(content) So(digest, ShouldNotBeNil) - resp, err = resty.R().Put(baseURL + loc) + resp, err = resty.R().Put(loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 400) // without the Content-Length should fail - resp, err = resty.R().SetQueryParam("digest", digest.String()).Put(baseURL + loc) + resp, err = resty.R().SetQueryParam("digest", digest.String()).Put(loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 400) // without any data to send, should fail resp, err = resty.R().SetQueryParam("digest", digest.String()). - SetHeader("Content-Type", "application/octet-stream").Put(baseURL + loc) + SetHeader("Content-Type", "application/octet-stream").Put(loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 400) // monolithic blob upload: success resp, err = resty.R().SetQueryParam("digest", digest.String()). - SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(baseURL + loc) + SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 201) - blobLoc := resp.Header().Get("Location") + blobLoc := Location(baseURL, resp, config) So(blobLoc, ShouldNotBeEmpty) So(resp.Header().Get("Content-Length"), ShouldEqual, "0") So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty) // upload reference should now be removed - resp, err = resty.R().Get(baseURL + loc) + resp, err = resty.R().Get(loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 404) // blob reference should be accessible - resp, err = resty.R().Get(baseURL + blobLoc) + resp, err = resty.R().Get(blobLoc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) }) Convey("Monolithic blob upload with multiple name components", func() { Print("\nMonolithic blob upload with multiple name components") - resp, err := resty.R().Post(baseURL + "/v2/repo1/repo2/repo3/blobs/uploads/") + resp, err := resty.R().Post(baseURL + "/v2/repo10/repo20/repo30/blobs/uploads/") So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 202) - loc := resp.Header().Get("Location") + loc := Location(baseURL, resp, config) So(loc, ShouldNotBeEmpty) - resp, err = resty.R().Get(baseURL + loc) + resp, err = resty.R().Get(loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 204) - resp, err = resty.R().Get(baseURL + "/v2/repo1/repo2/repo3/tags/list") + resp, err = resty.R().Get(baseURL + "/v2/repo10/repo20/repo30/tags/list") So(err, ShouldBeNil) - So(resp.StatusCode(), ShouldEqual, 200) - So(resp.String(), ShouldNotBeEmpty) + if !config.Compliance { + // stricter check for zot ci/cd + So(resp.StatusCode(), ShouldEqual, 200) + So(resp.String(), ShouldNotBeEmpty) + } // without a "?digest=<>" should fail content := []byte("this is a blob") digest := godigest.FromBytes(content) So(digest, ShouldNotBeNil) - resp, err = resty.R().Put(baseURL + loc) + resp, err = resty.R().Put(loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 400) // without the Content-Length should fail - resp, err = resty.R().SetQueryParam("digest", digest.String()).Put(baseURL + loc) + resp, err = resty.R().SetQueryParam("digest", digest.String()).Put(loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 400) // without any data to send, should fail resp, err = resty.R().SetQueryParam("digest", digest.String()). - SetHeader("Content-Type", "application/octet-stream").Put(baseURL + loc) + SetHeader("Content-Type", "application/octet-stream").Put(loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 400) // monolithic blob upload: success resp, err = resty.R().SetQueryParam("digest", digest.String()). - SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(baseURL + loc) + SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 201) - blobLoc := resp.Header().Get("Location") + blobLoc := Location(baseURL, resp, config) So(blobLoc, ShouldNotBeEmpty) So(resp.Header().Get("Content-Length"), ShouldEqual, "0") So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty) // upload reference should now be removed - resp, err = resty.R().Get(baseURL + loc) + resp, err = resty.R().Get(loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 404) // blob reference should be accessible - resp, err = resty.R().Get(baseURL + blobLoc) + resp, err = resty.R().Get(blobLoc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) }) Convey("Chunked blob upload", func() { Print("\nChunked blob upload") - resp, err := resty.R().Post(baseURL + "/v2/repo/blobs/uploads/") + resp, err := resty.R().Post(baseURL + "/v2/repo3/blobs/uploads/") So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 202) - loc := resp.Header().Get("Location") + loc := Location(baseURL, resp, config) So(loc, ShouldNotBeEmpty) var buf bytes.Buffer @@ -215,12 +241,12 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) { // write first chunk contentRange := fmt.Sprintf("%d-%d", 0, len(chunk1)) resp, err = resty.R().SetHeader("Content-Type", "application/octet-stream"). - SetHeader("Content-Range", contentRange).SetBody(chunk1).Patch(baseURL + loc) + SetHeader("Content-Range", contentRange).SetBody(chunk1).Patch(loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 202) // check progress - resp, err = resty.R().Get(baseURL + loc) + resp, err = resty.R().Get(loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 204) r := resp.Header().Get("Range") @@ -230,7 +256,7 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) { // write same chunk should fail contentRange = fmt.Sprintf("%d-%d", 0, len(chunk1)) resp, err = resty.R().SetHeader("Content-Type", "application/octet-stream"). - SetHeader("Content-Range", contentRange).SetBody(chunk1).Patch(baseURL + loc) + SetHeader("Content-Range", contentRange).SetBody(chunk1).Patch(loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 400) So(resp.String(), ShouldNotBeEmpty) @@ -247,31 +273,31 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) { contentRange = fmt.Sprintf("%d-%d", len(chunk1), len(buf.Bytes())) resp, err = resty.R().SetQueryParam("digest", digest.String()). SetHeader("Content-Range", contentRange). - SetHeader("Content-Type", "application/octet-stream").SetBody(chunk2).Put(baseURL + loc) + SetHeader("Content-Type", "application/octet-stream").SetBody(chunk2).Put(loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 201) - blobLoc := resp.Header().Get("Location") + blobLoc := Location(baseURL, resp, config) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 201) So(blobLoc, ShouldNotBeEmpty) So(resp.Header().Get("Content-Length"), ShouldEqual, "0") So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty) // upload reference should now be removed - resp, err = resty.R().Get(baseURL + loc) + resp, err = resty.R().Get(loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 404) // blob reference should be accessible - resp, err = resty.R().Get(baseURL + blobLoc) + resp, err = resty.R().Get(blobLoc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) }) Convey("Chunked blob upload with multiple name components", func() { Print("\nChunked blob upload with multiple name components") - resp, err := resty.R().Post(baseURL + "/v2/repo4/repo5/repo6/blobs/uploads/") + resp, err := resty.R().Post(baseURL + "/v2/repo40/repo50/repo60/blobs/uploads/") So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 202) - loc := resp.Header().Get("Location") + loc := Location(baseURL, resp, config) So(loc, ShouldNotBeEmpty) var buf bytes.Buffer @@ -283,12 +309,12 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) { // write first chunk contentRange := fmt.Sprintf("%d-%d", 0, len(chunk1)) resp, err = resty.R().SetHeader("Content-Type", "application/octet-stream"). - SetHeader("Content-Range", contentRange).SetBody(chunk1).Patch(baseURL + loc) + SetHeader("Content-Range", contentRange).SetBody(chunk1).Patch(loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 202) // check progress - resp, err = resty.R().Get(baseURL + loc) + resp, err = resty.R().Get(loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 204) r := resp.Header().Get("Range") @@ -298,7 +324,7 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) { // write same chunk should fail contentRange = fmt.Sprintf("%d-%d", 0, len(chunk1)) resp, err = resty.R().SetHeader("Content-Type", "application/octet-stream"). - SetHeader("Content-Range", contentRange).SetBody(chunk1).Patch(baseURL + loc) + SetHeader("Content-Range", contentRange).SetBody(chunk1).Patch(loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 400) So(resp.String(), ShouldNotBeEmpty) @@ -315,21 +341,21 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) { contentRange = fmt.Sprintf("%d-%d", len(chunk1), len(buf.Bytes())) resp, err = resty.R().SetQueryParam("digest", digest.String()). SetHeader("Content-Range", contentRange). - SetHeader("Content-Type", "application/octet-stream").SetBody(chunk2).Put(baseURL + loc) + SetHeader("Content-Type", "application/octet-stream").SetBody(chunk2).Put(loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 201) - blobLoc := resp.Header().Get("Location") + blobLoc := Location(baseURL, resp, config) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 201) So(blobLoc, ShouldNotBeEmpty) So(resp.Header().Get("Content-Length"), ShouldEqual, "0") So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty) // upload reference should now be removed - resp, err = resty.R().Get(baseURL + loc) + resp, err = resty.R().Get(loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 404) // blob reference should be accessible - resp, err = resty.R().Get(baseURL + blobLoc) + resp, err = resty.R().Get(blobLoc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) }) @@ -337,25 +363,25 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) { Convey("Create and delete uploads", func() { Print("\nCreate and delete uploads") // create a upload - resp, err := resty.R().Post(baseURL + "/v2/repo/blobs/uploads/") + resp, err := resty.R().Post(baseURL + "/v2/repo4/blobs/uploads/") So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 202) - loc := resp.Header().Get("Location") + loc := Location(baseURL, resp, config) So(loc, ShouldNotBeEmpty) // delete this upload - resp, err = resty.R().Delete(baseURL + loc) + resp, err = resty.R().Delete(loc) So(err, ShouldBeNil) - So(resp.StatusCode(), ShouldEqual, 200) + So(resp.StatusCode(), ShouldEqual, 204) }) Convey("Create and delete blobs", func() { Print("\nCreate and delete blobs") // create a upload - resp, err := resty.R().Post(baseURL + "/v2/repo/blobs/uploads/") + resp, err := resty.R().Post(baseURL + "/v2/repo5/blobs/uploads/") So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 202) - loc := resp.Header().Get("Location") + loc := Location(baseURL, resp, config) So(loc, ShouldNotBeEmpty) content := []byte("this is a blob") @@ -363,15 +389,15 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) { So(digest, ShouldNotBeNil) // monolithic blob upload resp, err = resty.R().SetQueryParam("digest", digest.String()). - SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(baseURL + loc) + SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 201) - blobLoc := resp.Header().Get("Location") + blobLoc := Location(baseURL, resp, config) So(blobLoc, ShouldNotBeEmpty) So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty) // delete this blob - resp, err = resty.R().Delete(baseURL + blobLoc) + resp, err = resty.R().Delete(blobLoc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 202) So(resp.Header().Get("Content-Length"), ShouldEqual, "0") @@ -380,21 +406,21 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) { Convey("Mount blobs", func() { Print("\nMount blobs from another repository") // create a upload - resp, err := resty.R().Post(baseURL + "/v2/repo/blobs/uploads/?digest=\"abc\"&&from=\"xyz\"") + resp, err := resty.R().Post(baseURL + "/v2/repo6/blobs/uploads/?digest=\"abc\"&&from=\"xyz\"") So(err, ShouldBeNil) - So(resp.StatusCode(), ShouldEqual, 405) + So(resp.StatusCode(), ShouldBeIn, []int{201, 202, 405}) }) Convey("Manifests", func() { Print("\nManifests") // create a blob/layer - resp, err := resty.R().Post(baseURL + "/v2/repo/blobs/uploads/") + resp, err := resty.R().Post(baseURL + "/v2/repo7/blobs/uploads/") So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 202) - loc := resp.Header().Get("Location") + loc := Location(baseURL, resp, config) So(loc, ShouldNotBeEmpty) - resp, err = resty.R().Get(baseURL + loc) + resp, err = resty.R().Get(loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 204) content := []byte("this is a blob") @@ -402,7 +428,7 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) { So(digest, ShouldNotBeNil) // monolithic blob upload: success resp, err = resty.R().SetQueryParam("digest", digest.String()). - SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(baseURL + loc) + SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 201) blobLoc := resp.Header().Get("Location") @@ -417,7 +443,7 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) { digest = godigest.FromBytes(content) So(digest, ShouldNotBeNil) resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json"). - SetBody(content).Put(baseURL + "/v2/repo/manifests/test:1.0") + SetBody(content).Put(baseURL + "/v2/repo7/manifests/test:1.0") So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 201) d := resp.Header().Get(api.DistContentDigestKey) @@ -425,48 +451,63 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) { So(d, ShouldEqual, digest.String()) // check/get by tag - resp, err = resty.R().Head(baseURL + "/v2/repo/manifests/test:1.0") + resp, err = resty.R().Head(baseURL + "/v2/repo7/manifests/test:1.0") So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) - resp, err = resty.R().Get(baseURL + "/v2/repo/manifests/test:1.0") + resp, err = resty.R().Get(baseURL + "/v2/repo7/manifests/test:1.0") So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) So(resp.Body(), ShouldNotBeEmpty) // check/get by reference - resp, err = resty.R().Head(baseURL + "/v2/repo/manifests/" + digest.String()) + resp, err = resty.R().Head(baseURL + "/v2/repo7/manifests/" + digest.String()) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) - resp, err = resty.R().Get(baseURL + "/v2/repo/manifests/" + digest.String()) + resp, err = resty.R().Get(baseURL + "/v2/repo7/manifests/" + digest.String()) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) So(resp.Body(), ShouldNotBeEmpty) // delete manifest - resp, err = resty.R().Delete(baseURL + "/v2/repo/manifests/test:1.0") + resp, err = resty.R().Delete(baseURL + "/v2/repo7/manifests/test:1.0") So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) // delete again should fail - resp, err = resty.R().Delete(baseURL + "/v2/repo/manifests/" + digest.String()) + resp, err = resty.R().Delete(baseURL + "/v2/repo7/manifests/" + digest.String()) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 404) // check/get by tag - resp, err = resty.R().Head(baseURL + "/v2/repo/manifests/test:1.0") + resp, err = resty.R().Head(baseURL + "/v2/repo7/manifests/test:1.0") So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 404) - resp, err = resty.R().Get(baseURL + "/v2/repo/manifests/test:1.0") + resp, err = resty.R().Get(baseURL + "/v2/repo7/manifests/test:1.0") So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 404) So(resp.Body(), ShouldNotBeEmpty) // check/get by reference - resp, err = resty.R().Head(baseURL + "/v2/repo/manifests/" + digest.String()) + resp, err = resty.R().Head(baseURL + "/v2/repo7/manifests/" + digest.String()) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 404) - resp, err = resty.R().Get(baseURL + "/v2/repo/manifests/" + digest.String()) + resp, err = resty.R().Get(baseURL + "/v2/repo7/manifests/" + digest.String()) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 404) So(resp.Body(), ShouldNotBeEmpty) }) + + // this is an additional test for repository names (alphanumeric) + Convey("Repository names", func() { + Print("\nRepository names") + // create a blob/layer + resp, err := resty.R().Post(baseURL + "/v2/repotest/blobs/uploads/") + So(err, ShouldBeNil) + So(resp.StatusCode(), ShouldEqual, 202) + resp, err = resty.R().Post(baseURL + "/v2/repotest123/blobs/uploads/") + So(err, ShouldBeNil) + So(resp.StatusCode(), ShouldEqual, 202) + resp, err = resty.R().Post(baseURL + "/v2/repoTest123/blobs/uploads/") + So(err, ShouldBeNil) + So(resp.StatusCode(), ShouldEqual, 202) + }) }) }