diff --git a/docs/data-sources/postgresql_credential.md b/docs/data-sources/postgresql_credential.md index d7a6872f..e934819e 100644 --- a/docs/data-sources/postgresql_credential.md +++ b/docs/data-sources/postgresql_credential.md @@ -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 diff --git a/docs/data-sources/postgresql_instance.md b/docs/data-sources/postgresql_instance.md index 046e1e2e..944cc752 100644 --- a/docs/data-sources/postgresql_instance.md +++ b/docs/data-sources/postgresql_instance.md @@ -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 diff --git a/docs/resources/postgresql_credential.md b/docs/resources/postgresql_credential.md index 45dcee0a..38f31539 100644 --- a/docs/resources/postgresql_credential.md +++ b/docs/resources/postgresql_credential.md @@ -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 diff --git a/docs/resources/postgresql_instance.md b/docs/resources/postgresql_instance.md index 574d7258..5e6120ea 100644 --- a/docs/resources/postgresql_instance.md +++ b/docs/resources/postgresql_instance.md @@ -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 diff --git a/stackit/internal/services/postgresql/credential/datasource.go b/stackit/internal/services/postgresql/credential/datasource.go index 56858b83..8ce05d1f 100644 --- a/stackit/internal/services/postgresql/credential/datasource.go +++ b/stackit/internal/services/postgresql/credential/datasource.go @@ -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)") } diff --git a/stackit/internal/services/postgresql/credential/resource.go b/stackit/internal/services/postgresql/credential/resource.go index 36ee0f3e..61461c55 100644 --- a/stackit/internal/services/postgresql/credential/resource.go +++ b/stackit/internal/services/postgresql/credential/resource.go @@ -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)") } diff --git a/stackit/internal/services/postgresql/credential/resource_test.go b/stackit/internal/services/postgresql/credential/resource_test.go deleted file mode 100644 index 499700f5..00000000 --- a/stackit/internal/services/postgresql/credential/resource_test.go +++ /dev/null @@ -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) - } - } - }) - } -} diff --git a/stackit/internal/services/postgresql/instance/datasource.go b/stackit/internal/services/postgresql/instance/datasource.go index cfab5dea..8cf20bc0 100644 --- a/stackit/internal/services/postgresql/instance/datasource.go +++ b/stackit/internal/services/postgresql/instance/datasource.go @@ -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)") } diff --git a/stackit/internal/services/postgresql/instance/resource.go b/stackit/internal/services/postgresql/instance/resource.go index f41fb7d7..14cfcbda 100644 --- a/stackit/internal/services/postgresql/instance/resource.go +++ b/stackit/internal/services/postgresql/instance/resource.go @@ -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 = ¶metersModel{} - 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 = ¶metersModel{} - 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)") } diff --git a/stackit/internal/services/postgresql/instance/resource_test.go b/stackit/internal/services/postgresql/instance/resource_test.go deleted file mode 100644 index 78fbf83a..00000000 --- a/stackit/internal/services/postgresql/instance/resource_test.go +++ /dev/null @@ -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{}, - ¶metersModel{}, - &[]string{}, - &postgresql.CreateInstancePayload{ - Parameters: &postgresql.InstanceParameters{ - Plugins: &[]string{}, - }, - }, - true, - }, - { - "nil_values", - &Model{}, - ¶metersModel{}, - nil, - &postgresql.CreateInstancePayload{ - Parameters: &postgresql.InstanceParameters{ - Plugins: nil, - }, - }, - true, - }, - { - "simple_values", - &Model{ - Name: types.StringValue("name"), - PlanId: types.StringValue("plan"), - }, - ¶metersModel{ - 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(""), - }, - ¶metersModel{ - 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, - ¶metersModel{}, - &[]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{}, - ¶metersModel{}, - &[]string{}, - &postgresql.PartialUpdateInstancePayload{ - Parameters: &postgresql.InstanceParameters{ - Plugins: &[]string{}, - }, - }, - true, - }, - { - "simple_values", - &Model{ - PlanId: types.StringValue("plan"), - }, - ¶metersModel{ - 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(""), - }, - ¶metersModel{ - 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, - ¶metersModel{}, - &[]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) - } - } - }) - } -} diff --git a/stackit/internal/services/postgresql/postgresql_acc_test.go b/stackit/internal/services/postgresql/postgresql_acc_test.go deleted file mode 100644 index ca3c2ce1..00000000 --- a/stackit/internal/services/postgresql/postgresql_acc_test.go +++ /dev/null @@ -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 -}