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" ) // 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 } // 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() if _, err := c.PutObject(ctx, &s3.PutObjectInput{ Bucket: aws.String(o.Bucket), Key: aws.String(o.Key), Body: f, }); err != nil { return "", fmt.Errorf("s3 put: %w", err) } return fmt.Sprintf("s3://%s/%s", o.Bucket, o.Key), nil } // 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 }