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

feat(conf): add optional field_labels to override Labels #188

Closed
wants to merge 2 commits into from
Closed
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
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
62 changes: 62 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,30 @@ func (c Config) String() string {
return string(b)
}

// GetJiraFieldKey returns the jira key associated to a field.
func GetJiraFieldKey(client *jira.Client, project string, issueType string, field string) (string, error) {
options := &jira.GetQueryOptions{
ProjectKeys: project,
Expand: "projects.issuetypes.fields",
}
meta, _, err := client.Issue.GetCreateMetaWithOptions(options)
if err != nil {
return "", err
}
it := meta.Projects[0].GetIssueTypeWithName(issueType)
if it == nil {
return "", fmt.Errorf("jira: Issue type %s not found", issueType)
}
fields, err := it.GetAllFields()
if err != nil {
return "", err
}
if val, ok := fields[field]; ok {
return val, nil
}
return "", fmt.Errorf("jira: Field %s not found in %s issue type", field, issueType)
}

// 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 +324,41 @@ 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"
}
}

// 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
}

rc.FieldLabelsKey, err = GetJiraFieldKey(client, rc.Project, rc.IssueType, rc.FieldLabels)
if err != nil {
return err
}
fmt.Printf("\n\nrc.FieldLabelsKey=%s\n", rc.FieldLabels)
fmt.Printf("rc.FieldLabelsKey=%s\n", rc.FieldLabelsKey)

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
31 changes: 21 additions & 10 deletions pkg/notify/notify.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,32 +137,32 @@

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 @@
}

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 @@
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 (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 @@

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 @@ -424,6 +434,7 @@

func (r *Receiver) create(issue *jira.Issue) (bool, error) {
level.Debug(r.logger).Log("msg", "create", "issue", fmt.Sprintf("%+v", *issue.Fields))
level.Debug(r.logger).Log("msg", "create", "issue", fmt.Sprintf("%s", *&issue.Fields.Labels))

Check failure on line 437 in pkg/notify/notify.go

View workflow job for this annotation

GitHub Actions / test

SA4001: *&x will be simplified to x. It will not copy x. (staticcheck)
newIssue, resp, err := r.client.Create(issue)
if err != nil {
return handleJiraErrResponse("Issue.Create", resp, err, r.logger)
Expand Down
Loading