feat: add database_id attribute to resource model and update related functions

This commit is contained in:
Andre_Harms 2026-02-05 09:14:03 +01:00
parent 502b2f5e0a
commit 91913c3446
6 changed files with 90 additions and 65 deletions

View file

@ -26,6 +26,7 @@ type DataSourceModel struct {
ProjectId types.String `tfsdk:"project_id"` ProjectId types.String `tfsdk:"project_id"`
InstanceId types.String `tfsdk:"instance_id"` InstanceId types.String `tfsdk:"instance_id"`
Region types.String `tfsdk:"region"` Region types.String `tfsdk:"region"`
DatabaseID types.Int64 `tfsdk:"database_id"`
TerraformID types.String `tfsdk:"tf_id"` TerraformID types.String `tfsdk:"tf_id"`
} }
@ -93,8 +94,14 @@ func (r *databaseDataSource) Schema(ctx context.Context, _ datasource.SchemaRequ
MarkdownDescription: "Region of the PostgresFlex instance.", MarkdownDescription: "Region of the PostgresFlex instance.",
Optional: true, Optional: true,
} }
s.Attributes["database_id"] = schema.Int64Attribute{
Description: "The ID of the database.",
Optional: true,
Computed: true,
}
s.Attributes["tf_id"] = schema.StringAttribute{ s.Attributes["tf_id"] = schema.StringAttribute{
Description: "Terraform's internal resource ID. It is structured as \\\"`project_id`,`region`,`instance_id`,`id`\\\".\",", Description: "Terraform's internal resource ID. It is structured as \\\"`project_id`,`region`,`instance_id`," +
"`database_id`\\\".\",",
Optional: true, Optional: true,
Computed: true, Computed: true,
} }
@ -164,7 +171,7 @@ func (r *databaseDataSource) getDatabaseByNameOrID(
projectId, region, instanceId string, projectId, region, instanceId string,
diags *diag.Diagnostics, diags *diag.Diagnostics,
) (*postgresflexalpha.ListDatabase, error) { ) (*postgresflexalpha.ListDatabase, error) {
isIdSet := !model.Id.IsNull() && !model.Id.IsUnknown() isIdSet := !model.DatabaseID.IsNull() && !model.DatabaseID.IsUnknown()
isNameSet := !model.Name.IsNull() && !model.Name.IsUnknown() isNameSet := !model.Name.IsNull() && !model.Name.IsUnknown()
if (isIdSet && isNameSet) || (!isIdSet && !isNameSet) { if (isIdSet && isNameSet) || (!isIdSet && !isNameSet) {
@ -176,8 +183,8 @@ func (r *databaseDataSource) getDatabaseByNameOrID(
} }
if isIdSet { if isIdSet {
databaseId := model.Id.ValueInt64() databaseId := model.DatabaseID.ValueInt64()
ctx = tflog.SetField(ctx, "id", databaseId) ctx = tflog.SetField(ctx, "database_id", databaseId)
return getDatabaseById(ctx, r.client, projectId, region, instanceId, databaseId) return getDatabaseById(ctx, r.client, projectId, region, instanceId, databaseId)
} }

View file

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

View file

@ -4,6 +4,7 @@ import (
"context" "context"
"testing" "testing"
"github.com/google/go-cmp/cmp"
"github.com/stackitcloud/stackit-sdk-go/core/utils" "github.com/stackitcloud/stackit-sdk-go/core/utils"
postgresflex "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/postgresflexalpha" postgresflex "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/postgresflexalpha"
) )
@ -194,3 +195,38 @@ func TestGetDatabase(t *testing.T) {
) )
} }
} }
func TestCleanString(t *testing.T) {
testcases := []struct {
name string
given *string
expected *string
}{
{
name: "should remove quotes",
given: utils.Ptr("\"quoted\""),
expected: utils.Ptr("quoted"),
},
{
name: "should handle nil",
given: nil,
expected: nil,
},
{
name: "should not change unquoted string",
given: utils.Ptr("unquoted"),
expected: utils.Ptr("unquoted"),
},
}
for _, tc := range testcases {
t.Run(
tc.name, func(t *testing.T) {
actual := cleanString(tc.given)
if diff := cmp.Diff(tc.expected, actual); diff != "" {
t.Errorf("string mismatch (-want +got):\n%s", diff)
}
},
)
}
}

View file

@ -2,7 +2,6 @@ package postgresflexalpha
import ( import (
"fmt" "fmt"
"strings"
"github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/postgresflexalpha" "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/postgresflexalpha"
@ -34,6 +33,7 @@ func mapFields(
} }
model.Id = types.Int64Value(databaseId) model.Id = types.Int64Value(databaseId)
model.DatabaseID = types.Int64Value(databaseId)
model.Name = types.StringPointerValue(source.Name) model.Name = types.StringPointerValue(source.Name)
model.Owner = types.StringPointerValue(cleanString(source.Owner)) model.Owner = types.StringPointerValue(cleanString(source.Owner))
model.Region = types.StringValue(region) model.Region = types.StringValue(region)
@ -63,6 +63,7 @@ func mapResourceFields(source *postgresflexalpha.ListDatabase, model *ResourceMo
} }
model.Id = types.Int64Value(databaseId) model.Id = types.Int64Value(databaseId)
model.DatabaseID = types.Int64Value(databaseId)
model.Name = types.StringPointerValue(source.Name) model.Name = types.StringPointerValue(source.Name)
model.Owner = types.StringPointerValue(cleanString(source.Owner)) model.Owner = types.StringPointerValue(cleanString(source.Owner))
return nil return nil
@ -79,12 +80,3 @@ func toCreatePayload(model *ResourceModel) (*postgresflexalpha.CreateDatabaseReq
Owner: model.Owner.ValueStringPointer(), Owner: model.Owner.ValueStringPointer(),
}, nil }, nil
} }
// cleanString removes leading and trailing quotes which are sometimes returned by the API.
func cleanString(s *string) *string {
if s == nil {
return nil
}
res := strings.Trim(*s, "\"")
return &res
}

View file

@ -46,6 +46,7 @@ func TestMapFields(t *testing.T) {
Owner: types.StringValue("my-owner"), Owner: types.StringValue("my-owner"),
}, },
Region: types.StringValue("eu01"), Region: types.StringValue("eu01"),
DatabaseID: types.Int64Value(1),
}, },
}, },
}, },
@ -70,6 +71,7 @@ func TestMapFields(t *testing.T) {
Name: types.StringValue("my-db"), Name: types.StringValue("my-db"),
Owner: types.StringNull(), Owner: types.StringNull(),
}, },
DatabaseID: types.Int64Value(1),
Region: types.StringValue("eu01"), Region: types.StringValue("eu01"),
}, },
}, },
@ -149,6 +151,7 @@ func TestMapResourceFields(t *testing.T) {
Name: types.StringValue("my-db"), Name: types.StringValue("my-db"),
Owner: types.StringValue("my-owner"), Owner: types.StringValue("my-owner"),
}, },
DatabaseID: types.Int64Value(1),
}, },
}, },
}, },
@ -233,38 +236,3 @@ func TestToCreatePayload(t *testing.T) {
) )
} }
} }
func TestCleanString(t *testing.T) {
testcases := []struct {
name string
given *string
expected *string
}{
{
name: "should remove quotes",
given: utils.Ptr("\"quoted\""),
expected: utils.Ptr("quoted"),
},
{
name: "should handle nil",
given: nil,
expected: nil,
},
{
name: "should not change unquoted string",
given: utils.Ptr("unquoted"),
expected: utils.Ptr("unquoted"),
},
}
for _, tc := range testcases {
t.Run(
tc.name, func(t *testing.T) {
actual := cleanString(tc.given)
if diff := cmp.Diff(tc.expected, actual); diff != "" {
t.Errorf("string mismatch (-want +got):\n%s", diff)
}
},
)
}
}

View file

@ -13,6 +13,9 @@ import (
"github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/identityschema" "github.com/hashicorp/terraform-plugin-framework/resource/identityschema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema" "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-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/stackitcloud/stackit-sdk-go/core/oapierror" "github.com/stackitcloud/stackit-sdk-go/core/oapierror"
@ -37,6 +40,7 @@ var (
type ResourceModel struct { type ResourceModel struct {
postgresflexalpha2.DatabaseModel postgresflexalpha2.DatabaseModel
TerraformID types.String `tfsdk:"tf_id"` TerraformID types.String `tfsdk:"tf_id"`
DatabaseID types.Int64 `tfsdk:"database_id"`
} }
// DatabaseResourceIdentityModel describes the resource's identity attributes. // DatabaseResourceIdentityModel describes the resource's identity attributes.
@ -44,7 +48,7 @@ type DatabaseResourceIdentityModel struct {
ProjectID types.String `tfsdk:"project_id"` ProjectID types.String `tfsdk:"project_id"`
Region types.String `tfsdk:"region"` Region types.String `tfsdk:"region"`
InstanceID types.String `tfsdk:"instance_id"` InstanceID types.String `tfsdk:"instance_id"`
DatabaseID types.Int64 `tfsdk:"id"` DatabaseID types.Int64 `tfsdk:"database_id"`
} }
// NewDatabaseResource is a helper function to simplify the provider implementation. // NewDatabaseResource is a helper function to simplify the provider implementation.
@ -123,10 +127,18 @@ var modifiersFileByte []byte
func (r *databaseResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { func (r *databaseResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
s := postgresflexalpha2.DatabaseResourceSchema(ctx) s := postgresflexalpha2.DatabaseResourceSchema(ctx)
s.Attributes["tf_id"] = schema.StringAttribute{ s.Attributes["tf_id"] = schema.StringAttribute{
Description: "Terraform's internal resource ID. It is structured as \\\"`project_id`,`region`,`instance_id`,`id`\\\".\",", Description: "Terraform's internal resource ID. It is structured as \\\"`project_id`,`region`,`instance_id`,`database_id`\\\".\",",
Optional: true, Optional: true,
Computed: 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) fields, err := postgresflexUtils.ReadModifiersConfig(modifiersFileByte)
if err != nil { if err != nil {
@ -159,7 +171,7 @@ func (r *databaseResource) IdentitySchema(
"instance_id": identityschema.StringAttribute{ "instance_id": identityschema.StringAttribute{
RequiredForImport: true, RequiredForImport: true,
}, },
"id": identityschema.StringAttribute{ "database_id": identityschema.Int64Attribute{
// database id // database id
RequiredForImport: true, RequiredForImport: true,
}, },
@ -289,12 +301,12 @@ func (r *databaseResource) Read(
projectId := identityData.ProjectID.ValueString() projectId := identityData.ProjectID.ValueString()
instanceId := identityData.InstanceID.ValueString() instanceId := identityData.InstanceID.ValueString()
databaseId := model.Id.ValueInt64() databaseId := model.DatabaseID.ValueInt64()
region := r.providerData.GetRegionWithOverride(identityData.Region) region := r.providerData.GetRegionWithOverride(identityData.Region)
ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "instance_id", instanceId)
ctx = tflog.SetField(ctx, "region", region) ctx = tflog.SetField(ctx, "region", region)
ctx = tflog.SetField(ctx, "id", databaseId) //database id ctx = tflog.SetField(ctx, "database_id", databaseId)
databaseResp, err := getDatabaseById(ctx, r.client, projectId, region, instanceId, databaseId) databaseResp, err := getDatabaseById(ctx, r.client, projectId, region, instanceId, databaseId)
if err != nil { if err != nil {
@ -356,7 +368,7 @@ func (r *databaseResource) Update(
instanceId := identityData.InstanceID.ValueString() instanceId := identityData.InstanceID.ValueString()
region := r.providerData.GetRegionWithOverride(identityData.Region) region := r.providerData.GetRegionWithOverride(identityData.Region)
databaseId64 := model.Id.ValueInt64() databaseId64 := model.DatabaseID.ValueInt64() // database id
if databaseId64 > math.MaxInt32 { if databaseId64 > math.MaxInt32 {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error in type conversion", "int value too large (databaseId)") core.LogAndAddError(ctx, &resp.Diagnostics, "Error in type conversion", "int value too large (databaseId)")
return return
@ -366,7 +378,7 @@ func (r *databaseResource) Update(
ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "instance_id", instanceId)
ctx = tflog.SetField(ctx, "region", region) ctx = tflog.SetField(ctx, "region", region)
ctx = tflog.SetField(ctx, "id", databaseId) ctx = tflog.SetField(ctx, "database_id", databaseId)
// Retrieve values from state // Retrieve values from state
var stateModel ResourceModel var stateModel ResourceModel
@ -453,7 +465,7 @@ func (r *databaseResource) Delete(
projectId := identityData.ProjectID.ValueString() projectId := identityData.ProjectID.ValueString()
instanceId := identityData.InstanceID.ValueString() instanceId := identityData.InstanceID.ValueString()
region := r.providerData.GetRegionWithOverride(identityData.Region) region := r.providerData.GetRegionWithOverride(identityData.Region)
databaseId64 := model.Id.ValueInt64() databaseId64 := model.DatabaseID.ValueInt64() //database id
if databaseId64 > math.MaxInt32 { if databaseId64 > math.MaxInt32 {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error in type conversion", "int value too large (databaseId)") core.LogAndAddError(ctx, &resp.Diagnostics, "Error in type conversion", "int value too large (databaseId)")
@ -463,7 +475,7 @@ func (r *databaseResource) Delete(
ctx = tflog.SetField(ctx, "project_id", projectId) ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "instance_id", instanceId) ctx = tflog.SetField(ctx, "instance_id", instanceId)
ctx = tflog.SetField(ctx, "region", region) ctx = tflog.SetField(ctx, "region", region)
ctx = tflog.SetField(ctx, "id", databaseId) //database id ctx = tflog.SetField(ctx, "database_id", databaseId)
// Delete existing record set // Delete existing record set
err := r.client.DeleteDatabaseRequestExecute(ctx, projectId, region, instanceId, databaseId) err := r.client.DeleteDatabaseRequestExecute(ctx, projectId, region, instanceId, databaseId)
@ -477,7 +489,7 @@ func (r *databaseResource) Delete(
} }
// ImportState imports a resource into the Terraform state on success. // ImportState imports a resource into the Terraform state on success.
// The expected import identifier format is: [project_id],[region],[instance_id],[id] // The expected import identifier format is: [project_id],[region],[instance_id],[database_id]
func (r *databaseResource) ImportState( func (r *databaseResource) ImportState(
ctx context.Context, ctx context.Context,
req resource.ImportStateRequest, req resource.ImportStateRequest,
@ -489,7 +501,7 @@ func (r *databaseResource) ImportState(
ctx, &resp.Diagnostics, ctx, &resp.Diagnostics,
"Error importing database", "Error importing database",
fmt.Sprintf( fmt.Sprintf(
"Expected import identifier with format [project_id],[region],[instance_id],[id], got %q", "Expected import identifier with format [project_id],[region],[instance_id],[database_id], got %q",
req.ID, req.ID,
), ),
) )
@ -499,7 +511,7 @@ func (r *databaseResource) ImportState(
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), idParts[0])...) resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), idParts[0])...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("region"), idParts[1])...) resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("region"), idParts[1])...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("instance_id"), idParts[2])...) resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("instance_id"), idParts[2])...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), idParts[3])...) //database id resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("database_id"), idParts[3])...)
core.LogAndAddWarning( core.LogAndAddWarning(
ctx, ctx,
&resp.Diagnostics, &resp.Diagnostics,
@ -547,7 +559,7 @@ func (r *databaseResource) ImportState(
resp.Diagnostics.Append( resp.Diagnostics.Append(
resp.State.SetAttribute( resp.State.SetAttribute(
ctx, ctx,
path.Root("id"), // database id path.Root("database_id"),
identityData.DatabaseID.ValueInt64(), identityData.DatabaseID.ValueInt64(),
)..., )...,
) )