Skip to content

Commit e3063e9

Browse files
committed
feat(conf): add optional field_labels to override Labels
Signed-off-by: Ricardo Melo <ricardo@cropa.ca>
1 parent d43cbab commit e3063e9

File tree

6 files changed

+103
-12
lines changed

6 files changed

+103
-12
lines changed

cmd/jiralert/main.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ func main() {
8181
os.Exit(1)
8282
}
8383

84+
if config.GetJiraFieldKey() != nil {
85+
level.Error(logger).Log("msg", "error discovering jira key for field alert params", "err", err)
86+
}
87+
8488
tmpl, err := template.LoadTemplate(config.Template, logger)
8589
if err != nil {
8690
level.Error(logger).Log("msg", "error loading templates", "path", config.Template, "err", err)

examples/jiralert.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ receivers:
4040
- name: 'jira-ab'
4141
# JIRA project to create the issue in. Required.
4242
project: AB
43+
# Define the jira field used by jiralert to avoid ticket duplication. Optional (default: Labels)
44+
field_labels: Labels
4345
# Copy all Prometheus labels into separate JIRA labels. Optional (default: false).
4446
add_group_labels: false
4547
# Include ticket update as comment too. Optional (default: false).

pkg/config/config.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"strings"
2424
"time"
2525

26+
"github.com/andygrunwald/go-jira"
2627
"github.com/go-kit/log"
2728
"github.com/go-kit/log/level"
2829

@@ -143,6 +144,8 @@ type ReceiverConfig struct {
143144
Priority string `yaml:"priority" json:"priority"`
144145
Description string `yaml:"description" json:"description"`
145146
WontFixResolution string `yaml:"wont_fix_resolution" json:"wont_fix_resolution"`
147+
FieldLabels string `yaml:"field_labels" json:"field_labels"`
148+
FieldLabelsKey string `yaml:"field_labels_key" json:"field_labels_key"`
146149
Fields map[string]interface{} `yaml:"fields" json:"fields"`
147150
Components []string `yaml:"components" json:"components"`
148151
StaticLabels []string `yaml:"static_labels" json:"static_labels"`
@@ -194,6 +197,58 @@ func (c Config) String() string {
194197
return string(b)
195198
}
196199

200+
// GetJiraFieldKey returns the jira key associated to a field.
201+
func (c *Config) GetJiraFieldKey() error {
202+
var missingField []string
203+
for _, rc := range c.Receivers {
204+
// descover jira labels key.
205+
var client *jira.Client
206+
var err error
207+
if rc.User != "" && rc.Password != "" {
208+
tp := jira.BasicAuthTransport{
209+
Username: rc.User,
210+
Password: string(rc.Password),
211+
}
212+
client, err = jira.NewClient(tp.Client(), rc.APIURL)
213+
} else if rc.PersonalAccessToken != "" {
214+
tp := jira.PATAuthTransport{
215+
Token: string(rc.PersonalAccessToken),
216+
}
217+
client, err = jira.NewClient(tp.Client(), rc.APIURL)
218+
}
219+
220+
if err != nil {
221+
return err
222+
}
223+
224+
options := &jira.GetQueryOptions{
225+
ProjectKeys: rc.Project,
226+
Expand: "projects.issuetypes.fields",
227+
}
228+
meta, _, err := client.Issue.GetCreateMetaWithOptions(options)
229+
if err != nil {
230+
return err
231+
}
232+
it := meta.Projects[0].GetIssueTypeWithName(rc.IssueType)
233+
if it == nil {
234+
return fmt.Errorf("jira: Issue type %s not found", rc.IssueType)
235+
}
236+
fields, err := it.GetAllFields()
237+
if err != nil {
238+
return err
239+
}
240+
if val, ok := fields[rc.FieldLabels]; ok {
241+
rc.FieldLabelsKey = val
242+
continue
243+
}
244+
missingField = append(missingField, rc.FieldLabels)
245+
}
246+
if len(missingField) != 0 {
247+
return fmt.Errorf("jira: Fields %s ", missingField)
248+
}
249+
return nil
250+
}
251+
197252
// UnmarshalYAML implements the yaml.Unmarshaler interface.
198253
func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
199254
// We want to set c to the defaults and then overwrite it with the input.
@@ -297,6 +352,14 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
297352
if rc.WontFixResolution == "" && c.Defaults.WontFixResolution != "" {
298353
rc.WontFixResolution = c.Defaults.WontFixResolution
299354
}
355+
if rc.FieldLabels == "" {
356+
if c.Defaults.FieldLabels != "" {
357+
rc.FieldLabels = c.Defaults.FieldLabels
358+
} else {
359+
rc.FieldLabels = "Labels"
360+
}
361+
}
362+
300363
if rc.AutoResolve != nil {
301364
if rc.AutoResolve.State == "" {
302365
return fmt.Errorf("bad config in receiver %q, 'auto_resolve' was defined with empty 'state' field", rc.Name)

pkg/config/config_test.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ receivers:
5858
# Copy all Prometheus labels into separate JIRA labels. Optional (default: false).
5959
add_group_labels: false
6060
update_in_comment: false
61+
field_labels: Labels
62+
field_labels_key: labels
6163
static_labels: ["somelabel"]
6264
6365
- name: 'jira-xy'
@@ -76,6 +78,8 @@ receivers:
7678
customfield_10002: { "value": "red" }
7779
# MultiSelect
7880
customfield_10003: [{"value": "red" }, {"value": "blue" }, {"value": "green" }]
81+
field_labels: Labels
82+
field_labels_key: labels
7983
8084
# File containing template definitions. Required.
8185
template: jiralert.tmpl
@@ -129,6 +133,8 @@ type receiverTestConfig struct {
129133
Priority string `yaml:"priority,omitempty"`
130134
Description string `yaml:"description,omitempty"`
131135
WontFixResolution string `yaml:"wont_fix_resolution,omitempty"`
136+
FieldLabels string `yaml:"field_labels" json:"field_labels"`
137+
FieldLabelsKey string `yaml:"field_labels_key" json:"field_labels_key"`
132138
AddGroupLabels *bool `yaml:"add_group_labels,omitempty"`
133139
UpdateInComment *bool `yaml:"update_in_comment,omitempty"`
134140
StaticLabels []string `yaml:"static_labels" json:"static_labels"`
@@ -336,14 +342,16 @@ func TestReceiverOverrides(t *testing.T) {
336342
{"Priority", "Critical", "Critical"},
337343
{"Description", "A nice description", "A nice description"},
338344
{"WontFixResolution", "Won't Fix", "Won't Fix"},
345+
{"FieldLabels", "Labels", "Labels"},
346+
{"FieldLabelsKey", "labels", "labels"},
339347
{"AddGroupLabels", &addGroupLabelsFalseVal, &addGroupLabelsFalseVal},
340348
{"AddGroupLabels", &addGroupLabelsTrueVal, &addGroupLabelsTrueVal},
341349
{"UpdateInComment", &updateInCommentFalseVal, &updateInCommentFalseVal},
342350
{"UpdateInComment", &updateInCommentTrueVal, &updateInCommentTrueVal},
343351
{"AutoResolve", &AutoResolve{State: "Done"}, &autoResolve},
344352
{"StaticLabels", []string{"somelabel"}, []string{"somelabel"}},
345353
} {
346-
optionalFields := []string{"Priority", "Description", "WontFixResolution", "AddGroupLabels", "UpdateInComment", "AutoResolve", "StaticLabels"}
354+
optionalFields := []string{"Priority", "Description", "WontFixResolution", "FieldLabels", "FieldLabelsKey", "AddGroupLabels", "UpdateInComment", "AutoResolve", "StaticLabels"}
347355
defaultsConfig := newReceiverTestConfig(mandatoryReceiverFields(), optionalFields)
348356
receiverConfig := newReceiverTestConfig([]string{"Name"}, optionalFields)
349357

pkg/notify/notify.go

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -137,32 +137,32 @@ func (r *Receiver) Notify(data *alertmanager.Data, hashJiraLabel bool, updateSum
137137

138138
if len(data.Alerts.Firing()) == 0 {
139139
if r.conf.AutoResolve != nil {
140-
level.Debug(r.logger).Log("msg", "no firing alert; resolving issue", "key", issue.Key, "label", issueGroupLabel)
140+
level.Debug(r.logger).Log("msg", "no firing alert; resolving issue", "key", issue.Key, r.conf.FieldLabels, issueGroupLabel)
141141
retry, err := r.resolveIssue(issue.Key)
142142
if err != nil {
143143
return retry, err
144144
}
145145
return false, nil
146146
}
147147

148-
level.Debug(r.logger).Log("msg", "no firing alert; summary checked, nothing else to do.", "key", issue.Key, "label", issueGroupLabel)
148+
level.Debug(r.logger).Log("msg", "no firing alert; summary checked, nothing else to do.", "key", issue.Key, r.conf.FieldLabels, issueGroupLabel)
149149
return false, nil
150150
}
151151

152152
// The set of JIRA status categories is fixed, this is a safe check to make.
153153
if issue.Fields.Status.StatusCategory.Key != "done" {
154-
level.Debug(r.logger).Log("msg", "issue is unresolved, all is done", "key", issue.Key, "label", issueGroupLabel)
154+
level.Debug(r.logger).Log("msg", "issue is unresolved, all is done", "key", issue.Key, r.conf.FieldLabels, issueGroupLabel)
155155
return false, nil
156156
}
157157

158158
if reopenTickets {
159159
if r.conf.WontFixResolution != "" && issue.Fields.Resolution != nil &&
160160
issue.Fields.Resolution.Name == r.conf.WontFixResolution {
161-
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)
161+
level.Info(r.logger).Log("msg", "issue was resolved as won't fix, not reopening", "key", issue.Key, r.conf.FieldLabels, issueGroupLabel, "resolution", issue.Fields.Resolution.Name)
162162
return false, nil
163163
}
164164

165-
level.Info(r.logger).Log("msg", "issue was recently resolved, reopening", "key", issue.Key, "label", issueGroupLabel)
165+
level.Info(r.logger).Log("msg", "issue was recently resolved, reopening", "key", issue.Key, r.conf.FieldLabels, issueGroupLabel)
166166
return r.reopen(issue.Key)
167167
}
168168

@@ -171,11 +171,11 @@ func (r *Receiver) Notify(data *alertmanager.Data, hashJiraLabel bool, updateSum
171171
}
172172

173173
if len(data.Alerts.Firing()) == 0 {
174-
level.Debug(r.logger).Log("msg", "no firing alert; nothing to do.", "label", issueGroupLabel)
174+
level.Debug(r.logger).Log("msg", "no firing alert; nothing to do.", r.conf.FieldLabels, issueGroupLabel)
175175
return false, nil
176176
}
177177

178-
level.Info(r.logger).Log("msg", "no recent matching issue found, creating new issue", "label", issueGroupLabel)
178+
level.Info(r.logger).Log("msg", "no recent matching issue found, creating new issue", r.conf.FieldLabels, issueGroupLabel)
179179

180180
issueType, err := r.tmpl.Execute(r.conf.IssueType, data)
181181
if err != nil {
@@ -190,10 +190,14 @@ func (r *Receiver) Notify(data *alertmanager.Data, hashJiraLabel bool, updateSum
190190
Type: jira.IssueType{Name: issueType},
191191
Description: issueDesc,
192192
Summary: issueSummary,
193-
Labels: append(staticLabels, issueGroupLabel),
194193
Unknowns: tcontainer.NewMarshalMap(),
195194
},
196195
}
196+
if r.conf.FieldLabels == "Labels" {
197+
issue.Fields.Labels = append(staticLabels, issueGroupLabel)
198+
} else {
199+
issue.Fields.Unknowns[r.conf.FieldLabelsKey] = append(staticLabels, issueGroupLabel)
200+
}
197201
if r.conf.Priority != "" {
198202
issuePrio, err := r.tmpl.Execute(r.conf.Priority, data)
199203
if err != nil {
@@ -312,9 +316,15 @@ func toGroupTicketLabel(groupLabels alertmanager.KV, hashJiraLabel bool) string
312316
}
313317

314318
func (r *Receiver) search(projects []string, issueLabel string) (*jira.Issue, bool, error) {
319+
var labelKey string
315320
// Search multiple projects in case issue was moved and further alert firings are desired in existing JIRA.
316321
projectList := "'" + strings.Join(projects, "', '") + "'"
317-
query := fmt.Sprintf("project in(%s) and labels=%q order by resolutiondate desc", projectList, issueLabel)
322+
if r.conf.FieldLabels == "Labels" {
323+
labelKey = "labels"
324+
} else {
325+
labelKey = fmt.Sprintf("cf[%s]", strings.Split(r.conf.FieldLabelsKey, "_")[1])
326+
}
327+
query := fmt.Sprintf("project in(%s) and %s=%q order by resolutiondate desc", projectList, labelKey, issueLabel)
318328
options := &jira.SearchOptions{
319329
Fields: []string{"summary", "status", "resolution", "resolutiondate", "description", "comment"},
320330
MaxResults: 2,
@@ -361,7 +371,7 @@ func (r *Receiver) findIssueToReuse(project string, issueGroupLabel string) (*ji
361371

362372
resolutionTime := time.Time(issue.Fields.Resolutiondate)
363373
if resolutionTime != (time.Time{}) && resolutionTime.Add(time.Duration(*r.conf.ReopenDuration)).Before(r.timeNow()) && *r.conf.ReopenDuration != 0 {
364-
level.Debug(r.logger).Log("msg", "existing resolved issue is too old to reopen, skipping", "key", issue.Key, "label", issueGroupLabel, "resolution_time", resolutionTime.Format(time.RFC3339), "reopen_duration", *r.conf.ReopenDuration)
374+
level.Debug(r.logger).Log("msg", "existing resolved issue is too old to reopen, skipping", "key", issue.Key, r.conf.FieldLabels, issueGroupLabel, "resolution_time", resolutionTime.Format(time.RFC3339), "reopen_duration", *r.conf.ReopenDuration)
365375
return nil, false, nil
366376
}
367377

@@ -423,7 +433,6 @@ func (r *Receiver) reopen(issueKey string) (bool, error) {
423433
}
424434

425435
func (r *Receiver) create(issue *jira.Issue) (bool, error) {
426-
level.Debug(r.logger).Log("msg", "create", "issue", fmt.Sprintf("%+v", *issue.Fields))
427436
newIssue, resp, err := r.client.Create(issue)
428437
if err != nil {
429438
return handleJiraErrResponse("Issue.Create", resp, err, r.logger)

pkg/notify/notify_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ func testReceiverConfig1() *config.ReceiverConfig {
168168
ReopenDuration: &reopen,
169169
ReopenState: "reopened",
170170
WontFixResolution: "won't-fix",
171+
FieldLabels: "Labels",
171172
}
172173
}
173174

@@ -180,6 +181,7 @@ func testReceiverConfig2() *config.ReceiverConfig {
180181
ReopenState: "reopened",
181182
Description: `{{ .Alerts.Firing | len }}`,
182183
WontFixResolution: "won't-fix",
184+
FieldLabels: "Labels",
183185
}
184186
}
185187

@@ -193,6 +195,7 @@ func testReceiverConfigAddComments() *config.ReceiverConfig {
193195
ReopenState: "reopened",
194196
Description: `{{ .Alerts.Firing | len }}`,
195197
WontFixResolution: "won't-fix",
198+
FieldLabels: "Labels",
196199
UpdateInComment: &updateInCommentValue,
197200
}
198201
}
@@ -206,6 +209,7 @@ func testReceiverConfigAutoResolve() *config.ReceiverConfig {
206209
ReopenDuration: &reopen,
207210
ReopenState: "reopened",
208211
WontFixResolution: "won't-fix",
212+
FieldLabels: "Labels",
209213
AutoResolve: &autoResolve,
210214
}
211215
}
@@ -218,6 +222,7 @@ func testReceiverConfigWithStaticLabels() *config.ReceiverConfig {
218222
ReopenDuration: &reopen,
219223
ReopenState: "reopened",
220224
WontFixResolution: "won't-fix",
225+
FieldLabels: "Labels",
221226
StaticLabels: []string{"somelabel"},
222227
}
223228
}

0 commit comments

Comments
 (0)