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
"log"
"os"
2021-05-19 04:29:15 +02:00
"time"
2020-10-30 16:30:03 +01:00
2021-10-08 03:28:04 +02:00
"github.com/TwiN/gatus/v3/alerting"
"github.com/TwiN/gatus/v3/alerting/alert"
"github.com/TwiN/gatus/v3/alerting/provider"
"github.com/TwiN/gatus/v3/config/maintenance"
"github.com/TwiN/gatus/v3/config/ui"
"github.com/TwiN/gatus/v3/config/web"
"github.com/TwiN/gatus/v3/core"
"github.com/TwiN/gatus/v3/security"
"github.com/TwiN/gatus/v3/storage"
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-06-26 03:31:34 +02:00
)
2019-09-06 06:01:48 +02:00
2019-10-20 04:03:55 +02:00
var (
2021-10-23 22:47:12 +02:00
// ErrNoEndpointInConfig is an error returned when a configuration file has no endpoints configured
ErrNoEndpointInConfig = errors . New ( "configuration file should contain at least 1 endpoint" )
2020-10-23 22:07:51 +02:00
// ErrConfigFileNotFound is an error returned when the configuration file could not be found
ErrConfigFileNotFound = errors . New ( "configuration file not found" )
// 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" )
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
2021-10-24 21:03:41 +02:00
Debug bool ` yaml:"debug,omitempty" `
2020-10-17 05:07:14 +02:00
// Metrics Whether to expose metrics at /metrics
2021-10-24 21:03:41 +02:00
Metrics bool ` yaml:"metrics,omitempty" `
2020-10-17 05:07:14 +02:00
2021-05-19 04:29:15 +02:00
// SkipInvalidConfigUpdate Whether to make the application ignore invalid configuration
// if the configuration file is updated while the application is running
2021-10-24 21:03:41 +02:00
SkipInvalidConfigUpdate bool ` yaml:"skip-invalid-config-update,omitempty" `
2021-05-19 04:29:15 +02:00
2020-10-17 05:07:14 +02:00
// DisableMonitoringLock Whether to disable the monitoring lock
2021-10-23 22:47:12 +02:00
// The monitoring lock is what prevents multiple endpoints from being processed at the same time.
2020-10-17 05:07:14 +02:00
// Disabling this may lead to inaccurate response times
2021-10-24 21:03:41 +02:00
DisableMonitoringLock bool ` yaml:"disable-monitoring-lock,omitempty" `
2020-10-17 05:07:14 +02:00
// Security Configuration for securing access to Gatus
2021-10-24 21:03:41 +02:00
Security * security . Config ` yaml:"security,omitempty" `
2020-10-17 05:07:14 +02:00
// Alerting Configuration for alerting
2021-10-24 21:03:41 +02:00
Alerting * alerting . Config ` yaml:"alerting,omitempty" `
2020-10-17 05:07:14 +02:00
2021-10-23 22:47:12 +02:00
// Endpoints List of endpoints to monitor
2021-10-24 21:03:41 +02:00
Endpoints [ ] * core . Endpoint ` yaml:"endpoints,omitempty" `
2021-10-23 22:47:12 +02:00
// Services List of endpoints to monitor
//
// XXX: Remove this in v5.0.0
// XXX: This is not a typo -- not v4.0.0, but v5.0.0 -- I want to give enough time for people to migrate
//
// Deprecated in favor of Endpoints
2021-10-24 21:03:41 +02:00
Services [ ] * core . Endpoint ` yaml:"services,omitempty" `
2020-10-30 16:30:03 +01:00
2021-02-03 05:06:34 +01:00
// Storage is the configuration for how the data is stored
2021-10-24 21:03:41 +02:00
Storage * storage . Config ` yaml:"storage,omitempty" `
2021-02-03 05:06:34 +01:00
2021-10-23 22:47:12 +02:00
// Web is the web configuration for the application
2021-10-24 21:03:41 +02:00
Web * web . Config ` yaml:"web,omitempty" `
2021-05-19 04:29:15 +02:00
2021-09-11 07:51:14 +02:00
// UI is the configuration for the UI
2021-10-24 21:03:41 +02:00
UI * ui . Config ` yaml:"ui,omitempty" `
2021-09-11 07:51:14 +02:00
2021-09-22 06:04:51 +02:00
// Maintenance is the configuration for creating a maintenance window in which no alerts are sent
2021-10-24 21:03:41 +02:00
Maintenance * maintenance . Config ` yaml:"maintenance,omitempty" `
2021-09-22 06:04:51 +02:00
2021-05-19 04:29:15 +02:00
filePath string // path to the file from which config was loaded from
lastFileModTime time . Time // last modification time
2020-06-26 03:31:34 +02:00
}
2021-05-19 04:29:15 +02:00
// HasLoadedConfigurationFileBeenModified returns whether the file that the
// configuration has been loaded from has been modified since it was last read
func ( config Config ) HasLoadedConfigurationFileBeenModified ( ) bool {
if fileInfo , err := os . Stat ( config . filePath ) ; err == nil {
if ! fileInfo . ModTime ( ) . IsZero ( ) {
return config . lastFileModTime . Unix ( ) != fileInfo . ModTime ( ) . Unix ( )
}
2019-09-06 06:01:48 +02:00
}
2021-05-19 04:29:15 +02:00
return false
2019-09-06 06:01:48 +02:00
}
2021-05-19 04:29:15 +02:00
// UpdateLastFileModTime refreshes Config.lastFileModTime
func ( config * Config ) UpdateLastFileModTime ( ) {
if fileInfo , err := os . Stat ( config . filePath ) ; err == nil {
if ! fileInfo . ModTime ( ) . IsZero ( ) {
config . lastFileModTime = fileInfo . ModTime ( )
}
2021-09-04 04:28:49 +02:00
} else {
log . Println ( "[config][UpdateLastFileModTime] Ran into error updating lastFileModTime:" , err . Error ( ) )
2021-05-19 04:29:15 +02:00
}
2021-01-10 04:55:36 +01:00
}
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.
2021-05-19 04:29:15 +02:00
func Load ( configFile string ) ( * Config , 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 ) {
2021-05-19 04:29:15 +02:00
return nil , ErrConfigFileNotFound
2019-12-04 22:44:35 +01:00
}
2021-05-19 04:29:15 +02:00
return nil , err
2019-12-04 22:44:35 +01:00
}
2021-05-19 04:29:15 +02:00
cfg . filePath = configFile
cfg . UpdateLastFileModTime ( )
return cfg , nil
2019-12-04 22:44:35 +01:00
}
2020-10-20 01:26:29 +02:00
// LoadDefaultConfiguration loads the default configuration file
2021-05-19 04:29:15 +02:00
func LoadDefaultConfiguration ( ) ( * Config , error ) {
cfg , 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
}
2021-05-19 04:29:15 +02:00
return nil , err
2019-12-04 22:44:35 +01:00
}
2021-05-19 04:29:15 +02:00
return cfg , nil
2019-12-04 22:44:35 +01:00
}
2019-10-20 04:03:55 +02:00
func readConfigurationFile ( fileName string ) ( config * Config , err error ) {
var bytes [ ] byte
2021-12-03 07:44:17 +01:00
if bytes , err = os . 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
}
2021-10-24 20:51:21 +02:00
// parseAndValidateConfigBytes parses a Gatus configuration file into a Config struct and validates its parameters
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
2021-10-25 00:34:39 +02:00
yamlBytes = [ ] byte ( os . ExpandEnv ( string ( yamlBytes ) ) )
2019-12-04 23:27:27 +01:00
// Parse configuration file
2021-10-24 20:51:21 +02:00
if err = yaml . Unmarshal ( yamlBytes , & config ) ; err != nil {
2020-10-16 18:12:00 +02:00
return
}
2021-10-23 22:47:12 +02:00
if config != nil && len ( config . Services ) > 0 { // XXX: Remove this in v5.0.0
log . Println ( "WARNING: Your configuration is using 'services:', which is deprecated in favor of 'endpoints:'." )
log . Println ( "WARNING: See https://github.com/TwiN/gatus/issues/191 for more information" )
config . Endpoints = append ( config . Endpoints , config . Services ... )
config . Services = nil
}
// Check if the configuration file at least has endpoints configured
if config == nil || config . Endpoints == nil || len ( config . Endpoints ) == 0 {
err = ErrNoEndpointInConfig
2019-10-20 04:03:55 +02:00
} else {
2021-10-23 22:47:12 +02:00
validateAlertingConfig ( config . Alerting , config . Endpoints , config . Debug )
2021-05-19 04:29:15 +02:00
if err := validateSecurityConfig ( config ) ; err != nil {
return nil , err
}
2021-10-23 22:47:12 +02:00
if err := validateEndpointsConfig ( config ) ; err != nil {
2021-05-19 04:29:15 +02:00
return nil , err
}
if err := validateWebConfig ( config ) ; err != nil {
return nil , err
}
2021-09-11 07:51:14 +02:00
if err := validateUIConfig ( config ) ; err != nil {
return nil , err
}
2021-09-22 06:04:51 +02:00
if err := validateMaintenanceConfig ( config ) ; err != nil {
return nil , err
}
2021-05-19 04:29:15 +02:00
if err := validateStorageConfig ( config ) ; err != nil {
return nil , err
}
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-05-19 04:29:15 +02:00
func validateStorageConfig ( config * Config ) error {
2021-02-03 05:06:34 +01:00
if config . Storage == nil {
2021-07-16 04:07:30 +02:00
config . Storage = & storage . Config {
2021-08-07 18:11:35 +02:00
Type : storage . TypeMemory ,
2021-07-16 04:07:30 +02:00
}
2021-10-29 01:35:46 +02:00
} else {
if err := config . Storage . ValidateAndSetDefaults ( ) ; err != nil {
return err
}
2021-02-03 05:06:34 +01:00
}
2021-05-19 04:29:15 +02:00
return nil
2021-02-03 05:06:34 +01:00
}
2021-09-22 06:04:51 +02:00
func validateMaintenanceConfig ( config * Config ) error {
if config . Maintenance == nil {
config . Maintenance = maintenance . GetDefaultConfig ( )
} else {
if err := config . Maintenance . ValidateAndSetDefaults ( ) ; err != nil {
return err
}
}
return nil
}
2021-09-11 07:51:14 +02:00
func validateUIConfig ( config * Config ) error {
if config . UI == nil {
2021-09-22 06:47:51 +02:00
config . UI = ui . GetDefaultConfig ( )
2021-09-11 07:51:14 +02:00
} else {
2021-09-22 06:47:51 +02:00
if err := config . UI . ValidateAndSetDefaults ( ) ; err != nil {
2021-09-11 07:51:14 +02:00
return err
}
}
return nil
}
2021-05-19 04:29:15 +02:00
func validateWebConfig ( config * Config ) error {
2020-11-19 19:39:48 +01:00
if config . Web == nil {
2021-09-22 06:47:51 +02:00
config . Web = web . GetDefaultConfig ( )
2020-11-19 19:39:48 +01:00
} else {
2021-09-22 06:47:51 +02:00
return config . Web . ValidateAndSetDefaults ( )
2020-11-19 19:39:48 +01:00
}
2021-05-19 04:29:15 +02:00
return nil
2020-11-19 19:39:48 +01:00
}
2021-10-23 22:47:12 +02:00
func validateEndpointsConfig ( config * Config ) error {
for _ , endpoint := range config . Endpoints {
2020-09-22 23:46:40 +02:00
if config . Debug {
2021-10-23 22:47:12 +02:00
log . Printf ( "[config][validateEndpointsConfig] Validating endpoint '%s'" , endpoint . Name )
2020-09-22 23:46:40 +02:00
}
2021-10-23 22:47:12 +02:00
if err := endpoint . ValidateAndSetDefaults ( ) ; err != nil {
2021-05-19 04:29:15 +02:00
return err
}
2020-09-22 23:46:40 +02:00
}
2021-10-23 22:47:12 +02:00
log . Printf ( "[config][validateEndpointsConfig] Validated %d endpoints" , len ( config . Endpoints ) )
2021-05-19 04:29:15 +02:00
return nil
2020-09-22 23:46:40 +02:00
}
2021-05-19 04:29:15 +02:00
func validateSecurityConfig ( config * Config ) error {
2020-10-15 01:22:58 +02:00
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.
2021-05-19 04:29:15 +02:00
return ErrInvalidSecurityConfig
2020-10-15 01:22:58 +02:00
}
}
2021-05-19 04:29:15 +02:00
return nil
2020-10-15 01:22:58 +02:00
}
2021-05-16 03:31:32 +02:00
// validateAlertingConfig validates the alerting configuration
2021-10-23 22:47:12 +02:00
// Note that the alerting configuration has to be validated before the endpoint configuration, because the default alert
// returned by provider.AlertProvider.GetDefaultAlert() must be parsed before core.Endpoint.ValidateAndSetDefaults()
2021-05-16 03:31:32 +02:00
// sets the default alert values when none are set.
2021-10-23 22:47:12 +02:00
func validateAlertingConfig ( alertingConfig * alerting . Config , endpoints [ ] * core . Endpoint , debug bool ) {
2021-05-19 04:29:15 +02:00
if alertingConfig == nil {
2020-09-22 23:46:40 +02:00
log . Printf ( "[config][validateAlertingConfig] Alerting is not configured" )
return
}
2021-05-19 04:29:15 +02:00
alertTypes := [ ] alert . Type {
alert . TypeCustom ,
alert . TypeDiscord ,
2021-12-03 03:05:17 +01:00
alert . TypeEmail ,
2021-05-19 04:29:15 +02:00
alert . TypeMattermost ,
alert . TypeMessagebird ,
2021-12-10 03:18:44 +01:00
alert . TypeOpsgenie ,
2021-05-19 04:29:15 +02:00
alert . TypePagerDuty ,
alert . TypeSlack ,
2021-07-30 01:54:40 +02:00
alert . TypeTeams ,
2021-05-19 04:29:15 +02:00
alert . TypeTelegram ,
alert . TypeTwilio ,
2020-09-22 23:46:40 +02:00
}
2021-05-19 04:29:15 +02:00
var validProviders , invalidProviders [ ] alert . Type
2020-09-25 01:52:59 +02:00
for _ , alertType := range alertTypes {
2021-05-19 04:29:15 +02:00
alertProvider := alertingConfig . GetAlertingProviderByAlertType ( alertType )
2020-09-25 01:52:59 +02:00
if alertProvider != nil {
if alertProvider . IsValid ( ) {
2021-05-16 03:31:32 +02:00
// Parse alerts with the provider's default alert
if alertProvider . GetDefaultAlert ( ) != nil {
2021-10-23 22:47:12 +02:00
for _ , endpoint := range endpoints {
for alertIndex , endpointAlert := range endpoint . Alerts {
if alertType == endpointAlert . Type {
2021-05-19 04:29:15 +02:00
if debug {
2021-10-23 22:47:12 +02:00
log . Printf ( "[config][validateAlertingConfig] Parsing alert %d with provider's default alert for provider=%s in endpoint=%s" , alertIndex , alertType , endpoint . Name )
2021-05-16 03:31:32 +02:00
}
2021-10-23 22:47:12 +02:00
provider . ParseWithDefaultAlert ( alertProvider . GetDefaultAlert ( ) , endpointAlert )
2021-05-16 03:31:32 +02:00
}
}
}
}
2020-09-25 01:52:59 +02:00
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 )
}