From 83753c3ee12cfa6e63d5743b6a3500871a5fd1ce Mon Sep 17 00:00:00 2001 From: Florian Beisel Date: Thu, 18 Jan 2024 01:04:43 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A7=20Create=20a=20configuration=20fil?= =?UTF-8?q?e=20interface?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds the ability to provide a config file in different ways: The following options are given in the order in which they are checked: 1. A config file path passed by the -c parameter on the command line 2. A config file path passed by the HS_CONFIGFILE environment variable 3. A config.json file in the application root Should these fail we try to get the parameters for the config struct directly from environment variables or secret files as used in a container environment. As example the JwtKey can be supplied by either passing the value through: * An environment variable HS_CONFIG_JWTKEY which contains the value directly * An environment variable HS_CONFIG_JWTKEY_FILE which contains a path pointing to the Secret File --- .gitignore | 5 ++- auth/auth.go | 2 +- config.example.json | 4 ++ config/config.go | 88 +++++++++++++++++++++++++++++++++++++++- main.go | 11 ++++- middleware/middleware.go | 2 +- 6 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 config.example.json diff --git a/.gitignore b/.gitignore index c327c4d..202658d 100644 --- a/.gitignore +++ b/.gitignore @@ -71,4 +71,7 @@ $RECYCLE.BIN/ !.gitsubmodules # sqlite database -*.db \ No newline at end of file +*.db + +# config file +config.json \ No newline at end of file diff --git a/auth/auth.go b/auth/auth.go index 7770423..f44078d 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -15,7 +15,7 @@ func GenerateToken(username string) (string, error) { } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - tokenString, err := token.SignedString(config.JwtKey) + tokenString, err := token.SignedString(config.GlobalConfig.JwtKey) return tokenString, err } diff --git a/config.example.json b/config.example.json new file mode 100644 index 0000000..1db7dc2 --- /dev/null +++ b/config.example.json @@ -0,0 +1,4 @@ +{ + "jwtKey": "your_secret_key", + "databaseFile": "hostname-service.db" +} diff --git a/config/config.go b/config/config.go index af8671f..9848647 100644 --- a/config/config.go +++ b/config/config.go @@ -1,3 +1,89 @@ package config -var JwtKey = []byte("your_secret_key") +import ( + "encoding/json" + "errors" + "flag" + "io/ioutil" + "os" + "reflect" + "strings" + // You might need additional imports for file handling, etc. +) + +type AppConfig struct { + JwtKey string + DatabaseFile string +} + +var GlobalConfig *AppConfig + +func LoadConfig() error { + GlobalConfig = &AppConfig{} + + // Check command line arguments + configFile := flag.String("c", "", "path to config file") + flag.Parse() + + if *configFile != "" { + return loadConfigFromFile(*configFile, GlobalConfig) + } + + // Check environment variable for config file path + envConfigFile := os.Getenv("HS_CONFIGFILE") + if envConfigFile != "" { + return loadConfigFromFile(envConfigFile, GlobalConfig) + } + + // Try to load from a default config file + defaultConfigFile := "config.json" // Replace with your default config file name + if _, err := os.Stat(defaultConfigFile); err == nil { + return loadConfigFromFile(defaultConfigFile, GlobalConfig) + } + + // Load config from environment variables or docker secrets + loadConfigFromEnvOrSecrets(GlobalConfig) + + // Check if the configuration has been loaded successfully + if GlobalConfig.JwtKey == "" || GlobalConfig.DatabaseFile == "" { + // Add more checks as necessary for other required config fields + return errors.New("failed to load configuration from any source") + } + + return nil +} + +func loadConfigFromFile(filePath string, config *AppConfig) error { + fileData, err := ioutil.ReadFile(filePath) + if err != nil { + return err + } + + err = json.Unmarshal(fileData, config) + if err != nil { + return err + } + + return nil +} + +func loadConfigFromEnvOrSecrets(config *AppConfig) { + val := reflect.ValueOf(config).Elem() + typ := val.Type() + + for i := 0; i < val.NumField(); i++ { + field := typ.Field(i) + envVar := "HS_CONFIG_" + strings.ToUpper(field.Name) + if value, exists := os.LookupEnv(envVar); exists { + val.Field(i).SetString(value) + } + + // Handling Docker secrets (file-based secrets) + secretFileEnvVar := envVar + "_FILE" + if secretFilePath, exists := os.LookupEnv(secretFileEnvVar); exists { + if secretValue, err := ioutil.ReadFile(secretFilePath); err == nil { + val.Field(i).SetString(string(secretValue)) + } + } + } +} diff --git a/main.go b/main.go index 8663228..84707da 100644 --- a/main.go +++ b/main.go @@ -1,8 +1,11 @@ package main import ( + "log" + "git.beisel.it/florian/hostname-service/api" "git.beisel.it/florian/hostname-service/auth" + "git.beisel.it/florian/hostname-service/config" "git.beisel.it/florian/hostname-service/db" "git.beisel.it/florian/hostname-service/docs" "git.beisel.it/florian/hostname-service/middleware" @@ -29,9 +32,15 @@ import ( // @description Type "Bearer" followed by a space and JWT token. func main() { + + err := config.LoadConfig() + if err != nil { + log.Fatalf("Failed to load configuration: %v", err) + } + gin.SetMode(gin.DebugMode) - db.Init("hostname-service.db") + db.Init(config.GlobalConfig.DatabaseFile) router := gin.Default() diff --git a/middleware/middleware.go b/middleware/middleware.go index ca72c0e..0d7da7f 100644 --- a/middleware/middleware.go +++ b/middleware/middleware.go @@ -23,7 +23,7 @@ func Authenticate() gin.HandlerFunc { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method") } - return config.JwtKey, nil + return config.GlobalConfig.JwtKey, nil }) if err != nil {