diff --git a/python/understack-workflows/understack_workflows/create_node.py b/python/understack-workflows/understack_workflows/create_node.py deleted file mode 100644 index 8dff54e90..000000000 --- a/python/understack-workflows/understack_workflows/create_node.py +++ /dev/null @@ -1,27 +0,0 @@ -import json -import logging -import sys - -from understack_workflows.ironic.client import IronicClient -from understack_workflows.ironic.secrets import read_secret - -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"), -) - -node_config = json.loads(sys.argv[1]) -response = client.create_node(node_config) -logger.debug(response) diff --git a/python/understack-workflows/understack_workflows/ironic/client.py b/python/understack-workflows/understack_workflows/ironic/client.py index 536437f18..ddcc4abf4 100644 --- a/python/understack-workflows/understack_workflows/ironic/client.py +++ b/python/understack-workflows/understack_workflows/ironic/client.py @@ -1,54 +1,21 @@ -from ironicclient import client as iclient -from keystoneauth1 import session -from keystoneauth1.identity import v3 +from understack_workflows.openstack.client import get_ironic_client class IronicClient: def __init__( self, - svc_url: str, - username: str, - password: str, - auth_url: str, - tenant_name: str, ) -> None: """Initialize our ironicclient wrapper.""" - self.svc_url = svc_url - self.username = username - self.password = password - self.auth_url = auth_url - self.tenant_name = tenant_name self.logged_in = False - self.os_ironic_api_version = "1.82" def login(self): - auth = v3.Password( - auth_url=self.auth_url, - username=self.username, - password=self.password, - project_name=self.tenant_name, - project_domain_name="Default", - user_domain_name="Default", - ) - insecure_ssl = True - sess = session.Session( - auth=auth, verify=(not insecure_ssl), app_name="nautobot" - ) - self.client = iclient.Client( - 1, - endpoint_override=self.svc_url, - session=sess, - insecure=insecure_ssl, - ) - self.client.negotiate_api_version() + self.client = get_ironic_client() self.logged_in = True def create_node(self, node_data: dict): self._ensure_logged_in() - return self.client.node.create( - os_ironic_api_version=self.os_ironic_api_version, **node_data - ) + return self.client.node.create(**node_data) def list_nodes(self): self._ensure_logged_in() @@ -59,35 +26,36 @@ def get_node(self, node_ident: str, fields: list[str] | None = None): self._ensure_logged_in() return self.client.node.get( - node_ident, fields, os_ironic_api_version=self.os_ironic_api_version + node_ident, + fields, ) def update_node(self, node_id, patch): self._ensure_logged_in() return self.client.node.update( - node_id, patch, os_ironic_api_version=self.os_ironic_api_version + node_id, + patch, ) def create_port(self, port_data: dict): self._ensure_logged_in() - return self.client.port.create( - os_ironic_api_version=self.os_ironic_api_version, **port_data - ) + return self.client.port.create(**port_data) def update_port(self, port_id: str, patch: list): self._ensure_logged_in() return self.client.port.update( - port_id, patch, os_ironic_api_version=self.os_ironic_api_version + port_id, + patch, ) def delete_port(self, port_id: str): self._ensure_logged_in() return self.client.port.delete( - port_id, os_ironic_api_version=self.os_ironic_api_version + port_id, ) def list_ports(self, node_id: dict): diff --git a/python/understack-workflows/understack_workflows/ironic/secrets.py b/python/understack-workflows/understack_workflows/ironic/secrets.py deleted file mode 100644 index af57d92e8..000000000 --- a/python/understack-workflows/understack_workflows/ironic/secrets.py +++ /dev/null @@ -1,20 +0,0 @@ -import logging -import os -import re - -logger = logging.getLogger(__name__) - - -def read_secret(secret_name: str) -> str: - """Retrieve value of Kubernetes secret.""" - - def normalized(name): - return re.sub(r"[^A-Za-z0-9-_]", "", name) - - base_path = os.environ.get("SECRETS_BASE_PATH", "/etc/ironic-secrets/") - secret_path = os.path.join(base_path, normalized(secret_name)) - try: - return open(secret_path).read() - except FileNotFoundError: - logger.error(f"Secret {secret_name} is not defined.") - return "" diff --git a/python/understack-workflows/understack_workflows/main/sync_interfaces.py b/python/understack-workflows/understack_workflows/main/sync_interfaces.py index e41da9b58..7e5a0c7f2 100644 --- a/python/understack-workflows/understack_workflows/main/sync_interfaces.py +++ b/python/understack-workflows/understack_workflows/main/sync_interfaces.py @@ -85,13 +85,7 @@ def main(): nautobot_ports = get_nautobot_interfaces(nautobot, device_id) # get Ironic Ports - client = IronicClient( - svc_url=os.environ["IRONIC_SVC_URL"], - username=os.environ["IRONIC_USERNAME"], - password=os.environ["IRONIC_PASSWORD"], - auth_url=os.environ["IRONIC_AUTH_URL"], - tenant_name=os.environ["IRONIC_TENANT"], - ) + client = IronicClient() logger.info("Fetching Ironic Ports ...") ironic_ports = client.list_ports(device_id) diff --git a/python/understack-workflows/understack_workflows/main/sync_keystone.py b/python/understack-workflows/understack_workflows/main/sync_keystone.py index f1f3b6bac..8f91d455b 100644 --- a/python/understack-workflows/understack_workflows/main/sync_keystone.py +++ b/python/understack-workflows/understack_workflows/main/sync_keystone.py @@ -3,15 +3,14 @@ import uuid from enum import StrEnum -import openstack -from openstack.connection import Connection - from understack_workflows.domain import DefaultDomain from understack_workflows.domain import domain_id from understack_workflows.helpers import credential from understack_workflows.helpers import parser_nautobot_args from understack_workflows.helpers import setup_logger from understack_workflows.nautobot import Nautobot +from understack_workflows.openstack.client import Connection +from understack_workflows.openstack.client import get_openstack_client logger = setup_logger(__name__, level=logging.INFO) @@ -123,7 +122,7 @@ def do_action( def main(): args = argument_parser().parse_args() - conn = openstack.connect(cloud=args.os_cloud) + conn = get_openstack_client(cloud=args.os_cloud) nb_token = args.nautobot_token or credential("nb-token", "token") nautobot = Nautobot(args.nautobot_url, nb_token, logger=logger) diff --git a/python/understack-workflows/understack_workflows/main/sync_obm_creds.py b/python/understack-workflows/understack_workflows/main/sync_obm_creds.py index 0b77004d2..c296552a3 100644 --- a/python/understack-workflows/understack_workflows/main/sync_obm_creds.py +++ b/python/understack-workflows/understack_workflows/main/sync_obm_creds.py @@ -7,7 +7,6 @@ from understack_workflows.helpers import credential from understack_workflows.helpers import setup_logger from understack_workflows.ironic.client import IronicClient -from understack_workflows.ironic.secrets import read_secret from understack_workflows.node_configuration import IronicNodeConfiguration logger = setup_logger(__name__) @@ -20,13 +19,7 @@ def main(): ) 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"), - ) + client = IronicClient() interface_update_event = json.loads(sys.argv[1]) logger.debug(f"Received: {interface_update_event}") diff --git a/python/understack-workflows/understack_workflows/main/sync_server.py b/python/understack-workflows/understack_workflows/main/sync_server.py index 8efeab983..dd85d26a5 100644 --- a/python/understack-workflows/understack_workflows/main/sync_server.py +++ b/python/understack-workflows/understack_workflows/main/sync_server.py @@ -6,7 +6,6 @@ from understack_workflows.helpers import setup_logger from understack_workflows.ironic.client import IronicClient -from understack_workflows.ironic.secrets import read_secret from understack_workflows.node_configuration import IronicNodeConfiguration logger = setup_logger(__name__) @@ -26,16 +25,6 @@ def get_args(): return json.loads(sys.argv[1]) -def get_ironic_client(): - return 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 get_ironic_node(node, ironic_client): logger.debug(f"Checking if node UUID {node.uuid} exists in Ironic.") @@ -82,7 +71,7 @@ def main(): update_data = interface_update_event["data"] logger.info("Pushing device new node to Ironic.") - ironic_client = get_ironic_client() + ironic_client = IronicClient() node = IronicNodeConfiguration.from_event(interface_update_event) ironic_node = get_ironic_node(node, ironic_client) diff --git a/python/understack-workflows/understack_workflows/openstack/__init__.py b/python/understack-workflows/understack_workflows/openstack/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/python/understack-workflows/understack_workflows/openstack/client.py b/python/understack-workflows/understack_workflows/openstack/client.py new file mode 100644 index 000000000..b755ab062 --- /dev/null +++ b/python/understack-workflows/understack_workflows/openstack/client.py @@ -0,0 +1,56 @@ +"""helper to setup OpenStack clients.""" + +# attempt to prevent re-export +import os as _os +import sys as _sys +from importlib import metadata as _meta + +from ironicclient.client import Client as IronicClient +from ironicclient.client import get_client as _get_ironic_client +from openstack import config as _os_config +from openstack.connection import Connection + +try: + _pkg_ver = _meta.version(__package__.split(".")[0]) +except Exception: + _pkg_ver = "dev" + +try: + _prog_name = _os.path.basename(_sys.argv[0]) or "local" +except Exception: + _prog_name = "local" + + +def _get_os_cloud_region(cloud=None, region_name=""): + """Returns a keystoneauth1 Session based on our clouds.yaml.""" + return _os_config.get_cloud_region( + load_yaml_config=True, + load_envvars=True, + app_name=_prog_name, + app_version=_pkg_ver, + cloud=cloud, + region_name=region_name, + ) + + +def get_openstack_client(cloud=None, region_name="") -> Connection: + """Returns an OpenStackSDK Connection based on our clouds.yaml.""" + cloud_region = _get_os_cloud_region(cloud, region_name) + + return Connection(config=cloud_region) + + +def get_ironic_client(cloud=None, region_name="") -> IronicClient: + """Returns our Ironic Client wrapper configured from our clouds.yaml.""" + cloud_region = _get_os_cloud_region(cloud, region_name) + client = _get_ironic_client( + "1", session=cloud_region.get_session(), os_ironic_api_version="latest" + ) + client.negotiate_api_version() + return client + + +__all__ = [ + "get_openstack_client", + "get_ironic_client", +] diff --git a/workflows/argo-events/kustomization.yaml b/workflows/argo-events/kustomization.yaml index b016af527..f2e396da1 100644 --- a/workflows/argo-events/kustomization.yaml +++ b/workflows/argo-events/kustomization.yaml @@ -9,7 +9,6 @@ resources: - secrets/obm-creds.yaml - secrets/placeholder-obm-creds.yaml - secrets/operate-workflow-sa.token.yaml - - secrets/production-ironic-for-argo-creds.yaml - secrets/nautobot-token.yaml - secrets/placeholder-obm-legacy-passwords.yaml - sensors/ironic-node-update.yaml diff --git a/workflows/argo-events/secrets/production-ironic-for-argo-creds.yaml b/workflows/argo-events/secrets/production-ironic-for-argo-creds.yaml deleted file mode 100644 index df766ed41..000000000 --- a/workflows/argo-events/secrets/production-ironic-for-argo-creds.yaml +++ /dev/null @@ -1,12 +0,0 @@ ---- -apiVersion: v1 -data: - IRONIC_AUTH_URL: aHR0cDovL2tleXN0b25lLWFwaS5vcGVuc3RhY2suc3ZjLmNsdXN0ZXIubG9jYWw6NTAwMA== - IRONIC_PASSWORD: YXJnb3dvcmtmbG93 - IRONIC_SVC_URL: aHR0cDovL2lyb25pYy1hcGkub3BlbnN0YWNrLnN2Yy5jbHVzdGVyLmxvY2FsOjYzODU= - IRONIC_TENANT: dW5kZXJjbG91ZA== - IRONIC_USERNAME: YXJnb3dvcmtmbG93 -kind: Secret -metadata: - name: production-ironic-for-argo-creds - namespace: argo diff --git a/workflows/argo-events/workflowtemplates/sync-interfaces-to-ironic.yaml b/workflows/argo-events/workflowtemplates/sync-interfaces-to-ironic.yaml index 78952f6c6..cab08fbbc 100644 --- a/workflows/argo-events/workflowtemplates/sync-interfaces-to-ironic.yaml +++ b/workflows/argo-events/workflowtemplates/sync-interfaces-to-ironic.yaml @@ -24,9 +24,10 @@ spec: - --device-id - "{{inputs.parameters.device_id}}" - --debug - envFrom: - - secretRef: - name: production-ironic-for-argo-creds + volumeMounts: + - mountPath: /etc/openstack + name: openstack-svc-acct + readOnly: true env: - name: NAUTOBOT_API valueFrom: @@ -38,3 +39,7 @@ spec: secretKeyRef: name: nautobot-token key: token + volumes: + - name: openstack-svc-acct + secret: + secretName: openstack-svc-acct diff --git a/workflows/argo-events/workflowtemplates/sync-obm-creds.yaml b/workflows/argo-events/workflowtemplates/sync-obm-creds.yaml index 88a48d25a..8c9eff2f4 100644 --- a/workflows/argo-events/workflowtemplates/sync-obm-creds.yaml +++ b/workflows/argo-events/workflowtemplates/sync-obm-creds.yaml @@ -41,16 +41,16 @@ spec: args: - "{{workflow.parameters.interface_update_event}}" volumeMounts: - - mountPath: /etc/ironic-secrets/ - name: ironic-secrets + - mountPath: /etc/openstack + name: openstack-svc-acct readOnly: true - mountPath: /etc/obm name: obm-secret readOnly: true volumes: - - name: ironic-secrets + - name: openstack-svc-acct secret: - secretName: production-ironic-for-argo-creds + secretName: openstack-svc-acct - name: obm-secret secret: secretName: "{{ inputs.parameters.obm }}" diff --git a/workflows/argo-events/workflowtemplates/sync-server-to-ironic.yaml b/workflows/argo-events/workflowtemplates/sync-server-to-ironic.yaml index 93e7678a9..e23dd3128 100644 --- a/workflows/argo-events/workflowtemplates/sync-server-to-ironic.yaml +++ b/workflows/argo-events/workflowtemplates/sync-server-to-ironic.yaml @@ -25,10 +25,10 @@ spec: args: - "{{workflow.parameters.interface_update_event}}" volumeMounts: - - mountPath: /etc/ironic-secrets/ - name: ironic-secrets + - mountPath: /etc/openstack + name: openstack-svc-acct readOnly: true volumes: - - name: ironic-secrets + - name: openstack-svc-acct secret: - secretName: production-ironic-for-argo-creds + secretName: openstack-svc-acct