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

add ability to update in comments #161

Closed
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
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
16 changes: 9 additions & 7 deletions cmd/jiralert/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,10 @@ import (
)

const (
unknownReceiver = "<unknown>"
logFormatLogfmt = "logfmt"
logFormatJSON = "json"
unknownReceiver = "<unknown>"
logFormatLogfmt = "logfmt"
logFormatJSON = "json"
defaultMaxDescriptionLength = 32767 // https://jira.atlassian.com/browse/JRASERVER-64351
)

var (
Expand All @@ -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 = "<local build>"
Expand Down Expand Up @@ -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.
Expand Down
4 changes: 3 additions & 1 deletion examples/jiralert.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,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
3 changes: 3 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ type ReceiverConfig struct {
// Label copy settings
AddGroupLabels bool `yaml:"add_group_labels" json:"add_group_labels"`

// 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.
AutoResolve *AutoResolve `yaml:"auto_resolve" json:"auto_resolve"`

Expand Down
4 changes: 4 additions & 0 deletions pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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"`
Expand Down Expand Up @@ -330,6 +332,8 @@ 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},
{"UpdateInComment", true, true},
{"AutoResolve", &AutoResolve{State: "Done"}, &autoResolve},
{"StaticLabels", []string{"somelabel"}, []string{"somelabel"}},
} {
Expand Down
30 changes: 29 additions & 1 deletion pkg/notify/notify.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand All @@ -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, 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")
Expand All @@ -85,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.
Expand All @@ -107,6 +113,13 @@ func (r *Receiver) Notify(data *alertmanager.Data, hashJiraLabel bool, updateSum
}
}

if r.conf.UpdateInComment {
Copy link
Contributor

Choose a reason for hiding this comment

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

this works better for me to avoid flodding of comments if repeat interval is set low

		if r.conf.UpdateInComment {
			if issue.Fields.Description != issueDesc {
				retry, err := r.addComment(issue.Key, issueDesc)
				if err != nil {
					return retry, err
				}
			}
		}

Copy link
Contributor

Choose a reason for hiding this comment

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

And add descriptionhere

func (r *Receiver) search(project, issueLabel string) (*jira.Issue, bool, error) {
	query := fmt.Sprintf("project=\"%s\" and labels=%q order by resolutiondate desc", project, issueLabel)
	options := &jira.SearchOptions{
		Fields:     []string{"summary", "status", "resolution", "resolutiondate", "description"},
		MaxResults: 2,
	}

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)
Expand Down Expand Up @@ -365,6 +378,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)
}
Expand Down
7 changes: 6 additions & 1 deletion pkg/notify/notify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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, true, 32768)
require.NoError(t, err)
require.Equal(t, tcase.expectedJiraIssues, fakeJira.issuesByKey)
}); !ok {
Expand Down