Repository: kubernetes-sigs/cluster-api-provider-openstack

Issue:
Makefile: gotestsum download ignores detected OS
/kind bug

**What steps did you take and what happened:**

The `hack/tools/Makefile` correctly detects macOS via `OS := $(shell go env GOOS)` but then hardcodes `linux` in the gotestsum download URL, causing binary incompatibility.

```makefile
# Line ~45 in hack/tools/Makefile
curl -L https://github.com/gotestyourself/gotestsum/releases/download/v$(GOTESTSUM_VERSION)/gotestsum_$(GOTESTSUM_VERSION)_linux_$(ARCH).tar.gz
```

Downloads Linux binary on macOS resulting in `cannot execute binary file: Exec format error`

**What did you expect to happen:**


**Anything else you would like to add:**
[Miscellaneous information that will assist in solving the issue.]


**Environment:**

- Cluster API Provider OpenStack version (Or `git rev-parse HEAD` if manually built):
- Cluster-API version:
- OpenStack version:
- Minikube/KIND version:
- Kubernetes version (use `kubectl version`):
- OS (e.g. from `/etc/os-release`):


Hints from issue comments:

Solution patch:
diff --git a/hack/tools/Makefile b/hack/tools/Makefile
index a6089774..54b9c50b 100644
--- a/hack/tools/Makefile
+++ b/hack/tools/Makefile
@@ -81,7 +81,7 @@ $(SETUP_ENVTEST): go.mod go.sum | $(BIN_DIR)
 
 GOTESTSUM := $(BIN_DIR)/gotestsum
 $(GOTESTSUM): | $(BIN_DIR)
-	curl -L https://github.com/gotestyourself/gotestsum/releases/download/v$(GOTESTSUM_VERSION)/gotestsum_$(GOTESTSUM_VERSION)_linux_$(ARCH).tar.gz | \
+	curl -L https://github.com/gotestyourself/gotestsum/releases/download/v$(GOTESTSUM_VERSION)/gotestsum_$(GOTESTSUM_VERSION)_$(OS)_$(ARCH).tar.gz | \
 		tar -C $(BIN_DIR) -zxvf - gotestsum
 
 GH_SHARE := $(SHARE_DIR)/gh
diff --git a/pkg/cloud/services/networking/securitygroups_rules.go b/pkg/cloud/services/networking/securitygroups_rules.go
index d74ff421..110a89d4 100644
--- a/pkg/cloud/services/networking/securitygroups_rules.go
+++ b/pkg/cloud/services/networking/securitygroups_rules.go
@@ -258,20 +258,19 @@ func getSGWorkerAllowAll(remoteGroupIDSelf, secControlPlaneGroupID string) []res
 // Permit ports that defined in openStackCluster.Spec.APIServerLoadBalancer.AdditionalPorts.
 func getSGControlPlaneAdditionalPorts(ports []int) []resolvedSecurityGroupRuleSpec {
 	controlPlaneRules := []resolvedSecurityGroupRuleSpec{}
-
-	r := []resolvedSecurityGroupRuleSpec{
-		{
-			Description: "Additional ports",
-			Direction:   "ingress",
-			EtherType:   "IPv4",
-			Protocol:    "tcp",
-		},
-	}
+	// Preallocate r with len(ports)
+	r := make([]resolvedSecurityGroupRuleSpec, len(ports))
 	for i, p := range ports {
-		r[i].PortRangeMin = p
-		r[i].PortRangeMax = p
-		controlPlaneRules = append(controlPlaneRules, r...)
+		r[i] = resolvedSecurityGroupRuleSpec{
+			Description:  "Additional port",
+			Direction:    "ingress",
+			EtherType:    "IPv4",
+			Protocol:     "tcp",
+			PortRangeMin: p,
+			PortRangeMax: p,
+		}
 	}
+	controlPlaneRules = append(controlPlaneRules, r...)
 	return controlPlaneRules
 }
 



Test patch:
diff --git a/pkg/cloud/services/networking/securitygroups_test.go b/pkg/cloud/services/networking/securitygroups_test.go
index d3042619..6dc01b49 100644
--- a/pkg/cloud/services/networking/securitygroups_test.go
+++ b/pkg/cloud/services/networking/securitygroups_test.go
@@ -681,3 +681,62 @@ func TestService_ReconcileSecurityGroups(t *testing.T) {
 		})
 	}
 }
+
+func TestGetSGControlPlaneAdditionalPorts(t *testing.T) {
+	tests := []struct {
+		name  string
+		ports []int
+		want  []resolvedSecurityGroupRuleSpec
+	}{
+		{
+			name:  "no ports",
+			ports: []int{},
+			want:  []resolvedSecurityGroupRuleSpec{},
+		},
+		{
+			name:  "single port",
+			ports: []int{6443},
+			want: []resolvedSecurityGroupRuleSpec{
+				{
+					Description:  "Additional port",
+					Direction:    "ingress",
+					EtherType:    "IPv4",
+					Protocol:     "tcp",
+					PortRangeMin: 6443,
+					PortRangeMax: 6443,
+				},
+			},
+		},
+		{
+			name:  "multiple ports",
+			ports: []int{80, 443},
+			want: []resolvedSecurityGroupRuleSpec{
+				{
+					Description:  "Additional port",
+					Direction:    "ingress",
+					EtherType:    "IPv4",
+					Protocol:     "tcp",
+					PortRangeMin: 80,
+					PortRangeMax: 80,
+				},
+				{
+					Description:  "Additional port",
+					Direction:    "ingress",
+					EtherType:    "IPv4",
+					Protocol:     "tcp",
+					PortRangeMin: 443,
+					PortRangeMax: 443,
+				},
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := getSGControlPlaneAdditionalPorts(tt.ports)
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("getSGControlPlaneAdditionalPorts() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}