fix: fixed some tests

This commit is contained in:
Marcel_Henselin 2025-12-29 11:10:42 +01:00
parent 5b6576da1c
commit ff9f47edc3
12 changed files with 941 additions and 418 deletions

View file

@ -127,7 +127,6 @@ func (r *databaseDataSource) Schema(_ context.Context, _ datasource.SchemaReques
// Read refreshes the Terraform state with the latest data.
func (r *databaseDataSource) Read(
ctx context.Context,
// TODO - make it pointer
req datasource.ReadRequest,
resp *datasource.ReadResponse,
) { // nolint:gocritic // function signature required by Terraform

View file

@ -58,7 +58,6 @@ type databaseResource struct {
// Use the modifier to set the effective region in the current plan.
func (r *databaseResource) ModifyPlan(
ctx context.Context,
// TODO - make it pointer
req resource.ModifyPlanRequest,
resp *resource.ModifyPlanResponse,
) { // nolint:gocritic // function signature required by Terraform
@ -201,7 +200,6 @@ func (r *databaseResource) Schema(_ context.Context, _ resource.SchemaRequest, r
// Create creates the resource and sets the initial Terraform state.
func (r *databaseResource) Create(
ctx context.Context,
// TODO - make it pointer
req resource.CreateRequest,
resp *resource.CreateResponse,
) { // nolint:gocritic // function signature required by Terraform
@ -292,7 +290,6 @@ func (r *databaseResource) Create(
// Read refreshes the Terraform state with the latest data.
func (r *databaseResource) Read(
ctx context.Context,
// TODO - make it pointer
req resource.ReadRequest,
resp *resource.ReadResponse,
) { // nolint:gocritic // function signature required by Terraform

View file

@ -0,0 +1,514 @@
package postgresflex
import (
"context"
"reflect"
"testing"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)
func TestFlavorType_Equal(t1 *testing.T) {
type fields struct {
ObjectType basetypes.ObjectType
}
type args struct {
o attr.Type
}
tests := []struct {
name string
fields fields
args args
want bool
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t1.Run(tt.name, func(t1 *testing.T) {
t := FlavorType{
ObjectType: tt.fields.ObjectType,
}
if got := t.Equal(tt.args.o); got != tt.want {
t1.Errorf("Equal() = %v, want %v", got, tt.want)
}
})
}
}
func TestFlavorType_String(t1 *testing.T) {
type fields struct {
ObjectType basetypes.ObjectType
}
tests := []struct {
name string
fields fields
want string
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t1.Run(tt.name, func(t1 *testing.T) {
t := FlavorType{
ObjectType: tt.fields.ObjectType,
}
if got := t.String(); got != tt.want {
t1.Errorf("String() = %v, want %v", got, tt.want)
}
})
}
}
func TestFlavorType_ValueFromObject(t1 *testing.T) {
type fields struct {
ObjectType basetypes.ObjectType
}
type args struct {
in0 context.Context
in basetypes.ObjectValue
}
tests := []struct {
name string
fields fields
args args
want basetypes.ObjectValuable
want1 diag.Diagnostics
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t1.Run(tt.name, func(t1 *testing.T) {
t := FlavorType{
ObjectType: tt.fields.ObjectType,
}
got, got1 := t.ValueFromObject(tt.args.in0, tt.args.in)
if !reflect.DeepEqual(got, tt.want) {
t1.Errorf("ValueFromObject() got = %v, want %v", got, tt.want)
}
if !reflect.DeepEqual(got1, tt.want1) {
t1.Errorf("ValueFromObject() got1 = %v, want %v", got1, tt.want1)
}
})
}
}
func TestFlavorType_ValueFromTerraform(t1 *testing.T) {
type fields struct {
ObjectType basetypes.ObjectType
}
type args struct {
ctx context.Context
in tftypes.Value
}
tests := []struct {
name string
fields fields
args args
want attr.Value
wantErr bool
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t1.Run(tt.name, func(t1 *testing.T) {
t := FlavorType{
ObjectType: tt.fields.ObjectType,
}
got, err := t.ValueFromTerraform(tt.args.ctx, tt.args.in)
if (err != nil) != tt.wantErr {
t1.Errorf("ValueFromTerraform() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t1.Errorf("ValueFromTerraform() got = %v, want %v", got, tt.want)
}
})
}
}
func TestFlavorType_ValueType(t1 *testing.T) {
type fields struct {
ObjectType basetypes.ObjectType
}
type args struct {
in0 context.Context
}
tests := []struct {
name string
fields fields
args args
want attr.Value
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t1.Run(tt.name, func(t1 *testing.T) {
t := FlavorType{
ObjectType: tt.fields.ObjectType,
}
if got := t.ValueType(tt.args.in0); !reflect.DeepEqual(got, tt.want) {
t1.Errorf("ValueType() = %v, want %v", got, tt.want)
}
})
}
}
func TestFlavorValue_AttributeTypes(t *testing.T) {
type fields struct {
Cpu basetypes.Int64Value
Description basetypes.StringValue
Id basetypes.StringValue
Ram basetypes.Int64Value
state attr.ValueState
}
type args struct {
in0 context.Context
}
tests := []struct {
name string
fields fields
args args
want map[string]attr.Type
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
v := FlavorValue{
Cpu: tt.fields.Cpu,
Description: tt.fields.Description,
Id: tt.fields.Id,
Ram: tt.fields.Ram,
state: tt.fields.state,
}
if got := v.AttributeTypes(tt.args.in0); !reflect.DeepEqual(got, tt.want) {
t.Errorf("AttributeTypes() = %v, want %v", got, tt.want)
}
})
}
}
func TestFlavorValue_Equal(t *testing.T) {
type fields struct {
Cpu basetypes.Int64Value
Description basetypes.StringValue
Id basetypes.StringValue
Ram basetypes.Int64Value
state attr.ValueState
}
type args struct {
o attr.Value
}
tests := []struct {
name string
fields fields
args args
want bool
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
v := FlavorValue{
Cpu: tt.fields.Cpu,
Description: tt.fields.Description,
Id: tt.fields.Id,
Ram: tt.fields.Ram,
state: tt.fields.state,
}
if got := v.Equal(tt.args.o); got != tt.want {
t.Errorf("Equal() = %v, want %v", got, tt.want)
}
})
}
}
func TestFlavorValue_IsNull(t *testing.T) {
type fields struct {
Cpu basetypes.Int64Value
Description basetypes.StringValue
Id basetypes.StringValue
Ram basetypes.Int64Value
state attr.ValueState
}
tests := []struct {
name string
fields fields
want bool
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
v := FlavorValue{
Cpu: tt.fields.Cpu,
Description: tt.fields.Description,
Id: tt.fields.Id,
Ram: tt.fields.Ram,
state: tt.fields.state,
}
if got := v.IsNull(); got != tt.want {
t.Errorf("IsNull() = %v, want %v", got, tt.want)
}
})
}
}
func TestFlavorValue_IsUnknown(t *testing.T) {
type fields struct {
Cpu basetypes.Int64Value
Description basetypes.StringValue
Id basetypes.StringValue
Ram basetypes.Int64Value
state attr.ValueState
}
tests := []struct {
name string
fields fields
want bool
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
v := FlavorValue{
Cpu: tt.fields.Cpu,
Description: tt.fields.Description,
Id: tt.fields.Id,
Ram: tt.fields.Ram,
state: tt.fields.state,
}
if got := v.IsUnknown(); got != tt.want {
t.Errorf("IsUnknown() = %v, want %v", got, tt.want)
}
})
}
}
func TestFlavorValue_String(t *testing.T) {
type fields struct {
Cpu basetypes.Int64Value
Description basetypes.StringValue
Id basetypes.StringValue
Ram basetypes.Int64Value
state attr.ValueState
}
tests := []struct {
name string
fields fields
want string
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
v := FlavorValue{
Cpu: tt.fields.Cpu,
Description: tt.fields.Description,
Id: tt.fields.Id,
Ram: tt.fields.Ram,
state: tt.fields.state,
}
if got := v.String(); got != tt.want {
t.Errorf("String() = %v, want %v", got, tt.want)
}
})
}
}
func TestFlavorValue_ToObjectValue(t *testing.T) {
type fields struct {
Cpu basetypes.Int64Value
Description basetypes.StringValue
Id basetypes.StringValue
Ram basetypes.Int64Value
state attr.ValueState
}
type args struct {
in0 context.Context
}
tests := []struct {
name string
fields fields
args args
want basetypes.ObjectValue
want1 diag.Diagnostics
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
v := FlavorValue{
Cpu: tt.fields.Cpu,
Description: tt.fields.Description,
Id: tt.fields.Id,
Ram: tt.fields.Ram,
state: tt.fields.state,
}
got, got1 := v.ToObjectValue(tt.args.in0)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("ToObjectValue() got = %v, want %v", got, tt.want)
}
if !reflect.DeepEqual(got1, tt.want1) {
t.Errorf("ToObjectValue() got1 = %v, want %v", got1, tt.want1)
}
})
}
}
func TestFlavorValue_ToTerraformValue(t *testing.T) {
type fields struct {
Cpu basetypes.Int64Value
Description basetypes.StringValue
Id basetypes.StringValue
Ram basetypes.Int64Value
state attr.ValueState
}
type args struct {
ctx context.Context
}
tests := []struct {
name string
fields fields
args args
want tftypes.Value
wantErr bool
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
v := FlavorValue{
Cpu: tt.fields.Cpu,
Description: tt.fields.Description,
Id: tt.fields.Id,
Ram: tt.fields.Ram,
state: tt.fields.state,
}
got, err := v.ToTerraformValue(tt.args.ctx)
if (err != nil) != tt.wantErr {
t.Errorf("ToTerraformValue() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("ToTerraformValue() got = %v, want %v", got, tt.want)
}
})
}
}
func TestFlavorValue_Type(t *testing.T) {
type fields struct {
Cpu basetypes.Int64Value
Description basetypes.StringValue
Id basetypes.StringValue
Ram basetypes.Int64Value
state attr.ValueState
}
type args struct {
ctx context.Context
}
tests := []struct {
name string
fields fields
args args
want attr.Type
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
v := FlavorValue{
Cpu: tt.fields.Cpu,
Description: tt.fields.Description,
Id: tt.fields.Id,
Ram: tt.fields.Ram,
state: tt.fields.state,
}
if got := v.Type(tt.args.ctx); !reflect.DeepEqual(got, tt.want) {
t.Errorf("Type() = %v, want %v", got, tt.want)
}
})
}
}
func TestNewFlavorValue(t *testing.T) {
type args struct {
attributeTypes map[string]attr.Type
attributes map[string]attr.Value
}
tests := []struct {
name string
args args
want FlavorValue
want1 diag.Diagnostics
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, got1 := NewFlavorValue(tt.args.attributeTypes, tt.args.attributes)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewFlavorValue() got = %v, want %v", got, tt.want)
}
if !reflect.DeepEqual(got1, tt.want1) {
t.Errorf("NewFlavorValue() got1 = %v, want %v", got1, tt.want1)
}
})
}
}
func TestNewFlavorValueMust(t *testing.T) {
type args struct {
attributeTypes map[string]attr.Type
attributes map[string]attr.Value
}
tests := []struct {
name string
args args
want FlavorValue
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := NewFlavorValueMust(tt.args.attributeTypes, tt.args.attributes); !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewFlavorValueMust() = %v, want %v", got, tt.want)
}
})
}
}
func TestNewFlavorValueNull(t *testing.T) {
tests := []struct {
name string
want FlavorValue
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := NewFlavorValueNull(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewFlavorValueNull() = %v, want %v", got, tt.want)
}
})
}
}
func TestNewFlavorValueUnknown(t *testing.T) {
tests := []struct {
name string
want FlavorValue
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := NewFlavorValueUnknown(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewFlavorValueUnknown() = %v, want %v", got, tt.want)
}
})
}
}

View file

@ -101,7 +101,7 @@ func mapFields(
}
var flavorValues map[string]attr.Value
if instance.FlavorId == nil {
if instance.FlavorId == nil || *instance.FlavorId == "" {
return fmt.Errorf("instance has no flavor id")
}
if !flavor.Id.IsUnknown() && !flavor.Id.IsNull() {
@ -110,12 +110,28 @@ func mapFields(
}
}
if model.Flavor.IsNull() || model.Flavor.IsUnknown() {
var nodeType string
if flavor.NodeType.IsUnknown() || flavor.NodeType.IsNull() {
if instance.Replicas == nil {
return fmt.Errorf("instance has no replicas setting")
}
switch *instance.Replicas {
case 1:
nodeType = "Single"
case 3:
nodeType = "Replicas"
default:
return fmt.Errorf("could not determine replicas settings")
}
} else {
nodeType = flavor.NodeType.ValueString()
}
flavorValues = map[string]attr.Value{
"id": flavor.Id,
"description": flavor.Description,
"cpu": flavor.CPU,
"ram": flavor.RAM,
"node_type": flavor.NodeType,
"node_type": types.StringValue(nodeType),
}
} else {
flavorValues = model.Flavor.Attributes()
@ -143,6 +159,11 @@ func mapFields(
return fmt.Errorf("creating storage: %w", core.DiagsToError(diags))
}
if instance.Replicas == nil {
diags.AddError("error mapping fields", "replicas is nil")
return fmt.Errorf("replicas is nil")
}
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, instanceId)
model.InstanceId = types.StringValue(instanceId)
model.Name = types.StringPointerValue(instance.Name)
@ -159,7 +180,13 @@ func mapFields(
return nil
}
func toCreatePayload(model *Model, flavor *flavorModel, storage *storageModel, enc *encryptionModel, net *networkModel) (*postgresflex.CreateInstanceRequestPayload, error) {
func toCreatePayload(
model *Model,
flavor *flavorModel,
storage *storageModel,
enc *encryptionModel,
net *networkModel,
) (*postgresflex.CreateInstanceRequestPayload, error) {
if model == nil {
return nil, fmt.Errorf("nil model")
}
@ -170,10 +197,13 @@ func toCreatePayload(model *Model, flavor *flavorModel, storage *storageModel, e
return nil, fmt.Errorf("nil storage")
}
if model.Replicas.ValueInt64() > math.MaxInt32 {
return nil, fmt.Errorf("replica count too big: %d", model.Replicas.ValueInt64())
var replVal int32
if !model.Replicas.IsNull() && !model.Replicas.IsUnknown() {
if model.Replicas.ValueInt64() > math.MaxInt32 {
return nil, fmt.Errorf("replica count too big: %d", model.Replicas.ValueInt64())
}
replVal = int32(model.Replicas.ValueInt64()) // nolint:gosec // check is performed above
}
replVal := int32(model.Replicas.ValueInt64()) // nolint:gosec // check is performed above
storagePayload := &postgresflex.CreateInstanceRequestPayloadGetStorageArgType{
PerformanceClass: conversion.StringValueToPointer(storage.Class),
@ -197,6 +227,10 @@ func toCreatePayload(model *Model, flavor *flavorModel, storage *storageModel, e
}
}
if len(aclElements) < 1 {
return nil, fmt.Errorf("no acl elements found")
}
networkPayload := &postgresflex.CreateInstanceRequestPayloadGetNetworkArgType{}
if net != nil {
networkPayload = &postgresflex.CreateInstanceRequestPayloadGetNetworkArgType{
@ -205,6 +239,19 @@ func toCreatePayload(model *Model, flavor *flavorModel, storage *storageModel, e
}
}
if model.Replicas.IsNull() || model.Replicas.IsUnknown() {
if !flavor.NodeType.IsNull() && !flavor.NodeType.IsUnknown() {
switch strings.ToLower(flavor.NodeType.ValueString()) {
case "single":
replVal = int32(1)
case "replica":
replVal = int32(3)
default:
return nil, fmt.Errorf("flavor has invalid replica attribute")
}
}
}
return &postgresflex.CreateInstanceRequestPayload{
BackupSchedule: conversion.StringValueToPointer(model.BackupSchedule),
FlavorId: conversion.StringValueToPointer(flavor.Id),
@ -259,10 +306,22 @@ func loadFlavorId(ctx context.Context, client postgresflexClient, model *Model,
if ram == nil {
return fmt.Errorf("nil RAM")
}
nodeType := conversion.StringValueToPointer(flavor.NodeType)
if nodeType == nil {
return fmt.Errorf("nil 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"))
case 3:
nodeType = conversion.StringValueToPointer(types.StringValue("Replicas"))
default:
return fmt.Errorf("unknown Replicas value: %d", model.Replicas.ValueInt64())
}
}
storageClass := conversion.StringValueToPointer(storage.Class)
if storageClass == nil {
return fmt.Errorf("nil StorageClass")

View file

@ -0,0 +1,80 @@
package postgresflexalpha
import (
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
)
type Model struct {
Id types.String `tfsdk:"id"` // needed by TF
InstanceId types.String `tfsdk:"instance_id"`
ProjectId types.String `tfsdk:"project_id"`
Name types.String `tfsdk:"name"`
BackupSchedule types.String `tfsdk:"backup_schedule"`
Flavor types.Object `tfsdk:"flavor"`
Replicas types.Int64 `tfsdk:"replicas"`
Storage types.Object `tfsdk:"storage"`
Version types.String `tfsdk:"version"`
Region types.String `tfsdk:"region"`
Encryption types.Object `tfsdk:"encryption"`
Network types.Object `tfsdk:"network"`
}
type encryptionModel struct {
KeyRingId types.String `tfsdk:"keyring_id"`
KeyId types.String `tfsdk:"key_id"`
KeyVersion types.String `tfsdk:"key_version"`
ServiceAccount types.String `tfsdk:"service_account"`
}
var encryptionTypes = map[string]attr.Type{
"keyring_id": basetypes.StringType{},
"key_id": basetypes.StringType{},
"key_version": basetypes.StringType{},
"service_account": basetypes.StringType{},
}
type networkModel struct {
ACL types.List `tfsdk:"acl"`
AccessScope types.String `tfsdk:"access_scope"`
InstanceAddress types.String `tfsdk:"instance_address"`
RouterAddress types.String `tfsdk:"router_address"`
}
var networkTypes = map[string]attr.Type{
"acl": basetypes.ListType{ElemType: types.StringType},
"access_scope": basetypes.StringType{},
"instance_address": basetypes.StringType{},
"router_address": basetypes.StringType{},
}
// Struct corresponding to Model.Flavor
type flavorModel struct {
Id types.String `tfsdk:"id"`
Description types.String `tfsdk:"description"`
CPU types.Int64 `tfsdk:"cpu"`
RAM types.Int64 `tfsdk:"ram"`
NodeType types.String `tfsdk:"node_type"`
}
// Types corresponding to flavorModel
var flavorTypes = map[string]attr.Type{
"id": basetypes.StringType{},
"description": basetypes.StringType{},
"cpu": basetypes.Int64Type{},
"ram": basetypes.Int64Type{},
"node_type": basetypes.StringType{},
}
// Struct corresponding to Model.Storage
type storageModel struct {
Class types.String `tfsdk:"class"`
Size types.Int64 `tfsdk:"size"`
}
// Types corresponding to storageModel
var storageTypes = map[string]attr.Type{
"class": basetypes.StringType{},
"size": basetypes.Int64Type{},
}

View file

@ -8,6 +8,7 @@ import (
"strings"
"time"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
postgresflex "github.com/mhenselin/terraform-provider-stackitprivatepreview/pkg/postgresflexalpha"
"github.com/mhenselin/terraform-provider-stackitprivatepreview/pkg/postgresflexalpha/wait"
postgresflexUtils "github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/utils"
@ -35,85 +36,13 @@ import (
// Ensure the implementation satisfies the expected interfaces.
var (
_ resource.Resource = &instanceResource{}
_ resource.ResourceWithConfigure = &instanceResource{}
_ resource.ResourceWithImportState = &instanceResource{}
_ resource.ResourceWithModifyPlan = &instanceResource{}
_ resource.Resource = &instanceResource{}
_ resource.ResourceWithConfigure = &instanceResource{}
_ resource.ResourceWithImportState = &instanceResource{}
_ resource.ResourceWithModifyPlan = &instanceResource{}
_ resource.ResourceWithValidateConfig = &instanceResource{}
)
type Model struct {
Id types.String `tfsdk:"id"` // needed by TF
InstanceId types.String `tfsdk:"instance_id"`
ProjectId types.String `tfsdk:"project_id"`
Name types.String `tfsdk:"name"`
BackupSchedule types.String `tfsdk:"backup_schedule"`
Flavor types.Object `tfsdk:"flavor"`
Replicas types.Int64 `tfsdk:"replicas"`
Storage types.Object `tfsdk:"storage"`
Version types.String `tfsdk:"version"`
Region types.String `tfsdk:"region"`
Encryption types.Object `tfsdk:"encryption"`
Network types.Object `tfsdk:"network"`
}
type encryptionModel struct {
KeyRingId types.String `tfsdk:"keyring_id"`
KeyId types.String `tfsdk:"key_id"`
KeyVersion types.String `tfsdk:"key_version"`
ServiceAccount types.String `tfsdk:"service_account"`
}
var encryptionTypes = map[string]attr.Type{
"keyring_id": basetypes.StringType{},
"key_id": basetypes.StringType{},
"key_version": basetypes.StringType{},
"service_account": basetypes.StringType{},
}
type networkModel struct {
ACL types.List `tfsdk:"acl"`
AccessScope types.String `tfsdk:"access_scope"`
InstanceAddress types.String `tfsdk:"instance_address"`
RouterAddress types.String `tfsdk:"router_address"`
}
var networkTypes = map[string]attr.Type{
"acl": basetypes.ListType{ElemType: types.StringType},
"access_scope": basetypes.StringType{},
"instance_address": basetypes.StringType{},
"router_address": basetypes.StringType{},
}
// Struct corresponding to Model.Flavor
type flavorModel struct {
Id types.String `tfsdk:"id"`
Description types.String `tfsdk:"description"`
CPU types.Int64 `tfsdk:"cpu"`
RAM types.Int64 `tfsdk:"ram"`
NodeType types.String `tfsdk:"node_type"`
}
// Types corresponding to flavorModel
var flavorTypes = map[string]attr.Type{
"id": basetypes.StringType{},
"description": basetypes.StringType{},
"cpu": basetypes.Int64Type{},
"ram": basetypes.Int64Type{},
"node_type": basetypes.StringType{},
}
// Struct corresponding to Model.Storage
type storageModel struct {
Class types.String `tfsdk:"class"`
Size types.Int64 `tfsdk:"size"`
}
// Types corresponding to storageModel
var storageTypes = map[string]attr.Type{
"class": basetypes.StringType{},
"size": basetypes.Int64Type{},
}
// NewInstanceResource is a helper function to simplify the provider implementation.
func NewInstanceResource() resource.Resource {
return &instanceResource{}
@ -125,6 +54,24 @@ type instanceResource struct {
providerData core.ProviderData
}
func (r *instanceResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) {
var data Model
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
if data.Replicas.IsNull() || data.Replicas.IsUnknown() {
resp.Diagnostics.AddAttributeWarning(
path.Root("replicas"),
"Missing Attribute Configuration",
"Expected replicas to be configured. "+
"The resource may return unexpected results.",
)
}
}
// 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
@ -179,18 +126,35 @@ func (r *instanceResource) Configure(ctx context.Context, req resource.Configure
// Schema defines the schema for the resource.
func (r *instanceResource) Schema(_ context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
descriptions := map[string]string{
"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.",
"main": "Postgres Flex instance resource schema. Must have a `region` specified in the provider configuration.",
"id": "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`instance_id`\".",
"instance_id": "ID of the PostgresFlex instance.",
"project_id": "STACKIT project ID to which the instance is associated.",
"name": "Instance name.",
"acl": "The Access Control List (ACL) for the PostgresFlex instance.",
"region": "The resource region. If not defined, the provider region is used.",
"encryption": "The encryption block.",
"key_id": "Key ID of the encryption key.",
"main": "Postgres Flex instance resource schema. Must have a `region` specified in the provider configuration.",
"id": "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`instance_id`\".",
"instance_id": "ID of the PostgresFlex instance.",
"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.",
"flavor": "The block that defines the flavor data.",
"flavor_id": "The ID of the flavor.",
"flavor_description": "The flavor detailed flavor name.",
"flavor_cpu": "The CPU count of the flavor.",
"flavor_ram": "The RAM count of the flavor.",
"flavor_node_type": "The node type of the flavor. (Single or Replicas)",
"replicas": "The number of replicas.",
"storage": "The block of the storage configuration.",
"storage_class": "The storage class used.",
"storage_size": "The disk size of the storage.",
"region": "The resource region. If not defined, the provider region is used.",
"version": "The database version used.",
"encryption": "The encryption block.",
"keyring_id": "KeyRing ID of the encryption key.",
"key_id": "Key ID of the encryption key.",
"key_version": "Key version of the encryption key.",
"service_account": "The service account ID of the service account.",
"network": "The network block configuration.",
"access_scope": "The access scope. (Either SNA or PUBLIC)",
"acl": "The Access Control List (ACL) for the PostgresFlex instance.",
"instance_address": "The returned instance address.",
"router_address": "The returned router address.",
}
// TODO @mhenselin - do the rest
resp.Schema = schema.Schema{
Description: descriptions["main"],
@ -240,44 +204,64 @@ func (r *instanceResource) Schema(_ context.Context, req resource.SchemaRequest,
Required: true,
},
"flavor": schema.SingleNestedAttribute{
Required: true,
Required: true,
Description: descriptions["flavor"],
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Computed: true,
Optional: true,
Description: descriptions["flavor_id"],
Computed: true,
Optional: true,
PlanModifiers: []planmodifier.String{
UseStateForUnknownIfFlavorUnchanged(req),
stringplanmodifier.RequiresReplace(),
},
},
"description": schema.StringAttribute{
Computed: true,
Computed: true,
Description: descriptions["flavor_description"],
PlanModifiers: []planmodifier.String{
UseStateForUnknownIfFlavorUnchanged(req),
},
},
"cpu": schema.Int64Attribute{
Required: true,
Description: descriptions["flavor_cpu"],
Required: true,
},
"ram": schema.Int64Attribute{
Required: true,
Description: descriptions["flavor_ram"],
Required: true,
},
"node_type": schema.StringAttribute{
Description: descriptions["flavor_node_type"],
Computed: true,
Optional: true,
PlanModifiers: []planmodifier.String{
// TODO @mhenselin anschauen
UseStateForUnknownIfFlavorUnchanged(req),
stringplanmodifier.RequiresReplace(),
},
},
},
},
"replicas": schema.Int64Attribute{
Required: true,
PlanModifiers: []planmodifier.Int64{
int64planmodifier.RequiresReplace(),
},
},
"storage": schema.SingleNestedAttribute{
Required: true,
Attributes: map[string]schema.Attribute{
"class": schema.StringAttribute{
Required: true,
Required: true,
Description: descriptions["storage_class"],
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"size": schema.Int64Attribute{
Required: true,
Description: descriptions["storage_size"],
Required: true,
// PlanModifiers: []planmodifier.Int64{
// TODO - req replace if new size smaller than state size
// int64planmodifier.RequiresReplaceIf(),
@ -286,7 +270,8 @@ func (r *instanceResource) Schema(_ context.Context, req resource.SchemaRequest,
},
},
"version": schema.StringAttribute{
Required: true,
Description: descriptions["version"],
Required: true,
},
"region": schema.StringAttribute{
Optional: true,
@ -399,7 +384,11 @@ func (r *instanceResource) Schema(_ context.Context, req resource.SchemaRequest,
}
// 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
// Retrieve values from plan
var model Model
diags := req.Plan.Get(ctx, &model)

View file

@ -2,10 +2,12 @@ package postgresflexalpha
import (
"context"
"reflect"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
postgresflex "github.com/mhenselin/terraform-provider-stackitprivatepreview/pkg/postgresflexalpha"
"github.com/stackitcloud/stackit-sdk-go/core/utils"
@ -25,7 +27,6 @@ import (
// }
func TestMapFields(t *testing.T) {
t.Skip("Skipping - needs refactoring")
const testRegion = "region"
tests := []struct {
description string
@ -40,16 +41,26 @@ func TestMapFields(t *testing.T) {
isValid bool
}{
{
"default_values",
"default_values does exactly mean what?",
Model{
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
Replicas: types.Int64Value(1),
},
&postgresflex.GetInstanceResponse{
FlavorId: utils.Ptr("flavor_id"),
Replicas: postgresflex.GetInstanceResponseGetReplicasAttributeType(utils.Ptr(int32(1))),
},
&flavorModel{
NodeType: types.StringValue("Single"),
},
&postgresflex.GetInstanceResponse{},
&flavorModel{},
&storageModel{},
&encryptionModel{},
&networkModel{},
&networkModel{
ACL: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("0.0.0.0/0"),
}),
},
testRegion,
Model{
Id: types.StringValue("pid,region,iid"),
@ -63,202 +74,96 @@ func TestMapFields(t *testing.T) {
"description": types.StringNull(),
"cpu": types.Int64Null(),
"ram": types.Int64Null(),
"node_type": types.StringNull(),
"node_type": types.StringValue("Single"),
}),
Replicas: types.Int64Value(1),
Encryption: types.ObjectValueMust(encryptionTypes, map[string]attr.Value{
"keyring_id": types.StringNull(),
"key_id": types.StringNull(),
"key_version": types.StringNull(),
"service_account": types.StringNull(),
}),
Replicas: types.Int64Null(),
Storage: types.ObjectValueMust(storageTypes, map[string]attr.Value{
"class": types.StringNull(),
"size": types.Int64Null(),
}),
Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{
"acl": types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("0.0.0.0/0"),
}),
"access_scope": types.StringNull(),
"instance_address": types.StringNull(),
"router_address": types.StringNull(),
}),
Version: types.StringNull(),
Region: types.StringValue(testRegion),
},
true,
},
{
"simple_values",
Model{
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
},
&postgresflex.GetInstanceResponse{
// Acl: &[]string{
// "ip1",
// "ip2",
// "",
// },
BackupSchedule: utils.Ptr("schedule"),
// Flavor: &postgresflex.Flavor{
// Cpu: utils.Ptr(int64(12)),
// Description: utils.Ptr("description"),
// Id: utils.Ptr("flavor_id"),
// Ram: utils.Ptr(int64(34)),
// },
Id: utils.Ptr("iid"),
Name: utils.Ptr("name"),
Replicas: postgresflex.GetInstanceResponseGetReplicasAttributeType(utils.Ptr(int32(56))),
Status: postgresflex.GetInstanceResponseGetStatusAttributeType(utils.Ptr("status")),
Storage: &postgresflex.Storage{
PerformanceClass: utils.Ptr("class"),
Size: utils.Ptr(int64(78)),
},
Version: utils.Ptr("version"),
},
&flavorModel{},
&storageModel{},
&encryptionModel{},
&networkModel{},
testRegion,
Model{
Id: types.StringValue("pid,region,iid"),
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
Name: types.StringValue("name"),
// ACL: types.ListValueMust(types.StringType, []attr.Value{
// types.StringValue("ip1"),
// types.StringValue("ip2"),
// types.StringValue(""),
// }),
BackupSchedule: types.StringValue("schedule"),
Flavor: types.ObjectValueMust(flavorTypes, map[string]attr.Value{
"id": types.StringValue("flavor_id"),
"description": types.StringValue("description"),
"cpu": types.Int64Value(12),
"ram": types.Int64Value(34),
}),
Replicas: types.Int64Value(56),
Storage: types.ObjectValueMust(storageTypes, map[string]attr.Value{
"class": types.StringValue("class"),
"size": types.Int64Value(78),
}),
Version: types.StringValue("version"),
Region: types.StringValue(testRegion),
},
true,
},
{
"simple_values_no_flavor_and_storage",
Model{
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
},
&postgresflex.GetInstanceResponse{
// Acl: &[]string{
// "ip1",
// "ip2",
// "",
// },
BackupSchedule: utils.Ptr("schedule"),
FlavorId: nil,
Id: utils.Ptr("iid"),
Name: utils.Ptr("name"),
Replicas: postgresflex.GetInstanceResponseGetReplicasAttributeType(utils.Ptr(int32(56))),
Status: postgresflex.GetInstanceResponseGetStatusAttributeType(utils.Ptr("status")),
Storage: nil,
Version: utils.Ptr("version"),
},
&flavorModel{
CPU: types.Int64Value(12),
RAM: types.Int64Value(34),
},
&storageModel{
Class: types.StringValue("class"),
Size: types.Int64Value(78),
},
&encryptionModel{},
&networkModel{},
testRegion,
Model{
Id: types.StringValue("pid,region,iid"),
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
Name: types.StringValue("name"),
// ACL: types.ListValueMust(types.StringType, []attr.Value{
// types.StringValue("ip1"),
// types.StringValue("ip2"),
// types.StringValue(""),
// }),
BackupSchedule: types.StringValue("schedule"),
Flavor: types.ObjectValueMust(flavorTypes, map[string]attr.Value{
"id": types.StringNull(),
"description": types.StringNull(),
"cpu": types.Int64Value(12),
"ram": types.Int64Value(34),
}),
Replicas: types.Int64Value(56),
Storage: types.ObjectValueMust(storageTypes, map[string]attr.Value{
"class": types.StringValue("class"),
"size": types.Int64Value(78),
}),
Version: types.StringValue("version"),
Region: types.StringValue(testRegion),
},
true,
},
{
"acl_unordered",
Model{
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
// ACL: types.ListValueMust(types.StringType, []attr.Value{
// types.StringValue("ip2"),
// types.StringValue(""),
// types.StringValue("ip1"),
// }),
},
&postgresflex.GetInstanceResponse{
// Acl: &[]string{
// "",
// "ip1",
// "ip2",
// },
BackupSchedule: utils.Ptr("schedule"),
FlavorId: nil,
Id: utils.Ptr("iid"),
Name: utils.Ptr("name"),
Replicas: postgresflex.GetInstanceResponseGetReplicasAttributeType(utils.Ptr(int32(56))),
Status: postgresflex.GetInstanceResponseGetStatusAttributeType(utils.Ptr("status")),
Storage: nil,
Version: utils.Ptr("version"),
},
&flavorModel{
CPU: types.Int64Value(12),
RAM: types.Int64Value(34),
},
&storageModel{
Class: types.StringValue("class"),
Size: types.Int64Value(78),
},
&encryptionModel{},
&networkModel{},
testRegion,
Model{
Id: types.StringValue("pid,region,iid"),
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
Name: types.StringValue("name"),
// ACL: types.ListValueMust(types.StringType, []attr.Value{
// types.StringValue("ip2"),
// types.StringValue(""),
// types.StringValue("ip1"),
// }),
BackupSchedule: types.StringValue("schedule"),
Flavor: types.ObjectValueMust(flavorTypes, map[string]attr.Value{
"id": types.StringNull(),
"description": types.StringNull(),
"cpu": types.Int64Value(12),
"ram": types.Int64Value(34),
}),
Replicas: types.Int64Value(56),
Storage: types.ObjectValueMust(storageTypes, map[string]attr.Value{
"class": types.StringValue("class"),
"size": types.Int64Value(78),
}),
Version: types.StringValue("version"),
Region: types.StringValue(testRegion),
},
true,
},
// {
// "acl_unordered",
// Model{
// InstanceId: types.StringValue("iid"),
// ProjectId: types.StringValue("pid"),
// // ACL: types.ListValueMust(types.StringType, []attr.Value{
// // types.StringValue("ip2"),
// // types.StringValue(""),
// // types.StringValue("ip1"),
// // }),
// },
// &postgresflex.GetInstanceResponse{
// // Acl: &[]string{
// // "",
// // "ip1",
// // "ip2",
// // },
// BackupSchedule: utils.Ptr("schedule"),
// FlavorId: nil,
// Id: utils.Ptr("iid"),
// Name: utils.Ptr("name"),
// Replicas: postgresflex.GetInstanceResponseGetReplicasAttributeType(utils.Ptr(int32(56))),
// Status: postgresflex.GetInstanceResponseGetStatusAttributeType(utils.Ptr("status")),
// Storage: nil,
// Version: utils.Ptr("version"),
// },
// &flavorModel{
// CPU: types.Int64Value(12),
// RAM: types.Int64Value(34),
// },
// &storageModel{
// Class: types.StringValue("class"),
// Size: types.Int64Value(78),
// },
// &encryptionModel{},
// &networkModel{},
// testRegion,
// Model{
// Id: types.StringValue("pid,region,iid"),
// InstanceId: types.StringValue("iid"),
// ProjectId: types.StringValue("pid"),
// Name: types.StringValue("name"),
// // ACL: types.ListValueMust(types.StringType, []attr.Value{
// // types.StringValue("ip2"),
// // types.StringValue(""),
// // types.StringValue("ip1"),
// // }),
// BackupSchedule: types.StringValue("schedule"),
// Flavor: types.ObjectValueMust(flavorTypes, map[string]attr.Value{
// "id": types.StringNull(),
// "description": types.StringNull(),
// "cpu": types.Int64Value(12),
// "ram": types.Int64Value(34),
// }),
// Replicas: types.Int64Value(56),
// Storage: types.ObjectValueMust(storageTypes, map[string]attr.Value{
// "class": types.StringValue("class"),
// "size": types.Int64Value(78),
// }),
// Version: types.StringValue("version"),
// Region: types.StringValue(testRegion),
// },
// true,
// },
{
"nil_response",
Model{
@ -300,7 +205,7 @@ func TestMapFields(t *testing.T) {
t.Fatalf("Should not have failed: %v", err)
}
if tt.isValid {
diff := cmp.Diff(tt.state, tt.expected)
diff := cmp.Diff(tt.expected, tt.state)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
@ -310,7 +215,6 @@ func TestMapFields(t *testing.T) {
}
func TestToCreatePayload(t *testing.T) {
t.Skip("Skipping - needs refactoring")
tests := []struct {
description string
input *Model
@ -324,89 +228,50 @@ func TestToCreatePayload(t *testing.T) {
}{
{
"default_values",
&Model{},
&Model{
Replicas: types.Int64Value(1),
},
[]string{},
&flavorModel{},
&storageModel{},
&encryptionModel{},
&networkModel{},
&networkModel{
ACL: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("0.0.0.0/0"),
}),
},
&postgresflex.CreateInstanceRequestPayload{
Storage: postgresflex.CreateInstanceRequestPayloadGetStorageAttributeType(&postgresflex.Storage{}),
Encryption: &postgresflex.InstanceEncryption{},
Network: &postgresflex.InstanceNetwork{
Acl: &[]string{"0.0.0.0/0"},
},
Replicas: postgresflex.CreateInstanceRequestPayloadGetReplicasAttributeType(utils.Ptr(int32(1))),
},
true,
},
{
"use flavor node_type instead of replicas",
&Model{},
[]string{},
&flavorModel{
NodeType: types.StringValue("Single"),
},
&storageModel{},
&encryptionModel{},
&networkModel{
ACL: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("0.0.0.0/0"),
}),
},
&postgresflex.CreateInstanceRequestPayload{
//Acl: &[]string{},
Storage: postgresflex.CreateInstanceRequestPayloadGetStorageAttributeType(&postgresflex.Storage{}),
},
true,
},
{
"simple_values",
&Model{
BackupSchedule: types.StringValue("schedule"),
Name: types.StringValue("name"),
Replicas: types.Int64Value(12),
Version: types.StringValue("version"),
},
[]string{
"ip_1",
"ip_2",
},
&flavorModel{
Id: types.StringValue("flavor_id"),
},
&storageModel{
Class: types.StringValue("class"),
Size: types.Int64Value(34),
},
&encryptionModel{},
&networkModel{},
&postgresflex.CreateInstanceRequestPayload{
// Acl: &[]string{
// "ip_1",
// "ip_2",
// },
BackupSchedule: utils.Ptr("schedule"),
FlavorId: utils.Ptr("flavor_id"),
Name: utils.Ptr("name"),
Replicas: postgresflex.CreateInstanceRequestPayloadGetReplicasAttributeType(utils.Ptr(int32(56))),
Storage: postgresflex.CreateInstanceRequestPayloadGetStorageAttributeType(&postgresflex.Storage{
PerformanceClass: utils.Ptr("class"),
Size: utils.Ptr(int64(34)),
}),
Version: utils.Ptr("version"),
},
true,
},
{
" ^^1null_fields_and_int_conversions",
&Model{
BackupSchedule: types.StringNull(),
Name: types.StringNull(),
Replicas: types.Int64Value(2123456789),
Version: types.StringNull(),
},
[]string{
"",
},
&flavorModel{
Id: types.StringNull(),
},
&storageModel{
Class: types.StringNull(),
Size: types.Int64Null(),
},
&encryptionModel{},
&networkModel{},
&postgresflex.CreateInstanceRequestPayload{
// Acl: &[]string{
// "",
// },
BackupSchedule: nil,
FlavorId: nil,
Name: nil,
Replicas: postgresflex.CreateInstanceRequestPayloadGetReplicasAttributeType(utils.Ptr(int32(2123456789))),
Storage: postgresflex.CreateInstanceRequestPayloadGetStorageAttributeType(&postgresflex.Storage{
PerformanceClass: nil,
Size: nil,
}),
Version: nil,
Storage: postgresflex.CreateInstanceRequestPayloadGetStorageAttributeType(&postgresflex.Storage{}),
Encryption: &postgresflex.InstanceEncryption{},
Network: &postgresflex.InstanceNetwork{
Acl: &[]string{"0.0.0.0/0"},
},
Replicas: postgresflex.CreateInstanceRequestPayloadGetReplicasAttributeType(utils.Ptr(int32(1))),
},
true,
},
@ -465,7 +330,7 @@ func TestToCreatePayload(t *testing.T) {
t.Fatalf("Should not have failed: %v", err)
}
if tt.isValid {
diff := cmp.Diff(output, tt.expected)
diff := cmp.Diff(tt.expected, output)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
@ -780,3 +645,22 @@ func TestToCreatePayload(t *testing.T) {
// })
// }
// }
func TestNewInstanceResource(t *testing.T) {
tests := []struct {
name string
want resource.Resource
}{
{
name: "create empty instance resource",
want: &instanceResource{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := NewInstanceResource(); !reflect.DeepEqual(got, tt.want) {
t.Errorf("NewInstanceResource() = %v, want %v", got, tt.want)
}
})
}
}