-
Notifications
You must be signed in to change notification settings - Fork 24
HYPERFLEET-1371 - refactor: remove environments framework, unify startup #327
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
kuudori
wants to merge
4
commits into
openshift-hyperfleet:main
Choose a base branch
from
kuudori:HYPERFLEET-1371-remove-environments
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
58fd785
HYPERFLEET-1371 - refactor: remove environments framework, unify startup
kuudori 8db813f
HYPERFLEET-1371 - fix: address CodeRabbit nitpicks
kuudori f5dc68a
HYPERFLEET-1371 - fix: address coderabbit findings
kuudori b04a450
HYPERFLEET-1371 - refactor: unify server implementation, remove testc…
kuudori File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,184 +1,91 @@ | ||
| # AGENTS.md | ||
|
|
||
| This file provides guidance to AI coding agents working with the HyperFleet API repository. | ||
| ## Project Identity | ||
|
|
||
| For Claude Code users: also see `CLAUDE.md` (auto-loaded) and `.claude/rules/` (loaded per file context). | ||
| HyperFleet API is a **stateless REST API** serving as the pure CRUD data layer for HyperFleet cluster lifecycle management. It persists clusters, node pools, and adapter statuses to PostgreSQL - no business logic, no events. Sentinel handles orchestration; adapters execute and report back. | ||
|
|
||
| ## Commands | ||
| - **Language**: Go 1.26+ with FIPS crypto (`CGO_ENABLED=1 GOEXPERIMENT=boringcrypto`) | ||
| - **Database**: PostgreSQL 14.2 with GORM ORM | ||
| - **API Spec**: TypeSpec -> `hyperfleet-api-spec` Go module -> oapi-codegen -> Go models | ||
| - **Architecture**: Container-based dependency injection, config-driven route registration, transaction-per-request middleware | ||
|
|
||
| ### Setup (fresh clone) | ||
| ## Critical First Steps | ||
|
|
||
| ``` | ||
| make generate-all # REQUIRED FIRST — generated code not in git | ||
| go mod download | ||
| make install-hooks # Install pre-commit hooks (secret scanning, linting, etc.) | ||
| make db/setup # Start local PostgreSQL container | ||
| make build # Build binary (CGO_ENABLED=1 GOEXPERIMENT=boringcrypto) | ||
| ./bin/hyperfleet-api migrate | ||
| make run-no-auth # Start server without auth | ||
| ``` | ||
|
|
||
| ### Build & Run | ||
|
|
||
| ``` | ||
| make build # Build hyperfleet-api binary to bin/ | ||
| make install # Build and install to GOPATH/bin | ||
| make run # Build, migrate, and run with auth (auto-generates dev JWT at /tmp/hf-dev-token.txt) | ||
| make run-no-auth # Build, migrate, and run without auth | ||
| ``` | ||
|
|
||
| ### Code Generation | ||
| **Generated code is not checked into git.** Before building, testing, or even running `go mod download`: | ||
|
|
||
| ```bash | ||
| make generate-all # Generates OpenAPI types + mock implementations | ||
| ``` | ||
| make generate # Extract schema from hyperfleet-api-spec module, then run oapi-codegen | ||
| make generate-mocks # Regenerate mock implementations (go generate) | ||
| make generate-all # Both of the above | ||
| ``` | ||
|
|
||
| ### Verification | ||
|
|
||
| ``` | ||
| make verify # go vet + gofmt check | ||
| make lint # golangci-lint | ||
| make test # Unit tests (HYPERFLEET_ENV=unit_testing) | ||
| make test-integration # Integration tests with testcontainers (HYPERFLEET_ENV=integration_testing) | ||
| make test-helm # Helm chart lint + template validation | ||
| make verify-all # verify + lint + test — fast, no DB needed | ||
| make test-all # lint + test + test-integration + test-helm — full suite | ||
| ``` | ||
| Setup sequence for a fresh clone: | ||
| 1. `make generate-all` - generate OpenAPI models and mocks | ||
| 2. `go mod download` - fetch dependencies | ||
| 3. `make install-hooks` - install pre-commit hooks | ||
| 4. `make db/setup` - start local PostgreSQL container | ||
| 5. `make build` - build binary | ||
| 6. `./bin/hyperfleet-api migrate` - apply database migrations | ||
| 7. `make run-no-auth` - start server without authentication | ||
|
|
||
| ### Database | ||
|
|
||
| ``` | ||
| make db/setup # Start PostgreSQL container | ||
| make db/login # Connect to local PostgreSQL | ||
| make db/teardown # Stop and remove container | ||
| ``` | ||
| Tool versions are pinned in `tools/go.mod` and invoked via `go tool -modfile=tools/go.mod <name>`. | ||
|
|
||
| Run `make help` for the complete target list. | ||
|
|
||
| ## Testing | ||
|
|
||
| **Unit tests**: `make test` — sets `HYPERFLEET_ENV=unit_testing`, runs `./pkg/...` and `./cmd/...` | ||
|
|
||
| **Integration tests**: `make test-integration` — sets `HYPERFLEET_ENV=integration_testing` and `TESTCONTAINERS_RYUK_DISABLED=true`. Testcontainers auto-creates isolated PostgreSQL instances. Located in `test/integration/`. | ||
|
|
||
| **Helm tests**: `make test-helm` — lints and renders templates with multiple value combinations. | ||
|
|
||
| **Mock generation**: `make generate-mocks` — uses `go generate` directives with `go.uber.org/mock/gomock`. Never write mocks manually. | ||
|
|
||
| **Test factories**: `test/factories/` — create resources via the service layer, not directly in DB. Use `NewCluster()`, `NewClusterWithStatus()`, `NewClusterWithLabels()`. | ||
|
|
||
| **Integration test setup**: `test.RegisterIntegration(t)` returns `(helper, client)`. Uses Gomega assertions and Resty HTTP client. | ||
|
|
||
| **Environment variables for tests**: | ||
|
|
||
| - `HYPERFLEET_ENV` — selects config: `unit_testing`, `integration_testing`, `development` | ||
| - `TESTCONTAINERS_RYUK_DISABLED=true` — required in CI | ||
| - Entity adapter requirements are configured per entity kind in `config.yaml` under `entities[].required_adapters` | ||
|
|
||
| ## Project Structure | ||
|
|
||
| ``` | ||
| cmd/hyperfleet-api/ # Entry point + subcommands (serve, migrate) | ||
| container/ # Lazily constructed dependencies (DAOs, services, validator, JWT) | ||
| servecmd/ # serve command; api_server.go is the composition root | ||
| server/ # HTTP servers, router, middleware, entity route registration | ||
| environments/ # Environment configs (development, unit_testing, etc.) | ||
| pkg/ | ||
| api/openapi/ # GENERATED — models + embedded spec (never edit) | ||
| handlers/ # HTTP handler pattern, validation and error handling | ||
| services/ # Service interfaces + sqlXxxService implementations | ||
| dao/ # DAO interfaces + sqlXxxDao implementations | ||
| db/ # SessionFactory, transaction middleware, migrations | ||
| errors/ # ServiceError type, RFC 9457 Problem Details | ||
| logger/ # Structured logging (slog-based) | ||
| config/ # Configuration management | ||
| openapi/ | ||
| README.md # Schema import, code generation, and validation details | ||
| openapi.yaml # Not in git — generated by make generate | ||
| oapi-codegen.yaml # Code generation config | ||
| test/ | ||
| integration/ # Integration tests (testcontainers) | ||
| factories/ # Test data factories | ||
| charts/ # Helm chart for Kubernetes deployment | ||
| ``` | ||
| ## Verification | ||
|
|
||
| **Generated code** (not in git — run `make generate-all`): | ||
|
|
||
| - `pkg/api/openapi/` — Go models + embedded spec | ||
| - `*_mock.go` — Mock implementations | ||
|
|
||
| ## Code Style | ||
|
|
||
| ### Imports | ||
|
|
||
| Order: stdlib → external → internal (`github.com/openshift-hyperfleet/hyperfleet-api/...`) | ||
|
|
||
| ### Errors | ||
|
|
||
| Use constructor functions from `pkg/errors/errors.go`: `NotFound()`, `Validation()`, `GeneralError()`, `Conflict()`, `ValidationWithDetails()`. Error codes: `HYPERFLEET-CAT-NUM` format. All service methods return `*errors.ServiceError`. | ||
|
|
||
| ### Logging | ||
|
|
||
| Use `pkg/logger/` — `logger.Info(ctx, "msg")`, `logger.With(ctx, "key", val).Error("msg")`. Never use `fmt.Println` or `log.Print`. | ||
|
|
||
| ### Services | ||
|
|
||
| Interface + `sql*Service` struct. Constructor injection of DAOs. Return `*errors.ServiceError`. Add `//go:generate mockgen` directive for mocks. | ||
|
|
||
| ### DAOs | ||
|
|
||
| Interface + `sql*Dao` struct. Get session via `sessionFactory.New(ctx)`. Call `db.MarkForRollback(ctx, err)` on write errors. Return stdlib `error`. | ||
|
|
||
| ### Entity Routes | ||
|
|
||
| Entity types are config-driven — declared in `config.yaml` under `entities:` and auto-registered at startup. See `cmd/hyperfleet-api/server/routes_entities.go`. | ||
|
|
||
| ### Dependency Injection | ||
|
|
||
| `cmd/hyperfleet-api/container` holds dependencies only (DAOs, services, schema validator, JWT handler), lazily constructed and cached. Composition — middleware chains, registrars, router, server — lives in `cmd/hyperfleet-api/servecmd/api_server.go` (`BuildAPIServer`), shared with `test/helper.go` so tests exercise production wiring. Do not add `*config.ApplicationConfig` to `cmd/hyperfleet-api/server`; it takes the narrow `cfg` interface instead. | ||
|
|
||
| ## Git Workflow | ||
|
|
||
| ### Commit Format | ||
|
|
||
| ``` | ||
| HYPERFLEET-### - type: description | ||
| ``` | ||
|
|
||
| Types: `feat`, `fix`, `refactor`, `test`, `docs`, `chore` | ||
|
|
||
| Add co-author line for AI-assisted commits: | ||
|
|
||
| ``` | ||
| Co-Authored-By: Claude <noreply@anthropic.com> | ||
| ``` | ||
| | Command | What it does | Requires DB? | | ||
| |---|---|---| | ||
| | `make verify` | go vet + gofmt check | No | | ||
| | `make lint` | golangci-lint | No | | ||
| | `make test` | Unit tests | No | | ||
| | `make test-integration` | Integration tests (testcontainers) | No (auto-creates) | | ||
| | `make test-helm` | Helm chart lint + template validation | No | | ||
| | `make verify-all` | verify + lint + test (single command) | No | | ||
| | `make test-all` | lint + test + test-integration + test-helm | Auto-creates | | ||
|
|
||
| ### Pre-commit Hooks | ||
| Quick feedback: `make verify-all`. Full pre-push: `make test-all`. | ||
|
|
||
| Install: `make install-hooks` | ||
| ## Source of Truth | ||
|
|
||
| Hooks: | ||
| | Topic | Where to look | | ||
| |---|---| | ||
| | OpenAPI spec & code generation | [openapi/README.md](openapi/README.md) | | ||
| | Handler pipeline & validation | `pkg/handlers/CLAUDE.md` | | ||
| | Service interface & status aggregation | `pkg/services/CLAUDE.md` | | ||
| | DAO patterns & session access | `pkg/dao/CLAUDE.md` | | ||
| | SessionFactory & transactions | `pkg/db/CLAUDE.md` | | ||
| | Error constructors & RFC 9457 | `pkg/errors/CLAUDE.md` | | ||
| | Test conventions & factories | `test/CLAUDE.md` | | ||
| | Helm chart testing | `charts/CLAUDE.md` | | ||
| | Development setup | [docs/development.md](docs/development.md) | | ||
| | Deployment | [docs/deployment.md](docs/deployment.md) | | ||
| | Authentication | [docs/authentication.md](docs/authentication.md) | | ||
| | Contributing | [CONTRIBUTING.md](CONTRIBUTING.md) | | ||
|
|
||
| - `leaktk.git.pre-commit` — secret scanning (open-source, no VPN required) | ||
| - `hyperfleet-commitlint` — validates commit message format (commit-msg stage) | ||
| - `hyperfleet-gofmt` — Go code formatting | ||
| - `hyperfleet-golangci-lint` — linting | ||
| - `hyperfleet-go-vet` — Go vet checks | ||
| - `trailing-whitespace` — removes trailing whitespace | ||
| - `end-of-file-fixer` — ensures files end with newline | ||
| - `check-added-large-files` — prevents large files from being committed | ||
| ## Architecture Context | ||
|
|
||
| ### Branching | ||
| **Request flow**: Router -> Middleware (logging, auth, transaction) -> Handler -> Service -> DAO -> GORM -> PostgreSQL | ||
|
|
||
| Create feature branches from `main`. PRs target `main`. | ||
| - **Startup wiring**: `servecmd.runServe` loads config -> `container.NewContainer(cfg)` -> `BuildAPIServer(...)` -> `server.NewRouterFromConfig` + `server.NewAPIServer`. Shutdown uses `pkg/closer` (LIFO order): readiness probe -> health drain -> metrics drain -> API drain -> JWT close -> OTel flush -> DB pool close | ||
| - **Entity routes are config-driven**: declared in `config.yaml` under `entities:`, registered at startup via `registry.LoadDescriptors()`, routes auto-generated by `RegisterEntityRoutes`. No per-entity Go code needed. | ||
| - **Transaction middleware** creates GORM transactions for write requests only (POST/PUT/PATCH/DELETE); reads skip for performance | ||
| - **Status aggregation**: Service layer synthesizes `Available`, `Reconciled`, and `LastKnownReconciled` conditions from adapter reports | ||
| - **Public routes** (`/openapi`, `/openapi.html`, metadata) bypass auth and schema validation; everything else is gated by both, auth outermost | ||
| - **Container** (`cmd/hyperfleet-api/container`) lazily constructs and caches dependencies. Holds DAOs, services, schema validator, JWT handler - but does NOT own their lifecycle. Shutdown ordering is handled by `pkg/closer` in the composition root. | ||
| - **Server package** (`cmd/hyperfleet-api/server`) deliberately does **not** import `pkg/config` - takes narrow `cfg` interfaces instead. Put anything needing `*config.ApplicationConfig` in the composition root. | ||
|
|
||
| ## Boundaries | ||
|
|
||
| - **Never edit** `pkg/api/openapi/` or `*_mock.go` — regenerate with `make generate-all` | ||
| - **Never set** `status.phase` manually — calculated from adapter conditions | ||
| - **Never create** direct DB connections — use `SessionFactory.New(ctx)` for transaction participation | ||
| - **Never edit** files in `pkg/api/openapi/` or `*_mock.go` - regenerate with `make generate-all` | ||
| - **Never set** `status.phase` manually - calculated from adapter conditions | ||
| - **Never create** direct DB connections - use `SessionFactory.New(ctx)` for transaction participation | ||
| - **FIPS required**: build with `CGO_ENABLED=1 GOEXPERIMENT=boringcrypto` | ||
| - **Spec source of truth**: `hyperfleet-api-spec` Go module; update `go.mod` to change spec versions — see [openapi/README.md](openapi/README.md) for full details on schema import, code generation, and validation | ||
| - **Tool versions** pinned in `tools/go.mod` — don't manually install oapi-codegen or golangci-lint | ||
| - **Spec source of truth**: `hyperfleet-api-spec` Go module; update `go.mod` to change spec versions - see [openapi/README.md](openapi/README.md) | ||
| - **Tool versions** pinned in `tools/go.mod` - don't manually install oapi-codegen or golangci-lint | ||
|
|
||
| ## Gotchas | ||
|
|
||
| - **`make generate-all` is mandatory** - build and tests fail without it; generated code is gitignored | ||
| - **`pkg/api/openapi/` is read-only** - never hand-edit, always regenerate | ||
| - **Service methods return `*errors.ServiceError`, not stdlib `error`** - use constructor functions from `pkg/errors/errors.go`; see `pkg/errors/CLAUDE.md` for the full reference | ||
| - **`apiServer.Close()` severs in-flight requests** - always use `Shutdown(ctx)` with a drain budget; `Close()` is only the force-close fallback after `Shutdown` times out | ||
| - **Container has no `Close()`** - lifecycle (JWT handler, session factory, OTel) is managed by `pkg/closer` in the composition root, not on `Container` | ||
| - **Integration tests share a single testcontainer** - `test.NewHelper(t)` initializes once per process via `sync.Once`; the PostgreSQL container, API server, and JWK mock are shared across all tests in the suite | ||
| - **Schema validation requires the OpenAPI spec file** - if `HYPERFLEET_SERVER_OPENAPI_SCHEMA_PATH` is unset, validation is skipped; `TestMain` in integration tests auto-sets it to `test/validation-schema.yaml` | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tip
nit — non-blocking suggestion
Category: Pattern
NewContainer(cfg)in this description doesn't match the actual signatureNewContainer(cfg, closer). Thecloserparameter is central to the new shutdown design — worth including here so the architecture description is accurate.