Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2080,6 +2080,38 @@ func (r *reconciler) reconcileObservedConfiguration(ctx context.Context, hcp *hy
func (r *reconciler) reconcileCloudConfig(ctx context.Context, hcp *hyperv1.HostedControlPlane) error {

switch hcp.Spec.Platform.Type {
case hyperv1.AWSPlatform:
reference := cpomanifests.AWSProviderConfig(hcp.Namespace)
if err := r.cpClient.Get(ctx, client.ObjectKeyFromObject(reference), reference); err != nil {
return fmt.Errorf("failed to fetch %s/%s configmap from management cluster: %w", reference.Namespace, reference.Name, err)
}

cm := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Namespace: ConfigNamespace, Name: CloudProviderCMName}}
if _, err := r.CreateOrUpdate(ctx, r.client, cm, func() error {
if cm.Data == nil {
cm.Data = map[string]string{}
}
cm.Data["config"] = reference.Data["aws.conf"]

// Sync CA bundle from additionalTrustBundle if configured.
// The CCCMO's trusted_ca_bundle_controller looks for the "ca-bundle.pem" key
// in the cloud-provider-config ConfigMap to include in the merged trust bundle
// for CCM pods. This ensures that custom CAs for AWS API endpoints are trusted.
if hcp.Spec.AdditionalTrustBundle != nil {
cpUserCAConfigMap := cpomanifests.UserCAConfigMap(hcp.Namespace)
if err := r.cpClient.Get(ctx, client.ObjectKeyFromObject(cpUserCAConfigMap), cpUserCAConfigMap); err != nil {
return fmt.Errorf("failed to get AdditionalTrustBundle ConfigMap: %w", err)
}
if caBundle, ok := cpUserCAConfigMap.Data["ca-bundle.crt"]; ok {
cm.Data["ca-bundle.pem"] = caBundle
}
} else {
delete(cm.Data, "ca-bundle.pem")
}
return nil
}); err != nil {
return fmt.Errorf("failed to reconcile the %s/%s configmap: %w", cm.Namespace, cm.Name, err)
}
case hyperv1.AzurePlatform:
// This is needed for the e2e tests and only for Azure: https://github.com/openshift/origin/blob/625733dd1ce7ebf40c3dd0abd693f7bb54f2d580/test/extended/util/cluster/cluster.go#L186
reference := cpomanifests.AzureProviderConfig(hcp.Namespace)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,144 @@ func TestReconcileUserCertCABundle(t *testing.T) {
}
}

func TestReconcileCloudConfigAWS(t *testing.T) {
testNamespace := "master-cluster1"
testHCPName := "cluster1"
tests := map[string]struct {
inputHCP *hyperv1.HostedControlPlane
inputObjects []client.Object
expectConfig bool
expectCABundle bool
}{
"When AWS platform has no additionalTrustBundle it should create cloud-provider-config without ca-bundle.pem": {
inputHCP: &hyperv1.HostedControlPlane{
ObjectMeta: metav1.ObjectMeta{
Name: testHCPName,
Namespace: testNamespace,
},
Spec: hyperv1.HostedControlPlaneSpec{
Platform: hyperv1.PlatformSpec{
Type: hyperv1.AWSPlatform,
},
},
},
inputObjects: []client.Object{
&corev1.ConfigMap{
ObjectMeta: cpomanifests.AWSProviderConfig(testNamespace).ObjectMeta,
Data: map[string]string{
"aws.conf": "[Global]\nZone = us-east-1a\n",
},
},
},
expectConfig: true,
expectCABundle: false,
},
"When AWS platform has additionalTrustBundle it should include ca-bundle.pem in cloud-provider-config": {
inputHCP: &hyperv1.HostedControlPlane{
ObjectMeta: metav1.ObjectMeta{
Name: testHCPName,
Namespace: testNamespace,
},
Spec: hyperv1.HostedControlPlaneSpec{
Platform: hyperv1.PlatformSpec{
Type: hyperv1.AWSPlatform,
},
AdditionalTrustBundle: &corev1.LocalObjectReference{
Name: cpomanifests.UserCAConfigMap(testNamespace).Name,
},
},
},
inputObjects: []client.Object{
&corev1.ConfigMap{
ObjectMeta: cpomanifests.AWSProviderConfig(testNamespace).ObjectMeta,
Data: map[string]string{
"aws.conf": "[Global]\nZone = us-east-1a\n",
},
},
&corev1.ConfigMap{
ObjectMeta: cpomanifests.UserCAConfigMap(testNamespace).ObjectMeta,
Data: map[string]string{
"ca-bundle.crt": "-----BEGIN CERTIFICATE-----\ntest-ca-cert\n-----END CERTIFICATE-----\n",
},
},
},
expectConfig: true,
expectCABundle: true,
},
"When AWS platform removes additionalTrustBundle it should remove ca-bundle.pem from cloud-provider-config": {
inputHCP: &hyperv1.HostedControlPlane{
ObjectMeta: metav1.ObjectMeta{
Name: testHCPName,
Namespace: testNamespace,
},
Spec: hyperv1.HostedControlPlaneSpec{
Platform: hyperv1.PlatformSpec{
Type: hyperv1.AWSPlatform,
},
},
},
inputObjects: []client.Object{
&corev1.ConfigMap{
ObjectMeta: cpomanifests.AWSProviderConfig(testNamespace).ObjectMeta,
Data: map[string]string{
"aws.conf": "[Global]\nZone = us-east-1a\n",
},
},
},
expectConfig: true,
expectCABundle: false,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
g := NewGomegaWithT(t)

// For the "remove" test case, pre-populate the guest cluster with a ConfigMap that has ca-bundle.pem
guestObjects := []client.Object{}
if strings.Contains(name, "removes") {
guestObjects = append(guestObjects, &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Namespace: ConfigNamespace,
Name: CloudProviderCMName,
},
Data: map[string]string{
"config": "[Global]\nZone = us-east-1a\n",
"ca-bundle.pem": "old-ca-bundle",
},
})
}

r := &reconciler{
client: fake.NewClientBuilder().WithScheme(api.Scheme).WithObjects(guestObjects...).Build(),
CreateOrUpdateProvider: &simpleCreateOrUpdater{},
cpClient: fake.NewClientBuilder().WithScheme(api.Scheme).WithObjects(append(test.inputObjects, test.inputHCP)...).Build(),
hcpName: testHCPName,
hcpNamespace: testNamespace,
}
err := r.reconcileCloudConfig(t.Context(), test.inputHCP)
g.Expect(err).To(BeNil())

guestCloudConfig := &corev1.ConfigMap{}
err = r.client.Get(t.Context(), types.NamespacedName{
Namespace: ConfigNamespace,
Name: CloudProviderCMName,
}, guestCloudConfig)

if test.expectConfig {
g.Expect(err).To(BeNil())
g.Expect(guestCloudConfig.Data["config"]).To(Equal("[Global]\nZone = us-east-1a\n"))
}

if test.expectCABundle {
g.Expect(guestCloudConfig.Data["ca-bundle.pem"]).To(Equal("-----BEGIN CERTIFICATE-----\ntest-ca-cert\n-----END CERTIFICATE-----\n"))
} else if test.expectConfig {
_, hasCABundle := guestCloudConfig.Data["ca-bundle.pem"]
g.Expect(hasCABundle).To(BeFalse())
}
})
}
}

var _ manifestReconciler = manifestAndReconcile[*rbacv1.ClusterRole]{}

func TestDestroyCloudResources(t *testing.T) {
Expand Down
5 changes: 5 additions & 0 deletions support/globalconfig/infrastructure.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ func ReconcileInfrastructure(infra *configv1.Infrastructure, hcp *hyperv1.Hosted
})
}
infra.Status.PlatformStatus.AWS.ResourceTags = tags
// Set CloudConfig to reference the cloud-provider-config ConfigMap in the guest cluster.
// This allows the kube-cloud-config controller and CCCMO to locate the cloud provider
// configuration, including any additional trust bundles for custom AWS API endpoints.
infra.Spec.CloudConfig.Name = "cloud-provider-config"
infra.Spec.CloudConfig.Key = "config"
case hyperv1.AzurePlatform:
infra.Spec.CloudConfig.Name = "cloud.conf"
if infra.Status.PlatformStatus.Azure == nil {
Expand Down
69 changes: 69 additions & 0 deletions support/globalconfig/infrastructure_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package globalconfig

import (
"testing"

hyperv1 "github.com/openshift/hypershift/api/hypershift/v1beta1"

configv1 "github.com/openshift/api/config/v1"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func TestReconcileInfrastructure_AWSCloudConfig(t *testing.T) {
tests := map[string]struct {
hcp *hyperv1.HostedControlPlane
expectedCloudConfigName string
expectedCloudConfigKey string
}{
"When reconciling AWS infrastructure it should set CloudConfig name and key": {
hcp: &hyperv1.HostedControlPlane{
ObjectMeta: metav1.ObjectMeta{
Name: "cluster1",
Namespace: "master-cluster1",
},
Spec: hyperv1.HostedControlPlaneSpec{
Platform: hyperv1.PlatformSpec{
Type: hyperv1.AWSPlatform,
AWS: &hyperv1.AWSPlatformSpec{
Region: "us-east-1",
},
},
InfraID: "test-infra-id",
},
Status: hyperv1.HostedControlPlaneStatus{
ControlPlaneEndpoint: hyperv1.APIEndpoint{
Host: "api.example.com",
Port: 6443,
},
},
},
expectedCloudConfigName: "cloud-provider-config",
expectedCloudConfigKey: "config",
},
}

for name, test := range tests {
t.Run(name, func(t *testing.T) {
infra := InfrastructureConfig()
ReconcileInfrastructure(infra, test.hcp)

if infra.Spec.CloudConfig.Name != test.expectedCloudConfigName {
t.Errorf("expected CloudConfig.Name = %q, got %q", test.expectedCloudConfigName, infra.Spec.CloudConfig.Name)
}
if infra.Spec.CloudConfig.Key != test.expectedCloudConfigKey {
t.Errorf("expected CloudConfig.Key = %q, got %q", test.expectedCloudConfigKey, infra.Spec.CloudConfig.Key)
}

if infra.Status.PlatformStatus == nil || infra.Status.PlatformStatus.AWS == nil {
t.Fatal("expected AWS PlatformStatus to be set")
}
if infra.Status.PlatformStatus.AWS.Region != "us-east-1" {
t.Errorf("expected AWS region = %q, got %q", "us-east-1", infra.Status.PlatformStatus.AWS.Region)
}
if infra.Spec.PlatformSpec.Type != configv1.AWSPlatformType {
t.Errorf("expected PlatformSpec.Type = %q, got %q", configv1.AWSPlatformType, infra.Spec.PlatformSpec.Type)
}
})
}
}