diff --git a/Makefile b/Makefile index 6c79a3ff..82a2991f 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,7 @@ fmt vet verify verify-mod deps mod-tidy \ manifests generate generate-deepcopy generate-clientset verify-clientset setup-envtest \ codegen-passthrough codegen-registry codegen-verify codegen verify-codegen \ + codegen-passthrough-clobber \ codegen-conversion verify-conversion \ generate-openapi verify-openapi swagger-ui \ image-api image-operator image-push-api image-push-operator @@ -334,14 +335,25 @@ generate-clientset: codegen-conversion $(CLIENT_GEN) $(BRIDGE_GEN) verify-clientset: generate-clientset git diff --exit-code clientset/ -codegen-passthrough: build-api-codegen +codegen-passthrough: codegen-registry cd api && ../bin/passthrough-gen \ -import-path github.com/openshift/hypershift/api/hypershift/v1beta1 \ -types HostedClusterSpec,NodePoolSpec \ -output-dir v1alpha1 \ - -package v1alpha1 + -package v1alpha1 \ + -registry ../hack/api-codegen/pkg/registry/field_metadata.json + rm -f api/v1alpha1/zz_generated.passthrough.go.raw -codegen-registry: codegen-passthrough generate-deepcopy build-api-codegen +codegen-passthrough-clobber: + rm -f api/v1alpha1/zz_generated.passthrough.go + cd api && ../bin/passthrough-gen \ + -import-path github.com/openshift/hypershift/api/hypershift/v1beta1 \ + -types HostedClusterSpec,NodePoolSpec \ + -output-dir v1alpha1 \ + -package v1alpha1 \ + -registry ../hack/api-codegen/pkg/registry/field_metadata.json + +codegen-registry: generate-deepcopy build-api-codegen ./bin/marker-scanner \ -input-dirs api/v1alpha1 \ -output-file hack/api-codegen/pkg/registry/field_metadata.go \ diff --git a/api/v1alpha1/public/hostedclusterspecpassthrough_types.go b/api/v1alpha1/public/hostedclusterspecpassthrough_types.go index 337159da..182defe4 100644 --- a/api/v1alpha1/public/hostedclusterspecpassthrough_types.go +++ b/api/v1alpha1/public/hostedclusterspecpassthrough_types.go @@ -11,51 +11,56 @@ type HostedClusterSpecPassthrough struct { // release specifies the desired OCP release payload for all the hosted cluster components. // +k8s:openapi-gen=true // +hyperfleet:write-mode=mutable + // +required Release hypershiftv1beta1.Release `json:"release"` // 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=true // +hyperfleet:write-mode=mutable + // +required Platform hypershiftv1beta1.PlatformSpec `json:"platform"` // networking specifies network configuration for the hosted cluster. // +k8s:openapi-gen=true // +hyperfleet:write-mode=mutable + // +required Networking hypershiftv1beta1.ClusterNetworking `json:"networking"` // 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 - Etcd hypershiftv1beta1.EtcdSpec `json:"etcd"` // issuerURL is an OIDC issuer URL which will be used as the issuer in all // +k8s:openapi-gen=true // +hyperfleet:write-mode=mutable + // +optional IssuerURL string `json:"issuerURL,omitempty"` // configuration specifies configuration for individual OCP components in the // +k8s:openapi-gen=true // +hyperfleet:write-mode=service-set - Configuration *ClusterConfiguration `json:"configuration,omitempty"` + // +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"` // imageContentSources specifies image mirrors that can be used by cluster // +k8s:openapi-gen=true // +hyperfleet:write-mode=mutable - // +kubebuilder:validation:MaxItems=50 + // +optional ImageContentSources []hypershiftv1beta1.ImageContentSource `json:"imageContentSources,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"` } diff --git a/api/v1alpha1/public/nodepoolspecpassthrough_types.go b/api/v1alpha1/public/nodepoolspecpassthrough_types.go index cf8cf446..3931949c 100644 --- a/api/v1alpha1/public/nodepoolspecpassthrough_types.go +++ b/api/v1alpha1/public/nodepoolspecpassthrough_types.go @@ -11,17 +11,21 @@ type NodePoolSpecPassthrough struct { // clusterName is the name of the HostedCluster this NodePool belongs to. // +k8s:openapi-gen=true // +hyperfleet:write-mode=mutable + // +required 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 + // +required Release hypershiftv1beta1.Release `json:"release"` // platform specifies the underlying infrastructure provider for the NodePool // +k8s:openapi-gen=true // +hyperfleet:write-mode=mutable + // +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=true // +hyperfleet:write-mode=mutable + // +optional Replicas *int32 `json:"replicas,omitempty"` } diff --git a/api/v1alpha1/public/openapi.yaml b/api/v1alpha1/public/openapi.yaml index 6a72b984..22193661 100644 --- a/api/v1alpha1/public/openapi.yaml +++ b/api/v1alpha1/public/openapi.yaml @@ -3033,57 +3033,14 @@ 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 - x-kubernetes-validations: - - message: Only managed configuration must be set when managementType is Managed - rule: 'self.managementType == ''Managed'' ? has(self.managed) : !has(self.managed)' - - message: Only unmanaged configuration must be set when managementType is Unmanaged - rule: 'self.managementType == ''Unmanaged'' ? has(self.unmanaged) : !has(self.unmanaged)' fips: description: fips indicates whether this cluster's nodes will be running in FIPS mode. type: boolean @@ -3114,26 +3071,9 @@ components: - source type: object 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 @@ -3142,17 +3082,6 @@ 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 @@ -3162,238 +3091,21 @@ 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: - - etcd - networking - platform - - pullSecret - release - - services 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 @@ -3404,67 +3116,8 @@ 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 - platform - release type: object diff --git a/api/v1alpha1/public/zz_generated.deepcopy.go b/api/v1alpha1/public/zz_generated.deepcopy.go index 5e1925da..976d2b78 100644 --- a/api/v1alpha1/public/zz_generated.deepcopy.go +++ b/api/v1alpha1/public/zz_generated.deepcopy.go @@ -261,10 +261,9 @@ func (in *HostedClusterSpecPassthrough) DeepCopyInto(out *HostedClusterSpecPasst in.Platform.DeepCopyInto(&out.Platform) in.Networking.DeepCopyInto(&out.Networking) out.AutoNode = in.AutoNode - in.Etcd.DeepCopyInto(&out.Etcd) 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/zz_generated.passthrough.go b/api/v1alpha1/zz_generated.passthrough.go index 9144772a..06ffaa0b 100644 --- a/api/v1alpha1/zz_generated.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=false - // +hyperfleet:write-mode=service-set + // +k8s:openapi-gen=true + // +hyperfleet:write-mode=mutable // +required Release hypershiftv1beta1.Release `json:"release"` // controlPlaneRelease is like spec.release but only for the components running on the management cluster. @@ -42,8 +42,8 @@ type HostedClusterSpecPassthrough struct { // +optional Channel string `json:"channel,omitempty"` // platform specifies the underlying infrastructure provider for the cluster - // +k8s:openapi-gen=false - // +hyperfleet:write-mode=service-set + // +k8s:openapi-gen=true + // +hyperfleet:write-mode=mutable // +required Platform hypershiftv1beta1.PlatformSpec `json:"platform"` // kubeAPIServerDNSName specifies a desired DNS name to resolve to the KAS. @@ -67,8 +67,8 @@ type HostedClusterSpecPassthrough struct { // +optional DNS hypershiftv1beta1.DNSSpec `json:"dns,omitempty"` // networking specifies network configuration for the hosted cluster. - // +k8s:openapi-gen=false - // +hyperfleet:write-mode=service-set + // +k8s:openapi-gen=true + // +hyperfleet:write-mode=mutable // +required Networking hypershiftv1beta1.ClusterNetworking `json:"networking"` // autoscaling specifies auto-scaling behavior that applies to all NodePools @@ -102,8 +102,8 @@ type HostedClusterSpecPassthrough struct { // +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 + // +k8s:openapi-gen=true + // +hyperfleet:write-mode=mutable // +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 @@ -127,8 +127,8 @@ type HostedClusterSpecPassthrough struct { // +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 + // +k8s:openapi-gen=true + // +hyperfleet:write-mode=mutable // +optional ImageContentSources []hypershiftv1beta1.ImageContentSource `json:"imageContentSources,omitempty"` // additionalTrustBundle is a local reference to a ConfigMap that must have a "ca-bundle.crt" key @@ -181,23 +181,23 @@ 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=false - // +hyperfleet:write-mode=service-set + // +k8s:openapi-gen=true + // +hyperfleet:write-mode=mutable // +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 + // +k8s:openapi-gen=true + // +hyperfleet:write-mode=mutable // +required Release hypershiftv1beta1.Release `json:"release"` // platform specifies the underlying infrastructure provider for the NodePool - // +k8s:openapi-gen=false - // +hyperfleet:write-mode=service-set + // +k8s:openapi-gen=true + // +hyperfleet:write-mode=mutable // +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 + // +k8s:openapi-gen=true + // +hyperfleet:write-mode=mutable // +optional Replicas *int32 `json:"replicas,omitempty"` // management specifies behavior for managing nodes in the pool, such as diff --git a/docs/api/passthrough-design.md b/docs/api/passthrough-design.md new file mode 100644 index 00000000..2591587a --- /dev/null +++ b/docs/api/passthrough-design.md @@ -0,0 +1,147 @@ +# Passthrough Codegen Design + +## Overview + +The passthrough codegen pipeline generates Go struct types that mirror upstream HyperShift API types (`HostedClusterSpec`, `NodePoolSpec`) into the HyperFleet `api/v1alpha1` package. Each mirrored struct (e.g., `HostedClusterSpecPassthrough`) carries curated markers that control field visibility, mutability, and feature gating. These markers drive all downstream codegen: OpenAPI schemas, REST types, conversion functions, and CRD variants. + +## Goals + +1. When HyperShift adds, removes, or renames fields, `make codegen-passthrough` picks up the change and adds it with safe defaults. +2. Human-curated markers (visibility, write-mode) survive the regeneration round-trip. +3. A single source of truth (the committed passthrough file) feeds both the field registry and the regenerated output. +4. The pipeline is verifiable: `git diff --exit-code` after regeneration confirms nothing drifted. + +## File roles + +| File | Role | +|------|------| +| `api/v1alpha1/zz_generated.passthrough.go` | The committed passthrough types. Human-curated markers live here. This is the source of truth for field policy. Despite the `zz_generated` prefix, this file is intentionally hand-edited to curate markers, then regenerated to pick up upstream struct changes. | +| `api/v1alpha1/configuration.go` | Local mirror of `ClusterConfiguration` with granular markers on nested fields (kubelet, machineConfig). Used by the scanner for nested field marker extraction. | +| `hack/api-codegen/pkg/registry/field_metadata.json` | Generated field registry (JSON). Produced by `marker-scanner` from the passthrough file. Consumed by `passthrough-gen`, `conversion-gen`, and `openapi-gen`. | +| `hack/api-codegen/pkg/registry/field_metadata.go` | Generated field registry (Go). Same data as the JSON, importable by Go code. | + +## Pipeline + +``` + ┌──────────────────────────────────────────┐ + │ api/v1alpha1/zz_generated.passthrough.go │ + │ (committed, human-curated markers) │ + └─────────────┬────────────────────────────┘ + │ + make codegen-registry + (marker-scanner scans markers) + │ + ▼ + ┌──────────────────────────────────────┐ + │ hack/api-codegen/pkg/registry/ │ + │ field_metadata.json │ + │ field_metadata.go │ + └─────────────┬────────────────────────┘ + │ + make codegen-passthrough + (passthrough-gen reads registry + + HyperShift source via go list) + │ + ▼ + ┌──────────────────────────────────────────┐ + │ api/v1alpha1/zz_generated.passthrough.go │ + │ (regenerated, markers preserved) │ + └──────────────────────────────────────────┘ + │ + ┌──────────────┼──────────────┐ + ▼ ▼ ▼ + codegen-conversion generate-openapi codegen (CRD verify) +``` + +The loop is closed: the committed file feeds the registry, and the registry feeds regeneration. When the round-trip is correct, `git diff` after regeneration shows no changes. + +## Curation workflow + +### Adding markers to an existing field + +1. Edit `api/v1alpha1/zz_generated.passthrough.go` — change markers on the field (e.g., set `+k8s:openapi-gen=true` to make it visible). +2. Run `make codegen-registry` to update the registry. +3. Run `make codegen-passthrough` to verify the round-trip (should produce no diff). +4. Run downstream codegen (`make codegen-conversion`, `make generate-openapi`) to propagate the change to REST types and OpenAPI. +5. Commit. + +### Picking up new fields from a HyperShift bump + +1. Update the HyperShift dependency in `api/go.mod` and `hack/api-codegen/go.mod`. +2. Run `make codegen-registry` (unchanged — scans existing committed file). +3. Run `make codegen-passthrough` — new fields appear with safe defaults: + - `+k8s:openapi-gen=false` (hidden from public API) + - `+hyperfleet:write-mode=service-set` (not customer-writable) +4. Review the diff. Curate markers on any new fields that should be visible or mutable. +5. Re-run `make codegen-registry && make codegen-passthrough` to verify round-trip. +6. Run downstream codegen and commit. + +### Removing fields after a HyperShift bump + +If HyperShift removes a field from `HostedClusterSpec` or `NodePoolSpec`, `make codegen-passthrough` will regenerate the file without that field. The registry will contain a stale entry for it, but that is harmless — unused registry entries don't affect codegen. The stale entry is cleaned up the next time `make codegen-registry` runs after the regenerated file is committed. + +## Marker types + +The registry captures the following marker categories from the passthrough file: + +| Marker | Registry field | Purpose | +|--------|---------------|---------| +| `+k8s:openapi-gen=false` | `hidden: true` | Field excluded from public OpenAPI and REST types | +| `+hyperfleet:write-mode=mutable\|immutable\|service-set` | `writeMode` | Controls customer mutability | +| `+openshift:enable:FeatureGate=X` | `featureGate` | Field gated behind a feature flag | +| `+hyperfleet:validation:FeatureGateAwareWriteMode:...` | `featureGateAwareWriteModes` | Write-mode varies by active feature gates | + +Upstream markers like `+optional` and `+required` are propagated directly from HyperShift source by `passthrough-gen` via `isForwardedMarker()` — they do not go through the registry. + +## Safe defaults + +When `passthrough-gen` encounters a field from HyperShift that has no entry in the registry (i.e., a newly added field), it applies safe defaults: + +- `+k8s:openapi-gen=false` — hidden from the public API until explicitly curated +- `+hyperfleet:write-mode=service-set` — not customer-writable until explicitly allowed + +This ensures new upstream fields don't accidentally become visible or mutable. + +## Resolved gaps + +The following issues existed in main and were fixed on this branch: + +1. **Stale embedded registry** (fixed): `passthrough-gen` embedded a copy of `field_metadata.json` via `//go:embed`. Removed the embedded copy and made `-registry` mandatory. + +2. **Scanner skips `zz_generated.passthrough.go`** (fixed): Added an exception for `zz_generated.passthrough.go` in the marker scanner's file filter. + +3. **Conversion-gen skips `zz_generated.passthrough.go`** (fixed): Same file filter fix in `conversion-gen`'s `parseTypes()`. + +4. **Cyclomatic complexity** (fixed): Extracted `parseStructType()` from `conversion-gen`'s `parseTypes()` to bring complexity under the lint threshold. + +## Makefile targets + +``` +make codegen-passthrough # registry → passthrough-gen → zz_generated.passthrough.go +make codegen-registry # marker-scanner → field_metadata.{json,go} +make generate # full pipeline (registry + conversion + openapi + clientset) +make verify-codegen # CI check: codegen outputs match committed files +``` + +### Dependency chain + +``` +codegen-passthrough + └─ codegen-registry + ├─ generate-deepcopy (controller-gen deepcopy) + └─ build-api-codegen (builds passthrough-gen, marker-scanner, etc.) +``` + +`codegen-passthrough` depends on `codegen-registry` to ensure the registry JSON is fresh before regeneration. + +## Verification + +The round-trip is verified by: + +```bash +make codegen-registry +make codegen-passthrough +git diff --exit-code api/v1alpha1/zz_generated.passthrough.go +``` + +CI runs this via `make verify-codegen`. diff --git a/hack/api-codegen/cmd/passthrough-gen/field_metadata.json b/hack/api-codegen/cmd/passthrough-gen/field_metadata.json deleted file mode 100644 index 167ecff8..00000000 --- a/hack/api-codegen/cmd/passthrough-gen/field_metadata.json +++ /dev/null @@ -1,577 +0,0 @@ -[ - { - "fieldPath": "allowedUnsafeSysctls", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "apiServer", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "authentication", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "containerLogMaxFiles", - "writeMode": "mutable" - }, - { - "fieldPath": "containerLogMaxSize", - "writeMode": "mutable" - }, - { - "fieldPath": "cpuManagerPolicy", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "cpuManagerPolicyOptions", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "cpuManagerReconcilePeriod", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "evictionHard", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "evictionSoft", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "evictionSoftGracePeriod", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "featureGate", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "image", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "imageGCHighThresholdPercent", - "writeMode": "mutable" - }, - { - "fieldPath": "imageGCLowThresholdPercent", - "writeMode": "mutable" - }, - { - "fieldPath": "imageMinimumGCAge", - "writeMode": "mutable" - }, - { - "fieldPath": "ingress", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "kubeReserved", - "writeMode": "immutable" - }, - { - "fieldPath": "kubelet", - "writeMode": "service-set" - }, - { - "fieldPath": "kubelet.allowedUnsafeSysctls", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "kubelet.containerLogMaxFiles", - "writeMode": "mutable" - }, - { - "fieldPath": "kubelet.containerLogMaxSize", - "writeMode": "mutable" - }, - { - "fieldPath": "kubelet.cpuManagerPolicy", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "kubelet.cpuManagerPolicyOptions", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "kubelet.cpuManagerReconcilePeriod", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "kubelet.evictionHard", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "kubelet.evictionSoft", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "kubelet.evictionSoftGracePeriod", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "kubelet.imageGCHighThresholdPercent", - "writeMode": "mutable" - }, - { - "fieldPath": "kubelet.imageGCLowThresholdPercent", - "writeMode": "mutable" - }, - { - "fieldPath": "kubelet.imageMinimumGCAge", - "writeMode": "mutable" - }, - { - "fieldPath": "kubelet.kubeReserved", - "writeMode": "immutable" - }, - { - "fieldPath": "kubelet.maxPods", - "writeMode": "mutable" - }, - { - "fieldPath": "kubelet.memoryThrottlingFactor", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "kubelet.podPidsLimit", - "writeMode": "mutable" - }, - { - "fieldPath": "kubelet.registryBurst", - "writeMode": "mutable", - "featureGate": "HyperFleetKubeletAdvanced" - }, - { - "fieldPath": "kubelet.registryPullQPS", - "writeMode": "mutable", - "featureGate": "HyperFleetKubeletAdvanced" - }, - { - "fieldPath": "kubelet.serializeImagePulls", - "writeMode": "mutable", - "featureGate": "HyperFleetKubeletAdvanced" - }, - { - "fieldPath": "kubelet.streamingConnectionIdleTimeout", - "writeMode": "mutable" - }, - { - "fieldPath": "kubelet.systemReserved", - "writeMode": "immutable" - }, - { - "fieldPath": "kubelet.topologyManagerPolicy", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "kubelet.topologyManagerScope", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "machineConfig", - "writeMode": "service-set" - }, - { - "fieldPath": "machineConfig.allowedKernelArguments", - "writeMode": "immutable", - "featureGate": "HyperFleetMachineConfig" - }, - { - "fieldPath": "machineConfig.extensions", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "machineConfig.files", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "machineConfig.fips", - "writeMode": "immutable" - }, - { - "fieldPath": "machineConfig.kernelArguments", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "machineConfig.kernelType", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "machineConfig.systemdUnits", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "maxPods", - "writeMode": "mutable" - }, - { - "fieldPath": "memoryThrottlingFactor", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "network", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "oauth", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "podPidsLimit", - "writeMode": "mutable" - }, - { - "fieldPath": "proxy", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "registryBurst", - "writeMode": "mutable", - "featureGate": "HyperFleetKubeletAdvanced" - }, - { - "fieldPath": "registryPullQPS", - "writeMode": "mutable", - "featureGate": "HyperFleetKubeletAdvanced" - }, - { - "fieldPath": "scheduler", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "serializeImagePulls", - "writeMode": "mutable", - "featureGate": "HyperFleetKubeletAdvanced" - }, - { - "fieldPath": "spec.accountId", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.autoRepair", - "writeMode": "mutable" - }, - { - "fieldPath": "spec.creatorARN", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.deleteProtection", - "writeMode": "mutable" - }, - { - "fieldPath": "spec.displayName", - "writeMode": "mutable" - }, - { - "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.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": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.fips", - "writeMode": "service-set" - }, - { - "fieldPath": "spec.hostedCluster.imageContentSources", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.infraID", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.infrastructureAvailabilityPolicy", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.issuerURL", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.kubeAPIServerDNSName", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.labels", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.networking", - "writeMode": "service-set", - "hidden": true - }, - { - "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": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.pullSecret", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.hostedCluster.release", - "writeMode": "service-set", - "hidden": true - }, - { - "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", - "hidden": true - }, - { - "fieldPath": "spec.internalPoolId", - "writeMode": "service-set", - "hidden": true - }, - { - "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": "service-set", - "hidden": true - }, - { - "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.pausedUntil", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.nodePool.platform", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.nodePool.release", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.nodePool.replicas", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.nodePool.taints", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.nodePool.tuningConfig", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "spec.properties", - "writeMode": "mutable" - }, - { - "fieldPath": "spec.tags", - "writeMode": "mutable", - "featureGate": "HyperFleetAutoScaling" - }, - { - "fieldPath": "streamingConnectionIdleTimeout", - "writeMode": "mutable" - }, - { - "fieldPath": "systemReserved", - "writeMode": "immutable" - }, - { - "fieldPath": "topologyManagerPolicy", - "writeMode": "service-set", - "hidden": true - }, - { - "fieldPath": "topologyManagerScope", - "writeMode": "service-set", - "hidden": true - } -] \ No newline at end of file diff --git a/hack/api-codegen/cmd/passthrough-gen/main.go b/hack/api-codegen/cmd/passthrough-gen/main.go index cc6295b5..6d47f40e 100644 --- a/hack/api-codegen/cmd/passthrough-gen/main.go +++ b/hack/api-codegen/cmd/passthrough-gen/main.go @@ -1,7 +1,6 @@ package main import ( - _ "embed" "flag" "fmt" "log" @@ -12,9 +11,6 @@ import ( "github.com/openshift-online/rosa-hyperfleet-api/hack/api-codegen/pkg/passthrough" ) -//go:embed field_metadata.json -var embeddedRegistry []byte - func main() { var ( sourceDir string @@ -30,13 +26,13 @@ func main() { flag.StringVar(&importPath, "import-path", "", "Go import path to resolve via go.mod (use this OR -source-dir)") flag.StringVar(&outputDir, "output-dir", "", "Directory for generated output (required)") flag.StringVar(&typeNames, "types", "", "Comma-separated list of type names to generate (required)") - flag.StringVar(®istryFile, "registry", "", "Path to field metadata registry (optional)") + flag.StringVar(®istryFile, "registry", "", "Path to field metadata registry (required)") flag.StringVar(&packageName, "package", "v1alpha1", "Package name for generated code") flag.StringVar(&fieldPrefix, "field-prefix", "", "Dotted path prefix for registry lookups (e.g., spec.hostedCluster)") flag.Parse() // Validate flags - if outputDir == "" || typeNames == "" { + if outputDir == "" || typeNames == "" || registryFile == "" { flag.Usage() os.Exit(1) } @@ -55,28 +51,16 @@ func main() { types[i] = strings.TrimSpace(types[i]) } - // Load registry: use explicit file if provided, otherwise use embedded default - var registry markers.FieldRegistry - if registryFile != "" { - log.Printf("Loading field registry from: %s", registryFile) - var err error - registry, err = markers.LoadRegistryFromJSON(registryFile) - if err != nil { - log.Fatalf("Failed to load registry: %v", err) - } - log.Printf("Loaded %d field markers from registry", len(registry)) - } else { - var err error - registry, err = markers.LoadRegistryFromJSONBytes(embeddedRegistry) - if err != nil { - log.Fatalf("Failed to load embedded registry: %v", err) - } - log.Printf("Loaded %d field markers from embedded registry", len(registry)) + // Load registry + log.Printf("Loading field registry from: %s", registryFile) + registry, err := markers.LoadRegistryFromJSON(registryFile) + if err != nil { + log.Fatalf("Failed to load registry: %v", err) } + log.Printf("Loaded %d field markers from registry", len(registry)) // Create generator var gen *passthrough.Generator - var err error if importPath != "" { log.Printf("Resolving import path: %s", importPath) diff --git a/hack/api-codegen/pkg/conversion/generator.go b/hack/api-codegen/pkg/conversion/generator.go index 2d16f7fe..3a97c07e 100644 --- a/hack/api-codegen/pkg/conversion/generator.go +++ b/hack/api-codegen/pkg/conversion/generator.go @@ -138,7 +138,7 @@ func (g *Generator) parseTypes() error { pkgs, err := parser.ParseDir(fset, dir, func(fi os.FileInfo) bool { name := fi.Name() return !strings.HasSuffix(name, "_test.go") && - !strings.HasPrefix(name, "zz_generated") + (!strings.HasPrefix(name, "zz_generated") || name == "zz_generated.passthrough.go") }, parser.ParseComments) if err != nil { return fmt.Errorf("parsing directory %s: %w", dir, err) @@ -165,29 +165,7 @@ func (g *Generator) parseTypes() error { structType, ok := typeSpec.Type.(*ast.StructType) if ok { - ti := &typeInfo{ - Name: typeName, - StructType: structType, - Doc: genDecl.Doc, - } - - for _, field := range structType.Fields.List { - if len(field.Names) == 0 { - ti.Embeds = append(ti.Embeds, g.exprToString(field.Type)) - continue - } - for _, name := range field.Names { - if !name.IsExported() { - continue - } - fi := g.parseField(typeName, field, name) - if fi != nil { - ti.Fields = append(ti.Fields, fi) - } - } - } - - g.typeInfos[typeName] = ti + g.typeInfos[typeName] = g.parseStructType(typeName, structType, genDecl.Doc) continue } @@ -288,6 +266,26 @@ func (g *Generator) extractClientMarkers(file *ast.File) { } } +func (g *Generator) parseStructType(typeName string, structType *ast.StructType, doc *ast.CommentGroup) *typeInfo { + ti := &typeInfo{Name: typeName, StructType: structType, Doc: doc} + for _, field := range structType.Fields.List { + if len(field.Names) == 0 { + ti.Embeds = append(ti.Embeds, g.exprToString(field.Type)) + continue + } + for _, name := range field.Names { + if !name.IsExported() { + continue + } + fi := g.parseField(typeName, field, name) + if fi != nil { + ti.Fields = append(ti.Fields, fi) + } + } + } + return ti +} + func (g *Generator) parseField(typeName string, field *ast.Field, name *ast.Ident) *fieldInfo { goName := name.Name jsonName := g.extractJSONTag(field) diff --git a/hack/api-codegen/pkg/markers/scanner.go b/hack/api-codegen/pkg/markers/scanner.go index 10096dd8..0f59b438 100644 --- a/hack/api-codegen/pkg/markers/scanner.go +++ b/hack/api-codegen/pkg/markers/scanner.go @@ -56,7 +56,7 @@ func (s *MarkerScanner) scanDir(dir string) error { // Skip test files and generated files name := fi.Name() return !strings.HasSuffix(name, "_test.go") && - !strings.HasPrefix(name, "zz_generated") + (!strings.HasPrefix(name, "zz_generated") || name == "zz_generated.passthrough.go") }, parser.ParseComments) if err != nil { diff --git a/hack/api-codegen/pkg/markers/scanner_test.go b/hack/api-codegen/pkg/markers/scanner_test.go deleted file mode 100644 index 4f75d281..00000000 --- a/hack/api-codegen/pkg/markers/scanner_test.go +++ /dev/null @@ -1,234 +0,0 @@ -package markers - -import ( - "os" - "path/filepath" - "testing" -) - -func TestMarkerExtraction(t *testing.T) { - // Create a temporary directory with test files - tmpDir := t.TempDir() - - testFile := filepath.Join(tmpDir, "types.go") - content := `package test - -// Root type - scanner starts here -type Cluster struct { - Spec ClusterSpec ` + "`json:\"spec\"`" + ` -} - -type ClusterSpec struct { - // Customer can set and change - // +hyperfleet:write-mode=mutable - DeleteProtection *bool ` + "`json:\"deleteProtection,omitempty\"`" + ` - - // Customer sets on create, cannot change - // +hyperfleet:write-mode=immutable - Name string ` + "`json:\"name\"`" + ` - - // Platform sets, customer cannot see - // +k8s:openapi-gen=false - // +hyperfleet:write-mode=service-set - AccountID string ` + "`json:\"accountId\"`" + ` - - // Gated field - // +openshift:enable:FeatureGate=HyperFleetEtcdConfig - // +hyperfleet:write-mode=immutable - Etcd *EtcdSpec ` + "`json:\"etcd,omitempty\"`" + ` -} - -type EtcdSpec struct { - // +hyperfleet:write-mode=immutable - ManagementType string ` + "`json:\"managementType\"`" + ` -} -` - - if err := os.WriteFile(testFile, []byte(content), 0644); err != nil { - t.Fatalf("Failed to write test file: %v", err) - } - - // Create scanner and scan - scanner := NewScanner([]string{tmpDir}, false) - if err := scanner.Scan(); err != nil { - t.Fatalf("Scan failed: %v", err) - } - - // Verify results - paths are now fully qualified from root type - tests := []struct { - fieldPath string - writeMode WriteMode - featureGate string - hidden bool - }{ - {"spec.deleteProtection", Mutable, "", false}, - {"spec.name", Immutable, "", false}, - {"spec.accountId", ServiceSet, "", true}, - {"spec.etcd", Immutable, "HyperFleetEtcdConfig", false}, - {"spec.etcd.managementType", Immutable, "", false}, - } - - for _, tt := range tests { - t.Run(tt.fieldPath, func(t *testing.T) { - meta, found := scanner.Registry[tt.fieldPath] - if !found { - t.Fatalf("Field %s not found in registry", tt.fieldPath) - } - - if meta.WriteMode != tt.writeMode { - t.Errorf("WriteMode = %v, want %v", meta.WriteMode, tt.writeMode) - } - - if meta.FeatureGate != tt.featureGate { - t.Errorf("FeatureGate = %v, want %v", meta.FeatureGate, tt.featureGate) - } - - if meta.Hidden != tt.hidden { - t.Errorf("Hidden = %v, want %v", meta.Hidden, tt.hidden) - } - }) - } -} - -func TestPassthroughTypesPrefixed(t *testing.T) { - tmpDir := t.TempDir() - - testFile := filepath.Join(tmpDir, "types.go") - content := `package test - -type HostedClusterSpecPassthrough struct { - // +k8s:openapi-gen=true - // +hyperfleet:write-mode=service-set - PausedUntil *string ` + "`json:\"pausedUntil,omitempty\"`" + ` - - // +k8s:openapi-gen=true - // +hyperfleet:write-mode=immutable - FIPS bool ` + "`json:\"fips\"`" + ` -} - -type NodePoolSpecPassthrough struct { - // +k8s:openapi-gen=false - // +hyperfleet:write-mode=service-set - PausedUntil *string ` + "`json:\"pausedUntil,omitempty\"`" + ` - - // +k8s:openapi-gen=false - // +hyperfleet:write-mode=service-set - Replicas *int ` + "`json:\"replicas,omitempty\"`" + ` -} -` - - if err := os.WriteFile(testFile, []byte(content), 0644); err != nil { - t.Fatalf("Failed to write test file: %v", err) - } - - scanner := NewScanner([]string{tmpDir}, false) - if err := scanner.Scan(); err != nil { - t.Fatalf("Scan failed: %v", err) - } - - tests := []struct { - fieldPath string - writeMode WriteMode - hidden bool - }{ - {"spec.hostedCluster.pausedUntil", ServiceSet, false}, - {"spec.hostedCluster.fips", Immutable, false}, - {"spec.nodePool.pausedUntil", ServiceSet, true}, - {"spec.nodePool.replicas", ServiceSet, true}, - } - - for _, tt := range tests { - t.Run(tt.fieldPath, func(t *testing.T) { - meta, found := scanner.Registry[tt.fieldPath] - if !found { - t.Fatalf("Field %s not found in registry (keys: %v)", tt.fieldPath, registryKeys(scanner.Registry)) - } - if meta.WriteMode != tt.writeMode { - t.Errorf("WriteMode = %v, want %v", meta.WriteMode, tt.writeMode) - } - if meta.Hidden != tt.hidden { - t.Errorf("Hidden = %v, want %v", meta.Hidden, tt.hidden) - } - }) - } - - // Verify no flat "pausedUntil" key exists (the old collision) - if _, found := scanner.Registry["pausedUntil"]; found { - t.Error("flat key \"pausedUntil\" should not exist; passthrough fields must be prefixed") - } -} - -func registryKeys(r FieldRegistry) []string { - keys := make([]string, 0, len(r)) - for k := range r { - keys = append(keys, k) - } - return keys -} - -func TestValidation(t *testing.T) { - tests := []struct { - name string - content string - wantErr bool - }{ - { - name: "valid - all visible fields have write mode", - content: `package test -type Root struct { - Spec Spec ` + "`json:\"spec\"`" + ` -} -type Spec struct { - // +hyperfleet:write-mode=mutable - Field string ` + "`json:\"field\"`" + ` -}`, - wantErr: false, - }, - { - name: "invalid - field has marker but missing write mode", - content: `package test -type Root struct { - Spec Spec ` + "`json:\"spec\"`" + ` -} -type Spec struct { - // +openshift:enable:FeatureGate=Test - Field string ` + "`json:\"field\"`" + ` -}`, - wantErr: true, - }, - { - name: "valid - hidden field without write mode is OK", - content: `package test -type Root struct { - Spec Spec ` + "`json:\"spec\"`" + ` -} -type Spec struct { - // +k8s:openapi-gen=false - // +hyperfleet:write-mode=service-set - Field string ` + "`json:\"field\"`" + ` -}`, - wantErr: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tmpDir := t.TempDir() - testFile := filepath.Join(tmpDir, "types.go") - - if err := os.WriteFile(testFile, []byte(tt.content), 0644); err != nil { - t.Fatalf("Failed to write test file: %v", err) - } - - scanner := NewScanner([]string{tmpDir}, false) - if err := scanner.Scan(); err != nil { - t.Fatalf("Scan failed: %v", err) - } - - err := scanner.Registry.Validate() - if (err != nil) != tt.wantErr { - t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} diff --git a/hack/api-codegen/pkg/passthrough/loader.go b/hack/api-codegen/pkg/passthrough/loader.go index 1b8fe279..73b477a6 100644 --- a/hack/api-codegen/pkg/passthrough/loader.go +++ b/hack/api-codegen/pkg/passthrough/loader.go @@ -175,7 +175,6 @@ func isForwardedMarker(marker string) bool { func (g *Generator) typeToString(expr ast.Expr) string { switch t := expr.(type) { case *ast.Ident: - // Check if this is a type from the source package that needs to be qualified typeName := t.Name if g.SourcePackageAlias != "" && g.isSourcePackageType(typeName) { return g.SourcePackageAlias + "." + typeName diff --git a/hack/api-codegen/pkg/registry/field_metadata.go b/hack/api-codegen/pkg/registry/field_metadata.go index 18583288..dd594e42 100644 --- a/hack/api-codegen/pkg/registry/field_metadata.go +++ b/hack/api-codegen/pkg/registry/field_metadata.go @@ -321,6 +321,160 @@ 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.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: ServiceSet, + Hidden: true, + }, + "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, @@ -335,6 +489,77 @@ 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 d83072b8..6884ce26 100644 --- a/hack/api-codegen/pkg/registry/field_metadata.json +++ b/hack/api-codegen/pkg/registry/field_metadata.json @@ -300,6 +300,160 @@ "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.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": "service-set", + "hidden": true + }, + { + "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", @@ -314,6 +468,77 @@ "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/platform-api/go.mod b/platform-api/go.mod index 1f1345d6..743ac98e 100644 --- a/platform-api/go.mod +++ b/platform-api/go.mod @@ -24,6 +24,7 @@ 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 @@ -89,7 +90,6 @@ 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/platform-api/pkg/conversion/types.go b/platform-api/pkg/conversion/types.go index 91c5dfc6..e2ee017b 100644 --- a/platform-api/pkg/conversion/types.go +++ b/platform-api/pkg/conversion/types.go @@ -4,6 +4,9 @@ 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" ) @@ -11,12 +14,38 @@ 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 *hypershiftv1beta1.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) @@ -25,6 +54,10 @@ 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"` + // Etcd is service-set (platform-managed, hidden from API) + Etcd hypershiftv1beta1.EtcdSpec `json:"etcd"` // EvictionHard is service-set (platform-managed, hidden from API) EvictionHard map[string]string `json:"evictionHard"` // EvictionSoft is service-set (platform-managed, hidden from API) @@ -37,8 +70,14 @@ 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) @@ -49,24 +88,64 @@ 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"` }