mirror of
https://github.com/TwiN/gatus.git
synced 2024-11-21 23:43:27 +01:00
Revert "feat: Support multiple configuration files" (#395)
Revert "feat: Support multiple configuration files (#389)"
This reverts commit 8e14302765
.
This commit is contained in:
parent
8e14302765
commit
87740e74a6
@ -149,7 +149,7 @@ If you want to create your own configuration, see [Docker](#docker) for informat
|
|||||||
|
|
||||||
By default, the configuration file is expected to be at `config/config.yaml`.
|
By default, the configuration file is expected to be at `config/config.yaml`.
|
||||||
|
|
||||||
You can specify a custom path by setting the `GATUS_CONFIG_PATH` environment variable. If `GATUS_CONFIG_PATH` points to a directory, all `*.yaml` and `*.yml` files inside this directory and its subdirectories are concatenated. The previously used environment variable `GATUS_CONFIG_FILE` is deprecated but still works.
|
You can specify a custom path by setting the `GATUS_CONFIG_FILE` environment variable.
|
||||||
|
|
||||||
Here's a simple example:
|
Here's a simple example:
|
||||||
```yaml
|
```yaml
|
||||||
|
126
config/config.go
126
config/config.go
@ -3,10 +3,8 @@ package config
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/TwiN/gatus/v5/alerting"
|
"github.com/TwiN/gatus/v5/alerting"
|
||||||
@ -42,9 +40,6 @@ var (
|
|||||||
|
|
||||||
// ErrInvalidSecurityConfig is an error returned when the security configuration is invalid
|
// ErrInvalidSecurityConfig is an error returned when the security configuration is invalid
|
||||||
ErrInvalidSecurityConfig = errors.New("invalid security configuration")
|
ErrInvalidSecurityConfig = errors.New("invalid security configuration")
|
||||||
|
|
||||||
// ErrEarlyReturn is returned to break out of a loop from a callback early
|
|
||||||
ErrEarlyReturn = errors.New("early escape")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config is the main configuration structure
|
// Config is the main configuration structure
|
||||||
@ -89,7 +84,7 @@ type Config struct {
|
|||||||
// WARNING: This is in ALPHA and may change or be completely removed in the future
|
// WARNING: This is in ALPHA and may change or be completely removed in the future
|
||||||
Remote *remote.Config `yaml:"remote,omitempty"`
|
Remote *remote.Config `yaml:"remote,omitempty"`
|
||||||
|
|
||||||
configPath string // path to the file or directory from which config was loaded
|
filePath string // path to the file from which config was loaded from
|
||||||
lastFileModTime time.Time // last modification time
|
lastFileModTime time.Time // last modification time
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,80 +98,63 @@ func (config *Config) GetEndpointByKey(key string) *core.Endpoint {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasLoadedConfigurationFileBeenModified returns whether one of the file that the
|
// HasLoadedConfigurationFileBeenModified returns whether the file that the
|
||||||
// configuration has been loaded from has been modified since it was last read
|
// configuration has been loaded from has been modified since it was last read
|
||||||
func (config Config) HasLoadedConfigurationFileBeenModified() bool {
|
func (config Config) HasLoadedConfigurationFileBeenModified() bool {
|
||||||
lastMod := config.lastFileModTime.Unix()
|
if fileInfo, err := os.Stat(config.filePath); err == nil {
|
||||||
fileInfo, err := os.Stat(config.configPath)
|
if !fileInfo.ModTime().IsZero() {
|
||||||
if err != nil {
|
return config.lastFileModTime.Unix() != fileInfo.ModTime().Unix()
|
||||||
|
}
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
|
||||||
if fileInfo.IsDir() {
|
|
||||||
err = walkConfigDir(config.configPath, func (path string, d fs.DirEntry, err error) error {
|
|
||||||
if info, err := d.Info(); err == nil && lastMod < info.ModTime().Unix() {
|
|
||||||
return ErrEarlyReturn
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return err == ErrEarlyReturn
|
|
||||||
}
|
|
||||||
return !fileInfo.ModTime().IsZero() && config.lastFileModTime.Unix() < fileInfo.ModTime().Unix()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateLastFileModTime refreshes Config.lastFileModTime
|
// UpdateLastFileModTime refreshes Config.lastFileModTime
|
||||||
func (config *Config) UpdateLastFileModTime() {
|
func (config *Config) UpdateLastFileModTime() {
|
||||||
config.lastFileModTime = time.Now()
|
if fileInfo, err := os.Stat(config.filePath); err == nil {
|
||||||
|
if !fileInfo.ModTime().IsZero() {
|
||||||
|
config.lastFileModTime = fileInfo.ModTime()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Println("[config][UpdateLastFileModTime] Ran into error updating lastFileModTime:", err.Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadConfiguration loads the full configuration composed from the main configuration file
|
// Load loads a custom configuration file
|
||||||
// and all composed configuration files
|
// Note that the misconfiguration of some fields may lead to panics. This is on purpose.
|
||||||
func LoadConfiguration(configPath string) (*Config, error) {
|
func Load(configFile string) (*Config, error) {
|
||||||
var composedContents []byte
|
log.Printf("[config][Load] Reading configuration from configFile=%s", configFile)
|
||||||
var fileInfo os.FileInfo
|
cfg, err := readConfigurationFile(configFile)
|
||||||
var usedConfigPath string = ""
|
|
||||||
|
|
||||||
for _, cpath := range []string{configPath, DefaultConfigurationFilePath, DefaultFallbackConfigurationFilePath} {
|
|
||||||
if len(cpath) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
fileInfo, err = os.Stat(cpath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
if os.IsNotExist(err) {
|
||||||
}
|
|
||||||
|
|
||||||
usedConfigPath = configPath
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if len(usedConfigPath) == 0 {
|
|
||||||
return nil, ErrConfigFileNotFound
|
return nil, ErrConfigFileNotFound
|
||||||
}
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cfg.filePath = configFile
|
||||||
|
cfg.UpdateLastFileModTime()
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
if fileInfo.IsDir() {
|
// LoadDefaultConfiguration loads the default configuration file
|
||||||
walkConfigDir(configPath, func(path string, d fs.DirEntry, err error) error {
|
func LoadDefaultConfiguration() (*Config, error) {
|
||||||
bytes, rerr := os.ReadFile(path)
|
cfg, err := Load(DefaultConfigurationFilePath)
|
||||||
if rerr == nil {
|
if err != nil {
|
||||||
log.Printf("[config][Load] Reading configuration from configFile=%s", path)
|
if err == ErrConfigFileNotFound {
|
||||||
composedContents = append(composedContents, bytes...)
|
return Load(DefaultFallbackConfigurationFilePath)
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
bytes, serr := os.ReadFile(usedConfigPath)
|
|
||||||
if serr == nil {
|
|
||||||
log.Printf("[config][Load] Reading configuration from configFile=%s", configPath)
|
|
||||||
composedContents = bytes
|
|
||||||
}
|
}
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
if len(composedContents) == 0 {
|
func readConfigurationFile(fileName string) (config *Config, err error) {
|
||||||
return nil, ErrConfigFileNotFound
|
var bytes []byte
|
||||||
|
if bytes, err = os.ReadFile(fileName); err == nil {
|
||||||
|
// file exists, so we'll parse it and return it
|
||||||
|
return parseAndValidateConfigBytes(bytes)
|
||||||
}
|
}
|
||||||
config, err := parseAndValidateConfigBytes(composedContents)
|
return
|
||||||
config.configPath = usedConfigPath
|
|
||||||
config.UpdateLastFileModTime()
|
|
||||||
return config, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseAndValidateConfigBytes parses a Gatus configuration file into a Config struct and validates its parameters
|
// parseAndValidateConfigBytes parses a Gatus configuration file into a Config struct and validates its parameters
|
||||||
@ -354,25 +332,3 @@ func validateAlertingConfig(alertingConfig *alerting.Config, endpoints []*core.E
|
|||||||
}
|
}
|
||||||
log.Printf("[config][validateAlertingConfig] configuredProviders=%s; ignoredProviders=%s", validProviders, invalidProviders)
|
log.Printf("[config][validateAlertingConfig] configuredProviders=%s; ignoredProviders=%s", validProviders, invalidProviders)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// walkConfigDir is a wrapper for filepath.WalkDir that strips directories and non-config files
|
|
||||||
func walkConfigDir(path string, fn fs.WalkDirFunc) error {
|
|
||||||
if len(path) == 0 {
|
|
||||||
// If the user didn't provide a directory, we'll just use the default config file, so we can return nil now.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return filepath.WalkDir(path, func(path string, d fs.DirEntry, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if d == nil || d.IsDir() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
ext := filepath.Ext(path)
|
|
||||||
if ext != ".yml" && ext != ".yaml" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fn(path, d, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
@ -29,40 +29,19 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestLoadFileThatDoesNotExist(t *testing.T) {
|
func TestLoadFileThatDoesNotExist(t *testing.T) {
|
||||||
_, err := LoadConfiguration("file-that-does-not-exist.yaml")
|
_, err := Load("file-that-does-not-exist.yaml")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("Should've returned an error, because the file specified doesn't exist")
|
t.Error("Should've returned an error, because the file specified doesn't exist")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadDefaultConfigurationFile(t *testing.T) {
|
func TestLoadDefaultConfigurationFile(t *testing.T) {
|
||||||
_, err := LoadConfiguration("")
|
_, err := LoadDefaultConfiguration()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("Should've returned an error, because there's no configuration files at the default path nor the default fallback path")
|
t.Error("Should've returned an error, because there's no configuration files at the default path nor the default fallback path")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadConfigurationFile(t *testing.T) {
|
|
||||||
_, err := LoadConfiguration("../test-conf/config.yaml")
|
|
||||||
if nil != err {
|
|
||||||
t.Error("Should not have returned an error, because the configuration file exists at the provided path")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLoadDistributedConfiguration(t *testing.T) {
|
|
||||||
_, err := LoadConfiguration("../test-conf/conf.d/")
|
|
||||||
if nil != err {
|
|
||||||
t.Error("Should not have returned an error, because configuration files exist at the provided path for distributed files")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLoadCombinedConfiguration(t *testing.T) {
|
|
||||||
_, err := LoadConfiguration("../test-conf/empty-conf.d/")
|
|
||||||
if nil == err {
|
|
||||||
t.Error("Should have returned an error, because the configuration directory does not contain any configuration files")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParseAndValidateConfigBytes(t *testing.T) {
|
func TestParseAndValidateConfigBytes(t *testing.T) {
|
||||||
file := t.TempDir() + "/test.db"
|
file := t.TempDir() + "/test.db"
|
||||||
config, err := parseAndValidateConfigBytes([]byte(fmt.Sprintf(`
|
config, err := parseAndValidateConfigBytes([]byte(fmt.Sprintf(`
|
||||||
|
14
main.go
14
main.go
@ -52,14 +52,14 @@ func save() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadConfiguration() (*config.Config, error) {
|
func loadConfiguration() (cfg *config.Config, err error) {
|
||||||
var configPath = os.Getenv("GATUS_CONFIG_PATH")
|
customConfigFile := os.Getenv("GATUS_CONFIG_FILE")
|
||||||
// Backwards compatibility
|
if len(customConfigFile) > 0 {
|
||||||
if len(configPath) == 0 {
|
cfg, err = config.Load(customConfigFile)
|
||||||
configPath = os.Getenv("GATUS_CONFIG_FILE")
|
} else {
|
||||||
|
cfg, err = config.LoadDefaultConfiguration()
|
||||||
}
|
}
|
||||||
|
return
|
||||||
return config.LoadConfiguration(configPath)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// initializeStorage initializes the storage provider
|
// initializeStorage initializes the storage provider
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
endpoints:
|
|
||||||
- name: front-end
|
|
||||||
group: core
|
|
||||||
url: "https://twin.sh/health"
|
|
||||||
interval: 5m
|
|
||||||
conditions:
|
|
||||||
- "[STATUS] == 200"
|
|
||||||
- "[BODY].status == UP"
|
|
||||||
- "[RESPONSE_TIME] < 150"
|
|
||||||
|
|
||||||
- name: back-end
|
|
||||||
group: core
|
|
||||||
url: "https://example.org/"
|
|
||||||
interval: 5m
|
|
||||||
conditions:
|
|
||||||
- "[STATUS] == 200"
|
|
||||||
- "[CERTIFICATE_EXPIRATION] > 48h"
|
|
||||||
|
|
||||||
- name: monitoring
|
|
||||||
group: internal
|
|
||||||
url: "https://example.org/"
|
|
||||||
interval: 5m
|
|
||||||
conditions:
|
|
||||||
- "[STATUS] == 200"
|
|
@ -1,29 +0,0 @@
|
|||||||
endpoints:
|
|
||||||
- name: nas
|
|
||||||
group: internal
|
|
||||||
url: "https://example.org/"
|
|
||||||
interval: 5m
|
|
||||||
conditions:
|
|
||||||
- "[STATUS] == 200"
|
|
||||||
|
|
||||||
- name: example-dns-query
|
|
||||||
url: "8.8.8.8" # Address of the DNS server to use
|
|
||||||
interval: 5m
|
|
||||||
dns:
|
|
||||||
query-name: "example.com"
|
|
||||||
query-type: "A"
|
|
||||||
conditions:
|
|
||||||
- "[BODY] == 93.184.216.34"
|
|
||||||
- "[DNS_RCODE] == NOERROR"
|
|
||||||
|
|
||||||
- name: icmp-ping
|
|
||||||
url: "icmp://example.org"
|
|
||||||
interval: 1m
|
|
||||||
conditions:
|
|
||||||
- "[CONNECTED] == true"
|
|
||||||
|
|
||||||
- name: check-domain-expiration
|
|
||||||
url: "https://example.org/"
|
|
||||||
interval: 1h
|
|
||||||
conditions:
|
|
||||||
- "[DOMAIN_EXPIRATION] > 720h"
|
|
@ -1,6 +0,0 @@
|
|||||||
endpoints:
|
|
||||||
- name: example
|
|
||||||
url: https://example.org
|
|
||||||
interval: 30s
|
|
||||||
conditions:
|
|
||||||
- "[STATUS] == 200"
|
|
0
test-conf/empty-conf.d/.gitignore
vendored
0
test-conf/empty-conf.d/.gitignore
vendored
Loading…
Reference in New Issue
Block a user