From 1d5e0f155a7fe2763cac335d94f1134372457f77 Mon Sep 17 00:00:00 2001
From: Michael McCune <elmiko@redhat.com>
Date: Fri, 25 Feb 2022 09:35:51 -0500
Subject: [PATCH] add user configurable cluster api version

This change introduces an environment variable, `CAPI_VERSION`, through
which a user can set the API version for the group they are using. This
change is being added to address situations where a user might have
multiple API versions for the cluster api group and wishes to be
explicit about which version is selected.

Also adds unit tests and documentation for the new behavior. This change
does not break the existing behavior.
---
 .../cloudprovider/clusterapi/README.md        | 17 +++++++
 .../clusterapi/clusterapi_controller.go       | 19 +++++++-
 .../clusterapi/clusterapi_controller_test.go  | 48 +++++++++++++++++++
 3 files changed, 83 insertions(+), 1 deletion(-)

diff --git a/cluster-autoscaler/cloudprovider/clusterapi/README.md b/cluster-autoscaler/cloudprovider/clusterapi/README.md
index 20581f1b89..bd800ffa4f 100644
--- a/cluster-autoscaler/cloudprovider/clusterapi/README.md
+++ b/cluster-autoscaler/cloudprovider/clusterapi/README.md
@@ -180,6 +180,23 @@ the machine annotation on nodes will be `test.8s.io/machine`, the machine deleti
 annotation will be `test.k8s.io/delete-machine`, and the cluster name label will be
 `test.k8s.io/cluster-name`.
 
+## Specifying a Custom Resource Version
+
+When determining the group version for the Cluster API types, by default the autoscaler
+will look for the latest version of the group. For example, if `MachineDeployments`
+exist in the `cluster.x-k8s.io` group at versions `v1alpha1` and `v1beta1`, the
+autoscaler will choose `v1beta1`.
+
+In some cases it may be desirable to specify which version of the API the cluster
+autoscaler should use. This can be useful in debugging scenarios, or in situations
+where you have deployed multiple API versions and wish to ensure that the autoscaler
+uses a specific version.
+
+Setting the `CAPI_VERSION` environment variable will instruct the autoscaler to use
+the version specified. This works in a similar fashion as the API group environment
+variable with the exception that there is no default value. When this variable is not
+set, the autoscaler will use the behavior described above.
+
 ## Sample manifest
 
 A sample manifest that will create a deployment running the autoscaler is
diff --git a/cluster-autoscaler/cloudprovider/clusterapi/clusterapi_controller.go b/cluster-autoscaler/cloudprovider/clusterapi/clusterapi_controller.go
index 54877b8b64..1cc7f1989b 100644
--- a/cluster-autoscaler/cloudprovider/clusterapi/clusterapi_controller.go
+++ b/cluster-autoscaler/cloudprovider/clusterapi/clusterapi_controller.go
@@ -46,7 +46,9 @@ const (
 	nodeProviderIDIndex    = "nodeProviderIDIndex"
 	defaultCAPIGroup       = "cluster.x-k8s.io"
 	// CAPIGroupEnvVar contains the environment variable name which allows overriding defaultCAPIGroup.
-	CAPIGroupEnvVar               = "CAPI_GROUP"
+	CAPIGroupEnvVar = "CAPI_GROUP"
+	// CAPIVersionEnvVar contains the environment variable name which allows overriding the Cluster API group version.
+	CAPIVersionEnvVar             = "CAPI_VERSION"
 	resourceNameMachine           = "machines"
 	resourceNameMachineSet        = "machinesets"
 	resourceNameMachineDeployment = "machinedeployments"
@@ -305,6 +307,17 @@ func getCAPIGroup() string {
 	return g
 }
 
+// getCAPIVersion returns a string the specifies the version for the API.
+// It will return either the value from the CAPI_VERSION environment variable,
+// or an empty string if the variable is not set.
+func getCAPIVersion() string {
+	v := os.Getenv(CAPIVersionEnvVar)
+	if v != "" {
+		klog.V(4).Infof("Using API Version %q", v)
+	}
+	return v
+}
+
 // newMachineController constructs a controller that watches Nodes,
 // Machines and MachineSet as they are added, updated and deleted on
 // the cluster.
@@ -415,6 +428,10 @@ func groupVersionHasResource(client discovery.DiscoveryInterface, groupVersion,
 }
 
 func getAPIGroupPreferredVersion(client discovery.DiscoveryInterface, APIGroup string) (string, error) {
+	if version := os.Getenv(CAPIVersionEnvVar); version != "" {
+		return version, nil
+	}
+
 	groupList, err := client.ServerGroups()
 	if err != nil {
 		return "", fmt.Errorf("failed to get ServerGroups: %v", err)
diff --git a/cluster-autoscaler/cloudprovider/clusterapi/clusterapi_controller_test.go b/cluster-autoscaler/cloudprovider/clusterapi/clusterapi_controller_test.go
index 158c5b78e7..5413de38a5 100644
--- a/cluster-autoscaler/cloudprovider/clusterapi/clusterapi_controller_test.go
+++ b/cluster-autoscaler/cloudprovider/clusterapi/clusterapi_controller_test.go
@@ -1369,6 +1369,26 @@ func TestControllerGetAPIVersionGroup(t *testing.T) {
 	}
 }
 
+func TestControllerGetAPIVersion(t *testing.T) {
+	expected := "v1beta1"
+	if err := os.Setenv(CAPIVersionEnvVar, expected); err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+	observed := getCAPIVersion()
+	if observed != expected {
+		t.Fatalf("Wrong API Version detected, expected %q, got %q", expected, observed)
+	}
+
+	if err := os.Unsetenv(CAPIVersionEnvVar); err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
+	expected = ""
+	observed = getCAPIVersion()
+	if observed != expected {
+		t.Fatalf("Wrong API Version detected, expected %q, got %q", expected, observed)
+	}
+}
+
 func TestControllerGetAPIVersionGroupWithMachineDeployments(t *testing.T) {
 	testConfig := createMachineDeploymentTestConfig(RandomString(6), RandomString(6), RandomString(6), 1, map[string]string{
 		nodeGroupMinSizeAnnotationKey: "1",
@@ -1424,28 +1444,40 @@ func TestControllerGetAPIVersionGroupWithMachineDeployments(t *testing.T) {
 }
 
 func TestGetAPIGroupPreferredVersion(t *testing.T) {
+	customVersion := "v1"
 	testCases := []struct {
 		description      string
 		APIGroup         string
 		preferredVersion string
+		envVar           string
 		error            bool
 	}{
 		{
 			description:      "find version for default API group",
 			APIGroup:         defaultCAPIGroup,
 			preferredVersion: "v1alpha3",
+			envVar:           "",
 			error:            false,
 		},
 		{
 			description:      "find version for another API group",
 			APIGroup:         customCAPIGroup,
 			preferredVersion: "v1beta1",
+			envVar:           "",
+			error:            false,
+		},
+		{
+			description:      "find version for another API group while overriding version with env var",
+			APIGroup:         customCAPIGroup,
+			preferredVersion: customVersion,
+			envVar:           customVersion,
 			error:            false,
 		},
 		{
 			description:      "API group does not exist",
 			APIGroup:         "does.not.exist",
 			preferredVersion: "",
+			envVar:           "",
 			error:            true,
 		},
 	}
@@ -1459,11 +1491,23 @@ func TestGetAPIGroupPreferredVersion(t *testing.T) {
 				{
 					GroupVersion: fmt.Sprintf("%s/v1alpha3", defaultCAPIGroup),
 				},
+				{
+					GroupVersion: fmt.Sprintf("%s/%s", customCAPIGroup, customVersion),
+				},
 			},
 		},
 	}
 	for _, tc := range testCases {
 		t.Run(tc.description, func(t *testing.T) {
+			if tc.envVar == "" {
+				if err := os.Unsetenv(CAPIVersionEnvVar); err != nil {
+					t.Fatalf("unexpected error: %v", err)
+				}
+			} else {
+				if err := os.Setenv(CAPIVersionEnvVar, tc.envVar); err != nil {
+					t.Fatalf("unexpected error: %v", err)
+				}
+			}
 			version, err := getAPIGroupPreferredVersion(discoveryClient, tc.APIGroup)
 			if (err != nil) != tc.error {
 				t.Errorf("expected to have error: %t. Had an error: %t", tc.error, err != nil)
@@ -1473,6 +1517,10 @@ func TestGetAPIGroupPreferredVersion(t *testing.T) {
 			}
 		})
 	}
+
+	if err := os.Unsetenv(CAPIVersionEnvVar); err != nil {
+		t.Fatalf("unexpected error: %v", err)
+	}
 }
 
 func TestGroupVersionHasResource(t *testing.T) {
-- 
GitLab