diff --git a/pkg/postgresflexalpha/api_default.go b/pkg/postgresflexalpha/api_default.go index ca3987aa..8207f74a 100644 --- a/pkg/postgresflexalpha/api_default.go +++ b/pkg/postgresflexalpha/api_default.go @@ -193,7 +193,7 @@ type DefaultApi interface { @param region The region which should be addressed @return ApiGetFlavorsRequestRequest */ - GetFlavorsRequest(ctx context.Context, projectId string, region string) ApiGetFlavorsRequestRequest + GetFlavorsRequest(ctx context.Context, projectId string, region string, page, size *int64, sort *FlavorSort) ApiGetFlavorsRequestRequest /* GetFlavorsRequestExecute executes the request @@ -203,7 +203,7 @@ type DefaultApi interface { @return GetFlavorsResponse */ - GetFlavorsRequestExecute(ctx context.Context, projectId string, region string) (*GetFlavorsResponse, error) + GetFlavorsRequestExecute(ctx context.Context, projectId string, region string, page, size *int64, sort *FlavorSort) (*GetFlavorsResponse, error) /* GetInstanceRequest Get Specific Instance Get information about a specific available instance @@ -2520,7 +2520,7 @@ Get all available flavors for a project. @param region The region which should be addressed @return ApiGetFlavorsRequestRequest */ -func (a *APIClient) GetFlavorsRequest(ctx context.Context, projectId string, region string) ApiGetFlavorsRequestRequest { +func (a *APIClient) GetFlavorsRequest(ctx context.Context, projectId string, region string, page, size *int64, sort *FlavorSort) ApiGetFlavorsRequestRequest { return GetFlavorsRequestRequest{ apiService: a.defaultApi, ctx: ctx, @@ -2529,7 +2529,7 @@ func (a *APIClient) GetFlavorsRequest(ctx context.Context, projectId string, reg } } -func (a *APIClient) GetFlavorsRequestExecute(ctx context.Context, projectId string, region string) (*GetFlavorsResponse, error) { +func (a *APIClient) GetFlavorsRequestExecute(ctx context.Context, projectId string, region string, page, size *int64, sort *FlavorSort) (*GetFlavorsResponse, error) { r := GetFlavorsRequestRequest{ apiService: a.defaultApi, ctx: ctx, diff --git a/pkg/postgresflexalpha/model_get_instance_response.go b/pkg/postgresflexalpha/model_get_instance_response.go index c07e9d71..066ef5fe 100644 --- a/pkg/postgresflexalpha/model_get_instance_response.go +++ b/pkg/postgresflexalpha/model_get_instance_response.go @@ -221,6 +221,26 @@ func setGetInstanceResponseGetStorageAttributeType(arg *GetInstanceResponseGetSt *arg = &val } +/* + types and functions for encryption +*/ + +// isModel +type GetInstanceResponseGetEncryptionAttributeType = *InstanceEncryption +type GetInstanceResponseGetEncryptionArgType = InstanceEncryption +type GetInstanceResponseGetEncryptionRetType = InstanceEncryption + +func getGetInstanceResponseGetEncryptionAttributeTypeOk(arg GetInstanceResponseGetEncryptionAttributeType) (ret GetInstanceResponseGetEncryptionRetType, ok bool) { + if arg == nil { + return ret, false + } + return *arg, true +} + +func setGetInstanceResponseGetEncryptionAttributeType(arg *GetInstanceResponseGetEncryptionAttributeType, val GetInstanceResponseGetEncryptionRetType) { + *arg = &val +} + /* types and functions for version */ @@ -256,6 +276,7 @@ type GetInstanceResponse struct { // Whether the instance can be deleted or not. // REQUIRED IsDeletable GetInstanceResponsegetIsDeletableAttributeType `json:"isDeletable" required:"true"` + Encryption GetInstanceResponseGetEncryptionAttributeType `json:"encryption,omitempty"` // The name of the instance. // REQUIRED Name GetInstanceResponseGetNameAttributeType `json:"name" required:"true"` diff --git a/pkg/postgresflexalpha/wait/wait_test.go b/pkg/postgresflexalpha/wait/wait_test.go index fd31979d..fa6e71b3 100644 --- a/pkg/postgresflexalpha/wait/wait_test.go +++ b/pkg/postgresflexalpha/wait/wait_test.go @@ -4,7 +4,6 @@ package wait import ( "context" - "math" "testing" "time" @@ -144,16 +143,15 @@ func TestCreateInstanceWaitHandler(t *testing.T) { }, } for _, tt := range tests { - t.Run( - tt.desc, func(t *testing.T) { - instanceId := "foo-bar" + t.Run(tt.desc, func(t *testing.T) { + instanceId := "foo-bar" - apiClient := &apiClientInstanceMocked{ - instanceId: instanceId, - instanceState: tt.instanceState, - instanceGetFails: tt.instanceGetFails, - usersGetErrorStatus: tt.usersGetErrorStatus, - } + apiClient := &apiClientInstanceMocked{ + instanceId: instanceId, + instanceState: tt.instanceState, + instanceGetFails: tt.instanceGetFails, + usersGetErrorStatus: tt.usersGetErrorStatus, + } var wantRes *postgresflex.GetInstanceResponse if tt.wantResp { @@ -172,8 +170,8 @@ func TestCreateInstanceWaitHandler(t *testing.T) { if !cmp.Equal(gotRes, wantRes) { t.Fatalf("handler gotRes = %v, want %v", gotRes, wantRes) } - }, - ) + } + }) } } @@ -221,15 +219,14 @@ func TestUpdateInstanceWaitHandler(t *testing.T) { }, } for _, tt := range tests { - t.Run( - tt.desc, func(t *testing.T) { - instanceId := "foo-bar" + t.Run(tt.desc, func(t *testing.T) { + instanceId := "foo-bar" - apiClient := &apiClientInstanceMocked{ - instanceId: instanceId, - instanceState: tt.instanceState, - instanceGetFails: tt.instanceGetFails, - } + apiClient := &apiClientInstanceMocked{ + instanceId: instanceId, + instanceState: tt.instanceState, + instanceGetFails: tt.instanceGetFails, + } var wantRes *postgresflex.GetInstanceResponse if tt.wantResp { @@ -248,8 +245,8 @@ func TestUpdateInstanceWaitHandler(t *testing.T) { if !cmp.Equal(gotRes, wantRes) { t.Fatalf("handler gotRes = %v, want %v", gotRes, wantRes) } - }, - ) + } + }) } } @@ -378,22 +375,22 @@ func TestDeleteUserWaitHandler(t *testing.T) { } for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { - userId := "foo-bar" + userId := int64(1001) - apiClient := &apiClientUserMocked{ - getFails: tt.getFails, - userId: userId, - isUserDeleted: !tt.deleteFails, - } + apiClient := &apiClientUserMocked{ + getFails: tt.getFails, + userId: userId, + isUserDeleted: !tt.deleteFails, + } - handler := DeleteUserWaitHandler(context.Background(), apiClient, "", "", "", userId) + handler := DeleteUserWaitHandler(context.Background(), apiClient, "", "", "", userId) - _, err := handler.SetTimeout(10 * time.Millisecond).WaitWithContext(context.Background()) + _, err := handler.SetTimeout(10 * time.Millisecond).WaitWithContext(context.Background()) - if (err != nil) != tt.wantErr { - t.Fatalf("handler error = %v, wantErr %v", err, tt.wantErr) - } - }, + if (err != nil) != tt.wantErr { + t.Fatalf("handler error = %v, wantErr %v", err, tt.wantErr) + } + }, ) } } diff --git a/sample/postresql.tf b/sample/postresql.tf index c337e594..39e50e7d 100644 --- a/sample/postresql.tf +++ b/sample/postresql.tf @@ -1,7 +1,6 @@ resource "stackitprivatepreview_postgresflexalpha_instance" "ptlsdbsrv" { project_id = var.project_id name = "pgsql-example-instance" - acl = ["0.0.0.0/0"] backup_schedule = "0 0 * * *" flavor = { cpu = 2 @@ -29,7 +28,7 @@ resource "stackitprivatepreview_postgresflexalpha_instance" "ptlsdbsrv" { data "stackitprivatepreview_postgresflexalpha_instance" "datapsql" { project_id = var.project_id - instance_id = "e0c028e0-a201-4b75-8ee5-50a0ad17b0d7" + instance_id = "fdb6573e-2dea-4e1d-a638-9157cf90c3ba" region = "eu01" } diff --git a/stackit/internal/services/postgresflexalpha/flavor.go b/stackit/internal/services/postgresflexalpha/flavor.go new file mode 100644 index 00000000..bd9d2352 --- /dev/null +++ b/stackit/internal/services/postgresflexalpha/flavor.go @@ -0,0 +1,502 @@ +package postgresflex + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +var _ basetypes.ObjectTypable = FlavorType{} + +type FlavorType struct { + basetypes.ObjectType +} + +func (t FlavorType) Equal(o attr.Type) bool { + other, ok := o.(FlavorType) + + if !ok { + return false + } + + return t.ObjectType.Equal(other.ObjectType) +} + +func (t FlavorType) String() string { + return "FlavorType" +} + +func (t FlavorType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { + var diags diag.Diagnostics + + attributes := in.Attributes() + + cpuAttribute, ok := attributes["cpu"] + + if !ok { + diags.AddError( + "Attribute Missing", + `cpu is missing from object`) + + return nil, diags + } + + cpuVal, ok := cpuAttribute.(basetypes.Int64Value) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`cpu expected to be basetypes.Int64Value, was: %T`, cpuAttribute)) + } + + 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)) + } + + idAttribute, ok := attributes["id"] + + if !ok { + diags.AddError( + "Attribute Missing", + `id is missing from object`) + + return nil, diags + } + + idVal, ok := idAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`id expected to be basetypes.StringValue, was: %T`, idAttribute)) + } + + memoryAttribute, ok := attributes["memory"] + + if !ok { + diags.AddError( + "Attribute Missing", + `memory is missing from object`) + + return nil, diags + } + + ramVal, ok := memoryAttribute.(basetypes.Int64Value) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`memory expected to be basetypes.Int64Value, was: %T`, memoryAttribute)) + } + + if diags.HasError() { + return nil, diags + } + + return FlavorValue{ + Cpu: cpuVal, + Description: descriptionVal, + Id: idVal, + Ram: ramVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewFlavorValueNull() FlavorValue { + return FlavorValue{ + state: attr.ValueStateNull, + } +} + +func NewFlavorValueUnknown() FlavorValue { + return FlavorValue{ + state: attr.ValueStateUnknown, + } +} + +func NewFlavorValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (FlavorValue, 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 FlavorValue Attribute Value", + "While creating a FlavorValue value, a missing attribute value was detected. "+ + "A FlavorValue 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("FlavorValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()), + ) + + continue + } + + if !attributeType.Equal(attribute.Type(ctx)) { + diags.AddError( + "Invalid FlavorValue Attribute Type", + "While creating a FlavorValue value, an invalid attribute value was detected. "+ + "A FlavorValue 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("FlavorValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+ + fmt.Sprintf("FlavorValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)), + ) + } + } + + for name := range attributes { + _, ok := attributeTypes[name] + + if !ok { + diags.AddError( + "Extra FlavorValue Attribute Value", + "While creating a FlavorValue value, an extra attribute value was detected. "+ + "A FlavorValue 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 FlavorValue Attribute Name: %s", name), + ) + } + } + + if diags.HasError() { + return NewFlavorValueUnknown(), diags + } + + cpuAttribute, ok := attributes["cpu"] + + if !ok { + diags.AddError( + "Attribute Missing", + `cpu is missing from object`) + + return NewFlavorValueUnknown(), diags + } + + cpuVal, ok := cpuAttribute.(basetypes.Int64Value) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`cpu expected to be basetypes.Int64Value, was: %T`, cpuAttribute)) + } + + descriptionAttribute, ok := attributes["description"] + + if !ok { + diags.AddError( + "Attribute Missing", + `description is missing from object`) + + return NewFlavorValueUnknown(), diags + } + + descriptionVal, ok := descriptionAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`description expected to be basetypes.StringValue, was: %T`, descriptionAttribute)) + } + + idAttribute, ok := attributes["id"] + + if !ok { + diags.AddError( + "Attribute Missing", + `id is missing from object`) + + return NewFlavorValueUnknown(), diags + } + + idVal, ok := idAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`id expected to be basetypes.StringValue, was: %T`, idAttribute)) + } + + memoryAttribute, ok := attributes["memory"] + + if !ok { + diags.AddError( + "Attribute Missing", + `memory is missing from object`) + + return NewFlavorValueUnknown(), diags + } + + memoryVal, ok := memoryAttribute.(basetypes.Int64Value) + + if !ok { + diags.AddError( + "Attribute Wrong Type", + fmt.Sprintf(`memory expected to be basetypes.Int64Value, was: %T`, memoryAttribute)) + } + + if diags.HasError() { + return NewFlavorValueUnknown(), diags + } + + return FlavorValue{ + Cpu: cpuVal, + Description: descriptionVal, + Id: idVal, + Ram: memoryVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewFlavorValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) FlavorValue { + object, diags := NewFlavorValue(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("NewFlavorValueMust received error(s): " + strings.Join(diagsStrings, "\n")) + } + + return object +} + +func (t FlavorType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + if in.Type() == nil { + return NewFlavorValueNull(), 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 NewFlavorValueUnknown(), nil + } + + if in.IsNull() { + return NewFlavorValueNull(), 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 NewFlavorValueMust(FlavorValue{}.AttributeTypes(ctx), attributes), nil +} + +func (t FlavorType) ValueType(ctx context.Context) attr.Value { + return FlavorValue{} +} + +var _ basetypes.ObjectValuable = FlavorValue{} + +type FlavorValue struct { + Cpu basetypes.Int64Value `tfsdk:"cpu"` + Description basetypes.StringValue `tfsdk:"description"` + Id basetypes.StringValue `tfsdk:"id"` + Ram basetypes.Int64Value `tfsdk:"ram"` + state attr.ValueState +} + +func (v FlavorValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + attrTypes := make(map[string]tftypes.Type, 4) + + var val tftypes.Value + var err error + + attrTypes["cpu"] = basetypes.Int64Type{}.TerraformType(ctx) + attrTypes["description"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["id"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["memory"] = basetypes.Int64Type{}.TerraformType(ctx) + + objectType := tftypes.Object{AttributeTypes: attrTypes} + + switch v.state { + case attr.ValueStateKnown: + vals := make(map[string]tftypes.Value, 4) + + val, err = v.Cpu.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["cpu"] = val + + val, err = v.Description.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["description"] = val + + val, err = v.Id.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["id"] = val + + val, err = v.Ram.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["memory"] = 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 FlavorValue) IsNull() bool { + return v.state == attr.ValueStateNull +} + +func (v FlavorValue) IsUnknown() bool { + return v.state == attr.ValueStateUnknown +} + +func (v FlavorValue) String() string { + return "FlavorValue" +} + +func (v FlavorValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { + var diags diag.Diagnostics + + attributeTypes := map[string]attr.Type{ + "cpu": basetypes.Int64Type{}, + "description": basetypes.StringType{}, + "id": basetypes.StringType{}, + "memory": basetypes.Int64Type{}, + } + + 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{ + "cpu": v.Cpu, + "description": v.Description, + "id": v.Id, + "memory": v.Ram, + }) + + return objVal, diags +} + +func (v FlavorValue) Equal(o attr.Value) bool { + other, ok := o.(FlavorValue) + + if !ok { + return false + } + + if v.state != other.state { + return false + } + + if v.state != attr.ValueStateKnown { + return true + } + + if !v.Cpu.Equal(other.Cpu) { + return false + } + + if !v.Description.Equal(other.Description) { + return false + } + + if !v.Id.Equal(other.Id) { + return false + } + + if !v.Ram.Equal(other.Ram) { + return false + } + + return true +} + +func (v FlavorValue) Type(ctx context.Context) attr.Type { + return FlavorType{ + basetypes.ObjectType{ + AttrTypes: v.AttributeTypes(ctx), + }, + } +} + +func (v FlavorValue) AttributeTypes(ctx context.Context) map[string]attr.Type { + return map[string]attr.Type{ + "cpu": basetypes.Int64Type{}, + "description": basetypes.StringType{}, + "id": basetypes.StringType{}, + "memory": basetypes.Int64Type{}, + } +} diff --git a/stackit/internal/services/postgresflexalpha/instance/datasource.go b/stackit/internal/services/postgresflexalpha/instance/datasource.go index aa54f7eb..03af6612 100644 --- a/stackit/internal/services/postgresflexalpha/instance/datasource.go +++ b/stackit/internal/services/postgresflexalpha/instance/datasource.go @@ -60,7 +60,7 @@ func (r *instanceDataSource) Configure(ctx context.Context, req datasource.Confi } // Schema defines the schema for the data source. -func (r *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { +func (r *instanceDataSource) Schema(ctx context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { descriptions := map[string]string{ "main": "Postgres Flex instance data source schema. Must have a `region` specified in the provider configuration.", "id": "Terraform's internal data source. ID. It is structured as \"`project_id`,`region`,`instance_id`\".", @@ -98,11 +98,6 @@ func (r *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaReques Description: descriptions["name"], Computed: true, }, - "acl": schema.ListAttribute{ - Description: descriptions["acl"], - ElementType: types.StringType, - Computed: true, - }, "backup_schedule": schema.StringAttribute{ Computed: true, }, @@ -121,7 +116,15 @@ func (r *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaReques "ram": schema.Int64Attribute{ Computed: true, }, + "node_type": schema.StringAttribute{ + Computed: true, + }, }, + //CustomType: postgresflex.FlavorType{ + // ObjectType: types.ObjectType{ + // AttrTypes: postgresflex.FlavorValue{}.AttributeTypes(ctx), + // }, + //}, }, "replicas": schema.Int64Attribute{ Computed: true, @@ -146,7 +149,7 @@ func (r *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaReques Description: descriptions["region"], }, "encryption": schema.SingleNestedAttribute{ - Required: true, + Computed: true, Attributes: map[string]schema.Attribute{ "key_id": schema.StringAttribute{ Description: descriptions["key_id"], @@ -236,13 +239,30 @@ func (r *instanceDataSource) Read(ctx context.Context, req datasource.ReadReques } var flavor = &flavorModel{} + if instanceResp != nil && instanceResp.FlavorId != nil { + flavor.Id = types.StringValue(*instanceResp.FlavorId) + } + if !(model.Flavor.IsNull() || model.Flavor.IsUnknown()) { diags = model.Flavor.As(ctx, flavor, basetypes.ObjectAsOptions{}) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return } + + err := getFlavorModelById(ctx, r.client, &model, flavor) + if err != nil { + resp.Diagnostics.AddError(err.Error(), err.Error()) + return + } + + diags = model.Flavor.As(ctx, flavor, basetypes.ObjectAsOptions{}) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } } + var storage = &storageModel{} if !(model.Storage.IsNull() || model.Storage.IsUnknown()) { diags = model.Storage.As(ctx, storage, basetypes.ObjectAsOptions{}) @@ -261,7 +281,16 @@ func (r *instanceDataSource) Read(ctx context.Context, req datasource.ReadReques } } - err = mapFields(ctx, instanceResp, &model, flavor, storage, network, region) + var encryption = &encryptionModel{} + if !(model.Encryption.IsNull() || model.Encryption.IsUnknown()) { + diags = model.Encryption.As(ctx, encryption, basetypes.ObjectAsOptions{}) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + } + + err = mapFields(ctx, instanceResp, &model, flavor, storage, encryption, network, region) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Processing API payload: %v", err)) return diff --git a/stackit/internal/services/postgresflexalpha/instance/functions.go b/stackit/internal/services/postgresflexalpha/instance/functions.go index cb8f5f75..3460f520 100644 --- a/stackit/internal/services/postgresflexalpha/instance/functions.go +++ b/stackit/internal/services/postgresflexalpha/instance/functions.go @@ -3,6 +3,7 @@ package postgresflexalpha import ( "context" "fmt" + "strings" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/types" @@ -13,10 +14,19 @@ import ( ) type postgresflexClient interface { - GetFlavorsRequestExecute(ctx context.Context, projectId string, region string) (*postgresflex.GetFlavorsResponse, error) + GetFlavorsRequestExecute(ctx context.Context, projectId string, region string, page, size *int64, sort *postgresflex.FlavorSort) (*postgresflex.GetFlavorsResponse, error) } -func mapFields(ctx context.Context, resp *postgresflex.GetInstanceResponse, model *Model, flavor *flavorModel, storage *storageModel, network *networkModel, region string) error { +func mapFields( + ctx context.Context, + resp *postgresflex.GetInstanceResponse, + model *Model, + flavor *flavorModel, + storage *storageModel, + encryption *encryptionModel, + network *networkModel, + region string, +) error { if resp == nil { return fmt.Errorf("response input is nil") } @@ -34,28 +44,26 @@ func mapFields(ctx context.Context, resp *postgresflex.GetInstanceResponse, mode return fmt.Errorf("instance id not present") } - /* - - var aclList basetypes.ListValue - var diags diag.Diagnostics - if instance.Acl == nil { - aclList = types.ListNull(types.StringType) - } else { - respACL := *instance.Acl - modelACL, err := utils.ListValuetoStringSlice(model.ACL) - if err != nil { - return err - } - - reconciledACL := utils.ReconcileStringSlices(modelACL, respACL) - - aclList, diags = types.ListValueFrom(ctx, types.StringType, reconciledACL) - if diags.HasError() { - return fmt.Errorf("mapping ACL: %w", core.DiagsToError(diags)) - } + var encryptionValues map[string]attr.Value + if instance.Encryption == nil { + encryptionValues = map[string]attr.Value{ + "keyring_id": encryption.KeyRingId, + "key_id": encryption.KeyId, + "key_version": encryption.KeyVersion, + "service_account": encryption.ServiceAccount, } - - */ + } else { + encryptionValues = map[string]attr.Value{ + "keyring_id": types.StringValue(*instance.Encryption.KekKeyRingId), + "key_id": types.StringValue(*instance.Encryption.KekKeyId), + "key_version": types.StringValue(*instance.Encryption.KekKeyVersion), + "service_account": types.StringValue(*instance.Encryption.ServiceAccount), + } + } + encryptionObject, diags := types.ObjectValue(encryptionTypes, encryptionValues) + if diags.HasError() { + return fmt.Errorf("creating encryption: %w", core.DiagsToError(diags)) + } var networkValues map[string]attr.Value if instance.Network == nil { @@ -93,21 +101,25 @@ func mapFields(ctx context.Context, resp *postgresflex.GetInstanceResponse, mode var flavorValues map[string]attr.Value if instance.FlavorId == nil { + return fmt.Errorf("instance has no flavor id") + } + if !flavor.Id.IsUnknown() && !flavor.Id.IsNull() { + if *instance.FlavorId != flavor.Id.ValueString() { + return fmt.Errorf("instance has different flavor id %s - %s", *instance.FlavorId, flavor.Id.ValueString()) + } + } + if model.Flavor.IsNull() || model.Flavor.IsUnknown() { flavorValues = map[string]attr.Value{ "id": flavor.Id, "description": flavor.Description, "cpu": flavor.CPU, "ram": flavor.RAM, + "node_type": flavor.NodeType, } } else { - // TODO @mhenselin - // flavorValues = map[string]attr.Value{ - // "id": types.StringValue(*instance.FlavorId), - // "description": types.StringValue(*instance.FlavorId.Description), - // "cpu": types.Int64PointerValue(instance.FlavorId.Cpu), - // "ram": types.Int64PointerValue(instance.FlavorId.Memory), - // } + flavorValues = model.Flavor.Attributes() } + flavorObject, diags := types.ObjectValue(flavorTypes, flavorValues) if diags.HasError() { return fmt.Errorf("creating flavor: %w", core.DiagsToError(diags)) @@ -133,7 +145,6 @@ func mapFields(ctx context.Context, resp *postgresflex.GetInstanceResponse, mode model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, instanceId) model.InstanceId = types.StringValue(instanceId) model.Name = types.StringPointerValue(instance.Name) - // model.ACL = aclList model.Network = networkObject model.BackupSchedule = types.StringPointerValue(instance.BackupSchedule) model.Flavor = flavorObject @@ -142,18 +153,15 @@ func mapFields(ctx context.Context, resp *postgresflex.GetInstanceResponse, mode model.Storage = storageObject model.Version = types.StringPointerValue(instance.Version) model.Region = types.StringValue(region) - //model.Encryption = types.ObjectValue() - //model.Network = networkModel + model.Encryption = encryptionObject + model.Network = networkObject return nil } -func toCreatePayload(model *Model, acl []string, 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") } - if acl == nil { - return nil, fmt.Errorf("nil acl") - } if flavor == nil { return nil, fmt.Errorf("nil flavor") } @@ -162,46 +170,57 @@ func toCreatePayload(model *Model, acl []string, flavor *flavorModel, storage *s } replVal := int32(model.Replicas.ValueInt64()) + + storagePayload := &postgresflex.CreateInstanceRequestPayloadGetStorageArgType{ + PerformanceClass: conversion.StringValueToPointer(storage.Class), + Size: conversion.Int64ValueToPointer(storage.Size), + } + + encryptionPayload := &postgresflex.CreateInstanceRequestPayloadGetEncryptionArgType{} + if enc != nil { + encryptionPayload.KekKeyId = conversion.StringValueToPointer(enc.KeyId) + encryptionPayload.KekKeyVersion = conversion.StringValueToPointer(enc.KeyVersion) + encryptionPayload.KekKeyRingId = conversion.StringValueToPointer(enc.KeyRingId) + encryptionPayload.ServiceAccount = conversion.StringValueToPointer(enc.ServiceAccount) + } + + var aclElements []string + if net != nil && !(net.ACL.IsNull() || net.ACL.IsUnknown()) { + aclElements = make([]string, 0, len(net.ACL.Elements())) + diags := net.ACL.ElementsAs(context.TODO(), &aclElements, false) + if diags.HasError() { + return nil, fmt.Errorf("creating network: %w", core.DiagsToError(diags)) + } + } + + networkPayload := &postgresflex.CreateInstanceRequestPayloadGetNetworkArgType{} + if net != nil { + networkPayload = &postgresflex.CreateInstanceRequestPayloadGetNetworkArgType{ + AccessScope: postgresflex.InstanceNetworkGetAccessScopeAttributeType(conversion.StringValueToPointer(net.AccessScope)), + Acl: &aclElements, + } + } + return &postgresflex.CreateInstanceRequestPayload{ - // TODO - verify working - //Acl: &[]string{ - // strings.Join(acl, ","), - //}, BackupSchedule: conversion.StringValueToPointer(model.BackupSchedule), FlavorId: conversion.StringValueToPointer(flavor.Id), Name: conversion.StringValueToPointer(model.Name), // TODO - verify working - Replicas: postgresflex.CreateInstanceRequestPayloadGetReplicasAttributeType(&replVal), - // TODO - verify working - Storage: postgresflex.CreateInstanceRequestPayloadGetStorageAttributeType(&postgresflex.Storage{ - PerformanceClass: conversion.StringValueToPointer(storage.Class), - Size: conversion.Int64ValueToPointer(storage.Size), - }), - Version: conversion.StringValueToPointer(model.Version), - // TODO - verify working - Encryption: postgresflex.CreateInstanceRequestPayloadGetEncryptionAttributeType( - &postgresflex.InstanceEncryption{ - KekKeyId: conversion.StringValueToPointer(enc.KeyId), // model.Encryption.Attributes(), - KekKeyRingId: conversion.StringValueToPointer(enc.KeyRingId), - KekKeyVersion: conversion.StringValueToPointer(enc.KeyVersion), - ServiceAccount: conversion.StringValueToPointer(enc.ServiceAccount), - }, - ), - Network: &postgresflex.InstanceNetwork{ - AccessScope: postgresflex.InstanceNetworkGetAccessScopeAttributeType( - conversion.StringValueToPointer(net.AccessScope), - ), - }, + Replicas: postgresflex.CreateInstanceRequestPayloadGetReplicasAttributeType(&replVal), + Storage: storagePayload, + Version: conversion.StringValueToPointer(model.Version), + Encryption: encryptionPayload, + Network: networkPayload, }, nil } -func toUpdatePayload(model *Model, acl []string, flavor *flavorModel, storage *storageModel) (*postgresflex.UpdateInstancePartiallyRequestPayload, error) { +func toUpdatePayload(model *Model, flavor *flavorModel, storage *storageModel, network *networkModel) (*postgresflex.UpdateInstancePartiallyRequestPayload, error) { if model == nil { return nil, fmt.Errorf("nil model") } - if acl == nil { - return nil, fmt.Errorf("nil acl") - } + // if acl == nil { + // return nil, fmt.Errorf("nil acl") + // } if flavor == nil { return nil, fmt.Errorf("nil flavor") } @@ -224,7 +243,7 @@ func toUpdatePayload(model *Model, acl []string, flavor *flavorModel, storage *s }, nil } -func loadFlavorId(ctx context.Context, client postgresflexClient, model *Model, flavor *flavorModel) error { +func loadFlavorId(ctx context.Context, client postgresflexClient, model *Model, flavor *flavorModel, storage *storageModel) error { if model == nil { return fmt.Errorf("nil model") } @@ -239,28 +258,61 @@ 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") + } + 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() - res, err := client.GetFlavorsRequestExecute(ctx, projectId, region) + + flavorList, err := getAllFlavors(ctx, client, projectId, region) if err != nil { - return fmt.Errorf("listing postgresflex flavors: %w", err) + return err } avl := "" - if res.Flavors == nil { - return fmt.Errorf("finding flavors for project %s", projectId) - } - for _, f := range *res.Flavors { + foundFlavorCount := 0 + 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 *postgresflex.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) - break + foundFlavorCount++ } - avl = fmt.Sprintf("%s\n- %d CPU, %d GB RAM", avl, *f.Cpu, *f.Memory) + 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("multiple flavors found: %d flavors", foundFlavorCount) } if flavor.Id.ValueString() == "" { return fmt.Errorf("couldn't find flavor, available specs are:%s", avl) @@ -268,3 +320,75 @@ func loadFlavorId(ctx context.Context, client postgresflexClient, model *Model, return nil } + +func getAllFlavors(ctx context.Context, client postgresflexClient, projectId, region string) ([]postgresflex.ListFlavors, error) { + if projectId == "" || region == "" { + return nil, fmt.Errorf("listing postgresflex flavors: projectId and region are required") + } + var flavorList []postgresflex.ListFlavors + + page := int64(1) + size := int64(10) + for { + sort := postgresflex.FLAVORSORT_INDEX_ASC + res, err := client.GetFlavorsRequestExecute(ctx, projectId, region, &page, &size, &sort) + if err != nil { + return nil, fmt.Errorf("listing postgresflex flavors: %w", err) + } + if res.Flavors == nil { + return nil, fmt.Errorf("finding flavors for project %s", projectId) + } + pagination := res.GetPagination() + flavorList = append(flavorList, *res.Flavors...) + + if *pagination.TotalRows == int64(len(flavorList)) { + break + } + page++ + } + return flavorList, nil +} + +func getFlavorModelById(ctx context.Context, client postgresflexClient, 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 +} diff --git a/stackit/internal/services/postgresflexalpha/instance/resource.go b/stackit/internal/services/postgresflexalpha/instance/resource.go index 38834766..c2cf8bd1 100644 --- a/stackit/internal/services/postgresflexalpha/instance/resource.go +++ b/stackit/internal/services/postgresflexalpha/instance/resource.go @@ -8,14 +8,14 @@ import ( "strings" "time" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" - //postgresflex "github.com/mhenselin/terraform-provider-stackitprivatepreview/pkg/postgresflexalpha" 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" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" "github.com/hashicorp/terraform-plugin-log/tflog" @@ -46,7 +46,6 @@ type Model struct { InstanceId types.String `tfsdk:"instance_id"` ProjectId types.String `tfsdk:"project_id"` Name types.String `tfsdk:"name"` - ACL types.List `tfsdk:"acl"` BackupSchedule types.String `tfsdk:"backup_schedule"` Flavor types.Object `tfsdk:"flavor"` Replicas types.Int64 `tfsdk:"replicas"` @@ -91,6 +90,7 @@ type flavorModel struct { 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 @@ -99,6 +99,7 @@ var flavorTypes = map[string]attr.Type{ "description": basetypes.StringType{}, "cpu": basetypes.Int64Type{}, "ram": basetypes.Int64Type{}, + "node_type": basetypes.StringType{}, } // Struct corresponding to Model.Storage @@ -178,17 +179,18 @@ 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{ - "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.", - // TODO @mhenselin - do the rest + "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.", } + // TODO @mhenselin - do the rest resp.Schema = schema.Schema{ Description: descriptions["main"], @@ -234,11 +236,6 @@ func (r *instanceResource) Schema(_ context.Context, req resource.SchemaRequest, ), }, }, - "acl": schema.ListAttribute{ - Description: descriptions["acl"], - ElementType: types.StringType, - Required: true, - }, "backup_schedule": schema.StringAttribute{ Required: true, }, @@ -247,8 +244,10 @@ func (r *instanceResource) Schema(_ context.Context, req resource.SchemaRequest, Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ Computed: true, + Optional: true, PlanModifiers: []planmodifier.String{ UseStateForUnknownIfFlavorUnchanged(req), + stringplanmodifier.RequiresReplace(), }, }, "description": schema.StringAttribute{ @@ -350,14 +349,19 @@ func (r *instanceResource) Schema(_ context.Context, req resource.SchemaRequest, Required: true, Attributes: map[string]schema.Attribute{ "access_scope": schema.StringAttribute{ + Default: stringdefault.StaticString( + "PUBLIC", + ), Description: descriptions["access_scope"], - Required: true, + Computed: true, + Optional: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), stringplanmodifier.UseStateForUnknown(), }, Validators: []validator.String{ validate.NoSeparator(), + stringvalidator.OneOf("SNA", "PUBLIC"), }, }, "acl": schema.ListAttribute{ @@ -371,6 +375,7 @@ func (r *instanceResource) Schema(_ context.Context, req resource.SchemaRequest, "instance_address": schema.StringAttribute{ Description: descriptions["instance_address"], Computed: true, + Optional: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, @@ -378,6 +383,7 @@ func (r *instanceResource) Schema(_ context.Context, req resource.SchemaRequest, "router_address": schema.StringAttribute{ Description: descriptions["router_address"], Computed: true, + Optional: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, @@ -409,27 +415,6 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "region", region) - var acl []string - if !(model.ACL.IsNull() || model.ACL.IsUnknown()) { - diags = model.ACL.ElementsAs(ctx, &acl, false) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - } - var flavor = &flavorModel{} - if !(model.Flavor.IsNull() || model.Flavor.IsUnknown()) { - diags = model.Flavor.As(ctx, flavor, basetypes.ObjectAsOptions{}) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - err := loadFlavorId(ctx, r.client, &model, flavor) - if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Loading flavor ID: %v", err)) - return - } - } var storage = &storageModel{} if !(model.Storage.IsNull() || model.Storage.IsUnknown()) { diags = model.Storage.As(ctx, storage, basetypes.ObjectAsOptions{}) @@ -439,6 +424,42 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques } } + var flavor = &flavorModel{} + if !(model.Flavor.IsNull() || model.Flavor.IsUnknown()) { + diags = model.Flavor.As(ctx, flavor, basetypes.ObjectAsOptions{}) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + err := loadFlavorId(ctx, r.client, &model, flavor, storage) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Loading flavor ID: %v", err)) + return + } + } + + if flavor.Id.IsNull() || flavor.Id.IsUnknown() { + err := loadFlavorId(ctx, r.client, &model, flavor, storage) + if err != nil { + resp.Diagnostics.AddError(err.Error(), err.Error()) + return + } + flavorValues := map[string]attr.Value{ + "id": flavor.Id, + "description": flavor.Description, + "cpu": flavor.CPU, + "ram": flavor.RAM, + "node_type": flavor.NodeType, + } + var flavorObject basetypes.ObjectValue + flavorObject, diags = types.ObjectValue(flavorTypes, flavorValues) + resp.Diagnostics.Append(diags...) + if diags.HasError() { + return + } + model.Flavor = flavorObject + } + var encryption = &encryptionModel{} if !(model.Encryption.IsNull() || model.Encryption.IsUnknown()) { diags = model.Encryption.As(ctx, encryption, basetypes.ObjectAsOptions{}) @@ -457,8 +478,17 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques } } + var acl []string + if !(network.ACL.IsNull() || network.ACL.IsUnknown()) { + diags = network.ACL.ElementsAs(ctx, &acl, false) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + } + // Generate API request body from model - payload, err := toCreatePayload(&model, acl, flavor, storage, encryption, network) + payload, err := toCreatePayload(&model, flavor, storage, encryption, network) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Creating API payload: %v", err)) return @@ -487,7 +517,7 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques } // Map response body to schema - err = mapFields(ctx, waitResp, &model, flavor, storage, network, region) + err = mapFields(ctx, waitResp, &model, flavor, storage, encryption, network, region) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Processing API payload: %v", err)) return @@ -545,6 +575,15 @@ func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r } } + var encryption = &encryptionModel{} + if !(model.Encryption.IsNull() || model.Encryption.IsUnknown()) { + diags = model.Encryption.As(ctx, encryption, basetypes.ObjectAsOptions{}) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + } + instanceResp, err := r.client.GetInstanceRequest(ctx, projectId, region, instanceId).Execute() if err != nil { oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped @@ -564,7 +603,7 @@ func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r } // Map response body to schema - err = mapFields(ctx, instanceResp, &model, flavor, storage, network, region) + err = mapFields(ctx, instanceResp, &model, flavor, storage, encryption, network, region) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Processing API payload: %v", err)) return @@ -597,27 +636,15 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "region", region) - var acl []string - if !(model.ACL.IsNull() || model.ACL.IsUnknown()) { - diags = model.ACL.ElementsAs(ctx, &acl, false) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - } - var flavor = &flavorModel{} - if !(model.Flavor.IsNull() || model.Flavor.IsUnknown()) { - diags = model.Flavor.As(ctx, flavor, basetypes.ObjectAsOptions{}) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - err := loadFlavorId(ctx, r.client, &model, flavor) - if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Loading flavor ID: %v", err)) - return - } - } + //var acl []string + //if !(model.ACL.IsNull() || model.ACL.IsUnknown()) { + // diags = model.ACL.ElementsAs(ctx, &acl, false) + // resp.Diagnostics.Append(diags...) + // if resp.Diagnostics.HasError() { + // return + // } + //} + var storage = &storageModel{} if !(model.Storage.IsNull() || model.Storage.IsUnknown()) { diags = model.Storage.As(ctx, storage, basetypes.ObjectAsOptions{}) @@ -627,8 +654,40 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques } } + var flavor = &flavorModel{} + if !(model.Flavor.IsNull() || model.Flavor.IsUnknown()) { + diags = model.Flavor.As(ctx, flavor, basetypes.ObjectAsOptions{}) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + err := loadFlavorId(ctx, r.client, &model, flavor, storage) + if err != nil { + core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Loading flavor ID: %v", err)) + return + } + } + + var network = &networkModel{} + if !(model.Network.IsNull() || model.Network.IsUnknown()) { + diags = model.Network.As(ctx, network, basetypes.ObjectAsOptions{}) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + } + + var encryption = &encryptionModel{} + if !(model.Encryption.IsNull() || model.Encryption.IsUnknown()) { + diags = model.Encryption.As(ctx, encryption, basetypes.ObjectAsOptions{}) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + } + // Generate API request body from model - payload, err := toUpdatePayload(&model, acl, flavor, storage) + payload, err := toUpdatePayload(&model, flavor, storage, network) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Creating API payload: %v", err)) return @@ -648,17 +707,8 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques return } - var network = &networkModel{} - if !(model.Network.IsNull() || model.Network.IsUnknown()) { - diags = model.Network.As(ctx, network, basetypes.ObjectAsOptions{}) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - } - // Map response body to schema - err = mapFields(ctx, waitResp, &model, flavor, storage, network, region) + err = mapFields(ctx, waitResp, &model, flavor, storage, encryption, network, region) if err != nil { core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Processing API payload: %v", err)) return diff --git a/stackit/internal/services/postgresflexalpha/instance/resource_test.go b/stackit/internal/services/postgresflexalpha/instance/resource_test.go index 7235a07f..037fec77 100644 --- a/stackit/internal/services/postgresflexalpha/instance/resource_test.go +++ b/stackit/internal/services/postgresflexalpha/instance/resource_test.go @@ -87,7 +87,7 @@ func TestMapFields(t *testing.T) { // Cpu: utils.Ptr(int64(12)), // Description: utils.Ptr("description"), // Id: utils.Ptr("flavor_id"), - // Memory: utils.Ptr(int64(34)), + // Ram: utils.Ptr(int64(34)), //}, Id: utils.Ptr("iid"), Name: utils.Ptr("name"), @@ -633,7 +633,7 @@ func TestToCreatePayload(t *testing.T) { // Id: utils.Ptr("fid-1"), // Cpu: utils.Ptr(int64(2)), // Description: utils.Ptr("description"), -// Memory: utils.Ptr(int64(8)), +// Ram: utils.Ptr(int64(8)), // }, // }, // }, @@ -658,13 +658,13 @@ func TestToCreatePayload(t *testing.T) { // Id: utils.Ptr("fid-1"), // Cpu: utils.Ptr(int64(2)), // Description: utils.Ptr("description"), -// Memory: utils.Ptr(int64(8)), +// Ram: utils.Ptr(int64(8)), // }, // { // Id: utils.Ptr("fid-2"), // Cpu: utils.Ptr(int64(1)), // Description: utils.Ptr("description"), -// Memory: utils.Ptr(int64(4)), +// Ram: utils.Ptr(int64(4)), // }, // }, // }, @@ -689,13 +689,13 @@ func TestToCreatePayload(t *testing.T) { // Id: utils.Ptr("fid-1"), // Cpu: utils.Ptr(int64(1)), // Description: utils.Ptr("description"), -// Memory: utils.Ptr(int64(8)), +// Ram: utils.Ptr(int64(8)), // }, // { // Id: utils.Ptr("fid-2"), // Cpu: utils.Ptr(int64(1)), // Description: utils.Ptr("description"), -// Memory: utils.Ptr(int64(4)), +// Ram: utils.Ptr(int64(4)), // }, // }, // }, diff --git a/stackit/internal/services/sqlserverflexalpha/instance/functions.go b/stackit/internal/services/sqlserverflexalpha/instance/functions.go index dc228a41..4ea1e222 100644 --- a/stackit/internal/services/sqlserverflexalpha/instance/functions.go +++ b/stackit/internal/services/sqlserverflexalpha/instance/functions.go @@ -199,17 +199,13 @@ func toCreatePayload(model *Model, storage *storageModel, encryption *encryption encryptionPayload.ServiceAccount = conversion.StringValueToPointer(encryption.ServiceAccount) } - networkPayload := &sqlserverflex.CreateInstanceRequestPayloadGetNetworkArgType{} - if network != nil { - networkPayload.AccessScope = sqlserverflex.CreateInstanceRequestPayloadNetworkGetAccessScopeAttributeType(conversion.StringValueToPointer(network.AccessScope)) - } - flavorId := "" if !(model.Flavor.IsNull() || model.Flavor.IsUnknown()) { modelValues := model.Flavor.Attributes() if _, ok := modelValues["id"]; !ok { return nil, fmt.Errorf("flavor has not yet been created") } + // TODO - how to get rid of that trim? flavorId = strings.Trim(modelValues["id"].String(), "\"") } @@ -222,7 +218,15 @@ func toCreatePayload(model *Model, storage *storageModel, encryption *encryption } } + networkPayload := &sqlserverflex.CreateInstanceRequestPayloadGetNetworkArgType{} + if network != nil { + networkPayload.AccessScope = sqlserverflex.CreateInstanceRequestPayloadNetworkGetAccessScopeAttributeType(conversion.StringValueToPointer(network.AccessScope)) + // TODO - implement as soon as exists + // networkPayload.ACL + } + return &sqlserverflex.CreateInstanceRequestPayload{ + // TODO - remove as soon as exists in network Acl: &aclElements, BackupSchedule: conversion.StringValueToPointer(model.BackupSchedule), FlavorId: &flavorId, diff --git a/stackit/internal/services/sqlserverflexalpha/instance/resource_test.go b/stackit/internal/services/sqlserverflexalpha/instance/resource_test.go index 70886b23..5a9ac2e8 100644 --- a/stackit/internal/services/sqlserverflexalpha/instance/resource_test.go +++ b/stackit/internal/services/sqlserverflexalpha/instance/resource_test.go @@ -731,7 +731,7 @@ func TestMapFields(t *testing.T) { // Id: utils.Ptr("fid-1"), // Cpu: utils.Ptr(int64(2)), // Description: utils.Ptr("description"), -// Memory: utils.Ptr(int64(8)), +// Ram: utils.Ptr(int64(8)), // }, // }, // }, @@ -756,13 +756,13 @@ func TestMapFields(t *testing.T) { // Id: utils.Ptr("fid-1"), // Cpu: utils.Ptr(int64(2)), // Description: utils.Ptr("description"), -// Memory: utils.Ptr(int64(8)), +// Ram: utils.Ptr(int64(8)), // }, // { // Id: utils.Ptr("fid-2"), // Cpu: utils.Ptr(int64(1)), // Description: utils.Ptr("description"), -// Memory: utils.Ptr(int64(4)), +// Ram: utils.Ptr(int64(4)), // }, // }, // }, @@ -787,13 +787,13 @@ func TestMapFields(t *testing.T) { // Id: utils.Ptr("fid-1"), // Cpu: utils.Ptr(int64(1)), // Description: utils.Ptr("description"), -// Memory: utils.Ptr(int64(8)), +// Ram: utils.Ptr(int64(8)), // }, // { // Id: utils.Ptr("fid-2"), // Cpu: utils.Ptr(int64(1)), // Description: utils.Ptr("description"), -// Memory: utils.Ptr(int64(4)), +// Ram: utils.Ptr(int64(4)), // }, // }, // },