Skip to content

Commit e29b951

Browse files
authored
Allow a default for allow_restricted_indices within an API Key role descriptor (elastic#1315)
* Allow a default for `allow_restricted_indices` within an API Key role descriptor * Changelog
1 parent 5b31272 commit e29b951

File tree

8 files changed

+668
-4
lines changed

8 files changed

+668
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
- [Refactor] Regenerate the SLO client using the current OpenAPI spec ([#1303](https://github.com/elastic/terraform-provider-elasticstack/pull/1303))
1818
- Add support for `data_view_id` in the `elasticstack_kibana_slo` resource ([#1305](https://github.com/elastic/terraform-provider-elasticstack/pull/1305))
1919
- Add support for `unenrollment_timeout` in `elasticstack_fleet_agent_policy` ([#1169](https://github.com/elastic/terraform-provider-elasticstack/issues/1169))
20+
- Handle default value for `allow_restricted_indices` in `elasticstack_elasticsearch_security_api_key` ([#1315](https://github.com/elastic/terraform-provider-elasticstack/pull/1315))
2021

2122
## [0.11.17] - 2025-07-21
2223

internal/elasticsearch/security/api_key/acc_test.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,3 +550,73 @@ resource "elasticstack_elasticsearch_security_api_key" "test" {
550550
}
551551
`, apiKeyName)
552552
}
553+
554+
func TestAccResourceSecurityApiKeyWithDefaultAllowRestrictedIndices(t *testing.T) {
555+
// generate a random name
556+
apiKeyName := sdkacctest.RandStringFromCharSet(10, sdkacctest.CharSetAlphaNum)
557+
558+
resource.Test(t, resource.TestCase{
559+
PreCheck: func() { acctest.PreCheck(t) },
560+
CheckDestroy: checkResourceSecurityApiKeyDestroy,
561+
ProtoV6ProviderFactories: acctest.Providers,
562+
Steps: []resource.TestStep{
563+
{
564+
SkipFunc: versionutils.CheckIfVersionIsUnsupported(api_key.MinVersion),
565+
Config: testAccResourceSecurityApiKeyWithoutAllowRestrictedIndices(apiKeyName),
566+
Check: resource.ComposeTestCheckFunc(
567+
resource.TestCheckResourceAttr("elasticstack_elasticsearch_security_api_key.test", "name", apiKeyName),
568+
resource.TestCheckResourceAttrWith("elasticstack_elasticsearch_security_api_key.test", "role_descriptors", func(testValue string) error {
569+
var testRoleDescriptor map[string]models.ApiKeyRoleDescriptor
570+
if err := json.Unmarshal([]byte(testValue), &testRoleDescriptor); err != nil {
571+
return err
572+
}
573+
574+
expectedRoleDescriptor := map[string]models.ApiKeyRoleDescriptor{
575+
"role-default": {
576+
Cluster: []string{"monitor"},
577+
Indices: []models.IndexPerms{{
578+
Names: []string{"logs-*", "metrics-*"},
579+
Privileges: []string{"read", "view_index_metadata"},
580+
}},
581+
},
582+
}
583+
584+
if !reflect.DeepEqual(testRoleDescriptor, expectedRoleDescriptor) {
585+
return fmt.Errorf("role descriptor mismatch:\nexpected: %+v\nactual: %+v", expectedRoleDescriptor, testRoleDescriptor)
586+
}
587+
588+
return nil
589+
}),
590+
resource.TestCheckResourceAttrSet("elasticstack_elasticsearch_security_api_key.test", "api_key"),
591+
resource.TestCheckResourceAttrSet("elasticstack_elasticsearch_security_api_key.test", "encoded"),
592+
resource.TestCheckResourceAttrSet("elasticstack_elasticsearch_security_api_key.test", "id"),
593+
),
594+
},
595+
},
596+
})
597+
}
598+
599+
func testAccResourceSecurityApiKeyWithoutAllowRestrictedIndices(apiKeyName string) string {
600+
return fmt.Sprintf(`
601+
provider "elasticstack" {
602+
elasticsearch {}
603+
}
604+
605+
resource "elasticstack_elasticsearch_security_api_key" "test" {
606+
name = "%s"
607+
608+
role_descriptors = jsonencode({
609+
role-default = {
610+
cluster = ["monitor"]
611+
indices = [{
612+
names = ["logs-*", "metrics-*"]
613+
privileges = ["read", "view_index_metadata"]
614+
# Note: allow_restricted_indices is NOT specified here - should default to false
615+
}]
616+
}
617+
})
618+
619+
expiration = "2d"
620+
}
621+
`, apiKeyName)
622+
}

internal/elasticsearch/security/api_key/models.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ type tfModel struct {
3636
KeyID types.String `tfsdk:"key_id"`
3737
Name types.String `tfsdk:"name"`
3838
Type types.String `tfsdk:"type"`
39-
RoleDescriptors jsontypes.Normalized `tfsdk:"role_descriptors"`
39+
RoleDescriptors RoleDescriptorsValue `tfsdk:"role_descriptors"`
4040
Expiration types.String `tfsdk:"expiration"`
4141
ExpirationTimestamp types.Int64 `tfsdk:"expiration_timestamp"`
4242
Metadata jsontypes.Normalized `tfsdk:"metadata"`
@@ -205,15 +205,15 @@ func (model *tfModel) populateFromAPI(apiKey models.ApiKeyResponse, serverVersio
205205
model.Metadata = jsontypes.NewNormalizedNull()
206206

207207
if serverVersion.GreaterThanOrEqual(MinVersionReturningRoleDescriptors) {
208-
model.RoleDescriptors = jsontypes.NewNormalizedNull()
208+
model.RoleDescriptors = NewRoleDescriptorsNull()
209209

210210
if apiKey.RolesDescriptors != nil {
211211
descriptors, diags := marshalNormalizedJsonValue(apiKey.RolesDescriptors)
212212
if diags.HasError() {
213213
return diags
214214
}
215215

216-
model.RoleDescriptors = descriptors
216+
model.RoleDescriptors = NewRoleDescriptorsValue(descriptors.ValueString())
217217
}
218218
}
219219

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package api_key
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes"
8+
"github.com/hashicorp/terraform-plugin-framework/attr"
9+
"github.com/hashicorp/terraform-plugin-framework/diag"
10+
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
11+
"github.com/hashicorp/terraform-plugin-go/tftypes"
12+
)
13+
14+
var (
15+
_ basetypes.StringTypable = (*RoleDescriptorsType)(nil)
16+
)
17+
18+
type RoleDescriptorsType struct {
19+
jsontypes.NormalizedType
20+
}
21+
22+
// String returns a human readable string of the type name.
23+
func (t RoleDescriptorsType) String() string {
24+
return "api_key.RoleDescriptorsType"
25+
}
26+
27+
// ValueType returns the Value type.
28+
func (t RoleDescriptorsType) ValueType(ctx context.Context) attr.Value {
29+
return RoleDescriptorsValue{}
30+
}
31+
32+
// Equal returns true if the given type is equivalent.
33+
func (t RoleDescriptorsType) Equal(o attr.Type) bool {
34+
other, ok := o.(RoleDescriptorsType)
35+
36+
if !ok {
37+
return false
38+
}
39+
40+
return t.StringType.Equal(other.StringType)
41+
}
42+
43+
// ValueFromString returns a StringValuable type given a StringValue.
44+
func (t RoleDescriptorsType) ValueFromString(ctx context.Context, in basetypes.StringValue) (basetypes.StringValuable, diag.Diagnostics) {
45+
return RoleDescriptorsValue{
46+
Normalized: jsontypes.Normalized{
47+
StringValue: in,
48+
},
49+
}, nil
50+
}
51+
52+
// ValueFromTerraform returns a Value given a tftypes.Value. This is meant to convert the tftypes.Value into a more convenient Go type
53+
// for the provider to consume the data with.
54+
func (t RoleDescriptorsType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) {
55+
attrValue, err := t.StringType.ValueFromTerraform(ctx, in)
56+
if err != nil {
57+
return nil, err
58+
}
59+
60+
stringValue, ok := attrValue.(basetypes.StringValue)
61+
if !ok {
62+
return nil, fmt.Errorf("unexpected value type of %T", attrValue)
63+
}
64+
65+
stringValuable, diags := t.ValueFromString(ctx, stringValue)
66+
if diags.HasError() {
67+
return nil, fmt.Errorf("unexpected error converting StringValue to StringValuable: %v", diags)
68+
}
69+
70+
return stringValuable, nil
71+
}
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
package api_key
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes"
8+
"github.com/hashicorp/terraform-plugin-framework/attr"
9+
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
10+
"github.com/hashicorp/terraform-plugin-go/tftypes"
11+
"github.com/stretchr/testify/assert"
12+
)
13+
14+
func TestRoleDescriptorsType_String(t *testing.T) {
15+
roleDescriptorsType := RoleDescriptorsType{}
16+
expected := "api_key.RoleDescriptorsType"
17+
actual := roleDescriptorsType.String()
18+
assert.Equal(t, expected, actual)
19+
}
20+
21+
func TestRoleDescriptorsType_ValueType(t *testing.T) {
22+
roleDescriptorsType := RoleDescriptorsType{}
23+
ctx := context.Background()
24+
25+
value := roleDescriptorsType.ValueType(ctx)
26+
27+
assert.IsType(t, RoleDescriptorsValue{}, value)
28+
}
29+
30+
func TestRoleDescriptorsType_Equal(t *testing.T) {
31+
tests := []struct {
32+
name string
33+
thisType RoleDescriptorsType
34+
other attr.Type
35+
expected bool
36+
}{
37+
{
38+
name: "equal to same type",
39+
thisType: RoleDescriptorsType{},
40+
other: RoleDescriptorsType{},
41+
expected: true,
42+
},
43+
{
44+
name: "not equal to different type",
45+
thisType: RoleDescriptorsType{},
46+
other: basetypes.StringType{},
47+
expected: false,
48+
},
49+
{
50+
name: "not equal to nil",
51+
thisType: RoleDescriptorsType{},
52+
other: nil,
53+
expected: false,
54+
},
55+
}
56+
57+
for _, tt := range tests {
58+
t.Run(tt.name, func(t *testing.T) {
59+
actual := tt.thisType.Equal(tt.other)
60+
assert.Equal(t, tt.expected, actual)
61+
})
62+
}
63+
}
64+
65+
func TestRoleDescriptorsType_ValueFromString(t *testing.T) {
66+
tests := []struct {
67+
name string
68+
input basetypes.StringValue
69+
expectedValue RoleDescriptorsValue
70+
expectedDiags bool
71+
}{
72+
{
73+
name: "valid string value",
74+
input: basetypes.NewStringValue(`{"role1": {"cluster": ["read"]}}`),
75+
expectedValue: RoleDescriptorsValue{
76+
Normalized: jsontypes.Normalized{
77+
StringValue: basetypes.NewStringValue(`{"role1": {"cluster": ["read"]}}`),
78+
},
79+
},
80+
expectedDiags: false,
81+
},
82+
{
83+
name: "null string value",
84+
input: basetypes.NewStringNull(),
85+
expectedValue: RoleDescriptorsValue{
86+
Normalized: jsontypes.Normalized{
87+
StringValue: basetypes.NewStringNull(),
88+
},
89+
},
90+
expectedDiags: false,
91+
},
92+
{
93+
name: "unknown string value",
94+
input: basetypes.NewStringUnknown(),
95+
expectedValue: RoleDescriptorsValue{
96+
Normalized: jsontypes.Normalized{
97+
StringValue: basetypes.NewStringUnknown(),
98+
},
99+
},
100+
expectedDiags: false,
101+
},
102+
{
103+
name: "empty string value",
104+
input: basetypes.NewStringValue(""),
105+
expectedValue: RoleDescriptorsValue{
106+
Normalized: jsontypes.Normalized{
107+
StringValue: basetypes.NewStringValue(""),
108+
},
109+
},
110+
expectedDiags: false,
111+
},
112+
}
113+
114+
for _, tt := range tests {
115+
t.Run(tt.name, func(t *testing.T) {
116+
roleDescriptorsType := RoleDescriptorsType{}
117+
ctx := context.Background()
118+
119+
value, diags := roleDescriptorsType.ValueFromString(ctx, tt.input)
120+
121+
if tt.expectedDiags {
122+
assert.True(t, diags.HasError())
123+
} else {
124+
assert.False(t, diags.HasError())
125+
}
126+
127+
assert.Equal(t, tt.expectedValue, value)
128+
})
129+
}
130+
}
131+
132+
func TestRoleDescriptorsType_ValueFromTerraform(t *testing.T) {
133+
tests := []struct {
134+
name string
135+
input tftypes.Value
136+
expectedError bool
137+
expectedType interface{}
138+
}{
139+
{
140+
name: "valid string terraform value",
141+
input: tftypes.NewValue(tftypes.String, `{"role1": {"cluster": ["read"]}}`),
142+
expectedError: false,
143+
expectedType: RoleDescriptorsValue{},
144+
},
145+
{
146+
name: "null terraform value",
147+
input: tftypes.NewValue(tftypes.String, nil),
148+
expectedError: false,
149+
expectedType: RoleDescriptorsValue{},
150+
},
151+
{
152+
name: "unknown terraform value",
153+
input: tftypes.NewValue(tftypes.String, tftypes.UnknownValue),
154+
expectedError: false,
155+
expectedType: RoleDescriptorsValue{},
156+
},
157+
{
158+
name: "invalid terraform value type",
159+
input: tftypes.NewValue(tftypes.Number, 123),
160+
expectedError: true,
161+
expectedType: nil,
162+
},
163+
}
164+
165+
for _, tt := range tests {
166+
t.Run(tt.name, func(t *testing.T) {
167+
roleDescriptorsType := RoleDescriptorsType{}
168+
ctx := context.Background()
169+
170+
value, err := roleDescriptorsType.ValueFromTerraform(ctx, tt.input)
171+
172+
if tt.expectedError {
173+
assert.Error(t, err)
174+
assert.Nil(t, value)
175+
} else {
176+
assert.NoError(t, err)
177+
if tt.expectedType != nil {
178+
assert.IsType(t, tt.expectedType, value)
179+
}
180+
}
181+
})
182+
}
183+
}

0 commit comments

Comments
 (0)