Skip to content

Commit

Permalink
Add JIRA notification support as a new receiver
Browse files Browse the repository at this point in the history
This commit introduces a JIRA notifier for handling alert notifications. It implements creating, updating, and resolving JIRA issues via the JIRA API, supports custom templates for summary and description, handles authentication, and includes configurations for transitions and custom fields.
  • Loading branch information
yuri-tceretian committed Feb 12, 2025
1 parent 3f7d451 commit ce2e229
Show file tree
Hide file tree
Showing 6 changed files with 565 additions and 22 deletions.
4 changes: 4 additions & 0 deletions notify/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/grafana/alerting/receivers/discord"
"github.com/grafana/alerting/receivers/email"
"github.com/grafana/alerting/receivers/googlechat"
"github.com/grafana/alerting/receivers/jira"
"github.com/grafana/alerting/receivers/kafka"
"github.com/grafana/alerting/receivers/line"
"github.com/grafana/alerting/receivers/mqtt"
Expand Down Expand Up @@ -91,6 +92,9 @@ func BuildReceiverIntegrations(
for i, cfg := range receiver.GooglechatConfigs {
ci(i, cfg.Metadata, googlechat.New(cfg.Settings, cfg.Metadata, tmpl, nw(cfg.Metadata), img, nl(cfg.Metadata), version))
}
for i, cfg := range receiver.JiraConfigs {
ci(i, cfg.Metadata, jira.New(cfg.Settings, cfg.Metadata, tmpl, nw(cfg.Metadata), nl(cfg.Metadata)))
}
for i, cfg := range receiver.KafkaConfigs {
ci(i, cfg.Metadata, kafka.New(cfg.Settings, cfg.Metadata, tmpl, nw(cfg.Metadata), img, nl(cfg.Metadata)))
}
Expand Down
1 change: 1 addition & 0 deletions notify/receivers.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ type GrafanaReceiverConfig struct {
DiscordConfigs []*NotifierConfig[discord.Config]
EmailConfigs []*NotifierConfig[email.Config]
GooglechatConfigs []*NotifierConfig[googlechat.Config]
JiraConfigs []*NotifierConfig[jira.Config]
KafkaConfigs []*NotifierConfig[kafka.Config]
LineConfigs []*NotifierConfig[line.Config]
OpsgenieConfigs []*NotifierConfig[opsgenie.Config]
Expand Down
111 changes: 89 additions & 22 deletions receivers/jira/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,55 +3,122 @@ package jira
import (
"encoding/json"
"fmt"
"net/url"

"github.com/pkg/errors"
"github.com/prometheus/common/model"

"github.com/grafana/alerting/receivers"
)

var (
DefaultSummary = `{{ template "jira.default.summary" . }}`
DefaultDescription = `{{ template "jira.default.description" . }}`
DefaultPriority = `{{ template "jira.default.priority" . }}`
)

type Config struct {
URL string `yaml:"api_url,omitempty" json:"api_url,omitempty"`
URL *url.URL

Project string
Summary string
Description string
Labels []string
Priority string
IssueType string

Project string `yaml:"project,omitempty" json:"project,omitempty"`
Summary string `yaml:"summary,omitempty" json:"summary,omitempty"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Labels []string `yaml:"labels,omitempty" json:"labels,omitempty"`
Priority string `yaml:"priority,omitempty" json:"priority,omitempty"`
IssueType string `yaml:"issue_type,omitempty" json:"issue_type,omitempty"`
ReopenTransition string
ResolveTransition string
WontFixResolution string
ReopenDuration model.Duration

ReopenTransition string `yaml:"reopen_transition,omitempty" json:"reopen_transition,omitempty"`
ResolveTransition string `yaml:"resolve_transition,omitempty" json:"resolve_transition,omitempty"`
WontFixResolution string `yaml:"wont_fix_resolution,omitempty" json:"wont_fix_resolution,omitempty"`
ReopenDuration model.Duration `yaml:"reopen_duration,omitempty" json:"reopen_duration,omitempty"`
Fields map[string]any

Fields map[string]any `yaml:"fields,omitempty" json:"fields,omitempty"`
User string
Password string
}

func NewConfig(jsonData json.RawMessage) (Config, error) {
settings := Config{}
func NewConfig(jsonData json.RawMessage, decryptFn receivers.DecryptFunc) (Config, error) {
type raw struct {
URL string `yaml:"api_url,omitempty" json:"api_url,omitempty"`
Project string `yaml:"project,omitempty" json:"project,omitempty"`
Summary string `yaml:"summary,omitempty" json:"summary,omitempty"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
Labels []string `yaml:"labels,omitempty" json:"labels,omitempty"`
Priority string `yaml:"priority,omitempty" json:"priority,omitempty"`
IssueType string `yaml:"issue_type,omitempty" json:"issue_type,omitempty"`
ReopenTransition string `yaml:"reopen_transition,omitempty" json:"reopen_transition,omitempty"`
ResolveTransition string `yaml:"resolve_transition,omitempty" json:"resolve_transition,omitempty"`
WontFixResolution string `yaml:"wont_fix_resolution,omitempty" json:"wont_fix_resolution,omitempty"`
ReopenDuration string `yaml:"reopen_duration,omitempty" json:"reopen_duration,omitempty"`
Fields map[string]any `yaml:"fields,omitempty" json:"fields,omitempty"`
User string `yaml:"username,omitempty" json:"username,omitempty"`
Password string `yaml:"password,omitempty" json:"password,omitempty"`
}

settings := raw{}
err := json.Unmarshal(jsonData, &settings)
if err != nil {
return settings, fmt.Errorf("failed to unmarshal settings: %w", err)
return Config{}, fmt.Errorf("failed to unmarshal settings: %w", err)
}

if settings.URL == "" {
return settings, errors.New("could not find api_url property in settings")
return Config{}, errors.New("could not find api_url property in settings")
}
u, err := url.Parse(settings.URL)
if err != nil {
return Config{}, fmt.Errorf("field api_url is not a valid URL: %w", err)
}

var d model.Duration
if settings.ReopenDuration != "" {
d, err = model.ParseDuration(settings.ReopenDuration)
if err != nil {
return Config{}, fmt.Errorf("field reopen_duration is not a valid duration: %w", err)
}
}

if settings.Project == "" {
return settings, fmt.Errorf("missing project in jira_config")
return Config{}, fmt.Errorf("missing project in jira_config")
}
if settings.IssueType == "" {
return settings, fmt.Errorf("missing issue_type in jira_config")
return Config{}, fmt.Errorf("missing issue_type in jira_config")
}

if settings.Summary == "" {
settings.Summary = `{{ template "jira.default.summary" . }}`
settings.Summary = DefaultSummary
}
if settings.Description == "" {
settings.Description = `{{ template "jira.default.description" . }}`
settings.Description = DefaultDescription
}
if settings.Priority == "" {
settings.Priority = `{{ template "jira.default.priority" . }}`
settings.Priority = DefaultPriority
}

// https://developer.atlassian.com/cloud/jira/platform/basic-auth-for-rest-apis/#basic-auth-for-rest-apis
settings.User = decryptFn("username", settings.User)
if settings.User == "" {
return Config{}, errors.New("username not found")
}
settings.Password = decryptFn("password", settings.Password)
if settings.Password == "" {
return Config{}, errors.New("password not found")
}

return settings, nil
return Config{
URL: u,
Project: settings.Project,
Summary: settings.Summary,
Description: settings.Description,
Labels: settings.Labels,
Priority: settings.Priority,
IssueType: settings.IssueType,
ReopenTransition: settings.ReopenTransition,
ResolveTransition: settings.ResolveTransition,
WontFixResolution: settings.WontFixResolution,
ReopenDuration: d,
Fields: settings.Fields,
User: settings.User,
Password: settings.Password,
}, nil
}
Loading

0 comments on commit ce2e229

Please sign in to comment.