diff --git a/stackit/internal/services/sfs/export-policy/resource.go b/stackit/internal/services/sfs/export-policy/resource.go index e9095a2ef..03eee4d6c 100644 --- a/stackit/internal/services/sfs/export-policy/resource.go +++ b/stackit/internal/services/sfs/export-policy/resource.go @@ -276,7 +276,7 @@ func (r *exportPolicyResource) Create(ctx context.Context, req resource.CreateRe createResp, err := r.client.CreateShareExportPolicy(ctx, projectId, region).CreateShareExportPolicyPayload(*payload).Execute() if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating export policy", fmt.Sprintf("Calling API: %v", err)) + sfsUtils.LogAndAddError(ctx, &resp.Diagnostics, "Error creating export policy", "Calling API", err) return } @@ -402,7 +402,7 @@ func (r *exportPolicyResource) Update(ctx context.Context, req resource.UpdateRe _, err = r.client.UpdateShareExportPolicy(ctx, projectId, region, exportPolicyId).UpdateShareExportPolicyPayload(*payload).Execute() if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating export policy", fmt.Sprintf("Calling API to update export policy: %v", err)) + sfsUtils.LogAndAddError(ctx, &resp.Diagnostics, "Error updating export policy", "Calling API to update export policy", err) return } diff --git a/stackit/internal/services/sfs/resourcepool/resource.go b/stackit/internal/services/sfs/resourcepool/resource.go index a1fda38fb..47b91e0b7 100644 --- a/stackit/internal/services/sfs/resourcepool/resource.go +++ b/stackit/internal/services/sfs/resourcepool/resource.go @@ -232,7 +232,7 @@ func (r *resourcePoolResource) Create(ctx context.Context, req resource.CreateRe CreateResourcePoolPayload(*payload). Execute() if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating resource pool", fmt.Sprintf("Calling API: %v", err)) + sfsUtils.LogAndAddError(ctx, &resp.Diagnostics, "Error creating resource pool", "Calling API", err) return } @@ -380,7 +380,7 @@ func (r *resourcePoolResource) Update(ctx context.Context, req resource.UpdateRe return } } - core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating resource pool", fmt.Sprintf("Calling API: %v", err)) + sfsUtils.LogAndAddError(ctx, &resp.Diagnostics, "Error updating resource pool", "Calling API", err) return } diff --git a/stackit/internal/services/sfs/sfs_test.go b/stackit/internal/services/sfs/sfs_test.go index a9af2fccf..833ec57c9 100644 --- a/stackit/internal/services/sfs/sfs_test.go +++ b/stackit/internal/services/sfs/sfs_test.go @@ -162,3 +162,48 @@ resource "stackit_sfs_share" "example" { }, }) } + +func TestLogAndErrorPrintsValidationMessage(t *testing.T) { + projectId := uuid.NewString() + const region = "eu01" + s := testutil.NewMockServer(t) + defer s.Server.Close() + tfConfig := fmt.Sprintf(` +provider "stackit" { + default_region = "%s" + sfs_custom_endpoint = "%s" + service_account_token = "mock-server-needs-no-auth" + enable_beta_resources = true +} +resource "stackit_sfs_resource_pool" "resourcepool" { + project_id = "%s" + name = "sfs-instance" + availability_zone = "eu01-m" + performance_class = "Standard" + size_gigabytes = 512 + ip_acl = ["192.168.2.0/24"] +} +`, region, s.Server.URL, projectId) + s.Reset(testutil.MockResponse{ + StatusCode: http.StatusBadRequest, + ToJsonBody: sfs.ValidationError{ + Type: utils.Ptr("storage.stackit.cloud/validation-error"), + Title: utils.Ptr("Validation Failed"), + Fields: &[]sfs.ValidationErrorField{ + { + Field: utils.Ptr("ip"), + Reason: utils.Ptr("999.999.999 is not a valid ip address"), + }, + }, + }, + }) + resource.UnitTest(t, resource.TestCase{ + ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: tfConfig, + ExpectError: regexp.MustCompile(".*Calling API: Validation Failed\n\nField: ip \\| Reason: 999.999.999 is not a valid ip address"), + }, + }, + }) +} diff --git a/stackit/internal/services/sfs/share/resource.go b/stackit/internal/services/sfs/share/resource.go index 50d17e916..7ecb47dd8 100644 --- a/stackit/internal/services/sfs/share/resource.go +++ b/stackit/internal/services/sfs/share/resource.go @@ -230,7 +230,7 @@ func (r *shareResource) Create(ctx context.Context, req resource.CreateRequest, CreateSharePayload(payload). Execute() if err != nil { - core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating share", fmt.Sprintf("Calling API: %v", err)) + sfsUtils.LogAndAddError(ctx, &resp.Diagnostics, "Error creating share", "Calling API", err) return } @@ -382,7 +382,7 @@ func (r *shareResource) Update(ctx context.Context, req resource.UpdateRequest, return } } - core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating share", fmt.Sprintf("Calling API: %v", err)) + sfsUtils.LogAndAddError(ctx, &resp.Diagnostics, "Error updating share", "Calling API", err) return } diff --git a/stackit/internal/services/sfs/utils/util.go b/stackit/internal/services/sfs/utils/util.go index 2f3f8d023..cc35f0a23 100644 --- a/stackit/internal/services/sfs/utils/util.go +++ b/stackit/internal/services/sfs/utils/util.go @@ -2,8 +2,11 @@ package utils import ( "context" + "errors" "fmt" + "strings" + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" "github.com/stackitcloud/stackit-sdk-go/services/sfs" "github.com/hashicorp/terraform-plugin-framework/diag" @@ -28,3 +31,40 @@ func ConfigureClient(ctx context.Context, providerData *core.ProviderData, diags return apiClient } + +func DescribeValidationError(err sfs.ValidationError) string { + var sb strings.Builder + if err.Title != nil { + sb.WriteString(*err.Title) + sb.WriteRune('\n') + } + if fields := err.Fields; fields != nil { + for _, field := range *fields { + sb.WriteRune('\n') + sb.WriteString("Field: ") + if field.Field != nil { + sb.WriteString(*field.Field) + } + sb.WriteString(" | Reason: ") + if field.Reason != nil { + sb.WriteString(*field.Reason) + } + } + } + return sb.String() +} + +func LogAndAddError(ctx context.Context, diags *diag.Diagnostics, summary, detail string, err error) { + if err == nil { + return + } + message := err.Error() + var oapiErr *oapierror.GenericOpenAPIError + if errors.As(err, &oapiErr) { + errModel := oapiErr.Model + if validationErr, ok := errModel.(sfs.ValidationError); ok { + message = DescribeValidationError(validationErr) + } + } + core.LogAndAddError(ctx, diags, summary, fmt.Sprintf("%s: %v", detail, message)) +} diff --git a/stackit/internal/services/sfs/utils/util_test.go b/stackit/internal/services/sfs/utils/util_test.go index f6f88537c..25a81a38b 100644 --- a/stackit/internal/services/sfs/utils/util_test.go +++ b/stackit/internal/services/sfs/utils/util_test.go @@ -6,9 +6,11 @@ import ( "reflect" "testing" + "github.com/google/go-cmp/cmp" "github.com/hashicorp/terraform-plugin-framework/diag" sdkClients "github.com/stackitcloud/stackit-sdk-go/core/clients" "github.com/stackitcloud/stackit-sdk-go/core/config" + utils2 "github.com/stackitcloud/stackit-sdk-go/core/utils" "github.com/stackitcloud/stackit-sdk-go/services/sfs" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" @@ -91,3 +93,50 @@ func TestConfigureClient(t *testing.T) { }) } } + +func TestDescribeValidationError(t *testing.T) { + tests := []struct { + name string + err sfs.ValidationError + want string + }{ + { + name: "just title", + err: sfs.ValidationError{ + Title: utils2.Ptr("nice title"), + }, + want: `nice title +`, + }, + { + name: "with fields", + err: sfs.ValidationError{ + Title: utils2.Ptr("nice title"), + Fields: &[]sfs.ValidationErrorField{ + { + Field: utils2.Ptr("field-a"), + Reason: utils2.Ptr("reason-a"), + }, + { + Reason: utils2.Ptr("reason-b"), + }, + { + Field: utils2.Ptr("field-c"), + }, + }, + }, + want: `nice title + +Field: field-a | Reason: reason-a +Field: | Reason: reason-b +Field: field-c | Reason: `, + }, + } + + for _, tt := range tests { + got := DescribeValidationError(tt.err) + if d := cmp.Diff(got, tt.want); d != "" { + t.Errorf("DescribeValidationError() = got diff: %s", d) + } + } +} diff --git a/stackit/internal/testutil/mockserver.go b/stackit/internal/testutil/mockserver.go index 478adafb4..9ab2604e9 100644 --- a/stackit/internal/testutil/mockserver.go +++ b/stackit/internal/testutil/mockserver.go @@ -53,18 +53,19 @@ func (m *MockServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { next.Handler(w, r) return } + status := next.StatusCode + if status == 0 { + status = http.StatusOK + } if next.ToJsonBody != nil { bs, err := json.Marshal(next.ToJsonBody) if err != nil { m.t.Fatalf("Error marshaling response body: %v", err) } w.Header().Set("content-type", "application/json") + w.WriteHeader(status) w.Write(bs) //nolint:errcheck //test will fail when this happens } - status := next.StatusCode - if status == 0 { - status = http.StatusOK - } w.WriteHeader(status) }