Skip to content
26 changes: 19 additions & 7 deletions api/v1alpha1/stackitcluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@
// +required
CredentialsSecretRef corev1.SecretReference `json:"credentialsSecretRef"`

// network references the existing STACKIT network the cluster will use.
// For MVP the network must already exist.
// +required
Network StackitClusterNetworkSpec `json:"network"`
// network configures the STACKIT network the cluster will use.
// omiting the ID will create a managed dedicated network

Check failure on line 51 in api/v1alpha1/stackitcluster_types.go

View workflow job for this annotation

GitHub Actions / Run on Ubuntu

`omiting` is a misspelling of `omitting` (misspell)
// +optional
Network StackitClusterNetworkSpec `json:"network,omitempty"`

// apiServerLoadBalancer configures the load balancer in front of the
// Kubernetes API server.
Expand All @@ -76,10 +76,10 @@
// StackitClusterNetworkSpec references an existing STACKIT network.
type StackitClusterNetworkSpec struct {
// id is the STACKIT network ID.
// +required
// +kubebuilder:validation:MinLength=1
// Omitting the network ID provisions a dedicated STACKIT network for the cluster, tag it as managed (owned) and stores the ID
// +kubebuilder:validation:Pattern=`^[0-9a-fA-F-]{36}$`
ID string `json:"id"`
// +optional
ID string `json:"id,omitempty"`
}

// StackitAPIServerLoadBalancerSpec configures an optional load balancer for
Expand Down Expand Up @@ -160,6 +160,10 @@
// +optional
Initialization StackitClusterInitializationStatus `json:"initialization,omitempty"`

// network stores observed provider-managed network resources.
// +optional
Network StackitNetworkStatus `json:"network,omitempty"`

// apiServerEndpoint is the endpoint the Kubernetes API server is reachable at.
// +optional
APIServerEndpoint clusterv1.APIEndpoint `json:"apiServerEndpoint,omitempty,omitzero"`
Expand Down Expand Up @@ -217,6 +221,14 @@
CloudInitHash string `json:"cloudInitHash,omitempty"`
}

// network contains status of a STACKIT network created by CAPSTK
type StackitNetworkStatus struct {
// id is the STACKIT network ID
// +optional
ID string `json:"id,omitempty"`
Managed bool `json:"managed,omitempty"`
}

// Condition types maintained by the StackitClusterReconciler.
const (
ClusterReadyCondition = "Ready"
Expand Down
9 changes: 4 additions & 5 deletions api/v1alpha1/stackitmachine_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ type StackitMachineSpec struct {
RootVolume StackitRootVolumeSpec `json:"rootVolume,omitempty"`

// network references the STACKIT network the VM should be attached to.
// +required
Network StackitMachineNetworkSpec `json:"network"`
// +optional
Network StackitMachineNetworkSpec `json:"network,omitempty"`

// securityGroups is a list of STACKIT security group IDs applied to the VM.
// +optional
Expand Down Expand Up @@ -101,10 +101,9 @@ type StackitRootVolumeSpec struct {
// StackitMachineNetworkSpec references an existing STACKIT network.
type StackitMachineNetworkSpec struct {
// id is the STACKIT network ID.
// +required
// +kubebuilder:validation:MinLength=1
// +optional
// +kubebuilder:validation:Pattern=`^[0-9a-fA-F-]{36}$`
ID string `json:"id"`
ID string `json:"id,omitempty"`
}

// StackitMachineStatus defines the observed state of StackitMachine.
Expand Down
16 changes: 16 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions cloud/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ import "context"
// - be idempotent (CreateServer/EnsureAPIServerLoadBalancer must not produce
// duplicates when a resource with matching tags already exists)
type Client interface {
GetNetwork(ctx context.Context, id string) (*Network, error)

CreateNetwork(ctx context.Context, input CreateNetworkInput) (*Network, error)

DeleteNetwork(ctx context.Context, id string) error

GetServer(ctx context.Context, id string) (*Server, error)

FindServerByTags(ctx context.Context, tags map[string]string) (*Server, error)
Expand All @@ -33,8 +39,6 @@ type Client interface {

DeleteServer(ctx context.Context, id string) error

GetNetwork(ctx context.Context, id string) (*Network, error)

EnsureBastion(ctx context.Context, input BastionInput) (*Bastion, error)

DeleteBastion(ctx context.Context, input BastionInput, status Bastion) error
Expand Down
8 changes: 8 additions & 0 deletions cloud/fake/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,14 @@ func (c *Client) GetNetwork(_ context.Context, id string) (*cloud.Network, error
return &out, nil
}

func (c *Client) CreateNetwork(_ context.Context, _ cloud.CreateNetworkInput) (*cloud.Network, error) {
return nil, nil // TODO
}

func (c *Client) DeleteNetwork(_ context.Context, _ string) error {
return nil // TODO
}

func (c *Client) EnsureBastion(_ context.Context, input cloud.BastionInput) (*cloud.Bastion, error) {
c.mu.Lock()
defer c.mu.Unlock()
Expand Down
12 changes: 12 additions & 0 deletions cloud/sdk_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,18 @@ func (c *SDKClient) GetNetwork(ctx context.Context, id string) (*Network, error)
}, nil
}

func (c *SDKClient) CreateNetwork(ctx context.Context, input CreateNetworkInput) (*Network, error) {
if input.Name == "" {
return nil, fmt.Errorf("%w: network name is required", ErrInvalidInput)
}

return nil, nil // TODO
}

func (c *SDKClient) DeleteNetwork(ctx context.Context, id string) error {
return nil // TODO
}

func (c *SDKClient) EnsureBastion(ctx context.Context, input BastionInput) (*Bastion, error) {
if input.Name == "" || input.NetworkID == "" || input.ImageID == "" ||
input.MachineType == "" || input.SSHKeyName == "" {
Expand Down
34 changes: 34 additions & 0 deletions cloud/services/network/network.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
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
*/

package network

import (
infrav1 "github.com/stackitcloud/cluster-api-provider-stackit/api/v1alpha1"
"github.com/stackitcloud/cluster-api-provider-stackit/cloud"
"github.com/stackitcloud/cluster-api-provider-stackit/util"
)

const (
// DefaultNetworkPrefix defines the default CIDR allocated for a managed cluster network.
DefaultNetworkPrefix = "10.0.0.0/24"
)

func NetworkTags(stackitCluster *infrav1.StackitCluster) map[string]string {
return util.ClusterTags(stackitCluster.Name, stackitCluster.Namespace, stackitCluster.Spec.AdditionalLabels)
}

func Input(stackitCluster *infrav1.StackitCluster) cloud.CreateNetworkInput {
return cloud.CreateNetworkInput{
Name: stackitCluster.Name + "-network",
IPv4Prefix: DefaultNetworkPrefix,
Tags: NetworkTags(stackitCluster),
}
}
6 changes: 6 additions & 0 deletions cloud/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ type Bastion struct {
SecurityGroupID string
}

type CreateNetworkInput struct {
Name string
IPv4Prefix string
Tags map[string]string
}

// CreateServerInput holds all parameters required to create a new VM.
type CreateServerInput struct {
Name string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,16 +200,15 @@ spec:
x-kubernetes-map-type: atomic
network:
description: |-
network references the existing STACKIT network the cluster will use.
For MVP the network must already exist.
network configures the STACKIT network the cluster will use.
ommiting the ID will create a managed dedicated network
properties:
id:
description: id is the STACKIT network ID.
minLength: 1
description: |-
id is the STACKIT network ID.
Omitting the network ID provisions a dedicated STACKIT network for the cluster, tag it as managed (owned) and stores the ID
pattern: ^[0-9a-fA-F-]{36}$
type: string
required:
- id
type: object
projectID:
description: projectID is the STACKIT project that owns the cluster
Expand All @@ -225,7 +224,6 @@ spec:
type: string
required:
- credentialsSecretRef
- network
- projectID
- region
type: object
Expand Down Expand Up @@ -396,6 +394,15 @@ spec:
is provisioned.
type: boolean
type: object
network:
description: network stores observed provider-managed network resources.
properties:
id:
description: id is the STACKIT network ID
type: string
managed:
type: boolean
type: object
ready:
description: ready indicates that the infrastructure required for
the cluster is ready.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,16 +236,15 @@ spec:
x-kubernetes-map-type: atomic
network:
description: |-
network references the existing STACKIT network the cluster will use.
For MVP the network must already exist.
network configures the STACKIT network the cluster will use.
ommiting the ID will create a managed dedicated network
properties:
id:
description: id is the STACKIT network ID.
minLength: 1
description: |-
id is the STACKIT network ID.
Omitting the network ID provisions a dedicated STACKIT network for the cluster, tag it as managed (owned) and stores the ID
pattern: ^[0-9a-fA-F-]{36}$
type: string
required:
- id
type: object
projectID:
description: projectID is the STACKIT project that owns the
Expand All @@ -261,7 +260,6 @@ spec:
type: string
required:
- credentialsSecretRef
- network
- projectID
- region
type: object
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,8 @@ spec:
properties:
id:
description: id is the STACKIT network ID.
minLength: 1
pattern: ^[0-9a-fA-F-]{36}$
type: string
required:
- id
type: object
providerID:
description: |-
Expand Down Expand Up @@ -142,7 +139,6 @@ spec:
required:
- imageID
- machineType
- network
type: object
status:
description: status defines the observed state of StackitMachine
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,8 @@ spec:
properties:
id:
description: id is the STACKIT network ID.
minLength: 1
pattern: ^[0-9a-fA-F-]{36}$
type: string
required:
- id
type: object
providerID:
description: |-
Expand Down Expand Up @@ -175,7 +172,6 @@ spec:
required:
- imageID
- machineType
- network
type: object
required:
- spec
Expand Down
48 changes: 27 additions & 21 deletions controller/stackitcluster_infrastructure.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,27 +89,32 @@
clusterScope.SetFailureDomains(failureDomainsFromZones(stackitCluster.Spec.Region, zones))

network, err := cloudClient.GetNetwork(ctx, stackitCluster.Spec.Network.ID)
if err != nil {
log.Error(err, "Failed to get network",
"networkID", stackitCluster.Spec.Network.ID,
"region", stackitCluster.Spec.Region,
)
record.Warnf(
stackitCluster, "FailedGetNetwork",
"Failed to get network %s in region %s: %v",
stackitCluster.Spec.Network.ID, stackitCluster.Spec.Region, err,
)
stackitCluster.Status.Ready = false
return util.CloudFailureResult(
&stackitCluster.Status.Conditions,
stackitCluster.Generation,
"NetworkNotFound",
err,
retryableErrorRequeueAfter,
false,
infrav1.ClusterNetworkReadyCondition,
infrav1.ClusterReadyCondition,
)
if stackitCluster.Spec.Network.ID != "" {
if err != nil {
log.Error(err, "Failed to get network",
"networkID", stackitCluster.Spec.Network.ID,
"region", stackitCluster.Spec.Region,
)
record.Warnf(
stackitCluster, "FailedGetNetwork",
"Failed to get network %s in region %s: %v",
stackitCluster.Spec.Network.ID, stackitCluster.Spec.Region, err,
)
stackitCluster.Status.Ready = false
return util.CloudFailureResult(
&stackitCluster.Status.Conditions,
stackitCluster.Generation,
"NetworkNotFound",
err,
retryableErrorRequeueAfter,
false,
infrav1.ClusterNetworkReadyCondition,
infrav1.ClusterReadyCondition,
)
}
} else {

Check failure on line 115 in controller/stackitcluster_infrastructure.go

View workflow job for this annotation

GitHub Actions / Run on Ubuntu

SA9003: empty branch (staticcheck)
// TODO: implement cloudClient.EnsureNetwork

}
clusterScope.SetConditions(
metav1.ConditionTrue,
Expand Down Expand Up @@ -453,6 +458,7 @@
clusterScope.ClearBastionStatus()
record.Event(stackitCluster, "BastionDeleted", "Deleted bastion")
}
// TODO: implement cloudClient.deleteNetwork - "if managed" etc.
controllerutil.RemoveFinalizer(stackitCluster, infrav1.ClusterFinalizer)
return ctrl.Result{}, nil
}
Loading
Loading