mirror of
https://github.com/project-zot/zot.git
synced 2024-12-30 22:34:13 -05:00
commit
915c994c6c
6 changed files with 152 additions and 81 deletions
28
README.md
28
README.md
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue