2019-09-04 23:37:13 +00:00
package main
import (
2019-12-04 21:44:35 +00:00
"os"
2021-02-06 01:45:28 +00:00
"os/signal"
2023-12-25 01:48:43 +00:00
"strconv"
2021-02-06 01:45:28 +00:00
"syscall"
2021-05-19 02:29:15 +00:00
"time"
2020-10-30 15:30:03 +00:00
2022-12-06 06:41:09 +00:00
"github.com/TwiN/gatus/v5/config"
"github.com/TwiN/gatus/v5/controller"
"github.com/TwiN/gatus/v5/storage/store"
"github.com/TwiN/gatus/v5/watchdog"
2024-11-14 00:02:53 +00:00
"github.com/TwiN/logr"
2019-09-04 23:37:13 +00:00
)
2024-11-14 04:54:00 +00:00
const (
GatusConfigPathEnvVar = "GATUS_CONFIG_PATH"
GatusConfigFileEnvVar = "GATUS_CONFIG_FILE" // Deprecated in favor of GatusConfigPathEnvVar
GatusLogLevelEnvVar = "GATUS_LOG_LEVEL"
)
2019-09-04 23:37:13 +00:00
func main ( ) {
2023-12-25 01:48:43 +00:00
if delayInSeconds , _ := strconv . Atoi ( os . Getenv ( "GATUS_DELAY_START_SECONDS" ) ) ; delayInSeconds > 0 {
2024-11-14 04:54:00 +00:00
logr . Infof ( "Delaying start by %d seconds" , delayInSeconds )
2023-12-25 01:48:43 +00:00
time . Sleep ( time . Duration ( delayInSeconds ) * time . Second )
}
2024-11-14 04:54:00 +00:00
configureLogging ( )
2021-05-19 02:29:15 +00:00
cfg , err := loadConfiguration ( )
if err != nil {
panic ( err )
}
2021-10-28 23:35:46 +00:00
initializeStorage ( cfg )
2021-05-19 02:29:15 +00:00
start ( cfg )
2021-02-06 01:45:28 +00:00
// Wait for termination signal
2021-05-19 02:29:15 +00:00
signalChannel := make ( chan os . Signal , 1 )
2021-02-06 01:45:28 +00:00
done := make ( chan bool , 1 )
2021-05-19 02:29:15 +00:00
signal . Notify ( signalChannel , os . Interrupt , syscall . SIGTERM )
2021-02-06 01:45:28 +00:00
go func ( ) {
2021-05-19 02:29:15 +00:00
<- signalChannel
2024-11-14 04:54:00 +00:00
logr . Info ( "Received termination signal, attempting to gracefully shut down" )
2023-08-04 22:30:15 +00:00
stop ( cfg )
2021-05-19 02:29:15 +00:00
save ( )
2021-02-06 01:45:28 +00:00
done <- true
} ( )
<- done
2024-11-14 04:54:00 +00:00
logr . Info ( "Shutting down" )
2019-09-07 01:59:50 +00:00
}
2021-07-09 03:39:12 +00:00
func start ( cfg * config . Config ) {
2022-07-29 00:07:53 +00:00
go controller . Handle ( cfg )
2021-07-09 03:39:12 +00:00
watchdog . Monitor ( cfg )
go listenToConfigurationFileChanges ( cfg )
}
2023-08-04 22:30:15 +00:00
func stop ( cfg * config . Config ) {
watchdog . Shutdown ( cfg )
2021-05-19 02:29:15 +00:00
controller . Shutdown ( )
}
func save ( ) {
2021-10-28 23:35:46 +00:00
if err := store . Get ( ) . Save ( ) ; err != nil {
2024-11-14 04:54:00 +00:00
logr . Errorf ( "Failed to save storage provider: %s" , err . Error ( ) )
2021-05-19 02:29:15 +00:00
}
}
2024-11-14 04:54:00 +00:00
func configureLogging ( ) {
logLevelAsString := os . Getenv ( GatusLogLevelEnvVar )
if logLevel , err := logr . LevelFromString ( logLevelAsString ) ; err != nil {
logr . SetThreshold ( logr . LevelInfo )
if len ( logLevelAsString ) == 0 {
logr . Infof ( "[main.configureLogging] Defaulting log level to %s" , logr . LevelInfo )
} else {
logr . Warnf ( "[main.configureLogging] Invalid log level '%s', defaulting to %s" , logLevelAsString , logr . LevelInfo )
}
} else {
logr . SetThreshold ( logLevel )
logr . Infof ( "[main.configureLogging] Log Level is set to %s" , logr . GetThreshold ( ) )
}
2024-11-14 00:02:53 +00:00
}
2023-01-08 22:53:37 +00:00
func loadConfiguration ( ) ( * config . Config , error ) {
2024-11-14 04:54:00 +00:00
configPath := os . Getenv ( GatusConfigPathEnvVar )
2023-01-08 22:53:37 +00:00
// Backwards compatibility
if len ( configPath ) == 0 {
2024-11-14 04:54:00 +00:00
if configPath = os . Getenv ( GatusConfigFileEnvVar ) ; len ( configPath ) > 0 {
logr . Warnf ( "WARNING: %s is deprecated. Please use %s instead." , GatusConfigFileEnvVar , GatusConfigPathEnvVar )
2023-01-11 00:04:19 +00:00
}
2019-12-04 21:44:35 +00:00
}
2023-01-08 22:53:37 +00:00
return config . LoadConfiguration ( configPath )
2021-05-19 02:29:15 +00:00
}
2021-10-28 23:35:46 +00:00
// initializeStorage initializes the storage provider
//
// Q: "TwiN, why are you putting this here? Wouldn't it make more sense to have this in the config?!"
// A: Yes. Yes it would make more sense to have it in the config package. But I don't want to import
2022-12-06 06:41:09 +00:00
// the massive SQL dependencies just because I want to import the config, so here we are.
2021-10-28 23:35:46 +00:00
func initializeStorage ( cfg * config . Config ) {
err := store . Initialize ( cfg . Storage )
if err != nil {
panic ( err )
}
// Remove all EndpointStatus that represent endpoints which no longer exist in the configuration
var keys [ ] string
2024-05-10 02:56:16 +00:00
for _ , ep := range cfg . Endpoints {
keys = append ( keys , ep . Key ( ) )
2021-10-28 23:35:46 +00:00
}
2024-05-16 01:29:45 +00:00
for _ , ee := range cfg . ExternalEndpoints {
keys = append ( keys , ee . Key ( ) )
2024-04-09 01:00:40 +00:00
}
2021-10-28 23:35:46 +00:00
numberOfEndpointStatusesDeleted := store . Get ( ) . DeleteAllEndpointStatusesNotInKeys ( keys )
if numberOfEndpointStatusesDeleted > 0 {
2024-11-14 04:54:00 +00:00
logr . Infof ( "[main.initializeStorage] Deleted %d endpoint statuses because their matching endpoints no longer existed" , numberOfEndpointStatusesDeleted )
2021-10-28 23:35:46 +00:00
}
2024-05-16 01:29:45 +00:00
// Clean up the triggered alerts from the storage provider and load valid triggered endpoint alerts
numberOfPersistedTriggeredAlertsLoaded := 0
for _ , ep := range cfg . Endpoints {
var checksums [ ] string
for _ , alert := range ep . Alerts {
if alert . IsEnabled ( ) {
checksums = append ( checksums , alert . Checksum ( ) )
}
}
numberOfTriggeredAlertsDeleted := store . Get ( ) . DeleteAllTriggeredAlertsNotInChecksumsByEndpoint ( ep , checksums )
2024-11-14 04:54:00 +00:00
if numberOfTriggeredAlertsDeleted > 0 {
logr . Debugf ( "[main.initializeStorage] Deleted %d triggered alerts for endpoint with key=%s because their configurations have been changed or deleted" , numberOfTriggeredAlertsDeleted , ep . Key ( ) )
2024-05-16 01:29:45 +00:00
}
for _ , alert := range ep . Alerts {
exists , resolveKey , numberOfSuccessesInARow , err := store . Get ( ) . GetTriggeredEndpointAlert ( ep , alert )
if err != nil {
2024-11-14 04:54:00 +00:00
logr . Errorf ( "[main.initializeStorage] Failed to get triggered alert for endpoint with key=%s: %s" , ep . Key ( ) , err . Error ( ) )
2024-05-16 01:29:45 +00:00
continue
}
if exists {
alert . Triggered , alert . ResolveKey = true , resolveKey
ep . NumberOfSuccessesInARow , ep . NumberOfFailuresInARow = numberOfSuccessesInARow , alert . FailureThreshold
numberOfPersistedTriggeredAlertsLoaded ++
}
}
}
for _ , ee := range cfg . ExternalEndpoints {
var checksums [ ] string
for _ , alert := range ee . Alerts {
if alert . IsEnabled ( ) {
checksums = append ( checksums , alert . Checksum ( ) )
}
}
convertedEndpoint := ee . ToEndpoint ( )
numberOfTriggeredAlertsDeleted := store . Get ( ) . DeleteAllTriggeredAlertsNotInChecksumsByEndpoint ( convertedEndpoint , checksums )
2024-11-14 04:54:00 +00:00
if numberOfTriggeredAlertsDeleted > 0 {
logr . Debugf ( "[main.initializeStorage] Deleted %d triggered alerts for endpoint with key=%s because their configurations have been changed or deleted" , numberOfTriggeredAlertsDeleted , ee . Key ( ) )
2024-05-16 01:29:45 +00:00
}
for _ , alert := range ee . Alerts {
exists , resolveKey , numberOfSuccessesInARow , err := store . Get ( ) . GetTriggeredEndpointAlert ( convertedEndpoint , alert )
if err != nil {
2024-11-14 04:54:00 +00:00
logr . Errorf ( "[main.initializeStorage] Failed to get triggered alert for endpoint with key=%s: %s" , ee . Key ( ) , err . Error ( ) )
2024-05-16 01:29:45 +00:00
continue
}
if exists {
alert . Triggered , alert . ResolveKey = true , resolveKey
ee . NumberOfSuccessesInARow , ee . NumberOfFailuresInARow = numberOfSuccessesInARow , alert . FailureThreshold
numberOfPersistedTriggeredAlertsLoaded ++
}
}
}
if numberOfPersistedTriggeredAlertsLoaded > 0 {
2024-11-14 04:54:00 +00:00
logr . Infof ( "[main.initializeStorage] Loaded %d persisted triggered alerts" , numberOfPersistedTriggeredAlertsLoaded )
2024-05-16 01:29:45 +00:00
}
2021-10-28 23:35:46 +00:00
}
2021-05-19 02:29:15 +00:00
func listenToConfigurationFileChanges ( cfg * config . Config ) {
for {
time . Sleep ( 30 * time . Second )
2023-01-08 22:53:37 +00:00
if cfg . HasLoadedConfigurationBeenModified ( ) {
2024-11-14 04:54:00 +00:00
logr . Info ( "[main.listenToConfigurationFileChanges] Configuration file has been modified" )
2023-08-04 22:30:15 +00:00
stop ( cfg )
2021-12-03 23:20:09 +00:00
time . Sleep ( time . Second ) // Wait a bit to make sure everything is done.
2021-05-19 02:29:15 +00:00
save ( )
updatedConfig , err := loadConfiguration ( )
if err != nil {
if cfg . SkipInvalidConfigUpdate {
2024-11-14 04:54:00 +00:00
logr . Errorf ( "[main.listenToConfigurationFileChanges] Failed to load new configuration: %s" , err . Error ( ) )
logr . Error ( "[main.listenToConfigurationFileChanges] The configuration file was updated, but it is not valid. The old configuration will continue being used." )
2021-05-19 02:29:15 +00:00
// Update the last file modification time to avoid trying to process the same invalid configuration again
cfg . UpdateLastFileModTime ( )
continue
} else {
panic ( err )
}
}
2023-08-04 22:30:15 +00:00
store . Get ( ) . Close ( )
2021-12-03 23:20:09 +00:00
initializeStorage ( updatedConfig )
2021-05-19 02:29:15 +00:00
start ( updatedConfig )
return
}
2019-12-04 21:44:35 +00:00
}
}