diff --git a/internal/testutils/functions.go b/internal/testutils/functions.go index e672d57c..0006751a 100644 --- a/internal/testutils/functions.go +++ b/internal/testutils/functions.go @@ -123,3 +123,7 @@ func StringFromTemplate(tplFile string, data any) (string, error) { return tplBuf.String(), nil } + +func ResStr(prefix, resource, name string) string { + return fmt.Sprintf("%s_%s.%s", prefix, resource, name) +} diff --git a/stackit/internal/services/postgresflexalpha/postgresflex_acc_test.go b/stackit/internal/services/postgresflexalpha/postgresflex_acc_test.go index 29facfee..7ec8c4f0 100644 --- a/stackit/internal/services/postgresflexalpha/postgresflex_acc_test.go +++ b/stackit/internal/services/postgresflexalpha/postgresflex_acc_test.go @@ -19,6 +19,8 @@ import ( fwresource "github.com/hashicorp/terraform-plugin-framework/resource" ) +const pfx = "stackitprivatepreview_postgresflexalpha" + func TestInstanceResourceSchema(t *testing.T) { t.Parallel() @@ -149,6 +151,8 @@ func getExample() resData { func TestAccInstance(t *testing.T) { exData := getExample() + t.Logf(" ... working on instance %s", exData.TfName) + resName := fmt.Sprintf( "stackitprivatepreview_postgresflexalpha_instance.%s", exData.TfName, @@ -211,6 +215,8 @@ func TestAccInstance(t *testing.T) { func TestAccInstanceWithUsers(t *testing.T) { data := getExample() + t.Logf(" ... working on instance %s", data.TfName) + userName := "testUser" 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{ PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testutils.TestAccProtoV6ProviderFactories, @@ -241,10 +237,10 @@ func TestAccInstanceWithUsers(t *testing.T) { data, ), Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr(resName, "name", data.Name), - resource.TestCheckResourceAttrSet(resName, "id"), - resource.TestCheckResourceAttr(resUserName, "name", userName), - resource.TestCheckResourceAttrSet(resUserName, "id"), + resource.TestCheckResourceAttr(testutils.ResStr(pfx, "instance", data.TfName), "name", data.Name), + resource.TestCheckResourceAttrSet(testutils.ResStr(pfx, "instance", data.TfName), "id"), + resource.TestCheckResourceAttr(testutils.ResStr(pfx, "user", userName), "name", userName), + resource.TestCheckResourceAttrSet(testutils.ResStr(pfx, "user", userName), "id"), ), }, }, @@ -253,6 +249,8 @@ func TestAccInstanceWithUsers(t *testing.T) { func TestAccInstanceWithDatabases(t *testing.T) { data := getExample() + t.Logf(" ... working on instance %s", data.TfName) + dbName := "testDb" userName := "testUser" data.Users = []User{ diff --git a/stackit/internal/services/postgresflexalpha/user/resource.go b/stackit/internal/services/postgresflexalpha/user/resource.go index 8984a4ea..d4f9e78e 100644 --- a/stackit/internal/services/postgresflexalpha/user/resource.go +++ b/stackit/internal/services/postgresflexalpha/user/resource.go @@ -9,12 +9,14 @@ import ( "net/http" "strconv" "strings" + "time" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/resource/identityschema" 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" 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/resource" @@ -52,7 +54,7 @@ type UserResourceIdentityModel struct { ProjectID types.String `tfsdk:"project_id"` Region types.String `tfsdk:"region"` 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. @@ -150,13 +152,6 @@ func (r *userResource) Create( return } - // Read identity data - var identityData UserResourceIdentityModel - resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...) - if resp.Diagnostics.HasError() { - return - } - ctx = core.InitProviderContext(ctx) arg := &clientArg{ @@ -178,6 +173,7 @@ func (r *userResource) Create( core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating user", fmt.Sprintf("Creating API payload: %v", err)) return } + // Create new user userResp, err := r.client.CreateUserRequest( ctx, @@ -185,13 +181,13 @@ func (r *userResource) Create( arg.region, arg.instanceId, ).CreateUserRequestPayload(*payload).Execute() - if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating user", fmt.Sprintf("Calling API: %v", err)) return } - if userResp.Id == nil || *userResp.Id == 0 { + id, ok := userResp.GetIdOk() + if !ok || id == 0 { core.LogAndAddError( ctx, &resp.Diagnostics, @@ -200,13 +196,8 @@ func (r *userResource) Create( ) return } - model.Id = types.Int64Value(userResp.GetId()) - 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) @@ -215,28 +206,52 @@ func (r *userResource) Create( ProjectID: types.StringValue(arg.projectId), Region: types.StringValue(arg.region), InstanceID: types.StringValue(arg.instanceId), - UserID: types.Int64Value(userResp.GetId()), + UserID: types.Int64Value(id), } resp.Diagnostics.Append(resp.Identity.Set(ctx, identity)...) if resp.Diagnostics.HasError() { return } - // Verify creation - exists, err := r.getUserResource(ctx, &model, arg) + model.Id = types.Int64Value(id) + 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.CreateUserWaitHandler( + ctx, + r.client, + arg.projectId, + arg.instanceId, + arg.region, + arg.userId, + ).SetSleepBeforeWait( + 10 * time.Second, + ).SetTimeout( + 15 * time.Minute, + ).WaitWithContext(ctx) if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating user", fmt.Sprintf("Calling API: %v", err)) - return - } - - if !exists { core.LogAndAddError( - ctx, &resp.Diagnostics, "Error creating user", - fmt.Sprintf("User ID '%v' resource not found after creation", model.UserId.ValueInt64()), + ctx, + &resp.Diagnostics, + "create user", + fmt.Sprintf("Instance creation waiting: %v", err), ) return } + + if waitResp.Id == nil || *waitResp.Id != arg.userId { + core.LogAndAddError( + ctx, + &resp.Diagnostics, + "create user", + "Instance creation waiting: returned id is nil or wrong", + ) + return + } + // Set state to fully populated data diags = resp.State.Set(ctx, model) resp.Diagnostics.Append(diags...) @@ -320,23 +335,12 @@ func (r *userResource) Update( return } - // Read identity data - var identityData UserResourceIdentityModel - resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...) - if resp.Diagnostics.HasError() { - return - } - ctx = core.InitProviderContext(ctx) - arg, errExt := r.extractIdentityData(model, identityData) - if errExt != nil { - core.LogAndAddError( - ctx, - &resp.Diagnostics, - extractErrorSummary, - fmt.Sprintf(extractErrorMessage, errExt), - ) + arg := &clientArg{ + projectId: model.ProjectId.ValueString(), + instanceId: model.InstanceId.ValueString(), + region: r.providerData.GetRegionWithOverride(model.Region), } ctx = r.setTFLogFields(ctx, arg) diff --git a/stackit/internal/services/sqlserverflexbeta/database/planModifiers.yaml b/stackit/internal/services/sqlserverflexbeta/database/planModifiers.yaml index d6209230..1d010ed7 100644 --- a/stackit/internal/services/sqlserverflexbeta/database/planModifiers.yaml +++ b/stackit/internal/services/sqlserverflexbeta/database/planModifiers.yaml @@ -31,7 +31,7 @@ fields: - name: 'owner' modifiers: - - 'RequiresReplace' + - 'UseStateForUnknown' - name: 'database_name' modifiers: @@ -43,6 +43,7 @@ fields: - name: 'compatibility' modifiers: + - 'UseStateForUnknown' - 'RequiresReplace' - name: 'compatibility_level' diff --git a/stackit/internal/services/sqlserverflexbeta/sqlserverflex_acc_test.go b/stackit/internal/services/sqlserverflexbeta/sqlserverflex_acc_test.go index e6c776dc..33f7cd29 100644 --- a/stackit/internal/services/sqlserverflexbeta/sqlserverflex_acc_test.go +++ b/stackit/internal/services/sqlserverflexbeta/sqlserverflex_acc_test.go @@ -120,6 +120,7 @@ func getExample() resData { func TestAccInstance(t *testing.T) { exData := getExample() + t.Logf(" ... working on instance %s", exData.TfName) updNameData := exData updNameData.Name = "name-updated" diff --git a/stackit/internal/services/sqlserverflexbeta/user/planModifiers.yaml b/stackit/internal/services/sqlserverflexbeta/user/planModifiers.yaml index fe4025ee..8ff346ab 100644 --- a/stackit/internal/services/sqlserverflexbeta/user/planModifiers.yaml +++ b/stackit/internal/services/sqlserverflexbeta/user/planModifiers.yaml @@ -3,16 +3,13 @@ fields: modifiers: - 'UseStateForUnknown' - - name: 'user_id' - modifiers: - - 'UseStateForUnknown' - - name: 'instance_id' validators: - validate.NoSeparator - validate.UUID modifiers: - 'UseStateForUnknown' + - 'RequiresReplace' - name: 'project_id' validators: @@ -28,6 +25,7 @@ fields: - name: 'user_id' modifiers: + - 'UseStateForUnknown' - 'RequiresReplace' - name: 'username' diff --git a/stackit/internal/wait/postgresflexalpha/wait.go b/stackit/internal/wait/postgresflexalpha/wait.go index c490b605..8f82dfbb 100644 --- a/stackit/internal/wait/postgresflexalpha/wait.go +++ b/stackit/internal/wait/postgresflexalpha/wait.go @@ -2,7 +2,10 @@ package postgresflexalpha import ( "context" + "errors" "fmt" + "math" + "net/http" "time" "github.com/hashicorp/terraform-plugin-log/tflog" @@ -40,7 +43,7 @@ type APIClientInstanceInterface interface { // APIClientUserInterface Interface needed for tests 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, error, ) @@ -202,3 +205,34 @@ func PartialUpdateInstanceWaitHandler( handler.SetTimeout(45 * time.Minute).SetSleepBeforeWait(30 * time.Second) return handler } + +// CreateUserWaitHandler will wait for instance creation +func CreateUserWaitHandler( + 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 +}