package postgresflexalpha import ( "context" "fmt" "time" "github.com/hashicorp/terraform-plugin-log/tflog" postgresflex "github.com/mhenselin/terraform-provider-stackitprivatepreview/pkg/postgresflexalpha" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" "github.com/stackitcloud/stackit-sdk-go/core/wait" ) // "READY" "PENDING" "PROGRESSING" "FAILURE" "UNKNOWN" "TERMINATING" const ( InstanceStateEmpty = "" InstanceStateProgressing = "PROGRESSING" InstanceStateSuccess = "READY" InstanceStateFailed = "FAILURE" InstanceStateTerminating = "TERMINATING" InstanceStateUnknown = "UNKNOWN" InstanceStatePending = "PENDING" ) // APIClientInstanceInterface Interface needed for tests type APIClientInstanceInterface interface { GetInstanceRequestExecute(ctx context.Context, projectId, region, instanceId string) ( *postgresflex.GetInstanceResponse, error, ) ListUsersRequestExecute( ctx context.Context, projectId string, region string, instanceId string, ) (*postgresflex.ListUserResponse, error) } // APIClientUserInterface Interface needed for tests type APIClientUserInterface interface { GetUserRequestExecute(ctx context.Context, projectId, region, instanceId string, userId int64) ( *postgresflex.GetUserResponse, error, ) } // CreateInstanceWaitHandler will wait for instance creation func CreateInstanceWaitHandler( ctx context.Context, a APIClientInstanceInterface, projectId, region, instanceId string, ) *wait.AsyncActionHandler[postgresflex.GetInstanceResponse] { instanceCreated := false var instanceGetResponse *postgresflex.GetInstanceResponse maxWait := time.Minute * 45 startTime := time.Now() extendedTimeout := 0 handler := wait.New( func() (waitFinished bool, response *postgresflex.GetInstanceResponse, err error) { if !instanceCreated { s, err := a.GetInstanceRequestExecute(ctx, projectId, region, instanceId) if err != nil { return false, nil, err } if s == nil || s.Id == nil || *s.Id != instanceId || s.Status == nil { return false, nil, nil } tflog.Debug(ctx, "waiting for instance ready", map[string]interface{}{ "status": *s.Status, }) switch *s.Status { default: 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: if time.Since(startTime) < maxWait { return false, nil, nil } tflog.Warn( ctx, fmt.Sprintf( "Wait handler still got status %s after %v for instance: %s", InstanceStateProgressing, maxWait, instanceId, ), ) if extendedTimeout < 3 { maxWait = maxWait + time.Minute*5 extendedTimeout = extendedTimeout + 1 if *s.Network.AccessScope == "SNA" { ready := true if s.Network == nil || s.Network.InstanceAddress == nil { tflog.Warn(ctx, "Waiting for instance_address") ready = false } if s.Network.RouterAddress == nil { tflog.Warn(ctx, "Waiting for router_address") ready = false } if !ready { return false, nil, nil } } if s.IsDeletable == nil { tflog.Warn(ctx, "Waiting for is_deletable") return false, nil, nil } } instanceCreated = true instanceGetResponse = s case InstanceStateSuccess: if *s.Network.AccessScope == "SNA" { if s.Network == nil || s.Network.InstanceAddress == nil { tflog.Warn(ctx, "Waiting for instance_address") return false, nil, nil } if s.Network.RouterAddress == nil { tflog.Info(ctx, "Waiting for router_address") return false, nil, nil } } instanceCreated = true instanceGetResponse = s case InstanceStateFailed: 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) } } tflog.Info(ctx, "Waiting for instance (calling list users") // // User operations aren't available right after an instance is deemed successful // // To check if they are, perform a users request _, err = a.ListUsersRequestExecute(ctx, projectId, region, instanceId) 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 if !ok { return false, nil, err } if oapiErr.StatusCode < 500 { return true, instanceGetResponse, fmt.Errorf( "users request after instance creation returned %d status code", oapiErr.StatusCode, ) } return false, nil, nil }, ) // Sleep before wait is set because sometimes API returns 404 right after creation request handler.SetTimeout(90 * time.Minute).SetSleepBeforeWait(30 * time.Second) return handler } // PartialUpdateInstanceWaitHandler will wait for instance update func PartialUpdateInstanceWaitHandler( ctx context.Context, a APIClientInstanceInterface, projectId, region, instanceId string, ) *wait.AsyncActionHandler[postgresflex.GetInstanceResponse] { handler := wait.New( func() (waitFinished bool, response *postgresflex.GetInstanceResponse, err error) { s, err := a.GetInstanceRequestExecute(ctx, projectId, region, instanceId) if err != nil { return false, nil, err } if s == nil || s.Id == nil || *s.Id != instanceId || s.Status == nil { return false, nil, nil } switch *s.Status { default: 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 InstanceStateProgressing: return false, nil, nil case InstanceStateSuccess: return true, s, nil case InstanceStateTerminating: return false, nil, nil case InstanceStateUnknown: return false, nil, nil case InstanceStateFailed: return true, s, fmt.Errorf("update failed for instance with id %s", instanceId) } }, ) handler.SetTimeout(45 * time.Minute).SetSleepBeforeWait(30 * time.Second) return handler }