From f4498e85f312e2ed5ce6a0d913619812a1b2c4f7 Mon Sep 17 00:00:00 2001 From: Mauritz Uphoff <39736813+h3adex@users.noreply.github.com> Date: Wed, 7 May 2025 11:54:54 +0200 Subject: [PATCH] fix(alertgroups): add expression validator to detect new lines on expressions (#807) --- .../observability/alertgroup/resource.go | 4 ++ .../observability/log-alertgroup/resource.go | 4 ++ .../internal/services/ske/cluster/resource.go | 2 +- stackit/internal/validate/validate.go | 31 ++++++++ stackit/internal/validate/validate_test.go | 71 +++++++++++++++++++ 5 files changed, 111 insertions(+), 1 deletion(-) diff --git a/stackit/internal/services/observability/alertgroup/resource.go b/stackit/internal/services/observability/alertgroup/resource.go index ca47816b..e3e68de6 100644 --- a/stackit/internal/services/observability/alertgroup/resource.go +++ b/stackit/internal/services/observability/alertgroup/resource.go @@ -207,6 +207,10 @@ func (a *alertGroupResource) Schema(_ context.Context, _ resource.SchemaRequest, Required: true, Validators: []validator.String{ stringvalidator.LengthBetween(1, 600), + // The API currently accepts expressions with trailing newlines but does not return them, + // leading to inconsistent Terraform results. This issue has been reported to the Obs team. + // Until it is resolved, we proactively notify users if their input contains a trailing newline. + validate.ValidNoTrailingNewline(), }, }, "for": schema.StringAttribute{ diff --git a/stackit/internal/services/observability/log-alertgroup/resource.go b/stackit/internal/services/observability/log-alertgroup/resource.go index 330ec1fe..ea9ca27a 100644 --- a/stackit/internal/services/observability/log-alertgroup/resource.go +++ b/stackit/internal/services/observability/log-alertgroup/resource.go @@ -207,6 +207,10 @@ func (l *logAlertGroupResource) Schema(_ context.Context, _ resource.SchemaReque Required: true, Validators: []validator.String{ stringvalidator.LengthBetween(1, 600), + // The API currently accepts expressions with trailing newlines but does not return them, + // leading to inconsistent Terraform results. This issue has been reported to the Obs team. + // Until it is resolved, we proactively notify users if their input contains a trailing newline. + validate.ValidNoTrailingNewline(), }, }, "for": schema.StringAttribute{ diff --git a/stackit/internal/services/ske/cluster/resource.go b/stackit/internal/services/ske/cluster/resource.go index 5ccf31f3..d481727b 100644 --- a/stackit/internal/services/ske/cluster/resource.go +++ b/stackit/internal/services/ske/cluster/resource.go @@ -997,7 +997,7 @@ func toNodepoolsPayload(ctx context.Context, m *Model, availableMachineVersions if v.IsNull() || v.IsUnknown() { continue } - s, err := conversion.ToString(context.TODO(), v) + s, err := conversion.ToString(ctx, v) if err != nil { continue } diff --git a/stackit/internal/validate/validate.go b/stackit/internal/validate/validate.go index 57b14966..a8129fff 100644 --- a/stackit/internal/validate/validate.go +++ b/stackit/internal/validate/validate.go @@ -313,3 +313,34 @@ func ValidDurationString() *Validator { }, } } + +// ValidNoTrailingNewline returns a Validator that checks if the input string has no trailing newline +// character ("\n" or "\r\n"). If a trailing newline is present, a diagnostic error will be appended. +func ValidNoTrailingNewline() *Validator { + description := `The value must not have a trailing newline character ("\n" or "\r\n"). You can remove a trailing newline by using Terraform's built-in chomp() function.` + + return &Validator{ + description: description, + validate: func(_ context.Context, req validator.StringRequest, resp *validator.StringResponse) { + val := req.ConfigValue.ValueString() + if val == "" { + return + } + if len(val) >= 2 && val[len(val)-2:] == "\r\n" { + resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + req.Path, + description, + val, + )) + return + } + if val[len(val)-1] == '\n' { + resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + req.Path, + description, + val, + )) + } + }, + } +} diff --git a/stackit/internal/validate/validate_test.go b/stackit/internal/validate/validate_test.go index 31618006..f3120fae 100644 --- a/stackit/internal/validate/validate_test.go +++ b/stackit/internal/validate/validate_test.go @@ -845,3 +845,74 @@ func TestValidTtlDuration(t *testing.T) { }) } } + +func TestValidNoTrailingNewline(t *testing.T) { + tests := []struct { + description string + input string + isValid bool + }{ + { + "string with no trailing newline", + "abc", + true, + }, + { + "string with trailing \\n", + "abc\n", + false, + }, + { + "string with trailing \\r\\n", + "abc\r\n", + false, + }, + { + "string with internal newlines but not trailing", + "abc\ndef\nghi", + true, + }, + { + "empty string", + "", + true, + }, + { + "string that is just \\n", + "\n", + false, + }, + { + "string that is just \\r\\n", + "\r\n", + false, + }, + { + "string with multiple newlines, trailing", + "abc\n\n", + false, + }, + { + "string with newlines but ends with character", + "abc\ndef\n", + false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + r := validator.StringResponse{} + va := ValidNoTrailingNewline() + va.ValidateString(context.Background(), validator.StringRequest{ + ConfigValue: types.StringValue(tt.input), + }, &r) + + if !tt.isValid && !r.Diagnostics.HasError() { + t.Fatalf("Expected validation to fail for input: %q", tt.input) + } + if tt.isValid && r.Diagnostics.HasError() { + t.Fatalf("Expected validation to succeed for input: %q, but got errors: %v", tt.input, r.Diagnostics.Errors()) + } + }) + } +}