make build # Build all binaries
make hypershift-operator # Build hypershift-operator
make control-plane-operator # Build control-plane-operator
make hypershift # Build CLImake test # Run unit tests with race detection
make e2e # Build E2E test binaries
make e2ev2 # Build v2 E2E test binary (bin/test-e2e-v2)
make tests # Compile all tests (no execution)
make test-envtest-ocp # Run envtest for CEL validations (OpenShift k8s versions)
make test-envtest-kube # Run envtest for vanilla k8s versions
make test-envtest-api-all # Run envtest for bothTo run a single unit test or package:
GO111MODULE=on GOWORK=off GOFLAGS=-mod=vendor go test -race -run TestName ./path/to/package/...To run envtest against a single k8s version:
ENVTEST_OCP_K8S_VERSIONS=1.35.0 make test-envtest-ocpTo run envtest versions in parallel:
ENVTEST_JOBS=MAX make test-envtest-ocp # All versions in parallel
ENVTEST_JOBS=3 make test-envtest-ocp # Up to 3 versions in parallelTo run envtest for a single suite, use Ginkgo's --focus flag:
GO111MODULE=on GOWORK=off GOFLAGS=-mod=vendor go test -tags envtest -race ./test/envtest/... -- --focus="hostedclusters.*etcd"make lint # Run golangci-lint
make lint-fix # Auto-fix linting issues
make verify # Full verification (generate, update, staticcheck, fmt, vet, lint, codespell, gitlint)
make staticcheck # Run staticcheck on core packages
make fmt # Format code
make vet # Run go vet
make verify-codespell # Catch spelling errors in markdown
make run-gitlint # Validate commit message format
make pre-commit # Full pre-PR gate (build, e2e compile, verify, test)make api # Regenerate all CRDs, deepcopy, clients
make api-lint-fix # Run API linter and auto-fix violations
make generate # Run go generate (cleans stale *_mock.go files first)
make clients # Update generated clients
make update # Full update (api-deps, workspace-sync, deps, api, api-docs, clients, docs-aggregate)Use support/upsert/ for safe resource creation and updates. Follow owner reference patterns for proper garbage collection.
Controllers follow standard controller-runtime reconcile loop patterns. Locations:
hypershift-operator/controllers/— HostedCluster and NodePool reconciliationcontrol-plane-operator/controllers/— control plane component reconciliation (v2 framework)
Platform-specific logic is isolated in separate packages. Common interfaces are defined in support/ packages, with platform implementations in respective controller subdirectories.
This repository contains multiple Go modules. The api/ directory is a separate Go module with its own api/go.mod (module path: github.com/openshift/hypershift/api). The main module at the repository root consumes the api/ module through vendoring.
This means:
- Edits to files under
api/(e.g.api/hypershift/v1beta1/) are not visible to the main module until the vendored copy is updated. - After modifying any types, constants, or functions in
api/, you must runmake updateto regenerate CRDs, revendor dependencies, and sync everything.make updateruns the full sequence:api-deps→workspace-sync→deps→api→api-docs→clients→docs-aggregate. Without this, the main module build will fail withundefinederrors for any new symbols added inapi/. - Do not modify
vendor/directories directly. Thevendor/directories are managed bygo mod vendor(viamake depsandmake api-deps). Always usemake updateto keep them in sync. - Running
go build ./...orgo vet ./...from the repository root will not compile theapi/module — it is a separate module. To build/vet the API module, run commands from within theapi/directory. - The
hack/workspace/directory contains a Go workspace configuration (go.work) that can be used for local development across both modules.
Run make pre-commit before submitting a PR. It executes the full sequence: build, e2e compile, verify (formatting, linting, gitlint), and unit tests. This is the single command that catches most CI failures locally.
The minimum Go version is declared in go.mod. The api/ module uses omitzero struct tags (available since Go 1.24) and other features that require this minimum version.
api/is a separate Go module: Always runmake updateafter modifying types in theapi/package. See Multi-Module Structure above for details.- Do not modify
vendor/directories directly: They are managed bygo mod vendorviamake update. - Use
make verifybefore submitting PRs to catch formatting/generation issues. - Platform-specific controllers require their respective cloud credentials for testing.
- E2E tests need proper cloud infrastructure setup (S3 buckets, DNS zones, etc.).
make generatecleans stale*_mock.gofiles viagit clean -fxbefore regenerating — don't hand-edit mock files.- No unrelated changes in PRs: Do not include cosmetic formatting, whitespace, or import reordering changes in files unrelated to the PR's purpose. Unrelated changes increase review surface and make PRs harder to revert cleanly. If you notice something worth cleaning up, do it in a separate PR.
- Follow existing codebase patterns: Before implementing a new approach (e.g., using service-ca operator for TLS), search the codebase for how similar problems are already solved (e.g.,
reconcileSelfSignedCA). HyperShift self-signs certificates — use the existing self-signing pattern instead of relying on external certificate operators.
- Features/epics/stories/tasks: Create in the CNTRLPLANE project (Red Hat OpenShift Control Planes)
- Bugs: Create in the OCPBUGS project (OpenShift Bugs)
- Components: Use
HyperShift / AROfor ARO HCP,HyperShift / ROSAfor ROSA HCP, orHyperShiftwhen platform is unclear
Use conventional commit format. Validate with make run-gitlint. Do NOT put Jira IDs in commit messages — they belong only in PR titles.
<type>(<scope>): <description>
[optional body]
Signed-off-by: <name> <email>
Types: feat, fix, docs, style, refactor, test, chore, build, ci, perf, revert. Title max 120 chars, body lines max 140 chars. Include a Signed-off-by footer — get name/email from git config user.name and git config user.email.
Use the git-commit-format skill for full details and examples.
Before creating a PR or after addressing review comments, use the restructure-commits skill to reorganize all branch commits into logical, component-based commits. This ensures every PR has a clean, reviewable commit history grouped by architectural boundary.
See CONTRIBUTING.md for the full contribution guidelines. Key points for agents:
- Use the
restructure-commitsskill to organize commits by component (see Restructuring Commits above) - Run
make pre-committo build, compile e2e tests, run verification (formatting, linting, gitlint), and run unit tests
Prefix with a Jira ticket number: OCPBUGS-12345: Fix memory leak in controller. Use NO-JIRA: only when no Jira issue exists (sparingly).
Follow the template in .github/PULL_REQUEST_TEMPLATE.md.
- Open the PR in draft mode to avoid triggering all CI jobs and notifying approvers
- Run necessary CI jobs manually with
/test <job-name> - Mark as "Ready for Review" once tests pass and required labels are applied
After addressing review feedback, use the restructure-commits skill again to reorganize commits before force-pushing. This keeps the commit history clean for subsequent review rounds.
For test naming and formatting rules, see .claude/skills/code-formatting.
Additional review-derived rules:
- Do not export functions that are only used in tests. Use unexported helpers or keep them in
_test.gofiles. - Do not leave dead code (functions defined but never called). Remove unused code before submitting.
- When adding new exported functions or methods, cover them with unit tests. For controller reconciliation methods, test at minimum: happy path, missing/empty input, disabled capability, and the primary error path.
- Do not leave TODO comments in validation regex patterns or CEL rules — resolve them before submitting. Reviewers have blocked PRs for shipping regex patterns with placeholder character classes (e.g., allowing
{and}in UUID fields, or missing anchoring constraints). - When writing regex for API validation, match the upstream format exactly. For UUIDs, use
[0-9a-f]{8}-...; for Azure resource names, verify the allowed character set against Azure documentation. Do not over-broaden patterns with catch-all classes like[a-zA-Z0-9-_().{}]. - Use real-world values in test fixtures when possible (e.g.,
quay.io/openshift-release-dev/ocp-release:4.21.10-x86_64instead ofexample.com/image:latest). Real values catch edge cases that synthetic values miss. - When adding test assertions for OwnerReferences, check that they were actually set during reconciliation — not just that the object exists.