- Manifest.Tags from build.tags (^[a-z0-9_]+$, duplicates dropped), round-tripped by SaveManifest - summer build passes -tags=<list> through goBuildArgs and prints the build tags line - attach: s3blob imported only by s3_on.go (//go:build summercms_s3); an untagged binary refuses an s3:// bucket_url naming the tag, build.tags and -tags - go mod tidy adds the AWS SDK v2 modules as indirect requirements - storage, modules, scaffolding, configuration and scaling docs plus the lagoon README
120 lines
3.3 KiB
Go
120 lines
3.3 KiB
Go
package attach
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/url"
|
|
"strings"
|
|
"sync"
|
|
|
|
"git.golem15.com/golem15/summercms/modules/backpack"
|
|
"git.golem15.com/golem15/summercms/modules/compass"
|
|
"gocloud.dev/blob"
|
|
_ "gocloud.dev/blob/fileblob"
|
|
_ "gocloud.dev/blob/memblob"
|
|
)
|
|
|
|
const defaultPublicPathPrefix = "/storage/uploads"
|
|
|
|
// S3BuildTag is the Go build tag that links the gocloud S3 driver
|
|
// (s3blob, imported only by s3_on.go). List it under build.tags in summer.yaml so
|
|
// summer build passes it to go build; a raw go build needs -tags summercms_s3.
|
|
const S3BuildTag = "summercms_s3"
|
|
|
|
var (
|
|
prefixMu sync.RWMutex
|
|
publicPathPrefix = defaultPublicPathPrefix
|
|
)
|
|
|
|
func setPublicPathPrefix(prefix string) {
|
|
prefixMu.Lock()
|
|
publicPathPrefix = prefix
|
|
prefixMu.Unlock()
|
|
}
|
|
|
|
// PublicPathPrefix is the URL prefix prepended to partition+filename.
|
|
func PublicPathPrefix() string {
|
|
prefixMu.RLock()
|
|
defer prefixMu.RUnlock()
|
|
return publicPathPrefix
|
|
}
|
|
|
|
func normalizeBucketURL(raw string) (string, error) {
|
|
u, err := url.Parse(raw)
|
|
if err != nil {
|
|
return "", fmt.Errorf("attach: bucket_url: %w", err)
|
|
}
|
|
switch strings.ToLower(u.Scheme) {
|
|
case "mem", "memory":
|
|
return "mem://", nil
|
|
case "fileblob":
|
|
u.Scheme = "file"
|
|
fallthrough
|
|
case "file":
|
|
q := u.Query()
|
|
if q.Get("create_dir") == "" {
|
|
q.Set("create_dir", "true")
|
|
u.RawQuery = q.Encode()
|
|
}
|
|
return u.String(), nil
|
|
default:
|
|
return raw, nil
|
|
}
|
|
}
|
|
|
|
// checkS3Driver refuses an s3:// bucket URL when the binary was built
|
|
// without S3BuildTag, naming the tag and where to set it.
|
|
func checkS3Driver(bucketURL string) error {
|
|
if s3Enabled {
|
|
return nil
|
|
}
|
|
scheme, _, ok := strings.Cut(bucketURL, "://")
|
|
if !ok || !strings.EqualFold(scheme, "s3") {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("attach: storage.uploads.bucket_url uses s3:// but this binary was built without the %s build tag; "+
|
|
"list %s under build.tags in summer.yaml and rebuild with summer build (a raw go build needs -tags %s, or GOFLAGS=-tags=%s)",
|
|
S3BuildTag, S3BuildTag, S3BuildTag, S3BuildTag)
|
|
}
|
|
|
|
// OpenBucket opens storage.uploads.bucket_url (file://, mem://, or s3:// in a
|
|
// binary built with S3BuildTag) and records
|
|
// storage.uploads.public_path_prefix. An empty bucket_url fails boot.
|
|
func OpenBucket(ctx context.Context, cfg *compass.Config) (*blob.Bucket, error) {
|
|
if cfg == nil {
|
|
return nil, fmt.Errorf("attach: config is nil")
|
|
}
|
|
raw := strings.TrimSpace(cfg.String("storage.uploads.bucket_url"))
|
|
if raw == "" {
|
|
return nil, fmt.Errorf("attach: storage.uploads.bucket_url is empty (set SUMMER_STORAGE__UPLOADS__BUCKET_URL)")
|
|
}
|
|
bucketURL, err := normalizeBucketURL(raw)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := checkS3Driver(bucketURL); err != nil {
|
|
return nil, err
|
|
}
|
|
bucket, err := blob.OpenBucket(ctx, bucketURL)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("attach: open bucket %q: %w", raw, err)
|
|
}
|
|
prefix := strings.TrimSpace(cfg.String("storage.uploads.public_path_prefix"))
|
|
if prefix == "" {
|
|
prefix = defaultPublicPathPrefix
|
|
}
|
|
setPublicPathPrefix(prefix)
|
|
return bucket, nil
|
|
}
|
|
|
|
// Publish stores the opened bucket once on the app, matching lagoon.Publish.
|
|
func Publish(app *backpack.App, bucket *blob.Bucket) error {
|
|
if app == nil {
|
|
return fmt.Errorf("attach: app is nil")
|
|
}
|
|
if bucket == nil {
|
|
return fmt.Errorf("attach: bucket is nil")
|
|
}
|
|
return app.Publish(bucket)
|
|
}
|