-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix Windows CI test failures by cleaning env vars (#43)
* Fix Windows CI test failures by cleaning env vars Add script to clean the test environment variables. Uses a Go script for cross-OS portability. * Move cleanenv to publicly installable and documented command * Move error docs to their own h2 section * Address PR docs comments * Add cleanenv package doc * Renamed App.Out to App.StdOut to address comments
- Loading branch information
1 parent
bba38b6
commit bb77d44
Showing
4 changed files
with
239 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
// Command cleanenv removes all environment variables that match given prefixes before running its arguments as a command. | ||
// | ||
// For example, this is useful in GitHub Actions: | ||
// | ||
// export GOOS=js GOARCH=wasm | ||
// cleanenv -remove-prefix GITHUB_ -- go test -cover ./... | ||
// | ||
// The '-remove-prefix' flag can be repeated multiple times to remove even more environment variables. | ||
package main | ||
|
||
import ( | ||
"errors" | ||
"flag" | ||
"fmt" | ||
"io" | ||
"os" | ||
"os/exec" | ||
"strings" | ||
) | ||
|
||
func main() { | ||
app := App{ | ||
Args: os.Args[1:], | ||
Env: os.Environ(), | ||
StdOut: os.Stdout, | ||
ErrOut: os.Stderr, | ||
} | ||
err := app.Run() | ||
if err != nil { | ||
fmt.Fprintln(app.ErrOut, err) | ||
exitCode := 1 | ||
var exitErr *exec.ExitError | ||
if errors.As(err, &exitErr) { | ||
exitCode = exitErr.ExitCode() | ||
} | ||
os.Exit(exitCode) | ||
} | ||
} | ||
|
||
type App struct { | ||
Args []string | ||
Env []string | ||
StdOut, ErrOut io.Writer | ||
} | ||
|
||
func (a App) Run() error { | ||
set := flag.NewFlagSet("cleanenv", flag.ContinueOnError) | ||
var removePrefixes StringSliceFlag | ||
set.Var(&removePrefixes, "remove-prefix", "Remove one or more environment variables with the given prefixes.") | ||
if err := set.Parse(a.Args); err != nil { | ||
return err | ||
} | ||
|
||
var cleanEnv []string | ||
for _, keyValue := range a.Env { | ||
tokens := strings.SplitN(keyValue, "=", 2) | ||
if allowEnvName(tokens[0], removePrefixes) { | ||
cleanEnv = append(cleanEnv, keyValue) | ||
} | ||
} | ||
|
||
arg0, argv, err := splitArgs(set.Args()) | ||
if err != nil { | ||
return err | ||
} | ||
cmd := exec.Command(arg0, argv...) | ||
cmd.Env = cleanEnv | ||
cmd.Stdout = a.StdOut | ||
cmd.Stderr = a.ErrOut | ||
return cmd.Run() | ||
} | ||
|
||
type StringSliceFlag []string | ||
|
||
func (s *StringSliceFlag) Set(value string) error { | ||
*s = append(*s, value) | ||
return nil | ||
} | ||
|
||
func (s *StringSliceFlag) String() string { | ||
return strings.Join(*s, ", ") | ||
} | ||
|
||
func allowEnvName(name string, removePrefixes []string) bool { | ||
for _, prefix := range removePrefixes { | ||
if strings.HasPrefix(name, prefix) { | ||
return false | ||
} | ||
} | ||
return true | ||
} | ||
|
||
func splitArgs(args []string) (string, []string, error) { | ||
if len(args) == 0 { | ||
return "", nil, errors.New("not enough args to run a command") | ||
} | ||
return args[0], args[1:], nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
package main | ||
|
||
import ( | ||
"bytes" | ||
"strings" | ||
"testing" | ||
) | ||
|
||
func TestRun(t *testing.T) { | ||
t.Parallel() | ||
const bashPrintCleanVars = `env | grep CLEAN_ | sort | tr '\n' ' '` | ||
for _, tc := range []struct { | ||
name string | ||
env []string | ||
args []string | ||
expectOutput string | ||
expectErr string | ||
}{ | ||
{ | ||
name: "zero args", | ||
expectErr: "not enough args to run a command", | ||
}, | ||
{ | ||
name: "all env passed through", | ||
env: []string{ | ||
"CLEAN_BAR=bar", | ||
"CLEAN_FOO=foo", | ||
}, | ||
args: []string{"bash", "-c", bashPrintCleanVars}, | ||
expectOutput: "CLEAN_BAR=bar CLEAN_FOO=foo", | ||
}, | ||
{ | ||
name: "remove one variable prefix", | ||
env: []string{ | ||
"CLEAN_BAR=bar", | ||
"CLEAN_FOO=foo", | ||
}, | ||
args: []string{ | ||
"-remove-prefix=CLEAN_BAR", "--", | ||
"bash", "-c", bashPrintCleanVars, | ||
}, | ||
expectOutput: "CLEAN_FOO=foo", | ||
}, | ||
{ | ||
name: "remove common variable prefix", | ||
env: []string{ | ||
"CLEAN_COMMON_BAR=bar", | ||
"CLEAN_COMMON_BAZ=baz", | ||
"CLEAN_FOO=foo", | ||
}, | ||
args: []string{ | ||
"-remove-prefix=CLEAN_COMMON_", "--", | ||
"bash", "-c", bashPrintCleanVars, | ||
}, | ||
expectOutput: "CLEAN_FOO=foo", | ||
}, | ||
{ | ||
name: "remove multiple prefixes", | ||
env: []string{ | ||
"CLEAN_BAR=bar", | ||
"CLEAN_BAZ=baz", | ||
"CLEAN_FOO=foo", | ||
}, | ||
args: []string{ | ||
"-remove-prefix=CLEAN_BAR", | ||
"-remove-prefix=CLEAN_FOO", "--", | ||
"bash", "-c", bashPrintCleanVars, | ||
}, | ||
expectOutput: "CLEAN_BAZ=baz", | ||
}, | ||
} { | ||
tc := tc // enable parallel sub-tests | ||
t.Run(tc.name, func(t *testing.T) { | ||
t.Parallel() | ||
var output bytes.Buffer | ||
app := App{ | ||
Args: tc.args, | ||
Env: tc.env, | ||
StdOut: &output, | ||
ErrOut: &output, | ||
} | ||
err := app.Run() | ||
assertEqualError(t, tc.expectErr, err) | ||
if tc.expectErr != "" { | ||
return | ||
} | ||
|
||
outputStr := strings.TrimSpace(output.String()) | ||
if outputStr != tc.expectOutput { | ||
t.Errorf("Unexpected output: %q != %q", tc.expectOutput, outputStr) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func assertEqualError(t *testing.T, expected string, err error) { | ||
t.Helper() | ||
if expected == "" { | ||
if err != nil { | ||
t.Error("Unexpected error:", err) | ||
} | ||
return | ||
} | ||
|
||
if err == nil { | ||
t.Error("Expected error, got nil") | ||
return | ||
} | ||
message := err.Error() | ||
if expected != message { | ||
t.Errorf("Unexpected error message: %q != %q", expected, message) | ||
} | ||
} |