From 0822da45b0c9df44cb7ac78d27c4549522e4ffb4 Mon Sep 17 00:00:00 2001 From: oliashish Date: Thu, 22 Jan 2026 08:18:08 -0500 Subject: [PATCH] Fix: added customizable ansibleEE env ConfigMap name Currently there is only one hardcoded string `openstack-aee-default-env` for ansibleEE environment variable, which is applied throughout the cluser. This PR tries to let users ability to customize the ansibleEE env configMap for deployments and nodeset levels individually. The precedence order for ansibleEEEnvConfigMap is deployment > nodeset > default. --- ...ack.org_openstackdataplanedeployments.yaml | 9 ++ ...nstack.org_openstackdataplanenodesets.yaml | 12 ++ api/dataplane/v1beta1/common.go | 3 + .../openstackdataplanedeployment_types.go | 9 ++ .../openstackdataplanenodeset_types.go | 21 ++- bindata/crds/crds.yaml | 21 +++ ...ack.org_openstackdataplanedeployments.yaml | 9 ++ ...nstack.org_openstackdataplanenodesets.yaml | 12 ++ ...openstackdataplanedeployment_controller.go | 4 + internal/dataplane/util/ansible_execution.go | 9 +- ...enstackdataplanenodeset_controller_test.go | 143 ++++++++++++++++++ 11 files changed, 247 insertions(+), 5 deletions(-) diff --git a/api/bases/dataplane.openstack.org_openstackdataplanedeployments.yaml b/api/bases/dataplane.openstack.org_openstackdataplanedeployments.yaml index b9f4d61bb..d79059895 100644 --- a/api/bases/dataplane.openstack.org_openstackdataplanedeployments.yaml +++ b/api/bases/dataplane.openstack.org_openstackdataplanedeployments.yaml @@ -59,6 +59,15 @@ spec: description: OpenStackDataPlaneDeploymentSpec defines the desired state of OpenStackDataPlaneDeployment properties: + ansibleEEEnvConfigMapName: + description: |- + AnsibleEEEnvConfigMapName is the name of the ConfigMap containing environment + variables to inject into the Ansible Execution Environment pod. + If set, this takes precedence over the NodeSet's AnsibleEEEnvConfigMapName. + If not specified, the NodeSet's AnsibleEEEnvConfigMapName is used, or the + default "openstack-aee-default-env" if neither is set. + maxLength: 253 + type: string ansibleExtraVars: description: AnsibleExtraVars for ansible execution x-kubernetes-preserve-unknown-fields: true diff --git a/api/bases/dataplane.openstack.org_openstackdataplanenodesets.yaml b/api/bases/dataplane.openstack.org_openstackdataplanenodesets.yaml index 5eead8763..056566192 100644 --- a/api/bases/dataplane.openstack.org_openstackdataplanenodesets.yaml +++ b/api/bases/dataplane.openstack.org_openstackdataplanenodesets.yaml @@ -55,6 +55,18 @@ spec: description: OpenStackDataPlaneNodeSetSpec defines the desired state of OpenStackDataPlaneNodeSet properties: + ansibleEEEnvConfigMapName: + default: openstack-aee-default-env + description: |- + AnsibleEEEnvConfigMapName is the name of the ConfigMap containing environment + variables to inject into the Ansible Execution Environment pod. + The ConfigMap should contain environment variables used by ansible-runner + such as ANSIBLE_VERBOSITY, ANSIBLE_FORCE_COLOR, or other runner settings. + See https://ansible.readthedocs.io/projects/runner/en/stable/intro/#env-settings-settings-for-runner-itself + If not specified, defaults to "openstack-aee-default-env". + This can be overridden at the Deployment level. + maxLength: 253 + type: string baremetalSetTemplate: description: BaremetalSetTemplate Template for BaremetalSet for the NodeSet diff --git a/api/dataplane/v1beta1/common.go b/api/dataplane/v1beta1/common.go index 57e558a07..79476f60a 100644 --- a/api/dataplane/v1beta1/common.go +++ b/api/dataplane/v1beta1/common.go @@ -198,4 +198,7 @@ type AnsibleEESpec struct { // the ansible execution run with. Without specifying, it will run with // default serviceaccount ServiceAccountName string + // AnsibleEEEnvConfigMapName is the name of ConfigMap containing environment + // variables to inject to the Ansible execution environment pod. + AnsibleEEEnvConfigMapName string `json:"ansibleEEEnvConfigMapName,omitempty"` } diff --git a/api/dataplane/v1beta1/openstackdataplanedeployment_types.go b/api/dataplane/v1beta1/openstackdataplanedeployment_types.go index ed77051e1..a57b2139a 100644 --- a/api/dataplane/v1beta1/openstackdataplanedeployment_types.go +++ b/api/dataplane/v1beta1/openstackdataplanedeployment_types.go @@ -71,6 +71,15 @@ type OpenStackDataPlaneDeploymentSpec struct { // +kubebuilder:validation:Optional // AnsibleJobNodeSelector to target subset of worker nodes running the ansible jobs AnsibleJobNodeSelector map[string]string `json:"ansibleJobNodeSelector,omitempty"` + + // +kubebuilder:validation:Optional + // +kubebuilder:validation:MaxLength:=253 + // AnsibleEEEnvConfigMapName is the name of the ConfigMap containing environment + // variables to inject into the Ansible Execution Environment pod. + // If set, this takes precedence over the NodeSet's AnsibleEEEnvConfigMapName. + // If not specified, the NodeSet's AnsibleEEEnvConfigMapName is used, or the + // default "openstack-aee-default-env" if neither is set. + AnsibleEEEnvConfigMapName string `json:"ansibleEEEnvConfigMapName,omitempty"` } // OpenStackDataPlaneDeploymentStatus defines the observed state of OpenStackDataPlaneDeployment diff --git a/api/dataplane/v1beta1/openstackdataplanenodeset_types.go b/api/dataplane/v1beta1/openstackdataplanenodeset_types.go index 6731593ae..91eafbbf5 100644 --- a/api/dataplane/v1beta1/openstackdataplanenodeset_types.go +++ b/api/dataplane/v1beta1/openstackdataplanenodeset_types.go @@ -86,6 +86,18 @@ type OpenStackDataPlaneNodeSetSpec struct { // +kubebuilder:default=true // +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:com.tectonic.ui:booleanSwitch"} TLSEnabled bool `json:"tlsEnabled" yaml:"tlsEnabled"` + + // +kubebuilder:validation:Optional + // +kubebuilder:default="openstack-aee-default-env" + // +kubebuilder:validation:MaxLength:=253 + // AnsibleEEEnvConfigMapName is the name of the ConfigMap containing environment + // variables to inject into the Ansible Execution Environment pod. + // The ConfigMap should contain environment variables used by ansible-runner + // such as ANSIBLE_VERBOSITY, ANSIBLE_FORCE_COLOR, or other runner settings. + // See https://ansible.readthedocs.io/projects/runner/en/stable/intro/#env-settings-settings-for-runner-itself + // If not specified, defaults to "openstack-aee-default-env". + // This can be overridden at the Deployment level. + AnsibleEEEnvConfigMapName string `json:"ansibleEEEnvConfigMapName,omitempty"` } // +kubebuilder:object:root=true @@ -205,10 +217,11 @@ func (instance *OpenStackDataPlaneNodeSet) InitConditions() { // GetAnsibleEESpec - get the fields that will be passed to AEE Job func (instance OpenStackDataPlaneNodeSet) GetAnsibleEESpec() AnsibleEESpec { return AnsibleEESpec{ - NetworkAttachments: instance.Spec.NetworkAttachments, - ExtraMounts: instance.Spec.NodeTemplate.ExtraMounts, - Env: instance.Spec.Env, - ServiceAccountName: instance.Name, + NetworkAttachments: instance.Spec.NetworkAttachments, + ExtraMounts: instance.Spec.NodeTemplate.ExtraMounts, + Env: instance.Spec.Env, + ServiceAccountName: instance.Name, + AnsibleEEEnvConfigMapName: instance.Spec.AnsibleEEEnvConfigMapName, } } diff --git a/bindata/crds/crds.yaml b/bindata/crds/crds.yaml index c37c9e9d9..5eee5f6c4 100644 --- a/bindata/crds/crds.yaml +++ b/bindata/crds/crds.yaml @@ -17144,6 +17144,15 @@ spec: description: OpenStackDataPlaneDeploymentSpec defines the desired state of OpenStackDataPlaneDeployment properties: + ansibleEEEnvConfigMapName: + description: |- + AnsibleEEEnvConfigMapName is the name of the ConfigMap containing environment + variables to inject into the Ansible Execution Environment pod. + If set, this takes precedence over the NodeSet's AnsibleEEEnvConfigMapName. + If not specified, the NodeSet's AnsibleEEEnvConfigMapName is used, or the + default "openstack-aee-default-env" if neither is set. + maxLength: 253 + type: string ansibleExtraVars: description: AnsibleExtraVars for ansible execution x-kubernetes-preserve-unknown-fields: true @@ -17399,6 +17408,18 @@ spec: description: OpenStackDataPlaneNodeSetSpec defines the desired state of OpenStackDataPlaneNodeSet properties: + ansibleEEEnvConfigMapName: + default: openstack-aee-default-env + description: |- + AnsibleEEEnvConfigMapName is the name of the ConfigMap containing environment + variables to inject into the Ansible Execution Environment pod. + The ConfigMap should contain environment variables used by ansible-runner + such as ANSIBLE_VERBOSITY, ANSIBLE_FORCE_COLOR, or other runner settings. + See https://ansible.readthedocs.io/projects/runner/en/stable/intro/#env-settings-settings-for-runner-itself + If not specified, defaults to "openstack-aee-default-env". + This can be overridden at the Deployment level. + maxLength: 253 + type: string baremetalSetTemplate: description: BaremetalSetTemplate Template for BaremetalSet for the NodeSet diff --git a/config/crd/bases/dataplane.openstack.org_openstackdataplanedeployments.yaml b/config/crd/bases/dataplane.openstack.org_openstackdataplanedeployments.yaml index b9f4d61bb..d79059895 100644 --- a/config/crd/bases/dataplane.openstack.org_openstackdataplanedeployments.yaml +++ b/config/crd/bases/dataplane.openstack.org_openstackdataplanedeployments.yaml @@ -59,6 +59,15 @@ spec: description: OpenStackDataPlaneDeploymentSpec defines the desired state of OpenStackDataPlaneDeployment properties: + ansibleEEEnvConfigMapName: + description: |- + AnsibleEEEnvConfigMapName is the name of the ConfigMap containing environment + variables to inject into the Ansible Execution Environment pod. + If set, this takes precedence over the NodeSet's AnsibleEEEnvConfigMapName. + If not specified, the NodeSet's AnsibleEEEnvConfigMapName is used, or the + default "openstack-aee-default-env" if neither is set. + maxLength: 253 + type: string ansibleExtraVars: description: AnsibleExtraVars for ansible execution x-kubernetes-preserve-unknown-fields: true diff --git a/config/crd/bases/dataplane.openstack.org_openstackdataplanenodesets.yaml b/config/crd/bases/dataplane.openstack.org_openstackdataplanenodesets.yaml index 5eead8763..056566192 100644 --- a/config/crd/bases/dataplane.openstack.org_openstackdataplanenodesets.yaml +++ b/config/crd/bases/dataplane.openstack.org_openstackdataplanenodesets.yaml @@ -55,6 +55,18 @@ spec: description: OpenStackDataPlaneNodeSetSpec defines the desired state of OpenStackDataPlaneNodeSet properties: + ansibleEEEnvConfigMapName: + default: openstack-aee-default-env + description: |- + AnsibleEEEnvConfigMapName is the name of the ConfigMap containing environment + variables to inject into the Ansible Execution Environment pod. + The ConfigMap should contain environment variables used by ansible-runner + such as ANSIBLE_VERBOSITY, ANSIBLE_FORCE_COLOR, or other runner settings. + See https://ansible.readthedocs.io/projects/runner/en/stable/intro/#env-settings-settings-for-runner-itself + If not specified, defaults to "openstack-aee-default-env". + This can be overridden at the Deployment level. + maxLength: 253 + type: string baremetalSetTemplate: description: BaremetalSetTemplate Template for BaremetalSet for the NodeSet diff --git a/internal/controller/dataplane/openstackdataplanedeployment_controller.go b/internal/controller/dataplane/openstackdataplanedeployment_controller.go index c690ae6b7..eecca0032 100644 --- a/internal/controller/dataplane/openstackdataplanedeployment_controller.go +++ b/internal/controller/dataplane/openstackdataplanedeployment_controller.go @@ -288,6 +288,10 @@ func (r *OpenStackDataPlaneDeploymentReconciler) Reconcile(ctx context.Context, ansibleEESpec.AnsibleSkipTags = instance.Spec.AnsibleSkipTags ansibleEESpec.AnsibleLimit = instance.Spec.AnsibleLimit ansibleEESpec.ExtraVars = instance.Spec.AnsibleExtraVars + // override the ansibleEEEnvConfigMapName on nodeset if there is any provided for a specific deployment. + if instance.Spec.AnsibleEEEnvConfigMapName != "" { + ansibleEESpec.AnsibleEEEnvConfigMapName = instance.Spec.AnsibleEEEnvConfigMapName + } if nodeSet.Status.DNSClusterAddresses != nil && nodeSet.Status.CtlplaneSearchDomain != "" { ansibleEESpec.DNSConfig = &corev1.PodDNSConfig{ diff --git a/internal/dataplane/util/ansible_execution.go b/internal/dataplane/util/ansible_execution.go index a5addc974..b7b41b9d3 100644 --- a/internal/dataplane/util/ansible_execution.go +++ b/internal/dataplane/util/ansible_execution.go @@ -65,11 +65,18 @@ func AnsibleExecution( return nil } + // Fallback for backwards compatibility with existing NodeSets created + // before ansibleEEEnvConfigMapName field was added + envConfigMapName := aeeSpec.AnsibleEEEnvConfigMapName + if envConfigMapName == "" { + envConfigMapName = "openstack-aee-default-env" + } + ansibleEE := EEJob{ Name: executionName, Namespace: deployment.GetNamespace(), Labels: labels, - EnvConfigMapName: "openstack-aee-default-env", + EnvConfigMapName: envConfigMapName, } ansibleEE.NetworkAttachments = aeeSpec.NetworkAttachments diff --git a/test/functional/dataplane/openstackdataplanenodeset_controller_test.go b/test/functional/dataplane/openstackdataplanenodeset_controller_test.go index 2cc3648d8..73c655a91 100644 --- a/test/functional/dataplane/openstackdataplanenodeset_controller_test.go +++ b/test/functional/dataplane/openstackdataplanenodeset_controller_test.go @@ -1811,4 +1811,147 @@ var _ = Describe("Dataplane NodeSet Test", func() { }) }) }) + + When("A NodeSet is created without specifying ansibleEEEnvConfigMapName", func() { + BeforeEach(func() { + nodeSetSpec := DefaultDataPlaneNodeSetSpec("edpm-compute") + nodeSetSpec["preProvisioned"] = true + nodeSetSpec["services"] = []string{"bootstrap"} + // NOT setting ansibleEEEnvConfigMapName - should use default + + DeferCleanup(th.DeleteInstance, CreateNetConfig(dataplaneNetConfigName, DefaultNetConfigSpec())) + DeferCleanup(th.DeleteInstance, CreateDNSMasq(dnsMasqName, DefaultDNSMasqSpec())) + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(dataplaneNodeSetName, nodeSetSpec)) + DeferCleanup(th.DeleteInstance, CreateDataplaneDeployment(dataplaneDeploymentName, DefaultDataPlaneDeploymentSpec())) + CreateSSHSecret(dataplaneSSHSecretName) + CreateCABundleSecret(caBundleSecretName) + SimulateDNSMasqComplete(dnsMasqName) + SimulateIPSetComplete(dataplaneNodeName) + SimulateDNSDataComplete(dataplaneNodeSetName) + }) + + It("Should use default openstack-aee-default-env ConfigMap in the Job", func() { + Eventually(func(g Gomega) { + ansibleeeName := types.NamespacedName{ + Name: fmt.Sprintf("bootstrap-%s-%s", dataplaneDeploymentName.Name, dataplaneNodeSetName.Name), + Namespace: namespace, + } + ansibleEE := GetAnsibleee(ansibleeeName) + + // Verify EnvFrom references the default ConfigMap + g.Expect(ansibleEE.Spec.Template.Spec.Containers[0].EnvFrom).To(HaveLen(1)) + g.Expect(ansibleEE.Spec.Template.Spec.Containers[0].EnvFrom[0].ConfigMapRef).NotTo(BeNil()) + g.Expect(ansibleEE.Spec.Template.Spec.Containers[0].EnvFrom[0].ConfigMapRef.LocalObjectReference.Name).To(Equal("openstack-aee-default-env")) + g.Expect(*ansibleEE.Spec.Template.Spec.Containers[0].EnvFrom[0].ConfigMapRef.Optional).To(BeTrue()) + }, th.Timeout, th.Interval).Should(Succeed()) + }) + }) + + When("A NodeSet is created with custom ansibleEEEnvConfigMapName", func() { + BeforeEach(func() { + nodeSetSpec := DefaultDataPlaneNodeSetSpec("edpm-compute") + nodeSetSpec["preProvisioned"] = true + nodeSetSpec["services"] = []string{"bootstrap"} + nodeSetSpec["ansibleEEEnvConfigMapName"] = "custom-ansible-env" + + DeferCleanup(th.DeleteInstance, CreateNetConfig(dataplaneNetConfigName, DefaultNetConfigSpec())) + DeferCleanup(th.DeleteInstance, CreateDNSMasq(dnsMasqName, DefaultDNSMasqSpec())) + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(dataplaneNodeSetName, nodeSetSpec)) + DeferCleanup(th.DeleteInstance, CreateDataplaneDeployment(dataplaneDeploymentName, DefaultDataPlaneDeploymentSpec())) + CreateSSHSecret(dataplaneSSHSecretName) + CreateCABundleSecret(caBundleSecretName) + SimulateDNSMasqComplete(dnsMasqName) + SimulateIPSetComplete(dataplaneNodeName) + SimulateDNSDataComplete(dataplaneNodeSetName) + }) + + It("Should use custom ConfigMap name from NodeSet in the Job", func() { + Eventually(func(g Gomega) { + ansibleeeName := types.NamespacedName{ + Name: fmt.Sprintf("bootstrap-%s-%s", dataplaneDeploymentName.Name, dataplaneNodeSetName.Name), + Namespace: namespace, + } + ansibleEE := GetAnsibleee(ansibleeeName) + + // Verify EnvFrom references the custom ConfigMap from NodeSet + g.Expect(ansibleEE.Spec.Template.Spec.Containers[0].EnvFrom).To(HaveLen(1)) + g.Expect(ansibleEE.Spec.Template.Spec.Containers[0].EnvFrom[0].ConfigMapRef).NotTo(BeNil()) + g.Expect(ansibleEE.Spec.Template.Spec.Containers[0].EnvFrom[0].ConfigMapRef.LocalObjectReference.Name).To(Equal("custom-ansible-env")) + }, th.Timeout, th.Interval).Should(Succeed()) + }) + }) + + When("A Deployment overrides NodeSet's ansibleEEEnvConfigMapName", func() { + BeforeEach(func() { + nodeSetSpec := DefaultDataPlaneNodeSetSpec("edpm-compute") + nodeSetSpec["preProvisioned"] = true + nodeSetSpec["services"] = []string{"bootstrap"} + nodeSetSpec["ansibleEEEnvConfigMapName"] = "nodeset-ansible-env" + + deploymentSpec := DefaultDataPlaneDeploymentSpec() + deploymentSpec["ansibleEEEnvConfigMapName"] = "deployment-override-env" + + DeferCleanup(th.DeleteInstance, CreateNetConfig(dataplaneNetConfigName, DefaultNetConfigSpec())) + DeferCleanup(th.DeleteInstance, CreateDNSMasq(dnsMasqName, DefaultDNSMasqSpec())) + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(dataplaneNodeSetName, nodeSetSpec)) + DeferCleanup(th.DeleteInstance, CreateDataplaneDeployment(dataplaneDeploymentName, deploymentSpec)) + CreateSSHSecret(dataplaneSSHSecretName) + CreateCABundleSecret(caBundleSecretName) + SimulateDNSMasqComplete(dnsMasqName) + SimulateIPSetComplete(dataplaneNodeName) + SimulateDNSDataComplete(dataplaneNodeSetName) + }) + + It("Should use Deployment's ConfigMap name instead of NodeSet's", func() { + Eventually(func(g Gomega) { + ansibleeeName := types.NamespacedName{ + Name: fmt.Sprintf("bootstrap-%s-%s", dataplaneDeploymentName.Name, dataplaneNodeSetName.Name), + Namespace: namespace, + } + ansibleEE := GetAnsibleee(ansibleeeName) + + // Verify EnvFrom references the Deployment's ConfigMap (override) + g.Expect(ansibleEE.Spec.Template.Spec.Containers[0].EnvFrom).To(HaveLen(1)) + g.Expect(ansibleEE.Spec.Template.Spec.Containers[0].EnvFrom[0].ConfigMapRef).NotTo(BeNil()) + g.Expect(ansibleEE.Spec.Template.Spec.Containers[0].EnvFrom[0].ConfigMapRef.LocalObjectReference.Name).To(Equal("deployment-override-env")) + }, th.Timeout, th.Interval).Should(Succeed()) + }) + }) + + When("A Deployment without ansibleEEEnvConfigMapName uses NodeSet's value", func() { + BeforeEach(func() { + nodeSetSpec := DefaultDataPlaneNodeSetSpec("edpm-compute") + nodeSetSpec["preProvisioned"] = true + nodeSetSpec["services"] = []string{"bootstrap"} + nodeSetSpec["ansibleEEEnvConfigMapName"] = "nodeset-specific-env" + + // Deployment does NOT set ansibleEEEnvConfigMapName + deploymentSpec := DefaultDataPlaneDeploymentSpec() + + DeferCleanup(th.DeleteInstance, CreateNetConfig(dataplaneNetConfigName, DefaultNetConfigSpec())) + DeferCleanup(th.DeleteInstance, CreateDNSMasq(dnsMasqName, DefaultDNSMasqSpec())) + DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(dataplaneNodeSetName, nodeSetSpec)) + DeferCleanup(th.DeleteInstance, CreateDataplaneDeployment(dataplaneDeploymentName, deploymentSpec)) + CreateSSHSecret(dataplaneSSHSecretName) + CreateCABundleSecret(caBundleSecretName) + SimulateDNSMasqComplete(dnsMasqName) + SimulateIPSetComplete(dataplaneNodeName) + SimulateDNSDataComplete(dataplaneNodeSetName) + }) + + It("Should use NodeSet's ConfigMap name when Deployment doesn't specify one", func() { + Eventually(func(g Gomega) { + ansibleeeName := types.NamespacedName{ + Name: fmt.Sprintf("bootstrap-%s-%s", dataplaneDeploymentName.Name, dataplaneNodeSetName.Name), + Namespace: namespace, + } + ansibleEE := GetAnsibleee(ansibleeeName) + + // Verify EnvFrom references NodeSet's ConfigMap (no override) + g.Expect(ansibleEE.Spec.Template.Spec.Containers[0].EnvFrom).To(HaveLen(1)) + g.Expect(ansibleEE.Spec.Template.Spec.Containers[0].EnvFrom[0].ConfigMapRef).NotTo(BeNil()) + g.Expect(ansibleEE.Spec.Template.Spec.Containers[0].EnvFrom[0].ConfigMapRef.LocalObjectReference.Name).To(Equal("nodeset-specific-env")) + }, th.Timeout, th.Interval).Should(Succeed()) + }) + }) })