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:
parent
0150fea302
commit
979220be66
26 changed files with 3630 additions and 2759 deletions
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
||||
"github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/conversion"
|
||||
postgresflexUtils "github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/utils"
|
||||
|
||||
|
|
@ -84,12 +85,10 @@ func (r *databaseDataSource) Schema(_ context.Context, _ datasource.SchemaReques
|
|||
Description: descriptions["id"],
|
||||
Computed: true,
|
||||
},
|
||||
"database_id": schema.StringAttribute{
|
||||
"database_id": schema.Int64Attribute{
|
||||
Description: descriptions["database_id"],
|
||||
Required: true,
|
||||
Validators: []validator.String{
|
||||
validate.NoSeparator(),
|
||||
},
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
"instance_id": schema.StringAttribute{
|
||||
Description: descriptions["instance_id"],
|
||||
|
|
@ -109,7 +108,11 @@ func (r *databaseDataSource) Schema(_ context.Context, _ datasource.SchemaReques
|
|||
},
|
||||
"name": schema.StringAttribute{
|
||||
Description: descriptions["name"],
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Validators: []validator.String{
|
||||
stringvalidator.LengthAtLeast(1),
|
||||
},
|
||||
},
|
||||
"owner": schema.StringAttribute{
|
||||
Description: descriptions["owner"],
|
||||
|
|
@ -137,6 +140,18 @@ func (r *databaseDataSource) Read(
|
|||
return
|
||||
}
|
||||
|
||||
// validation for exactly one of database_id or name
|
||||
isIdSet := !model.DatabaseId.IsNull() && !model.DatabaseId.IsUnknown()
|
||||
isNameSet := !model.Name.IsNull() && !model.Name.IsUnknown()
|
||||
|
||||
if (isIdSet && isNameSet) || (!isIdSet && !isNameSet) {
|
||||
core.LogAndAddError(
|
||||
ctx, &resp.Diagnostics,
|
||||
"Invalid configuration", "Exactly one of 'database_id' or 'name' must be specified.",
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
|
|
@ -148,7 +163,19 @@ func (r *databaseDataSource) Read(
|
|||
ctx = tflog.SetField(ctx, "database_id", databaseId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
|
||||
databaseResp, err := getDatabase(ctx, r.client, projectId, region, instanceId, databaseId)
|
||||
var databaseResp *postgresflexalpha.ListDatabase
|
||||
var err error
|
||||
|
||||
if isIdSet {
|
||||
databaseId := model.DatabaseId.ValueInt64()
|
||||
ctx = tflog.SetField(ctx, "database_id", databaseId)
|
||||
databaseResp, err = getDatabaseById(ctx, r.client, projectId, region, instanceId, databaseId)
|
||||
} else {
|
||||
databaseName := model.Name.ValueString()
|
||||
ctx = tflog.SetField(ctx, "name", databaseName)
|
||||
databaseResp, err = getDatabaseByName(ctx, r.client, projectId, region, instanceId, databaseName)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
utils.LogError(
|
||||
ctx,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,81 @@
|
|||
package postgresflexalpha
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
postgresflex "github.com/mhenselin/terraform-provider-stackitprivatepreview/pkg/postgresflexalpha"
|
||||
)
|
||||
|
||||
// databaseClientReader represents the contract to listing databases from postgresflex.APIClient.
|
||||
type databaseClientReader interface {
|
||||
ListDatabasesRequest(
|
||||
ctx context.Context,
|
||||
projectId string,
|
||||
region string,
|
||||
instanceId string,
|
||||
) postgresflex.ApiListDatabasesRequestRequest
|
||||
}
|
||||
|
||||
// getDatabaseById gets a database by its ID.
|
||||
func getDatabaseById(
|
||||
ctx context.Context,
|
||||
client databaseClientReader,
|
||||
projectId, region, instanceId string,
|
||||
databaseId int64,
|
||||
) (*postgresflex.ListDatabase, error) {
|
||||
filter := func(db postgresflex.ListDatabase) bool {
|
||||
return db.Id != nil && *db.Id == databaseId
|
||||
}
|
||||
return getDatabase(ctx, client, projectId, region, instanceId, filter)
|
||||
}
|
||||
|
||||
// getDatabaseByName gets a database by its name.
|
||||
func getDatabaseByName(
|
||||
ctx context.Context,
|
||||
client databaseClientReader,
|
||||
projectId, region, instanceId, databaseName string,
|
||||
) (*postgresflex.ListDatabase, error) {
|
||||
filter := func(db postgresflex.ListDatabase) bool {
|
||||
return db.Name != nil && *db.Name == databaseName
|
||||
}
|
||||
return getDatabase(ctx, client, projectId, region, instanceId, filter)
|
||||
}
|
||||
|
||||
// getDatabase is a helper function to retrieve a database using a filter function.
|
||||
// Hint: The API does not have a GetDatabase endpoint, only ListDatabases
|
||||
func getDatabase(
|
||||
ctx context.Context,
|
||||
client databaseClientReader,
|
||||
projectId, region, instanceId string,
|
||||
filter func(db postgresflex.ListDatabase) bool,
|
||||
) (*postgresflex.ListDatabase, error) {
|
||||
if projectId == "" || region == "" || instanceId == "" {
|
||||
return nil, fmt.Errorf("all parameters (project, region, instance) are required")
|
||||
}
|
||||
|
||||
const pageSize = 25
|
||||
|
||||
for page := int64(1); ; page++ {
|
||||
res, err := client.ListDatabasesRequest(ctx, projectId, region, instanceId).
|
||||
Page(page).Size(pageSize).Sort(postgresflex.DATABASESORT_INDEX_ASC).Execute()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("requesting database list (page %d): %w", page, err)
|
||||
}
|
||||
|
||||
// If the API returns no databases, we have reached the end of the list.
|
||||
if res.Databases == nil || len(*res.Databases) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// Iterate over databases to find a match
|
||||
for _, db := range *res.Databases {
|
||||
if filter(db) {
|
||||
foundDb := db
|
||||
return &foundDb, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("database not found for instance %s", instanceId)
|
||||
}
|
||||
|
|
@ -0,0 +1,196 @@
|
|||
package postgresflexalpha
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
postgresflex "github.com/mhenselin/terraform-provider-stackitprivatepreview/pkg/postgresflexalpha"
|
||||
"github.com/stackitcloud/stackit-sdk-go/core/utils"
|
||||
)
|
||||
|
||||
type mockRequest struct {
|
||||
executeFunc func() (*postgresflex.ListDatabasesResponse, error)
|
||||
}
|
||||
|
||||
func (m *mockRequest) Page(_ int64) postgresflex.ApiListDatabasesRequestRequest { return m }
|
||||
func (m *mockRequest) Size(_ int64) postgresflex.ApiListDatabasesRequestRequest { return m }
|
||||
func (m *mockRequest) Sort(_ postgresflex.DatabaseSort) postgresflex.ApiListDatabasesRequestRequest {
|
||||
return m
|
||||
}
|
||||
func (m *mockRequest) Execute() (*postgresflex.ListDatabasesResponse, error) {
|
||||
return m.executeFunc()
|
||||
}
|
||||
|
||||
type mockDBClient struct {
|
||||
executeRequest func() postgresflex.ApiListDatabasesRequestRequest
|
||||
}
|
||||
|
||||
var _ databaseClientReader = (*mockDBClient)(nil)
|
||||
|
||||
func (m *mockDBClient) ListDatabasesRequest(
|
||||
_ context.Context,
|
||||
_, _, _ string,
|
||||
) postgresflex.ApiListDatabasesRequestRequest {
|
||||
return m.executeRequest()
|
||||
}
|
||||
|
||||
func TestGetDatabase(t *testing.T) {
|
||||
mockResp := func(page int64) (*postgresflex.ListDatabasesResponse, error) {
|
||||
if page == 1 {
|
||||
return &postgresflex.ListDatabasesResponse{
|
||||
Databases: &[]postgresflex.ListDatabase{
|
||||
{Id: utils.Ptr(int64(1)), Name: utils.Ptr("first")},
|
||||
{Id: utils.Ptr(int64(2)), Name: utils.Ptr("second")},
|
||||
},
|
||||
Pagination: &postgresflex.Pagination{
|
||||
Page: utils.Ptr(int64(1)),
|
||||
TotalPages: utils.Ptr(int64(2)),
|
||||
Size: utils.Ptr(int64(3)),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
if page == 2 {
|
||||
return &postgresflex.ListDatabasesResponse{
|
||||
Databases: &[]postgresflex.ListDatabase{{Id: utils.Ptr(int64(3)), Name: utils.Ptr("three")}},
|
||||
Pagination: &postgresflex.Pagination{
|
||||
Page: utils.Ptr(int64(2)),
|
||||
TotalPages: utils.Ptr(int64(2)),
|
||||
Size: utils.Ptr(int64(3)),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &postgresflex.ListDatabasesResponse{
|
||||
Databases: &[]postgresflex.ListDatabase{},
|
||||
Pagination: &postgresflex.Pagination{
|
||||
Page: utils.Ptr(int64(3)),
|
||||
TotalPages: utils.Ptr(int64(2)),
|
||||
Size: utils.Ptr(int64(3)),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
description string
|
||||
projectId string
|
||||
region string
|
||||
instanceId string
|
||||
wantErr bool
|
||||
wantDbName string
|
||||
wantDbId int64
|
||||
}{
|
||||
{
|
||||
description: "Success - Found by name on first page",
|
||||
projectId: "pid", region: "reg", instanceId: "inst",
|
||||
wantErr: false,
|
||||
wantDbName: "second",
|
||||
},
|
||||
{
|
||||
description: "Success - Found by id on first page",
|
||||
projectId: "pid", region: "reg", instanceId: "inst",
|
||||
wantErr: false,
|
||||
wantDbId: 2,
|
||||
},
|
||||
{
|
||||
description: "Success - Found by name on second page",
|
||||
projectId: "pid", region: "reg", instanceId: "inst",
|
||||
wantErr: false,
|
||||
wantDbName: "three",
|
||||
},
|
||||
{
|
||||
description: "Success - Found by id on second page",
|
||||
projectId: "pid", region: "reg", instanceId: "inst",
|
||||
wantErr: false,
|
||||
wantDbId: 1,
|
||||
},
|
||||
{
|
||||
description: "Error - API failure",
|
||||
projectId: "pid", region: "reg", instanceId: "inst",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
description: "Error - Missing parameters",
|
||||
projectId: "", region: "reg", instanceId: "inst",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
description: "Error - Search by name not found after all pages",
|
||||
projectId: "pid", region: "reg", instanceId: "inst",
|
||||
wantDbName: "non-existent",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
description: "Error - Search by id not found after all pages",
|
||||
projectId: "pid", region: "reg", instanceId: "inst",
|
||||
wantDbId: 999999,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(
|
||||
tt.description, func(t *testing.T) {
|
||||
var currentPage int64
|
||||
client := &mockDBClient{
|
||||
executeRequest: func() postgresflex.ApiListDatabasesRequestRequest {
|
||||
return &mockRequest{
|
||||
executeFunc: func() (*postgresflex.ListDatabasesResponse, error) {
|
||||
currentPage++
|
||||
return mockResp(currentPage)
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var actual *postgresflex.ListDatabase
|
||||
var errDB error
|
||||
|
||||
if tt.wantDbName != "" {
|
||||
actual, errDB = getDatabaseByName(
|
||||
t.Context(),
|
||||
client,
|
||||
tt.projectId,
|
||||
tt.region,
|
||||
tt.instanceId,
|
||||
tt.wantDbName,
|
||||
)
|
||||
} else if tt.wantDbId != 0 {
|
||||
actual, errDB = getDatabaseById(
|
||||
t.Context(),
|
||||
client,
|
||||
tt.projectId,
|
||||
tt.region,
|
||||
tt.instanceId,
|
||||
tt.wantDbId,
|
||||
)
|
||||
} else {
|
||||
actual, errDB = getDatabase(
|
||||
context.Background(),
|
||||
client,
|
||||
tt.projectId,
|
||||
tt.region,
|
||||
tt.instanceId,
|
||||
func(_ postgresflex.ListDatabase) bool { return false },
|
||||
)
|
||||
}
|
||||
|
||||
if (errDB != nil) != tt.wantErr {
|
||||
t.Errorf("getDatabase() error = %v, wantErr %v", errDB, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !tt.wantErr && tt.wantDbName != "" && actual != nil {
|
||||
if *actual.Name != tt.wantDbName {
|
||||
t.Errorf("getDatabase() got name = %v, want %v", *actual.Name, tt.wantDbName)
|
||||
}
|
||||
}
|
||||
|
||||
if !tt.wantErr && tt.wantDbId != 0 && actual != nil {
|
||||
if *actual.Id != tt.wantDbId {
|
||||
t.Errorf("getDatabase() got id = %v, want %v", *actual.Id, tt.wantDbId)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -259,7 +259,7 @@ func (r *databaseResource) Create(
|
|||
databaseId := *databaseResp.Id
|
||||
ctx = tflog.SetField(ctx, "database_id", databaseId)
|
||||
|
||||
database, err := getDatabase(ctx, r.client, projectId, region, instanceId, databaseId)
|
||||
database, err := getDatabaseById(ctx, r.client, projectId, region, instanceId, databaseId)
|
||||
if err != nil {
|
||||
core.LogAndAddError(
|
||||
ctx,
|
||||
|
|
@ -314,7 +314,7 @@ func (r *databaseResource) Read(
|
|||
ctx = tflog.SetField(ctx, "database_id", databaseId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
|
||||
databaseResp, err := getDatabase(ctx, r.client, projectId, region, instanceId, databaseId)
|
||||
databaseResp, err := getDatabaseById(ctx, r.client, projectId, region, instanceId, databaseId)
|
||||
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) {
|
||||
|
|
@ -353,7 +353,7 @@ func (r *databaseResource) Update(
|
|||
ctx context.Context,
|
||||
req resource.UpdateRequest,
|
||||
resp *resource.UpdateResponse,
|
||||
) { // nolint:gocritic // function signature required by Terraform
|
||||
) {
|
||||
var model Model
|
||||
diags := req.Plan.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
|
|
@ -430,7 +430,6 @@ func (r *databaseResource) Update(
|
|||
return
|
||||
}
|
||||
tflog.Info(ctx, "Postgres Flex database updated")
|
||||
|
||||
}
|
||||
|
||||
// Delete deletes the resource and removes the Terraform state on success.
|
||||
|
|
@ -530,14 +529,11 @@ func mapFields(resp *postgresflexalpha.ListDatabase, model *Model, region string
|
|||
return nil
|
||||
}
|
||||
|
||||
func mapFieldsUpdate(res *postgresflexalpha.UpdateDatabaseResponse, model *Model, region string) error {
|
||||
if res == nil {
|
||||
return fmt.Errorf("response is nil")
|
||||
}
|
||||
return mapFields(res.Database, model, region)
|
||||
}
|
||||
|
||||
func mapFieldsUpdatePartially(res *postgresflexalpha.UpdateDatabasePartiallyResponse, model *Model, region string) error {
|
||||
func mapFieldsUpdatePartially(
|
||||
res *postgresflexalpha.UpdateDatabasePartiallyResponse,
|
||||
model *Model,
|
||||
region string,
|
||||
) error {
|
||||
if res == nil {
|
||||
return fmt.Errorf("response is nil")
|
||||
}
|
||||
|
|
@ -564,26 +560,3 @@ func toCreatePayload(model *Model) (*postgresflexalpha.CreateDatabaseRequestPayl
|
|||
}
|
||||
|
||||
var errDatabaseNotFound = errors.New("database not found")
|
||||
|
||||
// The API does not have a GetDatabase endpoint, only ListDatabases
|
||||
func getDatabase(
|
||||
ctx context.Context,
|
||||
client *postgresflexalpha.APIClient,
|
||||
projectId, region, instanceId string,
|
||||
databaseId int64,
|
||||
) (*postgresflexalpha.ListDatabase, error) {
|
||||
// TODO - implement pagination handling
|
||||
resp, err := client.ListDatabasesRequestExecute(ctx, projectId, region, instanceId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp == nil || resp.Databases == nil {
|
||||
return nil, fmt.Errorf("response is nil")
|
||||
}
|
||||
for _, database := range *resp.Databases {
|
||||
if database.Id != nil && *database.Id == databaseId {
|
||||
return &database, nil
|
||||
}
|
||||
}
|
||||
return nil, errDatabaseNotFound
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue