diff --git a/Makefile b/Makefile index 7720cf67c..895e3076b 100644 --- a/Makefile +++ b/Makefile @@ -169,7 +169,7 @@ test: $(SETUP_ENVTEST) ## Run unit and integration tests KUBEBUILDER_ASSETS="$(KUBEBUILDER_ASSETS)" go test ./... $(TEST_ARGS) # Allow overriding the e2e configurations -GINKGO_FOCUS ?= Workload cluster creation +GINKGO_FOCUS ?= Workload cluster creation|GKE workload cluster creation GINKGO_SKIP ?= API Version Upgrade GINKGO_NODES ?= 1 GINKGO_NOCOLOR ?= false diff --git a/cloud/scope/managedmachinepool.go b/cloud/scope/managedmachinepool.go index e2a4577a4..1f4f5fc83 100644 --- a/cloud/scope/managedmachinepool.go +++ b/cloud/scope/managedmachinepool.go @@ -312,3 +312,15 @@ func (s *ManagedMachinePoolScope) NodePoolLocation() string { func (s *ManagedMachinePoolScope) NodePoolFullName() string { return fmt.Sprintf("%s/nodePools/%s", s.NodePoolLocation(), s.NodePoolName()) } + +// SetInfrastructureMachineKind sets the infrastructure machine kind in the status if it is not set already, returning +// `true` if the status was updated. This supports MachinePool Machines. +func (s *ManagedMachinePoolScope) SetInfrastructureMachineKind() bool { + if s.GCPManagedMachinePool.Status.InfrastructureMachineKind != infrav1exp.GCPManagedMachinePoolMachineKind { + s.GCPManagedMachinePool.Status.InfrastructureMachineKind = infrav1exp.GCPManagedMachinePoolMachineKind + + return true + } + + return false +} diff --git a/cloud/scope/managedmachinepool_test.go b/cloud/scope/managedmachinepool_test.go index b8fa01dd0..ba1c87cb2 100644 --- a/cloud/scope/managedmachinepool_test.go +++ b/cloud/scope/managedmachinepool_test.go @@ -31,7 +31,9 @@ var _ = Describe("GCPManagedMachinePool Scope", func() { Namespace: namespace, }, Spec: v1beta1.GCPManagedMachinePoolSpec{ - NodePoolName: nodePoolName, + GCPManagedMachinePoolClassSpec: v1beta1.GCPManagedMachinePoolClassSpec{ + NodePoolName: nodePoolName, + }, }, } TestMP = &clusterv1exp.MachinePool{ @@ -52,6 +54,32 @@ var _ = Describe("GCPManagedMachinePool Scope", func() { }) }) + Context("Test MachinePool InfrastructureMachineKind", func() { + It("should set infrastructure machine kind when empty", func() { + TestGCPMMP.Status = v1beta1.GCPManagedMachinePoolStatus{} + machinePoolScope := ManagedMachinePoolScope{ + GCPManagedMachinePool: TestGCPMMP, + } + + update := machinePoolScope.SetInfrastructureMachineKind() + Expect(machinePoolScope.GCPManagedMachinePool.Status.InfrastructureMachineKind).To(Equal(v1beta1.GCPManagedMachinePoolMachineKind)) + Expect(update).To(BeTrue()) + }) + + It("should not update infrastructure machine kind if already set", func() { + TestGCPMMP.Status = v1beta1.GCPManagedMachinePoolStatus{ + InfrastructureMachineKind: v1beta1.GCPManagedMachinePoolMachineKind, + } + machinePoolScope := ManagedMachinePoolScope{ + GCPManagedMachinePool: TestGCPMMP, + } + + update := machinePoolScope.SetInfrastructureMachineKind() + Expect(machinePoolScope.GCPManagedMachinePool.Status.InfrastructureMachineKind).To(Equal(v1beta1.GCPManagedMachinePoolMachineKind)) + Expect(update).To(BeFalse()) + }) + }) + Context("Test ConvertToSdkNodePool", func() { It("should convert to SDK node pool with default values", func() { sdkNodePool := ConvertToSdkNodePool(*TestGCPMMP, *TestMP, false, TestClusterName) diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmanagedclustertemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmanagedclustertemplates.yaml new file mode 100644 index 000000000..b90f8fc2f --- /dev/null +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmanagedclustertemplates.yaml @@ -0,0 +1,357 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: gcpmanagedclustertemplates.infrastructure.cluster.x-k8s.io +spec: + group: infrastructure.cluster.x-k8s.io + names: + categories: + - cluster-api + kind: GCPManagedClusterTemplate + listKind: GCPManagedClusterTemplateList + plural: gcpmanagedclustertemplates + shortNames: + - amct + singular: gcpmanagedclustertemplate + scope: Namespaced + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + description: GCPManagedClusterTemplate is the Schema for the GCPManagedClusterTemplates + API. + 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: GCPManagedClusterTemplateSpec defines the desired state of + GCPManagedClusterTemplate. + properties: + template: + description: GCPManagedClusterTemplateResource describes the data + needed to create an GCPManagedCluster from a template. + properties: + spec: + description: GCPManagedClusterTemplateResourceSpec specifies an + GCP managed cluster template resource. + properties: + additionalLabels: + additionalProperties: + type: string + description: |- + AdditionalLabels is an optional set of tags to add to GCP resources managed by the GCP provider, in addition to the + ones added by default. + type: object + controlPlaneEndpoint: + description: ControlPlaneEndpoint represents the endpoint + used to communicate with the control plane. + properties: + host: + description: The hostname on which the API server is serving. + type: string + port: + description: The port on which the API server is serving. + format: int32 + type: integer + required: + - host + - port + type: object + credentialsRef: + description: |- + CredentialsRef is a reference to a Secret that contains the credentials to use for provisioning this cluster. If not + supplied then the credentials of the controller will be used. + properties: + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + namespace: + description: |- + Namespace of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + type: string + required: + - name + - namespace + type: object + loadBalancer: + description: LoadBalancerSpec contains configuration for one + or more LoadBalancers. + properties: + apiServerInstanceGroupTagOverride: + description: |- + APIServerInstanceGroupTagOverride overrides the default setting for the + tag used when creating the API Server Instance Group. + maxLength: 16 + pattern: (^[1-9][0-9]{0,31}$)|(^[a-z][a-z0-9-]{4,28}[a-z0-9]$) + type: string + internalLoadBalancer: + description: InternalLoadBalancer is the configuration + for an Internal Passthrough Network Load Balancer. + properties: + name: + description: |- + Name is the name of the Load Balancer. If not set a default name + will be used. For an Internal Load Balancer service the default + name is "api-internal". + pattern: (^[1-9][0-9]{0,31}$)|(^[a-z][a-z0-9-]{4,28}[a-z0-9]$) + type: string + subnet: + description: |- + Subnet is the name of the subnet to use for a regional Load Balancer. A subnet is + required for the Load Balancer, if not defined the first configured subnet will be + used. + type: string + type: object + loadBalancerType: + description: |- + LoadBalancerType defines the type of Load Balancer that should be created. + If not set, a Global External Proxy Load Balancer will be created by default. + type: string + type: object + network: + description: NetworkSpec encapsulates all things related to + the GCP network. + properties: + autoCreateSubnetworks: + description: |- + AutoCreateSubnetworks: When set to true, the VPC network is created + in "auto" mode. When set to false, the VPC network is created in + "custom" mode. + + An auto mode VPC network starts with one subnet per region. Each + subnet has a predetermined range as described in Auto mode VPC + network IP ranges. + + Defaults to true. + type: boolean + hostProject: + description: HostProject is the name of the project hosting + the shared VPC network resources. + type: string + loadBalancerBackendPort: + description: Allow for configuration of load balancer + backend (useful for changing apiserver port) + format: int32 + type: integer + mtu: + default: 1460 + description: |- + Mtu: Maximum Transmission Unit in bytes. The minimum value for this field is + 1300 and the maximum value is 8896. The suggested value is 1500, which is + the default MTU used on the Internet, or 8896 if you want to use Jumbo + frames. If unspecified, the value defaults to 1460. + More info: https://pkg.go.dev/google.golang.org/api/compute/v1#Network + format: int64 + maximum: 8896 + minimum: 1300 + type: integer + name: + description: Name is the name of the network to be used. + type: string + subnets: + description: Subnets configuration. + items: + description: SubnetSpec configures an GCP Subnet. + properties: + cidrBlock: + description: |- + CidrBlock is the range of internal addresses that are owned by this + subnetwork. Provide this property when you create the subnetwork. For + example, 10.0.0.0/8 or 192.168.0.0/16. Ranges must be unique and + non-overlapping within a network. Only IPv4 is supported. This field + can be set only at resource creation time. + type: string + description: + description: Description is an optional description + associated with the resource. + type: string + enableFlowLogs: + description: |- + EnableFlowLogs: Whether to enable flow logging for this subnetwork. + If this field is not explicitly set, it will not appear in get + listings. If not set the default behavior is to disable flow logging. + type: boolean + name: + description: Name defines a unique identifier to + reference this resource. + type: string + privateGoogleAccess: + description: |- + PrivateGoogleAccess defines whether VMs in this subnet can access + Google services without assigning external IP addresses + type: boolean + purpose: + default: PRIVATE_RFC_1918 + description: |- + Purpose: The purpose of the resource. + If unspecified, the purpose defaults to PRIVATE_RFC_1918. + The enableFlowLogs field isn't supported with the purpose field set to INTERNAL_HTTPS_LOAD_BALANCER. + + Possible values: + "INTERNAL_HTTPS_LOAD_BALANCER" - Subnet reserved for Internal + HTTP(S) Load Balancing. + "PRIVATE" - Regular user created or automatically created subnet. + "PRIVATE_RFC_1918" - Regular user created or automatically created + subnet. + "PRIVATE_SERVICE_CONNECT" - Subnetworks created for Private Service + Connect in the producer network. + "REGIONAL_MANAGED_PROXY" - Subnetwork used for Regional + Internal/External HTTP(S) Load Balancing. + enum: + - INTERNAL_HTTPS_LOAD_BALANCER + - PRIVATE_RFC_1918 + - PRIVATE + - PRIVATE_SERVICE_CONNECT + - REGIONAL_MANAGED_PROXY + type: string + region: + description: Region is the name of the region where + the Subnetwork resides. + type: string + secondaryCidrBlocks: + additionalProperties: + type: string + description: |- + SecondaryCidrBlocks defines secondary CIDR ranges, + from which secondary IP ranges of a VM may be allocated + type: object + stackType: + default: IPV4_ONLY + description: |- + StackType: The stack type for the subnet. If set to IPV4_ONLY, new VMs in + the subnet are assigned IPv4 addresses only. If set to IPV4_IPV6, new VMs in + the subnet can be assigned both IPv4 and IPv6 addresses. If not specified, + IPV4_ONLY is used. This field can be both set at resource creation time and + updated using patch. + + Possible values: + "IPV4_IPV6" - New VMs in this subnet can have both IPv4 and IPv6 + addresses. + "IPV4_ONLY" - New VMs in this subnet will only be assigned IPv4 addresses. + "IPV6_ONLY" - New VMs in this subnet will only be assigned IPv6 addresses. + enum: + - IPV4_ONLY + - IPV4_IPV6 + - IPV6_ONLY + type: string + type: object + type: array + type: object + project: + description: Project is the name of the project to deploy + the cluster to. + type: string + region: + description: The GCP Region the cluster lives in. + type: string + resourceManagerTags: + description: |- + ResourceManagerTags is an optional set of tags to apply to GCP resources managed + by the GCP provider. GCP supports a maximum of 50 tags per resource. + items: + description: ResourceManagerTag is a tag to apply to GCP + resources managed by the GCP provider. + properties: + key: + description: |- + Key is the key part of the tag. A tag key can have a maximum of 63 characters and cannot + be empty. Tag key must begin and end with an alphanumeric character, and must contain + only uppercase, lowercase alphanumeric characters, and the following special + characters `._-`. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z0-9]([0-9A-Za-z_.-]{0,61}[a-zA-Z0-9])?$ + type: string + parentID: + description: |- + ParentID is the ID of the hierarchical resource where the tags are defined + e.g. at the Organization or the Project level. To find the Organization or Project ID ref + https://cloud.google.com/resource-manager/docs/creating-managing-organization#retrieving_your_organization_id + https://cloud.google.com/resource-manager/docs/creating-managing-projects#identifying_projects + An OrganizationID must consist of decimal numbers, and cannot have leading zeroes. + A ProjectID must be 6 to 30 characters in length, can only contain lowercase letters, + numbers, and hyphens, and must start with a letter, and cannot end with a hyphen. + maxLength: 32 + minLength: 1 + pattern: (^[1-9][0-9]{0,31}$)|(^[a-z][a-z0-9-]{4,28}[a-z0-9]$) + type: string + value: + description: |- + Value is the value part of the tag. A tag value can have a maximum of 63 characters and + cannot be empty. Tag value must begin and end with an alphanumeric character, and must + contain only uppercase, lowercase alphanumeric characters, and the following special + characters `_-.@%=+:,*#&(){}[]` and spaces. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z0-9]([0-9A-Za-z_.@%=+:,*#&()\[\]{}\-\s]{0,61}[a-zA-Z0-9])?$ + type: string + required: + - key + - parentID + - value + type: object + type: array + serviceEndpoints: + description: |- + ServiceEndpoints contains the custom GCP Service Endpoint urls for each applicable service. + For instance, the user can specify a new endpoint for the compute service. + properties: + compute: + description: ComputeServiceEndpoint is the custom endpoint + url for the Compute Service + format: uri + pattern: ^https:// + type: string + container: + description: ContainerServiceEndpoint is the custom endpoint + url for the Container Service + format: uri + pattern: ^https:// + type: string + iam: + description: IAMServiceEndpoint is the custom endpoint + url for the IAM Service + format: uri + pattern: ^https:// + type: string + resourceManager: + description: ResourceManagerServiceEndpoint is the custom + endpoint url for the Resource Manager Service + format: uri + pattern: ^https:// + type: string + type: object + required: + - project + - region + type: object + required: + - spec + type: object + required: + - template + type: object + type: object + served: true + storage: true diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmanagedcontrolplanes.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmanagedcontrolplanes.yaml index 2eb9c294d..9e2ace104 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmanagedcontrolplanes.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmanagedcontrolplanes.yaml @@ -177,6 +177,13 @@ spec: Possible values: none, logging.googleapis.com/kubernetes (default). Value is ignored when enableAutopilot = true. type: string + machineTemplate: + description: |- + MachineTemplate contains information about how machines + should be shaped when creating or updating a control plane. + For the GCPManagedControlPlaneTemplate, this field is used + only to fulfill the CAPI contract. + type: object master_authorized_networks_config: description: |- MasterAuthorizedNetworksConfig represents configuration options for master authorized networks feature of the GKE cluster. diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmanagedcontrolplanetemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmanagedcontrolplanetemplates.yaml new file mode 100644 index 000000000..4c8d6b011 --- /dev/null +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmanagedcontrolplanetemplates.yaml @@ -0,0 +1,203 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: gcpmanagedcontrolplanetemplates.infrastructure.cluster.x-k8s.io +spec: + group: infrastructure.cluster.x-k8s.io + names: + categories: + - cluster-api + kind: GCPManagedControlPlaneTemplate + listKind: GCPManagedControlPlaneTemplateList + plural: gcpmanagedcontrolplanetemplates + shortNames: + - amcpt + singular: gcpmanagedcontrolplanetemplate + scope: Namespaced + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + description: GCPManagedControlPlaneTemplate is the Schema for the GCPManagedControlPlaneTemplates + API. + 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: GCPManagedControlPlaneTemplateSpec defines the desired state + of GCPManagedControlPlaneTemplate. + properties: + template: + description: GCPManagedControlPlaneTemplateResource describes the + data needed to create an GCPManagedCluster from a template. + properties: + spec: + description: GCPManagedControlPlaneTemplateResourceSpec specifies + an GCP managed control plane template resource. + properties: + clusterNetwork: + description: ClusterNetwork define the cluster network. + properties: + pod: + description: Pod defines the range of CIDRBlock list from + where it gets the IP address. + properties: + cidrBlock: + description: |- + CidrBlock is where all pods in the cluster are assigned an IP address from this range. Enter a range + (in CIDR notation) within a network range, a mask, or leave this field blank to use a default range. + This setting is permanent. + type: string + type: object + privateCluster: + description: PrivateCluster defines the private cluster + spec. + properties: + controlPlaneCidrBlock: + description: |- + ControlPlaneCidrBlock is the IP range in CIDR notation to use for the hosted master network. This range must not + overlap with any other ranges in use within the cluster's network. Honored when enabled is true. + type: string + controlPlaneGlobalAccess: + description: ControlPlaneGlobalAccess is whenever + master is accessible globally or not. Honored when + enabled is true. + type: boolean + disableDefaultSNAT: + description: DisableDefaultSNAT disables cluster default + sNAT rules. Honored when enabled is true. + type: boolean + enablePrivateEndpoint: + description: |- + EnablePrivateEndpoint: Whether the master's internal IP + address is used as the cluster endpoint. + type: boolean + enablePrivateNodes: + description: |- + EnablePrivateNodes: Whether nodes have internal IP + addresses only. If enabled, all nodes are given only RFC + 1918 private addresses and communicate with the master via + private networking. + type: boolean + type: object + service: + description: Service defines the range of CIDRBlock list + from where it gets the IP address. + properties: + cidrBlock: + description: |- + CidrBlock is where cluster services will be assigned an IP address from this IP address range. Enter a range + (in CIDR notation) within a network range, a mask, or leave this field blank to use a default range. + This setting is permanent. + type: string + type: object + useIPAliases: + description: |- + UseIPAliases is whether alias IPs will be used for pod IPs in the cluster. If false, routes will be used for + pod IPs in the cluster. + type: boolean + type: object + enableAutopilot: + description: EnableAutopilot indicates whether to enable autopilot + for this GKE cluster. + type: boolean + enableIdentityService: + description: EnableIdentityService indicates whether to enable + Identity Service component for this GKE cluster. + type: boolean + location: + description: |- + Location represents the location (region or zone) in which the GKE cluster + will be created. + type: string + loggingService: + description: |- + LoggingService represents configuration of logging service feature of the GKE cluster. + Possible values: none, logging.googleapis.com/kubernetes (default). + Value is ignored when enableAutopilot = true. + type: string + machineTemplate: + description: |- + MachineTemplate contains information about how machines + should be shaped when creating or updating a control plane. + For the GCPManagedControlPlaneTemplate, this field is used + only to fulfill the CAPI contract. + type: object + master_authorized_networks_config: + description: |- + MasterAuthorizedNetworksConfig represents configuration options for master authorized networks feature of the GKE cluster. + This feature is disabled if this field is not specified. + properties: + cidr_blocks: + description: |- + cidr_blocks define up to 50 external networks that could access + Kubernetes master through HTTPS. + items: + description: MasterAuthorizedNetworksConfigCidrBlock + contains an optional name and one CIDR block. + properties: + cidr_block: + description: cidr_block must be specified in CIDR + notation. + pattern: ^(?:[0-9]{1,3}\.){3}[0-9]{1,3}(?:\/([0-9]|[1-2][0-9]|3[0-2]))?$|^([a-fA-F0-9:]+:+)+[a-fA-F0-9]+\/[0-9]{1,3}$ + type: string + display_name: + description: display_name is an field for users + to identify CIDR blocks. + type: string + type: object + type: array + gcp_public_cidrs_access_enabled: + description: Whether master is accessible via Google Compute + Engine Public IP addresses. + type: boolean + type: object + monitoringService: + description: |- + MonitoringService represents configuration of monitoring service feature of the GKE cluster. + Possible values: none, monitoring.googleapis.com/kubernetes (default). + Value is ignored when enableAutopilot = true. + type: string + project: + description: Project is the name of the project to deploy + the cluster to. + type: string + releaseChannel: + description: ReleaseChannel represents the release channel + of the GKE cluster. + enum: + - rapid + - regular + - stable + type: string + required: + - location + - project + type: object + required: + - spec + type: object + required: + - template + type: object + type: object + served: true + storage: true diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmanagedmachinepools.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmanagedmachinepools.yaml index 1f6acafba..66079abce 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmanagedmachinepools.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmanagedmachinepools.yaml @@ -335,6 +335,10 @@ spec: - type type: object type: array + infrastructureMachineKind: + description: InfrastructureMachineKind is the kind of the infrastructure + resources behind MachinePool Machines. + type: string ready: default: false description: Ready denotes that the GCPManagedMachinePool has joined diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmanagedmachinepooltemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmanagedmachinepooltemplates.yaml new file mode 100644 index 000000000..8a4333a06 --- /dev/null +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_gcpmanagedmachinepooltemplates.yaml @@ -0,0 +1,294 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: gcpmanagedmachinepooltemplates.infrastructure.cluster.x-k8s.io +spec: + group: infrastructure.cluster.x-k8s.io + names: + categories: + - cluster-api + kind: GCPManagedMachinePoolTemplate + listKind: GCPManagedMachinePoolTemplateList + plural: gcpmanagedmachinepooltemplates + shortNames: + - ammpt + singular: gcpmanagedmachinepooltemplate + scope: Namespaced + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + description: GCPManagedMachinePoolTemplate is the Schema for the GCPManagedMachinePoolTemplates + API. + 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: GCPManagedMachinePoolTemplateSpec defines the desired state + of GCPManagedMachinePoolTemplate. + properties: + template: + description: GCPManagedMachinePoolTemplateResource describes the data + needed to create an GCPManagedCluster from a template. + properties: + spec: + description: GCPManagedMachinePoolTemplateResourceSpec specifies + an GCP managed control plane template resource. + properties: + additionalLabels: + additionalProperties: + type: string + description: |- + AdditionalLabels is an optional set of tags to add to GCP resources managed by the GCP provider, in addition to the + ones added by default. + type: object + diskSizeGB: + description: |- + DiskSizeGB is size of the disk attached to each node, + specified in GB. + format: int64 + minimum: 10 + type: integer + diskSizeGb: + description: |- + DiskSizeGb is the size of the disk attached to each node, specified in GB. + The smallest allowed disk size is 10GB. If unspecified, the default disk size is 100GB. + format: int32 + type: integer + diskType: + description: DiskType is type of the disk attached to each + node. + enum: + - pd-standard + - pd-ssd + - pd-balanced + type: string + imageType: + description: ImageType is image type to use for this nodepool. + type: string + instanceType: + description: InstanceType is name of Compute Engine machine + type. + type: string + kubernetesLabels: + additionalProperties: + type: string + description: KubernetesLabels specifies the labels to apply + to the nodes of the node pool. + type: object + kubernetesTaints: + description: KubernetesTaints specifies the taints to apply + to the nodes of the node pool. + items: + description: Taint represents a Kubernetes taint. + properties: + effect: + description: Effect specifies the effect for the taint. + enum: + - NoSchedule + - NoExecute + - PreferNoSchedule + type: string + key: + description: Key is the key of the taint + type: string + value: + description: Value is the value of the taint + type: string + required: + - effect + - key + - value + type: object + type: array + linuxNodeConfig: + description: LinuxNodeConfig specifies the settings for Linux + agent nodes. + properties: + cgroupMode: + description: CgroupMode specifies the cgroup mode for + this node pool. + format: int32 + type: integer + sysctls: + description: Sysctls specifies the sysctl settings for + this node pool. + items: + description: SysctlConfig specifies the sysctl settings + for Linux nodes. + properties: + parameter: + description: Parameter specifies sysctl parameter + name. + type: string + value: + description: Value specifies sysctl parameter value. + type: string + type: object + type: array + type: object + localSsdCount: + description: LocalSsdCount is the number of local SSD disks + to be attached to the node. + format: int32 + type: integer + machineType: + description: |- + MachineType is the name of a Google Compute Engine [machine + type](https://cloud.google.com/compute/docs/machine-types). + If unspecified, the default machine type is `e2-medium`. + type: string + management: + description: Management specifies the node pool management + options. + properties: + autoRepair: + description: |- + AutoRepair specifies whether the node auto-repair is enabled for the node + pool. If enabled, the nodes in this node pool will be monitored and, if + they fail health checks too many times, an automatic repair action will be + triggered. + type: boolean + autoUpgrade: + description: |- + AutoUpgrade specifies whether node auto-upgrade is enabled for the node + pool. If enabled, node auto-upgrade helps keep the nodes in your node pool + up to date with the latest release version of Kubernetes. + type: boolean + type: object + maxPodsPerNode: + description: |- + MaxPodsPerNode is constraint enforced on the max num of + pods per node. + format: int64 + maximum: 256 + minimum: 8 + type: integer + nodeLocations: + description: |- + NodeLocations is the list of zones in which the NodePool's + nodes should be located. + items: + type: string + type: array + nodeNetwork: + description: |- + NodeNetwork specifies the node network configuration + options. + properties: + createPodRange: + description: |- + CreatePodRange specifies whether to create a new range for + pod IPs in this node pool. + type: boolean + podRangeCidrBlock: + description: |- + PodRangeCidrBlock is the IP address range for pod IPs in + this node pool. + type: string + podRangeName: + description: PodRangeName is ID of the secondary range + for pod IPs. + type: string + tags: + description: |- + Tags is list of instance tags applied to all nodes. Tags + are used to identify valid sources or targets for network + firewalls. + items: + type: string + type: array + type: object + nodePoolName: + description: |- + NodePoolName specifies the name of the GKE node pool corresponding to this MachinePool. If you don't specify a name + then a default name will be created based on the namespace and name of the managed machine pool. + type: string + nodeSecurity: + description: NodeSecurity specifies the node security options. + properties: + enableIntegrityMonitoring: + description: |- + EnableIntegrityMonitoring defines whether the instance has + integrity monitoring enabled. + type: boolean + enableSecureBoot: + description: |- + EnableSecureBoot defines whether the instance has Secure + Boot enabled. + type: boolean + sandboxType: + description: SandboxType is type of the sandbox to use + for the node. + type: string + serviceAccount: + description: |- + ServiceAccount specifies the identity details for node + pool. + properties: + email: + description: |- + Email is the Google Cloud Platform Service Account to be + used by the node VMs. + type: string + scopes: + description: |- + Scopes is a set of Google API scopes to be made available + on all of the node VMs under the "default" service account. + items: + type: string + type: array + type: object + type: object + scaling: + description: Scaling specifies scaling for the node pool + properties: + enableAutoscaling: + description: Is autoscaling enabled for this node pool. + If unspecified, the default value is true. + type: boolean + locationPolicy: + description: Location policy used when scaling up a nodepool. + enum: + - balanced + - any + type: string + maxCount: + description: MaxCount specifies the maximum number of + nodes in the node pool + format: int32 + type: integer + minCount: + description: MinCount specifies the minimum number of + nodes in the node pool + format: int32 + type: integer + type: object + type: object + required: + - spec + type: object + required: + - template + type: object + type: object + served: true + storage: true diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 874c9179e..69b655353 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -12,6 +12,9 @@ resources: - bases/infrastructure.cluster.x-k8s.io_gcpmanagedclusters.yaml - bases/infrastructure.cluster.x-k8s.io_gcpmanagedcontrolplanes.yaml - bases/infrastructure.cluster.x-k8s.io_gcpmanagedmachinepools.yaml +- bases/infrastructure.cluster.x-k8s.io_gcpmanagedclustertemplates.yaml +- bases/infrastructure.cluster.x-k8s.io_gcpmanagedcontrolplanetemplates.yaml +- bases/infrastructure.cluster.x-k8s.io_gcpmanagedmachinepooltemplates.yaml # +kubebuilder:scaffold:crdkustomizeresource diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml index 0956ab370..e67016656 100644 --- a/config/webhook/manifests.yaml +++ b/config/webhook/manifests.yaml @@ -148,6 +148,26 @@ webhooks: resources: - gcpmanagedmachinepools sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /mutate-infrastructure-cluster-x-k8s-io-v1beta1-gcpmanagedmachinepooltemplate + failurePolicy: Fail + name: mgcpmanagedmachinepooltemplate.kb.io + rules: + - apiGroups: + - infrastructure.cluster.x-k8s.io + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - gcpmanagedmachinepooltemplates + sideEffects: None --- apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration @@ -258,6 +278,26 @@ webhooks: resources: - gcpmanagedclusters sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /validate-infrastructure-cluster-x-k8s-io-v1beta1-gcpmanagedclustertemplate + failurePolicy: Fail + name: vgcpmanagedclustertemplate.kb.io + rules: + - apiGroups: + - infrastructure.cluster.x-k8s.io + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - gcpmanagedclustertemplates + sideEffects: None - admissionReviewVersions: - v1 clientConfig: @@ -278,6 +318,26 @@ webhooks: resources: - gcpmanagedcontrolplanes sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /validate-infrastructure-cluster-x-k8s-io-v1beta1-gcpmanagedcontrolplanetemplate + failurePolicy: Fail + name: vgcpmanagedcontrolplanetemplate.kb.io + rules: + - apiGroups: + - infrastructure.cluster.x-k8s.io + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - gcpmanagedcontrolplanetemplates + sideEffects: None - admissionReviewVersions: - v1 clientConfig: diff --git a/exp/api/v1beta1/gcpmanagedcluster_conversion.go b/exp/api/v1beta1/gcpmanagedcluster_conversion.go new file mode 100644 index 000000000..6e93e0bca --- /dev/null +++ b/exp/api/v1beta1/gcpmanagedcluster_conversion.go @@ -0,0 +1,23 @@ +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +// Hub marks GCPManagedCluster as a conversion hub. +func (*GCPManagedCluster) Hub() {} + +// Hub marks GCPManagedClusterList as a conversion hub. +func (*GCPManagedClusterList) Hub() {} diff --git a/exp/api/v1beta1/gcpmanagedclustertemplate_types.go b/exp/api/v1beta1/gcpmanagedclustertemplate_types.go new file mode 100644 index 000000000..274a2eea2 --- /dev/null +++ b/exp/api/v1beta1/gcpmanagedclustertemplate_types.go @@ -0,0 +1,56 @@ +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// GCPManagedClusterTemplateSpec defines the desired state of GCPManagedClusterTemplate. +type GCPManagedClusterTemplateSpec struct { + Template GCPManagedClusterTemplateResource `json:"template"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:resource:path=gcpmanagedclustertemplates,scope=Namespaced,categories=cluster-api,shortName=amct +// +kubebuilder:storageversion + +// GCPManagedClusterTemplate is the Schema for the GCPManagedClusterTemplates API. +type GCPManagedClusterTemplate struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec GCPManagedClusterTemplateSpec `json:"spec,omitempty"` +} + +// +kubebuilder:object:root=true + +// GCPManagedClusterTemplateList contains a list of GCPManagedClusterTemplates. +type GCPManagedClusterTemplateList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []GCPManagedClusterTemplate `json:"items"` +} + +func init() { + SchemeBuilder.Register(&GCPManagedClusterTemplate{}, &GCPManagedClusterTemplateList{}) +} + +// GCPManagedClusterTemplateResource describes the data needed to create an GCPManagedCluster from a template. +type GCPManagedClusterTemplateResource struct { + Spec GCPManagedClusterTemplateResourceSpec `json:"spec"` +} diff --git a/exp/api/v1beta1/gcpmanagedclustertemplate_webhook.go b/exp/api/v1beta1/gcpmanagedclustertemplate_webhook.go new file mode 100644 index 000000000..c72f31739 --- /dev/null +++ b/exp/api/v1beta1/gcpmanagedclustertemplate_webhook.go @@ -0,0 +1,73 @@ +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "fmt" + "reflect" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +// log is for logging in this package. +var gmctlog = logf.Log.WithName("gcpclustertemplate-resource") + +// SetupWebhookWithManager sets up and registers the webhook with the manager. +func (r *GCPManagedClusterTemplate) SetupWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr). + For(r). + Complete() +} + +//+kubebuilder:webhook:path=/validate-infrastructure-cluster-x-k8s-io-v1beta1-gcpmanagedclustertemplate,mutating=false,failurePolicy=fail,sideEffects=None,groups=infrastructure.cluster.x-k8s.io,resources=gcpmanagedclustertemplates,verbs=create;update,versions=v1beta1,name=vgcpmanagedclustertemplate.kb.io,admissionReviewVersions=v1 + +var _ webhook.Validator = &GCPManagedClusterTemplate{} + +// ValidateCreate implements webhook.Validator so a webhook will be registered for the type. +func (r *GCPManagedClusterTemplate) ValidateCreate() (admission.Warnings, error) { + gmctlog.Info("validate create", "name", r.Name) + + return nil, nil +} + +// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type. +func (r *GCPManagedClusterTemplate) ValidateUpdate(oldRaw runtime.Object) (admission.Warnings, error) { + gmctlog.Info("validate update", "name", r.Name) + + old, ok := oldRaw.(*GCPManagedClusterTemplate) + if !ok { + return nil, apierrors.NewBadRequest(fmt.Sprintf("expected an GCPManagedClusterTemplate but got a %T", oldRaw)) + } + + if !reflect.DeepEqual(r.Spec, old.Spec) { + return nil, apierrors.NewBadRequest("GCPManagedClusterTemplate.Spec is immutable") + } + + return nil, nil +} + +// ValidateDelete implements webhook.Validator so a webhook will be registered for the type. +func (r *GCPManagedClusterTemplate) ValidateDelete() (admission.Warnings, error) { + gmctlog.Info("validate delete", "name", r.Name) + + return nil, nil +} diff --git a/exp/api/v1beta1/gcpmanagedcontrolplane_conversion.go b/exp/api/v1beta1/gcpmanagedcontrolplane_conversion.go new file mode 100644 index 000000000..a5a52238e --- /dev/null +++ b/exp/api/v1beta1/gcpmanagedcontrolplane_conversion.go @@ -0,0 +1,23 @@ +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +// Hub marks GCPManagedCluster as a conversion hub. +func (*GCPManagedControlPlane) Hub() {} + +// Hub marks GCPManagedClusterList as a conversion hub. +func (*GCPManagedControlPlaneList) Hub() {} diff --git a/exp/api/v1beta1/gcpmanagedcontrolplane_types.go b/exp/api/v1beta1/gcpmanagedcontrolplane_types.go index 53ecffcdd..6022836e0 100644 --- a/exp/api/v1beta1/gcpmanagedcontrolplane_types.go +++ b/exp/api/v1beta1/gcpmanagedcontrolplane_types.go @@ -115,6 +115,8 @@ type AuthenticatorGroupConfig struct { // GCPManagedControlPlaneSpec defines the desired state of GCPManagedControlPlane. type GCPManagedControlPlaneSpec struct { + GCPManagedControlPlaneClassSpec `json:",inline"` + // ClusterName allows you to specify the name of the GKE cluster. // If you don't specify a name then a default name will be created // based on the namespace and name of the managed control plane. @@ -125,24 +127,6 @@ type GCPManagedControlPlaneSpec struct { // +optional Description string `json:"description,omitempty"` - // ClusterNetwork define the cluster network. - // +optional - ClusterNetwork *ClusterNetwork `json:"clusterNetwork,omitempty"` - - // Project is the name of the project to deploy the cluster to. - Project string `json:"project"` - // Location represents the location (region or zone) in which the GKE cluster - // will be created. - Location string `json:"location"` - // EnableAutopilot indicates whether to enable autopilot for this GKE cluster. - // +optional - EnableAutopilot bool `json:"enableAutopilot"` - // EnableIdentityService indicates whether to enable Identity Service component for this GKE cluster. - // +optional - EnableIdentityService bool `json:"enableIdentityService"` - // ReleaseChannel represents the release channel of the GKE cluster. - // +optional - ReleaseChannel *ReleaseChannel `json:"releaseChannel,omitempty"` // ControlPlaneVersion represents the control plane version of the GKE cluster. // If not specified, the default version currently supported by GKE will be // used. @@ -151,28 +135,16 @@ type GCPManagedControlPlaneSpec struct { // // +optional ControlPlaneVersion *string `json:"controlPlaneVersion,omitempty"` + // Version represents the control plane version of the GKE cluster. // If not specified, the default version currently supported by GKE will be // used. // +optional Version *string `json:"version,omitempty"` + // Endpoint represents the endpoint used to communicate with the control plane. // +optional Endpoint clusterv1.APIEndpoint `json:"endpoint"` - // MasterAuthorizedNetworksConfig represents configuration options for master authorized networks feature of the GKE cluster. - // This feature is disabled if this field is not specified. - // +optional - MasterAuthorizedNetworksConfig *MasterAuthorizedNetworksConfig `json:"master_authorized_networks_config,omitempty"` - // LoggingService represents configuration of logging service feature of the GKE cluster. - // Possible values: none, logging.googleapis.com/kubernetes (default). - // Value is ignored when enableAutopilot = true. - // +optional - LoggingService *LoggingService `json:"loggingService,omitempty"` - // MonitoringService represents configuration of monitoring service feature of the GKE cluster. - // Possible values: none, monitoring.googleapis.com/kubernetes (default). - // Value is ignored when enableAutopilot = true. - // +optional - MonitoringService *MonitoringService `json:"monitoringService,omitempty"` } // GCPManagedControlPlaneStatus defines the observed state of GCPManagedControlPlane. diff --git a/exp/api/v1beta1/gcpmanagedcontrolplane_webhook_test.go b/exp/api/v1beta1/gcpmanagedcontrolplane_webhook_test.go index 7a89871ce..c2caf0260 100644 --- a/exp/api/v1beta1/gcpmanagedcontrolplane_webhook_test.go +++ b/exp/api/v1beta1/gcpmanagedcontrolplane_webhook_test.go @@ -82,11 +82,19 @@ func TestGCPManagedControlPlaneDefaultingWebhook(t *testing.T) { resourceName: "cluster1", resourceNS: "default", spec: GCPManagedControlPlaneSpec{ - ClusterName: "cluster1_autopilot", - Version: &vV1_27_1, - EnableAutopilot: true, + ClusterName: "cluster1_autopilot", + Version: &vV1_27_1, + GCPManagedControlPlaneClassSpec: GCPManagedControlPlaneClassSpec{ + EnableAutopilot: true, + }, + }, + expectSpec: GCPManagedControlPlaneSpec{ + ClusterName: "cluster1_autopilot", + Version: &vV1_27_1, + GCPManagedControlPlaneClassSpec: GCPManagedControlPlaneClassSpec{ + EnableAutopilot: true, + }, }, - expectSpec: GCPManagedControlPlaneSpec{ClusterName: "cluster1_autopilot", Version: &vV1_27_1, EnableAutopilot: true}, }, } @@ -136,9 +144,11 @@ func TestGCPManagedControlPlaneValidatingWebhookCreate(t *testing.T) { expectError: true, expectWarn: false, spec: GCPManagedControlPlaneSpec{ - ClusterName: "", - EnableAutopilot: true, - ReleaseChannel: nil, + ClusterName: "", + GCPManagedControlPlaneClassSpec: GCPManagedControlPlaneClassSpec{ + EnableAutopilot: true, + ReleaseChannel: nil, + }, }, }, { @@ -146,9 +156,11 @@ func TestGCPManagedControlPlaneValidatingWebhookCreate(t *testing.T) { expectError: false, expectWarn: false, spec: GCPManagedControlPlaneSpec{ - ClusterName: "", - EnableAutopilot: true, - ReleaseChannel: &releaseChannel, + ClusterName: "", + GCPManagedControlPlaneClassSpec: GCPManagedControlPlaneClassSpec{ + EnableAutopilot: true, + ReleaseChannel: &releaseChannel, + }, }, }, { @@ -213,7 +225,9 @@ func TestGCPManagedControlPlaneValidatingWebhookUpdate(t *testing.T) { expectError: true, spec: GCPManagedControlPlaneSpec{ ClusterName: "default_cluster1", - Project: "new-project", + GCPManagedControlPlaneClassSpec: GCPManagedControlPlaneClassSpec{ + Project: "new-project", + }, }, }, { @@ -221,15 +235,19 @@ func TestGCPManagedControlPlaneValidatingWebhookUpdate(t *testing.T) { expectError: true, spec: GCPManagedControlPlaneSpec{ ClusterName: "default_cluster1", - Location: "us-west4", + GCPManagedControlPlaneClassSpec: GCPManagedControlPlaneClassSpec{ + Location: "us-west4", + }, }, }, { name: "request to enable/disable autopilot should cause an error", expectError: true, spec: GCPManagedControlPlaneSpec{ - ClusterName: "default_cluster1", - EnableAutopilot: true, + ClusterName: "default_cluster1", + GCPManagedControlPlaneClassSpec: GCPManagedControlPlaneClassSpec{ + EnableAutopilot: true, + }, }, }, { @@ -237,9 +255,11 @@ func TestGCPManagedControlPlaneValidatingWebhookUpdate(t *testing.T) { expectError: false, spec: GCPManagedControlPlaneSpec{ ClusterName: "default_cluster1", - ClusterNetwork: &ClusterNetwork{ - PrivateCluster: &PrivateCluster{ - EnablePrivateEndpoint: false, + GCPManagedControlPlaneClassSpec: GCPManagedControlPlaneClassSpec{ + ClusterNetwork: &ClusterNetwork{ + PrivateCluster: &PrivateCluster{ + EnablePrivateEndpoint: false, + }, }, }, }, @@ -256,9 +276,11 @@ func TestGCPManagedControlPlaneValidatingWebhookUpdate(t *testing.T) { oldMCP := &GCPManagedControlPlane{ Spec: GCPManagedControlPlaneSpec{ ClusterName: "default_cluster1", - ClusterNetwork: &ClusterNetwork{ - PrivateCluster: &PrivateCluster{ - EnablePrivateEndpoint: true, + GCPManagedControlPlaneClassSpec: GCPManagedControlPlaneClassSpec{ + ClusterNetwork: &ClusterNetwork{ + PrivateCluster: &PrivateCluster{ + EnablePrivateEndpoint: true, + }, }, }, }, diff --git a/exp/api/v1beta1/gcpmanagedcontrolplanetemplate_types.go b/exp/api/v1beta1/gcpmanagedcontrolplanetemplate_types.go new file mode 100644 index 000000000..de2ea2872 --- /dev/null +++ b/exp/api/v1beta1/gcpmanagedcontrolplanetemplate_types.go @@ -0,0 +1,56 @@ +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// GCPManagedControlPlaneTemplateSpec defines the desired state of GCPManagedControlPlaneTemplate. +type GCPManagedControlPlaneTemplateSpec struct { + Template GCPManagedControlPlaneTemplateResource `json:"template"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:resource:path=gcpmanagedcontrolplanetemplates,scope=Namespaced,categories=cluster-api,shortName=amcpt +// +kubebuilder:storageversion + +// GCPManagedControlPlaneTemplate is the Schema for the GCPManagedControlPlaneTemplates API. +type GCPManagedControlPlaneTemplate struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec GCPManagedControlPlaneTemplateSpec `json:"spec,omitempty"` +} + +// +kubebuilder:object:root=true + +// GCPManagedControlPlaneTemplateList contains a list of GCPManagedControlPlaneTemplates. +type GCPManagedControlPlaneTemplateList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []GCPManagedControlPlaneTemplate `json:"items"` +} + +func init() { + SchemeBuilder.Register(&GCPManagedControlPlaneTemplate{}, &GCPManagedControlPlaneTemplateList{}) +} + +// GCPManagedControlPlaneTemplateResource describes the data needed to create an GCPManagedCluster from a template. +type GCPManagedControlPlaneTemplateResource struct { + Spec GCPManagedControlPlaneTemplateResourceSpec `json:"spec"` +} diff --git a/exp/api/v1beta1/gcpmanagedcontrolplanetemplate_webhook.go b/exp/api/v1beta1/gcpmanagedcontrolplanetemplate_webhook.go new file mode 100644 index 000000000..424529a1d --- /dev/null +++ b/exp/api/v1beta1/gcpmanagedcontrolplanetemplate_webhook.go @@ -0,0 +1,135 @@ +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + "github.com/google/go-cmp/cmp" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/validation/field" + ctrl "sigs.k8s.io/controller-runtime" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +// log is for logging in this package. +var gmcptlog = logf.Log.WithName("gcpmanagedcontrolplane-resource") + +func (r *GCPManagedControlPlaneTemplate) SetupWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr). + For(r). + Complete() +} + +//+kubebuilder:webhook:path=/validate-infrastructure-cluster-x-k8s-io-v1beta1-gcpmanagedcontrolplanetemplate,mutating=false,failurePolicy=fail,sideEffects=None,groups=infrastructure.cluster.x-k8s.io,resources=gcpmanagedcontrolplanetemplates,verbs=create;update,versions=v1beta1,name=vgcpmanagedcontrolplanetemplate.kb.io,admissionReviewVersions=v1 + +var _ webhook.Validator = &GCPManagedControlPlaneTemplate{} + +// ValidateCreate implements webhook.Validator so a webhook will be registered for the type. +func (r *GCPManagedControlPlaneTemplate) ValidateCreate() (admission.Warnings, error) { + gmcptlog.Info("validate create", "name", r.Name) + var allErrs field.ErrorList + var allWarns admission.Warnings + + if r.Spec.Template.Spec.EnableAutopilot && r.Spec.Template.Spec.ReleaseChannel == nil { + allErrs = append(allErrs, field.Required(field.NewPath("spec", "ReleaseChannel"), "Release channel is required for an autopilot enabled cluster")) + } + + if r.Spec.Template.Spec.EnableAutopilot && r.Spec.Template.Spec.LoggingService != nil { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "LoggingService"), + r.Spec.Template.Spec.LoggingService, "can't be set when autopilot is enabled")) + } + + if r.Spec.Template.Spec.EnableAutopilot && r.Spec.Template.Spec.MonitoringService != nil { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "MonitoringService"), + r.Spec.Template.Spec.LoggingService, "can't be set when autopilot is enabled")) + } + + if len(allErrs) == 0 { + return allWarns, nil + } + + return allWarns, apierrors.NewInvalid(GroupVersion.WithKind("GCPManagedControlPlaneTemplate").GroupKind(), r.Name, allErrs) +} + +// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type. +func (r *GCPManagedControlPlaneTemplate) ValidateUpdate(oldRaw runtime.Object) (admission.Warnings, error) { + gmcptlog.Info("validate update", "name", r.Name) + var allErrs field.ErrorList + old := oldRaw.(*GCPManagedControlPlaneTemplate) + + if !cmp.Equal(r.Spec.Template.Spec.Project, old.Spec.Template.Spec.Project) { + allErrs = append(allErrs, + field.Invalid(field.NewPath("spec", "Project"), + r.Spec.Template.Spec.Project, "field is immutable"), + ) + } + + if !cmp.Equal(r.Spec.Template.Spec.Location, old.Spec.Template.Spec.Location) { + allErrs = append(allErrs, + field.Invalid(field.NewPath("spec", "Location"), + r.Spec.Template.Spec.Location, "field is immutable"), + ) + } + + if !cmp.Equal(r.Spec.Template.Spec.EnableAutopilot, old.Spec.Template.Spec.EnableAutopilot) { + allErrs = append(allErrs, + field.Invalid(field.NewPath("spec", "EnableAutopilot"), + r.Spec.Template.Spec.EnableAutopilot, "field is immutable"), + ) + } + + if old.Spec.Template.Spec.EnableAutopilot && r.Spec.Template.Spec.LoggingService != nil { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "LoggingService"), + r.Spec.Template.Spec.LoggingService, "can't be set when autopilot is enabled")) + } + + if old.Spec.Template.Spec.EnableAutopilot && r.Spec.Template.Spec.MonitoringService != nil { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "MonitoringService"), + r.Spec.Template.Spec.LoggingService, "can't be set when autopilot is enabled")) + } + + if r.Spec.Template.Spec.LoggingService != nil { + err := r.Spec.Template.Spec.LoggingService.Validate() + if err != nil { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "LoggingService"), + r.Spec.Template.Spec.LoggingService, err.Error())) + } + } + + if r.Spec.Template.Spec.MonitoringService != nil { + err := r.Spec.Template.Spec.MonitoringService.Validate() + if err != nil { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "MonitoringService"), + r.Spec.Template.Spec.MonitoringService, err.Error())) + } + } + + if len(allErrs) == 0 { + return nil, nil + } + + return nil, apierrors.NewInvalid(GroupVersion.WithKind("GCPManagedControlPlaneTemplate").GroupKind(), r.Name, allErrs) +} + +// ValidateDelete implements webhook.Validator so a webhook will be registered for the type. +func (r *GCPManagedControlPlaneTemplate) ValidateDelete() (admission.Warnings, error) { + gmcptlog.Info("validate delete", "name", r.Name) + + return nil, nil +} diff --git a/exp/api/v1beta1/gcpmanagedcontrolplanetemplate_webhook_test.go b/exp/api/v1beta1/gcpmanagedcontrolplanetemplate_webhook_test.go new file mode 100644 index 000000000..b5520db3f --- /dev/null +++ b/exp/api/v1beta1/gcpmanagedcontrolplanetemplate_webhook_test.go @@ -0,0 +1,17 @@ +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 diff --git a/exp/api/v1beta1/gcpmanagedmachinepool_conversion.go b/exp/api/v1beta1/gcpmanagedmachinepool_conversion.go new file mode 100644 index 000000000..4fbc93f9d --- /dev/null +++ b/exp/api/v1beta1/gcpmanagedmachinepool_conversion.go @@ -0,0 +1,23 @@ +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +// Hub marks GCPManagedMachinePool as a conversion hub. +func (*GCPManagedMachinePool) Hub() {} + +// Hub marks GCPManagedMachinePoolList as a conversion hub. +func (*GCPManagedMachinePoolList) Hub() {} diff --git a/exp/api/v1beta1/gcpmanagedmachinepool_types.go b/exp/api/v1beta1/gcpmanagedmachinepool_types.go index 2420f5465..fd8eb83c3 100644 --- a/exp/api/v1beta1/gcpmanagedmachinepool_types.go +++ b/exp/api/v1beta1/gcpmanagedmachinepool_types.go @@ -18,7 +18,6 @@ package v1beta1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - infrav1 "sigs.k8s.io/cluster-api-provider-gcp/api/v1beta1" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" ) @@ -26,6 +25,9 @@ const ( // ManagedMachinePoolFinalizer allows Reconcile to clean up GCP resources associated with the GCPManagedMachinePool before // removing it from the apiserver. ManagedMachinePoolFinalizer = "gcpmanagedmachinepool.infrastructure.cluster.x-k8s.io" + + // GCPManagedMachinePoolMachineKind indicates the kind of an GCPManagedMachinePoolMachine. + GCPManagedMachinePoolMachineKind = "GCPManagedMachinePoolMachine" ) // DiskType is type of the disk attached to node. @@ -45,72 +47,8 @@ const ( // GCPManagedMachinePoolSpec defines the desired state of GCPManagedMachinePool. type GCPManagedMachinePoolSpec struct { - // NodePoolName specifies the name of the GKE node pool corresponding to this MachinePool. If you don't specify a name - // then a default name will be created based on the namespace and name of the managed machine pool. - // +optional - NodePoolName string `json:"nodePoolName,omitempty"` - // MachineType is the name of a Google Compute Engine [machine - // type](https://cloud.google.com/compute/docs/machine-types). - // If unspecified, the default machine type is `e2-medium`. - // +optional - MachineType *string `json:"machineType,omitempty"` - // DiskSizeGb is the size of the disk attached to each node, specified in GB. - // The smallest allowed disk size is 10GB. If unspecified, the default disk size is 100GB. - // +optional - DiskSizeGb *int32 `json:"diskSizeGb,omitempty"` - // LocalSsdCount is the number of local SSD disks to be attached to the node. - // +optional - LocalSsdCount *int32 `json:"localSsdCount,omitempty"` - // Scaling specifies scaling for the node pool - // +optional - Scaling *NodePoolAutoScaling `json:"scaling,omitempty"` - // NodeLocations is the list of zones in which the NodePool's - // nodes should be located. - // +optional - NodeLocations []string `json:"nodeLocations,omitempty"` - // ImageType is image type to use for this nodepool. - // +optional - ImageType *string `json:"imageType,omitempty"` - // InstanceType is name of Compute Engine machine type. - // +optional - InstanceType *string `json:"instanceType,omitempty"` - // DiskType is type of the disk attached to each node. - // +optional - DiskType *DiskType `json:"diskType,omitempty"` - // DiskSizeGB is size of the disk attached to each node, - // specified in GB. - // +kubebuilder:validation:Minimum:=10 - // +optional - DiskSizeGB *int64 `json:"diskSizeGB,omitempty"` - // MaxPodsPerNode is constraint enforced on the max num of - // pods per node. - // +kubebuilder:validation:Minimum:=8 - // +kubebuilder:validation:Maximum:=256 - // +optional - MaxPodsPerNode *int64 `json:"maxPodsPerNode,omitempty"` - // NodeNetwork specifies the node network configuration - // options. - // +optional - NodeNetwork NodeNetworkConfig `json:"nodeNetwork,omitempty"` - // NodeSecurity specifies the node security options. - // +optional - NodeSecurity NodeSecurityConfig `json:"nodeSecurity,omitempty"` - // KubernetesLabels specifies the labels to apply to the nodes of the node pool. - // +optional - KubernetesLabels infrav1.Labels `json:"kubernetesLabels,omitempty"` - // KubernetesTaints specifies the taints to apply to the nodes of the node pool. - // +optional - KubernetesTaints Taints `json:"kubernetesTaints,omitempty"` - // AdditionalLabels is an optional set of tags to add to GCP resources managed by the GCP provider, in addition to the - // ones added by default. - // +optional - AdditionalLabels infrav1.Labels `json:"additionalLabels,omitempty"` - // Management specifies the node pool management options. - // +optional - Management *NodePoolManagement `json:"management,omitempty"` - // LinuxNodeConfig specifies the settings for Linux agent nodes. - // +optional - LinuxNodeConfig *LinuxNodeConfig `json:"linuxNodeConfig,omitempty"` + GCPManagedMachinePoolClassSpec `json:",inline"` + // ProviderIDList are the provider IDs of instances in the // managed instance group corresponding to the nodegroup represented by this // machine pool @@ -179,6 +117,9 @@ type GCPManagedMachinePoolStatus struct { Replicas int32 `json:"replicas"` // Conditions specifies the cpnditions for the managed machine pool Conditions clusterv1.Conditions `json:"conditions,omitempty"` + // InfrastructureMachineKind is the kind of the infrastructure resources behind MachinePool Machines. + // +optional + InfrastructureMachineKind string `json:"infrastructureMachineKind,omitempty"` } // +kubebuilder:object:root=true diff --git a/exp/api/v1beta1/gcpmanagedmachinepool_webhook_test.go b/exp/api/v1beta1/gcpmanagedmachinepool_webhook_test.go index 410e76937..f1f2ec492 100644 --- a/exp/api/v1beta1/gcpmanagedmachinepool_webhook_test.go +++ b/exp/api/v1beta1/gcpmanagedmachinepool_webhook_test.go @@ -46,24 +46,30 @@ func TestGCPManagedMachinePoolValidatingWebhookCreate(t *testing.T) { { name: "valid node pool name", spec: GCPManagedMachinePoolSpec{ - NodePoolName: "nodepool1", + GCPManagedMachinePoolClassSpec: GCPManagedMachinePoolClassSpec{ + NodePoolName: "nodepool1", + }, }, expectError: false, }, { name: "node pool name is too long", spec: GCPManagedMachinePoolSpec{ - NodePoolName: strings.Repeat("A", maxNodePoolNameLength+1), + GCPManagedMachinePoolClassSpec: GCPManagedMachinePoolClassSpec{ + NodePoolName: strings.Repeat("A", maxNodePoolNameLength+1), + }, }, expectError: true, }, { name: "scaling with valid min/max count", spec: GCPManagedMachinePoolSpec{ - NodePoolName: "nodepool1", - Scaling: &NodePoolAutoScaling{ - MinCount: &minCount, - MaxCount: &maxCount, + GCPManagedMachinePoolClassSpec: GCPManagedMachinePoolClassSpec{ + NodePoolName: "nodepool1", + Scaling: &NodePoolAutoScaling{ + MinCount: &minCount, + MaxCount: &maxCount, + }, }, }, expectError: false, @@ -71,10 +77,12 @@ func TestGCPManagedMachinePoolValidatingWebhookCreate(t *testing.T) { { name: "scaling with invalid min/max count", spec: GCPManagedMachinePoolSpec{ - NodePoolName: "nodepool1", - Scaling: &NodePoolAutoScaling{ - MinCount: &invalidMinCount, - MaxCount: &maxCount, + GCPManagedMachinePoolClassSpec: GCPManagedMachinePoolClassSpec{ + NodePoolName: "nodepool1", + Scaling: &NodePoolAutoScaling{ + MinCount: &invalidMinCount, + MaxCount: &maxCount, + }, }, }, expectError: true, @@ -82,10 +90,12 @@ func TestGCPManagedMachinePoolValidatingWebhookCreate(t *testing.T) { { name: "scaling with max < min count", spec: GCPManagedMachinePoolSpec{ - NodePoolName: "nodepool1", - Scaling: &NodePoolAutoScaling{ - MinCount: &maxCount, - MaxCount: &minCount, + GCPManagedMachinePoolClassSpec: GCPManagedMachinePoolClassSpec{ + NodePoolName: "nodepool1", + Scaling: &NodePoolAutoScaling{ + MinCount: &maxCount, + MaxCount: &minCount, + }, }, }, expectError: true, @@ -93,11 +103,13 @@ func TestGCPManagedMachinePoolValidatingWebhookCreate(t *testing.T) { { name: "autoscaling disabled and min/max provided", spec: GCPManagedMachinePoolSpec{ - NodePoolName: "nodepool1", - Scaling: &NodePoolAutoScaling{ - EnableAutoscaling: &enableAutoscaling, - MinCount: &minCount, - MaxCount: &maxCount, + GCPManagedMachinePoolClassSpec: GCPManagedMachinePoolClassSpec{ + NodePoolName: "nodepool1", + Scaling: &NodePoolAutoScaling{ + EnableAutoscaling: &enableAutoscaling, + MinCount: &minCount, + MaxCount: &maxCount, + }, }, }, expectError: true, @@ -105,20 +117,24 @@ func TestGCPManagedMachinePoolValidatingWebhookCreate(t *testing.T) { { name: "valid non-negative values", spec: GCPManagedMachinePoolSpec{ - NodePoolName: "nodepool1", - DiskSizeGb: &diskSizeGb, - MaxPodsPerNode: &maxPods, - LocalSsdCount: &localSsds, + GCPManagedMachinePoolClassSpec: GCPManagedMachinePoolClassSpec{ + NodePoolName: "nodepool1", + DiskSizeGb: &diskSizeGb, + MaxPodsPerNode: &maxPods, + LocalSsdCount: &localSsds, + }, }, expectError: false, }, { name: "invalid negative values", spec: GCPManagedMachinePoolSpec{ - NodePoolName: "nodepool1", - DiskSizeGb: &invalidDiskSizeGb, - MaxPodsPerNode: &invalidMaxPods, - LocalSsdCount: &invalidLocalSsds, + GCPManagedMachinePoolClassSpec: GCPManagedMachinePoolClassSpec{ + NodePoolName: "nodepool1", + DiskSizeGb: &invalidDiskSizeGb, + MaxPodsPerNode: &invalidMaxPods, + LocalSsdCount: &invalidLocalSsds, + }, }, expectError: true, }, @@ -153,16 +169,20 @@ func TestGCPManagedMachinePoolValidatingWebhookUpdate(t *testing.T) { { name: "node pool is not mutated", spec: GCPManagedMachinePoolSpec{ - NodePoolName: "nodepool1", + GCPManagedMachinePoolClassSpec: GCPManagedMachinePoolClassSpec{ + NodePoolName: "nodepool1", + }, }, expectError: false, }, { name: "mutable fields are mutated", spec: GCPManagedMachinePoolSpec{ - NodePoolName: "nodepool1", - AdditionalLabels: infrav1.Labels{ - "testKey": "testVal", + GCPManagedMachinePoolClassSpec: GCPManagedMachinePoolClassSpec{ + NodePoolName: "nodepool1", + AdditionalLabels: infrav1.Labels{ + "testKey": "testVal", + }, }, }, expectError: false, @@ -170,8 +190,10 @@ func TestGCPManagedMachinePoolValidatingWebhookUpdate(t *testing.T) { { name: "immutable field disk size is mutated", spec: GCPManagedMachinePoolSpec{ - NodePoolName: "nodepool1", - DiskSizeGb: &diskSizeGb, + GCPManagedMachinePoolClassSpec: GCPManagedMachinePoolClassSpec{ + NodePoolName: "nodepool1", + DiskSizeGb: &diskSizeGb, + }, }, expectError: true, }, @@ -186,7 +208,9 @@ func TestGCPManagedMachinePoolValidatingWebhookUpdate(t *testing.T) { } oldMMP := &GCPManagedMachinePool{ Spec: GCPManagedMachinePoolSpec{ - NodePoolName: "nodepool1", + GCPManagedMachinePoolClassSpec: GCPManagedMachinePoolClassSpec{ + NodePoolName: "nodepool1", + }, }, } diff --git a/exp/api/v1beta1/gcpmanagedmachinepooltemplate_types.go b/exp/api/v1beta1/gcpmanagedmachinepooltemplate_types.go new file mode 100644 index 000000000..5e85e8ec1 --- /dev/null +++ b/exp/api/v1beta1/gcpmanagedmachinepooltemplate_types.go @@ -0,0 +1,56 @@ +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// GCPManagedMachinePoolTemplateSpec defines the desired state of GCPManagedMachinePoolTemplate. +type GCPManagedMachinePoolTemplateSpec struct { + Template GCPManagedMachinePoolTemplateResource `json:"template"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:resource:path=gcpmanagedmachinepooltemplates,scope=Namespaced,categories=cluster-api,shortName=ammpt +// +kubebuilder:storageversion + +// GCPManagedMachinePoolTemplate is the Schema for the GCPManagedMachinePoolTemplates API. +type GCPManagedMachinePoolTemplate struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec GCPManagedMachinePoolTemplateSpec `json:"spec,omitempty"` +} + +// +kubebuilder:object:root=true + +// GCPManagedMachinePoolTemplateList contains a list of GCPManagedMachinePoolTemplates. +type GCPManagedMachinePoolTemplateList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []GCPManagedMachinePoolTemplate `json:"items"` +} + +func init() { + SchemeBuilder.Register(&GCPManagedMachinePoolTemplate{}, &GCPManagedMachinePoolTemplateList{}) +} + +// GCPManagedMachinePoolTemplateResource describes the data needed to create an GCPManagedCluster from a template. +type GCPManagedMachinePoolTemplateResource struct { + Spec GCPManagedMachinePoolTemplateResourceSpec `json:"spec"` +} diff --git a/exp/api/v1beta1/gcpmanagedmachinepooltemplate_webhook.go b/exp/api/v1beta1/gcpmanagedmachinepooltemplate_webhook.go new file mode 100644 index 000000000..97abd6edf --- /dev/null +++ b/exp/api/v1beta1/gcpmanagedmachinepooltemplate_webhook.go @@ -0,0 +1,41 @@ +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + ctrl "sigs.k8s.io/controller-runtime" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" +) + +// log is for logging in this package. +var gmmplog = logf.Log.WithName("gcpmanagedmachinepool-resource") + +func (r *GCPManagedMachinePoolTemplate) SetupWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr). + For(r). + Complete() +} + +//+kubebuilder:webhook:path=/mutate-infrastructure-cluster-x-k8s-io-v1beta1-gcpmanagedmachinepooltemplate,mutating=true,failurePolicy=fail,sideEffects=None,groups=infrastructure.cluster.x-k8s.io,resources=gcpmanagedmachinepooltemplates,verbs=create;update,versions=v1beta1,name=mgcpmanagedmachinepooltemplate.kb.io,admissionReviewVersions=v1 + +var _ webhook.Defaulter = &GCPManagedMachinePool{} + +// Default implements webhook.Defaulter so a webhook will be registered for the type. +func (r *GCPManagedMachinePoolTemplate) Default() { + gmmplog.Info("default", "name", r.Name) +} diff --git a/exp/api/v1beta1/types_class.go b/exp/api/v1beta1/types_class.go new file mode 100644 index 000000000..5027ddf14 --- /dev/null +++ b/exp/api/v1beta1/types_class.go @@ -0,0 +1,139 @@ +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import infrav1 "sigs.k8s.io/cluster-api-provider-gcp/api/v1beta1" + +// GCPManagedControlPlaneClassSpec defines the GCPManagedControlPlane properties that may be shared across several gcp managed control planes. +type GCPManagedControlPlaneClassSpec struct { + // MachineTemplate contains information about how machines + // should be shaped when creating or updating a control plane. + // For the GCPManagedControlPlaneTemplate, this field is used + // only to fulfill the CAPI contract. + // +optional + MachineTemplate *GCPManagedControlPlaneTemplateMachineTemplate `json:"machineTemplate,omitempty"` + + // ClusterNetwork define the cluster network. + // +optional + ClusterNetwork *ClusterNetwork `json:"clusterNetwork,omitempty"` + + // Project is the name of the project to deploy the cluster to. + Project string `json:"project"` + + // Location represents the location (region or zone) in which the GKE cluster + // will be created. + Location string `json:"location"` + + // EnableAutopilot indicates whether to enable autopilot for this GKE cluster. + // +optional + EnableAutopilot bool `json:"enableAutopilot"` + + // EnableIdentityService indicates whether to enable Identity Service component for this GKE cluster. + // +optional + EnableIdentityService bool `json:"enableIdentityService"` + + // ReleaseChannel represents the release channel of the GKE cluster. + // +optional + ReleaseChannel *ReleaseChannel `json:"releaseChannel,omitempty"` + + // MasterAuthorizedNetworksConfig represents configuration options for master authorized networks feature of the GKE cluster. + // This feature is disabled if this field is not specified. + // +optional + MasterAuthorizedNetworksConfig *MasterAuthorizedNetworksConfig `json:"master_authorized_networks_config,omitempty"` + + // LoggingService represents configuration of logging service feature of the GKE cluster. + // Possible values: none, logging.googleapis.com/kubernetes (default). + // Value is ignored when enableAutopilot = true. + // +optional + LoggingService *LoggingService `json:"loggingService,omitempty"` + + // MonitoringService represents configuration of monitoring service feature of the GKE cluster. + // Possible values: none, monitoring.googleapis.com/kubernetes (default). + // Value is ignored when enableAutopilot = true. + // +optional + MonitoringService *MonitoringService `json:"monitoringService,omitempty"` +} + +// GCPManagedMachinePoolClassSpec defines the GCPManagedMachinePool properties that may be shared across several GCP managed machinepools. +type GCPManagedMachinePoolClassSpec struct { + // NodePoolName specifies the name of the GKE node pool corresponding to this MachinePool. If you don't specify a name + // then a default name will be created based on the namespace and name of the managed machine pool. + // +optional + NodePoolName string `json:"nodePoolName,omitempty"` + // MachineType is the name of a Google Compute Engine [machine + // type](https://cloud.google.com/compute/docs/machine-types). + // If unspecified, the default machine type is `e2-medium`. + // +optional + MachineType *string `json:"machineType,omitempty"` + // DiskSizeGb is the size of the disk attached to each node, specified in GB. + // The smallest allowed disk size is 10GB. If unspecified, the default disk size is 100GB. + // +optional + DiskSizeGb *int32 `json:"diskSizeGb,omitempty"` + // LocalSsdCount is the number of local SSD disks to be attached to the node. + // +optional + LocalSsdCount *int32 `json:"localSsdCount,omitempty"` + // Scaling specifies scaling for the node pool + // +optional + Scaling *NodePoolAutoScaling `json:"scaling,omitempty"` + // NodeLocations is the list of zones in which the NodePool's + // nodes should be located. + // +optional + NodeLocations []string `json:"nodeLocations,omitempty"` + // ImageType is image type to use for this nodepool. + // +optional + ImageType *string `json:"imageType,omitempty"` + // InstanceType is name of Compute Engine machine type. + // +optional + InstanceType *string `json:"instanceType,omitempty"` + // DiskType is type of the disk attached to each node. + // +optional + DiskType *DiskType `json:"diskType,omitempty"` + // DiskSizeGB is size of the disk attached to each node, + // specified in GB. + // +kubebuilder:validation:Minimum:=10 + // +optional + DiskSizeGB *int64 `json:"diskSizeGB,omitempty"` + // MaxPodsPerNode is constraint enforced on the max num of + // pods per node. + // +kubebuilder:validation:Minimum:=8 + // +kubebuilder:validation:Maximum:=256 + // +optional + MaxPodsPerNode *int64 `json:"maxPodsPerNode,omitempty"` + // NodeNetwork specifies the node network configuration + // options. + // +optional + NodeNetwork NodeNetworkConfig `json:"nodeNetwork,omitempty"` + // NodeSecurity specifies the node security options. + // +optional + NodeSecurity NodeSecurityConfig `json:"nodeSecurity,omitempty"` + // KubernetesLabels specifies the labels to apply to the nodes of the node pool. + // +optional + KubernetesLabels infrav1.Labels `json:"kubernetesLabels,omitempty"` + // KubernetesTaints specifies the taints to apply to the nodes of the node pool. + // +optional + KubernetesTaints Taints `json:"kubernetesTaints,omitempty"` + // AdditionalLabels is an optional set of tags to add to GCP resources managed by the GCP provider, in addition to the + // ones added by default. + // +optional + AdditionalLabels infrav1.Labels `json:"additionalLabels,omitempty"` + // Management specifies the node pool management options. + // +optional + Management *NodePoolManagement `json:"management,omitempty"` + // LinuxNodeConfig specifies the settings for Linux agent nodes. + // +optional + LinuxNodeConfig *LinuxNodeConfig `json:"linuxNodeConfig,omitempty"` +} diff --git a/exp/api/v1beta1/types_template.go b/exp/api/v1beta1/types_template.go new file mode 100644 index 000000000..91626a0f8 --- /dev/null +++ b/exp/api/v1beta1/types_template.go @@ -0,0 +1,78 @@ +/* +Copyright 2025 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + infrav1 "sigs.k8s.io/cluster-api-provider-gcp/api/v1beta1" + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" +) + +// GCPManagedControlPlaneTemplateResourceSpec specifies an GCP managed control plane template resource. +type GCPManagedControlPlaneTemplateResourceSpec struct { + GCPManagedControlPlaneClassSpec `json:",inline"` +} + +// GCPManagedControlPlaneTemplateMachineTemplate is only used to fulfill the CAPI contract which expects a +// MachineTemplate field for any controlplane ref in a topology. +type GCPManagedControlPlaneTemplateMachineTemplate struct{} + +// GCPManagedMachinePoolTemplateResourceSpec specifies an GCP managed control plane template resource. +type GCPManagedMachinePoolTemplateResourceSpec struct { + GCPManagedMachinePoolClassSpec `json:",inline"` +} + +// GCPManagedClusterTemplateResourceSpec specifies an GCP managed cluster template resource. +type GCPManagedClusterTemplateResourceSpec struct { + // Project is the name of the project to deploy the cluster to. + Project string `json:"project"` + + // The GCP Region the cluster lives in. + Region string `json:"region"` + + // ControlPlaneEndpoint represents the endpoint used to communicate with the control plane. + // +optional + ControlPlaneEndpoint clusterv1.APIEndpoint `json:"controlPlaneEndpoint"` + + // NetworkSpec encapsulates all things related to the GCP network. + // +optional + Network infrav1.NetworkSpec `json:"network"` + + // AdditionalLabels is an optional set of tags to add to GCP resources managed by the GCP provider, in addition to the + // ones added by default. + // +optional + AdditionalLabels infrav1.Labels `json:"additionalLabels,omitempty"` + + // ResourceManagerTags is an optional set of tags to apply to GCP resources managed + // by the GCP provider. GCP supports a maximum of 50 tags per resource. + // +maxItems=50 + // +optional + ResourceManagerTags infrav1.ResourceManagerTags `json:"resourceManagerTags,omitempty"` + + // CredentialsRef is a reference to a Secret that contains the credentials to use for provisioning this cluster. If not + // supplied then the credentials of the controller will be used. + // +optional + CredentialsRef *infrav1.ObjectReference `json:"credentialsRef,omitempty"` + + // LoadBalancerSpec contains configuration for one or more LoadBalancers. + // +optional + LoadBalancer infrav1.LoadBalancerSpec `json:"loadBalancer,omitempty"` + + // ServiceEndpoints contains the custom GCP Service Endpoint urls for each applicable service. + // For instance, the user can specify a new endpoint for the compute service. + // +optional + ServiceEndpoints *infrav1.ServiceEndpoints `json:"serviceEndpoints,omitempty"` +} diff --git a/exp/api/v1beta1/zz_generated.deepcopy.go b/exp/api/v1beta1/zz_generated.deepcopy.go index 73a813a13..3cd3f63ac 100644 --- a/exp/api/v1beta1/zz_generated.deepcopy.go +++ b/exp/api/v1beta1/zz_generated.deepcopy.go @@ -231,26 +231,25 @@ func (in *GCPManagedClusterStatus) DeepCopy() *GCPManagedClusterStatus { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GCPManagedControlPlane) DeepCopyInto(out *GCPManagedControlPlane) { +func (in *GCPManagedClusterTemplate) DeepCopyInto(out *GCPManagedClusterTemplate) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPManagedControlPlane. -func (in *GCPManagedControlPlane) DeepCopy() *GCPManagedControlPlane { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPManagedClusterTemplate. +func (in *GCPManagedClusterTemplate) DeepCopy() *GCPManagedClusterTemplate { if in == nil { return nil } - out := new(GCPManagedControlPlane) + out := new(GCPManagedClusterTemplate) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *GCPManagedControlPlane) DeepCopyObject() runtime.Object { +func (in *GCPManagedClusterTemplate) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } @@ -258,31 +257,31 @@ func (in *GCPManagedControlPlane) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GCPManagedControlPlaneList) DeepCopyInto(out *GCPManagedControlPlaneList) { +func (in *GCPManagedClusterTemplateList) DeepCopyInto(out *GCPManagedClusterTemplateList) { *out = *in out.TypeMeta = in.TypeMeta in.ListMeta.DeepCopyInto(&out.ListMeta) if in.Items != nil { in, out := &in.Items, &out.Items - *out = make([]GCPManagedControlPlane, len(*in)) + *out = make([]GCPManagedClusterTemplate, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPManagedControlPlaneList. -func (in *GCPManagedControlPlaneList) DeepCopy() *GCPManagedControlPlaneList { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPManagedClusterTemplateList. +func (in *GCPManagedClusterTemplateList) DeepCopy() *GCPManagedClusterTemplateList { if in == nil { return nil } - out := new(GCPManagedControlPlaneList) + out := new(GCPManagedClusterTemplateList) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *GCPManagedControlPlaneList) DeepCopyObject() runtime.Object { +func (in *GCPManagedClusterTemplateList) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } @@ -290,8 +289,112 @@ func (in *GCPManagedControlPlaneList) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GCPManagedControlPlaneSpec) DeepCopyInto(out *GCPManagedControlPlaneSpec) { +func (in *GCPManagedClusterTemplateResource) DeepCopyInto(out *GCPManagedClusterTemplateResource) { + *out = *in + in.Spec.DeepCopyInto(&out.Spec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPManagedClusterTemplateResource. +func (in *GCPManagedClusterTemplateResource) DeepCopy() *GCPManagedClusterTemplateResource { + if in == nil { + return nil + } + out := new(GCPManagedClusterTemplateResource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GCPManagedClusterTemplateResourceSpec) DeepCopyInto(out *GCPManagedClusterTemplateResourceSpec) { *out = *in + out.ControlPlaneEndpoint = in.ControlPlaneEndpoint + in.Network.DeepCopyInto(&out.Network) + if in.AdditionalLabels != nil { + in, out := &in.AdditionalLabels, &out.AdditionalLabels + *out = make(apiv1beta1.Labels, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.ResourceManagerTags != nil { + in, out := &in.ResourceManagerTags, &out.ResourceManagerTags + *out = make(apiv1beta1.ResourceManagerTags, len(*in)) + copy(*out, *in) + } + if in.CredentialsRef != nil { + in, out := &in.CredentialsRef, &out.CredentialsRef + *out = new(apiv1beta1.ObjectReference) + **out = **in + } + in.LoadBalancer.DeepCopyInto(&out.LoadBalancer) + if in.ServiceEndpoints != nil { + in, out := &in.ServiceEndpoints, &out.ServiceEndpoints + *out = new(apiv1beta1.ServiceEndpoints) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPManagedClusterTemplateResourceSpec. +func (in *GCPManagedClusterTemplateResourceSpec) DeepCopy() *GCPManagedClusterTemplateResourceSpec { + if in == nil { + return nil + } + out := new(GCPManagedClusterTemplateResourceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GCPManagedClusterTemplateSpec) DeepCopyInto(out *GCPManagedClusterTemplateSpec) { + *out = *in + in.Template.DeepCopyInto(&out.Template) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPManagedClusterTemplateSpec. +func (in *GCPManagedClusterTemplateSpec) DeepCopy() *GCPManagedClusterTemplateSpec { + if in == nil { + return nil + } + out := new(GCPManagedClusterTemplateSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GCPManagedControlPlane) DeepCopyInto(out *GCPManagedControlPlane) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPManagedControlPlane. +func (in *GCPManagedControlPlane) DeepCopy() *GCPManagedControlPlane { + if in == nil { + return nil + } + out := new(GCPManagedControlPlane) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GCPManagedControlPlane) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GCPManagedControlPlaneClassSpec) DeepCopyInto(out *GCPManagedControlPlaneClassSpec) { + *out = *in + if in.MachineTemplate != nil { + in, out := &in.MachineTemplate, &out.MachineTemplate + *out = new(GCPManagedControlPlaneTemplateMachineTemplate) + **out = **in + } if in.ClusterNetwork != nil { in, out := &in.ClusterNetwork, &out.ClusterNetwork *out = new(ClusterNetwork) @@ -302,17 +405,6 @@ func (in *GCPManagedControlPlaneSpec) DeepCopyInto(out *GCPManagedControlPlaneSp *out = new(ReleaseChannel) **out = **in } - if in.ControlPlaneVersion != nil { - in, out := &in.ControlPlaneVersion, &out.ControlPlaneVersion - *out = new(string) - **out = **in - } - if in.Version != nil { - in, out := &in.Version, &out.Version - *out = new(string) - **out = **in - } - out.Endpoint = in.Endpoint if in.MasterAuthorizedNetworksConfig != nil { in, out := &in.MasterAuthorizedNetworksConfig, &out.MasterAuthorizedNetworksConfig *out = new(MasterAuthorizedNetworksConfig) @@ -330,6 +422,65 @@ func (in *GCPManagedControlPlaneSpec) DeepCopyInto(out *GCPManagedControlPlaneSp } } +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPManagedControlPlaneClassSpec. +func (in *GCPManagedControlPlaneClassSpec) DeepCopy() *GCPManagedControlPlaneClassSpec { + if in == nil { + return nil + } + out := new(GCPManagedControlPlaneClassSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GCPManagedControlPlaneList) DeepCopyInto(out *GCPManagedControlPlaneList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]GCPManagedControlPlane, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPManagedControlPlaneList. +func (in *GCPManagedControlPlaneList) DeepCopy() *GCPManagedControlPlaneList { + if in == nil { + return nil + } + out := new(GCPManagedControlPlaneList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GCPManagedControlPlaneList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GCPManagedControlPlaneSpec) DeepCopyInto(out *GCPManagedControlPlaneSpec) { + *out = *in + in.GCPManagedControlPlaneClassSpec.DeepCopyInto(&out.GCPManagedControlPlaneClassSpec) + if in.ControlPlaneVersion != nil { + in, out := &in.ControlPlaneVersion, &out.ControlPlaneVersion + *out = new(string) + **out = **in + } + if in.Version != nil { + in, out := &in.Version, &out.Version + *out = new(string) + **out = **in + } + out.Endpoint = in.Endpoint +} + // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPManagedControlPlaneSpec. func (in *GCPManagedControlPlaneSpec) DeepCopy() *GCPManagedControlPlaneSpec { if in == nil { @@ -368,26 +519,25 @@ func (in *GCPManagedControlPlaneStatus) DeepCopy() *GCPManagedControlPlaneStatus } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GCPManagedMachinePool) DeepCopyInto(out *GCPManagedMachinePool) { +func (in *GCPManagedControlPlaneTemplate) DeepCopyInto(out *GCPManagedControlPlaneTemplate) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPManagedMachinePool. -func (in *GCPManagedMachinePool) DeepCopy() *GCPManagedMachinePool { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPManagedControlPlaneTemplate. +func (in *GCPManagedControlPlaneTemplate) DeepCopy() *GCPManagedControlPlaneTemplate { if in == nil { return nil } - out := new(GCPManagedMachinePool) + out := new(GCPManagedControlPlaneTemplate) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *GCPManagedMachinePool) DeepCopyObject() runtime.Object { +func (in *GCPManagedControlPlaneTemplate) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } @@ -395,31 +545,31 @@ func (in *GCPManagedMachinePool) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GCPManagedMachinePoolList) DeepCopyInto(out *GCPManagedMachinePoolList) { +func (in *GCPManagedControlPlaneTemplateList) DeepCopyInto(out *GCPManagedControlPlaneTemplateList) { *out = *in out.TypeMeta = in.TypeMeta in.ListMeta.DeepCopyInto(&out.ListMeta) if in.Items != nil { in, out := &in.Items, &out.Items - *out = make([]GCPManagedMachinePool, len(*in)) + *out = make([]GCPManagedControlPlaneTemplate, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPManagedMachinePoolList. -func (in *GCPManagedMachinePoolList) DeepCopy() *GCPManagedMachinePoolList { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPManagedControlPlaneTemplateList. +func (in *GCPManagedControlPlaneTemplateList) DeepCopy() *GCPManagedControlPlaneTemplateList { if in == nil { return nil } - out := new(GCPManagedMachinePoolList) + out := new(GCPManagedControlPlaneTemplateList) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *GCPManagedMachinePoolList) DeepCopyObject() runtime.Object { +func (in *GCPManagedControlPlaneTemplateList) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } @@ -427,7 +577,97 @@ func (in *GCPManagedMachinePoolList) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GCPManagedMachinePoolSpec) DeepCopyInto(out *GCPManagedMachinePoolSpec) { +func (in *GCPManagedControlPlaneTemplateMachineTemplate) DeepCopyInto(out *GCPManagedControlPlaneTemplateMachineTemplate) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPManagedControlPlaneTemplateMachineTemplate. +func (in *GCPManagedControlPlaneTemplateMachineTemplate) DeepCopy() *GCPManagedControlPlaneTemplateMachineTemplate { + if in == nil { + return nil + } + out := new(GCPManagedControlPlaneTemplateMachineTemplate) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GCPManagedControlPlaneTemplateResource) DeepCopyInto(out *GCPManagedControlPlaneTemplateResource) { + *out = *in + in.Spec.DeepCopyInto(&out.Spec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPManagedControlPlaneTemplateResource. +func (in *GCPManagedControlPlaneTemplateResource) DeepCopy() *GCPManagedControlPlaneTemplateResource { + if in == nil { + return nil + } + out := new(GCPManagedControlPlaneTemplateResource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GCPManagedControlPlaneTemplateResourceSpec) DeepCopyInto(out *GCPManagedControlPlaneTemplateResourceSpec) { + *out = *in + in.GCPManagedControlPlaneClassSpec.DeepCopyInto(&out.GCPManagedControlPlaneClassSpec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPManagedControlPlaneTemplateResourceSpec. +func (in *GCPManagedControlPlaneTemplateResourceSpec) DeepCopy() *GCPManagedControlPlaneTemplateResourceSpec { + if in == nil { + return nil + } + out := new(GCPManagedControlPlaneTemplateResourceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GCPManagedControlPlaneTemplateSpec) DeepCopyInto(out *GCPManagedControlPlaneTemplateSpec) { + *out = *in + in.Template.DeepCopyInto(&out.Template) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPManagedControlPlaneTemplateSpec. +func (in *GCPManagedControlPlaneTemplateSpec) DeepCopy() *GCPManagedControlPlaneTemplateSpec { + if in == nil { + return nil + } + out := new(GCPManagedControlPlaneTemplateSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GCPManagedMachinePool) DeepCopyInto(out *GCPManagedMachinePool) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPManagedMachinePool. +func (in *GCPManagedMachinePool) DeepCopy() *GCPManagedMachinePool { + if in == nil { + return nil + } + out := new(GCPManagedMachinePool) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GCPManagedMachinePool) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GCPManagedMachinePoolClassSpec) DeepCopyInto(out *GCPManagedMachinePoolClassSpec) { *out = *in if in.MachineType != nil { in, out := &in.MachineType, &out.MachineType @@ -510,6 +750,54 @@ func (in *GCPManagedMachinePoolSpec) DeepCopyInto(out *GCPManagedMachinePoolSpec *out = new(LinuxNodeConfig) (*in).DeepCopyInto(*out) } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPManagedMachinePoolClassSpec. +func (in *GCPManagedMachinePoolClassSpec) DeepCopy() *GCPManagedMachinePoolClassSpec { + if in == nil { + return nil + } + out := new(GCPManagedMachinePoolClassSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GCPManagedMachinePoolList) DeepCopyInto(out *GCPManagedMachinePoolList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]GCPManagedMachinePool, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPManagedMachinePoolList. +func (in *GCPManagedMachinePoolList) DeepCopy() *GCPManagedMachinePoolList { + if in == nil { + return nil + } + out := new(GCPManagedMachinePoolList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GCPManagedMachinePoolList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GCPManagedMachinePoolSpec) DeepCopyInto(out *GCPManagedMachinePoolSpec) { + *out = *in + in.GCPManagedMachinePoolClassSpec.DeepCopyInto(&out.GCPManagedMachinePoolClassSpec) if in.ProviderIDList != nil { in, out := &in.ProviderIDList, &out.ProviderIDList *out = make([]string, len(*in)) @@ -549,6 +837,112 @@ func (in *GCPManagedMachinePoolStatus) DeepCopy() *GCPManagedMachinePoolStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GCPManagedMachinePoolTemplate) DeepCopyInto(out *GCPManagedMachinePoolTemplate) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPManagedMachinePoolTemplate. +func (in *GCPManagedMachinePoolTemplate) DeepCopy() *GCPManagedMachinePoolTemplate { + if in == nil { + return nil + } + out := new(GCPManagedMachinePoolTemplate) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GCPManagedMachinePoolTemplate) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GCPManagedMachinePoolTemplateList) DeepCopyInto(out *GCPManagedMachinePoolTemplateList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]GCPManagedMachinePoolTemplate, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPManagedMachinePoolTemplateList. +func (in *GCPManagedMachinePoolTemplateList) DeepCopy() *GCPManagedMachinePoolTemplateList { + if in == nil { + return nil + } + out := new(GCPManagedMachinePoolTemplateList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GCPManagedMachinePoolTemplateList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GCPManagedMachinePoolTemplateResource) DeepCopyInto(out *GCPManagedMachinePoolTemplateResource) { + *out = *in + in.Spec.DeepCopyInto(&out.Spec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPManagedMachinePoolTemplateResource. +func (in *GCPManagedMachinePoolTemplateResource) DeepCopy() *GCPManagedMachinePoolTemplateResource { + if in == nil { + return nil + } + out := new(GCPManagedMachinePoolTemplateResource) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GCPManagedMachinePoolTemplateResourceSpec) DeepCopyInto(out *GCPManagedMachinePoolTemplateResourceSpec) { + *out = *in + in.GCPManagedMachinePoolClassSpec.DeepCopyInto(&out.GCPManagedMachinePoolClassSpec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPManagedMachinePoolTemplateResourceSpec. +func (in *GCPManagedMachinePoolTemplateResourceSpec) DeepCopy() *GCPManagedMachinePoolTemplateResourceSpec { + if in == nil { + return nil + } + out := new(GCPManagedMachinePoolTemplateResourceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GCPManagedMachinePoolTemplateSpec) DeepCopyInto(out *GCPManagedMachinePoolTemplateSpec) { + *out = *in + in.Template.DeepCopyInto(&out.Template) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GCPManagedMachinePoolTemplateSpec. +func (in *GCPManagedMachinePoolTemplateSpec) DeepCopy() *GCPManagedMachinePoolTemplateSpec { + if in == nil { + return nil + } + out := new(GCPManagedMachinePoolTemplateSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LinuxNodeConfig) DeepCopyInto(out *LinuxNodeConfig) { *out = *in diff --git a/exp/controllers/gcpmanagedmachinepool_controller.go b/exp/controllers/gcpmanagedmachinepool_controller.go index 96dc4c0ca..afb3ab92f 100644 --- a/exp/controllers/gcpmanagedmachinepool_controller.go +++ b/exp/controllers/gcpmanagedmachinepool_controller.go @@ -321,6 +321,7 @@ func (r *GCPManagedMachinePoolReconciler) reconcile(ctx context.Context, managed log.Info("Reconciling GCPManagedMachinePool") controllerutil.AddFinalizer(managedMachinePoolScope.GCPManagedMachinePool, infrav1exp.ManagedMachinePoolFinalizer) + managedMachinePoolScope.SetInfrastructureMachineKind() if err := managedMachinePoolScope.PatchObject(); err != nil { return ctrl.Result{}, err } diff --git a/main.go b/main.go index e1f1bad51..f897ca236 100644 --- a/main.go +++ b/main.go @@ -252,12 +252,21 @@ func setupWebhooks(mgr ctrl.Manager) error { if err := (&infrav1exp.GCPManagedCluster{}).SetupWebhookWithManager(mgr); err != nil { return fmt.Errorf("setting up GCPManagedCluster webhook: %w", err) } + if err := (&infrav1exp.GCPManagedClusterTemplate{}).SetupWebhookWithManager(mgr); err != nil { + return fmt.Errorf("setting up GCPManagedClusterTemplate webhook: %w", err) + } if err := (&infrav1exp.GCPManagedControlPlane{}).SetupWebhookWithManager(mgr); err != nil { return fmt.Errorf("setting up GCPManagedControlPlane webhook: %w", err) } + if err := (&infrav1exp.GCPManagedControlPlaneTemplate{}).SetupWebhookWithManager(mgr); err != nil { + return fmt.Errorf("setting up GCPManagedControlPlaneTemplate webhook: %w", err) + } if err := (&infrav1exp.GCPManagedMachinePool{}).SetupWebhookWithManager(mgr); err != nil { return fmt.Errorf("setting up GCPManagedMachinePool webhook: %w", err) } + if err := (&infrav1exp.GCPManagedMachinePoolTemplate{}).SetupWebhookWithManager(mgr); err != nil { + return fmt.Errorf("setting up GCPManagedMachinePoolTemplate webhook: %w", err) + } } return nil diff --git a/templates/cluster-template-gke-autopilot-clusterclass.yaml b/templates/cluster-template-gke-autopilot-clusterclass.yaml new file mode 100644 index 000000000..45d34ecca --- /dev/null +++ b/templates/cluster-template-gke-autopilot-clusterclass.yaml @@ -0,0 +1,108 @@ +apiVersion: cluster.x-k8s.io/v1beta1 +kind: ClusterClass +metadata: + name: ${CLUSTER_CLASS_NAME} + namespace: default +spec: + controlPlane: + ref: + apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 + kind: GCPManagedControlPlaneTemplate + name: ${CLUSTER_NAME}-control-plane + infrastructure: + ref: + apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 + kind: GCPManagedClusterTemplate + name: ${CLUSTER_NAME} + variables: + - name: region + required: true + schema: + openAPIV3Schema: + type: string + default: us-east4 + - name: networkName + required: true + schema: + openAPIV3Schema: + type: string + default: gke-default-network + patches: + - name: managedClusterRegion + definitions: + - selector: + apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 + kind: GCPManagedClusterTemplate + matchResources: + infrastructureCluster: true + jsonPatches: + - op: add + path: /spec/template/spec/region + valueFrom: + variable: region + - name: managedControlPlaneRegion + definitions: + - selector: + apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 + kind: GCPManagedControlPlaneTemplate + matchResources: + controlPlane: true + jsonPatches: + - op: add + path: /spec/template/spec/location + valueFrom: + variable: region + - name: managedClusterNetwork + definitions: + - selector: + apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 + kind: GCPManagedClusterTemplate + matchResources: + infrastructureCluster: true + jsonPatches: + - op: add + path: /spec/template/spec/network/name + valueFrom: + variable: networkName +--- +kind: GCPManagedControlPlaneTemplate +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +metadata: + name: "${CLUSTER_NAME}-control-plane" +spec: + template: + spec: + project: "${GCP_PROJECT}" + location: "set-by-patch" + enableAutopilot: true + releaseChannel: "regular" +--- +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +kind: GCPManagedClusterTemplate +metadata: + name: ${CLUSTER_NAME} +spec: + template: + spec: + project: "${GCP_PROJECT}" + region: "set-by-patch" + network: + name: "set-by-patch" +--- +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +kind: GCPMachineTemplate +metadata: + name: "${CLUSTER_NAME}-md-0" +spec: + template: + spec: + instanceType: "${GCP_MACHINE_TYPE}" +--- +apiVersion: bootstrap.cluster.x-k8s.io/v1beta1 +kind: KubeadmConfigTemplate +metadata: + name: ${CLUSTER_NAME}-md-0 + namespace: default +spec: + template: + spec: {} diff --git a/templates/cluster-template-gke-autopilot-topology.yaml b/templates/cluster-template-gke-autopilot-topology.yaml new file mode 100644 index 000000000..ea3274697 --- /dev/null +++ b/templates/cluster-template-gke-autopilot-topology.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: cluster.x-k8s.io/v1beta1 +kind: Cluster +metadata: + name: ${CLUSTER_NAME} +spec: + clusterNetwork: + pods: + cidrBlocks: + - 192.168.0.0/16 + topology: + class: ${CLUSTER_CLASS_NAME} + version: ${KUBERNETES_VERSION} + variables: + - name: region + value: ${GCP_REGION} + - name: networkName + value: ${GCP_NETWORK_NAME} diff --git a/test/e2e/config/gcp-ci.yaml b/test/e2e/config/gcp-ci.yaml index 86751cfb9..d7d0a402d 100644 --- a/test/e2e/config/gcp-ci.yaml +++ b/test/e2e/config/gcp-ci.yaml @@ -73,6 +73,7 @@ providers: - sourcePath: "${PWD}/test/e2e/data/infrastructure-gcp/cluster-template-ci-gke-autopilot.yaml" - sourcePath: "${PWD}/test/e2e/data/infrastructure-gcp/cluster-template-ci-gke-custom-subnet.yaml" - sourcePath: "${PWD}/test/e2e/data/infrastructure-gcp/cluster-template-ci-with-internal-lb.yaml" + - sourcePath: "${PWD}/test/e2e/data/infrastructure-gcp/withclusterclass/cluster-template-ci-gke-autopilot-topology.yaml" variables: KUBERNETES_VERSION: "${KUBERNETES_VERSION:-v1.31.0}" diff --git a/test/e2e/data/infrastructure-gcp/withclusterclass/cluster-template-ci-gke-autopilot-topology.yaml b/test/e2e/data/infrastructure-gcp/withclusterclass/cluster-template-ci-gke-autopilot-topology.yaml new file mode 100644 index 000000000..575cc9678 --- /dev/null +++ b/test/e2e/data/infrastructure-gcp/withclusterclass/cluster-template-ci-gke-autopilot-topology.yaml @@ -0,0 +1,127 @@ +apiVersion: cluster.x-k8s.io/v1beta1 +kind: ClusterClass +metadata: + name: gke-autopilot-cc + namespace: default +spec: + controlPlane: + ref: + apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 + kind: GCPManagedControlPlaneTemplate + name: ${CLUSTER_NAME}-control-plane + infrastructure: + ref: + apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 + kind: GCPManagedClusterTemplate + name: ${CLUSTER_NAME} + variables: + - name: region + required: true + schema: + openAPIV3Schema: + type: string + default: us-east4 + - name: networkName + required: true + schema: + openAPIV3Schema: + type: string + default: gke-default-network + patches: + - name: managedClusterRegion + definitions: + - selector: + apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 + kind: GCPManagedClusterTemplate + matchResources: + infrastructureCluster: true + jsonPatches: + - op: add + path: /spec/template/spec/region + valueFrom: + variable: region + - name: managedControlPlaneRegion + definitions: + - selector: + apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 + kind: GCPManagedControlPlaneTemplate + matchResources: + controlPlane: true + jsonPatches: + - op: add + path: /spec/template/spec/location + valueFrom: + variable: region + - name: managedClusterNetwork + definitions: + - selector: + apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 + kind: GCPManagedClusterTemplate + matchResources: + infrastructureCluster: true + jsonPatches: + - op: add + path: /spec/template/spec/network/name + valueFrom: + variable: networkName +--- +kind: GCPManagedControlPlaneTemplate +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +metadata: + name: "${CLUSTER_NAME}-control-plane" +spec: + template: + spec: + project: "${GCP_PROJECT}" + location: "set-by-patch" + enableAutopilot: true + releaseChannel: "regular" +--- +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +kind: GCPManagedClusterTemplate +metadata: + name: ${CLUSTER_NAME} +spec: + template: + spec: + project: "${GCP_PROJECT}" + region: "set-by-patch" + network: + name: "set-by-patch" +--- +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 +kind: GCPMachineTemplate +metadata: + name: "${CLUSTER_NAME}-md-0" +spec: + template: + spec: + instanceType: "${GCP_MACHINE_TYPE}" +--- +apiVersion: bootstrap.cluster.x-k8s.io/v1beta1 +kind: KubeadmConfigTemplate +metadata: + name: ${CLUSTER_NAME}-md-0 + namespace: default +spec: + template: + spec: {} +--- +apiVersion: cluster.x-k8s.io/v1beta1 +kind: Cluster +metadata: + name: ${CLUSTER_NAME} +spec: + clusterNetwork: + pods: + cidrBlocks: + - 192.168.0.0/16 + topology: + class: gke-autopilot-cc + version: ${KUBERNETES_VERSION} + variables: + - name: region + value: ${GCP_REGION} + - name: networkName + value: ${GCP_NETWORK_NAME} + diff --git a/test/e2e/e2e_gke_test.go b/test/e2e/e2e_gke_test.go index d6fe45b93..087ac0e71 100644 --- a/test/e2e/e2e_gke_test.go +++ b/test/e2e/e2e_gke_test.go @@ -188,4 +188,29 @@ var _ = Describe("GKE workload cluster creation", func() { }, result) }) }) + + Context("Creating a GKE cluster with autopilot from a cluster class", func() { + It("Should create a cluster class and a cluster from it", func() { + By("Initializes a managed control plane and managed cluster") + + ApplyManagedClusterTemplateAndWait(ctx, ApplyManagedClusterTemplateAndWaitInput{ + ClusterProxy: bootstrapClusterProxy, + ConfigCluster: clusterctl.ConfigClusterInput{ + LogFolder: clusterctlLogFolder, + ClusterctlConfigPath: clusterctlConfigPath, + KubeconfigPath: bootstrapClusterProxy.GetKubeconfigPath(), + InfrastructureProvider: clusterctl.DefaultInfrastructureProvider, + Flavor: "ci-gke-autopilot-topology", + Namespace: namespace.Name, + ClusterName: clusterName, + KubernetesVersion: e2eConfig.GetVariable(KubernetesVersion), + ControlPlaneMachineCount: ptr.To[int64](1), + WorkerMachineCount: ptr.To[int64](0), + }, + WaitForClusterIntervals: e2eConfig.GetIntervals(specName, "wait-cluster"), + WaitForControlPlaneIntervals: e2eConfig.GetIntervals(specName, "wait-control-plane"), + WaitForMachinePools: e2eConfig.GetIntervals(specName, "wait-worker-machine-pools"), + }, result) + }) + }) })