2023-12-21 16:04:04 +00:00
|
|
|
package server
|
|
|
|
|
|
|
|
import (
|
2024-01-24 22:21:48 +00:00
|
|
|
"errors"
|
2023-12-21 16:04:04 +00:00
|
|
|
"fmt"
|
2024-01-25 18:57:41 +00:00
|
|
|
"git.myservermanager.com/varakh/upda/util"
|
2023-12-21 16:04:04 +00:00
|
|
|
"github.com/adrg/xdg"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
"go.uber.org/zap/zapcore"
|
|
|
|
"gorm.io/driver/postgres"
|
|
|
|
"gorm.io/driver/sqlite"
|
|
|
|
"gorm.io/gorm"
|
2024-01-24 22:21:48 +00:00
|
|
|
"gorm.io/gorm/logger"
|
2023-12-21 16:04:04 +00:00
|
|
|
"log"
|
|
|
|
"moul.io/zapgorm2"
|
|
|
|
"os"
|
2024-01-25 18:57:41 +00:00
|
|
|
"path/filepath"
|
2023-12-21 16:04:04 +00:00
|
|
|
"strconv"
|
2024-04-26 18:24:47 +00:00
|
|
|
"strings"
|
2023-12-21 16:04:04 +00:00
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
type appConfig struct {
|
2024-01-24 22:21:48 +00:00
|
|
|
timeZone string
|
|
|
|
isDevelopment bool
|
|
|
|
isDebug bool
|
2023-12-21 16:04:04 +00:00
|
|
|
}
|
|
|
|
|
2024-10-24 20:52:41 +00:00
|
|
|
type webConfig struct {
|
|
|
|
title string
|
|
|
|
apiUrl string
|
|
|
|
}
|
|
|
|
|
2023-12-21 16:04:04 +00:00
|
|
|
type serverConfig struct {
|
2024-06-10 18:03:25 +00:00
|
|
|
port int
|
|
|
|
listen string
|
|
|
|
tlsEnabled bool
|
|
|
|
tlsCertPath string
|
|
|
|
tlsKeyPath string
|
|
|
|
timeout time.Duration
|
|
|
|
corsAllowCredentials bool
|
|
|
|
corsAllowOrigins []string
|
|
|
|
corsAllowMethods []string
|
|
|
|
corsAllowHeaders []string
|
|
|
|
corsExposeHeaders []string
|
2023-12-21 16:04:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type authConfig struct {
|
2024-04-26 18:24:47 +00:00
|
|
|
authMethod string
|
|
|
|
basicAuthUser string
|
|
|
|
basicAuthPassword string
|
|
|
|
basicAuthCredentials map[string]string
|
2023-12-21 16:04:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type taskConfig struct {
|
|
|
|
updateCleanStaleEnabled bool
|
2024-05-16 07:33:32 +00:00
|
|
|
updateCleanStaleInterval time.Duration
|
2023-12-21 16:04:04 +00:00
|
|
|
updateCleanStaleMaxAge time.Duration
|
|
|
|
eventCleanStaleEnabled bool
|
2024-05-16 07:33:32 +00:00
|
|
|
eventCleanStaleInterval time.Duration
|
2023-12-21 16:04:04 +00:00
|
|
|
eventCleanStaleMaxAge time.Duration
|
2024-04-26 12:37:50 +00:00
|
|
|
actionsEnqueueEnabled bool
|
2024-05-16 07:33:32 +00:00
|
|
|
actionsEnqueueInterval time.Duration
|
2024-04-26 12:37:50 +00:00
|
|
|
actionsEnqueueBatchSize int
|
|
|
|
actionsInvokeEnabled bool
|
2024-05-16 07:33:32 +00:00
|
|
|
actionsInvokeInterval time.Duration
|
2024-04-26 12:37:50 +00:00
|
|
|
actionsInvokeBatchSize int
|
|
|
|
actionsInvokeMaxRetries int
|
|
|
|
actionsCleanStaleEnabled bool
|
2024-05-16 07:33:32 +00:00
|
|
|
actionsCleanStaleInterval time.Duration
|
2024-04-26 12:37:50 +00:00
|
|
|
actionsCleanStaleMaxAge time.Duration
|
2024-05-16 07:33:32 +00:00
|
|
|
prometheusRefreshInterval time.Duration
|
2023-12-22 12:13:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type lockConfig struct {
|
|
|
|
redisEnabled bool
|
|
|
|
redisUrl string
|
2023-12-21 16:04:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type webhookConfig struct {
|
|
|
|
tokenLength int
|
|
|
|
}
|
|
|
|
|
|
|
|
type prometheusConfig struct {
|
|
|
|
enabled bool
|
|
|
|
path string
|
|
|
|
secureTokenEnabled bool
|
|
|
|
secureToken string
|
|
|
|
}
|
|
|
|
|
|
|
|
type Environment struct {
|
|
|
|
appConfig *appConfig
|
2024-10-24 20:52:41 +00:00
|
|
|
webConfig *webConfig
|
2023-12-21 16:04:04 +00:00
|
|
|
authConfig *authConfig
|
|
|
|
serverConfig *serverConfig
|
|
|
|
taskConfig *taskConfig
|
2023-12-22 12:13:44 +00:00
|
|
|
lockConfig *lockConfig
|
2023-12-21 16:04:04 +00:00
|
|
|
webhookConfig *webhookConfig
|
|
|
|
prometheusConfig *prometheusConfig
|
|
|
|
db *gorm.DB
|
|
|
|
}
|
|
|
|
|
|
|
|
func bootstrapEnvironment() *Environment {
|
|
|
|
var err error
|
|
|
|
|
2024-01-24 22:21:48 +00:00
|
|
|
// bootstrap logging (configured independently and required before any other action)
|
|
|
|
loggingLevel := os.Getenv(envLoggingLevel)
|
|
|
|
if loggingLevel == "" {
|
|
|
|
if err = os.Setenv(envLoggingLevel, loggingLevelDefault); err != nil {
|
|
|
|
log.Fatalf("Cannot set logging level: %v", err)
|
2023-12-21 16:04:04 +00:00
|
|
|
}
|
2024-01-24 22:21:48 +00:00
|
|
|
loggingLevel = os.Getenv(envLoggingLevel)
|
|
|
|
}
|
|
|
|
var level zap.AtomicLevel
|
|
|
|
if level, err = zap.ParseAtomicLevel(loggingLevel); err != nil {
|
|
|
|
log.Fatalf("Cannot parse logging level: %v", err)
|
|
|
|
}
|
|
|
|
loggingEncoding := os.Getenv(envLoggingEncoding)
|
|
|
|
if loggingEncoding == "" {
|
|
|
|
if err = os.Setenv(envLoggingEncoding, loggingEncodingDefault); err != nil {
|
|
|
|
log.Fatalf("Cannot set logging encoding: %v", err)
|
|
|
|
}
|
|
|
|
loggingEncoding = os.Getenv(envLoggingEncoding)
|
|
|
|
}
|
|
|
|
if loggingEncoding != "json" && loggingEncoding != "console" {
|
|
|
|
log.Fatalf("Cannot parse logging level: %v", errors.New("only 'json' and 'console' are allowed logging encodings"))
|
|
|
|
}
|
|
|
|
isDebug := level.Level() == zap.DebugLevel
|
|
|
|
isDevelopment := os.Getenv(envDevelopment) == "true"
|
|
|
|
var loggingEncoderConfig zapcore.EncoderConfig
|
|
|
|
if loggingEncoding == "json" {
|
|
|
|
loggingEncoderConfig = zap.NewProductionEncoderConfig()
|
|
|
|
} else {
|
|
|
|
loggingEncoderConfig = zap.NewDevelopmentEncoderConfig()
|
2023-12-21 16:04:04 +00:00
|
|
|
}
|
|
|
|
|
2024-01-25 18:57:41 +00:00
|
|
|
logPaths := []string{"stderr"}
|
|
|
|
loggingDirectory := os.Getenv(envLoggingDirectory)
|
|
|
|
|
|
|
|
if loggingDirectory != "" {
|
|
|
|
logFile := filepath.Join(loggingDirectory, loggingFileNameDefault)
|
|
|
|
|
|
|
|
if err = util.CreateFileWithParent(logFile); err != nil {
|
|
|
|
log.Fatalf("Log file '%s' cannot be created: %v", loggingDirectory, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
logPaths = append(logPaths, logFile)
|
|
|
|
}
|
|
|
|
|
2024-01-24 22:21:48 +00:00
|
|
|
var zapConfig *zap.Config
|
|
|
|
if isDebug {
|
|
|
|
zapConfig = &zap.Config{
|
|
|
|
Level: level,
|
|
|
|
Development: isDevelopment,
|
|
|
|
Encoding: loggingEncoding,
|
|
|
|
EncoderConfig: loggingEncoderConfig,
|
2024-01-25 18:57:41 +00:00
|
|
|
OutputPaths: logPaths,
|
|
|
|
ErrorOutputPaths: logPaths,
|
2024-01-24 22:21:48 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
zapConfig = &zap.Config{
|
|
|
|
Level: level,
|
|
|
|
Development: isDevelopment,
|
|
|
|
Sampling: &zap.SamplingConfig{
|
|
|
|
Initial: 100,
|
|
|
|
Thereafter: 100,
|
|
|
|
},
|
|
|
|
Encoding: loggingEncoding,
|
|
|
|
EncoderConfig: loggingEncoderConfig,
|
2024-01-25 18:57:41 +00:00
|
|
|
OutputPaths: logPaths,
|
|
|
|
ErrorOutputPaths: logPaths,
|
2024-01-24 22:21:48 +00:00
|
|
|
}
|
2023-12-21 16:04:04 +00:00
|
|
|
}
|
|
|
|
|
2024-01-24 22:21:48 +00:00
|
|
|
zapLogger := zap.Must(zapConfig.Build())
|
2024-06-03 19:11:33 +00:00
|
|
|
defer func(zapLogger *zap.Logger) {
|
|
|
|
_ = zapLogger.Sync()
|
|
|
|
}(zapLogger)
|
2024-01-24 22:21:48 +00:00
|
|
|
zap.ReplaceGlobals(zapLogger)
|
2023-12-21 16:04:04 +00:00
|
|
|
|
|
|
|
// assign defaults from given environment variables and validate
|
|
|
|
bootstrapFromEnvironmentAndValidate()
|
|
|
|
|
|
|
|
// parse environment variables in actual configuration structs
|
|
|
|
// app config
|
2024-01-24 22:21:48 +00:00
|
|
|
ac := &appConfig{
|
|
|
|
timeZone: os.Getenv(envTZ),
|
|
|
|
isDebug: isDebug,
|
|
|
|
isDevelopment: isDevelopment,
|
2023-12-21 16:04:04 +00:00
|
|
|
}
|
|
|
|
|
2024-10-24 20:52:41 +00:00
|
|
|
// web config
|
|
|
|
var webC *webConfig
|
|
|
|
webC = &webConfig{
|
|
|
|
title: os.Getenv(envWebTitle),
|
|
|
|
apiUrl: os.Getenv(envWebApiUrl),
|
|
|
|
}
|
|
|
|
|
2023-12-21 16:04:04 +00:00
|
|
|
// server config
|
|
|
|
var sc *serverConfig
|
|
|
|
|
|
|
|
var serverPort int
|
|
|
|
if serverPort, err = strconv.Atoi(os.Getenv(envServerPort)); err != nil {
|
|
|
|
zap.L().Sugar().Fatalf("Invalid server port. Reason: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
serverTlsEnabled := os.Getenv(envServerTlsEnabled) == "true"
|
|
|
|
|
|
|
|
if serverTlsEnabled {
|
|
|
|
failIfEnvKeyNotPresent(envServerTlsCertPath)
|
|
|
|
failIfEnvKeyNotPresent(envServerTlsKeyPath)
|
|
|
|
}
|
|
|
|
|
|
|
|
var serverTimeout time.Duration
|
|
|
|
var errParse error
|
|
|
|
if serverTimeout, errParse = time.ParseDuration(os.Getenv(envServerTimeout)); errParse != nil {
|
|
|
|
zap.L().Sugar().Fatalf("Could not parse timeout. Reason: %s", errParse.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
sc = &serverConfig{
|
2024-06-10 18:03:25 +00:00
|
|
|
port: serverPort,
|
|
|
|
timeout: serverTimeout,
|
|
|
|
listen: os.Getenv(envServerListen),
|
|
|
|
tlsEnabled: serverTlsEnabled,
|
|
|
|
tlsCertPath: os.Getenv(envServerTlsCertPath),
|
|
|
|
tlsKeyPath: os.Getenv(envServerTlsKeyPath),
|
|
|
|
corsAllowCredentials: os.Getenv(envCorsAllowCredentials) == "true",
|
|
|
|
corsExposeHeaders: []string{os.Getenv(envCorsExposeHeaders)},
|
|
|
|
corsAllowOrigins: []string{os.Getenv(envCorsAllowOrigins)},
|
|
|
|
corsAllowMethods: []string{os.Getenv(envCorsAllowMethods)},
|
|
|
|
corsAllowHeaders: []string{os.Getenv(envCorsAllowHeaders)},
|
2023-12-21 16:04:04 +00:00
|
|
|
}
|
|
|
|
|
2024-04-26 18:24:47 +00:00
|
|
|
authMode := os.Getenv(envAuthMode)
|
|
|
|
|
|
|
|
if authMode != authModeBasicSingle && authMode != authModeBasicCredentials {
|
|
|
|
zap.L().Sugar().Fatalln("Invalid auth mode. Reason: must be one of ['basic_single','basic_credentials'")
|
|
|
|
}
|
|
|
|
|
2024-01-24 22:21:48 +00:00
|
|
|
authC := &authConfig{
|
2024-04-26 18:24:47 +00:00
|
|
|
authMethod: authMode,
|
|
|
|
}
|
|
|
|
|
|
|
|
if authModeBasicSingle == authMode {
|
|
|
|
failIfEnvKeyNotPresent(envBasicAuthUser)
|
|
|
|
failIfEnvKeyNotPresent(envBasicAuthPassword)
|
|
|
|
authC.basicAuthUser = os.Getenv(envBasicAuthUser)
|
|
|
|
authC.basicAuthPassword = os.Getenv(envBasicAuthPassword)
|
|
|
|
}
|
|
|
|
if authModeBasicCredentials == authMode {
|
|
|
|
failIfEnvKeyNotPresent(envBasicAuthCredentials)
|
|
|
|
authC.basicAuthCredentials = parseBasicAuthCredentials(envBasicAuthCredentials)
|
2023-12-21 16:04:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// task config
|
|
|
|
var tc *taskConfig
|
|
|
|
|
2024-05-16 07:33:32 +00:00
|
|
|
updateCleanStaleInterval := parseDuration(envTaskUpdateCleanStaleInterval)
|
|
|
|
updateCleanStaleMaxAge := parseDuration(envTaskUpdateCleanStaleMaxAge)
|
|
|
|
eventCleanStaleMaxAge := parseDuration(envTaskEventCleanStaleMaxAge)
|
|
|
|
actionsCleanStaleMaxAge := parseDuration(envTaskActionsCleanStaleMaxAge)
|
|
|
|
eventCleanStaleInterval := parseDuration(envTaskEventCleanStaleInterval)
|
|
|
|
actionsEnqueueInterval := parseDuration(envTaskActionsEnqueueInterval)
|
|
|
|
actionsInvokeInterval := parseDuration(envTaskActionsInvokeInterval)
|
|
|
|
actionsCleanStaleInterval := parseDuration(envTaskActionsCleanStaleInterval)
|
|
|
|
prometheusRefreshInterval := parseDuration(envTaskPrometheusRefreshInterval)
|
2023-12-21 16:04:04 +00:00
|
|
|
|
2024-04-26 12:37:50 +00:00
|
|
|
var actionsEnqueueBatchSize int
|
|
|
|
if actionsEnqueueBatchSize, err = strconv.Atoi(os.Getenv(envTaskActionsEnqueueBatchSize)); err != nil {
|
|
|
|
zap.L().Sugar().Fatalf("Invalid actions enqueue batch size. Reason: %v", err)
|
|
|
|
}
|
|
|
|
if actionsEnqueueBatchSize <= 0 {
|
|
|
|
zap.L().Sugar().Fatalf("Invalid actions enqueue batch size, must be a positive number.")
|
|
|
|
}
|
|
|
|
|
|
|
|
var actionsInvokeBatchSize int
|
|
|
|
if actionsInvokeBatchSize, err = strconv.Atoi(os.Getenv(envTaskActionsInvokeBatchSize)); err != nil {
|
|
|
|
zap.L().Sugar().Fatalf("Invalid actions invoke batch size. Reason: %v", err)
|
|
|
|
}
|
|
|
|
if actionsInvokeBatchSize <= 0 {
|
|
|
|
zap.L().Sugar().Fatalf("Invalid actions invoke batch size, must be a positive number.")
|
|
|
|
}
|
|
|
|
|
|
|
|
var actionsInvokeMaxRetries int
|
|
|
|
if actionsInvokeMaxRetries, err = strconv.Atoi(os.Getenv(envTaskActionsInvokeMaxRetries)); err != nil {
|
|
|
|
zap.L().Sugar().Fatalf("Invalid actions invoke max retries. Reason: %v", err)
|
|
|
|
}
|
|
|
|
if actionsInvokeMaxRetries <= 0 {
|
|
|
|
zap.L().Sugar().Fatalf("Invalid actions invoke max retries, must be a positive number.")
|
|
|
|
}
|
|
|
|
|
2023-12-21 16:04:04 +00:00
|
|
|
tc = &taskConfig{
|
|
|
|
updateCleanStaleEnabled: os.Getenv(envTaskUpdateCleanStaleEnabled) == "true",
|
2024-05-16 07:33:32 +00:00
|
|
|
updateCleanStaleInterval: updateCleanStaleInterval,
|
2023-12-21 16:04:04 +00:00
|
|
|
updateCleanStaleMaxAge: updateCleanStaleMaxAge,
|
|
|
|
eventCleanStaleEnabled: os.Getenv(envTaskEventCleanStaleEnabled) == "true",
|
2024-05-16 07:33:32 +00:00
|
|
|
eventCleanStaleInterval: eventCleanStaleInterval,
|
2023-12-21 16:04:04 +00:00
|
|
|
eventCleanStaleMaxAge: eventCleanStaleMaxAge,
|
2024-04-26 12:37:50 +00:00
|
|
|
actionsEnqueueEnabled: os.Getenv(envTaskActionsEnqueueEnabled) == "true",
|
2024-05-16 07:33:32 +00:00
|
|
|
actionsEnqueueInterval: actionsEnqueueInterval,
|
2024-04-26 12:37:50 +00:00
|
|
|
actionsEnqueueBatchSize: actionsEnqueueBatchSize,
|
|
|
|
actionsInvokeEnabled: os.Getenv(envTaskActionsInvokeEnabled) == "true",
|
2024-05-16 07:33:32 +00:00
|
|
|
actionsInvokeInterval: actionsInvokeInterval,
|
2024-04-26 12:37:50 +00:00
|
|
|
actionsInvokeBatchSize: actionsInvokeBatchSize,
|
|
|
|
actionsInvokeMaxRetries: actionsInvokeMaxRetries,
|
|
|
|
actionsCleanStaleEnabled: os.Getenv(envTaskActionsCleanStaleEnabled) == "true",
|
2024-05-16 07:33:32 +00:00
|
|
|
actionsCleanStaleInterval: actionsCleanStaleInterval,
|
2024-04-26 12:37:50 +00:00
|
|
|
actionsCleanStaleMaxAge: actionsCleanStaleMaxAge,
|
2024-05-16 07:33:32 +00:00
|
|
|
prometheusRefreshInterval: prometheusRefreshInterval,
|
2023-12-22 12:13:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var lc *lockConfig
|
|
|
|
lc = &lockConfig{
|
|
|
|
redisEnabled: os.Getenv(envLockRedisEnabled) == "true",
|
|
|
|
redisUrl: os.Getenv(envLockRedisUrl),
|
2023-12-21 16:04:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
webhookTokenLength := 32
|
|
|
|
if webhookTokenLength, err = strconv.Atoi(os.Getenv(envWebhooksTokenLength)); err != nil {
|
|
|
|
zap.L().Sugar().Fatalf("Invalid webhook token length. Reason: %v", err)
|
|
|
|
}
|
|
|
|
if webhookTokenLength <= 0 {
|
|
|
|
zap.L().Sugar().Fatalln("Invalid webhook token length. Reason: must be a positive number")
|
|
|
|
}
|
|
|
|
|
2024-01-24 22:21:48 +00:00
|
|
|
wc := &webhookConfig{
|
2023-12-21 16:04:04 +00:00
|
|
|
tokenLength: webhookTokenLength,
|
|
|
|
}
|
|
|
|
|
2024-01-24 22:21:48 +00:00
|
|
|
pc := &prometheusConfig{
|
2023-12-21 16:04:04 +00:00
|
|
|
enabled: os.Getenv(envPrometheusEnabled) == "true",
|
|
|
|
path: os.Getenv(envPrometheusMetricsPath),
|
|
|
|
secureTokenEnabled: os.Getenv(envPrometheusSecureTokenEnabled) == "true",
|
|
|
|
secureToken: os.Getenv(envPrometheusSecureToken),
|
|
|
|
}
|
|
|
|
|
2024-01-24 22:21:48 +00:00
|
|
|
if pc.enabled && pc.secureTokenEnabled {
|
2023-12-21 16:04:04 +00:00
|
|
|
failIfEnvKeyNotPresent(envPrometheusSecureToken)
|
|
|
|
}
|
|
|
|
|
|
|
|
// database setup
|
2024-01-24 22:21:48 +00:00
|
|
|
gormConfig := &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)}
|
|
|
|
if isDebug && isDevelopment {
|
|
|
|
gormZapLogger := zap.Must(zapConfig.Build())
|
2024-06-03 19:11:33 +00:00
|
|
|
defer func(gormZapLogger *zap.Logger) {
|
|
|
|
_ = gormZapLogger.Sync()
|
|
|
|
}(gormZapLogger)
|
2024-01-24 22:21:48 +00:00
|
|
|
gormLogger := zapgorm2.New(gormZapLogger)
|
|
|
|
gormConfig = &gorm.Config{Logger: gormLogger}
|
|
|
|
}
|
2023-12-21 16:04:04 +00:00
|
|
|
|
|
|
|
var db *gorm.DB
|
|
|
|
zap.L().Sugar().Infof("Using database type '%s'", os.Getenv(envDbType))
|
|
|
|
|
|
|
|
if os.Getenv(envDbType) == dbTypeSqlite {
|
|
|
|
if os.Getenv(envDbSqliteFile) == "" {
|
|
|
|
var defaultDbFile string
|
2024-06-10 18:03:25 +00:00
|
|
|
if defaultDbFile, err = xdg.DataFile(name + "/" + dbTypeSqliteDbNameDefault); err != nil {
|
2023-12-21 16:04:04 +00:00
|
|
|
zap.L().Sugar().Fatalf("Database file '%s' could not be created. Reason: %v", defaultDbFile, err)
|
|
|
|
}
|
|
|
|
setEnvKeyDefault(envDbSqliteFile, defaultDbFile)
|
|
|
|
}
|
|
|
|
|
|
|
|
dbFile := os.Getenv(envDbSqliteFile)
|
|
|
|
zap.L().Sugar().Infof("Using database file '%s'", dbFile)
|
|
|
|
|
2024-01-25 18:57:41 +00:00
|
|
|
if err = util.CreateFileWithParent(dbFile); err != nil {
|
|
|
|
zap.L().Sugar().Fatalf("Database file '%s' cannot be created: %v", dbFile, err)
|
|
|
|
}
|
|
|
|
|
2024-01-24 22:21:48 +00:00
|
|
|
if db, err = gorm.Open(sqlite.Open(dbFile), gormConfig); err != nil {
|
2023-12-21 16:04:04 +00:00
|
|
|
zap.L().Sugar().Fatalf("Could not setup database: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if res := db.Exec("PRAGMA foreign_keys = ON"); res.Error != nil {
|
2024-04-26 12:37:50 +00:00
|
|
|
zap.L().Sugar().Fatalf("Could not execute foreign key for SQLite: %v", res.Error)
|
2023-12-21 16:04:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
sqlDb, _ := db.DB()
|
|
|
|
sqlDb.SetMaxOpenConns(1)
|
|
|
|
zap.L().Sugar().Infof("SQLite: restricting max connections to '1'")
|
|
|
|
} else if os.Getenv(envDbType) == dbTypePostgres {
|
|
|
|
host := os.Getenv(envDbPostgresHost)
|
|
|
|
port := os.Getenv(envDbPostgresPort)
|
|
|
|
dbUser := os.Getenv(envDbPostgresUser)
|
|
|
|
dbPass := os.Getenv(envDbPostgresPassword)
|
|
|
|
dbName := os.Getenv(envDbPostgresName)
|
|
|
|
dbTZ := os.Getenv(envDbPostgresTimeZone)
|
|
|
|
|
|
|
|
if host == "" || port == "" || dbUser == "" || dbPass == "" || dbName == "" || dbTZ == "" {
|
|
|
|
zap.L().Sugar().Fatalf("Some configuration for database type '%s' is missing", os.Getenv(envDbType))
|
|
|
|
}
|
|
|
|
|
|
|
|
dsn := fmt.Sprintf("host=%v user=%v password=%v dbname=%v port=%v sslmode=disable TimeZone=%v", host, dbUser, dbPass, dbName, port, dbTZ)
|
2024-01-24 22:21:48 +00:00
|
|
|
if db, err = gorm.Open(postgres.Open(dsn), gormConfig); err != nil {
|
2023-12-21 16:04:04 +00:00
|
|
|
zap.L().Sugar().Fatalf("Could not setup database: %v", err)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
zap.L().Sugar().Fatalf("Database type '%s' or '%s' is required", dbTypeSqlite, dbTypePostgres)
|
|
|
|
}
|
|
|
|
|
|
|
|
if db == nil {
|
|
|
|
zap.L().Sugar().Fatalf("Could not setup database")
|
|
|
|
}
|
|
|
|
|
2024-01-24 22:21:48 +00:00
|
|
|
env := &Environment{appConfig: ac,
|
2024-10-24 20:52:41 +00:00
|
|
|
webConfig: webC,
|
2024-01-24 22:21:48 +00:00
|
|
|
authConfig: authC,
|
2023-12-21 16:04:04 +00:00
|
|
|
serverConfig: sc,
|
|
|
|
taskConfig: tc,
|
2023-12-22 12:13:44 +00:00
|
|
|
lockConfig: lc,
|
2024-01-24 22:21:48 +00:00
|
|
|
webhookConfig: wc,
|
|
|
|
prometheusConfig: pc,
|
2023-12-21 16:04:04 +00:00
|
|
|
db: db}
|
|
|
|
|
2024-04-26 12:37:50 +00:00
|
|
|
if err = env.db.AutoMigrate(&Update{}, &Webhook{}, &Event{}, &Secret{}, &Action{}, &ActionInvocation{}); err != nil {
|
2023-12-21 16:04:04 +00:00
|
|
|
zap.L().Sugar().Fatalf("Could not migrate database schema: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
zap.L().Sugar().Infof("appConfig %+v", env.appConfig)
|
|
|
|
zap.L().Sugar().Infof("serverConfig %+v", env.serverConfig)
|
|
|
|
zap.L().Sugar().Infof("taskConfig %+v", env.taskConfig)
|
|
|
|
zap.L().Sugar().Infof("webhookConfig %+v", env.webhookConfig)
|
|
|
|
|
|
|
|
return env
|
|
|
|
}
|
|
|
|
|
|
|
|
func bootstrapFromEnvironmentAndValidate() {
|
2024-04-26 18:24:47 +00:00
|
|
|
failIfEnvKeyNotPresent(envSecret)
|
|
|
|
|
|
|
|
// auth mode
|
|
|
|
setEnvKeyDefault(envAuthMode, authModeDefault)
|
|
|
|
|
2023-12-21 16:04:04 +00:00
|
|
|
// app
|
|
|
|
setEnvKeyDefault(envTZ, tzDefault)
|
|
|
|
|
2024-10-24 20:52:41 +00:00
|
|
|
// web
|
|
|
|
setEnvKeyDefault(envWebTitle, webTitleDefault)
|
|
|
|
setEnvKeyDefault(envWebApiUrl, webApiUrlDefault)
|
|
|
|
|
2023-12-21 16:04:04 +00:00
|
|
|
// webhook
|
|
|
|
setEnvKeyDefault(envWebhooksTokenLength, webhooksTokenLengthDefault)
|
|
|
|
|
2023-12-22 12:13:44 +00:00
|
|
|
// lock
|
|
|
|
setEnvKeyDefault(envLockRedisEnabled, redisEnabledDefault)
|
|
|
|
|
2023-12-21 16:04:04 +00:00
|
|
|
// task
|
|
|
|
setEnvKeyDefault(envTaskUpdateCleanStaleEnabled, taskUpdateCleanStaleEnabledDefault)
|
|
|
|
setEnvKeyDefault(envTaskUpdateCleanStaleInterval, taskUpdateCleanStaleIntervalDefault)
|
|
|
|
setEnvKeyDefault(envTaskUpdateCleanStaleMaxAge, taskUpdateCleanStaleMaxAgeDefault)
|
|
|
|
|
|
|
|
setEnvKeyDefault(envTaskEventCleanStaleEnabled, taskEventCleanStaleEnabledDefault)
|
|
|
|
setEnvKeyDefault(envTaskEventCleanStaleInterval, taskEventCleanStaleIntervalDefault)
|
|
|
|
setEnvKeyDefault(envTaskEventCleanStaleMaxAge, taskEventCleanStaleMaxAgeDefault)
|
|
|
|
|
2024-04-26 12:37:50 +00:00
|
|
|
setEnvKeyDefault(envTaskActionsEnqueueEnabled, taskActionsEnqueueEnabledDefault)
|
|
|
|
setEnvKeyDefault(envTaskActionsEnqueueInterval, taskActionsEnqueueIntervalDefault)
|
|
|
|
setEnvKeyDefault(envTaskActionsEnqueueBatchSize, taskActionsEnqueueBatchSizeDefault)
|
|
|
|
|
|
|
|
setEnvKeyDefault(envTaskActionsInvokeEnabled, taskActionsInvokeEnabledDefault)
|
|
|
|
setEnvKeyDefault(envTaskActionsInvokeInterval, taskActionsInvokeIntervalDefault)
|
|
|
|
setEnvKeyDefault(envTaskActionsInvokeBatchSize, taskActionsInvokeBatchSizeDefault)
|
|
|
|
setEnvKeyDefault(envTaskActionsInvokeMaxRetries, taskActionsInvokeMaxRetriesDefault)
|
|
|
|
|
|
|
|
setEnvKeyDefault(envTaskActionsCleanStaleEnabled, taskActionsCleanStaleEnabledDefault)
|
|
|
|
setEnvKeyDefault(envTaskActionsCleanStaleInterval, taskActionsCleanStaleIntervalDefault)
|
|
|
|
setEnvKeyDefault(envTaskActionsCleanStaleMaxAge, taskActionsCleanStaleMaxAgeDefault)
|
|
|
|
|
2023-12-21 16:04:04 +00:00
|
|
|
setEnvKeyDefault(envTaskPrometheusRefreshInterval, taskPrometheusRefreshDefault)
|
|
|
|
|
|
|
|
// prometheus
|
|
|
|
setEnvKeyDefault(envPrometheusEnabled, prometheusEnabledDefault)
|
|
|
|
setEnvKeyDefault(envPrometheusMetricsPath, prometheusMetricsPathDefault)
|
|
|
|
setEnvKeyDefault(envPrometheusSecureTokenEnabled, prometheusSecureTokenEnabledDefault)
|
|
|
|
|
|
|
|
// db
|
|
|
|
setEnvKeyDefault(envDbType, dbTypeSqlite)
|
|
|
|
|
|
|
|
if os.Getenv(envDbType) == dbTypePostgres {
|
|
|
|
setEnvKeyDefault(envDbPostgresHost, dbTypePostgresHostDefault)
|
|
|
|
setEnvKeyDefault(envDbPostgresPort, dbTypePostgresPortDefault)
|
|
|
|
setEnvKeyDefault(envDbPostgresTimeZone, dbTypePostgresTZDefault)
|
|
|
|
}
|
|
|
|
|
|
|
|
// server
|
|
|
|
setEnvKeyDefault(envServerPort, serverPortDefault)
|
|
|
|
setEnvKeyDefault(envServerListen, serverListenDefault)
|
|
|
|
setEnvKeyDefault(envServerTlsEnabled, serverTlsEnabledDefault)
|
2024-06-10 18:03:25 +00:00
|
|
|
setEnvKeyDefault(envCorsAllowOrigins, corsAllowOriginsDefault)
|
2023-12-21 16:04:04 +00:00
|
|
|
setEnvKeyDefault(envCorsAllowMethods, corsAllowMethodsDefault)
|
|
|
|
setEnvKeyDefault(envCorsAllowHeaders, corsAllowHeadersDefault)
|
2024-06-10 18:03:25 +00:00
|
|
|
setEnvKeyDefault(envCorsAllowCredentials, corsAllowCredentialsDefault)
|
|
|
|
setEnvKeyDefault(envCorsExposeHeaders, corsExposeHeadersDefault)
|
2023-12-21 16:04:04 +00:00
|
|
|
setEnvKeyDefault(envServerTimeout, serverTimeoutDefault)
|
|
|
|
}
|
|
|
|
|
|
|
|
func failIfEnvKeyNotPresent(key string) {
|
|
|
|
if os.Getenv(key) == "" {
|
|
|
|
zap.L().Sugar().Fatalf("Not all required ENV variables given. Please set '%s'", key)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func setEnvKeyDefault(key string, defaultValue string) {
|
|
|
|
var err error
|
|
|
|
if os.Getenv(key) == "" {
|
|
|
|
if err = os.Setenv(key, defaultValue); err != nil {
|
|
|
|
zap.L().Sugar().Fatalf("Could not set default value for ENV variable '%s'", key)
|
|
|
|
}
|
|
|
|
|
|
|
|
zap.L().Sugar().Infof("Set '%s' to '%s'", key, defaultValue)
|
|
|
|
}
|
|
|
|
}
|
2024-04-26 18:24:47 +00:00
|
|
|
|
2024-05-16 07:33:32 +00:00
|
|
|
func parseDuration(envProperty string) time.Duration {
|
|
|
|
var duration time.Duration
|
|
|
|
var err error
|
|
|
|
|
|
|
|
if duration, err = time.ParseDuration(os.Getenv(envProperty)); err != nil {
|
|
|
|
zap.L().Sugar().Fatalf("Could not parse duration for '%s'. Reason: %s", envProperty, err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
return duration
|
|
|
|
}
|
|
|
|
|
2024-04-26 18:24:47 +00:00
|
|
|
func parseBasicAuthCredentials(envProperty string) map[string]string {
|
|
|
|
if envProperty == "" {
|
|
|
|
zap.L().Sugar().Fatalln("Invalid env for parsing basic auth credentials")
|
|
|
|
}
|
|
|
|
credentialsFromEnv := os.Getenv(envProperty)
|
|
|
|
|
|
|
|
var credentials []string
|
|
|
|
credentials = strings.Split(credentialsFromEnv, ",")
|
|
|
|
|
|
|
|
basicAuthCredentials := make(map[string]string)
|
|
|
|
|
|
|
|
for _, c := range credentials {
|
|
|
|
pair := strings.Split(c, "=")
|
|
|
|
|
|
|
|
if len(pair) != 2 {
|
|
|
|
zap.L().Sugar().Fatalln("Invalid basic auth credentials. Reason: credentials must be specified with the = separator per credential entry")
|
|
|
|
}
|
|
|
|
|
|
|
|
if pair[0] == "" {
|
|
|
|
zap.L().Sugar().Fatalln("Invalid basic auth credentials. Reason: username must not be blank")
|
|
|
|
}
|
|
|
|
|
|
|
|
if pair[1] == "" {
|
|
|
|
zap.L().Sugar().Fatalln("Invalid basic auth credentials. Reason: password must not be blank")
|
|
|
|
}
|
|
|
|
|
|
|
|
basicAuthCredentials[pair[0]] = pair[1]
|
|
|
|
}
|
|
|
|
|
|
|
|
return basicAuthCredentials
|
|
|
|
}
|