fix: postgres_fixes #54

Merged
marcel.henselin merged 4 commits from fix/postgres_fixes into alpha 2026-02-13 08:15:22 +00:00
10 changed files with 242 additions and 140 deletions

View file

@ -123,3 +123,7 @@ func StringFromTemplate(tplFile string, data any) (string, error) {
return tplBuf.String(), nil return tplBuf.String(), nil
} }
func ResStr(prefix, resource, name string) string {
return fmt.Sprintf("%s_%s.%s", prefix, resource, name)
}

View file

@ -51,8 +51,8 @@ func mapFields(
return nil return nil
} }
// mapResourceFields maps fields from a ListDatabase API response to a resourceModel for the resource. // mapResourceFields maps fields from a GetDatabase API response to a resourceModel for the resource.
func mapResourceFields(source *postgresflexalpha.ListDatabase, model *resourceModel) error { func mapResourceFields(source *postgresflexalpha.GetDatabaseResponse, model *resourceModel) error {
if source == nil { if source == nil {
return fmt.Errorf("response is nil") return fmt.Errorf("response is nil")
} }

View file

@ -6,22 +6,22 @@ import (
"errors" "errors"
"fmt" "fmt"
"math" "math"
"net/http"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/identityschema" "github.com/hashicorp/terraform-plugin-framework/resource/identityschema"
"github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/postgresflexalpha" "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/postgresflexalpha"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/conversion" "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/conversion"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/core" "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/core"
postgresflexalpha2 "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/database/resources_gen" postgresflexalpha2 "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/database/resources_gen"
postgresflexUtils "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/utils" postgresflexUtils "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/utils"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/utils" "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/utils"
postgresflexalpha3 "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/wait/postgresflexalpha"
) )
var ( var (
@ -236,7 +236,10 @@ func (r *databaseResource) Create(
return return
} }
database, err := getDatabaseById(ctx, r.client, projectId, region, instanceId, databaseId) database, err := postgresflexalpha3.GetDatabaseByIdWaitHandler(ctx, r.client, projectId, region, instanceId, databaseId).
SetTimeout(15 * time.Minute).
SetSleepBeforeWait(15 * time.Second).
WaitWithContext(ctx)
if err != nil { if err != nil {
core.LogAndAddError( core.LogAndAddError(
ctx, ctx,
@ -304,14 +307,17 @@ func (r *databaseResource) Read(
ctx = tflog.SetField(ctx, "region", region) ctx = tflog.SetField(ctx, "region", region)
ctx = tflog.SetField(ctx, "database_id", databaseId) ctx = tflog.SetField(ctx, "database_id", databaseId)
databaseResp, err := getDatabaseById(ctx, r.client, projectId, region, instanceId, databaseId) databaseResp, err := postgresflexalpha3.GetDatabaseByIdWaitHandler(ctx, r.client, projectId, region, instanceId, databaseId).
SetTimeout(15 * time.Minute).
SetSleepBeforeWait(15 * time.Second).
WaitWithContext(ctx)
if err != nil { if err != nil {
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped core.LogAndAddError(
if (ok && oapiErr.StatusCode == http.StatusNotFound) || errors.Is(err, errDatabaseNotFound) { ctx,
resp.State.RemoveResource(ctx) &resp.Diagnostics,
return "Error creating database",
} fmt.Sprintf("Getting database details after creation: %v", err),
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading database", fmt.Sprintf("Calling API: %v", err)) )
return return
} }
@ -433,15 +439,12 @@ func (r *databaseResource) Update(
ctx = core.LogResponse(ctx) ctx = core.LogResponse(ctx)
// Map response body to schema databaseResp, err := postgresflexalpha3.GetDatabaseByIdWaitHandler(ctx, r.client, projectId, region, instanceId, databaseId64).
databaseResp, err := getDatabaseById(ctx, r.client, projectId, region, instanceId, databaseId64) SetTimeout(15 * time.Minute).
SetSleepBeforeWait(15 * time.Second).
WaitWithContext(ctx)
if err != nil { if err != nil {
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped core.LogAndAddError(ctx, &resp.Diagnostics, "error updating database", err.Error())
if (ok && oapiErr.StatusCode == http.StatusNotFound) || errors.Is(err, errDatabaseNotFound) {
resp.State.RemoveResource(ctx)
return
}
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading database", fmt.Sprintf("Calling API: %v", err))
return return
} }

View file

@ -19,6 +19,8 @@ import (
fwresource "github.com/hashicorp/terraform-plugin-framework/resource" fwresource "github.com/hashicorp/terraform-plugin-framework/resource"
) )
const pfx = "stackitprivatepreview_postgresflexalpha"
func TestInstanceResourceSchema(t *testing.T) { func TestInstanceResourceSchema(t *testing.T) {
t.Parallel() t.Parallel()
@ -149,6 +151,8 @@ func getExample() resData {
func TestAccInstance(t *testing.T) { func TestAccInstance(t *testing.T) {
exData := getExample() exData := getExample()
t.Logf(" ... working on instance %s", exData.TfName)
resName := fmt.Sprintf( resName := fmt.Sprintf(
"stackitprivatepreview_postgresflexalpha_instance.%s", "stackitprivatepreview_postgresflexalpha_instance.%s",
exData.TfName, exData.TfName,
@ -211,6 +215,8 @@ func TestAccInstance(t *testing.T) {
func TestAccInstanceWithUsers(t *testing.T) { func TestAccInstanceWithUsers(t *testing.T) {
data := getExample() data := getExample()
t.Logf(" ... working on instance %s", data.TfName)
userName := "testUser" userName := "testUser"
data.Users = []User{ data.Users = []User{
{ {
@ -220,16 +226,6 @@ func TestAccInstanceWithUsers(t *testing.T) {
}, },
} }
resName := fmt.Sprintf(
"stackitprivatepreview_postgresflexalpha_instance.%s",
data.TfName,
)
resUserName := fmt.Sprintf(
"stackitprivatepreview_postgresflexalpha_user.%s",
userName,
)
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
ProtoV6ProviderFactories: testutils.TestAccProtoV6ProviderFactories, ProtoV6ProviderFactories: testutils.TestAccProtoV6ProviderFactories,
@ -241,10 +237,10 @@ func TestAccInstanceWithUsers(t *testing.T) {
data, data,
), ),
Check: resource.ComposeAggregateTestCheckFunc( Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(resName, "name", data.Name), resource.TestCheckResourceAttr(testutils.ResStr(pfx, "instance", data.TfName), "name", data.Name),
resource.TestCheckResourceAttrSet(resName, "id"), resource.TestCheckResourceAttrSet(testutils.ResStr(pfx, "instance", data.TfName), "id"),
resource.TestCheckResourceAttr(resUserName, "name", userName), resource.TestCheckResourceAttr(testutils.ResStr(pfx, "user", userName), "name", userName),
resource.TestCheckResourceAttrSet(resUserName, "id"), resource.TestCheckResourceAttrSet(testutils.ResStr(pfx, "user", userName), "id"),
), ),
}, },
}, },
@ -253,6 +249,8 @@ func TestAccInstanceWithUsers(t *testing.T) {
func TestAccInstanceWithDatabases(t *testing.T) { func TestAccInstanceWithDatabases(t *testing.T) {
data := getExample() data := getExample()
t.Logf(" ... working on instance %s", data.TfName)
dbName := "testDb" dbName := "testDb"
userName := "testUser" userName := "testUser"
data.Users = []User{ data.Users = []User{

View file

@ -7,7 +7,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types"
postgresflex "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/postgresflexalpha" postgresflex "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/postgresflexalpha"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/conversion"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/core" "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/core"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/utils" "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/utils"
) )
@ -80,7 +79,7 @@ func toUpdatePayload(model *resourceModel, roles *[]string) (
} }
return &postgresflex.UpdateUserRequestPayload{ return &postgresflex.UpdateUserRequestPayload{
Name: conversion.StringValueToPointer(model.Name), Name: model.Name.ValueStringPointer(),
Roles: toPayloadRoles(roles), Roles: toPayloadRoles(roles),
}, nil }, nil
} }
@ -96,7 +95,7 @@ func toCreatePayload(model *resourceModel, roles *[]string) (*postgresflex.Creat
return &postgresflex.CreateUserRequestPayload{ return &postgresflex.CreateUserRequestPayload{
Roles: toPayloadRoles(roles), Roles: toPayloadRoles(roles),
Name: conversion.StringValueToPointer(model.Name), Name: model.Name.ValueStringPointer(),
}, nil }, nil
} }

View file

@ -3,24 +3,23 @@ package postgresflexalpha
import ( import (
"context" "context"
_ "embed" _ "embed"
"errors"
"fmt" "fmt"
"math" "math"
"net/http"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/resource/identityschema" "github.com/hashicorp/terraform-plugin-framework/resource/identityschema"
postgresflex "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/postgresflexalpha" postgresflex "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/postgresflexalpha"
postgresflexalpha "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/user/resources_gen" postgresflexalpha "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/user/resources_gen"
postgresflexUtils "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/utils" postgresflexUtils "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/utils"
postgresflexalphaWait "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/wait/postgresflexalpha"
"github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/conversion" "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/conversion"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/core" "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/core"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/utils" "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/utils"
@ -52,7 +51,7 @@ type UserResourceIdentityModel struct {
ProjectID types.String `tfsdk:"project_id"` ProjectID types.String `tfsdk:"project_id"`
Region types.String `tfsdk:"region"` Region types.String `tfsdk:"region"`
InstanceID types.String `tfsdk:"instance_id"` InstanceID types.String `tfsdk:"instance_id"`
UserID types.Int64 `tfsdk:"database_id"` UserID types.Int64 `tfsdk:"user_id"`
} }
// userResource implements the resource handling for a PostgreSQL Flex user. // userResource implements the resource handling for a PostgreSQL Flex user.
@ -150,13 +149,6 @@ func (r *userResource) Create(
return return
} }
// Read identity data
var identityData UserResourceIdentityModel
resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...)
if resp.Diagnostics.HasError() {
return
}
ctx = core.InitProviderContext(ctx) ctx = core.InitProviderContext(ctx)
arg := &clientArg{ arg := &clientArg{
@ -178,6 +170,7 @@ func (r *userResource) Create(
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating user", fmt.Sprintf("Creating API payload: %v", err)) core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating user", fmt.Sprintf("Creating API payload: %v", err))
return return
} }
// Create new user // Create new user
userResp, err := r.client.CreateUserRequest( userResp, err := r.client.CreateUserRequest(
ctx, ctx,
@ -185,13 +178,13 @@ func (r *userResource) Create(
arg.region, arg.region,
arg.instanceId, arg.instanceId,
).CreateUserRequestPayload(*payload).Execute() ).CreateUserRequestPayload(*payload).Execute()
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating user", fmt.Sprintf("Calling API: %v", err)) core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating user", fmt.Sprintf("Calling API: %v", err))
return return
} }
if userResp.Id == nil || *userResp.Id == 0 { id, ok := userResp.GetIdOk()
if !ok || id == 0 {
core.LogAndAddError( core.LogAndAddError(
ctx, ctx,
&resp.Diagnostics, &resp.Diagnostics,
@ -200,13 +193,9 @@ func (r *userResource) Create(
) )
return return
} }
model.Id = types.Int64Value(userResp.GetId()) arg.userId = id
model.UserId = types.Int64Value(userResp.GetId())
model.Password = types.StringValue(userResp.GetPassword())
model.Status = types.StringValue(userResp.GetStatus())
model.ConnectionString = types.StringValue(userResp.GetConnectionString())
ctx = tflog.SetField(ctx, "user_id", userResp.GetId()) ctx = tflog.SetField(ctx, "user_id", id)
ctx = core.LogResponse(ctx) ctx = core.LogResponse(ctx)
@ -215,28 +204,65 @@ func (r *userResource) Create(
ProjectID: types.StringValue(arg.projectId), ProjectID: types.StringValue(arg.projectId),
Region: types.StringValue(arg.region), Region: types.StringValue(arg.region),
InstanceID: types.StringValue(arg.instanceId), InstanceID: types.StringValue(arg.instanceId),
UserID: types.Int64Value(userResp.GetId()), UserID: types.Int64Value(id),
} }
resp.Diagnostics.Append(resp.Identity.Set(ctx, identity)...) resp.Diagnostics.Append(resp.Identity.Set(ctx, identity)...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
} }
// Verify creation model.Id = types.Int64Value(id)
exists, err := r.getUserResource(ctx, &model, arg) model.UserId = types.Int64Value(id)
model.Password = types.StringValue(userResp.GetPassword())
model.Status = types.StringValue(userResp.GetStatus())
model.ConnectionString = types.StringValue(userResp.GetConnectionString())
waitResp, err := postgresflexalphaWait.GetUserByIdWaitHandler(
ctx,
r.client,
arg.projectId,
arg.instanceId,
arg.region,
id,
).SetSleepBeforeWait(
10 * time.Second,
).SetTimeout(
15 * time.Minute,
).WaitWithContext(ctx)
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating user", fmt.Sprintf("Calling API: %v", err))
return
}
if !exists {
core.LogAndAddError( core.LogAndAddError(
ctx, &resp.Diagnostics, "Error creating user", ctx,
fmt.Sprintf("User ID '%v' resource not found after creation", model.UserId.ValueInt64()), &resp.Diagnostics,
"create user",
fmt.Sprintf("Instance creation waiting: %v", err),
) )
return return
} }
if waitResp.Id == nil {
core.LogAndAddError(
ctx,
&resp.Diagnostics,
"create user",
"Instance creation waiting: returned id is nil",
)
return
}
if waitResp.Id == nil || *waitResp.Id != id {
core.LogAndAddError(
ctx,
&resp.Diagnostics,
"create user",
fmt.Sprintf(
"Instance creation waiting: returned id is wrong: %+v - %+v",
waitResp.Id,
id,
),
)
return
}
// Set state to fully populated data // Set state to fully populated data
diags = resp.State.Set(ctx, model) diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)
@ -272,17 +298,39 @@ func (r *userResource) Read(
ctx = core.InitProviderContext(ctx) ctx = core.InitProviderContext(ctx)
// Read resource state // Read resource state
exists, err := r.getUserResource(ctx, &model, arg) waitResp, err := postgresflexalphaWait.GetUserByIdWaitHandler(
ctx,
r.client,
arg.projectId,
arg.instanceId,
arg.region,
model.UserId.ValueInt64(),
).SetSleepBeforeWait(
10 * time.Second,
).SetTimeout(
15 * time.Minute,
).WaitWithContext(ctx)
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading user", fmt.Sprintf("Calling API: %v", err)) core.LogAndAddError(
ctx,
&resp.Diagnostics,
"read user",
fmt.Sprintf("Instance creation waiting: %v", err),
)
return return
} }
if !exists { if waitResp.Id == nil || *waitResp.Id != model.UserId.ValueInt64() {
resp.State.RemoveResource(ctx) core.LogAndAddError(
ctx,
&resp.Diagnostics,
"read user",
"Instance creation waiting: returned id is nil or wrong",
)
return return
} }
arg.userId = *waitResp.Id
ctx = core.LogResponse(ctx) ctx = core.LogResponse(ctx)
@ -320,23 +368,12 @@ func (r *userResource) Update(
return return
} }
// Read identity data
var identityData UserResourceIdentityModel
resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...)
if resp.Diagnostics.HasError() {
return
}
ctx = core.InitProviderContext(ctx) ctx = core.InitProviderContext(ctx)
arg, errExt := r.extractIdentityData(model, identityData) arg := &clientArg{
if errExt != nil { projectId: model.ProjectId.ValueString(),
core.LogAndAddError( instanceId: model.InstanceId.ValueString(),
ctx, region: r.providerData.GetRegionWithOverride(model.Region),
&resp.Diagnostics,
extractErrorSummary,
fmt.Sprintf(extractErrorMessage, errExt),
)
} }
ctx = r.setTFLogFields(ctx, arg) ctx = r.setTFLogFields(ctx, arg)
@ -397,21 +434,40 @@ func (r *userResource) Update(
} }
// Verify update // Verify update
exists, err := r.getUserResource(ctx, &stateModel, arg) waitResp, err := postgresflexalphaWait.GetUserByIdWaitHandler(
ctx,
r.client,
arg.projectId,
arg.instanceId,
arg.region,
model.UserId.ValueInt64(),
).SetSleepBeforeWait(
10 * time.Second,
).SetTimeout(
15 * time.Minute,
).WaitWithContext(ctx)
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating user", fmt.Sprintf("Calling API: %v", err))
return
}
if !exists {
core.LogAndAddError( core.LogAndAddError(
ctx, &resp.Diagnostics, "Error updating user", ctx,
fmt.Sprintf("User ID '%v' resource not found after update", stateModel.UserId.ValueInt64()), &resp.Diagnostics,
"read user",
fmt.Sprintf("Instance creation waiting: %v", err),
) )
return return
} }
if waitResp.Id == nil || *waitResp.Id != model.UserId.ValueInt64() {
core.LogAndAddError(
ctx,
&resp.Diagnostics,
"read user",
"Instance creation waiting: returned id is nil or wrong",
)
return
}
arg.userId = *waitResp.Id
// Set state to fully populated data // Set state to fully populated data
diags = resp.State.Set(ctx, stateModel) diags = resp.State.Set(ctx, stateModel)
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)
@ -470,19 +526,19 @@ func (r *userResource) Delete(
ctx = core.LogResponse(ctx) ctx = core.LogResponse(ctx)
// Verify deletion // TODO: Verify deletion
exists, err := r.getUserResource(ctx, &model, arg) //exists, err := r.getUserResource(ctx, &model, arg)
if err != nil { //if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting user", fmt.Sprintf("Calling API: %v", err)) // core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting user", fmt.Sprintf("Calling API: %v", err))
return // return
} //}
if exists { //if exists {
core.LogAndAddError( // core.LogAndAddError(
ctx, &resp.Diagnostics, "Error deleting user", // ctx, &resp.Diagnostics, "Error deleting user",
fmt.Sprintf("User ID '%v' resource still exists after deletion", model.UserId.ValueInt64()), // fmt.Sprintf("User ID '%v' resource still exists after deletion", model.UserId.ValueInt64()),
) // )
return // return
} //}
resp.State.RemoveResource(ctx) resp.State.RemoveResource(ctx)
@ -513,34 +569,6 @@ func (r *userResource) IdentitySchema(
} }
} }
// getUserResource refreshes the resource state by calling the API and mapping the response to the model.
// Returns true if the resource state was successfully refreshed, false if the resource does not exist.
func (r *userResource) getUserResource(ctx context.Context, model *resourceModel, arg *clientArg) (bool, error) {
if arg.userId > math.MaxInt32 {
return false, errors.New("error in type conversion: int value too large (userId)")
}
userId := int32(arg.userId)
// API Call
userResp, err := r.client.GetUserRequest(ctx, arg.projectId, arg.region, arg.instanceId, userId).Execute()
if err != nil {
var oapiErr *oapierror.GenericOpenAPIError
if errors.As(err, &oapiErr) && oapiErr.StatusCode == http.StatusNotFound {
return false, nil
}
return false, fmt.Errorf("error fetching user resource: %w", err)
}
if err := mapResourceFields(userResp, model, arg.region); err != nil {
return false, fmt.Errorf("error mapping user resource: %w", err)
}
return true, nil
}
// clientArg holds the arguments for API calls. // clientArg holds the arguments for API calls.
type clientArg struct { type clientArg struct {
projectId string projectId string

View file

@ -31,7 +31,7 @@ fields:
- name: 'owner' - name: 'owner'
modifiers: modifiers:
- 'RequiresReplace' - 'UseStateForUnknown'
- name: 'database_name' - name: 'database_name'
modifiers: modifiers:
@ -43,6 +43,7 @@ fields:
- name: 'compatibility' - name: 'compatibility'
modifiers: modifiers:
- 'UseStateForUnknown'
- 'RequiresReplace' - 'RequiresReplace'
- name: 'compatibility_level' - name: 'compatibility_level'

View file

@ -120,6 +120,7 @@ func getExample() resData {
func TestAccInstance(t *testing.T) { func TestAccInstance(t *testing.T) {
exData := getExample() exData := getExample()
t.Logf(" ... working on instance %s", exData.TfName)
updNameData := exData updNameData := exData
updNameData.Name = "name-updated" updNameData.Name = "name-updated"

View file

@ -3,16 +3,13 @@ fields:
modifiers: modifiers:
- 'UseStateForUnknown' - 'UseStateForUnknown'
- name: 'user_id'
modifiers:
- 'UseStateForUnknown'
- name: 'instance_id' - name: 'instance_id'
validators: validators:
- validate.NoSeparator - validate.NoSeparator
- validate.UUID - validate.UUID
modifiers: modifiers:
- 'UseStateForUnknown' - 'UseStateForUnknown'
- 'RequiresReplace'
- name: 'project_id' - name: 'project_id'
validators: validators:
@ -28,6 +25,7 @@ fields:
- name: 'user_id' - name: 'user_id'
modifiers: modifiers:
- 'UseStateForUnknown'
- 'RequiresReplace' - 'RequiresReplace'
- name: 'username' - name: 'username'

View file

@ -2,7 +2,10 @@ package postgresflexalpha
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"math"
"net/http"
"time" "time"
"github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-plugin-log/tflog"
@ -40,10 +43,18 @@ type APIClientInstanceInterface interface {
// APIClientUserInterface Interface needed for tests // APIClientUserInterface Interface needed for tests
type APIClientUserInterface interface { type APIClientUserInterface interface {
GetUserRequestExecute(ctx context.Context, projectId, region, instanceId string, userId int64) ( GetUserRequestExecute(ctx context.Context, projectId, region, instanceId string, userId int32) (
*postgresflex.GetUserResponse, *postgresflex.GetUserResponse,
error, error,
) )
GetDatabaseRequestExecute(
ctx context.Context,
projectId string,
region string,
instanceId string,
databaseId int32,
) (*postgresflex.GetDatabaseResponse, error)
} }
// CreateInstanceWaitHandler will wait for instance creation // CreateInstanceWaitHandler will wait for instance creation
@ -202,3 +213,62 @@ func PartialUpdateInstanceWaitHandler(
handler.SetTimeout(45 * time.Minute).SetSleepBeforeWait(30 * time.Second) handler.SetTimeout(45 * time.Minute).SetSleepBeforeWait(30 * time.Second)
return handler return handler
} }
// GetUserByIdWaitHandler will wait for instance creation
func GetUserByIdWaitHandler(
ctx context.Context,
a APIClientUserInterface,
projectId, instanceId, region string,
userId int64,
) *wait.AsyncActionHandler[postgresflex.GetUserResponse] {
handler := wait.New(
func() (waitFinished bool, response *postgresflex.GetUserResponse, err error) {
if userId > math.MaxInt32 {
return false, nil, fmt.Errorf("userId value is too big for int32")
}
userId32 := int32(userId)
s, err := a.GetUserRequestExecute(ctx, projectId, region, instanceId, userId32)
if err != nil {
var oapiErr *oapierror.GenericOpenAPIError
ok := errors.As(err, &oapiErr)
if !ok {
return false, nil, fmt.Errorf("could not convert error to oapierror.GenericOpenAPIError")
}
if oapiErr.StatusCode != http.StatusNotFound {
return false, nil, err
}
return false, nil, nil
}
return true, s, nil
},
)
return handler
}
// GetDatabaseByIdWaitHandler will wait for instance creation
func GetDatabaseByIdWaitHandler(
ctx context.Context,
a APIClientUserInterface,
projectId, instanceId, region string,
databaseId int64,
) *wait.AsyncActionHandler[postgresflex.GetDatabaseResponse] {
handler := wait.New(
func() (waitFinished bool, response *postgresflex.GetDatabaseResponse, err error) {
dbId32 := int32(databaseId)
s, err := a.GetDatabaseRequestExecute(ctx, projectId, region, instanceId, dbId32)
if err != nil {
var oapiErr *oapierror.GenericOpenAPIError
ok := errors.As(err, &oapiErr)
if !ok {
return false, nil, fmt.Errorf("could not convert error to oapierror.GenericOpenAPIError")
}
if oapiErr.StatusCode != http.StatusNotFound {
return false, nil, err
}
return false, nil, nil
}
return true, s, nil
},
)
return handler
}