chore: adjust pagination for postgres database and flavor listing (#20)

* feat: implement pagination for database listing

* fix: change database_id attribute type from string to int64

* refactor: rename getDatabase to getDatabaseById for clarity

* fix: improve error handling for database not found scenario

* feat: add validation for database_id and name attributes; implement separate functions for fetching databases by ID and name

* feat: implement database client interface and update database fetching functions

* refactor: rename matcher to filter for clarity and update pagination logic

* feat: implement flavors retrieval with pagination and filtering support

* refactor: rename flavor import for consistency and clarity

* feat: add support for InstanceStatePending in wait handler logic

* refactor: simplify GetFlavorsRequest and GetFlavorsRequestExecute by removing pagination parameters

* refactor: improve readability of test cases by formatting function signatures and restructuring test runs

* refactor: remove pagination parameters from GetFlavorsRequest in test case

* refactor: simplify function signatures and improve readability in datasource and resource files

* refactor: add descriptions for user-related attributes in datasource schema

* refactor: enhance user resource schema with additional attributes and improve logging

* refactor: delete unused file

* refactor: standardize formatting and improve function naming for user resource management

* refactor: remove skip from TestMapFields and update roles initialization in resource tests

* fix: golangci lint issues

* fix: golangci lint issues again

* fix: golangci lint issues again
This commit is contained in:
Andre_Harms 2026-01-16 16:23:10 +01:00 committed by GitHub
parent 0150fea302
commit 979220be66
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 3630 additions and 2759 deletions

View file

@ -45,6 +45,7 @@ var (
_ resource.ResourceWithModifyPlan = &instanceResource{}
)
//nolint:unused // TODO: remove if not needed later
var validNodeTypes []string = []string{
"Single",
"Replica",
@ -126,7 +127,11 @@ func (r *instanceResource) Metadata(_ context.Context, req resource.MetadataRequ
}
// Configure adds the provider configured client to the resource.
func (r *instanceResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
func (r *instanceResource) Configure(
ctx context.Context,
req resource.ConfigureRequest,
resp *resource.ConfigureResponse,
) {
var ok bool
r.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics)
if !ok {
@ -143,7 +148,11 @@ func (r *instanceResource) Configure(ctx context.Context, req resource.Configure
// ModifyPlan implements resource.ResourceWithModifyPlan.
// Use the modifier to set the effective region in the current plan.
func (r *instanceResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { // nolint:gocritic // function signature required by Terraform
func (r *instanceResource) ModifyPlan(
ctx context.Context,
req resource.ModifyPlanRequest,
resp *resource.ModifyPlanResponse,
) { // nolint:gocritic // function signature required by Terraform
var configModel Model
// skip initial empty configuration to avoid follow-up errors
if req.Config.Raw.IsNull() {
@ -422,7 +431,11 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r
}
// Create creates the resource and sets the initial Terraform state.
func (r *instanceResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { // nolint:gocritic // function signature required by Terraform
func (r *instanceResource) Create(
ctx context.Context,
req resource.CreateRequest,
resp *resource.CreateResponse,
) { // nolint:gocritic // function signature required by Terraform
var model Model
diags := req.Plan.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
@ -467,11 +480,20 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques
// Generate API request body from model
payload, err := toCreatePayload(&model, storage, encryption, network)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Creating API payload: %v", err))
core.LogAndAddError(
ctx,
&resp.Diagnostics,
"Error creating instance",
fmt.Sprintf("Creating API payload: %v", err),
)
return
}
// Create new instance
createResp, err := r.client.CreateInstanceRequest(ctx, projectId, region).CreateInstanceRequestPayload(*payload).Execute()
createResp, err := r.client.CreateInstanceRequest(
ctx,
projectId,
region,
).CreateInstanceRequestPayload(*payload).Execute()
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Calling API: %v", err))
return
@ -480,31 +502,54 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques
ctx = core.LogResponse(ctx)
instanceId := *createResp.Id
utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{
"id": utils.BuildInternalTerraformId(projectId, region, instanceId),
"instance_id": instanceId,
})
utils.SetAndLogStateFields(
ctx, &resp.Diagnostics, &resp.State, map[string]any{
"id": utils.BuildInternalTerraformId(projectId, region, instanceId),
"instance_id": instanceId,
},
)
if resp.Diagnostics.HasError() {
return
}
// The creation waiter sometimes returns an error from the API: "instance with id xxx has unexpected status Failure"
// which can be avoided by sleeping before wait
waitResp, err := wait.CreateInstanceWaitHandler(ctx, r.client, projectId, instanceId, region).SetSleepBeforeWait(30 * time.Second).WaitWithContext(ctx)
waitResp, err := wait.CreateInstanceWaitHandler(
ctx,
r.client,
projectId,
instanceId,
region,
).SetSleepBeforeWait(30 * time.Second).WaitWithContext(ctx)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Instance creation waiting: %v", err))
core.LogAndAddError(
ctx,
&resp.Diagnostics,
"Error creating instance",
fmt.Sprintf("Instance creation waiting: %v", err),
)
return
}
if waitResp.FlavorId == nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", "Instance creation waiting: returned flavor id is nil")
core.LogAndAddError(
ctx,
&resp.Diagnostics,
"Error creating instance",
"Instance creation waiting: returned flavor id is nil",
)
return
}
// Map response body to schema
err = mapFields(ctx, waitResp, &model, storage, encryption, network, region)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Processing API payload: %v", err))
core.LogAndAddError(
ctx,
&resp.Diagnostics,
"Error creating instance",
fmt.Sprintf("Processing API payload: %v", err),
)
return
}
// Set state to fully populated data
@ -523,7 +568,11 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques
}
// Read refreshes the Terraform state with the latest data.
func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
func (r *instanceResource) Read(
ctx context.Context,
req resource.ReadRequest,
resp *resource.ReadResponse,
) { // nolint:gocritic // function signature required by Terraform
var model Model
diags := req.State.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
@ -584,7 +633,12 @@ func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r
// Map response body to schema
err = mapFields(ctx, instanceResp, &model, storage, encryption, network, region)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Processing API payload: %v", err))
core.LogAndAddError(
ctx,
&resp.Diagnostics,
"Error reading instance",
fmt.Sprintf("Processing API payload: %v", err),
)
return
}
// Set refreshed state
@ -597,7 +651,11 @@ func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r
}
// Update updates the resource and sets the updated Terraform state on success.
func (r *instanceResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform
func (r *instanceResource) Update(
ctx context.Context,
req resource.UpdateRequest,
resp *resource.UpdateResponse,
) { // nolint:gocritic // function signature required by Terraform
// Retrieve values from plan
var model Model
diags := req.Plan.Get(ctx, &model)
@ -646,12 +704,21 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques
// Generate API request body from model
payload, err := toUpdatePayload(&model, storage, network)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Creating API payload: %v", err))
core.LogAndAddError(
ctx,
&resp.Diagnostics,
"Error updating instance",
fmt.Sprintf("Creating API payload: %v", err),
)
return
}
// Update existing instance
err = r.client.UpdateInstanceRequest(ctx, projectId, region, instanceId).UpdateInstanceRequestPayload(*payload).Execute()
// err = r.client.UpdateInstancePartiallyRequest(ctx, projectId, region, instanceId).UpdateInstancePartiallyRequestPayload(*payload).Execute()
err = r.client.UpdateInstanceRequest(
ctx,
projectId,
region,
instanceId,
).UpdateInstanceRequestPayload(*payload).Execute()
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", err.Error())
return
@ -661,14 +728,24 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques
waitResp, err := wait.UpdateInstanceWaitHandler(ctx, r.client, projectId, instanceId, region).WaitWithContext(ctx)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Instance update waiting: %v", err))
core.LogAndAddError(
ctx,
&resp.Diagnostics,
"Error updating instance",
fmt.Sprintf("Instance update waiting: %v", err),
)
return
}
// Map response body to schema
err = mapFields(ctx, waitResp, &model, storage, encryption, network, region)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Processing API payload: %v", err))
core.LogAndAddError(
ctx,
&resp.Diagnostics,
"Error updating instance",
fmt.Sprintf("Processing API payload: %v", err),
)
return
}
diags = resp.State.Set(ctx, model)
@ -680,7 +757,11 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques
}
// Delete deletes the resource and removes the Terraform state on success.
func (r *instanceResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { // nolint:gocritic // function signature required by Terraform
func (r *instanceResource) Delete(
ctx context.Context,
req resource.DeleteRequest,
resp *resource.DeleteResponse,
) { // nolint:gocritic // function signature required by Terraform
// Retrieve values from state
var model Model
diags := req.State.Get(ctx, &model)
@ -709,7 +790,12 @@ func (r *instanceResource) Delete(ctx context.Context, req resource.DeleteReques
_, err = wait.DeleteInstanceWaitHandler(ctx, r.client, projectId, instanceId, region).WaitWithContext(ctx)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting instance", fmt.Sprintf("Instance deletion waiting: %v", err))
core.LogAndAddError(
ctx,
&resp.Diagnostics,
"Error deleting instance",
fmt.Sprintf("Instance deletion waiting: %v", err),
)
return
}
tflog.Info(ctx, "SQLServer Flex instance deleted")
@ -717,11 +803,16 @@ func (r *instanceResource) Delete(ctx context.Context, req resource.DeleteReques
// ImportState imports a resource into the Terraform state on success.
// The expected format of the resource import identifier is: project_id,instance_id
func (r *instanceResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
func (r *instanceResource) ImportState(
ctx context.Context,
req resource.ImportStateRequest,
resp *resource.ImportStateResponse,
) {
idParts := strings.Split(req.ID, core.Separator)
if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" {
core.LogAndAddError(ctx, &resp.Diagnostics,
core.LogAndAddError(
ctx, &resp.Diagnostics,
"Error importing instance",
fmt.Sprintf("Expected import identifier with format: [project_id],[region],[instance_id] Got: %q", req.ID),
)