From a2c25bede212ae426fb2fbe98dfcd6477e1940ef Mon Sep 17 00:00:00 2001 From: Alexander Dahmen Date: Wed, 26 Mar 2025 14:19:28 +0100 Subject: [PATCH] fix(mongodb): User role should be updatable (#731) Signed-off-by: Alexander Dahmen --- .../services/mongodbflex/user/resource.go | 83 ++++++++++++++++- .../mongodbflex/user/resource_test.go | 88 +++++++++++++++++++ 2 files changed, 168 insertions(+), 3 deletions(-) diff --git a/stackit/internal/services/mongodbflex/user/resource.go b/stackit/internal/services/mongodbflex/user/resource.go index fd6aa28c..753115db 100644 --- a/stackit/internal/services/mongodbflex/user/resource.go +++ b/stackit/internal/services/mongodbflex/user/resource.go @@ -287,9 +287,72 @@ func (r *userResource) Read(ctx context.Context, req resource.ReadRequest, resp } // Update updates the resource and sets the updated Terraform state on success. -func (r *userResource) 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 user", "User can't be updated") +func (r *userResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform + // Retrieve values from plan + var model Model + diags := req.Plan.Get(ctx, &model) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + projectId := model.ProjectId.ValueString() + instanceId := model.InstanceId.ValueString() + userId := model.UserId.ValueString() + ctx = tflog.SetField(ctx, "project_id", projectId) + ctx = tflog.SetField(ctx, "instance_id", instanceId) + ctx = tflog.SetField(ctx, "user_id", userId) + + // Retrieve values from state + var stateModel Model + diags = req.State.Get(ctx, &stateModel) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + var roles []string + if !(model.Roles.IsNull() || model.Roles.IsUnknown()) { + diags = model.Roles.ElementsAs(ctx, &roles, false) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + } + + // Generate API request body from model + payload, err := toUpdatePayload(&model, roles) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating user", fmt.Sprintf("Updating API payload: %v", err)) + return + } + + // Update existing instance + err = r.client.UpdateUser(ctx, projectId, instanceId, userId).UpdateUserPayload(*payload).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating user", err.Error()) + return + } + + userResp, err := r.client.GetUser(ctx, projectId, instanceId, userId).Execute() + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating user", fmt.Sprintf("Calling API: %v", err)) + return + } + + // Map response body to schema + err = mapFields(userResp, &stateModel) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating user", fmt.Sprintf("Processing API payload: %v", err)) + return + } + + // Set state to fully populated data + diags = resp.State.Set(ctx, stateModel) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + tflog.Info(ctx, "MongoDB Flex user updated") } // Delete deletes the resource and removes the Terraform state on success. @@ -450,3 +513,17 @@ func toCreatePayload(model *Model, roles []string) (*mongodbflex.CreateUserPaylo Database: conversion.StringValueToPointer(model.Database), }, nil } + +func toUpdatePayload(model *Model, roles []string) (*mongodbflex.UpdateUserPayload, error) { + if model == nil { + return nil, fmt.Errorf("nil model") + } + if roles == nil { + return nil, fmt.Errorf("nil roles") + } + + return &mongodbflex.UpdateUserPayload{ + Roles: &roles, + Database: conversion.StringValueToPointer(model.Database), + }, nil +} diff --git a/stackit/internal/services/mongodbflex/user/resource_test.go b/stackit/internal/services/mongodbflex/user/resource_test.go index 69dcc396..cc888fff 100644 --- a/stackit/internal/services/mongodbflex/user/resource_test.go +++ b/stackit/internal/services/mongodbflex/user/resource_test.go @@ -377,3 +377,91 @@ func TestToCreatePayload(t *testing.T) { }) } } + +func TestToUpdatePayload(t *testing.T) { + tests := []struct { + description string + input *Model + inputRoles []string + expected *mongodbflex.UpdateUserPayload + isValid bool + }{ + { + "default_values", + &Model{}, + []string{}, + &mongodbflex.UpdateUserPayload{ + Roles: &[]string{}, + Database: nil, + }, + true, + }, + { + "simple values", + &Model{ + Username: types.StringValue("username"), + Database: types.StringValue("database"), + }, + []string{ + "role_1", + "role_2", + }, + &mongodbflex.UpdateUserPayload{ + Roles: &[]string{ + "role_1", + "role_2", + }, + Database: utils.Ptr("database"), + }, + true, + }, + { + "null_fields", + &Model{ + Username: types.StringNull(), + Database: types.StringNull(), + }, + []string{ + "", + }, + &mongodbflex.UpdateUserPayload{ + Roles: &[]string{ + "", + }, + Database: nil, + }, + true, + }, + { + "nil_model", + nil, + []string{}, + nil, + false, + }, + { + "nil_roles", + &Model{}, + nil, + nil, + false, + }, + } + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + output, err := toUpdatePayload(tt.input, tt.inputRoles) + if !tt.isValid && err == nil { + t.Fatalf("Should have failed") + } + if tt.isValid && err != nil { + t.Fatalf("Should not have failed: %v", err) + } + if tt.isValid { + diff := cmp.Diff(output, tt.expected) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } + } + }) + } +}