From 20684f22076eadfb048fb566ca5a789f62b168fd Mon Sep 17 00:00:00 2001 From: Guilherme Branco Date: Tue, 11 Aug 2026 09:18:43 -0300 Subject: [PATCH 01/15] ROSAENG-62084 | feat: map cluster_id wire field to NodePool metadata.namespace Platform-api returns cluster_id in every NodePool response (required field per OpenAPI schema) but there was no +wire:field mapping for it. The Adapter left it as an unmapped top-level key that the Kubernetes decoder silently dropped, so NodePool.Namespace was always empty after any Get/List/Create call. Add +wire:field=cluster_id,meta=namespace to the NodePool type. wire-gen picks this up and adds {Wire: "cluster_id", Meta: "namespace"} to defaultMappings, so adaptItem now lifts the value into metadata.namespace. The mapping is a no-op for Cluster responses since they carry no cluster_id field. Add an assertion in the SDK e2e sanity test to catch any regression. --- api/v1alpha1/public/nodepool_types.go | 1 + clientset/transport/wire_mappings_generated.go | 1 + test/e2e-sdk/sdk_sanity_test.go | 4 +++- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/api/v1alpha1/public/nodepool_types.go b/api/v1alpha1/public/nodepool_types.go index 2a753640..a86db636 100644 --- a/api/v1alpha1/public/nodepool_types.go +++ b/api/v1alpha1/public/nodepool_types.go @@ -15,6 +15,7 @@ import ( // +genclient // +wire:field=name,meta=name // +wire:field=id,meta=uid +// +wire:field=cluster_id,meta=namespace // +wire:field=resource_version,meta=resourceVersion // +wire:field=generation,meta=generation // +wire:watch=disabled diff --git a/clientset/transport/wire_mappings_generated.go b/clientset/transport/wire_mappings_generated.go index cbec772c..abddb067 100644 --- a/clientset/transport/wire_mappings_generated.go +++ b/clientset/transport/wire_mappings_generated.go @@ -20,6 +20,7 @@ package transport // defaultMappings maps platform-api wire-format field names to Kubernetes // metadata field names. Generated from +wire:field markers on CRD types. var defaultMappings = []FieldMapping{ + {Wire: "cluster_id", Meta: "namespace"}, {Wire: "generation", Meta: "generation"}, {Wire: "id", Meta: "uid"}, {Wire: "name", Meta: "name"}, diff --git a/test/e2e-sdk/sdk_sanity_test.go b/test/e2e-sdk/sdk_sanity_test.go index 0dfb72b3..ff511572 100644 --- a/test/e2e-sdk/sdk_sanity_test.go +++ b/test/e2e-sdk/sdk_sanity_test.go @@ -394,7 +394,9 @@ var _ = Describe("SDK E2E: cluster and nodepool lifecycle", Ordered, func() { Expect(err).ToNot(HaveOccurred(), "SDK nodepool create") nodepoolID = string(np.UID) nodepoolCreated = true - GinkgoWriter.Printf("NodePool %s created (id=%s)\n", npName, nodepoolID) + Expect(np.Namespace).To(Equal(clusterID), + "nodepool.metadata.namespace should be the parent cluster ID (from cluster_id wire field)") + GinkgoWriter.Printf("NodePool %s created (id=%s, cluster_id=%s)\n", npName, nodepoolID, np.Namespace) By("waiting for nodepool Ready") nodepools := cs.HyperfleetV1alpha1().NodePools(clusterID) From 2e3654fa4d584d411662c5dd35f3b298f85a41bc Mon Sep 17 00:00:00 2001 From: Guilherme Branco Date: Tue, 11 Aug 2026 09:25:14 -0300 Subject: [PATCH 02/15] ROSAENG-62084 | fix: align e2e-sdk BASE_URL to E2E_BASE_URL convention All other e2e targets (test-e2e-api, test-e2e-cli, test-e2e-zoa) pass the platform API URL as E2E_BASE_URL into the test process. The test-e2e-sdk target was using the bare BASE_URL name on both sides. Rename to match. --- Makefile | 2 +- test/e2e-sdk/sdk_sanity_test.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index f8919563..01127076 100644 --- a/Makefile +++ b/Makefile @@ -222,7 +222,7 @@ test-e2e-zoa: $(GINKGO) --output-dir=$(TEST_OUTPUT_DIR) ./test/e2e-zoa test-e2e-sdk: $(GINKGO) - BASE_URL="$${BASE_URL}" \ + E2E_BASE_URL="$${BASE_URL}" \ E2E_ACCOUNT_ID="$${E2E_ACCOUNT_ID}" \ E2E_CUSTOMER_ACCOUNT_ID="$${E2E_CUSTOMER_ACCOUNT_ID}" \ CUSTOMER_AWS_PROFILE="$${CUSTOMER_AWS_PROFILE}" \ diff --git a/test/e2e-sdk/sdk_sanity_test.go b/test/e2e-sdk/sdk_sanity_test.go index ff511572..0a54fd70 100644 --- a/test/e2e-sdk/sdk_sanity_test.go +++ b/test/e2e-sdk/sdk_sanity_test.go @@ -18,7 +18,7 @@ limitations under the License. // // Required environment variables: // -// BASE_URL — platform API base URL +// E2E_BASE_URL — platform API base URL // ROSACTL_BIN — path to the rosactl binary // CUSTOMER_AWS_PROFILE — AWS profile for customer-account operations // @@ -119,9 +119,9 @@ var _ = Describe("SDK E2E: cluster and nodepool lifecycle", Ordered, func() { BeforeAll(func() { ctx = context.Background() - baseURL = os.Getenv("BASE_URL") + baseURL = os.Getenv("E2E_BASE_URL") if baseURL == "" { - Skip("BASE_URL is not set") + Skip("E2E_BASE_URL is not set") } rosactlBin = os.Getenv("ROSACTL_BIN") if rosactlBin == "" { From d200da5d59e2b9cd3be8545bf9e0eedd4b965438 Mon Sep 17 00:00:00 2001 From: Guilherme Branco Date: Tue, 11 Aug 2026 11:40:23 -0300 Subject: [PATCH 03/15] =?UTF-8?q?ROSAENG-62084=20|=20refactor:=20rename=20?= =?UTF-8?q?wire=E2=86=92bridge=20throughout=20clientset=20and=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename transport/wire.go → bridge.go, wire_mappings_generated.go → bridge_mappings_generated.go - Rename wrappers/ package to platform/; wire_wrappers_generated.go → bridge_wrappers_generated.go - Rename hack/clientset/cmd/wire-gen/ → bridge-gen/; update go.mod module path - Update bridge-gen modes: mappings → bridge, wrappers → platform - Rename +wire:field/watch/wait markers → +bridge:field/watch/wait in all CRD types - Rename FieldMapping.Wire → FieldMapping.Bridge; update all usages in transport and generator - Update all import paths, Makefile variables, and doc references --- CLAUDE.md | 2 +- Makefile | 32 ++-- api/v1alpha1/cluster_types.go | 12 +- api/v1alpha1/nodepool_types.go | 12 +- api/v1alpha1/public/cluster_types.go | 12 +- api/v1alpha1/public/nodepool_types.go | 14 +- clientset/docs/architecture.md | 12 +- clientset/hyperfleet.go | 10 +- .../bridge_wrappers_generated.go} | 4 +- clientset/{wrappers => platform}/options.go | 2 +- .../platform_test.go} | 2 +- clientset/transport/{wire.go => bridge.go} | 58 ++++--- .../transport/bridge_mappings_generated.go | 38 +++++ .../{wire_test.go => bridge_test.go} | 67 ++++++-- .../transport/wire_mappings_generated.go | 28 ---- docs/api/v2-sdk-initiative.md | 22 +-- hack/api-codegen/pkg/conversion/generator.go | 6 +- .../cmd/{wire-gen => bridge-gen}/go.mod | 2 +- .../cmd/{wire-gen => bridge-gen}/main.go | 150 ++++++++---------- test/e2e-sdk/sdk_sanity_test.go | 20 +-- 20 files changed, 279 insertions(+), 226 deletions(-) rename clientset/{wrappers/wire_wrappers_generated.go => platform/bridge_wrappers_generated.go} (99%) rename clientset/{wrappers => platform}/options.go (98%) rename clientset/{wrappers/wrappers_test.go => platform/platform_test.go} (99%) rename clientset/transport/{wire.go => bridge.go} (86%) create mode 100644 clientset/transport/bridge_mappings_generated.go rename clientset/transport/{wire_test.go => bridge_test.go} (88%) delete mode 100644 clientset/transport/wire_mappings_generated.go rename hack/clientset/cmd/{wire-gen => bridge-gen}/go.mod (86%) rename hack/clientset/cmd/{wire-gen => bridge-gen}/main.go (77%) diff --git a/CLAUDE.md b/CLAUDE.md index fef70863..6e23287d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -54,7 +54,7 @@ clientset/go.mod ← generated typed K8s client for Hyper hyperfleet-operator/go.mod ← requires: fleetdb, api platform-api/go.mod ← requires: fleetdb, api hack/api-codegen/go.mod ← codegen tools (openapi-gen, crd-variants, conversion-gen) -hack/clientset/cmd/wire-gen/go.mod ← wire generation for clientset +hack/clientset/cmd/bridge-gen/go.mod ← wire generation for clientset hack/tools/go.mod ← dev tooling dependencies ``` diff --git a/Makefile b/Makefile index 01127076..7fa116c6 100644 --- a/Makefile +++ b/Makefile @@ -37,7 +37,7 @@ TOOLS_BIN_DIR := $(TOOLS_DIR)/bin GOLANGCI_LINT := $(abspath $(TOOLS_BIN_DIR)/golangci-lint) CONTROLLER_GEN := $(abspath $(TOOLS_BIN_DIR)/controller-gen) CLIENT_GEN := $(abspath $(TOOLS_BIN_DIR)/client-gen) -WIRE_GEN := $(abspath $(TOOLS_BIN_DIR)/wire-gen) +BRIDGE_GEN := $(abspath $(TOOLS_BIN_DIR)/bridge-gen) SETUP_ENVTEST := $(abspath $(TOOLS_BIN_DIR)/setup-envtest) GINKGO := $(abspath $(TOOLS_BIN_DIR)/ginkgo) @@ -51,8 +51,8 @@ SDK_OUTPUT_PKG ?= $(SDK_MODULE)/clientset WIRE_INPUT_DIR ?= $(abspath api/v1alpha1/public) WIRE_OUTPUT_DIR ?= $(abspath clientset/transport) WIRE_OUTPUT_PKG ?= transport -WRAPPERS_OUTPUT_DIR ?= $(abspath clientset/wrappers) -WRAPPERS_OUTPUT_PKG ?= wrappers +PLATFORM_OUTPUT_DIR ?= $(abspath clientset/platform) +PLATFORM_OUTPUT_PKG ?= platform TYPED_PKG_IMPORT ?= $(SDK_MODULE)/clientset/generated/typed/v1alpha1/public API_PKG_IMPORT ?= $(SDK_MODULE)/api/v1alpha1/public SDK_HEADER_FILE ?= $(abspath hack/clientset/license-boilerplate.go.txt) @@ -74,8 +74,8 @@ $(SETUP_ENVTEST): $(TOOLS_DIR)/go.mod $(CLIENT_GEN): $(TOOLS_DIR)/go.mod cd $(TOOLS_DIR); go build -tags=tools -o $(abspath $(TOOLS_BIN_DIR))/client-gen k8s.io/code-generator/cmd/client-gen -$(WIRE_GEN): hack/clientset/cmd/wire-gen/main.go - cd hack/clientset/cmd/wire-gen && go build -o $(WIRE_GEN) . +$(BRIDGE_GEN): hack/clientset/cmd/bridge-gen/main.go + cd hack/clientset/cmd/bridge-gen && go build -o $(BRIDGE_GEN) . $(GINKGO): $(TOOLS_DIR)/go.mod cd $(TOOLS_DIR); go build -tags=tools -o $(abspath $(TOOLS_BIN_DIR))/ginkgo github.com/onsi/ginkgo/v2/ginkgo @@ -95,7 +95,7 @@ help: @echo "Test:" @echo " test All tests (unit + integration)" @echo " test-unit Unit tests: API + operator + codegen + clientset (no external services)" - @echo " test-clientset Clientset unit tests (transport, wrappers)" + @echo " test-clientset Clientset unit tests (transport, platform)" @echo " test-integration Integration tests: FleetDB + operator (podman)" @echo " test-e2e-authz E2E authz (starts local infra)" @echo " test-e2e-api E2E API" @@ -257,7 +257,7 @@ fmt: cd platform-api && go fmt ./... cd hack/api-codegen && go fmt ./... cd clientset && go fmt ./... - cd hack/clientset/cmd/wire-gen && go fmt ./... + cd hack/clientset/cmd/bridge-gen && go fmt ./... vet: cd hyperfleet-db && go vet ./... @@ -265,7 +265,7 @@ vet: cd platform-api && go vet ./... cd hack/api-codegen && go vet ./... cd clientset && go vet ./... - cd hack/clientset/cmd/wire-gen && go vet ./... + cd hack/clientset/cmd/bridge-gen && go vet ./... lint: $(GOLANGCI_LINT) cd hyperfleet-db && $(GOLANGCI_LINT) run --config ../.golangci.yml --timeout 5m ./... @@ -273,7 +273,7 @@ lint: $(GOLANGCI_LINT) cd platform-api && $(GOLANGCI_LINT) run --config ../.golangci.yml --timeout 5m ./... cd hack/api-codegen && $(GOLANGCI_LINT) run --config ../../.golangci.yml --timeout 5m ./... cd clientset && $(GOLANGCI_LINT) run --config ../.golangci.yml --timeout 5m ./... - cd hack/clientset/cmd/wire-gen && $(GOLANGCI_LINT) run --config $(abspath .golangci.yml) --timeout 5m ./... + cd hack/clientset/cmd/bridge-gen && $(GOLANGCI_LINT) run --config $(abspath .golangci.yml) --timeout 5m ./... # All Go modules in the repo (used by verify and MintMaker/Renovate post-upgrade). override MOD_TIDY_DIRS := hyperfleet-db api hyperfleet-operator platform-api test clientset hack/tools hack/api-codegen @@ -301,7 +301,7 @@ manifests: $(CONTROLLER_GEN) generate: $(CONTROLLER_GEN) $(CONTROLLER_GEN) object paths="./api/..." -generate-clientset: $(CLIENT_GEN) $(WIRE_GEN) +generate-clientset: $(CLIENT_GEN) $(BRIDGE_GEN) cd api && $(CLIENT_GEN) \ --input-base "$(SDK_API_PKG)" \ --input "$(SDK_INPUT)" \ @@ -309,17 +309,17 @@ generate-clientset: $(CLIENT_GEN) $(WIRE_GEN) --output-dir "$(SDK_OUTPUT_DIR)" \ --output-pkg "$(SDK_OUTPUT_PKG)" \ --go-header-file "$(SDK_HEADER_FILE)" - $(WIRE_GEN) \ - --mode mappings \ + $(BRIDGE_GEN) \ + --mode bridge \ --input-dir "$(WIRE_INPUT_DIR)" \ --output-dir "$(WIRE_OUTPUT_DIR)" \ --output-pkg "$(WIRE_OUTPUT_PKG)" \ --go-header-file "$(SDK_HEADER_FILE)" - $(WIRE_GEN) \ - --mode wrappers \ + $(BRIDGE_GEN) \ + --mode platform \ --input-dir "$(WIRE_INPUT_DIR)" \ - --output-dir "$(WRAPPERS_OUTPUT_DIR)" \ - --output-pkg "$(WRAPPERS_OUTPUT_PKG)" \ + --output-dir "$(PLATFORM_OUTPUT_DIR)" \ + --output-pkg "$(PLATFORM_OUTPUT_PKG)" \ --typed-pkg-import "$(TYPED_PKG_IMPORT)" \ --typed-client-prefix "V1alpha1Public" \ --api-pkg-import "$(API_PKG_IMPORT)" \ diff --git a/api/v1alpha1/cluster_types.go b/api/v1alpha1/cluster_types.go index 3673e598..e98c1f83 100644 --- a/api/v1alpha1/cluster_types.go +++ b/api/v1alpha1/cluster_types.go @@ -140,12 +140,12 @@ type PlacementReference struct { // +genclient // +genclient:nonNamespaced -// +wire:field=name,meta=name -// +wire:field=id,meta=uid -// +wire:field=resource_version,meta=resourceVersion -// +wire:field=generation,meta=generation -// +wire:watch=disabled -// +wire:wait +// +bridge:field=name,meta=name +// +bridge:field=id,meta=uid +// +bridge:field=resource_version,meta=resourceVersion +// +bridge:field=generation,meta=generation +// +bridge:watch=disabled +// +bridge:wait // +kubebuilder:object:root=true // +kubebuilder:subresource:status // +kubebuilder:resource:scope=Namespaced,shortName=hfc diff --git a/api/v1alpha1/nodepool_types.go b/api/v1alpha1/nodepool_types.go index d7567309..5fed453f 100644 --- a/api/v1alpha1/nodepool_types.go +++ b/api/v1alpha1/nodepool_types.go @@ -89,12 +89,12 @@ type NodePoolStatus struct { } // +genclient -// +wire:field=name,meta=name -// +wire:field=id,meta=uid -// +wire:field=resource_version,meta=resourceVersion -// +wire:field=generation,meta=generation -// +wire:watch=disabled -// +wire:wait +// +bridge:field=name,meta=name +// +bridge:field=id,meta=uid +// +bridge:field=resource_version,meta=resourceVersion +// +bridge:field=generation,meta=generation +// +bridge:watch=disabled +// +bridge:wait // +kubebuilder:object:root=true // +kubebuilder:subresource:status // +kubebuilder:resource:scope=Namespaced,shortName=hfnp diff --git a/api/v1alpha1/public/cluster_types.go b/api/v1alpha1/public/cluster_types.go index b92adcf4..ef01e378 100644 --- a/api/v1alpha1/public/cluster_types.go +++ b/api/v1alpha1/public/cluster_types.go @@ -15,12 +15,12 @@ import ( // +kubebuilder:subresource:status // +genclient // +genclient:nonNamespaced -// +wire:field=name,meta=name -// +wire:field=id,meta=uid -// +wire:field=resource_version,meta=resourceVersion -// +wire:field=generation,meta=generation -// +wire:watch=disabled -// +wire:wait +// +bridge:field=name,meta=name +// +bridge:field=id,meta=uid +// +bridge:field=resource_version,meta=resourceVersion +// +bridge:field=generation,meta=generation +// +bridge:watch=disabled +// +bridge:wait type Cluster struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` diff --git a/api/v1alpha1/public/nodepool_types.go b/api/v1alpha1/public/nodepool_types.go index a86db636..d0b4ff48 100644 --- a/api/v1alpha1/public/nodepool_types.go +++ b/api/v1alpha1/public/nodepool_types.go @@ -13,13 +13,13 @@ import ( // +kubebuilder:resource:scope=Namespaced // +kubebuilder:subresource:status // +genclient -// +wire:field=name,meta=name -// +wire:field=id,meta=uid -// +wire:field=cluster_id,meta=namespace -// +wire:field=resource_version,meta=resourceVersion -// +wire:field=generation,meta=generation -// +wire:watch=disabled -// +wire:wait +// +bridge:field=name,meta=name +// +bridge:field=id,meta=uid +// +bridge:field=cluster_id,meta=namespace +// +bridge:field=resource_version,meta=resourceVersion +// +bridge:field=generation,meta=generation +// +bridge:watch=disabled +// +bridge:wait type NodePool struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` diff --git a/clientset/docs/architecture.md b/clientset/docs/architecture.md index 8ec2a39f..e54d7a0e 100644 --- a/clientset/docs/architecture.md +++ b/clientset/docs/architecture.md @@ -40,7 +40,7 @@ make generate-clientset # regenerate from CRD types make verify-clientset # fail if generated output differs from committed files ``` -`generate-clientset` runs two generators in sequence — `client-gen` for the typed clients and `wire-gen` for field mappings and wrappers: +`generate-clientset` runs two generators in sequence — `client-gen` for the typed clients and `bridge-gen` for field mappings and wrappers: ```makefile SDK_CLIENTSET ?= generated @@ -51,7 +51,7 @@ WIRE_OUTPUT_DIR ?= $(abspath clientset/transport) WRAPPERS_OUTPUT_DIR ?= $(abspath clientset/wrappers) ``` -`wire-gen` is a stdlib-only command built from `hack/clientset/cmd/wire-gen/` with its +`bridge-gen` is a stdlib-only command built from `hack/clientset/cmd/bridge-gen/` with its own `go.mod`. It is compiled automatically as a dependency of the target. ### What gets generated @@ -67,13 +67,13 @@ clientset/generated/ fake/ # fake implementations for testing clientset/transport/ - wire_mappings_generated.go # defaultMappings from +wire:field markers + wire_mappings_generated.go # defaultMappings from +bridge:field markers clientset/wrappers/ - wire_wrappers_generated.go # platform-scoped interfaces + WaitUntil from +wire:wait markers + wire_wrappers_generated.go # platform-scoped interfaces + WaitUntil from +bridge:wait markers ``` -> Files with `_generated.go` suffix are generated by wire-gen. Do not edit them manually. +> Files with `_generated.go` suffix are generated by bridge-gen. Do not edit them manually. > Files under `clientset/generated/` are generated by client-gen. Do not edit them manually. ### install package (hand-written, required by generated code) @@ -267,7 +267,7 @@ The Hyperfleet platform API does not support the Kubernetes watch stream protoco On each CRD type, a marker drives WaitUntil generation: ```go -// +wire:wait → WaitUntil method is generated +// +bridge:wait → WaitUntil method is generated ``` **`WaitUntil` contract** diff --git a/clientset/hyperfleet.go b/clientset/hyperfleet.go index 565c1d64..b8370e7e 100644 --- a/clientset/hyperfleet.go +++ b/clientset/hyperfleet.go @@ -24,7 +24,7 @@ limitations under the License. // AccountID: "123456789012", // AWSConfig: awsCfg, // }) -// cluster, err := cs.HyperfleetV1alpha1().Clusters().Get(ctx, "my-cluster", wrappers.GetOptions{}) +// cluster, err := cs.HyperfleetV1alpha1().Clusters().Get(ctx, "my-cluster", platform.GetOptions{}) package hyperfleet import ( @@ -35,7 +35,7 @@ import ( "github.com/openshift-online/rosa-hyperfleet-api/clientset/generated/scheme" hfrest "github.com/openshift-online/rosa-hyperfleet-api/clientset/rest" "github.com/openshift-online/rosa-hyperfleet-api/clientset/transport" - "github.com/openshift-online/rosa-hyperfleet-api/clientset/wrappers" + "github.com/openshift-online/rosa-hyperfleet-api/clientset/platform" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" k8srest "k8s.io/client-go/rest" @@ -43,7 +43,7 @@ import ( // Interface is the top-level client interface for the Hyperfleet platform API. type Interface interface { - HyperfleetV1alpha1() wrappers.V1alpha1PublicInterface + HyperfleetV1alpha1() platform.V1alpha1PublicInterface } // Clientset implements Interface. @@ -53,8 +53,8 @@ type Clientset struct { // HyperfleetV1alpha1 returns the typed client for the hyperfleet.io/v1alpha1 group. // Watch is disabled (returns ErrWatchNotSupported); use WaitUntil for polling-based waits. -func (c *Clientset) HyperfleetV1alpha1() wrappers.V1alpha1PublicInterface { - return wrappers.NewV1alpha1PublicClient(c.generated.V1alpha1Public()) +func (c *Clientset) HyperfleetV1alpha1() platform.V1alpha1PublicInterface { + return platform.NewV1alpha1PublicClient(c.generated.V1alpha1Public()) } // NewForConfig creates a Clientset from a Config, wiring AWS SigV4 authentication diff --git a/clientset/wrappers/wire_wrappers_generated.go b/clientset/platform/bridge_wrappers_generated.go similarity index 99% rename from clientset/wrappers/wire_wrappers_generated.go rename to clientset/platform/bridge_wrappers_generated.go index 0e3b3ff4..7fb36af2 100644 --- a/clientset/wrappers/wire_wrappers_generated.go +++ b/clientset/platform/bridge_wrappers_generated.go @@ -13,9 +13,9 @@ 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. */ -// Code generated by wire-gen. DO NOT EDIT. +// Code generated by bridge-gen. DO NOT EDIT. -package wrappers +package platform import ( "context" diff --git a/clientset/wrappers/options.go b/clientset/platform/options.go similarity index 98% rename from clientset/wrappers/options.go rename to clientset/platform/options.go index 7bf5b76c..6d822bd9 100644 --- a/clientset/wrappers/options.go +++ b/clientset/platform/options.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package wrappers +package platform // GetOptions configures a single-resource read. // Currently only the default behavior is supported. diff --git a/clientset/wrappers/wrappers_test.go b/clientset/platform/platform_test.go similarity index 99% rename from clientset/wrappers/wrappers_test.go rename to clientset/platform/platform_test.go index 93ae5828..12f5a970 100644 --- a/clientset/wrappers/wrappers_test.go +++ b/clientset/platform/platform_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package wrappers +package platform import ( "context" diff --git a/clientset/transport/wire.go b/clientset/transport/bridge.go similarity index 86% rename from clientset/transport/wire.go rename to clientset/transport/bridge.go index f28224fe..448b1c65 100644 --- a/clientset/transport/wire.go +++ b/clientset/transport/bridge.go @@ -29,7 +29,7 @@ import ( // FieldMapping declares a correspondence between a platform-api wire-format // field name and a Kubernetes metadata field name. type FieldMapping struct { - Wire string // flat field name in the platform-api response/request body + Bridge string // flat field name in the platform-api response/request body Meta string // field name inside the Kubernetes metadata object } @@ -37,23 +37,37 @@ type FieldMapping struct { // responses into the Kubernetes metadata structure expected by the generated decoder. type Adapter struct { inner http.RoundTripper - mappings []FieldMapping + mappings map[string][]FieldMapping } // NewAdapter returns an Adapter that wraps inner. Field mappings are loaded -// from defaultMappings, which is generated by wire-gen from +wire:field markers +// from defaultMappings, which is generated by bridge-gen from +bridge:field markers // on the CRD types and must not be configured by callers. func NewAdapter(inner http.RoundTripper) *Adapter { return &Adapter{inner: inner, mappings: defaultMappings} } +// resourceFromPath extracts the resource type from a URL path by scanning +// segments right-to-left and returning the first segment that matches a +// mappings key (e.g. "clusters", "nodepools"). Returns "" if no match. +func resourceFromPath(path string, mappings map[string][]FieldMapping) string { + segments := strings.Split(path, "/") + for i := len(segments) - 1; i >= 0; i-- { + if _, ok := mappings[segments[i]]; ok { + return segments[i] + } + } + return "" +} + // RoundTrip implements http.RoundTripper. It rewrites the request body from // Kubernetes wire format to the platform-api flat format, adjusts pagination // query parameters, forwards the request via the inner transport, then rewrites // the response body back to Kubernetes format. func (a *Adapter) RoundTrip(req *http.Request) (*http.Response, error) { + mappings := a.mappings[resourceFromPath(req.URL.Path, a.mappings)] var err error - req, err = a.adaptRequest(req) + req, err = a.adaptRequest(req, mappings) if err != nil { return nil, err } @@ -62,7 +76,7 @@ func (a *Adapter) RoundTrip(req *http.Request) (*http.Response, error) { if err != nil { return nil, err } - return a.adaptResponse(resp) + return a.adaptResponse(resp, mappings) } // adaptRequest transforms a Kubernetes-format request body into the platform-api @@ -78,7 +92,7 @@ func (a *Adapter) RoundTrip(req *http.Request) (*http.Response, error) { // For namespaced POST requests the namespace segment encodes the parent resource // ID (e.g. clusterID); it is injected as "cluster_id" in the body before the // SigV4 transport strips the namespace from the URL. -func (a *Adapter) adaptRequest(req *http.Request) (*http.Request, error) { +func (a *Adapter) adaptRequest(req *http.Request, mappings []FieldMapping) (*http.Request, error) { if req.Body == nil { return req, nil } @@ -110,9 +124,9 @@ func (a *Adapter) adaptRequest(req *http.Request) (*http.Request, error) { return req, nil } - for _, fm := range a.mappings { + for _, fm := range mappings { if v, ok := meta[fm.Meta]; ok { - raw[fm.Wire] = v + raw[fm.Bridge] = v } } delete(raw, "metadata") @@ -166,7 +180,7 @@ func (a *Adapter) adaptRequest(req *http.Request) (*http.Request, error) { // The Kubernetes runtime decoder populates v1alpha1.Cluster by JSON field name, // so it expects the cluster name in metadata.name, the UUID in metadata.uid, etc. // Fields that have no mapping are preserved as-is (spec, status pass through). -func (a *Adapter) adaptResponse(resp *http.Response) (*http.Response, error) { +func (a *Adapter) adaptResponse(resp *http.Response, mappings []FieldMapping) (*http.Response, error) { if resp.StatusCode < 200 || resp.StatusCode >= 300 { return adaptErrorResponse(resp) } @@ -188,9 +202,9 @@ func (a *Adapter) adaptResponse(resp *http.Response) (*http.Response, error) { var adapted []byte if itemsJSON, ok := raw["items"]; ok { - adapted = a.adaptList(raw, itemsJSON) - } else if a.hasWireField(raw) { - adapted = a.adaptItem(raw) + adapted = a.adaptList(raw, itemsJSON, mappings) + } else if a.hasWireField(raw, mappings) { + adapted = a.adaptItem(raw, mappings) } else { adapted = body } @@ -294,9 +308,9 @@ func httpStatusToReason(code int) string { // hasWireField reports whether any mapped wire field is present in raw, // used to detect single-object responses. -func (a *Adapter) hasWireField(raw map[string]json.RawMessage) bool { - for _, fm := range a.mappings { - if _, ok := raw[fm.Wire]; ok { +func (a *Adapter) hasWireField(raw map[string]json.RawMessage, mappings []FieldMapping) bool { + for _, fm := range mappings { + if _, ok := raw[fm.Bridge]; ok { return true } } @@ -304,7 +318,7 @@ func (a *Adapter) hasWireField(raw map[string]json.RawMessage) bool { } // adaptList rewrites {"items": [...]} to {"items": [, ...]}. -func (a *Adapter) adaptList(raw map[string]json.RawMessage, itemsJSON json.RawMessage) []byte { +func (a *Adapter) adaptList(raw map[string]json.RawMessage, itemsJSON json.RawMessage, mappings []FieldMapping) []byte { var items []json.RawMessage if err := json.Unmarshal(itemsJSON, &items); err != nil { out, _ := json.Marshal(raw) @@ -318,7 +332,7 @@ func (a *Adapter) adaptList(raw map[string]json.RawMessage, itemsJSON json.RawMe adapted[i] = item continue } - adapted[i] = a.adaptItem(m) + adapted[i] = a.adaptItem(m, mappings) } raw["items"], _ = json.Marshal(adapted) @@ -334,7 +348,7 @@ func (a *Adapter) adaptList(raw map[string]json.RawMessage, itemsJSON json.RawMe // continuation. The Hyperfleet platform API uses offset-based pagination instead: // it has no cursor mechanism and accepts an integer "offset" parameter. // -// The wrappers.ListOptions.Offset field is bridged by encoding the integer offset +// The platform.ListOptions.Offset field is bridged by encoding the integer offset // as a numeric string in ListOptions.Continue before calling the inner client. // This method recognizes that encoding and rewrites the query parameter so the // platform API receives the value it expects. @@ -362,13 +376,13 @@ func (a *Adapter) adaptListQuery(req *http.Request) *http.Request { // adaptItem lifts wire-format envelope fields into metadata using the configured // mappings. All other fields (spec, status, etc.) are preserved unchanged. -func (a *Adapter) adaptItem(m map[string]json.RawMessage) json.RawMessage { +func (a *Adapter) adaptItem(m map[string]json.RawMessage, mappings []FieldMapping) json.RawMessage { meta := make(map[string]json.RawMessage) - for _, fm := range a.mappings { - if v, ok := m[fm.Wire]; ok { + for _, fm := range mappings { + if v, ok := m[fm.Bridge]; ok { meta[fm.Meta] = v - delete(m, fm.Wire) + delete(m, fm.Bridge) } } diff --git a/clientset/transport/bridge_mappings_generated.go b/clientset/transport/bridge_mappings_generated.go new file mode 100644 index 00000000..2005b708 --- /dev/null +++ b/clientset/transport/bridge_mappings_generated.go @@ -0,0 +1,38 @@ +/* +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. +*/ +// Code generated by bridge-gen. DO NOT EDIT. + +package transport + +// defaultMappings maps platform-api wire-format field names to Kubernetes +// metadata field names, keyed by the lowercase plural resource name that +// matches the URL path segment (e.g. "clusters", "nodepools"). +// Generated from +bridge:field markers on CRD types. +var defaultMappings = map[string][]FieldMapping{ + "clusters": { + {Bridge: "generation", Meta: "generation"}, + {Bridge: "id", Meta: "uid"}, + {Bridge: "name", Meta: "name"}, + {Bridge: "resource_version", Meta: "resourceVersion"}, + }, + "nodepools": { + {Bridge: "cluster_id", Meta: "namespace"}, + {Bridge: "generation", Meta: "generation"}, + {Bridge: "id", Meta: "uid"}, + {Bridge: "name", Meta: "name"}, + {Bridge: "resource_version", Meta: "resourceVersion"}, + }, +} diff --git a/clientset/transport/wire_test.go b/clientset/transport/bridge_test.go similarity index 88% rename from clientset/transport/wire_test.go rename to clientset/transport/bridge_test.go index 6f957e7f..1ffb4cc9 100644 --- a/clientset/transport/wire_test.go +++ b/clientset/transport/bridge_test.go @@ -83,9 +83,11 @@ func assertNoField(t *testing.T, m map[string]json.RawMessage, key string) { } // mustAdaptRequest calls adaptRequest and fails the test if it returns an error. +// The resource type is derived from the request URL path. func mustAdaptRequest(t *testing.T, a *Adapter, req *http.Request) *http.Request { t.Helper() - out, err := a.adaptRequest(req) + mappings := a.mappings[resourceFromPath(req.URL.Path, a.mappings)] + out, err := a.adaptRequest(req, mappings) if err != nil { t.Fatalf("adaptRequest: unexpected error: %v", err) } @@ -93,9 +95,11 @@ func mustAdaptRequest(t *testing.T, a *Adapter, req *http.Request) *http.Request } // mustAdaptResponse calls adaptResponse and fails the test if it returns an error. -func mustAdaptResponse(t *testing.T, a *Adapter, resp *http.Response) *http.Response { +// resource is the lowercase plural resource name (e.g. "clusters", "nodepools", or "" +// for tests that do not depend on field mappings). +func mustAdaptResponse(t *testing.T, a *Adapter, resource string, resp *http.Response) *http.Response { t.Helper() - out, err := a.adaptResponse(resp) + out, err := a.adaptResponse(resp, a.mappings[resource]) if err != nil { t.Fatalf("adaptResponse: unexpected error: %v", err) } @@ -238,7 +242,7 @@ func TestAdaptRequest_ReadErrorPropagated(t *testing.T) { req, _ := http.NewRequest(http.MethodPut, "https://example.com/api/v0/clusters/id", io.NopCloser(errReader{err: errors.New("read failure")})) - if _, err := a.adaptRequest(req); err == nil { + if _, err := a.adaptRequest(req, nil); err == nil { t.Error("expected error when body read fails") } } @@ -248,7 +252,7 @@ func TestAdaptRequest_CloseErrorPropagated(t *testing.T) { req, _ := http.NewRequest(http.MethodPut, "https://example.com/api/v0/clusters/id", errCloser{Reader: strings.NewReader(`{"metadata":{"name":"c"},"spec":{}}`), err: errors.New("close failure")}) - if _, err := a.adaptRequest(req); err == nil { + if _, err := a.adaptRequest(req, nil); err == nil { t.Error("expected error when body close fails") } } @@ -264,7 +268,7 @@ func TestAdaptResponse_SingleItemLiftedIntoMetadata(t *testing.T) { Header: make(http.Header), } - out := mustAdaptResponse(t, a, resp) + out := mustAdaptResponse(t, a, "clusters", resp) m := readResponseBody(out) assertNoField(t, m, "id") @@ -289,7 +293,7 @@ func TestAdaptResponse_ListItemsAdapted(t *testing.T) { Header: make(http.Header), } - out := mustAdaptResponse(t, a, resp) + out := mustAdaptResponse(t, a, "clusters", resp) m := readResponseBody(out) var items []map[string]json.RawMessage @@ -335,7 +339,7 @@ func TestAdaptResponse_NonPlatformAPIErrorPassesThrough(t *testing.T) { Header: make(http.Header), } - out := mustAdaptResponse(t, a, resp) + out := mustAdaptResponse(t, a, "", resp) b, err := io.ReadAll(out.Body) if err != nil { t.Fatalf("reading response body: %v", err) @@ -469,7 +473,7 @@ func TestAdaptResponse_PlatformAPIErrorSurfacedAsMetav1Status(t *testing.T) { Header: make(http.Header), } - out := mustAdaptResponse(t, a, resp) + out := mustAdaptResponse(t, a, "", resp) m := readMetav1Status(t, out) assertField(t, m, "kind", `"Status"`) assertField(t, m, "status", `"Failure"`) @@ -495,7 +499,7 @@ func TestAdaptResponse_NoWireFieldsPassesThrough(t *testing.T) { Header: make(http.Header), } - out := mustAdaptResponse(t, a, resp) + out := mustAdaptResponse(t, a, "", resp) b, _ := io.ReadAll(out.Body) if string(b) != original { t.Errorf("body unexpectedly changed: %s", b) @@ -510,7 +514,7 @@ func TestAdaptResponse_MalformedListItemPassesThroughUnchanged(t *testing.T) { Header: make(http.Header), } - out := mustAdaptResponse(t, a, resp) + out := mustAdaptResponse(t, a, "", resp) m := readResponseBody(out) var items []json.RawMessage @@ -520,6 +524,43 @@ func TestAdaptResponse_MalformedListItemPassesThroughUnchanged(t *testing.T) { } } +func TestAdaptResponse_NodepoolClusterIDMappedToNamespace(t *testing.T) { + a := newAdapter() + resp := &http.Response{ + StatusCode: 200, + Body: io.NopCloser(strings.NewReader( + `{"id":"np-uid","name":"my-np","cluster_id":"cluster-uid","resource_version":"1","generation":1,"spec":{}}`)), + Header: make(http.Header), + } + + out := mustAdaptResponse(t, a, "nodepools", resp) + m := readResponseBody(out) + + var meta map[string]json.RawMessage + _ = json.Unmarshal(m["metadata"], &meta) + assertField(t, meta, "namespace", `"cluster-uid"`) + assertNoField(t, m, "cluster_id") +} + +func TestAdaptResponse_ClusterDoesNotMapClusterIDToNamespace(t *testing.T) { + a := newAdapter() + resp := &http.Response{ + StatusCode: 200, + Body: io.NopCloser(strings.NewReader( + `{"id":"cluster-uid","name":"my-cluster","resource_version":"1","generation":1,"spec":{}}`)), + Header: make(http.Header), + } + + out := mustAdaptResponse(t, a, "clusters", resp) + m := readResponseBody(out) + + var meta map[string]json.RawMessage + _ = json.Unmarshal(m["metadata"], &meta) + if _, ok := meta["namespace"]; ok { + t.Error("clusters mapping should not produce a namespace field") + } +} + func TestAdaptResponse_ReadErrorPropagated(t *testing.T) { a := newAdapter() resp := &http.Response{ @@ -528,7 +569,7 @@ func TestAdaptResponse_ReadErrorPropagated(t *testing.T) { Header: make(http.Header), } - if _, err := a.adaptResponse(resp); err == nil { + if _, err := a.adaptResponse(resp, nil); err == nil { t.Error("expected error when response body read fails") } } @@ -541,7 +582,7 @@ func TestAdaptResponse_CloseErrorPropagated(t *testing.T) { Header: make(http.Header), } - if _, err := a.adaptResponse(resp); err == nil { + if _, err := a.adaptResponse(resp, nil); err == nil { t.Error("expected error when response body close fails") } } diff --git a/clientset/transport/wire_mappings_generated.go b/clientset/transport/wire_mappings_generated.go deleted file mode 100644 index abddb067..00000000 --- a/clientset/transport/wire_mappings_generated.go +++ /dev/null @@ -1,28 +0,0 @@ -/* -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. -*/ -// Code generated by wire-gen. DO NOT EDIT. - -package transport - -// defaultMappings maps platform-api wire-format field names to Kubernetes -// metadata field names. Generated from +wire:field markers on CRD types. -var defaultMappings = []FieldMapping{ - {Wire: "cluster_id", Meta: "namespace"}, - {Wire: "generation", Meta: "generation"}, - {Wire: "id", Meta: "uid"}, - {Wire: "name", Meta: "name"}, - {Wire: "resource_version", Meta: "resourceVersion"}, -} diff --git a/docs/api/v2-sdk-initiative.md b/docs/api/v2-sdk-initiative.md index 993e724c..830d425f 100644 --- a/docs/api/v2-sdk-initiative.md +++ b/docs/api/v2-sdk-initiative.md @@ -140,7 +140,7 @@ The v1 SDK is generated from a proprietary metamodel DSL (`ocm-api-model`). The The v2 SDK exposes its generated interface directly — there is no v1-compatibility adapter. Consumers migrate to the new interface (see [Interface Decision](#interface-decision)). -**Generated core**: Auto-generated from the HyperFleet CRD types (`api/v1alpha1/`) using `client-gen` (from `k8s.io/code-generator`), the same tool that generates typed clientsets for any Kubernetes operator. This produces typed verb clients (`Create`, `Get`, `List`, `Update`, `Delete`) that match the CRD types exactly. A custom `wire-gen` tool extends the generated clients with platform-specific behavior (watch suppression, `WaitUntil` polling). +**Generated core**: Auto-generated from the HyperFleet CRD types (`api/v1alpha1/`) using `client-gen` (from `k8s.io/code-generator`), the same tool that generates typed clientsets for any Kubernetes operator. This produces typed verb clients (`Create`, `Get`, `List`, `Update`, `Delete`) that match the CRD types exactly. A custom `bridge-gen` tool extends the generated clients with platform-specific behavior (watch suppression, `WaitUntil` polling). ### SDK Release Cadence and Strategy @@ -152,7 +152,7 @@ In v1, the SDK was released separately from the backend. The v2 api will support ├────────────────────────────────────────────┤ │ Generated Core (from CRD types) │ │ Typed clients, models (client-gen) │ -│ Platform wrappers (wire-gen) │ +│ Platform wrappers (bridge-gen) │ ├────────────────────────────────────────────┤ │ Connection / Auth / Transport │ │ AWS SigV4 auth, retry, logging │ @@ -168,16 +168,16 @@ Go types (api/v1alpha1/*.go) ↓ client-gen (k8s.io/code-generator) ↓ Typed clientset: Create/Get/List/Update/Delete per resource ↓ - ↓ wire-gen --mode=mappings → wire field name → metadata field name mappings - ↓ wire-gen --mode=wrappers → Watch override (ErrWatchNotSupported) + ↓ bridge-gen --mode=bridge → wire field name → metadata field name mappings + ↓ bridge-gen --mode=platform → Watch override (ErrWatchNotSupported) WaitUntil polling helper ``` -Markers in the CRD type comments drive `wire-gen` output: +Markers in the CRD type comments drive `bridge-gen` output: -- `+wire:field=,meta=` — field name mapping (transport layer) -- `+wire:watch=disabled` — suppress Watch; generate an override returning `ErrWatchNotSupported` -- `+wire:wait` — generate `WaitUntil(ctx, id, condition func(*T) bool, interval, timeout)` +- `+bridge:field=,meta=` — field name mapping (transport layer) +- `+bridge:watch=disabled` — suppress Watch; generate an override returning `ErrWatchNotSupported` +- `+bridge:wait` — generate `WaitUntil(ctx, id, condition func(*T) bool, interval, timeout)` The entire pipeline runs as `make generate-clientset`. @@ -333,8 +333,8 @@ Set up the `clientset/` module with: Set up the generation pipeline: - Use `client-gen` to generate typed clientsets from `api/v1alpha1/` CRD types -- Use `wire-gen --mode=mappings` to generate wire↔metadata field name mappings from `+wire:field` markers -- Use `wire-gen --mode=wrappers` to generate Watch overrides and `WaitUntil` polling helpers from `+wire:watch=disabled` / `+wire:wait` markers +- Use `bridge-gen --mode=bridge` to generate wire↔metadata field name mappings from `+bridge:field` markers +- Use `bridge-gen --mode=platform` to generate Watch overrides and `WaitUntil` polling helpers from `+bridge:watch=disabled` / `+bridge:wait` markers - Wire the generated client into the SDK's transport layer (`clientset/transport`) - Expose the wrapped clientset through `clientset/hyperfleet.go` @@ -378,7 +378,7 @@ The rosa CLI supports **both** SDKs side by side — v1 (`ocm-sdk-go`) remains t ## Decisions Made -1. **Generation approach**: CRD-types-first. Drop the proprietary OCM metamodel DSL. Generate the v2 SDK directly from the CRD type definitions using `client-gen` (standard Kubernetes tooling) plus a custom `wire-gen` for platform-specific extensions (watch suppression, `WaitUntil` polling). This avoids the OpenAPI intermediary step and keeps generation aligned with the operator's type definitions as the single source of truth. +1. **Generation approach**: CRD-types-first. Drop the proprietary OCM metamodel DSL. Generate the v2 SDK directly from the CRD type definitions using `client-gen` (standard Kubernetes tooling) plus a custom `bridge-gen` for platform-specific extensions (watch suppression, `WaitUntil` polling). This avoids the OpenAPI intermediary step and keeps generation aligned with the operator's type definitions as the single source of truth. 2. **Auth model**: AWS SigV4 (IAM auth), not OCM SSO tokens. The HyperFleet API authenticates all requests via AWS IAM credentials. 3. **Initial surface**: Cluster + NodePool only. Tenancy and authz (account linking, policies, attachments, authorization check) are deferred to a future iteration, along with access transparency, service logs, etc. 4. **Interface style**: Kubernetes-style, modeled on `client-go` — typed resource structs (`ObjectMeta`/`Spec`/`Status`) constructed as struct literals, and a typed client exposing `Create`/`Get`/`List`/`Update`/`Patch`/`Delete` verbs. No fluent builders. diff --git a/hack/api-codegen/pkg/conversion/generator.go b/hack/api-codegen/pkg/conversion/generator.go index eabe7a6f..2d16f7fe 100644 --- a/hack/api-codegen/pkg/conversion/generator.go +++ b/hack/api-codegen/pkg/conversion/generator.go @@ -18,7 +18,7 @@ import ( "github.com/openshift-online/rosa-hyperfleet-api/hack/api-codegen/pkg/registry" ) -var clientgenMarkerRE = regexp.MustCompile(`\+genclient\b|\+wire:`) +var clientgenMarkerRE = regexp.MustCompile(`\+genclient\b|\+bridge:`) // Generator generates REST types and conversion functions from CRD types. type Generator struct { @@ -240,10 +240,10 @@ func (g *Generator) parseTypes() error { } // extractClientMarkers scans all comment groups in a file for +genclient and -// +wire:* markers that appear in floating comment blocks (separated by a blank +// +bridge:* markers that appear in floating comment blocks (separated by a blank // line from the type's doc comment). It associates each marker set with the // nearest following type declaration, mirroring the convention used by -// client-gen and wire-gen. +// client-gen and bridge-gen. func (g *Generator) extractClientMarkers(file *ast.File) { type typePos struct { name string diff --git a/hack/clientset/cmd/wire-gen/go.mod b/hack/clientset/cmd/bridge-gen/go.mod similarity index 86% rename from hack/clientset/cmd/wire-gen/go.mod rename to hack/clientset/cmd/bridge-gen/go.mod index daf710da..4e252692 100644 --- a/hack/clientset/cmd/wire-gen/go.mod +++ b/hack/clientset/cmd/bridge-gen/go.mod @@ -1,3 +1,3 @@ -module github.com/openshift-online/rosa-hyperfleet-api/hack/cmd/wire-gen +module github.com/openshift-online/rosa-hyperfleet-api/hack/cmd/bridge-gen go 1.21 diff --git a/hack/clientset/cmd/wire-gen/main.go b/hack/clientset/cmd/bridge-gen/main.go similarity index 77% rename from hack/clientset/cmd/wire-gen/main.go rename to hack/clientset/cmd/bridge-gen/main.go index c349b02a..546c2188 100644 --- a/hack/clientset/cmd/wire-gen/main.go +++ b/hack/clientset/cmd/bridge-gen/main.go @@ -14,15 +14,15 @@ See the License for the specific language governing permissions and limitations under the License. */ -// wire-gen reads +wire:* markers from Go source files and emits generated Go code. +// bridge-gen reads +bridge:* markers from Go source files and emits generated Go code. // // Modes: // -// --mode=mappings (default): emits wire_mappings_generated.go containing a -// FieldMapping slice derived from +wire:field markers. +// --mode=bridge (default): emits bridge_mappings_generated.go containing a +// per-type FieldMapping map derived from +bridge:field markers. // -// --mode=wrappers: emits wire_wrappers_generated.go containing wrapper types -// for resources annotated with +wire:watch=disabled and/or +wire:wait. +// --mode=platform: emits bridge_wrappers_generated.go containing wrapper types +// for resources annotated with +bridge:watch=disabled and/or +bridge:wait. // The Watch method returns ErrWatchNotSupported; WaitUntil provides // polling-based synchronization via a caller-supplied condition function. package main @@ -44,44 +44,52 @@ import ( ) var ( - wireMarkerRE = regexp.MustCompile(`\+wire:field=([^,\s]+),meta=([^\s]+)`) - watchDisabledRE = regexp.MustCompile(`\+wire:watch=disabled`) - waitRE = regexp.MustCompile(`\+wire:wait\b`) + bridgeMarkerRE = regexp.MustCompile(`\+bridge:field=([^,\s]+),meta=([^\s]+)`) + watchDisabledRE = regexp.MustCompile(`\+bridge:watch=disabled`) + waitRE = regexp.MustCompile(`\+bridge:wait\b`) nonNamespacedRE = regexp.MustCompile(`\+genclient:nonNamespaced\b`) ) // fieldMapping holds a single wire→metadata field translation. type fieldMapping struct { - Wire string + Bridge string Meta string } -// resourceType describes a CRD type annotated with +wire:watch or +wire:wait. +// resourceType describes a CRD type annotated with +bridge:watch or +bridge:wait. type resourceType struct { - Name string // e.g. "Cluster" - PluralName string // e.g. "Clusters" - LowerName string // e.g. "cluster" + Name string // e.g. "Cluster" + PluralName string // e.g. "Clusters" + PluralLower string // e.g. "clusters" — URL path segment key + LowerName string // e.g. "cluster" WatchDisabled bool Wait bool - NonNamespaced bool // set when +genclient:nonNamespaced is present + NonNamespaced bool // set when +genclient:nonNamespaced is present + Mappings []fieldMapping // from +bridge:field markers on this type } // ── Templates ──────────────────────────────────────────────────────────────── -const mappingsTmpl = `// Code generated by wire-gen. DO NOT EDIT. +const mappingsTmpl = `// Code generated by bridge-gen. DO NOT EDIT. package {{.Package}} // defaultMappings maps platform-api wire-format field names to Kubernetes -// metadata field names. Generated from +wire:field markers on CRD types. -var defaultMappings = []FieldMapping{ -{{- range .Mappings}} - {Wire: "{{.Wire}}", Meta: "{{.Meta}}"}, -{{- end}} +// metadata field names, keyed by the lowercase plural resource name that +// matches the URL path segment (e.g. "clusters", "nodepools"). +// Generated from +bridge:field markers on CRD types. +var defaultMappings = map[string][]FieldMapping{ +{{- range .Types}}{{if .Mappings}} + "{{.PluralLower}}": { + {{- range .Mappings}} + {Bridge: "{{.Bridge}}", Meta: "{{.Meta}}"}, + {{- end}} + }, +{{- end}}{{end}} } ` -const wrappersTmpl = `// Code generated by wire-gen. DO NOT EDIT. +const platformTmpl = `// Code generated by bridge-gen. DO NOT EDIT. package {{.Package}} @@ -241,18 +249,18 @@ func (w *wrappedV1alpha1) {{.PluralName}}(namespace string) {{.Name}}Interface { // ── main ───────────────────────────────────────────────────────────────────── func main() { - mode := flag.String("mode", "mappings", "generation mode: mappings or wrappers") - inputDir := flag.String("input-dir", "", "directory of Go source files to scan for +wire:* markers") + mode := flag.String("mode", "bridge", "generation mode: bridge or platform") + inputDir := flag.String("input-dir", "", "directory of Go source files to scan for +bridge:* markers") outputDir := flag.String("output-dir", "", "directory to write the generated file") outputPkg := flag.String("output-pkg", "transport", "Go package name for the generated file") headerFile := flag.String("go-header-file", "", "file whose contents are prepended to the generated output") - typedPkg := flag.String("typed-pkg-import", "", "[wrappers] import path of the generated typed client package") - apiPkg := flag.String("api-pkg-import", "", "[wrappers] import path of the CRD API types package") - typedClientPrefix := flag.String("typed-client-prefix", "V1alpha1", "[wrappers] group-level interface name prefix (e.g. V1alpha1 or V1alpha1Public)") + typedPkg := flag.String("typed-pkg-import", "", "[platform] import path of the generated typed client package") + apiPkg := flag.String("api-pkg-import", "", "[platform] import path of the CRD API types package") + typedClientPrefix := flag.String("typed-client-prefix", "V1alpha1", "[platform] group-level interface name prefix (e.g. V1alpha1 or V1alpha1Public)") flag.Parse() if *inputDir == "" || *outputDir == "" { - fmt.Fprintln(os.Stderr, "wire-gen: --input-dir and --output-dir are required") + fmt.Fprintln(os.Stderr, "bridge-gen: --input-dir and --output-dir are required") os.Exit(1) } @@ -263,25 +271,25 @@ func main() { } switch *mode { - case "mappings": + case "bridge": generateMappings(*inputDir, *outputDir, *outputPkg, header) - case "wrappers": + case "platform": if *typedPkg == "" || *apiPkg == "" { - fmt.Fprintln(os.Stderr, "wire-gen: wrappers mode requires --typed-pkg-import and --api-pkg-import") + fmt.Fprintln(os.Stderr, "bridge-gen: platform mode requires --typed-pkg-import and --api-pkg-import") os.Exit(1) } - generateWrappers(*inputDir, *outputDir, *outputPkg, *typedPkg, *apiPkg, *typedClientPrefix, header) + generatePlatform(*inputDir, *outputDir, *outputPkg, *typedPkg, *apiPkg, *typedClientPrefix, header) default: - fatalf("unknown mode %q; use mappings or wrappers", *mode) + fatalf("unknown mode %q; use bridge or platform", *mode) } } -// ── mappings mode ───────────────────────────────────────────────────────────── +// ── bridge mode ───────────────────────────────────────────────────────────── func generateMappings(inputDir, outputDir, pkg, header string) { - mappings := collectMappings(inputDir) + types := collectResourceTypes(inputDir) - outPath := filepath.Join(outputDir, "wire_mappings_generated.go") + outPath := filepath.Join(outputDir, "bridge_mappings_generated.go") f, err := os.Create(outPath) if err != nil { fatalf("creating %s: %v", outPath, err) @@ -290,64 +298,31 @@ func generateMappings(inputDir, outputDir, pkg, header string) { if err := f.Close(); err != nil { fatalf("closing %s: %v", outPath, err) } - fmt.Printf("wire-gen: wrote %s\n", outPath) + fmt.Printf("bridge-gen: wrote %s\n", outPath) }() if _, err := fmt.Fprint(f, header); err != nil { fatalf("writing header to %s: %v", outPath, err) } - tmpl := template.Must(template.New("mappings").Parse(mappingsTmpl)) + tmpl := template.Must(template.New("bridge").Parse(mappingsTmpl)) if err := tmpl.Execute(f, map[string]any{ - "Package": pkg, - "Mappings": mappings, + "Package": pkg, + "Types": types, }); err != nil { fatalf("rendering template: %v", err) } } -// collectMappings parses all Go source files in dir and extracts unique -// +wire:field=,meta= markers from any comment in the file. -// All comment groups are scanned so markers in floating comment blocks are captured. -func collectMappings(dir string) []fieldMapping { - fset := token.NewFileSet() - pkgs, err := parser.ParseDir(fset, dir, nil, parser.ParseComments) - if err != nil { - fatalf("parsing %s: %v", dir, err) - } - - seen := map[string]bool{} - var mappings []fieldMapping - - for _, pkg := range pkgs { - for _, file := range pkg.Files { - for _, cg := range file.Comments { - for _, comment := range cg.List { - for _, m := range wireMarkerRE.FindAllStringSubmatch(comment.Text, -1) { - key := m[1] + ":" + m[2] - if !seen[key] { - seen[key] = true - mappings = append(mappings, fieldMapping{Wire: m[1], Meta: m[2]}) - } - } - } - } - } - } - - sort.Slice(mappings, func(i, j int) bool { return mappings[i].Wire < mappings[j].Wire }) - return mappings -} - -// ── wrappers mode ───────────────────────────────────────────────────────────── +// ── platform mode ───────────────────────────────────────────────────────────── -func generateWrappers(inputDir, outputDir, pkg, typedPkgImport, apiPkgImport, typedClientPrefix, header string) { +func generatePlatform(inputDir, outputDir, pkg, typedPkgImport, apiPkgImport, typedClientPrefix, header string) { types := collectResourceTypes(inputDir) if len(types) == 0 { - fatalf("no resource types with +wire:watch or +wire:wait markers found in %s", inputDir) + fatalf("no resource types with +bridge:watch or +bridge:wait markers found in %s", inputDir) } - outPath := filepath.Join(outputDir, "wire_wrappers_generated.go") + outPath := filepath.Join(outputDir, "bridge_wrappers_generated.go") f, err := os.Create(outPath) if err != nil { fatalf("creating %s: %v", outPath, err) @@ -356,7 +331,7 @@ func generateWrappers(inputDir, outputDir, pkg, typedPkgImport, apiPkgImport, ty if err := f.Close(); err != nil { fatalf("closing %s: %v", outPath, err) } - fmt.Printf("wire-gen: wrote %s\n", outPath) + fmt.Printf("bridge-gen: wrote %s\n", outPath) }() if _, err := fmt.Fprint(f, header); err != nil { @@ -373,7 +348,7 @@ func generateWrappers(inputDir, outputDir, pkg, typedPkgImport, apiPkgImport, ty } } - tmpl := template.Must(template.New("wrappers").Parse(wrappersTmpl)) + tmpl := template.Must(template.New("platform").Parse(platformTmpl)) if err := tmpl.Execute(f, map[string]any{ "Package": pkg, "ApiPkgImport": apiPkgImport, @@ -388,7 +363,7 @@ func generateWrappers(inputDir, outputDir, pkg, typedPkgImport, apiPkgImport, ty } // collectResourceTypes scans all Go source files in dir for type declarations -// annotated with +wire:watch=disabled and/or +wire:wait. +// annotated with +bridge:watch=disabled and/or +bridge:wait. // // Because generators conventionally place markers in a floating comment block // (separated by a blank line from the actual doc comment), markers are found by @@ -431,6 +406,8 @@ func collectResourceTypes(dir string) []resourceType { // next type declaration that follows it. for _, cg := range file.Comments { var hasWatch, hasWait, hasNonNamespaced bool + var typeMappings []fieldMapping + seenMappings := map[string]bool{} for _, c := range cg.List { if watchDisabledRE.MatchString(c.Text) { hasWatch = true @@ -441,10 +418,18 @@ func collectResourceTypes(dir string) []resourceType { if nonNamespacedRE.MatchString(c.Text) { hasNonNamespaced = true } + for _, m := range bridgeMarkerRE.FindAllStringSubmatch(c.Text, -1) { + key := m[1] + ":" + m[2] + if !seenMappings[key] { + seenMappings[key] = true + typeMappings = append(typeMappings, fieldMapping{Bridge: m[1], Meta: m[2]}) + } + } } if !hasWatch && !hasWait { continue } + sort.Slice(typeMappings, func(i, j int) bool { return typeMappings[i].Bridge < typeMappings[j].Bridge }) cgEnd := cg.End() for _, td := range typeDecls { @@ -456,13 +441,16 @@ func collectResourceTypes(dir string) []resourceType { } seen[td.name] = true name := td.name + plural := name + "s" types = append(types, resourceType{ Name: name, - PluralName: name + "s", + PluralName: plural, + PluralLower: strings.ToLower(plural), LowerName: strings.ToLower(name[:1]) + name[1:], WatchDisabled: hasWatch, Wait: hasWait, NonNamespaced: hasNonNamespaced, + Mappings: typeMappings, }) break } @@ -488,6 +476,6 @@ func readHeader(path string) string { } func fatalf(format string, args ...any) { - fmt.Fprintf(os.Stderr, "wire-gen: "+format+"\n", args...) + fmt.Fprintf(os.Stderr, "bridge-gen: "+format+"\n", args...) os.Exit(1) } diff --git a/test/e2e-sdk/sdk_sanity_test.go b/test/e2e-sdk/sdk_sanity_test.go index 0a54fd70..223cf0cc 100644 --- a/test/e2e-sdk/sdk_sanity_test.go +++ b/test/e2e-sdk/sdk_sanity_test.go @@ -53,7 +53,7 @@ import ( hyperfleet "github.com/openshift-online/rosa-hyperfleet-api/clientset" hfrest "github.com/openshift-online/rosa-hyperfleet-api/clientset/rest" - "github.com/openshift-online/rosa-hyperfleet-api/clientset/wrappers" + "github.com/openshift-online/rosa-hyperfleet-api/clientset/platform" v1alpha1 "github.com/openshift-online/rosa-hyperfleet-api/api/v1alpha1/public" awstest "github.com/openshift-online/rosa-hyperfleet-api/test/helpers/aws" hypershiftv1beta1 "github.com/openshift/hypershift/api/hypershift/v1beta1" @@ -213,7 +213,7 @@ var _ = Describe("SDK E2E: cluster and nodepool lifecycle", Ordered, func() { } if nodepoolCreated && nodepoolID != "" && clusterID != "" { GinkgoWriter.Printf("DeferCleanup: initiating nodepool %s deletion\n", nodepoolID) - if err := cs.HyperfleetV1alpha1().NodePools(clusterID).Delete(cleanupCtx, nodepoolID, wrappers.DeleteOptions{}); err != nil { + if err := cs.HyperfleetV1alpha1().NodePools(clusterID).Delete(cleanupCtx, nodepoolID, platform.DeleteOptions{}); err != nil { GinkgoWriter.Printf("DeferCleanup WARNING: nodepool delete: %v\n", err) } else { nodepoolCreated = false @@ -319,7 +319,7 @@ var _ = Describe("SDK E2E: cluster and nodepool lifecycle", Ordered, func() { }, }, }, - }, wrappers.CreateOptions{}) + }, platform.CreateOptions{}) Expect(err).ToNot(HaveOccurred(), "SDK cluster create") clusterID = string(cluster.UID) clusterCreated = true @@ -390,7 +390,7 @@ var _ = Describe("SDK E2E: cluster and nodepool lifecycle", Ordered, func() { Release: hypershiftv1beta1.Release{Image: version}, }, }, - }, wrappers.CreateOptions{}) + }, platform.CreateOptions{}) Expect(err).ToNot(HaveOccurred(), "SDK nodepool create") nodepoolID = string(np.UID) nodepoolCreated = true @@ -414,11 +414,11 @@ var _ = Describe("SDK E2E: cluster and nodepool lifecycle", Ordered, func() { GinkgoWriter.Printf("NodePool %s is Ready\n", npName) By("patching nodepool replicas") - current, err := nodepools.Get(ctx, nodepoolID, wrappers.GetOptions{}) + current, err := nodepools.Get(ctx, nodepoolID, platform.GetOptions{}) Expect(err).ToNot(HaveOccurred(), "getting nodepool for patch") newReplicas := int32(3) current.Spec.NodePool.Replicas = &newReplicas - updated, err := nodepools.Update(ctx, current, wrappers.UpdateOptions{}) + updated, err := nodepools.Update(ctx, current, platform.UpdateOptions{}) Expect(err).ToNot(HaveOccurred(), "updating nodepool replicas") Expect(*updated.Spec.NodePool.Replicas).To(Equal(newReplicas)) GinkgoWriter.Printf("NodePool replicas updated to %d\n", newReplicas) @@ -444,7 +444,7 @@ var _ = Describe("SDK E2E: cluster and nodepool lifecycle", Ordered, func() { Release: hypershiftv1beta1.Release{Image: version}, }, }, - }, wrappers.CreateOptions{}) + }, platform.CreateOptions{}) Expect(err).ToNot(HaveOccurred(), "SDK extra nodepool create") extraNodepoolID = string(extraNp.UID) extraNodepoolCreated = true @@ -469,7 +469,7 @@ var _ = Describe("SDK E2E: cluster and nodepool lifecycle", Ordered, func() { extraNodepoolCreated = false By("initiating nodepool deletion") - Expect(cs.HyperfleetV1alpha1().NodePools(clusterID).Delete(ctx, nodepoolID, wrappers.DeleteOptions{})).To(Succeed()) + Expect(cs.HyperfleetV1alpha1().NodePools(clusterID).Delete(ctx, nodepoolID, platform.DeleteOptions{})).To(Succeed()) nodepoolCreated = false GinkgoWriter.Printf("NodePool %s deletion initiated\n", nodepoolID) @@ -652,7 +652,7 @@ func verifyRolesTrustOIDCProvider(roles hypershiftv1beta1.AWSRolesRef, oidcProvi func deleteNodepool(ctx context.Context, cs *hyperfleet.Clientset, clusterID, nodepoolID string) error { nodepools := cs.HyperfleetV1alpha1().NodePools(clusterID) - if err := nodepools.Delete(ctx, nodepoolID, wrappers.DeleteOptions{}); err != nil { + if err := nodepools.Delete(ctx, nodepoolID, platform.DeleteOptions{}); err != nil { return fmt.Errorf("nodepool delete: %w", err) } return nodepools.WaitUntil(ctx, nodepoolID, @@ -671,7 +671,7 @@ func deleteNodepool(ctx context.Context, cs *hyperfleet.Clientset, clusterID, no func deleteCluster(ctx context.Context, cs *hyperfleet.Clientset, customerAccountID, clusterID, clusterName string) error { clusters := cs.HyperfleetV1alpha1().Clusters() - if err := clusters.Delete(ctx, clusterID, wrappers.DeleteOptions{}); err != nil { + if err := clusters.Delete(ctx, clusterID, platform.DeleteOptions{}); err != nil { return fmt.Errorf("cluster delete: %w", err) } return clusters.WaitUntil(ctx, clusterID, From 366350b22ae54961f1799782906a5b062a939bdd Mon Sep 17 00:00:00 2001 From: Guilherme Branco Date: Tue, 11 Aug 2026 11:43:56 -0300 Subject: [PATCH 04/15] ROSAENG-62084 | refactor: rename WIRE_ Makefile vars to BRIDGE_, update architecture.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - WIRE_INPUT/OUTPUT_DIR/PKG → BRIDGE_INPUT/OUTPUT_DIR/PKG in Makefile - Fix stale architecture.md references: variable names, file paths (wire_* → bridge_*), package qualifier (wrappers.* → platform.*), directory paths (wrappers/ → platform/) --- Makefile | 14 ++++++------- clientset/docs/architecture.md | 36 +++++++++++++++++----------------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/Makefile b/Makefile index 7fa116c6..7746e249 100644 --- a/Makefile +++ b/Makefile @@ -48,9 +48,9 @@ SDK_INPUT ?= v1alpha1/public SDK_CLIENTSET ?= generated SDK_OUTPUT_DIR ?= $(abspath clientset) SDK_OUTPUT_PKG ?= $(SDK_MODULE)/clientset -WIRE_INPUT_DIR ?= $(abspath api/v1alpha1/public) -WIRE_OUTPUT_DIR ?= $(abspath clientset/transport) -WIRE_OUTPUT_PKG ?= transport +BRIDGE_INPUT_DIR ?= $(abspath api/v1alpha1/public) +BRIDGE_OUTPUT_DIR ?= $(abspath clientset/transport) +BRIDGE_OUTPUT_PKG ?= transport PLATFORM_OUTPUT_DIR ?= $(abspath clientset/platform) PLATFORM_OUTPUT_PKG ?= platform TYPED_PKG_IMPORT ?= $(SDK_MODULE)/clientset/generated/typed/v1alpha1/public @@ -311,13 +311,13 @@ generate-clientset: $(CLIENT_GEN) $(BRIDGE_GEN) --go-header-file "$(SDK_HEADER_FILE)" $(BRIDGE_GEN) \ --mode bridge \ - --input-dir "$(WIRE_INPUT_DIR)" \ - --output-dir "$(WIRE_OUTPUT_DIR)" \ - --output-pkg "$(WIRE_OUTPUT_PKG)" \ + --input-dir "$(BRIDGE_INPUT_DIR)" \ + --output-dir "$(BRIDGE_OUTPUT_DIR)" \ + --output-pkg "$(BRIDGE_OUTPUT_PKG)" \ --go-header-file "$(SDK_HEADER_FILE)" $(BRIDGE_GEN) \ --mode platform \ - --input-dir "$(WIRE_INPUT_DIR)" \ + --input-dir "$(BRIDGE_INPUT_DIR)" \ --output-dir "$(PLATFORM_OUTPUT_DIR)" \ --output-pkg "$(PLATFORM_OUTPUT_PKG)" \ --typed-pkg-import "$(TYPED_PKG_IMPORT)" \ diff --git a/clientset/docs/architecture.md b/clientset/docs/architecture.md index e54d7a0e..46012b93 100644 --- a/clientset/docs/architecture.md +++ b/clientset/docs/architecture.md @@ -5,7 +5,7 @@ The SDK provides a typed Go client for the Hyperfleet platform API, using the same interface style as `client-go`: ```go -cs.HyperfleetV1alpha1().Clusters().Get(ctx, "my-cluster", wrappers.GetOptions{}) +cs.HyperfleetV1alpha1().Clusters().Get(ctx, "my-cluster", platform.GetOptions{}) ``` It is built in two parts: @@ -46,9 +46,9 @@ make verify-clientset # fail if generated output differs from committed file SDK_CLIENTSET ?= generated SDK_OUTPUT_DIR ?= $(abspath clientset) SDK_OUTPUT_PKG ?= $(SDK_MODULE)/clientset -WIRE_INPUT_DIR ?= $(abspath api/v1alpha1) -WIRE_OUTPUT_DIR ?= $(abspath clientset/transport) -WRAPPERS_OUTPUT_DIR ?= $(abspath clientset/wrappers) +BRIDGE_INPUT_DIR ?= $(abspath api/v1alpha1) +BRIDGE_OUTPUT_DIR ?= $(abspath clientset/transport) +PLATFORM_OUTPUT_DIR ?= $(abspath clientset/platform) ``` `bridge-gen` is a stdlib-only command built from `hack/clientset/cmd/bridge-gen/` with its @@ -67,10 +67,10 @@ clientset/generated/ fake/ # fake implementations for testing clientset/transport/ - wire_mappings_generated.go # defaultMappings from +bridge:field markers + bridge_mappings_generated.go # defaultMappings from +bridge:field markers -clientset/wrappers/ - wire_wrappers_generated.go # platform-scoped interfaces + WaitUntil from +bridge:wait markers +clientset/platform/ + bridge_wrappers_generated.go # platform-scoped interfaces + WaitUntil from +bridge:wait markers ``` > Files with `_generated.go` suffix are generated by bridge-gen. Do not edit them manually. @@ -106,7 +106,7 @@ The platform API differs from a standard Kubernetes API in three ways that requi |---|---| | Requests are signed with AWS SigV4 | `transport/sigv4.go` — custom RoundTripper | | Resources are account-scoped, not namespace-scoped | SigV4 transport extracts the Kubernetes namespace from the URL, maps it to `X-Amz-Account-Id`, and strips the `/namespaces/{ns}/` segment | -| Wire format is flat JSON, not Kubernetes nested metadata | `transport/wire.go` — request/response adapter | +| Wire format is flat JSON, not Kubernetes nested metadata | `transport/bridge.go` — request/response adapter | ### `rest/config.go` — SDK configuration @@ -130,7 +130,7 @@ Every outbound request goes through `SigV4RoundTripper.RoundTrip`: 2. The request body is buffered, hashed (SHA-256), and restored so SigV4 can include the payload hash in the signature. 3. The request is signed with `aws/signer/v4` against the `execute-api` service. -### `transport/wire.go` — request/response adapter +### `transport/bridge.go` — request/response adapter The `Adapter` RoundTripper handles four transformations: @@ -154,7 +154,7 @@ Both single-object and list (`{"items": [...]}`) responses are handled. **Request rewriting** — the Kubernetes serializer produces nested metadata. The adapter flattens it back to the platform wire format before sending. For namespaced POST requests (e.g. nodepool create), the namespace segment encodes the parent cluster ID; the adapter injects it as `"cluster_id"` in the body before the SigV4 transport strips the namespace from the URL. -**Pagination rewrite** — `wrappers.ListOptions.Offset` is bridged by encoding the integer as a numeric string in `metav1.ListOptions.Continue`. The adapter detects this encoding and rewrites `?continue=N` to `?offset=N` so the platform API receives the parameter it expects. +**Pagination rewrite** — `platform.ListOptions.Offset` is bridged by encoding the integer as a numeric string in `metav1.ListOptions.Continue`. The adapter detects this encoding and rewrites `?continue=N` to `?offset=N` so the platform API receives the parameter it expects. **Error response translation** — platform API errors use a different envelope from `metav1.Status`: @@ -172,9 +172,9 @@ client-go's `transformResponse` cannot parse this format and falls back to `Stat This ensures `k8s.io/apimachinery/pkg/api/errors` helpers (`IsNotFound`, `IsForbidden`, etc.) classify errors correctly and that callers receive the full server message rather than a generic unknown error. -### `wrappers/options.go` — platform-scoped option types +### `platform/options.go` — platform-scoped option types -Rather than exposing `metav1.GetOptions`, `metav1.ListOptions`, etc. (which carry Kubernetes-specific fields the platform API does not honor), the wrappers package defines its own minimal option types: +Rather than exposing `metav1.GetOptions`, `metav1.ListOptions`, etc. (which carry Kubernetes-specific fields the platform API does not honor), the platform package defines its own minimal option types: ```go type GetOptions struct{} @@ -247,18 +247,18 @@ An empty version string causes `metav1.AddToGroupVersion` to panic when register `client-gen` derives the method name from the directory structure. Because the types live directly under `v1alpha1/` with no parent group directory, it generates `V1alpha1()` (not `HyperfleetV1alpha1()`). The `hyperfleet.go` wrapper renames it and wraps it with the generated wrappers client: ```go -func (c *Clientset) HyperfleetV1alpha1() wrappers.V1alpha1Interface { - return wrappers.NewV1alpha1Client(c.generated.V1alpha1()) +func (c *Clientset) HyperfleetV1alpha1() platform.V1alpha1PublicInterface { + return platform.NewV1alpha1PublicClient(c.generated.V1alpha1()) } ``` --- -### `wrappers/wire_wrappers_generated.go` — platform interface + WaitUntil +### `platform/bridge_wrappers_generated.go` — platform interface + WaitUntil -The Hyperfleet platform API does not support the Kubernetes watch stream protocol. The `wrappers` package provides generated wrapper types that: +The Hyperfleet platform API does not support the Kubernetes watch stream protocol. The `platform` package provides generated wrapper types that: -1. Expose only the operations the platform API supports, using platform-specific option types from `wrappers/options.go`. +1. Expose only the operations the platform API supports, using platform-specific option types from `platform/options.go`. 2. Route `Update` calls by UID — the wrapper deep-copies the object and sets `Name = UID` before calling the inner client, so the generated client builds the PUT URL with the UID regardless of what the caller has in `metadata.name`. The `name` field in the body is discarded by the server's update DTO. 3. Add `WaitUntil` — a polling-based alternative to Watch that repeatedly calls `Get` and evaluates a caller-supplied condition. @@ -313,6 +313,6 @@ err := cs.HyperfleetV1alpha1().Clusters().WaitUntil( ## Testing ```bash -make test-clientset # unit tests: transport, wrappers (no external services) +make test-clientset # unit tests: transport, platform (no external services) make verify-clientset # regenerate and fail if output differs from committed files ``` From 63c8de5297a414d9ab02afe176a890de037aea52 Mon Sep 17 00:00:00 2001 From: Guilherme Branco Date: Tue, 11 Aug 2026 11:49:52 -0300 Subject: [PATCH 05/15] ROSAENG-62084 | feat: add generate-all and verify-all convenience targets generate-all: runs manifests, deepcopy, passthrough, conversion, clientset, and openapi in one pass verify-all: fails if any generated output (codegen, conversion, clientset, openapi) is out of date --- Makefile | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 7746e249..db0d10ce 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ 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 mod-tidy \ - manifests generate generate-clientset verify-clientset setup-envtest \ + manifests generate generate-all generate-clientset verify-clientset verify-all setup-envtest \ codegen-passthrough codegen-registry codegen-verify codegen verify-codegen \ codegen-conversion verify-conversion \ generate-openapi verify-openapi swagger-ui \ @@ -115,8 +115,10 @@ help: @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-all Run all code generators in one pass" + @echo " verify-all Fail if any generated output is out of date" + @echo " generate-clientset Generate typed client SDK from CRD types" + @echo " verify-clientset Fail if generated clientset is out of date" @echo " codegen-passthrough Generate passthrough types from HyperShift" @echo " codegen-registry Generate field metadata registry from markers" @echo " codegen-verify Verify codegen outputs compile" @@ -351,6 +353,10 @@ verify-codegen: codegen git diff --exit-code api/v1alpha1/zz_generated.deepcopy.go git diff --exit-code hack/api-codegen/pkg/registry/ +generate-all: manifests generate codegen-passthrough codegen-conversion generate-clientset generate-openapi + +verify-all: verify-codegen verify-conversion verify-clientset verify-openapi + CONVERSION_OUTPUT_DIR ?= platform-api/pkg/conversion/v1alpha1 CONVERSION_OUTPUT_PKG ?= github.com/openshift-online/rosa-hyperfleet-api/platform-api/pkg/conversion CONVERSION_CRD_PKG ?= github.com/openshift-online/rosa-hyperfleet-api/api/v1alpha1 From bb4657cf4d4b250d5b1c64859715152800dd7a05 Mon Sep 17 00:00:00 2001 From: Guilherme Branco Date: Tue, 11 Aug 2026 11:59:54 -0300 Subject: [PATCH 06/15] ROSAENG-62084 | chore: regenerate all codegen artifacts after bridge marker rename - Replace hostedclusterspec.passthrough.go with zz_generated.passthrough.go (passthrough-gen output) - Regenerate zz_generated.deepcopy.go: Configuration field type changed to v1beta1.ClusterConfiguration - Regenerate field_metadata registry, CRD bases, openapi.yaml, conversion types to reflect +bridge:field/watch/wait marker renames from +wire: --- api/v1alpha1/nodepool_types.go | 1 + api/v1alpha1/public/nodepool_types.go | 2 +- api/v1alpha1/public/openapi.yaml | 342 +- api/v1alpha1/zz_generated.deepcopy.go | 2 +- ...through.go => zz_generated.passthrough.go} | 51 +- .../pkg/registry/field_metadata.go | 412 --- .../pkg/registry/field_metadata.json | 412 --- .../crd/bases/hyperfleet.io_clusters.yaml | 2811 ++++++++++++++++- .../crd/bases/hyperfleet.io_nodepools.yaml | 3 +- platform-api/pkg/conversion/types.go | 77 - .../v1alpha1/controlplaneupgradepolicy.go | 62 + 11 files changed, 3082 insertions(+), 1093 deletions(-) rename api/v1alpha1/{hostedclusterspec.passthrough.go => zz_generated.passthrough.go} (91%) create mode 100644 platform-api/pkg/conversion/v1alpha1/controlplaneupgradepolicy.go diff --git a/api/v1alpha1/nodepool_types.go b/api/v1alpha1/nodepool_types.go index 5fed453f..776b204e 100644 --- a/api/v1alpha1/nodepool_types.go +++ b/api/v1alpha1/nodepool_types.go @@ -89,6 +89,7 @@ type NodePoolStatus struct { } // +genclient +// +bridge:field=cluster_id,meta=namespace // +bridge:field=name,meta=name // +bridge:field=id,meta=uid // +bridge:field=resource_version,meta=resourceVersion diff --git a/api/v1alpha1/public/nodepool_types.go b/api/v1alpha1/public/nodepool_types.go index d0b4ff48..b3c94d39 100644 --- a/api/v1alpha1/public/nodepool_types.go +++ b/api/v1alpha1/public/nodepool_types.go @@ -13,9 +13,9 @@ import ( // +kubebuilder:resource:scope=Namespaced // +kubebuilder:subresource:status // +genclient +// +bridge:field=cluster_id,meta=namespace // +bridge:field=name,meta=name // +bridge:field=id,meta=uid -// +bridge:field=cluster_id,meta=namespace // +bridge:field=resource_version,meta=resourceVersion // +bridge:field=generation,meta=generation // +bridge:watch=disabled diff --git a/api/v1alpha1/public/openapi.yaml b/api/v1alpha1/public/openapi.yaml index 9e7440df..e75462d9 100644 --- a/api/v1alpha1/public/openapi.yaml +++ b/api/v1alpha1/public/openapi.yaml @@ -3033,14 +3033,49 @@ components: additionalProperties: true description: HostedClusterSpecPassthrough mirrors HostedClusterSpec from upstream HyperShift properties: + additionalTrustBundle: + description: additionalTrustBundle is a local reference to a ConfigMap that must have a "ca-bundle.crt" key + type: object + x-kubernetes-map-type: atomic + auditWebhook: + description: auditWebhook contains metadata for configuring an audit webhook endpoint + type: object + x-kubernetes-map-type: atomic autoNode: description: autoNode specifies the configuration for automatic node provisioning and lifecycle management. type: object + autoscaling: + description: autoscaling specifies auto-scaling behavior that applies to all NodePools + type: object + x-kubernetes-validations: + - message: scaleDown can only be set when scaling is ScaleUpAndScaleDown + rule: 'self.scaling == ''ScaleUpAndScaleDown'' ? true : !has(self.scaleDown)' + capabilities: + description: capabilities allows for disabling optional components at cluster install time. + type: object + x-kubernetes-validations: + - message: Capabilities can not be both enabled and disabled at once. + rule: 'has(self.enabled) && has(self.disabled) ? self.enabled.all(e, !(e in self.disabled)) : true' channel: description: channel is an identifier for explicitly requesting that a non-default set of updates be applied to this cluster. type: string + clusterID: + description: clusterID uniquely identifies this cluster. This is expected to be an RFC4122 UUID value (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx in hexadecimal digits). + type: string configuration: $ref: '#/components/schemas/ClusterConfiguration' + controlPlaneRelease: + description: controlPlaneRelease is like spec.release but only for the components running on the management cluster. + type: object + controllerAvailabilityPolicy: + description: controllerAvailabilityPolicy specifies the availability policy applied to critical control plane components like the Kube API Server. + enum: + - HighlyAvailable + - SingleReplica + type: string + dns: + description: dns specifies the DNS configuration for the hosted cluster ingress. + type: object etcd: description: etcd specifies configuration for the control plane etcd cluster. The type: object @@ -3078,11 +3113,27 @@ components: required: - source type: object - maxItems: 50 type: array + infraID: + description: infraID is a globally unique identifier for the cluster. + type: string + infrastructureAvailabilityPolicy: + description: infrastructureAvailabilityPolicy specifies the availability policy applied to infrastructure services which run on the hosted cluster data plane like the ingress controller and image registry controller. + enum: + - HighlyAvailable + - SingleReplica + type: string issuerURL: description: issuerURL is an OIDC issuer URL which will be used as the issuer in all type: string + kubeAPIServerDNSName: + description: kubeAPIServerDNSName specifies a desired DNS name to resolve to the KAS. + type: string + labels: + additionalProperties: + type: string + description: labels when specified, define what custom labels are added to the hcp pods. + type: object networking: description: networking specifies network configuration for the hosted cluster. type: object @@ -3091,6 +3142,17 @@ components: rule: (!has(self.machineNetwork) && self.clusterNetwork.all(c, self.serviceNetwork.all(s, c.cidr != s.cidr)) || (has(self.machineNetwork) && (self.machineNetwork.all(m, self.clusterNetwork.all(c, m.cidr != c.cidr)) && self.machineNetwork.all(m, self.serviceNetwork.all(s, m.cidr != s.cidr)) && self.clusterNetwork.all(c, self.serviceNetwork.all(s, c.cidr != s.cidr))))) - message: allocateNodeCIDRs can only be set to Enabled when networkType is 'Other' rule: 'has(self.allocateNodeCIDRs) && self.allocateNodeCIDRs == ''Enabled'' ? self.networkType == ''Other'' : true' + nodeSelector: + additionalProperties: + type: string + description: nodeSelector when specified, is propagated to all control plane Deployments and Stateful sets running management side. + type: object + olmCatalogPlacement: + description: olmCatalogPlacement specifies the placement of OLM catalog components. By default, + enum: + - management + - guest + type: string operatorConfiguration: description: operatorConfiguration specifies configuration for individual OCP operators in the cluster. type: object @@ -3100,23 +3162,241 @@ components: platform: description: platform specifies the underlying infrastructure provider for the cluster type: object + pullSecret: + description: pullSecret is a local reference to a Secret that must have a ".dockerconfigjson" key whose content must be a valid Openshift pull secret JSON. + type: object + x-kubernetes-map-type: atomic release: description: release specifies the desired OCP release payload for all the hosted cluster components. type: object + secretEncryption: + description: secretEncryption specifies a Kubernetes secret encryption strategy for the + type: object + serviceAccountSigningKey: + description: serviceAccountSigningKey is a local reference to a secret that must have a "key" key whose content must be the private key + type: object + x-kubernetes-map-type: atomic + services: + description: services specifies how individual control plane services endpoints are published for consumption. + items: + description: |- + ServicePublishingStrategyMapping specifies how individual control plane services endpoints are published for consumption. + This includes APIServer;OAuthServer;Konnectivity;Ignition. + If a given service is not present in this list, it will be exposed publicly by default. + properties: + service: + description: |- + service identifies the type of service being published. + It can be APIServer;OAuthServer;Konnectivity;Ignition + OVNSbDb;OIDC are no-op and kept for backward compatibility. + This field is immutable. + enum: + - APIServer + - OAuthServer + - OIDC + - Konnectivity + - Ignition + - OVNSbDb + type: string + servicePublishingStrategy: + description: servicePublishingStrategy specifies how to publish a service endpoint. + properties: + loadBalancer: + description: loadBalancer configures exposing a service using a dedicated LoadBalancer. + properties: + hostname: + description: |- + hostname is the name of the DNS record that will be created pointing to the LoadBalancer and passed through to consumers of the service. + If omitted, the value will be inferred from the corev1.Service Load balancer type .status. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: hostname must be a valid domain name (e.g., example.com) + rule: self.matches('^(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,}$') + type: object + nodePort: + description: nodePort configures exposing a service using a NodePort. + properties: + address: + description: address is the host/ip that the NodePort service is exposed over. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: address must be a valid hostname, IPv4, or IPv6 address + rule: self.matches('^(([a-zA-Z0-9][-a-zA-Z0-9]*\\.)+[a-zA-Z]{2,}|localhost)$') || self.matches('^((\\d{1,3}\\.){3}\\d{1,3})$') || self.matches('^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$') + port: + description: |- + port is the port of the NodePort service. If <=0, the port is dynamically + assigned when the service is created. + format: int32 + type: integer + required: + - address + type: object + route: + description: |- + route configures exposing a service using a Route through and an ingress controller behind a cloud Load Balancer. + The specifics of the setup are platform dependent. + properties: + hostname: + description: |- + hostname is the name of the DNS record that will be created pointing to the Route and passed through to consumers of the service. + If omitted, the value will be inferred from management ingress.Spec.Domain. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: hostname must be a valid domain name (e.g., example.com) + rule: self.matches('^(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,}$') + type: object + type: + description: |- + type is the publishing strategy used for the service. + It can be LoadBalancer;NodePort;Route;None;S3 + enum: + - LoadBalancer + - NodePort + - Route + - None + - S3 + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: nodePort is required when type is NodePort, and forbidden otherwise + rule: 'self.type == ''NodePort'' ? has(self.nodePort) : !has(self.nodePort)' + - message: only route is allowed when type is Route, and forbidden otherwise + rule: 'self.type == ''Route'' ? !has(self.nodePort) && !has(self.loadBalancer) : !has(self.route)' + - message: only loadBalancer is required when type is LoadBalancer, and forbidden otherwise + rule: 'self.type == ''LoadBalancer'' ? !has(self.nodePort) && !has(self.route) : !has(self.loadBalancer)' + - message: None does not allowed any configuration for loadBalancer, nodePort, or route + rule: 'self.type == ''None'' ? !has(self.nodePort) && !has(self.route) && !has(self.loadBalancer) : true' + - message: S3 does not allowed any configuration for loadBalancer, nodePort, or route + rule: 'self.type == ''S3'' ? !has(self.nodePort) && !has(self.route) && !has(self.loadBalancer) : true' + required: + - service + - servicePublishingStrategy + type: object + type: array + sshKey: + description: sshKey is a local reference to a Secret that must have a "id_rsa.pub" key whose content must be the public part of 1..N SSH keys. + type: object + x-kubernetes-map-type: atomic + tolerations: + description: tolerations when specified, define what custom tolerations are added to the hcp pods. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists, Equal, Lt, and Gt. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + Lt and Gt perform numeric comparisons (requires feature gate TaintTolerationComparisonOperators). + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + updateService: + description: updateService may be used to specify the preferred upstream update service. + type: string required: + - autoNode - etcd - fips - networking - platform + - pullSecret - release + - services + - sshKey type: object NodePoolSpecPassthrough: additionalProperties: true description: NodePoolSpecPassthrough mirrors NodePoolSpec from upstream HyperShift properties: + arch: + description: arch is the preferred processor architecture for the NodePool. Different platforms might have different supported architectures. + type: string + autoScaling: + description: autoScaling specifies auto-scaling behavior for the NodePool. + type: object + x-kubernetes-validations: + - message: max must be equal or greater than min + rule: self.max >= self.min clusterName: description: clusterName is the name of the HostedCluster this NodePool belongs to. type: string + config: + description: config is a list of references to ConfigMaps containing serialized + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + type: array + management: + description: management specifies behavior for managing nodes in the pool, such as + type: object + x-kubernetes-validations: + - message: The 'inPlace' field can only be set when 'upgradeType' is 'InPlace' + rule: '!has(self.inPlace) || self.upgradeType == ''InPlace''' + nodeDrainTimeout: + description: nodeDrainTimeout is the maximum amount of time that the controller will spend on retrying to drain a node until it succeeds. + type: string + nodeLabels: + additionalProperties: + type: string + description: nodeLabels propagates a list of labels to Nodes, only once on creation. + type: object + nodeVolumeDetachTimeout: + description: nodeVolumeDetachTimeout is the maximum amount of time that the controller will spend on detaching volumes from a node. + type: string + osImageStream: + description: osImageStream specifies an OS stream to be used for nodes in this pool. + type: object + pausedUntil: + description: pausedUntil is a field that can be used to pause reconciliation on the NodePool controller. Resulting in any change to the NodePool being ignored. + type: string platform: description: platform specifies the underlying infrastructure provider for the NodePool type: object @@ -3127,8 +3407,68 @@ components: description: replicas is the desired number of nodes the pool should maintain. If unset, the controller default value is 0. format: int32 type: integer + taints: + description: taints if specified, propagates a list of taints to Nodes, only once on creation. + items: + description: |- + taint is as v1 Core but without TimeAdded. + https://github.com/kubernetes/kubernetes/blob/ed8cad1e80d096257921908a52ac69cf1f41a098/staging/src/k8s.io/api/core/v1/types.go#L3037-L3053 + Validation replicates the same validation as the upstream https://github.com/kubernetes/kubernetes/blob/9a2a7537f035969a68e432b4cc276dbce8ce1735/pkg/util/taints/taints.go#L273. + See also https://kubernetes.io/docs/concepts/overview/working-with-objects/names/. + properties: + effect: + description: |- + effect is the effect of the taint on pods + that do not tolerate the taint. + Valid effects are NoSchedule, PreferNoSchedule and NoExecute. + enum: + - NoSchedule + - PreferNoSchedule + - NoExecute + type: string + key: + description: key is the taint key to be applied to a node. + maxLength: 253 + minLength: 1 + type: string + x-kubernetes-validations: + - message: key must be a qualified name with an optional subdomain prefix e.g. example.com/MyName + rule: self.matches('^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/)?[A-Za-z0-9]([-A-Za-z0-9_.]{0,61}[A-Za-z0-9])?$') + value: + description: value is the taint value corresponding to the taint key. + maxLength: 253 + type: string + x-kubernetes-validations: + - message: Value must start and end with alphanumeric characters and can only contain '-', '_', '.' in the middle + rule: self.matches('^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$') + required: + - effect + - key + type: object + type: array + tuningConfig: + description: tuningConfig is a list of references to ConfigMaps containing serialized + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + type: array required: - clusterName + - management + - osImageStream - platform - release type: object diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index e3ed3108..4a6cea83 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -376,7 +376,7 @@ func (in *HostedClusterSpecPassthrough) DeepCopyInto(out *HostedClusterSpecPasst } if in.Configuration != nil { in, out := &in.Configuration, &out.Configuration - *out = new(ClusterConfiguration) + *out = new(v1beta1.ClusterConfiguration) (*in).DeepCopyInto(*out) } if in.OperatorConfiguration != nil { diff --git a/api/v1alpha1/hostedclusterspec.passthrough.go b/api/v1alpha1/zz_generated.passthrough.go similarity index 91% rename from api/v1alpha1/hostedclusterspec.passthrough.go rename to api/v1alpha1/zz_generated.passthrough.go index 741f97ce..35472e9a 100644 --- a/api/v1alpha1/hostedclusterspec.passthrough.go +++ b/api/v1alpha1/zz_generated.passthrough.go @@ -12,8 +12,8 @@ import ( // HostedClusterSpecPassthrough mirrors HostedClusterSpec from upstream HyperShift type HostedClusterSpecPassthrough struct { // release specifies the desired OCP release payload for all the hosted cluster components. - // +k8s:openapi-gen=true - // +hyperfleet:write-mode=mutable + // +k8s:openapi-gen=false + // +hyperfleet:write-mode=service-set Release hypershiftv1beta1.Release `json:"release"` // controlPlaneRelease is like spec.release but only for the components running on the management cluster. // +k8s:openapi-gen=false @@ -36,8 +36,8 @@ type HostedClusterSpecPassthrough struct { // +hyperfleet:write-mode=service-set Channel string `json:"channel,omitempty"` // platform specifies the underlying infrastructure provider for the cluster - // +k8s:openapi-gen=true - // +hyperfleet:write-mode=mutable + // +k8s:openapi-gen=false + // +hyperfleet:write-mode=service-set Platform hypershiftv1beta1.PlatformSpec `json:"platform"` // kubeAPIServerDNSName specifies a desired DNS name to resolve to the KAS. // +k8s:openapi-gen=false @@ -56,8 +56,8 @@ type HostedClusterSpecPassthrough struct { // +hyperfleet:write-mode=service-set DNS hypershiftv1beta1.DNSSpec `json:"dns,omitempty"` // networking specifies network configuration for the hosted cluster. - // +k8s:openapi-gen=true - // +hyperfleet:write-mode=mutable + // +k8s:openapi-gen=false + // +hyperfleet:write-mode=service-set Networking hypershiftv1beta1.ClusterNetworking `json:"networking"` // autoscaling specifies auto-scaling behavior that applies to all NodePools // +k8s:openapi-gen=false @@ -66,16 +66,14 @@ type HostedClusterSpecPassthrough struct { // autoNode specifies the configuration for automatic node provisioning and lifecycle management. // +k8s:openapi-gen=true // +hyperfleet:write-mode=service-set - // +optional AutoNode hypershiftv1beta1.AutoNode `json:"autoNode,omitzero"` // etcd specifies configuration for the control plane etcd cluster. The - // +k8s:openapi-gen=true - // +hyperfleet:write-mode=mutable + // +k8s:openapi-gen=false + // +hyperfleet:write-mode=service-set Etcd hypershiftv1beta1.EtcdSpec `json:"etcd"` // services specifies how individual control plane services endpoints are published for consumption. // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set - // +kubebuilder:validation:MaxItems=10 Services []hypershiftv1beta1.ServicePublishingStrategyMapping `json:"services"` // pullSecret is a local reference to a Secret that must have a ".dockerconfigjson" key whose content must be a valid Openshift pull secret JSON. // +k8s:openapi-gen=false @@ -86,8 +84,8 @@ type HostedClusterSpecPassthrough struct { // +hyperfleet:write-mode=service-set SSHKey corev1.LocalObjectReference `json:"sshKey"` // issuerURL is an OIDC issuer URL which will be used as the issuer in all - // +k8s:openapi-gen=true - // +hyperfleet:write-mode=mutable + // +k8s:openapi-gen=false + // +hyperfleet:write-mode=service-set IssuerURL string `json:"issuerURL,omitempty"` // serviceAccountSigningKey is a local reference to a secret that must have a "key" key whose content must be the private key // +k8s:openapi-gen=false @@ -96,7 +94,7 @@ type HostedClusterSpecPassthrough struct { // configuration specifies configuration for individual OCP components in the // +k8s:openapi-gen=true // +hyperfleet:write-mode=service-set - Configuration *ClusterConfiguration `json:"configuration,omitempty"` + Configuration *hypershiftv1beta1.ClusterConfiguration `json:"configuration,omitempty"` // operatorConfiguration specifies configuration for individual OCP operators in the cluster. // +k8s:openapi-gen=true // +hyperfleet:write-mode=service-set @@ -106,9 +104,8 @@ type HostedClusterSpecPassthrough struct { // +hyperfleet:write-mode=service-set AuditWebhook *corev1.LocalObjectReference `json:"auditWebhook,omitempty"` // imageContentSources specifies image mirrors that can be used by cluster - // +k8s:openapi-gen=true - // +hyperfleet:write-mode=mutable - // +kubebuilder:validation:MaxItems=50 + // +k8s:openapi-gen=false + // +hyperfleet:write-mode=service-set ImageContentSources []hypershiftv1beta1.ImageContentSource `json:"imageContentSources,omitempty"` // additionalTrustBundle is a local reference to a ConfigMap that must have a "ca-bundle.crt" key // +k8s:openapi-gen=false @@ -133,17 +130,14 @@ type HostedClusterSpecPassthrough struct { // nodeSelector when specified, is propagated to all control plane Deployments and Stateful sets running management side. // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set - // +kubebuilder:validation:MaxProperties=100 NodeSelector map[string]string `json:"nodeSelector,omitempty"` // tolerations when specified, define what custom tolerations are added to the hcp pods. // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set - // +kubebuilder:validation:MaxItems=50 Tolerations []corev1.Toleration `json:"tolerations,omitempty"` // labels when specified, define what custom labels are added to the hcp pods. // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set - // +kubebuilder:validation:MaxProperties=100 Labels map[string]string `json:"labels,omitempty"` // capabilities allows for disabling optional components at cluster install time. // +k8s:openapi-gen=false @@ -154,20 +148,20 @@ type HostedClusterSpecPassthrough struct { // NodePoolSpecPassthrough mirrors NodePoolSpec from upstream HyperShift type NodePoolSpecPassthrough struct { // clusterName is the name of the HostedCluster this NodePool belongs to. - // +k8s:openapi-gen=true - // +hyperfleet:write-mode=mutable + // +k8s:openapi-gen=false + // +hyperfleet:write-mode=service-set ClusterName string `json:"clusterName"` // release specifies the OCP release used for this NodePool. It drives the machine ignition configuration (including - // +k8s:openapi-gen=true - // +hyperfleet:write-mode=mutable + // +k8s:openapi-gen=false + // +hyperfleet:write-mode=service-set Release hypershiftv1beta1.Release `json:"release"` // platform specifies the underlying infrastructure provider for the NodePool - // +k8s:openapi-gen=true - // +hyperfleet:write-mode=mutable + // +k8s:openapi-gen=false + // +hyperfleet:write-mode=service-set Platform hypershiftv1beta1.NodePoolPlatform `json:"platform"` // replicas is the desired number of nodes the pool should maintain. If unset, the controller default value is 0. - // +k8s:openapi-gen=true - // +hyperfleet:write-mode=mutable + // +k8s:openapi-gen=false + // +hyperfleet:write-mode=service-set Replicas *int32 `json:"replicas,omitempty"` // management specifies behavior for managing nodes in the pool, such as // +k8s:openapi-gen=false @@ -192,12 +186,10 @@ type NodePoolSpecPassthrough struct { // nodeLabels propagates a list of labels to Nodes, only once on creation. // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set - // +kubebuilder:validation:MaxProperties=100 NodeLabels map[string]string `json:"nodeLabels,omitempty"` // taints if specified, propagates a list of taints to Nodes, only once on creation. // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set - // +kubebuilder:validation:MaxItems=50 Taints []hypershiftv1beta1.Taint `json:"taints,omitempty"` // pausedUntil is a field that can be used to pause reconciliation on the NodePool controller. Resulting in any change to the NodePool being ignored. // +k8s:openapi-gen=false @@ -214,6 +206,5 @@ type NodePoolSpecPassthrough struct { // osImageStream specifies an OS stream to be used for nodes in this pool. // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set - // +optional OSImageStream hypershiftv1beta1.OSImageStreamReference `json:"osImageStream,omitzero"` } diff --git a/hack/api-codegen/pkg/registry/field_metadata.go b/hack/api-codegen/pkg/registry/field_metadata.go index c0ca523d..18583288 100644 --- a/hack/api-codegen/pkg/registry/field_metadata.go +++ b/hack/api-codegen/pkg/registry/field_metadata.go @@ -321,347 +321,6 @@ var FieldRegistry = map[string]FieldMeta{ FieldPath: "spec.expirationTimestamp", WriteMode: Mutable, }, - "spec.hostedCluster.additionalTrustBundle": { - FieldPath: "spec.hostedCluster.additionalTrustBundle", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.auditWebhook": { - FieldPath: "spec.hostedCluster.auditWebhook", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.autoNode": { - FieldPath: "spec.hostedCluster.autoNode", - WriteMode: ServiceSet, - }, - "spec.hostedCluster.autoscaling": { - FieldPath: "spec.hostedCluster.autoscaling", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.capabilities": { - FieldPath: "spec.hostedCluster.capabilities", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.channel": { - FieldPath: "spec.hostedCluster.channel", - WriteMode: ServiceSet, - }, - "spec.hostedCluster.clusterID": { - FieldPath: "spec.hostedCluster.clusterID", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.configuration": { - FieldPath: "spec.hostedCluster.configuration", - WriteMode: ServiceSet, - }, - "spec.hostedCluster.configuration.apiServer": { - FieldPath: "spec.hostedCluster.configuration.apiServer", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.configuration.authentication": { - FieldPath: "spec.hostedCluster.configuration.authentication", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.configuration.featureGate": { - FieldPath: "spec.hostedCluster.configuration.featureGate", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.configuration.image": { - FieldPath: "spec.hostedCluster.configuration.image", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.configuration.ingress": { - FieldPath: "spec.hostedCluster.configuration.ingress", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.configuration.kubelet": { - FieldPath: "spec.hostedCluster.configuration.kubelet", - WriteMode: ServiceSet, - }, - "spec.hostedCluster.configuration.kubelet.allowedUnsafeSysctls": { - FieldPath: "spec.hostedCluster.configuration.kubelet.allowedUnsafeSysctls", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.configuration.kubelet.containerLogMaxFiles": { - FieldPath: "spec.hostedCluster.configuration.kubelet.containerLogMaxFiles", - WriteMode: Mutable, - }, - "spec.hostedCluster.configuration.kubelet.containerLogMaxSize": { - FieldPath: "spec.hostedCluster.configuration.kubelet.containerLogMaxSize", - WriteMode: Mutable, - }, - "spec.hostedCluster.configuration.kubelet.cpuManagerPolicy": { - FieldPath: "spec.hostedCluster.configuration.kubelet.cpuManagerPolicy", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.configuration.kubelet.cpuManagerPolicyOptions": { - FieldPath: "spec.hostedCluster.configuration.kubelet.cpuManagerPolicyOptions", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.configuration.kubelet.cpuManagerReconcilePeriod": { - FieldPath: "spec.hostedCluster.configuration.kubelet.cpuManagerReconcilePeriod", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.configuration.kubelet.evictionHard": { - FieldPath: "spec.hostedCluster.configuration.kubelet.evictionHard", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.configuration.kubelet.evictionSoft": { - FieldPath: "spec.hostedCluster.configuration.kubelet.evictionSoft", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.configuration.kubelet.evictionSoftGracePeriod": { - FieldPath: "spec.hostedCluster.configuration.kubelet.evictionSoftGracePeriod", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.configuration.kubelet.imageGCHighThresholdPercent": { - FieldPath: "spec.hostedCluster.configuration.kubelet.imageGCHighThresholdPercent", - WriteMode: Mutable, - }, - "spec.hostedCluster.configuration.kubelet.imageGCLowThresholdPercent": { - FieldPath: "spec.hostedCluster.configuration.kubelet.imageGCLowThresholdPercent", - WriteMode: Mutable, - }, - "spec.hostedCluster.configuration.kubelet.imageMinimumGCAge": { - FieldPath: "spec.hostedCluster.configuration.kubelet.imageMinimumGCAge", - WriteMode: Mutable, - }, - "spec.hostedCluster.configuration.kubelet.kubeReserved": { - FieldPath: "spec.hostedCluster.configuration.kubelet.kubeReserved", - WriteMode: Immutable, - }, - "spec.hostedCluster.configuration.kubelet.maxPods": { - FieldPath: "spec.hostedCluster.configuration.kubelet.maxPods", - WriteMode: Mutable, - }, - "spec.hostedCluster.configuration.kubelet.memoryThrottlingFactor": { - FieldPath: "spec.hostedCluster.configuration.kubelet.memoryThrottlingFactor", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.configuration.kubelet.podPidsLimit": { - FieldPath: "spec.hostedCluster.configuration.kubelet.podPidsLimit", - WriteMode: Mutable, - }, - "spec.hostedCluster.configuration.kubelet.registryBurst": { - FieldPath: "spec.hostedCluster.configuration.kubelet.registryBurst", - WriteMode: Mutable, - FeatureGate: "HyperFleetKubeletAdvanced", - }, - "spec.hostedCluster.configuration.kubelet.registryPullQPS": { - FieldPath: "spec.hostedCluster.configuration.kubelet.registryPullQPS", - WriteMode: Mutable, - FeatureGate: "HyperFleetKubeletAdvanced", - }, - "spec.hostedCluster.configuration.kubelet.serializeImagePulls": { - FieldPath: "spec.hostedCluster.configuration.kubelet.serializeImagePulls", - WriteMode: Mutable, - FeatureGate: "HyperFleetKubeletAdvanced", - }, - "spec.hostedCluster.configuration.kubelet.streamingConnectionIdleTimeout": { - FieldPath: "spec.hostedCluster.configuration.kubelet.streamingConnectionIdleTimeout", - WriteMode: Mutable, - }, - "spec.hostedCluster.configuration.kubelet.systemReserved": { - FieldPath: "spec.hostedCluster.configuration.kubelet.systemReserved", - WriteMode: Immutable, - }, - "spec.hostedCluster.configuration.kubelet.topologyManagerPolicy": { - FieldPath: "spec.hostedCluster.configuration.kubelet.topologyManagerPolicy", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.configuration.kubelet.topologyManagerScope": { - FieldPath: "spec.hostedCluster.configuration.kubelet.topologyManagerScope", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.configuration.machineConfig": { - FieldPath: "spec.hostedCluster.configuration.machineConfig", - WriteMode: ServiceSet, - }, - "spec.hostedCluster.configuration.machineConfig.allowedKernelArguments": { - FieldPath: "spec.hostedCluster.configuration.machineConfig.allowedKernelArguments", - WriteMode: Immutable, - FeatureGate: "HyperFleetMachineConfig", - }, - "spec.hostedCluster.configuration.machineConfig.extensions": { - FieldPath: "spec.hostedCluster.configuration.machineConfig.extensions", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.configuration.machineConfig.files": { - FieldPath: "spec.hostedCluster.configuration.machineConfig.files", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.configuration.machineConfig.kernelArguments": { - FieldPath: "spec.hostedCluster.configuration.machineConfig.kernelArguments", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.configuration.machineConfig.kernelType": { - FieldPath: "spec.hostedCluster.configuration.machineConfig.kernelType", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.configuration.machineConfig.systemdUnits": { - FieldPath: "spec.hostedCluster.configuration.machineConfig.systemdUnits", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.configuration.network": { - FieldPath: "spec.hostedCluster.configuration.network", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.configuration.oauth": { - FieldPath: "spec.hostedCluster.configuration.oauth", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.configuration.proxy": { - FieldPath: "spec.hostedCluster.configuration.proxy", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.configuration.scheduler": { - FieldPath: "spec.hostedCluster.configuration.scheduler", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.controlPlaneRelease": { - FieldPath: "spec.hostedCluster.controlPlaneRelease", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.controllerAvailabilityPolicy": { - FieldPath: "spec.hostedCluster.controllerAvailabilityPolicy", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.dns": { - FieldPath: "spec.hostedCluster.dns", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.etcd": { - FieldPath: "spec.hostedCluster.etcd", - WriteMode: Mutable, - }, - "spec.hostedCluster.fips": { - FieldPath: "spec.hostedCluster.fips", - WriteMode: ServiceSet, - }, - "spec.hostedCluster.imageContentSources": { - FieldPath: "spec.hostedCluster.imageContentSources", - WriteMode: Mutable, - }, - "spec.hostedCluster.infraID": { - FieldPath: "spec.hostedCluster.infraID", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.infrastructureAvailabilityPolicy": { - FieldPath: "spec.hostedCluster.infrastructureAvailabilityPolicy", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.issuerURL": { - FieldPath: "spec.hostedCluster.issuerURL", - WriteMode: Mutable, - }, - "spec.hostedCluster.kubeAPIServerDNSName": { - FieldPath: "spec.hostedCluster.kubeAPIServerDNSName", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.labels": { - FieldPath: "spec.hostedCluster.labels", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.networking": { - FieldPath: "spec.hostedCluster.networking", - WriteMode: Mutable, - }, - "spec.hostedCluster.nodeSelector": { - FieldPath: "spec.hostedCluster.nodeSelector", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.olmCatalogPlacement": { - FieldPath: "spec.hostedCluster.olmCatalogPlacement", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.operatorConfiguration": { - FieldPath: "spec.hostedCluster.operatorConfiguration", - WriteMode: ServiceSet, - }, - "spec.hostedCluster.pausedUntil": { - FieldPath: "spec.hostedCluster.pausedUntil", - WriteMode: ServiceSet, - }, - "spec.hostedCluster.platform": { - FieldPath: "spec.hostedCluster.platform", - WriteMode: Mutable, - }, - "spec.hostedCluster.pullSecret": { - FieldPath: "spec.hostedCluster.pullSecret", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.release": { - FieldPath: "spec.hostedCluster.release", - WriteMode: Mutable, - }, - "spec.hostedCluster.secretEncryption": { - FieldPath: "spec.hostedCluster.secretEncryption", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.serviceAccountSigningKey": { - FieldPath: "spec.hostedCluster.serviceAccountSigningKey", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.services": { - FieldPath: "spec.hostedCluster.services", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.sshKey": { - FieldPath: "spec.hostedCluster.sshKey", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.tolerations": { - FieldPath: "spec.hostedCluster.tolerations", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.hostedCluster.updateService": { - FieldPath: "spec.hostedCluster.updateService", - WriteMode: ServiceSet, - Hidden: true, - }, "spec.internalId": { FieldPath: "spec.internalId", WriteMode: ServiceSet, @@ -676,77 +335,6 @@ var FieldRegistry = map[string]FieldMeta{ FieldPath: "spec.labels", WriteMode: Mutable, }, - "spec.nodePool.arch": { - FieldPath: "spec.nodePool.arch", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.nodePool.autoScaling": { - FieldPath: "spec.nodePool.autoScaling", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.nodePool.clusterName": { - FieldPath: "spec.nodePool.clusterName", - WriteMode: Mutable, - }, - "spec.nodePool.config": { - FieldPath: "spec.nodePool.config", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.nodePool.management": { - FieldPath: "spec.nodePool.management", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.nodePool.nodeDrainTimeout": { - FieldPath: "spec.nodePool.nodeDrainTimeout", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.nodePool.nodeLabels": { - FieldPath: "spec.nodePool.nodeLabels", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.nodePool.nodeVolumeDetachTimeout": { - FieldPath: "spec.nodePool.nodeVolumeDetachTimeout", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.nodePool.osImageStream": { - FieldPath: "spec.nodePool.osImageStream", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.nodePool.pausedUntil": { - FieldPath: "spec.nodePool.pausedUntil", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.nodePool.platform": { - FieldPath: "spec.nodePool.platform", - WriteMode: Mutable, - }, - "spec.nodePool.release": { - FieldPath: "spec.nodePool.release", - WriteMode: Mutable, - }, - "spec.nodePool.replicas": { - FieldPath: "spec.nodePool.replicas", - WriteMode: Mutable, - }, - "spec.nodePool.taints": { - FieldPath: "spec.nodePool.taints", - WriteMode: ServiceSet, - Hidden: true, - }, - "spec.nodePool.tuningConfig": { - FieldPath: "spec.nodePool.tuningConfig", - WriteMode: ServiceSet, - Hidden: true, - }, "spec.properties": { FieldPath: "spec.properties", WriteMode: Mutable, diff --git a/hack/api-codegen/pkg/registry/field_metadata.json b/hack/api-codegen/pkg/registry/field_metadata.json index 43aa0ae1..d83072b8 100644 --- a/hack/api-codegen/pkg/registry/field_metadata.json +++ b/hack/api-codegen/pkg/registry/field_metadata.json @@ -300,347 +300,6 @@ "fieldPath": "spec.expirationTimestamp", "writeMode": "mutable" }, - { - "fieldPath": "spec.hostedCluster.additionalTrustBundle", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.auditWebhook", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.autoNode", - "writeMode": "service-set" - }, - { - "fieldPath": "spec.hostedCluster.autoscaling", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.capabilities", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.channel", - "writeMode": "service-set" - }, - { - "fieldPath": "spec.hostedCluster.clusterID", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.configuration", - "writeMode": "service-set" - }, - { - "fieldPath": "spec.hostedCluster.configuration.apiServer", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.configuration.authentication", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.configuration.featureGate", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.configuration.image", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.configuration.ingress", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.configuration.kubelet", - "writeMode": "service-set" - }, - { - "fieldPath": "spec.hostedCluster.configuration.kubelet.allowedUnsafeSysctls", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.configuration.kubelet.containerLogMaxFiles", - "writeMode": "mutable" - }, - { - "fieldPath": "spec.hostedCluster.configuration.kubelet.containerLogMaxSize", - "writeMode": "mutable" - }, - { - "fieldPath": "spec.hostedCluster.configuration.kubelet.cpuManagerPolicy", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.configuration.kubelet.cpuManagerPolicyOptions", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.configuration.kubelet.cpuManagerReconcilePeriod", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.configuration.kubelet.evictionHard", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.configuration.kubelet.evictionSoft", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.configuration.kubelet.evictionSoftGracePeriod", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.configuration.kubelet.imageGCHighThresholdPercent", - "writeMode": "mutable" - }, - { - "fieldPath": "spec.hostedCluster.configuration.kubelet.imageGCLowThresholdPercent", - "writeMode": "mutable" - }, - { - "fieldPath": "spec.hostedCluster.configuration.kubelet.imageMinimumGCAge", - "writeMode": "mutable" - }, - { - "fieldPath": "spec.hostedCluster.configuration.kubelet.kubeReserved", - "writeMode": "immutable" - }, - { - "fieldPath": "spec.hostedCluster.configuration.kubelet.maxPods", - "writeMode": "mutable" - }, - { - "fieldPath": "spec.hostedCluster.configuration.kubelet.memoryThrottlingFactor", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.configuration.kubelet.podPidsLimit", - "writeMode": "mutable" - }, - { - "fieldPath": "spec.hostedCluster.configuration.kubelet.registryBurst", - "writeMode": "mutable", - "featureGate": "HyperFleetKubeletAdvanced" - }, - { - "fieldPath": "spec.hostedCluster.configuration.kubelet.registryPullQPS", - "writeMode": "mutable", - "featureGate": "HyperFleetKubeletAdvanced" - }, - { - "fieldPath": "spec.hostedCluster.configuration.kubelet.serializeImagePulls", - "writeMode": "mutable", - "featureGate": "HyperFleetKubeletAdvanced" - }, - { - "fieldPath": "spec.hostedCluster.configuration.kubelet.streamingConnectionIdleTimeout", - "writeMode": "mutable" - }, - { - "fieldPath": "spec.hostedCluster.configuration.kubelet.systemReserved", - "writeMode": "immutable" - }, - { - "fieldPath": "spec.hostedCluster.configuration.kubelet.topologyManagerPolicy", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.configuration.kubelet.topologyManagerScope", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.configuration.machineConfig", - "writeMode": "service-set" - }, - { - "fieldPath": "spec.hostedCluster.configuration.machineConfig.allowedKernelArguments", - "writeMode": "immutable", - "featureGate": "HyperFleetMachineConfig" - }, - { - "fieldPath": "spec.hostedCluster.configuration.machineConfig.extensions", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.configuration.machineConfig.files", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.configuration.machineConfig.kernelArguments", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.configuration.machineConfig.kernelType", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.configuration.machineConfig.systemdUnits", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.configuration.network", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.configuration.oauth", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.configuration.proxy", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.configuration.scheduler", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.controlPlaneRelease", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.controllerAvailabilityPolicy", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.dns", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.etcd", - "writeMode": "mutable" - }, - { - "fieldPath": "spec.hostedCluster.fips", - "writeMode": "service-set" - }, - { - "fieldPath": "spec.hostedCluster.imageContentSources", - "writeMode": "mutable" - }, - { - "fieldPath": "spec.hostedCluster.infraID", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.infrastructureAvailabilityPolicy", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.issuerURL", - "writeMode": "mutable" - }, - { - "fieldPath": "spec.hostedCluster.kubeAPIServerDNSName", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.labels", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.networking", - "writeMode": "mutable" - }, - { - "fieldPath": "spec.hostedCluster.nodeSelector", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.olmCatalogPlacement", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.operatorConfiguration", - "writeMode": "service-set" - }, - { - "fieldPath": "spec.hostedCluster.pausedUntil", - "writeMode": "service-set" - }, - { - "fieldPath": "spec.hostedCluster.platform", - "writeMode": "mutable" - }, - { - "fieldPath": "spec.hostedCluster.pullSecret", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.release", - "writeMode": "mutable" - }, - { - "fieldPath": "spec.hostedCluster.secretEncryption", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.serviceAccountSigningKey", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.services", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.sshKey", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.tolerations", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.updateService", - "writeMode": "service-set", - "hidden": true - }, { "fieldPath": "spec.internalId", "writeMode": "service-set", @@ -655,77 +314,6 @@ "fieldPath": "spec.labels", "writeMode": "mutable" }, - { - "fieldPath": "spec.nodePool.arch", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.nodePool.autoScaling", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.nodePool.clusterName", - "writeMode": "mutable" - }, - { - "fieldPath": "spec.nodePool.config", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.nodePool.management", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.nodePool.nodeDrainTimeout", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.nodePool.nodeLabels", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.nodePool.nodeVolumeDetachTimeout", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.nodePool.osImageStream", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.nodePool.pausedUntil", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.nodePool.platform", - "writeMode": "mutable" - }, - { - "fieldPath": "spec.nodePool.release", - "writeMode": "mutable" - }, - { - "fieldPath": "spec.nodePool.replicas", - "writeMode": "mutable" - }, - { - "fieldPath": "spec.nodePool.taints", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.nodePool.tuningConfig", - "writeMode": "service-set", - "hidden": true - }, { "fieldPath": "spec.properties", "writeMode": "mutable" diff --git a/hyperfleet-operator/config/crd/bases/hyperfleet.io_clusters.yaml b/hyperfleet-operator/config/crd/bases/hyperfleet.io_clusters.yaml index c8caa6c2..65a8cf95 100644 --- a/hyperfleet-operator/config/crd/bases/hyperfleet.io_clusters.yaml +++ b/hyperfleet-operator/config/crd/bases/hyperfleet.io_clusters.yaml @@ -577,188 +577,2689 @@ spec: OCP components in the properties: apiServer: - description: apiServer contains advanced network settings - for the API server. - type: object - authentication: - description: authentication contains configuration for the - cluster authentication. - type: object - featureGate: - description: featureGate contains the desired configuration - for feature gates. - type: object - image: - description: image contains the configuration for internal - registry. - type: object - ingress: - description: ingress contains the configuration for ingress. - type: object - kubelet: - description: kubelet contains the configuration for kubelet - on nodes. + description: |- + apiServer holds configuration (like serving certificates, client CA and CORS domains) + shared by all API servers in the system, among them especially kube-apiserver + and openshift-apiserver. properties: - allowedUnsafeSysctls: + additionalCORSAllowedOrigins: + description: |- + additionalCORSAllowedOrigins lists additional, user-defined regular expressions describing hosts for which the + API server allows access using the CORS headers. This may be needed to access the API and the integrated OAuth + server from JavaScript applications. + The values are regular expressions that correspond to the Golang regular expression language. items: type: string - maxItems: 256 type: array - containerLogMaxFiles: - format: int32 - type: integer - containerLogMaxSize: - type: string - cpuManagerPolicy: - type: string - cpuManagerPolicyOptions: - additionalProperties: - type: string - maxProperties: 32 - type: object - cpuManagerReconcilePeriod: - type: string - evictionHard: - additionalProperties: - type: string - maxProperties: 32 + x-kubernetes-list-type: atomic + audit: + default: + profile: Default + description: |- + audit specifies the settings for audit configuration to be applied to all OpenShift-provided + API servers in the cluster. + properties: + customRules: + description: |- + customRules specify profiles per group. These profile take precedence over the + top-level profile field if they apply. They are evaluation from top to bottom and + the first one that matches, applies. + items: + description: |- + AuditCustomRule describes a custom rule for an audit profile that takes precedence over + the top-level profile. + properties: + group: + description: group is a name of group a request + user must be member of in order to this profile + to apply. + minLength: 1 + type: string + profile: + description: |- + profile specifies the name of the desired audit policy configuration to be deployed to + all OpenShift-provided API servers in the cluster. + + The following profiles are provided: + - Default: the existing default policy. + - WriteRequestBodies: like 'Default', but logs request and response HTTP payloads for + write requests (create, update, patch). + - AllRequestBodies: like 'WriteRequestBodies', but also logs request and response + HTTP payloads for read requests (get, list). + - None: no requests are logged at all, not even oauthaccesstokens and oauthauthorizetokens. + + If unset, the 'Default' profile is used as the default. + enum: + - Default + - WriteRequestBodies + - AllRequestBodies + - None + type: string + required: + - group + - profile + type: object + type: array + x-kubernetes-list-map-keys: + - group + x-kubernetes-list-type: map + profile: + default: Default + description: |- + profile specifies the name of the desired top-level audit profile to be applied to all requests + sent to any of the OpenShift-provided API servers in the cluster (kube-apiserver, + openshift-apiserver and oauth-apiserver), with the exception of those requests that match + one or more of the customRules. + + The following profiles are provided: + - Default: default policy which means MetaData level logging with the exception of events + (not logged at all), oauthaccesstokens and oauthauthorizetokens (both logged at RequestBody + level). + - WriteRequestBodies: like 'Default', but logs request and response HTTP payloads for + write requests (create, update, patch). + - AllRequestBodies: like 'WriteRequestBodies', but also logs request and response + HTTP payloads for read requests (get, list). + - None: no requests are logged at all, not even oauthaccesstokens and oauthauthorizetokens. + + Warning: It is not recommended to disable audit logging by using the `None` profile unless you + are fully aware of the risks of not logging data that can be beneficial when troubleshooting issues. + If you disable audit logging and a support situation arises, you might need to enable audit logging + and reproduce the issue in order to troubleshoot properly. + + If unset, the 'Default' profile is used as the default. + enum: + - Default + - WriteRequestBodies + - AllRequestBodies + - None + type: string type: object - evictionSoft: - additionalProperties: - type: string - maxProperties: 32 + clientCA: + description: |- + clientCA references a ConfigMap containing a certificate bundle for the signers that will be recognized for + incoming client certificates in addition to the operator managed signers. If this is empty, then only operator managed signers are valid. + You usually only have to set this if you have your own PKI you wish to honor client certificates from. + The ConfigMap must exist in the openshift-config namespace and contain the following required fields: + - ConfigMap.Data["ca-bundle.crt"] - CA bundle. + properties: + name: + description: name is the metadata.name of the referenced + config map + type: string + required: + - name type: object - evictionSoftGracePeriod: - additionalProperties: - type: string - maxProperties: 32 + encryption: + description: encryption allows the configuration of encryption + of resources at the datastore layer. + properties: + kms: + description: |- + kms defines the configuration for the external KMS instance that manages the encryption keys, + when KMS encryption is enabled sensitive resources will be encrypted using keys managed by an + externally configured KMS instance. + + The Key Management Service (KMS) instance provides symmetric encryption and is responsible for + managing the lifecyle of the encryption keys outside of the control plane. + This allows integration with an external provider to manage the data encryption keys securely. + properties: + aws: + description: |- + aws defines the key config for using an AWS KMS instance + for the encryption. The AWS KMS instance is managed + by the user outside the purview of the control plane. + properties: + keyARN: + description: |- + keyARN specifies the Amazon Resource Name (ARN) of the AWS KMS key used for encryption. + The value must adhere to the format `arn:aws:kms:::key/`, where: + - `` is the AWS region consisting of lowercase letters and hyphens followed by a number. + - `` is a 12-digit numeric identifier for the AWS account. + - `` is a unique identifier for the KMS key, consisting of lowercase hexadecimal characters and hyphens. + maxLength: 128 + minLength: 1 + type: string + x-kubernetes-validations: + - message: keyARN must follow the format `arn:aws:kms:::key/`. + The account ID must be a 12 digit number + and the region and key ID should consist + only of lowercase hexadecimal characters + and hyphens (-). + rule: self.matches('^arn:aws:kms:[a-z0-9-]+:[0-9]{12}:key/[a-f0-9-]+$') + region: + description: |- + region specifies the AWS region where the KMS instance exists, and follows the format + `--`, e.g.: `us-east-1`. + Only lowercase letters and hyphens followed by numbers are allowed. + maxLength: 64 + minLength: 1 + type: string + x-kubernetes-validations: + - message: region must be a valid AWS region, + consisting of lowercase characters, digits + and hyphens (-) only. + rule: self.matches('^[a-z0-9]+(-[a-z0-9]+)*$') + required: + - keyARN + - region + type: object + type: + description: |- + type defines the kind of platform for the KMS provider. + Available provider types are AWS only. + enum: + - AWS + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: aws config is required when kms provider + type is AWS, and forbidden otherwise + rule: 'has(self.type) && self.type == ''AWS'' ? has(self.aws) + : !has(self.aws)' + type: + description: |- + type defines what encryption type should be used to encrypt resources at the datastore layer. + When this field is unset (i.e. when it is set to the empty string), identity is implied. + The behavior of unset can and will change over time. Even if encryption is enabled by default, + the meaning of unset may change to a different encryption type based on changes in best practices. + + When encryption is enabled, all sensitive resources shipped with the platform are encrypted. + This list of sensitive resources can and will change over time. The current authoritative list is: + + 1. secrets + 2. configmaps + 3. routes.route.openshift.io + 4. oauthaccesstokens.oauth.openshift.io + 5. oauthauthorizetokens.oauth.openshift.io + type: string type: object - imageGCHighThresholdPercent: - format: int32 - type: integer - imageGCLowThresholdPercent: - format: int32 - type: integer - imageMinimumGCAge: - type: string - kubeReserved: - additionalProperties: - type: string - maxProperties: 32 + servingCerts: + description: |- + servingCert is the TLS cert info for serving secure traffic. If not specified, operator managed certificates + will be used for serving secure traffic. + properties: + namedCertificates: + description: |- + namedCertificates references secrets containing the TLS cert info for serving secure traffic to specific hostnames. + If no named certificates are provided, or no named certificates match the server name as understood by a client, + the defaultServingCertificate will be used. + items: + description: APIServerNamedServingCert maps a server + DNS name, as understood by a client, to a certificate. + properties: + names: + description: |- + names is a optional list of explicit DNS names (leading wildcards allowed) that should use this certificate to + serve secure traffic. If no names are provided, the implicit names will be extracted from the certificates. + Exact names trump over wildcard names. Explicit names defined here trump over extracted implicit names. + items: + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: atomic + servingCertificate: + description: |- + servingCertificate references a kubernetes.io/tls type secret containing the TLS cert info for serving secure traffic. + The secret must exist in the openshift-config namespace and contain the following required fields: + - Secret.Data["tls.key"] - TLS private key. + - Secret.Data["tls.crt"] - TLS certificate. + properties: + name: + description: name is the metadata.name of + the referenced secret + type: string + required: + - name + type: object + type: object + maxItems: 32 + type: array + x-kubernetes-list-type: atomic type: object - maxPods: - format: int32 - type: integer - memoryThrottlingFactor: - type: number - podPidsLimit: - format: int64 - type: integer - registryBurst: - format: int32 - type: integer - registryPullQPS: - format: int32 - type: integer - serializeImagePulls: - type: boolean - streamingConnectionIdleTimeout: + tlsAdherence: + description: |- + tlsAdherence controls if components in the cluster adhere to the TLS security profile + configured on this APIServer resource. + + Valid values are "LegacyAdheringComponentsOnly" and "StrictAllComponents". + + When set to "LegacyAdheringComponentsOnly", components that already honor the + cluster-wide TLS profile continue to do so. Components that do not already honor + it continue to use their individual TLS configurations. + + When set to "StrictAllComponents", all components must honor the configured TLS + profile unless they have a component-specific TLS configuration that overrides + it. This mode is recommended for security-conscious deployments and is required + for certain compliance frameworks. + + Note: Some components such as Kubelet and IngressController have their own + dedicated TLS configuration mechanisms via KubeletConfig and IngressController + CRs respectively. When these component-specific TLS configurations are set, + they take precedence over the cluster-wide tlsSecurityProfile. When not set, + these components fall back to the cluster-wide default. + + Components that encounter an unknown value for tlsAdherence should treat it + as "StrictAllComponents" and log a warning to ensure forward compatibility + while defaulting to the more secure behavior. + + This field is optional. + When omitted, this means the user has no opinion and the platform is left + to choose reasonable defaults. These defaults are subject to change over time. + The current default is LegacyAdheringComponentsOnly. + + Once set, this field may be changed to a different value, but may not be removed. + enum: + - LegacyAdheringComponentsOnly + - StrictAllComponents type: string - systemReserved: - additionalProperties: - type: string - maxProperties: 32 + tlsSecurityProfile: + description: |- + tlsSecurityProfile specifies settings for TLS connections for externally exposed servers. + + When omitted, this means no opinion and the platform is left to choose a reasonable default, which is subject to change over time. + The current default is the Intermediate profile. + properties: + custom: + description: |- + custom is a user-defined TLS security profile. Be extremely careful using a custom + profile as invalid configurations can be catastrophic. An example custom profile + looks like this: + + minTLSVersion: VersionTLS11 + ciphers: + - ECDHE-ECDSA-CHACHA20-POLY1305 + - ECDHE-RSA-CHACHA20-POLY1305 + - ECDHE-RSA-AES128-GCM-SHA256 + - ECDHE-ECDSA-AES128-GCM-SHA256 + nullable: true + properties: + ciphers: + description: |- + ciphers is used to specify the cipher algorithms that are negotiated + during the TLS handshake. Operators may remove entries that their operands + do not support. For example, to use only ECDHE-RSA-AES128-GCM-SHA256 (yaml): + + ciphers: + - ECDHE-RSA-AES128-GCM-SHA256 + + TLS 1.3 cipher suites (e.g. TLS_AES_128_GCM_SHA256) are not configurable + and are always enabled when TLS 1.3 is negotiated. + items: + type: string + type: array + x-kubernetes-list-type: atomic + minTLSVersion: + description: |- + minTLSVersion is used to specify the minimal version of the TLS protocol + that is negotiated during the TLS handshake. For example, to use TLS + versions 1.1, 1.2 and 1.3 (yaml): + + minTLSVersion: VersionTLS11 + enum: + - VersionTLS10 + - VersionTLS11 + - VersionTLS12 + - VersionTLS13 + type: string + type: object + intermediate: + description: |- + intermediate is a TLS profile for use when you do not need compatibility with + legacy clients and want to remain highly secure while being compatible with + most clients currently in use. + + This profile is equivalent to a Custom profile specified as: + minTLSVersion: VersionTLS12 + ciphers: + - TLS_AES_128_GCM_SHA256 + - TLS_AES_256_GCM_SHA384 + - TLS_CHACHA20_POLY1305_SHA256 + - ECDHE-ECDSA-AES128-GCM-SHA256 + - ECDHE-RSA-AES128-GCM-SHA256 + - ECDHE-ECDSA-AES256-GCM-SHA384 + - ECDHE-RSA-AES256-GCM-SHA384 + - ECDHE-ECDSA-CHACHA20-POLY1305 + - ECDHE-RSA-CHACHA20-POLY1305 + nullable: true + type: object + modern: + description: |- + modern is a TLS security profile for use with clients that support TLS 1.3 and + do not need backward compatibility for older clients. + + This profile is equivalent to a Custom profile specified as: + minTLSVersion: VersionTLS13 + ciphers: + - TLS_AES_128_GCM_SHA256 + - TLS_AES_256_GCM_SHA384 + - TLS_CHACHA20_POLY1305_SHA256 + nullable: true + type: object + old: + description: |- + old is a TLS profile for use when services need to be accessed by very old + clients or libraries and should be used only as a last resort. + + This profile is equivalent to a Custom profile specified as: + minTLSVersion: VersionTLS10 + ciphers: + - TLS_AES_128_GCM_SHA256 + - TLS_AES_256_GCM_SHA384 + - TLS_CHACHA20_POLY1305_SHA256 + - ECDHE-ECDSA-AES128-GCM-SHA256 + - ECDHE-RSA-AES128-GCM-SHA256 + - ECDHE-ECDSA-AES256-GCM-SHA384 + - ECDHE-RSA-AES256-GCM-SHA384 + - ECDHE-ECDSA-CHACHA20-POLY1305 + - ECDHE-RSA-CHACHA20-POLY1305 + - ECDHE-ECDSA-AES128-SHA256 + - ECDHE-RSA-AES128-SHA256 + - ECDHE-ECDSA-AES128-SHA + - ECDHE-RSA-AES128-SHA + - ECDHE-ECDSA-AES256-SHA + - ECDHE-RSA-AES256-SHA + - AES128-GCM-SHA256 + - AES256-GCM-SHA384 + - AES128-SHA256 + - AES128-SHA + - AES256-SHA + - DES-CBC3-SHA + nullable: true + type: object + type: + description: |- + type is one of Old, Intermediate, Modern or Custom. Custom provides the + ability to specify individual TLS security profile parameters. + + The profiles are based on version 5.7 of the Mozilla Server Side TLS + configuration guidelines. The cipher lists consist of the configuration's + "ciphersuites" followed by the Go-specific "ciphers" from the guidelines. + See: https://ssl-config.mozilla.org/guidelines/5.7.json + + The profiles are intent based, so they may change over time as new ciphers are + developed and existing ciphers are found to be insecure. Depending on + precisely which ciphers are available to a process, the list may be reduced. + enum: + - Old + - Intermediate + - Modern + - Custom + type: string type: object - topologyManagerPolicy: - type: string - topologyManagerScope: - type: string type: object - machineConfig: - description: machineConfig contains the configuration for - machine-level settings. + authentication: + description: |- + authentication specifies cluster-wide settings for authentication (like OAuth and + webhook token authenticators). properties: - allowedKernelArguments: - items: - type: string - maxItems: 128 - type: array - extensions: - items: - type: string - maxItems: 64 - type: array - files: + oauthMetadata: + description: |- + oauthMetadata contains the discovery endpoint data for OAuth 2.0 + Authorization Server Metadata for an external OAuth server. + This discovery document can be viewed from its served location: + oc get --raw '/.well-known/oauth-authorization-server' + For further details, see the IETF Draft: + https://tools.ietf.org/html/draft-ietf-oauth-discovery-04#section-2 + If oauthMetadata.name is non-empty, this value has precedence + over any metadata reference stored in status. + The key "oauthMetadata" is used to locate the data. + If specified and the config map or expected key is not found, no metadata is served. + If the specified metadata is not valid, no metadata is served. + The namespace for this config map is openshift-config. + properties: + name: + description: name is the metadata.name of the referenced + config map + type: string + required: + - name + type: object + oidcProviders: + description: |- + oidcProviders are OIDC identity providers that can issue tokens for this cluster + Can only be set if "Type" is set to "OIDC". + + At most one provider can be configured. items: properties: - contents: - maxLength: 262144 - type: string - group: + claimMappings: + description: claimMappings is a required field that + configures the rules to be used by the Kubernetes + API server for translating claims in a JWT token, + issued by the identity provider, to a cluster + identity. + properties: + extra: + description: |- + extra is an optional field for configuring the mappings used to construct the extra attribute for the cluster identity. + When omitted, no extra attributes will be present on the cluster identity. + + key values for extra mappings must be unique. + A maximum of 32 extra attribute mappings may be provided. + items: + description: |- + ExtraMapping allows specifying a key and CEL expression to evaluate the keys' value. + It is used to create additional mappings and attributes added to a cluster identity from a provided authentication token. + properties: + key: + description: |- + key is a required field that specifies the string to use as the extra attribute key. + + key must be a domain-prefix path (e.g 'example.org/foo'). + key must not exceed 510 characters in length. + key must contain the '/' character, separating the domain and path characters. + key must not be empty. + + The domain portion of the key (string of characters prior to the '/') must be a valid RFC1123 subdomain. + It must not exceed 253 characters in length. + It must start and end with an alphanumeric character. + It must only contain lower case alphanumeric characters and '-' or '.'. + It must not use the reserved domains, or be subdomains of, "kubernetes.io", "k8s.io", and "openshift.io". + + The path portion of the key (string of characters after the '/') must not be empty and must consist of at least one alphanumeric character, percent-encoded octets, '-', '.', '_', '~', '!', '$', '&', ''', '(', ')', '*', '+', ',', ';', '=', and ':'. + It must not exceed 256 characters in length. + maxLength: 510 + minLength: 1 + type: string + x-kubernetes-validations: + - message: key must contain the '/' character + rule: self.contains('/') + - message: the domain of the key must + consist of only lower case alphanumeric + characters, '-' or '.', and must start + and end with an alphanumeric character + rule: self.split('/', 2)[0].matches("^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$") + - message: the domain of the key must + not exceed 253 characters in length + rule: self.split('/', 2)[0].size() <= + 253 + - message: the domain 'kubernetes.io' + is reserved for Kubernetes use + rule: self.split('/', 2)[0] != 'kubernetes.io' + - message: the subdomains '*.kubernetes.io' + are reserved for Kubernetes use + rule: '!self.split(''/'', 2)[0].endsWith(''.kubernetes.io'')' + - message: the domain 'k8s.io' is reserved + for Kubernetes use + rule: self.split('/', 2)[0] != 'k8s.io' + - message: the subdomains '*.k8s.io' are + reserved for Kubernetes use + rule: '!self.split(''/'', 2)[0].endsWith(''.k8s.io'')' + - message: the domain 'openshift.io' is + reserved for OpenShift use + rule: self.split('/', 2)[0] != 'openshift.io' + - message: the subdomains '*.openshift.io' + are reserved for OpenShift use + rule: '!self.split(''/'', 2)[0].endsWith(''.openshift.io'')' + - message: the path of the key must not + be empty and must consist of at least + one alphanumeric character, percent-encoded + octets, apostrophe, '-', '.', '_', + '~', '!', '$', '&', '(', ')', '*', + '+', ',', ';', '=', and ':' + rule: self.split('/', 2)[1].matches('[A-Za-z0-9/\\-._~%!$&\'()*+;=:]+') + - message: the path of the key must not + exceed 256 characters in length + rule: self.split('/', 2)[1].size() <= + 256 + valueExpression: + description: |- + valueExpression is a required field to specify the CEL expression to extract the extra attribute value from a JWT token's claims. + valueExpression must produce a string or string array value. + "", [], and null are treated as the extra mapping not being present. + Empty string values within an array are filtered out. + + CEL expressions have access to the token claims through a CEL variable, 'claims'. + 'claims' is a map of claim names to claim values. + For example, the 'sub' claim value can be accessed as 'claims.sub'. + Nested claims can be accessed using dot notation ('claims.foo.bar'). + + valueExpression must not exceed 1024 characters in length. + valueExpression must not be empty. + maxLength: 1024 + minLength: 1 + type: string + required: + - key + - valueExpression + type: object + maxItems: 32 + type: array + x-kubernetes-list-map-keys: + - key + x-kubernetes-list-type: map + groups: + description: |- + groups is an optional field that configures how the groups of a cluster identity should be constructed from the claims in a JWT token issued by the identity provider. + + When referencing a claim, if the claim is present in the JWT token, its value must be a list of groups separated by a comma (','). + + For example - '"example"' and '"exampleOne", "exampleTwo", "exampleThree"' are valid claim values. + properties: + claim: + description: |- + claim is an optional field for specifying the JWT token claim that is used in the mapping. + The value of this claim will be assigned to the field in which this mapping is associated. + claim must not exceed 256 characters in length. + When set to the empty string `""`, this means that no named claim should be used for the group mapping. + claim is required when the ExternalOIDCWithUpstreamParity feature gate is not enabled. + maxLength: 256 + type: string + expression: + description: |- + expression is an optional CEL expression used to derive + group values from JWT claims. + + CEL expressions have access to the token claims through a CEL variable, 'claims'. + + expression must be at least 1 character and must not exceed 1024 characters in length . + + When specified, claim must not be set or be explicitly set to the empty string (`""`). + maxLength: 1024 + minLength: 1 + type: string + prefix: + description: |- + prefix is an optional field that configures the prefix that will be applied to the cluster identity attribute during the process of mapping JWT claims to cluster identity attributes. + + When omitted or set to an empty string (""), no prefix is applied to the cluster identity attribute. + Must not be set to a non-empty value when expression is set. + + Example: if `prefix` is set to "myoidc:" and the `claim` in JWT contains an array of strings "a", "b" and "c", the mapping will result in an array of string "myoidc:a", "myoidc:b" and "myoidc:c". + type: string + type: object + uid: + description: |- + uid is an optional field for configuring the claim mapping used to construct the uid for the cluster identity. + + When using uid.claim to specify the claim it must be a single string value. + When using uid.expression the expression must result in a single string value. + + When omitted, this means the user has no opinion and the platform is left to choose a default, which is subject to change over time. + + The current default is to use the 'sub' claim. + properties: + claim: + description: |- + claim is an optional field for specifying the JWT token claim that is used in the mapping. + The value of this claim will be assigned to the field in which this mapping is associated. + + Precisely one of claim or expression must be set. + claim must not be specified when expression is set. + When specified, claim must be at least 1 character in length and must not exceed 256 characters in length. + maxLength: 256 + minLength: 1 + type: string + expression: + description: |- + expression is an optional field for specifying a CEL expression that produces a string value from JWT token claims. + + CEL expressions have access to the token claims through a CEL variable, 'claims'. + 'claims' is a map of claim names to claim values. + For example, the 'sub' claim value can be accessed as 'claims.sub'. + Nested claims can be accessed using dot notation ('claims.foo.bar'). + + Precisely one of claim or expression must be set. + expression must not be specified when claim is set. + When specified, expression must be at least 1 character in length and must not exceed 1024 characters in length. + maxLength: 1024 + minLength: 1 + type: string + type: object + x-kubernetes-validations: + - message: precisely one of claim or expression + must be set + rule: 'has(self.claim) ? !has(self.expression) + : has(self.expression)' + username: + description: username is a required field that + configures how the username of a cluster identity + should be constructed from the claims in a + JWT token issued by the identity provider. + properties: + claim: + description: |- + claim is an optional field that configures the JWT token claim whose value is assigned to the cluster identity field associated with this mapping. + claim is required when the ExternalOIDCWithUpstreamParity feature gate is not enabled. + When the ExternalOIDCWithUpstreamParity feature gate is enabled, claim must not be set when expression is set. + + claim must not be an empty string ("") and must not exceed 256 characters. + maxLength: 256 + minLength: 1 + type: string + expression: + description: |- + expression is an optional CEL expression used to derive + the username from JWT claims. + + CEL expressions have access to the token claims + through a CEL variable, 'claims'. + + expression must be at least 1 character and must not exceed 1024 characters in length. + expression must not be set when claim is set. + maxLength: 1024 + minLength: 1 + type: string + prefix: + description: |- + prefix configures the prefix that should be prepended to the value of the JWT claim. + + prefix must be set when prefixPolicy is set to 'Prefix' and must be unset otherwise. + properties: + prefixString: + description: |- + prefixString is a required field that configures the prefix that will be applied to cluster identity username attribute during the process of mapping JWT claims to cluster identity attributes. + + prefixString must not be an empty string (""). + minLength: 1 + type: string + required: + - prefixString + type: object + prefixPolicy: + description: |- + prefixPolicy is an optional field that configures how a prefix should be applied to the value of the JWT claim specified in the 'claim' field. + + Allowed values are 'Prefix', 'NoPrefix', and omitted (not provided or an empty string). + + When set to 'Prefix', the value specified in the prefix field will be prepended to the value of the JWT claim. + The prefix field must be set when prefixPolicy is 'Prefix'. + Must not be set to 'Prefix' when expression is set. + When set to 'NoPrefix', no prefix will be prepended to the value of the JWT claim. + When omitted, this means no opinion and the platform is left to choose any prefixes that are applied which is subject to change over time. + Currently, the platform prepends `{issuerURL}#` to the value of the JWT claim when the claim is not 'email'. + + As an example, consider the following scenario: + + `prefix` is unset, `issuerURL` is set to `https://myoidc.tld`, + the JWT claims include "username":"userA" and "email":"userA@myoidc.tld", + and `claim` is set to: + - "username": the mapped value will be "https://myoidc.tld#userA" + - "email": the mapped value will be "userA@myoidc.tld" + enum: + - "" + - NoPrefix + - Prefix + type: string + type: object + x-kubernetes-validations: + - message: prefix must be set if prefixPolicy + is 'Prefix', but must remain unset otherwise + rule: 'has(self.prefixPolicy) && self.prefixPolicy + == ''Prefix'' ? (has(self.prefix) && size(self.prefix.prefixString) + > 0) : !has(self.prefix)' + required: + - username + type: object + claimValidationRules: + description: |- + claimValidationRules is an optional field that configures the rules to be used by the Kubernetes API server for validating the claims in a JWT token issued by the identity provider. + + Validation rules are joined via an AND operation. + items: + description: |- + TokenClaimValidationRule represents a validation rule based on token claims. + If type is RequiredClaim, requiredClaim must be set. + If Type is CEL, CEL must be set and RequiredClaim must be omitted. + properties: + cel: + description: |- + cel holds the CEL expression and message for validation. + Must be set when Type is "CEL", and forbidden otherwise. + properties: + expression: + description: |- + expression is a CEL expression evaluated against token claims. + expression is required, must be at least 1 character in length and must not exceed 1024 characters. + The expression must return a boolean value where 'true' signals a valid token and 'false' an invalid one. + maxLength: 1024 + minLength: 1 + type: string + message: + description: |- + message is a required human-readable message to be logged by the Kubernetes API server if the CEL expression defined in 'expression' fails. + message must be at least 1 character in length and must not exceed 256 characters. + maxLength: 256 + minLength: 1 + type: string + required: + - expression + - message + type: object + requiredClaim: + description: |- + requiredClaim allows configuring a required claim name and its expected value. + This field is required when `type` is set to RequiredClaim, and must be omitted when `type` is set to any other value. + The Kubernetes API server uses this field to validate if an incoming JWT is valid for this identity provider. + properties: + claim: + description: |- + claim is a required field that configures the name of the required claim. + When taken from the JWT claims, claim must be a string value. + + claim must not be an empty string (""). + minLength: 1 + type: string + requiredValue: + description: |- + requiredValue is a required field that configures the value that 'claim' must have when taken from the incoming JWT claims. + If the value in the JWT claims does not match, the token will be rejected for authentication. + + requiredValue must not be an empty string (""). + minLength: 1 + type: string + required: + - claim + - requiredValue + type: object + type: + description: |- + type is an optional field that configures the type of the validation rule. + + Allowed values are "RequiredClaim" and "CEL". + + When set to 'RequiredClaim', the Kubernetes API server will be configured to validate that the incoming JWT contains the required claim and that its value matches the required value. + + When set to 'CEL', the Kubernetes API server will be configured to validate the incoming JWT against the configured CEL expression. + type: string + required: + - type + type: object + x-kubernetes-validations: + - message: requiredClaim must be set when type + is 'RequiredClaim', and forbidden otherwise + rule: 'has(self.type) && self.type == ''RequiredClaim'' + ? has(self.requiredClaim) : !has(self.requiredClaim)' + type: array + x-kubernetes-list-type: atomic + issuer: + description: issuer is a required field that configures + how the platform interacts with the identity provider + and how tokens issued from the identity provider + are evaluated by the Kubernetes API server. + properties: + audiences: + description: |- + audiences is a required field that configures the acceptable audiences the JWT token, issued by the identity provider, must be issued to. + At least one of the entries must match the 'aud' claim in the JWT token. + + audiences must contain at least one entry and must not exceed ten entries. + items: + minLength: 1 + type: string + maxItems: 10 + minItems: 1 + type: array + x-kubernetes-list-type: set + discoveryURL: + description: |- + discoveryURL is an optional field that, if specified, overrides the default discovery endpoint used to retrieve OIDC configuration metadata. + By default, the discovery URL is derived from `issuerURL` as "{issuerURL}/.well-known/openid-configuration". + + The discoveryURL must be a valid absolute HTTPS URL. + It must not contain query parameters, user information, or fragments. + Additionally, it must differ from the value of `issuerURL` (ignoring trailing slashes). + The discoveryURL value must be at least 1 character long and no longer than 2048 characters. + maxLength: 2048 + minLength: 1 + type: string + x-kubernetes-validations: + - message: discoveryURL must be a valid URL + rule: isURL(self) + - message: discoveryURL must be a valid https + URL + rule: url(self).getScheme() == 'https' + - message: discoveryURL must not contain query + parameters + rule: url(self).getQuery().size() == 0 + - message: discoveryURL must not contain fragments + rule: self.matches('^[^#]*$') + - message: discoveryURL must not contain user + info + rule: '!self.matches(''^https://.+:.+@.+/.*$'')' + issuerCertificateAuthority: + description: |- + issuerCertificateAuthority is an optional field that configures the certificate authority, used by the Kubernetes API server, to validate the connection to the identity provider when fetching discovery information. + + When not specified, the system trust is used. + + When specified, it must reference a ConfigMap in the openshift-config namespace containing the PEM-encoded CA certificates under the 'ca-bundle.crt' key in the data field of the ConfigMap. + properties: + name: + description: name is the metadata.name of + the referenced config map + type: string + required: + - name + type: object + issuerURL: + description: |- + issuerURL is a required field that configures the URL used to issue tokens by the identity provider. + The Kubernetes API server determines how authentication tokens should be handled by matching the 'iss' claim in the JWT to the issuerURL of configured identity providers. + + Must be at least 1 character and must not exceed 512 characters in length. + Must be a valid URL that uses the 'https' scheme and does not contain a query, fragment or user. + maxLength: 512 + minLength: 1 + type: string + x-kubernetes-validations: + - message: must be a valid URL + rule: isURL(self) + - message: must use the 'https' scheme + rule: isURL(self) && url(self).getScheme() + == 'https' + - message: must not have a query + rule: isURL(self) && url(self).getQuery() + == {} + - message: must not have a fragment + rule: self.find('#(.+)$') == '' + - message: must not have user info + rule: self.find('@') == '' + required: + - audiences + - issuerURL + type: object + name: + description: |- + name is a required field that configures the unique human-readable identifier associated with the identity provider. + It is used to distinguish between multiple identity providers and has no impact on token validation or authentication mechanics. + + name must not be an empty string (""). + minLength: 1 + type: string + oidcClients: + description: |- + oidcClients is an optional field that configures how on-cluster, platform clients should request tokens from the identity provider. + oidcClients must not exceed 20 entries and entries must have unique namespace/name pairs. + items: + description: OIDCClientConfig configures how platform + clients interact with identity providers as + an authentication method. + properties: + clientID: + description: |- + clientID is a required field that configures the client identifier, from the identity provider, that the platform component uses for authentication requests made to the identity provider. + The identity provider must accept this identifier for platform components to be able to use the identity provider as an authentication mode. + + clientID must not be an empty string (""). + minLength: 1 + type: string + clientSecret: + description: |- + clientSecret is an optional field that configures the client secret used by the platform component when making authentication requests to the identity provider. + + When not specified, no client secret will be used when making authentication requests to the identity provider. + + When specified, clientSecret references a Secret in the 'openshift-config' namespace that contains the client secret in the 'clientSecret' key of the '.data' field. + + The client secret will be used when making authentication requests to the identity provider. + + Public clients do not require a client secret but private clients do require a client secret to work with the identity provider. + properties: + name: + description: name is the metadata.name + of the referenced secret + type: string + required: + - name + type: object + componentName: + description: |- + componentName is a required field that specifies the name of the platform component being configured to use the identity provider as an authentication mode. + + It is used in combination with componentNamespace as a unique identifier. + + componentName must not be an empty string ("") and must not exceed 256 characters in length. + maxLength: 256 + minLength: 1 + type: string + componentNamespace: + description: |- + componentNamespace is a required field that specifies the namespace in which the platform component being configured to use the identity provider as an authentication mode is running. + + It is used in combination with componentName as a unique identifier. + + componentNamespace must not be an empty string ("") and must not exceed 63 characters in length. + maxLength: 63 + minLength: 1 + type: string + extraScopes: + description: |- + extraScopes is an optional field that configures the extra scopes that should be requested by the platform component when making authentication requests to the identity provider. + This is useful if you have configured claim mappings that requires specific scopes to be requested beyond the standard OIDC scopes. + + When omitted, no additional scopes are requested. + items: + type: string + type: array + x-kubernetes-list-type: set + required: + - clientID + - componentName + - componentNamespace + type: object + maxItems: 20 + type: array + x-kubernetes-list-map-keys: + - componentNamespace + - componentName + x-kubernetes-list-type: map + userValidationRules: + description: |- + userValidationRules is an optional field that configures the set of rules used to validate the cluster user identity that was constructed via mapping token claims to user identity attributes. + Rules are CEL expressions that must evaluate to 'true' for authentication to succeed. + If any rule in the chain of rules evaluates to 'false', authentication will fail. + When specified, at least one rule must be specified and no more than 64 rules may be specified. + items: + description: |- + TokenUserValidationRule provides a CEL-based rule used to validate a token subject. + Each rule contains a CEL expression that is evaluated against the token’s claims. + properties: + expression: + description: |- + expression is a required CEL expression that performs a validation on cluster user identity attributes like username, groups, etc. + + The expression must evaluate to a boolean value. + When the expression evaluates to 'true', the cluster user identity is considered valid. + When the expression evaluates to 'false', the cluster user identity is not considered valid. + expression must be at least 1 character in length and must not exceed 1024 characters. + maxLength: 1024 + minLength: 1 + type: string + message: + description: |- + message is a required human-readable message to be logged by the Kubernetes API server if the CEL expression defined in 'expression' fails. + message must be at least 1 character in length and must not exceed 256 characters. + maxLength: 256 + minLength: 1 + type: string + required: + - expression + - message + type: object + maxItems: 64 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - expression + x-kubernetes-list-type: map + required: + - claimMappings + - issuer + - name + type: object + maxItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + serviceAccountIssuer: + description: |- + serviceAccountIssuer is the identifier of the bound service account token + issuer. + The default is https://kubernetes.default.svc + WARNING: Updating this field will not result in immediate invalidation of all bound tokens with the + previous issuer value. Instead, the tokens issued by previous service account issuer will continue to + be trusted for a time period chosen by the platform (currently set to 24h). + This time period is subject to change over time. + This allows internal components to transition to use new service account issuer without service distruption. + type: string + type: + description: |- + type identifies the cluster managed, user facing authentication mode in use. + Specifically, it manages the component that responds to login attempts. + The default is IntegratedOAuth. + type: string + webhookTokenAuthenticator: + description: |- + webhookTokenAuthenticator configures a remote token reviewer. + These remote authentication webhooks can be used to verify bearer tokens + via the tokenreviews.authentication.k8s.io REST API. This is required to + honor bearer tokens that are provisioned by an external authentication service. + + Can only be set if "Type" is set to "None". + properties: + kubeConfig: + description: |- + kubeConfig references a secret that contains kube config file data which + describes how to access the remote webhook service. + The namespace for the referenced secret is openshift-config. + + For further details, see: + + https://kubernetes.io/docs/reference/access-authn-authz/authentication/#webhook-token-authentication + + The key "kubeConfig" is used to locate the data. + If the secret or expected key is not found, the webhook is not honored. + If the specified kube config data is not valid, the webhook is not honored. + properties: + name: + description: name is the metadata.name of the + referenced secret + type: string + required: + - name + type: object + required: + - kubeConfig + type: object + webhookTokenAuthenticators: + description: webhookTokenAuthenticators is DEPRECATED, + setting it has no effect. + items: + description: |- + deprecatedWebhookTokenAuthenticator holds the necessary configuration options for a remote token authenticator. + It's the same as WebhookTokenAuthenticator but it's missing the 'required' validation on KubeConfig field. + properties: + kubeConfig: + description: |- + kubeConfig contains kube config file data which describes how to access the remote webhook service. + For further details, see: + https://kubernetes.io/docs/reference/access-authn-authz/authentication/#webhook-token-authentication + The key "kubeConfig" is used to locate the data. + If the secret or expected key is not found, the webhook is not honored. + If the specified kube config data is not valid, the webhook is not honored. + The namespace for this secret is determined by the point of use. + properties: + name: + description: name is the metadata.name of the + referenced secret + type: string + required: + - name + type: object + type: object + type: array + x-kubernetes-list-type: atomic + type: object + featureGate: + description: featureGate holds cluster-wide information about + feature gates. + properties: + customNoUpgrade: + description: |- + customNoUpgrade allows the enabling or disabling of any feature. Turning this feature set on IS NOT SUPPORTED, CANNOT BE UNDONE, and PREVENTS UPGRADES. + Because of its nature, this setting cannot be validated. If you have any typos or accidentally apply invalid combinations + your cluster may fail in an unrecoverable way. featureSet must equal "CustomNoUpgrade" must be set to use this field. + nullable: true + properties: + disabled: + description: disabled is a list of all feature gates + that you want to force off + items: + description: FeatureGateName is a string to enforce + patterns on the name of a FeatureGate + pattern: ^([A-Za-z0-9-]+\.)*[A-Za-z0-9-]+\.?$ + type: string + type: array + enabled: + description: enabled is a list of all feature gates + that you want to force on + items: + description: FeatureGateName is a string to enforce + patterns on the name of a FeatureGate + pattern: ^([A-Za-z0-9-]+\.)*[A-Za-z0-9-]+\.?$ + type: string + type: array + type: object + featureSet: + description: |- + featureSet changes the list of features in the cluster. The default is empty. Be very careful adjusting this setting. + Turning on or off features may cause irreversible changes in your cluster which cannot be undone. + enum: + - CustomNoUpgrade + - DevPreviewNoUpgrade + - TechPreviewNoUpgrade + - OKD + - "" + type: string + x-kubernetes-validations: + - message: CustomNoUpgrade may not be changed + rule: 'oldSelf == ''CustomNoUpgrade'' ? self == ''CustomNoUpgrade'' + : true' + - message: TechPreviewNoUpgrade may not be changed + rule: 'oldSelf == ''TechPreviewNoUpgrade'' ? self == + ''TechPreviewNoUpgrade'' : true' + - message: DevPreviewNoUpgrade may not be changed + rule: 'oldSelf == ''DevPreviewNoUpgrade'' ? self == + ''DevPreviewNoUpgrade'' : true' + - message: OKD cannot transition to Default + rule: 'oldSelf == ''OKD'' ? self != '''' : true' + type: object + image: + description: |- + image governs policies related to imagestream imports and runtime configuration + for external registries. It allows cluster admins to configure which registries + OpenShift is allowed to import images from, extra CA trust bundles for external + registries, and policies to block or allow registry hostnames. + When exposing OpenShift's image registry to the public, this also lets cluster + admins specify the external hostname. + This input will be part of every payload generated by the controllers for any NodePool of the HostedCluster. + Changing this value will trigger a rollout for all existing NodePools in the cluster. + properties: + additionalTrustedCA: + description: |- + additionalTrustedCA is a reference to a ConfigMap containing additional CAs that + should be trusted during imagestream import, pod image pull, build image pull, and + imageregistry pullthrough. + The namespace for this config map is openshift-config. + properties: + name: + description: name is the metadata.name of the referenced + config map + type: string + required: + - name + type: object + allowedRegistriesForImport: + description: |- + allowedRegistriesForImport limits the container image registries that normal users may import + images from. Set this list to the registries that you trust to contain valid Docker + images and that you want applications to be able to import from. Users with + permission to create Images or ImageStreamMappings via the API are not affected by + this policy - typically only administrators or system integrations will have those + permissions. + items: + description: |- + RegistryLocation contains a location of the registry specified by the registry domain + name. The domain name might include wildcards, like '*' or '??'. + properties: + domainName: + description: |- + domainName specifies a domain name for the registry + In case the registry use non-standard (80 or 443) port, the port should be included + in the domain name as well. + type: string + insecure: + description: |- + insecure indicates whether the registry is secure (https) or insecure (http) + By default (if not specified) the registry is assumed as secure. + type: boolean + type: object + type: array + x-kubernetes-list-type: atomic + externalRegistryHostnames: + description: |- + externalRegistryHostnames provides the hostnames for the default external image + registry. The external hostname should be set only when the image registry + is exposed externally. The first value is used in 'publicDockerImageRepository' + field in ImageStreams. The value must be in "hostname[:port]" format. + items: + type: string + type: array + x-kubernetes-list-type: atomic + imageStreamImportMode: + description: |- + imageStreamImportMode controls the import mode behaviour of imagestreams. + It can be set to `Legacy` or `PreserveOriginal` or the empty string. If this value + is specified, this setting is applied to all newly created imagestreams which do not have the + value set. `Legacy` indicates that the legacy behaviour should be used. + For manifest lists, the legacy behaviour will discard the manifest list and import a single + sub-manifest. In this case, the platform is chosen in the following order of priority: + 1. tag annotations; 2. control plane arch/os; 3. linux/amd64; 4. the first manifest in the list. + `PreserveOriginal` indicates that the original manifest will be preserved. For manifest lists, + the manifest list and all its sub-manifests will be imported. When empty, the behaviour will be + decided based on the payload type advertised by the ClusterVersion status, i.e single arch payload + implies the import mode is Legacy and multi payload implies PreserveOriginal. + enum: + - "" + - Legacy + - PreserveOriginal + type: string + registrySources: + description: |- + registrySources contains configuration that determines how the container runtime + should treat individual registries when accessing images for builds+pods. (e.g. + whether or not to allow insecure access). It does not contain configuration for the + internal cluster registry. + properties: + allowedRegistries: + description: |- + allowedRegistries are the only registries permitted for image pull and push actions. All other registries are denied. + + Only one of BlockedRegistries or AllowedRegistries may be set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + blockedRegistries: + description: |- + blockedRegistries cannot be used for image pull and push actions. All other registries are permitted. + + Only one of BlockedRegistries or AllowedRegistries may be set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + containerRuntimeSearchRegistries: + description: |- + containerRuntimeSearchRegistries are registries that will be searched when pulling images that do not have fully qualified + domains in their pull specs. Registries will be searched in the order provided in the list. + Note: this search list only works with the container runtime, i.e CRI-O. Will NOT work with builds or imagestream imports. + format: hostname + items: + type: string + minItems: 1 + type: array + x-kubernetes-list-type: set + insecureRegistries: + description: insecureRegistries are registries which + do not have a valid TLS certificates or only support + HTTP connections. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-validations: + - message: Only one of blockedRegistries or allowedRegistries + may be set + rule: 'has(self.blockedRegistries) ? !has(self.allowedRegistries) + : true' + type: object + ingress: + description: |- + ingress holds cluster-wide information about ingress, including the default ingress domain + used for routes. + properties: + appsDomain: + description: |- + appsDomain is an optional domain to use instead of the one specified + in the domain field when a Route is created without specifying an explicit + host. If appsDomain is nonempty, this value is used to generate default + host values for Route. Unlike domain, appsDomain may be modified after + installation. + This assumes a new ingresscontroller has been setup with a wildcard + certificate. + type: string + componentRoutes: + description: |- + componentRoutes is an optional list of routes that are managed by OpenShift components + that a cluster-admin is able to configure the hostname and serving certificate for. + The namespace and name of each route in this list should match an existing entry in the + status.componentRoutes list. + + To determine the set of configurable Routes, look at namespace and name of entries in the + .status.componentRoutes list, where participating operators write the status of + configurable routes. + items: + description: ComponentRouteSpec allows for configuration + of a route's hostname and serving certificate. + properties: + hostname: + description: hostname is the hostname that should + be used by the route. + pattern: ^([a-zA-Z0-9\p{S}\p{L}]((-?[a-zA-Z0-9\p{S}\p{L}]{0,62})?)|([a-zA-Z0-9\p{S}\p{L}](([a-zA-Z0-9-\p{S}\p{L}]{0,61}[a-zA-Z0-9\p{S}\p{L}])?)(\.)){1,}([a-zA-Z\p{L}]){2,63})$|^(([a-z0-9][-a-z0-9]{0,61}[a-z0-9]|[a-z0-9]{1,63})[\.]){0,}([a-z0-9][-a-z0-9]{0,61}[a-z0-9]|[a-z0-9]{1,63})$ + type: string + name: + description: |- + name is the logical name of the route to customize. + + The namespace and name of this componentRoute must match a corresponding + entry in the list of status.componentRoutes if the route is to be customized. + maxLength: 256 + minLength: 1 + type: string + namespace: + description: |- + namespace is the namespace of the route to customize. + + The namespace and name of this componentRoute must match a corresponding + entry in the list of status.componentRoutes if the route is to be customized. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + servingCertKeyPairSecret: + description: |- + servingCertKeyPairSecret is a reference to a secret of type `kubernetes.io/tls` in the openshift-config namespace. + The serving cert/key pair must match and will be used by the operator to fulfill the intent of serving with this name. + If the custom hostname uses the default routing suffix of the cluster, + the Secret specification for a serving certificate will not be needed. + properties: + name: + description: name is the metadata.name of the + referenced secret + type: string + required: + - name + type: object + required: + - hostname + - name + - namespace + type: object + type: array + x-kubernetes-list-map-keys: + - namespace + - name + x-kubernetes-list-type: map + domain: + description: |- + domain is used to generate a default host name for a route when the + route's host name is empty. The generated host name will follow this + pattern: "..". + + It is also used as the default wildcard domain suffix for ingress. The + default ingresscontroller domain will follow this pattern: "*.". + + Once set, changing domain is not currently supported. + type: string + x-kubernetes-validations: + - message: domain is immutable once set + rule: self == oldSelf + loadBalancer: + description: |- + loadBalancer contains the load balancer details in general which are not only specific to the underlying infrastructure + provider of the current cluster and are required for Ingress Controller to work on OpenShift. + properties: + platform: + description: |- + platform holds configuration specific to the underlying + infrastructure provider for the ingress load balancers. + When omitted, this means the user has no opinion and the platform is left + to choose reasonable defaults. These defaults are subject to change over time. + properties: + aws: + description: aws contains settings specific to + the Amazon Web Services infrastructure provider. + properties: + type: + description: |- + type allows user to set a load balancer type. + When this field is set the default ingresscontroller will get created using the specified LBType. + If this field is not set then the default ingress controller of LBType Classic will be created. + Valid values are: + + * "Classic": A Classic Load Balancer that makes routing decisions at either + the transport layer (TCP/SSL) or the application layer (HTTP/HTTPS). See + the following for additional details: + + https://docs.aws.amazon.com/AmazonECS/latest/developerguide/load-balancer-types.html#clb + + * "NLB": A Network Load Balancer that makes routing decisions at the + transport layer (TCP/SSL). See the following for additional details: + + https://docs.aws.amazon.com/AmazonECS/latest/developerguide/load-balancer-types.html#nlb + enum: + - NLB + - Classic + type: string + required: + - type + type: object + type: + description: |- + type is the underlying infrastructure provider for the cluster. + Allowed values are "AWS", "Azure", "BareMetal", "GCP", "Libvirt", + "OpenStack", "VSphere", "oVirt", "KubeVirt", "EquinixMetal", "PowerVS", + "AlibabaCloud", "Nutanix" and "None". Individual components may not support all platforms, + and must handle unrecognized platforms as None if they do not support that platform. + enum: + - "" + - AWS + - Azure + - BareMetal + - GCP + - Libvirt + - OpenStack + - None + - VSphere + - oVirt + - IBMCloud + - KubeVirt + - EquinixMetal + - PowerVS + - AlibabaCloud + - Nutanix + - External + type: string + type: object + type: object + requiredHSTSPolicies: + description: |- + requiredHSTSPolicies specifies HSTS policies that are required to be set on newly created or updated routes + matching the domainPattern/s and namespaceSelector/s that are specified in the policy. + Each requiredHSTSPolicy must have at least a domainPattern and a maxAge to validate a route HSTS Policy route + annotation, and affect route admission. + + A candidate route is checked for HSTS Policies if it has the HSTS Policy route annotation: + "haproxy.router.openshift.io/hsts_header" + E.g. haproxy.router.openshift.io/hsts_header: max-age=31536000;preload;includeSubDomains + + - For each candidate route, if it matches a requiredHSTSPolicy domainPattern and optional namespaceSelector, + then the maxAge, preloadPolicy, and includeSubdomainsPolicy must be valid to be admitted. Otherwise, the route + is rejected. + - The first match, by domainPattern and optional namespaceSelector, in the ordering of the RequiredHSTSPolicies + determines the route's admission status. + - If the candidate route doesn't match any requiredHSTSPolicy domainPattern and optional namespaceSelector, + then it may use any HSTS Policy annotation. + + The HSTS policy configuration may be changed after routes have already been created. An update to a previously + admitted route may then fail if the updated route does not conform to the updated HSTS policy configuration. + However, changing the HSTS policy configuration will not cause a route that is already admitted to stop working. + + Note that if there are no RequiredHSTSPolicies, any HSTS Policy annotation on the route is valid. + items: + properties: + domainPatterns: + description: |- + domainPatterns is a list of domains for which the desired HSTS annotations are required. + If domainPatterns is specified and a route is created with a spec.host matching one of the domains, + the route must specify the HSTS Policy components described in the matching RequiredHSTSPolicy. + + The use of wildcards is allowed like this: *.foo.com matches everything under foo.com. + foo.com only matches foo.com, so to cover foo.com and everything under it, you must specify *both*. + items: + type: string + minItems: 1 + type: array + includeSubDomainsPolicy: + description: |- + includeSubDomainsPolicy means the HSTS Policy should apply to any subdomains of the host's + domain name. Thus, for the host bar.foo.com, if includeSubDomainsPolicy was set to RequireIncludeSubDomains: + - the host app.bar.foo.com would inherit the HSTS Policy of bar.foo.com + - the host bar.foo.com would inherit the HSTS Policy of bar.foo.com + - the host foo.com would NOT inherit the HSTS Policy of bar.foo.com + - the host def.foo.com would NOT inherit the HSTS Policy of bar.foo.com + enum: + - RequireIncludeSubDomains + - RequireNoIncludeSubDomains + - NoOpinion + type: string + maxAge: + description: |- + maxAge is the delta time range in seconds during which hosts are regarded as HSTS hosts. + If set to 0, it negates the effect, and hosts are removed as HSTS hosts. + If set to 0 and includeSubdomains is specified, all subdomains of the host are also removed as HSTS hosts. + maxAge is a time-to-live value, and if this policy is not refreshed on a client, the HSTS + policy will eventually expire on that client. + properties: + largestMaxAge: + description: |- + The largest allowed value (in seconds) of the RequiredHSTSPolicy max-age + This value can be left unspecified, in which case no upper limit is enforced. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + smallestMaxAge: + description: |- + The smallest allowed value (in seconds) of the RequiredHSTSPolicy max-age + Setting max-age=0 allows the deletion of an existing HSTS header from a host. This is a necessary + tool for administrators to quickly correct mistakes. + This value can be left unspecified, in which case no lower limit is enforced. + format: int32 + maximum: 2147483647 + minimum: 0 + type: integer + type: object + namespaceSelector: + description: |- + namespaceSelector specifies a label selector such that the policy applies only to those routes that + are in namespaces with labels that match the selector, and are in one of the DomainPatterns. + Defaults to the empty LabelSelector, which matches everything. + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are + ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that + the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + preloadPolicy: + description: |- + preloadPolicy directs the client to include hosts in its host preload list so that + it never needs to do an initial load to get the HSTS header (note that this is not defined + in RFC 6797 and is therefore client implementation-dependent). + enum: + - RequirePreload + - RequireNoPreload + - NoOpinion + type: string + required: + - domainPatterns + type: object + type: array + type: object + network: + description: |- + network holds cluster-wide information about the network. It is used to configure the desired network configuration, such as: IP address pools for services/pod IPs, network plugin, etc. + Please view network.spec for an explanation on what applies when configuring this resource. + properties: + clusterNetwork: + description: |- + IP address pool to use for pod IPs. + This field is immutable after installation. + items: + description: |- + ClusterNetworkEntry is a contiguous block of IP addresses from which pod IPs + are allocated. + properties: + cidr: + description: The complete block for pod IPs. + type: string + hostPrefix: + description: |- + The size (prefix) of block to allocate to each node. If this + field is not used by the plugin, it can be left unset. + format: int32 + minimum: 0 + type: integer + type: object + type: array + x-kubernetes-list-type: atomic + externalIP: + description: |- + externalIP defines configuration for controllers that + affect Service.ExternalIP. If nil, then ExternalIP is + not allowed to be set. + properties: + autoAssignCIDRs: + description: |- + autoAssignCIDRs is a list of CIDRs from which to automatically assign + Service.ExternalIP. These are assigned when the service is of type + LoadBalancer. In general, this is only useful for bare-metal clusters. + In Openshift 3.x, this was misleadingly called "IngressIPs". + Automatically assigned External IPs are not affected by any + ExternalIPPolicy rules. + Currently, only one entry may be provided. + items: + type: string + type: array + x-kubernetes-list-type: atomic + policy: + description: |- + policy is a set of restrictions applied to the ExternalIP field. + If nil or empty, then ExternalIP is not allowed to be set. + properties: + allowedCIDRs: + description: allowedCIDRs is the list of allowed + CIDRs. + items: + type: string + type: array + x-kubernetes-list-type: atomic + rejectedCIDRs: + description: |- + rejectedCIDRs is the list of disallowed CIDRs. These take precedence + over allowedCIDRs. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + type: object + networkDiagnostics: + description: |- + networkDiagnostics defines network diagnostics configuration. + + Takes precedence over spec.disableNetworkDiagnostics in network.operator.openshift.io. + If networkDiagnostics is not specified or is empty, + and the spec.disableNetworkDiagnostics flag in network.operator.openshift.io is set to true, + the network diagnostics feature will be disabled. + properties: + mode: + description: |- + mode controls the network diagnostics mode + + When omitted, this means the user has no opinion and the platform is left + to choose reasonable defaults. These defaults are subject to change over time. + The current default is All. + enum: + - "" + - All + - Disabled + type: string + sourcePlacement: + description: |- + sourcePlacement controls the scheduling of network diagnostics source deployment + + See NetworkDiagnosticsSourcePlacement for more details about default values. + properties: + nodeSelector: + additionalProperties: + type: string + description: |- + nodeSelector is the node selector applied to network diagnostics components + + When omitted, this means the user has no opinion and the platform is left + to choose reasonable defaults. These defaults are subject to change over time. + The current default is `kubernetes.io/os: linux`. + type: object + tolerations: + description: |- + tolerations is a list of tolerations applied to network diagnostics components + + When omitted, this means the user has no opinion and the platform is left + to choose reasonable defaults. These defaults are subject to change over time. + The current default is an empty list. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists, Equal, Lt, and Gt. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + Lt and Gt perform numeric comparisons (requires feature gate TaintTolerationComparisonOperators). + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + x-kubernetes-list-type: atomic + type: object + targetPlacement: + description: |- + targetPlacement controls the scheduling of network diagnostics target daemonset + + See NetworkDiagnosticsTargetPlacement for more details about default values. + properties: + nodeSelector: + additionalProperties: + type: string + description: |- + nodeSelector is the node selector applied to network diagnostics components + + When omitted, this means the user has no opinion and the platform is left + to choose reasonable defaults. These defaults are subject to change over time. + The current default is `kubernetes.io/os: linux`. + type: object + tolerations: + description: |- + tolerations is a list of tolerations applied to network diagnostics components + + When omitted, this means the user has no opinion and the platform is left + to choose reasonable defaults. These defaults are subject to change over time. + The current default is `- operator: "Exists"` which means that all taints are tolerated. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists, Equal, Lt, and Gt. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + Lt and Gt perform numeric comparisons (requires feature gate TaintTolerationComparisonOperators). + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + networkType: + description: |- + networkType is the plugin that is to be deployed (e.g. OVNKubernetes). + This should match a value that the cluster-network-operator understands, + or else no networking will be installed. + Currently supported values are: + - OVNKubernetes + This field is immutable after installation. + type: string + serviceNetwork: + description: |- + IP address pool for services. + Currently, we only support a single entry here. + This field is immutable after installation. + items: + type: string + type: array + x-kubernetes-list-type: atomic + serviceNodePortRange: + description: |- + The port range allowed for Services of type NodePort. + If not specified, the default of 30000-32767 will be used. + Such Services without a NodePort specified will have one + automatically allocated from this range. + This parameter can be updated after the cluster is + installed. + pattern: ^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])-([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$ + type: string + type: object + x-kubernetes-validations: + - message: cannot set networkDiagnostics.sourcePlacement and + networkDiagnostics.targetPlacement when networkDiagnostics.mode + is Disabled + rule: '!has(self.networkDiagnostics) || !has(self.networkDiagnostics.mode) + || self.networkDiagnostics.mode!=''Disabled'' || !has(self.networkDiagnostics.sourcePlacement) + && !has(self.networkDiagnostics.targetPlacement)' + oauth: + description: |- + oauth holds cluster-wide information about OAuth. + It is used to configure the integrated OAuth server. + This configuration is only honored when the top level Authentication config has type set to IntegratedOAuth. + properties: + identityProviders: + description: |- + identityProviders is an ordered list of ways for a user to identify themselves. + When this list is empty, no identities are provisioned for users. + items: + description: IdentityProvider provides identities for + users authenticating using credentials + properties: + basicAuth: + description: basicAuth contains configuration options + for the BasicAuth IdP + properties: + ca: + description: |- + ca is an optional reference to a config map by name containing the PEM-encoded CA bundle. + It is used as a trust anchor to validate the TLS certificate presented by the remote server. + The key "ca.crt" is used to locate the data. + If specified and the config map or expected key is not found, the identity provider is not honored. + If the specified ca data is not valid, the identity provider is not honored. + If empty, the default system roots are used. + The namespace for this config map is openshift-config. + properties: + name: + description: name is the metadata.name of + the referenced config map + type: string + required: + - name + type: object + tlsClientCert: + description: |- + tlsClientCert is an optional reference to a secret by name that contains the + PEM-encoded TLS client certificate to present when connecting to the server. + The key "tls.crt" is used to locate the data. + If specified and the secret or expected key is not found, the identity provider is not honored. + If the specified certificate data is not valid, the identity provider is not honored. + The namespace for this secret is openshift-config. + properties: + name: + description: name is the metadata.name of + the referenced secret + type: string + required: + - name + type: object + tlsClientKey: + description: |- + tlsClientKey is an optional reference to a secret by name that contains the + PEM-encoded TLS private key for the client certificate referenced in tlsClientCert. + The key "tls.key" is used to locate the data. + If specified and the secret or expected key is not found, the identity provider is not honored. + If the specified certificate data is not valid, the identity provider is not honored. + The namespace for this secret is openshift-config. + properties: + name: + description: name is the metadata.name of + the referenced secret + type: string + required: + - name + type: object + url: + description: url is the remote URL to connect + to + type: string + type: object + github: + description: github enables user authentication + using GitHub credentials + properties: + ca: + description: |- + ca is an optional reference to a config map by name containing the PEM-encoded CA bundle. + It is used as a trust anchor to validate the TLS certificate presented by the remote server. + The key "ca.crt" is used to locate the data. + If specified and the config map or expected key is not found, the identity provider is not honored. + If the specified ca data is not valid, the identity provider is not honored. + If empty, the default system roots are used. + This can only be configured when hostname is set to a non-empty value. + The namespace for this config map is openshift-config. + properties: + name: + description: name is the metadata.name of + the referenced config map + type: string + required: + - name + type: object + clientID: + description: clientID is the oauth client ID + type: string + clientSecret: + description: |- + clientSecret is a required reference to the secret by name containing the oauth client secret. + The key "clientSecret" is used to locate the data. + If the secret or expected key is not found, the identity provider is not honored. + The namespace for this secret is openshift-config. + properties: + name: + description: name is the metadata.name of + the referenced secret + type: string + required: + - name + type: object + hostname: + description: |- + hostname is the optional domain (e.g. "mycompany.com") for use with a hosted instance of + GitHub Enterprise. + It must match the GitHub Enterprise settings value configured at /setup/settings#hostname. + type: string + organizations: + description: organizations optionally restricts + which organizations are allowed to log in + items: + type: string + type: array + teams: + description: teams optionally restricts which + teams are allowed to log in. Format is /. + items: + type: string + type: array + type: object + gitlab: + description: gitlab enables user authentication + using GitLab credentials + properties: + ca: + description: |- + ca is an optional reference to a config map by name containing the PEM-encoded CA bundle. + It is used as a trust anchor to validate the TLS certificate presented by the remote server. + The key "ca.crt" is used to locate the data. + If specified and the config map or expected key is not found, the identity provider is not honored. + If the specified ca data is not valid, the identity provider is not honored. + If empty, the default system roots are used. + The namespace for this config map is openshift-config. + properties: + name: + description: name is the metadata.name of + the referenced config map + type: string + required: + - name + type: object + clientID: + description: clientID is the oauth client ID + type: string + clientSecret: + description: |- + clientSecret is a required reference to the secret by name containing the oauth client secret. + The key "clientSecret" is used to locate the data. + If the secret or expected key is not found, the identity provider is not honored. + The namespace for this secret is openshift-config. + properties: + name: + description: name is the metadata.name of + the referenced secret + type: string + required: + - name + type: object + url: + description: url is the oauth server base URL + type: string + type: object + google: + description: google enables user authentication + using Google credentials + properties: + clientID: + description: clientID is the oauth client ID + type: string + clientSecret: + description: |- + clientSecret is a required reference to the secret by name containing the oauth client secret. + The key "clientSecret" is used to locate the data. + If the secret or expected key is not found, the identity provider is not honored. + The namespace for this secret is openshift-config. + properties: + name: + description: name is the metadata.name of + the referenced secret + type: string + required: + - name + type: object + hostedDomain: + description: hostedDomain is the optional Google + App domain (e.g. "mycompany.com") to restrict + logins to + type: string + type: object + htpasswd: + description: htpasswd enables user authentication + using an HTPasswd file to validate credentials + properties: + fileData: + description: |- + fileData is a required reference to a secret by name containing the data to use as the htpasswd file. + The key "htpasswd" is used to locate the data. + If the secret or expected key is not found, the identity provider is not honored. + If the specified htpasswd data is not valid, the identity provider is not honored. + The namespace for this secret is openshift-config. + properties: + name: + description: name is the metadata.name of + the referenced secret + type: string + required: + - name + type: object + type: object + keystone: + description: keystone enables user authentication + using keystone password credentials + properties: + ca: + description: |- + ca is an optional reference to a config map by name containing the PEM-encoded CA bundle. + It is used as a trust anchor to validate the TLS certificate presented by the remote server. + The key "ca.crt" is used to locate the data. + If specified and the config map or expected key is not found, the identity provider is not honored. + If the specified ca data is not valid, the identity provider is not honored. + If empty, the default system roots are used. + The namespace for this config map is openshift-config. + properties: + name: + description: name is the metadata.name of + the referenced config map + type: string + required: + - name + type: object + domainName: + description: domainName is required for keystone + v3 + type: string + tlsClientCert: + description: |- + tlsClientCert is an optional reference to a secret by name that contains the + PEM-encoded TLS client certificate to present when connecting to the server. + The key "tls.crt" is used to locate the data. + If specified and the secret or expected key is not found, the identity provider is not honored. + If the specified certificate data is not valid, the identity provider is not honored. + The namespace for this secret is openshift-config. + properties: + name: + description: name is the metadata.name of + the referenced secret + type: string + required: + - name + type: object + tlsClientKey: + description: |- + tlsClientKey is an optional reference to a secret by name that contains the + PEM-encoded TLS private key for the client certificate referenced in tlsClientCert. + The key "tls.key" is used to locate the data. + If specified and the secret or expected key is not found, the identity provider is not honored. + If the specified certificate data is not valid, the identity provider is not honored. + The namespace for this secret is openshift-config. + properties: + name: + description: name is the metadata.name of + the referenced secret + type: string + required: + - name + type: object + url: + description: url is the remote URL to connect + to + type: string + type: object + ldap: + description: ldap enables user authentication using + LDAP credentials + properties: + attributes: + description: attributes maps LDAP attributes + to identities + properties: + email: + description: |- + email is the list of attributes whose values should be used as the email address. Optional. + If unspecified, no email is set for the identity + items: + type: string + type: array + id: + description: |- + id is the list of attributes whose values should be used as the user ID. Required. + First non-empty attribute is used. At least one attribute is required. If none of the listed + attribute have a value, authentication fails. + LDAP standard identity attribute is "dn" + items: + type: string + type: array + name: + description: |- + name is the list of attributes whose values should be used as the display name. Optional. + If unspecified, no display name is set for the identity + LDAP standard display name attribute is "cn" + items: + type: string + type: array + preferredUsername: + description: |- + preferredUsername is the list of attributes whose values should be used as the preferred username. + LDAP standard login attribute is "uid" + items: + type: string + type: array + type: object + bindDN: + description: bindDN is an optional DN to bind + with during the search phase. + type: string + bindPassword: + description: |- + bindPassword is an optional reference to a secret by name + containing a password to bind with during the search phase. + The key "bindPassword" is used to locate the data. + If specified and the secret or expected key is not found, the identity provider is not honored. + The namespace for this secret is openshift-config. + properties: + name: + description: name is the metadata.name of + the referenced secret + type: string + required: + - name + type: object + ca: + description: |- + ca is an optional reference to a config map by name containing the PEM-encoded CA bundle. + It is used as a trust anchor to validate the TLS certificate presented by the remote server. + The key "ca.crt" is used to locate the data. + If specified and the config map or expected key is not found, the identity provider is not honored. + If the specified ca data is not valid, the identity provider is not honored. + If empty, the default system roots are used. + The namespace for this config map is openshift-config. + properties: + name: + description: name is the metadata.name of + the referenced config map + type: string + required: + - name + type: object + insecure: + description: |- + insecure, if true, indicates the connection should not use TLS + WARNING: Should not be set to `true` with the URL scheme "ldaps://" as "ldaps://" URLs always + attempt to connect using TLS, even when `insecure` is set to `true` + When `true`, "ldap://" URLS connect insecurely. When `false`, "ldap://" URLs are upgraded to + a TLS connection using StartTLS as specified in https://tools.ietf.org/html/rfc2830. + type: boolean + url: + description: |- + url is an RFC 2255 URL which specifies the LDAP search parameters to use. + The syntax of the URL is: + ldap://host:port/basedn?attribute?scope?filter + type: string + type: object + mappingMethod: + description: |- + mappingMethod determines how identities from this provider are mapped to users + Defaults to "claim" type: string - mode: - format: int32 - type: integer - overwrite: - type: boolean - path: + name: + description: |- + name is used to qualify the identities returned by this provider. + - It MUST be unique and not shared by any other identity provider used + - It MUST be a valid path segment: name cannot equal "." or ".." or contain "/" or "%" or ":" + Ref: https://godoc.org/github.com/openshift/origin/pkg/user/apis/user/validation#ValidateIdentityProviderName type: string - user: + openID: + description: openID enables user authentication + using OpenID credentials + properties: + ca: + description: |- + ca is an optional reference to a config map by name containing the PEM-encoded CA bundle. + It is used as a trust anchor to validate the TLS certificate presented by the remote server. + The key "ca.crt" is used to locate the data. + If specified and the config map or expected key is not found, the identity provider is not honored. + If the specified ca data is not valid, the identity provider is not honored. + If empty, the default system roots are used. + The namespace for this config map is openshift-config. + properties: + name: + description: name is the metadata.name of + the referenced config map + type: string + required: + - name + type: object + claims: + description: claims mappings + properties: + email: + description: |- + email is the list of claims whose values should be used as the email address. Optional. + If unspecified, no email is set for the identity + items: + type: string + type: array + x-kubernetes-list-type: atomic + groups: + description: |- + groups is the list of claims value of which should be used to synchronize groups + from the OIDC provider to OpenShift for the user. + If multiple claims are specified, the first one with a non-empty value is used. + items: + description: |- + OpenIDClaim represents a claim retrieved from an OpenID provider's tokens or userInfo + responses + minLength: 1 + type: string + type: array + x-kubernetes-list-type: atomic + name: + description: |- + name is the list of claims whose values should be used as the display name. Optional. + If unspecified, no display name is set for the identity + items: + type: string + type: array + x-kubernetes-list-type: atomic + preferredUsername: + description: |- + preferredUsername is the list of claims whose values should be used as the preferred username. + If unspecified, the preferred username is determined from the value of the sub claim + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + clientID: + description: clientID is the oauth client ID + type: string + clientSecret: + description: |- + clientSecret is a required reference to the secret by name containing the oauth client secret. + The key "clientSecret" is used to locate the data. + If the secret or expected key is not found, the identity provider is not honored. + The namespace for this secret is openshift-config. + properties: + name: + description: name is the metadata.name of + the referenced secret + type: string + required: + - name + type: object + extraAuthorizeParameters: + additionalProperties: + type: string + description: extraAuthorizeParameters are any + custom parameters to add to the authorize + request. + type: object + extraScopes: + description: extraScopes are any scopes to request + in addition to the standard "openid" scope. + items: + type: string + type: array + issuer: + description: |- + issuer is the URL that the OpenID Provider asserts as its Issuer Identifier. + It must use the https scheme with no query or fragment component. + type: string + type: object + requestHeader: + description: requestHeader enables user authentication + using request header credentials + properties: + ca: + description: |- + ca is a required reference to a config map by name containing the PEM-encoded CA bundle. + It is used as a trust anchor to validate the TLS certificate presented by the remote server. + Specifically, it allows verification of incoming requests to prevent header spoofing. + The key "ca.crt" is used to locate the data. + If the config map or expected key is not found, the identity provider is not honored. + If the specified ca data is not valid, the identity provider is not honored. + The namespace for this config map is openshift-config. + properties: + name: + description: name is the metadata.name of + the referenced config map + type: string + required: + - name + type: object + challengeURL: + description: |- + challengeURL is a URL to redirect unauthenticated /authorize requests to + Unauthenticated requests from OAuth clients which expect WWW-Authenticate challenges will be + redirected here. + ${url} is replaced with the current URL, escaped to be safe in a query parameter + https://www.example.com/sso-login?then=${url} + ${query} is replaced with the current query string + https://www.example.com/auth-proxy/oauth/authorize?${query} + Required when challenge is set to true. + type: string + clientCommonNames: + description: |- + clientCommonNames is an optional list of common names to require a match from. If empty, any + client certificate validated against the clientCA bundle is considered authoritative. + items: + type: string + type: array + emailHeaders: + description: emailHeaders is the set of headers + to check for the email address + items: + type: string + type: array + headers: + description: headers is the set of headers to + check for identity information + items: + type: string + type: array + loginURL: + description: |- + loginURL is a URL to redirect unauthenticated /authorize requests to + Unauthenticated requests from OAuth clients which expect interactive logins will be redirected here + ${url} is replaced with the current URL, escaped to be safe in a query parameter + https://www.example.com/sso-login?then=${url} + ${query} is replaced with the current query string + https://www.example.com/auth-proxy/oauth/authorize?${query} + Required when login is set to true. + type: string + nameHeaders: + description: nameHeaders is the set of headers + to check for the display name + items: + type: string + type: array + preferredUsernameHeaders: + description: preferredUsernameHeaders is the + set of headers to check for the preferred + username + items: + type: string + type: array + type: object + type: + description: type identifies the identity provider + type for this entry. type: string - required: - - path type: object - maxItems: 256 type: array - kernelArguments: - items: - type: string - maxItems: 128 - type: array - kernelType: - type: string - systemdUnits: + x-kubernetes-list-type: atomic + templates: + description: templates allow you to customize pages like + the login page. + properties: + error: + description: |- + error is the name of a secret that specifies a go template to use to render error pages + during the authentication or grant flow. + The key "errors.html" is used to locate the template data. + If specified and the secret or expected key is not found, the default error page is used. + If the specified template is not valid, the default error page is used. + If unspecified, the default error page is used. + The namespace for this secret is openshift-config. + properties: + name: + description: name is the metadata.name of the + referenced secret + type: string + required: + - name + type: object + login: + description: |- + login is the name of a secret that specifies a go template to use to render the login page. + The key "login.html" is used to locate the template data. + If specified and the secret or expected key is not found, the default login page is used. + If the specified template is not valid, the default login page is used. + If unspecified, the default login page is used. + The namespace for this secret is openshift-config. + properties: + name: + description: name is the metadata.name of the + referenced secret + type: string + required: + - name + type: object + providerSelection: + description: |- + providerSelection is the name of a secret that specifies a go template to use to render + the provider selection page. + The key "providers.html" is used to locate the template data. + If specified and the secret or expected key is not found, the default provider selection page is used. + If the specified template is not valid, the default provider selection page is used. + If unspecified, the default provider selection page is used. + The namespace for this secret is openshift-config. + properties: + name: + description: name is the metadata.name of the + referenced secret + type: string + required: + - name + type: object + type: object + tokenConfig: + description: tokenConfig contains options for authorization + and access tokens + properties: + accessTokenInactivityTimeout: + description: |- + accessTokenInactivityTimeout defines the token inactivity timeout + for tokens granted by any client. + The value represents the maximum amount of time that can occur between + consecutive uses of the token. Tokens become invalid if they are not + used within this temporal window. The user will need to acquire a new + token to regain access once a token times out. Takes valid time + duration string such as "5m", "1.5h" or "2h45m". The minimum allowed + value for duration is 300s (5 minutes). If the timeout is configured + per client, then that value takes precedence. If the timeout value is + not specified and the client does not override the value, then tokens + are valid until their lifetime. + + WARNING: existing tokens' timeout will not be affected (lowered) by changing this value + type: string + accessTokenInactivityTimeoutSeconds: + description: 'accessTokenInactivityTimeoutSeconds + - DEPRECATED: setting this field has no effect.' + format: int32 + type: integer + accessTokenMaxAgeSeconds: + description: accessTokenMaxAgeSeconds defines the + maximum age of access tokens + format: int32 + type: integer + type: object + type: object + x-kubernetes-validations: + - message: spec.configuration.oauth.tokenConfig.accessTokenInactivityTimeout + minimum acceptable token timeout value is 300 seconds + rule: '!has(self.tokenConfig) || !has(self.tokenConfig.accessTokenInactivityTimeout) + || duration(self.tokenConfig.accessTokenInactivityTimeout).getSeconds() + >= 300' + operatorhub: + description: |- + operatorhub specifies the configuration for the Operator Lifecycle Manager in the HostedCluster. This is only configured at deployment time but the controller are not reconcilling over it. + The OperatorHub configuration will be constantly reconciled if catalog placement is management, but only on cluster creation otherwise. + properties: + disableAllDefaultSources: + description: |- + disableAllDefaultSources allows you to disable all the default hub + sources. If this is true, a specific entry in sources can be used to + enable a default source. If this is false, a specific entry in + sources can be used to disable or enable a default source. + type: boolean + sources: + description: |- + sources is the list of default hub sources and their configuration. + If the list is empty, it implies that the default hub sources are + enabled on the cluster unless disableAllDefaultSources is true. + If disableAllDefaultSources is true and sources is not empty, + the configuration present in sources will take precedence. The list of + default hub sources and their current state will always be reflected in + the status block. items: + description: HubSource is used to specify the hub source + and its configuration properties: - contents: - maxLength: 65536 - type: string - dropins: - items: - properties: - contents: - maxLength: 32768 - type: string - name: - type: string - required: - - name - type: object - maxItems: 16 - type: array - enabled: + disabled: + description: disabled is used to disable a default + hub source on cluster type: boolean name: + description: name is the name of one of the default + hub sources + maxLength: 253 + minLength: 1 type: string - required: - - name type: object - maxItems: 64 type: array type: object - network: - description: network contains the configuration for cluster - networking. - type: object - oauth: - description: oauth contains the configuration for OAuth. - type: object proxy: - description: proxy contains the configuration for the cluster-wide - proxy. + description: |- + proxy holds cluster-wide information on how to configure default proxies for the cluster. + This affects traffic flowing from the hosted cluster data plane. + The controllers will generate a machineConfig with the proxy config for the cluster. + This MachineConfig will be part of every payload generated by the controllers for any NodePool of the HostedCluster. + Changing this value will trigger a rollout for all existing NodePools in the cluster. + properties: + httpProxy: + description: httpProxy is the URL of the proxy for HTTP + requests. Empty means unset and will not result in + an env var. + type: string + httpsProxy: + description: httpsProxy is the URL of the proxy for HTTPS + requests. Empty means unset and will not result in + an env var. + type: string + noProxy: + description: |- + noProxy is a comma-separated list of hostnames and/or CIDRs and/or IPs for which the proxy should not be used. + Empty means unset and will not result in an env var. + type: string + readinessEndpoints: + description: readinessEndpoints is a list of endpoints + used to verify readiness of the proxy. + items: + type: string + type: array + trustedCA: + description: |- + trustedCA is a reference to a ConfigMap containing a CA certificate bundle. + The trustedCA field should only be consumed by a proxy validator. The + validator is responsible for reading the certificate bundle from the required + key "ca-bundle.crt", merging it with the system default trust bundle, + and writing the merged trust bundle to a ConfigMap named "trusted-ca-bundle" + in the "openshift-config-managed" namespace. Clients that expect to make + proxy connections must use the trusted-ca-bundle for all HTTPS requests to + the proxy, and may use the trusted-ca-bundle for non-proxy HTTPS requests as + well. + + The namespace for the ConfigMap referenced by trustedCA is + "openshift-config". Here is an example ConfigMap (in yaml): + + apiVersion: v1 + kind: ConfigMap + metadata: + name: user-ca-bundle + namespace: openshift-config + data: + ca-bundle.crt: | + -----BEGIN CERTIFICATE----- + Custom CA certificate bundle. + -----END CERTIFICATE----- + properties: + name: + description: name is the metadata.name of the referenced + config map + type: string + required: + - name + type: object type: object scheduler: - description: scheduler contains the configuration for scheduler. + description: |- + scheduler holds cluster-wide config information to run the Kubernetes Scheduler + and influence its placement decisions. The canonical name for this config is `cluster`. + properties: + defaultNodeSelector: + description: |- + defaultNodeSelector helps set the cluster-wide default node selector to + restrict pod placement to specific nodes. This is applied to the pods + created in all namespaces and creates an intersection with any existing + nodeSelectors already set on a pod, additionally constraining that pod's selector. + For example, + defaultNodeSelector: "type=user-node,region=east" would set nodeSelector + field in pod spec to "type=user-node,region=east" to all pods created + in all namespaces. Namespaces having project-wide node selectors won't be + impacted even if this field is set. This adds an annotation section to + the namespace. + For example, if a new namespace is created with + node-selector='type=user-node,region=east', + the annotation openshift.io/node-selector: type=user-node,region=east + gets added to the project. When the openshift.io/node-selector annotation + is set on the project the value is used in preference to the value we are setting + for defaultNodeSelector field. + For instance, + openshift.io/node-selector: "type=user-node,region=west" means + that the default of "type=user-node,region=east" set in defaultNodeSelector + would not be applied. + type: string + mastersSchedulable: + description: |- + mastersSchedulable allows masters nodes to be schedulable. When this flag is + turned on, all the master nodes in the cluster will be made schedulable, + so that workload pods can run on them. The default value for this field is false, + meaning none of the master nodes are schedulable. + Important Note: Once the workload pods start running on the master nodes, + extreme care must be taken to ensure that cluster-critical control plane components + are not impacted. + Please turn on this field after doing due diligence. + type: boolean + policy: + description: |- + DEPRECATED: the scheduler Policy API has been deprecated and will be removed in a future release. + policy is a reference to a ConfigMap containing scheduler policy which has + user specified predicates and priorities. If this ConfigMap is not available + scheduler will default to use DefaultAlgorithmProvider. + The namespace for this configmap is openshift-config. + properties: + name: + description: name is the metadata.name of the referenced + config map + type: string + required: + - name + type: object + profile: + description: |- + profile sets which scheduling profile should be set in order to configure scheduling + decisions for new pods. + + Valid values are "LowNodeUtilization", "HighNodeUtilization", "NoScoring" + Defaults to "LowNodeUtilization" + enum: + - "" + - LowNodeUtilization + - HighNodeUtilization + - NoScoring + type: string + profileCustomizations: + description: |- + profileCustomizations contains configuration for modifying the default behavior of existing scheduler profiles. + Deprecated: no longer needed, since DRA is GA starting with 4.21, and + is enabled by' default in the cluster, this field will be removed in 4.24. + properties: + dynamicResourceAllocation: + description: |- + dynamicResourceAllocation allows to enable or disable dynamic resource allocation within the scheduler. + Dynamic resource allocation is an API for requesting and sharing resources between pods and containers inside a pod. + Third-party resource drivers are responsible for tracking and allocating resources. + Different kinds of resources support arbitrary parameters for defining requirements and initialization. + Valid values are Enabled, Disabled and omitted. + When omitted, this means no opinion and the platform is left to choose a reasonable default, + which is subject to change over time. + The current default is Disabled. + enum: + - "" + - Enabled + - Disabled + type: string + type: object type: object type: object controlPlaneRelease: @@ -1107,7 +3608,6 @@ spec: required: - source type: object - maxItems: 50 type: array infraID: description: infraID is a globally unique identifier for the cluster. @@ -1134,7 +3634,6 @@ spec: type: string description: labels when specified, define what custom labels are added to the hcp pods. - maxProperties: 100 type: object networking: description: networking specifies network configuration for the @@ -1341,7 +3840,6 @@ spec: description: nodeSelector when specified, is propagated to all control plane Deployments and Stateful sets running management side. - maxProperties: 100 type: object olmCatalogPlacement: description: olmCatalogPlacement specifies the placement of OLM @@ -5856,7 +8354,6 @@ spec: - service - servicePublishingStrategy type: object - maxItems: 10 type: array sshKey: description: sshKey is a local reference to a Secret that must @@ -5914,13 +8411,13 @@ spec: If the operator is Exists, the value should be empty, otherwise just a regular string. type: string type: object - maxItems: 50 type: array updateService: description: updateService may be used to specify the preferred upstream update service. type: string required: + - autoNode - etcd - fips - networking diff --git a/hyperfleet-operator/config/crd/bases/hyperfleet.io_nodepools.yaml b/hyperfleet-operator/config/crd/bases/hyperfleet.io_nodepools.yaml index 660b6505..f29a5508 100644 --- a/hyperfleet-operator/config/crd/bases/hyperfleet.io_nodepools.yaml +++ b/hyperfleet-operator/config/crd/bases/hyperfleet.io_nodepools.yaml @@ -277,7 +277,6 @@ spec: type: string description: nodeLabels propagates a list of labels to Nodes, only once on creation. - maxProperties: 100 type: object nodeVolumeDetachTimeout: description: nodeVolumeDetachTimeout is the maximum amount of @@ -1859,7 +1858,6 @@ spec: - effect - key type: object - maxItems: 50 type: array tuningConfig: description: tuningConfig is a list of references to ConfigMaps @@ -1884,6 +1882,7 @@ spec: required: - clusterName - management + - osImageStream - platform - release type: object diff --git a/platform-api/pkg/conversion/types.go b/platform-api/pkg/conversion/types.go index c20d84a6..91c5dfc6 100644 --- a/platform-api/pkg/conversion/types.go +++ b/platform-api/pkg/conversion/types.go @@ -4,9 +4,6 @@ package conversion import ( v1alpha1 "github.com/openshift-online/rosa-hyperfleet-api/api/v1alpha1" - configv1 "github.com/openshift/api/config/v1" - hypershiftv1beta1 "github.com/openshift/hypershift/api/hypershift/v1beta1" - corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -14,38 +11,12 @@ import ( type ServiceSetFields struct { // AccountID is service-set (platform-managed, hidden from API) AccountID string `json:"accountId"` - // AdditionalTrustBundle is service-set (platform-managed, hidden from API) - AdditionalTrustBundle *corev1.LocalObjectReference `json:"additionalTrustBundle"` // AllowedUnsafeSysctls is service-set (platform-managed, hidden from API) AllowedUnsafeSysctls []string `json:"allowedUnsafeSysctls"` // ApiServer is service-set (platform-managed, hidden from API) ApiServer *v1alpha1.APIServerNetworkConfiguration `json:"apiServer"` - // Arch is service-set (platform-managed, hidden from API) - Arch string `json:"arch"` - // AuditWebhook is service-set (platform-managed, hidden from API) - AuditWebhook *corev1.LocalObjectReference `json:"auditWebhook"` // Authentication is service-set (platform-managed, hidden from API) Authentication *v1alpha1.ClusterAuthentication `json:"authentication"` - // AutoNode is service-set (platform-managed, hidden from API) - AutoNode hypershiftv1beta1.AutoNode `json:"autoNode"` - // AutoScaling is service-set (platform-managed, hidden from API) - AutoScaling *hypershiftv1beta1.NodePoolAutoScaling `json:"autoScaling"` - // Autoscaling is service-set (platform-managed, hidden from API) - Autoscaling hypershiftv1beta1.ClusterAutoscaling `json:"autoscaling"` - // Capabilities is service-set (platform-managed, hidden from API) - Capabilities *hypershiftv1beta1.Capabilities `json:"capabilities"` - // Channel is service-set (platform-managed, hidden from API) - Channel string `json:"channel"` - // ClusterID is service-set (platform-managed, hidden from API) - ClusterID string `json:"clusterID"` - // Config is service-set (platform-managed, hidden from API) - Config []corev1.LocalObjectReference `json:"config"` - // Configuration is service-set (platform-managed, hidden from API) - Configuration *v1alpha1.ClusterConfiguration `json:"configuration"` - // ControlPlaneRelease is service-set (platform-managed, hidden from API) - ControlPlaneRelease *hypershiftv1beta1.Release `json:"controlPlaneRelease"` - // ControllerAvailabilityPolicy is service-set (platform-managed, hidden from API) - ControllerAvailabilityPolicy hypershiftv1beta1.AvailabilityPolicy `json:"controllerAvailabilityPolicy"` // CpuManagerPolicy is service-set (platform-managed, hidden from API) CpuManagerPolicy *string `json:"cpuManagerPolicy"` // CpuManagerPolicyOptions is service-set (platform-managed, hidden from API) @@ -54,8 +25,6 @@ type ServiceSetFields struct { CpuManagerReconcilePeriod *metav1.Duration `json:"cpuManagerReconcilePeriod"` // CreatorARN is service-set (platform-managed, hidden from API) CreatorARN string `json:"creatorARN"` - // Dns is service-set (platform-managed, hidden from API) - Dns hypershiftv1beta1.DNSSpec `json:"dns"` // EvictionHard is service-set (platform-managed, hidden from API) EvictionHard map[string]string `json:"evictionHard"` // EvictionSoft is service-set (platform-managed, hidden from API) @@ -68,14 +37,8 @@ type ServiceSetFields struct { FeatureGate *v1alpha1.FeatureGateConfiguration `json:"featureGate"` // Files is service-set (platform-managed, hidden from API) Files []v1alpha1.FileSpec `json:"files"` - // Fips is service-set (platform-managed, hidden from API) - Fips bool `json:"fips"` // Image is service-set (platform-managed, hidden from API) Image *v1alpha1.ImageConfiguration `json:"image"` - // InfraID is service-set (platform-managed, hidden from API) - InfraID string `json:"infraID"` - // InfrastructureAvailabilityPolicy is service-set (platform-managed, hidden from API) - InfrastructureAvailabilityPolicy hypershiftv1beta1.AvailabilityPolicy `json:"infrastructureAvailabilityPolicy"` // Ingress is service-set (platform-managed, hidden from API) Ingress *v1alpha1.IngressConfiguration `json:"ingress"` // InternalID is service-set (platform-managed, hidden from API) @@ -86,64 +49,24 @@ type ServiceSetFields struct { KernelArguments []string `json:"kernelArguments"` // KernelType is service-set (platform-managed, hidden from API) KernelType *string `json:"kernelType"` - // KubeAPIServerDNSName is service-set (platform-managed, hidden from API) - KubeAPIServerDNSName string `json:"kubeAPIServerDNSName"` // Kubelet is service-set (platform-managed, hidden from API) Kubelet *v1alpha1.KubeletConfig `json:"kubelet"` - // Labels is service-set (platform-managed, hidden from API) - Labels map[string]string `json:"labels"` // MachineConfig is service-set (platform-managed, hidden from API) MachineConfig *v1alpha1.MachineConfigSpec `json:"machineConfig"` - // Management is service-set (platform-managed, hidden from API) - Management hypershiftv1beta1.NodePoolManagement `json:"management"` // MemoryThrottlingFactor is service-set (platform-managed, hidden from API) MemoryThrottlingFactor *float64 `json:"memoryThrottlingFactor"` // Network is service-set (platform-managed, hidden from API) Network *v1alpha1.NetworkConfiguration `json:"network"` - // NodeDrainTimeout is service-set (platform-managed, hidden from API) - NodeDrainTimeout *metav1.Duration `json:"nodeDrainTimeout"` - // NodeLabels is service-set (platform-managed, hidden from API) - NodeLabels map[string]string `json:"nodeLabels"` - // NodeSelector is service-set (platform-managed, hidden from API) - NodeSelector map[string]string `json:"nodeSelector"` - // NodeVolumeDetachTimeout is service-set (platform-managed, hidden from API) - NodeVolumeDetachTimeout *metav1.Duration `json:"nodeVolumeDetachTimeout"` // Oauth is service-set (platform-managed, hidden from API) Oauth *v1alpha1.OAuthConfiguration `json:"oauth"` - // OlmCatalogPlacement is service-set (platform-managed, hidden from API) - OlmCatalogPlacement hypershiftv1beta1.OLMCatalogPlacement `json:"olmCatalogPlacement"` - // OperatorConfiguration is service-set (platform-managed, hidden from API) - OperatorConfiguration *hypershiftv1beta1.OperatorConfiguration `json:"operatorConfiguration"` - // OsImageStream is service-set (platform-managed, hidden from API) - OsImageStream hypershiftv1beta1.OSImageStreamReference `json:"osImageStream"` - // PausedUntil is service-set (platform-managed, hidden from API) - PausedUntil *string `json:"pausedUntil"` // Proxy is service-set (platform-managed, hidden from API) Proxy *v1alpha1.ProxyConfiguration `json:"proxy"` - // PullSecret is service-set (platform-managed, hidden from API) - PullSecret corev1.LocalObjectReference `json:"pullSecret"` // Scheduler is service-set (platform-managed, hidden from API) Scheduler *v1alpha1.SchedulerConfiguration `json:"scheduler"` - // SecretEncryption is service-set (platform-managed, hidden from API) - SecretEncryption *hypershiftv1beta1.SecretEncryptionSpec `json:"secretEncryption"` - // ServiceAccountSigningKey is service-set (platform-managed, hidden from API) - ServiceAccountSigningKey *corev1.LocalObjectReference `json:"serviceAccountSigningKey"` - // Services is service-set (platform-managed, hidden from API) - Services []hypershiftv1beta1.ServicePublishingStrategyMapping `json:"services"` - // SshKey is service-set (platform-managed, hidden from API) - SshKey corev1.LocalObjectReference `json:"sshKey"` // SystemdUnits is service-set (platform-managed, hidden from API) SystemdUnits []v1alpha1.SystemdUnit `json:"systemdUnits"` - // Taints is service-set (platform-managed, hidden from API) - Taints []hypershiftv1beta1.Taint `json:"taints"` - // Tolerations is service-set (platform-managed, hidden from API) - Tolerations []corev1.Toleration `json:"tolerations"` // TopologyManagerPolicy is service-set (platform-managed, hidden from API) TopologyManagerPolicy *string `json:"topologyManagerPolicy"` // TopologyManagerScope is service-set (platform-managed, hidden from API) TopologyManagerScope *string `json:"topologyManagerScope"` - // TuningConfig is service-set (platform-managed, hidden from API) - TuningConfig []corev1.LocalObjectReference `json:"tuningConfig"` - // UpdateService is service-set (platform-managed, hidden from API) - UpdateService configv1.URL `json:"updateService"` } diff --git a/platform-api/pkg/conversion/v1alpha1/controlplaneupgradepolicy.go b/platform-api/pkg/conversion/v1alpha1/controlplaneupgradepolicy.go new file mode 100644 index 00000000..0995948d --- /dev/null +++ b/platform-api/pkg/conversion/v1alpha1/controlplaneupgradepolicy.go @@ -0,0 +1,62 @@ +// Code generated by conversion-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "encoding/json" + + v1alpha1 "github.com/openshift-online/rosa-hyperfleet-api/api/v1alpha1" + rest "github.com/openshift-online/rosa-hyperfleet-api/api/v1alpha1/public" + "github.com/openshift-online/rosa-hyperfleet-api/platform-api/pkg/conversion" +) + +// ProjectControlPlaneUpgradePolicy converts CRD ControlPlaneUpgradePolicy to REST (visible fields only). +// Uses JSON roundtrip: marshal CRD → unmarshal into REST, so hidden fields are +// automatically dropped (REST types lack them). +func ProjectControlPlaneUpgradePolicy(crd *v1alpha1.ControlPlaneUpgradePolicy) *rest.ControlPlaneUpgradePolicy { + if crd == nil { + return nil + } + + spec := projectControlPlaneUpgradePolicySpec(crd.Spec) + status := projectControlPlaneUpgradePolicyStatus(crd.Status) + return &rest.ControlPlaneUpgradePolicy{ + TypeMeta: crd.TypeMeta, + ObjectMeta: crd.ObjectMeta, + Spec: spec, + Status: status, + } +} + +func projectControlPlaneUpgradePolicySpec(crd v1alpha1.ControlPlaneUpgradePolicySpec) rest.ControlPlaneUpgradePolicySpec { + data, _ := json.Marshal(crd) + var out rest.ControlPlaneUpgradePolicySpec + _ = json.Unmarshal(data, &out) + return out +} + +func projectControlPlaneUpgradePolicyStatus(crd v1alpha1.ControlPlaneUpgradePolicyStatus) rest.ControlPlaneUpgradePolicyStatus { + data, _ := json.Marshal(crd) + var out rest.ControlPlaneUpgradePolicyStatus + _ = json.Unmarshal(data, &out) + return out +} + +// UnprojectControlPlaneUpgradePolicy converts REST ControlPlaneUpgradePolicySpec to CRD with service-set enrichment. +// Uses JSON roundtrip: marshal REST → unmarshal into CRD, then overlay service-set fields. +func UnprojectControlPlaneUpgradePolicy(spec *rest.ControlPlaneUpgradePolicySpec, enrichment *conversion.ServiceSetFields) *v1alpha1.ControlPlaneUpgradePolicySpec { + if spec == nil { + return nil + } + + data, _ := json.Marshal(spec) + var crdSpec v1alpha1.ControlPlaneUpgradePolicySpec + _ = json.Unmarshal(data, &crdSpec) + + if enrichment != nil { + ssData, _ := json.Marshal(enrichment) + _ = json.Unmarshal(ssData, &crdSpec) + } + + return &crdSpec +} From 3d89cb510aba9497e8f1db51052f0a49888f1d65 Mon Sep 17 00:00:00 2001 From: Guilherme Branco Date: Tue, 11 Aug 2026 12:03:22 -0300 Subject: [PATCH 07/15] ROSAENG-62084 | fix: remove cluster_id from nodepool creation log cluster_id (np.Namespace) is a customer resource identifier; logging it may expose customer data in test output. --- platform-api/go.mod | 2 +- test/e2e-sdk/sdk_sanity_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/platform-api/go.mod b/platform-api/go.mod index 743ac98e..1f1345d6 100644 --- a/platform-api/go.mod +++ b/platform-api/go.mod @@ -24,7 +24,6 @@ require ( github.com/openshift-online/rosa-hyperfleet-api/api v0.0.0 github.com/openshift-online/rosa-hyperfleet-api/hack/api-codegen v0.0.0-00010101000000-000000000000 github.com/openshift-online/rosa-hyperfleet-api/hyperfleet-db v0.0.0 - github.com/openshift/api v0.0.0-20260416105050-3c6b218b8a80 github.com/openshift/hypershift/api v0.0.0-20260625052409-9acec4759a16 github.com/prometheus/client_golang v1.23.2 github.com/redis/go-redis/v9 v9.21.0 @@ -90,6 +89,7 @@ require ( github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/onsi/gomega v1.42.1 // indirect + github.com/openshift/api v0.0.0-20260416105050-3c6b218b8a80 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.70.1 // indirect diff --git a/test/e2e-sdk/sdk_sanity_test.go b/test/e2e-sdk/sdk_sanity_test.go index 223cf0cc..83b720d6 100644 --- a/test/e2e-sdk/sdk_sanity_test.go +++ b/test/e2e-sdk/sdk_sanity_test.go @@ -396,7 +396,7 @@ var _ = Describe("SDK E2E: cluster and nodepool lifecycle", Ordered, func() { nodepoolCreated = true Expect(np.Namespace).To(Equal(clusterID), "nodepool.metadata.namespace should be the parent cluster ID (from cluster_id wire field)") - GinkgoWriter.Printf("NodePool %s created (id=%s, cluster_id=%s)\n", npName, nodepoolID, np.Namespace) + GinkgoWriter.Printf("NodePool %s created (id=%s)\n", npName, nodepoolID) By("waiting for nodepool Ready") nodepools := cs.HyperfleetV1alpha1().NodePools(clusterID) From acccfd5cc7e1c2e6400190da6b988bfe5d3ac90f Mon Sep 17 00:00:00 2001 From: Guilherme Branco Date: Tue, 11 Aug 2026 12:28:59 -0300 Subject: [PATCH 08/15] ROSAENG-62084 | fix: remove stale ControlPlaneUpgradePolicy conversion file ControlPlaneUpgradePolicy never existed as a standalone CRD type; only its Spec/Status sub-types are embedded in Cluster. The discoverResources() logic inferred it as a resource from matching Spec+Status names and generated a file referencing a non-existent root type. Fix: require the root type to exist in typeInfos (parsed from the actual Go struct declarations) before treating a Spec+Status pair as a top-level resource. Delete the stale generated file. --- .../v1alpha1/controlplaneupgradepolicy.go | 62 ------------------- 1 file changed, 62 deletions(-) delete mode 100644 platform-api/pkg/conversion/v1alpha1/controlplaneupgradepolicy.go diff --git a/platform-api/pkg/conversion/v1alpha1/controlplaneupgradepolicy.go b/platform-api/pkg/conversion/v1alpha1/controlplaneupgradepolicy.go deleted file mode 100644 index 0995948d..00000000 --- a/platform-api/pkg/conversion/v1alpha1/controlplaneupgradepolicy.go +++ /dev/null @@ -1,62 +0,0 @@ -// Code generated by conversion-gen. DO NOT EDIT. - -package v1alpha1 - -import ( - "encoding/json" - - v1alpha1 "github.com/openshift-online/rosa-hyperfleet-api/api/v1alpha1" - rest "github.com/openshift-online/rosa-hyperfleet-api/api/v1alpha1/public" - "github.com/openshift-online/rosa-hyperfleet-api/platform-api/pkg/conversion" -) - -// ProjectControlPlaneUpgradePolicy converts CRD ControlPlaneUpgradePolicy to REST (visible fields only). -// Uses JSON roundtrip: marshal CRD → unmarshal into REST, so hidden fields are -// automatically dropped (REST types lack them). -func ProjectControlPlaneUpgradePolicy(crd *v1alpha1.ControlPlaneUpgradePolicy) *rest.ControlPlaneUpgradePolicy { - if crd == nil { - return nil - } - - spec := projectControlPlaneUpgradePolicySpec(crd.Spec) - status := projectControlPlaneUpgradePolicyStatus(crd.Status) - return &rest.ControlPlaneUpgradePolicy{ - TypeMeta: crd.TypeMeta, - ObjectMeta: crd.ObjectMeta, - Spec: spec, - Status: status, - } -} - -func projectControlPlaneUpgradePolicySpec(crd v1alpha1.ControlPlaneUpgradePolicySpec) rest.ControlPlaneUpgradePolicySpec { - data, _ := json.Marshal(crd) - var out rest.ControlPlaneUpgradePolicySpec - _ = json.Unmarshal(data, &out) - return out -} - -func projectControlPlaneUpgradePolicyStatus(crd v1alpha1.ControlPlaneUpgradePolicyStatus) rest.ControlPlaneUpgradePolicyStatus { - data, _ := json.Marshal(crd) - var out rest.ControlPlaneUpgradePolicyStatus - _ = json.Unmarshal(data, &out) - return out -} - -// UnprojectControlPlaneUpgradePolicy converts REST ControlPlaneUpgradePolicySpec to CRD with service-set enrichment. -// Uses JSON roundtrip: marshal REST → unmarshal into CRD, then overlay service-set fields. -func UnprojectControlPlaneUpgradePolicy(spec *rest.ControlPlaneUpgradePolicySpec, enrichment *conversion.ServiceSetFields) *v1alpha1.ControlPlaneUpgradePolicySpec { - if spec == nil { - return nil - } - - data, _ := json.Marshal(spec) - var crdSpec v1alpha1.ControlPlaneUpgradePolicySpec - _ = json.Unmarshal(data, &crdSpec) - - if enrichment != nil { - ssData, _ := json.Marshal(enrichment) - _ = json.Unmarshal(ssData, &crdSpec) - } - - return &crdSpec -} From 7375e7b3e197b32e39a308eea8aae4ac38cb87d0 Mon Sep 17 00:00:00 2001 From: Guilherme Branco Date: Tue, 11 Aug 2026 12:37:07 -0300 Subject: [PATCH 09/15] ROSAENG-62084 | fix: address security and doc findings from review - bridge.go: bypass adaptList when mappings is empty so list responses are returned unchanged instead of injecting empty metadata into items - bridge_test.go: regression test covering list with empty mappings - architecture.md: fix HyperfleetV1alpha1 example to use V1alpha1Public() - CLAUDE.md: update bridge-gen description from "wire generation" to "bridge and platform generation" --- CLAUDE.md | 2 +- clientset/docs/architecture.md | 2 +- clientset/transport/bridge.go | 6 +++++- clientset/transport/bridge_test.go | 26 ++++++++++++++++++++++++++ 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 6e23287d..b2195333 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -54,7 +54,7 @@ clientset/go.mod ← generated typed K8s client for Hyper hyperfleet-operator/go.mod ← requires: fleetdb, api platform-api/go.mod ← requires: fleetdb, api hack/api-codegen/go.mod ← codegen tools (openapi-gen, crd-variants, conversion-gen) -hack/clientset/cmd/bridge-gen/go.mod ← wire generation for clientset +hack/clientset/cmd/bridge-gen/go.mod ← bridge and platform generation for clientset hack/tools/go.mod ← dev tooling dependencies ``` diff --git a/clientset/docs/architecture.md b/clientset/docs/architecture.md index 46012b93..9ee710d3 100644 --- a/clientset/docs/architecture.md +++ b/clientset/docs/architecture.md @@ -248,7 +248,7 @@ An empty version string causes `metav1.AddToGroupVersion` to panic when register ```go func (c *Clientset) HyperfleetV1alpha1() platform.V1alpha1PublicInterface { - return platform.NewV1alpha1PublicClient(c.generated.V1alpha1()) + return platform.NewV1alpha1PublicClient(c.generated.V1alpha1Public()) } ``` diff --git a/clientset/transport/bridge.go b/clientset/transport/bridge.go index 448b1c65..b8c5090f 100644 --- a/clientset/transport/bridge.go +++ b/clientset/transport/bridge.go @@ -202,7 +202,11 @@ func (a *Adapter) adaptResponse(resp *http.Response, mappings []FieldMapping) (* var adapted []byte if itemsJSON, ok := raw["items"]; ok { - adapted = a.adaptList(raw, itemsJSON, mappings) + if len(mappings) == 0 { + adapted = body + } else { + adapted = a.adaptList(raw, itemsJSON, mappings) + } } else if a.hasWireField(raw, mappings) { adapted = a.adaptItem(raw, mappings) } else { diff --git a/clientset/transport/bridge_test.go b/clientset/transport/bridge_test.go index 1ffb4cc9..e848ca34 100644 --- a/clientset/transport/bridge_test.go +++ b/clientset/transport/bridge_test.go @@ -524,6 +524,32 @@ func TestAdaptResponse_MalformedListItemPassesThroughUnchanged(t *testing.T) { } } +func TestAdaptResponse_ListWithEmptyMappingsPassesThroughUnchanged(t *testing.T) { + a := newAdapter() + original := `{"items":[{"id":"x","name":"foo","spec":{}}]}` + resp := &http.Response{ + StatusCode: 200, + Body: io.NopCloser(strings.NewReader(original)), + Header: make(http.Header), + } + + out := mustAdaptResponse(t, a, "", resp) + b, _ := io.ReadAll(out.Body) + if string(b) != original { + t.Errorf("body unexpectedly changed: %s", b) + } + var m map[string]json.RawMessage + _ = json.Unmarshal(b, &m) + var items []map[string]json.RawMessage + _ = json.Unmarshal(m["items"], &items) + if len(items) != 1 { + t.Fatalf("len(items) = %d, want 1", len(items)) + } + if _, ok := items[0]["metadata"]; ok { + t.Error("metadata injected into list item with empty mappings") + } +} + func TestAdaptResponse_NodepoolClusterIDMappedToNamespace(t *testing.T) { a := newAdapter() resp := &http.Response{ From 15df5ac0f42b6be3851a7c0209ed6ab3e66b1950 Mon Sep 17 00:00:00 2001 From: Guilherme Branco Date: Tue, 11 Aug 2026 12:40:00 -0300 Subject: [PATCH 10/15] ROSAENG-62084 | chore: fix gofmt issues in clientset and bridge-gen --- clientset/hyperfleet.go | 2 +- clientset/transport/bridge.go | 2 +- hack/clientset/cmd/bridge-gen/main.go | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/clientset/hyperfleet.go b/clientset/hyperfleet.go index b8370e7e..1fedf741 100644 --- a/clientset/hyperfleet.go +++ b/clientset/hyperfleet.go @@ -33,9 +33,9 @@ import ( generatedclientset "github.com/openshift-online/rosa-hyperfleet-api/clientset/generated" "github.com/openshift-online/rosa-hyperfleet-api/clientset/generated/scheme" + "github.com/openshift-online/rosa-hyperfleet-api/clientset/platform" hfrest "github.com/openshift-online/rosa-hyperfleet-api/clientset/rest" "github.com/openshift-online/rosa-hyperfleet-api/clientset/transport" - "github.com/openshift-online/rosa-hyperfleet-api/clientset/platform" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" k8srest "k8s.io/client-go/rest" diff --git a/clientset/transport/bridge.go b/clientset/transport/bridge.go index b8c5090f..a8f84dac 100644 --- a/clientset/transport/bridge.go +++ b/clientset/transport/bridge.go @@ -30,7 +30,7 @@ import ( // field name and a Kubernetes metadata field name. type FieldMapping struct { Bridge string // flat field name in the platform-api response/request body - Meta string // field name inside the Kubernetes metadata object + Meta string // field name inside the Kubernetes metadata object } // Adapter wraps an inner RoundTripper and rewrites platform-api wire format diff --git a/hack/clientset/cmd/bridge-gen/main.go b/hack/clientset/cmd/bridge-gen/main.go index 546c2188..1e0d15e7 100644 --- a/hack/clientset/cmd/bridge-gen/main.go +++ b/hack/clientset/cmd/bridge-gen/main.go @@ -44,7 +44,7 @@ import ( ) var ( - bridgeMarkerRE = regexp.MustCompile(`\+bridge:field=([^,\s]+),meta=([^\s]+)`) + bridgeMarkerRE = regexp.MustCompile(`\+bridge:field=([^,\s]+),meta=([^\s]+)`) watchDisabledRE = regexp.MustCompile(`\+bridge:watch=disabled`) waitRE = regexp.MustCompile(`\+bridge:wait\b`) nonNamespacedRE = regexp.MustCompile(`\+genclient:nonNamespaced\b`) @@ -53,15 +53,15 @@ var ( // fieldMapping holds a single wire→metadata field translation. type fieldMapping struct { Bridge string - Meta string + Meta string } // resourceType describes a CRD type annotated with +bridge:watch or +bridge:wait. type resourceType struct { - Name string // e.g. "Cluster" - PluralName string // e.g. "Clusters" - PluralLower string // e.g. "clusters" — URL path segment key - LowerName string // e.g. "cluster" + Name string // e.g. "Cluster" + PluralName string // e.g. "Clusters" + PluralLower string // e.g. "clusters" — URL path segment key + LowerName string // e.g. "cluster" WatchDisabled bool Wait bool NonNamespaced bool // set when +genclient:nonNamespaced is present From 68f218302eb095463da580f41c01e3dd819f863b Mon Sep 17 00:00:00 2001 From: Guilherme Branco Date: Tue, 11 Aug 2026 15:20:21 -0300 Subject: [PATCH 11/15] propagate upstream optional/required markers through passthrough-gen - loader.go: forward +optional/+required from upstream HyperShift field doc comments into generated passthrough types via an explicit allowlist (upstreamForwardedMarkerPrefixes); fixes autoNode: Required value in tests - crd-variants: add --strip-passthrough-cel mode that auto-detects passthrough subtrees via Go AST and strips x-kubernetes-validations in-place; replaces manual flag; removes emoji from output - Makefile: codegen-registry now depends on codegen-passthrough so any direct invocation of codegen-registry, codegen-conversion, or verify-conversion always regenerates the passthrough file first - Regenerated CRDs and zz_generated.passthrough.go --- Makefile | 10 +- api/v1alpha1/zz_generated.passthrough.go | 48 + hack/api-codegen/cmd/crd-variants/main.go | 37 +- .../pkg/featuregate/crd_strip_cel.go | 167 + .../pkg/featuregate/detect_passthrough.go | 204 + hack/api-codegen/pkg/passthrough/loader.go | 37 +- .../crd/bases/hyperfleet.io_clusters.yaml | 14682 +++++++--------- .../crd/bases/hyperfleet.io_nodepools.yaml | 3336 ++-- 8 files changed, 8626 insertions(+), 9895 deletions(-) create mode 100644 hack/api-codegen/pkg/featuregate/crd_strip_cel.go create mode 100644 hack/api-codegen/pkg/featuregate/detect_passthrough.go diff --git a/Makefile b/Makefile index db0d10ce..fb5cba64 100644 --- a/Makefile +++ b/Makefile @@ -297,8 +297,12 @@ deps: # ── Code Generation ────────────────────────────────────────────────────── -manifests: $(CONTROLLER_GEN) +CRD_VARIANTS := $(abspath bin/crd-variants) +CRD_BASES_DIR := hyperfleet-operator/config/crd/bases + +manifests: $(CONTROLLER_GEN) build-api-codegen cd hyperfleet-operator && $(CONTROLLER_GEN) crd:allowDangerousTypes=true paths="../api/v1alpha1" output:crd:dir=config/crd/bases + $(CRD_VARIANTS) --strip-passthrough-cel --api-dir api/v1alpha1 --crd-dir $(CRD_BASES_DIR) generate: $(CONTROLLER_GEN) $(CONTROLLER_GEN) object paths="./api/..." @@ -337,7 +341,7 @@ codegen-passthrough: build-api-codegen -output-dir v1alpha1 \ -package v1alpha1 -codegen-registry: generate build-api-codegen +codegen-registry: codegen-passthrough generate build-api-codegen ./bin/marker-scanner \ -input-dirs api/v1alpha1 \ -output-file hack/api-codegen/pkg/registry/field_metadata.go \ @@ -353,7 +357,7 @@ verify-codegen: codegen git diff --exit-code api/v1alpha1/zz_generated.deepcopy.go git diff --exit-code hack/api-codegen/pkg/registry/ -generate-all: manifests generate codegen-passthrough codegen-conversion generate-clientset generate-openapi +generate-all: codegen-passthrough generate codegen-registry manifests codegen-conversion generate-clientset generate-openapi verify-all: verify-codegen verify-conversion verify-clientset verify-openapi diff --git a/api/v1alpha1/zz_generated.passthrough.go b/api/v1alpha1/zz_generated.passthrough.go index 35472e9a..9144772a 100644 --- a/api/v1alpha1/zz_generated.passthrough.go +++ b/api/v1alpha1/zz_generated.passthrough.go @@ -14,134 +14,167 @@ type HostedClusterSpecPassthrough struct { // release specifies the desired OCP release payload for all the hosted cluster components. // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +required Release hypershiftv1beta1.Release `json:"release"` // controlPlaneRelease is like spec.release but only for the components running on the management cluster. // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +optional ControlPlaneRelease *hypershiftv1beta1.Release `json:"controlPlaneRelease,omitempty"` // clusterID uniquely identifies this cluster. This is expected to be an RFC4122 UUID value (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx in hexadecimal digits). // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +optional ClusterID string `json:"clusterID,omitempty"` // infraID is a globally unique identifier for the cluster. // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +optional InfraID string `json:"infraID,omitempty"` // updateService may be used to specify the preferred upstream update service. // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +optional UpdateService configv1.URL `json:"updateService,omitempty"` // channel is an identifier for explicitly requesting that a non-default set of updates be applied to this cluster. // +k8s:openapi-gen=true // +hyperfleet:write-mode=service-set + // +optional Channel string `json:"channel,omitempty"` // platform specifies the underlying infrastructure provider for the cluster // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +required Platform hypershiftv1beta1.PlatformSpec `json:"platform"` // kubeAPIServerDNSName specifies a desired DNS name to resolve to the KAS. // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +optional KubeAPIServerDNSName string `json:"kubeAPIServerDNSName,omitempty"` // controllerAvailabilityPolicy specifies the availability policy applied to critical control plane components like the Kube API Server. // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +optional ControllerAvailabilityPolicy hypershiftv1beta1.AvailabilityPolicy `json:"controllerAvailabilityPolicy,omitempty"` // infrastructureAvailabilityPolicy specifies the availability policy applied to infrastructure services which run on the hosted cluster data plane like the ingress controller and image registry controller. // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +optional InfrastructureAvailabilityPolicy hypershiftv1beta1.AvailabilityPolicy `json:"infrastructureAvailabilityPolicy,omitempty"` // dns specifies the DNS configuration for the hosted cluster ingress. // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +optional DNS hypershiftv1beta1.DNSSpec `json:"dns,omitempty"` // networking specifies network configuration for the hosted cluster. // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +required Networking hypershiftv1beta1.ClusterNetworking `json:"networking"` // autoscaling specifies auto-scaling behavior that applies to all NodePools // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +optional Autoscaling hypershiftv1beta1.ClusterAutoscaling `json:"autoscaling,omitempty"` // autoNode specifies the configuration for automatic node provisioning and lifecycle management. // +k8s:openapi-gen=true // +hyperfleet:write-mode=service-set + // +optional AutoNode hypershiftv1beta1.AutoNode `json:"autoNode,omitzero"` // etcd specifies configuration for the control plane etcd cluster. The // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +required Etcd hypershiftv1beta1.EtcdSpec `json:"etcd"` // services specifies how individual control plane services endpoints are published for consumption. // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +required Services []hypershiftv1beta1.ServicePublishingStrategyMapping `json:"services"` // pullSecret is a local reference to a Secret that must have a ".dockerconfigjson" key whose content must be a valid Openshift pull secret JSON. // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +required PullSecret corev1.LocalObjectReference `json:"pullSecret"` // sshKey is a local reference to a Secret that must have a "id_rsa.pub" key whose content must be the public part of 1..N SSH keys. // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +optional SSHKey corev1.LocalObjectReference `json:"sshKey"` // issuerURL is an OIDC issuer URL which will be used as the issuer in all // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +optional IssuerURL string `json:"issuerURL,omitempty"` // serviceAccountSigningKey is a local reference to a secret that must have a "key" key whose content must be the private key // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +optional ServiceAccountSigningKey *corev1.LocalObjectReference `json:"serviceAccountSigningKey,omitempty"` // configuration specifies configuration for individual OCP components in the // +k8s:openapi-gen=true // +hyperfleet:write-mode=service-set + // +optional Configuration *hypershiftv1beta1.ClusterConfiguration `json:"configuration,omitempty"` // operatorConfiguration specifies configuration for individual OCP operators in the cluster. // +k8s:openapi-gen=true // +hyperfleet:write-mode=service-set + // +optional OperatorConfiguration *hypershiftv1beta1.OperatorConfiguration `json:"operatorConfiguration,omitempty"` // auditWebhook contains metadata for configuring an audit webhook endpoint // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +optional AuditWebhook *corev1.LocalObjectReference `json:"auditWebhook,omitempty"` // imageContentSources specifies image mirrors that can be used by cluster // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +optional ImageContentSources []hypershiftv1beta1.ImageContentSource `json:"imageContentSources,omitempty"` // additionalTrustBundle is a local reference to a ConfigMap that must have a "ca-bundle.crt" key // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +optional AdditionalTrustBundle *corev1.LocalObjectReference `json:"additionalTrustBundle,omitempty"` // secretEncryption specifies a Kubernetes secret encryption strategy for the // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +optional SecretEncryption *hypershiftv1beta1.SecretEncryptionSpec `json:"secretEncryption,omitempty"` // fips indicates whether this cluster's nodes will be running in FIPS mode. // +k8s:openapi-gen=true // +hyperfleet:write-mode=service-set + // +optional FIPS bool `json:"fips"` // pausedUntil is a field that can be used to pause reconciliation on the HostedCluster controller, resulting in any change to the HostedCluster being ignored. // +k8s:openapi-gen=true // +hyperfleet:write-mode=service-set + // +optional PausedUntil *string `json:"pausedUntil,omitempty"` // olmCatalogPlacement specifies the placement of OLM catalog components. By default, // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +optional OLMCatalogPlacement hypershiftv1beta1.OLMCatalogPlacement `json:"olmCatalogPlacement,omitempty"` // nodeSelector when specified, is propagated to all control plane Deployments and Stateful sets running management side. // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +optional NodeSelector map[string]string `json:"nodeSelector,omitempty"` // tolerations when specified, define what custom tolerations are added to the hcp pods. // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +optional Tolerations []corev1.Toleration `json:"tolerations,omitempty"` // labels when specified, define what custom labels are added to the hcp pods. // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +optional Labels map[string]string `json:"labels,omitempty"` // capabilities allows for disabling optional components at cluster install time. // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +optional Capabilities *hypershiftv1beta1.Capabilities `json:"capabilities,omitempty"` } @@ -150,61 +183,76 @@ type NodePoolSpecPassthrough struct { // clusterName is the name of the HostedCluster this NodePool belongs to. // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +required ClusterName string `json:"clusterName"` // release specifies the OCP release used for this NodePool. It drives the machine ignition configuration (including // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +required Release hypershiftv1beta1.Release `json:"release"` // platform specifies the underlying infrastructure provider for the NodePool // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +required Platform hypershiftv1beta1.NodePoolPlatform `json:"platform"` // replicas is the desired number of nodes the pool should maintain. If unset, the controller default value is 0. // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +optional Replicas *int32 `json:"replicas,omitempty"` // management specifies behavior for managing nodes in the pool, such as // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +required Management hypershiftv1beta1.NodePoolManagement `json:"management"` // autoScaling specifies auto-scaling behavior for the NodePool. // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +optional AutoScaling *hypershiftv1beta1.NodePoolAutoScaling `json:"autoScaling,omitempty"` // config is a list of references to ConfigMaps containing serialized // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +optional Config []corev1.LocalObjectReference `json:"config,omitempty"` // nodeDrainTimeout is the maximum amount of time that the controller will spend on retrying to drain a node until it succeeds. // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +optional NodeDrainTimeout *metav1.Duration `json:"nodeDrainTimeout,omitempty"` // nodeVolumeDetachTimeout is the maximum amount of time that the controller will spend on detaching volumes from a node. // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +optional NodeVolumeDetachTimeout *metav1.Duration `json:"nodeVolumeDetachTimeout,omitempty"` // nodeLabels propagates a list of labels to Nodes, only once on creation. // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +optional NodeLabels map[string]string `json:"nodeLabels,omitempty"` // taints if specified, propagates a list of taints to Nodes, only once on creation. // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +optional Taints []hypershiftv1beta1.Taint `json:"taints,omitempty"` // pausedUntil is a field that can be used to pause reconciliation on the NodePool controller. Resulting in any change to the NodePool being ignored. // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +optional PausedUntil *string `json:"pausedUntil,omitempty"` // tuningConfig is a list of references to ConfigMaps containing serialized // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +optional TuningConfig []corev1.LocalObjectReference `json:"tuningConfig,omitempty"` // arch is the preferred processor architecture for the NodePool. Different platforms might have different supported architectures. // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +optional Arch string `json:"arch,omitempty"` // osImageStream specifies an OS stream to be used for nodes in this pool. // +k8s:openapi-gen=false // +hyperfleet:write-mode=service-set + // +optional OSImageStream hypershiftv1beta1.OSImageStreamReference `json:"osImageStream,omitzero"` } diff --git a/hack/api-codegen/cmd/crd-variants/main.go b/hack/api-codegen/cmd/crd-variants/main.go index a18118d2..c2a1c3c9 100644 --- a/hack/api-codegen/cmd/crd-variants/main.go +++ b/hack/api-codegen/cmd/crd-variants/main.go @@ -5,20 +5,49 @@ import ( "fmt" "log" "os" + "strings" "github.com/openshift-online/rosa-hyperfleet-api/hack/api-codegen/pkg/featuregate" ) func main() { var ( - inputFile = flag.String("input", "", "Input CRD YAML file") - outputDir = flag.String("output-dir", "config/crd/variants", "Output directory for CRD variants") - baseName = flag.String("base-name", "", "Base name for output files (e.g., 'cluster' produces cluster_default.yaml)") - featureSet = flag.String("feature-set", "", "Generate only one feature set variant (default, techpreview, devpreview)") + inputFile = flag.String("input", "", "Input CRD YAML file") + outputDir = flag.String("output-dir", "config/crd/variants", "Output directory for CRD variants") + baseName = flag.String("base-name", "", "Base name for output files (e.g., 'cluster' produces cluster_default.yaml)") + featureSet = flag.String("feature-set", "", "Generate only one feature set variant (default, techpreview, devpreview)") + stripPassthroughCEL = flag.Bool("strip-passthrough-cel", false, "Strip x-kubernetes-validations from passthrough subtrees detected in --api-dir") + apiDir = flag.String("api-dir", "", "Go source directory to scan for passthrough types (used with --strip-passthrough-cel)") + crdDir = flag.String("crd-dir", "", "Directory containing CRD YAML files (used with --strip-passthrough-cel)") ) flag.Parse() + // --strip-passthrough-cel: auto-detect passthrough fields and strip in-place. + if *stripPassthroughCEL { + if *apiDir == "" || *crdDir == "" { + fmt.Fprintln(os.Stderr, "Error: --api-dir and --crd-dir are required with --strip-passthrough-cel") + flag.Usage() + os.Exit(1) + } + targets, err := featuregate.DetectPassthroughTargets(*apiDir, *crdDir) + if err != nil { + log.Fatalf("Detecting passthrough targets: %v", err) + } + if len(targets) == 0 { + fmt.Println("No passthrough fields detected — nothing to strip.") + return + } + for _, t := range targets { + if err := featuregate.StripCELFromSubtrees(t.CRDFile, t.Paths); err != nil { + log.Fatalf("Stripping CEL from %s: %v", t.CRDFile, err) + } + fmt.Printf("Stripped x-kubernetes-validations from %s in %s\n", + strings.Join(t.Paths, ", "), t.CRDFile) + } + return + } + if *inputFile == "" { fmt.Fprintln(os.Stderr, "Error: --input is required") flag.Usage() diff --git a/hack/api-codegen/pkg/featuregate/crd_strip_cel.go b/hack/api-codegen/pkg/featuregate/crd_strip_cel.go new file mode 100644 index 00000000..e96684e6 --- /dev/null +++ b/hack/api-codegen/pkg/featuregate/crd_strip_cel.go @@ -0,0 +1,167 @@ +package featuregate + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "gopkg.in/yaml.v3" +) + +// StripCELFromSubtrees reads a CRD YAML file, removes all x-kubernetes-validations +// keys from within each named dot-separated field path, and writes the result back +// in-place. Paths are relative to openAPIV3Schema (e.g. "spec.hostedCluster"). +func StripCELFromSubtrees(crdPath string, fieldPaths []string) error { + data, err := os.ReadFile(crdPath) + if err != nil { + return fmt.Errorf("reading CRD: %w", err) + } + + var doc yaml.Node + if err := yaml.Unmarshal(data, &doc); err != nil { + return fmt.Errorf("parsing YAML: %w", err) + } + + for _, path := range fieldPaths { + segments := strings.Split(path, ".") + if err := stripCELAtPath(&doc, segments); err != nil { + return fmt.Errorf("stripping path %q: %w", path, err) + } + } + + tmp, err := os.CreateTemp(filepath.Dir(crdPath), ".strip-cel-*.yaml") + if err != nil { + return fmt.Errorf("creating temp file: %w", err) + } + tmpName := tmp.Name() + + enc := yaml.NewEncoder(tmp) + enc.SetIndent(2) + if err := enc.Encode(&doc); err != nil { + tmp.Close() + os.Remove(tmpName) + return fmt.Errorf("writing YAML: %w", err) + } + if err := enc.Close(); err != nil { + tmp.Close() + os.Remove(tmpName) + return fmt.Errorf("closing encoder: %w", err) + } + if err := tmp.Close(); err != nil { + os.Remove(tmpName) + return fmt.Errorf("closing temp file: %w", err) + } + if err := os.Rename(tmpName, crdPath); err != nil { + os.Remove(tmpName) + return fmt.Errorf("renaming temp file: %w", err) + } + + return nil +} + +// stripCELAtPath navigates to the subtree at the given field path segments +// (relative to openAPIV3Schema) and recursively removes x-kubernetes-validations. +// It handles both single-version and multi-version CRDs by traversing the +// spec.versions[*].schema.openAPIV3Schema prefix automatically. +func stripCELAtPath(doc *yaml.Node, segments []string) error { + // Walk every version's schema — the generator produces one version but + // the function handles multiple to be safe. + versions := findSchemaNodes(doc) + for _, schema := range versions { + target := navigateTo(schema, segments) + if target != nil { + stripCELRecursive(target) + } + } + return nil +} + +// findSchemaNodes returns the openAPIV3Schema mapping node for each version. +func findSchemaNodes(doc *yaml.Node) []*yaml.Node { + // spec.versions[*].schema.openAPIV3Schema + spec := mappingChild(doc, "spec") + if spec == nil { + return nil + } + versionsSeq := mappingChild(spec, "versions") + if versionsSeq == nil || versionsSeq.Kind != yaml.SequenceNode { + return nil + } + var schemas []*yaml.Node + for _, ver := range versionsSeq.Content { + schema := mappingChild(ver, "schema") + if schema == nil { + continue + } + openAPI := mappingChild(schema, "openAPIV3Schema") + if openAPI != nil { + schemas = append(schemas, openAPI) + } + } + return schemas +} + +// navigateTo descends through "properties" wrappers following the segment path. +// Each segment steps into the "properties" map of the current node. +func navigateTo(node *yaml.Node, segments []string) *yaml.Node { + cur := node + for _, seg := range segments { + props := mappingChild(cur, "properties") + if props == nil { + return nil + } + cur = mappingChild(props, seg) + if cur == nil { + return nil + } + } + return cur +} + +// stripCELRecursive removes x-kubernetes-validations from node and all descendants. +func stripCELRecursive(node *yaml.Node) { + if node == nil { + return + } + if node.Kind == yaml.MappingNode { + newContent := make([]*yaml.Node, 0, len(node.Content)) + for i := 0; i < len(node.Content); i += 2 { + if i+1 >= len(node.Content) { + break + } + key := node.Content[i] + val := node.Content[i+1] + if key.Value == "x-kubernetes-validations" { + continue + } + stripCELRecursive(val) + newContent = append(newContent, key, val) + } + node.Content = newContent + return + } + for _, child := range node.Content { + stripCELRecursive(child) + } +} + +// mappingChild returns the value node for key in a YAML mapping node, or nil. +func mappingChild(node *yaml.Node, key string) *yaml.Node { + if node == nil { + return nil + } + // Unwrap document node + if node.Kind == yaml.DocumentNode && len(node.Content) > 0 { + return mappingChild(node.Content[0], key) + } + if node.Kind != yaml.MappingNode { + return nil + } + for i := 0; i+1 < len(node.Content); i += 2 { + if node.Content[i].Value == key { + return node.Content[i+1] + } + } + return nil +} diff --git a/hack/api-codegen/pkg/featuregate/detect_passthrough.go b/hack/api-codegen/pkg/featuregate/detect_passthrough.go new file mode 100644 index 00000000..0100dc36 --- /dev/null +++ b/hack/api-codegen/pkg/featuregate/detect_passthrough.go @@ -0,0 +1,204 @@ +package featuregate + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "os" + "path/filepath" + "reflect" + "strings" +) + +// PassthroughTarget describes a CRD file and the schema paths within it that +// contain embedded HyperShift passthrough types and should have their +// x-kubernetes-validations stripped. +type PassthroughTarget struct { + // CRDFile is the absolute path to the CRD YAML file. + CRDFile string + // Paths are dot-separated schema paths relative to openAPIV3Schema + // (e.g. "spec.hostedCluster"). + Paths []string +} + +// DetectPassthroughTargets scans the Go source files in apiDir for root CRD +// types (marked +kubebuilder:object:root=true) whose Spec structs contain +// fields typed with a name ending in "Passthrough". For each such field it +// locates the corresponding CRD file in crdDir and records the schema path. +// +// CRD files are matched by the lowercase-plural of the root type name +// (e.g. Cluster → clusters → *_clusters.yaml). +func DetectPassthroughTargets(apiDir, crdDir string) ([]PassthroughTarget, error) { + fset := token.NewFileSet() + pkgs, err := parser.ParseDir(fset, apiDir, func(fi os.FileInfo) bool { + return !strings.HasSuffix(fi.Name(), "_test.go") + }, parser.ParseComments) + if err != nil { + return nil, fmt.Errorf("parsing %s: %w", apiDir, err) + } + + // Collect all struct types by name and their position in the file. + type structEntry struct { + st *ast.StructType + pos token.Pos + } + structs := make(map[string]structEntry) + + // rootTypes: names of types annotated with +kubebuilder:object:root=true. + // Detected by finding comment groups containing the marker, then mapping + // them to the nearest following type declaration in the same file. + rootTypes := make(map[string]bool) + + for _, pkg := range pkgs { + for _, file := range pkg.Files { + // Build a sorted list of (commentGroupEndPos, markerPresent) for this file. + // We only care about comment groups that contain the root marker. + var markerGroupEnds []token.Pos + for _, cg := range file.Comments { + if hasMarker(cg, "+kubebuilder:object:root=true") { + markerGroupEnds = append(markerGroupEnds, cg.End()) + } + } + + for _, decl := range file.Decls { + gd, ok := decl.(*ast.GenDecl) + if !ok || gd.Tok != token.TYPE { + continue + } + for _, spec := range gd.Specs { + ts, ok := spec.(*ast.TypeSpec) + if !ok { + continue + } + st, ok := ts.Type.(*ast.StructType) + if !ok { + continue + } + structs[ts.Name.Name] = structEntry{st: st, pos: gd.Pos()} + + // A type is a root type if any marker comment group in the + // same file ends before this declaration starts. We use a + // generous window: any marker group that ends within 50 lines + // before the type declaration is considered associated with it. + declLine := fset.Position(gd.Pos()).Line + for _, end := range markerGroupEnds { + endLine := fset.Position(end).Line + if endLine < declLine && declLine-endLine <= 50 { + rootTypes[ts.Name.Name] = true + break + } + } + } + } + } + } + + // For each root type, inspect its Spec struct for Passthrough fields. + type hit struct { + plural string // CRD plural name (e.g. "clusters") + jsonTag string // JSON field name (e.g. "hostedCluster") + } + var hits []hit + + for typeName := range rootTypes { + specName := typeName + "Spec" + entry, ok := structs[specName] + if !ok { + continue + } + plural := strings.ToLower(typeName) + "s" + for _, field := range entry.st.Fields.List { + typStr := typeString(field.Type) + if !strings.HasSuffix(typStr, "Passthrough") { + continue + } + tag := jsonTag(field) + if tag == "" || tag == "-" { + continue + } + hits = append(hits, hit{plural: plural, jsonTag: tag}) + } + } + + if len(hits) == 0 { + return nil, nil + } + + // Resolve each hit to an actual CRD file in crdDir. + entries, err := os.ReadDir(crdDir) + if err != nil { + return nil, fmt.Errorf("reading CRD dir %s: %w", crdDir, err) + } + + // Index CRD files by their plural suffix: "clusters" → full path. + crdByPlural := make(map[string]string) + for _, e := range entries { + if e.IsDir() || !strings.HasSuffix(e.Name(), ".yaml") { + continue + } + // File names look like hyperfleet.io_clusters.yaml + parts := strings.SplitN(strings.TrimSuffix(e.Name(), ".yaml"), "_", 2) + if len(parts) == 2 { + crdByPlural[parts[1]] = filepath.Join(crdDir, e.Name()) + } + } + + // Group hits by CRD file. + byFile := make(map[string][]string) + for _, h := range hits { + crdFile, ok := crdByPlural[h.plural] + if !ok { + return nil, fmt.Errorf("no CRD file found for plural %q in %s", h.plural, crdDir) + } + byFile[crdFile] = append(byFile[crdFile], "spec."+h.jsonTag) + } + + var targets []PassthroughTarget + for file, paths := range byFile { + targets = append(targets, PassthroughTarget{CRDFile: file, Paths: paths}) + } + return targets, nil +} + +// hasMarker reports whether the comment group contains the given marker text. +func hasMarker(cg *ast.CommentGroup, marker string) bool { + if cg == nil { + return false + } + for _, c := range cg.List { + if strings.Contains(c.Text, marker) { + return true + } + } + return false +} + +// typeString returns the base type name from a field type expression, +// stripping any pointer or selector qualifier. +func typeString(expr ast.Expr) string { + switch t := expr.(type) { + case *ast.Ident: + return t.Name + case *ast.StarExpr: + return typeString(t.X) + case *ast.SelectorExpr: + return typeString(t.Sel) + case *ast.ArrayType: + return typeString(t.Elt) + } + return "" +} + +// jsonTag extracts the first comma-separated segment of the "json" struct tag. +func jsonTag(field *ast.Field) string { + if field.Tag == nil { + return "" + } + raw := strings.Trim(field.Tag.Value, "`") + tag := reflect.StructTag(raw).Get("json") + if tag == "" { + return "" + } + return strings.SplitN(tag, ",", 2)[0] +} diff --git a/hack/api-codegen/pkg/passthrough/loader.go b/hack/api-codegen/pkg/passthrough/loader.go index 67a2c138..1b8fe279 100644 --- a/hack/api-codegen/pkg/passthrough/loader.go +++ b/hack/api-codegen/pkg/passthrough/loader.go @@ -109,6 +109,13 @@ func (g *Generator) GenerateTypeDef(typeName string) (*TypeDef, error) { return typeDef, nil } +// upstreamForwardedMarkerPrefixes lists the Go marker prefixes from upstream +// source comments that should be propagated into the generated passthrough type. +var upstreamForwardedMarkerPrefixes = []string{ + "+optional", + "+required", +} + // createFieldDef creates a field definition with appropriate markers func (g *Generator) createFieldDef(fieldName string, field *ast.Field) FieldDef { fieldDef := FieldDef{ @@ -124,13 +131,18 @@ func (g *Generator) createFieldDef(fieldName string, field *ast.Field) FieldDef } } - // Extract documentation (first line only, collapsed to single line) + // Extract documentation and forwarded upstream Go markers. + // Only marker lines matching upstreamForwardedMarkerPrefixes are kept; + // the first non-marker, non-empty line becomes the field description. + var upstreamMarkers []string if field.Doc != nil { - doc := strings.TrimSpace(field.Doc.Text()) - // Take only first line and collapse to single line - lines := strings.Split(doc, "\n") - if len(lines) > 0 { - fieldDef.Doc = strings.TrimSpace(lines[0]) + for _, comment := range field.Doc.List { + text := strings.TrimSpace(strings.TrimPrefix(comment.Text, "//")) + if isForwardedMarker(text) { + upstreamMarkers = append(upstreamMarkers, text) + } else if fieldDef.Doc == "" && text != "" && !strings.HasPrefix(text, "+") { + fieldDef.Doc = text + } } } @@ -143,11 +155,22 @@ func (g *Generator) createFieldDef(fieldName string, field *ast.Field) FieldDef if lookupName == "" { lookupName = fieldName } - fieldDef.Markers = g.getMarkersForField(lookupName) + fieldDef.Markers = append(g.getMarkersForField(lookupName), upstreamMarkers...) return fieldDef } +// isForwardedMarker reports whether an upstream marker should be propagated +// into the generated passthrough type. +func isForwardedMarker(marker string) bool { + for _, prefix := range upstreamForwardedMarkerPrefixes { + if strings.HasPrefix(marker, prefix) { + return true + } + } + return false +} + // typeToString converts an AST type expression to a string func (g *Generator) typeToString(expr ast.Expr) string { switch t := expr.(type) { diff --git a/hyperfleet-operator/config/crd/bases/hyperfleet.io_clusters.yaml b/hyperfleet-operator/config/crd/bases/hyperfleet.io_clusters.yaml index 65a8cf95..7a4a7bd0 100644 --- a/hyperfleet-operator/config/crd/bases/hyperfleet.io_clusters.yaml +++ b/hyperfleet-operator/config/crd/bases/hyperfleet.io_clusters.yaml @@ -1,4 +1,3 @@ ---- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -12,7394 +11,5950 @@ spec: listKind: ClusterList plural: clusters shortNames: - - hfc + - hfc singular: cluster scope: Namespaced versions: - - additionalPrinterColumns: - - jsonPath: .status.phase - name: Phase - type: string - - jsonPath: .status.placementRef.managementCluster - name: MC - type: string - - jsonPath: .status.controlPlaneEndpoint.host - name: Endpoint - priority: 1 - type: string - - jsonPath: .spec.expirationTimestamp - name: Expires - priority: 1 - type: date - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha1 - schema: - openAPIV3Schema: - description: |- - Cluster is the Schema for the clusters API. - It represents a ROSA HCP cluster whose lifecycle is managed by the hyperfleet-operator. - metadata.Name is the human-readable cluster name; metadata.Namespace is the cluster UUID. - The owning account is the label hyperfleet.io/account-id. - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - type: object - spec: - description: |- - ClusterSpec defines the desired state of a ROSA HCP cluster. - metadata.Name is the human-readable cluster name; metadata.Namespace is the cluster UUID. - The owning AWS account is stored as the label hyperfleet.io/account-id. - properties: - accountId: - description: AccountID is the AWS account that owns this cluster. - type: string - controlPlaneUpgradePolicy: - description: ControlPlaneUpgradePolicy is the control plane upgrade - policy defined by the user. - properties: - nextRun: - description: NextRun is the time the upgrade should run for "manual" - upgrade policies - format: date-time - type: string - schedule: - description: |- - Schedule defines a cron expression that calculates the next automatic upgrade scheduling. - The cron expression must follow the standard 5-field format: - ┌───────────── minute (0 - 59) - │ ┌───────────── hour (0 - 23) - │ │ ┌───────────── day of month (1 - 31) - │ │ │ ┌───────────── month (1 - 12) - │ │ │ │ ┌───────────── day of week (0 - 6) (Sunday to Saturday) - │ │ │ │ │ - * * * * * - maxLength: 256 - pattern: ^(\*|([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])|([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])-([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])|\*/([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])|([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])(,([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]))*) - (\*|([0-9]|1[0-9]|2[0-3])|([0-9]|1[0-9]|2[0-3])-([0-9]|1[0-9]|2[0-3])|\*/([0-9]|1[0-9]|2[0-3])|([0-9]|1[0-9]|2[0-3])(,([0-9]|1[0-9]|2[0-3]))*) - (\*|([1-9]|1[0-9]|2[0-9]|3[0-1])|([1-9]|1[0-9]|2[0-9]|3[0-1])-([1-9]|1[0-9]|2[0-9]|3[0-1])|\*/([1-9]|1[0-9]|2[0-9]|3[0-1])|([1-9]|1[0-9]|2[0-9]|3[0-1])(,([1-9]|1[0-9]|2[0-9]|3[0-1]))*) - (\*|([1-9]|1[0-2])|([1-9]|1[0-2])-([1-9]|1[0-2])|\*/([1-9]|1[0-2])|([1-9]|1[0-2])(,([1-9]|1[0-2]))*) - (\*|[0-6]|[0-6]-[0-6]|\*/[0-6]|[0-6](,[0-6])*)$ - type: string - scheduleType: - description: |- - ScheduleType indicates if the control plane upgrade policy is "manual" and it's executed only one time or - whether it is "automatic" where an expression will calculate recurrent upgrades. - enum: - - Manual - - Automatic - type: string - updateType: - description: |- - UpdateType indicates if it is a control plane upgrade policy defined by the user or - triggered by Red Hat for addressing critical CVEs. - enum: - - UserInitiated - - ServiceInitiated - type: string - upgradeScope: - description: |- - UpgradeScope indicates if minor version upgrades are allowed for automatic upgrades. - Manual upgrades always allow it. - enum: - - PatchOnly - - PatchAndMinor - type: string - version: - description: Version is the desired upgrade version on "manual" - upgrade policies. - maxLength: 64 - type: string - required: - - scheduleType - - updateType - type: object - x-kubernetes-validations: - - message: version and nextRun are required when scheduleType is Manual - rule: self.scheduleType != 'Manual' || (has(self.version) && has(self.nextRun)) - - message: schedule must not be set when scheduleType is Manual - rule: self.scheduleType != 'Manual' || !has(self.schedule) - - message: schedule is required when scheduleType is Automatic - rule: self.scheduleType != 'Automatic' || has(self.schedule) - - message: version and nextRun must not be set when scheduleType is - Automatic - rule: self.scheduleType != 'Automatic' || (!has(self.version) && - !has(self.nextRun)) - - message: upgradeScope must not be set when scheduleType is Manual - rule: self.scheduleType != 'Manual' || !has(self.upgradeScope) - - message: upgradeScope is required when scheduleType is Automatic - rule: self.scheduleType != 'Automatic' || has(self.upgradeScope) - creatorARN: - description: CreatorARN is the IAM ARN of the user who created this - cluster. - pattern: '^arn:aws:' - type: string - deleteProtection: - description: DeleteProtection prevents accidental deletion when enabled. - type: boolean - displayName: - description: DisplayName is a human-readable name for the cluster. - maxLength: 256 - type: string - expirationTimestamp: - description: ExpirationTimestamp marks when this cluster should be - automatically deleted. - format: date-time - type: string - hostedCluster: - description: |- - HostedCluster contains the upstream HyperShift fields, mirrored as - passthrough types with per-field visibility and write-mode markers. - properties: - additionalTrustBundle: - description: additionalTrustBundle is a local reference to a ConfigMap - that must have a "ca-bundle.crt" key - properties: - name: - default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - type: string - type: object - x-kubernetes-map-type: atomic - auditWebhook: - description: auditWebhook contains metadata for configuring an - audit webhook endpoint - properties: - name: - default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - type: string - type: object - x-kubernetes-map-type: atomic - autoNode: - description: autoNode specifies the configuration for automatic - node provisioning and lifecycle management. - properties: - provisionerConfig: - description: provisionerConfig specifies the provisioner used - for automatic node management. - properties: - karpenter: - description: karpenter specifies the configuration for - the Karpenter provisioner. - properties: - aws: - description: aws specifies the AWS-specific configuration - for Karpenter. - properties: - roleARN: - description: "roleARN specifies the ARN of the - IAM role that Karpenter assumes to provision\nand - manage EC2 instances in the hosted cluster's - AWS account.\n\nThe referenced role must have - a trust relationship that allows it to be assumed\nby - the karpenter service account in the hosted - cluster via OIDC.\nExample:\n{\n\t\"Version\": - \"2012-10-17\",\n\t\"Statement\": [\n\t\t{\n\t\t\t\"Effect\": - \"Allow\",\n\t\t\t\"Principal\": {\n\t\t\t\t\"Federated\": - \"\"\n\t\t\t},\n\t\t\t\"Action\": - \"sts:AssumeRoleWithWebIdentity\",\n\t\t\t\"Condition\": - {\n\t\t\t\t\"StringEquals\": {\n\t\t\t\t\t\":sub\": - \"system:serviceaccount:kube-system:karpenter\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t]\n}\n\nThe - following is an example of the policy document - for this role.\n\n{\n\t\"Version\": \"2012-10-17\",\n\t\"Statement\": - [\n\t\t{\n\t\t\t\"Sid\": \"AllowScopedEC2InstanceAccessActions\",\n\t\t\t\"Effect\": - \"Allow\",\n\t\t\t\"Resource\": [\n\t\t\t\t\"arn:*:ec2:*::image/*\",\n\t\t\t\t\"arn:*:ec2:*::snapshot/*\",\n\t\t\t\t\"arn:*:ec2:*:*:security-group/*\",\n\t\t\t\t\"arn:*:ec2:*:*:subnet/*\"\n\t\t\t],\n\t\t\t\"Action\": - [\n\t\t\t\t\"ec2:RunInstances\",\n\t\t\t\t\"ec2:CreateFleet\"\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"Sid\": - \"AllowScopedEC2LaunchTemplateAccessActions\",\n\t\t\t\"Effect\": - \"Allow\",\n\t\t\t\"Resource\": \"arn:*:ec2:*:*:launch-template/*\",\n\t\t\t\"Action\": - [\n\t\t\t\t\"ec2:RunInstances\",\n\t\t\t\t\"ec2:CreateFleet\"\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"Sid\": - \"AllowScopedEC2InstanceActionsWithTags\",\n\t\t\t\"Effect\": - \"Allow\",\n\t\t\t\"Resource\": [\n\t\t\t\t\"arn:*:ec2:*:*:fleet/*\",\n\t\t\t\t\"arn:*:ec2:*:*:instance/*\",\n\t\t\t\t\"arn:*:ec2:*:*:volume/*\",\n\t\t\t\t\"arn:*:ec2:*:*:network-interface/*\",\n\t\t\t\t\"arn:*:ec2:*:*:launch-template/*\",\n\t\t\t\t\"arn:*:ec2:*:*:spot-instances-request/*\"\n\t\t\t],\n\t\t\t\"Action\": - [\n\t\t\t\t\"ec2:RunInstances\",\n\t\t\t\t\"ec2:CreateFleet\",\n\t\t\t\t\"ec2:CreateLaunchTemplate\"\n\t\t\t],\n\t\t\t\"Condition\": - {\n\t\t\t\t\"StringLike\": {\n\t\t\t\t\t\"aws:RequestTag/karpenter.sh/nodepool\": - \"*\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"Sid\": - \"AllowScopedResourceCreationTagging\",\n\t\t\t\"Effect\": - \"Allow\",\n\t\t\t\"Resource\": [\n\t\t\t\t\"arn:*:ec2:*:*:fleet/*\",\n\t\t\t\t\"arn:*:ec2:*:*:instance/*\",\n\t\t\t\t\"arn:*:ec2:*:*:volume/*\",\n\t\t\t\t\"arn:*:ec2:*:*:network-interface/*\",\n\t\t\t\t\"arn:*:ec2:*:*:launch-template/*\",\n\t\t\t\t\"arn:*:ec2:*:*:spot-instances-request/*\"\n\t\t\t],\n\t\t\t\"Action\": - \"ec2:CreateTags\",\n\t\t\t\"Condition\": {\n\t\t\t\t\"StringEquals\": - {\n\t\t\t\t\t\"ec2:CreateAction\": [\n\t\t\t\t\t\t\"RunInstances\",\n\t\t\t\t\t\t\"CreateFleet\",\n\t\t\t\t\t\t\"CreateLaunchTemplate\"\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t\"StringLike\": - {\n\t\t\t\t\t\"aws:RequestTag/karpenter.sh/nodepool\": - \"*\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"Sid\": - \"AllowScopedResourceTagging\",\n\t\t\t\"Effect\": - \"Allow\",\n\t\t\t\"Resource\": \"arn:*:ec2:*:*:instance/*\",\n\t\t\t\"Action\": - \"ec2:CreateTags\",\n\t\t\t\"Condition\": {\n\t\t\t\t\"StringLike\": - {\n\t\t\t\t\t\"aws:ResourceTag/karpenter.sh/nodepool\": - \"*\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"Sid\": - \"AllowScopedDeletion\",\n\t\t\t\"Effect\": - \"Allow\",\n\t\t\t\"Resource\": [\n\t\t\t\t\"arn:*:ec2:*:*:instance/*\",\n\t\t\t\t\"arn:*:ec2:*:*:launch-template/*\"\n\t\t\t],\n\t\t\t\"Action\": - [\n\t\t\t\t\"ec2:TerminateInstances\",\n\t\t\t\t\"ec2:DeleteLaunchTemplate\"\n\t\t\t],\n\t\t\t\"Condition\": - {\n\t\t\t\t\"StringLike\": {\n\t\t\t\t\t\"aws:ResourceTag/karpenter.sh/nodepool\": - \"*\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"Sid\": - \"AllowRegionalReadActions\",\n\t\t\t\"Effect\": - \"Allow\",\n\t\t\t\"Resource\": \"*\",\n\t\t\t\"Action\": - [\n\t\t\t\t\"ec2:DescribeImages\",\n\t\t\t\t\"ec2:DescribeInstances\",\n\t\t\t\t\"ec2:DescribeInstanceTypeOfferings\",\n\t\t\t\t\"ec2:DescribeInstanceTypes\",\n\t\t\t\t\"ec2:DescribeLaunchTemplates\",\n\t\t\t\t\"ec2:DescribeSecurityGroups\",\n\t\t\t\t\"ec2:DescribeSpotPriceHistory\",\n\t\t\t\t\"ec2:DescribeSubnets\"\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"Sid\": - \"AllowSSMReadActions\",\n\t\t\t\"Effect\": - \"Allow\",\n\t\t\t\"Resource\": \"arn:*:ssm:*::parameter/aws/service/*\",\n\t\t\t\"Action\": - \"ssm:GetParameter\"\n\t\t},\n\t\t{\n\t\t\t\"Sid\": - \"AllowPricingReadActions\",\n\t\t\t\"Effect\": - \"Allow\",\n\t\t\t\"Resource\": \"*\",\n\t\t\t\"Action\": - \"pricing:GetProducts\"\n\t\t},\n\t\t{\n\t\t\t\"Sid\": - \"AllowInterruptionQueueActions\",\n\t\t\t\"Effect\": - \"Allow\",\n\t\t\t\"Resource\": \"*\",\n\t\t\t\"Action\": - [\n\t\t\t\t\"sqs:DeleteMessage\",\n\t\t\t\t\"sqs:GetQueueUrl\",\n\t\t\t\t\"sqs:ReceiveMessage\"\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\t\"Sid\": - \"AllowPassingInstanceRole\",\n\t\t\t\"Effect\": - \"Allow\",\n\t\t\t\"Resource\": \"arn:*:iam::*:role/*\",\n\t\t\t\"Action\": - \"iam:PassRole\",\n\t\t\t\"Condition\": {\n\t\t\t\t\"StringEquals\": - {\n\t\t\t\t\t\"iam:PassedToService\": [\n\t\t\t\t\t\t\"ec2.amazonaws.com\",\n\t\t\t\t\t\t\"ec2.amazonaws.com.cn\"\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"Sid\": - \"AllowScopedInstanceProfileCreationActions\",\n\t\t\t\"Effect\": - \"Allow\",\n\t\t\t\"Resource\": \"arn:*:iam::*:instance-profile/*\",\n\t\t\t\"Action\": - [\n\t\t\t\t\"iam:CreateInstanceProfile\"\n\t\t\t],\n\t\t\t\"Condition\": - {\n\t\t\t\t\"StringLike\": {\n\t\t\t\t\t\"aws:RequestTag/karpenter.k8s.aws/ec2nodeclass\": - \"*\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"Sid\": - \"AllowScopedInstanceProfileTagActions\",\n\t\t\t\"Effect\": - \"Allow\",\n\t\t\t\"Resource\": \"arn:*:iam::*:instance-profile/*\",\n\t\t\t\"Action\": - [\n\t\t\t\t\"iam:TagInstanceProfile\"\n\t\t\t],\n\t\t\t\"Condition\": - {\n\t\t\t\t\"StringLike\": {\n\t\t\t\t\t\"aws:ResourceTag/karpenter.k8s.aws/ec2nodeclass\": - \"*\",\n\t\t\t\t\t\"aws:RequestTag/karpenter.k8s.aws/ec2nodeclass\": - \"*\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"Sid\": - \"AllowScopedInstanceProfileActions\",\n\t\t\t\"Effect\": - \"Allow\",\n\t\t\t\"Resource\": \"arn:*:iam::*:instance-profile/*\",\n\t\t\t\"Action\": - [\n\t\t\t\t\"iam:AddRoleToInstanceProfile\",\n\t\t\t\t\"iam:RemoveRoleFromInstanceProfile\",\n\t\t\t\t\"iam:DeleteInstanceProfile\"\n\t\t\t],\n\t\t\t\"Condition\": - {\n\t\t\t\t\"StringLike\": {\n\t\t\t\t\t\"aws:ResourceTag/karpenter.k8s.aws/ec2nodeclass\": - \"*\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"Sid\": - \"AllowInstanceProfileReadActions\",\n\t\t\t\"Effect\": - \"Allow\",\n\t\t\t\"Resource\": \"arn:*:iam::*:instance-profile/*\",\n\t\t\t\"Action\": - \"iam:GetInstanceProfile\"\n\t\t},\n\t\t{\n\t\t\t\"Sid\": - \"AllowUnscopedInstanceProfileListAction\",\n\t\t\t\"Effect\": - \"Allow\",\n\t\t\t\"Resource\": \"*\",\n\t\t\t\"Action\": - \"iam:ListInstanceProfiles\"\n\t\t}\n\t]\n}" - maxLength: 2048 - type: string - x-kubernetes-validations: - - message: roleARN must be a valid AWS IAM role - ARN (e.g. arn:aws:iam::123456789012:role/MyRole) - rule: self.matches('^arn:(aws|aws-cn|aws-us-gov):iam::[0-9]{12}:role/.+$') - required: - - roleARN - type: object - platform: - description: platform specifies the infrastructure - platform that Karpenter should provision nodes on. - enum: - - AWS - maxLength: 100 - type: string - required: - - platform - type: object - x-kubernetes-validations: - - message: aws is required when platform is AWS, and forbidden - otherwise - rule: 'self.platform == ''AWS'' ? has(self.aws) : !has(self.aws)' - name: - description: name specifies the name of the provisioner - to use for automatic node management. - enum: - - Karpenter - type: string - required: - - name - type: object - x-kubernetes-validations: - - message: karpenter is required when name is Karpenter, and - forbidden otherwise - rule: 'self.name == ''Karpenter'' ? has(self.karpenter) - : !has(self.karpenter)' - required: - - provisionerConfig - type: object - autoscaling: - description: autoscaling specifies auto-scaling behavior that - applies to all NodePools - properties: - balancingIgnoredLabels: - description: |- - balancingIgnoredLabels sets "--balancing-ignore-label