2019-09-06 06:01:48 +02:00
package config
import (
2019-10-20 04:03:55 +02:00
"errors"
2020-10-30 16:30:03 +01:00
"io/ioutil"
"log"
"os"
2020-09-19 22:22:12 +02:00
"github.com/TwinProduction/gatus/alerting"
2020-09-25 01:52:59 +02:00
"github.com/TwinProduction/gatus/alerting/provider"
2019-09-07 02:25:31 +02:00
"github.com/TwinProduction/gatus/core"
2020-11-11 18:40:06 +01:00
"github.com/TwinProduction/gatus/k8s"
2020-10-15 01:22:58 +02:00
"github.com/TwinProduction/gatus/security"
2021-02-03 05:06:34 +01:00
"github.com/TwinProduction/gatus/storage"
"github.com/TwinProduction/gatus/util"
2019-09-06 06:01:48 +02:00
"gopkg.in/yaml.v2"
)
2020-06-26 03:31:34 +02:00
const (
2020-09-26 21:15:50 +02:00
// DefaultConfigurationFilePath is the default path that will be used to search for the configuration file
// if a custom path isn't configured through the GATUS_CONFIG_FILE environment variable
2020-06-26 03:31:34 +02:00
DefaultConfigurationFilePath = "config/config.yaml"
2020-10-20 01:26:29 +02:00
// DefaultFallbackConfigurationFilePath is the default fallback path that will be used to search for the
// configuration file if DefaultConfigurationFilePath didn't work
DefaultFallbackConfigurationFilePath = "config/config.yml"
2020-11-19 19:39:48 +01:00
// DefaultAddress is the default address the service will bind to
DefaultAddress = "0.0.0.0"
// DefaultPort is the default port the service will listen on
DefaultPort = 8080
2020-06-26 03:31:34 +02:00
)
2019-09-06 06:01:48 +02:00
2019-10-20 04:03:55 +02:00
var (
2020-10-23 22:07:51 +02:00
// ErrNoServiceInConfig is an error returned when a configuration file has no services configured
ErrNoServiceInConfig = errors . New ( "configuration file should contain at least 1 service" )
// ErrConfigFileNotFound is an error returned when the configuration file could not be found
ErrConfigFileNotFound = errors . New ( "configuration file not found" )
// ErrConfigNotLoaded is an error returned when an attempt to Get() the configuration before loading it is made
ErrConfigNotLoaded = errors . New ( "configuration is nil" )
// ErrInvalidSecurityConfig is an error returned when the security configuration is invalid
2020-10-15 01:22:58 +02:00
ErrInvalidSecurityConfig = errors . New ( "invalid security configuration" )
2020-10-23 22:07:51 +02:00
config * Config
2019-10-20 04:03:55 +02:00
)
2019-09-06 06:01:48 +02:00
2020-09-26 21:15:50 +02:00
// Config is the main configuration structure
2020-06-26 03:31:34 +02:00
type Config struct {
2020-10-17 05:07:14 +02:00
// Debug Whether to enable debug logs
Debug bool ` yaml:"debug" `
// Metrics Whether to expose metrics at /metrics
Metrics bool ` yaml:"metrics" `
// DisableMonitoringLock Whether to disable the monitoring lock
// The monitoring lock is what prevents multiple services from being processed at the same time.
// Disabling this may lead to inaccurate response times
DisableMonitoringLock bool ` yaml:"disable-monitoring-lock" `
// Security Configuration for securing access to Gatus
2020-10-15 01:22:58 +02:00
Security * security . Config ` yaml:"security" `
2020-10-17 05:07:14 +02:00
// Alerting Configuration for alerting
2020-09-19 22:22:12 +02:00
Alerting * alerting . Config ` yaml:"alerting" `
2020-10-17 05:07:14 +02:00
// Services List of services to monitor
Services [ ] * core . Service ` yaml:"services" `
2020-10-30 16:30:03 +01:00
2020-11-11 22:29:30 +01:00
// Kubernetes is the Kubernetes configuration
2020-11-11 18:40:06 +01:00
Kubernetes * k8s . Config ` yaml:"kubernetes" `
2020-11-19 19:39:48 +01:00
2021-02-03 05:06:34 +01:00
// Storage is the configuration for how the data is stored
Storage * storage . Config ` yaml:"storage" `
2020-11-20 23:24:56 +01:00
// Web is the configuration for the web listener
2021-02-01 07:37:56 +01:00
Web * WebConfig ` yaml:"web" `
2020-06-26 03:31:34 +02:00
}
2020-10-20 01:26:29 +02:00
// Get returns the configuration, or panics if the configuration hasn't loaded yet
2019-09-06 06:01:48 +02:00
func Get ( ) * Config {
if config == nil {
2019-12-04 22:44:35 +01:00
panic ( ErrConfigNotLoaded )
2019-09-06 06:01:48 +02:00
}
return config
}
2021-01-10 04:55:36 +01:00
// Set sets the configuration
// Used only for testing
func Set ( cfg * Config ) {
config = cfg
}
2020-10-20 01:26:29 +02:00
// Load loads a custom configuration file
2020-11-21 23:35:08 +01:00
// Note that the misconfiguration of some fields may lead to panics. This is on purpose.
2019-12-04 22:44:35 +01:00
func Load ( configFile string ) error {
2020-09-22 23:46:40 +02:00
log . Printf ( "[config][Load] Reading configuration from configFile=%s" , configFile )
2019-12-04 22:44:35 +01:00
cfg , err := readConfigurationFile ( configFile )
if err != nil {
if os . IsNotExist ( err ) {
return ErrConfigFileNotFound
}
2020-10-23 22:31:49 +02:00
return err
2019-12-04 22:44:35 +01:00
}
config = cfg
return nil
}
2020-10-20 01:26:29 +02:00
// LoadDefaultConfiguration loads the default configuration file
2019-12-04 22:44:35 +01:00
func LoadDefaultConfiguration ( ) error {
2020-06-26 03:31:34 +02:00
err := Load ( DefaultConfigurationFilePath )
2019-12-04 22:44:35 +01:00
if err != nil {
if err == ErrConfigFileNotFound {
2020-10-20 01:26:29 +02:00
return Load ( DefaultFallbackConfigurationFilePath )
2019-12-04 22:44:35 +01:00
}
return err
}
return nil
}
2019-10-20 04:03:55 +02:00
func readConfigurationFile ( fileName string ) ( config * Config , err error ) {
var bytes [ ] byte
if bytes , err = ioutil . ReadFile ( fileName ) ; err == nil {
2019-10-20 03:39:31 +02:00
// file exists, so we'll parse it and return it
2019-10-20 04:03:55 +02:00
return parseAndValidateConfigBytes ( bytes )
2019-09-06 06:01:48 +02:00
}
2019-10-20 04:03:55 +02:00
return
2019-09-06 06:01:48 +02:00
}
2019-10-20 04:03:55 +02:00
func parseAndValidateConfigBytes ( yamlBytes [ ] byte ) ( config * Config , err error ) {
2019-12-04 23:27:27 +01:00
// Expand environment variables
yamlBytes = [ ] byte ( os . ExpandEnv ( string ( yamlBytes ) ) )
// Parse configuration file
2019-10-20 03:42:03 +02:00
err = yaml . Unmarshal ( yamlBytes , & config )
2020-10-16 18:12:00 +02:00
if err != nil {
return
}
2020-11-11 21:10:44 +01:00
// Check if the configuration file at least has services configured or Kubernetes auto discovery enabled
if config == nil || ( ( config . Services == nil || len ( config . Services ) == 0 ) && ( config . Kubernetes == nil || ! config . Kubernetes . AutoDiscover ) ) {
2019-10-20 04:03:55 +02:00
err = ErrNoServiceInConfig
} else {
2020-11-11 20:46:19 +01:00
// Note that the functions below may panic, and this is on purpose to prevent Gatus from starting with
// invalid configurations
2020-09-22 23:46:40 +02:00
validateAlertingConfig ( config )
2020-10-15 01:22:58 +02:00
validateSecurityConfig ( config )
2020-09-22 23:46:40 +02:00
validateServicesConfig ( config )
2020-11-11 20:22:31 +01:00
validateKubernetesConfig ( config )
2020-11-20 23:24:56 +01:00
validateWebConfig ( config )
2021-02-03 05:06:34 +01:00
validateStorageConfig ( config )
2019-09-07 02:25:31 +02:00
}
2019-10-20 03:42:03 +02:00
return
2019-09-06 06:01:48 +02:00
}
2020-09-22 23:46:40 +02:00
2021-02-03 05:06:34 +01:00
func validateStorageConfig ( config * Config ) {
if config . Storage == nil {
config . Storage = & storage . Config { }
}
err := storage . Initialize ( config . Storage )
if err != nil {
panic ( err )
}
// Remove all ServiceStatus that represent services which no longer exist in the configuration
var keys [ ] string
for _ , service := range config . Services {
keys = append ( keys , util . ConvertGroupAndServiceToKey ( service . Group , service . Name ) )
}
numberOfServiceStatusesDeleted := storage . Get ( ) . DeleteAllServiceStatusesNotInKeys ( keys )
if numberOfServiceStatusesDeleted > 0 {
log . Printf ( "[config][validateStorageConfig] Deleted %d service statuses because their matching services no longer existed" , numberOfServiceStatusesDeleted )
}
}
2020-11-20 23:24:56 +01:00
func validateWebConfig ( config * Config ) {
2020-11-19 19:39:48 +01:00
if config . Web == nil {
2021-02-01 07:37:56 +01:00
config . Web = & WebConfig { Address : DefaultAddress , Port : DefaultPort }
2020-11-19 19:39:48 +01:00
} else {
config . Web . validateAndSetDefaults ( )
}
}
2020-11-11 20:22:31 +01:00
func validateKubernetesConfig ( config * Config ) {
if config . Kubernetes != nil && config . Kubernetes . AutoDiscover {
2020-11-11 21:10:44 +01:00
if config . Kubernetes . ServiceTemplate == nil {
panic ( "kubernetes.service-template cannot be nil" )
}
2020-11-11 21:14:21 +01:00
if config . Debug {
log . Println ( "[config][validateKubernetesConfig] Automatically discovering Kubernetes services..." )
}
2020-11-11 20:46:19 +01:00
discoveredServices , err := k8s . DiscoverServices ( config . Kubernetes )
if err != nil {
panic ( err )
}
2020-11-11 20:22:31 +01:00
config . Services = append ( config . Services , discoveredServices ... )
2020-11-11 21:14:21 +01:00
log . Printf ( "[config][validateKubernetesConfig] Discovered %d Kubernetes services" , len ( discoveredServices ) )
2020-11-11 20:22:31 +01:00
}
}
2020-09-22 23:46:40 +02:00
func validateServicesConfig ( config * Config ) {
for _ , service := range config . Services {
if config . Debug {
log . Printf ( "[config][validateServicesConfig] Validating service '%s'" , service . Name )
}
service . ValidateAndSetDefaults ( )
}
log . Printf ( "[config][validateServicesConfig] Validated %d services" , len ( config . Services ) )
}
2020-10-15 01:22:58 +02:00
func validateSecurityConfig ( config * Config ) {
if config . Security != nil {
if config . Security . IsValid ( ) {
if config . Debug {
log . Printf ( "[config][validateSecurityConfig] Basic security configuration has been validated" )
}
} else {
// If there was an attempt to configure security, then it must mean that some confidential or private
// data are exposed. As a result, we'll force a panic because it's better to be safe than sorry.
panic ( ErrInvalidSecurityConfig )
}
}
}
2020-09-22 23:46:40 +02:00
func validateAlertingConfig ( config * Config ) {
if config . Alerting == nil {
log . Printf ( "[config][validateAlertingConfig] Alerting is not configured" )
return
}
2020-09-25 01:52:59 +02:00
alertTypes := [ ] core . AlertType {
2021-03-05 03:26:17 +01:00
core . CustomAlert ,
core . DiscordAlert ,
2020-11-14 15:55:37 +01:00
core . MattermostAlert ,
2020-11-23 22:20:06 +01:00
core . MessagebirdAlert ,
2020-09-25 01:52:59 +02:00
core . PagerDutyAlert ,
2021-03-05 03:26:17 +01:00
core . SlackAlert ,
2021-03-31 01:38:34 +02:00
core . TelegramAlert ,
2021-03-05 03:26:17 +01:00
core . TwilioAlert ,
2020-09-22 23:46:40 +02:00
}
2020-09-25 01:52:59 +02:00
var validProviders , invalidProviders [ ] core . AlertType
for _ , alertType := range alertTypes {
alertProvider := GetAlertingProviderByAlertType ( config , alertType )
if alertProvider != nil {
if alertProvider . IsValid ( ) {
validProviders = append ( validProviders , alertType )
} else {
log . Printf ( "[config][validateAlertingConfig] Ignoring provider=%s because configuration is invalid" , alertType )
invalidProviders = append ( invalidProviders , alertType )
}
} else {
invalidProviders = append ( invalidProviders , alertType )
}
2020-09-22 23:46:40 +02:00
}
log . Printf ( "[config][validateAlertingConfig] configuredProviders=%s; ignoredProviders=%s" , validProviders , invalidProviders )
}
2020-09-25 01:52:59 +02:00
2020-10-23 22:07:51 +02:00
// GetAlertingProviderByAlertType returns an provider.AlertProvider by its corresponding core.AlertType
2020-09-25 01:52:59 +02:00
func GetAlertingProviderByAlertType ( config * Config , alertType core . AlertType ) provider . AlertProvider {
switch alertType {
2021-03-05 03:26:17 +01:00
case core . CustomAlert :
if config . Alerting . Custom == nil {
2020-09-25 01:52:59 +02:00
// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil
return nil
}
2021-03-05 03:26:17 +01:00
return config . Alerting . Custom
case core . DiscordAlert :
if config . Alerting . Discord == nil {
// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil
return nil
}
return config . Alerting . Discord
2020-11-14 15:55:37 +01:00
case core . MattermostAlert :
if config . Alerting . Mattermost == nil {
// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil
return nil
}
return config . Alerting . Mattermost
2020-11-23 22:20:06 +01:00
case core . MessagebirdAlert :
if config . Alerting . Messagebird == nil {
// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil
return nil
}
return config . Alerting . Messagebird
2020-09-25 01:52:59 +02:00
case core . PagerDutyAlert :
if config . Alerting . PagerDuty == nil {
// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil
return nil
}
return config . Alerting . PagerDuty
2021-03-05 03:26:17 +01:00
case core . SlackAlert :
if config . Alerting . Slack == nil {
2020-09-25 01:52:59 +02:00
// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil
return nil
}
2021-03-05 03:26:17 +01:00
return config . Alerting . Slack
2021-03-31 01:38:34 +02:00
case core . TelegramAlert :
if config . Alerting . Telegram == nil {
// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil
return nil
}
return config . Alerting . Telegram
2021-03-05 03:26:17 +01:00
case core . TwilioAlert :
if config . Alerting . Twilio == nil {
// Since we're returning an interface, we need to explicitly return nil, even if the provider itself is nil
return nil
}
return config . Alerting . Twilio
2020-09-25 01:52:59 +02:00
}
return nil
}