Skip to content

Commit f4be46e

Browse files
FyuselGokceGK
andauthored
fix(ske): Store ids immediately after provisioning (#1289)
STACKITTPR-397 Signed-off-by: Alexander Dahmen <alexander.dahmen@inovex.de> Co-authored-by: Gökce Gök Klingel <161626272+GokceGK@users.noreply.github.com>
1 parent 871a62d commit f4be46e

3 files changed

Lines changed: 169 additions & 11 deletions

File tree

stackit/internal/services/ske/cluster/resource.go

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import (
1717
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
1818
"github.com/hashicorp/terraform-plugin-framework/attr"
1919
"github.com/hashicorp/terraform-plugin-framework/diag"
20-
"github.com/hashicorp/terraform-plugin-framework/path"
2120
"github.com/hashicorp/terraform-plugin-framework/resource"
2221
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
2322
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
@@ -730,9 +729,16 @@ func (r *clusterResource) Create(ctx context.Context, req resource.CreateRequest
730729
projectId := model.ProjectId.ValueString()
731730
region := model.Region.ValueString()
732731
clusterName := model.Name.ValueString()
733-
ctx = tflog.SetField(ctx, "project_id", projectId)
734-
ctx = tflog.SetField(ctx, "name", clusterName)
735-
ctx = tflog.SetField(ctx, "region", region)
732+
733+
// Write id attributes to state before polling via the wait handler - just in case anything goes wrong during the wait handler
734+
ctx = utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{
735+
"project_id": projectId,
736+
"region": region,
737+
"name": clusterName,
738+
})
739+
if resp.Diagnostics.HasError() {
740+
return
741+
}
736742

737743
// If SKE functionality is not enabled, enable it
738744
err := r.enablementClient.EnableServiceRegional(ctx, region, projectId, utils.SKEServiceId).Execute()
@@ -2237,8 +2243,10 @@ func (r *clusterResource) ImportState(ctx context.Context, req resource.ImportSt
22372243
return
22382244
}
22392245

2240-
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), idParts[0])...)
2241-
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("region"), idParts[1])...)
2242-
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("name"), idParts[2])...)
2246+
ctx = utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{
2247+
"project_id": idParts[0],
2248+
"region": idParts[1],
2249+
"name": idParts[2],
2250+
})
22432251
tflog.Info(ctx, "SKE cluster state imported")
22442252
}

stackit/internal/services/ske/kubeconfig/resource.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -255,10 +255,12 @@ func (r *kubeconfigResource) Create(ctx context.Context, req resource.CreateRequ
255255

256256
model.KubeconfigId = types.StringValue(kubeconfigUUID)
257257

258-
ctx = tflog.SetField(ctx, "project_id", projectId)
259-
ctx = tflog.SetField(ctx, "cluster_name", clusterName)
260-
ctx = tflog.SetField(ctx, "kube_config_id", kubeconfigUUID)
261-
ctx = tflog.SetField(ctx, "region", region)
258+
ctx = utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{
259+
"project_id": projectId,
260+
"cluster_name": clusterName,
261+
"kube_config_id": kubeconfigUUID,
262+
"region": region,
263+
})
262264

263265
err := r.createKubeconfig(ctx, &model)
264266

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package ske
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"regexp"
7+
"testing"
8+
9+
"github.com/google/uuid"
10+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
11+
"github.com/stackitcloud/stackit-sdk-go/core/utils"
12+
"github.com/stackitcloud/stackit-sdk-go/services/serviceenablement"
13+
"github.com/stackitcloud/stackit-sdk-go/services/ske"
14+
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil"
15+
)
16+
17+
func TestSKEClusterSavesIDsOnError(t *testing.T) {
18+
projectId := uuid.NewString()
19+
const (
20+
clusterName = "cluster-name"
21+
kubernetesVersionMin = "1.33.8"
22+
region = "eu01"
23+
machineType = "g2i.2"
24+
)
25+
s := testutil.NewMockServer(t)
26+
defer s.Server.Close()
27+
tfConfig := fmt.Sprintf(`
28+
provider "stackit" {
29+
default_region = "%s"
30+
ske_custom_endpoint = "%[2]s"
31+
service_enablement_custom_endpoint = "%[2]s"
32+
service_account_token = "mock-server-needs-no-auth"
33+
}
34+
35+
resource "stackit_ske_cluster" "cluster" {
36+
project_id = "%s"
37+
name = "%s"
38+
kubernetes_version_min = "%s"
39+
node_pools = [{
40+
availability_zones = ["eu01-1"]
41+
machine_type = "%s"
42+
os_version_min = "1.0.0"
43+
maximum = 2
44+
minimum = 1
45+
name = "node-name"
46+
}
47+
]
48+
}
49+
50+
`, region, s.Server.URL, projectId, clusterName, kubernetesVersionMin, machineType)
51+
52+
resource.UnitTest(t, resource.TestCase{
53+
ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
54+
Steps: []resource.TestStep{
55+
{
56+
PreConfig: func() {
57+
s.Reset(
58+
testutil.MockResponse{
59+
Description: "service enablement request",
60+
ToJsonBody: serviceenablement.ServiceStatus{
61+
State: utils.Ptr(serviceenablement.SERVICESTATUSSTATE_ENABLED),
62+
},
63+
StatusCode: http.StatusOK,
64+
},
65+
testutil.MockResponse{
66+
Description: "service enablement wait handler",
67+
ToJsonBody: serviceenablement.ServiceStatus{
68+
State: utils.Ptr(serviceenablement.SERVICESTATUSSTATE_ENABLED),
69+
Error: nil,
70+
},
71+
StatusCode: http.StatusOK,
72+
},
73+
testutil.MockResponse{
74+
Description: "kubernetes versions",
75+
ToJsonBody: ske.ProviderOptions{
76+
MachineImages: utils.Ptr([]ske.MachineImage{
77+
{
78+
Name: utils.Ptr("flatcar"),
79+
Versions: utils.Ptr([]ske.MachineImageVersion{
80+
{
81+
State: utils.Ptr("supported"),
82+
Version: utils.Ptr("1.0.0"),
83+
ExpirationDate: nil,
84+
Cri: utils.Ptr([]ske.CRI{
85+
{
86+
Name: utils.Ptr(ske.CRINAME_CONTAINERD),
87+
},
88+
}),
89+
},
90+
}),
91+
},
92+
}),
93+
MachineTypes: utils.Ptr([]ske.MachineType{
94+
{
95+
Name: utils.Ptr(machineType),
96+
},
97+
}),
98+
KubernetesVersions: utils.Ptr([]ske.KubernetesVersion{
99+
{
100+
State: utils.Ptr("supported"),
101+
ExpirationDate: nil,
102+
Version: utils.Ptr(kubernetesVersionMin),
103+
},
104+
}),
105+
},
106+
},
107+
testutil.MockResponse{
108+
Description: "create",
109+
ToJsonBody: ske.Cluster{
110+
Name: utils.Ptr(string(clusterName)),
111+
},
112+
},
113+
testutil.MockResponse{
114+
Description: "failing waiter",
115+
StatusCode: http.StatusInternalServerError,
116+
},
117+
)
118+
},
119+
Config: tfConfig,
120+
ExpectError: regexp.MustCompile("Error creating/updating cluster.*"),
121+
},
122+
{
123+
PreConfig: func() {
124+
s.Reset(
125+
testutil.MockResponse{
126+
Description: "refresh",
127+
Handler: func(w http.ResponseWriter, req *http.Request) {
128+
expected := fmt.Sprintf("/v2/projects/%s/regions/%s/clusters/%s", projectId, region, clusterName)
129+
if req.URL.Path != expected {
130+
t.Errorf("expected request to %s, got %s", expected, req.URL.Path)
131+
}
132+
w.WriteHeader(http.StatusInternalServerError)
133+
},
134+
},
135+
testutil.MockResponse{Description: "delete", StatusCode: http.StatusAccepted},
136+
testutil.MockResponse{Description: "ListClusterResponse is called for checking removal",
137+
ToJsonBody: ske.ListClustersResponse{
138+
Items: &[]ske.Cluster{},
139+
},
140+
},
141+
)
142+
},
143+
RefreshState: true,
144+
ExpectError: regexp.MustCompile("Error reading cluster*"),
145+
},
146+
},
147+
})
148+
}

0 commit comments

Comments
 (0)