Skip to content

Commit 7da964b

Browse files
Fix ACL rule validation and port ordering issues
This commit addresses three code review comments: 1. Add validation for ruleset create path - The ruleset create path was skipping verifyNetworkACLRuleParams before calling createNetworkACLRules - Added validation loop to verify each ruleset element before creation - This prevents panics from unchecked type assertions in createNetworkACLRule 2. Add validation for all rule creation paths - createNetworkACLRule is called from Create and Update paths for both 'rule' and 'ruleset' fields - Added validation in Update path for both 'rule' and 'ruleset' before calling updateNetworkACLRules - Ensures all new rules are validated before creation, preventing less actionable CloudStack API errors 3. Sort ports for deterministic rule numbering - When expanding deprecated 'ports' field, iteration used portsSet.List() which is unordered - Rule numbers were derived as baseRuleNum + portIndex, causing non-deterministic numbering across runs - Added sort.Strings() to ensure stable, deterministic rule number assignment Additionally, consolidated duplicate validation loops into a helper function validateRulesList() to improve code maintainability and reduce duplication across Create and Update paths. The helper correctly skips validation for out-of-band rule placeholders (created by managed=true) as they are markers for deletion. All changes tested with full ACL acceptance test suite (15 tests): - TestAccCloudStackNetworkACLRule_basic - TestAccCloudStackNetworkACLRule_update - TestAccCloudStackNetworkACLRule_ruleset_basic - TestAccCloudStackNetworkACLRule_ruleset_update - TestAccCloudStackNetworkACLRule_ruleset_insert - TestAccCloudStackNetworkACLRule_ruleset_insert_plan_check - TestAccCloudStackNetworkACLRule_ruleset_managed - TestAccCloudStackNetworkACLRule_ruleset_not_managed - TestAccCloudStackNetworkACLRule_rule_managed - TestAccCloudStackNetworkACLRule_rule_not_managed - TestAccCloudStackNetworkACLRule_icmp_fields_no_spurious_diff - TestAccCloudStackNetworkACLRule_icmp_fields_add_remove_rule - TestAccCloudStackNetworkACLRule_deprecated_ports - TestAccCloudStackNetworkACLRule_deprecated_ports_managed - TestAccCloudStackNetworkACLRule_deprecated_ports_not_managed
1 parent 4e43e06 commit 7da964b

File tree

1 file changed

+53
-7
lines changed

1 file changed

+53
-7
lines changed

cloudstack/resource_cloudstack_network_acl_rule.go

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -456,11 +456,8 @@ func resourceCloudStackNetworkACLRuleCreate(d *schema.ResourceData, meta interfa
456456
log.Printf("[DEBUG] Processing %d rules from 'rule' field", len(nrs))
457457

458458
// Validate rules BEFORE assigning numbers, so we can detect user-provided rule_number
459-
for _, rule := range nrs {
460-
if err := verifyNetworkACLRuleParams(d, rule.(map[string]interface{})); err != nil {
461-
log.Printf("[ERROR] Failed to verify rule parameters: %v", err)
462-
return err
463-
}
459+
if err := validateRulesList(d, nrs, "rule"); err != nil {
460+
return err
464461
}
465462

466463
// Assign rule numbers to rules that don't have them
@@ -490,6 +487,11 @@ func resourceCloudStackNetworkACLRuleCreate(d *schema.ResourceData, meta interfa
490487
// Convert Set to list (no auto-numbering needed, rule_number is required)
491488
rulesList := nrs.List()
492489

490+
// Validate rules BEFORE creating them
491+
if err := validateRulesList(d, rulesList, "ruleset"); err != nil {
492+
return err
493+
}
494+
493495
err := createNetworkACLRules(d, meta, &rules, rulesList)
494496
if err != nil {
495497
log.Printf("[ERROR] Failed to create network ACL rules: %v", err)
@@ -667,9 +669,18 @@ func createNetworkACLRule(d *schema.ResourceData, meta interface{}, rule map[str
667669
baseRuleNum = ruleNum
668670
}
669671

672+
// Convert TypeSet to sorted list for deterministic rule number assignment
673+
// This ensures that rule numbers are stable across runs
674+
portsList := portsSet.List()
675+
portsStrings := make([]string, len(portsList))
676+
for i, port := range portsList {
677+
portsStrings[i] = port.(string)
678+
}
679+
sort.Strings(portsStrings)
680+
log.Printf("[DEBUG] Sorted ports for deterministic numbering: %v", portsStrings)
681+
670682
portIndex := 0
671-
for _, port := range portsSet.List() {
672-
portValue := port.(string)
683+
for _, portValue := range portsStrings {
673684

674685
// Check if this port already has a UUID
675686
if _, hasUUID := getRuleUUID(rule, portValue); !hasUUID {
@@ -1153,6 +1164,11 @@ func resourceCloudStackNetworkACLRuleUpdate(d *schema.ResourceData, meta interfa
11531164

11541165
log.Printf("[DEBUG] Rule list changed, performing efficient updates")
11551166

1167+
// Validate new rules BEFORE assigning numbers
1168+
if err := validateRulesList(d, newRules, "rule"); err != nil {
1169+
return err
1170+
}
1171+
11561172
// Assign rule numbers to new rules that don't have them
11571173
newRulesWithNumbers := assignRuleNumbers(newRules)
11581174

@@ -1170,6 +1186,11 @@ func resourceCloudStackNetworkACLRuleUpdate(d *schema.ResourceData, meta interfa
11701186

11711187
log.Printf("[DEBUG] Ruleset changed: %d old rules -> %d new rules", len(oldRules), len(newRules))
11721188

1189+
// Validate new rules BEFORE updating
1190+
if err := validateRulesList(d, newRules, "ruleset"); err != nil {
1191+
return err
1192+
}
1193+
11731194
// No migration needed for ruleset (no deprecated 'ports' field)
11741195
// No auto-numbering needed (rule_number is required)
11751196
err := updateNetworkACLRules(d, meta, oldRules, newRules)
@@ -1273,6 +1294,31 @@ func verifyNetworkACLParams(d *schema.ResourceData) error {
12731294
return nil
12741295
}
12751296

1297+
// validateRulesList validates all rules in a list by calling verifyNetworkACLRuleParams on each
1298+
// This helper consolidates the validation logic used in Create and Update paths for both 'rule' and 'ruleset' fields
1299+
// Out-of-band rule placeholders (created by managed=true) are skipped as they are markers for deletion
1300+
func validateRulesList(d *schema.ResourceData, rules []interface{}, fieldName string) error {
1301+
validatedCount := 0
1302+
for i, rule := range rules {
1303+
ruleMap := rule.(map[string]interface{})
1304+
1305+
// Skip validation for out-of-band rule placeholders
1306+
// These are created by managed=true and are just markers for deletion
1307+
if isOutOfBandRulePlaceholder(ruleMap) {
1308+
log.Printf("[DEBUG] Skipping validation for out-of-band rule placeholder at index %d", i)
1309+
continue
1310+
}
1311+
1312+
if err := verifyNetworkACLRuleParams(d, ruleMap); err != nil {
1313+
log.Printf("[ERROR] Failed to verify %s rule %d parameters: %v", fieldName, i, err)
1314+
return fmt.Errorf("validation failed for %s rule %d: %w", fieldName, i, err)
1315+
}
1316+
validatedCount++
1317+
}
1318+
log.Printf("[DEBUG] Successfully validated %d %s rules (skipped %d out-of-band placeholders)", validatedCount, fieldName, len(rules)-validatedCount)
1319+
return nil
1320+
}
1321+
12761322
func verifyNetworkACLRuleParams(d *schema.ResourceData, rule map[string]interface{}) error {
12771323
log.Printf("[DEBUG] Verifying parameters for rule: %+v", rule)
12781324

0 commit comments

Comments
 (0)