Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# host-management-openstack

Kubernetes controller that manages bare metal host power state via OpenStack Ironic. Watches HostLease custom resources (defined in bare-metal-fulfillment-operator) and reconciles desired power state (`spec.poweredOn`) through Ironic APIs. Optionally integrates with AAP for provisioning workflows via osac-operator's shared provisioning lifecycle.

## Critical Rules

- **No CRDs in this repo** — HostLease is owned by bare-metal-fulfillment-operator; install it before deploying
- **Always `make manifests`** after modifying RBAC markers in controller code
- **Never edit** `config/rbac/role.yaml` — it is generated by controller-gen
- **Always `go mod tidy`** before committing
- Run `make lint test` before committing

## Dev Environment

**Language**: Go (see `go.mod`) | **Framework**: controller-runtime (Kubebuilder v4) | **Build tool**: Make | **Container tool**: Podman (default) | **Test framework**: Ginkgo v2 + Gomega | **Linter**: golangci-lint (see `Makefile`)

```bash
make build # Build manager binary
make test # Unit tests (uses envtest, excludes e2e)
make test-e2e # E2E tests (auto-creates/tears down Kind cluster host-management-openstack-test-e2e)
make lint # golangci-lint
make lint-fix # golangci-lint run --fix
make fmt && make vet # Format and vet

make manifests # Generate RBAC, CRD, and webhook manifests via controller-gen (only RBAC output today — no local CRDs or webhooks)
make generate # Generate DeepCopy methods via controller-gen

make run # Run controller locally (requires HOSTLEASE_NAMESPACE env var)
make deploy IMG=<registry>/host-management-openstack:tag
make undeploy # Remove operator from cluster

make image-build IMG=<registry>/host-management-openstack:tag # Build with Podman
make image-push IMG=<registry>/host-management-openstack:tag
make docker-buildx PLATFORMS=linux/amd64,linux/arm64 # Multi-arch build
make build-installer IMG=... # Generate dist/install.yaml (consolidated manifest)

pre-commit run --all-files # Run all pre-commit hooks
```

## Repository Structure

```text
host-management-openstack/
├── cmd/
│ ├── main.go # Operator entry point (manager setup, AAP config, namespace filtering)
│ └── main_test.go # Entry point tests
├── internal/
│ ├── controller/ # HostLease reconciliation (power sync, provisioning lifecycle, finalizers)
│ └── management/ # Power management abstraction (pluggable backend interface, OpenStack impl)
├── config/
│ ├── default/ # Kustomize default overlay
│ ├── manager/ # Manager deployment manifest
│ ├── rbac/ # Generated RBAC rules
│ ├── prometheus/ # Prometheus monitoring config (commented out by default)
│ ├── network-policy/ # Network policy definitions (commented out by default)
│ └── samples/ # Example HostLease CR
├── test/
│ ├── e2e/ # End-to-end tests (Kind cluster)
│ └── utils/ # Test utilities
├── Makefile # Build, test, lint, deploy targets
├── go.mod # Go dependencies (see go.mod)
└── .golangci.yml # Linter configuration
```

## Resources Managed

This operator manages no CRDs. It watches **HostLease** CRs (from bare-metal-fulfillment-operator) filtered by `spec.hostClass == "openstack"` and reconciles their power state via Ironic using `spec.externalHostID` as the Ironic node UUID.

## Architecture

```text
HostLease CR (spec.poweredOn, spec.externalHostID)
↓ (reconcile, filtered by hostClass == "openstack")
HostLease Controller (named "openstack-host")
├── (power sync via) Management Client → OpenStack Ironic
└── (optional provisioning via) osac-operator/pkg/provisioning → AAP
```

### Key Subsystems

| Package | Purpose |
|---------|---------|
| `internal/controller/` | HostLease reconciliation: finalizers, power sync (60s recheck), AAP provisioning (30s poll) |
| `internal/management/` | Power management abstraction with pluggable backend; OpenStack impl via gophercloud v2 |
| `cmd/main.go` | Manager setup, AAP client config (`OSAC_AAP_*` env vars), namespace filtering (`HOSTLEASE_NAMESPACE`) |

## CI

GitHub Actions (`.github/workflows/`):
- **build-image.yaml** — runs tests, builds + pushes container image to GHCR
- **pre-commit.yaml** — pre-commit hooks + golangci-lint on PRs

## Code Quality

- **golangci-lint** (see `Makefile`) with dupl, errcheck, ginkgolinter, goconst, gocyclo, govet, ineffassign, lll, misspell, prealloc, revive, staticcheck, unconvert, unused; formatters: gofmt, goimports (see `.golangci.yml`)
- **Pre-commit hooks**: trailing-whitespace, check-merge-conflict, end-of-file-fixer, check-added-large-files, check-case-conflict, check-json, check-symlinks, detect-private-key, yamllint --strict (excludes `config/`), golangci-lint run --fix
- **Tests**: Ginkgo v2 + Gomega with envtest for unit tests; Kind cluster for e2e (`test/e2e/`)

## Container Security

- **Base images**: Go toolset (builder, see `Containerfile`), gcr.io/distroless/static:nonroot (runtime)
- **Multi-stage build**: CGO_ENABLED=0, runs as non-root user 65532
- **Default registry**: ghcr.io/osac-project/host-management-openstack:latest

## Code Generation Flow

1. Modify RBAC markers in controller code (`internal/controller/`)
2. Run `make manifests` → runs controller-gen for RBAC, CRD, and webhook output; only RBAC is produced today (HostLease CRD is external)
3. Run `make generate` → regenerates DeepCopy methods if API types change

## Test Structure

- `internal/controller/hostlease_controller_test.go` — reconciliation scenarios with envtest
- `internal/management/openstack_internal_test.go` — OpenStack client unit tests
- `internal/management/management_test.go` — backend factory tests
- `cmd/main_test.go` — entry point tests
- `test/e2e/` — end-to-end tests with Kind cluster (named `host-management-openstack-test-e2e`)
- `test/utils/` — shared test utilities
- **ENVTEST_K8S_VERSION**: Auto-detected from k8s.io/api version in go.mod

## Cross-Component Integration

- **bare-metal-fulfillment-operator** — owns the HostLease CRD; must be installed first
- **osac-operator** — provides `pkg/provisioning` (lifecycle state machine) and `pkg/aap` (AAP client); update with `go get github.com/osac-project/osac-operator@<sha> && go mod tidy`
- **fulfillment-service** — creates HostLease resources during bare metal provisioning
Loading