wails-release/internal/upload/upload.go

93 lines
2.9 KiB
Go
Raw Permalink Normal View History

package upload
import (
"context"
"fmt"
"os"
"strings"
"github.com/aws/aws-sdk-go-v2/aws"
awsconfig "github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
)
// PutObjectAPI is the subset of *s3.Client used here. Tests inject a fake.
type PutObjectAPI interface {
PutObject(ctx context.Context, in *s3.PutObjectInput, opts ...func(*s3.Options)) (*s3.PutObjectOutput, error)
}
// Opts configures a single upload.
type Opts struct {
Bucket string
Key string
FilePath string
// ACL, if set, is applied to the uploaded object as a canned ACL
// (e.g. "public-read"). Empty means no ACL is sent — required for
// buckets with Object Ownership = "Bucket owner enforced".
ACL string
}
// RenderKey substitutes {version} and {filename} placeholders.
func RenderKey(template, version, filename string) string {
r := strings.NewReplacer("{version}", version, "{filename}", filename)
return r.Replace(template)
}
// Upload PUTs FilePath at s3://Bucket/Key and returns the s3:// URL.
func Upload(ctx context.Context, c PutObjectAPI, o Opts) (string, error) {
f, err := os.Open(o.FilePath)
if err != nil {
return "", fmt.Errorf("open %s: %w", o.FilePath, err)
}
defer f.Close()
in := &s3.PutObjectInput{
Bucket: aws.String(o.Bucket),
Key: aws.String(o.Key),
Body: f,
}
if o.ACL != "" {
in.ACL = types.ObjectCannedACL(o.ACL)
}
if _, err := c.PutObject(ctx, in); err != nil {
return "", fmt.Errorf("s3 put: %w", err)
}
return fmt.Sprintf("s3://%s/%s", o.Bucket, o.Key), nil
}
// HTTPSURL returns the HTTPS form of an upload location. For AWS S3 it uses
// the regional virtual-hosted form; for a custom endpoint it appends the
// bucket and key path-style, matching how NewClient configures the client.
//
// The bucket may still be private — this is purely the URL where the object
// would be served if it (or its bucket) were publicly readable.
func HTTPSURL(bucket, key, region, endpointURL string) string {
if endpointURL != "" {
return strings.TrimRight(endpointURL, "/") + "/" + bucket + "/" + key
}
return fmt.Sprintf("https://%s.s3.%s.amazonaws.com/%s", bucket, region, key)
}
// WriteFileForTest is exposed for use from this package's tests.
func WriteFileForTest(path string, b []byte) error {
return os.WriteFile(path, b, 0o600)
}
// NewClient builds an *s3.Client honouring an optional custom endpoint
// for S3-compatible storage.
func NewClient(ctx context.Context, region, endpointURL string) (*s3.Client, error) {
cfg, err := awsconfig.LoadDefaultConfig(ctx, awsconfig.WithRegion(region))
if err != nil {
return nil, fmt.Errorf("aws config: %w", err)
}
opts := []func(*s3.Options){}
if endpointURL != "" {
opts = append(opts, func(o *s3.Options) {
o.BaseEndpoint = aws.String(endpointURL)
o.UsePathStyle = true
})
}
return s3.NewFromConfig(cfg, opts...), nil
}