diff --git a/registry/app/api/wire.go b/registry/app/api/wire.go index 1fb8f8e610..92bb13ecaf 100644 --- a/registry/app/api/wire.go +++ b/registry/app/api/wire.go @@ -48,6 +48,7 @@ import ( storagedriver "github.com/harness/gitness/registry/app/driver" "github.com/harness/gitness/registry/app/driver/factory" "github.com/harness/gitness/registry/app/driver/filesystem" + "github.com/harness/gitness/registry/app/driver/gcs" "github.com/harness/gitness/registry/app/driver/s3-aws" "github.com/harness/gitness/registry/app/pkg" "github.com/harness/gitness/registry/app/pkg/base" @@ -86,18 +87,26 @@ func DefaultStorageProvider(ctx context.Context, c *types.Config) (storagedriver var d storagedriver.StorageDriver var err error - if c.Registry.Storage.StorageType == "filesystem" { + switch c.Registry.Storage.StorageType { + case "filesystem": filesystem.Register(ctx) d, err = factory.Create(ctx, "filesystem", config.GetFilesystemParams(c)) if err != nil { log.Fatal().Stack().Err(err).Msgf("") panic(err) } - } else { + case "gcs": + gcs.Register(ctx) + d, err = factory.Create(ctx, "gcs", config.GetGCSStorageParameters(c)) + if err != nil { + log.Error().Stack().Err(err).Msg("failed to init gcs Blob storage") + panic(err) + } + default: s3.Register(ctx) d, err = factory.Create(ctx, "s3aws", config.GetS3StorageParameters(c)) if err != nil { - log.Error().Stack().Err(err).Msg("failed to init s3 Blob storage ") + log.Error().Stack().Err(err).Msg("failed to init s3 Blob storage") panic(err) } } diff --git a/registry/app/driver/gcs/gcs.go b/registry/app/driver/gcs/gcs.go index c38e9b96a5..b6fa7b1240 100644 --- a/registry/app/driver/gcs/gcs.go +++ b/registry/app/driver/gcs/gcs.go @@ -49,8 +49,7 @@ import ( ) const ( - driverName = "gcs" - dummyProjectID = "" + driverName = "gcs" minChunkSize = 256 * 1024 defaultChunkSize = 16 * 1024 * 1024 @@ -88,6 +87,11 @@ func init() { factory.Register(driverName, &gcsDriverFactory{}) } +// TODO: figure-out why init is not called automatically +func Register(ctx context.Context) { + log.Ctx(ctx).Info().Msgf("registering gcs driver") +} + // gcsDriverFactory implements the factory.StorageDriverFactory interface. type gcsDriverFactory struct{} @@ -105,6 +109,7 @@ var _ storagedriver.StorageDriver = &driver{} // Objects are stored at absolute keys in the provided bucket. type driver struct { client *http.Client + gcs *storage.Client bucket *storage.BucketHandle email string privateKey []byte @@ -256,6 +261,7 @@ func New(_ context.Context, params driverParameters) (storagedriver.StorageDrive return nil, fmt.Errorf("invalid chunksize: %d is not a positive multiple of %d", params.chunkSize, minChunkSize) } d := &driver{ + gcs: params.gcs, bucket: params.gcs.Bucket(params.bucket), rootDirectory: rootDirectory, email: params.email, @@ -932,6 +938,22 @@ func (d *driver) keyToPath(key string) string { return "/" + strings.Trim(strings.TrimPrefix(key, d.rootDirectory), "/") } -func (d *driver) CopyObject(_ context.Context, _, _, _ string) error { - return fmt.Errorf("not yet implemented") +func (d *driver) CopyObject(ctx context.Context, srcKey, destBucket, destKey string) error { + src := d.bucket.Object(d.pathToKey(srcKey)) + + dst := d.gcs.Bucket(destBucket).Object(destKey) + + copier := dst.CopierFrom(src) + copier.ContentType = blobContentType + + _, err := copier.Run(ctx) + if err != nil { + var status *googleapi.Error + if errors.As(err, &status) && status.Code == http.StatusNotFound { + return storagedriver.PathNotFoundError{Path: srcKey} + } + return fmt.Errorf("copy %q to %q/%q: %w", srcKey, destBucket, destKey, err) + } + + return nil } diff --git a/registry/config/helper.go b/registry/config/helper.go index 68184b39a9..8c8612ff2f 100644 --- a/registry/config/helper.go +++ b/registry/config/helper.go @@ -45,3 +45,9 @@ func GetFilesystemParams(c *types.Config) map[string]any { props["rootdirectory"] = c.Registry.Storage.FileSystemStorage.RootDirectory return props } + +func GetGCSStorageParameters(c *types.Config) map[string]any { + gcsProperties := make(map[string]any) + gcsProperties["bucket"] = c.Registry.Storage.GCSStorage.Bucket + return gcsProperties +} diff --git a/types/config.go b/types/config.go index 8784e4a9c8..f4bf643e1e 100644 --- a/types/config.go +++ b/types/config.go @@ -511,7 +511,7 @@ type Config struct { Registry struct { Enable bool `envconfig:"GITNESS_REGISTRY_ENABLED" default:"true"` Storage struct { - // StorageType defines the type of storage to use for the registry. Options are: `filesystem`, `s3aws` + // StorageType defines the type of storage to use for the registry. Options are: `filesystem`, `s3aws`, `gcs` StorageType string `envconfig:"GITNESS_REGISTRY_STORAGE_TYPE" default:"filesystem"` // FileSystemStorage defines the configuration for the filesystem storage if StorageType is `filesystem`. @@ -544,6 +544,12 @@ type Config struct { Redirect bool `envconfig:"GITNESS_REGISTRY_S3_STORAGE_REDIRECT" default:"false"` Provider string `envconfig:"GITNESS_REGISTRY_S3_PROVIDER" default:"cloudflare"` } + + // GCSStorage defines the configuration for the GCS storage if StorageType is `gcs`. + // Authentication is handled via workload identity (google.DefaultTokenSource). + GCSStorage struct { + Bucket string `envconfig:"GITNESS_REGISTRY_GCS_BUCKET"` + } } HTTP struct {