Skip to content

ROSAENG-61801: feat: adding passthrough types /api/v1alpha1 - #193

Closed
cdoan1 wants to merge 13 commits into
openshift-online:mainfrom
cdoan1:ROSAENG-61801-passthrough-types
Closed

ROSAENG-61801: feat: adding passthrough types /api/v1alpha1#193
cdoan1 wants to merge 13 commits into
openshift-online:mainfrom
cdoan1:ROSAENG-61801-passthrough-types

Conversation

@cdoan1

@cdoan1 cdoan1 commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Description

Add codegen passthrough types to /api/v1alpha1

  • One place to add/curate fields as upstream HyperShift evolves
  • Markers (+hyperfleet:write-mode, +k8s:openapi-gen, +openshift:enable:FeatureGate) live on the actual CRD types
  • The codegen pipeline (marker-scanner, openapi-gen) reads the same types the operator reconciles
  • No version translation layer needed

NOTE: this PR is on top of ROSAENG-62606-codegen-component

  • hyperfleet-operator: ClusterSpec.HostedCluster is still hypershiftv1beta1.HostedClusterSpec. The render code (render/cluster.go:185) does cluster.Spec.HostedCluster.DeepCopy() and builds a hypershiftv1beta1.HostedCluster
    object directly.
  • platform-api: The handlers (handlers/cluster.go) and converters (convert.go) read/write cr.Spec.HostedCluster.IssuerURL on the upstream type. The runtime libraries (internal/codegen/registry, featuregate, conversion) exist
    but no handler or middleware calls into them.

The passthrough structs (HostedClusterSpecPassthrough, NodePoolSpecPassthrough) and the runtime libraries are scaffolding for downstream phases:

  • Phase 2 (ROSAENG-61802): field validation middleware will use the registry to enforce write-mode rules on API requests
  • Phase 3 (ROSAENG-61803): conversion functions will map between passthrough types and upstream types, replacing the hardcoded injection in render code
  • Phase 5 (ROSAENG-61805): openapi-gen will scan the markers to produce typed OpenAPI schemas

Deploying now is safe — the new envelope fields (DisplayName, Tags, etc.) are all optional/omitempty and the CRD will accept them, but no code path reads or acts on them yet.

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Refactoring (no functional changes)
  • CI/CD or tooling change

Testing

  • Unit tests pass (make test)
  • Integration tests pass (if applicable)
  • Manual verification completed

Checklist

  • My code follows the project's coding conventions
  • I have updated documentation as needed
  • I have added tests that prove my fix/feature works
  • All new and existing tests pass

Summary by CodeRabbit

  • New Features
    • Added configuration options for cluster and node pool resources, including kubelet, machine, networking, authentication, labels, repair, and display settings.
    • Added feature-gated API fields with support for default, TechPreview, and DevPreview variants.
    • Added API validation for mutable, immutable, and platform-managed fields.
    • Added generated API schemas, passthrough types, conversion support, and CRD variants.
  • Documentation
    • Added guidance for using the API code generation and validation tools.
  • Tests
    • Added comprehensive coverage for field markers, feature gates, validation, conversion, schemas, and generated API output.

@openshift-ci

openshift-ci Bot commented Jul 28, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: cdoan1

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci Bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Jul 28, 2026
@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Changes

The PR adds a standalone API code-generation module with marker scanning, field registries, feature-gate filtering, passthrough and conversion generation, OpenAPI output, and request validation. It also adds API configuration types, generated passthrough types, CLI commands, tests, documentation, dependencies, and Makefile workflows.

API codegen and validation

Layer / File(s) Summary
Marker scanning and registry generation
hack/api-codegen/pkg/markers/*, hack/api-codegen/pkg/registry/*
Scans Go markers into field metadata and emits deterministic Go and JSON registries.
Feature-gate semantics and CRD variants
hack/api-codegen/pkg/featuregate/*, platform-api/internal/codegen/featuregate/*
Defines feature stages and gates, filters registry fields, and generates feature-specific CRD variants.
Passthrough and OpenAPI generators
hack/api-codegen/pkg/passthrough/*, hack/api-codegen/pkg/openapi/*
Mirrors upstream structs with metadata markers and generates Swagger definitions from Go ASTs.
REST and CRD conversion generation
hack/api-codegen/pkg/conversion/*
Generates REST types, ServiceSet fields, and Cluster/NodePool projection helpers, including mirror mappings.
Metadata-driven request validation
hack/api-codegen/pkg/validation/*
Enforces field write modes, feature gates, immutable updates, service-set restrictions, and aggregated validation errors.
API envelope and generated passthrough types
hyperfleet-operator/api/v1alpha1/*
Adds configuration and envelope fields with markers and generated HostedCluster/NodePool passthrough structs.
CLI and Makefile pipeline integration
hack/api-codegen/cmd/*, Makefile, hack/api-codegen/go.mod, hack/api-codegen/README.md
Adds generator CLIs, module metadata, documentation, and build/test/coverage/verification/codegen targets.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

@cdoan1 cdoan1 changed the title ROSAENG-61801L feat: adding passthrough types /api/v1alpha1 ROSAENG-61801: feat: adding passthrough types /api/v1alpha1 Jul 28, 2026
@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Jul 28, 2026
@openshift-ci-robot

openshift-ci-robot commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

@cdoan1: This pull request references ROSAENG-61801 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "5.0.0" version, but no target version was set.

Details

In response to this:

Description

Add codegen passthrough types to /api/v1alpha1

  • One place to add/curate fields as upstream HyperShift evolves
  • Markers (+hyperfleet:write-mode, +k8s:openapi-gen, +openshift:enable:FeatureGate) live on the actual CRD types
  • The codegen pipeline (marker-scanner, openapi-gen) reads the same types the operator reconciles
  • No version translation layer needed

NOTE: this PR is on top of ROSAENG-62606-codegen-component

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Refactoring (no functional changes)
  • CI/CD or tooling change

Testing

  • Unit tests pass (make test)
  • Integration tests pass (if applicable)
  • Manual verification completed

Checklist

  • My code follows the project's coding conventions
  • I have updated documentation as needed
  • I have added tests that prove my fix/feature works
  • All new and existing tests pass

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the openshift-eng/jira-lifecycle-plugin repository.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 13

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (8)
hack/api-codegen/cmd/conversion-gen/main.go-41-48 (1)

41-48: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reject empty --input-dirs entries. Trailing or doubled commas pass "" to filepath.Abs, which resolves to the current working directory; trim entries and fail fast on empties.

🤖 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 `@hack/api-codegen/cmd/conversion-gen/main.go` around lines 41 - 48, Update the
inputDirs parsing loop to trim whitespace from each entry before calling
filepath.Abs, and fail fast when a trimmed entry is empty so trailing or doubled
commas are rejected instead of resolving to the current directory. Preserve
absolute-path conversion for valid entries and keep the existing error handling
through log.Fatalf.
hack/api-codegen/pkg/openapi/generator_test.go-12-13 (1)

12-13: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Use a test-owned temp file.
/tmp/openapi-test.json can collide with concurrent test runs, and the cleanup path ignores os.Remove errors. Use filepath.Join(t.TempDir(), "openapi.json") instead.

🤖 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 `@hack/api-codegen/pkg/openapi/generator_test.go` around lines 12 - 13, Update
the test setup around tmpFile to create the file with filepath.Join(t.TempDir(),
"openapi.json") instead of a shared /tmp path. Remove the manual defer cleanup,
since t.TempDir() owns lifecycle cleanup and avoids concurrent test collisions.

Source: Path instructions

hack/api-codegen/cmd/verify-configuration/main.go-68-85 (1)

68-85: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Validate the write-mode value, not just marker presence.

A typo such as +hyperfleet:write-mode=mutabl passes because any containing string sets hasWriteMode. Parse the marker and allow-list mutable, immutable, and service-set.

🤖 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 `@hack/api-codegen/cmd/verify-configuration/main.go` around lines 68 - 85,
Update the marker handling in the field and inline-comment scans to parse the
value following +hyperfleet:write-mode= instead of treating marker presence as
valid. Set hasWriteMode only for mutable, immutable, or service-set; reject
unknown or misspelled values while preserving the existing visibility-marker
handling.
hack/api-codegen/pkg/validation/validator.go-92-124 (1)

92-124: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Non-deterministic error ordering.

req.Fields is a map, so the for fieldPath := range req.Fields loop yields errors in a randomized order. Callers surfacing ValidationErrors.Error() to users (or logging it) will see field ordering vary run-to-run for the same invalid request. Consider sorting collected field paths (or the resulting errors slice) before returning.

🤖 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 `@hack/api-codegen/pkg/validation/validator.go` around lines 92 - 124, Make
Validator.Validate produce deterministic ValidationErrors by sorting the field
paths from req.Fields before iterating, or sorting the collected errors before
returning. Preserve the existing feature-gate and validateWriteMode checks while
ensuring identical invalid requests always return errors in a stable field
order.
hack/api-codegen/pkg/featuregate/registry.go-50-62 (1)

50-62: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Non-deterministic gate order.

GatesForFeatureSet appends to gates while iterating the HyperFleetFeatureGates map, so the returned order is randomized per run. This feeds directly into SummarizeFeatureSet's %v formatting in crd_filter.go, producing non-reproducible summary text across runs. Sort the result before returning.

🤖 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 `@hack/api-codegen/pkg/featuregate/registry.go` around lines 50 - 62, Sort the
gates collected by GatesForFeatureSet before returning them, ensuring
deterministic ordering for SummarizeFeatureSet output while preserving the
existing stage filtering behavior.
hack/api-codegen/pkg/featuregate/crd_variant.go-26-70 (1)

26-70: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Swallowed Close error on the error path.

At lines 57-64, f.Close() is invoked without checking its return value before returning the primary error. Per the Go error-handling guideline for this repo, error returns should not be discarded — a failure to close (e.g. flush error) after a partial write could leave a corrupted/truncated output file without any signal.

🔧 Proposed fix
 	if err := encoder.Encode(&crd); err != nil {
-		f.Close()
+		_ = f.Close()
 		return fmt.Errorf("writing YAML: %w", err)
 	}
 	if err := encoder.Close(); err != nil {
-		f.Close()
+		_ = f.Close()
 		return fmt.Errorf("closing YAML encoder: %w", err)
 	}

As per path instructions, **/*.go should "Never ignore error returns."

🤖 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 `@hack/api-codegen/pkg/featuregate/crd_variant.go` around lines 26 - 70, Update
the error paths in GenerateVariant so every f.Close() call checks and handles
its returned error instead of discarding it, including failures from
encoder.Encode and encoder.Close. Preserve the original operation error while
incorporating any close failure into the returned error, and keep the existing
successful close flow unchanged.

Source: Path instructions

hack/api-codegen/pkg/passthrough/loader.go-250-260 (1)

250-260: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Potential panic when typeName == "Spec".

base := strings.TrimSuffix(typeName, "Spec") yields "" when typeName is exactly "Spec", and since base != typeName the code proceeds to runes[0] = ... on an empty slice, panicking. Unlikely in practice given current type names, but there's no guard.

🐛 Proposed fix
 	base := strings.TrimSuffix(typeName, "Spec")
-	if base == typeName {
+	if base == typeName || base == "" {
 		return ""
 	}
🤖 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 `@hack/api-codegen/pkg/passthrough/loader.go` around lines 250 - 260, Guard
deriveFieldPrefix after trimming the “Spec” suffix so an empty base returns an
empty prefix before indexing runes. Preserve the existing behavior for non-Spec
type names and valid suffixed types.
hack/api-codegen/pkg/openapi/generator.go-190-231 (1)

190-231: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fields without any JSON tag are silently dropped from the schema instead of falling back to the Go field name.

jsonName == "" || jsonName == "-" is treated as "excluded," but Go's encoding/json semantics only exclude on explicit json:"-"; a field with no tag at all is still marshaled using its Go field name. Combined with extractJSONTag returning "" for untagged fields, such fields silently vanish from the generated OpenAPI schema with no warning.

🐛 Proposed fix
 			jsonName := g.extractJSONTag(field)
-			if jsonName == "" || jsonName == "-" {
+			if jsonName == "-" {
 				continue
 			}
+			if jsonName == "" {
+				jsonName = name.Name
+			}
🤖 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 `@hack/api-codegen/pkg/openapi/generator.go` around lines 190 - 231, Update the
field-name handling in the schema-generation loop around extractJSONTag and
generateFieldSchema so an untagged field falls back to its Go identifier name,
matching encoding/json behavior. Continue excluding fields only when the
explicit JSON tag is "-", and preserve existing handling for tagged names and
required fields.
🧹 Nitpick comments (19)
hack/api-codegen/cmd/featuregate-info/main.go (1)

12-47: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Handle CLI output errors consistently. Both commands discard errors from fmt.Print*/fmt.Fprint*, which can report a broken pipe or failed output stream.

  • hack/api-codegen/cmd/featuregate-info/main.go#L12-L47: route output through a helper that exits non-zero when writing fails.
  • hack/api-codegen/cmd/verify-configuration/main.go#L15-L115: use the same checked-output pattern for diagnostics and success messages.

As per path instructions, “Never ignore error returns.”

🤖 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 `@hack/api-codegen/cmd/featuregate-info/main.go` around lines 12 - 47, Route
every fmt.Print*/fmt.Fprint* call in
hack/api-codegen/cmd/featuregate-info/main.go (lines 12-47) through a shared
checked-output helper that exits non-zero on write failure; apply the same
pattern to all diagnostic and success output in
hack/api-codegen/cmd/verify-configuration/main.go (lines 15-115), ensuring no
output error returns are ignored.

Source: Path instructions

hack/api-codegen/pkg/validation/gated_writemode_test.go (1)

162-172: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assertion doesn't verify error content, only that an error occurred.

errorReason (e.g. "immutable", "service-set") is set per test case to document the expected failure category, but the check only verifies errStr is non-empty rather than strings.Contains(errStr, tt.errorReason). This means the carefully-crafted precedence scenarios (e.g. "Multiple gates - first match wins") wouldn't actually catch a regression that returns the wrong kind of validation error (e.g. "service-set" instead of "immutable") as long as some error is returned. validator_test.go already uses the correct strings.Contains pattern nearby.

✅ Proposed fix
+	"strings"
 	"testing"
 ...
 			} else if tt.errorReason != "" {
-				errStr := err.Error()
-				if errStr == "" || len(errStr) == 0 {
-					t.Errorf("Expected error containing %q, got empty error", tt.errorReason)
-				}
+				if !strings.Contains(err.Error(), tt.errorReason) {
+					t.Errorf("Expected error containing %q, got %v", tt.errorReason, err)
+				}
🤖 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 `@hack/api-codegen/pkg/validation/gated_writemode_test.go` around lines 162 -
172, Update the error assertion in the gated writemode test’s expectError branch
to verify that err.Error() contains tt.errorReason using strings.Contains.
Preserve the existing nil and empty-error checks, and add the required strings
import if it is not already present.
hack/api-codegen/pkg/featuregate/crd_filter.go (1)

11-58: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Field-inclusion predicate duplicated across the package.

The "skip hidden → no gate means always include → otherwise check IsGateEnabled" logic is repeated verbatim in FilterCRDFields and FieldsForFeatureSet, and reimplemented a third time in crd_variant.go's shouldIncludeField. Extracting a single func isFieldIncluded(meta registry.FieldMeta, fs FeatureSet) bool helper would remove the duplication and prevent the three copies from silently diverging.

🤖 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 `@hack/api-codegen/pkg/featuregate/crd_filter.go` around lines 11 - 58, Extract
the shared field-inclusion predicate into an isFieldIncluded(meta
registry.FieldMeta, fs FeatureSet) bool helper, covering hidden fields, ungated
fields, and IsGateEnabled checks. Update FilterCRDFields, FieldsForFeatureSet,
and crd_variant.go’s shouldIncludeField to call this helper, removing their
duplicated logic while preserving current behavior.
hack/api-codegen/pkg/featuregate/crd_variant.go (2)

26-47: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated read-parse-filter logic between GenerateVariant and WriteVariantToWriter.

Both functions repeat the same "read file → yaml.Unmarshal → build filterContextfilterCRDNode" sequence. Consider extracting a shared loadAndFilterCRD(inputPath string, featureSet FeatureSet) (*yaml.Node, error) helper used by both, and by GenerateVariant's error-checked file write and WriteVariantToWriter's writer-based encode.

Also applies to: 204-235

🤖 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 `@hack/api-codegen/pkg/featuregate/crd_variant.go` around lines 26 - 47,
Extract the duplicated read, YAML unmarshal, filterContext creation, and
filterCRDNode invocation from GenerateVariant and WriteVariantToWriter into a
shared loadAndFilterCRD helper returning the filtered yaml.Node and errors.
Update both callers to use this helper while preserving GenerateVariant’s file
output and WriteVariantToWriter’s writer-based encoding behavior.

161-180: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Same inclusion predicate duplicated here as in crd_filter.go.

shouldIncludeField re-implements the Hidden/FeatureGate inclusion check already present in crd_filter.go's FilterCRDFields/FieldsForFeatureSet. Worth consolidating into a shared helper (see companion comment on crd_filter.go).

🤖 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 `@hack/api-codegen/pkg/featuregate/crd_variant.go` around lines 161 - 180, The
field inclusion predicate is duplicated between
CRDVariantGenerator.shouldIncludeField and crd_filter.go. Consolidate the Hidden
and FeatureGate checks into a shared helper, then update shouldIncludeField and
the existing FilterCRDFields/FieldsForFeatureSet path to reuse it while
preserving structural-field handling and current inclusion behavior.
hack/api-codegen/pkg/featuregate/crd_variant_test.go (1)

105-207: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding a regression case for schema-keyword vs. field-name path collisions.

The gated field in this test (tags) has its entire subtree removed, so it doesn't exercise the scenario where a real nested field shares a name with an OpenAPI schema keyword (e.g. type) at the same nesting depth — see the critical-severity comment on crd_variant.go's filterCRDNode. Once that's fixed, a test case here would help lock in the fix.

🤖 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 `@hack/api-codegen/pkg/featuregate/crd_variant_test.go` around lines 105 - 207,
Extend TestCRDVariantGenerator_GenerateVariant with a nested gated schema field
whose real property name collides with an OpenAPI keyword such as “type”, then
assert the generated default and TechPreview variants preserve or remove that
field correctly. Ensure the fixture exercises filterCRDNode at the same nesting
depth for both the schema keyword and the field-name path.
hack/api-codegen/pkg/validation/example_test.go (1)

54-60: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Example doesn't exercise real validator behavior.

Unlike the other Example* functions in this file, ExampleValidator_Validate_immutable just prints a static string rather than actually validating a request — it documents intent but doesn't prove the immutable write-mode path works. gated_writemode_test.go shows the pattern for constructing a Validator with a custom registry entry that has an immutable field; mirroring that here (with a fabricated field, similar to the spec.name/registry.Immutable cases used elsewhere) would make this example an actual, trustworthy demonstration.

🤖 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 `@hack/api-codegen/pkg/validation/example_test.go` around lines 54 - 60, The
ExampleValidator_Validate_immutable example must exercise Validator behavior
instead of printing a static message. Mirror the custom-registry setup from
gated_writemode_test.go, defining a fabricated immutable field with the
appropriate registry and write-mode markers, then validate create and update
requests to demonstrate that setting is allowed on create but rejected on
update.
hack/api-codegen/pkg/passthrough/integration_test.go (1)

94-105: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reinvents strings.Contains.

contains/findSubstring duplicate the standard library's strings.Contains.

♻️ Proposed fix
-// Check for expected type definitions
-if !contains(contentStr, "type HostedClusterSpecPassthrough struct") {
+if !strings.Contains(contentStr, "type HostedClusterSpecPassthrough struct") {
 	t.Error("Missing HostedClusterSpecPassthrough type definition")
 }
...
-func contains(s, substr string) bool {
-	return len(s) > 0 && len(substr) > 0 && len(s) >= len(substr) && findSubstring(s, substr)
-}
-
-func findSubstring(s, substr string) bool {
-	for i := 0; i <= len(s)-len(substr); i++ {
-		if s[i:i+len(substr)] == substr {
-			return true
-		}
-	}
-	return false
-}

(remember to add "strings" to the import block)

🤖 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 `@hack/api-codegen/pkg/passthrough/integration_test.go` around lines 94 - 105,
Replace the custom contains/findSubstring helpers with the standard library’s
strings.Contains, adding the strings import and updating all callers of contains
accordingly; remove both redundant helper functions.
hack/api-codegen/pkg/passthrough/loader_test.go (2)

1-1: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Tests requiring live module resolution aren't separated from pure unit tests. TestLoadHyperShiftTypes, TestGenerateTypeDef, and TestGenerate all call NewGeneratorFromImportPath, which shells out to go list and needs network/module-cache access to resolve github.com/openshift/hypershift/api, unlike the properly-labeled integration_test.go.

  • hack/api-codegen/pkg/passthrough/loader_test.go#L10-30: tag TestLoadHyperShiftTypes with a build constraint (e.g. //go:build integration) or move it alongside integration_test.go.
  • hack/api-codegen/pkg/passthrough/loader_test.go#L168-203: same for TestGenerateTypeDef.
  • hack/api-codegen/pkg/passthrough/generator_test.go#L1-67: same for TestGenerate.
🤖 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 `@hack/api-codegen/pkg/passthrough/loader_test.go` at line 1, Separate the live
module-resolution tests from default unit-test runs by adding the existing
integration build constraint to TestLoadHyperShiftTypes, TestGenerateTypeDef,
and TestGenerate. Apply the constraint consistently to the files containing
these tests, preserving their current test logic and allowing them to run only
when integration tests are explicitly selected.

10-30: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Network/module-resolution dependent tests lack a build tag.

Same pattern as generator_test.go: these tests require live go list resolution of the HyperShift module. See consolidated comment.

Also applies to: 168-203

🤖 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 `@hack/api-codegen/pkg/passthrough/loader_test.go` around lines 10 - 30, Add
the same network/module-resolution build tag used by generator_test.go to the
tests in loader_test.go, including TestLoadHyperShiftTypes and the additional
tests around lines 168–203, so they are excluded unless explicitly enabled.
hack/api-codegen/pkg/passthrough/generator_test.go (1)

1-67: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Network/module-resolution dependent test lacks a build tag.

TestGenerate calls NewGeneratorFromImportPath which shells out to go list for github.com/openshift/hypershift/api/..., requiring network/module-cache access — the same pattern flagged in loader_test.go. See consolidated comment.

🤖 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 `@hack/api-codegen/pkg/passthrough/generator_test.go` around lines 1 - 67, Mark
TestGenerate with the repository’s established build tag for
network/module-resolution-dependent tests, matching the convention used in
loader_test.go. Ensure the tag is placed so default test runs exclude this test
while explicitly tagged runs still execute it.
hack/api-codegen/pkg/passthrough/gomod.go (2)

19-32: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

go list invocation has no timeout/cancellation.

exec.Command with CombinedOutput() can hang indefinitely if go list stalls (e.g. slow/unreachable module proxy), blocking the codegen run with no way to cancel it.

As per path instructions, Go code should use context.Context for cancellation and timeouts.

🔧 Proposed fix
+import "context"
+import "time"
+
 func ResolvePackageDir(importPath string) (string, error) {
-	cmd := exec.Command("go", "list", "-f", "{{.Dir}}", importPath)
+	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+	defer cancel()
+	cmd := exec.CommandContext(ctx, "go", "list", "-f", "{{.Dir}}", importPath)
 	output, err := cmd.CombinedOutput()
🤖 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 `@hack/api-codegen/pkg/passthrough/gomod.go` around lines 19 - 32, Update
ResolvePackageDir to accept or obtain a context.Context and invoke go list with
exec.CommandContext, applying the established timeout policy so stalled module
resolution can be cancelled. Preserve the existing output trimming and error
messages, including command output when execution fails.

Source: Path instructions


44-63: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hardcoded "hypershift" prefix contradicts the generic function name.

NewGeneratorFromImportPath is written as a general-purpose constructor from any import path, but the alias is built as "hypershift" + lastSegment regardless of the actual package. If this is ever invoked for a non-HyperShift import path, the generated alias would be misleading/wrong (e.g. .../config/v1 → alias "hypershiftv1").

♻️ Proposed fix
-	// Extract package alias from import path (last segment)
-	parts := strings.Split(importPath, "/")
-	packageAlias := "hypershift" + parts[len(parts)-1] // e.g., "hypershiftv1beta1"
+	// Extract package alias from the last two import path segments to
+	// disambiguate versioned packages (e.g. "hypershiftv1beta1").
+	parts := strings.Split(importPath, "/")
+	packageAlias := parts[len(parts)-1]
+	if len(parts) >= 2 {
+		packageAlias = parts[len(parts)-2] + parts[len(parts)-1]
+	}
🤖 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 `@hack/api-codegen/pkg/passthrough/gomod.go` around lines 44 - 63, Update
NewGeneratorFromImportPath so SourcePackageAlias is derived generically from the
import path’s final segment without prepending the hardcoded “hypershift”
prefix. Preserve the existing import-path resolution and generator
initialization behavior.
hack/api-codegen/pkg/passthrough/loader.go (1)

1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate struct-tag parsing and directory-filtering logic between passthrough and openapi packages. Both packages independently hand-roll the same small helpers instead of sharing a common internal package, and both tag parsers reimplement what reflect.StructTag already provides correctly.

  • hack/api-codegen/pkg/passthrough/loader.go#L262-277: replace parseStructTag with reflect.StructTag(tag).Get("json") and move it to a shared helper package.
  • hack/api-codegen/pkg/openapi/generator.go#L316-356: replace extractJSONTag/isRequired's manual strings.Fields parsing with the same shared reflect.StructTag-based helper.
  • hack/api-codegen/pkg/passthrough/loader.go#L21-26: extract the _test.go/zz_generated* skip predicate used by parser.ParseDir into a shared helper.
  • hack/api-codegen/pkg/openapi/generator.go#L115-120: reuse the same shared skip predicate instead of the duplicated inline closure.
🤖 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 `@hack/api-codegen/pkg/passthrough/loader.go` at line 1, Create a shared
internal helper package for JSON struct-tag extraction and generated/test-file
filtering, then update passthrough’s parseStructTag and openapi’s
extractJSONTag/isRequired to use reflect.StructTag-based parsing while
preserving required-field behavior. Replace both parser.ParseDir skip closures
with the shared predicate, including _test.go and zz_generated* exclusions, and
remove the duplicated local helpers.
hack/api-codegen/pkg/passthrough/generator.go (1)

86-90: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Debug .raw artifact is written unconditionally on every run.

The raw pre-format output is written on every successful Generate() call, not just on formatting failure, leaving a stray zz_generated.passthrough.go.raw file alongside the real output in every generated directory.

♻️ Proposed fix
-	// Write unformatted first for debugging
-	outputFile := filepath.Join(outputDir, "zz_generated.passthrough.go")
-	if err := os.WriteFile(outputFile+".raw", buf.Bytes(), 0644); err != nil {
-		return fmt.Errorf("writing raw output file: %w", err)
-	}
-
 	// Format the generated code
+	outputFile := filepath.Join(outputDir, "zz_generated.passthrough.go")
 	formatted, err := format.Source(buf.Bytes())
 	if err != nil {
+		// Dump the raw output only when formatting fails, to aid debugging.
+		_ = os.WriteFile(outputFile+".raw", buf.Bytes(), 0644)
 		return fmt.Errorf("formatting generated code: %w", err)
 	}
🤖 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 `@hack/api-codegen/pkg/passthrough/generator.go` around lines 86 - 90, Update
Generate so the raw output artifact is only written when formatting fails,
rather than unconditionally before formatting. Move or defer the os.WriteFile
call for outputFile+".raw" into the formatting-error path while preserving the
existing error context and normal formatted output behavior.
hyperfleet-operator/api/v1alpha1/configuration.go (1)

186-224: 📐 Maintainability & Code Quality | 🔵 Trivial

Nine configuration sub-types are still stubs.

APIServerNetworkConfiguration, ClusterAuthentication, FeatureGateConfiguration, ImageConfiguration, IngressConfiguration, NetworkConfiguration, OAuthConfiguration, SchedulerConfiguration, ProxyConfiguration are empty placeholders. Since they're referenced from ClusterConfiguration (hidden/service-set), they won't block this PR, but flagging so follow-up work is tracked.

Want me to open a tracking issue for filling these in with granular markers similar to KubeletConfig/MachineConfigSpec?

🤖 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 `@hyperfleet-operator/api/v1alpha1/configuration.go` around lines 186 - 224,
The nine configuration types—APIServerNetworkConfiguration,
ClusterAuthentication, FeatureGateConfiguration, ImageConfiguration,
IngressConfiguration, NetworkConfiguration, OAuthConfiguration,
SchedulerConfiguration, and ProxyConfiguration—remain empty stubs. Define their
configuration fields with granular markers consistent with KubeletConfig and
MachineConfigSpec, preserving their existing references from
ClusterConfiguration.
hack/api-codegen/pkg/markers/generator.go (1)

129-154: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Unrecognized WriteMode values are silently dropped instead of failing generation.

Neither switch has a default case; an unexpected WriteMode (e.g. a scanner bug or marker typo) results in a generated FieldMeta entry missing its WriteMode, which is harder to notice than a build failure.

♻️ Proposed fix
 		switch meta.WriteMode {
 		case Mutable:
 			field.WriteMode = "Mutable"
 		case Immutable:
 			field.WriteMode = "Immutable"
 		case ServiceSet:
 			field.WriteMode = "ServiceSet"
+		default:
+			return fmt.Errorf("unknown write mode %q for field %q", meta.WriteMode, path)
 		}
🤖 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 `@hack/api-codegen/pkg/markers/generator.go` around lines 129 - 154, Add
default handling to both WriteMode switches in the generator logic around
meta.WriteMode and gated.WriteMode, and fail generation immediately when an
unrecognized value is encountered. Ensure the failure identifies the invalid
WriteMode and relevant field or feature-gate context instead of silently
producing an empty WriteMode.
hack/api-codegen/pkg/markers/json.go (1)

20-26: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Duplicate jsonField struct + no validation of writeMode values on load.

jsonField is redefined identically in both GenerateJSON and LoadRegistryFromJSONBytes. Additionally, LoadRegistryFromJSONBytes casts field.WriteMode directly to WriteMode without checking it's one of Mutable/Immutable/ServiceSet — a typo in a hand-edited registry JSON (or a future marker-scanner bug) would load silently instead of failing.

♻️ Proposed fix
+type jsonField struct {
+	FieldPath                  string                 `json:"fieldPath"`
+	WriteMode                  string                 `json:"writeMode,omitempty"`
+	FeatureGate                string                 `json:"featureGate,omitempty"`
+	Hidden                     bool                   `json:"hidden,omitempty"`
+	FeatureGateAwareWriteModes []FeatureGateWriteMode `json:"featureGateAwareWriteModes,omitempty"`
+}
+
 func LoadRegistryFromJSONBytes(data []byte) (FieldRegistry, error) {
-	type jsonField struct { ... }
 	var fields []jsonField
 	if err := json.Unmarshal(data, &fields); err != nil {
 		return nil, fmt.Errorf("unmarshaling JSON: %w", err)
 	}

 	registry := make(FieldRegistry)
 	for _, field := range fields {
+		wm := WriteMode(field.WriteMode)
+		if wm != "" && wm != Mutable && wm != Immutable && wm != ServiceSet {
+			return nil, fmt.Errorf("field %q: unknown writeMode %q", field.FieldPath, field.WriteMode)
+		}
 		registry[field.FieldPath] = FieldMeta{
 			FieldPath:                  field.FieldPath,
-			WriteMode:                  WriteMode(field.WriteMode),
+			WriteMode:                  wm,
 			...

Also applies to: 71-97

🤖 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 `@hack/api-codegen/pkg/markers/json.go` around lines 20 - 26, Deduplicate the
identical jsonField definition by declaring it once and reusing it in both
GenerateJSON and LoadRegistryFromJSONBytes. In LoadRegistryFromJSONBytes,
validate each non-empty field.WriteMode against the supported WriteMode values
Mutable, Immutable, and ServiceSet before converting it; return an error for any
unknown value instead of loading it silently.
hack/api-codegen/pkg/conversion/generator.go (1)

200-221: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Hardcoded type/prefix dispatch tables duplicated and fragile.

buildFieldPath special-cases exactly HostedCluster/NodePool prefixes for passthrough types (Lines 208-217), and the v1alpha1Types list in qualifyType (Lines 271-284) is duplicated verbatim inside generateRESTType (Lines 409-412). Both must be manually kept in sync as new passthrough types or v1alpha1 configuration types are added — the same failure mode that produced the registry Hidden-flag drift found elsewhere in this PR. Consider deriving the passthrough prefix from a struct/registry convention rather than name prefixes, and hoisting the v1alpha1Types list to a single package-level slice reused by both call sites.

Also applies to: 268-308, 395-419

🤖 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 `@hack/api-codegen/pkg/conversion/generator.go` around lines 200 - 221, Remove
the duplicated type/prefix dispatch in Generator.buildFieldPath by deriving
passthrough registry prefixes from the existing struct or registry convention
instead of hardcoded HostedCluster and NodePool name checks. Hoist the
v1alpha1Types list used by qualifyType and generateRESTType into one
package-level slice, then reuse it from both call sites so additions remain
synchronized.
🤖 Prompt for all review comments with 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.

Inline comments:
In `@hack/api-codegen/cmd/passthrough-gen/field_metadata.json`:
- Around line 317-320: Synchronize the six affected field metadata entries,
including spec.hostedCluster.autoNode and the entries at the other referenced
locations, with the canonical registry so each preserves its hidden-field
designation. Update the metadata definitions to include the matching hidden
setting while retaining their existing fieldPath and writeMode values.

In `@hack/api-codegen/go.mod`:
- Around line 1-48: Align the API dependency versions in the codegen module’s
go.mod with those declared by hyperfleet-operator/api/go.mod, updating both
github.com/openshift/hypershift/api and the indirect github.com/openshift/api
pins. Ensure passthrough.NewGeneratorFromImportPath uses the same module graph
as the consumer so generated types include the newer upstream fields.

In `@hack/api-codegen/pkg/conversion/generator.go`:
- Around line 504-547: Update the ServiceSetFields collection around fieldsMap
to deduplicate using a resource-qualified key derived from the full field path,
including the spec.hostedCluster or spec.nodePool segment, rather than only the
leaf JSON tag. Derive distinct GoName and JSONTag values from that qualified
path so each resource’s release, platform, and pausedUntil fields remain
separate, and ensure generateUnprojectFunction uses the corresponding qualified
members for each resource.

In `@hack/api-codegen/pkg/featuregate/crd_filter.go`:
- Around line 11-33: Make FilterCRDFields return fields in a deterministic order
by sorting the collected includedFields before returning it. Preserve the
existing hidden-field and feature-gate filtering behavior, and add the required
standard-library import for sorting.

In `@hack/api-codegen/pkg/passthrough/generator.go`:
- Around line 106-156: Update Generator.collectImports to derive qualified-type
imports from g.parsedFiles and each source *ast.File.Imports declaration,
resolving selector qualifiers to their actual import paths instead of matching
only configv1, corev1, and metav1 prefixes. Preserve the existing source-package
import handling and deduplication, and ensure every referenced external
qualifier is included in the generated imports.

In `@hack/api-codegen/pkg/passthrough/loader.go`:
- Around line 217-248: Update Generator.getMarkersForField so a registry entry
with an empty meta.WriteMode still receives the safe default
+hyperfleet:write-mode=service-set marker. Preserve explicitly configured write
modes and the existing openapi-gen and feature-gate markers, and add coverage in
loader_test.go for the registered-field case.

In `@hack/api-codegen/pkg/registry/field_metadata.go`:
- Around line 363-366: Mark the six affected spec.hostedCluster fields—autoNode,
channel, configuration, fips, operatorConfiguration, and pausedUntil—with
Hidden: true in their registry entries. Keep their existing FieldPath and
WriteMode values unchanged so generateRESTType excludes them from visible REST
fields.

In `@hack/api-codegen/pkg/validation/validator.go`:
- Around line 162-177: Update the registry.Immutable branch in Validate so an
OperationUpdate request with nil ExistingFields returns a validation error
instead of allowing the immutable field through. Preserve the existing
immutable-field error when ExistingFields is populated and the field already
exists, while retaining the allowed behavior for genuinely new fields.

In `@hack/tools/go.mod`:
- Line 3: Update the Go version directive in hack/tools/go.mod from 1.26.3 to
1.26.5, preserving the rest of the module configuration.

In `@hyperfleet-operator/api/v1alpha1/cluster_types.go`:
- Around line 75-81: Update the kubebuilder validation pattern on CreatorARN to
anchor both the beginning and end of the value, requiring the entire string—not
just its prefix—to match the intended AWS ARN pattern. Preserve the existing ARN
prefix validation while rejecting trailing or otherwise unexpected content.

In `@Makefile`:
- Around line 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.

In `@platform-api/internal/codegen/registry/field_metadata.json`:
- Line 1: Synchronize the passthrough-gen field metadata registry with the
platform-api registry by marking HostedClusterSpec fields autoNode, channel,
configuration, fips, operatorConfiguration, and pausedUntil as hidden. Update
the passthrough-gen copy, or regenerate both registries from a shared canonical
source, while preserving the existing metadata for all other fields.
- Around line 317-321: Synchronize the six affected hidden field metadata
entries between platform-api/internal/codegen/registry/field_metadata.json and
hack/api-codegen/cmd/passthrough-gen/field_metadata.json, including the
spec.hostedCluster.autoNode entry and the entries at the referenced ranges.
Ensure each matching fieldPath has identical hidden configuration in both files.

---

Minor comments:
In `@hack/api-codegen/cmd/conversion-gen/main.go`:
- Around line 41-48: Update the inputDirs parsing loop to trim whitespace from
each entry before calling filepath.Abs, and fail fast when a trimmed entry is
empty so trailing or doubled commas are rejected instead of resolving to the
current directory. Preserve absolute-path conversion for valid entries and keep
the existing error handling through log.Fatalf.

In `@hack/api-codegen/cmd/verify-configuration/main.go`:
- Around line 68-85: Update the marker handling in the field and inline-comment
scans to parse the value following +hyperfleet:write-mode= instead of treating
marker presence as valid. Set hasWriteMode only for mutable, immutable, or
service-set; reject unknown or misspelled values while preserving the existing
visibility-marker handling.

In `@hack/api-codegen/pkg/featuregate/crd_variant.go`:
- Around line 26-70: Update the error paths in GenerateVariant so every
f.Close() call checks and handles its returned error instead of discarding it,
including failures from encoder.Encode and encoder.Close. Preserve the original
operation error while incorporating any close failure into the returned error,
and keep the existing successful close flow unchanged.

In `@hack/api-codegen/pkg/featuregate/registry.go`:
- Around line 50-62: Sort the gates collected by GatesForFeatureSet before
returning them, ensuring deterministic ordering for SummarizeFeatureSet output
while preserving the existing stage filtering behavior.

In `@hack/api-codegen/pkg/openapi/generator_test.go`:
- Around line 12-13: Update the test setup around tmpFile to create the file
with filepath.Join(t.TempDir(), "openapi.json") instead of a shared /tmp path.
Remove the manual defer cleanup, since t.TempDir() owns lifecycle cleanup and
avoids concurrent test collisions.

In `@hack/api-codegen/pkg/openapi/generator.go`:
- Around line 190-231: Update the field-name handling in the schema-generation
loop around extractJSONTag and generateFieldSchema so an untagged field falls
back to its Go identifier name, matching encoding/json behavior. Continue
excluding fields only when the explicit JSON tag is "-", and preserve existing
handling for tagged names and required fields.

In `@hack/api-codegen/pkg/passthrough/loader.go`:
- Around line 250-260: Guard deriveFieldPrefix after trimming the “Spec” suffix
so an empty base returns an empty prefix before indexing runes. Preserve the
existing behavior for non-Spec type names and valid suffixed types.

In `@hack/api-codegen/pkg/validation/validator.go`:
- Around line 92-124: Make Validator.Validate produce deterministic
ValidationErrors by sorting the field paths from req.Fields before iterating, or
sorting the collected errors before returning. Preserve the existing
feature-gate and validateWriteMode checks while ensuring identical invalid
requests always return errors in a stable field order.

---

Nitpick comments:
In `@hack/api-codegen/cmd/featuregate-info/main.go`:
- Around line 12-47: Route every fmt.Print*/fmt.Fprint* call in
hack/api-codegen/cmd/featuregate-info/main.go (lines 12-47) through a shared
checked-output helper that exits non-zero on write failure; apply the same
pattern to all diagnostic and success output in
hack/api-codegen/cmd/verify-configuration/main.go (lines 15-115), ensuring no
output error returns are ignored.

In `@hack/api-codegen/pkg/conversion/generator.go`:
- Around line 200-221: Remove the duplicated type/prefix dispatch in
Generator.buildFieldPath by deriving passthrough registry prefixes from the
existing struct or registry convention instead of hardcoded HostedCluster and
NodePool name checks. Hoist the v1alpha1Types list used by qualifyType and
generateRESTType into one package-level slice, then reuse it from both call
sites so additions remain synchronized.

In `@hack/api-codegen/pkg/featuregate/crd_filter.go`:
- Around line 11-58: Extract the shared field-inclusion predicate into an
isFieldIncluded(meta registry.FieldMeta, fs FeatureSet) bool helper, covering
hidden fields, ungated fields, and IsGateEnabled checks. Update FilterCRDFields,
FieldsForFeatureSet, and crd_variant.go’s shouldIncludeField to call this
helper, removing their duplicated logic while preserving current behavior.

In `@hack/api-codegen/pkg/featuregate/crd_variant_test.go`:
- Around line 105-207: Extend TestCRDVariantGenerator_GenerateVariant with a
nested gated schema field whose real property name collides with an OpenAPI
keyword such as “type”, then assert the generated default and TechPreview
variants preserve or remove that field correctly. Ensure the fixture exercises
filterCRDNode at the same nesting depth for both the schema keyword and the
field-name path.

In `@hack/api-codegen/pkg/featuregate/crd_variant.go`:
- Around line 26-47: Extract the duplicated read, YAML unmarshal, filterContext
creation, and filterCRDNode invocation from GenerateVariant and
WriteVariantToWriter into a shared loadAndFilterCRD helper returning the
filtered yaml.Node and errors. Update both callers to use this helper while
preserving GenerateVariant’s file output and WriteVariantToWriter’s writer-based
encoding behavior.
- Around line 161-180: The field inclusion predicate is duplicated between
CRDVariantGenerator.shouldIncludeField and crd_filter.go. Consolidate the Hidden
and FeatureGate checks into a shared helper, then update shouldIncludeField and
the existing FilterCRDFields/FieldsForFeatureSet path to reuse it while
preserving structural-field handling and current inclusion behavior.

In `@hack/api-codegen/pkg/markers/generator.go`:
- Around line 129-154: Add default handling to both WriteMode switches in the
generator logic around meta.WriteMode and gated.WriteMode, and fail generation
immediately when an unrecognized value is encountered. Ensure the failure
identifies the invalid WriteMode and relevant field or feature-gate context
instead of silently producing an empty WriteMode.

In `@hack/api-codegen/pkg/markers/json.go`:
- Around line 20-26: Deduplicate the identical jsonField definition by declaring
it once and reusing it in both GenerateJSON and LoadRegistryFromJSONBytes. In
LoadRegistryFromJSONBytes, validate each non-empty field.WriteMode against the
supported WriteMode values Mutable, Immutable, and ServiceSet before converting
it; return an error for any unknown value instead of loading it silently.

In `@hack/api-codegen/pkg/passthrough/generator_test.go`:
- Around line 1-67: Mark TestGenerate with the repository’s established build
tag for network/module-resolution-dependent tests, matching the convention used
in loader_test.go. Ensure the tag is placed so default test runs exclude this
test while explicitly tagged runs still execute it.

In `@hack/api-codegen/pkg/passthrough/generator.go`:
- Around line 86-90: Update Generate so the raw output artifact is only written
when formatting fails, rather than unconditionally before formatting. Move or
defer the os.WriteFile call for outputFile+".raw" into the formatting-error path
while preserving the existing error context and normal formatted output
behavior.

In `@hack/api-codegen/pkg/passthrough/gomod.go`:
- Around line 19-32: Update ResolvePackageDir to accept or obtain a
context.Context and invoke go list with exec.CommandContext, applying the
established timeout policy so stalled module resolution can be cancelled.
Preserve the existing output trimming and error messages, including command
output when execution fails.
- Around line 44-63: Update NewGeneratorFromImportPath so SourcePackageAlias is
derived generically from the import path’s final segment without prepending the
hardcoded “hypershift” prefix. Preserve the existing import-path resolution and
generator initialization behavior.

In `@hack/api-codegen/pkg/passthrough/integration_test.go`:
- Around line 94-105: Replace the custom contains/findSubstring helpers with the
standard library’s strings.Contains, adding the strings import and updating all
callers of contains accordingly; remove both redundant helper functions.

In `@hack/api-codegen/pkg/passthrough/loader_test.go`:
- Line 1: Separate the live module-resolution tests from default unit-test runs
by adding the existing integration build constraint to TestLoadHyperShiftTypes,
TestGenerateTypeDef, and TestGenerate. Apply the constraint consistently to the
files containing these tests, preserving their current test logic and allowing
them to run only when integration tests are explicitly selected.
- Around line 10-30: Add the same network/module-resolution build tag used by
generator_test.go to the tests in loader_test.go, including
TestLoadHyperShiftTypes and the additional tests around lines 168–203, so they
are excluded unless explicitly enabled.

In `@hack/api-codegen/pkg/passthrough/loader.go`:
- Line 1: Create a shared internal helper package for JSON struct-tag extraction
and generated/test-file filtering, then update passthrough’s parseStructTag and
openapi’s extractJSONTag/isRequired to use reflect.StructTag-based parsing while
preserving required-field behavior. Replace both parser.ParseDir skip closures
with the shared predicate, including _test.go and zz_generated* exclusions, and
remove the duplicated local helpers.

In `@hack/api-codegen/pkg/validation/example_test.go`:
- Around line 54-60: The ExampleValidator_Validate_immutable example must
exercise Validator behavior instead of printing a static message. Mirror the
custom-registry setup from gated_writemode_test.go, defining a fabricated
immutable field with the appropriate registry and write-mode markers, then
validate create and update requests to demonstrate that setting is allowed on
create but rejected on update.

In `@hack/api-codegen/pkg/validation/gated_writemode_test.go`:
- Around line 162-172: Update the error assertion in the gated writemode test’s
expectError branch to verify that err.Error() contains tt.errorReason using
strings.Contains. Preserve the existing nil and empty-error checks, and add the
required strings import if it is not already present.

In `@hyperfleet-operator/api/v1alpha1/configuration.go`:
- Around line 186-224: The nine configuration
types—APIServerNetworkConfiguration, ClusterAuthentication,
FeatureGateConfiguration, ImageConfiguration, IngressConfiguration,
NetworkConfiguration, OAuthConfiguration, SchedulerConfiguration, and
ProxyConfiguration—remain empty stubs. Define their configuration fields with
granular markers consistent with KubeletConfig and MachineConfigSpec, preserving
their existing references from ClusterConfiguration.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: openshift-online/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: de1710c6-e62b-423b-8a55-cc8d66c64571

📥 Commits

Reviewing files that changed from the base of the PR and between d6bd56b and a74e6f2.

⛔ Files ignored due to path filters (2)
  • hack/api-codegen/go.sum is excluded by !**/*.sum
  • hyperfleet-operator/api/v1alpha1/zz_generated.passthrough.go.raw is excluded by !**/zz_generated*
📒 Files selected for processing (55)
  • Makefile
  • hack/api-codegen/README.md
  • hack/api-codegen/cmd/conversion-gen/main.go
  • hack/api-codegen/cmd/crd-variants/main.go
  • hack/api-codegen/cmd/featuregate-info/main.go
  • hack/api-codegen/cmd/marker-scanner/main.go
  • hack/api-codegen/cmd/openapi-gen/main.go
  • hack/api-codegen/cmd/passthrough-gen/field_metadata.json
  • hack/api-codegen/cmd/passthrough-gen/main.go
  • hack/api-codegen/cmd/verify-configuration/main.go
  • hack/api-codegen/go.mod
  • hack/api-codegen/pkg/conversion/generator.go
  • hack/api-codegen/pkg/conversion/generator_test.go
  • hack/api-codegen/pkg/conversion/mirror_types.go
  • hack/api-codegen/pkg/conversion/mirror_types_test.go
  • hack/api-codegen/pkg/featuregate/crd_filter.go
  • hack/api-codegen/pkg/featuregate/crd_variant.go
  • hack/api-codegen/pkg/featuregate/crd_variant_test.go
  • hack/api-codegen/pkg/featuregate/featuregate_test.go
  • hack/api-codegen/pkg/featuregate/registry.go
  • hack/api-codegen/pkg/featuregate/types.go
  • hack/api-codegen/pkg/markers/gated_writemode_test.go
  • hack/api-codegen/pkg/markers/generator.go
  • hack/api-codegen/pkg/markers/json.go
  • hack/api-codegen/pkg/markers/json_test.go
  • hack/api-codegen/pkg/markers/scanner.go
  • hack/api-codegen/pkg/markers/scanner_test.go
  • hack/api-codegen/pkg/markers/types.go
  • hack/api-codegen/pkg/openapi/generator.go
  • hack/api-codegen/pkg/openapi/generator_test.go
  • hack/api-codegen/pkg/openapi/types.go
  • hack/api-codegen/pkg/passthrough/generator.go
  • hack/api-codegen/pkg/passthrough/generator_test.go
  • hack/api-codegen/pkg/passthrough/gomod.go
  • hack/api-codegen/pkg/passthrough/integration_test.go
  • hack/api-codegen/pkg/passthrough/loader.go
  • hack/api-codegen/pkg/passthrough/loader_test.go
  • hack/api-codegen/pkg/passthrough/types.go
  • hack/api-codegen/pkg/registry/field_metadata.go
  • hack/api-codegen/pkg/registry/field_metadata.json
  • hack/api-codegen/pkg/validation/example_test.go
  • hack/api-codegen/pkg/validation/gated_writemode_test.go
  • hack/api-codegen/pkg/validation/validator.go
  • hack/api-codegen/pkg/validation/validator_test.go
  • hack/tools/go.mod
  • hyperfleet-operator/api/go.mod
  • hyperfleet-operator/api/v1alpha1/cluster_types.go
  • hyperfleet-operator/api/v1alpha1/configuration.go
  • hyperfleet-operator/api/v1alpha1/hostedclusterspec.passthrough.go
  • hyperfleet-operator/api/v1alpha1/nodepool_types.go
  • platform-api/internal/codegen/conversion/cluster.go
  • platform-api/internal/codegen/featuregate/registry.go
  • platform-api/internal/codegen/featuregate/types.go
  • platform-api/internal/codegen/registry/field_metadata.go
  • platform-api/internal/codegen/registry/field_metadata.json

Comment on lines +317 to +320
{
"fieldPath": "spec.hostedCluster.autoNode",
"writeMode": "service-set"
},

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Registry drift vs. platform-api/internal/codegen/registry/field_metadata.json on 6 hidden fields.

See consolidated comment below for full detail and both affected files.

Also applies to: 331-334, 340-343, 364-367, 413-416, 417-420

🤖 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 `@hack/api-codegen/cmd/passthrough-gen/field_metadata.json` around lines 317 -
320, Synchronize the six affected field metadata entries, including
spec.hostedCluster.autoNode and the entries at the other referenced locations,
with the canonical registry so each preserves its hidden-field designation.
Update the metadata definitions to include the matching hidden setting while
retaining their existing fieldPath and writeMode values.

Comment thread hack/api-codegen/go.mod
Comment on lines +504 to +547
fieldsMap := make(map[string]serviceSetField) // Use map to deduplicate

for path, meta := range registry.FieldRegistry {
if meta.WriteMode == registry.ServiceSet {
// Infer Go field name from path
goName := g.pathToGoName(path)

// Infer type from parsed types
goType := g.inferTypeFromPath(path)

jsonTag := g.pathToJSONTag(path)

// Use JSONTag as key to deduplicate (same field name from different paths)
// Prefer longer GoType (more specific)
if existing, exists := fieldsMap[jsonTag]; exists {
if len(goType) > len(existing.GoType) {
fieldsMap[jsonTag] = serviceSetField{
GoName: goName,
GoType: goType,
JSONTag: jsonTag,
FieldPath: path,
}
}
} else {
fieldsMap[jsonTag] = serviceSetField{
GoName: goName,
GoType: goType,
JSONTag: jsonTag,
FieldPath: path,
}
}
}
}

// Convert map to slice for sorting
var fields []serviceSetField
for _, f := range fieldsMap {
fields = append(fields, f)
}

// Sort for consistent output
sort.Slice(fields, func(i, j int) bool {
return fields[i].GoName < fields[j].GoName
})

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.

🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win

ServiceSetFields dedup collides across resource namespaces (hostedCluster vs nodePool).

fieldsMap is keyed by jsonTag (line 504), and both pathToJSONTag/pathToGoName derive names from only the last path segment. Registry paths spec.hostedCluster.release/spec.nodePool.release, spec.hostedCluster.platform/spec.nodePool.platform, and spec.hostedCluster.pausedUntil/spec.nodePool.pausedUntil all collide on the same leaf name. The tie-break at Lines 518-526 (prefer the textually-longer GoType) silently drops one resource's mapping, so ServiceSetFields ends up with a single Platform/Release/PausedUntil member typed for only one of HostedCluster/NodePool. generateUnprojectFunction (Lines 926-931) then emits crdSpec.<Field> = enrichment.<Field> for both resources against that single member — producing either a compile error (type mismatch) or, if types happen to coincide, silently wrong enrichment data for the other resource.

Key fieldsMap (and derive the Go field name) from the full field path rather than the bare leaf segment, e.g. qualify by the spec.hostedCluster/spec.nodePool segment so each resource gets a distinct ServiceSetFields member.

🐛 Proposed direction
-	fieldsMap := make(map[string]serviceSetField) // Use map to deduplicate
+	fieldsMap := make(map[string]serviceSetField) // keyed by full path, not leaf jsonTag
 
 	for path, meta := range registry.FieldRegistry {
 		if meta.WriteMode == registry.ServiceSet {
-			// Infer Go field name from path
-			goName := g.pathToGoName(path)
+			// Infer Go field name from full path to avoid collisions between
+			// e.g. spec.hostedCluster.platform and spec.nodePool.platform
+			goName := g.pathToQualifiedGoName(path)
 			...
-			// Use JSONTag as key to deduplicate (same field name from different paths)
-			if existing, exists := fieldsMap[jsonTag]; exists {
+			if existing, exists := fieldsMap[path]; exists {

Also applies to: 926-931

🤖 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 `@hack/api-codegen/pkg/conversion/generator.go` around lines 504 - 547, Update
the ServiceSetFields collection around fieldsMap to deduplicate using a
resource-qualified key derived from the full field path, including the
spec.hostedCluster or spec.nodePool segment, rather than only the leaf JSON tag.
Derive distinct GoName and JSONTag values from that qualified path so each
resource’s release, platform, and pausedUntil fields remain separate, and ensure
generateUnprojectFunction uses the corresponding qualified members for each
resource.

Comment on lines +11 to +33
func FilterCRDFields(featureSet FeatureSet) []string {
var includedFields []string

for fieldPath, meta := range registry.FieldRegistry {
// Skip hidden fields - they never appear in CRDs
if meta.Hidden {
continue
}

// If field has no gate, it's always included (GA)
if meta.FeatureGate == "" {
includedFields = append(includedFields, fieldPath)
continue
}

// Check if this feature gate is enabled for the feature set
if IsGateEnabled(meta.FeatureGate, featureSet) {
includedFields = append(includedFields, fieldPath)
}
}

return includedFields
}

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

Non-deterministic field order from map iteration.

FilterCRDFields builds includedFields by iterating registry.FieldRegistry, a Go map. Map iteration order is randomized per run, so the returned slice order (and therefore any generated/serialized output derived from it) will vary between runs even when the underlying data is unchanged. For a codegen tool whose output is expected to be reproducible (e.g. for verify-* targets diffing regenerated files), this can produce spurious diffs.

🐛 Proposed fix
+import "sort"
+
 func FilterCRDFields(featureSet FeatureSet) []string {
 	var includedFields []string
 	for fieldPath, meta := range registry.FieldRegistry {
 		...
 	}
+	sort.Strings(includedFields)
 	return includedFields
 }
📝 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
func FilterCRDFields(featureSet FeatureSet) []string {
var includedFields []string
for fieldPath, meta := range registry.FieldRegistry {
// Skip hidden fields - they never appear in CRDs
if meta.Hidden {
continue
}
// If field has no gate, it's always included (GA)
if meta.FeatureGate == "" {
includedFields = append(includedFields, fieldPath)
continue
}
// Check if this feature gate is enabled for the feature set
if IsGateEnabled(meta.FeatureGate, featureSet) {
includedFields = append(includedFields, fieldPath)
}
}
return includedFields
}
import "sort"
func FilterCRDFields(featureSet FeatureSet) []string {
var includedFields []string
for fieldPath, meta := range registry.FieldRegistry {
// Skip hidden fields - they never appear in CRDs
if meta.Hidden {
continue
}
// If field has no gate, it's always included (GA)
if meta.FeatureGate == "" {
includedFields = append(includedFields, fieldPath)
continue
}
// Check if this feature gate is enabled for the feature set
if IsGateEnabled(meta.FeatureGate, featureSet) {
includedFields = append(includedFields, fieldPath)
}
}
sort.Strings(includedFields)
return includedFields
}
🤖 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 `@hack/api-codegen/pkg/featuregate/crd_filter.go` around lines 11 - 33, Make
FilterCRDFields return fields in a deterministic order by sorting the collected
includedFields before returning it. Preserve the existing hidden-field and
feature-gate filtering behavior, and add the required standard-library import
for sorting.

Comment on lines +106 to +156
// collectImports extracts unique imports needed for the generated types
func (g *Generator) collectImports(typeDefs []*TypeDef) []string {
importSet := make(map[string]bool)

// Add source package import if we have a source package
if g.SourcePackage != "" && g.SourcePackageAlias != "" {
importSet[fmt.Sprintf(`%s "%s"`, g.SourcePackageAlias, g.SourcePackage)] = true
}

for _, typeDef := range typeDefs {
for _, field := range typeDef.Fields {
// Strip pointer/slice prefixes repeatedly to handle combined
// forms like []*configv1.Something, then extract map values.
typeStr := field.Type
for strings.HasPrefix(typeStr, "*") || strings.HasPrefix(typeStr, "[]") {
typeStr = strings.TrimPrefix(typeStr, "*")
typeStr = strings.TrimPrefix(typeStr, "[]")
}
if strings.HasPrefix(typeStr, "map[") {
// Extract value type from map[K]V
if idx := strings.LastIndex(typeStr, "]"); idx != -1 && idx+1 < len(typeStr) {
typeStr = typeStr[idx+1:]
typeStr = strings.TrimPrefix(typeStr, "*")
}
}

// Extract package from type names like "configv1.URL"
if strings.Contains(typeStr, ".") {
// For now, we'll need to manually map these
// In a real implementation, we'd track imports from the AST
if strings.HasPrefix(typeStr, "configv1.") {
importSet[`configv1 "github.com/openshift/api/config/v1"`] = true
}
if strings.HasPrefix(typeStr, "corev1.") {
importSet[`corev1 "k8s.io/api/core/v1"`] = true
}
if strings.HasPrefix(typeStr, "metav1.") {
importSet[`metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"`] = true
}
}
}
}

// Convert set to sorted slice
var imports []string
for imp := range importSet {
imports = append(imports, imp)
}

return imports
}

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 | 🏗️ Heavy lift

Hardcoded import mapping is fragile and can silently produce non-compiling generated code.

collectImports only recognizes configv1., corev1., and metav1. prefixes via string matching; any other qualified type referenced by a source struct (quite plausible for a large API surface like HostedClusterSpec) will be silently omitted from the generated import (...) block, producing generated Go source that fails to compile. This is acknowledged directly in the code's own comment. Neither generator_test.go nor integration_test.go actually go build/go vet the generated output, so this gap would go undetected until someone consumes the generated file.

Since the AST is already available on Generator (via g.parsedFiles), imports could be derived from the actual *ast.File.Imports declarations of the source files (matching selector qualifiers back to their real import paths) instead of a hardcoded prefix table.

Want me to implement AST-based import resolution (mapping SelectorExpr qualifiers to the source file's actual import declarations) instead of the hardcoded prefix table?

🤖 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 `@hack/api-codegen/pkg/passthrough/generator.go` around lines 106 - 156, Update
Generator.collectImports to derive qualified-type imports from g.parsedFiles and
each source *ast.File.Imports declaration, resolving selector qualifiers to
their actual import paths instead of matching only configv1, corev1, and metav1
prefixes. Preserve the existing source-package import handling and
deduplication, and ensure every referenced external qualifier is included in the
generated imports.

Comment thread hack/tools/go.mod
Comment on lines 75 to 81
// CreatorARN is the IAM ARN of the user who created this cluster.
// Used to bootstrap the initial cluster-admin RBAC mapping.
// +k8s:openapi-gen=false
// +hyperfleet:write-mode=service-set
// +optional
// +kubebuilder:validation:Pattern=`^arn:aws:`
CreatorARN string `json:"creatorARN,omitempty"`

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Anchor the CreatorARN regex pattern.

^arn:aws: only validates the prefix; anything can follow, so malformed ARNs (or unexpected trailing content) pass validation. As per path instructions, regexes should be anchored (^$).

🛡️ Proposed fix
-	// +kubebuilder:validation:Pattern=`^arn:aws:`
+	// +kubebuilder:validation:Pattern=`^arn:aws:iam::\d{12}:(user|role)/[\w+=,.@-]+$`

As per path instructions, **/*.{py,js,ts,go,rs,java,rb,php,kt,swift,cs}: "Normalize Unicode and anchor regexes (^$); watch for ReDoS."

📝 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
// CreatorARN is the IAM ARN of the user who created this cluster.
// Used to bootstrap the initial cluster-admin RBAC mapping.
// +k8s:openapi-gen=false
// +hyperfleet:write-mode=service-set
// +optional
// +kubebuilder:validation:Pattern=`^arn:aws:`
CreatorARN string `json:"creatorARN,omitempty"`
// CreatorARN is the IAM ARN of the user who created this cluster.
// Used to bootstrap the initial cluster-admin RBAC mapping.
// +k8s:openapi-gen=false
// +hyperfleet:write-mode=service-set
// +optional
// +kubebuilder:validation:Pattern=`^arn:aws:iam::\d{12}:(user|role)/[\w+=,.@-]+$`
CreatorARN string `json:"creatorARN,omitempty"`
🤖 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 `@hyperfleet-operator/api/v1alpha1/cluster_types.go` around lines 75 - 81,
Update the kubebuilder validation pattern on CreatorARN to anchor both the
beginning and end of the value, requiring the entire string—not just its
prefix—to match the intended AWS ARN pattern. Preserve the existing ARN prefix
validation while rejecting trailing or otherwise unexpected content.

Source: Path instructions

Comment thread Makefile
Comment on lines +272 to +275
codegen-verify: build-api-codegen
cd hyperfleet-operator/api && go build ./...
cd platform-api && go build ./internal/codegen/...

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.

@@ -0,0 +1,583 @@
[

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Two field_metadata.json registries have drifted on 6 hidden fields. spec.hostedCluster.autoNode, .channel, .configuration, .fips, .operatorConfiguration, and .pausedUntil are marked "hidden": true in the platform-api registry but not in the passthrough-gen embedded registry, even though both are meant to reflect the same source markers on HostedClusterSpec. Since passthrough-gen's copy drives +k8s:openapi-gen=false suppression for generated passthrough types, these 6 platform-managed fields will be visible in the customer-facing OpenAPI schema while the validator still treats them as hidden/service-set-only — an inconsistent contract between the two pipelines.

  • platform-api/internal/codegen/registry/field_metadata.json#L317-426: source of truth for the 6 fields' hidden: true state; regenerate/sync the passthrough-gen copy from this (or a shared source) so both stay in lockstep.
  • hack/api-codegen/cmd/passthrough-gen/field_metadata.json#L317-420: add "hidden": true to autoNode, channel, configuration, fips, operatorConfiguration, and pausedUntil to match the platform-api registry, or better, generate both files from a single canonical registry to prevent future drift.
🤖 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 `@platform-api/internal/codegen/registry/field_metadata.json` at line 1,
Synchronize the passthrough-gen field metadata registry with the platform-api
registry by marking HostedClusterSpec fields autoNode, channel, configuration,
fips, operatorConfiguration, and pausedUntil as hidden. Update the
passthrough-gen copy, or regenerate both registries from a shared canonical
source, while preserving the existing metadata for all other fields.

Comment on lines +317 to +321
{
"fieldPath": "spec.hostedCluster.autoNode",
"writeMode": "service-set",
"hidden": true
},

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Registry drift vs. hack/api-codegen/cmd/passthrough-gen/field_metadata.json on 6 hidden fields.

See consolidated comment below for full detail and both affected files.

Also applies to: 332-336, 342-346, 367-371, 417-421, 422-426

🤖 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 `@platform-api/internal/codegen/registry/field_metadata.json` around lines 317
- 321, Synchronize the six affected hidden field metadata entries between
platform-api/internal/codegen/registry/field_metadata.json and
hack/api-codegen/cmd/passthrough-gen/field_metadata.json, including the
spec.hostedCluster.autoNode entry and the entries at the referenced ranges.
Ensure each matching fieldPath has identical hidden configuration in both files.

@openshift-ci openshift-ci Bot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jul 28, 2026
cdoan1 and others added 11 commits July 28, 2026 12:11
Port the build-time code generators from cdoan1/hyperfleet-api-codegen
into the monorepo under hack/api-codegen/. Includes 7 generator commands,
7 library packages, Makefile integration, and codegen integration plan.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Bump go directive to 1.26.5 in hack/api-codegen and hack/tools
- Fix conversion generator: conditional imports, OutputDir-derived
  package names, duplicate helper prevention, go/printer fallback
  for unsupported AST expressions
- Fix CRD variant generator: encoder.Close(), file close error
  propagation, hidden field exclusion, transparent items/additionalProperties
- Fix marker scanner: per-directory type cache, cycle guard for
  recursive structs
- Fix OpenAPI generator: shared resolveTypeSchema helper, pointer/slice
  stripping for array elements and map values
- Fix passthrough generator: repeated pointer/slice prefix stripping
- Fix validator: explicit matched flag for feature-gate overrides
- Move codegen binaries to repo-root bin/ for consistency

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Avoid mutating g.FieldPrefix so each type derives its own prefix
  independently when processing multiple types
- Render interface, func, ellipsis, and generic AST expressions via
  go/printer instead of silently dropping to interface{}

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add generated passthrough structs (HostedClusterSpecPassthrough,
NodePoolSpecPassthrough) with per-field write-mode, visibility, and
feature-gate markers to v1alpha1 CRD types. Add envelope fields
(DisplayName, DeleteProtection, Tags, etc.) to ClusterSpec and
NodePoolSpec. Add runtime libraries for field registry, feature gates,
and service-set conversion. Add Makefile codegen pipeline targets.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use --source-dir with go list from hyperfleet-operator/api/ (which has
the HyperShift dependency) instead of --import-path from repo root.
Remove the conflicting zz_generated.passthrough.go after generation
since the curated hostedclusterspec.passthrough.go is the authoritative
file. Regenerate registry from v1alpha1 markers (75 fields).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The collectImports function iterated a map without sorting, causing
non-deterministic import ordering in the generated .raw file across
runs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ypes

The stale deepcopy caused integration test failures — controller-runtime
silently dropped new fields during reconciliation, preventing status
propagation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Align openshift/hypershift/api and transitive deps with the rest of
the monorepo.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove the embedded registry from passthrough-gen and make --registry
a required flag. The Makefile now passes the runtime registry
(platform-api/internal/codegen/registry/field_metadata.json) directly,
eliminating the stale-copy problem.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@cdoan1
cdoan1 force-pushed the ROSAENG-61801-passthrough-types branch from 342d72a to f6cf272 Compare July 28, 2026 17:18
@openshift-ci openshift-ci Bot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jul 28, 2026
cdoan1 and others added 2 commits July 28, 2026 12:40
isRootType excluded *Passthrough types, so the scanner never walked
their fields into the registry. This meant curating markers on
passthrough fields (e.g. flipping openapi-gen to true) had no effect
on the generated registry. Treat Passthrough types as scan roots,
bringing the registry from 75 to 119 fields.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
UBI go-toolset defaults GOCACHE to /opt/app-root/src/.cache/go-build
which is not writable as user 1001. Create /tmp/gocache and set
GOCACHE to point there.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@cdoan1

cdoan1 commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator Author

/test on-demand-e2e

@cdoan1 cdoan1 added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Jul 31, 2026
@cdoan1 cdoan1 closed this Jul 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. jira/valid-reference Indicates that this PR references a valid Jira ticket of any type.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants