diff --git a/cmd/jiralert/main.go b/cmd/jiralert/main.go index 6f8ddec..5a2e2de 100644 --- a/cmd/jiralert/main.go +++ b/cmd/jiralert/main.go @@ -49,9 +49,6 @@ var ( logFormat = flag.String("log.format", logFormatLogfmt, "Log format to use ("+logFormatLogfmt+", "+logFormatJSON+")") hashJiraLabel = flag.Bool("hash-jira-label", false, "if enabled: renames ALERT{...} to JIRALERT{...}; also hashes the key-value pairs inside of JIRALERT{...} in the created jira issue labels"+ "- this ensures that the label text does not overflow the allowed length in jira (255)") - updateSummary = flag.Bool("update-summary", true, "When false, jiralert does not update the summary of the existing jira issue, even when changes are spotted.") - updateDescription = flag.Bool("update-description", true, "When false, jiralert does not update the description of the existing jira issue, even when changes are spotted.") - reopenTickets = flag.Bool("reopen-tickets", true, "When false, jiralert does not reopen tickets.") maxDescriptionLength = flag.Int("max-description-length", defaultMaxDescriptionLength, "Maximum length of Descriptions. Truncate to this size avoid server errors.") // Version is the build version, set by make to latest git tag/hash via `-ldflags "-X main.Version=$(VERSION)"`. @@ -126,7 +123,7 @@ func main() { return } - if retry, err := notify.NewReceiver(logger, conf, tmpl, client.Issue).Notify(&data, *hashJiraLabel, *updateSummary, *updateDescription, *reopenTickets, *maxDescriptionLength); err != nil { + if retry, err := notify.NewReceiver(logger, conf, tmpl, client.Issue).Notify(&data, *hashJiraLabel, *maxDescriptionLength); err != nil { var status int if retry { // Instruct Alertmanager to retry. diff --git a/examples/jiralert.yml b/examples/jiralert.yml index 46263c3..b1c6801 100644 --- a/examples/jiralert.yml +++ b/examples/jiralert.yml @@ -33,6 +33,9 @@ defaults: other_projects: ["OTHER1", "OTHER2"] # Include ticket update as comment. Optional (default: false). update_in_comment: false + # Enable or disable automatic updates to the summary and description fields. Set to False to prevent updates. This setting can also be configured at the receiver level to override the global default. + update_summary: True + update_description: True # Receiver definitions. At least one must be defined. receivers: diff --git a/pkg/config/config.go b/pkg/config/config.go index 7fc63c4..a1a8638 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -153,6 +153,15 @@ type ReceiverConfig struct { // Flag to enable updates in comments. UpdateInComment *bool `yaml:"update_in_comment" json:"update_in_comment"` + // Flag to enable updates in summary. + UpdateSummary *bool `yaml:"update_summary" json:"update_summary"` + + // Flag to enable updates in description. + UpdateDescription *bool `yaml:"update_description" json:"update_description"` + + // Flag to enable reopen tickets. + ReopenTickets *bool `yaml:"reopen_tickets" json:"reopen_tickets"` + // Flag to auto-resolve opened issue when the alert is resolved. AutoResolve *AutoResolve `yaml:"auto_resolve" json:"auto_resolve"` @@ -324,6 +333,12 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error { if rc.UpdateInComment == nil { rc.UpdateInComment = c.Defaults.UpdateInComment } + if rc.UpdateSummary == nil { + rc.UpdateSummary = c.Defaults.UpdateSummary + } + if rc.UpdateDescription == nil { + rc.UpdateDescription = c.Defaults.UpdateDescription + } } if len(c.Receivers) == 0 { diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index e0bd0c9..6ad9a78 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -48,6 +48,9 @@ defaults: reopen_duration: 0h update_in_comment: true static_labels: ["defaultlabel"] + update_summary: true + update_description: true + reopen_tickets: true # Receiver definitions. At least one must be defined. receivers: @@ -58,6 +61,9 @@ receivers: # Copy all Prometheus labels into separate JIRA labels. Optional (default: false). add_group_labels: false update_in_comment: false + update_summary: false + update_description: true + reopen_tickets: false static_labels: ["somelabel"] - name: 'jira-xy' @@ -132,6 +138,9 @@ type receiverTestConfig struct { AddGroupLabels *bool `yaml:"add_group_labels,omitempty"` UpdateInComment *bool `yaml:"update_in_comment,omitempty"` StaticLabels []string `yaml:"static_labels" json:"static_labels"` + UpdateSummary *bool `yaml:"update_summary" json:"update_summary"` + UpdateDescription *bool `yaml:"update_description" json:"update_description"` + ReopenTickets *bool `yaml:"reopen_tickets" json:"reopen_tickets"` AutoResolve *AutoResolve `yaml:"auto_resolve" json:"auto_resolve"` @@ -320,6 +329,8 @@ func TestReceiverOverrides(t *testing.T) { addGroupLabelsFalseVal := false updateInCommentTrueVal := true updateInCommentFalseVal := false + updateSummaryFalseVal := false + updateDescriptionFalseVal := false // We'll override one key at a time and check the value in the receiver. for _, test := range []struct { @@ -340,6 +351,8 @@ func TestReceiverOverrides(t *testing.T) { {"AddGroupLabels", &addGroupLabelsTrueVal, &addGroupLabelsTrueVal}, {"UpdateInComment", &updateInCommentFalseVal, &updateInCommentFalseVal}, {"UpdateInComment", &updateInCommentTrueVal, &updateInCommentTrueVal}, + {"UpdateSummary", &updateSummaryFalseVal, &updateSummaryFalseVal}, + {"UpdateDescription", &updateDescriptionFalseVal, &updateDescriptionFalseVal}, {"AutoResolve", &AutoResolve{State: "Done"}, &autoResolve}, {"StaticLabels", []string{"somelabel"}, []string{"somelabel"}}, } { @@ -379,6 +392,8 @@ func newReceiverTestConfig(mandatory []string, optional []string) *receiverTestC r := receiverTestConfig{} addGroupLabelsDefaultVal := true updateInCommentDefaultVal := true + updateSummaryDefaultVal := true + updateDescriptionDefaultVal := true for _, name := range mandatory { var value reflect.Value @@ -399,6 +414,10 @@ func newReceiverTestConfig(mandatory []string, optional []string) *receiverTestC value = reflect.ValueOf(&addGroupLabelsDefaultVal) } else if name == "UpdateInComment" { value = reflect.ValueOf(&updateInCommentDefaultVal) + } else if name == "UpdateSummary" { + value = reflect.ValueOf(&updateSummaryDefaultVal) + } else if name == "UpdateDescription" { + value = reflect.ValueOf(&updateDescriptionDefaultVal) } else if name == "AutoResolve" { value = reflect.ValueOf(&AutoResolve{State: "Done"}) } else if name == "StaticLabels" { diff --git a/pkg/notify/notify.go b/pkg/notify/notify.go index e4b96a2..6035c20 100644 --- a/pkg/notify/notify.go +++ b/pkg/notify/notify.go @@ -61,7 +61,7 @@ func NewReceiver(logger log.Logger, c *config.ReceiverConfig, t *template.Templa } // Notify manages JIRA issues based on alertmanager webhook notify message. -func (r *Receiver) Notify(data *alertmanager.Data, hashJiraLabel bool, updateSummary bool, updateDescription bool, reopenTickets bool, maxDescriptionLength int) (bool, error) { +func (r *Receiver) Notify(data *alertmanager.Data, hashJiraLabel bool, maxDescriptionLength int) (bool, error) { project, err := r.tmpl.Execute(r.conf.Project, data) if err != nil { return false, errors.Wrap(err, "generate project from template") @@ -94,7 +94,7 @@ func (r *Receiver) Notify(data *alertmanager.Data, hashJiraLabel bool, updateSum if issue != nil { // Update summary if needed. - if updateSummary { + if r.conf.UpdateSummary != nil && *r.conf.UpdateSummary { if issue.Fields.Summary != issueSummary { level.Debug(r.logger).Log("updateSummaryDisabled executing") retry, err := r.updateSummary(issue.Key, issueSummary) @@ -125,8 +125,8 @@ func (r *Receiver) Notify(data *alertmanager.Data, hashJiraLabel bool, updateSum } } - // update description if enabled. This has to be done after comment adding logic which needs to handle redundant commentary vs description case. - if updateDescription { + // update description after possibly adding a comment so that it's possible to detect redundant first comment + if r.conf.UpdateDescription != nil && *r.conf.UpdateDescription { if issue.Fields.Description != issueDesc { retry, err := r.updateDescription(issue.Key, issueDesc) if err != nil { @@ -155,7 +155,7 @@ func (r *Receiver) Notify(data *alertmanager.Data, hashJiraLabel bool, updateSum return false, nil } - if reopenTickets { + if r.conf.ReopenTickets != nil && *r.conf.ReopenTickets { if r.conf.WontFixResolution != "" && issue.Fields.Resolution != nil && issue.Fields.Resolution.Name == r.conf.WontFixResolution { level.Info(r.logger).Log("msg", "issue was resolved as won't fix, not reopening", "key", issue.Key, "label", issueGroupLabel, "resolution", issue.Fields.Resolution.Name) diff --git a/pkg/notify/notify_test.go b/pkg/notify/notify_test.go index 49d686f..f9e4221 100644 --- a/pkg/notify/notify_test.go +++ b/pkg/notify/notify_test.go @@ -162,23 +162,35 @@ func (f *fakeJira) DoTransition(ticketID, transitionID string) (*jira.Response, func testReceiverConfig1() *config.ReceiverConfig { reopen := config.Duration(1 * time.Hour) + updateSummary := true + updateDescription := true + reopenTickets := true return &config.ReceiverConfig{ Project: "abc", Summary: `[{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .GroupLabels.SortedPairs.Values | join " " }} {{ if gt (len .CommonLabels) (len .GroupLabels) }}({{ with .CommonLabels.Remove .GroupLabels.Names }}{{ .Values | join " " }}{{ end }}){{ end }}`, ReopenDuration: &reopen, ReopenState: "reopened", + ReopenTickets: &reopenTickets, + UpdateSummary: &updateSummary, + UpdateDescription: &updateDescription, WontFixResolution: "won't-fix", } } func testReceiverConfig2() *config.ReceiverConfig { reopen := config.Duration(1 * time.Hour) + updateSummary := true + updateDescription := true + reopenTickets := true return &config.ReceiverConfig{ Project: "abc", Summary: `[{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .GroupLabels.SortedPairs.Values | join " " }} {{ if gt (len .CommonLabels) (len .GroupLabels) }}({{ with .CommonLabels.Remove .GroupLabels.Names }}{{ .Values | join " " }}{{ end }}){{ end }}`, ReopenDuration: &reopen, ReopenState: "reopened", + ReopenTickets: &reopenTickets, Description: `{{ .Alerts.Firing | len }}`, + UpdateSummary: &updateSummary, + UpdateDescription: &updateDescription, WontFixResolution: "won't-fix", } } @@ -186,6 +198,8 @@ func testReceiverConfig2() *config.ReceiverConfig { func testReceiverConfigAddComments() *config.ReceiverConfig { reopen := config.Duration(1 * time.Hour) updateInCommentValue := true + updateSummary := true + updateDescription := true return &config.ReceiverConfig{ Project: "abc", Summary: `[{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .GroupLabels.SortedPairs.Values | join " " }} {{ if gt (len .CommonLabels) (len .GroupLabels) }}({{ with .CommonLabels.Remove .GroupLabels.Names }}{{ .Values | join " " }}{{ end }}){{ end }}`, @@ -194,17 +208,25 @@ func testReceiverConfigAddComments() *config.ReceiverConfig { Description: `{{ .Alerts.Firing | len }}`, WontFixResolution: "won't-fix", UpdateInComment: &updateInCommentValue, + UpdateSummary: &updateSummary, + UpdateDescription: &updateDescription, } } func testReceiverConfigAutoResolve() *config.ReceiverConfig { reopen := config.Duration(1 * time.Hour) + updateSummary := true + updateDescription := true + reopenTickets := true autoResolve := config.AutoResolve{State: "Done"} return &config.ReceiverConfig{ Project: "abc", Summary: `[{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .GroupLabels.SortedPairs.Values | join " " }} {{ if gt (len .CommonLabels) (len .GroupLabels) }}({{ with .CommonLabels.Remove .GroupLabels.Names }}{{ .Values | join " " }}{{ end }}){{ end }}`, ReopenDuration: &reopen, ReopenState: "reopened", + ReopenTickets: &reopenTickets, + UpdateSummary: &updateSummary, + UpdateDescription: &updateDescription, WontFixResolution: "won't-fix", AutoResolve: &autoResolve, } @@ -701,7 +723,7 @@ func TestNotify_JIRAInteraction(t *testing.T) { return testNowTime } - _, err := receiver.Notify(tcase.inputAlert, true, true, true, true, 32768) + _, err := receiver.Notify(tcase.inputAlert, true, 32768) require.NoError(t, err) require.Equal(t, tcase.expectedJiraIssues, fakeJira.issuesByKey) }); !ok {