diff --git a/pkg/postgresflexalpha/model_create_instance_request_payload.go b/pkg/postgresflexalpha/model_create_instance_request_payload.go index 96d88b0c..8f1f668e 100644 --- a/pkg/postgresflexalpha/model_create_instance_request_payload.go +++ b/pkg/postgresflexalpha/model_create_instance_request_payload.go @@ -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"` diff --git a/pkg/postgresflexalpha/wait/wait.go b/pkg/postgresflexalpha/wait/wait.go index 99174835..e3909d94 100644 --- a/pkg/postgresflexalpha/wait/wait.go +++ b/pkg/postgresflexalpha/wait/wait.go @@ -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, diff --git a/stackit/internal/services/postgresflexalpha/instance/datasource.go b/stackit/internal/services/postgresflexalpha/instance/datasource.go index d477264d..b9d5edcb 100644 --- a/stackit/internal/services/postgresflexalpha/instance/datasource.go +++ b/stackit/internal/services/postgresflexalpha/instance/datasource.go @@ -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 { diff --git a/stackit/internal/services/postgresflexalpha/instance/functions.go b/stackit/internal/services/postgresflexalpha/instance/functions.go index 1635324e..9f7d0482 100644 --- a/stackit/internal/services/postgresflexalpha/instance/functions.go +++ b/stackit/internal/services/postgresflexalpha/instance/functions.go @@ -253,6 +253,7 @@ func toCreatePayload( } return &postgresflex.CreateInstanceRequestPayload{ + Acl: &aclElements, BackupSchedule: conversion.StringValueToPointer(model.BackupSchedule), Encryption: encryptionPayload, FlavorId: conversion.StringValueToPointer(flavor.Id), diff --git a/stackit/internal/services/postgresflexalpha/instance/models.go b/stackit/internal/services/postgresflexalpha/instance/models.go index f2ffa39a..51d74521 100644 --- a/stackit/internal/services/postgresflexalpha/instance/models.go +++ b/stackit/internal/services/postgresflexalpha/instance/models.go @@ -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"` diff --git a/stackit/internal/services/postgresflexalpha/instance/resource.go b/stackit/internal/services/postgresflexalpha/instance/resource.go index e1f058b4..55fd34cf 100644 --- a/stackit/internal/services/postgresflexalpha/instance/resource.go +++ b/stackit/internal/services/postgresflexalpha/instance/resource.go @@ -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") }