Move web and ui configurations in their own packages

This commit is contained in:
TwinProduction 2021-09-22 00:47:51 -04:00
parent dda83761b5
commit df3a2016ff
14 changed files with 191 additions and 128 deletions

View File

@ -11,6 +11,8 @@ import (
"github.com/TwinProduction/gatus/alerting/alert" "github.com/TwinProduction/gatus/alerting/alert"
"github.com/TwinProduction/gatus/alerting/provider" "github.com/TwinProduction/gatus/alerting/provider"
"github.com/TwinProduction/gatus/config/maintenance" "github.com/TwinProduction/gatus/config/maintenance"
"github.com/TwinProduction/gatus/config/ui"
"github.com/TwinProduction/gatus/config/web"
"github.com/TwinProduction/gatus/core" "github.com/TwinProduction/gatus/core"
"github.com/TwinProduction/gatus/security" "github.com/TwinProduction/gatus/security"
"github.com/TwinProduction/gatus/storage" "github.com/TwinProduction/gatus/storage"
@ -25,12 +27,6 @@ const (
// DefaultFallbackConfigurationFilePath is the default fallback path that will be used to search for the // DefaultFallbackConfigurationFilePath is the default fallback path that will be used to search for the
// configuration file if DefaultConfigurationFilePath didn't work // configuration file if DefaultConfigurationFilePath didn't work
DefaultFallbackConfigurationFilePath = "config/config.yml" DefaultFallbackConfigurationFilePath = "config/config.yml"
// 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
) )
var ( var (
@ -42,10 +38,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")
// StaticFolder is the path to the location of the static folder from the root path of the project
// The only reason this is exposed is to allow running tests from a different path than the root path of the project
StaticFolder = "./web/static"
) )
// Config is the main configuration structure // Config is the main configuration structure
@ -78,10 +70,10 @@ type Config struct {
Storage *storage.Config `yaml:"storage"` Storage *storage.Config `yaml:"storage"`
// Web is the configuration for the web listener // Web is the configuration for the web listener
Web *WebConfig `yaml:"web"` Web *web.Config `yaml:"web"`
// UI is the configuration for the UI // UI is the configuration for the UI
UI *UIConfig `yaml:"ui"` UI *ui.Config `yaml:"ui"`
// Maintenance is the configuration for creating a maintenance window in which no alerts are sent // Maintenance is the configuration for creating a maintenance window in which no alerts are sent
Maintenance *maintenance.Config `yaml:"maintenance"` Maintenance *maintenance.Config `yaml:"maintenance"`
@ -221,9 +213,9 @@ func validateMaintenanceConfig(config *Config) error {
func validateUIConfig(config *Config) error { func validateUIConfig(config *Config) error {
if config.UI == nil { if config.UI == nil {
config.UI = GetDefaultUIConfig() config.UI = ui.GetDefaultConfig()
} else { } else {
if err := config.UI.validateAndSetDefaults(); err != nil { if err := config.UI.ValidateAndSetDefaults(); err != nil {
return err return err
} }
} }
@ -232,9 +224,9 @@ func validateUIConfig(config *Config) error {
func validateWebConfig(config *Config) error { func validateWebConfig(config *Config) error {
if config.Web == nil { if config.Web == nil {
config.Web = GetDefaultWebConfig() config.Web = web.GetDefaultConfig()
} else { } else {
return config.Web.validateAndSetDefaults() return config.Web.ValidateAndSetDefaults()
} }
return nil return nil
} }

View File

@ -17,6 +17,8 @@ import (
"github.com/TwinProduction/gatus/alerting/provider/telegram" "github.com/TwinProduction/gatus/alerting/provider/telegram"
"github.com/TwinProduction/gatus/alerting/provider/twilio" "github.com/TwinProduction/gatus/alerting/provider/twilio"
"github.com/TwinProduction/gatus/client" "github.com/TwinProduction/gatus/client"
"github.com/TwinProduction/gatus/config/ui"
"github.com/TwinProduction/gatus/config/web"
"github.com/TwinProduction/gatus/core" "github.com/TwinProduction/gatus/core"
) )
@ -36,9 +38,9 @@ func TestLoadDefaultConfigurationFile(t *testing.T) {
func TestParseAndValidateConfigBytes(t *testing.T) { func TestParseAndValidateConfigBytes(t *testing.T) {
file := t.TempDir() + "/test.db" file := t.TempDir() + "/test.db"
StaticFolder = "../web/static" ui.StaticFolder = "../web/static"
defer func() { defer func() {
StaticFolder = "./web/static" ui.StaticFolder = "./web/static"
}() }()
config, err := parseAndValidateConfigBytes([]byte(fmt.Sprintf(` config, err := parseAndValidateConfigBytes([]byte(fmt.Sprintf(`
storage: storage:
@ -175,11 +177,11 @@ services:
if config.Metrics { if config.Metrics {
t.Error("Metrics should've been false by default") t.Error("Metrics should've been false by default")
} }
if config.Web.Address != DefaultAddress { if config.Web.Address != web.DefaultAddress {
t.Errorf("Bind address should have been %s, because it is the default value", DefaultAddress) t.Errorf("Bind address should have been %s, because it is the default value", web.DefaultAddress)
} }
if config.Web.Port != DefaultPort { if config.Web.Port != web.DefaultPort {
t.Errorf("Port should have been %d, because it is the default value", DefaultPort) t.Errorf("Port should have been %d, because it is the default value", web.DefaultPort)
} }
if config.Services[0].URL != "https://twin.sh/health" { if config.Services[0].URL != "https://twin.sh/health" {
t.Errorf("URL should have been %s", "https://twin.sh/health") t.Errorf("URL should have been %s", "https://twin.sh/health")
@ -226,8 +228,8 @@ services:
if config.Web.Address != "127.0.0.1" { if config.Web.Address != "127.0.0.1" {
t.Errorf("Bind address should have been %s, because it is specified in config", "127.0.0.1") t.Errorf("Bind address should have been %s, because it is specified in config", "127.0.0.1")
} }
if config.Web.Port != DefaultPort { if config.Web.Port != web.DefaultPort {
t.Errorf("Port should have been %d, because it is the default value", DefaultPort) t.Errorf("Port should have been %d, because it is the default value", web.DefaultPort)
} }
} }
@ -256,8 +258,8 @@ services:
if config.Services[0].Interval != 60*time.Second { if config.Services[0].Interval != 60*time.Second {
t.Errorf("Interval should have been %s, because it is the default value", 60*time.Second) t.Errorf("Interval should have been %s, because it is the default value", 60*time.Second)
} }
if config.Web.Address != DefaultAddress { if config.Web.Address != web.DefaultAddress {
t.Errorf("Bind address should have been %s, because it is the default value", DefaultAddress) t.Errorf("Bind address should have been %s, because it is the default value", web.DefaultAddress)
} }
if config.Web.Port != 12345 { if config.Web.Port != 12345 {
t.Errorf("Port should have been %d, because it is specified in config", 12345) t.Errorf("Port should have been %d, because it is specified in config", 12345)
@ -340,11 +342,11 @@ services:
if config.Services[0].Interval != 60*time.Second { if config.Services[0].Interval != 60*time.Second {
t.Errorf("Interval should have been %s, because it is the default value", 60*time.Second) t.Errorf("Interval should have been %s, because it is the default value", 60*time.Second)
} }
if config.Web.Address != DefaultAddress { if config.Web.Address != web.DefaultAddress {
t.Errorf("Bind address should have been %s, because it is the default value", DefaultAddress) t.Errorf("Bind address should have been %s, because it is the default value", web.DefaultAddress)
} }
if config.Web.Port != DefaultPort { if config.Web.Port != web.DefaultPort {
t.Errorf("Port should have been %d, because it is the default value", DefaultPort) t.Errorf("Port should have been %d, because it is the default value", web.DefaultPort)
} }
if userAgent := config.Services[0].Headers["User-Agent"]; userAgent != "Test/2.0" { if userAgent := config.Services[0].Headers["User-Agent"]; userAgent != "Test/2.0" {
t.Errorf("User-Agent should've been %s, got %s", "Test/2.0", userAgent) t.Errorf("User-Agent should've been %s, got %s", "Test/2.0", userAgent)

View File

@ -14,7 +14,7 @@ func TestGetDefaultConfig(t *testing.T) {
} }
} }
func TestConfig_Validate(t *testing.T) { func TestConfig_ValidateAndSetDefaults(t *testing.T) {
yes, no := true, false yes, no := true, false
scenarios := []struct { scenarios := []struct {
name string name string

View File

@ -1,41 +0,0 @@
package config
import (
"bytes"
"html/template"
)
const (
defaultTitle = "Health Dashboard | Gatus"
defaultLogo = ""
)
// UIConfig is the configuration for the UI of Gatus
type UIConfig struct {
Title string `yaml:"title"` // Title of the page
Logo string `yaml:"logo"` // Logo to display on the page
}
// GetDefaultUIConfig returns a UIConfig struct with the default values
func GetDefaultUIConfig() *UIConfig {
return &UIConfig{
Title: defaultTitle,
Logo: defaultLogo,
}
}
func (cfg *UIConfig) validateAndSetDefaults() error {
if len(cfg.Title) == 0 {
cfg.Title = defaultTitle
}
t, err := template.ParseFiles(StaticFolder + "/index.html")
if err != nil {
return err
}
var buffer bytes.Buffer
err = t.Execute(&buffer, cfg)
if err != nil {
return err
}
return nil
}

48
config/ui/ui.go Normal file
View File

@ -0,0 +1,48 @@
package ui
import (
"bytes"
"html/template"
)
const (
defaultTitle = "Health Dashboard | Gatus"
defaultLogo = ""
)
var (
// StaticFolder is the path to the location of the static folder from the root path of the project
// The only reason this is exposed is to allow running tests from a different path than the root path of the project
StaticFolder = "./web/static"
)
// Config is the configuration for the UI of Gatus
type Config struct {
Title string `yaml:"title"` // Title of the page
Logo string `yaml:"logo"` // Logo to display on the page
}
// GetDefaultConfig returns a Config struct with the default values
func GetDefaultConfig() *Config {
return &Config{
Title: defaultTitle,
Logo: defaultLogo,
}
}
// ValidateAndSetDefaults validates the UI configuration and sets the default values if necessary.
func (cfg *Config) ValidateAndSetDefaults() error {
if len(cfg.Title) == 0 {
cfg.Title = defaultTitle
}
t, err := template.ParseFiles(StaticFolder + "/index.html")
if err != nil {
return err
}
var buffer bytes.Buffer
err = t.Execute(&buffer, cfg)
if err != nil {
return err
}
return nil
}

26
config/ui/ui_test.go Normal file
View File

@ -0,0 +1,26 @@
package ui
import (
"testing"
)
func TestConfig_ValidateAndSetDefaults(t *testing.T) {
StaticFolder = "../../web/static"
defer func() {
StaticFolder = "./web/static"
}()
cfg := &Config{Title: ""}
if err := cfg.ValidateAndSetDefaults(); err != nil {
t.Error("expected no error, got", err.Error())
}
}
func TestGetDefaultConfig(t *testing.T) {
defaultConfig := GetDefaultConfig()
if defaultConfig.Title != defaultTitle {
t.Error("expected GetDefaultConfig() to return defaultTitle, got", defaultConfig.Title)
}
if defaultConfig.Logo != defaultLogo {
t.Error("expected GetDefaultConfig() to return defaultLogo, got", defaultConfig.Logo)
}
}

View File

@ -1,24 +0,0 @@
package config
import "testing"
func TestUIConfig_validateAndSetDefaults(t *testing.T) {
StaticFolder = "../web/static"
defer func() {
StaticFolder = "./web/static"
}()
uiConfig := &UIConfig{Title: ""}
if err := uiConfig.validateAndSetDefaults(); err != nil {
t.Error("expected no error, got", err.Error())
}
}
func TestGetDefaultUIConfig(t *testing.T) {
defaultUIConfig := GetDefaultUIConfig()
if defaultUIConfig.Title != defaultTitle {
t.Error("expected GetDefaultUIConfig() to return defaultTitle, got", defaultUIConfig.Title)
}
if defaultUIConfig.Logo != defaultLogo {
t.Error("expected GetDefaultUIConfig() to return defaultLogo, got", defaultUIConfig.Logo)
}
}

View File

@ -1,13 +1,21 @@
package config package web
import ( import (
"fmt" "fmt"
"math" "math"
) )
// WebConfig is the structure which supports the configuration of the endpoint const (
// 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
)
// Config is the structure which supports the configuration of the endpoint
// which provides access to the web frontend // which provides access to the web frontend
type WebConfig struct { type Config struct {
// Address to listen on (defaults to 0.0.0.0 specified by DefaultAddress) // Address to listen on (defaults to 0.0.0.0 specified by DefaultAddress)
Address string `yaml:"address"` Address string `yaml:"address"`
@ -15,13 +23,13 @@ type WebConfig struct {
Port int `yaml:"port"` Port int `yaml:"port"`
} }
// GetDefaultWebConfig returns a WebConfig struct with the default values // GetDefaultConfig returns a Config struct with the default values
func GetDefaultWebConfig() *WebConfig { func GetDefaultConfig() *Config {
return &WebConfig{Address: DefaultAddress, Port: DefaultPort} return &Config{Address: DefaultAddress, Port: DefaultPort}
} }
// validateAndSetDefaults checks and sets the default values for fields that are not set // ValidateAndSetDefaults validates the web configuration and sets the default values if necessary.
func (web *WebConfig) validateAndSetDefaults() error { func (web *Config) ValidateAndSetDefaults() error {
// Validate the Address // Validate the Address
if len(web.Address) == 0 { if len(web.Address) == 0 {
web.Address = DefaultAddress web.Address = DefaultAddress
@ -36,6 +44,6 @@ func (web *WebConfig) validateAndSetDefaults() error {
} }
// SocketAddress returns the combination of the Address and the Port // SocketAddress returns the combination of the Address and the Port
func (web *WebConfig) SocketAddress() string { func (web *Config) SocketAddress() string {
return fmt.Sprintf("%s:%d", web.Address, web.Port) return fmt.Sprintf("%s:%d", web.Address, web.Port)
} }

65
config/web/web_test.go Normal file
View File

@ -0,0 +1,65 @@
package web
import (
"testing"
)
func TestGetDefaultConfig(t *testing.T) {
defaultConfig := GetDefaultConfig()
if defaultConfig.Port != DefaultPort {
t.Error("expected default config to have the default port")
}
if defaultConfig.Address != DefaultAddress {
t.Error("expected default config to have the default address")
}
}
func TestConfig_ValidateAndSetDefaults(t *testing.T) {
scenarios := []struct {
name string
cfg *Config
expectedAddress string
expectedPort int
expectedErr bool
}{
{
name: "no-explicit-config",
cfg: &Config{},
expectedAddress: "0.0.0.0",
expectedPort: 8080,
expectedErr: false,
},
{
name: "invalid-port",
cfg: &Config{Port: 100000000},
expectedErr: true,
},
}
for _, scenario := range scenarios {
t.Run(scenario.name, func(t *testing.T) {
err := scenario.cfg.ValidateAndSetDefaults()
if (err != nil) != scenario.expectedErr {
t.Errorf("expected the existence of an error to be %v, got %v", scenario.expectedErr, err)
return
}
if !scenario.expectedErr {
if scenario.cfg.Port != scenario.expectedPort {
t.Errorf("expected port to be %d, got %d", scenario.expectedPort, scenario.cfg.Port)
}
if scenario.cfg.Address != scenario.expectedAddress {
t.Errorf("expected address to be %s, got %s", scenario.expectedAddress, scenario.cfg.Address)
}
}
})
}
}
func TestConfig_SocketAddress(t *testing.T) {
web := &Config{
Address: "0.0.0.0",
Port: 8081,
}
if web.SocketAddress() != "0.0.0.0:8081" {
t.Errorf("expected %s, got %s", "0.0.0.0:8081", web.SocketAddress())
}
}

View File

@ -1,15 +0,0 @@
package config
import (
"testing"
)
func TestWebConfig_SocketAddress(t *testing.T) {
web := &WebConfig{
Address: "0.0.0.0",
Port: 8081,
}
if web.SocketAddress() != "0.0.0.0:8081" {
t.Errorf("expected %s, got %s", "0.0.0.0:8081", web.SocketAddress())
}
}

View File

@ -8,7 +8,8 @@ import (
"os" "os"
"time" "time"
"github.com/TwinProduction/gatus/config" "github.com/TwinProduction/gatus/config/ui"
"github.com/TwinProduction/gatus/config/web"
"github.com/TwinProduction/gatus/controller/handler" "github.com/TwinProduction/gatus/controller/handler"
"github.com/TwinProduction/gatus/security" "github.com/TwinProduction/gatus/security"
) )
@ -20,8 +21,8 @@ var (
) )
// Handle creates the router and starts the server // Handle creates the router and starts the server
func Handle(securityConfig *security.Config, webConfig *config.WebConfig, uiConfig *config.UIConfig, enableMetrics bool) { func Handle(securityConfig *security.Config, webConfig *web.Config, uiConfig *ui.Config, enableMetrics bool) {
var router http.Handler = handler.CreateRouter(config.StaticFolder, securityConfig, uiConfig, enableMetrics) var router http.Handler = handler.CreateRouter(ui.StaticFolder, securityConfig, uiConfig, enableMetrics)
if os.Getenv("ENVIRONMENT") == "dev" { if os.Getenv("ENVIRONMENT") == "dev" {
router = handler.DevelopmentCORS(router) router = handler.DevelopmentCORS(router)
} }

View File

@ -8,12 +8,13 @@ import (
"testing" "testing"
"github.com/TwinProduction/gatus/config" "github.com/TwinProduction/gatus/config"
"github.com/TwinProduction/gatus/config/web"
"github.com/TwinProduction/gatus/core" "github.com/TwinProduction/gatus/core"
) )
func TestHandle(t *testing.T) { func TestHandle(t *testing.T) {
cfg := &config.Config{ cfg := &config.Config{
Web: &config.WebConfig{ Web: &web.Config{
Address: "0.0.0.0", Address: "0.0.0.0",
Port: rand.Intn(65534), Port: rand.Intn(65534),
}, },

View File

@ -3,14 +3,14 @@ package handler
import ( import (
"net/http" "net/http"
"github.com/TwinProduction/gatus/config" "github.com/TwinProduction/gatus/config/ui"
"github.com/TwinProduction/gatus/security" "github.com/TwinProduction/gatus/security"
"github.com/TwinProduction/health" "github.com/TwinProduction/health"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/client_golang/prometheus/promhttp"
) )
func CreateRouter(staticFolder string, securityConfig *security.Config, uiConfig *config.UIConfig, enabledMetrics bool) *mux.Router { func CreateRouter(staticFolder string, securityConfig *security.Config, uiConfig *ui.Config, enabledMetrics bool) *mux.Router {
router := mux.NewRouter() router := mux.NewRouter()
if enabledMetrics { if enabledMetrics {
router.Handle("/metrics", promhttp.Handler()).Methods("GET") router.Handle("/metrics", promhttp.Handler()).Methods("GET")

View File

@ -5,10 +5,10 @@ import (
"log" "log"
"net/http" "net/http"
"github.com/TwinProduction/gatus/config" "github.com/TwinProduction/gatus/config/ui"
) )
func SinglePageApplication(staticFolder string, ui *config.UIConfig) http.HandlerFunc { func SinglePageApplication(staticFolder string, ui *ui.Config) http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) { return func(writer http.ResponseWriter, request *http.Request) {
t, err := template.ParseFiles(staticFolder + "/index.html") t, err := template.ParseFiles(staticFolder + "/index.html")
if err != nil { if err != nil {