Skip to content

Commit 7a36730

Browse files
Copilottobio
andcommitted
Add validator to prevent duplicate versions in required_versions
Co-authored-by: tobio <[email protected]>
1 parent bc8388e commit 7a36730

File tree

4 files changed

+255
-0
lines changed

4 files changed

+255
-0
lines changed

internal/fleet/agent_policy/acc_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -973,3 +973,51 @@ data "elasticstack_fleet_enrollment_tokens" "test_policy" {
973973
}
974974
`, fmt.Sprintf("Policy %s", id))
975975
}
976+
977+
func TestAccResourceAgentPolicyWithDuplicateRequiredVersions(t *testing.T) {
978+
policyName := sdkacctest.RandStringFromCharSet(22, sdkacctest.CharSetAlphaNum)
979+
980+
resource.Test(t, resource.TestCase{
981+
PreCheck: func() { acctest.PreCheck(t) },
982+
ProtoV6ProviderFactories: acctest.Providers,
983+
Steps: []resource.TestStep{
984+
{
985+
SkipFunc: versionutils.CheckIfVersionIsUnsupported(minVersionAgentPolicy),
986+
Config: testAccResourceAgentPolicyCreateWithDuplicateRequiredVersions(policyName),
987+
ExpectError: regexp.MustCompile(".*Duplicate Version.*8.15.0.*"),
988+
},
989+
},
990+
})
991+
}
992+
993+
func testAccResourceAgentPolicyCreateWithDuplicateRequiredVersions(id string) string {
994+
return fmt.Sprintf(`
995+
provider "elasticstack" {
996+
elasticsearch {}
997+
kibana {}
998+
}
999+
1000+
resource "elasticstack_fleet_agent_policy" "test_policy" {
1001+
name = "%s"
1002+
namespace = "default"
1003+
description = "Test Agent Policy with Duplicate Required Versions"
1004+
monitor_logs = true
1005+
monitor_metrics = false
1006+
skip_destroy = false
1007+
required_versions = [
1008+
{
1009+
version = "8.15.0"
1010+
percentage = 50
1011+
},
1012+
{
1013+
version = "8.15.0"
1014+
percentage = 100
1015+
}
1016+
]
1017+
}
1018+
1019+
data "elasticstack_fleet_enrollment_tokens" "test_policy" {
1020+
policy_id = elasticstack_fleet_agent_policy.test_policy.policy_id
1021+
}
1022+
`, fmt.Sprintf("Policy %s", id))
1023+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package agent_policy
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
8+
"github.com/hashicorp/terraform-plugin-framework/types"
9+
)
10+
11+
var _ validator.Set = (*uniqueVersionValidator)(nil)
12+
13+
// uniqueVersionValidator validates that all required_versions have unique version strings
14+
type uniqueVersionValidator struct{}
15+
16+
func (v uniqueVersionValidator) Description(ctx context.Context) string {
17+
return "Ensures that all required_versions entries have unique version strings"
18+
}
19+
20+
func (v uniqueVersionValidator) MarkdownDescription(ctx context.Context) string {
21+
return "Ensures that all required_versions entries have unique version strings"
22+
}
23+
24+
func (v uniqueVersionValidator) ValidateSet(ctx context.Context, req validator.SetRequest, resp *validator.SetResponse) {
25+
// If the value is unknown or null, validation should not be performed
26+
if req.ConfigValue.IsUnknown() || req.ConfigValue.IsNull() {
27+
return
28+
}
29+
30+
elements := req.ConfigValue.Elements()
31+
versions := make(map[string]bool)
32+
33+
for _, elem := range elements {
34+
obj, ok := elem.(types.Object)
35+
if !ok {
36+
resp.Diagnostics.AddAttributeError(
37+
req.Path,
38+
"Invalid Type",
39+
fmt.Sprintf("Expected object type in set, got %T", elem),
40+
)
41+
continue
42+
}
43+
44+
attrs := obj.Attributes()
45+
versionAttr, ok := attrs["version"]
46+
if !ok {
47+
continue
48+
}
49+
50+
versionStr, ok := versionAttr.(types.String)
51+
if !ok {
52+
continue
53+
}
54+
55+
if versionStr.IsNull() || versionStr.IsUnknown() {
56+
continue
57+
}
58+
59+
version := versionStr.ValueString()
60+
if versions[version] {
61+
resp.Diagnostics.AddAttributeError(
62+
req.Path,
63+
"Duplicate Version",
64+
fmt.Sprintf("The version '%s' appears multiple times in required_versions. Each version must be unique.", version),
65+
)
66+
return
67+
}
68+
versions[version] = true
69+
}
70+
}
71+
72+
// UniqueVersions returns a validator that ensures all required_versions have unique version strings
73+
func UniqueVersions() validator.Set {
74+
return uniqueVersionValidator{}
75+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package agent_policy
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/hashicorp/terraform-plugin-framework/attr"
8+
"github.com/hashicorp/terraform-plugin-framework/diag"
9+
"github.com/hashicorp/terraform-plugin-framework/path"
10+
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
11+
"github.com/hashicorp/terraform-plugin-framework/types"
12+
"github.com/stretchr/testify/assert"
13+
)
14+
15+
func TestUniqueVersionsValidator(t *testing.T) {
16+
ctx := context.Background()
17+
elemType := getRequiredVersionsElementType()
18+
19+
t.Run("valid - unique versions", func(t *testing.T) {
20+
elements := []attr.Value{
21+
types.ObjectValueMust(
22+
map[string]attr.Type{
23+
"version": types.StringType,
24+
"percentage": types.Int32Type,
25+
},
26+
map[string]attr.Value{
27+
"version": types.StringValue("8.15.0"),
28+
"percentage": types.Int32Value(50),
29+
},
30+
),
31+
types.ObjectValueMust(
32+
map[string]attr.Type{
33+
"version": types.StringType,
34+
"percentage": types.Int32Type,
35+
},
36+
map[string]attr.Value{
37+
"version": types.StringValue("8.16.0"),
38+
"percentage": types.Int32Value(50),
39+
},
40+
),
41+
}
42+
43+
setValue := types.SetValueMust(elemType, elements)
44+
45+
req := validator.SetRequest{
46+
Path: path.Root("required_versions"),
47+
ConfigValue: setValue,
48+
}
49+
resp := &validator.SetResponse{
50+
Diagnostics: diag.Diagnostics{},
51+
}
52+
53+
UniqueVersions().ValidateSet(ctx, req, resp)
54+
55+
assert.False(t, resp.Diagnostics.HasError(), "Expected no errors for unique versions")
56+
})
57+
58+
t.Run("invalid - duplicate versions", func(t *testing.T) {
59+
elements := []attr.Value{
60+
types.ObjectValueMust(
61+
map[string]attr.Type{
62+
"version": types.StringType,
63+
"percentage": types.Int32Type,
64+
},
65+
map[string]attr.Value{
66+
"version": types.StringValue("8.15.0"),
67+
"percentage": types.Int32Value(50),
68+
},
69+
),
70+
types.ObjectValueMust(
71+
map[string]attr.Type{
72+
"version": types.StringType,
73+
"percentage": types.Int32Type,
74+
},
75+
map[string]attr.Value{
76+
"version": types.StringValue("8.15.0"),
77+
"percentage": types.Int32Value(100),
78+
},
79+
),
80+
}
81+
82+
setValue := types.SetValueMust(elemType, elements)
83+
84+
req := validator.SetRequest{
85+
Path: path.Root("required_versions"),
86+
ConfigValue: setValue,
87+
}
88+
resp := &validator.SetResponse{
89+
Diagnostics: diag.Diagnostics{},
90+
}
91+
92+
UniqueVersions().ValidateSet(ctx, req, resp)
93+
94+
assert.True(t, resp.Diagnostics.HasError(), "Expected error for duplicate versions")
95+
assert.Contains(t, resp.Diagnostics.Errors()[0].Detail(), "8.15.0")
96+
})
97+
98+
t.Run("valid - null value", func(t *testing.T) {
99+
setValue := types.SetNull(elemType)
100+
101+
req := validator.SetRequest{
102+
Path: path.Root("required_versions"),
103+
ConfigValue: setValue,
104+
}
105+
resp := &validator.SetResponse{
106+
Diagnostics: diag.Diagnostics{},
107+
}
108+
109+
UniqueVersions().ValidateSet(ctx, req, resp)
110+
111+
assert.False(t, resp.Diagnostics.HasError(), "Expected no errors for null value")
112+
})
113+
114+
t.Run("valid - empty set", func(t *testing.T) {
115+
setValue := types.SetValueMust(elemType, []attr.Value{})
116+
117+
req := validator.SetRequest{
118+
Path: path.Root("required_versions"),
119+
ConfigValue: setValue,
120+
}
121+
resp := &validator.SetResponse{
122+
Diagnostics: diag.Diagnostics{},
123+
}
124+
125+
UniqueVersions().ValidateSet(ctx, req, resp)
126+
127+
assert.False(t, resp.Diagnostics.HasError(), "Expected no errors for empty set")
128+
})
129+
}

internal/fleet/agent_policy/schema.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,9 @@ func getSchema() schema.Schema {
156156
},
157157
Optional: true,
158158
CustomType: RequiredVersionsType{SetType: basetypes.SetType{ElemType: types.ObjectType{AttrTypes: map[string]attr.Type{"version": types.StringType, "percentage": types.Int32Type}}}},
159+
Validators: []validator.Set{
160+
UniqueVersions(),
161+
},
159162
},
160163
}}
161164
}

0 commit comments

Comments
 (0)