diff --git a/.gitignore b/.gitignore index 10cfefe..9035ee4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,9 @@ # Project binary poseidon +# Configuration file +configuration.yaml + # TLS certificate/key *.crt *.key - -configuration.yaml diff --git a/config/config.go b/config/config.go index 2c4559f..5bb5222 100644 --- a/config/config.go +++ b/config/config.go @@ -2,6 +2,7 @@ package config import ( "crypto/tls" + "errors" "flag" "fmt" "gitlab.hpi.de/codeocean/codemoon/poseidon/logging" @@ -13,6 +14,7 @@ import ( "strings" ) +// Config contains the default configuration of Poseidon. var ( Config = &configuration{ Server: server{ @@ -25,22 +27,25 @@ var ( }, Nomad: nomad{ Address: "", - Token: "", Port: 4646, + Token: "", TLS: false, }, Logger: logger{ Level: "INFO", }, } - log = logging.GetLogger("config") - TLSConfig = &tls.Config{ + configurationFilePath = "./configuration.yaml" + configurationInitialized = false + log = logging.GetLogger("config") + TLSConfig = &tls.Config{ MinVersion: tls.VersionTLS13, CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256}, PreferServerCipherSuites: true, } ) +// server configures the Poseidon webserver. type server struct { Address string Port int @@ -50,27 +55,39 @@ type server struct { KeyFile string } +// nomad configures the used Nomad cluster. type nomad struct { Address string - Token string Port int + Token string TLS bool } +// logger configures the used logger. type logger struct { Level string } +// configuration contains the complete configuration of Poseidon. type configuration struct { Server server Nomad nomad Logger logger } -func InitConfig() { +// InitConfig merges configuration options from environment variables and +// a configuration file into the default configuration. Calls of InitConfig +// after the first call have no effect and return an error. InitConfig +// should be called directly after starting the program. +func InitConfig() error { + if configurationInitialized { + return errors.New("configuration is already initialized") + } + configurationInitialized = true content := readConfigFile() Config.mergeYaml(content) Config.mergeEnvironmentVariables() + return nil } func (c *configuration) NomadAPIURL() *url.URL { @@ -93,16 +110,21 @@ func parseURL(address string, port int, tls bool) *url.URL { } func readConfigFile() []byte { - var configFilePath string - flag.StringVar(&configFilePath, "config", "./configuration.yaml", "path of the yaml config file") - flag.Parse() - data, err := os.ReadFile(configFilePath) + parseFlags() + data, err := os.ReadFile(configurationFilePath) if err != nil { log.WithError(err).Info("Using default configuration...") } return data } +func parseFlags() { + if flag.Lookup("config") == nil { + flag.StringVar(&configurationFilePath, "config", configurationFilePath, "path of the yaml config file") + } + flag.Parse() +} + func (c *configuration) mergeYaml(content []byte) { if err := yaml.Unmarshal(content, c); err != nil { log.WithError(err).Fatal("Could not parse configuration file") @@ -114,20 +136,20 @@ func (c *configuration) mergeEnvironmentVariables() { } func readFromEnvironment(prefix string, value reflect.Value) { - if !value.CanSet() || !value.CanInterface() { + logEntry := log.WithField("prefix", prefix) + // if value was not derived from a pointer, it is not possible to alter its contents + if !value.CanSet() { + logEntry.Warn("Cannot overwrite struct field that can not be set") return } if value.Kind() != reflect.Struct { content, ok := os.LookupEnv(prefix) - - logEntry := log. - WithField("prefix", prefix). - WithField("content", content) - if !ok { return } + logEntry = logEntry.WithField("content", content) + switch value.Kind() { case reflect.String: value.SetString(content) diff --git a/configuration.example.yaml b/configuration.example.yaml new file mode 100644 index 0000000..3921ade --- /dev/null +++ b/configuration.example.yaml @@ -0,0 +1,30 @@ +# Configuration of the Poseidon webserver +server: + # Address on which the webserver listens + address: 127.0.0.1 + # Port on which the webserver listens + port: 3000 + # If set, this token is required in the X-Poseidon-Token header for each route except /health + token: SECRET + # If set, the API uses TLS for all incoming connections + tls: true + # The path to the certificate file used for TLS + certfile: ./poseidon.crt + # The path to the key file used for TLS + keyfile: ./poseidon.key + +# Configuration of the used Nomad cluster +nomad: + # IP address / domain of the Nomad server + address: 127.0.0.1 + # Port of the Nomad server + port: 4646 + # Authenticate requests to the Nomad server with this token + token: SECRET + # Specifies whether to use TLS when communicating with the Nomad server + tls: false + +# Configuration of the logger +logger: + # Log level that is used after reading the config (INFO until then) + level: DEBUG diff --git a/main.go b/main.go index 9b42130..84ece86 100644 --- a/main.go +++ b/main.go @@ -63,7 +63,9 @@ func shutdownOnOSSignal(server *http.Server) { } func main() { - config.InitConfig() + if err := config.InitConfig(); err != nil { + log.WithError(err).Warn("Could not initialize configuration") + } logging.InitializeLogging(config.Config.Logger.Level) server := initServer() log.WithField("address", server.Addr).Info("Starting server")