diff --git a/cloud/linode/cloud.go b/cloud/linode/cloud.go index a5184f32..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" @@ -58,6 +59,7 @@ var Options struct { ClusterCIDRIPv4 string NodeCIDRMaskSizeIPv4 int NodeCIDRMaskSizeIPv6 int + NodeBalancerPrefix string } type linodeCloud struct { @@ -69,8 +71,9 @@ type linodeCloud struct { } var ( - instanceCache *instances - ipHolderCharLimit int = 23 + instanceCache *instances + ipHolderCharLimit int = 23 + NodeBalancerPrefixCharLimit int = 19 ) func init() { @@ -193,6 +196,19 @@ func newCloud() (cloudprovider.Interface, error) { return nil, fmt.Errorf("%s", msg) } + 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) + } + + 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) + } + // 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..4924cf7d 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", "") @@ -126,6 +128,57 @@ 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 nodebalancer-prefix is longer than 19 chars", func(t *testing.T) { + prefix := Options.NodeBalancerPrefix + rtEnabled := Options.EnableRouteController + Options.EnableRouteController = false + Options.LoadBalancerType = "nodebalancer" + Options.NodeBalancerPrefix = strings.Repeat("a", 21) + defer func() { + Options.NodeBalancerPrefix = prefix + Options.LoadBalancerType = "" + Options.EnableRouteController = rtEnabled + }() + _, err := newCloud() + t.Log(err) + 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.NodeBalancerPrefix = "" + defer func() { + Options.NodeBalancerPrefix = prefix + Options.LoadBalancerType = "" + Options.EnableRouteController = rtEnabled + }() + _, err := newCloud() + t.Log(err) + require.Error(t, err, "expected error if nodebalancer-prefix is empty") + require.ErrorContains(t, err, "nodebalancer-prefix must be no empty") + }) + + t.Run("should fail if not validated nodebalancer-prefix", 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") + }) } func Test_linodeCloud_LoadBalancer(t *testing.T) { diff --git a/cloud/linode/loadbalancers.go b/cloud/linode/loadbalancers.go index 662ccb53..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("ccm-%s", 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/deploy/chart/templates/daemonset.yaml b/deploy/chart/templates/daemonset.yaml index 240dbfa1..5e996543 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.nodeBalancerPrefix }} + - --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..69d6259f 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 nodeBalancer 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 diff --git a/docs/configuration/environment.md b/docs/configuration/environment.md index 928941d7..0ce99c30 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 NoadBalancers. | ## Configuration Methods diff --git a/main.go b/main.go index 3576d776..508f2634 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.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) {