fix: re-implement acl part in top level

feat: implement tf identity
This commit is contained in:
Marcel_Henselin 2025-12-30 16:30:18 +01:00
parent 1027643f95
commit 29f693308e
6 changed files with 116 additions and 36 deletions

View file

@ -17,6 +17,26 @@ import (
// checks if the CreateInstanceRequestPayload type satisfies the MappedNullable interface at compile time
var _ MappedNullable = &CreateInstanceRequestPayload{}
/*
types and functions for acl
*/
// isArray
type CreateInstanceRequestPayloadGetAclAttributeType = *[]string
type CreateInstanceRequestPayloadGetAclArgType = []string
type CreateInstanceRequestPayloadGetAclRetType = []string
func getCreateInstanceRequestPayloadGetAclAttributeTypeOk(arg CreateInstanceRequestPayloadGetAclAttributeType) (ret CreateInstanceRequestPayloadGetAclRetType, ok bool) {
if arg == nil {
return ret, false
}
return *arg, true
}
func setCreateInstanceRequestPayloadGetAclAttributeType(arg *CreateInstanceRequestPayloadGetAclAttributeType, val CreateInstanceRequestPayloadGetAclRetType) {
*arg = &val
}
/*
types and functions for backupSchedule
*/
@ -203,6 +223,7 @@ type CreateInstanceRequestPayloadGetVersionRetType = string
// CreateInstanceRequestPayload struct for CreateInstanceRequestPayload
type CreateInstanceRequestPayload struct {
Acl CreateInstanceRequestPayloadGetAclAttributeType `json:"acl" required:"true"`
// The schedule for on what time and how often the database backup will be created. The schedule is written as a cron schedule.
// REQUIRED
BackupSchedule CreateInstanceRequestPayloadGetBackupScheduleAttributeType `json:"backupSchedule" required:"true"`

View file

@ -1,5 +1,3 @@
// Copyright (c) STACKIT
package wait
import (
@ -14,15 +12,18 @@ import (
"github.com/stackitcloud/stackit-sdk-go/core/wait"
)
// "READY" "PENDING" "PROGRESSING" "FAILURE" "UNKNOWN" "TERMINATING"
const (
InstanceStateEmpty = ""
InstanceStateProgressing = "Progressing"
InstanceStateSuccess = "Ready"
InstanceStateFailed = "Failure"
InstanceStateDeleted = "Deleted"
InstanceStateProgressing = "PROGRESSING"
InstanceStateSuccess = "READY"
InstanceStateFailed = "FAILURE"
InstanceStateTerminating = "TERMINATING"
InstanceStateUnknown = "UNKNOWN"
InstanceStatePending = "PENDING"
)
// Interface needed for tests
// APIClientInstanceInterface Interface needed for tests
type APIClientInstanceInterface interface {
GetInstanceRequestExecute(ctx context.Context, projectId, region, instanceId string) (
*postgresflex.GetInstanceResponse,
@ -30,12 +31,14 @@ type APIClientInstanceInterface interface {
)
ListUsersRequestExecute(
ctx context.Context, projectId string, region string,
ctx context.Context,
projectId string,
region string,
instanceId string,
) (*postgresflex.ListUserResponse, error)
}
// Interface needed for tests
// APIClientUserInterface Interface needed for tests
type APIClientUserInterface interface {
GetUserRequestExecute(ctx context.Context, projectId, region, instanceId string, userId int64) (
*postgresflex.GetUserResponse,
@ -66,6 +69,10 @@ func CreateInstanceWaitHandler(
return true, s, fmt.Errorf("instance with id %s has unexpected status %s", instanceId, *s.Status)
case InstanceStateEmpty:
return false, nil, nil
case InstanceStatePending:
return false, nil, nil
case InstanceStateUnknown:
return false, nil, nil
case InstanceStateProgressing:
return false, nil, nil
case InstanceStateSuccess:
@ -80,7 +87,10 @@ func CreateInstanceWaitHandler(
instanceCreated = true
instanceGetResponse = s
case InstanceStateFailed:
return true, s, fmt.Errorf("create failed for instance with id %s", instanceId)
tflog.Warn(ctx, fmt.Sprintf("Wait handler got status FAILURE for instance: %s", instanceId))
return false, nil, nil
// API responds with FAILURE for some seconds and then the instance goes to READY
// return true, s, fmt.Errorf("create failed for instance with id %s", instanceId)
}
}
@ -90,7 +100,7 @@ func CreateInstanceWaitHandler(
if err == nil {
return true, instanceGetResponse, 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
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 {
return false, nil, err
}
@ -127,7 +137,9 @@ func PartialUpdateInstanceWaitHandler(
return true, s, fmt.Errorf("instance with id %s has unexpected status %s", instanceId, *s.Status)
case InstanceStateEmpty:
return false, nil, nil
case InstanceStateProgressing:
case InstanceStateUnknown:
return false, nil, nil
case InstanceStateTerminating:
return false, nil, nil
case InstanceStateSuccess:
return true, s, nil
@ -155,20 +167,31 @@ func DeleteInstanceWaitHandler(
if s == nil || s.Id == nil || *s.Id != instanceId || s.Status == nil {
return false, nil, nil
}
switch *s.Status {
default:
return true, nil, fmt.Errorf("instance with id %s has unexpected status %s", instanceId, *s.Status)
case InstanceStateSuccess:
return false, nil, nil
case InstanceStateDeleted:
return true, nil, nil
// TODO - maybe we want to validate status if no 404 error (only unknown or terminating should be valid)
//switch *s.Status {
//default:
// return true, nil, fmt.Errorf("instance with id %s has unexpected status %s", instanceId, *s.Status)
//case InstanceStateSuccess:
// return false, nil, nil
//case InstanceStateTerminating:
// return false, nil, nil
//}
// TODO - add tflog for ignored cases
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 {
return false, nil, err
}
if oapiErr.StatusCode != 404 {
return false, nil, err
}
return true, nil, nil
},
)
handler.SetTimeout(5 * time.Minute)
return handler
}
// TODO - remove
// ForceDeleteInstanceWaitHandler will wait for instance deletion
func ForceDeleteInstanceWaitHandler(
ctx context.Context,

View file

@ -19,7 +19,6 @@ import (
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/mhenselin/terraform-provider-stackitprivatepreview/pkg/postgresflexalpha/wait"
)
// Ensure the implementation satisfies the expected interfaces.
@ -227,11 +226,11 @@ func (r *instanceDataSource) Read(ctx context.Context, req datasource.ReadReques
ctx = core.LogResponse(ctx)
if instanceResp != nil && instanceResp.Status != nil && *instanceResp.Status == wait.InstanceStateDeleted {
resp.State.RemoveResource(ctx)
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", "Instance was deleted successfully")
return
}
//if instanceResp != nil && instanceResp.Status != nil && *instanceResp.Status == wait.InstanceStateDeleted {
// resp.State.RemoveResource(ctx)
// core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", "Instance was deleted successfully")
// return
//}
var flavor = &flavorModel{}
if instanceResp != nil && instanceResp.FlavorId != nil {

View file

@ -253,6 +253,7 @@ func toCreatePayload(
}
return &postgresflex.CreateInstanceRequestPayload{
Acl: &aclElements,
BackupSchedule: conversion.StringValueToPointer(model.BackupSchedule),
Encryption: encryptionPayload,
FlavorId: conversion.StringValueToPointer(flavor.Id),

View file

@ -22,6 +22,10 @@ type Model struct {
Network types.Object `tfsdk:"network"`
}
type IdentityModel struct {
ID types.String `tfsdk:"id"`
}
type encryptionModel struct {
KeyRingId types.String `tfsdk:"keyring_id"`
KeyId types.String `tfsdk:"key_id"`

View file

@ -8,6 +8,7 @@ import (
"strings"
"time"
"github.com/hashicorp/terraform-plugin-framework/resource/identityschema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
postgresflex "github.com/mhenselin/terraform-provider-stackitprivatepreview/pkg/postgresflexalpha"
"github.com/mhenselin/terraform-provider-stackitprivatepreview/pkg/postgresflexalpha/wait"
@ -41,6 +42,7 @@ var (
_ resource.ResourceWithImportState = &instanceResource{}
_ resource.ResourceWithModifyPlan = &instanceResource{}
_ resource.ResourceWithValidateConfig = &instanceResource{}
_ resource.ResourceWithIdentity = &instanceResource{}
)
// NewInstanceResource is a helper function to simplify the provider implementation.
@ -388,6 +390,16 @@ func (r *instanceResource) Schema(_ context.Context, req resource.SchemaRequest,
}
}
func (r *instanceResource) IdentitySchema(_ context.Context, _ resource.IdentitySchemaRequest, resp *resource.IdentitySchemaResponse) {
resp.IdentitySchema = identityschema.Schema{
Attributes: map[string]identityschema.Attribute{
"id": identityschema.StringAttribute{
RequiredForImport: true, // must be set during import by the practitioner
},
},
}
}
// Create creates the resource and sets the initial Terraform state.
func (r *instanceResource) Create(
ctx context.Context,
@ -495,18 +507,24 @@ func (r *instanceResource) Create(
}
ctx = core.LogResponse(ctx)
instanceId := *createResp.Id
utils.SetAndLogStateFields(ctx, &resp.Diagnostics, &resp.State, map[string]any{
"instance_id": instanceId,
})
model.InstanceId = types.StringValue(instanceId)
model.Id = utils.BuildInternalTerraformId(projectId, region, instanceId)
resp.Diagnostics.Append(resp.State.Set(ctx, &model)...)
if resp.Diagnostics.HasError() {
return
}
// Set data returned by API in identity
identity := IdentityModel{
ID: utils.BuildInternalTerraformId(projectId, region, instanceId),
}
resp.Diagnostics.Append(resp.Identity.Set(ctx, identity)...)
waitResp, err := wait.CreateInstanceWaitHandler(ctx, r.client, projectId, region, instanceId).WaitWithContext(ctx)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Instance creation waiting: %v", err))
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating instance", fmt.Sprintf("Wait handler error: %v", err))
return
}
@ -534,6 +552,13 @@ func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r
return
}
// Read identity data
var identityData IdentityModel
resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...)
if resp.Diagnostics.HasError() {
return
}
ctx = core.InitProviderContext(ctx)
projectId := model.ProjectId.ValueString()
@ -591,10 +616,10 @@ func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r
ctx = core.LogResponse(ctx)
if instanceResp != nil && instanceResp.Status != nil && *instanceResp.Status == wait.InstanceStateDeleted {
resp.State.RemoveResource(ctx)
return
}
//if instanceResp != nil && instanceResp.Status != nil && *instanceResp.Status == wait.InstanceStateDeleted {
// resp.State.RemoveResource(ctx)
// return
//}
// Map response body to schema
err = mapFields(ctx, instanceResp, &model, flavor, storage, encryption, network, region)
@ -602,12 +627,19 @@ func (r *instanceResource) Read(ctx context.Context, req resource.ReadRequest, r
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading instance", fmt.Sprintf("Processing API payload: %v", err))
return
}
// Set refreshed state
diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...)
resp.Diagnostics.Append(resp.State.Set(ctx, model)...)
if resp.Diagnostics.HasError() {
return
}
identityData.ID = model.Id
resp.Diagnostics.Append(resp.Identity.Set(ctx, identityData)...)
if resp.Diagnostics.HasError() {
return
}
tflog.Info(ctx, "Postgres Flex instance read")
}