Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions docs/resources/postgresflex_user.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,13 @@ import {
to = stackit_postgresflex_user.import-example
id = "${var.project_id},${var.region},${var.postgres_instance_id},${var.user_id}"
}

# Only use the import statement, if you want to import an existing postgresflex user
# usually the imported user will not have a password after importing
# to be able to reference the imported users password, add reset as fifth parameter
# (this will reset the password of the imported user)
import {
to = stackit_postgresflex_user.import-example
id = "${var.project_id},${var.region},${var.postgres_instance_id},${var.user_id},reset"
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,12 @@ import {
to = stackit_postgresflex_user.import-example
id = "${var.project_id},${var.region},${var.postgres_instance_id},${var.user_id}"
}

# Only use the import statement, if you want to import an existing postgresflex user
# usually the imported user will not have a password after importing
# to be able to reference the imported users password, add reset as fifth parameter
# (this will reset the password of the imported user)
import {
to = stackit_postgresflex_user.import-example
id = "${var.project_id},${var.region},${var.postgres_instance_id},${var.user_id},reset"
}
58 changes: 53 additions & 5 deletions stackit/internal/services/postgresflex/postgresflex_acc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,12 @@ var testConfigDatabaseVarsMinUpdated = func() config.Variables {

// User - MIN
var testConfigUserVarsMin = config.Variables{
"project_id": config.StringVariable(testutil.ProjectId),
"name": config.StringVariable(fmt.Sprintf("tf-acc-%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlphaNum))),
"acl": config.StringVariable("192.168.0.0/24"),
"backup_schedule": config.StringVariable("0 16 * * *"),
"flavor_id": config.StringVariable("4.8-replica"),
"project_id": config.StringVariable(testutil.ProjectId),
"name": config.StringVariable(fmt.Sprintf("tf-acc-%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlphaNum))),
"acl": config.StringVariable("192.168.0.0/24"),
"backup_schedule": config.StringVariable("0 16 * * *"),
// "flavor_id": config.StringVariable("4.8-replica"),
"flavor_id": config.StringVariable("2.4"),
"storage_class": config.StringVariable("premium-perf2-stackit"),
"storage_size": config.IntegerVariable(5),
"instance_version": config.StringVariable("16"),
Expand Down Expand Up @@ -722,6 +723,7 @@ func TestAccPostgresFlexDatabaseMin(t *testing.T) {
}

func TestAccPostgresFlexUserMin(t *testing.T) {
var initialPassword string
resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
CheckDestroy: testCheckDestroy,
Expand Down Expand Up @@ -832,6 +834,52 @@ func TestAccPostgresFlexUserMin(t *testing.T) {
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"password", "uri"},
},
// Import with password reset
{
ConfigVariables: testConfigUserVarsMin,
ResourceName: "stackit_postgresflex_user.user",
ImportStateIdFunc: func(s *terraform.State) (string, error) {
r, ok := s.RootModule().Resources["stackit_postgresflex_user.user"]
if !ok {
return "", fmt.Errorf("couldn't find resource stackit_postgresflex_user.user")
}

projectId, ok := r.Primary.Attributes["project_id"]
if !ok {
return "", fmt.Errorf("couldn't find attribute project_id")
}
region, ok := r.Primary.Attributes["region"]
if !ok {
return "", fmt.Errorf("couldn't find attribute region")
}
instanceId, ok := r.Primary.Attributes["instance_id"]
if !ok {
return "", fmt.Errorf("couldn't find attribute instance_id")
}
userId, ok := r.Primary.Attributes["user_id"]
if !ok {
return "", fmt.Errorf("couldn't find attribute user_id")
}
initialPassword = r.Primary.Attributes["password"]
return fmt.Sprintf("%s,%s,%s,%s,reset", projectId, region, instanceId, userId), nil
},
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"password", "uri"},
ImportStateCheck: func(s []*terraform.InstanceState) error {
if len(s) != 1 {
return fmt.Errorf("expected 1 state, got %d", len(s))
}
newPassword, ok := s[0].Attributes["password"]
if !ok || newPassword == "" {
return fmt.Errorf("expected password to be set in imported state")
}
if initialPassword != "" && newPassword == initialPassword {
return fmt.Errorf("expected password to have changed after reset, but got same password")
}
return nil
},
},
// Update
{
ConfigVariables: testConfigUserVarsMinUpdated,
Expand Down
77 changes: 75 additions & 2 deletions stackit/internal/services/postgresflex/user/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,7 @@ func (r *userResource) Delete(ctx context.Context, req resource.DeleteRequest, r

_, err = wait.DeleteUserWaitHandler(ctx, r.client.DefaultAPI, projectId, region, instanceId, userId).WaitWithContext(ctx)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting user", fmt.Sprintf("Instance deletion waiting: %v", err))
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting user", fmt.Sprintf("User deletion waiting: %v", err))
return
}
tflog.Info(ctx, "Postgres Flex user deleted")
Expand All @@ -564,6 +564,24 @@ func (r *userResource) Delete(ctx context.Context, req resource.DeleteRequest, r
// The expected format of the resource import identifier is: project_id,zone_id,record_set_id
func (r *userResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
idParts := strings.Split(req.ID, core.Separator)

if len(idParts) == 4 {
r.defaultImportState(ctx, req, resp, idParts)
return
}

if len(idParts) == 5 {
r.withPasswordResetImportState(ctx, req, resp, idParts)
return
}

core.LogAndAddError(ctx, &resp.Diagnostics,
"Error importing user",
fmt.Sprintf("Expected import identifier with format [project_id],[region],[instance_id],[user_id] - got %q", req.ID),
)
}

func (r *userResource) defaultImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse, idParts []string) {
if len(idParts) != 4 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" || idParts[3] == "" {
core.LogAndAddError(ctx, &resp.Diagnostics,
"Error importing user",
Expand All @@ -577,12 +595,67 @@ func (r *userResource) ImportState(ctx context.Context, req resource.ImportState
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("instance_id"), idParts[2])...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("user_id"), idParts[3])...)
core.LogAndAddWarning(ctx, &resp.Diagnostics,
"Postgresflex user imported with empty password and empty uri",
"Postgresflex user imported with empty password and uri",
"The user password and uri are not imported as they are only available upon creation of a new user. The password and uri fields will be empty.",
)
tflog.Info(ctx, "Postgresflex user state imported")
}

func (r *userResource) withPasswordResetImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse, idParts []string) {
if len(idParts) != 5 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" || idParts[3] == "" || idParts[4] != "reset" {
core.LogAndAddError(ctx, &resp.Diagnostics,
"Error importing user",
fmt.Sprintf("Expected import identifier with format [project_id],[region],[instance_id],[user_id],reset, got %q", req.ID),
)
return
}

userId, err := strconv.ParseInt(idParts[3], 10, 64)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics,
"Error importing user",
fmt.Sprintf("Could not convert value %q to int64", idParts[3]),
)
return
}

resPwResp, err := r.client.DefaultAPI.ResetUserPassword(ctx, idParts[0], idParts[1], idParts[2], userId).Execute()
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics,
"Error importing user",
fmt.Sprintf("Could not convert value %q to int64", idParts[3]),
)
return
}

if resPwResp == nil {
core.LogAndAddError(ctx, &resp.Diagnostics,
"Error importing user",
"Api response is nil",
)
return
}

pw, ok := resPwResp.GetPasswordOk()
if !ok {
core.LogAndAddError(ctx, &resp.Diagnostics,
"Error importing user",
fmt.Sprintf("Returned password is not ok, got %q", *pw),
)
}

resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), idParts[0])...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("region"), idParts[1])...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("instance_id"), idParts[2])...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("user_id"), idParts[3])...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("password"), *pw)...)
core.LogAndAddWarning(ctx, &resp.Diagnostics,
"Postgresflex user imported with empty uri",
"The user uri is not imported as it is only available upon creation of a new user. The uri field will be empty.",
)
tflog.Info(ctx, "Postgresflex user state imported")
}

func mapFieldsCreate(userResp *postgresflex.CreateUserResponse, getUserResp *postgresflex.GetUserResponse, instanceResp *postgresflex.GetInstanceResponse, model *Model, region string) error {
if userResp == nil {
return fmt.Errorf("create response is nil")
Expand Down
Loading