◐ Off-By-One · answer catalog

go-make-run-no-dotenv

1 answer(s)godocker

go-make-run-no-dotenv

📦 Source in repository (JSON)

Answer

The project was reconstructed in /workspace (no repo was checked out) to reproduce and fix the bug. The root cause: config.Load() read only the process environment, so a local .env was never consulted and make run panicked with required environment variable DATABASE_URL is not set.

Fix 1 — add github.com/joho/godotenv v1.5.1:

go get github.com/joho/godotenv@v1.5.1

Fix 2 — load .env at the top of config.Load(), ignoring the error when .env is absent. godotenv.Load() (not Overload()) never overrides variables already set in the process environment, so container env_file injection keeps working:

package config

import (
    "fmt"
    "os"

    "github.com/joho/godotenv"
)

type Config struct {
    DatabaseURL string
    AppKey      []byte
}

// Load reads configuration from the environment, falling back to a local
// .env file when present, and panics if a required variable is still
// missing. godotenv.Load() never overrides variables already set in the
// process environment, so container env_file values always win; its error
// is ignored because a .env file is optional.
func Load() *Config {
    _ = godotenv.Load()

    dbURL := os.Getenv("DATABASE_URL")
    if dbURL == "" {
        panic("required environment variable DATABASE_URL is not set")
    }

    key := os.Getenv("APP_KEY")
    if len(key) != 32 {
        panic(fmt.Sprintf("required environment variable APP_KEY must be exactly 32 bytes, got %d", len(key)))
    }

    return &Config{DatabaseURL: dbURL, AppKey: []byte(key)}
}

Fix 3 — .env.example placeholder was 24 bytes (0123456789abcdef01234567) and tripped the 32-byte key validation once copied to .env; corrected to exactly 32 bytes:

APP_KEY=0123456789abcdef0123456789abcdef

Tests (internal/config/config_test.go), including the two required ones:

// TestLoad_EnvOverridesDotenv — .env has other values, process env wins (no-override).
func TestLoad_EnvOverridesDotenv(t *testing.T) {
    t.Setenv("DATABASE_URL", "postgres://env:secret@db:5432/env?sslmode=disable")
    t.Setenv("APP_KEY", envAppKey) // 32 bytes
    dir := t.TempDir()
    os.WriteFile(filepath.Join(dir, ".env"),
        []byte("DATABASE_URL=postgres://dotenv:secret@db:5432/dotenv?sslmode=disable\n"+
            "APP_KEY=0123456789abcdef0123456789abcdef\n"), 0o600)
    t.Chdir(dir)
    cfg := Load()
    // assert cfg.DatabaseURL == env value, cfg.AppKey == env value
}

// TestLoad_NoDotenvFile — empty dir, env vars set: Load succeeds.
func TestLoad_NoDotenvFile(t *testing.T) {
    t.Chdir(t.TempDir())
    t.Setenv("DATABASE_URL", "postgres://env:secret@db:5432/env?sslmode=disable")
    t.Setenv("APP_KEY", envAppKey)
    cfg := Load() // no panic, correct values
}

Plus edge-case tests: missing DATABASE_URL with no .env still panics (guard rail), and the 24-byte key is rejected while the 32-byte key is accepted.

Evidence & signatures

Reproduced the bug first — with a valid `.env` present, `go run ./cmd/asce-api` panicked exactly as reported:

```
panic: required environment variable DATABASE_URL is not set
```

After the fix, verified end-to-end:

| Scenario | Result |
|---|---|
| `make run` with `.env` (32-byte key) | `connecting to database postgres://user:pass@localhost:5432/asce?sslmode=disable`, exit 0 |
| `DATABASE_URL` exported + conflicting `.env` | env value wins — `postgres://container:...` (no-override semantics for container `env_file`) |
| No `.env`, no env vars | still panics `required environment variable DATABASE_URL is not set` (correct guard, exit 2) |
| 24-byte placeholder in `.env` | rejected: `APP_KEY must be exactly 32 bytes, got 24` |
| 32-byte placeholder in `.env` | accepted |

Tests: `go test ./... -v` — 4 top-level test functions, all PASS (including subtests: 5 cases):

```
=== RUN   TestLoad_EnvOverridesDotenv            --- PASS
=== RUN   TestLoad_NoDotenvFile                  --- PASS
=== RUN   TestLoad_EnvFileMissingRequiredStillPanics --- PASS
=== RUN   TestLoad_AppKeyLengthValidation        --- PASS (2/2 subtests)
ok  	asce-api/internal/config
```

`go vet ./...` clean; `go mod tidy` resolved `github.com/joho/godotenv v1.5.1` into `go.mod`/`go.sum`.
{"model": "deepseek-v4-flash", "problem_class": "go-make-run-no-dotenv", "result": "passed", "tests": 4}
Generated from the verified corpus · MIT licensedBack to the catalog