Skip to content

Commit 2bb2fcd

Browse files
authored
feat: Adding new PagerDuty integration based on Events API v2 (#105)
Signed-off-by: Eric Tendian <erictendian@gmail.com>
1 parent 8f2ae5e commit 2bb2fcd

File tree

4 files changed

+526
-0
lines changed

4 files changed

+526
-0
lines changed

docs/services/pagerduty_v2.md

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# PagerDuty V2
2+
3+
## Parameters
4+
5+
The PagerDuty notification service is used to trigger PagerDuty events and requires specifying the following settings:
6+
7+
* `serviceKeys` - a dictionary with the following structure:
8+
* `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)
9+
10+
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.
11+
12+
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.
13+
14+
## Configuration
15+
16+
The following snippet contains sample PagerDuty service configuration. It assumes the service you want to alert on is called `my-service`.
17+
18+
```yaml
19+
apiVersion: v1
20+
kind: Secret
21+
metadata:
22+
name: <secret-name>
23+
stringData:
24+
pagerduty-key-my-service: <pd-integration-key>
25+
```
26+
27+
```yaml
28+
apiVersion: v1
29+
kind: ConfigMap
30+
metadata:
31+
name: <config-map-name>
32+
data:
33+
service.pagerdutyv2: |
34+
serviceKeys:
35+
my-service: $pagerduty-key-my-service
36+
```
37+
38+
## Template
39+
40+
[Notification templates](../templates.md) support specifying subject for PagerDuty notifications:
41+
42+
```yaml
43+
apiVersion: v1
44+
kind: ConfigMap
45+
metadata:
46+
name: <config-map-name>
47+
data:
48+
template.rollout-aborted: |
49+
message: Rollout {{.rollout.metadata.name}} is aborted.
50+
pagerdutyv2:
51+
summary: "Rollout {{.rollout.metadata.name}} is aborted."
52+
severity: "critical"
53+
source: "{{.rollout.metadata.name}}"
54+
```
55+
56+
The parameters for the PagerDuty configuration in the template generally match with the payload for the Events API v2 endpoint. All parameters are strings.
57+
58+
* `summary` - (required) A brief text summary of the event, used to generate the summaries/titles of any associated alerts.
59+
* `severity` - (required) The perceived severity of the status the event is describing with respect to the affected system. Allowed values: `critical`, `warning`, `error`, `info`
60+
* `source` - (required) The unique location of the affected system, preferably a hostname or FQDN.
61+
* `component` - Component of the source machine that is responsible for the event.
62+
* `group` - Logical grouping of components of a service.
63+
* `class` - The class/type of the event.
64+
* `url` - The URL that should be used for the link "View in ArgoCD" in PagerDuty.
65+
66+
The `timestamp` and `custom_details` parameters are not currently supported.
67+
68+
## Annotation
69+
70+
Annotation sample for PagerDuty notifications:
71+
72+
```yaml
73+
apiVersion: argoproj.io/v1alpha1
74+
kind: Rollout
75+
metadata:
76+
annotations:
77+
notifications.argoproj.io/subscribe.on-rollout-aborted.pagerdutyv2: "<serviceID for Pagerduty>"
78+
```

pkg/services/pagerdutyv2.go

+165
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
package services
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"fmt"
7+
texttemplate "text/template"
8+
9+
"github.com/PagerDuty/go-pagerduty"
10+
log "github.com/sirupsen/logrus"
11+
)
12+
13+
type PagerDutyV2Notification struct {
14+
Summary string `json:"summary"`
15+
Severity string `json:"severity"`
16+
Source string `json:"source"`
17+
Component string `json:"component,omitempty"`
18+
Group string `json:"group,omitempty"`
19+
Class string `json:"class,omitempty"`
20+
URL string `json:"url"`
21+
}
22+
23+
type PagerdutyV2Options struct {
24+
ServiceKeys map[string]string `json:"serviceKeys"`
25+
}
26+
27+
func (p *PagerDutyV2Notification) GetTemplater(name string, f texttemplate.FuncMap) (Templater, error) {
28+
summary, err := texttemplate.New(name).Funcs(f).Parse(p.Summary)
29+
if err != nil {
30+
return nil, err
31+
}
32+
severity, err := texttemplate.New(name).Funcs(f).Parse(p.Severity)
33+
if err != nil {
34+
return nil, err
35+
}
36+
source, err := texttemplate.New(name).Funcs(f).Parse(p.Source)
37+
if err != nil {
38+
return nil, err
39+
}
40+
component, err := texttemplate.New(name).Funcs(f).Parse(p.Component)
41+
if err != nil {
42+
return nil, err
43+
}
44+
group, err := texttemplate.New(name).Funcs(f).Parse(p.Group)
45+
if err != nil {
46+
return nil, err
47+
}
48+
class, err := texttemplate.New(name).Funcs(f).Parse(p.Class)
49+
if err != nil {
50+
return nil, err
51+
}
52+
url, err := texttemplate.New(name).Funcs(f).Parse(p.URL)
53+
if err != nil {
54+
return nil, err
55+
}
56+
57+
return func(notification *Notification, vars map[string]interface{}) error {
58+
if notification.PagerdutyV2 == nil {
59+
notification.PagerdutyV2 = &PagerDutyV2Notification{}
60+
}
61+
var summaryData bytes.Buffer
62+
if err := summary.Execute(&summaryData, vars); err != nil {
63+
return err
64+
}
65+
notification.PagerdutyV2.Summary = summaryData.String()
66+
67+
var severityData bytes.Buffer
68+
if err := severity.Execute(&severityData, vars); err != nil {
69+
return err
70+
}
71+
notification.PagerdutyV2.Severity = severityData.String()
72+
73+
var sourceData bytes.Buffer
74+
if err := source.Execute(&sourceData, vars); err != nil {
75+
return err
76+
}
77+
notification.PagerdutyV2.Source = sourceData.String()
78+
79+
var componentData bytes.Buffer
80+
if err := component.Execute(&componentData, vars); err != nil {
81+
return err
82+
}
83+
notification.PagerdutyV2.Component = componentData.String()
84+
85+
var groupData bytes.Buffer
86+
if err := group.Execute(&groupData, vars); err != nil {
87+
return err
88+
}
89+
notification.PagerdutyV2.Group = groupData.String()
90+
91+
var classData bytes.Buffer
92+
if err := class.Execute(&classData, vars); err != nil {
93+
return err
94+
}
95+
notification.PagerdutyV2.Class = classData.String()
96+
97+
var urlData bytes.Buffer
98+
if err := url.Execute(&urlData, vars); err != nil {
99+
return err
100+
}
101+
notification.PagerdutyV2.URL = urlData.String()
102+
103+
return nil
104+
}, nil
105+
}
106+
107+
func NewPagerdutyV2Service(opts PagerdutyV2Options) NotificationService {
108+
return &pagerdutyV2Service{opts: opts}
109+
}
110+
111+
type pagerdutyV2Service struct {
112+
opts PagerdutyV2Options
113+
}
114+
115+
func (p pagerdutyV2Service) Send(notification Notification, dest Destination) error {
116+
routingKey, ok := p.opts.ServiceKeys[dest.Recipient]
117+
if !ok {
118+
return fmt.Errorf("no API key configured for recipient %s", dest.Recipient)
119+
}
120+
121+
if notification.PagerdutyV2 == nil {
122+
return fmt.Errorf("no config found for pagerdutyv2")
123+
}
124+
125+
event := buildEvent(routingKey, notification)
126+
127+
response, err := pagerduty.ManageEventWithContext(context.TODO(), event)
128+
if err != nil {
129+
log.Errorf("Error: %v", err)
130+
return err
131+
}
132+
log.Debugf("PagerDuty event triggered succesfully. Status: %v, Message: %v", response.Status, response.Message)
133+
return nil
134+
}
135+
136+
func buildEvent(routingKey string, notification Notification) pagerduty.V2Event {
137+
payload := pagerduty.V2Payload{
138+
Summary: notification.PagerdutyV2.Summary,
139+
Severity: notification.PagerdutyV2.Severity,
140+
Source: notification.PagerdutyV2.Source,
141+
}
142+
143+
if len(notification.PagerdutyV2.Component) > 0 {
144+
payload.Component = notification.PagerdutyV2.Component
145+
}
146+
if len(notification.PagerdutyV2.Group) > 0 {
147+
payload.Group = notification.PagerdutyV2.Group
148+
}
149+
if len(notification.PagerdutyV2.Class) > 0 {
150+
payload.Class = notification.PagerdutyV2.Class
151+
}
152+
153+
event := pagerduty.V2Event{
154+
RoutingKey: routingKey,
155+
Action: "trigger",
156+
Payload: &payload,
157+
Client: "ArgoCD",
158+
}
159+
160+
if len(notification.PagerdutyV2.URL) > 0 {
161+
event.ClientURL = notification.PagerdutyV2.URL
162+
}
163+
164+
return event
165+
}

0 commit comments

Comments
 (0)