|
1 | 1 | // Package venv defines the root virtualized environment threaded from the |
2 | 2 | // Terragrunt binary entrypoint down through the CLI and its commands. |
3 | 3 | // |
4 | | -// A [Venv] bundles the two side-effect handles every layer below the CLI |
5 | | -// needs to do its work: [vfs.FS] for filesystem reads and writes, and |
6 | | -// [vexec.Exec] for spawning subprocesses. Production code constructs the |
| 4 | +// A [Venv] bundles the side-effect handles every layer below the CLI needs |
| 5 | +// to do its work: [vfs.FS] for filesystem reads and writes, [vexec.Exec] |
| 6 | +// for spawning subprocesses, the shell environment variables read at |
| 7 | +// startup, and the stdout/stderr writers. Production code constructs the |
7 | 8 | // real bundle once at the top via [OSVenv]; tests construct an in-memory |
8 | 9 | // bundle and drive the full CLI through it. |
9 | 10 | // |
|
13 | 14 | package venv |
14 | 15 |
|
15 | 16 | import ( |
| 17 | + "errors" |
| 18 | + "io" |
| 19 | + "os" |
| 20 | + "strings" |
| 21 | + |
16 | 22 | "github.com/gruntwork-io/terragrunt/internal/vexec" |
17 | 23 | "github.com/gruntwork-io/terragrunt/internal/vfs" |
| 24 | + "github.com/gruntwork-io/terragrunt/internal/writer" |
18 | 25 | ) |
19 | 26 |
|
20 | | -// Venv is the root virtualized environment. It carries the filesystem |
21 | | -// and process-execution handles that every Terragrunt operation needs. |
| 27 | +// ErrVenvEnvUnset is the panic value [Venv.RequireEnv] raises when Env is |
| 28 | +// nil. Production callers build the Venv through [OSVenv], so it points at a |
| 29 | +// test that forgot to set Env rather than a runtime condition. |
| 30 | +var ErrVenvEnvUnset = errors.New("venv.Venv.Env is required but unset") |
| 31 | + |
| 32 | +// Venv is the root virtualized environment. It carries the filesystem, |
| 33 | +// process-execution, environment-variable, and writer handles that every |
| 34 | +// Terragrunt operation needs. Env is shared by reference across the run and |
| 35 | +// mutated in place as provider-cache, hook, and inputs contributions resolve. |
22 | 36 | type Venv struct { |
23 | | - // FS backs every filesystem read and write. |
24 | | - FS vfs.FS |
25 | | - // Exec spawns every subprocess: tofu, terraform, git, hooks, |
26 | | - // external auth providers, tflint. |
27 | | - Exec vexec.Exec |
| 37 | + FS vfs.FS |
| 38 | + Exec vexec.Exec |
| 39 | + Env map[string]string |
| 40 | + Writers writer.Writers |
| 41 | +} |
| 42 | + |
| 43 | +// WithWriter returns a copy of v whose primary writer is w. |
| 44 | +func (v Venv) WithWriter(w io.Writer) Venv { |
| 45 | + v.Writers.Writer = w |
| 46 | + |
| 47 | + return v |
| 48 | +} |
| 49 | + |
| 50 | +// WithErrWriter returns a copy of v whose error writer is w. |
| 51 | +func (v Venv) WithErrWriter(w io.Writer) Venv { |
| 52 | + v.Writers.ErrWriter = w |
| 53 | + |
| 54 | + return v |
| 55 | +} |
| 56 | + |
| 57 | +// WithExec returns a copy of v whose process executor is exec. |
| 58 | +func (v Venv) WithExec(exec vexec.Exec) Venv { |
| 59 | + v.Exec = exec |
| 60 | + |
| 61 | + return v |
| 62 | +} |
| 63 | + |
| 64 | +// WithHandler returns a copy of v whose executor is an in-memory exec driven |
| 65 | +// by h, for the in-memory test bundles this package serves. |
| 66 | +func (v Venv) WithHandler(h vexec.Handler) Venv { |
| 67 | + v.Exec = vexec.NewMemExec(h) |
| 68 | + |
| 69 | + return v |
28 | 70 | } |
29 | 71 |
|
30 | | -// OSVenv builds the production [Venv]: the real OS filesystem and the |
31 | | -// real OS process executor. |
| 72 | +// WithFS returns a copy of v backed by fs. |
| 73 | +func (v Venv) WithFS(fs vfs.FS) Venv { |
| 74 | + v.FS = fs |
| 75 | + |
| 76 | + return v |
| 77 | +} |
| 78 | + |
| 79 | +// WithEnv returns a copy of v whose shell environment is env. A nil env |
| 80 | +// becomes an empty map so the result still satisfies [Venv.RequireEnv]. |
| 81 | +func (v Venv) WithEnv(env map[string]string) Venv { |
| 82 | + if env == nil { |
| 83 | + env = map[string]string{} |
| 84 | + } |
| 85 | + |
| 86 | + v.Env = env |
| 87 | + |
| 88 | + return v |
| 89 | +} |
| 90 | + |
| 91 | +// RequireEnv panics with [ErrVenvEnvUnset] when Env is nil, guarding |
| 92 | +// functions that write into the shared environment. |
| 93 | +func (v Venv) RequireEnv() { |
| 94 | + if v.Env == nil { |
| 95 | + panic(ErrVenvEnvUnset) |
| 96 | + } |
| 97 | +} |
| 98 | + |
| 99 | +// OSVenv builds the production [Venv]: the real OS filesystem, the real |
| 100 | +// OS process executor, a snapshot of the OS environment, and stdout/stderr |
| 101 | +// wired to the real OS streams. |
32 | 102 | func OSVenv() Venv { |
33 | | - return Venv{FS: vfs.NewOSFS(), Exec: vexec.NewOSExec()} |
| 103 | + return Venv{ |
| 104 | + FS: vfs.NewOSFS(), |
| 105 | + Exec: vexec.NewOSExec(), |
| 106 | + Env: parseEnviron(os.Environ()), |
| 107 | + Writers: writer.Writers{Writer: os.Stdout, ErrWriter: os.Stderr}, |
| 108 | + } |
| 109 | +} |
| 110 | + |
| 111 | +// parseEnviron turns os.Environ-style KEY=VALUE entries into a map. An entry |
| 112 | +// with no "=" maps the whole string to an empty value. |
| 113 | +func parseEnviron(environ []string) map[string]string { |
| 114 | + out := make(map[string]string, len(environ)) |
| 115 | + |
| 116 | + for _, entry := range environ { |
| 117 | + key, value, _ := strings.Cut(entry, "=") |
| 118 | + out[key] = value |
| 119 | + } |
| 120 | + |
| 121 | + return out |
34 | 122 | } |
0 commit comments