Skip to content

Commit

Permalink
Alerting: Add MQTT notifications receiver
Browse files Browse the repository at this point in the history
  • Loading branch information
alexander-akhmetov committed Feb 2, 2025
1 parent 397e224 commit 02f00e1
Show file tree
Hide file tree
Showing 7 changed files with 378 additions and 0 deletions.
37 changes: 37 additions & 0 deletions docs/resources/contact_point.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -212,6 +213,42 @@ Read-Only:
- `uid` (String) The UID of the contact point.


<a id="nestedblock--mqtt"></a>
### 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.

<a id="nestedblock--mqtt--tls_config"></a>
### 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`.



<a id="nestedblock--oncall"></a>
### Nested Schema for `oncall`

Expand Down
46 changes: 46 additions & 0 deletions examples/resources/grafana_contact_point/_acc_mqtt_receiver.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Basic MQTT configuration
resource "grafana_contact_point" "mqtt_basic" {
name = "mqtt-basic"

mqtt {
broker_url = "tcp://localhost:1883"
topic = "grafana/alerts"
qos = 0
}
}

# MQTT with authentication
resource "grafana_contact_point" "mqtt_auth" {
name = "mqtt-auth"

mqtt {
broker_url = "tcp://localhost:1883"
topic = "grafana/alerts"
client_id = "grafana-client"
username = "mqtt-user"
password = "secret123"
message_format = "json"
qos = 1
}
}

# MQTT with TLS
resource "grafana_contact_point" "mqtt_tls" {
name = "mqtt-tls"

mqtt {
broker_url = "ssl://localhost:8883"
topic = "grafana/alerts"
client_id = "grafana-secure"
message_format = "json"
qos = 2
retain = true

tls_config {
insecure_skip_verify = false
ca_certificate = file("ca.pem")
client_certificate = file("client-cert.pem")
client_key = file("client-key.pem")
}
}
}
17 changes: 17 additions & 0 deletions examples/resources/grafana_contact_point/_acc_receiver_types.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
24 changes: 24 additions & 0 deletions internal/resources/grafana/resource_alerting_contact_point.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ var (
googleChatNotifier{},
kafkaNotifier{},
lineNotifier{},
mqttNotifier{},
oncallNotifier{},
opsGenieNotifier{},
pagerDutyNotifier{},
Expand Down Expand Up @@ -419,6 +420,23 @@ func packNotifierStringField(gfSettings, tfSettings *map[string]interface{}, gfK
}
}

func packNotifierIntField(gfSettings, tfSettings *map[string]interface{}, gfKey, tfKey string) error {
v, err := strconv.Atoi((*gfSettings)[gfKey].(string))
if err != nil {
return err
}
(*tfSettings)[tfKey] = int64(v)
delete(*gfSettings, gfKey)
return nil
}

func packNotifierBoolField(gfSettings, tfSettings *map[string]interface{}, gfKey, tfKey string) {
if v, ok := (*gfSettings)[gfKey]; ok && v != nil {
(*tfSettings)[tfKey] = v.(bool)
delete(*gfSettings, gfKey)
}
}

func packSecureFields(tfSettings, state map[string]interface{}, secureFields []string) {
for _, tfKey := range secureFields {
if v, ok := state[tfKey]; ok && v != nil {
Expand All @@ -427,6 +445,12 @@ func packSecureFields(tfSettings, state map[string]interface{}, secureFields []s
}
}

func unpackNotifierBoolField(tfSettings, gfSettings *map[string]interface{}, tfKey, gfKey string) {
if v, ok := (*tfSettings)[tfKey]; ok && v != nil {
(*gfSettings)[gfKey] = v.(bool)
}
}

func unpackNotifierStringField(tfSettings, gfSettings *map[string]interface{}, tfKey, gfKey string) {
if v, ok := (*tfSettings)[tfKey]; ok && v != nil {
(*gfSettings)[gfKey] = v.(string)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,174 @@ 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, &notifier, "brokerUrl", "broker_url")
packNotifierStringField(&settings, &notifier, "topic", "topic")
packNotifierStringField(&settings, &notifier, "clientId", "client_id")
packNotifierStringField(&settings, &notifier, "messageFormat", "message_format")
packNotifierStringField(&settings, &notifier, "username", "username")
packNotifierIntField(&settings, &notifier, "qos", "qos")
packNotifierBoolField(&settings, &notifier, "retain", "retain")

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")

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{})

packNotifierBoolField(&settings, &tls, "insecureSkipVerify", "insecure_skip_verify")
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{})

unpackNotifierBoolField(&json, &tls, "insecure_skip_verify", "insecureSkipVerify")
unpackNotifierStringField(&json, &tls, "ca_certificate", "caCertificate")
unpackNotifierStringField(&json, &tls, "client_certificate", "clientCertificate")
unpackNotifierStringField(&json, &tls, "client_key", "clientKey")

return tls
}

type oncallNotifier struct {
}

Expand Down
Loading

0 comments on commit 02f00e1

Please sign in to comment.