Skip to content
Merged
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 @@ -102,6 +102,9 @@
- **Dependencies:** Bump STACKIT SDK core module to `v0.26.0`
- [v0.10.0](services/edge/CHANGELOG.md#v0100)
- Align package to latest API specification
- [v0.11.0](services/edge/CHANGELOG.md#v0110)
- **Improvement:** Use new `WaiterHandler` struct in the Edge WaitHandler
- **Deprecation:** Deprecated `ErrInstanceCreationFailed` and `ErrInstanceIsBeingDeleted` in `wait` package
- `git`:
- [v0.11.2](services/git/CHANGELOG.md#v0112)
- **Dependencies:** Bump STACKIT SDK core module from `v0.24.0` to `v0.24.1`
Expand Down
4 changes: 4 additions & 0 deletions services/edge/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## v0.11.0
- **Improvement:** Use new `WaiterHandler` struct in the Edge WaitHandler
- **Deprecation:** Deprecated `ErrInstanceCreationFailed` and `ErrInstanceIsBeingDeleted` in `wait` package

## v0.10.0
- Align package to latest API specification

Expand Down
2 changes: 1 addition & 1 deletion services/edge/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v0.10.0
v0.11.0
144 changes: 78 additions & 66 deletions services/edge/v1beta1api/wait/wait.go
Comment thread
marceljk marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -22,38 +22,31 @@ const (
)

var (
ErrInstanceNotFound = errors.New("instance not found")
ErrInstanceNotFound = errors.New("instance not found")
// Deprecated: ErrInstanceStatusUndefined is no longer used and will be removed after 2026-11-07
ErrInstanceStatusUndefined = errors.New("instance status undefined")
ErrInstanceCreationFailed = errors.New("instance creation failed")
ErrInstanceIsBeingDeleted = errors.New("instance is being deleted")
// Deprecated: ErrInstanceCreationFailed is no longer used and will be removed after 2026-11-07
ErrInstanceCreationFailed = errors.New("instance creation failed")
// Deprecated: ErrInstanceIsBeingDeleted is no longer used and will be removed after 2026-11-07
ErrInstanceIsBeingDeleted = errors.New("instance is being deleted")
)

// createOrUpdateInstanceWaitHandler contains the shared logic for waiting on instance creation or updates.
func createOrUpdateInstanceWaitHandler(ctx context.Context, getInstance func(ctx context.Context) (*edge.Instance, error)) *wait.AsyncActionHandler[edge.Instance] {
handler := wait.New(func() (waitFinished bool, response *edge.Instance, err error) {
instance, err := getInstance(ctx)
if err != nil {
return false, nil, err
}

if instance == nil {
return false, nil, ErrInstanceNotFound
}

status := instance.Status
switch status {
case INSTANCESTATUS_ACTIVE:
return true, instance, nil
case INSTANCESTATUS_ERROR:
return true, instance, ErrInstanceCreationFailed
case INSTANCESTATUS_RECONCILING:
return false, nil, nil
case INSTANCESTATUS_DELETING:
return true, instance, ErrInstanceIsBeingDeleted
default:
return false, nil, nil
}
})
waitConfig := wait.WaiterHelper[edge.Instance, string]{
FetchInstance: func() (*edge.Instance, error) {
return getInstance(ctx)
},
GetState: func(e *edge.Instance) (string, error) {
if e == nil {
return "", ErrInstanceNotFound
}
return e.Status, nil
},
ActiveState: []string{INSTANCESTATUS_ACTIVE},
ErrorState: []string{INSTANCESTATUS_ERROR, INSTANCESTATUS_DELETING},
}
handler := wait.New(waitConfig.Wait())
handler.SetTimeout(timeoutMinutes * time.Minute)
return handler
}
Expand All @@ -74,17 +67,18 @@ func CreateOrUpdateInstanceByNameWaitHandler(ctx context.Context, a edge.Default

// deleteInstanceWaitHandler contains the shared logic for waiting on instance deletion.
func deleteInstanceWaitHandler(ctx context.Context, getInstance func(ctx context.Context) (*edge.Instance, error)) *wait.AsyncActionHandler[edge.Instance] {
handler := wait.New(func() (waitFinished bool, response *edge.Instance, err error) {
_, err = getInstance(ctx)
if err == nil {
return false, nil, nil
}
var oapiErr *oapierror.GenericOpenAPIError
if errors.As(err, &oapiErr) && oapiErr.StatusCode == http.StatusNotFound {
return true, nil, nil
}
return false, nil, err
})
waitConfig := wait.WaiterHelper[edge.Instance, string]{
FetchInstance: func() (*edge.Instance, error) {
return getInstance(ctx)
},
GetState: func(e *edge.Instance) (string, error) {
if e == nil {
return "", ErrInstanceNotFound
}
return e.Status, nil
},
}
handler := wait.New(waitConfig.Wait())
handler.SetTimeout(timeoutMinutes * time.Minute)
return handler
}
Expand All @@ -105,21 +99,30 @@ func DeleteInstanceByNameWaitHandler(ctx context.Context, a edge.DefaultAPI, pro

// kubeconfigWaitHandlerHelper contains the shared logic for waiting for the instance to become ready before retrieving the kubeconfig.
func kubeconfigWaitHandlerHelper(ctx context.Context, checkInstance func(ctx context.Context) error, getKubeconfig func(ctx context.Context) (*edge.Kubeconfig, error)) *wait.AsyncActionHandler[edge.Kubeconfig] {
handler := wait.New(func() (waitFinished bool, response *edge.Kubeconfig, err error) {
err = checkInstance(ctx)
if err != nil {
return false, nil, err
}
kubeconfig, err := getKubeconfig(ctx)
var oapiErr *oapierror.GenericOpenAPIError
if err != nil {
if errors.As(err, &oapiErr) && oapiErr.StatusCode == http.StatusNotFound {
return false, nil, nil
waitConfig := wait.WaiterHelper[edge.Kubeconfig, string]{
FetchInstance: func() (*edge.Kubeconfig, error) {
err := checkInstance(ctx)
if err != nil {
return nil, err
}
return false, nil, err
}
return true, kubeconfig, nil
})
config, err := getKubeconfig(ctx)
if err != nil {
var apiErr *oapierror.GenericOpenAPIError
if ok := errors.As(err, &apiErr); ok && apiErr.StatusCode == http.StatusNotFound {
return nil, nil
}
}
return config, err
},
GetState: func(e *edge.Kubeconfig) (string, error) {
if e == nil {
return "NOT_READY", nil
}
return "READY", nil
},
ActiveState: []string{"READY"},
}
handler := wait.New(waitConfig.Wait())
handler.SetTimeout(timeoutMinutes * time.Minute)
return handler
}
Expand Down Expand Up @@ -158,21 +161,30 @@ func KubeconfigByInstanceNameWaitHandler(ctx context.Context, a edge.DefaultAPI,

// tokenWaitHandlerHelper contains the shared logic for waiting for the instance to become ready before retrieving the service token.
func tokenWaitHandlerHelper(ctx context.Context, checkInstance func(ctx context.Context) error, getToken func(ctx context.Context) (*edge.Token, error)) *wait.AsyncActionHandler[edge.Token] {
handler := wait.New(func() (waitFinished bool, response *edge.Token, err error) {
err = checkInstance(ctx)
if err != nil {
return false, nil, err
}
token, err := getToken(ctx)
var oapiErr *oapierror.GenericOpenAPIError
if err != nil {
if errors.As(err, &oapiErr) && oapiErr.StatusCode == http.StatusNotFound {
return false, nil, nil
waitConfig := wait.WaiterHelper[edge.Token, string]{
FetchInstance: func() (*edge.Token, error) {
err := checkInstance(ctx)
if err != nil {
return nil, err
}
return false, nil, err
}
return true, token, nil
})
token, err := getToken(ctx)
if err != nil {
var apiErr *oapierror.GenericOpenAPIError
if ok := errors.As(err, &apiErr); ok && apiErr.StatusCode == http.StatusNotFound {
return nil, nil
}
}
return token, err
},
GetState: func(e *edge.Token) (string, error) {
if e == nil {
return "NOT_READY", nil
}
return "READY", nil
},
ActiveState: []string{"READY"},
}
handler := wait.New(waitConfig.Wait())
handler.SetTimeout(timeoutMinutes * time.Minute)
return handler
}
Expand Down
2 changes: 1 addition & 1 deletion services/edge/v1beta1api/wait/wait_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ var createOrUpdateInstanceTests = []struct {
desc: "failed creation",
shouldFail: false,
instanceStatus: INSTANCESTATUS_ERROR,
wantErr: errors.New("instance creation failed"),
wantErr: errors.New("state is error"),
},
{
desc: "API fails",
Expand Down
141 changes: 73 additions & 68 deletions services/edge/wait/wait.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ const timeoutMinutes time.Duration = 10
var (
ErrInstanceNotFound = errors.New("instance not found")
ErrInstanceStatusUndefined = errors.New("instance status undefined")
ErrInstanceCreationFailed = errors.New("instance creation failed")
ErrInstanceIsBeingDeleted = errors.New("instance is being deleted")
// Deprecated: ErrInstanceCreationFailed is no longer used and will be removed after 2026-11-07
ErrInstanceCreationFailed = errors.New("instance creation failed")
// Deprecated: ErrInstanceIsBeingDeleted is no longer used and will be removed after 2026-11-07
ErrInstanceIsBeingDeleted = errors.New("instance is being deleted")
)

// EdgeCloudApiClient is the interface for Edge Cloud API calls which require a waiter.
Expand All @@ -33,33 +35,20 @@ type EdgeCloudApiClient interface {

// createOrUpdateInstanceWaitHandler contains the shared logic for waiting on instance creation or updates.
func createOrUpdateInstanceWaitHandler(ctx context.Context, getInstance func(ctx context.Context) (*edge.Instance, error)) *wait.AsyncActionHandler[edge.Instance] {
handler := wait.New(func() (waitFinished bool, response *edge.Instance, err error) {
instance, err := getInstance(ctx)
if err != nil {
return false, nil, err
}

if instance == nil || instance.Status == nil {
return false, nil, ErrInstanceNotFound
}
if instance == nil || instance.Status == nil {
return false, nil, ErrInstanceStatusUndefined
}

status := *instance.Status
switch status {
case edge.INSTANCESTATUS_ACTIVE:
return true, instance, nil
case edge.INSTANCESTATUS_ERROR:
return true, instance, ErrInstanceCreationFailed
case edge.INSTANCESTATUS_RECONCILING:
return false, nil, nil
case edge.INSTANCESTATUS_DELETING:
return true, instance, ErrInstanceIsBeingDeleted
default:
return false, nil, nil
}
})
waitConfig := wait.WaiterHelper[edge.Instance, edge.InstanceStatus]{
FetchInstance: func() (*edge.Instance, error) {
return getInstance(ctx)
},
GetState: func(instance *edge.Instance) (edge.InstanceStatus, error) {
if instance == nil || instance.Status == nil {
return "", ErrInstanceNotFound
}
return *instance.Status, nil
},
ActiveState: []edge.InstanceStatus{edge.INSTANCESTATUS_ACTIVE},
ErrorState: []edge.InstanceStatus{edge.INSTANCESTATUS_ERROR, edge.INSTANCESTATUS_DELETING},
}
handler := wait.New(waitConfig.Wait())
handler.SetTimeout(timeoutMinutes * time.Minute)
return handler
}
Expand All @@ -80,17 +69,15 @@ func CreateOrUpdateInstanceByNameWaitHandler(ctx context.Context, a EdgeCloudApi

// deleteInstanceWaitHandler contains the shared logic for waiting on instance deletion.
func deleteInstanceWaitHandler(ctx context.Context, getInstance func(ctx context.Context) (*edge.Instance, error)) *wait.AsyncActionHandler[edge.Instance] {
handler := wait.New(func() (waitFinished bool, response *edge.Instance, err error) {
_, err = getInstance(ctx)
if err == nil {
return false, nil, nil
}
var oapiErr *oapierror.GenericOpenAPIError
if errors.As(err, &oapiErr) && oapiErr.StatusCode == http.StatusNotFound {
return true, nil, nil
}
return false, nil, err
})
waitConfig := wait.WaiterHelper[edge.Instance, edge.InstanceStatus]{
FetchInstance: func() (*edge.Instance, error) {
return getInstance(ctx)
},
GetState: func(_ *edge.Instance) (edge.InstanceStatus, error) {
return "", nil
},
}
handler := wait.New(waitConfig.Wait())
handler.SetTimeout(timeoutMinutes * time.Minute)
return handler
}
Expand All @@ -111,21 +98,30 @@ func DeleteInstanceByNameWaitHandler(ctx context.Context, a EdgeCloudApiClient,

// kubeconfigWaitHandlerHelper contains the shared logic for waiting for the instance to become ready before retrieving the kubeconfig.
func kubeconfigWaitHandlerHelper(ctx context.Context, checkInstance func(ctx context.Context) error, getKubeconfig func(ctx context.Context) (*edge.Kubeconfig, error)) *wait.AsyncActionHandler[edge.Kubeconfig] {
handler := wait.New(func() (waitFinished bool, response *edge.Kubeconfig, err error) {
err = checkInstance(ctx)
if err != nil {
return false, nil, err
}
kubeconfig, err := getKubeconfig(ctx)
var oapiErr *oapierror.GenericOpenAPIError
if err != nil {
if errors.As(err, &oapiErr) && oapiErr.StatusCode == http.StatusNotFound {
return false, nil, nil
waitConfig := wait.WaiterHelper[edge.Kubeconfig, string]{
FetchInstance: func() (*edge.Kubeconfig, error) {
err := checkInstance(ctx)
if err != nil {
return nil, err
}
return false, nil, err
}
return true, kubeconfig, nil
})
config, err := getKubeconfig(ctx)
if err != nil {
var apiErr *oapierror.GenericOpenAPIError
if ok := errors.As(err, &apiErr); ok && apiErr.StatusCode == http.StatusNotFound {
return nil, nil
}
}
return config, err
},
GetState: func(k *edge.Kubeconfig) (string, error) {
if k == nil {
return "NOT_READY", nil
}
return "READY", nil
},
ActiveState: []string{"READY"},
}
handler := wait.New(waitConfig.Wait())
handler.SetTimeout(timeoutMinutes * time.Minute)
return handler
}
Expand Down Expand Up @@ -164,21 +160,30 @@ func KubeconfigByInstanceNameWaitHandler(ctx context.Context, a EdgeCloudApiClie

// tokenWaitHandlerHelper contains the shared logic for waiting for the instance to become ready before retrieving the service token.
func tokenWaitHandlerHelper(ctx context.Context, checkInstance func(ctx context.Context) error, getToken func(ctx context.Context) (*edge.Token, error)) *wait.AsyncActionHandler[edge.Token] {
handler := wait.New(func() (waitFinished bool, response *edge.Token, err error) {
err = checkInstance(ctx)
if err != nil {
return false, nil, err
}
token, err := getToken(ctx)
var oapiErr *oapierror.GenericOpenAPIError
if err != nil {
if errors.As(err, &oapiErr) && oapiErr.StatusCode == http.StatusNotFound {
return false, nil, nil
waitConfig := wait.WaiterHelper[edge.Token, string]{
FetchInstance: func() (*edge.Token, error) {
err := checkInstance(ctx)
if err != nil {
return nil, err
}
return false, nil, err
}
return true, token, nil
})
token, err := getToken(ctx)
if err != nil {
var apiErr *oapierror.GenericOpenAPIError
if ok := errors.As(err, &apiErr); ok && apiErr.StatusCode == http.StatusNotFound {
return nil, nil
}
}
return token, err
},
GetState: func(t *edge.Token) (string, error) {
if t == nil {
return "NOT_READY", nil
}
return "READY", nil
},
ActiveState: []string{"READY"},
}
handler := wait.New(waitConfig.Wait())
handler.SetTimeout(timeoutMinutes * time.Minute)
return handler
}
Expand Down
2 changes: 1 addition & 1 deletion services/edge/wait/wait_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ var createOrUpdateInstanceTests = []struct {
desc: "failed creation",
shouldFail: false,
instanceStatus: edge.INSTANCESTATUS_ERROR,
wantErr: errors.New("instance creation failed"),
wantErr: errors.New("state is error"),
},
{
desc: "API fails",
Expand Down
Loading