mirror of
https://github.com/project-zot/zot.git
synced 2024-12-23 22:27:35 -05:00
c8563d8672
"mount blob" was the only feature we didn't handle. We don't fully support it yet because we don't have access control support, but at least handle it and return something sane to remain compliant.
528 lines
18 KiB
Go
528 lines
18 KiB
Go
//nolint (dupl)
|
|
package v1_0_0
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/anuvu/zot/pkg/api"
|
|
"github.com/anuvu/zot/pkg/compliance"
|
|
godigest "github.com/opencontainers/go-digest"
|
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
. "github.com/smartystreets/goconvey/convey"
|
|
"github.com/smartystreets/goconvey/convey/reporting"
|
|
"gopkg.in/resty.v1"
|
|
)
|
|
|
|
func CheckWorkflows(t *testing.T, config *compliance.Config) {
|
|
if config == nil || config.Address == "" || config.Port == "" {
|
|
panic("insufficient config")
|
|
}
|
|
|
|
if config.OutputJSON {
|
|
outputJSONEnter()
|
|
defer outputJSONExit()
|
|
}
|
|
|
|
baseURL := fmt.Sprintf("http://%s:%s", config.Address, config.Port)
|
|
|
|
fmt.Println("------------------------------")
|
|
fmt.Println("Checking for v1.0.0 compliance")
|
|
fmt.Println("------------------------------")
|
|
|
|
Convey("Make API calls to the controller", t, func(c C) {
|
|
Convey("Check version", func() {
|
|
Print("\nCheck version")
|
|
resp, err := resty.R().Get(baseURL + "/v2/")
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
})
|
|
|
|
Convey("Get repository catalog", func() {
|
|
Print("\nGet repository catalog")
|
|
resp, err := resty.R().Get(baseURL + "/v2/_catalog")
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
So(resp.String(), ShouldNotBeEmpty)
|
|
So(resp.Header().Get("Content-Type"), ShouldEqual, api.DefaultMediaType)
|
|
var repoList api.RepositoryList
|
|
err = json.Unmarshal(resp.Body(), &repoList)
|
|
So(err, ShouldBeNil)
|
|
So(len(repoList.Repositories), ShouldEqual, 0)
|
|
|
|
// after newly created upload should succeed
|
|
resp, err = resty.R().Post(baseURL + "/v2/z/blobs/uploads/")
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 202)
|
|
|
|
// after newly created upload should succeed
|
|
resp, err = resty.R().Post(baseURL + "/v2/a/b/c/d/blobs/uploads/")
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 202)
|
|
|
|
resp, err = resty.R().SetResult(&api.RepositoryList{}).Get(baseURL + "/v2/_catalog")
|
|
So(err, ShouldBeNil)
|
|
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")
|
|
})
|
|
|
|
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")
|
|
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/")
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 202)
|
|
|
|
resp, err = resty.R().Get(baseURL + "/v2/repo/tags/list")
|
|
So(err, ShouldBeNil)
|
|
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/")
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 202)
|
|
loc := resp.Header().Get("Location")
|
|
So(loc, ShouldNotBeEmpty)
|
|
|
|
resp, err = resty.R().Get(baseURL + loc)
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 204)
|
|
|
|
resp, err = resty.R().Get(baseURL + "/v2/repo/tags/list")
|
|
So(err, ShouldBeNil)
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 201)
|
|
blobLoc := resp.Header().Get("Location")
|
|
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)
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
// blob reference should be accessible
|
|
resp, err = resty.R().Get(baseURL + 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/")
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 202)
|
|
loc := resp.Header().Get("Location")
|
|
So(loc, ShouldNotBeEmpty)
|
|
|
|
resp, err = resty.R().Get(baseURL + loc)
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 204)
|
|
|
|
resp, err = resty.R().Get(baseURL + "/v2/repo1/repo2/repo3/tags/list")
|
|
So(err, ShouldBeNil)
|
|
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)
|
|
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)
|
|
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)
|
|
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)
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 201)
|
|
blobLoc := resp.Header().Get("Location")
|
|
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)
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
// blob reference should be accessible
|
|
resp, err = resty.R().Get(baseURL + 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/")
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 202)
|
|
loc := resp.Header().Get("Location")
|
|
So(loc, ShouldNotBeEmpty)
|
|
|
|
var buf bytes.Buffer
|
|
chunk1 := []byte("this is the first chunk")
|
|
n, err := buf.Write(chunk1)
|
|
So(n, ShouldEqual, len(chunk1))
|
|
So(err, ShouldBeNil)
|
|
|
|
// 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)
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 202)
|
|
|
|
// check progress
|
|
resp, err = resty.R().Get(baseURL + loc)
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 204)
|
|
r := resp.Header().Get("Range")
|
|
So(r, ShouldNotBeEmpty)
|
|
So(r, ShouldEqual, "bytes="+contentRange)
|
|
|
|
// 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)
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 400)
|
|
So(resp.String(), ShouldNotBeEmpty)
|
|
|
|
chunk2 := []byte("this is the second chunk")
|
|
n, err = buf.Write(chunk2)
|
|
So(n, ShouldEqual, len(chunk2))
|
|
So(err, ShouldBeNil)
|
|
|
|
digest := godigest.FromBytes(buf.Bytes())
|
|
So(digest, ShouldNotBeNil)
|
|
|
|
// write final chunk
|
|
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)
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 201)
|
|
blobLoc := resp.Header().Get("Location")
|
|
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)
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
// blob reference should be accessible
|
|
resp, err = resty.R().Get(baseURL + 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/")
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 202)
|
|
loc := resp.Header().Get("Location")
|
|
So(loc, ShouldNotBeEmpty)
|
|
|
|
var buf bytes.Buffer
|
|
chunk1 := []byte("this is the first chunk")
|
|
n, err := buf.Write(chunk1)
|
|
So(n, ShouldEqual, len(chunk1))
|
|
So(err, ShouldBeNil)
|
|
|
|
// 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)
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 202)
|
|
|
|
// check progress
|
|
resp, err = resty.R().Get(baseURL + loc)
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 204)
|
|
r := resp.Header().Get("Range")
|
|
So(r, ShouldNotBeEmpty)
|
|
So(r, ShouldEqual, "bytes="+contentRange)
|
|
|
|
// 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)
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 400)
|
|
So(resp.String(), ShouldNotBeEmpty)
|
|
|
|
chunk2 := []byte("this is the second chunk")
|
|
n, err = buf.Write(chunk2)
|
|
So(n, ShouldEqual, len(chunk2))
|
|
So(err, ShouldBeNil)
|
|
|
|
digest := godigest.FromBytes(buf.Bytes())
|
|
So(digest, ShouldNotBeNil)
|
|
|
|
// write final chunk
|
|
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)
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 201)
|
|
blobLoc := resp.Header().Get("Location")
|
|
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)
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
// blob reference should be accessible
|
|
resp, err = resty.R().Get(baseURL + blobLoc)
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
})
|
|
|
|
Convey("Create and delete uploads", func() {
|
|
Print("\nCreate and delete uploads")
|
|
// create a upload
|
|
resp, err := resty.R().Post(baseURL + "/v2/repo/blobs/uploads/")
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 202)
|
|
loc := resp.Header().Get("Location")
|
|
So(loc, ShouldNotBeEmpty)
|
|
|
|
// delete this upload
|
|
resp, err = resty.R().Delete(baseURL + loc)
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
})
|
|
|
|
Convey("Create and delete blobs", func() {
|
|
Print("\nCreate and delete blobs")
|
|
// create a upload
|
|
resp, err := resty.R().Post(baseURL + "/v2/repo/blobs/uploads/")
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 202)
|
|
loc := resp.Header().Get("Location")
|
|
So(loc, ShouldNotBeEmpty)
|
|
|
|
content := []byte("this is a blob")
|
|
digest := godigest.FromBytes(content)
|
|
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)
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 201)
|
|
blobLoc := resp.Header().Get("Location")
|
|
So(blobLoc, ShouldNotBeEmpty)
|
|
So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty)
|
|
|
|
// delete this blob
|
|
resp, err = resty.R().Delete(baseURL + blobLoc)
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 202)
|
|
So(resp.Header().Get("Content-Length"), ShouldEqual, "0")
|
|
})
|
|
|
|
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\"")
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 405)
|
|
})
|
|
|
|
Convey("Manifests", func() {
|
|
Print("\nManifests")
|
|
// create a blob/layer
|
|
resp, err := resty.R().Post(baseURL + "/v2/repo/blobs/uploads/")
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 202)
|
|
loc := resp.Header().Get("Location")
|
|
So(loc, ShouldNotBeEmpty)
|
|
|
|
resp, err = resty.R().Get(baseURL + loc)
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 204)
|
|
content := []byte("this is a blob")
|
|
digest := godigest.FromBytes(content)
|
|
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)
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 201)
|
|
blobLoc := resp.Header().Get("Location")
|
|
So(blobLoc, ShouldNotBeEmpty)
|
|
So(resp.Header().Get("Content-Length"), ShouldEqual, "0")
|
|
So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty)
|
|
|
|
// create a manifest
|
|
m := ispec.Manifest{Layers: []ispec.Descriptor{{Digest: digest}}}
|
|
content, err = json.Marshal(m)
|
|
So(err, ShouldBeNil)
|
|
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")
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 201)
|
|
d := resp.Header().Get(api.DistContentDigestKey)
|
|
So(d, ShouldNotBeEmpty)
|
|
So(d, ShouldEqual, digest.String())
|
|
|
|
// check/get by tag
|
|
resp, err = resty.R().Head(baseURL + "/v2/repo/manifests/test:1.0")
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
resp, err = resty.R().Get(baseURL + "/v2/repo/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())
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
resp, err = resty.R().Get(baseURL + "/v2/repo/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")
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
// delete again should fail
|
|
resp, err = resty.R().Delete(baseURL + "/v2/repo/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")
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
resp, err = resty.R().Get(baseURL + "/v2/repo/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())
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
resp, err = resty.R().Get(baseURL + "/v2/repo/manifests/" + digest.String())
|
|
So(err, ShouldBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
So(resp.Body(), ShouldNotBeEmpty)
|
|
})
|
|
})
|
|
}
|
|
|
|
var (
|
|
old *os.File
|
|
r *os.File
|
|
w *os.File
|
|
outC chan string
|
|
)
|
|
|
|
func outputJSONEnter() {
|
|
// this env var instructs goconvey to output results to JSON (stdout)
|
|
os.Setenv("GOCONVEY_REPORTER", "json")
|
|
|
|
// stdout capture copied from: https://stackoverflow.com/a/29339052
|
|
old = os.Stdout
|
|
// keep backup of the real stdout
|
|
r, w, _ = os.Pipe()
|
|
outC = make(chan string)
|
|
os.Stdout = w
|
|
|
|
// copy the output in a separate goroutine so printing can't block indefinitely
|
|
go func() {
|
|
var buf bytes.Buffer
|
|
io.Copy(&buf, r)
|
|
outC <- buf.String()
|
|
}()
|
|
}
|
|
|
|
func outputJSONExit() {
|
|
// back to normal state
|
|
w.Close()
|
|
os.Stdout = old // restoring the real stdout
|
|
out := <-outC
|
|
|
|
// The output of JSON is combined with regular output, so we look for the
|
|
// first occurrence of the "{" character and take everything after that
|
|
rawJSON := "[{" + strings.Join(strings.Split(out, "{")[1:], "{")
|
|
rawJSON = strings.Replace(rawJSON, reporting.OpenJson, "", 1)
|
|
rawJSON = strings.Replace(rawJSON, reporting.CloseJson, "", 1)
|
|
tmp := strings.Split(rawJSON, ",")
|
|
rawJSON = strings.Join(tmp[0:len(tmp)-1], ",") + "]"
|
|
|
|
rawJSONMinified := validateMinifyRawJSON(rawJSON)
|
|
fmt.Println(rawJSONMinified)
|
|
}
|
|
|
|
func validateMinifyRawJSON(rawJSON string) string {
|
|
var j interface{}
|
|
err := json.Unmarshal([]byte(rawJSON), &j)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
rawJSONBytesMinified, err := json.Marshal(j)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return string(rawJSONBytesMinified)
|
|
}
|