diff --git a/.github/workflows/containers.yaml b/.github/workflows/containers.yaml index 2accadca6..954bece85 100644 --- a/.github/workflows/containers.yaml +++ b/.github/workflows/containers.yaml @@ -12,7 +12,6 @@ on: - "containers/dnsmasq/**" - "containers/ironic-nautobot-client/**" - "containers/ironic-vnc-container/**" - - "containers/shell-operator-ironic/**" - "containers/openstack-sync-operator/**" - "containers/understack-tests/**" - "python/**" @@ -44,8 +43,6 @@ jobs: context_path: "./containers/ironic-vnc-container/" prebuild_script: ./sync_from_upstream.sh prebuild_script_working_dir: containers/ironic-vnc-container/ - - name: shell-operator-ironic - target: prod - name: openstack-sync-operator target: prod - name: nautobot diff --git a/changelog.d/20260909_115017_syedhaseebahmed12_purge_legacy_ironic_runbook_operator.md b/changelog.d/20260909_115017_syedhaseebahmed12_purge_legacy_ironic_runbook_operator.md new file mode 100644 index 000000000..4ffbd182a --- /dev/null +++ b/changelog.d/20260909_115017_syedhaseebahmed12_purge_legacy_ironic_runbook_operator.md @@ -0,0 +1,18 @@ +### Deprecations and removals + +The legacy Ironic runbook operator, deprecated in v0.5.5, is now deleted: + +- `components/ironic/runbook-crd/` +- `components/ironic/runbook-operator/` +- `containers/shell-operator-ironic/` + +`shell-operator-ironic` is no longer built. Images already published to +`ghcr.io/rackerlabs/understack/shell-operator-ironic` are untouched, so a site +that has not finished migrating keeps a working image; it just stops receiving +new builds. + +No action is required. Nothing rendered these files, so a resync sees no change. +Runbooks are reconciled by the `ironicRunbooks` hook in `openstack-sync-operator` +against the `IronicRunbook` CRD in +`components/openstack-sync-operator/crds/`, with reference CRs under +`components/openstack-sync-plugins/ironic-runbooks/examples/`. diff --git a/components/ironic/runbook-crd/README.md b/components/ironic/runbook-crd/README.md deleted file mode 100644 index da43dbb71..000000000 --- a/components/ironic/runbook-crd/README.md +++ /dev/null @@ -1,154 +0,0 @@ -# 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 | - -## Running a Runbook - -Once the operator syncs the CRD into Ironic, you can execute a runbook against -a node using one of two CLI commands depending on the node's current -provisioning state: - -- **`node clean --runbook`** — node must be in `manageable` state -- **`node service --runbook`** — node must be in `active` or `available` state - -### OpenStack CLI - -```bash -# For nodes in 'manageable' state -openstack baremetal node clean --runbook CUSTOM_BMC_MAINTENANCE - -# For nodes in 'active' or 'available' state -openstack baremetal node service --runbook CUSTOM_BMC_MAINTENANCE - -# Check node state while the runbook executes -openstack baremetal node show -f value -c provision_state -``` - -### Python SDK - -```python -from understack_workflows.ironic_node import transition - -# node must already be in manageable state -transition( - node, - "clean", - expected_state="manageable", - runbook=runbook_uuid, -) -``` - -The `transition` helper calls `set_node_provision_state` and waits for the -node to return to `manageable` once all steps complete. - -### Trait-Based Automatic Execution - -Runbooks can also be triggered automatically by matching node traits. Add the -runbook name as a trait on the node: - -```bash -openstack baremetal node add trait CUSTOM_BMC_MAINTENANCE -``` - -Workflow code (e.g. `apply_firmware_updates` in `ironic_node.py`) can then -discover matching traits and execute the corresponding runbooks in order. - -## 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 deleted file mode 100644 index 437ceafd2..000000000 --- a/components/ironic/runbook-crd/bases/baremetal.ironicproject.org_runbooks.yaml +++ /dev/null @@ -1,198 +0,0 @@ -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). From API microversion 1.112+, this is a logical identifier and can be any string of 1-255 characters. Node eligibility is determined by the traits field instead.' - type: string - pattern: '^[a-zA-Z0-9._-]+$' - minLength: 1 - maxLength: 255 - description: - description: 'Description is a human-readable description of the runbook (OPTIONAL). Consistent with other Ironic objects. Available from API microversion 1.112 onwards.' - type: string - nullable: true - maxLength: 1000 - traits: - description: 'Traits is a list of traits that determine which nodes are permitted to use this runbook (OPTIONAL). Decouples runbook eligibility from the runbook name. Each trait must follow the CUSTOM_* naming convention. Available from API microversion 1.112 onwards. Default: []' - type: array - default: [] - items: - 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 - jsonPath: .spec.runbookName - - name: Description - type: string - description: Human-readable description of the runbook - jsonPath: .spec.description - priority: 1 - - 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 deleted file mode 100644 index 416ca9728..000000000 --- a/components/ironic/runbook-crd/kustomization.yaml +++ /dev/null @@ -1,10 +0,0 @@ -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 - - runbooks/runbook_bmc_maintenance.yaml diff --git a/components/ironic/runbook-crd/runbooks/runbook_bmc_maintenance.yaml b/components/ironic/runbook-crd/runbooks/runbook_bmc_maintenance.yaml deleted file mode 100644 index 8b525c907..000000000 --- a/components/ironic/runbook-crd/runbooks/runbook_bmc_maintenance.yaml +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: baremetal.ironicproject.org/v1alpha1 -kind: IronicRunbook -metadata: - name: bmc-maintenance - namespace: openstack -spec: - runbookName: bmc-maintenance - description: "Performs BMC maintenance operations including clearing the job queue and synchronizing the BMC clock." - disableRamdisk: true - traits: - - CUSTOM_DELL_IDRAC - - steps: - - interface: management - step: clear_job_queue - order: 1 - - - interface: management - step: set_bmc_clock - order: 2 diff --git a/components/ironic/runbook-crd/samples/runbook_bios_config.yaml b/components/ironic/runbook-crd/samples/runbook_bios_config.yaml deleted file mode 100644 index 5e875bc42..000000000 --- a/components/ironic/runbook-crd/samples/runbook_bios_config.yaml +++ /dev/null @@ -1,60 +0,0 @@ -# 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 deleted file mode 100644 index 2599b4698..000000000 --- a/components/ironic/runbook-crd/samples/runbook_disk_cleaning.yaml +++ /dev/null @@ -1,45 +0,0 @@ -# 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 deleted file mode 100644 index 943d89773..000000000 --- a/components/ironic/runbook-crd/samples/runbook_firmware_update.yaml +++ /dev/null @@ -1,82 +0,0 @@ -# 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 deleted file mode 100644 index 0862763e2..000000000 --- a/components/ironic/runbook-crd/samples/runbook_gpu_node_setup.yaml +++ /dev/null @@ -1,91 +0,0 @@ -# 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 deleted file mode 100644 index 54c43e73f..000000000 --- a/components/ironic/runbook-crd/samples/runbook_raid_config.yaml +++ /dev/null @@ -1,65 +0,0 @@ -# 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 deleted file mode 100644 index 5fcacc31b..000000000 --- a/components/ironic/runbook-crd/samples/runbook_v1alpha1_complete.yaml +++ /dev/null @@ -1,83 +0,0 @@ -# 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 deleted file mode 100644 index a2c865a01..000000000 --- a/components/ironic/runbook-crd/samples/runbook_v1alpha1_minimal.yaml +++ /dev/null @@ -1,26 +0,0 @@ -# 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 deleted file mode 100644 index 5560cd357..000000000 --- a/components/ironic/runbook-operator/kustomization.yaml +++ /dev/null @@ -1,12 +0,0 @@ -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 deleted file mode 100644 index 284d911cd..000000000 --- a/components/ironic/runbook-operator/role.yaml +++ /dev/null @@ -1,27 +0,0 @@ -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: - # Runbook permissions - - apiGroups: - - baremetal.ironicproject.org - resources: - - ironicrunbooks - verbs: - - get - - list - - watch - - # Status update permissions - - apiGroups: - - baremetal.ironicproject.org - resources: - - ironicrunbooks/status - verbs: - - get - - patch - - update diff --git a/components/ironic/runbook-operator/role_binding.yaml b/components/ironic/runbook-operator/role_binding.yaml deleted file mode 100644 index ac4add3a2..000000000 --- a/components/ironic/runbook-operator/role_binding.yaml +++ /dev/null @@ -1,21 +0,0 @@ -# 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 deleted file mode 100644 index 1426c3dd7..000000000 --- a/components/ironic/runbook-operator/service_account.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# 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 deleted file mode 100644 index a14fb1598..000000000 --- a/components/ironic/runbook-operator/shell-operator-ironic.yaml +++ /dev/null @@ -1,34 +0,0 @@ -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: infrasetup-system - volumes: - - name: infrasetup-system - secret: - secretName: infrasetup-system - items: - - key: clouds.yaml - path: clouds.yaml diff --git a/containers/shell-operator-ironic/Dockerfile b/containers/shell-operator-ironic/Dockerfile deleted file mode 100644 index 5af84f2e6..000000000 --- a/containers/shell-operator-ironic/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM ghcr.io/flant/shell-operator:v1.13.1 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 deleted file mode 100755 index f7f5a030b..000000000 --- a/containers/shell-operator-ironic/hooks/create_runbook.sh +++ /dev/null @@ -1,174 +0,0 @@ -#!/usr/bin/env bash - -if [[ $1 == "--config" ]] ; then - cat </dev/null || \ - echo "[create_runbook] WARNING: failed to patch status for ${name}" - } - - sync_runbook() { - # obj_path is a jq filter expression (e.g. "." or ".[0].object") - # pointing at the IronicRunbook object within BINDING_CONTEXT_PATH. - local obj_path="$1" - local resource_name namespace kind runbook_name description public owner - - resource_name=$(jq -r "${obj_path} | .metadata.name" "${BINDING_CONTEXT_PATH}") - namespace=$(jq -r "${obj_path} | .metadata.namespace" "${BINDING_CONTEXT_PATH}") - kind=$(jq -r "${obj_path} | .kind" "${BINDING_CONTEXT_PATH}") - runbook_name=$(jq -r "${obj_path} | .spec.runbookName" "${BINDING_CONTEXT_PATH}") - description=$(jq -r "${obj_path} | .spec.description // empty" "${BINDING_CONTEXT_PATH}") - public=$(jq -r "${obj_path} | .spec.public // empty" "${BINDING_CONTEXT_PATH}") - owner=$(jq -r "${obj_path} | .spec.owner // empty" "${BINDING_CONTEXT_PATH}") - - echo "[create_runbook] Creating runbook kind=${kind} name=${resource_name} namespace=${namespace} runbookName=${runbook_name} description=${description} public=${public} owner=${owner}" - - jq -r "${obj_path} | .spec.steps" "${BINDING_CONTEXT_PATH}" > /tmp/steps.json - - if ! jq -e 'type == "array" and length > 0' /tmp/steps.json >/dev/null 2>&1; then - echo "[create_runbook] FAILED: name=${resource_name} error=spec.steps is missing, null, or empty" >&2 - patch_status "${namespace}" "${resource_name}" "Failed" "spec.steps must be a non-empty array" - return 1 - fi - - command_args=(baremetal runbook create --name "${runbook_name}" --steps /tmp/steps.json) - - if [[ -n "${description}" ]]; then - command_args+=(--description "${description}") - fi - if [[ -n "${public}" ]]; then - command_args+=(--public "${public}") - fi - if [[ -n "${owner}" ]]; then - command_args+=(--owner "${owner}") - fi - - echo "[create_runbook] Running: openstack ${command_args[*]}" - - if output=$(openstack "${command_args[@]}" 2>&1); then - echo "[create_runbook] SUCCESS: Runbook created in Ironic name=${resource_name} output=${output}" - - traits_json=$(jq -c "${obj_path} | .spec.traits // []" "${BINDING_CONTEXT_PATH}") - if [[ "${traits_json}" != "[]" ]]; then - echo "[create_runbook] Setting traits name=${resource_name} traits=${traits_json}" - ironic_endpoint=$(openstack endpoint list --service baremetal --interface internal -f value -c URL 2>/dev/null | head -1) - if [[ -n "${ironic_endpoint}" ]]; then - token=$(openstack token issue -f value -c id) - echo "[create_runbook] PUT ${ironic_endpoint}/v1/runbooks/${runbook_name}/traits" - trait_response=$(curl -s -X PUT \ - -H "Content-Type: application/json" \ - -H "X-Auth-Token: ${token}" \ - -H "X-OpenStack-Ironic-API-Version: 1.112" \ - -d "{\"traits\": ${traits_json}}" \ - "${ironic_endpoint}/v1/runbooks/${runbook_name}/traits") - echo "[create_runbook] Traits response name=${resource_name} response=${trait_response}" - else - echo "[create_runbook] WARNING: Could not determine Ironic endpoint for traits" - fi - else - echo "[create_runbook] No traits to set name=${resource_name}" - fi - - patch_status "${namespace}" "${resource_name}" "Synced" "Successfully created runbook in Ironic" - echo "[create_runbook] Completed name=${resource_name} status=Synced" - else - # If it already exists, that's OK during sync - not an error - if echo "${output}" | grep -qi "already exists\|Conflict\|409"; then - echo "[create_runbook] Runbook already exists in Ironic name=${resource_name}, skipping create" - patch_status "${namespace}" "${resource_name}" "Synced" "Runbook already exists in Ironic" - else - echo "[create_runbook] FAILED: name=${resource_name} error=${output}" >&2 - patch_status "${namespace}" "${resource_name}" "Failed" "${output}" - return 1 - fi - fi - } - - echo "[create_runbook] Hook invoked, processing binding contexts" - binding_count=$(jq -r 'length' "${BINDING_CONTEXT_PATH}") - echo "[create_runbook] Found ${binding_count} binding context(s)" - - for ((i = 0; i < binding_count; i++)); do - type=$(jq -r ".[$i].type" "${BINDING_CONTEXT_PATH}") - echo "[create_runbook] Processing context=${i} type=${type}" - - if [[ $type == "Synchronization" ]] ; then - echo "[create_runbook] Synchronization event, reconciling existing resources" - objects_count=$(jq -r ".[$i].objects | length" "${BINDING_CONTEXT_PATH}") - echo "[create_runbook] Found ${objects_count} existing IronicRunbook(s) to reconcile" - for ((j = 0; j < objects_count; j++)); do - obj_name=$(jq -r ".[$i].objects[$j].object.metadata.name" "${BINDING_CONTEXT_PATH}") - obj_sync=$(jq -r ".[$i].objects[$j].object.status.syncStatus // empty" "${BINDING_CONTEXT_PATH}") - echo "[create_runbook] Checking name=${obj_name} syncStatus=${obj_sync}" - if [[ -z "${obj_sync}" || "${obj_sync}" == "null" ]]; then - echo "[create_runbook] Resource name=${obj_name} has no syncStatus, needs reconciliation" - # Re-map the jq path to point at the object within the sync event - ORIG_BINDING_CONTEXT_PATH="${BINDING_CONTEXT_PATH}" - jq -r ".[$i].objects[$j].object" "${BINDING_CONTEXT_PATH}" > /tmp/sync_object.json - BINDING_CONTEXT_PATH=/tmp/sync_object.json - sync_runbook "." - BINDING_CONTEXT_PATH="${ORIG_BINDING_CONTEXT_PATH}" - else - echo "[create_runbook] Resource name=${obj_name} already synced, skipping" - fi - done - continue - fi - - if [[ $type == "Event" ]] ; then - if ! sync_runbook ".[$i].object"; then - exit 1 - fi - fi - done - echo "[create_runbook] Hook finished" -fi diff --git a/containers/shell-operator-ironic/hooks/delete_runbook.sh b/containers/shell-operator-ironic/hooks/delete_runbook.sh deleted file mode 100755 index ec4c36d88..000000000 --- a/containers/shell-operator-ironic/hooks/delete_runbook.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env bash - -if [[ $1 == "--config" ]] ; then - cat <&1); then - echo "[delete_runbook] SUCCESS: Runbook deleted from Ironic name=${resource_name} output=${output}" - else - echo "[delete_runbook] FAILED: name=${resource_name} error=${output}" >&2 - exit 1 - fi - fi - done - echo "[delete_runbook] Hook finished" -fi diff --git a/containers/shell-operator-ironic/hooks/update_runbook.sh b/containers/shell-operator-ironic/hooks/update_runbook.sh deleted file mode 100755 index 400f7fdc6..000000000 --- a/containers/shell-operator-ironic/hooks/update_runbook.sh +++ /dev/null @@ -1,179 +0,0 @@ -#!/usr/bin/env bash - -if [[ $1 == "--config" ]] ; then - cat </dev/null || \ - echo "[update_runbook] WARNING: failed to patch status for ${name}" - } - - sync_runbook() { - # obj_path is a jq filter expression (e.g. "." or ".[0].object") - # pointing at the IronicRunbook object within BINDING_CONTEXT_PATH. - local obj_path="$1" - local resource_name namespace kind runbook_name description public owner runbook_uuid - - resource_name=$(jq -r "${obj_path} | .metadata.name" "${BINDING_CONTEXT_PATH}") - namespace=$(jq -r "${obj_path} | .metadata.namespace" "${BINDING_CONTEXT_PATH}") - kind=$(jq -r "${obj_path} | .kind" "${BINDING_CONTEXT_PATH}") - runbook_name=$(jq -r "${obj_path} | .spec.runbookName" "${BINDING_CONTEXT_PATH}") - description=$(jq -r "${obj_path} | .spec.description // empty" "${BINDING_CONTEXT_PATH}") - public=$(jq -r "${obj_path} | .spec.public // empty" "${BINDING_CONTEXT_PATH}") - owner=$(jq -r "${obj_path} | .spec.owner // empty" "${BINDING_CONTEXT_PATH}") - - echo "[update_runbook] Updating runbook kind=${kind} name=${resource_name} namespace=${namespace} runbookName=${runbook_name} description=${description} public=${public} owner=${owner}" - - jq -r "${obj_path} | .spec.steps" "${BINDING_CONTEXT_PATH}" > /tmp/steps.json - - if ! jq -e 'type == "array" and length > 0' /tmp/steps.json >/dev/null 2>&1; then - echo "[update_runbook] FAILED: name=${resource_name} error=spec.steps is missing, null, or empty" >&2 - patch_status "${namespace}" "${resource_name}" "Failed" "spec.steps must be a non-empty array" - return 1 - fi - - # Look up the existing runbook by name to get its UUID. If the show fails - # the runbook does not exist yet and we need to create it instead of set. - if runbook_uuid=$(openstack baremetal runbook show "${runbook_name}" -f value -c uuid 2>/dev/null); then - echo "[update_runbook] Found existing runbook name=${runbook_name} uuid=${runbook_uuid}" - command_args=(baremetal runbook set "${runbook_uuid}") - else - echo "[update_runbook] Runbook name=${runbook_name} not found, creating" - runbook_uuid="" - command_args=(baremetal runbook create) - fi - command_args+=(--name "${runbook_name}" --steps /tmp/steps.json) - - if [[ -n "${description}" ]]; then - command_args+=(--description "${description}") - fi - if [[ -n "${owner}" ]]; then - command_args+=(--owner "${owner}") - fi - - echo "[update_runbook] Running: openstack ${command_args[*]}" - - if output=$(openstack "${command_args[@]}" 2>&1); then - echo "[update_runbook] SUCCESS: Runbook updated in Ironic name=${resource_name} output=${output}" - - traits_json=$(jq -c "${obj_path} | .spec.traits // []" "${BINDING_CONTEXT_PATH}") - if [[ "${traits_json}" != "[]" ]]; then - echo "[update_runbook] Setting traits name=${resource_name} traits=${traits_json}" - # The traits endpoint requires the UUID; look it up if we just created - # the runbook and don't have it yet. - if [[ -z "${runbook_uuid}" ]]; then - runbook_uuid=$(openstack baremetal runbook show "${runbook_name}" -f value -c uuid 2>/dev/null) - fi - ironic_endpoint=$(openstack endpoint list --service baremetal --interface internal -f value -c URL 2>/dev/null | head -1) - if [[ -n "${ironic_endpoint}" && -n "${runbook_uuid}" ]]; then - token=$(openstack token issue -f value -c id) - echo "[update_runbook] PUT ${ironic_endpoint}/v1/runbooks/${runbook_uuid}/traits" - trait_response=$(curl -s -X PUT \ - -H "Content-Type: application/json" \ - -H "X-Auth-Token: ${token}" \ - -H "X-OpenStack-Ironic-API-Version: 1.112" \ - -d "{\"traits\": ${traits_json}}" \ - "${ironic_endpoint}/v1/runbooks/${runbook_uuid}/traits") - echo "[update_runbook] Traits response name=${resource_name} response=${trait_response}" - else - echo "[update_runbook] WARNING: Could not determine Ironic endpoint or runbook UUID for traits" - fi - else - echo "[update_runbook] No traits to set name=${resource_name}" - fi - - patch_status "${namespace}" "${resource_name}" "Synced" "Successfully updated runbook in Ironic" - echo "[update_runbook] Completed name=${resource_name} status=Synced" - else - echo "[update_runbook] FAILED: name=${resource_name} error=${output}" >&2 - patch_status "${namespace}" "${resource_name}" "Failed" "${output}" - return 1 - fi - } - - echo "[update_runbook] Hook invoked, processing binding contexts" - binding_count=$(jq -r 'length' "${BINDING_CONTEXT_PATH}") - echo "[update_runbook] Found ${binding_count} binding context(s)" - - for ((i = 0; i < binding_count; i++)); do - type=$(jq -r ".[$i].type" "${BINDING_CONTEXT_PATH}") - echo "[update_runbook] Processing context=${i} type=${type}" - - if [[ $type == "Synchronization" ]] ; then - echo "[update_runbook] Synchronization event, reconciling existing resources" - objects_count=$(jq -r ".[$i].objects | length" "${BINDING_CONTEXT_PATH}") - echo "[update_runbook] Found ${objects_count} existing IronicRunbook(s) to reconcile" - for ((j = 0; j < objects_count; j++)); do - obj_name=$(jq -r ".[$i].objects[$j].object.metadata.name" "${BINDING_CONTEXT_PATH}") - obj_sync=$(jq -r ".[$i].objects[$j].object.status.syncStatus // empty" "${BINDING_CONTEXT_PATH}") - echo "[update_runbook] Checking name=${obj_name} syncStatus=${obj_sync}" - if [[ -z "${obj_sync}" || "${obj_sync}" == "null" || "${obj_sync}" == "Failed" ]]; then - echo "[update_runbook] Resource name=${obj_name} needs sync (syncStatus=${obj_sync}), reconciling" - ORIG_BINDING_CONTEXT_PATH="${BINDING_CONTEXT_PATH}" - jq -r ".[$i].objects[$j].object" "${BINDING_CONTEXT_PATH}" > /tmp/sync_object.json - BINDING_CONTEXT_PATH=/tmp/sync_object.json - sync_runbook "." - BINDING_CONTEXT_PATH="${ORIG_BINDING_CONTEXT_PATH}" - else - echo "[update_runbook] Resource name=${obj_name} already synced, skipping" - fi - done - continue - fi - - if [[ $type == "Event" ]] ; then - if ! sync_runbook ".[$i].object"; then - exit 1 - fi - fi - done - echo "[update_runbook] Hook finished" -fi diff --git a/containers/shell-operator-ironic/requirements.txt b/containers/shell-operator-ironic/requirements.txt deleted file mode 100644 index e48726a9c..000000000 --- a/containers/shell-operator-ironic/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -pip -kubernetes -python-openstackclient -python-ironicclient