fix: pgsql user waiter
Some checks failed
CI Workflow / Check GoReleaser config (pull_request) Successful in 5s
CI Workflow / CI (pull_request) Failing after 19m55s
CI Workflow / Code coverage report (pull_request) Has been skipped
CI Workflow / Test readiness for publishing provider (pull_request) Successful in 31m10s

This commit is contained in:
Marcel S. Henselin 2026-02-13 09:14:27 +01:00
parent f2c9af8fc5
commit 440e8bf900
5 changed files with 116 additions and 78 deletions

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,8 +236,10 @@ func (r *databaseResource) Create(
return return
} }
// TODO: wait handler bauen database, err := postgresflexalpha3.GetDatabaseByIdWaitHandler(ctx, r.client, projectId, region, instanceId, databaseId).
database, err := getDatabaseById(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,
@ -305,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
} }
@ -434,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

@ -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,10 +3,8 @@ package postgresflexalpha
import ( import (
"context" "context"
_ "embed" _ "embed"
"errors"
"fmt" "fmt"
"math" "math"
"net/http"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -22,7 +20,6 @@ import (
"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"
@ -243,12 +240,25 @@ func (r *userResource) Create(
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 { if waitResp.Id == nil || *waitResp.Id != id {
core.LogAndAddError( core.LogAndAddError(
ctx, ctx,
&resp.Diagnostics, &resp.Diagnostics,
"create user", "create user",
"Instance creation waiting: returned id is nil or wrong", fmt.Sprintf(
"Instance creation waiting: returned id is wrong: %+v - %+v",
waitResp.Id,
id,
),
) )
return return
} }
@ -311,7 +321,7 @@ func (r *userResource) Read(
return return
} }
if waitResp.Id == nil || *waitResp.Id != arg.userId { if waitResp.Id == nil || *waitResp.Id != model.UserId.ValueInt64() {
core.LogAndAddError( core.LogAndAddError(
ctx, ctx,
&resp.Diagnostics, &resp.Diagnostics,
@ -424,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...)
@ -497,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)
@ -540,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

@ -47,6 +47,14 @@ type APIClientUserInterface interface {
*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
@ -236,3 +244,31 @@ func GetUserByIdWaitHandler(
) )
return handler 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
}