From caa3e5af8eff5c9bacc81a32744fbc7372bf43e6 Mon Sep 17 00:00:00 2001 From: Alik Khilazhev Date: Wed, 30 Nov 2022 14:57:48 +0700 Subject: [PATCH 1/5] feat: add reopen_enabled flag - fixes #120 Signed-off-by: Alik Khilazhev --- pkg/config/config.go | 11 +++++++++++ pkg/notify/notify.go | 9 ++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/pkg/config/config.go b/pkg/config/config.go index f72809c..b15fd59 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -135,6 +135,7 @@ type ReceiverConfig struct { Project string `yaml:"project" json:"project"` IssueType string `yaml:"issue_type" json:"issue_type"` Summary string `yaml:"summary" json:"summary"` + ReopenEnabled *bool `yaml:"reopen_enabled" json:"reopen_enabled"` ReopenState string `yaml:"reopen_state" json:"reopen_state"` ReopenDuration *Duration `yaml:"reopen_duration" json:"reopen_duration"` @@ -212,6 +213,11 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error { } } + if c.Defaults.ReopenEnabled == nil { + var defaultReopenEnabled = false + c.Defaults.ReopenEnabled = &defaultReopenEnabled + } + for _, rc := range c.Receivers { if rc.Name == "" { return fmt.Errorf("missing name for receiver %+v", rc) @@ -269,6 +275,11 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error { } rc.Summary = c.Defaults.Summary } + + if rc.ReopenEnabled == nil { + rc.ReopenEnabled = c.Defaults.ReopenEnabled + } + if rc.ReopenState == "" { if c.Defaults.ReopenState == "" { return fmt.Errorf("missing reopen_state in receiver %q", rc.Name) diff --git a/pkg/notify/notify.go b/pkg/notify/notify.go index 00a2d9e..0d1f733 100644 --- a/pkg/notify/notify.go +++ b/pkg/notify/notify.go @@ -17,12 +17,13 @@ import ( "bytes" "crypto/sha512" "fmt" - "github.com/andygrunwald/go-jira" "io" "reflect" "strings" "time" + "github.com/andygrunwald/go-jira" + "github.com/go-kit/log" "github.com/go-kit/log/level" "github.com/pkg/errors" @@ -299,6 +300,12 @@ func (r *Receiver) search(project, issueLabel string) (*jira.Issue, bool, error) } func (r *Receiver) findIssueToReuse(project string, issueGroupLabel string) (*jira.Issue, bool, error) { + + if !*r.conf.ReopenEnabled { + level.Debug(r.logger).Log("msg", "reopening disabled, skipping search for existing issue") + return nil, false, nil + } + issue, retry, err := r.search(project, issueGroupLabel) if err != nil { return nil, retry, err From f1b391fc716182749d3578ee9b056f123b22d362 Mon Sep 17 00:00:00 2001 From: Alik Khilazhev Date: Wed, 30 Nov 2022 15:40:10 +0700 Subject: [PATCH 2/5] chore: update docs and example Signed-off-by: Alik Khilazhev --- README.md | 10 +++++----- examples/jiralert.yml | 4 +++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index d1cc468..fbd401b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # JIRAlert -[![Build Status](https://github.com/prometheus-community/jiralert/workflows/test/badge.svg?branch=master)](https://github.com/prometheus-community/jiralert/actions?query=workflow%3Atest) -[![Go Report Card](https://goreportcard.com/badge/github.com/prometheus-community/jiralert)](https://goreportcard.com/report/github.com/prometheus-community/jiralert) +[![Build Status](https://github.com/prometheus-community/jiralert/workflows/test/badge.svg?branch=master)](https://github.com/prometheus-community/jiralert/actions?query=workflow%3Atest) +[![Go Report Card](https://goreportcard.com/badge/github.com/prometheus-community/jiralert)](https://goreportcard.com/report/github.com/prometheus-community/jiralert) [![GoDoc](https://godoc.org/github.com/prometheus-community/jiralert?status.svg)](https://godoc.org/github.com/prometheus-community/jiralert) [![Slack](https://img.shields.io/badge/join%20slack-%23jiralert-brightgreen.svg)](https://slack.cncf.io/) [Prometheus Alertmanager](https://github.com/prometheus/alertmanager) webhook receiver for [JIRA](https://www.atlassian.com/software/jira). @@ -9,7 +9,7 @@ JIRAlert implements Alertmanager's webhook HTTP API and connects to one or more JIRA instances to create highly configurable JIRA issues. One issue is created per distinct group key — as defined by the [`group_by`](https://prometheus.io/docs/alerting/configuration/#) parameter of Alertmanager's `route` configuration section — but not closed when the alert is resolved. The expectation is that a human will look at the issue, take any necessary action, then close it. If no human interaction is necessary then it should probably not alert in the first place. This behavior however can be modified by setting `auto_resolve` section, which will resolve the jira issue with required state. -If a corresponding JIRA issue already exists but is resolved, it is reopened. A JIRA transition must exist between the resolved state and the reopened state — as defined by `reopen_state` — or reopening will fail. Optionally a "won't fix" resolution — defined by `wont_fix_resolution` — may be defined: a JIRA issue with this resolution will not be reopened by JIRAlert. +If a corresponding JIRA issue already exists but is resolved, it is reopened. A JIRA transition must exist between the resolved state and the reopened state — as defined by `reopen_state` — or reopening will fail. Optionally a "won't fix" resolution — defined by `wont_fix_resolution` — may be defined: a JIRA issue with this resolution will not be reopened by JIRAlert. This feature could be disable by setting to `false` `reopen_enabled` option. ## Usage @@ -57,7 +57,7 @@ Each receiver must have a unique name (matching the Alertmanager receiver name), ## Alertmanager configuration -To enable Alertmanager to talk to JIRAlert you need to configure a webhook in Alertmanager. You can do that by adding a webhook receiver to your Alertmanager configuration. +To enable Alertmanager to talk to JIRAlert you need to configure a webhook in Alertmanager. You can do that by adding a webhook receiver to your Alertmanager configuration. ```yaml receivers: @@ -84,7 +84,7 @@ env DEBUG=1 ./jiralert ## Community -*Jiralert* is an open source project and we welcome new contributors and members +*Jiralert* is an open source project and we welcome new contributors and members of the community. Here are ways to get in touch with the community: * Issue Tracker: [GitHub Issues](https://github.com/prometheus-community/jiralert/issues) diff --git a/examples/jiralert.yml b/examples/jiralert.yml index 568437a..37126dd 100644 --- a/examples/jiralert.yml +++ b/examples/jiralert.yml @@ -14,6 +14,8 @@ defaults: summary: '{{ template "jira.summary" . }}' # Go template invocation for generating the description. Optional. description: '{{ template "jira.description" . }}' + # Enables reopening existing issues. Optional. Default is true. + reopen_enabled: true # State to transition into when reopening a closed issue. Required. reopen_state: "To Do" # Do not reopen issues with this resolution. Optional. @@ -50,7 +52,7 @@ receivers: # # Automatically resolve jira issues when alert is resolved. Optional. If declared, ensure state is not an empty string. auto_resolve: - state: 'Done' + state: 'Done' # File containing template definitions. Required. template: jiralert.tmpl From 0241111846592e4ac03139458f645e37200cec80 Mon Sep 17 00:00:00 2001 From: Alik Khilazhev Date: Wed, 30 Nov 2022 15:48:02 +0700 Subject: [PATCH 3/5] fix: nil pointer deref in tests Signed-off-by: Alik Khilazhev --- pkg/notify/notify.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/notify/notify.go b/pkg/notify/notify.go index 0d1f733..22aa294 100644 --- a/pkg/notify/notify.go +++ b/pkg/notify/notify.go @@ -301,7 +301,7 @@ func (r *Receiver) search(project, issueLabel string) (*jira.Issue, bool, error) func (r *Receiver) findIssueToReuse(project string, issueGroupLabel string) (*jira.Issue, bool, error) { - if !*r.conf.ReopenEnabled { + if r.conf.ReopenEnabled != nil && !*r.conf.ReopenEnabled { level.Debug(r.logger).Log("msg", "reopening disabled, skipping search for existing issue") return nil, false, nil } From 0cd3fc852524c5cb4a058fc14c7610ad2c4a9121 Mon Sep 17 00:00:00 2001 From: Alik Khilazhev Date: Wed, 30 Nov 2022 17:29:53 +0700 Subject: [PATCH 4/5] fix: issue resolution Signed-off-by: Alik Khilazhev --- pkg/notify/notify.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/pkg/notify/notify.go b/pkg/notify/notify.go index 22aa294..65fe93b 100644 --- a/pkg/notify/notify.go +++ b/pkg/notify/notify.go @@ -128,8 +128,12 @@ func (r *Receiver) Notify(data *alertmanager.Data, hashJiraLabel bool) (bool, er return false, nil } - level.Info(r.logger).Log("msg", "issue was recently resolved, reopening", "key", issue.Key, "label", issueGroupLabel) - return r.reopen(issue.Key) + if r.conf.ReopenEnabled != nil && !*r.conf.ReopenEnabled { + level.Debug(r.logger).Log("msg", "reopening disabled, skipping search for existing issue") + } else { + level.Info(r.logger).Log("msg", "issue was recently resolved, reopening", "key", issue.Key, "label", issueGroupLabel) + return r.reopen(issue.Key) + } } if len(data.Alerts.Firing()) == 0 { @@ -301,11 +305,6 @@ func (r *Receiver) search(project, issueLabel string) (*jira.Issue, bool, error) func (r *Receiver) findIssueToReuse(project string, issueGroupLabel string) (*jira.Issue, bool, error) { - if r.conf.ReopenEnabled != nil && !*r.conf.ReopenEnabled { - level.Debug(r.logger).Log("msg", "reopening disabled, skipping search for existing issue") - return nil, false, nil - } - issue, retry, err := r.search(project, issueGroupLabel) if err != nil { return nil, retry, err From 2201daf992a7421a87449483f56bc9f45592610f Mon Sep 17 00:00:00 2001 From: Rustem Sagdeev <50661238+SagdeevRR@users.noreply.github.com> Date: Mon, 18 Dec 2023 18:05:44 +0300 Subject: [PATCH 5/5] feat: add exclude label param (#1) * feat: add exclude label param * chore: add examples * chore: rename param --- examples/jiralert.yml | 8 ++++++++ pkg/config/config.go | 8 ++++++++ pkg/notify/notify.go | 7 ++++--- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/examples/jiralert.yml b/examples/jiralert.yml index 37126dd..a6713de 100644 --- a/examples/jiralert.yml +++ b/examples/jiralert.yml @@ -6,6 +6,9 @@ defaults: user: jiralert password: 'JIRAlert' + # Exclude labels in JIRA issue. + exclude_keys: [] + # The type of JIRA issue to create. Required. issue_type: Bug # Issue priority. Optional. @@ -32,6 +35,8 @@ receivers: project: AB # Copy all Prometheus labels into separate JIRA labels. Optional (default: false). add_group_labels: false + exclude_keys: + - pod - name: 'jira-xy' project: XY @@ -42,6 +47,9 @@ receivers: # Standard or custom field values to set on created issue. Optional. # # See https://developer.atlassian.com/server/jira/platform/jira-rest-api-examples/#setting-custom-field-data-for-other-field-types for further examples. + exclude_keys: + - pod + - job fields: # TextField customfield_10001: "Random text" diff --git a/pkg/config/config.go b/pkg/config/config.go index b15fd59..fc0adac 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -146,6 +146,9 @@ type ReceiverConfig struct { Fields map[string]interface{} `yaml:"fields" json:"fields"` Components []string `yaml:"components" json:"components"` + // ExcludeKeys settings + ExcludeKeys []string `yaml:"exclude_keys" json:"exclude_keys"` + // Label copy settings AddGroupLabels bool `yaml:"add_group_labels" json:"add_group_labels"` @@ -256,6 +259,11 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error { } } + // Check ExcludeKeys + if len(rc.ExcludeKeys) == 0 { + rc.ExcludeKeys = c.Defaults.ExcludeKeys + } + // Check required issue fields. if rc.Project == "" { if c.Defaults.Project == "" { diff --git a/pkg/notify/notify.go b/pkg/notify/notify.go index 65fe93b..94c3fbe 100644 --- a/pkg/notify/notify.go +++ b/pkg/notify/notify.go @@ -67,7 +67,8 @@ func (r *Receiver) Notify(data *alertmanager.Data, hashJiraLabel bool) (bool, er return false, errors.Wrap(err, "generate project from template") } - issueGroupLabel := toGroupTicketLabel(data.GroupLabels, hashJiraLabel) + excludeKeys := r.conf.ExcludeKeys + issueGroupLabel := toGroupTicketLabel(data.GroupLabels, hashJiraLabel, excludeKeys) issue, retry, err := r.findIssueToReuse(project, issueGroupLabel) if err != nil { @@ -253,7 +254,7 @@ func deepCopyWithTemplate(value interface{}, tmpl *template.Template, data inter // hashing ensures that JIRA validation still accepts the output even // if the combined length of all groupLabel key-value pairs would be // longer than 255 chars -func toGroupTicketLabel(groupLabels alertmanager.KV, hashJiraLabel bool) string { +func toGroupTicketLabel(groupLabels alertmanager.KV, hashJiraLabel bool, excludeKeys []string) string { // new opt in behavior if hashJiraLabel { hash := sha512.New() @@ -266,7 +267,7 @@ func toGroupTicketLabel(groupLabels alertmanager.KV, hashJiraLabel bool) string // old default behavior buf := bytes.NewBufferString("ALERT{") - for _, p := range groupLabels.SortedPairs() { + for _, p := range groupLabels.Remove(excludeKeys).SortedPairs() { buf.WriteString(p.Name) buf.WriteString(fmt.Sprintf("=%q,", p.Value)) }