Network ACL Rules modifications cause churn due to being a TypeList#281
Open
bhouse-nexthop wants to merge 7 commits intoapache:mainfrom
Open
Network ACL Rules modifications cause churn due to being a TypeList#281bhouse-nexthop wants to merge 7 commits intoapache:mainfrom
bhouse-nexthop wants to merge 7 commits intoapache:mainfrom
Conversation
- Implement assignRuleNumbers() function for sequential auto-numbering - Rules without rule_number get assigned sequential numbers starting from 1 - If a rule has explicit rule_number, numbering continues from that value - Integrated into Create, Update, and ports migration flows - Preserves config file order (TypeList maintains order naturally)
6a620ff to
f00af8f
Compare
- Add new 'ruleset' field as TypeSet alternative to 'rule' TypeList - Make rule_number required in ruleset (no auto-numbering needed) - Remove deprecated 'ports' field from ruleset schema - Add ConflictsWith to prevent using both 'rule' and 'ruleset' - Update Create, Read, Update, Delete functions to handle both fields - TypeSet prevents spurious diffs when rules are inserted in the middle - Add comprehensive example showing ruleset usage - Document key differences between rule and ruleset Add test cases for ruleset field - Add TestAccCloudStackNetworkACLRule_ruleset_basic test - Add TestAccCloudStackNetworkACLRule_ruleset_update test - Both tests mirror existing rule tests but use ruleset with explicit rule_number - All rules in ruleset tests have mandatory rule_number values - Tests verify TypeSet behavior and rule attributes - Ensures ruleset field works correctly for create and update operations
f00af8f to
27d5be4
Compare
Contributor
There was a problem hiding this comment.
Pull request overview
This PR addresses plan churn for cloudstack_network_acl_rule by introducing a new ruleset schema field (TypeSet) keyed by rule_number, while preserving the legacy rule (TypeList) behavior and updating docs/tests accordingly.
Changes:
- Add
ruleset(TypeSet) with requiredrule_numberto reduce spurious diffs when inserting/removing rules. - Implement explicit
rule_numberassignment for legacyruleto avoid CloudStack auto-enumeration order issues. - Update acceptance tests and documentation to cover and recommend
ruleset.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
cloudstack/resource_cloudstack_network_acl_rule.go |
Adds ruleset schema + hashing, UUID handling helpers, and rule-number enumeration; updates CRUD/read/update logic to support ruleset. |
cloudstack/resource_cloudstack_network_acl_rule_test.go |
Adds acceptance tests for ruleset behavior, including an insertion scenario and a plan-check step. |
website/docs/r/network_acl_rule.html.markdown |
Documents ruleset, its constraints (required rule_number, no deprecated ports), and usage guidance. |
Comments suppressed due to low confidence (1)
cloudstack/resource_cloudstack_network_acl_rule.go:1393
rulesMatchignoresrule_number,cidr_list, anddescriptionwhen pairing old/new rules. Withruleset(and even withrule), it’s common to have multiple rules sharing protocol/action/traffic_type/port; this matching can pair the wrong rules and then “update” an existing CloudStack rule to match a different one, effectively mutating the wrong ACL item. Forruleset, matching should primarily key offrule_number(since it’s required/unique); for legacyrule, consider includingrule_numberwhen present and/or includecidr_listin the match criteria to avoid incorrect pairings.
func rulesMatch(oldRule, newRule map[string]interface{}) bool {
oldProtocol := oldRule["protocol"].(string)
newProtocol := newRule["protocol"].(string)
oldTrafficType := oldRule["traffic_type"].(string)
newTrafficType := newRule["traffic_type"].(string)
oldAction := oldRule["action"].(string)
newAction := newRule["action"].(string)
if oldProtocol != newProtocol ||
oldTrafficType != newTrafficType ||
oldAction != newAction {
return false
}
protocol := newProtocol
if protocol == "tcp" || protocol == "udp" {
oldPort, oldHasPort := oldRule["port"].(string)
newPort, newHasPort := newRule["port"].(string)
if oldHasPort && newHasPort {
return oldPort == newPort
}
if oldHasPort != newHasPort {
return false
}
return true
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Member
|
@bhouse-nexthop Thanks for the PR. Can you check the comments on the PR? |
…tent - Update docs to state ruleset identifies rules by rule_number (not full content) - Add explicit requirement that each rule_number must be unique - Addresses review comment about misleading documentation
- Clarify that duplicate rule_number values will result in last-one-wins behavior - This is standard TypeSet behavior in Terraform, not a bug - Validation cannot prevent this as TypeSet deduplicates before CustomizeDiff runs Review Comment 1 Rebuttal: The review suggests adding CustomizeDiff validation to detect duplicate rule_number values. However, this is not possible because Terraform's TypeSet automatically deduplicates entries based on the hash function BEFORE CustomizeDiff runs. Since we hash on rule_number (the unique identifier), duplicate rule_numbers are automatically deduplicated with last-one-wins behavior. This is the INTENDED behavior of TypeSet - rule_number acts as a primary key, and having multiple rules with the same rule_number should result in one overwriting the other. This is analogous to how a map works in most programming languages. The documentation has been updated to make this behavior explicit and warn users that duplicate rule_numbers will result in only the last one being kept.
- Ensure nextNumber never moves backwards when encountering explicit rule_number - Use max(nextNumber, ruleNum+1) logic to prevent duplicates - Prevents scenario where auto-assigned rule gets same number as later explicit rule - Addresses review comment about potential duplicate rule numbers
- Adjust dummy rule format for ruleset to use 'uuid' string instead of 'uuids' map - Add rule_number to dummy rules for ruleset (required field) - Find highest existing rule_number and assign dummy rules starting from max+1 - Prevents conflicts between dummy rules and user-defined rules - Addresses review comment about managed mode incompatibility with ruleset
- Add granular plan check using plancheck.ExpectKnownValue to verify that exactly one nested block is added (ruleset.# changes from 3 to 4) - This provides more specific validation than just checking for Update action - Addresses review comment about strengthening plan check assertions
Collaborator
Author
Addressed all comments. Please re-run Copilot review. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
Inserting a rule in the middle of other rules will cause every rule after to be shown as modified. There is some logic to try to prevent the number of updates shown as being actually applied, but it causes alarm, and its not clear from the output what its actually doing or if its doing it properly.
Its not possible to simply convert the existing TypeList into a TypeSet because without a rule_number specified, the order cannot be preserved (as TypeSet uses a hash of the field). Terraform also doesn't provide any mechanism to do any inspection of the original order to auto-assign rule numbers in the right order for rules without a manually specified rule_number.
The choice was made to create a parallel
rulesetschema member with all the same child elements as theruleschema member. The differences are arule_numberbecomes a required field and it doesn't need to implement the legacyports->portconversion logic.For the legacy
ruleTypeList another change was made in order to perform explicit rule_number enumeration instead of relying on Cloudstack to auto-enumerate. When applying new entries in parallel, the order wouldn't be guaranteed to be preserved without this!This also includes test and documentation updates.
Fixes #279
Usecase
In my particular use-case, I auto-assign rule_numbers based on zones using a module as a helper. The module handles creation of the rule_number for me, and takes a starting index for each block of rules so I can logically group rules together in a "reserved range" for a particular "purpose".
Module Code
modules/cloudstack_network_acl/main.tf:Module Caller
myzone.tf:Explanation of Module usage
This is just describing the example module for how I use this PR. There are other ways to accomplish this especially for short manual lists. The below description is how to use the module in the body of this message, it doesn't directly relate to the PR itself. It can (and was previously) used with the prior implementation by replacing
dynamic "ruleset"withdynamic "rule"in the module.The implementation uses a list of lists, with each inner list having a start_idx so each rule does not need to be enumerated separately but instead is ordered naturally. As long as the start_idx isn't the same as any other start_idx, and the gap between each index is sufficiently large, no numbering should conflict. I recommend having each "block" have at least 100 entries reserved. The maximum rule_number is 65535.
In the above example, creating a new inner list to apply will only insert those rules from the inner list as the rule_number's are guaranteed to be unique, regardless of what index it has in the outer list. However, any ordering change of each inner list would impact all rules after it within the inner list (but have no impact on other lists contained in the outer list). So it is recommended to only append new rules to inner lists unless ordering is critical to prevent mass updates (but again, the blast radius would be limited to any inner list items that come after the entry, not to any other outer lists).