Add optional s3-acl input for canned ACLs on uploads
Lets the workflow set, e.g., public-read on the uploaded object so the HTTPS URL is actually downloadable without further configuration. Empty default means no ACL is sent — required for modern AWS buckets with Object Ownership = "Bucket owner enforced" that reject any ACL. Validates the value against the AWS canned-ACL list at config time so typos fail before the upload runs. Wires the input through action.yml, config, and the orchestrator; adds a unit test that the ACL is forwarded to PutObjectInput when set and omitted when empty. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
78f63e640f
commit
03da0c3e85
6 changed files with 53 additions and 3 deletions
|
|
@ -28,6 +28,7 @@ type Config struct {
|
|||
S3Key string
|
||||
S3EndpointURL string
|
||||
S3Region string
|
||||
S3ACL string
|
||||
}
|
||||
|
||||
// Load reads the action's INPUT_* environment variables.
|
||||
|
|
@ -51,6 +52,7 @@ func Load(get func(string) string) *Config {
|
|||
S3Key: get("INPUT_S3_KEY"),
|
||||
S3EndpointURL: get("INPUT_S3_ENDPOINT_URL"),
|
||||
S3Region: getOr(get, "INPUT_S3_REGION", "us-east-1"),
|
||||
S3ACL: get("INPUT_S3_ACL"),
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
|
@ -115,6 +117,14 @@ func (c *Config) Validate() error {
|
|||
if c.S3Bucket != "" && c.S3Key == "" {
|
||||
return fmt.Errorf("s3-bucket is set but s3-key is empty")
|
||||
}
|
||||
if c.S3ACL != "" {
|
||||
switch c.S3ACL {
|
||||
case "private", "public-read", "public-read-write", "authenticated-read",
|
||||
"aws-exec-read", "bucket-owner-read", "bucket-owner-full-control":
|
||||
default:
|
||||
return fmt.Errorf("s3-acl must be one of private, public-read, public-read-write, authenticated-read, aws-exec-read, bucket-owner-read, bucket-owner-full-control (got %q)", c.S3ACL)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"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.
|
||||
|
|
@ -21,6 +22,10 @@ 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.
|
||||
|
|
@ -37,11 +42,15 @@ func Upload(ctx context.Context, c PutObjectAPI, o Opts) (string, error) {
|
|||
}
|
||||
defer f.Close()
|
||||
|
||||
if _, err := c.PutObject(ctx, &s3.PutObjectInput{
|
||||
in := &s3.PutObjectInput{
|
||||
Bucket: aws.String(o.Bucket),
|
||||
Key: aws.String(o.Key),
|
||||
Body: f,
|
||||
}); err != nil {
|
||||
}
|
||||
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
|
||||
|
|
|
|||
|
|
@ -104,6 +104,34 @@ func TestUpload_CallsPutObjectWithCorrectInputs(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestUpload_ForwardsACLWhenSet(t *testing.T) {
|
||||
tmp := writeTempFile(t, "hello")
|
||||
c := &fakePut{}
|
||||
|
||||
if _, err := upload.Upload(context.Background(), c, upload.Opts{
|
||||
Bucket: "b", Key: "k", FilePath: tmp, ACL: "public-read",
|
||||
}); err != nil {
|
||||
t.Fatalf("unexpected: %v", err)
|
||||
}
|
||||
if string(c.calls[0].ACL) != "public-read" {
|
||||
t.Fatalf("ACL got %q want public-read", c.calls[0].ACL)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpload_OmitsACLWhenEmpty(t *testing.T) {
|
||||
tmp := writeTempFile(t, "hello")
|
||||
c := &fakePut{}
|
||||
|
||||
if _, err := upload.Upload(context.Background(), c, upload.Opts{
|
||||
Bucket: "b", Key: "k", FilePath: tmp,
|
||||
}); err != nil {
|
||||
t.Fatalf("unexpected: %v", err)
|
||||
}
|
||||
if c.calls[0].ACL != "" {
|
||||
t.Fatalf("ACL got %q want empty (no ACL sent)", c.calls[0].ACL)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpload_FailsWhenFileMissing(t *testing.T) {
|
||||
_, err := upload.Upload(context.Background(), &fakePut{}, upload.Opts{
|
||||
Bucket: "b",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue