Skip to content

Commit

Permalink
add ironic-to-nautobot-sync draft
Browse files Browse the repository at this point in the history
  • Loading branch information
skrobul committed Jul 17, 2024
1 parent 915984a commit bab33ef
Show file tree
Hide file tree
Showing 9 changed files with 281 additions and 0 deletions.
11 changes: 11 additions & 0 deletions .github/workflows/build-container-images.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ env:
VERSION_ARGO_UTILS: 0.0.1
VERSION_OBM_UTILS: 0.0.1
VERSION_PYTHON_NAUTOBOT_INT_SYNC: 0.0.1
VERSION_PYTHON_NAUTOBOT: 0.0.1

jobs:
build-ghcr-registry:
Expand Down Expand Up @@ -109,3 +110,13 @@ jobs:
tags: ghcr.io/rackerlabs/understack/nautobot-interfaces-sync:latest,ghcr.io/rackerlabs/understack/nautobot-interfaces-sync:${{ env.VERSION_PYTHON_NAUTOBOT_INT_SYNC }}
labels: |
org.opencontainers.image.version=${{ env.VERSION_PYTHON_NAUTOBOT_INT_SYNC }}
- name: Build and deploy Python 3.11 with PyNautobot
uses: docker/build-push-action@v5
with:
context: argo-workflows/ironic-to-nautobot-sync
file: argo-workflows/ironic-to-nautobot-sync/containers/Dockerfile
# push for all main branch commits
push: ${{ github.event_name != 'pull_request' }}
tags: ghcr.io/rackerlabs/understack/ironic-to-nautobot-sync:latest,ghcr.io/rackerlabs/understack/ironic-to-nautobot-sync:${{ env.VERSION_PYTHON_NAUTOBOT }}
labels: |
org.opencontainers.image.version=${{ env.VERSION_PYTHON_NAUTOBOT }}
Empty file.
44 changes: 44 additions & 0 deletions argo-workflows/ironic-to-nautobot-sync/code/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
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("--provisioning-state", 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")

24 changes: 24 additions & 0 deletions argo-workflows/ironic-to-nautobot-sync/code/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
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
provision_state = args.provisioning_state
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_status(device_uuid, provision_state)


if __name__ == "__main__":
main()
67 changes: 67 additions & 0 deletions argo-workflows/ironic-to-nautobot-sync/code/nautobot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import logging
import pynautobot
import requests
import sys
from typing import Protocol
from pynautobot.core.api import Api as NautobotApi
from pynautobot.models.dcim import Devices as NautobotDevice

class Nautobot:
ALLOWED_STATUSES = [
'enroll',
'verifying',
'manageable',
'inspecting',
'inspect wait',
'inspect failed',
'cleaning',
'clean wait',
'available',
'deploying',
'wait call-back'
'deploy failed',
'active',
'deleting',
'error',
'adopting',
'rescuing'
'rescue wait',
'rescue failed',
'rescue',
'unrescuing',
'unrescue failed',
'servicing',
'service wait',
'service failed'
]
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 update_status(self, device_id, new_status: str):
device = self.device_by_id(device_id)

if new_status not in self.ALLOWED_STATUSES:
raise Exception(f"Status {new_status} for device {device_id} is not in ALLOWED_STATUSES.")

return device.update({"status": new_status})
30 changes: 30 additions & 0 deletions argo-workflows/ironic-to-nautobot-sync/containers/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
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/ironic-to-nautobot-sync-python3.11.8"
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"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
requests==2.31.0
pynautobot==2.1.1
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
apiVersion: argoproj.io/v1alpha1
kind: Sensor
metadata:
finalizers:
- sensor-controller
labels:
argocd.argoproj.io/instance: argo-events
name: ironic-node-update
namespace: argo-events
spec:
dependencies:
- eventName: openstack
eventSourceName: openstack-amqp
name: ironic-dep
transform:
jq: ".body[\"oslo.message\"] | fromjson"
filters:
dataLogicalOperator: "and"
data:
- path: "event_type"
type: "string"
value:
- "baremetal.node.update.end"
template:
serviceAccountName: operate-workflow-sa
triggers:
- template:
name: ironic-node-update-trigger
k8s:
operation: create
parameters:
- dest: spec.arguments.parameters.0.value
src:
dataKey: payload.ironic_object\.data.uuid
dependencyName: ironic-dep
- dest: spec.arguments.parameters.1.value
src:
dataKey: payload.ironic_object\.data.provision_state
dependencyName: ironic-dep
source:
resource:
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: ironic-node-update-
spec:
arguments:
parameters:
- name: device_uuid
value: Device UUID
- name: provision_state
value: Ironic Provision state
entrypoint: start
serviceAccountName: workflow
templates:
- name: start
steps:
- - name: synchronize-provision-state-to-nautobot
templateRef:
name: synchronize-provision-state-to-nautobot
template: synchronize-state
arguments:
parameters:
- name: device_uuid
value: "{{workflow.parameters.device_uuid}}"
- name: provision_state
value: "{{workflow.parameters.provision_state}}"
35 changes: 35 additions & 0 deletions argo-workflows/ironic-to-nautobot-sync/workflowtemplates/sync.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
apiVersion: argoproj.io/v1alpha1
metadata:
name: synchronize-provision-state-to-nautobot
kind: WorkflowTemplate
spec:
arguments:
parameters:
- name: device_uuid
value: "{}"
- name: provision_state
value: "{}"
templates:
- name: synchronize-state
container:
image: ghcr.io/rackerlabs/understack/ironic-to-nautobot-sync:latest
command:
- python
- /app/main.py
args:
- --device_uuid
- "{{workflow.parameters.device_uuid}}"
- --provisioning-state
- "{{workflow.parameters.provision_state}}"
volumeMounts:
- mountPath: /etc/nb-token/
name: nb-token
readOnly: true
inputs:
parameters:
- name: device_uuid
- name: provision_state
volumes:
- name: nb-token
secret:
secretName: nautobot-token

0 comments on commit bab33ef

Please sign in to comment.