From 62e37aa96c690e0a37014a2f1fd9838ab23bef83 Mon Sep 17 00:00:00 2001 From: Jason Wells Date: Sun, 7 May 2023 08:34:07 -0700 Subject: [PATCH 1/5] add ability to update in comments (#160) Signed-off-by: Jason Wells --- cmd/jiralert/main.go | 3 ++- pkg/notify/notify.go | 25 ++++++++++++++++++++++++- pkg/notify/notify_test.go | 7 ++++++- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/cmd/jiralert/main.go b/cmd/jiralert/main.go index 86be130..40046bc 100644 --- a/cmd/jiralert/main.go +++ b/cmd/jiralert/main.go @@ -50,6 +50,7 @@ var ( "- 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.") + updateInComment = flag.Bool("update-in-comment", true, "When true, jiralert adds a comment with an update of the existing jira issue.") reopenTickets = flag.Bool("reopen-tickets", true, "When false, jiralert does not reopen tickets.") // Version is the build version, set by make to latest git tag/hash via `-ldflags "-X main.Version=$(VERSION)"`. @@ -124,7 +125,7 @@ func main() { return } - if retry, err := notify.NewReceiver(logger, conf, tmpl, client.Issue).Notify(&data, *hashJiraLabel, *updateSummary, *updateDescription, *reopenTickets); err != nil { + if retry, err := notify.NewReceiver(logger, conf, tmpl, client.Issue).Notify(&data, *hashJiraLabel, *updateSummary, *updateDescription, *updateInComment, *reopenTickets); err != nil { var status int if retry { // Instruct Alertmanager to retry. diff --git a/pkg/notify/notify.go b/pkg/notify/notify.go index ffcc6d8..3faf8c7 100644 --- a/pkg/notify/notify.go +++ b/pkg/notify/notify.go @@ -40,6 +40,7 @@ type jiraIssueService interface { Create(issue *jira.Issue) (*jira.Issue, *jira.Response, error) UpdateWithOptions(issue *jira.Issue, opts *jira.UpdateQueryOptions) (*jira.Issue, *jira.Response, error) + AddComment(issueID string, comment *jira.Comment) (*jira.Comment, *jira.Response, error) DoTransition(ticketID, transitionID string) (*jira.Response, error) } @@ -60,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) (bool, error) { +func (r *Receiver) Notify(data *alertmanager.Data, hashJiraLabel bool, updateSummary bool, updateDescription bool, updateInComment bool, reopenTickets bool) (bool, error) { project, err := r.tmpl.Execute(r.conf.Project, data) if err != nil { return false, errors.Wrap(err, "generate project from template") @@ -107,6 +108,13 @@ func (r *Receiver) Notify(data *alertmanager.Data, hashJiraLabel bool, updateSum } } + if updateInComment { + retry, err := r.addComment(issue.Key, issueDesc) + if err != nil { + return retry, err + } + } + if len(data.Alerts.Firing()) == 0 { if r.conf.AutoResolve != nil { level.Debug(r.logger).Log("msg", "no firing alert; resolving issue", "key", issue.Key, "label", issueGroupLabel) @@ -365,6 +373,21 @@ func (r *Receiver) updateDescription(issueKey string, description string) (bool, return false, nil } +func (r *Receiver) addComment(issueKey string, content string) (bool, error) { + level.Debug(r.logger).Log("msg", "adding comment to existing issue", "key", issueKey, "content", content) + + commentDetails := &jira.Comment{ + Body: content, + } + + comment, resp, err := r.client.AddComment(issueKey, commentDetails) + if err != nil { + return handleJiraErrResponse("Issue.AddComment", resp, err, r.logger) + } + level.Debug(r.logger).Log("msg", "added comment to issue", "key", issueKey, "id", comment.ID) + return false, nil +} + func (r *Receiver) reopen(issueKey string) (bool, error) { return r.doTransition(issueKey, r.conf.ReopenState) } diff --git a/pkg/notify/notify_test.go b/pkg/notify/notify_test.go index 8e73f4a..2c057a9 100644 --- a/pkg/notify/notify_test.go +++ b/pkg/notify/notify_test.go @@ -134,6 +134,11 @@ func (f *fakeJira) UpdateWithOptions(old *jira.Issue, _ *jira.UpdateQueryOptions return issue, nil, nil } +func (f *fakeJira) AddComment(issueID string, comment *jira.Comment) (*jira.Comment, *jira.Response, error) { + // This is a placeholder to keep fakeJira compatible with the updated Interface + return nil, nil, nil +} + func (f *fakeJira) DoTransition(ticketID, transitionID string) (*jira.Response, error) { issue, ok := f.issuesByKey[ticketID] if !ok { @@ -592,7 +597,7 @@ func TestNotify_JIRAInteraction(t *testing.T) { return testNowTime } - _, err := receiver.Notify(tcase.inputAlert, true, true, true, true) + _, err := receiver.Notify(tcase.inputAlert, true, true, true, false, true) require.NoError(t, err) require.Equal(t, tcase.expectedJiraIssues, fakeJira.issuesByKey) }); !ok { From f1d9a3578abe17dfb303f0ca25e92343b8cf6783 Mon Sep 17 00:00:00 2001 From: Ricardo Melo Date: Fri, 23 Jun 2023 09:26:47 -0400 Subject: [PATCH 2/5] Moving update in comment option from process argumento into config file. Signed-off-by: Jason Wells --- cmd/jiralert/main.go | 3 +-- examples/jiralert.yml | 2 ++ pkg/config/config.go | 6 ++++++ pkg/config/config_test.go | 7 ++++++- pkg/notify/notify.go | 4 ++-- pkg/notify/notify_test.go | 2 +- 6 files changed, 18 insertions(+), 6 deletions(-) diff --git a/cmd/jiralert/main.go b/cmd/jiralert/main.go index 40046bc..86be130 100644 --- a/cmd/jiralert/main.go +++ b/cmd/jiralert/main.go @@ -50,7 +50,6 @@ var ( "- 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.") - updateInComment = flag.Bool("update-in-comment", true, "When true, jiralert adds a comment with an update of the existing jira issue.") reopenTickets = flag.Bool("reopen-tickets", true, "When false, jiralert does not reopen tickets.") // Version is the build version, set by make to latest git tag/hash via `-ldflags "-X main.Version=$(VERSION)"`. @@ -125,7 +124,7 @@ func main() { return } - if retry, err := notify.NewReceiver(logger, conf, tmpl, client.Issue).Notify(&data, *hashJiraLabel, *updateSummary, *updateDescription, *updateInComment, *reopenTickets); err != nil { + if retry, err := notify.NewReceiver(logger, conf, tmpl, client.Issue).Notify(&data, *hashJiraLabel, *updateSummary, *updateDescription, *reopenTickets); err != nil { var status int if retry { // Instruct Alertmanager to retry. diff --git a/examples/jiralert.yml b/examples/jiralert.yml index da9b0b8..21ee72c 100644 --- a/examples/jiralert.yml +++ b/examples/jiralert.yml @@ -34,6 +34,8 @@ receivers: project: AB # Copy all Prometheus labels into separate JIRA labels. Optional (default: false). add_group_labels: false + # Include ticket update as comment too. Optional (default: false). + update_in_comment: false # Will be merged with the static_labels from the default map static_labels: ["anotherLabel"] diff --git a/pkg/config/config.go b/pkg/config/config.go index 71ba2b8..c6f269c 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -149,6 +149,9 @@ type ReceiverConfig struct { // Label copy settings AddGroupLabels bool `yaml:"add_group_labels" json:"add_group_labels"` + // Flag to auto-resolve opened issue when the alert is resolved. + UpdateInComment bool `yaml:"update_in_comment" json:"update_in_comment"` + // Flag to auto-resolve opened issue when the alert is resolved. AutoResolve *AutoResolve `yaml:"auto_resolve" json:"auto_resolve"` @@ -311,6 +314,9 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error { if len(c.Defaults.StaticLabels) > 0 { rc.StaticLabels = append(rc.StaticLabels, c.Defaults.StaticLabels...) } + if ! rc.UpdateInComment { + rc.UpdateInComment = c.Defaults.UpdateInComment + } } if len(c.Receivers) == 0 { diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index d851f72..81f76b3 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -56,6 +56,7 @@ receivers: project: AB # Copy all Prometheus labels into separate JIRA labels. Optional (default: false). add_group_labels: false + update_in_comment: false static_labels: ["somelabel"] - name: 'jira-xy' @@ -128,6 +129,7 @@ type receiverTestConfig struct { Description string `yaml:"description,omitempty"` WontFixResolution string `yaml:"wont_fix_resolution,omitempty"` AddGroupLabels bool `yaml:"add_group_labels,omitempty"` + UpdateInComment bool `yaml:"update_in_comment,omitempty"` StaticLabels []string `yaml:"static_labels" json:"static_labels"` AutoResolve *AutoResolve `yaml:"auto_resolve" json:"auto_resolve"` @@ -330,10 +332,11 @@ func TestReceiverOverrides(t *testing.T) { {"Description", "A nice description", "A nice description"}, {"WontFixResolution", "Won't Fix", "Won't Fix"}, {"AddGroupLabels", false, false}, + {"UpdateInComment", false, false}, {"AutoResolve", &AutoResolve{State: "Done"}, &autoResolve}, {"StaticLabels", []string{"somelabel"}, []string{"somelabel"}}, } { - optionalFields := []string{"Priority", "Description", "WontFixResolution", "AddGroupLabels", "AutoResolve", "StaticLabels"} + optionalFields := []string{"Priority", "Description", "WontFixResolution", "AddGroupLabels", "UpdateInComment", "AutoResolve", "StaticLabels"} defaultsConfig := newReceiverTestConfig(mandatoryReceiverFields(), optionalFields) receiverConfig := newReceiverTestConfig([]string{"Name"}, optionalFields) @@ -385,6 +388,8 @@ func newReceiverTestConfig(mandatory []string, optional []string) *receiverTestC var value reflect.Value if name == "AddGroupLabels" { value = reflect.ValueOf(true) + } else if name == "UpdateInComment" { + value = reflect.ValueOf(true) } 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 3faf8c7..c4a5c46 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, updateInComment bool, reopenTickets bool) (bool, error) { +func (r *Receiver) Notify(data *alertmanager.Data, hashJiraLabel bool, updateSummary bool, updateDescription bool, reopenTickets bool) (bool, error) { project, err := r.tmpl.Execute(r.conf.Project, data) if err != nil { return false, errors.Wrap(err, "generate project from template") @@ -108,7 +108,7 @@ func (r *Receiver) Notify(data *alertmanager.Data, hashJiraLabel bool, updateSum } } - if updateInComment { + if r.conf.UpdateInComment { retry, err := r.addComment(issue.Key, issueDesc) if err != nil { return retry, err diff --git a/pkg/notify/notify_test.go b/pkg/notify/notify_test.go index 2c057a9..cb4b0f3 100644 --- a/pkg/notify/notify_test.go +++ b/pkg/notify/notify_test.go @@ -597,7 +597,7 @@ func TestNotify_JIRAInteraction(t *testing.T) { return testNowTime } - _, err := receiver.Notify(tcase.inputAlert, true, true, true, false, true) + _, err := receiver.Notify(tcase.inputAlert, true, true, true, true) require.NoError(t, err) require.Equal(t, tcase.expectedJiraIssues, fakeJira.issuesByKey) }); !ok { From f80662f97db33ec17eece43aa73eadc9373d933c Mon Sep 17 00:00:00 2001 From: Ricardo Melo Date: Thu, 6 Jul 2023 08:37:35 -0400 Subject: [PATCH 3/5] Small fixes requested by @twotired: - Changed comment; - Replaced space indentation by tab indentation. Signed-off-by: Jason Wells --- pkg/config/config.go | 2 +- pkg/config/config_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/config/config.go b/pkg/config/config.go index c6f269c..07ea712 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -149,7 +149,7 @@ type ReceiverConfig struct { // Label copy settings AddGroupLabels bool `yaml:"add_group_labels" json:"add_group_labels"` - // Flag to auto-resolve opened issue when the alert is resolved. + // Flag to enable updates in comments. UpdateInComment bool `yaml:"update_in_comment" json:"update_in_comment"` // Flag to auto-resolve opened issue when the alert is resolved. diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 81f76b3..e370aa6 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -388,7 +388,7 @@ func newReceiverTestConfig(mandatory []string, optional []string) *receiverTestC var value reflect.Value if name == "AddGroupLabels" { value = reflect.ValueOf(true) - } else if name == "UpdateInComment" { + } else if name == "UpdateInComment" { value = reflect.ValueOf(true) } else if name == "AutoResolve" { value = reflect.ValueOf(&AutoResolve{State: "Done"}) From 98fe18a5a8f2283335b624dfb57b9e9eb12b0dc7 Mon Sep 17 00:00:00 2001 From: Jason Wells Date: Mon, 10 Jul 2023 04:21:36 -0700 Subject: [PATCH 4/5] truncate descriptions that exceed -max-description-length (default 32KB) (#165) * truncate descriptions that exceed -max-description-length (default 32,768) Signed-off-by: Jason Wells * Update main.go size was off by 1 Signed-off-by: Jason Wells --------- Signed-off-by: Jason Wells --- cmd/jiralert/main.go | 16 +++++++++------- pkg/notify/notify.go | 7 ++++++- pkg/notify/notify_test.go | 2 +- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/cmd/jiralert/main.go b/cmd/jiralert/main.go index 86be130..6f8ddec 100644 --- a/cmd/jiralert/main.go +++ b/cmd/jiralert/main.go @@ -36,9 +36,10 @@ import ( ) const ( - unknownReceiver = "" - logFormatLogfmt = "logfmt" - logFormatJSON = "json" + unknownReceiver = "" + logFormatLogfmt = "logfmt" + logFormatJSON = "json" + defaultMaxDescriptionLength = 32767 // https://jira.atlassian.com/browse/JRASERVER-64351 ) var ( @@ -48,9 +49,10 @@ 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.") + 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)"`. Version = "" @@ -124,7 +126,7 @@ func main() { return } - if retry, err := notify.NewReceiver(logger, conf, tmpl, client.Issue).Notify(&data, *hashJiraLabel, *updateSummary, *updateDescription, *reopenTickets); err != nil { + if retry, err := notify.NewReceiver(logger, conf, tmpl, client.Issue).Notify(&data, *hashJiraLabel, *updateSummary, *updateDescription, *reopenTickets, *maxDescriptionLength); err != nil { var status int if retry { // Instruct Alertmanager to retry. diff --git a/pkg/notify/notify.go b/pkg/notify/notify.go index c4a5c46..b929e4b 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) (bool, error) { +func (r *Receiver) Notify(data *alertmanager.Data, hashJiraLabel bool, updateSummary bool, updateDescription bool, reopenTickets 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") @@ -86,6 +86,11 @@ func (r *Receiver) Notify(data *alertmanager.Data, hashJiraLabel bool, updateSum return false, errors.Wrap(err, "render issue description") } + if len(issueDesc) > maxDescriptionLength { + level.Warn(r.logger).Log("msg", "truncating description", "original", len(issueDesc), "limit", maxDescriptionLength) + issueDesc = issueDesc[:maxDescriptionLength] + } + if issue != nil { // Update summary if needed. diff --git a/pkg/notify/notify_test.go b/pkg/notify/notify_test.go index cb4b0f3..bd3d022 100644 --- a/pkg/notify/notify_test.go +++ b/pkg/notify/notify_test.go @@ -597,7 +597,7 @@ func TestNotify_JIRAInteraction(t *testing.T) { return testNowTime } - _, err := receiver.Notify(tcase.inputAlert, true, true, true, true) + _, err := receiver.Notify(tcase.inputAlert, true, true, true, true, 32768) require.NoError(t, err) require.Equal(t, tcase.expectedJiraIssues, fakeJira.issuesByKey) }); !ok { From e6032aa6a6d6d5c235fa9369cb706a6dc8ec6712 Mon Sep 17 00:00:00 2001 From: Jason Wells Date: Tue, 25 Jul 2023 15:31:47 -0700 Subject: [PATCH 5/5] fix tests and do not attempt to retrieve update_in_comment from defaults Signed-off-by: Jason Wells --- examples/jiralert.yml | 6 +++--- pkg/config/config.go | 3 --- pkg/config/config_test.go | 5 ++--- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/examples/jiralert.yml b/examples/jiralert.yml index 21ee72c..8776a71 100644 --- a/examples/jiralert.yml +++ b/examples/jiralert.yml @@ -34,8 +34,6 @@ receivers: project: AB # Copy all Prometheus labels into separate JIRA labels. Optional (default: false). add_group_labels: false - # Include ticket update as comment too. Optional (default: false). - update_in_comment: false # Will be merged with the static_labels from the default map static_labels: ["anotherLabel"] @@ -58,7 +56,9 @@ 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' + # Include ticket update as comment. Optional (default: false). + update_in_comment: false # File containing template definitions. Required. template: jiralert.tmpl diff --git a/pkg/config/config.go b/pkg/config/config.go index 07ea712..2b8dd99 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -314,9 +314,6 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error { if len(c.Defaults.StaticLabels) > 0 { rc.StaticLabels = append(rc.StaticLabels, c.Defaults.StaticLabels...) } - if ! rc.UpdateInComment { - rc.UpdateInComment = c.Defaults.UpdateInComment - } } if len(c.Receivers) == 0 { diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index e370aa6..222e148 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -333,10 +333,11 @@ func TestReceiverOverrides(t *testing.T) { {"WontFixResolution", "Won't Fix", "Won't Fix"}, {"AddGroupLabels", false, false}, {"UpdateInComment", false, false}, + {"UpdateInComment", true, true}, {"AutoResolve", &AutoResolve{State: "Done"}, &autoResolve}, {"StaticLabels", []string{"somelabel"}, []string{"somelabel"}}, } { - optionalFields := []string{"Priority", "Description", "WontFixResolution", "AddGroupLabels", "UpdateInComment", "AutoResolve", "StaticLabels"} + optionalFields := []string{"Priority", "Description", "WontFixResolution", "AddGroupLabels", "AutoResolve", "StaticLabels"} defaultsConfig := newReceiverTestConfig(mandatoryReceiverFields(), optionalFields) receiverConfig := newReceiverTestConfig([]string{"Name"}, optionalFields) @@ -388,8 +389,6 @@ func newReceiverTestConfig(mandatory []string, optional []string) *receiverTestC var value reflect.Value if name == "AddGroupLabels" { value = reflect.ValueOf(true) - } else if name == "UpdateInComment" { - value = reflect.ValueOf(true) } else if name == "AutoResolve" { value = reflect.ValueOf(&AutoResolve{State: "Done"}) } else if name == "StaticLabels" {