diff --git a/docs/resources/contact_point.md b/docs/resources/contact_point.md index 058b2f72a..d4c1d7906 100644 --- a/docs/resources/contact_point.md +++ b/docs/resources/contact_point.md @@ -50,6 +50,7 @@ resource "grafana_contact_point" "my_contact_point" { - `googlechat` (Block Set) A contact point that sends notifications to Google Chat. (see [below for nested schema](#nestedblock--googlechat)) - `kafka` (Block Set) A contact point that publishes notifications to Apache Kafka topics. (see [below for nested schema](#nestedblock--kafka)) - `line` (Block Set) A contact point that sends notifications to LINE.me. (see [below for nested schema](#nestedblock--line)) +- `mqtt` (Block Set) A contact point that sends notifications to an MQTT broker. (see [below for nested schema](#nestedblock--mqtt)) - `oncall` (Block Set) A contact point that sends notifications to Grafana On-Call. (see [below for nested schema](#nestedblock--oncall)) - `opsgenie` (Block Set) A contact point that sends notifications to OpsGenie. (see [below for nested schema](#nestedblock--opsgenie)) - `org_id` (String) The Organization ID. If not set, the Org ID defined in the provider block will be used. @@ -212,6 +213,42 @@ Read-Only: - `uid` (String) The UID of the contact point. + +### Nested Schema for `mqtt` + +Required: + +- `broker_url` (String) The URL of the MQTT broker. +- `topic` (String) The topic to publish messages to. + +Optional: + +- `client_id` (String) The client ID to use when connecting to the broker. +- `disable_resolve_message` (Boolean) Whether to disable sending resolve messages. Defaults to `false`. +- `message_format` (String) The format of the message to send. Supported values are `json` and `text`. +- `password` (String, Sensitive) The password to use when connecting to the broker. +- `qos` (Number) The quality of service to use when sending messages. Supported values are 0, 1, and 2. Defaults to `0`. +- `retain` (Boolean) Whether to retain messages on the broker. Defaults to `false`. +- `settings` (Map of String, Sensitive) Additional custom properties to attach to the notifier. Defaults to `map[]`. +- `tls_config` (Block Set) TLS configuration for the connection. (see [below for nested schema](#nestedblock--mqtt--tls_config)) +- `username` (String) The username to use when connecting to the broker. + +Read-Only: + +- `uid` (String) The UID of the contact point. + + +### Nested Schema for `mqtt.tls_config` + +Optional: + +- `ca_certificate` (String, Sensitive) The CA certificate to use when verifying the server's certificate. +- `client_certificate` (String, Sensitive) The client certificate to use when connecting to the server. +- `client_key` (String, Sensitive) The client key to use when connecting to the server. +- `insecure_skip_verify` (Boolean) Whether to skip verification of the server's certificate chain and host name. Defaults to `false`. + + + ### Nested Schema for `oncall` diff --git a/examples/resources/grafana_contact_point/_acc_receiver_types.tf b/examples/resources/grafana_contact_point/_acc_receiver_types.tf index 752b5dda2..5c2573983 100644 --- a/examples/resources/grafana_contact_point/_acc_receiver_types.tf +++ b/examples/resources/grafana_contact_point/_acc_receiver_types.tf @@ -54,6 +54,23 @@ resource "grafana_contact_point" "receiver_types" { description = "description" } + mqtt { + broker_url = "tcp://localhost:1883" + client_id = "client_id" + topic = "grafana/alerts" + message_format = "json" + username = "username" + password = "password" + qos = 1 + retain = true + tls_config { + insecure_skip_verify = true + ca_certificate = "ca_cert" + client_certificate = "client_cert" + client_key = "client" + } + } + opsgenie { url = "http://opsgenie-api" api_key = "token" diff --git a/examples/resources/grafana_contact_point/_acc_receiver_types_11_3.tf b/examples/resources/grafana_contact_point/_acc_receiver_types_11_3.tf new file mode 100644 index 000000000..dffeab764 --- /dev/null +++ b/examples/resources/grafana_contact_point/_acc_receiver_types_11_3.tf @@ -0,0 +1,20 @@ +resource "grafana_contact_point" "receiver_types" { + name = "Receiver Types since v11.3" + + mqtt { + broker_url = "tcp://localhost:1883" + client_id = "grafana" + topic = "grafana/alerts" + message_format = "json" + username = "user" + password = "password123" + qos = 1 + retain = true + tls_config { + insecure_skip_verify = true + ca_certificate = "ca_cert" + client_certificate = "client_cert" + client_key = "client_key" + } + } +} diff --git a/internal/resources/grafana/resource_alerting_contact_point.go b/internal/resources/grafana/resource_alerting_contact_point.go index 9f43d236c..0eac8352a 100644 --- a/internal/resources/grafana/resource_alerting_contact_point.go +++ b/internal/resources/grafana/resource_alerting_contact_point.go @@ -28,6 +28,7 @@ var ( googleChatNotifier{}, kafkaNotifier{}, lineNotifier{}, + mqttNotifier{}, oncallNotifier{}, opsGenieNotifier{}, pagerDutyNotifier{}, diff --git a/internal/resources/grafana/resource_alerting_contact_point_notifiers.go b/internal/resources/grafana/resource_alerting_contact_point_notifiers.go index fee1ef7e3..f10913570 100644 --- a/internal/resources/grafana/resource_alerting_contact_point_notifiers.go +++ b/internal/resources/grafana/resource_alerting_contact_point_notifiers.go @@ -619,6 +619,184 @@ func (o lineNotifier) unpack(raw interface{}, name string) *models.EmbeddedConta } } +type mqttNotifier struct{} + +var _ notifier = (*mqttNotifier)(nil) + +func (o mqttNotifier) meta() notifierMeta { + return notifierMeta{ + field: "mqtt", + typeStr: "mqtt", + desc: "A contact point that sends notifications to an MQTT broker.", + secureFields: []string{"password"}, + } +} + +func (o mqttNotifier) schema() *schema.Resource { + r := commonNotifierResource() + r.Schema["broker_url"] = &schema.Schema{ + Type: schema.TypeString, + Required: true, + Description: "The URL of the MQTT broker.", + } + r.Schema["topic"] = &schema.Schema{ + Type: schema.TypeString, + Required: true, + Description: "The topic to publish messages to.", + } + r.Schema["client_id"] = &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "The client ID to use when connecting to the broker.", + } + r.Schema["message_format"] = &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{"json", "text"}, false), + Description: "The format of the message to send. Supported values are `json` and `text`.", + } + r.Schema["username"] = &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "The username to use when connecting to the broker.", + } + r.Schema["password"] = &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Sensitive: true, + Description: "The password to use when connecting to the broker.", + } + r.Schema["qos"] = &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Default: 0, + ValidateFunc: validation.IntBetween(0, 2), + Description: "The quality of service to use when sending messages. Supported values are 0, 1, and 2.", + } + r.Schema["retain"] = &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "Whether to retain messages on the broker.", + } + + r.Schema["tls_config"] = &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Description: "TLS configuration for the connection.", + Elem: tlsConfig{}.schema(), + } + + return r +} + +func (o mqttNotifier) pack(p *models.EmbeddedContactPoint, data *schema.ResourceData) (interface{}, error) { + notifier := packCommonNotifierFields(p) + settings := p.Settings.(map[string]interface{}) + + packNotifierStringField(&settings, ¬ifier, "brokerUrl", "broker_url") + packNotifierStringField(&settings, ¬ifier, "topic", "topic") + packNotifierStringField(&settings, ¬ifier, "clientId", "client_id") + packNotifierStringField(&settings, ¬ifier, "messageFormat", "message_format") + packNotifierStringField(&settings, ¬ifier, "username", "username") + if v, ok := settings["insecureSkipVerify"]; ok && v != nil { + notifier["insecure_skip_verify"] = v.(bool) + delete(settings, "insecureSkipVerify") + } + + packSecureFields(notifier, getNotifierConfigFromStateWithUID(data, o, p.UID), o.meta().secureFields) + + notifier["settings"] = packSettings(p) + return notifier, nil +} + +func (o mqttNotifier) unpack(raw interface{}, name string) *models.EmbeddedContactPoint { + json := raw.(map[string]interface{}) + uid, disableResolve, settings := unpackCommonNotifierFields(json) + + unpackNotifierStringField(&json, &settings, "broker_url", "brokerUrl") + unpackNotifierStringField(&json, &settings, "topic", "topic") + unpackNotifierStringField(&json, &settings, "client_id", "clientId") + unpackNotifierStringField(&json, &settings, "message_format", "messageFormat") + unpackNotifierStringField(&json, &settings, "username", "username") + unpackNotifierStringField(&json, &settings, "password", "password") + if v, ok := json["insecure_skip_verify"]; ok && v != nil { + settings["insecureSkipVerify"] = v.(bool) + } + + return &models.EmbeddedContactPoint{ + UID: uid, + Name: name, + Type: common.Ref(o.meta().typeStr), + DisableResolveMessage: disableResolve, + Settings: settings, + } +} + +type tlsConfig struct{} + +func (t tlsConfig) schema() *schema.Resource { + r := &schema.Resource{ + Schema: make(map[string]*schema.Schema), + } + + r.Schema["insecure_skip_verify"] = &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "Whether to skip verification of the server's certificate chain and host name.", + } + r.Schema["ca_certificate"] = &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Sensitive: true, + Description: "The CA certificate to use when verifying the server's certificate.", + } + r.Schema["client_certificate"] = &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Sensitive: true, + Description: "The client certificate to use when connecting to the server.", + } + r.Schema["client_key"] = &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Sensitive: true, + Description: "The client key to use when connecting to the server.", + } + + return r +} + +func (t tlsConfig) pack(p *models.EmbeddedContactPoint, data *schema.ResourceData) (interface{}, error) { + settings := p.Settings.(map[string]interface{}) + tls := make(map[string]interface{}) + + if v, ok := settings["insecureSkipVerify"]; ok && v != nil { + tls["insecure_skip_verify"] = v.(bool) + delete(settings, "insecureSkipVerify") + } + packNotifierStringField(&settings, &tls, "caCertificate", "ca_certificate") + packNotifierStringField(&settings, &tls, "clientCertificate", "client_certificate") + packNotifierStringField(&settings, &tls, "clientKey", "client_key") + + return tls, nil +} + +func (t tlsConfig) unpack(raw interface{}) map[string]interface{} { + json := raw.(map[string]interface{}) + tls := make(map[string]interface{}) + + if v, ok := json["insecure_skip_verify"]; ok && v != nil { + tls["insecureSkipVerify"] = v.(bool) + } + unpackNotifierStringField(&json, &tls, "ca_certificate", "caCertificate") + unpackNotifierStringField(&json, &tls, "client_certificate", "clientCertificate") + unpackNotifierStringField(&json, &tls, "client_key", "clientKey") + + return tls +} + type oncallNotifier struct { } diff --git a/internal/resources/grafana/resource_alerting_contact_point_test.go b/internal/resources/grafana/resource_alerting_contact_point_test.go index a14c91bf4..88bc94039 100644 --- a/internal/resources/grafana/resource_alerting_contact_point_test.go +++ b/internal/resources/grafana/resource_alerting_contact_point_test.go @@ -399,6 +399,40 @@ func TestAccContactPoint_notifiers10_3(t *testing.T) { }) } +func TestAccContactPoint_notifiers11_3(t *testing.T) { + testutils.CheckOSSTestsEnabled(t, ">=11.3.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_11_3.tf"), + Check: resource.ComposeTestCheckFunc( + checkAlertingContactPointExistsWithLength("grafana_contact_point.receiver_types", &points, 1), + // mqtt + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "mqtt.#", "1"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "mqtt.0.broker_url", "tcp://localhost:1883"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "mqtt.0.topic", "grafana/alerts"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "mqtt.0.client_id", "grafana"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "mqtt.0.message_format", "json"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "mqtt.0.username", "user"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "mqtt.0.password", "password123"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "mqtt.0.qos", "1"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "mqtt.0.tls_config.insecure_skip_verify", "true"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "mqtt.0.tls_config.ca_certificate", "ca_cer"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "mqtt.0.tls_config.client_certificate", "client_cert"), + resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "mqtt.0.tls_config.client_key", "client_key"), + ), + }, + }, + }) +} + func TestAccContactPoint_sensitiveData(t *testing.T) { testutils.CheckOSSTestsEnabled(t, ">=9.1.0")