diff --git a/CHANGELOG.md b/CHANGELOG.md index 2675c44bf..139211ac7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,6 @@ -## Release (2025-XX-XX) +## Release (2025-xx-xx) +- `scf`: [v0.2.1](services/scf/CHANGELOG.md#v021) + - **Feature:** Add waiter for deletion of organization - `iaas`: [v0.29.1](services/iaas/CHANGELOG.md#v0291) - **Bugfix:** Parsing oneOf with enum and string value diff --git a/services/scf/CHANGELOG.md b/services/scf/CHANGELOG.md index e4ee50462..695689a27 100644 --- a/services/scf/CHANGELOG.md +++ b/services/scf/CHANGELOG.md @@ -1,3 +1,6 @@ +## v0.2.1 +- **Feature:** Add waiter for deletion of organization + ## v0.2.0 - **Feature:** Add field `OrgId` in model `OrgManager` - **Feature:** Add new model `OrganizationCreateBffResponse` and `SpaceCreatedBffResponse` diff --git a/services/scf/VERSION b/services/scf/VERSION index 81fd7ba08..eac0a1441 100644 --- a/services/scf/VERSION +++ b/services/scf/VERSION @@ -1 +1 @@ -v0.2.0 \ No newline at end of file +v0.2.1 \ No newline at end of file diff --git a/services/scf/wait/wait.go b/services/scf/wait/wait.go new file mode 100644 index 000000000..3d0f7baaf --- /dev/null +++ b/services/scf/wait/wait.go @@ -0,0 +1,44 @@ +package wait + +import ( + "context" + "errors" + "fmt" + "net/http" + "time" + + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + "github.com/stackitcloud/stackit-sdk-go/core/wait" + "github.com/stackitcloud/stackit-sdk-go/services/scf" +) + +const statusDeletingFailed = "deleting_failed" + +// Interfaces needed for tests +type APIClientInterface interface { + GetOrganizationExecute(ctx context.Context, projectId, region, orgId string) (*scf.Organization, error) +} + +// DeleteOrganizationWaitHandler will wait for Organization deletion +func DeleteOrganizationWaitHandler(ctx context.Context, a APIClientInterface, projectId, region, orgId string) *wait.AsyncActionHandler[scf.Organization] { + handler := wait.New(func() (waitFinished bool, response *scf.Organization, err error) { + s, err := a.GetOrganizationExecute(ctx, projectId, region, orgId) + if err != nil { + var oapiErr *oapierror.GenericOpenAPIError + ok := errors.As(err, &oapiErr) + if ok && oapiErr.StatusCode == http.StatusNotFound { + return true, s, nil + } + return false, s, err + } + if s == nil { + return false, nil, errors.New("organization is nil") + } + if *s.Status == statusDeletingFailed { + return true, nil, fmt.Errorf("delete failed for Organization with id %s", orgId) + } + return false, s, nil + }) + handler.SetTimeout(20 * time.Minute) + return handler +} diff --git a/services/scf/wait/wait_test.go b/services/scf/wait/wait_test.go new file mode 100644 index 000000000..072d4fa6c --- /dev/null +++ b/services/scf/wait/wait_test.go @@ -0,0 +1,99 @@ +package wait + +import ( + "context" + "testing" + "time" + + "github.com/google/uuid" + + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + "github.com/stackitcloud/stackit-sdk-go/services/scf" +) + +var PROJECT_ID = uuid.New().String() +var INSTANCE_ID = uuid.New().String() + +const REGION = "eu01" + +type apiClientMocked struct { + getFails bool + errorCode int + returnInstance bool + projectId string + instanceId string + getSCFResponse *scf.Organization +} + +func (a *apiClientMocked) GetOrganizationExecute(_ context.Context, _, _, _ string) (*scf.Organization, error) { + if a.getFails { + return nil, &oapierror.GenericOpenAPIError{ + StatusCode: a.errorCode, + } + } + if !a.returnInstance { + return nil, nil + } + return a.getSCFResponse, nil +} + +func TestDeleteOrganizationWaitHandler(t *testing.T) { + statusDeletingFailed := "deleting_failed" + tests := []struct { + desc string + wantErr bool + wantReturnedInstance bool + getFails bool + errorCode int + returnInstance bool + getOrgResponse *scf.Organization + }{ + { + desc: "Instance deletion failed with error", + wantErr: true, + getFails: true, + }, + { + desc: "Instance is not found", + wantErr: false, + getFails: true, + errorCode: 404, + }, + { + desc: "Instance is in error state", + wantErr: true, + returnInstance: true, + getOrgResponse: &scf.Organization{ + Status: &statusDeletingFailed, + }, + }, + { + desc: "Instance is nil", + wantErr: true, + returnInstance: true, + getOrgResponse: nil, + }, + } + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + apiClient := &apiClientMocked{ + projectId: PROJECT_ID, + instanceId: INSTANCE_ID, + getFails: tt.getFails, + errorCode: tt.errorCode, + returnInstance: tt.returnInstance, + getSCFResponse: tt.getOrgResponse, + } + + handler := DeleteOrganizationWaitHandler(context.Background(), apiClient, apiClient.projectId, REGION, apiClient.instanceId) + response, 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 (response != nil) != tt.wantReturnedInstance { + t.Fatalf("handler gotRes = %v, want nil", response) + } + }) + } +}