Watch
1
0
Fork
You've already forked pkg-proxy
1
mirror of https://github.com/git-pkgs/proxy.git synced 2026-09-16 07:42:05 -04:00
pkg-proxy/internal/storage/gcs.go
Andrew Nesbitt 41ae7d6520
Add GCS storage backend with lighter dependencies (#179)
* 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>
2026-08-29 11:23:07 +01:00

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
}