Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions docs/resources/object_bucket_acl.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,9 @@ The `owner` configuration block supports the following arguments:

The `grantee` configuration block supports the following arguments:

* `id` - (Required) The canonical user ID of the grantee.
* `type` - (Required) Type of grantee. Valid values: CanonicalUser.
* `id` - (Optional) The canonical user ID of the grantee.
* `type` - (Required) Type of grantee. Valid values: CanonicalUser, Group.
* `uri` - (Optional) The uri of the grantee if type is Group.

## Attributes Reference

Expand Down
85 changes: 67 additions & 18 deletions internal/services/object/bucket_acl.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ import (

const (
BucketACLSeparator = "/"

// AWS predefined group URIs for bucket ACL grants
AuthenticatedUsersURI = "http://acs.amazonaws.com/groups/global/AuthenticatedUsers"
AllUsersURI = "http://acs.amazonaws.com/groups/global/AllUsers"
)

func ResourceBucketACL() *schema.Resource {
Expand Down Expand Up @@ -60,17 +64,26 @@ func ResourceBucketACL() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},
"uri": {
Type: schema.TypeString,
Optional: true,
Description: "The uri of the grantee if you are granting permissions to a predefined group.",
ValidateFunc: validation.StringInSlice([]string{
AuthenticatedUsersURI,
AllUsersURI,
}, false),
},
"id": {
Type: schema.TypeString,
Required: true,
Optional: true,
Description: "The project ID owner of the grantee.",
ValidateDiagFunc: verify.IsUUID(),
},
"type": {
Type: schema.TypeString,
Required: true,
Description: "Type of grantee. Valid values: `CanonicalUser`",
ValidateFunc: validation.StringInSlice([]string{string(s3Types.TypeCanonicalUser)}, false),
Optional: true,
Description: "Type of grantee. Valid values: `CanonicalUser`, `Group`",
ValidateFunc: validation.StringInSlice([]string{string(s3Types.TypeCanonicalUser), string(s3Types.TypeGroup)}, false),
},
},
},
Expand Down Expand Up @@ -183,7 +196,12 @@ func resourceBucketACLCreate(ctx context.Context, d *schema.ResourceData, m any)
}

if v, ok := d.GetOk("access_control_policy"); ok && len(v.([]any)) > 0 && v.([]any)[0] != nil {
input.AccessControlPolicy = expandBucketACLAccessControlPolicy(v.([]any))
accessControlPolicy, err := expandAndValidateBucketACLAccessControlPolicy(v.([]any))
if err != nil {
return diag.FromErr(err)
}

input.AccessControlPolicy = accessControlPolicy
}

out, err := conn.PutBucketAcl(ctx, input)
Expand All @@ -198,30 +216,35 @@ func resourceBucketACLCreate(ctx context.Context, d *schema.ResourceData, m any)
return resourceBucketACLRead(ctx, d, m)
}

func expandBucketACLAccessControlPolicy(l []any) *s3Types.AccessControlPolicy {
func expandAndValidateBucketACLAccessControlPolicy(l []any) (*s3Types.AccessControlPolicy, error) {
if len(l) == 0 || l[0] == nil {
return nil
return nil, nil
}

tfMap, ok := l[0].(map[string]any)
if !ok {
return nil
return nil, nil
}

result := &s3Types.AccessControlPolicy{}

if v, ok := tfMap["grant"].(*schema.Set); ok && v.Len() > 0 {
result.Grants = expandBucketACLAccessControlPolicyGrants(v.List())
grants, err := expandAndValidateBucketACLAccessControlPolicyGrants(v.List())
if err != nil {
return nil, err
}

result.Grants = grants
}

if v, ok := tfMap["owner"].([]any); ok && len(v) > 0 && v[0] != nil {
result.Owner = expandBucketACLAccessControlPolicyOwner(v)
}

return result
return result, nil
}

func expandBucketACLAccessControlPolicyGrants(l []any) []s3Types.Grant {
func expandAndValidateBucketACLAccessControlPolicyGrants(l []any) ([]s3Types.Grant, error) {
grants := make([]s3Types.Grant, 0, len(l))

for _, tfMapRaw := range l {
Expand All @@ -233,7 +256,12 @@ func expandBucketACLAccessControlPolicyGrants(l []any) []s3Types.Grant {
grant := s3Types.Grant{}

if v, ok := tfMap["grantee"].([]any); ok && len(v) > 0 && v[0] != nil {
grant.Grantee = expandBucketACLAccessControlPolicyGrantsGrantee(v)
grantee, err := expandAndValidateBucketACLAccessControlPolicyGrantsGrantee(v)
if err != nil {
return nil, err
}

grant.Grantee = grantee
}

if v, ok := tfMap["permission"].(string); ok && v != "" {
Expand All @@ -243,17 +271,17 @@ func expandBucketACLAccessControlPolicyGrants(l []any) []s3Types.Grant {
grants = append(grants, grant)
}

return grants
return grants, nil
}

func expandBucketACLAccessControlPolicyGrantsGrantee(l []any) *s3Types.Grantee {
func expandAndValidateBucketACLAccessControlPolicyGrantsGrantee(l []any) (*s3Types.Grantee, error) {
if len(l) == 0 || l[0] == nil {
return nil
return nil, nil
}

tfMap, ok := l[0].(map[string]any)
if !ok {
return nil
return nil, nil
}

result := &s3Types.Grantee{}
Expand All @@ -262,11 +290,23 @@ func expandBucketACLAccessControlPolicyGrantsGrantee(l []any) *s3Types.Grantee {
result.ID = buildBucketOwnerID(aws.String(v))
}

if v, ok := tfMap["uri"].(string); ok && v != "" {
result.URI = aws.String(v)
}

if v, ok := tfMap["type"].(string); ok && v != "" {
result.Type = s3Types.Type(v)
}

return result
if result.Type == s3Types.TypeCanonicalUser && result.ID == nil {
return nil, errors.New("id is required when grantee type is CanonicalUser")
}

if result.Type == s3Types.TypeGroup && result.URI == nil {
return nil, errors.New("uri is required when grantee type is Group")
}

return result, nil
}

func expandBucketACLAccessControlPolicyOwner(l []any) *s3Types.Owner {
Expand Down Expand Up @@ -345,6 +385,10 @@ func flattenBucketACLAccessControlPolicyGrantsGrantee(grantee *s3Types.Grantee)
m["display_name"] = NormalizeOwnerID(grantee.DisplayName)
}

if grantee.URI != nil {
m["uri"] = *grantee.URI
}

if grantee.ID != nil {
m["id"] = NormalizeOwnerID(grantee.ID)
}
Expand Down Expand Up @@ -451,7 +495,12 @@ func resourceBucketACLUpdate(ctx context.Context, d *schema.ResourceData, m any)
}

if d.HasChange("access_control_policy") {
input.AccessControlPolicy = expandBucketACLAccessControlPolicy(d.Get("access_control_policy").([]any))
accessControlPolicy, err := expandAndValidateBucketACLAccessControlPolicy(d.Get("access_control_policy").([]any))
if err != nil {
return diag.FromErr(err)
}

input.AccessControlPolicy = accessControlPolicy
}

_, err = conn.PutBucketAcl(ctx, input)
Expand Down
38 changes: 28 additions & 10 deletions internal/services/object/bucket_acl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,7 @@ func TestAccObjectBucketACL_Grantee(t *testing.T) {

testBucketName := sdkacctest.RandomWithPrefix("tf-tests-scw-object-acl-grantee")

ownerID := "105bdce1-64c0-48ab-899d-868455867ecf"
ownerIDChild := "50ab77d5-56bd-4981-a118-4e0fa5309b59"
ownerID := "105bdce1-64c0-48ab-899d-868455867ecf" // scaleway-dev-tools-org
resource.Test(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
ProviderFactories: tt.ProviderFactories,
Expand Down Expand Up @@ -128,9 +127,13 @@ func TestAccObjectBucketACL_Grantee(t *testing.T) {
Config: fmt.Sprintf(`
resource "scaleway_object_bucket" "main" {
name = "%[1]s"
region = "%[4]s"
region = "%[3]s"
}


data "scaleway_iam_user" "devtool" {
email = "[email protected]"
}

resource "scaleway_object_bucket_acl" "main" {
bucket = scaleway_object_bucket.main.id
access_control_policy {
Expand All @@ -141,29 +144,44 @@ func TestAccObjectBucketACL_Grantee(t *testing.T) {
}
permission = "FULL_CONTROL"
}

grant {
grantee {
id = "%[2]s"
type = "CanonicalUser"
}
permission = "WRITE"
}

grant {
grantee {
id = "%[3]s"
id = data.scaleway_iam_user.devtool.id
type = "CanonicalUser"
}
permission = "FULL_CONTROL"
}


grant {
grantee {
uri = "%[4]s"
type = "Group"
}
permission = "READ_ACP"
}

grant {
grantee {
uri = "%[5]s"
type = "Group"
}
permission = "READ"
}
owner {
id = "%[2]s"
}
}
}
`, testBucketName, ownerID, ownerIDChild, objectTestsMainRegion),
`, testBucketName, ownerID, objectTestsMainRegion, object.AuthenticatedUsersURI, object.AllUsersURI),
Check: resource.ComposeTestCheckFunc(
objectchecks.CheckBucketExists(tt, "scaleway_object_bucket.main", true),
resource.TestCheckResourceAttr("scaleway_object_bucket_acl.main", "bucket", testBucketName),
Expand Down Expand Up @@ -263,7 +281,7 @@ func TestAccObjectBucketACL_WithBucketName(t *testing.T) {
name = "%s"
region = "%s"
}

resource "scaleway_object_bucket_acl" "main" {
bucket = scaleway_object_bucket.main.name
acl = "public-read"
Expand Down
2 changes: 1 addition & 1 deletion internal/services/object/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ func objectIsPublic(acl *s3.GetObjectAclOutput) bool {
for _, grant := range acl.Grants {
if grant.Grantee != nil &&
grant.Grantee.Type == s3Types.TypeGroup &&
*grant.Grantee.URI == "http://acs.amazonaws.com/groups/global/AllUsers" {
*grant.Grantee.URI == AllUsersURI {
return true
}
}
Expand Down
Loading
Loading