Skip to content

Commit e135317

Browse files
committed
Generate a default cloud.conf on AWS
OpenShift clusters prior to v4.14 did not explicitly need a cloud.conf file for AWS. In v.4.19, they do. This change makes sure that there is a minimally usable default cloud configuration present. Signed-off-by: Nolan Brubaker <[email protected]>
1 parent 86f8c6d commit e135317

File tree

4 files changed

+67
-29
lines changed

4 files changed

+67
-29
lines changed

pkg/cloud/aws/aws_config_transformer.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@ import (
1313
awsconfig "k8s.io/cloud-provider-aws/pkg/providers/v1/config"
1414
)
1515

16+
const defaultConfig = `[Global]
17+
`
18+
1619
// CloudConfigTransformer is used to inject OpenShift configuration defaults into the Cloud Provider config
17-
// for the AWS Cloud Provider.
20+
// for the AWS Cloud Provider. If an empty source string is provided, a minimal default configuration will be created.
1821
func CloudConfigTransformer(source string, infra *configv1.Infrastructure, network *configv1.Network) (string, error) {
1922
cfg, err := readAWSConfig(source)
2023
if err != nil {
@@ -26,13 +29,19 @@ func CloudConfigTransformer(source string, infra *configv1.Infrastructure, netwo
2629
return marshalAWSConfig(cfg)
2730
}
2831

32+
// readAWSConfig will parse a source string into a proper *awsconfig.CloudConfig.
33+
// If an empty source string is provided, a default configuration will be used.
2934
func readAWSConfig(source string) (*awsconfig.CloudConfig, error) {
35+
cfg := &awsconfig.CloudConfig{}
36+
37+
// There are cases in which a cloud config was not installed with a cluster, and this is valid.
38+
// We should, however, populate the configuration so that it doesn't fail on later versions that require
39+
// a cloud.conf.
3040
if len(source) == 0 {
31-
return nil, fmt.Errorf("empty INI file")
41+
source = defaultConfig
3242
}
3343

3444
// Use the same method the AWS CCM uses to load configuration.
35-
cfg := &awsconfig.CloudConfig{}
3645
if err := gcfg.FatalOnly(gcfg.ReadStringInto(cfg, source)); err != nil {
3746
return nil, fmt.Errorf("failed to parse INI file: %w", err)
3847
}

pkg/cloud/cloud.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ import (
2525
// consumer of the transformer.
2626
type cloudConfigTransformer func(source string, infra *configv1.Infrastructure, network *configv1.Network) (string, error)
2727

28+
// defaultCloudConfig returns a default cloud configuration in the event that a cluster does not have a source config map.
29+
// Clusters created prior to 4.14 do not always have a source config map.
30+
type defaultCloudConfig func(platform configv1.PlatformType) (string, error)
31+
2832
// GetCloudConfigTransformer returns the function that should be used to transform
2933
// the cloud configuration config map, and a boolean to indicate if the config should
3034
// be synced from the CCO namespace before applying the transformation.

pkg/controllers/cloud_config_sync_controller.go

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -108,17 +108,19 @@ func (r *CloudConfigReconciler) Reconcile(ctx context.Context, req ctrl.Request)
108108
}
109109
}
110110

111-
if !managedConfigFound {
111+
// Only look for an unmanaged config if the managed one isn't found and a name was specified.
112+
if !managedConfigFound && infra.Spec.CloudConfig.Name != "" {
112113
openshiftUnmanagedCMKey := client.ObjectKey{
113114
Name: infra.Spec.CloudConfig.Name,
114115
Namespace: OpenshiftConfigNamespace,
115116
}
116-
if err := r.Get(ctx, openshiftUnmanagedCMKey, sourceCM); err != nil {
117+
if err := r.Get(ctx, openshiftUnmanagedCMKey, sourceCM); errors.IsNotFound(err) {
118+
klog.Warningf("unmanaged cloud-config is not found, falling back to default cloud config.")
119+
} else if err != nil {
117120
klog.Errorf("unable to get cloud-config for sync")
118121
if err := r.setDegradedCondition(ctx); err != nil {
119122
return ctrl.Result{}, fmt.Errorf("failed to set conditions for cloud config controller: %v", err)
120123
}
121-
return ctrl.Result{}, err
122124
}
123125
}
124126

@@ -188,40 +190,51 @@ func (r *CloudConfigReconciler) isCloudConfigSyncNeeded(platformStatus *configv1
188190
return false, fmt.Errorf("platformStatus is required")
189191
}
190192
switch platformStatus.Type {
191-
case configv1.AzurePlatformType,
193+
case configv1.AWSPlatformType,
194+
configv1.AzurePlatformType,
192195
configv1.GCPPlatformType,
193196
configv1.VSpherePlatformType,
194197
configv1.IBMCloudPlatformType,
195198
configv1.PowerVSPlatformType,
196199
configv1.OpenStackPlatformType,
197200
configv1.NutanixPlatformType:
198201
return true, nil
199-
case configv1.AWSPlatformType:
200-
// Some of AWS regions might require to sync a cloud-config, in such case reference in infra resource will be presented
201-
return infraCloudConfigRef.Name != "", nil
202202
default:
203203
return false, nil
204204
}
205205
}
206206

207+
// prepareSourceConfigMap creates a usable ConfigMap for further processing into a cloud.conf file.
207208
func (r *CloudConfigReconciler) prepareSourceConfigMap(source *corev1.ConfigMap, infra *configv1.Infrastructure) (*corev1.ConfigMap, error) {
209+
cloudConfCm := source.DeepCopy()
210+
// We might have an empty ConfigMap in clusters created before 4.14.
211+
if cloudConfCm.Data == nil {
212+
cloudConfCm.Data = make(map[string]string)
213+
}
214+
208215
// Keys might be different between openshift-config/cloud-config and openshift-config-managed/kube-cloud-config
209216
// Always use "cloud.conf" which is default one across openshift
210-
cloudConfCm := source.DeepCopy()
211217
if _, ok := cloudConfCm.Data[defaultConfigKey]; ok {
212218
return cloudConfCm, nil
213219
}
214220

221+
// If a user provides their own cloud config, copy that over into the default key.
215222
infraConfigKey := infra.Spec.CloudConfig.Key
216223
if val, ok := cloudConfCm.Data[infraConfigKey]; ok {
217224
cloudConfCm.Data[defaultConfigKey] = val
218225
delete(cloudConfCm.Data, infraConfigKey)
219226
return cloudConfCm, nil
227+
} else if !ok && len(cloudConfCm.Data) > 0 {
228+
// Return an error if they provided a non-existent one and there was a cloud.conf specified.
229+
return nil, fmt.Errorf("key %s specified in infra resource does not exist in source configmap %s",
230+
infraConfigKey, client.ObjectKeyFromObject(source),
231+
)
220232
}
221-
return nil, fmt.Errorf(
222-
"key %s specified in infra resource does not found in source configmap %s",
223-
infraConfigKey, client.ObjectKeyFromObject(source),
224-
)
233+
234+
// If there was no data in the configmap and the user didn't specify their own make a default entry for it.
235+
cloudConfCm.Data[defaultConfigKey] = ""
236+
237+
return cloudConfCm, nil
225238
}
226239

227240
func (r *CloudConfigReconciler) isCloudConfigEqual(source *corev1.ConfigMap, target *corev1.ConfigMap) bool {

pkg/controllers/cloud_config_sync_controller_test.go

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ const (
2828
infraCloudConfKey = "foo"
2929

3030
defaultAzureConfig = `{"cloud":"AzurePublicCloud","tenantId":"0000000-0000-0000-0000-000000000000","Entries":null,"subscriptionId":"0000000-0000-0000-0000-000000000000","vmType":"standard","putVMSSVMBatchSize":0,"enableMigrateToIPBasedBackendPoolAPI":false,"clusterServiceLoadBalancerHealthProbeMode":"shared"}`
31+
defaultAWSConfig = `[Global]
32+
`
3133
)
3234

3335
func makeInfrastructureResource(platform configv1.PlatformType) *configv1.Infrastructure {
@@ -84,8 +86,7 @@ func makeInfraStatus(platform configv1.PlatformType) configv1.InfrastructureStat
8486
}
8587

8688
func makeInfraCloudConfig(platform configv1.PlatformType) *corev1.ConfigMap {
87-
defaultConfig := `[Global]
88-
`
89+
defaultConfig := defaultAWSConfig
8990

9091
if platform == configv1.AzurePlatformType {
9192
defaultConfig = defaultAzureConfig
@@ -98,8 +99,7 @@ func makeInfraCloudConfig(platform configv1.PlatformType) *corev1.ConfigMap {
9899
}
99100

100101
func makeManagedCloudConfig(platform configv1.PlatformType) *corev1.ConfigMap {
101-
defaultConfig := `[Global]
102-
`
102+
defaultConfig := defaultAWSConfig
103103

104104
if platform == configv1.AzurePlatformType {
105105
defaultConfig = defaultAzureConfig
@@ -151,14 +151,6 @@ var _ = Describe("prepareSourceConfigMap reconciler method", func() {
151151
Expect(reconciler.isCloudConfigEqual(preparedConfig, managedCloudConfig)).Should(BeTrue())
152152
})
153153

154-
It("config preparation should fail if key from infra resource does not found", func() {
155-
brokenInfraConfig := infraCloudConfig.DeepCopy()
156-
brokenInfraConfig.Data = map[string]string{"hehehehehe": "bar"}
157-
_, err := reconciler.prepareSourceConfigMap(brokenInfraConfig, infra)
158-
Expect(err).Should(Not(Succeed()))
159-
Expect(err.Error()).Should(BeEquivalentTo("key foo specified in infra resource does not found in source configmap openshift-config/test-config"))
160-
})
161-
162154
It("config preparation should not touch extra fields in infra ConfigMap", func() {
163155
extendedInfraConfig := infraCloudConfig.DeepCopy()
164156
extendedInfraConfig.Data = map[string]string{infraCloudConfKey: "{}", "{}": "{}"}
@@ -467,21 +459,28 @@ var _ = Describe("Cloud config sync reconciler", func() {
467459
Expect(cl.Create(ctx, makeInfraCloudConfig(configv1.AWSPlatformType))).To(Succeed())
468460
})
469461

470-
It("should skip config sync for AWS platform if there is no reference in infra resource", func() {
462+
It("should sync a default config AWS platform if there is no reference in infra resource", func() {
471463
infraResource := makeInfrastructureResource(configv1.AWSPlatformType)
472464
infraResource.Spec.CloudConfig.Name = ""
473465
Expect(cl.Create(ctx, infraResource)).To(Succeed())
474466

475467
infraResource.Status = makeInfraStatus(infraResource.Spec.PlatformSpec.Type)
476468
Expect(cl.Status().Update(ctx, infraResource.DeepCopy())).To(Succeed())
477469

470+
fetchedResource := &configv1.Infrastructure{}
471+
Expect(cl.Get(ctx, client.ObjectKeyFromObject(infraResource), fetchedResource)).To(Succeed())
472+
Expect(fetchedResource.Spec.CloudConfig.Name).To(Equal(""))
473+
478474
_, err := reconciler.Reconcile(context.TODO(), ctrl.Request{})
479475
Expect(err).To(BeNil())
480476

481477
allCMs := &corev1.ConfigMapList{}
482478
Expect(cl.List(ctx, allCMs, &client.ListOptions{Namespace: targetNamespaceName})).To(Succeed())
483479

484-
Expect(len(allCMs.Items)).To(BeZero())
480+
Expect(len(allCMs.Items)).To(BeEquivalentTo(1))
481+
// Our code ensures that there is at a minimum a global section.
482+
// The CCM itself may end up defaulting values for us, so don't use exact string matching.
483+
Expect(allCMs.Items[0].Data[defaultConfigKey]).To(HavePrefix(defaultAWSConfig))
485484
})
486485

487486
It("should perform config sync for AWS platform if there is a reference in infra resource", func() {
@@ -500,6 +499,19 @@ var _ = Describe("Cloud config sync reconciler", func() {
500499
Expect(len(allCMs.Items)).NotTo(BeZero())
501500
Expect(len(allCMs.Items)).To(BeEquivalentTo(1))
502501
})
502+
503+
It("should error if a user-specified configmap key isn't present", func() {
504+
infraResource := makeInfrastructureResource(configv1.AWSPlatformType)
505+
infraResource.Spec.CloudConfig.Key = "notfound"
506+
Expect(cl.Create(ctx, infraResource)).To(Succeed())
507+
508+
infraResource.Status = makeInfraStatus(infraResource.Spec.PlatformSpec.Type)
509+
Expect(cl.Status().Update(ctx, infraResource.DeepCopy())).To(Succeed())
510+
511+
_, err := reconciler.Reconcile(context.TODO(), ctrl.Request{})
512+
Expect(err).To(Not(BeNil()))
513+
514+
})
503515
})
504516

505517
Context("On Azure platform", func() {

0 commit comments

Comments
 (0)