Skip to content

Commit

Permalink
jiralert: Improved docs and config.
Browse files Browse the repository at this point in the history
Signed-off-by: Bartek Plotka <[email protected]>
  • Loading branch information
bwplotka committed Apr 12, 2019
1 parent efb01aa commit e04fc79
Show file tree
Hide file tree
Showing 7 changed files with 67 additions and 108 deletions.
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,19 @@ $ curl -H "Content-type: application/json" -X POST \

## Configuration

The configuration file is essentially a list of receivers matching 1-to-1 all Alertmanager receivers using JIRAlert; plus defaults (in the form of a partially defined receiver); and a pointer to the template file.
The configuration file is essentially a list of JiraAlert receivers plus defaults (in the form of a partially defined receiver); and a pointer to the template file.

Each receiver must have a unique name (matching the Alertmanager receiver name), JIRA API access fields (URL, username and password), a handful of required issue fields (such as the JIRA project and issue summary), some optional issue fields (e.g. priority) and a `fields` map for other (standard or custom) JIRA fields. Most of these may use [Go templating](https://golang.org/pkg/text/template/) to generate the actual field values based on the contents of the Alertmanager notification. The exact same data structures and functions as those defined in the [Alertmanager template reference](https://prometheus.io/docs/alerting/notifications/) are available in JIRAlert.
You can find more docs in [the configuration itself](/pkg/config/config.go)

Each receiver must have:
* a unique name (matching the Alertmanager receiver name)
* JIRA API access fields (URL, username and password),
* handful of required issue fields (such as the JIRA project and issue summary),
* some optional issue fields (e.g. priority) and a `fields` map for other (standard or custom) JIRA fields.

Most of these may use [Go templating](https://golang.org/pkg/text/template/) to generate the actual field values based on the contents of the Alertmanager notification.

The exact same data structures and functions as those defined in the [Alertmanager template reference](https://prometheus.io/docs/alerting/notifications/) are available in JIRAlert.

## Alertmanager configuration

Expand Down
23 changes: 2 additions & 21 deletions cmd/jiralert/content.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,6 @@ const (
<h2>Configuration</h2>
<pre>{{ .Config }}</pre>
{{- end }}
{{ define "content.error" -}}
<h2>Error</h2>
<pre>{{ .Err }}</pre>
{{- end }}
`
)

Expand All @@ -66,16 +61,12 @@ type tdata struct {

// `/config` only
Config string

// `/error` only
Err error
}

var (
allTemplates = template.Must(template.New("").Parse(templates))
homeTemplate = pageTemplate("home")
configTemplate = pageTemplate("config")
// errorTemplate = pageTemplate("error")
)

func pageTemplate(name string) *template.Template {
Expand All @@ -86,7 +77,7 @@ func pageTemplate(name string) *template.Template {
// HomeHandlerFunc is the HTTP handler for the home page (`/`).
func HomeHandlerFunc() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
homeTemplate.Execute(w, &tdata{
_ = homeTemplate.Execute(w, &tdata{
DocsUrl: docsUrl,
})
}
Expand All @@ -95,19 +86,9 @@ func HomeHandlerFunc() func(http.ResponseWriter, *http.Request) {
// ConfigHandlerFunc is the HTTP handler for the `/config` page. It outputs the configuration marshaled in YAML format.
func ConfigHandlerFunc(config *config.Config) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
configTemplate.Execute(w, &tdata{
_ = configTemplate.Execute(w, &tdata{
DocsUrl: docsUrl,
Config: config.String(),
})
}
}

// HandleError is an error handler that other handlers defer to in case of error. It is important to not have written
// anything to w before calling HandleError(), or the 500 status code won't be set (and the content might be mixed up).
//func HandleError(err error, metricsPath string, w http.ResponseWriter, r *http.Request) {
// w.WriteHeader(http.StatusInternalServerError)
// errorTemplate.Execute(w, &tdata{
// DocsUrl: docsUrl,
// Err: err,
// })
//}
14 changes: 7 additions & 7 deletions cmd/jiralert/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,19 +47,19 @@ func main() {

log.Infof("Starting JIRAlert version %s", Version)

config, _, err := config.LoadFile(*configFile)
cfg, _, err := config.LoadFile(*configFile)
if err != nil {
log.Fatalf("Error loading configuration: %s", err)
}

tmpl, err := template.LoadTemplate(config.Template)
tmpl, err := template.LoadTemplate(cfg.Template)
if err != nil {
log.Fatalf("Error loading templates from %s: %s", config.Template, err)
log.Fatalf("Error loading templates from %s: %s", cfg.Template, err)
}

http.HandleFunc("/alert", func(w http.ResponseWriter, req *http.Request) {
log.V(1).Infof("Handling /alert webhook request")
defer req.Body.Close()
defer func() { _ = req.Body.Close() }()

// https://godoc.org/github.com/prometheus/alertmanager/template#Data
data := alertmanager.Data{}
Expand All @@ -68,7 +68,7 @@ func main() {
return
}

conf := config.ReceiverByName(data.Receiver)
conf := cfg.ReceiverByName(data.Receiver)
if conf == nil {
errorHandler(w, http.StatusNotFound, fmt.Errorf("receiver missing: %s", data.Receiver), unknownReceiver, &data)
return
Expand All @@ -78,7 +78,7 @@ func main() {
// Filter out resolved alerts, not interested in them.
alerts := data.Alerts.Firing()
if len(alerts) < len(data.Alerts) {
log.Warningf("Please set \"send_resolved: false\" on receiver %s in the Alertmanager config", conf.Name)
log.Warningf("Please set \"send_resolved: false\" on receiver %s in the Alertmanager cfg", conf.Name)
data.Alerts = alerts
}

Expand All @@ -104,7 +104,7 @@ func main() {
})

http.HandleFunc("/", HomeHandlerFunc())
http.HandleFunc("/config", ConfigHandlerFunc(config))
http.HandleFunc("/cfg", ConfigHandlerFunc(cfg))
http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { http.Error(w, "OK", http.StatusOK) })
http.Handle("/metrics", promhttp.Handler())

Expand Down
10 changes: 0 additions & 10 deletions examples/jiralert.tmpl

This file was deleted.

51 changes: 0 additions & 51 deletions examples/jiralert.yml

This file was deleted.

47 changes: 35 additions & 12 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ func Load(s string) (*Config, error) {
// LoadFile parses the given YAML file into a Config.
func LoadFile(filename string) (*Config, []byte, error) {
log.V(1).Infof("Loading configuration from %q", filename)

content, err := ioutil.ReadFile(filename)
if err != nil {
return nil, nil, err
Expand Down Expand Up @@ -74,31 +75,52 @@ func resolveFilepaths(baseDir string, cfg *Config) {
cfg.Template = join(cfg.Template)
}

// ReceiverConfig is the configuration for one receiver. It has a unique name and includes API access fields (URL, user
// and password) and issue fields (required -- e.g. project, issue type -- and optional -- e.g. priority).
// ReceiverConfig is the configuration for one receiver.
type ReceiverConfig struct {
// Name represents unique name for a receiver.
// If Iiralert is used with Alertmanager, name it as Alertmanager receiver that sends alert via webhook to Jiralert for
// desired propagation.
Name string `yaml:"name" json:"name"`

// API access fields
// APIURL specifies API URL for JIRA e.g https://<your org>.atlassian.net.
// Required.
APIURL string `yaml:"api_url" json:"api_url"`
// User specifies user to pass in basicauth against JIRA.
User string `yaml:"user" json:"user"`
// Password specifies password in baiscauth against JIRA.
Password Secret `yaml:"password" json:"password"`

// Required issue fields
// Required issue fields.

// Projects specifies in what project within org to create/reopen JIRA issues.
Project string `yaml:"project" json:"project"`
// IssueType specifies what type of the issue to use for new JIRA issues.
IssueType string `yaml:"issue_type" json:"issue_type"`
// Summary specifies Golang template invocation for generating the issue summary.
Summary string `yaml:"summary" json:"summary"`
// ReopenState specifies the state to transition into when reopening a closed issue.
// This state has to exists for the chosen project.
ReopenState string `yaml:"reopen_state" json:"reopen_state"`
// ReopenDuration specifies the time after being closed that an issue should be reopened, after which,
// a new issue is created instead. Set to large value if you always want to reopen.
ReopenDuration *Duration `yaml:"reopen_duration" json:"reopen_duration"`

// Optional issue fields
// Optional issue fields.

// Priority represents issue priority.
Priority string `yaml:"priority" json:"priority"`
// Description specifies Golang template invocation for generating the issue description.
Description string `yaml:"description" json:"description"`
// WontFixResolution specifies to not reopen issues with this resolution. Useful for silencing alerts.
WontFixResolution string `yaml:"wont_fix_resolution" json:"wont_fix_resolution"`
// Fields specifies standard or custom field values to set on created issue.
//
// See https://developer.atlassian.com/server/jira/platform/jira-rest-api-examples/#setting-custom-field-data-for-other-field-types for further examples.
Fields map[string]interface{} `yaml:"fields" json:"fields"`
// Components specifies issue components. Sometimes required, depending on JIRA project.
Components []string `yaml:"components" json:"components"`
ReopenDuration *Duration `yaml:"reopen_duration" json:"reopen_duration"`

// Label copy settings
// AddGroupLabels specifies if all Prometheus labels should be copied into separate JIRA labels.
// Default: false.
AddGroupLabels bool `yaml:"add_group_labels" json:"add_group_labels"`

// Catches all undefined fields and must be empty after parsing.
Expand All @@ -123,8 +145,13 @@ func (rc *ReceiverConfig) UnmarshalYAML(unmarshal func(interface{}) error) error

// Config is the top-level configuration for JIRAlert's config file.
type Config struct {
// Default specifies default values to be used in place of any ReceiverConfig' empty field.
Defaults *ReceiverConfig `yaml:"defaults,omitempty" json:"defaults,omitempty"`

// Receivers contains configuration per each receiver.
Receivers []*ReceiverConfig `yaml:"receivers,omitempty" json:"receivers,omitempty"`

// Template specifies an optional file with template definitions.
Template string `yaml:"template" json:"template"`

// Catches all undefined fields and must be empty after parsing.
Expand Down Expand Up @@ -232,10 +259,6 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
return fmt.Errorf("no receivers defined")
}

if c.Template == "" {
return fmt.Errorf("missing template file")
}

return checkOverflow(c.XXX, "config")
}

Expand Down
16 changes: 11 additions & 5 deletions pkg/template/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,19 @@ var funcs = template.FuncMap{
}

// LoadTemplate reads and parses all templates defined in the given file and constructs a jiralert.Template.
// If file is empty, or no file is passed, empty file template is returned.
func LoadTemplate(path string) (*Template, error) {
log.V(1).Infof("Loading templates from %q", path)
tmpl, err := template.New("").Option("missingkey=zero").Funcs(funcs).ParseFiles(path)
if err != nil {
return nil, err
if len(path) > 0 {
log.V(1).Infof("Loading templates from %q", path)
tmpl, err := template.New("").Option("missingkey=zero").Funcs(funcs).ParseFiles(path)
if err != nil {
return nil, err
}
return &Template{tmpl: tmpl}, nil
}
return &Template{tmpl: tmpl}, nil

log.V(1).Info("No template was passed, creating empty template")
return &Template{tmpl: template.New("")}, nil
}

func (t *Template) Err() error {
Expand Down

0 comments on commit e04fc79

Please sign in to comment.