Skip to content

Commit

Permalink
Add pager duty config
Browse files Browse the repository at this point in the history
  • Loading branch information
alexandreLamarre committed Nov 18, 2022
1 parent 5bb6c30 commit 4f0d8a4
Show file tree
Hide file tree
Showing 9 changed files with 615 additions and 382 deletions.
3 changes: 2 additions & 1 deletion internal/alertmanager/alertmanager_main.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,8 @@ func startOpniServer(configFile string) {
lg.Info("request configs are equal")
})
hookServer := &http.Server{
Addr: fmt.Sprintf(":%d", shared.AlertingDefaultHookPort),
// explicitly set this to 0.0.0.0 for test environment
Addr: fmt.Sprintf("0.0.0.0:%d", shared.AlertingDefaultHookPort),
Handler: mux,
}
go func() {
Expand Down
68 changes: 65 additions & 3 deletions pkg/alerting/routing/api_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ package routing

import (
"fmt"
"net/url"
"time"

"github.com/containerd/containerd/pkg/cri/config"
cfg "github.com/prometheus/alertmanager/config"
commoncfg "github.com/prometheus/common/config"
"github.com/prometheus/common/model"
"golang.org/x/text/cases"
"golang.org/x/text/language"
"net/url"
"time"
)

var (
Expand Down Expand Up @@ -38,6 +39,23 @@ var (
Text: ``,
}
normalizeTitle = cases.Title(language.AmericanEnglish)

// DefaultPagerdutyConfig defines default values for PagerDuty configurations.
DefaultPagerdutyConfig = PagerdutyConfig{
NotifierConfig: &cfg.NotifierConfig{
VSendResolved: true,
},
Description: `{{ template "pagerduty.default.description" .}}`,
Client: `{{ template "pagerduty.default.client" . }}`,
ClientURL: `{{ template "pagerduty.default.clientURL" . }}`,
}
// DefaultPagerdutyDetails defines the default values for PagerDuty details.
DefaultPagerdutyDetails = map[string]string{
"firing": `{{ template "pagerduty.default.instances" .Alerts.Firing }}`,
"resolved": `{{ template "pagerduty.default.instances" .Alerts.Resolved }}`,
"num_firing": `{{ .Alerts.Firing | len }}`,
"num_resolved": `{{ .Alerts.Resolved | len }}`,
}
)

// Receiver configuration provides configuration on how to contact a receiver.
Expand All @@ -47,7 +65,7 @@ type Receiver struct {
Name string `yaml:"name" json:"name"`

EmailConfigs []*EmailConfig `yaml:"email_configs,omitempty" json:"email_configs,omitempty"`
PagerdutyConfigs []*cfg.PagerdutyConfig `yaml:"pagerduty_configs,omitempty" json:"pagerduty_configs,omitempty"`
PagerdutyConfigs []*PagerdutyConfig `yaml:"pagerduty_configs,omitempty" json:"pagerduty_configs,omitempty"`
SlackConfigs []*SlackConfig `yaml:"slack_configs,omitempty" json:"slack_configs,omitempty"`
WebhookConfigs []*cfg.WebhookConfig `yaml:"webhook_configs,omitempty" json:"webhook_configs,omitempty"`
OpsGenieConfigs []*cfg.OpsGenieConfig `yaml:"opsgenie_configs,omitempty" json:"opsgenie_configs,omitempty"`
Expand Down Expand Up @@ -158,6 +176,50 @@ func (c *SlackConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
return nil
}

// PagerdutyConfig configures notifications via PagerDuty.
type PagerdutyConfig struct {
*cfg.NotifierConfig `yaml:",inline" json:",inline"`

HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"`

// Change from secret to string since the string is stored in a kube secret anyways
ServiceKey string `yaml:"service_key,omitempty" json:"service_key,omitempty"`
// Change from secret to string since the string is stored in a kube secret anyways
RoutingKey string `yaml:"routing_key,omitempty" json:"routing_key,omitempty"`
URL *cfg.URL `yaml:"url,omitempty" json:"url,omitempty"`
Client string `yaml:"client,omitempty" json:"client,omitempty"`
ClientURL string `yaml:"client_url,omitempty" json:"client_url,omitempty"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Details map[string]string `yaml:"details,omitempty" json:"details,omitempty"`
Images []*cfg.PagerdutyImage `yaml:"images,omitempty" json:"images,omitempty"`
Links []*cfg.PagerdutyLink `yaml:"links,omitempty" json:"links,omitempty"`
Severity string `yaml:"severity,omitempty" json:"severity,omitempty"`
Class string `yaml:"class,omitempty" json:"class,omitempty"`
Component string `yaml:"component,omitempty" json:"component,omitempty"`
Group string `yaml:"group,omitempty" json:"group,omitempty"`
}

// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *PagerdutyConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
*c = DefaultPagerdutyConfig
type plain PagerdutyConfig
if err := unmarshal((*plain)(c)); err != nil {
return err
}
if c.RoutingKey == "" && c.ServiceKey == "" {
return fmt.Errorf("missing service or routing key in PagerDuty config")
}
if c.Details == nil {
c.Details = make(map[string]string)
}
for k, v := range DefaultPagerdutyDetails {
if _, ok := c.Details[k]; !ok {
c.Details[k] = v
}
}
return nil
}

// required due to https://github.com/rancher/opni/issues/542
type GlobalConfig struct {
// ResolveTimeout is the time after which an alert is declared resolved
Expand Down
2 changes: 1 addition & 1 deletion pkg/alerting/routing/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ func (o *OpniInternalRouting) Add(
Position: metadata.Position,
}
} else {
return fmt.Errorf("(conditionId, endpointId) key already exists and should not")
return shared.WithFailedPreconditionError("(conditionId, endpointId) key already exists and should not")
}
}
return nil
Expand Down
25 changes: 25 additions & 0 deletions pkg/alerting/routing/ops.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@ func (r *RoutingTree) UpdateIndividualEndpointNode(
if e := req.GetAlertEndpoint().GetEmail(); e != nil {
return EmailEndpointInternalId
}
if p := req.GetAlertEndpoint().GetPagerDuty(); p != nil {
return PagerDutyEndpointInternalId
}
return "unknown"
}
newEndpointType := newEndpointTypeFunc()
Expand Down Expand Up @@ -174,6 +177,16 @@ func (r *RoutingTree) UpdateIndividualEndpointNode(
return err
}
r.Receivers[recvPos].EmailConfigs[toTraverseItem.position] = emailCfg
case PagerDutyEndpointInternalId:
pagerCfg, err := NewPagerDutyReceiverNode(req.GetAlertEndpoint().GetPagerDuty())
if err != nil {
return err
}
pagerCfg, err = WithPagerDutyImplementatino(pagerCfg, toTraverseItem.details)
if err != nil {
return err
}
r.Receivers[recvPos].PagerdutyConfigs[toTraverseItem.position] = pagerCfg
}
}
} else {
Expand Down Expand Up @@ -205,6 +218,12 @@ func (r *RoutingTree) UpdateIndividualEndpointNode(
r.Receivers[recvPos].EmailConfigs,
toTraverseItem.position,
toTraverseItem.position+1)
case PagerDutyEndpointInternalId:
r.Receivers[recvPos].PagerdutyConfigs = slices.Delete(
r.Receivers[recvPos].PagerdutyConfigs,
toTraverseItem.position,
toTraverseItem.position+1)

}

newPos, newType, err := r.Receivers[recvPos].AddEndpoint(req.GetAlertEndpoint(), toTraverseItem.details)
Expand Down Expand Up @@ -269,6 +288,12 @@ func (r *RoutingTree) DeleteIndividualEndpointNode(
r.Receivers[recvPos].EmailConfigs,
toTraverseItem.position,
toTraverseItem.position+1)
case PagerDutyEndpointInternalId:
r.Receivers[recvPos].PagerdutyConfigs = slices.Delete(
r.Receivers[recvPos].PagerdutyConfigs,
toTraverseItem.position,
toTraverseItem.position+1,
)
}
}
// clean up
Expand Down
44 changes: 44 additions & 0 deletions pkg/alerting/routing/receivers.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (

const SlackEndpointInternalId = "slack"
const EmailEndpointInternalId = "email"
const PagerDutyEndpointInternalId = "pagerduty"

func (r *Receiver) AddEndpoint(
alertEndpoint *alertingv1.AlertEndpoint,
Expand Down Expand Up @@ -46,6 +47,19 @@ func (r *Receiver) AddEndpoint(
r.EmailConfigs = append(r.EmailConfigs, emailCfg)
return len(r.EmailConfigs) - 1, EmailEndpointInternalId, nil
}
if p := alertEndpoint.GetPagerDuty(); p != nil {
pagerCfg, err := NewPagerDutyReceiverNode(p)
if err != nil {
return -1, "", err
}

pagerCfg, err = WithPagerDutyImplementatino(pagerCfg, details)
if err != nil {
return -1, "", err
}
r.PagerdutyConfigs = append(r.PagerdutyConfigs, pagerCfg)
return len(r.PagerdutyConfigs) - 1, PagerDutyEndpointInternalId, nil
}
return -1, "", validation.Errorf("unknown endpoint type : %v", alertEndpoint)
}

Expand Down Expand Up @@ -179,6 +193,27 @@ func WithEmailImplementation(email *EmailConfig, impl *alertingv1.EndpointImplem
return email, nil
}

func NewPagerDutyReceiverNode(endpoint *alertingv1.PagerDutyEndpoint) (*PagerdutyConfig, error) {
pg := &PagerdutyConfig{
ServiceKey: endpoint.IntegrationKey,
}
return pg, nil
}

func WithPagerDutyImplementatino(pg *PagerdutyConfig, impl *alertingv1.EndpointImplementation) (*PagerdutyConfig, error) {
if impl.SendResolved == nil {
pg.NotifierConfig = &cfg.NotifierConfig{
VSendResolved: false,
}
} else {
pg.NotifierConfig = &cfg.NotifierConfig{
VSendResolved: *impl.SendResolved,
}
}
pg.Description = impl.Title + "\n---\n" + impl.Body
return pg, nil
}

// does the opposite of WithXXXXImplementation
func (r *RoutingTree) ExtractImplementationDetails(conditionId, endpointType string, position int) (*alertingv1.EndpointImplementation, error) {
// find the condition Id receiver
Expand All @@ -200,6 +235,15 @@ func (r *RoutingTree) ExtractImplementationDetails(conditionId, endpointType str
Body: r.Receivers[recvIdx].EmailConfigs[position].HTML,
SendResolved: &r.Receivers[recvIdx].EmailConfigs[position].VSendResolved,
}, nil
case PagerDutyEndpointInternalId:
strArr := strings.Split(r.Receivers[recvIdx].PagerdutyConfigs[position].Description, "\n")
title := strArr[0]
body := strings.Join(strArr[1:], "\n")
return &alertingv1.EndpointImplementation{
Title: title,
Body: body,
SendResolved: &r.Receivers[recvIdx].PagerdutyConfigs[position].VSendResolved,
}, nil
default:
return nil, validation.Errorf("unknown endpoint type %s", endpointType)
}
Expand Down
Loading

0 comments on commit 4f0d8a4

Please sign in to comment.