2020package cloudstack
2121
2222import (
23- "bytes"
2423 "context"
2524 "fmt"
2625 "log"
@@ -192,7 +191,6 @@ func resourceCloudStackNetworkACLRule() *schema.Resource {
192191 Type : schema .TypeSet ,
193192 Optional : true ,
194193 ConflictsWith : []string {"rule" },
195- Set : resourceCloudStackNetworkACLRulesetHash ,
196194 Elem : & schema.Resource {
197195 Schema : map [string ]* schema.Schema {
198196 "rule_number" : {
@@ -207,9 +205,10 @@ func resourceCloudStackNetworkACLRule() *schema.Resource {
207205 },
208206
209207 "cidr_list" : {
210- Type : schema .TypeList ,
208+ Type : schema .TypeSet ,
211209 Required : true ,
212210 Elem : & schema.Schema {Type : schema .TypeString },
211+ Set : schema .HashString ,
213212 },
214213
215214 "protocol" : {
@@ -272,21 +271,6 @@ func resourceCloudStackNetworkACLRule() *schema.Resource {
272271 }
273272}
274273
275- // resourceCloudStackNetworkACLRulesetHash computes the hash for a ruleset element
276- // Only the rule_number is used for hashing since it uniquely identifies a rule
277- // This prevents spurious diffs when fields with defaults are populated in state but not in config
278- func resourceCloudStackNetworkACLRulesetHash (v interface {}) int {
279- var buf bytes.Buffer
280- m := v .(map [string ]interface {})
281-
282- // Only hash on rule_number since it's the unique identifier
283- if v , ok := m ["rule_number" ]; ok {
284- buf .WriteString (fmt .Sprintf ("%d-" , v .(int )))
285- }
286-
287- return schema .HashString (buf .String ())
288- }
289-
290274// Helper functions for UUID handling to abstract differences between
291275// 'rule' (uses uuids map) and 'ruleset' (uses uuid string)
292276
@@ -608,8 +592,15 @@ func createNetworkACLRule(d *schema.ResourceData, meta interface{}, rule map[str
608592
609593 // Set the CIDR list
610594 var cidrList []string
611- for _ , cidr := range rule ["cidr_list" ].([]interface {}) {
612- cidrList = append (cidrList , cidr .(string ))
595+ if cidrSet , ok := rule ["cidr_list" ].(* schema.Set ); ok {
596+ for _ , cidr := range cidrSet .List () {
597+ cidrList = append (cidrList , cidr .(string ))
598+ }
599+ } else {
600+ // Fallback for 'rule' field which uses TypeList
601+ for _ , cidr := range rule ["cidr_list" ].([]interface {}) {
602+ cidrList = append (cidrList , cidr .(string ))
603+ }
613604 }
614605 p .SetCidrlist (cidrList )
615606 log .Printf ("[DEBUG] Set cidr_list=%v" , cidrList )
@@ -868,15 +859,26 @@ func processPortForRuleUnified(portKey string, rule map[string]interface{}, rule
868859 // Delete the known rule so only unknown rules remain in the ruleMap
869860 delete (ruleMap , id )
870861
871- var cidrs []interface {}
872- for _ , cidr := range strings .Split (r .Cidrlist , "," ) {
873- cidrs = append (cidrs , cidr )
862+ // Create a list or set with all CIDR's depending on field type
863+ // Check if this is a ruleset rule (has uuid field) vs rule (has uuids field)
864+ _ , isRuleset := rule ["uuid" ]
865+ if isRuleset {
866+ cidrs := & schema.Set {F : schema .HashString }
867+ for _ , cidr := range strings .Split (r .Cidrlist , "," ) {
868+ cidrs .Add (cidr )
869+ }
870+ rule ["cidr_list" ] = cidrs
871+ } else {
872+ var cidrs []interface {}
873+ for _ , cidr := range strings .Split (r .Cidrlist , "," ) {
874+ cidrs = append (cidrs , cidr )
875+ }
876+ rule ["cidr_list" ] = cidrs
874877 }
875878
876879 rule ["action" ] = strings .ToLower (r .Action )
877880 rule ["protocol" ] = r .Protocol
878881 rule ["traffic_type" ] = strings .ToLower (r .Traffictype )
879- rule ["cidr_list" ] = cidrs
880882 rule ["rule_number" ] = r .Number
881883 rule ["description" ] = r .Reason
882884 // Set ICMP fields to 0 for non-ICMP protocols to avoid spurious diffs
@@ -978,10 +980,19 @@ func resourceCloudStackNetworkACLRuleRead(d *schema.ResourceData, meta interface
978980 // Delete the known rule so only unknown rules remain in the ruleMap
979981 delete (ruleMap , id )
980982
981- // Create a list with all CIDR's
982- var cidrs []interface {}
983- for _ , cidr := range strings .Split (r .Cidrlist , "," ) {
984- cidrs = append (cidrs , cidr )
983+ // Create a list or set with all CIDR's depending on field type
984+ if usingRuleset {
985+ cidrs := & schema.Set {F : schema .HashString }
986+ for _ , cidr := range strings .Split (r .Cidrlist , "," ) {
987+ cidrs .Add (cidr )
988+ }
989+ rule ["cidr_list" ] = cidrs
990+ } else {
991+ var cidrs []interface {}
992+ for _ , cidr := range strings .Split (r .Cidrlist , "," ) {
993+ cidrs = append (cidrs , cidr )
994+ }
995+ rule ["cidr_list" ] = cidrs
985996 }
986997
987998 // Update the values
@@ -990,7 +1001,6 @@ func resourceCloudStackNetworkACLRuleRead(d *schema.ResourceData, meta interface
9901001 rule ["icmp_type" ] = r .Icmptype
9911002 rule ["icmp_code" ] = r .Icmpcode
9921003 rule ["traffic_type" ] = strings .ToLower (r .Traffictype )
993- rule ["cidr_list" ] = cidrs
9941004 rule ["rule_number" ] = r .Number
9951005 rule ["description" ] = r .Reason
9961006 if usingRuleset {
@@ -1017,17 +1027,25 @@ func resourceCloudStackNetworkACLRuleRead(d *schema.ResourceData, meta interface
10171027 // Delete the known rule so only unknown rules remain in the ruleMap
10181028 delete (ruleMap , id )
10191029
1020- // Create a list with all CIDR's
1021- var cidrs []interface {}
1022- for _ , cidr := range strings .Split (r .Cidrlist , "," ) {
1023- cidrs = append (cidrs , cidr )
1030+ // Create a list or set with all CIDR's depending on field type
1031+ if usingRuleset {
1032+ cidrs := & schema.Set {F : schema .HashString }
1033+ for _ , cidr := range strings .Split (r .Cidrlist , "," ) {
1034+ cidrs .Add (cidr )
1035+ }
1036+ rule ["cidr_list" ] = cidrs
1037+ } else {
1038+ var cidrs []interface {}
1039+ for _ , cidr := range strings .Split (r .Cidrlist , "," ) {
1040+ cidrs = append (cidrs , cidr )
1041+ }
1042+ rule ["cidr_list" ] = cidrs
10241043 }
10251044
10261045 // Update the values
10271046 rule ["action" ] = strings .ToLower (r .Action )
10281047 rule ["protocol" ] = r .Protocol
10291048 rule ["traffic_type" ] = strings .ToLower (r .Traffictype )
1030- rule ["cidr_list" ] = cidrs
10311049 rule ["rule_number" ] = r .Number
10321050 rule ["description" ] = r .Reason
10331051 delete (rule , "port" ) // Remove port field for "all" protocol
@@ -1063,15 +1081,15 @@ func resourceCloudStackNetworkACLRuleRead(d *schema.ResourceData, meta interface
10631081 outOfBandRuleNumber := maxRuleNumber + 1
10641082
10651083 for uuid := range ruleMap {
1066- // We need to create and add a placeholder value to a list as the
1067- // cidr_list is a required field and thus needs a value
1068- cidrs := []interface {}{uuid }
1069-
10701084 // Make a placeholder rule to hold the unknown UUID
10711085 // Format differs between 'rule' and 'ruleset'
10721086 var rule map [string ]interface {}
10731087 if usingRuleset {
10741088 // For ruleset: use 'uuid' string and include rule_number
1089+ // cidr_list is a TypeSet for ruleset
1090+ cidrs := & schema.Set {F : schema .HashString }
1091+ cidrs .Add (uuid )
1092+
10751093 // Include all fields with defaults to avoid spurious diffs
10761094 rule = map [string ]interface {}{
10771095 "cidr_list" : cidrs ,
@@ -1088,6 +1106,8 @@ func resourceCloudStackNetworkACLRuleRead(d *schema.ResourceData, meta interface
10881106 outOfBandRuleNumber ++
10891107 } else {
10901108 // For rule: use 'uuids' map
1109+ // cidr_list is a TypeList for rule
1110+ cidrs := []interface {}{uuid }
10911111 rule = map [string ]interface {}{
10921112 "cidr_list" : cidrs ,
10931113 "protocol" : uuid ,
@@ -1107,8 +1127,9 @@ func resourceCloudStackNetworkACLRuleRead(d *schema.ResourceData, meta interface
11071127 // For TypeSet, we need to be very careful about state updates
11081128 // The SDK has issues with properly clearing removed elements from TypeSet
11091129 // So we explicitly set to empty first, then set the new value
1110- // Use the same hash function as defined in the schema to avoid spurious diffs
1111- hashFunc := resourceCloudStackNetworkACLRulesetHash
1130+ // Use schema.HashResource to match the default hash function
1131+ rulesetResource := resourceCloudStackNetworkACLRule ().Schema ["ruleset" ].Elem .(* schema.Resource )
1132+ hashFunc := schema .HashResource (rulesetResource )
11121133
11131134 // First, clear the ruleset completely
11141135 emptySet := schema .NewSet (hashFunc , []interface {}{})
@@ -1529,7 +1550,17 @@ func performNormalRuleUpdates(d *schema.ResourceData, meta interface{}, cs *clou
15291550
15301551 newRuleMap := newRule .(map [string ]interface {})
15311552 log .Printf ("[DEBUG] Comparing old rule %+v with new rule %+v" , oldRuleMap , newRuleMap )
1532- if rulesMatch (oldRuleMap , newRuleMap ) {
1553+
1554+ // For ruleset rules, match by rule_number only
1555+ // For regular rules, use the full rulesMatch function
1556+ var matched bool
1557+ if isRulesetRule (oldRuleMap ) && isRulesetRule (newRuleMap ) {
1558+ matched = rulesetRulesMatchByNumber (oldRuleMap , newRuleMap )
1559+ } else {
1560+ matched = rulesMatch (oldRuleMap , newRuleMap )
1561+ }
1562+
1563+ if matched {
15331564 log .Printf ("[DEBUG] Found matching new rule for old rule" )
15341565
15351566 // Copy UUID from old rule to new rule
@@ -1609,6 +1640,20 @@ func performNormalRuleUpdates(d *schema.ResourceData, meta interface{}, cs *clou
16091640 return nil
16101641}
16111642
1643+ // rulesetRulesMatchByNumber matches ruleset rules by rule_number only
1644+ // This allows changes to other fields (CIDR, port, protocol, etc.) to be detected as updates
1645+ func rulesetRulesMatchByNumber (oldRule , newRule map [string ]interface {}) bool {
1646+ oldRuleNum , oldHasRuleNum := oldRule ["rule_number" ].(int )
1647+ newRuleNum , newHasRuleNum := newRule ["rule_number" ].(int )
1648+
1649+ // Both must have rule_number and they must match
1650+ if ! oldHasRuleNum || ! newHasRuleNum {
1651+ return false
1652+ }
1653+
1654+ return oldRuleNum == newRuleNum
1655+ }
1656+
16121657func rulesMatch (oldRule , newRule map [string ]interface {}) bool {
16131658 oldProtocol := oldRule ["protocol" ].(string )
16141659 newProtocol := newRule ["protocol" ].(string )
@@ -1725,20 +1770,34 @@ func ruleNeedsUpdate(oldRule, newRule map[string]interface{}) bool {
17251770 }
17261771 }
17271772
1728- oldCidrs := oldRule ["cidr_list" ].([]interface {})
1729- newCidrs := newRule ["cidr_list" ].([]interface {})
1730- if len (oldCidrs ) != len (newCidrs ) {
1731- log .Printf ("[DEBUG] CIDR list length changed: %d -> %d" , len (oldCidrs ), len (newCidrs ))
1732- return true
1773+ // Handle cidr_list comparison - can be TypeSet (ruleset) or TypeList (rule)
1774+ var oldCidrStrs , newCidrStrs []string
1775+
1776+ // Extract old CIDRs
1777+ if oldSet , ok := oldRule ["cidr_list" ].(* schema.Set ); ok {
1778+ for _ , cidr := range oldSet .List () {
1779+ oldCidrStrs = append (oldCidrStrs , cidr .(string ))
1780+ }
1781+ } else if oldList , ok := oldRule ["cidr_list" ].([]interface {}); ok {
1782+ for _ , cidr := range oldList {
1783+ oldCidrStrs = append (oldCidrStrs , cidr .(string ))
1784+ }
17331785 }
17341786
1735- oldCidrStrs := make ([]string , len (oldCidrs ))
1736- newCidrStrs := make ([]string , len (newCidrs ))
1737- for i , cidr := range oldCidrs {
1738- oldCidrStrs [i ] = cidr .(string )
1787+ // Extract new CIDRs
1788+ if newSet , ok := newRule ["cidr_list" ].(* schema.Set ); ok {
1789+ for _ , cidr := range newSet .List () {
1790+ newCidrStrs = append (newCidrStrs , cidr .(string ))
1791+ }
1792+ } else if newList , ok := newRule ["cidr_list" ].([]interface {}); ok {
1793+ for _ , cidr := range newList {
1794+ newCidrStrs = append (newCidrStrs , cidr .(string ))
1795+ }
17391796 }
1740- for i , cidr := range newCidrs {
1741- newCidrStrs [i ] = cidr .(string )
1797+
1798+ if len (oldCidrStrs ) != len (newCidrStrs ) {
1799+ log .Printf ("[DEBUG] CIDR list length changed: %d -> %d" , len (oldCidrStrs ), len (newCidrStrs ))
1800+ return true
17421801 }
17431802
17441803 sort .Strings (oldCidrStrs )
@@ -1768,13 +1827,22 @@ func updateNetworkACLRule(cs *cloudstack.CloudStackClient, oldRule, newRule map[
17681827 p .SetAction (newRule ["action" ].(string ))
17691828
17701829 var cidrList []string
1771- for _ , cidr := range newRule ["cidr_list" ].([]interface {}) {
1772- cidrList = append (cidrList , cidr .(string ))
1830+ if cidrSet , ok := newRule ["cidr_list" ].(* schema.Set ); ok {
1831+ for _ , cidr := range cidrSet .List () {
1832+ cidrList = append (cidrList , cidr .(string ))
1833+ }
1834+ } else {
1835+ for _ , cidr := range newRule ["cidr_list" ].([]interface {}) {
1836+ cidrList = append (cidrList , cidr .(string ))
1837+ }
17731838 }
17741839 p .SetCidrlist (cidrList )
17751840
1776- if desc , ok := newRule ["description" ].(string ); ok && desc != "" {
1841+ // Set description from the new rule
1842+ if desc , ok := newRule ["description" ].(string ); ok {
17771843 p .SetReason (desc )
1844+ } else {
1845+ p .SetReason ("" )
17781846 }
17791847
17801848 p .SetProtocol (newRule ["protocol" ].(string ))
0 commit comments