0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2024-12-30 22:34:13 -05:00

Merge pull request #52 from rchincha/compl

Compliance cleanup
This commit is contained in:
Serge Hallyn 2019-12-26 21:19:58 -06:00 committed by GitHub
commit 915c994c6c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 152 additions and 81 deletions

View file

@ -59,6 +59,34 @@ Examples of config files are available in [examples/](examples/) dir.
bin/zot compliance -H hostIP -P port [-V "all"] [--json] bin/zot compliance -H hostIP -P port [-V "all"] [--json]
``` ```
Compliance is important for the following reasons:
1. A standards-based client code can be implemented that can then interact with
compliant registries.
2. Customers benefit from the ability to move and locate their images across
compliant registries.
## Methodology
* A _positive_ compliance means the registry is compliant and meaningful work
can be accomplished when interacting with that registry.
* A _negative_ compliance means the registry is compliant, however, it only
returns errors that are compliant and no meaningful work can be performed when
interacting with that registry.
The focus of compliance tests is _positive_ compliance.
## Compliance Reports
Registry | Notes
---------|------
zot | <ul><li>[Mount Blob](https://github.com/opencontainers/distribution-spec/blob/master/spec.md#mount-blob) is not implemented contingent upon [Issue #51](https://github.com/anuvu/zot/issues/51)</li></ul>
docker | <ul><li>[Patch Blob Upload](https://github.com/opencontainers/distribution-spec/blob/master/spec.md#patch-blob-upload) is not [implemented](https://github.com/docker/distribution/blob/master/registry/handlers/blobupload.go#L136)</li><li>Repository names cannot be mixed case due to [Issue #2771](https://github.com/docker/distribution/issues/2771)</li></ul>
quay | TBD
# Ecosystem # Ecosystem
## skopeo ## skopeo

View file

@ -42,6 +42,7 @@ func (c *Controller) Run() error {
handlers.PrintRecoveryStack(false))) handlers.PrintRecoveryStack(false)))
c.Router = engine c.Router = engine
c.Router.UseEncodedPath()
_ = NewRouteHandler(c) _ = NewRouteHandler(c)
c.ImageStore = storage.NewImageStore(c.Config.Storage.RootDirectory, c.Log) c.ImageStore = storage.NewImageStore(c.Config.Storage.RootDirectory, c.Log)

View file

@ -6,7 +6,7 @@ import "regexp"
var ( var (
// alphaNumericRegexp defines the alpha numeric atom, typically a // alphaNumericRegexp defines the alpha numeric atom, typically a
// component of names. This only allows lower case characters and digits. // 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 // separatorRegexp defines the separators allowed to be embedded in name
// components. This allow one period, one or two underscore and multiple // components. This allow one period, one or two underscore and multiple

View file

@ -838,7 +838,7 @@ func (rh *RouteHandler) DeleteBlobUpload(w http.ResponseWriter, r *http.Request)
return return
} }
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusNoContent)
} }
type RepositoryList struct { type RepositoryList struct {

View file

@ -5,8 +5,9 @@ type Config struct {
Port string Port string
Version string Version string
OutputJSON bool OutputJSON bool
Compliance bool
} }
func NewConfig() *Config { func NewConfig() *Config {
return &Config{} return &Config{Compliance: true}
} }

View file

@ -19,6 +19,20 @@ import (
"gopkg.in/resty.v1" "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) { func CheckWorkflows(t *testing.T, config *compliance.Config) {
if config == nil || config.Address == "" || config.Port == "" { if config == nil || config.Address == "" || config.Port == "" {
panic("insufficient config") panic("insufficient config")
@ -70,140 +84,152 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) {
So(resp.StatusCode(), ShouldEqual, 200) So(resp.StatusCode(), ShouldEqual, 200)
So(resp.String(), ShouldNotBeEmpty) So(resp.String(), ShouldNotBeEmpty)
r := resp.Result().(*api.RepositoryList) r := resp.Result().(*api.RepositoryList)
So(len(r.Repositories), ShouldBeGreaterThan, 0) if !config.Compliance {
So(r.Repositories[0], ShouldEqual, "a/b/c/d") // stricter check for zot ci/cd
So(r.Repositories[1], ShouldEqual, "z") 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() { Convey("Get images in a repository", func() {
Print("\nGet images in a repository") Print("\nGet images in a repository")
// non-existent repository should fail // 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 404) So(resp.StatusCode(), ShouldEqual, 404)
So(resp.String(), ShouldNotBeEmpty) So(resp.String(), ShouldNotBeEmpty)
// after newly created upload should succeed // 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 202) 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200) if !config.Compliance {
So(resp.String(), ShouldNotBeEmpty) // stricter check for zot ci/cd
So(resp.StatusCode(), ShouldEqual, 200)
So(resp.String(), ShouldNotBeEmpty)
}
}) })
Convey("Monolithic blob upload", func() { Convey("Monolithic blob upload", func() {
Print("\nMonolithic blob upload") 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 202) So(resp.StatusCode(), ShouldEqual, 202)
loc := resp.Header().Get("Location") loc := Location(baseURL, resp, config)
So(loc, ShouldNotBeEmpty) So(loc, ShouldNotBeEmpty)
resp, err = resty.R().Get(baseURL + loc) resp, err = resty.R().Get(loc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 204) 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200) if !config.Compliance {
So(resp.String(), ShouldNotBeEmpty) // stricter check for zot ci/cd
So(resp.StatusCode(), ShouldEqual, 200)
So(resp.String(), ShouldNotBeEmpty)
}
// without a "?digest=<>" should fail // without a "?digest=<>" should fail
content := []byte("this is a blob") content := []byte("this is a blob")
digest := godigest.FromBytes(content) digest := godigest.FromBytes(content)
So(digest, ShouldNotBeNil) So(digest, ShouldNotBeNil)
resp, err = resty.R().Put(baseURL + loc) resp, err = resty.R().Put(loc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 400) So(resp.StatusCode(), ShouldEqual, 400)
// without the Content-Length should fail // 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 400) So(resp.StatusCode(), ShouldEqual, 400)
// without any data to send, should fail // without any data to send, should fail
resp, err = resty.R().SetQueryParam("digest", digest.String()). 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 400) So(resp.StatusCode(), ShouldEqual, 400)
// monolithic blob upload: success // monolithic blob upload: success
resp, err = resty.R().SetQueryParam("digest", digest.String()). 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 201) So(resp.StatusCode(), ShouldEqual, 201)
blobLoc := resp.Header().Get("Location") blobLoc := Location(baseURL, resp, config)
So(blobLoc, ShouldNotBeEmpty) So(blobLoc, ShouldNotBeEmpty)
So(resp.Header().Get("Content-Length"), ShouldEqual, "0") So(resp.Header().Get("Content-Length"), ShouldEqual, "0")
So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty) So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty)
// upload reference should now be removed // upload reference should now be removed
resp, err = resty.R().Get(baseURL + loc) resp, err = resty.R().Get(loc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 404) So(resp.StatusCode(), ShouldEqual, 404)
// blob reference should be accessible // blob reference should be accessible
resp, err = resty.R().Get(baseURL + blobLoc) resp, err = resty.R().Get(blobLoc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200) So(resp.StatusCode(), ShouldEqual, 200)
}) })
Convey("Monolithic blob upload with multiple name components", func() { Convey("Monolithic blob upload with multiple name components", func() {
Print("\nMonolithic blob upload with multiple name components") 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 202) So(resp.StatusCode(), ShouldEqual, 202)
loc := resp.Header().Get("Location") loc := Location(baseURL, resp, config)
So(loc, ShouldNotBeEmpty) So(loc, ShouldNotBeEmpty)
resp, err = resty.R().Get(baseURL + loc) resp, err = resty.R().Get(loc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 204) 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200) if !config.Compliance {
So(resp.String(), ShouldNotBeEmpty) // stricter check for zot ci/cd
So(resp.StatusCode(), ShouldEqual, 200)
So(resp.String(), ShouldNotBeEmpty)
}
// without a "?digest=<>" should fail // without a "?digest=<>" should fail
content := []byte("this is a blob") content := []byte("this is a blob")
digest := godigest.FromBytes(content) digest := godigest.FromBytes(content)
So(digest, ShouldNotBeNil) So(digest, ShouldNotBeNil)
resp, err = resty.R().Put(baseURL + loc) resp, err = resty.R().Put(loc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 400) So(resp.StatusCode(), ShouldEqual, 400)
// without the Content-Length should fail // 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 400) So(resp.StatusCode(), ShouldEqual, 400)
// without any data to send, should fail // without any data to send, should fail
resp, err = resty.R().SetQueryParam("digest", digest.String()). 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 400) So(resp.StatusCode(), ShouldEqual, 400)
// monolithic blob upload: success // monolithic blob upload: success
resp, err = resty.R().SetQueryParam("digest", digest.String()). 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 201) So(resp.StatusCode(), ShouldEqual, 201)
blobLoc := resp.Header().Get("Location") blobLoc := Location(baseURL, resp, config)
So(blobLoc, ShouldNotBeEmpty) So(blobLoc, ShouldNotBeEmpty)
So(resp.Header().Get("Content-Length"), ShouldEqual, "0") So(resp.Header().Get("Content-Length"), ShouldEqual, "0")
So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty) So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty)
// upload reference should now be removed // upload reference should now be removed
resp, err = resty.R().Get(baseURL + loc) resp, err = resty.R().Get(loc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 404) So(resp.StatusCode(), ShouldEqual, 404)
// blob reference should be accessible // blob reference should be accessible
resp, err = resty.R().Get(baseURL + blobLoc) resp, err = resty.R().Get(blobLoc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200) So(resp.StatusCode(), ShouldEqual, 200)
}) })
Convey("Chunked blob upload", func() { Convey("Chunked blob upload", func() {
Print("\nChunked blob upload") 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 202) So(resp.StatusCode(), ShouldEqual, 202)
loc := resp.Header().Get("Location") loc := Location(baseURL, resp, config)
So(loc, ShouldNotBeEmpty) So(loc, ShouldNotBeEmpty)
var buf bytes.Buffer var buf bytes.Buffer
@ -215,12 +241,12 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) {
// write first chunk // write first chunk
contentRange := fmt.Sprintf("%d-%d", 0, len(chunk1)) contentRange := fmt.Sprintf("%d-%d", 0, len(chunk1))
resp, err = resty.R().SetHeader("Content-Type", "application/octet-stream"). 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 202) So(resp.StatusCode(), ShouldEqual, 202)
// check progress // check progress
resp, err = resty.R().Get(baseURL + loc) resp, err = resty.R().Get(loc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 204) So(resp.StatusCode(), ShouldEqual, 204)
r := resp.Header().Get("Range") r := resp.Header().Get("Range")
@ -230,7 +256,7 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) {
// write same chunk should fail // write same chunk should fail
contentRange = fmt.Sprintf("%d-%d", 0, len(chunk1)) contentRange = fmt.Sprintf("%d-%d", 0, len(chunk1))
resp, err = resty.R().SetHeader("Content-Type", "application/octet-stream"). 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 400) So(resp.StatusCode(), ShouldEqual, 400)
So(resp.String(), ShouldNotBeEmpty) 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())) contentRange = fmt.Sprintf("%d-%d", len(chunk1), len(buf.Bytes()))
resp, err = resty.R().SetQueryParam("digest", digest.String()). resp, err = resty.R().SetQueryParam("digest", digest.String()).
SetHeader("Content-Range", contentRange). 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 201) So(resp.StatusCode(), ShouldEqual, 201)
blobLoc := resp.Header().Get("Location") blobLoc := Location(baseURL, resp, config)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 201) So(resp.StatusCode(), ShouldEqual, 201)
So(blobLoc, ShouldNotBeEmpty) So(blobLoc, ShouldNotBeEmpty)
So(resp.Header().Get("Content-Length"), ShouldEqual, "0") So(resp.Header().Get("Content-Length"), ShouldEqual, "0")
So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty) So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty)
// upload reference should now be removed // upload reference should now be removed
resp, err = resty.R().Get(baseURL + loc) resp, err = resty.R().Get(loc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 404) So(resp.StatusCode(), ShouldEqual, 404)
// blob reference should be accessible // blob reference should be accessible
resp, err = resty.R().Get(baseURL + blobLoc) resp, err = resty.R().Get(blobLoc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200) So(resp.StatusCode(), ShouldEqual, 200)
}) })
Convey("Chunked blob upload with multiple name components", func() { Convey("Chunked blob upload with multiple name components", func() {
Print("\nChunked blob upload with multiple name components") 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 202) So(resp.StatusCode(), ShouldEqual, 202)
loc := resp.Header().Get("Location") loc := Location(baseURL, resp, config)
So(loc, ShouldNotBeEmpty) So(loc, ShouldNotBeEmpty)
var buf bytes.Buffer var buf bytes.Buffer
@ -283,12 +309,12 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) {
// write first chunk // write first chunk
contentRange := fmt.Sprintf("%d-%d", 0, len(chunk1)) contentRange := fmt.Sprintf("%d-%d", 0, len(chunk1))
resp, err = resty.R().SetHeader("Content-Type", "application/octet-stream"). 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 202) So(resp.StatusCode(), ShouldEqual, 202)
// check progress // check progress
resp, err = resty.R().Get(baseURL + loc) resp, err = resty.R().Get(loc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 204) So(resp.StatusCode(), ShouldEqual, 204)
r := resp.Header().Get("Range") r := resp.Header().Get("Range")
@ -298,7 +324,7 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) {
// write same chunk should fail // write same chunk should fail
contentRange = fmt.Sprintf("%d-%d", 0, len(chunk1)) contentRange = fmt.Sprintf("%d-%d", 0, len(chunk1))
resp, err = resty.R().SetHeader("Content-Type", "application/octet-stream"). 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 400) So(resp.StatusCode(), ShouldEqual, 400)
So(resp.String(), ShouldNotBeEmpty) 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())) contentRange = fmt.Sprintf("%d-%d", len(chunk1), len(buf.Bytes()))
resp, err = resty.R().SetQueryParam("digest", digest.String()). resp, err = resty.R().SetQueryParam("digest", digest.String()).
SetHeader("Content-Range", contentRange). 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 201) So(resp.StatusCode(), ShouldEqual, 201)
blobLoc := resp.Header().Get("Location") blobLoc := Location(baseURL, resp, config)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 201) So(resp.StatusCode(), ShouldEqual, 201)
So(blobLoc, ShouldNotBeEmpty) So(blobLoc, ShouldNotBeEmpty)
So(resp.Header().Get("Content-Length"), ShouldEqual, "0") So(resp.Header().Get("Content-Length"), ShouldEqual, "0")
So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty) So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty)
// upload reference should now be removed // upload reference should now be removed
resp, err = resty.R().Get(baseURL + loc) resp, err = resty.R().Get(loc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 404) So(resp.StatusCode(), ShouldEqual, 404)
// blob reference should be accessible // blob reference should be accessible
resp, err = resty.R().Get(baseURL + blobLoc) resp, err = resty.R().Get(blobLoc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200) So(resp.StatusCode(), ShouldEqual, 200)
}) })
@ -337,25 +363,25 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) {
Convey("Create and delete uploads", func() { Convey("Create and delete uploads", func() {
Print("\nCreate and delete uploads") Print("\nCreate and delete uploads")
// create a upload // 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 202) So(resp.StatusCode(), ShouldEqual, 202)
loc := resp.Header().Get("Location") loc := Location(baseURL, resp, config)
So(loc, ShouldNotBeEmpty) So(loc, ShouldNotBeEmpty)
// delete this upload // delete this upload
resp, err = resty.R().Delete(baseURL + loc) resp, err = resty.R().Delete(loc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200) So(resp.StatusCode(), ShouldEqual, 204)
}) })
Convey("Create and delete blobs", func() { Convey("Create and delete blobs", func() {
Print("\nCreate and delete blobs") Print("\nCreate and delete blobs")
// create a upload // 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 202) So(resp.StatusCode(), ShouldEqual, 202)
loc := resp.Header().Get("Location") loc := Location(baseURL, resp, config)
So(loc, ShouldNotBeEmpty) So(loc, ShouldNotBeEmpty)
content := []byte("this is a blob") content := []byte("this is a blob")
@ -363,15 +389,15 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) {
So(digest, ShouldNotBeNil) So(digest, ShouldNotBeNil)
// monolithic blob upload // monolithic blob upload
resp, err = resty.R().SetQueryParam("digest", digest.String()). 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 201) So(resp.StatusCode(), ShouldEqual, 201)
blobLoc := resp.Header().Get("Location") blobLoc := Location(baseURL, resp, config)
So(blobLoc, ShouldNotBeEmpty) So(blobLoc, ShouldNotBeEmpty)
So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty) So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty)
// delete this blob // delete this blob
resp, err = resty.R().Delete(baseURL + blobLoc) resp, err = resty.R().Delete(blobLoc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 202) So(resp.StatusCode(), ShouldEqual, 202)
So(resp.Header().Get("Content-Length"), ShouldEqual, "0") So(resp.Header().Get("Content-Length"), ShouldEqual, "0")
@ -380,21 +406,21 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) {
Convey("Mount blobs", func() { Convey("Mount blobs", func() {
Print("\nMount blobs from another repository") Print("\nMount blobs from another repository")
// create a upload // 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 405) So(resp.StatusCode(), ShouldBeIn, []int{201, 202, 405})
}) })
Convey("Manifests", func() { Convey("Manifests", func() {
Print("\nManifests") Print("\nManifests")
// create a blob/layer // 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 202) So(resp.StatusCode(), ShouldEqual, 202)
loc := resp.Header().Get("Location") loc := Location(baseURL, resp, config)
So(loc, ShouldNotBeEmpty) So(loc, ShouldNotBeEmpty)
resp, err = resty.R().Get(baseURL + loc) resp, err = resty.R().Get(loc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 204) So(resp.StatusCode(), ShouldEqual, 204)
content := []byte("this is a blob") content := []byte("this is a blob")
@ -402,7 +428,7 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) {
So(digest, ShouldNotBeNil) So(digest, ShouldNotBeNil)
// monolithic blob upload: success // monolithic blob upload: success
resp, err = resty.R().SetQueryParam("digest", digest.String()). 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 201) So(resp.StatusCode(), ShouldEqual, 201)
blobLoc := resp.Header().Get("Location") blobLoc := resp.Header().Get("Location")
@ -417,7 +443,7 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) {
digest = godigest.FromBytes(content) digest = godigest.FromBytes(content)
So(digest, ShouldNotBeNil) So(digest, ShouldNotBeNil)
resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json"). 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 201) So(resp.StatusCode(), ShouldEqual, 201)
d := resp.Header().Get(api.DistContentDigestKey) d := resp.Header().Get(api.DistContentDigestKey)
@ -425,48 +451,63 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) {
So(d, ShouldEqual, digest.String()) So(d, ShouldEqual, digest.String())
// check/get by tag // 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200) 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200) So(resp.StatusCode(), ShouldEqual, 200)
So(resp.Body(), ShouldNotBeEmpty) So(resp.Body(), ShouldNotBeEmpty)
// check/get by reference // 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200) 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200) So(resp.StatusCode(), ShouldEqual, 200)
So(resp.Body(), ShouldNotBeEmpty) So(resp.Body(), ShouldNotBeEmpty)
// delete manifest // 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200) So(resp.StatusCode(), ShouldEqual, 200)
// delete again should fail // 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 404) So(resp.StatusCode(), ShouldEqual, 404)
// check/get by tag // 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 404) 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 404) So(resp.StatusCode(), ShouldEqual, 404)
So(resp.Body(), ShouldNotBeEmpty) So(resp.Body(), ShouldNotBeEmpty)
// check/get by reference // 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 404) 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 404) So(resp.StatusCode(), ShouldEqual, 404)
So(resp.Body(), ShouldNotBeEmpty) 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)
})
}) })
} }