-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #27 from skrobul/workflow-create-node
feat: Workflow to synchronize Nautobot Device to Ironic Node
- Loading branch information
Showing
28 changed files
with
1,563 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
--- | ||
name: build-container-images | ||
|
||
on: | ||
pull_request: | ||
paths: | ||
- 'argo-workflows/generic/containers/*' | ||
- 'argo-workflows/ironic-nautobot-sync/containers/*' | ||
push: | ||
branches: | ||
- main | ||
paths: | ||
- 'argo-workflows/generic/containers/*' | ||
- 'argo-workflows/ironic-nautobot-sync/containers/*' | ||
|
||
# bump container versions here, they will be populated to tags and labels | ||
env: | ||
VERSION_PYTHON311: 0.0.1 | ||
VERSION_PYTHON312: 0.0.1 | ||
VERSION_PYTHON_IRONIC: 0.0.1 | ||
|
||
jobs: | ||
build-ghcr-registry: | ||
runs-on: ubuntu-latest | ||
permissions: | ||
packages: write | ||
contents: read | ||
steps: | ||
- uses: actions/checkout@v4 | ||
- name: Set up Docker Buildx | ||
uses: docker/setup-buildx-action@v3 | ||
|
||
- name: Login to ghcr.io | ||
if: ${{ github.event_name != 'pull_request' }} | ||
uses: docker/login-action@v3 | ||
with: | ||
registry: "ghcr.io" | ||
username: ${{ github.actor }} | ||
password: ${{ secrets.GITHUB_TOKEN }} | ||
|
||
- name: Build and deploy Python 3.11 image | ||
uses: docker/build-push-action@v5 | ||
with: | ||
context: argo-workflows/generic/ | ||
file: argo-workflows/generic/containers/Dockerfile.python311_alpine | ||
# push for all main branch commits | ||
push: ${{ github.event_name != 'pull_request' }} | ||
tags: ghcr.io/rackerlabs/understack/argo-python3.11.8-alpine3.19:latest,ghcr.io/rackerlabs/understack/argo-python3.11.8-alpine3.19:${{ env.VERSION_PYTHON311 }} | ||
labels: | | ||
org.opencontainers.image.version: "${{ env.VERSION_PYTHON311 }}" | ||
- name: Build and deploy Python 3.12 image | ||
uses: docker/build-push-action@v5 | ||
with: | ||
context: argo-workflows/generic/ | ||
file: argo-workflows/generic/containers/Dockerfile.python312_alpine | ||
# push for all main branch commits | ||
push: ${{ github.event_name != 'pull_request' }} | ||
tags: ghcr.io/rackerlabs/understack/argo-python3.12.2-alpine3.19:latest,ghcr.io/rackerlabs/understack/argo-python3.12.2-alpine3.19:${{ env.VERSION_PYTHON312 }} | ||
labels: | | ||
org.opencontainers.image.version: "${{ env.VERSION_PYTHON312 }}" | ||
- name: Build and deploy Python 3.11 with Ironic client | ||
uses: docker/build-push-action@v5 | ||
with: | ||
context: argo-workflows/ironic-nautobot-sync | ||
file: argo-workflows/ironic-nautobot-sync/containers/Dockerfile.ironic | ||
# push for all main branch commits | ||
push: ${{ github.event_name != 'pull_request' }} | ||
tags: ghcr.io/rackerlabs/understack/argo-ironic-client-python3.11.8:latest,ghcr.io/rackerlabs/understack/argo-ironic-client-python3.11.8:${{ env.VERSION_PYTHON_IRONIC }} | ||
labels: | | ||
org.opencontainers.image.version: "${{ env.VERSION_PYTHON_IRONIC }}" |
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
44 changes: 44 additions & 0 deletions
44
argo-workflows/generic/containers/Dockerfile.python311_alpine
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
FROM python:3.11.8-alpine3.19 as builder | ||
|
||
LABEL org.opencontainers.image.title="Python 3.11 image base image" | ||
LABEL org.opencontainers.image.base.name="ghcr.io/rackerlabs/understack/argo-python3.11.8-alpine3.19" | ||
LABEL org.opencontainers.image.source=https://github.com/rackerlabs/understack | ||
|
||
ENV PYTHONUNBUFFERED=1 | ||
|
||
RUN python -m venv /opt/venv | ||
ENV PATH="/opt/venv/bin:$PATH" | ||
|
||
ARG APP_PATH=/app | ||
ARG APP_USER=appuser | ||
ARG APP_GROUP=appgroup | ||
ARG APP_USER_UID=1000 | ||
ARG APP_GROUP_GID=1000 | ||
|
||
RUN addgroup -g $APP_GROUP_GID -S $APP_GROUP && \ | ||
adduser -S -s /sbin/nologin -u $APP_USER_UID -G $APP_GROUP $APP_USER && \ | ||
mkdir $APP_PATH && \ | ||
chown $APP_USER:$APP_GROUP $APP_PATH | ||
WORKDIR /app | ||
CMD ["python", "-"] | ||
|
||
# Example usage in final image | ||
# FROM ghcr.io/rackerlabs/understack/argo-python3.11.8-alpine3.19 | ||
# | ||
# # This section needs to be repeated in child images | ||
# ARG APP_PATH=/app | ||
# ARG APP_USER=appuser | ||
# ARG APP_GROUP=appgroup | ||
# ARG APP_USER_UID=1000 | ||
# ARG APP_GROUP_GID=1000 | ||
# | ||
# | ||
# 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 python-ironicclient==5.4.0 | ||
# | ||
# FROM ghcr.io/rackerlabs/understack/argo-python3.11.8-alpine3.19 as prod | ||
# ENV PATH="/opt/venv/bin:$PATH" | ||
# COPY --from=builder /opt/venv /opt/venv | ||
# | ||
# WORKDIR /app | ||
# CMD ["python", "-"] |
44 changes: 44 additions & 0 deletions
44
argo-workflows/generic/containers/Dockerfile.python312_alpine
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
FROM python:3.12.2-alpine3.19 as builder | ||
|
||
LABEL org.opencontainers.image.title="Python 3.12 image base image" | ||
LABEL org.opencontainers.image.base.name="ghcr.io/rackerlabs/understack/argo-python3.12.2-alpine3.19" | ||
LABEL org.opencontainers.image.source=https://github.com/rackerlabs/understack | ||
|
||
ENV PYTHONUNBUFFERED=1 | ||
|
||
RUN python -m venv /opt/venv | ||
ENV PATH="/opt/venv/bin:$PATH" | ||
|
||
ARG APP_PATH=/app | ||
ARG APP_USER=appuser | ||
ARG APP_GROUP=appgroup | ||
ARG APP_USER_UID=1000 | ||
ARG APP_GROUP_GID=1000 | ||
|
||
RUN addgroup -g $APP_GROUP_GID -S $APP_GROUP && \ | ||
adduser -S -s /sbin/nologin -u $APP_USER_UID -G $APP_GROUP $APP_USER && \ | ||
mkdir $APP_PATH && \ | ||
chown $APP_USER:$APP_GROUP $APP_PATH | ||
WORKDIR /app | ||
CMD ["python", "-"] | ||
|
||
# Example usage in final image | ||
# FROM ghcr.io/rackerlabs/understack/argo-python3.12.2-alpine3.19 | ||
# | ||
# # This section needs to be repeated in child images | ||
# ARG APP_PATH=/app | ||
# ARG APP_USER=appuser | ||
# ARG APP_GROUP=appgroup | ||
# ARG APP_USER_UID=1000 | ||
# ARG APP_GROUP_GID=1000 | ||
# | ||
# | ||
# 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 python-ironicclient==5.4.0 | ||
# | ||
# FROM ghcr.io/rackerlabs/understack/argo-python3.12.2-alpine3.19 as prod | ||
# ENV PATH="/opt/venv/bin:$PATH" | ||
# COPY --from=builder /opt/venv /opt/venv | ||
# | ||
# WORKDIR /app | ||
# CMD ["python", "-"] |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import json | ||
import logging | ||
import sys | ||
|
||
from ironic.client import IronicClient | ||
from 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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
from ironicclient import client as iclient | ||
from keystoneauth1 import session | ||
from keystoneauth1.identity import v3 | ||
|
||
|
||
class IronicClient: | ||
def __init__( | ||
self, | ||
svc_url: str, | ||
username: str, | ||
password: str, | ||
auth_url: str, | ||
tenant_name: str, | ||
) -> None: | ||
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.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 | ||
) | ||
|
||
def list_nodes(self): | ||
self._ensure_logged_in() | ||
|
||
return self.client.node.list() | ||
|
||
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) | ||
|
||
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) | ||
|
||
def _ensure_logged_in(self): | ||
if not self.logged_in: | ||
self.login() |
19 changes: 19 additions & 0 deletions
19
argo-workflows/ironic-nautobot-sync/code/ironic/secrets.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import os | ||
import re | ||
import logging | ||
|
||
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, "r").read() | ||
except FileNotFoundError: | ||
logger.error(f"Secret {secret_name} is not defined.") | ||
return "" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
from dataclasses import dataclass | ||
from dataclasses import field | ||
|
||
|
||
@dataclass | ||
class PhysicalNic: | ||
id: str | ||
ethernet_mac_address: str | ||
type: str = "phy" | ||
mtu: int = 1500 | ||
|
||
|
||
@dataclass | ||
class NetworkRoute: | ||
network: str | ||
netmask: str | ||
gateway: str | ||
|
||
|
||
@dataclass | ||
class BondedNic: | ||
id: str | ||
link: str | ||
ethernet_mac_address: str | ||
mtu: int = 1500 | ||
type: str = "bond" | ||
bond_links: list[str] = field(default_factory=list) | ||
bond_mode: str = "802.1ad" | ||
bond_xmit_hash_policy: str = "layer3+4" | ||
bond_miimon: int | None = 100 | ||
routes: list[NetworkRoute] = field(default_factory=list) | ||
|
||
|
||
@dataclass | ||
class Network: | ||
id: str | None = None | ||
network_id: str | None = None | ||
link: str | None = None | ||
type: str = "ipv4" | ||
ip_address: str | None = None | ||
netmask: str | None = None | ||
routes: list[NetworkRoute] = field(default_factory=list) | ||
|
||
|
||
@dataclass | ||
class Service: | ||
type: str | ||
address: str | ||
|
||
|
||
@dataclass | ||
class NetworkInfo: | ||
links: list[PhysicalNic | BondedNic] = field(default_factory=list) | ||
networks: list[Network] = field(default_factory=list) | ||
services: list[Service] = field(default_factory=list) |
Oops, something went wrong.