Skip to content

Commit 9d96427

Browse files
committed
Adds validation for slo_id
1 parent 641f7f6 commit 9d96427

File tree

3 files changed

+140
-2
lines changed

3 files changed

+140
-2
lines changed

docs/resources/kibana_slo.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ resource "elasticstack_kibana_slo" "timeslice_metric" {
248248
- `kql_custom_indicator` (Block List, Max: 1) (see [below for nested schema](#nestedblock--kql_custom_indicator))
249249
- `metric_custom_indicator` (Block List, Max: 1) (see [below for nested schema](#nestedblock--metric_custom_indicator))
250250
- `settings` (Block List, Max: 1) The default settings should be sufficient for most users, but if needed, these properties can be overwritten. (see [below for nested schema](#nestedblock--settings))
251-
- `slo_id` (String) An ID (8 and 36 characters). If omitted, a UUIDv1 will be generated server-side.
251+
- `slo_id` (String) An ID (8 to 48 characters) that contains only letters, numbers, hyphens, and underscores. If omitted, a UUIDv1 will be generated server-side.
252252
- `space_id` (String) An identifier for the space. If space_id is not provided, the default space is used.
253253
- `tags` (List of String) The tags for the SLO.
254254
- `timeslice_metric_indicator` (Block List, Max: 1) Defines a timeslice metric indicator for SLO. (see [below for nested schema](#nestedblock--timeslice_metric_indicator))

internal/kibana/slo.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package kibana
33
import (
44
"context"
55
"fmt"
6+
"regexp"
67

78
"github.com/elastic/terraform-provider-elasticstack/generated/slo"
89
"github.com/elastic/terraform-provider-elasticstack/internal/clients"
@@ -81,11 +82,15 @@ func getSchema() map[string]*schema.Schema {
8182

8283
return map[string]*schema.Schema{
8384
"slo_id": {
84-
Description: "An ID (8 and 36 characters). If omitted, a UUIDv1 will be generated server-side.",
85+
Description: "An ID (8 to 48 characters) that contains only letters, numbers, hyphens, and underscores. If omitted, a UUIDv1 will be generated server-side.",
8586
Type: schema.TypeString,
8687
Optional: true,
8788
Computed: true,
8889
ForceNew: true,
90+
ValidateFunc: validation.All(
91+
validation.StringLenBetween(8, 48),
92+
validation.StringMatch(regexp.MustCompile(`^[a-zA-Z0-9_-]+$`), "must contain only letters, numbers, hyphens, and underscores"),
93+
),
8994
},
9095
"name": {
9196
Description: "The name of the SLO.",

internal/kibana/slo_test.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,83 @@ func TestAccResourceSloErrors(t *testing.T) {
611611
})
612612
}
613613

614+
func TestAccResourceSloValidation(t *testing.T) {
615+
resource.Test(t, resource.TestCase{
616+
PreCheck: func() { acctest.PreCheck(t) },
617+
ProtoV6ProviderFactories: acctest.Providers,
618+
Steps: []resource.TestStep{
619+
{
620+
Config: getSLOConfigWithInvalidSloId("short", "sh", "apm_latency_indicator"),
621+
ExpectError: regexp.MustCompile(`expected length of slo_id to be in the range \(8 - 48\)`),
622+
},
623+
{
624+
Config: getSLOConfigWithInvalidSloId("toolongid", "this-id-is-way-too-long-and-exceeds-the-48-character-limit-for-slo-ids", "apm_latency_indicator"),
625+
ExpectError: regexp.MustCompile(`expected length of slo_id to be in the range \(8 - 48\)`),
626+
},
627+
{
628+
Config: getSLOConfigWithInvalidSloId("invalidchars", "invalid@id$", "apm_latency_indicator"),
629+
ExpectError: regexp.MustCompile(`must contain only letters, numbers, hyphens, and underscores`),
630+
},
631+
},
632+
})
633+
}
634+
635+
func TestSloIdValidation(t *testing.T) {
636+
resource := kibanaresource.ResourceSlo()
637+
sloIdSchema := resource.Schema["slo_id"]
638+
639+
// Test valid slo_id values
640+
validIds := []string{
641+
"valid_id", // 8 chars with underscore
642+
"valid-id", // 8 chars with hyphen
643+
"validId123", // 11 chars with mixed case and numbers
644+
"a1234567", // exactly 8 chars
645+
"this-is-a-very-long-but-valid-slo-id-12345678", // exactly 48 chars
646+
}
647+
648+
for _, id := range validIds {
649+
warnings, errors := sloIdSchema.ValidateFunc(id, "slo_id")
650+
if len(errors) > 0 {
651+
t.Errorf("Expected valid ID %q to pass validation, but got errors: %v", id, errors)
652+
}
653+
if len(warnings) > 0 {
654+
t.Errorf("Expected valid ID %q to have no warnings, but got: %v", id, warnings)
655+
}
656+
}
657+
658+
// Test invalid slo_id values
659+
invalidTests := []struct {
660+
id string
661+
expectedErr string
662+
}{
663+
{"short", "expected length of slo_id to be in the range (8 - 48)"},
664+
{"1234567", "expected length of slo_id to be in the range (8 - 48)"}, // 7 chars
665+
{"this-is-a-very-long-slo-id-that-exceeds-the-48-character-limit-for-sure", "expected length of slo_id to be in the range (8 - 48)"}, // > 48 chars
666+
{"invalid@id", "must contain only letters, numbers, hyphens, and underscores"},
667+
{"invalid$id", "must contain only letters, numbers, hyphens, and underscores"},
668+
{"invalid id", "must contain only letters, numbers, hyphens, and underscores"}, // space
669+
{"invalid.id", "must contain only letters, numbers, hyphens, and underscores"}, // period
670+
}
671+
672+
for _, test := range invalidTests {
673+
_, errors := sloIdSchema.ValidateFunc(test.id, "slo_id")
674+
if len(errors) == 0 {
675+
t.Errorf("Expected invalid ID %q to fail validation", test.id)
676+
} else {
677+
found := false
678+
for _, err := range errors {
679+
if strings.Contains(err.Error(), test.expectedErr) {
680+
found = true
681+
break
682+
}
683+
}
684+
if !found {
685+
t.Errorf("Expected error for ID %q to contain %q, but got: %v", test.id, test.expectedErr, errors)
686+
}
687+
}
688+
}
689+
}
690+
614691
func checkResourceSloDestroy(s *terraform.State) error {
615692
client, err := clients.NewAcceptanceTestingClient()
616693
if err != nil {
@@ -856,3 +933,59 @@ func getSLOConfig(vars sloVars) string {
856933

857934
return config
858935
}
936+
937+
func getSLOConfigWithInvalidSloId(name, sloId, indicatorType string) string {
938+
configTemplate := `
939+
provider "elasticstack" {
940+
elasticsearch {}
941+
kibana {}
942+
}
943+
944+
resource "elasticstack_elasticsearch_index" "my_index" {
945+
name = "my-index-%s"
946+
deletion_protection = false
947+
}
948+
949+
resource "elasticstack_kibana_slo" "test_slo" {
950+
name = "%s"
951+
slo_id = "%s"
952+
description = "fully sick SLO"
953+
954+
%s
955+
956+
time_window {
957+
duration = "7d"
958+
type = "rolling"
959+
}
960+
961+
budgeting_method = "timeslices"
962+
963+
objective {
964+
target = 0.999
965+
timeslice_target = 0.95
966+
timeslice_window = "5m"
967+
}
968+
969+
depends_on = [elasticstack_elasticsearch_index.my_index]
970+
971+
}
972+
`
973+
974+
var indicator string
975+
switch indicatorType {
976+
case "apm_latency_indicator":
977+
indicator = fmt.Sprintf(`
978+
apm_latency_indicator {
979+
environment = "production"
980+
service = "my-service"
981+
transaction_type = "request"
982+
transaction_name = "GET /sup/dawg"
983+
index = "my-index-%s"
984+
threshold = 500
985+
}
986+
`, name)
987+
}
988+
989+
config := fmt.Sprintf(configTemplate, name, name, sloId, indicator)
990+
return config
991+
}

0 commit comments

Comments
 (0)