Skip to content
Merged
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
12 changes: 7 additions & 5 deletions pkg/alertmanager/alertmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,18 +149,20 @@ func New(cfg *Config) (*Alertmanager, error) {
}

// ApplyConfig applies a new configuration to an Alertmanager.
func (am *Alertmanager) ApplyConfig(conf *config.Config) error {
func (am *Alertmanager) ApplyConfig(userID string, conf *config.Config) error {
var (
tmpl *template.Template
pipeline notify.Stage
)

// TODO(cortex): How to support template files?
if len(conf.Templates) != 0 {
return fmt.Errorf("template files are not yet supported")
templateFiles := make([]string, len(conf.Templates), len(conf.Templates))
if len(conf.Templates) > 0 {
for i, t := range conf.Templates {
templateFiles[i] = filepath.Join(am.cfg.DataDir, "templates", userID, t)
}
}

tmpl, err := template.FromGlobs()
tmpl, err := template.FromGlobs(templateFiles...)
if err != nil {
return err
}
Expand Down
39 changes: 36 additions & 3 deletions pkg/alertmanager/multitenant.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"net"
"net/http"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
Expand Down Expand Up @@ -421,12 +422,44 @@ func (am *MultitenantAlertmanager) transformConfig(userID string, amConfig *amco
return amConfig, nil
}

func (am *MultitenantAlertmanager) createTemplatesFile(userID, fn, content string) (bool, error) {
dir := filepath.Join(am.cfg.DataDir, "templates", userID, filepath.Dir(fn))
err := os.MkdirAll(dir, 0755)
if err != nil {
return false, fmt.Errorf("unable to create Alertmanager templates directory %q: %s", dir, err)
}

file := filepath.Join(dir, fn)
// Check if the template file already exists and if it has changed
if tmpl, err := ioutil.ReadFile(file); err == nil && string(tmpl) == content {
return false, nil
}

if err := ioutil.WriteFile(file, []byte(content), 0644); err != nil {
return false, fmt.Errorf("unable to create Alertmanager template file %q: %s", file, err)
}

return true, nil
}

// setConfig applies the given configuration to the alertmanager for `userID`,
// creating an alertmanager if it doesn't already exist.
func (am *MultitenantAlertmanager) setConfig(userID string, config configs.Config) error {
_, hasExisting := am.alertmanagers[userID]
var amConfig *amconfig.Config
var err error
var hasTemplateChanges bool

for fn, content := range config.TemplateFiles {
hasChanged, err := am.createTemplatesFile(userID, fn, content)
if err != nil {
return err
}

if hasChanged {
hasTemplateChanges = true
}
}

if config.AlertmanagerConfig == "" {
if am.fallbackConfig == "" {
Expand Down Expand Up @@ -462,9 +495,9 @@ func (am *MultitenantAlertmanager) setConfig(userID string, config configs.Confi
am.alertmanagersMtx.Lock()
am.alertmanagers[userID] = newAM
am.alertmanagersMtx.Unlock()
} else if am.cfgs[userID].AlertmanagerConfig != config.AlertmanagerConfig {
} else if am.cfgs[userID].AlertmanagerConfig != config.AlertmanagerConfig || hasTemplateChanges {
// If the config changed, apply the new one.
err := am.alertmanagers[userID].ApplyConfig(amConfig)
err := am.alertmanagers[userID].ApplyConfig(userID, amConfig)
if err != nil {
return fmt.Errorf("unable to apply Alertmanager config for user %v: %v", userID, err)
}
Expand All @@ -486,7 +519,7 @@ func (am *MultitenantAlertmanager) newAlertmanager(userID string, amConfig *amco
return nil, fmt.Errorf("unable to start Alertmanager for user %v: %v", userID, err)
}

if err := newAM.ApplyConfig(amConfig); err != nil {
if err := newAM.ApplyConfig(userID, amConfig); err != nil {
return nil, fmt.Errorf("unable to apply initial config for user %v: %v", userID, err)
}
return newAM, nil
Expand Down
22 changes: 18 additions & 4 deletions pkg/configs/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"database/sql"
"encoding/json"
"fmt"
"html/template"
"io/ioutil"
"net/http"
"strconv"
Expand Down Expand Up @@ -57,6 +58,8 @@ func (a *API) RegisterRoutes(r *mux.Router) {
// be used.
{"get_rules", "GET", "/api/prom/configs/rules", a.getConfig},
{"set_rules", "POST", "/api/prom/configs/rules", a.setConfig},
{"get_templates", "GET", "/api/prom/configs/templates", a.getConfig},
{"set_templates", "POST", "/api/prom/configs/templates", a.setConfig},
{"get_alertmanager_config", "GET", "/api/prom/configs/alertmanager", a.getConfig},
{"set_alertmanager_config", "POST", "/api/prom/configs/alertmanager", a.setConfig},
{"validate_alertmanager_config", "POST", "/api/prom/configs/alertmanager/validate", a.validateAlertmanagerConfig},
Expand Down Expand Up @@ -124,6 +127,11 @@ func (a *API) setConfig(w http.ResponseWriter, r *http.Request) {
http.Error(w, fmt.Sprintf("Invalid rules: %v", err), http.StatusBadRequest)
return
}
if err := validateTemplateFiles(cfg); err != nil {
level.Error(logger).Log("msg", "invalid templates", "err", err)
http.Error(w, fmt.Sprintf("Invalid templates: %v", err), http.StatusBadRequest)
return
}
if err := a.db.SetConfig(userID, cfg); err != nil {
// XXX: Untested
level.Error(logger).Log("msg", "error storing config", "err", err)
Expand Down Expand Up @@ -162,10 +170,6 @@ func validateAlertmanagerConfig(cfg string) error {
return err
}

if len(amCfg.Templates) != 0 {
return fmt.Errorf("template files are not supported in Cortex yet")
}

for _, recv := range amCfg.Receivers {
if len(recv.EmailConfigs) != 0 {
return fmt.Errorf("email notifications are not supported in Cortex yet")
Expand All @@ -180,6 +184,16 @@ func validateRulesFiles(c configs.Config) error {
return err
}

func validateTemplateFiles(c configs.Config) error {
for fn, content := range c.TemplateFiles {
if _, err := template.New(fn).Parse(content); err != nil {
return err
}
}

return nil
}

// ConfigsView renders multiple configurations, mapping userID to configs.View.
// Exposed only for tests.
type ConfigsView struct {
Expand Down
11 changes: 0 additions & 11 deletions pkg/configs/api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,17 +233,6 @@ var amCfgValidationTests = []struct {
errContains: "yaml",
}, {
config: `
route:
receiver: noop
templates:
- "/path/to/file"

receivers:
- name: noop`,
shouldFail: true,
errContains: "template files are not supported in Cortex yet",
}, {
config: `
global:
smtp_smarthost: localhost:25
smtp_from: alertmanager@example.org
Expand Down
4 changes: 4 additions & 0 deletions pkg/configs/configs.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ func (v *RuleFormatVersion) UnmarshalJSON(data []byte) error {
type Config struct {
// RulesFiles maps from a rules filename to file contents.
RulesConfig RulesConfig
TemplateFiles map[string]string
AlertmanagerConfig string
}

Expand All @@ -81,6 +82,7 @@ type Config struct {
type configCompat struct {
RulesFiles map[string]string `json:"rules_files"`
RuleFormatVersion RuleFormatVersion `json:"rule_format_version"`
TemplateFiles map[string]string `json:"template_files"`
AlertmanagerConfig string `json:"alertmanager_config"`
}

Expand All @@ -89,6 +91,7 @@ func (c Config) MarshalJSON() ([]byte, error) {
compat := &configCompat{
RulesFiles: c.RulesConfig.Files,
RuleFormatVersion: c.RulesConfig.FormatVersion,
TemplateFiles: c.TemplateFiles,
AlertmanagerConfig: c.AlertmanagerConfig,
}

Expand All @@ -106,6 +109,7 @@ func (c *Config) UnmarshalJSON(data []byte) error {
Files: compat.RulesFiles,
FormatVersion: compat.RuleFormatVersion,
},
TemplateFiles: compat.TemplateFiles,
AlertmanagerConfig: compat.AlertmanagerConfig,
}
return nil
Expand Down