1package conf
2
3import (
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
33type 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
86type 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
99func (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
115type 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
123type GraphiteConf struct {
124	Host    string
125	Headers map[string]string
126}
127
128// AnnotateConf contains the elastic configuration to enable Annotations support
129type 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
139type 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
158type ElasticConf AnnotateConf
159
160// AzureConf contains configuration for an Azure metrics
161type 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
173func (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
205type 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
216type 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
222func (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
235type 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
249type 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
257type 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
267type 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
288type LDAPGroup struct {
289	// group search path string
290	Path string
291	// Access to grant members of group Ex: "Admin"
292	Role string
293}
294
295type CloudWatchConf struct {
296	Enabled        bool
297	ExpansionLimit int
298	PagesLimit     int
299	Concurrency    int
300}
301
302func (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
317func (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
325const (
326	defaultHTTPListen = ":8070"
327)
328
329// NewSystemConf retruns a system conf with default values set
330func 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
355func LoadSystemConfigFile(fileName string) (*SystemConf, error) {
356	return loadSystemConfig(fileName, true)
357}
358
359// LoadSystemConfig is like LoadSystemConfigFile but loads the config from a string
360func LoadSystemConfig(conf string) (*SystemConf, error) {
361	return loadSystemConfig(conf, false)
362}
363
364func 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
418func (sc *SystemConf) GetHTTPListen() string {
419	return sc.HTTPListen
420}
421
422// GetHTTPSListen returns the hostname:port that Bosun should listen on with tls
423func (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.
428func (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.
433func (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
438func (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
443func (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
448func (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
453func (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.
459func (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.
465func (sc *SystemConf) GetPingDuration() time.Duration {
466	return sc.PingDuration.Duration
467}
468
469// GetLedisDir returns the directory where Ledis should store its files
470func (sc *SystemConf) GetLedisDir() string {
471	return sc.DBConf.LedisDir
472}
473
474// GetLedisBindAddr returns the address that Ledis should listen on
475func (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.
481func (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
493func (sc *SystemConf) GetRedisMasterName() string {
494	return sc.DBConf.RedisMasterName
495}
496
497// GetRedisDb returns the redis database number to use
498func (sc *SystemConf) GetRedisDb() int {
499	return sc.DBConf.RedisDb
500}
501
502// GetRedisPassword returns the password that should be used to connect to redis
503func (sc *SystemConf) GetRedisPassword() string {
504	return sc.DBConf.RedisPassword
505}
506
507// RedisClientSetName returns if CLIENT SETNAME shoud send to redis.
508func (sc *SystemConf) IsRedisClientSetName() bool {
509	return sc.DBConf.RedisClientSetName
510}
511
512func (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
518func (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
525func (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
531func (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
537func (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
543func (sc *SystemConf) GetDefaultRunEvery() int {
544	return sc.DefaultRunEvery
545}
546
547// GetAlertCheckDistribution returns if the alert rule checks are scattered over check period
548func (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
554func (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
560func (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
566func (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/
572func (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.
578func (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
583func (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.
589func (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
594func (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
600func (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
605func (sc *SystemConf) SetTSDBHost(tsdbHost string) {
606	sc.OpenTSDBConf.Host = tsdbHost
607}
608
609// GetExampleExpression returns the default expression for "Expression" tab.
610func (sc *SystemConf) GetExampleExpression() string {
611	return sc.ExampleExpression
612}
613
614// GetTSDBHost returns the configured TSDBHost
615func (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
621func (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
626func (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.
632func (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.
641func (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.
660func (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
683func (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
690func (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.
702func (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
708func (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
753func 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
771func 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
789func (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
794func (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.
808type Duration struct {
809	time.Duration
810}
811
812// UnmarshalText is the method called by TOML when decoding a value
813func (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.
821type URL struct {
822	*url.URL
823}
824
825// UnmarshalText is the method called by TOML when decoding a value
826func (u *URL) UnmarshalText(text []byte) error {
827	var err error
828	u.URL, err = url.Parse(string(bytes.Trim(text, `\"`)))
829	return err
830}
831