From 9a18db49ad3ca72f297cbf1217009506d4cac852 Mon Sep 17 00:00:00 2001 From: "Marcel S. Henselin" Date: Tue, 30 Dec 2025 11:50:36 +0100 Subject: [PATCH] fix: add missing pagination to api client fix: add missing docs --- docs/resources/postgresflexalpha_instance.md | 1 + pkg/postgresflexalpha/api_default.go | 6 + sample/postresql.tf | 1 + sample/tf.sh | 10 +- sample/user.tf | 40 +- .../postgresflexalpha/instance/functions.go | 62 +- .../instance/functions_test.go | 678 +++++++++++++++++- .../postgresflexalpha/instance/models.go | 1 + .../postgresflexalpha/instance/resource.go | 5 + 9 files changed, 752 insertions(+), 52 deletions(-) diff --git a/docs/resources/postgresflexalpha_instance.md b/docs/resources/postgresflexalpha_instance.md index 60d4f2f4..969f288e 100644 --- a/docs/resources/postgresflexalpha_instance.md +++ b/docs/resources/postgresflexalpha_instance.md @@ -49,6 +49,7 @@ import { - `network` (Attributes) The network block configuration. (see [below for nested schema](#nestedatt--network)) - `project_id` (String) STACKIT project ID to which the instance is associated. - `replicas` (Number) +- `retention_days` (Number) The days of the retention period. - `storage` (Attributes) (see [below for nested schema](#nestedatt--storage)) - `version` (String) The database version used. diff --git a/pkg/postgresflexalpha/api_default.go b/pkg/postgresflexalpha/api_default.go index 8207f74a..ddad2739 100644 --- a/pkg/postgresflexalpha/api_default.go +++ b/pkg/postgresflexalpha/api_default.go @@ -2526,6 +2526,9 @@ func (a *APIClient) GetFlavorsRequest(ctx context.Context, projectId string, reg ctx: ctx, projectId: projectId, region: region, + page: page, + size: size, + sort: sort, } } @@ -2535,6 +2538,9 @@ func (a *APIClient) GetFlavorsRequestExecute(ctx context.Context, projectId stri ctx: ctx, projectId: projectId, region: region, + page: page, + size: size, + sort: sort, } return r.Execute() } diff --git a/sample/postresql.tf b/sample/postresql.tf index b1785d4b..6d486035 100644 --- a/sample/postresql.tf +++ b/sample/postresql.tf @@ -2,6 +2,7 @@ resource "stackitprivatepreview_postgresflexalpha_instance" "ptlsdbsrv" { project_id = var.project_id name = "pgsql-example-instance" backup_schedule = "0 0 * * *" + retention_days = 33 flavor = { cpu = 2 ram = 4 diff --git a/sample/tf.sh b/sample/tf.sh index 65d7f28e..ce213a21 100755 --- a/sample/tf.sh +++ b/sample/tf.sh @@ -8,12 +8,18 @@ TERRAFORM_CONFIG=$(pwd)/sample.tfrc export TERRAFORM_CONFIG parsed_options=$( - getopt -n "$0" -o l -- "$@" + getopt -n "$0" -o dil -- "$@" ) || exit eval "set -- $parsed_options" while [ "$#" -gt 0 ]; do case $1 in - (-l) TF_LOG=TRACE + (-d) TF_LOG=DEBUG + export TF_LOG + shift;; + (-i) TF_LOG=INFO + export TF_LOG + shift;; + (-t) TF_LOG=TRACE export TF_LOG shift;; (--) shift; break;; diff --git a/sample/user.tf b/sample/user.tf index fd72a23b..e51ee01f 100644 --- a/sample/user.tf +++ b/sample/user.tf @@ -1,20 +1,20 @@ -data "stackitprivatepreview_postgresflexalpha_user" "example" { - project_id = stackitprivatepreview_postgresflexalpha_instance.ptlsdbsrv.project_id - instance_id = stackitprivatepreview_postgresflexalpha_instance.ptlsdbsrv.instance_id - user_id = 1 -} - -resource "stackitprivatepreview_postgresflexalpha_user" "ptlsdbuser" { - project_id = stackitprivatepreview_postgresflexalpha_instance.ptlsdbsrv.project_id - instance_id = stackitprivatepreview_postgresflexalpha_instance.ptlsdbsrv.instance_id - username = var.db_username - roles = ["createdb", "login"] - # roles = ["createdb", "login", "createrole"] -} - -resource "stackitprivatepreview_sqlserverflexalpha_user" "ptlsdbuser" { - project_id = stackitprivatepreview_sqlserverflexalpha_instance.ptlsdbsqlsrv.project_id - instance_id = stackitprivatepreview_sqlserverflexalpha_instance.ptlsdbsqlsrv.instance_id - username = var.db_username - roles = ["login"] -} +# data "stackitprivatepreview_postgresflexalpha_user" "example" { +# project_id = stackitprivatepreview_postgresflexalpha_instance.ptlsdbsrv.project_id +# instance_id = stackitprivatepreview_postgresflexalpha_instance.ptlsdbsrv.instance_id +# user_id = 1 +# } +# +# resource "stackitprivatepreview_postgresflexalpha_user" "ptlsdbuser" { +# project_id = stackitprivatepreview_postgresflexalpha_instance.ptlsdbsrv.project_id +# instance_id = stackitprivatepreview_postgresflexalpha_instance.ptlsdbsrv.instance_id +# username = var.db_username +# roles = ["createdb", "login"] +# # roles = ["createdb", "login", "createrole"] +# } +# +# resource "stackitprivatepreview_sqlserverflexalpha_user" "ptlsdbuser" { +# project_id = stackitprivatepreview_sqlserverflexalpha_instance.ptlsdbsqlsrv.project_id +# instance_id = stackitprivatepreview_sqlserverflexalpha_instance.ptlsdbsqlsrv.instance_id +# username = var.db_username +# roles = ["login"] +# } diff --git a/stackit/internal/services/postgresflexalpha/instance/functions.go b/stackit/internal/services/postgresflexalpha/instance/functions.go index 6e2a8412..1635324e 100644 --- a/stackit/internal/services/postgresflexalpha/instance/functions.go +++ b/stackit/internal/services/postgresflexalpha/instance/functions.go @@ -254,14 +254,14 @@ func toCreatePayload( return &postgresflex.CreateInstanceRequestPayload{ BackupSchedule: conversion.StringValueToPointer(model.BackupSchedule), + Encryption: encryptionPayload, FlavorId: conversion.StringValueToPointer(flavor.Id), Name: conversion.StringValueToPointer(model.Name), - // TODO - verify working - Replicas: postgresflex.CreateInstanceRequestPayloadGetReplicasAttributeType(&replVal), - Storage: storagePayload, - Version: conversion.StringValueToPointer(model.Version), - Encryption: encryptionPayload, - Network: networkPayload, + Network: networkPayload, + Replicas: postgresflex.CreateInstanceRequestPayloadGetReplicasAttributeType(&replVal), + RetentionDays: conversion.Int64ValueToPointer(model.RetentionDays), + Storage: storagePayload, + Version: conversion.StringValueToPointer(model.Version), }, nil } @@ -298,25 +298,25 @@ func loadFlavorId(ctx context.Context, client postgresflexClient, model *Model, if flavor == nil { return fmt.Errorf("nil flavor") } - cpu := conversion.Int64ValueToPointer(flavor.CPU) - if cpu == nil { + cpu := flavor.CPU.ValueInt64() + if cpu == 0 { return fmt.Errorf("nil CPU") } - ram := conversion.Int64ValueToPointer(flavor.RAM) - if ram == nil { + ram := flavor.RAM.ValueInt64() + if ram == 0 { return fmt.Errorf("nil RAM") } - nodeType := conversion.StringValueToPointer(flavor.NodeType) - if nodeType == nil { + nodeType := flavor.NodeType.ValueString() + if nodeType == "" { if model.Replicas.IsNull() || model.Replicas.IsUnknown() { return fmt.Errorf("nil NodeType") } switch model.Replicas.ValueInt64() { case 1: - nodeType = conversion.StringValueToPointer(types.StringValue("Single")) + nodeType = "Single" case 3: - nodeType = conversion.StringValueToPointer(types.StringValue("Replicas")) + nodeType = "Replica" default: return fmt.Errorf("unknown Replicas value: %d", model.Replicas.ValueInt64()) } @@ -341,14 +341,15 @@ func loadFlavorId(ctx context.Context, client postgresflexClient, model *Model, avl := "" foundFlavorCount := 0 + var foundFlavors []string for _, f := range flavorList { if f.Id == nil || f.Cpu == nil || f.Memory == nil { continue } - if !strings.EqualFold(*f.NodeType, *nodeType) { + if !strings.EqualFold(*f.NodeType, nodeType) { continue } - if *f.Cpu == *cpu && *f.Memory == *ram { + if *f.Cpu == cpu && *f.Memory == ram { var useSc *postgresflex.FlavorStorageClassesStorageClass for _, sc := range *f.StorageClasses { if *sc.Class != *storageClass { @@ -365,6 +366,7 @@ func loadFlavorId(ctx context.Context, client postgresflexClient, model *Model, flavor.Id = types.StringValue(*f.Id) flavor.Description = types.StringValue(*f.Description) + foundFlavors = append(foundFlavors, fmt.Sprintf("%s (%d/%d - %s)", *f.Id, *f.Cpu, *f.Memory, *f.NodeType)) foundFlavorCount++ } for _, cls := range *f.StorageClasses { @@ -372,7 +374,12 @@ func loadFlavorId(ctx context.Context, client postgresflexClient, model *Model, } } if foundFlavorCount > 1 { - return fmt.Errorf("multiple flavors found: %d flavors", foundFlavorCount) + return fmt.Errorf( + "number of flavors returned: %d\nmultiple flavors found: %d flavors\n %s\n", + len(flavorList), + foundFlavorCount, + strings.Join(foundFlavors, "\n "), + ) } if flavor.Id.ValueString() == "" { return fmt.Errorf("couldn't find flavor, available specs are:%s", avl) @@ -390,6 +397,7 @@ func getAllFlavors(ctx context.Context, client postgresflexClient, projectId, re page := int64(1) size := int64(10) sort := postgresflex.FLAVORSORT_INDEX_ASC + counter := 0 for { res, err := client.GetFlavorsRequestExecute(ctx, projectId, region, &page, &size, &sort) if err != nil { @@ -399,12 +407,28 @@ func getAllFlavors(ctx context.Context, client postgresflexClient, projectId, re return nil, fmt.Errorf("finding flavors for project %s", projectId) } pagination := res.GetPagination() - flavorList = append(flavorList, *res.Flavors...) + flavors := res.GetFlavors() + for _, flavor := range flavors { + flavorList = append(flavorList, flavor) + } - if *pagination.TotalRows <= int64(len(flavorList)) { + if *pagination.TotalRows < int64(len(flavorList)) { + return nil, fmt.Errorf("total rows is smaller than current accumulated list - that should not happen") + } + if *pagination.TotalRows == int64(len(flavorList)) { break } page++ + + if page > *pagination.TotalPages { + break + } + + // implement a breakpoint + counter++ + if counter > 1000 { + panic("too many pagination results") + } } return flavorList, nil } diff --git a/stackit/internal/services/postgresflexalpha/instance/functions_test.go b/stackit/internal/services/postgresflexalpha/instance/functions_test.go index 739ee99f..d6468b87 100644 --- a/stackit/internal/services/postgresflexalpha/instance/functions_test.go +++ b/stackit/internal/services/postgresflexalpha/instance/functions_test.go @@ -6,14 +6,17 @@ import ( "reflect" "testing" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" postgresflex "github.com/mhenselin/terraform-provider-stackitprivatepreview/pkg/postgresflexalpha" "github.com/stackitcloud/stackit-sdk-go/core/utils" ) type postgresFlexClientMocked struct { returnError bool + firstItem int + lastItem int } - type testFlavor struct { Cpu int64 Description string @@ -46,6 +49,382 @@ var responseList = []testFlavor{ {Class: "sc3", MaxIoPerSec: 0, MaxThroughInMb: 0}, }, }, + { + Cpu: 1, + Description: "flavor 1.2", + Id: "flv1.2", + MaxGB: 500, + Memory: 2, + MinGB: 5, + NodeType: "single", + StorageClasses: []testFlavorStorageClass{ + {Class: "sc1", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc2", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc3", MaxIoPerSec: 0, MaxThroughInMb: 0}, + }, + }, + { + Cpu: 1, + Description: "flavor 1.3", + Id: "flv1.3", + MaxGB: 500, + Memory: 3, + MinGB: 5, + NodeType: "single", + StorageClasses: []testFlavorStorageClass{ + {Class: "sc1", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc2", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc3", MaxIoPerSec: 0, MaxThroughInMb: 0}, + }, + }, + { + Cpu: 1, + Description: "flavor 1.4", + Id: "flv1.4", + MaxGB: 500, + Memory: 4, + MinGB: 5, + NodeType: "single", + StorageClasses: []testFlavorStorageClass{ + {Class: "sc1", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc2", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc3", MaxIoPerSec: 0, MaxThroughInMb: 0}, + }, + }, + { + Cpu: 1, + Description: "flavor 1.5", + Id: "flv1.5", + MaxGB: 500, + Memory: 5, + MinGB: 5, + NodeType: "single", + StorageClasses: []testFlavorStorageClass{ + {Class: "sc1", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc2", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc3", MaxIoPerSec: 0, MaxThroughInMb: 0}, + }, + }, + { + Cpu: 1, + Description: "flavor 1.6", + Id: "flv1.6", + MaxGB: 500, + Memory: 6, + MinGB: 5, + NodeType: "single", + StorageClasses: []testFlavorStorageClass{ + {Class: "sc1", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc2", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc3", MaxIoPerSec: 0, MaxThroughInMb: 0}, + }, + }, + { + Cpu: 1, + Description: "flavor 1.7", + Id: "flv1.7", + MaxGB: 500, + Memory: 7, + MinGB: 5, + NodeType: "single", + StorageClasses: []testFlavorStorageClass{ + {Class: "sc1", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc2", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc3", MaxIoPerSec: 0, MaxThroughInMb: 0}, + }, + }, + { + Cpu: 1, + Description: "flavor 1.8", + Id: "flv1.8", + MaxGB: 500, + Memory: 8, + MinGB: 5, + NodeType: "single", + StorageClasses: []testFlavorStorageClass{ + {Class: "sc1", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc2", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc3", MaxIoPerSec: 0, MaxThroughInMb: 0}, + }, + }, + { + Cpu: 1, + Description: "flavor 1.9", + Id: "flv1.9", + MaxGB: 500, + Memory: 9, + MinGB: 5, + NodeType: "single", + StorageClasses: []testFlavorStorageClass{ + {Class: "sc1", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc2", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc3", MaxIoPerSec: 0, MaxThroughInMb: 0}, + }, + }, + /* ......................................................... */ + { + Cpu: 2, + Description: "flavor 2.1", + Id: "flv2.1", + MaxGB: 500, + Memory: 1, + MinGB: 5, + NodeType: "single", + StorageClasses: []testFlavorStorageClass{ + {Class: "sc1", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc2", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc3", MaxIoPerSec: 0, MaxThroughInMb: 0}, + }, + }, + { + Cpu: 2, + Description: "flavor 2.2", + Id: "flv2.2", + MaxGB: 500, + Memory: 2, + MinGB: 5, + NodeType: "single", + StorageClasses: []testFlavorStorageClass{ + {Class: "sc1", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc2", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc3", MaxIoPerSec: 0, MaxThroughInMb: 0}, + }, + }, + { + Cpu: 2, + Description: "flavor 2.3", + Id: "flv2.3", + MaxGB: 500, + Memory: 3, + MinGB: 5, + NodeType: "single", + StorageClasses: []testFlavorStorageClass{ + {Class: "sc1", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc2", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc3", MaxIoPerSec: 0, MaxThroughInMb: 0}, + }, + }, + { + Cpu: 2, + Description: "flavor 2.4", + Id: "flv2.4", + MaxGB: 500, + Memory: 4, + MinGB: 5, + NodeType: "single", + StorageClasses: []testFlavorStorageClass{ + {Class: "sc1", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc2", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc3", MaxIoPerSec: 0, MaxThroughInMb: 0}, + }, + }, + { + Cpu: 2, + Description: "flavor 2.5", + Id: "flv2.5", + MaxGB: 500, + Memory: 5, + MinGB: 5, + NodeType: "single", + StorageClasses: []testFlavorStorageClass{ + {Class: "sc1", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc2", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc3", MaxIoPerSec: 0, MaxThroughInMb: 0}, + }, + }, + { + Cpu: 2, + Description: "flavor 2.6", + Id: "flv2.6", + MaxGB: 500, + Memory: 6, + MinGB: 5, + NodeType: "single", + StorageClasses: []testFlavorStorageClass{ + {Class: "sc1", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc2", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc3", MaxIoPerSec: 0, MaxThroughInMb: 0}, + }, + }, + /* ......................................................... */ + { + Cpu: 1, + Description: "flavor 1.1", + Id: "flv1.1", + MaxGB: 500, + Memory: 1, + MinGB: 5, + NodeType: "replica", + StorageClasses: []testFlavorStorageClass{ + {Class: "sc1", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc2", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc3", MaxIoPerSec: 0, MaxThroughInMb: 0}, + }, + }, + { + Cpu: 1, + Description: "flavor 1.2", + Id: "flv1.2", + MaxGB: 500, + Memory: 2, + MinGB: 5, + NodeType: "replica", + StorageClasses: []testFlavorStorageClass{ + {Class: "sc1", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc2", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc3", MaxIoPerSec: 0, MaxThroughInMb: 0}, + }, + }, + { + Cpu: 1, + Description: "flavor 1.3", + Id: "flv1.3", + MaxGB: 500, + Memory: 3, + MinGB: 5, + NodeType: "replica", + StorageClasses: []testFlavorStorageClass{ + {Class: "sc1", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc2", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc3", MaxIoPerSec: 0, MaxThroughInMb: 0}, + }, + }, + { + Cpu: 1, + Description: "flavor 1.4", + Id: "flv1.4", + MaxGB: 500, + Memory: 4, + MinGB: 5, + NodeType: "replica", + StorageClasses: []testFlavorStorageClass{ + {Class: "sc1", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc2", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc3", MaxIoPerSec: 0, MaxThroughInMb: 0}, + }, + }, + { + Cpu: 1, + Description: "flavor 1.5", + Id: "flv1.5", + MaxGB: 500, + Memory: 5, + MinGB: 5, + NodeType: "replica", + StorageClasses: []testFlavorStorageClass{ + {Class: "sc1", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc2", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc3", MaxIoPerSec: 0, MaxThroughInMb: 0}, + }, + }, + { + Cpu: 1, + Description: "flavor 1.6", + Id: "flv1.6", + MaxGB: 500, + Memory: 6, + MinGB: 5, + NodeType: "replica", + StorageClasses: []testFlavorStorageClass{ + {Class: "sc1", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc2", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc3", MaxIoPerSec: 0, MaxThroughInMb: 0}, + }, + }, + /* ......................................................... */ + { + Cpu: 2, + Description: "flavor 2.1", + Id: "flv2.1", + MaxGB: 500, + Memory: 1, + MinGB: 5, + NodeType: "replica", + StorageClasses: []testFlavorStorageClass{ + {Class: "sc1", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc2", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc3", MaxIoPerSec: 0, MaxThroughInMb: 0}, + }, + }, + { + Cpu: 2, + Description: "flavor 2.2", + Id: "flv2.2", + MaxGB: 500, + Memory: 2, + MinGB: 5, + NodeType: "replica", + StorageClasses: []testFlavorStorageClass{ + {Class: "sc1", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc2", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc3", MaxIoPerSec: 0, MaxThroughInMb: 0}, + }, + }, + { + Cpu: 2, + Description: "flavor 2.3", + Id: "flv2.3", + MaxGB: 500, + Memory: 3, + MinGB: 5, + NodeType: "replica", + StorageClasses: []testFlavorStorageClass{ + {Class: "sc1", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc2", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc3", MaxIoPerSec: 0, MaxThroughInMb: 0}, + }, + }, + { + Cpu: 2, + Description: "flavor 2.4", + Id: "flv2.4", + MaxGB: 500, + Memory: 4, + MinGB: 5, + NodeType: "replica", + StorageClasses: []testFlavorStorageClass{ + {Class: "sc1", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc2", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc3", MaxIoPerSec: 0, MaxThroughInMb: 0}, + }, + }, + { + Cpu: 2, + Description: "flavor 2.5", + Id: "flv2.5", + MaxGB: 500, + Memory: 5, + MinGB: 5, + NodeType: "replica", + StorageClasses: []testFlavorStorageClass{ + {Class: "sc1", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc2", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc3", MaxIoPerSec: 0, MaxThroughInMb: 0}, + }, + }, + { + Cpu: 2, + Description: "flavor 2.6", + Id: "flv2.6", + MaxGB: 500, + Memory: 6, + MinGB: 5, + NodeType: "replica", + StorageClasses: []testFlavorStorageClass{ + {Class: "sc1", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc2", MaxIoPerSec: 0, MaxThroughInMb: 0}, + {Class: "sc3", MaxIoPerSec: 0, MaxThroughInMb: 0}, + }, + }, + /* ......................................................... */ +} + +func testFlavorListToResponseFlavorList(f []testFlavor) []postgresflex.ListFlavors { + result := make([]postgresflex.ListFlavors, len(f)) + for i, flavor := range f { + result[i] = testFlavorToResponseFlavor(flavor) + } + return result } func testFlavorToResponseFlavor(f testFlavor) postgresflex.ListFlavors { @@ -69,7 +448,7 @@ func testFlavorToResponseFlavor(f testFlavor) postgresflex.ListFlavors { } } -func (c postgresFlexClientMocked) GetFlavorsRequestExecute(_ context.Context, _, _ string, _ *int64, _ *int64, _ *postgresflex.FlavorSort) (*postgresflex.GetFlavorsResponse, error) { +func (c postgresFlexClientMocked) GetFlavorsRequestExecute(_ context.Context, _, _ string, page *int64, size *int64, _ *postgresflex.FlavorSort) (*postgresflex.GetFlavorsResponse, error) { if c.returnError { return nil, fmt.Errorf("get flavors failed") } @@ -77,17 +456,32 @@ func (c postgresFlexClientMocked) GetFlavorsRequestExecute(_ context.Context, _, var res postgresflex.GetFlavorsResponse var resFlavors []postgresflex.ListFlavors - for _, flv := range responseList { + myList := responseList[c.firstItem : c.lastItem+1] + // fmt.Printf("list length: %d\n", len(myList)) + + firstItem := *page**size - *size + // fmt.Printf("firstItem: %d\n", firstItem) + if firstItem > int64(len(myList)) { + firstItem = int64(len(myList)) + } + + lastItem := firstItem + *size + // fmt.Printf("lastItem: %d\n", lastItem) + if lastItem > int64(len(myList)) { + lastItem = int64(len(myList)) + } + + for _, flv := range myList[firstItem:lastItem] { resFlavors = append(resFlavors, testFlavorToResponseFlavor(flv)) } res.Flavors = &resFlavors res.Pagination = &postgresflex.Pagination{ - Page: utils.Ptr(int64(1)), - Size: utils.Ptr(int64(10)), + Page: page, + Size: size, Sort: utils.Ptr("id.asc"), TotalPages: utils.Ptr(int64(1)), - TotalRows: utils.Ptr(int64(len(responseList))), + TotalRows: utils.Ptr(int64(len(myList))), } return &res, nil @@ -99,36 +493,298 @@ func Test_getAllFlavors(t *testing.T) { region string } tests := []struct { - name string - args args - want []postgresflex.ListFlavors - wantErr bool + name string + args args + firstItem int + lastItem int + want []postgresflex.ListFlavors + wantErr bool }{ { - name: "success", + name: "find exactly one flavor", args: args{ projectId: "project", region: "region", }, + firstItem: 0, + lastItem: 0, want: []postgresflex.ListFlavors{ testFlavorToResponseFlavor(responseList[0]), }, wantErr: false, }, + { + name: "get exactly 1 page flavors", + args: args{ + projectId: "project", + region: "region", + }, + firstItem: 0, + lastItem: 9, + want: testFlavorListToResponseFlavorList(responseList[0:10]), + wantErr: false, + }, + { + name: "get exactly 20 flavors", + args: args{ + projectId: "project", + region: "region", + }, + firstItem: 0, + lastItem: 20, + // 0 indexed therefore we want :21 + want: testFlavorListToResponseFlavorList(responseList[0:21]), + wantErr: false, + }, + { + name: "get all flavors", + args: args{ + projectId: "project", + region: "region", + }, + firstItem: 0, + // we take care of max value at another place + lastItem: 20000, + // 0 indexed therefore we want :21 + want: testFlavorListToResponseFlavorList(responseList), + wantErr: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + first := tt.firstItem + if first > len(responseList)-1 { + first = len(responseList) - 1 + } + last := tt.lastItem + if last > len(responseList)-1 { + last = len(responseList) - 1 + } mockClient := postgresFlexClientMocked{ returnError: tt.wantErr, + firstItem: first, + lastItem: last, } got, err := getAllFlavors(context.TODO(), mockClient, tt.args.projectId, tt.args.region) if (err != nil) != tt.wantErr { t.Errorf("getAllFlavors() error = %v, wantErr %v", err, tt.wantErr) return } + + if diff := cmp.Diff(tt.want, got); diff != "" { + t.Errorf("mismatch (-want +got):\n%s", diff) + } + if !reflect.DeepEqual(got, tt.want) { t.Errorf("getAllFlavors() got = %v, want %v", got, tt.want) } }) } } + +func Test_loadFlavorId(t *testing.T) { + type args struct { + ctx context.Context + model *Model + flavor *flavorModel + storage *storageModel + } + tests := []struct { + name string + args args + firstItem int + lastItem int + want []postgresflex.ListFlavors + wantErr bool + }{ + { + name: "find a single flavor", + args: args{ + ctx: context.Background(), + model: &Model{ + ProjectId: basetypes.NewStringValue("project"), + Region: basetypes.NewStringValue("region"), + }, + flavor: &flavorModel{ + CPU: basetypes.NewInt64Value(1), + RAM: basetypes.NewInt64Value(1), + NodeType: basetypes.NewStringValue("Single"), + }, + storage: &storageModel{ + Class: basetypes.NewStringValue("sc1"), + Size: basetypes.NewInt64Value(100), + }, + }, + firstItem: 0, + lastItem: 3, + want: []postgresflex.ListFlavors{ + testFlavorToResponseFlavor(responseList[0]), + }, + wantErr: false, + }, + { + name: "find a single flavor by replicas option", + args: args{ + ctx: context.Background(), + model: &Model{ + ProjectId: basetypes.NewStringValue("project"), + Region: basetypes.NewStringValue("region"), + Replicas: basetypes.NewInt64Value(1), + }, + flavor: &flavorModel{ + CPU: basetypes.NewInt64Value(1), + RAM: basetypes.NewInt64Value(1), + }, + storage: &storageModel{ + Class: basetypes.NewStringValue("sc1"), + Size: basetypes.NewInt64Value(100), + }, + }, + firstItem: 0, + lastItem: 3, + want: []postgresflex.ListFlavors{ + testFlavorToResponseFlavor(responseList[0]), + }, + wantErr: false, + }, + { + name: "fail finding find a single flavor by replicas option", + args: args{ + ctx: context.Background(), + model: &Model{ + ProjectId: basetypes.NewStringValue("project"), + Region: basetypes.NewStringValue("region"), + Replicas: basetypes.NewInt64Value(1), + }, + flavor: &flavorModel{ + CPU: basetypes.NewInt64Value(1), + RAM: basetypes.NewInt64Value(1), + }, + storage: &storageModel{ + Class: basetypes.NewStringValue("sc1"), + Size: basetypes.NewInt64Value(100), + }, + }, + firstItem: 13, + lastItem: 23, + want: []postgresflex.ListFlavors{}, + wantErr: true, + }, + { + name: "find a replicas flavor", + args: args{ + ctx: context.Background(), + model: &Model{ + ProjectId: basetypes.NewStringValue("project"), + Region: basetypes.NewStringValue("region"), + }, + flavor: &flavorModel{ + CPU: basetypes.NewInt64Value(1), + RAM: basetypes.NewInt64Value(1), + NodeType: basetypes.NewStringValue("Replica"), + }, + storage: &storageModel{ + Class: basetypes.NewStringValue("sc1"), + Size: basetypes.NewInt64Value(100), + }, + }, + firstItem: 0, + lastItem: len(responseList) - 1, + want: []postgresflex.ListFlavors{ + testFlavorToResponseFlavor(responseList[11]), + }, + wantErr: false, + }, + { + name: "find a replicas flavor by replicas option", + args: args{ + ctx: context.Background(), + model: &Model{ + ProjectId: basetypes.NewStringValue("project"), + Region: basetypes.NewStringValue("region"), + Replicas: basetypes.NewInt64Value(3), + }, + flavor: &flavorModel{ + CPU: basetypes.NewInt64Value(1), + RAM: basetypes.NewInt64Value(1), + }, + storage: &storageModel{ + Class: basetypes.NewStringValue("sc1"), + Size: basetypes.NewInt64Value(100), + }, + }, + firstItem: 0, + lastItem: len(responseList) - 1, + want: []postgresflex.ListFlavors{ + testFlavorToResponseFlavor(responseList[11]), + }, + wantErr: false, + }, + { + name: "fail finding a replica flavor", + args: args{ + ctx: context.Background(), + model: &Model{ + ProjectId: basetypes.NewStringValue("project"), + Region: basetypes.NewStringValue("region"), + Replicas: basetypes.NewInt64Value(3), + }, + flavor: &flavorModel{ + CPU: basetypes.NewInt64Value(1), + RAM: basetypes.NewInt64Value(1), + }, + storage: &storageModel{ + Class: basetypes.NewStringValue("sc1"), + Size: basetypes.NewInt64Value(100), + }, + }, + firstItem: 0, + lastItem: 10, + want: []postgresflex.ListFlavors{}, + wantErr: true, + }, + { + name: "no flavor found error", + args: args{ + ctx: context.Background(), + model: &Model{ + ProjectId: basetypes.NewStringValue("project"), + Region: basetypes.NewStringValue("region"), + }, + flavor: &flavorModel{ + CPU: basetypes.NewInt64Value(10), + RAM: basetypes.NewInt64Value(1000), + NodeType: basetypes.NewStringValue("Single"), + }, + storage: &storageModel{ + Class: basetypes.NewStringValue("sc1"), + Size: basetypes.NewInt64Value(100), + }, + }, + firstItem: 0, + lastItem: 3, + want: []postgresflex.ListFlavors{}, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + first := tt.firstItem + if first > len(responseList)-1 { + first = len(responseList) - 1 + } + last := tt.lastItem + if last > len(responseList)-1 { + last = len(responseList) - 1 + } + mockClient := postgresFlexClientMocked{ + returnError: tt.wantErr, + firstItem: first, + lastItem: last, + } + if err := loadFlavorId(tt.args.ctx, mockClient, tt.args.model, tt.args.flavor, tt.args.storage); (err != nil) != tt.wantErr { + t.Errorf("loadFlavorId() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/stackit/internal/services/postgresflexalpha/instance/models.go b/stackit/internal/services/postgresflexalpha/instance/models.go index 1e550135..f2ffa39a 100644 --- a/stackit/internal/services/postgresflexalpha/instance/models.go +++ b/stackit/internal/services/postgresflexalpha/instance/models.go @@ -14,6 +14,7 @@ type Model struct { BackupSchedule types.String `tfsdk:"backup_schedule"` Flavor types.Object `tfsdk:"flavor"` Replicas types.Int64 `tfsdk:"replicas"` + RetentionDays types.Int64 `tfsdk:"retention_days"` Storage types.Object `tfsdk:"storage"` Version types.String `tfsdk:"version"` Region types.String `tfsdk:"region"` diff --git a/stackit/internal/services/postgresflexalpha/instance/resource.go b/stackit/internal/services/postgresflexalpha/instance/resource.go index 1340bd07..e1f058b4 100644 --- a/stackit/internal/services/postgresflexalpha/instance/resource.go +++ b/stackit/internal/services/postgresflexalpha/instance/resource.go @@ -132,6 +132,7 @@ func (r *instanceResource) Schema(_ context.Context, req resource.SchemaRequest, "project_id": "STACKIT project ID to which the instance is associated.", "name": "Instance name.", "backup_schedule": "The schedule for on what time and how often the database backup will be created. The schedule is written as a cron schedule.", + "retention_days": "The days of the retention period.", "flavor": "The block that defines the flavor data.", "flavor_id": "The ID of the flavor.", "flavor_description": "The flavor detailed flavor name.", @@ -203,6 +204,10 @@ func (r *instanceResource) Schema(_ context.Context, req resource.SchemaRequest, "backup_schedule": schema.StringAttribute{ Required: true, }, + "retention_days": schema.Int64Attribute{ + Description: descriptions["retention_days"], + Required: true, + }, "flavor": schema.SingleNestedAttribute{ Required: true, Description: descriptions["flavor"],