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 38 commits
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.

16 changes: 16 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,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 @@ -540,6 +543,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 @@ -742,6 +756,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 @@ -909,6 +924,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
49 changes: 49 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,42 @@ 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"`
StaticLabels []string `yaml:"static_labels,omitempty" json:"static_labels,omitempty"`
GroupLabels []string `yaml:"group_labels,omitempty" json:"group_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
90 changes: 90 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ global:
# Note that Go does not support unencrypted connections to remote SMTP endpoints.
[ smtp_require_tls: <bool> | default = true ]

# Default settings for the JIRA integration.
[ jira_api_url: <string> ]
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 @@ -695,6 +698,8 @@ email_configs:
[ - <email_config>, ... ]
msteams_configs:
[ - <msteams_config>, ... ]
jira_configs:
[ - <jira_config>, ... ]
opsgenie_configs:
[ - <opsgenie_config>, ... ]
pagerduty_configs:
Expand Down Expand Up @@ -942,6 +947,91 @@ 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 an 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" . }}' ]

# 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

# Priority of issue
jkroepke marked this conversation as resolved.
Show resolved Hide resolved
[ priority: <tmpl_string> | default = '{{ template "jira.default.priority" . }}' ]

# 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".
# 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
jkroepke marked this conversation as resolved.
Show resolved Hide resolved
[ wont_fix_resolution: <string> ]

# If reopen_transition is defined, reopen issue not older than ... Note: The resolutiondate field is used to determine the age of the issue.
jkroepke marked this conversation as resolved.
Show resolved Hide resolved
[ reopen_duration: <duration> ]

# Other issue and custom fields
jkroepke marked this conversation as resolved.
Show resolved Hide resolved
fields:
SuperQ marked this conversation as resolved.
Show resolved Hide resolved
[ <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 ]
```

#### `<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 @@ -37,6 +37,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
Copy link
Contributor Author

Choose a reason for hiding this comment

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

A new dependency is mandatory for the map[any]any cast, because encoding/json does not support that type

Copy link
Member

Choose a reason for hiding this comment

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

Not terribly excited to depend on a project that isn't very active (e.g. it doesn't have a go.mod). While I understand that it is stable and proven, I'll look for other options.

Can you also provide an example of something that breaks encoding/json?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

custom_fields:
  customfield_10010: { value: "Monitoring" }

Alertmanager parses the nested map as map[interface{}]interface{} and encoding/json does not support this kind of map. The library ensures that each nested map is casted to map[string]interface{}

Copy link
Contributor Author

@jkroepke jkroepke May 27, 2024

Choose a reason for hiding this comment

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

I'll look for other options.

https://github.com/json-iterator/go natively supports encoding of map[interface{}]interface{}, but I feel there would much less acceptance for a different JSON library.


If you try to go with own code, I have prepare a test file for you:

https://github.com/jkroepke/homelab/blob/main/go/map_cast/main_test.go

I also figured out that gopkg.in/yaml.v2 provides nested maps as map[any]any while gopkg.in/yaml.v3 would use map[string]any, eben numbers are used as YAML key.

Not 100% sure about this, but I guess, the issue could be also resolved, if gopkg.in/yaml.v3 would used to read the config file.

Copy link
Member

Choose a reason for hiding this comment

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

json-iterator is already an indirect dependency via prometheus/client_golang so I'd say that it's ok to use it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have no clue, why json-iterator works fine on the lined go test above, but while integrate it inside AM, tests in AM are failed with

error marshalling request body: jira.issue.Fields: json: unsupported type: map[interface {}]interface {}

I may give up here, since I don't know why it works on a synthetic environment, but not on AM.

For reference, here is my code (cf85bbe)

github.com/xlab/treeprint v1.2.0
go.uber.org/atomic v1.11.0
golang.org/x/mod v0.18.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,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
Loading