From 8e53d82182940bcd94a3f30a06f1e06e21aec8dd Mon Sep 17 00:00:00 2001 From: Marek Skrobacki Date: Mon, 5 Aug 2024 13:50:22 +0100 Subject: [PATCH 1/3] undersync: workflow to sync individual switches This essentially allows to bypass the Nautobot Device lookup and call the undersync directly on the switches by giving their UUIDs. That workflow has been requested in PUC-387, part 2. --- .../{sync.yaml => undersync-device.yaml} | 0 .../workflowtemplates/undersync-switch.yaml | 39 +++++++++++++++ python/understack-workflows/pyproject.toml | 1 + .../understack_workflows/helpers.py | 41 ++++++++++++++++ .../understack_workflows/main/undersync.py | 48 +++++++++++++++++++ .../main/undersync_switch.py | 35 ++++++++++++++ 6 files changed, 164 insertions(+) rename argo-workflows/trigger-undersync/workflowtemplates/{sync.yaml => undersync-device.yaml} (100%) create mode 100644 argo-workflows/trigger-undersync/workflowtemplates/undersync-switch.yaml create mode 100644 python/understack-workflows/understack_workflows/main/undersync.py create mode 100644 python/understack-workflows/understack_workflows/main/undersync_switch.py diff --git a/argo-workflows/trigger-undersync/workflowtemplates/sync.yaml b/argo-workflows/trigger-undersync/workflowtemplates/undersync-device.yaml similarity index 100% rename from argo-workflows/trigger-undersync/workflowtemplates/sync.yaml rename to argo-workflows/trigger-undersync/workflowtemplates/undersync-device.yaml diff --git a/argo-workflows/trigger-undersync/workflowtemplates/undersync-switch.yaml b/argo-workflows/trigger-undersync/workflowtemplates/undersync-switch.yaml new file mode 100644 index 00000000..4e6fb5dd --- /dev/null +++ b/argo-workflows/trigger-undersync/workflowtemplates/undersync-switch.yaml @@ -0,0 +1,39 @@ +apiVersion: argoproj.io/v1alpha1 +metadata: + name: undersync-switch +kind: WorkflowTemplate +spec: + entrypoint: undersync-switch + serviceAccountName: workflow + templates: + - name: undersync-switch + container: + image: ghcr.io/rackerlabs/understack/ironic-nautobot-client:latest + command: + - undersync-switch + args: + - --switch_uuids + - "{{workflow.parameters.switch_uuids}}" + - --dry-run + - "{{workflow.parameters.dry_run}}" + - --force + - "{{workflow.parameters.force}}" + volumeMounts: + - mountPath: /etc/nb-token/ + name: nb-token + readOnly: true + - mountPath: /etc/undersync/ + name: undersync-token + readOnly: true + inputs: + parameters: + - name: switch_uuids + - name: force + - name: dry_run + volumes: + - name: nb-token + secret: + secretName: nautobot-token + - name: undersync-token + secret: + secretName: undersync-token diff --git a/python/understack-workflows/pyproject.toml b/python/understack-workflows/pyproject.toml index 933349ec..3d382b2d 100644 --- a/python/understack-workflows/pyproject.toml +++ b/python/understack-workflows/pyproject.toml @@ -43,6 +43,7 @@ synchronize-interfaces = "understack_workflows.main.synchronize_interfaces:main" synchronize-obm-creds = "understack_workflows.main.synchronize_obm_creds:main" 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" [tool.setuptools.packages.find] # avoid packaging up our tests diff --git a/python/understack-workflows/understack_workflows/helpers.py b/python/understack-workflows/understack_workflows/helpers.py index d6f7be1e..a930312e 100644 --- a/python/understack-workflows/understack_workflows/helpers.py +++ b/python/understack-workflows/understack_workflows/helpers.py @@ -2,6 +2,7 @@ import logging import os import pathlib +from functools import partial import sushy @@ -35,6 +36,46 @@ def arg_parser(name): return parser +def undersync_switch_parser(name): + parser = argparse.ArgumentParser( + prog=os.path.basename(name), + description="Trigger undersync run for a set of switches.", + ) + parser.add_argument( + "--switch_uuids", + type=__comma_list, + required=True, + help="Comma separated list of UUIDs of the switches to Undersync", + ) + parser.add_argument( + "--force", + type=__boolean_args, + help="Call Undersync's force endpoint", + required=False, + ) + parser.add_argument( + "--dry-run", + type=__boolean_args, + help="Call Undersync's dry-run endpoint", + required=False, + ) + + return parser + + +def __boolean_args(val): + normalised = str(val).upper() + if normalised in ["YES", "TRUE", "T", "1"]: + return True + elif normalised in ["NO", "FALSE", "F", "N", "0"]: + return False + else: + raise argparse.ArgumentTypeError("boolean expected") + + +__comma_list = partial(str.split, sep=",") + + def credential(subpath, item): ref = pathlib.Path("/etc").joinpath(subpath).joinpath(item) with ref.open() as f: diff --git a/python/understack-workflows/understack_workflows/main/undersync.py b/python/understack-workflows/understack_workflows/main/undersync.py new file mode 100644 index 00000000..5561d7ea --- /dev/null +++ b/python/understack-workflows/understack_workflows/main/undersync.py @@ -0,0 +1,48 @@ +from functools import cached_property + +import requests + + +class Undersync: + def __init__( + self, + auth_token: str, + api_url="http://undersync-service.undersync.svc.cluster.local:8080", + ) -> None: + self.token = auth_token + self.api_url = api_url + + def sync_devices(self, switch_uuids: str | list[str], force=False, dry_run=False): + if isinstance(switch_uuids, list): + switch_uuids = ",".join(switch_uuids) + + if dry_run: + return self.dry_run(switch_uuids) + elif force: + return self.force(switch_uuids) + else: + return self.sync(switch_uuids) + + @cached_property + def client(self): + session = requests.Session() + session.headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {self.token}", + } + return session + + def sync(self, uuids: str) -> requests.Response: + response = self.client.post(f"{self.api_url}/v1/devices/{uuids}/sync") + response.raise_for_status() + return response + + def dry_run(self, uuids: str) -> requests.Response: + response = self.client.post(f"{self.api_url}/v1/devices/{uuids}/dry-run") + response.raise_for_status() + return response + + def force(self, uuids: str) -> requests.Response: + response = self.client.post(f"{self.api_url}/v1/devices/{uuids}/force") + response.raise_for_status() + return response diff --git a/python/understack-workflows/understack_workflows/main/undersync_switch.py b/python/understack-workflows/understack_workflows/main/undersync_switch.py new file mode 100644 index 00000000..e96fb16b --- /dev/null +++ b/python/understack-workflows/understack_workflows/main/undersync_switch.py @@ -0,0 +1,35 @@ +import sys + +from understack_workflows.helpers import credential +from understack_workflows.helpers import setup_logger +from understack_workflows.helpers import undersync_switch_parser +from understack_workflows.main.undersync import Undersync + + +def call_undersync(args, switches): + undersync_token = credential("undersync", "token") + if not undersync_token: + logger.error("Please provide auth token for Undersync.") + sys.exit(1) + undersync = Undersync(undersync_token) + + try: + logger.debug(f"Syncing switches: {switches} {args.dry_run=} {args.force=}") + return undersync.sync_devices(switches, dry_run=args.dry_run, force=args.force) + except Exception as error: + logger.error(error) + sys.exit(2) + + +def main(): + """Requests an Undersync run on a pair of switches.""" + parser = undersync_switch_parser(__file__) + args = parser.parse_args() + + response = call_undersync(args, args.switch_uuids) + logger.info(f"Undersync returned: {response.json()}") + + +logger = setup_logger(__name__) +if __name__ == "__main__": + main() From f6b295bbe5a1648de4ad9b0a73a579db230c9736 Mon Sep 17 00:00:00 2001 From: Marek Skrobacki Date: Mon, 5 Aug 2024 18:04:21 +0100 Subject: [PATCH 2/3] move undersync_switch_parser to main file --- .../understack_workflows/helpers.py | 31 ++-------------- .../main/undersync_switch.py | 35 +++++++++++++++++-- 2 files changed, 34 insertions(+), 32 deletions(-) diff --git a/python/understack-workflows/understack_workflows/helpers.py b/python/understack-workflows/understack_workflows/helpers.py index a930312e..2d5f78b5 100644 --- a/python/understack-workflows/understack_workflows/helpers.py +++ b/python/understack-workflows/understack_workflows/helpers.py @@ -36,34 +36,7 @@ def arg_parser(name): return parser -def undersync_switch_parser(name): - parser = argparse.ArgumentParser( - prog=os.path.basename(name), - description="Trigger undersync run for a set of switches.", - ) - parser.add_argument( - "--switch_uuids", - type=__comma_list, - required=True, - help="Comma separated list of UUIDs of the switches to Undersync", - ) - parser.add_argument( - "--force", - type=__boolean_args, - help="Call Undersync's force endpoint", - required=False, - ) - parser.add_argument( - "--dry-run", - type=__boolean_args, - help="Call Undersync's dry-run endpoint", - required=False, - ) - - return parser - - -def __boolean_args(val): +def boolean_args(val): normalised = str(val).upper() if normalised in ["YES", "TRUE", "T", "1"]: return True @@ -73,7 +46,7 @@ def __boolean_args(val): raise argparse.ArgumentTypeError("boolean expected") -__comma_list = partial(str.split, sep=",") +comma_list_args = partial(str.split, sep=",") def credential(subpath, item): diff --git a/python/understack-workflows/understack_workflows/main/undersync_switch.py b/python/understack-workflows/understack_workflows/main/undersync_switch.py index e96fb16b..be9d5bbd 100644 --- a/python/understack-workflows/understack_workflows/main/undersync_switch.py +++ b/python/understack-workflows/understack_workflows/main/undersync_switch.py @@ -1,8 +1,11 @@ +import argparse +import os import sys +from understack_workflows.helpers import boolean_args +from understack_workflows.helpers import comma_list_args from understack_workflows.helpers import credential from understack_workflows.helpers import setup_logger -from understack_workflows.helpers import undersync_switch_parser from understack_workflows.main.undersync import Undersync @@ -21,10 +24,36 @@ def call_undersync(args, switches): sys.exit(2) +def argument_parser(): + parser = argparse.ArgumentParser( + prog=os.path.basename(__file__), + description="Trigger undersync run for a set of switches.", + ) + parser.add_argument( + "--switch_uuids", + type=comma_list_args, + required=True, + help="Comma separated list of UUIDs of the switches to Undersync", + ) + parser.add_argument( + "--force", + type=boolean_args, + help="Call Undersync's force endpoint", + required=False, + ) + parser.add_argument( + "--dry-run", + type=boolean_args, + help="Call Undersync's dry-run endpoint", + required=False, + ) + + return parser + + def main(): """Requests an Undersync run on a pair of switches.""" - parser = undersync_switch_parser(__file__) - args = parser.parse_args() + args = argument_parser().parse_args() response = call_undersync(args, args.switch_uuids) logger.info(f"Undersync returned: {response.json()}") From eee55e9bd0cbd1e78ec1f162361e079650710e91 Mon Sep 17 00:00:00 2001 From: Marek Skrobacki Date: Mon, 5 Aug 2024 18:07:59 +0100 Subject: [PATCH 3/3] undersync: move client module into separate package --- .../understack_workflows/main/undersync_switch.py | 2 +- .../understack_workflows/undersync/__init__.py | 0 .../{main/undersync.py => undersync/client.py} | 0 3 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 python/understack-workflows/understack_workflows/undersync/__init__.py rename python/understack-workflows/understack_workflows/{main/undersync.py => undersync/client.py} (100%) diff --git a/python/understack-workflows/understack_workflows/main/undersync_switch.py b/python/understack-workflows/understack_workflows/main/undersync_switch.py index be9d5bbd..daabc87a 100644 --- a/python/understack-workflows/understack_workflows/main/undersync_switch.py +++ b/python/understack-workflows/understack_workflows/main/undersync_switch.py @@ -6,7 +6,7 @@ from understack_workflows.helpers import comma_list_args from understack_workflows.helpers import credential from understack_workflows.helpers import setup_logger -from understack_workflows.main.undersync import Undersync +from understack_workflows.undersync.client import Undersync def call_undersync(args, switches): diff --git a/python/understack-workflows/understack_workflows/undersync/__init__.py b/python/understack-workflows/understack_workflows/undersync/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/python/understack-workflows/understack_workflows/main/undersync.py b/python/understack-workflows/understack_workflows/undersync/client.py similarity index 100% rename from python/understack-workflows/understack_workflows/main/undersync.py rename to python/understack-workflows/understack_workflows/undersync/client.py