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

Allowing selected (custom/standard) fields to be updated #158

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
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
Loading