Skip to content

fix: customdiff#6

Open
gurusai-voleti wants to merge 2 commits intomainfrom
bkt_acl_fix
Open

fix: customdiff#6
gurusai-voleti wants to merge 2 commits intomainfrom
bkt_acl_fix

Conversation

@gurusai-voleti
Copy link
Owner

Release Note Template for Downstream PRs (will be copied)

See Write release notes for guidance.


@gurusai-voleti
Copy link
Owner Author

/gemini review

@gemini-code-assist
Copy link

Summary of Changes

Hello @gurusai-voleti, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a persistent 'permadiff' problem within the google_storage_bucket_acl resource, specifically concerning how Terraform detects and manages changes to default project-level Access Control Lists (ACLs) for Google Cloud Storage buckets. The core change involves a significant refactoring of the CustomizeDiff logic to ensure that these default ACLs are correctly identified and handled, preventing unnecessary updates and ensuring stability in Terraform plans. Additionally, the PR extends protection against accidental deletion for these critical default ACLs.

Highlights

  • Refactored CustomDiff Logic: The resourceStorageRoleEntityCustomizeDiff function has been completely rewritten to intelligently handle changes to role_entity lists, preserving order and preventing 'permadiff' issues for default GCP ACLs.
  • New Default ACL Helper: A new helper function, isDefaultGcpAcl, was introduced to accurately identify default project-level ACLs (specifically for owners, editors, and viewers) for consistent processing within the custom diff logic.
  • Extended Deletion Protection: The resourceStorageBucketAclDelete function now explicitly skips the deletion of project-editors- (with OWNER role) and project-viewers- (with READER role), aligning with existing protection for project-owners- and preventing accidental removal of these critical default ACLs.
  • Updated Test Coverage: New test cases and a helper function (testGoogleStorageBucketsAclBasic2Owner) were added to validate the updated behavior, particularly focusing on the correct handling of default owner-related ACLs.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the CustomizeDiff logic for storage_bucket_acl to address permutation diffs, and adds logic to prevent deletion of more default ACLs. The new CustomizeDiff logic is a good improvement, but there are opportunities to make it more robust by adding type assertion checks and using type assertions instead of fmt.Sprint. The new logic in resourceStorageBucketAclDelete for skipping default ACLs contains a bug and should be fixed. I've also included a suggestion to improve the readability of the new isDefaultGcpAcl helper function.

Comment on lines 417 to 425
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
}

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 +64 to +65
oldList, _ := oldData.([]interface{})
newList, _ := newData.([]interface{})

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")
	}


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{}{}

// 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)

// 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)

Comment on lines 107 to 118
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
}

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-")
}

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the CustomizeDiff logic for storage_bucket_acl to better handle default ACLs and prevent permadiffs. The new implementation preserves the order of existing ACLs and prevents the removal of default project-level ACLs for owners, editors, and viewers. The logic in resourceStorageBucketAclDelete is also updated to align with this change, and corresponding tests are added.

My feedback includes a suggestion to refactor the new isDefaultGcpAcl helper for better maintainability and to correct a misleading log message in resourceStorageBucketAclDelete for improved clarity during debugging.

Comment on lines 107 to 118
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
}

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
}

Comment on lines 417 to 425
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
}

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
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant