diff --git a/docs/data-sources/sqlserverflex_instance.md b/docs/data-sources/sqlserverflex_instance.md
index b13f91fa1..4fa559558 100644
--- a/docs/data-sources/sqlserverflex_instance.md
+++ b/docs/data-sources/sqlserverflex_instance.md
@@ -29,20 +29,36 @@ data "stackit_sqlserverflex_instance" "example" {
### Optional
+- `network` (Attributes) The network configuration of the instance. (see [below for nested schema](#nestedatt--network))
- `region` (String) The resource region. If not defined, the provider region is used.
### Read-Only
- `acl` (List of String) The Access Control List (ACL) for the SQLServer Flex instance.
- `backup_schedule` (String) The backup schedule. Should follow the cron scheduling system format (e.g. "0 0 * * *").
+- `edition` (String) Edition of the MSSQL server instance.
- `flavor` (Attributes) (see [below for nested schema](#nestedatt--flavor))
+- `flavor_id` (String) The flavor ID of the SQLServer Flex instance.
- `id` (String) Terraform's internal data source. ID. It is structured as "`project_id`,`region`,`instance_id`".
- `name` (String) Instance name.
- `options` (Attributes) Custom parameters for the SQLServer Flex instance. (see [below for nested schema](#nestedatt--options))
- `replicas` (Number)
+- `retention_days` (Number) The days (30 to 90) for how long the backup files should be stored before cleaned up.
- `storage` (Attributes) (see [below for nested schema](#nestedatt--storage))
- `version` (String)
+
+### Nested Schema for `network`
+
+Optional:
+
+- `access_scope` (String) The network access scope of the instance. 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.
+
+Read-Only:
+
+- `acl` (List of String) List of IPV4 cidr.
+
+
### Nested Schema for `flavor`
diff --git a/docs/resources/sqlserverflex_instance.md b/docs/resources/sqlserverflex_instance.md
index 471868583..b520e4a3b 100644
--- a/docs/resources/sqlserverflex_instance.md
+++ b/docs/resources/sqlserverflex_instance.md
@@ -18,10 +18,7 @@ resource "stackit_sqlserverflex_instance" "example" {
name = "example-instance"
acl = ["XXX.XXX.XXX.X/XX", "XX.XXX.XX.X/XX"]
backup_schedule = "00 00 * * *"
- flavor = {
- cpu = 4
- ram = 16
- }
+ flavor_id = "4.16-Single"
storage = {
class = "premium-perf2-stackit"
size = 5
@@ -35,21 +32,25 @@ resource "stackit_sqlserverflex_instance" "example" {
### Required
-- `flavor` (Attributes) (see [below for nested schema](#nestedatt--flavor))
- `name` (String) Instance name.
- `project_id` (String) STACKIT project ID to which the instance is associated.
### Optional
-- `acl` (List of String) The Access Control List (ACL) for the SQLServer Flex instance.
-- `backup_schedule` (String) The backup schedule. Should follow the cron scheduling system format (e.g. "0 0 * * *")
-- `options` (Attributes) (see [below for nested schema](#nestedatt--options))
+- `acl` (List of String, Deprecated) The Access Control List (ACL) for the SQLServer Flex instance.
+- `backup_schedule` (String) The backup schedule. Should follow the cron scheduling system format (e.g. "0 0 * * *") Will be required in the future. Set a value to prevent breaking changes.
+- `flavor` (Attributes) (see [below for nested schema](#nestedatt--flavor))
+- `flavor_id` (String) The flavor ID of the SQLServer Flex instance.
+- `network` (Attributes) The network configuration of the instance. Will be required in the future. Set a value to prevent breaking changes. (see [below for nested schema](#nestedatt--network))
+- `options` (Attributes, Deprecated) (see [below for nested schema](#nestedatt--options))
- `region` (String) The resource region. If not defined, the provider region is used.
-- `storage` (Attributes) (see [below for nested schema](#nestedatt--storage))
-- `version` (String)
+- `retention_days` (Number) The days (30 to 90) for how long the backup files should be stored before cleaned up. Will be required in the future. Set a value to prevent breaking changes.
+- `storage` (Attributes) The object containing information about the storage size and class. Will be required in the future. Set a value to prevent breaking changes. (see [below for nested schema](#nestedatt--storage))
+- `version` (String) The sqlserver version used for the instance. Possible values are: `2022`. Will be required in the future. Set a value to prevent breaking changes.
### Read-Only
+- `edition` (String) Edition of the MSSQL server instance.
- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`region`,`instance_id`".
- `instance_id` (String) ID of the SQLServer Flex instance.
- `replicas` (Number)
@@ -68,16 +69,25 @@ Read-Only:
- `id` (String)
+
+### Nested Schema for `network`
+
+Optional:
+
+- `access_scope` (String) The network access scope of the instance. 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. Possible values are: `PUBLIC`, `SNA`.
+- `acl` (List of String) List of IPV4 cidr.
+
+
### Nested Schema for `options`
Optional:
-- `retention_days` (Number)
+- `retention_days` (Number, Deprecated)
Read-Only:
-- `edition` (String)
+- `edition` (String, Deprecated)
@@ -88,8 +98,8 @@ Optional:
- `class` (String) The storage class. You can list available storage classes using the [STACKIT CLI](https://github.com/stackitcloud/stackit-cli):
```bash
stackit beta sqlserverflex options --storages --flavor-id FLAVOR_ID
-```
-- `size` (Number)
+``` Will be required in the future. Set a value to prevent breaking changes.
+- `size` (Number) The storage size in Gigabytes. Will be required in the future. Set a value to prevent breaking changes.
## Import
diff --git a/examples/resources/stackit_sqlserverflex_instance/resource.tf b/examples/resources/stackit_sqlserverflex_instance/resource.tf
index 463e36fa0..45bb93b54 100644
--- a/examples/resources/stackit_sqlserverflex_instance/resource.tf
+++ b/examples/resources/stackit_sqlserverflex_instance/resource.tf
@@ -3,10 +3,7 @@ resource "stackit_sqlserverflex_instance" "example" {
name = "example-instance"
acl = ["XXX.XXX.XXX.X/XX", "XX.XXX.XX.X/XX"]
backup_schedule = "00 00 * * *"
- flavor = {
- cpu = 4
- ram = 16
- }
+ flavor_id = "4.16-Single"
storage = {
class = "premium-perf2-stackit"
size = 5
diff --git a/go.mod b/go.mod
index f25089fce..96ea610b3 100644
--- a/go.mod
+++ b/go.mod
@@ -44,7 +44,7 @@ require (
github.com/stackitcloud/stackit-sdk-go/services/serviceenablement v1.7.0
github.com/stackitcloud/stackit-sdk-go/services/sfs v0.11.0
github.com/stackitcloud/stackit-sdk-go/services/ske v1.18.0
- github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.13.0
+ github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.16.0
github.com/stackitcloud/stackit-sdk-go/services/telemetrylink v0.2.0
github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter v0.3.0
github.com/stackitcloud/stackit-sdk-go/services/vpn v0.14.0
diff --git a/go.sum b/go.sum
index 1f54e5dc8..1987e89dd 100644
--- a/go.sum
+++ b/go.sum
@@ -734,8 +734,8 @@ github.com/stackitcloud/stackit-sdk-go/services/sfs v0.11.0 h1:YhQ2GF+AyHVUps1iE
github.com/stackitcloud/stackit-sdk-go/services/sfs v0.11.0/go.mod h1:jMlBoXqrPNX5nXbo6oT7exalqilw1jiLPoIp4Cn0CdI=
github.com/stackitcloud/stackit-sdk-go/services/ske v1.18.0 h1:6Umom7V12bVa7D3dnMJAKj/GWg7wqR9FpqmZ8TjsO1Q=
github.com/stackitcloud/stackit-sdk-go/services/ske v1.18.0/go.mod h1:TbqmZhLMofmfl+HhVl6oHYcI3zvXTm1vRjN3A/fOkM4=
-github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.13.0 h1:w7l3Z7lkDKnJ+3rudGjZ8GMKxGmpIS5dT6pO9MMNWqQ=
-github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.13.0/go.mod h1:AiUoMAqQcOlMgDtkVJlqI7P/VGD5xjN3dYjERGnwN/M=
+github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.16.0 h1:A6zN5JUz5DOTigTxAyAC+4p5VpJzaYDftx2yIH0mgn8=
+github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.16.0/go.mod h1:AiUoMAqQcOlMgDtkVJlqI7P/VGD5xjN3dYjERGnwN/M=
github.com/stackitcloud/stackit-sdk-go/services/telemetrylink v0.2.0 h1:U1mQoCk0TXc8NsSc/Sl9PKMdEyJpWNU2zLnsqmx6wEc=
github.com/stackitcloud/stackit-sdk-go/services/telemetrylink v0.2.0/go.mod h1:hgw8janWmDfP2bnuZensxqcAePr49BX5ug8Rq85o+h8=
github.com/stackitcloud/stackit-sdk-go/services/telemetryrouter v0.3.0 h1:MEvzGItcbig+9A4JvK2E5W6/mqXDPafiGkDZ1BprBAI=
diff --git a/stackit/internal/services/observability/instance/resource.go b/stackit/internal/services/observability/instance/resource.go
index 18fdcdc67..f7e2a9eda 100644
--- a/stackit/internal/services/observability/instance/resource.go
+++ b/stackit/internal/services/observability/instance/resource.go
@@ -540,7 +540,7 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.Int32{
- int32planmodifier.UseStateForUnknownIf(int32planmodifier.Int32Changed, "metrics_retention_days", "sets `UseStateForUnknown` only if `metrics_retention_days` has not changed"),
+ int32planmodifier.UseStateForUnknownIf(int32planmodifier.Int32Changed(path.Root("metrics_retention_days")), "sets `UseStateForUnknown` only if `metrics_retention_days` has not changed"),
},
},
"metrics_retention_days_5m_downsampling": schema.Int32Attribute{
@@ -548,7 +548,7 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.Int32{
- int32planmodifier.UseStateForUnknownIf(int32planmodifier.Int32Changed, "metrics_retention_days_5m_downsampling", "sets `UseStateForUnknown` only if `metrics_retention_days_5m_downsampling` has not changed"),
+ int32planmodifier.UseStateForUnknownIf(int32planmodifier.Int32Changed(path.Root("metrics_retention_days_5m_downsampling")), "sets `UseStateForUnknown` only if `metrics_retention_days_5m_downsampling` has not changed"),
},
},
"metrics_retention_days_1h_downsampling": schema.Int32Attribute{
@@ -556,7 +556,7 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.Int32{
- int32planmodifier.UseStateForUnknownIf(int32planmodifier.Int32Changed, "metrics_retention_days_1h_downsampling", "sets `UseStateForUnknown` only if `metrics_retention_days_1h_downsampling` has not changed"),
+ int32planmodifier.UseStateForUnknownIf(int32planmodifier.Int32Changed(path.Root("metrics_retention_days_1h_downsampling")), "sets `UseStateForUnknown` only if `metrics_retention_days_1h_downsampling` has not changed"),
},
},
"metrics_url": schema.StringAttribute{
diff --git a/stackit/internal/services/sqlserverflex/instance/datasource.go b/stackit/internal/services/sqlserverflex/instance/datasource.go
index 973e7f70c..e3807d012 100644
--- a/stackit/internal/services/sqlserverflex/instance/datasource.go
+++ b/stackit/internal/services/sqlserverflex/instance/datasource.go
@@ -19,7 +19,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
- sqlserverflex "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v2api"
+ sqlserverflex "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v3api"
)
// Ensure the implementation satisfies the expected interfaces.
@@ -62,15 +62,21 @@ 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) {
descriptions := map[string]string{
- "main": "SQLServer 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 SQLServer Flex instance.",
- "project_id": "STACKIT project ID to which the instance is associated.",
- "name": "Instance name.",
- "acl": "The Access Control List (ACL) for the SQLServer Flex instance.",
- "backup_schedule": `The backup schedule. Should follow the cron scheduling system format (e.g. "0 0 * * *").`,
- "options": "Custom parameters for the SQLServer Flex instance.",
- "region": "The resource region. If not defined, the provider region is used.",
+ "main": "SQLServer 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 SQLServer Flex instance.",
+ "project_id": "STACKIT project ID to which the instance is associated.",
+ "name": "Instance name.",
+ "acl": "The Access Control List (ACL) for the SQLServer Flex instance.",
+ "backup_schedule": `The backup schedule. Should follow the cron scheduling system format (e.g. "0 0 * * *").`,
+ "options": "Custom parameters for the SQLServer Flex instance.",
+ "flavor_id": "The flavor ID of the SQLServer Flex instance.",
+ "network": "The network configuration of the instance.",
+ "network.access_scope": "The network access scope of the instance. 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.",
+ "network.acl": "List of IPV4 cidr.",
+ "retention_days": "The days (30 to 90) for how long the backup files should be stored before cleaned up.",
+ "edition": "Edition of the MSSQL server instance.",
+ "region": "The resource region. If not defined, the provider region is used.",
}
resp.Schema = schema.Schema{
@@ -118,14 +124,33 @@ func (r *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaReques
"description": schema.StringAttribute{
Computed: true,
},
- "cpu": schema.Int32Attribute{
+ "cpu": schema.Int64Attribute{
Computed: true,
},
- "ram": schema.Int32Attribute{
+ "ram": schema.Int64Attribute{
Computed: true,
},
},
},
+ "flavor_id": schema.StringAttribute{
+ Description: descriptions["flavor_id"],
+ Computed: true,
+ },
+ "network": schema.SingleNestedAttribute{
+ Description: descriptions["network"],
+ Optional: true,
+ Attributes: map[string]schema.Attribute{
+ "access_scope": schema.StringAttribute{
+ Description: descriptions["network.access_scope"],
+ Optional: true,
+ },
+ "acl": schema.ListAttribute{
+ Description: descriptions["network.acl"],
+ ElementType: types.StringType,
+ Computed: true,
+ },
+ },
+ },
"replicas": schema.Int32Attribute{
Computed: true,
},
@@ -143,6 +168,10 @@ func (r *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaReques
"version": schema.StringAttribute{
Computed: true,
},
+ "edition": schema.StringAttribute{
+ Description: descriptions["edition"],
+ Computed: true,
+ },
"options": schema.SingleNestedAttribute{
Description: descriptions["options"],
Computed: true,
@@ -150,11 +179,15 @@ func (r *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaReques
"edition": schema.StringAttribute{
Computed: true,
},
- "retention_days": schema.Int64Attribute{
+ "retention_days": schema.Int32Attribute{
Computed: true,
},
},
},
+ "retention_days": schema.Int32Attribute{
+ Description: descriptions["retention_days"],
+ Computed: true,
+ },
"region": schema.StringAttribute{
// the region cannot be found, so it has to be passed
Optional: true,
@@ -181,7 +214,7 @@ func (r *instanceDataSource) Read(ctx context.Context, req datasource.ReadReques
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "instance_id", instanceId)
ctx = tflog.SetField(ctx, "region", region)
- instanceResp, err := r.client.DefaultAPI.GetInstance(ctx, projectId, instanceId, region).Execute()
+ instanceResp, err := r.client.DefaultAPI.GetInstance(ctx, projectId, region, instanceId).Execute()
if err != nil {
utils.LogError(
ctx,
@@ -199,22 +232,6 @@ func (r *instanceDataSource) Read(ctx context.Context, req datasource.ReadReques
ctx = core.LogResponse(ctx)
- 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
- }
- }
- 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 options = &optionsModel{}
if !(model.Options.IsNull() || model.Options.IsUnknown()) {
diags = model.Options.As(ctx, options, basetypes.ObjectAsOptions{})
@@ -224,7 +241,19 @@ func (r *instanceDataSource) Read(ctx context.Context, req datasource.ReadReques
}
}
- err = mapFields(ctx, instanceResp, &model, flavor, storage, options, region)
+ flavorResp, err := getFlavor(ctx, r.client.DefaultAPI, projectId, region, instanceResp.FlavorId)
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Finding flavor: %v", err))
+ return
+ }
+ flavor := &flavorModel{
+ Id: types.StringValue(flavorResp.Id),
+ Description: types.StringValue(flavorResp.Description),
+ CPU: types.Int64Value(flavorResp.Cpu),
+ RAM: types.Int64Value(flavorResp.Memory),
+ }
+
+ err = mapFields(ctx, instanceResp, &model, flavor, 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/sqlserverflex/instance/resource.go b/stackit/internal/services/sqlserverflex/instance/resource.go
index 100493d18..71a9a5ce0 100644
--- a/stackit/internal/services/sqlserverflex/instance/resource.go
+++ b/stackit/internal/services/sqlserverflex/instance/resource.go
@@ -6,13 +6,20 @@ import (
"fmt"
"net/http"
"regexp"
- "strconv"
"strings"
"time"
+ "github.com/hashicorp/terraform-plugin-framework-validators/int32validator"
+ "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
+ "github.com/hashicorp/terraform-plugin-framework-validators/objectvalidator"
+ "github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int32planmodifier"
+ sdkUtils "github.com/stackitcloud/stackit-sdk-go/core/utils"
sqlserverflexUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/sqlserverflex/utils"
+ int32planmodifier2 "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils/planmodifiers/int32planmodifier"
+ listplanmodifier2 "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils/planmodifiers/listplanmodifier"
+ objectplanmodifier2 "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils/planmodifiers/objectplanmodifier"
stringplanmodifierCustom "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils/planmodifiers/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
@@ -30,14 +37,13 @@ import (
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
- "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
- sqlserverflex "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v2api"
- "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v2api/wait"
+ sqlserverflex "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v3api"
+ "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v3api/wait"
)
// Ensure the implementation satisfies the expected interfaces.
@@ -49,34 +55,52 @@ var (
)
type Model struct {
- Id types.String `tfsdk:"id"` // needed by TF
- InstanceId types.String `tfsdk:"instance_id"`
- ProjectId types.String `tfsdk:"project_id"`
- Name types.String `tfsdk:"name"`
+ Id types.String `tfsdk:"id"` // needed by TF
+ InstanceId types.String `tfsdk:"instance_id"`
+ ProjectId types.String `tfsdk:"project_id"`
+ Name types.String `tfsdk:"name"`
+ // Deprecated: ACL is deprecated and will be removed after January 2027
ACL types.List `tfsdk:"acl"`
BackupSchedule types.String `tfsdk:"backup_schedule"`
Flavor types.Object `tfsdk:"flavor"`
+ FlavorId types.String `tfsdk:"flavor_id"`
Storage types.Object `tfsdk:"storage"`
Version types.String `tfsdk:"version"`
Replicas types.Int32 `tfsdk:"replicas"`
- Options types.Object `tfsdk:"options"`
- Region types.String `tfsdk:"region"`
+ Edition types.String `tfsdk:"edition"`
+ // Deprecated: Options is deprecated and will be removed after January 2027
+ Options types.Object `tfsdk:"options"`
+ RetentionDays types.Int32 `tfsdk:"retention_days"`
+ Network types.Object `tfsdk:"network"`
+ Region types.String `tfsdk:"region"`
+}
+
+// Struct corresponding to Model.Network
+type networkModel struct {
+ AccessScope types.String `tfsdk:"access_scope"`
+ Acl types.List `tfsdk:"acl"`
+}
+
+// types corresponding to Network
+var networkTypes = map[string]attr.Type{
+ "access_scope": basetypes.StringType{},
+ "acl": basetypes.ListType{ElemType: types.StringType},
}
// Struct corresponding to Model.Flavor
type flavorModel struct {
Id types.String `tfsdk:"id"`
Description types.String `tfsdk:"description"`
- CPU types.Int32 `tfsdk:"cpu"`
- RAM types.Int32 `tfsdk:"ram"`
+ CPU types.Int64 `tfsdk:"cpu"`
+ RAM types.Int64 `tfsdk:"ram"`
}
// Types corresponding to flavorModel
var flavorTypes = map[string]attr.Type{
"id": basetypes.StringType{},
"description": basetypes.StringType{},
- "cpu": basetypes.Int32Type{},
- "ram": basetypes.Int32Type{},
+ "cpu": basetypes.Int64Type{},
+ "ram": basetypes.Int64Type{},
}
// Struct corresponding to Model.Storage
@@ -94,13 +118,13 @@ var storageTypes = map[string]attr.Type{
// Struct corresponding to Model.Options
type optionsModel struct {
Edition types.String `tfsdk:"edition"`
- RetentionDays types.Int64 `tfsdk:"retention_days"`
+ RetentionDays types.Int32 `tfsdk:"retention_days"`
}
// Types corresponding to optionsModel
var optionsTypes = map[string]attr.Type{
"edition": basetypes.StringType{},
- "retention_days": basetypes.Int64Type{},
+ "retention_days": basetypes.Int32Type{},
}
// NewInstanceResource is a helper function to simplify the provider implementation.
@@ -159,25 +183,135 @@ func (r *instanceResource) ModifyPlan(ctx context.Context, req resource.ModifyPl
return
}
+ handleV3Migration(ctx, &planModel, &configModel, resp)
+
resp.Diagnostics.Append(resp.Plan.Set(ctx, planModel)...)
if resp.Diagnostics.HasError() {
return
}
}
+func handleV3Migration(ctx context.Context, planModel, configModel *Model, resp *resource.ModifyPlanResponse) {
+ // backup_schedule
+ if configModel.BackupSchedule.IsNull() || configModel.BackupSchedule.IsUnknown() {
+ if planModel.BackupSchedule.IsNull() || planModel.BackupSchedule.IsUnknown() {
+ planModel.BackupSchedule = types.StringValue("0 0 * * *")
+ }
+ resp.Diagnostics.AddAttributeWarning(path.Root("backup_schedule"),
+ "backup_schedule will be required in future", "backup_schedule will be a required field after January 2027. Set a value to prevent breaking changes. Fallback to '0 0 * * *' during deprecation period.")
+ }
+
+ // storage
+ if configModel.Storage.IsNull() || configModel.Storage.IsUnknown() {
+ if planModel.Storage.IsNull() || planModel.Storage.IsUnknown() {
+ planModel.Storage = types.ObjectValueMust(storageTypes, map[string]attr.Value{
+ "class": types.StringValue("premium-perf12-stackit"),
+ "size": types.Int64Value(40),
+ })
+ }
+ resp.Diagnostics.AddAttributeWarning(path.Root("storage"),
+ "storage will be required in future", "storage will be a required field after January 2027. Set values to prevent breaking changes. Fallback to class 'premium-perf12-stackit' with a size of 40 gigabytes during deprecation period.")
+ } else {
+ var storageConfig = &storageModel{}
+ resp.Diagnostics.Append(configModel.Storage.As(ctx, storageConfig, basetypes.ObjectAsOptions{})...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ var storagePlan = &storageModel{}
+ resp.Diagnostics.Append(configModel.Storage.As(ctx, storagePlan, basetypes.ObjectAsOptions{})...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ // storage.class
+ if storageConfig.Class.IsNull() || storageConfig.Class.ValueString() == "" {
+ if storagePlan.Class.IsNull() || storagePlan.Class.ValueString() == "" {
+ storagePlan.Class = types.StringValue("premium-perf12-stackit")
+ }
+ resp.Diagnostics.AddAttributeWarning(path.Root("storage.class"),
+ "storage.class will be required in future", "storage.class will be a required field after January 2027. Set a value to prevent breaking changes. Fallback to 'premium-perf12-stackit' during deprecation period.")
+ }
+
+ // storage.size
+ if storageConfig.Size.IsNull() {
+ if storagePlan.Size.IsNull() {
+ storagePlan.Size = types.Int64Value(40)
+ }
+ resp.Diagnostics.AddAttributeWarning(path.Root("storage.size"),
+ "storage.size will be required in future", "storage.size will be a required field after January 2027. Set a value to prevent breaking changes. Fallback to 40 gigabytes during deprecation period.")
+ }
+
+ var diags diag.Diagnostics
+ planModel.Storage, diags = types.ObjectValue(storageTypes, map[string]attr.Value{
+ "class": storagePlan.Class,
+ "size": storagePlan.Size,
+ })
+ resp.Diagnostics.Append(diags...)
+ }
+
+ // version
+ if configModel.Version.IsNull() || configModel.Version.IsUnknown() {
+ if planModel.Version.IsNull() || planModel.Version.IsUnknown() {
+ planModel.Version = types.StringValue(string(sqlserverflex.INSTANCEVERSION__2022))
+ }
+ resp.Diagnostics.AddAttributeWarning(path.Root("version"),
+ "version will be required in future", "version will be a required field after January 2027. Set a value to prevent breaking changes. Fallback to '2022' during deprecation period.")
+ }
+
+ // acl
+ if (configModel.ACL.IsNull() || configModel.ACL.IsUnknown()) && (configModel.Network.IsNull() || configModel.Network.IsUnknown()) {
+ // Not setting default ACL and scope to the configModel, instead we send an empty array to the API, where they set the default value.
+ resp.Diagnostics.AddAttributeWarning(path.Root("network").AtName("acl"),
+ "network.acl will be required in future", "network.acl will be a required field after January 2027. Set values to prevent breaking changes.")
+ }
+
+ // retention_days
+ var optionsConfig = &optionsModel{}
+ if !(configModel.Options.IsNull() || configModel.Options.IsUnknown()) {
+ resp.Diagnostics.Append(configModel.Options.As(ctx, optionsConfig, basetypes.ObjectAsOptions{})...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ }
+ var optionsPlan = &optionsModel{}
+ if !(configModel.Options.IsNull() || configModel.Options.IsUnknown()) {
+ resp.Diagnostics.Append(configModel.Options.As(ctx, optionsPlan, basetypes.ObjectAsOptions{})...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ }
+ if (optionsConfig.RetentionDays.IsNull() || optionsConfig.RetentionDays.IsUnknown()) && (configModel.RetentionDays.IsNull() || configModel.RetentionDays.IsUnknown()) {
+ if (optionsPlan.RetentionDays.IsNull() || optionsPlan.RetentionDays.IsUnknown()) && (planModel.RetentionDays.IsNull() || planModel.RetentionDays.IsUnknown()) {
+ planModel.RetentionDays = types.Int32Value(30)
+ }
+ resp.Diagnostics.AddAttributeWarning(path.Root("retention_days"),
+ "retention_days will be required in future", "retention_days will be a required field after January 2027. Set a value to prevent breaking changes. Fallback to 30 days during deprecation period.")
+ }
+}
+
// Schema defines the schema for the resource.
func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
+ willBeRequired := " Will be required in the future. Set a value to prevent breaking changes."
descriptions := map[string]string{
- "main": "SQLServer 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 SQLServer Flex instance.",
- "project_id": "STACKIT project ID to which the instance is associated.",
- "name": "Instance name.",
- "acl": "The Access Control List (ACL) for the SQLServer Flex instance.",
- "backup_schedule": `The backup schedule. Should follow the cron scheduling system format (e.g. "0 0 * * *")`,
- "options": "Custom parameters for the SQLServer Flex instance.",
- "region": "The resource region. If not defined, the provider region is used.",
- "storage_class": "The storage class. You can list available storage classes using the [STACKIT CLI](https://github.com/stackitcloud/stackit-cli):\n```bash\nstackit beta sqlserverflex options --storages --flavor-id FLAVOR_ID\n```",
+ "main": "SQLServer 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 SQLServer Flex instance.",
+ "project_id": "STACKIT project ID to which the instance is associated.",
+ "name": "Instance name.",
+ "acl": "The Access Control List (ACL) for the SQLServer Flex instance.",
+ "backup_schedule": `The backup schedule. Should follow the cron scheduling system format (e.g. "0 0 * * *")` + willBeRequired,
+ "options": "Custom parameters for the SQLServer Flex instance.",
+ "flavor_id": "The flavor ID of the SQLServer Flex instance.",
+ "network": "The network configuration of the instance." + willBeRequired,
+ "network.access_scope": "The network access scope of the instance. 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.",
+ "network.acl": "List of IPV4 cidr." + willBeRequired,
+ "retention_days": "The days (30 to 90) for how long the backup files should be stored before cleaned up." + willBeRequired,
+ "edition": "Edition of the MSSQL server instance.",
+ "region": "The resource region. If not defined, the provider region is used.",
+ "storage": "The object containing information about the storage size and class." + willBeRequired,
+ "storage.class": "The storage class. You can list available storage classes using the [STACKIT CLI](https://github.com/stackitcloud/stackit-cli):\n```bash\nstackit beta sqlserverflex options --storages --flavor-id FLAVOR_ID\n```" + willBeRequired,
+ "storage.size": "The storage size in Gigabytes." + willBeRequired,
+ "version": "The sqlserver version used for the instance. " + utils.FormatPossibleValues(sdkUtils.EnumSliceToStringSlice(sqlserverflex.AllowedInstanceVersionEnumValues)...) + willBeRequired,
}
resp.Schema = schema.Schema{
@@ -224,12 +358,18 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r
},
},
"acl": schema.ListAttribute{
- Description: descriptions["acl"],
- ElementType: types.StringType,
- Optional: true,
- Computed: true,
+ Description: descriptions["acl"],
+ DeprecationMessage: "acl is deprecated and will be removed after January 2027. Use instead `network.acl`.",
+ ElementType: types.StringType,
+ Optional: true,
+ Computed: true,
PlanModifiers: []planmodifier.List{
- listplanmodifier.UseStateForUnknown(),
+ listplanmodifier2.UseStateForUnknownIf(listplanmodifier2.ListChanged(path.Root("network").AtName("acl")), "sets `UseStateForUnknown` only if `network.acl` has not changed"),
+ },
+ Validators: []validator.List{
+ listvalidator.ConflictsWith(
+ path.Root("network").AtName("acl").Expression(),
+ ),
},
},
"backup_schedule": schema.StringAttribute{
@@ -242,7 +382,8 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r
},
},
"flavor": schema.SingleNestedAttribute{
- Required: true,
+ Computed: true,
+ Optional: true,
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Computed: true,
@@ -256,13 +397,64 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r
stringplanmodifier.UseStateForUnknown(),
},
},
- "cpu": schema.Int32Attribute{
+ "cpu": schema.Int64Attribute{
Required: true,
},
- "ram": schema.Int32Attribute{
+ "ram": schema.Int64Attribute{
Required: true,
},
},
+ Validators: []validator.Object{
+ objectvalidator.ExactlyOneOf(
+ path.Root("flavor_id").Expression(),
+ path.Root("flavor").Expression(),
+ ),
+ },
+ },
+ "flavor_id": schema.StringAttribute{
+ Description: descriptions["flavor_id"],
+ Computed: true,
+ Optional: true,
+ Validators: []validator.String{
+ stringvalidator.ExactlyOneOf(
+ path.Root("flavor_id").Expression(),
+ path.Root("flavor").Expression(),
+ ),
+ },
+ },
+ "network": schema.SingleNestedAttribute{
+ Description: descriptions["network"],
+ Computed: true,
+ Optional: true,
+ PlanModifiers: []planmodifier.Object{
+ objectplanmodifier2.UseStateForUnknownIf(objectplanmodifier2.ListChanged(path.Root("acl")), "sets `UseStateForUnknown` only if `acl` has not changed"),
+ },
+ Attributes: map[string]schema.Attribute{
+ "access_scope": schema.StringAttribute{
+ Description: "The network access scope of the instance. 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. " + utils.FormatPossibleValues(sdkUtils.EnumSliceToStringSlice(sqlserverflex.AllowedInstanceNetworkAccessScopeEnumValues)...),
+ Computed: true,
+ Optional: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ stringplanmodifier.UseStateForUnknown(),
+ },
+ },
+ "acl": schema.ListAttribute{
+ Description: "List of IPV4 cidr.",
+ ElementType: types.StringType,
+ Optional: true,
+ Computed: true,
+ PlanModifiers: []planmodifier.List{
+ listplanmodifier2.UseStateForUnknownIf(listplanmodifier2.ListChanged(path.Root("acl")), "sets `UseStateForUnknown` only if `acl` has not changed"),
+ },
+ Validators: []validator.List{
+ listvalidator.ConflictsWith(
+ path.Root("acl").Expression(),
+ ),
+ listvalidator.SizeAtLeast(1),
+ },
+ },
+ },
},
"replicas": schema.Int32Attribute{
Computed: true,
@@ -271,15 +463,16 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r
},
},
"storage": schema.SingleNestedAttribute{
- Optional: true,
- Computed: true,
+ Description: descriptions["storage"],
+ Optional: true,
+ Computed: true,
PlanModifiers: []planmodifier.Object{
objectplanmodifier.RequiresReplace(),
objectplanmodifier.UseStateForUnknown(),
},
Attributes: map[string]schema.Attribute{
"class": schema.StringAttribute{
- Description: descriptions["storage_class"],
+ Description: descriptions["storage.class"],
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.String{
@@ -288,8 +481,9 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r
},
},
"size": schema.Int64Attribute{
- Optional: true,
- Computed: true,
+ Description: descriptions["storage.size"],
+ Optional: true,
+ Computed: true,
PlanModifiers: []planmodifier.Int64{
int64planmodifier.RequiresReplace(),
int64planmodifier.UseStateForUnknown(),
@@ -298,37 +492,63 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r
},
},
"version": schema.StringAttribute{
- Optional: true,
- Computed: true,
+ Description: descriptions["version"],
+ Optional: true,
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
+ },
+ "edition": schema.StringAttribute{
+ Description: descriptions["edition"],
+ Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"options": schema.SingleNestedAttribute{
- Optional: true,
- Computed: true,
+ DeprecationMessage: "option is deprecated and will be removed after January 2027.",
+ Optional: true,
+ Computed: true,
PlanModifiers: []planmodifier.Object{
- objectplanmodifier.RequiresReplace(),
- objectplanmodifier.UseStateForUnknown(),
+ objectplanmodifier2.UseStateForUnknownIf(objectplanmodifier2.Int32Changed(path.Root("retention_days")), "sets `UseStateForUnknown` only if `retention_days` has not changed"),
},
Attributes: map[string]schema.Attribute{
"edition": schema.StringAttribute{
- Computed: true,
+ DeprecationMessage: "edition is deprecated and will be removed after January 2027.",
+ Computed: true,
PlanModifiers: []planmodifier.String{
- stringplanmodifier.RequiresReplace(),
stringplanmodifier.UseStateForUnknown(),
},
},
- "retention_days": schema.Int64Attribute{
- Optional: true,
- Computed: true,
- PlanModifiers: []planmodifier.Int64{
- int64planmodifier.RequiresReplace(),
- int64planmodifier.UseStateForUnknown(),
+ "retention_days": schema.Int32Attribute{
+ DeprecationMessage: "retention_days is deprecated and will be removed after January 2027. Use instead `retention_days` from root.",
+ Optional: true,
+ Computed: true,
+ PlanModifiers: []planmodifier.Int32{
+ int32planmodifier2.UseStateForUnknownIf(int32planmodifier2.Int32Changed(path.Root("retention_days")), "sets `UseStateForUnknown` only if `retention_days` has not changed"),
+ },
+ Validators: []validator.Int32{
+ int32validator.ConflictsWith(
+ path.Root("retention_days").Expression(),
+ ),
},
},
},
},
+ "retention_days": schema.Int32Attribute{
+ Description: descriptions["retention_days"],
+ Optional: true,
+ Computed: true,
+ PlanModifiers: []planmodifier.Int32{
+ int32planmodifier2.UseStateForUnknownIf(int32planmodifier2.Int32Changed(path.Root("options").AtName("retention_days")), "sets `UseStateForUnknown` only if `options.retention_days` has not changed"),
+ },
+ Validators: []validator.Int32{
+ int32validator.ConflictsWith(
+ path.Root("options").AtName("retention_days").Expression(),
+ ),
+ },
+ },
"region": schema.StringAttribute{
Optional: true,
// must be computed to allow for storing the override value from the provider
@@ -398,8 +618,17 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques
}
}
+ 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
+ }
+ }
+
// Generate API request body from model
- payload, err := toCreatePayload(&model, acl, flavor, storage, options)
+ payload, err := toCreatePayload(&model, acl, flavor, storage, options, network)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Creating API payload: %v", err))
return
@@ -413,12 +642,12 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques
ctx = core.LogResponse(ctx)
- if createResp.Id == nil {
+ if createResp.Id == "" {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", "Got empty instance id")
return
}
- instanceId := *createResp.Id
+ instanceId := createResp.Id
// Write id attributes to state before polling via the wait handler - just in case anything goes wrong during the wait handler
ctx = utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{
"project_id": projectId,
@@ -430,14 +659,14 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques
}
// The creation waiter sometimes returns an error from the API: "instance with id xxx has unexpected status Failure"
// which can be avoided by sleeping before wait
- waitResp, err := wait.CreateInstanceWaitHandler(ctx, r.client.DefaultAPI, projectId, instanceId, region).SetSleepBeforeWait(30 * time.Second).WaitWithContext(ctx)
+ waitResp, err := wait.CreateInstanceWaitHandler(ctx, r.client.DefaultAPI, projectId, region, instanceId).SetSleepBeforeWait(30 * time.Second).WaitWithContext(ctx)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Instance creation waiting: %v", err))
return
}
// Map response body to schema
- err = mapFields(ctx, waitResp, &model, flavor, storage, options, region)
+ err = mapFields(ctx, waitResp, &model, flavor, region)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Processing API payload: %v", err))
return
@@ -449,9 +678,13 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques
return
}
- // After the instance creation, database might not be ready to accept connections immediately.
- // That is why we add a sleep
- time.Sleep(120 * time.Second)
+ select {
+ case <-ctx.Done():
+ return
+ // After the instance creation, database might not be ready to accept connections immediately. That is why we add a sleep
+ case <-time.After(120 * time.Second):
+ // continue
+ }
tflog.Info(ctx, "SQLServer Flex instance created")
}
@@ -480,47 +713,43 @@ func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r
ctx = tflog.SetField(ctx, "instance_id", instanceId)
ctx = tflog.SetField(ctx, "region", region)
- 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
- }
- }
- 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() {
+ instanceResp, err := r.client.DefaultAPI.GetInstance(ctx, projectId, region, instanceId).Execute()
+ if err != nil {
+ if oapiErr, ok := errors.AsType[*oapierror.GenericOpenAPIError](err); ok && oapiErr.StatusCode == http.StatusNotFound {
+ resp.State.RemoveResource(ctx)
return
}
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", err.Error())
+ return
}
- var options = &optionsModel{}
- if !(model.Options.IsNull() || model.Options.IsUnknown()) {
- diags = model.Options.As(ctx, options, basetypes.ObjectAsOptions{})
+ ctx = core.LogResponse(ctx)
+
+ // Get flavor
+ 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
}
- }
-
- instanceResp, err := r.client.DefaultAPI.GetInstance(ctx, projectId, instanceId, region).Execute()
- if err != nil {
- var oapiErr *oapierror.GenericOpenAPIError
- if errors.As(err, &oapiErr) && oapiErr.StatusCode == http.StatusNotFound {
- resp.State.RemoveResource(ctx)
+ } else {
+ // Read the flavor here from the API, because during an import the flavor should be set
+ flavorResp, err := getFlavor(ctx, r.client.DefaultAPI, projectId, region, instanceResp.FlavorId)
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Finding flavor: %v", err))
return
}
- core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", err.Error())
- return
+ flavor = &flavorModel{
+ Id: types.StringValue(flavorResp.Id),
+ Description: types.StringValue(flavorResp.Description),
+ CPU: types.Int64Value(flavorResp.Cpu),
+ RAM: types.Int64Value(flavorResp.Memory),
+ }
}
- ctx = core.LogResponse(ctx)
-
// Map response body to schema
- err = mapFields(ctx, instanceResp, &model, flavor, storage, options, region)
+ err = mapFields(ctx, instanceResp, &model, flavor, region)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Processing API payload: %v", err))
return
@@ -593,14 +822,23 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques
}
}
+ 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
+ }
+ }
+
// Generate API request body from model
- payload, err := toUpdatePayload(&model, acl, flavor)
+ payload, err := toUpdatePayload(&model, acl, flavor, storage, options, network)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Creating API payload: %v", err))
return
}
// Update existing instance
- _, err = r.client.DefaultAPI.PartialUpdateInstance(ctx, projectId, instanceId, region).PartialUpdateInstancePayload(*payload).Execute()
+ err = r.client.DefaultAPI.PartialUpdateInstance(ctx, projectId, region, instanceId).PartialUpdateInstancePayload(*payload).Execute()
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", err.Error())
return
@@ -608,14 +846,14 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques
ctx = core.LogResponse(ctx)
- waitResp, err := wait.UpdateInstanceWaitHandler(ctx, r.client.DefaultAPI, projectId, instanceId, region).WaitWithContext(ctx)
+ waitResp, err := wait.UpdateInstanceWaitHandler(ctx, r.client.DefaultAPI, projectId, region, instanceId).WaitWithContext(ctx)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Instance update waiting: %v", err))
return
}
// Map response body to schema
- err = mapFields(ctx, waitResp, &model, flavor, storage, options, region)
+ err = mapFields(ctx, waitResp, &model, flavor, region)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Processing API payload: %v", err))
return
@@ -648,10 +886,9 @@ func (r *instanceResource) Delete(ctx context.Context, req resource.DeleteReques
ctx = tflog.SetField(ctx, "region", region)
// Delete existing instance
- err := r.client.DefaultAPI.DeleteInstance(ctx, projectId, instanceId, region).Execute()
+ err := r.client.DefaultAPI.DeleteInstance(ctx, projectId, region, instanceId).Execute()
if err != nil {
- var oapiErr *oapierror.GenericOpenAPIError
- if errors.As(err, &oapiErr) && oapiErr.StatusCode == http.StatusNotFound {
+ if oapiErr, ok := errors.AsType[*oapierror.GenericOpenAPIError](err); ok && oapiErr.StatusCode == http.StatusNotFound {
resp.State.RemoveResource(ctx)
return
}
@@ -661,7 +898,7 @@ func (r *instanceResource) Delete(ctx context.Context, req resource.DeleteReques
ctx = core.LogResponse(ctx)
- _, err = wait.DeleteInstanceWaitHandler(ctx, r.client.DefaultAPI, projectId, instanceId, region).WaitWithContext(ctx)
+ _, err = wait.DeleteInstanceWaitHandler(ctx, r.client.DefaultAPI, projectId, region, instanceId).WaitWithContext(ctx)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting instance", fmt.Sprintf("Instance deletion waiting: %v", err))
return
@@ -690,33 +927,29 @@ func (r *instanceResource) ImportState(ctx context.Context, req resource.ImportS
tflog.Info(ctx, "SQLServer Flex instance state imported")
}
-func mapFields(ctx context.Context, resp *sqlserverflex.GetInstanceResponse, model *Model, flavor *flavorModel, storage *storageModel, options *optionsModel, region string) error {
+func mapFields(ctx context.Context, resp *sqlserverflex.GetInstanceResponse, model *Model, flavor *flavorModel, region string) error {
if resp == nil {
return fmt.Errorf("response input is nil")
}
- if resp.Item == nil {
- return fmt.Errorf("no instance provided")
- }
if model == nil {
return fmt.Errorf("model input is nil")
}
- instance := resp.Item
var instanceId string
if model.InstanceId.ValueString() != "" {
instanceId = model.InstanceId.ValueString()
- } else if instance.Id != nil {
- instanceId = *instance.Id
+ } else if resp.Id != "" {
+ instanceId = resp.Id
} else {
return fmt.Errorf("instance id not present")
}
var aclList basetypes.ListValue
var diags diag.Diagnostics
- if instance.Acl == nil || instance.Acl.Items == nil {
+ if resp.Network.Acl == nil {
aclList = types.ListNull(types.StringType)
} else {
- respACL := instance.Acl.Items
+ respACL := resp.Network.Acl
modelACL, err := utils.ListValueToStringSlice(model.ACL)
if err != nil {
return err
@@ -730,71 +963,38 @@ func mapFields(ctx context.Context, resp *sqlserverflex.GetInstanceResponse, mod
}
}
- var flavorValues map[string]attr.Value
- if instance.Flavor == nil {
- flavorValues = map[string]attr.Value{
- "id": flavor.Id,
- "description": flavor.Description,
- "cpu": flavor.CPU,
- "ram": flavor.RAM,
- }
- } else {
- flavorValues = map[string]attr.Value{
- "id": types.StringValue(*instance.Flavor.Id),
- "description": types.StringValue(*instance.Flavor.Description),
- "cpu": types.Int32PointerValue(instance.Flavor.Cpu),
- "ram": types.Int32PointerValue(instance.Flavor.Memory),
- }
+ networkValues := map[string]attr.Value{
+ "acl": aclList,
+ "access_scope": types.StringPointerValue((*string)(resp.Network.AccessScope)),
+ }
+ networkObject, diags := types.ObjectValue(networkTypes, networkValues)
+ if diags.HasError() {
+ return fmt.Errorf("mapping network: %w", core.DiagsToError(diags))
+ }
+
+ flavorValues := map[string]attr.Value{
+ "id": flavor.Id,
+ "description": flavor.Description,
+ "cpu": flavor.CPU,
+ "ram": flavor.RAM,
}
flavorObject, diags := types.ObjectValue(flavorTypes, flavorValues)
if diags.HasError() {
return fmt.Errorf("creating flavor: %w", core.DiagsToError(diags))
}
- var storageValues map[string]attr.Value
- if instance.Storage == nil {
- storageValues = map[string]attr.Value{
- "class": storage.Class,
- "size": storage.Size,
- }
- } else {
- storageValues = map[string]attr.Value{
- "class": types.StringValue(*instance.Storage.Class),
- "size": types.Int64PointerValue(instance.Storage.Size),
- }
+ storageValues := map[string]attr.Value{
+ "class": types.StringPointerValue(resp.Storage.Class),
+ "size": types.Int64PointerValue(resp.Storage.Size),
}
storageObject, diags := types.ObjectValue(storageTypes, storageValues)
if diags.HasError() {
return fmt.Errorf("creating storage: %w", core.DiagsToError(diags))
}
- var optionsValues map[string]attr.Value
- if instance.Options == nil {
- optionsValues = map[string]attr.Value{
- "edition": options.Edition,
- "retention_days": options.RetentionDays,
- }
- } else {
- retentionDays := options.RetentionDays
- retentionDaysString, ok := (*instance.Options)["retentionDays"]
- if ok {
- retentionDaysValue, err := strconv.ParseInt(retentionDaysString, 10, 64)
- if err != nil {
- return fmt.Errorf("parse retentionDays to int64: %w", err)
- }
- retentionDays = types.Int64Value(retentionDaysValue)
- }
-
- edition := options.Edition
- editionValue, ok := (*instance.Options)["edition"]
- if ok {
- edition = types.StringValue(editionValue)
- }
-
- optionsValues = map[string]attr.Value{
- "edition": edition,
- "retention_days": retentionDays,
- }
+ optionsValues := map[string]attr.Value{
+ "edition": types.StringValue(string(resp.Edition)),
+ "retention_days": types.Int32Value(resp.RetentionDays),
}
optionsObject, diags := types.ObjectValue(optionsTypes, optionsValues)
if diags.HasError() {
@@ -803,81 +1003,148 @@ func mapFields(ctx context.Context, resp *sqlserverflex.GetInstanceResponse, mod
// If the API returned "0 0 * * *" but user defined "00 00 * * *" in its config,
// we keep the user's "00 00 * * *" in the state to satisfy Terraform.
- backupScheduleApiResp := types.StringPointerValue(instance.BackupSchedule)
+ backupScheduleApiResp := types.StringValue(resp.BackupSchedule)
if utils.SimplifyCronString(model.BackupSchedule.ValueString()) != utils.SimplifyCronString(backupScheduleApiResp.ValueString()) {
// If the API actually changed it to something else, use the API value
- model.BackupSchedule = types.StringPointerValue(instance.BackupSchedule)
+ model.BackupSchedule = types.StringValue(resp.BackupSchedule)
}
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, instanceId)
model.InstanceId = types.StringValue(instanceId)
- model.Name = types.StringPointerValue(instance.Name)
+ model.Name = types.StringValue(resp.Name)
model.ACL = aclList
model.Flavor = flavorObject
- model.Replicas = types.Int32PointerValue(instance.Replicas)
+ model.FlavorId = types.StringValue(resp.FlavorId)
+ model.Replicas = types.Int32Value(int32(resp.Replicas))
model.Storage = storageObject
- model.Version = types.StringPointerValue(instance.Version)
+ model.Version = types.StringValue(string(resp.Version))
model.Options = optionsObject
model.Region = types.StringValue(region)
+ model.RetentionDays = types.Int32Value(resp.RetentionDays)
+ model.Edition = types.StringValue(string(resp.Edition))
+ model.Network = networkObject
return nil
}
-func toCreatePayload(model *Model, acl []string, flavor *flavorModel, storage *storageModel, options *optionsModel) (*sqlserverflex.CreateInstancePayload, error) {
+func toCreatePayload(model *Model, acl []string, flavor *flavorModel, storage *storageModel, options *optionsModel, network *networkModel) (*sqlserverflex.CreateInstancePayload, error) {
if model == nil {
return nil, fmt.Errorf("nil model")
}
- aclPayload := &sqlserverflex.InstanceDocumentationACL{}
+
+ // Network
+ networkPayload := sqlserverflex.CreateInstancePayloadNetwork{}
if acl != nil {
- aclPayload.Items = acl
+ networkPayload.Acl = acl
+ } else if network != nil && !(network.Acl.IsNull() || network.Acl.IsUnknown()) {
+ var err error
+ networkPayload.Acl, err = conversion.StringListToSlice(network.Acl)
+ if err != nil {
+ return nil, err
+ }
+ networkPayload.AccessScope = (*sqlserverflex.InstanceNetworkAccessScope)(network.AccessScope.ValueStringPointer())
+ } else {
+ // TODO: Return here an error after the deprecation period. During the deprecation period, we set here an empty ACL to catch the breaking change from v2 -> v3 api.
+ networkPayload.Acl = []string{}
}
- if flavor == nil {
- return nil, fmt.Errorf("nil flavor")
+
+ // Flavor
+ var flavorId string
+ if flavor != nil && !(flavor.Id.IsNull() || flavor.Id.IsUnknown()) {
+ flavorId = flavor.Id.ValueString()
+ } else if !model.FlavorId.IsNull() {
+ flavorId = model.FlavorId.ValueString()
+ } else {
+ return nil, fmt.Errorf("flavor is missing")
}
- storagePayload := &sqlserverflex.InstanceDocumentationStorage{}
- if storage != nil {
- storagePayload.Class = conversion.StringValueToPointer(storage.Class)
- storagePayload.Size = conversion.Int64ValueToPointer(storage.Size)
+
+ // Storage
+ storagePayload := sqlserverflex.StorageCreate{}
+ if storage == nil {
+ return nil, fmt.Errorf("storage configuration is missing")
}
- optionsPayload := &sqlserverflex.InstanceDocumentationOptions{}
- if options != nil {
- optionsPayload.Edition = conversion.StringValueToPointer(options.Edition)
- retentionDaysInt := conversion.Int64ValueToPointer(options.RetentionDays)
- var retentionDays *string
- if retentionDaysInt != nil {
- retentionDays = new(strconv.FormatInt(*retentionDaysInt, 10))
- }
- optionsPayload.RetentionDays = retentionDays
+ storagePayload.Class = storage.Class.ValueString()
+ storagePayload.Size = storage.Size.ValueInt64()
+
+ // Retention days
+ var retentionDays int32
+ if options != nil && !options.RetentionDays.IsNull() {
+ retentionDays = options.RetentionDays.ValueInt32()
+ } else if !model.RetentionDays.IsNull() {
+ retentionDays = model.RetentionDays.ValueInt32()
+ } else {
+ return nil, fmt.Errorf("retention days are missing")
}
return &sqlserverflex.CreateInstancePayload{
- Acl: aclPayload,
- BackupSchedule: conversion.StringValueToPointer(model.BackupSchedule),
- FlavorId: flavor.Id.ValueString(),
- Name: model.Name.ValueString(),
- Storage: storagePayload,
- Version: conversion.StringValueToPointer(model.Version),
- Options: optionsPayload,
+ BackupSchedule: model.BackupSchedule.ValueString(),
+ Encryption: nil,
+ FlavorId: flavorId,
+ Labels: nil,
+ Name: model.Name.ValueString(),
+ Network: networkPayload,
+ RetentionDays: retentionDays,
+ Storage: storagePayload,
+ Version: sqlserverflex.InstanceVersion(model.Version.ValueString()),
+ AdditionalProperties: nil,
}, nil
}
-func toUpdatePayload(model *Model, acl []string, flavor *flavorModel) (*sqlserverflex.PartialUpdateInstancePayload, error) {
+func toUpdatePayload(model *Model, acl []string, flavor *flavorModel, storage *storageModel, options *optionsModel, network *networkModel) (*sqlserverflex.PartialUpdateInstancePayload, error) {
if model == nil {
return nil, fmt.Errorf("nil model")
}
- aclPayload := &sqlserverflex.InstanceDocumentationACL{}
+ networkPayload := &sqlserverflex.PartialUpdateInstancePayloadNetwork{}
if acl != nil {
- aclPayload.Items = acl
+ networkPayload.Acl = acl
+ } else if network != nil && !(network.Acl.IsNull() || network.Acl.IsUnknown()) {
+ var err error
+ networkPayload.Acl, err = conversion.StringListToSlice(network.Acl)
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ // TODO: Return here an error after the deprecation period. During the deprecation period, we set here an empty ACL to catch the breaking change from v2 -> v3 api.
+ networkPayload.Acl = []string{}
}
- if flavor == nil {
- return nil, fmt.Errorf("nil flavor")
+
+ var flavorId *string
+ if flavor != nil && !(flavor.Id.IsNull() || flavor.Id.IsUnknown()) {
+ flavorId = flavor.Id.ValueStringPointer()
+ } else if !(model.FlavorId.IsNull() || model.FlavorId.IsUnknown()) {
+ flavorId = model.FlavorId.ValueStringPointer()
+ } else {
+ return nil, fmt.Errorf("flavor is missing")
+ }
+
+ var versionPayload *sqlserverflex.InstanceVersionOpt
+ if version := conversion.StringValueToPointer(model.Version); version != nil {
+ versionPayload = new(sqlserverflex.InstanceVersionOpt(*version))
+ }
+
+ storagePayload := &sqlserverflex.StorageUpdate{}
+ if storage == nil || storage.Size.IsNull() {
+ return nil, fmt.Errorf("storage configuration is missing")
+ }
+ storagePayload.Size = storage.Size.ValueInt64Pointer()
+
+ // Retention days
+ var retentionDays *int32
+ if !(model.RetentionDays.IsNull() || model.RetentionDays.IsUnknown()) {
+ retentionDays = model.RetentionDays.ValueInt32Pointer()
+ } else if !(options.RetentionDays.IsNull() || options.RetentionDays.IsUnknown()) {
+ retentionDays = options.RetentionDays.ValueInt32Pointer()
}
return &sqlserverflex.PartialUpdateInstancePayload{
- Acl: aclPayload,
- BackupSchedule: conversion.StringValueToPointer(model.BackupSchedule),
- FlavorId: conversion.StringValueToPointer(flavor.Id),
- Name: conversion.StringValueToPointer(model.Name),
- Version: conversion.StringValueToPointer(model.Version),
+ BackupSchedule: conversion.StringValueToPointer(model.BackupSchedule),
+ FlavorId: flavorId,
+ Labels: nil,
+ Name: conversion.StringValueToPointer(model.Name),
+ Network: networkPayload,
+ RetentionDays: retentionDays,
+ Storage: storagePayload,
+ Version: versionPayload,
+ AdditionalProperties: nil,
}, nil
}
@@ -893,11 +1160,11 @@ func loadFlavorId(ctx context.Context, client sqlserverflexClient, model *Model,
if flavor == nil {
return fmt.Errorf("nil flavor")
}
- cpu := conversion.Int32ValueToPointer(flavor.CPU)
+ cpu := conversion.Int64ValueToPointer(flavor.CPU)
if cpu == nil {
return fmt.Errorf("nil CPU")
}
- ram := conversion.Int32ValueToPointer(flavor.RAM)
+ ram := conversion.Int64ValueToPointer(flavor.RAM)
if ram == nil {
return fmt.Errorf("nil RAM")
}
@@ -915,15 +1182,15 @@ func loadFlavorId(ctx context.Context, client sqlserverflexClient, model *Model,
return fmt.Errorf("finding flavors for project %s", projectId)
}
for _, f := range res.Flavors {
- if f.Id == nil || f.Cpu == nil || f.Memory == nil {
+ if f.Id == "" || f.Cpu == 0 || f.Memory == 0 {
continue
}
- if *f.Cpu == *cpu && *f.Memory == *ram {
- flavor.Id = types.StringValue(*f.Id)
- flavor.Description = types.StringValue(*f.Description)
+ if f.Cpu == *cpu && f.Memory == *ram {
+ flavor.Id = types.StringValue(f.Id)
+ flavor.Description = types.StringValue(f.Description)
break
}
- avl = fmt.Sprintf("%s\n- %d CPU, %d GB RAM", avl, *f.Cpu, *f.Memory)
+ 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)
@@ -931,3 +1198,17 @@ func loadFlavorId(ctx context.Context, client sqlserverflexClient, model *Model,
return nil
}
+
+func getFlavor(ctx context.Context, client sqlserverflexClient, projectId, region, flavorId string) (*sqlserverflex.ListFlavors, error) {
+ req := client.ListFlavors(ctx, projectId, region)
+ flavorsResp, err := client.ListFlavorsExecute(req)
+ if err != nil {
+ return nil, fmt.Errorf("failed to list flavors: %w", err)
+ }
+ for _, flavor := range flavorsResp.Flavors {
+ if flavor.Id == flavorId {
+ return &flavor, nil
+ }
+ }
+ return nil, fmt.Errorf("flavor with ID %q not found in project %q", flavorId, projectId)
+}
diff --git a/stackit/internal/services/sqlserverflex/instance/resource_test.go b/stackit/internal/services/sqlserverflex/instance/resource_test.go
index 5300afe79..d8d5051b0 100644
--- a/stackit/internal/services/sqlserverflex/instance/resource_test.go
+++ b/stackit/internal/services/sqlserverflex/instance/resource_test.go
@@ -7,8 +7,9 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
- sqlserverflex "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v2api"
+ sqlserverflex "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v3api"
)
type sqlserverflexClientMocked struct {
@@ -21,7 +22,7 @@ func (c *sqlserverflexClientMocked) ListFlavors(_ context.Context, _, _ string)
return c.listFlavorsReq
}
-func (c *sqlserverflexClientMocked) ListFlavorsExecute(_ sqlserverflex.ApiListFlavorsRequest) (*sqlserverflex.ListFlavorsResponse, error) {
+func (c *sqlserverflexClientMocked) ListFlavorsExecute(_ sqlserverflex.ApiListFlavorsRequest) (*sqlserverflex.ListFlavorsResponse, error) { // nolint:gocritic // function signature required by generated SDK
if c.returnError {
return nil, fmt.Errorf("get flavors failed")
}
@@ -36,94 +37,84 @@ func TestMapFields(t *testing.T) {
state Model
input *sqlserverflex.GetInstanceResponse
flavor *flavorModel
- storage *storageModel
- options *optionsModel
region string
expected Model
isValid bool
}{
{
- "default_values",
- Model{
+ description: "default_values",
+ state: Model{
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
},
- &sqlserverflex.GetInstanceResponse{
- Item: &sqlserverflex.Instance{},
- },
- &flavorModel{},
- &storageModel{},
- &optionsModel{},
- testRegion,
- Model{
+ input: &sqlserverflex.GetInstanceResponse{},
+ flavor: &flavorModel{},
+ region: testRegion,
+ expected: Model{
Id: types.StringValue("pid,region,iid"),
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
- Name: types.StringNull(),
+ Name: types.StringValue(""),
ACL: types.ListNull(types.StringType),
BackupSchedule: types.StringNull(),
Flavor: types.ObjectValueMust(flavorTypes, map[string]attr.Value{
"id": types.StringNull(),
"description": types.StringNull(),
- "cpu": types.Int32Null(),
- "ram": types.Int32Null(),
+ "cpu": types.Int64Null(),
+ "ram": types.Int64Null(),
}),
- Replicas: types.Int32Null(),
+ FlavorId: types.StringValue(""),
+ Replicas: types.Int32Value(0),
Storage: types.ObjectValueMust(storageTypes, map[string]attr.Value{
"class": types.StringNull(),
"size": types.Int64Null(),
}),
Options: types.ObjectValueMust(optionsTypes, map[string]attr.Value{
- "edition": types.StringNull(),
- "retention_days": types.Int64Null(),
+ "edition": types.StringValue(""),
+ "retention_days": types.Int32Value(0),
}),
- Version: types.StringNull(),
- Region: types.StringValue(testRegion),
+ Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{
+ "acl": types.ListNull(types.StringType),
+ "access_scope": types.StringNull(),
+ }),
+ RetentionDays: types.Int32Value(0),
+ Edition: types.StringValue(""),
+ Version: types.StringValue(""),
+ Region: types.StringValue(testRegion),
},
- true,
+ isValid: true,
},
{
- "simple_values",
- Model{
+ description: "simple_values",
+ state: Model{
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
},
- &sqlserverflex.GetInstanceResponse{
- Item: &sqlserverflex.Instance{
- Acl: &sqlserverflex.ACL{
- Items: []string{
- "ip1",
- "ip2",
- "",
- },
- },
- BackupSchedule: new("schedule"),
- Flavor: &sqlserverflex.Flavor{
- Cpu: new(int32(12)),
- Description: new("description"),
- Id: new("flavor_id"),
- Memory: new(int32(34)),
- },
- Id: new("iid"),
- Name: new("name"),
- Replicas: new(int32(56)),
- Status: new("status"),
- Storage: &sqlserverflex.Storage{
- Class: new("class"),
- Size: new(int64(78)),
- },
- Options: &map[string]string{
- "edition": "edition",
- "retentionDays": "1",
+ input: &sqlserverflex.GetInstanceResponse{
+ Network: sqlserverflex.InstanceNetwork{
+ Acl: []string{
+ "ip1",
+ "ip2",
+ "",
},
- Version: new("version"),
},
+ BackupSchedule: "schedule",
+ FlavorId: "flavor_id",
+ Id: "iid",
+ Name: "name",
+ Replicas: 56,
+ State: "status",
+ Storage: sqlserverflex.Storage{
+ Class: new("class"),
+ Size: new(int64(78)),
+ },
+ Edition: "edition",
+ RetentionDays: 1,
+ Version: "version",
},
- &flavorModel{},
- &storageModel{},
- &optionsModel{},
- testRegion,
- Model{
+ flavor: &flavorModel{},
+ region: testRegion,
+ expected: Model{
Id: types.StringValue("pid,region,iid"),
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
@@ -135,10 +126,17 @@ func TestMapFields(t *testing.T) {
}),
BackupSchedule: types.StringValue("schedule"),
Flavor: types.ObjectValueMust(flavorTypes, map[string]attr.Value{
- "id": types.StringValue("flavor_id"),
- "description": types.StringValue("description"),
- "cpu": types.Int32Value(12),
- "ram": types.Int32Value(34),
+ "id": types.StringNull(),
+ "description": types.StringNull(),
+ "cpu": types.Int64Null(),
+ "ram": types.Int64Null(),
+ }),
+ FlavorId: types.StringValue("flavor_id"),
+ Edition: types.StringValue("edition"),
+ RetentionDays: types.Int32Value(1),
+ Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{
+ "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("ip1"), types.StringValue("ip2"), types.StringValue("")}),
+ "access_scope": types.StringNull(),
}),
Replicas: types.Int32Value(56),
Storage: types.ObjectValueMust(storageTypes, map[string]attr.Value{
@@ -147,56 +145,47 @@ func TestMapFields(t *testing.T) {
}),
Options: types.ObjectValueMust(optionsTypes, map[string]attr.Value{
"edition": types.StringValue("edition"),
- "retention_days": types.Int64Value(1),
+ "retention_days": types.Int32Value(1),
}),
Version: types.StringValue("version"),
Region: types.StringValue(testRegion),
},
- true,
+ isValid: true,
},
{
- "simple_values_no_flavor_and_storage",
- Model{
+ description: "simple_values_no_flavor_and_storage",
+ state: Model{
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
},
- &sqlserverflex.GetInstanceResponse{
- Item: &sqlserverflex.Instance{
- Acl: &sqlserverflex.ACL{
- Items: []string{
- "ip1",
- "ip2",
- "",
- },
- },
- BackupSchedule: new("schedule"),
- Flavor: nil,
- Id: new("iid"),
- Name: new("name"),
- Replicas: new(int32(56)),
- Status: new("status"),
- Storage: nil,
- Options: &map[string]string{
- "edition": "edition",
- "retentionDays": "1",
+ input: &sqlserverflex.GetInstanceResponse{
+ BackupSchedule: "schedule",
+ FlavorId: "",
+ Id: "iid",
+ Name: "name",
+ Replicas: 56,
+ State: "status",
+ Storage: sqlserverflex.Storage{
+ Class: new("class"),
+ Size: new(int64(78)),
+ },
+ Network: sqlserverflex.InstanceNetwork{
+ Acl: []string{
+ "ip1",
+ "ip2",
+ "",
},
- Version: new("version"),
},
+ Edition: "edition",
+ RetentionDays: 1,
+ Version: "version",
},
- &flavorModel{
- CPU: types.Int32Value(12),
- RAM: types.Int32Value(34),
- },
- &storageModel{
- Class: types.StringValue("class"),
- Size: types.Int64Value(78),
- },
- &optionsModel{
- Edition: types.StringValue("edition"),
- RetentionDays: types.Int64Value(1),
+ flavor: &flavorModel{
+ CPU: types.Int64Value(12),
+ RAM: types.Int64Value(34),
},
- testRegion,
- Model{
+ region: testRegion,
+ expected: Model{
Id: types.StringValue("pid,region,iid"),
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
@@ -210,26 +199,33 @@ func TestMapFields(t *testing.T) {
Flavor: types.ObjectValueMust(flavorTypes, map[string]attr.Value{
"id": types.StringNull(),
"description": types.StringNull(),
- "cpu": types.Int32Value(12),
- "ram": types.Int32Value(34),
+ "cpu": types.Int64Value(12),
+ "ram": types.Int64Value(34),
+ }),
+ FlavorId: types.StringValue(""),
+ Edition: types.StringValue("edition"),
+ RetentionDays: types.Int32Value(1),
+ Replicas: types.Int32Value(56),
+ Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{
+ "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("ip1"), types.StringValue("ip2"), types.StringValue("")}),
+ "access_scope": types.StringNull(),
}),
- Replicas: types.Int32Value(56),
Storage: types.ObjectValueMust(storageTypes, map[string]attr.Value{
"class": types.StringValue("class"),
"size": types.Int64Value(78),
}),
Options: types.ObjectValueMust(optionsTypes, map[string]attr.Value{
"edition": types.StringValue("edition"),
- "retention_days": types.Int64Value(1),
+ "retention_days": types.Int32Value(1),
}),
Version: types.StringValue("version"),
Region: types.StringValue(testRegion),
},
- true,
+ isValid: true,
},
{
- "acls_unordered",
- Model{
+ description: "acls_unordered",
+ state: Model{
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
ACL: types.ListValueMust(types.StringType, []attr.Value{
@@ -238,40 +234,34 @@ func TestMapFields(t *testing.T) {
types.StringValue("ip1"),
}),
},
- &sqlserverflex.GetInstanceResponse{
- Item: &sqlserverflex.Instance{
- Acl: &sqlserverflex.ACL{
- Items: []string{
- "",
- "ip1",
- "ip2",
- },
- },
- BackupSchedule: new("schedule"),
- Flavor: nil,
- Id: new("iid"),
- Name: new("name"),
- Replicas: new(int32(56)),
- Status: new("status"),
- Storage: nil,
- Options: &map[string]string{
- "edition": "edition",
- "retentionDays": "1",
+ input: &sqlserverflex.GetInstanceResponse{
+ Network: sqlserverflex.InstanceNetwork{
+ Acl: []string{
+ "",
+ "ip1",
+ "ip2",
},
- Version: new("version"),
},
+ BackupSchedule: "schedule",
+ FlavorId: "",
+ Id: "iid",
+ Name: "name",
+ Replicas: 56,
+ State: "status",
+ Storage: sqlserverflex.Storage{
+ Class: new("class"),
+ Size: new(int64(78)),
+ },
+ Edition: "edition",
+ RetentionDays: 1,
+ Version: "version",
},
- &flavorModel{
- CPU: types.Int32Value(12),
- RAM: types.Int32Value(34),
- },
- &storageModel{
- Class: types.StringValue("class"),
- Size: types.Int64Value(78),
+ flavor: &flavorModel{
+ CPU: types.Int64Value(12),
+ RAM: types.Int64Value(34),
},
- &optionsModel{},
- testRegion,
- Model{
+ region: testRegion,
+ expected: Model{
Id: types.StringValue("pid,region,iid"),
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
@@ -285,55 +275,57 @@ func TestMapFields(t *testing.T) {
Flavor: types.ObjectValueMust(flavorTypes, map[string]attr.Value{
"id": types.StringNull(),
"description": types.StringNull(),
- "cpu": types.Int32Value(12),
- "ram": types.Int32Value(34),
+ "cpu": types.Int64Value(12),
+ "ram": types.Int64Value(34),
+ }),
+ FlavorId: types.StringValue(""),
+ Edition: types.StringValue("edition"),
+ RetentionDays: types.Int32Value(1),
+ Replicas: types.Int32Value(56),
+ Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{
+ "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("ip2"), types.StringValue(""), types.StringValue("ip1")}),
+ "access_scope": types.StringNull(),
}),
- Replicas: types.Int32Value(56),
Storage: types.ObjectValueMust(storageTypes, map[string]attr.Value{
"class": types.StringValue("class"),
"size": types.Int64Value(78),
}),
Options: types.ObjectValueMust(optionsTypes, map[string]attr.Value{
"edition": types.StringValue("edition"),
- "retention_days": types.Int64Value(1),
+ "retention_days": types.Int32Value(1),
}),
Version: types.StringValue("version"),
Region: types.StringValue(testRegion),
},
- true,
+ isValid: true,
},
{
- "nil_response",
- Model{
+ description: "nil_response",
+ state: Model{
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
},
- nil,
- &flavorModel{},
- &storageModel{},
- &optionsModel{},
- testRegion,
- Model{},
- false,
+ input: nil,
+ flavor: &flavorModel{},
+ region: testRegion,
+ expected: Model{},
+ isValid: false,
},
{
- "no_resource_id",
- Model{
- InstanceId: types.StringValue("iid"),
- ProjectId: types.StringValue("pid"),
+ description: "no_resource_id",
+ state: Model{
+ ProjectId: types.StringValue("pid"),
},
- &sqlserverflex.GetInstanceResponse{},
- &flavorModel{},
- &storageModel{},
- &optionsModel{},
- testRegion,
- Model{},
- false,
+ input: &sqlserverflex.GetInstanceResponse{},
+ flavor: &flavorModel{},
+ region: testRegion,
+ expected: Model{},
+ isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
- err := mapFields(context.Background(), tt.input, &tt.state, tt.flavor, tt.storage, tt.options, tt.region)
+ err := mapFields(context.Background(), tt.input, &tt.state, tt.flavor, tt.region)
if !tt.isValid && err == nil {
t.Fatalf("Should have failed")
}
@@ -341,7 +333,7 @@ func TestMapFields(t *testing.T) {
t.Fatalf("Should not have failed: %v", err)
}
if tt.isValid {
- diff := cmp.Diff(tt.state, tt.expected)
+ diff := cmp.Diff(tt.expected, tt.state)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
@@ -358,180 +350,179 @@ func TestToCreatePayload(t *testing.T) {
inputFlavor *flavorModel
inputStorage *storageModel
inputOptions *optionsModel
+ inputNetwork *networkModel
expected *sqlserverflex.CreateInstancePayload
isValid bool
}{
{
- "default_values",
- &Model{},
- []string{},
- &flavorModel{},
- &storageModel{},
- &optionsModel{},
- &sqlserverflex.CreateInstancePayload{
- Acl: &sqlserverflex.InstanceDocumentationACL{
- Items: []string{},
+ description: "default_values",
+ input: &Model{
+ FlavorId: types.StringValue("fid"),
+ RetentionDays: types.Int32Value(1),
+ },
+ inputAcl: []string{},
+ inputFlavor: &flavorModel{},
+ inputStorage: &storageModel{},
+ inputOptions: &optionsModel{},
+ inputNetwork: &networkModel{},
+ expected: &sqlserverflex.CreateInstancePayload{
+ FlavorId: "fid",
+ RetentionDays: 1,
+ Network: sqlserverflex.CreateInstancePayloadNetwork{
+ Acl: []string{},
+ },
+ Storage: sqlserverflex.StorageCreate{
+ Class: "",
+ Size: 0,
},
- Storage: &sqlserverflex.InstanceDocumentationStorage{},
- Options: &sqlserverflex.InstanceDocumentationOptions{},
},
- true,
+ isValid: true,
},
{
- "simple_values",
- &Model{
+ description: "simple_values",
+ input: &Model{
+ FlavorId: types.StringValue("fid"),
BackupSchedule: types.StringValue("schedule"),
Name: types.StringValue("name"),
Replicas: types.Int32Value(12),
Version: types.StringValue("version"),
},
- []string{
+ inputAcl: []string{
"ip_1",
"ip_2",
},
- &flavorModel{
- Id: types.StringValue("flavor_id"),
- },
- &storageModel{
+ inputFlavor: &flavorModel{},
+ inputStorage: &storageModel{
Class: types.StringValue("class"),
Size: types.Int64Value(34),
},
- &optionsModel{
+ inputOptions: &optionsModel{
Edition: types.StringValue("edition"),
- RetentionDays: types.Int64Value(1),
+ RetentionDays: types.Int32Value(1),
},
- &sqlserverflex.CreateInstancePayload{
- Acl: &sqlserverflex.InstanceDocumentationACL{
- Items: []string{
- "ip_1",
- "ip_2",
- },
+ inputNetwork: &networkModel{},
+ expected: &sqlserverflex.CreateInstancePayload{
+ Network: sqlserverflex.CreateInstancePayloadNetwork{
+ Acl: []string{"ip_1", "ip_2"},
},
- BackupSchedule: new("schedule"),
- FlavorId: "flavor_id",
+ BackupSchedule: "schedule",
+ FlavorId: "fid",
Name: "name",
- Storage: &sqlserverflex.InstanceDocumentationStorage{
- Class: new("class"),
- Size: new(int64(34)),
+ Storage: sqlserverflex.StorageCreate{
+ Class: "class",
+ Size: 34,
},
- Options: &sqlserverflex.InstanceDocumentationOptions{
- Edition: new("edition"),
- RetentionDays: new("1"),
- },
- Version: new("version"),
+ RetentionDays: 1,
+ Version: "version",
},
- true,
+ isValid: true,
},
{
- "null_fields_and_int_conversions",
- &Model{
+ description: "null_fields_and_int_conversions",
+ input: &Model{
+ FlavorId: types.StringValue("fid"),
+ RetentionDays: types.Int32Value(1),
BackupSchedule: types.StringNull(),
Name: types.StringNull(),
Replicas: types.Int32Value(2123456789),
Version: types.StringNull(),
},
- []string{
+ inputAcl: []string{
"",
},
- &flavorModel{
- Id: types.StringNull(),
- },
- &storageModel{
+ inputFlavor: &flavorModel{},
+ inputStorage: &storageModel{
Class: types.StringNull(),
Size: types.Int64Null(),
},
- &optionsModel{
+ inputOptions: &optionsModel{
Edition: types.StringNull(),
- RetentionDays: types.Int64Null(),
+ RetentionDays: types.Int32Null(),
},
- &sqlserverflex.CreateInstancePayload{
- Acl: &sqlserverflex.InstanceDocumentationACL{
- Items: []string{
- "",
- },
+ inputNetwork: &networkModel{},
+ expected: &sqlserverflex.CreateInstancePayload{
+ Network: sqlserverflex.CreateInstancePayloadNetwork{
+ Acl: []string{""},
},
- BackupSchedule: nil,
- FlavorId: "",
+ RetentionDays: 1,
+ BackupSchedule: "",
+ FlavorId: "fid",
Name: "",
- Storage: &sqlserverflex.InstanceDocumentationStorage{
- Class: nil,
- Size: nil,
+ Storage: sqlserverflex.StorageCreate{
+ Class: "",
+ Size: 0,
},
- Options: &sqlserverflex.InstanceDocumentationOptions{},
- Version: nil,
+ Version: "",
},
- true,
+ isValid: true,
},
{
- "nil_model",
- nil,
- []string{},
- &flavorModel{},
- &storageModel{},
- &optionsModel{},
- nil,
- false,
+ description: "nil_model",
+ input: nil,
+ inputAcl: []string{},
+ inputFlavor: &flavorModel{},
+ inputStorage: &storageModel{},
+ inputOptions: &optionsModel{},
+ inputNetwork: &networkModel{},
+ expected: nil,
+ isValid: false,
},
{
- "nil_acl",
- &Model{},
- nil,
- &flavorModel{},
- &storageModel{},
- &optionsModel{},
- &sqlserverflex.CreateInstancePayload{
- Acl: &sqlserverflex.InstanceDocumentationACL{},
- Storage: &sqlserverflex.InstanceDocumentationStorage{},
- Options: &sqlserverflex.InstanceDocumentationOptions{},
+ description: "nil_acl",
+ input: &Model{
+ FlavorId: types.StringValue("fid"),
+ RetentionDays: types.Int32Value(0),
},
- true,
+ inputAcl: nil,
+ inputFlavor: &flavorModel{},
+ inputStorage: &storageModel{},
+ inputOptions: &optionsModel{},
+ inputNetwork: &networkModel{},
+ expected: &sqlserverflex.CreateInstancePayload{
+ FlavorId: "fid",
+ Network: sqlserverflex.CreateInstancePayloadNetwork{
+ Acl: []string{},
+ },
+ },
+ isValid: true,
},
{
- "nil_flavor",
- &Model{},
- []string{},
- nil,
- &storageModel{},
- &optionsModel{},
- nil,
- false,
+ description: "nil_flavor",
+ input: &Model{},
+ inputAcl: []string{},
+ inputFlavor: nil,
+ inputStorage: &storageModel{},
+ inputOptions: &optionsModel{},
+ inputNetwork: &networkModel{},
+ expected: nil,
+ isValid: false,
},
{
- "nil_storage",
- &Model{},
- []string{},
- &flavorModel{},
- nil,
- &optionsModel{},
- &sqlserverflex.CreateInstancePayload{
- Acl: &sqlserverflex.InstanceDocumentationACL{
- Items: []string{},
- },
- Storage: &sqlserverflex.InstanceDocumentationStorage{},
- Options: &sqlserverflex.InstanceDocumentationOptions{},
- },
- true,
+ description: "nil_storage",
+ input: &Model{},
+ inputAcl: []string{},
+ inputFlavor: &flavorModel{},
+ inputStorage: nil,
+ inputOptions: &optionsModel{},
+ inputNetwork: &networkModel{},
+ expected: &sqlserverflex.CreateInstancePayload{},
+ isValid: false,
},
{
- "nil_options",
- &Model{},
- []string{},
- &flavorModel{},
- &storageModel{},
- nil,
- &sqlserverflex.CreateInstancePayload{
- Acl: &sqlserverflex.InstanceDocumentationACL{
- Items: []string{},
- },
- Storage: &sqlserverflex.InstanceDocumentationStorage{},
- Options: &sqlserverflex.InstanceDocumentationOptions{},
- },
- true,
+ description: "nil_options",
+ input: &Model{},
+ inputAcl: []string{},
+ inputFlavor: &flavorModel{},
+ inputStorage: &storageModel{},
+ inputOptions: nil,
+ inputNetwork: &networkModel{},
+ expected: &sqlserverflex.CreateInstancePayload{},
+ isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
- output, err := toCreatePayload(tt.input, tt.inputAcl, tt.inputFlavor, tt.inputStorage, tt.inputOptions)
+ output, err := toCreatePayload(tt.input, tt.inputAcl, tt.inputFlavor, tt.inputStorage, tt.inputOptions, tt.inputNetwork)
if !tt.isValid && err == nil {
t.Fatalf("Should have failed")
}
@@ -550,111 +541,165 @@ func TestToCreatePayload(t *testing.T) {
func TestToUpdatePayload(t *testing.T) {
tests := []struct {
- description string
- input *Model
- inputAcl []string
- inputFlavor *flavorModel
- expected *sqlserverflex.PartialUpdateInstancePayload
- isValid bool
+ description string
+ input *Model
+ inputAcl []string
+ inputFlavor *flavorModel
+ inputStorage *storageModel
+ inputOptions *optionsModel
+ inputNetwork *networkModel
+ expected *sqlserverflex.PartialUpdateInstancePayload
+ isValid bool
}{
{
- "default_values",
- &Model{},
- []string{},
- &flavorModel{},
- &sqlserverflex.PartialUpdateInstancePayload{
- Acl: &sqlserverflex.InstanceDocumentationACL{
- Items: []string{},
+ description: "default_values",
+ input: &Model{
+ FlavorId: types.StringValue("fid"),
+ },
+ inputAcl: []string{},
+ inputFlavor: &flavorModel{},
+ inputStorage: &storageModel{
+ Class: types.StringValue("class"),
+ Size: types.Int64Value(34),
+ },
+ inputOptions: &optionsModel{},
+ inputNetwork: &networkModel{},
+ expected: &sqlserverflex.PartialUpdateInstancePayload{
+ FlavorId: new("fid"),
+ Network: &sqlserverflex.PartialUpdateInstancePayloadNetwork{
+ Acl: []string{},
+ },
+ Storage: &sqlserverflex.StorageUpdate{
+ Size: new(int64(34)),
},
},
- true,
+ isValid: true,
},
{
- "simple_values",
- &Model{
+ description: "simple_values",
+ input: &Model{
BackupSchedule: types.StringValue("schedule"),
Name: types.StringValue("name"),
Replicas: types.Int32Value(12),
- Version: types.StringValue("version"),
+ Version: types.StringValue(string(sqlserverflex.INSTANCEVERSIONOPT__2022)),
},
- []string{
+ inputAcl: []string{
"ip_1",
"ip_2",
},
- &flavorModel{
+ inputFlavor: &flavorModel{
Id: types.StringValue("flavor_id"),
},
- &sqlserverflex.PartialUpdateInstancePayload{
- Acl: &sqlserverflex.InstanceDocumentationACL{
- Items: []string{
+ inputStorage: &storageModel{
+ Class: types.StringValue("class"),
+ Size: types.Int64Value(34),
+ },
+ inputOptions: &optionsModel{},
+ inputNetwork: &networkModel{},
+ expected: &sqlserverflex.PartialUpdateInstancePayload{
+ Network: &sqlserverflex.PartialUpdateInstancePayloadNetwork{
+ Acl: []string{
"ip_1",
"ip_2",
},
},
+ Storage: &sqlserverflex.StorageUpdate{
+ Size: new(int64(34)),
+ },
BackupSchedule: new("schedule"),
FlavorId: new("flavor_id"),
Name: new("name"),
- Version: new("version"),
+ Version: new(sqlserverflex.INSTANCEVERSIONOPT__2022),
},
- true,
+ isValid: true,
},
{
- "null_fields_and_int_conversions",
- &Model{
+ description: "null_fields_and_int_conversions",
+ input: &Model{
+ FlavorId: types.StringValue("fid"),
BackupSchedule: types.StringNull(),
Name: types.StringNull(),
Replicas: types.Int32Value(2123456789),
Version: types.StringNull(),
},
- []string{
+ inputAcl: []string{
"",
},
- &flavorModel{
+ inputFlavor: &flavorModel{
Id: types.StringNull(),
},
- &sqlserverflex.PartialUpdateInstancePayload{
- Acl: &sqlserverflex.InstanceDocumentationACL{
- Items: []string{
+ inputNetwork: &networkModel{},
+ inputStorage: &storageModel{
+ Size: types.Int64Value(0),
+ },
+ inputOptions: &optionsModel{},
+ expected: &sqlserverflex.PartialUpdateInstancePayload{
+ Network: &sqlserverflex.PartialUpdateInstancePayloadNetwork{
+ Acl: []string{
"",
},
},
+ Storage: &sqlserverflex.StorageUpdate{
+ Size: new(int64(0)),
+ },
BackupSchedule: nil,
- FlavorId: nil,
+ FlavorId: new("fid"),
Name: nil,
Version: nil,
},
- true,
+ isValid: true,
},
{
- "nil_model",
- nil,
- []string{},
- &flavorModel{},
- nil,
- false,
+ description: "nil_model",
+ input: nil,
+ inputAcl: []string{},
+ inputFlavor: &flavorModel{},
+ inputStorage: &storageModel{},
+ inputOptions: &optionsModel{},
+ inputNetwork: &networkModel{},
+ expected: nil,
+ isValid: false,
},
{
- "nil_acl",
- &Model{},
- nil,
- &flavorModel{},
- &sqlserverflex.PartialUpdateInstancePayload{
- Acl: &sqlserverflex.InstanceDocumentationACL{},
+ description: "nil_acl",
+ input: &Model{
+ FlavorId: types.StringValue("fid"),
},
- true,
+ inputAcl: nil,
+ inputFlavor: &flavorModel{},
+ inputStorage: &storageModel{
+ Size: types.Int64Value(34),
+ },
+ inputOptions: &optionsModel{},
+ inputNetwork: &networkModel{},
+ expected: &sqlserverflex.PartialUpdateInstancePayload{
+ FlavorId: new("fid"),
+ Network: &sqlserverflex.PartialUpdateInstancePayloadNetwork{
+ Acl: []string{},
+ },
+ Storage: &sqlserverflex.StorageUpdate{
+ Size: new(int64(34)),
+ },
+ },
+ isValid: true,
},
{
- "nil_flavor",
- &Model{},
- []string{},
- nil,
- nil,
- false,
+ description: "nil_flavor",
+ input: &Model{},
+ inputAcl: []string{},
+ inputFlavor: nil,
+ inputStorage: &storageModel{
+ Size: types.Int64Value(34),
+ },
+ inputOptions: &optionsModel{},
+ inputNetwork: &networkModel{},
+ expected: nil,
+ isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
- output, err := toUpdatePayload(tt.input, tt.inputAcl, tt.inputFlavor)
+ output, err := toUpdatePayload(tt.input, tt.inputAcl, tt.inputFlavor, tt.inputStorage, tt.inputOptions, tt.inputNetwork)
if !tt.isValid && err == nil {
t.Fatalf("Should have failed")
}
@@ -683,24 +728,24 @@ func TestLoadFlavorId(t *testing.T) {
{
"ok_flavor",
&flavorModel{
- CPU: types.Int32Value(2),
- RAM: types.Int32Value(8),
+ CPU: types.Int64Value(2),
+ RAM: types.Int64Value(8),
},
&sqlserverflex.ListFlavorsResponse{
- Flavors: []sqlserverflex.InstanceFlavorEntry{
+ Flavors: []sqlserverflex.ListFlavors{
{
- Id: new("fid-1"),
- Cpu: new(int32(2)),
- Description: new("description"),
- Memory: new(int32(8)),
+ Id: "fid-1",
+ Cpu: 2,
+ Description: "description",
+ Memory: 8,
},
},
},
&flavorModel{
Id: types.StringValue("fid-1"),
Description: types.StringValue("description"),
- CPU: types.Int32Value(2),
- RAM: types.Int32Value(8),
+ CPU: types.Int64Value(2),
+ RAM: types.Int64Value(8),
},
false,
true,
@@ -708,30 +753,30 @@ func TestLoadFlavorId(t *testing.T) {
{
"ok_flavor_2",
&flavorModel{
- CPU: types.Int32Value(2),
- RAM: types.Int32Value(8),
+ CPU: types.Int64Value(2),
+ RAM: types.Int64Value(8),
},
&sqlserverflex.ListFlavorsResponse{
- Flavors: []sqlserverflex.InstanceFlavorEntry{
+ Flavors: []sqlserverflex.ListFlavors{
{
- Id: new("fid-1"),
- Cpu: new(int32(2)),
- Description: new("description"),
- Memory: new(int32(8)),
+ Id: "fid-1",
+ Cpu: 2,
+ Description: "description",
+ Memory: 8,
},
{
- Id: new("fid-2"),
- Cpu: new(int32(1)),
- Description: new("description"),
- Memory: new(int32(4)),
+ Id: "fid-2",
+ Cpu: 1,
+ Description: "description",
+ Memory: 4,
},
},
},
&flavorModel{
Id: types.StringValue("fid-1"),
Description: types.StringValue("description"),
- CPU: types.Int32Value(2),
- RAM: types.Int32Value(8),
+ CPU: types.Int64Value(2),
+ RAM: types.Int64Value(8),
},
false,
true,
@@ -739,28 +784,28 @@ func TestLoadFlavorId(t *testing.T) {
{
"no_matching_flavor",
&flavorModel{
- CPU: types.Int32Value(2),
- RAM: types.Int32Value(8),
+ CPU: types.Int64Value(2),
+ RAM: types.Int64Value(8),
},
&sqlserverflex.ListFlavorsResponse{
- Flavors: []sqlserverflex.InstanceFlavorEntry{
+ Flavors: []sqlserverflex.ListFlavors{
{
- Id: new("fid-1"),
- Cpu: new(int32(1)),
- Description: new("description"),
- Memory: new(int32(8)),
+ Id: "fid-1",
+ Cpu: 1,
+ Description: "description",
+ Memory: 8,
},
{
- Id: new("fid-2"),
- Cpu: new(int32(1)),
- Description: new("description"),
- Memory: new(int32(4)),
+ Id: "fid-2",
+ Cpu: 1,
+ Description: "description",
+ Memory: 4,
},
},
},
&flavorModel{
- CPU: types.Int32Value(2),
- RAM: types.Int32Value(8),
+ CPU: types.Int64Value(2),
+ RAM: types.Int64Value(8),
},
false,
false,
@@ -768,13 +813,13 @@ func TestLoadFlavorId(t *testing.T) {
{
"nil_response",
&flavorModel{
- CPU: types.Int32Value(2),
- RAM: types.Int32Value(8),
+ CPU: types.Int64Value(2),
+ RAM: types.Int64Value(8),
},
&sqlserverflex.ListFlavorsResponse{},
&flavorModel{
- CPU: types.Int32Value(2),
- RAM: types.Int32Value(8),
+ CPU: types.Int64Value(2),
+ RAM: types.Int64Value(8),
},
false,
false,
@@ -782,13 +827,13 @@ func TestLoadFlavorId(t *testing.T) {
{
"error_response",
&flavorModel{
- CPU: types.Int32Value(2),
- RAM: types.Int32Value(8),
+ CPU: types.Int64Value(2),
+ RAM: types.Int64Value(8),
},
&sqlserverflex.ListFlavorsResponse{},
&flavorModel{
- CPU: types.Int32Value(2),
- RAM: types.Int32Value(8),
+ CPU: types.Int64Value(2),
+ RAM: types.Int64Value(8),
},
true,
false,
@@ -823,3 +868,437 @@ func TestLoadFlavorId(t *testing.T) {
})
}
}
+
+func TestHandleV3Migration(t *testing.T) {
+ tests := []struct {
+ name string
+ configModel *Model
+ planModel *Model
+ expectedPlan *Model
+ warnings int
+ }{
+ {
+ name: "all_values_provided_no_migration",
+ configModel: &Model{
+ ACL: types.ListNull(types.StringType),
+ BackupSchedule: types.StringValue("1 2 * * *"),
+ Storage: types.ObjectValueMust(storageTypes, map[string]attr.Value{
+ "class": types.StringValue("custom-class"),
+ "size": types.Int64Value(100),
+ }),
+ Version: types.StringValue(string(sqlserverflex.INSTANCEVERSIONOPT__2022)),
+ Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{
+ "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("10.0.0.0/24")}),
+ "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)),
+ }),
+ RetentionDays: types.Int32Value(45),
+ },
+ planModel: &Model{
+ ACL: types.ListNull(types.StringType),
+ BackupSchedule: types.StringValue("1 2 * * *"),
+ Storage: types.ObjectValueMust(storageTypes, map[string]attr.Value{
+ "class": types.StringValue("custom-class"),
+ "size": types.Int64Value(100),
+ }),
+ Version: types.StringValue(string(sqlserverflex.INSTANCEVERSIONOPT__2022)),
+ Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{
+ "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("10.0.0.0/24")}),
+ "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)),
+ }),
+ RetentionDays: types.Int32Value(45),
+ },
+ expectedPlan: &Model{
+ ACL: types.ListNull(types.StringType),
+ BackupSchedule: types.StringValue("1 2 * * *"),
+ Storage: types.ObjectValueMust(storageTypes, map[string]attr.Value{
+ "class": types.StringValue("custom-class"),
+ "size": types.Int64Value(100),
+ }),
+ Version: types.StringValue(string(sqlserverflex.INSTANCEVERSIONOPT__2022)),
+ Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{
+ "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("10.0.0.0/24")}),
+ "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)),
+ }),
+ RetentionDays: types.Int32Value(45),
+ },
+ warnings: 0,
+ },
+ {
+ name: "migration_triggered_for_all_null_fields",
+ configModel: &Model{
+ BackupSchedule: types.StringNull(),
+ Storage: types.ObjectNull(storageTypes),
+ Version: types.StringNull(),
+ ACL: types.ListNull(types.StringType),
+ Network: types.ObjectNull(networkTypes),
+ RetentionDays: types.Int32Null(),
+ Options: types.ObjectNull(optionsTypes),
+ },
+ planModel: &Model{
+ BackupSchedule: types.StringNull(),
+ Storage: types.ObjectNull(storageTypes),
+ Version: types.StringNull(),
+ ACL: types.ListNull(types.StringType),
+ Network: types.ObjectNull(networkTypes),
+ RetentionDays: types.Int32Null(),
+ Options: types.ObjectNull(optionsTypes),
+ },
+ expectedPlan: &Model{
+ BackupSchedule: types.StringValue("0 0 * * *"),
+ Storage: types.ObjectValueMust(storageTypes, map[string]attr.Value{
+ "class": types.StringValue("premium-perf12-stackit"),
+ "size": types.Int64Value(40),
+ }),
+ Version: types.StringValue(string(sqlserverflex.INSTANCEVERSION__2022)),
+ ACL: types.ListNull(types.StringType),
+ Network: types.ObjectNull(networkTypes),
+ RetentionDays: types.Int32Value(30),
+ Options: types.ObjectNull(optionsTypes),
+ },
+ warnings: 5,
+ },
+ {
+ name: "no_storage_size_set",
+ configModel: &Model{
+ BackupSchedule: types.StringValue("1 2 * * *"),
+ Storage: types.ObjectValueMust(storageTypes, map[string]attr.Value{
+ "class": types.StringValue("premium-perf12-stackit"),
+ "size": types.Int64Null(),
+ }),
+ Version: types.StringValue(string(sqlserverflex.INSTANCEVERSIONOPT__2022)),
+ ACL: types.ListNull(types.StringType),
+ Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{
+ "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("10.0.0.0/24")}),
+ "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)),
+ }),
+ RetentionDays: types.Int32Value(1),
+ Options: types.ObjectNull(optionsTypes),
+ },
+ planModel: &Model{
+ BackupSchedule: types.StringValue("1 2 * * *"),
+ Storage: types.ObjectValueMust(storageTypes, map[string]attr.Value{
+ "class": types.StringValue("premium-perf12-stackit"),
+ "size": types.Int64Null(),
+ }),
+ Version: types.StringValue(string(sqlserverflex.INSTANCEVERSIONOPT__2022)),
+ ACL: types.ListNull(types.StringType),
+ Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{
+ "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("10.0.0.0/24")}),
+ "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)),
+ }),
+ RetentionDays: types.Int32Value(1),
+ Options: types.ObjectNull(optionsTypes),
+ },
+ expectedPlan: &Model{
+ BackupSchedule: types.StringValue("1 2 * * *"),
+ Storage: types.ObjectValueMust(storageTypes, map[string]attr.Value{
+ "class": types.StringValue("premium-perf12-stackit"),
+ "size": types.Int64Value(40),
+ }),
+ Version: types.StringValue(string(sqlserverflex.INSTANCEVERSION__2022)),
+ ACL: types.ListNull(types.StringType),
+ Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{
+ "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("10.0.0.0/24")}),
+ "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)),
+ }),
+ RetentionDays: types.Int32Value(1),
+ Options: types.ObjectNull(optionsTypes),
+ },
+ warnings: 1,
+ },
+ {
+ name: "no_storage_class_set",
+ configModel: &Model{
+ BackupSchedule: types.StringValue("1 2 * * *"),
+ Storage: types.ObjectValueMust(storageTypes, map[string]attr.Value{
+ "class": types.StringNull(),
+ "size": types.Int64Value(20),
+ }),
+ Version: types.StringValue(string(sqlserverflex.INSTANCEVERSIONOPT__2022)),
+ ACL: types.ListNull(types.StringType),
+ Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{
+ "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("10.0.0.0/24")}),
+ "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)),
+ }),
+ RetentionDays: types.Int32Value(1),
+ Options: types.ObjectNull(optionsTypes),
+ },
+ planModel: &Model{
+ BackupSchedule: types.StringValue("1 2 * * *"),
+ Storage: types.ObjectValueMust(storageTypes, map[string]attr.Value{
+ "class": types.StringNull(),
+ "size": types.Int64Value(20),
+ }),
+ Version: types.StringValue(string(sqlserverflex.INSTANCEVERSIONOPT__2022)),
+ ACL: types.ListNull(types.StringType),
+ Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{
+ "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("10.0.0.0/24")}),
+ "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)),
+ }),
+ RetentionDays: types.Int32Value(1),
+ Options: types.ObjectNull(optionsTypes),
+ },
+ expectedPlan: &Model{
+ BackupSchedule: types.StringValue("1 2 * * *"),
+ Storage: types.ObjectValueMust(storageTypes, map[string]attr.Value{
+ "class": types.StringValue("premium-perf12-stackit"),
+ "size": types.Int64Value(20),
+ }),
+ Version: types.StringValue(string(sqlserverflex.INSTANCEVERSION__2022)),
+ ACL: types.ListNull(types.StringType),
+ Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{
+ "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("10.0.0.0/24")}),
+ "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)),
+ }),
+ RetentionDays: types.Int32Value(1),
+ Options: types.ObjectNull(optionsTypes),
+ },
+ warnings: 1,
+ },
+ {
+ name: "no_version_set",
+ configModel: &Model{
+ BackupSchedule: types.StringValue("1 2 * * *"),
+ Storage: types.ObjectValueMust(storageTypes, map[string]attr.Value{
+ "class": types.StringValue("premium-perf12-stackit"),
+ "size": types.Int64Value(40),
+ }),
+ Version: types.StringNull(),
+ ACL: types.ListNull(types.StringType),
+ Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{
+ "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("10.0.0.0/24")}),
+ "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)),
+ }),
+ RetentionDays: types.Int32Value(1),
+ Options: types.ObjectNull(optionsTypes),
+ },
+ planModel: &Model{
+ BackupSchedule: types.StringValue("1 2 * * *"),
+ Storage: types.ObjectValueMust(storageTypes, map[string]attr.Value{
+ "class": types.StringValue("premium-perf12-stackit"),
+ "size": types.Int64Value(40),
+ }),
+ Version: types.StringNull(),
+ ACL: types.ListNull(types.StringType),
+ Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{
+ "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("10.0.0.0/24")}),
+ "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)),
+ }),
+ RetentionDays: types.Int32Value(1),
+ Options: types.ObjectNull(optionsTypes),
+ },
+ expectedPlan: &Model{
+ BackupSchedule: types.StringValue("1 2 * * *"),
+ Storage: types.ObjectValueMust(storageTypes, map[string]attr.Value{
+ "class": types.StringValue("premium-perf12-stackit"),
+ "size": types.Int64Value(40),
+ }),
+ Version: types.StringValue(string(sqlserverflex.INSTANCEVERSION__2022)),
+ ACL: types.ListNull(types.StringType),
+ Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{
+ "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("10.0.0.0/24")}),
+ "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)),
+ }),
+ RetentionDays: types.Int32Value(1),
+ Options: types.ObjectNull(optionsTypes),
+ },
+ warnings: 1,
+ },
+ {
+ name: "retention_days_provided_in_options_no_migration",
+ configModel: &Model{
+ ACL: types.ListNull(types.StringType),
+ BackupSchedule: types.StringValue("0 0 * * *"),
+ Storage: types.ObjectValueMust(storageTypes, map[string]attr.Value{
+ "class": types.StringValue("premium-perf12-stackit"),
+ "size": types.Int64Value(40),
+ }),
+ Version: types.StringValue(string(sqlserverflex.INSTANCEVERSION__2022)),
+ Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{
+ "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("193.148.160.0/19")}),
+ "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)),
+ }),
+ RetentionDays: types.Int32Null(),
+ Options: types.ObjectValueMust(optionsTypes, map[string]attr.Value{
+ "edition": types.StringValue("edition"),
+ "retention_days": types.Int32Value(15),
+ }),
+ },
+ planModel: &Model{
+ ACL: types.ListNull(types.StringType),
+ BackupSchedule: types.StringValue("0 0 * * *"),
+ Storage: types.ObjectValueMust(storageTypes, map[string]attr.Value{
+ "class": types.StringValue("premium-perf12-stackit"),
+ "size": types.Int64Value(40),
+ }),
+ Version: types.StringValue(string(sqlserverflex.INSTANCEVERSION__2022)),
+ Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{
+ "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("193.148.160.0/19")}),
+ "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)),
+ }),
+ RetentionDays: types.Int32Null(),
+ Options: types.ObjectValueMust(optionsTypes, map[string]attr.Value{
+ "edition": types.StringValue("edition"),
+ "retention_days": types.Int32Value(15),
+ }),
+ },
+ expectedPlan: &Model{
+ ACL: types.ListNull(types.StringType),
+ BackupSchedule: types.StringValue("0 0 * * *"),
+ Storage: types.ObjectValueMust(storageTypes, map[string]attr.Value{
+ "class": types.StringValue("premium-perf12-stackit"),
+ "size": types.Int64Value(40),
+ }),
+ Version: types.StringValue(string(sqlserverflex.INSTANCEVERSION__2022)),
+ Network: types.ObjectValueMust(networkTypes, map[string]attr.Value{
+ "acl": types.ListValueMust(types.StringType, []attr.Value{types.StringValue("193.148.160.0/19")}),
+ "access_scope": types.StringValue(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)),
+ }),
+ RetentionDays: types.Int32Null(),
+ Options: types.ObjectValueMust(optionsTypes, map[string]attr.Value{
+ "edition": types.StringValue("edition"),
+ "retention_days": types.Int32Value(15),
+ }),
+ },
+ warnings: 0,
+ },
+ {
+ name: "config ",
+ configModel: &Model{
+ BackupSchedule: types.StringNull(),
+ Storage: types.ObjectNull(storageTypes),
+ Version: types.StringNull(),
+ ACL: types.ListNull(types.StringType),
+ Network: types.ObjectNull(networkTypes),
+ RetentionDays: types.Int32Null(),
+ Options: types.ObjectNull(optionsTypes),
+ },
+ planModel: &Model{
+ BackupSchedule: types.StringValue("1 1 * * *"),
+ Storage: types.ObjectValueMust(storageTypes, map[string]attr.Value{
+ "class": types.StringValue("custom-class"),
+ "size": types.Int64Value(80),
+ }),
+ Version: types.StringValue(string(sqlserverflex.INSTANCEVERSIONOPT__2022)),
+ ACL: types.ListNull(types.StringType),
+ Network: types.ObjectNull(networkTypes),
+ RetentionDays: types.Int32Value(60),
+ Options: types.ObjectNull(optionsTypes),
+ },
+ expectedPlan: &Model{
+ BackupSchedule: types.StringValue("1 1 * * *"),
+ Storage: types.ObjectValueMust(storageTypes, map[string]attr.Value{
+ "class": types.StringValue("custom-class"),
+ "size": types.Int64Value(80),
+ }),
+ Version: types.StringValue(string(sqlserverflex.INSTANCEVERSIONOPT__2022)),
+ ACL: types.ListNull(types.StringType),
+ Network: types.ObjectNull(networkTypes),
+ RetentionDays: types.Int32Value(60),
+ Options: types.ObjectNull(optionsTypes),
+ },
+ warnings: 5,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ resp := &resource.ModifyPlanResponse{}
+ handleV3Migration(context.Background(), tt.planModel, tt.configModel, resp)
+
+ if len(resp.Diagnostics.Warnings()) != tt.warnings {
+ t.Errorf("expected %d warnings, got %d", tt.warnings, len(resp.Diagnostics.Warnings()))
+ }
+
+ diff := cmp.Diff(tt.planModel, tt.expectedPlan)
+ if diff != "" {
+ t.Fatalf("Data does not match: %s", diff)
+ }
+ })
+ }
+}
+
+func TestGetFlavor(t *testing.T) {
+ tests := []struct {
+ description string
+ flavorId string
+ mockedResp *sqlserverflex.ListFlavorsResponse
+ expected *sqlserverflex.ListFlavors
+ getFlavorsFails bool
+ isValid bool
+ }{
+ {
+ description: "ok_flavor_found",
+ flavorId: "fid-1",
+ mockedResp: &sqlserverflex.ListFlavorsResponse{
+ Flavors: []sqlserverflex.ListFlavors{
+ {
+ Id: "fid-1",
+ Cpu: 2,
+ Description: "description-1",
+ Memory: 8,
+ },
+ {
+ Id: "fid-2",
+ Cpu: 4,
+ Description: "description-2",
+ Memory: 16,
+ },
+ },
+ },
+ expected: &sqlserverflex.ListFlavors{
+ Id: "fid-1",
+ Cpu: 2,
+ Description: "description-1",
+ Memory: 8,
+ },
+ getFlavorsFails: false,
+ isValid: true,
+ },
+ {
+ description: "flavor_not_found",
+ flavorId: "fid-3",
+ mockedResp: &sqlserverflex.ListFlavorsResponse{
+ Flavors: []sqlserverflex.ListFlavors{
+ {
+ Id: "fid-1",
+ Cpu: 2,
+ Description: "description-1",
+ Memory: 8,
+ },
+ },
+ },
+ expected: nil,
+ getFlavorsFails: false,
+ isValid: false,
+ },
+ {
+ description: "error_response",
+ flavorId: "fid-1",
+ mockedResp: nil,
+ expected: nil,
+ getFlavorsFails: true,
+ isValid: false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.description, func(t *testing.T) {
+ client := &sqlserverflexClientMocked{
+ returnError: tt.getFlavorsFails,
+ listFlavorsResp: tt.mockedResp,
+ }
+ got, err := getFlavor(context.Background(), client, "pid", "region", tt.flavorId)
+ 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(got, tt.expected)
+ if diff != "" {
+ t.Fatalf("Data does not match: %s", diff)
+ }
+ }
+ })
+ }
+}
diff --git a/stackit/internal/services/sqlserverflex/sqlserverflex_acc_test.go b/stackit/internal/services/sqlserverflex/sqlserverflex_acc_test.go
index 34f2a0f2d..bda442e1a 100644
--- a/stackit/internal/services/sqlserverflex/sqlserverflex_acc_test.go
+++ b/stackit/internal/services/sqlserverflex/sqlserverflex_acc_test.go
@@ -11,11 +11,12 @@ import (
"github.com/hashicorp/terraform-plugin-testing/config"
"github.com/hashicorp/terraform-plugin-testing/helper/acctest"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
+ "github.com/hashicorp/terraform-plugin-testing/plancheck"
"github.com/hashicorp/terraform-plugin-testing/terraform"
"github.com/stackitcloud/stackit-sdk-go/core/utils"
- sqlserverflex "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v2api"
- "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v2api/wait"
+ sqlserverflex "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v3api"
+ "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v3api/wait"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil"
@@ -28,38 +29,33 @@ var (
resourceMinConfig string
)
var testConfigVarsMin = config.Variables{
- "project_id": config.StringVariable(testutil.ProjectId),
- "name": config.StringVariable(fmt.Sprintf("tf-acc-%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlphaNum))),
- "flavor_cpu": config.IntegerVariable(4),
- "flavor_ram": config.IntegerVariable(16),
- "flavor_description": config.StringVariable("SQLServer-Flex-4.16-Single-Standard-EU01"),
- "replicas": config.IntegerVariable(1),
- "flavor_id": config.StringVariable("4.16-Single"),
- "username": config.StringVariable(fmt.Sprintf("tf-acc-user-%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlpha))),
- "role": config.StringVariable("##STACKIT_LoginManager##"),
+ "project_id": config.StringVariable(testutil.ProjectId),
+ "name": config.StringVariable(fmt.Sprintf("tf-acc-%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlphaNum))),
+ "replicas": config.IntegerVariable(1),
+ "flavor_id": config.StringVariable("4.16-Single"),
+ "username": config.StringVariable(fmt.Sprintf("tf-acc-user-%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlpha))),
+ "role": config.StringVariable("##STACKIT_LoginManager##"),
}
var testConfigVarsMax = config.Variables{
- "project_id": config.StringVariable(testutil.ProjectId),
- "name": config.StringVariable(fmt.Sprintf("tf-acc-%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlphaNum))),
- "acl1": config.StringVariable("192.168.0.0/16"),
- "flavor_cpu": config.IntegerVariable(4),
- "flavor_ram": config.IntegerVariable(16),
- "flavor_description": config.StringVariable("SQLServer-Flex-4.16-Single-Standard-EU01"),
- "storage_class": config.StringVariable("premium-perf2-stackit"),
- "storage_size": config.IntegerVariable(40),
- "server_version": config.StringVariable("2022"),
- "replicas": config.IntegerVariable(1),
- "options_retention_days": config.IntegerVariable(64),
- "flavor_id": config.StringVariable("4.16-Single"),
- "backup_schedule": config.StringVariable("0 6 * * *"),
- "username": config.StringVariable(fmt.Sprintf("tf-acc-user-%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlpha))),
- "role": config.StringVariable("##STACKIT_LoginManager##"),
- "region": config.StringVariable(testutil.Region),
+ "project_id": config.StringVariable(testutil.ProjectId),
+ "name": config.StringVariable(fmt.Sprintf("tf-acc-%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlphaNum))),
+ "acl1": config.StringVariable("192.168.0.0/16"),
+ "storage_class": config.StringVariable("premium-perf2-stackit"),
+ "storage_size": config.IntegerVariable(40),
+ "server_version": config.StringVariable("2022"),
+ "replicas": config.IntegerVariable(1),
+ "access_scope": config.StringVariable(string(sqlserverflex.INSTANCENETWORKACCESSSCOPE_PUBLIC)),
+ "retention_days": config.IntegerVariable(32),
+ "flavor_id": config.StringVariable("4.16-Single"),
+ "backup_schedule": config.StringVariable("0 6 * * *"),
+ "username": config.StringVariable(fmt.Sprintf("tf-acc-user-%s", acctest.RandStringFromCharSet(7, acctest.CharSetAlpha))),
+ "role": config.StringVariable("##STACKIT_LoginManager##"),
+ "region": config.StringVariable(testutil.Region),
}
func configVarsMinUpdated() config.Variables {
- temp := maps.Clone(testConfigVarsMax)
+ temp := maps.Clone(testConfigVarsMin)
temp["name"] = config.StringVariable(testutil.ConvertConfigVariable(temp["name"]) + "changed")
return temp
}
@@ -67,6 +63,8 @@ func configVarsMinUpdated() config.Variables {
func configVarsMaxUpdated() config.Variables {
temp := maps.Clone(testConfigVarsMax)
temp["backup_schedule"] = config.StringVariable("0 12 * * *")
+ temp["acl1"] = config.StringVariable("192.168.2.0/16")
+ temp["retention_days"] = config.IntegerVariable(40)
return temp
}
@@ -79,44 +77,24 @@ func TestAccSQLServerFlexMinResource(t *testing.T) {
{
Config: testutil.NewConfigBuilder().BuildProviderConfig() + "\n" + resourceMinConfig,
ConfigVariables: testConfigVarsMin,
+ ConfigPlanChecks: resource.ConfigPlanChecks{
+ PreApply: []plancheck.PlanCheck{
+ plancheck.ExpectResourceAction("stackit_sqlserverflex_instance.instance", plancheck.ResourceActionCreate),
+ plancheck.ExpectResourceAction("stackit_sqlserverflex_user.user", plancheck.ResourceActionCreate),
+ },
+ },
Check: resource.ComposeAggregateTestCheckFunc(
// Instance
resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "project_id", testutil.ConvertConfigVariable(testConfigVarsMin["project_id"])),
resource.TestCheckResourceAttrSet("stackit_sqlserverflex_instance.instance", "instance_id"),
resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "name", testutil.ConvertConfigVariable(testConfigVarsMin["name"])),
resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "acl.#", "1"),
- resource.TestCheckResourceAttrSet("stackit_sqlserverflex_instance.instance", "flavor.id"),
- resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.description", testutil.ConvertConfigVariable(testConfigVarsMin["flavor_description"])),
+ resource.TestCheckResourceAttrSet("stackit_sqlserverflex_instance.instance", "flavor_id"),
+ resource.TestCheckNoResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.id"),
+ resource.TestCheckNoResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.description"),
resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "replicas", testutil.ConvertConfigVariable(testConfigVarsMin["replicas"])),
- resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.cpu", testutil.ConvertConfigVariable(testConfigVarsMin["flavor_cpu"])),
- resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.ram", testutil.ConvertConfigVariable(testConfigVarsMin["flavor_ram"])),
- // User
- resource.TestCheckResourceAttrPair(
- "stackit_sqlserverflex_user.user", "project_id",
- "stackit_sqlserverflex_instance.instance", "project_id",
- ),
- resource.TestCheckResourceAttrPair(
- "stackit_sqlserverflex_user.user", "instance_id",
- "stackit_sqlserverflex_instance.instance", "instance_id",
- ),
- resource.TestCheckResourceAttrSet("stackit_sqlserverflex_user.user", "user_id"),
- resource.TestCheckResourceAttrSet("stackit_sqlserverflex_user.user", "password"),
- ),
- },
- // Update
- {
- Config: testutil.NewConfigBuilder().BuildProviderConfig() + "\n" + resourceMinConfig,
- ConfigVariables: testConfigVarsMin,
- Check: resource.ComposeAggregateTestCheckFunc(
- // Instance
- resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "project_id", testutil.ConvertConfigVariable(testConfigVarsMin["project_id"])),
- resource.TestCheckResourceAttrSet("stackit_sqlserverflex_instance.instance", "instance_id"),
- resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "name", testutil.ConvertConfigVariable(testConfigVarsMin["name"])),
- resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "acl.#", "1"),
- resource.TestCheckResourceAttrSet("stackit_sqlserverflex_instance.instance", "flavor.id"),
- resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.description", testutil.ConvertConfigVariable(testConfigVarsMin["flavor_description"])),
- resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.cpu", testutil.ConvertConfigVariable(testConfigVarsMin["flavor_cpu"])),
- resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.ram", testutil.ConvertConfigVariable(testConfigVarsMin["flavor_ram"])),
+ resource.TestCheckNoResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.cpu"),
+ resource.TestCheckNoResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.ram"),
// User
resource.TestCheckResourceAttrPair(
"stackit_sqlserverflex_user.user", "project_id",
@@ -132,7 +110,13 @@ func TestAccSQLServerFlexMinResource(t *testing.T) {
},
// data source
{
- Config: testutil.NewConfigBuilder().BuildProviderConfig() + "\n" + resourceMinConfig,
+ Config: testutil.NewConfigBuilder().BuildProviderConfig() + "\n" + resourceMinConfig,
+ ConfigPlanChecks: resource.ConfigPlanChecks{
+ PreApply: []plancheck.PlanCheck{
+ plancheck.ExpectResourceAction("stackit_sqlserverflex_instance.instance", plancheck.ResourceActionNoop),
+ plancheck.ExpectResourceAction("stackit_sqlserverflex_user.user", plancheck.ResourceActionNoop),
+ },
+ },
ConfigVariables: testConfigVarsMin,
Check: resource.ComposeAggregateTestCheckFunc(
// Instance data
@@ -152,10 +136,11 @@ func TestAccSQLServerFlexMinResource(t *testing.T) {
),
resource.TestCheckResourceAttr("data.stackit_sqlserverflex_instance.instance", "acl.#", "1"),
+ resource.TestCheckResourceAttrSet("data.stackit_sqlserverflex_instance.instance", "flavor_id"),
resource.TestCheckResourceAttr("data.stackit_sqlserverflex_instance.instance", "flavor.id", testutil.ConvertConfigVariable(testConfigVarsMin["flavor_id"])),
- resource.TestCheckResourceAttr("data.stackit_sqlserverflex_instance.instance", "flavor.description", testutil.ConvertConfigVariable(testConfigVarsMin["flavor_description"])),
- resource.TestCheckResourceAttr("data.stackit_sqlserverflex_instance.instance", "flavor.cpu", testutil.ConvertConfigVariable(testConfigVarsMin["flavor_cpu"])),
- resource.TestCheckResourceAttr("data.stackit_sqlserverflex_instance.instance", "flavor.ram", testutil.ConvertConfigVariable(testConfigVarsMin["flavor_ram"])),
+ resource.TestCheckResourceAttrSet("data.stackit_sqlserverflex_instance.instance", "flavor.description"),
+ resource.TestCheckResourceAttrSet("data.stackit_sqlserverflex_instance.instance", "flavor.cpu"),
+ resource.TestCheckResourceAttrSet("data.stackit_sqlserverflex_instance.instance", "flavor.ram"),
// User data
resource.TestCheckResourceAttr("data.stackit_sqlserverflex_user.user", "project_id", testutil.ConvertConfigVariable(testConfigVarsMin["project_id"])),
@@ -181,9 +166,16 @@ func TestAccSQLServerFlexMinResource(t *testing.T) {
return fmt.Sprintf("%s,%s,%s", testutil.ProjectId, testutil.Region, instanceId), nil
},
- ImportState: true,
- ImportStateVerify: true,
- ImportStateVerifyIgnore: []string{"backup_schedule"},
+ ImportState: true,
+ ImportStateVerify: true,
+ ImportStateVerifyIgnore: []string{
+ "backup_schedule",
+ // Will be added during the import, because it's unknown which attribute defined the flavor
+ "flavor.cpu",
+ "flavor.description",
+ "flavor.id",
+ "flavor.ram",
+ },
ImportStateCheck: func(s []*terraform.InstanceState) error {
if len(s) != 1 {
return fmt.Errorf("expected 1 state, got %d", len(s))
@@ -216,7 +208,13 @@ func TestAccSQLServerFlexMinResource(t *testing.T) {
},
// Update
{
- Config: testutil.NewConfigBuilder().BuildProviderConfig() + "\n" + resourceMinConfig,
+ Config: testutil.NewConfigBuilder().BuildProviderConfig() + "\n" + resourceMinConfig,
+ ConfigPlanChecks: resource.ConfigPlanChecks{
+ PreApply: []plancheck.PlanCheck{
+ plancheck.ExpectResourceAction("stackit_sqlserverflex_instance.instance", plancheck.ResourceActionUpdate),
+ plancheck.ExpectResourceAction("stackit_sqlserverflex_user.user", plancheck.ResourceActionNoop),
+ },
+ },
ConfigVariables: configVarsMinUpdated(),
Check: resource.ComposeAggregateTestCheckFunc(
// Instance data
@@ -224,10 +222,11 @@ func TestAccSQLServerFlexMinResource(t *testing.T) {
resource.TestCheckResourceAttrSet("stackit_sqlserverflex_instance.instance", "instance_id"),
resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "name", testutil.ConvertConfigVariable(configVarsMinUpdated()["name"])),
resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "acl.#", "1"),
- resource.TestCheckResourceAttrSet("stackit_sqlserverflex_instance.instance", "flavor.id"),
- resource.TestCheckResourceAttrSet("stackit_sqlserverflex_instance.instance", "flavor.description"),
- resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.cpu", testutil.ConvertConfigVariable(configVarsMinUpdated()["flavor_cpu"])),
- resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.ram", testutil.ConvertConfigVariable(configVarsMinUpdated()["flavor_ram"])),
+ resource.TestCheckResourceAttrSet("stackit_sqlserverflex_instance.instance", "flavor_id"),
+ resource.TestCheckNoResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.id"),
+ resource.TestCheckNoResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.description"),
+ resource.TestCheckNoResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.cpu"),
+ resource.TestCheckNoResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.ram"),
),
},
// Deletion is done by the framework implicitly
@@ -242,59 +241,32 @@ func TestAccSQLServerFlexMaxResource(t *testing.T) {
Steps: []resource.TestStep{
// Creation
{
- Config: testutil.NewConfigBuilder().BuildProviderConfig() + "\n" + resourceMaxConfig,
- ConfigVariables: testConfigVarsMax,
- Check: resource.ComposeAggregateTestCheckFunc(
- // Instance
- resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "project_id", testutil.ConvertConfigVariable(testConfigVarsMax["project_id"])),
- resource.TestCheckResourceAttrSet("stackit_sqlserverflex_instance.instance", "instance_id"),
- resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "name", testutil.ConvertConfigVariable(testConfigVarsMax["name"])),
- resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "acl.#", "1"),
- resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "acl.0", testutil.ConvertConfigVariable(testConfigVarsMax["acl1"])),
- resource.TestCheckResourceAttrSet("stackit_sqlserverflex_instance.instance", "flavor.id"),
- resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.description", testutil.ConvertConfigVariable(testConfigVarsMax["flavor_description"])),
- resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "replicas", testutil.ConvertConfigVariable(testConfigVarsMax["replicas"])),
- resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.cpu", testutil.ConvertConfigVariable(testConfigVarsMax["flavor_cpu"])),
- resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.ram", testutil.ConvertConfigVariable(testConfigVarsMax["flavor_ram"])),
- resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "storage.class", testutil.ConvertConfigVariable(testConfigVarsMax["storage_class"])),
- resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "storage.size", testutil.ConvertConfigVariable(testConfigVarsMax["storage_size"])),
- resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "version", testutil.ConvertConfigVariable(testConfigVarsMax["server_version"])),
- resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "options.retention_days", testutil.ConvertConfigVariable(testConfigVarsMax["options_retention_days"])),
- resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "backup_schedule", testutil.ConvertConfigVariable(testConfigVarsMax["backup_schedule"])),
- resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "region", testutil.Region),
- // User
- resource.TestCheckResourceAttrPair(
- "stackit_sqlserverflex_user.user", "project_id",
- "stackit_sqlserverflex_instance.instance", "project_id",
- ),
- resource.TestCheckResourceAttrPair(
- "stackit_sqlserverflex_user.user", "instance_id",
- "stackit_sqlserverflex_instance.instance", "instance_id",
- ),
- resource.TestCheckResourceAttrSet("stackit_sqlserverflex_user.user", "user_id"),
- resource.TestCheckResourceAttrSet("stackit_sqlserverflex_user.user", "password"),
- ),
- },
- // Update
- {
- Config: testutil.NewConfigBuilder().BuildProviderConfig() + "\n" + resourceMaxConfig,
+ Config: testutil.NewConfigBuilder().BuildProviderConfig() + "\n" + resourceMaxConfig,
+ ConfigPlanChecks: resource.ConfigPlanChecks{
+ PreApply: []plancheck.PlanCheck{
+ plancheck.ExpectResourceAction("stackit_sqlserverflex_instance.instance", plancheck.ResourceActionCreate),
+ plancheck.ExpectResourceAction("stackit_sqlserverflex_user.user", plancheck.ResourceActionCreate),
+ },
+ },
ConfigVariables: testConfigVarsMax,
Check: resource.ComposeAggregateTestCheckFunc(
// Instance
resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "project_id", testutil.ConvertConfigVariable(testConfigVarsMax["project_id"])),
resource.TestCheckResourceAttrSet("stackit_sqlserverflex_instance.instance", "instance_id"),
resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "name", testutil.ConvertConfigVariable(testConfigVarsMax["name"])),
- resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "acl.#", "1"),
- resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "acl.0", testutil.ConvertConfigVariable(testConfigVarsMax["acl1"])),
- resource.TestCheckResourceAttrSet("stackit_sqlserverflex_instance.instance", "flavor.id"),
- resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.description", testutil.ConvertConfigVariable(testConfigVarsMax["flavor_description"])),
+ resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "network.acl.#", "1"),
+ resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "network.acl.0", testutil.ConvertConfigVariable(testConfigVarsMax["acl1"])),
+ resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "network.access_scope", testutil.ConvertConfigVariable(testConfigVarsMax["access_scope"])),
+ resource.TestCheckResourceAttrSet("stackit_sqlserverflex_instance.instance", "flavor_id"),
+ resource.TestCheckNoResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.id"),
+ resource.TestCheckNoResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.description"),
resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "replicas", testutil.ConvertConfigVariable(testConfigVarsMax["replicas"])),
- resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.cpu", testutil.ConvertConfigVariable(testConfigVarsMax["flavor_cpu"])),
- resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.ram", testutil.ConvertConfigVariable(testConfigVarsMax["flavor_ram"])),
+ resource.TestCheckNoResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.cpu"),
+ resource.TestCheckNoResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.ram"),
resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "storage.class", testutil.ConvertConfigVariable(testConfigVarsMax["storage_class"])),
resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "storage.size", testutil.ConvertConfigVariable(testConfigVarsMax["storage_size"])),
resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "version", testutil.ConvertConfigVariable(testConfigVarsMax["server_version"])),
- resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "options.retention_days", testutil.ConvertConfigVariable(testConfigVarsMax["options_retention_days"])),
+ resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "retention_days", testutil.ConvertConfigVariable(testConfigVarsMax["retention_days"])),
resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "backup_schedule", testutil.ConvertConfigVariable(testConfigVarsMax["backup_schedule"])),
resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "region", testutil.Region),
// User
@@ -333,12 +305,13 @@ func TestAccSQLServerFlexMaxResource(t *testing.T) {
resource.TestCheckResourceAttr("data.stackit_sqlserverflex_instance.instance", "acl.#", "1"),
resource.TestCheckResourceAttr("data.stackit_sqlserverflex_instance.instance", "acl.0", testutil.ConvertConfigVariable(testConfigVarsMax["acl1"])),
- resource.TestCheckResourceAttr("data.stackit_sqlserverflex_instance.instance", "flavor.id", testutil.ConvertConfigVariable(testConfigVarsMax["flavor_id"])),
- resource.TestCheckResourceAttr("data.stackit_sqlserverflex_instance.instance", "flavor.description", testutil.ConvertConfigVariable(testConfigVarsMax["flavor_description"])),
- resource.TestCheckResourceAttr("data.stackit_sqlserverflex_instance.instance", "flavor.cpu", testutil.ConvertConfigVariable(testConfigVarsMax["flavor_cpu"])),
- resource.TestCheckResourceAttr("data.stackit_sqlserverflex_instance.instance", "flavor.ram", testutil.ConvertConfigVariable(testConfigVarsMax["flavor_ram"])),
+ resource.TestCheckResourceAttrSet("data.stackit_sqlserverflex_instance.instance", "flavor_id"),
+ resource.TestCheckResourceAttrSet("data.stackit_sqlserverflex_instance.instance", "flavor.id"),
+ resource.TestCheckResourceAttrSet("data.stackit_sqlserverflex_instance.instance", "flavor.description"),
+ resource.TestCheckResourceAttrSet("data.stackit_sqlserverflex_instance.instance", "flavor.cpu"),
+ resource.TestCheckResourceAttrSet("data.stackit_sqlserverflex_instance.instance", "flavor.ram"),
resource.TestCheckResourceAttr("data.stackit_sqlserverflex_instance.instance", "replicas", testutil.ConvertConfigVariable(testConfigVarsMax["replicas"])),
- resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "options.retention_days", testutil.ConvertConfigVariable(testConfigVarsMax["options_retention_days"])),
+ resource.TestCheckResourceAttr("data.stackit_sqlserverflex_instance.instance", "retention_days", testutil.ConvertConfigVariable(testConfigVarsMax["retention_days"])),
resource.TestCheckResourceAttr("data.stackit_sqlserverflex_instance.instance", "backup_schedule", testutil.ConvertConfigVariable(testConfigVarsMax["backup_schedule"])),
// User data
@@ -367,9 +340,16 @@ func TestAccSQLServerFlexMaxResource(t *testing.T) {
return fmt.Sprintf("%s,%s,%s", testutil.ProjectId, testutil.Region, instanceId), nil
},
- ImportState: true,
- ImportStateVerify: true,
- ImportStateVerifyIgnore: []string{"backup_schedule"},
+ ImportState: true,
+ ImportStateVerify: true,
+ ImportStateVerifyIgnore: []string{
+ "backup_schedule",
+ // Will be added during the import, because it's unknown which attribute defined the flavor
+ "flavor.cpu",
+ "flavor.description",
+ "flavor.id",
+ "flavor.ram",
+ },
ImportStateCheck: func(s []*terraform.InstanceState) error {
if len(s) != 1 {
return fmt.Errorf("expected 1 state, got %d", len(s))
@@ -407,6 +387,12 @@ func TestAccSQLServerFlexMaxResource(t *testing.T) {
{
Config: testutil.NewConfigBuilder().BuildProviderConfig() + "\n" + resourceMaxConfig,
ConfigVariables: configVarsMaxUpdated(),
+ ConfigPlanChecks: resource.ConfigPlanChecks{
+ PreApply: []plancheck.PlanCheck{
+ plancheck.ExpectResourceAction("stackit_sqlserverflex_instance.instance", plancheck.ResourceActionUpdate),
+ plancheck.ExpectResourceAction("stackit_sqlserverflex_user.user", plancheck.ResourceActionNoop),
+ },
+ },
Check: resource.ComposeAggregateTestCheckFunc(
// Instance data
resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "project_id", testutil.ConvertConfigVariable(configVarsMaxUpdated()["project_id"])),
@@ -414,15 +400,16 @@ func TestAccSQLServerFlexMaxResource(t *testing.T) {
resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "name", testutil.ConvertConfigVariable(configVarsMaxUpdated()["name"])),
resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "acl.#", "1"),
resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "acl.0", testutil.ConvertConfigVariable(configVarsMaxUpdated()["acl1"])),
- resource.TestCheckResourceAttrSet("stackit_sqlserverflex_instance.instance", "flavor.id"),
- resource.TestCheckResourceAttrSet("stackit_sqlserverflex_instance.instance", "flavor.description"),
- resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.cpu", testutil.ConvertConfigVariable(configVarsMaxUpdated()["flavor_cpu"])),
- resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.ram", testutil.ConvertConfigVariable(configVarsMaxUpdated()["flavor_ram"])),
+ resource.TestCheckResourceAttrSet("stackit_sqlserverflex_instance.instance", "flavor_id"),
+ resource.TestCheckNoResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.id"),
+ resource.TestCheckNoResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.description"),
+ resource.TestCheckNoResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.cpu"),
+ resource.TestCheckNoResourceAttr("stackit_sqlserverflex_instance.instance", "flavor.ram"),
resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "replicas", testutil.ConvertConfigVariable(configVarsMaxUpdated()["replicas"])),
resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "storage.class", testutil.ConvertConfigVariable(configVarsMaxUpdated()["storage_class"])),
resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "storage.size", testutil.ConvertConfigVariable(configVarsMaxUpdated()["storage_size"])),
resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "version", testutil.ConvertConfigVariable(configVarsMaxUpdated()["server_version"])),
- resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "options.retention_days", testutil.ConvertConfigVariable(configVarsMaxUpdated()["options_retention_days"])),
+ resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "retention_days", testutil.ConvertConfigVariable(configVarsMaxUpdated()["retention_days"])),
resource.TestCheckResourceAttr("stackit_sqlserverflex_instance.instance", "backup_schedule", testutil.ConvertConfigVariable(configVarsMaxUpdated()["backup_schedule"])),
),
},
@@ -453,19 +440,19 @@ func testAccChecksqlserverflexDestroy(s *terraform.State) error {
return fmt.Errorf("getting instancesResp: %w", err)
}
- items := instancesResp.Items
+ items := instancesResp.Instances
for i := range items {
- if items[i].Id == nil {
+ if items[i].Id == "" {
continue
}
- if utils.Contains(instancesToDestroy, *items[i].Id) {
- err := client.DefaultAPI.DeleteInstance(ctx, testutil.ProjectId, *items[i].Id, testutil.Region).Execute()
+ if utils.Contains(instancesToDestroy, items[i].Id) {
+ err := client.DefaultAPI.DeleteInstance(ctx, testutil.ProjectId, items[i].Id, testutil.Region).Execute()
if err != nil {
- return fmt.Errorf("destroying instance %s during CheckDestroy: %w", *items[i].Id, err)
+ return fmt.Errorf("destroying instance %s during CheckDestroy: %w", items[i].Id, err)
}
- _, err = wait.DeleteInstanceWaitHandler(ctx, client.DefaultAPI, testutil.ProjectId, *items[i].Id, testutil.Region).WaitWithContext(ctx)
+ _, err = wait.DeleteInstanceWaitHandler(ctx, client.DefaultAPI, testutil.ProjectId, items[i].Id, testutil.Region).WaitWithContext(ctx)
if err != nil {
- return fmt.Errorf("destroying instance %s during CheckDestroy: waiting for deletion %w", *items[i].Id, err)
+ return fmt.Errorf("destroying instance %s during CheckDestroy: waiting for deletion %w", items[i].Id, err)
}
}
}
diff --git a/stackit/internal/services/sqlserverflex/sqlserverflex_test.go b/stackit/internal/services/sqlserverflex/sqlserverflex_test.go
index 044c59d98..1da299f65 100644
--- a/stackit/internal/services/sqlserverflex/sqlserverflex_test.go
+++ b/stackit/internal/services/sqlserverflex/sqlserverflex_test.go
@@ -9,7 +9,7 @@ import (
"github.com/google/uuid"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
- sqlserverflex "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v2api"
+ sqlserverflex "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v3api"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil"
)
@@ -45,12 +45,12 @@ resource "stackit_sqlserverflex_instance" "instance" {
`, region, s.Server.URL, projectId, name, flavorCpu, flavorRam)
flavor := testutil.MockResponse{
ToJsonBody: &sqlserverflex.ListFlavorsResponse{
- Flavors: []sqlserverflex.InstanceFlavorEntry{
+ Flavors: []sqlserverflex.ListFlavors{
{
- Cpu: new(int32(flavorCpu)),
- Memory: new(int32(flavorRam)),
- Id: new(flavorId),
- Description: new("test-flavor-id"),
+ Cpu: flavorCpu,
+ Memory: flavorRam,
+ Id: flavorId,
+ Description: "test-flavor-id",
},
},
},
@@ -66,7 +66,7 @@ resource "stackit_sqlserverflex_instance" "instance" {
testutil.MockResponse{
Description: "create",
ToJsonBody: sqlserverflex.CreateInstanceResponse{
- Id: new(instanceId),
+ Id: instanceId,
},
},
testutil.MockResponse{
@@ -84,7 +84,7 @@ resource "stackit_sqlserverflex_instance" "instance" {
testutil.MockResponse{
Description: "refresh",
Handler: func(w http.ResponseWriter, req *http.Request) {
- expected := fmt.Sprintf("/v2/projects/%s/regions/%s/instances/%s", projectId, region, instanceId)
+ expected := fmt.Sprintf("/v3/projects/%s/regions/%s/instances/%s", projectId, region, instanceId)
if req.URL.Path != expected {
t.Errorf("expected request to %s, got %s", expected, req.URL.Path)
}
diff --git a/stackit/internal/services/sqlserverflex/testdata/resource-max.tf b/stackit/internal/services/sqlserverflex/testdata/resource-max.tf
index a0cf700a1..9797377e5 100644
--- a/stackit/internal/services/sqlserverflex/testdata/resource-max.tf
+++ b/stackit/internal/services/sqlserverflex/testdata/resource-max.tf
@@ -1,11 +1,11 @@
variable "project_id" {}
variable "name" {}
variable "acl1" {}
-variable "flavor_cpu" {}
-variable "flavor_ram" {}
+variable "flavor_id" {}
variable "storage_class" {}
variable "storage_size" {}
-variable "options_retention_days" {}
+variable "access_scope" {}
+variable "retention_days" {}
variable "backup_schedule" {}
variable "username" {}
variable "role" {}
@@ -15,19 +15,17 @@ variable "region" {}
resource "stackit_sqlserverflex_instance" "instance" {
project_id = var.project_id
name = var.name
- acl = [var.acl1]
- flavor = {
- cpu = var.flavor_cpu
- ram = var.flavor_ram
- }
+ flavor_id = var.flavor_id
storage = {
class = var.storage_class
size = var.storage_size
}
- version = var.server_version
- options = {
- retention_days = var.options_retention_days
+ network = {
+ acl = [var.acl1]
+ access_scope = var.access_scope
}
+ retention_days = var.retention_days
+ version = var.server_version
backup_schedule = var.backup_schedule
region = var.region
}
diff --git a/stackit/internal/services/sqlserverflex/testdata/resource-min.tf b/stackit/internal/services/sqlserverflex/testdata/resource-min.tf
index 3953ddf16..c7c783d03 100644
--- a/stackit/internal/services/sqlserverflex/testdata/resource-min.tf
+++ b/stackit/internal/services/sqlserverflex/testdata/resource-min.tf
@@ -1,17 +1,13 @@
variable "project_id" {}
variable "name" {}
-variable "flavor_cpu" {}
-variable "flavor_ram" {}
+variable "flavor_id" {}
variable "username" {}
variable "role" {}
resource "stackit_sqlserverflex_instance" "instance" {
project_id = var.project_id
name = var.name
- flavor = {
- cpu = var.flavor_cpu
- ram = var.flavor_ram
- }
+ flavor_id = var.flavor_id
}
resource "stackit_sqlserverflex_user" "user" {
diff --git a/stackit/internal/services/sqlserverflex/user/datasource.go b/stackit/internal/services/sqlserverflex/user/datasource.go
index b5ed2b5c1..a5e79ad19 100644
--- a/stackit/internal/services/sqlserverflex/user/datasource.go
+++ b/stackit/internal/services/sqlserverflex/user/datasource.go
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"net/http"
+ "strconv"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
sqlserverflexUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/sqlserverflex/utils"
@@ -19,7 +20,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
- sqlserverflex "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v2api"
+ sqlserverflex "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v3api"
)
// Ensure the implementation satisfies the expected interfaces.
@@ -35,7 +36,7 @@ type DataSourceModel struct {
Username types.String `tfsdk:"username"`
Roles types.Set `tfsdk:"roles"`
Host types.String `tfsdk:"host"`
- Port types.Int64 `tfsdk:"port"`
+ Port types.Int32 `tfsdk:"port"`
Region types.String `tfsdk:"region"`
}
@@ -127,7 +128,7 @@ func (r *userDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, r
"host": schema.StringAttribute{
Computed: true,
},
- "port": schema.Int64Attribute{
+ "port": schema.Int32Attribute{
Computed: true,
},
"region": schema.StringAttribute{
@@ -152,21 +153,27 @@ func (r *userDataSource) Read(ctx context.Context, req datasource.ReadRequest, r
projectId := model.ProjectId.ValueString()
instanceId := model.InstanceId.ValueString()
- userId := model.UserId.ValueString()
+ userIdStr := model.UserId.ValueString()
region := r.providerData.GetRegionWithOverride(model.Region)
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "instance_id", instanceId)
- ctx = tflog.SetField(ctx, "user_id", userId)
+ ctx = tflog.SetField(ctx, "user_id", userIdStr)
ctx = tflog.SetField(ctx, "region", region)
- recordSetResp, err := r.client.DefaultAPI.GetUser(ctx, projectId, instanceId, userId, region).Execute()
+ userId, err := strconv.ParseInt(userIdStr, 10, 64)
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading user", fmt.Sprintf("Parsing user ID: %v", err))
+ return
+ }
+
+ recordSetResp, err := r.client.DefaultAPI.GetUser(ctx, projectId, region, instanceId, userId).Execute()
if err != nil {
utils.LogError(
ctx,
&resp.Diagnostics,
err,
"Reading user",
- fmt.Sprintf("User with ID %q or instance with ID %q does not exist in project %q.", userId, instanceId, projectId),
+ fmt.Sprintf("User with ID %d or instance with ID %q does not exist in project %q.", userId, instanceId, projectId),
map[int]string{
http.StatusForbidden: fmt.Sprintf("Project with ID %q not found or forbidden access", projectId),
},
@@ -194,19 +201,18 @@ func (r *userDataSource) Read(ctx context.Context, req datasource.ReadRequest, r
}
func mapDataSourceFields(userResp *sqlserverflex.GetUserResponse, model *DataSourceModel, region string) error {
- if userResp == nil || userResp.Item == nil {
+ if userResp == nil {
return fmt.Errorf("response is nil")
}
if model == nil {
return fmt.Errorf("model input is nil")
}
- user := userResp.Item
var userId string
if model.UserId.ValueString() != "" {
userId = model.UserId.ValueString()
- } else if user.Id != nil {
- userId = *user.Id
+ } else if userResp.Id != 0 {
+ userId = strconv.FormatInt(userResp.Id, 10)
} else {
return fmt.Errorf("user id not present")
}
@@ -214,13 +220,13 @@ func mapDataSourceFields(userResp *sqlserverflex.GetUserResponse, model *DataSou
model.ProjectId.ValueString(), region, model.InstanceId.ValueString(), userId,
)
model.UserId = types.StringValue(userId)
- model.Username = types.StringPointerValue(user.Username)
+ model.Username = types.StringValue(userResp.Username)
- if user.Roles == nil {
+ if userResp.Roles == nil {
model.Roles = types.SetNull(types.StringType)
} else {
roles := []attr.Value{}
- for _, role := range user.Roles {
+ for _, role := range userResp.Roles {
roles = append(roles, types.StringValue(role))
}
rolesSet, diags := types.SetValue(types.StringType, roles)
@@ -229,8 +235,8 @@ func mapDataSourceFields(userResp *sqlserverflex.GetUserResponse, model *DataSou
}
model.Roles = rolesSet
}
- model.Host = types.StringPointerValue(user.Host)
- model.Port = types.Int64PointerValue(user.Port)
+ model.Host = types.StringValue(userResp.Host)
+ model.Port = types.Int32Value(userResp.Port)
model.Region = types.StringValue(region)
return nil
}
diff --git a/stackit/internal/services/sqlserverflex/user/datasource_test.go b/stackit/internal/services/sqlserverflex/user/datasource_test.go
index 7cb9e868d..a9d5609e5 100644
--- a/stackit/internal/services/sqlserverflex/user/datasource_test.go
+++ b/stackit/internal/services/sqlserverflex/user/datasource_test.go
@@ -6,7 +6,7 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/types"
- sqlserverflex "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v2api"
+ sqlserverflex "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v3api"
)
func TestMapDataSourceFields(t *testing.T) {
@@ -20,19 +20,17 @@ func TestMapDataSourceFields(t *testing.T) {
}{
{
"default_values",
- &sqlserverflex.GetUserResponse{
- Item: &sqlserverflex.UserResponseUser{},
- },
+ &sqlserverflex.GetUserResponse{},
testRegion,
DataSourceModel{
Id: types.StringValue("pid,region,iid,uid"),
UserId: types.StringValue("uid"),
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
- Username: types.StringNull(),
+ Username: types.StringValue(""),
Roles: types.SetNull(types.StringType),
- Host: types.StringNull(),
- Port: types.Int64Null(),
+ Host: types.StringValue(""),
+ Port: types.Int32Value(0),
Region: types.StringValue(testRegion),
},
true,
@@ -40,16 +38,14 @@ func TestMapDataSourceFields(t *testing.T) {
{
"simple_values",
&sqlserverflex.GetUserResponse{
- Item: &sqlserverflex.UserResponseUser{
- Roles: []string{
- "role_1",
- "role_2",
- "",
- },
- Username: new("username"),
- Host: new("host"),
- Port: new(int64(1234)),
+ Roles: []string{
+ "role_1",
+ "role_2",
+ "",
},
+ Username: "username",
+ Host: "host",
+ Port: int32(1234),
},
testRegion,
DataSourceModel{
@@ -64,7 +60,7 @@ func TestMapDataSourceFields(t *testing.T) {
types.StringValue(""),
}),
Host: types.StringValue("host"),
- Port: types.Int64Value(1234),
+ Port: types.Int32Value(1234),
Region: types.StringValue(testRegion),
},
true,
@@ -72,24 +68,20 @@ func TestMapDataSourceFields(t *testing.T) {
{
"null_fields_and_int_conversions",
&sqlserverflex.GetUserResponse{
- Item: &sqlserverflex.UserResponseUser{
- Id: new("uid"),
- Roles: []string{},
- Username: nil,
- Host: nil,
- Port: new(int64(2123456789)),
- },
+ Id: 1,
+ Roles: []string{},
+ Port: 2123456789,
},
testRegion,
DataSourceModel{
- Id: types.StringValue("pid,region,iid,uid"),
- UserId: types.StringValue("uid"),
+ Id: types.StringValue("pid,region,iid,1"),
+ UserId: types.StringValue("1"),
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
- Username: types.StringNull(),
+ Username: types.StringValue(""),
Roles: types.SetValueMust(types.StringType, []attr.Value{}),
- Host: types.StringNull(),
- Port: types.Int64Value(2123456789),
+ Host: types.StringValue(""),
+ Port: types.Int32Value(2123456789),
Region: types.StringValue(testRegion),
},
true,
@@ -110,9 +102,7 @@ func TestMapDataSourceFields(t *testing.T) {
},
{
"no_resource_id",
- &sqlserverflex.GetUserResponse{
- Item: &sqlserverflex.UserResponseUser{},
- },
+ &sqlserverflex.GetUserResponse{},
testRegion,
DataSourceModel{},
false,
diff --git a/stackit/internal/services/sqlserverflex/user/resource.go b/stackit/internal/services/sqlserverflex/user/resource.go
index ef48249a9..6f2b11401 100644
--- a/stackit/internal/services/sqlserverflex/user/resource.go
+++ b/stackit/internal/services/sqlserverflex/user/resource.go
@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"net/http"
+ "strconv"
"strings"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier"
@@ -27,7 +28,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
- sqlserverflex "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v2api"
+ sqlserverflex "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v3api"
)
// Ensure the implementation satisfies the expected interfaces.
@@ -47,7 +48,7 @@ type Model struct {
Roles types.Set `tfsdk:"roles"`
Password types.String `tfsdk:"password"`
Host types.String `tfsdk:"host"`
- Port types.Int64 `tfsdk:"port"`
+ Port types.Int32 `tfsdk:"port"`
Region types.String `tfsdk:"region"`
// RotateWhenChanged is a map of arbitrary key/value pairs that will force
// recreation of the resource when they change, enabling resource rotation based on
@@ -199,7 +200,7 @@ func (r *userResource) Schema(_ context.Context, _ resource.SchemaRequest, resp
"host": schema.StringAttribute{
Computed: true,
},
- "port": schema.Int64Attribute{
+ "port": schema.Int32Attribute{
Computed: true,
},
"region": schema.StringAttribute{
@@ -262,7 +263,7 @@ func (r *userResource) Create(ctx context.Context, req resource.CreateRequest, r
return
}
// Create new user
- userResp, err := r.client.DefaultAPI.CreateUser(ctx, projectId, instanceId, region).CreateUserPayload(*payload).Execute()
+ userResp, err := r.client.DefaultAPI.CreateUser(ctx, projectId, region, instanceId).CreateUserPayload(*payload).Execute()
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating user", fmt.Sprintf("Calling API: %v", err))
return
@@ -270,11 +271,11 @@ func (r *userResource) Create(ctx context.Context, req resource.CreateRequest, r
ctx = core.LogResponse(ctx)
- if userResp == nil || userResp.Item == nil || userResp.Item.Id == nil || *userResp.Item.Id == "" {
+ if userResp == nil || userResp.Id == 0 {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating user", "API didn't return user Id. A user might have been created")
return
}
- userId := *userResp.Item.Id
+ userId := strconv.FormatInt(userResp.Id, 10)
ctx = utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{
"project_id": projectId,
"region": region,
@@ -309,8 +310,8 @@ func (r *userResource) Read(ctx context.Context, req resource.ReadRequest, resp
projectId := model.ProjectId.ValueString()
instanceId := model.InstanceId.ValueString()
- userId := model.UserId.ValueString()
- if userId == "" {
+ userIdStr := model.UserId.ValueString()
+ if userIdStr == "" {
// Resource not yet created; ID is unknown.
resp.State.RemoveResource(ctx)
return
@@ -318,13 +319,17 @@ func (r *userResource) Read(ctx context.Context, req resource.ReadRequest, resp
region := r.providerData.GetRegionWithOverride(model.Region)
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "instance_id", instanceId)
- ctx = tflog.SetField(ctx, "user_id", userId)
+ ctx = tflog.SetField(ctx, "user_id", userIdStr)
ctx = tflog.SetField(ctx, "region", region)
+ userId, err := strconv.ParseInt(userIdStr, 10, 64)
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading user", fmt.Sprintf("Parsing user ID: %v", err))
+ return
+ }
- recordSetResp, err := r.client.DefaultAPI.GetUser(ctx, projectId, instanceId, userId, region).Execute()
+ userResp, err := r.client.DefaultAPI.GetUser(ctx, projectId, region, instanceId, userId).Execute()
if err != nil {
- var oapiErr *oapierror.GenericOpenAPIError
- if errors.As(err, &oapiErr) && oapiErr.StatusCode == http.StatusNotFound {
+ if oapiErr, ok := errors.AsType[*oapierror.GenericOpenAPIError](err); ok && oapiErr.StatusCode == http.StatusNotFound {
resp.State.RemoveResource(ctx)
return
}
@@ -335,7 +340,7 @@ func (r *userResource) Read(ctx context.Context, req resource.ReadRequest, resp
ctx = core.LogResponse(ctx)
// Map response body to schema
- err = mapFields(recordSetResp, &model, region)
+ err = mapFields(userResp, &model, region)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading user", fmt.Sprintf("Processing API payload: %v", err))
return
@@ -370,18 +375,23 @@ func (r *userResource) Delete(ctx context.Context, req resource.DeleteRequest, r
projectId := model.ProjectId.ValueString()
instanceId := model.InstanceId.ValueString()
- userId := model.UserId.ValueString()
+ userIdStr := model.UserId.ValueString()
region := model.Region.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "instance_id", instanceId)
- ctx = tflog.SetField(ctx, "user_id", userId)
+ ctx = tflog.SetField(ctx, "user_id", userIdStr)
ctx = tflog.SetField(ctx, "region", region)
- // Delete existing record set
- err := r.client.DefaultAPI.DeleteUser(ctx, projectId, instanceId, userId, region).Execute()
+ userId, err := strconv.ParseInt(userIdStr, 10, 64)
+ if err != nil {
+ core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting user", fmt.Sprintf("Parsing user ID: %v", err))
+ return
+ }
+
+ // Delete existing user
+ err = r.client.DefaultAPI.DeleteUser(ctx, projectId, region, instanceId, userId).Execute()
if err != nil {
- var oapiErr *oapierror.GenericOpenAPIError
- if errors.As(err, &oapiErr) && oapiErr.StatusCode == http.StatusNotFound {
+ if oapiErr, ok := errors.AsType[*oapierror.GenericOpenAPIError](err); ok && oapiErr.StatusCode == http.StatusNotFound {
return
}
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting user", fmt.Sprintf("Calling API: %v", err))
@@ -394,7 +404,7 @@ func (r *userResource) Delete(ctx context.Context, req resource.DeleteRequest, r
}
// ImportState imports a resource into the Terraform state on success.
-// The expected format of the resource import identifier is: project_id,zone_id,record_set_id
+// The expected format of the resource import identifier is: project_id,region,instance_id,user_id
func (r *userResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
idParts := strings.Split(req.ID, core.Separator)
if len(idParts) != 4 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" || idParts[3] == "" {
@@ -419,30 +429,26 @@ func (r *userResource) ImportState(ctx context.Context, req resource.ImportState
}
func mapFieldsCreate(userResp *sqlserverflex.CreateUserResponse, model *Model, region string) error {
- if userResp == nil || userResp.Item == nil {
+ if userResp == nil {
return fmt.Errorf("response is nil")
}
if model == nil {
return fmt.Errorf("model input is nil")
}
- user := userResp.Item
- if user.Id == nil {
+ if userResp.Id == 0 {
return fmt.Errorf("user id not present")
}
- userId := *user.Id
+ userId := strconv.FormatInt(userResp.Id, 10)
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, model.InstanceId.ValueString(), userId)
model.UserId = types.StringValue(userId)
- model.Username = types.StringPointerValue(user.Username)
+ model.Username = types.StringValue(userResp.Username)
- if user.Password == nil {
- return fmt.Errorf("user password not present")
- }
- model.Password = types.StringValue(*user.Password)
+ model.Password = types.StringValue(userResp.Password)
- if user.Roles != nil {
+ if userResp.Roles != nil {
roles := []attr.Value{}
- for _, role := range user.Roles {
+ for _, role := range userResp.Roles {
roles = append(roles, types.StringValue(role))
}
rolesSet, diags := types.SetValue(types.StringType, roles)
@@ -456,26 +462,25 @@ func mapFieldsCreate(userResp *sqlserverflex.CreateUserResponse, model *Model, r
model.Roles = types.SetNull(types.StringType)
}
- model.Host = types.StringPointerValue(user.Host)
- model.Port = types.Int64PointerValue(user.Port)
+ model.Host = types.StringValue(userResp.Host)
+ model.Port = types.Int32Value(userResp.Port)
model.Region = types.StringValue(region)
return nil
}
func mapFields(userResp *sqlserverflex.GetUserResponse, model *Model, region string) error {
- if userResp == nil || userResp.Item == nil {
+ if userResp == nil {
return fmt.Errorf("response is nil")
}
if model == nil {
return fmt.Errorf("model input is nil")
}
- user := userResp.Item
var userId string
if model.UserId.ValueString() != "" {
userId = model.UserId.ValueString()
- } else if user.Id != nil {
- userId = *user.Id
+ } else if userResp.Id != 0 {
+ userId = strconv.FormatInt(userResp.Id, 10)
} else {
return fmt.Errorf("user id not present")
}
@@ -486,11 +491,11 @@ func mapFields(userResp *sqlserverflex.GetUserResponse, model *Model, region str
userId,
)
model.UserId = types.StringValue(userId)
- model.Username = types.StringPointerValue(user.Username)
+ model.Username = types.StringValue(userResp.Username)
- if user.Roles != nil {
+ if userResp.Roles != nil {
roles := []attr.Value{}
- for _, role := range user.Roles {
+ for _, role := range userResp.Roles {
roles = append(roles, types.StringValue(role))
}
rolesSet, diags := types.SetValue(types.StringType, roles)
@@ -504,8 +509,8 @@ func mapFields(userResp *sqlserverflex.GetUserResponse, model *Model, region str
model.Roles = types.SetNull(types.StringType)
}
- model.Host = types.StringPointerValue(user.Host)
- model.Port = types.Int64PointerValue(user.Port)
+ model.Host = types.StringValue(userResp.Host)
+ model.Port = types.Int32Value(userResp.Port)
model.Region = types.StringValue(region)
return nil
}
diff --git a/stackit/internal/services/sqlserverflex/user/resource_test.go b/stackit/internal/services/sqlserverflex/user/resource_test.go
index 904fa36dd..78d15dc66 100644
--- a/stackit/internal/services/sqlserverflex/user/resource_test.go
+++ b/stackit/internal/services/sqlserverflex/user/resource_test.go
@@ -6,7 +6,7 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/types"
- sqlserverflex "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v2api"
+ sqlserverflex "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v3api"
)
func TestMapFieldsCreate(t *testing.T) {
@@ -21,22 +21,20 @@ func TestMapFieldsCreate(t *testing.T) {
{
"default_values",
&sqlserverflex.CreateUserResponse{
- Item: &sqlserverflex.SingleUser{
- Id: new("uid"),
- Password: new(""),
- },
+ Id: 1,
+ Password: "secret",
},
testRegion,
Model{
- Id: types.StringValue("pid,region,iid,uid"),
- UserId: types.StringValue("uid"),
+ Id: types.StringValue("pid,region,iid,1"),
+ UserId: types.StringValue("1"),
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
- Username: types.StringNull(),
+ Username: types.StringValue(""),
Roles: types.SetNull(types.StringType),
- Password: types.StringValue(""),
- Host: types.StringNull(),
- Port: types.Int64Null(),
+ Password: types.StringValue("secret"),
+ Host: types.StringValue(""),
+ Port: types.Int32Value(0),
Region: types.StringValue(testRegion),
RotateWhenChanged: types.MapNull(types.StringType),
},
@@ -45,23 +43,21 @@ func TestMapFieldsCreate(t *testing.T) {
{
"simple_values",
&sqlserverflex.CreateUserResponse{
- Item: &sqlserverflex.SingleUser{
- Id: new("uid"),
- Roles: []string{
- "role_1",
- "role_2",
- "",
- },
- Username: new("username"),
- Password: new("password"),
- Host: new("host"),
- Port: new(int64(1234)),
+ Id: 1,
+ Roles: []string{
+ "role_1",
+ "role_2",
+ "",
},
+ Username: "username",
+ Password: "password",
+ Host: "host",
+ Port: 1234,
},
testRegion,
Model{
- Id: types.StringValue("pid,region,iid,uid"),
- UserId: types.StringValue("uid"),
+ Id: types.StringValue("pid,region,iid,1"),
+ UserId: types.StringValue("1"),
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
Username: types.StringValue("username"),
@@ -72,7 +68,7 @@ func TestMapFieldsCreate(t *testing.T) {
}),
Password: types.StringValue("password"),
Host: types.StringValue("host"),
- Port: types.Int64Value(1234),
+ Port: types.Int32Value(1234),
Region: types.StringValue(testRegion),
RotateWhenChanged: types.MapNull(types.StringType),
},
@@ -81,26 +77,22 @@ func TestMapFieldsCreate(t *testing.T) {
{
"null_fields_and_int_conversions",
&sqlserverflex.CreateUserResponse{
- Item: &sqlserverflex.SingleUser{
- Id: new("uid"),
- Roles: []string{},
- Username: nil,
- Password: new(""),
- Host: nil,
- Port: new(int64(2123456789)),
- },
+ Id: 1,
+ Roles: []string{},
+ Password: "",
+ Port: 2123456789,
},
testRegion,
Model{
- Id: types.StringValue("pid,region,iid,uid"),
- UserId: types.StringValue("uid"),
+ Id: types.StringValue("pid,region,iid,1"),
+ UserId: types.StringValue("1"),
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
- Username: types.StringNull(),
+ Username: types.StringValue(""),
Roles: types.SetValueMust(types.StringType, []attr.Value{}),
Password: types.StringValue(""),
- Host: types.StringNull(),
- Port: types.Int64Value(2123456789),
+ Host: types.StringValue(""),
+ Port: types.Int32Value(2123456789),
Region: types.StringValue(testRegion),
RotateWhenChanged: types.MapNull(types.StringType),
},
@@ -122,20 +114,14 @@ func TestMapFieldsCreate(t *testing.T) {
},
{
"no_resource_id",
- &sqlserverflex.CreateUserResponse{
- Item: &sqlserverflex.SingleUser{},
- },
+ &sqlserverflex.CreateUserResponse{},
testRegion,
Model{},
false,
},
{
"no_password",
- &sqlserverflex.CreateUserResponse{
- Item: &sqlserverflex.SingleUser{
- Id: new("uid"),
- },
- },
+ &sqlserverflex.CreateUserResponse{},
testRegion,
Model{},
false,
@@ -176,19 +162,17 @@ func TestMapFields(t *testing.T) {
}{
{
"default_values",
- &sqlserverflex.GetUserResponse{
- Item: &sqlserverflex.UserResponseUser{},
- },
+ &sqlserverflex.GetUserResponse{},
testRegion,
Model{
Id: types.StringValue("pid,region,iid,uid"),
UserId: types.StringValue("uid"),
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
- Username: types.StringNull(),
+ Username: types.StringValue(""),
Roles: types.SetNull(types.StringType),
- Host: types.StringNull(),
- Port: types.Int64Null(),
+ Host: types.StringValue(""),
+ Port: types.Int32Value(0),
Region: types.StringValue(testRegion),
RotateWhenChanged: types.MapNull(types.StringType),
},
@@ -197,16 +181,14 @@ func TestMapFields(t *testing.T) {
{
"simple_values",
&sqlserverflex.GetUserResponse{
- Item: &sqlserverflex.UserResponseUser{
- Roles: []string{
- "role_1",
- "role_2",
- "",
- },
- Username: new("username"),
- Host: new("host"),
- Port: new(int64(1234)),
+ Roles: []string{
+ "role_1",
+ "role_2",
+ "",
},
+ Username: "username",
+ Host: "host",
+ Port: 1234,
},
testRegion,
Model{
@@ -221,7 +203,7 @@ func TestMapFields(t *testing.T) {
types.StringValue(""),
}),
Host: types.StringValue("host"),
- Port: types.Int64Value(1234),
+ Port: types.Int32Value(1234),
Region: types.StringValue(testRegion),
RotateWhenChanged: types.MapNull(types.StringType),
},
@@ -230,24 +212,20 @@ func TestMapFields(t *testing.T) {
{
"null_fields_and_int_conversions",
&sqlserverflex.GetUserResponse{
- Item: &sqlserverflex.UserResponseUser{
- Id: new("uid"),
- Roles: []string{},
- Username: nil,
- Host: nil,
- Port: new(int64(2123456789)),
- },
+ Id: 1,
+ Roles: []string{},
+ Port: 2123456789,
},
testRegion,
Model{
- Id: types.StringValue("pid,region,iid,uid"),
- UserId: types.StringValue("uid"),
+ Id: types.StringValue("pid,region,iid,1"),
+ UserId: types.StringValue("1"),
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
- Username: types.StringNull(),
+ Username: types.StringValue(""),
Roles: types.SetValueMust(types.StringType, []attr.Value{}),
- Host: types.StringNull(),
- Port: types.Int64Value(2123456789),
+ Host: types.StringValue(""),
+ Port: types.Int32Value(2123456789),
Region: types.StringValue(testRegion),
RotateWhenChanged: types.MapNull(types.StringType),
},
@@ -269,9 +247,7 @@ func TestMapFields(t *testing.T) {
},
{
"no_resource_id",
- &sqlserverflex.GetUserResponse{
- Item: &sqlserverflex.UserResponseUser{},
- },
+ &sqlserverflex.GetUserResponse{},
testRegion,
Model{},
false,
diff --git a/stackit/internal/services/sqlserverflex/utils/util.go b/stackit/internal/services/sqlserverflex/utils/util.go
index 46e2a1796..f4b50fe52 100644
--- a/stackit/internal/services/sqlserverflex/utils/util.go
+++ b/stackit/internal/services/sqlserverflex/utils/util.go
@@ -4,7 +4,7 @@ import (
"context"
"fmt"
- sqlserverflex "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v2api"
+ sqlserverflex "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v3api"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/stackitcloud/stackit-sdk-go/core/config"
diff --git a/stackit/internal/services/sqlserverflex/utils/util_test.go b/stackit/internal/services/sqlserverflex/utils/util_test.go
index 489d6a610..d0ea73d82 100644
--- a/stackit/internal/services/sqlserverflex/utils/util_test.go
+++ b/stackit/internal/services/sqlserverflex/utils/util_test.go
@@ -10,7 +10,7 @@ import (
sdkClients "github.com/stackitcloud/stackit-sdk-go/core/clients"
"github.com/stackitcloud/stackit-sdk-go/core/config"
- sqlserverflex "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v2api"
+ sqlserverflex "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v3api"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
diff --git a/stackit/internal/utils/planmodifiers/int32planmodifier/use_state_for_unknown_if.go b/stackit/internal/utils/planmodifiers/int32planmodifier/use_state_for_unknown_if.go
index f06211b78..3a1dd58e0 100644
--- a/stackit/internal/utils/planmodifiers/int32planmodifier/use_state_for_unknown_if.go
+++ b/stackit/internal/utils/planmodifiers/int32planmodifier/use_state_for_unknown_if.go
@@ -4,6 +4,7 @@ import (
"context"
"github.com/hashicorp/terraform-plugin-framework/diag"
+ "github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
)
@@ -14,20 +15,18 @@ type UseStateForUnknownFuncResponse struct {
}
// UseStateForUnknownIfFunc is a conditional function used in UseStateForUnknownIf
-type UseStateForUnknownIfFunc func(context.Context, string, planmodifier.Int32Request, *UseStateForUnknownFuncResponse)
+type UseStateForUnknownIfFunc func(context.Context, planmodifier.Int32Request, *UseStateForUnknownFuncResponse)
type useStateForUnknownIf struct {
- ifFunc UseStateForUnknownIfFunc
- attributeName string
- description string
+ ifFunc UseStateForUnknownIfFunc
+ description string
}
// UseStateForUnknownIf returns a plan modifier similar to UseStateForUnknown with a conditional
-func UseStateForUnknownIf(f UseStateForUnknownIfFunc, attributeName, description string) planmodifier.Int32 {
+func UseStateForUnknownIf(f UseStateForUnknownIfFunc, description string) planmodifier.Int32 {
return useStateForUnknownIf{
- ifFunc: f,
- attributeName: attributeName,
- description: description,
+ ifFunc: f,
+ description: description,
}
}
@@ -59,7 +58,7 @@ func (m useStateForUnknownIf) PlanModifyInt32(ctx context.Context, req planmodif
// (https://github.com/hashicorp/terraform-plugin-framework/blob/44348af3923c82a93c64ae7dca906d9850ba956b/resource/schema/stringplanmodifier/use_state_for_unknown.go#L38)
funcResponse := &UseStateForUnknownFuncResponse{}
- m.ifFunc(ctx, m.attributeName, req, funcResponse)
+ m.ifFunc(ctx, req, funcResponse)
resp.Diagnostics.Append(funcResponse.Diagnostics...)
if resp.Diagnostics.HasError() {
@@ -72,25 +71,25 @@ func (m useStateForUnknownIf) PlanModifyInt32(ctx context.Context, req planmodif
}
// Int32Changed sets UseStateForUnkown to true if the attribute's planned value matches the current state
-func Int32Changed(ctx context.Context, attributeName string, request planmodifier.Int32Request, response *UseStateForUnknownFuncResponse) { // nolint:gocritic // function signature required by Terraform
- dependencyPath := request.Path.ParentPath().AtName(attributeName)
-
- var attributePlan types.Int32
- diags := request.Plan.GetAttribute(ctx, dependencyPath, &attributePlan)
- response.Diagnostics.Append(diags...)
- if response.Diagnostics.HasError() {
- return
- }
-
- var attributeState types.Int32
- diags = request.State.GetAttribute(ctx, dependencyPath, &attributeState)
- response.Diagnostics.Append(diags...)
- if response.Diagnostics.HasError() {
- return
- }
-
- if attributeState == attributePlan {
- response.UseStateForUnknown = true
- return
+func Int32Changed(attributePath path.Path) UseStateForUnknownIfFunc {
+ return func(ctx context.Context, request planmodifier.Int32Request, response *UseStateForUnknownFuncResponse) {
+ var attributePlan types.Int32
+ diags := request.Plan.GetAttribute(ctx, attributePath, &attributePlan)
+ response.Diagnostics.Append(diags...)
+ if response.Diagnostics.HasError() {
+ return
+ }
+
+ var attributeState types.Int32
+ diags = request.State.GetAttribute(ctx, attributePath, &attributeState)
+ response.Diagnostics.Append(diags...)
+ if response.Diagnostics.HasError() {
+ return
+ }
+
+ if attributeState == attributePlan {
+ response.UseStateForUnknown = true
+ return
+ }
}
}
diff --git a/stackit/internal/utils/planmodifiers/int32planmodifier/use_state_for_unknown_if_test.go b/stackit/internal/utils/planmodifiers/int32planmodifier/use_state_for_unknown_if_test.go
index 185c86c03..93ae40c69 100644
--- a/stackit/internal/utils/planmodifiers/int32planmodifier/use_state_for_unknown_if_test.go
+++ b/stackit/internal/utils/planmodifiers/int32planmodifier/use_state_for_unknown_if_test.go
@@ -25,7 +25,7 @@ func TestUseStateForUnknownIf_PlanModifyInt32(t *testing.T) {
stateValue: types.Int32Null(),
planValue: types.Int32Unknown(),
configValue: types.Int32Value(10),
- ifFunc: func(_ context.Context, _ string, _ planmodifier.Int32Request, resp *UseStateForUnknownFuncResponse) {
+ ifFunc: func(_ context.Context, _ planmodifier.Int32Request, resp *UseStateForUnknownFuncResponse) {
// This should not be reached because the state is null
resp.UseStateForUnknown = true
},
@@ -36,7 +36,7 @@ func TestUseStateForUnknownIf_PlanModifyInt32(t *testing.T) {
stateValue: types.Int32Value(5),
planValue: types.Int32Value(10),
configValue: types.Int32Value(10),
- ifFunc: func(_ context.Context, _ string, _ planmodifier.Int32Request, resp *UseStateForUnknownFuncResponse) {
+ ifFunc: func(_ context.Context, _ planmodifier.Int32Request, resp *UseStateForUnknownFuncResponse) {
// This should not be reached because the plan is known
resp.UseStateForUnknown = true
},
@@ -47,7 +47,7 @@ func TestUseStateForUnknownIf_PlanModifyInt32(t *testing.T) {
stateValue: types.Int32Value(5),
planValue: types.Int32Unknown(),
configValue: types.Int32Unknown(),
- ifFunc: func(_ context.Context, _ string, _ planmodifier.Int32Request, resp *UseStateForUnknownFuncResponse) {
+ ifFunc: func(_ context.Context, _ planmodifier.Int32Request, resp *UseStateForUnknownFuncResponse) {
// This should not be reached
resp.UseStateForUnknown = true
},
@@ -58,7 +58,7 @@ func TestUseStateForUnknownIf_PlanModifyInt32(t *testing.T) {
stateValue: types.Int32Value(5),
planValue: types.Int32Unknown(),
configValue: types.Int32Null(), // Simulating computed only
- ifFunc: func(_ context.Context, _ string, _ planmodifier.Int32Request, resp *UseStateForUnknownFuncResponse) {
+ ifFunc: func(_ context.Context, _ planmodifier.Int32Request, resp *UseStateForUnknownFuncResponse) {
resp.UseStateForUnknown = false
},
expectedPlanValue: types.Int32Unknown(),
@@ -68,7 +68,7 @@ func TestUseStateForUnknownIf_PlanModifyInt32(t *testing.T) {
stateValue: types.Int32Value(5),
planValue: types.Int32Unknown(),
configValue: types.Int32Null(),
- ifFunc: func(_ context.Context, _ string, _ planmodifier.Int32Request, resp *UseStateForUnknownFuncResponse) {
+ ifFunc: func(_ context.Context, _ planmodifier.Int32Request, resp *UseStateForUnknownFuncResponse) {
resp.UseStateForUnknown = true
},
expectedPlanValue: types.Int32Value(5),
@@ -78,7 +78,7 @@ func TestUseStateForUnknownIf_PlanModifyInt32(t *testing.T) {
stateValue: types.Int32Value(5),
planValue: types.Int32Unknown(),
configValue: types.Int32Null(),
- ifFunc: func(_ context.Context, _ string, _ planmodifier.Int32Request, resp *UseStateForUnknownFuncResponse) {
+ ifFunc: func(_ context.Context, _ planmodifier.Int32Request, resp *UseStateForUnknownFuncResponse) {
resp.Diagnostics.AddError("Test Error", "Something went wrong")
},
expectedPlanValue: types.Int32Unknown(),
@@ -89,7 +89,7 @@ func TestUseStateForUnknownIf_PlanModifyInt32(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Initialize the modifier
- modifier := UseStateForUnknownIf(tt.ifFunc, "", "test description")
+ modifier := UseStateForUnknownIf(tt.ifFunc, "test description")
// Construct request
req := planmodifier.Int32Request{
diff --git a/stackit/internal/utils/planmodifiers/listplanmodifier/use_state_for_unknown_if.go b/stackit/internal/utils/planmodifiers/listplanmodifier/use_state_for_unknown_if.go
new file mode 100644
index 000000000..09cc327bf
--- /dev/null
+++ b/stackit/internal/utils/planmodifiers/listplanmodifier/use_state_for_unknown_if.go
@@ -0,0 +1,95 @@
+package listplanmodifier
+
+import (
+ "context"
+
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+ "github.com/hashicorp/terraform-plugin-framework/path"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+type UseStateForUnknownFuncResponse struct {
+ UseStateForUnknown bool
+ Diagnostics diag.Diagnostics
+}
+
+// UseStateForUnknownIfFunc is a conditional function used in UseStateForUnknownIf
+type UseStateForUnknownIfFunc func(context.Context, planmodifier.ListRequest, *UseStateForUnknownFuncResponse)
+
+type useStateForUnknownIf struct {
+ ifFunc UseStateForUnknownIfFunc
+ description string
+}
+
+// UseStateForUnknownIf returns a plan modifier similar to UseStateForUnknown with a conditional
+func UseStateForUnknownIf(f UseStateForUnknownIfFunc, description string) planmodifier.List {
+ return useStateForUnknownIf{
+ ifFunc: f,
+ description: description,
+ }
+}
+
+func (m useStateForUnknownIf) Description(context.Context) string {
+ return m.description
+}
+
+func (m useStateForUnknownIf) MarkdownDescription(ctx context.Context) string {
+ return m.Description(ctx)
+}
+
+func (m useStateForUnknownIf) PlanModifyList(ctx context.Context, req planmodifier.ListRequest, resp *planmodifier.ListResponse) { // nolint:gocritic // function signature required by Terraform
+ // Do nothing if there is no state value.
+ if req.StateValue.IsNull() {
+ return
+ }
+
+ // Do nothing if there is a known planned value.
+ if !req.PlanValue.IsUnknown() {
+ return
+ }
+
+ // Do nothing if there is an unknown configuration value, otherwise interpolation gets messed up.
+ if req.ConfigValue.IsUnknown() {
+ return
+ }
+
+ // The above checks are taken from the UseStateForUnknown plan modifier implementation
+ // (https://github.com/hashicorp/terraform-plugin-framework/blob/44348af3923c82a93c64ae7dca906d9850ba956b/resource/schema/stringplanmodifier/use_state_for_unknown.go#L38)
+
+ funcResponse := &UseStateForUnknownFuncResponse{}
+ m.ifFunc(ctx, req, funcResponse)
+
+ resp.Diagnostics.Append(funcResponse.Diagnostics...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ if funcResponse.UseStateForUnknown {
+ resp.PlanValue = req.StateValue
+ }
+}
+
+// ListChanged sets UseStateForUnkown to true if the attribute's planned value matches the current state
+func ListChanged(attributePath path.Path) UseStateForUnknownIfFunc {
+ return func(ctx context.Context, request planmodifier.ListRequest, response *UseStateForUnknownFuncResponse) {
+ var attributePlan types.List
+ diags := request.Plan.GetAttribute(ctx, attributePath, &attributePlan)
+ response.Diagnostics.Append(diags...)
+ if response.Diagnostics.HasError() {
+ return
+ }
+
+ var attributeState types.List
+ diags = request.State.GetAttribute(ctx, attributePath, &attributeState)
+ response.Diagnostics.Append(diags...)
+ if response.Diagnostics.HasError() {
+ return
+ }
+
+ if attributeState.Equal(attributePlan) {
+ response.UseStateForUnknown = true
+ return
+ }
+ }
+}
diff --git a/stackit/internal/utils/planmodifiers/listplanmodifier/use_state_for_unknown_if_test.go b/stackit/internal/utils/planmodifiers/listplanmodifier/use_state_for_unknown_if_test.go
new file mode 100644
index 000000000..42852c215
--- /dev/null
+++ b/stackit/internal/utils/planmodifiers/listplanmodifier/use_state_for_unknown_if_test.go
@@ -0,0 +1,129 @@
+package listplanmodifier
+
+import (
+ "context"
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+func TestUseStateForUnknownIf_PlanModifyList(t *testing.T) {
+ ctx := context.Background()
+
+ tests := []struct {
+ name string
+ stateValue types.List
+ planValue types.List
+ configValue types.List
+ ifFunc UseStateForUnknownIfFunc
+ expectedPlanValue types.List
+ expectedError bool
+ }{
+ {
+ name: "State is Null (Creation)",
+ stateValue: types.ListNull(types.StringType),
+ planValue: types.ListUnknown(types.StringType),
+ configValue: types.ListValueMust(types.StringType, []attr.Value{types.StringValue("value1")}),
+ ifFunc: func(_ context.Context, _ planmodifier.ListRequest, resp *UseStateForUnknownFuncResponse) {
+ // This should not be reached because the state is null
+ resp.UseStateForUnknown = true
+ },
+ expectedPlanValue: types.ListUnknown(types.StringType),
+ },
+ {
+ name: "Plan is already known - (User updated the value)",
+ stateValue: types.ListValueMust(types.StringType, []attr.Value{types.StringValue("1")}),
+ planValue: types.ListValueMust(types.StringType, []attr.Value{types.StringValue("2")}),
+ configValue: types.ListValueMust(types.StringType, []attr.Value{types.StringValue("2")}),
+ ifFunc: func(_ context.Context, _ planmodifier.ListRequest, resp *UseStateForUnknownFuncResponse) {
+ // This should not be reached because the plan is known
+ resp.UseStateForUnknown = true
+ },
+ expectedPlanValue: types.ListValueMust(types.StringType, []attr.Value{types.StringValue("2")}),
+ },
+ {
+ name: "Config is Unknown (Interpolation)",
+ stateValue: types.ListValueMust(types.StringType, []attr.Value{types.StringValue("1")}),
+ planValue: types.ListUnknown(types.StringType),
+ configValue: types.ListUnknown(types.StringType),
+ ifFunc: func(_ context.Context, _ planmodifier.ListRequest, resp *UseStateForUnknownFuncResponse) {
+ // This should not be reached
+ resp.UseStateForUnknown = true
+ },
+ expectedPlanValue: types.ListUnknown(types.StringType),
+ },
+ {
+ name: "Condition returns False (Do not use state)",
+ stateValue: types.ListValueMust(types.StringType, []attr.Value{types.StringValue("1")}),
+ planValue: types.ListUnknown(types.StringType),
+ configValue: types.ListNull(types.StringType), // Simulating computed only
+ ifFunc: func(_ context.Context, _ planmodifier.ListRequest, resp *UseStateForUnknownFuncResponse) {
+ resp.UseStateForUnknown = false
+ },
+ expectedPlanValue: types.ListUnknown(types.StringType),
+ },
+ {
+ name: "Condition returns True (Use state)",
+ stateValue: types.ListValueMust(types.StringType, []attr.Value{types.StringValue("1")}),
+ planValue: types.ListUnknown(types.StringType),
+ configValue: types.ListNull(types.StringType),
+ ifFunc: func(_ context.Context, _ planmodifier.ListRequest, resp *UseStateForUnknownFuncResponse) {
+ resp.UseStateForUnknown = true
+ },
+ expectedPlanValue: types.ListValueMust(types.StringType, []attr.Value{types.StringValue("1")}),
+ },
+ {
+ name: "Func returns Error",
+ stateValue: types.ListValueMust(types.StringType, []attr.Value{types.StringValue("1")}),
+ planValue: types.ListUnknown(types.StringType),
+ configValue: types.ListNull(types.StringType),
+ ifFunc: func(_ context.Context, _ planmodifier.ListRequest, resp *UseStateForUnknownFuncResponse) {
+ resp.Diagnostics.AddError("Test Error", "Something went wrong")
+ },
+ expectedPlanValue: types.ListUnknown(types.StringType),
+ expectedError: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ // Initialize the modifier
+ modifier := UseStateForUnknownIf(tt.ifFunc, "test description")
+
+ // Construct request
+ req := planmodifier.ListRequest{
+ StateValue: tt.stateValue,
+ PlanValue: tt.planValue,
+ ConfigValue: tt.configValue,
+ }
+
+ // Construct response
+ // Note: In the framework, resp.PlanValue is initialized to req.PlanValue
+ // before the modifier is called. We must simulate this.
+ resp := &planmodifier.ListResponse{
+ PlanValue: tt.planValue,
+ }
+
+ // Run the modifier
+ modifier.PlanModifyList(ctx, req, resp)
+
+ // Check Errors
+ if tt.expectedError {
+ if !resp.Diagnostics.HasError() {
+ t.Error("Expected error, got none")
+ }
+ } else {
+ if resp.Diagnostics.HasError() {
+ t.Errorf("Unexpected error: %s", resp.Diagnostics)
+ }
+ }
+
+ // Check Plan Value
+ if !resp.PlanValue.Equal(tt.expectedPlanValue) {
+ t.Errorf("PlanValue mismatch.\nExpected: %s\nGot: %s", tt.expectedPlanValue, resp.PlanValue)
+ }
+ })
+ }
+}
diff --git a/stackit/internal/utils/planmodifiers/objectplanmodifier/use_state_for_unknown_if.go b/stackit/internal/utils/planmodifiers/objectplanmodifier/use_state_for_unknown_if.go
new file mode 100644
index 000000000..941807b3a
--- /dev/null
+++ b/stackit/internal/utils/planmodifiers/objectplanmodifier/use_state_for_unknown_if.go
@@ -0,0 +1,119 @@
+package objectplanmodifier
+
+import (
+ "context"
+
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+ "github.com/hashicorp/terraform-plugin-framework/path"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+type UseStateForUnknownFuncResponse struct {
+ UseStateForUnknown bool
+ Diagnostics diag.Diagnostics
+}
+
+// UseStateForUnknownIfFunc is a conditional function used in UseStateForUnknownIf
+type UseStateForUnknownIfFunc func(context.Context, planmodifier.ObjectRequest, *UseStateForUnknownFuncResponse)
+
+type useStateForUnknownIf struct {
+ ifFunc UseStateForUnknownIfFunc
+ description string
+}
+
+// UseStateForUnknownIf returns a plan modifier similar to UseStateForUnknown with a conditional
+func UseStateForUnknownIf(f UseStateForUnknownIfFunc, description string) planmodifier.Object {
+ return useStateForUnknownIf{
+ ifFunc: f,
+ description: description,
+ }
+}
+
+func (m useStateForUnknownIf) Description(context.Context) string {
+ return m.description
+}
+
+func (m useStateForUnknownIf) MarkdownDescription(ctx context.Context) string {
+ return m.Description(ctx)
+}
+
+func (m useStateForUnknownIf) PlanModifyObject(ctx context.Context, req planmodifier.ObjectRequest, resp *planmodifier.ObjectResponse) { // nolint:gocritic // function signature required by Terraform
+ // Do nothing if there is no state value.
+ if req.StateValue.IsNull() {
+ return
+ }
+
+ // Do nothing if there is a known planned value.
+ if !req.PlanValue.IsUnknown() {
+ return
+ }
+
+ // Do nothing if there is an unknown configuration value, otherwise interpolation gets messed up.
+ if req.ConfigValue.IsUnknown() {
+ return
+ }
+
+ // The above checks are taken from the UseStateForUnknown plan modifier implementation
+ // (https://github.com/hashicorp/terraform-plugin-framework/blob/44348af3923c82a93c64ae7dca906d9850ba956b/resource/schema/stringplanmodifier/use_state_for_unknown.go#L38)
+
+ funcResponse := &UseStateForUnknownFuncResponse{}
+ m.ifFunc(ctx, req, funcResponse)
+
+ resp.Diagnostics.Append(funcResponse.Diagnostics...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ if funcResponse.UseStateForUnknown {
+ resp.PlanValue = req.StateValue
+ }
+}
+
+// Int32Changed sets UseStateForUnkown to true if the attribute's planned value matches the current state
+func Int32Changed(attributePath path.Path) UseStateForUnknownIfFunc {
+ return func(ctx context.Context, request planmodifier.ObjectRequest, response *UseStateForUnknownFuncResponse) {
+ var attributePlan types.Int32
+ diags := request.Plan.GetAttribute(ctx, attributePath, &attributePlan)
+ response.Diagnostics.Append(diags...)
+ if response.Diagnostics.HasError() {
+ return
+ }
+
+ var attributeState types.Int32
+ diags = request.State.GetAttribute(ctx, attributePath, &attributeState)
+ response.Diagnostics.Append(diags...)
+ if response.Diagnostics.HasError() {
+ return
+ }
+
+ if attributeState.Equal(attributePlan) {
+ response.UseStateForUnknown = true
+ return
+ }
+ }
+}
+
+// ListChanged sets UseStateForUnkown to true if the attribute's planned value matches the current state
+func ListChanged(attributePath path.Path) UseStateForUnknownIfFunc {
+ return func(ctx context.Context, request planmodifier.ObjectRequest, response *UseStateForUnknownFuncResponse) {
+ var attributePlan types.List
+ diags := request.Plan.GetAttribute(ctx, attributePath, &attributePlan)
+ response.Diagnostics.Append(diags...)
+ if response.Diagnostics.HasError() {
+ return
+ }
+
+ var attributeState types.List
+ diags = request.State.GetAttribute(ctx, attributePath, &attributeState)
+ response.Diagnostics.Append(diags...)
+ if response.Diagnostics.HasError() {
+ return
+ }
+
+ if attributeState.Equal(attributePlan) {
+ response.UseStateForUnknown = true
+ return
+ }
+ }
+}
diff --git a/stackit/internal/utils/planmodifiers/objectplanmodifier/use_state_for_unknown_if_test.go b/stackit/internal/utils/planmodifiers/objectplanmodifier/use_state_for_unknown_if_test.go
new file mode 100644
index 000000000..0f273a6f3
--- /dev/null
+++ b/stackit/internal/utils/planmodifiers/objectplanmodifier/use_state_for_unknown_if_test.go
@@ -0,0 +1,133 @@
+package objectplanmodifier
+
+import (
+ "context"
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+func TestUseStateForUnknownIf_PlanModifyObject(t *testing.T) {
+ attrTypesObject := map[string]attr.Type{
+ "key": types.StringType,
+ }
+
+ ctx := context.Background()
+
+ tests := []struct {
+ name string
+ stateValue types.Object
+ planValue types.Object
+ configValue types.Object
+ ifFunc UseStateForUnknownIfFunc
+ expectedPlanValue types.Object
+ expectedError bool
+ }{
+ {
+ name: "State is Null (Creation)",
+ stateValue: types.ObjectNull(attrTypesObject),
+ planValue: types.ObjectUnknown(attrTypesObject),
+ configValue: types.ObjectValueMust(attrTypesObject, map[string]attr.Value{"key": types.StringValue("value1")}),
+ ifFunc: func(_ context.Context, _ planmodifier.ObjectRequest, resp *UseStateForUnknownFuncResponse) {
+ // This should not be reached because the state is null
+ resp.UseStateForUnknown = true
+ },
+ expectedPlanValue: types.ObjectUnknown(attrTypesObject),
+ },
+ {
+ name: "Plan is already known - (User updated the value)",
+ stateValue: types.ObjectValueMust(attrTypesObject, map[string]attr.Value{"key": types.StringValue("value1")}),
+ planValue: types.ObjectValueMust(attrTypesObject, map[string]attr.Value{"key": types.StringValue("value2")}),
+ configValue: types.ObjectValueMust(attrTypesObject, map[string]attr.Value{"key": types.StringValue("value2")}),
+ ifFunc: func(_ context.Context, _ planmodifier.ObjectRequest, resp *UseStateForUnknownFuncResponse) {
+ // This should not be reached because the plan is known
+ resp.UseStateForUnknown = true
+ },
+ expectedPlanValue: types.ObjectValueMust(attrTypesObject, map[string]attr.Value{"key": types.StringValue("value2")}),
+ },
+ {
+ name: "Config is Unknown (Interpolation)",
+ stateValue: types.ObjectValueMust(attrTypesObject, map[string]attr.Value{"key": types.StringValue("value1")}),
+ planValue: types.ObjectUnknown(attrTypesObject),
+ configValue: types.ObjectUnknown(attrTypesObject),
+ ifFunc: func(_ context.Context, _ planmodifier.ObjectRequest, resp *UseStateForUnknownFuncResponse) {
+ // This should not be reached
+ resp.UseStateForUnknown = true
+ },
+ expectedPlanValue: types.ObjectUnknown(attrTypesObject),
+ },
+ {
+ name: "Condition returns False (Do not use state)",
+ stateValue: types.ObjectValueMust(attrTypesObject, map[string]attr.Value{"key": types.StringValue("value1")}),
+ planValue: types.ObjectUnknown(attrTypesObject),
+ configValue: types.ObjectNull(attrTypesObject), // Simulating computed only
+ ifFunc: func(_ context.Context, _ planmodifier.ObjectRequest, resp *UseStateForUnknownFuncResponse) {
+ resp.UseStateForUnknown = false
+ },
+ expectedPlanValue: types.ObjectUnknown(attrTypesObject),
+ },
+ {
+ name: "Condition returns True (Use state)",
+ stateValue: types.ObjectValueMust(attrTypesObject, map[string]attr.Value{"key": types.StringValue("value1")}),
+ planValue: types.ObjectUnknown(attrTypesObject),
+ configValue: types.ObjectNull(attrTypesObject),
+ ifFunc: func(_ context.Context, _ planmodifier.ObjectRequest, resp *UseStateForUnknownFuncResponse) {
+ resp.UseStateForUnknown = true
+ },
+ expectedPlanValue: types.ObjectValueMust(attrTypesObject, map[string]attr.Value{"key": types.StringValue("value1")}),
+ },
+ {
+ name: "Func returns Error",
+ stateValue: types.ObjectValueMust(attrTypesObject, map[string]attr.Value{"key": types.StringValue("value1")}),
+ planValue: types.ObjectUnknown(attrTypesObject),
+ configValue: types.ObjectNull(attrTypesObject),
+ ifFunc: func(_ context.Context, _ planmodifier.ObjectRequest, resp *UseStateForUnknownFuncResponse) {
+ resp.Diagnostics.AddError("Test Error", "Something went wrong")
+ },
+ expectedPlanValue: types.ObjectUnknown(attrTypesObject),
+ expectedError: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ // Initialize the modifier
+ modifier := UseStateForUnknownIf(tt.ifFunc, "test description")
+
+ // Construct request
+ req := planmodifier.ObjectRequest{
+ StateValue: tt.stateValue,
+ PlanValue: tt.planValue,
+ ConfigValue: tt.configValue,
+ }
+
+ // Construct response
+ // Note: In the framework, resp.PlanValue is initialized to req.PlanValue
+ // before the modifier is called. We must simulate this.
+ resp := &planmodifier.ObjectResponse{
+ PlanValue: tt.planValue,
+ }
+
+ // Run the modifier
+ modifier.PlanModifyObject(ctx, req, resp)
+
+ // Check Errors
+ if tt.expectedError {
+ if !resp.Diagnostics.HasError() {
+ t.Error("Expected error, got none")
+ }
+ } else {
+ if resp.Diagnostics.HasError() {
+ t.Errorf("Unexpected error: %s", resp.Diagnostics)
+ }
+ }
+
+ // Check Plan Value
+ if !resp.PlanValue.Equal(tt.expectedPlanValue) {
+ t.Errorf("PlanValue mismatch.\nExpected: %s\nGot: %s", tt.expectedPlanValue, resp.PlanValue)
+ }
+ })
+ }
+}