Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions PROJECT
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,21 @@ resources:
defaulting: true
validation: true
webhookVersion: v1
- api:
crdVersion: v1
namespaced: true
controller: true
domain: cluster.x-k8s.io
group: infrastructure
kind: StackitManagedCluster
path: github.com/stackitcloud/cluster-api-provider-stackit/api/v1alpha1
version: v1alpha1
- api:
crdVersion: v1
namespaced: true
domain: cluster.x-k8s.io
group: infrastructure
kind: StackitManagedNodePool
path: github.com/stackitcloud/cluster-api-provider-stackit/api/v1alpha1
version: v1alpha1
version: "3"
152 changes: 152 additions & 0 deletions api/v1alpha1/stackitmanagedcluster_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/*
Copyright 2026.

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

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

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

package v1alpha1

import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clusterv1 "sigs.k8s.io/cluster-api/api/core/v1beta2"
)

// StackitManagedClusterSpec defines the desired state of StackitManagedCluster.
// +kubebuilder:validation:XValidation:rule="has(self.credentialsSecretRef.name) && size(self.credentialsSecretRef.name) > 0",message="credentialsSecretRef.name is required"
type StackitManagedClusterSpec struct {
// projectID is the STACKIT project that owns the cluster infrastructure.
// +required
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:Pattern=`^[0-9a-fA-F-]{36}$`
ProjectID string `json:"projectID"`

// region is the STACKIT region in which infrastructure is created.
// +required
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:Pattern=`^[a-z]{2}[0-9]{2}$`

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this an official pattern you got from our stackit api docs?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pattern is not coming from the SKE api docs. I reused the existing validation from StackitClusterSpec.Region.

Region string `json:"region"`

// clusterName is the SKE cluster name in STACKIT.
// +required
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=11
// +kubebuilder:validation:Pattern=`^[a-z0-9]([a-z0-9-]{0,9}[a-z0-9])?$`
ClusterName string `json:"clusterName"`

// credentialsSecretRef references the Secret containing STACKIT credentials.
// +required
CredentialsSecretRef corev1.SecretReference `json:"credentialsSecretRef"`

// kubernetes configures the SKE cluster Kubernetes version.
// +required
Kubernetes StackitManagedClusterKubernetesSpec `json:"kubernetes"`

// network configures the STACKIT network for the SKE cluster.
// +optional
Network *StackitManagedClusterNetworkSpec `json:"network,omitempty"`

// controlPlaneEndpoint represents the endpoint used to communicate with the
// control plane.
// +optional
ControlPlaneEndpoint clusterv1.APIEndpoint `json:"controlPlaneEndpoint,omitempty,omitzero"`

// labels are key-value pairs applied to the SKE cluster object.
// +optional
Labels map[string]string `json:"labels,omitempty"`
}

// StackitManagedClusterKubernetesSpec configures the Kubernetes version for an
// SKE cluster.
type StackitManagedClusterKubernetesSpec struct {
// version is the full Kubernetes version used for the cluster
// (major.minor.patch).
// +required
// +kubebuilder:validation:Pattern=`^[0-9]+\.[0-9]+\.[0-9]+$`
Version string `json:"version"`
}

// StackitManagedClusterNetworkSpec configures the STACKIT network for an SKE
// cluster.
type StackitManagedClusterNetworkSpec struct {
// id is the ID of the STACKIT network into which the cluster will be
// deployed.
// +required
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:Pattern=`^[0-9a-fA-F-]{36}$`
ID string `json:"id"`
}

// StackitManagedClusterStatus defines the observed state of StackitManagedCluster.
type StackitManagedClusterStatus struct {
// ready indicates that the infrastructure required for the cluster is ready.
// +optional
Ready bool `json:"ready,omitempty"`

// initialization provides Cluster API v1beta2 contract state.
// +optional
Initialization StackitManagedClusterInitializationStatus `json:"initialization,omitempty,omitzero"`

// conditions represent the current state of the StackitManagedCluster resource.
// +listType=map
// +listMapKey=type
// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`
}

// StackitManagedClusterInitializationStatus provides observations of the
// StackitManagedCluster initialization process.
// +kubebuilder:validation:MinProperties=1
type StackitManagedClusterInitializationStatus struct {
// provisioned is true when the infrastructure provider reports that the
// cluster infrastructure is fully provisioned.
// +optional
Provisioned *bool `json:"provisioned,omitempty"`
}

// +kubebuilder:object:root=true
// +kubebuilder:resource:path=stackitmanagedclusters,shortName=stmc,scope=Namespaced,categories=cluster-api
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Ready",type=boolean,JSONPath=".status.ready"
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=".metadata.creationTimestamp"
// +kubebuilder:storageversion

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

// metadata is a standard object metadata
// +optional
metav1.ObjectMeta `json:"metadata,omitzero"`

// spec defines the desired state of StackitManagedCluster
// +required
Spec StackitManagedClusterSpec `json:"spec"`

// status defines the observed state of StackitManagedCluster
// +optional
Status StackitManagedClusterStatus `json:"status,omitzero"`
}

// +kubebuilder:object:root=true

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

func init() {
SchemeBuilder.Register(&StackitManagedCluster{}, &StackitManagedClusterList{})
}
209 changes: 209 additions & 0 deletions api/v1alpha1/stackitmanagednodepool_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
/*
Copyright 2026.

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

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

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

package v1alpha1

import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// StackitManagedNodePoolSpec defines the desired state of StackitManagedNodePool.
// +kubebuilder:validation:XValidation:rule="self.minimum <= self.maximum",message="minimum must be less than or equal to maximum"
type StackitManagedNodePoolSpec struct {
// name is the name of the node pool in SKE.
// +required
// +kubebuilder:validation:MaxLength=15
Name string `json:"name"`

// availabilityZones is the list of availability zones for the node pool.
// +required
AvailabilityZones []string `json:"availabilityZones"`

// machine configures the VM instance type and OS image for the node pool.
// +required
Machine StackitManagedNodePoolMachineSpec `json:"machine"`

// minimum is the minimum number of nodes in the pool.
// +required
// +kubebuilder:validation:Maximum=1000
Minimum int32 `json:"minimum"`

// maximum is the maximum number of nodes in the pool.
// +required
// +kubebuilder:validation:Minimum=1
// +kubebuilder:validation:Maximum=1000
Maximum int32 `json:"maximum"`

// volume configures the disk volume for each node in the pool.
// +required
Volume StackitManagedNodePoolVolumeSpec `json:"volume"`

// allowSystemComponents allows system components to run on this node pool.
// +optional
AllowSystemComponents *bool `json:"allowSystemComponents,omitempty"`

// labels is a map of labels attached to all worker nodes of this node pool.
// +optional
// +kubebuilder:validation:MaxProperties=80
Labels map[string]string `json:"labels,omitempty"`

// taints is a list of taints for all worker nodes of this node pool.
// +optional
Taints []StackitManagedNodePoolTaint `json:"taints,omitempty"`

// maxSurge is the maximum number of additional VMs created during an update.
// +optional
MaxSurge *int32 `json:"maxSurge,omitempty"`

// maxUnavailable is the maximum number of VMs that can be unavailable during
// an update.
// +optional
MaxUnavailable *int32 `json:"maxUnavailable,omitempty"`

// providerIDList are the identification IDs of machine instances provided by
// the provider. This field must match the provider IDs as seen on the Node
// objects corresponding to a MachinePool's machine instances.
// +optional
// +listType=atomic
// +kubebuilder:validation:MaxItems=10000
// +kubebuilder:validation:items:MinLength=1
// +kubebuilder:validation:items:MaxLength=512
ProviderIDList []string `json:"providerIDList,omitempty"`
Comment on lines +77 to +85

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this does not belong into the spec that is something that we retrieve from the stackit api and store inside the status

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or where did you get the idea from putting this inside the spec?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This placement comes directly from the CAPI v1beta2 InfraMachinePool contract.

CAPI requires an InfraMachinePool to expose the instance provider IDs as spec.providerIDList. Core CAPI reads that field to match the MachinePool instances with Kubernetes Nodes and to track deletion.

So although this is controller-populated information rather than user desired state, it intentionally lives in spec for CAPI compatibility. I think we should keep it here for that reason.

https://cluster-api.sigs.k8s.io/developer/providers/contracts/infra-machinepool.html

}

// StackitManagedNodePoolMachineSpec configures the machine flavor and OS image
// for an SKE node pool.
type StackitManagedNodePoolMachineSpec struct {
// type selects the STACKIT machine type for the node pool.
// +required
Type string `json:"type"`

// image configures the OS image for the node pool.
// +required
Image StackitManagedNodePoolImageSpec `json:"image"`
}

// StackitManagedNodePoolImageSpec configures the OS image name and version for
// an SKE node pool.
type StackitManagedNodePoolImageSpec struct {
// name is the OS image name.
// +required
Name string `json:"name"`

// version is the OS image version.
// +required
Version string `json:"version"`
}

// StackitManagedNodePoolVolumeSpec configures the boot disk volume for an SKE
// node pool.
type StackitManagedNodePoolVolumeSpec struct {
// size is the size of the volume in GiB.
// +required
// +kubebuilder:validation:Minimum=20
// +kubebuilder:validation:Maximum=10240
Size int32 `json:"size"`

// type is the volume performance class.
// +optional
Type string `json:"type,omitempty"`
}

// StackitManagedNodePoolTaint defines a Kubernetes taint applied to nodes in
// an SKE node pool.
type StackitManagedNodePoolTaint struct {
// effect is the taint effect to apply.
// +required
// +kubebuilder:validation:Enum=NoSchedule;PreferNoSchedule;NoExecute
Effect corev1.TaintEffect `json:"effect"`

// key is the taint key to be applied to a node.
// +required
Key string `json:"key"`

// value is the taint value corresponding to the taint key.
// +optional
Value string `json:"value,omitempty"`
}

// StackitManagedNodePoolStatus defines the observed state of StackitManagedNodePool.
type StackitManagedNodePoolStatus struct {
// ready indicates that the node pool infrastructure is ready.
// +optional
Ready bool `json:"ready,omitempty"`

// initialization provides Cluster API v1beta2 contract state.
// +optional
Initialization StackitManagedNodePoolInitializationStatus `json:"initialization,omitempty,omitzero"`

// replicas is the most recently observed number of replicas.
// +optional
Replicas int32 `json:"replicas"`

// conditions represent the current state of the StackitManagedNodePool resource.
// +listType=map
// +listMapKey=type
// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`
}

// StackitManagedNodePoolInitializationStatus holds Cluster API initialization
// state for a StackitManagedNodePool.
// +kubebuilder:validation:MinProperties=1
type StackitManagedNodePoolInitializationStatus struct {
// provisioned is true when the node pool infrastructure is provisioned.
// +optional
Provisioned *bool `json:"provisioned,omitempty"`
}

// +kubebuilder:object:root=true
// +kubebuilder:resource:path=stackitmanagednodepools,shortName=stmnp,scope=Namespaced,categories=cluster-api
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Ready",type=boolean,JSONPath=".status.ready"
// +kubebuilder:printcolumn:name="Replicas",type=integer,JSONPath=".status.replicas"
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=".metadata.creationTimestamp"
// +kubebuilder:storageversion

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

// metadata is a standard object metadata
// +optional
metav1.ObjectMeta `json:"metadata,omitzero"`

// spec defines the desired state of StackitManagedNodePool
// +required
Spec StackitManagedNodePoolSpec `json:"spec"`

// status defines the observed state of StackitManagedNodePool
// +optional
Status StackitManagedNodePoolStatus `json:"status,omitzero"`
}

// +kubebuilder:object:root=true

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

func init() {
SchemeBuilder.Register(&StackitManagedNodePool{}, &StackitManagedNodePoolList{})
}
Loading
Loading