alertmanager/client/client.go (327 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 ( "bytes" "fmt" "io/ioutil" "net/http" "strings" "sync" "github.com/facebookincubator/prometheus-configmanager/fsclient" "github.com/facebookincubator/prometheus-configmanager/prometheus/alert" "github.com/facebookincubator/prometheus-configmanager/alertmanager/config" "gopkg.in/yaml.v2" ) type AlertmanagerClient interface { CreateReceiver(tenantID string, rec config.Receiver) error GetReceivers(tenantID string) ([]config.Receiver, error) UpdateReceiver(tenantID, receiverName string, newRec *config.Receiver) error DeleteReceiver(tenantID, receiverName string) error // ModifyNetworkRoute updates an existing routing tree for the given // tenant, or creates one if it already exists. Ensures that the base // route matches all alerts with label "tenantID" = <tenantID>. ModifyTenantRoute(tenantID string, route *config.Route) error // GetRoute returns the routing tree for the given tenantID GetRoute(tenantID string) (*config.Route, error) // GetTenants returns a list of tenants configured in the system GetTenants() ([]string, error) GetGlobalConfig() (*config.GlobalConfig, error) SetGlobalConfig(globalConfig config.GlobalConfig) error GetTemplateFileList() ([]string, error) AddTemplateFile(path string) error RemoveTemplateFile(path string) error // ReloadAlertmanager triggers the alertmanager process to reload the // configuration file(s) ReloadAlertmanager() error Tenancy() *alert.TenancyConfig } type ClientConfig struct { ConfigPath string AlertmanagerURL string FsClient fsclient.FSClient Tenancy *alert.TenancyConfig DeleteRoutes bool } // Client provides methods to create and read receiver configurations type client struct { conf ClientConfig sync.RWMutex } func NewClient(conf ClientConfig) AlertmanagerClient { return &client{ conf: ClientConfig{ ConfigPath: conf.ConfigPath, AlertmanagerURL: conf.AlertmanagerURL, FsClient: conf.FsClient, Tenancy: conf.Tenancy, DeleteRoutes: conf.DeleteRoutes, }, } } // CreateReceiver writes a new receiver to the config file with the tenantID // prepended to the name so multiple tenants can be supported func (c *client) CreateReceiver(tenantID string, rec config.Receiver) error { c.Lock() defer c.Unlock() conf, err := c.readConfigFile() if err != nil { return err } rec.Secure(tenantID) conf.Receivers = append(conf.Receivers, &rec) err = conf.Validate() if err != nil { return err } return c.writeConfigFile(conf) } // GetReceivers returns the receiver configs for the given tenantID func (c *client) GetReceivers(tenantID string) ([]config.Receiver, error) { c.RLock() defer c.RUnlock() conf, err := c.readConfigFile() if err != nil { return []config.Receiver{}, nil } recs := make([]config.Receiver, 0) for _, rec := range conf.Receivers { if strings.HasPrefix(rec.Name, config.ReceiverTenantPrefix(tenantID)) { if rec.Name == config.ReceiverTenantPrefix(tenantID)+config.TenantBaseRoutePostfix { continue } rec.Unsecure(tenantID) recs = append(recs, *rec) } } return recs, nil } // UpdateReceiver modifies an existing receiver func (c *client) UpdateReceiver(tenantID, receiverName string, newRec *config.Receiver) error { c.Lock() defer c.Unlock() conf, err := c.readConfigFile() if err != nil { return err } newRec.Secure(tenantID) receiverToUpdate := config.SecureReceiverName(receiverName, tenantID) receiverIdx := -1 for idx, rec := range conf.Receivers { if rec.Name == receiverToUpdate { receiverIdx = idx break } } if receiverIdx < 0 { return fmt.Errorf("Receiver '%s' not found", newRec.Name) } conf.Receivers[receiverIdx] = newRec err = conf.Validate() if err != nil { return fmt.Errorf("Error updating receiver: %v", err) } return c.writeConfigFile(conf) } // DeleteReceiver removes a receiver from the configuration func (c *client) DeleteReceiver(tenantID, receiverName string) error { c.Lock() defer c.Unlock() conf, err := c.readConfigFile() if err != nil { return err } receiverToDelete := config.SecureReceiverName(receiverName, tenantID) foundReceiver := false for idx, rec := range conf.Receivers { if rec.Name == receiverToDelete { conf.Receivers = append(conf.Receivers[:idx], conf.Receivers[idx+1:]...) foundReceiver = true break } } if !foundReceiver { return fmt.Errorf("receiver '%s' does not exist", receiverName) } if c.conf.DeleteRoutes { conf.RemoveReceiverFromRoute(receiverToDelete) } else { if conf.SearchRoutesForReceiver(receiverToDelete) { return fmt.Errorf("reciever '%s' referenced in route. Update routing tree and remove references before deleting this receiver", receiverName) } } return c.writeConfigFile(conf) } // ModifyTenantRoute takes a new route for a tenant and replaces the old one, // ensuring that receivers are properly named and the resulting config is valid. // Creates a new one if it doesn't already exist. If single-tenant client this // just modifies the entire routing tree func (c *client) ModifyTenantRoute(tenantID string, route *config.Route) error { c.Lock() defer c.Unlock() conf, err := c.readConfigFile() if err != nil { return err } // ensure base route is valid base route for this tenant baseRoute := c.getBaseRouteForTenant(tenantID, conf) if route.Receiver != baseRoute.Receiver { return fmt.Errorf("route base receiver is incorrect (should be \"%s\"). "+ "The base node should match nothing, then add routes as children of the base node", baseRoute.Receiver) } if route.Match == nil { route.Match = map[string]string{} } if c.conf.Tenancy.RestrictorLabel != "" { route.Match[c.conf.Tenancy.RestrictorLabel] = tenantID } for _, childRoute := range route.Routes { if childRoute == nil { continue } secureRoute(tenantID, childRoute) } tenantRouteIdx := conf.GetRouteIdx(config.MakeBaseRouteName(tenantID)) if tenantRouteIdx < 0 { err := conf.InitializeNetworkBaseRoute(route, c.conf.Tenancy.RestrictorLabel, tenantID) if err != nil { return err } } else { conf.Route.Routes[tenantRouteIdx] = route } err = conf.Validate() if err != nil { return err } return c.writeConfigFile(conf) } // GetRoute returns the base route for the given tenantID func (c *client) GetRoute(tenantID string) (*config.Route, error) { c.RLock() defer c.RUnlock() conf, err := c.readConfigFile() if err != nil { return &config.Route{}, err } routeIdx := conf.GetRouteIdx(config.MakeBaseRouteName(tenantID)) if routeIdx >= 0 { route := conf.Route.Routes[routeIdx] unsecureRoute(tenantID, route) return route, nil } return nil, fmt.Errorf("Route for tenant %s does not exist", tenantID) } func (c *client) GetTenants() ([]string, error) { c.RLock() defer c.RUnlock() conf, err := c.readConfigFile() if err != nil { return []string{}, err } tenants := make([]string, 0) for _, rec := range conf.Receivers { if strings.Contains(rec.Name, config.TenantBaseRoutePostfix) { tenants = append(tenants, rec.Name[0:strings.Index(rec.Name, config.TenantBaseRoutePostfix)-1]) } } return tenants, nil } func (c *client) GetTemplateFileList() ([]string, error) { c.RLock() defer c.RUnlock() conf, err := c.readConfigFile() if err != nil { return []string{}, err } return conf.Templates, nil } func (c *client) AddTemplateFile(path string) error { c.Lock() defer c.Unlock() conf, err := c.readConfigFile() if err != nil { return err } conf.Templates = append(conf.Templates, path) return c.writeConfigFile(conf) } func (c *client) RemoveTemplateFile(path string) error { c.Lock() defer c.Unlock() conf, err := c.readConfigFile() if err != nil { return err } tmplIdx := -1 for idx, tmpl := range conf.Templates { if tmpl == path { tmplIdx = idx break } } if tmplIdx == -1 { return fmt.Errorf("path not found: %s", path) } // Remove element from template list conf.Templates = append(conf.Templates[:tmplIdx], conf.Templates[tmplIdx+1:]...) return c.writeConfigFile(conf) } func (c *client) ReloadAlertmanager() error { resp, err := http.Post(fmt.Sprintf("http://%s%s", c.conf.AlertmanagerURL, "/-/reload"), "text/plain", &bytes.Buffer{}) if err != nil { return fmt.Errorf("error reloading alertmanager: %v", err) } if resp.StatusCode != http.StatusOK { msg, _ := ioutil.ReadAll(resp.Body) return fmt.Errorf("code: %d error reloading alertmanager: %s", resp.StatusCode, msg) } return nil } func (c *client) GetGlobalConfig() (*config.GlobalConfig, error) { c.Lock() defer c.Unlock() conf, err := c.readConfigFile() if err != nil { return nil, err } return conf.Global, nil } func (c *client) SetGlobalConfig(globalConfig config.GlobalConfig) error { c.Lock() defer c.Unlock() conf, err := c.readConfigFile() if err != nil { return err } conf.Global = &globalConfig err = conf.Validate() if err != nil { return err } return c.writeConfigFile(conf) } func (c *client) Tenancy() *alert.TenancyConfig { return c.conf.Tenancy } func (c *client) readConfigFile() (*config.Config, error) { configFile := config.Config{} file, err := c.conf.FsClient.ReadFile(c.conf.ConfigPath) if err != nil { return nil, fmt.Errorf("error reading config files: %v", err) } err = yaml.Unmarshal(file, &configFile) return &configFile, err } func (c *client) writeConfigFile(conf *config.Config) error { yamlFile, err := yaml.Marshal(conf) if err != nil { return fmt.Errorf("error marshaling config file: %v", err) } err = c.conf.FsClient.WriteFile(c.conf.ConfigPath, yamlFile, 0660) if err != nil { return fmt.Errorf("error writing config file: %v", err) } return nil } // secureRoute ensure that all receivers in the route have the // proper tenantID-prefixed receiver name func secureRoute(tenantID string, route *config.Route) { route.Receiver = config.SecureReceiverName(route.Receiver, tenantID) for _, childRoute := range route.Routes { secureRoute(tenantID, childRoute) } } // unsecureRoute traverses a routing tree and reverts receiver // names to their non-prefixed original names func unsecureRoute(tenantID string, route *config.Route) { if !strings.HasSuffix(route.Receiver, config.TenantBaseRoutePostfix) { route.Receiver = config.UnsecureReceiverName(route.Receiver, tenantID) } for _, childRoute := range route.Routes { unsecureRoute(tenantID, childRoute) } } func (c *client) getBaseRouteForTenant(tenantID string, conf *config.Config) *config.Route { baseRouteName := config.MakeBaseRouteName(tenantID) for _, route := range conf.Route.Routes { if route.Receiver == baseRouteName { return route } } return &config.Route{Receiver: config.MakeBaseRouteName(tenantID)} }