Skip to content

Commit

Permalink
Merge pull request #15 from fguimond/dedup-key
Browse files Browse the repository at this point in the history
Dedup key & severity mapping
  • Loading branch information
Nikki Attea committed Jul 16, 2019
2 parents 9780576 + e9cb5a3 commit 02fc48f
Show file tree
Hide file tree
Showing 7 changed files with 315 additions and 19 deletions.
6 changes: 5 additions & 1 deletion .gitignore
@@ -1,4 +1,5 @@
# Binaries for programs and plugins
sensu-pagerduty-handler
*.exe
*.exe~
*.dll
Expand All @@ -16,4 +17,7 @@ vendor/

# deploy
bonsai/
dist/
dist/

# IDE's
.idea/
33 changes: 30 additions & 3 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion Gopkg.toml
Expand Up @@ -35,7 +35,11 @@

[[constraint]]
name = "github.com/sensu/sensu-plugins-go-library"
version = "0.2.0"
version = "0.3.0"

[[constraint]]
name = "github.com/stretchr/testify"
version = "1.3.0"

[prune]
go-tests = true
Expand Down
55 changes: 52 additions & 3 deletions README.md
Expand Up @@ -32,6 +32,9 @@ Example Sensu Go handler definition:
"command": "sensu-pagerduty-handler",
"env_vars": [
"PAGERDUTY_TOKEN=SECRET",
"PAGERDUTY_DEDUP_KEY=SENSU_EVENT_LABEL",
"PAGERDUTY_DEDUP_KEY_TEMPLATE={{.Entity.Name}}-{{.Check.Name}}",
"PAGERDUTY_STATUS_MAP={\"info\":[130,10],\"error\":[4]}"
],
"timeout": 10,
"filters": [
Expand Down Expand Up @@ -73,13 +76,58 @@ Usage:
sensu-pagerduty-handler [flags]
Flags:
-h, --help help for sensu-pagerduty-handler
-t, --token string The PagerDuty V2 API authentication token, use default from PAGERDUTY_TOKEN env var
-d, --dedup-key string The Sensu event label specifying the PagerDuty V2 API deduplication key, use default from PAGERDUTY_DEDUP_KEY env var
-k, --dedup-key-template string The PagerDuty V2 API deduplication key template, use default from PAGERDUTY_DEDUP_KEY_TEMPLATE env var
-h, --help help for sensu-pagerduty-handler
-s, --status-map string The status map used to translate a Sensu check status to a PagerDuty severity, use default from PAGERDUTY_STATUS_MAP env var
-t, --token string The PagerDuty V2 API authentication token, use default from PAGERDUTY_TOKEN env var
```

**Note:** Make sure to set the `PAGERDUTY_TOKEN` environment variable for sensitive credentials in production to prevent leaking into system process table. Please remember command arguments can be viewed by unprivileged users using commands such as `ps` or `top`. The `--token` argument is provided as an override primarily for testing purposes.

### Deduplication Key Priority

The deduplication key is determined using the following priority:
1. --dedup-key -- specifies the entity label containing the key
1. --dedup-key-template -- a template containing the values
1. the default value containing the entity and check names

### PagerDuty Severity Mapping

Optionally you can provide mapping information between the Sensu check status and the PagerDuty incident severity.
To provide the mapping you need to use the `--status-map` command line option or the `PAGERDUTY_STATUS_MAP` environment variable.
The option accepts a JSON document containing the mapping information. Here's an example of the JSON document:

```json
{
"info": [
0,
1
],
"warning": [
2
],
"critical:": [
3
],
"error": [
4,
5,
6,
7,
8,
9,
10
]
}
```

The valid [PagerDuty alert severity levels][5] are the following:
* `info`
* `warning`
* `critical`
* `error`

## Contributing

See https://github.com/sensu/sensu-go/blob/master/CONTRIBUTING.md
Expand All @@ -88,3 +136,4 @@ See https://github.com/sensu/sensu-go/blob/master/CONTRIBUTING.md
[2]: https://www.pagerduty.com/
[3]: https://docs.sensu.io/sensu-go/5.0/reference/handlers/#how-do-sensu-handlers-work
[4]: https://github.com/sensu/sensu-pagerduty-handler/releases
[5]: https://support.pagerduty.com/docs/dynamic-notifications#section-eventalert-severity-levels
7 changes: 5 additions & 2 deletions event.json
@@ -1,4 +1,5 @@
{
"timestamp": 1561246706000,
"entity": {
"entity_class": "agent",
"system": {
Expand Down Expand Up @@ -58,7 +59,9 @@
"metadata": {
"name": "webserver01",
"namespace": "default",
"labels": null,
"labels": {
"someLabel": "one hell of a deduplication key"
},
"annotations": null
}
},
Expand Down Expand Up @@ -103,7 +106,7 @@
"issued": 1542667666,
"output": "example output",
"state": "failing",
"status": 1,
"status": 2,
"total_state_change": 0,
"last_ok": 0,
"occurrences": 1,
Expand Down
131 changes: 122 additions & 9 deletions main.go
@@ -1,18 +1,25 @@
package main

import (
"encoding/json"
"fmt"

"github.com/PagerDuty/go-pagerduty"
corev2 "github.com/sensu/sensu-go/api/core/v2"
"github.com/sensu/sensu-plugins-go-library/sensu"
"github.com/sensu/sensu-plugins-go-library/templates"
"log"
)

type HandlerConfig struct {
sensu.PluginConfig
authToken string
authToken string
dedupKey string
dedupKeyTemplate string
statusMapJson string
}

type eventStatusMap map[string][]uint32

var (
config = HandlerConfig{
PluginConfig: sensu.PluginConfig{
Expand All @@ -31,6 +38,33 @@ var (
Value: &config.authToken,
Default: "",
},
{
Path: "dedup-key",
Env: "PAGERDUTY_DEDUP_KEY",
Argument: "dedup-key",
Shorthand: "d",
Usage: "The Sensu event label specifying the PagerDuty V2 API deduplication key, use default from PAGERDUTY_DEDUP_KEY env var",
Value: &config.dedupKey,
Default: "",
},
{
Path: "dedup-key-template",
Env: "PAGERDUTY_DEDUP_KEY_TEMPLATE",
Argument: "dedup-key-template",
Shorthand: "k",
Usage: "The PagerDuty V2 API deduplication key template, use default from PAGERDUTY_DEDUP_KEY_TEMPLATE env var",
Value: &config.dedupKeyTemplate,
Default: "",
},
{
Path: "status-map",
Env: "PAGERDUTY_STATUS_MAP",
Argument: "status-map",
Shorthand: "s",
Usage: "The status map used to translate a Sensu check status to a PagerDuty severity, use default from PAGERDUTY_STATUS_MAP env var",
Value: &config.statusMapJson,
Default: "",
},
}
)

Expand All @@ -47,12 +81,11 @@ func checkArgs(event *corev2.Event) error {
}

func manageIncident(event *corev2.Event) error {
severity := "warning"

if event.Check.Status < 3 {
severities := []string{"info", "warning", "critical"}
severity = severities[event.Check.Status]
severity, err := getPagerDutySeverity(event, config.statusMapJson)
if err != nil {
return err
}
log.Printf("Incident severity: %s", severity)

summary := fmt.Sprintf("%s/%s : %s", event.Entity.Name, event.Check.Name, event.Check.Output)

Expand All @@ -70,7 +103,13 @@ func manageIncident(event *corev2.Event) error {
action = "resolve"
}

dedupKey := fmt.Sprintf("%s-%s", event.Entity.Name, event.Check.Name)
dedupKey, err := getPagerDutyDedupKey(event)
if err != nil {
return err
}
if len(dedupKey) == 0 {
return fmt.Errorf("pagerduty dedup key is empty")
}

pdEvent := pagerduty.V2Event{
RoutingKey: config.authToken,
Expand All @@ -79,10 +118,84 @@ func manageIncident(event *corev2.Event) error {
DedupKey: dedupKey,
}

_, err := pagerduty.ManageEvent(pdEvent)
_, err = pagerduty.ManageEvent(pdEvent)
if err != nil {
return err
}

return nil
}

// getPagerDutyDedupKey returns the PagerDuty deduplication key. The following priority is used to determine the
// deduplication key.
// 1. --dedup-key -- specifies the entity label containing the key
// 2. --dedup-key-template -- a template containing the values
// 3. the default value including the entity and check names
func getPagerDutyDedupKey(event *corev2.Event) (string, error) {
if len(config.dedupKey) > 0 {
labelValue := event.Entity.Labels[config.dedupKey]
if len(labelValue) > 0 {
return labelValue, nil
} else {
return "", fmt.Errorf("no deduplication key value in label %s", config.dedupKey)
}
}

if len(config.dedupKeyTemplate) > 0 {
return templates.EvalTemplate("dedupKey", config.dedupKeyTemplate, event)
} else {
return fmt.Sprintf("%s-%s", event.Entity.Name, event.Check.Name), nil
}
}

func getPagerDutySeverity(event *corev2.Event, statusMapJson string) (string, error) {
var statusMap map[uint32]string
var err error

if len(statusMapJson) > 0 {
statusMap, err = parseStatusMap(statusMapJson)
if err != nil {
return "", err
}
}

if len(statusMap) > 0 {
status := event.Check.Status
severity := statusMap[status]
if len(severity) > 0 {
return severity, nil
}
}

// Default to these values is no status map is found
severity := "warning"
if event.Check.Status < 3 {
severities := []string{"info", "warning", "critical"}
severity = severities[event.Check.Status]
}

return severity, nil
}

func parseStatusMap(statusMapJson string) (map[uint32]string, error) {
validPagerDutySeverities := map[string]bool{"info": true, "critical": true, "warning": true, "error": true}

statusMap := eventStatusMap{}
err := json.Unmarshal([]byte(statusMapJson), &statusMap)
if err != nil {
return nil, err
}

// Reverse the map to key it on the status
statusToSeverityMap := map[uint32]string{}
for severity, statuses := range statusMap {
if !validPagerDutySeverities[severity] {
return nil, fmt.Errorf("invalid pagerduty severity: %s", severity)
}
for i := range statuses {
statusToSeverityMap[uint32(statuses[i])] = severity
}
}

return statusToSeverityMap, nil
}

0 comments on commit 02fc48f

Please sign in to comment.