From 483081e10eaf80c1566e3a983f28c156293fcf0d Mon Sep 17 00:00:00 2001 From: Mike Fridman Date: Sat, 14 Dec 2024 09:20:05 -0500 Subject: [PATCH] feat: add -env flag and allow disabling with "none" --- CHANGELOG.md | 4 +++ README.md | 45 ++++++++++++++---------- cmd/goose/main.go | 85 ++++++++++++++++++++++++++++++++++++--------- internal/cfg/cfg.go | 43 ----------------------- 4 files changed, 100 insertions(+), 77 deletions(-) delete mode 100644 internal/cfg/cfg.go diff --git a/CHANGELOG.md b/CHANGELOG.md index a56be4fce..d715e9354 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +- Add support for loading environment variables from `.env` files, enabled by default. + - The default file name is `.env`, but can be changed with the `-env=` flag. + - To disable this feature, set `-env=none`. + ## [v3.23.1] - Store implementations can **optionally** implement the `TableExists` method to provide optimized diff --git a/README.md b/README.md index 45f64bd09..b09fe3a0e 100644 --- a/README.md +++ b/README.md @@ -52,24 +52,6 @@ brew install goose See [installation documentation](https://pressly.github.io/goose/installation/) for more details. -# Setting Up Environment Keys -To start, configure the environment keys using one of the following methods: - -**1. Via environment variables:** -```shell -export GOOSE_DRIVER=DRIVER -export GOOSE_DBSTRING=DBSTRING -export GOOSE_MIGRATION_DIR=MIGRATION_DIR -``` - -**2. Via `.env` files with corresponding variables. `.env` file example**: -```env -GOOSE_DRIVER=postgres -GOOSE_DBSTRING=postgres://admin:admin@localhost:5432/admin_db -GOOSE_MIGRATION_DIR=./migrations -``` -For more details about environment variables, see the [official documentation on environment variables]( https://pressly.github.io/goose/documentation/environment-variables/). - # Usage
@@ -254,6 +236,33 @@ Print the current version of the database: $ goose version $ goose: version 002 +# Environment Variables + +If you prefer to use environment variables, instead of passing the driver and database string as +arguments, you can set the following environment variables: + +**1. Via environment variables:** + +```shell +export GOOSE_DRIVER=DRIVER +export GOOSE_DBSTRING=DBSTRING +export GOOSE_MIGRATION_DIR=MIGRATION_DIR +``` + +**2. Via `.env` files with corresponding variables. `.env` file example**: + +```env +GOOSE_DRIVER=postgres +GOOSE_DBSTRING=postgres://admin:admin@localhost:5432/admin_db +GOOSE_MIGRATION_DIR=./migrations +``` + +Loading from `.env` files is enabled by default. To disable this feature, set the `-env=none` flag. +If you want to load from a specific file, set the `-env` flag to the file path. + +For more details about environment variables, see the [official documentation on environment +variables](https://pressly.github.io/goose/documentation/environment-variables/). + # Migrations goose supports migrations written in SQL or in Go. diff --git a/cmd/goose/main.go b/cmd/goose/main.go index 3083c7aab..1637c886e 100644 --- a/cmd/goose/main.go +++ b/cmd/goose/main.go @@ -17,15 +17,17 @@ import ( "text/tabwriter" "text/template" + "github.com/joho/godotenv" "github.com/mfridman/xflag" "github.com/pressly/goose/v3" - "github.com/pressly/goose/v3/internal/cfg" "github.com/pressly/goose/v3/internal/migrationstats" ) var ( + DefaultMigrationDir = "." + flags = flag.NewFlagSet("goose", flag.ExitOnError) - dir = flags.String("dir", cfg.DefaultMigrationDir, "directory with migration files, (GOOSE_MIGRATION_DIR env variable supported)") + dir = flags.String("dir", DefaultMigrationDir, "directory with migration files, (GOOSE_MIGRATION_DIR env variable supported)") table = flags.String("table", "goose_db_version", "migrations table name") verbose = flags.Bool("v", false, "enable verbose mode") help = flags.Bool("h", false, "print help") @@ -38,6 +40,7 @@ var ( noVersioning = flags.Bool("no-versioning", false, "apply migration commands with no versioning, in file order, from directory pointed to") noColor = flags.Bool("no-color", false, "disable color output (NO_COLOR env variable supported)") timeout = flags.Duration("timeout", 0, "maximum allowed duration for queries to run; e.g., 1h13m") + envFile = flags.String("env", "", "load environment variables from file (default .env)") ) var version string @@ -60,6 +63,20 @@ func main() { fmt.Printf("goose version: %s\n", strings.TrimSpace(version)) return } + + switch *envFile { + case "": + // Best effort to load default .env file + _ = godotenv.Load() + case "none": + // Do not load any env file + default: + if err := godotenv.Load(*envFile); err != nil { + log.Fatalf("failed to load env file: %v", err) + } + } + envConfig := loadEnvConfig() + if *verbose { goose.SetVerbose(true) } @@ -82,8 +99,8 @@ func main() { // The -dir option has not been set, check whether the env variable is set // before defaulting to ".". - if *dir == cfg.DefaultMigrationDir && cfg.GOOSEMIGRATIONDIR != "" { - *dir = cfg.GOOSEMIGRATIONDIR + if *dir == DefaultMigrationDir && envConfig.dir != "" { + *dir = envConfig.dir } switch args[0] { @@ -103,7 +120,7 @@ func main() { } return case "env": - for _, env := range cfg.List() { + for _, env := range envConfig.listEnvs() { fmt.Printf("%s=%q\n", env.Name, env.Value) } return @@ -125,7 +142,7 @@ func main() { return } - args = mergeArgs(args) + args = mergeArgs(envConfig, args) if len(args) < 3 { flags.Usage() os.Exit(1) @@ -147,7 +164,7 @@ func main() { arguments = append(arguments, args[3:]...) } options := []goose.OptionsFunc{} - if *noColor || checkNoColorFromEnv() { + if *noColor || envConfig.noColor { options = append(options, goose.WithNoColor(true)) } if *allowMissing { @@ -209,19 +226,14 @@ func mergeDrivers(drivers []string) []string { return merged } -func checkNoColorFromEnv() bool { - ok, _ := strconv.ParseBool(cfg.GOOSENOCOLOR) - return ok -} - -func mergeArgs(args []string) []string { +func mergeArgs(config *envConfig, args []string) []string { if len(args) < 1 { return args } - if s := cfg.GOOSEDRIVER; s != "" { + if s := config.driver; s != "" { args = append([]string{s}, args...) } - if s := cfg.GOOSEDBSTRING; s != "" { + if s := config.dbstring; s != "" { args = append([]string{args[0], s}, args[1:]...) } return args @@ -330,7 +342,7 @@ SELECT 'down SQL query'; // initDir will create a directory with an empty SQL migration file. func gooseInit(dir string) error { - if dir == "" || dir == cfg.DefaultMigrationDir { + if dir == "" || dir == DefaultMigrationDir { dir = "migrations" } _, err := os.Stat(dir) @@ -404,3 +416,44 @@ func printValidate(filename string, verbose bool) error { } return w.Flush() } + +type envConfig struct { + driver string + dbstring string + dir string + noColor bool +} + +func loadEnvConfig() *envConfig { + noColorBool, _ := strconv.ParseBool(envOr("NO_COLOR", "false")) + return &envConfig{ + driver: envOr("GOOSE_DRIVER", ""), + dbstring: envOr("GOOSE_DBSTRING", ""), + dir: envOr("GOOSE_MIGRATION_DIR", DefaultMigrationDir), + // https://no-color.org/ + noColor: noColorBool, + } +} + +func (c *envConfig) listEnvs() []envVar { + return []envVar{ + {Name: "GOOSE_DRIVER", Value: c.driver}, + {Name: "GOOSE_DBSTRING", Value: c.dbstring}, + {Name: "GOOSE_MIGRATION_DIR", Value: c.dir}, + {Name: "NO_COLOR", Value: strconv.FormatBool(c.noColor)}, + } +} + +type envVar struct { + Name string + Value string +} + +// envOr returns os.Getenv(key) if set, or else default. +func envOr(key, def string) string { + val := os.Getenv(key) + if val == "" { + val = def + } + return val +} diff --git a/internal/cfg/cfg.go b/internal/cfg/cfg.go deleted file mode 100644 index 9e220e3bc..000000000 --- a/internal/cfg/cfg.go +++ /dev/null @@ -1,43 +0,0 @@ -package cfg - -import ( - "github.com/joho/godotenv" - "os" -) - -var ( - _ = godotenv.Load() - GOOSEDRIVER = envOr("GOOSE_DRIVER", "") - GOOSEDBSTRING = envOr("GOOSE_DBSTRING", "") - GOOSEMIGRATIONDIR = envOr("GOOSE_MIGRATION_DIR", DefaultMigrationDir) - // https://no-color.org/ - GOOSENOCOLOR = envOr("NO_COLOR", "false") -) - -var ( - DefaultMigrationDir = "." -) - -// An EnvVar is an environment variable Name=Value. -type EnvVar struct { - Name string - Value string -} - -func List() []EnvVar { - return []EnvVar{ - {Name: "GOOSE_DRIVER", Value: GOOSEDRIVER}, - {Name: "GOOSE_DBSTRING", Value: GOOSEDBSTRING}, - {Name: "GOOSE_MIGRATION_DIR", Value: GOOSEMIGRATIONDIR}, - {Name: "NO_COLOR", Value: GOOSENOCOLOR}, - } -} - -// envOr returns os.Getenv(key) if set, or else default. -func envOr(key, def string) string { - val := os.Getenv(key) - if val == "" { - val = def - } - return val -}