diff --git a/.github/workflows/build-container-images.yaml b/.github/workflows/build-container-images.yaml index f7b434490..b7c4afd33 100644 --- a/.github/workflows/build-container-images.yaml +++ b/.github/workflows/build-container-images.yaml @@ -20,7 +20,7 @@ on: env: VERSION_PYTHON311: 0.0.1 VERSION_PYTHON312: 0.0.1 - VERSION_PYTHON_IRONIC: 0.0.2 + VERSION_PYTHON_IRONIC: 0.0.3 VERSION_ARGO_UTILS: 0.0.1 VERSION_OBM_UTILS: 0.0.1 VERSION_PYTHON_NAUTOBOT_INT_SYNC: 0.0.1 diff --git a/argo-workflows/ironic-nautobot-sync/code/synchronize-obm-creds.py b/argo-workflows/ironic-nautobot-sync/code/synchronize-obm-creds.py new file mode 100644 index 000000000..2f963b2bd --- /dev/null +++ b/argo-workflows/ironic-nautobot-sync/code/synchronize-obm-creds.py @@ -0,0 +1,100 @@ +import json +import logging +import sys + +import ironicclient.common.apiclient.exceptions + + +from ironic.client import IronicClient +from node_configuration import IronicNodeConfiguration +from ironic.secrets import read_secret + +logging.basicConfig(format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.DEBUG) +logger = logging.getLogger(__name__) + +if len(sys.argv) < 1: + raise ValueError("Please provide node configuration in JSON format as first argument.") + +logger.info("Pushing device new node to Ironic.") +client = IronicClient( + svc_url=read_secret("IRONIC_SVC_URL"), + username=read_secret("IRONIC_USERNAME"), + password=read_secret("IRONIC_PASSWORD"), + auth_url=read_secret("IRONIC_AUTH_URL"), + tenant_name=read_secret("IRONIC_TENANT"), +) + + +def event_to_node_configuration(event: dict) -> IronicNodeConfiguration: + node_config = IronicNodeConfiguration() + node_config.conductor_group = None + node_config.driver = "redfish" + + node_config.chassis_uuid = None + node_config.uuid = event["device"]["id"] + node_config.name = event["device"]["name"] + + return node_config + + +interface_update_event = json.loads(sys.argv[1]) +logger.debug(f"Received: {interface_update_event}") +update_data = interface_update_event["data"] + +node_id = update_data["device"]["id"] +logger.debug(f"Checking if node with UUID: {node_id} exists in Ironic.") + +try: + ironic_node = client.get_node(node_id) +except ironicclient.common.apiclient.exceptions.NotFound: + logger.debug(f"Node: {node_id} not found in Ironic.") + ironic_node = None + sys.exit(1) + +STATES_ALLOWING_UPDATES = ["enroll"] +if ironic_node.provision_state not in STATES_ALLOWING_UPDATES: + logger.info( + f"Device {node_id} is in a {ironic_node.provision_state} provisioning state, so the updates are not allowed." + ) + sys.exit(0) + + +def credential_secrets(): + """ + Returns name of the Kubernetes Secret used to store OBM credentials for server node_id + """ + username = None + password = None + with open("/etc/obm/username") as f: + # strip leading and trailing whitespace + username = f.read().strip() + + with open("/etc/obm/password") as f: + # strip leading and trailing whitespace + password = f.read().strip() + + return [username, password] + +def replace_or_add_field(path, current_val, expected_val): + if current_val == expected_val: + return None + if current_val is None: + return {"op": "add", "path": path, "value": expected_val} + else: + return {"op": "replace", "path": path, "value": expected_val} + +# Update OBM credentials +expected_username, expected_password = credential_secrets() + +current_username = ironic_node.driver_info.get("redfish_username", None) +current_password_is_set = ironic_node.driver_info.get("redfish_password", None) + +patches = [ + replace_or_add_field('/driver_info/redfish_username', current_username, expected_username), + replace_or_add_field('/driver_info/redfish_password', current_password_is_set, expected_password), +] +patches = [p for p in patches if p is not None] + +response = client.update_node(node_id, patches) +logger.info(f"Patching: {patches}") +logger.info(f"Updated: {response}") diff --git a/argo-workflows/ironic-nautobot-sync/sensors/nautobot-interface-update-sensor.yaml b/argo-workflows/ironic-nautobot-sync/sensors/nautobot-interface-update-sensor.yaml index 0419a323b..509faa004 100644 --- a/argo-workflows/ironic-nautobot-sync/sensors/nautobot-interface-update-sensor.yaml +++ b/argo-workflows/ironic-nautobot-sync/sensors/nautobot-interface-update-sensor.yaml @@ -44,6 +44,10 @@ spec: src: dataKey: body dependencyName: nautobot-dep + - dest: spec.arguments.parameters.1.value + src: + dataKey: body.data.device.name + dependencyName: nautobot-dep source: resource: apiVersion: argoproj.io/v1alpha1 @@ -55,8 +59,10 @@ spec: parameters: - name: interface_update_event value: Some nautobot interface has changed + - name: device_hostname + value: hostname of the device that event is for entrypoint: start - serviceAccountName: operate-workflow-sa + serviceAccountName: workflow templates: - name: start steps: @@ -64,3 +70,7 @@ spec: templateRef: name: synchronize-server-to-ironic template: synchronize-server + - - name: synchronize-server-obm-creds + templateRef: + name: synchronize-obm-creds + template: main diff --git a/argo-workflows/ironic-nautobot-sync/workflowtemplates/synchronize-obm-creds.yaml b/argo-workflows/ironic-nautobot-sync/workflowtemplates/synchronize-obm-creds.yaml new file mode 100644 index 000000000..df49b9c2e --- /dev/null +++ b/argo-workflows/ironic-nautobot-sync/workflowtemplates/synchronize-obm-creds.yaml @@ -0,0 +1,53 @@ +apiVersion: argoproj.io/v1alpha1 +metadata: + name: synchronize-obm-creds +kind: WorkflowTemplate +spec: + entrypoint: main + arguments: + parameters: + - name: interface_update_event + value: "{}" + templates: + - name: main + steps: + - - name: load-obm-creds + templateRef: + name: get-obm-creds + template: main + arguments: + parameters: + - name: hostname + value: '{{workflow.parameters.device_hostname}}' + - - name: synchronize-obm-creds + template: synchronize-obm-creds + arguments: + parameters: + - name: obm + value: "{{ steps.load-obm-creds.outputs.parameters.secret }}" + + - name: synchronize-obm-creds + inputs: + parameters: + - name: obm + container: + image: ghcr.io/rackerlabs/understack/argo-ironic-client-python3.11.8:latest + command: + - python + - /app/synchronize-obm-creds.py + args: + - "{{workflow.parameters.interface_update_event}}" + volumeMounts: + - mountPath: /etc/ironic-secrets/ + name: ironic-secrets + readOnly: true + - mountPath: /etc/obm + name: obm-secret + readOnly: true + volumes: + - name: ironic-secrets + secret: + secretName: production-ironic-for-argo-creds + - name: obm-secret + secret: + secretName: "{{ inputs.parameters.obm }}"