From 018dfe874618d933c18500d73072bc5af3ac1b18 Mon Sep 17 00:00:00 2001 From: "Alex_Mihail.Tiugan" Date: Wed, 19 Aug 2026 16:46:31 +0300 Subject: [PATCH 1/2] - added ufw to go.work file - added ufw examples - added ufw waiters and unit tests --- examples/ufw/go.mod | 15 ++ examples/ufw/ufw.go | 240 +++++++++++++++++++++++++ go.work | 1 + services/ufw/v1api/wait/wait.go | 85 +++++++++ services/ufw/v1api/wait/wait_test.go | 255 +++++++++++++++++++++++++++ 5 files changed, 596 insertions(+) create mode 100644 examples/ufw/go.mod create mode 100644 examples/ufw/ufw.go create mode 100644 services/ufw/v1api/wait/wait.go create mode 100644 services/ufw/v1api/wait/wait_test.go diff --git a/examples/ufw/go.mod b/examples/ufw/go.mod new file mode 100644 index 000000000..ddad5cc82 --- /dev/null +++ b/examples/ufw/go.mod @@ -0,0 +1,15 @@ +module github.com/stackitcloud/stackit-sdk-go/examples/ufw + +go 1.25.9 + +replace github.com/stackitcloud/stackit-sdk-go/services/ufw => ../../services/ufw + +require ( + github.com/stackitcloud/stackit-sdk-go/core v0.26.0 + github.com/stackitcloud/stackit-sdk-go/services/ufw v0.0.0-00010101000000-000000000000 +) + +require ( + github.com/golang-jwt/jwt/v5 v5.3.1 // indirect + github.com/google/uuid v1.6.0 // indirect +) diff --git a/examples/ufw/ufw.go b/examples/ufw/ufw.go new file mode 100644 index 000000000..3602d4584 --- /dev/null +++ b/examples/ufw/ufw.go @@ -0,0 +1,240 @@ +package main + +import ( + "context" + "fmt" + "os" + "reflect" + "strings" + + "github.com/stackitcloud/stackit-sdk-go/core/config" + ufw "github.com/stackitcloud/stackit-sdk-go/services/ufw/v1api" + "github.com/stackitcloud/stackit-sdk-go/services/ufw/v1api/wait" +) + +func main() { + region := "eu01" // Region where the resources will be created + projectId := "PROJECT_ID" // UUID of your STACKIT project + instanceId := "INSTANCE_ID" // UUID of the instance to which the firewall rule will be attached + productType := "PRODUCT_TYPE" // Type of the instance to which the firewall rule will be attached (e.g. "redis", but you can get them from provider-options route) + ufwRuleType := "ACL" // Type of the rule that you want to create (ACL, SecurityRule, PublicIp, but you can get them from provider-options route + + ctx := context.Background() + + token := "" + ufwClient, err := ufw.NewAPIClient(config.WithToken(token)) + if err != nil { + fmt.Fprintf(os.Stderr, "[UFW] Creating API client: %v\n", err) + os.Exit(1) + } + + // Create a new firewall rule + description := "Created from SDK" + rulePayloadToCreate := ufw.CreateRulePayload{ + InstanceId: instanceId, + Product: productType, + SourceIP: "11.11.11.11/32", + Type: ufwRuleType, + Description: &description, + } + + createdRuleResponse, err := createFirewallRule(ctx, ufwClient, projectId, region, rulePayloadToCreate) + + if err != nil { + fmt.Fprintf(os.Stderr, "[UFW] Error when creating firewall rule: %v\n", err) + return + } + + fmt.Printf("Created firewall rule response: %+v\n", createdRuleResponse) + + // Get the firewall rule + testGetRule, err := getFirewallRule(ctx, ufwClient, projectId, region, *createdRuleResponse.RefId) + if err != nil { + fmt.Fprintf(os.Stderr, "[UFW] Error when getting firewall rule: %v\n", err) + return + } + + fmt.Printf("Firewall rule details: %+v\n", testGetRule) + + if err := verifyPayloadMatch(testGetRule, rulePayloadToCreate); err != nil { + fmt.Fprintf(os.Stderr, "[UFW] Verification failed after creation:\n%v\n", err) + return + } + fmt.Println("Created rule fields verified successfully.") + + // Update the firewall rule + rulePayloadToUpdate := ufw.UpdateRulePayload{ + SourceIP: "22.22.22.22/32", + } + + updatedRuleResponse, err := updateFirewallRule(ctx, ufwClient, projectId, region, *createdRuleResponse.RefId, rulePayloadToUpdate) + + if err != nil { + fmt.Fprintf(os.Stderr, "[UFW] Error when updating firewall rule: %v\n", err) + return + } + + fmt.Printf("Updated firewall rule details: %+v\n", updatedRuleResponse) + + testGetRule, err = getFirewallRule(ctx, ufwClient, projectId, region, *updatedRuleResponse.RefId) + if err != nil { + fmt.Fprintf(os.Stderr, "[UFW] Error when getting updated firewall rule: %v\n", err) + return + } + + if err := verifyPayloadMatch(testGetRule, rulePayloadToUpdate); err != nil { + fmt.Fprintf(os.Stderr, "[UFW] Verification failed after update:\n%v\n", err) + return + } + fmt.Println("Updated rule fields verified successfully.") + + // Delete the firewall rule + err = deleteFirewallRule(ctx, ufwClient, projectId, region, *updatedRuleResponse.RefId) + if err != nil { + fmt.Fprintf(os.Stderr, "[UFW] Error when deleting firewall rule: %v\n", err) + return + } + + testGetRule, err = getFirewallRule(ctx, ufwClient, projectId, region, *updatedRuleResponse.RefId) + if !strings.Contains(err.Error(), "404") { + fmt.Fprintf(os.Stderr, "[UFW] Error while verifying deleted rule: %v\n", err) + return + } + + // List all firewall rules + listUFWRules(ctx, ufwClient, projectId, region) + + fmt.Println("All firewall rules successfully tested and cleaned up.") +} + +func listUFWRules(ctx context.Context, ufwClient *ufw.APIClient, projectId, region string) { + listRulesResponse, err := ufwClient.DefaultAPI.ListRules(ctx, projectId, region).Execute() + if err != nil { + fmt.Fprintf(os.Stderr, "[UFW] Error when listing firewall rules: %v\n", err) + return + } + + fmt.Println("List of firewall rules:") + for _, rule := range listRulesResponse.Rules { + fmt.Printf("%+v\n", rule) + } +} + +func getFirewallRule(ctx context.Context, ufwClient *ufw.APIClient, projectId, region, ruleId string) (*ufw.RuleResponse, error) { + return ufwClient.DefaultAPI.GetRule(ctx, projectId, region, ruleId).Execute() +} + +func createFirewallRule(ctx context.Context, ufwClient *ufw.APIClient, projectId, region string, payload ufw.CreateRulePayload) (*ufw.SecurityRuleSuccessfullyCreatedResponse, error) { + createdFirewallRule, err := ufwClient.DefaultAPI.CreateRule(ctx, projectId, region).CreateRulePayload(payload).Execute() + if err != nil { + return nil, err + } + + createdFirewallRuleId := createdFirewallRule.RefId + fmt.Printf("Created firewall rule with ID: %s\n", *createdFirewallRuleId) + + _, err = wait.CreateRuleWaitHandler(ctx, ufwClient.DefaultAPI, projectId, region, *createdFirewallRuleId).WaitWithContext(ctx) + if err != nil { + return nil, err + } + + return createdFirewallRule, nil +} + +func updateFirewallRule(ctx context.Context, ufwClient *ufw.APIClient, projectId, region, ruleId string, payload ufw.UpdateRulePayload) (*ufw.SecurityRuleSuccessfullyCreatedResponse, error) { + updatedFirewallRule, err := ufwClient.DefaultAPI.UpdateRule(ctx, projectId, region, ruleId).UpdateRulePayload(payload).Execute() + if err != nil { + return nil, err + } + + fmt.Printf("Updated firewall rule with ID: %s\n", *updatedFirewallRule.RefId) + + _, err = wait.UpdateRuleWaitHandler(ctx, ufwClient.DefaultAPI, projectId, region, *updatedFirewallRule.RefId).WaitWithContext(ctx) + if err != nil { + return nil, err + } + + return updatedFirewallRule, nil +} + +func deleteFirewallRule(ctx context.Context, ufwClient *ufw.APIClient, projectId, region, ruleId string) error { + deleteRuleResponse, err := ufwClient.DefaultAPI.DeleteRule(ctx, projectId, region, ruleId).Execute() + if err != nil { + return err + } + + fmt.Printf("Deleted firewall rule with ID: %s\n", ruleId) + fmt.Printf("Deleted firewall rule response: %+v\n", deleteRuleResponse) + + _, err = wait.DeleteRuleWaitHandler(ctx, ufwClient.DefaultAPI, projectId, region, ruleId).WaitWithContext(ctx) + if err != nil { + return err + } + + return nil +} + +func verifyPayloadMatch(actual any, expected any) error { + var mismatches []string + + actualVal := reflect.ValueOf(actual) + if actualVal.Kind() == reflect.Pointer { + actualVal = actualVal.Elem() + } + + expectedVal := reflect.ValueOf(expected) + if expectedVal.Kind() == reflect.Pointer { + expectedVal = expectedVal.Elem() + } + + expectedType := expectedVal.Type() + + for i := 0; i < expectedVal.NumField(); i++ { + fieldName := expectedType.Field(i).Name + + if fieldName == "AdditionalProperties" { + continue // AdditionalProperties field is not part of the API response struct + } + + if fieldName == "Description" { + continue // Description field is overridden by the API, will be fixed in the future + } + + expectedField := expectedVal.Field(i) + + // Look for a matching field in the API response struct + actualField := actualVal.FieldByName(fieldName) + if !actualField.IsValid() { + continue // Field exists in payload but not in response struct, safe to skip + } + + // Dereference pointers cleanly to get string representations + expStr := formatReflectValue(expectedField) + actStr := formatReflectValue(actualField) + + // Skip uninitialized/nil fields in the expected payload + // (e.g. fields omitted from an UpdateRulePayload) + if expStr == "" || expStr == "" { + continue + } + + if expStr != actStr { + mismatches = append(mismatches, fmt.Sprintf("%s: expected %q, got %q", fieldName, expStr, actStr)) + } + } + + if len(mismatches) > 0 { + return fmt.Errorf("field mismatches found:\n- %s", strings.Join(mismatches, "\n- ")) + } + return nil +} + +func formatReflectValue(v reflect.Value) string { + if v.Kind() == reflect.Pointer { + if v.IsNil() { + return "" + } + v = v.Elem() + } + return fmt.Sprintf("%v", v.Interface()) +} diff --git a/go.work b/go.work index cd4c676f0..e779ff3fb 100644 --- a/go.work +++ b/go.work @@ -38,6 +38,7 @@ use ( ./examples/sqlserverflex ./examples/telemetrylink ./examples/telemetryrouter + ./examples/ufw ./examples/valkey ./examples/vpn ./examples/waiter diff --git a/services/ufw/v1api/wait/wait.go b/services/ufw/v1api/wait/wait.go new file mode 100644 index 000000000..7661c0d89 --- /dev/null +++ b/services/ufw/v1api/wait/wait.go @@ -0,0 +1,85 @@ +package wait + +import ( + "context" + "errors" + "net/http" + "time" + + "github.com/stackitcloud/stackit-sdk-go/core/wait" + ufw "github.com/stackitcloud/stackit-sdk-go/services/ufw/v1api" +) + +type RuleStatus string + +const ( + RuleStatusActive RuleStatus = "Active" + RuleStatusCreating RuleStatus = "Creating" + RuleStatusUpdating RuleStatus = "Updating" + RuleStatusPending RuleStatus = "Pending" + RuleStatusDeleting RuleStatus = "Deleting" + RuleStatusError RuleStatus = "Error" +) + +func CreateRuleWaitHandler(ctx context.Context, a ufw.DefaultAPI, projectId, region, ruleId string) *wait.AsyncActionHandler[ufw.RuleResponse] { + waitConfig := wait.WaiterHelper[ufw.RuleResponse, RuleStatus]{ + FetchInstance: a.GetRule(ctx, projectId, region, ruleId).Execute, + GetState: func(ruleResp *ufw.RuleResponse) (RuleStatus, error) { + if ruleResp == nil { + return "", errors.New("empty response") + } + if ruleResp.Status == nil { + return "", errors.New("status is missing") + } + return RuleStatus(*ruleResp.Status), nil + }, + ActiveState: []RuleStatus{RuleStatusActive}, + ErrorState: []RuleStatus{RuleStatusError}, + } + + handler := wait.New(waitConfig.Wait()) + handler.SetTimeout(5 * time.Minute) + return handler +} + +func UpdateRuleWaitHandler(ctx context.Context, a ufw.DefaultAPI, projectId, region, ruleId string) *wait.AsyncActionHandler[ufw.RuleResponse] { + waitConfig := wait.WaiterHelper[ufw.RuleResponse, RuleStatus]{ + FetchInstance: a.GetRule(ctx, projectId, region, ruleId).Execute, + GetState: func(ruleResp *ufw.RuleResponse) (RuleStatus, error) { + if ruleResp == nil { + return "", errors.New("empty response") + } + if ruleResp.Status == nil { + return "", errors.New("status is missing") + } + return RuleStatus(*ruleResp.Status), nil + }, + ActiveState: []RuleStatus{RuleStatusActive}, + ErrorState: []RuleStatus{RuleStatusError}, + } + + handler := wait.New(waitConfig.Wait()) + handler.SetTimeout(5 * time.Minute) + return handler +} + +func DeleteRuleWaitHandler(ctx context.Context, a ufw.DefaultAPI, projectId, region, ruleId string) *wait.AsyncActionHandler[ufw.RuleResponse] { + waitConfig := wait.WaiterHelper[ufw.RuleResponse, RuleStatus]{ + FetchInstance: a.GetRule(ctx, projectId, region, ruleId).Execute, + GetState: func(ruleResp *ufw.RuleResponse) (RuleStatus, error) { + if ruleResp == nil { + return "", errors.New("empty response") + } + if ruleResp.Status == nil { + return "", errors.New("status is missing") + } + return RuleStatus(*ruleResp.Status), nil + }, + ErrorState: []RuleStatus{RuleStatusError}, + DeleteHttpErrorStatusCodes: []int{http.StatusNotFound}, + } + + handler := wait.New(waitConfig.Wait()) + handler.SetTimeout(5 * time.Minute) + return handler +} diff --git a/services/ufw/v1api/wait/wait_test.go b/services/ufw/v1api/wait/wait_test.go new file mode 100644 index 000000000..ee8fee745 --- /dev/null +++ b/services/ufw/v1api/wait/wait_test.go @@ -0,0 +1,255 @@ +package wait + +import ( + "context" + "net/http" + "testing" + "testing/synctest" + + "github.com/google/go-cmp/cmp" + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" + "github.com/stackitcloud/stackit-sdk-go/core/utils" + ufw "github.com/stackitcloud/stackit-sdk-go/services/ufw/v1api" +) + +type mockSettings struct { + isDeleted bool + getFails bool + resourceState string +} + +func newAPIMock(settings mockSettings) ufw.DefaultAPI { + return &ufw.DefaultAPIServiceMock{ + GetRuleExecuteMock: utils.Ptr( + func(_ ufw.ApiGetRuleRequest) (*ufw.RuleResponse, error) { + if settings.isDeleted { + return nil, &oapierror.GenericOpenAPIError{ + StatusCode: http.StatusNotFound, + } + } + if settings.getFails { + return nil, &oapierror.GenericOpenAPIError{ + StatusCode: 500, + } + } + + return &ufw.RuleResponse{ + Status: utils.Ptr(settings.resourceState), + }, nil + }, + ), + } +} + +func TestCreateRuleWaitHandler(t *testing.T) { + tests := []struct { + description string + getFails bool + resourceState string + wantError bool + wantResponse bool + }{ + { + description: "create_succeeded", + getFails: false, + resourceState: string(RuleStatusActive), + wantError: false, + wantResponse: true, + }, + { + description: "create_failed", + getFails: false, + resourceState: string(RuleStatusError), + wantError: true, + wantResponse: true, + }, + { + description: "get_fails", + getFails: true, + resourceState: "", + wantError: true, + wantResponse: false, + }, + { + description: "timeout", + getFails: false, + resourceState: "ANOTHER STATE", + wantError: true, + wantResponse: false, + }, + } + for _, currentTest := range tests { + t.Run(currentTest.description, func(t *testing.T) { + synctest.Test(t, func(t *testing.T) { + apiClient := newAPIMock(mockSettings{ + getFails: currentTest.getFails, + resourceState: currentTest.resourceState, + isDeleted: false, + }) + + var expectedResponse *ufw.RuleResponse + if currentTest.wantResponse { + expectedResponse = &ufw.RuleResponse{ + Status: utils.Ptr(currentTest.resourceState), + } + } + + handler := CreateRuleWaitHandler(context.Background(), apiClient, "pid", "region", "ruleId") + + gotResponse, err := handler.WaitWithContext(context.Background()) + + if (err != nil) != currentTest.wantError { + t.Fatalf("handler error = %v, wantErr %v", err, currentTest.wantError) + } + if !cmp.Equal(gotResponse, expectedResponse) { + t.Fatalf("handler gotResponse = %v, expectedResponse = %v", gotResponse, expectedResponse) + } + }) + }) + } +} + +func TestUpdateRuleWaitHandler(t *testing.T) { + tests := []struct { + description string + getFails bool + resourceState string + wantError bool + wantResponse bool + }{ + { + description: "update_succeeded", + getFails: false, + resourceState: string(RuleStatusActive), + wantError: false, + wantResponse: true, + }, + { + description: "update_failed", + getFails: false, + resourceState: string(RuleStatusError), + wantError: true, + wantResponse: true, + }, + { + description: "get_fails", + getFails: true, + resourceState: "", + wantError: true, + wantResponse: false, + }, + { + description: "timeout", + getFails: false, + resourceState: "ANOTHER STATE", + wantError: true, + wantResponse: false, + }, + } + for _, currentTest := range tests { + t.Run(currentTest.description, func(t *testing.T) { + synctest.Test(t, func(t *testing.T) { + apiClient := newAPIMock(mockSettings{ + getFails: currentTest.getFails, + resourceState: currentTest.resourceState, + isDeleted: false, + }) + + var expectedResponse *ufw.RuleResponse + if currentTest.wantResponse { + expectedResponse = &ufw.RuleResponse{ + Status: utils.Ptr(currentTest.resourceState), + } + } + + handler := UpdateRuleWaitHandler(context.Background(), apiClient, "pid", "region", "ruleId") + + gotResponse, err := handler.WaitWithContext(context.Background()) + + if (err != nil) != currentTest.wantError { + t.Fatalf("handler error = %v, wantErr %v", err, currentTest.wantError) + } + if !cmp.Equal(gotResponse, expectedResponse) { + t.Fatalf("handler gotResponse = %v, expectedResponse = %v", gotResponse, expectedResponse) + } + }) + }) + } +} + +func TestDeleteRuleWaitHandler(t *testing.T) { + tests := []struct { + description string + isDeleted bool + getFails bool + resourceState string + wantError bool + wantResponse bool + }{ + { + description: "delete_succeeded", + isDeleted: true, + getFails: false, + resourceState: "", + wantError: false, + wantResponse: false, + }, + { + description: "delete_failed", + isDeleted: false, + getFails: false, + resourceState: string(RuleStatusError), + wantError: true, + wantResponse: true, + }, + { + description: "get_fails", + isDeleted: false, + getFails: true, + resourceState: "", + wantError: true, + wantResponse: false, + }, + { + description: "timeout", + isDeleted: false, + getFails: false, + resourceState: "ANOTHER STATE", + wantError: true, + wantResponse: false, + }, + } + for _, currentTest := range tests { + t.Run(currentTest.description, func(t *testing.T) { + synctest.Test(t, func(t *testing.T) { + apiClient := newAPIMock(mockSettings{ + getFails: currentTest.getFails, + resourceState: currentTest.resourceState, + isDeleted: currentTest.isDeleted, + }) + + var expectedResponse *ufw.RuleResponse + if currentTest.wantResponse { + expectedResponse = &ufw.RuleResponse{ + Status: utils.Ptr(currentTest.resourceState), + } + } + + handler := DeleteRuleWaitHandler(context.Background(), apiClient, "pid", "region", "ruleId") + + gotResponse, err := handler.WaitWithContext(context.Background()) + + if (err != nil) != currentTest.wantError { + t.Fatalf("handler error = %v, wantErr %v", err, currentTest.wantError) + } + + if !currentTest.wantResponse && gotResponse != nil { + t.Fatalf("handler gotResponse = %v, expectedResponse = %v", gotResponse, expectedResponse) + } + if currentTest.wantResponse && !cmp.Equal(gotResponse, expectedResponse) { + t.Fatalf("handler gotResponse = %v, expectedResponse = %v", gotResponse, expectedResponse) + } + }) + }) + } +} From 194052f6cf451c44a5ba2842b54f612b910e473a Mon Sep 17 00:00:00 2001 From: "Alex_Mihail.Tiugan" Date: Wed, 19 Aug 2026 17:14:45 +0300 Subject: [PATCH 2/2] - fixed linter --- examples/ufw/ufw.go | 14 +++++++------- services/ufw/go.mod | 5 ++++- services/ufw/v1api/wait/wait_test.go | 1 + 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/examples/ufw/ufw.go b/examples/ufw/ufw.go index 3602d4584..31e6e5d3d 100644 --- a/examples/ufw/ufw.go +++ b/examples/ufw/ufw.go @@ -38,7 +38,7 @@ func main() { Description: &description, } - createdRuleResponse, err := createFirewallRule(ctx, ufwClient, projectId, region, rulePayloadToCreate) + createdRuleResponse, err := createFirewallRule(ctx, ufwClient, projectId, region, &rulePayloadToCreate) if err != nil { fmt.Fprintf(os.Stderr, "[UFW] Error when creating firewall rule: %v\n", err) @@ -95,7 +95,7 @@ func main() { return } - testGetRule, err = getFirewallRule(ctx, ufwClient, projectId, region, *updatedRuleResponse.RefId) + _, err = getFirewallRule(ctx, ufwClient, projectId, region, *updatedRuleResponse.RefId) if !strings.Contains(err.Error(), "404") { fmt.Fprintf(os.Stderr, "[UFW] Error while verifying deleted rule: %v\n", err) return @@ -115,8 +115,8 @@ func listUFWRules(ctx context.Context, ufwClient *ufw.APIClient, projectId, regi } fmt.Println("List of firewall rules:") - for _, rule := range listRulesResponse.Rules { - fmt.Printf("%+v\n", rule) + for i := range listRulesResponse.Rules { + fmt.Printf("%+v\n", listRulesResponse.Rules[i]) } } @@ -124,8 +124,8 @@ func getFirewallRule(ctx context.Context, ufwClient *ufw.APIClient, projectId, r return ufwClient.DefaultAPI.GetRule(ctx, projectId, region, ruleId).Execute() } -func createFirewallRule(ctx context.Context, ufwClient *ufw.APIClient, projectId, region string, payload ufw.CreateRulePayload) (*ufw.SecurityRuleSuccessfullyCreatedResponse, error) { - createdFirewallRule, err := ufwClient.DefaultAPI.CreateRule(ctx, projectId, region).CreateRulePayload(payload).Execute() +func createFirewallRule(ctx context.Context, ufwClient *ufw.APIClient, projectId, region string, payload *ufw.CreateRulePayload) (*ufw.SecurityRuleSuccessfullyCreatedResponse, error) { + createdFirewallRule, err := ufwClient.DefaultAPI.CreateRule(ctx, projectId, region).CreateRulePayload(*payload).Execute() if err != nil { return nil, err } @@ -174,7 +174,7 @@ func deleteFirewallRule(ctx context.Context, ufwClient *ufw.APIClient, projectId return nil } -func verifyPayloadMatch(actual any, expected any) error { +func verifyPayloadMatch(actual, expected any) error { var mismatches []string actualVal := reflect.ValueOf(actual) diff --git a/services/ufw/go.mod b/services/ufw/go.mod index 1b5ed90f4..ff379233f 100644 --- a/services/ufw/go.mod +++ b/services/ufw/go.mod @@ -2,7 +2,10 @@ module github.com/stackitcloud/stackit-sdk-go/services/ufw go 1.25 -require github.com/stackitcloud/stackit-sdk-go/core v0.26.0 +require ( + github.com/google/go-cmp v0.7.0 + github.com/stackitcloud/stackit-sdk-go/core v0.26.0 +) require ( github.com/golang-jwt/jwt/v5 v5.3.1 // indirect diff --git a/services/ufw/v1api/wait/wait_test.go b/services/ufw/v1api/wait/wait_test.go index ee8fee745..70a4605d4 100644 --- a/services/ufw/v1api/wait/wait_test.go +++ b/services/ufw/v1api/wait/wait_test.go @@ -7,6 +7,7 @@ import ( "testing/synctest" "github.com/google/go-cmp/cmp" + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" "github.com/stackitcloud/stackit-sdk-go/core/utils" ufw "github.com/stackitcloud/stackit-sdk-go/services/ufw/v1api"