Skip to content

Commit 717323f

Browse files
authored
fix(sqlserverflex): Store ids immediately after provisioning (#1280)
STACKITTPR-395 Signed-off-by: Alexander Dahmen <alexander.dahmen@inovex.de>
1 parent 8555662 commit 717323f

3 files changed

Lines changed: 133 additions & 12 deletions

File tree

stackit/internal/services/sqlserverflex/instance/resource.go

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import (
1414
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
1515
"github.com/hashicorp/terraform-plugin-framework/attr"
1616
"github.com/hashicorp/terraform-plugin-framework/diag"
17-
"github.com/hashicorp/terraform-plugin-framework/path"
1817
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
1918
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
2019
"github.com/hashicorp/terraform-plugin-log/tflog"
@@ -407,8 +406,21 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques
407406

408407
ctx = core.LogResponse(ctx)
409408

409+
if createResp.Id == nil {
410+
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", "Got empty instance id")
411+
return
412+
}
413+
410414
instanceId := *createResp.Id
411-
ctx = tflog.SetField(ctx, "instance_id", instanceId)
415+
// Write id attributes to state before polling via the wait handler - just in case anything goes wrong during the wait handler
416+
ctx = utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{
417+
"project_id": projectId,
418+
"region": region,
419+
"instance_id": instanceId,
420+
})
421+
if resp.Diagnostics.HasError() {
422+
return
423+
}
412424
// The creation waiter sometimes returns an error from the API: "instance with id xxx has unexpected status Failure"
413425
// which can be avoided by sleeping before wait
414426
waitResp, err := wait.CreateInstanceWaitHandler(ctx, r.client, projectId, instanceId, region).SetSleepBeforeWait(30 * time.Second).WaitWithContext(ctx)
@@ -653,9 +665,11 @@ func (r *instanceResource) ImportState(ctx context.Context, req resource.ImportS
653665
return
654666
}
655667

656-
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), idParts[0])...)
657-
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("region"), idParts[1])...)
658-
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("instance_id"), idParts[2])...)
668+
ctx = utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{
669+
"project_id": idParts[0],
670+
"region": idParts[1],
671+
"instance_id": idParts[2],
672+
})
659673
tflog.Info(ctx, "SQLServer Flex instance state imported")
660674
}
661675

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package sqlserverflex
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/sqlserverflex"
13+
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil"
14+
)
15+
16+
func TestSQLServerFlexInstanceSavesIDsOnError(t *testing.T) {
17+
projectId := uuid.NewString()
18+
instanceId := uuid.NewString()
19+
const (
20+
name = "instance-name"
21+
flavorCpu = 4
22+
flavorRam = 16
23+
flavorId = "4.16-Single"
24+
region = "eu01"
25+
)
26+
s := testutil.NewMockServer(t)
27+
defer s.Server.Close()
28+
tfConfig := fmt.Sprintf(`
29+
provider "stackit" {
30+
default_region = "%s"
31+
sqlserverflex_custom_endpoint = "%s"
32+
service_account_token = "mock-server-needs-no-auth"
33+
}
34+
35+
resource "stackit_sqlserverflex_instance" "instance" {
36+
project_id = "%s"
37+
name = "%s"
38+
flavor = {
39+
cpu = %d
40+
ram = %d
41+
}
42+
}
43+
44+
`, region, s.Server.URL, projectId, name, flavorCpu, flavorRam)
45+
flavor := testutil.MockResponse{
46+
ToJsonBody: &sqlserverflex.ListFlavorsResponse{
47+
Flavors: &[]sqlserverflex.InstanceFlavorEntry{
48+
{
49+
Cpu: utils.Ptr(int64(flavorCpu)),
50+
Memory: utils.Ptr(int64(flavorRam)),
51+
Id: utils.Ptr(flavorId),
52+
Description: utils.Ptr("test-flavor-id"),
53+
},
54+
},
55+
},
56+
}
57+
58+
resource.UnitTest(t, resource.TestCase{
59+
ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
60+
Steps: []resource.TestStep{
61+
{
62+
PreConfig: func() {
63+
s.Reset(
64+
flavor,
65+
testutil.MockResponse{
66+
Description: "create",
67+
ToJsonBody: sqlserverflex.CreateInstanceResponse{
68+
Id: utils.Ptr(instanceId),
69+
},
70+
},
71+
testutil.MockResponse{
72+
Description: "failing waiter",
73+
StatusCode: http.StatusInternalServerError,
74+
},
75+
)
76+
},
77+
Config: tfConfig,
78+
ExpectError: regexp.MustCompile("Error creating instance.*"),
79+
},
80+
{
81+
PreConfig: func() {
82+
s.Reset(
83+
testutil.MockResponse{
84+
Description: "refresh",
85+
Handler: func(w http.ResponseWriter, req *http.Request) {
86+
expected := fmt.Sprintf("/v2/projects/%s/regions/%s/instances/%s", projectId, region, instanceId)
87+
if req.URL.Path != expected {
88+
t.Errorf("expected request to %s, got %s", expected, req.URL.Path)
89+
}
90+
w.WriteHeader(http.StatusInternalServerError)
91+
},
92+
},
93+
testutil.MockResponse{Description: "delete", StatusCode: http.StatusAccepted},
94+
testutil.MockResponse{Description: "delete waiter", StatusCode: http.StatusNotFound},
95+
)
96+
},
97+
RefreshState: true,
98+
ExpectError: regexp.MustCompile("Error reading instance*"),
99+
},
100+
},
101+
})
102+
}

stackit/internal/services/sqlserverflex/user/resource.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import (
1616
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
1717

1818
"github.com/hashicorp/terraform-plugin-framework/attr"
19-
"github.com/hashicorp/terraform-plugin-framework/path"
2019
"github.com/hashicorp/terraform-plugin-framework/resource"
2120
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
2221
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
@@ -255,8 +254,12 @@ func (r *userResource) Create(ctx context.Context, req resource.CreateRequest, r
255254
return
256255
}
257256
userId := *userResp.Item.Id
258-
ctx = tflog.SetField(ctx, "user_id", userId)
259-
257+
ctx = utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{
258+
"project_id": projectId,
259+
"region": region,
260+
"instance_id": instanceId,
261+
"user_id": userId,
262+
})
260263
// Map response body to schema
261264
err = mapFieldsCreate(userResp, &model, region)
262265
if err != nil {
@@ -372,10 +375,12 @@ func (r *userResource) ImportState(ctx context.Context, req resource.ImportState
372375
return
373376
}
374377

375-
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), idParts[0])...)
376-
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("region"), idParts[1])...)
377-
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("instance_id"), idParts[2])...)
378-
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("user_id"), idParts[3])...)
378+
ctx = utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{
379+
"project_id": idParts[0],
380+
"region": idParts[1],
381+
"instance_id": idParts[2],
382+
"user_id": idParts[3],
383+
})
379384
core.LogAndAddWarning(ctx, &resp.Diagnostics,
380385
"SQLServer Flex user imported with empty password",
381386
"The user password is not imported as it is only available upon creation of a new user. The password field will be empty.",

0 commit comments

Comments
 (0)