chore: changed and refactored providers (#36)

## Description

<!-- **Please link some issue here describing what you are trying to achieve.**

In case there is no issue present for your PR, please consider creating one.
At least please give us some description what you are trying to achieve and why your change is needed. -->

relates to #1234

## Checklist

- [ ] Issue was linked above
- [ ] Code format was applied: `make fmt`
- [ ] Examples were added / adjusted (see `examples/` directory)
- [x] Docs are up-to-date: `make generate-docs` (will be checked by CI)
- [ ] Unit tests got implemented or updated
- [ ] Acceptance tests got implemented or updated (see e.g. [here](f5f99d1709/stackit/internal/services/dns/dns_acc_test.go))
- [x] Unit tests are passing: `make test` (will be checked by CI)
- [x] No linter issues: `make lint` (will be checked by CI)

Co-authored-by: Marcel S. Henselin <marcel.henselin@stackit.cloud>
Reviewed-on: #36
Reviewed-by: Marcel_Henselin <marcel.henselin@stackit.cloud>
Co-authored-by: Andre Harms <andre.harms@stackit.cloud>
Co-committed-by: Andre Harms <andre.harms@stackit.cloud>
This commit is contained in:
Andre_Harms 2026-02-10 08:10:02 +00:00 committed by Marcel_Henselin
parent b1b359f436
commit de019908d2
Signed by: tf-provider.git.onstackit.cloud
GPG key ID: 6D7E8A1ED8955A9C
70 changed files with 6250 additions and 2608 deletions

View file

@ -2,6 +2,7 @@ package postgresflexalpha
import (
"context"
_ "embed"
"errors"
"fmt"
"math"
@ -9,60 +10,53 @@ import (
"strconv"
"strings"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/identityschema"
postgresflex "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/postgresflexalpha"
postgresflexalpha "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/user/resources_gen"
postgresflexUtils "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/services/postgresflexalpha/utils"
postgresflex "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/postgresflexalpha"
"github.com/hashicorp/terraform-plugin-framework-validators/setvalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/conversion"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/core"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/stackit/internal/utils"
"tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/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/oapierror"
)
// Ensure the implementation satisfies the expected interfaces.
var (
// Ensure the implementation satisfies the expected interfaces.
_ resource.Resource = &userResource{}
_ resource.ResourceWithConfigure = &userResource{}
_ resource.ResourceWithImportState = &userResource{}
_ resource.ResourceWithModifyPlan = &userResource{}
)
_ resource.ResourceWithIdentity = &userResource{}
type Model struct {
Id types.String `tfsdk:"id"` // needed by TF
UserId types.Int64 `tfsdk:"user_id"`
InstanceId types.String `tfsdk:"instance_id"`
ProjectId types.String `tfsdk:"project_id"`
Username types.String `tfsdk:"username"`
Roles types.Set `tfsdk:"roles"`
Password types.String `tfsdk:"password"`
Host types.String `tfsdk:"host"`
Port types.Int64 `tfsdk:"port"`
Region types.String `tfsdk:"region"`
Status types.String `tfsdk:"status"`
ConnectionString types.String `tfsdk:"connection_string"`
}
// Error message constants
extractErrorSummary = "extracting failed"
extractErrorMessage = "Extracting identity data: %v"
)
// NewUserResource is a helper function to simplify the provider implementation.
func NewUserResource() resource.Resource {
return &userResource{}
}
// userResource is the resource implementation.
// resourceModel represents the Terraform resource state for a PostgreSQL Flex user.
type resourceModel = postgresflexalpha.UserModel
// UserResourceIdentityModel describes the resource's identity attributes.
type UserResourceIdentityModel struct {
ProjectID types.String `tfsdk:"project_id"`
Region types.String `tfsdk:"region"`
InstanceID types.String `tfsdk:"instance_id"`
UserID types.Int64 `tfsdk:"database_id"`
}
// userResource implements the resource handling for a PostgreSQL Flex user.
type userResource struct {
client *postgresflex.APIClient
providerData core.ProviderData
@ -75,7 +69,7 @@ func (r *userResource) ModifyPlan(
req resource.ModifyPlanRequest,
resp *resource.ModifyPlanResponse,
) { // nolint:gocritic // function signature required by Terraform
var configModel Model
var configModel resourceModel
// skip initial empty configuration to avoid follow-up errors
if req.Config.Raw.IsNull() {
return
@ -85,7 +79,7 @@ func (r *userResource) ModifyPlan(
return
}
var planModel Model
var planModel resourceModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &planModel)...)
if resp.Diagnostics.HasError() {
return
@ -123,117 +117,25 @@ func (r *userResource) Configure(ctx context.Context, req resource.ConfigureRequ
tflog.Info(ctx, "Postgres Flex user client configured")
}
//go:embed planModifiers.yaml
var modifiersFileByte []byte
// Schema defines the schema for the resource.
func (r *userResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
rolesOptions := []string{"login", "createdb", "createrole"}
func (r *userResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
s := postgresflexalpha.UserResourceSchema(ctx)
descriptions := map[string]string{
"main": "Postgres Flex user resource schema. Must have a `region` specified in the provider configuration.",
"id": "Terraform's internal resource ID. It is structured as \"`project_id`,`region`,`instance_id`,`user_id`\".",
"user_id": "User ID.",
"instance_id": "ID of the PostgresFlex instance.",
"project_id": "STACKIT project ID to which the instance is associated.",
"username": "The name of the user.",
"roles": "Database access levels for the user. " + utils.FormatPossibleValues(rolesOptions...),
"region": "The resource region. If not defined, the provider region is used.",
"status": "The current status of the user.",
"password": "The password for the user. This is only set upon creation.",
"host": "The host of the Postgres Flex instance.",
"port": "The port of the Postgres Flex instance.",
"connection_string": "The connection string for the user to the instance.",
fields, err := utils.ReadModifiersConfig(modifiersFileByte)
if err != nil {
resp.Diagnostics.AddError("error during read modifiers config file", err.Error())
return
}
resp.Schema = schema.Schema{
Description: descriptions["main"],
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Description: descriptions["id"],
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"user_id": schema.Int64Attribute{
Description: descriptions["user_id"],
Computed: true,
PlanModifiers: []planmodifier.Int64{
int64planmodifier.UseStateForUnknown(),
},
Validators: []validator.Int64{},
},
"instance_id": schema.StringAttribute{
Description: descriptions["instance_id"],
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
stringplanmodifier.UseStateForUnknown(),
},
Validators: []validator.String{
validate.UUID(),
validate.NoSeparator(),
},
},
"project_id": schema.StringAttribute{
Description: descriptions["project_id"],
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
stringplanmodifier.UseStateForUnknown(),
},
Validators: []validator.String{
validate.UUID(),
validate.NoSeparator(),
},
},
"username": schema.StringAttribute{
Description: descriptions["username"],
Required: true,
PlanModifiers: []planmodifier.String{
// stringplanmodifier.RequiresReplace(),
},
},
"roles": schema.SetAttribute{
Description: descriptions["roles"],
ElementType: types.StringType,
Required: true,
Validators: []validator.Set{
setvalidator.ValueStringsAre(
stringvalidator.OneOf(rolesOptions...),
),
},
},
"password": schema.StringAttribute{
Description: descriptions["password"],
Computed: true,
Sensitive: true,
},
"host": schema.StringAttribute{
Description: descriptions["host"],
Computed: true,
},
"port": schema.Int64Attribute{
Description: descriptions["port"],
Computed: true,
},
"region": schema.StringAttribute{
Optional: true,
// must be computed to allow for storing the override value from the provider
Computed: true,
Description: descriptions["region"],
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"status": schema.StringAttribute{
Description: descriptions["status"],
Computed: true,
},
"connection_string": schema.StringAttribute{
Description: descriptions["connection_string"],
Computed: true,
},
},
err = utils.AddPlanModifiersToResourceSchema(fields, &s)
if err != nil {
resp.Diagnostics.AddError("error adding plan modifiers", err.Error())
return
}
resp.Schema = s
}
// Create creates the resource and sets the initial Terraform state.
@ -242,16 +144,33 @@ func (r *userResource) Create(
req resource.CreateRequest,
resp *resource.CreateResponse,
) { // nolint:gocritic // function signature required by Terraform
var model Model
var model resourceModel
diags := req.Plan.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// Read identity data
var identityData UserResourceIdentityModel
resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...)
if resp.Diagnostics.HasError() {
return
}
ctx = core.InitProviderContext(ctx)
ctx = r.setTFLogFields(ctx, &model)
arg := r.getClientArg(&model)
arg, errExt := r.extractIdentityData(model, identityData)
if errExt != nil {
core.LogAndAddError(
ctx,
&resp.Diagnostics,
extractErrorSummary,
fmt.Sprintf(extractErrorMessage, errExt),
)
}
ctx = r.setTFLogFields(ctx, arg)
var roles = r.expandRoles(ctx, model.Roles, &resp.Diagnostics)
if resp.Diagnostics.HasError() {
@ -277,8 +196,6 @@ func (r *userResource) Create(
return
}
ctx = core.LogResponse(ctx)
if userResp.Id == nil || *userResp.Id == 0 {
core.LogAndAddError(
ctx,
@ -288,12 +205,28 @@ func (r *userResource) Create(
)
return
}
model.Id = types.Int64PointerValue(userResp.Id)
model.UserId = types.Int64PointerValue(userResp.Id)
model.Password = types.StringPointerValue(userResp.Password)
ctx = tflog.SetField(ctx, "user_id", *userResp.Id)
exists, err := r.getUserResource(ctx, &model)
ctx = core.LogResponse(ctx)
// Set data returned by API in identity
identity := UserResourceIdentityModel{
ProjectID: types.StringValue(arg.projectId),
Region: types.StringValue(arg.region),
InstanceID: types.StringValue(arg.instanceId),
UserID: types.Int64PointerValue(userResp.Id),
}
resp.Diagnostics.Append(resp.Identity.Set(ctx, identity)...)
if resp.Diagnostics.HasError() {
return
}
// Verify creation
exists, err := r.getUserResource(ctx, &model, arg)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating user", fmt.Sprintf("Calling API: %v", err))
@ -322,16 +255,38 @@ func (r *userResource) Read(
req resource.ReadRequest,
resp *resource.ReadResponse,
) { // nolint:gocritic // function signature required by Terraform
var model Model
var model resourceModel
diags := req.State.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// Read identity data
var identityData UserResourceIdentityModel
resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...)
if resp.Diagnostics.HasError() {
return
}
ctx = core.InitProviderContext(ctx)
exists, err := r.getUserResource(ctx, &model)
arg, errExt := r.extractIdentityData(model, identityData)
if errExt != nil {
core.LogAndAddError(
ctx,
&resp.Diagnostics,
extractErrorSummary,
fmt.Sprintf(extractErrorMessage, errExt),
)
}
ctx = r.setTFLogFields(ctx, arg)
ctx = core.InitProviderContext(ctx)
// Read resource state
exists, err := r.getUserResource(ctx, &model, arg)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading user", fmt.Sprintf("Calling API: %v", err))
@ -360,19 +315,37 @@ func (r *userResource) Update(
req resource.UpdateRequest,
resp *resource.UpdateResponse,
) { // nolint:gocritic // function signature required by Terraform
var model Model
var model resourceModel
diags := req.Plan.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// Read identity data
var identityData UserResourceIdentityModel
resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...)
if resp.Diagnostics.HasError() {
return
}
ctx = core.InitProviderContext(ctx)
arg, errExt := r.extractIdentityData(model, identityData)
if errExt != nil {
core.LogAndAddError(
ctx,
&resp.Diagnostics,
extractErrorSummary,
fmt.Sprintf(extractErrorMessage, errExt),
)
}
ctx = r.setTFLogFields(ctx, arg)
ctx = core.InitProviderContext(ctx)
ctx = r.setTFLogFields(ctx, &model)
arg := r.getClientArg(&model)
// Retrieve values from state
var stateModel Model
var stateModel resourceModel
diags = req.State.Get(ctx, &stateModel)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
@ -413,7 +386,8 @@ func (r *userResource) Update(
ctx = core.LogResponse(ctx)
exists, err := r.getUserResource(ctx, &stateModel)
// Verify update
exists, err := r.getUserResource(ctx, &stateModel, arg)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating user", fmt.Sprintf("Calling API: %v", err))
@ -443,16 +417,33 @@ func (r *userResource) Delete(
req resource.DeleteRequest,
resp *resource.DeleteResponse,
) { // nolint:gocritic // function signature required by Terraform
var model Model
var model resourceModel
diags := req.State.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// Read identity data
var identityData UserResourceIdentityModel
resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...)
if resp.Diagnostics.HasError() {
return
}
ctx = core.InitProviderContext(ctx)
ctx = r.setTFLogFields(ctx, &model)
arg := r.getClientArg(&model)
arg, errExt := r.extractIdentityData(model, identityData)
if errExt != nil {
core.LogAndAddError(
ctx,
&resp.Diagnostics,
extractErrorSummary,
fmt.Sprintf(extractErrorMessage, errExt),
)
}
ctx = r.setTFLogFields(ctx, arg)
ctx = core.InitProviderContext(ctx)
userId64 := arg.userId
if userId64 > math.MaxInt32 {
@ -469,7 +460,8 @@ func (r *userResource) Delete(
ctx = core.LogResponse(ctx)
exists, err := r.getUserResource(ctx, &model)
// Verify deletion
exists, err := r.getUserResource(ctx, &model, arg)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting user", fmt.Sprintf("Calling API: %v", err))
return
@ -487,40 +479,31 @@ func (r *userResource) Delete(
tflog.Info(ctx, "Postgres Flex user deleted")
}
// ImportState imports a resource into the Terraform state on success.
// The expected format of the resource import identifier is: project_id,zone_id,record_set_id
func (r *userResource) ImportState(
ctx context.Context,
req resource.ImportStateRequest,
resp *resource.ImportStateResponse,
// IdentitySchema defines the fields that are required to uniquely identify a resource.
func (r *userResource) IdentitySchema(
_ context.Context,
_ resource.IdentitySchemaRequest,
response *resource.IdentitySchemaResponse,
) {
idParts := strings.Split(req.ID, core.Separator)
if len(idParts) != 4 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" || idParts[3] == "" {
core.LogAndAddError(
ctx, &resp.Diagnostics,
"Error importing user",
fmt.Sprintf(
"Expected import identifier with format [project_id],[region],[instance_id],[user_id], got %q",
req.ID,
),
)
return
response.IdentitySchema = identityschema.Schema{
Attributes: map[string]identityschema.Attribute{
"project_id": identityschema.StringAttribute{
RequiredForImport: true,
},
"region": identityschema.StringAttribute{
RequiredForImport: true,
},
"instance_id": identityschema.StringAttribute{
RequiredForImport: true,
},
"user_id": identityschema.Int64Attribute{
RequiredForImport: true,
},
},
}
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("instance_id"), idParts[2])...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("user_id"), idParts[3])...)
core.LogAndAddWarning(
ctx,
&resp.Diagnostics,
"postgresflexalpha user imported with empty password and empty uri",
"The user password and uri are not imported as they are only available upon creation of a new user. The password and uri fields will be empty.",
)
tflog.Info(ctx, "postgresflexalpha user state imported")
}
func mapFields(userResp *postgresflex.GetUserResponse, model *Model, region string) error {
func mapFields(userResp *postgresflex.GetUserResponse, model *resourceModel, region string) error {
if userResp == nil {
return fmt.Errorf("response is nil")
}
@ -537,14 +520,12 @@ func mapFields(userResp *postgresflex.GetUserResponse, model *Model, region stri
} else {
return fmt.Errorf("user id not present")
}
model.Id = utils.BuildInternalTerraformId(
model.ProjectId.ValueString(), region, model.InstanceId.ValueString(), strconv.FormatInt(userId, 10),
)
model.UserId = types.Int64Value(userId)
model.Username = types.StringPointerValue(user.Name)
model.Name = types.StringPointerValue(user.Name)
if user.Roles == nil {
model.Roles = types.SetNull(types.StringType)
model.Roles = types.List(types.SetNull(types.StringType))
} else {
var roles []attr.Value
for _, role := range *user.Roles {
@ -554,27 +535,21 @@ func mapFields(userResp *postgresflex.GetUserResponse, model *Model, region stri
if diags.HasError() {
return fmt.Errorf("failed to map roles: %w", core.DiagsToError(diags))
}
model.Roles = rolesSet
model.Roles = types.List(rolesSet)
}
model.Host = types.StringPointerValue(user.Host)
model.Port = types.Int64PointerValue(user.Port)
model.Region = types.StringValue(region)
model.Status = types.StringPointerValue(user.Status)
model.ConnectionString = types.StringPointerValue(user.ConnectionString)
return nil
}
// getUserResource refreshes the resource state by calling the API and mapping the response to the model.
// Returns true if the resource state was successfully refreshed, false if the resource does not exist.
func (r *userResource) getUserResource(ctx context.Context, model *Model) (bool, error) {
ctx = r.setTFLogFields(ctx, model)
arg := r.getClientArg(model)
func (r *userResource) getUserResource(ctx context.Context, model *resourceModel, arg *clientArg) (bool, error) {
userId64 := arg.userId
if userId64 > math.MaxInt32 {
if arg.userId > math.MaxInt32 {
return false, errors.New("error in type conversion: int value too large (userId)")
}
userId := int32(userId64)
userId := int32(arg.userId)
// API Call
userResp, err := r.client.GetUserRequest(ctx, arg.projectId, arg.region, arg.instanceId, userId).Execute()
@ -588,13 +563,14 @@ func (r *userResource) getUserResource(ctx context.Context, model *Model) (bool,
return false, fmt.Errorf("error fetching user resource: %w", err)
}
if err := mapFields(userResp, model, arg.region); err != nil {
if err := mapResourceFields(userResp, model, arg.region); err != nil {
return false, fmt.Errorf("error mapping user resource: %w", err)
}
return true, nil
}
// clientArg holds the arguments for API calls.
type clientArg struct {
projectId string
instanceId string
@ -602,29 +578,137 @@ type clientArg struct {
userId int64
}
// getClientArg constructs client arguments from the model.
func (r *userResource) getClientArg(model *Model) *clientArg {
return &clientArg{
projectId: model.ProjectId.ValueString(),
instanceId: model.InstanceId.ValueString(),
region: r.providerData.GetRegionWithOverride(model.Region),
userId: model.UserId.ValueInt64(),
// ImportState imports a resource into the Terraform state on success.
// The expected import identifier format is: [project_id],[region],[instance_id],[database_id]
func (r *userResource) ImportState(
ctx context.Context,
req resource.ImportStateRequest,
resp *resource.ImportStateResponse,
) {
ctx = core.InitProviderContext(ctx)
if req.ID != "" {
idParts := strings.Split(req.ID, core.Separator)
if len(idParts) != 4 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" || idParts[3] == "" {
core.LogAndAddError(
ctx, &resp.Diagnostics,
"Error importing user",
fmt.Sprintf(
"Expected import identifier with format [project_id],[region],[instance_id],[user_id], got %q",
req.ID,
),
)
return
}
userId, err := strconv.ParseInt(idParts[3], 10, 64)
if err != nil {
core.LogAndAddError(
ctx,
&resp.Diagnostics,
"Error importing user",
fmt.Sprintf("Invalid user_id format: %q. It must be a valid integer.", idParts[3]),
)
return
}
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), idParts[0])...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("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("user_id"), userId)...)
tflog.Info(ctx, "Postgres Flex user state imported")
return
}
// If no ID is provided, attempt to read identity attributes from the import configuration
var identityData UserResourceIdentityModel
resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...)
if resp.Diagnostics.HasError() {
return
}
projectId := identityData.ProjectID.ValueString()
region := identityData.Region.ValueString()
instanceId := identityData.InstanceID.ValueString()
userId := identityData.UserID.ValueInt64()
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), projectId)...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("region"), region)...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("instance_id"), instanceId)...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("user_id"), userId)...)
tflog.Info(ctx, "Postgres Flex user state imported")
}
// extractIdentityData extracts essential identifiers from the resource model, falling back to the identity model.
func (r *userResource) extractIdentityData(
model resourceModel,
identity UserResourceIdentityModel,
) (*clientArg, error) {
var projectId, region, instanceId string
var userId int64
if !model.UserId.IsNull() && !model.UserId.IsUnknown() {
userId = model.UserId.ValueInt64()
} else {
if identity.UserID.IsNull() || identity.UserID.IsUnknown() {
return nil, fmt.Errorf("user_id not found in config")
}
userId = identity.UserID.ValueInt64()
}
if !model.ProjectId.IsNull() && !model.ProjectId.IsUnknown() {
projectId = model.ProjectId.ValueString()
} else {
if identity.ProjectID.IsNull() || identity.ProjectID.IsUnknown() {
return nil, 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 nil, 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 nil, fmt.Errorf("instance_id not found in config")
}
instanceId = identity.InstanceID.ValueString()
}
return &clientArg{
projectId: projectId,
instanceId: instanceId,
region: region,
userId: userId,
}, nil
}
// setTFLogFields adds relevant fields to the context for terraform logging purposes.
func (r *userResource) setTFLogFields(ctx context.Context, model *Model) context.Context {
usrCtx := r.getClientArg(model)
ctx = tflog.SetField(ctx, "project_id", usrCtx.projectId)
ctx = tflog.SetField(ctx, "instance_id", usrCtx.instanceId)
ctx = tflog.SetField(ctx, "user_id", usrCtx.userId)
ctx = tflog.SetField(ctx, "region", usrCtx.region)
func (r *userResource) setTFLogFields(ctx context.Context, arg *clientArg) context.Context {
ctx = tflog.SetField(ctx, "project_id", arg.projectId)
ctx = tflog.SetField(ctx, "instance_id", arg.instanceId)
ctx = tflog.SetField(ctx, "region", arg.region)
ctx = tflog.SetField(ctx, "user_id", arg.userId)
return ctx
}
func (r *userResource) expandRoles(ctx context.Context, rolesSet types.Set, diags *diag.Diagnostics) []string {
// expandRoles converts a Terraform list of roles to a string slice.
func (r *userResource) expandRoles(ctx context.Context, rolesSet types.List, diags *diag.Diagnostics) []string {
if rolesSet.IsNull() || rolesSet.IsUnknown() {
return nil
}
@ -632,42 +716,3 @@ func (r *userResource) expandRoles(ctx context.Context, rolesSet types.Set, diag
diags.Append(rolesSet.ElementsAs(ctx, &roles, false)...)
return roles
}
func toCreatePayload(model *Model, roles *[]string) (*postgresflex.CreateUserRequestPayload, error) {
if model == nil {
return nil, fmt.Errorf("nil model")
}
if roles == nil {
return nil, fmt.Errorf("nil roles")
}
return &postgresflex.CreateUserRequestPayload{
Roles: toPayloadRoles(roles),
Name: conversion.StringValueToPointer(model.Username),
}, nil
}
func toPayloadRoles(roles *[]string) *[]postgresflex.UserRole {
var userRoles = make([]postgresflex.UserRole, 0, len(*roles))
for _, role := range *roles {
userRoles = append(userRoles, postgresflex.UserRole(role))
}
return &userRoles
}
func toUpdatePayload(model *Model, roles *[]string) (
*postgresflex.UpdateUserRequestPayload,
error,
) {
if model == nil {
return nil, fmt.Errorf("nil model")
}
if roles == nil {
return nil, fmt.Errorf("nil roles")
}
return &postgresflex.UpdateUserRequestPayload{
Name: conversion.StringValueToPointer(model.Username),
Roles: toPayloadRoles(roles),
}, nil
}