This is an automated email from the ASF dual-hosted git repository.

pearl11594 pushed a commit to branch fix-acl-ports
in repository 
https://gitbox.apache.org/repos/asf/cloudstack-terraform-provider.git


The following commit(s) were added to refs/heads/fix-acl-ports by this push:
     new 6b1239c  add support to call updateNetworkAclItem when a rule is 
modified rather than re-creating
6b1239c is described below

commit 6b1239c9dbbef7b0989e0b352d95b6863159220e
Author: Pearl Dsilva <[email protected]>
AuthorDate: Wed Oct 8 17:27:39 2025 -0400

    add support to call updateNetworkAclItem when a rule is modified rather 
than re-creating
---
 cloudstack/resource_cloudstack_network_acl_rule.go | 433 ++++++++++++++-------
 1 file changed, 297 insertions(+), 136 deletions(-)

diff --git a/cloudstack/resource_cloudstack_network_acl_rule.go 
b/cloudstack/resource_cloudstack_network_acl_rule.go
index be6085f..1bc17ce 100644
--- a/cloudstack/resource_cloudstack_network_acl_rule.go
+++ b/cloudstack/resource_cloudstack_network_acl_rule.go
@@ -23,6 +23,7 @@ import (
        "context"
        "fmt"
        "log"
+       "sort"
        "strconv"
        "strings"
        "sync"
@@ -58,7 +59,7 @@ func resourceCloudStackNetworkACLRule() *schema.Resource {
                        },
 
                        "rule": {
-                               Type:     schema.TypeSet,
+                               Type:     schema.TypeList,
                                Optional: true,
                                Elem: &schema.Resource{
                                        Schema: map[string]*schema.Schema{
@@ -75,10 +76,9 @@ func resourceCloudStackNetworkACLRule() *schema.Resource {
                                                },
 
                                                "cidr_list": {
-                                                       Type:     
schema.TypeSet,
+                                                       Type:     
schema.TypeList,
                                                        Required: true,
                                                        Elem:     
&schema.Schema{Type: schema.TypeString},
-                                                       Set:      
schema.HashString,
                                                },
 
                                                "protocol": {
@@ -155,12 +155,12 @@ func resourceCloudStackNetworkACLRuleCreate(d 
*schema.ResourceData, meta interfa
        }
 
        // Create all rules that are configured
-       if nrs := d.Get("rule").(*schema.Set); nrs.Len() > 0 {
-               // Create an empty rule set to hold all newly created rules
-               rules := 
resourceCloudStackNetworkACLRule().Schema["rule"].ZeroValue().(*schema.Set)
+       if nrs := d.Get("rule").([]interface{}); len(nrs) > 0 {
+               // Create an empty rule list to hold all newly created rules
+               rules := make([]interface{}, 0)
 
-               log.Printf("[DEBUG] Processing %d rules", nrs.Len())
-               err := createNetworkACLRules(d, meta, rules, nrs)
+               log.Printf("[DEBUG] Processing %d rules", len(nrs))
+               err := createNetworkACLRules(d, meta, &rules, nrs)
                if err != nil {
                        log.Printf("[ERROR] Failed to create network ACL rules: 
%v", err)
                        return err
@@ -184,15 +184,15 @@ func resourceCloudStackNetworkACLRuleCreate(d 
*schema.ResourceData, meta interfa
        return resourceCloudStackNetworkACLRuleRead(d, meta)
 }
 
-func createNetworkACLRules(d *schema.ResourceData, meta interface{}, rules 
*schema.Set, nrs *schema.Set) error {
-       log.Printf("[DEBUG] Creating %d network ACL rules", nrs.Len())
+func createNetworkACLRules(d *schema.ResourceData, meta interface{}, rules 
*[]interface{}, nrs []interface{}) error {
+       log.Printf("[DEBUG] Creating %d network ACL rules", len(nrs))
        var errs *multierror.Error
 
        var wg sync.WaitGroup
-       wg.Add(nrs.Len())
+       wg.Add(len(nrs))
 
        sem := make(chan struct{}, d.Get("parallelism").(int))
-       for i, rule := range nrs.List() {
+       for i, rule := range nrs {
                // Put in a tiny sleep here to avoid DoS'ing the API
                time.Sleep(500 * time.Millisecond)
 
@@ -208,8 +208,8 @@ func createNetworkACLRules(d *schema.ResourceData, meta 
interface{}, rules *sche
                                log.Printf("[ERROR] Failed to create rule #%d: 
%v", index+1, err)
                                errs = multierror.Append(errs, fmt.Errorf("rule 
#%d: %v", index+1, err))
                        } else if len(rule["uuids"].(map[string]interface{})) > 
0 {
-                               log.Printf("[DEBUG] Successfully created rule 
#%d, adding to rules set", index+1)
-                               rules.Add(rule)
+                               log.Printf("[DEBUG] Successfully created rule 
#%d, adding to rules list", index+1)
+                               *rules = append(*rules, rule)
                        } else {
                                log.Printf("[WARN] Rule #%d created but has no 
UUIDs", index+1)
                        }
@@ -261,7 +261,7 @@ func createNetworkACLRule(d *schema.ResourceData, meta 
interface{}, rule map[str
 
        // Set the CIDR list
        var cidrList []string
-       for _, cidr := range rule["cidr_list"].(*schema.Set).List() {
+       for _, cidr := range rule["cidr_list"].([]interface{}) {
                cidrList = append(cidrList, cidr.(string))
        }
        p.SetCidrlist(cidrList)
@@ -422,12 +422,12 @@ func resourceCloudStackNetworkACLRuleRead(d 
*schema.ResourceData, meta interface
        }
        log.Printf("[DEBUG] Loaded %d rules into ruleMap", len(ruleMap))
 
-       // Create an empty schema.Set to hold all rules
-       rules := 
resourceCloudStackNetworkACLRule().Schema["rule"].ZeroValue().(*schema.Set)
+       // Create an empty rule list to hold all rules
+       var rules []interface{}
 
        // Read all rules that are configured
-       if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 {
-               for _, rule := range rs.List() {
+       if rs := d.Get("rule").([]interface{}); len(rs) > 0 {
+               for _, rule := range rs {
                        rule := rule.(map[string]interface{})
                        uuids := rule["uuids"].(map[string]interface{})
                        log.Printf("[DEBUG] Processing rule with protocol=%s, 
uuids=%+v", rule["protocol"].(string), uuids)
@@ -450,10 +450,10 @@ func resourceCloudStackNetworkACLRuleRead(d 
*schema.ResourceData, meta interface
                                // Delete the known rule so only unknown rules 
remain in the ruleMap
                                delete(ruleMap, id.(string))
 
-                               // Create a set with all CIDR's
-                               cidrs := &schema.Set{F: schema.HashString}
+                               // Create a list with all CIDR's
+                               var cidrs []interface{}
                                for _, cidr := range strings.Split(r.Cidrlist, 
",") {
-                                       cidrs.Add(cidr)
+                                       cidrs = append(cidrs, cidr)
                                }
 
                                // Update the values
@@ -463,7 +463,7 @@ func resourceCloudStackNetworkACLRuleRead(d 
*schema.ResourceData, meta interface
                                rule["icmp_code"] = r.Icmpcode
                                rule["traffic_type"] = 
strings.ToLower(r.Traffictype)
                                rule["cidr_list"] = cidrs
-                               rules.Add(rule)
+                               rules = append(rules, rule)
                                log.Printf("[DEBUG] Added ICMP rule to state: 
%+v", rule)
                        }
 
@@ -485,10 +485,10 @@ func resourceCloudStackNetworkACLRuleRead(d 
*schema.ResourceData, meta interface
                                // Delete the known rule so only unknown rules 
remain in the ruleMap
                                delete(ruleMap, id.(string))
 
-                               // Create a set with all CIDR's
-                               cidrs := &schema.Set{F: schema.HashString}
+                               // Create a list with all CIDR's
+                               var cidrs []interface{}
                                for _, cidr := range strings.Split(r.Cidrlist, 
",") {
-                                       cidrs.Add(cidr)
+                                       cidrs = append(cidrs, cidr)
                                }
 
                                // Update the values
@@ -496,7 +496,7 @@ func resourceCloudStackNetworkACLRuleRead(d 
*schema.ResourceData, meta interface
                                rule["protocol"] = r.Protocol
                                rule["traffic_type"] = 
strings.ToLower(r.Traffictype)
                                rule["cidr_list"] = cidrs
-                               rules.Add(rule)
+                               rules = append(rules, rule)
                                log.Printf("[DEBUG] Added ALL rule to state: 
%+v", rule)
                        }
 
@@ -505,12 +505,12 @@ func resourceCloudStackNetworkACLRuleRead(d 
*schema.ResourceData, meta interface
                                ps, hasPortsSet := rule["ports"].(*schema.Set)
                                portStr, hasPort := rule["port"].(string)
 
-                               if hasPortsSet && ps != nil && ps.Len() > 0 {
+                               if hasPortsSet && ps.Len() > 0 {
                                        // Handle deprecated ports field 
(multiple ports)
                                        log.Printf("[DEBUG] Processing %d ports 
for TCP/UDP rule (deprecated field)", ps.Len())
 
-                                       // Create an empty schema.Set to hold 
all ports
-                                       ports := &schema.Set{F: 
schema.HashString}
+                                       // Create an empty list to hold all 
ports
+                                       var ports []interface{}
 
                                        // Loop through all ports and retrieve 
their info
                                        for _, port := range ps.List() {
@@ -531,10 +531,10 @@ func resourceCloudStackNetworkACLRuleRead(d 
*schema.ResourceData, meta interface
                                                // Delete the known rule so 
only unknown rules remain in the ruleMap
                                                delete(ruleMap, id.(string))
 
-                                               // Create a set with all CIDR's
-                                               cidrs := &schema.Set{F: 
schema.HashString}
+                                               // Create a list with all CIDR's
+                                               var cidrs []interface{}
                                                for _, cidr := range 
strings.Split(r.Cidrlist, ",") {
-                                                       cidrs.Add(cidr)
+                                                       cidrs = append(cidrs, 
cidr)
                                                }
 
                                                // Update the values
@@ -542,17 +542,16 @@ func resourceCloudStackNetworkACLRuleRead(d 
*schema.ResourceData, meta interface
                                                rule["protocol"] = r.Protocol
                                                rule["traffic_type"] = 
strings.ToLower(r.Traffictype)
                                                rule["cidr_list"] = cidrs
-                                               ports.Add(port)
+                                               ports = append(ports, port)
                                                log.Printf("[DEBUG] Added port 
%s to TCP/UDP rule", port.(string))
                                        }
 
-                                       // Add this rule to the rules set with 
ports
-                                       rule["ports"] = ports
-                                       rules.Add(rule)
+                                       // Add this rule to the rules list with 
ports
+                                       rule["ports"] = 
schema.NewSet(schema.HashString, ports)
+                                       rules = append(rules, rule)
                                        log.Printf("[DEBUG] Added TCP/UDP rule 
with deprecated ports to state: %+v", rule)
 
                                } else if hasPort && portStr != "" {
-                                       // Handle new port field (single port)
                                        log.Printf("[DEBUG] Processing single 
port for TCP/UDP rule: %s", portStr)
 
                                        id, ok := uuids[portStr]
@@ -561,7 +560,6 @@ func resourceCloudStackNetworkACLRuleRead(d 
*schema.ResourceData, meta interface
                                                continue
                                        }
 
-                                       // Get the rule
                                        r, ok := ruleMap[id.(string)]
                                        if !ok {
                                                log.Printf("[DEBUG] TCP/UDP 
rule for port %s with ID %s not found, removing UUID", portStr, id.(string))
@@ -572,10 +570,10 @@ func resourceCloudStackNetworkACLRuleRead(d 
*schema.ResourceData, meta interface
                                        // Delete the known rule so only 
unknown rules remain in the ruleMap
                                        delete(ruleMap, id.(string))
 
-                                       // Create a set with all CIDR's
-                                       cidrs := &schema.Set{F: 
schema.HashString}
+                                       // Create a list with all CIDR's
+                                       var cidrs []interface{}
                                        for _, cidr := range 
strings.Split(r.Cidrlist, ",") {
-                                               cidrs.Add(cidr)
+                                               cidrs = append(cidrs, cidr)
                                        }
 
                                        // Update the values
@@ -584,11 +582,10 @@ func resourceCloudStackNetworkACLRuleRead(d 
*schema.ResourceData, meta interface
                                        rule["traffic_type"] = 
strings.ToLower(r.Traffictype)
                                        rule["cidr_list"] = cidrs
                                        rule["port"] = portStr
-                                       rules.Add(rule)
+                                       rules = append(rules, rule)
                                        log.Printf("[DEBUG] Added TCP/UDP rule 
with single port to state: %+v", rule)
 
                                } else {
-                                       // Handle rule with no port (all ports)
                                        log.Printf("[DEBUG] Processing TCP/UDP 
rule with no port specified")
 
                                        id, ok := uuids["all_ports"]
@@ -597,7 +594,6 @@ func resourceCloudStackNetworkACLRuleRead(d 
*schema.ResourceData, meta interface
                                                continue
                                        }
 
-                                       // Get the rule
                                        r, ok := ruleMap[id.(string)]
                                        if !ok {
                                                log.Printf("[DEBUG] TCP/UDP 
rule for all_ports with ID %s not found, removing UUID", id.(string))
@@ -605,13 +601,12 @@ func resourceCloudStackNetworkACLRuleRead(d 
*schema.ResourceData, meta interface
                                                continue
                                        }
 
-                                       // Delete the known rule so only 
unknown rules remain in the ruleMap
                                        delete(ruleMap, id.(string))
 
-                                       // Create a set with all CIDR's
-                                       cidrs := &schema.Set{F: 
schema.HashString}
+                                       // Create a list with all CIDR's
+                                       var cidrs []interface{}
                                        for _, cidr := range 
strings.Split(r.Cidrlist, ",") {
-                                               cidrs.Add(cidr)
+                                               cidrs = append(cidrs, cidr)
                                        }
 
                                        // Update the values
@@ -619,7 +614,7 @@ func resourceCloudStackNetworkACLRuleRead(d 
*schema.ResourceData, meta interface
                                        rule["protocol"] = r.Protocol
                                        rule["traffic_type"] = 
strings.ToLower(r.Traffictype)
                                        rule["cidr_list"] = cidrs
-                                       rules.Add(rule)
+                                       rules = append(rules, rule)
                                        log.Printf("[DEBUG] Added TCP/UDP rule 
with no port to state: %+v", rule)
                                }
                        }
@@ -630,10 +625,9 @@ func resourceCloudStackNetworkACLRuleRead(d 
*schema.ResourceData, meta interface
        managed := d.Get("managed").(bool)
        if managed && len(ruleMap) > 0 {
                for uuid := range ruleMap {
-                       // We need to create and add a dummy value to a 
schema.Set as the
+                       // We need to create and add a dummy value to a list as 
the
                        // cidr_list is a required field and thus needs a value
-                       cidrs := &schema.Set{F: schema.HashString}
-                       cidrs.Add(uuid)
+                       cidrs := []interface{}{uuid}
 
                        // Make a dummy rule to hold the unknown UUID
                        rule := map[string]interface{}{
@@ -642,14 +636,14 @@ func resourceCloudStackNetworkACLRuleRead(d 
*schema.ResourceData, meta interface
                                "uuids":     map[string]interface{}{uuid: uuid},
                        }
 
-                       // Add the dummy rule to the rules set
-                       rules.Add(rule)
+                       // Add the dummy rule to the rules list
+                       rules = append(rules, rule)
                        log.Printf("[DEBUG] Added managed dummy rule for UUID 
%s", uuid)
                }
        }
 
-       if rules.Len() > 0 {
-               log.Printf("[DEBUG] Setting %d rules in state", rules.Len())
+       if len(rules) > 0 {
+               log.Printf("[DEBUG] Setting %d rules in state", len(rules))
                if err := d.Set("rule", rules); err != nil {
                        log.Printf("[ERROR] Failed to set rule attribute: %v", 
err)
                        return err
@@ -669,40 +663,16 @@ func resourceCloudStackNetworkACLRuleUpdate(d 
*schema.ResourceData, meta interfa
                return err
        }
 
-       // Check if the rule set as a whole has changed
+       // Check if the rule list has changed
        if d.HasChange("rule") {
                o, n := d.GetChange("rule")
-               ors := o.(*schema.Set).Difference(n.(*schema.Set))
-               nrs := n.(*schema.Set).Difference(o.(*schema.Set))
-
-               // We need to start with a rule set containing all the rules we
-               // already have and want to keep. Any rules that are not deleted
-               // correctly and any newly created rules, will be added to this
-               // set to make sure we end up in a consistent state
-               rules := o.(*schema.Set).Intersection(n.(*schema.Set))
-
-               // First loop through all the new rules and create (before 
destroy) them
-               if nrs.Len() > 0 {
-                       err := createNetworkACLRules(d, meta, rules, nrs)
-
-                       // We need to update this first to preserve the correct 
state
-                       d.Set("rule", rules)
-
-                       if err != nil {
-                               return err
-                       }
-               }
-
-               // Then loop through all the old rules and delete them
-               if ors.Len() > 0 {
-                       err := deleteNetworkACLRules(d, meta, rules, ors)
+               oldRules := o.([]interface{})
+               newRules := n.([]interface{})
 
-                       // We need to update this first to preserve the correct 
state
-                       d.Set("rule", rules)
-
-                       if err != nil {
-                               return err
-                       }
+               log.Printf("[DEBUG] Rule list changed, performing efficient 
updates")
+               err := updateNetworkACLRules(d, meta, oldRules, newRules)
+               if err != nil {
+                       return err
                }
        }
 
@@ -710,59 +680,19 @@ func resourceCloudStackNetworkACLRuleUpdate(d 
*schema.ResourceData, meta interfa
 }
 
 func resourceCloudStackNetworkACLRuleDelete(d *schema.ResourceData, meta 
interface{}) error {
-       // Create an empty rule set to hold all rules that where
-       // not deleted correctly
-       rules := 
resourceCloudStackNetworkACLRule().Schema["rule"].ZeroValue().(*schema.Set)
-
        // Delete all rules
-       if ors := d.Get("rule").(*schema.Set); ors.Len() > 0 {
-               err := deleteNetworkACLRules(d, meta, rules, ors)
-
-               // We need to update this first to preserve the correct state
-               d.Set("rule", rules)
-
-               if err != nil {
-                       return err
-               }
-       }
-
-       return nil
-}
-
-func deleteNetworkACLRules(d *schema.ResourceData, meta interface{}, rules 
*schema.Set, ors *schema.Set) error {
-       var errs *multierror.Error
-
-       var wg sync.WaitGroup
-       wg.Add(ors.Len())
-
-       sem := make(chan struct{}, d.Get("parallelism").(int))
-       for _, rule := range ors.List() {
-               // Put a sleep here to avoid DoS'ing the API
-               time.Sleep(500 * time.Millisecond)
-
-               go func(rule map[string]interface{}) {
-                       defer wg.Done()
-                       sem <- struct{}{}
-
-                       // Delete a single rule
-                       err := deleteNetworkACLRule(d, meta, rule)
-
-                       // If we have at least one UUID, we need to save the 
rule
-                       if len(rule["uuids"].(map[string]interface{})) > 0 {
-                               rules.Add(rule)
-                       }
-
+       if ors := d.Get("rule").([]interface{}); len(ors) > 0 {
+               for _, rule := range ors {
+                       ruleMap := rule.(map[string]interface{})
+                       err := deleteNetworkACLRule(d, meta, ruleMap)
                        if err != nil {
-                               errs = multierror.Append(errs, err)
+                               log.Printf("[ERROR] Failed to delete rule: %v", 
err)
+                               return err
                        }
-
-                       <-sem
-               }(rule.(map[string]interface{}))
+               }
        }
 
-       wg.Wait()
-
-       return errs.ErrorOrNil()
+       return nil
 }
 
 func deleteNetworkACLRule(d *schema.ResourceData, meta interface{}, rule 
map[string]interface{}) error {
@@ -936,3 +866,234 @@ func checkACLListExists(cs *cloudstack.CloudStackClient, 
aclID string) (bool, er
        log.Printf("[DEBUG] ACL list check result: count=%d", count)
        return count > 0, nil
 }
+
+func updateNetworkACLRules(d *schema.ResourceData, meta interface{}, oldRules, 
newRules []interface{}) error {
+       cs := meta.(*cloudstack.CloudStackClient)
+       log.Printf("[DEBUG] Updating ACL rules: %d old rules, %d new rules", 
len(oldRules), len(newRules))
+
+       oldRuleMap := make(map[string]map[string]interface{})
+       newRuleMap := make(map[string]map[string]interface{})
+
+       for _, rule := range oldRules {
+               ruleMap := rule.(map[string]interface{})
+               key := createRuleKey(ruleMap)
+               oldRuleMap[key] = ruleMap
+               log.Printf("[DEBUG] Old rule key: %s", key)
+       }
+
+       for _, rule := range newRules {
+               ruleMap := rule.(map[string]interface{})
+               key := createRuleKey(ruleMap)
+               newRuleMap[key] = ruleMap
+               log.Printf("[DEBUG] New rule key: %s", key)
+       }
+
+       for key, oldRule := range oldRuleMap {
+               if _, exists := newRuleMap[key]; !exists {
+                       log.Printf("[DEBUG] Deleting rule: %s", key)
+                       err := deleteNetworkACLRule(d, meta, oldRule)
+                       if err != nil {
+                               return fmt.Errorf("failed to delete rule %s: 
%v", key, err)
+                       }
+               }
+       }
+
+       var rulesToCreate []interface{}
+       for key, newRule := range newRuleMap {
+               if _, exists := oldRuleMap[key]; !exists {
+                       log.Printf("[DEBUG] Creating new rule: %s", key)
+                       rulesToCreate = append(rulesToCreate, newRule)
+               }
+       }
+
+       if len(rulesToCreate) > 0 {
+               var createdRules []interface{}
+               err := createNetworkACLRules(d, meta, &createdRules, 
rulesToCreate)
+               if err != nil {
+                       return fmt.Errorf("failed to create new rules: %v", err)
+               }
+       }
+
+       for key, newRule := range newRuleMap {
+               if oldRule, exists := oldRuleMap[key]; exists {
+                       if ruleNeedsUpdate(oldRule, newRule) {
+                               log.Printf("[DEBUG] Updating rule: %s", key)
+                               err := updateNetworkACLRule(cs, oldRule, 
newRule)
+                               if err != nil {
+                                       return fmt.Errorf("failed to update 
rule %s: %v", key, err)
+                               }
+                       }
+               }
+       }
+
+       return nil
+}
+
+func createRuleKey(rule map[string]interface{}) string {
+       protocol := rule["protocol"].(string)
+       trafficType := rule["traffic_type"].(string)
+
+       if protocol == "icmp" {
+               icmpType := rule["icmp_type"].(int)
+               icmpCode := rule["icmp_code"].(int)
+               return fmt.Sprintf("%s-%s-icmp-%d-%d", protocol, trafficType, 
icmpType, icmpCode)
+       }
+
+       if protocol == "all" {
+               return fmt.Sprintf("%s-%s-all", protocol, trafficType)
+       }
+
+       if protocol == "tcp" || protocol == "udp" {
+               portStr, hasPort := rule["port"].(string)
+               if hasPort && portStr != "" {
+                       return fmt.Sprintf("%s-%s-port-%s", protocol, 
trafficType, portStr)
+               } else {
+                       return fmt.Sprintf("%s-%s-noport", protocol, 
trafficType)
+               }
+       }
+
+       // For numeric protocols
+       return fmt.Sprintf("%s-%s", protocol, trafficType)
+}
+
+func ruleNeedsUpdate(oldRule, newRule map[string]interface{}) bool {
+       if oldRule["action"].(string) != newRule["action"].(string) {
+               log.Printf("[DEBUG] Action changed: %s -> %s", 
oldRule["action"].(string), newRule["action"].(string))
+               return true
+       }
+
+       if oldRule["protocol"].(string) != newRule["protocol"].(string) {
+               log.Printf("[DEBUG] Protocol changed: %s -> %s", 
oldRule["protocol"].(string), newRule["protocol"].(string))
+               return true
+       }
+
+       if oldRule["traffic_type"].(string) != newRule["traffic_type"].(string) 
{
+               log.Printf("[DEBUG] Traffic type changed: %s -> %s", 
oldRule["traffic_type"].(string), newRule["traffic_type"].(string))
+               return true
+       }
+
+       oldDesc, oldHasDesc := oldRule["description"].(string)
+       newDesc, newHasDesc := newRule["description"].(string)
+       if oldHasDesc != newHasDesc || (oldHasDesc && newHasDesc && oldDesc != 
newDesc) {
+               log.Printf("[DEBUG] Description changed: %s -> %s", oldDesc, 
newDesc)
+               return true
+       }
+
+       protocol := newRule["protocol"].(string)
+       switch protocol {
+       case "icmp":
+               if oldRule["icmp_type"].(int) != newRule["icmp_type"].(int) {
+                       log.Printf("[DEBUG] ICMP type changed: %d -> %d", 
oldRule["icmp_type"].(int), newRule["icmp_type"].(int))
+                       return true
+               }
+               if oldRule["icmp_code"].(int) != newRule["icmp_code"].(int) {
+                       log.Printf("[DEBUG] ICMP code changed: %d -> %d", 
oldRule["icmp_code"].(int), newRule["icmp_code"].(int))
+                       return true
+               }
+       case "tcp", "udp":
+               oldPort, oldHasPort := oldRule["port"].(string)
+               newPort, newHasPort := newRule["port"].(string)
+               if oldHasPort != newHasPort || (oldHasPort && newHasPort && 
oldPort != newPort) {
+                       log.Printf("[DEBUG] Port changed: %s -> %s", oldPort, 
newPort)
+                       return true
+               }
+       }
+
+       oldCidrs := oldRule["cidr_list"].([]interface{})
+       newCidrs := newRule["cidr_list"].([]interface{})
+       if len(oldCidrs) != len(newCidrs) {
+               log.Printf("[DEBUG] CIDR list length changed: %d -> %d", 
len(oldCidrs), len(newCidrs))
+               return true
+       }
+
+       oldCidrStrs := make([]string, len(oldCidrs))
+       newCidrStrs := make([]string, len(newCidrs))
+       for i, cidr := range oldCidrs {
+               oldCidrStrs[i] = cidr.(string)
+       }
+       for i, cidr := range newCidrs {
+               newCidrStrs[i] = cidr.(string)
+       }
+
+       sort.Strings(oldCidrStrs)
+       sort.Strings(newCidrStrs)
+
+       for i, oldCidr := range oldCidrStrs {
+               if oldCidr != newCidrStrs[i] {
+                       log.Printf("[DEBUG] CIDR changed at index %d: %s -> 
%s", i, oldCidr, newCidrStrs[i])
+                       return true
+               }
+       }
+
+       return false
+}
+
+func updateNetworkACLRule(cs *cloudstack.CloudStackClient, oldRule, newRule 
map[string]interface{}) error {
+       uuids := oldRule["uuids"].(map[string]interface{})
+
+       for key, uuid := range uuids {
+               if key == "%" {
+                       continue
+               }
+
+               log.Printf("[DEBUG] Updating ACL rule with UUID: %s", 
uuid.(string))
+               p := cs.NetworkACL.NewUpdateNetworkACLItemParams(uuid.(string))
+
+               p.SetAction(newRule["action"].(string))
+
+               var cidrList []string
+               for _, cidr := range newRule["cidr_list"].([]interface{}) {
+                       cidrList = append(cidrList, cidr.(string))
+               }
+               p.SetCidrlist(cidrList)
+
+               if desc, ok := newRule["description"].(string); ok && desc != 
"" {
+                       p.SetReason(desc)
+               }
+
+               p.SetProtocol(newRule["protocol"].(string))
+
+               p.SetTraffictype(newRule["traffic_type"].(string))
+
+               protocol := newRule["protocol"].(string)
+               switch protocol {
+               case "icmp":
+                       if icmpType, ok := newRule["icmp_type"].(int); ok {
+                               p.SetIcmptype(icmpType)
+                               log.Printf("[DEBUG] Set icmp_type=%d", icmpType)
+                       }
+                       if icmpCode, ok := newRule["icmp_code"].(int); ok {
+                               p.SetIcmpcode(icmpCode)
+                               log.Printf("[DEBUG] Set icmp_code=%d", icmpCode)
+                       }
+               case "tcp", "udp":
+                       if portStr, hasPort := newRule["port"].(string); 
hasPort && portStr != "" {
+                               m := splitPorts.FindStringSubmatch(portStr)
+                               if m != nil {
+                                       startPort, err := strconv.Atoi(m[1])
+                                       if err == nil {
+                                               endPort := startPort
+                                               if m[2] != "" {
+                                                       if ep, err := 
strconv.Atoi(m[2]); err == nil {
+                                                               endPort = ep
+                                                       }
+                                               }
+                                               p.SetStartport(startPort)
+                                               p.SetEndport(endPort)
+                                               log.Printf("[DEBUG] Set port 
start=%d, end=%d", startPort, endPort)
+                                       }
+                               }
+                       }
+               }
+
+               _, err := cs.NetworkACL.UpdateNetworkACLItem(p)
+               if err != nil {
+                       log.Printf("[ERROR] Failed to update ACL rule %s: %v", 
uuid.(string), err)
+                       return err
+               }
+
+               log.Printf("[DEBUG] Successfully updated ACL rule %s", 
uuid.(string))
+       }
+
+       return nil
+}

Reply via email to