diff --git a/Makefile b/Makefile index 20d2ab57c..32e74e3b4 100644 --- a/Makefile +++ b/Makefile @@ -343,6 +343,10 @@ dev-vsphere-creds: envsubst dev-eks-creds: dev-aws-creds +.PHONY: dev-openstack-creds +dev-openstack-creds: envsubst + @NAMESPACE=$(NAMESPACE) $(ENVSUBST) -no-unset -i config/dev/openstack-credentials.yaml | $(KUBECTL) apply -f - + .PHONY: dev-apply ## Apply the development environment by deploying the kind cluster, local registry and the HMC helm chart. dev-apply: kind-deploy registry-deploy dev-push dev-deploy dev-templates dev-release diff --git a/config/dev/openstack-credentials.yaml b/config/dev/openstack-credentials.yaml new file mode 100644 index 000000000..5664e9539 --- /dev/null +++ b/config/dev/openstack-credentials.yaml @@ -0,0 +1,33 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: openstack-cloud-config + namespace: ${NAMESPACE} +stringData: + clouds.yaml: | + clouds: + openstack: + auth: + auth_url: ${OS_AUTH_URL} + username: ${OS_USERNAME} + password: ${OS_PASSWORD} + project_id: ${OS_PROJECT_ID} + project_name: ${OS_PROJECT_NAME} + user_domain_name: ${OS_USER_DOMAIN_NAME} + region_name: ${OS_REGION_NAME} + interface: ${OS_INTERFACE} + identity_api_version: ${OS_IDENTITY_API_VERSION} +--- +apiVersion: hmc.mirantis.com/v1alpha1 +kind: Credential +metadata: + name: openstack-cluster-identity-cred + namespace: ${NAMESPACE} +spec: + description: OpenStack credentials + identityRef: + apiVersion: v1 + kind: Secret + name: openstack-cloud-config + namespace: ${NAMESPACE} diff --git a/internal/controller/managedcluster_controller.go b/internal/controller/managedcluster_controller.go index e849822f3..c1891db78 100644 --- a/internal/controller/managedcluster_controller.go +++ b/internal/controller/managedcluster_controller.go @@ -761,6 +761,25 @@ func (r *ManagedClusterReconciler) reconcileCredentialPropagation(ctx context.Co Reason: hmc.SucceededReason, Message: "vSphere CCM credentials created", }) + case "openstack": + l.Info("OpenStack creds propagation start") + if err := credspropagation.PropagateOpenStackSecrets(ctx, propnCfg); err != nil { + errMsg := fmt.Sprintf("failed to create OpenStack CCM credentials: %s", err) + apimeta.SetStatusCondition(managedCluster.GetConditions(), metav1.Condition{ + Type: hmc.CredentialsPropagatedCondition, + Status: metav1.ConditionFalse, + Reason: hmc.FailedReason, + Message: errMsg, + }) + return errors.New(errMsg) + } + + apimeta.SetStatusCondition(managedCluster.GetConditions(), metav1.Condition{ + Type: hmc.CredentialsPropagatedCondition, + Status: metav1.ConditionTrue, + Reason: hmc.SucceededReason, + Message: "OpenStack CCM credentials created", + }) default: apimeta.SetStatusCondition(managedCluster.GetConditions(), metav1.Condition{ Type: hmc.CredentialsPropagatedCondition, diff --git a/internal/credspropagation/openstack.go b/internal/credspropagation/openstack.go new file mode 100644 index 000000000..bdb0a2f95 --- /dev/null +++ b/internal/credspropagation/openstack.go @@ -0,0 +1,76 @@ +// Copyright 2024 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package credspropagation + +import ( + "context" + "fmt" + + hmc "github.com/Mirantis/hmc/api/v1alpha1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func PropagateOpenStackSecrets(ctx context.Context, cfg *PropagationCfg) error { + openstackManagedCluster := &hmc.ManagedCluster{} + if err := cfg.Client.Get(ctx, client.ObjectKey{ + Name: cfg.ManagedCluster.Name, + Namespace: cfg.ManagedCluster.Namespace, + }, openstackManagedCluster); err != nil { + return fmt.Errorf("failed to get ManagedCluster %s: %w", cfg.ManagedCluster.Name, err) + } + + openstackCredential := &hmc.Credential{} + if err := cfg.Client.Get(ctx, client.ObjectKey{ + Name: openstackManagedCluster.Spec.Credential, + Namespace: openstackManagedCluster.Namespace, + }, openstackCredential); err != nil { + return fmt.Errorf("failed to get OpenStackCredential %s: %w", cfg.ManagedCluster.Spec.Credential, err) + } + + // Fetch the secret containing OpenStack credentials + openstackSecret := &corev1.Secret{} + openstackSecretName := openstackCredential.Spec.IdentityRef.Name + openstackSecretNamespace := openstackCredential.Spec.IdentityRef.Namespace + if err := cfg.Client.Get(ctx, client.ObjectKey{ + Name: openstackSecretName, + Namespace: openstackSecretNamespace, + }, openstackSecret); err != nil { + return fmt.Errorf("failed to get OpenStack secret %s: %w", openstackSecretName, err) + } + + // Generate CCM secret + ccmSecret, err := generateOpenStackCCMSecret(openstackSecret) + if err != nil { + return fmt.Errorf("failed to generate OpenStack CCM secret: %s", err) + } + + // Apply CCM config + if err := applyCCMConfigs(ctx, cfg.KubeconfSecret, ccmSecret); err != nil { + return fmt.Errorf("failed to apply OpenStack CCM secret: %s", err) + } + + return nil +} + +func generateOpenStackCCMSecret(openstackSecret *corev1.Secret) (*corev1.Secret, error) { + // Use the data from the fetched secret + secretData := map[string][]byte{ + "clouds.yaml": openstackSecret.Data["clouds.yaml"], + } + + return makeSecret("openstack-cloud-config", metav1.NamespaceSystem, secretData), nil +} diff --git a/internal/webhook/managedcluster_webhook.go b/internal/webhook/managedcluster_webhook.go index 71603351f..ce782ae24 100644 --- a/internal/webhook/managedcluster_webhook.go +++ b/internal/webhook/managedcluster_webhook.go @@ -284,6 +284,10 @@ func isCredMatchTemplate(cred *hmcv1alpha1.Credential, template *hmcv1alpha1.Clu if idtyKind != "VSphereClusterIdentity" { return errMsg(provider) } + case "infrastructure-openstack": + if idtyKind != "Secret" { + return errMsg(provider) + } default: if strings.HasPrefix(provider, "infrastructure-") { return fmt.Errorf("unsupported infrastructure provider %s", provider)