diff --git a/.gitignore b/.gitignore index 3c429c9dd..bcfa6a1c2 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ website/node_modules *.test *.iml *.vscode +__debug_* website/vendor diff --git a/CHANGELOG.md b/CHANGELOG.md index 94fbd16a6..3eeddf786 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## [Unreleased] +- Create `elasticstack_kibana_maintenance_window` resource. ([#1224](https://github.com/elastic/terraform-provider-elasticstack/pull/1224)) - Add support for `solution` field in `elasticstack_kibana_space` resource and data source ([#1102](https://github.com/elastic/terraform-provider-elasticstack/issues/1102)) - Add `slo_id` validation to `elasticstack_kibana_slo` ([#1221](https://github.com/elastic/terraform-provider-elasticstack/pull/1221)) - Add `ignore_missing_component_templates` to `elasticstack_elasticsearch_index_template` ([#1206](https://github.com/elastic/terraform-provider-elasticstack/pull/1206)) diff --git a/docs/resources/kibana_maintenance_window.md b/docs/resources/kibana_maintenance_window.md new file mode 100644 index 000000000..adb6d9b66 --- /dev/null +++ b/docs/resources/kibana_maintenance_window.md @@ -0,0 +1,110 @@ +--- +subcategory: "Kibana" +layout: "" +page_title: "Elasticstack: elasticstack_kibana_maintenance_window Resource" +description: |- + Manages Kibana maintenance windows. +--- + +# Resource: elasticstack_kibana_maintenance_window + +Creates and manages Kibana [maintenance windows](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-maintenance-window) + +## Example Usage + +```terraform +provider "elasticstack" { + elasticsearch {} + kibana {} +} + +resource "elasticstack_kibana_maintenance_window" "my_maintenance_window" { + title = "UPDATE TEST" + enabled = true + + custom_schedule = { + start = "1993-01-01T05:00:00.200Z" + duration = "12d" + + recurring = { + every = "21d" + on_week_day = ["MO", "+3TU", "-2FR"] + on_month_day = [1, 2, 4, 6, 7] + on_month = [12] + } + } + + scope = { + alerting = { + kql = "_id: '1234'" + } + } +} +``` + + +## Schema + +### Required + +- `custom_schedule` (Attributes) A set schedule over which the maintenance window applies. (see [below for nested schema](#nestedatt--custom_schedule)) +- `title` (String) The name of the maintenance window. + +### Optional + +- `enabled` (Boolean) Whether the current maintenance window is enabled. +- `scope` (Attributes) An object that narrows the scope of what is affected by this maintenance window. (see [below for nested schema](#nestedatt--scope)) +- `space_id` (String) An identifier for the space. If space_id is not provided, the default space is used. + +### Read-Only + +- `id` (String) Generated ID for the maintenance window. + + +### Nested Schema for `custom_schedule` + +Required: + +- `duration` (String) The duration of the schedule. It allows values in `` format. `` is one of `d`, `h`, `m`, or `s` for days, hours, minutes, seconds. For example: `1d`, `5h`, `30m`, `5000s`. +- `recurring` (Attributes) A set schedule over which the maintenance window applies. (see [below for nested schema](#nestedatt--custom_schedule--recurring)) +- `start` (String) The start date and time of the schedule, provided in ISO 8601 format and set to the UTC timezone. For example: `2025-03-12T12:00:00.000Z`. + +Optional: + +- `timezone` (String) The timezone of the schedule. The default timezone is UTC. + + +### Nested Schema for `custom_schedule.recurring` + +Optional: + +- `end` (String) The end date and time of the schedule, provided in ISO 8601 format and set to the UTC timezone. For example: `2025-03-12T12:00:00.000Z`. +- `every` (String) The duration of the schedule. It allows values in `` format. `` is one of `d`, `h`, `m`, or `s` for days, hours, minutes, seconds. For example: `1d`, `5h`, `30m`, `5000s`. +- `occurrences` (Number) The total number of recurrences of the schedule. +- `on_month` (List of Number) The specific months for a recurring schedule. Valid values are 1-12. +- `on_month_day` (List of Number) The specific days of the month for a recurring schedule. Valid values are 1-31. +- `on_week_day` (List of String) The specific days of the week (`[MO,TU,WE,TH,FR,SA,SU]`) or nth day of month (`[+1MO, -3FR, +2WE, -4SA, -5SU]`) for a recurring schedule. + + + + +### Nested Schema for `scope` + +Required: + +- `alerting` (Attributes) A set schedule over which the maintenance window applies. (see [below for nested schema](#nestedatt--scope--alerting)) + + +### Nested Schema for `scope.alerting` + +Required: + +- `kql` (String) A filter written in Kibana Query Language (KQL). + +## Import + +Import is supported using the following syntax: + +```shell +terraform import elasticstack_kibana_maintenance_window.my_maintenance_window / +``` diff --git a/examples/resources/elasticstack_kibana_maintenance_window/import.sh b/examples/resources/elasticstack_kibana_maintenance_window/import.sh new file mode 100644 index 000000000..42d786bab --- /dev/null +++ b/examples/resources/elasticstack_kibana_maintenance_window/import.sh @@ -0,0 +1 @@ +terraform import elasticstack_kibana_maintenance_window.my_maintenance_window / diff --git a/examples/resources/elasticstack_kibana_maintenance_window/resource.tf b/examples/resources/elasticstack_kibana_maintenance_window/resource.tf new file mode 100644 index 000000000..2c564de26 --- /dev/null +++ b/examples/resources/elasticstack_kibana_maintenance_window/resource.tf @@ -0,0 +1,27 @@ +provider "elasticstack" { + elasticsearch {} + kibana {} +} + +resource "elasticstack_kibana_maintenance_window" "my_maintenance_window" { + title = "UPDATE TEST" + enabled = true + + custom_schedule = { + start = "1993-01-01T05:00:00.200Z" + duration = "12d" + + recurring = { + every = "21d" + on_week_day = ["MO", "+3TU", "-2FR"] + on_month_day = [1, 2, 4, 6, 7] + on_month = [12] + } + } + + scope = { + alerting = { + kql = "_id: '1234'" + } + } +} diff --git a/generated/kbapi/kibana.gen.go b/generated/kbapi/kibana.gen.go index 5eff5b6fb..0ac695f8f 100644 --- a/generated/kbapi/kibana.gen.go +++ b/generated/kbapi/kibana.gen.go @@ -4421,6 +4421,103 @@ type DeleteAgentConfigurationJSONRequestBody = APMUIDeleteServiceObject // CreateUpdateAgentConfigurationJSONRequestBody defines body for CreateUpdateAgentConfiguration for application/json ContentType. type CreateUpdateAgentConfigurationJSONRequestBody = APMUIAgentConfigurationIntakeObject +// PostMaintenanceWindowJSONBody defines parameters for PostMaintenanceWindow. + +type PostMaintenanceWindowJSONBody struct { + // Enabled Whether the current maintenance window is enabled. Disabled maintenance windows do not suppress notifications. + Enabled *bool `json:"enabled,omitempty"` + Schedule struct { + Custom struct { + // Duration The duration of the schedule. It allows values in `` format. `` is one of `d`, `h`, `m`, or `s` for hours, minutes, seconds. For example: `1d`, `5h`, `30m`, `5000s`. + Duration string `json:"duration"` + Recurring *struct { + // End The end date of a recurring schedule, provided in ISO 8601 format and set to the UTC timezone. For example: `2025-04-01T00:00:00.000Z`. + End *string `json:"end,omitempty"` + + // Every The interval and frequency of a recurring schedule. It allows values in `` format. `` is one of `d`, `w`, `M`, or `y` for days, weeks, months, years. For example: `15d`, `2w`, `3m`, `1y`. + Every *string `json:"every,omitempty"` + + // Occurrences The total number of recurrences of the schedule. + Occurrences *float32 `json:"occurrences,omitempty"` + + // OnMonth The specific months for a recurring schedule. Valid values are 1-12. + OnMonth *[]float32 `json:"onMonth,omitempty"` + + // OnMonthDay The specific days of the month for a recurring schedule. Valid values are 1-31. + OnMonthDay *[]float32 `json:"onMonthDay,omitempty"` + + // OnWeekDay The specific days of the week (`[MO,TU,WE,TH,FR,SA,SU]`) or nth day of month (`[+1MO, -3FR, +2WE, -4SA, -5SU]`) for a recurring schedule. + OnWeekDay *[]string `json:"onWeekDay,omitempty"` + } `json:"recurring,omitempty"` + + // Start The start date and time of the schedule, provided in ISO 8601 format and set to the UTC timezone. For example: `2025-03-12T12:00:00.000Z`. + Start string `json:"start"` + + // Timezone The timezone of the schedule. The default timezone is UTC. + Timezone *string `json:"timezone,omitempty"` + } `json:"custom"` + } `json:"schedule"` + Scope *struct { + Alerting struct { + Query struct { + // Kql A filter written in Kibana Query Language (KQL). Only alerts matching this query will be supressed by the maintenance window. + Kql string `json:"kql"` + } `json:"query"` + } `json:"alerting"` + } `json:"scope,omitempty"` + + // Title The name of the maintenance window. While this name does not have to be unique, a distinctive name can help you identify a specific maintenance window. + Title string `json:"title"` +} + +// PatchMaintenanceWindowIdJSONBody defines parameters for PatchMaintenanceWindowId. +type PatchMaintenanceWindowIdJSONBody struct { + // Enabled Whether the current maintenance window is enabled. Disabled maintenance windows do not suppress notifications. + Enabled *bool `json:"enabled,omitempty"` + Schedule *struct { + Custom struct { + // Duration The duration of the schedule. It allows values in `` format. `` is one of `d`, `h`, `m`, or `s` for hours, minutes, seconds. For example: `1d`, `5h`, `30m`, `5000s`. + Duration string `json:"duration"` + Recurring *struct { + // End The end date of a recurring schedule, provided in ISO 8601 format and set to the UTC timezone. For example: `2025-04-01T00:00:00.000Z`. + End *string `json:"end,omitempty"` + + // Every The interval and frequency of a recurring schedule. It allows values in `` format. `` is one of `d`, `w`, `M`, or `y` for days, weeks, months, years. For example: `15d`, `2w`, `3m`, `1y`. + Every *string `json:"every,omitempty"` + + // Occurrences The total number of recurrences of the schedule. + Occurrences *float32 `json:"occurrences,omitempty"` + + // OnMonth The specific months for a recurring schedule. Valid values are 1-12. + OnMonth *[]float32 `json:"onMonth,omitempty"` + + // OnMonthDay The specific days of the month for a recurring schedule. Valid values are 1-31. + OnMonthDay *[]float32 `json:"onMonthDay,omitempty"` + + // OnWeekDay The specific days of the week (`[MO,TU,WE,TH,FR,SA,SU]`) or nth day of month (`[+1MO, -3FR, +2WE, -4SA, -5SU]`) for a recurring schedule. + OnWeekDay *[]string `json:"onWeekDay,omitempty"` + } `json:"recurring,omitempty"` + + // Start The start date and time of the schedule, provided in ISO 8601 format and set to the UTC timezone. For example: `2025-03-12T12:00:00.000Z`. + Start string `json:"start"` + + // Timezone The timezone of the schedule. The default timezone is UTC. + Timezone *string `json:"timezone,omitempty"` + } `json:"custom"` + } `json:"schedule,omitempty"` + Scope *struct { + Alerting struct { + Query struct { + // Kql A filter written in Kibana Query Language (KQL). Only alerts matching this query will be supressed by the maintenance window. + Kql string `json:"kql"` + } `json:"query"` + } `json:"alerting"` + } `json:"scope,omitempty"` + + // Title The name of the maintenance window. While this name does not have to be unique, a distinctive name can help you identify a specific maintenance window. + Title *string `json:"title,omitempty"` +} + // PostFleetAgentPoliciesJSONRequestBody defines body for PostFleetAgentPolicies for application/json ContentType. type PostFleetAgentPoliciesJSONRequestBody PostFleetAgentPoliciesJSONBody @@ -4469,6 +4566,12 @@ type CreateDataViewDefaultwJSONRequestBody = DataViewsCreateDataViewRequestObjec // UpdateDataViewDefaultJSONRequestBody defines body for UpdateDataViewDefault for application/json ContentType. type UpdateDataViewDefaultJSONRequestBody = DataViewsUpdateDataViewRequestObject +// PostMaintenanceWindowJSONRequestBody defines body for PostMaintenanceWindow for application/json ContentType. +type PostMaintenanceWindowJSONRequestBody PostMaintenanceWindowJSONBody + +// PatchMaintenanceWindowIdJSONRequestBody defines body for PatchMaintenanceWindowId for application/json ContentType. +type PatchMaintenanceWindowIdJSONRequestBody PatchMaintenanceWindowIdJSONBody + // Getter for additional properties for AgentPolicy_PackagePolicies_1_Elasticsearch_Privileges. Returns the specified // element and whether it was found func (a AgentPolicy_PackagePolicies_1_Elasticsearch_Privileges) Get(fieldName string) (value interface{}, found bool) { @@ -18124,6 +18227,22 @@ type ClientInterface interface { UpdateDataViewDefaultWithBody(ctx context.Context, spaceId SpaceId, viewId DataViewsViewId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) UpdateDataViewDefault(ctx context.Context, spaceId SpaceId, viewId DataViewsViewId, body UpdateDataViewDefaultJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // PostMaintenanceWindowWithBody request with any body + PostMaintenanceWindowWithBody(ctx context.Context, spaceId SpaceId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + PostMaintenanceWindow(ctx context.Context, spaceId SpaceId, body PostMaintenanceWindowJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // DeleteMaintenanceWindowId request + DeleteMaintenanceWindowId(ctx context.Context, spaceId SpaceId, id string, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetMaintenanceWindowId request + GetMaintenanceWindowId(ctx context.Context, spaceId SpaceId, id string, reqEditors ...RequestEditorFn) (*http.Response, error) + + // PatchMaintenanceWindowIdWithBody request with any body + PatchMaintenanceWindowIdWithBody(ctx context.Context, spaceId SpaceId, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + PatchMaintenanceWindowId(ctx context.Context, spaceId SpaceId, id string, body PatchMaintenanceWindowIdJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) } func (c *Client) DeleteAgentConfigurationWithBody(ctx context.Context, params *DeleteAgentConfigurationParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { @@ -19026,6 +19145,78 @@ func NewCreateUpdateAgentConfigurationRequestWithBody(server string, params *Cre return req, nil } +func (c *Client) PostMaintenanceWindowWithBody(ctx context.Context, spaceId SpaceId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostMaintenanceWindowRequestWithBody(c.Server, spaceId, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) PostMaintenanceWindow(ctx context.Context, spaceId SpaceId, body PostMaintenanceWindowJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostMaintenanceWindowRequest(c.Server, spaceId, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) DeleteMaintenanceWindowId(ctx context.Context, spaceId SpaceId, id string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteMaintenanceWindowIdRequest(c.Server, spaceId, id) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetMaintenanceWindowId(ctx context.Context, spaceId SpaceId, id string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetMaintenanceWindowIdRequest(c.Server, spaceId, id) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) PatchMaintenanceWindowIdWithBody(ctx context.Context, spaceId SpaceId, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPatchMaintenanceWindowIdRequestWithBody(c.Server, spaceId, id, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) PatchMaintenanceWindowId(ctx context.Context, spaceId SpaceId, id string, body PatchMaintenanceWindowIdJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPatchMaintenanceWindowIdRequest(c.Server, spaceId, id, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + // NewGetFleetAgentPoliciesRequest generates requests for GetFleetAgentPolicies func NewGetFleetAgentPoliciesRequest(server string, params *GetFleetAgentPoliciesParams) (*http.Request, error) { var err error @@ -21337,6 +21528,189 @@ func NewUpdateDataViewDefaultRequestWithBody(server string, spaceId SpaceId, vie return req, nil } +// NewPostMaintenanceWindowRequest calls the generic PostMaintenanceWindow builder with application/json body +func NewPostMaintenanceWindowRequest(server string, spaceId SpaceId, body PostMaintenanceWindowJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewPostMaintenanceWindowRequestWithBody(server, spaceId, "application/json", bodyReader) +} + +// NewPostMaintenanceWindowRequestWithBody generates requests for PostMaintenanceWindow with any type of body +func NewPostMaintenanceWindowRequestWithBody(server string, spaceId SpaceId, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "spaceId", runtime.ParamLocationPath, spaceId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/s/%s/api/maintenance_window", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewDeleteMaintenanceWindowIdRequest generates requests for DeleteMaintenanceWindowId +func NewDeleteMaintenanceWindowIdRequest(server string, spaceId SpaceId, id string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "spaceId", runtime.ParamLocationPath, spaceId) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "id", runtime.ParamLocationPath, id) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/s/%s/api/maintenance_window/%s", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("DELETE", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewGetMaintenanceWindowIdRequest generates requests for GetMaintenanceWindowId +func NewGetMaintenanceWindowIdRequest(server string, spaceId SpaceId, id string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "spaceId", runtime.ParamLocationPath, spaceId) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "id", runtime.ParamLocationPath, id) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/s/%s/api/maintenance_window/%s", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewPatchMaintenanceWindowIdRequest calls the generic PatchMaintenanceWindowId builder with application/json body +func NewPatchMaintenanceWindowIdRequest(server string, spaceId SpaceId, id string, body PatchMaintenanceWindowIdJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewPatchMaintenanceWindowIdRequestWithBody(server, spaceId, id, "application/json", bodyReader) +} + +// NewPatchMaintenanceWindowIdRequestWithBody generates requests for PatchMaintenanceWindowId with any type of body +func NewPatchMaintenanceWindowIdRequestWithBody(server string, spaceId SpaceId, id string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "spaceId", runtime.ParamLocationPath, spaceId) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "id", runtime.ParamLocationPath, id) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/s/%s/api/maintenance_window/%s", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("PATCH", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { for _, r := range c.RequestEditors { if err := r(ctx, req); err != nil { @@ -21544,6 +21918,22 @@ type ClientWithResponsesInterface interface { UpdateDataViewDefaultWithBodyWithResponse(ctx context.Context, spaceId SpaceId, viewId DataViewsViewId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateDataViewDefaultResponse, error) UpdateDataViewDefaultWithResponse(ctx context.Context, spaceId SpaceId, viewId DataViewsViewId, body UpdateDataViewDefaultJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateDataViewDefaultResponse, error) + + // PostMaintenanceWindowWithBodyWithResponse request with any body + PostMaintenanceWindowWithBodyWithResponse(ctx context.Context, spaceId SpaceId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostMaintenanceWindowResponse, error) + + PostMaintenanceWindowWithResponse(ctx context.Context, spaceId SpaceId, body PostMaintenanceWindowJSONRequestBody, reqEditors ...RequestEditorFn) (*PostMaintenanceWindowResponse, error) + + // DeleteMaintenanceWindowIdWithResponse request + DeleteMaintenanceWindowIdWithResponse(ctx context.Context, spaceId SpaceId, id string, reqEditors ...RequestEditorFn) (*DeleteMaintenanceWindowIdResponse, error) + + // GetMaintenanceWindowIdWithResponse request + GetMaintenanceWindowIdWithResponse(ctx context.Context, spaceId SpaceId, id string, reqEditors ...RequestEditorFn) (*GetMaintenanceWindowIdResponse, error) + + // PatchMaintenanceWindowIdWithBodyWithResponse request with any body + PatchMaintenanceWindowIdWithBodyWithResponse(ctx context.Context, spaceId SpaceId, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PatchMaintenanceWindowIdResponse, error) + + PatchMaintenanceWindowIdWithResponse(ctx context.Context, spaceId SpaceId, id string, body PatchMaintenanceWindowIdJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchMaintenanceWindowIdResponse, error) } type DeleteAgentConfigurationResponse struct { @@ -22927,20 +23317,299 @@ func (c *ClientWithResponses) CreateUpdateAgentConfigurationWithResponse(ctx con return ParseCreateUpdateAgentConfigurationResponse(rsp) } -// GetFleetAgentPoliciesWithResponse request returning *GetFleetAgentPoliciesResponse -func (c *ClientWithResponses) GetFleetAgentPoliciesWithResponse(ctx context.Context, params *GetFleetAgentPoliciesParams, reqEditors ...RequestEditorFn) (*GetFleetAgentPoliciesResponse, error) { - rsp, err := c.GetFleetAgentPolicies(ctx, params, reqEditors...) - if err != nil { - return nil, err - } - return ParseGetFleetAgentPoliciesResponse(rsp) -} +type PostMaintenanceWindowResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *struct { + // CreatedAt The date and time when the maintenance window was created. + CreatedAt string `json:"created_at"` -// PostFleetAgentPoliciesWithBodyWithResponse request with arbitrary body returning *PostFleetAgentPoliciesResponse -func (c *ClientWithResponses) PostFleetAgentPoliciesWithBodyWithResponse(ctx context.Context, params *PostFleetAgentPoliciesParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostFleetAgentPoliciesResponse, error) { - rsp, err := c.PostFleetAgentPoliciesWithBody(ctx, params, contentType, body, reqEditors...) - if err != nil { - return nil, err + // CreatedBy The identifier for the user that created the maintenance window. + CreatedBy *string `json:"created_by"` + + // Enabled Whether the current maintenance window is enabled. Disabled maintenance windows do not suppress notifications. + Enabled bool `json:"enabled"` + + // Id The identifier for the maintenance window. + Id string `json:"id"` + Schedule struct { + Custom struct { + // Duration The duration of the schedule. It allows values in `` format. `` is one of `d`, `h`, `m`, or `s` for hours, minutes, seconds. For example: `1d`, `5h`, `30m`, `5000s`. + Duration string `json:"duration"` + Recurring *struct { + // End The end date of a recurring schedule, provided in ISO 8601 format and set to the UTC timezone. For example: `2025-04-01T00:00:00.000Z`. + End *string `json:"end,omitempty"` + + // Every The interval and frequency of a recurring schedule. It allows values in `` format. `` is one of `d`, `w`, `M`, or `y` for days, weeks, months, years. For example: `15d`, `2w`, `3m`, `1y`. + Every *string `json:"every,omitempty"` + + // Occurrences The total number of recurrences of the schedule. + Occurrences *float32 `json:"occurrences,omitempty"` + + // OnMonth The specific months for a recurring schedule. Valid values are 1-12. + OnMonth *[]float32 `json:"onMonth,omitempty"` + + // OnMonthDay The specific days of the month for a recurring schedule. Valid values are 1-31. + OnMonthDay *[]float32 `json:"onMonthDay,omitempty"` + + // OnWeekDay The specific days of the week (`[MO,TU,WE,TH,FR,SA,SU]`) or nth day of month (`[+1MO, -3FR, +2WE, -4SA, -5SU]`) for a recurring schedule. + OnWeekDay *[]string `json:"onWeekDay,omitempty"` + } `json:"recurring,omitempty"` + + // Start The start date and time of the schedule, provided in ISO 8601 format and set to the UTC timezone. For example: `2025-03-12T12:00:00.000Z`. + Start string `json:"start"` + + // Timezone The timezone of the schedule. The default timezone is UTC. + Timezone *string `json:"timezone,omitempty"` + } `json:"custom"` + } `json:"schedule"` + Scope *struct { + Alerting struct { + Query struct { + // Kql A filter written in Kibana Query Language (KQL). + Kql string `json:"kql"` + } `json:"query"` + } `json:"alerting"` + } `json:"scope,omitempty"` + + // Status The current status of the maintenance window. + Status PostMaintenanceWindow200Status `json:"status"` + + // Title The name of the maintenance window. + Title string `json:"title"` + + // UpdatedAt The date and time when the maintenance window was last updated. + UpdatedAt string `json:"updated_at"` + + // UpdatedBy The identifier for the user that last updated this maintenance window. + UpdatedBy *string `json:"updated_by"` + } +} +type PostMaintenanceWindow200Status string + +// Status returns HTTPResponse.Status +func (r PostMaintenanceWindowResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r PostMaintenanceWindowResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type DeleteMaintenanceWindowIdResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r DeleteMaintenanceWindowIdResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r DeleteMaintenanceWindowIdResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetMaintenanceWindowIdResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *struct { + // CreatedAt The date and time when the maintenance window was created. + CreatedAt string `json:"created_at"` + + // CreatedBy The identifier for the user that created the maintenance window. + CreatedBy *string `json:"created_by"` + + // Enabled Whether the current maintenance window is enabled. Disabled maintenance windows do not suppress notifications. + Enabled bool `json:"enabled"` + + // Id The identifier for the maintenance window. + Id string `json:"id"` + Schedule struct { + Custom struct { + // Duration The duration of the schedule. It allows values in `` format. `` is one of `d`, `h`, `m`, or `s` for hours, minutes, seconds. For example: `1d`, `5h`, `30m`, `5000s`. + Duration string `json:"duration"` + Recurring *struct { + // End The end date of a recurring schedule, provided in ISO 8601 format and set to the UTC timezone. For example: `2025-04-01T00:00:00.000Z`. + End *string `json:"end,omitempty"` + + // Every The interval and frequency of a recurring schedule. It allows values in `` format. `` is one of `d`, `w`, `M`, or `y` for days, weeks, months, years. For example: `15d`, `2w`, `3m`, `1y`. + Every *string `json:"every,omitempty"` + + // Occurrences The total number of recurrences of the schedule. + Occurrences *float32 `json:"occurrences,omitempty"` + + // OnMonth The specific months for a recurring schedule. Valid values are 1-12. + OnMonth *[]float32 `json:"onMonth,omitempty"` + + // OnMonthDay The specific days of the month for a recurring schedule. Valid values are 1-31. + OnMonthDay *[]float32 `json:"onMonthDay,omitempty"` + + // OnWeekDay The specific days of the week (`[MO,TU,WE,TH,FR,SA,SU]`) or nth day of month (`[+1MO, -3FR, +2WE, -4SA, -5SU]`) for a recurring schedule. + OnWeekDay *[]string `json:"onWeekDay,omitempty"` + } `json:"recurring,omitempty"` + + // Start The start date and time of the schedule, provided in ISO 8601 format and set to the UTC timezone. For example: `2025-03-12T12:00:00.000Z`. + Start string `json:"start"` + + // Timezone The timezone of the schedule. The default timezone is UTC. + Timezone *string `json:"timezone,omitempty"` + } `json:"custom"` + } `json:"schedule"` + Scope *struct { + Alerting struct { + Query struct { + // Kql A filter written in Kibana Query Language (KQL). + Kql string `json:"kql"` + } `json:"query"` + } `json:"alerting"` + } `json:"scope,omitempty"` + + // Status The current status of the maintenance window. + Status GetMaintenanceWindowId200Status `json:"status"` + + // Title The name of the maintenance window. + Title string `json:"title"` + + // UpdatedAt The date and time when the maintenance window was last updated. + UpdatedAt string `json:"updated_at"` + + // UpdatedBy The identifier for the user that last updated this maintenance window. + UpdatedBy *string `json:"updated_by"` + } +} +type GetMaintenanceWindowId200Status string + +// Status returns HTTPResponse.Status +func (r GetMaintenanceWindowIdResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetMaintenanceWindowIdResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type PatchMaintenanceWindowIdResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *struct { + // CreatedAt The date and time when the maintenance window was created. + CreatedAt string `json:"created_at"` + + // CreatedBy The identifier for the user that created the maintenance window. + CreatedBy *string `json:"created_by"` + + // Enabled Whether the current maintenance window is enabled. Disabled maintenance windows do not suppress notifications. + Enabled bool `json:"enabled"` + + // Id The identifier for the maintenance window. + Id string `json:"id"` + Schedule struct { + Custom struct { + // Duration The duration of the schedule. It allows values in `` format. `` is one of `d`, `h`, `m`, or `s` for hours, minutes, seconds. For example: `1d`, `5h`, `30m`, `5000s`. + Duration string `json:"duration"` + Recurring *struct { + // End The end date of a recurring schedule, provided in ISO 8601 format and set to the UTC timezone. For example: `2025-04-01T00:00:00.000Z`. + End *string `json:"end,omitempty"` + + // Every The interval and frequency of a recurring schedule. It allows values in `` format. `` is one of `d`, `w`, `M`, or `y` for days, weeks, months, years. For example: `15d`, `2w`, `3m`, `1y`. + Every *string `json:"every,omitempty"` + + // Occurrences The total number of recurrences of the schedule. + Occurrences *float32 `json:"occurrences,omitempty"` + + // OnMonth The specific months for a recurring schedule. Valid values are 1-12. + OnMonth *[]float32 `json:"onMonth,omitempty"` + + // OnMonthDay The specific days of the month for a recurring schedule. Valid values are 1-31. + OnMonthDay *[]float32 `json:"onMonthDay,omitempty"` + + // OnWeekDay The specific days of the week (`[MO,TU,WE,TH,FR,SA,SU]`) or nth day of month (`[+1MO, -3FR, +2WE, -4SA, -5SU]`) for a recurring schedule. + OnWeekDay *[]string `json:"onWeekDay,omitempty"` + } `json:"recurring,omitempty"` + + // Start The start date and time of the schedule, provided in ISO 8601 format and set to the UTC timezone. For example: `2025-03-12T12:00:00.000Z`. + Start string `json:"start"` + + // Timezone The timezone of the schedule. The default timezone is UTC. + Timezone *string `json:"timezone,omitempty"` + } `json:"custom"` + } `json:"schedule"` + Scope *struct { + Alerting struct { + Query struct { + // Kql A filter written in Kibana Query Language (KQL). + Kql string `json:"kql"` + } `json:"query"` + } `json:"alerting"` + } `json:"scope,omitempty"` + + // Status The current status of the maintenance window. + Status PatchMaintenanceWindowId200Status `json:"status"` + + // Title The name of the maintenance window. + Title string `json:"title"` + + // UpdatedAt The date and time when the maintenance window was last updated. + UpdatedAt string `json:"updated_at"` + + // UpdatedBy The identifier for the user that last updated this maintenance window. + UpdatedBy *string `json:"updated_by"` + } +} +type PatchMaintenanceWindowId200Status string + +// Status returns HTTPResponse.Status +func (r PatchMaintenanceWindowIdResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r PatchMaintenanceWindowIdResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// GetFleetAgentPoliciesWithResponse request returning *GetFleetAgentPoliciesResponse +func (c *ClientWithResponses) GetFleetAgentPoliciesWithResponse(ctx context.Context, params *GetFleetAgentPoliciesParams, reqEditors ...RequestEditorFn) (*GetFleetAgentPoliciesResponse, error) { + rsp, err := c.GetFleetAgentPolicies(ctx, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetFleetAgentPoliciesResponse(rsp) +} + +// PostFleetAgentPoliciesWithBodyWithResponse request with arbitrary body returning *PostFleetAgentPoliciesResponse +func (c *ClientWithResponses) PostFleetAgentPoliciesWithBodyWithResponse(ctx context.Context, params *PostFleetAgentPoliciesParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostFleetAgentPoliciesResponse, error) { + rsp, err := c.PostFleetAgentPoliciesWithBody(ctx, params, contentType, body, reqEditors...) + if err != nil { + return nil, err } return ParsePostFleetAgentPoliciesResponse(rsp) } @@ -23570,6 +24239,58 @@ func ParseCreateUpdateAgentConfigurationResponse(rsp *http.Response) (*CreateUpd return response, nil } +// PostMaintenanceWindowWithBodyWithResponse request with arbitrary body returning *PostMaintenanceWindowResponse +func (c *ClientWithResponses) PostMaintenanceWindowWithBodyWithResponse(ctx context.Context, spaceId SpaceId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostMaintenanceWindowResponse, error) { + rsp, err := c.PostMaintenanceWindowWithBody(ctx, spaceId, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParsePostMaintenanceWindowResponse(rsp) +} + +func (c *ClientWithResponses) PostMaintenanceWindowWithResponse(ctx context.Context, spaceId SpaceId, body PostMaintenanceWindowJSONRequestBody, reqEditors ...RequestEditorFn) (*PostMaintenanceWindowResponse, error) { + rsp, err := c.PostMaintenanceWindow(ctx, spaceId, body, reqEditors...) + if err != nil { + return nil, err + } + return ParsePostMaintenanceWindowResponse(rsp) +} + +// DeleteMaintenanceWindowIdWithResponse request returning *DeleteMaintenanceWindowIdResponse +func (c *ClientWithResponses) DeleteMaintenanceWindowIdWithResponse(ctx context.Context, spaceId SpaceId, id string, reqEditors ...RequestEditorFn) (*DeleteMaintenanceWindowIdResponse, error) { + rsp, err := c.DeleteMaintenanceWindowId(ctx, spaceId, id, reqEditors...) + if err != nil { + return nil, err + } + return ParseDeleteMaintenanceWindowIdResponse(rsp) +} + +// GetMaintenanceWindowIdWithResponse request returning *GetMaintenanceWindowIdResponse +func (c *ClientWithResponses) GetMaintenanceWindowIdWithResponse(ctx context.Context, spaceId SpaceId, id string, reqEditors ...RequestEditorFn) (*GetMaintenanceWindowIdResponse, error) { + rsp, err := c.GetMaintenanceWindowId(ctx, spaceId, id, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetMaintenanceWindowIdResponse(rsp) +} + +// PatchMaintenanceWindowIdWithBodyWithResponse request with arbitrary body returning *PatchMaintenanceWindowIdResponse +func (c *ClientWithResponses) PatchMaintenanceWindowIdWithBodyWithResponse(ctx context.Context, spaceId SpaceId, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PatchMaintenanceWindowIdResponse, error) { + rsp, err := c.PatchMaintenanceWindowIdWithBody(ctx, spaceId, id, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParsePatchMaintenanceWindowIdResponse(rsp) +} + +func (c *ClientWithResponses) PatchMaintenanceWindowIdWithResponse(ctx context.Context, spaceId SpaceId, id string, body PatchMaintenanceWindowIdJSONRequestBody, reqEditors ...RequestEditorFn) (*PatchMaintenanceWindowIdResponse, error) { + rsp, err := c.PatchMaintenanceWindowId(ctx, spaceId, id, body, reqEditors...) + if err != nil { + return nil, err + } + return ParsePatchMaintenanceWindowIdResponse(rsp) +} + // ParseGetFleetAgentPoliciesResponse parses an HTTP response from a GetFleetAgentPoliciesWithResponse call func ParseGetFleetAgentPoliciesResponse(rsp *http.Response) (*GetFleetAgentPoliciesResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -25121,3 +25842,286 @@ func ParseUpdateDataViewDefaultResponse(rsp *http.Response) (*UpdateDataViewDefa return response, nil } + +// ParsePostMaintenanceWindowResponse parses an HTTP response from a PostMaintenanceWindowWithResponse call +func ParsePostMaintenanceWindowResponse(rsp *http.Response) (*PostMaintenanceWindowResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &PostMaintenanceWindowResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest struct { + // CreatedAt The date and time when the maintenance window was created. + CreatedAt string `json:"created_at"` + + // CreatedBy The identifier for the user that created the maintenance window. + CreatedBy *string `json:"created_by"` + + // Enabled Whether the current maintenance window is enabled. Disabled maintenance windows do not suppress notifications. + Enabled bool `json:"enabled"` + + // Id The identifier for the maintenance window. + Id string `json:"id"` + Schedule struct { + Custom struct { + // Duration The duration of the schedule. It allows values in `` format. `` is one of `d`, `h`, `m`, or `s` for hours, minutes, seconds. For example: `1d`, `5h`, `30m`, `5000s`. + Duration string `json:"duration"` + Recurring *struct { + // End The end date of a recurring schedule, provided in ISO 8601 format and set to the UTC timezone. For example: `2025-04-01T00:00:00.000Z`. + End *string `json:"end,omitempty"` + + // Every The interval and frequency of a recurring schedule. It allows values in `` format. `` is one of `d`, `w`, `M`, or `y` for days, weeks, months, years. For example: `15d`, `2w`, `3m`, `1y`. + Every *string `json:"every,omitempty"` + + // Occurrences The total number of recurrences of the schedule. + Occurrences *float32 `json:"occurrences,omitempty"` + + // OnMonth The specific months for a recurring schedule. Valid values are 1-12. + OnMonth *[]float32 `json:"onMonth,omitempty"` + + // OnMonthDay The specific days of the month for a recurring schedule. Valid values are 1-31. + OnMonthDay *[]float32 `json:"onMonthDay,omitempty"` + + // OnWeekDay The specific days of the week (`[MO,TU,WE,TH,FR,SA,SU]`) or nth day of month (`[+1MO, -3FR, +2WE, -4SA, -5SU]`) for a recurring schedule. + OnWeekDay *[]string `json:"onWeekDay,omitempty"` + } `json:"recurring,omitempty"` + + // Start The start date and time of the schedule, provided in ISO 8601 format and set to the UTC timezone. For example: `2025-03-12T12:00:00.000Z`. + Start string `json:"start"` + + // Timezone The timezone of the schedule. The default timezone is UTC. + Timezone *string `json:"timezone,omitempty"` + } `json:"custom"` + } `json:"schedule"` + Scope *struct { + Alerting struct { + Query struct { + // Kql A filter written in Kibana Query Language (KQL). + Kql string `json:"kql"` + } `json:"query"` + } `json:"alerting"` + } `json:"scope,omitempty"` + + // Status The current status of the maintenance window. + Status PostMaintenanceWindow200Status `json:"status"` + + // Title The name of the maintenance window. + Title string `json:"title"` + + // UpdatedAt The date and time when the maintenance window was last updated. + UpdatedAt string `json:"updated_at"` + + // UpdatedBy The identifier for the user that last updated this maintenance window. + UpdatedBy *string `json:"updated_by"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseDeleteMaintenanceWindowIdResponse parses an HTTP response from a DeleteMaintenanceWindowIdWithResponse call +func ParseDeleteMaintenanceWindowIdResponse(rsp *http.Response) (*DeleteMaintenanceWindowIdResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &DeleteMaintenanceWindowIdResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseGetMaintenanceWindowIdResponse parses an HTTP response from a GetMaintenanceWindowIdWithResponse call +func ParseGetMaintenanceWindowIdResponse(rsp *http.Response) (*GetMaintenanceWindowIdResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetMaintenanceWindowIdResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest struct { + // CreatedAt The date and time when the maintenance window was created. + CreatedAt string `json:"created_at"` + + // CreatedBy The identifier for the user that created the maintenance window. + CreatedBy *string `json:"created_by"` + + // Enabled Whether the current maintenance window is enabled. Disabled maintenance windows do not suppress notifications. + Enabled bool `json:"enabled"` + + // Id The identifier for the maintenance window. + Id string `json:"id"` + Schedule struct { + Custom struct { + // Duration The duration of the schedule. It allows values in `` format. `` is one of `d`, `h`, `m`, or `s` for hours, minutes, seconds. For example: `1d`, `5h`, `30m`, `5000s`. + Duration string `json:"duration"` + Recurring *struct { + // End The end date of a recurring schedule, provided in ISO 8601 format and set to the UTC timezone. For example: `2025-04-01T00:00:00.000Z`. + End *string `json:"end,omitempty"` + + // Every The interval and frequency of a recurring schedule. It allows values in `` format. `` is one of `d`, `w`, `M`, or `y` for days, weeks, months, years. For example: `15d`, `2w`, `3m`, `1y`. + Every *string `json:"every,omitempty"` + + // Occurrences The total number of recurrences of the schedule. + Occurrences *float32 `json:"occurrences,omitempty"` + + // OnMonth The specific months for a recurring schedule. Valid values are 1-12. + OnMonth *[]float32 `json:"onMonth,omitempty"` + + // OnMonthDay The specific days of the month for a recurring schedule. Valid values are 1-31. + OnMonthDay *[]float32 `json:"onMonthDay,omitempty"` + + // OnWeekDay The specific days of the week (`[MO,TU,WE,TH,FR,SA,SU]`) or nth day of month (`[+1MO, -3FR, +2WE, -4SA, -5SU]`) for a recurring schedule. + OnWeekDay *[]string `json:"onWeekDay,omitempty"` + } `json:"recurring,omitempty"` + + // Start The start date and time of the schedule, provided in ISO 8601 format and set to the UTC timezone. For example: `2025-03-12T12:00:00.000Z`. + Start string `json:"start"` + + // Timezone The timezone of the schedule. The default timezone is UTC. + Timezone *string `json:"timezone,omitempty"` + } `json:"custom"` + } `json:"schedule"` + Scope *struct { + Alerting struct { + Query struct { + // Kql A filter written in Kibana Query Language (KQL). + Kql string `json:"kql"` + } `json:"query"` + } `json:"alerting"` + } `json:"scope,omitempty"` + + // Status The current status of the maintenance window. + Status GetMaintenanceWindowId200Status `json:"status"` + + // Title The name of the maintenance window. + Title string `json:"title"` + + // UpdatedAt The date and time when the maintenance window was last updated. + UpdatedAt string `json:"updated_at"` + + // UpdatedBy The identifier for the user that last updated this maintenance window. + UpdatedBy *string `json:"updated_by"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParsePatchMaintenanceWindowIdResponse parses an HTTP response from a PatchMaintenanceWindowIdWithResponse call +func ParsePatchMaintenanceWindowIdResponse(rsp *http.Response) (*PatchMaintenanceWindowIdResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &PatchMaintenanceWindowIdResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest struct { + // CreatedAt The date and time when the maintenance window was created. + CreatedAt string `json:"created_at"` + + // CreatedBy The identifier for the user that created the maintenance window. + CreatedBy *string `json:"created_by"` + + // Enabled Whether the current maintenance window is enabled. Disabled maintenance windows do not suppress notifications. + Enabled bool `json:"enabled"` + + // Id The identifier for the maintenance window. + Id string `json:"id"` + Schedule struct { + Custom struct { + // Duration The duration of the schedule. It allows values in `` format. `` is one of `d`, `h`, `m`, or `s` for hours, minutes, seconds. For example: `1d`, `5h`, `30m`, `5000s`. + Duration string `json:"duration"` + Recurring *struct { + // End The end date of a recurring schedule, provided in ISO 8601 format and set to the UTC timezone. For example: `2025-04-01T00:00:00.000Z`. + End *string `json:"end,omitempty"` + + // Every The interval and frequency of a recurring schedule. It allows values in `` format. `` is one of `d`, `w`, `M`, or `y` for days, weeks, months, years. For example: `15d`, `2w`, `3m`, `1y`. + Every *string `json:"every,omitempty"` + + // Occurrences The total number of recurrences of the schedule. + Occurrences *float32 `json:"occurrences,omitempty"` + + // OnMonth The specific months for a recurring schedule. Valid values are 1-12. + OnMonth *[]float32 `json:"onMonth,omitempty"` + + // OnMonthDay The specific days of the month for a recurring schedule. Valid values are 1-31. + OnMonthDay *[]float32 `json:"onMonthDay,omitempty"` + + // OnWeekDay The specific days of the week (`[MO,TU,WE,TH,FR,SA,SU]`) or nth day of month (`[+1MO, -3FR, +2WE, -4SA, -5SU]`) for a recurring schedule. + OnWeekDay *[]string `json:"onWeekDay,omitempty"` + } `json:"recurring,omitempty"` + + // Start The start date and time of the schedule, provided in ISO 8601 format and set to the UTC timezone. For example: `2025-03-12T12:00:00.000Z`. + Start string `json:"start"` + + // Timezone The timezone of the schedule. The default timezone is UTC. + Timezone *string `json:"timezone,omitempty"` + } `json:"custom"` + } `json:"schedule"` + Scope *struct { + Alerting struct { + Query struct { + // Kql A filter written in Kibana Query Language (KQL). + Kql string `json:"kql"` + } `json:"query"` + } `json:"alerting"` + } `json:"scope,omitempty"` + + // Status The current status of the maintenance window. + Status PatchMaintenanceWindowId200Status `json:"status"` + + // Title The name of the maintenance window. + Title string `json:"title"` + + // UpdatedAt The date and time when the maintenance window was last updated. + UpdatedAt string `json:"updated_at"` + + // UpdatedBy The identifier for the user that last updated this maintenance window. + UpdatedBy *string `json:"updated_by"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} diff --git a/generated/kbapi/transform_schema.go b/generated/kbapi/transform_schema.go index 17d86fb24..1e6af5f1d 100644 --- a/generated/kbapi/transform_schema.go +++ b/generated/kbapi/transform_schema.go @@ -108,6 +108,7 @@ func (s Schema) MustGetPath(path string) *Path { type Path struct { Parameters []Map `yaml:"parameters,omitempty"` Get Map `yaml:"get,omitempty"` + Patch Map `yaml:"patch,omitempty"` Post Map `yaml:"post,omitempty"` Put Map `yaml:"put,omitempty"` Delete Map `yaml:"delete,omitempty"` @@ -123,6 +124,9 @@ func (p Path) Endpoints(yield func(key string, endpoint Map) bool) { if p.Put != nil { yield("put", p.Put) } + if p.Patch != nil { + yield("patch", p.Patch) + } if p.Delete != nil { yield("delete", p.Delete) } @@ -136,6 +140,8 @@ func (p Path) GetEndpoint(method string) Map { return p.Post case "put": return p.Put + case "patch": + return p.Patch case "delete": return p.Delete default: @@ -160,6 +166,8 @@ func (p *Path) SetEndpoint(method string, endpoint Map) { p.Post = endpoint case "put": p.Put = endpoint + case "patch": + p.Patch = endpoint case "delete": p.Delete = endpoint default: @@ -572,6 +580,8 @@ func transformFilterPaths(schema *Schema) { "/api/synthetics/params": {"post"}, "/api/synthetics/params/{id}": {"get", "put", "delete"}, "/api/apm/settings/agent-configuration": {"get", "put", "delete"}, + "/api/maintenance_window": {"post"}, + "/api/maintenance_window/{id}": {"delete", "get", "patch"}, "/api/actions/connector/{id}": {"get", "put", "post", "delete"}, "/api/actions/connectors": {"get"}, } @@ -720,6 +730,8 @@ func transformKibanaPaths(schema *Schema) { "/api/data_views", "/api/data_views/data_view", "/api/data_views/data_view/{viewId}", + "/api/maintenance_window", + "/api/maintenance_window/{id}", "/api/actions/connector/{id}", "/api/actions/connectors", } diff --git a/internal/clients/kibana_oapi/maintenance_window.go b/internal/clients/kibana_oapi/maintenance_window.go new file mode 100644 index 000000000..8babf2c92 --- /dev/null +++ b/internal/clients/kibana_oapi/maintenance_window.go @@ -0,0 +1,73 @@ +package kibana_oapi + +import ( + "context" + "net/http" + + "github.com/elastic/terraform-provider-elasticstack/generated/kbapi" + "github.com/elastic/terraform-provider-elasticstack/internal/utils" + "github.com/hashicorp/terraform-plugin-framework/diag" +) + +// GetMaintenanceWindow reads a maintenance window from the API by ID +func GetMaintenanceWindow(ctx context.Context, client *Client, spaceID string, maintenanceWindowID string) (*kbapi.GetMaintenanceWindowIdResponse, diag.Diagnostics) { + resp, err := client.API.GetMaintenanceWindowIdWithResponse(ctx, spaceID, maintenanceWindowID) + + if err != nil { + return nil, utils.FrameworkDiagFromError(err) + } + + switch resp.StatusCode() { + case http.StatusOK: + return resp, nil + default: + return nil, reportUnknownError(resp.StatusCode(), resp.Body) + } +} + +// CreateMaintenanceWindow creates a new maintenance window. +func CreateMaintenanceWindow(ctx context.Context, client *Client, spaceID string, body kbapi.PostMaintenanceWindowJSONRequestBody) (*kbapi.PostMaintenanceWindowResponse, diag.Diagnostics) { + resp, err := client.API.PostMaintenanceWindowWithResponse(ctx, spaceID, body) + if err != nil { + return nil, utils.FrameworkDiagFromError(err) + } + + switch resp.StatusCode() { + case http.StatusOK: + return resp, nil + default: + return nil, reportUnknownError(resp.StatusCode(), resp.Body) + } +} + +// UpdateMaintenanceWindow updates an existing maintenance window. +func UpdateMaintenanceWindow(ctx context.Context, client *Client, spaceID string, maintenanceWindowID string, req kbapi.PatchMaintenanceWindowIdJSONRequestBody) diag.Diagnostics { + resp, err := client.API.PatchMaintenanceWindowIdWithResponse(ctx, spaceID, maintenanceWindowID, req) + if err != nil { + return utils.FrameworkDiagFromError(err) + } + + switch resp.StatusCode() { + case http.StatusOK: + return nil + default: + return reportUnknownError(resp.StatusCode(), resp.Body) + } +} + +// DeleteMaintenanceWindow deletes an existing maintenance window. +func DeleteMaintenanceWindow(ctx context.Context, client *Client, spaceID string, maintenanceWindowID string) diag.Diagnostics { + resp, err := client.API.DeleteMaintenanceWindowIdWithResponse(ctx, spaceID, maintenanceWindowID) + if err != nil { + return utils.FrameworkDiagFromError(err) + } + + switch resp.StatusCode() { + case http.StatusNoContent: + return nil + case http.StatusNotFound: + return nil + default: + return reportUnknownError(resp.StatusCode(), resp.Body) + } +} diff --git a/internal/kibana/alerting.go b/internal/kibana/alerting.go index ab1b83960..b6e5fac13 100644 --- a/internal/kibana/alerting.go +++ b/internal/kibana/alerting.go @@ -4,11 +4,11 @@ import ( "context" "encoding/json" "fmt" - "regexp" "strings" "github.com/elastic/terraform-provider-elasticstack/internal/clients" "github.com/elastic/terraform-provider-elasticstack/internal/clients/kibana" + "github.com/elastic/terraform-provider-elasticstack/internal/kibana/validators" "github.com/elastic/terraform-provider-elasticstack/internal/models" "github.com/elastic/terraform-provider-elasticstack/internal/utils" "github.com/hashicorp/go-version" @@ -22,14 +22,6 @@ var frequencyMinSupportedVersion = version.Must(version.NewVersion("8.6.0")) var alertsFilterMinSupportedVersion = version.Must(version.NewVersion("8.9.0")) var alertDelayMinSupportedVersion = version.Must(version.NewVersion("8.13.0")) -// Avoid lint error on deprecated SchemaValidateFunc usage. -// -//nolint:staticcheck -func stringIsAlertingDuration() schema.SchemaValidateFunc { - r := regexp.MustCompile(`^[1-9][0-9]*(?:d|h|m|s)$`) - return validation.StringMatch(r, "string is not a valid Alerting duration in seconds (s), minutes (m), hours (h), or days (d)") -} - func ResourceAlertingRule() *schema.Resource { apikeySchema := map[string]*schema.Schema{ "rule_id": { @@ -80,7 +72,7 @@ func ResourceAlertingRule() *schema.Resource { Description: "The check interval, which specifies how frequently the rule conditions are checked. The interval must be specified in seconds, minutes, hours or days.", Type: schema.TypeString, Required: true, - ValidateFunc: stringIsAlertingDuration(), + ValidateFunc: validators.StringIsAlertingDurationSDKV2(), }, "actions": { Description: "An action that runs under defined conditions.", @@ -129,7 +121,7 @@ func ResourceAlertingRule() *schema.Resource { Description: "Defines how often an alert generates repeated actions. This custom action interval must be specified in seconds, minutes, hours, or days. For example, 10m or 1h. This property is applicable only if `notify_when` is `onThrottleInterval`. NOTE: This is a rule level property; if you update the rule in Kibana, it is automatically changed to use action-specific `throttle` values.", Type: schema.TypeString, Optional: true, - ValidateFunc: stringIsAlertingDuration(), + ValidateFunc: validators.StringIsAlertingDurationSDKV2(), }, }, }, @@ -207,7 +199,7 @@ func ResourceAlertingRule() *schema.Resource { Description: "Deprecated in 8.13.0. Defines how often an alert generates repeated actions. This custom action interval must be specified in seconds, minutes, hours, or days. For example, 10m or 1h. This property is applicable only if `notify_when` is `onThrottleInterval`. NOTE: This is a rule level property; if you update the rule in Kibana, it is automatically changed to use action-specific `throttle` values.", Type: schema.TypeString, Optional: true, - ValidateFunc: stringIsAlertingDuration(), + ValidateFunc: validators.StringIsAlertingDurationSDKV2(), }, "scheduled_task_id": { Description: "ID of the scheduled task that will execute the alert.", diff --git a/internal/kibana/maintenance_window/acc_test.go b/internal/kibana/maintenance_window/acc_test.go new file mode 100644 index 000000000..c68db0765 --- /dev/null +++ b/internal/kibana/maintenance_window/acc_test.go @@ -0,0 +1,116 @@ +package maintenance_window_test + +import ( + "testing" + + "github.com/elastic/terraform-provider-elasticstack/internal/acctest" + "github.com/elastic/terraform-provider-elasticstack/internal/versionutils" + "github.com/hashicorp/go-version" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +var minMaintenanceWindowAPISupport = version.Must(version.NewVersion("9.1.0")) + +func TestAccResourceMaintenanceWindow(t *testing.T) { + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProtoV6ProviderFactories: acctest.Providers, + Steps: []resource.TestStep{ + { + SkipFunc: versionutils.CheckIfVersionIsUnsupported(minMaintenanceWindowAPISupport), + Config: testAccResourceMaintenanceWindowCreate, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("elasticstack_kibana_maintenance_window.test_maintenance_window", "title", "Terraform Maintenance Window"), + resource.TestCheckResourceAttr("elasticstack_kibana_maintenance_window.test_maintenance_window", "enabled", "true"), + resource.TestCheckResourceAttr("elasticstack_kibana_maintenance_window.test_maintenance_window", "custom_schedule.start", "1992-01-01T05:00:00.200Z"), + resource.TestCheckResourceAttr("elasticstack_kibana_maintenance_window.test_maintenance_window", "custom_schedule.duration", "10d"), + resource.TestCheckResourceAttr("elasticstack_kibana_maintenance_window.test_maintenance_window", "custom_schedule.timezone", "UTC"), + resource.TestCheckResourceAttr("elasticstack_kibana_maintenance_window.test_maintenance_window", "custom_schedule.recurring.every", "20d"), + resource.TestCheckResourceAttr("elasticstack_kibana_maintenance_window.test_maintenance_window", "custom_schedule.recurring.end", "2029-05-17T05:05:00.000Z"), + resource.TestCheckResourceAttr("elasticstack_kibana_maintenance_window.test_maintenance_window", "custom_schedule.recurring.on_week_day.0", "MO"), + resource.TestCheckResourceAttr("elasticstack_kibana_maintenance_window.test_maintenance_window", "custom_schedule.recurring.on_week_day.1", "TU"), + resource.TestCheckResourceAttr("elasticstack_kibana_maintenance_window.test_maintenance_window", "scope.alerting.kql", "_id: '1234'"), + ), + }, + { + SkipFunc: versionutils.CheckIfVersionIsUnsupported(minMaintenanceWindowAPISupport), + Config: testAccResourceMaintenanceWindowUpdate, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("elasticstack_kibana_maintenance_window.test_maintenance_window", "title", "Terraform Maintenance Window UPDATED"), + resource.TestCheckResourceAttr("elasticstack_kibana_maintenance_window.test_maintenance_window", "enabled", "false"), + resource.TestCheckResourceAttr("elasticstack_kibana_maintenance_window.test_maintenance_window", "custom_schedule.start", "1999-02-02T05:00:00.200Z"), + resource.TestCheckResourceAttr("elasticstack_kibana_maintenance_window.test_maintenance_window", "custom_schedule.duration", "12d"), + resource.TestCheckResourceAttr("elasticstack_kibana_maintenance_window.test_maintenance_window", "custom_schedule.timezone", "Asia/Taipei"), + resource.TestCheckResourceAttr("elasticstack_kibana_maintenance_window.test_maintenance_window", "custom_schedule.recurring.every", "21d"), + resource.TestCheckResourceAttr("elasticstack_kibana_maintenance_window.test_maintenance_window", "custom_schedule.recurring.on_month_day.0", "1"), + resource.TestCheckResourceAttr("elasticstack_kibana_maintenance_window.test_maintenance_window", "custom_schedule.recurring.on_month_day.1", "2"), + resource.TestCheckResourceAttr("elasticstack_kibana_maintenance_window.test_maintenance_window", "custom_schedule.recurring.on_month_day.2", "3"), + resource.TestCheckResourceAttr("elasticstack_kibana_maintenance_window.test_maintenance_window", "custom_schedule.recurring.on_month.0", "4"), + resource.TestCheckResourceAttr("elasticstack_kibana_maintenance_window.test_maintenance_window", "custom_schedule.recurring.on_month.1", "5"), + resource.TestCheckResourceAttr("elasticstack_kibana_maintenance_window.test_maintenance_window", "scope.alerting.kql", "_id: 'foobar'"), + ), + }, + }, + }) +} + +const testAccResourceMaintenanceWindowCreate = ` +provider "elasticstack" { + elasticsearch {} + kibana {} +} + +resource "elasticstack_kibana_maintenance_window" "test_maintenance_window" { + title = "Terraform Maintenance Window" + enabled = true + + custom_schedule = { + start = "1992-01-01T05:00:00.200Z" + duration = "10d" + timezone = "UTC" + + recurring = { + every = "20d" + end = "2029-05-17T05:05:00.000Z" + on_week_day = ["MO", "TU"] + } + } + + scope = { + alerting = { + kql = "_id: '1234'" + } + } +} +` + +const testAccResourceMaintenanceWindowUpdate = ` +provider "elasticstack" { + elasticsearch {} + kibana {} +} + +resource "elasticstack_kibana_maintenance_window" "test_maintenance_window" { + title = "Terraform Maintenance Window UPDATED" + enabled = false + + custom_schedule = { + start = "1999-02-02T05:00:00.200Z" + duration = "12d" + timezone = "Asia/Taipei" + + recurring = { + every = "21d" + on_month_day = [1, 2, 3] + on_month = [4, 5] + } + } + + scope = { + alerting = { + kql = "_id: 'foobar'" + } + } +} +` diff --git a/internal/kibana/maintenance_window/create.go b/internal/kibana/maintenance_window/create.go new file mode 100644 index 000000000..a38a14a3c --- /dev/null +++ b/internal/kibana/maintenance_window/create.go @@ -0,0 +1,83 @@ +package maintenance_window + +import ( + "context" + + "github.com/elastic/terraform-provider-elasticstack/internal/clients/kibana_oapi" + "github.com/elastic/terraform-provider-elasticstack/internal/utils" + "github.com/hashicorp/go-version" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func (r *MaintenanceWindowResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + // Retrieve values from plan + var planMaintenanceWindow MaintenanceWindowModel + + diags := req.Plan.Get(ctx, &planMaintenanceWindow) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Generate API request body from plan + body, diags := planMaintenanceWindow.toAPICreateRequest(ctx) + + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + isSupported, sdkDiags := r.client.EnforceMinVersion(ctx, version.Must(version.NewVersion("9.1.0"))) + resp.Diagnostics.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) + if resp.Diagnostics.HasError() { + return + } + + if !isSupported { + resp.Diagnostics.AddError("Unsupported server version", "Maintenance windows are not supported until Elastic Stack v9.0. Upgrade the target server to use this resource") + return + } + + client, err := r.client.GetKibanaOapiClient() + if err != nil { + resp.Diagnostics.AddError(err.Error(), "") + return + } + + spaceID := planMaintenanceWindow.SpaceID.ValueString() + createMaintenanceWindowResponse, diags := kibana_oapi.CreateMaintenanceWindow(ctx, client, spaceID, body) + + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + /* + * In create/update paths we typically follow the write operation with a read, and then set the state from the read. + * We want to avoid a dirty plan immediately after an apply. + */ + maintenanceWindowID := createMaintenanceWindowResponse.JSON200.Id + readMaintenanceWindowResponse, diags := kibana_oapi.GetMaintenanceWindow(ctx, client, spaceID, maintenanceWindowID) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + if readMaintenanceWindowResponse == nil { + resp.State.RemoveResource(ctx) + return + } + + diags = planMaintenanceWindow.fromAPIReadResponse(ctx, readMaintenanceWindowResponse) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + planMaintenanceWindow.ID = types.StringValue(maintenanceWindowID) + planMaintenanceWindow.SpaceID = types.StringValue(spaceID) + + diags = resp.State.Set(ctx, planMaintenanceWindow) + resp.Diagnostics.Append(diags...) +} diff --git a/internal/kibana/maintenance_window/delete.go b/internal/kibana/maintenance_window/delete.go new file mode 100644 index 000000000..52228ee62 --- /dev/null +++ b/internal/kibana/maintenance_window/delete.go @@ -0,0 +1,28 @@ +package maintenance_window + +import ( + "context" + + "github.com/elastic/terraform-provider-elasticstack/internal/clients/kibana_oapi" + "github.com/hashicorp/terraform-plugin-framework/resource" +) + +func (r *MaintenanceWindowResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var stateModel MaintenanceWindowModel + + diags := req.State.Get(ctx, &stateModel) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + client, err := r.client.GetKibanaOapiClient() + if err != nil { + resp.Diagnostics.AddError(err.Error(), "") + return + } + + maintenanceWindowID, spaceID := stateModel.getMaintenanceWindowIDAndSpaceID() + diags = kibana_oapi.DeleteMaintenanceWindow(ctx, client, spaceID, maintenanceWindowID) + resp.Diagnostics.Append(diags...) +} diff --git a/internal/kibana/maintenance_window/models.go b/internal/kibana/maintenance_window/models.go new file mode 100644 index 000000000..22dff578f --- /dev/null +++ b/internal/kibana/maintenance_window/models.go @@ -0,0 +1,315 @@ +package maintenance_window + +import ( + "context" + "encoding/json" + + "github.com/elastic/terraform-provider-elasticstack/generated/kbapi" + "github.com/elastic/terraform-provider-elasticstack/internal/clients" + "github.com/elastic/terraform-provider-elasticstack/internal/utils" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +type MaintenanceWindowModel struct { + ID types.String `tfsdk:"id"` + SpaceID types.String `tfsdk:"space_id"` + Title types.String `tfsdk:"title"` + Enabled types.Bool `tfsdk:"enabled"` + CustomSchedule MaintenanceWindowSchedule `tfsdk:"custom_schedule"` + Scope *MaintenanceWindowScope `tfsdk:"scope"` +} + +type MaintenanceWindowScope struct { + Alerting MaintenanceWindowAlertingScope `tfsdk:"alerting"` +} + +type MaintenanceWindowAlertingScope struct { + Kql types.String `tfsdk:"kql"` +} + +type MaintenanceWindowSchedule struct { + Start types.String `tfsdk:"start"` + Duration types.String `tfsdk:"duration"` + Timezone types.String `tfsdk:"timezone"` + Recurring *MaintenanceWindowScheduleRecurring `tfsdk:"recurring"` +} + +type MaintenanceWindowScheduleRecurring struct { + End types.String `tfsdk:"end"` + Every types.String `tfsdk:"every"` + Occurrences types.Int32 `tfsdk:"occurrences"` + OnWeekDay types.List `tfsdk:"on_week_day"` + OnMonthDay types.List `tfsdk:"on_month_day"` + OnMonth types.List `tfsdk:"on_month"` +} + +/* CREATE */ + +func (model MaintenanceWindowModel) toAPICreateRequest(ctx context.Context) (kbapi.PostMaintenanceWindowJSONRequestBody, diag.Diagnostics) { + body := kbapi.PostMaintenanceWindowJSONRequestBody{ + Enabled: model.Enabled.ValueBoolPointer(), + Title: model.Title.ValueString(), + } + + body.Schedule.Custom.Duration = model.CustomSchedule.Duration.ValueString() + body.Schedule.Custom.Start = model.CustomSchedule.Start.ValueString() + + if !model.CustomSchedule.Timezone.IsNull() && !model.CustomSchedule.Timezone.IsUnknown() { + body.Schedule.Custom.Timezone = model.CustomSchedule.Timezone.ValueStringPointer() + } + + customRecurring, diags := model.CustomSchedule.Recurring.toAPIRequest(ctx) + body.Schedule.Custom.Recurring = customRecurring + body.Scope = model.Scope.toAPIRequest() + + return body, diags +} + +/* READ */ + +func (model *MaintenanceWindowModel) fromAPIReadResponse(ctx context.Context, data *kbapi.GetMaintenanceWindowIdResponse) diag.Diagnostics { + if data == nil { + return nil + } + + var diags = diag.Diagnostics{} + var response = &ResponseJson{} + + if err := json.Unmarshal(data.Body, response); err != nil { + diags.AddError(err.Error(), "cannot unmarshal GetMaintenanceWindowIdResponse") + return diags + } + + return model._fromAPIResponse(ctx, *response) +} + +/* UPDATE */ + +func (model MaintenanceWindowModel) toAPIUpdateRequest(ctx context.Context) (kbapi.PatchMaintenanceWindowIdJSONRequestBody, diag.Diagnostics) { + body := kbapi.PatchMaintenanceWindowIdJSONRequestBody{ + Enabled: model.Enabled.ValueBoolPointer(), + Title: model.Title.ValueStringPointer(), + } + + body.Schedule = &struct { + Custom struct { + Duration string `json:"duration"` + Recurring *struct { + End *string `json:"end,omitempty"` + Every *string `json:"every,omitempty"` + Occurrences *float32 `json:"occurrences,omitempty"` + OnMonth *[]float32 `json:"onMonth,omitempty"` + OnMonthDay *[]float32 `json:"onMonthDay,omitempty"` + OnWeekDay *[]string `json:"onWeekDay,omitempty"` + } `json:"recurring,omitempty"` + Start string `json:"start"` + Timezone *string `json:"timezone,omitempty"` + } `json:"custom"` + }{ + Custom: struct { + Duration string `json:"duration"` + Recurring *struct { + End *string `json:"end,omitempty"` + Every *string `json:"every,omitempty"` + Occurrences *float32 `json:"occurrences,omitempty"` + OnMonth *[]float32 `json:"onMonth,omitempty"` + OnMonthDay *[]float32 `json:"onMonthDay,omitempty"` + OnWeekDay *[]string `json:"onWeekDay,omitempty"` + } `json:"recurring,omitempty"` + Start string `json:"start"` + Timezone *string `json:"timezone,omitempty"` + }{ + Duration: model.CustomSchedule.Duration.ValueString(), + Start: model.CustomSchedule.Start.ValueString(), + }, + } + + if utils.IsKnown(model.CustomSchedule.Timezone) { + body.Schedule.Custom.Timezone = model.CustomSchedule.Timezone.ValueStringPointer() + } + + customRecurring, diags := model.CustomSchedule.Recurring.toAPIRequest(ctx) + body.Schedule.Custom.Recurring = customRecurring + body.Scope = model.Scope.toAPIRequest() + + return body, diags +} + +/* DELETE */ + +func (model MaintenanceWindowModel) getMaintenanceWindowIDAndSpaceID() (maintenanceWindowID string, spaceID string) { + maintenanceWindowID = model.ID.ValueString() + spaceID = model.SpaceID.ValueString() + + resourceID := model.ID.ValueString() + maybeCompositeID, _ := clients.CompositeIdFromStr(resourceID) + if maybeCompositeID != nil { + maintenanceWindowID = maybeCompositeID.ResourceId + spaceID = maybeCompositeID.ClusterId + } + + return +} + +/* RESPONSE HANDLER */ + +func (model *MaintenanceWindowModel) _fromAPIResponse(ctx context.Context, response ResponseJson) diag.Diagnostics { + var diags = diag.Diagnostics{} + + model.Title = types.StringValue(response.Title) + model.Enabled = types.BoolValue(response.Enabled) + + model.CustomSchedule = MaintenanceWindowSchedule{ + Start: types.StringValue(response.Schedule.Custom.Start), + Duration: types.StringValue(response.Schedule.Custom.Duration), + Timezone: types.StringPointerValue(response.Schedule.Custom.Timezone), + Recurring: &MaintenanceWindowScheduleRecurring{ + End: types.StringNull(), + Every: types.StringNull(), + OnWeekDay: types.ListNull(types.StringType), + OnMonth: types.ListNull(types.Int32Type), + OnMonthDay: types.ListNull(types.Int32Type), + }, + } + + if response.Schedule.Custom.Recurring != nil { + model.CustomSchedule.Recurring.End = types.StringPointerValue(response.Schedule.Custom.Recurring.End) + model.CustomSchedule.Recurring.Every = types.StringPointerValue(response.Schedule.Custom.Recurring.Every) + + if response.Schedule.Custom.Recurring.Occurrences != nil { + occurrences := int32(*response.Schedule.Custom.Recurring.Occurrences) + model.CustomSchedule.Recurring.Occurrences = types.Int32PointerValue(&occurrences) + } + + if response.Schedule.Custom.Recurring.OnWeekDay != nil { + onWeekDay, d := types.ListValueFrom(ctx, types.StringType, response.Schedule.Custom.Recurring.OnWeekDay) + + if d.HasError() { + diags.Append(d...) + } else { + model.CustomSchedule.Recurring.OnWeekDay = onWeekDay + } + } + + if response.Schedule.Custom.Recurring.OnMonth != nil { + onMonth, d := types.ListValueFrom(ctx, types.Int32Type, response.Schedule.Custom.Recurring.OnMonth) + + if d.HasError() { + diags.Append(d...) + } else { + model.CustomSchedule.Recurring.OnMonth = onMonth + } + } + + if response.Schedule.Custom.Recurring.OnMonthDay != nil { + onMonthDay, d := types.ListValueFrom(ctx, types.Int32Type, response.Schedule.Custom.Recurring.OnMonthDay) + + if d.HasError() { + diags.Append(d...) + } else { + model.CustomSchedule.Recurring.OnMonthDay = onMonthDay + } + } + } + + if response.Scope != nil { + model.Scope = &MaintenanceWindowScope{ + Alerting: MaintenanceWindowAlertingScope{ + Kql: types.StringValue(response.Scope.Alerting.Query.Kql), + }, + } + } + + return diags +} + +/* HELPERS */ + +func (model *MaintenanceWindowScope) toAPIRequest() *struct { + Alerting struct { + Query struct { + Kql string `json:"kql"` + } `json:"query"` + } `json:"alerting"` +} { + if model == nil { + return nil + } + + return &struct { + Alerting struct { + Query struct { + Kql string `json:"kql"` + } `json:"query"` + } `json:"alerting"` + }{ + Alerting: struct { + Query struct { + Kql string `json:"kql"` + } `json:"query"` + }{ + Query: struct { + Kql string `json:"kql"` + }{ + Kql: model.Alerting.Kql.ValueString(), + }, + }, + } +} + +func (model *MaintenanceWindowScheduleRecurring) toAPIRequest(ctx context.Context) (*struct { + End *string `json:"end,omitempty"` + Every *string `json:"every,omitempty"` + Occurrences *float32 `json:"occurrences,omitempty"` + OnMonth *[]float32 `json:"onMonth,omitempty"` + OnMonthDay *[]float32 `json:"onMonthDay,omitempty"` + OnWeekDay *[]string `json:"onWeekDay,omitempty"` +}, diag.Diagnostics) { + if model == nil { + return nil, nil + } + + var diags diag.Diagnostics + result := &struct { + End *string `json:"end,omitempty"` + Every *string `json:"every,omitempty"` + Occurrences *float32 `json:"occurrences,omitempty"` + OnMonth *[]float32 `json:"onMonth,omitempty"` + OnMonthDay *[]float32 `json:"onMonthDay,omitempty"` + OnWeekDay *[]string `json:"onWeekDay,omitempty"` + }{} + + if utils.IsKnown(model.End) { + result.End = model.End.ValueStringPointer() + } + + if utils.IsKnown(model.Every) { + result.Every = model.Every.ValueStringPointer() + } + + if utils.IsKnown(model.Occurrences) { + occurrences := float32(model.Occurrences.ValueInt32()) + result.Occurrences = &occurrences + } + + if utils.IsKnown(model.OnWeekDay) { + var onWeekDay []string + diags.Append(model.OnWeekDay.ElementsAs(ctx, &onWeekDay, true)...) + result.OnWeekDay = &onWeekDay + } + + if utils.IsKnown(model.OnMonth) { + var onMonth []float32 + diags.Append(model.OnMonth.ElementsAs(ctx, &onMonth, true)...) + result.OnMonth = &onMonth + } + + if utils.IsKnown(model.OnMonthDay) { + var onMonthDay []float32 + diags.Append(model.OnMonthDay.ElementsAs(ctx, &onMonthDay, true)...) + result.OnMonthDay = &onMonthDay + } + + return result, diags +} diff --git a/internal/kibana/maintenance_window/models_test.go b/internal/kibana/maintenance_window/models_test.go new file mode 100644 index 000000000..84b641ce2 --- /dev/null +++ b/internal/kibana/maintenance_window/models_test.go @@ -0,0 +1,505 @@ +package maintenance_window + +import ( + "context" + "testing" + + "github.com/elastic/terraform-provider-elasticstack/generated/kbapi" + "github.com/elastic/terraform-provider-elasticstack/internal/utils" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/stretchr/testify/require" +) + +var modelWithAllFields = MaintenanceWindowModel{ + Title: types.StringValue("test response"), + Enabled: types.BoolValue(true), + + CustomSchedule: MaintenanceWindowSchedule{ + Start: types.StringValue("1993-01-01T05:00:00.200Z"), + Duration: types.StringValue("13d"), + Timezone: types.StringValue("America/Martinique"), + + Recurring: &MaintenanceWindowScheduleRecurring{ + Every: types.StringValue("21d"), + End: types.StringValue("2029-05-17T05:05:00.000Z"), + Occurrences: types.Int32Null(), + OnWeekDay: types.ListValueMust(types.StringType, []attr.Value{types.StringValue("MO"), types.StringValue("-2FR"), types.StringValue("+4SA")}), + OnMonth: types.ListValueMust(types.Int32Type, []attr.Value{types.Int32Value(6)}), + OnMonthDay: types.ListValueMust(types.Int32Type, []attr.Value{types.Int32Value(1), types.Int32Value(2), types.Int32Value(3)}), + }, + }, + + Scope: &MaintenanceWindowScope{ + Alerting: MaintenanceWindowAlertingScope{ + Kql: types.StringValue("_id: '1234'"), + }, + }, +} + +var modelOccurrencesNoScope = MaintenanceWindowModel{ + Title: types.StringValue("test response"), + Enabled: types.BoolValue(true), + + CustomSchedule: MaintenanceWindowSchedule{ + Start: types.StringValue("1993-01-01T05:00:00.200Z"), + Duration: types.StringValue("13d"), + Timezone: types.StringNull(), + + Recurring: &MaintenanceWindowScheduleRecurring{ + Every: types.StringValue("21d"), + End: types.StringNull(), + Occurrences: types.Int32Value(42), + OnWeekDay: types.ListNull(types.StringType), + OnMonth: types.ListNull(types.Int32Type), + OnMonthDay: types.ListNull(types.Int32Type), + }, + }, + + Scope: nil, +} + +func TestMaintenanceWindowFromAPI(t *testing.T) { + ctx := context.Background() + var diags diag.Diagnostics + + tests := []struct { + name string + response ResponseJson + existingModel MaintenanceWindowModel + expectedModel MaintenanceWindowModel + }{ + { + name: "all fields", + existingModel: MaintenanceWindowModel{}, + response: ResponseJson{ + Id: "existing-space-id/id", + CreatedAt: "created_at", + Enabled: true, + Title: "test response", + Schedule: ResponseJsonSchedule{ + Custom: ResponseJsonCustomSchedule{ + Start: "1993-01-01T05:00:00.200Z", + Duration: "13d", + Timezone: utils.Pointer("America/Martinique"), + Recurring: &ResponseJsonRecurring{ + Every: utils.Pointer("21d"), + End: utils.Pointer("2029-05-17T05:05:00.000Z"), + OnWeekDay: utils.Pointer([]string{"MO", "-2FR", "+4SA"}), + OnMonth: utils.Pointer([]float32{6}), + OnMonthDay: utils.Pointer([]float32{1, 2, 3}), + }, + }, + }, + Scope: &ResponseJsonScope{ + Alerting: ResponseJsonAlerting{ + Query: ResponseJsonAlertingQuery{ + Kql: "_id: '1234'", + }, + }, + }, + }, + expectedModel: modelWithAllFields, + }, + { + name: "occurrences and no scope", + existingModel: MaintenanceWindowModel{}, + response: ResponseJson{ + Id: "existing-space-id/id", + CreatedAt: "created_at", + Enabled: true, + Title: "test response", + Schedule: ResponseJsonSchedule{ + Custom: ResponseJsonCustomSchedule{ + Start: "1993-01-01T05:00:00.200Z", + Duration: "13d", + Recurring: &ResponseJsonRecurring{ + Every: utils.Pointer("21d"), + Occurrences: utils.Pointer(float32(42)), + }, + }, + }, + Scope: nil, + }, + expectedModel: modelOccurrencesNoScope, + }, + } + + require.Empty(t, diags) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + diags := tt.existingModel._fromAPIResponse(ctx, tt.response) + + require.Equal(t, tt.expectedModel, tt.existingModel) + require.Empty(t, diags) + }) + } +} + +func TestMaintenanceWindowToAPICreateRequest(t *testing.T) { + ctx := context.Background() + var diags diag.Diagnostics + + tests := []struct { + name string + model MaintenanceWindowModel + expectedRequest kbapi.PostMaintenanceWindowJSONRequestBody + }{ + { + name: "all fields", + model: modelWithAllFields, + expectedRequest: kbapi.PostMaintenanceWindowJSONRequestBody{ + Enabled: utils.Pointer(true), + Title: "test response", + Schedule: struct { + Custom struct { + Duration string `json:"duration"` + Recurring *struct { + End *string `json:"end,omitempty"` + Every *string `json:"every,omitempty"` + Occurrences *float32 `json:"occurrences,omitempty"` + OnMonth *[]float32 `json:"onMonth,omitempty"` + OnMonthDay *[]float32 `json:"onMonthDay,omitempty"` + OnWeekDay *[]string `json:"onWeekDay,omitempty"` + } `json:"recurring,omitempty"` + Start string `json:"start"` + Timezone *string `json:"timezone,omitempty"` + } `json:"custom"` + }{ + Custom: struct { + Duration string `json:"duration"` + Recurring *struct { + End *string `json:"end,omitempty"` + Every *string `json:"every,omitempty"` + Occurrences *float32 `json:"occurrences,omitempty"` + OnMonth *[]float32 `json:"onMonth,omitempty"` + OnMonthDay *[]float32 `json:"onMonthDay,omitempty"` + OnWeekDay *[]string `json:"onWeekDay,omitempty"` + } `json:"recurring,omitempty"` + Start string `json:"start"` + Timezone *string `json:"timezone,omitempty"` + }{ + Start: "1993-01-01T05:00:00.200Z", + Duration: "13d", + Timezone: utils.Pointer("America/Martinique"), + Recurring: utils.Pointer(struct { + End *string `json:"end,omitempty"` + Every *string `json:"every,omitempty"` + Occurrences *float32 `json:"occurrences,omitempty"` + OnMonth *[]float32 `json:"onMonth,omitempty"` + OnMonthDay *[]float32 `json:"onMonthDay,omitempty"` + OnWeekDay *[]string `json:"onWeekDay,omitempty"` + }{ + Every: utils.Pointer("21d"), + End: utils.Pointer("2029-05-17T05:05:00.000Z"), + OnWeekDay: utils.Pointer([]string{"MO", "-2FR", "+4SA"}), + OnMonth: utils.Pointer([]float32{6}), + OnMonthDay: utils.Pointer([]float32{1, 2, 3}), + }), + }, + }, + Scope: utils.Pointer(struct { + Alerting struct { + Query struct { + Kql string `json:"kql"` + } `json:"query"` + } `json:"alerting"` + }{ + Alerting: struct { + Query struct { + Kql string `json:"kql"` + } `json:"query"` + }{ + Query: struct { + Kql string `json:"kql"` + }{ + Kql: "_id: '1234'", + }, + }, + }, + ), + }, + }, + { + name: "occurrences and no scope", + model: modelOccurrencesNoScope, + expectedRequest: kbapi.PostMaintenanceWindowJSONRequestBody{ + Enabled: utils.Pointer(true), + Title: "test response", + Schedule: struct { + Custom struct { + Duration string `json:"duration"` + Recurring *struct { + End *string `json:"end,omitempty"` + Every *string `json:"every,omitempty"` + Occurrences *float32 `json:"occurrences,omitempty"` + OnMonth *[]float32 `json:"onMonth,omitempty"` + OnMonthDay *[]float32 `json:"onMonthDay,omitempty"` + OnWeekDay *[]string `json:"onWeekDay,omitempty"` + } `json:"recurring,omitempty"` + Start string `json:"start"` + Timezone *string `json:"timezone,omitempty"` + } `json:"custom"` + }{ + Custom: struct { + Duration string `json:"duration"` + Recurring *struct { + End *string `json:"end,omitempty"` + Every *string `json:"every,omitempty"` + Occurrences *float32 `json:"occurrences,omitempty"` + OnMonth *[]float32 `json:"onMonth,omitempty"` + OnMonthDay *[]float32 `json:"onMonthDay,omitempty"` + OnWeekDay *[]string `json:"onWeekDay,omitempty"` + } `json:"recurring,omitempty"` + Start string `json:"start"` + Timezone *string `json:"timezone,omitempty"` + }{ + Start: "1993-01-01T05:00:00.200Z", + Duration: "13d", + + Recurring: utils.Pointer(struct { + End *string `json:"end,omitempty"` + Every *string `json:"every,omitempty"` + Occurrences *float32 `json:"occurrences,omitempty"` + OnMonth *[]float32 `json:"onMonth,omitempty"` + OnMonthDay *[]float32 `json:"onMonthDay,omitempty"` + OnWeekDay *[]string `json:"onWeekDay,omitempty"` + }{ + Every: utils.Pointer("21d"), + Occurrences: utils.Pointer(float32(42)), + }), + }, + }, + }, + }, + } + + require.Empty(t, diags) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + request, diags := tt.model.toAPICreateRequest(ctx) + require.Equal(t, request, tt.expectedRequest) + require.Empty(t, diags) + }) + } +} + +func TestMaintenanceWindowToAPIUpdateRequest(t *testing.T) { + ctx := context.Background() + var diags diag.Diagnostics + + tests := []struct { + name string + model MaintenanceWindowModel + expectedRequest kbapi.PatchMaintenanceWindowIdJSONRequestBody + }{ + { + name: "all fields", + model: modelWithAllFields, + expectedRequest: kbapi.PatchMaintenanceWindowIdJSONRequestBody{ + Enabled: utils.Pointer(true), + Title: utils.Pointer("test response"), + Schedule: utils.Pointer(struct { + Custom struct { + Duration string `json:"duration"` + Recurring *struct { + End *string `json:"end,omitempty"` + Every *string `json:"every,omitempty"` + Occurrences *float32 `json:"occurrences,omitempty"` + OnMonth *[]float32 `json:"onMonth,omitempty"` + OnMonthDay *[]float32 `json:"onMonthDay,omitempty"` + OnWeekDay *[]string `json:"onWeekDay,omitempty"` + } `json:"recurring,omitempty"` + Start string `json:"start"` + Timezone *string `json:"timezone,omitempty"` + } `json:"custom"` + }{ + Custom: struct { + Duration string `json:"duration"` + Recurring *struct { + End *string `json:"end,omitempty"` + Every *string `json:"every,omitempty"` + Occurrences *float32 `json:"occurrences,omitempty"` + OnMonth *[]float32 `json:"onMonth,omitempty"` + OnMonthDay *[]float32 `json:"onMonthDay,omitempty"` + OnWeekDay *[]string `json:"onWeekDay,omitempty"` + } `json:"recurring,omitempty"` + Start string `json:"start"` + Timezone *string `json:"timezone,omitempty"` + }{ + Start: "1993-01-01T05:00:00.200Z", + Duration: "13d", + Timezone: utils.Pointer("America/Martinique"), + Recurring: utils.Pointer(struct { + End *string `json:"end,omitempty"` + Every *string `json:"every,omitempty"` + Occurrences *float32 `json:"occurrences,omitempty"` + OnMonth *[]float32 `json:"onMonth,omitempty"` + OnMonthDay *[]float32 `json:"onMonthDay,omitempty"` + OnWeekDay *[]string `json:"onWeekDay,omitempty"` + }{ + Every: utils.Pointer("21d"), + End: utils.Pointer("2029-05-17T05:05:00.000Z"), + OnWeekDay: utils.Pointer([]string{"MO", "-2FR", "+4SA"}), + OnMonth: utils.Pointer([]float32{6}), + OnMonthDay: utils.Pointer([]float32{1, 2, 3}), + }), + }, + }), + Scope: utils.Pointer(struct { + Alerting struct { + Query struct { + Kql string `json:"kql"` + } `json:"query"` + } `json:"alerting"` + }{ + Alerting: struct { + Query struct { + Kql string `json:"kql"` + } `json:"query"` + }{ + Query: struct { + Kql string `json:"kql"` + }{ + Kql: "_id: '1234'", + }, + }, + }, + ), + }, + }, + { + name: "just title, enabled and schedule", + model: MaintenanceWindowModel{ + ID: types.StringValue("/existing-space-id/id"), + Title: types.StringValue("test response"), + Enabled: types.BoolValue(true), + CustomSchedule: MaintenanceWindowSchedule{ + Start: types.StringValue("1993-01-01T05:00:00.200Z"), + Duration: types.StringValue("13d"), + }, + }, + expectedRequest: kbapi.PatchMaintenanceWindowIdJSONRequestBody{ + Enabled: utils.Pointer(true), + Title: utils.Pointer("test response"), + Schedule: utils.Pointer(struct { + Custom struct { + Duration string `json:"duration"` + Recurring *struct { + End *string `json:"end,omitempty"` + Every *string `json:"every,omitempty"` + Occurrences *float32 `json:"occurrences,omitempty"` + OnMonth *[]float32 `json:"onMonth,omitempty"` + OnMonthDay *[]float32 `json:"onMonthDay,omitempty"` + OnWeekDay *[]string `json:"onWeekDay,omitempty"` + } `json:"recurring,omitempty"` + Start string `json:"start"` + Timezone *string `json:"timezone,omitempty"` + } `json:"custom"` + }{ + Custom: struct { + Duration string `json:"duration"` + Recurring *struct { + End *string `json:"end,omitempty"` + Every *string `json:"every,omitempty"` + Occurrences *float32 `json:"occurrences,omitempty"` + OnMonth *[]float32 `json:"onMonth,omitempty"` + OnMonthDay *[]float32 `json:"onMonthDay,omitempty"` + OnWeekDay *[]string `json:"onWeekDay,omitempty"` + } `json:"recurring,omitempty"` + Start string `json:"start"` + Timezone *string `json:"timezone,omitempty"` + }{ + Start: "1993-01-01T05:00:00.200Z", + Duration: "13d", + }, + }), + }, + }, + { + name: "just the scope and schedule", + model: MaintenanceWindowModel{ + ID: types.StringValue("/existing-space-id/id"), + + CustomSchedule: MaintenanceWindowSchedule{ + Start: types.StringValue("1993-01-01T05:00:00.200Z"), + Duration: types.StringValue("13d"), + }, + + Scope: &MaintenanceWindowScope{ + Alerting: MaintenanceWindowAlertingScope{ + Kql: types.StringValue("_id: '1234'"), + }, + }, + }, + expectedRequest: kbapi.PatchMaintenanceWindowIdJSONRequestBody{ + Schedule: utils.Pointer(struct { + Custom struct { + Duration string `json:"duration"` + Recurring *struct { + End *string `json:"end,omitempty"` + Every *string `json:"every,omitempty"` + Occurrences *float32 `json:"occurrences,omitempty"` + OnMonth *[]float32 `json:"onMonth,omitempty"` + OnMonthDay *[]float32 `json:"onMonthDay,omitempty"` + OnWeekDay *[]string `json:"onWeekDay,omitempty"` + } `json:"recurring,omitempty"` + Start string `json:"start"` + Timezone *string `json:"timezone,omitempty"` + } `json:"custom"` + }{ + Custom: struct { + Duration string `json:"duration"` + Recurring *struct { + End *string `json:"end,omitempty"` + Every *string `json:"every,omitempty"` + Occurrences *float32 `json:"occurrences,omitempty"` + OnMonth *[]float32 `json:"onMonth,omitempty"` + OnMonthDay *[]float32 `json:"onMonthDay,omitempty"` + OnWeekDay *[]string `json:"onWeekDay,omitempty"` + } `json:"recurring,omitempty"` + Start string `json:"start"` + Timezone *string `json:"timezone,omitempty"` + }{ + Start: "1993-01-01T05:00:00.200Z", + Duration: "13d", + }, + }), + + Scope: utils.Pointer(struct { + Alerting struct { + Query struct { + Kql string `json:"kql"` + } `json:"query"` + } `json:"alerting"` + }{ + Alerting: struct { + Query struct { + Kql string `json:"kql"` + } `json:"query"` + }{ + Query: struct { + Kql string `json:"kql"` + }{ + Kql: "_id: '1234'", + }, + }, + }, + ), + }, + }, + } + + require.Empty(t, diags) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + request, diags := tt.model.toAPIUpdateRequest(ctx) + require.Equal(t, request, tt.expectedRequest) + require.Empty(t, diags) + }) + } +} diff --git a/internal/kibana/maintenance_window/read.go b/internal/kibana/maintenance_window/read.go new file mode 100644 index 000000000..aafc2a315 --- /dev/null +++ b/internal/kibana/maintenance_window/read.go @@ -0,0 +1,62 @@ +package maintenance_window + +import ( + "context" + + "github.com/elastic/terraform-provider-elasticstack/internal/clients/kibana_oapi" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func (r *MaintenanceWindowResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var stateModel MaintenanceWindowModel + + req.State.GetAttribute(ctx, path.Root("id"), &stateModel.ID) + req.State.GetAttribute(ctx, path.Root("space_id"), &stateModel.SpaceID) + + serverVersion, sdkDiags := r.client.ServerVersion(ctx) + if sdkDiags.HasError() { + return + } + + serverFlavor, sdkDiags := r.client.ServerFlavor(ctx) + if sdkDiags.HasError() { + return + } + + diags := validateMaintenanceWindowServer(serverVersion, serverFlavor) + if diags.HasError() { + return + } + + client, err := r.client.GetKibanaOapiClient() + if err != nil { + resp.Diagnostics.AddError(err.Error(), "") + return + } + + maintenanceWindowID, spaceID := stateModel.getMaintenanceWindowIDAndSpaceID() + maintenanceWindow, diags := kibana_oapi.GetMaintenanceWindow(ctx, client, spaceID, maintenanceWindowID) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + if maintenanceWindow == nil { + resp.State.RemoveResource(ctx) + return + } + + diags = stateModel.fromAPIReadResponse(ctx, maintenanceWindow) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + stateModel.ID = types.StringValue(maintenanceWindowID) + stateModel.SpaceID = types.StringValue(spaceID) + + diags = resp.State.Set(ctx, stateModel) + resp.Diagnostics.Append(diags...) +} diff --git a/internal/kibana/maintenance_window/resource.go b/internal/kibana/maintenance_window/resource.go new file mode 100644 index 000000000..60ffb79a0 --- /dev/null +++ b/internal/kibana/maintenance_window/resource.go @@ -0,0 +1,40 @@ +package maintenance_window + +import ( + "context" + "fmt" + + "github.com/elastic/terraform-provider-elasticstack/internal/clients" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" +) + +var ( + _ resource.Resource = &MaintenanceWindowResource{} + _ resource.ResourceWithConfigure = &MaintenanceWindowResource{} + _ resource.ResourceWithImportState = &MaintenanceWindowResource{} +) + +// NewResource is a helper function to simplify the provider implementation. +func NewResource() resource.Resource { + return &MaintenanceWindowResource{} +} + +type MaintenanceWindowResource struct { + client *clients.ApiClient +} + +func (r *MaintenanceWindowResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + client, diags := clients.ConvertProviderData(req.ProviderData) + resp.Diagnostics.Append(diags...) + r.client = client +} + +// Metadata returns the provider type name. +func (r *MaintenanceWindowResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = fmt.Sprintf("%s_%s", req.ProviderTypeName, "kibana_maintenance_window") +} + +func (r *MaintenanceWindowResource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), request, response) +} diff --git a/internal/kibana/maintenance_window/response_types.go b/internal/kibana/maintenance_window/response_types.go new file mode 100644 index 000000000..e8c9198b3 --- /dev/null +++ b/internal/kibana/maintenance_window/response_types.go @@ -0,0 +1,49 @@ +package maintenance_window + +/* +* The types generated automatically for kibana_oapi are deeply nested a very hard to use. +* This file defines convenience types that can be used to define these neestes objects +* when needed. + */ + +type ResponseJson struct { + CreatedAt string `json:"created_at"` + CreatedBy *string `json:"created_by"` + Enabled bool `json:"enabled"` + Id string `json:"id"` + Schedule ResponseJsonSchedule `json:"schedule"` + Scope *ResponseJsonScope `json:"scope,omitempty"` + Title string `json:"title"` +} + +type ResponseJsonSchedule struct { + Custom ResponseJsonCustomSchedule `json:"custom"` +} + +type ResponseJsonCustomSchedule struct { + Duration string `json:"duration"` + Recurring *ResponseJsonRecurring `json:"recurring,omitempty"` + Start string `json:"start"` + Timezone *string `json:"timezone,omitempty"` +} + +type ResponseJsonRecurring struct { + End *string `json:"end,omitempty"` + Every *string `json:"every,omitempty"` + Occurrences *float32 `json:"occurrences,omitempty"` + OnMonth *[]float32 `json:"onMonth,omitempty"` + OnMonthDay *[]float32 `json:"onMonthDay,omitempty"` + OnWeekDay *[]string `json:"onWeekDay,omitempty"` +} + +type ResponseJsonScope struct { + Alerting ResponseJsonAlerting `json:"alerting"` +} + +type ResponseJsonAlerting struct { + Query ResponseJsonAlertingQuery `json:"query"` +} + +type ResponseJsonAlertingQuery struct { + Kql string `json:"kql"` +} diff --git a/internal/kibana/maintenance_window/schema.go b/internal/kibana/maintenance_window/schema.go new file mode 100644 index 000000000..4ad24e1c5 --- /dev/null +++ b/internal/kibana/maintenance_window/schema.go @@ -0,0 +1,153 @@ +package maintenance_window + +import ( + "context" + + "github.com/elastic/terraform-provider-elasticstack/internal/kibana/validators" + "github.com/hashicorp/terraform-plugin-framework-validators/int32validator" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func (r *MaintenanceWindowResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "Manages Kibana maintenance windows", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Generated ID for the maintenance window.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "space_id": schema.StringAttribute{ + Description: "An identifier for the space. If space_id is not provided, the default space is used.", + Optional: true, + Computed: true, + Default: stringdefault.StaticString("default"), + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "title": schema.StringAttribute{ + Description: "The name of the maintenance window.", + Required: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "enabled": schema.BoolAttribute{ + Description: "Whether the current maintenance window is enabled.", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "custom_schedule": schema.SingleNestedAttribute{ + Description: "A set schedule over which the maintenance window applies.", + Required: true, + Attributes: map[string]schema.Attribute{ + "start": schema.StringAttribute{ + Description: "The start date and time of the schedule, provided in ISO 8601 format and set to the UTC timezone. For example: `2025-03-12T12:00:00.000Z`.", + Required: true, + Validators: []validator.String{ + validators.StringIsISO8601{}, + }, + }, + "duration": schema.StringAttribute{ + Description: "The duration of the schedule. It allows values in `` format. `` is one of `d`, `h`, `m`, or `s` for days, hours, minutes, seconds. For example: `1d`, `5h`, `30m`, `5000s`.", + Required: true, + Validators: []validator.String{ + validators.StringIsAlertingDuration{}, + }, + }, + "timezone": schema.StringAttribute{ + Description: "The timezone of the schedule. The default timezone is UTC.", + Optional: true, + Computed: true, + }, + "recurring": schema.SingleNestedAttribute{ + Description: "A set schedule over which the maintenance window applies.", + Required: true, + Attributes: map[string]schema.Attribute{ + "end": schema.StringAttribute{ + Description: "The end date and time of the schedule, provided in ISO 8601 format and set to the UTC timezone. For example: `2025-03-12T12:00:00.000Z`.", + Optional: true, + Validators: []validator.String{ + validators.StringIsISO8601{}, + }, + }, + "every": schema.StringAttribute{ + Description: "The duration of the schedule. It allows values in `` format. `` is one of `d`, `h`, `m`, or `s` for days, hours, minutes, seconds. For example: `1d`, `5h`, `30m`, `5000s`.", + Optional: true, + Validators: []validator.String{ + validators.StringIsMaintenanceWindowIntervalFrequency{}, + }, + }, + "occurrences": schema.Int32Attribute{ + Description: "The total number of recurrences of the schedule.", + Optional: true, + Validators: []validator.Int32{ + int32validator.AtLeast(1), + }, + }, + "on_week_day": schema.ListAttribute{ + Description: "The specific days of the week (`[MO,TU,WE,TH,FR,SA,SU]`) or nth day of month (`[+1MO, -3FR, +2WE, -4SA, -5SU]`) for a recurring schedule.", + ElementType: types.StringType, + Optional: true, + Validators: []validator.List{ + listvalidator.ValueStringsAre( + validators.StringIsMaintenanceWindowOnWeekDay{}, + ), + }, + }, + "on_month_day": schema.ListAttribute{ + Description: "The specific days of the month for a recurring schedule. Valid values are 1-31.", + ElementType: types.Int32Type, + Optional: true, + Validators: []validator.List{ + listvalidator.ValueInt32sAre( + int32validator.Between(1, 31), + ), + }, + }, + "on_month": schema.ListAttribute{ + Description: "The specific months for a recurring schedule. Valid values are 1-12.", + ElementType: types.Int32Type, + Optional: true, + Validators: []validator.List{ + listvalidator.ValueInt32sAre( + int32validator.Between(1, 12), + ), + }, + }, + }, + }, + }, + }, + "scope": schema.SingleNestedAttribute{ + Description: "An object that narrows the scope of what is affected by this maintenance window.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "alerting": schema.SingleNestedAttribute{ + Description: "A set schedule over which the maintenance window applies.", + Required: true, + Attributes: map[string]schema.Attribute{ + "kql": schema.StringAttribute{ + Description: "A filter written in Kibana Query Language (KQL).", + Required: true, + }, + }, + }, + }, + }, + }, + } +} diff --git a/internal/kibana/maintenance_window/update.go b/internal/kibana/maintenance_window/update.go new file mode 100644 index 000000000..2c0942fce --- /dev/null +++ b/internal/kibana/maintenance_window/update.go @@ -0,0 +1,80 @@ +package maintenance_window + +import ( + "context" + + "github.com/elastic/terraform-provider-elasticstack/internal/clients/kibana_oapi" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func (r *MaintenanceWindowResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var planMaintenanceWindow MaintenanceWindowModel + + diags := req.Plan.Get(ctx, &planMaintenanceWindow) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + serverVersion, sdkDiags := r.client.ServerVersion(ctx) + if sdkDiags.HasError() { + return + } + + serverFlavor, sdkDiags := r.client.ServerFlavor(ctx) + if sdkDiags.HasError() { + return + } + + diags = validateMaintenanceWindowServer(serverVersion, serverFlavor) + if diags.HasError() { + return + } + + client, err := r.client.GetKibanaOapiClient() + if err != nil { + resp.Diagnostics.AddError(err.Error(), "") + return + } + + body, diags := planMaintenanceWindow.toAPIUpdateRequest(ctx) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + maintenanceWindowID, spaceID := planMaintenanceWindow.getMaintenanceWindowIDAndSpaceID() + diags = kibana_oapi.UpdateMaintenanceWindow(ctx, client, spaceID, maintenanceWindowID, body) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + /* + * In create/update paths we typically follow the write operation with a read, and then set the state from the read. + * We want to avoid a dirty plan immediately after an apply. + */ + readMaintenanceWindowResponse, diags := kibana_oapi.GetMaintenanceWindow(ctx, client, spaceID, maintenanceWindowID) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + if readMaintenanceWindowResponse == nil { + resp.State.RemoveResource(ctx) + return + } + + diags = planMaintenanceWindow.fromAPIReadResponse(ctx, readMaintenanceWindowResponse) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + planMaintenanceWindow.ID = types.StringValue(maintenanceWindowID) + planMaintenanceWindow.SpaceID = types.StringValue(spaceID) + + diags = resp.State.Set(ctx, planMaintenanceWindow) + resp.Diagnostics.Append(diags...) +} diff --git a/internal/kibana/maintenance_window/version_utils.go b/internal/kibana/maintenance_window/version_utils.go new file mode 100644 index 000000000..4c62d3507 --- /dev/null +++ b/internal/kibana/maintenance_window/version_utils.go @@ -0,0 +1,21 @@ +package maintenance_window + +import ( + "fmt" + + "github.com/hashicorp/go-version" + "github.com/hashicorp/terraform-plugin-framework/diag" +) + +func validateMaintenanceWindowServer(serverVersion *version.Version, serverFlavor string) diag.Diagnostics { + var serverlessFlavor = "serverless" + var maintenanceWindowPublicAPIMinSupportedVersion = version.Must(version.NewVersion("9.1.0")) + var diags diag.Diagnostics + + if serverVersion.LessThan(maintenanceWindowPublicAPIMinSupportedVersion) && serverFlavor != serverlessFlavor { + diags.AddError("Maintenance window API not supported", fmt.Sprintf(`The maintenance Window public API feature requires a minimum Elasticsearch version of "%s" or a serverless Kibana instance.`, maintenanceWindowPublicAPIMinSupportedVersion)) + return diags + } + + return nil +} diff --git a/internal/kibana/validators/is_alerting_duration.go b/internal/kibana/validators/is_alerting_duration.go new file mode 100644 index 000000000..715b91769 --- /dev/null +++ b/internal/kibana/validators/is_alerting_duration.go @@ -0,0 +1,49 @@ +package validators + +import ( + "context" + "regexp" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +var alertingDurationPattern = "^[1-9][0-9]*(?:d|h|m|s)$" + +func StringMatchesAlertingDurationRegex(s string) (matched bool, err error) { + return regexp.MatchString(alertingDurationPattern, s) +} + +type StringIsAlertingDuration struct{} + +func (s StringIsAlertingDuration) Description(_ context.Context) string { + return "a valid alerting duration in seconds (s), minutes (m), hours (h), or days (d)" +} + +func (s StringIsAlertingDuration) MarkdownDescription(ctx context.Context) string { + return s.Description(ctx) +} + +func (s StringIsAlertingDuration) ValidateString(_ context.Context, req validator.StringRequest, resp *validator.StringResponse) { + if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { + return + } + + if matched, err := StringMatchesAlertingDurationRegex(req.ConfigValue.ValueString()); err != nil || !matched { + resp.Diagnostics.AddAttributeError( + req.Path, + "expected value to be a valid alerting duration", + "This value must be a valid alerting duration in seconds (s), minutes (m), hours (h), or days (d).", + ) + return + } +} + +// Avoid lint error on deprecated SchemaValidateFunc usage. +// +//nolint:staticcheck +func StringIsAlertingDurationSDKV2() schema.SchemaValidateFunc { + r := regexp.MustCompile(alertingDurationPattern) + return validation.StringMatch(r, "string is not a valid Alerting duration in seconds (s), minutes (m), hours (h), or days (d).") +} diff --git a/internal/kibana/validators/is_iso8601_string.go b/internal/kibana/validators/is_iso8601_string.go new file mode 100644 index 000000000..ba5dd31b0 --- /dev/null +++ b/internal/kibana/validators/is_iso8601_string.go @@ -0,0 +1,38 @@ +package validators + +import ( + "context" + "regexp" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +func StringMatchesISO8601Regex(s string) (matched bool, err error) { + pattern := `(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))` + return regexp.MatchString(pattern, s) +} + +type StringIsISO8601 struct{} + +func (s StringIsISO8601) Description(_ context.Context) string { + return "a valid ISO8601 date and time formatted string" +} + +func (s StringIsISO8601) MarkdownDescription(ctx context.Context) string { + return s.Description(ctx) +} + +func (s StringIsISO8601) ValidateString(_ context.Context, req validator.StringRequest, resp *validator.StringResponse) { + if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { + return + } + + if matched, err := StringMatchesISO8601Regex(req.ConfigValue.ValueString()); err != nil || !matched { + resp.Diagnostics.AddAttributeError( + req.Path, + "expected value to be a valid ISO8601 string", + "This value must be a valid ISO8601 date and time formatted string.", + ) + return + } +} diff --git a/internal/kibana/validators/is_maintenance_window_interval.go b/internal/kibana/validators/is_maintenance_window_interval.go new file mode 100644 index 000000000..3f0f6c1a6 --- /dev/null +++ b/internal/kibana/validators/is_maintenance_window_interval.go @@ -0,0 +1,38 @@ +package validators + +import ( + "context" + "regexp" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +func StringMatchesIntervalFrequencyRegex(s string) (matched bool, err error) { + pattern := `^[1-9][0-9]*(?:d|w|M|y)$` + return regexp.MatchString(pattern, s) +} + +type StringIsMaintenanceWindowIntervalFrequency struct{} + +func (s StringIsMaintenanceWindowIntervalFrequency) Description(_ context.Context) string { + return "a valid interval/frequency. Allowed values are in the `` format. `` is one of `d`, `w`, `M`, or `y` for days, weeks, months, years. For example: `15d`, `2w`, `3m`, `1y`." +} + +func (s StringIsMaintenanceWindowIntervalFrequency) MarkdownDescription(ctx context.Context) string { + return s.Description(ctx) +} + +func (s StringIsMaintenanceWindowIntervalFrequency) ValidateString(_ context.Context, req validator.StringRequest, resp *validator.StringResponse) { + if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { + return + } + + if matched, err := StringMatchesIntervalFrequencyRegex(req.ConfigValue.ValueString()); err != nil || !matched { + resp.Diagnostics.AddAttributeError( + req.Path, + "expected value to be a valid interval/frequency", + "This value must be a valid interval/frequency. Allowed values are in the `` format. `` is one of `d`, `w`, `M`, or `y` for days, weeks, months, years. For example: `15d`, `2w`, `3m`, `1y`.", + ) + return + } +} diff --git a/internal/kibana/validators/is_maintenance_window_week_day.go b/internal/kibana/validators/is_maintenance_window_week_day.go new file mode 100644 index 000000000..07c4cce48 --- /dev/null +++ b/internal/kibana/validators/is_maintenance_window_week_day.go @@ -0,0 +1,38 @@ +package validators + +import ( + "context" + "regexp" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +func StringMatchesOnWeekDayRegex(s string) (matched bool, err error) { + pattern := `^(((\+|-)[1-5])?(MO|TU|WE|TH|FR|SA|SU))$` + return regexp.MatchString(pattern, s) +} + +type StringIsMaintenanceWindowOnWeekDay struct{} + +func (s StringIsMaintenanceWindowOnWeekDay) Description(_ context.Context) string { + return "a valid OnWeekDay. Accepted values are specific days of the week (`[MO,TU,WE,TH,FR,SA,SU]`) or nth day of month (`[+1MO, -3FR, +2WE, -4SA, -5SU]`)." +} + +func (s StringIsMaintenanceWindowOnWeekDay) MarkdownDescription(ctx context.Context) string { + return s.Description(ctx) +} + +func (s StringIsMaintenanceWindowOnWeekDay) ValidateString(_ context.Context, req validator.StringRequest, resp *validator.StringResponse) { + if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { + return + } + + if matched, err := StringMatchesOnWeekDayRegex(req.ConfigValue.ValueString()); err != nil || !matched { + resp.Diagnostics.AddAttributeError( + req.Path, + "expected value to be a valid OnWeekDay", + "This value must be a valid OnWeekDay. Accepted values are specific days of the week (`[MO,TU,WE,TH,FR,SA,SU]`) or nth day of month (`[+1MO, -3FR, +2WE, -4SA, -5SU]`).", + ) + return + } +} diff --git a/internal/kibana/validators/validators_test.go b/internal/kibana/validators/validators_test.go new file mode 100644 index 000000000..16ebbab9e --- /dev/null +++ b/internal/kibana/validators/validators_test.go @@ -0,0 +1,256 @@ +package validators + +import ( + "reflect" + "testing" +) + +func TestStringMatchesAlertingDuration(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + duration string + matched bool + }{ + { + name: "valid Alerting duration string (30d)", + duration: "30d", + matched: true, + }, + { + name: "invalid Alerting duration unit (0s)", + duration: "0s", + matched: false, + }, + { + name: "invalid Alerting duration value (.12y)", + duration: ".12y", + matched: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + matched, _ := StringMatchesAlertingDurationRegex(tt.duration) + if !reflect.DeepEqual(matched, tt.matched) { + t.Errorf("StringMatchesAlertingDurationRegex() failed match = %v, want %v", matched, tt.matched) + } + }) + } +} + +func TestStringMatchesISO8601(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + date string + matched bool + }{ + { + name: "valid complete date 1", + date: "1994-11-05T13:15:30Z", + matched: true, + }, + { + name: "valid complete date 2", + date: "1997-07-04T19:20+01:00", + matched: true, + }, + { + name: "valid complete date 3", + date: "1994-11-05T08:15:30-05:00", + matched: true, + }, + { + name: "valid complete date plus hours, minutes and seconds", + date: "1997-07-16T19:20:30+01:00", + matched: true, + }, + { + name: "valid complete date plus hours, minutes, seconds and a decimal fraction of a second", + date: "1997-07-16T19:20:30.45+01:00", + matched: true, + }, { + name: "invalid year", + date: "1997", + matched: false, + }, + { + name: "invalid year and month", + date: "1997-07", + matched: false, + }, + { + name: "invalid complete date", + date: "1997-07-04", + matched: false, + }, + { + name: "invalid hours and minutes", + date: "1997-40-04T30:220+01:00", + matched: false, + }, + { + name: "invalid seconds", + date: "1997-07-16T19:20:80+01:00", + matched: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + matched, _ := StringMatchesISO8601Regex(tt.date) + if !reflect.DeepEqual(matched, tt.matched) { + t.Errorf("StringMatchesISO8601Regex() failed match = %v, want %v", matched, tt.matched) + } + }) + } +} + +func TestStringMatchesOnWeekDay(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + onWeekDay string + matched bool + }{ + { + name: "valid on_week_day string (+1MO)", + onWeekDay: "+1MO", + matched: true, + }, + { + name: "valid on_week_day string (+2TU)", + onWeekDay: "+2TU", + matched: true, + }, + { + name: "valid on_week_day string (+3WE)", + onWeekDay: "+3WE", + matched: true, + }, + { + name: "valid on_week_day string (+4TH)", + onWeekDay: "+4TH", + matched: true, + }, + { + name: "valid on_week_day string (+5FR)", + onWeekDay: "+5FR", + matched: true, + }, + { + name: "valid on_week_day string (-5SA)", + onWeekDay: "-5SA", + matched: true, + }, + { + name: "valid on_week_day string (-4SU)", + onWeekDay: "-4SU", + matched: true, + }, + { + name: "valid on_week_day string (-3MO)", + onWeekDay: "-3MO", + matched: true, + }, + { + name: "valid on_week_day string (-2TU)", + onWeekDay: "-2TU", + matched: true, + }, + { + name: "valid on_week_day string (-1WE)", + onWeekDay: "-1WE", + matched: true, + }, + { + name: "invalid on_week_day unit (FOOBAR)", + onWeekDay: "FOOBAR", + matched: false, + }, + { + name: "invalid on_week_day string (+9MO)", + onWeekDay: "+9MO", + matched: false, + }, + { + name: "invalid on_week_day string (-7FR)", + onWeekDay: "-7FR", + matched: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + matched, _ := StringMatchesOnWeekDayRegex(tt.onWeekDay) + if !reflect.DeepEqual(matched, tt.matched) { + t.Errorf("StringMatchesOnWeekDayRegex() failed match = %v, want %v", matched, tt.matched) + } + }) + } +} + +func TestStringMatchesIntervalFrequencyRegex(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + intervalFrequency string + matched bool + }{ + + { + name: "valid interval/frequency string (2d)", + intervalFrequency: "2d", + matched: true, + }, + { + name: "valid interval/frequency string (5w)", + intervalFrequency: "5w", + matched: true, + }, + { + name: "valid interval/frequency string (3M)", + intervalFrequency: "3M", + matched: true, + }, + { + name: "valid interval/frequency string (1y)", + intervalFrequency: "1y", + matched: true, + }, + { + name: "invalid interval/frequency string (5m)", + intervalFrequency: "5m", + matched: false, + }, + { + name: "invalid interval/frequency string (-1w)", + intervalFrequency: "-1w", + matched: false, + }, + { + name: "invalid interval/frequency string (invalid)", + intervalFrequency: "invalid", + matched: false, + }, + { + name: "invalid interval/frequency empty string", + intervalFrequency: " ", + matched: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + matched, _ := StringMatchesIntervalFrequencyRegex(tt.intervalFrequency) + if !reflect.DeepEqual(matched, tt.matched) { + t.Errorf("StringMatchesOnWeekDayRegex() failed match = %v, want %v", matched, tt.matched) + } + }) + } +} diff --git a/provider/plugin_framework.go b/provider/plugin_framework.go index d7031c9ce..5e33ee247 100644 --- a/provider/plugin_framework.go +++ b/provider/plugin_framework.go @@ -21,6 +21,7 @@ import ( "github.com/elastic/terraform-provider-elasticstack/internal/fleet/server_host" "github.com/elastic/terraform-provider-elasticstack/internal/kibana/data_view" "github.com/elastic/terraform-provider-elasticstack/internal/kibana/import_saved_objects" + "github.com/elastic/terraform-provider-elasticstack/internal/kibana/maintenance_window" "github.com/elastic/terraform-provider-elasticstack/internal/kibana/spaces" "github.com/elastic/terraform-provider-elasticstack/internal/kibana/synthetics" "github.com/elastic/terraform-provider-elasticstack/internal/kibana/synthetics/parameter" @@ -108,6 +109,7 @@ func (p *Provider) Resources(ctx context.Context) []func() resource.Resource { output.NewResource, server_host.NewResource, system_user.NewSystemUserResource, + maintenance_window.NewResource, enrich.NewEnrichPolicyResource, } } diff --git a/templates/resources/kibana_maintenance_window.md.tmpl b/templates/resources/kibana_maintenance_window.md.tmpl new file mode 100644 index 000000000..e7fddee61 --- /dev/null +++ b/templates/resources/kibana_maintenance_window.md.tmpl @@ -0,0 +1,23 @@ +--- +subcategory: "Kibana" +layout: "" +page_title: "Elasticstack: elasticstack_kibana_maintenance_window Resource" +description: |- + Manages Kibana maintenance windows. +--- + +# Resource: elasticstack_kibana_maintenance_window + +Creates and manages Kibana [maintenance windows](https://www.elastic.co/docs/api/doc/kibana/group/endpoint-maintenance-window) + +## Example Usage + +{{ tffile "examples/resources/elasticstack_kibana_maintenance_window/resource.tf" }} + +{{ .SchemaMarkdown | trimspace }} + +## Import + +Import is supported using the following syntax: + +{{ codefile "shell" "examples/resources/elasticstack_kibana_maintenance_window/import.sh" }}