Skip to content

Commit 97e2534

Browse files
committed
Add support for timeslice metrics indicator
* Add support for the resource based on OpenAPI docs. * Add tests * Generate docs
1 parent 77c15d2 commit 97e2534

File tree

4 files changed

+484
-1
lines changed

4 files changed

+484
-1
lines changed

docs/resources/kibana_slo.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,41 @@ resource "elasticstack_kibana_slo" "custom_metric" {
190190
timeslice_window = "5m"
191191
}
192192
193+
}
194+
195+
//Available from 8.12.0
196+
resource "elasticstack_kibana_slo" "timeslice_metric" {
197+
name = "timeslice metric"
198+
description = "timeslice metric"
199+
200+
timeslice_metric_indicator {
201+
index = "my-index"
202+
timestamp_field = "@timestamp"
203+
metric {
204+
metrics {
205+
name = "A"
206+
aggregation = "sum"
207+
field = "latency"
208+
}
209+
equation = "A"
210+
comparator = "GT"
211+
threshold = 100
212+
}
213+
}
214+
215+
time_window {
216+
duration = "7d"
217+
type = "rolling"
218+
}
219+
220+
budgeting_method = "timeslices"
221+
222+
objective {
223+
target = 0.95
224+
timeslice_target = 0.95
225+
timeslice_window = "5m"
226+
}
227+
193228
}
194229
```
195230

@@ -216,6 +251,7 @@ resource "elasticstack_kibana_slo" "custom_metric" {
216251
- `slo_id` (String) An ID (8 and 36 characters). If omitted, a UUIDv1 will be generated server-side.
217252
- `space_id` (String) An identifier for the space. If space_id is not provided, the default space is used.
218253
- `tags` (List of String) The tags for the SLO.
254+
- `timeslice_metric_indicator` (Block List, Max: 1) Defines a timeslice metric indicator for SLO. (see [below for nested schema](#nestedblock--timeslice_metric_indicator))
219255

220256
### Read-Only
221257

@@ -405,6 +441,44 @@ Optional:
405441
- `frequency` (String)
406442
- `sync_delay` (String)
407443

444+
445+
<a id="nestedblock--timeslice_metric_indicator"></a>
446+
### Nested Schema for `timeslice_metric_indicator`
447+
448+
Required:
449+
450+
- `index` (String)
451+
- `metric` (Block List, Min: 1, Max: 1) (see [below for nested schema](#nestedblock--timeslice_metric_indicator--metric))
452+
- `timestamp_field` (String)
453+
454+
Optional:
455+
456+
- `filter` (String)
457+
458+
<a id="nestedblock--timeslice_metric_indicator--metric"></a>
459+
### Nested Schema for `timeslice_metric_indicator.metric`
460+
461+
Required:
462+
463+
- `comparator` (String)
464+
- `equation` (String)
465+
- `metrics` (Block List, Min: 1) (see [below for nested schema](#nestedblock--timeslice_metric_indicator--metric--metrics))
466+
- `threshold` (Number)
467+
468+
<a id="nestedblock--timeslice_metric_indicator--metric--metrics"></a>
469+
### Nested Schema for `timeslice_metric_indicator.metric.metrics`
470+
471+
Required:
472+
473+
- `aggregation` (String) The aggregation type for this metric. One of: sum, avg, min, max, value_count, percentile, doc_count. Determines which other fields are required:
474+
- `name` (String) The unique name for this metric. Used as a variable in the equation field.
475+
476+
Optional:
477+
478+
- `field` (String) Field to aggregate. Required for aggregations: sum, avg, min, max, value_count, percentile. Must NOT be set for doc_count.
479+
- `filter` (String) Optional KQL filter for this metric. Supported for all aggregations except doc_count.
480+
- `percentile` (Number) Percentile value (e.g., 99). Required if aggregation is 'percentile'. Must NOT be set for other aggregations.
481+
408482
## Import
409483

410484
Import is supported using the following syntax:

examples/resources/elasticstack_kibana_slo/resource.tf

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,38 @@ resource "elasticstack_kibana_slo" "custom_metric" {
176176
}
177177

178178
}
179+
180+
//Available from 8.12.0
181+
resource "elasticstack_kibana_slo" "timeslice_metric" {
182+
name = "timeslice metric"
183+
description = "timeslice metric"
184+
185+
timeslice_metric_indicator {
186+
index = "my-index"
187+
timestamp_field = "@timestamp"
188+
metric {
189+
metrics {
190+
name = "A"
191+
aggregation = "sum"
192+
field = "latency"
193+
}
194+
equation = "A"
195+
comparator = "GT"
196+
threshold = 100
197+
}
198+
}
199+
200+
time_window {
201+
duration = "7d"
202+
type = "rolling"
203+
}
204+
205+
budgeting_method = "timeslices"
206+
207+
objective {
208+
target = 0.95
209+
timeslice_target = 0.95
210+
timeslice_window = "5m"
211+
}
212+
213+
}

internal/kibana/slo.go

Lines changed: 150 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ package kibana
33
import (
44
"context"
55
"fmt"
6+
"github.com/elastic/terraform-provider-elasticstack/internal/clients/kibana"
67

78
"github.com/elastic/terraform-provider-elasticstack/generated/slo"
89
"github.com/elastic/terraform-provider-elasticstack/internal/clients"
9-
"github.com/elastic/terraform-provider-elasticstack/internal/clients/kibana"
1010
"github.com/elastic/terraform-provider-elasticstack/internal/models"
1111
"github.com/elastic/terraform-provider-elasticstack/internal/utils"
1212
"github.com/hashicorp/go-version"
@@ -390,6 +390,84 @@ func getSchema() map[string]*schema.Schema {
390390
},
391391
},
392392
},
393+
"timeslice_metric_indicator": {
394+
Description: "Defines a timeslice metric indicator for SLO.",
395+
Type: schema.TypeList,
396+
Optional: true,
397+
MaxItems: 1,
398+
Elem: &schema.Resource{
399+
Schema: map[string]*schema.Schema{
400+
"index": {
401+
Type: schema.TypeString,
402+
Required: true,
403+
},
404+
"timestamp_field": {
405+
Type: schema.TypeString,
406+
Required: true,
407+
},
408+
"filter": {
409+
Type: schema.TypeString,
410+
Optional: true,
411+
},
412+
"metric": {
413+
Type: schema.TypeList,
414+
Required: true,
415+
MaxItems: 1,
416+
Elem: &schema.Resource{
417+
Schema: map[string]*schema.Schema{
418+
"metrics": {
419+
Type: schema.TypeList,
420+
Required: true,
421+
MinItems: 1,
422+
Elem: &schema.Resource{
423+
Schema: map[string]*schema.Schema{
424+
"name": {
425+
Type: schema.TypeString,
426+
Required: true,
427+
Description: "The unique name for this metric. Used as a variable in the equation field.",
428+
},
429+
"aggregation": {
430+
Type: schema.TypeString,
431+
Required: true,
432+
Description: "The aggregation type for this metric. One of: sum, avg, min, max, value_count, percentile, doc_count. Determines which other fields are required:",
433+
},
434+
"field": {
435+
Type: schema.TypeString,
436+
Optional: true,
437+
Description: "Field to aggregate. Required for aggregations: sum, avg, min, max, value_count, percentile. Must NOT be set for doc_count.",
438+
},
439+
"percentile": {
440+
Type: schema.TypeFloat,
441+
Optional: true,
442+
Description: "Percentile value (e.g., 99). Required if aggregation is 'percentile'. Must NOT be set for other aggregations.",
443+
},
444+
"filter": {
445+
Type: schema.TypeString,
446+
Optional: true,
447+
Description: "Optional KQL filter for this metric. Supported for all aggregations except doc_count.",
448+
},
449+
},
450+
},
451+
},
452+
"equation": {
453+
Type: schema.TypeString,
454+
Required: true,
455+
},
456+
"comparator": {
457+
Type: schema.TypeString,
458+
Required: true,
459+
ValidateFunc: validation.StringInSlice([]string{"GT", "GTE", "LT", "LTE"}, false),
460+
},
461+
"threshold": {
462+
Type: schema.TypeFloat,
463+
Required: true,
464+
},
465+
},
466+
},
467+
},
468+
},
469+
},
470+
},
393471
"time_window": {
394472
Description: "Currently support `calendarAligned` and `rolling` time windows. Any duration greater than 1 day can be used: days, weeks, months, quarters, years. Rolling time window requires a duration, e.g. `1w` for one week, and type: `rolling`. SLOs defined with such time window, will only consider the SLI data from the last duration period as a moving window. Calendar aligned time window requires a duration, limited to `1M` for monthly or `1w` for weekly, and type: `calendarAligned`.",
395473
Type: schema.TypeList,
@@ -634,6 +712,60 @@ func getSloFromResourceData(d *schema.ResourceData) (models.Slo, diag.Diagnostic
634712
},
635713
}
636714

715+
case "timeslice_metric_indicator":
716+
params := d.Get("timeslice_metric_indicator.0").(map[string]interface{})
717+
metricBlock := params["metric"].([]interface{})[0].(map[string]interface{})
718+
metricsIface := metricBlock["metrics"].([]interface{})
719+
metrics := make([]slo.IndicatorPropertiesTimesliceMetricParamsMetricMetricsInner, len(metricsIface))
720+
for i, m := range metricsIface {
721+
metric := m.(map[string]interface{})
722+
agg := metric["aggregation"].(string)
723+
switch agg {
724+
case "sum", "avg", "min", "max", "value_count":
725+
metrics[i] = slo.IndicatorPropertiesTimesliceMetricParamsMetricMetricsInner{
726+
TimesliceMetricBasicMetricWithField: &slo.TimesliceMetricBasicMetricWithField{
727+
Name: metric["name"].(string),
728+
Aggregation: agg,
729+
Field: metric["field"].(string),
730+
},
731+
}
732+
case "percentile":
733+
metrics[i] = slo.IndicatorPropertiesTimesliceMetricParamsMetricMetricsInner{
734+
TimesliceMetricPercentileMetric: &slo.TimesliceMetricPercentileMetric{
735+
Name: metric["name"].(string),
736+
Aggregation: agg,
737+
Field: metric["field"].(string),
738+
Percentile: metric["percentile"].(float64),
739+
},
740+
}
741+
case "doc_count":
742+
metrics[i] = slo.IndicatorPropertiesTimesliceMetricParamsMetricMetricsInner{
743+
TimesliceMetricDocCountMetric: &slo.TimesliceMetricDocCountMetric{
744+
Name: metric["name"].(string),
745+
Aggregation: agg,
746+
},
747+
}
748+
default:
749+
return models.Slo{}, diag.Errorf("metrics[%d]: unsupported aggregation '%s'", i, agg)
750+
}
751+
}
752+
indicator = slo.SloResponseIndicator{
753+
IndicatorPropertiesTimesliceMetric: &slo.IndicatorPropertiesTimesliceMetric{
754+
Type: indicatorAddressToType[indicatorType],
755+
Params: slo.IndicatorPropertiesTimesliceMetricParams{
756+
Index: params["index"].(string),
757+
TimestampField: params["timestamp_field"].(string),
758+
Filter: getOrNilString("filter", d),
759+
Metric: slo.IndicatorPropertiesTimesliceMetricParamsMetric{
760+
Metrics: metrics,
761+
Equation: metricBlock["equation"].(string),
762+
Comparator: metricBlock["comparator"].(string),
763+
Threshold: metricBlock["threshold"].(float64),
764+
},
765+
},
766+
},
767+
}
768+
637769
default:
638770
return models.Slo{}, diag.Errorf("unknown indicator type %s", indicatorType)
639771
}
@@ -873,6 +1005,22 @@ func resourceSloRead(ctx context.Context, d *schema.ResourceData, meta interface
8731005
"total": total,
8741006
})
8751007

1008+
case s.Indicator.IndicatorPropertiesTimesliceMetric != nil:
1009+
indicatorAddress = indicatorTypeToAddress[s.Indicator.IndicatorPropertiesTimesliceMetric.Type]
1010+
params := s.Indicator.IndicatorPropertiesTimesliceMetric.Params
1011+
metric := []map[string]interface{}{{
1012+
"metrics": params.Metric.Metrics,
1013+
"equation": params.Metric.Equation,
1014+
"comparator": params.Metric.Comparator,
1015+
"threshold": params.Metric.Threshold,
1016+
}}
1017+
indicator = append(indicator, map[string]interface{}{
1018+
"index": params.Index,
1019+
"timestamp_field": params.TimestampField,
1020+
"filter": params.Filter,
1021+
"metric": metric,
1022+
})
1023+
8761024
default:
8771025
return diag.Errorf("indicator not set")
8781026
}
@@ -964,6 +1112,7 @@ var indicatorAddressToType = map[string]string{
9641112
"kql_custom_indicator": "sli.kql.custom",
9651113
"metric_custom_indicator": "sli.metric.custom",
9661114
"histogram_custom_indicator": "sli.histogram.custom",
1115+
"timeslice_metric_indicator": "sli.timeslice.metric",
9671116
}
9681117

9691118
var indicatorTypeToAddress = utils.FlipMap(indicatorAddressToType)

0 commit comments

Comments
 (0)