diff --git a/commands/commands.go b/commands/commands.go index a9644acc95..03740c7afb 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -1,9 +1,11 @@ package commands import ( + "errors" "fmt" "os" "path/filepath" + "runtime" "sort" "strings" @@ -33,6 +35,10 @@ import ( "github.com/docker/machine/utils" ) +var ( + ErrUnknownShell = errors.New("unknown shell") +) + type machineConfig struct { machineName string machineDir string @@ -253,6 +259,10 @@ var Commands = []cli.Command{ Name: "swarm", Usage: "Display the Swarm config instead of the Docker daemon", }, + cli.StringFlag{ + Name: "shell", + Usage: "Force environment to be configured for specified shell", + }, cli.BoolFlag{ Name: "unset, u", Usage: "Unset variables instead of setting them", @@ -669,3 +679,19 @@ func getCertPathInfo(c *cli.Context) libmachine.CertPathInfo { ClientKeyPath: clientKeyPath, } } + +func detectShell() (string, error) { + // attempt to get the SHELL env var + shell := filepath.Base(os.Getenv("SHELL")) + // none detected; check for windows env + if runtime.GOOS == "windows" { + log.Printf("On Windows, please specify either 'cmd' or 'powershell' with the --shell flag.\n\n") + return "", ErrUnknownShell + } + + if shell == "" { + return "", ErrUnknownShell + } + + return shell, nil +} diff --git a/commands/create.go b/commands/create.go index c22e2f1d97..23d2d07e4e 100644 --- a/commands/create.go +++ b/commands/create.go @@ -2,7 +2,6 @@ package commands import ( "fmt" - "os" "path/filepath" log "github.com/Sirupsen/logrus" @@ -96,21 +95,8 @@ func cmdCreate(c *cli.Context) { log.Fatalf("error setting active host: %v", err) } - info := "" - userShell := filepath.Base(os.Getenv("SHELL")) - - switch userShell { - case "fish": - info = fmt.Sprintf("%s env %s | source", c.App.Name, name) - default: - info = fmt.Sprintf(`eval "$(%s env %s)"`, c.App.Name, name) - } - - log.Infof("%q has been created and is now the active machine.", name) - - if info != "" { - log.Infof("To point your Docker client at it, run this in your shell: %s", info) - } + info := fmt.Sprintf("%s env %s", c.App.Name, name) + log.Infof("To point your Docker client at it, run this in your shell: %s", info) } // If the user has specified a driver, they should not see the flags for all diff --git a/commands/env.go b/commands/env.go index 2e688be2db..79059c7c12 100644 --- a/commands/env.go +++ b/commands/env.go @@ -4,8 +4,8 @@ import ( "fmt" "net/url" "os" - "path/filepath" "strings" + "text/template" log "github.com/Sirupsen/logrus" @@ -13,14 +13,72 @@ import ( "github.com/docker/machine/utils" ) +const ( + envTmpl = `{{ .Prefix }}DOCKER_TLS_VERIFY{{ .Delimiter }}{{ .DockerTLSVerify }}{{ .Suffix }}{{ .Prefix }}DOCKER_HOST{{ .Delimiter }}{{ .DockerHost }}{{ .Suffix }}{{ .Prefix }}DOCKER_CERT_PATH{{ .Delimiter }}{{ .DockerCertPath }}{{ .Suffix }}{{ .UsageHint }}` +) + +type ShellConfig struct { + Prefix string + Delimiter string + Suffix string + DockerCertPath string + DockerHost string + DockerTLSVerify string + UsageHint string +} + func cmdEnv(c *cli.Context) { - userShell := filepath.Base(os.Getenv("SHELL")) + userShell := c.String("shell") + if userShell == "" { + shell, err := detectShell() + if err != nil { + log.Fatal(err) + } + userShell = shell + } + + t := template.New("envConfig") + + usageHint := generateUsageHint(c.App.Name, c.Args().First(), userShell) + + shellCfg := ShellConfig{ + DockerCertPath: "", + DockerHost: "", + DockerTLSVerify: "", + } + + // unset vars if c.Bool("unset") { switch userShell { case "fish": - fmt.Printf("set -e DOCKER_TLS_VERIFY;\nset -e DOCKER_CERT_PATH;\nset -e DOCKER_HOST;\n") + shellCfg.Prefix = "set -e " + shellCfg.Delimiter = "" + shellCfg.Suffix = ";\n" + case "powershell": + shellCfg.Prefix = "Remove-Item Env:\\\\" + shellCfg.Delimiter = "" + shellCfg.Suffix = "\n" + case "cmd": + // since there is no way to unset vars in cmd just reset to empty + shellCfg.DockerCertPath = "" + shellCfg.DockerHost = "" + shellCfg.DockerTLSVerify = "" + shellCfg.Prefix = "set " + shellCfg.Delimiter = "=" + shellCfg.Suffix = "\n" default: - fmt.Println("unset DOCKER_TLS_VERIFY DOCKER_CERT_PATH DOCKER_HOST") + shellCfg.Prefix = "unset " + shellCfg.Delimiter = " " + shellCfg.Suffix = "\n" + } + + tmpl, err := t.Parse(envTmpl) + if err != nil { + log.Fatal(err) + } + + if err := tmpl.Execute(os.Stdout, shellCfg); err != nil { + log.Fatal(err) } return } @@ -31,7 +89,7 @@ func cmdEnv(c *cli.Context) { } if cfg.machineUrl == "" { - log.Fatalf("%s is not running. Please start this with docker-machine start %s", cfg.machineName, cfg.machineName) + log.Fatalf("%s is not running. Please start this with %s start %s", cfg.machineName, c.App.Name, cfg.machineName) } dockerHost := cfg.machineUrl @@ -83,32 +141,64 @@ func cmdEnv(c *cli.Context) { } } - usageHint := generateUsageHint(c.Args().First(), userShell) + shellCfg = ShellConfig{ + DockerCertPath: cfg.machineDir, + DockerHost: dockerHost, + DockerTLSVerify: "1", + UsageHint: usageHint, + } switch userShell { case "fish": - fmt.Printf("set -x DOCKER_TLS_VERIFY 1;\nset -x DOCKER_CERT_PATH %q;\nset -x DOCKER_HOST %s;\n\n%s\n", - cfg.machineDir, dockerHost, usageHint) + shellCfg.Prefix = "set -x " + shellCfg.Suffix = "\";\n" + shellCfg.Delimiter = " \"" + case "powershell": + shellCfg.Prefix = "$Env:" + shellCfg.Suffix = "\"\n" + shellCfg.Delimiter = " = \"" + case "cmd": + shellCfg.Prefix = "set " + shellCfg.Suffix = "\n" + shellCfg.Delimiter = "=" default: - fmt.Printf("export DOCKER_TLS_VERIFY=1\nexport DOCKER_CERT_PATH=%q\nexport DOCKER_HOST=%s\n\n%s\n", - cfg.machineDir, dockerHost, usageHint) + shellCfg.Prefix = "export " + shellCfg.Suffix = "\"\n" + shellCfg.Delimiter = "=\"" + } + + tmpl, err := t.Parse(envTmpl) + if err != nil { + log.Fatal(err) + } + + if err := tmpl.Execute(os.Stdout, shellCfg); err != nil { + log.Fatal(err) } } -func generateUsageHint(machineName string, userShell string) string { +func generateUsageHint(appName, machineName, userShell string) string { cmd := "" switch userShell { case "fish": if machineName != "" { - cmd = fmt.Sprintf("eval (docker-machine env %s)", machineName) + cmd = fmt.Sprintf("eval (%s env %s)", appName, machineName) } else { - cmd = "eval (docker-machine env)" + cmd = fmt.Sprintf("eval (%s env)", appName) } + case "powershell": + if machineName != "" { + cmd = fmt.Sprintf("%s env --shell=powershell %s | Invoke-Expression", appName, machineName) + } else { + cmd = fmt.Sprintf("%s env --shell=powershell | Invoke-Expression", appName) + } + case "cmd": + cmd = "copy and paste the above values into your command prompt" default: if machineName != "" { - cmd = fmt.Sprintf("eval \"$(docker-machine env %s)\"", machineName) + cmd = fmt.Sprintf("eval \"$(%s env %s)\"", appName, machineName) } else { - cmd = "eval \"$(docker-machine env)\"" + cmd = fmt.Sprintf("eval \"$(%s env)\"", appName) } } diff --git a/commands/env_test.go b/commands/env_test.go index e65bd80d0d..a90aa63b23 100644 --- a/commands/env_test.go +++ b/commands/env_test.go @@ -83,6 +83,9 @@ func TestCmdEnvBash(t *testing.T) { set := flag.NewFlagSet("config", 0) c := cli.NewContext(nil, set, set) + c.App = &cli.App{ + Name: "docker-machine-test", + } cmdEnv(c) w.Close() @@ -103,9 +106,9 @@ func TestCmdEnvBash(t *testing.T) { testMachineDir := filepath.Join(store.GetPath(), "machines", host.Name) expected := map[string]string{ - "DOCKER_TLS_VERIFY": "1", + "DOCKER_TLS_VERIFY": "\"1\"", "DOCKER_CERT_PATH": fmt.Sprintf("\"%s\"", testMachineDir), - "DOCKER_HOST": "unix:///var/run/docker.sock", + "DOCKER_HOST": "\"unix:///var/run/docker.sock\"", } for k, v := range envvars { @@ -181,6 +184,9 @@ func TestCmdEnvFish(t *testing.T) { set := flag.NewFlagSet("config", 0) c := cli.NewContext(nil, set, set) + c.App = &cli.App{ + Name: "docker-machine-test", + } cmdEnv(c) w.Close() @@ -201,9 +207,111 @@ func TestCmdEnvFish(t *testing.T) { testMachineDir := filepath.Join(store.GetPath(), "machines", host.Name) expected := map[string]string{ - "DOCKER_TLS_VERIFY": "1", + "DOCKER_TLS_VERIFY": "\"1\"", "DOCKER_CERT_PATH": fmt.Sprintf("\"%s\"", testMachineDir), - "DOCKER_HOST": "unix:///var/run/docker.sock", + "DOCKER_HOST": "\"unix:///var/run/docker.sock\"", + } + + for k, v := range envvars { + if v != expected[k] { + t.Fatalf("Expected %s == <%s>, but was <%s>", k, expected[k], v) + } + } +} + +func TestCmdEnvPowerShell(t *testing.T) { + stdout := os.Stdout + shell := os.Getenv("SHELL") + r, w, _ := os.Pipe() + + os.Stdout = w + os.Setenv("MACHINE_STORAGE_PATH", TestStoreDir) + os.Setenv("SHELL", "") + + defer func() { + os.Setenv("MACHINE_STORAGE_PATH", "") + os.Setenv("SHELL", shell) + os.Stdout = stdout + }() + + if err := clearHosts(); err != nil { + t.Fatal(err) + } + + flags := getTestDriverFlags() + + store, sErr := getTestStore() + if sErr != nil { + t.Fatal(sErr) + } + + mcn, err := libmachine.New(store) + if err != nil { + t.Fatal(err) + } + + hostOptions := &libmachine.HostOptions{ + EngineOptions: &engine.EngineOptions{}, + SwarmOptions: &swarm.SwarmOptions{ + Master: false, + Discovery: "", + Address: "", + Host: "", + }, + AuthOptions: &auth.AuthOptions{}, + } + + host, err := mcn.Create("test-a", "none", hostOptions, flags) + if err != nil { + t.Fatal(err) + } + + host, err = mcn.Get("test-a") + if err != nil { + t.Fatalf("error loading host: %v", err) + } + + if err := mcn.SetActive(host); err != nil { + t.Fatalf("error setting active host: %v", err) + } + + outStr := make(chan string) + + go func() { + var testOutput bytes.Buffer + io.Copy(&testOutput, r) + outStr <- testOutput.String() + }() + + set := flag.NewFlagSet("config", 0) + set.String("shell", "powershell", "") + c := cli.NewContext(nil, set, set) + c.App = &cli.App{ + Name: "docker-machine-test", + } + cmdEnv(c) + + w.Close() + + out := <-outStr + + // parse the output into a map of envvar:value for easier testing below + envvars := make(map[string]string) + for _, e := range strings.Split(strings.TrimSpace(out), "\n") { + if !strings.HasPrefix(e, "$Env") { + continue + } + kv := strings.SplitN(e, " = ", 2) + key, value := kv[0], kv[1] + envvars[strings.Replace(key, "$Env:", "", 1)] = value + } + + testMachineDir := filepath.Join(store.GetPath(), "machines", host.Name) + + expected := map[string]string{ + "DOCKER_TLS_VERIFY": "\"1\"", + "DOCKER_CERT_PATH": fmt.Sprintf("\"%s\"", testMachineDir), + "DOCKER_HOST": "\"unix:///var/run/docker.sock\"", } for k, v := range envvars { diff --git a/drivers/virtualbox/virtualbox.go b/drivers/virtualbox/virtualbox.go index 3351a94048..dcaa0c5171 100644 --- a/drivers/virtualbox/virtualbox.go +++ b/drivers/virtualbox/virtualbox.go @@ -423,7 +423,7 @@ func (d *Driver) Remove() error { return err } if s == state.Running { - if err := d.Kill(); err != nil { + if err := d.Stop(); err != nil { return err } }