Feat/separate functions (#19)
* chore: work save * fix: refactor flavors * fix: refactor pg user and database * fix: refactor flavor parameters * fix: refactor tf script * chore: work save * chore: work save * chore: work save --------- Co-authored-by: Marcel S. Henselin <marcel.henselin@stackit.cloud>
This commit is contained in:
parent
910551f09d
commit
0150fea302
54 changed files with 6010 additions and 2826 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -15,9 +15,9 @@ bin/
|
|||
**/terraform.tfstate**
|
||||
.terraform.lock.hcl
|
||||
.terraform.tfstate.lock.info
|
||||
main.tf
|
||||
example.tf
|
||||
index.tf
|
||||
**/config.tfrc
|
||||
**/variables.tf
|
||||
**/service_account.json
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
|
|
|||
43
docs/data-sources/postgresflexalpha_flavor.md
Normal file
43
docs/data-sources/postgresflexalpha_flavor.md
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "stackitprivatepreview_postgresflexalpha_flavor Data Source - stackitprivatepreview"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
|
||||
---
|
||||
|
||||
# stackitprivatepreview_postgresflexalpha_flavor (Data Source)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Required
|
||||
|
||||
- `cpu` (Number) The cpu count of the instance.
|
||||
- `node_type` (String) defines the nodeType it can be either single or replica
|
||||
- `project_id` (String) The cpu count of the instance.
|
||||
- `ram` (Number) The memory of the instance in Gibibyte.
|
||||
- `region` (String) The flavor description.
|
||||
- `storage_class` (String) The memory of the instance in Gibibyte.
|
||||
|
||||
### Read-Only
|
||||
|
||||
- `description` (String) The flavor description.
|
||||
- `flavor_id` (String) The flavor id of the instance flavor.
|
||||
- `id` (String) The terraform id of the instance flavor.
|
||||
- `max_gb` (Number) maximum storage which can be ordered for the flavor in Gigabyte.
|
||||
- `min_gb` (Number) minimum storage which is required to order in Gigabyte.
|
||||
- `storage_classes` (Attributes List) (see [below for nested schema](#nestedatt--storage_classes))
|
||||
|
||||
<a id="nestedatt--storage_classes"></a>
|
||||
### Nested Schema for `storage_classes`
|
||||
|
||||
Read-Only:
|
||||
|
||||
- `class` (String)
|
||||
- `max_io_per_sec` (Number)
|
||||
- `max_through_in_mb` (Number)
|
||||
|
|
@ -35,11 +35,12 @@ data "stackitprivatepreview_postgresflexalpha_instance" "example" {
|
|||
|
||||
- `backup_schedule` (String)
|
||||
- `encryption` (Attributes) (see [below for nested schema](#nestedatt--encryption))
|
||||
- `flavor` (Attributes) (see [below for nested schema](#nestedatt--flavor))
|
||||
- `flavor_id` (String)
|
||||
- `id` (String) Terraform's internal data source. ID. It is structured as "`project_id`,`region`,`instance_id`".
|
||||
- `name` (String) Instance name.
|
||||
- `network` (Attributes) (see [below for nested schema](#nestedatt--network))
|
||||
- `replicas` (Number)
|
||||
- `retention_days` (Number)
|
||||
- `storage` (Attributes) (see [below for nested schema](#nestedatt--storage))
|
||||
- `version` (String)
|
||||
|
||||
|
|
@ -54,18 +55,6 @@ Read-Only:
|
|||
- `service_account` (String)
|
||||
|
||||
|
||||
<a id="nestedatt--flavor"></a>
|
||||
### Nested Schema for `flavor`
|
||||
|
||||
Read-Only:
|
||||
|
||||
- `cpu` (Number)
|
||||
- `description` (String)
|
||||
- `id` (String)
|
||||
- `node_type` (String)
|
||||
- `ram` (Number)
|
||||
|
||||
|
||||
<a id="nestedatt--network"></a>
|
||||
### Nested Schema for `network`
|
||||
|
||||
|
|
|
|||
43
docs/data-sources/sqlserverflexalpha_flavor.md
Normal file
43
docs/data-sources/sqlserverflexalpha_flavor.md
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "stackitprivatepreview_sqlserverflexalpha_flavor Data Source - stackitprivatepreview"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
|
||||
---
|
||||
|
||||
# stackitprivatepreview_sqlserverflexalpha_flavor (Data Source)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
||||
### Required
|
||||
|
||||
- `cpu` (Number) The cpu count of the instance.
|
||||
- `node_type` (String) defines the nodeType it can be either single or replica
|
||||
- `project_id` (String) The cpu count of the instance.
|
||||
- `ram` (Number) The memory of the instance in Gibibyte.
|
||||
- `region` (String) The flavor description.
|
||||
- `storage_class` (String) The memory of the instance in Gibibyte.
|
||||
|
||||
### Read-Only
|
||||
|
||||
- `description` (String) The flavor description.
|
||||
- `flavor_id` (String) The flavor id of the instance flavor.
|
||||
- `id` (String) The terraform id of the instance flavor.
|
||||
- `max_gb` (Number) maximum storage which can be ordered for the flavor in Gigabyte.
|
||||
- `min_gb` (Number) minimum storage which is required to order in Gigabyte.
|
||||
- `storage_classes` (Attributes List) (see [below for nested schema](#nestedatt--storage_classes))
|
||||
|
||||
<a id="nestedatt--storage_classes"></a>
|
||||
### Nested Schema for `storage_classes`
|
||||
|
||||
Read-Only:
|
||||
|
||||
- `class` (String)
|
||||
- `max_io_per_sec` (Number)
|
||||
- `max_through_in_mb` (Number)
|
||||
|
|
@ -1,16 +1,31 @@
|
|||
---
|
||||
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||
page_title: "stackitprivatepreview_postgresflex_database Resource - stackitprivatepreview"
|
||||
page_title: "stackitprivatepreview_postgresflexalpha_database Resource - stackitprivatepreview"
|
||||
subcategory: ""
|
||||
description: |-
|
||||
Postgres Flex database resource schema. Must have a region specified in the provider configuration.
|
||||
---
|
||||
|
||||
# stackitprivatepreview_postgresflex_database (Resource)
|
||||
# stackitprivatepreview_postgresflexalpha_database (Resource)
|
||||
|
||||
Postgres Flex database resource schema. Must have a `region` specified in the provider configuration.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```terraform
|
||||
resource "stackitprivatepreview_postgresflexalpha_database" "example" {
|
||||
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
instance_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
||||
name = "mydb"
|
||||
owner = "myusername"
|
||||
}
|
||||
|
||||
# Only use the import statement, if you want to import an existing postgresflex database
|
||||
import {
|
||||
to = stackitprivatepreview_postgresflexalpha_database.import-example
|
||||
id = "${var.project_id},${var.region},${var.postgres_instance_id},${var.postgres_database_id}"
|
||||
}
|
||||
```
|
||||
|
||||
<!-- schema generated by tfplugindocs -->
|
||||
## Schema
|
||||
|
|
@ -28,5 +43,5 @@ Postgres Flex database resource schema. Must have a `region` specified in the pr
|
|||
|
||||
### Read-Only
|
||||
|
||||
- `database_id` (String) Database ID.
|
||||
- `database_id` (Number) Database ID.
|
||||
- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`region`,`instance_id`,`database_id`".
|
||||
|
|
@ -44,7 +44,7 @@ import {
|
|||
|
||||
- `backup_schedule` (String)
|
||||
- `encryption` (Attributes) The encryption block. (see [below for nested schema](#nestedatt--encryption))
|
||||
- `flavor` (Attributes) The block that defines the flavor data. (see [below for nested schema](#nestedatt--flavor))
|
||||
- `flavor_id` (String)
|
||||
- `name` (String) Instance name.
|
||||
- `network` (Attributes) The network block configuration. (see [below for nested schema](#nestedatt--network))
|
||||
- `project_id` (String) STACKIT project ID to which the instance is associated.
|
||||
|
|
@ -73,24 +73,6 @@ Required:
|
|||
- `service_account` (String) The service account ID of the service account.
|
||||
|
||||
|
||||
<a id="nestedatt--flavor"></a>
|
||||
### Nested Schema for `flavor`
|
||||
|
||||
Required:
|
||||
|
||||
- `cpu` (Number) The CPU count of the flavor.
|
||||
- `ram` (Number) The RAM count of the flavor.
|
||||
|
||||
Optional:
|
||||
|
||||
- `id` (String) The ID of the flavor.
|
||||
- `node_type` (String) The node type of the flavor. (Single or Replicas)
|
||||
|
||||
Read-Only:
|
||||
|
||||
- `description` (String) The flavor detailed flavor name.
|
||||
|
||||
|
||||
<a id="nestedatt--network"></a>
|
||||
### Nested Schema for `network`
|
||||
|
||||
|
|
|
|||
|
|
@ -44,10 +44,7 @@ import {
|
|||
### Read-Only
|
||||
|
||||
- `connection_string` (String)
|
||||
- `host` (String)
|
||||
- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`region`,`instance_id`,`user_id`".
|
||||
- `password` (String, Sensitive)
|
||||
- `port` (Number)
|
||||
- `status` (String)
|
||||
- `uri` (String, Sensitive)
|
||||
- `user_id` (Number) User ID.
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ import {
|
|||
### Required
|
||||
|
||||
- `encryption` (Attributes) The encryption block. (see [below for nested schema](#nestedatt--encryption))
|
||||
- `flavor` (Attributes) (see [below for nested schema](#nestedatt--flavor))
|
||||
- `flavor_id` (String)
|
||||
- `name` (String) Instance name.
|
||||
- `network` (Attributes) The network block. (see [below for nested schema](#nestedatt--network))
|
||||
- `project_id` (String) STACKIT project ID to which the instance is associated.
|
||||
|
|
@ -75,30 +75,12 @@ Required:
|
|||
- `service_account` (String)
|
||||
|
||||
|
||||
<a id="nestedatt--flavor"></a>
|
||||
### Nested Schema for `flavor`
|
||||
|
||||
Required:
|
||||
|
||||
- `cpu` (Number)
|
||||
- `node_type` (String)
|
||||
- `ram` (Number)
|
||||
|
||||
Optional:
|
||||
|
||||
- `id` (String)
|
||||
|
||||
Read-Only:
|
||||
|
||||
- `description` (String)
|
||||
|
||||
|
||||
<a id="nestedatt--network"></a>
|
||||
### Nested Schema for `network`
|
||||
|
||||
Required:
|
||||
|
||||
- `access_scope` (String) The access scope of the instance. (e.g. SNA)
|
||||
- `access_scope` (String) The access scope of the instance. (SNA | PUBLIC)
|
||||
- `acl` (List of String) The Access Control List (ACL) for the SQLServer Flex instance.
|
||||
|
||||
Read-Only:
|
||||
|
|
|
|||
20
go.mod
20
go.mod
|
|
@ -12,22 +12,20 @@ require (
|
|||
github.com/hashicorp/terraform-plugin-testing v1.14.0
|
||||
github.com/stackitcloud/stackit-sdk-go/core v0.20.1
|
||||
github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.4.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/teambition/rrule-go v1.8.2
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc // indirect
|
||||
golang.org/x/telemetry v0.0.0-20260109210033-bd525da824e2 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/ProtonMail/go-crypto v1.3.0 // indirect
|
||||
github.com/agext/levenshtein v1.2.3 // indirect
|
||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
||||
github.com/cloudflare/circl v1.6.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/cloudflare/circl v1.6.2 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
|
|
@ -57,23 +55,21 @@ require (
|
|||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/oklog/run v1.2.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
github.com/zclconf/go-cty v1.17.0 // indirect
|
||||
golang.org/x/crypto v0.46.0 // indirect
|
||||
golang.org/x/mod v0.31.0 // indirect
|
||||
golang.org/x/mod v0.32.0 // indirect
|
||||
golang.org/x/net v0.48.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.39.0 // indirect
|
||||
golang.org/x/text v0.32.0 // indirect
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
golang.org/x/text v0.33.0 // indirect
|
||||
golang.org/x/tools v0.40.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 // indirect
|
||||
google.golang.org/grpc v1.77.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
|
||||
google.golang.org/grpc v1.78.0 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
tool golang.org/x/tools/cmd/goimports
|
||||
|
|
|
|||
32
go.sum
32
go.sum
|
|
@ -11,8 +11,8 @@ github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew
|
|||
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
|
||||
github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw=
|
||||
github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c=
|
||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/cloudflare/circl v1.6.2 h1:hL7VBpHHKzrV5WTfHCaBsgx/HGbBYlgrwvNXEVDYYsQ=
|
||||
github.com/cloudflare/circl v1.6.2/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
||||
|
|
@ -68,8 +68,8 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l
|
|||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/hashicorp/go-plugin v1.7.0 h1:YghfQH/0QmPNc/AZMTFE3ac8fipZyZECHdDPshfk+mA=
|
||||
github.com/hashicorp/go-plugin v1.7.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
||||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
|
|
@ -188,8 +188,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
|||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
|
||||
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
|
||||
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
|
||||
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
|
|
@ -212,10 +212,10 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc h1:bH6xUXay0AIFMElXG2rQ4uiE+7ncwtiOdPfYK1NK2XA=
|
||||
golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/telemetry v0.0.0-20260109210033-bd525da824e2 h1:O1cMQHRfwNpDfDJerqRoE2oD+AFlyid87D40L/OkkJo=
|
||||
golang.org/x/telemetry v0.0.0-20260109210033-bd525da824e2/go.mod h1:b7fPSJ0pKZ3ccUh8gnTONJxhn3c/PS6tyzQvyqw4iA8=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
||||
|
|
@ -224,8 +224,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
|
|
@ -238,10 +238,10 @@ gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E
|
|||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
||||
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2 h1:2I6GHUeJ/4shcDpoUlLs/2WPnhg7yJwvXtqcMJt9liA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251213004720-97cd9d5aeac2/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
||||
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ func CreateInstanceWaitHandler(
|
|||
}
|
||||
}
|
||||
|
||||
tflog.Info(ctx, "Waiting for instance (calling list users")
|
||||
// // User operations aren't available right after an instance is deemed successful
|
||||
// // To check if they are, perform a users request
|
||||
_, err = a.ListUsersRequestExecute(ctx, projectId, region, instanceId)
|
||||
|
|
@ -114,7 +115,7 @@ func CreateInstanceWaitHandler(
|
|||
},
|
||||
)
|
||||
// Sleep before wait is set because sometimes API returns 404 right after creation request
|
||||
handler.SetTimeout(45 * time.Minute).SetSleepBeforeWait(15 * time.Second)
|
||||
handler.SetTimeout(90 * time.Minute).SetSleepBeforeWait(30 * time.Second)
|
||||
return handler
|
||||
}
|
||||
|
||||
|
|
@ -154,72 +155,72 @@ func PartialUpdateInstanceWaitHandler(
|
|||
return handler
|
||||
}
|
||||
|
||||
// DeleteInstanceWaitHandler will wait for instance deletion
|
||||
func DeleteInstanceWaitHandler(
|
||||
ctx context.Context,
|
||||
a APIClientInstanceInterface,
|
||||
projectId, region, instanceId string,
|
||||
) *wait.AsyncActionHandler[struct{}] {
|
||||
handler := wait.New(
|
||||
func() (waitFinished bool, response *struct{}, err error) {
|
||||
s, err := a.GetInstanceRequestExecute(ctx, projectId, region, instanceId)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
if s == nil || s.Id == nil || *s.Id != instanceId || s.Status == nil {
|
||||
return false, nil, nil
|
||||
}
|
||||
// TODO - maybe we want to validate status if no 404 error (only unknown or terminating should be valid)
|
||||
// switch *s.Status {
|
||||
// default:
|
||||
// return true, nil, fmt.Errorf("instance with id %s has unexpected status %s", instanceId, *s.Status)
|
||||
// case InstanceStateSuccess:
|
||||
// return false, nil, nil
|
||||
// case InstanceStateTerminating:
|
||||
// return false, nil, nil
|
||||
// }
|
||||
|
||||
// TODO - add tflog for ignored cases
|
||||
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
|
||||
if !ok {
|
||||
return false, nil, err
|
||||
}
|
||||
if oapiErr.StatusCode != 404 {
|
||||
return false, nil, err
|
||||
}
|
||||
return true, nil, nil
|
||||
},
|
||||
)
|
||||
handler.SetTimeout(5 * time.Minute)
|
||||
return handler
|
||||
}
|
||||
|
||||
// TODO - remove
|
||||
// ForceDeleteInstanceWaitHandler will wait for instance deletion
|
||||
func ForceDeleteInstanceWaitHandler(
|
||||
ctx context.Context,
|
||||
a APIClientInstanceInterface,
|
||||
projectId, region, instanceId string,
|
||||
) *wait.AsyncActionHandler[struct{}] {
|
||||
handler := wait.New(
|
||||
func() (waitFinished bool, response *struct{}, err error) {
|
||||
_, err = a.GetInstanceRequestExecute(ctx, projectId, region, instanceId)
|
||||
if err == nil {
|
||||
return false, nil, nil
|
||||
}
|
||||
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
|
||||
if !ok {
|
||||
return false, nil, err
|
||||
}
|
||||
if oapiErr.StatusCode != 404 {
|
||||
return false, nil, err
|
||||
}
|
||||
return true, nil, nil
|
||||
},
|
||||
)
|
||||
handler.SetTimeout(15 * time.Minute)
|
||||
return handler
|
||||
}
|
||||
//// DeleteInstanceWaitHandler will wait for instance deletion
|
||||
//func DeleteInstanceWaitHandler(
|
||||
// ctx context.Context,
|
||||
// a APIClientInstanceInterface,
|
||||
// projectId, region, instanceId string,
|
||||
//) *wait.AsyncActionHandler[struct{}] {
|
||||
// handler := wait.New(
|
||||
// func() (waitFinished bool, response *struct{}, err error) {
|
||||
// s, err := a.GetInstanceRequestExecute(ctx, projectId, region, instanceId)
|
||||
// if err != nil {
|
||||
// return false, nil, err
|
||||
// }
|
||||
// if s == nil || s.Id == nil || *s.Id != instanceId || s.Status == nil {
|
||||
// return false, nil, nil
|
||||
// }
|
||||
// // TODO - maybe we want to validate status if no 404 error (only unknown or terminating should be valid)
|
||||
// // switch *s.Status {
|
||||
// // default:
|
||||
// // return true, nil, fmt.Errorf("instance with id %s has unexpected status %s", instanceId, *s.Status)
|
||||
// // case InstanceStateSuccess:
|
||||
// // return false, nil, nil
|
||||
// // case InstanceStateTerminating:
|
||||
// // return false, nil, nil
|
||||
// // }
|
||||
//
|
||||
// // TODO - add tflog for ignored cases
|
||||
// oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
|
||||
// if !ok {
|
||||
// return false, nil, err
|
||||
// }
|
||||
// if oapiErr.StatusCode != 404 {
|
||||
// return false, nil, err
|
||||
// }
|
||||
// return true, nil, nil
|
||||
// },
|
||||
// )
|
||||
// handler.SetTimeout(5 * time.Minute)
|
||||
// return handler
|
||||
//}
|
||||
//
|
||||
//// TODO - remove
|
||||
//// ForceDeleteInstanceWaitHandler will wait for instance deletion
|
||||
//func ForceDeleteInstanceWaitHandler(
|
||||
// ctx context.Context,
|
||||
// a APIClientInstanceInterface,
|
||||
// projectId, region, instanceId string,
|
||||
//) *wait.AsyncActionHandler[struct{}] {
|
||||
// handler := wait.New(
|
||||
// func() (waitFinished bool, response *struct{}, err error) {
|
||||
// _, err = a.GetInstanceRequestExecute(ctx, projectId, region, instanceId)
|
||||
// if err == nil {
|
||||
// return false, nil, nil
|
||||
// }
|
||||
// oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
|
||||
// if !ok {
|
||||
// return false, nil, err
|
||||
// }
|
||||
// if oapiErr.StatusCode != 404 {
|
||||
// return false, nil, err
|
||||
// }
|
||||
// return true, nil, nil
|
||||
// },
|
||||
// )
|
||||
// handler.SetTimeout(15 * time.Minute)
|
||||
// return handler
|
||||
//}
|
||||
|
||||
// DeleteUserWaitHandler will wait for delete
|
||||
func DeleteUserWaitHandler(
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
# see other files
|
||||
3
sample/postgres/outputs.tf
Normal file
3
sample/postgres/outputs.tf
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
output "postgres_flavor" {
|
||||
value = data.stackitprivatepreview_postgresflexalpha_flavor.pgsql_flavor.flavor_id
|
||||
}
|
||||
79
sample/postgres/postresql.tf
Normal file
79
sample/postgres/postresql.tf
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
data "stackitprivatepreview_postgresflexalpha_flavor" "pgsql_flavor" {
|
||||
project_id = var.project_id
|
||||
region = "eu01"
|
||||
cpu = 2
|
||||
ram = 4
|
||||
node_type = "Single"
|
||||
storage_class = "premium-perf2-stackit"
|
||||
}
|
||||
|
||||
resource "stackitprivatepreview_postgresflexalpha_instance" "msh-sna-pe-example" {
|
||||
project_id = var.project_id
|
||||
name = "msh-sna-pe-example"
|
||||
backup_schedule = "0 0 * * *"
|
||||
retention_days = 33
|
||||
flavor_id = data.stackitprivatepreview_postgresflexalpha_flavor.pgsql_flavor.flavor_id
|
||||
replicas = 1
|
||||
storage = {
|
||||
# class = "premium-perf2-stackit"
|
||||
class = data.stackitprivatepreview_postgresflexalpha_flavor.pgsql_flavor.storage_class
|
||||
size = 10
|
||||
}
|
||||
encryption = {
|
||||
# key_id = stackit_kms_key.key.key_id
|
||||
# keyring_id = stackit_kms_keyring.keyring.keyring_id
|
||||
key_id = var.key_id
|
||||
keyring_id = var.keyring_id
|
||||
key_version = var.key_version
|
||||
service_account = var.sa_email
|
||||
}
|
||||
network = {
|
||||
acl = ["0.0.0.0/0", "193.148.160.0/19", "170.85.2.177/32"]
|
||||
access_scope = "SNA"
|
||||
}
|
||||
version = 14
|
||||
}
|
||||
|
||||
resource "stackitprivatepreview_postgresflexalpha_user" "ptlsdbadminuser" {
|
||||
project_id = var.project_id
|
||||
instance_id = stackitprivatepreview_postgresflexalpha_instance.msh-sna-pe-example.instance_id
|
||||
username = var.db_admin_username
|
||||
roles = ["createdb", "login"]
|
||||
# roles = ["createdb", "login", "createrole"]
|
||||
}
|
||||
|
||||
resource "stackitprivatepreview_postgresflexalpha_user" "ptlsdbuser" {
|
||||
project_id = var.project_id
|
||||
instance_id = stackitprivatepreview_postgresflexalpha_instance.msh-sna-pe-example.instance_id
|
||||
username = var.db_username
|
||||
roles = ["login"]
|
||||
# roles = ["createdb", "login", "createrole"]
|
||||
}
|
||||
|
||||
resource "stackitprivatepreview_postgresflexalpha_database" "example" {
|
||||
depends_on = [stackitprivatepreview_postgresflexalpha_user.ptlsdbadminuser]
|
||||
project_id = var.project_id
|
||||
instance_id = stackitprivatepreview_postgresflexalpha_instance.msh-sna-pe-example.instance_id
|
||||
name = var.db_name
|
||||
owner = var.db_admin_username
|
||||
}
|
||||
|
||||
data "stackitprivatepreview_postgresflexalpha_instance" "datapsql" {
|
||||
project_id = var.project_id
|
||||
instance_id = var.instance_id
|
||||
region = "eu01"
|
||||
}
|
||||
|
||||
output "psql_instance_id" {
|
||||
value = data.stackitprivatepreview_postgresflexalpha_instance.datapsql.instance_id
|
||||
}
|
||||
|
||||
output "psql_user_password" {
|
||||
value = stackitprivatepreview_postgresflexalpha_user.ptlsdbuser.password
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
output "psql_user_conn" {
|
||||
value = stackitprivatepreview_postgresflexalpha_user.ptlsdbuser.connection_string
|
||||
sensitive = true
|
||||
}
|
||||
|
|
@ -20,5 +20,5 @@ terraform {
|
|||
provider "stackitprivatepreview" {
|
||||
default_region = "eu01"
|
||||
enable_beta_resources = true
|
||||
service_account_key_path = "./service_account.json"
|
||||
service_account_key_path = "../service_account.json"
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
resource "stackitprivatepreview_postgresflexalpha_instance" "ptlsdbsrv" {
|
||||
project_id = var.project_id
|
||||
name = "pgsql-example-instance"
|
||||
backup_schedule = "0 0 * * *"
|
||||
retention_days = 33
|
||||
flavor = {
|
||||
cpu = 2
|
||||
ram = 4
|
||||
}
|
||||
replicas = 1
|
||||
storage = {
|
||||
class = "premium-perf2-stackit"
|
||||
size = 5
|
||||
}
|
||||
encryption = {
|
||||
# key_id = stackit_kms_key.key.key_id
|
||||
# keyring_id = stackit_kms_keyring.keyring.keyring_id
|
||||
key_id = var.key_id
|
||||
keyring_id = var.keyring_id
|
||||
key_version = var.key_version
|
||||
service_account = var.sa_email
|
||||
}
|
||||
network = {
|
||||
acl = ["0.0.0.0/0", "193.148.160.0/19"]
|
||||
access_scope = "SNA"
|
||||
}
|
||||
version = 14
|
||||
}
|
||||
|
||||
# data "stackitprivatepreview_postgresflexalpha_instance" "datapsql" {
|
||||
# project_id = var.project_id
|
||||
# instance_id = "fdb6573e-2dea-4e1d-a638-9157cf90c3ba"
|
||||
# region = "eu01"
|
||||
# }
|
||||
#
|
||||
# output "sample_psqlinstance" {
|
||||
# value = data.stackitprivatepreview_postgresflexalpha_instance.datapsql
|
||||
# }
|
||||
12
sample/sqlserver/flavor.tf
Normal file
12
sample/sqlserver/flavor.tf
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
data "stackitprivatepreview_sqlserverflexalpha_flavor" "sqlserver_flavor" {
|
||||
project_id = var.project_id
|
||||
region = "eu01"
|
||||
cpu = 4
|
||||
ram = 16
|
||||
node_type = "Single"
|
||||
storage_class = "premium-perf2-stackit"
|
||||
}
|
||||
|
||||
output "sqlserver_flavor" {
|
||||
value = data.stackitprivatepreview_sqlserverflexalpha_flavor.sqlserver_flavor.flavor_id
|
||||
}
|
||||
24
sample/sqlserver/providers.tf
Normal file
24
sample/sqlserver/providers.tf
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
terraform {
|
||||
required_providers {
|
||||
# stackit = {
|
||||
# source = "registry.terraform.io/stackitcloud/stackit"
|
||||
# version = "~> 0.70"
|
||||
# }
|
||||
stackitprivatepreview = {
|
||||
source = "registry.terraform.io/mhenselin/stackitprivatepreview"
|
||||
version = "~> 0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# provider "stackit" {
|
||||
# default_region = "eu01"
|
||||
# enable_beta_resources = true
|
||||
# service_account_key_path = "./service_account.json"
|
||||
# }
|
||||
|
||||
provider "stackitprivatepreview" {
|
||||
default_region = "eu01"
|
||||
enable_beta_resources = true
|
||||
service_account_key_path = "../service_account.json"
|
||||
}
|
||||
|
|
@ -18,42 +18,12 @@
|
|||
# value = stackit_kms_key.key.key_id
|
||||
# }
|
||||
|
||||
# resource "stackitalpha_postgresflexalpha_instance" "ptlsdbsrv" {
|
||||
# project_id = var.project_id
|
||||
# name = "example-instance"
|
||||
# acl = ["0.0.0.0/0"]
|
||||
# backup_schedule = "0 0 * * *"
|
||||
# flavor = {
|
||||
# cpu = 2
|
||||
# ram = 4
|
||||
# }
|
||||
# replicas = 1
|
||||
# storage = {
|
||||
# class = "premium-perf2-stackit"
|
||||
# size = 5
|
||||
# }
|
||||
# version = 14
|
||||
# encryption = {
|
||||
# key_id = stackitalpha_kms_key.key.id
|
||||
# keyring_id = stackitalpha_kms_keyring.keyring.keyring_id
|
||||
# key_version = "1"
|
||||
# service_account = var.sa_email
|
||||
# }
|
||||
# network = {
|
||||
# access_scope = "SNA"
|
||||
# }
|
||||
# }
|
||||
|
||||
resource "stackitprivatepreview_sqlserverflexalpha_instance" "ptlsdbsqlsrv" {
|
||||
resource "stackitprivatepreview_sqlserverflexalpha_instance" "sqlsrv" {
|
||||
project_id = var.project_id
|
||||
name = "msh-example-instance-002"
|
||||
backup_schedule = "0 3 * * *"
|
||||
retention_days = 31
|
||||
flavor = {
|
||||
cpu = 4
|
||||
ram = 16
|
||||
node_type = "Single"
|
||||
}
|
||||
flavor_id = data.stackitprivatepreview_sqlserverflexalpha_flavor.sqlserver_flavor.flavor_id
|
||||
storage = {
|
||||
class = "premium-perf2-stackit"
|
||||
size = 50
|
||||
|
|
@ -73,28 +43,27 @@ resource "stackitprivatepreview_sqlserverflexalpha_instance" "ptlsdbsqlsrv" {
|
|||
}
|
||||
}
|
||||
|
||||
# data "stackitalpha_sqlserverflexalpha_instance" "test" {
|
||||
# data "stackitprivatepreview_sqlserverflexalpha_instance" "test" {
|
||||
# project_id = var.project_id
|
||||
# instance_id = var.instance_id
|
||||
# region = "eu01"
|
||||
# }
|
||||
|
||||
# output "test" {
|
||||
# value = data.stackitalpha_sqlserverflexalpha_instance.test
|
||||
# value = data.stackitprivatepreview_sqlserverflexalpha_instance.test
|
||||
# }
|
||||
|
||||
# data "stackitalpha_sqlserverflexalpha_user" "testuser" {
|
||||
# project_id = var.project_id
|
||||
# instance_id = var.instance_id
|
||||
# region = "eu01"
|
||||
# }
|
||||
resource "stackitprivatepreview_sqlserverflexalpha_user" "ptlsdbadminuser" {
|
||||
project_id = var.project_id
|
||||
instance_id = stackitprivatepreview_sqlserverflexalpha_instance.sqlsrv.instance_id
|
||||
username = var.db_admin_username
|
||||
roles = ["##STACKIT_LoginManager##", "##STACKIT_DatabaseManager##"]
|
||||
}
|
||||
|
||||
resource "stackitprivatepreview_sqlserverflexalpha_user" "ptlsdbuser" {
|
||||
project_id = var.project_id
|
||||
instance_id = stackitprivatepreview_sqlserverflexalpha_instance.sqlsrv.instance_id
|
||||
username = var.db_username
|
||||
roles = ["##STACKIT_LoginManager##"]
|
||||
}
|
||||
|
||||
# data "stackitprivatepreview_sqlserverflexalpha_instance" "existing" {
|
||||
# project_id = var.project_id
|
||||
# instance_id = "b31575e9-9dbd-4ff6-b341-82d89c34f14f"
|
||||
# region = "eu01"
|
||||
# }
|
||||
#
|
||||
# output "myinstance" {
|
||||
# value = data.stackitprivatepreview_sqlserverflexalpha_instance.existing
|
||||
# }
|
||||
11
sample/sqlserver/variables.tf.example
Normal file
11
sample/sqlserver/variables.tf.example
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
variable "project_id" {
|
||||
default = "<PROJECT ID UUID>"
|
||||
}
|
||||
|
||||
variable "sa_email" {
|
||||
default = "<SERVICE ACCOUNT EMAIL>"
|
||||
}
|
||||
|
||||
variable "db_username" {
|
||||
default = "<DB USERNAME>"
|
||||
}
|
||||
58
sample/tf.sh
58
sample/tf.sh
|
|
@ -3,28 +3,44 @@
|
|||
|
||||
# ./tf.sh apply > >(tee -a stdout.log) 2> >(tee -a stderr.log >&2)
|
||||
|
||||
# copy or rename sample.tfrc.example and adjust it
|
||||
TERRAFORM_CONFIG=$(pwd)/sample.tfrc
|
||||
export TERRAFORM_CONFIG
|
||||
usage() {
|
||||
echo "$0 usage:" && grep "[[:space:]].)\ #" "$0" | sed 's/#//' | sed -r 's/([a-z])\)/-\1/';
|
||||
exit 0;
|
||||
}
|
||||
|
||||
parsed_options=$(
|
||||
getopt -n "$0" -o dil -- "$@"
|
||||
) || exit
|
||||
eval "set -- $parsed_options"
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case $1 in
|
||||
(-d) TF_LOG=DEBUG
|
||||
export TF_LOG
|
||||
shift;;
|
||||
(-i) TF_LOG=INFO
|
||||
export TF_LOG
|
||||
shift;;
|
||||
(-t) TF_LOG=TRACE
|
||||
export TF_LOG
|
||||
shift;;
|
||||
(--) shift; break;;
|
||||
(*) echo "Unknown option ${1}" # should never be reached.
|
||||
[ $# -eq 0 ] && usage
|
||||
|
||||
CONFIG_FOLDER=$(dirname "$0")
|
||||
BINARY=terraform
|
||||
|
||||
while getopts ":b:hdit" arg; do
|
||||
case $arg in
|
||||
b) # Set binary (default is terraform).
|
||||
BINARY=${OPTARG}
|
||||
shift 2
|
||||
;;
|
||||
d) # Set log level to DEBUG.
|
||||
TF_LOG=DEBUG
|
||||
export TF_LOG
|
||||
shift
|
||||
;;
|
||||
i) # Set log level to INFO.
|
||||
TF_LOG=INFO
|
||||
export TF_LOG
|
||||
shift
|
||||
;;
|
||||
t) # Set log level to TRACE.
|
||||
TF_LOG=TRACE
|
||||
export TF_LOG
|
||||
shift
|
||||
;;
|
||||
h | *) # Display help.
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
terraform "$*"
|
||||
TERRAFORM_CONFIG=${CONFIG_FOLDER}/config.tfrc
|
||||
export TERRAFORM_CONFIG
|
||||
|
||||
${BINARY} "$@"
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
|
||||
# ./tofu.sh apply > >(tee -a stdout.log) 2> >(tee -a stderr.log >&2)
|
||||
|
||||
# copy or rename sample.tfrc.example and adjust it
|
||||
TERRAFORM_CONFIG=$(pwd)/sample.tfrc
|
||||
export TERRAFORM_CONFIG
|
||||
|
||||
parsed_options=$(
|
||||
getopt -n "$0" -o l -- "$@"
|
||||
) || exit
|
||||
eval "set -- $parsed_options"
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case $1 in
|
||||
(-l) TF_LOG=TRACE
|
||||
export TF_LOG
|
||||
shift;;
|
||||
(--) shift; break;;
|
||||
(*) echo "Unknown option ${1}" # should never be reached.
|
||||
esac
|
||||
done
|
||||
|
||||
tofu "$*"
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
# data "stackitprivatepreview_postgresflexalpha_user" "example" {
|
||||
# project_id = stackitprivatepreview_postgresflexalpha_instance.ptlsdbsrv.project_id
|
||||
# instance_id = stackitprivatepreview_postgresflexalpha_instance.ptlsdbsrv.instance_id
|
||||
# user_id = 1
|
||||
# }
|
||||
#
|
||||
# resource "stackitprivatepreview_postgresflexalpha_user" "ptlsdbuser" {
|
||||
# project_id = stackitprivatepreview_postgresflexalpha_instance.ptlsdbsrv.project_id
|
||||
# instance_id = stackitprivatepreview_postgresflexalpha_instance.ptlsdbsrv.instance_id
|
||||
# username = var.db_username
|
||||
# roles = ["createdb", "login"]
|
||||
# # roles = ["createdb", "login", "createrole"]
|
||||
# }
|
||||
#
|
||||
# resource "stackitprivatepreview_sqlserverflexalpha_user" "ptlsdbuser" {
|
||||
# project_id = stackitprivatepreview_sqlserverflexalpha_instance.ptlsdbsqlsrv.project_id
|
||||
# instance_id = stackitprivatepreview_sqlserverflexalpha_instance.ptlsdbsqlsrv.instance_id
|
||||
# username = var.db_username
|
||||
# roles = ["login"]
|
||||
# }
|
||||
|
|
@ -9,7 +9,7 @@ ROOT_DIR=$(git rev-parse --show-toplevel)
|
|||
before_hash=$(find docs -type f -exec sha256sum {} \; | sort | sha256sum | awk '{print $1}')
|
||||
|
||||
# re-generate the docs
|
||||
$ROOT_DIR/scripts/tfplugindocs.sh
|
||||
"${ROOT_DIR}/scripts/tfplugindocs.sh"
|
||||
|
||||
after_hash=$(find docs -type f -exec sha256sum {} \; | sort | sha256sum | awk '{print $1}')
|
||||
|
||||
|
|
|
|||
|
|
@ -5,12 +5,15 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
||||
|
|
@ -90,7 +93,7 @@ func (r *databaseResource) ModifyPlan(
|
|||
|
||||
// Metadata returns the resource type name.
|
||||
func (r *databaseResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_postgresflex_database"
|
||||
resp.TypeName = req.ProviderTypeName + "_postgresflexalpha_database"
|
||||
}
|
||||
|
||||
// Configure adds the provider configured client to the resource.
|
||||
|
|
@ -136,15 +139,13 @@ func (r *databaseResource) Schema(_ context.Context, _ resource.SchemaRequest, r
|
|||
stringplanmodifier.UseStateForUnknown(),
|
||||
},
|
||||
},
|
||||
"database_id": schema.StringAttribute{
|
||||
"database_id": schema.Int64Attribute{
|
||||
Description: descriptions["database_id"],
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
},
|
||||
Validators: []validator.String{
|
||||
validate.NoSeparator(),
|
||||
PlanModifiers: []planmodifier.Int64{
|
||||
int64planmodifier.UseStateForUnknown(),
|
||||
},
|
||||
Validators: []validator.Int64{},
|
||||
},
|
||||
"instance_id": schema.StringAttribute{
|
||||
Description: descriptions["instance_id"],
|
||||
|
|
@ -171,18 +172,20 @@ func (r *databaseResource) Schema(_ context.Context, _ resource.SchemaRequest, r
|
|||
},
|
||||
},
|
||||
"name": schema.StringAttribute{
|
||||
Description: descriptions["name"],
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
Description: descriptions["name"],
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{},
|
||||
Validators: []validator.String{
|
||||
stringvalidator.RegexMatches(
|
||||
regexp.MustCompile("^[a-z]([a-z0-9]*)?$"),
|
||||
"must start with a letter, must have lower case letters or numbers",
|
||||
),
|
||||
},
|
||||
},
|
||||
"owner": schema.StringAttribute{
|
||||
Description: descriptions["owner"],
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
Description: descriptions["owner"],
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{},
|
||||
},
|
||||
"region": schema.StringAttribute{
|
||||
Optional: true,
|
||||
|
|
@ -348,11 +351,86 @@ func (r *databaseResource) Read(
|
|||
// Update updates the resource and sets the updated Terraform state on success.
|
||||
func (r *databaseResource) Update(
|
||||
ctx context.Context,
|
||||
_ resource.UpdateRequest,
|
||||
req resource.UpdateRequest,
|
||||
resp *resource.UpdateResponse,
|
||||
) { // nolint:gocritic // function signature required by Terraform
|
||||
// Update shouldn't be called
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating database", "Database can't be updated")
|
||||
var model Model
|
||||
diags := req.Plan.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
instanceId := model.InstanceId.ValueString()
|
||||
databaseId := model.DatabaseId.ValueInt64()
|
||||
region := model.Region.ValueString()
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "instance_id", instanceId)
|
||||
ctx = tflog.SetField(ctx, "database_id", databaseId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
|
||||
// Retrieve values from state
|
||||
var stateModel Model
|
||||
diags = req.State.Get(ctx, &stateModel)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
modified := false
|
||||
var payload postgresflexalpha.UpdateDatabasePartiallyRequestPayload
|
||||
if stateModel.Name != model.Name {
|
||||
payload.Name = model.Name.ValueStringPointer()
|
||||
modified = true
|
||||
}
|
||||
|
||||
if stateModel.Owner != model.Owner {
|
||||
payload.Owner = model.Owner.ValueStringPointer()
|
||||
modified = true
|
||||
}
|
||||
|
||||
if !modified {
|
||||
tflog.Info(ctx, "no modification detected")
|
||||
return
|
||||
}
|
||||
|
||||
// Update existing database
|
||||
res, err := r.client.UpdateDatabasePartiallyRequest(
|
||||
ctx,
|
||||
projectId,
|
||||
region,
|
||||
instanceId,
|
||||
databaseId,
|
||||
).UpdateDatabasePartiallyRequestPayload(payload).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "error updating database", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
// Map response body to schema
|
||||
err = mapFieldsUpdatePartially(res, &model, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(
|
||||
ctx,
|
||||
&resp.Diagnostics,
|
||||
"Error updating database",
|
||||
fmt.Sprintf("Processing API payload: %v", err),
|
||||
)
|
||||
return
|
||||
}
|
||||
// Set state to fully populated data
|
||||
diags = resp.State.Set(ctx, model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
tflog.Info(ctx, "Postgres Flex database updated")
|
||||
|
||||
}
|
||||
|
||||
// Delete deletes the resource and removes the Terraform state on success.
|
||||
|
|
@ -361,7 +439,6 @@ func (r *databaseResource) Delete(
|
|||
req resource.DeleteRequest,
|
||||
resp *resource.DeleteResponse,
|
||||
) { // nolint:gocritic // function signature required by Terraform
|
||||
// Retrieve values from plan
|
||||
var model Model
|
||||
diags := req.State.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
|
|
@ -424,11 +501,11 @@ func (r *databaseResource) ImportState(
|
|||
tflog.Info(ctx, "Postgres Flex database state imported")
|
||||
}
|
||||
|
||||
func mapFields(databaseResp *postgresflexalpha.ListDatabase, model *Model, region string) error {
|
||||
if databaseResp == nil {
|
||||
func mapFields(resp *postgresflexalpha.ListDatabase, model *Model, region string) error {
|
||||
if resp == nil {
|
||||
return fmt.Errorf("response is nil")
|
||||
}
|
||||
if databaseResp.Id == nil || *databaseResp.Id == 0 {
|
||||
if resp.Id == nil || *resp.Id == 0 {
|
||||
return fmt.Errorf("id not present")
|
||||
}
|
||||
if model == nil {
|
||||
|
|
@ -438,8 +515,8 @@ func mapFields(databaseResp *postgresflexalpha.ListDatabase, model *Model, regio
|
|||
var databaseId int64
|
||||
if model.DatabaseId.ValueInt64() != 0 {
|
||||
databaseId = model.DatabaseId.ValueInt64()
|
||||
} else if databaseResp.Id != nil {
|
||||
databaseId = *databaseResp.Id
|
||||
} else if resp.Id != nil {
|
||||
databaseId = *resp.Id
|
||||
} else {
|
||||
return fmt.Errorf("database id not present")
|
||||
}
|
||||
|
|
@ -447,33 +524,32 @@ func mapFields(databaseResp *postgresflexalpha.ListDatabase, model *Model, regio
|
|||
model.ProjectId.ValueString(), region, model.InstanceId.ValueString(), strconv.FormatInt(databaseId, 10),
|
||||
)
|
||||
model.DatabaseId = types.Int64Value(databaseId)
|
||||
model.Name = types.StringPointerValue(databaseResp.Name)
|
||||
model.Name = types.StringPointerValue(resp.Name)
|
||||
model.Region = types.StringValue(region)
|
||||
|
||||
ownerStr, err := mapOwner(databaseResp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error mapping owner: %w", err)
|
||||
}
|
||||
|
||||
model.Owner = types.StringPointerValue(ownerStr)
|
||||
model.Owner = types.StringPointerValue(cleanString(resp.Owner))
|
||||
return nil
|
||||
}
|
||||
|
||||
func mapOwner(databaseResp *postgresflexalpha.ListDatabase) (*string, error) {
|
||||
if databaseResp == nil {
|
||||
return nil, fmt.Errorf("response is nil")
|
||||
func mapFieldsUpdate(res *postgresflexalpha.UpdateDatabaseResponse, model *Model, region string) error {
|
||||
if res == nil {
|
||||
return fmt.Errorf("response is nil")
|
||||
}
|
||||
return mapFields(res.Database, model, region)
|
||||
}
|
||||
|
||||
if databaseResp.Owner == nil {
|
||||
return nil, nil
|
||||
func mapFieldsUpdatePartially(res *postgresflexalpha.UpdateDatabasePartiallyResponse, model *Model, region string) error {
|
||||
if res == nil {
|
||||
return fmt.Errorf("response is nil")
|
||||
}
|
||||
ownerStr := *databaseResp.Owner
|
||||
return mapFields(res.Database, model, region)
|
||||
}
|
||||
|
||||
// If the field is returned between with quotes, we trim them to prevent an inconsistent result after apply
|
||||
ownerStr = strings.TrimPrefix(ownerStr, `"`)
|
||||
ownerStr = strings.TrimSuffix(ownerStr, `"`)
|
||||
|
||||
return &ownerStr, nil
|
||||
func cleanString(s *string) *string {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
res := strings.Trim(*s, "\"")
|
||||
return &res
|
||||
}
|
||||
|
||||
func toCreatePayload(model *Model) (*postgresflexalpha.CreateDatabaseRequestPayload, error) {
|
||||
|
|
@ -496,6 +572,7 @@ func getDatabase(
|
|||
projectId, region, instanceId string,
|
||||
databaseId int64,
|
||||
) (*postgresflexalpha.ListDatabase, error) {
|
||||
// TODO - implement pagination handling
|
||||
resp, err := client.ListDatabasesRequestExecute(ctx, projectId, region, instanceId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package postgresflexalpha
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
|
@ -182,3 +183,50 @@ func TestToCreatePayload(t *testing.T) {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_cleanString(t *testing.T) {
|
||||
type args struct {
|
||||
s *string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *string
|
||||
}{
|
||||
{
|
||||
name: "simple_value",
|
||||
args: args{
|
||||
s: utils.Ptr("mytest"),
|
||||
},
|
||||
want: utils.Ptr("mytest"),
|
||||
},
|
||||
{
|
||||
name: "simple_value_with_quotes",
|
||||
args: args{
|
||||
s: utils.Ptr("\"mytest\""),
|
||||
},
|
||||
want: utils.Ptr("mytest"),
|
||||
},
|
||||
{
|
||||
name: "simple_values_with_quotes",
|
||||
args: args{
|
||||
s: utils.Ptr("\"my test here\""),
|
||||
},
|
||||
want: utils.Ptr("my test here"),
|
||||
},
|
||||
{
|
||||
name: "simple_values",
|
||||
args: args{
|
||||
s: utils.Ptr("my test here"),
|
||||
},
|
||||
want: utils.Ptr("my test here"),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := cleanString(tt.args.s); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("cleanString() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,502 +0,0 @@
|
|||
package postgresflex
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/attr"
|
||||
"github.com/hashicorp/terraform-plugin-framework/diag"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
|
||||
"github.com/hashicorp/terraform-plugin-go/tftypes"
|
||||
)
|
||||
|
||||
var _ basetypes.ObjectTypable = FlavorType{}
|
||||
|
||||
type FlavorType struct {
|
||||
basetypes.ObjectType
|
||||
}
|
||||
|
||||
func (t FlavorType) Equal(o attr.Type) bool {
|
||||
other, ok := o.(FlavorType)
|
||||
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return t.ObjectType.Equal(other.ObjectType)
|
||||
}
|
||||
|
||||
func (t FlavorType) String() string {
|
||||
return "FlavorType"
|
||||
}
|
||||
|
||||
func (t FlavorType) ValueFromObject(_ context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) {
|
||||
var diags diag.Diagnostics
|
||||
|
||||
attributes := in.Attributes()
|
||||
|
||||
cpuAttribute, ok := attributes["cpu"]
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Missing",
|
||||
`cpu is missing from object`)
|
||||
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
cpuVal, ok := cpuAttribute.(basetypes.Int64Value)
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Wrong Type",
|
||||
fmt.Sprintf(`cpu expected to be basetypes.Int64Value, was: %T`, cpuAttribute))
|
||||
}
|
||||
|
||||
descriptionAttribute, ok := attributes["description"]
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Missing",
|
||||
`description is missing from object`)
|
||||
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
descriptionVal, ok := descriptionAttribute.(basetypes.StringValue)
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Wrong Type",
|
||||
fmt.Sprintf(`description expected to be basetypes.StringValue, was: %T`, descriptionAttribute))
|
||||
}
|
||||
|
||||
idAttribute, ok := attributes["id"]
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Missing",
|
||||
`id is missing from object`)
|
||||
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
idVal, ok := idAttribute.(basetypes.StringValue)
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Wrong Type",
|
||||
fmt.Sprintf(`id expected to be basetypes.StringValue, was: %T`, idAttribute))
|
||||
}
|
||||
|
||||
memoryAttribute, ok := attributes["memory"]
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Missing",
|
||||
`memory is missing from object`)
|
||||
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
ramVal, ok := memoryAttribute.(basetypes.Int64Value)
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Wrong Type",
|
||||
fmt.Sprintf(`memory expected to be basetypes.Int64Value, was: %T`, memoryAttribute))
|
||||
}
|
||||
|
||||
if diags.HasError() {
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
return FlavorValue{
|
||||
Cpu: cpuVal,
|
||||
Description: descriptionVal,
|
||||
Id: idVal,
|
||||
Ram: ramVal,
|
||||
state: attr.ValueStateKnown,
|
||||
}, diags
|
||||
}
|
||||
|
||||
func NewFlavorValueNull() FlavorValue {
|
||||
return FlavorValue{
|
||||
state: attr.ValueStateNull,
|
||||
}
|
||||
}
|
||||
|
||||
func NewFlavorValueUnknown() FlavorValue {
|
||||
return FlavorValue{
|
||||
state: attr.ValueStateUnknown,
|
||||
}
|
||||
}
|
||||
|
||||
func NewFlavorValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (FlavorValue, diag.Diagnostics) {
|
||||
var diags diag.Diagnostics
|
||||
|
||||
// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521
|
||||
ctx := context.Background()
|
||||
|
||||
for name, attributeType := range attributeTypes {
|
||||
attribute, ok := attributes[name]
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Missing FlavorValue Attribute Value",
|
||||
"While creating a FlavorValue value, a missing attribute value was detected. "+
|
||||
"A FlavorValue must contain values for all attributes, even if null or unknown. "+
|
||||
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
|
||||
fmt.Sprintf("FlavorValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()),
|
||||
)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if !attributeType.Equal(attribute.Type(ctx)) {
|
||||
diags.AddError(
|
||||
"Invalid FlavorValue Attribute Type",
|
||||
"While creating a FlavorValue value, an invalid attribute value was detected. "+
|
||||
"A FlavorValue must use a matching attribute type for the value. "+
|
||||
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
|
||||
fmt.Sprintf("FlavorValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+
|
||||
fmt.Sprintf("FlavorValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
for name := range attributes {
|
||||
_, ok := attributeTypes[name]
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Extra FlavorValue Attribute Value",
|
||||
"While creating a FlavorValue value, an extra attribute value was detected. "+
|
||||
"A FlavorValue must not contain values beyond the expected attribute types. "+
|
||||
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
|
||||
fmt.Sprintf("Extra FlavorValue Attribute Name: %s", name),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if diags.HasError() {
|
||||
return NewFlavorValueUnknown(), diags
|
||||
}
|
||||
|
||||
cpuAttribute, ok := attributes["cpu"]
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Missing",
|
||||
`cpu is missing from object`)
|
||||
|
||||
return NewFlavorValueUnknown(), diags
|
||||
}
|
||||
|
||||
cpuVal, ok := cpuAttribute.(basetypes.Int64Value)
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Wrong Type",
|
||||
fmt.Sprintf(`cpu expected to be basetypes.Int64Value, was: %T`, cpuAttribute))
|
||||
}
|
||||
|
||||
descriptionAttribute, ok := attributes["description"]
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Missing",
|
||||
`description is missing from object`)
|
||||
|
||||
return NewFlavorValueUnknown(), diags
|
||||
}
|
||||
|
||||
descriptionVal, ok := descriptionAttribute.(basetypes.StringValue)
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Wrong Type",
|
||||
fmt.Sprintf(`description expected to be basetypes.StringValue, was: %T`, descriptionAttribute))
|
||||
}
|
||||
|
||||
idAttribute, ok := attributes["id"]
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Missing",
|
||||
`id is missing from object`)
|
||||
|
||||
return NewFlavorValueUnknown(), diags
|
||||
}
|
||||
|
||||
idVal, ok := idAttribute.(basetypes.StringValue)
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Wrong Type",
|
||||
fmt.Sprintf(`id expected to be basetypes.StringValue, was: %T`, idAttribute))
|
||||
}
|
||||
|
||||
memoryAttribute, ok := attributes["memory"]
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Missing",
|
||||
`memory is missing from object`)
|
||||
|
||||
return NewFlavorValueUnknown(), diags
|
||||
}
|
||||
|
||||
memoryVal, ok := memoryAttribute.(basetypes.Int64Value)
|
||||
|
||||
if !ok {
|
||||
diags.AddError(
|
||||
"Attribute Wrong Type",
|
||||
fmt.Sprintf(`memory expected to be basetypes.Int64Value, was: %T`, memoryAttribute))
|
||||
}
|
||||
|
||||
if diags.HasError() {
|
||||
return NewFlavorValueUnknown(), diags
|
||||
}
|
||||
|
||||
return FlavorValue{
|
||||
Cpu: cpuVal,
|
||||
Description: descriptionVal,
|
||||
Id: idVal,
|
||||
Ram: memoryVal,
|
||||
state: attr.ValueStateKnown,
|
||||
}, diags
|
||||
}
|
||||
|
||||
func NewFlavorValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) FlavorValue {
|
||||
object, diags := NewFlavorValue(attributeTypes, attributes)
|
||||
|
||||
if diags.HasError() {
|
||||
// This could potentially be added to the diag package.
|
||||
diagsStrings := make([]string, 0, len(diags))
|
||||
|
||||
for _, diagnostic := range diags {
|
||||
diagsStrings = append(diagsStrings, fmt.Sprintf(
|
||||
"%s | %s | %s",
|
||||
diagnostic.Severity(),
|
||||
diagnostic.Summary(),
|
||||
diagnostic.Detail()))
|
||||
}
|
||||
|
||||
panic("NewFlavorValueMust received error(s): " + strings.Join(diagsStrings, "\n"))
|
||||
}
|
||||
|
||||
return object
|
||||
}
|
||||
|
||||
func (t FlavorType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) {
|
||||
if in.Type() == nil {
|
||||
return NewFlavorValueNull(), nil
|
||||
}
|
||||
|
||||
if !in.Type().Equal(t.TerraformType(ctx)) {
|
||||
return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type())
|
||||
}
|
||||
|
||||
if !in.IsKnown() {
|
||||
return NewFlavorValueUnknown(), nil
|
||||
}
|
||||
|
||||
if in.IsNull() {
|
||||
return NewFlavorValueNull(), nil
|
||||
}
|
||||
|
||||
attributes := map[string]attr.Value{}
|
||||
|
||||
val := map[string]tftypes.Value{}
|
||||
|
||||
err := in.As(&val)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for k, v := range val {
|
||||
a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
attributes[k] = a
|
||||
}
|
||||
|
||||
return NewFlavorValueMust(FlavorValue{}.AttributeTypes(ctx), attributes), nil
|
||||
}
|
||||
|
||||
func (t FlavorType) ValueType(_ context.Context) attr.Value {
|
||||
return FlavorValue{}
|
||||
}
|
||||
|
||||
var _ basetypes.ObjectValuable = FlavorValue{}
|
||||
|
||||
type FlavorValue struct {
|
||||
Cpu basetypes.Int64Value `tfsdk:"cpu"`
|
||||
Description basetypes.StringValue `tfsdk:"description"`
|
||||
Id basetypes.StringValue `tfsdk:"id"`
|
||||
Ram basetypes.Int64Value `tfsdk:"ram"`
|
||||
state attr.ValueState
|
||||
}
|
||||
|
||||
func (v FlavorValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) {
|
||||
attrTypes := make(map[string]tftypes.Type, 4)
|
||||
|
||||
var val tftypes.Value
|
||||
var err error
|
||||
|
||||
attrTypes["cpu"] = basetypes.Int64Type{}.TerraformType(ctx)
|
||||
attrTypes["description"] = basetypes.StringType{}.TerraformType(ctx)
|
||||
attrTypes["id"] = basetypes.StringType{}.TerraformType(ctx)
|
||||
attrTypes["memory"] = basetypes.Int64Type{}.TerraformType(ctx)
|
||||
|
||||
objectType := tftypes.Object{AttributeTypes: attrTypes}
|
||||
|
||||
switch v.state {
|
||||
case attr.ValueStateKnown:
|
||||
vals := make(map[string]tftypes.Value, 4)
|
||||
|
||||
val, err = v.Cpu.ToTerraformValue(ctx)
|
||||
|
||||
if err != nil {
|
||||
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
|
||||
}
|
||||
|
||||
vals["cpu"] = val
|
||||
|
||||
val, err = v.Description.ToTerraformValue(ctx)
|
||||
|
||||
if err != nil {
|
||||
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
|
||||
}
|
||||
|
||||
vals["description"] = val
|
||||
|
||||
val, err = v.Id.ToTerraformValue(ctx)
|
||||
|
||||
if err != nil {
|
||||
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
|
||||
}
|
||||
|
||||
vals["id"] = val
|
||||
|
||||
val, err = v.Ram.ToTerraformValue(ctx)
|
||||
|
||||
if err != nil {
|
||||
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
|
||||
}
|
||||
|
||||
vals["memory"] = val
|
||||
|
||||
if err := tftypes.ValidateValue(objectType, vals); err != nil {
|
||||
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
|
||||
}
|
||||
|
||||
return tftypes.NewValue(objectType, vals), nil
|
||||
case attr.ValueStateNull:
|
||||
return tftypes.NewValue(objectType, nil), nil
|
||||
case attr.ValueStateUnknown:
|
||||
return tftypes.NewValue(objectType, tftypes.UnknownValue), nil
|
||||
default:
|
||||
panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state))
|
||||
}
|
||||
}
|
||||
|
||||
func (v FlavorValue) IsNull() bool {
|
||||
return v.state == attr.ValueStateNull
|
||||
}
|
||||
|
||||
func (v FlavorValue) IsUnknown() bool {
|
||||
return v.state == attr.ValueStateUnknown
|
||||
}
|
||||
|
||||
func (v FlavorValue) String() string {
|
||||
return "FlavorValue"
|
||||
}
|
||||
|
||||
func (v FlavorValue) ToObjectValue(_ context.Context) (basetypes.ObjectValue, diag.Diagnostics) {
|
||||
var diags diag.Diagnostics
|
||||
|
||||
attributeTypes := map[string]attr.Type{
|
||||
"cpu": basetypes.Int64Type{},
|
||||
"description": basetypes.StringType{},
|
||||
"id": basetypes.StringType{},
|
||||
"memory": basetypes.Int64Type{},
|
||||
}
|
||||
|
||||
if v.IsNull() {
|
||||
return types.ObjectNull(attributeTypes), diags
|
||||
}
|
||||
|
||||
if v.IsUnknown() {
|
||||
return types.ObjectUnknown(attributeTypes), diags
|
||||
}
|
||||
|
||||
objVal, diags := types.ObjectValue(
|
||||
attributeTypes,
|
||||
map[string]attr.Value{
|
||||
"cpu": v.Cpu,
|
||||
"description": v.Description,
|
||||
"id": v.Id,
|
||||
"memory": v.Ram,
|
||||
})
|
||||
|
||||
return objVal, diags
|
||||
}
|
||||
|
||||
func (v FlavorValue) Equal(o attr.Value) bool {
|
||||
other, ok := o.(FlavorValue)
|
||||
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if v.state != other.state {
|
||||
return false
|
||||
}
|
||||
|
||||
if v.state != attr.ValueStateKnown {
|
||||
return true
|
||||
}
|
||||
|
||||
if !v.Cpu.Equal(other.Cpu) {
|
||||
return false
|
||||
}
|
||||
|
||||
if !v.Description.Equal(other.Description) {
|
||||
return false
|
||||
}
|
||||
|
||||
if !v.Id.Equal(other.Id) {
|
||||
return false
|
||||
}
|
||||
|
||||
if !v.Ram.Equal(other.Ram) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (v FlavorValue) Type(ctx context.Context) attr.Type {
|
||||
return FlavorType{
|
||||
basetypes.ObjectType{
|
||||
AttrTypes: v.AttributeTypes(ctx),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (v FlavorValue) AttributeTypes(_ context.Context) map[string]attr.Type {
|
||||
return map[string]attr.Type{
|
||||
"cpu": basetypes.Int64Type{},
|
||||
"description": basetypes.StringType{},
|
||||
"id": basetypes.StringType{},
|
||||
"memory": basetypes.Int64Type{},
|
||||
}
|
||||
}
|
||||
251
stackit/internal/services/postgresflexalpha/flavor/datasource.go
Normal file
251
stackit/internal/services/postgresflexalpha/flavor/datasource.go
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
package postgresFlexAlphaFlavor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/attr"
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
|
||||
"github.com/mhenselin/terraform-provider-stackitprivatepreview/pkg/postgresflexalpha"
|
||||
"github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/conversion"
|
||||
postgresflex "github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha"
|
||||
postgresflexUtils "github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/utils"
|
||||
"github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/utils"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
"github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/core"
|
||||
)
|
||||
|
||||
// Ensure the implementation satisfies the expected interfaces.
|
||||
var (
|
||||
_ datasource.DataSource = &flavorDataSource{}
|
||||
)
|
||||
|
||||
type FlavorModel struct {
|
||||
ProjectId types.String `tfsdk:"project_id"`
|
||||
Region types.String `tfsdk:"region"`
|
||||
StorageClass types.String `tfsdk:"storage_class"`
|
||||
Cpu types.Int64 `tfsdk:"cpu"`
|
||||
Description types.String `tfsdk:"description"`
|
||||
Id types.String `tfsdk:"id"`
|
||||
FlavorId types.String `tfsdk:"flavor_id"`
|
||||
MaxGb types.Int64 `tfsdk:"max_gb"`
|
||||
Memory types.Int64 `tfsdk:"ram"`
|
||||
MinGb types.Int64 `tfsdk:"min_gb"`
|
||||
NodeType types.String `tfsdk:"node_type"`
|
||||
StorageClasses types.List `tfsdk:"storage_classes"`
|
||||
}
|
||||
|
||||
// NewFlavorDataSource is a helper function to simplify the provider implementation.
|
||||
func NewFlavorDataSource() datasource.DataSource {
|
||||
return &flavorDataSource{}
|
||||
}
|
||||
|
||||
// flavorDataSource is the data source implementation.
|
||||
type flavorDataSource struct {
|
||||
client *postgresflexalpha.APIClient
|
||||
providerData core.ProviderData
|
||||
}
|
||||
|
||||
// Metadata returns the data source type name.
|
||||
func (r *flavorDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_postgresflexalpha_flavor"
|
||||
}
|
||||
|
||||
// Configure adds the provider configured client to the data source.
|
||||
func (r *flavorDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
|
||||
var ok bool
|
||||
r.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
apiClient := postgresflexUtils.ConfigureClient(ctx, &r.providerData, &resp.Diagnostics)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
r.client = apiClient
|
||||
tflog.Info(ctx, "Postgres Flex instance client configured")
|
||||
}
|
||||
|
||||
func (r *flavorDataSource) Schema(ctx context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
resp.Schema = schema.Schema{
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"project_id": schema.StringAttribute{
|
||||
Required: true,
|
||||
Description: "The cpu count of the instance.",
|
||||
MarkdownDescription: "The cpu count of the instance.",
|
||||
},
|
||||
"region": schema.StringAttribute{
|
||||
Required: true,
|
||||
Description: "The flavor description.",
|
||||
MarkdownDescription: "The flavor description.",
|
||||
},
|
||||
"cpu": schema.Int64Attribute{
|
||||
Required: true,
|
||||
Description: "The cpu count of the instance.",
|
||||
MarkdownDescription: "The cpu count of the instance.",
|
||||
},
|
||||
"ram": schema.Int64Attribute{
|
||||
Required: true,
|
||||
Description: "The memory of the instance in Gibibyte.",
|
||||
MarkdownDescription: "The memory of the instance in Gibibyte.",
|
||||
},
|
||||
"storage_class": schema.StringAttribute{
|
||||
Required: true,
|
||||
Description: "The memory of the instance in Gibibyte.",
|
||||
MarkdownDescription: "The memory of the instance in Gibibyte.",
|
||||
},
|
||||
"description": schema.StringAttribute{
|
||||
Computed: true,
|
||||
Description: "The flavor description.",
|
||||
MarkdownDescription: "The flavor description.",
|
||||
},
|
||||
"id": schema.StringAttribute{
|
||||
Computed: true,
|
||||
Description: "The terraform id of the instance flavor.",
|
||||
MarkdownDescription: "The terraform id of the instance flavor.",
|
||||
},
|
||||
"flavor_id": schema.StringAttribute{
|
||||
Computed: true,
|
||||
Description: "The flavor id of the instance flavor.",
|
||||
MarkdownDescription: "The flavor id of the instance flavor.",
|
||||
},
|
||||
"max_gb": schema.Int64Attribute{
|
||||
Computed: true,
|
||||
Description: "maximum storage which can be ordered for the flavor in Gigabyte.",
|
||||
MarkdownDescription: "maximum storage which can be ordered for the flavor in Gigabyte.",
|
||||
},
|
||||
"min_gb": schema.Int64Attribute{
|
||||
Computed: true,
|
||||
Description: "minimum storage which is required to order in Gigabyte.",
|
||||
MarkdownDescription: "minimum storage which is required to order in Gigabyte.",
|
||||
},
|
||||
"node_type": schema.StringAttribute{
|
||||
Required: true,
|
||||
Description: "defines the nodeType it can be either single or replica",
|
||||
MarkdownDescription: "defines the nodeType it can be either single or replica",
|
||||
},
|
||||
"storage_classes": schema.ListNestedAttribute{
|
||||
Computed: true,
|
||||
NestedObject: schema.NestedAttributeObject{
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"class": schema.StringAttribute{
|
||||
Computed: true,
|
||||
},
|
||||
"max_io_per_sec": schema.Int64Attribute{
|
||||
Computed: true,
|
||||
},
|
||||
"max_through_in_mb": schema.Int64Attribute{
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
CustomType: postgresflex.StorageClassesType{
|
||||
ObjectType: types.ObjectType{
|
||||
AttrTypes: postgresflex.StorageClassesValue{}.AttributeTypes(ctx),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (r *flavorDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
|
||||
var model FlavorModel
|
||||
diags := req.Config.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
|
||||
flavors, err := getAllFlavors(ctx, r.client, projectId, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading flavors", fmt.Sprintf("getAllFlavors: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
var foundFlavors []postgresflexalpha.ListFlavors
|
||||
for _, flavor := range flavors {
|
||||
if model.Cpu.ValueInt64() != *flavor.Cpu {
|
||||
continue
|
||||
}
|
||||
if model.Memory.ValueInt64() != *flavor.Memory {
|
||||
continue
|
||||
}
|
||||
if model.NodeType.ValueString() != *flavor.NodeType {
|
||||
continue
|
||||
}
|
||||
for _, sc := range *flavor.StorageClasses {
|
||||
if model.StorageClass.ValueString() != *sc.Class {
|
||||
continue
|
||||
}
|
||||
foundFlavors = append(foundFlavors, flavor)
|
||||
}
|
||||
}
|
||||
if len(foundFlavors) == 0 {
|
||||
resp.Diagnostics.AddError("get flavor", "could not find requested flavor")
|
||||
return
|
||||
}
|
||||
if len(foundFlavors) > 1 {
|
||||
resp.Diagnostics.AddError("get flavor", "found too many matching flavors")
|
||||
return
|
||||
}
|
||||
|
||||
f := foundFlavors[0]
|
||||
model.Description = types.StringValue(*f.Description)
|
||||
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, *f.Id)
|
||||
model.FlavorId = types.StringValue(*f.Id)
|
||||
model.MaxGb = types.Int64Value(*f.MaxGB)
|
||||
model.MinGb = types.Int64Value(*f.MinGB)
|
||||
|
||||
if f.StorageClasses == nil {
|
||||
model.StorageClasses = types.ListNull(postgresflex.StorageClassesType{
|
||||
ObjectType: basetypes.ObjectType{
|
||||
AttrTypes: postgresflex.StorageClassesValue{}.AttributeTypes(ctx),
|
||||
},
|
||||
})
|
||||
} else {
|
||||
var scList []attr.Value
|
||||
for _, sc := range *f.StorageClasses {
|
||||
scList = append(
|
||||
scList,
|
||||
postgresflex.NewStorageClassesValueMust(
|
||||
postgresflex.StorageClassesValue{}.AttributeTypes(ctx),
|
||||
map[string]attr.Value{
|
||||
"class": types.StringValue(*sc.Class),
|
||||
"max_io_per_sec": types.Int64Value(*sc.MaxIoPerSec),
|
||||
"max_through_in_mb": types.Int64Value(*sc.MaxThroughInMb),
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
storageClassesList := types.ListValueMust(
|
||||
postgresflex.StorageClassesType{
|
||||
ObjectType: basetypes.ObjectType{
|
||||
AttrTypes: postgresflex.StorageClassesValue{}.AttributeTypes(ctx),
|
||||
},
|
||||
},
|
||||
scList,
|
||||
)
|
||||
model.StorageClasses = storageClassesList
|
||||
}
|
||||
|
||||
// Set refreshed state
|
||||
diags = resp.State.Set(ctx, model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
tflog.Info(ctx, "Postgres Flex flavors read")
|
||||
}
|
||||
201
stackit/internal/services/postgresflexalpha/flavor/functions.go
Normal file
201
stackit/internal/services/postgresflexalpha/flavor/functions.go
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
package postgresFlexAlphaFlavor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
postgresflex "github.com/mhenselin/terraform-provider-stackitprivatepreview/pkg/postgresflexalpha"
|
||||
)
|
||||
|
||||
type flavorsClient interface {
|
||||
GetFlavorsRequestExecute(
|
||||
ctx context.Context,
|
||||
projectId, region string,
|
||||
page, size *int64,
|
||||
sort *postgresflex.FlavorSort,
|
||||
) (*postgresflex.GetFlavorsResponse, error)
|
||||
}
|
||||
|
||||
//func loadFlavorId(ctx context.Context, client flavorsClient, model *Model, flavor *flavorModel, storage *storageModel) error {
|
||||
// if model == nil {
|
||||
// return fmt.Errorf("nil model")
|
||||
// }
|
||||
// if flavor == nil {
|
||||
// return fmt.Errorf("nil flavor")
|
||||
// }
|
||||
// cpu := flavor.CPU.ValueInt64()
|
||||
// if cpu == 0 {
|
||||
// return fmt.Errorf("nil CPU")
|
||||
// }
|
||||
// ram := flavor.RAM.ValueInt64()
|
||||
// if ram == 0 {
|
||||
// return fmt.Errorf("nil RAM")
|
||||
// }
|
||||
//
|
||||
// nodeType := flavor.NodeType.ValueString()
|
||||
// if nodeType == "" {
|
||||
// if model.Replicas.IsNull() || model.Replicas.IsUnknown() {
|
||||
// return fmt.Errorf("nil NodeType")
|
||||
// }
|
||||
// switch model.Replicas.ValueInt64() {
|
||||
// case 1:
|
||||
// nodeType = "Single"
|
||||
// case 3:
|
||||
// nodeType = "Replica"
|
||||
// default:
|
||||
// return fmt.Errorf("unknown Replicas value: %d", model.Replicas.ValueInt64())
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// storageClass := conversion.StringValueToPointer(storage.Class)
|
||||
// if storageClass == nil {
|
||||
// return fmt.Errorf("nil StorageClass")
|
||||
// }
|
||||
// storageSize := conversion.Int64ValueToPointer(storage.Size)
|
||||
// if storageSize == nil {
|
||||
// return fmt.Errorf("nil StorageSize")
|
||||
// }
|
||||
//
|
||||
// projectId := model.ProjectId.ValueString()
|
||||
// region := model.Region.ValueString()
|
||||
//
|
||||
// flavorList, err := getAllFlavors(ctx, client, projectId, region)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// avl := ""
|
||||
// foundFlavorCount := 0
|
||||
// var foundFlavors []string
|
||||
// for _, f := range flavorList {
|
||||
// if f.Id == nil || f.Cpu == nil || f.Memory == nil {
|
||||
// continue
|
||||
// }
|
||||
// if !strings.EqualFold(*f.NodeType, nodeType) {
|
||||
// continue
|
||||
// }
|
||||
// if *f.Cpu == cpu && *f.Memory == ram {
|
||||
// var useSc *postgresflex.FlavorStorageClassesStorageClass
|
||||
// for _, sc := range *f.StorageClasses {
|
||||
// if *sc.Class != *storageClass {
|
||||
// continue
|
||||
// }
|
||||
// if *storageSize < *f.MinGB || *storageSize > *f.MaxGB {
|
||||
// return fmt.Errorf("storage size %d out of bounds (min: %d - max: %d)", *storageSize, *f.MinGB, *f.MaxGB)
|
||||
// }
|
||||
// useSc = &sc
|
||||
// }
|
||||
// if useSc == nil {
|
||||
// return fmt.Errorf("no storage class found for %s", *storageClass)
|
||||
// }
|
||||
//
|
||||
// flavor.Id = types.StringValue(*f.Id)
|
||||
// flavor.Description = types.StringValue(*f.Description)
|
||||
// foundFlavors = append(foundFlavors, fmt.Sprintf("%s (%d/%d - %s)", *f.Id, *f.Cpu, *f.Memory, *f.NodeType))
|
||||
// foundFlavorCount++
|
||||
// }
|
||||
// for _, cls := range *f.StorageClasses {
|
||||
// avl = fmt.Sprintf("%s\n- %d CPU, %d GB RAM, storage %s (min: %d - max: %d)", avl, *f.Cpu, *f.Memory, *cls.Class, *f.MinGB, *f.MaxGB)
|
||||
// }
|
||||
// }
|
||||
// if foundFlavorCount > 1 {
|
||||
// return fmt.Errorf(
|
||||
// "number of flavors returned: %d\nmultiple flavors found: %d flavors\n %s",
|
||||
// len(flavorList),
|
||||
// foundFlavorCount,
|
||||
// strings.Join(foundFlavors, "\n "),
|
||||
// )
|
||||
// }
|
||||
// if flavor.Id.ValueString() == "" {
|
||||
// return fmt.Errorf("couldn't find flavor, available specs are:%s", avl)
|
||||
// }
|
||||
//
|
||||
// return nil
|
||||
//}
|
||||
|
||||
func getAllFlavors(ctx context.Context, client flavorsClient, projectId, region string) ([]postgresflex.ListFlavors, error) {
|
||||
if projectId == "" || region == "" {
|
||||
return nil, fmt.Errorf("listing postgresflex flavors: projectId and region are required")
|
||||
}
|
||||
var flavorList []postgresflex.ListFlavors
|
||||
|
||||
page := int64(1)
|
||||
size := int64(10)
|
||||
sort := postgresflex.FLAVORSORT_INDEX_ASC
|
||||
counter := 0
|
||||
for {
|
||||
res, err := client.GetFlavorsRequestExecute(ctx, projectId, region, &page, &size, &sort)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("listing postgresflex flavors: %w", err)
|
||||
}
|
||||
if res.Flavors == nil {
|
||||
return nil, fmt.Errorf("finding flavors for project %s", projectId)
|
||||
}
|
||||
pagination := res.GetPagination()
|
||||
flavors := res.GetFlavors()
|
||||
flavorList = append(flavorList, flavors...)
|
||||
|
||||
if *pagination.TotalRows < int64(len(flavorList)) {
|
||||
return nil, fmt.Errorf("total rows is smaller than current accumulated list - that should not happen")
|
||||
}
|
||||
if *pagination.TotalRows == int64(len(flavorList)) {
|
||||
break
|
||||
}
|
||||
page++
|
||||
|
||||
if page > *pagination.TotalPages {
|
||||
break
|
||||
}
|
||||
|
||||
// implement a breakpoint
|
||||
counter++
|
||||
if counter > 1000 {
|
||||
panic("too many pagination results")
|
||||
}
|
||||
}
|
||||
return flavorList, nil
|
||||
}
|
||||
|
||||
//func getFlavorModelById(ctx context.Context, client flavorsClient, model *Model, flavor *flavorModel) error {
|
||||
// if model == nil {
|
||||
// return fmt.Errorf("nil model")
|
||||
// }
|
||||
// if flavor == nil {
|
||||
// return fmt.Errorf("nil flavor")
|
||||
// }
|
||||
// id := conversion.StringValueToPointer(flavor.Id)
|
||||
// if id == nil {
|
||||
// return fmt.Errorf("nil flavor ID")
|
||||
// }
|
||||
//
|
||||
// flavor.Id = types.StringValue("")
|
||||
//
|
||||
// projectId := model.ProjectId.ValueString()
|
||||
// region := model.Region.ValueString()
|
||||
//
|
||||
// flavorList, err := getAllFlavors(ctx, client, projectId, region)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// avl := ""
|
||||
// for _, f := range flavorList {
|
||||
// if f.Id == nil || f.Cpu == nil || f.Memory == nil {
|
||||
// continue
|
||||
// }
|
||||
// if *f.Id == *id {
|
||||
// flavor.Id = types.StringValue(*f.Id)
|
||||
// flavor.Description = types.StringValue(*f.Description)
|
||||
// flavor.CPU = types.Int64Value(*f.Cpu)
|
||||
// flavor.RAM = types.Int64Value(*f.Memory)
|
||||
// flavor.NodeType = types.StringValue(*f.NodeType)
|
||||
// break
|
||||
// }
|
||||
// avl = fmt.Sprintf("%s\n- %d CPU, %d GB RAM", avl, *f.Cpu, *f.Memory)
|
||||
// }
|
||||
// if flavor.Id.ValueString() == "" {
|
||||
// return fmt.Errorf("couldn't find flavor, available specs are: %s", avl)
|
||||
// }
|
||||
//
|
||||
// return nil
|
||||
//}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
package postgresFlexAlphaFlavor
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/mhenselin/terraform-provider-stackitprivatepreview/pkg/postgresflexalpha"
|
||||
"github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/conversion"
|
||||
"github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha"
|
||||
postgresflexUtils "github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/utils"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
"github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/core"
|
||||
)
|
||||
|
||||
// Ensure the implementation satisfies the expected interfaces.
|
||||
var (
|
||||
_ datasource.DataSource = &flavorListDataSource{}
|
||||
)
|
||||
|
||||
// NewFlavorListDataSource is a helper function to simplify the provider implementation.
|
||||
func NewFlavorListDataSource() datasource.DataSource {
|
||||
return &flavorListDataSource{}
|
||||
}
|
||||
|
||||
// flavorDataSource is the data source implementation.
|
||||
type flavorListDataSource struct {
|
||||
client *postgresflexalpha.APIClient
|
||||
providerData core.ProviderData
|
||||
}
|
||||
|
||||
// Metadata returns the data source type name.
|
||||
func (r *flavorListDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_postgresflexalpha_flavorlist"
|
||||
}
|
||||
|
||||
// Configure adds the provider configured client to the data source.
|
||||
func (r *flavorListDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
|
||||
var ok bool
|
||||
r.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
apiClient := postgresflexUtils.ConfigureClient(ctx, &r.providerData, &resp.Diagnostics)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
r.client = apiClient
|
||||
tflog.Info(ctx, "Postgres Flex flavors client configured")
|
||||
}
|
||||
|
||||
func (r *flavorListDataSource) Schema(ctx context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
resp.Schema = postgresflex.FlavorDataSourceSchema(ctx)
|
||||
}
|
||||
|
||||
func (r *flavorListDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
|
||||
var model postgresflex.FlavorModel
|
||||
diags := req.Config.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
|
||||
// Set refreshed state
|
||||
diags = resp.State.Set(ctx, model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
tflog.Info(ctx, "Postgres Flex flavors read")
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,514 +0,0 @@
|
|||
package postgresflex
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/attr"
|
||||
"github.com/hashicorp/terraform-plugin-framework/diag"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
|
||||
"github.com/hashicorp/terraform-plugin-go/tftypes"
|
||||
)
|
||||
|
||||
func TestFlavorType_Equal(t1 *testing.T) {
|
||||
type fields struct {
|
||||
ObjectType basetypes.ObjectType
|
||||
}
|
||||
type args struct {
|
||||
o attr.Type
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t1.Run(tt.name, func(t1 *testing.T) {
|
||||
t := FlavorType{
|
||||
ObjectType: tt.fields.ObjectType,
|
||||
}
|
||||
if got := t.Equal(tt.args.o); got != tt.want {
|
||||
t1.Errorf("Equal() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlavorType_String(t1 *testing.T) {
|
||||
type fields struct {
|
||||
ObjectType basetypes.ObjectType
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want string
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t1.Run(tt.name, func(t1 *testing.T) {
|
||||
t := FlavorType{
|
||||
ObjectType: tt.fields.ObjectType,
|
||||
}
|
||||
if got := t.String(); got != tt.want {
|
||||
t1.Errorf("String() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlavorType_ValueFromObject(t1 *testing.T) {
|
||||
type fields struct {
|
||||
ObjectType basetypes.ObjectType
|
||||
}
|
||||
type args struct {
|
||||
in0 context.Context
|
||||
in basetypes.ObjectValue
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want basetypes.ObjectValuable
|
||||
want1 diag.Diagnostics
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t1.Run(tt.name, func(t1 *testing.T) {
|
||||
t := FlavorType{
|
||||
ObjectType: tt.fields.ObjectType,
|
||||
}
|
||||
got, got1 := t.ValueFromObject(tt.args.in0, tt.args.in)
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t1.Errorf("ValueFromObject() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
if !reflect.DeepEqual(got1, tt.want1) {
|
||||
t1.Errorf("ValueFromObject() got1 = %v, want %v", got1, tt.want1)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlavorType_ValueFromTerraform(t1 *testing.T) {
|
||||
type fields struct {
|
||||
ObjectType basetypes.ObjectType
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
in tftypes.Value
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want attr.Value
|
||||
wantErr bool
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t1.Run(tt.name, func(t1 *testing.T) {
|
||||
t := FlavorType{
|
||||
ObjectType: tt.fields.ObjectType,
|
||||
}
|
||||
got, err := t.ValueFromTerraform(tt.args.ctx, tt.args.in)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t1.Errorf("ValueFromTerraform() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t1.Errorf("ValueFromTerraform() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlavorType_ValueType(t1 *testing.T) {
|
||||
type fields struct {
|
||||
ObjectType basetypes.ObjectType
|
||||
}
|
||||
type args struct {
|
||||
in0 context.Context
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want attr.Value
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t1.Run(tt.name, func(t1 *testing.T) {
|
||||
t := FlavorType{
|
||||
ObjectType: tt.fields.ObjectType,
|
||||
}
|
||||
if got := t.ValueType(tt.args.in0); !reflect.DeepEqual(got, tt.want) {
|
||||
t1.Errorf("ValueType() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlavorValue_AttributeTypes(t *testing.T) {
|
||||
type fields struct {
|
||||
Cpu basetypes.Int64Value
|
||||
Description basetypes.StringValue
|
||||
Id basetypes.StringValue
|
||||
Ram basetypes.Int64Value
|
||||
state attr.ValueState
|
||||
}
|
||||
type args struct {
|
||||
in0 context.Context
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want map[string]attr.Type
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
v := FlavorValue{
|
||||
Cpu: tt.fields.Cpu,
|
||||
Description: tt.fields.Description,
|
||||
Id: tt.fields.Id,
|
||||
Ram: tt.fields.Ram,
|
||||
state: tt.fields.state,
|
||||
}
|
||||
if got := v.AttributeTypes(tt.args.in0); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("AttributeTypes() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlavorValue_Equal(t *testing.T) {
|
||||
type fields struct {
|
||||
Cpu basetypes.Int64Value
|
||||
Description basetypes.StringValue
|
||||
Id basetypes.StringValue
|
||||
Ram basetypes.Int64Value
|
||||
state attr.ValueState
|
||||
}
|
||||
type args struct {
|
||||
o attr.Value
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
v := FlavorValue{
|
||||
Cpu: tt.fields.Cpu,
|
||||
Description: tt.fields.Description,
|
||||
Id: tt.fields.Id,
|
||||
Ram: tt.fields.Ram,
|
||||
state: tt.fields.state,
|
||||
}
|
||||
if got := v.Equal(tt.args.o); got != tt.want {
|
||||
t.Errorf("Equal() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlavorValue_IsNull(t *testing.T) {
|
||||
type fields struct {
|
||||
Cpu basetypes.Int64Value
|
||||
Description basetypes.StringValue
|
||||
Id basetypes.StringValue
|
||||
Ram basetypes.Int64Value
|
||||
state attr.ValueState
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want bool
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
v := FlavorValue{
|
||||
Cpu: tt.fields.Cpu,
|
||||
Description: tt.fields.Description,
|
||||
Id: tt.fields.Id,
|
||||
Ram: tt.fields.Ram,
|
||||
state: tt.fields.state,
|
||||
}
|
||||
if got := v.IsNull(); got != tt.want {
|
||||
t.Errorf("IsNull() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlavorValue_IsUnknown(t *testing.T) {
|
||||
type fields struct {
|
||||
Cpu basetypes.Int64Value
|
||||
Description basetypes.StringValue
|
||||
Id basetypes.StringValue
|
||||
Ram basetypes.Int64Value
|
||||
state attr.ValueState
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want bool
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
v := FlavorValue{
|
||||
Cpu: tt.fields.Cpu,
|
||||
Description: tt.fields.Description,
|
||||
Id: tt.fields.Id,
|
||||
Ram: tt.fields.Ram,
|
||||
state: tt.fields.state,
|
||||
}
|
||||
if got := v.IsUnknown(); got != tt.want {
|
||||
t.Errorf("IsUnknown() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlavorValue_String(t *testing.T) {
|
||||
type fields struct {
|
||||
Cpu basetypes.Int64Value
|
||||
Description basetypes.StringValue
|
||||
Id basetypes.StringValue
|
||||
Ram basetypes.Int64Value
|
||||
state attr.ValueState
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want string
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
v := FlavorValue{
|
||||
Cpu: tt.fields.Cpu,
|
||||
Description: tt.fields.Description,
|
||||
Id: tt.fields.Id,
|
||||
Ram: tt.fields.Ram,
|
||||
state: tt.fields.state,
|
||||
}
|
||||
if got := v.String(); got != tt.want {
|
||||
t.Errorf("String() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlavorValue_ToObjectValue(t *testing.T) {
|
||||
type fields struct {
|
||||
Cpu basetypes.Int64Value
|
||||
Description basetypes.StringValue
|
||||
Id basetypes.StringValue
|
||||
Ram basetypes.Int64Value
|
||||
state attr.ValueState
|
||||
}
|
||||
type args struct {
|
||||
in0 context.Context
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want basetypes.ObjectValue
|
||||
want1 diag.Diagnostics
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
v := FlavorValue{
|
||||
Cpu: tt.fields.Cpu,
|
||||
Description: tt.fields.Description,
|
||||
Id: tt.fields.Id,
|
||||
Ram: tt.fields.Ram,
|
||||
state: tt.fields.state,
|
||||
}
|
||||
got, got1 := v.ToObjectValue(tt.args.in0)
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("ToObjectValue() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
if !reflect.DeepEqual(got1, tt.want1) {
|
||||
t.Errorf("ToObjectValue() got1 = %v, want %v", got1, tt.want1)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlavorValue_ToTerraformValue(t *testing.T) {
|
||||
type fields struct {
|
||||
Cpu basetypes.Int64Value
|
||||
Description basetypes.StringValue
|
||||
Id basetypes.StringValue
|
||||
Ram basetypes.Int64Value
|
||||
state attr.ValueState
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want tftypes.Value
|
||||
wantErr bool
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
v := FlavorValue{
|
||||
Cpu: tt.fields.Cpu,
|
||||
Description: tt.fields.Description,
|
||||
Id: tt.fields.Id,
|
||||
Ram: tt.fields.Ram,
|
||||
state: tt.fields.state,
|
||||
}
|
||||
got, err := v.ToTerraformValue(tt.args.ctx)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ToTerraformValue() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("ToTerraformValue() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlavorValue_Type(t *testing.T) {
|
||||
type fields struct {
|
||||
Cpu basetypes.Int64Value
|
||||
Description basetypes.StringValue
|
||||
Id basetypes.StringValue
|
||||
Ram basetypes.Int64Value
|
||||
state attr.ValueState
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want attr.Type
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
v := FlavorValue{
|
||||
Cpu: tt.fields.Cpu,
|
||||
Description: tt.fields.Description,
|
||||
Id: tt.fields.Id,
|
||||
Ram: tt.fields.Ram,
|
||||
state: tt.fields.state,
|
||||
}
|
||||
if got := v.Type(tt.args.ctx); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Type() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewFlavorValue(t *testing.T) {
|
||||
type args struct {
|
||||
attributeTypes map[string]attr.Type
|
||||
attributes map[string]attr.Value
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want FlavorValue
|
||||
want1 diag.Diagnostics
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, got1 := NewFlavorValue(tt.args.attributeTypes, tt.args.attributes)
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("NewFlavorValue() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
if !reflect.DeepEqual(got1, tt.want1) {
|
||||
t.Errorf("NewFlavorValue() got1 = %v, want %v", got1, tt.want1)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewFlavorValueMust(t *testing.T) {
|
||||
type args struct {
|
||||
attributeTypes map[string]attr.Type
|
||||
attributes map[string]attr.Value
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want FlavorValue
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := NewFlavorValueMust(tt.args.attributeTypes, tt.args.attributes); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("NewFlavorValueMust() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewFlavorValueNull(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
want FlavorValue
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := NewFlavorValueNull(); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("NewFlavorValueNull() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewFlavorValueUnknown(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
want FlavorValue
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := NewFlavorValueUnknown(); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("NewFlavorValueUnknown() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
package postgresflex
|
||||
|
|
@ -100,25 +100,11 @@ func (r *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaReques
|
|||
"backup_schedule": schema.StringAttribute{
|
||||
Computed: true,
|
||||
},
|
||||
"flavor": schema.SingleNestedAttribute{
|
||||
"retention_days": schema.Int64Attribute{
|
||||
Computed: true,
|
||||
},
|
||||
"flavor_id": schema.StringAttribute{
|
||||
Computed: true,
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Computed: true,
|
||||
},
|
||||
"description": schema.StringAttribute{
|
||||
Computed: true,
|
||||
},
|
||||
"cpu": schema.Int64Attribute{
|
||||
Computed: true,
|
||||
},
|
||||
"ram": schema.Int64Attribute{
|
||||
Computed: true,
|
||||
},
|
||||
"node_type": schema.StringAttribute{
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
"replicas": schema.Int64Attribute{
|
||||
Computed: true,
|
||||
|
|
@ -226,31 +212,6 @@ func (r *instanceDataSource) Read(ctx context.Context, req datasource.ReadReques
|
|||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
var flavor = &flavorModel{}
|
||||
if instanceResp != nil && instanceResp.FlavorId != nil {
|
||||
flavor.Id = types.StringValue(*instanceResp.FlavorId)
|
||||
}
|
||||
|
||||
if !model.Flavor.IsNull() && !model.Flavor.IsUnknown() {
|
||||
diags = model.Flavor.As(ctx, flavor, basetypes.ObjectAsOptions{})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
err := getFlavorModelById(ctx, r.client, &model, flavor)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError(err.Error(), err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
diags = model.Flavor.As(ctx, flavor, basetypes.ObjectAsOptions{})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var storage = &storageModel{}
|
||||
if !model.Storage.IsNull() && !model.Storage.IsUnknown() {
|
||||
diags = model.Storage.As(ctx, storage, basetypes.ObjectAsOptions{})
|
||||
|
|
@ -278,7 +239,7 @@ func (r *instanceDataSource) Read(ctx context.Context, req datasource.ReadReques
|
|||
}
|
||||
}
|
||||
|
||||
err = mapFields(ctx, instanceResp, &model, flavor, storage, encryption, network, region)
|
||||
err = mapFields(ctx, r.client, instanceResp, &model, storage, encryption, network, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/attr"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
|
|
@ -20,9 +19,9 @@ type postgresflexClient interface {
|
|||
|
||||
func mapFields(
|
||||
ctx context.Context,
|
||||
client postgresflexClient,
|
||||
resp *postgresflex.GetInstanceResponse,
|
||||
model *Model,
|
||||
flavor *flavorModel,
|
||||
storage *storageModel,
|
||||
encryption *encryptionModel,
|
||||
network *networkModel,
|
||||
|
|
@ -80,19 +79,11 @@ func mapFields(
|
|||
return fmt.Errorf("creating network (acl list): %w", core.DiagsToError(diags))
|
||||
}
|
||||
|
||||
var routerAddress string
|
||||
if instance.Network.RouterAddress != nil {
|
||||
routerAddress = *instance.Network.RouterAddress
|
||||
diags.AddWarning("field missing while mapping fields", "router_address was empty in API response")
|
||||
}
|
||||
if instance.Network.InstanceAddress == nil {
|
||||
return fmt.Errorf("creating network: no instance address returned")
|
||||
}
|
||||
networkValues = map[string]attr.Value{
|
||||
"acl": aclList,
|
||||
"access_scope": types.StringValue(string(*instance.Network.AccessScope)),
|
||||
"instance_address": types.StringValue(*instance.Network.InstanceAddress),
|
||||
"router_address": types.StringValue(routerAddress),
|
||||
"access_scope": types.StringPointerValue((*string)(instance.Network.AccessScope)),
|
||||
"instance_address": types.StringPointerValue(instance.Network.InstanceAddress),
|
||||
"router_address": types.StringPointerValue(instance.Network.RouterAddress),
|
||||
}
|
||||
}
|
||||
networkObject, diags := types.ObjectValue(networkTypes, networkValues)
|
||||
|
|
@ -100,48 +91,6 @@ func mapFields(
|
|||
return fmt.Errorf("creating network: %w", core.DiagsToError(diags))
|
||||
}
|
||||
|
||||
var flavorValues map[string]attr.Value
|
||||
if instance.FlavorId == nil || *instance.FlavorId == "" {
|
||||
return fmt.Errorf("instance has no flavor id")
|
||||
}
|
||||
if !flavor.Id.IsUnknown() && !flavor.Id.IsNull() {
|
||||
if *instance.FlavorId != flavor.Id.ValueString() {
|
||||
return fmt.Errorf("instance has different flavor id %s - %s", *instance.FlavorId, flavor.Id.ValueString())
|
||||
}
|
||||
}
|
||||
if model.Flavor.IsNull() || model.Flavor.IsUnknown() {
|
||||
var nodeType string
|
||||
if flavor.NodeType.IsUnknown() || flavor.NodeType.IsNull() {
|
||||
if instance.Replicas == nil {
|
||||
return fmt.Errorf("instance has no replicas setting")
|
||||
}
|
||||
switch *instance.Replicas {
|
||||
case 1:
|
||||
nodeType = "Single"
|
||||
case 3:
|
||||
nodeType = "Replicas"
|
||||
default:
|
||||
return fmt.Errorf("could not determine replicas settings")
|
||||
}
|
||||
} else {
|
||||
nodeType = flavor.NodeType.ValueString()
|
||||
}
|
||||
flavorValues = map[string]attr.Value{
|
||||
"id": flavor.Id,
|
||||
"description": flavor.Description,
|
||||
"cpu": flavor.CPU,
|
||||
"ram": flavor.RAM,
|
||||
"node_type": types.StringValue(nodeType),
|
||||
}
|
||||
} else {
|
||||
flavorValues = model.Flavor.Attributes()
|
||||
}
|
||||
|
||||
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{
|
||||
|
|
@ -167,10 +116,8 @@ func mapFields(
|
|||
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, instanceId)
|
||||
model.InstanceId = types.StringValue(instanceId)
|
||||
model.Name = types.StringPointerValue(instance.Name)
|
||||
model.Network = networkObject
|
||||
model.BackupSchedule = types.StringPointerValue(instance.BackupSchedule)
|
||||
model.Flavor = flavorObject
|
||||
// TODO - verify working
|
||||
model.FlavorId = types.StringPointerValue(instance.FlavorId)
|
||||
model.Replicas = types.Int64Value(int64(*instance.Replicas))
|
||||
model.Storage = storageObject
|
||||
model.Version = types.StringPointerValue(instance.Version)
|
||||
|
|
@ -182,7 +129,6 @@ func mapFields(
|
|||
|
||||
func toCreatePayload(
|
||||
model *Model,
|
||||
flavor *flavorModel,
|
||||
storage *storageModel,
|
||||
enc *encryptionModel,
|
||||
net *networkModel,
|
||||
|
|
@ -190,9 +136,6 @@ func toCreatePayload(
|
|||
if model == nil {
|
||||
return nil, fmt.Errorf("nil model")
|
||||
}
|
||||
if flavor == nil {
|
||||
return nil, fmt.Errorf("nil flavor")
|
||||
}
|
||||
if storage == nil {
|
||||
return nil, fmt.Errorf("nil storage")
|
||||
}
|
||||
|
|
@ -239,24 +182,11 @@ func toCreatePayload(
|
|||
}
|
||||
}
|
||||
|
||||
if model.Replicas.IsNull() || model.Replicas.IsUnknown() {
|
||||
if !flavor.NodeType.IsNull() && !flavor.NodeType.IsUnknown() {
|
||||
switch strings.ToLower(flavor.NodeType.ValueString()) {
|
||||
case "single":
|
||||
replVal = int32(1)
|
||||
case "replica":
|
||||
replVal = int32(3)
|
||||
default:
|
||||
return nil, fmt.Errorf("flavor has invalid replica attribute")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &postgresflex.CreateInstanceRequestPayload{
|
||||
Acl: &aclElements,
|
||||
BackupSchedule: conversion.StringValueToPointer(model.BackupSchedule),
|
||||
Encryption: encryptionPayload,
|
||||
FlavorId: conversion.StringValueToPointer(flavor.Id),
|
||||
FlavorId: conversion.StringValueToPointer(model.FlavorId),
|
||||
Name: conversion.StringValueToPointer(model.Name),
|
||||
Network: networkPayload,
|
||||
Replicas: postgresflex.CreateInstanceRequestPayloadGetReplicasAttributeType(&replVal),
|
||||
|
|
@ -266,13 +196,14 @@ func toCreatePayload(
|
|||
}, nil
|
||||
}
|
||||
|
||||
func toUpdatePayload(model *Model, flavor *flavorModel, storage *storageModel, _ *networkModel) (*postgresflex.UpdateInstancePartiallyRequestPayload, error) {
|
||||
func toUpdatePayload(
|
||||
model *Model,
|
||||
storage *storageModel,
|
||||
_ *networkModel,
|
||||
) (*postgresflex.UpdateInstancePartiallyRequestPayload, error) {
|
||||
if model == nil {
|
||||
return nil, fmt.Errorf("nil model")
|
||||
}
|
||||
if flavor == nil {
|
||||
return nil, fmt.Errorf("nil flavor")
|
||||
}
|
||||
if storage == nil {
|
||||
return nil, fmt.Errorf("nil storage")
|
||||
}
|
||||
|
|
@ -282,7 +213,7 @@ func toUpdatePayload(model *Model, flavor *flavorModel, storage *storageModel, _
|
|||
// Items: &acl,
|
||||
// },
|
||||
BackupSchedule: conversion.StringValueToPointer(model.BackupSchedule),
|
||||
FlavorId: conversion.StringValueToPointer(flavor.Id),
|
||||
FlavorId: conversion.StringValueToPointer(model.FlavorId),
|
||||
Name: conversion.StringValueToPointer(model.Name),
|
||||
// Replicas: conversion.Int64ValueToPointer(model.Replicas),
|
||||
Storage: &postgresflex.StorageUpdate{
|
||||
|
|
@ -291,187 +222,3 @@ func toUpdatePayload(model *Model, flavor *flavorModel, storage *storageModel, _
|
|||
Version: conversion.StringValueToPointer(model.Version),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func loadFlavorId(ctx context.Context, client postgresflexClient, model *Model, flavor *flavorModel, storage *storageModel) error {
|
||||
if model == nil {
|
||||
return fmt.Errorf("nil model")
|
||||
}
|
||||
if flavor == nil {
|
||||
return fmt.Errorf("nil flavor")
|
||||
}
|
||||
cpu := flavor.CPU.ValueInt64()
|
||||
if cpu == 0 {
|
||||
return fmt.Errorf("nil CPU")
|
||||
}
|
||||
ram := flavor.RAM.ValueInt64()
|
||||
if ram == 0 {
|
||||
return fmt.Errorf("nil RAM")
|
||||
}
|
||||
|
||||
nodeType := flavor.NodeType.ValueString()
|
||||
if nodeType == "" {
|
||||
if model.Replicas.IsNull() || model.Replicas.IsUnknown() {
|
||||
return fmt.Errorf("nil NodeType")
|
||||
}
|
||||
switch model.Replicas.ValueInt64() {
|
||||
case 1:
|
||||
nodeType = "Single"
|
||||
case 3:
|
||||
nodeType = "Replica"
|
||||
default:
|
||||
return fmt.Errorf("unknown Replicas value: %d", model.Replicas.ValueInt64())
|
||||
}
|
||||
}
|
||||
|
||||
storageClass := conversion.StringValueToPointer(storage.Class)
|
||||
if storageClass == nil {
|
||||
return fmt.Errorf("nil StorageClass")
|
||||
}
|
||||
storageSize := conversion.Int64ValueToPointer(storage.Size)
|
||||
if storageSize == nil {
|
||||
return fmt.Errorf("nil StorageSize")
|
||||
}
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := model.Region.ValueString()
|
||||
|
||||
flavorList, err := getAllFlavors(ctx, client, projectId, region)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
avl := ""
|
||||
foundFlavorCount := 0
|
||||
var foundFlavors []string
|
||||
for _, f := range flavorList {
|
||||
if f.Id == nil || f.Cpu == nil || f.Memory == nil {
|
||||
continue
|
||||
}
|
||||
if !strings.EqualFold(*f.NodeType, nodeType) {
|
||||
continue
|
||||
}
|
||||
if *f.Cpu == cpu && *f.Memory == ram {
|
||||
var useSc *postgresflex.FlavorStorageClassesStorageClass
|
||||
for _, sc := range *f.StorageClasses {
|
||||
if *sc.Class != *storageClass {
|
||||
continue
|
||||
}
|
||||
if *storageSize < *f.MinGB || *storageSize > *f.MaxGB {
|
||||
return fmt.Errorf("storage size %d out of bounds (min: %d - max: %d)", *storageSize, *f.MinGB, *f.MaxGB)
|
||||
}
|
||||
useSc = &sc
|
||||
}
|
||||
if useSc == nil {
|
||||
return fmt.Errorf("no storage class found for %s", *storageClass)
|
||||
}
|
||||
|
||||
flavor.Id = types.StringValue(*f.Id)
|
||||
flavor.Description = types.StringValue(*f.Description)
|
||||
foundFlavors = append(foundFlavors, fmt.Sprintf("%s (%d/%d - %s)", *f.Id, *f.Cpu, *f.Memory, *f.NodeType))
|
||||
foundFlavorCount++
|
||||
}
|
||||
for _, cls := range *f.StorageClasses {
|
||||
avl = fmt.Sprintf("%s\n- %d CPU, %d GB RAM, storage %s (min: %d - max: %d)", avl, *f.Cpu, *f.Memory, *cls.Class, *f.MinGB, *f.MaxGB)
|
||||
}
|
||||
}
|
||||
if foundFlavorCount > 1 {
|
||||
return fmt.Errorf(
|
||||
"number of flavors returned: %d\nmultiple flavors found: %d flavors\n %s",
|
||||
len(flavorList),
|
||||
foundFlavorCount,
|
||||
strings.Join(foundFlavors, "\n "),
|
||||
)
|
||||
}
|
||||
if flavor.Id.ValueString() == "" {
|
||||
return fmt.Errorf("couldn't find flavor, available specs are:%s", avl)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getAllFlavors(ctx context.Context, client postgresflexClient, projectId, region string) ([]postgresflex.ListFlavors, error) {
|
||||
if projectId == "" || region == "" {
|
||||
return nil, fmt.Errorf("listing postgresflex flavors: projectId and region are required")
|
||||
}
|
||||
var flavorList []postgresflex.ListFlavors
|
||||
|
||||
page := int64(1)
|
||||
size := int64(10)
|
||||
sort := postgresflex.FLAVORSORT_INDEX_ASC
|
||||
counter := 0
|
||||
for {
|
||||
res, err := client.GetFlavorsRequestExecute(ctx, projectId, region, &page, &size, &sort)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("listing postgresflex flavors: %w", err)
|
||||
}
|
||||
if res.Flavors == nil {
|
||||
return nil, fmt.Errorf("finding flavors for project %s", projectId)
|
||||
}
|
||||
pagination := res.GetPagination()
|
||||
flavors := res.GetFlavors()
|
||||
flavorList = append(flavorList, flavors...)
|
||||
|
||||
if *pagination.TotalRows < int64(len(flavorList)) {
|
||||
return nil, fmt.Errorf("total rows is smaller than current accumulated list - that should not happen")
|
||||
}
|
||||
if *pagination.TotalRows == int64(len(flavorList)) {
|
||||
break
|
||||
}
|
||||
page++
|
||||
|
||||
if page > *pagination.TotalPages {
|
||||
break
|
||||
}
|
||||
|
||||
// implement a breakpoint
|
||||
counter++
|
||||
if counter > 1000 {
|
||||
panic("too many pagination results")
|
||||
}
|
||||
}
|
||||
return flavorList, nil
|
||||
}
|
||||
|
||||
func getFlavorModelById(ctx context.Context, client postgresflexClient, model *Model, flavor *flavorModel) error {
|
||||
if model == nil {
|
||||
return fmt.Errorf("nil model")
|
||||
}
|
||||
if flavor == nil {
|
||||
return fmt.Errorf("nil flavor")
|
||||
}
|
||||
id := conversion.StringValueToPointer(flavor.Id)
|
||||
if id == nil {
|
||||
return fmt.Errorf("nil flavor ID")
|
||||
}
|
||||
|
||||
flavor.Id = types.StringValue("")
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := model.Region.ValueString()
|
||||
|
||||
flavorList, err := getAllFlavors(ctx, client, projectId, region)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
avl := ""
|
||||
for _, f := range flavorList {
|
||||
if f.Id == nil || f.Cpu == nil || f.Memory == nil {
|
||||
continue
|
||||
}
|
||||
if *f.Id == *id {
|
||||
flavor.Id = types.StringValue(*f.Id)
|
||||
flavor.Description = types.StringValue(*f.Description)
|
||||
flavor.CPU = types.Int64Value(*f.Cpu)
|
||||
flavor.RAM = types.Int64Value(*f.Memory)
|
||||
flavor.NodeType = types.StringValue(*f.NodeType)
|
||||
break
|
||||
}
|
||||
avl = fmt.Sprintf("%s\n- %d CPU, %d GB RAM", avl, *f.Cpu, *f.Memory)
|
||||
}
|
||||
if flavor.Id.ValueString() == "" {
|
||||
return fmt.Errorf("couldn't find flavor, available specs are: %s", avl)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,11 +3,7 @@ package postgresflexalpha
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
|
||||
postgresflex "github.com/mhenselin/terraform-provider-stackitprivatepreview/pkg/postgresflexalpha"
|
||||
"github.com/stackitcloud/stackit-sdk-go/core/utils"
|
||||
)
|
||||
|
|
@ -490,327 +486,303 @@ func (c postgresFlexClientMocked) GetFlavorsRequestExecute(
|
|||
return &res, nil
|
||||
}
|
||||
|
||||
func Test_getAllFlavors(t *testing.T) {
|
||||
type args struct {
|
||||
projectId string
|
||||
region string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
firstItem int
|
||||
lastItem int
|
||||
want []postgresflex.ListFlavors
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "find exactly one flavor",
|
||||
args: args{
|
||||
projectId: "project",
|
||||
region: "region",
|
||||
},
|
||||
firstItem: 0,
|
||||
lastItem: 0,
|
||||
want: []postgresflex.ListFlavors{
|
||||
testFlavorToResponseFlavor(responseList[0]),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "get exactly 1 page flavors",
|
||||
args: args{
|
||||
projectId: "project",
|
||||
region: "region",
|
||||
},
|
||||
firstItem: 0,
|
||||
lastItem: 9,
|
||||
want: testFlavorListToResponseFlavorList(responseList[0:10]),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "get exactly 20 flavors",
|
||||
args: args{
|
||||
projectId: "project",
|
||||
region: "region",
|
||||
},
|
||||
firstItem: 0,
|
||||
lastItem: 20,
|
||||
// 0 indexed therefore we want :21
|
||||
want: testFlavorListToResponseFlavorList(responseList[0:21]),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "get all flavors",
|
||||
args: args{
|
||||
projectId: "project",
|
||||
region: "region",
|
||||
},
|
||||
firstItem: 0,
|
||||
lastItem: len(responseList),
|
||||
want: testFlavorListToResponseFlavorList(responseList),
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
first := tt.firstItem
|
||||
if first > len(responseList)-1 {
|
||||
first = len(responseList) - 1
|
||||
}
|
||||
last := tt.lastItem
|
||||
if last > len(responseList)-1 {
|
||||
last = len(responseList) - 1
|
||||
}
|
||||
mockClient := postgresFlexClientMocked{
|
||||
returnError: tt.wantErr,
|
||||
firstItem: first,
|
||||
lastItem: last,
|
||||
}
|
||||
got, err := getAllFlavors(context.TODO(), mockClient, tt.args.projectId, tt.args.region)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("getAllFlavors() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
//func Test_getAllFlavors(t *testing.T) {
|
||||
// type args struct {
|
||||
// projectId string
|
||||
// region string
|
||||
// }
|
||||
// tests := []struct {
|
||||
// name string
|
||||
// args args
|
||||
// firstItem int
|
||||
// lastItem int
|
||||
// want []postgresflex.ListFlavors
|
||||
// wantErr bool
|
||||
// }{
|
||||
// {
|
||||
// name: "find exactly one flavor",
|
||||
// args: args{
|
||||
// projectId: "project",
|
||||
// region: "region",
|
||||
// },
|
||||
// firstItem: 0,
|
||||
// lastItem: 0,
|
||||
// want: []postgresflex.ListFlavors{
|
||||
// testFlavorToResponseFlavor(responseList[0]),
|
||||
// },
|
||||
// wantErr: false,
|
||||
// },
|
||||
// {
|
||||
// name: "get exactly 1 page flavors",
|
||||
// args: args{
|
||||
// projectId: "project",
|
||||
// region: "region",
|
||||
// },
|
||||
// firstItem: 0,
|
||||
// lastItem: 9,
|
||||
// want: testFlavorListToResponseFlavorList(responseList[0:10]),
|
||||
// wantErr: false,
|
||||
// },
|
||||
// {
|
||||
// name: "get exactly 20 flavors",
|
||||
// args: args{
|
||||
// projectId: "project",
|
||||
// region: "region",
|
||||
// },
|
||||
// firstItem: 0,
|
||||
// lastItem: 20,
|
||||
// // 0 indexed therefore we want :21
|
||||
// want: testFlavorListToResponseFlavorList(responseList[0:21]),
|
||||
// wantErr: false,
|
||||
// },
|
||||
// {
|
||||
// name: "get all flavors",
|
||||
// args: args{
|
||||
// projectId: "project",
|
||||
// region: "region",
|
||||
// },
|
||||
// firstItem: 0,
|
||||
// lastItem: len(responseList),
|
||||
// want: testFlavorListToResponseFlavorList(responseList),
|
||||
// wantErr: false,
|
||||
// },
|
||||
// }
|
||||
// for _, tt := range tests {
|
||||
// t.Run(tt.name, func(t *testing.T) {
|
||||
// first := tt.firstItem
|
||||
// if first > len(responseList)-1 {
|
||||
// first = len(responseList) - 1
|
||||
// }
|
||||
// last := tt.lastItem
|
||||
// if last > len(responseList)-1 {
|
||||
// last = len(responseList) - 1
|
||||
// }
|
||||
// mockClient := postgresFlexClientMocked{
|
||||
// returnError: tt.wantErr,
|
||||
// firstItem: first,
|
||||
// lastItem: last,
|
||||
// }
|
||||
// got, err := getAllFlavors(context.TODO(), mockClient, tt.args.projectId, tt.args.region)
|
||||
// if (err != nil) != tt.wantErr {
|
||||
// t.Errorf("getAllFlavors() error = %v, wantErr %v", err, tt.wantErr)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// if diff := cmp.Diff(tt.want, got); diff != "" {
|
||||
// t.Errorf("mismatch (-want +got):\n%s", diff)
|
||||
// }
|
||||
//
|
||||
// if !reflect.DeepEqual(got, tt.want) {
|
||||
// t.Errorf("getAllFlavors() got = %v, want %v", got, tt.want)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
//}
|
||||
|
||||
if diff := cmp.Diff(tt.want, got); diff != "" {
|
||||
t.Errorf("mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("getAllFlavors() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_loadFlavorId(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
model *Model
|
||||
flavor *flavorModel
|
||||
storage *storageModel
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
firstItem int
|
||||
lastItem int
|
||||
want []postgresflex.ListFlavors
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "find a single flavor",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
model: &Model{
|
||||
ProjectId: basetypes.NewStringValue("project"),
|
||||
Region: basetypes.NewStringValue("region"),
|
||||
},
|
||||
flavor: &flavorModel{
|
||||
CPU: basetypes.NewInt64Value(1),
|
||||
RAM: basetypes.NewInt64Value(1),
|
||||
NodeType: basetypes.NewStringValue("Single"),
|
||||
},
|
||||
storage: &storageModel{
|
||||
Class: basetypes.NewStringValue("sc1"),
|
||||
Size: basetypes.NewInt64Value(100),
|
||||
},
|
||||
},
|
||||
firstItem: 0,
|
||||
lastItem: 3,
|
||||
want: []postgresflex.ListFlavors{
|
||||
testFlavorToResponseFlavor(responseList[0]),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "find a single flavor by replicas option",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
model: &Model{
|
||||
ProjectId: basetypes.NewStringValue("project"),
|
||||
Region: basetypes.NewStringValue("region"),
|
||||
Replicas: basetypes.NewInt64Value(1),
|
||||
},
|
||||
flavor: &flavorModel{
|
||||
CPU: basetypes.NewInt64Value(1),
|
||||
RAM: basetypes.NewInt64Value(1),
|
||||
},
|
||||
storage: &storageModel{
|
||||
Class: basetypes.NewStringValue("sc1"),
|
||||
Size: basetypes.NewInt64Value(100),
|
||||
},
|
||||
},
|
||||
firstItem: 0,
|
||||
lastItem: 3,
|
||||
want: []postgresflex.ListFlavors{
|
||||
testFlavorToResponseFlavor(responseList[0]),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "fail finding find a single flavor by replicas option",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
model: &Model{
|
||||
ProjectId: basetypes.NewStringValue("project"),
|
||||
Region: basetypes.NewStringValue("region"),
|
||||
Replicas: basetypes.NewInt64Value(1),
|
||||
},
|
||||
flavor: &flavorModel{
|
||||
CPU: basetypes.NewInt64Value(1),
|
||||
RAM: basetypes.NewInt64Value(1),
|
||||
},
|
||||
storage: &storageModel{
|
||||
Class: basetypes.NewStringValue("sc1"),
|
||||
Size: basetypes.NewInt64Value(100),
|
||||
},
|
||||
},
|
||||
firstItem: 13,
|
||||
lastItem: 23,
|
||||
want: []postgresflex.ListFlavors{},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "find a replicas flavor lower case",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
model: &Model{
|
||||
ProjectId: basetypes.NewStringValue("project"),
|
||||
Region: basetypes.NewStringValue("region"),
|
||||
},
|
||||
flavor: &flavorModel{
|
||||
CPU: basetypes.NewInt64Value(1),
|
||||
RAM: basetypes.NewInt64Value(1),
|
||||
NodeType: basetypes.NewStringValue("replica"),
|
||||
},
|
||||
storage: &storageModel{
|
||||
Class: basetypes.NewStringValue("sc1"),
|
||||
Size: basetypes.NewInt64Value(100),
|
||||
},
|
||||
},
|
||||
firstItem: 0,
|
||||
lastItem: len(responseList) - 1,
|
||||
want: []postgresflex.ListFlavors{
|
||||
testFlavorToResponseFlavor(responseList[16]),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "find a replicas flavor CamelCase",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
model: &Model{
|
||||
ProjectId: basetypes.NewStringValue("project"),
|
||||
Region: basetypes.NewStringValue("region"),
|
||||
},
|
||||
flavor: &flavorModel{
|
||||
CPU: basetypes.NewInt64Value(1),
|
||||
RAM: basetypes.NewInt64Value(1),
|
||||
NodeType: basetypes.NewStringValue("Replica"),
|
||||
},
|
||||
storage: &storageModel{
|
||||
Class: basetypes.NewStringValue("sc1"),
|
||||
Size: basetypes.NewInt64Value(100),
|
||||
},
|
||||
},
|
||||
firstItem: 0,
|
||||
lastItem: len(responseList) - 1,
|
||||
want: []postgresflex.ListFlavors{
|
||||
testFlavorToResponseFlavor(responseList[16]),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "find a replicas flavor by replicas option",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
model: &Model{
|
||||
ProjectId: basetypes.NewStringValue("project"),
|
||||
Region: basetypes.NewStringValue("region"),
|
||||
Replicas: basetypes.NewInt64Value(3),
|
||||
},
|
||||
flavor: &flavorModel{
|
||||
CPU: basetypes.NewInt64Value(1),
|
||||
RAM: basetypes.NewInt64Value(1),
|
||||
},
|
||||
storage: &storageModel{
|
||||
Class: basetypes.NewStringValue("sc1"),
|
||||
Size: basetypes.NewInt64Value(100),
|
||||
},
|
||||
},
|
||||
firstItem: 0,
|
||||
lastItem: len(responseList) - 1,
|
||||
want: []postgresflex.ListFlavors{
|
||||
testFlavorToResponseFlavor(responseList[16]),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "fail finding a replica flavor",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
model: &Model{
|
||||
ProjectId: basetypes.NewStringValue("project"),
|
||||
Region: basetypes.NewStringValue("region"),
|
||||
Replicas: basetypes.NewInt64Value(3),
|
||||
},
|
||||
flavor: &flavorModel{
|
||||
CPU: basetypes.NewInt64Value(1),
|
||||
RAM: basetypes.NewInt64Value(1),
|
||||
},
|
||||
storage: &storageModel{
|
||||
Class: basetypes.NewStringValue("sc1"),
|
||||
Size: basetypes.NewInt64Value(100),
|
||||
},
|
||||
},
|
||||
firstItem: 0,
|
||||
lastItem: 10,
|
||||
want: []postgresflex.ListFlavors{},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "no flavor found error",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
model: &Model{
|
||||
ProjectId: basetypes.NewStringValue("project"),
|
||||
Region: basetypes.NewStringValue("region"),
|
||||
},
|
||||
flavor: &flavorModel{
|
||||
CPU: basetypes.NewInt64Value(10),
|
||||
RAM: basetypes.NewInt64Value(1000),
|
||||
NodeType: basetypes.NewStringValue("Single"),
|
||||
},
|
||||
storage: &storageModel{
|
||||
Class: basetypes.NewStringValue("sc1"),
|
||||
Size: basetypes.NewInt64Value(100),
|
||||
},
|
||||
},
|
||||
firstItem: 0,
|
||||
lastItem: 3,
|
||||
want: []postgresflex.ListFlavors{},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
first := tt.firstItem
|
||||
if first > len(responseList)-1 {
|
||||
first = len(responseList) - 1
|
||||
}
|
||||
last := tt.lastItem
|
||||
if last > len(responseList)-1 {
|
||||
last = len(responseList) - 1
|
||||
}
|
||||
mockClient := postgresFlexClientMocked{
|
||||
returnError: tt.wantErr,
|
||||
firstItem: first,
|
||||
lastItem: last,
|
||||
}
|
||||
if err := loadFlavorId(tt.args.ctx, mockClient, tt.args.model, tt.args.flavor, tt.args.storage); (err != nil) != tt.wantErr {
|
||||
t.Errorf("loadFlavorId() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
//func Test_loadFlavorId(t *testing.T) {
|
||||
// type args struct {
|
||||
// ctx context.Context
|
||||
// model *Model
|
||||
// storage *storageModel
|
||||
// }
|
||||
// tests := []struct {
|
||||
// name string
|
||||
// args args
|
||||
// firstItem int
|
||||
// lastItem int
|
||||
// want []postgresflex.ListFlavors
|
||||
// wantErr bool
|
||||
// }{
|
||||
// {
|
||||
// name: "find a single flavor",
|
||||
// args: args{
|
||||
// ctx: context.Background(),
|
||||
// model: &Model{
|
||||
// ProjectId: basetypes.NewStringValue("project"),
|
||||
// Region: basetypes.NewStringValue("region"),
|
||||
// },
|
||||
// storage: &storageModel{
|
||||
// Class: basetypes.NewStringValue("sc1"),
|
||||
// Size: basetypes.NewInt64Value(100),
|
||||
// },
|
||||
// },
|
||||
// firstItem: 0,
|
||||
// lastItem: 3,
|
||||
// want: []postgresflex.ListFlavors{
|
||||
// testFlavorToResponseFlavor(responseList[0]),
|
||||
// },
|
||||
// wantErr: false,
|
||||
// },
|
||||
// {
|
||||
// name: "find a single flavor by replicas option",
|
||||
// args: args{
|
||||
// ctx: context.Background(),
|
||||
// model: &Model{
|
||||
// ProjectId: basetypes.NewStringValue("project"),
|
||||
// Region: basetypes.NewStringValue("region"),
|
||||
// Replicas: basetypes.NewInt64Value(1),
|
||||
// },
|
||||
// storage: &storageModel{
|
||||
// Class: basetypes.NewStringValue("sc1"),
|
||||
// Size: basetypes.NewInt64Value(100),
|
||||
// },
|
||||
// },
|
||||
// firstItem: 0,
|
||||
// lastItem: 3,
|
||||
// want: []postgresflex.ListFlavors{
|
||||
// testFlavorToResponseFlavor(responseList[0]),
|
||||
// },
|
||||
// wantErr: false,
|
||||
// },
|
||||
// {
|
||||
// name: "fail finding find a single flavor by replicas option",
|
||||
// args: args{
|
||||
// ctx: context.Background(),
|
||||
// model: &Model{
|
||||
// ProjectId: basetypes.NewStringValue("project"),
|
||||
// Region: basetypes.NewStringValue("region"),
|
||||
// Replicas: basetypes.NewInt64Value(1),
|
||||
// },
|
||||
// storage: &storageModel{
|
||||
// Class: basetypes.NewStringValue("sc1"),
|
||||
// Size: basetypes.NewInt64Value(100),
|
||||
// },
|
||||
// },
|
||||
// firstItem: 13,
|
||||
// lastItem: 23,
|
||||
// want: []postgresflex.ListFlavors{},
|
||||
// wantErr: true,
|
||||
// },
|
||||
// {
|
||||
// name: "find a replicas flavor lower case",
|
||||
// args: args{
|
||||
// ctx: context.Background(),
|
||||
// model: &Model{
|
||||
// ProjectId: basetypes.NewStringValue("project"),
|
||||
// Region: basetypes.NewStringValue("region"),
|
||||
// },
|
||||
// storage: &storageModel{
|
||||
// Class: basetypes.NewStringValue("sc1"),
|
||||
// Size: basetypes.NewInt64Value(100),
|
||||
// },
|
||||
// },
|
||||
// firstItem: 0,
|
||||
// lastItem: len(responseList) - 1,
|
||||
// want: []postgresflex.ListFlavors{
|
||||
// testFlavorToResponseFlavor(responseList[16]),
|
||||
// },
|
||||
// wantErr: false,
|
||||
// },
|
||||
// {
|
||||
// name: "find a replicas flavor CamelCase",
|
||||
// args: args{
|
||||
// ctx: context.Background(),
|
||||
// model: &Model{
|
||||
// ProjectId: basetypes.NewStringValue("project"),
|
||||
// Region: basetypes.NewStringValue("region"),
|
||||
// },
|
||||
// storage: &storageModel{
|
||||
// Class: basetypes.NewStringValue("sc1"),
|
||||
// Size: basetypes.NewInt64Value(100),
|
||||
// },
|
||||
// },
|
||||
// firstItem: 0,
|
||||
// lastItem: len(responseList) - 1,
|
||||
// want: []postgresflex.ListFlavors{
|
||||
// testFlavorToResponseFlavor(responseList[16]),
|
||||
// },
|
||||
// wantErr: false,
|
||||
// },
|
||||
// {
|
||||
// name: "find a replicas flavor by replicas option",
|
||||
// args: args{
|
||||
// ctx: context.Background(),
|
||||
// model: &Model{
|
||||
// ProjectId: basetypes.NewStringValue("project"),
|
||||
// Region: basetypes.NewStringValue("region"),
|
||||
// Replicas: basetypes.NewInt64Value(3),
|
||||
// },
|
||||
// flavor: &flavorModel{
|
||||
// CPU: basetypes.NewInt64Value(1),
|
||||
// RAM: basetypes.NewInt64Value(1),
|
||||
// },
|
||||
// storage: &storageModel{
|
||||
// Class: basetypes.NewStringValue("sc1"),
|
||||
// Size: basetypes.NewInt64Value(100),
|
||||
// },
|
||||
// },
|
||||
// firstItem: 0,
|
||||
// lastItem: len(responseList) - 1,
|
||||
// want: []postgresflex.ListFlavors{
|
||||
// testFlavorToResponseFlavor(responseList[16]),
|
||||
// },
|
||||
// wantErr: false,
|
||||
// },
|
||||
// {
|
||||
// name: "fail finding a replica flavor",
|
||||
// args: args{
|
||||
// ctx: context.Background(),
|
||||
// model: &Model{
|
||||
// ProjectId: basetypes.NewStringValue("project"),
|
||||
// Region: basetypes.NewStringValue("region"),
|
||||
// Replicas: basetypes.NewInt64Value(3),
|
||||
// },
|
||||
// flavor: &flavorModel{
|
||||
// CPU: basetypes.NewInt64Value(1),
|
||||
// RAM: basetypes.NewInt64Value(1),
|
||||
// },
|
||||
// storage: &storageModel{
|
||||
// Class: basetypes.NewStringValue("sc1"),
|
||||
// Size: basetypes.NewInt64Value(100),
|
||||
// },
|
||||
// },
|
||||
// firstItem: 0,
|
||||
// lastItem: 10,
|
||||
// want: []postgresflex.ListFlavors{},
|
||||
// wantErr: true,
|
||||
// },
|
||||
// {
|
||||
// name: "no flavor found error",
|
||||
// args: args{
|
||||
// ctx: context.Background(),
|
||||
// model: &Model{
|
||||
// ProjectId: basetypes.NewStringValue("project"),
|
||||
// Region: basetypes.NewStringValue("region"),
|
||||
// },
|
||||
// flavor: &flavorModel{
|
||||
// CPU: basetypes.NewInt64Value(10),
|
||||
// RAM: basetypes.NewInt64Value(1000),
|
||||
// NodeType: basetypes.NewStringValue("Single"),
|
||||
// },
|
||||
// storage: &storageModel{
|
||||
// Class: basetypes.NewStringValue("sc1"),
|
||||
// Size: basetypes.NewInt64Value(100),
|
||||
// },
|
||||
// },
|
||||
// firstItem: 0,
|
||||
// lastItem: 3,
|
||||
// want: []postgresflex.ListFlavors{},
|
||||
// wantErr: true,
|
||||
// },
|
||||
// }
|
||||
// for _, tt := range tests {
|
||||
// t.Run(tt.name, func(t *testing.T) {
|
||||
// first := tt.firstItem
|
||||
// if first > len(responseList)-1 {
|
||||
// first = len(responseList) - 1
|
||||
// }
|
||||
// last := tt.lastItem
|
||||
// if last > len(responseList)-1 {
|
||||
// last = len(responseList) - 1
|
||||
// }
|
||||
// mockClient := postgresFlexClientMocked{
|
||||
// returnError: tt.wantErr,
|
||||
// firstItem: first,
|
||||
// lastItem: last,
|
||||
// }
|
||||
// if err := loadFlavorId(tt.args.ctx, mockClient, tt.args.model, tt.args.flavor, tt.args.storage); (err != nil) != tt.wantErr {
|
||||
// t.Errorf("loadFlavorId() error = %v, wantErr %v", err, tt.wantErr)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ type Model struct {
|
|||
ProjectId types.String `tfsdk:"project_id"`
|
||||
Name types.String `tfsdk:"name"`
|
||||
BackupSchedule types.String `tfsdk:"backup_schedule"`
|
||||
Flavor types.Object `tfsdk:"flavor"`
|
||||
FlavorId types.String `tfsdk:"flavor_id"`
|
||||
Replicas types.Int64 `tfsdk:"replicas"`
|
||||
RetentionDays types.Int64 `tfsdk:"retention_days"`
|
||||
Storage types.Object `tfsdk:"storage"`
|
||||
|
|
@ -22,9 +22,11 @@ type Model struct {
|
|||
Network types.Object `tfsdk:"network"`
|
||||
}
|
||||
|
||||
type IdentityModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
}
|
||||
//type IdentityModel struct {
|
||||
// InstanceId types.String `tfsdk:"instance_id"`
|
||||
// Region types.String `tfsdk:"region"`
|
||||
// ProjectId types.String `tfsdk:"project_id"`
|
||||
//}
|
||||
|
||||
type encryptionModel struct {
|
||||
KeyRingId types.String `tfsdk:"keyring_id"`
|
||||
|
|
@ -54,24 +56,6 @@ var networkTypes = map[string]attr.Type{
|
|||
"router_address": basetypes.StringType{},
|
||||
}
|
||||
|
||||
// Struct corresponding to Model.Flavor
|
||||
type flavorModel struct {
|
||||
Id types.String `tfsdk:"id"`
|
||||
Description types.String `tfsdk:"description"`
|
||||
CPU types.Int64 `tfsdk:"cpu"`
|
||||
RAM types.Int64 `tfsdk:"ram"`
|
||||
NodeType types.String `tfsdk:"node_type"`
|
||||
}
|
||||
|
||||
// Types corresponding to flavorModel
|
||||
var flavorTypes = map[string]attr.Type{
|
||||
"id": basetypes.StringType{},
|
||||
"description": basetypes.StringType{},
|
||||
"cpu": basetypes.Int64Type{},
|
||||
"ram": basetypes.Int64Type{},
|
||||
"node_type": basetypes.StringType{},
|
||||
}
|
||||
|
||||
// Struct corresponding to Model.Storage
|
||||
type storageModel struct {
|
||||
Class types.String `tfsdk:"class"`
|
||||
|
|
|
|||
|
|
@ -6,16 +6,13 @@ import (
|
|||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/identityschema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
|
||||
postgresflex "github.com/mhenselin/terraform-provider-stackitprivatepreview/pkg/postgresflexalpha"
|
||||
"github.com/mhenselin/terraform-provider-stackitprivatepreview/pkg/postgresflexalpha/wait"
|
||||
postgresflexUtils "github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/utils"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/attr"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
||||
|
|
@ -42,7 +39,7 @@ var (
|
|||
_ resource.ResourceWithImportState = &instanceResource{}
|
||||
_ resource.ResourceWithModifyPlan = &instanceResource{}
|
||||
_ resource.ResourceWithValidateConfig = &instanceResource{}
|
||||
_ resource.ResourceWithIdentity = &instanceResource{}
|
||||
//_ resource.ResourceWithIdentity = &instanceResource{}
|
||||
)
|
||||
|
||||
// NewInstanceResource is a helper function to simplify the provider implementation.
|
||||
|
|
@ -128,35 +125,30 @@ func (r *instanceResource) Configure(ctx context.Context, req resource.Configure
|
|||
// Schema defines the schema for the resource.
|
||||
func (r *instanceResource) Schema(_ context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
descriptions := map[string]string{
|
||||
"main": "Postgres Flex instance resource schema. Must have a `region` specified in the provider configuration.",
|
||||
"id": "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`instance_id`\".",
|
||||
"instance_id": "ID of the PostgresFlex instance.",
|
||||
"project_id": "STACKIT project ID to which the instance is associated.",
|
||||
"name": "Instance name.",
|
||||
"backup_schedule": "The schedule for on what time and how often the database backup will be created. The schedule is written as a cron schedule.",
|
||||
"retention_days": "The days of the retention period.",
|
||||
"flavor": "The block that defines the flavor data.",
|
||||
"flavor_id": "The ID of the flavor.",
|
||||
"flavor_description": "The flavor detailed flavor name.",
|
||||
"flavor_cpu": "The CPU count of the flavor.",
|
||||
"flavor_ram": "The RAM count of the flavor.",
|
||||
"flavor_node_type": "The node type of the flavor. (Single or Replicas)",
|
||||
"replicas": "The number of replicas.",
|
||||
"storage": "The block of the storage configuration.",
|
||||
"storage_class": "The storage class used.",
|
||||
"storage_size": "The disk size of the storage.",
|
||||
"region": "The resource region. If not defined, the provider region is used.",
|
||||
"version": "The database version used.",
|
||||
"encryption": "The encryption block.",
|
||||
"keyring_id": "KeyRing ID of the encryption key.",
|
||||
"key_id": "Key ID of the encryption key.",
|
||||
"key_version": "Key version of the encryption key.",
|
||||
"service_account": "The service account ID of the service account.",
|
||||
"network": "The network block configuration.",
|
||||
"access_scope": "The access scope. (Either SNA or PUBLIC)",
|
||||
"acl": "The Access Control List (ACL) for the PostgresFlex instance.",
|
||||
"instance_address": "The returned instance address.",
|
||||
"router_address": "The returned router address.",
|
||||
"main": "Postgres Flex instance resource schema. Must have a `region` specified in the provider configuration.",
|
||||
"id": "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`instance_id`\".",
|
||||
"instance_id": "ID of the PostgresFlex instance.",
|
||||
"project_id": "STACKIT project ID to which the instance is associated.",
|
||||
"name": "Instance name.",
|
||||
"backup_schedule": "The schedule for on what time and how often the database backup will be created. The schedule is written as a cron schedule.",
|
||||
"retention_days": "The days of the retention period.",
|
||||
"flavor_id": "The ID of the flavor.",
|
||||
"replicas": "The number of replicas.",
|
||||
"storage": "The block of the storage configuration.",
|
||||
"storage_class": "The storage class used.",
|
||||
"storage_size": "The disk size of the storage.",
|
||||
"region": "The resource region. If not defined, the provider region is used.",
|
||||
"version": "The database version used.",
|
||||
"encryption": "The encryption block.",
|
||||
"keyring_id": "KeyRing ID of the encryption key.",
|
||||
"key_id": "Key ID of the encryption key.",
|
||||
"key_version": "Key version of the encryption key.",
|
||||
"service_account": "The service account ID of the service account.",
|
||||
"network": "The network block configuration.",
|
||||
"access_scope": "The access scope. (Either SNA or PUBLIC)",
|
||||
"acl": "The Access Control List (ACL) for the PostgresFlex instance.",
|
||||
"instance_address": "The returned instance address.",
|
||||
"router_address": "The returned router address.",
|
||||
}
|
||||
|
||||
resp.Schema = schema.Schema{
|
||||
|
|
@ -210,45 +202,8 @@ func (r *instanceResource) Schema(_ context.Context, req resource.SchemaRequest,
|
|||
Description: descriptions["retention_days"],
|
||||
Required: true,
|
||||
},
|
||||
"flavor": schema.SingleNestedAttribute{
|
||||
Required: true,
|
||||
Description: descriptions["flavor"],
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Description: descriptions["flavor_id"],
|
||||
Computed: true,
|
||||
Optional: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
UseStateForUnknownIfFlavorUnchanged(req),
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
},
|
||||
"description": schema.StringAttribute{
|
||||
Computed: true,
|
||||
Description: descriptions["flavor_description"],
|
||||
PlanModifiers: []planmodifier.String{
|
||||
UseStateForUnknownIfFlavorUnchanged(req),
|
||||
},
|
||||
},
|
||||
"cpu": schema.Int64Attribute{
|
||||
Description: descriptions["flavor_cpu"],
|
||||
Required: true,
|
||||
},
|
||||
"ram": schema.Int64Attribute{
|
||||
Description: descriptions["flavor_ram"],
|
||||
Required: true,
|
||||
},
|
||||
"node_type": schema.StringAttribute{
|
||||
Description: descriptions["flavor_node_type"],
|
||||
Computed: true,
|
||||
Optional: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
// TODO @mhenselin anschauen
|
||||
UseStateForUnknownIfFlavorUnchanged(req),
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
},
|
||||
},
|
||||
"flavor_id": schema.StringAttribute{
|
||||
Required: true,
|
||||
},
|
||||
"replicas": schema.Int64Attribute{
|
||||
Required: true,
|
||||
|
|
@ -390,15 +345,21 @@ func (r *instanceResource) Schema(_ context.Context, req resource.SchemaRequest,
|
|||
}
|
||||
}
|
||||
|
||||
func (r *instanceResource) IdentitySchema(_ context.Context, _ resource.IdentitySchemaRequest, resp *resource.IdentitySchemaResponse) {
|
||||
resp.IdentitySchema = identityschema.Schema{
|
||||
Attributes: map[string]identityschema.Attribute{
|
||||
"id": identityschema.StringAttribute{
|
||||
RequiredForImport: true, // must be set during import by the practitioner
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
//func (r *instanceResource) IdentitySchema(_ context.Context, _ resource.IdentitySchemaRequest, resp *resource.IdentitySchemaResponse) {
|
||||
// resp.IdentitySchema = identityschema.Schema{
|
||||
// Attributes: map[string]identityschema.Attribute{
|
||||
// "project_id": identityschema.StringAttribute{
|
||||
// RequiredForImport: true, // must be set during import by the practitioner
|
||||
// },
|
||||
// "region": identityschema.StringAttribute{
|
||||
// RequiredForImport: true, // must be set during import by the practitioner
|
||||
// },
|
||||
// "instance_id": identityschema.StringAttribute{
|
||||
// RequiredForImport: true, // must be set during import by the practitioner
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
//}
|
||||
|
||||
// Create creates the resource and sets the initial Terraform state.
|
||||
func (r *instanceResource) Create(
|
||||
|
|
@ -406,7 +367,6 @@ func (r *instanceResource) Create(
|
|||
req resource.CreateRequest,
|
||||
resp *resource.CreateResponse,
|
||||
) { // nolint:gocritic // function signature required by Terraform
|
||||
// Retrieve values from plan
|
||||
var model Model
|
||||
diags := req.Plan.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
|
|
@ -430,42 +390,6 @@ func (r *instanceResource) Create(
|
|||
}
|
||||
}
|
||||
|
||||
var flavor = &flavorModel{}
|
||||
if !model.Flavor.IsNull() && !model.Flavor.IsUnknown() {
|
||||
diags = model.Flavor.As(ctx, flavor, basetypes.ObjectAsOptions{})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
err := loadFlavorId(ctx, r.client, &model, flavor, storage)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Loading flavor ID: %v", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if flavor.Id.IsNull() || flavor.Id.IsUnknown() {
|
||||
err := loadFlavorId(ctx, r.client, &model, flavor, storage)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError(err.Error(), err.Error())
|
||||
return
|
||||
}
|
||||
flavorValues := map[string]attr.Value{
|
||||
"id": flavor.Id,
|
||||
"description": flavor.Description,
|
||||
"cpu": flavor.CPU,
|
||||
"ram": flavor.RAM,
|
||||
"node_type": flavor.NodeType,
|
||||
}
|
||||
var flavorObject basetypes.ObjectValue
|
||||
flavorObject, diags = types.ObjectValue(flavorTypes, flavorValues)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if diags.HasError() {
|
||||
return
|
||||
}
|
||||
model.Flavor = flavorObject
|
||||
}
|
||||
|
||||
var encryption = &encryptionModel{}
|
||||
if !model.Encryption.IsNull() && !model.Encryption.IsUnknown() {
|
||||
diags = model.Encryption.As(ctx, encryption, basetypes.ObjectAsOptions{})
|
||||
|
|
@ -494,7 +418,7 @@ func (r *instanceResource) Create(
|
|||
}
|
||||
|
||||
// Generate API request body from model
|
||||
payload, err := toCreatePayload(&model, flavor, storage, encryption, network)
|
||||
payload, err := toCreatePayload(&model, storage, encryption, network)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Creating API payload: %v", err))
|
||||
return
|
||||
|
|
@ -516,11 +440,13 @@ func (r *instanceResource) Create(
|
|||
return
|
||||
}
|
||||
|
||||
// Set data returned by API in identity
|
||||
identity := IdentityModel{
|
||||
ID: utils.BuildInternalTerraformId(projectId, region, instanceId),
|
||||
}
|
||||
resp.Diagnostics.Append(resp.Identity.Set(ctx, identity)...)
|
||||
//// Set data returned by API in identity
|
||||
//identity := IdentityModel{
|
||||
// InstanceId: types.StringValue(instanceId),
|
||||
// Region: types.StringValue(region),
|
||||
// ProjectId: types.StringValue(projectId),
|
||||
//}
|
||||
//resp.Diagnostics.Append(resp.Identity.Set(ctx, identity)...)
|
||||
|
||||
waitResp, err := wait.CreateInstanceWaitHandler(ctx, r.client, projectId, region, instanceId).WaitWithContext(ctx)
|
||||
if err != nil {
|
||||
|
|
@ -529,7 +455,7 @@ func (r *instanceResource) Create(
|
|||
}
|
||||
|
||||
// Map response body to schema
|
||||
err = mapFields(ctx, waitResp, &model, flavor, storage, encryption, network, region)
|
||||
err = mapFields(ctx, r.client, waitResp, &model, storage, encryption, network, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
@ -552,12 +478,12 @@ func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r
|
|||
return
|
||||
}
|
||||
|
||||
// Read identity data
|
||||
var identityData IdentityModel
|
||||
resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
//// Read identity data
|
||||
//var identityData IdentityModel
|
||||
//resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...)
|
||||
//if resp.Diagnostics.HasError() {
|
||||
// return
|
||||
//}
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
|
|
@ -568,35 +494,27 @@ 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{}
|
||||
var storage = storageModel{}
|
||||
if !model.Storage.IsNull() && !model.Storage.IsUnknown() {
|
||||
diags = model.Storage.As(ctx, storage, basetypes.ObjectAsOptions{})
|
||||
diags = model.Storage.As(ctx, &storage, basetypes.ObjectAsOptions{})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var network = &networkModel{}
|
||||
var network = networkModel{}
|
||||
if !model.Network.IsNull() && !model.Network.IsUnknown() {
|
||||
diags = model.Network.As(ctx, network, basetypes.ObjectAsOptions{})
|
||||
diags = model.Network.As(ctx, &network, basetypes.ObjectAsOptions{})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var encryption = &encryptionModel{}
|
||||
var encryption = encryptionModel{}
|
||||
if !model.Encryption.IsNull() && !model.Encryption.IsUnknown() {
|
||||
diags = model.Encryption.As(ctx, encryption, basetypes.ObjectAsOptions{})
|
||||
diags = model.Encryption.As(ctx, &encryption, basetypes.ObjectAsOptions{})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
|
|
@ -617,7 +535,7 @@ func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r
|
|||
ctx = core.LogResponse(ctx)
|
||||
|
||||
// Map response body to schema
|
||||
err = mapFields(ctx, instanceResp, &model, flavor, storage, encryption, network, region)
|
||||
err = mapFields(ctx, r.client, instanceResp, &model, &storage, &encryption, &network, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
@ -629,18 +547,19 @@ func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r
|
|||
return
|
||||
}
|
||||
|
||||
identityData.ID = model.Id
|
||||
resp.Diagnostics.Append(resp.Identity.Set(ctx, identityData)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
//identityData.InstanceId = model.InstanceId
|
||||
//identityData.Region = model.Region
|
||||
//identityData.ProjectId = model.ProjectId
|
||||
//resp.Diagnostics.Append(resp.Identity.Set(ctx, identityData)...)
|
||||
//if resp.Diagnostics.HasError() {
|
||||
// return
|
||||
//}
|
||||
|
||||
tflog.Info(ctx, "Postgres Flex instance read")
|
||||
}
|
||||
|
||||
// Update updates the resource and sets the updated Terraform state on success.
|
||||
func (r *instanceResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
// Retrieve values from plan
|
||||
var model Model
|
||||
diags := req.Plan.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
|
|
@ -676,20 +595,6 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques
|
|||
}
|
||||
}
|
||||
|
||||
var flavor = &flavorModel{}
|
||||
if !model.Flavor.IsNull() && !model.Flavor.IsUnknown() {
|
||||
diags = model.Flavor.As(ctx, flavor, basetypes.ObjectAsOptions{})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
err := loadFlavorId(ctx, r.client, &model, flavor, storage)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Loading flavor ID: %v", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var network = &networkModel{}
|
||||
if !model.Network.IsNull() && !model.Network.IsUnknown() {
|
||||
diags = model.Network.As(ctx, network, basetypes.ObjectAsOptions{})
|
||||
|
|
@ -709,7 +614,7 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques
|
|||
}
|
||||
|
||||
// Generate API request body from model
|
||||
payload, err := toUpdatePayload(&model, flavor, storage, network)
|
||||
payload, err := toUpdatePayload(&model, storage, network)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Creating API payload: %v", err))
|
||||
return
|
||||
|
|
@ -730,7 +635,7 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques
|
|||
}
|
||||
|
||||
// Map response body to schema
|
||||
err = mapFields(ctx, waitResp, &model, flavor, storage, encryption, network, region)
|
||||
err = mapFields(ctx, r.client, waitResp, &model, storage, encryption, network, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
@ -745,7 +650,6 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques
|
|||
|
||||
// Delete deletes the resource and removes the Terraform state on success.
|
||||
func (r *instanceResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
// Retrieve values from state
|
||||
var model Model
|
||||
diags := req.State.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
|
|
@ -771,11 +675,22 @@ func (r *instanceResource) Delete(ctx context.Context, req resource.DeleteReques
|
|||
|
||||
ctx = core.LogResponse(ctx)
|
||||
|
||||
_, err = wait.DeleteInstanceWaitHandler(ctx, r.client, projectId, region, instanceId).SetTimeout(45 * time.Minute).WaitWithContext(ctx)
|
||||
//_, err = wait.DeleteInstanceWaitHandler(ctx, r.client, projectId, region, instanceId).SetTimeout(45 * time.Minute).WaitWithContext(ctx)
|
||||
//if err != nil {
|
||||
// core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting instance", fmt.Sprintf("Instance deletion waiting: %v", err))
|
||||
// return
|
||||
//}
|
||||
|
||||
_, err = r.client.GetInstanceRequest(ctx, projectId, region, instanceId).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting instance", fmt.Sprintf("Instance deletion waiting: %v", err))
|
||||
return
|
||||
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
|
||||
if ok && oapiErr.StatusCode != http.StatusNotFound {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting instance", err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
resp.State.RemoveResource(ctx)
|
||||
tflog.Info(ctx, "Postgres Flex instance deleted")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ func TestMapFields(t *testing.T) {
|
|||
description string
|
||||
state Model
|
||||
input *postgresflex.GetInstanceResponse
|
||||
flavor *flavorModel
|
||||
storage *storageModel
|
||||
encryption *encryptionModel
|
||||
network *networkModel
|
||||
|
|
@ -51,9 +50,6 @@ func TestMapFields(t *testing.T) {
|
|||
FlavorId: utils.Ptr("flavor_id"),
|
||||
Replicas: postgresflex.GetInstanceResponseGetReplicasAttributeType(utils.Ptr(int32(1))),
|
||||
},
|
||||
&flavorModel{
|
||||
NodeType: types.StringValue("Single"),
|
||||
},
|
||||
&storageModel{},
|
||||
&encryptionModel{},
|
||||
&networkModel{
|
||||
|
|
@ -67,16 +63,10 @@ func TestMapFields(t *testing.T) {
|
|||
InstanceId: types.StringValue("iid"),
|
||||
ProjectId: types.StringValue("pid"),
|
||||
Name: types.StringNull(),
|
||||
FlavorId: types.StringValue("flavor_id"),
|
||||
//ACL: types.ListNull(types.StringType),
|
||||
BackupSchedule: types.StringNull(),
|
||||
Flavor: types.ObjectValueMust(flavorTypes, map[string]attr.Value{
|
||||
"id": types.StringNull(),
|
||||
"description": types.StringNull(),
|
||||
"cpu": types.Int64Null(),
|
||||
"ram": types.Int64Null(),
|
||||
"node_type": types.StringValue("Single"),
|
||||
}),
|
||||
Replicas: types.Int64Value(1),
|
||||
Replicas: types.Int64Value(1),
|
||||
Encryption: types.ObjectValueMust(encryptionTypes, map[string]attr.Value{
|
||||
"keyring_id": types.StringNull(),
|
||||
"key_id": types.StringNull(),
|
||||
|
|
@ -171,7 +161,6 @@ func TestMapFields(t *testing.T) {
|
|||
ProjectId: types.StringValue("pid"),
|
||||
},
|
||||
nil,
|
||||
&flavorModel{},
|
||||
&storageModel{},
|
||||
&encryptionModel{},
|
||||
&networkModel{},
|
||||
|
|
@ -186,7 +175,6 @@ func TestMapFields(t *testing.T) {
|
|||
ProjectId: types.StringValue("pid"),
|
||||
},
|
||||
&postgresflex.GetInstanceResponse{},
|
||||
&flavorModel{},
|
||||
&storageModel{},
|
||||
&encryptionModel{},
|
||||
&networkModel{},
|
||||
|
|
@ -197,7 +185,21 @@ func TestMapFields(t *testing.T) {
|
|||
}
|
||||
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.encryption, tt.network, tt.region)
|
||||
client := postgresFlexClientMocked{
|
||||
returnError: false,
|
||||
firstItem: 0,
|
||||
lastItem: 0,
|
||||
}
|
||||
err := mapFields(
|
||||
context.Background(),
|
||||
client,
|
||||
tt.input,
|
||||
&tt.state,
|
||||
tt.storage,
|
||||
tt.encryption,
|
||||
tt.network,
|
||||
tt.region,
|
||||
)
|
||||
if !tt.isValid && err == nil {
|
||||
t.Fatalf("Should have failed")
|
||||
}
|
||||
|
|
@ -219,7 +221,6 @@ func TestToCreatePayload(t *testing.T) {
|
|||
description string
|
||||
input *Model
|
||||
inputAcl []string
|
||||
inputFlavor *flavorModel
|
||||
inputStorage *storageModel
|
||||
inputEncryption *encryptionModel
|
||||
inputNetwork *networkModel
|
||||
|
|
@ -232,34 +233,6 @@ func TestToCreatePayload(t *testing.T) {
|
|||
Replicas: types.Int64Value(1),
|
||||
},
|
||||
[]string{},
|
||||
&flavorModel{},
|
||||
&storageModel{},
|
||||
&encryptionModel{},
|
||||
&networkModel{
|
||||
ACL: types.ListValueMust(types.StringType, []attr.Value{
|
||||
types.StringValue("0.0.0.0/0"),
|
||||
}),
|
||||
},
|
||||
&postgresflex.CreateInstanceRequestPayload{
|
||||
Acl: &[]string{"0.0.0.0/0"},
|
||||
Storage: postgresflex.CreateInstanceRequestPayloadGetStorageAttributeType(&postgresflex.Storage{}),
|
||||
Encryption: &postgresflex.InstanceEncryption{},
|
||||
Network: &postgresflex.InstanceNetwork{
|
||||
Acl: &[]string{"0.0.0.0/0"},
|
||||
},
|
||||
Replicas: postgresflex.CreateInstanceRequestPayloadGetReplicasAttributeType(utils.Ptr(int32(1))),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"use flavor node_type instead of replicas",
|
||||
&Model{},
|
||||
[]string{
|
||||
"0.0.0.0/0",
|
||||
},
|
||||
&flavorModel{
|
||||
NodeType: types.StringValue("Single"),
|
||||
},
|
||||
&storageModel{},
|
||||
&encryptionModel{},
|
||||
&networkModel{
|
||||
|
|
@ -282,7 +255,6 @@ func TestToCreatePayload(t *testing.T) {
|
|||
"nil_model",
|
||||
nil,
|
||||
[]string{},
|
||||
&flavorModel{},
|
||||
&storageModel{},
|
||||
&encryptionModel{},
|
||||
&networkModel{},
|
||||
|
|
@ -293,7 +265,6 @@ func TestToCreatePayload(t *testing.T) {
|
|||
"nil_acl",
|
||||
&Model{},
|
||||
nil,
|
||||
&flavorModel{},
|
||||
&storageModel{},
|
||||
&encryptionModel{},
|
||||
&networkModel{},
|
||||
|
|
@ -304,7 +275,6 @@ func TestToCreatePayload(t *testing.T) {
|
|||
"nil_flavor",
|
||||
&Model{},
|
||||
[]string{},
|
||||
nil,
|
||||
&storageModel{},
|
||||
&encryptionModel{},
|
||||
&networkModel{},
|
||||
|
|
@ -315,7 +285,6 @@ func TestToCreatePayload(t *testing.T) {
|
|||
"nil_storage",
|
||||
&Model{},
|
||||
[]string{},
|
||||
&flavorModel{},
|
||||
nil,
|
||||
&encryptionModel{},
|
||||
&networkModel{},
|
||||
|
|
@ -325,7 +294,7 @@ func TestToCreatePayload(t *testing.T) {
|
|||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.description, func(t *testing.T) {
|
||||
output, err := toCreatePayload(tt.input, tt.inputFlavor, tt.inputStorage, tt.inputEncryption, tt.inputNetwork)
|
||||
output, err := toCreatePayload(tt.input, tt.inputStorage, tt.inputEncryption, tt.inputNetwork)
|
||||
if !tt.isValid && err == nil {
|
||||
t.Fatalf("Should have failed")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,85 +0,0 @@
|
|||
package postgresflexalpha
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
|
||||
)
|
||||
|
||||
type useStateForUnknownIfFlavorUnchangedModifier struct {
|
||||
Req resource.SchemaRequest
|
||||
}
|
||||
|
||||
// UseStateForUnknownIfFlavorUnchanged returns a plan modifier similar to UseStateForUnknown
|
||||
// if the RAM and CPU values are not changed in the plan. Otherwise, the plan modifier does nothing.
|
||||
func UseStateForUnknownIfFlavorUnchanged(req resource.SchemaRequest) planmodifier.String {
|
||||
return useStateForUnknownIfFlavorUnchangedModifier{
|
||||
Req: req,
|
||||
}
|
||||
}
|
||||
|
||||
func (m useStateForUnknownIfFlavorUnchangedModifier) Description(context.Context) string {
|
||||
return "UseStateForUnknownIfFlavorUnchanged returns a plan modifier similar to UseStateForUnknown if the RAM and CPU values are not changed in the plan. Otherwise, the plan modifier does nothing."
|
||||
}
|
||||
|
||||
func (m useStateForUnknownIfFlavorUnchangedModifier) MarkdownDescription(ctx context.Context) string {
|
||||
return m.Description(ctx)
|
||||
}
|
||||
|
||||
func (m useStateForUnknownIfFlavorUnchangedModifier) PlanModifyString(ctx context.Context, req planmodifier.StringRequest, resp *planmodifier.StringResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
// Do nothing if there is no state value.
|
||||
if req.StateValue.IsNull() {
|
||||
return
|
||||
}
|
||||
|
||||
// Do nothing if there is a known planned value.
|
||||
if !req.PlanValue.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
// Do nothing if there is an unknown configuration value, otherwise interpolation gets messed up.
|
||||
if req.ConfigValue.IsUnknown() {
|
||||
return
|
||||
}
|
||||
|
||||
// The above checks are taken from the UseStateForUnknown plan modifier implementation
|
||||
// (https://github.com/hashicorp/terraform-plugin-framework/blob/main/resource/schema/stringplanmodifier/use_state_for_unknown.go#L38)
|
||||
|
||||
var stateModel Model
|
||||
diags := req.State.Get(ctx, &stateModel)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
var stateFlavor = &flavorModel{}
|
||||
if !stateModel.Flavor.IsNull() && !stateModel.Flavor.IsUnknown() {
|
||||
diags = stateModel.Flavor.As(ctx, stateFlavor, basetypes.ObjectAsOptions{})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var planModel Model
|
||||
diags = req.Plan.Get(ctx, &planModel)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
var planFlavor = &flavorModel{}
|
||||
if !planModel.Flavor.IsNull() && !planModel.Flavor.IsUnknown() {
|
||||
diags = planModel.Flavor.As(ctx, planFlavor, basetypes.ObjectAsOptions{})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if planFlavor.CPU == stateFlavor.CPU && planFlavor.RAM == stateFlavor.RAM {
|
||||
resp.PlanValue = req.StateValue
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,6 @@ import (
|
|||
"github.com/stackitcloud/stackit-sdk-go/core/config"
|
||||
|
||||
postgresflex "github.com/mhenselin/terraform-provider-stackitprivatepreview/pkg/postgresflexalpha"
|
||||
"github.com/mhenselin/terraform-provider-stackitprivatepreview/pkg/postgresflexalpha/wait"
|
||||
)
|
||||
|
||||
// Instance resource data
|
||||
|
|
@ -364,10 +363,10 @@ func testAccCheckPostgresFlexDestroy(s *terraform.State) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("deleting instance %s during CheckDestroy: %w", *items[i].Id, err)
|
||||
}
|
||||
_, err = wait.DeleteInstanceWaitHandler(ctx, client, testutil.ProjectId, testutil.Region, *items[i].Id).WaitWithContext(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("deleting instance %s during CheckDestroy: waiting for deletion %w", *items[i].Id, err)
|
||||
}
|
||||
//_, err = wait.DeleteInstanceWaitHandler(ctx, client, testutil.ProjectId, testutil.Region, *items[i].Id).WaitWithContext(ctx)
|
||||
//if err != nil {
|
||||
// return fmt.Errorf("deleting instance %s during CheckDestroy: waiting for deletion %w", *items[i].Id, err)
|
||||
//}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -41,16 +41,16 @@ var (
|
|||
)
|
||||
|
||||
type Model struct {
|
||||
Id types.String `tfsdk:"id"` // needed by TF
|
||||
UserId types.Int64 `tfsdk:"user_id"`
|
||||
InstanceId types.String `tfsdk:"instance_id"`
|
||||
ProjectId types.String `tfsdk:"project_id"`
|
||||
Username types.String `tfsdk:"username"`
|
||||
Roles types.Set `tfsdk:"roles"`
|
||||
Password types.String `tfsdk:"password"`
|
||||
Host types.String `tfsdk:"host"`
|
||||
Port types.Int64 `tfsdk:"port"`
|
||||
Uri types.String `tfsdk:"uri"`
|
||||
Id types.String `tfsdk:"id"` // needed by TF
|
||||
UserId types.Int64 `tfsdk:"user_id"`
|
||||
InstanceId types.String `tfsdk:"instance_id"`
|
||||
ProjectId types.String `tfsdk:"project_id"`
|
||||
Username types.String `tfsdk:"username"`
|
||||
Roles types.Set `tfsdk:"roles"`
|
||||
Password types.String `tfsdk:"password"`
|
||||
//Host types.String `tfsdk:"host"`
|
||||
//Port types.Int64 `tfsdk:"port"`
|
||||
//Uri types.String `tfsdk:"uri"`
|
||||
Region types.String `tfsdk:"region"`
|
||||
Status types.String `tfsdk:"status"`
|
||||
ConnectionString types.String `tfsdk:"connection_string"`
|
||||
|
|
@ -124,6 +124,7 @@ func (r *userResource) Configure(ctx context.Context, req resource.ConfigureRequ
|
|||
|
||||
// Schema defines the schema for the resource.
|
||||
func (r *userResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
// rolesOptions := []string{"login", "createdb", "createrole"}
|
||||
rolesOptions := []string{"login", "createdb"}
|
||||
|
||||
descriptions := map[string]string{
|
||||
|
|
@ -181,9 +182,9 @@ func (r *userResource) Schema(_ context.Context, _ resource.SchemaRequest, resp
|
|||
},
|
||||
},
|
||||
"username": schema.StringAttribute{
|
||||
Required: true,
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
// stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
},
|
||||
"roles": schema.SetAttribute{
|
||||
|
|
@ -192,7 +193,7 @@ func (r *userResource) Schema(_ context.Context, _ resource.SchemaRequest, resp
|
|||
Required: true,
|
||||
Validators: []validator.Set{
|
||||
setvalidator.ValueStringsAre(
|
||||
stringvalidator.OneOf("login", "createdb"),
|
||||
stringvalidator.OneOf(rolesOptions...),
|
||||
),
|
||||
},
|
||||
},
|
||||
|
|
@ -200,16 +201,16 @@ func (r *userResource) Schema(_ context.Context, _ resource.SchemaRequest, resp
|
|||
Computed: true,
|
||||
Sensitive: true,
|
||||
},
|
||||
"host": schema.StringAttribute{
|
||||
Computed: true,
|
||||
},
|
||||
"port": schema.Int64Attribute{
|
||||
Computed: true,
|
||||
},
|
||||
"uri": schema.StringAttribute{
|
||||
Computed: true,
|
||||
Sensitive: true,
|
||||
},
|
||||
//"host": schema.StringAttribute{
|
||||
// Computed: true,
|
||||
//},
|
||||
//"port": schema.Int64Attribute{
|
||||
// Computed: true,
|
||||
//},
|
||||
//"uri": schema.StringAttribute{
|
||||
// Computed: true,
|
||||
// Sensitive: true,
|
||||
//},
|
||||
"region": schema.StringAttribute{
|
||||
Optional: true,
|
||||
// must be computed to allow for storing the override value from the provider
|
||||
|
|
@ -375,7 +376,6 @@ func (r *userResource) Update(
|
|||
req resource.UpdateRequest,
|
||||
resp *resource.UpdateResponse,
|
||||
) { // nolint:gocritic // function signature required by Terraform
|
||||
// Retrieve values from plan
|
||||
var model Model
|
||||
diags := req.Plan.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
|
|
@ -466,7 +466,6 @@ func (r *userResource) Delete(
|
|||
req resource.DeleteRequest,
|
||||
resp *resource.DeleteResponse,
|
||||
) { // nolint:gocritic // function signature required by Terraform
|
||||
// Retrieve values from plan
|
||||
var model Model
|
||||
diags := req.State.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
|
|
@ -581,9 +580,10 @@ func mapFieldsCreate(
|
|||
model.Roles = rolesSet
|
||||
}
|
||||
|
||||
model.Password = types.StringValue(*user.Password)
|
||||
model.Password = types.StringPointerValue(user.Password)
|
||||
model.Region = types.StringValue(region)
|
||||
model.Status = types.StringPointerValue(user.Status)
|
||||
//model.Host = types.StringPointerValue()
|
||||
model.ConnectionString = types.StringPointerValue(user.ConnectionString)
|
||||
|
||||
return nil
|
||||
|
|
@ -625,8 +625,8 @@ func mapFields(userResp *postgresflex.GetUserResponse, model *Model, region stri
|
|||
}
|
||||
model.Roles = rolesSet
|
||||
}
|
||||
model.Host = types.StringPointerValue(user.Host)
|
||||
model.Port = types.Int64PointerValue(user.Port)
|
||||
//model.Host = types.StringPointerValue(user.Host)
|
||||
//model.Port = types.Int64PointerValue(user.Port)
|
||||
model.Region = types.StringValue(region)
|
||||
model.Status = types.StringPointerValue(user.Status)
|
||||
model.ConnectionString = types.StringPointerValue(user.ConnectionString)
|
||||
|
|
@ -667,6 +667,7 @@ func toUpdatePayload(model *Model, roles *[]string) (
|
|||
}
|
||||
|
||||
return &postgresflex.UpdateUserRequestPayload{
|
||||
Name: conversion.StringValueToPointer(model.Username),
|
||||
Roles: toPayloadRoles(roles),
|
||||
}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,252 @@
|
|||
package sqlserverFlexAlphaFlavor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/attr"
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
|
||||
"github.com/mhenselin/terraform-provider-stackitprivatepreview/pkg/sqlserverflexalpha"
|
||||
"github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/conversion"
|
||||
"github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/utils"
|
||||
|
||||
sqlserverflex "github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/services/sqlserverflexalpha"
|
||||
sqlserverflexUtils "github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/services/sqlserverflexalpha/utils"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
"github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/core"
|
||||
)
|
||||
|
||||
// Ensure the implementation satisfies the expected interfaces.
|
||||
var (
|
||||
_ datasource.DataSource = &flavorDataSource{}
|
||||
)
|
||||
|
||||
type FlavorModel struct {
|
||||
ProjectId types.String `tfsdk:"project_id"`
|
||||
Region types.String `tfsdk:"region"`
|
||||
StorageClass types.String `tfsdk:"storage_class"`
|
||||
Cpu types.Int64 `tfsdk:"cpu"`
|
||||
Description types.String `tfsdk:"description"`
|
||||
Id types.String `tfsdk:"id"`
|
||||
FlavorId types.String `tfsdk:"flavor_id"`
|
||||
MaxGb types.Int64 `tfsdk:"max_gb"`
|
||||
Memory types.Int64 `tfsdk:"ram"`
|
||||
MinGb types.Int64 `tfsdk:"min_gb"`
|
||||
NodeType types.String `tfsdk:"node_type"`
|
||||
StorageClasses types.List `tfsdk:"storage_classes"`
|
||||
}
|
||||
|
||||
// NewFlavorDataSource is a helper function to simplify the provider implementation.
|
||||
func NewFlavorDataSource() datasource.DataSource {
|
||||
return &flavorDataSource{}
|
||||
}
|
||||
|
||||
// flavorDataSource is the data source implementation.
|
||||
type flavorDataSource struct {
|
||||
client *sqlserverflexalpha.APIClient
|
||||
providerData core.ProviderData
|
||||
}
|
||||
|
||||
// Metadata returns the data source type name.
|
||||
func (r *flavorDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_sqlserverflexalpha_flavor"
|
||||
}
|
||||
|
||||
// Configure adds the provider configured client to the data source.
|
||||
func (r *flavorDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
|
||||
var ok bool
|
||||
r.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
apiClient := sqlserverflexUtils.ConfigureClient(ctx, &r.providerData, &resp.Diagnostics)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
r.client = apiClient
|
||||
tflog.Info(ctx, "Postgres Flex instance client configured")
|
||||
}
|
||||
|
||||
func (r *flavorDataSource) Schema(ctx context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
resp.Schema = schema.Schema{
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"project_id": schema.StringAttribute{
|
||||
Required: true,
|
||||
Description: "The cpu count of the instance.",
|
||||
MarkdownDescription: "The cpu count of the instance.",
|
||||
},
|
||||
"region": schema.StringAttribute{
|
||||
Required: true,
|
||||
Description: "The flavor description.",
|
||||
MarkdownDescription: "The flavor description.",
|
||||
},
|
||||
"cpu": schema.Int64Attribute{
|
||||
Required: true,
|
||||
Description: "The cpu count of the instance.",
|
||||
MarkdownDescription: "The cpu count of the instance.",
|
||||
},
|
||||
"ram": schema.Int64Attribute{
|
||||
Required: true,
|
||||
Description: "The memory of the instance in Gibibyte.",
|
||||
MarkdownDescription: "The memory of the instance in Gibibyte.",
|
||||
},
|
||||
"storage_class": schema.StringAttribute{
|
||||
Required: true,
|
||||
Description: "The memory of the instance in Gibibyte.",
|
||||
MarkdownDescription: "The memory of the instance in Gibibyte.",
|
||||
},
|
||||
"description": schema.StringAttribute{
|
||||
Computed: true,
|
||||
Description: "The flavor description.",
|
||||
MarkdownDescription: "The flavor description.",
|
||||
},
|
||||
"id": schema.StringAttribute{
|
||||
Computed: true,
|
||||
Description: "The terraform id of the instance flavor.",
|
||||
MarkdownDescription: "The terraform id of the instance flavor.",
|
||||
},
|
||||
"flavor_id": schema.StringAttribute{
|
||||
Computed: true,
|
||||
Description: "The flavor id of the instance flavor.",
|
||||
MarkdownDescription: "The flavor id of the instance flavor.",
|
||||
},
|
||||
"max_gb": schema.Int64Attribute{
|
||||
Computed: true,
|
||||
Description: "maximum storage which can be ordered for the flavor in Gigabyte.",
|
||||
MarkdownDescription: "maximum storage which can be ordered for the flavor in Gigabyte.",
|
||||
},
|
||||
"min_gb": schema.Int64Attribute{
|
||||
Computed: true,
|
||||
Description: "minimum storage which is required to order in Gigabyte.",
|
||||
MarkdownDescription: "minimum storage which is required to order in Gigabyte.",
|
||||
},
|
||||
"node_type": schema.StringAttribute{
|
||||
Required: true,
|
||||
Description: "defines the nodeType it can be either single or replica",
|
||||
MarkdownDescription: "defines the nodeType it can be either single or replica",
|
||||
},
|
||||
"storage_classes": schema.ListNestedAttribute{
|
||||
Computed: true,
|
||||
NestedObject: schema.NestedAttributeObject{
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"class": schema.StringAttribute{
|
||||
Computed: true,
|
||||
},
|
||||
"max_io_per_sec": schema.Int64Attribute{
|
||||
Computed: true,
|
||||
},
|
||||
"max_through_in_mb": schema.Int64Attribute{
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
CustomType: sqlserverflex.StorageClassesType{
|
||||
ObjectType: types.ObjectType{
|
||||
AttrTypes: sqlserverflex.StorageClassesValue{}.AttributeTypes(ctx),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (r *flavorDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
|
||||
var model FlavorModel
|
||||
diags := req.Config.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
|
||||
flavors, err := getAllFlavors(ctx, r.client, projectId, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading flavors", fmt.Sprintf("getAllFlavors: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
var foundFlavors []sqlserverflexalpha.ListFlavors
|
||||
for _, flavor := range flavors {
|
||||
if model.Cpu.ValueInt64() != *flavor.Cpu {
|
||||
continue
|
||||
}
|
||||
if model.Memory.ValueInt64() != *flavor.Memory {
|
||||
continue
|
||||
}
|
||||
if model.NodeType.ValueString() != *flavor.NodeType {
|
||||
continue
|
||||
}
|
||||
for _, sc := range *flavor.StorageClasses {
|
||||
if model.StorageClass.ValueString() != *sc.Class {
|
||||
continue
|
||||
}
|
||||
foundFlavors = append(foundFlavors, flavor)
|
||||
}
|
||||
}
|
||||
if len(foundFlavors) == 0 {
|
||||
resp.Diagnostics.AddError("get flavor", "could not find requested flavor")
|
||||
return
|
||||
}
|
||||
if len(foundFlavors) > 1 {
|
||||
resp.Diagnostics.AddError("get flavor", "found too many matching flavors")
|
||||
return
|
||||
}
|
||||
|
||||
f := foundFlavors[0]
|
||||
model.Description = types.StringValue(*f.Description)
|
||||
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, *f.Id)
|
||||
model.FlavorId = types.StringValue(*f.Id)
|
||||
model.MaxGb = types.Int64Value(*f.MaxGB)
|
||||
model.MinGb = types.Int64Value(*f.MinGB)
|
||||
|
||||
if f.StorageClasses == nil {
|
||||
model.StorageClasses = types.ListNull(sqlserverflex.StorageClassesType{
|
||||
ObjectType: basetypes.ObjectType{
|
||||
AttrTypes: sqlserverflex.StorageClassesValue{}.AttributeTypes(ctx),
|
||||
},
|
||||
})
|
||||
} else {
|
||||
var scList []attr.Value
|
||||
for _, sc := range *f.StorageClasses {
|
||||
scList = append(
|
||||
scList,
|
||||
sqlserverflex.NewStorageClassesValueMust(
|
||||
sqlserverflex.StorageClassesValue{}.AttributeTypes(ctx),
|
||||
map[string]attr.Value{
|
||||
"class": types.StringValue(*sc.Class),
|
||||
"max_io_per_sec": types.Int64Value(*sc.MaxIoPerSec),
|
||||
"max_through_in_mb": types.Int64Value(*sc.MaxThroughInMb),
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
storageClassesList := types.ListValueMust(
|
||||
sqlserverflex.StorageClassesType{
|
||||
ObjectType: basetypes.ObjectType{
|
||||
AttrTypes: sqlserverflex.StorageClassesValue{}.AttributeTypes(ctx),
|
||||
},
|
||||
},
|
||||
scList,
|
||||
)
|
||||
model.StorageClasses = storageClassesList
|
||||
}
|
||||
|
||||
// Set refreshed state
|
||||
diags = resp.State.Set(ctx, model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
tflog.Info(ctx, "Postgres Flex flavors read")
|
||||
}
|
||||
201
stackit/internal/services/sqlserverflexalpha/flavor/functions.go
Normal file
201
stackit/internal/services/sqlserverflexalpha/flavor/functions.go
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
package sqlserverFlexAlphaFlavor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
sqlserverflex "github.com/mhenselin/terraform-provider-stackitprivatepreview/pkg/sqlserverflexalpha"
|
||||
)
|
||||
|
||||
type flavorsClient interface {
|
||||
GetFlavorsRequestExecute(
|
||||
ctx context.Context,
|
||||
projectId, region string,
|
||||
page, size *int64,
|
||||
sort *sqlserverflex.FlavorSort,
|
||||
) (*sqlserverflex.GetFlavorsResponse, error)
|
||||
}
|
||||
|
||||
//func loadFlavorId(ctx context.Context, client flavorsClient, model *Model, flavor *flavorModel, storage *storageModel) error {
|
||||
// if model == nil {
|
||||
// return fmt.Errorf("nil model")
|
||||
// }
|
||||
// if flavor == nil {
|
||||
// return fmt.Errorf("nil flavor")
|
||||
// }
|
||||
// cpu := flavor.CPU.ValueInt64()
|
||||
// if cpu == 0 {
|
||||
// return fmt.Errorf("nil CPU")
|
||||
// }
|
||||
// ram := flavor.RAM.ValueInt64()
|
||||
// if ram == 0 {
|
||||
// return fmt.Errorf("nil RAM")
|
||||
// }
|
||||
//
|
||||
// nodeType := flavor.NodeType.ValueString()
|
||||
// if nodeType == "" {
|
||||
// if model.Replicas.IsNull() || model.Replicas.IsUnknown() {
|
||||
// return fmt.Errorf("nil NodeType")
|
||||
// }
|
||||
// switch model.Replicas.ValueInt64() {
|
||||
// case 1:
|
||||
// nodeType = "Single"
|
||||
// case 3:
|
||||
// nodeType = "Replica"
|
||||
// default:
|
||||
// return fmt.Errorf("unknown Replicas value: %d", model.Replicas.ValueInt64())
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// storageClass := conversion.StringValueToPointer(storage.Class)
|
||||
// if storageClass == nil {
|
||||
// return fmt.Errorf("nil StorageClass")
|
||||
// }
|
||||
// storageSize := conversion.Int64ValueToPointer(storage.Size)
|
||||
// if storageSize == nil {
|
||||
// return fmt.Errorf("nil StorageSize")
|
||||
// }
|
||||
//
|
||||
// projectId := model.ProjectId.ValueString()
|
||||
// region := model.Region.ValueString()
|
||||
//
|
||||
// flavorList, err := getAllFlavors(ctx, client, projectId, region)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// avl := ""
|
||||
// foundFlavorCount := 0
|
||||
// var foundFlavors []string
|
||||
// for _, f := range flavorList {
|
||||
// if f.Id == nil || f.Cpu == nil || f.Memory == nil {
|
||||
// continue
|
||||
// }
|
||||
// if !strings.EqualFold(*f.NodeType, nodeType) {
|
||||
// continue
|
||||
// }
|
||||
// if *f.Cpu == cpu && *f.Memory == ram {
|
||||
// var useSc *sqlserverflex.FlavorStorageClassesStorageClass
|
||||
// for _, sc := range *f.StorageClasses {
|
||||
// if *sc.Class != *storageClass {
|
||||
// continue
|
||||
// }
|
||||
// if *storageSize < *f.MinGB || *storageSize > *f.MaxGB {
|
||||
// return fmt.Errorf("storage size %d out of bounds (min: %d - max: %d)", *storageSize, *f.MinGB, *f.MaxGB)
|
||||
// }
|
||||
// useSc = &sc
|
||||
// }
|
||||
// if useSc == nil {
|
||||
// return fmt.Errorf("no storage class found for %s", *storageClass)
|
||||
// }
|
||||
//
|
||||
// flavor.Id = types.StringValue(*f.Id)
|
||||
// flavor.Description = types.StringValue(*f.Description)
|
||||
// foundFlavors = append(foundFlavors, fmt.Sprintf("%s (%d/%d - %s)", *f.Id, *f.Cpu, *f.Memory, *f.NodeType))
|
||||
// foundFlavorCount++
|
||||
// }
|
||||
// for _, cls := range *f.StorageClasses {
|
||||
// avl = fmt.Sprintf("%s\n- %d CPU, %d GB RAM, storage %s (min: %d - max: %d)", avl, *f.Cpu, *f.Memory, *cls.Class, *f.MinGB, *f.MaxGB)
|
||||
// }
|
||||
// }
|
||||
// if foundFlavorCount > 1 {
|
||||
// return fmt.Errorf(
|
||||
// "number of flavors returned: %d\nmultiple flavors found: %d flavors\n %s",
|
||||
// len(flavorList),
|
||||
// foundFlavorCount,
|
||||
// strings.Join(foundFlavors, "\n "),
|
||||
// )
|
||||
// }
|
||||
// if flavor.Id.ValueString() == "" {
|
||||
// return fmt.Errorf("couldn't find flavor, available specs are:%s", avl)
|
||||
// }
|
||||
//
|
||||
// return nil
|
||||
//}
|
||||
|
||||
func getAllFlavors(ctx context.Context, client flavorsClient, projectId, region string) ([]sqlserverflex.ListFlavors, error) {
|
||||
if projectId == "" || region == "" {
|
||||
return nil, fmt.Errorf("listing sqlserverflex flavors: projectId and region are required")
|
||||
}
|
||||
var flavorList []sqlserverflex.ListFlavors
|
||||
|
||||
page := int64(1)
|
||||
size := int64(10)
|
||||
sort := sqlserverflex.FLAVORSORT_INDEX_ASC
|
||||
counter := 0
|
||||
for {
|
||||
res, err := client.GetFlavorsRequestExecute(ctx, projectId, region, &page, &size, &sort)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("listing sqlserverflex flavors: %w", err)
|
||||
}
|
||||
if res.Flavors == nil {
|
||||
return nil, fmt.Errorf("finding flavors for project %s", projectId)
|
||||
}
|
||||
pagination := res.GetPagination()
|
||||
flavors := res.GetFlavors()
|
||||
flavorList = append(flavorList, flavors...)
|
||||
|
||||
if *pagination.TotalRows < int64(len(flavorList)) {
|
||||
return nil, fmt.Errorf("total rows is smaller than current accumulated list - that should not happen")
|
||||
}
|
||||
if *pagination.TotalRows == int64(len(flavorList)) {
|
||||
break
|
||||
}
|
||||
page++
|
||||
|
||||
if page > *pagination.TotalPages {
|
||||
break
|
||||
}
|
||||
|
||||
// implement a breakpoint
|
||||
counter++
|
||||
if counter > 1000 {
|
||||
panic("too many pagination results")
|
||||
}
|
||||
}
|
||||
return flavorList, nil
|
||||
}
|
||||
|
||||
//func getFlavorModelById(ctx context.Context, client flavorsClient, model *Model, flavor *flavorModel) error {
|
||||
// if model == nil {
|
||||
// return fmt.Errorf("nil model")
|
||||
// }
|
||||
// if flavor == nil {
|
||||
// return fmt.Errorf("nil flavor")
|
||||
// }
|
||||
// id := conversion.StringValueToPointer(flavor.Id)
|
||||
// if id == nil {
|
||||
// return fmt.Errorf("nil flavor ID")
|
||||
// }
|
||||
//
|
||||
// flavor.Id = types.StringValue("")
|
||||
//
|
||||
// projectId := model.ProjectId.ValueString()
|
||||
// region := model.Region.ValueString()
|
||||
//
|
||||
// flavorList, err := getAllFlavors(ctx, client, projectId, region)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// avl := ""
|
||||
// for _, f := range flavorList {
|
||||
// if f.Id == nil || f.Cpu == nil || f.Memory == nil {
|
||||
// continue
|
||||
// }
|
||||
// if *f.Id == *id {
|
||||
// flavor.Id = types.StringValue(*f.Id)
|
||||
// flavor.Description = types.StringValue(*f.Description)
|
||||
// flavor.CPU = types.Int64Value(*f.Cpu)
|
||||
// flavor.RAM = types.Int64Value(*f.Memory)
|
||||
// flavor.NodeType = types.StringValue(*f.NodeType)
|
||||
// break
|
||||
// }
|
||||
// avl = fmt.Sprintf("%s\n- %d CPU, %d GB RAM", avl, *f.Cpu, *f.Memory)
|
||||
// }
|
||||
// if flavor.Id.ValueString() == "" {
|
||||
// return fmt.Errorf("couldn't find flavor, available specs are: %s", avl)
|
||||
// }
|
||||
//
|
||||
// return nil
|
||||
//}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
package postgresFlexAlphaFlavor
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/mhenselin/terraform-provider-stackitprivatepreview/pkg/postgresflexalpha"
|
||||
"github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/conversion"
|
||||
"github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha"
|
||||
postgresflexUtils "github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/utils"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
"github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/core"
|
||||
)
|
||||
|
||||
// Ensure the implementation satisfies the expected interfaces.
|
||||
var (
|
||||
_ datasource.DataSource = &flavorListDataSource{}
|
||||
)
|
||||
|
||||
// NewFlavorListDataSource is a helper function to simplify the provider implementation.
|
||||
func NewFlavorListDataSource() datasource.DataSource {
|
||||
return &flavorListDataSource{}
|
||||
}
|
||||
|
||||
// flavorDataSource is the data source implementation.
|
||||
type flavorListDataSource struct {
|
||||
client *postgresflexalpha.APIClient
|
||||
providerData core.ProviderData
|
||||
}
|
||||
|
||||
// Metadata returns the data source type name.
|
||||
func (r *flavorListDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_postgresflexalpha_flavorlist"
|
||||
}
|
||||
|
||||
// Configure adds the provider configured client to the data source.
|
||||
func (r *flavorListDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
|
||||
var ok bool
|
||||
r.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
apiClient := postgresflexUtils.ConfigureClient(ctx, &r.providerData, &resp.Diagnostics)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
r.client = apiClient
|
||||
tflog.Info(ctx, "Postgres Flex flavors client configured")
|
||||
}
|
||||
|
||||
func (r *flavorListDataSource) Schema(ctx context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
resp.Schema = postgresflex.FlavorDataSourceSchema(ctx)
|
||||
}
|
||||
|
||||
func (r *flavorListDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
|
||||
var model postgresflex.FlavorModel
|
||||
diags := req.Config.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
ctx = core.InitProviderContext(ctx)
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := r.providerData.GetRegionWithOverride(model.Region)
|
||||
ctx = tflog.SetField(ctx, "project_id", projectId)
|
||||
ctx = tflog.SetField(ctx, "region", region)
|
||||
|
||||
// Set refreshed state
|
||||
diags = resp.State.Set(ctx, model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
tflog.Info(ctx, "Postgres Flex flavors read")
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -253,22 +253,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() {
|
||||
flavor.Id = types.StringValue(*instanceResp.FlavorId)
|
||||
if flavor.Id.IsNull() || flavor.Id.IsUnknown() || flavor.Id.String() == "" {
|
||||
panic("WTF FlavorId can not be null or empty string")
|
||||
}
|
||||
err = getFlavorModelById(ctx, r.client, &model, flavor)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError(err.Error(), err.Error())
|
||||
return
|
||||
}
|
||||
if flavor.CPU.IsNull() || flavor.CPU.IsUnknown() || flavor.CPU.String() == "" {
|
||||
panic("WTF FlavorId can not be null or empty string")
|
||||
}
|
||||
}
|
||||
|
||||
var storage = &storageModel{}
|
||||
if !model.Storage.IsNull() && !model.Storage.IsUnknown() {
|
||||
diags = model.Storage.As(ctx, storage, basetypes.ObjectAsOptions{})
|
||||
|
|
@ -296,7 +280,7 @@ func (r *instanceDataSource) Read(ctx context.Context, req datasource.ReadReques
|
|||
}
|
||||
}
|
||||
|
||||
err = mapFields(ctx, instanceResp, &model, flavor, storage, encryption, network, region)
|
||||
err = mapFields(ctx, instanceResp, &model, storage, encryption, network, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
|
|||
|
|
@ -4,11 +4,9 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/attr"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
|
||||
sqlserverflex "github.com/mhenselin/terraform-provider-stackitprivatepreview/pkg/sqlserverflexalpha"
|
||||
"github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/conversion"
|
||||
"github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/core"
|
||||
|
|
@ -19,7 +17,7 @@ type sqlserverflexClient interface {
|
|||
GetFlavorsRequestExecute(ctx context.Context, projectId, region string, page, size *int64, sort *sqlserverflex.FlavorSort) (*sqlserverflex.GetFlavorsResponse, error)
|
||||
}
|
||||
|
||||
func mapFields(ctx context.Context, resp *sqlserverflex.GetInstanceResponse, model *Model, flavor *flavorModel, storage *storageModel, encryption *encryptionModel, network *networkModel, region string) error {
|
||||
func mapFields(ctx context.Context, resp *sqlserverflex.GetInstanceResponse, model *Model, storage *storageModel, encryption *encryptionModel, network *networkModel, region string) error {
|
||||
if resp == nil {
|
||||
return fmt.Errorf("response input is nil")
|
||||
}
|
||||
|
|
@ -37,26 +35,6 @@ func mapFields(ctx context.Context, resp *sqlserverflex.GetInstanceResponse, mod
|
|||
return fmt.Errorf("instance id not present")
|
||||
}
|
||||
|
||||
var flavorValues map[string]attr.Value
|
||||
if instance.FlavorId == nil {
|
||||
return fmt.Errorf("instance has no flavor id")
|
||||
}
|
||||
if *instance.FlavorId != flavor.Id.ValueString() {
|
||||
return fmt.Errorf("instance has different flavor id %s - %s", *instance.FlavorId, flavor.Id.ValueString())
|
||||
}
|
||||
|
||||
flavorValues = map[string]attr.Value{
|
||||
"id": flavor.Id,
|
||||
"description": flavor.Description,
|
||||
"cpu": flavor.CPU,
|
||||
"ram": flavor.RAM,
|
||||
"node_type": flavor.NodeType,
|
||||
}
|
||||
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{
|
||||
|
|
@ -163,7 +141,7 @@ func mapFields(ctx context.Context, resp *sqlserverflex.GetInstanceResponse, mod
|
|||
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, instanceId)
|
||||
model.InstanceId = types.StringValue(instanceId)
|
||||
model.Name = types.StringPointerValue(instance.Name)
|
||||
model.Flavor = flavorObject
|
||||
model.FlavorId = types.StringPointerValue(instance.FlavorId)
|
||||
model.Replicas = types.Int64Value(int64(*instance.Replicas))
|
||||
model.Storage = storageObject
|
||||
model.Version = types.StringValue(string(*instance.Version))
|
||||
|
|
@ -182,10 +160,6 @@ func toCreatePayload(model *Model, storage *storageModel, encryption *encryption
|
|||
return nil, fmt.Errorf("nil model")
|
||||
}
|
||||
|
||||
if model.Flavor.IsNull() || model.Flavor.IsUnknown() {
|
||||
return nil, fmt.Errorf("nil flavor")
|
||||
}
|
||||
|
||||
storagePayload := &sqlserverflex.CreateInstanceRequestPayloadGetStorageArgType{}
|
||||
if storage != nil {
|
||||
storagePayload.Class = conversion.StringValueToPointer(storage.Class)
|
||||
|
|
@ -200,16 +174,6 @@ func toCreatePayload(model *Model, storage *storageModel, encryption *encryption
|
|||
encryptionPayload.ServiceAccount = conversion.StringValueToPointer(encryption.ServiceAccount)
|
||||
}
|
||||
|
||||
flavorId := ""
|
||||
if !model.Flavor.IsNull() && !model.Flavor.IsUnknown() {
|
||||
modelValues := model.Flavor.Attributes()
|
||||
if _, ok := modelValues["id"]; !ok {
|
||||
return nil, fmt.Errorf("flavor has not yet been created")
|
||||
}
|
||||
// TODO - how to get rid of that trim?
|
||||
flavorId = strings.Trim(modelValues["id"].String(), "\"")
|
||||
}
|
||||
|
||||
var aclElements []string
|
||||
if network != nil && !network.ACL.IsNull() && !network.ACL.IsUnknown() {
|
||||
aclElements = make([]string, 0, len(network.ACL.Elements()))
|
||||
|
|
@ -227,31 +191,20 @@ func toCreatePayload(model *Model, storage *storageModel, encryption *encryption
|
|||
|
||||
return &sqlserverflex.CreateInstanceRequestPayload{
|
||||
BackupSchedule: conversion.StringValueToPointer(model.BackupSchedule),
|
||||
FlavorId: &flavorId,
|
||||
Encryption: encryptionPayload,
|
||||
FlavorId: conversion.StringValueToPointer(model.FlavorId),
|
||||
Name: conversion.StringValueToPointer(model.Name),
|
||||
Network: networkPayload,
|
||||
RetentionDays: conversion.Int64ValueToPointer(model.RetentionDays),
|
||||
Storage: storagePayload,
|
||||
Version: sqlserverflex.CreateInstanceRequestPayloadGetVersionAttributeType(conversion.StringValueToPointer(model.Version)),
|
||||
Encryption: encryptionPayload,
|
||||
RetentionDays: conversion.Int64ValueToPointer(model.RetentionDays),
|
||||
Network: networkPayload,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func toUpdatePayload(model *Model, storage *storageModel, network *networkModel) (*sqlserverflex.UpdateInstancePartiallyRequestPayload, error) {
|
||||
func toUpdatePartiallyPayload(model *Model, storage *storageModel, network *networkModel) (*sqlserverflex.UpdateInstancePartiallyRequestPayload, error) {
|
||||
if model == nil {
|
||||
return nil, fmt.Errorf("nil model")
|
||||
}
|
||||
if model.Flavor.IsNull() || model.Flavor.IsUnknown() {
|
||||
return nil, fmt.Errorf("nil flavor")
|
||||
}
|
||||
var flavorMdl flavorModel
|
||||
diag := model.Flavor.As(context.Background(), &flavorMdl, basetypes.ObjectAsOptions{
|
||||
UnhandledNullAsEmpty: true,
|
||||
UnhandledUnknownAsEmpty: false,
|
||||
})
|
||||
if diag.HasError() {
|
||||
return nil, fmt.Errorf("flavor conversion error: %v", diag.Errors())
|
||||
}
|
||||
|
||||
storagePayload := &sqlserverflex.UpdateInstanceRequestPayloadGetStorageArgType{}
|
||||
if storage != nil {
|
||||
|
|
@ -277,12 +230,11 @@ func toUpdatePayload(model *Model, storage *storageModel, network *networkModel)
|
|||
return nil, fmt.Errorf("replica count too big: %d", model.Replicas.ValueInt64())
|
||||
}
|
||||
replCount := int32(model.Replicas.ValueInt64()) // nolint:gosec // check is performed above
|
||||
flavorId := flavorMdl.Id.ValueString()
|
||||
return &sqlserverflex.UpdateInstancePartiallyRequestPayload{
|
||||
Network: networkPayload,
|
||||
BackupSchedule: conversion.StringValueToPointer(model.BackupSchedule),
|
||||
FlavorId: &flavorId,
|
||||
FlavorId: conversion.StringValueToPointer(model.FlavorId),
|
||||
Name: conversion.StringValueToPointer(model.Name),
|
||||
Network: networkPayload,
|
||||
Replicas: sqlserverflex.UpdateInstancePartiallyRequestPayloadGetReplicasAttributeType(&replCount),
|
||||
RetentionDays: conversion.Int64ValueToPointer(model.RetentionDays),
|
||||
Storage: storagePayload,
|
||||
|
|
@ -290,152 +242,15 @@ func toUpdatePayload(model *Model, storage *storageModel, network *networkModel)
|
|||
}, nil
|
||||
}
|
||||
|
||||
func getAllFlavors(ctx context.Context, client sqlserverflexClient, projectId, region string) ([]sqlserverflex.ListFlavors, error) {
|
||||
if projectId == "" || region == "" {
|
||||
return nil, fmt.Errorf("listing sqlserverflex flavors: projectId and region are required")
|
||||
}
|
||||
var flavorList []sqlserverflex.ListFlavors
|
||||
|
||||
page := int64(1)
|
||||
size := int64(10)
|
||||
for {
|
||||
sort := sqlserverflex.FLAVORSORT_INDEX_ASC
|
||||
res, err := client.GetFlavorsRequestExecute(ctx, projectId, region, &page, &size, &sort)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("listing sqlserverflex flavors: %w", err)
|
||||
}
|
||||
if res.Flavors == nil {
|
||||
return nil, fmt.Errorf("finding flavors for project %s", projectId)
|
||||
}
|
||||
pagination := res.GetPagination()
|
||||
flavorList = append(flavorList, *res.Flavors...)
|
||||
|
||||
if *pagination.TotalRows == int64(len(flavorList)) {
|
||||
break
|
||||
}
|
||||
page++
|
||||
}
|
||||
return flavorList, nil
|
||||
}
|
||||
|
||||
func loadFlavorId(ctx context.Context, client sqlserverflexClient, model *Model, flavor *flavorModel, storage *storageModel) error {
|
||||
if model == nil {
|
||||
return fmt.Errorf("nil model")
|
||||
}
|
||||
if flavor == nil {
|
||||
return fmt.Errorf("nil flavor")
|
||||
}
|
||||
cpu := conversion.Int64ValueToPointer(flavor.CPU)
|
||||
if cpu == nil {
|
||||
return fmt.Errorf("nil CPU")
|
||||
}
|
||||
ram := conversion.Int64ValueToPointer(flavor.RAM)
|
||||
if ram == nil {
|
||||
return fmt.Errorf("nil RAM")
|
||||
}
|
||||
nodeType := conversion.StringValueToPointer(flavor.NodeType)
|
||||
if nodeType == nil {
|
||||
return fmt.Errorf("nil NodeType")
|
||||
}
|
||||
storageClass := conversion.StringValueToPointer(storage.Class)
|
||||
if storageClass == nil {
|
||||
return fmt.Errorf("nil StorageClass")
|
||||
}
|
||||
storageSize := conversion.Int64ValueToPointer(storage.Size)
|
||||
if storageSize == nil {
|
||||
return fmt.Errorf("nil StorageSize")
|
||||
}
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := model.Region.ValueString()
|
||||
|
||||
flavorList, err := getAllFlavors(ctx, client, projectId, region)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
avl := ""
|
||||
foundFlavorCount := 0
|
||||
for _, f := range flavorList {
|
||||
if f.Id == nil || f.Cpu == nil || f.Memory == nil {
|
||||
continue
|
||||
}
|
||||
if !strings.EqualFold(*f.NodeType, *nodeType) {
|
||||
continue
|
||||
}
|
||||
if *f.Cpu == *cpu && *f.Memory == *ram {
|
||||
var useSc *sqlserverflex.FlavorStorageClassesStorageClass
|
||||
for _, sc := range *f.StorageClasses {
|
||||
if *sc.Class != *storageClass {
|
||||
continue
|
||||
}
|
||||
if *storageSize < *f.MinGB || *storageSize > *f.MaxGB {
|
||||
return fmt.Errorf("storage size %d out of bounds (min: %d - max: %d)", *storageSize, *f.MinGB, *f.MaxGB)
|
||||
}
|
||||
useSc = &sc
|
||||
}
|
||||
if useSc == nil {
|
||||
return fmt.Errorf("no storage class found for %s", *storageClass)
|
||||
}
|
||||
|
||||
flavor.Id = types.StringValue(*f.Id)
|
||||
flavor.Description = types.StringValue(*f.Description)
|
||||
foundFlavorCount++
|
||||
}
|
||||
for _, cls := range *f.StorageClasses {
|
||||
avl = fmt.Sprintf("%s\n- %d CPU, %d GB RAM, storage %s (min: %d - max: %d)", avl, *f.Cpu, *f.Memory, *cls.Class, *f.MinGB, *f.MaxGB)
|
||||
}
|
||||
}
|
||||
if foundFlavorCount > 1 {
|
||||
return fmt.Errorf("multiple flavors found: %d flavors", foundFlavorCount)
|
||||
}
|
||||
if flavor.Id.ValueString() == "" {
|
||||
return fmt.Errorf("couldn't find flavor, available specs are:%s", avl)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getFlavorModelById(ctx context.Context, client sqlserverflexClient, model *Model, flavor *flavorModel) error {
|
||||
if model == nil {
|
||||
return fmt.Errorf("nil model")
|
||||
}
|
||||
if flavor == nil {
|
||||
return fmt.Errorf("nil flavor")
|
||||
}
|
||||
id := conversion.StringValueToPointer(flavor.Id)
|
||||
if id == nil {
|
||||
return fmt.Errorf("nil flavor ID")
|
||||
}
|
||||
|
||||
flavor.Id = types.StringValue("")
|
||||
|
||||
projectId := model.ProjectId.ValueString()
|
||||
region := model.Region.ValueString()
|
||||
|
||||
flavorList, err := getAllFlavors(ctx, client, projectId, region)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
avl := ""
|
||||
for _, f := range flavorList {
|
||||
if f.Id == nil || f.Cpu == nil || f.Memory == nil {
|
||||
continue
|
||||
}
|
||||
if *f.Id == *id {
|
||||
flavor.Id = types.StringValue(*f.Id)
|
||||
flavor.Description = types.StringValue(*f.Description)
|
||||
flavor.CPU = types.Int64Value(*f.Cpu)
|
||||
flavor.RAM = types.Int64Value(*f.Memory)
|
||||
flavor.NodeType = types.StringValue(*f.NodeType)
|
||||
break
|
||||
}
|
||||
avl = fmt.Sprintf("%s\n- %d CPU, %d GB RAM", avl, *f.Cpu, *f.Memory)
|
||||
}
|
||||
if flavor.Id.ValueString() == "" {
|
||||
return fmt.Errorf("couldn't find flavor, available specs are: %s", avl)
|
||||
}
|
||||
|
||||
return nil
|
||||
func toUpdatePayload(model *Model, storage *storageModel, network *networkModel) (*sqlserverflex.UpdateInstanceRequestPayload, error) {
|
||||
return &sqlserverflex.UpdateInstanceRequestPayload{
|
||||
BackupSchedule: nil,
|
||||
FlavorId: nil,
|
||||
Name: nil,
|
||||
Network: nil,
|
||||
Replicas: nil,
|
||||
RetentionDays: nil,
|
||||
Storage: nil,
|
||||
Version: nil,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier"
|
||||
sqlserverflexUtils "github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/services/sqlserverflexalpha/utils"
|
||||
|
|
@ -57,7 +56,7 @@ type Model struct {
|
|||
ProjectId types.String `tfsdk:"project_id"`
|
||||
Name types.String `tfsdk:"name"`
|
||||
BackupSchedule types.String `tfsdk:"backup_schedule"`
|
||||
Flavor types.Object `tfsdk:"flavor"`
|
||||
FlavorId types.String `tfsdk:"flavor_id"`
|
||||
Encryption types.Object `tfsdk:"encryption"`
|
||||
IsDeletable types.Bool `tfsdk:"is_deletable"`
|
||||
Storage types.Object `tfsdk:"storage"`
|
||||
|
|
@ -98,24 +97,6 @@ var networkTypes = map[string]attr.Type{
|
|||
"router_address": basetypes.StringType{},
|
||||
}
|
||||
|
||||
// Struct corresponding to Model.FlavorId
|
||||
type flavorModel struct {
|
||||
Id types.String `tfsdk:"id"`
|
||||
Description types.String `tfsdk:"description"`
|
||||
CPU types.Int64 `tfsdk:"cpu"`
|
||||
RAM types.Int64 `tfsdk:"ram"`
|
||||
NodeType types.String `tfsdk:"node_type"`
|
||||
}
|
||||
|
||||
// Types corresponding to flavorModel
|
||||
var flavorTypes = map[string]attr.Type{
|
||||
"id": basetypes.StringType{},
|
||||
"description": basetypes.StringType{},
|
||||
"cpu": basetypes.Int64Type{},
|
||||
"ram": basetypes.Int64Type{},
|
||||
"node_type": basetypes.StringType{},
|
||||
}
|
||||
|
||||
// Struct corresponding to Model.Storage
|
||||
type storageModel struct {
|
||||
Class types.String `tfsdk:"class"`
|
||||
|
|
@ -198,11 +179,13 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r
|
|||
"instance_id": "ID of the SQLServer Flex instance.",
|
||||
"project_id": "STACKIT project ID to which the instance is associated.",
|
||||
"name": "Instance name.",
|
||||
"access_scope": "The access scope of the instance. (e.g. SNA)",
|
||||
"access_scope": "The access scope of the instance. (SNA | PUBLIC)",
|
||||
"flavor_id": "The flavor ID of the instance.",
|
||||
"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 * * *")`,
|
||||
"region": "The resource region. If not defined, the provider region is used.",
|
||||
"encryption": "The encryption block.",
|
||||
"replicas": "The number of replicas of the SQLServer Flex instance.",
|
||||
"network": "The network block.",
|
||||
"keyring_id": "STACKIT KMS - KeyRing ID of the encryption key to use.",
|
||||
"key_id": "STACKIT KMS - Key ID of the encryption key to use.",
|
||||
|
|
@ -271,78 +254,12 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r
|
|||
boolplanmodifier.UseStateForUnknown(),
|
||||
},
|
||||
},
|
||||
// TODO - make it either flavor_id or ram, cpu and node_type
|
||||
"flavor": schema.SingleNestedAttribute{
|
||||
PlanModifiers: []planmodifier.Object{
|
||||
objectplanmodifier.RequiresReplace(),
|
||||
objectplanmodifier.UseStateForUnknown(),
|
||||
"flavor_id": schema.StringAttribute{
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
},
|
||||
Required: true,
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
},
|
||||
},
|
||||
"description": schema.StringAttribute{
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
},
|
||||
},
|
||||
"node_type": schema.StringAttribute{
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
},
|
||||
Validators: []validator.String{
|
||||
stringvalidator.ConflictsWith([]path.Expression{
|
||||
path.MatchRelative().AtParent().AtName("id"),
|
||||
}...),
|
||||
stringvalidator.OneOfCaseInsensitive(validNodeTypes...),
|
||||
stringvalidator.AlsoRequires([]path.Expression{
|
||||
path.MatchRelative().AtParent().AtName("cpu"),
|
||||
path.MatchRelative().AtParent().AtName("ram"),
|
||||
}...),
|
||||
},
|
||||
},
|
||||
"cpu": schema.Int64Attribute{
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.Int64{
|
||||
int64planmodifier.RequiresReplace(),
|
||||
int64planmodifier.UseStateForUnknown(),
|
||||
},
|
||||
Validators: []validator.Int64{
|
||||
int64validator.ConflictsWith([]path.Expression{
|
||||
path.MatchRelative().AtParent().AtName("id"),
|
||||
}...),
|
||||
int64validator.AlsoRequires([]path.Expression{
|
||||
path.MatchRelative().AtParent().AtName("node_type"),
|
||||
path.MatchRelative().AtParent().AtName("ram"),
|
||||
}...),
|
||||
},
|
||||
},
|
||||
"ram": schema.Int64Attribute{
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.Int64{
|
||||
int64planmodifier.RequiresReplace(),
|
||||
int64planmodifier.UseStateForUnknown(),
|
||||
},
|
||||
Validators: []validator.Int64{
|
||||
int64validator.ConflictsWith([]path.Expression{
|
||||
path.MatchRelative().AtParent().AtName("id"),
|
||||
}...),
|
||||
int64validator.AlsoRequires([]path.Expression{
|
||||
path.MatchRelative().AtParent().AtName("node_type"),
|
||||
path.MatchRelative().AtParent().AtName("cpu"),
|
||||
}...),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"replicas": schema.Int64Attribute{
|
||||
Computed: true,
|
||||
|
|
@ -506,7 +423,6 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r
|
|||
|
||||
// Create creates the resource and sets the initial Terraform state.
|
||||
func (r *instanceResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { // nolint:gocritic // function signature required by Terraform
|
||||
// Retrieve values from plan
|
||||
var model Model
|
||||
diags := req.Plan.Get(ctx, &model)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
|
|
@ -548,37 +464,6 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques
|
|||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
if flavor.Id.IsNull() || flavor.Id.IsUnknown() {
|
||||
err := loadFlavorId(ctx, r.client, &model, flavor, storage)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError(err.Error(), err.Error())
|
||||
return
|
||||
}
|
||||
flavorValues := map[string]attr.Value{
|
||||
"id": flavor.Id,
|
||||
"description": flavor.Description,
|
||||
"cpu": flavor.CPU,
|
||||
"ram": flavor.RAM,
|
||||
"node_type": flavor.NodeType,
|
||||
}
|
||||
var flavorObject basetypes.ObjectValue
|
||||
flavorObject, diags = types.ObjectValue(flavorTypes, flavorValues)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if diags.HasError() {
|
||||
return
|
||||
}
|
||||
model.Flavor = flavorObject
|
||||
}
|
||||
|
||||
// Generate API request body from model
|
||||
payload, err := toCreatePayload(&model, storage, encryption, network)
|
||||
if err != nil {
|
||||
|
|
@ -596,6 +481,7 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques
|
|||
|
||||
instanceId := *createResp.Id
|
||||
utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{
|
||||
"id": utils.BuildInternalTerraformId(projectId, region, instanceId),
|
||||
"instance_id": instanceId,
|
||||
})
|
||||
if resp.Diagnostics.HasError() {
|
||||
|
|
@ -615,25 +501,8 @@ func (r *instanceResource) Create(ctx context.Context, req resource.CreateReques
|
|||
return
|
||||
}
|
||||
|
||||
if *waitResp.FlavorId != flavor.Id.ValueString() {
|
||||
core.LogAndAddError(
|
||||
ctx,
|
||||
&resp.Diagnostics,
|
||||
"Error creating instance",
|
||||
fmt.Sprintf("Instance creation waiting: returned flavor id differs (expected: %s, current: %s)", flavor.Id.ValueString(), *waitResp.FlavorId),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if flavor.CPU.IsNull() || flavor.CPU.IsUnknown() {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", "Instance creation waiting: flavor cpu is null or unknown")
|
||||
}
|
||||
if flavor.RAM.IsNull() || flavor.RAM.IsUnknown() {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", "Instance creation waiting: flavor ram is null or unknown")
|
||||
}
|
||||
|
||||
// Map response body to schema
|
||||
err = mapFields(ctx, waitResp, &model, flavor, storage, encryption, network, region)
|
||||
err = mapFields(ctx, waitResp, &model, storage, encryption, network, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
@ -672,27 +541,6 @@ 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
|
||||
}
|
||||
|
||||
err := getFlavorModelById(ctx, r.client, &model, flavor)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError(err.Error(), err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
diags = model.Flavor.As(ctx, flavor, basetypes.ObjectAsOptions{})
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var storage = &storageModel{}
|
||||
if !model.Storage.IsNull() && !model.Storage.IsUnknown() {
|
||||
diags = model.Storage.As(ctx, storage, basetypes.ObjectAsOptions{})
|
||||
|
|
@ -734,7 +582,7 @@ func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r
|
|||
ctx = core.LogResponse(ctx)
|
||||
|
||||
// Map response body to schema
|
||||
err = mapFields(ctx, instanceResp, &model, flavor, storage, encryption, network, region)
|
||||
err = mapFields(ctx, instanceResp, &model, storage, encryption, network, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
@ -795,37 +643,6 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques
|
|||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
if flavor.Id.IsNull() || flavor.Id.IsUnknown() {
|
||||
err := loadFlavorId(ctx, r.client, &model, flavor, storage)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError(err.Error(), err.Error())
|
||||
return
|
||||
}
|
||||
flavorValues := map[string]attr.Value{
|
||||
"id": flavor.Id,
|
||||
"description": flavor.Description,
|
||||
"cpu": flavor.CPU,
|
||||
"ram": flavor.RAM,
|
||||
"node_type": flavor.NodeType,
|
||||
}
|
||||
var flavorObject basetypes.ObjectValue
|
||||
flavorObject, diags = types.ObjectValue(flavorTypes, flavorValues)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if diags.HasError() {
|
||||
return
|
||||
}
|
||||
model.Flavor = flavorObject
|
||||
}
|
||||
|
||||
// Generate API request body from model
|
||||
payload, err := toUpdatePayload(&model, storage, network)
|
||||
if err != nil {
|
||||
|
|
@ -833,7 +650,8 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques
|
|||
return
|
||||
}
|
||||
// Update existing instance
|
||||
err = r.client.UpdateInstancePartiallyRequest(ctx, projectId, region, instanceId).UpdateInstancePartiallyRequestPayload(*payload).Execute()
|
||||
err = r.client.UpdateInstanceRequest(ctx, projectId, region, instanceId).UpdateInstanceRequestPayload(*payload).Execute()
|
||||
// err = r.client.UpdateInstancePartiallyRequest(ctx, projectId, region, instanceId).UpdateInstancePartiallyRequestPayload(*payload).Execute()
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", err.Error())
|
||||
return
|
||||
|
|
@ -848,7 +666,7 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques
|
|||
}
|
||||
|
||||
// Map response body to schema
|
||||
err = mapFields(ctx, waitResp, &model, flavor, storage, encryption, network, region)
|
||||
err = mapFields(ctx, waitResp, &model, storage, encryption, network, region)
|
||||
if err != nil {
|
||||
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Processing API payload: %v", err))
|
||||
return
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ func TestMapFields(t *testing.T) {
|
|||
description string
|
||||
state Model
|
||||
input *sqlserverflex.GetInstanceResponse
|
||||
flavor *flavorModel
|
||||
storage *storageModel
|
||||
encryption *encryptionModel
|
||||
network *networkModel
|
||||
|
|
@ -53,13 +52,6 @@ func TestMapFields(t *testing.T) {
|
|||
Edition: types.StringValue("edition 1"),
|
||||
Status: types.StringValue("status"),
|
||||
IsDeletable: types.BoolValue(true),
|
||||
Flavor: types.ObjectValueMust(flavorTypes, map[string]attr.Value{
|
||||
"id": types.StringValue("flavor_id"),
|
||||
"description": types.StringNull(),
|
||||
"cpu": types.Int64Null(),
|
||||
"ram": types.Int64Null(),
|
||||
"node_type": types.StringNull(),
|
||||
}),
|
||||
},
|
||||
&sqlserverflex.GetInstanceResponse{
|
||||
FlavorId: utils.Ptr("flavor_id"),
|
||||
|
|
@ -70,9 +62,6 @@ func TestMapFields(t *testing.T) {
|
|||
Status: sqlserverflex.GetInstanceResponseGetStatusAttributeType(utils.Ptr("status")),
|
||||
IsDeletable: utils.Ptr(true),
|
||||
},
|
||||
&flavorModel{
|
||||
Id: types.StringValue("flavor_id"),
|
||||
},
|
||||
&storageModel{},
|
||||
&encryptionModel{},
|
||||
&networkModel{
|
||||
|
|
@ -85,14 +74,7 @@ func TestMapFields(t *testing.T) {
|
|||
ProjectId: types.StringValue("pid"),
|
||||
Name: types.StringNull(),
|
||||
BackupSchedule: types.StringNull(),
|
||||
Flavor: types.ObjectValueMust(flavorTypes, map[string]attr.Value{
|
||||
"id": types.StringValue("flavor_id"),
|
||||
"description": types.StringNull(),
|
||||
"cpu": types.Int64Null(),
|
||||
"ram": types.Int64Null(),
|
||||
"node_type": types.StringNull(),
|
||||
}),
|
||||
Replicas: types.Int64Value(1),
|
||||
Replicas: types.Int64Value(1),
|
||||
Storage: types.ObjectValueMust(storageTypes, map[string]attr.Value{
|
||||
"class": types.StringNull(),
|
||||
"size": types.Int64Null(),
|
||||
|
|
@ -151,13 +133,6 @@ func TestMapFields(t *testing.T) {
|
|||
RouterAddress: nil,
|
||||
},
|
||||
},
|
||||
&flavorModel{
|
||||
Id: basetypes.NewStringValue("flavor_id"),
|
||||
Description: basetypes.NewStringValue("description"),
|
||||
CPU: basetypes.NewInt64Value(12),
|
||||
RAM: basetypes.NewInt64Value(34),
|
||||
NodeType: basetypes.NewStringValue("node_type"),
|
||||
},
|
||||
&storageModel{},
|
||||
&encryptionModel{},
|
||||
&networkModel{
|
||||
|
|
@ -174,14 +149,7 @@ func TestMapFields(t *testing.T) {
|
|||
ProjectId: types.StringValue("pid"),
|
||||
Name: types.StringValue("name"),
|
||||
BackupSchedule: types.StringValue("schedule"),
|
||||
Flavor: types.ObjectValueMust(flavorTypes, map[string]attr.Value{
|
||||
"id": types.StringValue("flavor_id"),
|
||||
"description": types.StringValue("description"),
|
||||
"cpu": types.Int64Value(12),
|
||||
"ram": types.Int64Value(34),
|
||||
"node_type": types.StringValue("node_type"),
|
||||
}),
|
||||
Replicas: types.Int64Value(56),
|
||||
Replicas: types.Int64Value(56),
|
||||
Storage: types.ObjectValueMust(storageTypes, map[string]attr.Value{
|
||||
"class": types.StringValue("class"),
|
||||
"size": types.Int64Value(78),
|
||||
|
|
@ -380,7 +348,7 @@ func TestMapFields(t *testing.T) {
|
|||
}
|
||||
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.encryption, tt.network, tt.region)
|
||||
err := mapFields(context.Background(), tt.input, &tt.state, tt.storage, tt.encryption, tt.network, tt.region)
|
||||
if !tt.isValid && err == nil {
|
||||
t.Fatalf("Should have failed")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import (
|
|||
"github.com/hashicorp/terraform-plugin-testing/terraform"
|
||||
"github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/core"
|
||||
"github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/testutil"
|
||||
core_config "github.com/stackitcloud/stackit-sdk-go/core/config"
|
||||
coreconfig "github.com/stackitcloud/stackit-sdk-go/core/config"
|
||||
"github.com/stackitcloud/stackit-sdk-go/core/utils"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex"
|
||||
"github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/wait"
|
||||
|
|
@ -28,6 +28,7 @@ var (
|
|||
//go:embed testdata/resource-min.tf
|
||||
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))),
|
||||
|
|
@ -440,7 +441,7 @@ func testAccChecksqlserverflexDestroy(s *terraform.State) error {
|
|||
client, err = sqlserverflex.NewAPIClient()
|
||||
} else {
|
||||
client, err = sqlserverflex.NewAPIClient(
|
||||
core_config.WithEndpoint(testutil.SQLServerFlexCustomEndpoint),
|
||||
coreconfig.WithEndpoint(testutil.SQLServerFlexCustomEndpoint),
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -18,14 +18,15 @@ import (
|
|||
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
|
||||
"github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/core"
|
||||
"github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/features"
|
||||
sdkauth "github.com/stackitcloud/stackit-sdk-go/core/auth"
|
||||
"github.com/stackitcloud/stackit-sdk-go/core/config"
|
||||
|
||||
postgresFlexAlphaDatabase "github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/database"
|
||||
"github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/flavor"
|
||||
postgresFlexAlphaInstance "github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/instance"
|
||||
postgresFlexAlphaUser "github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/user"
|
||||
sqlserverFlexAlphaFlavor "github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/services/sqlserverflexalpha/flavor"
|
||||
sqlServerFlexAlphaInstance "github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/services/sqlserverflexalpha/instance"
|
||||
sqlserverFlexAlphaUser "github.com/mhenselin/terraform-provider-stackitprivatepreview/stackit/internal/services/sqlserverflexalpha/user"
|
||||
sdkauth "github.com/stackitcloud/stackit-sdk-go/core/auth"
|
||||
"github.com/stackitcloud/stackit-sdk-go/core/config"
|
||||
)
|
||||
|
||||
// Ensure the implementation satisfies the expected interfaces
|
||||
|
|
@ -488,9 +489,13 @@ func (p *Provider) Configure(ctx context.Context, req provider.ConfigureRequest,
|
|||
// DataSources defines the data sources implemented in the provider.
|
||||
func (p *Provider) DataSources(_ context.Context) []func() datasource.DataSource {
|
||||
return []func() datasource.DataSource{
|
||||
postgresFlexAlphaFlavor.NewFlavorDataSource,
|
||||
//postgresFlexAlphaFlavor.NewFlavorListDataSource,
|
||||
postgresFlexAlphaDatabase.NewDatabaseDataSource,
|
||||
postgresFlexAlphaInstance.NewInstanceDataSource,
|
||||
postgresFlexAlphaUser.NewUserDataSource,
|
||||
|
||||
sqlserverFlexAlphaFlavor.NewFlavorDataSource,
|
||||
sqlServerFlexAlphaInstance.NewInstanceDataSource,
|
||||
sqlserverFlexAlphaUser.NewUserDataSource,
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue