Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alerting: add support for on-call integration #1087

Merged
merged 2 commits into from
Nov 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions docs/resources/contact_point.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ resource "grafana_contact_point" "my_contact_point" {
- `googlechat` (Block List) A contact point that sends notifications to Google Chat. (see [below for nested schema](#nestedblock--googlechat))
- `kafka` (Block List) A contact point that publishes notifications to Apache Kafka topics. (see [below for nested schema](#nestedblock--kafka))
- `line` (Block List) A contact point that sends notifications to LINE.me. (see [below for nested schema](#nestedblock--line))
- `oncall` (Block List) A contact point that sends notifications to Grafana On-Call. (see [below for nested schema](#nestedblock--oncall))
- `opsgenie` (Block List) A contact point that sends notifications to OpsGenie. (see [below for nested schema](#nestedblock--opsgenie))
- `pagerduty` (Block List) A contact point that sends notifications to PagerDuty. (see [below for nested schema](#nestedblock--pagerduty))
- `pushover` (Block List) A contact point that sends notifications to Pushover. (see [below for nested schema](#nestedblock--pushover))
Expand Down Expand Up @@ -208,6 +209,31 @@ Read-Only:
- `uid` (String) The UID of the contact point.


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

Required:

- `url` (String) The URL to send webhook requests to.

Optional:

- `authorization_credentials` (String, Sensitive) Allows a custom authorization scheme - attaches an auth header with this value. Do not use in conjunction with basic auth parameters.
- `authorization_scheme` (String) Allows a custom authorization scheme - attaches an auth header with this name. Do not use in conjunction with basic auth parameters.
- `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`.
- `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.
- `settings` (Map of String, Sensitive) Additional custom properties to attach to the notifier. Defaults to `map[]`.
- `title` (String) Templated title of the message.

Read-Only:

- `uid` (String) The UID of the contact point.


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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
resource "grafana_contact_point" "receiver_types" {
name = "Receiver Types since v10.2"

oncall {
url = "http://my-url"
http_method = "POST"
basic_auth_user = "user"
basic_auth_password = "password"
max_alerts = 100
message = "Custom message"
title = "Custom title"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ var notifiers = []notifier{
googleChatNotifier{},
kafkaNotifier{},
lineNotifier{},
oncallNotifier{},
opsGenieNotifier{},
pagerDutyNotifier{},
pushoverNotifier{},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,139 @@ func (o lineNotifier) unpack(raw interface{}, name string) gapi.ContactPoint {
}
}

type oncallNotifier struct {
}

var _ notifier = (*oncallNotifier)(nil)

func (w oncallNotifier) meta() notifierMeta {
return notifierMeta{
field: "oncall",
typeStr: "oncall",
desc: "A contact point that sends notifications to Grafana On-Call.",
secureFields: []string{"basic_auth_password", "authorization_credentials"},
}
}

func (w oncallNotifier) schema() *schema.Resource {
r := commonNotifierResource()
r.Schema["url"] = &schema.Schema{
Type: schema.TypeString,
Required: true,
Description: "The URL to send webhook requests to.",
}
r.Schema["http_method"] = &schema.Schema{
Type: schema.TypeString,
Optional: true,
Description: "The HTTP method to use in the request. Defaults to `POST`.",
}
r.Schema["basic_auth_user"] = &schema.Schema{
Type: schema.TypeString,
Optional: true,
Description: "The username to use in basic auth headers attached to the request. If omitted, basic auth will not be used.",
}
r.Schema["basic_auth_password"] = &schema.Schema{
Type: schema.TypeString,
Optional: true,
Sensitive: true,
Description: "The username to use in basic auth headers attached to the request. If omitted, basic auth will not be used.",
}
r.Schema["authorization_scheme"] = &schema.Schema{
Type: schema.TypeString,
Optional: true,
Description: "Allows a custom authorization scheme - attaches an auth header with this name. Do not use in conjunction with basic auth parameters.",
}
r.Schema["authorization_credentials"] = &schema.Schema{
Type: schema.TypeString,
Optional: true,
Sensitive: true,
Description: "Allows a custom authorization scheme - attaches an auth header with this value. Do not use in conjunction with basic auth parameters.",
}
r.Schema["max_alerts"] = &schema.Schema{
Type: schema.TypeInt,
Optional: true,
Description: "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.",
}
r.Schema["message"] = &schema.Schema{
Type: schema.TypeString,
Optional: true,
Description: "Custom message. You can use template variables.",
}
r.Schema["title"] = &schema.Schema{
Type: schema.TypeString,
Optional: true,
Description: "Templated title of the message.",
}
return r
}

func (w oncallNotifier) pack(p gapi.ContactPoint, data *schema.ResourceData) (interface{}, error) {
notifier := packCommonNotifierFields(&p)

packNotifierStringField(&p.Settings, &notifier, "url", "url")
packNotifierStringField(&p.Settings, &notifier, "httpMethod", "http_method")
packNotifierStringField(&p.Settings, &notifier, "username", "basic_auth_user")
packNotifierStringField(&p.Settings, &notifier, "password", "basic_auth_password")
packNotifierStringField(&p.Settings, &notifier, "authorization_scheme", "authorization_scheme")
packNotifierStringField(&p.Settings, &notifier, "authorization_credentials", "authorization_credentials")
packNotifierStringField(&p.Settings, &notifier, "message", "message")
packNotifierStringField(&p.Settings, &notifier, "title", "title")
if v, ok := p.Settings["maxAlerts"]; ok && v != nil {
switch typ := v.(type) {
case int:
notifier["max_alerts"] = v.(int)
case float64:
notifier["max_alerts"] = int(v.(float64))
case string:
val, err := strconv.Atoi(typ)
if err != nil {
panic(fmt.Errorf("failed to parse value of 'maxAlerts' to integer: %w", err))
}
notifier["max_alerts"] = val
default:
panic(fmt.Sprintf("unexpected type %T for 'maxAlerts': %v", typ, typ))
}
delete(p.Settings, "maxAlerts")
}

packSecureFields(notifier, getNotifierConfigFromStateWithUID(data, w, p.UID), w.meta().secureFields)

notifier["settings"] = packSettings(&p)
return notifier, nil
}

func (w oncallNotifier) unpack(raw interface{}, name string) gapi.ContactPoint {
json := raw.(map[string]interface{})
uid, disableResolve, settings := unpackCommonNotifierFields(json)

unpackNotifierStringField(&json, &settings, "url", "url")
unpackNotifierStringField(&json, &settings, "http_method", "httpMethod")
unpackNotifierStringField(&json, &settings, "basic_auth_user", "username")
unpackNotifierStringField(&json, &settings, "basic_auth_password", "password")
unpackNotifierStringField(&json, &settings, "authorization_scheme", "authorization_scheme")
unpackNotifierStringField(&json, &settings, "authorization_credentials", "authorization_credentials")
unpackNotifierStringField(&json, &settings, "message", "message")
unpackNotifierStringField(&json, &settings, "title", "title")
if v, ok := json["max_alerts"]; ok && v != nil {
switch typ := v.(type) {
case int:
settings["maxAlerts"] = v.(int)
case float64:
settings["maxAlerts"] = int(v.(float64))
default:
panic(fmt.Sprintf("unexpected type for maxAlerts: %v", typ))
}
}

return gapi.ContactPoint{
UID: uid,
Name: name,
Type: w.meta().typeStr,
DisableResolveMessage: disableResolve,
Settings: settings,
}
}

type opsGenieNotifier struct{}

var _ notifier = (*opsGenieNotifier)(nil)
Expand Down
30 changes: 30 additions & 0 deletions internal/resources/grafana/resource_alerting_contact_point_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,36 @@ func TestAccContactPoint_notifiers(t *testing.T) {
})
}

func TestAccContactPoint_notifiers10_2(t *testing.T) {
testutils.CheckOSSTestsEnabled(t, ">=10.2.0")

var points []gapi.ContactPoint

resource.ParallelTest(t, resource.TestCase{
ProviderFactories: testutils.ProviderFactories,
// Implicitly tests deletion.
CheckDestroy: testContactPointCheckDestroy(points),
Steps: []resource.TestStep{
// Test creation.
{
Config: testutils.TestAccExample(t, "resources/grafana_contact_point/_acc_receiver_types_10_2.tf"),
Check: resource.ComposeTestCheckFunc(
testContactPointCheckExists("grafana_contact_point.receiver_types", &points, 1),
// oncall
resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "oncall.#", "1"),
resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "oncall.0.url", "http://my-url"),
resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "oncall.0.http_method", "POST"),
resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "oncall.0.basic_auth_user", "user"),
resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "oncall.0.basic_auth_password", "password"),
resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "oncall.0.max_alerts", "100"),
resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "oncall.0.message", "Custom message"),
resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "oncall.0.title", "Custom title"),
),
},
},
})
}

func TestAccContactPoint_empty(t *testing.T) {
testutils.CheckOSSTestsEnabled(t, ">=9.1.0")

Expand Down