From 46cecdc007bd634c06b6736744184d07bcdeb80d Mon Sep 17 00:00:00 2001 From: garionion Date: Thu, 27 Feb 2025 17:42:16 +0100 Subject: [PATCH] feat: implement configuration loading from file, env vars and flags --- main.go | 102 +++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 83 insertions(+), 19 deletions(-) diff --git a/main.go b/main.go index 9725854..b0fb491 100644 --- a/main.go +++ b/main.go @@ -5,18 +5,21 @@ import ( "database/sql" "embed" "encoding/json" + "flag" "fmt" - "github.com/num30/config" "github.com/pressly/goose/v3" "go.uber.org/zap" "io" "net/http" + "os" + "strconv" "strings" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" _ "github.com/mattn/go-sqlite3" "golang.org/x/oauth2" + "gopkg.in/yaml.v2" ) //go:embed static/* @@ -30,7 +33,7 @@ type OAuthConfig struct { ClientSecret string `config:"client_secret" yaml:"client_secret"` AuthURL string `config:"auth_url" yaml:"auth_url"` TokenURL string `config:"token_url" yaml:"token_url"` - UserInfoURL string `config:"userinfo_url" yaml:"userinfo_url"` + UserInfoURL string `config:"user_info_url" yaml:"user_info_url"` } type DBConfig struct { @@ -61,34 +64,95 @@ var ( var db *sql.DB func main() { - // Use the global cfg variable instead of declaring a new one - cfgReader := config.NewConfReader("config").WithSearchDirs("./", ".", "../", "../../") - cfgErr := cfgReader.Read(&cfg) + // Load configuration from YAML file, environment variables, and flags + configFile := flag.String("config", "config.yaml", "Path to config file") + serverPort := flag.Int("port", 0, "Server port (overrides config file)") + oauthClientID := flag.String("oauth-client-id", "", "OAuth Client ID (overrides config file)") + oauthClientSecret := flag.String("oauth-client-secret", "", "OAuth Client Secret (overrides config file)") + oauthAuthURL := flag.String("oauth-auth-url", "", "OAuth Auth URL (overrides config file)") + oauthTokenURL := flag.String("oauth-token-url", "", "OAuth Token URL (overrides config file)") + oauthUserInfoURL := flag.String("oauth-user-info-url", "", "OAuth User Info URL (overrides config file)") + flag.Parse() + + // Set default values + cfg = Config{ + Env: "dev", + Server: struct { + Port int `config:"port"` + }{Port: 8088}, + OAuth: OAuthConfig{}, + DB: DBConfig{ + Driver: "sqlite3", + DBName: "./inventor.sqlite", + }, + } + + yamlData, err := os.ReadFile(*configFile) + if err != nil { + fmt.Printf("Warning: Could not read config file: %v\n", err) + } else { + if err := yaml.Unmarshal(yamlData, &cfg); err != nil { + fmt.Printf("Warning: Could not parse config file: %v\n", err) + } else { + fmt.Println("Successfully loaded configuration from", *configFile) + } + } + + if envPort := os.Getenv("INVENTOR_SERVER_PORT"); envPort != "" { + if port, err := strconv.Atoi(envPort); err == nil { + cfg.Server.Port = port + } + } + if envClientID := os.Getenv("INVENTOR_OAUTH_CLIENT_ID"); envClientID != "" { + cfg.OAuth.ClientID = envClientID + } + if envClientSecret := os.Getenv("INVENTOR_OAUTH_CLIENT_SECRET"); envClientSecret != "" { + cfg.OAuth.ClientSecret = envClientSecret + } + if envAuthURL := os.Getenv("INVENTOR_OAUTH_AUTH_URL"); envAuthURL != "" { + cfg.OAuth.AuthURL = envAuthURL + } + if envTokenURL := os.Getenv("INVENTOR_OAUTH_TOKEN_URL"); envTokenURL != "" { + cfg.OAuth.TokenURL = envTokenURL + } + if envUserInfoURL := os.Getenv("INVENTOR_OAUTH_USER_INFO_URL"); envUserInfoURL != "" { + cfg.OAuth.UserInfoURL = envUserInfoURL + } + + // 3. Override with command line flags (highest priority) + if *serverPort != 0 { + cfg.Server.Port = *serverPort + } + if *oauthClientID != "" { + cfg.OAuth.ClientID = *oauthClientID + } + if *oauthClientSecret != "" { + cfg.OAuth.ClientSecret = *oauthClientSecret + } + if *oauthAuthURL != "" { + cfg.OAuth.AuthURL = *oauthAuthURL + } + if *oauthTokenURL != "" { + cfg.OAuth.TokenURL = *oauthTokenURL + } + if *oauthUserInfoURL != "" { + cfg.OAuth.UserInfoURL = *oauthUserInfoURL + } zapcfg := zap.NewProductionConfig() if cfg.Env == "dev" { zapcfg = zap.NewDevelopmentConfig() } - logger, err := zapcfg.Build() + logger, err = zapcfg.Build() if err != nil { panic(fmt.Sprintf("Failed to initialize logger: %v", err)) } defer logger.Sync() - if cfgErr != nil { - logger.Fatal("Failed to load config", zap.Error(cfgErr)) - } + // Configuration errors are now handled inline during loading // Print the raw config to see what's actually loaded - logger.Info("Raw config loaded", zap.Any("config", cfg)) - - // Add detailed logging of the OAuth config - logger.Info("OAuth Configuration", - zap.String("ClientID", cfg.OAuth.ClientID), - zap.String("ClientSecret", cfg.OAuth.ClientSecret), - zap.String("AuthURL", cfg.OAuth.AuthURL), - zap.String("TokenURL", cfg.OAuth.TokenURL), - zap.String("UserInfoURL", cfg.OAuth.UserInfoURL)) + logger.Debug("Raw config loaded", zap.Any("config", cfg)) // Initialize OAuth2 oauthConfig = &oauth2.Config{ @@ -98,7 +162,7 @@ func main() { AuthURL: cfg.OAuth.AuthURL, TokenURL: cfg.OAuth.TokenURL, }, - RedirectURL: fmt.Sprintf("http://localhost:%d/oauth/callback", cfg.Server.Port), + RedirectURL: fmt.Sprintf("http://localhost:3000/oauth/callback"), Scopes: []string{"profile", "email"}, }