diff --git a/CHANGELOG.md b/CHANGELOG.md index 60d197428..be325f933 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -476,6 +476,10 @@ - **Feature:** Added `_UNKNOWN_DEFAULT_OPEN_API` fallback value to all enums to handle unknown API values gracefully. - [v1.11.0](services/sqlserverflex/CHANGELOG.md#v1110) - `v3beta1api`: **Feature:** Added `labels` to `CreateInstanceRequestPayload`, `GetInstanceReponse`, `UpdateInstancePartiallyRequestPayload`, `UpdateInstanceRequestPayload` + - [v1.12.0](services/sqlserverflex/CHANGELOG.md#v1120) + - `v1api`: + - **Improvement**: Use new `WaiterHelper` struct in the SQLServer Flex WaitHandler + - **Breaking change:** Change return type of `wait.DeleteInstanceWaitHandler()` to `*wait.AsyncActionHandler[sqlserverflex.GetInstanceResponse]` - `stackitmarketplace`: - [v1.17.5](services/stackitmarketplace/CHANGELOG.md#v1175) - **Dependencies:** Bump STACKIT SDK core module from `v0.24.0` to `v0.24.1` diff --git a/services/sqlserverflex/CHANGELOG.md b/services/sqlserverflex/CHANGELOG.md index 81b61ba0c..28c23e189 100644 --- a/services/sqlserverflex/CHANGELOG.md +++ b/services/sqlserverflex/CHANGELOG.md @@ -1,3 +1,8 @@ +## v1.12.0 +- `v1api`: + - **Improvement**: Use new `WaiterHelper` struct in the SQLServer Flex WaitHandler + - **Breaking change:** Change return type of `wait.DeleteInstanceWaitHandler()` to `*wait.AsyncActionHandler[sqlserverflex.GetInstanceResponse]` + ## v1.11.0 - `v3beta1api`: **Feature:** Added `labels` to `CreateInstanceRequestPayload`, `GetInstanceReponse`, `UpdateInstancePartiallyRequestPayload`, `UpdateInstanceRequestPayload` diff --git a/services/sqlserverflex/VERSION b/services/sqlserverflex/VERSION index 285cea5d1..13a15bd96 100644 --- a/services/sqlserverflex/VERSION +++ b/services/sqlserverflex/VERSION @@ -1 +1 @@ -v1.11.0 \ No newline at end of file +v1.12.0 \ No newline at end of file diff --git a/services/sqlserverflex/v2api/wait/wait.go b/services/sqlserverflex/v2api/wait/wait.go index 01747d844..5d53eb280 100644 --- a/services/sqlserverflex/v2api/wait/wait.go +++ b/services/sqlserverflex/v2api/wait/wait.go @@ -3,12 +3,9 @@ package wait import ( "context" "errors" - "fmt" "net/http" - "strings" "time" - "github.com/stackitcloud/stackit-sdk-go/core/oapierror" "github.com/stackitcloud/stackit-sdk-go/core/wait" sqlserverflex "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v2api" ) @@ -21,76 +18,58 @@ const ( InstanceStateFailed = "Failed" ) -// CreateInstanceWaitHandler will wait for instance creation -func CreateInstanceWaitHandler(ctx context.Context, a sqlserverflex.DefaultAPI, projectId, instanceId, region string) *wait.AsyncActionHandler[sqlserverflex.GetInstanceResponse] { - handler := wait.New(func() (waitFinished bool, response *sqlserverflex.GetInstanceResponse, err error) { - s, err := a.GetInstance(ctx, projectId, instanceId, region).Execute() - if err != nil { - return false, nil, err - } - if s == nil || s.Item == nil || s.Item.Id == nil || *s.Item.Id != instanceId || s.Item.Status == nil { - return false, nil, nil - } - switch strings.ToLower(*s.Item.Status) { - case strings.ToLower(InstanceStateSuccess): - return true, s, nil - case strings.ToLower(InstanceStateUnknown), strings.ToLower(InstanceStateFailed): - return true, s, fmt.Errorf("create failed for instance with id %s", instanceId) - default: - return false, s, nil - } - }) - handler.SetTimeout(45 * time.Minute) +func createOrUpdateInstanceWaitHandler(ctx context.Context, client sqlserverflex.DefaultAPI, projectId, instanceId, region string) *wait.AsyncActionHandler[sqlserverflex.GetInstanceResponse] { + waitConfig := wait.WaiterHelper[sqlserverflex.GetInstanceResponse, string]{ + FetchInstance: client.GetInstance(ctx, projectId, instanceId, region).Execute, + GetState: func(response *sqlserverflex.GetInstanceResponse) (string, error) { + if response == nil { + return "", errors.New("empty response") + } + if response.Item == nil { + return "", errors.New("empty instance") + } + if response.Item.Status == nil { + return "", errors.New("status is missing") + } + return *response.Item.Status, nil + }, + ActiveState: []string{InstanceStateSuccess}, + ErrorState: []string{InstanceStateUnknown, InstanceStateFailed, InstanceStateEmpty}, + } + + handler := wait.New(waitConfig.Wait()) handler.SetSleepBeforeWait(5 * time.Second) + handler.SetTimeout(45 * time.Minute) return handler } -// UpdateInstanceWaitHandler will wait for instance update -func UpdateInstanceWaitHandler(ctx context.Context, a sqlserverflex.DefaultAPI, projectId, instanceId, region string) *wait.AsyncActionHandler[sqlserverflex.GetInstanceResponse] { - handler := wait.New(func() (waitFinished bool, response *sqlserverflex.GetInstanceResponse, err error) { - s, err := a.GetInstance(ctx, projectId, instanceId, region).Execute() - if err != nil { - return false, nil, err - } - if s == nil || s.Item == nil || s.Item.Id == nil || *s.Item.Id != instanceId || s.Item.Status == nil { - return false, nil, nil - } - switch strings.ToLower(*s.Item.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) - default: - return false, s, nil - } - }) - handler.SetSleepBeforeWait(2 * time.Second) - handler.SetTimeout(45 * time.Minute) - return handler +// CreateInstanceWaitHandler will wait for instance creation +func CreateInstanceWaitHandler(ctx context.Context, client sqlserverflex.DefaultAPI, projectId, instanceId, region string) *wait.AsyncActionHandler[sqlserverflex.GetInstanceResponse] { + return createOrUpdateInstanceWaitHandler(ctx, client, projectId, instanceId, region) } -// PartialUpdateInstanceWaitHandler will wait for instance update -func PartialUpdateInstanceWaitHandler(ctx context.Context, a sqlserverflex.DefaultAPI, projectId, instanceId, region string) *wait.AsyncActionHandler[sqlserverflex.GetInstanceResponse] { - return UpdateInstanceWaitHandler(ctx, a, projectId, instanceId, region) +// UpdateInstanceWaitHandler will wait for instance update +func UpdateInstanceWaitHandler(ctx context.Context, client sqlserverflex.DefaultAPI, projectId, instanceId, region string) *wait.AsyncActionHandler[sqlserverflex.GetInstanceResponse] { + return createOrUpdateInstanceWaitHandler(ctx, client, projectId, instanceId, region) } // DeleteInstanceWaitHandler will wait for instance deletion -func DeleteInstanceWaitHandler(ctx context.Context, a sqlserverflex.DefaultAPI, projectId, instanceId, region string) *wait.AsyncActionHandler[struct{}] { - handler := wait.New(func() (waitFinished bool, response *struct{}, err error) { - _, err = a.GetInstance(ctx, projectId, instanceId, region).Execute() - 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 - }) +func DeleteInstanceWaitHandler(ctx context.Context, client sqlserverflex.DefaultAPI, projectId, instanceId, region string) *wait.AsyncActionHandler[sqlserverflex.GetInstanceResponse] { + waitConfig := wait.WaiterHelper[sqlserverflex.GetInstanceResponse, string]{ + FetchInstance: client.GetInstance(ctx, projectId, instanceId, region).Execute, + GetState: func(response *sqlserverflex.GetInstanceResponse) (string, error) { + if response == nil { + return "", errors.New("empty response") + } + if response.Item.Status == nil { + return "", errors.New("status is missing in response") + } + return *response.Item.Status, nil + }, + ErrorState: []string{InstanceStateFailed}, + DeleteHttpErrorStatusCodes: []int{http.StatusNotFound}, + } + handler := wait.New(waitConfig.Wait()) handler.SetTimeout(15 * time.Minute) return handler } diff --git a/services/sqlserverflex/v2api/wait/wait_test.go b/services/sqlserverflex/v2api/wait/wait_test.go index 1721e53e1..58e1bc363 100644 --- a/services/sqlserverflex/v2api/wait/wait_test.go +++ b/services/sqlserverflex/v2api/wait/wait_test.go @@ -2,6 +2,7 @@ package wait import ( "context" + "fmt" "testing" "testing/synctest" "time" @@ -10,6 +11,7 @@ import ( "github.com/stackitcloud/stackit-sdk-go/core/oapierror" "github.com/stackitcloud/stackit-sdk-go/core/utils" + "github.com/stackitcloud/stackit-sdk-go/core/wait" sqlserverflex "github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex/v2api" ) @@ -46,88 +48,7 @@ func newAPIMock(settings mockSettings) sqlserverflex.DefaultAPI { } } -func TestCreateInstanceWaitHandler(t *testing.T) { - tests := []struct { - desc string - instanceGetFails bool - instanceState string - usersGetErrorStatus int - wantErr bool - wantResp bool - }{ - { - desc: "create_succeeded", - instanceGetFails: false, - instanceState: InstanceStateSuccess, - wantErr: false, - wantResp: true, - }, - { - desc: "create_failed", - instanceGetFails: false, - instanceState: InstanceStateFailed, - wantErr: true, - wantResp: true, - }, - { - desc: "create_failed_2", - instanceGetFails: false, - instanceState: InstanceStateEmpty, - wantErr: true, - wantResp: true, - }, - { - desc: "instance_get_fails", - instanceGetFails: true, - wantErr: true, - wantResp: false, - }, - { - desc: "timeout", - instanceGetFails: false, - instanceState: InstanceStateProcessing, - wantErr: true, - wantResp: true, - }, - } - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - synctest.Test(t, func(t *testing.T) { - instanceId := "foo-bar" - instanceState := tt.instanceState - - apiClient := newAPIMock(mockSettings{ - instanceId: &instanceId, - instanceState: &instanceState, - instanceGetFails: tt.instanceGetFails, - }) - - var wantRes *sqlserverflex.GetInstanceResponse - if tt.wantResp { - wantRes = &sqlserverflex.GetInstanceResponse{ - Item: &sqlserverflex.Instance{ - Id: &instanceId, - Status: utils.Ptr(tt.instanceState), - }, - } - } - - handler := CreateInstanceWaitHandler(context.Background(), apiClient, "", instanceId, "") - - gotRes, err := handler.SetTimeout(10 * time.Millisecond).SetSleepBeforeWait(1 * time.Millisecond).WaitWithContext(context.Background()) - - if (err != nil) != tt.wantErr { - t.Fatalf("handler error = %v, wantErr %v", err, tt.wantErr) - } - if !cmp.Equal(gotRes, wantRes) { - t.Fatalf("handler gotRes = %v, want %v", gotRes, wantRes) - } - }) - }) - } -} - -func TestUpdateInstanceWaitHandler(t *testing.T) { +func TestCreateOrUpdateInstanceWaitHandler(t *testing.T) { tests := []struct { desc string instanceGetFails bool @@ -136,28 +57,28 @@ func TestUpdateInstanceWaitHandler(t *testing.T) { wantResp bool }{ { - desc: "update_succeeded", + desc: "create_or_update_succeeded", instanceGetFails: false, instanceState: InstanceStateSuccess, wantErr: false, wantResp: true, }, { - desc: "update_failed", + desc: "create_or_update_failed", instanceGetFails: false, instanceState: InstanceStateFailed, wantErr: true, wantResp: true, }, { - desc: "update_failed_2", + desc: "create_or_update_failed_2", instanceGetFails: false, instanceState: InstanceStateEmpty, wantErr: true, wantResp: true, }, { - desc: "get_fails", + desc: "instance_get_fails", instanceGetFails: true, wantErr: true, wantResp: false, @@ -167,43 +88,51 @@ func TestUpdateInstanceWaitHandler(t *testing.T) { instanceGetFails: false, instanceState: InstanceStateProcessing, wantErr: true, - wantResp: true, + wantResp: false, }, } - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - synctest.Test(t, func(t *testing.T) { - instanceId := "foo-bar" - instanceState := tt.instanceState - apiClient := newAPIMock(mockSettings{ - instanceId: &instanceId, - instanceState: &instanceState, - instanceGetFails: tt.instanceGetFails, - }) + handlers := map[string]func(context.Context, sqlserverflex.DefaultAPI, string, string, string) *wait.AsyncActionHandler[sqlserverflex.GetInstanceResponse]{ + "common logic": createOrUpdateInstanceWaitHandler, + "create": CreateInstanceWaitHandler, + "update": UpdateInstanceWaitHandler, + } - var wantRes *sqlserverflex.GetInstanceResponse - if tt.wantResp { - wantRes = &sqlserverflex.GetInstanceResponse{ - Item: &sqlserverflex.Instance{ - Id: &instanceId, - Status: utils.Ptr(tt.instanceState), - }, + for handlerDesc, handlerFn := range handlers { + for _, tt := range tests { + t.Run(fmt.Sprintf("%s - %s", handlerDesc, tt.desc), func(t *testing.T) { + synctest.Test(t, func(t *testing.T) { + instanceId := "foo-bar" + + apiClient := newAPIMock(mockSettings{ + instanceGetFails: tt.instanceGetFails, + instanceId: &instanceId, + instanceState: &tt.instanceState, + }) + + var wantRes *sqlserverflex.GetInstanceResponse + if tt.wantResp { + wantRes = &sqlserverflex.GetInstanceResponse{ + Item: &sqlserverflex.Instance{ + Id: &instanceId, + Status: utils.Ptr(tt.instanceState), + }, + } } - } - handler := UpdateInstanceWaitHandler(context.Background(), apiClient, "", instanceId, "") + handler := handlerFn(context.Background(), apiClient, "", instanceId, "") + gotRes, err := handler.SetTimeout(10 * time.Millisecond).SetSleepBeforeWait(1 * time.Millisecond).WaitWithContext(context.Background()) - gotRes, err := handler.SetTimeout(10 * time.Millisecond).SetSleepBeforeWait(1 * time.Millisecond).WaitWithContext(context.Background()) - - if (err != nil) != tt.wantErr { - t.Fatalf("handler error = %v, wantErr %v", err, tt.wantErr) - } - if !cmp.Equal(gotRes, wantRes) { - t.Fatalf("handler gotRes = %v, want %v", gotRes, wantRes) - } + if (err != nil) != tt.wantErr { + t.Fatalf("handler error = %v, wantErr %v", err, tt.wantErr) + } + diff := cmp.Diff(gotRes, wantRes) + if diff != "" { + t.Fatalf("handler gotRes = %+v\n want %+v\n diff = %s", gotRes, wantRes, diff) + } + }) }) - }) + } } }