alertmanager/client/template_client.go (186 lines of code) (raw):
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package client
import (
"fmt"
"reflect"
"sort"
"strings"
"unsafe"
"text/template"
"github.com/facebookincubator/prometheus-configmanager/fsclient"
"github.com/facebookincubator/prometheus-configmanager/prometheus/alert"
"github.com/thoas/go-funk"
)
const TemplateFilePostfix = ".tmpl"
// TemplateClient interface provides methods for modifying template files
// and individual templates within them
type TemplateClient interface {
GetTemplateFile(filename string) (string, error)
CreateTemplateFile(filename, fileText string) error
EditTemplateFile(filename, fileText string) error
DeleteTemplateFile(filename string) error
GetTemplates(filename string) (map[string]string, error)
GetTemplate(filename, tmplName string) (string, error)
AddTemplate(filename, tmplName, tmplText string) error
EditTemplate(filename, tmplName, tmplText string) error
DeleteTemplate(filename, tmplName string) error
Root() string
}
func NewTemplateClient(fsClient fsclient.FSClient, fileLocks *alert.FileLocker) TemplateClient {
return &templateClient{
fsClient: fsClient,
fileLocks: fileLocks,
}
}
type templateClient struct {
fsClient fsclient.FSClient
fileLocks *alert.FileLocker
}
func (t *templateClient) GetTemplateFile(filename string) (string, error) {
t.fileLocks.RLock(filename)
defer t.fileLocks.RUnlock(filename)
file, err := t.fsClient.ReadFile(addFilePostfix(filename))
if err != nil {
return "", err
}
return string(file), nil
}
func (t *templateClient) CreateTemplateFile(filename, fileText string) error {
t.fileLocks.Lock(filename)
defer t.fileLocks.Unlock(filename)
return t.fsClient.WriteFile(addFilePostfix(filename), []byte(fileText), 0660)
}
func (t *templateClient) EditTemplateFile(filename, fileText string) error {
t.fileLocks.Lock(filename)
defer t.fileLocks.Unlock(filename)
return t.fsClient.WriteFile(addFilePostfix(filename), []byte(fileText), 0660)
}
func (t *templateClient) DeleteTemplateFile(filename string) error {
t.fileLocks.Lock(filename)
defer t.fileLocks.Unlock(filename)
return t.fsClient.DeleteFile(addFilePostfix(filename))
}
func (t *templateClient) GetTemplates(filename string) (map[string]string, error) {
t.fileLocks.RLock(filename)
defer t.fileLocks.RUnlock(filename)
tmpl, err := t.readTmplFile(filename)
if err != nil {
return nil, err
}
tmplMap := getTemplatesByName(tmpl)
tmplTextMap := make(map[string]string, len(tmplMap))
for key, t := range tmplMap {
// Don't include template for entire file
if t.Name() == t.ParseName {
continue
}
tmplTextMap[key] = writeTemplateText(t)
}
return tmplTextMap, nil
}
func (t *templateClient) GetTemplate(filename, tmplName string) (string, error) {
t.fileLocks.RLock(filename)
defer t.fileLocks.RUnlock(filename)
tmplFile, err := t.readTmplFile(filename)
if err != nil {
return "", err
}
tmplMap := getTemplatesByName(tmplFile)
tmpl := tmplMap[tmplName]
if tmpl == nil {
return "", fmt.Errorf("template %s not found", tmplName)
}
return writeTemplateText(tmpl), nil
}
func (t *templateClient) AddTemplate(filename, tmplName, tmplText string) error {
t.fileLocks.Lock(filename)
defer t.fileLocks.Unlock(filename)
tmplFile, err := t.readTmplFile(filename)
if err != nil {
return err
}
tmplMap := getTemplatesByName(tmplFile)
if tmplMap[tmplName] != nil {
return fmt.Errorf("template %s already exists", tmplName)
}
newTmpl := &template.Template{}
newTmplBody, err := newTmpl.Parse(tmplText)
if err != nil {
return fmt.Errorf("error parsing template: %v", err)
}
tmplMap[tmplName] = newTmplBody
return t.writeTmplFile(filename, writeTmplMapText(tmplMap))
}
func (t *templateClient) EditTemplate(filename, tmplName, tmplText string) error {
t.fileLocks.Lock(filename)
defer t.fileLocks.Unlock(filename)
tmplFile, err := t.readTmplFile(filename)
if err != nil {
return err
}
tmplMap := getTemplatesByName(tmplFile)
if tmplMap[tmplName] == nil {
return fmt.Errorf("template %s does not exist", tmplName)
}
parseTmpl := &template.Template{}
newTmpl, err := parseTmpl.Parse(tmplText)
if err != nil {
return fmt.Errorf("error adding template: %v", err)
}
tmplMap[tmplName] = newTmpl
return t.writeTmplFile(filename, writeTmplMapText(tmplMap))
}
func (t *templateClient) DeleteTemplate(filename, tmplName string) error {
t.fileLocks.Lock(filename)
defer t.fileLocks.Unlock(filename)
tmplFile, err := t.readTmplFile(filename)
if err != nil {
return err
}
tmplMap := getTemplatesByName(tmplFile)
if tmplMap[tmplName] == nil {
return fmt.Errorf("template %s does not exist", tmplName)
}
delete(tmplMap, tmplName)
return t.writeTmplFile(filename, writeTmplMapText(tmplMap))
}
func (t *templateClient) Root() string {
return t.fsClient.Root()
}
func (t *templateClient) writeTmplFile(filename, text string) error {
err := t.fsClient.WriteFile(addFilePostfix(filename), []byte(text), 0660)
if err != nil {
return fmt.Errorf("error writing template file: %v", err)
}
return nil
}
func (t *templateClient) readTmplFile(filename string) (*template.Template, error) {
tmplFile, err := template.ParseFiles(t.Root() + addFilePostfix(filename))
if err != nil {
return nil, fmt.Errorf("error parsing template files: %v", err)
}
return tmplFile, nil
}
func addFilePostfix(filename string) string {
return filename + TemplateFilePostfix
}
func writeTemplateText(tmpl *template.Template) string {
return tmpl.Root.String()
}
func writeTmplMapText(tmplMap map[string]*template.Template) string {
str := strings.Builder{}
// Sort names for consistency
names := funk.Keys(tmplMap).([]string)
sort.Strings(names)
for _, name := range names {
tmpl := tmplMap[name]
if name == tmpl.Tree.ParseName {
continue
}
str.WriteString(defineTemplate(name, tmpl.Root.String()))
str.WriteRune('\n')
}
return str.String()
}
func defineTemplate(tmplName, tmplText string) string {
return fmt.Sprintf(`{{ define "%s" }}%s{{ end }}`, tmplName, tmplText)
}
func getTemplatesByName(tmpl *template.Template) map[string]*template.Template {
field := reflect.ValueOf(tmpl).Elem().FieldByName("tmpl")
return reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem().Interface().(map[string]*template.Template)
}