Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
2 changes: 1 addition & 1 deletion examples/ske/ske.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
3 changes: 3 additions & 0 deletions services/ske/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
2 changes: 1 addition & 1 deletion services/ske/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v1.17.0
v1.18.0
125 changes: 68 additions & 57 deletions services/ske/v2api/wait/wait.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package wait

import (
"context"
"fmt"
"errors"
"time"

"github.com/stackitcloud/stackit-sdk-go/core/wait"
Expand Down Expand Up @@ -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] {
Copy link
Copy Markdown
Member

@rubenhoenle rubenhoenle Jun 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a breaking change, you can't do this.

You must keep CreateOrUpdateClusterWaitHandler with public visibility, you can just deprecate it and mark it for removal ("removal" from user perspective, actually we will be changing it to private visibility) in 6 months. Deprecation should contain instructions to migrate to CreateClusterWaitHandler and UpdateClusterWaitHandler.

handler := wait.New(func() (waitFinished bool, response *ske.Cluster, err error) {
s, err := a.GetCluster(ctx, projectId, region, name).Execute()
if err != nil {
Expand Down Expand Up @@ -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
}
80 changes: 45 additions & 35 deletions services/ske/v2api/wait/wait_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package wait

import (
"context"
"fmt"
"net/http"
"testing"
"testing/synctest"
Expand All @@ -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"
)

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
}
})
})
})
}
}
}

Expand Down
Loading