@@ -99,10 +99,16 @@ func resourceCloudStackNetworkACLRule() *schema.Resource {
9999 },
100100
101101 "ports" : {
102- Type : schema .TypeSet ,
102+ Type : schema .TypeSet ,
103+ Optional : true ,
104+ Elem : & schema.Schema {Type : schema .TypeString },
105+ Set : schema .HashString ,
106+ Deprecated : "Use 'port' instead. The 'ports' field is deprecated and will be removed in a future version." ,
107+ },
108+
109+ "port" : {
110+ Type : schema .TypeString ,
103111 Optional : true ,
104- Elem : & schema.Schema {Type : schema .TypeString },
105- Set : schema .HashString ,
106112 },
107113
108114 "traffic_type" : {
@@ -299,42 +305,19 @@ func createNetworkACLRule(d *schema.ResourceData, meta interface{}, rule map[str
299305 log .Printf ("[DEBUG] Created ALL rule with ID=%s" , r .(* cloudstack.CreateNetworkACLResponse ).Id )
300306 }
301307
302- // If protocol is TCP or UDP, create the rule (with or without ports )
308+ // If protocol is TCP or UDP, create the rule (with or without port )
303309 if rule ["protocol" ].(string ) == "tcp" || rule ["protocol" ].(string ) == "udp" {
304- ps , ok := rule ["ports" ].(* schema.Set )
305- if ! ok || ps == nil {
306- log .Printf ("[DEBUG] No ports specified for TCP/UDP rule, creating rule for all ports" )
307- ps = & schema.Set {F : schema .HashString }
308- }
310+ portStr , hasPort := rule ["port" ].(string )
309311
310- // Create an empty schema.Set to hold all processed ports
311- ports := & schema.Set {F : schema .HashString }
312- log .Printf ("[DEBUG] Processing %d ports for TCP/UDP rule" , ps .Len ())
313-
314- if ps .Len () == 0 {
315- // Create a rule for all ports
316- r , err := Retry (4 , retryableACLCreationFunc (cs , p ))
317- if err != nil {
318- log .Printf ("[ERROR] Failed to create TCP/UDP rule for all ports: %v" , err )
319- return err
320- }
321- uuids ["all_ports" ] = r .(* cloudstack.CreateNetworkACLResponse ).Id
322- rule ["uuids" ] = uuids
323- log .Printf ("[DEBUG] Created TCP/UDP rule for all ports with ID=%s" , r .(* cloudstack.CreateNetworkACLResponse ).Id )
324- } else {
325- // Process specified ports
326- for _ , port := range ps .List () {
327- if _ , ok := uuids [port .(string )]; ok {
328- ports .Add (port )
329- rule ["ports" ] = ports
330- log .Printf ("[DEBUG] Port %s already has UUID, skipping" , port .(string ))
331- continue
332- }
312+ if hasPort && portStr != "" {
313+ // Handle single port
314+ log .Printf ("[DEBUG] Processing single port for TCP/UDP rule: %s" , portStr )
333315
334- m := splitPorts .FindStringSubmatch (port .(string ))
316+ if _ , ok := uuids [portStr ]; ! ok {
317+ m := splitPorts .FindStringSubmatch (portStr )
335318 if m == nil {
336- log .Printf ("[ERROR] Invalid port format: %s" , port .( string ) )
337- return fmt .Errorf ("%q is not a valid port value. Valid options are '80' or '80-90'" , port .( string ) )
319+ log .Printf ("[ERROR] Invalid port format: %s" , portStr )
320+ return fmt .Errorf ("%q is not a valid port value. Valid options are '80' or '80-90'" , portStr )
338321 }
339322
340323 startPort , err := strconv .Atoi (m [1 ])
@@ -354,20 +337,31 @@ func createNetworkACLRule(d *schema.ResourceData, meta interface{}, rule map[str
354337
355338 p .SetStartport (startPort )
356339 p .SetEndport (endPort )
357- log .Printf ("[DEBUG] Set ports start=%d, end=%d" , startPort , endPort )
340+ log .Printf ("[DEBUG] Set port start=%d, end=%d" , startPort , endPort )
358341
359342 r , err := Retry (4 , retryableACLCreationFunc (cs , p ))
360343 if err != nil {
361- log .Printf ("[ERROR] Failed to create TCP/UDP rule for port %s: %v" , port .( string ) , err )
344+ log .Printf ("[ERROR] Failed to create TCP/UDP rule for port %s: %v" , portStr , err )
362345 return err
363346 }
364347
365- ports .Add (port )
366- rule ["ports" ] = ports
367- uuids [port .(string )] = r .(* cloudstack.CreateNetworkACLResponse ).Id
348+ uuids [portStr ] = r .(* cloudstack.CreateNetworkACLResponse ).Id
368349 rule ["uuids" ] = uuids
369- log .Printf ("[DEBUG] Created TCP/UDP rule for port %s with ID=%s" , port .(string ), r .(* cloudstack.CreateNetworkACLResponse ).Id )
350+ log .Printf ("[DEBUG] Created TCP/UDP rule for port %s with ID=%s" , portStr , r .(* cloudstack.CreateNetworkACLResponse ).Id )
351+ } else {
352+ log .Printf ("[DEBUG] Port %s already has UUID, skipping" , portStr )
353+ }
354+ } else {
355+ // No port specified - create rule for all ports
356+ log .Printf ("[DEBUG] No port specified for TCP/UDP rule, creating rule for all ports" )
357+ r , err := Retry (4 , retryableACLCreationFunc (cs , p ))
358+ if err != nil {
359+ log .Printf ("[ERROR] Failed to create TCP/UDP rule for all ports: %v" , err )
360+ return err
370361 }
362+ uuids ["all_ports" ] = r .(* cloudstack.CreateNetworkACLResponse ).Id
363+ rule ["uuids" ] = uuids
364+ log .Printf ("[DEBUG] Created TCP/UDP rule for all ports with ID=%s" , r .(* cloudstack.CreateNetworkACLResponse ).Id )
371365 }
372366 }
373367
@@ -507,29 +501,71 @@ func resourceCloudStackNetworkACLRuleRead(d *schema.ResourceData, meta interface
507501 }
508502
509503 if rule ["protocol" ].(string ) == "tcp" || rule ["protocol" ].(string ) == "udp" {
510- ps , ok := rule ["ports" ].(* schema.Set )
511- if ! ok || ps == nil {
512- log .Printf ("[DEBUG] No ports specified for TCP/UDP rule, initializing empty set" )
513- ps = & schema.Set {F : schema .HashString }
514- }
504+ // Check for deprecated ports field first (for backward compatibility)
505+ ps , hasPortsSet := rule ["ports" ].(* schema.Set )
506+ portStr , hasPort := rule ["port" ].(string )
507+
508+ if hasPortsSet && ps != nil && ps .Len () > 0 {
509+ // Handle deprecated ports field (multiple ports)
510+ log .Printf ("[DEBUG] Processing %d ports for TCP/UDP rule (deprecated field)" , ps .Len ())
511+
512+ // Create an empty schema.Set to hold all ports
513+ ports := & schema.Set {F : schema .HashString }
514+
515+ // Loop through all ports and retrieve their info
516+ for _ , port := range ps .List () {
517+ id , ok := uuids [port .(string )]
518+ if ! ok {
519+ log .Printf ("[DEBUG] No UUID for port %s, skipping" , port .(string ))
520+ continue
521+ }
522+
523+ // Get the rule
524+ r , ok := ruleMap [id .(string )]
525+ if ! ok {
526+ log .Printf ("[DEBUG] TCP/UDP rule for port %s with ID %s not found, removing UUID" , port .(string ), id .(string ))
527+ delete (uuids , port .(string ))
528+ continue
529+ }
530+
531+ // Delete the known rule so only unknown rules remain in the ruleMap
532+ delete (ruleMap , id .(string ))
533+
534+ // Create a set with all CIDR's
535+ cidrs := & schema.Set {F : schema .HashString }
536+ for _ , cidr := range strings .Split (r .Cidrlist , "," ) {
537+ cidrs .Add (cidr )
538+ }
539+
540+ // Update the values
541+ rule ["action" ] = strings .ToLower (r .Action )
542+ rule ["protocol" ] = r .Protocol
543+ rule ["traffic_type" ] = strings .ToLower (r .Traffictype )
544+ rule ["cidr_list" ] = cidrs
545+ ports .Add (port )
546+ log .Printf ("[DEBUG] Added port %s to TCP/UDP rule" , port .(string ))
547+ }
515548
516- // Create an empty schema.Set to hold all ports
517- ports := & schema.Set {F : schema .HashString }
518- log .Printf ("[DEBUG] Processing %d ports for TCP/UDP rule" , ps .Len ())
549+ // Add this rule to the rules set with ports
550+ rule ["ports" ] = ports
551+ rules .Add (rule )
552+ log .Printf ("[DEBUG] Added TCP/UDP rule with deprecated ports to state: %+v" , rule )
519553
520- // Loop through all ports and retrieve their info
521- for _ , port := range ps .List () {
522- id , ok := uuids [port .(string )]
554+ } else if hasPort && portStr != "" {
555+ // Handle new port field (single port)
556+ log .Printf ("[DEBUG] Processing single port for TCP/UDP rule: %s" , portStr )
557+
558+ id , ok := uuids [portStr ]
523559 if ! ok {
524- log .Printf ("[DEBUG] No UUID for port %s, skipping" , port .( string ) )
560+ log .Printf ("[DEBUG] No UUID for port %s, skipping rule " , portStr )
525561 continue
526562 }
527563
528564 // Get the rule
529565 r , ok := ruleMap [id .(string )]
530566 if ! ok {
531- log .Printf ("[DEBUG] TCP/UDP rule for port %s with ID %s not found, removing UUID" , port .( string ) , id .(string ))
532- delete (uuids , port .( string ) )
567+ log .Printf ("[DEBUG] TCP/UDP rule for port %s with ID %s not found, removing UUID" , portStr , id .(string ))
568+ delete (uuids , portStr )
533569 continue
534570 }
535571
@@ -547,20 +583,44 @@ func resourceCloudStackNetworkACLRuleRead(d *schema.ResourceData, meta interface
547583 rule ["protocol" ] = r .Protocol
548584 rule ["traffic_type" ] = strings .ToLower (r .Traffictype )
549585 rule ["cidr_list" ] = cidrs
550- ports .Add (port )
551- log .Printf ("[DEBUG] Added port %s to TCP/UDP rule" , port .(string ))
552- }
553-
554- // If there is at least one port found, add this rule to the rules set
555- if ports .Len () > 0 {
556- rule ["ports" ] = ports
586+ rule ["port" ] = portStr
557587 rules .Add (rule )
558- log .Printf ("[DEBUG] Added TCP/UDP rule to state: %+v" , rule )
588+ log .Printf ("[DEBUG] Added TCP/UDP rule with single port to state: %+v" , rule )
589+
559590 } else {
560- // Add the rule even if no ports are specified, as ports are optional
561- rule ["ports" ] = ports
591+ // Handle rule with no port (all ports)
592+ log .Printf ("[DEBUG] Processing TCP/UDP rule with no port specified" )
593+
594+ id , ok := uuids ["all_ports" ]
595+ if ! ok {
596+ log .Printf ("[DEBUG] No UUID for all_ports, skipping rule" )
597+ continue
598+ }
599+
600+ // Get the rule
601+ r , ok := ruleMap [id .(string )]
602+ if ! ok {
603+ log .Printf ("[DEBUG] TCP/UDP rule for all_ports with ID %s not found, removing UUID" , id .(string ))
604+ delete (uuids , "all_ports" )
605+ continue
606+ }
607+
608+ // Delete the known rule so only unknown rules remain in the ruleMap
609+ delete (ruleMap , id .(string ))
610+
611+ // Create a set with all CIDR's
612+ cidrs := & schema.Set {F : schema .HashString }
613+ for _ , cidr := range strings .Split (r .Cidrlist , "," ) {
614+ cidrs .Add (cidr )
615+ }
616+
617+ // Update the values
618+ rule ["action" ] = strings .ToLower (r .Action )
619+ rule ["protocol" ] = r .Protocol
620+ rule ["traffic_type" ] = strings .ToLower (r .Traffictype )
621+ rule ["cidr_list" ] = cidrs
562622 rules .Add (rule )
563- log .Printf ("[DEBUG] Added TCP/UDP rule with no ports to state: %+v" , rule )
623+ log .Printf ("[DEBUG] Added TCP/UDP rule with no port to state: %+v" , rule )
564624 }
565625 }
566626 }
@@ -791,19 +851,26 @@ func verifyNetworkACLRuleParams(d *schema.ResourceData, rule map[string]interfac
791851 // No additional test are needed
792852 log .Printf ("[DEBUG] Protocol 'all' validated" )
793853 case "tcp" , "udp" :
794- if ports , ok := rule ["ports" ].(* schema.Set ); ok {
795- log .Printf ("[DEBUG] Found %d ports for TCP/UDP" , ports .Len ())
796- for _ , port := range ports .List () {
797- m := splitPorts .FindStringSubmatch (port .(string ))
798- if m == nil {
799- log .Printf ("[ERROR] Invalid port format: %s" , port .(string ))
800- return fmt .Errorf (
801- "%q is not a valid port value. Valid options are '80' or '80-90'" , port .(string ))
802- }
854+ // Check if deprecated ports field is used (not allowed for new configurations)
855+ portsSet , hasPortsSet := rule ["ports" ].(* schema.Set )
856+ portStr , hasPort := rule ["port" ].(string )
857+
858+ if hasPortsSet && portsSet .Len () > 0 {
859+ log .Printf ("[ERROR] Deprecated ports field used in new configuration" )
860+ return fmt .Errorf ("The 'ports' field is deprecated. Use 'port' instead for new configurations." )
861+ }
862+
863+ // Validate the new port field if used
864+ if hasPort && portStr != "" {
865+ log .Printf ("[DEBUG] Found port for TCP/UDP: %s" , portStr )
866+ m := splitPorts .FindStringSubmatch (portStr )
867+ if m == nil {
868+ log .Printf ("[ERROR] Invalid port format: %s" , portStr )
869+ return fmt .Errorf (
870+ "%q is not a valid port value. Valid options are '80' or '80-90'" , portStr )
803871 }
804872 } else {
805- log .Printf ("[DEBUG] No ports specified for TCP/UDP, assuming empty set" )
806- // Allow empty ports for TCP/UDP (your config has no ports)
873+ log .Printf ("[DEBUG] No port specified for TCP/UDP, allowing empty port" )
807874 }
808875 default :
809876 _ , err := strconv .ParseInt (protocol , 0 , 0 )
0 commit comments