image level lint: enforce manifest mandatory annotations

closes #536

Signed-off-by: Lisca Ana-Roberta <ana.kagome@yahoo.com>
This commit is contained in:
Lisca Ana-Roberta
2022-06-24 16:08:47 +03:00
committed by Andrei Aaron
parent 3d72dad507
commit 87fc941b3c
34 changed files with 1391 additions and 110 deletions
+6
View File
@@ -11,6 +11,12 @@ type ExtensionConfig struct {
Sync *sync.Config
Metrics *MetricsConfig
Scrub *ScrubConfig
Lint *LintConfig
}
type LintConfig struct {
Enabled *bool
MandatoryAnnotations []string
}
type SearchConfig struct {
+18
View File
@@ -0,0 +1,18 @@
//go:build lint
// +build lint
package extensions
import (
"zotregistry.io/zot/pkg/api/config"
"zotregistry.io/zot/pkg/extensions/lint"
"zotregistry.io/zot/pkg/log"
)
func GetLinter(config *config.Config, log log.Logger) *lint.Linter {
if config.Extensions == nil {
return lint.NewLinter(nil, log)
}
return lint.NewLinter(config.Extensions.Lint, log)
}
@@ -0,0 +1,17 @@
//go:build !lint
// +build !lint
package extensions
import (
"zotregistry.io/zot/pkg/api/config"
"zotregistry.io/zot/pkg/extensions/lint"
"zotregistry.io/zot/pkg/log"
)
func GetLinter(config *config.Config, log log.Logger) *lint.Linter {
log.Warn().Msg("lint extension is disabled because given zot binary doesn't" +
"include this feature please build a binary that does so")
return nil
}
+17
View File
@@ -0,0 +1,17 @@
//go:build !lint
// +build !lint
package lint
import (
godigest "github.com/opencontainers/go-digest"
"zotregistry.io/zot/pkg/storage"
)
type Linter struct{}
func (linter *Linter) Lint(repo string, manifestDigest godigest.Digest,
imageStore storage.ImageStore,
) (bool, error) {
return true, nil
}
+76
View File
@@ -0,0 +1,76 @@
//go:build lint
// +build lint
package lint
import (
"encoding/json"
godigest "github.com/opencontainers/go-digest"
ispec "github.com/opencontainers/image-spec/specs-go/v1"
"zotregistry.io/zot/pkg/extensions/config"
"zotregistry.io/zot/pkg/log"
"zotregistry.io/zot/pkg/storage"
)
type Linter struct {
config *config.LintConfig
log log.Logger
}
func NewLinter(config *config.LintConfig, log log.Logger) *Linter {
return &Linter{
config: config,
log: log,
}
}
func (linter *Linter) CheckMandatoryAnnotations(repo string, manifestDigest godigest.Digest,
imgStore storage.ImageStore,
) (bool, error) {
if linter.config == nil {
return true, nil
}
if (linter.config != nil && !*linter.config.Enabled) || len(linter.config.MandatoryAnnotations) == 0 {
return true, nil
}
mandatoryAnnotationsList := linter.config.MandatoryAnnotations
content, err := imgStore.GetBlobContent(repo, string(manifestDigest))
if err != nil {
linter.log.Error().Err(err).Msg("linter: unable to get image manifest")
return false, err
}
var manifest ispec.Manifest
if err := json.Unmarshal(content, &manifest); err != nil {
linter.log.Error().Err(err).Msg("linter: couldn't unmarshal manifest JSON")
return false, err
}
annotations := manifest.Annotations
for _, annot := range mandatoryAnnotationsList {
_, found := annotations[annot]
if !found {
// if annotations are not found, return false but it's not an error
linter.log.Error().Msgf("linter: missing %s annotations", annot)
return false, nil
}
}
return true, nil
}
func (linter *Linter) Lint(repo string, manifestDigest godigest.Digest,
imageStore storage.ImageStore,
) (bool, error) {
return linter.CheckMandatoryAnnotations(repo, manifestDigest, imageStore)
}
+645
View File
@@ -0,0 +1,645 @@
//go:build lint
// +build lint
package lint_test
import (
"context"
"encoding/json"
"io/ioutil"
"net/http"
"os"
"path"
"testing"
godigest "github.com/opencontainers/go-digest"
ispec "github.com/opencontainers/image-spec/specs-go/v1"
. "github.com/smartystreets/goconvey/convey"
"gopkg.in/resty.v1"
"zotregistry.io/zot/pkg/api"
"zotregistry.io/zot/pkg/api/config"
extconf "zotregistry.io/zot/pkg/extensions/config"
"zotregistry.io/zot/pkg/extensions/lint"
"zotregistry.io/zot/pkg/extensions/monitoring"
"zotregistry.io/zot/pkg/log"
"zotregistry.io/zot/pkg/storage"
"zotregistry.io/zot/pkg/test"
)
const (
username = "test"
passphrase = "test"
ServerCert = "../../test/data/server.cert"
ServerKey = "../../test/data/server.key"
CACert = "../../test/data/ca.crt"
AuthorizedNamespace = "everyone/isallowed"
UnauthorizedNamespace = "fortknox/notallowed"
ALICE = "alice"
AuthorizationNamespace = "authz/image"
AuthorizationAllRepos = "**"
)
func TestVerifyMandatoryAnnotations(t *testing.T) {
// nolint: dupl
Convey("Mandatory annotations disabled", t, func() {
port := test.GetFreePort()
baseURL := test.GetBaseURL(port)
conf := config.New()
conf.HTTP.Port = port
enabled := false
conf.Extensions = &extconf.ExtensionConfig{Lint: &extconf.LintConfig{}}
conf.Extensions.Lint.MandatoryAnnotations = []string{}
conf.Extensions.Lint.Enabled = &enabled
ctlr := api.NewController(conf)
dir := t.TempDir()
err := test.CopyFiles("../../../test/data", dir)
if err != nil {
panic(err)
}
ctlr.Config.Storage.RootDirectory = dir
go startServer(ctlr)
defer stopServer(ctlr)
test.WaitTillServerReady(baseURL)
resp, err := resty.R().SetBasicAuth(username, passphrase).
Get(baseURL + "/v2/zot-test/manifests/0.0.1")
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
manifestBlob := resp.Body()
var manifest ispec.Manifest
err = json.Unmarshal(manifestBlob, &manifest)
So(err, ShouldBeNil)
manifest.SchemaVersion = 2
content, err := json.Marshal(manifest)
So(err, ShouldBeNil)
resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json").
SetBody(content).Put(baseURL + "/v2/zot-test/manifests/0.0.1")
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusCreated)
})
// nolint: dupl
Convey("Mandatory annotations enabled, but no list in config", t, func() {
port := test.GetFreePort()
baseURL := test.GetBaseURL(port)
conf := config.New()
conf.HTTP.Port = port
enabled := true
conf.Extensions = &extconf.ExtensionConfig{Lint: &extconf.LintConfig{}}
conf.Extensions.Lint.MandatoryAnnotations = []string{}
conf.Extensions.Lint.Enabled = &enabled
ctlr := api.NewController(conf)
dir := t.TempDir()
err := test.CopyFiles("../../../test/data", dir)
if err != nil {
panic(err)
}
ctlr.Config.Storage.RootDirectory = dir
go startServer(ctlr)
defer stopServer(ctlr)
test.WaitTillServerReady(baseURL)
resp, err := resty.R().SetBasicAuth(username, passphrase).
Get(baseURL + "/v2/zot-test/manifests/0.0.1")
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
manifestBlob := resp.Body()
var manifest ispec.Manifest
err = json.Unmarshal(manifestBlob, &manifest)
So(err, ShouldBeNil)
manifest.SchemaVersion = 2
content, err := json.Marshal(manifest)
So(err, ShouldBeNil)
resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json").
SetBody(content).Put(baseURL + "/v2/zot-test/manifests/0.0.1")
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusCreated)
})
Convey("Mandatory annotations verification passing", t, func() {
port := test.GetFreePort()
baseURL := test.GetBaseURL(port)
conf := config.New()
conf.HTTP.Port = port
enabled := true
conf.Extensions = &extconf.ExtensionConfig{Lint: &extconf.LintConfig{}}
conf.Extensions.Lint.MandatoryAnnotations = []string{}
conf.Extensions.Lint.Enabled = &enabled
conf.Extensions.Lint.MandatoryAnnotations = []string{"annotation1", "annotation2", "annotation3"}
ctlr := api.NewController(conf)
dir := t.TempDir()
err := test.CopyFiles("../../../test/data", dir)
if err != nil {
panic(err)
}
ctlr.Config.Storage.RootDirectory = dir
go startServer(ctlr)
defer stopServer(ctlr)
test.WaitTillServerReady(baseURL)
resp, err := resty.R().SetBasicAuth(username, passphrase).
Get(baseURL + "/v2/zot-test/manifests/0.0.1")
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
manifestBlob := resp.Body()
var manifest ispec.Manifest
err = json.Unmarshal(manifestBlob, &manifest)
So(err, ShouldBeNil)
manifest.Annotations = make(map[string]string)
manifest.Annotations["annotation1"] = "testPass1"
manifest.Annotations["annotation2"] = "testPass2"
manifest.Annotations["annotation3"] = "testPass3"
manifest.SchemaVersion = 2
content, err := json.Marshal(manifest)
So(err, ShouldBeNil)
resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json").
SetBody(content).Put(baseURL + "/v2/zot-test/manifests/0.0.1")
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusCreated)
})
Convey("Mandatory annotations incomplete in manifest", t, func() {
port := test.GetFreePort()
baseURL := test.GetBaseURL(port)
conf := config.New()
conf.HTTP.Port = port
enabled := true
conf.Extensions = &extconf.ExtensionConfig{Lint: &extconf.LintConfig{}}
conf.Extensions.Lint.MandatoryAnnotations = []string{}
conf.Extensions.Lint.Enabled = &enabled
conf.Extensions.Lint.MandatoryAnnotations = []string{"annotation1", "annotation2", "annotation3"}
ctlr := api.NewController(conf)
dir := t.TempDir()
err := test.CopyFiles("../../../test/data", dir)
if err != nil {
panic(err)
}
ctlr.Config.Storage.RootDirectory = dir
go startServer(ctlr)
defer stopServer(ctlr)
test.WaitTillServerReady(baseURL)
resp, err := resty.R().SetBasicAuth(username, passphrase).
Get(baseURL + "/v2/zot-test/manifests/0.0.1")
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
manifestBlob := resp.Body()
var manifest ispec.Manifest
err = json.Unmarshal(manifestBlob, &manifest)
So(err, ShouldBeNil)
manifest.Annotations = make(map[string]string)
manifest.Annotations["annotation1"] = "testFail1"
manifest.Annotations["annotation3"] = "testFail3"
manifest.SchemaVersion = 2
content, err := json.Marshal(manifest)
So(err, ShouldBeNil)
resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json").
SetBody(content).Put(baseURL + "/v2/zot-test/manifests/0.0.1")
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusBadRequest)
})
Convey("Mandatory annotations verification passing - more annotations than the mandatory list", t, func() {
port := test.GetFreePort()
baseURL := test.GetBaseURL(port)
conf := config.New()
conf.HTTP.Port = port
enabled := true
conf.Extensions = &extconf.ExtensionConfig{Lint: &extconf.LintConfig{}}
conf.Extensions.Lint.MandatoryAnnotations = []string{}
conf.Extensions.Lint.Enabled = &enabled
conf.Extensions.Lint.MandatoryAnnotations = []string{"annotation1", "annotation2", "annotation3"}
ctlr := api.NewController(conf)
dir := t.TempDir()
err := test.CopyFiles("../../../test/data", dir)
if err != nil {
panic(err)
}
ctlr.Config.Storage.RootDirectory = dir
go startServer(ctlr)
defer stopServer(ctlr)
test.WaitTillServerReady(baseURL)
resp, err := resty.R().SetBasicAuth(username, passphrase).
Get(baseURL + "/v2/zot-test/manifests/0.0.1")
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
manifestBlob := resp.Body()
var manifest ispec.Manifest
err = json.Unmarshal(manifestBlob, &manifest)
So(err, ShouldBeNil)
manifest.Annotations = make(map[string]string)
manifest.Annotations["annotation1"] = "testPassMore1"
manifest.Annotations["annotation2"] = "testPassMore2"
manifest.Annotations["annotation3"] = "testPassMore3"
manifest.Annotations["annotation4"] = "testPassMore4"
manifest.SchemaVersion = 2
content, err := json.Marshal(manifest)
So(err, ShouldBeNil)
resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json").
SetBody(content).Put(baseURL + "/v2/zot-test/manifests/0.0.1")
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusCreated)
})
}
func TestVerifyMandatoryAnnotationsFunction(t *testing.T) {
Convey("Mandatory annotations disabled", t, func() {
enabled := false
lintConfig := &extconf.LintConfig{
Enabled: &enabled,
MandatoryAnnotations: []string{},
}
dir := t.TempDir()
err := test.CopyFiles("../../../test/data", dir)
if err != nil {
panic(err)
}
var index ispec.Index
linter := lint.NewLinter(lintConfig, log.NewLogger("debug", ""))
imgStore := storage.NewImageStore(dir, false, 0, false, false,
log.NewLogger("debug", ""), monitoring.NewMetricsServer(false, log.NewLogger("debug", "")), linter)
indexContent, err := imgStore.GetIndexContent("zot-test")
So(err, ShouldBeNil)
err = json.Unmarshal(indexContent, &index)
So(err, ShouldBeNil)
manifestDigest := index.Manifests[0].Digest
pass, err := linter.CheckMandatoryAnnotations("zot-test", manifestDigest, imgStore)
So(err, ShouldBeNil)
So(pass, ShouldBeTrue)
})
Convey("Mandatory annotations enabled, but no list in config", t, func() {
enabled := true
lintConfig := &extconf.LintConfig{
Enabled: &enabled,
MandatoryAnnotations: []string{},
}
dir := t.TempDir()
err := test.CopyFiles("../../../test/data", dir)
if err != nil {
panic(err)
}
var index ispec.Index
linter := lint.NewLinter(lintConfig, log.NewLogger("debug", ""))
imgStore := storage.NewImageStore(dir, false, 0, false, false,
log.NewLogger("debug", ""), monitoring.NewMetricsServer(false, log.NewLogger("debug", "")), linter)
indexContent, err := imgStore.GetIndexContent("zot-test")
So(err, ShouldBeNil)
err = json.Unmarshal(indexContent, &index)
So(err, ShouldBeNil)
manifestDigest := index.Manifests[0].Digest
pass, err := linter.CheckMandatoryAnnotations("zot-test", manifestDigest, imgStore)
So(err, ShouldBeNil)
So(pass, ShouldBeTrue)
})
Convey("Mandatory annotations verification passing", t, func() {
enabled := true
lintConfig := &extconf.LintConfig{
Enabled: &enabled,
MandatoryAnnotations: []string{"annotation1", "annotation2", "annotation3"},
}
dir := t.TempDir()
err := test.CopyFiles("../../../test/data", dir)
if err != nil {
panic(err)
}
var index ispec.Index
buf, err := ioutil.ReadFile(path.Join(dir, "zot-test", "index.json"))
So(err, ShouldBeNil)
err = json.Unmarshal(buf, &index)
So(err, ShouldBeNil)
manifestDigest := index.Manifests[0].Digest
var manifest ispec.Manifest
buf, err = ioutil.ReadFile(path.Join(dir, "zot-test", "blobs",
manifestDigest.Algorithm().String(), manifestDigest.Encoded()))
So(err, ShouldBeNil)
err = json.Unmarshal(buf, &manifest)
So(err, ShouldBeNil)
manifest.Annotations = make(map[string]string)
manifest.Annotations["annotation1"] = "testPass1"
manifest.Annotations["annotation2"] = "testPass2"
manifest.Annotations["annotation3"] = "testPass3"
manifest.SchemaVersion = 2
content, err := json.Marshal(manifest)
So(err, ShouldBeNil)
So(content, ShouldNotBeNil)
digest := godigest.FromBytes(content)
So(digest, ShouldNotBeNil)
err = ioutil.WriteFile(path.Join(dir, "zot-test", "blobs",
digest.Algorithm().String(), digest.Encoded()), content, 0o600)
So(err, ShouldBeNil)
manifestDesc := ispec.Descriptor{
Size: int64(len(content)),
Digest: digest,
}
index.Manifests = append(index.Manifests, manifestDesc)
linter := lint.NewLinter(lintConfig, log.NewLogger("debug", ""))
imgStore := storage.NewImageStore(dir, false, 0, false, false,
log.NewLogger("debug", ""), monitoring.NewMetricsServer(false, log.NewLogger("debug", "")), linter)
pass, err := linter.CheckMandatoryAnnotations("zot-test", digest, imgStore)
So(err, ShouldBeNil)
So(pass, ShouldBeTrue)
})
Convey("Mandatory annotations incomplete in manifest", t, func() {
enabled := true
lintConfig := &extconf.LintConfig{
Enabled: &enabled,
MandatoryAnnotations: []string{"annotation1", "annotation2", "annotation3"},
}
dir := t.TempDir()
err := test.CopyFiles("../../../test/data", dir)
if err != nil {
panic(err)
}
var index ispec.Index
buf, err := ioutil.ReadFile(path.Join(dir, "zot-test", "index.json"))
So(err, ShouldBeNil)
err = json.Unmarshal(buf, &index)
So(err, ShouldBeNil)
manifestDigest := index.Manifests[0].Digest
var manifest ispec.Manifest
buf, err = ioutil.ReadFile(path.Join(dir, "zot-test", "blobs",
manifestDigest.Algorithm().String(), manifestDigest.Encoded()))
So(err, ShouldBeNil)
err = json.Unmarshal(buf, &manifest)
So(err, ShouldBeNil)
manifest.Annotations = make(map[string]string)
manifest.Annotations["annotation1"] = "test1"
manifest.Annotations["annotation3"] = "test3"
manifest.SchemaVersion = 2
content, err := json.Marshal(manifest)
So(err, ShouldBeNil)
So(content, ShouldNotBeNil)
digest := godigest.FromBytes(content)
So(digest, ShouldNotBeNil)
err = ioutil.WriteFile(path.Join(dir, "zot-test", "blobs",
digest.Algorithm().String(), digest.Encoded()), content, 0o600)
So(err, ShouldBeNil)
manifestDesc := ispec.Descriptor{
Size: int64(len(content)),
Digest: digest,
}
index.Manifests = append(index.Manifests, manifestDesc)
linter := lint.NewLinter(lintConfig, log.NewLogger("debug", ""))
imgStore := storage.NewImageStore(dir, false, 0, false, false,
log.NewLogger("debug", ""), monitoring.NewMetricsServer(false, log.NewLogger("debug", "")), linter)
pass, err := linter.CheckMandatoryAnnotations("zot-test", digest, imgStore)
So(err, ShouldBeNil)
So(pass, ShouldBeFalse)
})
Convey("Mandatory annotations verification passing - more annotations than the mandatory list", t, func() {
enabled := true
lintConfig := &extconf.LintConfig{
Enabled: &enabled,
MandatoryAnnotations: []string{"annotation1", "annotation2", "annotation3"},
}
dir := t.TempDir()
err := test.CopyFiles("../../../test/data", dir)
if err != nil {
panic(err)
}
var index ispec.Index
buf, err := ioutil.ReadFile(path.Join(dir, "zot-test", "index.json"))
So(err, ShouldBeNil)
err = json.Unmarshal(buf, &index)
So(err, ShouldBeNil)
manifestDigest := index.Manifests[0].Digest
var manifest ispec.Manifest
buf, err = ioutil.ReadFile(path.Join(dir, "zot-test", "blobs",
manifestDigest.Algorithm().String(), manifestDigest.Encoded()))
So(err, ShouldBeNil)
err = json.Unmarshal(buf, &manifest)
So(err, ShouldBeNil)
manifest.Annotations = make(map[string]string)
manifest.Annotations["annotation1"] = "testPassMore1"
manifest.Annotations["annotation2"] = "testPassMore2"
manifest.Annotations["annotation3"] = "testPassMore3"
manifest.Annotations["annotation4"] = "testPassMore4"
manifest.SchemaVersion = 2
content, err := json.Marshal(manifest)
So(err, ShouldBeNil)
So(content, ShouldNotBeNil)
digest := godigest.FromBytes(content)
So(digest, ShouldNotBeNil)
err = ioutil.WriteFile(path.Join(dir, "zot-test", "blobs",
digest.Algorithm().String(), digest.Encoded()), content, 0o600)
So(err, ShouldBeNil)
manifestDesc := ispec.Descriptor{
Size: int64(len(content)),
Digest: digest,
}
index.Manifests = append(index.Manifests, manifestDesc)
linter := lint.NewLinter(lintConfig, log.NewLogger("debug", ""))
imgStore := storage.NewImageStore(dir, false, 0, false, false,
log.NewLogger("debug", ""), monitoring.NewMetricsServer(false, log.NewLogger("debug", "")), linter)
pass, err := linter.CheckMandatoryAnnotations("zot-test", digest, imgStore)
So(err, ShouldBeNil)
So(pass, ShouldBeTrue)
})
Convey("Cannot unmarshal manifest", t, func() {
enabled := true
lintConfig := &extconf.LintConfig{
Enabled: &enabled,
MandatoryAnnotations: []string{"annotation1", "annotation2", "annotation3"},
}
dir := t.TempDir()
err := test.CopyFiles("../../../test/data", dir)
if err != nil {
panic(err)
}
var index ispec.Index
buf, err := ioutil.ReadFile(path.Join(dir, "zot-test", "index.json"))
So(err, ShouldBeNil)
err = json.Unmarshal(buf, &index)
So(err, ShouldBeNil)
manifestDigest := index.Manifests[0].Digest
var manifest ispec.Manifest
buf, err = ioutil.ReadFile(path.Join(dir, "zot-test", "blobs",
manifestDigest.Algorithm().String(), manifestDigest.Encoded()))
So(err, ShouldBeNil)
err = json.Unmarshal(buf, &manifest)
So(err, ShouldBeNil)
manifest.Annotations = make(map[string]string)
manifest.Annotations["annotation1"] = "testUnmarshal1"
manifest.Annotations["annotation2"] = "testUnmarshal2"
manifest.Annotations["annotation3"] = "testUnmarshal3"
manifest.SchemaVersion = 2
content, err := json.Marshal(manifest)
So(err, ShouldBeNil)
So(content, ShouldNotBeNil)
digest := godigest.FromBytes(content)
So(digest, ShouldNotBeNil)
err = ioutil.WriteFile(path.Join(dir, "zot-test", "blobs",
digest.Algorithm().String(), digest.Encoded()), content, 0o600)
So(err, ShouldBeNil)
manifestDesc := ispec.Descriptor{
Size: int64(len(content)),
Digest: digest,
}
index.Manifests = append(index.Manifests, manifestDesc)
linter := lint.NewLinter(lintConfig, log.NewLogger("debug", ""))
imgStore := storage.NewImageStore(dir, false, 0, false, false,
log.NewLogger("debug", ""), monitoring.NewMetricsServer(false, log.NewLogger("debug", "")), linter)
err = os.Chmod(path.Join(dir, "zot-test", "blobs"), 0o000)
if err != nil {
panic(err)
}
pass, err := linter.CheckMandatoryAnnotations("zot-test", digest, imgStore)
So(err, ShouldNotBeNil)
So(pass, ShouldBeFalse)
err = os.Chmod(path.Join(dir, "zot-test", "blobs"), 0o755)
if err != nil {
panic(err)
}
})
}
func startServer(c *api.Controller) {
// this blocks
ctx := context.Background()
if err := c.Run(ctx); err != nil {
return
}
}
func stopServer(c *api.Controller) {
ctx := context.Background()
_ = c.Server.Shutdown(ctx)
}
+19 -3
View File
@@ -224,10 +224,15 @@ func TestRunScrubRepo(t *testing.T) {
defer os.Remove(logFile.Name()) // clean up
conf := config.New()
conf.Extensions = &extconf.ExtensionConfig{}
conf.Extensions.Lint = &extconf.LintConfig{}
dir := t.TempDir()
log := log.NewLogger("debug", logFile.Name())
metrics := monitoring.NewMetricsServer(false, log)
imgStore := storage.NewImageStore(dir, true, 1*time.Second, true, true, log, metrics)
imgStore := storage.NewImageStore(dir, true, 1*time.Second, true,
true, log, metrics, nil)
err = test.CopyFiles("../../../test/data/zot-test", path.Join(dir, repoName))
if err != nil {
@@ -247,10 +252,16 @@ func TestRunScrubRepo(t *testing.T) {
defer os.Remove(logFile.Name()) // clean up
conf := config.New()
conf.Extensions = &extconf.ExtensionConfig{}
conf.Extensions.Lint = &extconf.LintConfig{}
dir := t.TempDir()
log := log.NewLogger("debug", logFile.Name())
metrics := monitoring.NewMetricsServer(false, log)
imgStore := storage.NewImageStore(dir, true, 1*time.Second, true, true, log, metrics)
imgStore := storage.NewImageStore(dir, true, 1*time.Second, true,
true, log, metrics, nil)
err = test.CopyFiles("../../../test/data/zot-test", path.Join(dir, repoName))
if err != nil {
@@ -277,10 +288,15 @@ func TestRunScrubRepo(t *testing.T) {
defer os.Remove(logFile.Name()) // clean up
conf := config.New()
conf.Extensions = &extconf.ExtensionConfig{}
conf.Extensions.Lint = &extconf.LintConfig{}
dir := t.TempDir()
log := log.NewLogger("debug", logFile.Name())
metrics := monitoring.NewMetricsServer(false, log)
imgStore := storage.NewImageStore(dir, true, 1*time.Second, true, true, log, metrics)
imgStore := storage.NewImageStore(dir, true, 1*time.Second,
true, true, log, metrics, nil)
err = test.CopyFiles("../../../test/data/zot-test", path.Join(dir, repoName))
if err != nil {
+15 -4
View File
@@ -264,8 +264,13 @@ func TestImageFormat(t *testing.T) {
log := log.NewLogger("debug", "")
dbDir := "../../../../test/data"
conf := config.New()
conf.Extensions = &extconf.ExtensionConfig{}
conf.Extensions.Lint = &extconf.LintConfig{}
metrics := monitoring.NewMetricsServer(false, log)
defaultStore := storage.NewImageStore(dbDir, false, storage.DefaultGCDelay, false, false, log, metrics)
defaultStore := storage.NewImageStore(dbDir, false, storage.DefaultGCDelay,
false, false, log, metrics, nil)
storeController := storage.StoreController{DefaultStore: defaultStore}
olu := common.NewBaseOciLayoutUtils(storeController, log)
@@ -708,10 +713,16 @@ func TestUtilsMethod(t *testing.T) {
subRootDir := t.TempDir()
metrics := monitoring.NewMetricsServer(false, log)
defaultStore := storage.NewImageStore(rootDir, false, storage.DefaultGCDelay, false, false, log, metrics)
conf := config.New()
conf.Extensions = &extconf.ExtensionConfig{}
conf.Extensions.Lint = &extconf.LintConfig{}
subStore := storage.NewImageStore(subRootDir, false, storage.DefaultGCDelay, false, false, log, metrics)
metrics := monitoring.NewMetricsServer(false, log)
defaultStore := storage.NewImageStore(rootDir, false,
storage.DefaultGCDelay, false, false, log, metrics, nil)
subStore := storage.NewImageStore(subRootDir, false,
storage.DefaultGCDelay, false, false, log, metrics, nil)
subStoreMap := make(map[string]storage.ImageStore)
+12 -4
View File
@@ -92,7 +92,11 @@ func testSetup() error {
log := log.NewLogger("debug", "")
metrics := monitoring.NewMetricsServer(false, log)
storeController := storage.StoreController{DefaultStore: storage.NewImageStore(dir, false, storage.DefaultGCDelay, false, false, log, metrics)}
conf := config.New()
conf.Extensions = &extconf.ExtensionConfig{}
conf.Extensions.Lint = &extconf.LintConfig{}
storeController := storage.StoreController{DefaultStore: storage.NewImageStore(dir, false, storage.DefaultGCDelay, false, false, log, metrics, nil)}
layoutUtils := common.NewBaseOciLayoutUtils(storeController, log)
@@ -334,12 +338,16 @@ func TestMultipleStoragePath(t *testing.T) {
log := log.NewLogger("debug", "")
metrics := monitoring.NewMetricsServer(false, log)
conf := config.New()
conf.Extensions = &extconf.ExtensionConfig{}
conf.Extensions.Lint = &extconf.LintConfig{}
// Create ImageStore
firstStore := storage.NewImageStore(firstRootDir, false, storage.DefaultGCDelay, false, false, log, metrics)
firstStore := storage.NewImageStore(firstRootDir, false, storage.DefaultGCDelay, false, false, log, metrics, nil)
secondStore := storage.NewImageStore(secondRootDir, false, storage.DefaultGCDelay, false, false, log, metrics)
secondStore := storage.NewImageStore(secondRootDir, false, storage.DefaultGCDelay, false, false, log, metrics, nil)
thirdStore := storage.NewImageStore(thirdRootDir, false, storage.DefaultGCDelay, false, false, log, metrics)
thirdStore := storage.NewImageStore(thirdRootDir, false, storage.DefaultGCDelay, false, false, log, metrics, nil)
storeController := storage.StoreController{}
+6 -1
View File
@@ -97,10 +97,15 @@ func testSetup() error {
return err
}
conf := config.New()
conf.Extensions = &extconf.ExtensionConfig{}
conf.Extensions.Lint = &extconf.LintConfig{}
log := log.NewLogger("debug", "")
metrics := monitoring.NewMetricsServer(false, log)
storeController := storage.StoreController{
DefaultStore: storage.NewImageStore(rootDir, false, storage.DefaultGCDelay, false, false, log, metrics),
DefaultStore: storage.NewImageStore(rootDir, false, storage.DefaultGCDelay,
false, false, log, metrics, nil),
}
digestInfo = digestinfo.NewDigestInfo(storeController, log)
+2 -1
View File
@@ -274,7 +274,8 @@ func syncOneImage(imageChannel chan error, cfg Config, storeController storage.S
imageChannel <- nil
}
func syncRun(regCfg RegistryConfig, localRepo, remoteRepo, tag string, utils syncContextUtils,
func syncRun(regCfg RegistryConfig,
localRepo, remoteRepo, tag string, utils syncContextUtils,
log log.Logger,
) (bool, error) {
upstreamImageRef, err := getImageRef(utils.upstreamAddr, remoteRepo, tag)
+2 -1
View File
@@ -196,7 +196,8 @@ func syncCosignSignature(client *resty.Client, imageStore storage.ImageStore,
}
// push manifest
_, err = imageStore.PutImageManifest(localRepo, cosignTag, ispec.MediaTypeImageManifest, cosignManifestBuf)
_, err = imageStore.PutImageManifest(localRepo, cosignTag,
ispec.MediaTypeImageManifest, cosignManifestBuf)
if err != nil {
log.Error().Str("errorType", TypeOf(err)).
Err(err).Msg("couldn't upload cosign manifest")
+4 -3
View File
@@ -296,7 +296,8 @@ func getUpstreamContext(regCfg *RegistryConfig, credentials Credentials) *types.
}
// nolint:gocyclo // offloading some of the functionalities from here would make the code harder to follow
func syncRegistry(ctx context.Context, regCfg RegistryConfig, upstreamURL string,
func syncRegistry(ctx context.Context, regCfg RegistryConfig,
upstreamURL string,
storeController storage.StoreController, localCtx *types.SystemContext,
policyCtx *signature.PolicyContext, credentials Credentials,
retryOptions *retry.RetryOptions, log log.Logger,
@@ -509,7 +510,6 @@ func syncRegistry(ctx context.Context, regCfg RegistryConfig, upstreamURL string
}
// push from cache to repo
err = pushSyncedLocalImage(localRepo, tag, localCachePath, imageStore, log)
if err != nil {
log.Error().Str("errorType", TypeOf(err)).
Err(err).Msgf("error while pushing synced cached image %s",
@@ -573,7 +573,8 @@ func getLocalContexts(log log.Logger) (*types.SystemContext, *signature.PolicyCo
return localCtx, policyContext, nil
}
func Run(ctx context.Context, cfg Config, storeController storage.StoreController,
func Run(ctx context.Context, cfg Config,
storeController storage.StoreController,
wtgrp *goSync.WaitGroup, logger log.Logger,
) error {
var credentialsFile CredentialsFile
+13 -6
View File
@@ -61,7 +61,9 @@ func TestInjectSyncUtils(t *testing.T) {
log := log.Logger{Logger: zerolog.New(os.Stdout)}
metrics := monitoring.NewMetricsServer(false, log)
imageStore := storage.NewImageStore(t.TempDir(), false, storage.DefaultGCDelay, false, false, log, metrics)
imageStore := storage.NewImageStore(t.TempDir(), false, storage.DefaultGCDelay,
false, false, log, metrics, nil,
)
injected = test.InjectFailure(0)
_, err = getLocalCachePath(imageStore, testImage)
@@ -147,7 +149,8 @@ func TestSyncInternal(t *testing.T) {
}
ctx := context.Background()
So(Run(ctx, cfg, storage.StoreController{}, new(goSync.WaitGroup), log.NewLogger("debug", "")), ShouldNotBeNil)
So(Run(ctx, cfg, storage.StoreController{},
new(goSync.WaitGroup), log.NewLogger("debug", "")), ShouldNotBeNil)
_, err = getFileCredentials("/invalid/path/to/file")
So(err, ShouldNotBeNil)
@@ -157,7 +160,8 @@ func TestSyncInternal(t *testing.T) {
log := log.Logger{Logger: zerolog.New(os.Stdout)}
metrics := monitoring.NewMetricsServer(false, log)
imageStore := storage.NewImageStore(t.TempDir(), false, storage.DefaultGCDelay, false, false, log, metrics)
imageStore := storage.NewImageStore(t.TempDir(), false, storage.DefaultGCDelay,
false, false, log, metrics, nil)
err := os.Chmod(imageStore.RootDir(), 0o000)
So(err, ShouldBeNil)
@@ -340,7 +344,8 @@ func TestSyncInternal(t *testing.T) {
log := log.Logger{Logger: zerolog.New(os.Stdout)}
metrics := monitoring.NewMetricsServer(false, log)
imageStore := storage.NewImageStore(storageDir, false, storage.DefaultGCDelay, false, false, log, metrics)
imageStore := storage.NewImageStore(storageDir, false, storage.DefaultGCDelay,
false, false, log, metrics, nil)
refs := ReferenceList{[]artifactspec.Descriptor{
{
@@ -422,7 +427,8 @@ func TestSyncInternal(t *testing.T) {
log := log.Logger{Logger: zerolog.New(os.Stdout)}
metrics := monitoring.NewMetricsServer(false, log)
imageStore := storage.NewImageStore(storageDir, false, storage.DefaultGCDelay, false, false, log, metrics)
imageStore := storage.NewImageStore(storageDir, false, storage.DefaultGCDelay,
false, false, log, metrics, nil)
storeController := storage.StoreController{}
storeController.DefaultStore = imageStore
@@ -443,7 +449,8 @@ func TestSyncInternal(t *testing.T) {
panic(err)
}
testImageStore := storage.NewImageStore(testRootDir, false, storage.DefaultGCDelay, false, false, log, metrics)
testImageStore := storage.NewImageStore(testRootDir, false,
storage.DefaultGCDelay, false, false, log, metrics, nil)
manifestContent, _, _, err := testImageStore.GetImageManifest(testImage, testImageTag)
So(err, ShouldBeNil)
+101
View File
@@ -862,6 +862,107 @@ func TestConfigReloader(t *testing.T) {
})
}
func TestMandatoryAnnotations(t *testing.T) {
Convey("Verify mandatory annotations failing - on demand disabled", t, func() {
updateDuration, _ := time.ParseDuration("30m")
sctlr, srcBaseURL, _, _, _ := startUpstreamServer(t, false, false)
defer func() {
sctlr.Shutdown()
}()
regex := ".*"
var semver bool
tlsVerify := false
syncRegistryConfig := sync.RegistryConfig{
Content: []sync.Content{
{
Prefix: testImage,
Tags: &sync.Tags{
Regex: &regex,
Semver: &semver,
},
},
},
URLs: []string{srcBaseURL},
OnDemand: false,
PollInterval: updateDuration,
TLSVerify: &tlsVerify,
}
defaultVal := true
syncConfig := &sync.Config{
Enable: &defaultVal,
Registries: []sync.RegistryConfig{syncRegistryConfig},
}
destPort := test.GetFreePort()
destConfig := config.New()
destClient := resty.New()
destBaseURL := test.GetBaseURL(destPort)
destConfig.HTTP.Port = destPort
destDir := t.TempDir()
destConfig.Storage.RootDirectory = destDir
destConfig.Storage.Dedupe = false
destConfig.Storage.GC = false
destConfig.Extensions = &extconf.ExtensionConfig{}
destConfig.Extensions.Sync = syncConfig
logFile, err := ioutil.TempFile("", "zot-log*.txt")
So(err, ShouldBeNil)
destConfig.Log.Output = logFile.Name()
lintEnabled := true
destConfig.Extensions.Lint = &extconf.LintConfig{}
destConfig.Extensions.Lint.Enabled = &lintEnabled
destConfig.Extensions.Lint.MandatoryAnnotations = []string{"annot1", "annot2", "annot3"}
dctlr := api.NewController(destConfig)
go func() {
// this blocks
if err := dctlr.Run(context.Background()); err != nil {
return
}
}()
// wait till ready
for {
_, err := destClient.R().Get(destBaseURL)
if err == nil {
break
}
time.Sleep(100 * time.Millisecond)
}
defer func() {
dctlr.Shutdown()
}()
// give it time to set up sync
time.Sleep(3 * time.Second)
resp, err := destClient.R().Get(destBaseURL + "/v2/" + testImage + "/manifest/0.0.1")
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 404)
data, err := os.ReadFile(logFile.Name())
t.Logf("downstream log: %s", string(data))
So(err, ShouldBeNil)
So(string(data), ShouldContainSubstring, "couldn't upload manifest because of missing annotations")
})
}
func TestBadTLS(t *testing.T) {
Convey("Verify sync TLS feature", t, func() {
updateDuration, _ := time.ParseDuration("30m")
+12 -2
View File
@@ -270,7 +270,9 @@ func pushSyncedLocalImage(localRepo, tag, localCachePath string,
log.Info().Msgf("pushing synced local image %s/%s:%s to local registry", localCachePath, localRepo, tag)
metrics := monitoring.NewMetricsServer(false, log)
cacheImageStore := storage.NewImageStore(localCachePath, false, storage.DefaultGCDelay, false, false, log, metrics)
cacheImageStore := storage.NewImageStore(localCachePath, false,
storage.DefaultGCDelay, false, false, log, metrics, nil)
manifestContent, _, _, err := cacheImageStore.GetImageManifest(localRepo, tag)
if err != nil {
@@ -331,8 +333,16 @@ func pushSyncedLocalImage(localRepo, tag, localCachePath string,
}
}
_, err = imageStore.PutImageManifest(localRepo, tag, ispec.MediaTypeImageManifest, manifestContent)
_, err = imageStore.PutImageManifest(localRepo, tag,
ispec.MediaTypeImageManifest, manifestContent)
if err != nil {
if errors.Is(err, zerr.ErrImageLintAnnotations) {
log.Error().Str("errorType", TypeOf(err)).
Err(err).Msg("couldn't upload manifest because of missing annotations")
return nil
}
log.Error().Str("errorType", TypeOf(err)).
Err(err).Msg("couldn't upload manifest")