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

Add support for common labels in labelfield #143

Closed
wants to merge 5 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
30 changes: 30 additions & 0 deletions .github/workflows/docker-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Docker Image CI

on:
workflow_dispatch:
inputs:
version:
required: true
jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- name: Docker Login
# You may pin to the exact commit or the version.
# uses: docker/login-action@49ed152c8eca782a232dede0303416e8f356c37b
uses: docker/[email protected]
with:
username: ${{secrets.DOCKERHUB_USERNAME}}
password: ${{secrets.DOCKERHUB_PASSWORD}}
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
- name: Build the Docker image
run: docker build . --file Dockerfile --tag crikke95/jiralert:${{github.event.inputs.version}}
- name: Docker push
run: docker push crikke95/jiralert:${{github.event.inputs.version}}

3 changes: 2 additions & 1 deletion pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,8 @@ type ReceiverConfig struct {
Components []string `yaml:"components" json:"components"`

// Label copy settings
AddGroupLabels bool `yaml:"add_group_labels" json:"add_group_labels"`
AddGroupLabels bool `yaml:"add_group_labels" json:"add_group_labels"`
AddCommonLabels bool `yaml:"add_common_labels" json:"add_common_labels"`

// Flag to auto-resolve opened issue when the alert is resolved.
AutoResolve *AutoResolve `yaml:"auto_resolve" json:"auto_resolve"`
Expand Down
49 changes: 38 additions & 11 deletions pkg/notify/notify.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@ package notify

import (
"bytes"
"crypto/sha1"
"crypto/sha512"
"fmt"
"github.com/andygrunwald/go-jira"
"io"
"reflect"
"strings"
"time"

"github.com/andygrunwald/go-jira"

"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/pkg/errors"
Expand All @@ -32,6 +34,10 @@ import (
"github.com/trivago/tgo/tcontainer"
)

const (
jiraHashLabel = "Jiralert-checksum"
)

// TODO(bwplotka): Consider renaming this package to ticketer.

type jiraIssueService interface {
Expand Down Expand Up @@ -59,16 +65,36 @@ func NewReceiver(logger log.Logger, c *config.ReceiverConfig, t *template.Templa
return &Receiver{logger: logger, conf: c, tmpl: t, client: client, timeNow: time.Now}
}

func toChecksumTicketLabel(labels []string) string {

h := sha1.New()

for _, label := range labels {
_, _ = h.Write([]byte(label))
}

return fmt.Sprintf("%s=%x", jiraHashLabel, h.Sum(nil))
}

// Notify manages JIRA issues based on alertmanager webhook notify message.
func (r *Receiver) Notify(data *alertmanager.Data, hashJiraLabel bool) (bool, error) {
project, err := r.tmpl.Execute(r.conf.Project, data)
if err != nil {
return false, errors.Wrap(err, "generate project from template")
}

issueGroupLabel := toGroupTicketLabel(data.GroupLabels, hashJiraLabel)
labels := make([]string, 0)

if r.conf.AddCommonLabels {
for k, v := range data.CommonLabels {
labels = append(labels, fmt.Sprintf("%s=%q", k, v))
}
labels = append(labels, toChecksumTicketLabel(labels))
} else {
labels = append(labels, toGroupTicketLabel(data.GroupLabels, hashJiraLabel))
}

issue, retry, err := r.findIssueToReuse(project, issueGroupLabel)
issue, retry, err := r.findIssueToReuse(project, labels[len(labels)-1])
if err != nil {
return retry, err
}
Expand Down Expand Up @@ -103,40 +129,40 @@ func (r *Receiver) Notify(data *alertmanager.Data, hashJiraLabel bool) (bool, er

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, "label", labels)
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, "label", labels)
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, "label", labels)
return false, nil
}

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, "label", labels, "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, "label", labels)
return r.reopen(issue.Key)
}

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.", "label", labels)
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", "label", labels)

issueType, err := r.tmpl.Execute(r.conf.IssueType, data)
if err != nil {
Expand All @@ -149,7 +175,7 @@ func (r *Receiver) Notify(data *alertmanager.Data, hashJiraLabel bool) (bool, er
Type: jira.IssueType{Name: issueType},
Description: issueDesc,
Summary: issueSummary,
Labels: []string{issueGroupLabel},
Labels: labels,
Unknowns: tcontainer.NewMarshalMap(),
},
}
Expand Down Expand Up @@ -249,6 +275,7 @@ func deepCopyWithTemplate(value interface{}, tmpl *template.Template, data inter
// if the combined length of all groupLabel key-value pairs would be
// longer than 255 chars
func toGroupTicketLabel(groupLabels alertmanager.KV, hashJiraLabel bool) string {

// new opt in behavior
if hashJiraLabel {
hash := sha512.New()
Expand Down
46 changes: 46 additions & 0 deletions pkg/notify/notify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,18 @@ func testReceiverConfig2() *config.ReceiverConfig {
WontFixResolution: "won't-fix",
}
}
func testReceiverConfig3() *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 }}`,
WontFixResolution: "won't-fix",
AddCommonLabels: true,
}
}

func testReceiverConfigAutoResolve() *config.ReceiverConfig {
reopen := config.Duration(1 * time.Hour)
Expand All @@ -197,6 +209,40 @@ func TestNotify_JIRAInteraction(t *testing.T) {
initJira func(t *testing.T) *fakeJira
expectedJiraIssues map[string]*jira.Issue
}{
{
name: "empty jira, add common labels",
inputConfig: testReceiverConfig3(),
initJira: func(t *testing.T) *fakeJira { return newTestFakeJira() },
inputAlert: &alertmanager.Data{
CommonLabels: alertmanager.KV{"foo": "bar"},
Alerts: alertmanager.Alerts{
{Status: alertmanager.AlertFiring},
{Status: "not firing"},
{Status: alertmanager.AlertFiring},
},
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: testReceiverConfig1().Project},
Labels: []string{
`foo="bar"`,
"Jiralert-checksum=ab03a89430228717b59a73fe39f7b6a44aa79408",
},
Description: "2",
Status: &jira.Status{
StatusCategory: jira.StatusCategory{Key: "NotDone"},
},
Unknowns: tcontainer.MarshalMap{},
Summary: "[FIRING:2] b d ",
},
},
},
},
{
name: "empty jira, new alert group",
inputConfig: testReceiverConfig1(),
Expand Down