1package local
2
3import (
4	"context"
5	"flag"
6	"io/ioutil"
7	"os"
8	"path/filepath"
9	"strings"
10
11	"github.com/pkg/errors"
12	"github.com/prometheus/alertmanager/config"
13
14	"github.com/cortexproject/cortex/pkg/alertmanager/alertspb"
15)
16
17const (
18	Name = "local"
19)
20
21var (
22	errReadOnly = errors.New("local alertmanager config storage is read-only")
23	errState    = errors.New("local alertmanager storage does not support state persistency")
24)
25
26// StoreConfig configures a static file alertmanager store
27type StoreConfig struct {
28	Path string `yaml:"path"`
29}
30
31// RegisterFlags registers flags related to the alertmanager local storage.
32func (cfg *StoreConfig) RegisterFlagsWithPrefix(prefix string, f *flag.FlagSet) {
33	f.StringVar(&cfg.Path, prefix+"local.path", "", "Path at which alertmanager configurations are stored.")
34}
35
36// Store is used to load user alertmanager configs from a local disk
37type Store struct {
38	cfg StoreConfig
39}
40
41// NewStore returns a new file alert store.
42func NewStore(cfg StoreConfig) (*Store, error) {
43	return &Store{cfg}, nil
44}
45
46// ListAllUsers implements alertstore.AlertStore.
47func (f *Store) ListAllUsers(_ context.Context) ([]string, error) {
48	configs, err := f.reloadConfigs()
49	if err != nil {
50		return nil, err
51	}
52
53	userIDs := make([]string, 0, len(configs))
54	for userID := range configs {
55		userIDs = append(userIDs, userID)
56	}
57
58	return userIDs, nil
59}
60
61// GetAlertConfigs implements alertstore.AlertStore.
62func (f *Store) GetAlertConfigs(_ context.Context, userIDs []string) (map[string]alertspb.AlertConfigDesc, error) {
63	configs, err := f.reloadConfigs()
64	if err != nil {
65		return nil, err
66	}
67
68	filtered := make(map[string]alertspb.AlertConfigDesc, len(userIDs))
69	for _, userID := range userIDs {
70		if cfg, ok := configs[userID]; ok {
71			filtered[userID] = cfg
72		}
73	}
74
75	return filtered, nil
76}
77
78// GetAlertConfig implements alertstore.AlertStore.
79func (f *Store) GetAlertConfig(_ context.Context, user string) (alertspb.AlertConfigDesc, error) {
80	cfgs, err := f.reloadConfigs()
81	if err != nil {
82		return alertspb.AlertConfigDesc{}, err
83	}
84
85	cfg, exists := cfgs[user]
86
87	if !exists {
88		return alertspb.AlertConfigDesc{}, alertspb.ErrNotFound
89	}
90
91	return cfg, nil
92}
93
94// SetAlertConfig implements alertstore.AlertStore.
95func (f *Store) SetAlertConfig(_ context.Context, cfg alertspb.AlertConfigDesc) error {
96	return errReadOnly
97}
98
99// DeleteAlertConfig implements alertstore.AlertStore.
100func (f *Store) DeleteAlertConfig(_ context.Context, user string) error {
101	return errReadOnly
102}
103
104// ListUsersWithFullState implements alertstore.AlertStore.
105func (f *Store) ListUsersWithFullState(ctx context.Context) ([]string, error) {
106	return nil, errState
107}
108
109// GetFullState implements alertstore.AlertStore.
110func (f *Store) GetFullState(ctx context.Context, user string) (alertspb.FullStateDesc, error) {
111	return alertspb.FullStateDesc{}, errState
112}
113
114// SetFullState implements alertstore.AlertStore.
115func (f *Store) SetFullState(ctx context.Context, user string, cfg alertspb.FullStateDesc) error {
116	return errState
117}
118
119// DeleteFullState implements alertstore.AlertStore.
120func (f *Store) DeleteFullState(ctx context.Context, user string) error {
121	return errState
122}
123
124func (f *Store) reloadConfigs() (map[string]alertspb.AlertConfigDesc, error) {
125	configs := map[string]alertspb.AlertConfigDesc{}
126	err := filepath.Walk(f.cfg.Path, func(path string, info os.FileInfo, err error) error {
127		if err != nil {
128			return errors.Wrapf(err, "unable to walk file path at %s", path)
129		}
130
131		// Ignore files that are directories or not yaml files
132		ext := filepath.Ext(info.Name())
133		if info.IsDir() || (ext != ".yml" && ext != ".yaml") {
134			return nil
135		}
136
137		// Ensure the file is a valid Alertmanager Config.
138		_, err = config.LoadFile(path)
139		if err != nil {
140			return errors.Wrapf(err, "unable to load alertmanager config %s", path)
141		}
142
143		// Load the file to be returned by the store.
144		content, err := ioutil.ReadFile(path)
145		if err != nil {
146			return errors.Wrapf(err, "unable to read alertmanager config %s", path)
147		}
148
149		// The file name must correspond to the user tenant ID
150		user := strings.TrimSuffix(info.Name(), ext)
151
152		configs[user] = alertspb.AlertConfigDesc{
153			User:      user,
154			RawConfig: string(content),
155		}
156		return nil
157	})
158
159	return configs, err
160}
161