diff --git a/CHANGELOG.md b/CHANGELOG.md index 299077eb6..c756d56e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -540,6 +540,9 @@ - **Feature:** Introduce enums for various attributes - [v1.17.0](services/ske/CHANGELOG.md#v1170) - **Feature:** New field `ServiceAccountIssuer` for `ClusterStatus` model struct + - [v1.18.0](services/ske/CHANGELOG.md#v1180) + - `v2api`: + - **Improvement:** Use new WaiterHelper for ske waiters - `sqlserverflex`: - [v1.6.3](services/sqlserverflex/CHANGELOG.md#v163) - **Dependencies:** Bump STACKIT SDK core module from `v0.24.0` to `v0.24.1` diff --git a/examples/ske/ske.go b/examples/ske/ske.go index a79580a02..22fe56f47 100644 --- a/examples/ske/ske.go +++ b/examples/ske/ske.go @@ -84,7 +84,7 @@ func main() { } // Wait for cluster creation to complete - _, err = wait.CreateOrUpdateClusterWaitHandler(context.Background(), skeClient.DefaultAPI, projectId, region, clusterName).WaitWithContext(context.Background()) + _, err = wait.CreateClusterWaitHandler(context.Background(), skeClient.DefaultAPI, projectId, region, clusterName).WaitWithContext(context.Background()) if err != nil { fmt.Fprintf(os.Stderr, "Error when calling `CreateOrUpdateCluster`: %v\n", err) } else { diff --git a/services/ske/CHANGELOG.md b/services/ske/CHANGELOG.md index 2df13ef68..911afcee1 100644 --- a/services/ske/CHANGELOG.md +++ b/services/ske/CHANGELOG.md @@ -1,3 +1,6 @@ +## v1.18.0 +- **Improvement:** Use new WaiterHelper for ske waiters + ## v1.17.0 - **Feature:** New field `ServiceAccountIssuer` for `ClusterStatus` model struct diff --git a/services/ske/VERSION b/services/ske/VERSION index 07c3efad8..f72019128 100644 --- a/services/ske/VERSION +++ b/services/ske/VERSION @@ -1 +1 @@ -v1.17.0 +v1.18.0 diff --git a/services/ske/v2api/wait/wait.go b/services/ske/v2api/wait/wait.go index fa46dc32b..7bd0859fc 100644 --- a/services/ske/v2api/wait/wait.go +++ b/services/ske/v2api/wait/wait.go @@ -2,7 +2,7 @@ package wait import ( "context" - "fmt" + "errors" "time" "github.com/stackitcloud/stackit-sdk-go/core/wait" @@ -31,8 +31,18 @@ const ( RUNTIMEERRORCODE_OBSERVABILITY_INSTANCE_NOT_FOUND = ske.RUNTIMEERRORCODE_SKE_OBSERVABILITY_INSTANCE_NOT_FOUND ) -// CreateOrUpdateClusterWaitHandler will wait for cluster creation or update -func CreateOrUpdateClusterWaitHandler(ctx context.Context, a ske.DefaultAPI, projectId, region, name string) *wait.AsyncActionHandler[ske.Cluster] { +// CreateClusterWaitHandler will wait for cluster creation +func CreateClusterWaitHandler(ctx context.Context, a ske.DefaultAPI, projectId, region, name string) *wait.AsyncActionHandler[ske.Cluster] { + return createOrUpdateClusterWaitHandler(ctx, a, projectId, region, name) +} + +// UpdateClusterWaitHandler will wait for cluster update +func UpdateClusterWaitHandler(ctx context.Context, a ske.DefaultAPI, projectId, region, name string) *wait.AsyncActionHandler[ske.Cluster] { + return createOrUpdateClusterWaitHandler(ctx, a, projectId, region, name) +} + +// createOrUpdateClusterWaitHandler will wait for cluster creation or update +func createOrUpdateClusterWaitHandler(ctx context.Context, a ske.DefaultAPI, projectId, region, name string) *wait.AsyncActionHandler[ske.Cluster] { handler := wait.New(func() (waitFinished bool, response *ske.Cluster, err error) { s, err := a.GetCluster(ctx, projectId, region, name).Execute() if err != nil { @@ -151,72 +161,73 @@ func TriggerClusterWakeupWaitHandler(ctx context.Context, a ske.DefaultAPI, proj // RotateCredentialsWaitHandler will wait for credentials rotation func RotateCredentialsWaitHandler(ctx context.Context, a ske.DefaultAPI, projectId, region, clusterName string) *wait.AsyncActionHandler[ske.Cluster] { - handler := wait.New(func() (waitFinished bool, response *ske.Cluster, err error) { - s, err := a.GetCluster(ctx, projectId, region, clusterName).Execute() - if err != nil { - return false, nil, err - } - state := *s.Status.Aggregated - - if state == ske.CLUSTERSTATUSSTATE_STATE_HEALTHY || state == ske.CLUSTERSTATUSSTATE_STATE_HIBERNATED { - return true, s, nil - } - - if state == ske.CLUSTERSTATUSSTATE_STATE_RECONCILING { - return false, nil, nil - } - - return true, s, fmt.Errorf("unexpected state %s while waiting for cluster reconciliation", state) - }) - + waitConfig := wait.WaiterHelper[ske.Cluster, ske.ClusterStatusState]{ + FetchInstance: a.GetCluster(ctx, projectId, region, clusterName).Execute, + GetState: func(s *ske.Cluster) (ske.ClusterStatusState, error) { + if s == nil || s.Status == nil || s.Status.Aggregated == nil { + return "", errors.New("empty response or status") + } + return *s.Status.Aggregated, nil + }, + ActiveState: []ske.ClusterStatusState{ + ske.CLUSTERSTATUSSTATE_STATE_HEALTHY, + ske.CLUSTERSTATUSSTATE_STATE_HIBERNATED, + }, + ErrorState: []ske.ClusterStatusState{ + ske.CLUSTERSTATUSSTATE_STATE_UNHEALTHY, + ske.CLUSTERSTATUSSTATE_STATE_DELETING, + ske.CLUSTERSTATUSSTATE_STATE_HIBERNATING, + ske.CLUSTERSTATUSSTATE_STATE_WAKINGUP, + }, + } + + handler := wait.New(waitConfig.Wait()) handler.SetTimeout(10 * time.Minute) return handler } // StartCredentialsRotationWaitHandler will wait for credentials rotation func StartCredentialsRotationWaitHandler(ctx context.Context, a ske.DefaultAPI, projectId, region, clusterName string) *wait.AsyncActionHandler[ske.Cluster] { - handler := wait.New(func() (waitFinished bool, response *ske.Cluster, err error) { - s, err := a.GetCluster(ctx, projectId, region, clusterName).Execute() - if err != nil { - return false, nil, err - } - state := *s.Status.CredentialsRotation.Phase - - if state == ske.CREDENTIALSROTATIONSTATEPHASE_PREPARED { - return true, s, nil - } - - if state == ske.CREDENTIALSROTATIONSTATEPHASE_PREPARING { - return false, nil, nil - } - - return true, s, fmt.Errorf("unexpected status %s while waiting for cluster credentials rotation to be prepared", state) - }) - + waitConfig := wait.WaiterHelper[ske.Cluster, ske.CredentialsRotationStatePhase]{ + FetchInstance: a.GetCluster(ctx, projectId, region, clusterName).Execute, + GetState: func(s *ske.Cluster) (ske.CredentialsRotationStatePhase, error) { + if s == nil || s.Status == nil || s.Status.CredentialsRotation == nil || s.Status.CredentialsRotation.Phase == nil { + return "", errors.New("empty response or credentials rotation phase") + } + return *s.Status.CredentialsRotation.Phase, nil + }, + ActiveState: []ske.CredentialsRotationStatePhase{ske.CREDENTIALSROTATIONSTATEPHASE_PREPARED}, + ErrorState: []ske.CredentialsRotationStatePhase{ + ske.CREDENTIALSROTATIONSTATEPHASE_NEVER, + ske.CREDENTIALSROTATIONSTATEPHASE_COMPLETING, + ske.CREDENTIALSROTATIONSTATEPHASE_COMPLETED, + }, + } + + handler := wait.New(waitConfig.Wait()) handler.SetTimeout(45 * time.Minute) return handler } // CompleteCredentialsRotationWaitHandler will wait for credentials rotation func CompleteCredentialsRotationWaitHandler(ctx context.Context, a ske.DefaultAPI, projectId, region, clusterName string) *wait.AsyncActionHandler[ske.Cluster] { - handler := wait.New(func() (waitFinished bool, response *ske.Cluster, err error) { - s, err := a.GetCluster(ctx, projectId, region, clusterName).Execute() - if err != nil { - return false, nil, err - } - state := *s.Status.CredentialsRotation.Phase - - if state == ske.CREDENTIALSROTATIONSTATEPHASE_COMPLETED { - return true, s, nil - } - - if state == ske.CREDENTIALSROTATIONSTATEPHASE_COMPLETING { - return false, nil, nil - } - - return true, s, fmt.Errorf("unexpected status %s while waiting for cluster credentials rotation to be completed", state) - }) - + waitConfig := wait.WaiterHelper[ske.Cluster, ske.CredentialsRotationStatePhase]{ + FetchInstance: a.GetCluster(ctx, projectId, region, clusterName).Execute, + GetState: func(s *ske.Cluster) (ske.CredentialsRotationStatePhase, error) { + if s == nil || s.Status == nil || s.Status.CredentialsRotation == nil || s.Status.CredentialsRotation.Phase == nil { + return "", errors.New("empty response or credentials rotation phase") + } + return *s.Status.CredentialsRotation.Phase, nil + }, + ActiveState: []ske.CredentialsRotationStatePhase{ske.CREDENTIALSROTATIONSTATEPHASE_COMPLETED}, + ErrorState: []ske.CredentialsRotationStatePhase{ + ske.CREDENTIALSROTATIONSTATEPHASE_NEVER, + ske.CREDENTIALSROTATIONSTATEPHASE_PREPARING, + ske.CREDENTIALSROTATIONSTATEPHASE_PREPARED, + }, + } + + handler := wait.New(waitConfig.Wait()) handler.SetTimeout(45 * time.Minute) return handler } diff --git a/services/ske/v2api/wait/wait_test.go b/services/ske/v2api/wait/wait_test.go index 29dd38751..11d2bf8e6 100644 --- a/services/ske/v2api/wait/wait_test.go +++ b/services/ske/v2api/wait/wait_test.go @@ -2,6 +2,7 @@ package wait import ( "context" + "fmt" "net/http" "testing" "testing/synctest" @@ -11,6 +12,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" ske "github.com/stackitcloud/stackit-sdk-go/services/ske/v2api" ) @@ -84,7 +86,7 @@ func TestCreateOrUpdateClusterWaitHandler(t *testing.T) { wantResp bool }{ { - desc: "create_succeeded", + desc: "succeeded", getFails: false, resourceState: ske.CLUSTERSTATUSSTATE_STATE_HEALTHY, wantErr: false, @@ -119,48 +121,56 @@ func TestCreateOrUpdateClusterWaitHandler(t *testing.T) { wantResp: false, }, } - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - synctest.Test(t, func(t *testing.T) { - name := "cluster" - apiClient := newAPIMock(mockSettings{ - getFails: tt.getFails, - name: name, - resourceState: tt.resourceState, - invalidArgusInstance: tt.invalidArgusInstance, - }) + handlers := map[string]func(context.Context, ske.DefaultAPI, string, string, string) *wait.AsyncActionHandler[ske.Cluster]{ + "common logic": createOrUpdateClusterWaitHandler, + "create": CreateClusterWaitHandler, + "update": UpdateClusterWaitHandler, + } - var wantRes *ske.Cluster - rs := ske.ClusterStatusState(tt.resourceState) - if tt.wantResp { - wantRes = &ske.Cluster{ - Name: &name, - Status: &ske.ClusterStatus{ - Aggregated: &rs, - }, - } + 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) { + name := "cluster" + + apiClient := newAPIMock(mockSettings{ + getFails: tt.getFails, + name: name, + resourceState: tt.resourceState, + invalidArgusInstance: tt.invalidArgusInstance, + }) + + var wantRes *ske.Cluster + rs := ske.ClusterStatusState(tt.resourceState) + if tt.wantResp { + wantRes = &ske.Cluster{ + Name: &name, + Status: &ske.ClusterStatus{ + Aggregated: &rs, + }, + } - if tt.invalidArgusInstance { - wantRes.Status.Error = &ske.RuntimeError{ - Code: utils.Ptr(ske.RUNTIMEERRORCODE_SKE_OBSERVABILITY_INSTANCE_NOT_FOUND), - Message: utils.Ptr("invalid argus instance"), + if tt.invalidArgusInstance { + wantRes.Status.Error = &ske.RuntimeError{ + Code: utils.Ptr(ske.RUNTIMEERRORCODE_SKE_OBSERVABILITY_INSTANCE_NOT_FOUND), + Message: utils.Ptr("invalid argus instance"), + } } } - } - - handler := CreateOrUpdateClusterWaitHandler(context.Background(), apiClient, "", testRegion, name) - gotRes, err := handler.SetTimeout(10 * time.Millisecond).WaitWithContext(context.Background()) + handler := handlerFn(context.Background(), apiClient, "", testRegion, name) + gotRes, err := handler.SetTimeout(10 * 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) + } + if !cmp.Equal(gotRes, wantRes) { + t.Fatalf("handler gotRes = %+v, want %+v", gotRes, wantRes) + } + }) }) - }) + } } }