chore: work save

This commit is contained in:
Marcel_Henselin 2026-01-19 17:48:23 +01:00
parent 2abd4e2c72
commit c329d58970
8 changed files with 292 additions and 1507 deletions

View file

@ -3,12 +3,12 @@
page_title: "stackitprivatepreview_postgresflexalpha_instance Data Source - stackitprivatepreview" page_title: "stackitprivatepreview_postgresflexalpha_instance Data Source - stackitprivatepreview"
subcategory: "" subcategory: ""
description: |- description: |-
Postgres Flex instance data source schema. Must have a region specified in the provider configuration.
--- ---
# stackitprivatepreview_postgresflexalpha_instance (Data Source) # stackitprivatepreview_postgresflexalpha_instance (Data Source)
Postgres Flex instance data source schema. Must have a `region` specified in the provider configuration.
## Example Usage ## Example Usage
@ -24,44 +24,31 @@ data "stackitprivatepreview_postgresflexalpha_instance" "example" {
### Required ### Required
- `instance_id` (String) ID of the PostgresFlex instance. - `instance_id` (String) The ID of the instance.
- `project_id` (String) STACKIT project ID to which the instance is associated. - `project_id` (String) The STACKIT project ID.
- `region` (String) The region which should be addressed
### Optional
- `region` (String) The resource region. If not defined, the provider region is used.
### Read-Only ### Read-Only
- `backup_schedule` (String) - `backup_schedule` (String) The schedule for on what time and how often the database backup will be created. The schedule is written as a cron schedule.
- `encryption` (Attributes) (see [below for nested schema](#nestedatt--encryption)) - `flavor_id` (String) The id of the instance flavor.
- `flavor_id` (String) - `id` (String) The ID of the instance.
- `id` (String) Terraform's internal data source. ID. It is structured as "`project_id`,`region`,`instance_id`". - `is_deletable` (Boolean) Whether the instance can be deleted or not.
- `name` (String) Instance name. - `name` (String) The name of the instance.
- `network` (Attributes) (see [below for nested schema](#nestedatt--network)) - `network` (Attributes) The access configuration of the instance (see [below for nested schema](#nestedatt--network))
- `replicas` (Number) - `replicas` (Number) How many replicas the instance should have.
- `retention_days` (Number) - `retention_days` (Number) How long backups are retained. The value can only be between 32 and 365 days.
- `storage` (Attributes) (see [below for nested schema](#nestedatt--storage)) - `status` (String) The current status of the instance.
- `version` (String) - `storage` (Attributes) The object containing information about the storage size and class. (see [below for nested schema](#nestedatt--storage))
- `version` (String) The Postgres version used for the instance. See [Versions Endpoint](/documentation/postgres-flex-service/version/v3alpha1#tag/Version) for supported version parameters.
<a id="nestedatt--encryption"></a>
### Nested Schema for `encryption`
Read-Only:
- `key_id` (String)
- `key_version` (String)
- `keyring_id` (String)
- `service_account` (String)
<a id="nestedatt--network"></a> <a id="nestedatt--network"></a>
### Nested Schema for `network` ### Nested Schema for `network`
Read-Only: Read-Only:
- `access_scope` (String) - `access_scope` (String) The access scope of the instance. It defines if the instance is public or airgapped.
- `acl` (List of String) The Access Control List (ACL) for the PostgresFlex instance. - `acl` (List of String) List of IPV4 cidr.
- `instance_address` (String) - `instance_address` (String)
- `router_address` (String) - `router_address` (String)
@ -71,5 +58,5 @@ Read-Only:
Read-Only: Read-Only:
- `class` (String) - `performance_class` (String) The storage class for the storage.
- `size` (Number) - `size` (Number) The storage size in Gigabytes.

View file

@ -3,12 +3,12 @@
page_title: "stackitprivatepreview_postgresflexalpha_instance Resource - stackitprivatepreview" page_title: "stackitprivatepreview_postgresflexalpha_instance Resource - stackitprivatepreview"
subcategory: "" subcategory: ""
description: |- description: |-
Postgres Flex instance resource schema. Must have a region specified in the provider configuration.
--- ---
# stackitprivatepreview_postgresflexalpha_instance (Resource) # stackitprivatepreview_postgresflexalpha_instance (Resource)
Postgres Flex instance resource schema. Must have a `region` specified in the provider configuration.
## Example Usage ## Example Usage
@ -42,49 +42,42 @@ import {
### Required ### Required
- `backup_schedule` (String) - `backup_schedule` (String) The schedule for on what time and how often the database backup will be created. The schedule is written as a cron schedule.
- `encryption` (Attributes) The encryption block. (see [below for nested schema](#nestedatt--encryption)) - `flavor_id` (String) The id of the instance flavor.
- `flavor_id` (String) - `name` (String) The name of the instance.
- `name` (String) Instance name. - `network` (Attributes) The access configuration of the instance (see [below for nested schema](#nestedatt--network))
- `network` (Attributes) The network block configuration. (see [below for nested schema](#nestedatt--network)) - `replicas` (Number) How many replicas the instance should have.
- `project_id` (String) STACKIT project ID to which the instance is associated. - `retention_days` (Number) How long backups are retained. The value can only be between 32 and 365 days.
- `replicas` (Number) - `storage` (Attributes) The object containing information about the storage size and class. (see [below for nested schema](#nestedatt--storage))
- `retention_days` (Number) The days of the retention period. - `version` (String) The Postgres version used for the instance. See [Versions Endpoint](/documentation/postgres-flex-service/version/v3alpha1#tag/Version) for supported version parameters.
- `storage` (Attributes) (see [below for nested schema](#nestedatt--storage))
- `version` (String) The database version used.
### Optional ### Optional
- `region` (String) The resource region. If not defined, the provider region is used. - `encryption` (Attributes) The configuration for instance's volume and backup storage encryption.
⚠️ **Note:** This feature is in private preview. Supplying this object is only permitted for enabled accounts. If your account does not have access, the request will be rejected. (see [below for nested schema](#nestedatt--encryption))
- `instance_id` (String) The ID of the instance.
- `project_id` (String) The STACKIT project ID.
- `region` (String) The region which should be addressed
### Read-Only ### Read-Only
- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`region`,`instance_id`". - `id` (String) The ID of the instance.
- `instance_id` (String) ID of the PostgresFlex instance. - `is_deletable` (Boolean) Whether the instance can be deleted or not.
- `status` (String) The current status of the instance.
<a id="nestedatt--encryption"></a>
### Nested Schema for `encryption`
Required:
- `key_id` (String) Key ID of the encryption key.
- `key_version` (String) Key version of the encryption key.
- `keyring_id` (String) KeyRing ID of the encryption key.
- `service_account` (String) The service account ID of the service account.
<a id="nestedatt--network"></a> <a id="nestedatt--network"></a>
### Nested Schema for `network` ### Nested Schema for `network`
Required: Required:
- `acl` (List of String) The Access Control List (ACL) for the PostgresFlex instance. - `acl` (List of String) List of IPV4 cidr.
Optional: Optional:
- `access_scope` (String) The access scope. (Either SNA or PUBLIC) - `access_scope` (String) The access scope of the instance. It defines if the instance is public or airgapped.
- `instance_address` (String) The returned instance address. - `instance_address` (String)
- `router_address` (String) The returned router address. - `router_address` (String)
<a id="nestedatt--storage"></a> <a id="nestedatt--storage"></a>
@ -92,5 +85,16 @@ Optional:
Required: Required:
- `class` (String) The storage class used. - `performance_class` (String) The storage class for the storage.
- `size` (Number) The disk size of the storage. - `size` (Number) The storage size in Gigabytes.
<a id="nestedatt--encryption"></a>
### Nested Schema for `encryption`
Required:
- `kek_key_id` (String) The encryption-key key identifier
- `kek_key_ring_id` (String) The encryption-key keyring identifier
- `kek_key_version` (String) The encryption-key version
- `service_account` (String)

View file

@ -146,6 +146,8 @@ func PartialUpdateInstanceWaitHandler(
return false, nil, nil return false, nil, nil
case InstanceStateTerminating: case InstanceStateTerminating:
return false, nil, nil return false, nil, nil
case InstanceStatePending:
return false, nil, nil
case InstanceStateSuccess: case InstanceStateSuccess:
return true, s, nil return true, s, nil
case InstanceStateFailed: case InstanceStateFailed:

View file

@ -9,22 +9,22 @@ data "stackitprivatepreview_postgresflexalpha_flavor" "pgsql_flavor" {
resource "stackitprivatepreview_postgresflexalpha_instance" "msh-sna-pe-example" { resource "stackitprivatepreview_postgresflexalpha_instance" "msh-sna-pe-example" {
project_id = var.project_id project_id = var.project_id
name = "msh-sna-pe-example" name = "mshpetest2"
backup_schedule = "0 0 * * *" backup_schedule = "0 0 * * *"
retention_days = 33 retention_days = 45
flavor_id = data.stackitprivatepreview_postgresflexalpha_flavor.pgsql_flavor.flavor_id flavor_id = data.stackitprivatepreview_postgresflexalpha_flavor.pgsql_flavor.flavor_id
replicas = 1 replicas = 1
storage = { storage = {
# class = "premium-perf2-stackit" # class = "premium-perf2-stackit"
class = data.stackitprivatepreview_postgresflexalpha_flavor.pgsql_flavor.storage_class performance_class = data.stackitprivatepreview_postgresflexalpha_flavor.pgsql_flavor.storage_class
size = 10 size = 10
} }
encryption = { encryption = {
# key_id = stackit_kms_key.key.key_id # key_id = stackit_kms_key.key.key_id
# keyring_id = stackit_kms_keyring.keyring.keyring_id # keyring_id = stackit_kms_keyring.keyring.keyring_id
key_id = var.key_id kek_key_id = var.key_id
keyring_id = var.keyring_id kek_key_ring_id = var.keyring_id
key_version = var.key_version kek_key_version = var.key_version
service_account = var.sa_email service_account = var.sa_email
} }
network = { network = {

View file

@ -7,18 +7,13 @@ import (
"github.com/mhenselin/terraform-provider-stackitprivatepreview/pkg/postgresflexalpha" "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/conversion"
postgresflexalpha2 "github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/instance/datasources_gen"
postgresflexUtils "github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/utils" postgresflexUtils "github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/utils"
"github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/core" "github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/core"
"github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/utils" "github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/utils"
"github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/validate"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
) )
// Ensure the implementation satisfies the expected interfaces. // Ensure the implementation satisfies the expected interfaces.
@ -38,20 +33,12 @@ type instanceDataSource struct {
} }
// Metadata returns the data source type name. // Metadata returns the data source type name.
func (r *instanceDataSource) Metadata( func (r *instanceDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
_ context.Context,
req datasource.MetadataRequest,
resp *datasource.MetadataResponse,
) {
resp.TypeName = req.ProviderTypeName + "_postgresflexalpha_instance" resp.TypeName = req.ProviderTypeName + "_postgresflexalpha_instance"
} }
// Configure adds the provider configured client to the data source. // Configure adds the provider configured client to the data source.
func (r *instanceDataSource) Configure( func (r *instanceDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
ctx context.Context,
req datasource.ConfigureRequest,
resp *datasource.ConfigureResponse,
) {
var ok bool var ok bool
r.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics) r.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics)
if !ok { if !ok {
@ -67,131 +54,13 @@ func (r *instanceDataSource) Configure(
} }
// Schema defines the schema for the data source. // 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{ resp.Schema = postgresflexalpha2.InstanceDataSourceSchema(ctx)
"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`\".",
"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.",
}
resp.Schema = schema.Schema{
Description: descriptions["main"],
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Description: descriptions["id"],
Computed: true,
},
"instance_id": schema.StringAttribute{
Description: descriptions["instance_id"],
Required: true,
Validators: []validator.String{
validate.UUID(),
validate.NoSeparator(),
},
},
"project_id": schema.StringAttribute{
Description: descriptions["project_id"],
Required: true,
Validators: []validator.String{
validate.UUID(),
validate.NoSeparator(),
},
},
"name": schema.StringAttribute{
Description: descriptions["name"],
Computed: true,
},
"backup_schedule": schema.StringAttribute{
Computed: true,
},
"retention_days": schema.Int64Attribute{
Computed: true,
},
"flavor_id": schema.StringAttribute{
Computed: true,
},
"replicas": schema.Int64Attribute{
Computed: true,
},
"storage": schema.SingleNestedAttribute{
Computed: true,
Attributes: map[string]schema.Attribute{
"class": schema.StringAttribute{
Computed: true,
},
"size": schema.Int64Attribute{
Computed: true,
},
},
},
"version": schema.StringAttribute{
Computed: true,
},
"region": schema.StringAttribute{
// the region cannot be found, so it has to be passed
Optional: true,
Description: descriptions["region"],
},
"encryption": schema.SingleNestedAttribute{
Computed: true,
Attributes: map[string]schema.Attribute{
"key_id": schema.StringAttribute{
Description: descriptions["key_id"],
Computed: true,
},
"key_version": schema.StringAttribute{
Description: descriptions["key_version"],
Computed: true,
},
"keyring_id": schema.StringAttribute{
Description: descriptions["keyring_id"],
Computed: true,
},
"service_account": schema.StringAttribute{
Description: descriptions["service_account"],
Computed: true,
},
},
Description: descriptions["encryption"],
},
"network": schema.SingleNestedAttribute{
Computed: true,
Attributes: map[string]schema.Attribute{
"access_scope": schema.StringAttribute{
Description: descriptions["access_scope"],
Computed: true,
},
"acl": schema.ListAttribute{
Description: descriptions["acl"],
ElementType: types.StringType,
Computed: true,
},
"instance_address": schema.StringAttribute{
Description: descriptions["instance_address"],
Computed: true,
},
"router_address": schema.StringAttribute{
Description: descriptions["router_address"],
Computed: true,
},
},
Description: descriptions["network"],
},
},
}
} }
// Read refreshes the Terraform state with the latest data. // Read refreshes the Terraform state with the latest data.
func (r *instanceDataSource) Read( func (r *instanceDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
ctx context.Context, var model postgresflexalpha2.InstanceModel
req datasource.ReadRequest,
resp *datasource.ReadResponse,
) { // nolint:gocritic // function signature required by Terraform
var model Model
diags := req.Config.Get(ctx, &model) diags := req.Config.Get(ctx, &model)
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
@ -224,43 +93,12 @@ func (r *instanceDataSource) Read(
ctx = core.LogResponse(ctx) ctx = core.LogResponse(ctx)
var storage = &storageModel{} err = mapGetDataInstanceResponseToModel(ctx, &model, instanceResp)
if !model.Storage.IsNull() && !model.Storage.IsUnknown() {
diags = model.Storage.As(ctx, storage, basetypes.ObjectAsOptions{})
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
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
}
}
err = mapFields(ctx, instanceResp, &model, storage, encryption, network, region)
if err != nil { if err != nil {
core.LogAndAddError( core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Calling API: %v", err))
ctx,
&resp.Diagnostics,
"Error reading instance",
fmt.Sprintf("Processing API payload: %v", err),
)
return return
} }
// Set refreshed state // Set refreshed state
diags = resp.State.Set(ctx, model) diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)

View file

@ -3,217 +3,119 @@ package postgresflexalpha
import ( import (
"context" "context"
"fmt" "fmt"
"math"
"github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types"
postgresflex "github.com/mhenselin/terraform-provider-stackitprivatepreview/pkg/postgresflexalpha" postgresflex "github.com/mhenselin/terraform-provider-stackitprivatepreview/pkg/postgresflexalpha"
"github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/conversion" postgresflexalphadatasource "github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/instance/datasources_gen"
"github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/core" postgresflexalpharesource "github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/instance/resources_gen"
"github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/utils" "github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/utils"
) )
func mapFields( func mapGetInstanceResponseToModel(ctx context.Context, m *postgresflexalpharesource.InstanceModel, resp *postgresflex.GetInstanceResponse) error {
ctx context.Context, m.BackupSchedule = types.StringValue(resp.GetBackupSchedule())
resp *postgresflex.GetInstanceResponse, // need to leave out encryption, as the GetInstance endpoint does not provide it
model *Model, //m.Encryption = postgresflexalpharesource.NewEncryptionValueMust(
storage *storageModel, // m.Encryption.AttributeTypes(ctx),
encryption *encryptionModel, // map[string]attr.Value{
network *networkModel, // "kek_key_id": types.StringValue(resp.Encryption.GetKekKeyId()),
region string, // "kek_key_ring_id": types.StringValue(resp.Encryption.GetKekKeyRingId()),
) error { // "kek_key_version": types.StringValue(resp.Encryption.GetKekKeyVersion()),
if resp == nil { // "service_account": types.StringValue(resp.Encryption.GetServiceAccount()),
return fmt.Errorf("response input is nil") // },
//)
m.FlavorId = types.StringValue(resp.GetFlavorId())
if m.Id.IsNull() || m.Id.IsUnknown() {
m.Id = utils.BuildInternalTerraformId(m.ProjectId.ValueString(), m.Region.ValueString(), m.InstanceId.ValueString())
} }
if model == nil { m.InstanceId = types.StringPointerValue(resp.Id)
return fmt.Errorf("model input is nil") m.IsDeletable = types.BoolValue(resp.GetIsDeletable())
} m.Name = types.StringValue(resp.GetName())
instance := resp
var instanceId string netAcl, diags := types.ListValueFrom(ctx, types.StringType, resp.Network.GetAcl())
if model.InstanceId.ValueString() != "" {
instanceId = model.InstanceId.ValueString()
} else if instance.Id != nil {
instanceId = *instance.Id
} else {
return fmt.Errorf("instance id not present")
}
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() { if diags.HasError() {
return fmt.Errorf("creating encryption: %w", core.DiagsToError(diags)) return fmt.Errorf("failed converting network acl from response")
} }
var networkValues map[string]attr.Value net, diags := postgresflexalpharesource.NewNetworkValue(
if instance.Network == nil { postgresflexalpharesource.NetworkValue{}.AttributeTypes(ctx),
networkValues = map[string]attr.Value{ map[string]attr.Value{
"acl": network.ACL, "access_scope": types.StringValue(string(resp.Network.GetAccessScope())),
"access_scope": network.AccessScope, "acl": netAcl,
"instance_address": network.InstanceAddress, "instance_address": types.StringValue(resp.Network.GetInstanceAddress()),
"router_address": network.RouterAddress, "router_address": types.StringValue(resp.Network.GetRouterAddress()),
} },
} else { )
aclList, diags := types.ListValueFrom(ctx, types.StringType, *instance.Network.Acl)
if diags.HasError() {
return fmt.Errorf("creating network (acl list): %w", core.DiagsToError(diags))
}
networkValues = map[string]attr.Value{
"acl": aclList,
"access_scope": types.StringPointerValue((*string)(instance.Network.AccessScope)),
"instance_address": types.StringPointerValue(instance.Network.InstanceAddress),
"router_address": types.StringPointerValue(instance.Network.RouterAddress),
}
}
networkObject, diags := types.ObjectValue(networkTypes, networkValues)
if diags.HasError() { if diags.HasError() {
return fmt.Errorf("creating network: %w", core.DiagsToError(diags)) return fmt.Errorf("failed converting network from response")
} }
var storageValues map[string]attr.Value m.Network = net
if instance.Storage == nil { m.Replicas = types.Int64Value(int64(resp.GetReplicas()))
storageValues = map[string]attr.Value{ m.RetentionDays = types.Int64Value(resp.GetRetentionDays())
"class": storage.Class, m.Status = types.StringValue(string(resp.GetStatus()))
"size": storage.Size,
} storage, diags := postgresflexalpharesource.NewStorageValue(
} else { postgresflexalpharesource.StorageValue{}.AttributeTypes(ctx),
storageValues = map[string]attr.Value{ map[string]attr.Value{
"class": types.StringValue(*instance.Storage.PerformanceClass), "performance_class": types.StringValue(resp.Storage.GetPerformanceClass()),
"size": types.Int64PointerValue(instance.Storage.Size), "size": types.Int64Value(resp.Storage.GetSize()),
} },
} )
storageObject, diags := types.ObjectValue(storageTypes, storageValues)
if diags.HasError() { if diags.HasError() {
return fmt.Errorf("creating storage: %w", core.DiagsToError(diags)) return fmt.Errorf("failed converting storage from response")
} }
m.Storage = storage
if instance.Replicas == nil { m.Version = types.StringValue(resp.GetVersion())
diags.AddError("error mapping fields", "replicas is nil")
return fmt.Errorf("replicas is nil")
}
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, instanceId)
model.InstanceId = types.StringValue(instanceId)
model.Name = types.StringPointerValue(instance.Name)
model.BackupSchedule = types.StringPointerValue(instance.BackupSchedule)
model.FlavorId = types.StringPointerValue(instance.FlavorId)
model.Replicas = types.Int64Value(int64(*instance.Replicas))
model.Storage = storageObject
model.Version = types.StringPointerValue(instance.Version)
model.Region = types.StringValue(region)
model.Encryption = encryptionObject
model.Network = networkObject
return nil return nil
} }
func toCreatePayload( func mapGetDataInstanceResponseToModel(ctx context.Context, m *postgresflexalphadatasource.InstanceModel, resp *postgresflex.GetInstanceResponse) error {
model *Model, m.BackupSchedule = types.StringValue(resp.GetBackupSchedule())
storage *storageModel, //m.Encryption = postgresflexalpharesource.EncryptionValue{
enc *encryptionModel, // KekKeyId: types.StringValue(resp.Encryption.GetKekKeyId()),
net *networkModel, // KekKeyRingId: types.StringValue(resp.Encryption.GetKekKeyRingId()),
) (*postgresflex.CreateInstanceRequestPayload, error) { // KekKeyVersion: types.StringValue(resp.Encryption.GetKekKeyVersion()),
if model == nil { // ServiceAccount: types.StringValue(resp.Encryption.GetServiceAccount()),
return nil, fmt.Errorf("nil model") //}
} m.FlavorId = types.StringValue(resp.GetFlavorId())
if storage == nil { m.Id = utils.BuildInternalTerraformId(m.ProjectId.ValueString(), m.Region.ValueString(), m.InstanceId.ValueString())
return nil, fmt.Errorf("nil storage") m.InstanceId = types.StringPointerValue(resp.Id)
m.IsDeletable = types.BoolValue(resp.GetIsDeletable())
m.Name = types.StringValue(resp.GetName())
netAcl, diags := types.ListValueFrom(ctx, types.StringType, resp.Network.GetAcl())
if diags.HasError() {
return fmt.Errorf("failed converting network acl from response")
} }
var replVal int32 net, diags := postgresflexalphadatasource.NewNetworkValue(
if !model.Replicas.IsNull() && !model.Replicas.IsUnknown() { postgresflexalphadatasource.NetworkValue{}.AttributeTypes(ctx),
if model.Replicas.ValueInt64() > math.MaxInt32 { map[string]attr.Value{
return nil, fmt.Errorf("replica count too big: %d", model.Replicas.ValueInt64()) "access_scope": types.StringValue(string(resp.Network.GetAccessScope())),
} "acl": netAcl,
replVal = int32(model.Replicas.ValueInt64()) // nolint:gosec // check is performed above "instance_address": types.StringValue(resp.Network.GetInstanceAddress()),
} "router_address": types.StringValue(resp.Network.GetRouterAddress()),
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))
}
}
if len(aclElements) < 1 {
return nil, fmt.Errorf("no acl elements found")
}
networkPayload := &postgresflex.CreateInstanceRequestPayloadGetNetworkArgType{}
if net != nil {
networkPayload = &postgresflex.CreateInstanceRequestPayloadGetNetworkArgType{
AccessScope: postgresflex.InstanceNetworkGetAccessScopeAttributeType(conversion.StringValueToPointer(net.AccessScope)),
Acl: &aclElements,
}
}
return &postgresflex.CreateInstanceRequestPayload{
Acl: &aclElements,
BackupSchedule: conversion.StringValueToPointer(model.BackupSchedule),
Encryption: encryptionPayload,
FlavorId: conversion.StringValueToPointer(model.FlavorId),
Name: conversion.StringValueToPointer(model.Name),
Network: networkPayload,
Replicas: postgresflex.CreateInstanceRequestPayloadGetReplicasAttributeType(&replVal),
RetentionDays: conversion.Int64ValueToPointer(model.RetentionDays),
Storage: storagePayload,
Version: conversion.StringValueToPointer(model.Version),
}, nil
}
func toUpdatePayload(
model *Model,
storage *storageModel,
_ *networkModel,
) (*postgresflex.UpdateInstancePartiallyRequestPayload, error) {
if model == nil {
return nil, fmt.Errorf("nil model")
}
if storage == nil {
return nil, fmt.Errorf("nil storage")
}
return &postgresflex.UpdateInstancePartiallyRequestPayload{
// Acl: postgresflexalpha.UpdateInstancePartiallyRequestPayloadGetAclAttributeType{
// Items: &acl,
// },
BackupSchedule: conversion.StringValueToPointer(model.BackupSchedule),
FlavorId: conversion.StringValueToPointer(model.FlavorId),
Name: conversion.StringValueToPointer(model.Name),
// Replicas: conversion.Int64ValueToPointer(model.Replicas),
Storage: &postgresflex.StorageUpdate{
Size: conversion.Int64ValueToPointer(storage.Size),
}, },
Version: conversion.StringValueToPointer(model.Version), )
}, nil if diags.HasError() {
return fmt.Errorf("failed converting network from response")
}
m.Network = net
m.Replicas = types.Int64Value(int64(resp.GetReplicas()))
m.RetentionDays = types.Int64Value(resp.GetRetentionDays())
m.Status = types.StringValue(string(resp.GetStatus()))
storage, diags := postgresflexalphadatasource.NewStorageValue(
postgresflexalphadatasource.StorageValue{}.AttributeTypes(ctx),
map[string]attr.Value{
"performance_class": types.StringValue(resp.Storage.GetPerformanceClass()),
"size": types.Int64Value(resp.Storage.GetSize()),
},
)
if diags.HasError() {
return fmt.Errorf("failed converting storage from response")
}
m.Storage = storage
m.Version = types.StringValue(resp.GetVersion())
return nil
} }

View file

@ -3,32 +3,24 @@ package postgresflexalpha
import ( import (
"context" "context"
"fmt" "fmt"
"math"
"net/http" "net/http"
"regexp"
"strings" "strings"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
postgresflex "github.com/mhenselin/terraform-provider-stackitprivatepreview/pkg/postgresflexalpha"
"github.com/mhenselin/terraform-provider-stackitprivatepreview/pkg/postgresflexalpha/wait"
postgresflexUtils "github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/utils"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"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"
"github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/conversion"
"github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/core"
"github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/utils"
"github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/validate"
"github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
postgresflex "github.com/mhenselin/terraform-provider-stackitprivatepreview/pkg/postgresflexalpha"
"github.com/mhenselin/terraform-provider-stackitprivatepreview/pkg/postgresflexalpha/wait"
"github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/conversion"
"github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/core"
postgresflexalpha "github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/instance/resources_gen"
postgresflexUtils "github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/utils"
"github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/utils"
"github.com/stackitcloud/stackit-sdk-go/core/oapierror" "github.com/stackitcloud/stackit-sdk-go/core/oapierror"
) )
@ -53,12 +45,9 @@ type instanceResource struct {
providerData core.ProviderData providerData core.ProviderData
} }
func (r *instanceResource) ValidateConfig( func (r *instanceResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) {
ctx context.Context, var data postgresflexalpha.InstanceModel
req resource.ValidateConfigRequest, // var data Model
resp *resource.ValidateConfigResponse,
) {
var data Model
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
@ -77,12 +66,9 @@ func (r *instanceResource) ValidateConfig(
// ModifyPlan implements resource.ResourceWithModifyPlan. // ModifyPlan implements resource.ResourceWithModifyPlan.
// Use the modifier to set the effective region in the current plan. // Use the modifier to set the effective region in the current plan.
func (r *instanceResource) ModifyPlan( func (r *instanceResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { // nolint:gocritic // function signature required by Terraform
ctx context.Context, var configModel postgresflexalpha.InstanceModel
req resource.ModifyPlanRequest, // var configModel Model
resp *resource.ModifyPlanResponse,
) { // nolint:gocritic // function signature required by Terraform
var configModel Model
// skip initial empty configuration to avoid follow-up errors // skip initial empty configuration to avoid follow-up errors
if req.Config.Raw.IsNull() { if req.Config.Raw.IsNull() {
return return
@ -92,7 +78,8 @@ func (r *instanceResource) ModifyPlan(
return return
} }
var planModel Model var planModel postgresflexalpha.InstanceModel
// var planModel Model
resp.Diagnostics.Append(req.Plan.Get(ctx, &planModel)...) resp.Diagnostics.Append(req.Plan.Get(ctx, &planModel)...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
@ -135,243 +122,19 @@ func (r *instanceResource) Configure(
} }
// Schema defines the schema for the resource. // Schema defines the schema for the resource.
func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { func (r *instanceResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
descriptions := map[string]string{ resp.Schema = postgresflexalpha.InstanceResourceSchema(ctx)
"main": "Postgres Flex instance resource schema. Must have a `region` specified in the provider configuration.", resp.Schema = addPlanModifiers(resp.Schema)
"id": "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`instance_id`\".",
"instance_id": "ID of the PostgresFlex instance.",
"project_id": "STACKIT project ID to which the instance is associated.",
"name": "Instance name.",
"backup_schedule": "The schedule for on what time and how often the database backup will be created. The schedule is written as a cron schedule.",
"retention_days": "The days of the retention period.",
"flavor_id": "The ID of the flavor.",
"replicas": "The number of replicas.",
"storage": "The block of the storage configuration.",
"storage_class": "The storage class used.",
"storage_size": "The disk size of the storage.",
"region": "The resource region. If not defined, the provider region is used.",
"version": "The database version used.",
"encryption": "The encryption block.",
"keyring_id": "KeyRing ID of the encryption key.",
"key_id": "Key ID of the encryption key.",
"key_version": "Key version of the encryption key.",
"service_account": "The service account ID of the service account.",
"network": "The network block configuration.",
"access_scope": "The access scope. (Either SNA or PUBLIC)",
"acl": "The Access Control List (ACL) for the PostgresFlex instance.",
"instance_address": "The returned instance address.",
"router_address": "The returned router address.",
}
resp.Schema = schema.Schema{
Description: descriptions["main"],
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Description: descriptions["id"],
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"instance_id": schema.StringAttribute{
Description: descriptions["instance_id"],
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
Validators: []validator.String{
validate.UUID(),
validate.NoSeparator(),
},
},
"project_id": schema.StringAttribute{
Description: descriptions["project_id"],
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
stringplanmodifier.UseStateForUnknown(),
},
Validators: []validator.String{
validate.UUID(),
validate.NoSeparator(),
},
},
"name": schema.StringAttribute{
Description: descriptions["name"],
Required: true,
Validators: []validator.String{
stringvalidator.LengthAtLeast(1),
stringvalidator.RegexMatches(
regexp.MustCompile("^[a-z]([-a-z0-9]*[a-z0-9])?$"),
"must start with a letter, must have lower case letters, numbers or hyphens, and no hyphen at the end",
),
},
},
"backup_schedule": schema.StringAttribute{
Required: true,
},
"retention_days": schema.Int64Attribute{
Description: descriptions["retention_days"],
Required: true,
},
"flavor_id": schema.StringAttribute{
Required: true,
},
"replicas": schema.Int64Attribute{
Required: true,
PlanModifiers: []planmodifier.Int64{
int64planmodifier.RequiresReplace(),
},
},
"storage": schema.SingleNestedAttribute{
Required: true,
Attributes: map[string]schema.Attribute{
"class": schema.StringAttribute{
Required: true,
Description: descriptions["storage_class"],
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"size": schema.Int64Attribute{
Description: descriptions["storage_size"],
Required: true,
// PlanModifiers: []planmodifier.Int64{
// TODO - req replace if new size smaller than state size
// int64planmodifier.RequiresReplaceIf(),
// },
},
},
},
"version": schema.StringAttribute{
Description: descriptions["version"],
Required: true,
},
"region": schema.StringAttribute{
Optional: true,
// must be computed to allow for storing the override value from the provider
Computed: true,
Description: descriptions["region"],
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"encryption": schema.SingleNestedAttribute{
Required: true,
Attributes: map[string]schema.Attribute{
"key_id": schema.StringAttribute{
Description: descriptions["key_id"],
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
Validators: []validator.String{
validate.NoSeparator(),
},
},
"key_version": schema.StringAttribute{
Description: descriptions["key_version"],
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
Validators: []validator.String{
validate.NoSeparator(),
},
},
"keyring_id": schema.StringAttribute{
Description: descriptions["keyring_id"],
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
Validators: []validator.String{
validate.NoSeparator(),
},
},
"service_account": schema.StringAttribute{
Description: descriptions["service_account"],
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
Validators: []validator.String{
validate.NoSeparator(),
},
},
},
Description: descriptions["encryption"],
//Validators: nil,
PlanModifiers: []planmodifier.Object{},
},
"network": schema.SingleNestedAttribute{
Required: true,
Attributes: map[string]schema.Attribute{
"access_scope": schema.StringAttribute{
Default: stringdefault.StaticString(
"PUBLIC",
),
Description: descriptions["access_scope"],
Computed: true,
Optional: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
stringplanmodifier.UseStateForUnknown(),
},
Validators: []validator.String{
validate.NoSeparator(),
stringvalidator.OneOf("SNA", "PUBLIC"),
},
},
"acl": schema.ListAttribute{
Description: descriptions["acl"],
ElementType: types.StringType,
Required: true,
PlanModifiers: []planmodifier.List{
listplanmodifier.UseStateForUnknown(),
},
},
"instance_address": schema.StringAttribute{
Description: descriptions["instance_address"],
Computed: true,
Optional: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"router_address": schema.StringAttribute{
Description: descriptions["router_address"],
Computed: true,
Optional: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
},
Description: descriptions["network"],
//MarkdownDescription: "",
//Validators: nil,
PlanModifiers: []planmodifier.Object{},
},
},
}
} }
// func (r *instanceResource) IdentitySchema(_ context.Context, _ resource.IdentitySchemaRequest, resp *resource.IdentitySchemaResponse) { func addPlanModifiers(s schema.Schema) schema.Schema {
// resp.IdentitySchema = identityschema.Schema{ attr := s.Attributes["backup_schedule"].(schema.StringAttribute)
// Attributes: map[string]identityschema.Attribute{ attr.PlanModifiers = []planmodifier.String{
// "project_id": identityschema.StringAttribute{ stringplanmodifier.UseStateForUnknown(),
// RequiredForImport: true, // must be set during import by the practitioner }
// }, s.Attributes["backup_schedule"] = attr
// "region": identityschema.StringAttribute{ return s
// RequiredForImport: true, // must be set during import by the practitioner }
// },
// "instance_id": identityschema.StringAttribute{
// RequiredForImport: true, // must be set during import by the practitioner
// },
// },
// }
//}
// Create creates the resource and sets the initial Terraform state. // Create creates the resource and sets the initial Terraform state.
func (r *instanceResource) Create( func (r *instanceResource) Create(
@ -379,7 +142,8 @@ func (r *instanceResource) Create(
req resource.CreateRequest, req resource.CreateRequest,
resp *resource.CreateResponse, resp *resource.CreateResponse,
) { // nolint:gocritic // function signature required by Terraform ) { // nolint:gocritic // function signature required by Terraform
var model Model var model postgresflexalpha.InstanceModel
//var model Model
diags := req.Plan.Get(ctx, &model) diags := req.Plan.Get(ctx, &model)
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
@ -393,59 +157,22 @@ func (r *instanceResource) Create(
ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "region", region) ctx = tflog.SetField(ctx, "region", region)
var storage = &storageModel{} var netAcl []string
if !model.Storage.IsNull() && !model.Storage.IsUnknown() { diag := model.Network.Acl.ElementsAs(ctx, &netAcl, false)
diags = model.Storage.As(ctx, storage, basetypes.ObjectAsOptions{}) resp.Diagnostics.Append(diags...)
resp.Diagnostics.Append(diags...) if diag.HasError() {
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
}
}
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 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, storage, encryption, network)
if err != nil {
core.LogAndAddError(
ctx,
&resp.Diagnostics,
"Error creating instance",
fmt.Sprintf("Creating API payload: %v", err),
)
return return
} }
if model.Replicas.ValueInt64() > math.MaxInt32 {
resp.Diagnostics.AddError("invalid int32 value", "provided int64 value does not fit into int32")
return
}
replVal := int32(model.Replicas.ValueInt64()) // nolint:gosec // check is performed above
payload := modelToCreateInstancePayload(netAcl, model, replVal)
// Create new instance // Create new instance
createResp, err := r.client.CreateInstanceRequest( createResp, err := r.client.CreateInstanceRequest(ctx, projectId, region).CreateInstanceRequestPayload(payload).Execute()
ctx,
projectId,
region,
).CreateInstanceRequestPayload(*payload).Execute()
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Calling API: %v", err)) core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Calling API: %v", err))
return return
@ -463,26 +190,16 @@ func (r *instanceResource) Create(
waitResp, err := wait.CreateInstanceWaitHandler(ctx, r.client, projectId, region, instanceId).WaitWithContext(ctx) waitResp, err := wait.CreateInstanceWaitHandler(ctx, r.client, projectId, region, instanceId).WaitWithContext(ctx)
if err != nil { if err != nil {
core.LogAndAddError( core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Wait handler error: %v", err))
ctx,
&resp.Diagnostics,
"Error creating instance",
fmt.Sprintf("Wait handler error: %v", err),
)
return return
} }
// Map response body to schema err = mapGetInstanceResponseToModel(ctx, &model, waitResp)
err = mapFields(ctx, waitResp, &model, storage, encryption, network, region)
if err != nil { if err != nil {
core.LogAndAddError( core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Error creating model: %v", err))
ctx,
&resp.Diagnostics,
"Error creating instance",
fmt.Sprintf("Processing API payload: %v", err),
)
return return
} }
// Set state to fully populated data // Set state to fully populated data
diags = resp.State.Set(ctx, model) diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)
@ -492,13 +209,41 @@ func (r *instanceResource) Create(
tflog.Info(ctx, "Postgres Flex instance created") tflog.Info(ctx, "Postgres Flex instance created")
} }
func modelToCreateInstancePayload(netAcl []string, model postgresflexalpha.InstanceModel, replVal int32) postgresflex.CreateInstanceRequestPayload {
payload := postgresflex.CreateInstanceRequestPayload{
Acl: &netAcl,
BackupSchedule: model.BackupSchedule.ValueStringPointer(),
Encryption: &postgresflex.InstanceEncryption{
KekKeyId: model.Encryption.KekKeyId.ValueStringPointer(),
KekKeyRingId: model.Encryption.KekKeyRingId.ValueStringPointer(),
KekKeyVersion: model.Encryption.KekKeyVersion.ValueStringPointer(),
ServiceAccount: model.Encryption.ServiceAccount.ValueStringPointer(),
},
FlavorId: model.FlavorId.ValueStringPointer(),
Name: model.Name.ValueStringPointer(),
Network: &postgresflex.InstanceNetwork{
AccessScope: postgresflex.InstanceNetworkGetAccessScopeAttributeType(
model.Network.AccessScope.ValueStringPointer(),
),
Acl: &netAcl,
InstanceAddress: model.Network.InstanceAddress.ValueStringPointer(),
RouterAddress: model.Network.RouterAddress.ValueStringPointer(),
},
Replicas: postgresflex.CreateInstanceRequestPayloadGetReplicasAttributeType(&replVal),
RetentionDays: model.RetentionDays.ValueInt64Pointer(),
Storage: &postgresflex.StorageCreate{
PerformanceClass: model.Storage.PerformanceClass.ValueStringPointer(),
Size: model.Storage.Size.ValueInt64Pointer(),
},
Version: model.Version.ValueStringPointer(),
}
return payload
}
// Read refreshes the Terraform state with the latest data. // Read refreshes the Terraform state with the latest data.
func (r *instanceResource) Read( func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
ctx context.Context, var model postgresflexalpha.InstanceModel
req resource.ReadRequest, //var model Model
resp *resource.ReadResponse,
) { // nolint:gocritic // function signature required by Terraform
var model Model
diags := req.State.Get(ctx, &model) diags := req.State.Get(ctx, &model)
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
@ -514,33 +259,6 @@ func (r *instanceResource) Read(
ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "instance_id", instanceId)
ctx = tflog.SetField(ctx, "region", region) ctx = tflog.SetField(ctx, "region", region)
var storage = storageModel{}
if !model.Storage.IsNull() && !model.Storage.IsUnknown() {
diags = model.Storage.As(ctx, &storage, basetypes.ObjectAsOptions{})
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
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
}
}
instanceResp, err := r.client.GetInstanceRequest(ctx, projectId, region, instanceId).Execute() instanceResp, err := r.client.GetInstanceRequest(ctx, projectId, region, instanceId).Execute()
if err != nil { 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 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
@ -554,15 +272,9 @@ func (r *instanceResource) Read(
ctx = core.LogResponse(ctx) ctx = core.LogResponse(ctx)
// Map response body to schema err = mapGetInstanceResponseToModel(ctx, &model, instanceResp)
err = mapFields(ctx, instanceResp, &model, &storage, &encryption, &network, region)
if err != nil { if err != nil {
core.LogAndAddError( core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Processing API payload: %v", err))
ctx,
&resp.Diagnostics,
"Error reading instance",
fmt.Sprintf("Processing API payload: %v", err),
)
return return
} }
@ -576,12 +288,9 @@ func (r *instanceResource) Read(
} }
// Update updates the resource and sets the updated Terraform state on success. // Update updates the resource and sets the updated Terraform state on success.
func (r *instanceResource) Update( func (r *instanceResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform
ctx context.Context, var model postgresflexalpha.InstanceModel
req resource.UpdateRequest, //var model Model
resp *resource.UpdateResponse,
) { // nolint:gocritic // function signature required by Terraform
var model Model
diags := req.Plan.Get(ctx, &model) diags := req.Plan.Get(ctx, &model)
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
@ -597,61 +306,38 @@ func (r *instanceResource) Update(
ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "instance_id", instanceId)
ctx = tflog.SetField(ctx, "region", region) ctx = tflog.SetField(ctx, "region", region)
// nolint:gocritic // need that code later var netAcl []string
// var acl []string diag := model.Network.Acl.ElementsAs(ctx, &netAcl, false)
// if !(model.ACL.IsNull() || model.ACL.IsUnknown()) { resp.Diagnostics.Append(diags...)
// diags = model.ACL.ElementsAs(ctx, &acl, false) if diag.HasError() {
// 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{})
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
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, storage, network)
if err != nil {
core.LogAndAddError(
ctx,
&resp.Diagnostics,
"Error updating instance",
fmt.Sprintf("Creating API payload: %v", err),
)
return return
} }
replInt32 := int32(model.Replicas.ValueInt64())
payload := postgresflex.UpdateInstancePartiallyRequestPayload{
BackupSchedule: model.BackupSchedule.ValueStringPointer(),
FlavorId: model.FlavorId.ValueStringPointer(),
Name: model.Name.ValueStringPointer(),
Network: &postgresflex.InstanceNetwork{
AccessScope: postgresflex.InstanceNetworkGetAccessScopeAttributeType(
model.Network.AccessScope.ValueStringPointer(),
),
Acl: &netAcl,
},
Replicas: postgresflex.UpdateInstancePartiallyRequestPayloadGetReplicasAttributeType(&replInt32),
RetentionDays: model.RetentionDays.ValueInt64Pointer(),
Storage: &postgresflex.StorageUpdate{
Size: model.Storage.Size.ValueInt64Pointer(),
},
Version: model.Version.ValueStringPointer(),
}
// Update existing instance // Update existing instance
err = r.client.UpdateInstancePartiallyRequest( err := r.client.UpdateInstancePartiallyRequest(
ctx, ctx,
projectId, projectId,
region, region,
instanceId, instanceId,
).UpdateInstancePartiallyRequestPayload(*payload).Execute() ).UpdateInstancePartiallyRequestPayload(payload).Execute()
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", err.Error()) core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", err.Error())
return return
@ -659,34 +345,18 @@ func (r *instanceResource) Update(
ctx = core.LogResponse(ctx) ctx = core.LogResponse(ctx)
waitResp, err := wait.PartialUpdateInstanceWaitHandler( waitResp, err := wait.PartialUpdateInstanceWaitHandler(ctx, r.client, projectId, region, instanceId).WaitWithContext(ctx)
ctx,
r.client,
projectId,
region,
instanceId,
).WaitWithContext(ctx)
if err != nil { if err != nil {
core.LogAndAddError( core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Instance update waiting: %v", err))
ctx,
&resp.Diagnostics,
"Error updating instance",
fmt.Sprintf("Instance update waiting: %v", err),
)
return return
} }
// Map response body to schema err = mapGetInstanceResponseToModel(ctx, &model, waitResp)
err = mapFields(ctx, waitResp, &model, storage, encryption, network, region)
if err != nil { if err != nil {
core.LogAndAddError( core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Processing API payload: %v", err))
ctx,
&resp.Diagnostics,
"Error updating instance",
fmt.Sprintf("Processing API payload: %v", err),
)
return return
} }
diags = resp.State.Set(ctx, model) diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
@ -696,12 +366,9 @@ func (r *instanceResource) Update(
} }
// Delete deletes the resource and removes the Terraform state on success. // Delete deletes the resource and removes the Terraform state on success.
func (r *instanceResource) Delete( func (r *instanceResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { // nolint:gocritic // function signature required by Terraform
ctx context.Context, var model postgresflexalpha.InstanceModel
req resource.DeleteRequest, //var model Model
resp *resource.DeleteResponse,
) { // nolint:gocritic // function signature required by Terraform
var model Model
diags := req.State.Get(ctx, &model) diags := req.State.Get(ctx, &model)
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
@ -741,16 +408,11 @@ func (r *instanceResource) Delete(
// ImportState imports a resource into the Terraform state on success. // ImportState imports a resource into the Terraform state on success.
// The expected format of the resource import identifier is: project_id,instance_id // The expected format of the resource import identifier is: project_id,instance_id
func (r *instanceResource) ImportState( func (r *instanceResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
ctx context.Context,
req resource.ImportStateRequest,
resp *resource.ImportStateResponse,
) {
idParts := strings.Split(req.ID, core.Separator) idParts := strings.Split(req.ID, core.Separator)
if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" { if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" {
core.LogAndAddError( core.LogAndAddError(ctx, &resp.Diagnostics,
ctx, &resp.Diagnostics,
"Error importing instance", "Error importing instance",
fmt.Sprintf("Expected import identifier with format: [project_id],[region],[instance_id] Got: %q", req.ID), fmt.Sprintf("Expected import identifier with format: [project_id],[region],[instance_id] Got: %q", req.ID),
) )

View file

@ -1,16 +1,10 @@
package postgresflexalpha package postgresflexalpha
import ( import (
"context"
"reflect" "reflect"
"testing" "testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
postgresflex "github.com/mhenselin/terraform-provider-stackitprivatepreview/pkg/postgresflexalpha"
"github.com/stackitcloud/stackit-sdk-go/core/utils"
) )
// type postgresFlexClientMocked struct { // type postgresFlexClientMocked struct {
@ -26,608 +20,6 @@ import (
// return c.getFlavorsResp, nil // return c.getFlavorsResp, nil
// } // }
func TestMapFields(t *testing.T) {
const testRegion = "region"
tests := []struct {
description string
state Model
input *postgresflex.GetInstanceResponse
storage *storageModel
encryption *encryptionModel
network *networkModel
region string
expected Model
isValid bool
}{
{
"default_values does exactly mean what?",
Model{
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
Replicas: types.Int64Value(1),
},
&postgresflex.GetInstanceResponse{
FlavorId: utils.Ptr("flavor_id"),
Replicas: postgresflex.GetInstanceResponseGetReplicasAttributeType(utils.Ptr(int32(1))),
},
&storageModel{},
&encryptionModel{},
&networkModel{
ACL: types.ListValueMust(
types.StringType, []attr.Value{
types.StringValue("0.0.0.0/0"),
},
),
},
testRegion,
Model{
Id: types.StringValue("pid,region,iid"),
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
Name: types.StringNull(),
FlavorId: types.StringValue("flavor_id"),
//ACL: types.ListNull(types.StringType),
BackupSchedule: types.StringNull(),
Replicas: types.Int64Value(1),
Encryption: types.ObjectValueMust(
encryptionTypes, map[string]attr.Value{
"keyring_id": types.StringNull(),
"key_id": types.StringNull(),
"key_version": types.StringNull(),
"service_account": types.StringNull(),
},
),
Storage: types.ObjectValueMust(
storageTypes, map[string]attr.Value{
"class": types.StringNull(),
"size": types.Int64Null(),
},
),
Network: types.ObjectValueMust(
networkTypes, map[string]attr.Value{
"acl": types.ListValueMust(
types.StringType, []attr.Value{
types.StringValue("0.0.0.0/0"),
},
),
"access_scope": types.StringNull(),
"instance_address": types.StringNull(),
"router_address": types.StringNull(),
},
),
Version: types.StringNull(),
Region: types.StringValue(testRegion),
},
true,
},
// {
// "acl_unordered",
// Model{
// InstanceId: types.StringValue("iid"),
// ProjectId: types.StringValue("pid"),
// // ACL: types.ListValueMust(types.StringType, []attr.Value{
// // types.StringValue("ip2"),
// // types.StringValue(""),
// // types.StringValue("ip1"),
// // }),
// },
// &postgresflex.GetInstanceResponse{
// // Acl: &[]string{
// // "",
// // "ip1",
// // "ip2",
// // },
// BackupSchedule: utils.Ptr("schedule"),
// FlavorId: nil,
// Id: utils.Ptr("iid"),
// Name: utils.Ptr("name"),
// Replicas: postgresflex.GetInstanceResponseGetReplicasAttributeType(utils.Ptr(int32(56))),
// Status: postgresflex.GetInstanceResponseGetStatusAttributeType(utils.Ptr("status")),
// Storage: nil,
// Version: utils.Ptr("version"),
// },
// &flavorModel{
// CPU: types.Int64Value(12),
// RAM: types.Int64Value(34),
// },
// &storageModel{
// Class: types.StringValue("class"),
// Size: types.Int64Value(78),
// },
// &encryptionModel{},
// &networkModel{},
// testRegion,
// Model{
// Id: types.StringValue("pid,region,iid"),
// InstanceId: types.StringValue("iid"),
// ProjectId: types.StringValue("pid"),
// Name: types.StringValue("name"),
// // ACL: types.ListValueMust(types.StringType, []attr.Value{
// // types.StringValue("ip2"),
// // types.StringValue(""),
// // types.StringValue("ip1"),
// // }),
// BackupSchedule: types.StringValue("schedule"),
// Flavor: types.ObjectValueMust(flavorTypes, map[string]attr.Value{
// "id": types.StringNull(),
// "description": types.StringNull(),
// "cpu": types.Int64Value(12),
// "ram": types.Int64Value(34),
// }),
// Replicas: types.Int64Value(56),
// Storage: types.ObjectValueMust(storageTypes, map[string]attr.Value{
// "class": types.StringValue("class"),
// "size": types.Int64Value(78),
// }),
// Version: types.StringValue("version"),
// Region: types.StringValue(testRegion),
// },
// true,
// },
{
"nil_response",
Model{
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
},
nil,
&storageModel{},
&encryptionModel{},
&networkModel{},
testRegion,
Model{},
false,
},
{
"no_resource_id",
Model{
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
},
&postgresflex.GetInstanceResponse{},
&storageModel{},
&encryptionModel{},
&networkModel{},
testRegion,
Model{},
false,
},
}
for _, tt := range tests {
t.Run(
tt.description, func(t *testing.T) {
err := mapFields(
context.Background(),
tt.input,
&tt.state,
tt.storage,
tt.encryption,
tt.network,
tt.region,
)
if !tt.isValid && err == nil {
t.Fatalf("Should have failed")
}
if tt.isValid && err != nil {
t.Fatalf("Should not have failed: %v", err)
}
if tt.isValid {
diff := cmp.Diff(tt.expected, tt.state)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
}
},
)
}
}
func TestToCreatePayload(t *testing.T) {
tests := []struct {
description string
input *Model
inputAcl []string
inputStorage *storageModel
inputEncryption *encryptionModel
inputNetwork *networkModel
expected *postgresflex.CreateInstanceRequestPayload
isValid bool
}{
{
"default_values",
&Model{
Replicas: types.Int64Value(1),
},
[]string{},
&storageModel{},
&encryptionModel{},
&networkModel{
ACL: types.ListValueMust(
types.StringType, []attr.Value{
types.StringValue("0.0.0.0/0"),
},
),
},
&postgresflex.CreateInstanceRequestPayload{
Acl: &[]string{"0.0.0.0/0"},
Storage: postgresflex.CreateInstanceRequestPayloadGetStorageAttributeType(&postgresflex.Storage{}),
Encryption: &postgresflex.InstanceEncryption{},
Network: &postgresflex.InstanceNetwork{
Acl: &[]string{"0.0.0.0/0"},
},
Replicas: postgresflex.CreateInstanceRequestPayloadGetReplicasAttributeType(utils.Ptr(int32(1))),
},
true,
},
{
"nil_model",
nil,
[]string{},
&storageModel{},
&encryptionModel{},
&networkModel{},
nil,
false,
},
{
"nil_acl",
&Model{},
nil,
&storageModel{},
&encryptionModel{},
&networkModel{},
nil,
false,
},
{
"nil_flavor",
&Model{},
[]string{},
&storageModel{},
&encryptionModel{},
&networkModel{},
nil,
false,
},
{
"nil_storage",
&Model{},
[]string{},
nil,
&encryptionModel{},
&networkModel{},
nil,
false,
},
}
for _, tt := range tests {
t.Run(
tt.description, func(t *testing.T) {
output, err := toCreatePayload(tt.input, tt.inputStorage, tt.inputEncryption, tt.inputNetwork)
if !tt.isValid && err == nil {
t.Fatalf("Should have failed")
}
if tt.isValid && err != nil {
t.Fatalf("Should not have failed: %v", err)
}
if tt.isValid {
diff := cmp.Diff(tt.expected, output)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
}
},
)
}
}
// func TestToUpdatePayload(t *testing.T) {
// tests := []struct {
// description string
// input *Model
// inputAcl []string
// inputFlavor *flavorModel
// inputStorage *storageModel
// expected *postgresflex.PartialUpdateInstancePayload
// isValid bool
// }{
// {
// "default_values",
// &Model{},
// []string{},
// &flavorModel{},
// &storageModel{},
// &postgresflex.PartialUpdateInstancePayload{
// Acl: &postgresflex.ACL{
// Items: &[]string{},
// },
// Storage: &postgresflex.Storage{},
// },
// true,
// },
// {
// "simple_values",
// &Model{
// BackupSchedule: types.StringValue("schedule"),
// Name: types.StringValue("name"),
// Replicas: types.Int64Value(12),
// Version: types.StringValue("version"),
// },
// []string{
// "ip_1",
// "ip_2",
// },
// &flavorModel{
// Id: types.StringValue("flavor_id"),
// },
// &storageModel{
// Class: types.StringValue("class"),
// Size: types.Int64Value(34),
// },
// &postgresflex.PartialUpdateInstancePayload{
// Acl: &postgresflex.ACL{
// Items: &[]string{
// "ip_1",
// "ip_2",
// },
// },
// BackupSchedule: utils.Ptr("schedule"),
// FlavorId: utils.Ptr("flavor_id"),
// Name: utils.Ptr("name"),
// Replicas: utils.Ptr(int64(12)),
// Storage: &postgresflex.Storage{
// Class: utils.Ptr("class"),
// Size: utils.Ptr(int64(34)),
// },
// Version: utils.Ptr("version"),
// },
// true,
// },
// {
// "null_fields_and_int_conversions",
// &Model{
// BackupSchedule: types.StringNull(),
// Name: types.StringNull(),
// Replicas: types.Int64Value(2123456789),
// Version: types.StringNull(),
// },
// []string{
// "",
// },
// &flavorModel{
// Id: types.StringNull(),
// },
// &storageModel{
// Class: types.StringNull(),
// Size: types.Int64Null(),
// },
// &postgresflex.PartialUpdateInstancePayload{
// Acl: &postgresflex.ACL{
// Items: &[]string{
// "",
// },
// },
// BackupSchedule: nil,
// FlavorId: nil,
// Name: nil,
// Replicas: utils.Ptr(int64(2123456789)),
// Storage: &postgresflex.Storage{
// Class: nil,
// Size: nil,
// },
// Version: nil,
// },
// true,
// },
// {
// "nil_model",
// nil,
// []string{},
// &flavorModel{},
// &storageModel{},
// nil,
// false,
// },
// {
// "nil_acl",
// &Model{},
// nil,
// &flavorModel{},
// &storageModel{},
// nil,
// false,
// },
// {
// "nil_flavor",
// &Model{},
// []string{},
// nil,
// &storageModel{},
// nil,
// false,
// },
// {
// "nil_storage",
// &Model{},
// []string{},
// &flavorModel{},
// nil,
// nil,
// false,
// },
// }
// for _, tt := range tests {
// t.Run(tt.description, func(t *testing.T) {
// output, err := toUpdatePayload(tt.input, tt.inputAcl, tt.inputFlavor, tt.inputStorage)
// if !tt.isValid && err == nil {
// t.Fatalf("Should have failed")
// }
// if tt.isValid && err != nil {
// t.Fatalf("Should not have failed: %v", err)
// }
// if tt.isValid {
// diff := cmp.Diff(output, tt.expected)
// if diff != "" {
// t.Fatalf("Data does not match: %s", diff)
// }
// }
// })
// }
// }
//
// func TestLoadFlavorId(t *testing.T) {
// tests := []struct {
// description string
// inputFlavor *flavorModel
// mockedResp *postgresflex.ListFlavorsResponse
// expected *flavorModel
// getFlavorsFails bool
// isValid bool
// }{
// {
// "ok_flavor",
// &flavorModel{
// CPU: types.Int64Value(2),
// RAM: types.Int64Value(8),
// },
// &postgresflex.ListFlavorsResponse{
// Flavors: &[]postgresflex.Flavor{
// {
// Id: utils.Ptr("fid-1"),
// Cpu: utils.Ptr(int64(2)),
// Description: utils.Ptr("description"),
// Ram: utils.Ptr(int64(8)),
// },
// },
// },
// &flavorModel{
// Id: types.StringValue("fid-1"),
// Description: types.StringValue("description"),
// CPU: types.Int64Value(2),
// RAM: types.Int64Value(8),
// },
// false,
// true,
// },
// {
// "ok_flavor_2",
// &flavorModel{
// CPU: types.Int64Value(2),
// RAM: types.Int64Value(8),
// },
// &postgresflex.ListFlavorsResponse{
// Flavors: &[]postgresflex.Flavor{
// {
// Id: utils.Ptr("fid-1"),
// Cpu: utils.Ptr(int64(2)),
// Description: utils.Ptr("description"),
// Ram: utils.Ptr(int64(8)),
// },
// {
// Id: utils.Ptr("fid-2"),
// Cpu: utils.Ptr(int64(1)),
// Description: utils.Ptr("description"),
// Ram: utils.Ptr(int64(4)),
// },
// },
// },
// &flavorModel{
// Id: types.StringValue("fid-1"),
// Description: types.StringValue("description"),
// CPU: types.Int64Value(2),
// RAM: types.Int64Value(8),
// },
// false,
// true,
// },
// {
// "no_matching_flavor",
// &flavorModel{
// CPU: types.Int64Value(2),
// RAM: types.Int64Value(8),
// },
// &postgresflex.ListFlavorsResponse{
// Flavors: &[]postgresflex.Flavor{
// {
// Id: utils.Ptr("fid-1"),
// Cpu: utils.Ptr(int64(1)),
// Description: utils.Ptr("description"),
// Ram: utils.Ptr(int64(8)),
// },
// {
// Id: utils.Ptr("fid-2"),
// Cpu: utils.Ptr(int64(1)),
// Description: utils.Ptr("description"),
// Ram: utils.Ptr(int64(4)),
// },
// },
// },
// &flavorModel{
// CPU: types.Int64Value(2),
// RAM: types.Int64Value(8),
// },
// false,
// false,
// },
// {
// "nil_response",
// &flavorModel{
// CPU: types.Int64Value(2),
// RAM: types.Int64Value(8),
// },
// &postgresflex.ListFlavorsResponse{},
// &flavorModel{
// CPU: types.Int64Value(2),
// RAM: types.Int64Value(8),
// },
// false,
// false,
// },
// {
// "error_response",
// &flavorModel{
// CPU: types.Int64Value(2),
// RAM: types.Int64Value(8),
// },
// &postgresflex.ListFlavorsResponse{},
// &flavorModel{
// CPU: types.Int64Value(2),
// RAM: types.Int64Value(8),
// },
// true,
// false,
// },
// }
// for _, tt := range tests {
// t.Run(tt.description, func(t *testing.T) {
// client := &postgresFlexClientMocked{
// returnError: tt.getFlavorsFails,
// getFlavorsResp: tt.mockedResp,
// }
// model := &Model{
// ProjectId: types.StringValue("pid"),
// }
// flavorModel := &flavorModel{
// CPU: tt.inputFlavor.CPU,
// RAM: tt.inputFlavor.RAM,
// }
// err := loadFlavorId(context.Background(), client, model, flavorModel)
// if !tt.isValid && err == nil {
// t.Fatalf("Should have failed")
// }
// if tt.isValid && err != nil {
// t.Fatalf("Should not have failed: %v", err)
// }
// if tt.isValid {
// diff := cmp.Diff(flavorModel, tt.expected)
// if diff != "" {
// t.Fatalf("Data does not match: %s", diff)
// }
// }
// })
// }
// }
func TestNewInstanceResource(t *testing.T) { func TestNewInstanceResource(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
@ -639,12 +31,10 @@ func TestNewInstanceResource(t *testing.T) {
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run( t.Run(tt.name, func(t *testing.T) {
tt.name, func(t *testing.T) { if got := NewInstanceResource(); !reflect.DeepEqual(got, tt.want) {
if got := NewInstanceResource(); !reflect.DeepEqual(got, tt.want) { t.Errorf("NewInstanceResource() = %v, want %v", got, tt.want)
t.Errorf("NewInstanceResource() = %v, want %v", got, tt.want) }
} })
},
)
} }
} }