@@ -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,27 @@ 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+ // The floating IP is assigned to the load balancer when enabled, or to a
76+ // control plane server when the load balancer is disabled.
77+ // +optional
78+ FloatingIP * FloatingIPSpec `json:"floatingIP,omitempty"`
5979}
6080
6181// CloudscaleCredentialsReference references a Secret containing the API token.
@@ -69,28 +89,43 @@ type CloudscaleCredentialsReference struct {
6989 Namespace string `json:"namespace,omitempty"`
7090}
7191
72- // NetworkSpec defines the network configuration.
92+ // NetworkSpec defines a private network for the cluster.
93+ // Exactly one of UUID or CIDR must be specified.
7394type NetworkSpec struct {
74- // CIDR is the CIDR block for the private network subnet.
75- // +kubebuilder:default="10.0.0.0/24"
95+ // Name identifies this network within the cluster.
96+ // Used to reference this network from machine interface specs and LB config.
97+ // +kubebuilder:validation:Required
98+ // +kubebuilder:validation:Pattern=`^[a-z0-9]([a-z0-9-]*[a-z0-9])?$`
99+ // +kubebuilder:validation:MaxLength=63
100+ Name string `json:"name"`
101+
102+ // UUID references an existing cloudscale.ch network (BYO).
103+ // The network is not deleted on cluster teardown.
104+ // Mutually exclusive with CIDR.
105+ // +optional
106+ UUID string `json:"uuid,omitempty"`
107+
108+ // CIDR defines the subnet for a controller-managed network.
109+ // The network and subnet are created and deleted by CAPCS.
110+ // Mutually exclusive with UUID.
76111 // +optional
77112 CIDR string `json:"cidr,omitempty"`
78113
79114 // 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 .
115+ // Only applicable when CIDR is set (managed network).
116+ // By default, no gateway is configured on the subnet. This ensures
117+ // that outbound internet traffic uses the public network interface .
83118 // Set this to a specific IP address (e.g., "10.0.0.1") only if you have configured
84119 // a NAT gateway or similar infrastructure on the private network.
85120 // +optional
86- GatewayAddress * string `json:"gatewayAddress,omitempty"`
121+ GatewayAddress string `json:"gatewayAddress,omitempty"`
87122}
88123
89124// LoadBalancerSpec defines the load balancer configuration for the control plane.
90125type LoadBalancerSpec struct {
91126 // Enabled controls whether a load balancer is created for the control plane.
92127 // Set to false for external control planes (e.g., hosted control plane) where the endpoint
93- // is provided externally.
128+ // is provided externally, or when using a floating IP without a load balancer .
94129 // +kubebuilder:default=true
95130 // +optional
96131 Enabled * bool `json:"enabled,omitempty"`
@@ -113,6 +148,17 @@ type LoadBalancerSpec struct {
113148 // +optional
114149 APIServerPort int32 `json:"apiServerPort,omitempty"`
115150
151+ // Network places the LB VIP on a private network (internal LB).
152+ // References spec.networks[].name. Omit for a public LB.
153+ // +optional
154+ Network string `json:"network,omitempty"`
155+
156+ // IPFamily specifies the IP family for the LB VIP address(es).
157+ // +kubebuilder:validation:Enum=IPv4;IPv6;DualStack
158+ // +kubebuilder:default=DualStack
159+ // +optional
160+ IPFamily IPFamily `json:"ipFamily,omitempty"`
161+
116162 // HealthMonitor configures the load balancer health monitor.
117163 // +optional
118164 HealthMonitor HealthMonitorSpec `json:"healthMonitor,omitempty"`
@@ -149,19 +195,40 @@ type HealthMonitorSpec struct {
149195 DownThreshold int `json:"downThreshold,omitempty"`
150196}
151197
198+ // FloatingIPSpec configures a floating IP for the control plane endpoint.
199+ // Exactly one of IPFamily or UUID must be specified.
200+ type FloatingIPSpec struct {
201+ // IPFamily creates a new floating IP with this IP version.
202+ // A floating IP is a single address, so DualStack is not valid here.
203+ // Mutually exclusive with UUID.
204+ // +kubebuilder:validation:Enum=IPv4;IPv6
205+ // +optional
206+ IPFamily * IPFamily `json:"ipFamily,omitempty"`
207+
208+ // UUID references an existing floating IP (BYO).
209+ // The floating IP is not deleted on cluster teardown.
210+ // Mutually exclusive with IPFamily.
211+ // +optional
212+ UUID string `json:"uuid,omitempty"`
213+ }
214+
152215// CloudscaleClusterStatus defines the observed state of CloudscaleCluster.
153216type CloudscaleClusterStatus struct {
154217 // Initialization contains v1beta2 initialization tracking.
155218 // +optional
156219 Initialization * ClusterInitializationStatus `json:"initialization,omitempty"`
157220
158- // NetworkID is the cloudscale.ch network UUID.
221+ // Networks track the status of each network defined in spec.networks.
222+ // +listType=map
223+ // +listMapKey=name
159224 // +optional
160- NetworkID string `json:"networkID ,omitempty"`
225+ Networks [] NetworkStatus `json:"networks ,omitempty"`
161226
162- // SubnetID is the cloudscale.ch subnet UUID.
227+ // FloatingIPHREF is the cloudscale.ch floating IP HREF.
228+ // The cloudscale API identifies floating IPs by HREF (e.g. "/v1/floating-ips/192.0.2.0/32"),
229+ // not by UUID like other resources.
163230 // +optional
164- SubnetID string `json:"subnetID ,omitempty"`
231+ FloatingIPHREF string `json:"floatingIPHREF ,omitempty"`
165232
166233 // LoadBalancerID is the cloudscale.ch load balancer UUID.
167234 // +optional
@@ -184,20 +251,30 @@ type CloudscaleClusterStatus struct {
184251 LoadBalancerMemberIDs []string `json:"loadBalancerMemberIDs,omitempty"`
185252
186253 // 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.
195254 // +listType=map
196255 // +listMapKey=type
197256 // +optional
198257 Conditions []metav1.Condition `json:"conditions,omitempty"`
199258}
200259
260+ // NetworkStatus tracks the provisioned state of a single network.
261+ type NetworkStatus struct {
262+ // Name matches the logical name from spec.networks[].name.
263+ Name string `json:"name"`
264+
265+ // NetworkID is the cloudscale.ch network UUID.
266+ // +optional
267+ NetworkID string `json:"networkID,omitempty"`
268+
269+ // SubnetID is the cloudscale.ch subnet UUID.
270+ // +optional
271+ SubnetID string `json:"subnetID,omitempty"`
272+
273+ // Managed indicates whether CAPCS manages this network's lifecycle.
274+ // false for BYO networks (referenced by UUID), true for CAPCS-created networks (defined by CIDR).
275+ Managed bool `json:"managed"`
276+ }
277+
201278// ClusterInitializationStatus contains v1beta2 initialization tracking for CloudscaleCluster.
202279type ClusterInitializationStatus struct {
203280 // Provisioned indicates that all cluster infrastructure has been provisioned.
@@ -206,6 +283,16 @@ type ClusterInitializationStatus struct {
206283 Provisioned * bool `json:"provisioned,omitempty"`
207284}
208285
286+ // GetNetworkStatus returns the NetworkStatus for the given network name, or nil if not found.
287+ func (s * CloudscaleClusterStatus ) GetNetworkStatus (name string ) * NetworkStatus {
288+ for i := range s .Networks {
289+ if s .Networks [i ].Name == name {
290+ return & s .Networks [i ]
291+ }
292+ }
293+ return nil
294+ }
295+
209296// +kubebuilder:object:root=true
210297// +kubebuilder:subresource:status
211298// +kubebuilder:resource:path=cloudscaleclusters,scope=Namespaced,categories=cluster-api
0 commit comments