diff --git a/stackit/internal/services/postgresflexalpha/database/mapper.go b/stackit/internal/services/postgresflexalpha/database/mapper.go index 5785f4b7..9e38fe3f 100644 --- a/stackit/internal/services/postgresflexalpha/database/mapper.go +++ b/stackit/internal/services/postgresflexalpha/database/mapper.go @@ -51,8 +51,8 @@ func mapFields( return nil } -// mapResourceFields maps fields from a ListDatabase API response to a resourceModel for the resource. -func mapResourceFields(source *postgresflexalpha.ListDatabase, model *resourceModel) error { +// mapResourceFields maps fields from a GetDatabase API response to a resourceModel for the resource. +func mapResourceFields(source *postgresflexalpha.GetDatabaseResponse, model *resourceModel) error { if source == nil { return fmt.Errorf("response is nil") } diff --git a/stackit/internal/services/postgresflexalpha/database/resource.go b/stackit/internal/services/postgresflexalpha/database/resource.go index 84c830c5..530bb98c 100644 --- a/stackit/internal/services/postgresflexalpha/database/resource.go +++ b/stackit/internal/services/postgresflexalpha/database/resource.go @@ -6,22 +6,22 @@ import ( "errors" "fmt" "math" - "net/http" "strconv" "strings" + "time" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/identityschema" "github.com/hashicorp/terraform-plugin-framework/types" "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/stackit/internal/conversion" "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" 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" + postgresflexalpha3 "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/wait/postgresflexalpha" ) var ( @@ -236,8 +236,10 @@ func (r *databaseResource) Create( return } - // TODO: wait handler bauen - 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 { core.LogAndAddError( ctx, @@ -305,14 +307,17 @@ func (r *databaseResource) Read( ctx = tflog.SetField(ctx, "region", region) 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 { - 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 - 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)) + core.LogAndAddError( + ctx, + &resp.Diagnostics, + "Error creating database", + fmt.Sprintf("Getting database details after creation: %v", err), + ) return } @@ -434,15 +439,12 @@ func (r *databaseResource) Update( ctx = core.LogResponse(ctx) - // Map response body to schema - databaseResp, err := getDatabaseById(ctx, r.client, projectId, region, instanceId, databaseId64) + databaseResp, err := postgresflexalpha3.GetDatabaseByIdWaitHandler(ctx, r.client, projectId, region, instanceId, databaseId64). + SetTimeout(15 * time.Minute). + SetSleepBeforeWait(15 * time.Second). + WaitWithContext(ctx) 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 - 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)) + core.LogAndAddError(ctx, &resp.Diagnostics, "error updating database", err.Error()) return } diff --git a/stackit/internal/services/postgresflexalpha/user/mapper.go b/stackit/internal/services/postgresflexalpha/user/mapper.go index 37c8fc1f..8fb4278f 100644 --- a/stackit/internal/services/postgresflexalpha/user/mapper.go +++ b/stackit/internal/services/postgresflexalpha/user/mapper.go @@ -7,7 +7,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/types" 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/utils" ) @@ -80,7 +79,7 @@ func toUpdatePayload(model *resourceModel, roles *[]string) ( } return &postgresflex.UpdateUserRequestPayload{ - Name: conversion.StringValueToPointer(model.Name), + Name: model.Name.ValueStringPointer(), Roles: toPayloadRoles(roles), }, nil } @@ -96,7 +95,7 @@ func toCreatePayload(model *resourceModel, roles *[]string) (*postgresflex.Creat return &postgresflex.CreateUserRequestPayload{ Roles: toPayloadRoles(roles), - Name: conversion.StringValueToPointer(model.Name), + Name: model.Name.ValueStringPointer(), }, nil } diff --git a/stackit/internal/services/postgresflexalpha/user/resource.go b/stackit/internal/services/postgresflexalpha/user/resource.go index 5be7f650..c6cad445 100644 --- a/stackit/internal/services/postgresflexalpha/user/resource.go +++ b/stackit/internal/services/postgresflexalpha/user/resource.go @@ -3,10 +3,8 @@ package postgresflexalpha import ( "context" _ "embed" - "errors" "fmt" "math" - "net/http" "strconv" "strings" "time" @@ -22,7 +20,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/types" "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/core" "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/utils" @@ -243,12 +240,25 @@ func (r *userResource) Create( 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", - "Instance creation waiting: returned id is nil or wrong", + fmt.Sprintf( + "Instance creation waiting: returned id is wrong: %+v - %+v", + waitResp.Id, + id, + ), ) return } @@ -311,7 +321,7 @@ func (r *userResource) Read( return } - if waitResp.Id == nil || *waitResp.Id != arg.userId { + if waitResp.Id == nil || *waitResp.Id != model.UserId.ValueInt64() { core.LogAndAddError( ctx, &resp.Diagnostics, @@ -424,21 +434,40 @@ func (r *userResource) 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 { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating user", fmt.Sprintf("Calling API: %v", err)) - return - } - - if !exists { core.LogAndAddError( - ctx, &resp.Diagnostics, "Error updating user", - fmt.Sprintf("User ID '%v' resource not found after update", stateModel.UserId.ValueInt64()), + ctx, + &resp.Diagnostics, + "read user", + fmt.Sprintf("Instance creation waiting: %v", err), ) 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 diags = resp.State.Set(ctx, stateModel) resp.Diagnostics.Append(diags...) @@ -497,19 +526,19 @@ func (r *userResource) Delete( ctx = core.LogResponse(ctx) - // Verify deletion - exists, err := r.getUserResource(ctx, &model, arg) - if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting user", fmt.Sprintf("Calling API: %v", err)) - return - } - if exists { - core.LogAndAddError( - ctx, &resp.Diagnostics, "Error deleting user", - fmt.Sprintf("User ID '%v' resource still exists after deletion", model.UserId.ValueInt64()), - ) - return - } + // TODO: Verify deletion + //exists, err := r.getUserResource(ctx, &model, arg) + //if err != nil { + // core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting user", fmt.Sprintf("Calling API: %v", err)) + // return + //} + //if exists { + // core.LogAndAddError( + // ctx, &resp.Diagnostics, "Error deleting user", + // fmt.Sprintf("User ID '%v' resource still exists after deletion", model.UserId.ValueInt64()), + // ) + // return + //} 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. type clientArg struct { projectId string diff --git a/stackit/internal/wait/postgresflexalpha/wait.go b/stackit/internal/wait/postgresflexalpha/wait.go index 505fc0a1..8c8b80b3 100644 --- a/stackit/internal/wait/postgresflexalpha/wait.go +++ b/stackit/internal/wait/postgresflexalpha/wait.go @@ -47,6 +47,14 @@ type APIClientUserInterface interface { *postgresflex.GetUserResponse, error, ) + + GetDatabaseRequestExecute( + ctx context.Context, + projectId string, + region string, + instanceId string, + databaseId int32, + ) (*postgresflex.GetDatabaseResponse, error) } // CreateInstanceWaitHandler will wait for instance creation @@ -236,3 +244,31 @@ func GetUserByIdWaitHandler( ) 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 +}