Skip to content
Closed
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,6 @@ hyperfleet-operator/bin/
# Compiled operator binaries (built outside bin/)
hyperfleet-operator/manager
hyperfleet-operator/compactor

# Generated passthrough raw output (regenerate with make codegen-passthrough)
*.passthrough.go.raw
70 changes: 64 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
.PHONY: help build test test-unit test-integration lint clean \
build-hyperfleet-db build-operator build-api \
test-hyperfleet-db test-operator test-operator-int test-api \
build-hyperfleet-db build-operator build-api build-api-codegen \
test-hyperfleet-db test-operator test-operator-int test-api test-api-codegen \
coverage-api-codegen \
test-e2e test-e2e-api test-e2e-cli test-e2e-platform-monitoring test-e2e-zoa test-e2e-authz \
e2e-authz-infra-up e2e-authz-infra-down e2e-init-db \
fmt vet verify deps \
manifests generate setup-envtest \
codegen-passthrough codegen-registry codegen-verify \
image-api image-operator image-push-api image-push-operator

# ── Configuration ────────────────────────────────────────────────────────
Expand Down Expand Up @@ -57,17 +59,20 @@ help:
@echo " build-api Platform API server"
@echo " build-operator Hyperfleet operator (manager + compactor)"
@echo " build-hyperfleet-db Hyperfleet DB library"
@echo " build-api-codegen API codegen tools (build-time generators)"
@echo ""
@echo "Test:"
@echo " test All tests (unit + integration)"
@echo " test-unit Unit tests: API + operator (no external services)"
@echo " test-unit Unit tests: API + operator + codegen (no external services)"
@echo " test-integration Integration tests: FleetDB + operator (podman)"
@echo " test-e2e-authz E2E authz (starts local infra)"
@echo " test-e2e-api E2E API"
@echo " test-e2e-cli E2E CLI"
@echo " test-e2e-zoa E2E ZOA"
@echo " test-e2e-platform-monitoring E2E monitoring"
@echo ""
@echo " coverage-api-codegen Coverage report for codegen (hack/api-codegen)"
@echo ""
@echo "Code Quality:"
@echo " lint golangci-lint on all modules"
@echo " fmt Format Go source"
Expand All @@ -77,6 +82,9 @@ help:
@echo "Code Generation:"
@echo " manifests Generate CRD manifests"
@echo " generate Generate deepcopy methods"
@echo " codegen-passthrough Run passthrough-gen (raw + curated types)"
@echo " codegen-registry Run marker-scanner (field_metadata.go/.json)"
@echo " codegen-verify Verify codegen packages compile"
@echo " setup-envtest Install envtest binaries (etcd, kube-apiserver)"
@echo " deps Download and tidy all modules"
@echo ""
Expand All @@ -86,7 +94,7 @@ help:

# ── Build ────────────────────────────────────────────────────────────────

build: build-hyperfleet-db build-operator build-api
build: build-hyperfleet-db build-operator build-api build-api-codegen

build-hyperfleet-db:
cd hyperfleet-db && go build ./...
Expand All @@ -98,17 +106,36 @@ build-operator:
build-api:
cd platform-api && go build -o ../bin/rosa-hyperfleet-api ./cmd

build-api-codegen:
cd hack/api-codegen && go build -o ../../bin/passthrough-gen ./cmd/passthrough-gen
cd hack/api-codegen && go build -o ../../bin/marker-scanner ./cmd/marker-scanner
cd hack/api-codegen && go build -o ../../bin/openapi-gen ./cmd/openapi-gen
cd hack/api-codegen && go build -o ../../bin/conversion-gen ./cmd/conversion-gen
cd hack/api-codegen && go build -o ../../bin/crd-variants ./cmd/crd-variants
cd hack/api-codegen && go build -o ../../bin/featuregate-info ./cmd/featuregate-info
cd hack/api-codegen && go build -o ../../bin/verify-configuration ./cmd/verify-configuration

# ── Test ─────────────────────────────────────────────────────────────────

test: test-unit test-integration

test-unit: test-api test-operator
test-unit: test-api test-operator test-api-codegen

test-integration: test-hyperfleet-db test-operator-int

test-api:
cd platform-api && go test -v -race -count=1 $$(go list ./... | grep -v '/test/e2e')

test-api-codegen:
cd hack/api-codegen && go test -v -race -count=1 ./...

coverage-api-codegen:
cd hack/api-codegen && go test -race -coverprofile=coverage.out ./...
cd hack/api-codegen && go tool cover -func=coverage.out
@echo ""
@echo "HTML report: hack/api-codegen/coverage.html"
cd hack/api-codegen && go tool cover -html=coverage.out -o coverage.html

test-operator: $(SETUP_ENVTEST)
@ASSETS=$$($(SETUP_ENVTEST) use -p path --bin-dir $(ENVTEST_BIN_DIR)) && \
echo "envtest assets: $$ASSETS" && \
Expand Down Expand Up @@ -170,16 +197,19 @@ fmt:
cd hyperfleet-db && go fmt ./...
cd hyperfleet-operator && go fmt ./...
cd platform-api && go fmt ./...
cd hack/api-codegen && go fmt ./...

vet:
cd hyperfleet-db && go vet ./...
cd hyperfleet-operator && go vet ./...
cd platform-api && go vet ./...
cd hack/api-codegen && go vet ./...

lint: $(GOLANGCI_LINT)
cd hyperfleet-db && $(GOLANGCI_LINT) run --config ../.golangci.yml --timeout 5m ./...
cd hyperfleet-operator && $(GOLANGCI_LINT) run --config ../.golangci.yml --timeout 5m ./...
cd platform-api && $(GOLANGCI_LINT) run --config ../.golangci.yml --timeout 5m ./...
cd hack/api-codegen && $(GOLANGCI_LINT) run --config ../../.golangci.yml --timeout 5m ./...

verify:
cd hyperfleet-db && go mod tidy
Expand All @@ -188,20 +218,23 @@ verify:
cd platform-api && go mod tidy
cd test && go mod tidy
cd hack/tools && go mod tidy
cd hack/api-codegen && go mod tidy
git diff --exit-code \
hyperfleet-db/go.mod hyperfleet-db/go.sum \
hyperfleet-operator/api/go.mod hyperfleet-operator/api/go.sum \
hyperfleet-operator/go.mod hyperfleet-operator/go.sum \
platform-api/go.mod platform-api/go.sum \
test/go.mod test/go.sum \
hack/tools/go.mod hack/tools/go.sum
hack/tools/go.mod hack/tools/go.sum \
hack/api-codegen/go.mod hack/api-codegen/go.sum

deps:
cd hyperfleet-db && go mod download && go mod tidy
cd hyperfleet-operator/api && go mod download && go mod tidy
cd hyperfleet-operator && go mod download && go mod tidy
cd platform-api && go mod download && go mod tidy
cd test && go mod download && go mod tidy
cd hack/api-codegen && go mod download && go mod tidy

# ── Code Generation ──────────────────────────────────────────────────────

Expand All @@ -216,6 +249,31 @@ ENVTEST_BIN_DIR ?= $(shell pwd)/.envtest
setup-envtest: $(SETUP_ENVTEST)
$(SETUP_ENVTEST) use --bin-dir $(ENVTEST_BIN_DIR)

# ── Codegen Pipeline ────────────────────────────────────────────────────

HYPERSHIFT_IMPORT_PATH ?= github.com/openshift/hypershift/api/hypershift/v1beta1
HYPERSHIFT_TYPES ?= HostedClusterSpec,NodePoolSpec
V1ALPHA1_DIR := hyperfleet-operator/api/v1alpha1
REGISTRY_DIR := platform-api/internal/codegen/registry

codegen-passthrough: build-api-codegen
./bin/passthrough-gen \
--source-dir=$$(cd hyperfleet-operator/api && go list -f '{{.Dir}}' $(HYPERSHIFT_IMPORT_PATH)) \
--types=$(HYPERSHIFT_TYPES) \
--output-dir=$(V1ALPHA1_DIR) \
--package=v1alpha1 \
--registry=$(REGISTRY_DIR)/field_metadata.json
rm -f $(V1ALPHA1_DIR)/zz_generated.passthrough.go

codegen-registry: build-api-codegen
./bin/marker-scanner \
--input-dirs=$(V1ALPHA1_DIR) \
--output-file=$(REGISTRY_DIR)/field_metadata.go

codegen-verify: build-api-codegen
cd hyperfleet-operator/api && go build ./...
cd platform-api && go build ./internal/codegen/...

Comment on lines +273 to +276

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Run the configuration verifier in this target.

codegen-verify only compiles packages; it never executes verify-configuration, so missing write-mode markers pass this pipeline. Invoke it with $(V1ALPHA1_DIR)/configuration.go.

Proposed fix
 codegen-verify: build-api-codegen
+	./bin/verify-configuration $(V1ALPHA1_DIR)/configuration.go
 	cd hyperfleet-operator/api && go build ./...
 	cd platform-api && go build ./internal/codegen/...
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
codegen-verify: build-api-codegen
cd hyperfleet-operator/api && go build ./...
cd platform-api && go build ./internal/codegen/...
codegen-verify: build-api-codegen
./bin/verify-configuration $(V1ALPHA1_DIR)/configuration.go
cd hyperfleet-operator/api && go build ./...
cd platform-api && go build ./internal/codegen/...
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Makefile` around lines 272 - 275, Update the codegen-verify target after the
existing build commands to invoke verify-configuration with
$(V1ALPHA1_DIR)/configuration.go, ensuring configuration markers are validated
as part of verification.

# ── Images ───────────────────────────────────────────────────────────────

image-api:
Expand Down
30 changes: 30 additions & 0 deletions hack/api-codegen/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# api-codegen

![Coverage](https://img.shields.io/badge/coverage-31.3%25-yellow)

Build-time code generators for the ROSA Hyperfleet API. These tools scan Go types with markers and generate passthrough types, OpenAPI schemas, CRD variants, conversion functions, and field validation metadata.


## Generators

| Command | Purpose |
|---------|---------|
| `passthrough-gen` | Generate passthrough struct types from HyperShift API types |
| `marker-scanner` | Extract `+hyperfleet:` marker metadata from Go types |
| `openapi-gen` | Generate OpenAPI schemas from annotated Go types |
| `conversion-gen` | Generate conversion functions between API versions |
| `crd-variants` | Produce CRD variants filtered by feature gates |
| `featuregate-info` | Emit feature gate metadata for CRD fields |
| `verify-configuration` | Validate marker consistency across types |

## Usage

```bash
make build-api-codegen # Build all 7 generator binaries
make test-api-codegen # Run tests
make coverage-api-codegen # Generate coverage report
```

## Reference

[api-management](./docs/api/api-management.md)
70 changes: 70 additions & 0 deletions hack/api-codegen/cmd/conversion-gen/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package main

import (
"flag"
"fmt"
"log"
"os"
"path/filepath"
"strings"

"github.com/openshift-online/rosa-hyperfleet-api/hack/api-codegen/pkg/conversion"
)

func main() {
var (
apiVersion string
crdPackage string
inputDirs string
outputDir string
)

flag.StringVar(&apiVersion, "api-version", "v1alpha1", "API version to generate for")
flag.StringVar(&crdPackage, "crd-package", "", "Import path to CRD types (required)")
flag.StringVar(&inputDirs, "input-dirs", "", "Comma-separated list of directories containing CRD source files (required)")
flag.StringVar(&outputDir, "output-dir", "", "Output directory for generated code (required)")
flag.Parse()

if crdPackage == "" || inputDirs == "" || outputDir == "" {
flag.Usage()
fmt.Fprintf(os.Stderr, "\nError: Missing required flags\n\n")
fmt.Fprintf(os.Stderr, "Example usage:\n")
fmt.Fprintf(os.Stderr, " %s \\\n", os.Args[0])
fmt.Fprintf(os.Stderr, " --api-version=v1alpha1 \\\n")
fmt.Fprintf(os.Stderr, " --crd-package=github.com/openshift-online/rosa-hyperfleet-api/hack/api-codegen/api/v1alpha1 \\\n")
fmt.Fprintf(os.Stderr, " --input-dirs=./api/v1alpha1 \\\n")
fmt.Fprintf(os.Stderr, " --output-dir=./pkg/conversion/v1alpha1\n")
os.Exit(1)
}

// Split input directories
dirs := strings.Split(inputDirs, ",")
for i, dir := range dirs {
// Convert to absolute path
absDir, err := filepath.Abs(dir)
if err != nil {
log.Fatalf("Failed to resolve directory %s: %v", dir, err)
}
dirs[i] = absDir
}

// Create generator
gen := conversion.NewGenerator(apiVersion, crdPackage, dirs, outputDir)

log.Printf("Conversion code generator")
log.Printf(" API Version: %s", apiVersion)
log.Printf(" CRD Package: %s", crdPackage)
log.Printf(" Input Dirs: %s", strings.Join(dirs, ", "))
log.Printf(" Output Dir: %s", outputDir)
log.Println()

// Generate
if err := gen.Generate(); err != nil {
log.Fatalf("Generation failed: %v", err)
}

log.Println("✓ Successfully generated:")
log.Println(" - REST types (rest/)")
log.Println(" - ServiceSetFields (../types.go)")
log.Println(" - Conversion functions (cluster.go, nodepool.go)")
}
75 changes: 75 additions & 0 deletions hack/api-codegen/cmd/crd-variants/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package main

import (
"flag"
"fmt"
"log"
"os"

"github.com/openshift-online/rosa-hyperfleet-api/hack/api-codegen/pkg/featuregate"
)

func main() {
var (
inputFile = flag.String("input", "", "Input CRD YAML file")
outputDir = flag.String("output-dir", "config/crd/variants", "Output directory for CRD variants")
baseName = flag.String("base-name", "", "Base name for output files (e.g., 'cluster' produces cluster_default.yaml)")
featureSet = flag.String("feature-set", "", "Generate only one feature set variant (default, techpreview, devpreview)")
)

flag.Parse()

if *inputFile == "" {
fmt.Fprintln(os.Stderr, "Error: --input is required")
flag.Usage()
os.Exit(1)
}

if *baseName == "" {
fmt.Fprintln(os.Stderr, "Error: --base-name is required")
flag.Usage()
os.Exit(1)
}

// Create output directory
if err := os.MkdirAll(*outputDir, 0755); err != nil {
log.Fatalf("Creating output directory: %v", err)
}

g := featuregate.NewCRDVariantGenerator()

if *featureSet != "" {
// Generate single variant
var fs featuregate.FeatureSet
switch *featureSet {
case "default":
fs = featuregate.Default
case "techpreview":
fs = featuregate.TechPreviewNoUpgrade
case "devpreview":
fs = featuregate.DevPreviewNoUpgrade
default:
log.Fatalf("Invalid feature set: %s (must be default, techpreview, or devpreview)", *featureSet)
}

outputPath := fmt.Sprintf("%s/%s_%s.yaml", *outputDir, *baseName, *featureSet)
fmt.Printf("Generating %s variant: %s\n", *featureSet, outputPath)

if err := g.GenerateVariant(*inputFile, outputPath, fs); err != nil {
log.Fatalf("Generating variant: %v", err)
}

fmt.Printf("✓ Generated %s variant\n", *featureSet)
} else {
// Generate all variants
fmt.Printf("Generating all CRD variants from %s to %s/\n", *inputFile, *outputDir)

if err := g.GenerateAllVariants(*inputFile, *outputDir, *baseName); err != nil {
log.Fatalf("Generating variants: %v", err)
}

fmt.Printf("✓ Generated default variant: %s/%s_default.yaml\n", *outputDir, *baseName)
fmt.Printf("✓ Generated techpreview variant: %s/%s_techpreview.yaml\n", *outputDir, *baseName)
fmt.Printf("✓ Generated devpreview variant: %s/%s_devpreview.yaml\n", *outputDir, *baseName)
}
}
Loading