diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b14df339..03377fba3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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` diff --git a/services/edge/CHANGELOG.md b/services/edge/CHANGELOG.md index eb3f229bf..801ed5f91 100644 --- a/services/edge/CHANGELOG.md +++ b/services/edge/CHANGELOG.md @@ -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 diff --git a/services/edge/VERSION b/services/edge/VERSION index bf057dbfd..fd2726c91 100644 --- a/services/edge/VERSION +++ b/services/edge/VERSION @@ -1 +1 @@ -v0.10.0 +v0.11.0 diff --git a/services/edge/v1beta1api/wait/wait.go b/services/edge/v1beta1api/wait/wait.go index 3b9abae07..b8b7686b1 100644 --- a/services/edge/v1beta1api/wait/wait.go +++ b/services/edge/v1beta1api/wait/wait.go @@ -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 } @@ -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 } @@ -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 } @@ -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 } diff --git a/services/edge/v1beta1api/wait/wait_test.go b/services/edge/v1beta1api/wait/wait_test.go index a69f5d9c1..18c2efb8d 100644 --- a/services/edge/v1beta1api/wait/wait_test.go +++ b/services/edge/v1beta1api/wait/wait_test.go @@ -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", diff --git a/services/edge/wait/wait.go b/services/edge/wait/wait.go index a16a3cd9d..8a8222633 100644 --- a/services/edge/wait/wait.go +++ b/services/edge/wait/wait.go @@ -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. @@ -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 } @@ -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 } @@ -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 } @@ -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 } diff --git a/services/edge/wait/wait_test.go b/services/edge/wait/wait_test.go index 23f7a9989..a79b971dc 100644 --- a/services/edge/wait/wait_test.go +++ b/services/edge/wait/wait_test.go @@ -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",