Feat/separate functions (#19)

* chore: work save

* fix: refactor flavors

* fix: refactor pg user and database

* fix: refactor flavor parameters

* fix: refactor tf script

* chore: work save

* chore: work save

* chore: work save

---------

Co-authored-by: Marcel S. Henselin <marcel.henselin@stackit.cloud>
This commit is contained in:
Marcel S. Henselin 2026-01-13 12:19:12 +01:00 committed by GitHub
parent 910551f09d
commit 0150fea302
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
54 changed files with 6010 additions and 2826 deletions

View file

@ -0,0 +1,252 @@
package sqlserverFlexAlphaFlavor
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/mhenselin/terraform-provider-stackitprivatepreview/pkg/sqlserverflexalpha"
"github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/conversion"
"github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/utils"
sqlserverflex "github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/services/sqlserverflexalpha"
sqlserverflexUtils "github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/services/sqlserverflexalpha/utils"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/core"
)
// Ensure the implementation satisfies the expected interfaces.
var (
_ datasource.DataSource = &flavorDataSource{}
)
type FlavorModel struct {
ProjectId types.String `tfsdk:"project_id"`
Region types.String `tfsdk:"region"`
StorageClass types.String `tfsdk:"storage_class"`
Cpu types.Int64 `tfsdk:"cpu"`
Description types.String `tfsdk:"description"`
Id types.String `tfsdk:"id"`
FlavorId types.String `tfsdk:"flavor_id"`
MaxGb types.Int64 `tfsdk:"max_gb"`
Memory types.Int64 `tfsdk:"ram"`
MinGb types.Int64 `tfsdk:"min_gb"`
NodeType types.String `tfsdk:"node_type"`
StorageClasses types.List `tfsdk:"storage_classes"`
}
// NewFlavorDataSource is a helper function to simplify the provider implementation.
func NewFlavorDataSource() datasource.DataSource {
return &flavorDataSource{}
}
// flavorDataSource is the data source implementation.
type flavorDataSource struct {
client *sqlserverflexalpha.APIClient
providerData core.ProviderData
}
// Metadata returns the data source type name.
func (r *flavorDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_sqlserverflexalpha_flavor"
}
// Configure adds the provider configured client to the data source.
func (r *flavorDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
var ok bool
r.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics)
if !ok {
return
}
apiClient := sqlserverflexUtils.ConfigureClient(ctx, &r.providerData, &resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
}
r.client = apiClient
tflog.Info(ctx, "Postgres Flex instance client configured")
}
func (r *flavorDataSource) Schema(ctx context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"project_id": schema.StringAttribute{
Required: true,
Description: "The cpu count of the instance.",
MarkdownDescription: "The cpu count of the instance.",
},
"region": schema.StringAttribute{
Required: true,
Description: "The flavor description.",
MarkdownDescription: "The flavor description.",
},
"cpu": schema.Int64Attribute{
Required: true,
Description: "The cpu count of the instance.",
MarkdownDescription: "The cpu count of the instance.",
},
"ram": schema.Int64Attribute{
Required: true,
Description: "The memory of the instance in Gibibyte.",
MarkdownDescription: "The memory of the instance in Gibibyte.",
},
"storage_class": schema.StringAttribute{
Required: true,
Description: "The memory of the instance in Gibibyte.",
MarkdownDescription: "The memory of the instance in Gibibyte.",
},
"description": schema.StringAttribute{
Computed: true,
Description: "The flavor description.",
MarkdownDescription: "The flavor description.",
},
"id": schema.StringAttribute{
Computed: true,
Description: "The terraform id of the instance flavor.",
MarkdownDescription: "The terraform id of the instance flavor.",
},
"flavor_id": schema.StringAttribute{
Computed: true,
Description: "The flavor id of the instance flavor.",
MarkdownDescription: "The flavor id of the instance flavor.",
},
"max_gb": schema.Int64Attribute{
Computed: true,
Description: "maximum storage which can be ordered for the flavor in Gigabyte.",
MarkdownDescription: "maximum storage which can be ordered for the flavor in Gigabyte.",
},
"min_gb": schema.Int64Attribute{
Computed: true,
Description: "minimum storage which is required to order in Gigabyte.",
MarkdownDescription: "minimum storage which is required to order in Gigabyte.",
},
"node_type": schema.StringAttribute{
Required: true,
Description: "defines the nodeType it can be either single or replica",
MarkdownDescription: "defines the nodeType it can be either single or replica",
},
"storage_classes": schema.ListNestedAttribute{
Computed: true,
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"class": schema.StringAttribute{
Computed: true,
},
"max_io_per_sec": schema.Int64Attribute{
Computed: true,
},
"max_through_in_mb": schema.Int64Attribute{
Computed: true,
},
},
CustomType: sqlserverflex.StorageClassesType{
ObjectType: types.ObjectType{
AttrTypes: sqlserverflex.StorageClassesValue{}.AttributeTypes(ctx),
},
},
},
},
},
}
}
func (r *flavorDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var model FlavorModel
diags := req.Config.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
ctx = core.InitProviderContext(ctx)
projectId := model.ProjectId.ValueString()
region := r.providerData.GetRegionWithOverride(model.Region)
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "region", region)
flavors, err := getAllFlavors(ctx, r.client, projectId, region)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading flavors", fmt.Sprintf("getAllFlavors: %v", err))
return
}
var foundFlavors []sqlserverflexalpha.ListFlavors
for _, flavor := range flavors {
if model.Cpu.ValueInt64() != *flavor.Cpu {
continue
}
if model.Memory.ValueInt64() != *flavor.Memory {
continue
}
if model.NodeType.ValueString() != *flavor.NodeType {
continue
}
for _, sc := range *flavor.StorageClasses {
if model.StorageClass.ValueString() != *sc.Class {
continue
}
foundFlavors = append(foundFlavors, flavor)
}
}
if len(foundFlavors) == 0 {
resp.Diagnostics.AddError("get flavor", "could not find requested flavor")
return
}
if len(foundFlavors) > 1 {
resp.Diagnostics.AddError("get flavor", "found too many matching flavors")
return
}
f := foundFlavors[0]
model.Description = types.StringValue(*f.Description)
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, *f.Id)
model.FlavorId = types.StringValue(*f.Id)
model.MaxGb = types.Int64Value(*f.MaxGB)
model.MinGb = types.Int64Value(*f.MinGB)
if f.StorageClasses == nil {
model.StorageClasses = types.ListNull(sqlserverflex.StorageClassesType{
ObjectType: basetypes.ObjectType{
AttrTypes: sqlserverflex.StorageClassesValue{}.AttributeTypes(ctx),
},
})
} else {
var scList []attr.Value
for _, sc := range *f.StorageClasses {
scList = append(
scList,
sqlserverflex.NewStorageClassesValueMust(
sqlserverflex.StorageClassesValue{}.AttributeTypes(ctx),
map[string]attr.Value{
"class": types.StringValue(*sc.Class),
"max_io_per_sec": types.Int64Value(*sc.MaxIoPerSec),
"max_through_in_mb": types.Int64Value(*sc.MaxThroughInMb),
},
),
)
}
storageClassesList := types.ListValueMust(
sqlserverflex.StorageClassesType{
ObjectType: basetypes.ObjectType{
AttrTypes: sqlserverflex.StorageClassesValue{}.AttributeTypes(ctx),
},
},
scList,
)
model.StorageClasses = storageClassesList
}
// Set refreshed state
diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
tflog.Info(ctx, "Postgres Flex flavors read")
}

View file

@ -0,0 +1,201 @@
package sqlserverFlexAlphaFlavor
import (
"context"
"fmt"
sqlserverflex "github.com/mhenselin/terraform-provider-stackitprivatepreview/pkg/sqlserverflexalpha"
)
type flavorsClient interface {
GetFlavorsRequestExecute(
ctx context.Context,
projectId, region string,
page, size *int64,
sort *sqlserverflex.FlavorSort,
) (*sqlserverflex.GetFlavorsResponse, error)
}
//func loadFlavorId(ctx context.Context, client flavorsClient, model *Model, flavor *flavorModel, storage *storageModel) error {
// if model == nil {
// return fmt.Errorf("nil model")
// }
// if flavor == nil {
// return fmt.Errorf("nil flavor")
// }
// cpu := flavor.CPU.ValueInt64()
// if cpu == 0 {
// return fmt.Errorf("nil CPU")
// }
// ram := flavor.RAM.ValueInt64()
// if ram == 0 {
// return fmt.Errorf("nil RAM")
// }
//
// 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 = "Single"
// case 3:
// nodeType = "Replica"
// default:
// return fmt.Errorf("unknown Replicas value: %d", model.Replicas.ValueInt64())
// }
// }
//
// storageClass := conversion.StringValueToPointer(storage.Class)
// if storageClass == nil {
// return fmt.Errorf("nil StorageClass")
// }
// storageSize := conversion.Int64ValueToPointer(storage.Size)
// if storageSize == nil {
// return fmt.Errorf("nil StorageSize")
// }
//
// projectId := model.ProjectId.ValueString()
// region := model.Region.ValueString()
//
// flavorList, err := getAllFlavors(ctx, client, projectId, region)
// if err != nil {
// return err
// }
//
// 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) {
// continue
// }
// if *f.Cpu == cpu && *f.Memory == ram {
// var useSc *sqlserverflex.FlavorStorageClassesStorageClass
// for _, sc := range *f.StorageClasses {
// if *sc.Class != *storageClass {
// continue
// }
// if *storageSize < *f.MinGB || *storageSize > *f.MaxGB {
// return fmt.Errorf("storage size %d out of bounds (min: %d - max: %d)", *storageSize, *f.MinGB, *f.MaxGB)
// }
// useSc = &sc
// }
// if useSc == nil {
// return fmt.Errorf("no storage class found for %s", *storageClass)
// }
//
// 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 {
// avl = fmt.Sprintf("%s\n- %d CPU, %d GB RAM, storage %s (min: %d - max: %d)", avl, *f.Cpu, *f.Memory, *cls.Class, *f.MinGB, *f.MaxGB)
// }
// }
// if foundFlavorCount > 1 {
// return fmt.Errorf(
// "number of flavors returned: %d\nmultiple flavors found: %d flavors\n %s",
// len(flavorList),
// foundFlavorCount,
// strings.Join(foundFlavors, "\n "),
// )
// }
// if flavor.Id.ValueString() == "" {
// return fmt.Errorf("couldn't find flavor, available specs are:%s", avl)
// }
//
// return nil
//}
func getAllFlavors(ctx context.Context, client flavorsClient, projectId, region string) ([]sqlserverflex.ListFlavors, error) {
if projectId == "" || region == "" {
return nil, fmt.Errorf("listing sqlserverflex flavors: projectId and region are required")
}
var flavorList []sqlserverflex.ListFlavors
page := int64(1)
size := int64(10)
sort := sqlserverflex.FLAVORSORT_INDEX_ASC
counter := 0
for {
res, err := client.GetFlavorsRequestExecute(ctx, projectId, region, &page, &size, &sort)
if err != nil {
return nil, fmt.Errorf("listing sqlserverflex flavors: %w", err)
}
if res.Flavors == nil {
return nil, fmt.Errorf("finding flavors for project %s", projectId)
}
pagination := res.GetPagination()
flavors := res.GetFlavors()
flavorList = append(flavorList, flavors...)
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
}
//func getFlavorModelById(ctx context.Context, client flavorsClient, model *Model, flavor *flavorModel) error {
// if model == nil {
// return fmt.Errorf("nil model")
// }
// if flavor == nil {
// return fmt.Errorf("nil flavor")
// }
// id := conversion.StringValueToPointer(flavor.Id)
// if id == nil {
// return fmt.Errorf("nil flavor ID")
// }
//
// flavor.Id = types.StringValue("")
//
// projectId := model.ProjectId.ValueString()
// region := model.Region.ValueString()
//
// flavorList, err := getAllFlavors(ctx, client, projectId, region)
// if err != nil {
// return err
// }
//
// avl := ""
// for _, f := range flavorList {
// if f.Id == nil || f.Cpu == nil || f.Memory == nil {
// continue
// }
// if *f.Id == *id {
// flavor.Id = types.StringValue(*f.Id)
// flavor.Description = types.StringValue(*f.Description)
// flavor.CPU = types.Int64Value(*f.Cpu)
// flavor.RAM = types.Int64Value(*f.Memory)
// flavor.NodeType = types.StringValue(*f.NodeType)
// break
// }
// avl = fmt.Sprintf("%s\n- %d CPU, %d GB RAM", avl, *f.Cpu, *f.Memory)
// }
// if flavor.Id.ValueString() == "" {
// return fmt.Errorf("couldn't find flavor, available specs are: %s", avl)
// }
//
// return nil
//}

View file

@ -0,0 +1,79 @@
package postgresFlexAlphaFlavor
import (
"context"
"github.com/mhenselin/terraform-provider-stackitprivatepreview/pkg/postgresflexalpha"
"github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/conversion"
"github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha"
postgresflexUtils "github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/utils"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/core"
)
// Ensure the implementation satisfies the expected interfaces.
var (
_ datasource.DataSource = &flavorListDataSource{}
)
// NewFlavorListDataSource is a helper function to simplify the provider implementation.
func NewFlavorListDataSource() datasource.DataSource {
return &flavorListDataSource{}
}
// flavorDataSource is the data source implementation.
type flavorListDataSource struct {
client *postgresflexalpha.APIClient
providerData core.ProviderData
}
// Metadata returns the data source type name.
func (r *flavorListDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_postgresflexalpha_flavorlist"
}
// Configure adds the provider configured client to the data source.
func (r *flavorListDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
var ok bool
r.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics)
if !ok {
return
}
apiClient := postgresflexUtils.ConfigureClient(ctx, &r.providerData, &resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
}
r.client = apiClient
tflog.Info(ctx, "Postgres Flex flavors client configured")
}
func (r *flavorListDataSource) Schema(ctx context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = postgresflex.FlavorDataSourceSchema(ctx)
}
func (r *flavorListDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var model postgresflex.FlavorModel
diags := req.Config.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
ctx = core.InitProviderContext(ctx)
projectId := model.ProjectId.ValueString()
region := r.providerData.GetRegionWithOverride(model.Region)
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "region", region)
// Set refreshed state
diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
tflog.Info(ctx, "Postgres Flex flavors read")
}