Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

jira integration #3590

Merged
merged 46 commits into from
Jul 31, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
ab5c92c
Initial jira integration
jkroepke Nov 1, 2023
cf3de82
Update docs/configuration.md
jkroepke Dec 11, 2023
d236da4
Update config/notifiers.go
jkroepke Dec 11, 2023
e22d06b
Update notify/jira/jira.go
jkroepke Dec 11, 2023
bf991e0
Update docs/configuration.md
jkroepke Dec 11, 2023
6fb2bd1
Update docs/configuration.md
jkroepke Dec 11, 2023
c2ceba2
Update config/notifiers.go
jkroepke Dec 11, 2023
a0b94df
Update config/config.go
jkroepke Dec 11, 2023
3705a64
Update config/notifiers.go
jkroepke Dec 11, 2023
25b2657
Added check for jira_api_token/jira_api_token_file
jkroepke Dec 11, 2023
d65ca8b
Merge branch 'main' into jira
jkroepke Dec 11, 2023
a3975d9
make assets
jkroepke Dec 11, 2023
74138ba
use url.JoinPath
jkroepke Dec 11, 2023
ed70001
use go standard errors
jkroepke Dec 16, 2023
eb64057
Update docs/configuration.md
jkroepke Jan 5, 2024
8874392
Clarify Jira Data Center usage
jkroepke Jan 5, 2024
3aa5a4c
remove FieldsByKeys from issue search REST call
jkroepke Jan 5, 2024
9604737
Merge branch 'main' into jira
jkroepke Feb 4, 2024
6d2ceba
Merge branch 'main' into jira
jkroepke Feb 16, 2024
f945810
Merge branch 'main' into jira
jkroepke Apr 27, 2024
3229180
Merge branch 'refs/heads/main' into jira
jkroepke Apr 30, 2024
27c8b44
fix lint issues
jkroepke Apr 30, 2024
aac35bd
Merge branch 'main' into jira
jkroepke Apr 30, 2024
81d66d9
Apply suggestions from code review
jkroepke May 22, 2024
ebe6269
Merge branch 'main' into jira
jkroepke May 22, 2024
cdc96e6
remove jira specific credential options with http_config one
jkroepke May 22, 2024
b15de33
add unit tests for jira
jkroepke May 22, 2024
bd5b579
fix lint issues
jkroepke May 24, 2024
ebf5923
fixes from integration tests
jkroepke May 24, 2024
25808bf
Added default template for jira issue priority
jkroepke May 27, 2024
3e35db2
Apply suggestions from code review
jkroepke May 27, 2024
3166f69
config.Duration -> model.Duration
jkroepke May 27, 2024
495e21f
fix imports
jkroepke May 27, 2024
6e88b9d
go generate ./asset
jkroepke May 27, 2024
a7917b3
go generate ./asset
jkroepke May 27, 2024
48a904b
Merge branch 'main' into jira
jkroepke Jun 19, 2024
510ccb6
Remove `X-Force-Accept-Language` header
jkroepke Jun 19, 2024
23628b3
Make transitionIssue private
jkroepke Jun 20, 2024
ca08adb
truncate too long summary title
jkroepke Jun 25, 2024
dbda07a
Merge branch 'refs/heads/main' into jira
jkroepke Jun 25, 2024
b6df3c1
Apply suggestions from code review
jkroepke Jul 2, 2024
8bb43d0
Apply suggestions from code review
jkroepke Jul 2, 2024
e123003
Merge branch 'main' into jira
jkroepke Jul 2, 2024
0ac2ca5
pass context to api requests
jkroepke Jul 2, 2024
43c8f10
truncate description
jkroepke Jul 2, 2024
3517487
merge labels properties
jkroepke Jul 2, 2024
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
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.

35 changes: 35 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,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 @@ -539,6 +542,33 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
return fmt.Errorf("no msteams webhook URL 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
}
if !strings.HasSuffix(jira.APIURL.Path, "/") {
jira.APIURL.Path += "/"
}
jkroepke marked this conversation as resolved.
Show resolved Hide resolved
if jira.APIUsername == "" {
if c.Global.JiraAPIUsername == "" {
return fmt.Errorf("no global Jira Cloud username set")
jkroepke marked this conversation as resolved.
Show resolved Hide resolved
}
jira.APIUsername = c.Global.JiraAPIUsername
}
if jira.APIToken == "" && len(jira.APITokenFile) == 0 {
if c.Global.JiraAPIToken == "" && len(c.Global.JiraAPITokenFile) == 0 {
return fmt.Errorf("no global Jira Cloud API Token set either inline or in a file")
jkroepke marked this conversation as resolved.
Show resolved Hide resolved
}
jira.APIToken = c.Global.JiraAPIToken
jira.APITokenFile = c.Global.JiraAPITokenFile
}
}

names[rcv.Name] = struct{}{}
}
Expand Down Expand Up @@ -741,6 +771,10 @@ 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"`
JiraAPIUsername string `yaml:"jira_api_username,omitempty" json:"jira_api_username,omitempty"`
JiraAPIToken Secret `yaml:"jira_api_token,omitempty" json:"jira_api_token,omitempty"`
JiraAPITokenFile string `yaml:"jira_api_token_file,omitempty" json:"jira_api_token_file,omitempty"`
jkroepke marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -908,6 +942,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
58 changes: 58 additions & 0 deletions config/notifiers.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,14 @@ var (
Title: `{{ template "msteams.default.title" . }}`,
Text: `{{ template "msteams.default.text" . }}`,
}

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

// NotifierConfig contains base options common across all notifier configurations.
Expand Down Expand Up @@ -797,3 +805,53 @@ func (c *MSTeamsConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
type plain MSTeamsConfig
return unmarshal((*plain)(c))
}

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"`
APIUsername string `yaml:"api_username,omitempty" json:"api_username,omitempty"`
APIToken Secret `yaml:"api_token,omitempty" json:"api_token,omitempty"`
APITokenFile string `yaml:"api_token_file,omitempty" json:"api_token_file,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"`
StaticLabels []string `yaml:"static_labels,omitempty" json:"static_labels,omitempty"`
GroupLabels []string `yaml:"group_labels,omitempty" json:"group_labels,omitempty"`
Components []string `yaml:"components,omitempty" json:"components,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 duration `yaml:"reopen_duration,omitempty" json:"reopen_duration,omitempty"`

CustomFields map[string]any `yaml:"custom_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.APIToken == "" && c.APITokenFile == "" {
jkroepke marked this conversation as resolved.
Show resolved Hide resolved
return fmt.Errorf("missing api_token or api_token_file on jira_config")
jkroepke marked this conversation as resolved.
Show resolved Hide resolved
}

if c.APIToken != "" && len(c.APITokenFile) > 0 {
return fmt.Errorf("at most one of api_token & api_token_file must be configured")
}

if c.Project == "" {
return fmt.Errorf("missing project on jira_config")
jkroepke marked this conversation as resolved.
Show resolved Hide resolved
}
if c.IssueType == "" {
return fmt.Errorf("missing issue_type on jira_config")
jkroepke marked this conversation as resolved.
Show resolved Hide resolved
}

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
93 changes: 93 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ global:
# Note that Go does not support unencrypted connections to remote SMTP endpoints.
[ smtp_require_tls: <bool> | default = true ]

[ jira_api_url: <string> ]
jkroepke marked this conversation as resolved.
Show resolved Hide resolved
[ jira_api_username: <string> ]
[ jira_api_token: <secret> ]
[ jira_api_token_file: <filepath> ]
jkroepke marked this conversation as resolved.
Show resolved Hide resolved

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

### `<jira_config>`

JIRA notification are sent via [JIRA Rest API v2](https://developer.atlassian.com/cloud/jira/platform/rest/v2/intro/)
jkroepke marked this conversation as resolved.
Show resolved Hide resolved
or [JIRA REST API v3](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/#version).

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

```yaml
# Whether to notify about resolved alerts.
[ send_resolved: <boolean> | default = true ]

# The incoming webhook URL.
[ webhook_url: <secret> ]

jkroepke marked this conversation as resolved.
Show resolved Hide resolved
# The Atlassian Side to send Jira API requests to. API path must be included.
jkroepke marked this conversation as resolved.
Show resolved Hide resolved
# Example: https://company.atlassian.net/rest/api/2/
[ api_url: <string> | default = global.jira_api_url ]
[ api_username: <string> | default = global.jira_api_username ]
[ api_token: <string> | default = global.jira_api_token ]
jkroepke marked this conversation as resolved.
Show resolved Hide resolved
[ api_token_file: <string> | default = global.jira_api_token_file ]

# 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" . }}' ]

# Add labels to issues
jkroepke marked this conversation as resolved.
Show resolved Hide resolved
static_labels:
[ - <string> ... ]

# Add specific group labels to issue
jkroepke marked this conversation as resolved.
Show resolved Hide resolved
group_labels:
[ - <string> ... ]
jkroepke marked this conversation as resolved.
Show resolved Hide resolved

# JIRA components
components:
[ - <string> ... ]

# Priority of issue
jkroepke marked this conversation as resolved.
Show resolved Hide resolved
[ priority: <tmpl_string> ]
jkroepke marked this conversation as resolved.
Show resolved Hide resolved

# Type of issue, e.g. Bug
jkroepke marked this conversation as resolved.
Show resolved Hide resolved
[ issue_type: <string> ]

# Name of the workflow transition to resolve an issue. The target status must have the category "done"
[ resolve_transition: <string> ]

# Name of the workflow transition to reopen an issue. The target status should not have the category "done"
[ reopen_transition: <string> ]

# If reopen_transition is defined, ignore issues with that resolution
jkroepke marked this conversation as resolved.
Show resolved Hide resolved
[ wont_fix_resolution: <string> ]

# If reopen_transition is defined, reopen issue not older than ...
[ reopen_duration: <duration> ]

# Custom fields
custom_fields:
[ <string>: <custom_fields> ... ]


# The HTTP client's configuration.
[ http_config: <http_config> | default = global.http_config ]
```

#### `<custom_fields>`

Jira custom field can have multiple types. Depends on the filed 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:
# TextField
customfield_10001: "Random text"
# SelectList
customfield_10002: {"value": "red"}
# 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
Loading
Loading