diff --git a/docs/data-sources/ske_cluster.md b/docs/data-sources/ske_cluster.md
index b2a46ee9d..48a19458d 100644
--- a/docs/data-sources/ske_cluster.md
+++ b/docs/data-sources/ske_cluster.md
@@ -155,9 +155,27 @@ Read-Only:
Read-Only:
+- `cni` (Attributes) Container Network Interface (CNI) of the cluster. (see [below for nested schema](#nestedatt--network--cni))
- `control_plane` (Attributes) Control plane for the cluster. (see [below for nested schema](#nestedatt--network--control_plane))
- `id` (String) ID of the STACKIT Network Area (SNA) network into which the cluster will be deployed.
+
+### Nested Schema for `network.cni`
+
+Read-Only:
+
+- `calico` (Attributes) Set if Calico is used as CNI. (see [below for nested schema](#nestedatt--network--cni--calico))
+- `cilium` (Attributes) Set if Cilium is used as CNI. (see [below for nested schema](#nestedatt--network--cni--cilium))
+
+
+### Nested Schema for `network.cni.calico`
+
+
+
+### Nested Schema for `network.cni.cilium`
+
+
+
### Nested Schema for `network.control_plane`
diff --git a/docs/resources/ske_cluster.md b/docs/resources/ske_cluster.md
index 58c02c079..f9aa2c6f6 100644
--- a/docs/resources/ske_cluster.md
+++ b/docs/resources/ske_cluster.md
@@ -38,6 +38,9 @@ resource "stackit_ske_cluster" "example" {
control_plane = {
access_scope = "PUBLIC"
}
+ cni = {
+ cilium = {}
+ }
}
# Cluster audit log forwarding to a Telemetry Router.
# Private preview: only configurable for enabled accounts.
@@ -240,9 +243,27 @@ Optional:
Optional:
+- `cni` (Attributes) Container Network Interface (CNI) of the cluster. Exactly one of `calico` or `cilium` can be set. Defaults to Cilium. The field is immutable! (see [below for nested schema](#nestedatt--network--cni))
- `control_plane` (Attributes) Control plane for the cluster. (see [below for nested schema](#nestedatt--network--control_plane))
- `id` (String) ID of the STACKIT Network Area (SNA) network into which the cluster will be deployed.
+
+### Nested Schema for `network.cni`
+
+Optional:
+
+- `calico` (Attributes) Use Calico as CNI. Set to `{}` to enable. (see [below for nested schema](#nestedatt--network--cni--calico))
+- `cilium` (Attributes) Use Cilium as CNI. Set to `{}` to enable. (see [below for nested schema](#nestedatt--network--cni--cilium))
+
+
+### Nested Schema for `network.cni.calico`
+
+
+
+### Nested Schema for `network.cni.cilium`
+
+
+
### Nested Schema for `network.control_plane`
diff --git a/examples/resources/stackit_ske_cluster/resource.tf b/examples/resources/stackit_ske_cluster/resource.tf
index 3cb37acd6..75949a5c6 100644
--- a/examples/resources/stackit_ske_cluster/resource.tf
+++ b/examples/resources/stackit_ske_cluster/resource.tf
@@ -20,6 +20,9 @@ resource "stackit_ske_cluster" "example" {
control_plane = {
access_scope = "PUBLIC"
}
+ cni = {
+ cilium = {}
+ }
}
# Cluster audit log forwarding to a Telemetry Router.
# Private preview: only configurable for enabled accounts.
diff --git a/stackit/internal/services/ske/cluster/datasource.go b/stackit/internal/services/ske/cluster/datasource.go
index 86794bd1a..21e8ce51b 100644
--- a/stackit/internal/services/ske/cluster/datasource.go
+++ b/stackit/internal/services/ske/cluster/datasource.go
@@ -238,6 +238,22 @@ func (r *clusterDataSource) Schema(_ context.Context, _ datasource.SchemaRequest
},
},
},
+ "cni": schema.SingleNestedAttribute{
+ Description: "Container Network Interface (CNI) of the cluster.",
+ Computed: true,
+ Attributes: map[string]schema.Attribute{
+ "calico": schema.SingleNestedAttribute{
+ Description: "Set if Calico is used as CNI.",
+ Computed: true,
+ Attributes: map[string]schema.Attribute{},
+ },
+ "cilium": schema.SingleNestedAttribute{
+ Description: "Set if Cilium is used as CNI.",
+ Computed: true,
+ Attributes: map[string]schema.Attribute{},
+ },
+ },
+ },
},
},
diff --git a/stackit/internal/services/ske/cluster/resource.go b/stackit/internal/services/ske/cluster/resource.go
index 3130f8065..9abbab035 100644
--- a/stackit/internal/services/ske/cluster/resource.go
+++ b/stackit/internal/services/ske/cluster/resource.go
@@ -179,14 +179,31 @@ var maintenanceTypes = map[string]attr.Type{
type network struct {
ID types.String `tfsdk:"id"`
ControlPlane types.Object `tfsdk:"control_plane"`
+ CNI types.Object `tfsdk:"cni"`
}
// Types corresponding to network
var networkTypes = map[string]attr.Type{
"id": basetypes.StringType{},
"control_plane": types.ObjectType{AttrTypes: controlPlaneTypes},
+ "cni": types.ObjectType{AttrTypes: cniTypes},
}
+// Struct corresponding to network.CNI
+type cni struct {
+ Calico types.Object `tfsdk:"calico"`
+ Cilium types.Object `tfsdk:"cilium"`
+}
+
+// Types corresponding to cni
+var cniTypes = map[string]attr.Type{
+ "calico": types.ObjectType{AttrTypes: map[string]attr.Type{}},
+ "cilium": types.ObjectType{AttrTypes: map[string]attr.Type{}},
+}
+
+// The Cilium CNI is not modelled by the SDK yet and is sent as an additional property.
+const ciliumCNIKey = "cilium"
+
type controlPlane struct {
AccessScope types.String `tfsdk:"access_scope"`
}
@@ -752,6 +769,36 @@ func (r *clusterResource) Schema(_ context.Context, _ resource.SchemaRequest, re
},
},
},
+ "cni": schema.SingleNestedAttribute{
+ Description: "Container Network Interface (CNI) of the cluster. Exactly one of `calico` or `cilium` can be set. Defaults to Cilium. The field is immutable!",
+ Optional: true,
+ Computed: true,
+ Default: objectdefault.StaticValue(types.ObjectValueMust(cniTypes, map[string]attr.Value{
+ "calico": types.ObjectNull(map[string]attr.Type{}),
+ "cilium": types.ObjectValueMust(map[string]attr.Type{}, map[string]attr.Value{}),
+ })),
+ PlanModifiers: []planmodifier.Object{
+ objectplanmodifier.RequiresReplace(),
+ },
+ Attributes: map[string]schema.Attribute{
+ "calico": schema.SingleNestedAttribute{
+ Description: "Use Calico as CNI. Set to `{}` to enable.",
+ Optional: true,
+ Validators: []validator.Object{
+ objectvalidator.ExactlyOneOf(path.MatchRelative().AtParent().AtName("cilium")),
+ },
+ Attributes: map[string]schema.Attribute{},
+ },
+ "cilium": schema.SingleNestedAttribute{
+ Description: "Use Cilium as CNI. Set to `{}` to enable.",
+ Optional: true,
+ Validators: []validator.Object{
+ objectvalidator.ExactlyOneOf(path.MatchRelative().AtParent().AtName("calico")),
+ },
+ Attributes: map[string]schema.Attribute{},
+ },
+ },
+ },
},
},
"hibernations": schema.ListNestedAttribute{
@@ -1556,7 +1603,7 @@ func toMaintenancePayload(ctx context.Context, m *Model) (*ske.Maintenance, erro
func toNetworkPayload(ctx context.Context, m *Model) (*ske.Network, error) {
if m.Network.IsNull() || m.Network.IsUnknown() {
- return nil, nil
+ return &ske.Network{Cni: defaultCNIPayload()}, nil
}
network := network{}
@@ -1577,12 +1624,43 @@ func toNetworkPayload(ctx context.Context, m *Model) (*ske.Network, error) {
}
}
+ networkCNI, err := toCNIPayload(ctx, network.CNI)
+ if err != nil {
+ return nil, err
+ }
+
return &ske.Network{
Id: conversion.StringValueToPointer(network.ID),
ControlPlane: networkControlPlane,
+ Cni: networkCNI,
}, nil
}
+func defaultCNIPayload() *ske.CNI {
+ return &ske.CNI{AdditionalProperties: map[string]interface{}{ciliumCNIKey: map[string]interface{}{}}}
+}
+
+func toCNIPayload(ctx context.Context, o types.Object) (*ske.CNI, error) {
+ if utils.IsUndefined(o) {
+ return defaultCNIPayload(), nil
+ }
+
+ cniModel := cni{}
+ diags := o.As(ctx, &cniModel, basetypes.ObjectAsOptions{})
+ if diags.HasError() {
+ return nil, fmt.Errorf("converting network cni: %w", core.DiagsToError(diags))
+ }
+
+ payload := &ske.CNI{}
+ switch {
+ case !utils.IsUndefined(cniModel.Calico):
+ payload.Calico = map[string]interface{}{}
+ case !utils.IsUndefined(cniModel.Cilium):
+ payload.AdditionalProperties = map[string]interface{}{ciliumCNIKey: map[string]interface{}{}}
+ }
+ return payload, nil
+}
+
func mapFields(ctx context.Context, cl *ske.Cluster, m *Model, region string) error {
if cl == nil {
return fmt.Errorf("response input is nil")
@@ -1914,9 +1992,15 @@ func mapNetwork(cl *ske.Cluster, m *Model) error {
}
}
+ networkCNI, err := mapCNI(cl.Network.Cni)
+ if err != nil {
+ return err
+ }
+
networkValues := map[string]attr.Value{
"id": id,
"control_plane": networkControlPlane,
+ "cni": networkCNI,
}
networkObject, diags := types.ObjectValue(networkTypes, networkValues)
if diags.HasError() {
@@ -1926,6 +2010,31 @@ func mapNetwork(cl *ske.Cluster, m *Model) error {
return nil
}
+func mapCNI(c *ske.CNI) (types.Object, error) {
+ if c == nil {
+ return types.ObjectNull(cniTypes), nil
+ }
+
+ emptyObject := types.ObjectValueMust(map[string]attr.Type{}, map[string]attr.Value{})
+ calico := types.ObjectNull(cniTypes["calico"].(types.ObjectType).AttrTypes)
+ cilium := types.ObjectNull(cniTypes["cilium"].(types.ObjectType).AttrTypes)
+ if c.Calico != nil {
+ calico = emptyObject
+ }
+ if _, ok := c.AdditionalProperties[ciliumCNIKey]; ok {
+ cilium = emptyObject
+ }
+
+ obj, diags := types.ObjectValue(cniTypes, map[string]attr.Value{
+ "calico": calico,
+ "cilium": cilium,
+ })
+ if diags.HasError() {
+ return types.ObjectNull(cniTypes), fmt.Errorf("create network cni object: %w", core.DiagsToError(diags))
+ }
+ return obj, nil
+}
+
func getMaintenanceTimes(ctx context.Context, cl *ske.Cluster, m *Model) (startTime, endTime string, err error) {
var startTimeAPI, endTimeAPI time.Time
if cl.Maintenance.TimeWindow != nil {
diff --git a/stackit/internal/services/ske/cluster/resource_test.go b/stackit/internal/services/ske/cluster/resource_test.go
index 8e7b4f7f7..e2591a119 100644
--- a/stackit/internal/services/ske/cluster/resource_test.go
+++ b/stackit/internal/services/ske/cluster/resource_test.go
@@ -243,7 +243,8 @@ func TestMapFields(t *testing.T) {
"end": types.StringValue("13:14:15Z"),
}),
Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{
- "id": types.StringValue("nid"),
+ "cni": types.ObjectNull(cniTypes),
+ "id": types.StringValue("nid"),
"control_plane": types.ObjectValueMust(controlPlaneTypes, map[string]attr.Value{
"access_scope": types.StringValue("SNA"),
}),
@@ -770,7 +771,8 @@ func TestMapFields(t *testing.T) {
"end": types.StringValue("13:14:15Z"),
}),
Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{
- "id": types.StringValue("nid"),
+ "cni": types.ObjectNull(cniTypes),
+ "id": types.StringValue("nid"),
"control_plane": types.ObjectValueMust(controlPlaneTypes, map[string]attr.Value{
"access_scope": types.StringValue("SNA"),
}),
@@ -2365,7 +2367,8 @@ func TestToNetworkPayload(t *testing.T) {
ProjectId: types.StringValue("pid"),
Name: types.StringValue("name"),
Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{
- "id": types.StringValue("nid"),
+ "cni": types.ObjectNull(cniTypes),
+ "id": types.StringValue("nid"),
"control_plane": types.ObjectValueMust(controlPlaneTypes, map[string]attr.Value{
"access_scope": types.StringValue("SNA"),
}),
@@ -2376,6 +2379,7 @@ func TestToNetworkPayload(t *testing.T) {
ControlPlane: &ske.V2ControlPlaneNetwork{
AccessScope: ske.ACCESSSCOPE_SNA.Ptr(),
},
+ Cni: &ske.CNI{AdditionalProperties: map[string]interface{}{"cilium": map[string]interface{}{}}},
},
true,
},
@@ -2385,11 +2389,12 @@ func TestToNetworkPayload(t *testing.T) {
ProjectId: types.StringValue("pid"),
Name: types.StringValue("name"),
Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{
+ "cni": types.ObjectNull(cniTypes),
"id": types.StringNull(),
"control_plane": types.ObjectNull(controlPlaneTypes),
}),
},
- &ske.Network{},
+ &ske.Network{Cni: &ske.CNI{AdditionalProperties: map[string]interface{}{"cilium": map[string]interface{}{}}}},
true,
},
{
@@ -2398,6 +2403,7 @@ func TestToNetworkPayload(t *testing.T) {
ProjectId: types.StringValue("pid"),
Name: types.StringValue("name"),
Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{
+ "cni": types.ObjectNull(cniTypes),
"id": types.StringValue("nid"),
"control_plane": types.ObjectNull(controlPlaneTypes),
}),
@@ -2405,6 +2411,7 @@ func TestToNetworkPayload(t *testing.T) {
&ske.Network{
Id: new("nid"),
ControlPlane: nil,
+ Cni: &ske.CNI{AdditionalProperties: map[string]interface{}{"cilium": map[string]interface{}{}}},
},
true,
},
@@ -2415,7 +2422,24 @@ func TestToNetworkPayload(t *testing.T) {
Name: types.StringValue("name"),
Network: types.ObjectNull(networkTypes),
},
- nil,
+ &ske.Network{Cni: &ske.CNI{AdditionalProperties: map[string]interface{}{"cilium": map[string]interface{}{}}}},
+ true,
+ },
+ {
+ "calico",
+ &Model{
+ ProjectId: types.StringValue("pid"),
+ Name: types.StringValue("name"),
+ Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{
+ "id": types.StringNull(),
+ "control_plane": types.ObjectNull(controlPlaneTypes),
+ "cni": types.ObjectValueMust(cniTypes, map[string]attr.Value{
+ "calico": types.ObjectValueMust(map[string]attr.Type{}, map[string]attr.Value{}),
+ "cilium": types.ObjectNull(map[string]attr.Type{}),
+ }),
+ }),
+ },
+ &ske.Network{Cni: &ske.CNI{Calico: map[string]interface{}{}}},
true,
},
}
diff --git a/stackit/internal/services/ske/ske_test.go b/stackit/internal/services/ske/ske_test.go
index 7c78e3d11..7a3f725fe 100644
--- a/stackit/internal/services/ske/ske_test.go
+++ b/stackit/internal/services/ske/ske_test.go
@@ -229,6 +229,9 @@ resource "stackit_ske_cluster" "cluster" {
ControlPlane: new(ske.V2ControlPlaneNetwork{
AccessScope: new(ske.ACCESSSCOPE_PUBLIC),
}),
+ Cni: &ske.CNI{
+ AdditionalProperties: map[string]interface{}{"cilium": map[string]interface{}{}},
+ },
},
Maintenance: new(ske.Maintenance{
AutoUpdate: &ske.MaintenanceAutoUpdate{
diff --git a/stackit/internal/services/ske/utils/util.go b/stackit/internal/services/ske/utils/util.go
index 22c997bd4..3ae4f5732 100644
--- a/stackit/internal/services/ske/utils/util.go
+++ b/stackit/internal/services/ske/utils/util.go
@@ -5,7 +5,7 @@ import (
)
func IsEmptyNetwork(network *ske.Network) bool {
- if !network.HasId() && !network.HasControlPlane() {
+ if !network.HasId() && !network.HasControlPlane() && !network.HasCni() {
return true
}
return false