package sqlserverflexbeta 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/sqlserverflexbeta" ) // READY, PENDING, PROGRESSING, FAILURE, UNKNOWN, const ( InstanceStateEmpty = "" InstanceStateSuccess = "READY" InstanceStatePending = "PENDING" InstanceStateProcessing = "PROGRESSING" InstanceStateFailed = "FAILURE" InstanceStateUnknown = "UNKNOWN" InstanceStateTerminating = "TERMINATING" ) // APIClientInterface Interface needed for tests type APIClientInterface interface { GetInstanceRequestExecute( ctx context.Context, projectId, region, instanceId string, ) (*sqlserverflex.GetInstanceResponse, error) GetDatabaseRequestExecute( ctx context.Context, projectId string, region string, instanceId string, databaseName string, ) (*sqlserverflex.GetDatabaseResponse, error) GetUserRequestExecute( ctx context.Context, projectId string, region string, instanceId string, userId int64, ) (*sqlserverflex.GetUserResponse, error) ListRolesRequestExecute( ctx context.Context, projectId string, region string, instanceId string, ) (*sqlserverflex.ListRolesResponse, error) ListUsersRequest(ctx context.Context, projectId string, region string, instanceId string) sqlserverflex.ApiListUsersRequestRequest ListUsersRequestExecute( ctx context.Context, projectId string, region string, instanceId string, ) (*sqlserverflex.ListUserResponse, 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 APIClientInterface, 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 { var oapiErr *oapierror.GenericOpenAPIError ok := errors.As(err, &oapiErr) if !ok { return false, nil, fmt.Errorf("could not convert error to oapierror.GenericOpenAPIError: %w", err) } switch oapiErr.StatusCode { case http.StatusNotFound: return false, nil, nil default: return false, nil, fmt.Errorf("api error: %w", 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 != nil && s.Network.AccessScope != nil && *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 } } tflog.Info(ctx, "trying to get roles") time.Sleep(10 * time.Second) _, rolesErr := a.ListRolesRequestExecute(ctx, projectId, region, instanceId) if rolesErr != nil { var oapiErr *oapierror.GenericOpenAPIError ok := errors.As(rolesErr, &oapiErr) if !ok { return false, nil, fmt.Errorf("could not convert error to oapierror.GenericOpenAPIError") } if oapiErr.StatusCode != http.StatusInternalServerError { tflog.Info( ctx, "got error from api", map[string]interface{}{ "error": rolesErr.Error(), }, ) return false, nil, rolesErr } tflog.Info( ctx, "wait for get-roles to work hack", map[string]interface{}{}, ) time.Sleep(10 * time.Second) return false, nil, nil } tflog.Info(ctx, "trying to get users") time.Sleep(10 * time.Second) _, usersErr := a.ListUsersRequestExecute(ctx, projectId, region, instanceId) if usersErr != nil { var oapiErr *oapierror.GenericOpenAPIError ok := errors.As(usersErr, &oapiErr) if !ok { return false, nil, fmt.Errorf("could not convert error to oapierror.GenericOpenAPIError") } if oapiErr.StatusCode != http.StatusInternalServerError { tflog.Info( ctx, "got error from api", map[string]interface{}{ "error": rolesErr.Error(), }, ) return false, nil, usersErr } tflog.Info( ctx, "wait for get-users to work hack", map[string]interface{}{}, ) time.Sleep(10 * time.Second) return false, nil, nil } return true, s, nil case strings.ToLower(InstanceStateUnknown), strings.ToLower(InstanceStateFailed): return true, nil, 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, }, ) time.Sleep(10 * time.Second) return false, nil, nil default: tflog.Info( ctx, "Wait (create) received unknown status", map[string]interface{}{ "instanceId": instanceId, "status": s.Status, }, ) return true, nil, errors.New("unknown status received") } }, ) return handler } // UpdateInstanceWaitHandler will wait for instance update func UpdateInstanceWaitHandler( ctx context.Context, a APIClientInterface, 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, s, nil default: tflog.Info( ctx, "Wait (update) received unknown status", map[string]interface{}{ "instanceId": instanceId, "status": s.Status, }, ) return false, s, nil } }, ) return handler } // DeleteInstanceWaitHandler will wait for instance deletion func DeleteInstanceWaitHandler( ctx context.Context, a APIClientInterface, 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, s, 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(30 * time.Minute) return handler } // CreateDatabaseWaitHandler will wait for instance creation func CreateDatabaseWaitHandler( ctx context.Context, a APIClientInterface, projectId, instanceId, region, databaseName string, ) *wait.AsyncActionHandler[sqlserverflex.GetDatabaseResponse] { handler := wait.New( func() (waitFinished bool, response *sqlserverflex.GetDatabaseResponse, err error) { s, err := a.GetDatabaseRequestExecute(ctx, projectId, region, instanceId, databaseName) if err != nil { var oapiErr *oapierror.GenericOpenAPIError ok := errors.As(err, &oapiErr) if !ok { return false, nil, fmt.Errorf("get database - could not convert error to oapierror.GenericOpenAPIError: %s", err.Error()) } if oapiErr.StatusCode != http.StatusNotFound { return false, nil, err } return false, nil, nil } if s == nil || s.Name == nil || *s.Name != databaseName { return false, nil, errors.New("response did return different result") } return true, s, nil }, ) return handler } // CreateUserWaitHandler will wait for instance creation func CreateUserWaitHandler( ctx context.Context, a APIClientInterface, projectId, instanceId, region string, userId int64, ) *wait.AsyncActionHandler[sqlserverflex.GetUserResponse] { handler := wait.New( func() (waitFinished bool, response *sqlserverflex.GetUserResponse, err error) { s, err := a.GetUserRequestExecute(ctx, projectId, region, instanceId, userId) if err != 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 false, nil, nil } return true, s, nil }, ) return handler } // WaitForUserWaitHandler will wait for instance creation func WaitForUserWaitHandler( ctx context.Context, a APIClientInterface, projectId, instanceId, region, userName string, ) *wait.AsyncActionHandler[sqlserverflex.ListUserResponse] { startTime := time.Now() timeOut := 2 * time.Minute handler := wait.New( func() (waitFinished bool, response *sqlserverflex.ListUserResponse, err error) { if time.Since(startTime) > timeOut { return false, nil, errors.New("ran into timeout") } s, err := a.ListUsersRequest(ctx, projectId, region, instanceId).Size(100).Execute() if err != nil { var oapiErr *oapierror.GenericOpenAPIError ok := errors.As(err, &oapiErr) if !ok { return false, nil, fmt.Errorf("Wait (list users) could not convert error to oapierror.GenericOpenAPIError: %s", err.Error()) } if oapiErr.StatusCode != http.StatusNotFound { return false, nil, err } tflog.Info( ctx, "Wait (list users) still waiting", map[string]interface{}{}, ) return false, nil, nil } users, ok := s.GetUsersOk() if !ok { return false, nil, errors.New("no users found") } for _, u := range users { if u.GetUsername() == userName { return true, s, nil } } tflog.Info( ctx, "Wait (list users) user still not present", map[string]interface{}{}, ) return false, nil, nil }, ) return handler } // DeleteUserWaitHandler will wait for instance deletion func DeleteUserWaitHandler( ctx context.Context, a APIClientInterface, projectId, region, instanceId string, userId int64, ) *wait.AsyncActionHandler[struct{}] { handler := wait.New( func() (waitFinished bool, response *struct{}, err error) { _, err = a.GetUserRequestExecute(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 default: return false, nil, err } }, ) handler.SetTimeout(15 * time.Minute) handler.SetSleepBeforeWait(15 * time.Second) return handler }