Skip to content

Commit 1444376

Browse files
authored
fix(postgresflex): User role should be updatable (#733)
Signed-off-by: Alexander Dahmen <alexander.dahmen@inovex.de>
1 parent 3dc4fed commit 1444376

File tree

2 files changed

+164
-7
lines changed

2 files changed

+164
-7
lines changed

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

Lines changed: 81 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import (
2020
"github.com/hashicorp/terraform-plugin-framework/resource"
2121
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
2222
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
23-
"github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier"
2423
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
2524
"github.com/hashicorp/terraform-plugin-framework/types"
2625
"github.com/stackitcloud/stackit-sdk-go/core/config"
@@ -202,9 +201,6 @@ func (r *userResource) Schema(_ context.Context, _ resource.SchemaRequest, resp
202201
Description: descriptions["roles"],
203202
ElementType: types.StringType,
204203
Required: true,
205-
PlanModifiers: []planmodifier.Set{
206-
setplanmodifier.RequiresReplace(),
207-
},
208204
Validators: []validator.Set{
209205
setvalidator.ValueStringsAre(
210206
stringvalidator.OneOf("login", "createdb"),
@@ -344,9 +340,74 @@ func (r *userResource) Read(ctx context.Context, req resource.ReadRequest, resp
344340
}
345341

346342
// Update updates the resource and sets the updated Terraform state on success.
347-
func (r *userResource) Update(ctx context.Context, _ resource.UpdateRequest, resp *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform
348-
// Update shouldn't be called
349-
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating user", "User can't be updated")
343+
func (r *userResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform
344+
// Retrieve values from plan
345+
var model Model
346+
diags := req.Plan.Get(ctx, &model)
347+
resp.Diagnostics.Append(diags...)
348+
if resp.Diagnostics.HasError() {
349+
return
350+
}
351+
projectId := model.ProjectId.ValueString()
352+
instanceId := model.InstanceId.ValueString()
353+
userId := model.UserId.ValueString()
354+
region := model.Region.ValueString()
355+
ctx = tflog.SetField(ctx, "project_id", projectId)
356+
ctx = tflog.SetField(ctx, "instance_id", instanceId)
357+
ctx = tflog.SetField(ctx, "user_id", userId)
358+
ctx = tflog.SetField(ctx, "region", region)
359+
360+
// Retrieve values from state
361+
var stateModel Model
362+
diags = req.State.Get(ctx, &stateModel)
363+
resp.Diagnostics.Append(diags...)
364+
if resp.Diagnostics.HasError() {
365+
return
366+
}
367+
368+
var roles []string
369+
if !(model.Roles.IsNull() || model.Roles.IsUnknown()) {
370+
diags = model.Roles.ElementsAs(ctx, &roles, false)
371+
resp.Diagnostics.Append(diags...)
372+
if resp.Diagnostics.HasError() {
373+
return
374+
}
375+
}
376+
377+
// Generate API request body from model
378+
payload, err := toUpdatePayload(&model, roles)
379+
if err != nil {
380+
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating user", fmt.Sprintf("Updating API payload: %v", err))
381+
return
382+
}
383+
384+
// Update existing instance
385+
err = r.client.UpdateUser(ctx, projectId, region, instanceId, userId).UpdateUserPayload(*payload).Execute()
386+
if err != nil {
387+
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating user", err.Error())
388+
return
389+
}
390+
391+
userResp, err := r.client.GetUser(ctx, projectId, region, instanceId, userId).Execute()
392+
if err != nil {
393+
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating user", fmt.Sprintf("Calling API: %v", err))
394+
return
395+
}
396+
397+
// Map response body to schema
398+
err = mapFields(userResp, &stateModel, region)
399+
if err != nil {
400+
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating user", fmt.Sprintf("Processing API payload: %v", err))
401+
return
402+
}
403+
404+
// Set state to fully populated data
405+
diags = resp.State.Set(ctx, stateModel)
406+
resp.Diagnostics.Append(diags...)
407+
if resp.Diagnostics.HasError() {
408+
return
409+
}
410+
tflog.Info(ctx, "Postgres Flex user updated")
350411
}
351412

352413
// Delete deletes the resource and removes the Terraform state on success.
@@ -515,3 +576,16 @@ func toCreatePayload(model *Model, roles []string) (*postgresflex.CreateUserPayl
515576
Username: conversion.StringValueToPointer(model.Username),
516577
}, nil
517578
}
579+
580+
func toUpdatePayload(model *Model, roles []string) (*postgresflex.UpdateUserPayload, error) {
581+
if model == nil {
582+
return nil, fmt.Errorf("nil model")
583+
}
584+
if roles == nil {
585+
return nil, fmt.Errorf("nil roles")
586+
}
587+
588+
return &postgresflex.UpdateUserPayload{
589+
Roles: &roles,
590+
}, nil
591+
}

stackit/internal/services/postgresflex/user/resource_test.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,3 +385,86 @@ func TestToCreatePayload(t *testing.T) {
385385
})
386386
}
387387
}
388+
389+
func TestToUpdatePayload(t *testing.T) {
390+
tests := []struct {
391+
description string
392+
input *Model
393+
inputRoles []string
394+
expected *postgresflex.UpdateUserPayload
395+
isValid bool
396+
}{
397+
{
398+
"default_values",
399+
&Model{},
400+
[]string{},
401+
&postgresflex.UpdateUserPayload{
402+
Roles: &[]string{},
403+
},
404+
true,
405+
},
406+
{
407+
"default_values",
408+
&Model{
409+
Username: types.StringValue("username"),
410+
},
411+
[]string{
412+
"role_1",
413+
"role_2",
414+
},
415+
&postgresflex.UpdateUserPayload{
416+
Roles: &[]string{
417+
"role_1",
418+
"role_2",
419+
},
420+
},
421+
true,
422+
},
423+
{
424+
"null_fields_and_int_conversions",
425+
&Model{
426+
Username: types.StringNull(),
427+
},
428+
[]string{
429+
"",
430+
},
431+
&postgresflex.UpdateUserPayload{
432+
Roles: &[]string{
433+
"",
434+
},
435+
},
436+
true,
437+
},
438+
{
439+
"nil_model",
440+
nil,
441+
[]string{},
442+
nil,
443+
false,
444+
},
445+
{
446+
"nil_roles",
447+
&Model{},
448+
nil,
449+
nil,
450+
false,
451+
},
452+
}
453+
for _, tt := range tests {
454+
t.Run(tt.description, func(t *testing.T) {
455+
output, err := toUpdatePayload(tt.input, tt.inputRoles)
456+
if !tt.isValid && err == nil {
457+
t.Fatalf("Should have failed")
458+
}
459+
if tt.isValid && err != nil {
460+
t.Fatalf("Should not have failed: %v", err)
461+
}
462+
if tt.isValid {
463+
diff := cmp.Diff(output, tt.expected)
464+
if diff != "" {
465+
t.Fatalf("Data does not match: %s", diff)
466+
}
467+
}
468+
})
469+
}
470+
}

0 commit comments

Comments
 (0)