From b107d6d1a4a3d6741a6e9ec3fe86abfd38bb1a22 Mon Sep 17 00:00:00 2001 From: Ramkumar Chinchani Date: Wed, 9 Oct 2019 11:50:10 -0700 Subject: [PATCH] compliance: initial commit --- README.md | 15 +- pkg/api/BUILD.bazel | 7 +- pkg/api/routes.go | 13 +- pkg/cli/BUILD.bazel | 2 + pkg/cli/root.go | 39 ++++ pkg/compliance/BUILD.bazel | 8 + pkg/compliance/config.go | 11 ++ pkg/compliance/v1_0_0/BUILD.bazel | 28 +++ .../v1_0_0/check.go} | 185 ++++++++---------- pkg/compliance/v1_0_0/check_test.go | 57 ++++++ 10 files changed, 249 insertions(+), 116 deletions(-) create mode 100644 pkg/compliance/BUILD.bazel create mode 100644 pkg/compliance/config.go create mode 100644 pkg/compliance/v1_0_0/BUILD.bazel rename pkg/{api/routes_test.go => compliance/v1_0_0/check.go} (76%) create mode 100644 pkg/compliance/v1_0_0/check_test.go diff --git a/README.md b/README.md index cdd5c08d..db829b24 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ # zot [![Build Status](https://travis-ci.org/anuvu/zot.svg?branch=master)](https://travis-ci.org/anuvu/zot) [![codecov.io](http://codecov.io/github/anuvu/zot/coverage.svg?branch=master)](http://codecov.io/github/anuvu/zot?branch=master) **zot** is a vendor-neutral OCI image repository server purely based on -[OCI Distribution Specification] -(https://github.com/opencontainers/distribution-spec). +[OCI Distribution Specification](https://github.com/opencontainers/distribution-spec). * Conforms to [OCI distribution spec](https://github.com/opencontainers/distribution-spec) APIs * Uses [OCI storage layout](https://github.com/opencontainers/image-spec/blob/master/image-layout.md) for storage layout @@ -10,6 +9,7 @@ * Authentication via TLS mutual authentication and HTTP *BASIC* (local _htpasswd_ and LDAP) * Doesn't require _root_ privileges * Swagger based documentation +* Can run compliance checks against registries * Released under Apache 2.0 License # Presentations @@ -39,12 +39,19 @@ make Build artifacts are in bin/ -# Running - +# Serving +``` bin/zot serve _config-file_ +``` Examples of config files are available in [examples/](examples/) dir. +# Compliance checks + +``` +bin/zot -H hostIP -P port [-V "all"] +``` + # Ecosystem ## skopeo diff --git a/pkg/api/BUILD.bazel b/pkg/api/BUILD.bazel index 032b4ae0..563dc7be 100644 --- a/pkg/api/BUILD.bazel +++ b/pkg/api/BUILD.bazel @@ -34,10 +34,7 @@ go_library( go_test( name = "go_default_test", timeout = "short", - srcs = [ - "controller_test.go", - "routes_test.go", - ], + srcs = ["controller_test.go"], data = [ "//:exported_testdata", ], @@ -45,8 +42,6 @@ go_test( race = "on", deps = [ "@com_github_nmcclain_ldap//:go_default_library", - "@com_github_opencontainers_go_digest//:go_default_library", - "@com_github_opencontainers_image_spec//specs-go/v1:go_default_library", "@com_github_smartystreets_goconvey//convey:go_default_library", "@in_gopkg_resty_v1//:go_default_library", ], diff --git a/pkg/api/routes.go b/pkg/api/routes.go index f2ecb157..aeb580dc 100644 --- a/pkg/api/routes.go +++ b/pkg/api/routes.go @@ -28,10 +28,13 @@ import ( httpSwagger "github.com/swaggo/http-swagger" ) -const RoutePrefix = "/v2" -const DistAPIVersion = "Docker-Distribution-API-Version" -const DistContentDigestKey = "Docker-Content-Digest" -const BlobUploadUUID = "Blob-Upload-UUID" +const ( + RoutePrefix = "/v2" + DistAPIVersion = "Docker-Distribution-API-Version" + DistContentDigestKey = "Docker-Content-Digest" + BlobUploadUUID = "Blob-Upload-UUID" + DefaultMediaType = "application/json" +) type RouteHandler struct { c *Controller @@ -823,7 +826,7 @@ func WriteJSON(w http.ResponseWriter, status int, data interface{}) { if err != nil { w.WriteHeader(http.StatusInternalServerError) } - WriteData(w, status, "application/json; charset=utf-8", body) + WriteData(w, status, DefaultMediaType, body) } func WriteData(w http.ResponseWriter, status int, mediaType string, data []byte) { diff --git a/pkg/cli/BUILD.bazel b/pkg/cli/BUILD.bazel index 4bc2b3a9..5d121447 100644 --- a/pkg/cli/BUILD.bazel +++ b/pkg/cli/BUILD.bazel @@ -8,6 +8,8 @@ go_library( deps = [ "//errors:go_default_library", "//pkg/api:go_default_library", + "//pkg/compliance:go_default_library", + "//pkg/compliance/v1_0_0:go_default_library", "//pkg/storage:go_default_library", "@com_github_mitchellh_mapstructure//:go_default_library", "@com_github_opencontainers_distribution_spec//:go_default_library", diff --git a/pkg/cli/root.go b/pkg/cli/root.go index f6579a6f..66b65f3c 100644 --- a/pkg/cli/root.go +++ b/pkg/cli/root.go @@ -1,14 +1,19 @@ package cli import ( + "testing" + "github.com/anuvu/zot/errors" "github.com/anuvu/zot/pkg/api" + "github.com/anuvu/zot/pkg/compliance" "github.com/anuvu/zot/pkg/storage" "github.com/mitchellh/mapstructure" dspec "github.com/opencontainers/distribution-spec" "github.com/rs/zerolog/log" "github.com/spf13/cobra" "github.com/spf13/viper" + + "github.com/anuvu/zot/pkg/compliance/v1_0_0" ) // metadataConfig reports metadata after parsing, which we use to track @@ -23,6 +28,7 @@ func NewRootCmd() *cobra.Command { showVersion := false config := api.NewConfig() + // "serve" serveCmd := &cobra.Command{ Use: "serve ", Aliases: []string{"serve"}, @@ -53,6 +59,7 @@ func NewRootCmd() *cobra.Command { }, } + // "garbage-collect" gcDelUntagged := false gcDryRun := false @@ -79,6 +86,37 @@ func NewRootCmd() *cobra.Command { gcCmd.Flags().BoolVarP(&gcDryRun, "dry-run", "d", false, "do everything except remove the blobs") + // "compliance" + complianceConfig := compliance.NewConfig() + complianceCmd := &cobra.Command{ + Use: "compliance", + Aliases: []string{"co"}, + Short: "`compliance` checks compliance with respect to OCI distribution-spec", + Long: "`compliance` checks compliance with respect to OCI distribution-spec", + Run: func(cmd *cobra.Command, args []string) { + t := &testing.T{} + switch complianceConfig.Version { + case "all": + fallthrough + default: + v1_0_0.CheckWorkflows(t, complianceConfig) + } + }, + } + + complianceCmd.Flags().StringVarP(&complianceConfig.Address, "address", "H", "", + "Registry server address") + if err := complianceCmd.MarkFlagRequired("address"); err != nil { + panic(err) + } + complianceCmd.Flags().StringVarP(&complianceConfig.Port, "port", "P", "", + "Registry server port") + if err := complianceCmd.MarkFlagRequired("port"); err != nil { + panic(err) + } + complianceCmd.Flags().StringVarP(&complianceConfig.Version, "version", "V", "all", + "OCI dist-spec version to check") + rootCmd := &cobra.Command{ Use: "zot", Short: "`zot`", @@ -93,6 +131,7 @@ func NewRootCmd() *cobra.Command { rootCmd.AddCommand(serveCmd) rootCmd.AddCommand(gcCmd) + rootCmd.AddCommand(complianceCmd) rootCmd.Flags().BoolVarP(&showVersion, "version", "v", false, "show the version and exit") return rootCmd diff --git a/pkg/compliance/BUILD.bazel b/pkg/compliance/BUILD.bazel new file mode 100644 index 00000000..06c4565d --- /dev/null +++ b/pkg/compliance/BUILD.bazel @@ -0,0 +1,8 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["config.go"], + importpath = "github.com/anuvu/zot/pkg/compliance", + visibility = ["//visibility:public"], +) diff --git a/pkg/compliance/config.go b/pkg/compliance/config.go new file mode 100644 index 00000000..75605bda --- /dev/null +++ b/pkg/compliance/config.go @@ -0,0 +1,11 @@ +package compliance + +type Config struct { + Address string + Port string + Version string +} + +func NewConfig() *Config { + return &Config{} +} diff --git a/pkg/compliance/v1_0_0/BUILD.bazel b/pkg/compliance/v1_0_0/BUILD.bazel new file mode 100644 index 00000000..2a773aca --- /dev/null +++ b/pkg/compliance/v1_0_0/BUILD.bazel @@ -0,0 +1,28 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["check.go"], + importpath = "github.com/anuvu/zot/pkg/compliance/v1_0_0", + visibility = ["//visibility:public"], + deps = [ + "//pkg/api:go_default_library", + "//pkg/compliance:go_default_library", + "@com_github_opencontainers_go_digest//:go_default_library", + "@com_github_opencontainers_image_spec//specs-go/v1:go_default_library", + "@com_github_smartystreets_goconvey//convey:go_default_library", + "@in_gopkg_resty_v1//:go_default_library", + ], +) + +go_test( + name = "go_default_test", + timeout = "short", + srcs = ["check_test.go"], + embed = [":go_default_library"], + deps = [ + "//pkg/api:go_default_library", + "//pkg/compliance:go_default_library", + "@in_gopkg_resty_v1//:go_default_library", + ], +) diff --git a/pkg/api/routes_test.go b/pkg/compliance/v1_0_0/check.go similarity index 76% rename from pkg/api/routes_test.go rename to pkg/compliance/v1_0_0/check.go index a214ec2c..cedea1a9 100644 --- a/pkg/api/routes_test.go +++ b/pkg/compliance/v1_0_0/check.go @@ -1,96 +1,102 @@ -// nolint (dupl) -package api_test +//nolint (dupl) +package v1_0_0 import ( "bytes" - "context" "encoding/json" "fmt" - "io/ioutil" - "os" "testing" - "time" "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" "gopkg.in/resty.v1" ) -const ( - DefaultContentType = "application/json; charset=utf-8" - BaseURL = "http://127.0.0.1:8080" -) +func CheckWorkflows(t *testing.T, config *compliance.Config) { + if config == nil || config.Address == "" || config.Port == "" { + panic("insufficient config") + } + baseURL := fmt.Sprintf("http://%s:%s", config.Address, config.Port) + + fmt.Println("------------------------------") + fmt.Println("Checking for v1.0.0 compliance") + fmt.Println("------------------------------") -func TestAPI(t *testing.T) { Convey("Make API calls to the controller", t, func(c C) { - Convey("check version", func() { - resp, err := resty.R().Get(BaseURL + "/v2/") + 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() { - resp, err := resty.R().Get(BaseURL + "/v2/_catalog") + 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, DefaultContentType) + 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/") + 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/") + 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") + 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") + 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/") + 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") + 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() { - resp, err := resty.R().Post(BaseURL + "/v2/repo/blobs/uploads/") + 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) + 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") + resp, err = resty.R().Get(baseURL + "/v2/repo/tags/list") So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) So(resp.String(), ShouldNotBeEmpty) @@ -99,21 +105,21 @@ func TestAPI(t *testing.T) { content := []byte("this is a blob") digest := godigest.FromBytes(content) So(digest, ShouldNotBeNil) - resp, err = resty.R().Put(BaseURL + loc) + resp, err = resty.R().Put(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) + 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) + 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) + SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(baseURL + loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 201) blobLoc := resp.Header().Get("Location") @@ -121,27 +127,28 @@ func TestAPI(t *testing.T) { So(resp.Header().Get("Content-Length"), ShouldEqual, "0") So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty) // upload reference should now be removed - resp, err = resty.R().Get(BaseURL + loc) + resp, err = resty.R().Get(baseURL + loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 404) // blob reference should be accessible - resp, err = resty.R().Get(BaseURL + blobLoc) + resp, err = resty.R().Get(baseURL + blobLoc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) }) Convey("Monolithic blob upload with multiple name components", func() { - resp, err := resty.R().Post(BaseURL + "/v2/repo1/repo2/repo3/blobs/uploads/") + 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) + 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") + resp, err = resty.R().Get(baseURL + "/v2/repo1/repo2/repo3/tags/list") So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) So(resp.String(), ShouldNotBeEmpty) @@ -150,21 +157,21 @@ func TestAPI(t *testing.T) { content := []byte("this is a blob") digest := godigest.FromBytes(content) So(digest, ShouldNotBeNil) - resp, err = resty.R().Put(BaseURL + loc) + resp, err = resty.R().Put(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) + 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) + 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) + SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(baseURL + loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 201) blobLoc := resp.Header().Get("Location") @@ -172,17 +179,18 @@ func TestAPI(t *testing.T) { So(resp.Header().Get("Content-Length"), ShouldEqual, "0") So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty) // upload reference should now be removed - resp, err = resty.R().Get(BaseURL + loc) + resp, err = resty.R().Get(baseURL + loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 404) // blob reference should be accessible - resp, err = resty.R().Get(BaseURL + blobLoc) + resp, err = resty.R().Get(baseURL + blobLoc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) }) Convey("Chunked blob upload", func() { - resp, err := resty.R().Post(BaseURL + "/v2/repo/blobs/uploads/") + 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") @@ -197,12 +205,12 @@ func TestAPI(t *testing.T) { // write first chunk contentRange := fmt.Sprintf("%d-%d", 0, len(chunk1)) resp, err = resty.R().SetHeader("Content-Type", "application/octet-stream"). - SetHeader("Content-Range", contentRange).SetBody(chunk1).Patch(BaseURL + loc) + SetHeader("Content-Range", contentRange).SetBody(chunk1).Patch(baseURL + loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 202) // check progress - resp, err = resty.R().Get(BaseURL + loc) + resp, err = resty.R().Get(baseURL + loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 204) r := resp.Header().Get("Range") @@ -212,7 +220,7 @@ func TestAPI(t *testing.T) { // write same chunk should fail contentRange = fmt.Sprintf("%d-%d", 0, len(chunk1)) resp, err = resty.R().SetHeader("Content-Type", "application/octet-stream"). - SetHeader("Content-Range", contentRange).SetBody(chunk1).Patch(BaseURL + loc) + SetHeader("Content-Range", contentRange).SetBody(chunk1).Patch(baseURL + loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 400) So(resp.String(), ShouldNotBeEmpty) @@ -229,7 +237,7 @@ func TestAPI(t *testing.T) { contentRange = fmt.Sprintf("%d-%d", len(chunk1), len(buf.Bytes())) resp, err = resty.R().SetQueryParam("digest", digest.String()). SetHeader("Content-Range", contentRange). - SetHeader("Content-Type", "application/octet-stream").SetBody(chunk2).Put(BaseURL + loc) + SetHeader("Content-Type", "application/octet-stream").SetBody(chunk2).Put(baseURL + loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 201) blobLoc := resp.Header().Get("Location") @@ -239,17 +247,18 @@ func TestAPI(t *testing.T) { So(resp.Header().Get("Content-Length"), ShouldEqual, "0") So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty) // upload reference should now be removed - resp, err = resty.R().Get(BaseURL + loc) + resp, err = resty.R().Get(baseURL + loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 404) // blob reference should be accessible - resp, err = resty.R().Get(BaseURL + blobLoc) + resp, err = resty.R().Get(baseURL + blobLoc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 200) }) Convey("Chunked blob upload with multiple name components", func() { - resp, err := resty.R().Post(BaseURL + "/v2/repo4/repo5/repo6/blobs/uploads/") + 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") @@ -264,12 +273,12 @@ func TestAPI(t *testing.T) { // write first chunk contentRange := fmt.Sprintf("%d-%d", 0, len(chunk1)) resp, err = resty.R().SetHeader("Content-Type", "application/octet-stream"). - SetHeader("Content-Range", contentRange).SetBody(chunk1).Patch(BaseURL + loc) + SetHeader("Content-Range", contentRange).SetBody(chunk1).Patch(baseURL + loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 202) // check progress - resp, err = resty.R().Get(BaseURL + loc) + resp, err = resty.R().Get(baseURL + loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 204) r := resp.Header().Get("Range") @@ -279,7 +288,7 @@ func TestAPI(t *testing.T) { // write same chunk should fail contentRange = fmt.Sprintf("%d-%d", 0, len(chunk1)) resp, err = resty.R().SetHeader("Content-Type", "application/octet-stream"). - SetHeader("Content-Range", contentRange).SetBody(chunk1).Patch(BaseURL + loc) + SetHeader("Content-Range", contentRange).SetBody(chunk1).Patch(baseURL + loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 400) So(resp.String(), ShouldNotBeEmpty) @@ -296,7 +305,7 @@ func TestAPI(t *testing.T) { contentRange = fmt.Sprintf("%d-%d", len(chunk1), len(buf.Bytes())) resp, err = resty.R().SetQueryParam("digest", digest.String()). SetHeader("Content-Range", contentRange). - SetHeader("Content-Type", "application/octet-stream").SetBody(chunk2).Put(BaseURL + loc) + SetHeader("Content-Type", "application/octet-stream").SetBody(chunk2).Put(baseURL + loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 201) blobLoc := resp.Header().Get("Location") @@ -306,32 +315,34 @@ func TestAPI(t *testing.T) { So(resp.Header().Get("Content-Length"), ShouldEqual, "0") So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty) // upload reference should now be removed - resp, err = resty.R().Get(BaseURL + loc) + resp, err = resty.R().Get(baseURL + loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 404) // blob reference should be accessible - resp, err = resty.R().Get(BaseURL + blobLoc) + resp, err = resty.R().Get(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/") + 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) + 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/") + resp, err := resty.R().Post(baseURL + "/v2/repo/blobs/uploads/") So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 202) loc := resp.Header().Get("Location") @@ -342,7 +353,7 @@ func TestAPI(t *testing.T) { So(digest, ShouldNotBeNil) // monolithic blob upload resp, err = resty.R().SetQueryParam("digest", digest.String()). - SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(BaseURL + loc) + SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(baseURL + loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 201) blobLoc := resp.Header().Get("Location") @@ -350,21 +361,22 @@ func TestAPI(t *testing.T) { So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty) // delete this blob - resp, err = resty.R().Delete(BaseURL + blobLoc) + resp, err = resty.R().Delete(baseURL + blobLoc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 202) So(resp.Header().Get("Content-Length"), ShouldEqual, "0") }) Convey("Manifests", func() { + Print("\nManifests") // create a blob/layer - resp, err := resty.R().Post(BaseURL + "/v2/repo/blobs/uploads/") + resp, err := resty.R().Post(baseURL + "/v2/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) + resp, err = resty.R().Get(baseURL + loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 204) content := []byte("this is a blob") @@ -372,7 +384,7 @@ func TestAPI(t *testing.T) { So(digest, ShouldNotBeNil) // monolithic blob upload: success resp, err = resty.R().SetQueryParam("digest", digest.String()). - SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(BaseURL + loc) + SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(baseURL + loc) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 201) blobLoc := resp.Header().Get("Location") @@ -387,7 +399,7 @@ func TestAPI(t *testing.T) { digest = godigest.FromBytes(content) So(digest, ShouldNotBeNil) resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json"). - SetBody(content).Put(BaseURL + "/v2/repo/manifests/test:1.0") + SetBody(content).Put(baseURL + "/v2/repo/manifests/test:1.0") So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 201) d := resp.Header().Get(api.DistContentDigestKey) @@ -395,76 +407,47 @@ func TestAPI(t *testing.T) { So(d, ShouldEqual, digest.String()) // check/get by tag - resp, err = resty.R().Head(BaseURL + "/v2/repo/manifests/test:1.0") + resp, err = resty.R().Head(baseURL + "/v2/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") + 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()) + 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()) + 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") + 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()) + 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") + 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") + 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()) + 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()) + resp, err = resty.R().Get(baseURL + "/v2/repo/manifests/" + digest.String()) So(err, ShouldBeNil) So(resp.StatusCode(), ShouldEqual, 404) So(resp.Body(), ShouldNotBeEmpty) }) }) } - -func TestMain(m *testing.M) { - config := api.NewConfig() - c := api.NewController(config) - dir, err := ioutil.TempDir("", "oci-repo-test") - if err != nil { - panic(err) - } - //defer os.RemoveAll(dir) - c.Config.Storage.RootDirectory = dir - go func() { - // this blocks - if err := c.Run(); err != nil { - return - } - }() - for { - // poll until ready - resp, _ := resty.R().Get(BaseURL) - if resp.StatusCode() == 404 { - break - } - time.Sleep(100 * time.Millisecond) - } - status := m.Run() - ctx := context.Background() - _ = c.Server.Shutdown(ctx) - os.Exit(status) -} diff --git a/pkg/compliance/v1_0_0/check_test.go b/pkg/compliance/v1_0_0/check_test.go new file mode 100644 index 00000000..337b0ccf --- /dev/null +++ b/pkg/compliance/v1_0_0/check_test.go @@ -0,0 +1,57 @@ +package v1_0_0_test + +import ( + "context" + "fmt" + "io/ioutil" + "os" + "testing" + "time" + + "github.com/anuvu/zot/pkg/api" + "github.com/anuvu/zot/pkg/compliance" + "github.com/anuvu/zot/pkg/compliance/v1_0_0" + "gopkg.in/resty.v1" +) + +const ( + Address = "127.0.0.1" + Port = "8080" +) + +func TestWorkflows(t *testing.T) { + v1_0_0.CheckWorkflows(t, &compliance.Config{Address: Address, Port: Port}) +} + +func TestMain(m *testing.M) { + config := api.NewConfig() + config.HTTP.Address = Address + config.HTTP.Port = Port + c := api.NewController(config) + dir, err := ioutil.TempDir("", "oci-repo-test") + if err != nil { + panic(err) + } + //defer os.RemoveAll(dir) + c.Config.Storage.RootDirectory = dir + go func() { + // this blocks + if err := c.Run(); err != nil { + return + } + }() + + BaseURL := fmt.Sprintf("http://%s:%s", Address, Port) + for { + // poll until ready + resp, _ := resty.R().Get(BaseURL) + if resp.StatusCode() == 404 { + break + } + time.Sleep(100 * time.Millisecond) + } + status := m.Run() + ctx := context.Background() + _ = c.Server.Shutdown(ctx) + os.Exit(status) +}