Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## [Unreleased]

- Fix provider crash with `elasticstack_kibana_action_connector` when `config` or `secrets` was unset in 0.11.17 ([#1355](https://github.com/elastic/terraform-provider-elasticstack/pull/1355))
- Added `labels` field to `elasticstack_kibana_synthetics_monitor` resource for associating key-value pairs with monitors ([#1360](https://github.com/elastic/terraform-provider-elasticstack/pull/1360))
- Fixes provider crash with `elasticstack_kibana_slo` when using `kql_custom_indicator` with no `filter` set. ([#1354](https://github.com/elastic/terraform-provider-elasticstack/pull/1354))
- Updates for Security Detection Rules ([#1361](https://github.com/elastic/terraform-provider-elasticstack/pull/1361)
- Add support for `threat` property
Expand Down
6 changes: 6 additions & 0 deletions docs/resources/kibana_synthetics_monitor.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ resource "elasticstack_kibana_synthetics_monitor" "my_monitor" {
locations = ["us_west"]
enabled = false
tags = ["tag"]
labels = {
environment = "production"
team = "platform"
service = "web-app"
}
alert = {
status = {
enabled = true
Expand Down Expand Up @@ -76,6 +81,7 @@ resource "elasticstack_kibana_synthetics_monitor" "my_monitor" {
- `enabled` (Boolean) Whether the monitor is enabled. Default: `true`
- `http` (Attributes) HTTP Monitor specific fields (see [below for nested schema](#nestedatt--http))
- `icmp` (Attributes) ICMP Monitor specific fields (see [below for nested schema](#nestedatt--icmp))
- `labels` (Map of String) Key-value pairs of labels to associate with the monitor. Labels can be used for filtering and grouping monitors.
- `locations` (List of String) Where to deploy the monitor. Monitors can be deployed in multiple locations so that you can detect differences in availability and response times across those locations.
- `namespace` (String) The data stream namespace. Note: if you change its value, kibana creates new datastream. A user needs permissions for new/old datastream in update case to be able to see full monitor history. The `namespace` field should be lowercase and not contain spaces. The namespace must not include any of the following characters: *, \, /, ?, ", <, >, |, whitespace, ,, #, :, or -. Default: `default`
- `params` (String) Monitor parameters. Raw JSON object, use `jsonencode` function to represent JSON
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ resource "elasticstack_kibana_synthetics_monitor" "my_monitor" {
locations = ["us_west"]
enabled = false
tags = ["tag"]
labels = {
environment = "production"
team = "platform"
service = "web-app"
}
alert = {
status = {
enabled = true
Expand Down
106 changes: 106 additions & 0 deletions internal/kibana/synthetics/acc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"testing"

"github.com/elastic/terraform-provider-elasticstack/internal/acctest"
"github.com/elastic/terraform-provider-elasticstack/internal/kibana/synthetics"
"github.com/elastic/terraform-provider-elasticstack/internal/versionutils"
"github.com/hashicorp/go-version"
sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest"
Expand Down Expand Up @@ -339,6 +340,46 @@ resource "elasticstack_kibana_synthetics_monitor" "%s" {
playwright_options = jsonencode({"httpCredentials":{"password":"test","username":"test"},"ignoreHTTPSErrors":false})
}
}
`

httpMonitorLabelsConfig = `
resource "elasticstack_kibana_synthetics_monitor" "%s" {
name = "TestHttpMonitorLabels - %s"
private_locations = [elasticstack_kibana_synthetics_private_location.%s.label]
labels = {
environment = "production"
team = "platform"
service = "web-app"
}
http = {
url = "http://localhost:5601"
}
}
`

httpMonitorLabelsUpdated = `
resource "elasticstack_kibana_synthetics_monitor" "%s" {
name = "TestHttpMonitorLabels Updated - %s"
private_locations = [elasticstack_kibana_synthetics_private_location.%s.label]
labels = {
environment = "staging"
team = "platform-updated"
service = "web-app-v2"
}
http = {
url = "http://localhost:5601"
}
}
`

httpMonitorLabelsRemoved = `
resource "elasticstack_kibana_synthetics_monitor" "%s" {
name = "TestHttpMonitorLabels Removed - %s"
private_locations = [elasticstack_kibana_synthetics_private_location.%s.label]
http = {
url = "http://localhost:5601"
}
}
`
)

Expand Down Expand Up @@ -828,6 +869,71 @@ func TestSyntheticMonitorBrowserResource(t *testing.T) {
})
}

func TestSyntheticMonitorLabelsResource(t *testing.T) {
name := sdkacctest.RandStringFromCharSet(22, sdkacctest.CharSetAlphaNum)
id := "http-monitor-labels"
labelsMonitorId, labelsConfig := testMonitorConfig(id, httpMonitorLabelsConfig, name)
_, labelsConfigUpdated := testMonitorConfig(id, httpMonitorLabelsUpdated, name)
_, labelsConfigRemoved := testMonitorConfig(id, httpMonitorLabelsRemoved, name)

resource.Test(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
ProtoV6ProviderFactories: acctest.Providers,
Steps: []resource.TestStep{
// Create and Read monitor with labels
{
SkipFunc: versionutils.CheckIfVersionIsUnsupported(synthetics.MinLabelsVersion),
Config: labelsConfig,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttrSet(labelsMonitorId, "id"),
resource.TestCheckResourceAttr(labelsMonitorId, "name", "TestHttpMonitorLabels - "+name),
resource.TestCheckResourceAttr(labelsMonitorId, "labels.%", "3"),
resource.TestCheckResourceAttr(labelsMonitorId, "labels.environment", "production"),
resource.TestCheckResourceAttr(labelsMonitorId, "labels.team", "platform"),
resource.TestCheckResourceAttr(labelsMonitorId, "labels.service", "web-app"),
resource.TestCheckResourceAttr(labelsMonitorId, "http.url", "http://localhost:5601"),
),
},
// ImportState testing
{
SkipFunc: versionutils.CheckIfVersionIsUnsupported(synthetics.MinLabelsVersion),
ResourceName: labelsMonitorId,
ImportState: true,
ImportStateVerify: true,
Config: labelsConfig,
},
// Update labels - change values but keep same keys
{
SkipFunc: versionutils.CheckIfVersionIsUnsupported(synthetics.MinLabelsVersion),
Config: labelsConfigUpdated,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttrSet(labelsMonitorId, "id"),
resource.TestCheckResourceAttr(labelsMonitorId, "name", "TestHttpMonitorLabels Updated - "+name),
resource.TestCheckResourceAttr(labelsMonitorId, "labels.%", "3"),
resource.TestCheckResourceAttr(labelsMonitorId, "labels.environment", "staging"),
resource.TestCheckResourceAttr(labelsMonitorId, "labels.team", "platform-updated"),
resource.TestCheckResourceAttr(labelsMonitorId, "labels.service", "web-app-v2"),
),
},
// Remove all labels - this tests the round-trip consistency fix
{
SkipFunc: versionutils.CheckIfVersionIsUnsupported(synthetics.MinLabelsVersion),
Config: labelsConfigRemoved,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttrSet(labelsMonitorId, "id"),
resource.TestCheckResourceAttr(labelsMonitorId, "name", "TestHttpMonitorLabels Removed - "+name),
resource.TestCheckNoResourceAttr(labelsMonitorId, "labels.%"),
resource.TestCheckNoResourceAttr(labelsMonitorId, "labels.environment"),
resource.TestCheckNoResourceAttr(labelsMonitorId, "labels.team"),
resource.TestCheckNoResourceAttr(labelsMonitorId, "labels.service"),
resource.TestCheckNoResourceAttr(labelsMonitorId, "labels.version"),
),
},
// Delete testing automatically occurs in TestCase
},
})
}

func testMonitorConfig(id, cfg, name string) (string, string) {

resourceId := "elasticstack_kibana_synthetics_monitor." + id
Expand Down
10 changes: 9 additions & 1 deletion internal/kibana/synthetics/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import (
"context"
"fmt"

"github.com/hashicorp/go-version"

"github.com/hashicorp/terraform-plugin-framework/resource"
)

func (r *Resource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) {
var MinLabelsVersion = version.Must(version.NewVersion("8.16.0"))

func (r *Resource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) {
kibanaClient := GetKibanaClient(r, response.Diagnostics)
if kibanaClient == nil {
return
Expand All @@ -21,6 +24,11 @@ func (r *Resource) Create(ctx context.Context, request resource.CreateRequest, r
return
}

response.Diagnostics.Append(plan.enforceVersionConstraints(ctx, r.client)...)
if response.Diagnostics.HasError() {
return
}

input, diags := plan.toKibanaAPIRequest(ctx)
response.Diagnostics.Append(diags...)
if response.Diagnostics.HasError() {
Expand Down
55 changes: 55 additions & 0 deletions internal/kibana/synthetics/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/disaster37/go-kibana-rest/v8/kbapi"
"github.com/elastic/terraform-provider-elasticstack/internal/clients"
"github.com/elastic/terraform-provider-elasticstack/internal/diagutil"
"github.com/elastic/terraform-provider-elasticstack/internal/utils"
"github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes"
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
Expand Down Expand Up @@ -107,6 +108,7 @@ type tfModelV0 struct {
PrivateLocations []types.String `tfsdk:"private_locations"`
Enabled types.Bool `tfsdk:"enabled"`
Tags []types.String `tfsdk:"tags"`
Labels types.Map `tfsdk:"labels"`
Alert types.Object `tfsdk:"alert"` //tfAlertConfigV0
APMServiceName types.String `tfsdk:"service_name"`
TimeoutSeconds types.Int64 `tfsdk:"timeout"`
Expand Down Expand Up @@ -217,6 +219,11 @@ func monitorConfigSchema() schema.Schema {
Optional: true,
MarkdownDescription: "An array of tags.",
},
"labels": schema.MapAttribute{
ElementType: types.StringType,
Optional: true,
MarkdownDescription: "Key-value pairs of labels to associate with the monitor. Labels can be used for filtering and grouping monitors.",
},
"alert": monitorAlertConfigSchema(),
"service_name": schema.StringAttribute{
Optional: true,
Expand Down Expand Up @@ -557,6 +564,31 @@ func StringSliceValue(v []string) []types.String {
return res
}

func MapStringValue(v map[string]string) types.Map {
if len(v) == 0 {
return types.MapNull(types.StringType)
}
elements := make(map[string]attr.Value)
for k, val := range v {
elements[k] = types.StringValue(val)
}
mapValue, _ := types.MapValue(types.StringType, elements)
return mapValue
}

func ValueStringMap(v types.Map) map[string]string {
if v.IsNull() || v.IsUnknown() {
return make(map[string]string)
}
result := make(map[string]string)
for k, val := range v.Elements() {
if strVal, ok := val.(types.String); ok {
result[k] = strVal.ValueString()
}
}
return result
}

func toNormalizedValue(jsObj kbapi.JsonObject) (jsontypes.Normalized, error) {
res, err := json.Marshal(jsObj)
if err != nil {
Expand Down Expand Up @@ -679,6 +711,7 @@ func (v *tfModelV0) toModelV0(ctx context.Context, api *kbapi.SyntheticsMonitor,
PrivateLocations: StringSliceValue(privateLocLabels),
Enabled: types.BoolPointerValue(api.Enabled),
Tags: StringSliceValue(api.Tags),
Labels: MapStringValue(api.Labels),
Alert: alertV0,
APMServiceName: types.StringValue(api.APMServiceName),
TimeoutSeconds: types.Int64Value(timeout),
Expand Down Expand Up @@ -901,6 +934,7 @@ func (v *tfModelV0) toSyntheticsMonitorConfig(ctx context.Context) (*kbapi.Synth
PrivateLocations: ValueStringSlice(v.PrivateLocations),
Enabled: v.Enabled.ValueBoolPointer(),
Tags: ValueStringSlice(v.Tags),
Labels: ValueStringMap(v.Labels),
Alert: toTFAlertConfig(ctx, v.Alert),
APMServiceName: v.APMServiceName.ValueString(),
TimeoutSeconds: int(v.TimeoutSeconds.ValueInt64()),
Expand Down Expand Up @@ -1068,3 +1102,24 @@ func (v tfStatusConfigV0) toTfStatusConfigV0() *kbapi.SyntheticsStatusConfig {
Enabled: v.Enabled.ValueBoolPointer(),
}
}

func (v tfModelV0) enforceVersionConstraints(ctx context.Context, client *clients.ApiClient) diag.Diagnostics {
if utils.IsKnown(v.Labels) {
isSupported, sdkDiags := client.EnforceMinVersion(ctx, MinLabelsVersion)
diags := diagutil.FrameworkDiagsFromSDK(sdkDiags)
if diags.HasError() {
return diags
}

if !isSupported {
diags.AddAttributeError(
path.Root("labels"),
"Unsupported version for `labels` attribute",
fmt.Sprintf("The `labels` attribute requires server version %s or higher. Either remove the `labels` attribute or upgrade your Elastic Stack installation.", MinLabelsVersion.String()),
)
return diags
}
}

return nil
}
Loading