2020-05-11 17:13:24 -05:00
|
|
|
// nolint: dupl
|
|
|
|
package v1_0_0 // nolint:stylecheck,golint
|
2019-06-20 18:36:40 -05:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2019-12-13 15:57:51 -05:00
|
|
|
"io"
|
|
|
|
"os"
|
2021-04-05 19:40:33 -05:00
|
|
|
"path"
|
2019-12-13 15:57:51 -05:00
|
|
|
"strings"
|
2019-06-20 18:36:40 -05:00
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/anuvu/zot/pkg/api"
|
2019-10-09 13:50:10 -05:00
|
|
|
"github.com/anuvu/zot/pkg/compliance"
|
2019-06-20 18:36:40 -05:00
|
|
|
godigest "github.com/opencontainers/go-digest"
|
|
|
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
2020-05-11 17:13:24 -05:00
|
|
|
. "github.com/smartystreets/goconvey/convey" // nolint:golint,stylecheck
|
2019-12-13 15:57:51 -05:00
|
|
|
"github.com/smartystreets/goconvey/convey/reporting"
|
2019-06-20 18:36:40 -05:00
|
|
|
"gopkg.in/resty.v1"
|
|
|
|
)
|
|
|
|
|
2020-01-04 22:05:39 -05:00
|
|
|
func Location(baseURL string, resp *resty.Response) string {
|
2019-12-24 01:32:52 -05:00
|
|
|
// 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")
|
2020-01-04 22:05:39 -05:00
|
|
|
if loc[0] == '/' {
|
|
|
|
return baseURL + loc
|
2019-12-24 01:32:52 -05:00
|
|
|
}
|
|
|
|
|
2020-05-11 17:13:24 -05:00
|
|
|
return loc
|
2019-12-24 01:32:52 -05:00
|
|
|
}
|
|
|
|
|
2019-10-09 13:50:10 -05:00
|
|
|
func CheckWorkflows(t *testing.T, config *compliance.Config) {
|
|
|
|
if config == nil || config.Address == "" || config.Port == "" {
|
|
|
|
panic("insufficient config")
|
|
|
|
}
|
2019-12-13 15:57:51 -05:00
|
|
|
|
|
|
|
if config.OutputJSON {
|
|
|
|
outputJSONEnter()
|
2020-05-11 17:13:24 -05:00
|
|
|
|
2019-12-13 15:57:51 -05:00
|
|
|
defer outputJSONExit()
|
|
|
|
}
|
|
|
|
|
2019-10-09 13:50:10 -05:00
|
|
|
baseURL := fmt.Sprintf("http://%s:%s", config.Address, config.Port)
|
|
|
|
|
2021-04-05 19:40:33 -05:00
|
|
|
storageInfo := config.StorageInfo
|
|
|
|
|
2019-10-09 13:50:10 -05:00
|
|
|
fmt.Println("------------------------------")
|
|
|
|
fmt.Println("Checking for v1.0.0 compliance")
|
|
|
|
fmt.Println("------------------------------")
|
2019-06-20 18:36:40 -05:00
|
|
|
|
|
|
|
Convey("Make API calls to the controller", t, func(c C) {
|
2019-10-09 13:50:10 -05:00
|
|
|
Convey("Check version", func() {
|
|
|
|
Print("\nCheck version")
|
|
|
|
resp, err := resty.R().Get(baseURL + "/v2/")
|
2019-06-20 18:36:40 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
|
|
})
|
|
|
|
|
|
|
|
Convey("Get repository catalog", func() {
|
2019-10-09 13:50:10 -05:00
|
|
|
Print("\nGet repository catalog")
|
|
|
|
resp, err := resty.R().Get(baseURL + "/v2/_catalog")
|
2019-06-20 18:36:40 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
|
|
So(resp.String(), ShouldNotBeEmpty)
|
2019-10-09 13:50:10 -05:00
|
|
|
So(resp.Header().Get("Content-Type"), ShouldEqual, api.DefaultMediaType)
|
2019-06-20 18:36:40 -05:00
|
|
|
var repoList api.RepositoryList
|
|
|
|
err = json.Unmarshal(resp.Body(), &repoList)
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(len(repoList.Repositories), ShouldEqual, 0)
|
2019-08-29 15:39:32 -05:00
|
|
|
|
|
|
|
// after newly created upload should succeed
|
2019-10-09 13:50:10 -05:00
|
|
|
resp, err = resty.R().Post(baseURL + "/v2/z/blobs/uploads/")
|
2019-08-29 15:39:32 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 202)
|
|
|
|
|
|
|
|
// after newly created upload should succeed
|
2019-10-09 13:50:10 -05:00
|
|
|
resp, err = resty.R().Post(baseURL + "/v2/a/b/c/d/blobs/uploads/")
|
2019-08-29 15:39:32 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 202)
|
|
|
|
|
2019-10-09 13:50:10 -05:00
|
|
|
resp, err = resty.R().SetResult(&api.RepositoryList{}).Get(baseURL + "/v2/_catalog")
|
2019-08-29 15:39:32 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
|
|
So(resp.String(), ShouldNotBeEmpty)
|
|
|
|
r := resp.Result().(*api.RepositoryList)
|
2019-12-24 01:32:52 -05:00
|
|
|
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")
|
|
|
|
}
|
2019-06-20 18:36:40 -05:00
|
|
|
})
|
|
|
|
|
|
|
|
Convey("Get images in a repository", func() {
|
2019-10-09 13:50:10 -05:00
|
|
|
Print("\nGet images in a repository")
|
2019-06-20 18:36:40 -05:00
|
|
|
// non-existent repository should fail
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err := resty.R().Get(baseURL + "/v2/repo1/tags/list")
|
2019-06-20 18:36:40 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
|
|
So(resp.String(), ShouldNotBeEmpty)
|
|
|
|
|
2019-07-10 00:23:59 -05:00
|
|
|
// after newly created upload should succeed
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err = resty.R().Post(baseURL + "/v2/repo1/blobs/uploads/")
|
2019-06-20 18:36:40 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 202)
|
|
|
|
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err = resty.R().Get(baseURL + "/v2/repo1/tags/list")
|
2019-06-20 18:36:40 -05:00
|
|
|
So(err, ShouldBeNil)
|
2019-12-24 01:32:52 -05:00
|
|
|
if !config.Compliance {
|
|
|
|
// stricter check for zot ci/cd
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
|
|
So(resp.String(), ShouldNotBeEmpty)
|
|
|
|
}
|
2019-06-20 18:36:40 -05:00
|
|
|
})
|
|
|
|
|
|
|
|
Convey("Monolithic blob upload", func() {
|
2019-10-09 13:50:10 -05:00
|
|
|
Print("\nMonolithic blob upload")
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err := resty.R().Post(baseURL + "/v2/repo2/blobs/uploads/")
|
2019-06-20 18:36:40 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 202)
|
2020-01-04 22:05:39 -05:00
|
|
|
loc := Location(baseURL, resp)
|
2019-06-20 18:36:40 -05:00
|
|
|
So(loc, ShouldNotBeEmpty)
|
|
|
|
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err = resty.R().Get(loc)
|
2019-06-20 18:36:40 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 204)
|
|
|
|
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err = resty.R().Get(baseURL + "/v2/repo2/tags/list")
|
2019-06-20 18:36:40 -05:00
|
|
|
So(err, ShouldBeNil)
|
2019-12-24 01:32:52 -05:00
|
|
|
if !config.Compliance {
|
|
|
|
// stricter check for zot ci/cd
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
|
|
So(resp.String(), ShouldNotBeEmpty)
|
|
|
|
}
|
2019-06-20 18:36:40 -05:00
|
|
|
|
|
|
|
// without a "?digest=<>" should fail
|
2020-02-17 16:57:15 -05:00
|
|
|
content := []byte("this is a blob1")
|
2019-06-20 18:36:40 -05:00
|
|
|
digest := godigest.FromBytes(content)
|
|
|
|
So(digest, ShouldNotBeNil)
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err = resty.R().Put(loc)
|
2019-06-20 18:36:40 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 400)
|
|
|
|
// without the Content-Length should fail
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err = resty.R().SetQueryParam("digest", digest.String()).Put(loc)
|
2019-06-20 18:36:40 -05:00
|
|
|
So(err, ShouldBeNil)
|
2020-05-19 18:53:05 -05:00
|
|
|
So(resp.StatusCode(), ShouldEqual, 400)
|
2019-06-20 18:36:40 -05:00
|
|
|
// without any data to send, should fail
|
|
|
|
resp, err = resty.R().SetQueryParam("digest", digest.String()).
|
2019-12-24 01:32:52 -05:00
|
|
|
SetHeader("Content-Type", "application/octet-stream").Put(loc)
|
2019-06-20 18:36:40 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 400)
|
|
|
|
// monolithic blob upload: success
|
|
|
|
resp, err = resty.R().SetQueryParam("digest", digest.String()).
|
2019-12-24 01:32:52 -05:00
|
|
|
SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(loc)
|
2019-06-20 18:36:40 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 201)
|
2020-01-04 22:05:39 -05:00
|
|
|
blobLoc := Location(baseURL, resp)
|
2019-06-20 18:36:40 -05:00
|
|
|
So(blobLoc, ShouldNotBeEmpty)
|
|
|
|
So(resp.Header().Get("Content-Length"), ShouldEqual, "0")
|
|
|
|
So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty)
|
|
|
|
// upload reference should now be removed
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err = resty.R().Get(loc)
|
2019-06-20 18:36:40 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
|
|
// blob reference should be accessible
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err = resty.R().Get(blobLoc)
|
2019-06-20 18:36:40 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
|
|
})
|
|
|
|
|
2020-01-31 02:59:36 -05:00
|
|
|
Convey("Monolithic blob upload with body", func() {
|
|
|
|
Print("\nMonolithic blob upload")
|
|
|
|
// create content
|
2020-02-17 16:57:15 -05:00
|
|
|
content := []byte("this is a blob2")
|
2020-01-31 02:59:36 -05:00
|
|
|
digest := godigest.FromBytes(content)
|
|
|
|
So(digest, ShouldNotBeNil)
|
|
|
|
// setting invalid URL params should fail
|
|
|
|
resp, err := resty.R().
|
|
|
|
SetQueryParam("digest", digest.String()).
|
|
|
|
SetQueryParam("from", digest.String()).
|
|
|
|
SetHeader("Content-Type", "application/octet-stream").
|
|
|
|
SetBody(content).
|
|
|
|
Post(baseURL + "/v2/repo2/blobs/uploads/")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 405)
|
|
|
|
// setting a "?digest=<>" but without body should fail
|
|
|
|
resp, err = resty.R().
|
|
|
|
SetQueryParam("digest", digest.String()).
|
|
|
|
SetHeader("Content-Type", "application/octet-stream").
|
|
|
|
Post(baseURL + "/v2/repo2/blobs/uploads/")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 400)
|
|
|
|
// set a "?digest=<>"
|
|
|
|
resp, err = resty.R().
|
|
|
|
SetQueryParam("digest", digest.String()).
|
|
|
|
SetHeader("Content-Type", "application/octet-stream").
|
|
|
|
SetBody(content).
|
|
|
|
Post(baseURL + "/v2/repo2/blobs/uploads/")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 201)
|
|
|
|
loc := Location(baseURL, resp)
|
|
|
|
So(loc, ShouldNotBeEmpty)
|
|
|
|
// blob reference should be accessible
|
|
|
|
resp, err = resty.R().Get(loc)
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
|
|
})
|
|
|
|
|
2019-07-10 00:23:59 -05:00
|
|
|
Convey("Monolithic blob upload with multiple name components", func() {
|
2019-10-09 13:50:10 -05:00
|
|
|
Print("\nMonolithic blob upload with multiple name components")
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err := resty.R().Post(baseURL + "/v2/repo10/repo20/repo30/blobs/uploads/")
|
2019-07-10 00:23:59 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 202)
|
2020-01-04 22:05:39 -05:00
|
|
|
loc := Location(baseURL, resp)
|
2019-07-10 00:23:59 -05:00
|
|
|
So(loc, ShouldNotBeEmpty)
|
|
|
|
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err = resty.R().Get(loc)
|
2019-07-10 00:23:59 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 204)
|
|
|
|
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err = resty.R().Get(baseURL + "/v2/repo10/repo20/repo30/tags/list")
|
2019-07-10 00:23:59 -05:00
|
|
|
So(err, ShouldBeNil)
|
2019-12-24 01:32:52 -05:00
|
|
|
if !config.Compliance {
|
|
|
|
// stricter check for zot ci/cd
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
|
|
So(resp.String(), ShouldNotBeEmpty)
|
|
|
|
}
|
2019-07-10 00:23:59 -05:00
|
|
|
|
|
|
|
// without a "?digest=<>" should fail
|
2020-02-17 16:57:15 -05:00
|
|
|
content := []byte("this is a blob3")
|
2019-07-10 00:23:59 -05:00
|
|
|
digest := godigest.FromBytes(content)
|
|
|
|
So(digest, ShouldNotBeNil)
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err = resty.R().Put(loc)
|
2019-07-10 00:23:59 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 400)
|
|
|
|
// without the Content-Length should fail
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err = resty.R().SetQueryParam("digest", digest.String()).Put(loc)
|
2019-07-10 00:23:59 -05:00
|
|
|
So(err, ShouldBeNil)
|
2020-05-19 18:53:05 -05:00
|
|
|
So(resp.StatusCode(), ShouldEqual, 400)
|
2019-07-10 00:23:59 -05:00
|
|
|
// without any data to send, should fail
|
|
|
|
resp, err = resty.R().SetQueryParam("digest", digest.String()).
|
2019-12-24 01:32:52 -05:00
|
|
|
SetHeader("Content-Type", "application/octet-stream").Put(loc)
|
2019-07-10 00:23:59 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 400)
|
|
|
|
// monolithic blob upload: success
|
|
|
|
resp, err = resty.R().SetQueryParam("digest", digest.String()).
|
2019-12-24 01:32:52 -05:00
|
|
|
SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(loc)
|
2019-07-10 00:23:59 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 201)
|
2020-01-04 22:05:39 -05:00
|
|
|
blobLoc := Location(baseURL, resp)
|
2019-07-10 00:23:59 -05:00
|
|
|
So(blobLoc, ShouldNotBeEmpty)
|
|
|
|
So(resp.Header().Get("Content-Length"), ShouldEqual, "0")
|
|
|
|
So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty)
|
|
|
|
// upload reference should now be removed
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err = resty.R().Get(loc)
|
2019-07-10 00:23:59 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
|
|
// blob reference should be accessible
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err = resty.R().Get(blobLoc)
|
2019-07-10 00:23:59 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
|
|
})
|
|
|
|
|
2019-06-20 18:36:40 -05:00
|
|
|
Convey("Chunked blob upload", func() {
|
2019-10-09 13:50:10 -05:00
|
|
|
Print("\nChunked blob upload")
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err := resty.R().Post(baseURL + "/v2/repo3/blobs/uploads/")
|
2019-06-20 18:36:40 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 202)
|
2020-01-04 22:05:39 -05:00
|
|
|
loc := Location(baseURL, resp)
|
2019-06-20 18:36:40 -05:00
|
|
|
So(loc, ShouldNotBeEmpty)
|
|
|
|
|
|
|
|
var buf bytes.Buffer
|
2020-02-17 16:57:15 -05:00
|
|
|
chunk1 := []byte("this is the first chunk1")
|
2019-06-20 18:36:40 -05:00
|
|
|
n, err := buf.Write(chunk1)
|
|
|
|
So(n, ShouldEqual, len(chunk1))
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
|
2019-07-10 00:23:59 -05:00
|
|
|
// write first chunk
|
2020-01-09 19:31:34 -05:00
|
|
|
contentRange := fmt.Sprintf("%d-%d", 0, len(chunk1)-1)
|
2019-07-10 00:23:59 -05:00
|
|
|
resp, err = resty.R().SetHeader("Content-Type", "application/octet-stream").
|
2019-12-24 01:32:52 -05:00
|
|
|
SetHeader("Content-Range", contentRange).SetBody(chunk1).Patch(loc)
|
2019-07-10 00:23:59 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 202)
|
|
|
|
|
|
|
|
// check progress
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err = resty.R().Get(loc)
|
2019-07-10 00:23:59 -05:00
|
|
|
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
|
2020-01-09 19:31:34 -05:00
|
|
|
contentRange = fmt.Sprintf("%d-%d", 0, len(chunk1)-1)
|
2019-07-10 00:23:59 -05:00
|
|
|
resp, err = resty.R().SetHeader("Content-Type", "application/octet-stream").
|
2019-12-24 01:32:52 -05:00
|
|
|
SetHeader("Content-Range", contentRange).SetBody(chunk1).Patch(loc)
|
2019-07-10 00:23:59 -05:00
|
|
|
So(err, ShouldBeNil)
|
2020-01-31 02:59:36 -05:00
|
|
|
So(resp.StatusCode(), ShouldEqual, 416)
|
2019-07-10 00:23:59 -05:00
|
|
|
So(resp.String(), ShouldNotBeEmpty)
|
|
|
|
|
2020-02-17 16:57:15 -05:00
|
|
|
chunk2 := []byte("this is the second chunk1")
|
2019-07-10 00:23:59 -05:00
|
|
|
n, err = buf.Write(chunk2)
|
|
|
|
So(n, ShouldEqual, len(chunk2))
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
|
|
|
|
digest := godigest.FromBytes(buf.Bytes())
|
|
|
|
So(digest, ShouldNotBeNil)
|
|
|
|
|
|
|
|
// write final chunk
|
2020-01-09 19:31:34 -05:00
|
|
|
contentRange = fmt.Sprintf("%d-%d", len(chunk1), len(buf.Bytes())-1)
|
2019-07-10 00:23:59 -05:00
|
|
|
resp, err = resty.R().SetQueryParam("digest", digest.String()).
|
|
|
|
SetHeader("Content-Range", contentRange).
|
2019-12-24 01:32:52 -05:00
|
|
|
SetHeader("Content-Type", "application/octet-stream").SetBody(chunk2).Put(loc)
|
2019-07-10 00:23:59 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 201)
|
2020-01-04 22:05:39 -05:00
|
|
|
blobLoc := Location(baseURL, resp)
|
2019-07-10 00:23:59 -05:00
|
|
|
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
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err = resty.R().Get(loc)
|
2019-07-10 00:23:59 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
|
|
// blob reference should be accessible
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err = resty.R().Get(blobLoc)
|
2019-07-10 00:23:59 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
|
|
})
|
|
|
|
|
|
|
|
Convey("Chunked blob upload with multiple name components", func() {
|
2019-10-09 13:50:10 -05:00
|
|
|
Print("\nChunked blob upload with multiple name components")
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err := resty.R().Post(baseURL + "/v2/repo40/repo50/repo60/blobs/uploads/")
|
2019-07-10 00:23:59 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 202)
|
2020-01-04 22:05:39 -05:00
|
|
|
loc := Location(baseURL, resp)
|
2019-07-10 00:23:59 -05:00
|
|
|
So(loc, ShouldNotBeEmpty)
|
|
|
|
|
|
|
|
var buf bytes.Buffer
|
2020-02-17 16:57:15 -05:00
|
|
|
chunk1 := []byte("this is the first chunk2")
|
2019-07-10 00:23:59 -05:00
|
|
|
n, err := buf.Write(chunk1)
|
|
|
|
So(n, ShouldEqual, len(chunk1))
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
|
2019-06-20 18:36:40 -05:00
|
|
|
// write first chunk
|
2020-01-09 19:31:34 -05:00
|
|
|
contentRange := fmt.Sprintf("%d-%d", 0, len(chunk1)-1)
|
2019-06-20 18:36:40 -05:00
|
|
|
resp, err = resty.R().SetHeader("Content-Type", "application/octet-stream").
|
2019-12-24 01:32:52 -05:00
|
|
|
SetHeader("Content-Range", contentRange).SetBody(chunk1).Patch(loc)
|
2019-06-20 18:36:40 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 202)
|
|
|
|
|
|
|
|
// check progress
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err = resty.R().Get(loc)
|
2019-06-20 18:36:40 -05:00
|
|
|
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
|
2020-01-09 19:31:34 -05:00
|
|
|
contentRange = fmt.Sprintf("%d-%d", 0, len(chunk1)-1)
|
2019-06-20 18:36:40 -05:00
|
|
|
resp, err = resty.R().SetHeader("Content-Type", "application/octet-stream").
|
2019-12-24 01:32:52 -05:00
|
|
|
SetHeader("Content-Range", contentRange).SetBody(chunk1).Patch(loc)
|
2019-06-20 18:36:40 -05:00
|
|
|
So(err, ShouldBeNil)
|
2020-01-31 02:59:36 -05:00
|
|
|
So(resp.StatusCode(), ShouldEqual, 416)
|
2019-06-20 18:36:40 -05:00
|
|
|
So(resp.String(), ShouldNotBeEmpty)
|
|
|
|
|
2020-02-17 16:57:15 -05:00
|
|
|
chunk2 := []byte("this is the second chunk2")
|
2019-06-20 18:36:40 -05:00
|
|
|
n, err = buf.Write(chunk2)
|
|
|
|
So(n, ShouldEqual, len(chunk2))
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
|
|
|
|
digest := godigest.FromBytes(buf.Bytes())
|
|
|
|
So(digest, ShouldNotBeNil)
|
|
|
|
|
|
|
|
// write final chunk
|
2020-01-09 19:31:34 -05:00
|
|
|
contentRange = fmt.Sprintf("%d-%d", len(chunk1), len(buf.Bytes())-1)
|
2019-06-20 18:36:40 -05:00
|
|
|
resp, err = resty.R().SetQueryParam("digest", digest.String()).
|
|
|
|
SetHeader("Content-Range", contentRange).
|
2019-12-24 01:32:52 -05:00
|
|
|
SetHeader("Content-Type", "application/octet-stream").SetBody(chunk2).Put(loc)
|
2019-06-20 18:36:40 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 201)
|
2020-01-04 22:05:39 -05:00
|
|
|
blobLoc := Location(baseURL, resp)
|
2019-06-20 18:36:40 -05:00
|
|
|
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
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err = resty.R().Get(loc)
|
2019-06-20 18:36:40 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
|
|
// blob reference should be accessible
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err = resty.R().Get(blobLoc)
|
2019-06-20 18:36:40 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
|
|
})
|
|
|
|
|
|
|
|
Convey("Create and delete uploads", func() {
|
2019-10-09 13:50:10 -05:00
|
|
|
Print("\nCreate and delete uploads")
|
2019-06-20 18:36:40 -05:00
|
|
|
// create a upload
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err := resty.R().Post(baseURL + "/v2/repo4/blobs/uploads/")
|
2019-06-20 18:36:40 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 202)
|
2020-01-04 22:05:39 -05:00
|
|
|
loc := Location(baseURL, resp)
|
2019-06-20 18:36:40 -05:00
|
|
|
So(loc, ShouldNotBeEmpty)
|
|
|
|
|
|
|
|
// delete this upload
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err = resty.R().Delete(loc)
|
2019-06-20 18:36:40 -05:00
|
|
|
So(err, ShouldBeNil)
|
2019-12-24 01:32:52 -05:00
|
|
|
So(resp.StatusCode(), ShouldEqual, 204)
|
2019-06-20 18:36:40 -05:00
|
|
|
})
|
|
|
|
|
|
|
|
Convey("Create and delete blobs", func() {
|
2019-10-09 13:50:10 -05:00
|
|
|
Print("\nCreate and delete blobs")
|
2019-06-20 18:36:40 -05:00
|
|
|
// create a upload
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err := resty.R().Post(baseURL + "/v2/repo5/blobs/uploads/")
|
2019-06-20 18:36:40 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 202)
|
2020-01-04 22:05:39 -05:00
|
|
|
loc := Location(baseURL, resp)
|
2019-06-20 18:36:40 -05:00
|
|
|
So(loc, ShouldNotBeEmpty)
|
|
|
|
|
2020-02-17 16:57:15 -05:00
|
|
|
content := []byte("this is a blob4")
|
2019-06-20 18:36:40 -05:00
|
|
|
digest := godigest.FromBytes(content)
|
|
|
|
So(digest, ShouldNotBeNil)
|
|
|
|
// monolithic blob upload
|
|
|
|
resp, err = resty.R().SetQueryParam("digest", digest.String()).
|
2019-12-24 01:32:52 -05:00
|
|
|
SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(loc)
|
2019-06-20 18:36:40 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 201)
|
2020-01-04 22:05:39 -05:00
|
|
|
blobLoc := Location(baseURL, resp)
|
2019-06-20 18:36:40 -05:00
|
|
|
So(blobLoc, ShouldNotBeEmpty)
|
|
|
|
So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty)
|
|
|
|
|
|
|
|
// delete this blob
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err = resty.R().Delete(blobLoc)
|
2019-06-20 18:36:40 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 202)
|
|
|
|
So(resp.Header().Get("Content-Length"), ShouldEqual, "0")
|
|
|
|
})
|
|
|
|
|
2019-12-20 13:37:41 -05:00
|
|
|
Convey("Mount blobs", func() {
|
|
|
|
Print("\nMount blobs from another repository")
|
|
|
|
// create a upload
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err := resty.R().Post(baseURL + "/v2/repo6/blobs/uploads/?digest=\"abc\"&&from=\"xyz\"")
|
2019-12-20 13:37:41 -05:00
|
|
|
So(err, ShouldBeNil)
|
2019-12-24 01:32:52 -05:00
|
|
|
So(resp.StatusCode(), ShouldBeIn, []int{201, 202, 405})
|
2019-12-20 13:37:41 -05:00
|
|
|
})
|
|
|
|
|
2019-06-20 18:36:40 -05:00
|
|
|
Convey("Manifests", func() {
|
2019-10-09 13:50:10 -05:00
|
|
|
Print("\nManifests")
|
2019-06-20 18:36:40 -05:00
|
|
|
// create a blob/layer
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err := resty.R().Post(baseURL + "/v2/repo7/blobs/uploads/")
|
2019-06-20 18:36:40 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 202)
|
2020-01-04 22:05:39 -05:00
|
|
|
loc := Location(baseURL, resp)
|
2019-06-20 18:36:40 -05:00
|
|
|
So(loc, ShouldNotBeEmpty)
|
|
|
|
|
2021-04-05 19:40:33 -05:00
|
|
|
// since we are not specifying any prefix i.e provided in config while starting server,
|
|
|
|
// so it should store repo7 to global root dir
|
|
|
|
_, err = os.Stat(path.Join(storageInfo[0], "repo7"))
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err = resty.R().Get(loc)
|
2019-06-20 18:36:40 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 204)
|
2020-02-17 16:57:15 -05:00
|
|
|
content := []byte("this is a blob5")
|
2019-06-20 18:36:40 -05:00
|
|
|
digest := godigest.FromBytes(content)
|
|
|
|
So(digest, ShouldNotBeNil)
|
|
|
|
// monolithic blob upload: success
|
|
|
|
resp, err = resty.R().SetQueryParam("digest", digest.String()).
|
2019-12-24 01:32:52 -05:00
|
|
|
SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(loc)
|
2019-06-20 18:36:40 -05:00
|
|
|
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)
|
|
|
|
|
2020-02-13 14:00:11 -05:00
|
|
|
// check a non-existent manifest
|
|
|
|
resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json").
|
|
|
|
SetBody(content).Head(baseURL + "/v2/unknown/manifests/test:1.0")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
|
|
|
2019-06-20 18:36:40 -05:00
|
|
|
// create a manifest
|
2020-03-18 17:42:06 -05:00
|
|
|
m := ispec.Manifest{
|
|
|
|
Config: ispec.Descriptor{
|
|
|
|
Digest: digest,
|
|
|
|
Size: int64(len(content)),
|
|
|
|
},
|
|
|
|
Layers: []ispec.Descriptor{
|
|
|
|
{
|
|
|
|
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
|
|
|
Digest: digest,
|
|
|
|
Size: int64(len(content)),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
m.SchemaVersion = 2
|
2019-06-20 18:36:40 -05:00
|
|
|
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").
|
2019-12-24 01:32:52 -05:00
|
|
|
SetBody(content).Put(baseURL + "/v2/repo7/manifests/test:1.0")
|
2019-06-20 18:36:40 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 201)
|
|
|
|
d := resp.Header().Get(api.DistContentDigestKey)
|
|
|
|
So(d, ShouldNotBeEmpty)
|
|
|
|
So(d, ShouldEqual, digest.String())
|
|
|
|
|
2020-02-17 16:57:15 -05:00
|
|
|
content = []byte("this is a blob5")
|
2020-01-31 02:59:36 -05:00
|
|
|
digest = godigest.FromBytes(content)
|
|
|
|
So(digest, ShouldNotBeNil)
|
|
|
|
// create a manifest with same blob but a different tag
|
2020-03-18 17:42:06 -05:00
|
|
|
m = ispec.Manifest{
|
|
|
|
Config: ispec.Descriptor{
|
|
|
|
Digest: digest,
|
|
|
|
Size: int64(len(content)),
|
|
|
|
},
|
|
|
|
Layers: []ispec.Descriptor{
|
|
|
|
{
|
|
|
|
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
|
|
|
Digest: digest,
|
|
|
|
Size: int64(len(content)),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
m.SchemaVersion = 2
|
2020-01-31 02:59:36 -05:00
|
|
|
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/repo7/manifests/test:2.0")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 201)
|
|
|
|
d = resp.Header().Get(api.DistContentDigestKey)
|
|
|
|
So(d, ShouldNotBeEmpty)
|
|
|
|
So(d, ShouldEqual, digest.String())
|
|
|
|
|
2019-06-20 18:36:40 -05:00
|
|
|
// check/get by tag
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err = resty.R().Head(baseURL + "/v2/repo7/manifests/test:1.0")
|
2019-06-20 18:36:40 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
2021-07-22 23:01:35 -05:00
|
|
|
So(resp.Header().Get("Content-Type"), ShouldNotBeEmpty)
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err = resty.R().Get(baseURL + "/v2/repo7/manifests/test:1.0")
|
2019-06-20 18:36:40 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
|
|
So(resp.Body(), ShouldNotBeEmpty)
|
|
|
|
// check/get by reference
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err = resty.R().Head(baseURL + "/v2/repo7/manifests/" + digest.String())
|
2019-06-20 18:36:40 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
2021-07-22 23:01:35 -05:00
|
|
|
So(resp.Header().Get("Content-Type"), ShouldNotBeEmpty)
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err = resty.R().Get(baseURL + "/v2/repo7/manifests/" + digest.String())
|
2019-06-20 18:36:40 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
|
|
So(resp.Body(), ShouldNotBeEmpty)
|
|
|
|
|
2020-01-28 16:55:58 -05:00
|
|
|
// delete manifest by tag should fail
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err = resty.R().Delete(baseURL + "/v2/repo7/manifests/test:1.0")
|
2019-06-20 18:36:40 -05:00
|
|
|
So(err, ShouldBeNil)
|
2020-03-18 17:50:40 -05:00
|
|
|
So(resp.StatusCode(), ShouldEqual, 400)
|
2020-01-28 16:55:58 -05:00
|
|
|
// delete manifest by digest
|
|
|
|
resp, err = resty.R().Delete(baseURL + "/v2/repo7/manifests/" + digest.String())
|
|
|
|
So(err, ShouldBeNil)
|
2020-01-09 19:31:34 -05:00
|
|
|
So(resp.StatusCode(), ShouldEqual, 202)
|
2020-01-31 02:59:36 -05:00
|
|
|
// delete manifest by digest
|
|
|
|
resp, err = resty.R().Delete(baseURL + "/v2/repo7/manifests/" + digest.String())
|
|
|
|
So(err, ShouldBeNil)
|
2020-04-06 20:17:24 -05:00
|
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
2019-06-20 18:36:40 -05:00
|
|
|
// delete again should fail
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err = resty.R().Delete(baseURL + "/v2/repo7/manifests/" + digest.String())
|
2019-06-20 18:36:40 -05:00
|
|
|
So(err, ShouldBeNil)
|
2020-03-25 13:22:52 -05:00
|
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
2019-06-20 18:36:40 -05:00
|
|
|
|
|
|
|
// check/get by tag
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err = resty.R().Head(baseURL + "/v2/repo7/manifests/test:1.0")
|
2019-06-20 18:36:40 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err = resty.R().Get(baseURL + "/v2/repo7/manifests/test:1.0")
|
2019-06-20 18:36:40 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
|
|
So(resp.Body(), ShouldNotBeEmpty)
|
2020-01-31 02:59:36 -05:00
|
|
|
resp, err = resty.R().Head(baseURL + "/v2/repo7/manifests/test:2.0")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
|
|
resp, err = resty.R().Get(baseURL + "/v2/repo7/manifests/test:2.0")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
|
|
So(resp.Body(), ShouldNotBeEmpty)
|
2019-06-20 18:36:40 -05:00
|
|
|
// check/get by reference
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err = resty.R().Head(baseURL + "/v2/repo7/manifests/" + digest.String())
|
2019-06-20 18:36:40 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
2019-12-24 01:32:52 -05:00
|
|
|
resp, err = resty.R().Get(baseURL + "/v2/repo7/manifests/" + digest.String())
|
2019-06-20 18:36:40 -05:00
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
|
|
So(resp.Body(), ShouldNotBeEmpty)
|
|
|
|
})
|
2019-12-24 01:32:52 -05:00
|
|
|
|
2020-01-09 19:31:34 -05:00
|
|
|
// pagination
|
|
|
|
Convey("Pagination", func() {
|
|
|
|
Print("\nPagination")
|
|
|
|
|
|
|
|
for i := 0; i <= 4; i++ {
|
|
|
|
// create a blob/layer
|
|
|
|
resp, err := resty.R().Post(baseURL + "/v2/page0/blobs/uploads/")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 202)
|
|
|
|
loc := Location(baseURL, resp)
|
|
|
|
So(loc, ShouldNotBeEmpty)
|
|
|
|
|
|
|
|
resp, err = resty.R().Get(loc)
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 204)
|
2020-02-17 16:57:15 -05:00
|
|
|
content := []byte("this is a blob7")
|
2020-01-09 19:31:34 -05:00
|
|
|
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(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
|
2020-03-18 17:42:06 -05:00
|
|
|
m := ispec.Manifest{
|
|
|
|
Config: ispec.Descriptor{
|
|
|
|
Digest: digest,
|
|
|
|
Size: int64(len(content)),
|
|
|
|
},
|
|
|
|
Layers: []ispec.Descriptor{
|
|
|
|
{
|
|
|
|
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
|
|
|
Digest: digest,
|
|
|
|
Size: int64(len(content)),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
m.SchemaVersion = 2
|
2020-01-09 19:31:34 -05:00
|
|
|
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 + fmt.Sprintf("/v2/page0/manifests/test:%d.0", i))
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 201)
|
|
|
|
d := resp.Header().Get(api.DistContentDigestKey)
|
|
|
|
So(d, ShouldNotBeEmpty)
|
|
|
|
So(d, ShouldEqual, digest.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
resp, err := resty.R().Get(baseURL + "/v2/page0/tags/list")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
|
|
|
2020-04-06 20:17:24 -05:00
|
|
|
resp, err = resty.R().Get(baseURL + "/v2/page0/tags/list?n=0")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
|
|
|
2020-01-09 19:31:34 -05:00
|
|
|
resp, err = resty.R().Get(baseURL + "/v2/page0/tags/list?n=3")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
|
|
next := resp.Header().Get("Link")
|
|
|
|
So(next, ShouldNotBeEmpty)
|
|
|
|
|
|
|
|
u := baseURL + strings.Split(next, ";")[0]
|
|
|
|
resp, err = resty.R().Get(u)
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
|
|
next = resp.Header().Get("Link")
|
|
|
|
So(next, ShouldBeEmpty)
|
|
|
|
})
|
|
|
|
|
2019-12-24 01:32:52 -05:00
|
|
|
// 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)
|
|
|
|
})
|
2021-04-05 19:40:33 -05:00
|
|
|
|
|
|
|
Convey("Multiple Storage", func() {
|
|
|
|
// test APIS on subpath routes, default storage already tested above
|
|
|
|
// subpath route firsttest
|
|
|
|
resp, err := resty.R().Post(baseURL + "/v2/firsttest/first/blobs/uploads/")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 202)
|
|
|
|
firstloc := Location(baseURL, resp)
|
|
|
|
So(firstloc, ShouldNotBeEmpty)
|
|
|
|
|
|
|
|
resp, err = resty.R().Get(firstloc)
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 204)
|
|
|
|
|
|
|
|
// if firsttest route is used as prefix in url that means repo should be stored in subpaths["firsttest"] rootdir
|
|
|
|
_, err = os.Stat(path.Join(storageInfo[1], "firsttest/first"))
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
|
|
|
|
// subpath route secondtest
|
|
|
|
resp, err = resty.R().Post(baseURL + "/v2/secondtest/second/blobs/uploads/")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 202)
|
|
|
|
secondloc := Location(baseURL, resp)
|
|
|
|
So(secondloc, ShouldNotBeEmpty)
|
|
|
|
|
|
|
|
resp, err = resty.R().Get(secondloc)
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 204)
|
|
|
|
|
|
|
|
// if secondtest route is used as prefix in url that means repo should be stored in subpaths["secondtest"] rootdir
|
|
|
|
_, err = os.Stat(path.Join(storageInfo[2], "secondtest/second"))
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
|
|
|
|
content := []byte("this is a blob5")
|
|
|
|
digest := godigest.FromBytes(content)
|
|
|
|
So(digest, ShouldNotBeNil)
|
|
|
|
// monolithic blob upload: success
|
|
|
|
// first test
|
|
|
|
resp, err = resty.R().SetQueryParam("digest", digest.String()).
|
|
|
|
SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(firstloc)
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 201)
|
|
|
|
firstblobLoc := resp.Header().Get("Location")
|
|
|
|
So(firstblobLoc, ShouldNotBeEmpty)
|
|
|
|
So(resp.Header().Get("Content-Length"), ShouldEqual, "0")
|
|
|
|
So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty)
|
|
|
|
|
|
|
|
// second test
|
|
|
|
resp, err = resty.R().SetQueryParam("digest", digest.String()).
|
|
|
|
SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(secondloc)
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 201)
|
|
|
|
secondblobLoc := resp.Header().Get("Location")
|
|
|
|
So(secondblobLoc, ShouldNotBeEmpty)
|
|
|
|
So(resp.Header().Get("Content-Length"), ShouldEqual, "0")
|
|
|
|
So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty)
|
|
|
|
|
|
|
|
// check a non-existent manifest
|
|
|
|
resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json").
|
|
|
|
SetBody(content).Head(baseURL + "/v2/unknown/manifests/test:1.0")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
|
|
|
|
|
|
resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json").
|
|
|
|
SetBody(content).Head(baseURL + "/v2/firsttest/unknown/manifests/test:1.0")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
|
|
|
|
|
|
resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json").
|
|
|
|
SetBody(content).Head(baseURL + "/v2/secondtest/unknown/manifests/test:1.0")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
|
|
|
|
|
|
// create a manifest
|
|
|
|
m := ispec.Manifest{
|
|
|
|
Config: ispec.Descriptor{
|
|
|
|
Digest: digest,
|
|
|
|
Size: int64(len(content)),
|
|
|
|
},
|
|
|
|
Layers: []ispec.Descriptor{
|
|
|
|
{
|
|
|
|
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
|
|
|
Digest: digest,
|
|
|
|
Size: int64(len(content)),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
m.SchemaVersion = 2
|
|
|
|
content, err = json.Marshal(m)
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
digest = godigest.FromBytes(content)
|
|
|
|
So(digest, ShouldNotBeNil)
|
|
|
|
// subpath firsttest
|
|
|
|
resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json").
|
|
|
|
SetBody(content).Put(baseURL + "/v2/firsttest/first/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())
|
|
|
|
|
|
|
|
// subpath secondtest
|
|
|
|
resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json").
|
|
|
|
SetBody(content).Put(baseURL + "/v2/secondtest/second/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())
|
|
|
|
|
|
|
|
content = []byte("this is a blob5")
|
|
|
|
digest = godigest.FromBytes(content)
|
|
|
|
So(digest, ShouldNotBeNil)
|
|
|
|
// create a manifest with same blob but a different tag
|
|
|
|
m = ispec.Manifest{
|
|
|
|
Config: ispec.Descriptor{
|
|
|
|
Digest: digest,
|
|
|
|
Size: int64(len(content)),
|
|
|
|
},
|
|
|
|
Layers: []ispec.Descriptor{
|
|
|
|
{
|
|
|
|
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
|
|
|
Digest: digest,
|
|
|
|
Size: int64(len(content)),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
m.SchemaVersion = 2
|
|
|
|
content, err = json.Marshal(m)
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
digest = godigest.FromBytes(content)
|
|
|
|
So(digest, ShouldNotBeNil)
|
|
|
|
|
|
|
|
// subpath firsttest
|
|
|
|
resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json").
|
|
|
|
SetBody(content).Put(baseURL + "/v2/firsttest/first/manifests/test:2.0")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 201)
|
|
|
|
d = resp.Header().Get(api.DistContentDigestKey)
|
|
|
|
So(d, ShouldNotBeEmpty)
|
|
|
|
So(d, ShouldEqual, digest.String())
|
|
|
|
|
|
|
|
// subpath secondtest
|
|
|
|
resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json").
|
|
|
|
SetBody(content).Put(baseURL + "/v2/secondtest/second/manifests/test:2.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/firsttest/first/manifests/test:1.0")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
|
|
resp, err = resty.R().Get(baseURL + "/v2/firsttest/first/manifests/test:1.0")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
|
|
So(resp.Body(), ShouldNotBeEmpty)
|
|
|
|
resp, err = resty.R().Head(baseURL + "/v2/secondtest/second/manifests/test:1.0")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
|
|
resp, err = resty.R().Get(baseURL + "/v2/secondtest/second/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/firsttest/first/manifests/" + digest.String())
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
|
|
resp, err = resty.R().Get(baseURL + "/v2/firsttest/first/manifests/" + digest.String())
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
|
|
So(resp.Body(), ShouldNotBeEmpty)
|
|
|
|
|
|
|
|
resp, err = resty.R().Head(baseURL + "/v2/secondtest/second/manifests/" + digest.String())
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
|
|
resp, err = resty.R().Get(baseURL + "/v2/secondtest/second/manifests/" + digest.String())
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 200)
|
|
|
|
So(resp.Body(), ShouldNotBeEmpty)
|
|
|
|
|
|
|
|
// delete manifest by tag should fail
|
|
|
|
resp, err = resty.R().Delete(baseURL + "/v2/firsttest/first/manifests/test:1.0")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 400)
|
|
|
|
|
|
|
|
resp, err = resty.R().Delete(baseURL + "/v2/secondtest/second/manifests/test:1.0")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 400)
|
|
|
|
|
|
|
|
// delete manifest by digest
|
|
|
|
resp, err = resty.R().Delete(baseURL + "/v2/firsttest/first/manifests/" + digest.String())
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 202)
|
|
|
|
|
|
|
|
resp, err = resty.R().Delete(baseURL + "/v2/secondtest/second/manifests/" + digest.String())
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 202)
|
|
|
|
|
|
|
|
// delete manifest by digest
|
|
|
|
resp, err = resty.R().Delete(baseURL + "/v2/firsttest/first/manifests/" + digest.String())
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
|
|
|
|
|
|
resp, err = resty.R().Delete(baseURL + "/v2/secondtest/second/manifests/" + digest.String())
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
|
|
|
|
|
|
// delete again should fail
|
|
|
|
resp, err = resty.R().Delete(baseURL + "/v2/firsttest/first/manifests/" + digest.String())
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
|
|
|
|
|
|
resp, err = resty.R().Delete(baseURL + "/v2/secondtest/second/manifests/" + digest.String())
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
|
|
|
|
|
|
// check/get by tag
|
|
|
|
resp, err = resty.R().Head(baseURL + "/v2/firsttest/first/manifests/test:1.0")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
|
|
resp, err = resty.R().Get(baseURL + "/v2/firsttest/first/manifests/test:1.0")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
|
|
So(resp.Body(), ShouldNotBeEmpty)
|
|
|
|
|
|
|
|
resp, err = resty.R().Head(baseURL + "/v2/secondtest/second/manifests/test:1.0")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
|
|
resp, err = resty.R().Get(baseURL + "/v2/secondtest/second/manifests/test:1.0")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
|
|
So(resp.Body(), ShouldNotBeEmpty)
|
|
|
|
|
|
|
|
resp, err = resty.R().Head(baseURL + "/v2/firsttest/first/repo7/manifests/test:2.0")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
|
|
resp, err = resty.R().Get(baseURL + "/v2/firsttest/first/manifests/test:2.0")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
|
|
So(resp.Body(), ShouldNotBeEmpty)
|
|
|
|
|
|
|
|
resp, err = resty.R().Head(baseURL + "/v2/secondtest/second/manifests/test:2.0")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
|
|
resp, err = resty.R().Get(baseURL + "/v2/secondtest/second/manifests/test:2.0")
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
|
|
So(resp.Body(), ShouldNotBeEmpty)
|
|
|
|
|
|
|
|
// check/get by reference
|
|
|
|
resp, err = resty.R().Head(baseURL + "/v2/firsttest/first/manifests/" + digest.String())
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
|
|
resp, err = resty.R().Get(baseURL + "/v2/firsttest/first/manifests/" + digest.String())
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
|
|
So(resp.Body(), ShouldNotBeEmpty)
|
|
|
|
|
|
|
|
resp, err = resty.R().Head(baseURL + "/v2/secondtest/second/manifests/" + digest.String())
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
|
|
resp, err = resty.R().Get(baseURL + "/v2/secondtest/second/manifests/" + digest.String())
|
|
|
|
So(err, ShouldBeNil)
|
|
|
|
So(resp.StatusCode(), ShouldEqual, 404)
|
|
|
|
So(resp.Body(), ShouldNotBeEmpty)
|
|
|
|
})
|
2019-06-20 18:36:40 -05:00
|
|
|
})
|
|
|
|
}
|
2019-12-13 15:57:51 -05:00
|
|
|
|
2020-05-11 17:13:24 -05:00
|
|
|
// nolint: gochecknoglobals
|
2019-12-13 15:57:51 -05:00
|
|
|
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
|
2020-05-11 17:13:24 -05:00
|
|
|
|
|
|
|
_, err := io.Copy(&buf, r)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2019-12-13 15:57:51 -05:00
|
|
|
outC <- buf.String()
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
func outputJSONExit() {
|
|
|
|
// back to normal state
|
|
|
|
w.Close()
|
2020-05-11 17:13:24 -05:00
|
|
|
|
2019-12-13 15:57:51 -05:00
|
|
|
os.Stdout = old // restoring the real stdout
|
2020-05-11 17:13:24 -05:00
|
|
|
|
2019-12-13 15:57:51 -05:00
|
|
|
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{}
|
2020-05-11 17:13:24 -05:00
|
|
|
|
2019-12-13 15:57:51 -05:00
|
|
|
err := json.Unmarshal([]byte(rawJSON), &j)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2020-05-11 17:13:24 -05:00
|
|
|
|
2019-12-13 15:57:51 -05:00
|
|
|
rawJSONBytesMinified, err := json.Marshal(j)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2020-05-11 17:13:24 -05:00
|
|
|
|
2019-12-13 15:57:51 -05:00
|
|
|
return string(rawJSONBytesMinified)
|
|
|
|
}
|