From 1e3e23948a0b8735557d1bcdecfcbf995393b00a Mon Sep 17 00:00:00 2001 From: Marek Skrobacki Date: Tue, 6 Aug 2024 10:41:24 +0100 Subject: [PATCH] extract nautobot-update-cf workflow into understack_workflows --- .../workflowtemplates/sync.yaml | 12 +--- .../nautobot-update-cf/code/__init__.py | 0 .../nautobot-update-cf/code/helpers.py | 43 -------------- .../nautobot-update-cf/code/main.py | 25 -------- .../nautobot-update-cf/code/nautobot.py | 59 ------------------- .../nautobot-update-cf/containers/Dockerfile | 30 ---------- .../containers/requirements.txt | 2 - python/understack-workflows/pyproject.toml | 1 + .../main/nautobot_update_cf.py | 43 ++++++++++++++ 9 files changed, 47 insertions(+), 168 deletions(-) delete mode 100644 argo-workflows/nautobot-update-cf/code/__init__.py delete mode 100644 argo-workflows/nautobot-update-cf/code/helpers.py delete mode 100644 argo-workflows/nautobot-update-cf/code/main.py delete mode 100644 argo-workflows/nautobot-update-cf/code/nautobot.py delete mode 100644 argo-workflows/nautobot-update-cf/containers/Dockerfile delete mode 100644 argo-workflows/nautobot-update-cf/containers/requirements.txt create mode 100644 python/understack-workflows/understack_workflows/main/nautobot_update_cf.py diff --git a/argo-workflows/ironic-to-nautobot-sync/workflowtemplates/sync.yaml b/argo-workflows/ironic-to-nautobot-sync/workflowtemplates/sync.yaml index ff4241d49..ced216d1c 100644 --- a/argo-workflows/ironic-to-nautobot-sync/workflowtemplates/sync.yaml +++ b/argo-workflows/ironic-to-nautobot-sync/workflowtemplates/sync.yaml @@ -3,19 +3,13 @@ metadata: name: synchronize-provision-state-to-nautobot kind: WorkflowTemplate spec: - arguments: - parameters: - - name: device_uuid - value: "{}" - - name: provision_state - value: "{}" + serviceAccountName: workflow templates: - name: synchronize-state container: - image: ghcr.io/rackerlabs/understack/nautobot-update-cf:latest + image: ghcr.io/rackerlabs/understack/ironic-nautobot-client:latest command: - - python - - /app/main.py + - nautobot-update-cf args: - --device_uuid - "{{workflow.parameters.device_uuid}}" diff --git a/argo-workflows/nautobot-update-cf/code/__init__.py b/argo-workflows/nautobot-update-cf/code/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/argo-workflows/nautobot-update-cf/code/helpers.py b/argo-workflows/nautobot-update-cf/code/helpers.py deleted file mode 100644 index 77da35c71..000000000 --- a/argo-workflows/nautobot-update-cf/code/helpers.py +++ /dev/null @@ -1,43 +0,0 @@ -import argparse -import logging -import os -import sys - -logger = logging.getLogger(__name__) - - -def setup_logger(name): - logger = logging.getLogger(name) - handler = logging.StreamHandler() - formatter = logging.Formatter( - "%(asctime)s - %(name)s - %(levelname)s - %(message)s" - ) - handler.setFormatter(formatter) - logger.addHandler(handler) - logger.setLevel(logging.DEBUG) - return logger - - -def arg_parser(name): - parser = argparse.ArgumentParser( - prog=os.path.basename(name), - description="Ironic to Nautobot provisioning state sync", - ) - parser.add_argument("--device_uuid", required=True, help="Nautobot device UUID") - parser.add_argument("--field-name", required=True) - parser.add_argument("--field-value", required=True) - parser.add_argument("--nautobot_url", required=False) - parser.add_argument("--nautobot_token", required=False) - - return parser - -def exit_with_error(error): - logger.error(error) - sys.exit(1) - - -def credential(subpath, item): - try: - return open(f"/etc/{subpath}/{item}", "r").read().strip() - except FileNotFoundError: - exit_with_error(f"{subpath} {item} not found in mounted files") diff --git a/argo-workflows/nautobot-update-cf/code/main.py b/argo-workflows/nautobot-update-cf/code/main.py deleted file mode 100644 index 8a16088fa..000000000 --- a/argo-workflows/nautobot-update-cf/code/main.py +++ /dev/null @@ -1,25 +0,0 @@ -from nautobot import Nautobot -from helpers import arg_parser -from helpers import credential -from helpers import setup_logger - -logger = setup_logger(__name__) - - -def main(): - parser = arg_parser(__file__) - args = parser.parse_args() - - default_nb_url = "http://nautobot-default.nautobot.svc.cluster.local" - device_uuid = args.device_uuid - field_name = args.field_name - field_value = args.field_value - nb_url = args.nautobot_url or default_nb_url - nb_token = args.nautobot_token or credential("nb-token", "token") - - nautobot = Nautobot(nb_url, nb_token, logger=logger) - nautobot.update_cf(device_uuid, field_name, field_value) - - -if __name__ == "__main__": - main() diff --git a/argo-workflows/nautobot-update-cf/code/nautobot.py b/argo-workflows/nautobot-update-cf/code/nautobot.py deleted file mode 100644 index 1f53e4f42..000000000 --- a/argo-workflows/nautobot-update-cf/code/nautobot.py +++ /dev/null @@ -1,59 +0,0 @@ -import logging -import pynautobot -import requests -import sys -from pynautobot.core.api import Api as NautobotApi -from pynautobot.models.dcim import Devices as NautobotDevice - - -class Nautobot: - def __init__(self, url, token, logger=None, session=None): - self.url = url - self.token = token - self.logger = logger or logging.getLogger(__name__) - self.session = session or self.api_session(self.url, self.token) - - def exit_with_error(self, error): - self.logger.error(error) - sys.exit(1) - - def api_session(self, url: str, token: str) -> NautobotApi: - try: - return pynautobot.api(url, token=token) - except requests.exceptions.ConnectionError as e: - self.exit_with_error(e) - except pynautobot.core.query.RequestError as e: - self.exit_with_error(e) - - def device_by_id(self, device_id: str) -> NautobotDevice: - device = self.session.dcim.devices.get(device_id) - if not device: - self.exit_with_error(f"Device {device_id} not found in Nautobot") - return device - - def device_interfaces(self, device_id: str): - return self.session.dcim.interfaces.filter(device_id=device_id) - - def update_cf(self, device_id, field_name: str, field_value: str): - device = self.device_by_id(device_id) - device.custom_fields[field_name] = field_value - response = device.save() - self.logger.info(f"save result: {response}") - return response - - def uplink_switches(self, device_id) -> list[str]: - interfaces = self.device_interfaces(device_id) - ids = set() - for iface in interfaces: - endpoint = iface.connected_endpoint - if not endpoint: - continue - endpoint.full_details() - self.logger.debug(f"{iface} connected device {iface.connected_endpoint.device} ") - remote_switch = endpoint.device - if not remote_switch: - continue - - ids.add(remote_switch.id) - - return list(ids) diff --git a/argo-workflows/nautobot-update-cf/containers/Dockerfile b/argo-workflows/nautobot-update-cf/containers/Dockerfile deleted file mode 100644 index 6dd6548f1..000000000 --- a/argo-workflows/nautobot-update-cf/containers/Dockerfile +++ /dev/null @@ -1,30 +0,0 @@ -ARG BASE=ghcr.io/rackerlabs/understack/argo-python3.11.8-alpine3.19:latest - -FROM ${BASE} as builder - -ARG APP_PATH=/app -ARG APP_USER=appuser -ARG APP_GROUP=appgroup -ARG APP_USER_UID=1000 -ARG APP_GROUP_GID=1000 - -COPY --chown=${APP_USER}:${APP_GROUP} containers/requirements.txt /app -RUN --mount=type=cache,target=/var/cache/apk apk add --virtual build-deps gcc python3-dev musl-dev linux-headers -RUN --mount=type=cache,target=/root/.cache/.pip pip install --no-cache-dir -r /app/requirements.txt - -FROM ${BASE} as prod - -LABEL org.opencontainers.image.title="Python 3.11 image with pynautobot" -LABEL org.opencontainers.image.base.name="ghcr.io/rackerlabs/understack/nautobot-update-cf" -LABEL org.opencontainers.image.source=https://github.com/rackerlabs/understack - - -ENV PATH="/opt/venv/bin:$PATH" -COPY --from=builder /opt/venv /opt/venv - -WORKDIR /app - -USER $APP_USER - -COPY --chown=${APP_USER}:${APP_GROUP} code/ /app -CMD ["python", "/app/main.py"] diff --git a/argo-workflows/nautobot-update-cf/containers/requirements.txt b/argo-workflows/nautobot-update-cf/containers/requirements.txt deleted file mode 100644 index 9e442c354..000000000 --- a/argo-workflows/nautobot-update-cf/containers/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -requests==2.31.0 -pynautobot==2.1.1 diff --git a/python/understack-workflows/pyproject.toml b/python/understack-workflows/pyproject.toml index f2ae65d8a..b1b33e8ab 100644 --- a/python/understack-workflows/pyproject.toml +++ b/python/understack-workflows/pyproject.toml @@ -45,6 +45,7 @@ synchronize-server = "understack_workflows.main.synchronize_server:main" sync-nautobot-interfaces = "understack_workflows.main.sync_nautobot_interfaces:main" undersync-switch = "understack_workflows.main.undersync_switch:main" undersync-device = "understack_workflows.main.undersync_device:main" +nautobot-update-cf = "understack_workflows.main.nautobot_update_cf:main" [tool.setuptools.packages.find] # avoid packaging up our tests diff --git a/python/understack-workflows/understack_workflows/main/nautobot_update_cf.py b/python/understack-workflows/understack_workflows/main/nautobot_update_cf.py new file mode 100644 index 000000000..6ba74030c --- /dev/null +++ b/python/understack-workflows/understack_workflows/main/nautobot_update_cf.py @@ -0,0 +1,43 @@ +import argparse + +from sushy.main import os + +from understack_workflows.helpers import arg_parser +from understack_workflows.helpers import credential +from understack_workflows.helpers import setup_logger +from understack_workflows.nautobot import Nautobot + + +def argument_parser(): + parser = argparse.ArgumentParser( + prog=os.path.basename(__file__), + description="Ironic to Nautobot provisioning state sync", + ) + parser.add_argument("--device_uuid", required=True, help="Nautobot device UUID") + parser.add_argument("--field-name", required=True) + parser.add_argument("--field-value", required=True) + parser.add_argument("--nautobot_url", required=False) + parser.add_argument("--nautobot_token", required=False) + + return parser + + +logger = setup_logger(__name__) + + +def main(): + args = argument_parser().parse_args() + + default_nb_url = "http://nautobot-default.nautobot.svc.cluster.local" + device_uuid = args.device_uuid + field_name = args.field_name + field_value = args.field_value + nb_url = args.nautobot_url or default_nb_url + nb_token = args.nautobot_token or credential("nb-token", "token") + + nautobot = Nautobot(nb_url, nb_token, logger=logger) + nautobot.update_cf(device_uuid, field_name, field_value) + + +if __name__ == "__main__": + main()