@@ -28,6 +28,16 @@ const (
2828 ClusterFinalizer = "cloudscalecluster.infrastructure.cluster.x-k8s.io"
2929)
3030
31+ // IPFamily represents an IP family configuration.
32+ // +kubebuilder:validation:Enum=IPv4;IPv6;DualStack
33+ type IPFamily string
34+
35+ const (
36+ IPFamilyIPv4 IPFamily = "IPv4"
37+ IPFamilyIPv6 IPFamily = "IPv6"
38+ IPFamilyDualStack IPFamily = "DualStack"
39+ )
40+
3141// CloudscaleClusterSpec defines the desired state of CloudscaleCluster
3242type CloudscaleClusterSpec struct {
3343 // Region is the cloudscale.ch region (e.g., "rma", "lpg").
@@ -45,17 +55,30 @@ type CloudscaleClusterSpec struct {
4555 CredentialsRef CloudscaleCredentialsReference `json:"credentialsRef"`
4656
4757 // ControlPlaneEndpoint represents the endpoint to communicate with the control plane.
48- // This is set automatically from the load balancer's VIP address.
58+ // This is set automatically from the load balancer's VIP address or floating IP .
4959 // +optional
5060 ControlPlaneEndpoint clusterv1.APIEndpoint `json:"controlPlaneEndpoint,omitzero"`
5161
52- // Network contains network configuration for the cluster.
62+ // Networks define the private networks for this cluster.
63+ // Referenced by name from machine interface specs and LB config.
64+ // If empty, defaults to a single managed network named after the cluster.
65+ // +listType=map
66+ // +listMapKey=name
5367 // +optional
54- Network NetworkSpec `json:"network,omitzero "`
68+ Networks [] NetworkSpec `json:"networks,omitempty "`
5569
5670 // ControlPlaneLoadBalancer configures the load balancer for the control plane.
5771 // +optional
5872 ControlPlaneLoadBalancer LoadBalancerSpec `json:"controlPlaneLoadBalancer,omitzero"`
73+
74+ // FloatingIP configures a floating IP for a stable control plane endpoint.
75+ // When the load balancer is enabled (recommended), the floating IP is assigned
76+ // to the LB, providing a stable IP that survives LB recreation.
77+ // When using a BYO floating IP without a load balancer, the user must
78+ // configure a dummy interface on the control plane servers (see cloudscale.ch docs).
79+ // Managed floating IPs require the load balancer to be enabled.
80+ // +optional
81+ FloatingIP * FloatingIPSpec `json:"floatingIP,omitempty"`
5982}
6083
6184// CloudscaleCredentialsReference references a Secret containing the API token.
@@ -69,28 +92,43 @@ type CloudscaleCredentialsReference struct {
6992 Namespace string `json:"namespace,omitempty"`
7093}
7194
72- // NetworkSpec defines the network configuration.
95+ // NetworkSpec defines a private network for the cluster.
96+ // Exactly one of UUID or CIDR must be specified.
7397type NetworkSpec struct {
74- // CIDR is the CIDR block for the private network subnet.
75- // +kubebuilder:default="10.0.0.0/24"
98+ // Name identifies this network within the cluster.
99+ // Used to reference this network from machine interface specs and LB config.
100+ // +kubebuilder:validation:Required
101+ // +kubebuilder:validation:Pattern=`^[a-z0-9]([a-z0-9-]*[a-z0-9])?$`
102+ // +kubebuilder:validation:MaxLength=63
103+ Name string `json:"name"`
104+
105+ // UUID references an existing cloudscale.ch network (BYO).
106+ // The network is not deleted on cluster teardown.
107+ // Mutually exclusive with CIDR.
108+ // +optional
109+ UUID string `json:"uuid,omitempty"`
110+
111+ // CIDR defines the subnet for a controller-managed network.
112+ // The network and subnet are created and deleted by CAPCS.
113+ // Mutually exclusive with UUID.
76114 // +optional
77115 CIDR string `json:"cidr,omitempty"`
78116
79117 // GatewayAddress is the gateway IP address for the subnet.
80- // By default, no gateway is configured on the private network subnet. This ensures
81- // that outbound internet traffic uses the public network interface, which is required
82- // for the Cloud Controller Manager to reach the cloudscale.ch API .
118+ // Only applicable when CIDR is set (managed network).
119+ // By default, no gateway is configured on the subnet. This ensures
120+ // that outbound internet traffic uses the public network interface .
83121 // Set this to a specific IP address (e.g., "10.0.0.1") only if you have configured
84122 // a NAT gateway or similar infrastructure on the private network.
85123 // +optional
86- GatewayAddress * string `json:"gatewayAddress,omitempty"`
124+ GatewayAddress string `json:"gatewayAddress,omitempty"`
87125}
88126
89127// LoadBalancerSpec defines the load balancer configuration for the control plane.
90128type LoadBalancerSpec struct {
91129 // Enabled controls whether a load balancer is created for the control plane.
92130 // Set to false for external control planes (e.g., hosted control plane) where the endpoint
93- // is provided externally.
131+ // is provided externally, or when using a floating IP without a load balancer .
94132 // +kubebuilder:default=true
95133 // +optional
96134 Enabled * bool `json:"enabled,omitempty"`
@@ -113,6 +151,17 @@ type LoadBalancerSpec struct {
113151 // +optional
114152 APIServerPort int32 `json:"apiServerPort,omitempty"`
115153
154+ // Network places the LB VIP on a private network (internal LB).
155+ // References spec.networks[].name. Omit for a public LB.
156+ // +optional
157+ Network string `json:"network,omitempty"`
158+
159+ // IPFamily specifies the IP family for the LB VIP address(es).
160+ // +kubebuilder:validation:Enum=IPv4;IPv6;DualStack
161+ // +kubebuilder:default=DualStack
162+ // +optional
163+ IPFamily IPFamily `json:"ipFamily,omitempty"`
164+
116165 // HealthMonitor configures the load balancer health monitor.
117166 // +optional
118167 HealthMonitor HealthMonitorSpec `json:"healthMonitor,omitempty"`
@@ -149,19 +198,38 @@ type HealthMonitorSpec struct {
149198 DownThreshold int `json:"downThreshold,omitempty"`
150199}
151200
201+ // FloatingIPSpec configures a floating IP for the control plane endpoint.
202+ // Exactly one of IPFamily or UUID must be specified.
203+ type FloatingIPSpec struct {
204+ // IPFamily creates a new floating IP with this IP version.
205+ // A floating IP is a single address, so DualStack is not valid here.
206+ // Mutually exclusive with UUID.
207+ // +kubebuilder:validation:Enum=IPv4;IPv6
208+ // +optional
209+ IPFamily * IPFamily `json:"ipFamily,omitempty"`
210+
211+ // UUID references an existing floating IP (BYO).
212+ // The floating IP is not deleted on cluster teardown.
213+ // Mutually exclusive with IPFamily.
214+ // +optional
215+ UUID string `json:"uuid,omitempty"`
216+ }
217+
152218// CloudscaleClusterStatus defines the observed state of CloudscaleCluster.
153219type CloudscaleClusterStatus struct {
154220 // Initialization contains v1beta2 initialization tracking.
155221 // +optional
156222 Initialization * ClusterInitializationStatus `json:"initialization,omitempty"`
157223
158- // NetworkID is the cloudscale.ch network UUID.
224+ // Networks track the status of each network defined in spec.networks.
225+ // +listType=map
226+ // +listMapKey=name
159227 // +optional
160- NetworkID string `json:"networkID ,omitempty"`
228+ Networks [] NetworkStatus `json:"networks ,omitempty"`
161229
162- // SubnetID is the cloudscale.ch subnet UUID .
230+ // FloatingIP is the cloudscale.ch floating IP .
163231 // +optional
164- SubnetID string `json:"subnetID ,omitempty"`
232+ FloatingIP string `json:"floatingIP ,omitempty"`
165233
166234 // LoadBalancerID is the cloudscale.ch load balancer UUID.
167235 // +optional
@@ -184,20 +252,30 @@ type CloudscaleClusterStatus struct {
184252 LoadBalancerMemberIDs []string `json:"loadBalancerMemberIDs,omitempty"`
185253
186254 // conditions represent the current state of the CloudscaleCluster resource.
187- // Each condition has a unique type and reflects the status of a specific aspect of the resource.
188- //
189- // Standard condition types include:
190- // - "Available": the resource is fully functional
191- // - "Progressing": the resource is being created or updated
192- // - "Degraded": the resource failed to reach or maintain its desired state
193- //
194- // The status of each condition is one of True, False, or Unknown.
195255 // +listType=map
196256 // +listMapKey=type
197257 // +optional
198258 Conditions []metav1.Condition `json:"conditions,omitempty"`
199259}
200260
261+ // NetworkStatus tracks the provisioned state of a single network.
262+ type NetworkStatus struct {
263+ // Name matches the logical name from spec.networks[].name.
264+ Name string `json:"name"`
265+
266+ // NetworkID is the cloudscale.ch network UUID.
267+ // +optional
268+ NetworkID string `json:"networkID,omitempty"`
269+
270+ // SubnetID is the cloudscale.ch subnet UUID.
271+ // +optional
272+ SubnetID string `json:"subnetID,omitempty"`
273+
274+ // Managed indicates whether CAPCS manages this network's lifecycle.
275+ // false for BYO networks (referenced by UUID), true for CAPCS-created networks (defined by CIDR).
276+ Managed bool `json:"managed"`
277+ }
278+
201279// ClusterInitializationStatus contains v1beta2 initialization tracking for CloudscaleCluster.
202280type ClusterInitializationStatus struct {
203281 // Provisioned indicates that all cluster infrastructure has been provisioned.
@@ -206,6 +284,16 @@ type ClusterInitializationStatus struct {
206284 Provisioned * bool `json:"provisioned,omitempty"`
207285}
208286
287+ // GetNetworkStatus returns the NetworkStatus for the given network name, or nil if not found.
288+ func (s * CloudscaleClusterStatus ) GetNetworkStatus (name string ) * NetworkStatus {
289+ for i := range s .Networks {
290+ if s .Networks [i ].Name == name {
291+ return & s .Networks [i ]
292+ }
293+ }
294+ return nil
295+ }
296+
209297// +kubebuilder:object:root=true
210298// +kubebuilder:subresource:status
211299// +kubebuilder:resource:path=cloudscaleclusters,scope=Namespaced,categories=cluster-api
0 commit comments