Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions examples/jiralert.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ receivers:
# MultiSelect
customfield_10003: [{"value": "red"}, {"value": "blue"}, {"value": "green"}]
#
# List of standard or custom field names which values will be updated. Optional.
update_always_fields:
- customfield_10001
- customfield_10003
#
# Automatically resolve jira issues when alert is resolved. Optional. If declared, ensure state is not an empty string.
auto_resolve:
state: 'Done'
Expand Down
4 changes: 4 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ type ReceiverConfig struct {
Fields map[string]interface{} `yaml:"fields" json:"fields"`
Components []string `yaml:"components" json:"components"`
StaticLabels []string `yaml:"static_labels" json:"static_labels"`
FieldsToUpdate []string `yaml:"update_always_fields" json:"update_always_fields"`

// Label copy settings
AddGroupLabels *bool `yaml:"add_group_labels" json:"add_group_labels"`
Expand Down Expand Up @@ -312,6 +313,9 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
}
}
}
if len(c.Defaults.FieldsToUpdate) > 0 {
rc.FieldsToUpdate = c.Defaults.FieldsToUpdate
}
if len(c.Defaults.StaticLabels) > 0 {
rc.StaticLabels = append(rc.StaticLabels, c.Defaults.StaticLabels...)
}
Expand Down
5 changes: 5 additions & 0 deletions pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ receivers:
customfield_10002: { "value": "red" }
# MultiSelect
customfield_10003: [{"value": "red" }, {"value": "blue" }, {"value": "green" }]
# List of standard or custom field names which values will be updated. Optional.
update_always_fields:
- customfield_10001
- customfield_10003

# File containing template definitions. Required.
template: jiralert.tmpl
Expand Down Expand Up @@ -132,6 +136,7 @@ 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"`
FieldsToUpdate []string `yaml:"update_always_fields,omitempty"`

AutoResolve *AutoResolve `yaml:"auto_resolve" json:"auto_resolve"`

Expand Down
41 changes: 40 additions & 1 deletion pkg/notify/notify.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,17 @@ func (r *Receiver) Notify(data *alertmanager.Data, hashJiraLabel bool, updateSum
issueDesc = issueDesc[:maxDescriptionLength]
}

issueCustomFields := tcontainer.NewMarshalMap()

for _, field := range r.conf.FieldsToUpdate {
if _, ok := r.conf.Fields[field]; ok {
issueCustomFields[field], err = deepCopyWithTemplate(r.conf.Fields[field], r.tmpl, data)
if err != nil {
return false, errors.Wrap(err, "render issue fields")
}
}
}

if issue != nil {

// Update summary if needed.
Expand Down Expand Up @@ -135,6 +146,19 @@ func (r *Receiver) Notify(data *alertmanager.Data, hashJiraLabel bool, updateSum
}
}

for field := range issueCustomFields {
if _, ok := issue.Fields.Unknowns[field]; ok {
if issue.Fields.Unknowns[field] != issueCustomFields[field] {
retry, err = r.updateUnknownFields(issue.Key, tcontainer.MarshalMap(map[string]interface{}{
field: issueCustomFields[field],
}))
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 @@ -316,7 +340,7 @@ func (r *Receiver) search(projects []string, issueLabel string) (*jira.Issue, bo
projectList := "'" + strings.Join(projects, "', '") + "'"
query := fmt.Sprintf("project in(%s) and labels=%q order by resolutiondate desc", projectList, issueLabel)
options := &jira.SearchOptions{
Fields: []string{"summary", "status", "resolution", "resolutiondate", "description", "comment"},
Fields: append([]string{"summary", "status", "resolution", "resolutiondate", "description", "comment"}, r.conf.FieldsToUpdate...),
MaxResults: 2,
}

Expand Down Expand Up @@ -418,6 +442,21 @@ func (r *Receiver) addComment(issueKey string, content string) (bool, error) {
return false, nil
}

func (r *Receiver) updateUnknownFields(issueKey string, unknowns tcontainer.MarshalMap) (bool, error) {
level.Debug(r.logger).Log("msg", "updating issue with unknown fields", "key", issueKey, "unknowns", unknowns)

issueUpdate := &jira.Issue{
Key: issueKey,
Fields: &jira.IssueFields{
Unknowns: unknowns,
},
}
if _, resp, err := r.client.UpdateWithOptions(issueUpdate, nil); err != nil {
return handleJiraErrResponse("Issue.UpdateUnknownFields", resp, err, r.logger)
}
return false, nil
}

func (r *Receiver) reopen(issueKey string) (bool, error) {
return r.doTransition(issueKey, r.conf.ReopenState)
}
Expand Down
71 changes: 71 additions & 0 deletions pkg/notify/notify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ func (f *fakeJira) Search(jql string, options *jira.SearchOptions) ([]jira.Issue
var issues []jira.Issue
for _, key := range f.keysByQuery[jql] {
issue := jira.Issue{Key: key, Fields: &jira.IssueFields{}}
issue.Fields.Unknowns = f.issuesByKey[key].Fields.Unknowns
for _, field := range options.Fields {
switch field {
case "summary":
Expand Down Expand Up @@ -132,6 +133,10 @@ func (f *fakeJira) UpdateWithOptions(old *jira.Issue, _ *jira.UpdateQueryOptions
issue.Fields.Description = old.Fields.Description
}

if old.Fields.Unknowns != nil {
issue.Fields.Unknowns = old.Fields.Unknowns
}

f.issuesByKey[issue.Key] = issue
return issue, nil, nil
}
Expand Down Expand Up @@ -222,6 +227,21 @@ func testReceiverConfigWithStaticLabels() *config.ReceiverConfig {
}
}

func testReceiverConfigWithCustomFields() *config.ReceiverConfig {
reopen := config.Duration(1 * time.Hour)
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",
Description: `{{ .Alerts.Firing | len }}`,
Fields: tcontainer.MarshalMap(map[string]interface{}{
"customfield_12345": `{{ (index .Alerts 0).Annotations.AlertValue }}`,
}),
FieldsToUpdate: []string{"customfield_12345", "non_existant_field"},
}
}

func TestNotify_JIRAInteraction(t *testing.T) {
testNowTime := time.Now()

Expand Down Expand Up @@ -347,6 +367,57 @@ func TestNotify_JIRAInteraction(t *testing.T) {
},
},
},
{
name: "opened ticket, update specified custom field value",
inputConfig: testReceiverConfigWithCustomFields(),
initJira: func(t *testing.T) *fakeJira {
f := newTestFakeJira()
_, _, err := f.Create(&jira.Issue{
ID: "1",
Key: "1",
Fields: &jira.IssueFields{
Project: jira.Project{Key: testReceiverConfigWithCustomFields().Project},
Labels: []string{"JIRALERT{819ba5ecba4ea5946a8d17d285cb23f3bb6862e08bb602ab08fd231cd8e1a83a1d095b0208a661787e9035f0541817634df5a994d1b5d4200d6c68a7663c97f5}"},
Unknowns: tcontainer.MarshalMap(map[string]interface{}{
"customfield_12345": "90",
}),
Summary: "[FIRING:1] b d ",
Description: "1",
},
})
require.NoError(t, err)
return f
},
inputAlert: &alertmanager.Data{
Alerts: alertmanager.Alerts{
{Status: alertmanager.AlertFiring,
Annotations: alertmanager.KV{
"AlertValue": "95",
},
}, // New value for the field
},
Status: alertmanager.AlertFiring,
GroupLabels: alertmanager.KV{"a": "b", "c": "d"},
},
expectedJiraIssues: map[string]*jira.Issue{
"1": {
ID: "1",
Key: "1",
Fields: &jira.IssueFields{
Project: jira.Project{Key: testReceiverConfigWithCustomFields().Project},
Labels: []string{"JIRALERT{819ba5ecba4ea5946a8d17d285cb23f3bb6862e08bb602ab08fd231cd8e1a83a1d095b0208a661787e9035f0541817634df5a994d1b5d4200d6c68a7663c97f5}"},
Status: &jira.Status{
StatusCategory: jira.StatusCategory{Key: "NotDone"},
},
Unknowns: tcontainer.MarshalMap{
"customfield_12345": "95",
},
Summary: "[FIRING:1] b d ",
Description: "1",
},
},
},
},
{
name: "closed ticket, reopen and update summary",
inputConfig: testReceiverConfig1(),
Expand Down