Skip to content

Commit 4fc1308

Browse files
committed
Add security exception item resource
1 parent 15e62bf commit 4fc1308

File tree

33 files changed

+3116
-0
lines changed

33 files changed

+3116
-0
lines changed

internal/kibana/security_exception_item/acc_test.go

Lines changed: 474 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
package security_exception_item
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"time"
7+
8+
"github.com/elastic/terraform-provider-elasticstack/generated/kbapi"
9+
"github.com/elastic/terraform-provider-elasticstack/internal/clients/kibana_oapi"
10+
"github.com/elastic/terraform-provider-elasticstack/internal/utils"
11+
"github.com/hashicorp/terraform-plugin-framework/attr"
12+
"github.com/hashicorp/terraform-plugin-framework/diag"
13+
"github.com/hashicorp/terraform-plugin-framework/resource"
14+
"github.com/hashicorp/terraform-plugin-framework/types"
15+
)
16+
17+
func (r *ExceptionItemResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
18+
var plan ExceptionItemModel
19+
20+
diags := req.Plan.Get(ctx, &plan)
21+
resp.Diagnostics.Append(diags...)
22+
if resp.Diagnostics.HasError() {
23+
return
24+
}
25+
26+
client, err := r.client.GetKibanaOapiClient()
27+
if err != nil {
28+
resp.Diagnostics.AddError("Failed to get Kibana client", err.Error())
29+
return
30+
}
31+
32+
// Convert entries from Terraform model to API model
33+
entries, diags := convertEntriesToAPI(ctx, plan.Entries)
34+
resp.Diagnostics.Append(diags...)
35+
if resp.Diagnostics.HasError() {
36+
return
37+
}
38+
39+
// Build the request body
40+
body := kbapi.CreateExceptionListItemJSONRequestBody{
41+
ListId: kbapi.SecurityExceptionsAPIExceptionListHumanId(plan.ListID.ValueString()),
42+
Name: kbapi.SecurityExceptionsAPIExceptionListItemName(plan.Name.ValueString()),
43+
Description: kbapi.SecurityExceptionsAPIExceptionListItemDescription(plan.Description.ValueString()),
44+
Type: kbapi.SecurityExceptionsAPIExceptionListItemType(plan.Type.ValueString()),
45+
Entries: entries,
46+
}
47+
48+
// Set optional item_id
49+
if utils.IsKnown(plan.ItemID) && !plan.ItemID.IsNull() {
50+
itemID := kbapi.SecurityExceptionsAPIExceptionListItemHumanId(plan.ItemID.ValueString())
51+
body.ItemId = &itemID
52+
}
53+
54+
// Set optional namespace_type
55+
if utils.IsKnown(plan.NamespaceType) {
56+
nsType := kbapi.SecurityExceptionsAPIExceptionNamespaceType(plan.NamespaceType.ValueString())
57+
body.NamespaceType = &nsType
58+
}
59+
60+
// Set optional os_types
61+
if utils.IsKnown(plan.OsTypes) && !plan.OsTypes.IsNull() {
62+
var osTypes []string
63+
diags := plan.OsTypes.ElementsAs(ctx, &osTypes, false)
64+
resp.Diagnostics.Append(diags...)
65+
if resp.Diagnostics.HasError() {
66+
return
67+
}
68+
if len(osTypes) > 0 {
69+
osTypesArray := make(kbapi.SecurityExceptionsAPIExceptionListItemOsTypeArray, len(osTypes))
70+
for i, osType := range osTypes {
71+
osTypesArray[i] = kbapi.SecurityExceptionsAPIExceptionListOsType(osType)
72+
}
73+
body.OsTypes = &osTypesArray
74+
}
75+
}
76+
77+
// Set optional tags
78+
if utils.IsKnown(plan.Tags) && !plan.Tags.IsNull() {
79+
var tags []string
80+
diags := plan.Tags.ElementsAs(ctx, &tags, false)
81+
resp.Diagnostics.Append(diags...)
82+
if resp.Diagnostics.HasError() {
83+
return
84+
}
85+
if len(tags) > 0 {
86+
tagsArray := kbapi.SecurityExceptionsAPIExceptionListItemTags(tags)
87+
body.Tags = &tagsArray
88+
}
89+
}
90+
91+
// Set optional meta
92+
if utils.IsKnown(plan.Meta) && !plan.Meta.IsNull() {
93+
var meta kbapi.SecurityExceptionsAPIExceptionListItemMeta
94+
if err := json.Unmarshal([]byte(plan.Meta.ValueString()), &meta); err != nil {
95+
resp.Diagnostics.AddError("Failed to parse meta JSON", err.Error())
96+
return
97+
}
98+
body.Meta = &meta
99+
}
100+
101+
// Set optional comments
102+
if utils.IsKnown(plan.Comments) && !plan.Comments.IsNull() {
103+
var comments []CommentModel
104+
diags := plan.Comments.ElementsAs(ctx, &comments, false)
105+
resp.Diagnostics.Append(diags...)
106+
if resp.Diagnostics.HasError() {
107+
return
108+
}
109+
if len(comments) > 0 {
110+
commentsArray := make(kbapi.SecurityExceptionsAPICreateExceptionListItemCommentArray, len(comments))
111+
for i, comment := range comments {
112+
commentsArray[i] = kbapi.SecurityExceptionsAPICreateExceptionListItemComment{
113+
Comment: kbapi.SecurityExceptionsAPINonEmptyString(comment.Comment.ValueString()),
114+
}
115+
}
116+
body.Comments = &commentsArray
117+
}
118+
}
119+
120+
// Set optional expire_time
121+
if utils.IsKnown(plan.ExpireTime) && !plan.ExpireTime.IsNull() {
122+
expireTime, err := time.Parse(time.RFC3339, plan.ExpireTime.ValueString())
123+
if err != nil {
124+
resp.Diagnostics.AddError("Failed to parse expire_time", err.Error())
125+
return
126+
}
127+
expireTimeAPI := kbapi.SecurityExceptionsAPIExceptionListItemExpireTime(expireTime)
128+
body.ExpireTime = &expireTimeAPI
129+
}
130+
131+
// Create the exception item
132+
createResp, diags := kibana_oapi.CreateExceptionListItem(ctx, client, plan.SpaceID.ValueString(), body)
133+
resp.Diagnostics.Append(diags...)
134+
if resp.Diagnostics.HasError() {
135+
return
136+
}
137+
138+
if createResp == nil || createResp.JSON200 == nil {
139+
resp.Diagnostics.AddError("Failed to create exception item", "API returned empty response")
140+
return
141+
}
142+
143+
/*
144+
* In create/update paths we typically follow the write operation with a read, and then set the state from the read.
145+
* We want to avoid a dirty plan immediately after an apply.
146+
*/
147+
// Read back the created resource to get the final state
148+
readParams := &kbapi.ReadExceptionListItemParams{
149+
Id: (*kbapi.SecurityExceptionsAPIExceptionListItemId)(&createResp.JSON200.Id),
150+
}
151+
152+
readResp, diags := kibana_oapi.GetExceptionListItem(ctx, client, plan.SpaceID.ValueString(), readParams)
153+
resp.Diagnostics.Append(diags...)
154+
if resp.Diagnostics.HasError() {
155+
return
156+
}
157+
158+
if readResp == nil || readResp.JSON200 == nil {
159+
resp.State.RemoveResource(ctx)
160+
return
161+
}
162+
163+
// Update state with read response
164+
diags = r.updateStateFromAPIResponse(ctx, &plan, readResp.JSON200)
165+
resp.Diagnostics.Append(diags...)
166+
if resp.Diagnostics.HasError() {
167+
return
168+
}
169+
170+
diags = resp.State.Set(ctx, plan)
171+
resp.Diagnostics.Append(diags...)
172+
}
173+
174+
func (r *ExceptionItemResource) updateStateFromAPIResponse(ctx context.Context, model *ExceptionItemModel, apiResp *kbapi.SecurityExceptionsAPIExceptionListItem) diag.Diagnostics {
175+
var diags diag.Diagnostics
176+
177+
model.ID = types.StringValue(string(apiResp.Id))
178+
model.ItemID = types.StringValue(string(apiResp.ItemId))
179+
model.ListID = types.StringValue(string(apiResp.ListId))
180+
model.Name = types.StringValue(string(apiResp.Name))
181+
model.Description = types.StringValue(string(apiResp.Description))
182+
model.Type = types.StringValue(string(apiResp.Type))
183+
model.NamespaceType = types.StringValue(string(apiResp.NamespaceType))
184+
model.CreatedAt = types.StringValue(apiResp.CreatedAt.Format("2006-01-02T15:04:05.000Z"))
185+
model.CreatedBy = types.StringValue(apiResp.CreatedBy)
186+
model.UpdatedAt = types.StringValue(apiResp.UpdatedAt.Format("2006-01-02T15:04:05.000Z"))
187+
model.UpdatedBy = types.StringValue(apiResp.UpdatedBy)
188+
model.TieBreakerID = types.StringValue(apiResp.TieBreakerId)
189+
190+
// Set optional expire_time
191+
if apiResp.ExpireTime != nil {
192+
model.ExpireTime = types.StringValue(time.Time(*apiResp.ExpireTime).Format(time.RFC3339))
193+
} else {
194+
model.ExpireTime = types.StringNull()
195+
}
196+
197+
// Set optional os_types
198+
if apiResp.OsTypes != nil && len(*apiResp.OsTypes) > 0 {
199+
osTypes := make([]string, len(*apiResp.OsTypes))
200+
for i, osType := range *apiResp.OsTypes {
201+
osTypes[i] = string(osType)
202+
}
203+
list, d := types.ListValueFrom(ctx, types.StringType, osTypes)
204+
diags.Append(d...)
205+
model.OsTypes = list
206+
} else {
207+
model.OsTypes = types.ListNull(types.StringType)
208+
}
209+
210+
// Set optional tags
211+
if apiResp.Tags != nil && len(*apiResp.Tags) > 0 {
212+
list, d := types.ListValueFrom(ctx, types.StringType, *apiResp.Tags)
213+
diags.Append(d...)
214+
model.Tags = list
215+
} else {
216+
model.Tags = types.ListNull(types.StringType)
217+
}
218+
219+
// Set optional meta
220+
if apiResp.Meta != nil {
221+
metaJSON, err := json.Marshal(apiResp.Meta)
222+
if err != nil {
223+
diags.AddError("Failed to serialize meta", err.Error())
224+
return diags
225+
}
226+
model.Meta = types.StringValue(string(metaJSON))
227+
} else {
228+
model.Meta = types.StringNull()
229+
}
230+
231+
// Set entries (convert from API model to Terraform model)
232+
entriesList, d := convertEntriesFromAPI(ctx, apiResp.Entries)
233+
diags.Append(d...)
234+
model.Entries = entriesList
235+
236+
// Set optional comments
237+
if len(apiResp.Comments) > 0 {
238+
comments := make([]CommentModel, len(apiResp.Comments))
239+
for i, comment := range apiResp.Comments {
240+
comments[i] = CommentModel{
241+
ID: types.StringValue(string(comment.Id)),
242+
Comment: types.StringValue(string(comment.Comment)),
243+
}
244+
}
245+
list, d := types.ListValueFrom(ctx, types.ObjectType{
246+
AttrTypes: map[string]attr.Type{
247+
"id": types.StringType,
248+
"comment": types.StringType,
249+
},
250+
}, comments)
251+
diags.Append(d...)
252+
model.Comments = list
253+
} else {
254+
model.Comments = types.ListNull(types.ObjectType{
255+
AttrTypes: map[string]attr.Type{
256+
"id": types.StringType,
257+
"comment": types.StringType,
258+
},
259+
})
260+
}
261+
262+
return diags
263+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package security_exception_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 *ExceptionItemResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
12+
var state ExceptionItemModel
13+
14+
diags := req.State.Get(ctx, &state)
15+
resp.Diagnostics.Append(diags...)
16+
if resp.Diagnostics.HasError() {
17+
return
18+
}
19+
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+
// Delete by ID
27+
id := kbapi.SecurityExceptionsAPIExceptionListItemId(state.ID.ValueString())
28+
params := &kbapi.DeleteExceptionListItemParams{
29+
Id: &id,
30+
}
31+
32+
diags = kibana_oapi.DeleteExceptionListItem(ctx, client, state.SpaceID.ValueString(), params)
33+
resp.Diagnostics.Append(diags...)
34+
}

0 commit comments

Comments
 (0)