From 2f3c84bd1f86f1d005de23da50ce6348bd5e58f6 Mon Sep 17 00:00:00 2001
From: a
Date: Fri, 3 Nov 2023 17:28:01 -0500
Subject: [PATCH 1/2] feat(sync): local tmp store
Signed-off-by: a
---
pkg/cli/server/root.go | 6 -
pkg/extensions/config/sync/config.go | 1 +
pkg/extensions/extension_sync.go | 39 +-
.../sync/{local.go => destination.go} | 29 +-
pkg/extensions/sync/service.go | 47 +-
pkg/extensions/sync/sync.go | 2 +-
pkg/extensions/sync/sync_internal_test.go | 12 +-
test/blackbox/sync_remote.bats | 534 ++++++++++++++++++
8 files changed, 615 insertions(+), 55 deletions(-)
rename pkg/extensions/sync/{local.go => destination.go} (88%)
create mode 100644 test/blackbox/sync_remote.bats
diff --git a/pkg/cli/server/root.go b/pkg/cli/server/root.go
index 5809ddad7..67e184593 100644
--- a/pkg/cli/server/root.go
+++ b/pkg/cli/server/root.go
@@ -392,12 +392,6 @@ func validateConfiguration(config *config.Config, log zlog.Logger) error {
return zerr.ErrBadConfig
}
- // enforce filesystem storage in case sync feature is enabled
- if config.Extensions != nil && config.Extensions.Sync != nil {
- log.Error().Err(zerr.ErrBadConfig).Msg("sync supports only filesystem storage")
-
- return zerr.ErrBadConfig
- }
}
// enforce s3 driver on subpaths in case of using storage driver
diff --git a/pkg/extensions/config/sync/config.go b/pkg/extensions/config/sync/config.go
index 96c1361d4..eda4844b2 100644
--- a/pkg/extensions/config/sync/config.go
+++ b/pkg/extensions/config/sync/config.go
@@ -15,6 +15,7 @@ type Credentials struct {
type Config struct {
Enable *bool
CredentialsFile string
+ TmpDir string
Registries []RegistryConfig
}
diff --git a/pkg/extensions/extension_sync.go b/pkg/extensions/extension_sync.go
index 852112dc0..a8f9c16f9 100644
--- a/pkg/extensions/extension_sync.go
+++ b/pkg/extensions/extension_sync.go
@@ -6,6 +6,7 @@ package extensions
import (
"net"
"net/url"
+ "os"
"strings"
zerr "zotregistry.io/zot/errors"
@@ -41,23 +42,31 @@ func EnableSyncExtension(config *config.Config, metaDB mTypes.MetaDB,
isPeriodical := len(registryConfig.Content) != 0 && registryConfig.PollInterval != 0
isOnDemand := registryConfig.OnDemand
- if isPeriodical || isOnDemand {
- service, err := sync.New(registryConfig, config.Extensions.Sync.CredentialsFile,
- storeController, metaDB, log)
- if err != nil {
- return nil, err
- }
+ if !(isPeriodical || isOnDemand) {
+ continue
+ }
- if isPeriodical {
- // add to task scheduler periodic sync
- gen := sync.NewTaskGenerator(service, log)
- sch.SubmitGenerator(gen, registryConfig.PollInterval, scheduler.MediumPriority)
- }
+ tmpDir := config.Extensions.Sync.TmpDir
+ if tmpDir == "" {
+ // use an os tmpdir as tmpdir if not set
+ tmpDir = os.TempDir()
+ }
- if isOnDemand {
- // onDemand services used in routes.go
- onDemand.Add(service)
- }
+ service, err := sync.New(registryConfig, config.Extensions.Sync.CredentialsFile, tmpDir,
+ storeController, metaDB, log)
+ if err != nil {
+ return nil, err
+ }
+
+ if isPeriodical {
+ // add to task scheduler periodic sync
+ gen := sync.NewTaskGenerator(service, log)
+ sch.SubmitGenerator(gen, registryConfig.PollInterval, scheduler.MediumPriority)
+ }
+
+ if isOnDemand {
+ // onDemand services used in routes.go
+ onDemand.Add(service)
}
}
diff --git a/pkg/extensions/sync/local.go b/pkg/extensions/sync/destination.go
similarity index 88%
rename from pkg/extensions/sync/local.go
rename to pkg/extensions/sync/destination.go
index 86ccf357c..36483d4cf 100644
--- a/pkg/extensions/sync/local.go
+++ b/pkg/extensions/sync/destination.go
@@ -29,25 +29,34 @@ import (
storageTypes "zotregistry.io/zot/pkg/storage/types"
)
-type LocalRegistry struct {
+type DestinationRegistry struct {
storeController storage.StoreController
tempStorage OciLayoutStorage
metaDB mTypes.MetaDB
log log.Logger
}
-func NewLocalRegistry(storeController storage.StoreController, metaDB mTypes.MetaDB, log log.Logger) Local {
- return &LocalRegistry{
+func NewDestinationRegistry(
+ storeController storage.StoreController,
+ tmpStorage OciLayoutStorage,
+ metaDB mTypes.MetaDB,
+ log log.Logger,
+) Destination {
+ if tmpStorage == nil {
+ // to allow passing nil we can do this, noting that it will only work for a local StoreController
+ tmpStorage = NewOciLayoutStorage(storeController)
+ }
+ return &DestinationRegistry{
storeController: storeController,
metaDB: metaDB,
// first we sync from remote (using containers/image copy from docker:// to oci:) to a temp imageStore
// then we copy the image from tempStorage to zot's storage using ImageStore APIs
- tempStorage: NewOciLayoutStorage(storeController),
+ tempStorage: tmpStorage,
log: log,
}
}
-func (registry *LocalRegistry) CanSkipImage(repo, tag string, imageDigest digest.Digest) (bool, error) {
+func (registry *DestinationRegistry) CanSkipImage(repo, tag string, imageDigest digest.Digest) (bool, error) {
// check image already synced
imageStore := registry.storeController.GetImageStore(repo)
@@ -75,16 +84,16 @@ func (registry *LocalRegistry) CanSkipImage(repo, tag string, imageDigest digest
return true, nil
}
-func (registry *LocalRegistry) GetContext() *types.SystemContext {
+func (registry *DestinationRegistry) GetContext() *types.SystemContext {
return registry.tempStorage.GetContext()
}
-func (registry *LocalRegistry) GetImageReference(repo, reference string) (types.ImageReference, error) {
+func (registry *DestinationRegistry) GetImageReference(repo, reference string) (types.ImageReference, error) {
return registry.tempStorage.GetImageReference(repo, reference)
}
// finalize a syncing image.
-func (registry *LocalRegistry) CommitImage(imageReference types.ImageReference, repo, reference string) error {
+func (registry *DestinationRegistry) CommitImage(imageReference types.ImageReference, repo, reference string) error {
imageStore := registry.storeController.GetImageStore(repo)
tempImageStore := getImageStoreFromImageReference(imageReference, repo, reference)
@@ -180,7 +189,7 @@ func (registry *LocalRegistry) CommitImage(imageReference types.ImageReference,
return nil
}
-func (registry *LocalRegistry) copyManifest(repo string, manifestContent []byte, reference string,
+func (registry *DestinationRegistry) copyManifest(repo string, manifestContent []byte, reference string,
tempImageStore storageTypes.ImageStore,
) error {
imageStore := registry.storeController.GetImageStore(repo)
@@ -239,7 +248,7 @@ func (registry *LocalRegistry) copyManifest(repo string, manifestContent []byte,
}
// Copy a blob from one image store to another image store.
-func (registry *LocalRegistry) copyBlob(repo string, blobDigest digest.Digest, blobMediaType string,
+func (registry *DestinationRegistry) copyBlob(repo string, blobDigest digest.Digest, blobMediaType string,
tempImageStore storageTypes.ImageStore,
) error {
imageStore := registry.storeController.GetImageStore(repo)
diff --git a/pkg/extensions/sync/service.go b/pkg/extensions/sync/service.go
index 725af0870..63b5f360e 100644
--- a/pkg/extensions/sync/service.go
+++ b/pkg/extensions/sync/service.go
@@ -20,13 +20,14 @@ import (
"zotregistry.io/zot/pkg/log"
mTypes "zotregistry.io/zot/pkg/meta/types"
"zotregistry.io/zot/pkg/storage"
+ "zotregistry.io/zot/pkg/storage/local"
)
type BaseService struct {
config syncconf.RegistryConfig
credentials syncconf.CredentialsFile
remote Remote
- local Local
+ destination Destination
retryOptions *retry.RetryOptions
contentManager ContentManager
storeController storage.StoreController
@@ -37,8 +38,13 @@ type BaseService struct {
log log.Logger
}
-func New(opts syncconf.RegistryConfig, credentialsFilepath string,
- storeController storage.StoreController, metadb mTypes.MetaDB, log log.Logger,
+func New(
+ opts syncconf.RegistryConfig,
+ credentialsFilepath string,
+ tmpDir string,
+ storeController storage.StoreController,
+ metadb mTypes.MetaDB,
+ log log.Logger,
) (Service, error) {
service := &BaseService{}
@@ -60,7 +66,14 @@ func New(opts syncconf.RegistryConfig, credentialsFilepath string,
service.credentials = credentialsFile
service.contentManager = NewContentManager(opts.Content, log)
- service.local = NewLocalRegistry(storeController, metadb, log)
+
+ tmpImageStore := local.NewImageStore(tmpDir,
+ false, false, log, nil, nil, nil,
+ )
+
+ tmpStorage := NewOciLayoutStorage(storage.StoreController{DefaultStore: tmpImageStore})
+
+ service.destination = NewDestinationRegistry(storeController, tmpStorage, metadb, log)
retryOptions := &retry.RetryOptions{}
@@ -289,7 +302,7 @@ func (service *BaseService) SyncRepo(ctx context.Context, repo string) error {
service.log.Info().Str("repo", repo).Msgf("sync: syncing tags %v", tags)
// apply content.destination rule
- localRepo := service.contentManager.GetRepoDestination(repo)
+ destinationRepo := service.contentManager.GetRepoDestination(repo)
for _, tag := range tags {
if common.IsContextDone(ctx) {
@@ -303,7 +316,7 @@ func (service *BaseService) SyncRepo(ctx context.Context, repo string) error {
var manifestDigest digest.Digest
if err = retry.RetryIfNecessary(ctx, func() error {
- manifestDigest, err = service.syncTag(ctx, localRepo, repo, tag)
+ manifestDigest, err = service.syncTag(ctx, destinationRepo, repo, tag)
return err
}, service.retryOptions); err != nil {
@@ -320,7 +333,7 @@ func (service *BaseService) SyncRepo(ctx context.Context, repo string) error {
if manifestDigest != "" {
if err = retry.RetryIfNecessary(ctx, func() error {
- err = service.references.SyncAll(ctx, localRepo, repo, manifestDigest.String())
+ err = service.references.SyncAll(ctx, destinationRepo, repo, manifestDigest.String())
if errors.Is(err, zerr.ErrSyncReferrerNotFound) {
return nil
}
@@ -340,8 +353,8 @@ func (service *BaseService) SyncRepo(ctx context.Context, repo string) error {
return nil
}
-func (service *BaseService) syncTag(ctx context.Context, localRepo, remoteRepo, tag string) (digest.Digest, error) {
- copyOptions := getCopyOptions(service.remote.GetContext(), service.local.GetContext())
+func (service *BaseService) syncTag(ctx context.Context, destinationRepo, remoteRepo, tag string) (digest.Digest, error) {
+ copyOptions := getCopyOptions(service.remote.GetContext(), service.destination.GetContext())
policyContext, err := getPolicyContext(service.log)
if err != nil {
@@ -384,38 +397,38 @@ func (service *BaseService) syncTag(ctx context.Context, localRepo, remoteRepo,
}
}
- skipImage, err := service.local.CanSkipImage(localRepo, tag, manifestDigest)
+ skipImage, err := service.destination.CanSkipImage(destinationRepo, tag, manifestDigest)
if err != nil {
service.log.Error().Err(err).Str("errortype", common.TypeOf(err)).
- Str("repo", localRepo).Str("reference", tag).
+ Str("repo", destinationRepo).Str("reference", tag).
Msg("couldn't check if the local image can be skipped")
}
if !skipImage {
- localImageRef, err := service.local.GetImageReference(localRepo, tag)
+ localImageRef, err := service.destination.GetImageReference(destinationRepo, tag)
if err != nil {
service.log.Error().Err(err).Str("errortype", common.TypeOf(err)).
- Str("repo", localRepo).Str("reference", tag).Msg("couldn't get a local image reference")
+ Str("repo", destinationRepo).Str("reference", tag).Msg("couldn't get a local image reference")
return "", err
}
service.log.Info().Str("remote image", remoteImageRef.DockerReference().String()).
- Str("local image", fmt.Sprintf("%s:%s", localRepo, tag)).Msg("syncing image")
+ Str("local image", fmt.Sprintf("%s:%s", destinationRepo, tag)).Msg("syncing image")
_, err = copy.Image(ctx, policyContext, localImageRef, remoteImageRef, ©Options)
if err != nil {
service.log.Error().Err(err).Str("errortype", common.TypeOf(err)).
Str("remote image", remoteImageRef.DockerReference().String()).
- Str("local image", fmt.Sprintf("%s:%s", localRepo, tag)).Msg("coulnd't sync image")
+ Str("local image", fmt.Sprintf("%s:%s", destinationRepo, tag)).Msg("coulnd't sync image")
return "", err
}
- err = service.local.CommitImage(localImageRef, localRepo, tag)
+ err = service.destination.CommitImage(localImageRef, destinationRepo, tag)
if err != nil {
service.log.Error().Err(err).Str("errortype", common.TypeOf(err)).
- Str("repo", localRepo).Str("reference", tag).Msg("couldn't commit image to local image store")
+ Str("repo", destinationRepo).Str("reference", tag).Msg("couldn't commit image to local image store")
return "", err
}
diff --git a/pkg/extensions/sync/sync.go b/pkg/extensions/sync/sync.go
index bb4dd0bf4..c285c5726 100644
--- a/pkg/extensions/sync/sync.go
+++ b/pkg/extensions/sync/sync.go
@@ -65,7 +65,7 @@ type Remote interface {
}
// Local registry.
-type Local interface {
+type Destination interface {
Registry
// Check if an image is already synced
CanSkipImage(repo, tag string, imageDigest digest.Digest) (bool, error)
diff --git a/pkg/extensions/sync/sync_internal_test.go b/pkg/extensions/sync/sync_internal_test.go
index 4f025cd90..538318c0c 100644
--- a/pkg/extensions/sync/sync_internal_test.go
+++ b/pkg/extensions/sync/sync_internal_test.go
@@ -162,7 +162,7 @@ func TestService(t *testing.T) {
URLs: []string{"http://localhost"},
}
- service, err := New(conf, "", storage.StoreController{}, mocks.MetaDBMock{}, log.Logger{})
+ service, err := New(conf, "", os.TempDir(), storage.StoreController{}, mocks.MetaDBMock{}, log.Logger{})
So(err, ShouldBeNil)
err = service.SyncRepo(context.Background(), "repo")
@@ -170,7 +170,7 @@ func TestService(t *testing.T) {
})
}
-func TestLocalRegistry(t *testing.T) {
+func TestDestinationRegistry(t *testing.T) {
Convey("make StoreController", t, func() {
dir := t.TempDir()
@@ -185,7 +185,7 @@ func TestLocalRegistry(t *testing.T) {
syncImgStore := local.NewImageStore(dir, true, true, log, metrics, nil, cacheDriver)
repoName := "repo"
- registry := NewLocalRegistry(storage.StoreController{DefaultStore: syncImgStore}, nil, log)
+ registry := NewDestinationRegistry(storage.StoreController{DefaultStore: syncImgStore}, nil, nil, log)
imageReference, err := registry.GetImageReference(repoName, "1.0")
So(err, ShouldBeNil)
So(imageReference, ShouldNotBeNil)
@@ -302,7 +302,7 @@ func TestLocalRegistry(t *testing.T) {
syncImgStore := local.NewImageStore(dir, true, true, log, metrics, linter, cacheDriver)
repoName := "repo"
- registry := NewLocalRegistry(storage.StoreController{DefaultStore: syncImgStore}, nil, log)
+ registry := NewDestinationRegistry(storage.StoreController{DefaultStore: syncImgStore}, nil, nil, log)
err = registry.CommitImage(imageReference, repoName, "1.0")
So(err, ShouldBeNil)
@@ -336,7 +336,7 @@ func TestLocalRegistry(t *testing.T) {
})
Convey("trigger metaDB error on index manifest in CommitImage()", func() {
- registry := NewLocalRegistry(storage.StoreController{DefaultStore: syncImgStore}, mocks.MetaDBMock{
+ registry := NewDestinationRegistry(storage.StoreController{DefaultStore: syncImgStore}, nil, mocks.MetaDBMock{
SetRepoReferenceFn: func(ctx context.Context, repo string, reference string, imageMeta mTypes.ImageMeta) error {
if reference == "1.0" {
return zerr.ErrRepoMetaNotFound
@@ -351,7 +351,7 @@ func TestLocalRegistry(t *testing.T) {
})
Convey("trigger metaDB error on image manifest in CommitImage()", func() {
- registry := NewLocalRegistry(storage.StoreController{DefaultStore: syncImgStore}, mocks.MetaDBMock{
+ registry := NewDestinationRegistry(storage.StoreController{DefaultStore: syncImgStore}, nil, mocks.MetaDBMock{
SetRepoReferenceFn: func(ctx context.Context, repo, reference string, imageMeta mTypes.ImageMeta) error {
return zerr.ErrRepoMetaNotFound
},
diff --git a/test/blackbox/sync_remote.bats b/test/blackbox/sync_remote.bats
new file mode 100644
index 000000000..6a1ee2026
--- /dev/null
+++ b/test/blackbox/sync_remote.bats
@@ -0,0 +1,534 @@
+# Note: Intended to be run as "make run-blackbox-tests" or "make run-blackbox-ci"
+# Makefile target installs & checks all necessary tooling
+# Extra tools that are not covered in Makefile target needs to be added in verify_prerequisites()
+
+load helpers_zot
+load helpers_wait
+load helpers_cloud
+
+
+function verify_prerequisites() {
+ if [ ! $(command -v curl) ]; then
+ echo "you need to install curl as a prerequisite to running the tests" >&3
+ return 1
+ fi
+
+ if [ ! $(command -v jq) ]; then
+ echo "you need to install jq as a prerequisite to running the tests" >&3
+ return 1
+ fi
+
+ return 0
+}
+
+function setup_file() {
+ export COSIGN_PASSWORD=""
+ export COSIGN_OCI_EXPERIMENTAL=1
+ export COSIGN_EXPERIMENTAL=1
+
+ # Verify prerequisites are available
+ if ! $(verify_prerequisites); then
+ exit 1
+ fi
+
+ # Download test data to folder common for the entire suite, not just this file
+ skopeo --insecure-policy copy --format=oci docker://ghcr.io/project-zot/golang:1.20 oci:${TEST_DATA_DIR}/golang:1.20
+
+
+ # Setup zot server
+ local zot_sync_per_root_dir=${BATS_FILE_TMPDIR}/zot-per
+ local zot_sync_ondemand_root_dir=${BATS_FILE_TMPDIR}/zot-ondemand
+
+ local zot_sync_per_config_file=${BATS_FILE_TMPDIR}/zot_sync_per_config.json
+ local zot_sync_ondemand_config_file=${BATS_FILE_TMPDIR}/zot_sync_ondemand_config.json
+
+ local zot_minimal_root_dir=${BATS_FILE_TMPDIR}/zot-minimal
+ local zot_minimal_config_file=${BATS_FILE_TMPDIR}/zot_minimal_config.json
+
+ local oci_data_dir=${BATS_FILE_TMPDIR}/oci
+ mkdir -p ${zot_sync_per_root_dir}
+ mkdir -p ${zot_sync_ondemand_root_dir}
+ mkdir -p ${zot_minimal_root_dir}
+ mkdir -p ${oci_data_dir}
+
+ cat >${zot_sync_per_config_file} <${zot_sync_ondemand_config_file} <${zot_minimal_config_file} <${trust_policy_file} < config.json
+echo "hello world" > artifact.txt
+run oras push --plain-http 127.0.0.1:9000/hello-artifact:v2 \
+ --config config.json:application/vnd.acme.rocket.config.v1+json artifact.txt:text/plain -d -v
+ [ "$status" -eq 0 ]
+ rm -f artifact.txt
+ rm -f config.json
+}
+
+@test "sync oras artifact periodically" {
+# # wait for oras artifact to be copied
+run sleep 15s
+run oras pull --plain-http 127.0.0.1:8081/hello-artifact:v2 -d -v
+[ "$status" -eq 0 ]
+grep -q "hello world" artifact.txt
+rm -f artifact.txt
+}
+
+@test "sync oras artifact on demand" {
+run oras pull --plain-http 127.0.0.1:8082/hello-artifact:v2 -d -v
+[ "$status" -eq 0 ]
+grep -q "hello world" artifact.txt
+rm -f artifact.txt
+}
+
+# sync helm chart
+@test "push helm chart" {
+run helm package ${BATS_FILE_TMPDIR}/helm-charts/charts/zot -d ${BATS_FILE_TMPDIR}
+[ "$status" -eq 0 ]
+local chart_version=$(awk '/version/{printf $2}' ${BATS_FILE_TMPDIR}/helm-charts/charts/zot/Chart.yaml)
+run helm push ${BATS_FILE_TMPDIR}/zot-${chart_version}.tgz oci://localhost:9000/zot-chart
+[ "$status" -eq 0 ]
+}
+
+@test "sync helm chart periodically" {
+# wait for helm chart to be copied
+run sleep 15s
+
+local chart_version=$(awk '/version/{printf $2}' ${BATS_FILE_TMPDIR}/helm-charts/charts/zot/Chart.yaml)
+run helm pull oci://localhost:8081/zot-chart/zot --version ${chart_version} -d ${BATS_FILE_TMPDIR}
+[ "$status" -eq 0 ]
+}
+
+@test "sync helm chart on demand" {
+local chart_version=$(awk '/version/{printf $2}' ${BATS_FILE_TMPDIR}/helm-charts/charts/zot/Chart.yaml)
+run helm pull oci://localhost:8082/zot-chart/zot --version ${chart_version} -d ${BATS_FILE_TMPDIR}
+[ "$status" -eq 0 ]
+}
+
+# sync OCI artifacts
+@test "push OCI artifact (oci image mediatype) with regclient" {
+run regctl registry set localhost:9000 --tls disabled
+run regctl registry set localhost:8081 --tls disabled
+run regctl registry set localhost:8082 --tls disabled
+
+run regctl artifact put localhost:9000/artifact:demo <
Date: Tue, 21 Nov 2023 19:32:14 +0200
Subject: [PATCH 2/2] fix(sync): various fixes for s3+remote storage feature
Signed-off-by: Petu Eusebiu
---
Makefile | 1 +
examples/config-sync-cloud-storage.json | 49 ++
pkg/cli/server/extensions_test.go | 204 ++++++++
pkg/cli/server/root.go | 15 +
pkg/extensions/config/sync/config.go | 7 +-
pkg/extensions/extension_sync.go | 11 +-
pkg/extensions/sync/destination.go | 20 +-
pkg/extensions/sync/service.go | 27 +-
pkg/extensions/sync/sync_internal_test.go | 12 +-
test/blackbox/sync.bats | 6 +-
test/blackbox/sync_cloud.bats | 571 ++++++++++++++++++++++
test/blackbox/sync_remote.bats | 534 --------------------
12 files changed, 885 insertions(+), 572 deletions(-)
create mode 100644 examples/config-sync-cloud-storage.json
create mode 100644 test/blackbox/sync_cloud.bats
delete mode 100644 test/blackbox/sync_remote.bats
diff --git a/Makefile b/Makefile
index 07ce9c1c6..f7872de3d 100644
--- a/Makefile
+++ b/Makefile
@@ -493,6 +493,7 @@ run-blackbox-ci: check-blackbox-prerequisites binary binary-minimal cli
run-blackbox-cloud-ci: check-blackbox-prerequisites check-awslocal binary $(BATS)
echo running cloud CI bats tests; \
$(BATS) $(BATS_FLAGS) test/blackbox/cloud_only.bats
+ $(BATS) $(BATS_FLAGS) test/blackbox/sync_cloud.bats
.PHONY: run-blackbox-dedupe-nightly
run-blackbox-dedupe-nightly: check-blackbox-prerequisites check-awslocal binary binary-minimal
diff --git a/examples/config-sync-cloud-storage.json b/examples/config-sync-cloud-storage.json
new file mode 100644
index 000000000..bd4fbbb60
--- /dev/null
+++ b/examples/config-sync-cloud-storage.json
@@ -0,0 +1,49 @@
+{
+ "distSpecVersion": "1.1.0-dev",
+ "storage": {
+ "rootDirectory": "/tmp/zot",
+ "dedupe": true,
+ "gc": true,
+ "remoteCache": true,
+ "storageDriver": {
+ "name": "s3",
+ "rootdirectory": "/zot",
+ "region": "us-east-2",
+ "bucket": "zot-storage",
+ "secure": true,
+ "skipverify": false
+ },
+ "cacheDriver": {
+ "name": "dynamodb",
+ "region": "us-east-2",
+ "cacheTablename": "BlobTable"
+ }
+ },
+ "http": {
+ "address": "0.0.0.0",
+ "port": "8080"
+ },
+ "log": {
+ "level": "debug"
+ },
+ "extensions": {
+ "sync": {
+ "downloadDir": "/tmp/sync",
+ "registries": [
+ {
+ "urls": [
+ "http://localhost:5000"
+ ],
+ "onDemand": false,
+ "tlsVerify": false,
+ "PollInterval": "30m",
+ "content": [
+ {
+ "prefix": "**"
+ }
+ ]
+ }
+ ]
+ }
+ }
+}
diff --git a/pkg/cli/server/extensions_test.go b/pkg/cli/server/extensions_test.go
index 727e96a71..526fdc4dc 100644
--- a/pkg/cli/server/extensions_test.go
+++ b/pkg/cli/server/extensions_test.go
@@ -1653,3 +1653,207 @@ func TestOverlappingSyncRetentionConfig(t *testing.T) {
So(string(data), ShouldContainSubstring, "overlapping sync content\":{\"Prefix\":\"prod/*")
})
}
+
+func TestSyncWithRemoteStorageConfig(t *testing.T) {
+ oldArgs := os.Args
+
+ defer func() { os.Args = oldArgs }()
+
+ Convey("Test verify sync with remote storage works if sync.tmpdir is provided", t, func(c C) {
+ tmpfile, err := os.CreateTemp("", "zot-test*.json")
+ So(err, ShouldBeNil)
+ defer os.Remove(tmpfile.Name()) // clean up
+
+ content := `{
+ "distSpecVersion": "1.1.0-dev",
+ "storage": {
+ "rootDirectory": "%s",
+ "dedupe": false,
+ "remoteCache": false,
+ "storageDriver": {
+ "name": "s3",
+ "rootdirectory": "/zot",
+ "region": "us-east-2",
+ "regionendpoint": "localhost:4566",
+ "bucket": "zot-storage",
+ "secure": false,
+ "skipverify": false
+ }
+ },
+ "http": {
+ "address": "0.0.0.0",
+ "port": "%s"
+ },
+ "log": {
+ "level": "debug",
+ "output": "%s"
+ },
+ "extensions": {
+ "sync": {
+ "downloadDir": "/tmp/sync",
+ "registries": [
+ {
+ "urls": [
+ "http://localhost:9000"
+ ],
+ "onDemand": true,
+ "tlsVerify": false,
+ "content": [
+ {
+ "prefix": "**"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }`
+
+ logPath, err := runCLIWithConfig(t.TempDir(), content)
+ So(err, ShouldBeNil)
+
+ data, err := os.ReadFile(logPath)
+ So(err, ShouldBeNil)
+ defer os.Remove(logPath) // clean up
+ So(string(data), ShouldNotContainSubstring,
+ "using both sync and remote storage features needs config.Extensions.Sync.DownloadDir to be specified")
+ })
+
+ Convey("Test verify sync with remote storage panics if sync.tmpdir is not provided", t, func(c C) {
+ port := GetFreePort()
+ logFile, err := os.CreateTemp("", "zot-log*.txt")
+ So(err, ShouldBeNil)
+ defer os.Remove(logFile.Name()) // clean up
+
+ tmpfile, err := os.CreateTemp("", "zot-test*.json")
+ So(err, ShouldBeNil)
+ defer os.Remove(tmpfile.Name()) // clean up
+ content := fmt.Sprintf(`{
+ "distSpecVersion": "1.1.0-dev",
+ "storage": {
+ "rootDirectory": "%s",
+ "dedupe": false,
+ "remoteCache": false,
+ "storageDriver": {
+ "name": "s3",
+ "rootdirectory": "/zot",
+ "region": "us-east-2",
+ "regionendpoint": "localhost:4566",
+ "bucket": "zot-storage",
+ "secure": false,
+ "skipverify": false
+ }
+ },
+ "http": {
+ "address": "0.0.0.0",
+ "port": "%s"
+ },
+ "log": {
+ "level": "debug",
+ "output": "%s"
+ },
+ "extensions": {
+ "sync": {
+ "registries": [
+ {
+ "urls": [
+ "http://localhost:9000"
+ ],
+ "onDemand": true,
+ "tlsVerify": false,
+ "content": [
+ {
+ "prefix": "**"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }`, t.TempDir(), port, logFile.Name())
+
+ err = os.WriteFile(tmpfile.Name(), []byte(content), 0o0600)
+ So(err, ShouldBeNil)
+
+ os.Args = []string{"cli_test", "serve", tmpfile.Name()}
+ err = cli.NewServerRootCmd().Execute()
+ So(err, ShouldNotBeNil)
+
+ data, err := os.ReadFile(logFile.Name())
+ So(err, ShouldBeNil)
+ defer os.Remove(logFile.Name()) // clean up
+ So(string(data), ShouldContainSubstring,
+ "using both sync and remote storage features needs config.Extensions.Sync.DownloadDir to be specified")
+ })
+
+ Convey("Test verify sync with remote storage on subpath panics if sync.tmpdir is not provided", t, func(c C) {
+ port := GetFreePort()
+ logFile, err := os.CreateTemp("", "zot-log*.txt")
+ So(err, ShouldBeNil)
+ defer os.Remove(logFile.Name()) // clean up
+
+ tmpfile, err := os.CreateTemp("", "zot-test*.json")
+ So(err, ShouldBeNil)
+ defer os.Remove(tmpfile.Name()) // clean up
+ content := fmt.Sprintf(`{
+ "distSpecVersion": "1.1.0-dev",
+ "storage": {
+ "rootDirectory": "%s",
+ "subPaths":{
+ "/a": {
+ "rootDirectory": "%s",
+ "dedupe": false,
+ "remoteCache": false,
+ "storageDriver":{
+ "name":"s3",
+ "rootdirectory":"/zot-a",
+ "region":"us-east-2",
+ "bucket":"zot-storage",
+ "secure":true,
+ "skipverify":true
+ }
+ }
+ }
+ },
+ "http": {
+ "address": "0.0.0.0",
+ "port": "%s"
+ },
+ "log": {
+ "level": "debug",
+ "output": "%s"
+ },
+ "extensions": {
+ "sync": {
+ "registries": [
+ {
+ "urls": [
+ "http://localhost:9000"
+ ],
+ "onDemand": true,
+ "tlsVerify": false,
+ "content": [
+ {
+ "prefix": "**"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }`, t.TempDir(), t.TempDir(), port, logFile.Name())
+
+ err = os.WriteFile(tmpfile.Name(), []byte(content), 0o0600)
+ So(err, ShouldBeNil)
+
+ os.Args = []string{"cli_test", "serve", tmpfile.Name()}
+ err = cli.NewServerRootCmd().Execute()
+ So(err, ShouldNotBeNil)
+
+ data, err := os.ReadFile(logFile.Name())
+ So(err, ShouldBeNil)
+ defer os.Remove(logFile.Name()) // clean up
+ So(string(data), ShouldContainSubstring,
+ "using both sync and remote storage features needs config.Extensions.Sync.DownloadDir to be specified")
+ })
+}
diff --git a/pkg/cli/server/root.go b/pkg/cli/server/root.go
index 67e184593..a73ef3573 100644
--- a/pkg/cli/server/root.go
+++ b/pkg/cli/server/root.go
@@ -392,6 +392,13 @@ func validateConfiguration(config *config.Config, log zlog.Logger) error {
return zerr.ErrBadConfig
}
+ // enforce tmpDir in case sync + s3
+ if config.Extensions != nil && config.Extensions.Sync != nil && config.Extensions.Sync.DownloadDir == "" {
+ log.Error().Err(zerr.ErrBadConfig).
+ Msg("using both sync and remote storage features needs config.Extensions.Sync.DownloadDir to be specified")
+
+ return zerr.ErrBadConfig
+ }
}
// enforce s3 driver on subpaths in case of using storage driver
@@ -407,6 +414,14 @@ func validateConfiguration(config *config.Config, log zlog.Logger) error {
return zerr.ErrBadConfig
}
+
+ // enforce tmpDir in case sync + s3
+ if config.Extensions != nil && config.Extensions.Sync != nil && config.Extensions.Sync.DownloadDir == "" {
+ log.Error().Err(zerr.ErrBadConfig).
+ Msg("using both sync and remote storage features needs config.Extensions.Sync.DownloadDir to be specified")
+
+ return zerr.ErrBadConfig
+ }
}
}
}
diff --git a/pkg/extensions/config/sync/config.go b/pkg/extensions/config/sync/config.go
index eda4844b2..ec888a084 100644
--- a/pkg/extensions/config/sync/config.go
+++ b/pkg/extensions/config/sync/config.go
@@ -15,8 +15,11 @@ type Credentials struct {
type Config struct {
Enable *bool
CredentialsFile string
- TmpDir string
- Registries []RegistryConfig
+ /* DownloadDir is needed only in case of using cloud based storages
+ it uses regclient to first copy images into this dir (as oci layout)
+ and then move them into storage. */
+ DownloadDir string
+ Registries []RegistryConfig
}
type RegistryConfig struct {
diff --git a/pkg/extensions/extension_sync.go b/pkg/extensions/extension_sync.go
index a8f9c16f9..8c6c75baf 100644
--- a/pkg/extensions/extension_sync.go
+++ b/pkg/extensions/extension_sync.go
@@ -6,7 +6,6 @@ package extensions
import (
"net"
"net/url"
- "os"
"strings"
zerr "zotregistry.io/zot/errors"
@@ -46,14 +45,10 @@ func EnableSyncExtension(config *config.Config, metaDB mTypes.MetaDB,
continue
}
- tmpDir := config.Extensions.Sync.TmpDir
- if tmpDir == "" {
- // use an os tmpdir as tmpdir if not set
- tmpDir = os.TempDir()
- }
+ tmpDir := config.Extensions.Sync.DownloadDir
+ credsPath := config.Extensions.Sync.CredentialsFile
- service, err := sync.New(registryConfig, config.Extensions.Sync.CredentialsFile, tmpDir,
- storeController, metaDB, log)
+ service, err := sync.New(registryConfig, credsPath, tmpDir, storeController, metaDB, log)
if err != nil {
return nil, err
}
diff --git a/pkg/extensions/sync/destination.go b/pkg/extensions/sync/destination.go
index 36483d4cf..5150bc757 100644
--- a/pkg/extensions/sync/destination.go
+++ b/pkg/extensions/sync/destination.go
@@ -37,22 +37,18 @@ type DestinationRegistry struct {
}
func NewDestinationRegistry(
- storeController storage.StoreController,
- tmpStorage OciLayoutStorage,
+ storeController storage.StoreController, // local store controller
+ tempStoreController storage.StoreController, // temp store controller
metaDB mTypes.MetaDB,
log log.Logger,
) Destination {
- if tmpStorage == nil {
- // to allow passing nil we can do this, noting that it will only work for a local StoreController
- tmpStorage = NewOciLayoutStorage(storeController)
- }
return &DestinationRegistry{
storeController: storeController,
+ tempStorage: NewOciLayoutStorage(tempStoreController),
metaDB: metaDB,
// first we sync from remote (using containers/image copy from docker:// to oci:) to a temp imageStore
// then we copy the image from tempStorage to zot's storage using ImageStore APIs
- tempStorage: tmpStorage,
- log: log,
+ log: log,
}
}
@@ -288,9 +284,11 @@ func getImageStoreFromImageReference(imageReference types.ImageReference, repo,
tempRootDir = strings.ReplaceAll(imageReference.StringWithinTransport(), fmt.Sprintf("%s:", repo), "")
}
- metrics := monitoring.NewMetricsServer(false, log.Logger{})
+ return getImageStore(tempRootDir)
+}
- tempImageStore := local.NewImageStore(tempRootDir, false, false, log.Logger{}, metrics, nil, nil)
+func getImageStore(rootDir string) storageTypes.ImageStore {
+ metrics := monitoring.NewMetricsServer(false, log.Logger{})
- return tempImageStore
+ return local.NewImageStore(rootDir, false, false, log.Logger{}, metrics, nil, nil)
}
diff --git a/pkg/extensions/sync/service.go b/pkg/extensions/sync/service.go
index 63b5f360e..d8b4add3f 100644
--- a/pkg/extensions/sync/service.go
+++ b/pkg/extensions/sync/service.go
@@ -20,7 +20,6 @@ import (
"zotregistry.io/zot/pkg/log"
mTypes "zotregistry.io/zot/pkg/meta/types"
"zotregistry.io/zot/pkg/storage"
- "zotregistry.io/zot/pkg/storage/local"
)
type BaseService struct {
@@ -67,13 +66,20 @@ func New(
service.contentManager = NewContentManager(opts.Content, log)
- tmpImageStore := local.NewImageStore(tmpDir,
- false, false, log, nil, nil, nil,
- )
-
- tmpStorage := NewOciLayoutStorage(storage.StoreController{DefaultStore: tmpImageStore})
-
- service.destination = NewDestinationRegistry(storeController, tmpStorage, metadb, log)
+ if len(tmpDir) == 0 {
+ // first it will sync in tmpDir then it will move everything into local ImageStore
+ service.destination = NewDestinationRegistry(storeController, storeController, metadb, log)
+ } else {
+ // first it will sync under /rootDir/reponame/.sync/ then it will move everything into local ImageStore
+ service.destination = NewDestinationRegistry(
+ storeController,
+ storage.StoreController{
+ DefaultStore: getImageStore(tmpDir),
+ },
+ metadb,
+ log,
+ )
+ }
retryOptions := &retry.RetryOptions{}
@@ -140,7 +146,7 @@ func (service *BaseService) SetNextAvailableClient() error {
if err != nil {
service.log.Error().Err(err).Str("url", url).Msg("sync: failed to initialize http client")
- continue
+ return err
}
if !service.client.Ping() {
@@ -353,7 +359,8 @@ func (service *BaseService) SyncRepo(ctx context.Context, repo string) error {
return nil
}
-func (service *BaseService) syncTag(ctx context.Context, destinationRepo, remoteRepo, tag string) (digest.Digest, error) {
+func (service *BaseService) syncTag(ctx context.Context, destinationRepo, remoteRepo, tag string,
+) (digest.Digest, error) {
copyOptions := getCopyOptions(service.remote.GetContext(), service.destination.GetContext())
policyContext, err := getPolicyContext(service.log)
diff --git a/pkg/extensions/sync/sync_internal_test.go b/pkg/extensions/sync/sync_internal_test.go
index 538318c0c..50c572ca1 100644
--- a/pkg/extensions/sync/sync_internal_test.go
+++ b/pkg/extensions/sync/sync_internal_test.go
@@ -185,7 +185,8 @@ func TestDestinationRegistry(t *testing.T) {
syncImgStore := local.NewImageStore(dir, true, true, log, metrics, nil, cacheDriver)
repoName := "repo"
- registry := NewDestinationRegistry(storage.StoreController{DefaultStore: syncImgStore}, nil, nil, log)
+ storeController := storage.StoreController{DefaultStore: syncImgStore}
+ registry := NewDestinationRegistry(storeController, storeController, nil, log)
imageReference, err := registry.GetImageReference(repoName, "1.0")
So(err, ShouldBeNil)
So(imageReference, ShouldNotBeNil)
@@ -302,7 +303,8 @@ func TestDestinationRegistry(t *testing.T) {
syncImgStore := local.NewImageStore(dir, true, true, log, metrics, linter, cacheDriver)
repoName := "repo"
- registry := NewDestinationRegistry(storage.StoreController{DefaultStore: syncImgStore}, nil, nil, log)
+ storeController := storage.StoreController{DefaultStore: syncImgStore}
+ registry := NewDestinationRegistry(storeController, storeController, nil, log)
err = registry.CommitImage(imageReference, repoName, "1.0")
So(err, ShouldBeNil)
@@ -336,7 +338,8 @@ func TestDestinationRegistry(t *testing.T) {
})
Convey("trigger metaDB error on index manifest in CommitImage()", func() {
- registry := NewDestinationRegistry(storage.StoreController{DefaultStore: syncImgStore}, nil, mocks.MetaDBMock{
+ storeController := storage.StoreController{DefaultStore: syncImgStore}
+ registry := NewDestinationRegistry(storeController, storeController, mocks.MetaDBMock{
SetRepoReferenceFn: func(ctx context.Context, repo string, reference string, imageMeta mTypes.ImageMeta) error {
if reference == "1.0" {
return zerr.ErrRepoMetaNotFound
@@ -351,7 +354,8 @@ func TestDestinationRegistry(t *testing.T) {
})
Convey("trigger metaDB error on image manifest in CommitImage()", func() {
- registry := NewDestinationRegistry(storage.StoreController{DefaultStore: syncImgStore}, nil, mocks.MetaDBMock{
+ storeController := storage.StoreController{DefaultStore: syncImgStore}
+ registry := NewDestinationRegistry(storeController, storeController, mocks.MetaDBMock{
SetRepoReferenceFn: func(ctx context.Context, repo, reference string, imageMeta mTypes.ImageMeta) error {
return zerr.ErrRepoMetaNotFound
},
diff --git a/test/blackbox/sync.bats b/test/blackbox/sync.bats
index 36b14722e..6ec24f70c 100644
--- a/test/blackbox/sync.bats
+++ b/test/blackbox/sync.bats
@@ -139,14 +139,14 @@ EOF
EOF
git -C ${BATS_FILE_TMPDIR} clone https://github.com/project-zot/helm-charts.git
+ zot_serve ${ZOT_MINIMAL_PATH} ${zot_minimal_config_file}
+ wait_zot_reachable ${zot_port3}
+
zot_serve ${ZOT_PATH} ${zot_sync_per_config_file}
wait_zot_reachable ${zot_port1}
zot_serve ${ZOT_PATH} ${zot_sync_ondemand_config_file}
wait_zot_reachable ${zot_port2}
-
- zot_serve ${ZOT_MINIMAL_PATH} ${zot_minimal_config_file}
- wait_zot_reachable ${zot_port3}
}
function teardown_file() {
diff --git a/test/blackbox/sync_cloud.bats b/test/blackbox/sync_cloud.bats
new file mode 100644
index 000000000..a35e76700
--- /dev/null
+++ b/test/blackbox/sync_cloud.bats
@@ -0,0 +1,571 @@
+# Note: Intended to be run as "make run-blackbox-tests" or "make run-blackbox-ci"
+# Makefile target installs & checks all necessary tooling
+# Extra tools that are not covered in Makefile target needs to be added in verify_prerequisites()
+
+load helpers_zot
+load helpers_wait
+
+
+function verify_prerequisites() {
+ if [ ! $(command -v curl) ]; then
+ echo "you need to install curl as a prerequisite to running the tests" >&3
+ return 1
+ fi
+
+ if [ ! $(command -v jq) ]; then
+ echo "you need to install jq as a prerequisite to running the tests" >&3
+ return 1
+ fi
+
+ return 0
+}
+
+function setup_file() {
+ export COSIGN_PASSWORD=""
+ export COSIGN_OCI_EXPERIMENTAL=1
+ export COSIGN_EXPERIMENTAL=1
+
+ # Verify prerequisites are available
+ if ! $(verify_prerequisites); then
+ exit 1
+ fi
+
+ # Download test data to folder common for the entire suite, not just this file
+ skopeo --insecure-policy copy --format=oci docker://ghcr.io/project-zot/golang:1.20 oci:${TEST_DATA_DIR}/golang:1.20
+ # Setup zot server
+ local zot_sync_per_root_dir=${BATS_FILE_TMPDIR}/zot-per
+ local zot_sync_ondemand_root_dir=${BATS_FILE_TMPDIR}/zot-ondemand
+
+ local zot_sync_per_config_file=${BATS_FILE_TMPDIR}/zot_sync_per_config.json
+ local zot_sync_ondemand_config_file=${BATS_FILE_TMPDIR}/zot_sync_ondemand_config.json
+
+ local zot_minimal_root_dir=${BATS_FILE_TMPDIR}/zot-minimal
+ local zot_minimal_config_file=${BATS_FILE_TMPDIR}/zot_minimal_config.json
+
+ local oci_data_dir=${BATS_FILE_TMPDIR}/oci
+ mkdir -p ${zot_sync_per_root_dir}
+ mkdir -p ${zot_sync_ondemand_root_dir}
+ mkdir -p ${zot_minimal_root_dir}
+ mkdir -p ${oci_data_dir}
+ zot_port1=$(get_free_port)
+ echo ${zot_port1} > ${BATS_FILE_TMPDIR}/zot.port1
+ zot_port2=$(get_free_port)
+ echo ${zot_port2} > ${BATS_FILE_TMPDIR}/zot.port2
+ zot_port3=$(get_free_port)
+ echo ${zot_port3} > ${BATS_FILE_TMPDIR}/zot.port3
+
+ cat >${zot_sync_per_config_file} <${zot_sync_ondemand_config_file} <${zot_minimal_config_file} <${trust_policy_file} < config.json
+ echo "hello world" > artifact.txt
+ run oras push --plain-http 127.0.0.1:${zot_port3}/hello-artifact:v2 \
+ --config config.json:application/vnd.acme.rocket.config.v1+json artifact.txt:text/plain -d -v
+ [ "$status" -eq 0 ]
+ rm -f artifact.txt
+ rm -f config.json
+}
+
+@test "sync oras artifact periodically" {
+ zot_port1=`cat ${BATS_FILE_TMPDIR}/zot.port1`
+ # wait for oras artifact to be copied
+ run sleep 15s
+ run oras pull --plain-http 127.0.0.1:${zot_port1}/hello-artifact:v2 -d -v
+ [ "$status" -eq 0 ]
+ grep -q "hello world" artifact.txt
+ rm -f artifact.txt
+}
+
+@test "sync oras artifact on demand" {
+ zot_port2=`cat ${BATS_FILE_TMPDIR}/zot.port2`
+ run oras pull --plain-http 127.0.0.1:${zot_port2}/hello-artifact:v2 -d -v
+ [ "$status" -eq 0 ]
+ grep -q "hello world" artifact.txt
+ rm -f artifact.txt
+}
+
+# sync helm chart
+@test "push helm chart" {
+ zot_port3=`cat ${BATS_FILE_TMPDIR}/zot.port3`
+ run helm package ${BATS_FILE_TMPDIR}/helm-charts/charts/zot -d ${BATS_FILE_TMPDIR}
+ [ "$status" -eq 0 ]
+ local chart_version=$(awk '/version/{printf $2}' ${BATS_FILE_TMPDIR}/helm-charts/charts/zot/Chart.yaml)
+ run helm push ${BATS_FILE_TMPDIR}/zot-${chart_version}.tgz oci://localhost:${zot_port3}/zot-chart
+ [ "$status" -eq 0 ]
+}
+
+@test "sync helm chart periodically" {
+ zot_port1=`cat ${BATS_FILE_TMPDIR}/zot.port1`
+ # wait for helm chart to be copied
+ run sleep 15s
+
+ local chart_version=$(awk '/version/{printf $2}' ${BATS_FILE_TMPDIR}/helm-charts/charts/zot/Chart.yaml)
+ run helm pull oci://localhost:${zot_port1}/zot-chart/zot --version ${chart_version} -d ${BATS_FILE_TMPDIR}
+ [ "$status" -eq 0 ]
+}
+
+@test "sync helm chart on demand" {
+ zot_port2=`cat ${BATS_FILE_TMPDIR}/zot.port2`
+ local chart_version=$(awk '/version/{printf $2}' ${BATS_FILE_TMPDIR}/helm-charts/charts/zot/Chart.yaml)
+ run helm pull oci://localhost:${zot_port2}/zot-chart/zot --version ${chart_version} -d ${BATS_FILE_TMPDIR}
+ [ "$status" -eq 0 ]
+}
+
+# sync OCI artifacts
+@test "push OCI artifact (oci image mediatype) with regclient" {
+ zot_port1=`cat ${BATS_FILE_TMPDIR}/zot.port1`
+ zot_port2=`cat ${BATS_FILE_TMPDIR}/zot.port2`
+ zot_port3=`cat ${BATS_FILE_TMPDIR}/zot.port3`
+ run regctl registry set localhost:${zot_port3} --tls disabled
+ run regctl registry set localhost:${zot_port1} --tls disabled
+ run regctl registry set localhost:${zot_port2} --tls disabled
+
+ run regctl artifact put localhost:${zot_port3}/artifact:demo <&3
- return 1
- fi
-
- if [ ! $(command -v jq) ]; then
- echo "you need to install jq as a prerequisite to running the tests" >&3
- return 1
- fi
-
- return 0
-}
-
-function setup_file() {
- export COSIGN_PASSWORD=""
- export COSIGN_OCI_EXPERIMENTAL=1
- export COSIGN_EXPERIMENTAL=1
-
- # Verify prerequisites are available
- if ! $(verify_prerequisites); then
- exit 1
- fi
-
- # Download test data to folder common for the entire suite, not just this file
- skopeo --insecure-policy copy --format=oci docker://ghcr.io/project-zot/golang:1.20 oci:${TEST_DATA_DIR}/golang:1.20
-
-
- # Setup zot server
- local zot_sync_per_root_dir=${BATS_FILE_TMPDIR}/zot-per
- local zot_sync_ondemand_root_dir=${BATS_FILE_TMPDIR}/zot-ondemand
-
- local zot_sync_per_config_file=${BATS_FILE_TMPDIR}/zot_sync_per_config.json
- local zot_sync_ondemand_config_file=${BATS_FILE_TMPDIR}/zot_sync_ondemand_config.json
-
- local zot_minimal_root_dir=${BATS_FILE_TMPDIR}/zot-minimal
- local zot_minimal_config_file=${BATS_FILE_TMPDIR}/zot_minimal_config.json
-
- local oci_data_dir=${BATS_FILE_TMPDIR}/oci
- mkdir -p ${zot_sync_per_root_dir}
- mkdir -p ${zot_sync_ondemand_root_dir}
- mkdir -p ${zot_minimal_root_dir}
- mkdir -p ${oci_data_dir}
-
- cat >${zot_sync_per_config_file} <${zot_sync_ondemand_config_file} <${zot_minimal_config_file} <${trust_policy_file} < config.json
-echo "hello world" > artifact.txt
-run oras push --plain-http 127.0.0.1:9000/hello-artifact:v2 \
- --config config.json:application/vnd.acme.rocket.config.v1+json artifact.txt:text/plain -d -v
- [ "$status" -eq 0 ]
- rm -f artifact.txt
- rm -f config.json
-}
-
-@test "sync oras artifact periodically" {
-# # wait for oras artifact to be copied
-run sleep 15s
-run oras pull --plain-http 127.0.0.1:8081/hello-artifact:v2 -d -v
-[ "$status" -eq 0 ]
-grep -q "hello world" artifact.txt
-rm -f artifact.txt
-}
-
-@test "sync oras artifact on demand" {
-run oras pull --plain-http 127.0.0.1:8082/hello-artifact:v2 -d -v
-[ "$status" -eq 0 ]
-grep -q "hello world" artifact.txt
-rm -f artifact.txt
-}
-
-# sync helm chart
-@test "push helm chart" {
-run helm package ${BATS_FILE_TMPDIR}/helm-charts/charts/zot -d ${BATS_FILE_TMPDIR}
-[ "$status" -eq 0 ]
-local chart_version=$(awk '/version/{printf $2}' ${BATS_FILE_TMPDIR}/helm-charts/charts/zot/Chart.yaml)
-run helm push ${BATS_FILE_TMPDIR}/zot-${chart_version}.tgz oci://localhost:9000/zot-chart
-[ "$status" -eq 0 ]
-}
-
-@test "sync helm chart periodically" {
-# wait for helm chart to be copied
-run sleep 15s
-
-local chart_version=$(awk '/version/{printf $2}' ${BATS_FILE_TMPDIR}/helm-charts/charts/zot/Chart.yaml)
-run helm pull oci://localhost:8081/zot-chart/zot --version ${chart_version} -d ${BATS_FILE_TMPDIR}
-[ "$status" -eq 0 ]
-}
-
-@test "sync helm chart on demand" {
-local chart_version=$(awk '/version/{printf $2}' ${BATS_FILE_TMPDIR}/helm-charts/charts/zot/Chart.yaml)
-run helm pull oci://localhost:8082/zot-chart/zot --version ${chart_version} -d ${BATS_FILE_TMPDIR}
-[ "$status" -eq 0 ]
-}
-
-# sync OCI artifacts
-@test "push OCI artifact (oci image mediatype) with regclient" {
-run regctl registry set localhost:9000 --tls disabled
-run regctl registry set localhost:8081 --tls disabled
-run regctl registry set localhost:8082 --tls disabled
-
-run regctl artifact put localhost:9000/artifact:demo <