diff --git a/.github/workflows/containers.yaml b/.github/workflows/containers.yaml index 1fda72a06..96f83de84 100644 --- a/.github/workflows/containers.yaml +++ b/.github/workflows/containers.yaml @@ -33,6 +33,7 @@ jobs: - dnsmasq - ironic-nautobot-client - understack-tests + - shell-operator-ironic uses: ./.github/workflows/build-container-reuse.yaml secrets: inherit with: diff --git a/components/ironic/kustomization.yaml b/components/ironic/kustomization.yaml index 892c74827..c6256a6ac 100644 --- a/components/ironic/kustomization.yaml +++ b/components/ironic/kustomization.yaml @@ -12,3 +12,5 @@ resources: # working due to the way the chart hardcodes the config-file parameter which then # takes precedence over the directory - configmap-ironic-bin.yaml + - ./runbook-crd + - ./runbook-operator diff --git a/components/ironic/runbook-crd/README.md b/components/ironic/runbook-crd/README.md new file mode 100644 index 000000000..9eba03f28 --- /dev/null +++ b/components/ironic/runbook-crd/README.md @@ -0,0 +1,103 @@ +# Ironic Runbook Kubernetes CRD + +Kubernetes Custom Resource Definition (CRD) for managing Ironic baremetal runbooks. Runbooks define automated sequences of operations (cleaning, configuration, firmware updates) to be executed on baremetal nodes. + +## What is a Runbook? + +A Runbook is a collection of ordered steps that define automated operations on baremetal nodes in Ironic. Runbooks enable: + +- **Automated Cleaning**: Prepare nodes for reuse (disk wiping, BIOS config, firmware updates) +- **Declarative Workflows**: Define repeatable, version-controlled sequences +- **Trait-Based Matching**: Runbooks match to nodes when the runbook name matches a node trait + +## Quick Start + +### Installation + +```bash +# Install the CRD +kubectl apply -f bases/baremetal.ironicproject.org_runbooks.yaml +``` + +### Create Your First Runbook + +```bash +# Apply a minimal example +kubectl apply -f samples/runbook_v1alpha1_minimal.yaml + +# Verify it was created +kubectl get runbooks +kubectl describe runbook minimal-runbook +``` + +### View Available Samples + +```bash +# List all sample runbooks +ls samples/ + +# Apply a specific sample +kubectl apply -f samples/runbook_bios_config.yaml +``` + +## Field Requirements + +### ✅ Required Fields + +| Field | Type | Description | +|-------|------|-------------| +| `spec.runbookName` | string | Runbook name matching CUSTOM_* pattern | +| `spec.steps` | array | Ordered list of steps (minimum 1) | +| `steps[].interface` | enum | Hardware interface (bios, raid, deploy, etc.) | +| `steps[].step` | string | Step name (non-empty) | +| `steps[].order` | integer | Execution order (>= 0, unique) | + +### ❌ Optional Fields + +| Field | Type | Default | Description | +|-------|------|---------|-------------| +| `spec.disableRamdisk` | boolean | `false` | Skip ramdisk booting | +| `spec.public` | boolean | `false` | Public accessibility | +| `spec.owner` | string | `null` | Project/tenant owner | +| `spec.extra` | object | `{}` | Additional metadata | +| `steps[].args` | object | `{}` | Step-specific arguments | + +## Minimal Example + +```yaml +apiVersion: baremetal.ironicproject.org/v1alpha1 +kind: IronicRunbook +metadata: + name: minimal-runbook + namespace: default +spec: + runbookName: CUSTOM_MINIMAL + steps: + - interface: deploy + step: erase_devices + order: 1 +``` + +## Sample Runbooks + +| Sample | Use Case | Description | +|--------|----------|-------------| +| `runbook_v1alpha1_minimal.yaml` | Learning | Minimal example with required fields only | +| `runbook_v1alpha1_complete.yaml` | Reference | Complete example with all fields | +| `runbook_bios_config.yaml` | Compute Nodes | BIOS configuration for virtualization | +| `runbook_raid_config.yaml` | Storage Nodes | RAID setup (OS + data volumes) | +| `runbook_firmware_update.yaml` | Maintenance | Firmware updates (BIOS, BMC, NIC) | +| `runbook_disk_cleaning.yaml` | Node Reuse | Secure disk erasure | +| `runbook_gpu_node_setup.yaml` | ML/AI | GPU node configuration | + +## Support + +- **Ironic Documentation**: https://docs.openstack.org/ironic/latest/ +- **Kubernetes CRDs**: https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/ + +--- + +**Version**: v1alpha1 +**API Group**: baremetal.ironicproject.org +**Kind**: IronicRunbook +**Short Name**: rb diff --git a/components/ironic/runbook-crd/bases/baremetal.ironicproject.org_runbooks.yaml b/components/ironic/runbook-crd/bases/baremetal.ironicproject.org_runbooks.yaml new file mode 100644 index 000000000..e165f75c7 --- /dev/null +++ b/components/ironic/runbook-crd/bases/baremetal.ironicproject.org_runbooks.yaml @@ -0,0 +1,183 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: ironicrunbooks.baremetal.ironicproject.org + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 +spec: + group: baremetal.ironicproject.org + names: + kind: IronicRunbook + listKind: IronicRunbookList + plural: ironicrunbooks + singular: ironicrunbook + shortNames: + - rb + scope: Namespaced + versions: + - name: v1alpha1 + served: true + storage: true + schema: + openAPIV3Schema: + description: IronicRunbook represents a collection of ordered steps that define automated operations on baremetal nodes + type: object + required: + - spec + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: IronicRunbookSpec defines the desired state of IronicRunbook + type: object + required: + - runbookName + - steps + properties: + runbookName: + description: 'RunbookName is the unique name of the runbook (REQUIRED). Must match trait naming convention, typically CUSTOM_*. This name is used to match runbooks to nodes via traits.' + type: string + pattern: '^CUSTOM_[A-Z0-9_]+$' + minLength: 1 + maxLength: 255 + steps: + description: 'Steps is an ordered list of operations to execute (REQUIRED). Minimum 1 step required.' + type: array + minItems: 1 + items: + description: RunbookStep defines a single step in the runbook + type: object + required: + - interface + - step + - order + properties: + interface: + description: 'Interface specifies which hardware interface handles this step (REQUIRED). Must be one of the valid Ironic cleaning interfaces.' + type: string + enum: + - bios + - raid + - deploy + - management + - power + - storage + - vendor + - rescue + - console + - boot + - inspect + - network + - firmware + step: + description: 'Step is the name of the step to execute (REQUIRED). Must be a valid step name for the specified interface.' + type: string + minLength: 1 + maxLength: 255 + order: + description: 'Order defines the execution sequence (REQUIRED). Must be >= 0 and unique within the runbook. Lower numbers execute first.' + type: integer + minimum: 0 + args: + description: 'Args contains step-specific arguments (OPTIONAL). Structure depends on the interface and step. Default: {}' + type: object + x-kubernetes-preserve-unknown-fields: true + disableRamdisk: + description: 'DisableRamdisk skips booting the ramdisk for cleaning operations (OPTIONAL). Use when steps can run without IPA (Ironic Python Agent). Default: false' + type: boolean + default: false + public: + description: 'Public makes the runbook accessible to all projects/tenants (OPTIONAL). Cannot be true if owner is set. Default: false' + type: boolean + default: false + owner: + description: 'Owner identifies the project/tenant that owns this runbook (OPTIONAL). Cannot be set if public is true. Default: null' + type: string + nullable: true + maxLength: 255 + extra: + description: 'Extra contains additional metadata (OPTIONAL). Use for descriptions, versions, maintainer info, etc. Default: {}' + type: object + x-kubernetes-preserve-unknown-fields: true + status: + description: RunbookStatus defines the observed state of Runbook + type: object + properties: + ironicUUID: + description: IronicUUID is the UUID of this runbook in the Ironic API + type: string + syncStatus: + description: SyncStatus indicates the synchronization state with Ironic + type: string + enum: + - Synced + - Pending + - Failed + - Unknown + lastSyncTime: + description: LastSyncTime is the timestamp of the last successful sync with Ironic + type: string + format: date-time + observedGeneration: + description: ObservedGeneration reflects the generation of the most recently observed Runbook + type: integer + format: int64 + conditions: + description: Conditions represent the latest available observations of the runbook's state + type: array + items: + description: Condition contains details for one aspect of the current state of this API Resource + type: object + required: + - type + - status + - lastTransitionTime + properties: + type: + description: Type of condition (e.g., Ready, Validated, Synced) + type: string + status: + description: Status of the condition (True, False, Unknown) + type: string + enum: + - "True" + - "False" + - Unknown + lastTransitionTime: + description: LastTransitionTime is the last time the condition transitioned from one status to another + type: string + format: date-time + reason: + description: Reason contains a programmatic identifier indicating the reason for the condition's last transition + type: string + message: + description: Message is a human readable message indicating details about the transition + type: string + subresources: + status: {} + additionalPrinterColumns: + - name: Runbook Name + type: string + description: The runbook name used for trait matching + jsonPath: .spec.runbookName + - name: Steps + type: integer + description: Number of steps in the runbook + jsonPath: .spec.steps[*] + - name: Public + type: boolean + description: Whether the runbook is public + jsonPath: .spec.public + - name: Sync Status + type: string + description: Synchronization status with Ironic + jsonPath: .status.syncStatus + - name: Age + type: date + jsonPath: .metadata.creationTimestamp diff --git a/components/ironic/runbook-crd/kustomization.yaml b/components/ironic/runbook-crd/kustomization.yaml new file mode 100644 index 000000000..51e36b944 --- /dev/null +++ b/components/ironic/runbook-crd/kustomization.yaml @@ -0,0 +1,9 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +# Namespace for runbook resources +namespace: openstack + +# Create namespace if it doesn't exist +resources: + - bases/baremetal.ironicproject.org_runbooks.yaml diff --git a/components/ironic/runbook-crd/samples/runbook_bios_config.yaml b/components/ironic/runbook-crd/samples/runbook_bios_config.yaml new file mode 100644 index 000000000..5e875bc42 --- /dev/null +++ b/components/ironic/runbook-crd/samples/runbook_bios_config.yaml @@ -0,0 +1,60 @@ +# BIOS Configuration Runbook +# +# This runbook configures BIOS settings for compute nodes. +# Common use case: Enabling virtualization features for hypervisor nodes. +# +# Matches nodes with trait: CUSTOM_COMPUTE_BIOS + +apiVersion: baremetal.ironicproject.org/v1alpha1 +kind: IronicRunbook +metadata: + name: compute-bios-config + namespace: baremetal-system + labels: + use-case: bios-configuration + hardware-type: compute +spec: + runbookName: CUSTOM_COMPUTE_BIOS + + steps: + - interface: bios + step: apply_configuration + order: 1 + args: + settings: + # Enable logical processors (hyperthreading) + - name: LogicalProc + value: Enabled + + # Enable virtualization technology + - name: VirtualizationTechnology + value: Enabled + + # Enable Intel VT-d (IOMMU) + - name: VtForDirectIo + value: Enabled + + # Enable SR-IOV support + - name: SRIOV + value: Enabled + + # Set boot mode to UEFI + - name: BootMode + value: Uefi + + # Enable secure boot + - name: SecureBoot + value: Enabled + + extra: + description: "BIOS configuration for compute nodes with virtualization support" + version: "1.0.0" + use_case: "Hypervisor node preparation" + hardware_compatibility: + - "Dell PowerEdge R740" + - "Dell PowerEdge R640" + - "HPE ProLiant DL380 Gen10" + notes: | + This runbook enables common virtualization features required for + running KVM/QEMU workloads. Adjust settings based on your specific + hardware and requirements. diff --git a/components/ironic/runbook-crd/samples/runbook_disk_cleaning.yaml b/components/ironic/runbook-crd/samples/runbook_disk_cleaning.yaml new file mode 100644 index 000000000..2599b4698 --- /dev/null +++ b/components/ironic/runbook-crd/samples/runbook_disk_cleaning.yaml @@ -0,0 +1,45 @@ +# Disk Cleaning Runbook +# +# This runbook performs secure disk erasure for node reuse. +# Common use case: Preparing nodes for redeployment or decommissioning. +# +# Matches nodes with trait: CUSTOM_DISK_CLEAN + +apiVersion: baremetal.ironicproject.org/v1alpha1 +kind: IronicRunbook +metadata: + name: disk-cleaning + namespace: baremetal-system + labels: + use-case: disk-cleaning + security-level: standard +spec: + runbookName: CUSTOM_DISK_CLEAN + + steps: + # Step 1: Erase all devices + - interface: deploy + step: erase_devices + order: 1 + args: + # Empty list means erase all devices + erase_skip_list: [] + + extra: + description: "Standard disk cleaning for node reuse" + version: "1.0.0" + use_case: "Secure disk erasure before redeployment" + security_level: "standard" + notes: | + This runbook performs a standard disk erase on all storage devices. + + Erase method depends on Ironic configuration: + - ATA Secure Erase (if supported by drive) + - NVMe Format (for NVMe drives) + - Software-based shred (fallback) + + For high-security environments, consider: + - Multiple pass overwrite + - DoD 5220.22-M standard + - Physical destruction for decommissioning + estimated_duration: "30-120 minutes depending on disk size and method" diff --git a/components/ironic/runbook-crd/samples/runbook_firmware_update.yaml b/components/ironic/runbook-crd/samples/runbook_firmware_update.yaml new file mode 100644 index 000000000..943d89773 --- /dev/null +++ b/components/ironic/runbook-crd/samples/runbook_firmware_update.yaml @@ -0,0 +1,82 @@ +# Firmware Update Runbook +# +# This runbook updates firmware components on baremetal nodes. +# Common use case: Updating BIOS, BMC, and NIC firmware. +# +# Matches nodes with trait: CUSTOM_FIRMWARE_UPDATE + +apiVersion: baremetal.ironicproject.org/v1alpha1 +kind: IronicRunbook +metadata: + name: firmware-update + namespace: baremetal-system + labels: + use-case: firmware-update + hardware-type: general +spec: + runbookName: CUSTOM_FIRMWARE_UPDATE + + steps: + # Step 1: Update BIOS firmware + - interface: management + step: update_firmware + order: 1 + args: + component: bios + firmware_images: + - url: "http://firmware-repo.example.com/bios/R740_BIOS_2.15.0.bin" + checksum: "sha256:abc123..." + version: "2.15.0" + + # Step 2: Update BMC (iDRAC/iLO) firmware + - interface: management + step: update_firmware + order: 2 + args: + component: bmc + firmware_images: + - url: "http://firmware-repo.example.com/idrac/iDRAC9_4.40.00.00.bin" + checksum: "sha256:def456..." + version: "4.40.00.00" + + # Step 3: Update NIC firmware + - interface: management + step: update_firmware + order: 3 + args: + component: nic + firmware_images: + - url: "http://firmware-repo.example.com/nic/BCM5720_7.14.76.bin" + checksum: "sha256:ghi789..." + version: "7.14.76" + device_id: "14e4:165f" # Broadcom BCM5720 + + # Firmware updates typically don't need ramdisk + disableRamdisk: false + + extra: + description: "Firmware update runbook for BIOS, BMC, and NIC components" + version: "1.0.0" + use_case: "Firmware maintenance and security updates" + hardware_compatibility: + - "Dell PowerEdge R740" + - "Dell PowerEdge R640" + firmware_versions: + bios: "2.15.0" + bmc: "4.40.00.00" + nic: "7.14.76" + notes: | + This runbook updates critical firmware components: + 1. BIOS - System firmware + 2. BMC (iDRAC/iLO) - Management controller + 3. NIC - Network interface card + + Important: + - Ensure firmware images are accessible from the nodes + - Verify checksums match the official firmware releases + - Test on a single node before rolling out to production + - Some updates may require a reboot + warnings: + - "Firmware updates can take 10-30 minutes per component" + - "Do not power off nodes during firmware updates" + - "Verify hardware compatibility before applying updates" diff --git a/components/ironic/runbook-crd/samples/runbook_gpu_node_setup.yaml b/components/ironic/runbook-crd/samples/runbook_gpu_node_setup.yaml new file mode 100644 index 000000000..0862763e2 --- /dev/null +++ b/components/ironic/runbook-crd/samples/runbook_gpu_node_setup.yaml @@ -0,0 +1,91 @@ +# GPU Node Setup Runbook +# +# This runbook configures nodes for GPU workloads. +# Common use case: Preparing nodes for ML/AI or GPU compute workloads. +# +# Matches nodes with trait: CUSTOM_GPU_SETUP + +apiVersion: baremetal.ironicproject.org/v1alpha1 +kind: IronicRunbook +metadata: + name: gpu-node-setup + namespace: baremetal-system + labels: + use-case: gpu-configuration + hardware-type: gpu-compute +spec: + runbookName: CUSTOM_GPU_SETUP + + steps: + # Step 1: Configure BIOS for GPU support + - interface: bios + step: apply_configuration + order: 1 + args: + settings: + # Enable virtualization for GPU passthrough + - name: VirtualizationTechnology + value: Enabled + + # Enable VT-d for IOMMU + - name: VtForDirectIo + value: Enabled + + # Enable SR-IOV for GPU virtualization + - name: SRIOV + value: Enabled + + # Enable Above 4G Decoding for large GPU memory + - name: Above4GDecoding + value: Enabled + + # Set PCIe speed to maximum + - name: PcieSpeed + value: Auto + + # Enable NUMA for optimal GPU-CPU affinity + - name: NumaMode + value: Enabled + + # Step 2: Update GPU firmware (optional) + - interface: management + step: update_firmware + order: 2 + args: + component: gpu + firmware_images: + - url: "http://firmware-repo.example.com/gpu/nvidia-vbios-latest.bin" + checksum: "sha256:xyz123..." + version: "latest" + + extra: + description: "GPU node BIOS and firmware configuration" + version: "1.0.0" + use_case: "Preparing nodes for GPU compute workloads" + hardware_compatibility: + - "Dell PowerEdge R740 with NVIDIA GPUs" + - "HPE ProLiant DL380 Gen10 with NVIDIA GPUs" + gpu_support: + - "NVIDIA Tesla V100" + - "NVIDIA A100" + - "NVIDIA H100" + features_enabled: + - "GPU passthrough (VT-d)" + - "SR-IOV for GPU virtualization" + - "NUMA for optimal performance" + - "Above 4G decoding for large GPU memory" + notes: | + This runbook prepares nodes for GPU workloads by: + 1. Enabling virtualization features for GPU passthrough + 2. Configuring IOMMU (VT-d) for device assignment + 3. Enabling SR-IOV for GPU virtualization + 4. Optimizing PCIe and NUMA settings + + After running this runbook: + - Verify GPU visibility with 'lspci | grep -i nvidia' + - Install GPU drivers appropriate for your workload + - Configure GPU device plugins for Kubernetes + recommended_next_steps: + - "Install NVIDIA drivers" + - "Install NVIDIA Container Toolkit" + - "Deploy NVIDIA GPU Operator (for Kubernetes)" diff --git a/components/ironic/runbook-crd/samples/runbook_raid_config.yaml b/components/ironic/runbook-crd/samples/runbook_raid_config.yaml new file mode 100644 index 000000000..54c43e73f --- /dev/null +++ b/components/ironic/runbook-crd/samples/runbook_raid_config.yaml @@ -0,0 +1,65 @@ +# RAID Configuration Runbook +# +# This runbook configures RAID arrays for storage nodes. +# Common use case: Setting up RAID 1 for OS and RAID 6 for data. +# +# Matches nodes with trait: CUSTOM_STORAGE_RAID + +apiVersion: baremetal.ironicproject.org/v1alpha1 +kind: IronicRunbook +metadata: + name: storage-raid-config + namespace: baremetal-system + labels: + use-case: raid-configuration + hardware-type: storage +spec: + runbookName: CUSTOM_STORAGE_RAID + + steps: + # Step 1: Delete existing RAID configuration + - interface: raid + step: delete_configuration + order: 1 + args: {} + + # Step 2: Create new RAID configuration + - interface: raid + step: create_configuration + order: 2 + args: + logical_disks: + # RAID 1 for OS (root volume) + - size_gb: 500 + raid_level: "1" + is_root_volume: true + controller: "RAID.Integrated.1-1" + disk_type: "ssd" + interface_type: "sata" + volume_name: "OS" + + # RAID 6 for data storage + - size_gb: MAX + raid_level: "6" + is_root_volume: false + controller: "RAID.Integrated.1-1" + disk_type: "hdd" + interface_type: "sas" + volume_name: "DATA" + number_of_physical_disks: 8 + + extra: + description: "RAID configuration for storage nodes with OS and data volumes" + version: "1.0.0" + use_case: "Storage node RAID setup" + hardware_compatibility: + - "Dell PowerEdge R740xd" + - "Dell PowerEdge R7525" + raid_layout: | + - RAID 1 (500GB): Operating system on 2x SSDs + - RAID 6 (remaining): Data storage on 8x HDDs + notes: | + This configuration provides: + - High availability for OS with RAID 1 mirroring + - Large capacity with redundancy for data with RAID 6 + - Optimal performance by separating OS (SSD) and data (HDD) diff --git a/components/ironic/runbook-crd/samples/runbook_v1alpha1_complete.yaml b/components/ironic/runbook-crd/samples/runbook_v1alpha1_complete.yaml new file mode 100644 index 000000000..5fcacc31b --- /dev/null +++ b/components/ironic/runbook-crd/samples/runbook_v1alpha1_complete.yaml @@ -0,0 +1,83 @@ +# Complete Runbook Example - All Fields +# +# This example demonstrates all available fields in a runbook, +# including both required and optional fields. +# +# Required fields (✅): runbookName, steps, interface, step, order +# Optional fields (❌): disableRamdisk, public, owner, extra, args + +apiVersion: baremetal.ironicproject.org/v1alpha1 +kind: IronicRunbook +metadata: + name: complete-runbook + namespace: baremetal-system + labels: + environment: production + hardware-type: compute + version: v1.0.0 + annotations: + description: "Complete example showing all available fields" +spec: + # ✅ REQUIRED: Runbook name (must match CUSTOM_* pattern) + runbookName: CUSTOM_COMPLETE + + # ✅ REQUIRED: Ordered list of steps (minimum 1 step) + steps: + # Step 1: BIOS Configuration + - interface: bios # ✅ REQUIRED + step: apply_configuration # ✅ REQUIRED + order: 1 # ✅ REQUIRED + args: # ❌ OPTIONAL + settings: + - name: LogicalProc + value: Enabled + - name: VirtualizationTechnology + value: Enabled + - name: SRIOV + value: Enabled + + # Step 2: RAID Configuration + - interface: raid # ✅ REQUIRED + step: create_configuration # ✅ REQUIRED + order: 2 # ✅ REQUIRED + args: # ❌ OPTIONAL + logical_disks: + - size_gb: 100 + raid_level: "1" + is_root_volume: true + - size_gb: 500 + raid_level: "5" + is_root_volume: false + + # Step 3: Disk Cleaning + - interface: deploy # ✅ REQUIRED + step: erase_devices # ✅ REQUIRED + order: 3 # ✅ REQUIRED + args: # ❌ OPTIONAL + erase_skip_list: [] + + # ❌ OPTIONAL: Skip ramdisk booting (default: false) + disableRamdisk: false + + # ❌ OPTIONAL: Make runbook public (default: false) + # Note: Cannot be true if owner is set + public: false + + # ❌ OPTIONAL: Project/tenant owner (default: null) + # Note: Cannot be set if public is true + owner: "project-123" + + # ❌ OPTIONAL: Additional metadata (default: {}) + extra: + description: "Complete example runbook with all fields" + version: "1.0.0" + maintainer: "ops-team@example.com" + documentation: "https://docs.example.com/runbooks/complete" + tags: + - production + - compute + - complete-example + changelog: + - version: "1.0.0" + date: "2024-01-14" + changes: "Initial version" diff --git a/components/ironic/runbook-crd/samples/runbook_v1alpha1_minimal.yaml b/components/ironic/runbook-crd/samples/runbook_v1alpha1_minimal.yaml new file mode 100644 index 000000000..a2c865a01 --- /dev/null +++ b/components/ironic/runbook-crd/samples/runbook_v1alpha1_minimal.yaml @@ -0,0 +1,26 @@ +# Minimal Runbook Example - Required Fields Only +# +# This example shows the absolute minimum required to create a valid runbook. +# It includes only the 5 required fields: +# 1. spec.runbookName +# 2. spec.steps (array with min 1 step) +# 3. steps[].interface +# 4. steps[].step +# 5. steps[].order +# +# Use this as a starting point and add optional fields as needed. + +apiVersion: baremetal.ironicproject.org/v1alpha1 +kind: IronicRunbook +metadata: + name: minimal-runbook + namespace: default +spec: + # ✅ REQUIRED: Runbook name matching trait convention + runbookName: CUSTOM_MINIMAL + + # ✅ REQUIRED: At least one step + steps: + - interface: deploy # ✅ REQUIRED: Hardware interface + step: erase_devices # ✅ REQUIRED: Step name + order: 1 # ✅ REQUIRED: Execution order (unique) diff --git a/components/ironic/runbook-operator/kustomization.yaml b/components/ironic/runbook-operator/kustomization.yaml new file mode 100644 index 000000000..5560cd357 --- /dev/null +++ b/components/ironic/runbook-operator/kustomization.yaml @@ -0,0 +1,12 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +# Namespace for runbook resources +namespace: openstack + +# Create namespace if it doesn't exist +resources: + - service_account.yaml + - role.yaml + - role_binding.yaml + - shell-operator-ironic.yaml diff --git a/components/ironic/runbook-operator/role.yaml b/components/ironic/runbook-operator/role.yaml new file mode 100644 index 000000000..3a91993c4 --- /dev/null +++ b/components/ironic/runbook-operator/role.yaml @@ -0,0 +1,25 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: runbook-controller-role + labels: + app.kubernetes.io/name: ironicrunbook + app.kubernetes.io/component: rbac +rules: + # Read-only runbook permissions + - apiGroups: + - baremetal.ironicproject.org + resources: + - ironicrunbooks + verbs: + - get + - list + - watch + + # Read-only status permissions + - apiGroups: + - baremetal.ironicproject.org + resources: + - ironicrunbooks/status + verbs: + - get diff --git a/components/ironic/runbook-operator/role_binding.yaml b/components/ironic/runbook-operator/role_binding.yaml new file mode 100644 index 000000000..ac4add3a2 --- /dev/null +++ b/components/ironic/runbook-operator/role_binding.yaml @@ -0,0 +1,21 @@ +# RoleBinding for Runbook Controller +# +# This binds the runbook-controller-role to a service account. +# Modify the subjects section to bind to your desired users or service accounts. + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: runbook-controller-role-rolebinding + labels: + app.kubernetes.io/name: ironicrunbook + app.kubernetes.io/component: rbac +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: runbook-controller-role +subjects: + # Example: Bind to a service account (for controller) + - kind: ServiceAccount + name: runbook-controller + namespace: baremetal-system diff --git a/components/ironic/runbook-operator/service_account.yaml b/components/ironic/runbook-operator/service_account.yaml new file mode 100644 index 000000000..1426c3dd7 --- /dev/null +++ b/components/ironic/runbook-operator/service_account.yaml @@ -0,0 +1,13 @@ +# ServiceAccount for Runbook Controller +# +# This service account is used by the runbook controller/operator +# to manage runbook resources and sync with Ironic API. + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: runbook-controller + namespace: baremetal-system + labels: + app.kubernetes.io/name: ironicrunbook + app.kubernetes.io/component: controller diff --git a/components/ironic/runbook-operator/shell-operator-ironic.yaml b/components/ironic/runbook-operator/shell-operator-ironic.yaml new file mode 100644 index 000000000..763df655d --- /dev/null +++ b/components/ironic/runbook-operator/shell-operator-ironic.yaml @@ -0,0 +1,34 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: shell-operator-ironic + namespace: openstack +spec: + replicas: 1 + selector: + matchLabels: + app: shell-operator + template: + metadata: + labels: + app: shell-operator + spec: + serviceAccountName: runbook-controller + restartPolicy: Always + containers: + - name: shell-operator + image: ghcr.io/rackerlabs/understack/shell-operator-ironic:latest + imagePullPolicy: Always + env: + - name: OS_CLOUD + value: understack + volumeMounts: + - mountPath: /etc/openstack + name: baremetal-manage + volumes: + - name: baremetal-manage + secret: + secretName: baremetal-manage + items: + - key: clouds.yaml + path: clouds.yaml diff --git a/containers/shell-operator-ironic/Dockerfile b/containers/shell-operator-ironic/Dockerfile new file mode 100644 index 000000000..961bb412d --- /dev/null +++ b/containers/shell-operator-ironic/Dockerfile @@ -0,0 +1,11 @@ +FROM ghcr.io/flant/shell-operator:latest AS prod +LABEL org.opencontainers.image.description="shell-operator for Ironic Runbooks" + +RUN --mount=type=cache,target=/var/cache/apk apk add python3 +RUN python3 -m venv /opt/venv +ENV PATH="/opt/venv/bin:$PATH" + +COPY containers/shell-operator-ironic/requirements.txt requirements.txt +RUN pip install --no-cache --upgrade -r requirements.txt + +COPY containers/shell-operator-ironic/hooks /hooks diff --git a/containers/shell-operator-ironic/hooks/create_runbook.sh b/containers/shell-operator-ironic/hooks/create_runbook.sh new file mode 100755 index 000000000..2b6a7f352 --- /dev/null +++ b/containers/shell-operator-ironic/hooks/create_runbook.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +if [[ $1 == "--config" ]] ; then + cat < /tmp/steps.yaml + + # Ironic's runbook extra field is essentially a dict of dicts, representing a key values. baremetal cli allows you + # to pass in multiple --extra options, adding any you do pass. We would need to make an initial query to determine + # existing extras, and then sync the differences. This work is probably better suited to a full controller implementation. + # extra=$(jq -r '.spec.extra | [to_entries[] | "--extra \(.key)=\(.value | @json | @sh)"] | join(" ")' ${BINDING_CONTEXT_PATH}) + + command_args=(baremetal runbook create --name "${runbook_name}" --public "${public}" --steps /tmp/steps.yaml) + if [[ -n "${owner}" && "${owner}" != "null" ]]; then + command_args+=(--owner "${owner}") + fi + + echo "${kind}/${resource_name} created, running: openstack ${command_args[*]}" + + openstack "${command_args[@]}" + fi +fi diff --git a/containers/shell-operator-ironic/hooks/delete_runbook.sh b/containers/shell-operator-ironic/hooks/delete_runbook.sh new file mode 100755 index 000000000..541672118 --- /dev/null +++ b/containers/shell-operator-ironic/hooks/delete_runbook.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +if [[ $1 == "--config" ]] ; then + cat < /tmp/steps.yaml + + # Ironic's runbook extra field is essentially a dict of dicts, representing a key values. baremetal cli allows you + # to pass in multiple --extra options, adding any you do pass. We would need to make an initial query to determine + # existing extras, and then sync the differences. This work is probably better suited to a full controller implementation. + # extra=$(jq -r '.spec.extra | [to_entries[] | "--extra \(.key)=\(.value | @json | @sh)"] | join(" ")' ${BINDING_CONTEXT_PATH}) + + command_args=(baremetal runbook set "${runbook_name}" --public "${public}" --steps /tmp/steps.yaml) + if [[ -n "${owner}" && "${owner}" != "null" ]]; then + command_args+=(--owner "${owner}") + fi + + echo "${kind}/${resource_name} updated, running: openstack ${command_args[*]}" + + openstack "${command_args[@]}" + + fi +fi diff --git a/containers/shell-operator-ironic/requirements.txt b/containers/shell-operator-ironic/requirements.txt new file mode 100644 index 000000000..e48726a9c --- /dev/null +++ b/containers/shell-operator-ironic/requirements.txt @@ -0,0 +1,4 @@ +pip +kubernetes +python-openstackclient +python-ironicclient