From bf88b22c395b07a3eaf69a250549f6be97783898 Mon Sep 17 00:00:00 2001 From: staebler Date: Wed, 20 May 2020 10:26:17 -0400 Subject: [PATCH] kube_cloud_config: support for other Azure cloud environments Populate the `cloud` field in kube cloud.conf stored in the kube-cloud-config ConfigMap in the openshift-config-managed namespace. This field informs in which Azure cloud environment the cluster is installed and consequently which Azure API endpoint should be used when communicating via the Azure SDK. The value populated comes from the `.status.platformStatus.azure.cloudName` field of the infrastructure.config.openshift.io resource. If the field is empty or missing, then the default "AzurePublicCloud" value is set for the `cloud` field in the kube cloud config. If the field generated from the infrastructure resource conflicts with the field in the user-provided kube cloud config, then the controller will error on syncing the infrastructure resource. See https://github.com/openshift/enhancements/pull/321 https://issues.redhat.com/browse/CORS-1444 --- pkg/operator/kube_cloud_config/azure.go | 119 +++++ pkg/operator/kube_cloud_config/azure_test.go | 405 ++++++++++++++++++ pkg/operator/kube_cloud_config/controller.go | 3 +- .../kube_cloud_config/controller_test.go | 41 +- 4 files changed, 556 insertions(+), 12 deletions(-) create mode 100644 pkg/operator/kube_cloud_config/azure.go create mode 100644 pkg/operator/kube_cloud_config/azure_test.go diff --git a/pkg/operator/kube_cloud_config/azure.go b/pkg/operator/kube_cloud_config/azure.go new file mode 100644 index 000000000..d4fafa586 --- /dev/null +++ b/pkg/operator/kube_cloud_config/azure.go @@ -0,0 +1,119 @@ +package kube_cloud_config + +import ( + "bytes" + "encoding/json" + "fmt" + "strings" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/validation/field" + "sigs.k8s.io/yaml" + + configv1 "github.com/openshift/api/config/v1" + + "github.com/openshift/cluster-config-operator/pkg/operator/operatorclient" +) + +const ( + azureCloudFieldName = "cloud" +) + +var ( + validAzureCloudNames = map[configv1.AzureCloudEnvironment]bool{ + configv1.AzurePublicCloud: true, + configv1.AzureUSGovernmentCloud: true, + configv1.AzureChinaCloud: true, + configv1.AzureGermanCloud: true, + } + + validAzureCloudNameValues = func() []string { + v := make([]string, 0, len(validAzureCloudNames)) + for n := range validAzureCloudNames { + v = append(v, string(n)) + } + return v + }() +) + +// azureTransformer implements the cloudConfigTransformer. It uses the input ConfigMap and infra.status.platformStatus.azure.cloudName +// to create a new config that has the cloud field set. +// It returns an error if the platform is not AzurePlatformType. +func azureTransformer(input *corev1.ConfigMap, key string, infra *configv1.Infrastructure) (*corev1.ConfigMap, error) { + if !(infra.Status.PlatformStatus != nil && + infra.Status.PlatformStatus.Type == configv1.AzurePlatformType) { + return nil, fmt.Errorf("invalid platform, expected to be Azure") + } + + cloud := configv1.AzurePublicCloud + if azurePlatform := infra.Status.PlatformStatus.Azure; azurePlatform != nil { + if c := azurePlatform.CloudName; c != "" { + if !validAzureCloudNames[c] { + return nil, field.NotSupported(field.NewPath("status", "platformStatus", "azure", "cloudName"), c, validAzureCloudNameValues) + } + cloud = c + } + } + + output := input.DeepCopy() + output.Namespace = operatorclient.GlobalMachineSpecifiedConfigNamespace + output.Name = targetConfigName + delete(output.Data, key) + delete(output.BinaryData, key) + + var inCfgRaw []byte + if v, ok := input.Data[key]; ok { + inCfgRaw = []byte(v) + } else if v, ok := input.BinaryData[key]; ok { + inCfgRaw = v + } + + useInCfg := false + + var cfg map[string]interface{} + if len(inCfgRaw) > 0 { + if err := yaml.Unmarshal(inCfgRaw, &cfg); err != nil { + return nil, fmt.Errorf("failed to read the cloud.conf: %w", err) + } + if inCloudUntyped, ok := cfg[azureCloudFieldName]; ok { + inCloud, ok := inCloudUntyped.(string) + if !ok { + return nil, fmt.Errorf("invalid user-provided cloud.conf: \"cloud\" field is not a string") + } + if len(inCloud) > 0 { + if !strings.EqualFold(inCloud, string(cloud)) { + return nil, fmt.Errorf("invalid user-provided cloud.conf: \"cloud\" field in user-provided cloud.conf conflicts with infrastructure object") + } + useInCfg = true + } + } + } + + outCfgRaw := inCfgRaw + if !useInCfg { + if cfg == nil { + cfg = make(map[string]interface{}, 1) + } + cfg[azureCloudFieldName] = string(cloud) + outCfgBuffer := &bytes.Buffer{} + encoder := json.NewEncoder(outCfgBuffer) + encoder.SetIndent("", "\t") + if err := encoder.Encode(cfg); err != nil { + return nil, fmt.Errorf("failed to encode config: %w", err) + } + outCfgRaw = outCfgBuffer.Bytes() + } + + if _, ok := input.Data[key]; ok { + output.Data[targetConfigKey] = string(outCfgRaw) // store the config to same as input + } else if _, ok := input.BinaryData[key]; ok { + output.BinaryData[targetConfigKey] = outCfgRaw // store the config to same as input + } else { + if output.Data == nil { + output.Data = make(map[string]string, 1) + } + output.Data[targetConfigKey] = string(outCfgRaw) // store the new config to input key + } + + return output, nil +} diff --git a/pkg/operator/kube_cloud_config/azure_test.go b/pkg/operator/kube_cloud_config/azure_test.go new file mode 100644 index 000000000..152f7918d --- /dev/null +++ b/pkg/operator/kube_cloud_config/azure_test.go @@ -0,0 +1,405 @@ +package kube_cloud_config + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + configv1 "github.com/openshift/api/config/v1" +) + +func Test_azureTransformer(t *testing.T) { + cases := []struct { + name string + inputcm *corev1.ConfigMap + inputinfra *configv1.Infrastructure + + outputcm *corev1.ConfigMap + err string + }{ + { + name: "empty config map, non azure infra", + inputcm: &corev1.ConfigMap{}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{}}, + + outputcm: nil, + err: `invalid platform, expected to be Azure`, + }, { + name: "empty config map, non azure infra", + inputcm: &corev1.ConfigMap{}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.NonePlatformType}}, + + outputcm: nil, + err: `invalid platform, expected to be Azure`, + }, { + name: "empty config map, non azure infra", + inputcm: &corev1.ConfigMap{}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.NonePlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.NonePlatformType}}}, + + outputcm: nil, + err: `invalid platform, expected to be Azure`, + }, { + name: "empty config map, non azure infra", + inputcm: &corev1.ConfigMap{}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.AWSPlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.AWSPlatformType, AWS: &configv1.AWSPlatformStatus{Region: "test-region"}}}}, + + outputcm: nil, + err: `invalid platform, expected to be Azure`, + }, { + name: "non empty config map, non azure infra", + inputcm: &corev1.ConfigMap{Data: map[string]string{"config": `{"resourceGroup":"test-rg"}`}}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.AWSPlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.AWSPlatformType, AWS: &configv1.AWSPlatformStatus{Region: "test-region"}}}}, + + outputcm: nil, + err: `invalid platform, expected to be Azure`, + }, { + name: "empty config map, azure infra", + inputcm: &corev1.ConfigMap{}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.AzurePlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType}}}, + + outputcm: &corev1.ConfigMap{Data: map[string]string{"cloud.conf": `{ + "cloud": "AzurePublicCloud" +} +`}}, + err: ``, + }, { + name: "empty config map, azure infra", + inputcm: &corev1.ConfigMap{}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.AzurePlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType, Azure: &configv1.AzurePlatformStatus{ResourceGroupName: "test-rg"}}}}, + + outputcm: &corev1.ConfigMap{Data: map[string]string{"cloud.conf": `{ + "cloud": "AzurePublicCloud" +} +`}}, + err: ``, + }, { + name: "non empty config map, azure infra", + inputcm: &corev1.ConfigMap{Data: map[string]string{"config": `{"resourceGroup":"test-rg"}`}}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.AzurePlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType, Azure: &configv1.AzurePlatformStatus{ResourceGroupName: "test-rg"}}}}, + + outputcm: &corev1.ConfigMap{Data: map[string]string{"cloud.conf": `{ + "cloud": "AzurePublicCloud", + "resourceGroup": "test-rg" +} +`}}, + err: ``, + }, { + name: "empty config map, azure infra with public cloud", + inputcm: &corev1.ConfigMap{}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.AzurePlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType, Azure: &configv1.AzurePlatformStatus{CloudName: configv1.AzurePublicCloud}}}}, + + outputcm: &corev1.ConfigMap{Data: map[string]string{"cloud.conf": `{ + "cloud": "AzurePublicCloud" +} +`}}, + err: ``, + }, { + name: "empty config map, azure infra with US Gov cloud", + inputcm: &corev1.ConfigMap{}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.AzurePlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType, Azure: &configv1.AzurePlatformStatus{CloudName: configv1.AzureUSGovernmentCloud}}}}, + + outputcm: &corev1.ConfigMap{Data: map[string]string{"cloud.conf": `{ + "cloud": "AzureUSGovernmentCloud" +} +`}}, + err: ``, + }, { + name: "empty config map, azure infra with China cloud", + inputcm: &corev1.ConfigMap{}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.AzurePlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType, Azure: &configv1.AzurePlatformStatus{CloudName: configv1.AzureChinaCloud}}}}, + + outputcm: &corev1.ConfigMap{Data: map[string]string{"cloud.conf": `{ + "cloud": "AzureChinaCloud" +} +`}}, + err: ``, + }, { + name: "empty config map, azure infra with German cloud", + inputcm: &corev1.ConfigMap{}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.AzurePlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType, Azure: &configv1.AzurePlatformStatus{CloudName: configv1.AzureGermanCloud}}}}, + + outputcm: &corev1.ConfigMap{Data: map[string]string{"cloud.conf": `{ + "cloud": "AzureGermanCloud" +} +`}}, + err: ``, + }, { + name: "empty config map, azure infra with empty cloud", + inputcm: &corev1.ConfigMap{}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.AzurePlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType, Azure: &configv1.AzurePlatformStatus{CloudName: ""}}}}, + + outputcm: &corev1.ConfigMap{Data: map[string]string{"cloud.conf": `{ + "cloud": "AzurePublicCloud" +} +`}}, + err: ``, + }, { + name: "empty config map, azure infra with invalid cloud", + inputcm: &corev1.ConfigMap{}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.AzurePlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType, Azure: &configv1.AzurePlatformStatus{CloudName: "AzureOtherCloud"}}}}, + + outputcm: nil, + err: `status\.platformStatus\.azure\.cloudName: Unsupported value: "AzureOtherCloud": supported values: "\w+"(, "\w+")*`, + }, { + name: "non-empty config map, azure infra with public cloud", + inputcm: &corev1.ConfigMap{Data: map[string]string{"config": `{"resourceGroup":"test-rg"}`}}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.AzurePlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType, Azure: &configv1.AzurePlatformStatus{CloudName: configv1.AzurePublicCloud}}}}, + + outputcm: &corev1.ConfigMap{Data: map[string]string{"cloud.conf": `{ + "cloud": "AzurePublicCloud", + "resourceGroup": "test-rg" +} +`}}, + err: ``, + }, { + name: "non-empty config map, azure infra with US Gov cloud", + inputcm: &corev1.ConfigMap{Data: map[string]string{"config": `{"resourceGroup":"test-rg"}`}}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.AzurePlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType, Azure: &configv1.AzurePlatformStatus{CloudName: configv1.AzureUSGovernmentCloud}}}}, + + outputcm: &corev1.ConfigMap{Data: map[string]string{"cloud.conf": `{ + "cloud": "AzureUSGovernmentCloud", + "resourceGroup": "test-rg" +} +`}}, + err: ``, + }, { + name: "non-empty config map, azure infra with China cloud", + inputcm: &corev1.ConfigMap{Data: map[string]string{"config": `{"resourceGroup":"test-rg"}`}}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.AzurePlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType, Azure: &configv1.AzurePlatformStatus{CloudName: configv1.AzureChinaCloud}}}}, + + outputcm: &corev1.ConfigMap{Data: map[string]string{"cloud.conf": `{ + "cloud": "AzureChinaCloud", + "resourceGroup": "test-rg" +} +`}}, + err: ``, + }, { + name: "non-empty config map, azure infra with German cloud", + inputcm: &corev1.ConfigMap{Data: map[string]string{"config": `{"resourceGroup":"test-rg"}`}}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.AzurePlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType, Azure: &configv1.AzurePlatformStatus{CloudName: configv1.AzureGermanCloud}}}}, + + outputcm: &corev1.ConfigMap{Data: map[string]string{"cloud.conf": `{ + "cloud": "AzureGermanCloud", + "resourceGroup": "test-rg" +} +`}}, + err: ``, + }, { + name: "non-empty config map, azure infra with empty cloud", + inputcm: &corev1.ConfigMap{Data: map[string]string{"config": `{"resourceGroup":"test-rg"}`}}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.AzurePlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType, Azure: &configv1.AzurePlatformStatus{CloudName: ""}}}}, + + outputcm: &corev1.ConfigMap{Data: map[string]string{"cloud.conf": `{ + "cloud": "AzurePublicCloud", + "resourceGroup": "test-rg" +} +`}}, + err: ``, + }, { + name: "non-empty config map, azure infra with invalid cloud", + inputcm: &corev1.ConfigMap{Data: map[string]string{"config": `{"resourceGroup":"test-rg"}`}}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.AzurePlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType, Azure: &configv1.AzurePlatformStatus{CloudName: "AzureOtherCloud"}}}}, + + outputcm: nil, + err: `status\.platformStatus\.azure\.cloudName: Unsupported value: "AzureOtherCloud": supported values: "\w+"(, "\w+")*`, + }, { + name: "config map with matching cloud, azure infra with public cloud", + inputcm: &corev1.ConfigMap{Data: map[string]string{"config": `{"cloud":"AzurePublicCloud"}`}}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.AzurePlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType, Azure: &configv1.AzurePlatformStatus{CloudName: configv1.AzurePublicCloud}}}}, + + outputcm: &corev1.ConfigMap{Data: map[string]string{"cloud.conf": `{"cloud":"AzurePublicCloud"}`}}, + err: ``, + }, { + name: "config map with matching cloud, azure infra with US Gov cloud", + inputcm: &corev1.ConfigMap{Data: map[string]string{"config": `{"cloud":"AzureUSGovernmentCloud"}`}}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.AzurePlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType, Azure: &configv1.AzurePlatformStatus{CloudName: configv1.AzureUSGovernmentCloud}}}}, + + outputcm: &corev1.ConfigMap{Data: map[string]string{"cloud.conf": `{"cloud":"AzureUSGovernmentCloud"}`}}, + err: ``, + }, { + name: "config map with matching cloud, azure infra with China cloud", + inputcm: &corev1.ConfigMap{Data: map[string]string{"config": `{"cloud":"AzureChinaCloud"}`}}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.AzurePlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType, Azure: &configv1.AzurePlatformStatus{CloudName: configv1.AzureChinaCloud}}}}, + + outputcm: &corev1.ConfigMap{Data: map[string]string{"cloud.conf": `{"cloud":"AzureChinaCloud"}`}}, + err: ``, + }, { + name: "config map with matching cloud, azure infra with German cloud", + inputcm: &corev1.ConfigMap{Data: map[string]string{"config": `{"cloud":"AzureGermanCloud"}`}}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.AzurePlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType, Azure: &configv1.AzurePlatformStatus{CloudName: configv1.AzureGermanCloud}}}}, + + outputcm: &corev1.ConfigMap{Data: map[string]string{"cloud.conf": `{"cloud":"AzureGermanCloud"}`}}, + err: ``, + }, { + name: "config map with matching cloud, azure infra with empty cloud", + inputcm: &corev1.ConfigMap{Data: map[string]string{"config": `{"cloud":"AzurePublicCloud"}`}}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.AzurePlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType, Azure: &configv1.AzurePlatformStatus{CloudName: ""}}}}, + + outputcm: &corev1.ConfigMap{Data: map[string]string{"cloud.conf": `{"cloud":"AzurePublicCloud"}`}}, + err: ``, + }, { + name: "config map with empty cloud, azure infra with public cloud", + inputcm: &corev1.ConfigMap{Data: map[string]string{"config": `{"cloud":""}`}}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.AzurePlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType, Azure: &configv1.AzurePlatformStatus{CloudName: configv1.AzurePublicCloud}}}}, + + outputcm: &corev1.ConfigMap{Data: map[string]string{"cloud.conf": `{ + "cloud": "AzurePublicCloud" +} +`}}, + err: ``, + }, { + name: "config map with empty cloud, azure infra with US Gov cloud", + inputcm: &corev1.ConfigMap{Data: map[string]string{"config": `{"cloud":""}`}}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.AzurePlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType, Azure: &configv1.AzurePlatformStatus{CloudName: configv1.AzureUSGovernmentCloud}}}}, + + outputcm: &corev1.ConfigMap{Data: map[string]string{"cloud.conf": `{ + "cloud": "AzureUSGovernmentCloud" +} +`}}, + err: ``, + }, { + name: "config map with empty cloud, azure infra with China cloud", + inputcm: &corev1.ConfigMap{Data: map[string]string{"config": `{"cloud":""}`}}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.AzurePlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType, Azure: &configv1.AzurePlatformStatus{CloudName: configv1.AzureChinaCloud}}}}, + + outputcm: &corev1.ConfigMap{Data: map[string]string{"cloud.conf": `{ + "cloud": "AzureChinaCloud" +} +`}}, + err: ``, + }, { + name: "config map with empty cloud, azure infra with German cloud", + inputcm: &corev1.ConfigMap{Data: map[string]string{"config": `{"cloud":""}`}}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.AzurePlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType, Azure: &configv1.AzurePlatformStatus{CloudName: configv1.AzureGermanCloud}}}}, + + outputcm: &corev1.ConfigMap{Data: map[string]string{"cloud.conf": `{ + "cloud": "AzureGermanCloud" +} +`}}, + err: ``, + }, { + name: "config map with empty cloud, azure infra with empty cloud", + inputcm: &corev1.ConfigMap{Data: map[string]string{"config": `{"cloud":""}`}}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.AzurePlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType, Azure: &configv1.AzurePlatformStatus{CloudName: ""}}}}, + + outputcm: &corev1.ConfigMap{Data: map[string]string{"cloud.conf": `{ + "cloud": "AzurePublicCloud" +} +`}}, + err: ``, + }, { + name: "config map with empty cloud, azure infra with invalid cloud", + inputcm: &corev1.ConfigMap{Data: map[string]string{"config": `{"cloud":""}`}}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.AzurePlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType, Azure: &configv1.AzurePlatformStatus{CloudName: "AzureOtherCloud"}}}}, + + outputcm: nil, + err: `status\.platformStatus\.azure\.cloudName: Unsupported value: "AzureOtherCloud": supported values: "\w+"(, "\w+")*`, + }, { + name: "config map with conflicting cloud, azure infra with public cloud", + inputcm: &corev1.ConfigMap{Data: map[string]string{"config": `{"cloud":"AzureUSGovernmentCloud"}`}}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.AzurePlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType, Azure: &configv1.AzurePlatformStatus{CloudName: configv1.AzurePublicCloud}}}}, + + outputcm: nil, + err: `invalid user-provided cloud.conf: "cloud" field in user-provided cloud.conf conflicts with infrastructure object`, + }, { + name: "config map with conflicting cloud, azure infra with US Gov cloud", + inputcm: &corev1.ConfigMap{Data: map[string]string{"config": `{"cloud":"AzurePublicCloud"}`}}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.AzurePlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType, Azure: &configv1.AzurePlatformStatus{CloudName: configv1.AzureUSGovernmentCloud}}}}, + + outputcm: nil, + err: `invalid user-provided cloud.conf: "cloud" field in user-provided cloud.conf conflicts with infrastructure object`, + }, { + name: "config map with conflicting cloud, azure infra with China cloud", + inputcm: &corev1.ConfigMap{Data: map[string]string{"config": `{"cloud":"AzurePublicCloud"}`}}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.AzurePlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType, Azure: &configv1.AzurePlatformStatus{CloudName: configv1.AzureChinaCloud}}}}, + + outputcm: nil, + err: `invalid user-provided cloud.conf: "cloud" field in user-provided cloud.conf conflicts with infrastructure object`, + }, { + name: "config map with conflicting cloud, azure infra with German cloud", + inputcm: &corev1.ConfigMap{Data: map[string]string{"config": `{"cloud":"AzurePublicCloud"}`}}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.AzurePlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType, Azure: &configv1.AzurePlatformStatus{CloudName: configv1.AzureGermanCloud}}}}, + + outputcm: nil, + err: `invalid user-provided cloud.conf: "cloud" field in user-provided cloud.conf conflicts with infrastructure object`, + }, { + name: "config map with conflicting cloud, azure infra with empty cloud", + inputcm: &corev1.ConfigMap{Data: map[string]string{"config": `{"cloud":"AzureUSGovernmentCloud"}`}}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.AzurePlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType, Azure: &configv1.AzurePlatformStatus{CloudName: ""}}}}, + + outputcm: nil, + err: `invalid user-provided cloud.conf: "cloud" field in user-provided cloud.conf conflicts with infrastructure object`, + }, { + name: "config map with non-yaml data, azure infra", + inputcm: &corev1.ConfigMap{Data: map[string]string{"config": `not yaml`}}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.AzurePlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType, Azure: &configv1.AzurePlatformStatus{CloudName: ""}}}}, + + outputcm: nil, + err: `failed to read the cloud.conf: error unmarshaling`, + }, { + name: "config map with non-string cloud, azure infra", + inputcm: &corev1.ConfigMap{Data: map[string]string{"config": `{"cloud":1}`}}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.AzurePlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType, Azure: &configv1.AzurePlatformStatus{CloudName: ""}}}}, + + outputcm: nil, + err: `invalid user-provided cloud.conf: "cloud" field is not a string`, + }, { + name: "config map with binary data, azure infra", + inputcm: &corev1.ConfigMap{BinaryData: map[string][]byte{"config": []byte(`{"resourceGroup":"test-rg"}`)}}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.AzurePlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType, Azure: &configv1.AzurePlatformStatus{CloudName: ""}}}}, + + outputcm: &corev1.ConfigMap{BinaryData: map[string][]byte{"cloud.conf": []byte(`{ + "cloud": "AzurePublicCloud", + "resourceGroup": "test-rg" +} +`)}}, + err: ``, + }, { + name: "config map with unicode, azure infra with public cloud", + inputcm: &corev1.ConfigMap{Data: map[string]string{"config": `{"resourceGroup":"测试资源组"}`}}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.AzurePlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType, Azure: &configv1.AzurePlatformStatus{CloudName: configv1.AzurePublicCloud}}}}, + + outputcm: &corev1.ConfigMap{Data: map[string]string{"cloud.conf": `{ + "cloud": "AzurePublicCloud", + "resourceGroup": "测试资源组" +} +`}}, + err: ``, + }, { + name: "config map with unicode binary data, azure infra with public cloud", + inputcm: &corev1.ConfigMap{BinaryData: map[string][]byte{"config": []byte(`{"resourceGroup":"测试资源组"}`)}}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.AzurePlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType, Azure: &configv1.AzurePlatformStatus{CloudName: configv1.AzurePublicCloud}}}}, + + outputcm: &corev1.ConfigMap{BinaryData: map[string][]byte{"cloud.conf": []byte(`{ + "cloud": "AzurePublicCloud", + "resourceGroup": "测试资源组" +} +`)}}, + err: ``, + }, { + name: "config map with matching upper-case cloud, azure infra with public cloud", + inputcm: &corev1.ConfigMap{Data: map[string]string{"config": `{"cloud":"AZUREPUBLICCLOUD"}`}}, + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{Platform: configv1.AzurePlatformType, PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType, Azure: &configv1.AzurePlatformStatus{CloudName: configv1.AzurePublicCloud}}}}, + + outputcm: &corev1.ConfigMap{Data: map[string]string{"cloud.conf": `{"cloud":"AZUREPUBLICCLOUD"}`}}, + err: ``, + }, + } + + for _, test := range cases { + t.Run(test.name, func(t *testing.T) { + outputcm, err := azureTransformer(test.inputcm, "config", test.inputinfra) + if test.err == "" { + if assert.NoError(t, err) { + outputcm.ObjectMeta = metav1.ObjectMeta{} + assert.EqualValues(t, test.outputcm, outputcm) + } + } else { + assert.Regexp(t, test.err, err) + } + }) + } +} diff --git a/pkg/operator/kube_cloud_config/controller.go b/pkg/operator/kube_cloud_config/controller.go index 5a2a3504c..ab01856c8 100644 --- a/pkg/operator/kube_cloud_config/controller.go +++ b/pkg/operator/kube_cloud_config/controller.go @@ -53,7 +53,8 @@ func NewController(operatorClient operatorv1helpers.OperatorClient, infraLister: infraLister, configMapClient: configMapClient, cloudConfigTransformers: map[configv1.PlatformType]cloudConfigTransformer{ - configv1.AWSPlatformType: awsTransformer, + configv1.AWSPlatformType: awsTransformer, + configv1.AzurePlatformType: azureTransformer, }, } return factory.New(). diff --git a/pkg/operator/kube_cloud_config/controller_test.go b/pkg/operator/kube_cloud_config/controller_test.go index c2083ac2e..9cc38b423 100644 --- a/pkg/operator/kube_cloud_config/controller_test.go +++ b/pkg/operator/kube_cloud_config/controller_test.go @@ -97,16 +97,6 @@ func Test_sync(t *testing.T) { err string actions []ktesting.Action }{{ - inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType}}}, - inputdata: `{"somekey": "somevalue"}`, - - outputdata: map[string]string{"cloud.conf": `{"somekey": "somevalue"}`}, - actions: []ktesting.Action{ - ktesting.NewGetAction(schema.GroupVersionResource{Resource: "configmaps"}, "openshift-config", "cluster-config-v1"), - ktesting.NewGetAction(schema.GroupVersionResource{Resource: "configmaps"}, "openshift-config-managed", "kube-cloud-config"), - ktesting.NewUpdateAction(schema.GroupVersionResource{Resource: "configmaps"}, "openshift-config-managed", nil), - }, - }, { inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{PlatformStatus: &configv1.PlatformStatus{Type: configv1.GCPPlatformType}}}, inputdata: `[global] somekey = somevalue`, @@ -172,6 +162,34 @@ SubnetID = subnet-test Region = test-region URL = https://ec2.local SigningRegion = test-region +`}, + actions: []ktesting.Action{ + ktesting.NewGetAction(schema.GroupVersionResource{Resource: "configmaps"}, "openshift-config", "cluster-config-v1"), + ktesting.NewGetAction(schema.GroupVersionResource{Resource: "configmaps"}, "openshift-config-managed", "kube-cloud-config"), + ktesting.NewUpdateAction(schema.GroupVersionResource{Resource: "configmaps"}, "openshift-config-managed", nil), + }, + }, { + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType, Azure: &configv1.AzurePlatformStatus{}}}}, + inputdata: `{"resourceGroup":"test-rg"}`, + + outputdata: map[string]string{"cloud.conf": `{ + "cloud": "AzurePublicCloud", + "resourceGroup": "test-rg" +} +`}, + actions: []ktesting.Action{ + ktesting.NewGetAction(schema.GroupVersionResource{Resource: "configmaps"}, "openshift-config", "cluster-config-v1"), + ktesting.NewGetAction(schema.GroupVersionResource{Resource: "configmaps"}, "openshift-config-managed", "kube-cloud-config"), + ktesting.NewUpdateAction(schema.GroupVersionResource{Resource: "configmaps"}, "openshift-config-managed", nil), + }, + }, { + inputinfra: &configv1.Infrastructure{Status: configv1.InfrastructureStatus{PlatformStatus: &configv1.PlatformStatus{Type: configv1.AzurePlatformType, Azure: &configv1.AzurePlatformStatus{CloudName: configv1.AzureUSGovernmentCloud}}}}, + inputdata: `{"resourceGroup":"test-rg"}`, + + outputdata: map[string]string{"cloud.conf": `{ + "cloud": "AzureUSGovernmentCloud", + "resourceGroup": "test-rg" +} `}, actions: []ktesting.Action{ ktesting.NewGetAction(schema.GroupVersionResource{Resource: "configmaps"}, "openshift-config", "cluster-config-v1"), @@ -200,7 +218,8 @@ SubnetID = subnet-test infraLister: configv1listers.NewInfrastructureLister(indexerInfra), configMapClient: fake.CoreV1(), cloudConfigTransformers: map[configv1.PlatformType]cloudConfigTransformer{ - configv1.AWSPlatformType: awsTransformer, + configv1.AWSPlatformType: awsTransformer, + configv1.AzurePlatformType: azureTransformer, }, }