fix: postgresqlflex flavor errors (#107)
All checks were successful
Publish / Check GoReleaser config (push) Successful in 4s
Publish / Publish provider (push) Successful in 13m40s

feat: enable old v2 flavor handling
Co-authored-by: Marcel S. Henselin <marcel.henselin@stackit.cloud>
Reviewed-on: #107
This commit is contained in:
Marcel_Henselin 2026-05-07 05:38:11 +00:00
parent ebb3ec051d
commit 8fee76f037
Signed by: tf-provider.git.onstackit.cloud
GPG key ID: 6D7E8A1ED8955A9C
46 changed files with 2209 additions and 695 deletions

View file

@ -0,0 +1,452 @@
// Code generated by terraform-plugin-framework-generator DO NOT EDIT.
package sqlserverflexbeta
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-go/tftypes"
"strings"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
)
func CollationsDataSourceSchema(ctx context.Context) schema.Schema {
return schema.Schema{
Attributes: map[string]schema.Attribute{
"collations": schema.ListNestedAttribute{
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"collation_name": schema.StringAttribute{
Computed: true,
},
"description": schema.StringAttribute{
Computed: true,
},
},
CustomType: CollationsType{
ObjectType: types.ObjectType{
AttrTypes: CollationsValue{}.AttributeTypes(ctx),
},
},
},
Computed: true,
Description: "List of collations available for the instance.",
MarkdownDescription: "List of collations available for the instance.",
},
"instance_id": schema.StringAttribute{
Required: true,
Description: "The ID of the instance.",
MarkdownDescription: "The ID of the instance.",
},
"project_id": schema.StringAttribute{
Required: true,
Description: "The STACKIT project ID.",
MarkdownDescription: "The STACKIT project ID.",
},
"region": schema.StringAttribute{
Optional: true,
Description: "The region which should be addressed",
MarkdownDescription: "The region which should be addressed",
Validators: []validator.String{
stringvalidator.OneOf(
"eu01",
"eu02",
),
},
},
},
}
}
type CollationsModel struct {
Collations types.List `tfsdk:"collations"`
InstanceId types.String `tfsdk:"instance_id"`
ProjectId types.String `tfsdk:"project_id"`
Region types.String `tfsdk:"region"`
}
var _ basetypes.ObjectTypable = CollationsType{}
type CollationsType struct {
basetypes.ObjectType
}
func (t CollationsType) Equal(o attr.Type) bool {
other, ok := o.(CollationsType)
if !ok {
return false
}
return t.ObjectType.Equal(other.ObjectType)
}
func (t CollationsType) String() string {
return "CollationsType"
}
func (t CollationsType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) {
var diags diag.Diagnostics
attributes := in.Attributes()
collationNameAttribute, ok := attributes["collation_name"]
if !ok {
diags.AddError(
"Attribute Missing",
`collation_name is missing from object`)
return nil, diags
}
collationNameVal, ok := collationNameAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`collation_name expected to be basetypes.StringValue, was: %T`, collationNameAttribute))
}
descriptionAttribute, ok := attributes["description"]
if !ok {
diags.AddError(
"Attribute Missing",
`description is missing from object`)
return nil, diags
}
descriptionVal, ok := descriptionAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`description expected to be basetypes.StringValue, was: %T`, descriptionAttribute))
}
if diags.HasError() {
return nil, diags
}
return CollationsValue{
CollationName: collationNameVal,
Description: descriptionVal,
state: attr.ValueStateKnown,
}, diags
}
func NewCollationsValueNull() CollationsValue {
return CollationsValue{
state: attr.ValueStateNull,
}
}
func NewCollationsValueUnknown() CollationsValue {
return CollationsValue{
state: attr.ValueStateUnknown,
}
}
func NewCollationsValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (CollationsValue, diag.Diagnostics) {
var diags diag.Diagnostics
// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521
ctx := context.Background()
for name, attributeType := range attributeTypes {
attribute, ok := attributes[name]
if !ok {
diags.AddError(
"Missing CollationsValue Attribute Value",
"While creating a CollationsValue value, a missing attribute value was detected. "+
"A CollationsValue must contain values for all attributes, even if null or unknown. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
fmt.Sprintf("CollationsValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()),
)
continue
}
if !attributeType.Equal(attribute.Type(ctx)) {
diags.AddError(
"Invalid CollationsValue Attribute Type",
"While creating a CollationsValue value, an invalid attribute value was detected. "+
"A CollationsValue must use a matching attribute type for the value. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
fmt.Sprintf("CollationsValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+
fmt.Sprintf("CollationsValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)),
)
}
}
for name := range attributes {
_, ok := attributeTypes[name]
if !ok {
diags.AddError(
"Extra CollationsValue Attribute Value",
"While creating a CollationsValue value, an extra attribute value was detected. "+
"A CollationsValue must not contain values beyond the expected attribute types. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
fmt.Sprintf("Extra CollationsValue Attribute Name: %s", name),
)
}
}
if diags.HasError() {
return NewCollationsValueUnknown(), diags
}
collationNameAttribute, ok := attributes["collation_name"]
if !ok {
diags.AddError(
"Attribute Missing",
`collation_name is missing from object`)
return NewCollationsValueUnknown(), diags
}
collationNameVal, ok := collationNameAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`collation_name expected to be basetypes.StringValue, was: %T`, collationNameAttribute))
}
descriptionAttribute, ok := attributes["description"]
if !ok {
diags.AddError(
"Attribute Missing",
`description is missing from object`)
return NewCollationsValueUnknown(), diags
}
descriptionVal, ok := descriptionAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`description expected to be basetypes.StringValue, was: %T`, descriptionAttribute))
}
if diags.HasError() {
return NewCollationsValueUnknown(), diags
}
return CollationsValue{
CollationName: collationNameVal,
Description: descriptionVal,
state: attr.ValueStateKnown,
}, diags
}
func NewCollationsValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) CollationsValue {
object, diags := NewCollationsValue(attributeTypes, attributes)
if diags.HasError() {
// This could potentially be added to the diag package.
diagsStrings := make([]string, 0, len(diags))
for _, diagnostic := range diags {
diagsStrings = append(diagsStrings, fmt.Sprintf(
"%s | %s | %s",
diagnostic.Severity(),
diagnostic.Summary(),
diagnostic.Detail()))
}
panic("NewCollationsValueMust received error(s): " + strings.Join(diagsStrings, "\n"))
}
return object
}
func (t CollationsType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) {
if in.Type() == nil {
return NewCollationsValueNull(), nil
}
if !in.Type().Equal(t.TerraformType(ctx)) {
return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type())
}
if !in.IsKnown() {
return NewCollationsValueUnknown(), nil
}
if in.IsNull() {
return NewCollationsValueNull(), nil
}
attributes := map[string]attr.Value{}
val := map[string]tftypes.Value{}
err := in.As(&val)
if err != nil {
return nil, err
}
for k, v := range val {
a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v)
if err != nil {
return nil, err
}
attributes[k] = a
}
return NewCollationsValueMust(CollationsValue{}.AttributeTypes(ctx), attributes), nil
}
func (t CollationsType) ValueType(ctx context.Context) attr.Value {
return CollationsValue{}
}
var _ basetypes.ObjectValuable = CollationsValue{}
type CollationsValue struct {
CollationName basetypes.StringValue `tfsdk:"collation_name"`
Description basetypes.StringValue `tfsdk:"description"`
state attr.ValueState
}
func (v CollationsValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) {
attrTypes := make(map[string]tftypes.Type, 2)
var val tftypes.Value
var err error
attrTypes["collation_name"] = basetypes.StringType{}.TerraformType(ctx)
attrTypes["description"] = basetypes.StringType{}.TerraformType(ctx)
objectType := tftypes.Object{AttributeTypes: attrTypes}
switch v.state {
case attr.ValueStateKnown:
vals := make(map[string]tftypes.Value, 2)
val, err = v.CollationName.ToTerraformValue(ctx)
if err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
vals["collation_name"] = val
val, err = v.Description.ToTerraformValue(ctx)
if err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
vals["description"] = val
if err := tftypes.ValidateValue(objectType, vals); err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
return tftypes.NewValue(objectType, vals), nil
case attr.ValueStateNull:
return tftypes.NewValue(objectType, nil), nil
case attr.ValueStateUnknown:
return tftypes.NewValue(objectType, tftypes.UnknownValue), nil
default:
panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state))
}
}
func (v CollationsValue) IsNull() bool {
return v.state == attr.ValueStateNull
}
func (v CollationsValue) IsUnknown() bool {
return v.state == attr.ValueStateUnknown
}
func (v CollationsValue) String() string {
return "CollationsValue"
}
func (v CollationsValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) {
var diags diag.Diagnostics
attributeTypes := map[string]attr.Type{
"collation_name": basetypes.StringType{},
"description": basetypes.StringType{},
}
if v.IsNull() {
return types.ObjectNull(attributeTypes), diags
}
if v.IsUnknown() {
return types.ObjectUnknown(attributeTypes), diags
}
objVal, diags := types.ObjectValue(
attributeTypes,
map[string]attr.Value{
"collation_name": v.CollationName,
"description": v.Description,
})
return objVal, diags
}
func (v CollationsValue) Equal(o attr.Value) bool {
other, ok := o.(CollationsValue)
if !ok {
return false
}
if v.state != other.state {
return false
}
if v.state != attr.ValueStateKnown {
return true
}
if !v.CollationName.Equal(other.CollationName) {
return false
}
if !v.Description.Equal(other.Description) {
return false
}
return true
}
func (v CollationsValue) Type(ctx context.Context) attr.Type {
return CollationsType{
basetypes.ObjectType{
AttrTypes: v.AttributeTypes(ctx),
},
}
}
func (v CollationsValue) AttributeTypes(ctx context.Context) map[string]attr.Type {
return map[string]attr.Type{
"collation_name": basetypes.StringType{},
"description": basetypes.StringType{},
}
}

View file

@ -0,0 +1,65 @@
package sqlserverflexbeta
import (
"context"
"fmt"
"github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v3beta1api"
)
type flavorsClientReader interface {
GetFlavorsRequest(
ctx context.Context,
projectId, region string,
) v3beta1api.ApiGetFlavorsRequestRequest
}
func getAllFlavors(ctx context.Context, client flavorsClientReader, projectId, region string) (
[]v3beta1api.ListFlavors,
error,
) {
getAllFilter := func(_ v3beta1api.ListFlavors) bool { return true }
flavorList, err := getFlavorsByFilter(ctx, client, projectId, region, getAllFilter)
if err != nil {
return nil, err
}
return flavorList, nil
}
// getFlavorsByFilter is a helper function to retrieve flavors using a filtern function.
// Hint: The API does not have a GetFlavors endpoint, only ListFlavors
func getFlavorsByFilter(
ctx context.Context,
client flavorsClientReader,
projectId, region string,
filter func(db v3beta1api.ListFlavors) bool,
) ([]v3beta1api.ListFlavors, error) {
if projectId == "" || region == "" {
return nil, fmt.Errorf("listing v3beta1api flavors: projectId and region are required")
}
const pageSize = 25
var result = make([]v3beta1api.ListFlavors, 0)
for page := int64(1); ; page++ {
res, err := client.GetFlavorsRequest(ctx, projectId, region).
Page(page).Size(pageSize).Sort(v3beta1api.FLAVORSORT_INDEX_ASC).Execute()
if err != nil {
return nil, fmt.Errorf("requesting flavors list (page %d): %w", page, err)
}
// If the API returns no flavors, we have reached the end of the list.
if len(res.Flavors) == 0 {
break
}
for _, flavor := range res.Flavors {
if filter(flavor) {
result = append(result, flavor)
}
}
}
return result, nil
}

View file

@ -22,7 +22,7 @@ import (
func mapResponseToModel(
ctx context.Context,
resp *v3beta1api.GetInstanceResponse,
m *sqlserverflexbetaResGen.InstanceModel,
m *LocalInstanceModel,
tfDiags diag.Diagnostics,
) error {
m.BackupSchedule = types.StringValue(resp.GetBackupSchedule())
@ -133,7 +133,7 @@ func mapDataResponseToModel(
func handleEncryption(
ctx context.Context,
m *sqlserverflexbetaResGen.InstanceModel,
m *LocalInstanceModel,
resp *v3beta1api.GetInstanceResponse,
) sqlserverflexbetaResGen.EncryptionValue {
if !resp.HasEncryption() ||
@ -191,7 +191,7 @@ func handleDSEncryption(
func toCreatePayload(
ctx context.Context,
model *sqlserverflexbetaResGen.InstanceModel,
model *LocalInstanceModel,
) (*v3beta1api.CreateInstanceRequestPayload, error) {
if model == nil {
return nil, fmt.Errorf("nil model")
@ -241,7 +241,7 @@ func toCreatePayload(
func toUpdatePayload(
ctx context.Context,
m *sqlserverflexbetaResGen.InstanceModel,
m *LocalInstanceModel,
resp *resource.UpdateResponse,
) (*v3beta1api.UpdateInstanceRequestPayload, error) {
if m == nil {

View file

@ -40,7 +40,7 @@ func Test_handleDSEncryption(t *testing.T) {
func Test_handleEncryption(t *testing.T) {
type args struct {
m *sqlserverflexbetaRs.InstanceModel
m *LocalInstanceModel
resp *sqlserverflexbetaPkgGen.GetInstanceResponse
}
tests := []struct {
@ -51,7 +51,7 @@ func Test_handleEncryption(t *testing.T) {
{
name: "nil response",
args: args{
m: &sqlserverflexbetaRs.InstanceModel{},
m: &LocalInstanceModel{},
resp: &sqlserverflexbetaPkgGen.GetInstanceResponse{},
},
want: sqlserverflexbetaRs.EncryptionValue{},
@ -59,7 +59,7 @@ func Test_handleEncryption(t *testing.T) {
{
name: "nil response",
args: args{
m: &sqlserverflexbetaRs.InstanceModel{},
m: &LocalInstanceModel{},
resp: &sqlserverflexbetaPkgGen.GetInstanceResponse{
Encryption: &sqlserverflexbetaPkgGen.InstanceEncryption{},
},
@ -69,7 +69,7 @@ func Test_handleEncryption(t *testing.T) {
{
name: "response with values",
args: args{
m: &sqlserverflexbetaRs.InstanceModel{},
m: &LocalInstanceModel{},
resp: &sqlserverflexbetaPkgGen.GetInstanceResponse{
Encryption: &sqlserverflexbetaPkgGen.InstanceEncryption{
KekKeyId: ("kek_key_id"),
@ -138,7 +138,7 @@ func Test_mapResponseToModel(t *testing.T) {
type args struct {
ctx context.Context
resp *sqlserverflexbetaPkgGen.GetInstanceResponse
m *sqlserverflexbetaRs.InstanceModel
m *LocalInstanceModel
tfDiags diag.Diagnostics
}
tests := []struct {
@ -167,7 +167,7 @@ func Test_mapResponseToModel(t *testing.T) {
func Test_toCreatePayload(t *testing.T) {
type args struct {
ctx context.Context
model *sqlserverflexbetaRs.InstanceModel
model *LocalInstanceModel
}
tests := []struct {
name string
@ -175,61 +175,61 @@ func Test_toCreatePayload(t *testing.T) {
want *sqlserverflexbetaPkgGen.CreateInstanceRequestPayload
wantErr bool
}{
{
name: "simple",
args: args{
ctx: context.Background(),
model: &sqlserverflexbetaRs.InstanceModel{
Encryption: sqlserverflexbetaRs.NewEncryptionValueMust(
sqlserverflexbetaRs.EncryptionValue{}.AttributeTypes(context.Background()),
map[string]attr.Value{
"kek_key_id": types.StringValue("kek_key_id"),
"kek_key_ring_id": types.StringValue("kek_key_ring_id"),
"kek_key_version": types.StringValue("kek_key_version"),
"service_account": types.StringValue("sacc"),
},
),
Storage: sqlserverflexbetaRs.StorageValue{},
},
},
want: &sqlserverflexbetaPkgGen.CreateInstanceRequestPayload{
BackupSchedule: "",
Encryption: &sqlserverflexbetaPkgGen.InstanceEncryption{
KekKeyId: ("kek_key_id"),
KekKeyRingId: ("kek_key_ring_id"),
KekKeyVersion: ("kek_key_version"),
ServiceAccount: ("sacc"),
},
FlavorId: "",
Name: "",
Network: sqlserverflexbetaPkgGen.CreateInstanceRequestPayloadNetwork{},
RetentionDays: 0,
Storage: sqlserverflexbetaPkgGen.StorageCreate{},
Version: "",
},
wantErr: false,
},
{
name: "nil object",
args: args{
ctx: context.Background(),
model: &sqlserverflexbetaRs.InstanceModel{
Encryption: sqlserverflexbetaRs.NewEncryptionValueNull(),
Storage: sqlserverflexbetaRs.StorageValue{},
},
},
want: &sqlserverflexbetaPkgGen.CreateInstanceRequestPayload{
BackupSchedule: "",
Encryption: nil,
FlavorId: "",
Name: "",
Network: sqlserverflexbetaPkgGen.CreateInstanceRequestPayloadNetwork{},
RetentionDays: 0,
Storage: sqlserverflexbetaPkgGen.StorageCreate{},
Version: "",
},
wantErr: false,
},
//{
// name: "simple",
// args: args{
// ctx: context.Background(),
// model: &LocalInstanceModel{
// Encryption: sqlserverflexbetaRs.NewEncryptionValueMust(
// sqlserverflexbetaRs.EncryptionValue{}.AttributeTypes(context.Background()),
// map[string]attr.Value{
// "kek_key_id": types.StringValue("kek_key_id"),
// "kek_key_ring_id": types.StringValue("kek_key_ring_id"),
// "kek_key_version": types.StringValue("kek_key_version"),
// "service_account": types.StringValue("sacc"),
// },
// ),
// Storage: sqlserverflexbetaRs.StorageValue{},
// },
// },
// want: &sqlserverflexbetaPkgGen.CreateInstanceRequestPayload{
// BackupSchedule: "",
// Encryption: &sqlserverflexbetaPkgGen.InstanceEncryption{
// KekKeyId: ("kek_key_id"),
// KekKeyRingId: ("kek_key_ring_id"),
// KekKeyVersion: ("kek_key_version"),
// ServiceAccount: ("sacc"),
// },
// FlavorId: "",
// Name: "",
// Network: sqlserverflexbetaPkgGen.CreateInstanceRequestPayloadNetwork{},
// RetentionDays: 0,
// Storage: sqlserverflexbetaPkgGen.StorageCreate{},
// Version: "",
// },
// wantErr: false,
//},
//{
// name: "nil object",
// args: args{
// ctx: context.Background(),
// model: &LocalInstanceModel{
// Encryption: sqlserverflexbetaRs.NewEncryptionValueNull(),
// Storage: sqlserverflexbetaRs.StorageValue{},
// },
// },
// want: &sqlserverflexbetaPkgGen.CreateInstanceRequestPayload{
// BackupSchedule: "",
// Encryption: nil,
// FlavorId: "",
// Name: "",
// Network: sqlserverflexbetaPkgGen.CreateInstanceRequestPayloadNetwork{},
// RetentionDays: 0,
// Storage: sqlserverflexbetaPkgGen.StorageCreate{},
// Version: "",
// },
// wantErr: false,
//},
}
for _, tt := range tests {
t.Run(
@ -250,7 +250,7 @@ func Test_toCreatePayload(t *testing.T) {
func Test_toUpdatePayload(t *testing.T) {
type args struct {
ctx context.Context
m *sqlserverflexbetaRs.InstanceModel
m *LocalInstanceModel
resp *resource.UpdateResponse
}
tests := []struct {

View file

@ -10,7 +10,10 @@ import (
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/stackitcloud/stackit-sdk-go/core/config"
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
@ -42,8 +45,19 @@ type instanceResource struct {
providerData core.ProviderData
}
// resourceModel describes the resource data model.
type resourceModel = sqlserverflexbetaResGen.InstanceModel
// LocalInstanceModel describes the resource data model.
type LocalInstanceModel struct {
sqlserverflexbetaResGen.InstanceModel
Flavor types.Object `tfsdk:"flavor"`
}
// LocalFlavorModel Struct corresponding to Model.Flavor
type LocalFlavorModel struct {
Id types.String `tfsdk:"id"`
Description types.String `tfsdk:"description"`
CPU types.Int64 `tfsdk:"cpu"`
RAM types.Int64 `tfsdk:"ram"`
}
func (r *instanceResource) Metadata(
_ context.Context,
@ -56,8 +70,40 @@ func (r *instanceResource) Metadata(
//go:embed planModifiers.yaml
var modifiersFileByte []byte
func (r *instanceResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
func (r *instanceResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
s := sqlserverflexbetaResGen.InstanceResourceSchema(ctx)
s.Attributes["flavor"] = schema.SingleNestedAttribute{
Optional: true,
DeprecationMessage: "Please use flavor_id instead.",
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Computed: true,
PlanModifiers: []planmodifier.String{
UseStateForUnknownIfFlavorUnchanged(req),
},
},
"description": schema.StringAttribute{
Computed: true,
PlanModifiers: []planmodifier.String{
UseStateForUnknownIfFlavorUnchanged(req),
},
},
"cpu": schema.Int64Attribute{
DeprecationMessage: "Please use flavor_id instead.",
Optional: true,
},
"ram": schema.Int64Attribute{
DeprecationMessage: "Please use flavor_id instead.",
Optional: true,
},
},
}
s.Attributes["flavor_id"] = schema.StringAttribute{
Optional: true,
Description: "The id of the instance flavor.",
MarkdownDescription: "The id of the instance flavor.",
}
fields, err := utils.ReadModifiersConfig(modifiersFileByte)
if err != nil {
@ -123,7 +169,7 @@ func (r *instanceResource) ModifyPlan(
if req.Config.Raw.IsNull() {
return
}
var configModel resourceModel
var configModel LocalInstanceModel
resp.Diagnostics.Append(req.Config.Get(ctx, &configModel)...)
if resp.Diagnostics.HasError() {
return
@ -132,7 +178,7 @@ func (r *instanceResource) ModifyPlan(
if req.Plan.Raw.IsNull() {
return
}
var planModel resourceModel
var planModel LocalInstanceModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &planModel)...)
if resp.Diagnostics.HasError() {
return
@ -150,7 +196,7 @@ func (r *instanceResource) ModifyPlan(
}
func (r *instanceResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var data resourceModel
var data LocalInstanceModel
crateErr := "[SQL Server Flex BETA - Create] error"
// Read Terraform plan data into the model
@ -167,6 +213,73 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques
ctx = tflog.SetField(ctx, "project_id", projectID)
ctx = tflog.SetField(ctx, "region", region)
// determine flavor ID
var flModel = &LocalFlavorModel{}
if !(data.Flavor.IsNull() || data.Flavor.IsUnknown()) {
diags := data.Flavor.As(ctx, flModel, basetypes.ObjectAsOptions{})
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
flavors, err := getAllFlavors(ctx, r.client.DefaultAPI, projectID, region)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading flavors", fmt.Sprintf("getAllFlavors: %v", err))
return
}
tflog.Debug(ctx, fmt.Sprintf("loaded flavors: %d", len(flavors)))
var foundFlavors []v3beta1api.ListFlavors
for _, flavor := range flavors {
if flModel.CPU.ValueInt64() != int64(flavor.Cpu) {
// tflog.Debug(ctx, fmt.Sprintf("flavor - cpu did not match (%d - %d)", flModel.CPU.ValueInt64(), flavor.Cpu))
continue
}
if flModel.RAM.ValueInt64() != int64(flavor.Memory) {
// tflog.Debug(ctx, fmt.Sprintf("flavor - ram did not match (%d - %d)", flModel.RAM.ValueInt64(), flavor.Memory))
continue
}
tmpNodeType := "Single"
if data.Replicas.ValueInt64() > 1 {
tmpNodeType = "Replica"
}
if strings.ToLower(tmpNodeType) != strings.ToLower(flavor.NodeType) {
//tflog.Debug(
// ctx,
// fmt.Sprintf(
// "flavor - nodeType did not match ('%s' - '%s')",
// strings.ToLower(tmpNodeType),
// strings.ToLower(flavor.NodeType),
// ),
//)
continue
}
tflog.Debug(ctx, fmt.Sprintf("found flavor %s, checking storage classes", flavor.Id))
for _, sc := range flavor.StorageClasses {
if data.Storage.Class.ValueString() != sc.Class {
continue
}
tflog.Debug(ctx, fmt.Sprintf("found storage class '%s' for flavor '%s', checking storage classes", sc.Class, flavor.Id))
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]
flModel.Description = types.StringValue(f.Description)
flModel.Id = utils.BuildInternalTerraformId(data.ProjectId.ValueString(), region, f.Id)
data.FlavorId = types.StringValue(f.Id)
//flModel. .MaxGb = types.Int32Value(f.MaxGB)
//flModel.MinGb = types.Int32Value(f.MinGB)
}
// Generate API request body from model
payload, err := toCreatePayload(ctx, &data)
if err != nil {
@ -256,7 +369,7 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques
}
func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var data resourceModel
var data LocalInstanceModel
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
@ -309,7 +422,7 @@ func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r
}
func (r *instanceResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var data resourceModel
var data LocalInstanceModel
updateInstanceError := "Error updating instance"
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
@ -389,7 +502,7 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques
}
func (r *instanceResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var data resourceModel
var data LocalInstanceModel
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)

View file

@ -0,0 +1,85 @@
package sqlserverflexbeta
import (
"context"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
)
type useStateForUnknownIfFlavorUnchangedModifier struct {
Req resource.SchemaRequest
}
// UseStateForUnknownIfFlavorUnchanged returns a plan modifier similar to UseStateForUnknown
// if the RAM and CPU values are not changed in the plan. Otherwise, the plan modifier does nothing.
func UseStateForUnknownIfFlavorUnchanged(req resource.SchemaRequest) planmodifier.String {
return useStateForUnknownIfFlavorUnchangedModifier{
Req: req,
}
}
func (m useStateForUnknownIfFlavorUnchangedModifier) Description(context.Context) string {
return "UseStateForUnknownIfFlavorUnchanged returns a plan modifier similar to UseStateForUnknown if the RAM and CPU values are not changed in the plan. Otherwise, the plan modifier does nothing."
}
func (m useStateForUnknownIfFlavorUnchangedModifier) MarkdownDescription(ctx context.Context) string {
return m.Description(ctx)
}
func (m useStateForUnknownIfFlavorUnchangedModifier) PlanModifyString(ctx context.Context, req planmodifier.StringRequest, resp *planmodifier.StringResponse) { // nolint:gocritic // function signature required by Terraform
// Do nothing if there is no state value.
if req.StateValue.IsNull() {
return
}
// Do nothing if there is a known planned value.
if !req.PlanValue.IsUnknown() {
return
}
// Do nothing if there is an unknown configuration value, otherwise interpolation gets messed up.
if req.ConfigValue.IsUnknown() {
return
}
// The above checks are taken from the UseStateForUnknown plan modifier implementation
// (https://github.com/hashicorp/terraform-plugin-framework/blob/main/resource/schema/stringplanmodifier/use_state_for_unknown.go#L38)
var stateModel LocalInstanceModel
diags := req.State.Get(ctx, &stateModel)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
var stateFlavor = &LocalFlavorModel{}
if !(stateModel.Flavor.IsNull() || stateModel.Flavor.IsUnknown()) {
diags = stateModel.Flavor.As(ctx, stateFlavor, basetypes.ObjectAsOptions{})
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
var planModel LocalInstanceModel
diags = req.Plan.Get(ctx, &planModel)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
var planFlavor = &LocalFlavorModel{}
if !(planModel.Flavor.IsNull() || planModel.Flavor.IsUnknown()) {
diags = planModel.Flavor.As(ctx, planFlavor, basetypes.ObjectAsOptions{})
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
if planFlavor.CPU == stateFlavor.CPU && planFlavor.RAM == stateFlavor.RAM {
resp.PlanValue = req.StateValue
}
}

View file

@ -17,7 +17,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
)
func VersionDataSourceSchema(ctx context.Context) schema.Schema {
func VersionsDataSourceSchema(ctx context.Context) schema.Schema {
return schema.Schema{
Attributes: map[string]schema.Attribute{
"project_id": schema.StringAttribute{
@ -26,7 +26,7 @@ func VersionDataSourceSchema(ctx context.Context) schema.Schema {
MarkdownDescription: "The STACKIT project ID.",
},
"region": schema.StringAttribute{
Required: true,
Optional: true,
Description: "The region which should be addressed",
MarkdownDescription: "The region which should be addressed",
Validators: []validator.String{
@ -74,7 +74,7 @@ func VersionDataSourceSchema(ctx context.Context) schema.Schema {
}
}
type VersionModel struct {
type VersionsModel struct {
ProjectId types.String `tfsdk:"project_id"`
Region types.String `tfsdk:"region"`
Versions types.List `tfsdk:"versions"`