Skip to content
Open
Show file tree
Hide file tree
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
70 changes: 34 additions & 36 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,7 @@ make db/teardown # Stop and remove PostgreSQL container

### Code Generation
```bash
make generate # Regenerate Go models from openapi/openapi.yaml
make generate-vendor # Generate using vendor dependencies (offline mode)
make generate-mocks # Generate mock implementations for testing
```

## Project Structure
Expand Down Expand Up @@ -126,25 +125,24 @@ hyperfleet-api/

## Core Components

### 1. API Specification Workflow
### 1. CRD-Driven API

The API is specified using TypeSpec, which compiles to OpenAPI, which then generates Go models:
The API is dynamically generated from Kubernetes Custom Resource Definitions (CRDs):

```
TypeSpec (.tsp files in hyperfleet-api-spec repo)
↓ tsp compile
openapi/openapi.yaml (32KB, uses $ref for DRY)
↓ make generate (openapi-generator-cli in Podman)
pkg/api/openapi/model_*.go (Go structs)
pkg/api/openapi/api/openapi.yaml (44KB, fully resolved, embedded in binary)
charts/crds/*.yaml (CRD definitions)
↓ loaded at startup
pkg/crd/registry.go (CRD registry)
↓ generates
Dynamic routes + OpenAPI spec at runtime
```

**Key Points**:
- TypeSpec definitions are maintained in a separate `hyperfleet-api-spec` repository
- `openapi/openapi.yaml` is the source of truth for this repository (generated from TypeSpec)
- `make generate` uses Podman to run openapi-generator-cli, ensuring consistent versions
- Generated code includes JSON tags, validation, and type definitions
- The fully resolved spec is embedded at compile time via `//go:embed`
- CRD definitions in `charts/crds/` define resource types (Cluster, NodePool, IDP, etc.)
- Routes and OpenAPI spec are generated dynamically at startup
- No code generation required - just modify CRD YAML files
- Local development uses `CRD_PATH` env var to load CRDs from files
- Production loads CRDs from Kubernetes API

### 2. Database Layer

Expand Down Expand Up @@ -401,8 +399,8 @@ All subcommands support these logging flags:
```bash
# Prerequisites: Go 1.24, Podman, PostgreSQL client tools

# Generate OpenAPI code (required before go mod download)
make generate
# Generate mocks for testing
make generate-mocks

# Download Go module dependencies
go mod download
Expand All @@ -419,25 +417,24 @@ make build
# Run migrations
./bin/hyperfleet-api migrate

# Start server (no authentication)
# Start server (no authentication, loads CRDs from local files)
make run-no-auth
```

### Code Generation
### CRD Configuration

When the TypeSpec specification changes:
Resource types are defined by CRDs in `charts/crds/`. To add or modify resource types:

```bash
# Regenerate Go models from openapi/openapi.yaml
make generate

# This will:
# 1. Remove pkg/api/openapi/*
# 2. Build Docker image with openapi-generator-cli
# 3. Generate model_*.go files
# 4. Copy fully resolved openapi.yaml to pkg/api/openapi/api/
# Edit CRD files in charts/crds/
# Restart the server to pick up changes
make run-no-auth
```

The `CRD_PATH` environment variable controls where CRDs are loaded from:
- `make run-no-auth` sets `CRD_PATH=$(PWD)/charts/crds` automatically
- In production, CRDs are loaded from the Kubernetes API

### Testing

**Unit Tests**:
Expand Down Expand Up @@ -712,13 +709,13 @@ The server is configured in cmd/hyperfleet/server/:

**Solution**: Always run `./bin/hyperfleet-api migrate` after pulling code or changing schemas

### 2. Using Wrong OpenAPI File
### 2. CRD Changes Not Reflected

**Problem**: There are two openapi.yaml files:
- `openapi/openapi.yaml` (32KB, source, has $ref)
- `pkg/api/openapi/api/openapi.yaml` (44KB, generated, fully resolved)
**Problem**: Changes to CRD files in `charts/crds/` aren't showing up.

**Rule**: Only edit the source file. The generated file is overwritten by `make generate`.
**Solution**: Restart the server. CRDs are loaded at startup.
- For local dev: `make run-no-auth` loads from `charts/crds/`
- For production: CRDs are loaded from Kubernetes API

### 3. Context Session Access

Expand Down Expand Up @@ -795,6 +792,7 @@ The API is designed to be stateless and horizontally scalable:
Common issues and solutions:

1. **Database connection errors**: Check `make db/setup` was run and container is running
2. **Generated code issues**: Run `make generate` to regenerate from OpenAPI spec
3. **Test failures**: Ensure PostgreSQL container is running and `OCM_ENV` is set
4. **Build errors**: Verify Go version is 1.24+ with `go version`
2. **Missing mocks**: Run `make generate-mocks` to regenerate test mocks
3. **CRDs not loading**: Ensure `CRD_PATH` is set or Kubernetes cluster is accessible
4. **Test failures**: Ensure PostgreSQL container is running and `OCM_ENV` is set
5. **Build errors**: Verify Go version is 1.24+ with `go version`
11 changes: 7 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,18 @@ RUN --mount=type=cache,target=/opt/app-root/src/go/pkg/mod,uid=1001 \
GIT_SHA=${GIT_SHA} GIT_DIRTY=${GIT_DIRTY} BUILD_DATE=${BUILD_DATE} VERSION=${VERSION} \
make build

# Runtime stage
FROM ${BASE_IMAGE}
# Runtime stage - use target architecture for the base image
ARG BASE_IMAGE
ARG TARGETARCH
FROM --platform=linux/${TARGETARCH} ${BASE_IMAGE}

WORKDIR /app

COPY --from=builder /build/bin/hyperfleet-api /app/hyperfleet-api
COPY --from=builder /build/openapi/openapi.yaml /app/openapi/openapi.yaml

ENV OPENAPI_SCHEMA_PATH=/app/openapi/openapi.yaml
# CRD definitions are now loaded from Kubernetes API at runtime
# OpenAPI schema is generated dynamically from CRDs
# For provider-specific schemas, set OPENAPI_SCHEMA_PATH to override

USER 65532:65532

Expand Down
43 changes: 19 additions & 24 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
# Include bingo-managed tool variables
include .bingo/Variables.mk

# CGO_ENABLED=0 is not FIPS compliant. large commercial vendors and FedRAMP require FIPS compliant crypto
# Use ?= to allow Dockerfile to override (CGO_ENABLED=0 for Alpine-based dev images)
CGO_ENABLED ?= 1
# Go 1.25+ uses native FIPS 140-3 support (GOFIPS140) instead of BoringCrypto/GOEXPERIMENT.
# CGO is no longer required for FIPS compliance. Set GOFIPS140=latest at build time and
# GODEBUG=fips140=on at runtime to enable FIPS 140-3 mode.
# Use ?= to allow Dockerfile to override
CGO_ENABLED ?= 0
GOFIPS140 ?= latest

GO ?= go

Expand Down Expand Up @@ -114,43 +117,35 @@ lint: $(GOLANGCI_LINT) ## Run golangci-lint

##@ Code Generation

.PHONY: generate
generate: $(OAPI_CODEGEN) ## Generate OpenAPI types using oapi-codegen
rm -rf pkg/api/openapi
mkdir -p pkg/api/openapi
$(OAPI_CODEGEN) --config openapi/oapi-codegen.yaml openapi/openapi.yaml

.PHONY: generate-mocks
generate-mocks: $(MOCKGEN) ## Generate mock implementations for services
${GO} generate ./pkg/services/...

# OpenAPI spec is now dynamically generated from CRDs at runtime
.PHONY: generate-all
generate-all: generate generate-mocks ## Generate all code (openapi + mocks)

.PHONY: generate-vendor
generate-vendor: generate
generate-all: generate-mocks ## Generate all code (mocks only - OpenAPI is dynamic)

##@ Development

.PHONY: build
build: generate-all ## Build the hyperfleet-api binary
@mkdir -p bin
@echo "Building version: ${VERSION}"
CGO_ENABLED=$(CGO_ENABLED) GOEXPERIMENT=boringcrypto ${GO} build $(GOFLAGS) -ldflags="$(LDFLAGS)" -o bin/hyperfleet-api ./cmd/hyperfleet-api
CGO_ENABLED=$(CGO_ENABLED) GOFIPS140=$(GOFIPS140) ${GO} build $(GOFLAGS) -ldflags="$(LDFLAGS)" -o bin/hyperfleet-api ./cmd/hyperfleet-api

.PHONY: install
install: generate-all ## Build and install binary to GOPATH/bin
CGO_ENABLED=$(CGO_ENABLED) GOEXPERIMENT=boringcrypto ${GO} install $(GOFLAGS) -ldflags="$(LDFLAGS)" ./cmd/hyperfleet-api
CGO_ENABLED=$(CGO_ENABLED) GOFIPS140=$(GOFIPS140) ${GO} install $(GOFLAGS) -ldflags="$(LDFLAGS)" ./cmd/hyperfleet-api

.PHONY: run
run: build ## Run the application
./bin/hyperfleet-api migrate
./bin/hyperfleet-api serve
GODEBUG=fips140=on ./bin/hyperfleet-api migrate
GODEBUG=fips140=on CRD_PATH=$(PWD)/charts/crds ./bin/hyperfleet-api serve

.PHONY: run-no-auth
run-no-auth: build ## Run the application without auth
./bin/hyperfleet-api migrate
./bin/hyperfleet-api serve --enable-authz=false --enable-jwt=false
GODEBUG=fips140=on ./bin/hyperfleet-api migrate
GODEBUG=fips140=on CRD_PATH=$(PWD)/charts/crds ./bin/hyperfleet-api serve --enable-authz=false --enable-jwt=false

.PHONY: run/docs
run/docs: check-container-tool ## Run swagger and host the api spec
Expand All @@ -163,7 +158,7 @@ run/docs: check-container-tool ## Run swagger and host the api spec
cmds: ## Build all binaries under cmd/
@mkdir -p bin
for cmd in $$(ls cmd); do \
CGO_ENABLED=$(CGO_ENABLED) GOEXPERIMENT=boringcrypto ${GO} build \
CGO_ENABLED=$(CGO_ENABLED) GOFIPS140=$(GOFIPS140) ${GO} build \
$(GOFLAGS) \
-ldflags="$(LDFLAGS)" \
-o "bin/$${cmd}" \
Expand Down Expand Up @@ -196,24 +191,24 @@ secrets: ## Initialize secrets directory with default values

.PHONY: test
test: install secrets $(GOTESTSUM) ## Run unit tests
OCM_ENV=unit_testing $(GOTESTSUM) --format $(TEST_SUMMARY_FORMAT) -- -p 1 -v $(TESTFLAGS) \
GODEBUG=fips140=on OCM_ENV=unit_testing $(GOTESTSUM) --format $(TEST_SUMMARY_FORMAT) -- -p 1 -v $(TESTFLAGS) \
./pkg/... \
./cmd/...

.PHONY: ci-test-unit
ci-test-unit: install secrets $(GOTESTSUM) ## Run unit tests with JSON output
OCM_ENV=unit_testing $(GOTESTSUM) --jsonfile-timing-events=$(unit_test_json_output) --format $(TEST_SUMMARY_FORMAT) -- -p 1 -v $(TESTFLAGS) \
GODEBUG=fips140=on OCM_ENV=unit_testing $(GOTESTSUM) --jsonfile-timing-events=$(unit_test_json_output) --format $(TEST_SUMMARY_FORMAT) -- -p 1 -v $(TESTFLAGS) \
./pkg/... \
./cmd/...

.PHONY: test-integration
test-integration: install secrets $(GOTESTSUM) ## Run integration tests
TESTCONTAINERS_RYUK_DISABLED=true OCM_ENV=integration_testing $(GOTESTSUM) --format $(TEST_SUMMARY_FORMAT) -- -p 1 -ldflags -s -v -timeout 1h $(TESTFLAGS) \
GODEBUG=fips140=on TESTCONTAINERS_RYUK_DISABLED=true OCM_ENV=integration_testing $(GOTESTSUM) --format $(TEST_SUMMARY_FORMAT) -- -p 1 -ldflags -s -v -timeout 1h $(TESTFLAGS) \
./test/integration

.PHONY: ci-test-integration
ci-test-integration: install secrets $(GOTESTSUM) ## Run integration tests with JSON output
TESTCONTAINERS_RYUK_DISABLED=true OCM_ENV=integration_testing $(GOTESTSUM) --jsonfile-timing-events=$(integration_test_json_output) --format $(TEST_SUMMARY_FORMAT) -- -p 1 -ldflags -s -v -timeout 1h $(TESTFLAGS) \
GODEBUG=fips140=on TESTCONTAINERS_RYUK_DISABLED=true OCM_ENV=integration_testing $(GOTESTSUM) --jsonfile-timing-events=$(integration_test_json_output) --format $(TEST_SUMMARY_FORMAT) -- -p 1 -ldflags -s -v -timeout 1h $(TESTFLAGS) \
./test/integration
Comment on lines +194 to 212
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify where GOFIPS140 is (and isn't) propagated in Makefile test/build paths.
rg -n -A2 -B2 '^(build|install|cmds|test|ci-test-unit|test-integration|ci-test-integration):|GOFIPS140=|GODEBUG=fips140=on' Makefile

Repository: openshift-hyperfleet/hyperfleet-api

Length of output: 2875


Pass GOFIPS140 into go test invocations for consistency with build/install.

The test, ci-test-unit, test-integration, and ci-test-integration targets set GODEBUG=fips140=on but do not pass GOFIPS140=$(GOFIPS140) when compiling test binaries. This creates a mismatch: production binaries built via build/install/cmds use GOFIPS140=$(GOFIPS140), but test binaries are compiled without this flag, potentially using different crypto implementations during testing.

Suggested patch
-GODEBUG=fips140=on OCM_ENV=unit_testing $(GOTESTSUM) --format $(TEST_SUMMARY_FORMAT) -- -p 1 -v $(TESTFLAGS) \
+GOFIPS140=$(GOFIPS140) GODEBUG=fips140=on OCM_ENV=unit_testing $(GOTESTSUM) --format $(TEST_SUMMARY_FORMAT) -- -p 1 -v $(TESTFLAGS) \
 		./pkg/... \
 		./cmd/...

-GODEBUG=fips140=on OCM_ENV=unit_testing $(GOTESTSUM) --jsonfile-timing-events=$(unit_test_json_output) --format $(TEST_SUMMARY_FORMAT) -- -p 1 -v $(TESTFLAGS) \
+GOFIPS140=$(GOFIPS140) GODEBUG=fips140=on OCM_ENV=unit_testing $(GOTESTSUM) --jsonfile-timing-events=$(unit_test_json_output) --format $(TEST_SUMMARY_FORMAT) -- -p 1 -v $(TESTFLAGS) \
 		./pkg/... \
 		./cmd/...

-GODEBUG=fips140=on TESTCONTAINERS_RYUK_DISABLED=true OCM_ENV=integration_testing $(GOTESTSUM) --format $(TEST_SUMMARY_FORMAT) -- -p 1 -ldflags -s -v -timeout 1h $(TESTFLAGS) \
+GOFIPS140=$(GOFIPS140) GODEBUG=fips140=on TESTCONTAINERS_RYUK_DISABLED=true OCM_ENV=integration_testing $(GOTESTSUM) --format $(TEST_SUMMARY_FORMAT) -- -p 1 -ldflags -s -v -timeout 1h $(TESTFLAGS) \
 			./test/integration

-GODEBUG=fips140=on TESTCONTAINERS_RYUK_DISABLED=true OCM_ENV=integration_testing $(GOTESTSUM) --jsonfile-timing-events=$(integration_test_json_output) --format $(TEST_SUMMARY_FORMAT) -- -p 1 -ldflags -s -v -timeout 1h $(TESTFLAGS) \
+GOFIPS140=$(GOFIPS140) GODEBUG=fips140=on TESTCONTAINERS_RYUK_DISABLED=true OCM_ENV=integration_testing $(GOTESTSUM) --jsonfile-timing-events=$(integration_test_json_output) --format $(TEST_SUMMARY_FORMAT) -- -p 1 -ldflags -s -v -timeout 1h $(TESTFLAGS) \
 			./test/integration
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Makefile` around lines 194 - 212, The test targets (test, ci-test-unit,
test-integration, ci-test-integration) set GODEBUG but do not pass the GOFIPS140
build flag into the go test invocations; update each target to export/pass
GOFIPS140=$(GOFIPS140) into the $(GOTESTSUM) / go test command (e.g., prefix the
command with GOFIPS140=$(GOFIPS140) or add it to the environment list) so test
binaries are built with the same GOFIPS140 setting as build/install, ensuring
consistent crypto behavior for functions invoked during tests.


.PHONY: test-all
Expand Down
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,26 +52,29 @@ See [PREREQUISITES.md](PREREQUISITES.md) for installation instructions.
### Installation

```bash
# 1. Generate OpenAPI code and mocks
make generate-all
# 1. Generate mocks for testing
make generate-mocks

# 2. Install dependencies
go mod download

# 3. Build binary
make build

# 4. Setup database
# 4. Initialize secrets
make secrets

# 5. Setup database
make db/setup

# 5. Run migrations
# 6. Run migrations
./bin/hyperfleet-api migrate

# 6. Start service (no auth)
# 7. Start service (no auth)
make run-no-auth
```

**Note**: Generated code is not tracked in git. You must run `make generate-all` after cloning.
**Note**: Mocks are generated from source interfaces. Run `make generate-mocks` after cloning.

### Accessing the API

Expand Down Expand Up @@ -105,7 +108,6 @@ Kubernetes clusters with provider-specific configurations, labels, and adapter-b
Groups of compute nodes within clusters.

**Main endpoints:**
- `GET /api/hyperfleet/v1/nodepools`
- `GET/POST /api/hyperfleet/v1/clusters/{cluster_id}/nodepools`
- `GET /api/hyperfleet/v1/clusters/{cluster_id}/nodepools/{nodepool_id}`
- `GET/POST /api/hyperfleet/v1/clusters/{cluster_id}/nodepools/{nodepool_id}/statuses`
Expand All @@ -131,12 +133,10 @@ curl -G http://localhost:8000/api/hyperfleet/v1/clusters \

```bash
make build # Build binary to bin/
make run-no-auth # Run without authentication
make run-no-auth # Run without authentication (loads CRDs from local files)
make test # Run unit tests
make test-integration # Run integration tests
make generate # Generate OpenAPI models
make generate-mocks # Generate test mocks
make generate-all # Generate OpenAPI models and mocks
make db/setup # Create PostgreSQL container
make image # Build container image
```
Expand Down
4 changes: 3 additions & 1 deletion charts/Chart.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
apiVersion: v2
name: hyperfleet-api
description: HyperFleet API - Cluster Lifecycle Management Service
description: HyperFleet API - Cluster Lifecycle Management Service with Custom Resource Definitions for Kubernetes-native discoverability
type: application
version: 1.0.0
appVersion: "1.0.0"
Expand All @@ -12,4 +12,6 @@ keywords:
- api
- kubernetes
- cluster-management
- crd
- custom-resource-definition
home: https://github.com/openshift-hyperfleet/hyperfleet-api
Loading