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