Skip to content

Commit 0008aec

Browse files
DanNiEShclaude
andcommitted
Add Host controller with Ironic power management
Implement a controller that watches Host CRs (from osac-operator) and reconciles spec.online to Ironic power on/off. Filters on spec.hostManagementClass == "openstack" and syncs status.poweredOn. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6cfb356 commit 0008aec

19 files changed

Lines changed: 754 additions & 330 deletions

File tree

.devcontainer/devcontainer.json

Lines changed: 0 additions & 25 deletions
This file was deleted.

.devcontainer/post-install.sh

Lines changed: 0 additions & 23 deletions
This file was deleted.

.github/workflows/lint.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,4 @@ jobs:
2020
- name: Run linter
2121
uses: golangci/golangci-lint-action@v8
2222
with:
23-
version: v2.1.0
23+
version: v2.7.2

.github/workflows/test-e2e.yml

Lines changed: 0 additions & 34 deletions
This file was deleted.

Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Build the manager binary
2-
FROM golang:1.24 AS builder
2+
FROM golang:1.25 AS builder
33
ARG TARGETOS
44
ARG TARGETARCH
55

@@ -15,6 +15,7 @@ RUN go mod download
1515
COPY cmd/main.go cmd/main.go
1616
COPY api/ api/
1717
COPY internal/ internal/
18+
COPY pkg/ pkg/
1819

1920
# Build
2021
# the GOARCH has not a default value to allow the binary be built according to the host where the command

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ CONTROLLER_TOOLS_VERSION ?= v0.18.0
185185
ENVTEST_VERSION ?= $(shell go list -m -f "{{ .Version }}" sigs.k8s.io/controller-runtime | awk -F'[v.]' '{printf "release-%d.%d", $$2, $$3}')
186186
#ENVTEST_K8S_VERSION is the version of Kubernetes to use for setting up ENVTEST binaries (i.e. 1.31)
187187
ENVTEST_K8S_VERSION ?= $(shell go list -m -f "{{ .Version }}" k8s.io/api | awk -F'[v.]' '{printf "1.%d", $$3}')
188-
GOLANGCI_LINT_VERSION ?= v2.1.0
188+
GOLANGCI_LINT_VERSION ?= v2.7.2
189189

190190
.PHONY: kustomize
191191
kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary.

README.md

Lines changed: 129 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,131 @@
1-
# public_template
1+
# host-management-openstack
22

3-
Use this repository as the template for public-facing repositories. Please keep the following warning at the top of the repository README:
3+
Kubernetes controller that manages bare metal nodes via OpenStack Ironic. Watches Host CRs (defined in [osac-operator](https://github.com/DanNiESh/osac-operator)) and reconciles the desired state to Ironic.
44

5-
> [!WARNING]
6-
> Be mindful of the content you commit to this repository. Do not commit any
7-
> material containing Red Hat confidential content, including information about
8-
> future product development plans.
5+
## Description
6+
7+
This controller filters Host CRs where `spec.hostManagementClass == "openstack"` and uses `spec.id` as the Ironic node UUID to manage node lifecycle through Ironic.
8+
9+
## Test locally (with Ironic)
10+
11+
Run the host controller on your machine and point it at your Ironic API. You need a Kubernetes API (e.g. a `kind` cluster) so the controller can watch Host CRs; the controller runs locally and talks to Ironic from your local machine.
12+
13+
### 1. Create a kind cluster
14+
15+
```bash
16+
kind create cluster --name host-mgmt-test
17+
```
18+
19+
### 2. Install the Host CRD from osac-operator
20+
21+
The Host CR is defined in the [osac-operator](https://github.com/DanNiESh/osac-operator) `host-operator` branch.
22+
23+
```bash
24+
kubectl apply -f <path-to-osac-operator>/config/crd/bases/osac.openshift.io_hosts.yaml
25+
```
26+
27+
### 3. Set Ironic URL and token
28+
29+
```bash
30+
export IRONIC_URL="https://ironic-url"
31+
export OSAC_AUTH_TOKEN="token" # e.g. openstack token issue -f value -c id
32+
export IRONIC_INSECURE=true # optional, if using self-signed certs
33+
```
34+
35+
### 4. Run the controller
36+
37+
```bash
38+
make run
39+
```
40+
41+
The controller will use `IRONIC_URL` and `OSAC_AUTH_TOKEN` to connect to Ironic.
42+
43+
### 5. Apply a sample Host CR
44+
45+
In another terminal:
46+
47+
```bash
48+
kubectl apply -f config/samples/v1alpha1_host.yaml
49+
```
50+
51+
### 6. Verify
52+
53+
```bash
54+
# Check Host status
55+
kubectl get host host-sample -n osac-namespace -o yaml
56+
57+
# Watch controller logs for reconciliation
58+
```
59+
60+
## Getting Started
61+
62+
### Prerequisites
63+
- go version v1.25.0+
64+
- docker version 17.03+
65+
- kubectl version v1.11.3+
66+
- Access to a Kubernetes v1.11.3+ cluster
67+
- Access to an OpenStack Ironic endpoint
68+
69+
### To Deploy on the cluster
70+
**Build and push your image to the location specified by `IMG`:**
71+
72+
```sh
73+
make docker-build docker-push IMG=<some-registry>/host-management-openstack:tag
74+
```
75+
76+
**Install the Host CRD (from osac-operator):**
77+
78+
```sh
79+
kubectl apply -f <path-to-osac-operator>/config/crd/bases/osac.openshift.io_hosts.yaml
80+
```
81+
82+
**Deploy the Manager to the cluster with the image specified by `IMG`:**
83+
84+
```sh
85+
make deploy IMG=<some-registry>/host-management-openstack:tag
86+
```
87+
88+
> **NOTE**: If you encounter RBAC errors, you may need to grant yourself cluster-admin
89+
privileges or be logged in as admin.
90+
91+
**Create instances of your solution**
92+
You can apply the samples (examples) from the config/sample:
93+
94+
```sh
95+
kubectl apply -k config/samples/
96+
```
97+
98+
### To Uninstall
99+
**Delete the instances (CRs) from the cluster:**
100+
101+
```sh
102+
kubectl delete -k config/samples/
103+
```
104+
105+
**UnDeploy the controller from the cluster:**
106+
107+
```sh
108+
make undeploy
109+
```
110+
111+
## Contributing
112+
113+
**NOTE:** Run `make help` for more information on all potential `make` targets
114+
115+
More information can be found via the [Kubebuilder Documentation](https://book.kubebuilder.io/introduction.html)
116+
117+
## License
118+
119+
Copyright 2026.
120+
121+
Licensed under the Apache License, Version 2.0 (the "License");
122+
you may not use this file except in compliance with the License.
123+
You may obtain a copy of the License at
124+
125+
http://www.apache.org/licenses/LICENSE-2.0
126+
127+
Unless required by applicable law or agreed to in writing, software
128+
distributed under the License is distributed on an "AS IS" BASIS,
129+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
130+
See the License for the specific language governing permissions and
131+
limitations under the License.

cmd/main.go

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ import (
3636
"sigs.k8s.io/controller-runtime/pkg/metrics/filters"
3737
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
3838
"sigs.k8s.io/controller-runtime/pkg/webhook"
39+
40+
"github.com/osac-project/host-management-openstack/internal/controller"
41+
"github.com/osac-project/host-management-openstack/pkg/ironic"
42+
osacopenshiftiov1alpha1 "github.com/osac-project/osac-operator/api/v1alpha1"
3943
// +kubebuilder:scaffold:imports
4044
)
4145

@@ -46,7 +50,7 @@ var (
4650

4751
func init() {
4852
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
49-
53+
utilruntime.Must(osacopenshiftiov1alpha1.AddToScheme(scheme))
5054
// +kubebuilder:scaffold:scheme
5155
}
5256

@@ -116,7 +120,7 @@ func main() {
116120
filepath.Join(webhookCertPath, webhookCertKey),
117121
)
118122
if err != nil {
119-
setupLog.Error(err, "Failed to initialize webhook certificate watcher")
123+
setupLog.Error(err, "failed to initialize webhook certificate watcher")
120124
os.Exit(1)
121125
}
122126

@@ -165,7 +169,7 @@ func main() {
165169
filepath.Join(metricsCertPath, metricsCertKey),
166170
)
167171
if err != nil {
168-
setupLog.Error(err, "to initialize metrics certificate watcher", "error", err)
172+
setupLog.Error(err, "failed to initialize metrics certificate watcher", "error", err)
169173
os.Exit(1)
170174
}
171175

@@ -198,6 +202,34 @@ func main() {
198202
os.Exit(1)
199203
}
200204

205+
// Ironic client for bare metal power management
206+
var ironicClient *ironic.Client
207+
if ironicURLStr := os.Getenv("IRONIC_URL"); ironicURLStr != "" {
208+
authToken := os.Getenv("OSAC_AUTH_TOKEN")
209+
if authToken == "" {
210+
setupLog.Error(nil, "OSAC_AUTH_TOKEN required when IRONIC_URL is set")
211+
os.Exit(1)
212+
}
213+
ironicOpts := ironic.ClientOptions{InsecureSkipVerify: os.Getenv("IRONIC_INSECURE") == "true"}
214+
var ironicErr error
215+
ironicClient, ironicErr = ironic.NewClientWithToken(ironicURLStr, authToken, ironicOpts)
216+
if ironicErr != nil {
217+
setupLog.Error(ironicErr, "failed to create Ironic client")
218+
os.Exit(1)
219+
}
220+
setupLog.Info("Ironic client configured", "url", ironicURLStr)
221+
} else {
222+
setupLog.Info("IRONIC_URL not set")
223+
}
224+
225+
if err := (&controller.HostReconciler{
226+
Client: mgr.GetClient(),
227+
Scheme: mgr.GetScheme(),
228+
IronicClient: ironicClient,
229+
}).SetupWithManager(mgr); err != nil {
230+
setupLog.Error(err, "unable to create controller", "controller", "Host")
231+
os.Exit(1)
232+
}
201233
// +kubebuilder:scaffold:builder
202234

203235
if metricsCertWatcher != nil {

config/default/kustomization.yaml

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,29 +9,29 @@ namespace: host-management-openstack-system
99
namePrefix: host-management-openstack-
1010

1111
# Labels to add to all resources and selectors.
12-
#labels:
13-
#- includeSelectors: true
14-
# pairs:
15-
# someName: someValue
12+
# labels:
13+
# - includeSelectors: true
14+
# pairs:
15+
# someName: someValue
1616

1717
resources:
18-
#- ../crd
18+
# - ../crd
1919
- ../rbac
2020
- ../manager
2121
# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in
2222
# crd/kustomization.yaml
23-
#- ../webhook
23+
# - ../webhook
2424
# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required.
25-
#- ../certmanager
25+
# - ../certmanager
2626
# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'.
27-
#- ../prometheus
27+
# - ../prometheus
2828
# [METRICS] Expose the controller manager metrics service.
2929
- metrics_service.yaml
3030
# [NETWORK POLICY] Protect the /metrics endpoint and Webhook Server with NetworkPolicy.
3131
# Only Pod(s) running a namespace labeled with 'metrics: enabled' will be able to gather the metrics.
3232
# Only CR(s) which requires webhooks and are applied on namespaces labeled with 'webhooks: enabled' will
3333
# be able to communicate with the Webhook Server.
34-
#- ../network-policy
34+
# - ../network-policy
3535

3636
# Uncomment the patches line if you enable Metrics
3737
patches:
@@ -44,19 +44,19 @@ patches:
4444
# Uncomment the patches line if you enable Metrics and CertManager
4545
# [METRICS-WITH-CERTS] To enable metrics protected with certManager, uncomment the following line.
4646
# This patch will protect the metrics with certManager self-signed certs.
47-
#- path: cert_metrics_manager_patch.yaml
48-
# target:
49-
# kind: Deployment
47+
# - path: cert_metrics_manager_patch.yaml
48+
# target:
49+
# kind: Deployment
5050

5151
# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in
5252
# crd/kustomization.yaml
53-
#- path: manager_webhook_patch.yaml
54-
# target:
55-
# kind: Deployment
53+
# - path: manager_webhook_patch.yaml
54+
# target:
55+
# kind: Deployment
5656

5757
# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix.
5858
# Uncomment the following replacements to add the cert-manager CA injection annotations
59-
#replacements:
59+
# replacements:
6060
# - source: # Uncomment the following block to enable certificates for metrics
6161
# kind: Service
6262
# version: v1

0 commit comments

Comments
 (0)