diff --git a/stackit/internal/services/postgresflexalpha/database/resource.go b/stackit/internal/services/postgresflexalpha/database/resource.go index 169c3e99..f27fbc4e 100644 --- a/stackit/internal/services/postgresflexalpha/database/resource.go +++ b/stackit/internal/services/postgresflexalpha/database/resource.go @@ -14,9 +14,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/identityschema" "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" - "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" @@ -25,24 +22,33 @@ import ( "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/core" postgresflexalpha2 "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/database/resources_gen" postgresflexUtils "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/utils" + "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/utils" ) -// Ensure the implementation satisfies the expected interfaces. var ( + // Ensure the implementation satisfies the expected interfaces. _ resource.Resource = &databaseResource{} _ resource.ResourceWithConfigure = &databaseResource{} _ resource.ResourceWithImportState = &databaseResource{} _ resource.ResourceWithModifyPlan = &databaseResource{} _ resource.ResourceWithIdentity = &databaseResource{} + // Define errors errDatabaseNotFound = errors.New("database not found") + + // Error message constants + extractErrorSummary = "extracting failed" + extractErrorMessage = "Extracting identity data: %v" ) // ResourceModel describes the resource data model. type ResourceModel struct { postgresflexalpha2.DatabaseModel - TerraformID types.String `tfsdk:"id"` + ProjectID types.String `tfsdk:"project_id"` + Region types.String `tfsdk:"region"` + InstanceID types.String `tfsdk:"instance_id"` DatabaseID types.Int64 `tfsdk:"database_id"` + TerraformID types.String `tfsdk:"id"` } // DatabaseResourceIdentityModel describes the resource's identity attributes. @@ -86,7 +92,7 @@ func (r *databaseResource) ModifyPlan( return } - //TODO utils.AdaptRegion(ctx, configModel.Region, &planModel.Region, r.providerData.GetRegion(), resp) + utils.AdaptRegion(ctx, configModel.Region, &planModel.Region, r.providerData.GetRegion(), resp) if resp.Diagnostics.HasError() { return } @@ -128,19 +134,33 @@ var modifiersFileByte []byte // Schema defines the schema for the resource. func (r *databaseResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { s := postgresflexalpha2.DatabaseResourceSchema(ctx) + + s.Attributes["project_id"] = schema.StringAttribute{ + Description: "STACKIT project ID to which the instance is associated.", + MarkdownDescription: "STACKIT project ID to which the instance is associated.", + Required: true, + } + s.Attributes["instance_id"] = schema.StringAttribute{ + Description: "ID of the PostgresFlex instance.", + MarkdownDescription: "ID of the PostgresFlex instance.", + Required: true, + } + s.Attributes["region"] = schema.StringAttribute{ + Description: "Region of the PostgresFlex instance.", + MarkdownDescription: "Region of the PostgresFlex instance.", + Optional: true, + Computed: true, + } + s.Attributes["database_id"] = schema.Int64Attribute{ + Description: "The ID of the database.", + Computed: true, + } + s.Attributes["id"] = schema.StringAttribute{ Description: "Terraform's internal resource ID. It is structured as \\\"`project_id`,`region`,`instance_id`,`database_id`\\\".\",", Optional: true, Computed: true, } - s.Attributes["database_id"] = schema.Int64Attribute{ - Description: "ID of the database.", - Computed: true, - PlanModifiers: []planmodifier.Int64{ - int64planmodifier.UseStateForUnknown(), - }, - Validators: []validator.Int64{}, - } fields, err := postgresflexUtils.ReadModifiersConfig(modifiersFileByte) if err != nil { @@ -300,10 +320,16 @@ func (r *databaseResource) Read( ctx = core.InitProviderContext(ctx) - projectId := identityData.ProjectID.ValueString() - instanceId := identityData.InstanceID.ValueString() - databaseId := model.DatabaseID.ValueInt64() - region := r.providerData.GetRegionWithOverride(identityData.Region) + projectId, instanceId, region, databaseId, errExt := r.extractIdentityData(model, identityData) + if errExt != nil { + core.LogAndAddError( + ctx, + &resp.Diagnostics, + extractErrorSummary, + fmt.Sprintf(extractErrorMessage, errExt), + ) + } + ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "region", region) @@ -365,11 +391,16 @@ func (r *databaseResource) Update( ctx = core.InitProviderContext(ctx) - projectId := identityData.ProjectID.ValueString() - instanceId := identityData.InstanceID.ValueString() - region := r.providerData.GetRegionWithOverride(identityData.Region) + projectId, instanceId, region, databaseId64, errExt := r.extractIdentityData(model, identityData) + if errExt != nil { + core.LogAndAddError( + ctx, + &resp.Diagnostics, + extractErrorSummary, + fmt.Sprintf(extractErrorMessage, errExt), + ) + } - databaseId64 := model.DatabaseID.ValueInt64() // database id if databaseId64 > math.MaxInt32 { core.LogAndAddError(ctx, &resp.Diagnostics, "Error in type conversion", "int value too large (databaseId)") return @@ -463,10 +494,15 @@ func (r *databaseResource) Delete( ctx = core.InitProviderContext(ctx) - projectId := identityData.ProjectID.ValueString() - instanceId := identityData.InstanceID.ValueString() - region := r.providerData.GetRegionWithOverride(identityData.Region) - databaseId64 := model.DatabaseID.ValueInt64() //database id + projectId, instanceId, region, databaseId64, errExt := r.extractIdentityData(model, identityData) + if errExt != nil { + core.LogAndAddError( + ctx, + &resp.Diagnostics, + extractErrorSummary, + fmt.Sprintf(extractErrorMessage, errExt), + ) + } if databaseId64 > math.MaxInt32 { core.LogAndAddError(ctx, &resp.Diagnostics, "Error in type conversion", "int value too large (databaseId)") @@ -525,13 +561,12 @@ func (r *databaseResource) ImportState( resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("instance_id"), idParts[2])...) resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("database_id"), databaseId)...) - //TODO: Investigate if this logic is still required. - //core.LogAndAddWarning( - // ctx, - // &resp.Diagnostics, - // "Postgresflex database imported with empty password", - // "The database password is not imported as it is only available upon creation of a new database. The password field will be empty.", - //) + core.LogAndAddWarning( + ctx, + &resp.Diagnostics, + "Postgresflex database imported with empty password", + "The database password is not imported as it is only available upon creation of a new database. The password field will be empty.", + ) var identityData DatabaseResourceIdentityModel identityData.ProjectID = types.StringValue(idParts[0]) @@ -539,7 +574,7 @@ func (r *databaseResource) ImportState( identityData.InstanceID = types.StringValue(idParts[2]) identityData.DatabaseID = types.Int64Value(databaseId) - resp.Diagnostics.Append(req.Identity.Set(ctx, &identityData)...) + resp.Diagnostics.Append(resp.Identity.Set(ctx, &identityData)...) if resp.Diagnostics.HasError() { return @@ -548,3 +583,46 @@ func (r *databaseResource) ImportState( tflog.Info(ctx, "Postgres Flex instance state imported") } + +// extractIdentityData extracts essential identifiers from the resource model, falling back to the identity model. +func (r *databaseResource) extractIdentityData( + model ResourceModel, + identity DatabaseResourceIdentityModel, +) (projectId, region, instanceId string, databaseId int64, err error) { + if !model.DatabaseID.IsNull() && !model.DatabaseID.IsUnknown() { + databaseId = model.DatabaseID.ValueInt64() + } else { + if identity.DatabaseID.IsNull() || identity.DatabaseID.IsUnknown() { + return "", "", "", 0, fmt.Errorf("database_id not found in config") + } + databaseId = identity.DatabaseID.ValueInt64() + } + + if !model.ProjectID.IsNull() && !model.ProjectID.IsUnknown() { + projectId = model.ProjectID.ValueString() + } else { + if identity.ProjectID.IsNull() || identity.ProjectID.IsUnknown() { + return "", "", "", 0, fmt.Errorf("project_id not found in config") + } + projectId = identity.ProjectID.ValueString() + } + + if !model.Region.IsNull() && !model.Region.IsUnknown() { + region = r.providerData.GetRegionWithOverride(model.Region) + } else { + if identity.Region.IsNull() || identity.Region.IsUnknown() { + return "", "", "", 0, fmt.Errorf("region not found in config") + } + region = r.providerData.GetRegionWithOverride(identity.Region) + } + + if !model.InstanceID.IsNull() && !model.InstanceID.IsUnknown() { + instanceId = model.InstanceID.ValueString() + } else { + if identity.InstanceID.IsNull() || identity.InstanceID.IsUnknown() { + return "", "", "", 0, fmt.Errorf("instance_id not found in config") + } + instanceId = identity.InstanceID.ValueString() + } + return projectId, region, instanceId, databaseId, nil +}