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

# Codegen intermediate files
*.passthrough.go.raw
platform-api/openapi/generated-schemas.json
76 changes: 72 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
test-e2e test-e2e-api test-e2e-cli test-e2e-platform-monitoring test-e2e-zoa test-e2e-authz test-e2e-sdk \
e2e-authz-infra-up e2e-authz-infra-down e2e-init-db \
fmt vet verify deps \
manifests generate generate-clientset verify-clientset setup-envtest \
manifests generate generate-clientset verify-clientset \
generate-public-deepcopy setup-envtest \
codegen-passthrough codegen-registry codegen-verify codegen verify-codegen \
generate-openapi verify-openapi swagger-ui \
image-api image-operator image-push-api image-push-operator

# ── Configuration ────────────────────────────────────────────────────────
Expand Down Expand Up @@ -106,9 +109,18 @@ help:
@echo ""
@echo "Code Generation:"
@echo " manifests Generate CRD manifests"
@echo " generate Generate deepcopy methods"
@echo " generate-clientset Generate typed client SDK from CRD types"
@echo " verify-clientset Fail if generated clientset is out of date"
@echo " generate Generate deepcopy methods (v1alpha1)"
@echo " generate-clientset Generate typed client SDK from CRD types"
@echo " verify-clientset Fail if generated clientset is out of date"
@echo " generate-public-deepcopy Generate deepcopy methods (v2alpha1 public API)"
@echo " codegen Full codegen pipeline (passthrough → deepcopy → registry)"
@echo " codegen-passthrough Generate passthrough types from HyperShift"
@echo " codegen-registry Generate field metadata registry from markers"
@echo " codegen-verify Verify codegen outputs compile"
@echo " verify-codegen Fail if codegen outputs are out of date"
@echo " generate-openapi Generate and merge typed schemas into OpenAPI spec"
@echo " verify-openapi Fail if OpenAPI spec is out of date with codegen"
@echo " swagger-ui Run Swagger UI locally (default port 8282)"
@echo " setup-envtest Install envtest binaries (etcd, kube-apiserver)"
@echo " deps Download and tidy all modules"
@echo ""
Expand All @@ -134,6 +146,7 @@ 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/openapi-merge ./cmd/openapi-merge
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
Expand Down Expand Up @@ -260,6 +273,7 @@ verify:
cd hyperfleet-db && go mod tidy
cd hyperfleet-operator/api && go mod tidy
cd hyperfleet-operator && go mod tidy
cd api/public/v2alpha1 && go mod tidy
cd platform-api && go mod tidy
cd test && go mod tidy
cd hack/tools && go mod tidy
Expand All @@ -268,6 +282,7 @@ verify:
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 \
api/public/v2alpha1/go.mod api/public/v2alpha1/go.sum \
platform-api/go.mod platform-api/go.sum \
test/go.mod test/go.sum \
hack/tools/go.mod hack/tools/go.sum \
Expand All @@ -277,6 +292,7 @@ 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 api/public/v2alpha1 && 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
Expand Down Expand Up @@ -315,6 +331,58 @@ generate-clientset: $(CLIENT_GEN) $(WIRE_GEN)
verify-clientset: generate-clientset
git diff --exit-code clientset/

codegen-passthrough: build-api-codegen
cd api/public/v2alpha1 && ../../../bin/passthrough-gen \
-import-path github.com/openshift/hypershift/api/hypershift/v1beta1 \
-types HostedClusterSpec,NodePoolSpec \
-output-dir . \
-package v2alpha1
mv api/public/v2alpha1/zz_generated.passthrough.go api/public/v2alpha1/zz_generated.passthrough.go.raw

generate-public-deepcopy: codegen-passthrough $(CONTROLLER_GEN)
$(CONTROLLER_GEN) object paths="./api/public/v2alpha1/..."

codegen-registry: generate-public-deepcopy build-api-codegen
./bin/marker-scanner \
-input-dirs api/public/v2alpha1 \
-output-file hack/api-codegen/pkg/registry/field_metadata.go \
$(if $(VERBOSE),-verbose)

codegen-verify: codegen-registry
cd api/public/v2alpha1 && go build ./...
cd platform-api && go build ./...

codegen: codegen-verify

verify-codegen: codegen
git diff --exit-code api/public/v2alpha1/zz_generated.deepcopy.go
git diff --exit-code hack/api-codegen/pkg/registry/

OPENAPI_GENERATED ?= platform-api/openapi/generated-schemas.json
OPENAPI_SPEC ?= platform-api/openapi/openapi.yaml

generate-openapi: codegen-registry
cd hack/api-codegen && go build -o ../../bin/openapi-gen ./cmd/openapi-gen
./bin/openapi-gen \
-input-dirs ./api/public/v2alpha1 \
-output-file $(OPENAPI_GENERATED)
./bin/openapi-merge \
-spec $(OPENAPI_SPEC) \
-generated $(OPENAPI_GENERATED) \
-schemas ClusterSpec,NodePoolSpec,HostedClusterSpecPassthrough,NodePoolSpecPassthrough,ClusterConfiguration,KubeletConfig,MachineConfigSpec

verify-openapi: generate-openapi
git diff --exit-code $(OPENAPI_SPEC)

SWAGGER_UI_PORT ?= 8282

swagger-ui:
@echo "Swagger UI available at http://localhost:$(SWAGGER_UI_PORT)"
$(CONTAINER_ENGINE) run --rm -p $(SWAGGER_UI_PORT):8080 \
-e SWAGGER_JSON=/spec/openapi.yaml \
-v $(CURDIR)/$(OPENAPI_SPEC):/spec/openapi.yaml:ro \

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 | 🟡 Minor | ⚡ Quick win

Mount the configured OpenAPI file path.

Line 383 prefixes OPENAPI_SPEC with $(CURDIR). If OPENAPI_SPEC is absolute, the target mounts a repository-relative path instead of the configured file. Use $(abspath $(OPENAPI_SPEC)).

Proposed fix
-		-v $(CURDIR)/$(OPENAPI_SPEC):/spec/openapi.yaml:ro \
+		-v $(abspath $(OPENAPI_SPEC)):/spec/openapi.yaml:ro \
📝 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
-v $(CURDIR)/$(OPENAPI_SPEC):/spec/openapi.yaml:ro \
-v $(abspath $(OPENAPI_SPEC)):/spec/openapi.yaml:ro \
🤖 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` at line 383, Update the volume mount in the OpenAPI-related
Makefile target to use $(abspath $(OPENAPI_SPEC)) instead of prefixing
OPENAPI_SPEC with $(CURDIR), so both absolute and relative configured paths
resolve to the intended file.

swaggerapi/swagger-ui

ENVTEST_BIN_DIR ?= $(shell pwd)/.envtest

setup-envtest: $(SETUP_ENVTEST)
Expand Down
156 changes: 156 additions & 0 deletions api/public/v2alpha1/cluster_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/*
Copyright 2026.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package v2alpha1

import (
hypershiftv1beta1 "github.com/openshift/hypershift/api/hypershift/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)

// ClusterPhase represents the lifecycle phase of a Cluster.
// +kubebuilder:validation:Enum=WaitingForPlacement;Provisioning;Ready;Deleting
type ClusterPhase string

const (
ClusterPhaseWaitingForPlacement ClusterPhase = "WaitingForPlacement"
ClusterPhaseProvisioning ClusterPhase = "Provisioning"
ClusterPhaseReady ClusterPhase = "Ready"
ClusterPhaseDeleting ClusterPhase = "Deleting"
)

// ClusterSpec defines the desired state of a ROSA HCP cluster.
type ClusterSpec struct {
// DisplayName is a human-readable name for the cluster.
// +hyperfleet:write-mode=mutable
// +kubebuilder:validation:MaxLength=256
// +optional
DisplayName string `json:"displayName,omitempty"`

// DeleteProtection prevents accidental deletion when enabled.
// +hyperfleet:write-mode=mutable
// +optional
DeleteProtection *bool `json:"deleteProtection,omitempty"`

// ExpirationTimestamp marks when this cluster should be automatically deleted.
// +k8s:openapi-gen=true
// +hyperfleet:write-mode=mutable
// +optional
ExpirationTimestamp *metav1.Time `json:"expirationTimestamp,omitempty"`

// Properties are arbitrary key-value pairs for customer metadata.
// +hyperfleet:write-mode=mutable
// +optional
Properties map[string]string `json:"properties,omitempty"`

// Tags are customer-defined labels for organizational purposes.
// +hyperfleet:write-mode=mutable
// +openshift:enable:FeatureGate=HyperFleetAutoScaling
// +optional
Tags map[string]string `json:"tags,omitempty"`

// AccountID identifies the customer account (platform-managed, hidden from API).
// +k8s:openapi-gen=false
// +hyperfleet:write-mode=service-set
// +optional
AccountID string `json:"accountId,omitempty"`

// CreatorARN is the IAM ARN of the user who created this cluster.
// +k8s:openapi-gen=false
// +hyperfleet:write-mode=service-set
// +optional
// +kubebuilder:validation:Pattern=`^arn:aws:`
CreatorARN string `json:"creatorARN,omitempty"`
Comment on lines +76 to +77

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 | 🟡 Minor | ⚡ Quick win

Restrict on ARN partition rejects GovCloud and China ARNs.

The pattern ^arn:aws: rejects arn:aws-us-gov: and arn:aws-cn: principal ARNs. If the service must run in those partitions, the create path silently fails validation for every caller there. Widen the pattern if non-commercial partitions are in scope.

🛠️ Proposed pattern widening
-	// +kubebuilder:validation:Pattern=`^arn:aws:`
+	// +kubebuilder:validation:Pattern=`^arn:(aws|aws-cn|aws-us-gov):`
 	CreatorARN string `json:"creatorARN,omitempty"`
📝 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
// +kubebuilder:validation:Pattern=`^arn:aws:`
CreatorARN string `json:"creatorARN,omitempty"`
// +kubebuilder:validation:Pattern=`^arn:(aws|aws-cn|aws-us-gov):`
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 `@api/public/v2alpha1/cluster_types.go` around lines 76 - 77, Update the
validation pattern on CreatorARN to accept AWS commercial, GovCloud, and China
ARN partitions instead of only the `aws` partition; preserve ARN-prefix
validation while allowing `arn:aws-us-gov:` and `arn:aws-cn:` callers.


// InternalID is an internal platform identifier (platform-managed, hidden).
// +k8s:openapi-gen=false
// +hyperfleet:write-mode=service-set
// +optional
InternalID string `json:"internalId,omitempty"`

// HostedCluster is the full HyperShift HostedClusterSpec.
// +kubebuilder:validation:Required
HostedCluster hypershiftv1beta1.HostedClusterSpec `json:"hostedCluster"`
}

// ClusterStatus defines the observed state of a Cluster.
type ClusterStatus struct {
// +listType=map
// +listMapKey=type
// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`

// +optional
Phase ClusterPhase `json:"phase,omitempty"`

// +optional
ControlPlaneEndpoint hypershiftv1beta1.APIEndpoint `json:"controlPlaneEndpoint,omitempty"`

// +optional
Version string `json:"version,omitempty"`

// +optional
PlacementRef *PlacementReference `json:"placementRef,omitempty"`

// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
}

// PlacementReference identifies the management cluster assignment.
type PlacementReference struct {
Name string `json:"name"`
ManagementCluster string `json:"managementCluster"`
}

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:resource:scope=Namespaced,shortName=hfc
// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=".status.phase"
// +kubebuilder:printcolumn:name="MC",type=string,JSONPath=".status.placementRef.managementCluster"
// +kubebuilder:printcolumn:name="Endpoint",type=string,JSONPath=".status.controlPlaneEndpoint.host",priority=1
// +kubebuilder:printcolumn:name="Expires",type=date,JSONPath=".spec.expirationTimestamp",priority=1
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=".metadata.creationTimestamp"

// Cluster is the Schema for the clusters API.
type Cluster struct {
metav1.TypeMeta `json:",inline"`

// +optional
metav1.ObjectMeta `json:"metadata,omitzero"`

// +required
Spec ClusterSpec `json:"spec"`

// +optional
Status ClusterStatus `json:"status,omitzero"`
}

// +kubebuilder:object:root=true

// ClusterList contains a list of Cluster.
type ClusterList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitzero"`
Items []Cluster `json:"items"`
}

func init() {
SchemeBuilder.Register(func(s *runtime.Scheme) error {
s.AddKnownTypes(SchemeGroupVersion, &Cluster{}, &ClusterList{})
return nil
})
}
Loading