diff --git a/docs/data-sources/ske_cluster.md b/docs/data-sources/ske_cluster.md index d5d77458..f9aedba9 100644 --- a/docs/data-sources/ske_cluster.md +++ b/docs/data-sources/ske_cluster.md @@ -46,6 +46,7 @@ This should be used with care since it also disables a couple of other features - `maintenance` (Attributes) A single maintenance block as defined below (see [below for nested schema](#nestedatt--maintenance)) - `network` (Attributes) Network block as defined below. (see [below for nested schema](#nestedatt--network)) - `node_pools` (Attributes List) One or more `node_pool` block as defined below. (see [below for nested schema](#nestedatt--node_pools)) +- `pod_address_ranges` (List of String) The network ranges (in CIDR notation) used by pods of the cluster. ### Nested Schema for `extensions` diff --git a/docs/resources/ske_cluster.md b/docs/resources/ske_cluster.md index 3714f145..e2f56f28 100644 --- a/docs/resources/ske_cluster.md +++ b/docs/resources/ske_cluster.md @@ -66,6 +66,7 @@ Deprecated as of Kubernetes 1.25 and later - `egress_address_ranges` (List of String) The outgoing network ranges (in CIDR notation) of traffic originating from workload on the cluster. - `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`region`,`name`". - `kubernetes_version_used` (String) Full Kubernetes version used. For example, if 1.22 was set in `kubernetes_version_min`, this value may result to 1.22.15. SKE automatically updates the cluster Kubernetes version if you have set `maintenance.enable_kubernetes_version_updates` to true or if there is a mandatory update, as described in [Updates for Kubernetes versions and Operating System versions in SKE](https://docs.stackit.cloud/stackit/en/version-updates-in-ske-10125631.html). +- `pod_address_ranges` (List of String) The network ranges (in CIDR notation) used by pods of the cluster. ### Nested Schema for `node_pools` diff --git a/stackit/internal/services/ske/cluster/datasource.go b/stackit/internal/services/ske/cluster/datasource.go index 2a0f9ac1..099aeb96 100644 --- a/stackit/internal/services/ske/cluster/datasource.go +++ b/stackit/internal/services/ske/cluster/datasource.go @@ -98,6 +98,11 @@ func (r *clusterDataSource) Schema(_ context.Context, _ datasource.SchemaRequest Computed: true, ElementType: types.StringType, }, + "pod_address_ranges": schema.ListAttribute{ + Description: "The network ranges (in CIDR notation) used by pods of the cluster.", + Computed: true, + ElementType: types.StringType, + }, "node_pools": schema.ListNestedAttribute{ Description: "One or more `node_pool` block as defined below.", Computed: true, diff --git a/stackit/internal/services/ske/cluster/resource.go b/stackit/internal/services/ske/cluster/resource.go index 4aaf0952..411db9b4 100644 --- a/stackit/internal/services/ske/cluster/resource.go +++ b/stackit/internal/services/ske/cluster/resource.go @@ -83,6 +83,7 @@ type Model struct { Hibernations types.List `tfsdk:"hibernations"` Extensions types.Object `tfsdk:"extensions"` EgressAddressRanges types.List `tfsdk:"egress_address_ranges"` + PodAddressRanges types.List `tfsdk:"pod_address_ranges"` Region types.String `tfsdk:"region"` } @@ -384,6 +385,11 @@ func (r *clusterResource) Schema(_ context.Context, _ resource.SchemaRequest, re Computed: true, ElementType: types.StringType, }, + "pod_address_ranges": schema.ListAttribute{ + Description: "The network ranges (in CIDR notation) used by pods of the cluster.", + Computed: true, + ElementType: types.StringType, + }, "node_pools": schema.ListNestedAttribute{ Description: "One or more `node_pool` block as defined below.", Required: true, @@ -1378,6 +1384,15 @@ func mapFields(ctx context.Context, cl *ske.Cluster, m *Model, region string) er } } + m.PodAddressRanges = types.ListNull(types.StringType) + if cl.Status != nil { + var diags diag.Diagnostics + m.PodAddressRanges, diags = types.ListValueFrom(ctx, types.StringType, cl.Status.PodAddressRanges) + if diags.HasError() { + return fmt.Errorf("map podAddressRanges: %w", core.DiagsToError(diags)) + } + } + err := mapNodePools(ctx, cl, m) if err != nil { return fmt.Errorf("map node_pools: %w", err) diff --git a/stackit/internal/services/ske/cluster/resource_test.go b/stackit/internal/services/ske/cluster/resource_test.go index 6e1d6703..6fa00433 100644 --- a/stackit/internal/services/ske/cluster/resource_test.go +++ b/stackit/internal/services/ske/cluster/resource_test.go @@ -63,6 +63,7 @@ func TestMapFields(t *testing.T) { Hibernations: types.ListNull(types.ObjectType{AttrTypes: hibernationTypes}), Extensions: types.ObjectNull(extensionsTypes), EgressAddressRanges: types.ListNull(types.StringType), + PodAddressRanges: types.ListNull(types.StringType), Region: types.StringValue(testRegion), }, true, @@ -151,6 +152,7 @@ func TestMapFields(t *testing.T) { Error: nil, Hibernated: nil, EgressAddressRanges: &[]string{"0.0.0.0/32", "1.1.1.1/32"}, + PodAddressRanges: &[]string{"0.0.0.0/32", "1.1.1.1/32"}, }, }, testRegion, @@ -168,6 +170,13 @@ func TestMapFields(t *testing.T) { types.StringValue("1.1.1.1/32"), }, ), + PodAddressRanges: types.ListValueMust( + types.StringType, + []attr.Value{ + types.StringValue("0.0.0.0/32"), + types.StringValue("1.1.1.1/32"), + }, + ), NodePools: types.ListValueMust( types.ObjectType{AttrTypes: nodePoolTypes}, []attr.Value{ @@ -283,6 +292,7 @@ func TestMapFields(t *testing.T) { Hibernations: types.ListNull(types.ObjectType{AttrTypes: hibernationTypes}), Extensions: types.ObjectNull(extensionsTypes), EgressAddressRanges: types.ListNull(types.StringType), + PodAddressRanges: types.ListNull(types.StringType), Region: types.StringValue(testRegion), }, true, @@ -319,6 +329,7 @@ func TestMapFields(t *testing.T) { Maintenance: types.ObjectNull(maintenanceTypes), Hibernations: types.ListNull(types.ObjectType{AttrTypes: hibernationTypes}), EgressAddressRanges: types.ListNull(types.StringType), + PodAddressRanges: types.ListNull(types.StringType), Extensions: types.ObjectValueMust(extensionsTypes, map[string]attr.Value{ "acl": types.ObjectValueMust(aclTypes, map[string]attr.Value{ "enabled": types.BoolValue(true), @@ -369,6 +380,7 @@ func TestMapFields(t *testing.T) { Maintenance: types.ObjectNull(maintenanceTypes), Hibernations: types.ListNull(types.ObjectType{AttrTypes: hibernationTypes}), EgressAddressRanges: types.ListNull(types.StringType), + PodAddressRanges: types.ListNull(types.StringType), Extensions: types.ObjectValueMust(extensionsTypes, map[string]attr.Value{ "acl": types.ObjectValueMust(aclTypes, map[string]attr.Value{ "enabled": types.BoolValue(false), @@ -430,6 +442,7 @@ func TestMapFields(t *testing.T) { Maintenance: types.ObjectNull(maintenanceTypes), Hibernations: types.ListNull(types.ObjectType{AttrTypes: hibernationTypes}), EgressAddressRanges: types.ListNull(types.StringType), + PodAddressRanges: types.ListNull(types.StringType), Extensions: types.ObjectValueMust(extensionsTypes, map[string]attr.Value{ "acl": types.ObjectValueMust(aclTypes, map[string]attr.Value{ "enabled": types.BoolValue(true), @@ -470,6 +483,7 @@ func TestMapFields(t *testing.T) { Hibernations: types.ListNull(types.ObjectType{AttrTypes: hibernationTypes}), Extensions: types.ObjectNull(extensionsTypes), EgressAddressRanges: types.ListNull(types.StringType), + PodAddressRanges: types.ListNull(types.StringType), Region: types.StringValue(testRegion), }, true, @@ -598,6 +612,7 @@ func TestMapFields(t *testing.T) { KubernetesVersionUsed: types.StringValue("1.2.3"), AllowPrivilegedContainers: types.BoolValue(true), EgressAddressRanges: types.ListNull(types.StringType), + PodAddressRanges: types.ListNull(types.StringType), NodePools: types.ListValueMust( types.ObjectType{AttrTypes: nodePoolTypes}, []attr.Value{ diff --git a/stackit/internal/services/ske/ske_acc_test.go b/stackit/internal/services/ske/ske_acc_test.go index 8cb47e38..cc539f0c 100644 --- a/stackit/internal/services/ske/ske_acc_test.go +++ b/stackit/internal/services/ske/ske_acc_test.go @@ -12,7 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" - core_config "github.com/stackitcloud/stackit-sdk-go/core/config" + coreConfig "github.com/stackitcloud/stackit-sdk-go/core/config" "github.com/stackitcloud/stackit-sdk-go/core/utils" "github.com/stackitcloud/stackit-sdk-go/services/ske" "github.com/stackitcloud/stackit-sdk-go/services/ske/wait" @@ -33,27 +33,29 @@ var ( resourceMax string ) +var skeProviderOptions = NewSkeProviderOptions("flatcar") + var testConfigVarsMin = config.Variables{ "project_id": config.StringVariable(testutil.ProjectId), "name": config.StringVariable(minTestName), - "nodepool_availability_zone1": config.StringVariable("eu01-m"), + "nodepool_availability_zone1": config.StringVariable(fmt.Sprintf("%s-1", testutil.Region)), "nodepool_machine_type": config.StringVariable("g1.2"), "nodepool_minimum": config.StringVariable("1"), "nodepool_maximum": config.StringVariable("2"), "nodepool_name": config.StringVariable("np-acc-test"), - "kubernetes_version_min": config.StringVariable("1.31.8"), + "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("eu01"), + "region": config.StringVariable(testutil.Region), } var testConfigVarsMax = config.Variables{ "project_id": config.StringVariable(testutil.ProjectId), "organization_id": config.StringVariable(testutil.OrganizationId), "name": config.StringVariable(maxTestName), - "nodepool_availability_zone1": config.StringVariable("eu01-m"), + "nodepool_availability_zone1": config.StringVariable(fmt.Sprintf("%s-1", testutil.Region)), "nodepool_machine_type": config.StringVariable("g1.2"), "nodepool_minimum": config.StringVariable("1"), "nodepool_maximum": config.StringVariable("2"), @@ -63,8 +65,8 @@ var testConfigVarsMax = config.Variables{ "nodepool_label_value": config.StringVariable("value"), "nodepool_max_surge": config.StringVariable("1"), "nodepool_max_unavailable": config.StringVariable("1"), - "nodepool_os_name": config.StringVariable("flatcar"), - "nodepool_os_version_min": config.StringVariable("4152.2.1"), + "nodepool_os_name": config.StringVariable(skeProviderOptions.nodePoolOsName), + "nodepool_os_version_min": config.StringVariable(skeProviderOptions.GetCreateMachineVersion()), "nodepool_taints_effect": config.StringVariable("PreferNoSchedule"), "nodepool_taints_key": config.StringVariable("tkey"), "nodepool_taints_value": config.StringVariable("tvalue"), @@ -77,12 +79,12 @@ var testConfigVarsMax = config.Variables{ "nodepool_hibernations1_start": config.StringVariable("0 18 * * *"), "nodepool_hibernations1_end": config.StringVariable("59 23 * * *"), "nodepool_hibernations1_timezone": config.StringVariable("Europe/Berlin"), - "kubernetes_version_min": config.StringVariable("1.31.8"), + "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("eu01"), + "region": config.StringVariable(testutil.Region), "expiration": config.StringVariable("3600"), "refresh": config.StringVariable("true"), "dns_zone_name": config.StringVariable("acc-" + acctest.RandStringFromCharSet(6, acctest.CharSetAlpha)), @@ -90,20 +92,21 @@ var testConfigVarsMax = config.Variables{ } func configVarsMinUpdated() config.Variables { - updatedConfig := maps.Clone(testConfigVarsMax) - updatedConfig["kubernetes_version_min"] = config.StringVariable("1.31") + updatedConfig := maps.Clone(testConfigVarsMin) + updatedConfig["kubernetes_version_min"] = config.StringVariable(skeProviderOptions.GetUpdateK8sVersion()) return updatedConfig } func configVarsMaxUpdated() config.Variables { updatedConfig := maps.Clone(testConfigVarsMax) - updatedConfig["kubernetes_version_min"] = config.StringVariable("1.31") - updatedConfig["nodepool_os_version_min"] = config.StringVariable("4152.2.1") + updatedConfig["kubernetes_version_min"] = config.StringVariable(skeProviderOptions.GetUpdateK8sVersion()) + updatedConfig["nodepool_os_version_min"] = config.StringVariable(skeProviderOptions.GetUpdateMachineVersion()) updatedConfig["maintenance_end"] = config.StringVariable("03:03:03+00:00") return updatedConfig } + func TestAccSKEMin(t *testing.T) { resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, @@ -285,6 +288,8 @@ func TestAccSKEMax(t *testing.T) { resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "egress_address_ranges.#", "1"), resource.TestCheckResourceAttrSet("stackit_ske_cluster.cluster", "egress_address_ranges.0"), + resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "pod_address_ranges.#", "1"), + resource.TestCheckResourceAttrSet("stackit_ske_cluster.cluster", "pod_address_ranges.0"), resource.TestCheckResourceAttrSet("stackit_ske_cluster.cluster", "kubernetes_version_used"), // Kubeconfig @@ -355,6 +360,9 @@ func TestAccSKEMax(t *testing.T) { resource.TestCheckResourceAttr("data.stackit_ske_cluster.cluster", "egress_address_ranges.#", "1"), resource.TestCheckResourceAttrSet("data.stackit_ske_cluster.cluster", "egress_address_ranges.0"), + resource.TestCheckResourceAttr("data.stackit_ske_cluster.cluster", "pod_address_ranges.#", "1"), + resource.TestCheckResourceAttrSet("data.stackit_ske_cluster.cluster", "pod_address_ranges.0"), + resource.TestCheckResourceAttrSet("data.stackit_ske_cluster.cluster", "kubernetes_version_used"), ), }, @@ -435,6 +443,8 @@ func TestAccSKEMax(t *testing.T) { resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "egress_address_ranges.#", "1"), resource.TestCheckResourceAttrSet("stackit_ske_cluster.cluster", "egress_address_ranges.0"), + resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "pod_address_ranges.#", "1"), + resource.TestCheckResourceAttrSet("stackit_ske_cluster.cluster", "pod_address_ranges.0"), resource.TestCheckResourceAttrSet("stackit_ske_cluster.cluster", "kubernetes_version_used"), ), }, @@ -449,11 +459,11 @@ func testAccCheckSKEDestroy(s *terraform.State) error { var err error if testutil.SKECustomEndpoint == "" { client, err = ske.NewAPIClient( - core_config.WithRegion("eu01"), + coreConfig.WithRegion(testutil.Region), ) } else { client, err = ske.NewAPIClient( - core_config.WithEndpoint(testutil.SKECustomEndpoint), + coreConfig.WithEndpoint(testutil.SKECustomEndpoint), ) } if err != nil { @@ -493,3 +503,112 @@ func testAccCheckSKEDestroy(s *terraform.State) error { } return nil } + +type SkeProviderOptions struct { + options *ske.ProviderOptions + nodePoolOsName string +} + +// NewSkeProviderOptions fetches the latest available options from SKE. +func NewSkeProviderOptions(nodePoolOs string) *SkeProviderOptions { + // skip if TF_ACC=1 is not set + if !testutil.E2ETestsEnabled { + return &SkeProviderOptions{ + options: nil, + nodePoolOsName: nodePoolOs, + } + } + + ctx := context.Background() + + var client *ske.APIClient + var err error + + if testutil.SKECustomEndpoint == "" { + client, err = ske.NewAPIClient(coreConfig.WithRegion("eu01")) + } else { + client, err = ske.NewAPIClient(coreConfig.WithEndpoint(testutil.SKECustomEndpoint)) + } + + if err != nil { + panic("failed to create SKE client: " + err.Error()) + } + + options, err := client.ListProviderOptions(ctx).Execute() + if err != nil { + panic("failed to fetch SKE provider options: " + err.Error()) + } + + return &SkeProviderOptions{ + options: options, + nodePoolOsName: nodePoolOs, + } +} + +// getMachineVersionAt returns the N-th supported version for the specified machine image. +func (s *SkeProviderOptions) getMachineVersionAt(position int) string { + // skip if TF_ACC=1 is not set + if !testutil.E2ETestsEnabled { + return "" + } + + if s.options == nil || s.options.MachineImages == nil { + panic(fmt.Sprintf("no supported machine version found at position %d", position)) + } + + for _, mi := range *s.options.MachineImages { + if mi.Name != nil && *mi.Name == s.nodePoolOsName && mi.Versions != nil { + count := 0 + for _, v := range *mi.Versions { + if v.State != nil && v.Version != nil { + if count == position { + return *v.Version + } + count++ + } + } + } + } + + panic(fmt.Sprintf("no supported machine version found at position %d", position)) +} + +// getK8sVersionAt returns the N-th supported Kubernetes version. +func (s *SkeProviderOptions) getK8sVersionAt(position int) string { + // skip if TF_ACC=1 is not set + if !testutil.E2ETestsEnabled { + return "" + } + + if s.options == nil || s.options.KubernetesVersions == nil { + panic(fmt.Sprintf("no supported k8s version found at position %d", position)) + } + + count := 0 + for _, v := range *s.options.KubernetesVersions { + if v.State != nil && *v.State == "supported" && v.Version != nil { + if count == position { + return *v.Version + } + count++ + } + } + + panic(fmt.Sprintf("no supported k8s version found at position %d", position)) +} + +func (s *SkeProviderOptions) GetCreateMachineVersion() string { + return s.getMachineVersionAt(0) +} + +func (s *SkeProviderOptions) GetUpdateMachineVersion() string { + return s.getMachineVersionAt(1) +} + +func (s *SkeProviderOptions) GetCreateK8sVersion() string { + return s.getK8sVersionAt(0) +} + +func (s *SkeProviderOptions) GetUpdateK8sVersion() string { + return s.getK8sVersionAt(1) +} diff --git a/stackit/internal/testutil/testutil.go b/stackit/internal/testutil/testutil.go index 849487d5..59ee77ac 100644 --- a/stackit/internal/testutil/testutil.go +++ b/stackit/internal/testutil/testutil.go @@ -28,6 +28,9 @@ var ( "stackit": providerserver.NewProtocol6WithError(stackit.New("test-version")()), } + // E2ETestsEnabled checks if end-to-end tests should be run. + // It is enabled when the TF_ACC environment variable is set to "1". + E2ETestsEnabled = os.Getenv("TF_ACC") == "1" // OrganizationId is the id of organization used for tests OrganizationId = os.Getenv("TF_ACC_ORGANIZATION_ID") // ProjectId is the id of project used for tests