Repository: hetznercloud/cli (go)

Issue:
fix: some flags use StringArray which does not split by comma
### TL;DR

Some commands do not allow comma separated input for list flags. Example `hcloud firewall add-rule --destionation-ips` 

### Expected behavior

Our flags should consistently allow the use of commas to specify multiple values.

### Observed behavior

If a comma is used to separate the values, CLI does not split them and instead passes it along as one big value. It is not clear for the user how they can circumvent this.

### Minimal working example

```shell
$ hcloud firewall create test
$ hcloud firewall add-rule \
    --direction in \
    --port 443 \
    --protocol tcp \
    --description "Test" \
    --source-ips "127.0.0.1/32,127.0.0.2/32" \
    test
hcloud: source ips error on index 0: invalid CIDR address: 127.0.0.1/32,127.0.0.2/32
```

### Log output

```plain text

```

### Additional information

### Workaround

Until this is fixed, it can be worked around by specifying the flag once per value:

```shell
hcloud firewall add-rule --source-ips $IP1 --source-ips $IP2
```

### Issue

The commands use `StringArray()` instead of `StringSlice()` to define the argument. `StringArray()` specifically does not split the input by comma.

Pull request:
fix: some list flags are not correctly parsed
Three flags were not correctly split up when the user specified multiple values separated by comma. This happened because the `StringArray` flag explitely does not split the input. `StringSlice`, which we use nearly everywhere else, does split input by comma.

Fixes #986

Patch:
diff --git a/internal/cmd/firewall/add_rule.go b/internal/cmd/firewall/add_rule.go
index 2df06bd..16fad3c 100644
--- a/internal/cmd/firewall/add_rule.go
+++ b/internal/cmd/firewall/add_rule.go
@@ -31,9 +31,9 @@ var AddRuleCmd = base.Cmd{
 		_ = cmd.RegisterFlagCompletionFunc("protocol", cmpl.SuggestCandidates("icmp", "udp", "tcp", "esp", "gre"))
 		_ = cmd.MarkFlagRequired("protocol")
 
-		cmd.Flags().StringArray("source-ips", []string{}, "Source IPs (CIDR Notation) (required when direction is in)")
+		cmd.Flags().StringSlice("source-ips", []string{}, "Source IPs (CIDR Notation) (required when direction is in)")
 
-		cmd.Flags().StringArray("destination-ips", []string{}, "Destination IPs (CIDR Notation) (required when direction is out)")
+		cmd.Flags().StringSlice("destination-ips", []string{}, "Destination IPs (CIDR Notation) (required when direction is out)")
 
 		cmd.Flags().String("port", "", "Port to which traffic will be allowed, only applicable for protocols TCP and UDP, you can specify port ranges, sample: 80-85")
 
@@ -76,8 +76,8 @@ var AddRuleCmd = base.Cmd{
 func parseRuleFromArgs(flags *pflag.FlagSet) (*hcloud.FirewallRule, error) {
 	direction, _ := flags.GetString("direction")
 	protocol, _ := flags.GetString("protocol")
-	sourceIPs, _ := flags.GetStringArray("source-ips")
-	destinationIPs, _ := flags.GetStringArray("destination-ips")
+	sourceIPs, _ := flags.GetStringSlice("source-ips")
+	destinationIPs, _ := flags.GetStringSlice("destination-ips")
 	port, _ := flags.GetString("port")
 	description, _ := flags.GetString("description")
 
diff --git a/internal/cmd/firewall/delete_rule.go b/internal/cmd/firewall/delete_rule.go
index 3b3d160..2c6ad13 100644
--- a/internal/cmd/firewall/delete_rule.go
+++ b/internal/cmd/firewall/delete_rule.go
@@ -30,9 +30,9 @@ var DeleteRuleCmd = base.Cmd{
 		_ = cmd.RegisterFlagCompletionFunc("protocol", cmpl.SuggestCandidates("icmp", "udp", "tcp", "esp", "gre"))
 		_ = cmd.MarkFlagRequired("protocol")
 
-		cmd.Flags().StringArray("source-ips", []string{}, "Source IPs (CIDR Notation) (required when direction is in)")
+		cmd.Flags().StringSlice("source-ips", []string{}, "Source IPs (CIDR Notation) (required when direction is in)")
 
-		cmd.Flags().StringArray("destination-ips", []string{}, "Destination IPs (CIDR Notation) (required when direction is out)")
+		cmd.Flags().StringSlice("destination-ips", []string{}, "Destination IPs (CIDR Notation) (required when direction is out)")
 
 		cmd.Flags().String("port", "", "Port to which traffic will be allowed, only applicable for protocols TCP and UDP")
 


Test patch:
diff --git a/internal/cmd/firewall/add_rule_test.go b/internal/cmd/firewall/add_rule_test.go
index 0466a3a..9a3316c 100644
--- a/internal/cmd/firewall/add_rule_test.go
+++ b/internal/cmd/firewall/add_rule_test.go
@@ -32,7 +32,7 @@ func TestAddRule(t *testing.T) {
 		SetRules(gomock.Any(), fw, hcloud.FirewallSetRulesOpts{
 			Rules: []hcloud.FirewallRule{{
 				Direction:      hcloud.FirewallRuleDirectionIn,
-				SourceIPs:      []net.IPNet{{IP: net.IP{0, 0, 0, 0}, Mask: net.IPMask{0, 0, 0, 0}}},
+				SourceIPs:      []net.IPNet{{IP: net.IP{0, 0, 0, 0}, Mask: net.IPMask{0, 0, 0, 0}}, {IP: net.IP{127, 0, 0, 1}, Mask: net.IPMask{255, 255, 255, 255}}},
 				DestinationIPs: []net.IPNet{},
 				Protocol:       hcloud.FirewallRuleProtocolTCP,
 				Port:           hcloud.Ptr("80"),
@@ -44,7 +44,7 @@ func TestAddRule(t *testing.T) {
 		WaitForActions(gomock.Any(), gomock.Any(), []*hcloud.Action{{ID: 123}, {ID: 321}}).
 		Return(nil)
 
-	out, errOut, err := fx.Run(cmd, []string{"--direction", "in", "--protocol", "tcp", "--source-ips", "0.0.0.0/0", "--port", "80", "--description", "http", "test"})
+	out, errOut, err := fx.Run(cmd, []string{"--direction", "in", "--protocol", "tcp", "--source-ips", "0.0.0.0/0,127.0.0.1/32", "--port", "80", "--description", "http", "test"})
 
 	expOut := "Firewall Rules for Firewall 123 updated\n"
 
diff --git a/internal/cmd/firewall/delete_rule_test.go b/internal/cmd/firewall/delete_rule_test.go
index b97bbb0..66f1e78 100644
--- a/internal/cmd/firewall/delete_rule_test.go
+++ b/internal/cmd/firewall/delete_rule_test.go
@@ -25,7 +25,7 @@ func TestDeleteRule(t *testing.T) {
 		Name: "test",
 		Rules: []hcloud.FirewallRule{{
 			Direction:      hcloud.FirewallRuleDirectionIn,
-			SourceIPs:      []net.IPNet{{IP: net.IP{0, 0, 0, 0}, Mask: net.IPMask{0, 0, 0, 0}}},
+			SourceIPs:      []net.IPNet{{IP: net.IP{0, 0, 0, 0}, Mask: net.IPMask{0, 0, 0, 0}}, {IP: net.IP{127, 0, 0, 1}, Mask: net.IPMask{255, 255, 255, 255}}},
 			DestinationIPs: []net.IPNet{},
 			Protocol:       hcloud.FirewallRuleProtocolTCP,
 			Port:           hcloud.Ptr("80"),
@@ -43,7 +43,7 @@ func TestDeleteRule(t *testing.T) {
 		WaitForActions(gomock.Any(), gomock.Any(), []*hcloud.Action{{ID: 123}, {ID: 321}}).
 		Return(nil)
 
-	out, errOut, err := fx.Run(cmd, []string{"--direction", "in", "--protocol", "tcp", "--source-ips", "0.0.0.0/0", "--port", "80", "--description", "http", "test"})
+	out, errOut, err := fx.Run(cmd, []string{"--direction", "in", "--protocol", "tcp", "--source-ips", "0.0.0.0/0,127.0.0.1/32", "--port", "80", "--description", "http", "test"})
 
 	expOut := "Firewall Rules for Firewall 123 updated\n"