Skip to content
Closed
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
1 change: 1 addition & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,7 @@ type Receiver struct {
OpsGenieConfigs []*OpsGenieConfig `yaml:"opsgenie_configs,omitempty" json:"opsgenie_configs,omitempty"`
PushoverConfigs []*PushoverConfig `yaml:"pushover_configs,omitempty" json:"pushover_configs,omitempty"`
VictorOpsConfigs []*VictorOpsConfig `yaml:"victorops_configs,omitempty" json:"victorops_configs,omitempty"`
JiraConfigs []*JiraConfig `yaml:"jira_configs,omitempty" json:"jira_configs,omitempty"`

// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline" json:"-"`
Expand Down
62 changes: 62 additions & 0 deletions config/notifiers.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,17 @@ var (
Retry: duration(1 * time.Minute),
Expire: duration(1 * time.Hour),
}

// DefaultJiraConfig defines default values for Jira configurations.
DefaultJiraConfig = JiraConfig{
NotifierConfig: NotifierConfig{
VSendResolved: false,
},
Summary: `{{ template "jira.default.summary" . }}`,
Description: `{{ template "jira.default.description" . }}`,
IssueType: `Bug`,
Priority: `Critical`,
}
)

// NotifierConfig contains base options common across all notifier configurations.
Expand Down Expand Up @@ -392,3 +403,54 @@ func (c *PushoverConfig) UnmarshalYAML(unmarshal func(interface{}) error) error
}
return checkOverflow(c.XXX, "pushover config")
}

type JiraConfig struct {
NotifierConfig `yaml:",inline" json:",inline"`

Domain string
User string
Password Secret

Project string `yaml:"project" json:"project"`
IssueType string `yaml:"issueType" json:"issueType"`
Priority string `yaml:"priority" json:"priority"`
Summary string `yaml:"summary" json:"summary"`
Description string `yaml:"description" json:"description"`
Fields map[string]interface{} `yaml:"fields" json:"fields"`

// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline" json:"-"`
}

// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *JiraConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
*c = DefaultJiraConfig
type plain JiraConfig
if err := unmarshal((*plain)(c)); err != nil {
return err
}
if c.VSendResolved {
return fmt.Errorf("sending resolved notifications to Jira is not supported")
}
// Check API access fields
if c.Domain == "" {
return fmt.Errorf("missing domain in Jira config")
}
if c.User == "" {
return fmt.Errorf("missing user in Jira config")
}
if c.Password == "" {
return fmt.Errorf("missing password in Jira config")
}
// Check required issue fields
if c.Project == "" {
return fmt.Errorf("missing project in Jira config")
}
if c.IssueType == "" {
return fmt.Errorf("missing issue type in Jira config")
}
if c.Summary == "" {
return fmt.Errorf("missing summary in Jira config")
}
return checkOverflow(c.XXX, "Jira config")
}
76 changes: 76 additions & 0 deletions notify/impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ func BuildReceiverIntegrations(nc *config.Receiver, tmpl *template.Template) []I
n := NewPushover(c, tmpl)
add("pushover", i, n, c)
}
for i, c := range nc.JiraConfigs {
n := NewJira(c, tmpl)
add("jira", i, n, c)
}
return integrations
}

Expand Down Expand Up @@ -934,6 +938,78 @@ func (n *Pushover) Notify(ctx context.Context, as ...*types.Alert) (bool, error)
return false, nil
}

type Jira struct {
conf *config.JiraConfig
tmpl *template.Template
}

func NewJira(c *config.JiraConfig, t *template.Template) *Jira {
return &Jira{conf: c, tmpl: t}
}

type jiraCreateIssueRequest struct {
Fields map[string]interface{} `json:"fields"`
}

type jiraCreateIssueResponse struct {
ID string `json:"id"`
Key string `json:"key"`
Self string `json:"self"`
}

// Notify implements the Notifier interface.
func (n *Jira) Notify(ctx context.Context, as ...*types.Alert) (bool, error) {
var err error
data := n.tmpl.Data(receiverName(ctx), groupLabels(ctx), as...)
tmpl := tmplText(n.tmpl, data, &err)

fields := map[string]interface{}{
"project": map[string]string{"key": tmpl(n.conf.Project)},
"issuetype": map[string]string{"name": tmpl(n.conf.IssueType)},
"summary": tmpl(n.conf.Summary),
"description": tmpl(n.conf.Description),
}
// check errors from tmpl()
if err != nil {
return false, err
}
if n.conf.Priority != "" {
fields["priority"] = map[string]string{"name": n.conf.Priority}
}
for key, value := range n.conf.Fields {
fields[key] = value
}
body, err := json.Marshal(jiraCreateIssueRequest{Fields: fields})
if err != nil {
return false, err
}

req, err := http.NewRequest("POST", "https://jira.atlassian.com/rest/api/2/issue", bytes.NewBuffer(body))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will create a new JIRA every group_interval:
https://prometheus.io/docs/alerting/configuration/#route-route

I'm guessing it'll also create a new JIRA for every resolved alert notification.

Copy link
Contributor

@mattbostock mattbostock Jan 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correction: VSendResolved is set to false, so it won't create JIRAs for resolved alerts.

if err != nil {
return false, err
}
req.SetBasicAuth(n.conf.User, string(n.conf.Password))
req.URL.Host = n.conf.Domain
req.Host = n.conf.Domain
req.Header.Add("Content-Type", "application/json")

log.Debugf("Sending request body %q to %s", string(body), req.URL)
res, err := ctxhttp.Do(ctx, http.DefaultClient, req)
if err != nil {
return false, err
}
defer res.Body.Close()

// issue creation should return `201 Created`
if res.StatusCode != 201 {
body, _ := ioutil.ReadAll(res.Body)
retry := res.StatusCode == 500 || res.StatusCode == 503
return retry, fmt.Errorf("unexpected status %q from %v (body: %q)", res.Status, res.Request.URL.String(), string(body))
}

return false, nil
}

func tmplText(tmpl *template.Template, data *template.Data, err *error) func(string) string {
return func(name string) (s string) {
if *err != nil {
Expand Down
3 changes: 3 additions & 0 deletions template/default.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -190,3 +190,6 @@ Alerts Resolved:
{{ end }}
{{ end }}
{{ define "pushover.default.url" }}{{ template "__alertmanagerURL" . }}{{ end }}

{{ define "jira.default.summary" }}{{ template "__subject" . }}{{ end }}
{{ define "jira.default.description" }}{{ template "__text_alert_list" .Alerts.Firing }}{{ end }}
4 changes: 2 additions & 2 deletions template/internal/deftmpl/bindata.go

Large diffs are not rendered by default.

92 changes: 46 additions & 46 deletions ui/bindata.go

Large diffs are not rendered by default.