chore: changed and refactored providers #36

Merged
marcel.henselin merged 31 commits from feat/alpha_user_database into alpha 2026-02-10 08:10:03 +00:00
70 changed files with 6250 additions and 2608 deletions

View file

@ -1,6 +1,7 @@
package build package build
import ( import (
"bufio"
"bytes" "bytes"
"errors" "errors"
"fmt" "fmt"
@ -509,7 +510,7 @@ func generateServiceFiles(rootDir, generatorDir string) error {
oasFile := path.Join(generatorDir, "oas", fmt.Sprintf("%s%s.json", service.Name(), svcVersion.Name())) oasFile := path.Join(generatorDir, "oas", fmt.Sprintf("%s%s.json", service.Name(), svcVersion.Name()))
if _, oasErr := os.Stat(oasFile); os.IsNotExist(oasErr) { if _, oasErr := os.Stat(oasFile); os.IsNotExist(oasErr) {
slog.Warn(" coulc not find matching oas", "svc", service.Name(), "version", svcVersion.Name()) slog.Warn(" could not find matching oas", "svc", service.Name(), "version", svcVersion.Name())
continue continue
} }
@ -648,6 +649,15 @@ func generateServiceFiles(rootDir, generatorDir string) error {
return err return err
} }
} }
tfAnoErr := handleTfTagForDatasourceFile(
path.Join(tgtFolder, fmt.Sprintf("%s_data_source_gen.go", resource)),
scName,
resource,
)
if tfAnoErr != nil {
return tfAnoErr
}
} }
} }
} }
@ -655,6 +665,70 @@ func generateServiceFiles(rootDir, generatorDir string) error {
return nil return nil
} }
// handleTfTagForDatasourceFile replaces existing "id" with "stf_original_api_id"
func handleTfTagForDatasourceFile(filePath, service, resource string) error {
slog.Info(" handle terraform tag for datasource", "service", service, "resource", resource)
if !fileExists(filePath) {
slog.Warn(" could not find file, skipping", "path", filePath)
return nil
}
f, err := os.Open(filePath)
if err != nil {
return err
}
defer f.Close()
tmp, err := os.CreateTemp("", "replace-*")
if err != nil {
return err
}
defer tmp.Close()
sc := bufio.NewScanner(f)
for sc.Scan() {
resLine, err := handleLine(sc.Text())
if err != nil {
return err
}
if _, err := io.WriteString(tmp, resLine+"\n"); err != nil {
return err
}
}
if scErr := sc.Err(); scErr != nil {
return scErr
}
if err := tmp.Close(); err != nil {
return err
}
if err := f.Close(); err != nil {
return err
}
if err := os.Rename(tmp.Name(), filePath); err != nil {
log.Fatal(err)
}
return nil
}
func handleLine(line string) (string, error) {
schemaRegex := regexp.MustCompile(`(\s+")(id)(": schema.[a-zA-Z0-9]+Attribute{)`)
schemaMatches := schemaRegex.FindAllStringSubmatch(line, -1)
if schemaMatches != nil {
return fmt.Sprintf("%stf_original_api_id%s", schemaMatches[0][1], schemaMatches[0][3]), nil
}
modelRegex := regexp.MustCompile(`(\s+Id\s+types.[a-zA-Z0-9]+\s+.tfsdk:")(id)(".)`)
modelMatches := modelRegex.FindAllStringSubmatch(line, -1)
if modelMatches != nil {
return fmt.Sprintf("%stf_original_api_id%s", modelMatches[0][1], modelMatches[0][3]), nil
}
return line, nil
}
func checkCommands(commands []string) error { func checkCommands(commands []string) error {
for _, commandName := range commands { for _, commandName := range commands {
if !commandExists(commandName) { if !commandExists(commandName) {

View file

@ -38,12 +38,12 @@ Read-Only:
- `cpu` (Number) The cpu count of the instance. - `cpu` (Number) The cpu count of the instance.
- `description` (String) The flavor description. - `description` (String) The flavor description.
- `id` (String) The id of the instance flavor.
- `max_gb` (Number) maximum storage which can be ordered for the flavor in Gigabyte. - `max_gb` (Number) maximum storage which can be ordered for the flavor in Gigabyte.
- `memory` (Number) The memory of the instance in Gibibyte. - `memory` (Number) The memory of the instance in Gibibyte.
- `min_gb` (Number) minimum storage which is required to order in Gigabyte. - `min_gb` (Number) minimum storage which is required to order in Gigabyte.
- `node_type` (String) defines the nodeType it can be either single or replica - `node_type` (String) defines the nodeType it can be either single or replica
- `storage_classes` (Attributes List) maximum storage which can be ordered for the flavor in Gigabyte. (see [below for nested schema](#nestedatt--flavors--storage_classes)) - `storage_classes` (Attributes List) maximum storage which can be ordered for the flavor in Gigabyte. (see [below for nested schema](#nestedatt--flavors--storage_classes))
- `tf_original_api_id` (String) The id of the instance flavor.
<a id="nestedatt--flavors--storage_classes"></a> <a id="nestedatt--flavors--storage_classes"></a>
### Nested Schema for `flavors.storage_classes` ### Nested Schema for `flavors.storage_classes`

View file

@ -37,7 +37,6 @@ data "stackitprivatepreview_postgresflexalpha_instance" "example" {
⚠︝ **Note:** This feature is in private preview. Supplying this object is only permitted for enabled accounts. If your account does not have access, the request will be rejected. (see [below for nested schema](#nestedatt--encryption)) ⚠︝ **Note:** This feature is in private preview. Supplying this object is only permitted for enabled accounts. If your account does not have access, the request will be rejected. (see [below for nested schema](#nestedatt--encryption))
- `flavor_id` (String) The id of the instance flavor. - `flavor_id` (String) The id of the instance flavor.
- `id` (String) The ID of the instance.
- `is_deletable` (Boolean) Whether the instance can be deleted or not. - `is_deletable` (Boolean) Whether the instance can be deleted or not.
- `name` (String) The name of the instance. - `name` (String) The name of the instance.
- `network` (Attributes) The access configuration of the instance (see [below for nested schema](#nestedatt--network)) - `network` (Attributes) The access configuration of the instance (see [below for nested schema](#nestedatt--network))
@ -45,6 +44,7 @@ data "stackitprivatepreview_postgresflexalpha_instance" "example" {
- `retention_days` (Number) How long backups are retained. The value can only be between 32 and 365 days. - `retention_days` (Number) How long backups are retained. The value can only be between 32 and 365 days.
- `status` (String) The current status of the instance. - `status` (String) The current status of the instance.
- `storage` (Attributes) The object containing information about the storage size and class. (see [below for nested schema](#nestedatt--storage)) - `storage` (Attributes) The object containing information about the storage size and class. (see [below for nested schema](#nestedatt--storage))
- `tf_original_api_id` (String) The ID of the instance.
- `version` (String) The Postgres version used for the instance. See [Versions Endpoint](/documentation/postgres-flex-service/version/v3alpha1#tag/Version) for supported version parameters. - `version` (String) The Postgres version used for the instance. See [Versions Endpoint](/documentation/postgres-flex-service/version/v3alpha1#tag/Version) for supported version parameters.
<a id="nestedatt--connection_info"></a> <a id="nestedatt--connection_info"></a>

View file

@ -26,6 +26,6 @@ description: |-
- `collation_name` (String) The collation of the database. This database collation should match the *collation_name* of one of the collations given by the **Get database collation list** endpoint. - `collation_name` (String) The collation of the database. This database collation should match the *collation_name* of one of the collations given by the **Get database collation list** endpoint.
- `compatibility_level` (Number) CompatibilityLevel of the Database. - `compatibility_level` (Number) CompatibilityLevel of the Database.
- `id` (Number) The id of the database.
- `name` (String) The name of the database. - `name` (String) The name of the database.
- `owner` (String) The owner of the database. - `owner` (String) The owner of the database.
- `tf_original_api_id` (Number) The id of the database.

View file

@ -34,7 +34,6 @@ data "stackitprivatepreview_sqlserverflexalpha_instance" "example" {
- `edition` (String) Edition of the MSSQL server instance - `edition` (String) Edition of the MSSQL server instance
- `encryption` (Attributes) this defines which key to use for storage encryption (see [below for nested schema](#nestedatt--encryption)) - `encryption` (Attributes) this defines which key to use for storage encryption (see [below for nested schema](#nestedatt--encryption))
- `flavor_id` (String) The id of the instance flavor. - `flavor_id` (String) The id of the instance flavor.
- `id` (String) The ID of the instance.
- `is_deletable` (Boolean) Whether the instance can be deleted or not. - `is_deletable` (Boolean) Whether the instance can be deleted or not.
- `name` (String) The name of the instance. - `name` (String) The name of the instance.
- `network` (Attributes) The access configuration of the instance (see [below for nested schema](#nestedatt--network)) - `network` (Attributes) The access configuration of the instance (see [below for nested schema](#nestedatt--network))
@ -42,6 +41,7 @@ data "stackitprivatepreview_sqlserverflexalpha_instance" "example" {
- `retention_days` (Number) The days for how long the backup files should be stored before cleaned up. 30 to 365 - `retention_days` (Number) The days for how long the backup files should be stored before cleaned up. 30 to 365
- `status` (String) - `status` (String)
- `storage` (Attributes) The object containing information about the storage size and class. (see [below for nested schema](#nestedatt--storage)) - `storage` (Attributes) The object containing information about the storage size and class. (see [below for nested schema](#nestedatt--storage))
- `tf_original_api_id` (String) The ID of the instance.
- `version` (String) The sqlserver version used for the instance. - `version` (String) The sqlserver version used for the instance.
<a id="nestedatt--encryption"></a> <a id="nestedatt--encryption"></a>

View file

@ -34,7 +34,6 @@ data "stackitprivatepreview_sqlserverflexbeta_instance" "example" {
- `edition` (String) Edition of the MSSQL server instance - `edition` (String) Edition of the MSSQL server instance
- `encryption` (Attributes) this defines which key to use for storage encryption (see [below for nested schema](#nestedatt--encryption)) - `encryption` (Attributes) this defines which key to use for storage encryption (see [below for nested schema](#nestedatt--encryption))
- `flavor_id` (String) The id of the instance flavor. - `flavor_id` (String) The id of the instance flavor.
- `id` (String) The ID of the instance.
- `is_deletable` (Boolean) Whether the instance can be deleted or not. - `is_deletable` (Boolean) Whether the instance can be deleted or not.
- `name` (String) The name of the instance. - `name` (String) The name of the instance.
- `network` (Attributes) The access configuration of the instance (see [below for nested schema](#nestedatt--network)) - `network` (Attributes) The access configuration of the instance (see [below for nested schema](#nestedatt--network))
@ -42,6 +41,7 @@ data "stackitprivatepreview_sqlserverflexbeta_instance" "example" {
- `retention_days` (Number) The days for how long the backup files should be stored before cleaned up. 30 to 365 - `retention_days` (Number) The days for how long the backup files should be stored before cleaned up. 30 to 365
- `status` (String) - `status` (String)
- `storage` (Attributes) The object containing information about the storage size and class. (see [below for nested schema](#nestedatt--storage)) - `storage` (Attributes) The object containing information about the storage size and class. (see [below for nested schema](#nestedatt--storage))
- `tf_original_api_id` (String) The ID of the instance.
- `version` (String) The sqlserver version used for the instance. - `version` (String) The sqlserver version used for the instance.
<a id="nestedatt--encryption"></a> <a id="nestedatt--encryption"></a>

View file

@ -5,18 +5,17 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/conversion"
postgresflexUtils "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/utils"
"github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-log/tflog"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/core"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/utils"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/validate"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/conversion"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/core"
postgresflexalpha2 "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/database/datasources_gen"
postgresflexUtils "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/utils"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/utils"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/postgresflexalpha" "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/postgresflexalpha"
) )
@ -30,6 +29,12 @@ func NewDatabaseDataSource() datasource.DataSource {
return &databaseDataSource{} return &databaseDataSource{}
} }
// dataSourceModel maps the data source schema data.
type dataSourceModel struct {
postgresflexalpha2.DatabaseModel
TerraformID types.String `tfsdk:"id"`
}
// databaseDataSource is the data source implementation. // databaseDataSource is the data source implementation.
type databaseDataSource struct { type databaseDataSource struct {
client *postgresflexalpha.APIClient client *postgresflexalpha.APIClient
@ -66,132 +71,46 @@ func (r *databaseDataSource) Configure(
} }
// Schema defines the schema for the data source. // Schema defines the schema for the data source.
func (r *databaseDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { func (r *databaseDataSource) Schema(ctx context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
descriptions := map[string]string{
"main": "Postgres Flex database resource schema. Must have a `region` specified in the provider configuration.", s := postgresflexalpha2.DatabaseDataSourceSchema(ctx)
"id": "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`instance_id`,`database_id`\".", s.Attributes["id"] = schema.StringAttribute{
"database_id": "Database ID.", Description: "Terraform's internal resource ID. It is structured as \\\"`project_id`,`region`,`instance_id`," +
"instance_id": "ID of the Postgres Flex instance.", "`database_id`\\\".\",",
"project_id": "STACKIT project ID to which the instance is associated.", Computed: true,
"name": "Database name.",
"owner": "Username of the database owner.",
"region": "The resource region. If not defined, the provider region is used.",
} }
resp.Schema = schema.Schema{ resp.Schema = s
Description: descriptions["main"],
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Description: descriptions["id"],
Computed: true,
},
"database_id": schema.Int64Attribute{
Description: descriptions["database_id"],
Optional: true,
Computed: true,
},
"instance_id": schema.StringAttribute{
Description: descriptions["instance_id"],
Required: true,
Validators: []validator.String{
validate.UUID(),
validate.NoSeparator(),
},
},
"project_id": schema.StringAttribute{
Description: descriptions["project_id"],
Required: true,
Validators: []validator.String{
validate.UUID(),
validate.NoSeparator(),
},
},
"name": schema.StringAttribute{
Description: descriptions["name"],
Optional: true,
Computed: true,
Validators: []validator.String{
stringvalidator.LengthAtLeast(1),
},
},
"owner": schema.StringAttribute{
Description: descriptions["owner"],
Computed: true,
},
"region": schema.StringAttribute{
// the region cannot be found, so it has to be passed
Optional: true,
Description: descriptions["region"],
},
},
}
} }
// Read refreshes the Terraform state with the latest data. // Read fetches the data for the data source.
func (r *databaseDataSource) Read( func (r *databaseDataSource) Read(
ctx context.Context, ctx context.Context,
req datasource.ReadRequest, req datasource.ReadRequest,
resp *datasource.ReadResponse, resp *datasource.ReadResponse,
) { // nolint:gocritic // function signature required by Terraform ) { // nolint:gocritic // function signature required by Terraform
var model Model var model dataSourceModel
diags := req.Config.Get(ctx, &model) diags := req.Config.Get(ctx, &model)
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
} }
// validation for exactly one of database_id or name
isIdSet := !model.DatabaseId.IsNull() && !model.DatabaseId.IsUnknown()
isNameSet := !model.Name.IsNull() && !model.Name.IsUnknown()
if (isIdSet && isNameSet) || (!isIdSet && !isNameSet) {
core.LogAndAddError(
ctx, &resp.Diagnostics,
"Invalid configuration", "Exactly one of 'database_id' or 'name' must be specified.",
)
return
}
ctx = core.InitProviderContext(ctx) ctx = core.InitProviderContext(ctx)
projectId := model.ProjectId.ValueString() projectId := model.ProjectId.ValueString()
instanceId := model.InstanceId.ValueString() instanceId := model.InstanceId.ValueString()
databaseId := model.DatabaseId.ValueInt64()
region := r.providerData.GetRegionWithOverride(model.Region) region := r.providerData.GetRegionWithOverride(model.Region)
ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "instance_id", instanceId)
ctx = tflog.SetField(ctx, "database_id", databaseId)
ctx = tflog.SetField(ctx, "region", region) ctx = tflog.SetField(ctx, "region", region)
var databaseResp *postgresflexalpha.ListDatabase databaseResp, err := r.getDatabaseByNameOrID(ctx, &model, projectId, region, instanceId, &resp.Diagnostics)
var err error if resp.Diagnostics.HasError() {
return
if isIdSet {
databaseId := model.DatabaseId.ValueInt64()
ctx = tflog.SetField(ctx, "database_id", databaseId)
databaseResp, err = getDatabaseById(ctx, r.client, projectId, region, instanceId, databaseId)
} else {
databaseName := model.Name.ValueString()
ctx = tflog.SetField(ctx, "name", databaseName)
databaseResp, err = getDatabaseByName(ctx, r.client, projectId, region, instanceId, databaseName)
} }
if err != nil { if err != nil {
utils.LogError( handleReadError(ctx, &resp.Diagnostics, err, projectId, instanceId)
ctx,
&resp.Diagnostics,
err,
"Reading database",
fmt.Sprintf(
"Database with ID %q or instance with ID %q does not exist in project %q.",
databaseId,
instanceId,
projectId,
),
map[int]string{
http.StatusForbidden: fmt.Sprintf("Project with ID %q not found or forbidden access", projectId),
},
)
resp.State.RemoveResource(ctx) resp.State.RemoveResource(ctx)
return return
} }
@ -218,3 +137,60 @@ func (r *databaseDataSource) Read(
} }
tflog.Info(ctx, "Postgres Flex database read") tflog.Info(ctx, "Postgres Flex database read")
} }
// getDatabaseByNameOrID retrieves a single database by ensuring either a unique ID or name is provided.
func (r *databaseDataSource) getDatabaseByNameOrID(
ctx context.Context,
model *dataSourceModel,
projectId, region, instanceId string,
diags *diag.Diagnostics,
) (*postgresflexalpha.ListDatabase, error) {
isIdSet := !model.DatabaseId.IsNull() && !model.DatabaseId.IsUnknown()
isNameSet := !model.Name.IsNull() && !model.Name.IsUnknown()
if (isIdSet && isNameSet) || (!isIdSet && !isNameSet) {
diags.AddError(
"Invalid configuration",
"Exactly one of 'id' or 'name' must be specified.",
)
return nil, nil
}
if isIdSet {
databaseId := model.DatabaseId.ValueInt64()
ctx = tflog.SetField(ctx, "database_id", databaseId)
return getDatabaseById(ctx, r.client, projectId, region, instanceId, databaseId)
}
databaseName := model.Name.ValueString()
ctx = tflog.SetField(ctx, "name", databaseName)
return getDatabaseByName(ctx, r.client, projectId, region, instanceId, databaseName)
}
// handleReadError centralizes API error handling for the Read operation.
func handleReadError(ctx context.Context, diags *diag.Diagnostics, err error, projectId, instanceId string) {
utils.LogError(
ctx,
diags,
err,
"Reading database",
fmt.Sprintf(
"Could not retrieve database for instance %q in project %q.",
instanceId,
projectId,
),
map[int]string{
http.StatusBadRequest: fmt.Sprintf(
"Invalid request parameters for project %q and instance %q.",
projectId,
instanceId,
),
http.StatusNotFound: fmt.Sprintf(
"Database, instance %q, or project %q not found.",
instanceId,
projectId,
),
http.StatusForbidden: fmt.Sprintf("Forbidden access to project %q.", projectId),
},
)
}

View file

@ -0,0 +1,69 @@
// Code generated by terraform-plugin-framework-generator DO NOT EDIT.
package postgresflexalpha
import (
"context"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
)
func DatabaseDataSourceSchema(ctx context.Context) schema.Schema {
return schema.Schema{
Attributes: map[string]schema.Attribute{
"database_id": schema.Int64Attribute{
Required: true,
Description: "The ID of the database.",
MarkdownDescription: "The ID of the database.",
},
"tf_original_api_id": schema.Int64Attribute{
Computed: true,
Description: "The id of the database.",
MarkdownDescription: "The id of the database.",
},
"instance_id": schema.StringAttribute{
Required: true,
Description: "The ID of the instance.",
MarkdownDescription: "The ID of the instance.",
},
"name": schema.StringAttribute{
Computed: true,
Description: "The name of the database.",
MarkdownDescription: "The name of the database.",
},
"owner": schema.StringAttribute{
Computed: true,
Description: "The owner of the database.",
MarkdownDescription: "The owner of the database.",
},
"project_id": schema.StringAttribute{
Required: true,
Description: "The STACKIT project ID.",
MarkdownDescription: "The STACKIT project ID.",
},
"region": schema.StringAttribute{
Required: true,
Description: "The region which should be addressed",
MarkdownDescription: "The region which should be addressed",
Validators: []validator.String{
stringvalidator.OneOf(
"eu01",
),
},
},
},
}
}
type DatabaseModel struct {
DatabaseId types.Int64 `tfsdk:"database_id"`
Id types.Int64 `tfsdk:"tf_original_api_id"`
InstanceId types.String `tfsdk:"instance_id"`
Name types.String `tfsdk:"name"`
Owner types.String `tfsdk:"owner"`
ProjectId types.String `tfsdk:"project_id"`
Region types.String `tfsdk:"region"`
}

View file

@ -3,6 +3,7 @@ package postgresflexalpha
import ( import (
"context" "context"
"fmt" "fmt"
"strings"
postgresflex "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/postgresflexalpha" postgresflex "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/postgresflexalpha"
) )
@ -79,3 +80,12 @@ func getDatabase(
return nil, fmt.Errorf("database not found for instance %s", instanceId) return nil, fmt.Errorf("database not found for instance %s", instanceId)
} }
// cleanString removes leading and trailing quotes which are sometimes returned by the API.
func cleanString(s *string) *string {
if s == nil {
return nil
}
res := strings.Trim(*s, "\"")
return &res
}

View file

@ -4,6 +4,7 @@ import (
"context" "context"
"testing" "testing"
"github.com/google/go-cmp/cmp"
"github.com/stackitcloud/stackit-sdk-go/core/utils" "github.com/stackitcloud/stackit-sdk-go/core/utils"
postgresflex "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/postgresflexalpha" postgresflex "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/postgresflexalpha"
) )
@ -12,8 +13,8 @@ type mockRequest struct {
executeFunc func() (*postgresflex.ListDatabasesResponse, error) executeFunc func() (*postgresflex.ListDatabasesResponse, error)
} }
func (m *mockRequest) Page(_ int64) postgresflex.ApiListDatabasesRequestRequest { return m } func (m *mockRequest) Page(_ int32) postgresflex.ApiListDatabasesRequestRequest { return m }
func (m *mockRequest) Size(_ int64) postgresflex.ApiListDatabasesRequestRequest { return m } func (m *mockRequest) Size(_ int32) postgresflex.ApiListDatabasesRequestRequest { return m }
func (m *mockRequest) Sort(_ postgresflex.DatabaseSort) postgresflex.ApiListDatabasesRequestRequest { func (m *mockRequest) Sort(_ postgresflex.DatabaseSort) postgresflex.ApiListDatabasesRequestRequest {
return m return m
} }
@ -176,21 +177,56 @@ func TestGetDatabase(t *testing.T) {
} }
if (errDB != nil) != tt.wantErr { if (errDB != nil) != tt.wantErr {
t.Errorf("getDatabase() error = %v, wantErr %v", errDB, tt.wantErr) t.Errorf("getDatabaseByNameOrID() error = %v, wantErr %v", errDB, tt.wantErr)
return return
} }
if !tt.wantErr && tt.wantDbName != "" && actual != nil { if !tt.wantErr && tt.wantDbName != "" && actual != nil {
if *actual.Name != tt.wantDbName { if *actual.Name != tt.wantDbName {
t.Errorf("getDatabase() got name = %v, want %v", *actual.Name, tt.wantDbName) t.Errorf("getDatabaseByNameOrID() got name = %v, want %v", *actual.Name, tt.wantDbName)
} }
} }
if !tt.wantErr && tt.wantDbId != 0 && actual != nil { if !tt.wantErr && tt.wantDbId != 0 && actual != nil {
if *actual.Id != tt.wantDbId { if *actual.Id != tt.wantDbId {
t.Errorf("getDatabase() got id = %v, want %v", *actual.Id, tt.wantDbId) t.Errorf("getDatabaseByNameOrID() got id = %v, want %v", *actual.Id, tt.wantDbId)
} }
} }
}, },
) )
} }
} }
func TestCleanString(t *testing.T) {
testcases := []struct {
name string
given *string
expected *string
}{
{
name: "should remove quotes",
given: utils.Ptr("\"quoted\""),
expected: utils.Ptr("quoted"),
},
{
name: "should handle nil",
given: nil,
expected: nil,
},
{
name: "should not change unquoted string",
given: utils.Ptr("unquoted"),
expected: utils.Ptr("unquoted"),
},
}
for _, tc := range testcases {
t.Run(
tc.name, func(t *testing.T) {
actual := cleanString(tc.given)
if diff := cmp.Diff(tc.expected, actual); diff != "" {
t.Errorf("string mismatch (-want +got):\n%s", diff)
}
},
)
}
}

View file

@ -0,0 +1,92 @@
package postgresflexalpha
import (
"fmt"
"strconv"
"github.com/hashicorp/terraform-plugin-framework/types"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/postgresflexalpha"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/utils"
)
// mapFields maps fields from a ListDatabase API response to a resourceModel for the data source.
func mapFields(
source *postgresflexalpha.ListDatabase,
model *dataSourceModel,
region string,
) error {
if source == nil {
return fmt.Errorf("response is nil")
}
if source.Id == nil || *source.Id == 0 {
return fmt.Errorf("id not present")
}
if model == nil {
return fmt.Errorf("model given is nil")
}
var databaseId int64
if model.DatabaseId.ValueInt64() != 0 {
databaseId = model.DatabaseId.ValueInt64()
} else if source.Id != nil {
databaseId = *source.Id
} else {
return fmt.Errorf("database id not present")
}
model.Id = types.Int64Value(databaseId)
model.DatabaseId = types.Int64Value(databaseId)
model.Name = types.StringValue(source.GetName())
model.Owner = types.StringPointerValue(cleanString(source.Owner))
model.Region = types.StringValue(region)
model.ProjectId = types.StringValue(model.ProjectId.ValueString())
model.InstanceId = types.StringValue(model.InstanceId.ValueString())
model.TerraformID = utils.BuildInternalTerraformId(
model.ProjectId.ValueString(),
region,
model.InstanceId.ValueString(),
strconv.FormatInt(databaseId, 10),
)
return nil
}
// mapResourceFields maps fields from a ListDatabase API response to a resourceModel for the resource.
func mapResourceFields(source *postgresflexalpha.ListDatabase, model *resourceModel) error {
if source == nil {
return fmt.Errorf("response is nil")
}
if source.Id == nil || *source.Id == 0 {
return fmt.Errorf("id not present")
}
if model == nil {
return fmt.Errorf("model input is nil")
}
var databaseId int64
if model.Id.ValueInt64() != 0 {
databaseId = model.Id.ValueInt64()
} else if source.Id != nil {
databaseId = *source.Id
} else {
return fmt.Errorf("database id not present")
}
model.Id = types.Int64Value(databaseId)
model.DatabaseId = types.Int64Value(databaseId)
model.Name = types.StringValue(source.GetName())
model.Owner = types.StringPointerValue(cleanString(source.Owner))
return nil
}
// toCreatePayload converts the resource model to an API create payload.
func toCreatePayload(model *resourceModel) (*postgresflexalpha.CreateDatabaseRequestPayload, error) {
if model == nil {
return nil, fmt.Errorf("nil model")
}
return &postgresflexalpha.CreateDatabaseRequestPayload{
Name: model.Name.ValueStringPointer(),
Owner: model.Owner.ValueStringPointer(),
}, nil
}

View file

@ -0,0 +1,240 @@
package postgresflexalpha
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/stackitcloud/stackit-sdk-go/core/utils"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/postgresflexalpha"
datasource "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/database/datasources_gen"
)
func TestMapFields(t *testing.T) {
type given struct {
source *postgresflexalpha.ListDatabase
model *dataSourceModel
region string
}
type expected struct {
model *dataSourceModel
err bool
}
testcases := []struct {
name string
given given
expected expected
}{
{
name: "should map fields correctly",
given: given{
source: &postgresflexalpha.ListDatabase{
Id: utils.Ptr(int64(1)),
Name: utils.Ptr("my-db"),
Owner: utils.Ptr("\"my-owner\""),
},
model: &dataSourceModel{},
region: "eu01",
},
expected: expected{
model: &dataSourceModel{
DatabaseModel: datasource.DatabaseModel{
Id: types.Int64Value(1),
Name: types.StringValue("my-db"),
Owner: types.StringValue("my-owner"),
Region: types.StringValue("eu01"),
DatabaseId: types.Int64Value(1),
InstanceId: types.StringValue("my-instance"),
ProjectId: types.StringValue("my-project"),
},
TerraformID: types.StringValue("my-project,eu01,my-instance,1"),
},
},
},
{
name: "should preserve existing model ID",
given: given{
source: &postgresflexalpha.ListDatabase{
Id: utils.Ptr(int64(1)),
Name: utils.Ptr("my-db"),
},
model: &dataSourceModel{
DatabaseModel: datasource.DatabaseModel{
Id: types.Int64Value(1),
ProjectId: types.StringValue("my-project"),
InstanceId: types.StringValue("my-instance"),
},
},
region: "eu01",
},
expected: expected{
model: &dataSourceModel{
DatabaseModel: datasource.DatabaseModel{
Id: types.Int64Value(1),
Name: types.StringValue("my-db"),
Owner: types.StringNull(), DatabaseId: types.Int64Value(1),
Region: types.StringValue("eu01"),
InstanceId: types.StringValue("my-instance"),
ProjectId: types.StringValue("my-project"),
},
TerraformID: types.StringValue("my-project,eu01,my-instance,1"),
},
},
},
{
name: "should fail on nil source",
given: given{
source: nil,
model: &dataSourceModel{},
},
expected: expected{err: true},
},
{
name: "should fail on nil source ID",
given: given{
source: &postgresflexalpha.ListDatabase{Id: nil},
model: &dataSourceModel{},
},
expected: expected{err: true},
},
{
name: "should fail on nil model",
given: given{
source: &postgresflexalpha.ListDatabase{Id: utils.Ptr(int64(1))},
model: nil,
},
expected: expected{err: true},
},
}
for _, tc := range testcases {
t.Run(
tc.name, func(t *testing.T) {
err := mapFields(tc.given.source, tc.given.model, tc.given.region)
if (err != nil) != tc.expected.err {
t.Fatalf("expected error: %v, got: %v", tc.expected.err, err)
}
if err == nil {
if diff := cmp.Diff(tc.expected.model, tc.given.model); diff != "" {
t.Errorf("model mismatch (-want +got):\n%s", diff)
}
}
},
)
}
}
func TestMapResourceFields(t *testing.T) {
type given struct {
source *postgresflexalpha.ListDatabase
model *resourceModel
}
type expected struct {
model *resourceModel
err bool
}
testcases := []struct {
name string
given given
expected expected
}{
{
name: "should map fields correctly",
given: given{
source: &postgresflexalpha.ListDatabase{
Id: utils.Ptr(int64(1)),
Name: utils.Ptr("my-db"),
Owner: utils.Ptr("\"my-owner\""),
},
model: &resourceModel{},
},
expected: expected{
model: &resourceModel{
Id: types.Int64Value(1),
Name: types.StringValue("my-db"),
Owner: types.StringValue("my-owner"),
DatabaseId: types.Int64Value(1),
},
},
},
{
name: "should fail on nil source",
given: given{
source: nil,
model: &resourceModel{},
},
expected: expected{err: true},
},
}
for _, tc := range testcases {
t.Run(
tc.name, func(t *testing.T) {
err := mapResourceFields(tc.given.source, tc.given.model)
if (err != nil) != tc.expected.err {
t.Fatalf("expected error: %v, got: %v", tc.expected.err, err)
}
if err == nil {
if diff := cmp.Diff(tc.expected.model, tc.given.model); diff != "" {
t.Errorf("model mismatch (-want +got):\n%s", diff)
}
}
},
)
}
}
func TestToCreatePayload(t *testing.T) {
type given struct {
model *resourceModel
}
type expected struct {
payload *postgresflexalpha.CreateDatabaseRequestPayload
err bool
}
testcases := []struct {
name string
given given
expected expected
}{
{
name: "should convert model to payload",
given: given{
model: &resourceModel{
Name: types.StringValue("my-db"),
Owner: types.StringValue("my-owner"),
},
},
expected: expected{
payload: &postgresflexalpha.CreateDatabaseRequestPayload{
Name: utils.Ptr("my-db"),
Owner: utils.Ptr("my-owner"),
},
},
},
{
name: "should fail on nil model",
given: given{model: nil},
expected: expected{err: true},
},
}
for _, tc := range testcases {
t.Run(
tc.name, func(t *testing.T) {
actual, err := toCreatePayload(tc.given.model)
if (err != nil) != tc.expected.err {
t.Fatalf("expected error: %v, got: %v", tc.expected.err, err)
}
if err == nil {
if diff := cmp.Diff(tc.expected.payload, actual); diff != "" {
t.Errorf("payload mismatch (-want +got):\n%s", diff)
}
}
},
)
}
}

View file

@ -0,0 +1,35 @@
fields:
- name: 'id'
modifiers:
- 'UseStateForUnknown'
- name: 'database_id'
modifiers:
- 'UseStateForUnknown'
validators:
- validate.NoSeparator
- validate.UUID
- name: 'instance_id'
validators:
- validate.NoSeparator
- validate.UUID
modifiers:
- 'RequiresReplace'
- 'UseStateForUnknown'
- name: 'project_id'
modifiers:
- 'RequiresReplace'
- 'UseStateForUnknown'
validators:
- validate.NoSeparator
- validate.UUID
- name: 'name'
validators:
- validate.NoSeparator
- name: 'region'
modifiers:
- 'RequiresReplace'

View file

@ -2,70 +2,73 @@ package postgresflexalpha
import ( import (
"context" "context"
_ "embed"
"errors" "errors"
"fmt" "fmt"
"math" "math"
"net/http" "net/http"
"regexp"
"strconv" "strconv"
"strings" "strings"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/identityschema"
"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"
"github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/stackitcloud/stackit-sdk-go/core/oapierror" "github.com/stackitcloud/stackit-sdk-go/core/oapierror"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/postgresflexalpha" "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/postgresflexalpha"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/conversion" "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/conversion"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/core" "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/core"
postgresflexalpha2 "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/database/resources_gen"
postgresflexUtils "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/utils" postgresflexUtils "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/utils"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/utils" "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/utils"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/validate"
) )
// Ensure the implementation satisfies the expected interfaces.
var ( var (
// Ensure the implementation satisfies the expected interfaces.
_ resource.Resource = &databaseResource{} _ resource.Resource = &databaseResource{}
_ resource.ResourceWithConfigure = &databaseResource{} _ resource.ResourceWithConfigure = &databaseResource{}
_ resource.ResourceWithImportState = &databaseResource{} _ resource.ResourceWithImportState = &databaseResource{}
_ resource.ResourceWithModifyPlan = &databaseResource{} _ resource.ResourceWithModifyPlan = &databaseResource{}
) _ resource.ResourceWithIdentity = &databaseResource{}
type Model struct { // Define errors
Id types.String `tfsdk:"id"` // needed by TF errDatabaseNotFound = errors.New("database not found")
DatabaseId types.Int64 `tfsdk:"database_id"`
InstanceId types.String `tfsdk:"instance_id"` // Error message constants
ProjectId types.String `tfsdk:"project_id"` extractErrorSummary = "extracting failed"
Name types.String `tfsdk:"name"` extractErrorMessage = "Extracting identity data: %v"
Owner types.String `tfsdk:"owner"` )
Region types.String `tfsdk:"region"`
}
// NewDatabaseResource is a helper function to simplify the provider implementation. // NewDatabaseResource is a helper function to simplify the provider implementation.
func NewDatabaseResource() resource.Resource { func NewDatabaseResource() resource.Resource {
return &databaseResource{} return &databaseResource{}
} }
// resourceModel describes the resource data model.
type resourceModel = postgresflexalpha2.DatabaseModel
// DatabaseResourceIdentityModel describes the resource's identity attributes.
type DatabaseResourceIdentityModel struct {
ProjectID types.String `tfsdk:"project_id"`
Region types.String `tfsdk:"region"`
InstanceID types.String `tfsdk:"instance_id"`
DatabaseID types.Int64 `tfsdk:"database_id"`
}
// databaseResource is the resource implementation. // databaseResource is the resource implementation.
type databaseResource struct { type databaseResource struct {
client *postgresflexalpha.APIClient client *postgresflexalpha.APIClient
providerData core.ProviderData providerData core.ProviderData
} }
// ModifyPlan implements resource.ResourceWithModifyPlan. // ModifyPlan adjusts the plan to set the correct region.
// Use the modifier to set the effective region in the current plan.
func (r *databaseResource) ModifyPlan( func (r *databaseResource) ModifyPlan(
ctx context.Context, ctx context.Context,
req resource.ModifyPlanRequest, req resource.ModifyPlanRequest,
resp *resource.ModifyPlanResponse, resp *resource.ModifyPlanResponse,
) { // nolint:gocritic // function signature required by Terraform ) { // nolint:gocritic // function signature required by Terraform
var configModel Model var configModel resourceModel
// skip initial empty configuration to avoid follow-up errors // skip initial empty configuration to avoid follow-up errors
if req.Config.Raw.IsNull() { if req.Config.Raw.IsNull() {
return return
@ -75,7 +78,7 @@ func (r *databaseResource) ModifyPlan(
return return
} }
var planModel Model var planModel resourceModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &planModel)...) resp.Diagnostics.Append(req.Plan.Get(ctx, &planModel)...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
@ -117,85 +120,46 @@ func (r *databaseResource) Configure(
tflog.Info(ctx, "Postgres Flex database client configured") tflog.Info(ctx, "Postgres Flex database client configured")
} }
//go:embed planModifiers.yaml
var modifiersFileByte []byte
// Schema defines the schema for the resource. // Schema defines the schema for the resource.
func (r *databaseResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { func (r *databaseResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
descriptions := map[string]string{ s := postgresflexalpha2.DatabaseResourceSchema(ctx)
"main": "Postgres Flex database 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`,`database_id`\".", fields, err := utils.ReadModifiersConfig(modifiersFileByte)
"database_id": "Database ID.", if err != nil {
"instance_id": "ID of the Postgres Flex instance.", resp.Diagnostics.AddError("error during read modifiers config file", err.Error())
"project_id": "STACKIT project ID to which the instance is associated.", return
"name": "Database name.",
"owner": "Username of the database owner.",
"region": "The resource region. If not defined, the provider region is used.",
} }
resp.Schema = schema.Schema{ err = utils.AddPlanModifiersToResourceSchema(fields, &s)
Description: descriptions["main"], if err != nil {
Attributes: map[string]schema.Attribute{ resp.Diagnostics.AddError("error adding plan modifiers", err.Error())
"id": schema.StringAttribute{ return
Description: descriptions["id"], }
Computed: true, resp.Schema = s
PlanModifiers: []planmodifier.String{ }
stringplanmodifier.UseStateForUnknown(),
// IdentitySchema defines the schema for the resource's identity attributes.
func (r *databaseResource) IdentitySchema(
_ context.Context,
_ resource.IdentitySchemaRequest,
response *resource.IdentitySchemaResponse,
) {
response.IdentitySchema = identityschema.Schema{
Attributes: map[string]identityschema.Attribute{
"project_id": identityschema.StringAttribute{
RequiredForImport: true,
}, },
"region": identityschema.StringAttribute{
RequiredForImport: true,
}, },
"database_id": schema.Int64Attribute{ "instance_id": identityschema.StringAttribute{
Description: descriptions["database_id"], RequiredForImport: true,
Computed: true,
PlanModifiers: []planmodifier.Int64{
int64planmodifier.UseStateForUnknown(),
},
Validators: []validator.Int64{},
},
"instance_id": schema.StringAttribute{
Description: descriptions["instance_id"],
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
stringplanmodifier.UseStateForUnknown(),
},
Validators: []validator.String{
validate.UUID(),
validate.NoSeparator(),
},
},
"project_id": schema.StringAttribute{
Description: descriptions["project_id"],
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
stringplanmodifier.UseStateForUnknown(),
},
Validators: []validator.String{
validate.UUID(),
validate.NoSeparator(),
},
},
"name": schema.StringAttribute{
Description: descriptions["name"],
Required: true,
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{},
},
"region": schema.StringAttribute{
Optional: true,
// must be computed to allow for storing the override value from the provider
Computed: true,
Description: descriptions["region"],
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
}, },
"database_id": identityschema.Int64Attribute{
RequiredForImport: true,
}, },
}, },
} }
@ -207,18 +171,26 @@ func (r *databaseResource) Create(
req resource.CreateRequest, req resource.CreateRequest,
resp *resource.CreateResponse, resp *resource.CreateResponse,
) { // nolint:gocritic // function signature required by Terraform ) { // nolint:gocritic // function signature required by Terraform
var model Model var model resourceModel
diags := req.Plan.Get(ctx, &model) diags := req.Plan.Get(ctx, &model)
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
} }
// Read identity data
var identityData DatabaseResourceIdentityModel
resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...)
if resp.Diagnostics.HasError() {
return
}
ctx = core.InitProviderContext(ctx) ctx = core.InitProviderContext(ctx)
projectId := model.ProjectId.ValueString() projectId := identityData.ProjectID.ValueString()
region := model.Region.ValueString() region := identityData.ProjectID.ValueString()
instanceId := model.InstanceId.ValueString() instanceId := identityData.InstanceID.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "instance_id", instanceId)
ctx = tflog.SetField(ctx, "region", region) ctx = tflog.SetField(ctx, "region", region)
@ -272,7 +244,7 @@ func (r *databaseResource) Create(
} }
// Map response body to schema // Map response body to schema
err = mapFields(database, &model, region) err = mapResourceFields(database, &model)
if err != nil { if err != nil {
core.LogAndAddError( core.LogAndAddError(
ctx, ctx,
@ -282,9 +254,21 @@ func (r *databaseResource) Create(
) )
return return
} }
// Set data returned by API in identity
identity := DatabaseResourceIdentityModel{
ProjectID: types.StringValue(projectId),
Region: types.StringValue(region),
InstanceID: types.StringValue(instanceId),
DatabaseID: types.Int64Value(databaseId),
}
resp.Diagnostics.Append(resp.Identity.Set(ctx, identity)...)
if resp.Diagnostics.HasError() {
return
}
// Set state to fully populated data // Set state to fully populated data
diags = resp.State.Set(ctx, model) resp.Diagnostics.Append(resp.State.Set(ctx, model)...)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
} }
@ -297,23 +281,36 @@ func (r *databaseResource) Read(
req resource.ReadRequest, req resource.ReadRequest,
resp *resource.ReadResponse, resp *resource.ReadResponse,
) { // nolint:gocritic // function signature required by Terraform ) { // nolint:gocritic // function signature required by Terraform
var model Model var model resourceModel
diags := req.State.Get(ctx, &model) diags := req.State.Get(ctx, &model)
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
} }
// Read identity data
var identityData DatabaseResourceIdentityModel
resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...)
if resp.Diagnostics.HasError() {
return
}
ctx = core.InitProviderContext(ctx) ctx = core.InitProviderContext(ctx)
projectId := model.ProjectId.ValueString() projectId, instanceId, region, databaseId, errExt := r.extractIdentityData(model, identityData)
instanceId := model.InstanceId.ValueString() if errExt != nil {
databaseId := model.DatabaseId.ValueInt64() core.LogAndAddError(
region := r.providerData.GetRegionWithOverride(model.Region) ctx,
&resp.Diagnostics,
extractErrorSummary,
fmt.Sprintf(extractErrorMessage, errExt),
)
}
ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "instance_id", instanceId)
ctx = tflog.SetField(ctx, "database_id", databaseId)
ctx = tflog.SetField(ctx, "region", region) ctx = tflog.SetField(ctx, "region", region)
ctx = tflog.SetField(ctx, "database_id", databaseId)
databaseResp, err := getDatabaseById(ctx, r.client, projectId, region, instanceId, databaseId) databaseResp, err := getDatabaseById(ctx, r.client, projectId, region, instanceId, databaseId)
if err != nil { if err != nil {
@ -329,7 +326,7 @@ func (r *databaseResource) Read(
ctx = core.LogResponse(ctx) ctx = core.LogResponse(ctx)
// Map response body to schema // Map response body to schema
err = mapFields(databaseResp, &model, region) err = mapResourceFields(databaseResp, &model)
if err != nil { if err != nil {
core.LogAndAddError( core.LogAndAddError(
ctx, ctx,
@ -355,32 +352,45 @@ func (r *databaseResource) Update(
req resource.UpdateRequest, req resource.UpdateRequest,
resp *resource.UpdateResponse, resp *resource.UpdateResponse,
) { ) {
var model Model var model resourceModel
diags := req.Plan.Get(ctx, &model) diags := req.Plan.Get(ctx, &model)
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
} }
// Read identity data
var identityData DatabaseResourceIdentityModel
resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...)
if resp.Diagnostics.HasError() {
return
}
ctx = core.InitProviderContext(ctx) ctx = core.InitProviderContext(ctx)
projectId := model.ProjectId.ValueString() projectId, instanceId, region, databaseId64, errExt := r.extractIdentityData(model, identityData)
instanceId := model.InstanceId.ValueString() if errExt != nil {
databaseId64 := model.DatabaseId.ValueInt64() core.LogAndAddError(
ctx,
&resp.Diagnostics,
extractErrorSummary,
fmt.Sprintf(extractErrorMessage, errExt),
)
}
if databaseId64 > math.MaxInt32 { if databaseId64 > math.MaxInt32 {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error in type conversion", "int value too large (databaseId)") core.LogAndAddError(ctx, &resp.Diagnostics, "Error in type conversion", "int value too large (databaseId)")
return return
} }
databaseId := int32(databaseId64) databaseId := int32(databaseId64)
region := model.Region.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "instance_id", instanceId)
ctx = tflog.SetField(ctx, "database_id", databaseId)
ctx = tflog.SetField(ctx, "region", region) ctx = tflog.SetField(ctx, "region", region)
ctx = tflog.SetField(ctx, "database_id", databaseId)
// Retrieve values from state // Retrieve values from state
var stateModel Model var stateModel resourceModel
diags = req.State.Get(ctx, &stateModel) diags = req.State.Get(ctx, &stateModel)
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
@ -420,7 +430,7 @@ func (r *databaseResource) Update(
ctx = core.LogResponse(ctx) ctx = core.LogResponse(ctx)
// Map response body to schema // Map response body to schema
err = mapFieldsUpdatePartially(res, &model, region) err = mapResourceFields(res.Database, &model)
if err != nil { if err != nil {
core.LogAndAddError( core.LogAndAddError(
ctx, ctx,
@ -445,29 +455,41 @@ func (r *databaseResource) Delete(
req resource.DeleteRequest, req resource.DeleteRequest,
resp *resource.DeleteResponse, resp *resource.DeleteResponse,
) { // nolint:gocritic // function signature required by Terraform ) { // nolint:gocritic // function signature required by Terraform
var model Model var model resourceModel
diags := req.State.Get(ctx, &model) diags := req.State.Get(ctx, &model)
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
} }
// Read identity data
var identityData DatabaseResourceIdentityModel
resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...)
if resp.Diagnostics.HasError() {
return
}
ctx = core.InitProviderContext(ctx) ctx = core.InitProviderContext(ctx)
projectId := model.ProjectId.ValueString() projectId, instanceId, region, databaseId64, errExt := r.extractIdentityData(model, identityData)
instanceId := model.InstanceId.ValueString() if errExt != nil {
databaseId64 := model.DatabaseId.ValueInt64() core.LogAndAddError(
ctx,
&resp.Diagnostics,
extractErrorSummary,
fmt.Sprintf(extractErrorMessage, errExt),
)
}
if databaseId64 > math.MaxInt32 { if databaseId64 > math.MaxInt32 {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error in type conversion", "int value too large (databaseId)") core.LogAndAddError(ctx, &resp.Diagnostics, "Error in type conversion", "int value too large (databaseId)")
return return
} }
databaseId := int32(databaseId64) databaseId := int32(databaseId64)
region := model.Region.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "instance_id", instanceId)
ctx = tflog.SetField(ctx, "database_id", databaseId)
ctx = tflog.SetField(ctx, "region", region) ctx = tflog.SetField(ctx, "region", region)
ctx = tflog.SetField(ctx, "database_id", databaseId)
// Delete existing record set // Delete existing record set
err := r.client.DeleteDatabaseRequestExecute(ctx, projectId, region, instanceId, databaseId) err := r.client.DeleteDatabaseRequestExecute(ctx, projectId, region, instanceId, databaseId)
@ -481,13 +503,19 @@ func (r *databaseResource) Delete(
} }
// ImportState imports a resource into the Terraform state on success. // ImportState imports a resource into the Terraform state on success.
// The expected format of the resource import identifier is: project_id,zone_id,record_set_id // The expected import identifier format is: [project_id],[region],[instance_id],[database_id]
func (r *databaseResource) ImportState( func (r *databaseResource) ImportState(
ctx context.Context, ctx context.Context,
req resource.ImportStateRequest, req resource.ImportStateRequest,
resp *resource.ImportStateResponse, resp *resource.ImportStateResponse,
) { ) {
ctx = core.InitProviderContext(ctx)
if req.ID != "" {
idParts := strings.Split(req.ID, core.Separator) idParts := strings.Split(req.ID, core.Separator)
if len(idParts) != 4 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" || idParts[3] == "" { if len(idParts) != 4 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" || idParts[3] == "" {
core.LogAndAddError( core.LogAndAddError(
ctx, &resp.Diagnostics, ctx, &resp.Diagnostics,
@ -500,76 +528,93 @@ func (r *databaseResource) ImportState(
return return
} }
databaseId, err := strconv.ParseInt(idParts[3], 10, 64)
if err != nil {
core.LogAndAddError(
ctx,
&resp.Diagnostics,
"Error importing database",
fmt.Sprintf("Invalid database_id format: %q. It must be a valid integer.", idParts[3]),
)
return
}
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), idParts[0])...) resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), idParts[0])...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("region"), idParts[1])...) resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("region"), idParts[1])...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("instance_id"), idParts[2])...) resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("instance_id"), idParts[2])...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("database_id"), idParts[3])...) resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("database_id"), databaseId)...)
core.LogAndAddWarning( core.LogAndAddWarning(
ctx, ctx,
&resp.Diagnostics, &resp.Diagnostics,
"Postgresflex database imported with empty password", "Postgresflex database imported with empty password",
"The database password is not imported as it is only available upon creation of a new database. The password field will be empty.", "The database password is not imported as it is only available upon creation of a new database. The password field will be empty.",
) )
tflog.Info(ctx, "Postgres Flex database state imported")
return
}
// If no ID is provided, attempt to read identity attributes from the import configuration
var identityData DatabaseResourceIdentityModel
resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...)
if resp.Diagnostics.HasError() {
return
}
projectId := identityData.ProjectID.ValueString()
region := identityData.Region.ValueString()
instanceId := identityData.InstanceID.ValueString()
databaseId := identityData.DatabaseID.ValueInt64()
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), projectId)...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("region"), region)...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("instance_id"), instanceId)...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("database_id"), databaseId)...)
tflog.Info(ctx, "Postgres Flex database state imported") tflog.Info(ctx, "Postgres Flex database state imported")
} }
func mapFields(resp *postgresflexalpha.ListDatabase, model *Model, region string) error { // extractIdentityData extracts essential identifiers from the resource model, falling back to the identity model.
if resp == nil { func (r *databaseResource) extractIdentityData(
return fmt.Errorf("response is nil") model resourceModel,
} identity DatabaseResourceIdentityModel,
if resp.Id == nil || *resp.Id == 0 { ) (projectId, region, instanceId string, databaseId int64, err error) {
return fmt.Errorf("id not present") if !model.DatabaseId.IsNull() && !model.DatabaseId.IsUnknown() {
}
if model == nil {
return fmt.Errorf("model input is nil")
}
var databaseId int64
if model.DatabaseId.ValueInt64() != 0 {
databaseId = model.DatabaseId.ValueInt64() databaseId = model.DatabaseId.ValueInt64()
} else if resp.Id != nil {
databaseId = *resp.Id
} else { } else {
return fmt.Errorf("database id not present") if identity.DatabaseID.IsNull() || identity.DatabaseID.IsUnknown() {
return "", "", "", 0, fmt.Errorf("database_id not found in config")
} }
model.Id = utils.BuildInternalTerraformId( databaseId = identity.DatabaseID.ValueInt64()
model.ProjectId.ValueString(), region, model.InstanceId.ValueString(), strconv.FormatInt(databaseId, 10),
)
model.DatabaseId = types.Int64Value(databaseId)
model.Name = types.StringPointerValue(resp.Name)
model.Region = types.StringValue(region)
model.Owner = types.StringPointerValue(cleanString(resp.Owner))
return nil
} }
func mapFieldsUpdatePartially( if !model.ProjectId.IsNull() && !model.ProjectId.IsUnknown() {
res *postgresflexalpha.UpdateDatabasePartiallyResponse, projectId = model.ProjectId.ValueString()
model *Model, } else {
region string, if identity.ProjectID.IsNull() || identity.ProjectID.IsUnknown() {
) error { return "", "", "", 0, fmt.Errorf("project_id not found in config")
if res == nil {
return fmt.Errorf("response is nil")
} }
return mapFields(res.Database, model, region) projectId = identity.ProjectID.ValueString()
} }
func cleanString(s *string) *string { if !model.Region.IsNull() && !model.Region.IsUnknown() {
if s == nil { region = r.providerData.GetRegionWithOverride(model.Region)
return nil } else {
if identity.Region.IsNull() || identity.Region.IsUnknown() {
return "", "", "", 0, fmt.Errorf("region not found in config")
} }
res := strings.Trim(*s, "\"") region = r.providerData.GetRegionWithOverride(identity.Region)
return &res
} }
func toCreatePayload(model *Model) (*postgresflexalpha.CreateDatabaseRequestPayload, error) { if !model.InstanceId.IsNull() && !model.InstanceId.IsUnknown() {
if model == nil { instanceId = model.InstanceId.ValueString()
return nil, fmt.Errorf("nil model") } else {
if identity.InstanceID.IsNull() || identity.InstanceID.IsUnknown() {
return "", "", "", 0, fmt.Errorf("instance_id not found in config")
} }
instanceId = identity.InstanceID.ValueString()
return &postgresflexalpha.CreateDatabaseRequestPayload{ }
Name: model.Name.ValueStringPointer(), return projectId, region, instanceId, databaseId, nil
Owner: model.Owner.ValueStringPointer(),
}, nil
} }
var errDatabaseNotFound = errors.New("database not found")

View file

@ -1,232 +0,0 @@
package postgresflexalpha
import (
"reflect"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/stackitcloud/stackit-sdk-go/core/utils"
postgresflex "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/postgresflexalpha"
)
func TestMapFields(t *testing.T) {
const testRegion = "region"
tests := []struct {
description string
input *postgresflex.ListDatabase
region string
expected Model
isValid bool
}{
{
"default_values",
&postgresflex.ListDatabase{
Id: utils.Ptr(int64(1)),
},
testRegion,
Model{
Id: types.StringValue("pid,region,iid,1"),
DatabaseId: types.Int64Value(int64(1)),
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
Name: types.StringNull(),
Owner: types.StringNull(),
Region: types.StringValue(testRegion),
},
true,
},
{
"simple_values",
&postgresflex.ListDatabase{
Id: utils.Ptr(int64(1)),
Name: utils.Ptr("dbname"),
Owner: utils.Ptr("username"),
},
testRegion,
Model{
Id: types.StringValue("pid,region,iid,1"),
DatabaseId: types.Int64Value(int64(1)),
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
Name: types.StringValue("dbname"),
Owner: types.StringValue("username"),
Region: types.StringValue(testRegion),
},
true,
},
{
"null_fields_and_int_conversions",
&postgresflex.ListDatabase{
Id: utils.Ptr(int64(1)),
Name: utils.Ptr(""),
Owner: utils.Ptr(""),
},
testRegion,
Model{
Id: types.StringValue("pid,region,iid,1"),
DatabaseId: types.Int64Value(int64(1)),
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
Name: types.StringValue(""),
Owner: types.StringValue(""),
Region: types.StringValue(testRegion),
},
true,
},
{
"nil_response",
nil,
testRegion,
Model{},
false,
},
{
"empty_response",
&postgresflex.ListDatabase{},
testRegion,
Model{},
false,
},
{
"no_resource_id",
&postgresflex.ListDatabase{
Id: utils.Ptr(int64(0)),
Name: utils.Ptr("dbname"),
Owner: utils.Ptr("username"),
},
testRegion,
Model{},
false,
},
}
for _, tt := range tests {
t.Run(
tt.description, func(t *testing.T) {
state := &Model{
ProjectId: tt.expected.ProjectId,
InstanceId: tt.expected.InstanceId,
}
err := mapFields(tt.input, state, tt.region)
if !tt.isValid && err == nil {
t.Fatalf("Should have failed")
}
if tt.isValid && err != nil {
t.Fatalf("Should not have failed: %v", err)
}
if tt.isValid {
diff := cmp.Diff(state, &tt.expected)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
}
},
)
}
}
func TestToCreatePayload(t *testing.T) {
tests := []struct {
description string
input *Model
expected *postgresflex.CreateDatabaseRequestPayload
isValid bool
}{
{
"default_values",
&Model{
Name: types.StringValue("dbname"),
Owner: types.StringValue("username"),
},
&postgresflex.CreateDatabaseRequestPayload{
Name: utils.Ptr("dbname"),
Owner: utils.Ptr("username"),
},
true,
},
{
"null_fields",
&Model{
Name: types.StringNull(),
Owner: types.StringNull(),
},
&postgresflex.CreateDatabaseRequestPayload{
Name: nil,
Owner: nil,
},
true,
},
{
"nil_model",
nil,
nil,
false,
},
}
for _, tt := range tests {
t.Run(
tt.description, func(t *testing.T) {
output, err := toCreatePayload(tt.input)
if !tt.isValid && err == nil {
t.Fatalf("Should have failed")
}
if tt.isValid && err != nil {
t.Fatalf("Should not have failed: %v", err)
}
if tt.isValid {
diff := cmp.Diff(output, tt.expected)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
}
},
)
}
}
func 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)
}
})
}
}

View file

@ -4,6 +4,8 @@ package postgresflexalpha
import ( import (
"context" "context"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema"
@ -12,11 +14,23 @@ import (
func DatabaseResourceSchema(ctx context.Context) schema.Schema { func DatabaseResourceSchema(ctx context.Context) schema.Schema {
return schema.Schema{ return schema.Schema{
Attributes: map[string]schema.Attribute{ Attributes: map[string]schema.Attribute{
"database_id": schema.Int64Attribute{
Optional: true,
Computed: true,
Description: "The ID of the database.",
MarkdownDescription: "The ID of the database.",
},
"id": schema.Int64Attribute{ "id": schema.Int64Attribute{
Computed: true, Computed: true,
Description: "The id of the database.", Description: "The id of the database.",
MarkdownDescription: "The id of the database.", MarkdownDescription: "The id of the database.",
}, },
"instance_id": schema.StringAttribute{
Optional: true,
Computed: true,
Description: "The ID of the instance.",
MarkdownDescription: "The ID of the instance.",
},
"name": schema.StringAttribute{ "name": schema.StringAttribute{
Required: true, Required: true,
Description: "The name of the database.", Description: "The name of the database.",
@ -28,12 +42,33 @@ func DatabaseResourceSchema(ctx context.Context) schema.Schema {
Description: "The owner of the database.", Description: "The owner of the database.",
MarkdownDescription: "The owner of the database.", MarkdownDescription: "The owner of the database.",
}, },
"project_id": schema.StringAttribute{
Optional: true,
Computed: true,
Description: "The STACKIT project ID.",
MarkdownDescription: "The STACKIT project ID.",
},
"region": schema.StringAttribute{
Optional: true,
Computed: true,
Description: "The region which should be addressed",
MarkdownDescription: "The region which should be addressed",
Validators: []validator.String{
stringvalidator.OneOf(
"eu01",
),
},
},
}, },
} }
} }
type DatabaseModel struct { type DatabaseModel struct {
DatabaseId types.Int64 `tfsdk:"database_id"`
Id types.Int64 `tfsdk:"id"` Id types.Int64 `tfsdk:"id"`
InstanceId types.String `tfsdk:"instance_id"`
Name types.String `tfsdk:"name"` Name types.String `tfsdk:"name"`
Owner types.String `tfsdk:"owner"` Owner types.String `tfsdk:"owner"`
ProjectId types.String `tfsdk:"project_id"`
Region types.String `tfsdk:"region"`
} }

View file

@ -12,8 +12,8 @@ type mockRequest struct {
executeFunc func() (*postgresflex.GetFlavorsResponse, error) executeFunc func() (*postgresflex.GetFlavorsResponse, error)
} }
func (m *mockRequest) Page(_ int64) postgresflex.ApiGetFlavorsRequestRequest { return m } func (m *mockRequest) Page(_ int32) postgresflex.ApiGetFlavorsRequestRequest { return m }
func (m *mockRequest) Size(_ int64) postgresflex.ApiGetFlavorsRequestRequest { return m } func (m *mockRequest) Size(_ int32) postgresflex.ApiGetFlavorsRequestRequest { return m }
func (m *mockRequest) Sort(_ postgresflex.FlavorSort) postgresflex.ApiGetFlavorsRequestRequest { func (m *mockRequest) Sort(_ postgresflex.FlavorSort) postgresflex.ApiGetFlavorsRequestRequest {
return m return m
} }

View file

@ -21,12 +21,19 @@ func NewFlavorsDataSource() datasource.DataSource {
return &flavorsDataSource{} return &flavorsDataSource{}
} }
// dataSourceModel maps the data source schema data.
type dataSourceModel = postgresflexalphaGen.FlavorsModel
type flavorsDataSource struct { type flavorsDataSource struct {
client *postgresflexalpha.APIClient client *postgresflexalpha.APIClient
providerData core.ProviderData providerData core.ProviderData
} }
func (d *flavorsDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { func (d *flavorsDataSource) Metadata(
_ context.Context,
req datasource.MetadataRequest,
resp *datasource.MetadataResponse,
) {
resp.TypeName = req.ProviderTypeName + "_postgresflexalpha_flavors" resp.TypeName = req.ProviderTypeName + "_postgresflexalpha_flavors"
} }
@ -35,7 +42,11 @@ func (d *flavorsDataSource) Schema(ctx context.Context, _ datasource.SchemaReque
} }
// Configure adds the provider configured client to the data source. // Configure adds the provider configured client to the data source.
func (d *flavorsDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { func (d *flavorsDataSource) Configure(
ctx context.Context,
req datasource.ConfigureRequest,
resp *datasource.ConfigureResponse,
) {
var ok bool var ok bool
d.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics) d.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics)
if !ok { if !ok {
@ -51,7 +62,7 @@ func (d *flavorsDataSource) Configure(ctx context.Context, req datasource.Config
} }
func (d *flavorsDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { func (d *flavorsDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var data postgresflexalphaGen.FlavorsModel var data dataSourceModel
// Read Terraform configuration data into the model // Read Terraform configuration data into the model
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)

View file

@ -33,7 +33,7 @@ func FlavorsDataSourceSchema(ctx context.Context) schema.Schema {
Description: "The flavor description.", Description: "The flavor description.",
MarkdownDescription: "The flavor description.", MarkdownDescription: "The flavor description.",
}, },
"id": schema.StringAttribute{ "tf_original_api_id": schema.StringAttribute{
Computed: true, Computed: true,
Description: "The id of the instance flavor.", Description: "The id of the instance flavor.",
MarkdownDescription: "The id of the instance flavor.", MarkdownDescription: "The id of the instance flavor.",

View file

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"github.com/hashicorp/terraform-plugin-framework/types"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/postgresflexalpha" "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/postgresflexalpha"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/conversion" "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/conversion"
postgresflexalpha2 "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/instance/datasources_gen" postgresflexalpha2 "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/instance/datasources_gen"
@ -26,6 +27,12 @@ func NewInstanceDataSource() datasource.DataSource {
return &instanceDataSource{} return &instanceDataSource{}
} }
// dataSourceModel maps the data source schema data.
type dataSourceModel struct {
postgresflexalpha2.InstanceModel
TerraformID types.String `tfsdk:"id"`
}
// instanceDataSource is the data source implementation. // instanceDataSource is the data source implementation.
type instanceDataSource struct { type instanceDataSource struct {
client *postgresflexalpha.APIClient client *postgresflexalpha.APIClient
@ -33,12 +40,20 @@ type instanceDataSource struct {
} }
// Metadata returns the data source type name. // Metadata returns the data source type name.
func (r *instanceDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { func (r *instanceDataSource) Metadata(
_ context.Context,
req datasource.MetadataRequest,
resp *datasource.MetadataResponse,
) {
resp.TypeName = req.ProviderTypeName + "_postgresflexalpha_instance" resp.TypeName = req.ProviderTypeName + "_postgresflexalpha_instance"
} }
// Configure adds the provider configured client to the data source. // Configure adds the provider configured client to the data source.
func (r *instanceDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { func (r *instanceDataSource) Configure(
ctx context.Context,
req datasource.ConfigureRequest,
resp *datasource.ConfigureResponse,
) {
var ok bool var ok bool
r.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics) r.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics)
if !ok { if !ok {
@ -59,8 +74,12 @@ func (r *instanceDataSource) Schema(ctx context.Context, _ datasource.SchemaRequ
} }
// Read refreshes the Terraform state with the latest data. // Read refreshes the Terraform state with the latest data.
func (r *instanceDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform func (r *instanceDataSource) Read(
var model postgresflexalpha2.InstanceModel ctx context.Context,
req datasource.ReadRequest,
resp *datasource.ReadResponse,
) { // nolint:gocritic // function signature required by Terraform
var model dataSourceModel
diags := req.Config.Get(ctx, &model) diags := req.Config.Get(ctx, &model)
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {

View file

@ -88,7 +88,7 @@ func InstanceDataSourceSchema(ctx context.Context) schema.Schema {
Description: "The id of the instance flavor.", Description: "The id of the instance flavor.",
MarkdownDescription: "The id of the instance flavor.", MarkdownDescription: "The id of the instance flavor.",
}, },
"id": schema.StringAttribute{ "tf_original_api_id": schema.StringAttribute{
Computed: true, Computed: true,
Description: "The ID of the instance.", Description: "The ID of the instance.",
MarkdownDescription: "The ID of the instance.", MarkdownDescription: "The ID of the instance.",
@ -204,7 +204,7 @@ type InstanceModel struct {
ConnectionInfo ConnectionInfoValue `tfsdk:"connection_info"` ConnectionInfo ConnectionInfoValue `tfsdk:"connection_info"`
Encryption EncryptionValue `tfsdk:"encryption"` Encryption EncryptionValue `tfsdk:"encryption"`
FlavorId types.String `tfsdk:"flavor_id"` FlavorId types.String `tfsdk:"flavor_id"`
Id types.String `tfsdk:"id"` Id types.String `tfsdk:"tf_original_api_id"`
InstanceId types.String `tfsdk:"instance_id"` InstanceId types.String `tfsdk:"instance_id"`
IsDeletable types.Bool `tfsdk:"is_deletable"` IsDeletable types.Bool `tfsdk:"is_deletable"`
Name types.String `tfsdk:"name"` Name types.String `tfsdk:"name"`

View file

@ -14,8 +14,13 @@ import (
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/utils" "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/utils"
) )
func mapGetInstanceResponseToModel(ctx context.Context, m *postgresflexalpharesource.InstanceModel, resp *postgresflex.GetInstanceResponse) error { func mapGetInstanceResponseToModel(
tflog.Debug(ctx, ">>>> MSH DEBUG <<<<", map[string]interface{}{ ctx context.Context,
m *postgresflexalpharesource.InstanceModel,
resp *postgresflex.GetInstanceResponse,
) error {
tflog.Debug(
ctx, ">>>> MSH DEBUG <<<<", map[string]interface{}{
"id": m.Id.ValueString(), "id": m.Id.ValueString(),
"instance_id": m.InstanceId.ValueString(), "instance_id": m.InstanceId.ValueString(),
"backup_schedule": m.BackupSchedule.ValueString(), "backup_schedule": m.BackupSchedule.ValueString(),
@ -33,7 +38,8 @@ func mapGetInstanceResponseToModel(ctx context.Context, m *postgresflexalphareso
"network.router_address": m.Network.RouterAddress.ValueString(), "network.router_address": m.Network.RouterAddress.ValueString(),
"version": m.Version.ValueString(), "version": m.Version.ValueString(),
"network.acl": m.Network.Acl.String(), "network.acl": m.Network.Acl.String(),
}) },
)
m.BackupSchedule = types.StringValue(resp.GetBackupSchedule()) m.BackupSchedule = types.StringValue(resp.GetBackupSchedule())
m.Encryption = postgresflexalpharesource.NewEncryptionValueNull() m.Encryption = postgresflexalpharesource.NewEncryptionValueNull()
@ -61,7 +67,11 @@ func mapGetInstanceResponseToModel(ctx context.Context, m *postgresflexalphareso
m.FlavorId = types.StringValue(resp.GetFlavorId()) m.FlavorId = types.StringValue(resp.GetFlavorId())
if m.Id.IsNull() || m.Id.IsUnknown() { if m.Id.IsNull() || m.Id.IsUnknown() {
m.Id = utils.BuildInternalTerraformId(m.ProjectId.ValueString(), m.Region.ValueString(), m.InstanceId.ValueString()) m.Id = utils.BuildInternalTerraformId(
m.ProjectId.ValueString(),
m.Region.ValueString(),
m.InstanceId.ValueString(),
)
} }
m.InstanceId = types.StringPointerValue(resp.Id) m.InstanceId = types.StringPointerValue(resp.Id)
@ -121,7 +131,11 @@ func mapGetInstanceResponseToModel(ctx context.Context, m *postgresflexalphareso
return nil return nil
} }
func mapGetDataInstanceResponseToModel(ctx context.Context, m *postgresflexalphadatasource.InstanceModel, resp *postgresflex.GetInstanceResponse) error { func mapGetDataInstanceResponseToModel(
ctx context.Context,
m *dataSourceModel,
resp *postgresflex.GetInstanceResponse,
) error {
m.BackupSchedule = types.StringValue(resp.GetBackupSchedule()) m.BackupSchedule = types.StringValue(resp.GetBackupSchedule())
handleEncryption(m, resp) handleEncryption(m, resp)
m.ConnectionInfo.Host = types.StringValue(resp.ConnectionInfo.GetHost()) m.ConnectionInfo.Host = types.StringValue(resp.ConnectionInfo.GetHost())
@ -155,7 +169,7 @@ func mapGetDataInstanceResponseToModel(ctx context.Context, m *postgresflexalpha
return nil return nil
} }
func handleNetwork(ctx context.Context, m *postgresflexalphadatasource.InstanceModel, resp *postgresflex.GetInstanceResponse) error { func handleNetwork(ctx context.Context, m *dataSourceModel, resp *postgresflex.GetInstanceResponse) error {
netAcl, diags := types.ListValueFrom(ctx, types.StringType, resp.Network.GetAcl()) netAcl, diags := types.ListValueFrom(ctx, types.StringType, resp.Network.GetAcl())
if diags.HasError() { if diags.HasError() {
return fmt.Errorf("failed converting network acl from response") return fmt.Errorf("failed converting network acl from response")
@ -187,7 +201,7 @@ func handleNetwork(ctx context.Context, m *postgresflexalphadatasource.InstanceM
return nil return nil
} }
func handleEncryption(m *postgresflexalphadatasource.InstanceModel, resp *postgresflex.GetInstanceResponse) { func handleEncryption(m *dataSourceModel, resp *postgresflex.GetInstanceResponse) {
keyId := "" keyId := ""
if keyIdVal, ok := resp.Encryption.GetKekKeyIdOk(); ok { if keyIdVal, ok := resp.Encryption.GetKekKeyIdOk(); ok {
keyId = keyIdVal keyId = keyIdVal

View file

@ -23,8 +23,6 @@ import (
wait "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/wait/postgresflexalpha" wait "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/wait/postgresflexalpha"
) )
const packageName = "postgresflexalpha"
// Ensure the implementation satisfies the expected interfaces. // Ensure the implementation satisfies the expected interfaces.
var ( var (
_ resource.Resource = &instanceResource{} _ resource.Resource = &instanceResource{}
@ -40,11 +38,8 @@ func NewInstanceResource() resource.Resource {
return &instanceResource{} return &instanceResource{}
} }
// instanceResource is the resource implementation. // resourceModel describes the resource data model.
type instanceResource struct { type resourceModel = postgresflexalpha.InstanceModel
client *postgresflex.APIClient
providerData core.ProviderData
}
type InstanceResourceIdentityModel struct { type InstanceResourceIdentityModel struct {
ProjectID types.String `tfsdk:"project_id"` ProjectID types.String `tfsdk:"project_id"`
@ -52,8 +47,18 @@ type InstanceResourceIdentityModel struct {
InstanceID types.String `tfsdk:"instance_id"` InstanceID types.String `tfsdk:"instance_id"`
} }
func (r *instanceResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { // instanceResource is the resource implementation.
var data postgresflexalpha.InstanceModel type instanceResource struct {
client *postgresflex.APIClient
providerData core.ProviderData
}
func (r *instanceResource) ValidateConfig(
ctx context.Context,
req resource.ValidateConfigRequest,
resp *resource.ValidateConfigResponse,
) {
var data resourceModel
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
@ -72,8 +77,12 @@ func (r *instanceResource) ValidateConfig(ctx context.Context, req resource.Vali
// ModifyPlan implements resource.ResourceWithModifyPlan. // ModifyPlan implements resource.ResourceWithModifyPlan.
// Use the modifier to set the effective region in the current plan. // Use the modifier to set the effective region in the current plan.
func (r *instanceResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { // nolint:gocritic // function signature required by Terraform func (r *instanceResource) ModifyPlan(
var configModel postgresflexalpha.InstanceModel ctx context.Context,
req resource.ModifyPlanRequest,
resp *resource.ModifyPlanResponse,
) { // nolint:gocritic // function signature required by Terraform
var configModel resourceModel
// skip initial empty configuration to avoid follow-up errors // skip initial empty configuration to avoid follow-up errors
if req.Config.Raw.IsNull() { if req.Config.Raw.IsNull() {
return return
@ -83,7 +92,7 @@ func (r *instanceResource) ModifyPlan(ctx context.Context, req resource.ModifyPl
return return
} }
var planModel postgresflexalpha.InstanceModel var planModel resourceModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &planModel)...) resp.Diagnostics.Append(req.Plan.Get(ctx, &planModel)...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
@ -135,13 +144,13 @@ var modifiersFileByte []byte
// Schema defines the schema for the resource. // Schema defines the schema for the resource.
func (r *instanceResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { func (r *instanceResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
schema := postgresflexalpha.InstanceResourceSchema(ctx) schema := postgresflexalpha.InstanceResourceSchema(ctx)
fields, err := postgresflexUtils.ReadModifiersConfig(modifiersFileByte) fields, err := utils.ReadModifiersConfig(modifiersFileByte)
if err != nil { if err != nil {
resp.Diagnostics.AddError("error during read modifiers config file", err.Error()) resp.Diagnostics.AddError("error during read modifiers config file", err.Error())
return return
} }
err = postgresflexUtils.AddPlanModifiersToResourceSchema(fields, &schema) err = utils.AddPlanModifiersToResourceSchema(fields, &schema)
if err != nil { if err != nil {
resp.Diagnostics.AddError("error adding plan modifiers", err.Error()) resp.Diagnostics.AddError("error adding plan modifiers", err.Error())
return return
@ -149,7 +158,11 @@ func (r *instanceResource) Schema(ctx context.Context, _ resource.SchemaRequest,
resp.Schema = schema resp.Schema = schema
} }
func (r *instanceResource) IdentitySchema(_ context.Context, _ resource.IdentitySchemaRequest, resp *resource.IdentitySchemaResponse) { func (r *instanceResource) IdentitySchema(
_ context.Context,
_ resource.IdentitySchemaRequest,
resp *resource.IdentitySchemaResponse,
) {
resp.IdentitySchema = identityschema.Schema{ resp.IdentitySchema = identityschema.Schema{
Attributes: map[string]identityschema.Attribute{ Attributes: map[string]identityschema.Attribute{
"project_id": identityschema.StringAttribute{ "project_id": identityschema.StringAttribute{
@ -171,7 +184,7 @@ func (r *instanceResource) Create(
req resource.CreateRequest, req resource.CreateRequest,
resp *resource.CreateResponse, resp *resource.CreateResponse,
) { // nolint:gocritic // function signature required by Terraform ) { // nolint:gocritic // function signature required by Terraform
var model postgresflexalpha.InstanceModel var model resourceModel
diags := req.Plan.Get(ctx, &model) diags := req.Plan.Get(ctx, &model)
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)
@ -201,7 +214,11 @@ func (r *instanceResource) Create(
payload := modelToCreateInstancePayload(netAcl, model, replVal) payload := modelToCreateInstancePayload(netAcl, model, replVal)
// Create new instance // Create new instance
createResp, err := r.client.CreateInstanceRequest(ctx, projectId, region).CreateInstanceRequestPayload(payload).Execute() createResp, err := r.client.CreateInstanceRequest(
ctx,
projectId,
region,
).CreateInstanceRequestPayload(payload).Execute()
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "error creating instance", fmt.Sprintf("Calling API: %v", err)) core.LogAndAddError(ctx, &resp.Diagnostics, "error creating instance", fmt.Sprintf("Calling API: %v", err))
return return
@ -227,13 +244,23 @@ func (r *instanceResource) Create(
waitResp, err := wait.CreateInstanceWaitHandler(ctx, r.client, projectId, region, instanceId).WaitWithContext(ctx) waitResp, err := wait.CreateInstanceWaitHandler(ctx, r.client, projectId, region, instanceId).WaitWithContext(ctx)
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Wait handler error: %v", err)) core.LogAndAddError(
ctx,
&resp.Diagnostics,
"Error creating instance",
fmt.Sprintf("Wait handler error: %v", err),
)
return return
} }
err = mapGetInstanceResponseToModel(ctx, &model, waitResp) err = mapGetInstanceResponseToModel(ctx, &model, waitResp)
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Error creating model: %v", err)) core.LogAndAddError(
ctx,
&resp.Diagnostics,
"Error creating instance",
fmt.Sprintf("Error creating model: %v", err),
)
return return
} }
@ -246,7 +273,11 @@ func (r *instanceResource) Create(
tflog.Info(ctx, "Postgres Flex instance created") tflog.Info(ctx, "Postgres Flex instance created")
} }
func modelToCreateInstancePayload(netAcl []string, model postgresflexalpha.InstanceModel, replVal int32) postgresflex.CreateInstanceRequestPayload { func modelToCreateInstancePayload(
netAcl []string,
model postgresflexalpha.InstanceModel,
replVal int32,
) postgresflex.CreateInstanceRequestPayload {
var enc *postgresflex.InstanceEncryption var enc *postgresflex.InstanceEncryption
if !model.Encryption.IsNull() && !model.Encryption.IsUnknown() { if !model.Encryption.IsNull() && !model.Encryption.IsUnknown() {
enc = &postgresflex.InstanceEncryption{ enc = &postgresflex.InstanceEncryption{
@ -279,10 +310,14 @@ func modelToCreateInstancePayload(netAcl []string, model postgresflexalpha.Insta
} }
// Read refreshes the Terraform state with the latest data. // Read refreshes the Terraform state with the latest data.
func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform func (r *instanceResource) Read(
ctx context.Context,
req resource.ReadRequest,
resp *resource.ReadResponse,
) { // nolint:gocritic // function signature required by Terraform
functionErrorSummary := "read instance failed" functionErrorSummary := "read instance failed"
var model postgresflexalpha.InstanceModel var model resourceModel
diags := req.State.Get(ctx, &model) diags := req.State.Get(ctx, &model)
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
@ -371,7 +406,12 @@ func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r
err = mapGetInstanceResponseToModel(ctx, &model, instanceResp) err = mapGetInstanceResponseToModel(ctx, &model, instanceResp)
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, functionErrorSummary, fmt.Sprintf("Processing API payload: %v", err)) core.LogAndAddError(
ctx,
&resp.Diagnostics,
functionErrorSummary,
fmt.Sprintf("Processing API payload: %v", err),
)
return return
} }
@ -396,8 +436,12 @@ func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r
} }
// Update updates the resource and sets the updated Terraform state on success. // Update updates the resource and sets the updated Terraform state on success.
func (r *instanceResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform func (r *instanceResource) Update(
var model postgresflexalpha.InstanceModel ctx context.Context,
req resource.UpdateRequest,
resp *resource.UpdateResponse,
) { // nolint:gocritic // function signature required by Terraform
var model resourceModel
diags := req.Plan.Get(ctx, &model) diags := req.Plan.Get(ctx, &model)
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)
@ -475,15 +519,31 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques
ctx = core.LogResponse(ctx) ctx = core.LogResponse(ctx)
waitResp, err := wait.PartialUpdateInstanceWaitHandler(ctx, r.client, projectId, region, instanceId).WaitWithContext(ctx) waitResp, err := wait.PartialUpdateInstanceWaitHandler(
ctx,
r.client,
projectId,
region,
instanceId,
).WaitWithContext(ctx)
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Instance update waiting: %v", err)) core.LogAndAddError(
ctx,
&resp.Diagnostics,
"Error updating instance",
fmt.Sprintf("Instance update waiting: %v", err),
)
return return
} }
err = mapGetInstanceResponseToModel(ctx, &model, waitResp) err = mapGetInstanceResponseToModel(ctx, &model, waitResp)
if err != nil { if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Processing API payload: %v", err)) core.LogAndAddError(
ctx,
&resp.Diagnostics,
"Error updating instance",
fmt.Sprintf("Processing API payload: %v", err),
)
return return
} }
@ -496,8 +556,12 @@ func (r *instanceResource) Update(ctx context.Context, req resource.UpdateReques
} }
// Delete deletes the resource and removes the Terraform state on success. // Delete deletes the resource and removes the Terraform state on success.
func (r *instanceResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { // nolint:gocritic // function signature required by Terraform func (r *instanceResource) Delete(
var model postgresflexalpha.InstanceModel ctx context.Context,
req resource.DeleteRequest,
resp *resource.DeleteResponse,
) { // nolint:gocritic // function signature required by Terraform
var model resourceModel
diags := req.State.Get(ctx, &model) diags := req.State.Get(ctx, &model)
resp.Diagnostics.Append(diags...) resp.Diagnostics.Append(diags...)
@ -538,16 +602,24 @@ func (r *instanceResource) Delete(ctx context.Context, req resource.DeleteReques
// ImportState imports a resource into the Terraform state on success. // ImportState imports a resource into the Terraform state on success.
// The expected format of the resource import identifier is: project_id,region,instance_id // The expected format of the resource import identifier is: project_id,region,instance_id
func (r *instanceResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { func (r *instanceResource) ImportState(
ctx context.Context,
req resource.ImportStateRequest,
resp *resource.ImportStateResponse,
) {
ctx = core.InitProviderContext(ctx) ctx = core.InitProviderContext(ctx)
if req.ID != "" { if req.ID != "" {
idParts := strings.Split(req.ID, core.Separator) idParts := strings.Split(req.ID, core.Separator)
if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" { if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" {
core.LogAndAddError(ctx, &resp.Diagnostics, core.LogAndAddError(
ctx, &resp.Diagnostics,
"Error importing instance", "Error importing instance",
fmt.Sprintf("Expected import identifier with format: [project_id],[region],[instance_id] Got: %q", req.ID), fmt.Sprintf(
"Expected import identifier with format: [project_id],[region],[instance_id] Got: %q",
req.ID,
),
) )
return return
} }
@ -558,25 +630,20 @@ func (r *instanceResource) ImportState(ctx context.Context, req resource.ImportS
return return
} }
// If no ID is provided, attempt to read identity attributes from the import configuration
var identityData InstanceResourceIdentityModel var identityData InstanceResourceIdentityModel
resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...) resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
} }