Repository: tetratelabs/wazero
Pull request #1117 (2023-02-10 06:50:14)

cli: adds env-inherit flag for SCRATCH images
This adds an `env-inherit` boolean flag which adds all variables to the guest ENV.

For example, the below `env-reader.wasm` guest will see the variable `STAR` and anything set via the runner (docker, podman, etc.):
```
--snip--
FROM scratch
COPY --from=build /build/wazero/wazero /bin/wazero

ENTRYPOINT ["/bin/wazero", "run", "-env-inherit", "-cachedir=/cache", "-mount=.:/"]

ENV STAR=wazero

COPY --from=wasm / .
CMD ["env-reader.wasm"]
```

Note: This isn't appropriate otherwise as it will also propagate variables like PATH and TMPDIR which aren't likely valid on the guest, or in worst case may risk crashing.


Patch:
diff --git a/cmd/wazero/Dockerfile b/cmd/wazero/Dockerfile
index 180e7818..b0435d5f 100644
--- a/cmd/wazero/Dockerfile
+++ b/cmd/wazero/Dockerfile
@@ -13,4 +13,4 @@ RUN git clone --depth 1 https://github.com/tetratelabs/wazero.git \
 FROM scratch
 COPY --from=build /build/wazero/wazero /bin/wazero
 
-ENTRYPOINT ["/bin/wazero", "run", "-cachedir=/cache", "-mount=.:/"]
+ENTRYPOINT ["/bin/wazero", "run", "-env-inherit", "-cachedir=/cache", "-mount=.:/"]
diff --git a/cmd/wazero/wazero.go b/cmd/wazero/wazero.go
index fbbc54c1..9191580d 100644
--- a/cmd/wazero/wazero.go
+++ b/cmd/wazero/wazero.go
@@ -122,6 +122,11 @@ func doRun(args []string, stdOut io.Writer, stdErr logging.Writer, exit func(cod
 	flags.Var(&envs, "env", "key=value pair of environment variable to expose to the binary. "+
 		"Can be specified multiple times.")
 
+	var envExport bool
+	flags.BoolVar(&envExport, "env-inherit", false,
+		"inherits any environment variables from the calling process."+
+			"Variables specified with the <env> flag are appended to the inherited list.")
+
 	var mounts sliceFlag
 	flags.Var(&mounts, "mount",
 		"filesystem path to expose to the binary in the form of <path>[:<wasm path>][:ro]. "+
@@ -130,7 +135,7 @@ func doRun(args []string, stdOut io.Writer, stdErr logging.Writer, exit func(cod
 
 	var hostlogging logScopesFlag
 	flags.Var(&hostlogging, "hostlogging",
-		"A comma-separated list of host function scopes to log to stderr. "+
+		"a comma-separated list of host function scopes to log to stderr. "+
 			"This may be specified multiple times. Supported values: clock,exit,filesystem,memory,poll,random")
 
 	cacheDir := cacheDirFlag(flags)
@@ -159,6 +164,9 @@ func doRun(args []string, stdOut io.Writer, stdErr logging.Writer, exit func(cod
 
 	// Don't use map to preserve order
 	var env []string
+	if envExport {
+		envs = append(os.Environ(), envs...)
+	}
 	for _, e := range envs {
 		fields := strings.SplitN(e, "=", 2)
 		if len(fields) != 2 {


Test patch:
diff --git a/cmd/wazero/wazero_test.go b/cmd/wazero/wazero_test.go
index f59899d6..7b41d2b8 100644
--- a/cmd/wazero/wazero_test.go
+++ b/cmd/wazero/wazero_test.go
@@ -11,6 +11,7 @@ import (
 	"path"
 	"path/filepath"
 	"runtime"
+	"strings"
 	"testing"
 
 	"github.com/tetratelabs/wazero/api"
@@ -117,10 +118,10 @@ func TestCompile(t *testing.T) {
 		t.Run(tt.name, func(t *testing.T) {
 			args := append([]string{"compile"}, tt.wazeroOpts...)
 			args = append(args, wasmPath)
-			exitCode, stdOut, stdErr := runMain(t, args)
-			require.Zero(t, stdErr)
-			require.Equal(t, 0, exitCode, stdErr)
-			require.Zero(t, stdOut)
+			exitCode, stdout, stderr := runMain(t, args)
+			require.Zero(t, stderr)
+			require.Equal(t, 0, exitCode, stderr)
+			require.Zero(t, stdout)
 			if test := tt.test; test != nil {
 				test(t)
 			}
@@ -170,15 +171,32 @@ func TestCompile_Errors(t *testing.T) {
 	for _, tc := range tests {
 		tt := tc
 		t.Run(tt.message, func(t *testing.T) {
-			exitCode, _, stdErr := runMain(t, append([]string{"compile"}, tt.args...))
+			exitCode, _, stderr := runMain(t, append([]string{"compile"}, tt.args...))
 
 			require.Equal(t, 1, exitCode)
-			require.Contains(t, stdErr, tt.message)
+			require.Contains(t, stderr, tt.message)
 		})
 	}
 }
 
 func TestRun(t *testing.T) {
+	// Restore env logic borrowed from TestClearenv
+	defer func(origEnv []string) {
+		for _, pair := range origEnv {
+			// Environment variables on Windows can begin with =
+			// https://blogs.msdn.com/b/oldnewthing/archive/2010/05/06/10008132.aspx
+			i := strings.Index(pair[1:], "=") + 1
+			if err := os.Setenv(pair[:i], pair[i+1:]); err != nil {
+				t.Errorf("Setenv(%q, %q) failed during reset: %v", pair[:i], pair[i+1:], err)
+			}
+		}
+	}(os.Environ())
+
+	// Clear the environment first, so we can make strict assertions.
+	os.Clearenv()
+	os.Setenv("ANIMAL", "kitten")
+	os.Setenv("INHERITED", "wazero")
+
 	tmpDir, oldwd := requireChdirToTemp(t)
 	defer os.Chdir(oldwd) //nolint
 
@@ -232,6 +250,18 @@ func TestRun(t *testing.T) {
 			wazeroOpts:     []string{"--env=ANIMAL=bear", "--env=FOOD=sushi"},
 			expectedStdout: "ANIMAL=bear\x00FOOD=sushi\x00",
 		},
+		{
+			name:           "env-inherit",
+			wasm:           wasmWasiEnv,
+			wazeroOpts:     []string{"-env-inherit"},
+			expectedStdout: "ANIMAL=kitten\x00INHERITED=wazero\u0000",
+		},
+		{
+			name:           "env-inherit with env",
+			wasm:           wasmWasiEnv,
+			wazeroOpts:     []string{"-env-inherit", "--env=ANIMAL=bear"},
+			expectedStdout: "ANIMAL=bear\x00INHERITED=wazero\u0000", // not ANIMAL=kitten
+		},
 		{
 			name:           "interpreter",
 			wasm:           wasmWasiArg,
@@ -428,11 +458,11 @@ func TestRun(t *testing.T) {
 			args := append([]string{"run"}, tc.wazeroOpts...)
 			args = append(args, wasmPath)
 			args = append(args, tc.wasmArgs...)
-			exitCode, stdOut, stdErr := runMain(t, args)
+			exitCode, stdout, stderr := runMain(t, args)
 
-			require.Equal(t, tc.expectedStderr, stdErr)
-			require.Equal(t, tc.expectedExitCode, exitCode, stdErr)
-			require.Equal(t, tc.expectedStdout, stdOut)
+			require.Equal(t, tc.expectedStderr, stderr)
+			require.Equal(t, tc.expectedExitCode, exitCode, stderr)
+			require.Equal(t, tc.expectedStdout, stdout)
 			if test := tc.test; test != nil {
 				test(t)
 			}
@@ -441,10 +471,10 @@ func TestRun(t *testing.T) {
 }
 
 func TestVersion(t *testing.T) {
-	exitCode, stdOut, stdErr := runMain(t, []string{"version"})
+	exitCode, stdout, stderr := runMain(t, []string{"version"})
 	require.Equal(t, 0, exitCode)
-	require.Equal(t, version.GetWazeroVersion()+"\n", stdOut)
-	require.Equal(t, "", stdErr)
+	require.Equal(t, version.GetWazeroVersion()+"\n", stdout)
+	require.Equal(t, "", stderr)
 }
 
 func TestRun_Errors(t *testing.T) {
@@ -487,10 +517,10 @@ func TestRun_Errors(t *testing.T) {
 	for _, tc := range tests {
 		tt := tc
 		t.Run(tt.message, func(t *testing.T) {
-			exitCode, _, stdErr := runMain(t, append([]string{"run"}, tt.args...))
+			exitCode, _, stderr := runMain(t, append([]string{"run"}, tt.args...))
 
 			require.Equal(t, 1, exitCode)
-			require.Contains(t, stdErr, tt.message)
+			require.Contains(t, stderr, tt.message)
 		})
 	}
 }
@@ -621,9 +651,9 @@ func Test_logScopesFlag(t *testing.T) {
 }
 
 func TestHelp(t *testing.T) {
-	exitCode, _, stdErr := runMain(t, []string{"-h"})
+	exitCode, _, stderr := runMain(t, []string{"-h"})
 	require.Equal(t, 0, exitCode)
-	fmt.Println(stdErr)
+	fmt.Println(stderr)
 	require.Equal(t, `wazero CLI
 
 Usage:
@@ -633,7 +663,7 @@ Commands:
   compile	Pre-compiles a WebAssembly binary
   run		Runs a WebAssembly binary
   version	Displays the version of wazero CLI
-`, stdErr)
+`, stderr)
 }
 
 func runMain(t *testing.T, args []string) (int, string, string) {
@@ -645,8 +675,8 @@ func runMain(t *testing.T, args []string) (int, string, string) {
 	os.Args = append([]string{"wazero"}, args...)
 
 	var exitCode int
-	stdOut := &bytes.Buffer{}
-	stdErr := &bytes.Buffer{}
+	stdout := &bytes.Buffer{}
+	stderr := &bytes.Buffer{}
 	var exited bool
 	func() {
 		defer func() {
@@ -655,7 +685,7 @@ func runMain(t *testing.T, args []string) (int, string, string) {
 			}
 		}()
 		flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
-		doMain(stdOut, stdErr, func(code int) {
+		doMain(stdout, stderr, func(code int) {
 			exitCode = code
 			panic(code)
 		})
@@ -663,7 +693,7 @@ func runMain(t *testing.T, args []string) (int, string, string) {
 
 	require.True(t, exited)
 
-	return exitCode, stdOut.String(), stdErr.String()
+	return exitCode, stdout.String(), stderr.String()
 }
 
 // compileGoJS compiles "testdata/cat/cat.go" on demand as the binary generated