From 3dc4fedba1d7a94b3d01dcbc252dc8b2c2139b53 Mon Sep 17 00:00:00 2001 From: Alexander Dahmen Date: Mon, 24 Mar 2025 14:37:43 +0100 Subject: [PATCH] Ft/region adjustment service enablement (#718) * feat(serviceenablement): Region adjustment Signed-off-by: Alexander Dahmen * chore(ske): Remove deprecated ske project This resource was removed on October 10th 2024. Signed-off-by: Alexander Dahmen --------- Signed-off-by: Alexander Dahmen --- docs/data-sources/ske_cluster.md | 4 + docs/data-sources/ske_project.md | 30 --- docs/resources/ske_cluster.md | 3 +- docs/resources/ske_project.md | 30 --- .../stackit_ske_project/data-source.tf | 3 - .../resources/stackit_ske_project/resource.tf | 3 - go.mod | 2 +- go.sum | 4 +- .../services/ske/cluster/datasource.go | 31 ++- .../internal/services/ske/cluster/resource.go | 99 +++++-- .../services/ske/cluster/resource_test.go | 38 ++- .../services/ske/project/datasource.go | 142 ---------- .../internal/services/ske/project/resource.go | 249 ------------------ stackit/internal/services/ske/ske_acc_test.go | 36 ++- stackit/provider.go | 3 - 15 files changed, 163 insertions(+), 514 deletions(-) delete mode 100644 docs/data-sources/ske_project.md delete mode 100644 docs/resources/ske_project.md delete mode 100644 examples/data-sources/stackit_ske_project/data-source.tf delete mode 100644 examples/resources/stackit_ske_project/resource.tf delete mode 100644 stackit/internal/services/ske/project/datasource.go delete mode 100644 stackit/internal/services/ske/project/resource.go diff --git a/docs/data-sources/ske_cluster.md b/docs/data-sources/ske_cluster.md index 1d5573f6..d5d77458 100644 --- a/docs/data-sources/ske_cluster.md +++ b/docs/data-sources/ske_cluster.md @@ -27,6 +27,10 @@ data "stackit_ske_cluster" "example" { - `name` (String) The cluster name. - `project_id` (String) STACKIT project ID to which the cluster is associated. +### Optional + +- `region` (String) The resource region. If not defined, the provider region is used. + ### Read-Only - `allow_privileged_containers` (Boolean, Deprecated) DEPRECATED as of Kubernetes 1.25+ diff --git a/docs/data-sources/ske_project.md b/docs/data-sources/ske_project.md deleted file mode 100644 index 01e1100f..00000000 --- a/docs/data-sources/ske_project.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "stackit_ske_project Data Source - stackit" -subcategory: "" -description: |- - SKE project data source schema. Must have a region specified in the provider configuration. Warning: SKE project resource is no longer in use and will be removed with the next release. SKE service enablement is done automatically when a new cluster is created. ---- - -# stackit_ske_project (Data Source) - -SKE project data source schema. Must have a `region` specified in the provider configuration. Warning: SKE project resource is no longer in use and will be removed with the next release. SKE service enablement is done automatically when a new cluster is created. - -## Example Usage - -```terraform -data "stackit_ske_project" "example" { - project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -} -``` - - -## Schema - -### Required - -- `project_id` (String) STACKIT Project ID in which the kubernetes project is enabled. - -### Read-Only - -- `id` (String) Terraform's internal data source. ID. It is structured as "`project_id`". diff --git a/docs/resources/ske_cluster.md b/docs/resources/ske_cluster.md index af3c8769..3714f145 100644 --- a/docs/resources/ske_cluster.md +++ b/docs/resources/ske_cluster.md @@ -59,11 +59,12 @@ Deprecated as of Kubernetes 1.25 and later - `kubernetes_version_min` (String) The minimum Kubernetes version. This field will be used to set the minimum kubernetes version on creation/update of the cluster. If unset, the latest supported Kubernetes version will be used. 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). To get the current kubernetes version being used for your cluster, use the read-only `kubernetes_version_used` field. - `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)) +- `region` (String) The resource region. If not defined, the provider region is used. ### Read-Only - `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`,`name`". +- `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). diff --git a/docs/resources/ske_project.md b/docs/resources/ske_project.md deleted file mode 100644 index 502091b4..00000000 --- a/docs/resources/ske_project.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "stackit_ske_project Resource - stackit" -subcategory: "" -description: |- - SKE project resource schema. Must have a region specified in the provider configuration. This resource allows you to enable the SKE service and you can only have one per project. Before deleting this resource, all SKE clusters associated to the project must be deleted. Otherwise, error would occur due to the existing clusters. In such case, it is highly recommended to remove the SKE project from the state, directly using the "terraform state rm". Warning: SKE project resource is no longer in use and will be removed after October 10th 2024. SKE service enablement is done automatically when a new cluster is created. ---- - -# stackit_ske_project (Resource) - -SKE project resource schema. Must have a `region` specified in the provider configuration. This resource allows you to enable the SKE service and you can only have one per project. Before deleting this resource, all SKE clusters associated to the project must be deleted. Otherwise, error would occur due to the existing clusters. In such case, it is highly recommended to remove the SKE project from the state, directly using the "`terraform state rm`". Warning: SKE project resource is no longer in use and will be removed after October 10th 2024. SKE service enablement is done automatically when a new cluster is created. - -## Example Usage - -```terraform -resource "stackit_ske_project" "example" { - project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -} -``` - - -## Schema - -### Required - -- `project_id` (String) STACKIT Project ID in which the kubernetes project is enabled. - -### Read-Only - -- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`". diff --git a/examples/data-sources/stackit_ske_project/data-source.tf b/examples/data-sources/stackit_ske_project/data-source.tf deleted file mode 100644 index 5762ff58..00000000 --- a/examples/data-sources/stackit_ske_project/data-source.tf +++ /dev/null @@ -1,3 +0,0 @@ -data "stackit_ske_project" "example" { - project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -} diff --git a/examples/resources/stackit_ske_project/resource.tf b/examples/resources/stackit_ske_project/resource.tf deleted file mode 100644 index eadcca65..00000000 --- a/examples/resources/stackit_ske_project/resource.tf +++ /dev/null @@ -1,3 +0,0 @@ -resource "stackit_ske_project" "example" { - project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -} diff --git a/go.mod b/go.mod index c2ebc623..06e36b34 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( github.com/stackitcloud/stackit-sdk-go/services/serverbackup v0.6.0 github.com/stackitcloud/stackit-sdk-go/services/serverupdate v0.5.0 github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.6.0 - github.com/stackitcloud/stackit-sdk-go/services/serviceenablement v0.5.0 + github.com/stackitcloud/stackit-sdk-go/services/serviceenablement v1.0.0 github.com/stackitcloud/stackit-sdk-go/services/ske v0.22.0 github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.0.0 github.com/teambition/rrule-go v1.8.2 diff --git a/go.sum b/go.sum index 4aced637..fda775f4 100644 --- a/go.sum +++ b/go.sum @@ -191,8 +191,8 @@ github.com/stackitcloud/stackit-sdk-go/services/serverupdate v0.5.0 h1:TMUxDh8XG github.com/stackitcloud/stackit-sdk-go/services/serverupdate v0.5.0/go.mod h1:giHnHz3kHeLY8Av9MZLsyJlaTXYz+BuGqdP/SKB5Vo0= github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.6.0 h1:y+XzJcntHJ7M+IWWvAUkiVFA8op+jZxwHs3ktW2aLoA= github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.6.0/go.mod h1:J/Wa67cbDI1wAyxib9PiEbNqGfIoFUH+DSLueVazQx8= -github.com/stackitcloud/stackit-sdk-go/services/serviceenablement v0.5.0 h1:QG+rGBHsyXOlJ3ZIeOgExGqu9PoTlGY1rltW/VpG6lw= -github.com/stackitcloud/stackit-sdk-go/services/serviceenablement v0.5.0/go.mod h1:16dOVT052cMuHhUJ3NIcPuY7TrpCr9QlxmvvfjLZubA= +github.com/stackitcloud/stackit-sdk-go/services/serviceenablement v1.0.0 h1:Xxd5KUSWRt7FytnNWClLEa0n9GM6e5xAKo835ODSpAM= +github.com/stackitcloud/stackit-sdk-go/services/serviceenablement v1.0.0/go.mod h1:EMqjiq/72WKXSwnJGLpumUJS4Uwlyhg5vtNg7qWoGtc= github.com/stackitcloud/stackit-sdk-go/services/ske v0.22.0 h1:3KUVls8zXsbT2tOYRSHyp3/l0Kpjl4f3INmQKYTe65Y= github.com/stackitcloud/stackit-sdk-go/services/ske v0.22.0/go.mod h1:63IvXpBJTIVONAnGPSDo0sRJ+6n6tzO918OLqfYBxto= github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.0.0 h1:RYJO0rZea9+sxVfaJDWRo2zgfKNgiUcA5c0nbvZURiU= diff --git a/stackit/internal/services/ske/cluster/datasource.go b/stackit/internal/services/ske/cluster/datasource.go index 4e160a30..fba6fc2a 100644 --- a/stackit/internal/services/ske/cluster/datasource.go +++ b/stackit/internal/services/ske/cluster/datasource.go @@ -14,6 +14,7 @@ import ( "github.com/stackitcloud/stackit-sdk-go/core/oapierror" "github.com/stackitcloud/stackit-sdk-go/services/ske" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" + "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" ) @@ -29,7 +30,8 @@ func NewClusterDataSource() datasource.DataSource { // clusterDataSource is the data source implementation. type clusterDataSource struct { - client *ske.APIClient + client *ske.APIClient + providerData core.ProviderData } // Metadata returns the data source type name. @@ -44,7 +46,8 @@ func (r *clusterDataSource) Configure(ctx context.Context, req datasource.Config return } - providerData, ok := req.ProviderData.(core.ProviderData) + var ok bool + r.providerData, ok = req.ProviderData.(core.ProviderData) if !ok { core.LogAndAddError(ctx, &resp.Diagnostics, "Error configuring API client", fmt.Sprintf("Expected configure type stackit.ProviderData, got %T", req.ProviderData)) return @@ -52,15 +55,15 @@ func (r *clusterDataSource) Configure(ctx context.Context, req datasource.Config var apiClient *ske.APIClient var err error - if providerData.SKECustomEndpoint != "" { + if r.providerData.SKECustomEndpoint != "" { apiClient, err = ske.NewAPIClient( - config.WithCustomAuth(providerData.RoundTripper), - config.WithEndpoint(providerData.SKECustomEndpoint), + config.WithCustomAuth(r.providerData.RoundTripper), + config.WithEndpoint(r.providerData.SKECustomEndpoint), ) } else { apiClient, err = ske.NewAPIClient( - config.WithCustomAuth(providerData.RoundTripper), - config.WithRegion(providerData.GetRegion()), + config.WithCustomAuth(r.providerData.RoundTripper), + config.WithRegion(r.providerData.GetRegion()), ) } @@ -319,6 +322,11 @@ func (r *clusterDataSource) Schema(_ context.Context, _ datasource.SchemaRequest }, }, }, + "region": schema.StringAttribute{ + // the region cannot be found, so it has to be passed + Optional: true, + Description: "The resource region. If not defined, the provider region is used.", + }, }, } } @@ -334,8 +342,15 @@ func (r *clusterDataSource) Read(ctx context.Context, req datasource.ReadRequest projectId := state.ProjectId.ValueString() name := state.Name.ValueString() + var region string + if utils.IsUndefined(state.Region) { + region = r.providerData.GetRegion() + } else { + region = state.Region.ValueString() + } ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "name", name) + ctx = tflog.SetField(ctx, "region", region) clusterResp, err := r.client.GetCluster(ctx, projectId, name).Execute() if err != nil { oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped @@ -346,7 +361,7 @@ func (r *clusterDataSource) Read(ctx context.Context, req datasource.ReadRequest return } - err = mapFields(ctx, clusterResp, &state) + err = mapFields(ctx, clusterResp, &state, region) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading cluster", fmt.Sprintf("Processing API payload: %v", err)) return diff --git a/stackit/internal/services/ske/cluster/resource.go b/stackit/internal/services/ske/cluster/resource.go index b0ce5bce..f63d377a 100644 --- a/stackit/internal/services/ske/cluster/resource.go +++ b/stackit/internal/services/ske/cluster/resource.go @@ -59,6 +59,7 @@ var ( _ resource.Resource = &clusterResource{} _ resource.ResourceWithConfigure = &clusterResource{} _ resource.ResourceWithImportState = &clusterResource{} + _ resource.ResourceWithModifyPlan = &clusterResource{} ) type skeClient interface { @@ -79,6 +80,7 @@ type Model struct { Hibernations types.List `tfsdk:"hibernations"` Extensions types.Object `tfsdk:"extensions"` EgressAddressRanges types.List `tfsdk:"egress_address_ranges"` + Region types.String `tfsdk:"region"` } // Struct corresponding to Model.NodePools[i] @@ -236,6 +238,37 @@ func NewClusterResource() resource.Resource { type clusterResource struct { skeClient *ske.APIClient enablementClient *serviceenablement.APIClient + providerData core.ProviderData +} + +// ModifyPlan implements resource.ResourceWithModifyPlan. +// Use the modifier to set the effective region in the current plan. +func (r *clusterResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { // nolint:gocritic // function signature required by Terraform + var configModel Model + // skip initial empty configuration to avoid follow-up errors + if req.Config.Raw.IsNull() { + return + } + resp.Diagnostics.Append(req.Config.Get(ctx, &configModel)...) + if resp.Diagnostics.HasError() { + return + } + + var planModel Model + resp.Diagnostics.Append(req.Plan.Get(ctx, &planModel)...) + if resp.Diagnostics.HasError() { + return + } + + utils.AdaptRegion(ctx, configModel.Region, &planModel.Region, r.providerData.GetRegion(), resp) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.Plan.Set(ctx, planModel)...) + if resp.Diagnostics.HasError() { + return + } } // Metadata returns the resource type name. @@ -250,7 +283,8 @@ func (r *clusterResource) Configure(ctx context.Context, req resource.ConfigureR return } - providerData, ok := req.ProviderData.(core.ProviderData) + var ok bool + r.providerData, ok = req.ProviderData.(core.ProviderData) if !ok { core.LogAndAddError(ctx, &resp.Diagnostics, "Error configuring API client", fmt.Sprintf("Expected configure type stackit.ProviderData, got %T", req.ProviderData)) return @@ -259,15 +293,15 @@ func (r *clusterResource) Configure(ctx context.Context, req resource.ConfigureR var skeClient *ske.APIClient var enablementClient *serviceenablement.APIClient var err error - if providerData.SKECustomEndpoint != "" { + if r.providerData.SKECustomEndpoint != "" { skeClient, err = ske.NewAPIClient( - config.WithCustomAuth(providerData.RoundTripper), - config.WithEndpoint(providerData.SKECustomEndpoint), + config.WithCustomAuth(r.providerData.RoundTripper), + config.WithEndpoint(r.providerData.SKECustomEndpoint), ) } else { skeClient, err = ske.NewAPIClient( - config.WithCustomAuth(providerData.RoundTripper), - config.WithRegion(providerData.GetRegion()), + config.WithCustomAuth(r.providerData.RoundTripper), + config.WithRegion(r.providerData.GetRegion()), ) } @@ -276,15 +310,15 @@ func (r *clusterResource) Configure(ctx context.Context, req resource.ConfigureR return } - if providerData.ServiceEnablementCustomEndpoint != "" { + if r.providerData.ServiceEnablementCustomEndpoint != "" { enablementClient, err = serviceenablement.NewAPIClient( - config.WithCustomAuth(providerData.RoundTripper), - config.WithEndpoint(providerData.ServiceEnablementCustomEndpoint), + config.WithCustomAuth(r.providerData.RoundTripper), + config.WithEndpoint(r.providerData.ServiceEnablementCustomEndpoint), ) } else { enablementClient, err = serviceenablement.NewAPIClient( - config.WithCustomAuth(providerData.RoundTripper), - config.WithRegion(providerData.GetRegion()), + config.WithCustomAuth(r.providerData.RoundTripper), + config.WithRegion(r.providerData.GetRegion()), ) } @@ -307,6 +341,7 @@ func (r *clusterResource) Schema(_ context.Context, _ resource.SchemaRequest, re "max_surge": "Maximum number of additional VMs that are created during an update.", "max_unavailable": "Maximum number of VMs that that can be unavailable during an update.", "nodepool_validators": "If set (larger than 0), then it must be at least the amount of zones configured for the nodepool. The `max_surge` and `max_unavailable` fields cannot both be unset at the same time.", + "region": "The resource region. If not defined, the provider region is used.", } resp.Schema = schema.Schema{ @@ -315,7 +350,7 @@ func (r *clusterResource) Schema(_ context.Context, _ resource.SchemaRequest, re MarkdownDescription: fmt.Sprintf("%s\n\n-> %s", descriptions["main"], descriptions["node_pools_plan_note"]), Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ - Description: "Terraform's internal resource ID. It is structured as \"`project_id`,`name`\".", + Description: "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`name`\".", Computed: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), @@ -643,6 +678,15 @@ func (r *clusterResource) Schema(_ context.Context, _ resource.SchemaRequest, re }, }, }, + "region": schema.StringAttribute{ + Optional: true, + // must be computed to allow for storing the override value from the provider + Computed: true, + Description: descriptions["region"], + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, }, } } @@ -707,18 +751,20 @@ func (r *clusterResource) Create(ctx context.Context, req resource.CreateRequest } projectId := model.ProjectId.ValueString() + region := model.Region.ValueString() clusterName := model.Name.ValueString() ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "name", clusterName) + ctx = tflog.SetField(ctx, "region", region) // If SKE functionality is not enabled, enable it - err := r.enablementClient.EnableService(ctx, projectId, utils.SKEServiceId).Execute() + err := r.enablementClient.EnableServiceRegional(ctx, region, projectId, utils.SKEServiceId).Execute() if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating cluster", fmt.Sprintf("Calling API to enable SKE: %v", err)) return } - _, err = enablementWait.EnableServiceWaitHandler(ctx, r.enablementClient, projectId, utils.SKEServiceId).WaitWithContext(ctx) + _, err = enablementWait.EnableServiceWaitHandler(ctx, r.enablementClient, region, projectId, utils.SKEServiceId).WaitWithContext(ctx) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating cluster", fmt.Sprintf("Wait for SKE enablement: %v", err)) return @@ -821,6 +867,7 @@ func (r *clusterResource) createOrUpdateCluster(ctx context.Context, diags *diag // cluster vars projectId := model.ProjectId.ValueString() name := model.Name.ValueString() + region := model.Region.ValueString() kubernetes, hasDeprecatedVersion, err := toKubernetesPayload(model, availableKubernetesVersions, currentKubernetesVersion, diags) if err != nil { core.LogAndAddError(ctx, diags, "Error creating/updating cluster", fmt.Sprintf("Creating cluster config API payload: %v", err)) @@ -881,7 +928,7 @@ func (r *clusterResource) createOrUpdateCluster(ctx context.Context, diags *diag core.LogAndAddWarning(ctx, diags, "Warning during creating/updating cluster", fmt.Sprintf("Cluster is in Impaired state due to an invalid argus instance id, the cluster is usable but metrics won't be forwarded: %s", *waitResp.Status.Error.Message)) } - err = mapFields(ctx, waitResp, model) + err = mapFields(ctx, waitResp, model, region) if err != nil { core.LogAndAddError(ctx, diags, "Error creating/updating cluster", fmt.Sprintf("Processing API payload: %v", err)) return @@ -1324,7 +1371,7 @@ func toNetworkPayload(ctx context.Context, m *Model) (*ske.Network, error) { }, nil } -func mapFields(ctx context.Context, cl *ske.Cluster, m *Model) error { +func mapFields(ctx context.Context, cl *ske.Cluster, m *Model, region string) error { if cl == nil { return fmt.Errorf("response input is nil") } @@ -1343,11 +1390,13 @@ func mapFields(ctx context.Context, cl *ske.Cluster, m *Model) error { m.Name = types.StringValue(name) idParts := []string{ m.ProjectId.ValueString(), + region, name, } m.Id = types.StringValue( strings.Join(idParts, core.Separator), ) + m.Region = types.StringValue(region) if cl.Kubernetes != nil { m.KubernetesVersionUsed = types.StringPointerValue(cl.Kubernetes.Version) @@ -2034,8 +2083,13 @@ func (r *clusterResource) Read(ctx context.Context, req resource.ReadRequest, re } projectId := state.ProjectId.ValueString() name := state.Name.ValueString() + region := state.Region.ValueString() + if region == "" { + region = r.providerData.GetRegion() + } ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "name", name) + ctx = tflog.SetField(ctx, "region", region) clResp, err := r.skeClient.GetCluster(ctx, projectId, name).Execute() if err != nil { @@ -2048,7 +2102,7 @@ func (r *clusterResource) Read(ctx context.Context, req resource.ReadRequest, re return } - err = mapFields(ctx, clResp, &state) + err = mapFields(ctx, clResp, &state, region) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading cluster", fmt.Sprintf("Processing API payload: %v", err)) return @@ -2084,8 +2138,10 @@ func (r *clusterResource) Update(ctx context.Context, req resource.UpdateRequest projectId := model.ProjectId.ValueString() clName := model.Name.ValueString() + region := model.Region.ValueString() ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "name", clName) + ctx = tflog.SetField(ctx, "region", region) availableKubernetesVersions, availableMachines, err := r.loadAvailableVersions(ctx) if err != nil { @@ -2116,8 +2172,10 @@ func (r *clusterResource) Delete(ctx context.Context, req resource.DeleteRequest } projectId := model.ProjectId.ValueString() name := model.Name.ValueString() + region := model.Region.ValueString() ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "name", name) + ctx = tflog.SetField(ctx, "region", region) c := r.skeClient _, err := c.DeleteCluster(ctx, projectId, name).Execute() @@ -2138,15 +2196,16 @@ func (r *clusterResource) Delete(ctx context.Context, req resource.DeleteRequest func (r *clusterResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { idParts := strings.Split(req.ID, core.Separator) - if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" { + if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" { core.LogAndAddError(ctx, &resp.Diagnostics, "Error importing cluster", - fmt.Sprintf("Expected import identifier with format: [project_id],[name] Got: %q", req.ID), + fmt.Sprintf("Expected import identifier with format: [project_id],[region],[name] Got: %q", req.ID), ) return } resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), idParts[0])...) - resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("name"), idParts[1])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("region"), idParts[1])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("name"), idParts[2])...) tflog.Info(ctx, "SKE cluster state imported") } diff --git a/stackit/internal/services/ske/cluster/resource_test.go b/stackit/internal/services/ske/cluster/resource_test.go index 4f5e4f5c..0552f147 100644 --- a/stackit/internal/services/ske/cluster/resource_test.go +++ b/stackit/internal/services/ske/cluster/resource_test.go @@ -33,11 +33,13 @@ func (c *skeClientMocked) GetClusterExecute(_ context.Context, _, _ string) (*sk func TestMapFields(t *testing.T) { cs := ske.ClusterStatusState("OK") + const testRegion = "region" tests := []struct { description string stateExtensions types.Object stateNodePools types.List input *ske.Cluster + region string expected Model isValid bool }{ @@ -48,8 +50,9 @@ func TestMapFields(t *testing.T) { &ske.Cluster{ Name: utils.Ptr("name"), }, + testRegion, Model{ - Id: types.StringValue("pid,name"), + Id: types.StringValue("pid,region,name"), ProjectId: types.StringValue("pid"), Name: types.StringValue("name"), KubernetesVersion: types.StringNull(), @@ -60,6 +63,7 @@ func TestMapFields(t *testing.T) { Hibernations: types.ListNull(types.ObjectType{AttrTypes: hibernationTypes}), Extensions: types.ObjectNull(extensionsTypes), EgressAddressRanges: types.ListNull(types.StringType), + Region: types.StringValue(testRegion), }, true, }, @@ -149,8 +153,9 @@ func TestMapFields(t *testing.T) { EgressAddressRanges: &[]string{"0.0.0.0/32", "1.1.1.1/32"}, }, }, + testRegion, Model{ - Id: types.StringValue("pid,name"), + Id: types.StringValue("pid,region,name"), ProjectId: types.StringValue("pid"), Name: types.StringValue("name"), KubernetesVersion: types.StringNull(), @@ -253,6 +258,7 @@ func TestMapFields(t *testing.T) { }), }), }), + Region: types.StringValue(testRegion), }, true, }, @@ -264,8 +270,9 @@ func TestMapFields(t *testing.T) { Name: utils.Ptr("name"), Network: &ske.Network{}, }, + testRegion, Model{ - Id: types.StringValue("pid,name"), + Id: types.StringValue("pid,region,name"), ProjectId: types.StringValue("pid"), Name: types.StringValue("name"), KubernetesVersion: types.StringNull(), @@ -276,6 +283,7 @@ func TestMapFields(t *testing.T) { Hibernations: types.ListNull(types.ObjectType{AttrTypes: hibernationTypes}), Extensions: types.ObjectNull(extensionsTypes), EgressAddressRanges: types.ListNull(types.StringType), + Region: types.StringValue(testRegion), }, true, }, @@ -300,8 +308,9 @@ func TestMapFields(t *testing.T) { }, Name: utils.Ptr("name"), }, + testRegion, Model{ - Id: types.StringValue("pid,name"), + Id: types.StringValue("pid,region,name"), ProjectId: types.StringValue("pid"), Name: types.StringValue("name"), KubernetesVersion: types.StringNull(), @@ -324,6 +333,7 @@ func TestMapFields(t *testing.T) { "zones": types.ListNull(types.StringType), }), }), + Region: types.StringValue(testRegion), }, true, }, @@ -348,8 +358,9 @@ func TestMapFields(t *testing.T) { Extensions: &ske.Extension{}, Name: utils.Ptr("name"), }, + testRegion, Model{ - Id: types.StringValue("pid,name"), + Id: types.StringValue("pid,region,name"), ProjectId: types.StringValue("pid"), Name: types.StringValue("name"), KubernetesVersion: types.StringNull(), @@ -372,6 +383,7 @@ func TestMapFields(t *testing.T) { "zones": types.ListNull(types.StringType), }), }), + Region: types.StringValue(testRegion), }, true, }, @@ -407,8 +419,9 @@ func TestMapFields(t *testing.T) { }, Name: utils.Ptr("name"), }, + testRegion, Model{ - Id: types.StringValue("pid,name"), + Id: types.StringValue("pid,region,name"), ProjectId: types.StringValue("pid"), Name: types.StringValue("name"), KubernetesVersion: types.StringNull(), @@ -433,6 +446,7 @@ func TestMapFields(t *testing.T) { "zones": types.ListNull(types.StringType), }), }), + Region: types.StringValue(testRegion), }, true, }, @@ -444,8 +458,9 @@ func TestMapFields(t *testing.T) { Extensions: &ske.Extension{}, Name: utils.Ptr("name"), }, + testRegion, Model{ - Id: types.StringValue("pid,name"), + Id: types.StringValue("pid,region,name"), ProjectId: types.StringValue("pid"), Name: types.StringValue("name"), KubernetesVersion: types.StringNull(), @@ -455,6 +470,7 @@ func TestMapFields(t *testing.T) { Hibernations: types.ListNull(types.ObjectType{AttrTypes: hibernationTypes}), Extensions: types.ObjectNull(extensionsTypes), EgressAddressRanges: types.ListNull(types.StringType), + Region: types.StringValue(testRegion), }, true, }, @@ -573,8 +589,9 @@ func TestMapFields(t *testing.T) { Hibernated: nil, }, }, + testRegion, Model{ - Id: types.StringValue("pid,name"), + Id: types.StringValue("pid,region,name"), ProjectId: types.StringValue("pid"), Name: types.StringValue("name"), KubernetesVersion: types.StringNull(), @@ -659,6 +676,7 @@ func TestMapFields(t *testing.T) { }), }), }), + Region: types.StringValue(testRegion), }, true, }, @@ -667,6 +685,7 @@ func TestMapFields(t *testing.T) { types.ObjectNull(extensionsTypes), types.ListNull(types.ObjectType{AttrTypes: nodePoolTypes}), nil, + testRegion, Model{}, false, }, @@ -675,6 +694,7 @@ func TestMapFields(t *testing.T) { types.ObjectNull(extensionsTypes), types.ListNull(types.ObjectType{AttrTypes: nodePoolTypes}), &ske.Cluster{}, + testRegion, Model{}, false, }, @@ -686,7 +706,7 @@ func TestMapFields(t *testing.T) { Extensions: tt.stateExtensions, NodePools: tt.stateNodePools, } - err := mapFields(context.Background(), tt.input, state) + err := mapFields(context.Background(), tt.input, state, tt.region) if !tt.isValid && err == nil { t.Fatalf("Should have failed") } diff --git a/stackit/internal/services/ske/project/datasource.go b/stackit/internal/services/ske/project/datasource.go deleted file mode 100644 index 3e0bc962..00000000 --- a/stackit/internal/services/ske/project/datasource.go +++ /dev/null @@ -1,142 +0,0 @@ -package ske - -import ( - "context" - "fmt" - - "github.com/hashicorp/terraform-plugin-framework/datasource" - "github.com/hashicorp/terraform-plugin-framework/datasource/schema" - "github.com/hashicorp/terraform-plugin-framework/schema/validator" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-log/tflog" - "github.com/stackitcloud/stackit-sdk-go/core/config" - "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement" - "github.com/stackitcloud/stackit-sdk-go/services/ske" - "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" - "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" - "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" -) - -// Ensure the implementation satisfies the expected interfaces. -var ( - _ datasource.DataSource = &projectDataSource{} -) - -// NewProjectDataSource is a helper function to simplify the provider implementation. -func NewProjectDataSource() datasource.DataSource { - return &projectDataSource{} -} - -// projectDataSource is the data source implementation. -type projectDataSource struct { - skeClient *ske.APIClient - enablementClient *serviceenablement.APIClient -} - -// Metadata returns the data source type name. -func (r *projectDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_ske_project" -} - -// Configure adds the provider configured client to the data source. -func (r *projectDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { - // Prevent panic if the provider has not been configured. - if req.ProviderData == nil { - return - } - - providerData, ok := req.ProviderData.(core.ProviderData) - if !ok { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error configuring API client", fmt.Sprintf("Expected configure type stackit.ProviderData, got %T", req.ProviderData)) - return - } - - var apiClient *ske.APIClient - var enablementClient *serviceenablement.APIClient - var err error - if providerData.SKECustomEndpoint != "" { - apiClient, err = ske.NewAPIClient( - config.WithCustomAuth(providerData.RoundTripper), - config.WithEndpoint(providerData.SKECustomEndpoint), - ) - } else { - apiClient, err = ske.NewAPIClient( - config.WithCustomAuth(providerData.RoundTripper), - config.WithRegion(providerData.GetRegion()), - ) - } - - if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error configuring API client", fmt.Sprintf("Configuring client: %v. This is an error related to the provider configuration, not to the data source configuration", err)) - return - } - - if providerData.ServiceEnablementCustomEndpoint != "" { - enablementClient, err = serviceenablement.NewAPIClient( - config.WithCustomAuth(providerData.RoundTripper), - config.WithEndpoint(providerData.ServiceEnablementCustomEndpoint), - ) - } else { - enablementClient, err = serviceenablement.NewAPIClient( - config.WithCustomAuth(providerData.RoundTripper), - config.WithRegion(providerData.GetRegion()), - ) - } - - if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error configuring Service Enablement API client", fmt.Sprintf("Configuring client: %v. This is an error related to the provider configuration, not to the resource configuration", err)) - return - } - - r.skeClient = apiClient - r.enablementClient = enablementClient - tflog.Info(ctx, "SKE client configured") -} - -// Schema defines the schema for the data source. -func (r *projectDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { - resp.Schema = schema.Schema{ - Description: "SKE project data source schema. Must have a `region` specified in the provider configuration. Warning: SKE project resource is no longer in use and will be removed with the next release. SKE service enablement is done automatically when a new cluster is created.", - DeprecationMessage: "SKE project datasource is no longer in use and will be removed after October 10th 2024. SKE service enablement is done automatically when a new cluster is created.", - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Description: "Terraform's internal data source. ID. It is structured as \"`project_id`\".", - Computed: true, - }, - "project_id": schema.StringAttribute{ - Description: "STACKIT Project ID in which the kubernetes project is enabled.", - Required: true, - Validators: []validator.String{ - validate.UUID(), - validate.NoSeparator(), - }, - }, - }, - } -} - -// Read refreshes the Terraform state with the latest data. -func (r *projectDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform - var model Model - diags := req.Config.Get(ctx, &model) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - projectId := model.ProjectId.ValueString() - ctx = tflog.SetField(ctx, "project_id", projectId) - _, err := r.enablementClient.GetServiceStatus(ctx, projectId, utils.SKEServiceId).Execute() - if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading project", fmt.Sprintf("Calling API: %v", err)) - return - } - model.Id = types.StringValue(projectId) - model.ProjectId = types.StringValue(projectId) - diags = resp.State.Set(ctx, model) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - tflog.Info(ctx, "SKE project read") -} diff --git a/stackit/internal/services/ske/project/resource.go b/stackit/internal/services/ske/project/resource.go deleted file mode 100644 index eb008f0f..00000000 --- a/stackit/internal/services/ske/project/resource.go +++ /dev/null @@ -1,249 +0,0 @@ -package ske - -import ( - "context" - "fmt" - "strings" - - "github.com/hashicorp/terraform-plugin-framework/path" - "github.com/hashicorp/terraform-plugin-framework/resource" - "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" - "github.com/hashicorp/terraform-plugin-framework/schema/validator" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-log/tflog" - "github.com/stackitcloud/stackit-sdk-go/core/config" - "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement" - enablementWait "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement/wait" - "github.com/stackitcloud/stackit-sdk-go/services/ske" - "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" - "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" - "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" -) - -// Ensure the implementation satisfies the expected interfaces. -var ( - _ resource.Resource = &projectResource{} - _ resource.ResourceWithConfigure = &projectResource{} - _ resource.ResourceWithImportState = &projectResource{} -) - -type Model struct { - Id types.String `tfsdk:"id"` - ProjectId types.String `tfsdk:"project_id"` -} - -// NewProjectResource is a helper function to simplify the provider implementation. -func NewProjectResource() resource.Resource { - return &projectResource{} -} - -// projectResource is the resource implementation. -type projectResource struct { - skeClient *ske.APIClient - enablementClient *serviceenablement.APIClient -} - -// Metadata returns the resource type name. -func (r *projectResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_ske_project" -} - -// Configure adds the provider configured client to the resource. -func (r *projectResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { - // Prevent panic if the provider has not been configured. - if req.ProviderData == nil { - return - } - - providerData, ok := req.ProviderData.(core.ProviderData) - if !ok { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error configuring API client", fmt.Sprintf("Expected configure type stackit.ProviderData, got %T", req.ProviderData)) - return - } - - var apiClient *ske.APIClient - var enablementClient *serviceenablement.APIClient - var err error - if providerData.SKECustomEndpoint != "" { - apiClient, err = ske.NewAPIClient( - config.WithCustomAuth(providerData.RoundTripper), - config.WithEndpoint(providerData.SKECustomEndpoint), - ) - } else { - apiClient, err = ske.NewAPIClient( - config.WithCustomAuth(providerData.RoundTripper), - config.WithRegion(providerData.GetRegion()), - ) - } - - if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error configuring SKE API client", fmt.Sprintf("Configuring client: %v. This is an error related to the provider configuration, not to the resource configuration", err)) - return - } - - if providerData.ServiceEnablementCustomEndpoint != "" { - enablementClient, err = serviceenablement.NewAPIClient( - config.WithCustomAuth(providerData.RoundTripper), - config.WithEndpoint(providerData.ServiceEnablementCustomEndpoint), - ) - } else { - enablementClient, err = serviceenablement.NewAPIClient( - config.WithCustomAuth(providerData.RoundTripper), - config.WithRegion(providerData.GetRegion()), - ) - } - - if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error configuring Service Enablement API client", fmt.Sprintf("Configuring client: %v. This is an error related to the provider configuration, not to the resource configuration", err)) - return - } - - r.skeClient = apiClient - r.enablementClient = enablementClient - tflog.Info(ctx, "SKE project client configured") -} - -// Schema returns the Terraform schema structure -func (r *projectResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ - Description: "SKE project resource schema. Must have a `region` specified in the provider configuration. This resource allows you to enable the SKE service and you can only have one per project. " + - "Before deleting this resource, all SKE clusters associated to the project must be deleted. Otherwise, error would occur due to the existing clusters. In such case, it is highly recommended to remove the SKE project from the state, directly using the \"`terraform state rm`\". " + - "Warning: SKE project resource is no longer in use and will be removed after October 10th 2024. SKE service enablement is done automatically when a new cluster is created.", - DeprecationMessage: "SKE project resource is no longer in use and will be removed after October 10th 2024. SKE service enablement is done automatically when a new cluster is created. " + - "For deleting the SKE project resource, it is highly recommended to remove the SKE project from the state, directly using the \"`terraform state rm`\".", - Attributes: map[string]schema.Attribute{ - "id": schema.StringAttribute{ - Description: "Terraform's internal resource ID. It is structured as \"`project_id`\".", - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "project_id": schema.StringAttribute{ - Description: "STACKIT Project ID in which the kubernetes project is enabled.", - Required: true, - Validators: []validator.String{ - validate.UUID(), - validate.NoSeparator(), - }, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, - }, - } -} - -// Create creates the resource and sets the initial Terraform state. -func (r *projectResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { // nolint:gocritic // function signature required by Terraform - var model Model - resp.Diagnostics.Append(req.Plan.Get(ctx, &model)...) - if resp.Diagnostics.HasError() { - return - } - projectId := model.ProjectId.ValueString() - - // If SKE functionality is not enabled, enable it - err := r.enablementClient.EnableService(ctx, projectId, utils.SKEServiceId).Execute() - if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating project", fmt.Sprintf("Calling API to enable SKE: %v", err)) - return - } - - _, err = enablementWait.EnableServiceWaitHandler(ctx, r.enablementClient, projectId, utils.SKEServiceId).WaitWithContext(ctx) - if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating project", fmt.Sprintf("Wait for SKE enablement: %v", err)) - return - } - - diags := resp.State.Set(ctx, model) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - tflog.Info(ctx, "SKE project created") -} - -// Read refreshes the Terraform state with the latest data. -func (r *projectResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform - var model Model - diags := req.State.Get(ctx, &model) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - projectId := model.ProjectId.ValueString() - _, err := r.enablementClient.GetServiceStatus(ctx, projectId, utils.SKEServiceId).Execute() - if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading project", fmt.Sprintf("Calling API: %v", err)) - return - } - model.Id = types.StringValue(projectId) - model.ProjectId = types.StringValue(projectId) - diags = resp.State.Set(ctx, model) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - tflog.Info(ctx, "SKE project read") -} - -// Update updates the resource and sets the updated Terraform state on success. -func (r *projectResource) Update(ctx context.Context, _ resource.UpdateRequest, resp *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform - // Update shouldn't be called - core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating project", "Project can't be updated") -} - -// Delete deletes the resource and removes the Terraform state on success. -func (r *projectResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { // nolint:gocritic // function signature required by Terraform - var model Model - resp.Diagnostics.Append(req.State.Get(ctx, &model)...) - if resp.Diagnostics.HasError() { - return - } - projectId := model.ProjectId.ValueString() - ctx = tflog.SetField(ctx, "project_id", projectId) - - c := r.skeClient - - clusters, err := c.ListClusters(ctx, projectId).Execute() - if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting project", fmt.Sprintf("Calling API to get the list of clusters: %v", err)) - return - } - - if clusters != nil && len(*clusters.Items) > 0 { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting project", fmt.Sprintln("You still have clusters in the project. Please delete them before deleting the project.")) - return - } - - err = r.enablementClient.DisableService(ctx, projectId, utils.SKEServiceId).Execute() - if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting project", fmt.Sprintf("Calling API to disable SKE: %v", err)) - return - } - - _, err = enablementWait.DisableServiceWaitHandler(ctx, r.enablementClient, projectId, utils.SKEServiceId).WaitWithContext(ctx) - if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting project", fmt.Sprintf("Wait for SKE disabling: %v", err)) - return - } - tflog.Info(ctx, "SKE project deleted") -} - -// ImportState imports a resource into the Terraform state on success. -// The expected format of the resource import identifier is: project_id -func (r *projectResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { // nolint:gocritic // function signature required by Terraform - idParts := strings.Split(req.ID, core.Separator) - if len(idParts) != 1 || idParts[0] == "" { - core.LogAndAddError(ctx, &resp.Diagnostics, - "Error importing project", - fmt.Sprintf("Expected import identifier with format: [project_id] Got: %q", req.ID), - ) - return - } - resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), idParts[0])...) - tflog.Info(ctx, "SKE project state imported") -} diff --git a/stackit/internal/services/ske/ske_acc_test.go b/stackit/internal/services/ske/ske_acc_test.go index 69ef2620..2ee93601 100644 --- a/stackit/internal/services/ske/ske_acc_test.go +++ b/stackit/internal/services/ske/ske_acc_test.go @@ -22,16 +22,16 @@ var clusterResource = map[string]string{ "name": fmt.Sprintf("cl-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)), "name_min": fmt.Sprintf("cl-min-%s", acctest.RandStringFromCharSet(3, acctest.CharSetAlphaNum)), "kubernetes_version_min": "1.30", - "kubernetes_version_used": "1.30.7", + "kubernetes_version_used": "1.30.10", "kubernetes_version_min_new": "1.31", "kubernetes_version_used_new": "1.31.4", "nodepool_name": "np-acc-test", "nodepool_name_min": "np-acc-min-test", "nodepool_machine_type": "b1.2", - "nodepool_os_version_min": "4081.2.0", - "nodepool_os_version_used": "4081.2.0", - "nodepool_os_version_min_new": "4081.2.1", - "nodepool_os_version_used_new": "4081.2.1", + "nodepool_os_version_min": "4081.2.1", + "nodepool_os_version_used": "4081.2.1", + "nodepool_os_version_min_new": "4152.2.1", + "nodepool_os_version_used_new": "4152.2.1", "nodepool_os_name": "flatcar", "nodepool_minimum": "2", "nodepool_maximum": "3", @@ -86,11 +86,15 @@ func getDnsConfig() string { ) } -func getConfig(kubernetesVersion, nodePoolMachineOSVersion string, maintenanceEnd *string) string { +func getConfig(kubernetesVersion, nodePoolMachineOSVersion string, maintenanceEnd, region *string) string { maintenanceEndTF := clusterResource["maintenance_end"] if maintenanceEnd != nil { maintenanceEndTF = *maintenanceEnd } + var regionConfig string + if region != nil { + regionConfig = fmt.Sprintf(`region = %q`, *region) + } return fmt.Sprintf(` %s @@ -148,6 +152,7 @@ func getConfig(kubernetesVersion, nodePoolMachineOSVersion string, maintenanceEn start = "%s" end = "%s" } + %s } resource "stackit_ske_kubeconfig" "kubeconfig" { @@ -193,6 +198,7 @@ func getConfig(kubernetesVersion, nodePoolMachineOSVersion string, maintenanceEn clusterResource["maintenance_enable_machine_image_version_updates"], clusterResource["maintenance_start"], maintenanceEndTF, + regionConfig, // Kubeconfig clusterResource["kubeconfig_expiration"], @@ -200,6 +206,7 @@ func getConfig(kubernetesVersion, nodePoolMachineOSVersion string, maintenanceEn } func TestAccSKE(t *testing.T) { + testRegion := utils.Ptr("eu01") resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, CheckDestroy: testAccCheckSKEDestroy, @@ -208,7 +215,7 @@ func TestAccSKE(t *testing.T) { // 1) Creation { - Config: getConfig(clusterResource["kubernetes_version_min"], clusterResource["nodepool_os_version_min"], nil), + Config: getConfig(clusterResource["kubernetes_version_min"], clusterResource["nodepool_os_version_min"], nil, testRegion), Check: resource.ComposeAggregateTestCheckFunc( // cluster data resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "name", clusterResource["name"]), @@ -250,6 +257,7 @@ func TestAccSKE(t *testing.T) { resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "maintenance.enable_machine_image_version_updates", clusterResource["maintenance_enable_machine_image_version_updates"]), resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "maintenance.start", clusterResource["maintenance_start"]), resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "maintenance.end", clusterResource["maintenance_end"]), + resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "region", *testRegion), // Kubeconfig @@ -277,15 +285,16 @@ func TestAccSKE(t *testing.T) { } `, - getConfig(clusterResource["kubernetes_version_min"], clusterResource["nodepool_os_version_min"], nil), + getConfig(clusterResource["kubernetes_version_min"], clusterResource["nodepool_os_version_min"], nil, testRegion), clusterResource["project_id"], clusterResource["name"], ), Check: resource.ComposeAggregateTestCheckFunc( // cluster data - resource.TestCheckResourceAttr("data.stackit_ske_cluster.cluster", "id", fmt.Sprintf("%s,%s", + resource.TestCheckResourceAttr("data.stackit_ske_cluster.cluster", "id", fmt.Sprintf("%s,%s,%s", clusterResource["project_id"], + *testRegion, clusterResource["name"], )), resource.TestCheckResourceAttr("data.stackit_ske_cluster.cluster", "project_id", clusterResource["project_id"]), @@ -339,7 +348,7 @@ func TestAccSKE(t *testing.T) { if !ok { return "", fmt.Errorf("couldn't find attribute name") } - return fmt.Sprintf("%s,%s", testutil.ProjectId, name), nil + return fmt.Sprintf("%s,%s,%s", testutil.ProjectId, testutil.Region, name), nil }, ImportState: true, ImportStateVerify: true, @@ -348,7 +357,7 @@ func TestAccSKE(t *testing.T) { }, // 4) Update kubernetes version, OS version and maintenance end { - Config: getConfig(clusterResource["kubernetes_version_min_new"], clusterResource["nodepool_os_version_min_new"], utils.Ptr(clusterResource["maintenance_end_new"])), + Config: getConfig(clusterResource["kubernetes_version_min_new"], clusterResource["nodepool_os_version_min_new"], utils.Ptr(clusterResource["maintenance_end_new"]), testRegion), Check: resource.ComposeAggregateTestCheckFunc( // cluster data resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "project_id", clusterResource["project_id"]), @@ -390,11 +399,12 @@ func TestAccSKE(t *testing.T) { resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "maintenance.enable_machine_image_version_updates", clusterResource["maintenance_enable_machine_image_version_updates"]), resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "maintenance.start", clusterResource["maintenance_start"]), resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "maintenance.end", clusterResource["maintenance_end_new"]), + resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "region", *testRegion), ), }, // 5) Downgrade kubernetes and nodepool machine OS version { - Config: getConfig(clusterResource["kubernetes_version_min"], clusterResource["nodepool_os_version_min"], utils.Ptr(clusterResource["maintenance_end_new"])), + Config: getConfig(clusterResource["kubernetes_version_min"], clusterResource["nodepool_os_version_min"], utils.Ptr(clusterResource["maintenance_end_new"]), testRegion), Check: resource.ComposeAggregateTestCheckFunc( // cluster data resource.TestCheckResourceAttr("stackit_ske_cluster.cluster", "project_id", clusterResource["project_id"]), @@ -432,7 +442,7 @@ func testAccCheckSKEDestroy(s *terraform.State) error { if rs.Type != "stackit_ske_cluster" { continue } - // cluster terraform ID: = "[project_id],[cluster_name]" + // cluster terraform ID: = "[project_id],[region],[cluster_name]" clusterName := strings.Split(rs.Primary.ID, core.Separator)[1] clustersToDestroy = append(clustersToDestroy, clusterName) } diff --git a/stackit/provider.go b/stackit/provider.go index c15ee818..a392e096 100644 --- a/stackit/provider.go +++ b/stackit/provider.go @@ -70,7 +70,6 @@ import ( serviceAccountToken "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/serviceaccount/token" skeCluster "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/ske/cluster" skeKubeconfig "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/ske/kubeconfig" - skeProject "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/ske/project" sqlServerFlexInstance "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/sqlserverflex/instance" sqlServerFlexUser "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/sqlserverflex/user" @@ -505,7 +504,6 @@ func (p *Provider) DataSources(_ context.Context) []func() datasource.DataSource serverUpdateSchedule.NewScheduleDataSource, serverUpdateSchedule.NewSchedulesDataSource, serviceAccount.NewServiceAccountDataSource, - skeProject.NewProjectDataSource, skeCluster.NewClusterDataSource, } } @@ -568,7 +566,6 @@ func (p *Provider) Resources(_ context.Context) []func() resource.Resource { serviceAccount.NewServiceAccountResource, serviceAccountToken.NewServiceAccountTokenResource, serviceAccountKey.NewServiceAccountKeyResource, - skeProject.NewProjectResource, skeCluster.NewClusterResource, skeKubeconfig.NewKubeconfigResource, }