0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2024-12-16 21:56:37 -05:00

Merge pull request #30 from rchincha/compliance

compliance: initial commit
This commit is contained in:
Serge Hallyn 2019-10-14 13:24:49 -05:00 committed by GitHub
commit 51db55c890
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 249 additions and 116 deletions

View file

@ -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 [![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 **zot** is a vendor-neutral OCI image repository server purely based on
[OCI Distribution Specification] [OCI Distribution Specification](https://github.com/opencontainers/distribution-spec).
(https://github.com/opencontainers/distribution-spec).
* Conforms to [OCI distribution spec](https://github.com/opencontainers/distribution-spec) APIs * 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 * 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) * Authentication via TLS mutual authentication and HTTP *BASIC* (local _htpasswd_ and LDAP)
* Doesn't require _root_ privileges * Doesn't require _root_ privileges
* Swagger based documentation * Swagger based documentation
* Can run compliance checks against registries
* Released under Apache 2.0 License * Released under Apache 2.0 License
# Presentations # Presentations
@ -39,12 +39,19 @@ make
Build artifacts are in bin/ Build artifacts are in bin/
# Running # Serving
```
bin/zot serve _config-file_ bin/zot serve _config-file_
```
Examples of config files are available in [examples/](examples/) dir. Examples of config files are available in [examples/](examples/) dir.
# Compliance checks
```
bin/zot -H hostIP -P port [-V "all"]
```
# Ecosystem # Ecosystem
## skopeo ## skopeo

View file

@ -34,10 +34,7 @@ go_library(
go_test( go_test(
name = "go_default_test", name = "go_default_test",
timeout = "short", timeout = "short",
srcs = [ srcs = ["controller_test.go"],
"controller_test.go",
"routes_test.go",
],
data = [ data = [
"//:exported_testdata", "//:exported_testdata",
], ],
@ -45,8 +42,6 @@ go_test(
race = "on", race = "on",
deps = [ deps = [
"@com_github_nmcclain_ldap//:go_default_library", "@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", "@com_github_smartystreets_goconvey//convey:go_default_library",
"@in_gopkg_resty_v1//:go_default_library", "@in_gopkg_resty_v1//:go_default_library",
], ],

View file

@ -28,10 +28,13 @@ import (
httpSwagger "github.com/swaggo/http-swagger" httpSwagger "github.com/swaggo/http-swagger"
) )
const RoutePrefix = "/v2" const (
const DistAPIVersion = "Docker-Distribution-API-Version" RoutePrefix = "/v2"
const DistContentDigestKey = "Docker-Content-Digest" DistAPIVersion = "Docker-Distribution-API-Version"
const BlobUploadUUID = "Blob-Upload-UUID" DistContentDigestKey = "Docker-Content-Digest"
BlobUploadUUID = "Blob-Upload-UUID"
DefaultMediaType = "application/json"
)
type RouteHandler struct { type RouteHandler struct {
c *Controller c *Controller
@ -823,7 +826,7 @@ func WriteJSON(w http.ResponseWriter, status int, data interface{}) {
if err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError) 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) { func WriteData(w http.ResponseWriter, status int, mediaType string, data []byte) {

View file

@ -8,6 +8,8 @@ go_library(
deps = [ deps = [
"//errors:go_default_library", "//errors:go_default_library",
"//pkg/api: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", "//pkg/storage:go_default_library",
"@com_github_mitchellh_mapstructure//:go_default_library", "@com_github_mitchellh_mapstructure//:go_default_library",
"@com_github_opencontainers_distribution_spec//:go_default_library", "@com_github_opencontainers_distribution_spec//:go_default_library",

View file

@ -1,14 +1,19 @@
package cli package cli
import ( import (
"testing"
"github.com/anuvu/zot/errors" "github.com/anuvu/zot/errors"
"github.com/anuvu/zot/pkg/api" "github.com/anuvu/zot/pkg/api"
"github.com/anuvu/zot/pkg/compliance"
"github.com/anuvu/zot/pkg/storage" "github.com/anuvu/zot/pkg/storage"
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
dspec "github.com/opencontainers/distribution-spec" dspec "github.com/opencontainers/distribution-spec"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
"github.com/anuvu/zot/pkg/compliance/v1_0_0"
) )
// metadataConfig reports metadata after parsing, which we use to track // metadataConfig reports metadata after parsing, which we use to track
@ -23,6 +28,7 @@ func NewRootCmd() *cobra.Command {
showVersion := false showVersion := false
config := api.NewConfig() config := api.NewConfig()
// "serve"
serveCmd := &cobra.Command{ serveCmd := &cobra.Command{
Use: "serve <config>", Use: "serve <config>",
Aliases: []string{"serve"}, Aliases: []string{"serve"},
@ -53,6 +59,7 @@ func NewRootCmd() *cobra.Command {
}, },
} }
// "garbage-collect"
gcDelUntagged := false gcDelUntagged := false
gcDryRun := false gcDryRun := false
@ -79,6 +86,37 @@ func NewRootCmd() *cobra.Command {
gcCmd.Flags().BoolVarP(&gcDryRun, "dry-run", "d", false, gcCmd.Flags().BoolVarP(&gcDryRun, "dry-run", "d", false,
"do everything except remove the blobs") "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{ rootCmd := &cobra.Command{
Use: "zot", Use: "zot",
Short: "`zot`", Short: "`zot`",
@ -93,6 +131,7 @@ func NewRootCmd() *cobra.Command {
rootCmd.AddCommand(serveCmd) rootCmd.AddCommand(serveCmd)
rootCmd.AddCommand(gcCmd) rootCmd.AddCommand(gcCmd)
rootCmd.AddCommand(complianceCmd)
rootCmd.Flags().BoolVarP(&showVersion, "version", "v", false, "show the version and exit") rootCmd.Flags().BoolVarP(&showVersion, "version", "v", false, "show the version and exit")
return rootCmd return rootCmd

View file

@ -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"],
)

11
pkg/compliance/config.go Normal file
View file

@ -0,0 +1,11 @@
package compliance
type Config struct {
Address string
Port string
Version string
}
func NewConfig() *Config {
return &Config{}
}

View file

@ -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",
],
)

View file

@ -1,96 +1,102 @@
// nolint (dupl) //nolint (dupl)
package api_test package v1_0_0
import ( import (
"bytes" "bytes"
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil"
"os"
"testing" "testing"
"time"
"github.com/anuvu/zot/pkg/api" "github.com/anuvu/zot/pkg/api"
"github.com/anuvu/zot/pkg/compliance"
godigest "github.com/opencontainers/go-digest" godigest "github.com/opencontainers/go-digest"
ispec "github.com/opencontainers/image-spec/specs-go/v1" ispec "github.com/opencontainers/image-spec/specs-go/v1"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
"gopkg.in/resty.v1" "gopkg.in/resty.v1"
) )
const ( func CheckWorkflows(t *testing.T, config *compliance.Config) {
DefaultContentType = "application/json; charset=utf-8" if config == nil || config.Address == "" || config.Port == "" {
BaseURL = "http://127.0.0.1:8080" 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("Make API calls to the controller", t, func(c C) {
Convey("check version", func() { Convey("Check version", func() {
resp, err := resty.R().Get(BaseURL + "/v2/") Print("\nCheck version")
resp, err := resty.R().Get(baseURL + "/v2/")
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200) So(resp.StatusCode(), ShouldEqual, 200)
}) })
Convey("Get repository catalog", func() { 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200) So(resp.StatusCode(), ShouldEqual, 200)
So(resp.String(), ShouldNotBeEmpty) 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 var repoList api.RepositoryList
err = json.Unmarshal(resp.Body(), &repoList) err = json.Unmarshal(resp.Body(), &repoList)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(len(repoList.Repositories), ShouldEqual, 0) So(len(repoList.Repositories), ShouldEqual, 0)
// after newly created upload should succeed // 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 202) So(resp.StatusCode(), ShouldEqual, 202)
// after newly created upload should succeed // 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 202) 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200) So(resp.StatusCode(), ShouldEqual, 200)
So(resp.String(), ShouldNotBeEmpty) So(resp.String(), ShouldNotBeEmpty)
r := resp.Result().(*api.RepositoryList) r := resp.Result().(*api.RepositoryList)
So(len(r.Repositories), ShouldBeGreaterThan, 0)
So(r.Repositories[0], ShouldEqual, "a/b/c/d") So(r.Repositories[0], ShouldEqual, "a/b/c/d")
So(r.Repositories[1], ShouldEqual, "z") So(r.Repositories[1], ShouldEqual, "z")
}) })
Convey("Get images in a repository", func() { Convey("Get images in a repository", func() {
Print("\nGet images in a repository")
// non-existent repository should fail // non-existent repository should fail
resp, err := resty.R().Get(BaseURL + "/v2/repo/tags/list") resp, err := resty.R().Get(baseURL + "/v2/repo/tags/list")
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 404) So(resp.StatusCode(), ShouldEqual, 404)
So(resp.String(), ShouldNotBeEmpty) So(resp.String(), ShouldNotBeEmpty)
// after newly created upload should succeed // after newly created upload should succeed
resp, err = resty.R().Post(BaseURL + "/v2/repo/blobs/uploads/") resp, err = resty.R().Post(baseURL + "/v2/repo/blobs/uploads/")
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 202) So(resp.StatusCode(), ShouldEqual, 202)
resp, err = resty.R().Get(BaseURL + "/v2/repo/tags/list") resp, err = resty.R().Get(baseURL + "/v2/repo/tags/list")
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200) So(resp.StatusCode(), ShouldEqual, 200)
So(resp.String(), ShouldNotBeEmpty) So(resp.String(), ShouldNotBeEmpty)
}) })
Convey("Monolithic blob upload", func() { 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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 202) So(resp.StatusCode(), ShouldEqual, 202)
loc := resp.Header().Get("Location") loc := resp.Header().Get("Location")
So(loc, ShouldNotBeEmpty) So(loc, ShouldNotBeEmpty)
resp, err = resty.R().Get(BaseURL + loc) resp, err = resty.R().Get(baseURL + loc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 204) So(resp.StatusCode(), ShouldEqual, 204)
resp, err = resty.R().Get(BaseURL + "/v2/repo/tags/list") resp, err = resty.R().Get(baseURL + "/v2/repo/tags/list")
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200) So(resp.StatusCode(), ShouldEqual, 200)
So(resp.String(), ShouldNotBeEmpty) So(resp.String(), ShouldNotBeEmpty)
@ -99,21 +105,21 @@ func TestAPI(t *testing.T) {
content := []byte("this is a blob") content := []byte("this is a blob")
digest := godigest.FromBytes(content) digest := godigest.FromBytes(content)
So(digest, ShouldNotBeNil) So(digest, ShouldNotBeNil)
resp, err = resty.R().Put(BaseURL + loc) resp, err = resty.R().Put(baseURL + loc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 400) So(resp.StatusCode(), ShouldEqual, 400)
// without the Content-Length should fail // without the Content-Length should fail
resp, err = resty.R().SetQueryParam("digest", digest.String()).Put(BaseURL + loc) resp, err = resty.R().SetQueryParam("digest", digest.String()).Put(baseURL + loc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 400) So(resp.StatusCode(), ShouldEqual, 400)
// without any data to send, should fail // without any data to send, should fail
resp, err = resty.R().SetQueryParam("digest", digest.String()). resp, err = resty.R().SetQueryParam("digest", digest.String()).
SetHeader("Content-Type", "application/octet-stream").Put(BaseURL + loc) SetHeader("Content-Type", "application/octet-stream").Put(baseURL + loc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 400) So(resp.StatusCode(), ShouldEqual, 400)
// monolithic blob upload: success // monolithic blob upload: success
resp, err = resty.R().SetQueryParam("digest", digest.String()). resp, err = resty.R().SetQueryParam("digest", digest.String()).
SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(BaseURL + loc) SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(baseURL + loc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 201) So(resp.StatusCode(), ShouldEqual, 201)
blobLoc := resp.Header().Get("Location") blobLoc := resp.Header().Get("Location")
@ -121,27 +127,28 @@ func TestAPI(t *testing.T) {
So(resp.Header().Get("Content-Length"), ShouldEqual, "0") So(resp.Header().Get("Content-Length"), ShouldEqual, "0")
So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty) So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty)
// upload reference should now be removed // upload reference should now be removed
resp, err = resty.R().Get(BaseURL + loc) resp, err = resty.R().Get(baseURL + loc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 404) So(resp.StatusCode(), ShouldEqual, 404)
// blob reference should be accessible // blob reference should be accessible
resp, err = resty.R().Get(BaseURL + blobLoc) resp, err = resty.R().Get(baseURL + blobLoc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200) So(resp.StatusCode(), ShouldEqual, 200)
}) })
Convey("Monolithic blob upload with multiple name components", func() { Convey("Monolithic blob upload with multiple name components", func() {
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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 202) So(resp.StatusCode(), ShouldEqual, 202)
loc := resp.Header().Get("Location") loc := resp.Header().Get("Location")
So(loc, ShouldNotBeEmpty) So(loc, ShouldNotBeEmpty)
resp, err = resty.R().Get(BaseURL + loc) resp, err = resty.R().Get(baseURL + loc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 204) So(resp.StatusCode(), ShouldEqual, 204)
resp, err = resty.R().Get(BaseURL + "/v2/repo1/repo2/repo3/tags/list") resp, err = resty.R().Get(baseURL + "/v2/repo1/repo2/repo3/tags/list")
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200) So(resp.StatusCode(), ShouldEqual, 200)
So(resp.String(), ShouldNotBeEmpty) So(resp.String(), ShouldNotBeEmpty)
@ -150,21 +157,21 @@ func TestAPI(t *testing.T) {
content := []byte("this is a blob") content := []byte("this is a blob")
digest := godigest.FromBytes(content) digest := godigest.FromBytes(content)
So(digest, ShouldNotBeNil) So(digest, ShouldNotBeNil)
resp, err = resty.R().Put(BaseURL + loc) resp, err = resty.R().Put(baseURL + loc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 400) So(resp.StatusCode(), ShouldEqual, 400)
// without the Content-Length should fail // without the Content-Length should fail
resp, err = resty.R().SetQueryParam("digest", digest.String()).Put(BaseURL + loc) resp, err = resty.R().SetQueryParam("digest", digest.String()).Put(baseURL + loc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 400) So(resp.StatusCode(), ShouldEqual, 400)
// without any data to send, should fail // without any data to send, should fail
resp, err = resty.R().SetQueryParam("digest", digest.String()). resp, err = resty.R().SetQueryParam("digest", digest.String()).
SetHeader("Content-Type", "application/octet-stream").Put(BaseURL + loc) SetHeader("Content-Type", "application/octet-stream").Put(baseURL + loc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 400) So(resp.StatusCode(), ShouldEqual, 400)
// monolithic blob upload: success // monolithic blob upload: success
resp, err = resty.R().SetQueryParam("digest", digest.String()). resp, err = resty.R().SetQueryParam("digest", digest.String()).
SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(BaseURL + loc) SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(baseURL + loc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 201) So(resp.StatusCode(), ShouldEqual, 201)
blobLoc := resp.Header().Get("Location") blobLoc := resp.Header().Get("Location")
@ -172,17 +179,18 @@ func TestAPI(t *testing.T) {
So(resp.Header().Get("Content-Length"), ShouldEqual, "0") So(resp.Header().Get("Content-Length"), ShouldEqual, "0")
So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty) So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty)
// upload reference should now be removed // upload reference should now be removed
resp, err = resty.R().Get(BaseURL + loc) resp, err = resty.R().Get(baseURL + loc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 404) So(resp.StatusCode(), ShouldEqual, 404)
// blob reference should be accessible // blob reference should be accessible
resp, err = resty.R().Get(BaseURL + blobLoc) resp, err = resty.R().Get(baseURL + blobLoc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200) So(resp.StatusCode(), ShouldEqual, 200)
}) })
Convey("Chunked blob upload", func() { Convey("Chunked blob upload", func() {
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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 202) So(resp.StatusCode(), ShouldEqual, 202)
loc := resp.Header().Get("Location") loc := resp.Header().Get("Location")
@ -197,12 +205,12 @@ func TestAPI(t *testing.T) {
// write first chunk // write first chunk
contentRange := fmt.Sprintf("%d-%d", 0, len(chunk1)) contentRange := fmt.Sprintf("%d-%d", 0, len(chunk1))
resp, err = resty.R().SetHeader("Content-Type", "application/octet-stream"). resp, err = resty.R().SetHeader("Content-Type", "application/octet-stream").
SetHeader("Content-Range", contentRange).SetBody(chunk1).Patch(BaseURL + loc) SetHeader("Content-Range", contentRange).SetBody(chunk1).Patch(baseURL + loc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 202) So(resp.StatusCode(), ShouldEqual, 202)
// check progress // check progress
resp, err = resty.R().Get(BaseURL + loc) resp, err = resty.R().Get(baseURL + loc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 204) So(resp.StatusCode(), ShouldEqual, 204)
r := resp.Header().Get("Range") r := resp.Header().Get("Range")
@ -212,7 +220,7 @@ func TestAPI(t *testing.T) {
// write same chunk should fail // write same chunk should fail
contentRange = fmt.Sprintf("%d-%d", 0, len(chunk1)) contentRange = fmt.Sprintf("%d-%d", 0, len(chunk1))
resp, err = resty.R().SetHeader("Content-Type", "application/octet-stream"). resp, err = resty.R().SetHeader("Content-Type", "application/octet-stream").
SetHeader("Content-Range", contentRange).SetBody(chunk1).Patch(BaseURL + loc) SetHeader("Content-Range", contentRange).SetBody(chunk1).Patch(baseURL + loc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 400) So(resp.StatusCode(), ShouldEqual, 400)
So(resp.String(), ShouldNotBeEmpty) So(resp.String(), ShouldNotBeEmpty)
@ -229,7 +237,7 @@ func TestAPI(t *testing.T) {
contentRange = fmt.Sprintf("%d-%d", len(chunk1), len(buf.Bytes())) contentRange = fmt.Sprintf("%d-%d", len(chunk1), len(buf.Bytes()))
resp, err = resty.R().SetQueryParam("digest", digest.String()). resp, err = resty.R().SetQueryParam("digest", digest.String()).
SetHeader("Content-Range", contentRange). SetHeader("Content-Range", contentRange).
SetHeader("Content-Type", "application/octet-stream").SetBody(chunk2).Put(BaseURL + loc) SetHeader("Content-Type", "application/octet-stream").SetBody(chunk2).Put(baseURL + loc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 201) So(resp.StatusCode(), ShouldEqual, 201)
blobLoc := resp.Header().Get("Location") blobLoc := resp.Header().Get("Location")
@ -239,17 +247,18 @@ func TestAPI(t *testing.T) {
So(resp.Header().Get("Content-Length"), ShouldEqual, "0") So(resp.Header().Get("Content-Length"), ShouldEqual, "0")
So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty) So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty)
// upload reference should now be removed // upload reference should now be removed
resp, err = resty.R().Get(BaseURL + loc) resp, err = resty.R().Get(baseURL + loc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 404) So(resp.StatusCode(), ShouldEqual, 404)
// blob reference should be accessible // blob reference should be accessible
resp, err = resty.R().Get(BaseURL + blobLoc) resp, err = resty.R().Get(baseURL + blobLoc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200) So(resp.StatusCode(), ShouldEqual, 200)
}) })
Convey("Chunked blob upload with multiple name components", func() { Convey("Chunked blob upload with multiple name components", func() {
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(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 202) So(resp.StatusCode(), ShouldEqual, 202)
loc := resp.Header().Get("Location") loc := resp.Header().Get("Location")
@ -264,12 +273,12 @@ func TestAPI(t *testing.T) {
// write first chunk // write first chunk
contentRange := fmt.Sprintf("%d-%d", 0, len(chunk1)) contentRange := fmt.Sprintf("%d-%d", 0, len(chunk1))
resp, err = resty.R().SetHeader("Content-Type", "application/octet-stream"). resp, err = resty.R().SetHeader("Content-Type", "application/octet-stream").
SetHeader("Content-Range", contentRange).SetBody(chunk1).Patch(BaseURL + loc) SetHeader("Content-Range", contentRange).SetBody(chunk1).Patch(baseURL + loc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 202) So(resp.StatusCode(), ShouldEqual, 202)
// check progress // check progress
resp, err = resty.R().Get(BaseURL + loc) resp, err = resty.R().Get(baseURL + loc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 204) So(resp.StatusCode(), ShouldEqual, 204)
r := resp.Header().Get("Range") r := resp.Header().Get("Range")
@ -279,7 +288,7 @@ func TestAPI(t *testing.T) {
// write same chunk should fail // write same chunk should fail
contentRange = fmt.Sprintf("%d-%d", 0, len(chunk1)) contentRange = fmt.Sprintf("%d-%d", 0, len(chunk1))
resp, err = resty.R().SetHeader("Content-Type", "application/octet-stream"). resp, err = resty.R().SetHeader("Content-Type", "application/octet-stream").
SetHeader("Content-Range", contentRange).SetBody(chunk1).Patch(BaseURL + loc) SetHeader("Content-Range", contentRange).SetBody(chunk1).Patch(baseURL + loc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 400) So(resp.StatusCode(), ShouldEqual, 400)
So(resp.String(), ShouldNotBeEmpty) So(resp.String(), ShouldNotBeEmpty)
@ -296,7 +305,7 @@ func TestAPI(t *testing.T) {
contentRange = fmt.Sprintf("%d-%d", len(chunk1), len(buf.Bytes())) contentRange = fmt.Sprintf("%d-%d", len(chunk1), len(buf.Bytes()))
resp, err = resty.R().SetQueryParam("digest", digest.String()). resp, err = resty.R().SetQueryParam("digest", digest.String()).
SetHeader("Content-Range", contentRange). SetHeader("Content-Range", contentRange).
SetHeader("Content-Type", "application/octet-stream").SetBody(chunk2).Put(BaseURL + loc) SetHeader("Content-Type", "application/octet-stream").SetBody(chunk2).Put(baseURL + loc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 201) So(resp.StatusCode(), ShouldEqual, 201)
blobLoc := resp.Header().Get("Location") blobLoc := resp.Header().Get("Location")
@ -306,32 +315,34 @@ func TestAPI(t *testing.T) {
So(resp.Header().Get("Content-Length"), ShouldEqual, "0") So(resp.Header().Get("Content-Length"), ShouldEqual, "0")
So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty) So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty)
// upload reference should now be removed // upload reference should now be removed
resp, err = resty.R().Get(BaseURL + loc) resp, err = resty.R().Get(baseURL + loc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 404) So(resp.StatusCode(), ShouldEqual, 404)
// blob reference should be accessible // blob reference should be accessible
resp, err = resty.R().Get(BaseURL + blobLoc) resp, err = resty.R().Get(baseURL + blobLoc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200) So(resp.StatusCode(), ShouldEqual, 200)
}) })
Convey("Create and delete uploads", func() { Convey("Create and delete uploads", func() {
Print("\nCreate and delete uploads")
// create a upload // create a upload
resp, err := resty.R().Post(BaseURL + "/v2/repo/blobs/uploads/") resp, err := resty.R().Post(baseURL + "/v2/repo/blobs/uploads/")
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 202) So(resp.StatusCode(), ShouldEqual, 202)
loc := resp.Header().Get("Location") loc := resp.Header().Get("Location")
So(loc, ShouldNotBeEmpty) So(loc, ShouldNotBeEmpty)
// delete this upload // delete this upload
resp, err = resty.R().Delete(BaseURL + loc) resp, err = resty.R().Delete(baseURL + loc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200) So(resp.StatusCode(), ShouldEqual, 200)
}) })
Convey("Create and delete blobs", func() { Convey("Create and delete blobs", func() {
Print("\nCreate and delete blobs")
// create a upload // create a upload
resp, err := resty.R().Post(BaseURL + "/v2/repo/blobs/uploads/") resp, err := resty.R().Post(baseURL + "/v2/repo/blobs/uploads/")
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 202) So(resp.StatusCode(), ShouldEqual, 202)
loc := resp.Header().Get("Location") loc := resp.Header().Get("Location")
@ -342,7 +353,7 @@ func TestAPI(t *testing.T) {
So(digest, ShouldNotBeNil) So(digest, ShouldNotBeNil)
// monolithic blob upload // monolithic blob upload
resp, err = resty.R().SetQueryParam("digest", digest.String()). resp, err = resty.R().SetQueryParam("digest", digest.String()).
SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(BaseURL + loc) SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(baseURL + loc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 201) So(resp.StatusCode(), ShouldEqual, 201)
blobLoc := resp.Header().Get("Location") blobLoc := resp.Header().Get("Location")
@ -350,21 +361,22 @@ func TestAPI(t *testing.T) {
So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty) So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty)
// delete this blob // delete this blob
resp, err = resty.R().Delete(BaseURL + blobLoc) resp, err = resty.R().Delete(baseURL + blobLoc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 202) So(resp.StatusCode(), ShouldEqual, 202)
So(resp.Header().Get("Content-Length"), ShouldEqual, "0") So(resp.Header().Get("Content-Length"), ShouldEqual, "0")
}) })
Convey("Manifests", func() { Convey("Manifests", func() {
Print("\nManifests")
// create a blob/layer // create a blob/layer
resp, err := resty.R().Post(BaseURL + "/v2/repo/blobs/uploads/") resp, err := resty.R().Post(baseURL + "/v2/repo/blobs/uploads/")
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 202) So(resp.StatusCode(), ShouldEqual, 202)
loc := resp.Header().Get("Location") loc := resp.Header().Get("Location")
So(loc, ShouldNotBeEmpty) So(loc, ShouldNotBeEmpty)
resp, err = resty.R().Get(BaseURL + loc) resp, err = resty.R().Get(baseURL + loc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 204) So(resp.StatusCode(), ShouldEqual, 204)
content := []byte("this is a blob") content := []byte("this is a blob")
@ -372,7 +384,7 @@ func TestAPI(t *testing.T) {
So(digest, ShouldNotBeNil) So(digest, ShouldNotBeNil)
// monolithic blob upload: success // monolithic blob upload: success
resp, err = resty.R().SetQueryParam("digest", digest.String()). resp, err = resty.R().SetQueryParam("digest", digest.String()).
SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(BaseURL + loc) SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(baseURL + loc)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 201) So(resp.StatusCode(), ShouldEqual, 201)
blobLoc := resp.Header().Get("Location") blobLoc := resp.Header().Get("Location")
@ -387,7 +399,7 @@ func TestAPI(t *testing.T) {
digest = godigest.FromBytes(content) digest = godigest.FromBytes(content)
So(digest, ShouldNotBeNil) So(digest, ShouldNotBeNil)
resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json"). resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json").
SetBody(content).Put(BaseURL + "/v2/repo/manifests/test:1.0") SetBody(content).Put(baseURL + "/v2/repo/manifests/test:1.0")
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 201) So(resp.StatusCode(), ShouldEqual, 201)
d := resp.Header().Get(api.DistContentDigestKey) d := resp.Header().Get(api.DistContentDigestKey)
@ -395,76 +407,47 @@ func TestAPI(t *testing.T) {
So(d, ShouldEqual, digest.String()) So(d, ShouldEqual, digest.String())
// check/get by tag // check/get by tag
resp, err = resty.R().Head(BaseURL + "/v2/repo/manifests/test:1.0") resp, err = resty.R().Head(baseURL + "/v2/repo/manifests/test:1.0")
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200) So(resp.StatusCode(), ShouldEqual, 200)
resp, err = resty.R().Get(BaseURL + "/v2/repo/manifests/test:1.0") resp, err = resty.R().Get(baseURL + "/v2/repo/manifests/test:1.0")
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200) So(resp.StatusCode(), ShouldEqual, 200)
So(resp.Body(), ShouldNotBeEmpty) So(resp.Body(), ShouldNotBeEmpty)
// check/get by reference // check/get by reference
resp, err = resty.R().Head(BaseURL + "/v2/repo/manifests/" + digest.String()) resp, err = resty.R().Head(baseURL + "/v2/repo/manifests/" + digest.String())
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200) So(resp.StatusCode(), ShouldEqual, 200)
resp, err = resty.R().Get(BaseURL + "/v2/repo/manifests/" + digest.String()) resp, err = resty.R().Get(baseURL + "/v2/repo/manifests/" + digest.String())
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200) So(resp.StatusCode(), ShouldEqual, 200)
So(resp.Body(), ShouldNotBeEmpty) So(resp.Body(), ShouldNotBeEmpty)
// delete manifest // delete manifest
resp, err = resty.R().Delete(BaseURL + "/v2/repo/manifests/test:1.0") resp, err = resty.R().Delete(baseURL + "/v2/repo/manifests/test:1.0")
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200) So(resp.StatusCode(), ShouldEqual, 200)
// delete again should fail // delete again should fail
resp, err = resty.R().Delete(BaseURL + "/v2/repo/manifests/" + digest.String()) resp, err = resty.R().Delete(baseURL + "/v2/repo/manifests/" + digest.String())
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 404) So(resp.StatusCode(), ShouldEqual, 404)
// check/get by tag // check/get by tag
resp, err = resty.R().Head(BaseURL + "/v2/repo/manifests/test:1.0") resp, err = resty.R().Head(baseURL + "/v2/repo/manifests/test:1.0")
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 404) So(resp.StatusCode(), ShouldEqual, 404)
resp, err = resty.R().Get(BaseURL + "/v2/repo/manifests/test:1.0") resp, err = resty.R().Get(baseURL + "/v2/repo/manifests/test:1.0")
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 404) So(resp.StatusCode(), ShouldEqual, 404)
So(resp.Body(), ShouldNotBeEmpty) So(resp.Body(), ShouldNotBeEmpty)
// check/get by reference // check/get by reference
resp, err = resty.R().Head(BaseURL + "/v2/repo/manifests/" + digest.String()) resp, err = resty.R().Head(baseURL + "/v2/repo/manifests/" + digest.String())
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 404) So(resp.StatusCode(), ShouldEqual, 404)
resp, err = resty.R().Get(BaseURL + "/v2/repo/manifests/" + digest.String()) resp, err = resty.R().Get(baseURL + "/v2/repo/manifests/" + digest.String())
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 404) So(resp.StatusCode(), ShouldEqual, 404)
So(resp.Body(), ShouldNotBeEmpty) So(resp.Body(), ShouldNotBeEmpty)
}) })
}) })
} }
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)
}

View file

@ -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)
}