diff --git a/api/bases/dataplane.openstack.org_openstackdataplanenodesets.yaml b/api/bases/dataplane.openstack.org_openstackdataplanenodesets.yaml index 0c0e51a49..957e82487 100644 --- a/api/bases/dataplane.openstack.org_openstackdataplanenodesets.yaml +++ b/api/bases/dataplane.openstack.org_openstackdataplanenodesets.yaml @@ -1054,6 +1054,8 @@ spec: - subnetName type: object type: array + subscriptionManagerSecret: + type: string userData: properties: name: diff --git a/api/v1beta1/common.go b/api/v1beta1/common.go index 1291890eb..7a0192129 100644 --- a/api/v1beta1/common.go +++ b/api/v1beta1/common.go @@ -87,6 +87,13 @@ type NodeTemplate struct { // +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:io.kubernetes:Secret"} AnsibleSSHPrivateKeySecret string `json:"ansibleSSHPrivateKeySecret"` + // SubscriptionManagerSecret Name of a subscription-manager secret containing + // username and password for registering to node. + // + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:io.kubernetes:Secret"} + SubscriptionManagerSecret string `json:"subscriptionManagerSecret,omitempty"` + // Networks - Instance networks // +kubebuilder:validation:Optional Networks []infranetworkv1.IPSetNetwork `json:"networks,omitempty"` @@ -127,11 +134,13 @@ type AnsibleEESpec struct { // AnsibleSkipTags for ansible execution AnsibleSkipTags string `json:"ansibleSkipTags,omitempty"` // ExtraVars for ansible execution - ExtraVars map[string]json.RawMessage `json:"extraVars,omitempty"` + ExtraVars map[string]json.RawMessage `json:"extraVars,omitempty"` // ExtraMounts containing files which can be mounted into an Ansible Execution Pod ExtraMounts []storage.VolMounts `json:"extraMounts,omitempty"` // Env is a list containing the environment variables to pass to the pod Env []corev1.EnvVar `json:"env,omitempty"` // DNSConfig for setting dnsservers DNSConfig *corev1.PodDNSConfig `json:"dnsConfig,omitempty"` + // SubscriptionManagerSecret for ansible execution + SubscriptionManagerSecret string `json:"subscriptionManagerSecret,omitempty"` } diff --git a/api/v1beta1/openstackdataplanenodeset_types.go b/api/v1beta1/openstackdataplanenodeset_types.go index cdfe84336..7be46d99d 100644 --- a/api/v1beta1/openstackdataplanenodeset_types.go +++ b/api/v1beta1/openstackdataplanenodeset_types.go @@ -175,9 +175,10 @@ func (instance *OpenStackDataPlaneNodeSet) InitConditions() { // GetAnsibleEESpec - get the fields that will be passed to AEE func (instance OpenStackDataPlaneNodeSet) GetAnsibleEESpec() AnsibleEESpec { return AnsibleEESpec{ - NetworkAttachments: instance.Spec.NetworkAttachments, - ExtraMounts: instance.Spec.NodeTemplate.ExtraMounts, - Env: instance.Spec.Env, + NetworkAttachments: instance.Spec.NetworkAttachments, + ExtraMounts: instance.Spec.NodeTemplate.ExtraMounts, + Env: instance.Spec.Env, + SubscriptionManagerSecret: instance.Spec.NodeTemplate.SubscriptionManagerSecret, } } diff --git a/config/crd/bases/dataplane.openstack.org_openstackdataplanenodesets.yaml b/config/crd/bases/dataplane.openstack.org_openstackdataplanenodesets.yaml index 0c0e51a49..957e82487 100644 --- a/config/crd/bases/dataplane.openstack.org_openstackdataplanenodesets.yaml +++ b/config/crd/bases/dataplane.openstack.org_openstackdataplanenodesets.yaml @@ -1054,6 +1054,8 @@ spec: - subnetName type: object type: array + subscriptionManagerSecret: + type: string userData: properties: name: diff --git a/config/manifests/bases/dataplane-operator.clusterserviceversion.yaml b/config/manifests/bases/dataplane-operator.clusterserviceversion.yaml index 5fb714aef..43b5a4f9e 100644 --- a/config/manifests/bases/dataplane-operator.clusterserviceversion.yaml +++ b/config/manifests/bases/dataplane-operator.clusterserviceversion.yaml @@ -49,13 +49,18 @@ spec: path: nodeTemplate.ansibleSSHPrivateKeySecret x-descriptors: - urn:alm:descriptor:io.kubernetes:Secret + - description: SubscriptionManagerSecret Name of a subscription-manager secret + containing username and password for registering to node. + displayName: Subscription Manager Secret + path: nodeTemplate.subscriptionManagerSecret + x-descriptors: + - urn:alm:descriptor:io.kubernetes:Secret - description: AnsiblePort SSH port for Ansible connection displayName: Ansible Port path: nodes.ansible.ansiblePort x-descriptors: - urn:alm:descriptor:com.tectonic.ui:number - - description: PreProvisioned - Whether the nodes are actually pre-provisioned - (True) or should be preprovisioned (False) + - description: PreProvisioned - Set to true if the nodes have been Pre Provisioned. displayName: Pre Provisioned path: preProvisioned x-descriptors: diff --git a/controllers/openstackdataplanenodeset_controller.go b/controllers/openstackdataplanenodeset_controller.go index 179d26318..ff8a532be 100644 --- a/controllers/openstackdataplanenodeset_controller.go +++ b/controllers/openstackdataplanenodeset_controller.go @@ -278,6 +278,37 @@ func (r *OpenStackDataPlaneNodeSetReconciler) Reconcile(ctx context.Context, req return result, err } + if len(instance.Spec.NodeTemplate.SubscriptionManagerSecret) > 0 { + _, result, err = secret.VerifySecret( + ctx, + types.NamespacedName{ + Namespace: instance.Namespace, + Name: instance.Spec.NodeTemplate.SubscriptionManagerSecret, + }, + []string{"username", "password"}, + helper.GetClient(), + time.Second*5, + ) + + if err != nil { + if (result != ctrl.Result{}) { + instance.Status.Conditions.MarkFalse( + condition.InputReadyCondition, + condition.RequestedReason, + condition.SeverityInfo, + dataplanev1.InputReadyWaitingMessage, + "secret/"+instance.Spec.NodeTemplate.SubscriptionManagerSecret) + } else { + instance.Status.Conditions.MarkFalse( + condition.InputReadyCondition, + condition.RequestedReason, + condition.SeverityError, + err.Error()) + } + return result, err + } + } + // all our input checks out so report InputReady instance.Status.Conditions.MarkTrue(condition.InputReadyCondition, condition.InputReadyMessage) diff --git a/docs/assemblies/custom_resources.adoc b/docs/assemblies/custom_resources.adoc index 73d54597b..d95a40870 100644 --- a/docs/assemblies/custom_resources.adoc +++ b/docs/assemblies/custom_resources.adoc @@ -76,6 +76,11 @@ AnsibleEESpec is a specification of the ansible EE attributes | DNSConfig for setting dnsservers | *corev1.PodDNSConfig | false + +| subscriptionManagerSecret +| SubscriptionManagerSecret for ansible execution +| string +| false |=== <> @@ -170,6 +175,11 @@ NodeTemplate is a specification of the node attributes that override top level a | string | true +| subscriptionManagerSecret +| SubscriptionManagerSecret Name of a subscription-manager secret containing username and password for registering to node. https://kubernetes.io/docs/concepts/configuration/secret/#basic-authentication-secret +| string +| false + | networks | Networks - Instance networks | []infranetworkv1.IPSetNetwork diff --git a/pkg/util/ansible_execution.go b/pkg/util/ansible_execution.go index ac1027bc5..a267dec15 100644 --- a/pkg/util/ansible_execution.go +++ b/pkg/util/ansible_execution.go @@ -147,6 +147,46 @@ func AnsibleExecution( ansibleEE.Spec.ExtraMounts = append(aeeSpec.ExtraMounts, []storage.VolMounts{ansibleEEMounts}...) ansibleEE.Spec.Env = aeeSpec.Env + if service.Name == "bootstrap" && len(aeeSpec.SubscriptionManagerSecret) > 0 { + // Adding an InitContainer to execute `subscription-manager register` + // without exposing the password at `edpm_bootstrap_command` + ansibleEE.Spec.InitContainers = []corev1.Container{{ + ImagePullPolicy: "Always", + Image: ansibleEE.Spec.Image, + Name: "subscription", + Env: []corev1.EnvVar{{ + Name: "SECRET_USERNAME", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: aeeSpec.SubscriptionManagerSecret, + }, + Key: "username", + }, + }, + }, + { + Name: "SECRET_PASSWORD", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: aeeSpec.SubscriptionManagerSecret, + }, + Key: "password", + }, + }, + }, + { + Name: "RUNNER_PLAYBOOK", + Value: SubscriptionPlay, + }, + }, + Args: []string{"ansible-runner", "run", "/runner", "-p", "playbook.yaml"}, + VolumeMounts: ansibleEEMounts.Mounts, + }} + + } + err := controllerutil.SetControllerReference(obj, ansibleEE, helper.GetScheme()) if err != nil { return err diff --git a/pkg/util/const.go b/pkg/util/const.go index 8987f4751..6f9598378 100644 --- a/pkg/util/const.go +++ b/pkg/util/const.go @@ -19,4 +19,16 @@ package util const ( // AnsibleExecutionServiceNameLen max length for the ansibleEE service name prefix AnsibleExecutionServiceNameLen = 53 + SubscriptionPlay = ` +- hosts: all + strategy: linear + tasks: + - name: subscription-manager register + become: true + no_log: true + when: ansible_facts.distribution == 'RedHat' + ansible.builtin.shell: | + set -euxo pipefail + subscription-manager register --username {{ lookup('ansible.builtin.env', 'SECRET_USERNAME') }} --password {{ lookup('ansible.builtin.env', 'SECRET_PASSWORD') }} +` )