Skip to content
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
78 changes: 78 additions & 0 deletions docs/services/pagerduty_v2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# PagerDuty V2

## Parameters

The PagerDuty notification service is used to trigger PagerDuty events and requires specifying the following settings:

* `serviceKeys` - a dictionary with the following structure:
* `service-name: $pagerduty-key-service-name` where `service-name` is the name you want to use for the service to make events for, and `$pagerduty-key-service-name` is a reference to the secret that contains the actual PagerDuty integration key (Events API v2 integration)

If you want multiple Argo apps to trigger events to their respective PagerDuty services, create an integration key in each service you want to setup alerts for.

To create a PagerDuty integration key, [follow these instructions](https://support.pagerduty.com/docs/services-and-integrations#create-a-generic-events-api-integration) to add an Events API v2 integration to the service of your choice.

## Configuration

The following snippet contains sample PagerDuty service configuration. It assumes the service you want to alert on is called `my-service`.

```yaml
apiVersion: v1
kind: Secret
metadata:
name: <secret-name>
stringData:
pagerduty-key-my-service: <pd-integration-key>
```

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: <config-map-name>
data:
service.pagerdutyv2: |
serviceKeys:
my-service: $pagerduty-key-my-service
```

## Template

[Notification templates](../templates.md) support specifying subject for PagerDuty notifications:

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: <config-map-name>
data:
template.rollout-aborted: |
message: Rollout {{.rollout.metadata.name}} is aborted.
pagerdutyv2:
summary: "Rollout {{.rollout.metadata.name}} is aborted."
severity: "critical"
source: "{{.rollout.metadata.name}}"
```

The parameters for the PagerDuty configuration in the template generally match with the payload for the Events API v2 endpoint. All parameters are strings.

* `summary` - (required) A brief text summary of the event, used to generate the summaries/titles of any associated alerts.
* `severity` - (required) The perceived severity of the status the event is describing with respect to the affected system. Allowed values: `critical`, `warning`, `error`, `info`
* `source` - (required) The unique location of the affected system, preferably a hostname or FQDN.
* `component` - Component of the source machine that is responsible for the event.
* `group` - Logical grouping of components of a service.
* `class` - The class/type of the event.
* `url` - The URL that should be used for the link "View in ArgoCD" in PagerDuty.

The `timestamp` and `custom_details` parameters are not currently supported.

## Annotation

Annotation sample for PagerDuty notifications:

```yaml
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
annotations:
notifications.argoproj.io/subscribe.on-rollout-aborted.pagerdutyv2: "<serviceID for Pagerduty>"
```
165 changes: 165 additions & 0 deletions pkg/services/pagerdutyv2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package services

import (
"bytes"
"context"
"fmt"
texttemplate "text/template"

"github.com/PagerDuty/go-pagerduty"
log "github.com/sirupsen/logrus"
)

type PagerDutyV2Notification struct {
Summary string `json:"summary"`
Severity string `json:"severity"`
Source string `json:"source"`
Component string `json:"component,omitempty"`
Group string `json:"group,omitempty"`
Class string `json:"class,omitempty"`
URL string `json:"url"`
}

type PagerdutyV2Options struct {
ServiceKeys map[string]string `json:"serviceKeys"`
}

func (p *PagerDutyV2Notification) GetTemplater(name string, f texttemplate.FuncMap) (Templater, error) {
summary, err := texttemplate.New(name).Funcs(f).Parse(p.Summary)
if err != nil {
return nil, err
}
severity, err := texttemplate.New(name).Funcs(f).Parse(p.Severity)
if err != nil {
return nil, err
}
source, err := texttemplate.New(name).Funcs(f).Parse(p.Source)
if err != nil {
return nil, err
}
component, err := texttemplate.New(name).Funcs(f).Parse(p.Component)
if err != nil {
return nil, err
}
group, err := texttemplate.New(name).Funcs(f).Parse(p.Group)
if err != nil {
return nil, err
}
class, err := texttemplate.New(name).Funcs(f).Parse(p.Class)
if err != nil {
return nil, err
}
url, err := texttemplate.New(name).Funcs(f).Parse(p.URL)
if err != nil {
return nil, err
}

return func(notification *Notification, vars map[string]interface{}) error {
if notification.PagerdutyV2 == nil {
notification.PagerdutyV2 = &PagerDutyV2Notification{}
}
var summaryData bytes.Buffer
if err := summary.Execute(&summaryData, vars); err != nil {
return err
}
notification.PagerdutyV2.Summary = summaryData.String()

var severityData bytes.Buffer
if err := severity.Execute(&severityData, vars); err != nil {
return err
}
notification.PagerdutyV2.Severity = severityData.String()

var sourceData bytes.Buffer
if err := source.Execute(&sourceData, vars); err != nil {
return err
}
notification.PagerdutyV2.Source = sourceData.String()

var componentData bytes.Buffer
if err := component.Execute(&componentData, vars); err != nil {
return err
}
notification.PagerdutyV2.Component = componentData.String()

var groupData bytes.Buffer
if err := group.Execute(&groupData, vars); err != nil {
return err
}
notification.PagerdutyV2.Group = groupData.String()

var classData bytes.Buffer
if err := class.Execute(&classData, vars); err != nil {
return err
}
notification.PagerdutyV2.Class = classData.String()

var urlData bytes.Buffer
if err := url.Execute(&urlData, vars); err != nil {
return err
}
notification.PagerdutyV2.URL = urlData.String()

return nil
}, nil
}

func NewPagerdutyV2Service(opts PagerdutyV2Options) NotificationService {
return &pagerdutyV2Service{opts: opts}
}

type pagerdutyV2Service struct {
opts PagerdutyV2Options
}

func (p pagerdutyV2Service) Send(notification Notification, dest Destination) error {
routingKey, ok := p.opts.ServiceKeys[dest.Recipient]
if !ok {
return fmt.Errorf("no API key configured for recipient %s", dest.Recipient)
}

if notification.PagerdutyV2 == nil {
return fmt.Errorf("no config found for pagerdutyv2")
}

event := buildEvent(routingKey, notification)

response, err := pagerduty.ManageEventWithContext(context.TODO(), event)
if err != nil {
log.Errorf("Error: %v", err)
return err
}
log.Debugf("PagerDuty event triggered succesfully. Status: %v, Message: %v", response.Status, response.Message)
return nil
}

func buildEvent(routingKey string, notification Notification) pagerduty.V2Event {
payload := pagerduty.V2Payload{
Summary: notification.PagerdutyV2.Summary,
Severity: notification.PagerdutyV2.Severity,
Source: notification.PagerdutyV2.Source,
}

if len(notification.PagerdutyV2.Component) > 0 {
payload.Component = notification.PagerdutyV2.Component
}
if len(notification.PagerdutyV2.Group) > 0 {
payload.Group = notification.PagerdutyV2.Group
}
if len(notification.PagerdutyV2.Class) > 0 {
payload.Class = notification.PagerdutyV2.Class
}

event := pagerduty.V2Event{
RoutingKey: routingKey,
Action: "trigger",
Payload: &payload,
Client: "ArgoCD",
}

if len(notification.PagerdutyV2.URL) > 0 {
event.ClientURL = notification.PagerdutyV2.URL
}

return event
}
Loading