diff --git a/docs/resources/contact_point.md b/docs/resources/contact_point.md index e0890e806..16b674c18 100644 --- a/docs/resources/contact_point.md +++ b/docs/resources/contact_point.md @@ -523,9 +523,12 @@ Optional: - `basic_auth_password` (String, Sensitive) The username to use in basic auth headers attached to the request. If omitted, basic auth will not be used. - `basic_auth_user` (String) The username to use in basic auth headers attached to the request. If omitted, basic auth will not be used. - `disable_resolve_message` (Boolean) Whether to disable sending resolve messages. Defaults to `false`. +- `headers` (Map of String) Custom headers to attach to the request. +- `http_config` (Block Set, Max: 1) Common HTTP client options. (see [below for nested schema](#nestedblock--webhook--http_config)) - `http_method` (String) The HTTP method to use in the request. Defaults to `POST`. - `max_alerts` (Number) The maximum number of alerts to send in a single request. This can be helpful in limiting the size of the request body. The default is 0, which indicates no limit. - `message` (String) Custom message. You can use template variables. +- `payload` (Block Set, Max: 1) Optionally provide a templated payload. Overrides 'Message' and 'Title' field. (see [below for nested schema](#nestedblock--webhook--payload)) - `settings` (Map of String, Sensitive) Additional custom properties to attach to the notifier. Defaults to `map[]`. - `title` (String) Templated title of the message. - `tls_config` (Map of String, Sensitive) Allows configuring TLS for the webhook notifier. @@ -534,6 +537,65 @@ Read-Only: - `uid` (String) The UID of the contact point. + +### Nested Schema for `webhook.http_config` + +Optional: + +- `oauth2` (Block Set, Max: 1) OAuth2 configuration options. (see [below for nested schema](#nestedblock--webhook--http_config--oauth2)) + + +### Nested Schema for `webhook.http_config.oauth2` + +Required: + +- `client_id` (String) Client ID to use when authenticating. +- `client_secret` (String, Sensitive) Client secret to use when authenticating. +- `token_url` (String) URL for the access token endpoint. + +Optional: + +- `endpoint_params` (Map of String) Optional parameters to append to the access token request. +- `proxy_config` (Block Set, Max: 1) Optional proxy configuration for OAuth2 requests. (see [below for nested schema](#nestedblock--webhook--http_config--oauth2--proxy_config)) +- `scopes` (List of String) Optional scopes to request when obtaining an access token. +- `tls_config` (Block Set, Max: 1) Optional TLS configuration options for OAuth2 requests. (see [below for nested schema](#nestedblock--webhook--http_config--oauth2--tls_config)) + + +### Nested Schema for `webhook.http_config.oauth2.proxy_config` + +Optional: + +- `no_proxy` (String) Comma-separated list of addresses that should not use a proxy. +- `proxy_connect_header` (Map of String) Optional headers to send to proxies during CONNECT requests. +- `proxy_from_environment` (Boolean) Use environment HTTP_PROXY, HTTPS_PROXY and NO_PROXY to determine proxies. Defaults to `false`. +- `proxy_url` (String) HTTP proxy server to use to connect to the targets. + + + +### Nested Schema for `webhook.http_config.oauth2.tls_config` + +Optional: + +- `ca_certificate` (String, Sensitive) Certificate in PEM format to use when verifying the server's certificate chain. +- `client_certificate` (String, Sensitive) Client certificate in PEM format to use when connecting to the server. +- `client_key` (String, Sensitive) Client key in PEM format to use when connecting to the server. +- `insecure_skip_verify` (Boolean) Do not verify the server's certificate chain and host name. Defaults to `false`. + + + + + +### Nested Schema for `webhook.payload` + +Required: + +- `template` (String) Custom payload template. + +Optional: + +- `vars` (Map of String) Optionally provide a variables to be used in the payload template. They will be available in the template as `.Vars.`. + + ### Nested Schema for `wecom` diff --git a/examples/resources/grafana_contact_point/_acc_receiver_types_12_0.tf b/examples/resources/grafana_contact_point/_acc_receiver_types_12_0.tf new file mode 100644 index 000000000..3279c0292 --- /dev/null +++ b/examples/resources/grafana_contact_point/_acc_receiver_types_12_0.tf @@ -0,0 +1,17 @@ +resource "grafana_contact_point" "receiver_types" { + name = "Receiver Types since v12.0" + + webhook { + url = "http://my-url" + headers = { + Content-Type = "test-content-type" + X-Test-Header = "test-header-value" + } + payload { + template = "{{ .Receiver }}: {{ .Vars.var1 }}" + vars = { + var1 = "variable value" + } + } + } +} diff --git a/examples/resources/grafana_contact_point/_acc_receiver_types_12_1.tf b/examples/resources/grafana_contact_point/_acc_receiver_types_12_1.tf new file mode 100644 index 000000000..1446528d9 --- /dev/null +++ b/examples/resources/grafana_contact_point/_acc_receiver_types_12_1.tf @@ -0,0 +1,70 @@ +resource "grafana_contact_point" "receiver_types" { + name = "Receiver Types since v12.1" + + webhook { + url = "http://my-url" + http_method = "POST" + basic_auth_user = "user" + basic_auth_password = "password" + max_alerts = 100 + message = "Custom message" + title = "Custom title" + tls_config = { + insecure_skip_verify = true + ca_certificate = "ca.crt" + client_certificate = "client.crt" + client_key = "client.key" + } + http_config { + oauth2 { + client_id = "client_id" + client_secret = "client_secret" + token_url = "http://oauth2-token-url" + scopes = ["scope1", "scope2"] + endpoint_params = { + "param1" = "value1" + "param2" = "value2" + } + proxy_config { + proxy_url = "http://proxy-url" + proxy_from_environment = false + no_proxy = "localhost" + proxy_connect_header = { + "X-Proxy-Header" = "proxy-value" + } + } + tls_config { + insecure_skip_verify = true + ca_certificate = < 1 { + return map[string]any{}, fmt.Errorf("set contains more than one item: %q", items) + } + // Use the first item in the set as the child map + m, ok := items[0].(map[string]any) + if !ok { + return map[string]any{}, fmt.Errorf("unsupported value in set: %q", items[0]) + } + return m, nil } type notifier interface { diff --git a/internal/resources/grafana/resource_alerting_contact_point_notifiers.go b/internal/resources/grafana/resource_alerting_contact_point_notifiers.go index 76939569f..30f469e9e 100644 --- a/internal/resources/grafana/resource_alerting_contact_point_notifiers.go +++ b/internal/resources/grafana/resource_alerting_contact_point_notifiers.go @@ -1132,13 +1132,14 @@ func (w webhookNotifier) meta() notifierMeta { field: "webhook", typeStr: "webhook", desc: "A contact point that sends notifications to an arbitrary webhook, using the Prometheus webhook format defined here: https://prometheus.io/docs/alerting/latest/configuration/#webhook_config", - fieldMapper: map[string]fieldMapper{ + fieldMapper: withCommonHTTPConfigFieldMappers(map[string]fieldMapper{ "http_method": newKeyMapper("httpMethod"), "basic_auth_user": newKeyMapper("username"), "basic_auth_password": newKeyMapper("password"), "max_alerts": newFieldMapper("maxAlerts", valueAsInt, valueAsInt), "tls_config": newFieldMapper("tlsConfig", translateTLSConfigPack, translateTLSConfigUnpack), - }, + "headers": omitEmptyMapper(), + }), } } @@ -1197,6 +1198,34 @@ func (w webhookNotifier) schema() *schema.Resource { Sensitive: true, Description: "Allows configuring TLS for the webhook notifier.", } + r.Schema["headers"] = &schema.Schema{ + Type: schema.TypeMap, + Optional: true, + Description: "Custom headers to attach to the request.", + Elem: &schema.Schema{Type: schema.TypeString}, + } + r.Schema["payload"] = &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + MaxItems: 1, + Description: "Optionally provide a templated payload. Overrides 'Message' and 'Title' field.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "template": { + Type: schema.TypeString, + Required: true, + Description: "Custom payload template.", + }, + "vars": { + Type: schema.TypeMap, + Optional: true, + Description: "Optionally provide a variables to be used in the payload template. They will be available in the template as `.Vars.`.", + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + } + addCommonHTTPConfigResource(r) return r } diff --git a/internal/resources/grafana/resource_alerting_contact_point_test.go b/internal/resources/grafana/resource_alerting_contact_point_test.go index c3e38329c..741c69c21 100644 --- a/internal/resources/grafana/resource_alerting_contact_point_test.go +++ b/internal/resources/grafana/resource_alerting_contact_point_test.go @@ -529,6 +529,392 @@ func TestAccContactPoint_notifiers11_4(t *testing.T) { }) } +func TestAccContactPoint_notifiers12_0(t *testing.T) { + testutils.CheckOSSTestsEnabled(t, ">=12.0.0") + + var points models.ContactPoints + + resource.ParallelTest(t, resource.TestCase{ + ProtoV5ProviderFactories: testutils.ProtoV5ProviderFactories, + // Implicitly tests deletion. + CheckDestroy: alertingContactPointCheckExists.destroyed(&points, nil), + Steps: []resource.TestStep{ + // Multiple payload blocks are not allowed. + { + Config: ` + resource "grafana_contact_point" "receiver_types" { + name = "Receiver Types since v12.0" + + webhook { + url = "http://my-url" + headers = { + Content-Type = "test-content-type" + X-Test-Header = "test-header-value" + } + payload { + template = "{{ .Receiver }}: {{ .Vars.var1 }}" + vars = { + var1 = "variable value" + } + } + payload { + template = "{{ .Receiver }}: {{ .Vars.var1 }} 2" + vars = { + var1 = "variable value2" + } + } + } + } + `, + ExpectError: regexp.MustCompile(`Too many payload blocks`), + }, + // Template field required. + { + Config: testutils.TestAccExampleWithReplace(t, "resources/grafana_contact_point/_acc_receiver_types_12_0.tf", map[string]string{ + `template = "{{ .Receiver }}: {{ .Vars.var1 }}"`: ``, + }), + ExpectError: regexp.MustCompile(`Missing required argument`), + }, + // Empty payload and header are omitted. + { + Config: ` + resource "grafana_contact_point" "receiver_types" { + name = "Receiver Types since v12.0" + + webhook { + url = "http://my-url" + } + } + `, + Check: resource.ComposeTestCheckFunc( + checkAlertingContactPointExistsWithLength("grafana_contact_point.receiver_types", &points, 1), + // webhook + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.#", "1"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.url", "http://my-url"), + + // Sanity check to ensure empty TLS config is omitted instead of being set to an empty map. + func(s *terraform.State) error { + if val, ok := points[0].Settings.(map[string]interface{})["headers"]; ok { + return fmt.Errorf("headers was still present in the settings when it should have been omitted. value: %#v", val) + } + if val, ok := points[0].Settings.(map[string]interface{})["payload"]; ok { + return fmt.Errorf("payload was still present in the settings when it should have been omitted. value: %#v", val) + } + + return nil + }, + ), + }, + // Test creation. + { + Config: testutils.TestAccExample(t, "resources/grafana_contact_point/_acc_receiver_types_12_0.tf"), + Check: resource.ComposeTestCheckFunc( + checkAlertingContactPointExistsWithLength("grafana_contact_point.receiver_types", &points, 1), + // webhook + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.#", "1"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.url", "http://my-url"), + + // Since we are using schema.TypeSet for nested types, we need ".0" for this notation to correctly reference the nested element. + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.headers.%", "2"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.headers.Content-Type", "test-content-type"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.headers.X-Test-Header", "test-header-value"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.payload.0.template", "{{ .Receiver }}: {{ .Vars.var1 }}"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.payload.0.vars.%", "1"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.payload.0.vars.var1", "variable value"), + + // Sanity check to ensure empty TLS config is omitted instead of being set to an empty map. + func(s *terraform.State) error { + if val, ok := points[0].Settings.(map[string]interface{})["tlsConfig"]; ok { + return fmt.Errorf("tlsConfig was still present in the settings when it should have been omitted. value: %#v", val) + } + + return nil + }, + ), + }, + // Update non-sensitive data. + { + Config: testutils.TestAccExampleWithReplace(t, "resources/grafana_contact_point/_acc_receiver_types_12_0.tf", map[string]string{ + "\"test-header-value\"": "\"updated-test-header-value\"", + "\"variable value\"": "\"updated-variable value\"", + "\"{{ .Receiver }}: {{ .Vars.var1 }}\"": "\"Updated {{ .Receiver }}: {{ .Vars.var1 }}\"", + }), + + Check: resource.ComposeTestCheckFunc( + checkAlertingContactPointExistsWithLength("grafana_contact_point.receiver_types", &points, 1), + // webhook + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.#", "1"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.url", "http://my-url"), + + // Since we are using schema.TypeSet for nested types, we need ".0" for this notation to correctly reference the nested element. + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.headers.%", "2"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.headers.Content-Type", "test-content-type"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.headers.X-Test-Header", "updated-test-header-value"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.payload.0.template", "Updated {{ .Receiver }}: {{ .Vars.var1 }}"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.payload.0.vars.%", "1"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.payload.0.vars.var1", "updated-variable value"), + + // Sanity check to ensure empty TLS config is omitted instead of being set to an empty map. + func(s *terraform.State) error { + if val, ok := points[0].Settings.(map[string]interface{})["tlsConfig"]; ok { + return fmt.Errorf("tlsConfig was still present in the settings when it should have been omitted. value: %#v", val) + } + + return nil + }, + ), + }, + }, + }) +} + +func TestAccContactPoint_notifiers12_1(t *testing.T) { + testutils.CheckOSSTestsEnabled(t, ">=12.1.0") + + var points models.ContactPoints + + resource.ParallelTest(t, resource.TestCase{ + ProtoV5ProviderFactories: testutils.ProtoV5ProviderFactories, + // Implicitly tests deletion. + CheckDestroy: alertingContactPointCheckExists.destroyed(&points, nil), + Steps: []resource.TestStep{ + // Test creation. + { + Config: testutils.TestAccExample(t, "resources/grafana_contact_point/_acc_receiver_types_12_1.tf"), + Check: resource.ComposeTestCheckFunc( + checkAlertingContactPointExistsWithLength("grafana_contact_point.receiver_types", &points, 1), + // webhook + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.#", "1"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.url", "http://my-url"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_method", "POST"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.basic_auth_user", "user"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.basic_auth_password", "password"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.max_alerts", "100"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.message", "Custom message"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.title", "Custom title"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.tls_config.insecure_skip_verify", "true"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.tls_config.ca_certificate", "ca.crt"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.tls_config.client_certificate", "client.crt"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.tls_config.client_key", "client.key"), + + // Since we are using schema.TypeSet for nested types, we need ".0" for this notation to correctly reference the nested element. + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.client_id", "client_id"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.client_secret", "client_secret"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.token_url", "http://oauth2-token-url"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.scopes.#", "2"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.scopes.0", "scope1"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.scopes.1", "scope2"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.endpoint_params.param1", "value1"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.endpoint_params.param2", "value2"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.proxy_config.0.proxy_url", "http://proxy-url"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.proxy_config.0.proxy_from_environment", "false"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.proxy_config.0.no_proxy", "localhost"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.proxy_config.0.proxy_connect_header.X-Proxy-Header", "proxy-value"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.tls_config.0.insecure_skip_verify", "true"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.tls_config.0.ca_certificate", "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----\n"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.tls_config.0.client_certificate", "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----\n"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.tls_config.0.client_key", "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----\n"), + ), + }, + // Update sensitive data. + { + Config: testutils.TestAccExampleWithReplace(t, "resources/grafana_contact_point/_acc_receiver_types_12_1.tf", map[string]string{ + "\"client_secret\"": "\"updated_client_secret\"", + }), + + Check: resource.ComposeTestCheckFunc( + checkAlertingContactPointExistsWithLength("grafana_contact_point.receiver_types", &points, 1), + + // Updated + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.client_secret", "updated_client_secret"), + + // Unchanged + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.#", "1"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.url", "http://my-url"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_method", "POST"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.basic_auth_user", "user"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.basic_auth_password", "password"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.max_alerts", "100"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.message", "Custom message"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.title", "Custom title"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.tls_config.insecure_skip_verify", "true"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.tls_config.ca_certificate", "ca.crt"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.tls_config.client_certificate", "client.crt"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.tls_config.client_key", "client.key"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.client_id", "client_id"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.token_url", "http://oauth2-token-url"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.scopes.#", "2"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.scopes.0", "scope1"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.scopes.1", "scope2"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.endpoint_params.param1", "value1"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.endpoint_params.param2", "value2"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.proxy_config.0.proxy_url", "http://proxy-url"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.proxy_config.0.proxy_from_environment", "false"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.proxy_config.0.no_proxy", "localhost"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.proxy_config.0.proxy_connect_header.X-Proxy-Header", "proxy-value"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.tls_config.0.insecure_skip_verify", "true"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.tls_config.0.ca_certificate", "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----\n"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.tls_config.0.client_certificate", "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----\n"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.tls_config.0.client_key", "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----\n"), + ), + }, + // Update non-sensitive data. + { + Config: testutils.TestAccExampleWithReplace(t, "resources/grafana_contact_point/_acc_receiver_types_12_1.tf", map[string]string{ + "\"client_secret\"": "\"updated_client_secret\"", + "http://proxy-url": "http://updated-proxy-url", + }), + + Check: resource.ComposeTestCheckFunc( + checkAlertingContactPointExistsWithLength("grafana_contact_point.receiver_types", &points, 1), + + // Updated + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.client_secret", "updated_client_secret"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.proxy_config.0.proxy_url", "http://updated-proxy-url"), + + // Unchanged + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.#", "1"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.url", "http://my-url"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_method", "POST"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.basic_auth_user", "user"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.basic_auth_password", "password"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.max_alerts", "100"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.message", "Custom message"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.title", "Custom title"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.tls_config.insecure_skip_verify", "true"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.tls_config.ca_certificate", "ca.crt"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.tls_config.client_certificate", "client.crt"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.tls_config.client_key", "client.key"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.client_id", "client_id"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.token_url", "http://oauth2-token-url"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.scopes.#", "2"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.scopes.0", "scope1"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.scopes.1", "scope2"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.endpoint_params.param1", "value1"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.endpoint_params.param2", "value2"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.proxy_config.0.proxy_from_environment", "false"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.proxy_config.0.no_proxy", "localhost"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.proxy_config.0.proxy_connect_header.X-Proxy-Header", "proxy-value"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.tls_config.0.insecure_skip_verify", "true"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.tls_config.0.ca_certificate", "-----BEGIN CERTIFICATE-----\nMIGrMF+gAwIBAgIBATAFBgMrZXAwADAeFw0yNDExMTYxMDI4MzNaFw0yNTExMTYx\nMDI4MzNaMAAwKjAFBgMrZXADIQCf30GvRnHbs9gukA3DLXDK6W5JVgYw6mERU/60\n2M8+rjAFBgMrZXADQQCGmeaRp/AcjeqmJrF5Yh4d7aqsMSqVZvfGNDc0ppXyUgS3\nWMQ1+3T+/pkhU612HR0vFd3vyFhmB4yqFoNV8RML\n-----END CERTIFICATE-----\n"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.tls_config.0.client_certificate", "-----BEGIN CERTIFICATE-----\nMIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw\nDgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow\nEjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d\n7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B\n5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr\nBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1\nNDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l\nWf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc\n6MF9+Yw1Yy0t\n-----END CERTIFICATE-----\n"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.http_config.0.oauth2.0.tls_config.0.client_key", "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49\nAwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q\nEKTcWGekdmdDPsHloRNtsiCa697B2O9IFA==\n-----END EC PRIVATE KEY-----\n"), + ), + }, + }, + }) +} + +func TestAccContactPoint_TypeSet_MaxItems(t *testing.T) { + testutils.CheckOSSTestsEnabled(t, ">=12.1.0") + + resource.ParallelTest(t, resource.TestCase{ + ProtoV5ProviderFactories: testutils.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + // Multiple http_config blocks are not allowed. + { + Config: ` + resource "grafana_contact_point" "typset_max_items" { + name = "typset_max_items" + webhook { + url = "http://my-url" + http_config { + oauth2 { + client_id = "client_id" + client_secret = "client_secret" + token_url = "http://oauth2-token-url" + } + } + http_config { + oauth2 { + client_id = "client_id2" + client_secret = "client_secret2" + token_url = "http://oauth2-token-url2" + } + } + } + } + `, + ExpectError: regexp.MustCompile(`Too many http_config blocks`), + }, + // Multiple oauth2 blocks are not allowed. + { + Config: ` + resource "grafana_contact_point" "typset_max_items" { + name = "typset_max_items" + webhook { + url = "http://my-url" + http_config { + oauth2 { + client_id = "client_id" + client_secret = "client_secret" + token_url = "http://oauth2-token-url" + } + oauth2 { + client_id = "client_id2" + client_secret = "client_secret2" + token_url = "http://oauth2-token-url2" + } + } + } + } + `, + ExpectError: regexp.MustCompile(`Too many oauth2 blocks`), + }, + // Multiple proxy_config blocks are not allowed. + { + Config: ` + resource "grafana_contact_point" "typset_max_items" { + name = "typset_max_items" + webhook { + url = "http://my-url" + http_config { + oauth2 { + client_id = "client_id" + client_secret = "client_secret" + token_url = "http://oauth2-token-url" + proxy_config { + proxy_url = "http://proxy-url" + } + proxy_config { + proxy_url = "http://proxy-url" + } + } + } + } + } + `, + ExpectError: regexp.MustCompile(`Too many proxy_config blocks`), + }, + // Multiple tls_config blocks are not allowed. + { + Config: ` + resource "grafana_contact_point" "typset_max_items" { + name = "typset_max_items" + webhook { + url = "http://my-url" + http_config { + oauth2 { + client_id = "client_id" + client_secret = "client_secret" + token_url = "http://oauth2-token-url" + tls_config { + insecure_skip_verify = true + } + tls_config { + insecure_skip_verify = true + } + } + } + } + } + `, + ExpectError: regexp.MustCompile(`Too many tls_config blocks`), + }, + }, + }) +} + func TestAccContactPoint_sensitiveData(t *testing.T) { testutils.CheckOSSTestsEnabled(t, ">=9.1.0") @@ -766,8 +1152,8 @@ func TestAccContactPoint_minimalDefinitions(t *testing.T) { expectedAttrs[attr] = struct{}{} } for attr := range pt.Settings.(map[string]any) { - if _, ok := expectedAttrs[attr]; !ok { - return fmt.Errorf("contact point %s attribute %s should not exist in the contact point settings, but was found", key, attr) + if val, ok := expectedAttrs[attr]; !ok { + return fmt.Errorf("contact point %s attribute %s should not exist in the contact point settings, but was found: %v", key, attr, val) } delete(expectedAttrs, attr) } @@ -1038,6 +1424,9 @@ func TestAccContactPoint_minimalDefinitions(t *testing.T) { resource.TestCheckResourceAttr("grafana_contact_point.minimal_receivers", "webhook.0.max_alerts", "0"), resource.TestCheckResourceAttr("grafana_contact_point.minimal_receivers", "webhook.0.message", ""), resource.TestCheckResourceAttr("grafana_contact_point.minimal_receivers", "webhook.0.title", ""), + resource.TestCheckResourceAttr("grafana_contact_point.minimal_receivers", "webhook.0.headers.%", "0"), + resource.TestCheckResourceAttr("grafana_contact_point.minimal_receivers", "webhook.0.payload.#", "0"), + resource.TestCheckResourceAttr("grafana_contact_point.minimal_receivers", "webhook.0.http_config.#", "0"), checkOtherAttrsOmittedInResponse(&points, "webhook.0", "url", "maxAlerts", // TODO: This would be better omitted.