bosun.org@v0.0.0-20210513094433-e25bc3e69a1f/cmd/bosun/conf/system.go (about)

     1  package conf
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"net/http"
     7  	"net/http/httputil"
     8  	"net/url"
     9  	"strings"
    10  	"time"
    11  
    12  	"bosun.org/cloudwatch"
    13  	"bosun.org/slog"
    14  
    15  	"bosun.org/cmd/bosun/expr"
    16  	"bosun.org/graphite"
    17  	"bosun.org/opentsdb"
    18  	ainsightsmgmt "github.com/Azure/azure-sdk-for-go/services/appinsights/mgmt/2015-05-01/insights"
    19  	ainsights "github.com/Azure/azure-sdk-for-go/services/appinsights/v1/insights"
    20  	"github.com/influxdata/influxdb/client/v2"
    21  	promapi "github.com/prometheus/client_golang/api"
    22  	promv1 "github.com/prometheus/client_golang/api/prometheus/v1"
    23  
    24  	"github.com/Azure/azure-sdk-for-go/services/preview/monitor/mgmt/2018-03-01/insights"
    25  	"github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2018-02-01/resources"
    26  	"github.com/Azure/go-autorest/autorest"
    27  	"github.com/Azure/go-autorest/autorest/azure/auth"
    28  	"github.com/BurntSushi/toml"
    29  )
    30  
    31  // SystemConf contains all the information that bosun needs to run. Outside of the conf package
    32  // usage should be through conf.SystemConfProvider
    33  type SystemConf struct {
    34  	HTTPListen  string
    35  	HTTPSListen string
    36  	TLSCertFile string
    37  	TLSKeyFile  string
    38  
    39  	Hostname      string
    40  	Scheme        string // default http
    41  	Ping          bool
    42  	PingDuration  Duration // Duration from now to stop pinging hosts based on time since the host tag was touched
    43  	TimeAndDate   []int    // timeanddate.com cities list
    44  	SearchSince   Duration
    45  	ShortURLKey   string
    46  	InternetProxy string
    47  	MinGroupSize  int
    48  
    49  	UnknownThreshold       int
    50  	CheckFrequency         Duration // Time between alert checks: 5m
    51  	DefaultRunEvery        int      // Default number of check intervals to run each alert: 1
    52  	AlertCheckDistribution string   // Method to distribute alet checks. No distribution if equals ""
    53  
    54  	DBConf DBConf
    55  
    56  	SMTPConf SMTPConf
    57  
    58  	RuleVars map[string]string
    59  
    60  	ExampleExpression string
    61  
    62  	OpenTSDBConf     OpenTSDBConf
    63  	GraphiteConf     GraphiteConf
    64  	InfluxConf       InfluxConf
    65  	ElasticConf      map[string]ElasticConf
    66  	AzureMonitorConf map[string]AzureMonitorConf
    67  	PromConf         map[string]PromConf
    68  	CloudWatchConf   CloudWatchConf
    69  	AnnotateConf     AnnotateConf
    70  
    71  	AuthConf *AuthConf
    72  
    73  	MaxRenderedTemplateAge int // in days
    74  
    75  	EnableSave      bool
    76  	EnableReload    bool
    77  	CommandHookPath string
    78  	RuleFilePath    string
    79  	md              toml.MetaData
    80  }
    81  
    82  // EnabledBackends stores which query backends supported by bosun are enabled
    83  // via the system configuration. This is used so it can be passed to the rule parser
    84  // and the parse errors can be thrown for query functions that are used when the backend
    85  // is not enabled
    86  type EnabledBackends struct {
    87  	OpenTSDB     bool
    88  	Graphite     bool
    89  	Influx       bool
    90  	Elastic      bool
    91  	Annotate     bool
    92  	AzureMonitor bool
    93  	CloudWatch   bool
    94  	Prom         bool
    95  }
    96  
    97  // EnabledBackends returns and EnabledBackends struct which contains fields
    98  // to state if a backend is enabled in the configuration or not
    99  func (sc *SystemConf) EnabledBackends() EnabledBackends {
   100  	b := EnabledBackends{}
   101  	b.OpenTSDB = sc.OpenTSDBConf.Host != ""
   102  	b.Graphite = sc.GraphiteConf.Host != ""
   103  	b.Influx = sc.InfluxConf.URL != ""
   104  	b.Prom = sc.PromConf["default"].URL != ""
   105  	b.Elastic = len(sc.ElasticConf["default"].Hosts) != 0
   106  	b.Annotate = len(sc.AnnotateConf.Hosts) != 0
   107  	b.AzureMonitor = len(sc.AzureMonitorConf) != 0
   108  	b.CloudWatch = sc.CloudWatchConf.Enabled
   109  	return b
   110  }
   111  
   112  // OpenTSDBConf contains OpenTSDB specific configuration information. The ResponseLimit
   113  // will prevent Bosun from loading responses larger than its size in bytes. The version
   114  // enables certain features of OpenTSDB querying
   115  type OpenTSDBConf struct {
   116  	ResponseLimit int64
   117  	Host          string           // OpenTSDB relay and query destination: ny-devtsdb04:4242
   118  	Version       opentsdb.Version // If set to 2.2 , enable passthrough of wildcards and filters, and add support for groupby
   119  }
   120  
   121  // GraphiteConf contains a string representing the host of a graphite server and
   122  // a map of headers to be sent with each Graphite request
   123  type GraphiteConf struct {
   124  	Host    string
   125  	Headers map[string]string
   126  }
   127  
   128  // AnnotateConf contains the elastic configuration to enable Annotations support
   129  type AnnotateConf struct {
   130  	Hosts         []string // CSV of Elastic Hosts, currently the only backend in annotate
   131  	Version       string
   132  	SimpleClient  bool            // If true ES will connect over NewSimpleClient
   133  	ClientOptions ESClientOptions // ES client options
   134  	Index         string          // name of index / table
   135  }
   136  
   137  // ESClientOptions: elastic search client options
   138  // reference https://github.com/olivere/elastic/blob/release-branch.v3/client.go#L107
   139  type ESClientOptions struct {
   140  	Enabled                   bool          // if true use client option else ignore
   141  	BasicAuthUsername         string        // username for HTTP Basic Auth
   142  	BasicAuthPassword         string        // password for HTTP Basic Auth
   143  	Scheme                    string        // https (default http)
   144  	SnifferEnabled            bool          // sniffer enabled or disabled
   145  	SnifferTimeoutStartup     time.Duration // in seconds (default is 5 sec)
   146  	SnifferTimeout            time.Duration // in seconds (default is 2 sec)
   147  	SnifferInterval           time.Duration // in minutes (default is 15 min)
   148  	HealthcheckEnabled        bool          // healthchecks enabled or disabled
   149  	HealthcheckTimeoutStartup time.Duration // in seconds (default is 5 sec)
   150  	HealthcheckTimeout        time.Duration // in seconds (default is 1 sec)
   151  	HealthcheckInterval       time.Duration // in seconds (default is 60 sec)
   152  	MaxRetries                int           // max. number of retries before giving up (default 10)
   153  	GzipEnabled               bool          // enables or disables gzip compression (disabled by default)
   154  
   155  }
   156  
   157  // ElasticConf contains configuration for an elastic host that Bosun can query
   158  type ElasticConf AnnotateConf
   159  
   160  // AzureConf contains configuration for an Azure metrics
   161  type AzureMonitorConf struct {
   162  	SubscriptionId string
   163  	TenantId       string
   164  	ClientId       string
   165  	ClientSecret   string
   166  	Concurrency    int
   167  	DebugRequest   bool
   168  	DebugResponse  bool
   169  }
   170  
   171  // Valid returns if the configuration for the AzureMonitor has
   172  // required fields with appropriate values
   173  func (ac AzureMonitorConf) Valid() error {
   174  	present := make(map[string]bool)
   175  	missing := []string{}
   176  	errors := []string{}
   177  	present["SubscriptionId"] = ac.SubscriptionId != ""
   178  	present["TenantId"] = ac.TenantId != ""
   179  	present["ClientId"] = ac.ClientId != ""
   180  	present["ClientSecret"] = ac.ClientSecret != ""
   181  	for k, v := range present {
   182  		if !v {
   183  			missing = append(missing, k)
   184  		}
   185  	}
   186  	if len(missing) != 0 {
   187  		errors = append(errors, fmt.Sprintf("missing required fields: %v", strings.Join(missing, ", ")))
   188  	} else {
   189  		ccc := auth.NewClientCredentialsConfig(ac.ClientId, ac.ClientSecret, ac.TenantId)
   190  		_, err := ccc.Authorizer() // We don't use the value here, only checking for error
   191  		if err != nil {
   192  			errors = append(errors, fmt.Sprintf("problem creating valid authorization: %v", err.Error()))
   193  		}
   194  	}
   195  	if ac.Concurrency < 0 {
   196  		errors = append(errors, fmt.Sprintf("concurrency is %v and must be 0 or greater", ac.Concurrency))
   197  	}
   198  	if len(errors) != 0 {
   199  		return fmt.Errorf("%v", strings.Join(errors, " and "))
   200  	}
   201  	return nil
   202  }
   203  
   204  // InfluxConf contains configuration for an influx host that Bosun can query
   205  type InfluxConf struct {
   206  	URL       string
   207  	Username  string
   208  	Password  string `json:"-"`
   209  	UserAgent string
   210  	Timeout   Duration
   211  	UnsafeSSL bool
   212  	Precision string
   213  }
   214  
   215  // PromConf contains configuration for a Prometheus TSDB that Bosun can query
   216  type PromConf struct {
   217  	URL string
   218  }
   219  
   220  // Valid returns if the configuration for the PromConf has required fields needed
   221  // to create a prometheus tsdb client
   222  func (pc PromConf) Valid() error {
   223  	if pc.URL == "" {
   224  		return fmt.Errorf("missing URL field")
   225  	}
   226  	// NewClient makes sure the url is valid, no connections are made in this call
   227  	_, err := promapi.NewClient(promapi.Config{Address: pc.URL})
   228  	if err != nil {
   229  		return err
   230  	}
   231  	return nil
   232  }
   233  
   234  // DBConf stores the connection information for Bosun's internal storage
   235  type DBConf struct {
   236  	RedisHost          string
   237  	RedisDb            int
   238  	RedisPassword      string
   239  	RedisClientSetName bool
   240  	RedisSentinels     []string
   241  	RedisMasterName    string
   242  
   243  	LedisDir      string
   244  	LedisBindAddr string
   245  }
   246  
   247  // SMTPConf contains information for the mail server for which bosun will
   248  // send emails through
   249  type SMTPConf struct {
   250  	EmailFrom string
   251  	Host      string
   252  	Username  string
   253  	Password  string `json:"-"`
   254  }
   255  
   256  //AuthConf is configuration for bosun's authentication
   257  type AuthConf struct {
   258  	AuthDisabled bool
   259  	//Secret string to hash auth tokens. Needed to enable token auth.
   260  	TokenSecret string
   261  	//Secret sting used to encrypt cookie.
   262  	CookieSecret string
   263  	//LDAP configuration
   264  	LDAP LDAPConf
   265  }
   266  
   267  type LDAPConf struct {
   268  	// Domain name (used to make domain/username)
   269  	Domain string
   270  	//user base dn (LDAP Auth)
   271  	UserBaseDn string
   272  	// LDAP server
   273  	LdapAddr string
   274  	// allow insecure ldap connection?
   275  	AllowInsecure bool
   276  	// default permission level for anyone who can log in. Try "Reader".
   277  	DefaultPermission string
   278  	//List of group level permissions
   279  	Groups []LDAPGroup
   280  	//List of user specific permission levels
   281  	Users map[string]string
   282  	//Root search path for group lookups. Usually something like "DC=myorg,DC=com".
   283  	//Only needed if using group permissions
   284  	RootSearchPath string
   285  }
   286  
   287  //LDAPGroup is a Group level access specification for ldap
   288  type LDAPGroup struct {
   289  	// group search path string
   290  	Path string
   291  	// Access to grant members of group Ex: "Admin"
   292  	Role string
   293  }
   294  
   295  type CloudWatchConf struct {
   296  	Enabled        bool
   297  	ExpansionLimit int
   298  	PagesLimit     int
   299  	Concurrency    int
   300  }
   301  
   302  func (c CloudWatchConf) Valid() error {
   303  	// Check Cloudwatch Configuration
   304  	if c.PagesLimit < 1 {
   305  		return fmt.Errorf(`error in cloudwatch configuration. PagesLimit must be greater than 0`)
   306  	}
   307  
   308  	if c.ExpansionLimit < 1 {
   309  		return fmt.Errorf(`error in cloudwatch configuration. ExpansionLimit must be greater than 0`)
   310  	}
   311  	return nil
   312  }
   313  
   314  // GetSystemConfProvider returns the SystemConfProvider interface
   315  // and validates the logic of the configuration. If the configuration
   316  // is not valid an error is returned
   317  func (sc *SystemConf) GetSystemConfProvider() (SystemConfProvider, error) {
   318  	var provider SystemConfProvider = sc
   319  	if err := ValidateSystemConf(sc); err != nil {
   320  		return provider, err
   321  	}
   322  	return provider, nil
   323  }
   324  
   325  const (
   326  	defaultHTTPListen = ":8070"
   327  )
   328  
   329  // NewSystemConf retruns a system conf with default values set
   330  func newSystemConf() *SystemConf {
   331  	return &SystemConf{
   332  		Scheme:                 "http",
   333  		CheckFrequency:         Duration{Duration: time.Minute * 5},
   334  		DefaultRunEvery:        1,
   335  		HTTPListen:             defaultHTTPListen,
   336  		AlertCheckDistribution: "",
   337  		DBConf: DBConf{
   338  			LedisDir:           "ledis_data",
   339  			LedisBindAddr:      "127.0.0.1:9565",
   340  			RedisClientSetName: true,
   341  		},
   342  		MinGroupSize: 5,
   343  		PingDuration: Duration{Duration: time.Hour * 24},
   344  		OpenTSDBConf: OpenTSDBConf{
   345  			ResponseLimit: 1 << 20, // 1MB
   346  			Version:       opentsdb.Version2_1,
   347  		},
   348  		SearchSince:      Duration{time.Duration(opentsdb.Day) * 3},
   349  		UnknownThreshold: 5,
   350  	}
   351  }
   352  
   353  // LoadSystemConfigFile loads the system configuration in TOML format. It will
   354  // error if there are values in the config that were not parsed
   355  func LoadSystemConfigFile(fileName string) (*SystemConf, error) {
   356  	return loadSystemConfig(fileName, true)
   357  }
   358  
   359  // LoadSystemConfig is like LoadSystemConfigFile but loads the config from a string
   360  func LoadSystemConfig(conf string) (*SystemConf, error) {
   361  	return loadSystemConfig(conf, false)
   362  }
   363  
   364  func loadSystemConfig(conf string, isFileName bool) (*SystemConf, error) {
   365  	sc := newSystemConf()
   366  	var decodeMeta toml.MetaData
   367  	var err error
   368  	if isFileName {
   369  		decodeMeta, err = toml.DecodeFile(conf, &sc)
   370  	} else {
   371  		decodeMeta, err = toml.Decode(conf, &sc)
   372  	}
   373  	if err != nil {
   374  		return sc, err
   375  	}
   376  	if len(decodeMeta.Undecoded()) > 0 {
   377  		return sc, fmt.Errorf("undecoded fields in system configuration: %v", decodeMeta.Undecoded())
   378  	}
   379  
   380  	if sc.GetAlertCheckDistribution() != "" && sc.GetAlertCheckDistribution() != "simple" {
   381  		return sc, fmt.Errorf("invalid value %v for AlertCheckDistribution", sc.GetAlertCheckDistribution())
   382  	}
   383  
   384  	// iterate over each hosts
   385  	for hostPrefix, value := range sc.ElasticConf {
   386  		if value.SimpleClient && value.ClientOptions.Enabled {
   387  			return sc, fmt.Errorf("Can't use both ES SimpleClient and ES ClientOptions please remove or disable one in ElasticConf.%s: %#v", hostPrefix, sc.ElasticConf)
   388  		}
   389  	}
   390  
   391  	if sc.AnnotateConf.SimpleClient && sc.AnnotateConf.ClientOptions.Enabled {
   392  		return sc, fmt.Errorf("Can't use both ES SimpleClient and ES ClientOptions please remove or disable one in AnnotateConf: %#v", sc.AnnotateConf)
   393  	}
   394  
   395  	// Check Azure Monitor Configurations
   396  	for prefix, conf := range sc.AzureMonitorConf {
   397  		if err := conf.Valid(); err != nil {
   398  			return sc, fmt.Errorf(`error in configuration for Azure client "%v": %v`, prefix, err)
   399  		}
   400  	}
   401  
   402  	// Check Prometheus Monitor Configurations
   403  	for prefix, conf := range sc.PromConf {
   404  		if err := conf.Valid(); err != nil {
   405  			return sc, fmt.Errorf(`error in configuration for Prometheus client "%v": %v`, prefix, err)
   406  		}
   407  	}
   408  
   409  	sc.md = decodeMeta
   410  	// clear default http listen if not explicitly specified
   411  	if !decodeMeta.IsDefined("HTTPListen") && decodeMeta.IsDefined("HTTPSListen") {
   412  		sc.HTTPListen = ""
   413  	}
   414  	return sc, nil
   415  }
   416  
   417  // GetHTTPListen returns the hostname:port that Bosun should listen on
   418  func (sc *SystemConf) GetHTTPListen() string {
   419  	return sc.HTTPListen
   420  }
   421  
   422  // GetHTTPSListen returns the hostname:port that Bosun should listen on with tls
   423  func (sc *SystemConf) GetHTTPSListen() string {
   424  	return sc.HTTPSListen
   425  }
   426  
   427  // GetTLSCertFile returns the path to the tls certificate to listen with (pem format). Must be specified with HTTPSListen.
   428  func (sc *SystemConf) GetTLSCertFile() string {
   429  	return sc.TLSCertFile
   430  }
   431  
   432  // GetTLSKeyFile returns the path to the tls key to listen with (pem format). Must be specified with HTTPSListen.
   433  func (sc *SystemConf) GetTLSKeyFile() string {
   434  	return sc.TLSKeyFile
   435  }
   436  
   437  // GetSMTPHost returns the SMTP mail server host that Bosun will use to relay through
   438  func (sc *SystemConf) GetSMTPHost() string {
   439  	return sc.SMTPConf.Host
   440  }
   441  
   442  // GetSMTPUsername returns the SMTP username that Bosun will use to connect to the mail server
   443  func (sc *SystemConf) GetSMTPUsername() string {
   444  	return sc.SMTPConf.Username
   445  }
   446  
   447  // GetSMTPPassword returns the SMTP password that Bosun will use to connect to the mail server
   448  func (sc *SystemConf) GetSMTPPassword() string {
   449  	return sc.SMTPConf.Password
   450  }
   451  
   452  // GetEmailFrom returns the email address that Bosun will use to send mail notifications from
   453  func (sc *SystemConf) GetEmailFrom() string {
   454  	return sc.SMTPConf.EmailFrom
   455  }
   456  
   457  // GetPing returns if Bosun's pinging is enabled. When Ping is enabled, bosun will ping all hosts
   458  // that is has indexed and record metrics about those pings.
   459  func (sc *SystemConf) GetPing() bool {
   460  	return sc.Ping
   461  }
   462  
   463  // GetPingDuration returns the duration that discovered hosts (will be pinged until
   464  // the host is not seen.
   465  func (sc *SystemConf) GetPingDuration() time.Duration {
   466  	return sc.PingDuration.Duration
   467  }
   468  
   469  // GetLedisDir returns the directory where Ledis should store its files
   470  func (sc *SystemConf) GetLedisDir() string {
   471  	return sc.DBConf.LedisDir
   472  }
   473  
   474  // GetLedisBindAddr returns the address that Ledis should listen on
   475  func (sc *SystemConf) GetLedisBindAddr() string {
   476  	return sc.DBConf.LedisBindAddr
   477  }
   478  
   479  // GetRedisHost returns the host to use for Redis. If this is set than Redis
   480  // will be used instead of Ledis.
   481  func (sc *SystemConf) GetRedisHost() []string {
   482  	if sc.GetRedisMasterName() != "" {
   483  		return sc.DBConf.RedisSentinels
   484  	}
   485  	if sc.DBConf.RedisHost != "" {
   486  		return []string{sc.DBConf.RedisHost}
   487  	}
   488  	return []string{}
   489  }
   490  
   491  // GetRedisMasterName returns master name of redis instance within sentinel.
   492  // If this is return none empty string redis sentinel will be used
   493  func (sc *SystemConf) GetRedisMasterName() string {
   494  	return sc.DBConf.RedisMasterName
   495  }
   496  
   497  // GetRedisDb returns the redis database number to use
   498  func (sc *SystemConf) GetRedisDb() int {
   499  	return sc.DBConf.RedisDb
   500  }
   501  
   502  // GetRedisPassword returns the password that should be used to connect to redis
   503  func (sc *SystemConf) GetRedisPassword() string {
   504  	return sc.DBConf.RedisPassword
   505  }
   506  
   507  // RedisClientSetName returns if CLIENT SETNAME shoud send to redis.
   508  func (sc *SystemConf) IsRedisClientSetName() bool {
   509  	return sc.DBConf.RedisClientSetName
   510  }
   511  
   512  func (sc *SystemConf) GetAuthConf() *AuthConf {
   513  	return sc.AuthConf
   514  }
   515  
   516  // GetRuleVars user defined variables that will be available to the rule configuration
   517  // under "$sys.". This is so values with secrets can be defined in the system configuration
   518  func (sc *SystemConf) GetRuleVars() map[string]string {
   519  	return sc.RuleVars
   520  }
   521  
   522  // GetTimeAndDate returns the http://www.timeanddate.com/ that should be available to the UI
   523  // so it can show links to translate UTC times to various timezones. This feature is only
   524  // for creating UI Links as Bosun is expected to be running on a machine that is set to UTC
   525  func (sc *SystemConf) GetTimeAndDate() []int {
   526  	return sc.TimeAndDate
   527  }
   528  
   529  // GetSearchSince returns the duration that certain search requests should filter out results
   530  // if they are older (have not been indexed) since the duration
   531  func (sc *SystemConf) GetSearchSince() time.Duration {
   532  	return sc.SearchSince.Duration
   533  }
   534  
   535  // GetCheckFrequency returns the default CheckFrequency that the schedule should run at. Checks by
   536  // default will run at CheckFrequency * RunEvery
   537  func (sc *SystemConf) GetCheckFrequency() time.Duration {
   538  	return sc.CheckFrequency.Duration
   539  }
   540  
   541  // GetDefaultRunEvery returns the default multipler of how often an alert should run based on
   542  // the CheckFrequency. Checks by default will run at CheckFrequency * RunEvery
   543  func (sc *SystemConf) GetDefaultRunEvery() int {
   544  	return sc.DefaultRunEvery
   545  }
   546  
   547  // GetAlertCheckDistribution returns if the alert rule checks are scattered over check period
   548  func (sc *SystemConf) GetAlertCheckDistribution() string {
   549  	return sc.AlertCheckDistribution
   550  }
   551  
   552  // GetUnknownThreshold returns the threshold in which multiple unknown alerts in a check iteration
   553  // should be grouped into a single notification
   554  func (sc *SystemConf) GetUnknownThreshold() int {
   555  	return sc.UnknownThreshold
   556  }
   557  
   558  // GetMinGroupSize returns the minimum number of alerts needed to group the alerts
   559  // on Bosun's dashboard
   560  func (sc *SystemConf) GetMinGroupSize() int {
   561  	return sc.MinGroupSize
   562  }
   563  
   564  // GetShortURLKey returns the API key that should be used to generate https://goo.gl/ shortlinks
   565  // from Bosun's UI
   566  func (sc *SystemConf) GetShortURLKey() string {
   567  	return sc.ShortURLKey
   568  }
   569  
   570  // GetInternetProxy sets a proxy for outgoing network requests from Bosun. Currently it
   571  // only impacts requests made for shortlinks to https://goo.gl/
   572  func (sc *SystemConf) GetInternetProxy() string {
   573  	return sc.InternetProxy
   574  }
   575  
   576  // GetMaxRenderedTemplateAge returns the maximum time in days to keep rendered templates
   577  // after the incident end date.
   578  func (sc *SystemConf) GetMaxRenderedTemplateAge() int {
   579  	return sc.MaxRenderedTemplateAge
   580  }
   581  
   582  // SaveEnabled returns if saving via the UI and config editing API endpoints should be enabled
   583  func (sc *SystemConf) SaveEnabled() bool {
   584  	return sc.EnableSave
   585  }
   586  
   587  // ReloadEnabled returns if reloading of the rule config should be enabled. This will return
   588  // true if save is enabled but reload is not enabled.
   589  func (sc *SystemConf) ReloadEnabled() bool {
   590  	return sc.EnableSave || sc.EnableReload
   591  }
   592  
   593  // GetCommandHookPath returns the path of a command that should be run on every save
   594  func (sc *SystemConf) GetCommandHookPath() string {
   595  	return sc.CommandHookPath
   596  }
   597  
   598  // GetRuleFilePath returns the path to the file containing contains rules
   599  // rules include Alerts, Macros, Notifications, Templates, and Global Variables
   600  func (sc *SystemConf) GetRuleFilePath() string {
   601  	return sc.RuleFilePath
   602  }
   603  
   604  // SetTSDBHost sets the OpenTSDB host and used when Bosun is set to readonly mode
   605  func (sc *SystemConf) SetTSDBHost(tsdbHost string) {
   606  	sc.OpenTSDBConf.Host = tsdbHost
   607  }
   608  
   609  // GetExampleExpression returns the default expression for "Expression" tab.
   610  func (sc *SystemConf) GetExampleExpression() string {
   611  	return sc.ExampleExpression
   612  }
   613  
   614  // GetTSDBHost returns the configured TSDBHost
   615  func (sc *SystemConf) GetTSDBHost() string {
   616  	return sc.OpenTSDBConf.Host
   617  }
   618  
   619  // GetAnnotateElasticHosts returns the Elastic hosts that should be used for annotations.
   620  // Annotations are not enabled if this has no hosts
   621  func (sc *SystemConf) GetAnnotateElasticHosts() expr.ElasticConfig {
   622  	return parseESAnnoteConfig(sc)
   623  }
   624  
   625  // GetAnnotateIndex returns the name of the Elastic index that should be used for annotations
   626  func (sc *SystemConf) GetAnnotateIndex() string {
   627  	return sc.AnnotateConf.Index
   628  }
   629  
   630  // GetTSDBContext returns an OpenTSDB context limited to
   631  // c.ResponseLimit. A nil context is returned if TSDBHost is not set.
   632  func (sc *SystemConf) GetTSDBContext() opentsdb.Context {
   633  	if sc.OpenTSDBConf.Host == "" {
   634  		return nil
   635  	}
   636  	return opentsdb.NewLimitContext(sc.OpenTSDBConf.Host, sc.OpenTSDBConf.ResponseLimit, sc.OpenTSDBConf.Version)
   637  }
   638  
   639  // GetGraphiteContext returns a Graphite context which contains all the information needed
   640  // to query Graphite. A nil context is returned if GraphiteHost is not set.
   641  func (sc *SystemConf) GetGraphiteContext() graphite.Context {
   642  	if sc.GraphiteConf.Host == "" {
   643  		return nil
   644  	}
   645  	if len(sc.GraphiteConf.Headers) > 0 {
   646  		headers := http.Header(make(map[string][]string))
   647  		for k, v := range sc.GraphiteConf.Headers {
   648  			headers.Add(k, v)
   649  		}
   650  		return graphite.HostHeader{
   651  			Host:   sc.GraphiteConf.Host,
   652  			Header: headers,
   653  		}
   654  	}
   655  	return graphite.Host(sc.GraphiteConf.Host)
   656  }
   657  
   658  // GetInfluxContext returns a Influx context which contains all the information needed
   659  // to query Influx.
   660  func (sc *SystemConf) GetInfluxContext() client.HTTPConfig {
   661  	c := client.HTTPConfig{}
   662  	if sc.md.IsDefined("InfluxConf", "URL") {
   663  		c.Addr = sc.InfluxConf.URL
   664  	}
   665  	if sc.md.IsDefined("InfluxConf", "Username") {
   666  		c.Username = sc.InfluxConf.Username
   667  	}
   668  	if sc.md.IsDefined("InfluxConf", "Password") {
   669  		c.Password = sc.InfluxConf.Password
   670  	}
   671  	if sc.md.IsDefined("InfluxConf", "UserAgent") {
   672  		c.UserAgent = sc.InfluxConf.UserAgent
   673  	}
   674  	if sc.md.IsDefined("InfluxConf", "Timeout") {
   675  		c.Timeout = sc.InfluxConf.Timeout.Duration
   676  	}
   677  	if sc.md.IsDefined("InfluxConf", "UnsafeSsl") {
   678  		c.InsecureSkipVerify = sc.InfluxConf.UnsafeSSL
   679  	}
   680  	return c
   681  }
   682  
   683  func (sc *SystemConf) GetCloudWatchContext() cloudwatch.Context {
   684  	c := cloudwatch.GetContext()
   685  	return c
   686  }
   687  
   688  // GetPromContext initializes returns a collection of Prometheus API v1 client APIs (connections)
   689  // from the configuration
   690  func (sc *SystemConf) GetPromContext() expr.PromClients {
   691  	clients := make(expr.PromClients)
   692  	for prefix, conf := range sc.PromConf {
   693  		// Error is checked in validation (PromConf Valid())
   694  		client, _ := promapi.NewClient(promapi.Config{Address: conf.URL})
   695  		clients[prefix] = promv1.NewAPI(client)
   696  	}
   697  	return clients
   698  }
   699  
   700  // GetElasticContext returns an Elastic context which contains all the information
   701  // needed to run Elastic queries.
   702  func (sc *SystemConf) GetElasticContext() expr.ElasticHosts {
   703  	return parseESConfig(sc)
   704  }
   705  
   706  // GetAzureMonitorContext returns a the collection of API clients needed
   707  // query the Azure Monitor and Application Insights APIs
   708  func (sc *SystemConf) GetAzureMonitorContext() expr.AzureMonitorClients {
   709  	allClients := make(expr.AzureMonitorClients)
   710  	for prefix, conf := range sc.AzureMonitorConf {
   711  		cc := expr.AzureMonitorClientCollection{}
   712  		cc.TenantId = conf.TenantId
   713  		if conf.Concurrency == 0 {
   714  			cc.Concurrency = 10
   715  		} else {
   716  			cc.Concurrency = conf.Concurrency
   717  		}
   718  		cc.MetricsClient = insights.NewMetricsClient(conf.SubscriptionId)
   719  		cc.MetricDefinitionsClient = insights.NewMetricDefinitionsClient(conf.SubscriptionId)
   720  		cc.ResourcesClient = resources.NewClient(conf.SubscriptionId)
   721  		cc.AIComponentsClient = ainsightsmgmt.NewComponentsClient(conf.SubscriptionId)
   722  		cc.AIMetricsClient = ainsights.NewMetricsClient()
   723  		if conf.DebugRequest {
   724  			cc.ResourcesClient.RequestInspector, cc.MetricsClient.RequestInspector, cc.MetricDefinitionsClient.RequestInspector = azureLogRequest(), azureLogRequest(), azureLogRequest()
   725  			cc.AIComponentsClient.RequestInspector, cc.AIMetricsClient.RequestInspector = azureLogRequest(), azureLogRequest()
   726  		}
   727  		if conf.DebugResponse {
   728  			cc.ResourcesClient.ResponseInspector, cc.MetricsClient.ResponseInspector, cc.MetricDefinitionsClient.ResponseInspector = azureLogResponse(), azureLogResponse(), azureLogResponse()
   729  			cc.AIComponentsClient.ResponseInspector, cc.AIMetricsClient.ResponseInspector = azureLogResponse(), azureLogResponse()
   730  		}
   731  		ccc := auth.NewClientCredentialsConfig(conf.ClientId, conf.ClientSecret, conf.TenantId)
   732  		at, err := ccc.Authorizer()
   733  		if err != nil {
   734  			// Should not hit this since we check for authorizer errors in Validation
   735  			// This is checked before because this method is not called until the an expression is called
   736  			slog.Error("unexpected Azure Authorizer error: ", err)
   737  		}
   738  		// Application Insights needs a different authorizer to use the other Resource "api.application..."
   739  		rcc := auth.NewClientCredentialsConfig(conf.ClientId, conf.ClientSecret, conf.TenantId)
   740  		rcc.Resource = "https://api.applicationinsights.io"
   741  		rat, err := rcc.Authorizer()
   742  		if err != nil {
   743  			slog.Error("unexpected application insights azure authorizer error: ", err)
   744  		}
   745  		cc.MetricsClient.Authorizer, cc.MetricDefinitionsClient.Authorizer, cc.ResourcesClient.Authorizer = at, at, at
   746  		cc.AIComponentsClient.Authorizer, cc.AIMetricsClient.Authorizer = at, rat
   747  		allClients[prefix] = cc
   748  	}
   749  	return allClients
   750  }
   751  
   752  // azureLogRequest outputs HTTP requests to Azure to the logs
   753  func azureLogRequest() autorest.PrepareDecorator {
   754  	return func(p autorest.Preparer) autorest.Preparer {
   755  		return autorest.PreparerFunc(func(r *http.Request) (*http.Request, error) {
   756  			r, err := p.Prepare(r)
   757  			if err != nil {
   758  				slog.Warningf("failure to dump azure request: %v", err)
   759  			}
   760  			dump, err := httputil.DumpRequestOut(r, true)
   761  			if err != nil {
   762  				slog.Warningf("failure to dump azure request: %v", err)
   763  			}
   764  			slog.Info(string(dump))
   765  			return r, err
   766  		})
   767  	}
   768  }
   769  
   770  // azureLogRequest outputs HTTP responses from requests to Azure to the logs
   771  func azureLogResponse() autorest.RespondDecorator {
   772  	return func(p autorest.Responder) autorest.Responder {
   773  		return autorest.ResponderFunc(func(r *http.Response) error {
   774  			err := p.Respond(r)
   775  			if err != nil {
   776  				slog.Warningf("failure to dump azure response: %v", err)
   777  			}
   778  			dump, err := httputil.DumpResponse(r, true)
   779  			if err != nil {
   780  				slog.Warningf("failure to dump azure response: %v", err)
   781  			}
   782  			slog.Info(string(dump))
   783  			return err
   784  		})
   785  	}
   786  }
   787  
   788  // AnnotateEnabled returns if annotations have been enabled or not
   789  func (sc *SystemConf) AnnotateEnabled() bool {
   790  	return len(sc.AnnotateConf.Hosts) != 0
   791  }
   792  
   793  // MakeLink creates a HTML Link based on Bosun's configured Hostname
   794  func (sc *SystemConf) MakeLink(path string, v *url.Values) string {
   795  	u := url.URL{
   796  		Scheme: sc.Scheme,
   797  		Host:   sc.Hostname,
   798  		Path:   path,
   799  	}
   800  	if v != nil {
   801  		u.RawQuery = v.Encode()
   802  	}
   803  	return u.String()
   804  }
   805  
   806  // Duration is a time.Duration with a UnmarshalText method so
   807  // durations can be decoded from TOML.
   808  type Duration struct {
   809  	time.Duration
   810  }
   811  
   812  // UnmarshalText is the method called by TOML when decoding a value
   813  func (d *Duration) UnmarshalText(text []byte) error {
   814  	var err error
   815  	d.Duration, err = time.ParseDuration(string(text))
   816  	return err
   817  }
   818  
   819  // URL is a *url.URL with a UnmarshalText method so
   820  // a url can be decoded from TOML.
   821  type URL struct {
   822  	*url.URL
   823  }
   824  
   825  // UnmarshalText is the method called by TOML when decoding a value
   826  func (u *URL) UnmarshalText(text []byte) error {
   827  	var err error
   828  	u.URL, err = url.Parse(string(bytes.Trim(text, `\"`)))
   829  	return err
   830  }