Skip to content

Commit

Permalink
Updates to allow for use of additional PD-CEF fields
Browse files Browse the repository at this point in the history
- Add `--client-name` and `--sensu-base-url`. If provided, these options add a link to the Sensu dashboard from the
  Pagerduty Alert.
- Add `--link-annotations` option. If set, this option will any links provided in the check or entity annotations as
  links in the Pagerduty Event.
- Add ability to template additional [PD-CEF][PD-CEF] Fields from Sensu event data:
  - Add `--use-event-timestamp` option. If set this option will set the `Timestamp` field to the
    timestamp of the Sensu event.
  - Add `--class-template`, `--group-template` and `--component-template` option. If provided, this option allows for
    the `Class`, `Group` and `Component` [PD-CEF][PD-CEF] fields to be templated from Sensu event data. Note the
    `Component` field is set to the Sensu events `.Check.Name` by default if no template is provided to retain
    compatibility.

Signed-off-by: Angus Williams <anguswilliams@gmail.com>
  • Loading branch information
anguswilliams committed Mar 12, 2024
1 parent f116eaa commit ab1e367
Show file tree
Hide file tree
Showing 4 changed files with 678 additions and 62 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,19 @@ Versioning](http://semver.org/spec/v2.0.0.html).

## Unreleased

### Added
- Add `--client-name` and `--sensu-base-url`. If provided, these options add a link to the Sensu dashboard from the
Pagerduty Alert.
- Add `--link-annotations` option. If set, this option will any links provided in the check or entity annotations as
links in the Pagerduty Event.
- Add ability to template additional PD-CEF Fields from Sensu event data:
- Add `--use-event-timestamp` option. If set this option will set the `Timestamp` field to the
timestamp of the Sensu event.
- Add `--class-template`, `--group-template` and `--component-template` option. If provided, this option allows for
the `Class`, `Group` and `Component` PD-CEF fields to be templated from Sensu event data. Note the
`Component` field is set to the Sensu events `.Check.Name` by default if no template is provided to retain
compatibility.

## 2.6.0 - 2024-02-08

### Changed
Expand Down
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,24 @@ Available Commands:
Flags:
-e, --alternate-endpoint string The endpoint to use to send the PagerDuty events, can be set with PAGERDUTY_ALTERNATE_ENDPOINT
--class-template string Template for PD-CEF class field, can be set with PAGERDUTY_CLASS_TEMPLATE
--client-name string Name for the client, this will appear in Pagerduty when events are logged (default "Sensu")
--component-template string Template for PD-CEF component field, can be set with PAGERDUTY_COMPONENT_TEMPLATE
--contact-routing Enable contact routing
-k, --dedup-key-template string The PagerDuty V2 API deduplication key template, can be set with PAGERDUTY_DEDUP_KEY_TEMPLATE (default "{{.Entity.Name}}-{{.Check.Name}}")
--details-format string The format of the details output ('string' or 'json'), can be set with PAGERDUTY_DETAILS_FORMAT (default "string")
-d, --details-template string The template for the alert details, can be set with PAGERDUTY_DETAILS_TEMPLATE (default full event JSON)
--group-template string Template for PD-CEF group field, can be set with PAGERDUTY_GROUP_TEMPLATE
-h, --help help for sensu-pagerduty-handler
-l, --link-annotations Add links for any annotations that are a URL
-u, --sensu-base-url string Base URL for sensu. The handler will add a link to the event using this
-s, --status-map string The status map used to translate a Sensu check status to a PagerDuty severity, can be set with PAGERDUTY_STATUS_MAP
-S, --summary-template string The template for the alert summary, can be set with PAGERDUTY_SUMMARY_TEMPLATE (default "{{.Entity.Name}}/{{.Check.Name}} : {{.Check.Output}}")
--team string Envvar name for pager team(alphanumeric and underscores) holding PagerDuty V2 API authentication token, can be set with PAGERDUTY_TEAM
--team-suffix string Pager team suffix string to append if missing from team name, can be set with PAGERDUTY_TEAM_SUFFIX (default "_pagerduty_token")
--timeout uint The maximum amount of time in seconds to wait for the event to be created, can be set with PAGERDUTY_TIMEOUT (default 30)
-t, --token string The PagerDuty V2 API authentication token, can be set with PAGERDUTY_TOKEN
-T, --use-event-timestamp Use the timestamp from the Sensu event for the PD-CEF timestamp field
Use "sensu-pagerduty-handler [command] --help" for more information about a command.
```
Expand Down Expand Up @@ -158,10 +165,13 @@ override the corresponding environment variable.
| Argument | Environment Variable |
|----------------------|------------------------------|
| --alternate-endpoint | PAGERDUTY_ALTERNATE_ENDPOINT |
| --class-template | PAGERDUTY_CLASS_TEMPLATE |
| --component-template | PAGERDUTY_COMPONENT_TEMPLATE |
| --group-template | PAGERDUTY_GROUP_TEMPLATE |
| --dedup-key-template | PAGERDUTY_DEDUP_KEY_TEMPLATE |
| --details-template | PAGERDUTY_DETAILS_TEMPLATE |
| --details-format | PAGERDUTY_DETAILS_FORMAT |
| --sensu-base-url | PAGERDUTY_SENSU_BASE_URL |
| --status-map | PAGERDUTY_STATUS_MAP |
| --summary-template | PAGERDUTY_SUMMARY_TEMPLATE |
| --team | PAGERDUTY_TEAM |
Expand Down
223 changes: 219 additions & 4 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ type HandlerConfig struct {
alternateEndpoint string
contactRouting bool
contacts []string
clientName string
sensuBaseUrl string
linkAnnotations bool
useEventTimestamp bool
classTemplate string
groupTemplate string
componentTemplate string
}

type eventStatusMap map[string][]uint32
Expand Down Expand Up @@ -163,6 +170,68 @@ var (
Value: &config.contactRouting,
Default: false,
},
&sensu.PluginConfigOption[string]{
Path: "client-name",
Env: "",
Argument: "client-name",
Usage: "Name for the client, this will appear in Pagerduty when events are logged",
Value: &config.clientName,
Default: "Sensu",
},
&sensu.PluginConfigOption[string]{
Path: "sensu-base-url",
Env: "PAGERDUTY_SENSU_BASE_URL",
Argument: "sensu-base-url",
Shorthand: "u",
Usage: "Base URL for sensu. The handler will add a link to the event using this",
Value: &config.sensuBaseUrl,
Default: "",
},
&sensu.PluginConfigOption[bool]{
Path: "link-annotations",
Env: "",
Argument: "link-annotations",
Shorthand: "l",
Usage: "Add links for any annotations that are a URL",
Value: &config.linkAnnotations,
Default: false,
},
&sensu.PluginConfigOption[bool]{
Path: "use-event-timestamp",
Env: "",
Argument: "use-event-timestamp",
Shorthand: "T",
Usage: "Use the timestamp from the Sensu event for the PD-CEF timestamp field",
Value: &config.useEventTimestamp,
Default: false,
},
&sensu.PluginConfigOption[string]{
Path: "class-template",
Env: "PAGERDUTY_CLASS_TEMPLATE",
Argument: "class-template",
Shorthand: "",
Usage: "Template for PD-CEF class field, can be set with PAGERDUTY_CLASS_TEMPLATE",
Value: &config.classTemplate,
Default: "",
},
&sensu.PluginConfigOption[string]{
Path: "group-template",
Env: "PAGERDUTY_GROUP_TEMPLATE",
Argument: "group-template",
Shorthand: "",
Usage: "Template for PD-CEF group field, can be set with PAGERDUTY_GROUP_TEMPLATE",
Value: &config.groupTemplate,
Default: "",
},
&sensu.PluginConfigOption[string]{
Path: "component-template",
Env: "PAGERDUTY_COMPONENT_TEMPLATE",
Argument: "component-template",
Shorthand: "",
Usage: "Template for PD-CEF component field, can be set with PAGERDUTY_COMPONENT_TEMPLATE",
Value: &config.componentTemplate,
Default: "",
},
}
)

Expand Down Expand Up @@ -217,7 +286,6 @@ func checkArgs(event *corev2.Event) error {
if len(teamToken) != 0 {
config.authToken = teamToken
}

}

if config.contactRouting {
Expand Down Expand Up @@ -352,6 +420,21 @@ func manageIncident(event *corev2.Event, token string) error {
return err
}

group, err := getGroup(event)
if err != nil {
return err
}

component, err := getComponent(event)
if err != nil {
return err
}

class, err := getClass(event)
if err != nil {
return err
}

// "The maximum permitted length of PG event is 512 KB. Let's limit check output to 256KB to prevent triggering a failed send"
if len(event.Check.Output) > 256000 {
log.Printf("Warning Incident Payload Truncated!")
Expand All @@ -360,10 +443,13 @@ func manageIncident(event *corev2.Event, token string) error {

pdPayload := pagerduty.V2Payload{
Source: event.Entity.Name,
Component: event.Check.Name,
Component: component,
Severity: severity,
Summary: summary,
Details: details,
Class: class,
Group: group,
Timestamp: getTimestamp(event),
}

action := "trigger"
Expand All @@ -384,6 +470,9 @@ func manageIncident(event *corev2.Event, token string) error {
Action: action,
Payload: &pdPayload,
DedupKey: dedupKey,
Client: config.clientName,
ClientURL: getClientUrl(event),
Links: getLinks(event),
}

client := pagerduty.NewClient()
Expand Down Expand Up @@ -413,11 +502,17 @@ func manageIncident(event *corev2.Event, token string) error {
return err
}
// FUTURE send to AH
log.Printf("Failback event (%s) submitted to PagerDuty, Status: %s, Dedup Key: %s, Message: %s", action, failResponse.Status, failResponse.DedupKey, failResponse.Message)
log.Printf(
"Failback event (%s) submitted to PagerDuty, Status: %s, Dedup Key: %s, Message: %s", action,
failResponse.Status, failResponse.DedupKey, failResponse.Message,
)
}

// FUTURE send to AH
log.Printf("Event (%s) submitted to PagerDuty, Status: %s, Dedup Key: %s, Message: %s", action, eventResponse.Status, eventResponse.DedupKey, eventResponse.Message)
log.Printf(
"Event (%s) submitted to PagerDuty, Status: %s, Dedup Key: %s, Message: %s", action, eventResponse.Status,
eventResponse.DedupKey, eventResponse.Message,
)
return nil
}

Expand Down Expand Up @@ -490,6 +585,69 @@ func getSummary(event *corev2.Event) (string, error) {
return summary, nil
}

func getTimestamp(event *corev2.Event) string {
timestamp := ""
if config.useEventTimestamp {
timestamp = time.Unix(event.Timestamp, 0).Format(time.RFC3339)
}

return timestamp
}

func getGroup(event *corev2.Event) (string, error) {
var (
group string
err error
)

if len(config.groupTemplate) > 0 {
group, err = templates.EvalTemplate("group", config.groupTemplate, event)
if err != nil {
return "", fmt.Errorf("failued to evaluate template %s: %v", config.groupTemplate, err)
}
} else {
group = ""
}

return group, nil
}

func getComponent(event *corev2.Event) (string, error) {
var (
component string
err error
)

if len(config.componentTemplate) > 0 {
component, err = templates.EvalTemplate("component", config.componentTemplate, event)
if err != nil {
return "", fmt.Errorf("failued to evaluate template %s: %v", config.componentTemplate, err)
}
} else {
component = event.Check.Name
}

return component, nil
}

func getClass(event *corev2.Event) (string, error) {
var (
class string
err error
)

if len(config.classTemplate) > 0 {
class, err = templates.EvalTemplate("class", config.classTemplate, event)
if err != nil {
return "", fmt.Errorf("failued to evaluate template %s: %v", config.classTemplate, err)
}
} else {
class = ""
}

return class, nil
}

func getDetails(event *corev2.Event) (details interface{}, err error) {
if len(config.detailsTemplate) > 0 {
detailsStr, err := templates.EvalTemplate("details", config.detailsTemplate, event)
Expand All @@ -511,3 +669,60 @@ func getDetails(event *corev2.Event) (details interface{}, err error) {
}
return details, nil
}

func getClientUrl(event *corev2.Event) string {
if config.sensuBaseUrl == "" {
return ""
}

return fmt.Sprintf(
"%s/c/~/n/%s/events/%s/%s",
strings.TrimSuffix(config.sensuBaseUrl, "/"),
event.Namespace,
event.Entity.Name,
event.Check.Name,
)
}

type Link struct {
Text string `json:"text"`
Href string `json:"href"`
}

func isLink(s string) bool {
_, err := url.ParseRequestURI(s)

return err == nil
}

func getLinks(event *corev2.Event) []interface{} {
links := make([]interface{}, 0, len(event.Check.Annotations))

if !config.linkAnnotations {
return links
}

for key, value := range event.Check.Annotations {
if isLink(value) {
links = append(
links, Link{
Text: fmt.Sprintf("check %s", key),
Href: value,
},
)
}
}

for key, value := range event.Entity.Annotations {
if isLink(value) {
links = append(
links, Link{
Text: fmt.Sprintf("entity %s", key),
Href: value,
},
)
}
}

return links
}
Loading

0 comments on commit ab1e367

Please sign in to comment.