Skip to content

Commit c781c75

Browse files
Fix TypeSet hash to prevent spurious diffs on unchanged rules
The rule TypeSet was using the default hash function which includes ALL fields, including the computed 'uuid' field. This caused Terraform to show spurious diffs where unchanged rules appeared as being removed and re-added when other rules in the same ruleset were modified. Root Cause: - When rule A is updated (e.g., action changes), its UUID may change - The TypeSet hash included the UUID, so the set's internal representation changed even for unchanged rules - Terraform interpreted this as rules being removed and re-added, even though only their position in the set changed Solution: - Added custom hash function resourceCloudStackNetworkACLRulesetRuleHash - Hash is based only on user-configurable fields that define rule identity: rule_number, action, protocol, traffic_type, cidr_list, port, icmp_type, icmp_code, description - Explicitly excludes the computed 'uuid' field - This ensures rules are identified by their configuration, not by their API-assigned UUID All 11 acceptance tests passing (139.0 seconds total)
1 parent 06543e2 commit c781c75

1 file changed

Lines changed: 44 additions & 0 deletions

File tree

cloudstack/resource_cloudstack_network_acl_ruleset.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ func resourceCloudStackNetworkACLRuleset() *schema.Resource {
6464
"rule": {
6565
Type: schema.TypeSet,
6666
Required: true,
67+
Set: resourceCloudStackNetworkACLRulesetRuleHash,
6768
Elem: &schema.Resource{
6869
Schema: map[string]*schema.Schema{
6970
"rule_number": {
@@ -128,6 +129,49 @@ func resourceCloudStackNetworkACLRuleset() *schema.Resource {
128129
}
129130
}
130131

132+
// resourceCloudStackNetworkACLRulesetRuleHash computes a hash for a rule based on
133+
// its identifying attributes, excluding the computed uuid field. This ensures that
134+
// rules are identified by their configuration, not by their API-assigned UUID.
135+
func resourceCloudStackNetworkACLRulesetRuleHash(v interface{}) int {
136+
var buf strings.Builder
137+
m := v.(map[string]interface{})
138+
139+
// Include all fields that define the rule's identity from the user's perspective
140+
// Explicitly exclude "uuid" which is computed and should not affect the hash
141+
142+
buf.WriteString(fmt.Sprintf("%d-", m["rule_number"].(int)))
143+
buf.WriteString(fmt.Sprintf("%s-", m["action"].(string)))
144+
buf.WriteString(fmt.Sprintf("%s-", m["protocol"].(string)))
145+
buf.WriteString(fmt.Sprintf("%s-", m["traffic_type"].(string)))
146+
147+
// Hash the cidr_list set
148+
if v, ok := m["cidr_list"]; ok {
149+
cidrSet := v.(*schema.Set)
150+
for _, cidr := range cidrSet.List() {
151+
buf.WriteString(fmt.Sprintf("%s-", cidr.(string)))
152+
}
153+
}
154+
155+
// Include optional fields
156+
if v, ok := m["port"]; ok && v.(string) != "" {
157+
buf.WriteString(fmt.Sprintf("port:%s-", v.(string)))
158+
}
159+
160+
if v, ok := m["icmp_type"]; ok && v.(int) != 0 {
161+
buf.WriteString(fmt.Sprintf("icmp_type:%d-", v.(int)))
162+
}
163+
164+
if v, ok := m["icmp_code"]; ok && v.(int) != 0 {
165+
buf.WriteString(fmt.Sprintf("icmp_code:%d-", v.(int)))
166+
}
167+
168+
if v, ok := m["description"]; ok && v.(string) != "" {
169+
buf.WriteString(fmt.Sprintf("desc:%s-", v.(string)))
170+
}
171+
172+
return schema.HashString(buf.String())
173+
}
174+
131175
func resourceCloudStackNetworkACLRulesetCreate(d *schema.ResourceData, meta interface{}) error {
132176
// We need to set this upfront in order to be able to save a partial state
133177
d.SetId(d.Get("acl_id").(string))

0 commit comments

Comments
 (0)