Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -59,35 +59,62 @@ func ResourceStorageBucketAcl() *schema.Resource {
}

func resourceStorageRoleEntityCustomizeDiff(_ context.Context, diff *schema.ResourceDiff, meta interface{}) error {
keys := diff.GetChangedKeysPrefix("role_entity")
if len(keys) < 1 {
return nil
}
count := diff.Get("role_entity.#").(int)
if count < 1 {
oldData, newData := diff.GetChange("role_entity")

oldList, _ := oldData.([]interface{})
newList, _ := newData.([]interface{})
Comment on lines +64 to +65

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The type assertions to []interface{} for oldData and newData discard the ok boolean that indicates if the assertion was successful. If the assertion fails, oldList or newList will be nil. The current code handles this by checking len() == 0, but it silently treats invalid data as an empty list. It's safer and better practice to explicitly check the ok value and return an error if the type is not what's expected.

        oldList, ok1 := oldData.([]interface{})
	newList, ok2 := newData.([]interface{})
	if (!ok1 && oldData != nil) || (!ok2 && newData != nil) {
		return fmt.Errorf("role_entity was not a valid list")
	}


if len(oldList) == 0 || len(newList) == 0 {
return nil
}
state := map[string]struct{}{}
conf := map[string]struct{}{}
for i := 0; i < count; i++ {
old, new := diff.GetChange(fmt.Sprintf("role_entity.%d", i))
state[old.(string)] = struct{}{}
conf[new.(string)] = struct{}{}

newSet := make(map[string]struct{})
for _, item := range newList {
newSet[fmt.Sprint(item)] = struct{}{}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The role_entity is a list of strings. Using fmt.Sprint(item) is less efficient and less type-safe than a direct type assertion to string. Please use a type assertion item.(string) instead. This will also make the code more robust by panicking if the type is unexpectedly different, which is desirable for revealing bugs during testing.

                newSet[item.(string)] = struct{}{}

}
if len(state) != len(conf) {
return nil

visited := make(map[string]struct{})

var finalAcls []interface{}

// Preserve order from oldList and handle removals, this will help avoid permadiff
// Iterate through the original list to maintain its order.
for _, item := range oldList {
key := fmt.Sprint(item)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similar to a previous comment, it's better to use a type assertion item.(string) here instead of fmt.Sprint(item) for type safety and performance, as role_entity items are strings.

                key := item.(string)

if _, exists := newSet[key]; exists || isDefaultGcpAcl(key) {
visited[key] = struct{}{}
finalAcls = append(finalAcls, item)
}
}
for k := range state {
if _, ok := conf[k]; !ok {
// project-owners- is explicitly stripped from the roles that this
// resource will delete
if strings.Contains(k, "OWNER:project-owners-") {
continue
}
return nil

// Append any new additions
// Iterate through the new config to find items that weren't in the old list or newly added
for _, item := range newList {
key := fmt.Sprint(item)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similar to previous comments, it's better to use a type assertion item.(string) here instead of fmt.Sprint(item) for type safety and performance.

                key := item.(string)

if _, exists := visited[key]; !exists {
visited[key] = struct{}{}
finalAcls = append(finalAcls, item)
}
}
return diff.Clear("role_entity")

// finaclAcls will be applied if any change in existing config otherwise not diff is displayed
if err := diff.SetNew("role_entity", finalAcls); err != nil {
return fmt.Errorf("error setting new role entities in CustomizeDiff: %w", err)
}
return nil
}

func isDefaultGcpAcl(key string) bool {
if strings.HasPrefix(key, "OWNER:project-owners-") {
return true
}
if strings.HasPrefix(key, "OWNER:project-editors-") {
return true
}
if strings.HasPrefix(key, "READER:project-viewers-") {
return true
}
return false
}
Comment on lines +107 to 111

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This function can be written more concisely by combining the conditions with a logical OR (||) operator in a single return statement. This improves readability and reduces the number of return points.

func isDefaultGcpAcl(key string) bool {
	return strings.HasPrefix(key, "OWNER:project-owners-") ||
		strings.HasPrefix(key, "OWNER:project-editors-") ||
		strings.HasPrefix(key, "READER:project-viewers-")
}

Comment on lines +107 to 111

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The isDefaultGcpAcl function can be made more maintainable and readable by using a slice of prefixes and iterating over it. This approach makes it easier to add or remove default ACL prefixes in the future without adding more if statements.

Suggested change
func isDefaultGcpAcl(key string) bool {
if strings.HasPrefix(key, "OWNER:project-owners-") {
return true
}
if strings.HasPrefix(key, "OWNER:project-editors-") {
return true
}
if strings.HasPrefix(key, "READER:project-viewers-") {
return true
}
return false
}
func isDefaultGcpAcl(key string) bool {
defaultPrefixes := []string{
"OWNER:project-owners-",
"OWNER:project-editors-",
"READER:project-viewers-",
}
for _, p := range defaultPrefixes {
if strings.HasPrefix(key, p) {
return true
}
}
return false
}


type RoleEntity struct {
Expand Down Expand Up @@ -173,6 +200,7 @@ func resourceStorageBucketAclCreate(d *schema.ResourceData, meta interface{}) er
break
}
}

if alreadyInserted {
log.Printf("[DEBUG]: pair %s-%s already exists, not trying to insert again\n", pair.Role, pair.Entity)
continue
Expand Down Expand Up @@ -293,7 +321,6 @@ func resourceStorageBucketAclUpdate(d *schema.ResourceData, meta interface{}) er

old_re_map[res.Entity] = res.Role
}

for _, v := range new_re {
pair, err := GetRoleEntityPair(v.(string))

Expand All @@ -315,7 +342,6 @@ func resourceStorageBucketAclUpdate(d *schema.ResourceData, meta interface{}) er
return fmt.Errorf("Error updating ACL for bucket %s: %v", bucket, err)
}
}

for entity, role := range old_re_map {
if entity == fmt.Sprintf("project-owners-%s", project) && role == "OWNER" {
log.Printf("[WARN]: Skipping %s-%s; not deleting owner ACL.", role, entity)
Expand Down Expand Up @@ -388,6 +414,16 @@ func resourceStorageBucketAclDelete(d *schema.ResourceData, meta interface{}) er
continue
}

if res.Entity == fmt.Sprintf("project-editors-%s", project) && res.Role == "OWNER" {
log.Printf("[WARN]: Skipping %s-%s; not deleting owner ACL.", res.Role, res.Entity)
continue
}

if res.Entity == fmt.Sprintf("project-viewers-%s", project) && res.Role == "READER" {
log.Printf("[WARN]: Skipping %s-%s; not deleting owner ACL.", res.Role, res.Entity)
continue
}
Comment on lines +410 to +418

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The check res.Entity == fmt.Sprintf(...) is likely incorrect because it performs an exact match using the project ID, while the entity name for these default roles contains the project number. Using strings.HasPrefix is more robust. The two new checks can also be combined into one, and the log message not deleting owner ACL is inaccurate for the READER role and should be made more generic.

                if (strings.HasPrefix(res.Entity, "project-editors-") && res.Role == "OWNER") ||
			(strings.HasPrefix(res.Entity, "project-viewers-") && res.Role == "READER") {
			log.Printf("[WARN]: Skipping %s-%s; not deleting default ACL.", res.Role, res.Entity)
			continue
		}

Comment on lines +410 to +418

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The log message "not deleting owner ACL" is misleading when applied to editor and viewer roles. Using a more generic term like "default ACL" would be more accurate and improve clarity when debugging.

Suggested change
if res.Entity == fmt.Sprintf("project-editors-%s", project) && res.Role == "OWNER" {
log.Printf("[WARN]: Skipping %s-%s; not deleting owner ACL.", res.Role, res.Entity)
continue
}
if res.Entity == fmt.Sprintf("project-viewers-%s", project) && res.Role == "READER" {
log.Printf("[WARN]: Skipping %s-%s; not deleting owner ACL.", res.Role, res.Entity)
continue
}
if res.Entity == fmt.Sprintf("project-editors-%s", project) && res.Role == "OWNER" {
log.Printf("[WARN]: Skipping %s-%s; not deleting default ACL.", res.Role, res.Entity)
continue
}
if res.Entity == fmt.Sprintf("project-viewers-%s", project) && res.Role == "READER" {
log.Printf("[WARN]: Skipping %s-%s; not deleting default ACL.", res.Role, res.Entity)
continue
}


log.Printf("[DEBUG]: removing entity %s", res.Entity)

err = config.NewStorageClient(userAgent).BucketAccessControls.Delete(bucket, res.Entity).Do()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func TestAccStorageBucketAcl_upgrade(t *testing.T) {
},

{
Config: testGoogleStorageBucketsAclBasic2(bucketName),
Config: testGoogleStorageBucketsAclBasic2Owner(bucketName),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleStorageBucketAcl(t, bucketName, roleEntityBasic2),
testAccCheckGoogleStorageBucketAcl(t, bucketName, roleEntityBasic3_owner),
Expand Down Expand Up @@ -309,6 +309,20 @@ resource "google_storage_bucket_acl" "acl" {
`, bucketName, roleEntityOwners, roleEntityEditors, roleEntityViewers, roleEntityBasic2, roleEntityBasic3_owner)
}

func testGoogleStorageBucketsAclBasic2Owner(bucketName string) string {
return fmt.Sprintf(`
resource "google_storage_bucket" "bucket" {
name = "%s"
location = "US"
}

resource "google_storage_bucket_acl" "acl" {
bucket = google_storage_bucket.bucket.name
role_entity = ["%s", "%s", "%s", "%s", "%s", "%s"]
}
`, bucketName, roleEntityOwners, roleEntityEditors, roleEntityViewers, roleEntityBasic1, roleEntityBasic2, roleEntityBasic3_owner)
}

func testGoogleStorageBucketsAclBasicDelete(bucketName string) string {
return fmt.Sprintf(`
resource "google_storage_bucket" "bucket" {
Expand Down
Loading