mirror of
https://github.com/git-pkgs/proxy.git
synced 2026-09-16 07:42:05 -04:00
* Add GCS storage backend with Workload Identity support Register gocloud.dev/blob/gcsblob so gs:// URLs are accepted as a storage backend. Authentication uses Application Default Credentials, which makes GKE Workload Identity work out of the box; signed URLs (direct_serve) fall back to the IAM Credentials signBlob API when no private key is available. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Replace gcsblob with lightweight GCS backend * Extract GCS client into standalone module --------- Co-authored-by: Anthony A. <github@anthony-arnaud.fr> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
97 lines
2.2 KiB
Go
97 lines
2.2 KiB
Go
package storage
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"errors"
|
|
"io"
|
|
"time"
|
|
|
|
gcstorage "github.com/git-pkgs/gcs"
|
|
)
|
|
|
|
// GCS adapts a Google Cloud Storage bucket to Storage.
|
|
type GCS struct {
|
|
bucket *gcstorage.Bucket
|
|
url string
|
|
}
|
|
|
|
// OpenGCS opens a Google Cloud Storage bucket from a gs:// URL.
|
|
func OpenGCS(ctx context.Context, urlStr string) (*GCS, error) {
|
|
bucket, err := gcstorage.OpenBucket(ctx, urlStr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &GCS{bucket: bucket, url: urlStr}, nil
|
|
}
|
|
|
|
func (g *GCS) Store(ctx context.Context, path string, r io.Reader) (int64, string, error) {
|
|
h := sha256.New()
|
|
size, err := g.bucket.Write(ctx, path, io.TeeReader(r, h))
|
|
if err != nil {
|
|
return 0, "", err
|
|
}
|
|
return size, hex.EncodeToString(h.Sum(nil)), nil
|
|
}
|
|
|
|
func (g *GCS) Open(ctx context.Context, path string) (io.ReadCloser, error) {
|
|
r, err := g.bucket.Open(ctx, path)
|
|
if errors.Is(err, gcstorage.ErrNotFound) {
|
|
return nil, ErrNotFound
|
|
}
|
|
return r, err
|
|
}
|
|
|
|
func (g *GCS) Exists(ctx context.Context, path string) (bool, error) {
|
|
return g.bucket.Exists(ctx, path)
|
|
}
|
|
|
|
func (g *GCS) Delete(ctx context.Context, path string) error {
|
|
return g.bucket.Delete(ctx, path)
|
|
}
|
|
|
|
func (g *GCS) Size(ctx context.Context, path string) (int64, error) {
|
|
size, err := g.bucket.Size(ctx, path)
|
|
if errors.Is(err, gcstorage.ErrNotFound) {
|
|
return 0, ErrNotFound
|
|
}
|
|
return size, err
|
|
}
|
|
|
|
func (g *GCS) SignedURL(ctx context.Context, path string, expiry time.Duration) (string, error) {
|
|
u, err := g.bucket.SignedURL(ctx, path, expiry)
|
|
if errors.Is(err, gcstorage.ErrSignedURLUnsupported) {
|
|
return "", ErrSignedURLUnsupported
|
|
}
|
|
return u, err
|
|
}
|
|
|
|
func (g *GCS) UsedSpace(ctx context.Context) (int64, error) {
|
|
return g.bucket.UsedSpace(ctx)
|
|
}
|
|
|
|
func (g *GCS) ListPrefix(ctx context.Context, prefix string) ([]ObjectInfo, error) {
|
|
objects, err := g.bucket.ListPrefix(ctx, prefix)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result := make([]ObjectInfo, 0, len(objects))
|
|
for _, object := range objects {
|
|
result = append(result, ObjectInfo{
|
|
Path: object.Name,
|
|
Size: object.Size,
|
|
ModTime: object.ModTime,
|
|
})
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (g *GCS) Close() error {
|
|
return nil
|
|
}
|
|
|
|
func (g *GCS) URL() string {
|
|
return g.url
|
|
}
|