Skip to content

Commit

Permalink
jira integration (#3590)
Browse files Browse the repository at this point in the history
* Initial jira integration

Signed-off-by: Jan-Otto Kröpke <[email protected]>
Signed-off-by: Jan-Otto Kröpke <[email protected]>
Signed-off-by: Jan-Otto Kröpke <[email protected]>
Co-authored-by: Simon Pasquier <[email protected]>
Co-authored-by: Ben Kochie <[email protected]>
  • Loading branch information
3 people authored Jul 31, 2024
1 parent cad5fa5 commit a509c14
Show file tree
Hide file tree
Showing 12 changed files with 1,472 additions and 2 deletions.
4 changes: 2 additions & 2 deletions asset/assets_vfsdata.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,9 @@ func resolveFilepaths(baseDir string, cfg *Config) {
for _, cfg := range receiver.MSTeamsConfigs {
cfg.HTTPConfig.SetDirectory(baseDir)
}
for _, cfg := range receiver.JiraConfigs {
cfg.HTTPConfig.SetDirectory(baseDir)
}
}
}

Expand Down Expand Up @@ -548,6 +551,17 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
return fmt.Errorf("no msteams webhook URL or URLFile provided")
}
}
for _, jira := range rcv.JiraConfigs {
if jira.HTTPConfig == nil {
jira.HTTPConfig = c.Global.HTTPConfig
}
if jira.APIURL == nil {
if c.Global.JiraAPIURL == nil {
return fmt.Errorf("no global Jira Cloud URL set")
}
jira.APIURL = c.Global.JiraAPIURL
}
}

names[rcv.Name] = struct{}{}
}
Expand Down Expand Up @@ -752,6 +766,7 @@ type GlobalConfig struct {

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

JiraAPIURL *URL `yaml:"jira_api_url,omitempty" json:"jira_api_url,omitempty"`
SMTPFrom string `yaml:"smtp_from,omitempty" json:"smtp_from,omitempty"`
SMTPHello string `yaml:"smtp_hello,omitempty" json:"smtp_hello,omitempty"`
SMTPSmarthost HostPort `yaml:"smtp_smarthost,omitempty" json:"smtp_smarthost,omitempty"`
Expand Down Expand Up @@ -920,6 +935,7 @@ type Receiver struct {
TelegramConfigs []*TelegramConfig `yaml:"telegram_configs,omitempty" json:"telegram_configs,omitempty"`
WebexConfigs []*WebexConfig `yaml:"webex_configs,omitempty" json:"webex_configs,omitempty"`
MSTeamsConfigs []*MSTeamsConfig `yaml:"msteams_configs,omitempty" json:"msteams_configs,omitempty"`
JiraConfigs []*JiraConfig `yaml:"jira_configs,omitempty" json:"jira_configs,omitempty"`
}

// UnmarshalYAML implements the yaml.Unmarshaler interface for Receiver.
Expand Down
48 changes: 48 additions & 0 deletions config/notifiers.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"time"

commoncfg "github.com/prometheus/common/config"
"github.com/prometheus/common/model"
"github.com/prometheus/common/sigv4"
)

Expand Down Expand Up @@ -172,6 +173,15 @@ var (
Summary: `{{ template "msteams.default.summary" . }}`,
Text: `{{ template "msteams.default.text" . }}`,
}

DefaultJiraConfig = JiraConfig{
NotifierConfig: NotifierConfig{
VSendResolved: true,
},
Summary: `{{ template "jira.default.summary" . }}`,
Description: `{{ template "jira.default.description" . }}`,
Priority: `{{ template "jira.default.priority" . }}`,
}
)

// NotifierConfig contains base options common across all notifier configurations.
Expand Down Expand Up @@ -825,3 +835,41 @@ func (c *MSTeamsConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {

return nil
}

type JiraConfig struct {
NotifierConfig `yaml:",inline" json:",inline"`
HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"`

APIURL *URL `yaml:"api_url,omitempty" json:"api_url,omitempty"`

Project string `yaml:"project,omitempty" json:"project,omitempty"`
Summary string `yaml:"summary,omitempty" json:"summary,omitempty"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Labels []string `yaml:"labels,omitempty" json:"labels,omitempty"`
Priority string `yaml:"priority,omitempty" json:"priority,omitempty"`
IssueType string `yaml:"issue_type,omitempty" json:"issue_type,omitempty"`

ReopenTransition string `yaml:"reopen_transition,omitempty" json:"reopen_transition,omitempty"`
ResolveTransition string `yaml:"resolve_transition,omitempty" json:"resolve_transition,omitempty"`
WontFixResolution string `yaml:"wont_fix_resolution,omitempty" json:"wont_fix_resolution,omitempty"`
ReopenDuration model.Duration `yaml:"reopen_duration,omitempty" json:"reopen_duration,omitempty"`

Fields map[string]any `yaml:"fields,omitempty" json:"custom_fields,omitempty"`
}

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.Project == "" {
return fmt.Errorf("missing project in jira_config")
}
if c.IssueType == "" {
return fmt.Errorf("missing issue_type in jira_config")
}

return nil
}
4 changes: 4 additions & 0 deletions config/receiver/receiver.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/prometheus/alertmanager/notify"
"github.com/prometheus/alertmanager/notify/discord"
"github.com/prometheus/alertmanager/notify/email"
"github.com/prometheus/alertmanager/notify/jira"
"github.com/prometheus/alertmanager/notify/msteams"
"github.com/prometheus/alertmanager/notify/opsgenie"
"github.com/prometheus/alertmanager/notify/pagerduty"
Expand Down Expand Up @@ -92,6 +93,9 @@ func BuildReceiverIntegrations(nc config.Receiver, tmpl *template.Template, logg
for i, c := range nc.MSTeamsConfigs {
add("msteams", i, c, func(l log.Logger) (notify.Notifier, error) { return msteams.New(c, tmpl, l, httpOpts...) })
}
for i, c := range nc.JiraConfigs {
add("jira", i, c, func(l log.Logger) (notify.Notifier, error) { return jira.New(c, tmpl, l, httpOpts...) })
}

if errs.Len() > 0 {
return nil, &errs
Expand Down
96 changes: 96 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ global:
# The default TLS configuration for SMTP receivers
[ smtp_tls_config: <tls_config> ]

# Default settings for the JIRA integration.
[ jira_api_url: <string> ]

# The API URL to use for Slack notifications.
[ slack_api_url: <secret> ]
[ slack_api_url_file: <filepath> ]
Expand Down Expand Up @@ -697,6 +700,8 @@ email_configs:
[ - <email_config>, ... ]
msteams_configs:
[ - <msteams_config>, ... ]
jira_configs:
[ - <jira_config>, ... ]
opsgenie_configs:
[ - <opsgenie_config>, ... ]
pagerduty_configs:
Expand Down Expand Up @@ -944,6 +949,97 @@ Microsoft Teams notifications are sent via the [Incoming Webhooks](https://learn
[ http_config: <http_config> | default = global.http_config ]
```

### `<jira_config>`

JIRA notifications are sent via [JIRA Rest API v2](https://developer.atlassian.com/cloud/jira/platform/rest/v2/intro/)
or [JIRA REST API v3](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/#version).

Note: This integration is only tested against a Jira Cloud instance.
Jira Data Center (on premise instance) can work, but it's not guaranteed.

Both APIs have the same feature set. The difference is that V2 supports [Wiki Markup](https://jira.atlassian.com/secure/WikiRendererHelpAction.jspa?section=all)
for the issue description and V3 supports [Atlassian Document Format (ADF)](https://developer.atlassian.com/cloud/jira/platform/apis/document/structure/).
The default `jira.default.description` template only works with V2.

```yaml
# Whether to notify about resolved alerts.
[ send_resolved: <boolean> | default = true ]
# The URL to send API requests to. The full API path must be included.
# Example: https://company.atlassian.net/rest/api/2/
[ api_url: <string> | default = global.jira_api_url ]
# The project key where issues are created.
project: <string>
# Issue summary template.
[ summary: <tmpl_string> | default = '{{ template "jira.default.summary" . }}' ]
# Issue description template.
[ description: <tmpl_string> | default = '{{ template "jira.default.description" . }}' ]
# Labels to be added to the issue.
labels:
[ - <tmpl_string> ... ]
# Priority of the issue.
[ priority: <tmpl_string> | default = '{{ template "jira.default.priority" . }}' ]
# Type of the issue (e.g. Bug).
[ issue_type: <string> ]
# Name of the workflow transition to resolve an issue. The target status must have the category "done".
# NOTE: The name of the transition can be localized and depends on the language setting of the service account.
[ resolve_transition: <string> ]
# Name of the workflow transition to reopen an issue. The target status should not have the category "done".
# NOTE: The name of the transition can be localized and depends on the language setting of the service account.
[ reopen_transition: <string> ]
# If reopen_transition is defined, ignore issues with that resolution.
[ wont_fix_resolution: <string> ]
# If reopen_transition is defined, reopen the issue when it is not older than this value (rounded down to the nearest minute).
# The resolutiondate field is used to determine the age of the issue.
[ reopen_duration: <duration> ]
# Other issue and custom fields.
fields:
[ <string>: <jira_field> ... ]
# The HTTP client's configuration. You must use this configuration to supply the personal access token (PAT) as part of the HTTP `Authorization` header.
# For Jira Cloud, use basic_auth with the email address as the username and the PAT as the password.
# For Jira Data Center, use the 'authorization' field with 'credentials: <PAT value>'.
[ http_config: <http_config> | default = global.http_config ]
```
The `labels` field is a list of labels added to the issue. Template expressions are supported. For example:

```yaml
labels:
- 'alertmanager'
- '{{ .CommonLabels.severity }}'
```

#### `<jira_field>`

Jira issue field can have multiple types.
Depends on the field type, the values must be provided differently.
See https://developer.atlassian.com/server/jira/platform/jira-rest-api-examples/#setting-custom-field-data-for-other-field-types for further examples.

```yaml
fields:
# Components
components: { name: "Monitoring" }
# Custom Field TextField
customfield_10001: "Random text"
# Custom Field SelectList
customfield_10002: {"value": "red"}
# Custom Field MultiSelect
customfield_10003: [{"value": "red"}, {"value": "blue"}, {"value": "green"}]
```

### `<opsgenie_config>`

OpsGenie notifications are sent via the [OpsGenie API](https://docs.opsgenie.com/docs/alert-api).
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ require (
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749
github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546
github.com/stretchr/testify v1.9.0
github.com/trivago/tgo v1.0.7
github.com/xlab/treeprint v1.2.0
go.uber.org/atomic v1.11.0
go.uber.org/automaxprocs v1.5.3
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/trivago/tgo v1.0.7 h1:uaWH/XIy9aWYWpjm2CU3RpcqZXmX2ysQ9/Go+d9gyrM=
github.com/trivago/tgo v1.0.7/go.mod h1:w4dpD+3tzNIIiIfkWWa85w5/B77tlvdZckQ+6PkFnhc=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
Expand Down
Loading

0 comments on commit a509c14

Please sign in to comment.