mirror of
https://github.com/project-zot/zot.git
synced 2026-06-17 12:58:02 +08:00
GCS storage support (#3798)
feat(storage): add a GCS driver test(storage): add unit tests for GCS driver test(storage): add missing unit tests for GCS driver & resolve lint issues fix: configuration validation for GCS Storage test(storage): resolve panic by test due to setupGCS ignoring returned error test(storage): add dummy gcs credentials test: add darwin support for macos to run tests ci: update workflows to pin gcs emulator version lint: resolve long line lengths & formatting issues test: move error for gcs mock earlier with an error test: stop test using local google credentials and use mock instead test: add missing dummy creds test(storage): use storage-testbench for GCS, isolate GCS tests, fix driver Delete - Switch GCS emulator from fake-gcs-server to storage-testbench in CI. Run the GCS emulator only in the privileged-test job; remove it from minimal and extended test jobs. - Consolidate GCS tests under pkg/storage/gcs (needprivileges,linux). Add TestMain with HTTPS proxy and /etc/hosts so tests talk to storage-testbench; move GCS-specific cases from storage_test.go and scrub_test.go into gcs_test.go. Run GCS tests via a second privileged-test invocation and collect coverage in coverage-needprivileges-gcs.txt. - Make GCS driver Delete idempotent and normalize errors. Treat PathNotFoundError from Delete as success so that deleting an already-gone path (e.g. after GC under eventual consistency) does not fail. Add formatErr to map 404/not found to PathNotFoundError and use it for all driver methods so callers get consistent storage driver errors. - Drop GCS branches and helpers from storage_test.go and scrub_test.go so non-privileged tests only use local/S3; GCS is tested only in pkg/storage/gcs with storage-testbench. - Set GCSMOCK_ENDPOINT without /storage/v1/, as the rest of the URL is set in tests. - Show errors in case of failure to create bucket. - Consolidate StorageDriverMock structs inside the pkg/test/mocks package. Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com> Co-authored-by: Steven Marks <steve.marks@qomodo.io>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
//go:build needprivileges
|
||||
//go:build needprivileges && linux
|
||||
|
||||
package config_test
|
||||
|
||||
|
||||
+57
-45
@@ -299,6 +299,10 @@ func getStorageType(storageDriver map[string]any) string {
|
||||
return storageConstants.S3StorageDriverName
|
||||
}
|
||||
|
||||
if storeName == storageConstants.GCSStorageDriverName {
|
||||
return storageConstants.GCSStorageDriverName
|
||||
}
|
||||
|
||||
return storeName
|
||||
}
|
||||
|
||||
@@ -559,6 +563,57 @@ func validateExtensionsConfig(cfg *config.Config, logger zlog.Logger) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateStorageConfigSection(
|
||||
cfg *config.Config, logger zlog.Logger, storageConfig config.GlobalStorageConfig,
|
||||
) error {
|
||||
if len(storageConfig.StorageDriver) != 0 {
|
||||
// enforce s3/gcs driver in case of using storage driver
|
||||
if storageConfig.StorageDriver["name"] != storageConstants.S3StorageDriverName &&
|
||||
storageConfig.StorageDriver["name"] != storageConstants.GCSStorageDriverName {
|
||||
msg := "unsupported storage driver"
|
||||
logger.Error().Err(zerr.ErrBadConfig).Interface("storageDriver", storageConfig.StorageDriver["name"]).Msg(msg)
|
||||
|
||||
return fmt.Errorf("%w: %s", zerr.ErrBadConfig, msg)
|
||||
}
|
||||
|
||||
// enforce tmpDir in case sync + s3/gcs
|
||||
extensionsConfig := cfg.CopyExtensionsConfig()
|
||||
if extensionsConfig.IsSyncEnabled() && extensionsConfig.Sync.DownloadDir == "" {
|
||||
msg := "using both sync and remote storage features needs config.Extensions.Sync.DownloadDir to be specified"
|
||||
logger.Error().Err(zerr.ErrBadConfig).Msg(msg)
|
||||
|
||||
return fmt.Errorf("%w: %s", zerr.ErrBadConfig, msg)
|
||||
}
|
||||
}
|
||||
|
||||
// enforce s3/gcs driver on subpaths in case of using storage driver
|
||||
if len(storageConfig.SubPaths) > 0 {
|
||||
for route, subStorageConfig := range storageConfig.SubPaths {
|
||||
if len(subStorageConfig.StorageDriver) != 0 {
|
||||
if subStorageConfig.StorageDriver["name"] != storageConstants.S3StorageDriverName &&
|
||||
subStorageConfig.StorageDriver["name"] != storageConstants.GCSStorageDriverName {
|
||||
msg := "unsupported storage driver"
|
||||
logger.Error().Err(zerr.ErrBadConfig).Str("subpath", route).Interface("storageDriver",
|
||||
subStorageConfig.StorageDriver["name"]).Msg(msg)
|
||||
|
||||
return fmt.Errorf("%w: %s", zerr.ErrBadConfig, msg)
|
||||
}
|
||||
|
||||
// enforce tmpDir in case sync + s3/gcs
|
||||
extensionsConfig := cfg.CopyExtensionsConfig()
|
||||
if extensionsConfig.IsSyncEnabled() && extensionsConfig.Sync.DownloadDir == "" {
|
||||
msg := "using both sync and remote storage features needs config.Extensions.Sync.DownloadDir to be specified"
|
||||
logger.Error().Err(zerr.ErrBadConfig).Msg(msg)
|
||||
|
||||
return fmt.Errorf("%w: %s", zerr.ErrBadConfig, msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateConfiguration(config *config.Config, logger zlog.Logger) error {
|
||||
if err := validateHTTP(config, logger); err != nil {
|
||||
return err
|
||||
@@ -614,51 +669,8 @@ func validateConfiguration(config *config.Config, logger zlog.Logger) error {
|
||||
}
|
||||
|
||||
storageConfig := config.CopyStorageConfig()
|
||||
if len(storageConfig.StorageDriver) != 0 {
|
||||
// enforce s3 driver in case of using storage driver
|
||||
if storageConfig.StorageDriver["name"] != storageConstants.S3StorageDriverName {
|
||||
msg := "unsupported storage driver"
|
||||
logger.Error().Err(zerr.ErrBadConfig).Interface("cacheDriver", storageConfig.StorageDriver["name"]).Msg(msg)
|
||||
|
||||
return fmt.Errorf("%w: %s", zerr.ErrBadConfig, msg)
|
||||
}
|
||||
|
||||
// enforce tmpDir in case sync + s3
|
||||
extensionsConfig := config.CopyExtensionsConfig()
|
||||
if extensionsConfig.IsSyncEnabled() && extensionsConfig.Sync.DownloadDir == "" {
|
||||
msg := "using both sync and remote storage features needs config.Extensions.Sync.DownloadDir to be specified"
|
||||
logger.Error().Err(zerr.ErrBadConfig).Msg(msg)
|
||||
|
||||
return fmt.Errorf("%w: %s", zerr.ErrBadConfig, msg)
|
||||
}
|
||||
}
|
||||
|
||||
// enforce s3 driver on subpaths in case of using storage driver
|
||||
if storageConfig.SubPaths != nil {
|
||||
if len(storageConfig.SubPaths) > 0 {
|
||||
subPaths := storageConfig.SubPaths
|
||||
|
||||
for route, subStorageConfig := range subPaths {
|
||||
if len(subStorageConfig.StorageDriver) != 0 {
|
||||
if subStorageConfig.StorageDriver["name"] != storageConstants.S3StorageDriverName {
|
||||
msg := "unsupported storage driver"
|
||||
logger.Error().Err(zerr.ErrBadConfig).Str("subpath", route).Interface("storageDriver",
|
||||
subStorageConfig.StorageDriver["name"]).Msg(msg)
|
||||
|
||||
return fmt.Errorf("%w: %s", zerr.ErrBadConfig, msg)
|
||||
}
|
||||
|
||||
// enforce tmpDir in case sync + s3
|
||||
extensionsConfig := config.CopyExtensionsConfig()
|
||||
if extensionsConfig.IsSyncEnabled() && extensionsConfig.Sync.DownloadDir == "" {
|
||||
msg := "using both sync and remote storage features needs config.Extensions.Sync.DownloadDir to be specified"
|
||||
logger.Error().Err(zerr.ErrBadConfig).Msg(msg)
|
||||
|
||||
return fmt.Errorf("%w: %s", zerr.ErrBadConfig, msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := validateStorageConfigSection(config, logger, storageConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check glob patterns in authz config are compilable
|
||||
|
||||
@@ -24,5 +24,6 @@ const (
|
||||
DefaultGCDelay = 1 * time.Hour
|
||||
DefaultGCInterval = 1 * time.Hour
|
||||
S3StorageDriverName = "s3"
|
||||
GCSStorageDriverName = "gcs"
|
||||
LocalStorageDriverName = "local"
|
||||
)
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
package gcs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
// Add gcs support.
|
||||
storagedriver "github.com/distribution/distribution/v3/registry/storage/driver"
|
||||
_ "github.com/distribution/distribution/v3/registry/storage/driver/gcs"
|
||||
|
||||
storageConstants "zotregistry.dev/zot/v2/pkg/storage/constants"
|
||||
)
|
||||
|
||||
type Driver struct {
|
||||
store storagedriver.StorageDriver
|
||||
}
|
||||
|
||||
func New(storeDriver storagedriver.StorageDriver) *Driver {
|
||||
return &Driver{store: storeDriver}
|
||||
}
|
||||
|
||||
func (driver *Driver) Name() string {
|
||||
return storageConstants.GCSStorageDriverName
|
||||
}
|
||||
|
||||
func (driver *Driver) EnsureDir(path string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (driver *Driver) DirExists(path string) bool {
|
||||
if fi, err := driver.Stat(path); err == nil && fi.IsDir() {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (driver *Driver) Reader(path string, offset int64) (io.ReadCloser, error) {
|
||||
reader, err := driver.store.Reader(context.Background(), path, offset)
|
||||
if err != nil {
|
||||
return nil, driver.formatErr(err, path)
|
||||
}
|
||||
|
||||
return reader, nil
|
||||
}
|
||||
|
||||
func (driver *Driver) ReadFile(path string) ([]byte, error) {
|
||||
content, err := driver.store.GetContent(context.Background(), path)
|
||||
if err != nil {
|
||||
return nil, driver.formatErr(err, path)
|
||||
}
|
||||
|
||||
return content, nil
|
||||
}
|
||||
|
||||
func (driver *Driver) Delete(path string) error {
|
||||
err := driver.store.Delete(context.Background(), path)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Format the error first to convert GCS-specific 404 errors to PathNotFoundError
|
||||
formattedErr := driver.formatErr(err, path)
|
||||
|
||||
// Check if the formatted error is PathNotFoundError
|
||||
var pathNotFoundErr storagedriver.PathNotFoundError
|
||||
if errors.As(formattedErr, &pathNotFoundErr) {
|
||||
// For directory deletion, if the path doesn't exist, treat it as success (idempotent delete)
|
||||
// In GCS, directories are just prefixes, so if all objects are deleted,
|
||||
// the directory may already be gone (especially with eventual consistency in storage-testbench)
|
||||
// This makes Delete idempotent: deleting a non-existent path is a no-op
|
||||
return nil
|
||||
}
|
||||
|
||||
return formattedErr
|
||||
}
|
||||
|
||||
func (driver *Driver) Stat(path string) (storagedriver.FileInfo, error) {
|
||||
fileInfo, err := driver.store.Stat(context.Background(), path)
|
||||
if err != nil {
|
||||
return nil, driver.formatErr(err, path)
|
||||
}
|
||||
|
||||
return fileInfo, nil
|
||||
}
|
||||
|
||||
func (driver *Driver) Writer(filepath string, append bool) (storagedriver.FileWriter, error) { //nolint:predeclared
|
||||
writer, err := driver.store.Writer(context.Background(), filepath, append)
|
||||
if err != nil {
|
||||
return nil, driver.formatErr(err, filepath)
|
||||
}
|
||||
|
||||
return writer, nil
|
||||
}
|
||||
|
||||
func (driver *Driver) WriteFile(filepath string, content []byte) (int, error) {
|
||||
var n int
|
||||
|
||||
stwr, err := driver.store.Writer(context.Background(), filepath, false)
|
||||
if err != nil {
|
||||
return -1, driver.formatErr(err, filepath)
|
||||
}
|
||||
defer stwr.Close()
|
||||
|
||||
if n, err = stwr.Write(content); err != nil {
|
||||
return -1, driver.formatErr(err, filepath)
|
||||
}
|
||||
|
||||
if err := stwr.Commit(context.Background()); err != nil {
|
||||
return -1, driver.formatErr(err, filepath)
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (driver *Driver) Walk(path string, f storagedriver.WalkFn) error {
|
||||
return driver.formatErr(driver.store.Walk(context.Background(), path, f), path)
|
||||
}
|
||||
|
||||
func (driver *Driver) List(fullpath string) ([]string, error) {
|
||||
list, err := driver.store.List(context.Background(), fullpath)
|
||||
if err != nil {
|
||||
return nil, driver.formatErr(err, fullpath)
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (driver *Driver) Move(sourcePath string, destPath string) error {
|
||||
return driver.formatErr(driver.store.Move(context.Background(), sourcePath, destPath), sourcePath)
|
||||
}
|
||||
|
||||
func (driver *Driver) SameFile(path1, path2 string) bool {
|
||||
fi1, _ := driver.store.Stat(context.Background(), path1)
|
||||
|
||||
fi2, _ := driver.store.Stat(context.Background(), path2)
|
||||
|
||||
if fi1 != nil && fi2 != nil {
|
||||
if fi1.IsDir() == fi2.IsDir() &&
|
||||
fi1.ModTime() == fi2.ModTime() &&
|
||||
fi1.Path() == fi2.Path() &&
|
||||
fi1.Size() == fi2.Size() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Link puts an empty file that will act like a link between the original file and deduped one.
|
||||
// Because gcs doesn't support symlinks, wherever the storage will encounter an empty file, it will get the original one
|
||||
// from cache.
|
||||
func (driver *Driver) Link(src, dest string) error {
|
||||
return driver.formatErr(driver.store.PutContent(context.Background(), dest, []byte{}), dest)
|
||||
}
|
||||
|
||||
// formatErr converts GCS-specific 404/not found errors to PathNotFoundError.
|
||||
func (driver *Driver) formatErr(err error, path string) error {
|
||||
switch actual := err.(type) { //nolint: errorlint
|
||||
case nil:
|
||||
return nil
|
||||
case storagedriver.PathNotFoundError:
|
||||
actual.DriverName = driver.Name()
|
||||
if actual.Path == "" && path != "" {
|
||||
actual.Path = path
|
||||
}
|
||||
|
||||
return actual
|
||||
case storagedriver.InvalidPathError:
|
||||
actual.DriverName = driver.Name()
|
||||
|
||||
return actual
|
||||
case storagedriver.InvalidOffsetError:
|
||||
actual.DriverName = driver.Name()
|
||||
|
||||
return actual
|
||||
default:
|
||||
// Check for GCS-specific 404/not found errors by unwrapping the error chain
|
||||
errToCheck := err
|
||||
for errToCheck != nil {
|
||||
errStr := errToCheck.Error()
|
||||
isNotFound := strings.Contains(errStr, "object doesn't exist") ||
|
||||
strings.Contains(errStr, "Error 404") ||
|
||||
strings.Contains(errStr, "does not exist")
|
||||
|
||||
if isNotFound {
|
||||
return storagedriver.PathNotFoundError{
|
||||
DriverName: driver.Name(),
|
||||
Path: path,
|
||||
}
|
||||
}
|
||||
|
||||
if unwrappable, ok := errToCheck.(interface{ Unwrap() error }); ok {
|
||||
errToCheck = unwrappable.Unwrap()
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
storageError := storagedriver.Error{
|
||||
DriverName: driver.Name(),
|
||||
Detail: err,
|
||||
}
|
||||
|
||||
return storageError
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,364 @@
|
||||
package gcs_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/distribution/distribution/v3/registry/storage/driver"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"zotregistry.dev/zot/v2/pkg/extensions/monitoring"
|
||||
zlog "zotregistry.dev/zot/v2/pkg/log"
|
||||
"zotregistry.dev/zot/v2/pkg/storage/gcs"
|
||||
"zotregistry.dev/zot/v2/pkg/test/mocks"
|
||||
)
|
||||
|
||||
var errTest = errors.New("error")
|
||||
|
||||
type fileInfoMock struct {
|
||||
isDir bool
|
||||
size int64
|
||||
modTime time.Time
|
||||
path string
|
||||
}
|
||||
|
||||
func (f *fileInfoMock) Path() string { return f.path }
|
||||
func (f *fileInfoMock) Size() int64 { return f.size }
|
||||
func (f *fileInfoMock) ModTime() time.Time { return f.modTime }
|
||||
func (f *fileInfoMock) IsDir() bool { return f.isDir }
|
||||
|
||||
func TestDriver(t *testing.T) {
|
||||
Convey("GCS Driver", t, func() {
|
||||
storeMock := &mocks.StorageDriverMock{}
|
||||
gcsDriver := gcs.New(storeMock)
|
||||
|
||||
Convey("Name", func() {
|
||||
So(gcsDriver.Name(), ShouldEqual, "gcs")
|
||||
})
|
||||
|
||||
Convey("EnsureDir", func() {
|
||||
err := gcsDriver.EnsureDir("/test")
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("DirExists", func() {
|
||||
Convey("True", func() {
|
||||
storeMock.StatFn = func(ctx context.Context, path string) (driver.FileInfo, error) {
|
||||
return &mocks.FileInfoMock{
|
||||
IsDirFn: func() bool { return true },
|
||||
}, nil
|
||||
}
|
||||
So(gcsDriver.DirExists("/test"), ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("False - Not a dir", func() {
|
||||
storeMock.StatFn = func(ctx context.Context, path string) (driver.FileInfo, error) {
|
||||
return &mocks.FileInfoMock{
|
||||
IsDirFn: func() bool { return false },
|
||||
}, nil
|
||||
}
|
||||
So(gcsDriver.DirExists("/test"), ShouldBeFalse)
|
||||
})
|
||||
|
||||
Convey("False - Error", func() {
|
||||
storeMock.StatFn = func(ctx context.Context, path string) (driver.FileInfo, error) {
|
||||
return nil, errTest
|
||||
}
|
||||
So(gcsDriver.DirExists("/test"), ShouldBeFalse)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Reader", func() {
|
||||
Convey("Success", func() {
|
||||
storeMock.ReaderFn = func(ctx context.Context, path string, offset int64) (io.ReadCloser, error) {
|
||||
return io.NopCloser(strings.NewReader("")), nil
|
||||
}
|
||||
r, err := gcsDriver.Reader("/test", 0)
|
||||
So(err, ShouldBeNil)
|
||||
So(r, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("InvalidOffsetError", func() {
|
||||
storeMock.ReaderFn = func(ctx context.Context, path string, offset int64) (io.ReadCloser, error) {
|
||||
return nil, driver.InvalidOffsetError{Path: path, Offset: offset}
|
||||
}
|
||||
_, err := gcsDriver.Reader("/test", 100)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
var invalidOffset driver.InvalidOffsetError
|
||||
So(errors.As(err, &invalidOffset), ShouldBeTrue)
|
||||
So(invalidOffset.DriverName, ShouldEqual, "gcs")
|
||||
})
|
||||
})
|
||||
|
||||
Convey("ReadFile", func() {
|
||||
Convey("Success", func() {
|
||||
storeMock.GetContentFn = func(ctx context.Context, path string) ([]byte, error) {
|
||||
return []byte("content"), nil
|
||||
}
|
||||
content, err := gcsDriver.ReadFile("/test")
|
||||
So(err, ShouldBeNil)
|
||||
So(string(content), ShouldEqual, "content")
|
||||
})
|
||||
|
||||
Convey("PathNotFoundError with empty Path gets path set", func() {
|
||||
storeMock.GetContentFn = func(ctx context.Context, path string) ([]byte, error) {
|
||||
return nil, driver.PathNotFoundError{Path: ""} // Path empty so driver sets it
|
||||
}
|
||||
_, err := gcsDriver.ReadFile("/requested/path")
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
var pathErr driver.PathNotFoundError
|
||||
So(errors.As(err, &pathErr), ShouldBeTrue)
|
||||
So(pathErr.DriverName, ShouldEqual, "gcs")
|
||||
So(pathErr.Path, ShouldEqual, "/requested/path")
|
||||
})
|
||||
|
||||
Convey("GCS not-found string becomes PathNotFoundError", func() {
|
||||
for _, msg := range []string{"object doesn't exist", "Error 404", "does not exist"} {
|
||||
errMsg := msg
|
||||
storeMock.GetContentFn = func(ctx context.Context, path string) ([]byte, error) {
|
||||
//nolint:err113 // test needs variable not-found message
|
||||
return nil, fmt.Errorf("%s", errMsg)
|
||||
}
|
||||
_, err := gcsDriver.ReadFile("/key")
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
var pathErr driver.PathNotFoundError
|
||||
So(errors.As(err, &pathErr), ShouldBeTrue)
|
||||
So(pathErr.Path, ShouldEqual, "/key")
|
||||
}
|
||||
})
|
||||
|
||||
Convey("Generic error becomes storagedriver.Error", func() {
|
||||
storeMock.GetContentFn = func(ctx context.Context, path string) ([]byte, error) {
|
||||
return nil, errTest
|
||||
}
|
||||
_, err := gcsDriver.ReadFile("/test")
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
var storageErr driver.Error
|
||||
So(errors.As(err, &storageErr), ShouldBeTrue)
|
||||
So(storageErr.DriverName, ShouldEqual, "gcs")
|
||||
So(storageErr.Detail, ShouldEqual, errTest)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Delete", func() {
|
||||
Convey("Success", func() {
|
||||
storeMock.DeleteFn = func(ctx context.Context, path string) error {
|
||||
return nil
|
||||
}
|
||||
err := gcsDriver.Delete("/test")
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("PathNotFoundError is idempotent (return nil)", func() {
|
||||
storeMock.DeleteFn = func(ctx context.Context, path string) error {
|
||||
return driver.PathNotFoundError{Path: path}
|
||||
}
|
||||
err := gcsDriver.Delete("/nonexistent")
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("Other error is returned", func() {
|
||||
storeMock.DeleteFn = func(ctx context.Context, path string) error {
|
||||
return errTest
|
||||
}
|
||||
err := gcsDriver.Delete("/test")
|
||||
So(err, ShouldNotBeNil)
|
||||
So(errors.Is(err, errTest), ShouldBeFalse) // wrapped in storagedriver.Error
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Stat", func() {
|
||||
Convey("Success", func() {
|
||||
storeMock.StatFn = func(ctx context.Context, path string) (driver.FileInfo, error) {
|
||||
return &mocks.FileInfoMock{}, nil
|
||||
}
|
||||
fi, err := gcsDriver.Stat("/test")
|
||||
So(err, ShouldBeNil)
|
||||
So(fi, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("InvalidPathError", func() {
|
||||
storeMock.StatFn = func(ctx context.Context, path string) (driver.FileInfo, error) {
|
||||
return nil, driver.InvalidPathError{Path: path}
|
||||
}
|
||||
_, err := gcsDriver.Stat("/bad")
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
var invalidPath driver.InvalidPathError
|
||||
So(errors.As(err, &invalidPath), ShouldBeTrue)
|
||||
So(invalidPath.DriverName, ShouldEqual, "gcs")
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Writer", func() {
|
||||
Convey("Success", func() {
|
||||
storeMock.WriterFn = func(ctx context.Context, path string, isAppend bool) (driver.FileWriter, error) {
|
||||
return &mocks.FileWriterMock{}, nil
|
||||
}
|
||||
w, err := gcsDriver.Writer("/test", false)
|
||||
So(err, ShouldBeNil)
|
||||
So(w, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("Error", func() {
|
||||
storeMock.WriterFn = func(ctx context.Context, path string, isAppend bool) (driver.FileWriter, error) {
|
||||
return nil, errTest
|
||||
}
|
||||
_, err := gcsDriver.Writer("/test", false)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("WriteFile", func() {
|
||||
Convey("Success", func() {
|
||||
storeMock.WriterFn = func(ctx context.Context, path string, isAppend bool) (driver.FileWriter, error) {
|
||||
return &mocks.FileWriterMock{
|
||||
WriteFn: func(p []byte) (int, error) {
|
||||
return len(p), nil
|
||||
},
|
||||
CommitFn: func() error {
|
||||
return nil
|
||||
},
|
||||
CloseFn: func() error {
|
||||
return nil
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
n, err := gcsDriver.WriteFile("/test", []byte("content"))
|
||||
So(err, ShouldBeNil)
|
||||
So(n, ShouldEqual, 7)
|
||||
})
|
||||
|
||||
Convey("Writer Error", func() {
|
||||
storeMock.WriterFn = func(ctx context.Context, path string, isAppend bool) (driver.FileWriter, error) {
|
||||
return nil, errTest
|
||||
}
|
||||
_, err := gcsDriver.WriteFile("/test", []byte("content"))
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("Write Error", func() {
|
||||
storeMock.WriterFn = func(ctx context.Context, path string, isAppend bool) (driver.FileWriter, error) {
|
||||
return &mocks.FileWriterMock{
|
||||
WriteFn: func(p []byte) (int, error) {
|
||||
return 0, errTest
|
||||
},
|
||||
CloseFn: func() error { return nil },
|
||||
}, nil
|
||||
}
|
||||
_, err := gcsDriver.WriteFile("/test", []byte("content"))
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("Commit Error", func() {
|
||||
storeMock.WriterFn = func(ctx context.Context, path string, isAppend bool) (driver.FileWriter, error) {
|
||||
return &mocks.FileWriterMock{
|
||||
WriteFn: func(p []byte) (int, error) {
|
||||
return len(p), nil
|
||||
},
|
||||
CommitFn: func() error {
|
||||
return errTest
|
||||
},
|
||||
CloseFn: func() error { return nil },
|
||||
}, nil
|
||||
}
|
||||
_, err := gcsDriver.WriteFile("/test", []byte("content"))
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Walk", func() {
|
||||
storeMock.WalkFn = func(ctx context.Context, path string, f driver.WalkFn, _ ...func(*driver.WalkOptions)) error {
|
||||
return nil
|
||||
}
|
||||
err := gcsDriver.Walk("/test", nil)
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("List", func() {
|
||||
Convey("Success", func() {
|
||||
storeMock.ListFn = func(ctx context.Context, path string) ([]string, error) {
|
||||
return []string{"a"}, nil
|
||||
}
|
||||
l, err := gcsDriver.List("/test")
|
||||
So(err, ShouldBeNil)
|
||||
So(l, ShouldResemble, []string{"a"})
|
||||
})
|
||||
|
||||
Convey("Error", func() {
|
||||
storeMock.ListFn = func(ctx context.Context, path string) ([]string, error) {
|
||||
return nil, errTest
|
||||
}
|
||||
_, err := gcsDriver.List("/test")
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Move", func() {
|
||||
storeMock.MoveFn = func(ctx context.Context, sourcePath, destPath string) error {
|
||||
return nil
|
||||
}
|
||||
err := gcsDriver.Move("/src", "/dst")
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("SameFile", func() {
|
||||
Convey("True", func() {
|
||||
now := time.Now()
|
||||
storeMock.StatFn = func(ctx context.Context, path string) (driver.FileInfo, error) {
|
||||
return &fileInfoMock{
|
||||
isDir: false,
|
||||
size: 10,
|
||||
modTime: now,
|
||||
path: "/canonical/path",
|
||||
}, nil
|
||||
}
|
||||
So(gcsDriver.SameFile("/path1", "/path2"), ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("False - Different ModTime", func() {
|
||||
storeMock.StatFn = func(ctx context.Context, path string) (driver.FileInfo, error) {
|
||||
modTime := time.Now()
|
||||
if path == "/path2" {
|
||||
modTime = modTime.Add(1 * time.Hour)
|
||||
}
|
||||
|
||||
return &fileInfoMock{
|
||||
isDir: false,
|
||||
size: 10,
|
||||
modTime: modTime,
|
||||
path: path,
|
||||
}, nil
|
||||
}
|
||||
So(gcsDriver.SameFile("/path1", "/path2"), ShouldBeFalse)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Link", func() {
|
||||
storeMock.PutContentFn = func(ctx context.Context, path string, content []byte) error {
|
||||
return nil
|
||||
}
|
||||
err := gcsDriver.Link("/src", "/dst")
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewImageStore(t *testing.T) {
|
||||
Convey("NewImageStore", t, func() {
|
||||
storeMock := &mocks.StorageDriverMock{}
|
||||
log := zlog.NewTestLogger()
|
||||
metrics := monitoring.NewMetricsServer(false, log)
|
||||
imgStore := gcs.NewImageStore("/tmp", "/tmp", true, true, log, metrics, nil, storeMock, nil, nil, nil)
|
||||
So(imgStore, ShouldNotBeNil)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package gcs
|
||||
|
||||
import (
|
||||
// Add gcs support.
|
||||
"github.com/distribution/distribution/v3/registry/storage/driver"
|
||||
// Load gcs driver.
|
||||
_ "github.com/distribution/distribution/v3/registry/storage/driver/gcs"
|
||||
|
||||
"zotregistry.dev/zot/v2/pkg/compat"
|
||||
"zotregistry.dev/zot/v2/pkg/extensions/events"
|
||||
"zotregistry.dev/zot/v2/pkg/extensions/monitoring"
|
||||
zlog "zotregistry.dev/zot/v2/pkg/log"
|
||||
common "zotregistry.dev/zot/v2/pkg/storage/common"
|
||||
"zotregistry.dev/zot/v2/pkg/storage/imagestore"
|
||||
storageTypes "zotregistry.dev/zot/v2/pkg/storage/types"
|
||||
)
|
||||
|
||||
// NewImageStore returns a new image store backed by cloud storages.
|
||||
// see https://github.com/docker/docker.github.io/tree/master/registry/storage-drivers
|
||||
// Use the last argument to properly set a cache database, or it will default to boltDB local storage.
|
||||
func NewImageStore(rootDir string, cacheDir string, dedupe, commit bool, log zlog.Logger,
|
||||
metrics monitoring.MetricServer, linter common.Lint, store driver.StorageDriver,
|
||||
cacheDriver storageTypes.Cache, compat []compat.MediaCompatibility, recorder events.Recorder,
|
||||
) storageTypes.ImageStore {
|
||||
return imagestore.NewImageStore(
|
||||
rootDir,
|
||||
cacheDir,
|
||||
dedupe,
|
||||
commit,
|
||||
log,
|
||||
metrics,
|
||||
linter,
|
||||
New(store),
|
||||
cacheDriver,
|
||||
compat,
|
||||
recorder,
|
||||
)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
//go:build needprivileges
|
||||
//go:build needprivileges && linux
|
||||
|
||||
package local_test
|
||||
|
||||
|
||||
+116
-304
File diff suppressed because it is too large
Load Diff
+21
-7
@@ -18,6 +18,7 @@ import (
|
||||
"zotregistry.dev/zot/v2/pkg/log"
|
||||
common "zotregistry.dev/zot/v2/pkg/storage/common"
|
||||
"zotregistry.dev/zot/v2/pkg/storage/constants"
|
||||
"zotregistry.dev/zot/v2/pkg/storage/gcs"
|
||||
"zotregistry.dev/zot/v2/pkg/storage/local"
|
||||
"zotregistry.dev/zot/v2/pkg/storage/s3"
|
||||
storageTypes "zotregistry.dev/zot/v2/pkg/storage/types"
|
||||
@@ -63,7 +64,7 @@ func New(config *config.Config, linter common.Lint, metrics monitoring.MetricSer
|
||||
)
|
||||
} else {
|
||||
storeName := fmt.Sprintf("%v", config.Storage.StorageDriver["name"])
|
||||
if storeName != constants.S3StorageDriverName {
|
||||
if storeName != constants.S3StorageDriverName && storeName != constants.GCSStorageDriverName {
|
||||
log.Error().Err(zerr.ErrBadConfig).Str("storageDriver", storeName).
|
||||
Msg("unsupported storage driver")
|
||||
|
||||
@@ -92,8 +93,15 @@ func New(config *config.Config, linter common.Lint, metrics monitoring.MetricSer
|
||||
|
||||
// false positive lint - linter does not implement Lint method
|
||||
//nolint: typecheck,contextcheck
|
||||
defaultStore = s3.NewImageStore(rootDir, config.Storage.RootDirectory,
|
||||
config.Storage.Dedupe, config.Storage.Commit, log, metrics, linter, store, cacheDriver, config.HTTP.Compat, recorder)
|
||||
if storeName == constants.S3StorageDriverName {
|
||||
defaultStore = s3.NewImageStore(rootDir, config.Storage.RootDirectory,
|
||||
config.Storage.Dedupe, config.Storage.Commit, log, metrics, linter, store, cacheDriver,
|
||||
config.HTTP.Compat, recorder)
|
||||
} else {
|
||||
defaultStore = gcs.NewImageStore(rootDir, config.Storage.RootDirectory,
|
||||
config.Storage.Dedupe, config.Storage.Commit, log, metrics, linter, store, cacheDriver,
|
||||
config.HTTP.Compat, recorder)
|
||||
}
|
||||
}
|
||||
|
||||
storeController.DefaultStore = defaultStore
|
||||
@@ -178,7 +186,7 @@ func getSubStore(cfg *config.Config, subPaths map[string]config.StorageConfig,
|
||||
}
|
||||
} else {
|
||||
storeName := fmt.Sprintf("%v", storageConfig.StorageDriver["name"])
|
||||
if storeName != constants.S3StorageDriverName {
|
||||
if storeName != constants.S3StorageDriverName && storeName != constants.GCSStorageDriverName {
|
||||
log.Error().Err(zerr.ErrBadConfig).Str("storageDriver", storeName).
|
||||
Msg("unsupported storage driver")
|
||||
|
||||
@@ -210,9 +218,15 @@ func getSubStore(cfg *config.Config, subPaths map[string]config.StorageConfig,
|
||||
|
||||
// false positive lint - linter does not implement Lint method
|
||||
//nolint: typecheck
|
||||
subImageStore[route] = s3.NewImageStore(rootDir, storageConfig.RootDirectory,
|
||||
storageConfig.Dedupe, storageConfig.Commit, log, metrics, linter, store, cacheDriver, cfg.HTTP.Compat, recorder,
|
||||
)
|
||||
if storeName == constants.S3StorageDriverName {
|
||||
subImageStore[route] = s3.NewImageStore(rootDir, storageConfig.RootDirectory,
|
||||
storageConfig.Dedupe, storageConfig.Commit, log, metrics, linter, store, cacheDriver, cfg.HTTP.Compat, recorder,
|
||||
)
|
||||
} else {
|
||||
subImageStore[route] = gcs.NewImageStore(rootDir, storageConfig.RootDirectory,
|
||||
storageConfig.Dedupe, storageConfig.Commit, log, metrics, linter, store, cacheDriver, cfg.HTTP.Compat, recorder,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+53
-27
@@ -59,7 +59,9 @@ var DeleteReferrers = config.ImageRetention{ //nolint: gochecknoglobals
|
||||
}
|
||||
|
||||
func cleanupStorage(store storageTypes.Driver, name string) {
|
||||
_ = store.Delete(name)
|
||||
if store != nil {
|
||||
_ = store.Delete(name)
|
||||
}
|
||||
}
|
||||
|
||||
type createObjectStoreOpts struct {
|
||||
@@ -207,7 +209,8 @@ func TestGetAllDedupeReposCandidates(t *testing.T) {
|
||||
defer DumpKeys(t, opts.miniRedisAddr)
|
||||
}
|
||||
|
||||
if testcase.storageType == storageConstants.S3StorageDriverName {
|
||||
switch testcase.storageType {
|
||||
case storageConstants.S3StorageDriverName:
|
||||
tskip.SkipS3(t)
|
||||
|
||||
uuid, err := guuid.NewV4()
|
||||
@@ -219,9 +222,10 @@ func TestGetAllDedupeReposCandidates(t *testing.T) {
|
||||
opts.rootDir = testDir
|
||||
|
||||
var store storageTypes.Driver
|
||||
|
||||
store, imgStore, _, _ = createObjectsStore(opts)
|
||||
defer cleanupStorage(store, testDir)
|
||||
} else {
|
||||
default:
|
||||
_, imgStore, _, _ = createObjectsStore(opts)
|
||||
}
|
||||
|
||||
@@ -279,7 +283,8 @@ func TestStorageAPIs(t *testing.T) {
|
||||
defer DumpKeys(t, opts.miniRedisAddr)
|
||||
}
|
||||
|
||||
if testcase.storageType == storageConstants.S3StorageDriverName {
|
||||
switch testcase.storageType {
|
||||
case storageConstants.S3StorageDriverName:
|
||||
tskip.SkipS3(t)
|
||||
|
||||
uuid, err := guuid.NewV4()
|
||||
@@ -291,9 +296,10 @@ func TestStorageAPIs(t *testing.T) {
|
||||
opts.rootDir = testDir
|
||||
|
||||
var store storageTypes.Driver
|
||||
|
||||
store, imgStore, _, _ = createObjectsStore(opts)
|
||||
defer cleanupStorage(store, testDir)
|
||||
} else {
|
||||
default:
|
||||
_, imgStore, _, _ = createObjectsStore(opts)
|
||||
}
|
||||
|
||||
@@ -1028,7 +1034,8 @@ func TestMandatoryAnnotations(t *testing.T) {
|
||||
defer DumpKeys(t, opts.miniRedisAddr)
|
||||
}
|
||||
|
||||
if testcase.storageType == storageConstants.S3StorageDriverName {
|
||||
switch testcase.storageType {
|
||||
case storageConstants.S3StorageDriverName:
|
||||
tskip.SkipS3(t)
|
||||
|
||||
uuid, err := guuid.NewV4()
|
||||
@@ -1040,6 +1047,7 @@ func TestMandatoryAnnotations(t *testing.T) {
|
||||
opts.rootDir = testDir
|
||||
|
||||
var cacheDriver storageTypes.Cache
|
||||
|
||||
store, _, cacheDriver, _ = createObjectsStore(opts)
|
||||
|
||||
imgStore = imagestore.NewImageStore(testDir, cacheDir, false, false, log, metrics,
|
||||
@@ -1050,8 +1058,9 @@ func TestMandatoryAnnotations(t *testing.T) {
|
||||
}, store, cacheDriver, nil, nil)
|
||||
|
||||
defer cleanupStorage(store, testDir)
|
||||
} else {
|
||||
default:
|
||||
var cacheDriver storageTypes.Cache
|
||||
|
||||
store, _, cacheDriver, _ = createObjectsStore(opts)
|
||||
|
||||
imgStore = imagestore.NewImageStore(cacheDir, cacheDir, true, true, log, metrics,
|
||||
@@ -1115,8 +1124,10 @@ func TestMandatoryAnnotations(t *testing.T) {
|
||||
}, store, nil, nil, nil)
|
||||
} else {
|
||||
var cacheDriver storageTypes.Cache
|
||||
store, _, cacheDriver, _ = createObjectsStore(opts)
|
||||
|
||||
store, _, cacheDriver, err := createObjectsStore(opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
imgStore = imagestore.NewImageStore(cacheDir, cacheDir, true, true, log, metrics,
|
||||
&mocks.MockedLint{
|
||||
LintFn: func(repo string, manifestDigest godigest.Digest, imageStore storageTypes.ImageStore) (bool, error) {
|
||||
@@ -1223,7 +1234,8 @@ func TestDeleteBlobsInUse(t *testing.T) {
|
||||
defer DumpKeys(t, opts.miniRedisAddr)
|
||||
}
|
||||
|
||||
if testcase.storageType == storageConstants.S3StorageDriverName {
|
||||
switch testcase.storageType {
|
||||
case storageConstants.S3StorageDriverName:
|
||||
tskip.SkipS3(t)
|
||||
|
||||
uuid, err := guuid.NewV4()
|
||||
@@ -1238,7 +1250,7 @@ func TestDeleteBlobsInUse(t *testing.T) {
|
||||
store, imgStore, _, _ = createObjectsStore(opts)
|
||||
|
||||
defer cleanupStorage(store, testDir)
|
||||
} else {
|
||||
default:
|
||||
_, imgStore, _, _ = createObjectsStore(opts)
|
||||
}
|
||||
|
||||
@@ -1532,7 +1544,8 @@ func TestReuploadCorruptedBlob(t *testing.T) {
|
||||
defer DumpKeys(t, opts.miniRedisAddr)
|
||||
}
|
||||
|
||||
if testcase.storageType == storageConstants.S3StorageDriverName {
|
||||
switch testcase.storageType {
|
||||
case storageConstants.S3StorageDriverName:
|
||||
tskip.SkipS3(t)
|
||||
|
||||
uuid, err := guuid.NewV4()
|
||||
@@ -1545,7 +1558,7 @@ func TestReuploadCorruptedBlob(t *testing.T) {
|
||||
|
||||
driver, imgStore, _, _ = createObjectsStore(opts)
|
||||
defer cleanupStorage(driver, testDir)
|
||||
} else {
|
||||
default:
|
||||
driver, imgStore, _, _ = createObjectsStore(opts)
|
||||
}
|
||||
|
||||
@@ -1666,7 +1679,8 @@ func TestStorageHandler(t *testing.T) {
|
||||
defer DumpKeys(t, opts.miniRedisAddr)
|
||||
}
|
||||
|
||||
if testcase.storageType == storageConstants.S3StorageDriverName {
|
||||
switch testcase.storageType {
|
||||
case storageConstants.S3StorageDriverName:
|
||||
tskip.SkipS3(t)
|
||||
|
||||
var (
|
||||
@@ -1695,7 +1709,7 @@ func TestStorageHandler(t *testing.T) {
|
||||
|
||||
thirdStorageDriver, thirdStore, _, _ = createObjectsStore(opts)
|
||||
defer cleanupStorage(thirdStorageDriver, thirdRootDir)
|
||||
} else {
|
||||
default:
|
||||
firstRootDir = t.TempDir()
|
||||
opts.rootDir = firstRootDir
|
||||
opts.cacheDir = firstRootDir
|
||||
@@ -1784,7 +1798,8 @@ func TestGarbageCollectImageManifest(t *testing.T) {
|
||||
Convey("Garbage collect with default/long delay", func() {
|
||||
var imgStore storageTypes.ImageStore
|
||||
|
||||
if testcase.storageType == storageConstants.S3StorageDriverName {
|
||||
switch testcase.storageType {
|
||||
case storageConstants.S3StorageDriverName:
|
||||
tskip.SkipS3(t)
|
||||
|
||||
uuid, err := guuid.NewV4()
|
||||
@@ -1796,9 +1811,10 @@ func TestGarbageCollectImageManifest(t *testing.T) {
|
||||
opts.rootDir = testDir
|
||||
|
||||
var store storageTypes.Driver
|
||||
|
||||
store, imgStore, _, _ = createObjectsStore(opts)
|
||||
defer cleanupStorage(store, testDir)
|
||||
} else {
|
||||
default:
|
||||
_, imgStore, _, _ = createObjectsStore(opts)
|
||||
}
|
||||
|
||||
@@ -1945,7 +1961,8 @@ func TestGarbageCollectImageManifest(t *testing.T) {
|
||||
|
||||
gcDelay := 1 * time.Second
|
||||
|
||||
if testcase.storageType == storageConstants.S3StorageDriverName {
|
||||
switch testcase.storageType {
|
||||
case storageConstants.S3StorageDriverName:
|
||||
tskip.SkipS3(t)
|
||||
|
||||
uuid, err := guuid.NewV4()
|
||||
@@ -1957,9 +1974,10 @@ func TestGarbageCollectImageManifest(t *testing.T) {
|
||||
opts.rootDir = testDir
|
||||
|
||||
var store storageTypes.Driver
|
||||
|
||||
store, imgStore, _, _ = createObjectsStore(opts)
|
||||
defer cleanupStorage(store, testDir)
|
||||
} else {
|
||||
default:
|
||||
_, imgStore, _, _ = createObjectsStore(opts)
|
||||
}
|
||||
|
||||
@@ -2220,7 +2238,8 @@ func TestGarbageCollectImageManifest(t *testing.T) {
|
||||
|
||||
gcDelay := 3 * time.Second
|
||||
|
||||
if testcase.storageType == storageConstants.S3StorageDriverName {
|
||||
switch testcase.storageType {
|
||||
case storageConstants.S3StorageDriverName:
|
||||
tskip.SkipS3(t)
|
||||
|
||||
uuid, err := guuid.NewV4()
|
||||
@@ -2232,9 +2251,10 @@ func TestGarbageCollectImageManifest(t *testing.T) {
|
||||
opts.rootDir = testDir
|
||||
|
||||
var store storageTypes.Driver
|
||||
|
||||
store, imgStore, _, _ = createObjectsStore(opts)
|
||||
defer cleanupStorage(store, testDir)
|
||||
} else {
|
||||
default:
|
||||
_, imgStore, _, _ = createObjectsStore(opts)
|
||||
}
|
||||
|
||||
@@ -2467,7 +2487,8 @@ func TestGarbageCollectImageIndex(t *testing.T) {
|
||||
Convey("Garbage collect with default/long delay", func() {
|
||||
var imgStore storageTypes.ImageStore
|
||||
|
||||
if testcase.storageType == storageConstants.S3StorageDriverName {
|
||||
switch testcase.storageType {
|
||||
case storageConstants.S3StorageDriverName:
|
||||
tskip.SkipS3(t)
|
||||
|
||||
uuid, err := guuid.NewV4()
|
||||
@@ -2479,9 +2500,10 @@ func TestGarbageCollectImageIndex(t *testing.T) {
|
||||
opts.rootDir = testDir
|
||||
|
||||
var store storageTypes.Driver
|
||||
|
||||
store, imgStore, _, _ = createObjectsStore(opts)
|
||||
defer cleanupStorage(store, testDir)
|
||||
} else {
|
||||
default:
|
||||
_, imgStore, _, _ = createObjectsStore(opts)
|
||||
}
|
||||
|
||||
@@ -2586,7 +2608,8 @@ func TestGarbageCollectImageIndex(t *testing.T) {
|
||||
gcDelay := 2 * time.Second
|
||||
imageRetentionDelay := 2 * time.Second
|
||||
|
||||
if testcase.storageType == storageConstants.S3StorageDriverName {
|
||||
switch testcase.storageType {
|
||||
case storageConstants.S3StorageDriverName:
|
||||
tskip.SkipS3(t)
|
||||
|
||||
uuid, err := guuid.NewV4()
|
||||
@@ -2598,9 +2621,10 @@ func TestGarbageCollectImageIndex(t *testing.T) {
|
||||
opts.rootDir = testDir
|
||||
|
||||
var store storageTypes.Driver
|
||||
|
||||
store, imgStore, _, _ = createObjectsStore(opts)
|
||||
defer cleanupStorage(store, testDir)
|
||||
} else {
|
||||
default:
|
||||
_, imgStore, _, _ = createObjectsStore(opts)
|
||||
}
|
||||
|
||||
@@ -2876,7 +2900,8 @@ func TestGarbageCollectChainedImageIndexes(t *testing.T) {
|
||||
defer DumpKeys(t, opts.miniRedisAddr)
|
||||
}
|
||||
|
||||
if testcase.storageType == storageConstants.S3StorageDriverName {
|
||||
switch testcase.storageType {
|
||||
case storageConstants.S3StorageDriverName:
|
||||
tskip.SkipS3(t)
|
||||
|
||||
uuid, err := guuid.NewV4()
|
||||
@@ -2888,9 +2913,10 @@ func TestGarbageCollectChainedImageIndexes(t *testing.T) {
|
||||
opts.rootDir = testDir
|
||||
|
||||
var store storageTypes.Driver
|
||||
|
||||
store, imgStore, _, _ = createObjectsStore(opts)
|
||||
defer cleanupStorage(store, testDir)
|
||||
} else {
|
||||
default:
|
||||
_, imgStore, _, _ = createObjectsStore(opts)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
package common
|
||||
|
||||
type RlimT = uint64
|
||||
@@ -3,6 +3,7 @@ package mocks
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -10,16 +11,17 @@ import (
|
||||
)
|
||||
|
||||
type StorageDriverMock struct {
|
||||
NameFn func() string
|
||||
GetContentFn func(ctx context.Context, path string) ([]byte, error)
|
||||
PutContentFn func(ctx context.Context, path string, content []byte) error
|
||||
ReaderFn func(ctx context.Context, path string, offset int64) (io.ReadCloser, error)
|
||||
WriterFn func(ctx context.Context, path string, isAppend bool) (driver.FileWriter, error)
|
||||
StatFn func(ctx context.Context, path string) (driver.FileInfo, error)
|
||||
ListFn func(ctx context.Context, path string) ([]string, error)
|
||||
MoveFn func(ctx context.Context, sourcePath, destPath string) error
|
||||
DeleteFn func(ctx context.Context, path string) error
|
||||
WalkFn func(ctx context.Context, path string, f driver.WalkFn) error
|
||||
NameFn func() string
|
||||
GetContentFn func(ctx context.Context, path string) ([]byte, error)
|
||||
PutContentFn func(ctx context.Context, path string, content []byte) error
|
||||
ReaderFn func(ctx context.Context, path string, offset int64) (io.ReadCloser, error)
|
||||
WriterFn func(ctx context.Context, path string, isAppend bool) (driver.FileWriter, error)
|
||||
StatFn func(ctx context.Context, path string) (driver.FileInfo, error)
|
||||
ListFn func(ctx context.Context, path string) ([]string, error)
|
||||
MoveFn func(ctx context.Context, sourcePath, destPath string) error
|
||||
DeleteFn func(ctx context.Context, path string) error
|
||||
WalkFn func(ctx context.Context, path string, f driver.WalkFn, options ...func(*driver.WalkOptions)) error
|
||||
RedirectURLFn func(r *http.Request, path string) (string, error)
|
||||
}
|
||||
|
||||
//nolint:gochecknoglobals
|
||||
@@ -100,13 +102,23 @@ func (s *StorageDriverMock) Delete(ctx context.Context, path string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StorageDriverMock) RedirectURL(r *http.Request, path string) (string, error) {
|
||||
if s != nil && s.RedirectURLFn != nil {
|
||||
return s.RedirectURLFn(r, path)
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (s *StorageDriverMock) URLFor(ctx context.Context, path string, options map[string]any) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (s *StorageDriverMock) Walk(ctx context.Context, path string, f driver.WalkFn) error {
|
||||
func (s *StorageDriverMock) Walk(ctx context.Context, path string, f driver.WalkFn,
|
||||
options ...func(*driver.WalkOptions),
|
||||
) error {
|
||||
if s != nil && s.WalkFn != nil {
|
||||
return s.WalkFn(ctx, path, f)
|
||||
return s.WalkFn(ctx, path, f, options...)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -115,9 +127,14 @@ func (s *StorageDriverMock) Walk(ctx context.Context, path string, f driver.Walk
|
||||
type FileInfoMock struct {
|
||||
IsDirFn func() bool
|
||||
SizeFn func() int64
|
||||
PathFn func() string
|
||||
}
|
||||
|
||||
func (f *FileInfoMock) Path() string {
|
||||
if f != nil && f.PathFn != nil {
|
||||
return f.PathFn()
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
|
||||
@@ -20,3 +20,11 @@ func SkipDynamo(t *testing.T) {
|
||||
t.Skip("Skipping testing without AWS DynamoDB mock server")
|
||||
}
|
||||
}
|
||||
|
||||
func SkipGCS(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
if os.Getenv("GCSMOCK_ENDPOINT") == "" {
|
||||
t.Skip("Skipping testing without GCS mock server")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user