Skip to content

Commit

Permalink
feat(conf): add optional field_labels to override Labels
Browse files Browse the repository at this point in the history
Signed-off-by: Ricardo Melo <[email protected]>
  • Loading branch information
cropalato committed May 29, 2024
1 parent d43cbab commit cd931b9
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 12 deletions.
4 changes: 4 additions & 0 deletions cmd/jiralert/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ func main() {
os.Exit(1)
}

if config.GetJiraFieldKey() != nil {
level.Error(logger).Log("msg", "error discovering jira key for field alert params", "err", err)
}

tmpl, err := template.LoadTemplate(config.Template, logger)
if err != nil {
level.Error(logger).Log("msg", "error loading templates", "path", config.Template, "err", err)
Expand Down
2 changes: 2 additions & 0 deletions examples/jiralert.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ receivers:
- name: 'jira-ab'
# JIRA project to create the issue in. Required.
project: AB
# Define the jira field used by jiralert to avoid ticket duplication. Optional (default: Labels)
field_labels: Labels
# Copy all Prometheus labels into separate JIRA labels. Optional (default: false).
add_group_labels: false
# Include ticket update as comment too. Optional (default: false).
Expand Down
58 changes: 58 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"strings"
"time"

"github.com/andygrunwald/go-jira"
"github.com/go-kit/log"
"github.com/go-kit/log/level"

Expand Down Expand Up @@ -143,6 +144,8 @@ type ReceiverConfig struct {
Priority string `yaml:"priority" json:"priority"`
Description string `yaml:"description" json:"description"`
WontFixResolution string `yaml:"wont_fix_resolution" json:"wont_fix_resolution"`
FieldLabels string `yaml:"field_labels" json:"field_labels"`
FieldLabelsKey string `yaml:"field_labels_key" json:"field_labels_key"`
Fields map[string]interface{} `yaml:"fields" json:"fields"`
Components []string `yaml:"components" json:"components"`
StaticLabels []string `yaml:"static_labels" json:"static_labels"`
Expand Down Expand Up @@ -194,6 +197,53 @@ func (c Config) String() string {
return string(b)
}

// GetJiraFieldKey returns the jira key associated to a field.
func (c *Config) GetJiraFieldKey() error {
for _, rc := range c.Receivers {
// descover jira labels key.
var client *jira.Client
var err error
if rc.User != "" && rc.Password != "" {
tp := jira.BasicAuthTransport{
Username: rc.User,
Password: string(rc.Password),
}
client, err = jira.NewClient(tp.Client(), rc.APIURL)
} else if rc.PersonalAccessToken != "" {
tp := jira.PATAuthTransport{
Token: string(rc.PersonalAccessToken),
}
client, err = jira.NewClient(tp.Client(), rc.APIURL)
}

if err != nil {
return err
}

options := &jira.GetQueryOptions{
ProjectKeys: rc.Project,
Expand: "projects.issuetypes.fields",
}
meta, _, err := client.Issue.GetCreateMetaWithOptions(options)
if err != nil {
return err
}
it := meta.Projects[0].GetIssueTypeWithName(rc.IssueType)
if it == nil {
return fmt.Errorf("jira: Issue type %s not found", rc.IssueType)
}
fields, err := it.GetAllFields()
if err != nil {
return err
}
if val, ok := fields[rc.FieldLabels]; ok {
rc.FieldLabelsKey = val
}
return fmt.Errorf("jira: Field %s not found in %s issue type", rc.FieldLabels, rc.IssueType)

Check failure on line 242 in pkg/config/config.go

View workflow job for this annotation

GitHub Actions / test

SA4004: the surrounding loop is unconditionally terminated (staticcheck)
}
return nil
}

// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
// We want to set c to the defaults and then overwrite it with the input.
Expand Down Expand Up @@ -297,6 +347,14 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
if rc.WontFixResolution == "" && c.Defaults.WontFixResolution != "" {
rc.WontFixResolution = c.Defaults.WontFixResolution
}
if rc.FieldLabels == "" {
if c.Defaults.FieldLabels != "" {
rc.FieldLabels = c.Defaults.FieldLabels
} else {
rc.FieldLabels = "Labels"
}
}

if rc.AutoResolve != nil {
if rc.AutoResolve.State == "" {
return fmt.Errorf("bad config in receiver %q, 'auto_resolve' was defined with empty 'state' field", rc.Name)
Expand Down
10 changes: 9 additions & 1 deletion pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ receivers:
# Copy all Prometheus labels into separate JIRA labels. Optional (default: false).
add_group_labels: false
update_in_comment: false
field_labels: Labels
field_labels_key: labels
static_labels: ["somelabel"]
- name: 'jira-xy'
Expand All @@ -76,6 +78,8 @@ receivers:
customfield_10002: { "value": "red" }
# MultiSelect
customfield_10003: [{"value": "red" }, {"value": "blue" }, {"value": "green" }]
field_labels: Labels
field_labels_key: labels
# File containing template definitions. Required.
template: jiralert.tmpl
Expand Down Expand Up @@ -129,6 +133,8 @@ type receiverTestConfig struct {
Priority string `yaml:"priority,omitempty"`
Description string `yaml:"description,omitempty"`
WontFixResolution string `yaml:"wont_fix_resolution,omitempty"`
FieldLabels string `yaml:"field_labels" json:"field_labels"`
FieldLabelsKey string `yaml:"field_labels_key" json:"field_labels_key"`
AddGroupLabels *bool `yaml:"add_group_labels,omitempty"`
UpdateInComment *bool `yaml:"update_in_comment,omitempty"`
StaticLabels []string `yaml:"static_labels" json:"static_labels"`
Expand Down Expand Up @@ -336,14 +342,16 @@ func TestReceiverOverrides(t *testing.T) {
{"Priority", "Critical", "Critical"},
{"Description", "A nice description", "A nice description"},
{"WontFixResolution", "Won't Fix", "Won't Fix"},
{"FieldLabels", "Labels", "Labels"},
{"FieldLabelsKey", "labels", "labels"},
{"AddGroupLabels", &addGroupLabelsFalseVal, &addGroupLabelsFalseVal},
{"AddGroupLabels", &addGroupLabelsTrueVal, &addGroupLabelsTrueVal},
{"UpdateInComment", &updateInCommentFalseVal, &updateInCommentFalseVal},
{"UpdateInComment", &updateInCommentTrueVal, &updateInCommentTrueVal},
{"AutoResolve", &AutoResolve{State: "Done"}, &autoResolve},
{"StaticLabels", []string{"somelabel"}, []string{"somelabel"}},
} {
optionalFields := []string{"Priority", "Description", "WontFixResolution", "AddGroupLabels", "UpdateInComment", "AutoResolve", "StaticLabels"}
optionalFields := []string{"Priority", "Description", "WontFixResolution", "FieldLabels", "FieldLabelsKey", "AddGroupLabels", "UpdateInComment", "AutoResolve", "StaticLabels"}
defaultsConfig := newReceiverTestConfig(mandatoryReceiverFields(), optionalFields)
receiverConfig := newReceiverTestConfig([]string{"Name"}, optionalFields)

Expand Down
31 changes: 20 additions & 11 deletions pkg/notify/notify.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,32 +137,32 @@ func (r *Receiver) Notify(data *alertmanager.Data, hashJiraLabel bool, updateSum

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)
level.Debug(r.logger).Log("msg", "no firing alert; resolving issue", "key", issue.Key, r.conf.FieldLabels, issueGroupLabel)
retry, err := r.resolveIssue(issue.Key)
if err != nil {
return retry, err
}
return false, nil
}

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

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

if reopenTickets {
if r.conf.WontFixResolution != "" && issue.Fields.Resolution != nil &&
issue.Fields.Resolution.Name == r.conf.WontFixResolution {
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)
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)
return false, nil
}

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

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

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

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

issueType, err := r.tmpl.Execute(r.conf.IssueType, data)
if err != nil {
Expand All @@ -190,10 +190,14 @@ func (r *Receiver) Notify(data *alertmanager.Data, hashJiraLabel bool, updateSum
Type: jira.IssueType{Name: issueType},
Description: issueDesc,
Summary: issueSummary,
Labels: append(staticLabels, issueGroupLabel),
Unknowns: tcontainer.NewMarshalMap(),
},
}
if r.conf.FieldLabels == "Labels" {
issue.Fields.Labels = append(staticLabels, issueGroupLabel)
} else {
issue.Fields.Unknowns[r.conf.FieldLabelsKey] = append(staticLabels, issueGroupLabel)
}
if r.conf.Priority != "" {
issuePrio, err := r.tmpl.Execute(r.conf.Priority, data)
if err != nil {
Expand Down Expand Up @@ -312,9 +316,15 @@ func toGroupTicketLabel(groupLabels alertmanager.KV, hashJiraLabel bool) string
}

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

resolutionTime := time.Time(issue.Fields.Resolutiondate)
if resolutionTime != (time.Time{}) && resolutionTime.Add(time.Duration(*r.conf.ReopenDuration)).Before(r.timeNow()) && *r.conf.ReopenDuration != 0 {
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)
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)
return nil, false, nil
}

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

func (r *Receiver) create(issue *jira.Issue) (bool, error) {
level.Debug(r.logger).Log("msg", "create", "issue", fmt.Sprintf("%+v", *issue.Fields))
newIssue, resp, err := r.client.Create(issue)
if err != nil {
return handleJiraErrResponse("Issue.Create", resp, err, r.logger)
Expand Down
5 changes: 5 additions & 0 deletions pkg/notify/notify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ func testReceiverConfig1() *config.ReceiverConfig {
ReopenDuration: &reopen,
ReopenState: "reopened",
WontFixResolution: "won't-fix",
FieldLabels: "Labels",
}
}

Expand All @@ -180,6 +181,7 @@ func testReceiverConfig2() *config.ReceiverConfig {
ReopenState: "reopened",
Description: `{{ .Alerts.Firing | len }}`,
WontFixResolution: "won't-fix",
FieldLabels: "Labels",
}
}

Expand All @@ -193,6 +195,7 @@ func testReceiverConfigAddComments() *config.ReceiverConfig {
ReopenState: "reopened",
Description: `{{ .Alerts.Firing | len }}`,
WontFixResolution: "won't-fix",
FieldLabels: "Labels",
UpdateInComment: &updateInCommentValue,
}
}
Expand All @@ -206,6 +209,7 @@ func testReceiverConfigAutoResolve() *config.ReceiverConfig {
ReopenDuration: &reopen,
ReopenState: "reopened",
WontFixResolution: "won't-fix",
FieldLabels: "Labels",
AutoResolve: &autoResolve,
}
}
Expand All @@ -218,6 +222,7 @@ func testReceiverConfigWithStaticLabels() *config.ReceiverConfig {
ReopenDuration: &reopen,
ReopenState: "reopened",
WontFixResolution: "won't-fix",
FieldLabels: "Labels",
StaticLabels: []string{"somelabel"},
}
}
Expand Down

0 comments on commit cd931b9

Please sign in to comment.