Remove CRUD method logic from PostgreSQL (first step of removal) (#423)

* Remove CRUD method logic from PostgreSQL (first step of removal)

* remove comment

* removed unused vars and parameters

* move verb tense to the past

* also datasource

* apply change to credential

* improve error message, remove acc testing

* update docs
This commit is contained in:
Diogo Ferrão 2024-07-01 10:06:54 +01:00 committed by GitHub
parent 2a0998f511
commit e553628b5e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 40 additions and 1628 deletions

View file

@ -4,14 +4,14 @@ page_title: "stackit_postgresql_credential Data Source - stackit"
subcategory: ""
description: |-
PostgreSQL credential data source schema. Must have a region specified in the provider configuration.
!> The STACKIT PostgreSQL service will reach its end of support on June 30th 2024. Data sources of this type will stop working after that. Use stackitpostgresflexuser instead. For more details, check https://docs.stackit.cloud/stackit/en/bring-your-data-to-stackit-postgresql-flex-138347648.html
!> The STACKIT PostgreSQL service has reached its end of support on June 30th 2024. Resources of this type have stopped working since then. Use stackitpostgresflexuser instead. For more details, check https://docs.stackit.cloud/stackit/en/bring-your-data-to-stackit-postgresql-flex-138347648.html
---
# stackit_postgresql_credential (Data Source)
PostgreSQL credential data source schema. Must have a `region` specified in the provider configuration.
!> The STACKIT PostgreSQL service will reach its end of support on June 30th 2024. Data sources of this type will stop working after that. Use stackit_postgresflex_user instead. For more details, check https://docs.stackit.cloud/stackit/en/bring-your-data-to-stackit-postgresql-flex-138347648.html
!> The STACKIT PostgreSQL service has reached its end of support on June 30th 2024. Resources of this type have stopped working since then. Use stackit_postgresflex_user instead. For more details, check https://docs.stackit.cloud/stackit/en/bring-your-data-to-stackit-postgresql-flex-138347648.html
## Example Usage

View file

@ -4,14 +4,14 @@ page_title: "stackit_postgresql_instance Data Source - stackit"
subcategory: ""
description: |-
PostgreSQL instance data source schema. Must have a region specified in the provider configuration.
!> The STACKIT PostgreSQL service will reach its end of support on June 30th 2024. Data sources of this type will stop working after that. Use stackitpostgresflexinstance instead. For more details, check https://docs.stackit.cloud/stackit/en/bring-your-data-to-stackit-postgresql-flex-138347648.html
!> The STACKIT PostgreSQL service has reached its end of support on June 30th 2024. Resources of this type have stopped working since then. Use stackitpostgresflexinstance instead. For more details, check https://docs.stackit.cloud/stackit/en/bring-your-data-to-stackit-postgresql-flex-138347648.html
---
# stackit_postgresql_instance (Data Source)
PostgreSQL instance data source schema. Must have a `region` specified in the provider configuration.
!> The STACKIT PostgreSQL service will reach its end of support on June 30th 2024. Data sources of this type will stop working after that. Use stackit_postgresflex_instance instead. For more details, check https://docs.stackit.cloud/stackit/en/bring-your-data-to-stackit-postgresql-flex-138347648.html
!> The STACKIT PostgreSQL service has reached its end of support on June 30th 2024. Resources of this type have stopped working since then. Use stackit_postgresflex_instance instead. For more details, check https://docs.stackit.cloud/stackit/en/bring-your-data-to-stackit-postgresql-flex-138347648.html
## Example Usage

View file

@ -4,14 +4,14 @@ page_title: "stackit_postgresql_credential Resource - stackit"
subcategory: ""
description: |-
PostgreSQL credential resource schema. Must have a region specified in the provider configuration.
!> The STACKIT PostgreSQL service will reach its end of support on June 30th 2024. Resources of this type will stop working after that. Use stackitpostgresflexuser instead. For more details, check https://docs.stackit.cloud/stackit/en/bring-your-data-to-stackit-postgresql-flex-138347648.html
!> The STACKIT PostgreSQL service has reached its end of support on June 30th 2024. Resources of this type have stopped working since then. Use stackitpostgresflexuser instead. For more details, check https://docs.stackit.cloud/stackit/en/bring-your-data-to-stackit-postgresql-flex-138347648.html
---
# stackit_postgresql_credential (Resource)
PostgreSQL credential resource schema. Must have a `region` specified in the provider configuration.
!> The STACKIT PostgreSQL service will reach its end of support on June 30th 2024. Resources of this type will stop working after that. Use stackit_postgresflex_user instead. For more details, check https://docs.stackit.cloud/stackit/en/bring-your-data-to-stackit-postgresql-flex-138347648.html
!> The STACKIT PostgreSQL service has reached its end of support on June 30th 2024. Resources of this type have stopped working since then. Use stackit_postgresflex_user instead. For more details, check https://docs.stackit.cloud/stackit/en/bring-your-data-to-stackit-postgresql-flex-138347648.html
## Example Usage

View file

@ -4,14 +4,14 @@ page_title: "stackit_postgresql_instance Resource - stackit"
subcategory: ""
description: |-
PostgreSQL instance resource schema. Must have a region specified in the provider configuration.
!> The STACKIT PostgreSQL service will reach its end of support on June 30th 2024. Resources of this type will stop working after that. Use stackitpostgresflexinstance instead. Check https://docs.stackit.cloud/stackit/en/bring-your-data-to-stackit-postgresql-flex-138347648.html on how to backup and restore an instance from PostgreSQL to PostgreSQL Flex, then import the resource to Terraform using an "import" block (https://developer.hashicorp.com/terraform/language/import)
!> The STACKIT PostgreSQL service has reached its end of support on June 30th 2024. Resources of this type have stopped working since then. Use stackitpostgresflexinstance instead. Check https://docs.stackit.cloud/stackit/en/bring-your-data-to-stackit-postgresql-flex-138347648.html on how to backup and restore an instance from PostgreSQL to PostgreSQL Flex, then import the resource to Terraform using an "import" block (https://developer.hashicorp.com/terraform/language/import)
---
# stackit_postgresql_instance (Resource)
PostgreSQL instance resource schema. Must have a `region` specified in the provider configuration.
!> The STACKIT PostgreSQL service will reach its end of support on June 30th 2024. Resources of this type will stop working after that. Use stackit_postgresflex_instance instead. Check https://docs.stackit.cloud/stackit/en/bring-your-data-to-stackit-postgresql-flex-138347648.html on how to backup and restore an instance from PostgreSQL to PostgreSQL Flex, then import the resource to Terraform using an "import" block (https://developer.hashicorp.com/terraform/language/import)
!> The STACKIT PostgreSQL service has reached its end of support on June 30th 2024. Resources of this type have stopped working since then. Use stackit_postgresflex_instance instead. Check https://docs.stackit.cloud/stackit/en/bring-your-data-to-stackit-postgresql-flex-138347648.html on how to backup and restore an instance from PostgreSQL to PostgreSQL Flex, then import the resource to Terraform using an "import" block (https://developer.hashicorp.com/terraform/language/import)
## Example Usage

View file

@ -3,7 +3,6 @@ package postgresql
import (
"context"
"fmt"
"net/http"
"strings"
"github.com/hashicorp/terraform-plugin-framework/datasource"
@ -15,7 +14,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/stackitcloud/stackit-sdk-go/core/config"
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
"github.com/stackitcloud/stackit-sdk-go/services/postgresql"
)
@ -81,8 +79,8 @@ func (r *credentialDataSource) Schema(_ context.Context, _ datasource.SchemaRequ
"main": "PostgreSQL credential data source schema. Must have a `region` specified in the provider configuration.",
"deprecation_message": strings.Join(
[]string{
"The STACKIT PostgreSQL service will reach its end of support on June 30th 2024.",
"Data sources of this type will stop working after that.",
"The STACKIT PostgreSQL service has reached its end of support on June 30th 2024.",
"Resources of this type have stopped working since then.",
"Use stackit_postgresflex_user instead.",
"For more details, check https://docs.stackit.cloud/stackit/en/bring-your-data-to-stackit-postgresql-flex-138347648.html",
},
@ -160,42 +158,6 @@ func (r *credentialDataSource) Schema(_ context.Context, _ datasource.SchemaRequ
}
// Read refreshes the Terraform state with the latest data.
func (r *credentialDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
var model Model
diags := req.Config.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
projectId := model.ProjectId.ValueString()
instanceId := model.InstanceId.ValueString()
credentialId := model.CredentialId.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "instance_id", instanceId)
ctx = tflog.SetField(ctx, "credential_id", credentialId)
recordSetResp, err := r.client.GetCredentials(ctx, projectId, instanceId, credentialId).Execute()
if err != nil {
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
if ok && oapiErr.StatusCode == http.StatusNotFound {
resp.State.RemoveResource(ctx)
}
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading credential", fmt.Sprintf("Calling API: %v", err))
return
}
// Map response body to schema
err = mapFields(recordSetResp, &model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading credential", fmt.Sprintf("Processing API payload: %v", err))
return
}
// Set refreshed state
diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
tflog.Info(ctx, "PostgreSQL credential read")
func (r *credentialDataSource) Read(ctx context.Context, _ datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading credential data", "The STACKIT PostgreSQL service has reached its end of support on June 30th 2024. Resources of this type have stopped working since then. Use stackit_postgresflex_instance instead. Check https://docs.stackit.cloud/stackit/en/bring-your-data-to-stackit-postgresql-flex-138347648.html on how to backup and restore an instance from PostgreSQL to PostgreSQL Flex, then import the resource to Terraform using an \"import\" block (https://developer.hashicorp.com/terraform/language/import)")
}

View file

@ -3,7 +3,6 @@ package postgresql
import (
"context"
"fmt"
"net/http"
"strings"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
@ -11,17 +10,13 @@ import (
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/stackitcloud/stackit-sdk-go/core/config"
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
"github.com/stackitcloud/stackit-sdk-go/services/postgresql"
"github.com/stackitcloud/stackit-sdk-go/services/postgresql/wait"
)
// Ensure the implementation satisfies the expected interfaces.
@ -103,8 +98,8 @@ func (r *credentialResource) Schema(_ context.Context, _ resource.SchemaRequest,
"main": "PostgreSQL credential resource schema. Must have a `region` specified in the provider configuration.",
"deprecation_message": strings.Join(
[]string{
"The STACKIT PostgreSQL service will reach its end of support on June 30th 2024.",
"Resources of this type will stop working after that.",
"The STACKIT PostgreSQL service has reached its end of support on June 30th 2024.",
"Resources of this type have stopped working since then.",
"Use stackit_postgresflex_user instead.",
"For more details, check https://docs.stackit.cloud/stackit/en/bring-your-data-to-stackit-postgresql-flex-138347648.html",
},
@ -196,196 +191,27 @@ func (r *credentialResource) Schema(_ context.Context, _ resource.SchemaRequest,
}
// Create creates the resource and sets the initial Terraform state.
func (r *credentialResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { // nolint:gocritic // function signature required by Terraform
var model Model
diags := req.Plan.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
projectId := model.ProjectId.ValueString()
instanceId := model.InstanceId.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "instance_id", instanceId)
// Create new recordset
credentialsResp, err := r.client.CreateCredentials(ctx, projectId, instanceId).Execute()
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating credential", fmt.Sprintf("Calling API: %v", err))
return
}
if credentialsResp.Id == nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating credential", "Got empty credential id")
return
}
credentialId := *credentialsResp.Id
ctx = tflog.SetField(ctx, "credential_id", credentialId)
waitResp, err := wait.CreateCredentialsWaitHandler(ctx, r.client, projectId, instanceId, credentialId).WaitWithContext(ctx)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating credential", fmt.Sprintf("Instance creation waiting: %v", err))
return
}
// Map response body to schema
err = mapFields(waitResp, &model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating credential", fmt.Sprintf("Processing API payload: %v", err))
return
}
diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
tflog.Info(ctx, "PostgreSQL credential created")
func (r *credentialResource) Create(ctx context.Context, _ resource.CreateRequest, resp *resource.CreateResponse) { // nolint:gocritic // function signature required by Terraform
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating credential", "The STACKIT PostgreSQL service has reached its end of support on June 30th 2024. Resources of this type have stopped working since then. Use stackit_postgresflex_instance instead. Check https://docs.stackit.cloud/stackit/en/bring-your-data-to-stackit-postgresql-flex-138347648.html on how to backup and restore an instance from PostgreSQL to PostgreSQL Flex, then import the resource to Terraform using an \"import\" block (https://developer.hashicorp.com/terraform/language/import)")
}
// Read refreshes the Terraform state with the latest data.
func (r *credentialResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
var model Model
diags := req.State.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
projectId := model.ProjectId.ValueString()
instanceId := model.InstanceId.ValueString()
credentialId := model.CredentialId.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "instance_id", instanceId)
ctx = tflog.SetField(ctx, "credential_id", credentialId)
recordSetResp, err := r.client.GetCredentials(ctx, projectId, instanceId, credentialId).Execute()
if err != nil {
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
if ok && oapiErr.StatusCode == http.StatusNotFound {
resp.State.RemoveResource(ctx)
return
}
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading credential", fmt.Sprintf("Calling API: %v", err))
return
}
// Map response body to schema
err = mapFields(recordSetResp, &model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading credential", fmt.Sprintf("Processing API payload: %v", err))
return
}
// Set refreshed state
diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
tflog.Info(ctx, "PostgreSQL credential read")
func (r *credentialResource) Read(ctx context.Context, _ resource.ReadRequest, resp *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading credential", "The STACKIT PostgreSQL service has reached its end of support on June 30th 2024. Resources of this type have stopped working since then. Use stackit_postgresflex_instance instead. Check https://docs.stackit.cloud/stackit/en/bring-your-data-to-stackit-postgresql-flex-138347648.html on how to backup and restore an instance from PostgreSQL to PostgreSQL Flex, then import the resource to Terraform using an \"import\" block (https://developer.hashicorp.com/terraform/language/import)")
}
// Update updates the resource and sets the updated Terraform state on success.
func (r *credentialResource) Update(ctx context.Context, _ resource.UpdateRequest, resp *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform
// Update shouldn't be called
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating credential", "Credential can't be updated")
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating credential", "The STACKIT PostgreSQL service has reached its end of support on June 30th 2024. Resources of this type have stopped working since then. Use stackit_postgresflex_instance instead. Check https://docs.stackit.cloud/stackit/en/bring-your-data-to-stackit-postgresql-flex-138347648.html on how to backup and restore an instance from PostgreSQL to PostgreSQL Flex, then import the resource to Terraform using an \"import\" block (https://developer.hashicorp.com/terraform/language/import)")
}
// Delete deletes the resource and removes the Terraform state on success.
func (r *credentialResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { // nolint:gocritic // function signature required by Terraform
var model Model
diags := req.State.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
projectId := model.ProjectId.ValueString()
instanceId := model.InstanceId.ValueString()
credentialId := model.CredentialId.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "instance_id", instanceId)
ctx = tflog.SetField(ctx, "credential_id", credentialId)
// Delete existing record set
err := r.client.DeleteCredentials(ctx, projectId, instanceId, credentialId).Execute()
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting credential", fmt.Sprintf("Calling API: %v", err))
}
_, err = wait.DeleteCredentialsWaitHandler(ctx, r.client, projectId, instanceId, credentialId).WaitWithContext(ctx)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting credential", fmt.Sprintf("Instance deletion waiting: %v", err))
return
}
tflog.Info(ctx, "PostgreSQL credential deleted")
func (r *credentialResource) Delete(ctx context.Context, _ resource.DeleteRequest, resp *resource.DeleteResponse) { // nolint:gocritic // function signature required by Terraform
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting credential", "The STACKIT PostgreSQL service has reached its end of support on June 30th 2024. Resources of this type have stopped working since then. Use stackit_postgresflex_instance instead. Check https://docs.stackit.cloud/stackit/en/bring-your-data-to-stackit-postgresql-flex-138347648.html on how to backup and restore an instance from PostgreSQL to PostgreSQL Flex, then import the resource to Terraform using an \"import\" block (https://developer.hashicorp.com/terraform/language/import)")
}
// ImportState imports a resource into the Terraform state on success.
// The expected format of the resource import identifier is: project_id,instance_id,credential_id
func (r *credentialResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
idParts := strings.Split(req.ID, core.Separator)
if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" {
core.LogAndAddError(ctx, &resp.Diagnostics,
"Error importing credential",
fmt.Sprintf("Expected import identifier with format [project_id],[instance_id],[credential_id], got %q", req.ID),
)
return
}
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), idParts[0])...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("instance_id"), idParts[1])...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("credential_id"), idParts[2])...)
tflog.Info(ctx, "PostgreSQL credential state imported")
}
func mapFields(credentialsResp *postgresql.CredentialsResponse, model *Model) error {
if credentialsResp == nil {
return fmt.Errorf("response input is nil")
}
if credentialsResp.Raw == nil {
return fmt.Errorf("response credentials raw is nil")
}
if model == nil {
return fmt.Errorf("model input is nil")
}
credentials := credentialsResp.Raw.Credentials
var credentialId string
if model.CredentialId.ValueString() != "" {
credentialId = model.CredentialId.ValueString()
} else if credentialsResp.Id != nil {
credentialId = *credentialsResp.Id
} else {
return fmt.Errorf("credentials id not present")
}
idParts := []string{
model.ProjectId.ValueString(),
model.InstanceId.ValueString(),
credentialId,
}
model.Id = types.StringValue(
strings.Join(idParts, core.Separator),
)
model.CredentialId = types.StringValue(credentialId)
model.Hosts = types.ListNull(types.StringType)
if credentials != nil {
if credentials.Hosts != nil {
var hosts []attr.Value
for _, host := range *credentials.Hosts {
hosts = append(hosts, types.StringValue(host))
}
hostsList, diags := types.ListValue(types.StringType, hosts)
if diags.HasError() {
return fmt.Errorf("failed to map hosts: %w", core.DiagsToError(diags))
}
model.Hosts = hostsList
}
model.Host = types.StringPointerValue(credentials.Host)
model.HttpAPIURI = types.StringPointerValue(credentials.HttpApiUri)
model.Name = types.StringPointerValue(credentials.Name)
model.Password = types.StringPointerValue(credentials.Password)
model.Port = types.Int64PointerValue(credentials.Port)
model.Uri = types.StringPointerValue(credentials.Uri)
model.Username = types.StringPointerValue(credentials.Username)
}
return nil
// The expected format of the resource import identifier is: project_id,instance_id
func (r *credentialResource) ImportState(ctx context.Context, _ resource.ImportStateRequest, resp *resource.ImportStateResponse) {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error importing credential", "The STACKIT PostgreSQL service has reached its end of support on June 30th 2024. Resources of this type have stopped working since then. Use stackit_postgresflex_instance instead. Check https://docs.stackit.cloud/stackit/en/bring-your-data-to-stackit-postgresql-flex-138347648.html on how to backup and restore an instance from PostgreSQL to PostgreSQL Flex, then import the resource to Terraform using an \"import\" block (https://developer.hashicorp.com/terraform/language/import)")
}

View file

@ -1,156 +0,0 @@
package postgresql
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/stackitcloud/stackit-sdk-go/core/utils"
"github.com/stackitcloud/stackit-sdk-go/services/postgresql"
)
func TestMapFields(t *testing.T) {
tests := []struct {
description string
input *postgresql.CredentialsResponse
expected Model
isValid bool
}{
{
"default_values",
&postgresql.CredentialsResponse{
Id: utils.Ptr("cid"),
Raw: &postgresql.RawCredentials{},
},
Model{
Id: types.StringValue("pid,iid,cid"),
CredentialId: types.StringValue("cid"),
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
Host: types.StringNull(),
Hosts: types.ListNull(types.StringType),
HttpAPIURI: types.StringNull(),
Name: types.StringNull(),
Password: types.StringNull(),
Port: types.Int64Null(),
Uri: types.StringNull(),
Username: types.StringNull(),
},
true,
},
{
"simple_values",
&postgresql.CredentialsResponse{
Id: utils.Ptr("cid"),
Raw: &postgresql.RawCredentials{
Credentials: &postgresql.Credentials{
Host: utils.Ptr("host"),
Hosts: &[]string{
"host_1",
"",
},
HttpApiUri: utils.Ptr("http"),
Name: utils.Ptr("name"),
Password: utils.Ptr("password"),
Port: utils.Ptr(int64(1234)),
Uri: utils.Ptr("uri"),
Username: utils.Ptr("username"),
},
},
},
Model{
Id: types.StringValue("pid,iid,cid"),
CredentialId: types.StringValue("cid"),
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
Host: types.StringValue("host"),
Hosts: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("host_1"),
types.StringValue(""),
}),
HttpAPIURI: types.StringValue("http"),
Name: types.StringValue("name"),
Password: types.StringValue("password"),
Port: types.Int64Value(1234),
Uri: types.StringValue("uri"),
Username: types.StringValue("username"),
},
true,
},
{
"null_fields_and_int_conversions",
&postgresql.CredentialsResponse{
Id: utils.Ptr("cid"),
Raw: &postgresql.RawCredentials{
Credentials: &postgresql.Credentials{
Host: utils.Ptr(""),
Hosts: &[]string{},
HttpApiUri: nil,
Name: nil,
Password: nil,
Port: utils.Ptr(int64(2123456789)),
Uri: nil,
Username: nil,
},
},
},
Model{
Id: types.StringValue("pid,iid,cid"),
CredentialId: types.StringValue("cid"),
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
Host: types.StringValue(""),
Hosts: types.ListValueMust(types.StringType, []attr.Value{}),
HttpAPIURI: types.StringNull(),
Name: types.StringNull(),
Password: types.StringNull(),
Port: types.Int64Value(2123456789),
Uri: types.StringNull(),
Username: types.StringNull(),
},
true,
},
{
"nil_response",
nil,
Model{},
false,
},
{
"no_resource_id",
&postgresql.CredentialsResponse{},
Model{},
false,
},
{
"nil_raw_credential",
&postgresql.CredentialsResponse{
Id: utils.Ptr("cid"),
},
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)
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)
}
}
})
}
}

View file

@ -3,7 +3,6 @@ package postgresql
import (
"context"
"fmt"
"net/http"
"strings"
"github.com/hashicorp/terraform-plugin-framework/datasource"
@ -15,7 +14,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/stackitcloud/stackit-sdk-go/core/config"
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
"github.com/stackitcloud/stackit-sdk-go/services/postgresql"
)
@ -81,8 +79,8 @@ func (r *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaReques
"main": "PostgreSQL instance data source schema. Must have a `region` specified in the provider configuration.",
"deprecation_message": strings.Join(
[]string{
"The STACKIT PostgreSQL service will reach its end of support on June 30th 2024.",
"Data sources of this type will stop working after that.",
"The STACKIT PostgreSQL service has reached its end of support on June 30th 2024.",
"Resources of this type have stopped working since then.",
"Use stackit_postgresflex_instance instead.",
"For more details, check https://docs.stackit.cloud/stackit/en/bring-your-data-to-stackit-postgresql-flex-138347648.html",
},
@ -183,46 +181,6 @@ func (r *instanceDataSource) Schema(_ context.Context, _ datasource.SchemaReques
}
// 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
var model Model
diags := req.Config.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
projectId := model.ProjectId.ValueString()
instanceId := model.InstanceId.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "instance_id", instanceId)
instanceResp, err := r.client.GetInstance(ctx, projectId, instanceId).Execute()
if err != nil {
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
if ok && (oapiErr.StatusCode == http.StatusNotFound || oapiErr.StatusCode == http.StatusGone) {
resp.State.RemoveResource(ctx)
}
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Calling API: %v", err))
return
}
err = mapFields(instanceResp, &model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Processing API payload: %v", err))
return
}
// Compute and store values not present in the API response
err = loadPlanNameAndVersion(ctx, r.client, &model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Loading service plan details: %v", err))
return
}
// Set refreshed state
diags = resp.State.Set(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
tflog.Info(ctx, "PostgreSQL instance read")
func (r *instanceDataSource) Read(ctx context.Context, _ datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance data", "The STACKIT PostgreSQL service has reached its end of support on June 30th 2024. Resources of this type have stopped working since then. Use stackit_postgresflex_instance instead. Check https://docs.stackit.cloud/stackit/en/bring-your-data-to-stackit-postgresql-flex-138347648.html on how to backup and restore an instance from PostgreSQL to PostgreSQL Flex, then import the resource to Terraform using an \"import\" block (https://developer.hashicorp.com/terraform/language/import)")
}

View file

@ -3,28 +3,21 @@ package postgresql
import (
"context"
"fmt"
"net/http"
"strings"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/stackitcloud/stackit-sdk-go/core/config"
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
"github.com/stackitcloud/stackit-sdk-go/services/postgresql"
"github.com/stackitcloud/stackit-sdk-go/services/postgresql/wait"
)
// Ensure the implementation satisfies the expected interfaces.
@ -50,26 +43,6 @@ type Model struct {
PlanId types.String `tfsdk:"plan_id"`
}
// Struct corresponding to DataSourceModel.Parameters
type parametersModel struct {
EnableMonitoring types.Bool `tfsdk:"enable_monitoring"`
MetricsFrequency types.Int64 `tfsdk:"metrics_frequency"`
MetricsPrefix types.String `tfsdk:"metrics_prefix"`
MonitoringInstanceId types.String `tfsdk:"monitoring_instance_id"`
Plugins types.List `tfsdk:"plugins"`
SgwAcl types.String `tfsdk:"sgw_acl"`
}
// Types corresponding to parametersModel
var parametersTypes = map[string]attr.Type{
"enable_monitoring": basetypes.BoolType{},
"metrics_frequency": basetypes.Int64Type{},
"metrics_prefix": basetypes.StringType{},
"monitoring_instance_id": basetypes.StringType{},
"plugins": basetypes.ListType{ElemType: types.StringType},
"sgw_acl": basetypes.StringType{},
}
// NewInstanceResource is a helper function to simplify the provider implementation.
func NewInstanceResource() resource.Resource {
return &instanceResource{}
@ -127,8 +100,8 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r
"main": "PostgreSQL instance resource schema. Must have a `region` specified in the provider configuration.",
"deprecation_message": strings.Join(
[]string{
"The STACKIT PostgreSQL service will reach its end of support on June 30th 2024.",
"Resources of this type will stop working after that.",
"The STACKIT PostgreSQL service has reached its end of support on June 30th 2024.",
"Resources of this type have stopped working since then.",
"Use stackit_postgresflex_instance instead.",
"Check https://docs.stackit.cloud/stackit/en/bring-your-data-to-stackit-postgresql-flex-138347648.html on how to backup and restore an instance from PostgreSQL to PostgreSQL Flex, then import the resource to Terraform using an \"import\" block (https://developer.hashicorp.com/terraform/language/import)",
},
@ -263,488 +236,27 @@ func (r *instanceResource) Schema(_ context.Context, _ resource.SchemaRequest, r
}
// Create creates the resource and sets the initial Terraform state.
func (r *instanceResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { // nolint:gocritic // function signature required by Terraform
var model Model
diags := req.Plan.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
projectId := model.ProjectId.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId)
var parameters = &parametersModel{}
var parametersPlugins *[]string
if !(model.Parameters.IsNull() || model.Parameters.IsUnknown()) {
diags = model.Parameters.As(ctx, parameters, basetypes.ObjectAsOptions{})
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
if !(parameters.Plugins.IsNull() || parameters.Plugins.IsUnknown()) {
var pp []types.String
var res []string
diags = parameters.Plugins.ElementsAs(ctx, &pp, false)
resp.Diagnostics.Append(diags...)
for _, v := range pp {
res = append(res, v.ValueString())
}
parametersPlugins = &res
}
if resp.Diagnostics.HasError() {
return
}
}
err := r.loadPlanId(ctx, &model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Loading service plan: %v", err))
return
}
// Generate API request body from model
payload, err := toCreatePayload(&model, parameters, parametersPlugins)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Creating API payload: %v", err))
return
}
// Create new instance
createResp, err := r.client.CreateInstance(ctx, projectId).CreateInstancePayload(*payload).Execute()
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Calling API: %v", err))
return
}
instanceId := *createResp.InstanceId
ctx = tflog.SetField(ctx, "instance_id", instanceId)
waitResp, err := wait.CreateInstanceWaitHandler(ctx, r.client, projectId, instanceId).WaitWithContext(ctx)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Instance creation waiting: %v", err))
return
}
// Map response body to schema
err = mapFields(waitResp, &model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Processing API payload: %v", err))
return
}
// Set state to fully populated data
diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
tflog.Info(ctx, "Postgresql instance created")
}
func toCreatePayload(model *Model, parameters *parametersModel, parametersPlugins *[]string) (*postgresql.CreateInstancePayload, error) {
if model == nil {
return nil, fmt.Errorf("nil model")
}
if parameters == nil {
return &postgresql.CreateInstancePayload{
InstanceName: conversion.StringValueToPointer(model.Name),
PlanId: conversion.StringValueToPointer(model.PlanId),
}, nil
}
return &postgresql.CreateInstancePayload{
InstanceName: conversion.StringValueToPointer(model.Name),
Parameters: &postgresql.InstanceParameters{
EnableMonitoring: conversion.BoolValueToPointer(parameters.EnableMonitoring),
MetricsFrequency: conversion.Int64ValueToPointer(parameters.MetricsFrequency),
MetricsPrefix: conversion.StringValueToPointer(parameters.MetricsPrefix),
MonitoringInstanceId: conversion.StringValueToPointer(parameters.MonitoringInstanceId),
Plugins: parametersPlugins,
SgwAcl: conversion.StringValueToPointer(parameters.SgwAcl),
},
PlanId: conversion.StringValueToPointer(model.PlanId),
}, nil
func (r *instanceResource) Create(ctx context.Context, _ resource.CreateRequest, resp *resource.CreateResponse) { // nolint:gocritic // function signature required by Terraform
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", "The STACKIT PostgreSQL service has reached its end of support on June 30th 2024. Resources of this type have stopped working since then. Use stackit_postgresflex_instance instead. Check https://docs.stackit.cloud/stackit/en/bring-your-data-to-stackit-postgresql-flex-138347648.html on how to backup and restore an instance from PostgreSQL to PostgreSQL Flex, then import the resource to Terraform using an \"import\" block (https://developer.hashicorp.com/terraform/language/import)")
}
// 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
var model Model
diags := req.State.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
projectId := model.ProjectId.ValueString()
instanceId := model.InstanceId.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "instance_id", instanceId)
instanceResp, err := r.client.GetInstance(ctx, projectId, instanceId).Execute()
if err != nil {
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
if ok && (oapiErr.StatusCode == http.StatusNotFound || oapiErr.StatusCode == http.StatusGone) {
resp.State.RemoveResource(ctx)
return
}
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Calling API: %v", err))
return
}
// Map response body to schema
err = mapFields(instanceResp, &model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Processing API payload: %v", err))
return
}
// Compute and store values not present in the API response
err = loadPlanNameAndVersion(ctx, r.client, &model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Loading service plan details: %v", err))
return
}
// Set refreshed state
diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
tflog.Info(ctx, "PostgreSQL instance read")
func (r *instanceResource) Read(ctx context.Context, _ resource.ReadRequest, resp *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", "The STACKIT PostgreSQL service has reached its end of support on June 30th 2024. Resources of this type have stopped working since then. Use stackit_postgresflex_instance instead. Check https://docs.stackit.cloud/stackit/en/bring-your-data-to-stackit-postgresql-flex-138347648.html on how to backup and restore an instance from PostgreSQL to PostgreSQL Flex, then import the resource to Terraform using an \"import\" block (https://developer.hashicorp.com/terraform/language/import)")
}
// 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
var model Model
diags := req.Plan.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
projectId := model.ProjectId.ValueString()
instanceId := model.InstanceId.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "instance_id", instanceId)
var parameters = &parametersModel{}
var parametersPlugins *[]string
if !(model.Parameters.IsNull() || model.Parameters.IsUnknown()) {
diags = model.Parameters.As(ctx, parameters, basetypes.ObjectAsOptions{})
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
if !(parameters.Plugins.IsNull() || parameters.Plugins.IsUnknown()) {
var pp []types.String
var res []string
diags = parameters.Plugins.ElementsAs(ctx, &pp, false)
resp.Diagnostics.Append(diags...)
for _, v := range pp {
res = append(res, v.ValueString())
}
parametersPlugins = &res
}
}
err := r.loadPlanId(ctx, &model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Loading service plan: %v", err))
return
}
// Generate API request body from model
payload, err := toUpdatePayload(&model, parameters, parametersPlugins)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Creating API payload: %v", err))
return
}
// Update existing instance
err = r.client.PartialUpdateInstance(ctx, projectId, instanceId).PartialUpdateInstancePayload(*payload).Execute()
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Calling API: %v", err))
return
}
waitResp, err := wait.PartialUpdateInstanceWaitHandler(ctx, r.client, projectId, instanceId).WaitWithContext(ctx)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Instance update waiting: %v", err))
return
}
// Map response body to schema
err = mapFields(waitResp, &model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", fmt.Sprintf("Processing API payload: %v", err))
return
}
diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
tflog.Info(ctx, "PostgreSQL instance updated")
}
func toUpdatePayload(model *Model, parameters *parametersModel, parametersPlugins *[]string) (*postgresql.PartialUpdateInstancePayload, error) {
if model == nil {
return nil, fmt.Errorf("nil model")
}
if parameters == nil {
return &postgresql.PartialUpdateInstancePayload{
PlanId: conversion.StringValueToPointer(model.PlanId),
}, nil
}
return &postgresql.PartialUpdateInstancePayload{
Parameters: &postgresql.InstanceParameters{
EnableMonitoring: conversion.BoolValueToPointer(parameters.EnableMonitoring),
MetricsFrequency: conversion.Int64ValueToPointer(parameters.MetricsFrequency),
MetricsPrefix: conversion.StringValueToPointer(parameters.MetricsPrefix),
MonitoringInstanceId: conversion.StringValueToPointer(parameters.MonitoringInstanceId),
Plugins: parametersPlugins,
SgwAcl: conversion.StringValueToPointer(parameters.SgwAcl),
},
PlanId: conversion.StringValueToPointer(model.PlanId),
}, nil
func (r *instanceResource) Update(ctx context.Context, _ resource.UpdateRequest, resp *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating instance", "The STACKIT PostgreSQL service has reached its end of support on June 30th 2024. Resources of this type have stopped working since then. Use stackit_postgresflex_instance instead. Check https://docs.stackit.cloud/stackit/en/bring-your-data-to-stackit-postgresql-flex-138347648.html on how to backup and restore an instance from PostgreSQL to PostgreSQL Flex, then import the resource to Terraform using an \"import\" block (https://developer.hashicorp.com/terraform/language/import)")
}
// Delete deletes the resource and removes the Terraform state on success.
func (r *instanceResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { // nolint:gocritic // function signature required by Terraform
// Retrieve values from state
var model Model
diags := req.State.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
projectId := model.ProjectId.ValueString()
instanceId := model.InstanceId.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "instance_id", instanceId)
// Delete existing instance
err := r.client.DeleteInstance(ctx, projectId, instanceId).Execute()
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting instance", fmt.Sprintf("Calling API: %v", err))
return
}
_, err = wait.DeleteInstanceWaitHandler(ctx, r.client, projectId, instanceId).WaitWithContext(ctx)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting instance", fmt.Sprintf("Instance deletion waiting: %v", err))
return
}
tflog.Info(ctx, "PostgreSQL instance deleted")
func (r *instanceResource) Delete(ctx context.Context, _ resource.DeleteRequest, resp *resource.DeleteResponse) { // nolint:gocritic // function signature required by Terraform
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting instance", "The STACKIT PostgreSQL service has reached its end of support on June 30th 2024. Resources of this type have stopped working since then. Use stackit_postgresflex_instance instead. Check https://docs.stackit.cloud/stackit/en/bring-your-data-to-stackit-postgresql-flex-138347648.html on how to backup and restore an instance from PostgreSQL to PostgreSQL Flex, then import the resource to Terraform using an \"import\" block (https://developer.hashicorp.com/terraform/language/import)")
}
// ImportState imports a resource into the Terraform state on success.
// The expected format of the resource import identifier is: project_id,instance_id
func (r *instanceResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
idParts := strings.Split(req.ID, core.Separator)
if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" {
core.LogAndAddError(ctx, &resp.Diagnostics,
"Error importing instance",
fmt.Sprintf("Expected import identifier with format: [project_id],[instance_id] Got: %q", req.ID),
)
return
}
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), idParts[0])...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("instance_id"), idParts[1])...)
tflog.Info(ctx, "PostgreSQL instance state imported")
}
func mapFields(instance *postgresql.Instance, model *Model) error {
if instance == nil {
return fmt.Errorf("response input is nil")
}
if model == nil {
return fmt.Errorf("model input is nil")
}
var instanceId string
if model.InstanceId.ValueString() != "" {
instanceId = model.InstanceId.ValueString()
} else if instance.InstanceId != nil {
instanceId = *instance.InstanceId
} else {
return fmt.Errorf("instance id not present")
}
idParts := []string{
model.ProjectId.ValueString(),
instanceId,
}
model.Id = types.StringValue(
strings.Join(idParts, core.Separator),
)
model.InstanceId = types.StringValue(instanceId)
model.PlanId = types.StringPointerValue(instance.PlanId)
model.CfGuid = types.StringPointerValue(instance.CfGuid)
model.CfSpaceGuid = types.StringPointerValue(instance.CfSpaceGuid)
model.DashboardUrl = types.StringPointerValue(instance.DashboardUrl)
model.ImageUrl = types.StringPointerValue(instance.ImageUrl)
model.Name = types.StringPointerValue(instance.Name)
model.CfOrganizationGuid = types.StringPointerValue(instance.CfOrganizationGuid)
if instance.Parameters == nil {
model.Parameters = types.ObjectNull(parametersTypes)
} else {
parameters, err := mapParameters(*instance.Parameters)
if err != nil {
return fmt.Errorf("mapping parameters: %w", err)
}
model.Parameters = parameters
}
return nil
}
func mapParameters(params map[string]interface{}) (types.Object, error) {
attributes := map[string]attr.Value{}
for attribute := range parametersTypes {
valueInterface, ok := params[attribute]
if !ok {
// All fields are optional, so this is ok
// Set the value as nil, will be handled accordingly
valueInterface = nil
}
var value attr.Value
switch parametersTypes[attribute].(type) {
default:
return types.ObjectNull(parametersTypes), fmt.Errorf("found unexpected attribute type '%T'", parametersTypes[attribute])
case basetypes.StringType:
if valueInterface == nil {
value = types.StringNull()
} else {
valueString, ok := valueInterface.(string)
if !ok {
return types.ObjectNull(parametersTypes), fmt.Errorf("found attribute '%s' of type %T, failed to assert as string", attribute, valueInterface)
}
value = types.StringValue(valueString)
}
case basetypes.BoolType:
if valueInterface == nil {
value = types.BoolNull()
} else {
valueBool, ok := valueInterface.(bool)
if !ok {
return types.ObjectNull(parametersTypes), fmt.Errorf("found attribute '%s' of type %T, failed to assert as bool", attribute, valueInterface)
}
value = types.BoolValue(valueBool)
}
case basetypes.Int64Type:
if valueInterface == nil {
value = types.Int64Null()
} else {
// This may be int64, int32, int or float64
// We try to assert all 4
var valueInt64 int64
switch temp := valueInterface.(type) {
default:
return types.ObjectNull(parametersTypes), fmt.Errorf("found attribute '%s' of type %T, failed to assert as int", attribute, valueInterface)
case int64:
valueInt64 = temp
case int32:
valueInt64 = int64(temp)
case int:
valueInt64 = int64(temp)
case float64:
valueInt64 = int64(temp)
}
value = types.Int64Value(valueInt64)
}
case basetypes.ListType: // Assumed to be a list of strings
if valueInterface == nil {
value = types.ListNull(types.StringType)
} else {
// This may be []string{} or []interface{}
// We try to assert all 2
var valueList []attr.Value
switch temp := valueInterface.(type) {
default:
return types.ObjectNull(parametersTypes), fmt.Errorf("found attribute '%s' of type %T, failed to assert as array of interface", attribute, valueInterface)
case []string:
for _, x := range temp {
valueList = append(valueList, types.StringValue(x))
}
case []interface{}:
for _, x := range temp {
xString, ok := x.(string)
if !ok {
return types.ObjectNull(parametersTypes), fmt.Errorf("found attribute '%s' with element '%s' of type %T, failed to assert as string", attribute, x, x)
}
valueList = append(valueList, types.StringValue(xString))
}
}
temp2, diags := types.ListValue(types.StringType, valueList)
if diags.HasError() {
return types.ObjectNull(parametersTypes), fmt.Errorf("failed to map %s: %w", attribute, core.DiagsToError(diags))
}
value = temp2
}
}
attributes[attribute] = value
}
output, diags := types.ObjectValue(parametersTypes, attributes)
if diags.HasError() {
return types.ObjectNull(parametersTypes), fmt.Errorf("failed to create object: %w", core.DiagsToError(diags))
}
return output, nil
}
func (r *instanceResource) loadPlanId(ctx context.Context, model *Model) error {
projectId := model.ProjectId.ValueString()
res, err := r.client.ListOfferings(ctx, projectId).Execute()
if err != nil {
return fmt.Errorf("getting PostgreSQL offerings: %w", err)
}
version := model.Version.ValueString()
planName := model.PlanName.ValueString()
availableVersions := ""
availablePlanNames := ""
isValidVersion := false
for _, offer := range *res.Offerings {
if !strings.EqualFold(*offer.Version, version) {
availableVersions = fmt.Sprintf("%s\n- %s", availableVersions, *offer.Version)
continue
}
isValidVersion = true
for _, plan := range *offer.Plans {
if plan.Name == nil {
continue
}
if strings.EqualFold(*plan.Name, planName) && plan.Id != nil {
model.PlanId = types.StringPointerValue(plan.Id)
return nil
}
availablePlanNames = fmt.Sprintf("%s\n- %s", availablePlanNames, *plan.Name)
}
}
if !isValidVersion {
return fmt.Errorf("couldn't find version '%s', available versions are: %s", version, availableVersions)
}
return fmt.Errorf("couldn't find plan_name '%s' for version %s, available names are: %s", planName, version, availablePlanNames)
}
func loadPlanNameAndVersion(ctx context.Context, client *postgresql.APIClient, model *Model) error {
projectId := model.ProjectId.ValueString()
planId := model.PlanId.ValueString()
res, err := client.ListOfferings(ctx, projectId).Execute()
if err != nil {
return fmt.Errorf("getting PostgreSQL offerings: %w", err)
}
for _, offer := range *res.Offerings {
for _, plan := range *offer.Plans {
if strings.EqualFold(*plan.Id, planId) && plan.Id != nil {
model.PlanName = types.StringPointerValue(plan.Name)
model.Version = types.StringPointerValue(offer.Version)
return nil
}
}
}
return fmt.Errorf("couldn't find plan_name and version for plan_id '%s'", planId)
func (r *instanceResource) ImportState(ctx context.Context, _ resource.ImportStateRequest, resp *resource.ImportStateResponse) {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error importing instance", "The STACKIT PostgreSQL service has reached its end of support on June 30th 2024. Resources of this type have stopped working since then. Use stackit_postgresflex_instance instead. Check https://docs.stackit.cloud/stackit/en/bring-your-data-to-stackit-postgresql-flex-138347648.html on how to backup and restore an instance from PostgreSQL to PostgreSQL Flex, then import the resource to Terraform using an \"import\" block (https://developer.hashicorp.com/terraform/language/import)")
}

View file

@ -1,435 +0,0 @@
package postgresql
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/stackitcloud/stackit-sdk-go/core/utils"
"github.com/stackitcloud/stackit-sdk-go/services/postgresql"
)
func TestMapFields(t *testing.T) {
tests := []struct {
description string
input *postgresql.Instance
expected Model
isValid bool
}{
{
"default_values",
&postgresql.Instance{},
Model{
Id: types.StringValue("pid,iid"),
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
PlanId: types.StringNull(),
Name: types.StringNull(),
CfGuid: types.StringNull(),
CfSpaceGuid: types.StringNull(),
DashboardUrl: types.StringNull(),
ImageUrl: types.StringNull(),
CfOrganizationGuid: types.StringNull(),
Parameters: types.ObjectNull(parametersTypes),
},
true,
},
{
"simple_values",
&postgresql.Instance{
PlanId: utils.Ptr("plan"),
CfGuid: utils.Ptr("cf"),
CfSpaceGuid: utils.Ptr("space"),
DashboardUrl: utils.Ptr("dashboard"),
ImageUrl: utils.Ptr("image"),
InstanceId: utils.Ptr("iid"),
Name: utils.Ptr("name"),
CfOrganizationGuid: utils.Ptr("org"),
Parameters: &map[string]interface{}{
"enable_monitoring": true,
"metrics_frequency": 1234,
"plugins": []string{
"plugin_1",
"plugin_2",
"",
},
},
},
Model{
Id: types.StringValue("pid,iid"),
InstanceId: types.StringValue("iid"),
ProjectId: types.StringValue("pid"),
PlanId: types.StringValue("plan"),
Name: types.StringValue("name"),
CfGuid: types.StringValue("cf"),
CfSpaceGuid: types.StringValue("space"),
DashboardUrl: types.StringValue("dashboard"),
ImageUrl: types.StringValue("image"),
CfOrganizationGuid: types.StringValue("org"),
Parameters: types.ObjectValueMust(parametersTypes, map[string]attr.Value{
"enable_monitoring": types.BoolValue(true),
"metrics_frequency": types.Int64Value(1234),
"metrics_prefix": types.StringNull(),
"monitoring_instance_id": types.StringNull(),
"plugins": types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("plugin_1"),
types.StringValue("plugin_2"),
types.StringValue(""),
}),
"sgw_acl": types.StringNull(),
}),
},
true,
},
{
"nil_response",
nil,
Model{},
false,
},
{
"no_resource_id",
&postgresql.Instance{},
Model{},
false,
},
{
"wrong_param_types_1",
&postgresql.Instance{
Parameters: &map[string]interface{}{
"enable_monitoring": "true",
},
},
Model{},
false,
},
{
"wrong_param_types_2",
&postgresql.Instance{
Parameters: &map[string]interface{}{
"metrics_frequency": true,
},
},
Model{},
false,
},
{
"wrong_param_types_3",
&postgresql.Instance{
Parameters: &map[string]interface{}{
"metrics_frequency": 12.34,
},
},
Model{},
false,
},
{
"wrong_param_types_4",
&postgresql.Instance{
Parameters: &map[string]interface{}{
"plugins": "foo",
},
},
Model{},
false,
},
{
"wrong_param_types_5",
&postgresql.Instance{
Parameters: &map[string]interface{}{
"plugins": []bool{
true,
},
},
},
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)
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
inputParameters *parametersModel
inputParametersPlugins *[]string
expected *postgresql.CreateInstancePayload
isValid bool
}{
{
"default_values",
&Model{},
&parametersModel{},
&[]string{},
&postgresql.CreateInstancePayload{
Parameters: &postgresql.InstanceParameters{
Plugins: &[]string{},
},
},
true,
},
{
"nil_values",
&Model{},
&parametersModel{},
nil,
&postgresql.CreateInstancePayload{
Parameters: &postgresql.InstanceParameters{
Plugins: nil,
},
},
true,
},
{
"simple_values",
&Model{
Name: types.StringValue("name"),
PlanId: types.StringValue("plan"),
},
&parametersModel{
EnableMonitoring: types.BoolValue(true),
MetricsFrequency: types.Int64Value(123),
MetricsPrefix: types.StringValue("prefix"),
MonitoringInstanceId: types.StringValue("monitoring"),
SgwAcl: types.StringValue("sgw"),
},
&[]string{
"plugin_1",
"plugin_2",
},
&postgresql.CreateInstancePayload{
InstanceName: utils.Ptr("name"),
Parameters: &postgresql.InstanceParameters{
EnableMonitoring: utils.Ptr(true),
MetricsFrequency: utils.Ptr(int64(123)),
MetricsPrefix: utils.Ptr("prefix"),
MonitoringInstanceId: utils.Ptr("monitoring"),
Plugins: &[]string{
"plugin_1",
"plugin_2",
},
SgwAcl: utils.Ptr("sgw"),
},
PlanId: utils.Ptr("plan"),
},
true,
},
{
"null_fields_and_int_conversions",
&Model{
Name: types.StringValue(""),
PlanId: types.StringValue(""),
},
&parametersModel{
EnableMonitoring: types.BoolNull(),
MetricsFrequency: types.Int64Value(2123456789),
MetricsPrefix: types.StringNull(),
MonitoringInstanceId: types.StringNull(),
SgwAcl: types.StringNull(),
},
&[]string{
"",
},
&postgresql.CreateInstancePayload{
InstanceName: utils.Ptr(""),
Parameters: &postgresql.InstanceParameters{
EnableMonitoring: nil,
MetricsFrequency: utils.Ptr(int64(2123456789)),
MetricsPrefix: nil,
MonitoringInstanceId: nil,
Plugins: &[]string{
"",
},
SgwAcl: nil,
},
PlanId: utils.Ptr(""),
},
true,
},
{
"nil_model",
nil,
&parametersModel{},
&[]string{},
nil,
false,
},
{
"nil_parameters",
&Model{
Name: types.StringValue("name"),
PlanId: types.StringValue("plan"),
},
nil,
nil,
&postgresql.CreateInstancePayload{
InstanceName: utils.Ptr("name"),
PlanId: utils.Ptr("plan"),
},
true,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
output, err := toCreatePayload(tt.input, tt.inputParameters, tt.inputParametersPlugins)
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 TestToUpdatePayload(t *testing.T) {
tests := []struct {
description string
input *Model
inputParameters *parametersModel
inputParametersPlugins *[]string
expected *postgresql.PartialUpdateInstancePayload
isValid bool
}{
{
"default_values",
&Model{},
&parametersModel{},
&[]string{},
&postgresql.PartialUpdateInstancePayload{
Parameters: &postgresql.InstanceParameters{
Plugins: &[]string{},
},
},
true,
},
{
"simple_values",
&Model{
PlanId: types.StringValue("plan"),
},
&parametersModel{
EnableMonitoring: types.BoolValue(true),
MetricsFrequency: types.Int64Value(123),
MetricsPrefix: types.StringValue("prefix"),
MonitoringInstanceId: types.StringValue("monitoring"),
SgwAcl: types.StringValue("sgw"),
},
&[]string{
"plugin_1",
"plugin_2",
},
&postgresql.PartialUpdateInstancePayload{
Parameters: &postgresql.InstanceParameters{
EnableMonitoring: utils.Ptr(true),
MetricsFrequency: utils.Ptr(int64(123)),
MetricsPrefix: utils.Ptr("prefix"),
MonitoringInstanceId: utils.Ptr("monitoring"),
Plugins: &[]string{
"plugin_1",
"plugin_2",
},
SgwAcl: utils.Ptr("sgw"),
},
PlanId: utils.Ptr("plan"),
},
true,
},
{
"null_fields_and_int_conversions",
&Model{
PlanId: types.StringValue(""),
},
&parametersModel{
EnableMonitoring: types.BoolNull(),
MetricsFrequency: types.Int64Value(2123456789),
MetricsPrefix: types.StringNull(),
MonitoringInstanceId: types.StringNull(),
SgwAcl: types.StringNull(),
},
&[]string{
"",
},
&postgresql.PartialUpdateInstancePayload{
Parameters: &postgresql.InstanceParameters{
EnableMonitoring: nil,
MetricsFrequency: utils.Ptr(int64(2123456789)),
MetricsPrefix: nil,
MonitoringInstanceId: nil,
Plugins: &[]string{
"",
},
SgwAcl: nil,
},
PlanId: utils.Ptr(""),
},
true,
},
{
"nil_model",
nil,
&parametersModel{},
&[]string{},
nil,
false,
},
{
"nil_parameters",
&Model{
PlanId: types.StringValue("plan"),
},
nil,
nil,
&postgresql.PartialUpdateInstancePayload{
PlanId: utils.Ptr("plan"),
},
true,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
output, err := toUpdatePayload(tt.input, tt.inputParameters, tt.inputParametersPlugins)
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)
}
}
})
}
}

View file

@ -1,255 +0,0 @@
package postgresql_test
import (
"context"
"fmt"
"strings"
"testing"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/terraform"
"github.com/stackitcloud/stackit-sdk-go/core/config"
"github.com/stackitcloud/stackit-sdk-go/core/utils"
"github.com/stackitcloud/stackit-sdk-go/services/postgresql"
"github.com/stackitcloud/stackit-sdk-go/services/postgresql/wait"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil"
)
// Instance resource data
var instanceResource = map[string]string{
"project_id": testutil.ProjectId,
"name": testutil.ResourceNameWithDateTime("postgresql"),
"plan_id": "57d40175-0f4c-4bcc-b52d-cf5d2ee9f5a7",
"plan_name": "stackit-qa-postgresql-1.4.10-single",
"version": "13",
"sgw_acl": "192.168.0.0/16",
"metrics_frequency": "34",
"plugins": "foo-bar",
}
func resourceConfig(acls, frequency, plugins string) string {
return fmt.Sprintf(`
%s
resource "stackit_postgresql_instance" "instance" {
project_id = "%s"
name = "%s"
plan_name = "%s"
version = "%s"
parameters = {
sgw_acl = "%s"
plugins = ["%s"]
# metrics_frequency = %s
# metrics_prefix = "pre"
# enable_monitoring = true
# monitoring_instance_id = "b9e38481-4f3d-4a28-8ed0-43fd32c024c7"
}
}
resource "stackit_postgresql_credential" "credential" {
project_id = stackit_postgresql_instance.instance.project_id
instance_id = stackit_postgresql_instance.instance.instance_id
}
`,
testutil.PostgreSQLProviderConfig(),
instanceResource["project_id"],
instanceResource["name"],
instanceResource["plan_name"],
instanceResource["version"],
acls,
plugins,
frequency,
)
}
func TestAccPostgreSQLResource(t *testing.T) {
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
CheckDestroy: testAccCheckPostgreSQLDestroy,
Steps: []resource.TestStep{
// Creation
{
Config: resourceConfig(instanceResource["sgw_acl"], instanceResource["metrics_frequency"], instanceResource["plugins"]),
Check: resource.ComposeAggregateTestCheckFunc(
// Instance data
resource.TestCheckResourceAttr("stackit_postgresql_instance.instance", "project_id", instanceResource["project_id"]),
resource.TestCheckResourceAttrSet("stackit_postgresql_instance.instance", "instance_id"),
resource.TestCheckResourceAttr("stackit_postgresql_instance.instance", "plan_id", instanceResource["plan_id"]),
resource.TestCheckResourceAttr("stackit_postgresql_instance.instance", "plan_name", instanceResource["plan_name"]),
resource.TestCheckResourceAttr("stackit_postgresql_instance.instance", "version", instanceResource["version"]),
resource.TestCheckResourceAttr("stackit_postgresql_instance.instance", "name", instanceResource["name"]),
resource.TestCheckResourceAttr("stackit_postgresql_instance.instance", "parameters.sgw_acl", instanceResource["sgw_acl"]),
// Credential data
resource.TestCheckResourceAttrPair(
"stackit_postgresql_credential.credential", "project_id",
"stackit_postgresql_instance.instance", "project_id",
),
resource.TestCheckResourceAttrPair(
"stackit_postgresql_credential.credential", "instance_id",
"stackit_postgresql_instance.instance", "instance_id",
),
resource.TestCheckResourceAttrSet("stackit_postgresql_credential.credential", "credential_id"),
resource.TestCheckResourceAttrSet("stackit_postgresql_credential.credential", "host"),
),
},
{ // Data source
Config: fmt.Sprintf(`
%s
data "stackit_postgresql_instance" "instance" {
project_id = stackit_postgresql_instance.instance.project_id
instance_id = stackit_postgresql_instance.instance.instance_id
}
data "stackit_postgresql_credential" "credential" {
project_id = stackit_postgresql_credential.credential.project_id
instance_id = stackit_postgresql_credential.credential.instance_id
credential_id = stackit_postgresql_credential.credential.credential_id
}`,
resourceConfig(instanceResource["sgw_acl"], instanceResource["metrics_frequency"], instanceResource["plugins"]),
),
Check: resource.ComposeAggregateTestCheckFunc(
// Instance data
resource.TestCheckResourceAttr("data.stackit_postgresql_instance.instance", "project_id", instanceResource["project_id"]),
resource.TestCheckResourceAttrPair("stackit_postgresql_instance.instance", "instance_id",
"data.stackit_postgresql_instance.instance", "instance_id"),
resource.TestCheckResourceAttrPair("stackit_postgresql_credential.credential", "credential_id",
"data.stackit_postgresql_credential.credential", "credential_id"),
resource.TestCheckResourceAttr("data.stackit_postgresql_instance.instance", "plan_id", instanceResource["plan_id"]),
resource.TestCheckResourceAttr("data.stackit_postgresql_instance.instance", "name", instanceResource["name"]),
resource.TestCheckResourceAttr("data.stackit_postgresql_instance.instance", "parameters.sgw_acl", instanceResource["sgw_acl"]),
resource.TestCheckResourceAttr("data.stackit_postgresql_instance.instance", "parameters.plugins.#", "1"),
resource.TestCheckResourceAttr("data.stackit_postgresql_instance.instance", "parameters.plugins.0", instanceResource["plugins"]),
// Credential data
resource.TestCheckResourceAttr("data.stackit_postgresql_credential.credential", "project_id", instanceResource["project_id"]),
resource.TestCheckResourceAttrSet("data.stackit_postgresql_credential.credential", "credential_id"),
resource.TestCheckResourceAttrSet("data.stackit_postgresql_credential.credential", "host"),
resource.TestCheckResourceAttrSet("data.stackit_postgresql_credential.credential", "port"),
resource.TestCheckResourceAttrSet("data.stackit_postgresql_credential.credential", "uri"),
),
},
// Import
{
ResourceName: "stackit_postgresql_instance.instance",
ImportStateIdFunc: func(s *terraform.State) (string, error) {
r, ok := s.RootModule().Resources["stackit_postgresql_instance.instance"]
if !ok {
return "", fmt.Errorf("couldn't find resource stackit_postgresql_instance.instance")
}
instanceId, ok := r.Primary.Attributes["instance_id"]
if !ok {
return "", fmt.Errorf("couldn't find attribute instance_id")
}
return fmt.Sprintf("%s,%s", testutil.ProjectId, instanceId), nil
},
ImportState: true,
ImportStateVerify: true,
},
{
ResourceName: "stackit_postgresql_credential.credential",
ImportStateIdFunc: func(s *terraform.State) (string, error) {
r, ok := s.RootModule().Resources["stackit_postgresql_credential.credential"]
if !ok {
return "", fmt.Errorf("couldn't find resource stackit_postgresql_credential.credential")
}
instanceId, ok := r.Primary.Attributes["instance_id"]
if !ok {
return "", fmt.Errorf("couldn't find attribute instance_id")
}
credentialId, ok := r.Primary.Attributes["credential_id"]
if !ok {
return "", fmt.Errorf("couldn't find attribute credential_id")
}
return fmt.Sprintf("%s,%s,%s", testutil.ProjectId, instanceId, credentialId), nil
},
ImportState: true,
ImportStateVerify: true,
},
// Update
{
Config: resourceConfig(instanceResource["sgw_acl"], fmt.Sprintf("%s0", instanceResource["metrics_frequency"]), fmt.Sprintf("%s-baz", instanceResource["plugins"])),
Check: resource.ComposeAggregateTestCheckFunc(
// Instance data
resource.TestCheckResourceAttr("stackit_postgresql_instance.instance", "project_id", instanceResource["project_id"]),
resource.TestCheckResourceAttrSet("stackit_postgresql_instance.instance", "instance_id"),
resource.TestCheckResourceAttr("stackit_postgresql_instance.instance", "plan_id", instanceResource["plan_id"]),
resource.TestCheckResourceAttr("stackit_postgresql_instance.instance", "plan_name", instanceResource["plan_name"]),
resource.TestCheckResourceAttr("stackit_postgresql_instance.instance", "version", instanceResource["version"]),
resource.TestCheckResourceAttr("stackit_postgresql_instance.instance", "name", instanceResource["name"]),
resource.TestCheckResourceAttr("stackit_postgresql_instance.instance", "parameters.sgw_acl", instanceResource["sgw_acl"]),
resource.TestCheckResourceAttr("stackit_postgresql_instance.instance", "parameters.plugins.0", fmt.Sprintf("%s-baz", instanceResource["plugins"])),
),
},
// Deletion is done by the framework implicitly
},
})
}
func testAccCheckPostgreSQLDestroy(s *terraform.State) error {
ctx := context.Background()
var client *postgresql.APIClient
var err error
if testutil.PostgreSQLCustomEndpoint == "" {
client, err = postgresql.NewAPIClient()
} else {
client, err = postgresql.NewAPIClient(
config.WithEndpoint(testutil.PostgreSQLCustomEndpoint),
)
}
if err != nil {
return fmt.Errorf("creating client: %w", err)
}
instancesToDestroy := []string{}
for _, rs := range s.RootModule().Resources {
if rs.Type != "stackit_postgresql_instance" {
continue
}
// instance terraform ID: "[project_id],[instance_id]"
instanceId := strings.Split(rs.Primary.ID, core.Separator)[1]
instancesToDestroy = append(instancesToDestroy, instanceId)
}
instancesResp, err := client.ListInstances(ctx, testutil.ProjectId).Execute()
if err != nil {
return fmt.Errorf("getting instancesResp: %w", err)
}
instances := *instancesResp.Instances
for i := range instances {
if instances[i].InstanceId == nil {
continue
}
if utils.Contains(instancesToDestroy, *instances[i].InstanceId) {
if !checkInstanceDeleteSuccess(&instances[i]) {
err := client.DeleteInstanceExecute(ctx, testutil.ProjectId, *instances[i].InstanceId)
if err != nil {
return fmt.Errorf("destroying instance %s during CheckDestroy: %w", *instances[i].InstanceId, err)
}
_, err = wait.DeleteInstanceWaitHandler(ctx, client, testutil.ProjectId, *instances[i].InstanceId).WaitWithContext(ctx)
if err != nil {
return fmt.Errorf("destroying instance %s during CheckDestroy: waiting for deletion %w", *instances[i].InstanceId, err)
}
}
}
}
return nil
}
func checkInstanceDeleteSuccess(i *postgresql.Instance) bool {
if *i.LastOperation.Type != wait.InstanceTypeDelete {
return false
}
if *i.LastOperation.Type == wait.InstanceTypeDelete {
if *i.LastOperation.State != wait.InstanceStateSuccess {
return false
} else if strings.Contains(*i.LastOperation.Description, "DeleteFailed") || strings.Contains(*i.LastOperation.Description, "failed") {
return false
}
}
return true
}