// Copyright (c) STACKIT package sqlserverflexalpha import ( "context" "errors" "fmt" "net/http" "strings" "time" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/stackitcloud/stackit-sdk-go/core/oapierror" "github.com/stackitcloud/stackit-sdk-go/core/wait" sqlserverflex "tf-provider.git.onstackit.cloud/stackit-dev-tools/terraform-provider-stackitprivatepreview/pkg_gen/sqlserverflexalpha" ) // READY, PENDING, PROGRESSING, FAILURE, UNKNOWN, const ( InstanceStateEmpty = "" InstanceStateSuccess = "READY" InstanceStatePending = "PENDING" InstanceStateProcessing = "PROGRESSING" InstanceStateFailed = "FAILURE" InstanceStateUnknown = "UNKNOWN" InstanceStateTerminating = "TERMINATING" ) // APIClientInstanceInterface Interface needed for tests type APIClientInstanceInterface interface { GetInstanceRequestExecute(ctx context.Context, projectId, region, instanceId string) (*sqlserverflex.GetInstanceResponse, error) } // APIClientUserInterface Interface needed for tests type APIClientUserInterface interface { DeleteUserRequestExecute(ctx context.Context, projectId string, region string, instanceId string, userId int64) error } // CreateInstanceWaitHandler will wait for instance creation func CreateInstanceWaitHandler(ctx context.Context, a APIClientInstanceInterface, projectId, instanceId, region string) *wait.AsyncActionHandler[sqlserverflex.GetInstanceResponse] { handler := wait.New(func() (waitFinished bool, response *sqlserverflex.GetInstanceResponse, err error) { s, err := a.GetInstanceRequestExecute(ctx, projectId, region, instanceId) if err != nil { // TODO: catch 502, 503 and 504 return false, nil, err } if s == nil || s.Id == nil || *s.Id != instanceId || s.Status == nil { return false, nil, nil } switch strings.ToLower(string(*s.Status)) { case strings.ToLower(InstanceStateSuccess): if *s.Network.AccessScope == "SNA" { if s.Network.InstanceAddress == nil { tflog.Info(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 } } return true, s, nil case strings.ToLower(InstanceStateUnknown), strings.ToLower(InstanceStateFailed): return true, s, fmt.Errorf("create failed for instance with id %s", instanceId) case strings.ToLower(InstanceStatePending), strings.ToLower(InstanceStateProcessing): tflog.Info(ctx, "request is being handled", map[string]interface{}{ "status": *s.Status, }) return false, nil, nil default: tflog.Info(ctx, "Wait (create) received unknown status", map[string]interface{}{ "instanceId": instanceId, "status": s.Status, }) return false, s, nil } }) return handler } // UpdateInstanceWaitHandler will wait for instance update func UpdateInstanceWaitHandler(ctx context.Context, a APIClientInstanceInterface, projectId, instanceId, region string) *wait.AsyncActionHandler[sqlserverflex.GetInstanceResponse] { handler := wait.New(func() (waitFinished bool, response *sqlserverflex.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 strings.ToLower(string(*s.Status)) { case strings.ToLower(InstanceStateSuccess): return true, s, nil case strings.ToLower(InstanceStateUnknown), strings.ToLower(InstanceStateFailed): return true, s, fmt.Errorf("update failed for instance with id %s", instanceId) case strings.ToLower(InstanceStatePending), strings.ToLower(InstanceStateProcessing): tflog.Info(ctx, "request is being handled", map[string]interface{}{ "status": *s.Status, }) return false, nil, nil default: tflog.Info(ctx, "Wait (update) received unknown status", map[string]interface{}{ "instanceId": instanceId, "status": s.Status, }) return false, s, nil } }) handler.SetSleepBeforeWait(15 * time.Second) handler.SetTimeout(45 * time.Minute) return handler } // PartialUpdateInstanceWaitHandler will wait for instance update func PartialUpdateInstanceWaitHandler(ctx context.Context, a APIClientInstanceInterface, projectId, instanceId, region string) *wait.AsyncActionHandler[sqlserverflex.GetInstanceResponse] { return UpdateInstanceWaitHandler(ctx, a, projectId, instanceId, region) } // DeleteInstanceWaitHandler will wait for instance deletion func DeleteInstanceWaitHandler(ctx context.Context, a APIClientInstanceInterface, projectId, instanceId, region string) *wait.AsyncActionHandler[struct{}] { handler := wait.New(func() (waitFinished bool, response *struct{}, err error) { _, err = a.GetInstanceRequestExecute(ctx, projectId, region, instanceId) if err == nil { return false, nil, nil } var oapiErr *oapierror.GenericOpenAPIError ok := errors.As(err, &oapiErr) if !ok { return false, nil, fmt.Errorf("could not convert error to oapierror.GenericOpenAPIError") } if oapiErr.StatusCode != http.StatusNotFound { return false, nil, err } return true, nil, nil }) handler.SetTimeout(15 * time.Minute) return handler } // DeleteUserWaitHandler will wait for instance deletion func DeleteUserWaitHandler( ctx context.Context, a APIClientUserInterface, projectId, instanceId, region string, userId int64, ) *wait.AsyncActionHandler[struct{}] { handler := wait.New(func() (waitFinished bool, response *struct{}, err error) { err = a.DeleteUserRequestExecute(ctx, projectId, region, instanceId, userId) if err == nil { return false, nil, nil } var oapiErr *oapierror.GenericOpenAPIError ok := errors.As(err, &oapiErr) if !ok { return false, nil, fmt.Errorf("could not convert error to oapierror.GenericOpenAPIError") } switch oapiErr.StatusCode { case http.StatusNotFound: return true, nil, nil case http.StatusInternalServerError: tflog.Warn(ctx, "Wait handler got error 500") return false, nil, nil default: return false, nil, err } }) handler.SetTimeout(15 * time.Minute) handler.SetSleepBeforeWait(15 * time.Second) return handler }