Skip to content

Commit 77b2046

Browse files
committed
Add security list item resource
1 parent 15e62bf commit 77b2046

File tree

13 files changed

+724
-0
lines changed

13 files changed

+724
-0
lines changed
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package security_list_item_test
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/elastic/terraform-provider-elasticstack/internal/acctest"
8+
"github.com/elastic/terraform-provider-elasticstack/internal/clients"
9+
"github.com/elastic/terraform-provider-elasticstack/internal/clients/kibana_oapi"
10+
"github.com/google/uuid"
11+
"github.com/hashicorp/terraform-plugin-testing/config"
12+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
13+
)
14+
15+
func ensureListIndexExists(t *testing.T) {
16+
client, err := clients.NewAcceptanceTestingClient()
17+
if err != nil {
18+
t.Fatalf("Failed to create client: %v", err)
19+
}
20+
21+
kibanaClient, err := client.GetKibanaOapiClient()
22+
if err != nil {
23+
t.Fatalf("Failed to get Kibana client: %v", err)
24+
}
25+
26+
diags := kibana_oapi.CreateListIndex(context.Background(), kibanaClient, "default")
27+
if diags.HasError() {
28+
// It's OK if it already exists, we'll only fail on other errors
29+
for _, d := range diags {
30+
if d.Summary() != "Unexpected status code from server: got HTTP 409" {
31+
t.Fatalf("Failed to create list index: %v", d.Detail())
32+
}
33+
}
34+
}
35+
}
36+
37+
func TestAccResourceSecurityListItem(t *testing.T) {
38+
listID := "test-list-items-" + uuid.New().String()
39+
resource.Test(t, resource.TestCase{
40+
PreCheck: func() {
41+
acctest.PreCheck(t)
42+
ensureListIndexExists(t)
43+
},
44+
ProtoV6ProviderFactories: acctest.Providers,
45+
Steps: []resource.TestStep{
46+
{ // Create
47+
ConfigDirectory: acctest.NamedTestCaseDirectory("create"),
48+
ConfigVariables: config.Variables{
49+
"list_id": config.StringVariable(listID),
50+
"value": config.StringVariable("test-value-1"),
51+
},
52+
Check: resource.ComposeTestCheckFunc(
53+
resource.TestCheckResourceAttrSet("elasticstack_kibana_security_list_item.test", "id"),
54+
resource.TestCheckResourceAttr("elasticstack_kibana_security_list_item.test", "value", "test-value-1"),
55+
resource.TestCheckResourceAttrSet("elasticstack_kibana_security_list_item.test", "created_at"),
56+
resource.TestCheckResourceAttrSet("elasticstack_kibana_security_list_item.test", "created_by"),
57+
resource.TestCheckResourceAttrSet("elasticstack_kibana_security_list_item.test", "updated_at"),
58+
resource.TestCheckResourceAttrSet("elasticstack_kibana_security_list_item.test", "updated_by"),
59+
),
60+
},
61+
{ // Update
62+
ConfigDirectory: acctest.NamedTestCaseDirectory("update"),
63+
ConfigVariables: config.Variables{
64+
"list_id": config.StringVariable(listID),
65+
"value": config.StringVariable("test-value-updated"),
66+
},
67+
Check: resource.ComposeTestCheckFunc(
68+
resource.TestCheckResourceAttr("elasticstack_kibana_security_list_item.test", "value", "test-value-updated"),
69+
),
70+
},
71+
},
72+
})
73+
}
74+
75+
func TestAccResourceSecurityListItem_Space(t *testing.T) {
76+
spaceID := "test-space-" + uuid.New().String()
77+
listID := "test-list-" + uuid.New().String()
78+
79+
resource.Test(t, resource.TestCase{
80+
PreCheck: func() {
81+
acctest.PreCheck(t)
82+
ensureListIndexExistsInSpace(t, spaceID)
83+
},
84+
ProtoV6ProviderFactories: acctest.Providers,
85+
Steps: []resource.TestStep{
86+
{ // Create space, list, and list item
87+
ConfigDirectory: acctest.NamedTestCaseDirectory("space_create"),
88+
ConfigVariables: config.Variables{
89+
"space_id": config.StringVariable(spaceID),
90+
"list_id": config.StringVariable(listID),
91+
"value": config.StringVariable("192.168.1.1"),
92+
},
93+
Check: resource.ComposeTestCheckFunc(
94+
// Check space
95+
resource.TestCheckResourceAttr("elasticstack_kibana_space.test", "space_id", spaceID),
96+
resource.TestCheckResourceAttr("elasticstack_kibana_space.test", "name", "Test Security Lists Space"),
97+
// Check list
98+
resource.TestCheckResourceAttrSet("elasticstack_kibana_security_list.test", "id"),
99+
resource.TestCheckResourceAttr("elasticstack_kibana_security_list.test", "space_id", spaceID),
100+
resource.TestCheckResourceAttr("elasticstack_kibana_security_list.test", "list_id", listID),
101+
resource.TestCheckResourceAttr("elasticstack_kibana_security_list.test", "name", "IP Blocklist"),
102+
resource.TestCheckResourceAttr("elasticstack_kibana_security_list.test", "type", "ip"),
103+
// Check list item
104+
resource.TestCheckResourceAttrSet("elasticstack_kibana_security_list_item.test", "id"),
105+
resource.TestCheckResourceAttr("elasticstack_kibana_security_list_item.test", "space_id", spaceID),
106+
resource.TestCheckResourceAttr("elasticstack_kibana_security_list_item.test", "list_id", listID),
107+
resource.TestCheckResourceAttr("elasticstack_kibana_security_list_item.test", "value", "192.168.1.1"),
108+
resource.TestCheckResourceAttrSet("elasticstack_kibana_security_list_item.test", "created_at"),
109+
resource.TestCheckResourceAttrSet("elasticstack_kibana_security_list_item.test", "created_by"),
110+
),
111+
},
112+
{ // Update list item
113+
ConfigDirectory: acctest.NamedTestCaseDirectory("space_update"),
114+
ConfigVariables: config.Variables{
115+
"space_id": config.StringVariable(spaceID),
116+
"list_id": config.StringVariable(listID),
117+
"value": config.StringVariable("10.0.0.1"),
118+
},
119+
Check: resource.ComposeTestCheckFunc(
120+
resource.TestCheckResourceAttr("elasticstack_kibana_security_list_item.test", "value", "10.0.0.1"),
121+
),
122+
},
123+
},
124+
})
125+
}
126+
127+
func ensureListIndexExistsInSpace(t *testing.T, spaceID string) {
128+
client, err := clients.NewAcceptanceTestingClient()
129+
if err != nil {
130+
t.Fatalf("Failed to create client: %v", err)
131+
}
132+
133+
kibanaClient, err := client.GetKibanaOapiClient()
134+
if err != nil {
135+
t.Fatalf("Failed to get Kibana client: %v", err)
136+
}
137+
138+
diags := kibana_oapi.CreateListIndex(context.Background(), kibanaClient, spaceID)
139+
if diags.HasError() {
140+
// It's OK if it already exists, we'll only fail on other errors
141+
for _, d := range diags {
142+
if d.Summary() != "Unexpected status code from server: got HTTP 409" {
143+
t.Fatalf("Failed to create list index in space %s: %v", spaceID, d.Detail())
144+
}
145+
}
146+
}
147+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package security_list_item
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
7+
"github.com/elastic/terraform-provider-elasticstack/generated/kbapi"
8+
"github.com/elastic/terraform-provider-elasticstack/internal/clients/kibana_oapi"
9+
"github.com/hashicorp/terraform-plugin-framework/resource"
10+
)
11+
12+
func (r *securityListItemResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
13+
var plan SecurityListItemModel
14+
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
15+
if resp.Diagnostics.HasError() {
16+
return
17+
}
18+
19+
// Get Kibana client
20+
client, err := r.client.GetKibanaOapiClient()
21+
if err != nil {
22+
resp.Diagnostics.AddError("Failed to get Kibana client", err.Error())
23+
return
24+
}
25+
26+
// Convert plan to API request
27+
createReq, diags := plan.toAPICreateModel(ctx)
28+
resp.Diagnostics.Append(diags...)
29+
if resp.Diagnostics.HasError() {
30+
return
31+
}
32+
33+
// Create the list item
34+
createResp, diags := kibana_oapi.CreateListItem(ctx, client, plan.SpaceID.ValueString(), *createReq)
35+
resp.Diagnostics.Append(diags...)
36+
if resp.Diagnostics.HasError() {
37+
return
38+
}
39+
40+
if createResp == nil || createResp.JSON200 == nil {
41+
resp.Diagnostics.AddError("Failed to create security list item", "API returned empty response")
42+
return
43+
}
44+
45+
// Read the created list item to populate state
46+
id := kbapi.SecurityListsAPIListId(createResp.JSON200.Id)
47+
readParams := &kbapi.ReadListItemParams{
48+
Id: &id,
49+
}
50+
51+
readResp, diags := kibana_oapi.GetListItem(ctx, client, plan.SpaceID.ValueString(), readParams)
52+
resp.Diagnostics.Append(diags...)
53+
if resp.Diagnostics.HasError() {
54+
return
55+
}
56+
57+
if readResp == nil || readResp.JSON200 == nil {
58+
resp.State.RemoveResource(ctx)
59+
resp.Diagnostics.AddError("Failed to fetch security list item", "API returned empty response")
60+
return
61+
}
62+
63+
// Unmarshal the response body to get the list item
64+
var listItem kbapi.SecurityListsAPIListItem
65+
if err := json.Unmarshal(readResp.Body, &listItem); err != nil {
66+
resp.Diagnostics.AddError("Failed to parse list item response", err.Error())
67+
return
68+
}
69+
70+
// Update state with read response
71+
diags = plan.fromAPIModel(ctx, &listItem)
72+
resp.Diagnostics.Append(diags...)
73+
if resp.Diagnostics.HasError() {
74+
return
75+
}
76+
77+
resp.Diagnostics.Append(resp.State.Set(ctx, plan)...)
78+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package security_list_item
2+
3+
import (
4+
"context"
5+
6+
"github.com/elastic/terraform-provider-elasticstack/generated/kbapi"
7+
"github.com/elastic/terraform-provider-elasticstack/internal/clients/kibana_oapi"
8+
"github.com/hashicorp/terraform-plugin-framework/resource"
9+
)
10+
11+
func (r *securityListItemResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
12+
var state SecurityListItemModel
13+
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
14+
if resp.Diagnostics.HasError() {
15+
return
16+
}
17+
18+
// Get Kibana client
19+
client, err := r.client.GetKibanaOapiClient()
20+
if err != nil {
21+
resp.Diagnostics.AddError("Failed to get Kibana client", err.Error())
22+
return
23+
}
24+
25+
// Delete by ID
26+
id := kbapi.SecurityListsAPIListItemId(state.ID.ValueString())
27+
params := &kbapi.DeleteListItemParams{
28+
Id: &id,
29+
}
30+
31+
diags := kibana_oapi.DeleteListItem(ctx, client, state.SpaceID.ValueString(), params)
32+
resp.Diagnostics.Append(diags...)
33+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package security_list_item
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
7+
"github.com/elastic/terraform-provider-elasticstack/generated/kbapi"
8+
"github.com/elastic/terraform-provider-elasticstack/internal/utils"
9+
"github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes"
10+
"github.com/hashicorp/terraform-plugin-framework/diag"
11+
"github.com/hashicorp/terraform-plugin-framework/types"
12+
)
13+
14+
type SecurityListItemModel struct {
15+
ID types.String `tfsdk:"id"`
16+
SpaceID types.String `tfsdk:"space_id"`
17+
ListID types.String `tfsdk:"list_id"`
18+
Value types.String `tfsdk:"value"`
19+
Meta jsontypes.Normalized `tfsdk:"meta"`
20+
CreatedAt types.String `tfsdk:"created_at"`
21+
CreatedBy types.String `tfsdk:"created_by"`
22+
UpdatedAt types.String `tfsdk:"updated_at"`
23+
UpdatedBy types.String `tfsdk:"updated_by"`
24+
Version types.String `tfsdk:"version"`
25+
}
26+
27+
// toAPICreateModel converts the Terraform model to the API create request body
28+
func (m *SecurityListItemModel) toAPICreateModel(ctx context.Context) (*kbapi.CreateListItemJSONRequestBody, diag.Diagnostics) {
29+
var diags diag.Diagnostics
30+
31+
body := &kbapi.CreateListItemJSONRequestBody{
32+
ListId: kbapi.SecurityListsAPIListId(m.ListID.ValueString()),
33+
Value: kbapi.SecurityListsAPIListItemValue(m.Value.ValueString()),
34+
}
35+
36+
// Set optional ID if specified
37+
if utils.IsKnown(m.ID) {
38+
id := kbapi.SecurityListsAPIListItemId(m.ID.ValueString())
39+
body.Id = &id
40+
}
41+
42+
// Set optional meta if specified
43+
if utils.IsKnown(m.Meta) {
44+
var meta kbapi.SecurityListsAPIListItemMetadata
45+
if err := json.Unmarshal([]byte(m.Meta.ValueString()), &meta); err != nil {
46+
diags.AddError("Failed to parse meta JSON", err.Error())
47+
return nil, diags
48+
}
49+
body.Meta = &meta
50+
}
51+
52+
return body, diags
53+
}
54+
55+
// toAPIUpdateModel converts the Terraform model to the API update request body
56+
func (m *SecurityListItemModel) toAPIUpdateModel(ctx context.Context) (*kbapi.UpdateListItemJSONRequestBody, diag.Diagnostics) {
57+
var diags diag.Diagnostics
58+
59+
body := &kbapi.UpdateListItemJSONRequestBody{
60+
Id: kbapi.SecurityListsAPIListItemId(m.ID.ValueString()),
61+
Value: kbapi.SecurityListsAPIListItemValue(m.Value.ValueString()),
62+
}
63+
64+
// Set optional version if available
65+
if utils.IsKnown(m.Version) {
66+
version := kbapi.SecurityListsAPIListVersionId(m.Version.ValueString())
67+
body.UnderscoreVersion = &version
68+
}
69+
70+
// Set optional meta if specified
71+
if utils.IsKnown(m.Meta) {
72+
var meta kbapi.SecurityListsAPIListItemMetadata
73+
if err := json.Unmarshal([]byte(m.Meta.ValueString()), &meta); err != nil {
74+
diags.AddError("Failed to parse meta JSON", err.Error())
75+
return nil, diags
76+
}
77+
body.Meta = &meta
78+
}
79+
80+
return body, diags
81+
}
82+
83+
// fromAPIModel populates the Terraform model from an API response
84+
func (m *SecurityListItemModel) fromAPIModel(ctx context.Context, apiItem *kbapi.SecurityListsAPIListItem) diag.Diagnostics {
85+
var diags diag.Diagnostics
86+
87+
m.ID = types.StringValue(string(apiItem.Id))
88+
m.ListID = types.StringValue(string(apiItem.ListId))
89+
m.Value = types.StringValue(string(apiItem.Value))
90+
m.CreatedAt = types.StringValue(apiItem.CreatedAt.Format("2006-01-02T15:04:05.000Z"))
91+
m.CreatedBy = types.StringValue(apiItem.CreatedBy)
92+
m.UpdatedAt = types.StringValue(apiItem.UpdatedAt.Format("2006-01-02T15:04:05.000Z"))
93+
m.UpdatedBy = types.StringValue(apiItem.UpdatedBy)
94+
95+
// Set version if available
96+
if apiItem.UnderscoreVersion != nil {
97+
m.Version = types.StringValue(string(*apiItem.UnderscoreVersion))
98+
} else {
99+
m.Version = types.StringNull()
100+
}
101+
102+
// Set meta if available
103+
if apiItem.Meta != nil {
104+
metaJSON, err := json.Marshal(apiItem.Meta)
105+
if err != nil {
106+
diags.AddError("Failed to serialize meta", err.Error())
107+
return diags
108+
}
109+
m.Meta = jsontypes.NewNormalizedValue(string(metaJSON))
110+
} else {
111+
m.Meta = jsontypes.NewNormalizedNull()
112+
}
113+
114+
return diags
115+
}

0 commit comments

Comments
 (0)