Skip to content

Commit

Permalink
Merge pull request #112 from rackerlabs/update-device-obm-creds
Browse files Browse the repository at this point in the history
feat: update device OBM info in Ironic
  • Loading branch information
skrobul authored Jun 5, 2024
2 parents 8c324ff + a069595 commit d89c675
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 2 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build-container-images.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
100 changes: 100 additions & 0 deletions argo-workflows/ironic-nautobot-sync/code/synchronize-obm-creds.py
Original file line number Diff line number Diff line change
@@ -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}")
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -55,12 +59,18 @@ 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:
- - name: synchronize-server-to-ironic
templateRef:
name: synchronize-server-to-ironic
template: synchronize-server
- - name: synchronize-server-obm-creds
templateRef:
name: synchronize-obm-creds
template: main
Original file line number Diff line number Diff line change
@@ -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 }}"

0 comments on commit d89c675

Please sign in to comment.