docs/commands/env_test.go

583 lines
18 KiB
Go

package commands
import (
"os"
"path/filepath"
"strings"
"testing"
"fmt"
"github.com/docker/machine/commands/commandstest"
"github.com/docker/machine/commands/mcndirs"
"github.com/docker/machine/drivers/fakedriver"
"github.com/docker/machine/libmachine"
"github.com/docker/machine/libmachine/auth"
"github.com/docker/machine/libmachine/check"
"github.com/docker/machine/libmachine/host"
"github.com/docker/machine/libmachine/libmachinetest"
"github.com/docker/machine/libmachine/state"
"github.com/stretchr/testify/assert"
)
type FakeConnChecker struct {
DockerHost string
AuthOptions *auth.Options
Err error
}
func (fcc *FakeConnChecker) Check(_ *host.Host, _ bool) (string, *auth.Options, error) {
return fcc.DockerHost, fcc.AuthOptions, fcc.Err
}
type SimpleUsageHintGenerator struct {
Hint string
}
func (suhg *SimpleUsageHintGenerator) GenerateUsageHint(_ string, _ []string) string {
return suhg.Hint
}
func TestHints(t *testing.T) {
var tests = []struct {
userShell string
commandLine string
expectedHints string
}{
{"", "machine env default", "# Run this command to configure your shell: \n# eval \"$(machine env default)\"\n"},
{"", "machine env --no-proxy default", "# Run this command to configure your shell: \n# eval \"$(machine env --no-proxy default)\"\n"},
{"", "machine env --swarm default", "# Run this command to configure your shell: \n# eval \"$(machine env --swarm default)\"\n"},
{"", "machine env --no-proxy --swarm default", "# Run this command to configure your shell: \n# eval \"$(machine env --no-proxy --swarm default)\"\n"},
{"", "machine env --unset", "# Run this command to configure your shell: \n# eval \"$(machine env --unset)\"\n"},
{"fish", "./machine env --shell=fish default", "# Run this command to configure your shell: \n# eval (./machine env --shell=fish default)\n"},
{"fish", "./machine env --shell=fish --no-proxy default", "# Run this command to configure your shell: \n# eval (./machine env --shell=fish --no-proxy default)\n"},
{"fish", "./machine env --shell=fish --swarm default", "# Run this command to configure your shell: \n# eval (./machine env --shell=fish --swarm default)\n"},
{"fish", "./machine env --shell=fish --no-proxy --swarm default", "# Run this command to configure your shell: \n# eval (./machine env --shell=fish --no-proxy --swarm default)\n"},
{"fish", "./machine env --shell=fish --unset", "# Run this command to configure your shell: \n# eval (./machine env --shell=fish --unset)\n"},
{"powershell", "./machine env --shell=powershell default", "# Run this command to configure your shell: \n# ./machine env --shell=powershell default | Invoke-Expression\n"},
{"powershell", "./machine env --shell=powershell --no-proxy default", "# Run this command to configure your shell: \n# ./machine env --shell=powershell --no-proxy default | Invoke-Expression\n"},
{"powershell", "./machine env --shell=powershell --swarm default", "# Run this command to configure your shell: \n# ./machine env --shell=powershell --swarm default | Invoke-Expression\n"},
{"powershell", "./machine env --shell=powershell --no-proxy --swarm default", "# Run this command to configure your shell: \n# ./machine env --shell=powershell --no-proxy --swarm default | Invoke-Expression\n"},
{"powershell", "./machine env --shell=powershell --unset", "# Run this command to configure your shell: \n# ./machine env --shell=powershell --unset | Invoke-Expression\n"},
{"cmd", "./machine env --shell=cmd default", "REM Run this command to configure your shell: \nREM \tFOR /f \"tokens=*\" %i IN ('./machine env --shell=cmd default') DO %i\n"},
{"cmd", "./machine env --shell=cmd --no-proxy default", "REM Run this command to configure your shell: \nREM \tFOR /f \"tokens=*\" %i IN ('./machine env --shell=cmd --no-proxy default') DO %i\n"},
{"cmd", "./machine env --shell=cmd --swarm default", "REM Run this command to configure your shell: \nREM \tFOR /f \"tokens=*\" %i IN ('./machine env --shell=cmd --swarm default') DO %i\n"},
{"cmd", "./machine env --shell=cmd --no-proxy --swarm default", "REM Run this command to configure your shell: \nREM \tFOR /f \"tokens=*\" %i IN ('./machine env --shell=cmd --no-proxy --swarm default') DO %i\n"},
{"cmd", "./machine env --shell=cmd --unset", "REM Run this command to configure your shell: \nREM \tFOR /f \"tokens=*\" %i IN ('./machine env --shell=cmd --unset') DO %i\n"},
{"emacs", "./machine env --shell=emacs default", ";; Run this command to configure your shell: \n;; (with-temp-buffer (shell-command \"./machine env --shell=emacs default\" (current-buffer)) (eval-buffer))\n"},
{"emacs", "./machine env --shell=emacs --no-proxy default", ";; Run this command to configure your shell: \n;; (with-temp-buffer (shell-command \"./machine env --shell=emacs --no-proxy default\" (current-buffer)) (eval-buffer))\n"},
{"emacs", "./machine env --shell=emacs --swarm default", ";; Run this command to configure your shell: \n;; (with-temp-buffer (shell-command \"./machine env --shell=emacs --swarm default\" (current-buffer)) (eval-buffer))\n"},
{"emacs", "./machine env --shell=emacs --no-proxy --swarm default", ";; Run this command to configure your shell: \n;; (with-temp-buffer (shell-command \"./machine env --shell=emacs --no-proxy --swarm default\" (current-buffer)) (eval-buffer))\n"},
{"emacs", "./machine env --shell=emacs --unset", ";; Run this command to configure your shell: \n;; (with-temp-buffer (shell-command \"./machine env --shell=emacs --unset\" (current-buffer)) (eval-buffer))\n"},
}
for _, test := range tests {
hints := defaultUsageHinter.GenerateUsageHint(test.userShell, strings.Split(test.commandLine, " "))
assert.Equal(t, test.expectedHints, hints)
}
}
func revertUsageHinter(uhg UsageHintGenerator) {
defaultUsageHinter = uhg
}
func TestShellCfgSet(t *testing.T) {
const (
usageHint = "This is a usage hint"
)
// TODO: This should be embedded in some kind of wrapper struct for all
// these `env` operations.
defer revertUsageHinter(defaultUsageHinter)
defaultUsageHinter = &SimpleUsageHintGenerator{usageHint}
var tests = []struct {
description string
commandLine CommandLine
api libmachine.API
connChecker check.ConnChecker
noProxyVar string
noProxyValue string
expectedShellCfg *ShellConfig
expectedErr error
}{
{
description: "no host name specified",
commandLine: &commandstest.FakeCommandLine{
CliArgs: nil,
},
expectedShellCfg: nil,
expectedErr: errImproperEnvArgs,
},
{
description: "bash shell set happy path without any flags set",
commandLine: &commandstest.FakeCommandLine{
CliArgs: []string{"quux"},
LocalFlags: &commandstest.FakeFlagger{
Data: map[string]interface{}{
"shell": "bash",
"swarm": false,
"no-proxy": false,
},
},
},
api: &libmachinetest.FakeAPI{
Hosts: []*host.Host{
{
Name: "quux",
},
},
},
connChecker: &FakeConnChecker{
DockerHost: "tcp://1.2.3.4:2376",
AuthOptions: nil,
Err: nil,
},
expectedShellCfg: &ShellConfig{
Prefix: "export ",
Delimiter: "=\"",
Suffix: "\"\n",
DockerCertPath: filepath.Join(mcndirs.GetMachineDir(), "quux"),
DockerHost: "tcp://1.2.3.4:2376",
DockerTLSVerify: "1",
UsageHint: usageHint,
MachineName: "quux",
},
expectedErr: nil,
},
{
description: "fish shell set happy path",
commandLine: &commandstest.FakeCommandLine{
CliArgs: []string{"quux"},
LocalFlags: &commandstest.FakeFlagger{
Data: map[string]interface{}{
"shell": "fish",
"swarm": false,
"no-proxy": false,
},
},
},
api: &libmachinetest.FakeAPI{
Hosts: []*host.Host{
{
Name: "quux",
},
},
},
connChecker: &FakeConnChecker{
DockerHost: "tcp://1.2.3.4:2376",
AuthOptions: nil,
Err: nil,
},
expectedShellCfg: &ShellConfig{
Prefix: "set -gx ",
Suffix: "\";\n",
Delimiter: " \"",
DockerCertPath: filepath.Join(mcndirs.GetMachineDir(), "quux"),
DockerHost: "tcp://1.2.3.4:2376",
DockerTLSVerify: "1",
UsageHint: usageHint,
MachineName: "quux",
},
expectedErr: nil,
},
{
description: "powershell set happy path",
commandLine: &commandstest.FakeCommandLine{
CliArgs: []string{"quux"},
LocalFlags: &commandstest.FakeFlagger{
Data: map[string]interface{}{
"shell": "powershell",
"swarm": false,
"no-proxy": false,
},
},
},
api: &libmachinetest.FakeAPI{
Hosts: []*host.Host{
{
Name: "quux",
},
},
},
connChecker: &FakeConnChecker{
DockerHost: "tcp://1.2.3.4:2376",
AuthOptions: nil,
Err: nil,
},
expectedShellCfg: &ShellConfig{
Prefix: "$Env:",
Suffix: "\"\n",
Delimiter: " = \"",
DockerCertPath: filepath.Join(mcndirs.GetMachineDir(), "quux"),
DockerHost: "tcp://1.2.3.4:2376",
DockerTLSVerify: "1",
UsageHint: usageHint,
MachineName: "quux",
},
expectedErr: nil,
},
{
description: "emacs set happy path",
commandLine: &commandstest.FakeCommandLine{
CliArgs: []string{"quux"},
LocalFlags: &commandstest.FakeFlagger{
Data: map[string]interface{}{
"shell": "emacs",
"swarm": false,
"no-proxy": false,
},
},
},
api: &libmachinetest.FakeAPI{
Hosts: []*host.Host{
{
Name: "quux",
},
},
},
connChecker: &FakeConnChecker{
DockerHost: "tcp://1.2.3.4:2376",
AuthOptions: nil,
Err: nil,
},
expectedShellCfg: &ShellConfig{
Prefix: "(setenv \"",
Suffix: "\")\n",
Delimiter: "\" \"",
DockerCertPath: filepath.Join(mcndirs.GetMachineDir(), "quux"),
DockerHost: "tcp://1.2.3.4:2376",
DockerTLSVerify: "1",
UsageHint: usageHint,
MachineName: "quux",
},
expectedErr: nil,
},
{
description: "cmd.exe happy path",
commandLine: &commandstest.FakeCommandLine{
CliArgs: []string{"quux"},
LocalFlags: &commandstest.FakeFlagger{
Data: map[string]interface{}{
"shell": "cmd",
"swarm": false,
"no-proxy": false,
},
},
},
api: &libmachinetest.FakeAPI{
Hosts: []*host.Host{
{
Name: "quux",
},
},
},
connChecker: &FakeConnChecker{
DockerHost: "tcp://1.2.3.4:2376",
AuthOptions: nil,
Err: nil,
},
expectedShellCfg: &ShellConfig{
Prefix: "SET ",
Suffix: "\n",
Delimiter: "=",
DockerCertPath: filepath.Join(mcndirs.GetMachineDir(), "quux"),
DockerHost: "tcp://1.2.3.4:2376",
DockerTLSVerify: "1",
UsageHint: usageHint,
MachineName: "quux",
},
expectedErr: nil,
},
{
description: "bash shell set happy path with --no-proxy flag; no existing environment variable set",
commandLine: &commandstest.FakeCommandLine{
CliArgs: []string{"quux"},
LocalFlags: &commandstest.FakeFlagger{
Data: map[string]interface{}{
"shell": "bash",
"swarm": false,
"no-proxy": true,
},
},
},
api: &libmachinetest.FakeAPI{
Hosts: []*host.Host{
{
Name: "quux",
Driver: &fakedriver.Driver{
MockState: state.Running,
MockIP: "1.2.3.4",
},
},
},
},
connChecker: &FakeConnChecker{
DockerHost: "tcp://1.2.3.4:2376",
AuthOptions: nil,
Err: nil,
},
expectedShellCfg: &ShellConfig{
Prefix: "export ",
Delimiter: "=\"",
Suffix: "\"\n",
DockerCertPath: filepath.Join(mcndirs.GetMachineDir(), "quux"),
DockerHost: "tcp://1.2.3.4:2376",
DockerTLSVerify: "1",
UsageHint: usageHint,
NoProxyVar: "NO_PROXY",
NoProxyValue: "1.2.3.4", // From FakeDriver
MachineName: "quux",
},
noProxyVar: "NO_PROXY",
noProxyValue: "",
expectedErr: nil,
},
{
description: "bash shell set happy path with --no-proxy flag; existing environment variable _is_ set",
commandLine: &commandstest.FakeCommandLine{
CliArgs: []string{"quux"},
LocalFlags: &commandstest.FakeFlagger{
Data: map[string]interface{}{
"shell": "bash",
"swarm": false,
"no-proxy": true,
},
},
},
api: &libmachinetest.FakeAPI{
Hosts: []*host.Host{
{
Name: "quux",
Driver: &fakedriver.Driver{
MockState: state.Running,
MockIP: "1.2.3.4",
},
},
},
},
connChecker: &FakeConnChecker{
DockerHost: "tcp://1.2.3.4:2376",
AuthOptions: nil,
Err: nil,
},
expectedShellCfg: &ShellConfig{
Prefix: "export ",
Delimiter: "=\"",
Suffix: "\"\n",
DockerCertPath: filepath.Join(mcndirs.GetMachineDir(), "quux"),
DockerHost: "tcp://1.2.3.4:2376",
DockerTLSVerify: "1",
UsageHint: usageHint,
NoProxyVar: "no_proxy",
NoProxyValue: "192.168.59.1,1.2.3.4", // From FakeDriver
MachineName: "quux",
},
noProxyVar: "no_proxy",
noProxyValue: "192.168.59.1",
expectedErr: nil,
},
}
for _, test := range tests {
// TODO: Ideally this should not hit the environment at all but
// rather should go through an interface.
os.Setenv(test.noProxyVar, test.noProxyValue)
t.Log(test.description)
check.DefaultConnChecker = test.connChecker
shellCfg, err := shellCfgSet(test.commandLine, test.api)
assert.Equal(t, test.expectedShellCfg, shellCfg)
assert.Equal(t, test.expectedErr, err)
os.Unsetenv(test.noProxyVar)
}
}
func TestShellCfgUnset(t *testing.T) {
const (
usageHint = "This is the unset usage hint"
)
defer revertUsageHinter(defaultUsageHinter)
defaultUsageHinter = &SimpleUsageHintGenerator{usageHint}
var tests = []struct {
description string
commandLine CommandLine
api libmachine.API
connChecker check.ConnChecker
noProxyVar string
noProxyValue string
expectedShellCfg *ShellConfig
expectedErr error
}{
{
description: "more than expected args passed in",
commandLine: &commandstest.FakeCommandLine{
CliArgs: []string{"foo", "bar"},
},
expectedShellCfg: nil,
expectedErr: errImproperUnsetEnvArgs,
},
{
description: "bash shell unset happy path without any flags set",
commandLine: &commandstest.FakeCommandLine{
CliArgs: nil,
LocalFlags: &commandstest.FakeFlagger{
Data: map[string]interface{}{
"shell": "bash",
"swarm": false,
"no-proxy": false,
},
},
},
api: &libmachinetest.FakeAPI{},
connChecker: &FakeConnChecker{
DockerHost: "tcp://1.2.3.4:2376",
AuthOptions: nil,
Err: nil,
},
expectedShellCfg: &ShellConfig{
Prefix: "unset ",
Suffix: "\n",
Delimiter: "",
UsageHint: usageHint,
},
expectedErr: nil,
},
{
description: "fish shell unset happy path",
commandLine: &commandstest.FakeCommandLine{
CliArgs: nil,
LocalFlags: &commandstest.FakeFlagger{
Data: map[string]interface{}{
"shell": "fish",
"swarm": false,
"no-proxy": false,
},
},
},
api: &libmachinetest.FakeAPI{},
connChecker: &FakeConnChecker{
DockerHost: "tcp://1.2.3.4:2376",
AuthOptions: nil,
Err: nil,
},
expectedShellCfg: &ShellConfig{
Prefix: "set -e ",
Suffix: ";\n",
Delimiter: "",
UsageHint: usageHint,
},
expectedErr: nil,
},
{
description: "powershell unset happy path",
commandLine: &commandstest.FakeCommandLine{
CliArgs: nil,
LocalFlags: &commandstest.FakeFlagger{
Data: map[string]interface{}{
"shell": "powershell",
"swarm": false,
"no-proxy": false,
},
},
},
api: &libmachinetest.FakeAPI{},
connChecker: &FakeConnChecker{
DockerHost: "tcp://1.2.3.4:2376",
AuthOptions: nil,
Err: nil,
},
expectedShellCfg: &ShellConfig{
Prefix: `Remove-Item Env:\\`,
Suffix: "\n",
Delimiter: "",
UsageHint: usageHint,
},
expectedErr: nil,
},
{
description: "cmd.exe unset happy path",
commandLine: &commandstest.FakeCommandLine{
CliArgs: nil,
LocalFlags: &commandstest.FakeFlagger{
Data: map[string]interface{}{
"shell": "cmd",
"swarm": false,
"no-proxy": false,
},
},
},
api: &libmachinetest.FakeAPI{},
connChecker: &FakeConnChecker{
DockerHost: "tcp://1.2.3.4:2376",
AuthOptions: nil,
Err: nil,
},
expectedShellCfg: &ShellConfig{
Prefix: "SET ",
Suffix: "\n",
Delimiter: "=",
UsageHint: usageHint,
},
expectedErr: nil,
},
// TODO: There is kind of a funny bug (feature?) I discovered
// reasoning about unset() where if there was a NO_PROXY value
// set _before_ the original docker-machine env, it won't be
// restored (NO_PROXY won't be unset at all, it will stay the
// same). We should define expected behavior in this case.
}
for _, test := range tests {
os.Setenv(test.noProxyVar, test.noProxyValue)
t.Log(test.description)
check.DefaultConnChecker = test.connChecker
shellCfg, err := shellCfgUnset(test.commandLine, test.api)
assert.Equal(t, test.expectedShellCfg, shellCfg)
assert.Equal(t, test.expectedErr, err)
os.Setenv(test.noProxyVar, "")
}
}
func TestDetectBash(t *testing.T) {
originalShell := os.Getenv("SHELL")
os.Setenv("SHELL", "/bin/bash")
defer os.Setenv("SHELL", originalShell)
shell, err := detectShell()
assert.Nil(t, err)
assert.Equal(t, "bash", shell)
}
func TestDetectFish(t *testing.T) {
originalShell := os.Getenv("SHELL")
os.Setenv("SHELL", "/bin/bash")
defer os.Setenv("SHELL", originalShell)
originalFishdir := os.Getenv("__fish_bin_dir")
os.Setenv("__fish_bin_dir", "/usr/local/Cellar/fish/2.2.0/bin")
defer os.Setenv("__fish_bin_dir", originalFishdir)
shell, err := detectShell()
assert.Nil(t, err)
assert.Equal(t, "fish", shell)
}
func TestUnknowShell(t *testing.T) {
originalShell := os.Getenv("SHELL")
os.Setenv("SHELL", "")
defer os.Setenv("SHELL", originalShell)
shell, err := detectShell()
fmt.Println(shell)
assert.Equal(t, err, ErrUnknownShell)
assert.Equal(t, "", shell)
}