mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
feat: add --env-file flag to develop.sh (#25621)
Adds `--env-file` to `scripts/develop.sh` to allow reading environment from a given file. This makes it easier to configure things like external auth providers, access URLs, and other dev-time settings without exporting a wall of environment variables in every shell session. > Generated with [Coder Agents](https://coder.com/agents)
This commit is contained in:
+69
-3
@@ -27,6 +27,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/joho/godotenv"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
@@ -69,6 +70,24 @@ const (
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Pre-parse --env-file before serpent runs so that variables from
|
||||
// the file are visible to serpent's Env-tag resolution for other
|
||||
// options. The flag is also registered in the serpent OptionSet
|
||||
// below for --help discoverability.
|
||||
envFile, err := parseEnvFileFlag()
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "develop: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if envFile != "" {
|
||||
n, err := loadEnvFile(envFile)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "develop: error loading env file %s: %v\n", envFile, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
_, _ = fmt.Fprintf(os.Stderr, "develop: loaded %d variable(s) from %s\n", n, envFile)
|
||||
}
|
||||
|
||||
var cfg devConfig
|
||||
|
||||
cmd := &serpent.Command{
|
||||
@@ -182,6 +201,12 @@ func main() {
|
||||
Description: "Accept changed migration files and update tracking. Use when you've manually fixed the DB to match the new migrations.",
|
||||
Value: serpent.BoolOf(&cfg.dbContinue),
|
||||
},
|
||||
{
|
||||
Flag: "env-file",
|
||||
Env: "CODER_DEV_ENV_FILE",
|
||||
Description: "Path to a .env file to load before starting. Variables in the file do not override existing environment variables. Note: unquoted and double-quoted values undergo $VAR expansion against other entries in the same file (not the process environment); use single quotes for literal dollar signs.",
|
||||
Value: serpent.StringOf(&cfg.envFile),
|
||||
},
|
||||
},
|
||||
Handler: func(inv *serpent.Invocation) error {
|
||||
cfg.serverExtraArgs = inv.Args
|
||||
@@ -198,7 +223,7 @@ func main() {
|
||||
},
|
||||
}
|
||||
|
||||
err := cmd.Invoke(os.Args[1:]...).WithOS().Run()
|
||||
err = cmd.Invoke(os.Args[1:]...).WithOS().Run()
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
@@ -223,6 +248,9 @@ type devConfig struct {
|
||||
dbRollback bool
|
||||
dbReset bool
|
||||
dbContinue bool
|
||||
// envFile is populated by serpent for --help output; actual loading
|
||||
// uses parseEnvFileFlag() before serpent runs.
|
||||
envFile string
|
||||
projectRoot string
|
||||
binaryPath string
|
||||
configDir string
|
||||
@@ -435,8 +463,46 @@ func (c *devConfig) cmd(ctx context.Context, bin string, args ...string) *exec.C
|
||||
return cmd
|
||||
}
|
||||
|
||||
// filterEnv returns env with any variables whose key matches
|
||||
// exclude removed.
|
||||
// parseEnvFileFlag extracts the --env-file value from os.Args and
|
||||
// CODER_DEV_ENV_FILE before serpent runs, so that loaded variables
|
||||
// are visible to serpent's Env-tag resolution for other options.
|
||||
func parseEnvFileFlag() (string, error) {
|
||||
for i, arg := range os.Args[1:] {
|
||||
if arg == "--env-file" {
|
||||
if i+2 >= len(os.Args) {
|
||||
return "", xerrors.New("--env-file requires a value")
|
||||
}
|
||||
return os.Args[i+2], nil
|
||||
}
|
||||
if v, ok := strings.CutPrefix(arg, "--env-file="); ok {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
return os.Getenv("CODER_DEV_ENV_FILE"), nil
|
||||
}
|
||||
|
||||
// loadEnvFile reads the file at path using godotenv and sets any variables
|
||||
// not already present in the process environment. It returns the number of
|
||||
// variables set.
|
||||
func loadEnvFile(path string) (int, error) {
|
||||
vars, err := godotenv.Read(path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var n int
|
||||
for key, val := range vars {
|
||||
if _, exists := os.LookupEnv(key); exists {
|
||||
continue
|
||||
}
|
||||
if err := os.Setenv(key, val); err != nil {
|
||||
return n, err
|
||||
}
|
||||
n++
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// filterEnv returns env with any variables whose key matches exclude removed.
|
||||
func filterEnv(env []string, exclude ...string) []string {
|
||||
out := make([]string, 0, len(env))
|
||||
for _, e := range env {
|
||||
|
||||
Reference in New Issue
Block a user