diff --git a/CHANGELOG.md b/CHANGELOG.md index 129092171..454391f7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ ## [Unreleased] - Support 8.15.5 in acc tests ([#963](https://github.com/elastic/terraform-provider-elasticstack/pull/963)). +- Support 8.16.2 in acc tests ([#964](https://github.com/elastic/terraform-provider-elasticstack/pull/964)). +- Support several ssl fields in `elasticstack_kibana_synthetics_monitor` ([#967](https://github.com/elastic/terraform-provider-elasticstack/pull/967)) ## [0.11.12] - 2024-12-16 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2108d8cc3..9c69b9d6d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,3 +1,7 @@ +# Typical development workflow + +Fork the repo, work on an issue + ## Acceptance tests ```bash @@ -6,5 +10,41 @@ make docker-testacc Run a single test with terraform debug enabled: ```bash -make docker-testacc TF_LOG=DEBUG TESTARGS='-run ^TestAccResourceDataStreamLifecycle$$' -``` \ No newline at end of file +env TF_LOG=DEBUG make docker-testacc TESTARGS='-run ^TestAccResourceDataStreamLifecycle$$' +``` + +A way to forward debug logs to a file: +```bash +env TF_ACC_LOG_PATH=/tmp/tf.log TF_ACC_LOG=DEBUG TF_LOG=DEBUG make docker-testacc +``` + + +## Update documentation + +Update documentation templates in `./templates` directory and re-generate docs via: +```bash +make docs-generate +``` + +## Update `./CHANGELOG.md` + +List of previous commits is a good example of what should be included in the changelog. + + +## Pull request + +Format the code before pushing: +```bash +make fmt +``` + +Check if the linting: +```bash +make lint +``` + +Create a PR and check acceptance test matrix is green. + +## Run provider with local terraform + +TBD \ No newline at end of file diff --git a/docs/resources/kibana_synthetics_monitor.md b/docs/resources/kibana_synthetics_monitor.md index 1447afd71..fabdec368 100644 --- a/docs/resources/kibana_synthetics_monitor.md +++ b/docs/resources/kibana_synthetics_monitor.md @@ -144,6 +144,10 @@ Optional: - `proxy_header` (String) Additional headers to send to proxies during CONNECT requests.. Raw JSON object, use `jsonencode` function to represent JSON - `proxy_url` (String) The URL of the proxy to use for this monitor. - `response` (String) Controls the indexing of the HTTP response body contents to the `http.response.body.contents` field.. Raw JSON object, use `jsonencode` function to represent JSON +- `ssl_certificate` (String) Certificate. +- `ssl_certificate_authorities` (List of String) The list of root certificates for verifications is required. +- `ssl_key` (String, Sensitive) Certificate key. +- `ssl_key_passphrase` (String, Sensitive) Key passphrase. - `ssl_supported_protocols` (List of String) List of allowed SSL/TLS versions. - `ssl_verification_mode` (String) Controls the verification of server certificates. - `username` (String) The username for authenticating with the server. The credentials are passed with the request. @@ -174,6 +178,10 @@ Optional: - `check_send` (String) An optional payload string to send to the remote host. - `proxy_url` (String) The URL of the SOCKS5 proxy to use when connecting to the server. The value must be a URL with a scheme of `socks5://`. If the SOCKS5 proxy server requires client authentication, then a username and password can be embedded in the URL. When using a proxy, hostnames are resolved on the proxy server instead of on the client. You can change this behavior by setting the `proxy_use_local_resolver` option. - `proxy_use_local_resolver` (Boolean) A Boolean value that determines whether hostnames are resolved locally instead of being resolved on the proxy server. The default value is false, which means that name resolution occurs on the proxy server. +- `ssl_certificate` (String) Certificate. +- `ssl_certificate_authorities` (List of String) The list of root certificates for verifications is required. +- `ssl_key` (String, Sensitive) Certificate key. +- `ssl_key_passphrase` (String, Sensitive) Key passphrase. - `ssl_supported_protocols` (List of String) List of allowed SSL/TLS versions. - `ssl_verification_mode` (String) Controls the verification of server certificates. @@ -183,9 +191,4 @@ Import is supported using the following syntax: ```shell terraform import elasticstack_kibana_synthetics_monitor.my_monitor / -``` - -**NOTE:** Not all monitor fields are supported during the import due-to API limitation. -Full field support could be implemented after this [kibana issue](https://github.com/elastic/kibana/issues/189906) is resolved. - -Currently not supported fields during the import: `params`, `retest_on_failure`, `locations`, `http.proxy_header`, `http.username`, `http.password`, `http.check`, `http.response`, `tcp.check_send`, `tcp.check_receive` and monitor type `browser` +``` \ No newline at end of file diff --git a/internal/kibana/synthetics/acc_test.go b/internal/kibana/synthetics/acc_test.go index 664a5a847..dca2ed096 100644 --- a/internal/kibana/synthetics/acc_test.go +++ b/internal/kibana/synthetics/acc_test.go @@ -2,20 +2,31 @@ package synthetics_test import ( "fmt" - sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "testing" "github.com/elastic/terraform-provider-elasticstack/internal/acctest" "github.com/elastic/terraform-provider-elasticstack/internal/versionutils" "github.com/hashicorp/go-version" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) var ( minKibanaVersion = version.Must(version.NewVersion("8.14.0")) + kibana816Version = version.Must(version.NewVersion("8.16.0")) ) const ( + httpMonitorMinConfig = ` + +resource "elasticstack_kibana_synthetics_monitor" "%s" { + name = "TestHttpMonitorResource - %s" + private_locations = [elasticstack_kibana_synthetics_private_location.%s.label] + http = { + url = "http://localhost:5601" + } +} +` httpMonitorConfig = ` resource "elasticstack_kibana_synthetics_monitor" "%s" { @@ -42,6 +53,22 @@ resource "elasticstack_kibana_synthetics_monitor" "%s" { ipv6 = false } } +` + httpMonitorSslConfig = ` + +resource "elasticstack_kibana_synthetics_monitor" "%s" { + name = "TestHttpMonitorResource - %s" + private_locations = [elasticstack_kibana_synthetics_private_location.%s.label] + http = { + url = "http://localhost:5601" + ssl_verification_mode = "full" + ssl_supported_protocols = ["TLSv1.2"] + ssl_certificate_authorities = ["ca1", "ca2"] + ssl_certificate = "cert" + ssl_key = "key" + ssl_key_passphrase = "pass" + } +} ` httpMonitorUpdated = ` @@ -102,6 +129,34 @@ resource "elasticstack_kibana_synthetics_monitor" "%s" { retest_on_failure = false } +` + + tcpMonitorMinConfig = ` + +resource "elasticstack_kibana_synthetics_monitor" "%s" { + name = "TestTcpMonitorResource - %s" + private_locations = [elasticstack_kibana_synthetics_private_location.%s.label] + tcp = { + host = "http://localhost:5601" + } +} +` + + tcpMonitorSslConfig = ` + +resource "elasticstack_kibana_synthetics_monitor" "%s" { + name = "TestHttpMonitorResource - %s" + private_locations = [elasticstack_kibana_synthetics_private_location.%s.label] + tcp = { + host = "http://localhost:5601" + ssl_verification_mode = "full" + ssl_supported_protocols = ["TLSv1.2"] + ssl_certificate_authorities = ["ca1", "ca2"] + ssl_certificate = "cert" + ssl_key = "key" + ssl_key_passphrase = "pass" + } +} ` tcpMonitorConfig = ` @@ -160,6 +215,16 @@ resource "elasticstack_kibana_synthetics_monitor" "%s" { } ` + icmpMonitorMinConfig = ` + +resource "elasticstack_kibana_synthetics_monitor" "%s" { + name = "TestIcmpMonitorResource - %s" + private_locations = [elasticstack_kibana_synthetics_private_location.%s.label] + icmp = { + host = "localhost" + } +} +` icmpMonitorConfig = ` resource "elasticstack_kibana_synthetics_monitor" "%s" { @@ -218,6 +283,18 @@ resource "elasticstack_kibana_synthetics_monitor" "%s" { private_locations = [elasticstack_kibana_synthetics_private_location.%s.label] enabled = true tags = ["a", "b"] + service_name = "test apm service" + timeout = 30 + browser = { + inline_script = "step('Go to https://google.com.co', () => page.goto('https://www.google.com'))" + } +} +` + browserMonitorMinConfig = ` + +resource "elasticstack_kibana_synthetics_monitor" "%s" { + name = "TestBrowserMonitorResource - %s" + private_locations = [elasticstack_kibana_synthetics_private_location.%s.label] alert = { status = { enabled = true @@ -226,8 +303,6 @@ resource "elasticstack_kibana_synthetics_monitor" "%s" { enabled = true } } - service_name = "test apm service" - timeout = 30 browser = { inline_script = "step('Go to https://google.com.co', () => page.goto('https://www.google.com'))" } @@ -268,12 +343,60 @@ func TestSyntheticMonitorHTTPResource(t *testing.T) { name := sdkacctest.RandStringFromCharSet(22, sdkacctest.CharSetAlphaNum) id := "http-monitor" httpMonitorId, config := testMonitorConfig(id, httpMonitorConfig, name) + + bmName := sdkacctest.RandStringFromCharSet(22, sdkacctest.CharSetAlphaNum) + bmMonitorId, bmConfig := testMonitorConfig("http-monitor-min", httpMonitorMinConfig, bmName) + + sslName := sdkacctest.RandStringFromCharSet(22, sdkacctest.CharSetAlphaNum) + sslHttpMonitorId, sslConfig := testMonitorConfig("http-monitor-ssl", httpMonitorSslConfig, sslName) + _, configUpdated := testMonitorConfig(id, httpMonitorUpdated, name) resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ProtoV6ProviderFactories: acctest.Providers, Steps: []resource.TestStep{ + // Create and Read http monitor with minimum fields + { + SkipFunc: versionutils.CheckIfVersionIsUnsupported(minKibanaVersion), + Config: bmConfig, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(bmMonitorId, "id"), + resource.TestCheckResourceAttr(bmMonitorId, "name", "TestHttpMonitorResource - "+bmName), + resource.TestCheckResourceAttr(bmMonitorId, "space_id", "default"), + resource.TestCheckResourceAttr(bmMonitorId, "alert.status.enabled", "true"), + resource.TestCheckResourceAttr(bmMonitorId, "alert.tls.enabled", "true"), + resource.TestCheckResourceAttr(bmMonitorId, "http.url", "http://localhost:5601"), + ), + }, + // Create and Read http monitor with ssl fields, starting from ES 8.16.0 + { + SkipFunc: versionutils.CheckIfVersionIsUnsupported(kibana816Version), + Config: sslConfig, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(sslHttpMonitorId, "id"), + resource.TestCheckResourceAttr(sslHttpMonitorId, "name", "TestHttpMonitorResource - "+sslName), + resource.TestCheckResourceAttr(sslHttpMonitorId, "space_id", "default"), + resource.TestCheckResourceAttr(sslHttpMonitorId, "http.url", "http://localhost:5601"), + resource.TestCheckResourceAttr(sslHttpMonitorId, "http.ssl_verification_mode", "full"), + resource.TestCheckResourceAttr(sslHttpMonitorId, "http.ssl_supported_protocols.#", "1"), + resource.TestCheckResourceAttr(sslHttpMonitorId, "http.ssl_supported_protocols.0", "TLSv1.2"), + resource.TestCheckResourceAttr(sslHttpMonitorId, "http.ssl_certificate_authorities.#", "2"), + resource.TestCheckResourceAttr(sslHttpMonitorId, "http.ssl_certificate_authorities.0", "ca1"), + resource.TestCheckResourceAttr(sslHttpMonitorId, "http.ssl_certificate_authorities.1", "ca2"), + resource.TestCheckResourceAttr(sslHttpMonitorId, "http.ssl_certificate", "cert"), + resource.TestCheckResourceAttr(sslHttpMonitorId, "http.ssl_key", "key"), + resource.TestCheckResourceAttr(sslHttpMonitorId, "http.ssl_key_passphrase", "pass"), + ), + }, + // ImportState testing ssl fields + { + SkipFunc: versionutils.CheckIfVersionIsUnsupported(kibana816Version), + ResourceName: sslHttpMonitorId, + ImportState: true, + ImportStateVerify: true, + Config: sslConfig, + }, // Create and Read http monitor { SkipFunc: versionutils.CheckIfVersionIsUnsupported(minKibanaVersion), @@ -369,11 +492,58 @@ func TestSyntheticMonitorTCPResource(t *testing.T) { tcpMonitorId, config := testMonitorConfig(id, tcpMonitorConfig, name) _, configUpdated := testMonitorConfig(id, tcpMonitorUpdated, name) + bmName := sdkacctest.RandStringFromCharSet(22, sdkacctest.CharSetAlphaNum) + bmMonitorId, bmConfig := testMonitorConfig("tcp-monitor-min", tcpMonitorMinConfig, bmName) + + sslName := sdkacctest.RandStringFromCharSet(22, sdkacctest.CharSetAlphaNum) + sslTcpMonitorId, sslConfig := testMonitorConfig("tcp-monitor-ssl", tcpMonitorSslConfig, sslName) + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ProtoV6ProviderFactories: acctest.Providers, Steps: []resource.TestStep{ - + // Create and Read tcp monitor with minimum fields + { + SkipFunc: versionutils.CheckIfVersionIsUnsupported(minKibanaVersion), + Config: bmConfig, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(bmMonitorId, "id"), + resource.TestCheckResourceAttr(bmMonitorId, "name", "TestTcpMonitorResource - "+bmName), + resource.TestCheckResourceAttr(bmMonitorId, "space_id", "default"), + resource.TestCheckResourceAttr(bmMonitorId, "tcp.host", "http://localhost:5601"), + resource.TestCheckResourceAttr(bmMonitorId, "alert.status.enabled", "true"), + resource.TestCheckResourceAttr(bmMonitorId, "alert.tls.enabled", "true"), + ), + }, + // Create and Read tcp monitor with ssl fields, starting from ES 8.16.0 + // Create and Read tcp monitor with ssl fields, starting from ES 8.16.0 + { + SkipFunc: versionutils.CheckIfVersionIsUnsupported(kibana816Version), + Config: sslConfig, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(sslTcpMonitorId, "id"), + resource.TestCheckResourceAttr(sslTcpMonitorId, "name", "TestHttpMonitorResource - "+sslName), + resource.TestCheckResourceAttr(sslTcpMonitorId, "space_id", "default"), + resource.TestCheckResourceAttr(sslTcpMonitorId, "tcp.host", "http://localhost:5601"), + resource.TestCheckResourceAttr(sslTcpMonitorId, "tcp.ssl_verification_mode", "full"), + resource.TestCheckResourceAttr(sslTcpMonitorId, "tcp.ssl_supported_protocols.#", "1"), + resource.TestCheckResourceAttr(sslTcpMonitorId, "tcp.ssl_supported_protocols.0", "TLSv1.2"), + resource.TestCheckResourceAttr(sslTcpMonitorId, "tcp.ssl_certificate_authorities.#", "2"), + resource.TestCheckResourceAttr(sslTcpMonitorId, "tcp.ssl_certificate_authorities.0", "ca1"), + resource.TestCheckResourceAttr(sslTcpMonitorId, "tcp.ssl_certificate_authorities.1", "ca2"), + resource.TestCheckResourceAttr(sslTcpMonitorId, "tcp.ssl_certificate", "cert"), + resource.TestCheckResourceAttr(sslTcpMonitorId, "tcp.ssl_key", "key"), + resource.TestCheckResourceAttr(sslTcpMonitorId, "tcp.ssl_key_passphrase", "pass"), + ), + }, + // ImportState testing ssl fields + { + SkipFunc: versionutils.CheckIfVersionIsUnsupported(kibana816Version), + ResourceName: sslTcpMonitorId, + ImportState: true, + ImportStateVerify: true, + Config: sslConfig, + }, // Create and Read tcp monitor { SkipFunc: versionutils.CheckIfVersionIsUnsupported(minKibanaVersion), @@ -459,10 +629,26 @@ func TestSyntheticMonitorICMPResource(t *testing.T) { icmpMonitorId, config := testMonitorConfig(id, icmpMonitorConfig, name) _, configUpdated := testMonitorConfig(id, icmpMonitorUpdated, name) + bmName := sdkacctest.RandStringFromCharSet(22, sdkacctest.CharSetAlphaNum) + bmMonitorId, bmConfig := testMonitorConfig("icmp-monitor-min", icmpMonitorMinConfig, bmName) + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ProtoV6ProviderFactories: acctest.Providers, Steps: []resource.TestStep{ + // Create and Read icmp monitor with minimum fields + { + SkipFunc: versionutils.CheckIfVersionIsUnsupported(minKibanaVersion), + Config: bmConfig, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(bmMonitorId, "id"), + resource.TestCheckResourceAttr(bmMonitorId, "name", "TestIcmpMonitorResource - "+bmName), + resource.TestCheckResourceAttr(bmMonitorId, "space_id", "default"), + resource.TestCheckResourceAttr(bmMonitorId, "icmp.host", "localhost"), + resource.TestCheckResourceAttr(bmMonitorId, "alert.status.enabled", "true"), + resource.TestCheckResourceAttr(bmMonitorId, "alert.tls.enabled", "true"), + ), + }, // Create and Read icmp monitor { @@ -535,11 +721,26 @@ func TestSyntheticMonitorBrowserResource(t *testing.T) { browserMonitorId, config := testMonitorConfig(id, browserMonitorConfig, name) _, configUpdated := testMonitorConfig(id, browserMonitorUpdated, name) + bmName := sdkacctest.RandStringFromCharSet(22, sdkacctest.CharSetAlphaNum) + bmMonitorId, bmConfig := testMonitorConfig("browser-monitor-min", browserMonitorMinConfig, bmName) + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ProtoV6ProviderFactories: acctest.Providers, Steps: []resource.TestStep{ - + // Create and Read browser monitor with minimum fields + { + SkipFunc: versionutils.CheckIfVersionIsUnsupported(minKibanaVersion), + Config: bmConfig, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(bmMonitorId, "id"), + resource.TestCheckResourceAttr(bmMonitorId, "name", "TestBrowserMonitorResource - "+bmName), + resource.TestCheckResourceAttr(bmMonitorId, "space_id", "default"), + resource.TestCheckResourceAttr(bmMonitorId, "browser.inline_script", "step('Go to https://google.com.co', () => page.goto('https://www.google.com'))"), + resource.TestCheckResourceAttr(bmMonitorId, "alert.status.enabled", "true"), + resource.TestCheckResourceAttr(bmMonitorId, "alert.tls.enabled", "true"), + ), + }, // Create and Read browser monitor { SkipFunc: versionutils.CheckIfVersionIsUnsupported(minKibanaVersion), @@ -562,15 +763,15 @@ func TestSyntheticMonitorBrowserResource(t *testing.T) { resource.TestCheckResourceAttr(browserMonitorId, "browser.inline_script", "step('Go to https://google.com.co', () => page.goto('https://www.google.com'))"), ), }, - // ImportState testing - kibana doesn't return required parameter inline_script for browser monitor, so import state is not supported till the fix - /* { - SkipFunc: versionutils.CheckIfVersionIsUnsupported(minKibanaVersion), - ResourceName: browserMonitorId, - ImportState: true, - ImportStateVerify: true, - Config: config, - }, - */ // Update and Read browser monitor + // ImportState testing + { + SkipFunc: versionutils.CheckIfVersionIsUnsupported(kibana816Version), + ResourceName: browserMonitorId, + ImportState: true, + ImportStateVerify: true, + Config: config, + }, + // Update and Read browser monitor { SkipFunc: versionutils.CheckIfVersionIsUnsupported(minKibanaVersion), ResourceName: browserMonitorId, diff --git a/internal/kibana/synthetics/schema.go b/internal/kibana/synthetics/schema.go index 8fc9c0fae..3f5e3ceb7 100644 --- a/internal/kibana/synthetics/schema.go +++ b/internal/kibana/synthetics/schema.go @@ -11,16 +11,20 @@ import ( "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" "strconv" ) @@ -42,30 +46,39 @@ type tfAlertConfigV0 struct { TLS *tfStatusConfigV0 `tfsdk:"tls"` } +type tfSSLConfig struct { + SslVerificationMode types.String `tfsdk:"ssl_verification_mode"` + SslSupportedProtocols types.List `tfsdk:"ssl_supported_protocols"` + SslCertificateAuthorities []types.String `tfsdk:"ssl_certificate_authorities"` + SslCertificate types.String `tfsdk:"ssl_certificate"` + SslKey types.String `tfsdk:"ssl_key"` + SslKeyPassphrase types.String `tfsdk:"ssl_key_passphrase"` +} + type tfHTTPMonitorFieldsV0 struct { - URL types.String `tfsdk:"url"` - SslVerificationMode types.String `tfsdk:"ssl_verification_mode"` - SslSupportedProtocols types.List `tfsdk:"ssl_supported_protocols"` - MaxRedirects types.Int64 `tfsdk:"max_redirects"` - Mode types.String `tfsdk:"mode"` - IPv4 types.Bool `tfsdk:"ipv4"` - IPv6 types.Bool `tfsdk:"ipv6"` - ProxyURL types.String `tfsdk:"proxy_url"` - ProxyHeader jsontypes.Normalized `tfsdk:"proxy_header"` - Username types.String `tfsdk:"username"` - Password types.String `tfsdk:"password"` - Response jsontypes.Normalized `tfsdk:"response"` - Check jsontypes.Normalized `tfsdk:"check"` + URL types.String `tfsdk:"url"` + MaxRedirects types.Int64 `tfsdk:"max_redirects"` + Mode types.String `tfsdk:"mode"` + IPv4 types.Bool `tfsdk:"ipv4"` + IPv6 types.Bool `tfsdk:"ipv6"` + ProxyURL types.String `tfsdk:"proxy_url"` + ProxyHeader jsontypes.Normalized `tfsdk:"proxy_header"` + Username types.String `tfsdk:"username"` + Password types.String `tfsdk:"password"` + Response jsontypes.Normalized `tfsdk:"response"` + Check jsontypes.Normalized `tfsdk:"check"` + + tfSSLConfig } type tfTCPMonitorFieldsV0 struct { Host types.String `tfsdk:"host"` - SslVerificationMode types.String `tfsdk:"ssl_verification_mode"` - SslSupportedProtocols types.List `tfsdk:"ssl_supported_protocols"` CheckSend types.String `tfsdk:"check_send"` CheckReceive types.String `tfsdk:"check_receive"` ProxyURL types.String `tfsdk:"proxy_url"` ProxyUseLocalResolver types.Bool `tfsdk:"proxy_use_local_resolver"` + + tfSSLConfig } type tfICMPMonitorFieldsV0 struct { @@ -90,7 +103,7 @@ type tfModelV0 struct { PrivateLocations []types.String `tfsdk:"private_locations"` Enabled types.Bool `tfsdk:"enabled"` Tags []types.String `tfsdk:"tags"` - Alert *tfAlertConfigV0 `tfsdk:"alert"` + Alert types.Object `tfsdk:"alert"` //tfAlertConfigV0 APMServiceName types.String `tfsdk:"service_name"` TimeoutSeconds types.Int64 `tfsdk:"timeout"` HTTP *tfHTTPMonitorFieldsV0 `tfsdk:"http"` @@ -132,8 +145,10 @@ func monitorConfigSchema() schema.Schema { MarkdownDescription: "The namespace field should be lowercase and not contain spaces. The namespace must not include any of the following characters: *, \\, /, ?, \", <, >, |, whitespace, ,, #, :, or -. Default: `default`", Optional: true, PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), stringplanmodifier.RequiresReplace(), }, + Computed: true, }, "schedule": schema.Int64Attribute{ Optional: true, @@ -141,6 +156,8 @@ func monitorConfigSchema() schema.Schema { Validators: []validator.Int64{ int64validator.OneOf(1, 3, 5, 10, 15, 30, 60, 120, 240), }, + Computed: true, + PlanModifiers: []planmodifier.Int64{int64planmodifier.UseStateForUnknown()}, }, "locations": schema.ListAttribute{ ElementType: types.StringType, @@ -171,6 +188,8 @@ func monitorConfigSchema() schema.Schema { "enabled": schema.BoolAttribute{ Optional: true, MarkdownDescription: "Whether the monitor is enabled. Default: `true`", + Computed: true, + PlanModifiers: []planmodifier.Bool{boolplanmodifier.UseStateForUnknown()}, }, "tags": schema.ListAttribute{ ElementType: types.StringType, @@ -181,10 +200,14 @@ func monitorConfigSchema() schema.Schema { "service_name": schema.StringAttribute{ Optional: true, MarkdownDescription: "The APM service name.", + Computed: true, + PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}, }, "timeout": schema.Int64Attribute{ Optional: true, MarkdownDescription: "The monitor timeout in seconds, monitor will fail if it doesn’t complete within this time. Default: `16`", + Computed: true, + PlanModifiers: []planmodifier.Int64{int64planmodifier.UseStateForUnknown()}, }, "params": jsonObjectSchema("Monitor parameters"), "http": httpMonitorFieldsSchema(), @@ -267,7 +290,9 @@ func statusConfigSchema() schema.Attribute { Optional: true, Attributes: map[string]schema.Attribute{ "enabled": schema.BoolAttribute{ - Optional: true, + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Bool{boolplanmodifier.UseStateForUnknown()}, }, }, } @@ -281,6 +306,8 @@ func monitorAlertConfigSchema() schema.Attribute { "status": statusConfigSchema(), "tls": statusConfigSchema(), }, + Computed: true, + PlanModifiers: []planmodifier.Object{objectplanmodifier.UseStateForUnknown()}, } } @@ -307,6 +334,31 @@ func httpMonitorFieldsSchema() schema.Attribute { Computed: true, PlanModifiers: []planmodifier.List{listplanmodifier.UseStateForUnknown()}, }, + "ssl_certificate_authorities": schema.ListAttribute{ + ElementType: types.StringType, + Optional: true, + MarkdownDescription: "The list of root certificates for verifications is required.", + }, + "ssl_certificate": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Certificate.", + Computed: true, + PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}, + }, + "ssl_key": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Certificate key.", + Computed: true, + PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}, + Sensitive: true, + }, + "ssl_key_passphrase": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Key passphrase.", + Computed: true, + PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}, + Sensitive: true, + }, "max_redirects": schema.Int64Attribute{ Optional: true, MarkdownDescription: "The maximum number of redirects to follow. Default: `0`", @@ -319,14 +371,20 @@ func httpMonitorFieldsSchema() schema.Attribute { Validators: []validator.String{ stringvalidator.OneOf("any", "all"), }, + Computed: true, + PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}, }, "ipv4": schema.BoolAttribute{ Optional: true, MarkdownDescription: "Whether to ping using the ipv4 protocol.", + Computed: true, + PlanModifiers: []planmodifier.Bool{boolplanmodifier.UseStateForUnknown()}, }, "ipv6": schema.BoolAttribute{ Optional: true, MarkdownDescription: "Whether to ping using the ipv6 protocol.", + Computed: true, + PlanModifiers: []planmodifier.Bool{boolplanmodifier.UseStateForUnknown()}, }, "username": schema.StringAttribute{ Optional: true, @@ -372,6 +430,31 @@ func tcpMonitorFieldsSchema() schema.Attribute { Computed: true, PlanModifiers: []planmodifier.List{listplanmodifier.UseStateForUnknown()}, }, + "ssl_certificate_authorities": schema.ListAttribute{ + ElementType: types.StringType, + Optional: true, + MarkdownDescription: "The list of root certificates for verifications is required.", + }, + "ssl_certificate": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Certificate.", + Computed: true, + PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}, + }, + "ssl_key": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Certificate key.", + Computed: true, + PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}, + Sensitive: true, + }, + "ssl_key_passphrase": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Key passphrase.", + Computed: true, + PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}, + Sensitive: true, + }, "check_send": schema.StringAttribute{ Optional: true, MarkdownDescription: "An optional payload string to send to the remote host.", @@ -389,6 +472,8 @@ func tcpMonitorFieldsSchema() schema.Attribute { "proxy_use_local_resolver": schema.BoolAttribute{ Optional: true, MarkdownDescription: " A Boolean value that determines whether hostnames are resolved locally instead of being resolved on the proxy server. The default value is false, which means that name resolution occurs on the proxy server.", + Computed: true, + PlanModifiers: []planmodifier.Bool{boolplanmodifier.UseStateForUnknown()}, }, }, } @@ -558,6 +643,11 @@ func (v *tfModelV0) toModelV0(ctx context.Context, api *kbapi.SyntheticsMonitor) ResourceId: string(api.Id), } + alertV0, dg := toTfAlertConfigV0(ctx, api.Alert) + if dg.HasError() { + return nil, dg + } + return &tfModelV0{ ID: types.StringValue(resourceID.String()), Name: types.StringValue(api.Name), @@ -567,7 +657,7 @@ func (v *tfModelV0) toModelV0(ctx context.Context, api *kbapi.SyntheticsMonitor) PrivateLocations: StringSliceValue(privateLocLabels), Enabled: types.BoolPointerValue(api.Enabled), Tags: StringSliceValue(api.Tags), - Alert: toTfAlertConfigV0(api.Alert), + Alert: alertV0, APMServiceName: types.StringValue(api.APMServiceName), TimeoutSeconds: types.Int64Value(timeout), Params: params, @@ -588,18 +678,18 @@ func (v *tfTCPMonitorFieldsV0) toTfTCPMonitorFieldsV0(ctx context.Context, dg di if api.CheckReceive != "" { checkReceive = types.StringValue(api.CheckReceive) } - sslSupportedProtocols := utils.SliceToListType_String(ctx, api.SslSupportedProtocols, path.Root("tcp").AtName("ssl_supported_protocols"), &dg) + sslCfg, dg := toTFSSLConfig(ctx, dg, api, "tcp") + if dg.HasError() { return nil } return &tfTCPMonitorFieldsV0{ Host: types.StringValue(api.Host), - SslVerificationMode: types.StringValue(api.SslVerificationMode), - SslSupportedProtocols: sslSupportedProtocols, CheckSend: checkSend, CheckReceive: checkReceive, ProxyURL: types.StringValue(api.ProxyUrl), ProxyUseLocalResolver: types.BoolPointerValue(api.ProxyUseLocalResolver), + tfSSLConfig: sslCfg, } } @@ -671,36 +761,56 @@ func (v *tfHTTPMonitorFieldsV0) toTfHTTPMonitorFieldsV0(ctx context.Context, dg return nil } - sslSupportedProtocols := utils.SliceToListType_String(ctx, api.SslSupportedProtocols, path.Root("http").AtName("ssl_supported_protocols"), &dg) - + sslCfg, dg := toTFSSLConfig(ctx, dg, api, "http") if dg.HasError() { return nil } return &tfHTTPMonitorFieldsV0{ - URL: types.StringValue(api.Url), - SslVerificationMode: types.StringValue(api.SslVerificationMode), - SslSupportedProtocols: sslSupportedProtocols, - MaxRedirects: types.Int64Value(maxRedirects), - Mode: types.StringValue(string(api.Mode)), - IPv4: types.BoolPointerValue(api.Ipv4), - IPv6: types.BoolPointerValue(api.Ipv6), - Username: username, - Password: password, - ProxyHeader: proxyHeaders, - ProxyURL: types.StringValue(api.ProxyUrl), - Check: v.Check, - Response: v.Response, + URL: types.StringValue(api.Url), + MaxRedirects: types.Int64Value(maxRedirects), + Mode: types.StringValue(string(api.Mode)), + IPv4: types.BoolPointerValue(api.Ipv4), + IPv6: types.BoolPointerValue(api.Ipv6), + Username: username, + Password: password, + ProxyHeader: proxyHeaders, + ProxyURL: types.StringValue(api.ProxyUrl), + Check: v.Check, + Response: v.Response, + tfSSLConfig: sslCfg, } } -func toTfAlertConfigV0(alert *kbapi.MonitorAlertConfig) *tfAlertConfigV0 { +func toTFSSLConfig(ctx context.Context, dg diag.Diagnostics, api *kbapi.SyntheticsMonitor, p string) (tfSSLConfig, diag.Diagnostics) { + sslSupportedProtocols := utils.SliceToListType_String(ctx, api.SslSupportedProtocols, path.Root(p).AtName("ssl_supported_protocols"), &dg) + return tfSSLConfig{ + SslVerificationMode: types.StringValue(api.SslVerificationMode), + SslSupportedProtocols: sslSupportedProtocols, + SslCertificateAuthorities: StringSliceValue(api.SslCertificateAuthorities), + SslCertificate: types.StringValue(api.SslCertificate), + SslKey: types.StringValue(api.SslKey), + SslKeyPassphrase: types.StringValue(api.SslKeyPassphrase), + }, dg +} + +func toTfAlertConfigV0(ctx context.Context, alert *kbapi.MonitorAlertConfig) (basetypes.ObjectValue, diag.Diagnostics) { + + dg := diag.Diagnostics{} + + alertAttributes := monitorAlertConfigSchema().GetType().(attr.TypeWithAttributeTypes).AttributeTypes() + + var emptyAttr = map[string]attr.Type(nil) + if alert == nil { - return nil + return basetypes.NewObjectNull(emptyAttr), dg } - return &tfAlertConfigV0{ + + tfAlertConfig := tfAlertConfigV0{ Status: toTfStatusConfigV0(alert.Status), TLS: toTfStatusConfigV0(alert.Tls), } + + return types.ObjectValueFrom(ctx, alertAttributes, &tfAlertConfig) } func toTfStatusConfigV0(status *kbapi.SyntheticsStatusConfig) *tfStatusConfigV0 { @@ -718,7 +828,7 @@ func (v *tfModelV0) toKibanaAPIRequest(ctx context.Context) (*kibanaAPIRequest, if dg.HasError() { return nil, dg } - config, dg := v.toSyntheticsMonitorConfig() + config, dg := v.toSyntheticsMonitorConfig(ctx) if dg.HasError() { return nil, dg } @@ -745,18 +855,23 @@ func (v *tfModelV0) toMonitorFields(ctx context.Context) (kbapi.MonitorFields, d return nil, dg } -func (v *tfModelV0) toSyntheticsMonitorConfig() (*kbapi.SyntheticsMonitorConfig, diag.Diagnostics) { +func toTFAlertConfig(ctx context.Context, v basetypes.ObjectValue) *kbapi.MonitorAlertConfig { + var alert *kbapi.MonitorAlertConfig + if !(v.IsNull() || v.IsUnknown()) { + tfAlert := tfAlertConfigV0{} + tfsdk.ValueAs(ctx, v, &tfAlert) + alert = tfAlert.toTfAlertConfigV0() + } + return alert +} + +func (v *tfModelV0) toSyntheticsMonitorConfig(ctx context.Context) (*kbapi.SyntheticsMonitorConfig, diag.Diagnostics) { locations := Map[types.String, kbapi.MonitorLocation](v.Locations, func(s types.String) kbapi.MonitorLocation { return kbapi.MonitorLocation(s.ValueString()) }) params, dg := toJsonObject(v.Params) if dg.HasError() { return nil, dg } - var alert *kbapi.MonitorAlertConfig - if v.Alert != nil { - alert = v.Alert.toTfAlertConfigV0() - } - return &kbapi.SyntheticsMonitorConfig{ Name: v.Name.ValueString(), Schedule: kbapi.MonitorSchedule(v.Schedule.ValueInt64()), @@ -764,7 +879,7 @@ func (v *tfModelV0) toSyntheticsMonitorConfig() (*kbapi.SyntheticsMonitorConfig, PrivateLocations: ValueStringSlice(v.PrivateLocations), Enabled: v.Enabled.ValueBoolPointer(), Tags: ValueStringSlice(v.Tags), - Alert: alert, + Alert: toTFAlertConfig(ctx, v.Alert), APMServiceName: v.APMServiceName.ValueString(), TimeoutSeconds: int(v.TimeoutSeconds.ValueInt64()), Namespace: v.SpaceID.ValueString(), @@ -781,58 +896,104 @@ func tfInt64ToString(v types.Int64) string { return res } +func toSSLConfig(ctx context.Context, dg diag.Diagnostics, v tfSSLConfig, p string) (*kbapi.SSLConfig, diag.Diagnostics) { + + var ssl *kbapi.SSLConfig + if !(v.SslSupportedProtocols.IsNull() || v.SslSupportedProtocols.IsUnknown()) { + sslSupportedProtocols := utils.ListTypeToSlice_String(ctx, v.SslSupportedProtocols, path.Root(p).AtName("ssl_supported_protocols"), &dg) + if dg.HasError() { + return nil, dg + } + ssl = &kbapi.SSLConfig{} + ssl.SupportedProtocols = sslSupportedProtocols + } + + if !(v.SslVerificationMode.IsNull() || v.SslVerificationMode.IsUnknown()) { + if ssl == nil { + ssl = &kbapi.SSLConfig{} + } + ssl.VerificationMode = v.SslVerificationMode.ValueString() + } + + certAuths := ValueStringSlice(v.SslCertificateAuthorities) + if len(certAuths) > 0 { + if ssl == nil { + ssl = &kbapi.SSLConfig{} + } + ssl.CertificateAuthorities = certAuths + } + + if !(v.SslCertificate.IsUnknown() || v.SslCertificate.IsNull()) { + if ssl == nil { + ssl = &kbapi.SSLConfig{} + } + ssl.Certificate = v.SslCertificate.ValueString() + } + + if !(v.SslKey.IsUnknown() || v.SslKey.IsNull()) { + if ssl == nil { + ssl = &kbapi.SSLConfig{} + } + ssl.Key = v.SslKey.ValueString() + } + + if !(v.SslKeyPassphrase.IsUnknown() || v.SslKeyPassphrase.IsNull()) { + if ssl == nil { + ssl = &kbapi.SSLConfig{} + } + ssl.KeyPassphrase = v.SslKeyPassphrase.ValueString() + } + return ssl, dg +} + func (v *tfModelV0) toHttpMonitorFields(ctx context.Context) (kbapi.MonitorFields, diag.Diagnostics) { - proxyHeaders, dg := toJsonObject(v.HTTP.ProxyHeader) + http := v.HTTP + proxyHeaders, dg := toJsonObject(http.ProxyHeader) if dg.HasError() { return nil, dg } - response, dg := toJsonObject(v.HTTP.Response) + response, dg := toJsonObject(http.Response) if dg.HasError() { return nil, dg } - check, dg := toJsonObject(v.HTTP.Check) + check, dg := toJsonObject(http.Check) if dg.HasError() { return nil, dg } - sslSupportedProtocols := utils.ListTypeToSlice_String(ctx, v.HTTP.SslSupportedProtocols, path.Root("http").AtName("ssl_supported_protocols"), &dg) - if dg.HasError() { - return nil, dg - } + ssl, dg := toSSLConfig(ctx, dg, http.tfSSLConfig, "http") - maxRedirects := tfInt64ToString(v.HTTP.MaxRedirects) + maxRedirects := tfInt64ToString(http.MaxRedirects) return kbapi.HTTPMonitorFields{ - Url: v.HTTP.URL.ValueString(), - SslVerificationMode: v.HTTP.SslVerificationMode.ValueString(), - SslSupportedProtocols: sslSupportedProtocols, - MaxRedirects: maxRedirects, - Mode: kbapi.HttpMonitorMode(v.HTTP.Mode.ValueString()), - Ipv4: v.HTTP.IPv4.ValueBoolPointer(), - Ipv6: v.HTTP.IPv6.ValueBoolPointer(), - Username: v.HTTP.Username.ValueString(), - Password: v.HTTP.Password.ValueString(), - ProxyHeader: proxyHeaders, - ProxyUrl: v.HTTP.ProxyURL.ValueString(), - Response: response, - Check: check, + Url: http.URL.ValueString(), + Ssl: ssl, + MaxRedirects: maxRedirects, + Mode: kbapi.HttpMonitorMode(http.Mode.ValueString()), + Ipv4: http.IPv4.ValueBoolPointer(), + Ipv6: http.IPv6.ValueBoolPointer(), + Username: http.Username.ValueString(), + Password: http.Password.ValueString(), + ProxyHeader: proxyHeaders, + ProxyUrl: http.ProxyURL.ValueString(), + Response: response, + Check: check, }, dg } func (v *tfModelV0) toTCPMonitorFields(ctx context.Context) (kbapi.MonitorFields, diag.Diagnostics) { + + tcp := v.TCP + dg := diag.Diagnostics{} - sslSupportedProtocols := utils.ListTypeToSlice_String(ctx, v.TCP.SslSupportedProtocols, path.Root("tcp").AtName("ssl_supported_protocols"), &dg) - if dg.HasError() { - return nil, dg - } + ssl, dg := toSSLConfig(ctx, dg, tcp.tfSSLConfig, "tcp") return kbapi.TCPMonitorFields{ - Host: v.TCP.Host.ValueString(), - SslVerificationMode: v.TCP.SslVerificationMode.ValueString(), - SslSupportedProtocols: sslSupportedProtocols, - CheckSend: v.TCP.CheckSend.ValueString(), - CheckReceive: v.TCP.CheckReceive.ValueString(), - ProxyUrl: v.TCP.ProxyURL.ValueString(), - ProxyUseLocalResolver: v.TCP.ProxyUseLocalResolver.ValueBoolPointer(), + Host: tcp.Host.ValueString(), + CheckSend: tcp.CheckSend.ValueString(), + CheckReceive: tcp.CheckReceive.ValueString(), + ProxyUrl: tcp.ProxyURL.ValueString(), + ProxyUseLocalResolver: tcp.ProxyUseLocalResolver.ValueBoolPointer(), + Ssl: ssl, }, dg } diff --git a/internal/kibana/synthetics/schema_test.go b/internal/kibana/synthetics/schema_test.go index 509d9fb12..1711530bf 100644 --- a/internal/kibana/synthetics/schema_test.go +++ b/internal/kibana/synthetics/schema_test.go @@ -3,6 +3,7 @@ package synthetics import ( "context" "encoding/json" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" "testing" "github.com/disaster37/go-kibana-rest/v8/kbapi" @@ -23,6 +24,16 @@ func boolPointer(v bool) *bool { return res } +func toAlertObject(t *testing.T, v tfAlertConfigV0) basetypes.ObjectValue { + + alertAttributes := monitorAlertConfigSchema().GetType().(attr.TypeWithAttributeTypes).AttributeTypes() + from, dg := types.ObjectValueFrom(context.Background(), alertAttributes, &v) + if dg.HasError() { + t.Fatalf("Failed to create Alert object: %v", dg) + } + return from +} + func TestToModelV0(t *testing.T) { testcases := []struct { name string @@ -43,17 +54,23 @@ func TestToModelV0(t *testing.T) { TimeoutSeconds: types.Int64Value(0), Params: jsontypes.NewNormalizedValue("null"), HTTP: &tfHTTPMonitorFieldsV0{ - URL: types.StringValue(""), - SslVerificationMode: types.StringValue(""), - MaxRedirects: types.Int64Value(0), - Mode: types.StringValue(""), - Username: types.StringValue(""), - Password: types.StringValue(""), - ProxyHeader: jsontypes.NewNormalizedValue("null"), - ProxyURL: types.StringValue(""), - Response: jsontypes.NewNormalizedValue("null"), - Check: jsontypes.NewNormalizedValue("null"), - SslSupportedProtocols: types.ListNull(types.StringType), + URL: types.StringValue(""), + MaxRedirects: types.Int64Value(0), + Mode: types.StringValue(""), + Username: types.StringValue(""), + Password: types.StringValue(""), + ProxyHeader: jsontypes.NewNormalizedValue("null"), + ProxyURL: types.StringValue(""), + Response: jsontypes.NewNormalizedValue("null"), + Check: jsontypes.NewNormalizedValue("null"), + + tfSSLConfig: tfSSLConfig{ + SslVerificationMode: types.StringValue(""), + SslSupportedProtocols: types.ListNull(types.StringType), + SslCertificate: types.StringValue(""), + SslKey: types.StringValue(""), + SslKeyPassphrase: types.StringValue(""), + }, }, }, }, @@ -71,12 +88,17 @@ func TestToModelV0(t *testing.T) { TimeoutSeconds: types.Int64Value(0), Params: jsontypes.NewNormalizedValue("null"), TCP: &tfTCPMonitorFieldsV0{ - Host: types.StringValue(""), - SslVerificationMode: types.StringValue(""), - CheckSend: types.StringValue(""), - CheckReceive: types.StringValue(""), - ProxyURL: types.StringValue(""), - SslSupportedProtocols: types.ListNull(types.StringType), + Host: types.StringValue(""), + CheckSend: types.StringValue(""), + CheckReceive: types.StringValue(""), + ProxyURL: types.StringValue(""), + tfSSLConfig: tfSSLConfig{ + SslVerificationMode: types.StringValue(""), + SslSupportedProtocols: types.ListNull(types.StringType), + SslCertificate: types.StringValue(""), + SslKey: types.StringValue(""), + SslKeyPassphrase: types.StringValue(""), + }, }, }, }, @@ -160,6 +182,10 @@ func TestToModelV0(t *testing.T) { CheckRequestMethod: "POST", SslVerificationMode: "full", SslSupportedProtocols: []string{"TLSv1.2", "TLSv1.3"}, + SslCertificateAuthorities: []string{"cert1", "cert2"}, + SslCertificate: "cert", + SslKey: "key", + SslKeyPassphrase: "passphrase", }, expected: tfModelV0{ ID: types.StringValue("default/test-id-http"), @@ -170,16 +196,12 @@ func TestToModelV0(t *testing.T) { PrivateLocations: []types.String{types.StringValue("test private location")}, Enabled: types.BoolPointerValue(tBool), Tags: []types.String{types.StringValue("tag1"), types.StringValue("tag2")}, - Alert: &tfAlertConfigV0{Status: &tfStatusConfigV0{Enabled: types.BoolPointerValue(tBool)}, TLS: &tfStatusConfigV0{Enabled: types.BoolPointerValue(fBool)}}, + Alert: toAlertObject(t, tfAlertConfigV0{Status: &tfStatusConfigV0{Enabled: types.BoolPointerValue(tBool)}, TLS: &tfStatusConfigV0{Enabled: types.BoolPointerValue(fBool)}}), APMServiceName: types.StringValue("test-service-http"), TimeoutSeconds: types.Int64Value(30), Params: jsontypes.NewNormalizedValue(`{"param1":"value1"}`), HTTP: &tfHTTPMonitorFieldsV0{ - URL: types.StringValue("https://example.com"), - SslVerificationMode: types.StringValue("full"), - SslSupportedProtocols: types.ListValueMust(types.StringType, []attr.Value{ - types.StringValue("TLSv1.2"), types.StringValue("TLSv1.3"), - }), + URL: types.StringValue("https://example.com"), MaxRedirects: types.Int64Value(5), Mode: types.StringValue("all"), IPv4: types.BoolPointerValue(tBool), @@ -188,6 +210,17 @@ func TestToModelV0(t *testing.T) { Password: types.StringValue("pass"), ProxyHeader: jsontypes.NewNormalizedValue(`{"header1":"value1"}`), ProxyURL: types.StringValue("https://proxy.com"), + + tfSSLConfig: tfSSLConfig{ + SslVerificationMode: types.StringValue("full"), + SslSupportedProtocols: types.ListValueMust(types.StringType, []attr.Value{ + types.StringValue("TLSv1.2"), types.StringValue("TLSv1.3"), + }), + SslCertificateAuthorities: []types.String{types.StringValue("cert1"), types.StringValue("cert2")}, + SslCertificate: types.StringValue("cert"), + SslKey: types.StringValue("key"), + SslKeyPassphrase: types.StringValue("passphrase"), + }, }, }, }, @@ -206,19 +239,23 @@ func TestToModelV0(t *testing.T) { Locations: []kbapi.MonitorLocationConfig{ {Label: "test private location", IsServiceManaged: false}, }, - Origin: "origin", - Params: kbapi.JsonObject{"param1": "value1"}, - MaxAttempts: 3, - Revision: 1, - Ui: kbapi.JsonObject{"is_tls_enabled": false}, - Type: kbapi.Tcp, - SslVerificationMode: "full", - SslSupportedProtocols: []string{"TLSv1.2", "TLSv1.3"}, - ProxyUrl: "http://proxy.com", - Host: "example.com:9200", - CheckSend: "hello", - CheckReceive: "world", - ProxyUseLocalResolver: tBool, + Origin: "origin", + Params: kbapi.JsonObject{"param1": "value1"}, + MaxAttempts: 3, + Revision: 1, + Ui: kbapi.JsonObject{"is_tls_enabled": false}, + Type: kbapi.Tcp, + SslVerificationMode: "full", + SslSupportedProtocols: []string{"TLSv1.2", "TLSv1.3"}, + SslCertificateAuthorities: []string{"cert1", "cert2"}, + SslCertificate: "cert", + SslKey: "key", + SslKeyPassphrase: "passphrase", + ProxyUrl: "http://proxy.com", + Host: "example.com:9200", + CheckSend: "hello", + CheckReceive: "world", + ProxyUseLocalResolver: tBool, }, expected: tfModelV0{ ID: types.StringValue("default/test-id-tcp"), @@ -229,20 +266,26 @@ func TestToModelV0(t *testing.T) { PrivateLocations: []types.String{types.StringValue("test private location")}, Enabled: types.BoolPointerValue(tBool), Tags: nil, - Alert: &tfAlertConfigV0{Status: &tfStatusConfigV0{Enabled: types.BoolPointerValue(tBool)}}, + Alert: toAlertObject(t, tfAlertConfigV0{Status: &tfStatusConfigV0{Enabled: types.BoolPointerValue(tBool)}}), APMServiceName: types.StringValue("test-service-tcp"), TimeoutSeconds: types.Int64Value(30), Params: jsontypes.NewNormalizedValue(`{"param1":"value1"}`), TCP: &tfTCPMonitorFieldsV0{ - Host: types.StringValue("example.com:9200"), - SslVerificationMode: types.StringValue("full"), - SslSupportedProtocols: types.ListValueMust(types.StringType, []attr.Value{ - types.StringValue("TLSv1.2"), types.StringValue("TLSv1.3"), - }), + Host: types.StringValue("example.com:9200"), CheckSend: types.StringValue("hello"), CheckReceive: types.StringValue("world"), ProxyURL: types.StringValue("http://proxy.com"), ProxyUseLocalResolver: types.BoolPointerValue(tBool), + tfSSLConfig: tfSSLConfig{ + SslVerificationMode: types.StringValue("full"), + SslSupportedProtocols: types.ListValueMust(types.StringType, []attr.Value{ + types.StringValue("TLSv1.2"), types.StringValue("TLSv1.3"), + }), + SslCertificateAuthorities: []types.String{types.StringValue("cert1"), types.StringValue("cert2")}, + SslCertificate: types.StringValue("cert"), + SslKey: types.StringValue("key"), + SslKeyPassphrase: types.StringValue("passphrase"), + }, }, }, }, @@ -282,7 +325,7 @@ func TestToModelV0(t *testing.T) { PrivateLocations: []types.String{types.StringValue("test private location")}, Enabled: types.BoolPointerValue(tBool), Tags: nil, - Alert: &tfAlertConfigV0{Status: &tfStatusConfigV0{Enabled: types.BoolPointerValue(tBool)}}, + Alert: toAlertObject(t, tfAlertConfigV0{Status: &tfStatusConfigV0{Enabled: types.BoolPointerValue(tBool)}}), APMServiceName: types.StringValue("test-service-tcp"), TimeoutSeconds: types.Int64Value(30), Params: jsontypes.NewNormalizedValue(`{"param1":"value1"}`), @@ -337,7 +380,7 @@ func TestToModelV0(t *testing.T) { PrivateLocations: []types.String{types.StringValue("test private location")}, Enabled: types.BoolPointerValue(tBool), Tags: nil, - Alert: &tfAlertConfigV0{Status: &tfStatusConfigV0{Enabled: types.BoolPointerValue(tBool)}}, + Alert: toAlertObject(t, tfAlertConfigV0{Status: &tfStatusConfigV0{Enabled: types.BoolPointerValue(tBool)}}), APMServiceName: types.StringValue("test-service-tcp"), TimeoutSeconds: types.Int64Value(30), Params: jsontypes.NewNormalizedValue(`{"param1":"value1"}`), @@ -419,16 +462,12 @@ func TestToKibanaAPIRequest(t *testing.T) { PrivateLocations: []types.String{types.StringValue("test private location")}, Enabled: types.BoolPointerValue(tBool), Tags: []types.String{types.StringValue("tag1"), types.StringValue("tag2")}, - Alert: &tfAlertConfigV0{Status: &tfStatusConfigV0{Enabled: types.BoolPointerValue(tBool)}, TLS: &tfStatusConfigV0{Enabled: types.BoolPointerValue(fBool)}}, + Alert: toAlertObject(t, tfAlertConfigV0{Status: &tfStatusConfigV0{Enabled: types.BoolPointerValue(tBool)}, TLS: &tfStatusConfigV0{Enabled: types.BoolPointerValue(fBool)}}), APMServiceName: types.StringValue("test-service-http"), TimeoutSeconds: types.Int64Value(30), Params: jsontypes.NewNormalizedValue(`{"param1":"value1"}`), HTTP: &tfHTTPMonitorFieldsV0{ - URL: types.StringValue("https://example.com"), - SslVerificationMode: types.StringValue("full"), - SslSupportedProtocols: types.ListValueMust(types.StringType, []attr.Value{ - types.StringValue("TLSv1.2"), types.StringValue("TLSv1.3"), - }), + URL: types.StringValue("https://example.com"), MaxRedirects: types.Int64Value(5), Mode: types.StringValue("all"), IPv4: types.BoolPointerValue(tBool), @@ -439,6 +478,16 @@ func TestToKibanaAPIRequest(t *testing.T) { ProxyURL: types.StringValue("https://proxy.com"), Response: jsontypes.NewNormalizedValue(`{"response1":"value1"}`), Check: jsontypes.NewNormalizedValue(`{"check1":"value1"}`), + tfSSLConfig: tfSSLConfig{ + SslVerificationMode: types.StringValue("full"), + SslSupportedProtocols: types.ListValueMust(types.StringType, []attr.Value{ + types.StringValue("TLSv1.2"), types.StringValue("TLSv1.3"), + }), + SslCertificateAuthorities: []types.String{types.StringValue("cert1"), types.StringValue("cert2")}, + SslCertificate: types.StringValue("cert"), + SslKey: types.StringValue("key"), + SslKeyPassphrase: types.StringValue("passphrase"), + }, }, }, expected: kibanaAPIRequest{ @@ -456,19 +505,25 @@ func TestToKibanaAPIRequest(t *testing.T) { Params: kbapi.JsonObject{"param1": "value1"}, }, fields: kbapi.HTTPMonitorFields{ - Url: "https://example.com", - SslVerificationMode: "full", - SslSupportedProtocols: []string{"TLSv1.2", "TLSv1.3"}, - MaxRedirects: "5", - Mode: "all", - Ipv4: tBool, - Ipv6: fBool, - Username: "user", - Password: "pass", - ProxyHeader: kbapi.JsonObject{"header1": "value1"}, - ProxyUrl: "https://proxy.com", - Response: kbapi.JsonObject{"response1": "value1"}, - Check: kbapi.JsonObject{"check1": "value1"}, + Url: "https://example.com", + Ssl: &kbapi.SSLConfig{ + VerificationMode: "full", + SupportedProtocols: []string{"TLSv1.2", "TLSv1.3"}, + CertificateAuthorities: []string{"cert1", "cert2"}, + Certificate: "cert", + Key: "key", + KeyPassphrase: "passphrase", + }, + MaxRedirects: "5", + Mode: "all", + Ipv4: tBool, + Ipv6: fBool, + Username: "user", + Password: "pass", + ProxyHeader: kbapi.JsonObject{"header1": "value1"}, + ProxyUrl: "https://proxy.com", + Response: kbapi.JsonObject{"response1": "value1"}, + Check: kbapi.JsonObject{"check1": "value1"}, }, }, }, @@ -483,16 +538,22 @@ func TestToKibanaAPIRequest(t *testing.T) { PrivateLocations: nil, Enabled: types.BoolPointerValue(tBool), Tags: []types.String{types.StringValue("tag1"), types.StringValue("tag2")}, - Alert: &tfAlertConfigV0{Status: &tfStatusConfigV0{Enabled: types.BoolPointerValue(tBool)}}, + Alert: toAlertObject(t, tfAlertConfigV0{Status: &tfStatusConfigV0{Enabled: types.BoolPointerValue(tBool)}}), APMServiceName: types.StringValue("test-service-tcp"), TimeoutSeconds: types.Int64Value(30), Params: jsontypes.NewNormalizedValue(`{"param1":"value1"}`), TCP: &tfTCPMonitorFieldsV0{ - Host: types.StringValue("example.com:9200"), - SslVerificationMode: types.StringValue("full"), - SslSupportedProtocols: types.ListValueMust(types.StringType, []attr.Value{ - types.StringValue("TLSv1.2"), types.StringValue("TLSv1.3"), - }), + Host: types.StringValue("example.com:9200"), + tfSSLConfig: tfSSLConfig{ + SslVerificationMode: types.StringValue("full"), + SslSupportedProtocols: types.ListValueMust(types.StringType, []attr.Value{ + types.StringValue("TLSv1.2"), types.StringValue("TLSv1.3"), + }), + SslCertificateAuthorities: []types.String{types.StringValue("cert1"), types.StringValue("cert2")}, + SslCertificate: types.StringValue("cert"), + SslKey: types.StringValue("key"), + SslKeyPassphrase: types.StringValue("passphrase"), + }, CheckSend: types.StringValue("hello"), CheckReceive: types.StringValue("world"), ProxyURL: types.StringValue("http://proxy.com"), @@ -514,9 +575,15 @@ func TestToKibanaAPIRequest(t *testing.T) { Params: kbapi.JsonObject{"param1": "value1"}, }, fields: kbapi.TCPMonitorFields{ - Host: "example.com:9200", - SslVerificationMode: "full", - SslSupportedProtocols: []string{"TLSv1.2", "TLSv1.3"}, + Host: "example.com:9200", + Ssl: &kbapi.SSLConfig{ + VerificationMode: "full", + SupportedProtocols: []string{"TLSv1.2", "TLSv1.3"}, + CertificateAuthorities: []string{"cert1", "cert2"}, + Certificate: "cert", + Key: "key", + KeyPassphrase: "passphrase", + }, CheckSend: "hello", CheckReceive: "world", ProxyUrl: "http://proxy.com", @@ -535,7 +602,7 @@ func TestToKibanaAPIRequest(t *testing.T) { PrivateLocations: nil, Enabled: types.BoolPointerValue(tBool), Tags: []types.String{types.StringValue("tag1"), types.StringValue("tag2")}, - Alert: &tfAlertConfigV0{Status: &tfStatusConfigV0{Enabled: types.BoolPointerValue(tBool)}}, + Alert: toAlertObject(t, tfAlertConfigV0{Status: &tfStatusConfigV0{Enabled: types.BoolPointerValue(tBool)}}), APMServiceName: types.StringValue("test-service-tcp"), TimeoutSeconds: types.Int64Value(30), Params: jsontypes.NewNormalizedValue(`{"param1":"value1"}`), @@ -575,7 +642,7 @@ func TestToKibanaAPIRequest(t *testing.T) { PrivateLocations: nil, Enabled: types.BoolPointerValue(tBool), Tags: []types.String{types.StringValue("tag1"), types.StringValue("tag2")}, - Alert: &tfAlertConfigV0{Status: &tfStatusConfigV0{Enabled: types.BoolPointerValue(tBool)}}, + Alert: toAlertObject(t, tfAlertConfigV0{Status: &tfStatusConfigV0{Enabled: types.BoolPointerValue(tBool)}}), APMServiceName: types.StringValue("test-service-tcp"), TimeoutSeconds: types.Int64Value(30), Params: jsontypes.NewNormalizedValue(`{"param1":"value1"}`), @@ -661,17 +728,23 @@ func TestToModelV0MergeAttributes(t *testing.T) { Params: jsontypes.NewNormalizedValue(`{"param1":"value1"}`), RetestOnFailure: types.BoolValue(true), HTTP: &tfHTTPMonitorFieldsV0{ - URL: types.StringValue(""), - SslVerificationMode: types.StringValue(""), - SslSupportedProtocols: types.ListNull(types.StringType), - MaxRedirects: types.Int64Value(0), - Mode: types.StringValue(""), - ProxyURL: types.StringValue(""), - ProxyHeader: jsontypes.NewNormalizedValue(`{"header1":"value1"}`), - Username: types.StringValue("test"), - Password: types.StringValue("password"), - Check: jsontypes.NewNormalizedValue(`{"check1":"value1"}`), - Response: jsontypes.NewNormalizedValue(`{"response1":"value1"}`), + URL: types.StringValue(""), + MaxRedirects: types.Int64Value(0), + Mode: types.StringValue(""), + ProxyURL: types.StringValue(""), + ProxyHeader: jsontypes.NewNormalizedValue(`{"header1":"value1"}`), + Username: types.StringValue("test"), + Password: types.StringValue("password"), + Check: jsontypes.NewNormalizedValue(`{"check1":"value1"}`), + Response: jsontypes.NewNormalizedValue(`{"response1":"value1"}`), + + tfSSLConfig: tfSSLConfig{ + SslVerificationMode: types.StringValue(""), + SslSupportedProtocols: types.ListNull(types.StringType), + SslCertificate: types.StringValue(""), + SslKey: types.StringValue(""), + SslKeyPassphrase: types.StringValue(""), + }, }, }, }, @@ -699,12 +772,17 @@ func TestToModelV0MergeAttributes(t *testing.T) { TimeoutSeconds: types.Int64Value(0), Locations: []types.String{types.StringValue("us_east")}, TCP: &tfTCPMonitorFieldsV0{ - Host: types.StringValue(""), - SslVerificationMode: types.StringValue(""), - CheckSend: types.StringValue("hello"), - CheckReceive: types.StringValue("world"), - ProxyURL: types.StringValue(""), - SslSupportedProtocols: types.ListNull(types.StringType), + Host: types.StringValue(""), + CheckSend: types.StringValue("hello"), + CheckReceive: types.StringValue("world"), + ProxyURL: types.StringValue(""), + tfSSLConfig: tfSSLConfig{ + SslVerificationMode: types.StringValue(""), + SslSupportedProtocols: types.ListNull(types.StringType), + SslCertificate: types.StringValue(""), + SslKey: types.StringValue(""), + SslKeyPassphrase: types.StringValue(""), + }, }, }, }, diff --git a/libs/go-kibana-rest/kbapi/api.kibana_synthetics.go b/libs/go-kibana-rest/kbapi/api.kibana_synthetics.go index d4cb97855..c80c7cbb0 100644 --- a/libs/go-kibana-rest/kbapi/api.kibana_synthetics.go +++ b/libs/go-kibana-rest/kbapi/api.kibana_synthetics.go @@ -109,29 +109,36 @@ type BrowserMonitorFields struct { } type TCPMonitorFields struct { - Host string `json:"host"` - SslVerificationMode string `json:"ssl.verification_mode,omitempty"` - SslSupportedProtocols []string `json:"ssl.supported_protocols,omitempty"` - CheckSend string `json:"check.send,omitempty"` - CheckReceive string `json:"check.receive,omitempty"` - ProxyUrl string `json:"proxy_url,omitempty"` - ProxyUseLocalResolver *bool `json:"proxy_use_local_resolver,omitempty"` + Host string `json:"host"` + Ssl *SSLConfig `json:"ssl,omitempty"` + CheckSend string `json:"check.send,omitempty"` + CheckReceive string `json:"check.receive,omitempty"` + ProxyUrl string `json:"proxy_url,omitempty"` + ProxyUseLocalResolver *bool `json:"proxy_use_local_resolver,omitempty"` +} + +type SSLConfig struct { + VerificationMode string `json:"verification_mode,omitempty"` + SupportedProtocols []string `json:"supported_protocols,omitempty"` + CertificateAuthorities []string `json:"certificate_authorities,omitempty"` + Certificate string `json:"certificate,omitempty"` + Key string `json:"key,omitempty"` + KeyPassphrase string `json:"key_passphrase,omitempty"` } type HTTPMonitorFields struct { - Url string `json:"url"` - SslVerificationMode string `json:"ssl.verification_mode,omitempty"` - SslSupportedProtocols []string `json:"ssl.supported_protocols,omitempty"` - MaxRedirects string `json:"max_redirects,omitempty"` - Mode HttpMonitorMode `json:"mode,omitempty"` - Ipv4 *bool `json:"ipv4,omitempty"` - Ipv6 *bool `json:"ipv6,omitempty"` - Username string `json:"username,omitempty"` - Password string `json:"password,omitempty"` - ProxyHeader JsonObject `json:"proxy_headers,omitempty"` - ProxyUrl string `json:"proxy_url,omitempty"` - Response JsonObject `json:"response,omitempty"` - Check JsonObject `json:"check,omitempty"` + Url string `json:"url"` + Ssl *SSLConfig `json:"ssl,omitempty"` + MaxRedirects string `json:"max_redirects,omitempty"` + Mode HttpMonitorMode `json:"mode,omitempty"` + Ipv4 *bool `json:"ipv4,omitempty"` + Ipv6 *bool `json:"ipv6,omitempty"` + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + ProxyHeader JsonObject `json:"proxy_headers,omitempty"` + ProxyUrl string `json:"proxy_url,omitempty"` + Response JsonObject `json:"response,omitempty"` + Check JsonObject `json:"check,omitempty"` } type SyntheticsMonitorConfig struct { @@ -222,9 +229,13 @@ type SyntheticsMonitor struct { CheckRequestHeaders JsonObject `json:"check.request.headers,omitempty"` CheckRequestMethod string `json:"check.request.method,omitempty"` //http and tcp - ProxyUrl string `json:"proxy_url,omitempty"` - SslVerificationMode string `json:"ssl.verification_mode"` - SslSupportedProtocols []string `json:"ssl.supported_protocols"` + ProxyUrl string `json:"proxy_url,omitempty"` + SslVerificationMode string `json:"ssl.verification_mode"` + SslSupportedProtocols []string `json:"ssl.supported_protocols"` + SslCertificateAuthorities []string `json:"ssl.certificate_authorities,omitempty"` + SslCertificate string `json:"ssl.certificate,omitempty"` + SslKey string `json:"ssl.key,omitempty"` + SslKeyPassphrase string `json:"ssl.key_passphrase,omitempty"` //tcp and icmp Host string `json:"host,omitempty"` //tcp diff --git a/libs/go-kibana-rest/kbapi/api.kibana_synthetics_test.go b/libs/go-kibana-rest/kbapi/api.kibana_synthetics_test.go index 4d665df1b..ac6d6fbc5 100644 --- a/libs/go-kibana-rest/kbapi/api.kibana_synthetics_test.go +++ b/libs/go-kibana-rest/kbapi/api.kibana_synthetics_test.go @@ -146,15 +146,21 @@ func (s *KBAPITestSuite) TestKibanaSyntheticsMonitorAPI() { RetestOnFailure: f, }, fields: HTTPMonitorFields{ - Url: "http://localhost:5601", - SslSupportedProtocols: []string{"TLSv1.0", "TLSv1.1", "TLSv1.2"}, - SslVerificationMode: "full", - MaxRedirects: "2", - Mode: ModeAny, - Ipv4: t, - Ipv6: f, - Username: "test-user-name", - Password: "test-password", + Url: "http://localhost:5601", + Ssl: &SSLConfig{ + VerificationMode: "full", + SupportedProtocols: []string{"TLSv1.0", "TLSv1.1", "TLSv1.2"}, + CertificateAuthorities: []string{"ca1", "ca2"}, + Certificate: "cert", + Key: "key", + KeyPassphrase: "passphrase", + }, + MaxRedirects: "2", + Mode: ModeAny, + Ipv4: t, + Ipv6: f, + Username: "test-user-name", + Password: "test-password", ProxyHeader: map[string]interface{}{ "User-Agent": "test", }, @@ -214,9 +220,15 @@ func (s *KBAPITestSuite) TestKibanaSyntheticsMonitorAPI() { RetestOnFailure: f, }, fields: TCPMonitorFields{ - Host: "localhost:5601", - SslSupportedProtocols: []string{"TLSv1.0", "TLSv1.1", "TLSv1.2"}, - SslVerificationMode: "full", + Host: "localhost:5601", + Ssl: &SSLConfig{ + VerificationMode: "full", + SupportedProtocols: []string{"TLSv1.0", "TLSv1.1", "TLSv1.2"}, + CertificateAuthorities: []string{"ca1", "ca2"}, + Certificate: "cert", + Key: "key", + KeyPassphrase: "passphrase", + }, ProxyUseLocalResolver: t, ProxyUrl: "http://localhost", CheckSend: "Hello World", diff --git a/templates/resources/kibana_synthetics_monitor.md.tmpl b/templates/resources/kibana_synthetics_monitor.md.tmpl index 32c971d0c..6f99d891e 100644 --- a/templates/resources/kibana_synthetics_monitor.md.tmpl +++ b/templates/resources/kibana_synthetics_monitor.md.tmpl @@ -31,9 +31,4 @@ In case you would like to reset an optional monitor value, please set it explici Import is supported using the following syntax: -{{ codefile "shell" "examples/resources/elasticstack_kibana_synthetics_monitor/import.sh" }} - -**NOTE:** Not all monitor fields are supported during the import due-to API limitation. -Full field support could be implemented after this [kibana issue](https://github.com/elastic/kibana/issues/189906) is resolved. - -Currently not supported fields during the import: `params`, `retest_on_failure`, `locations`, `http.proxy_header`, `http.username`, `http.password`, `http.check`, `http.response`, `tcp.check_send`, `tcp.check_receive` and monitor type `browser` +{{ codefile "shell" "examples/resources/elasticstack_kibana_synthetics_monitor/import.sh" }} \ No newline at end of file