diff --git a/docs/data-sources/ske_cluster.md b/docs/data-sources/ske_cluster.md index 755198d3c..be663d6d9 100644 --- a/docs/data-sources/ske_cluster.md +++ b/docs/data-sources/ske_cluster.md @@ -117,8 +117,17 @@ Read-Only: Read-Only: +- `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.control_plane` + +Read-Only: + +- `access_scope` (String) Access scope of the control plane. It defines if the Kubernetes control plane is public or only available inside a STACKIT Network Area.Possible values are: `PUBLIC`, `SNA`. The field is immutable! + + ### Nested Schema for `node_pools` diff --git a/docs/resources/ske_cluster.md b/docs/resources/ske_cluster.md index 44616c9be..2dcccf85a 100644 --- a/docs/resources/ske_cluster.md +++ b/docs/resources/ske_cluster.md @@ -39,6 +39,11 @@ resource "stackit_ske_cluster" "example" { start = "01:00:00Z" end = "02:00:00Z" } + network = { + control_plane = { + access_scope = "PUBLIC" + } + } } # Only use the import statement, if you want to import an existing ske cluster @@ -204,4 +209,12 @@ Optional: Optional: +- `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.control_plane` + +Optional: + +- `access_scope` (String) Access scope of the control plane. It defines if the Kubernetes control plane is public or only available inside a STACKIT Network Area.Possible values are: `PUBLIC`, `SNA`. The field is immutable! diff --git a/examples/resources/stackit_ske_cluster/resource.tf b/examples/resources/stackit_ske_cluster/resource.tf index e87958fd2..07ac39f38 100644 --- a/examples/resources/stackit_ske_cluster/resource.tf +++ b/examples/resources/stackit_ske_cluster/resource.tf @@ -21,6 +21,11 @@ resource "stackit_ske_cluster" "example" { start = "01:00:00Z" end = "02:00:00Z" } + network = { + control_plane = { + access_scope = "PUBLIC" + } + } } # Only use the import statement, if you want to import an existing ske cluster diff --git a/stackit/internal/services/ske/cluster/datasource.go b/stackit/internal/services/ske/cluster/datasource.go index 2a7feca9c..9a379a1c3 100644 --- a/stackit/internal/services/ske/cluster/datasource.go +++ b/stackit/internal/services/ske/cluster/datasource.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" + sdkUtils "github.com/stackitcloud/stackit-sdk-go/core/utils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" skeUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/ske/utils" @@ -223,6 +224,16 @@ func (r *clusterDataSource) Schema(_ context.Context, _ datasource.SchemaRequest validate.UUID(), }, }, + "control_plane": schema.SingleNestedAttribute{ + Description: "Control plane for the cluster.", + Computed: true, + Attributes: map[string]schema.Attribute{ + "access_scope": schema.StringAttribute{ + Description: "Access scope of the control plane. It defines if the Kubernetes control plane is public or only available inside a STACKIT Network Area." + utils.FormatPossibleValues(sdkUtils.EnumSliceToStringSlice(ske.AllowedAccessScopeEnumValues)...) + " The field is immutable!", + Computed: true, + }, + }, + }, }, }, diff --git a/stackit/internal/services/ske/cluster/resource.go b/stackit/internal/services/ske/cluster/resource.go index a4bb35216..735fe8aa9 100644 --- a/stackit/internal/services/ske/cluster/resource.go +++ b/stackit/internal/services/ske/cluster/resource.go @@ -158,12 +158,23 @@ var maintenanceTypes = map[string]attr.Type{ // Struct corresponding to Model.Network type network struct { - ID types.String `tfsdk:"id"` + ID types.String `tfsdk:"id"` + ControlPlane types.Object `tfsdk:"control_plane"` } // Types corresponding to network var networkTypes = map[string]attr.Type{ - "id": basetypes.StringType{}, + "id": basetypes.StringType{}, + "control_plane": types.ObjectType{AttrTypes: controlPlaneTypes}, +} + +type controlPlane struct { + AccessScope types.String `tfsdk:"access_scope"` +} + +// Types corresponding to control plane +var controlPlaneTypes = map[string]attr.Type{ + "access_scope": basetypes.StringType{}, } // Struct corresponding to Model.Hibernations[i] @@ -566,6 +577,20 @@ func (r *clusterResource) Schema(_ context.Context, _ resource.SchemaRequest, re stringplanmodifier.RequiresReplace(), }, }, + "control_plane": schema.SingleNestedAttribute{ + Description: "Control plane for the cluster.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "access_scope": schema.StringAttribute{ + Description: "Access scope of the control plane. It defines if the Kubernetes control plane is public or only available inside a STACKIT Network Area." + utils.FormatPossibleValues(sdkUtils.EnumSliceToStringSlice(ske.AllowedAccessScopeEnumValues)...) + " The field is immutable!", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + }, + }, }, }, "hibernations": schema.ListNestedAttribute{ @@ -1367,8 +1392,21 @@ func toNetworkPayload(ctx context.Context, m *Model) (*ske.Network, error) { return nil, fmt.Errorf("converting network object: %v", diags.Errors()) } + var networkControlPlane *ske.V2ControlPlaneNetwork + if !utils.IsUndefined(network.ControlPlane) { + networkControlPlaneModel := controlPlane{} + diags = network.ControlPlane.As(ctx, &networkControlPlaneModel, basetypes.ObjectAsOptions{}) + if diags.HasError() { + return nil, fmt.Errorf("converting network control plane: %w", core.DiagsToError(diags)) + } + networkControlPlane = &ske.V2ControlPlaneNetwork{ + AccessScope: ske.V2ControlPlaneNetworkGetAccessScopeAttributeType(conversion.StringValueToPointer(networkControlPlaneModel.AccessScope)), + } + } + return &ske.Network{ - Id: conversion.StringValueToPointer(network.ID), + Id: conversion.StringValueToPointer(network.ID), + ControlPlane: networkControlPlane, }, nil } @@ -1672,12 +1710,32 @@ func mapNetwork(cl *ske.Cluster, m *Model) error { return nil } + var diags diag.Diagnostics id := types.StringNull() if cl.Network.Id != nil { id = types.StringValue(*cl.Network.Id) } + + networkControlPlane := types.ObjectNull(controlPlaneTypes) + if cl.Network.ControlPlane != nil { + controlPlaneAccessScope := types.StringNull() + if cl.Network.ControlPlane.AccessScope != nil { + controlPlaneAccessScope = types.StringValue(string(cl.Network.ControlPlane.GetAccessScope())) + } + + controlPlaneValues := map[string]attr.Value{ + "access_scope": controlPlaneAccessScope, + } + + networkControlPlane, diags = types.ObjectValue(controlPlaneTypes, controlPlaneValues) + if diags.HasError() { + return fmt.Errorf("creating network control plane: %w", core.DiagsToError(diags)) + } + } + networkValues := map[string]attr.Value{ - "id": id, + "id": id, + "control_plane": networkControlPlane, } networkObject, diags := types.ObjectValue(networkTypes, networkValues) if diags.HasError() { diff --git a/stackit/internal/services/ske/cluster/resource_test.go b/stackit/internal/services/ske/cluster/resource_test.go index e29c15cc8..f8d7137e2 100644 --- a/stackit/internal/services/ske/cluster/resource_test.go +++ b/stackit/internal/services/ske/cluster/resource_test.go @@ -110,6 +110,9 @@ func TestMapFields(t *testing.T) { }, Network: &ske.Network{ Id: utils.Ptr("nid"), + ControlPlane: &ske.V2ControlPlaneNetwork{ + AccessScope: ske.V2ControlPlaneNetworkGetAccessScopeAttributeType(utils.Ptr("SNA")), + }, }, Name: utils.Ptr("name"), Nodepools: &[]ske.Nodepool{ @@ -231,6 +234,9 @@ func TestMapFields(t *testing.T) { }), Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{ "id": types.StringValue("nid"), + "control_plane": types.ObjectValueMust(controlPlaneTypes, map[string]attr.Value{ + "access_scope": types.StringValue("SNA"), + }), }), Hibernations: types.ListValueMust( types.ObjectType{AttrTypes: hibernationTypes}, @@ -560,6 +566,9 @@ func TestMapFields(t *testing.T) { }, Network: &ske.Network{ Id: utils.Ptr("nid"), + ControlPlane: &ske.V2ControlPlaneNetwork{ + AccessScope: ske.V2ControlPlaneNetworkGetAccessScopeAttributeType(utils.Ptr("SNA")), + }, }, Name: utils.Ptr("name"), Nodepools: &[]ske.Nodepool{ @@ -648,6 +657,9 @@ func TestMapFields(t *testing.T) { }), Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{ "id": types.StringValue("nid"), + "control_plane": types.ObjectValueMust(controlPlaneTypes, map[string]attr.Value{ + "access_scope": types.StringValue("SNA"), + }), }), Hibernations: types.ListValueMust( types.ObjectType{AttrTypes: hibernationTypes}, @@ -2239,10 +2251,16 @@ func TestToNetworkPayload(t *testing.T) { Name: types.StringValue("name"), Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{ "id": types.StringValue("nid"), + "control_plane": types.ObjectValueMust(controlPlaneTypes, map[string]attr.Value{ + "access_scope": types.StringValue("SNA"), + }), }), }, &ske.Network{ Id: utils.Ptr("nid"), + ControlPlane: &ske.V2ControlPlaneNetwork{ + AccessScope: ske.V2ControlPlaneNetworkGetAccessScopeAttributeType(utils.Ptr("SNA")), + }, }, true, }, @@ -2252,12 +2270,29 @@ func TestToNetworkPayload(t *testing.T) { ProjectId: types.StringValue("pid"), Name: types.StringValue("name"), Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{ - "id": types.StringNull(), + "id": types.StringNull(), + "control_plane": types.ObjectNull(controlPlaneTypes), }), }, &ske.Network{}, true, }, + { + "no_control_plane", + &Model{ + ProjectId: types.StringValue("pid"), + Name: types.StringValue("name"), + Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{ + "id": types.StringValue("nid"), + "control_plane": types.ObjectNull(controlPlaneTypes), + }), + }, + &ske.Network{ + Id: utils.Ptr("nid"), + ControlPlane: nil, + }, + true, + }, { "no_network", &Model{ diff --git a/stackit/internal/services/ske/ske_acc_test.go b/stackit/internal/services/ske/ske_acc_test.go index c862daa98..a606c83be 100644 --- a/stackit/internal/services/ske/ske_acc_test.go +++ b/stackit/internal/services/ske/ske_acc_test.go @@ -49,9 +49,10 @@ var testConfigVarsMin = config.Variables{ "kubernetes_version_min": config.StringVariable(skeProviderOptions.GetCreateK8sVersion()), "maintenance_enable_machine_image_version_updates": config.StringVariable("true"), "maintenance_enable_kubernetes_version_updates": config.StringVariable("true"), - "maintenance_start": config.StringVariable("02:00:00+01:00"), - "maintenance_end": config.StringVariable("04:00:00+01:00"), - "region": config.StringVariable(testutil.Region), + "maintenance_start": config.StringVariable("02:00:00+01:00"), + "maintenance_end": config.StringVariable("04:00:00+01:00"), + "region": config.StringVariable(testutil.Region), + "network_control_plane_access_scope": config.StringVariable("PUBLIC"), } var testConfigVarsMax = config.Variables{ @@ -92,6 +93,7 @@ var testConfigVarsMax = config.Variables{ "refresh_before": config.StringVariable("600"), "dns_zone_name": config.StringVariable("acc-" + acctest.RandStringFromCharSet(6, acctest.CharSetAlpha)), "dns_name": config.StringVariable("acc-" + acctest.RandStringFromCharSet(6, acctest.CharSetAlpha) + ".runs.onstackit.cloud"), + "network_control_plane_access_scope": config.StringVariable("PUBLIC"), } var testConfigDatasource = config.Variables{ @@ -137,12 +139,14 @@ func TestAccSKEMin(t *testing.T) { resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "node_pools.0.minimum", testutil.ConvertConfigVariable(testConfigVarsMin["nodepool_minimum"])), resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "node_pools.0.name", testutil.ConvertConfigVariable(testConfigVarsMin["nodepool_name"])), resource.TestCheckResourceAttrSet("stackit_ske_cluster.cluster", "node_pools.0.os_version_used"), - resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "maintenance.enable_kubernetes_version_updates", testutil.ConvertConfigVariable(testConfigVarsMax["maintenance_enable_kubernetes_version_updates"])), - resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "maintenance.enable_machine_image_version_updates", testutil.ConvertConfigVariable(testConfigVarsMax["maintenance_enable_machine_image_version_updates"])), - resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "maintenance.start", testutil.ConvertConfigVariable(testConfigVarsMax["maintenance_start"])), - resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "maintenance.end", testutil.ConvertConfigVariable(testConfigVarsMax["maintenance_end"])), - resource.TestCheckResourceAttrSet("stackit_ske_cluster.cluster", "region"), + resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "kubernetes_version_min", testutil.ConvertConfigVariable(testConfigVarsMin["kubernetes_version_min"])), + resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "maintenance.enable_kubernetes_version_updates", testutil.ConvertConfigVariable(testConfigVarsMin["maintenance_enable_kubernetes_version_updates"])), + resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "maintenance.enable_machine_image_version_updates", testutil.ConvertConfigVariable(testConfigVarsMin["maintenance_enable_machine_image_version_updates"])), + resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "maintenance.start", testutil.ConvertConfigVariable(testConfigVarsMin["maintenance_start"])), + resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "maintenance.end", testutil.ConvertConfigVariable(testConfigVarsMin["maintenance_end"])), + resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "region", testutil.ConvertConfigVariable(testConfigVarsMin["region"])), resource.TestCheckResourceAttrSet("stackit_ske_cluster.cluster", "kubernetes_version_used"), + resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "network.control_plane.access_scope", testutil.ConvertConfigVariable(testConfigVarsMin["network_control_plane_access_scope"])), // Kubeconfig resource.TestCheckResourceAttrPair( @@ -182,6 +186,7 @@ func TestAccSKEMin(t *testing.T) { resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "maintenance.end", testutil.ConvertConfigVariable(testConfigVarsMax["maintenance_end"])), resource.TestCheckResourceAttrSet("stackit_ske_cluster.cluster", "region"), resource.TestCheckResourceAttrSet("data.stackit_ske_cluster.cluster", "kubernetes_version_used"), + resource.TestCheckResourceAttr("data.stackit_ske_cluster.cluster", "network.control_plane.access_scope", testutil.ConvertConfigVariable(testConfigVarsMin["network_control_plane_access_scope"])), ), }, // 3) Import cluster @@ -299,6 +304,8 @@ func TestAccSKEMax(t *testing.T) { resource.TestCheckResourceAttrSet("stackit_ske_cluster.cluster", "pod_address_ranges.0"), resource.TestCheckResourceAttrSet("stackit_ske_cluster.cluster", "kubernetes_version_used"), + resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "network.control_plane.access_scope", testutil.ConvertConfigVariable(testConfigVarsMax["network_control_plane_access_scope"])), + // Kubeconfig resource.TestCheckResourceAttrPair( "stackit_ske_kubeconfig.kubeconfig", "project_id", @@ -373,6 +380,8 @@ func TestAccSKEMax(t *testing.T) { resource.TestCheckResourceAttrSet("data.stackit_ske_cluster.cluster", "pod_address_ranges.0"), resource.TestCheckResourceAttrSet("data.stackit_ske_cluster.cluster", "kubernetes_version_used"), + + resource.TestCheckResourceAttr("data.stackit_ske_cluster.cluster", "network.control_plane.access_scope", testutil.ConvertConfigVariable(testConfigVarsMax["network_control_plane_access_scope"])), ), }, // 3) Import cluster diff --git a/stackit/internal/services/ske/testdata/resource-max.tf b/stackit/internal/services/ske/testdata/resource-max.tf index 192c9138f..fde7ff1cc 100644 --- a/stackit/internal/services/ske/testdata/resource-max.tf +++ b/stackit/internal/services/ske/testdata/resource-max.tf @@ -35,6 +35,7 @@ variable "refresh" {} variable "refresh_before" {} variable "dns_zone_name" {} variable "dns_name" {} +variable "network_control_plane_access_scope" {} resource "stackit_ske_cluster" "cluster" { project_id = var.project_id @@ -92,6 +93,11 @@ resource "stackit_ske_cluster" "cluster" { end = var.maintenance_end } region = var.region + network = { + control_plane = { + access_scope = var.network_control_plane_access_scope + } + } } resource "stackit_ske_kubeconfig" "kubeconfig" { diff --git a/stackit/internal/services/ske/testdata/resource-min.tf b/stackit/internal/services/ske/testdata/resource-min.tf index 776e7fc51..ab1784e64 100644 --- a/stackit/internal/services/ske/testdata/resource-min.tf +++ b/stackit/internal/services/ske/testdata/resource-min.tf @@ -11,6 +11,7 @@ variable "maintenance_enable_machine_image_version_updates" {} variable "maintenance_start" {} variable "maintenance_end" {} variable "region" {} +variable "network_control_plane_access_scope" {} resource "stackit_ske_cluster" "cluster" { @@ -36,6 +37,11 @@ resource "stackit_ske_cluster" "cluster" { end = var.maintenance_end } region = var.region + network = { + control_plane = { + access_scope = var.network_control_plane_access_scope + } + } } resource "stackit_ske_kubeconfig" "kubeconfig" {