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()) + }) + }) })