From c372cecc7708ccc02f04bb0895a4b7c2c57b125d Mon Sep 17 00:00:00 2001 From: Adam Ginna Date: Mon, 14 Jul 2025 14:17:49 +0200 Subject: [PATCH 01/13] [feat]: Add custom prefix for load balancer --- cloud/linode/cloud.go | 1 + main.go | 1 + 2 files changed, 2 insertions(+) diff --git a/cloud/linode/cloud.go b/cloud/linode/cloud.go index a5184f32..4314b771 100644 --- a/cloud/linode/cloud.go +++ b/cloud/linode/cloud.go @@ -58,6 +58,7 @@ var Options struct { ClusterCIDRIPv4 string NodeCIDRMaskSizeIPv4 int NodeCIDRMaskSizeIPv6 int + LoadBalancerPrefix string } type linodeCloud struct { diff --git a/main.go b/main.go index 3576d776..d82ac2dd 100644 --- a/main.go +++ b/main.go @@ -98,6 +98,7 @@ func main() { command.Flags().IntVar(&linode.Options.NodeBalancerBackendIPv4SubnetID, "nodebalancer-backend-ipv4-subnet-id", 0, "ipv4 subnet id to use for NodeBalancer backends") command.Flags().StringVar(&linode.Options.NodeBalancerBackendIPv4SubnetName, "nodebalancer-backend-ipv4-subnet-name", "", "ipv4 subnet name to use for NodeBalancer backends") command.Flags().BoolVar(&linode.Options.DisableNodeBalancerVPCBackends, "disable-nodebalancer-vpc-backends", false, "disables nodebalancer backends in VPCs (when enabled, nodebalancers will only have private IPs as backends for backward compatibility)") + command.Flags().StringVar(&linode.Options.LoadBalancerPrefix, "load-balancer-prefix", "ccm-", "Name prefix for LoadBalancers") // Set static flags command.Flags().VisitAll(func(fl *pflag.Flag) { From 65ef7f09638eb946774da4381c21101ce8186222 Mon Sep 17 00:00:00 2001 From: Adam Ginna Date: Wed, 16 Jul 2025 09:05:43 +0200 Subject: [PATCH 02/13] Update function for naming loadbalacer --- cloud/linode/loadbalancers.go | 2 +- main.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cloud/linode/loadbalancers.go b/cloud/linode/loadbalancers.go index 662ccb53..fcc34f67 100644 --- a/cloud/linode/loadbalancers.go +++ b/cloud/linode/loadbalancers.go @@ -227,7 +227,7 @@ func (l *loadbalancers) cleanupOldNodeBalancer(ctx context.Context, service *v1. // GetLoadBalancer will not modify service. func (l *loadbalancers) GetLoadBalancerName(_ context.Context, _ string, _ *v1.Service) string { unixNano := strconv.FormatInt(time.Now().UnixNano(), 16) - return fmt.Sprintf("ccm-%s", unixNano[len(unixNano)-12:]) + return fmt.Sprintf("%s%s", Options.LoadBalancerPrefix, unixNano[len(unixNano)-12:]) } // GetLoadBalancer returns the *v1.LoadBalancerStatus of service. diff --git a/main.go b/main.go index d82ac2dd..4ec2086f 100644 --- a/main.go +++ b/main.go @@ -98,7 +98,7 @@ func main() { command.Flags().IntVar(&linode.Options.NodeBalancerBackendIPv4SubnetID, "nodebalancer-backend-ipv4-subnet-id", 0, "ipv4 subnet id to use for NodeBalancer backends") command.Flags().StringVar(&linode.Options.NodeBalancerBackendIPv4SubnetName, "nodebalancer-backend-ipv4-subnet-name", "", "ipv4 subnet name to use for NodeBalancer backends") command.Flags().BoolVar(&linode.Options.DisableNodeBalancerVPCBackends, "disable-nodebalancer-vpc-backends", false, "disables nodebalancer backends in VPCs (when enabled, nodebalancers will only have private IPs as backends for backward compatibility)") - command.Flags().StringVar(&linode.Options.LoadBalancerPrefix, "load-balancer-prefix", "ccm-", "Name prefix for LoadBalancers") + command.Flags().StringVar(&linode.Options.LoadBalancerPrefix, "load-balancer-prefix", "ccm-", "Name prefix for LoadBalancers. (Max 20 char.)") // Set static flags command.Flags().VisitAll(func(fl *pflag.Flag) { From f958bc3c2bb4957cbb8e93a90d2f09b2877f568a Mon Sep 17 00:00:00 2001 From: Adam Ginna Date: Wed, 16 Jul 2025 11:48:03 +0200 Subject: [PATCH 03/13] Add limit error for prefix --- cloud/linode/cloud.go | 7 +++++++ cloud/linode/cloud_test.go | 10 ++++++++++ main.go | 2 +- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/cloud/linode/cloud.go b/cloud/linode/cloud.go index 4314b771..f6e4a707 100644 --- a/cloud/linode/cloud.go +++ b/cloud/linode/cloud.go @@ -72,6 +72,7 @@ type linodeCloud struct { var ( instanceCache *instances ipHolderCharLimit int = 23 + LoadBalancerPrefixCharLimit int = 20 ) func init() { @@ -194,6 +195,12 @@ func newCloud() (cloudprovider.Interface, error) { return nil, fmt.Errorf("%s", msg) } + if len(Options.LoadBalancerPrefix) > LoadBalancerPrefixCharLimit { + msg := fmt.Sprintf("load-balancer-prefix must be %d characters or less: %s is %d characters\n", LoadBalancerPrefixCharLimit, Options.LoadBalancerPrefix, len(Options.LoadBalancerPrefix)) + klog.Error(msg) + return nil, fmt.Errorf("%s", msg) + } + // create struct that satisfies cloudprovider.Interface lcloud := &linodeCloud{ client: linodeClient, diff --git a/cloud/linode/cloud_test.go b/cloud/linode/cloud_test.go index 689954b0..beee046f 100644 --- a/cloud/linode/cloud_test.go +++ b/cloud/linode/cloud_test.go @@ -126,6 +126,16 @@ func TestNewCloud(t *testing.T) { _, err := newCloud() assert.Error(t, err, "expected error if ipholdersuffix is longer than 23 chars") }) + + t.Run("should fail if load-balancer-prefix is longer than 20 chars", func(t *testing.T) { + prefix := Options.LoadBalancerPrefix + Options.LoadBalancerPrefix = strings.Repeat("a", 21) + defer func() { + Options.LoadBalancerPrefix = prefix + }() + _, err := newCloud() + assert.Error(t, err, "expected error if load-balancer-prefix is longer than 20 chars") + }) } func Test_linodeCloud_LoadBalancer(t *testing.T) { diff --git a/main.go b/main.go index 4ec2086f..b8cd41a3 100644 --- a/main.go +++ b/main.go @@ -98,7 +98,7 @@ func main() { command.Flags().IntVar(&linode.Options.NodeBalancerBackendIPv4SubnetID, "nodebalancer-backend-ipv4-subnet-id", 0, "ipv4 subnet id to use for NodeBalancer backends") command.Flags().StringVar(&linode.Options.NodeBalancerBackendIPv4SubnetName, "nodebalancer-backend-ipv4-subnet-name", "", "ipv4 subnet name to use for NodeBalancer backends") command.Flags().BoolVar(&linode.Options.DisableNodeBalancerVPCBackends, "disable-nodebalancer-vpc-backends", false, "disables nodebalancer backends in VPCs (when enabled, nodebalancers will only have private IPs as backends for backward compatibility)") - command.Flags().StringVar(&linode.Options.LoadBalancerPrefix, "load-balancer-prefix", "ccm-", "Name prefix for LoadBalancers. (Max 20 char.)") + command.Flags().StringVar(&linode.Options.LoadBalancerPrefix, "load-balancer-prefix", "ccm-", "Name prefix for LoadBalancers. (max. 20 char.)") // Set static flags command.Flags().VisitAll(func(fl *pflag.Flag) { From 891cd2e2b5e1ccb529239bd8b8efee282f844bea Mon Sep 17 00:00:00 2001 From: Adam Ginna Date: Wed, 16 Jul 2025 13:40:07 +0200 Subject: [PATCH 04/13] Add dash after prefix --- cloud/linode/cloud.go | 2 +- cloud/linode/cloud_test.go | 4 ++-- cloud/linode/loadbalancers.go | 2 +- main.go | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cloud/linode/cloud.go b/cloud/linode/cloud.go index f6e4a707..54103caa 100644 --- a/cloud/linode/cloud.go +++ b/cloud/linode/cloud.go @@ -72,7 +72,7 @@ type linodeCloud struct { var ( instanceCache *instances ipHolderCharLimit int = 23 - LoadBalancerPrefixCharLimit int = 20 + LoadBalancerPrefixCharLimit int = 19 ) func init() { diff --git a/cloud/linode/cloud_test.go b/cloud/linode/cloud_test.go index beee046f..2945d3e5 100644 --- a/cloud/linode/cloud_test.go +++ b/cloud/linode/cloud_test.go @@ -127,14 +127,14 @@ func TestNewCloud(t *testing.T) { assert.Error(t, err, "expected error if ipholdersuffix is longer than 23 chars") }) - t.Run("should fail if load-balancer-prefix is longer than 20 chars", func(t *testing.T) { + t.Run("should fail if load-balancer-prefix is longer than 19 chars", func(t *testing.T) { prefix := Options.LoadBalancerPrefix Options.LoadBalancerPrefix = strings.Repeat("a", 21) defer func() { Options.LoadBalancerPrefix = prefix }() _, err := newCloud() - assert.Error(t, err, "expected error if load-balancer-prefix is longer than 20 chars") + assert.Error(t, err, "expected error if load-balancer-prefix is longer than 19 chars") }) } diff --git a/cloud/linode/loadbalancers.go b/cloud/linode/loadbalancers.go index fcc34f67..7c8f2635 100644 --- a/cloud/linode/loadbalancers.go +++ b/cloud/linode/loadbalancers.go @@ -227,7 +227,7 @@ func (l *loadbalancers) cleanupOldNodeBalancer(ctx context.Context, service *v1. // GetLoadBalancer will not modify service. func (l *loadbalancers) GetLoadBalancerName(_ context.Context, _ string, _ *v1.Service) string { unixNano := strconv.FormatInt(time.Now().UnixNano(), 16) - return fmt.Sprintf("%s%s", Options.LoadBalancerPrefix, unixNano[len(unixNano)-12:]) + return fmt.Sprintf("%s-%s", Options.LoadBalancerPrefix, unixNano[len(unixNano)-12:]) } // GetLoadBalancer returns the *v1.LoadBalancerStatus of service. diff --git a/main.go b/main.go index b8cd41a3..c88280fd 100644 --- a/main.go +++ b/main.go @@ -98,7 +98,7 @@ func main() { command.Flags().IntVar(&linode.Options.NodeBalancerBackendIPv4SubnetID, "nodebalancer-backend-ipv4-subnet-id", 0, "ipv4 subnet id to use for NodeBalancer backends") command.Flags().StringVar(&linode.Options.NodeBalancerBackendIPv4SubnetName, "nodebalancer-backend-ipv4-subnet-name", "", "ipv4 subnet name to use for NodeBalancer backends") command.Flags().BoolVar(&linode.Options.DisableNodeBalancerVPCBackends, "disable-nodebalancer-vpc-backends", false, "disables nodebalancer backends in VPCs (when enabled, nodebalancers will only have private IPs as backends for backward compatibility)") - command.Flags().StringVar(&linode.Options.LoadBalancerPrefix, "load-balancer-prefix", "ccm-", "Name prefix for LoadBalancers. (max. 20 char.)") + command.Flags().StringVar(&linode.Options.LoadBalancerPrefix, "load-balancer-prefix", "ccm", "Name prefix for LoadBalancers. (max. 19 char.)") // Set static flags command.Flags().VisitAll(func(fl *pflag.Flag) { From c1f7c72eca8020face6aca45b4ed676dc9c06b6b Mon Sep 17 00:00:00 2001 From: Adam Ginna Date: Thu, 17 Jul 2025 10:18:20 +0200 Subject: [PATCH 05/13] Change name of flag to nodebalacer-prefix --- cloud/linode/cloud.go | 8 ++++---- cloud/linode/cloud_test.go | 10 +++++----- cloud/linode/loadbalancers.go | 2 +- docs/configuration/environment.md | 1 + main.go | 2 +- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/cloud/linode/cloud.go b/cloud/linode/cloud.go index 54103caa..474ee82a 100644 --- a/cloud/linode/cloud.go +++ b/cloud/linode/cloud.go @@ -58,7 +58,7 @@ var Options struct { ClusterCIDRIPv4 string NodeCIDRMaskSizeIPv4 int NodeCIDRMaskSizeIPv6 int - LoadBalancerPrefix string + NodeBalancerPrefix string } type linodeCloud struct { @@ -72,7 +72,7 @@ type linodeCloud struct { var ( instanceCache *instances ipHolderCharLimit int = 23 - LoadBalancerPrefixCharLimit int = 19 + NodeBalancerPrefixCharLimit int = 19 ) func init() { @@ -195,8 +195,8 @@ func newCloud() (cloudprovider.Interface, error) { return nil, fmt.Errorf("%s", msg) } - if len(Options.LoadBalancerPrefix) > LoadBalancerPrefixCharLimit { - msg := fmt.Sprintf("load-balancer-prefix must be %d characters or less: %s is %d characters\n", LoadBalancerPrefixCharLimit, Options.LoadBalancerPrefix, len(Options.LoadBalancerPrefix)) + if len(Options.NodeBalancerPrefix) > NodeBalancerPrefixCharLimit { + msg := fmt.Sprintf("nodebalancer-prefix must be %d characters or less: %s is %d characters\n", NodeBalancerPrefixCharLimit, Options.NodeBalancerPrefix, len(Options.NodeBalancerPrefix)) klog.Error(msg) return nil, fmt.Errorf("%s", msg) } diff --git a/cloud/linode/cloud_test.go b/cloud/linode/cloud_test.go index 2945d3e5..b791eac5 100644 --- a/cloud/linode/cloud_test.go +++ b/cloud/linode/cloud_test.go @@ -127,14 +127,14 @@ func TestNewCloud(t *testing.T) { assert.Error(t, err, "expected error if ipholdersuffix is longer than 23 chars") }) - t.Run("should fail if load-balancer-prefix is longer than 19 chars", func(t *testing.T) { - prefix := Options.LoadBalancerPrefix - Options.LoadBalancerPrefix = strings.Repeat("a", 21) + t.Run("should fail if nodebalancer-prefix is longer than 19 chars", func(t *testing.T) { + prefix := Options.NodeBalancerPrefix + Options.NodeBalancerPrefix = strings.Repeat("a", 21) defer func() { - Options.LoadBalancerPrefix = prefix + Options.NodeBalancerPrefix = prefix }() _, err := newCloud() - assert.Error(t, err, "expected error if load-balancer-prefix is longer than 19 chars") + assert.Error(t, err, "expected error if nodebalancer-prefix is longer than 19 chars") }) } diff --git a/cloud/linode/loadbalancers.go b/cloud/linode/loadbalancers.go index 7c8f2635..c37e0edb 100644 --- a/cloud/linode/loadbalancers.go +++ b/cloud/linode/loadbalancers.go @@ -227,7 +227,7 @@ func (l *loadbalancers) cleanupOldNodeBalancer(ctx context.Context, service *v1. // GetLoadBalancer will not modify service. func (l *loadbalancers) GetLoadBalancerName(_ context.Context, _ string, _ *v1.Service) string { unixNano := strconv.FormatInt(time.Now().UnixNano(), 16) - return fmt.Sprintf("%s-%s", Options.LoadBalancerPrefix, unixNano[len(unixNano)-12:]) + return fmt.Sprintf("%s-%s", Options.NodeBalancerPrefix, unixNano[len(unixNano)-12:]) } // GetLoadBalancer returns the *v1.LoadBalancerStatus of service. diff --git a/docs/configuration/environment.md b/docs/configuration/environment.md index 928941d7..e6716ea7 100644 --- a/docs/configuration/environment.md +++ b/docs/configuration/environment.md @@ -53,6 +53,7 @@ The CCM supports the following flags: | `--enable-ipv6-for-loadbalancers` | `false` | Set both IPv4 and IPv6 addresses for all LoadBalancer services (when disabled, only IPv4 is used). This can also be configured per-service using the `service.beta.kubernetes.io/linode-loadbalancer-enable-ipv6-ingress` annotation. | | `--node-cidr-mask-size-ipv4` | `24` | ipv4 cidr mask size for pod cidrs allocated to nodes | | `--node-cidr-mask-size-ipv6` | `64` | ipv6 cidr mask size for pod cidrs allocated to nodes | +| `--nodebalancer-prefix` | `ccm` | Name prefix for LoadBalancers. (max. 19 char.) | ## Configuration Methods diff --git a/main.go b/main.go index c88280fd..75026ca7 100644 --- a/main.go +++ b/main.go @@ -98,7 +98,7 @@ func main() { command.Flags().IntVar(&linode.Options.NodeBalancerBackendIPv4SubnetID, "nodebalancer-backend-ipv4-subnet-id", 0, "ipv4 subnet id to use for NodeBalancer backends") command.Flags().StringVar(&linode.Options.NodeBalancerBackendIPv4SubnetName, "nodebalancer-backend-ipv4-subnet-name", "", "ipv4 subnet name to use for NodeBalancer backends") command.Flags().BoolVar(&linode.Options.DisableNodeBalancerVPCBackends, "disable-nodebalancer-vpc-backends", false, "disables nodebalancer backends in VPCs (when enabled, nodebalancers will only have private IPs as backends for backward compatibility)") - command.Flags().StringVar(&linode.Options.LoadBalancerPrefix, "load-balancer-prefix", "ccm", "Name prefix for LoadBalancers. (max. 19 char.)") + command.Flags().StringVar(&linode.Options.NodeBalancerPrefix, "nodebalancer-prefix", "ccm", "Name prefix for LoadBalancers. (max. 19 char.)") // Set static flags command.Flags().VisitAll(func(fl *pflag.Flag) { From c9af4f3b9f221f9fa1832bbedb70877aaf28c780 Mon Sep 17 00:00:00 2001 From: Adam Ginna Date: Thu, 17 Jul 2025 10:28:57 +0200 Subject: [PATCH 06/13] Add flag to helm chart --- deploy/chart/templates/daemonset.yaml | 3 +++ deploy/chart/values.yaml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/deploy/chart/templates/daemonset.yaml b/deploy/chart/templates/daemonset.yaml index 240dbfa1..8af7b67f 100644 --- a/deploy/chart/templates/daemonset.yaml +++ b/deploy/chart/templates/daemonset.yaml @@ -156,6 +156,9 @@ spec: {{- if .Values.nodeBalancerBackendIPv4Subnet }} - --nodebalancer-backend-ipv4-subnet={{ .Values.nodeBalancerBackendIPv4Subnet }} {{- end }} + {{- if .Values.nodeBalancerBackendIPv4Subnet }} + - --nodebalancer-prefix={{ .Values.nodeBalancerPrefix }} + {{- end }} {{- if .Values.extraArgs }} {{- toYaml .Values.extraArgs | nindent 12 }} {{- end }} diff --git a/deploy/chart/values.yaml b/deploy/chart/values.yaml index 90e3aa28..88c378ba 100644 --- a/deploy/chart/values.yaml +++ b/deploy/chart/values.yaml @@ -116,6 +116,9 @@ tolerations: # nodeBalancerBackendIPv4SubnetName is the subnet name to use for the backend ips of the NodeBalancer # nodeBalancerBackendIPv4SubnetName: "" +# nodeBalancerPrefix is used to add prefix for nodeBalacer name. Default is "ccm" +# nodeBalancerPrefix: "" + # This section adds the ability to pass environment variables to adjust CCM defaults # https://github.com/linode/linode-cloud-controller-manager/blob/master/cloud/linode/loadbalancers.go # LINODE_HOSTNAME_ONLY_INGRESS type bool is supported From 429a1b527cc77bf553510e454e1d5cd6291b9870 Mon Sep 17 00:00:00 2001 From: Adam Ginna Date: Fri, 18 Jul 2025 15:58:45 +0200 Subject: [PATCH 07/13] Remove hardocoded string --- deploy/chart/templates/daemonset.yaml | 2 +- deploy/chart/values.yaml | 2 +- docs/configuration/environment.md | 2 +- main.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/deploy/chart/templates/daemonset.yaml b/deploy/chart/templates/daemonset.yaml index 8af7b67f..5e996543 100644 --- a/deploy/chart/templates/daemonset.yaml +++ b/deploy/chart/templates/daemonset.yaml @@ -156,7 +156,7 @@ spec: {{- if .Values.nodeBalancerBackendIPv4Subnet }} - --nodebalancer-backend-ipv4-subnet={{ .Values.nodeBalancerBackendIPv4Subnet }} {{- end }} - {{- if .Values.nodeBalancerBackendIPv4Subnet }} + {{- if .Values.nodeBalancerPrefix }} - --nodebalancer-prefix={{ .Values.nodeBalancerPrefix }} {{- end }} {{- if .Values.extraArgs }} diff --git a/deploy/chart/values.yaml b/deploy/chart/values.yaml index 88c378ba..69d6259f 100644 --- a/deploy/chart/values.yaml +++ b/deploy/chart/values.yaml @@ -116,7 +116,7 @@ tolerations: # nodeBalancerBackendIPv4SubnetName is the subnet name to use for the backend ips of the NodeBalancer # nodeBalancerBackendIPv4SubnetName: "" -# nodeBalancerPrefix is used to add prefix for nodeBalacer name. Default is "ccm" +# nodeBalancerPrefix is used to add prefix for nodeBalancer name. Default is "ccm" # nodeBalancerPrefix: "" # This section adds the ability to pass environment variables to adjust CCM defaults diff --git a/docs/configuration/environment.md b/docs/configuration/environment.md index e6716ea7..0ce99c30 100644 --- a/docs/configuration/environment.md +++ b/docs/configuration/environment.md @@ -53,7 +53,7 @@ The CCM supports the following flags: | `--enable-ipv6-for-loadbalancers` | `false` | Set both IPv4 and IPv6 addresses for all LoadBalancer services (when disabled, only IPv4 is used). This can also be configured per-service using the `service.beta.kubernetes.io/linode-loadbalancer-enable-ipv6-ingress` annotation. | | `--node-cidr-mask-size-ipv4` | `24` | ipv4 cidr mask size for pod cidrs allocated to nodes | | `--node-cidr-mask-size-ipv6` | `64` | ipv6 cidr mask size for pod cidrs allocated to nodes | -| `--nodebalancer-prefix` | `ccm` | Name prefix for LoadBalancers. (max. 19 char.) | +| `--nodebalancer-prefix` | `ccm` | Name prefix for NoadBalancers. | ## Configuration Methods diff --git a/main.go b/main.go index 75026ca7..f93c5546 100644 --- a/main.go +++ b/main.go @@ -98,7 +98,7 @@ func main() { command.Flags().IntVar(&linode.Options.NodeBalancerBackendIPv4SubnetID, "nodebalancer-backend-ipv4-subnet-id", 0, "ipv4 subnet id to use for NodeBalancer backends") command.Flags().StringVar(&linode.Options.NodeBalancerBackendIPv4SubnetName, "nodebalancer-backend-ipv4-subnet-name", "", "ipv4 subnet name to use for NodeBalancer backends") command.Flags().BoolVar(&linode.Options.DisableNodeBalancerVPCBackends, "disable-nodebalancer-vpc-backends", false, "disables nodebalancer backends in VPCs (when enabled, nodebalancers will only have private IPs as backends for backward compatibility)") - command.Flags().StringVar(&linode.Options.NodeBalancerPrefix, "nodebalancer-prefix", "ccm", "Name prefix for LoadBalancers. (max. 19 char.)") + command.Flags().StringVar(&linode.Options.NodeBalancerPrefix, "nodebalancer-prefix", "ccm", fmt.Sprintf("Name prefix for NoadBalancers. (max. %s char.)", linode.NodeBalancerPrefixCharLimit)) // Set static flags command.Flags().VisitAll(func(fl *pflag.Flag) { From 84c8da56fa95a25a6b8ca6959cfa7db5eaaf2ac9 Mon Sep 17 00:00:00 2001 From: Adam Ginna Date: Mon, 21 Jul 2025 09:12:52 +0200 Subject: [PATCH 08/13] Fix tests --- cloud/linode/cloud.go | 6 +++--- cloud/linode/cloud_test.go | 22 +++++++++++++++++++++- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/cloud/linode/cloud.go b/cloud/linode/cloud.go index 474ee82a..d63d622f 100644 --- a/cloud/linode/cloud.go +++ b/cloud/linode/cloud.go @@ -70,8 +70,8 @@ type linodeCloud struct { } var ( - instanceCache *instances - ipHolderCharLimit int = 23 + instanceCache *instances + ipHolderCharLimit int = 23 NodeBalancerPrefixCharLimit int = 19 ) @@ -195,7 +195,7 @@ func newCloud() (cloudprovider.Interface, error) { return nil, fmt.Errorf("%s", msg) } - if len(Options.NodeBalancerPrefix) > NodeBalancerPrefixCharLimit { + if len(Options.NodeBalancerPrefix) > NodeBalancerPrefixCharLimit { msg := fmt.Sprintf("nodebalancer-prefix must be %d characters or less: %s is %d characters\n", NodeBalancerPrefixCharLimit, Options.NodeBalancerPrefix, len(Options.NodeBalancerPrefix)) klog.Error(msg) return nil, fmt.Errorf("%s", msg) diff --git a/cloud/linode/cloud_test.go b/cloud/linode/cloud_test.go index b791eac5..0855b482 100644 --- a/cloud/linode/cloud_test.go +++ b/cloud/linode/cloud_test.go @@ -129,12 +129,32 @@ func TestNewCloud(t *testing.T) { t.Run("should fail if nodebalancer-prefix is longer than 19 chars", func(t *testing.T) { prefix := Options.NodeBalancerPrefix + rtEnabled := Options.EnableRouteController + Options.EnableRouteController = false + Options.LoadBalancerType = "nodebalancer" + Options.VPCNames = "vpc-test1,vpc-test2" + Options.NodeBalancerBackendIPv4SubnetName = "t1" + vpcIDs = map[string]int{"vpc-test1": 1, "vpc-test2": 2, "vpc-test3": 3} + subnetIDs = map[string]int{"t1": 1, "t2": 2, "t3": 3} Options.NodeBalancerPrefix = strings.Repeat("a", 21) defer func() { Options.NodeBalancerPrefix = prefix + Options.LoadBalancerType = "" + Options.EnableRouteController = rtEnabled + Options.VPCNames = "" + Options.NodeBalancerBackendIPv4SubnetID = 0 + Options.NodeBalancerBackendIPv4SubnetName = "" + vpcIDs = map[string]int{} + subnetIDs = map[string]int{} }() _, err := newCloud() - assert.Error(t, err, "expected error if nodebalancer-prefix is longer than 19 chars") + t.Log(err) + if !assert.Error(t, err, "expected error if nodebalancer-prefix is longer than 19 chars") { + t.Errorf("No error when nodebalancer-prefix is longer 19 than") + } + if !assert.ErrorContains(t, err, "nodebalancer-prefix") { + t.Errorf("Error message does not concern nodebalancer-prefix: %s", err) + } }) } From cd5e7ed91d361e2762ff52186067d24de69f5107 Mon Sep 17 00:00:00 2001 From: Adam Ginna Date: Mon, 21 Jul 2025 09:34:13 +0200 Subject: [PATCH 09/13] Update NodeBalancerPrefixCharLimit --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index f93c5546..508f2634 100644 --- a/main.go +++ b/main.go @@ -98,7 +98,7 @@ func main() { command.Flags().IntVar(&linode.Options.NodeBalancerBackendIPv4SubnetID, "nodebalancer-backend-ipv4-subnet-id", 0, "ipv4 subnet id to use for NodeBalancer backends") command.Flags().StringVar(&linode.Options.NodeBalancerBackendIPv4SubnetName, "nodebalancer-backend-ipv4-subnet-name", "", "ipv4 subnet name to use for NodeBalancer backends") command.Flags().BoolVar(&linode.Options.DisableNodeBalancerVPCBackends, "disable-nodebalancer-vpc-backends", false, "disables nodebalancer backends in VPCs (when enabled, nodebalancers will only have private IPs as backends for backward compatibility)") - command.Flags().StringVar(&linode.Options.NodeBalancerPrefix, "nodebalancer-prefix", "ccm", fmt.Sprintf("Name prefix for NoadBalancers. (max. %s char.)", linode.NodeBalancerPrefixCharLimit)) + command.Flags().StringVar(&linode.Options.NodeBalancerPrefix, "nodebalancer-prefix", "ccm", fmt.Sprintf("Name prefix for NoadBalancers. (max. %v char.)", linode.NodeBalancerPrefixCharLimit)) // Set static flags command.Flags().VisitAll(func(fl *pflag.Flag) { From bcaef92034e619a1513b0563373d9768580cceec Mon Sep 17 00:00:00 2001 From: Adam Ginna Date: Mon, 21 Jul 2025 10:24:20 +0200 Subject: [PATCH 10/13] Replace assert to require --- cloud/linode/cloud_test.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/cloud/linode/cloud_test.go b/cloud/linode/cloud_test.go index 0855b482..34ed25b6 100644 --- a/cloud/linode/cloud_test.go +++ b/cloud/linode/cloud_test.go @@ -149,12 +149,8 @@ func TestNewCloud(t *testing.T) { }() _, err := newCloud() t.Log(err) - if !assert.Error(t, err, "expected error if nodebalancer-prefix is longer than 19 chars") { - t.Errorf("No error when nodebalancer-prefix is longer 19 than") - } - if !assert.ErrorContains(t, err, "nodebalancer-prefix") { - t.Errorf("Error message does not concern nodebalancer-prefix: %s", err) - } + require.Error(t, err, "expected error if nodebalancer-prefix is longer than 19 chars") + require.ErrorContains(t, err, "nodebalancer-prefix") }) } From d24b5f3dd3345c7a1e181ef93f68a94ab5c21d17 Mon Sep 17 00:00:00 2001 From: Adam Ginna Date: Mon, 21 Jul 2025 10:57:06 +0200 Subject: [PATCH 11/13] Cover empty string case --- cloud/linode/cloud.go | 6 ++++++ cloud/linode/cloud_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/cloud/linode/cloud.go b/cloud/linode/cloud.go index d63d622f..5c9275f7 100644 --- a/cloud/linode/cloud.go +++ b/cloud/linode/cloud.go @@ -201,6 +201,12 @@ func newCloud() (cloudprovider.Interface, error) { return nil, fmt.Errorf("%s", msg) } + if len(Options.NodeBalancerPrefix) == 0 { + msg := "nodebalancer-prefix cannot be empty string" + klog.Error(msg) + return nil, fmt.Errorf("%s", msg) + } + // create struct that satisfies cloudprovider.Interface lcloud := &linodeCloud{ client: linodeClient, diff --git a/cloud/linode/cloud_test.go b/cloud/linode/cloud_test.go index 34ed25b6..07dd0656 100644 --- a/cloud/linode/cloud_test.go +++ b/cloud/linode/cloud_test.go @@ -152,6 +152,32 @@ func TestNewCloud(t *testing.T) { require.Error(t, err, "expected error if nodebalancer-prefix is longer than 19 chars") require.ErrorContains(t, err, "nodebalancer-prefix") }) + + t.Run("should fail if nodebalancer-prefix is empty", func(t *testing.T) { + prefix := Options.NodeBalancerPrefix + rtEnabled := Options.EnableRouteController + Options.EnableRouteController = false + Options.LoadBalancerType = "nodebalancer" + Options.VPCNames = "vpc-test1,vpc-test2" + Options.NodeBalancerBackendIPv4SubnetName = "t1" + vpcIDs = map[string]int{"vpc-test1": 1, "vpc-test2": 2, "vpc-test3": 3} + subnetIDs = map[string]int{"t1": 1, "t2": 2, "t3": 3} + Options.NodeBalancerPrefix = strings.Repeat("a", 21) + defer func() { + Options.NodeBalancerPrefix = prefix + Options.LoadBalancerType = "" + Options.EnableRouteController = rtEnabled + Options.VPCNames = "" + Options.NodeBalancerBackendIPv4SubnetID = 0 + Options.NodeBalancerBackendIPv4SubnetName = "" + vpcIDs = map[string]int{} + subnetIDs = map[string]int{} + }() + _, err := newCloud() + t.Log(err) + require.Error(t, err, "expected error if nodebalancer-prefix is empty") + require.ErrorContains(t, err, "nodebalancer-prefix cannot be empty") + }) } func Test_linodeCloud_LoadBalancer(t *testing.T) { From c88ab8553fb6b13955f97a3063cc7f3e1c35b9e8 Mon Sep 17 00:00:00 2001 From: Adam Ginna Date: Mon, 21 Jul 2025 11:54:33 +0200 Subject: [PATCH 12/13] Prefix validation --- cloud/linode/cloud.go | 6 ++++-- cloud/linode/cloud_test.go | 41 +++++++++++++++++++------------------- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/cloud/linode/cloud.go b/cloud/linode/cloud.go index 5c9275f7..ebe4165c 100644 --- a/cloud/linode/cloud.go +++ b/cloud/linode/cloud.go @@ -8,6 +8,7 @@ import ( "os" "strconv" "time" + "regexp" "github.com/spf13/pflag" "golang.org/x/exp/slices" @@ -201,8 +202,9 @@ func newCloud() (cloudprovider.Interface, error) { return nil, fmt.Errorf("%s", msg) } - if len(Options.NodeBalancerPrefix) == 0 { - msg := "nodebalancer-prefix cannot be empty string" + validPrefix := regexp.MustCompile(`^[a-zA-Z0-9_-]+$`) + if !validPrefix.MatchString(Options.NodeBalancerPrefix) { + msg := fmt.Sprintf("nodebalancer-prefix must be no empty and use only letters, numbers, underscores, and dashes: %s\n", Options.NodeBalancerPrefix) klog.Error(msg) return nil, fmt.Errorf("%s", msg) } diff --git a/cloud/linode/cloud_test.go b/cloud/linode/cloud_test.go index 07dd0656..74b1fd41 100644 --- a/cloud/linode/cloud_test.go +++ b/cloud/linode/cloud_test.go @@ -20,6 +20,7 @@ func TestNewCloudRouteControllerDisabled(t *testing.T) { t.Setenv("LINODE_API_TOKEN", "dummyapitoken") t.Setenv("LINODE_REGION", "us-east") t.Setenv("LINODE_REQUEST_TIMEOUT_SECONDS", "10") + Options.NodeBalancerPrefix = "ccm" t.Run("should not fail if vpc is empty and routecontroller is disabled", func(t *testing.T) { Options.VPCName = "" @@ -45,6 +46,7 @@ func TestNewCloud(t *testing.T) { t.Setenv("LINODE_REQUEST_TIMEOUT_SECONDS", "10") t.Setenv("LINODE_ROUTES_CACHE_TTL_SECONDS", "60") Options.LinodeGoDebug = true + Options.NodeBalancerPrefix = "ccm" t.Run("should fail if api token is empty", func(t *testing.T) { t.Setenv("LINODE_API_TOKEN", "") @@ -132,20 +134,11 @@ func TestNewCloud(t *testing.T) { rtEnabled := Options.EnableRouteController Options.EnableRouteController = false Options.LoadBalancerType = "nodebalancer" - Options.VPCNames = "vpc-test1,vpc-test2" - Options.NodeBalancerBackendIPv4SubnetName = "t1" - vpcIDs = map[string]int{"vpc-test1": 1, "vpc-test2": 2, "vpc-test3": 3} - subnetIDs = map[string]int{"t1": 1, "t2": 2, "t3": 3} Options.NodeBalancerPrefix = strings.Repeat("a", 21) defer func() { Options.NodeBalancerPrefix = prefix Options.LoadBalancerType = "" Options.EnableRouteController = rtEnabled - Options.VPCNames = "" - Options.NodeBalancerBackendIPv4SubnetID = 0 - Options.NodeBalancerBackendIPv4SubnetName = "" - vpcIDs = map[string]int{} - subnetIDs = map[string]int{} }() _, err := newCloud() t.Log(err) @@ -158,25 +151,33 @@ func TestNewCloud(t *testing.T) { rtEnabled := Options.EnableRouteController Options.EnableRouteController = false Options.LoadBalancerType = "nodebalancer" - Options.VPCNames = "vpc-test1,vpc-test2" - Options.NodeBalancerBackendIPv4SubnetName = "t1" - vpcIDs = map[string]int{"vpc-test1": 1, "vpc-test2": 2, "vpc-test3": 3} - subnetIDs = map[string]int{"t1": 1, "t2": 2, "t3": 3} - Options.NodeBalancerPrefix = strings.Repeat("a", 21) + Options.NodeBalancerPrefix = "" defer func() { Options.NodeBalancerPrefix = prefix Options.LoadBalancerType = "" Options.EnableRouteController = rtEnabled - Options.VPCNames = "" - Options.NodeBalancerBackendIPv4SubnetID = 0 - Options.NodeBalancerBackendIPv4SubnetName = "" - vpcIDs = map[string]int{} - subnetIDs = map[string]int{} }() _, err := newCloud() t.Log(err) require.Error(t, err, "expected error if nodebalancer-prefix is empty") - require.ErrorContains(t, err, "nodebalancer-prefix cannot be empty") + require.ErrorContains(t, err, "nodebalancer-prefix must be no empty") + }) + + t.Run("should fail if nodebalancer-prefix name validation", func(t *testing.T) { + prefix := Options.NodeBalancerPrefix + rtEnabled := Options.EnableRouteController + Options.EnableRouteController = false + Options.LoadBalancerType = "nodebalancer" + Options.NodeBalancerPrefix = "\\+x" + defer func() { + Options.NodeBalancerPrefix = prefix + Options.LoadBalancerType = "" + Options.EnableRouteController = rtEnabled + }() + _, err := newCloud() + t.Log(err) + require.Error(t, err, "expected error if not validated nodebalancer-prefix") + require.ErrorContains(t, err, "nodebalancer-prefix must be no empty and use only letters, numbers, underscores, and dashes") }) } From e65c24ce97c8e25637f9c4969ca04e16ad74f83f Mon Sep 17 00:00:00 2001 From: Adam Ginna Date: Mon, 21 Jul 2025 12:03:46 +0200 Subject: [PATCH 13/13] Update tests --- cloud/linode/cloud_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloud/linode/cloud_test.go b/cloud/linode/cloud_test.go index 74b1fd41..4924cf7d 100644 --- a/cloud/linode/cloud_test.go +++ b/cloud/linode/cloud_test.go @@ -163,7 +163,7 @@ func TestNewCloud(t *testing.T) { require.ErrorContains(t, err, "nodebalancer-prefix must be no empty") }) - t.Run("should fail if nodebalancer-prefix name validation", func(t *testing.T) { + t.Run("should fail if not validated nodebalancer-prefix", func(t *testing.T) { prefix := Options.NodeBalancerPrefix rtEnabled := Options.EnableRouteController Options.EnableRouteController = false