diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index cdb1139df68550b79dfee81ee8607899b8ff417d..f10b16f9e2f00a5589fc9858fb606e30289536a7 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -815,6 +815,11 @@ "Comment": "v0.7.0-62-g6002b41", "Rev": "6002b411ce820eaf03ac972a7fb354bb56f7aa95" }, + { + "ImportPath": "github.com/docker/machine/libmachine/shell", + "Comment": "v0.7.0-62-g6002b41", + "Rev": "6002b411ce820eaf03ac972a7fb354bb56f7aa95" + }, { "ImportPath": "github.com/docker/machine/libmachine/ssh", "Comment": "v0.7.0-62-g6002b41", diff --git a/cmd/minikube/cmd/env.go b/cmd/minikube/cmd/env.go index a6584a9ae9bc3bb273f7d38438ff1d57fca01058..249892b02c4a72186a9c9cc4f420953b3e11ddae 100644 --- a/cmd/minikube/cmd/env.go +++ b/cmd/minikube/cmd/env.go @@ -14,47 +14,251 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Part of this code is heavily inspired/copied by the following file: +// github.com/docker/machine/commands/env.go + package cmd import ( "fmt" "os" + "strings" + "text/template" "github.com/docker/machine/libmachine" + "github.com/docker/machine/libmachine/shell" "github.com/golang/glog" "github.com/spf13/cobra" "k8s.io/minikube/pkg/minikube/cluster" "k8s.io/minikube/pkg/minikube/constants" ) +const ( + envTmpl = `{{ .Prefix }}DOCKER_TLS_VERIFY{{ .Delimiter }}{{ .DockerTLSVerify }}{{ .Suffix }}{{ .Prefix }}DOCKER_HOST{{ .Delimiter }}{{ .DockerHost }}{{ .Suffix }}{{ .Prefix }}DOCKER_CERT_PATH{{ .Delimiter }}{{ .DockerCertPath }}{{ .Suffix }}{{ if .NoProxyVar }}{{ .Prefix }}{{ .NoProxyVar }}{{ .Delimiter }}{{ .NoProxyValue }}{{ .Suffix }}{{end}}{{ .UsageHint }}` +) + +type ShellConfig struct { + Prefix string + Delimiter string + Suffix string + DockerCertPath string + DockerHost string + DockerTLSVerify string + UsageHint string + NoProxyVar string + NoProxyValue string +} + +var ( + noProxy bool + forceShell string + unset bool +) + +func generateUsageHint(userShell string) string { + + cmd := "" + comment := "#" + commandLine := "minikube docker-env" + + switch userShell { + case "fish": + cmd = fmt.Sprintf("eval (%s)", commandLine) + case "powershell": + cmd = fmt.Sprintf("& %s | Invoke-Expression", commandLine) + case "cmd": + cmd = fmt.Sprintf("\t@FOR /f \"tokens=*\" %%i IN ('%s') DO @%%i", commandLine) + comment = "REM" + case "emacs": + cmd = fmt.Sprintf("(with-temp-buffer (shell-command \"%s\" (current-buffer)) (eval-buffer))", commandLine) + comment = ";;" + default: + cmd = fmt.Sprintf("eval $(%s)", commandLine) + } + + return fmt.Sprintf("%s Run this command to configure your shell: \n%s %s\n", comment, comment, cmd) +} + +func shellCfgSet(api libmachine.API) (*ShellConfig, error) { + + envMap, err := cluster.GetHostDockerEnv(api) + if err != nil { + return nil, err + } + + userShell, err := getShell(forceShell) + if err != nil { + return nil, err + } + + shellCfg := &ShellConfig{ + DockerCertPath: envMap["DOCKER_CERT_PATH"], + DockerHost: envMap["DOCKER_HOST"], + DockerTLSVerify: envMap["DOCKER_TLS_VERIFY"], + UsageHint: generateUsageHint(userShell), + } + + if noProxy { + + host, err := api.Load(constants.MachineName) + if err != nil { + return nil, fmt.Errorf("Error getting IP: ", err) + } + + ip, err := host.Driver.GetIP() + if err != nil { + return nil, fmt.Errorf("Error getting host IP: %s", err) + } + + noProxyVar, noProxyValue := findNoProxyFromEnv() + + // add the docker host to the no_proxy list idempotently + switch { + case noProxyValue == "": + noProxyValue = ip + case strings.Contains(noProxyValue, ip): + //ip already in no_proxy list, nothing to do + default: + noProxyValue = fmt.Sprintf("%s,%s", noProxyValue, ip) + } + + shellCfg.NoProxyVar = noProxyVar + shellCfg.NoProxyValue = noProxyValue + } + + switch userShell { + case "fish": + shellCfg.Prefix = "set -gx " + 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 = "=" + case "emacs": + shellCfg.Prefix = "(setenv \"" + shellCfg.Suffix = "\")\n" + shellCfg.Delimiter = "\" \"" + default: + shellCfg.Prefix = "export " + shellCfg.Suffix = "\"\n" + shellCfg.Delimiter = "=\"" + } + + return shellCfg, nil +} + +func shellCfgUnset(api libmachine.API) (*ShellConfig, error) { + + userShell, err := getShell(forceShell) + if err != nil { + return nil, err + } + + shellCfg := &ShellConfig{ + UsageHint: generateUsageHint(userShell), + } + + if noProxy { + shellCfg.NoProxyVar, shellCfg.NoProxyValue = findNoProxyFromEnv() + } + + switch userShell { + case "fish": + shellCfg.Prefix = "set -e " + shellCfg.Suffix = ";\n" + shellCfg.Delimiter = "" + case "powershell": + shellCfg.Prefix = `Remove-Item Env:\\` + shellCfg.Suffix = "\n" + shellCfg.Delimiter = "" + case "cmd": + shellCfg.Prefix = "SET " + shellCfg.Suffix = "\n" + shellCfg.Delimiter = "=" + case "emacs": + shellCfg.Prefix = "(setenv \"" + shellCfg.Suffix = ")\n" + shellCfg.Delimiter = "\" nil" + default: + shellCfg.Prefix = "unset " + shellCfg.Suffix = "\n" + shellCfg.Delimiter = "" + } + + return shellCfg, nil +} + +func executeTemplateStdout(shellCfg *ShellConfig) error { + t := template.New("envConfig") + tmpl, err := t.Parse(envTmpl) + if err != nil { + return err + } + + return tmpl.Execute(os.Stdout, shellCfg) +} + +func getShell(userShell string) (string, error) { + if userShell != "" { + return userShell, nil + } + return shell.Detect() +} + +func findNoProxyFromEnv() (string, string) { + // first check for an existing lower case no_proxy var + noProxyVar := "no_proxy" + noProxyValue := os.Getenv("no_proxy") + + // otherwise default to allcaps HTTP_PROXY + if noProxyValue == "" { + noProxyVar = "NO_PROXY" + noProxyValue = os.Getenv("NO_PROXY") + } + return noProxyVar, noProxyValue +} + // envCmd represents the docker-env command var dockerEnvCmd = &cobra.Command{ Use: "docker-env", Short: "sets up docker env variables; similar to '$(docker-machine env)'", Long: `sets up docker env variables; similar to '$(docker-machine env)'`, Run: func(cmd *cobra.Command, args []string) { + api := libmachine.NewClient(constants.Minipath, constants.MakeMiniPath("certs")) defer api.Close() - envMap, err := cluster.GetHostDockerEnv(api) - if err != nil { - glog.Errorln("Error setting machine env variable(s):", err) - os.Exit(1) + var ( + err error + shellCfg *ShellConfig + ) + + if unset { + shellCfg, err = shellCfgUnset(api) + if err != nil { + glog.Errorln("Error setting machine env variable(s):", err) + os.Exit(1) + } + } else { + shellCfg, err = shellCfgSet(api) + if err != nil { + glog.Errorln("Error setting machine env variable(s):", err) + os.Exit(1) + } } - fmt.Fprintln(os.Stdout, buildDockerEnvShellOutput(envMap)) - }, -} -func buildDockerEnvShellOutput(envMap map[string]string) string { - output := "" - for env_name, env_val := range envMap { - output += fmt.Sprintf("export %s=%s\n", env_name, env_val) - } - howToRun := "# Run this command to configure your shell: \n# eval $(minikube docker-env)" - output += howToRun - return output + executeTemplateStdout(shellCfg) + }, } func init() { RootCmd.AddCommand(dockerEnvCmd) + dockerEnvCmd.Flags().BoolVar(&noProxy, "no-proxy", false, "Add machine IP to NO_PROXY environment variable") + dockerEnvCmd.Flags().StringVar(&forceShell, "shell", "", "Force environment to be configured for a specified shell: [fish, cmd, powershell, tcsh, bash, zsh], default is auto-detect") + dockerEnvCmd.Flags().BoolVarP(&unset, "unset", "u", false, "Unset variables instead of setting them") } diff --git a/docs/minikube_docker-env.md b/docs/minikube_docker-env.md index 8abde6dbf3c42e933df5967e9c11cc4eb2b0e2fd..78ad5a64362b9975102823169f06afb0f8706a52 100644 --- a/docs/minikube_docker-env.md +++ b/docs/minikube_docker-env.md @@ -11,6 +11,14 @@ sets up docker env variables; similar to '$(docker-machine env)' minikube docker-env ``` +### Options + +``` + --no-proxy[=false]: Add machine IP to NO_PROXY environment variable + --shell="": Force environment to be configured for a specified shell: [fish, cmd, powershell, tcsh, bash, zsh], default is auto-detect + -u, --unset[=false]: Unset variables instead of setting them +``` + ### Options inherited from parent commands ``` diff --git a/vendor/github.com/docker/machine/libmachine/shell/shell.go b/vendor/github.com/docker/machine/libmachine/shell/shell.go new file mode 100644 index 0000000000000000000000000000000000000000..dd1579b5efd342907a02efa1dd1b16215212a0b8 --- /dev/null +++ b/vendor/github.com/docker/machine/libmachine/shell/shell.go @@ -0,0 +1,30 @@ +// +build !windows + +package shell + +import ( + "errors" + "fmt" + "os" + "path/filepath" +) + +var ( + ErrUnknownShell = errors.New("Error: Unknown shell") +) + +// Detect detects user's current shell. +func Detect() (string, error) { + shell := os.Getenv("SHELL") + + if shell == "" { + fmt.Printf("The default lines below are for a sh/bash shell, you can specify the shell you're using, with the --shell flag.\n\n") + return "", ErrUnknownShell + } + + if os.Getenv("__fish_bin_dir") != "" { + return "fish", nil + } + + return filepath.Base(shell), nil +} diff --git a/vendor/github.com/docker/machine/libmachine/shell/shell_windows.go b/vendor/github.com/docker/machine/libmachine/shell/shell_windows.go new file mode 100644 index 0000000000000000000000000000000000000000..89cd2c8b0a766804a66a46f28783ede2c9bb15f7 --- /dev/null +++ b/vendor/github.com/docker/machine/libmachine/shell/shell_windows.go @@ -0,0 +1,84 @@ +package shell + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "syscall" + "unsafe" +) + +// re-implementation of private function in https://github.com/golang/go/blob/master/src/syscall/syscall_windows.go#L945 +func getProcessEntry(pid int) (pe *syscall.ProcessEntry32, err error) { + snapshot, err := syscall.CreateToolhelp32Snapshot(syscall.TH32CS_SNAPPROCESS, 0) + if err != nil { + return nil, err + } + defer syscall.CloseHandle(syscall.Handle(snapshot)) + + var processEntry syscall.ProcessEntry32 + processEntry.Size = uint32(unsafe.Sizeof(processEntry)) + err = syscall.Process32First(snapshot, &processEntry) + if err != nil { + return nil, err + } + + for { + if processEntry.ProcessID == uint32(pid) { + pe = &processEntry + return + } + + err = syscall.Process32Next(snapshot, &processEntry) + if err != nil { + return nil, err + } + } +} + +// getNameAndItsPpid returns the exe file name its parent process id. +func getNameAndItsPpid(pid int) (exefile string, parentid int, err error) { + pe, err := getProcessEntry(pid) + if err != nil { + return "", 0, err + } + + name := syscall.UTF16ToString(pe.ExeFile[:]) + return name, int(pe.ParentProcessID), nil +} + +func Detect() (string, error) { + shell := os.Getenv("SHELL") + + if shell == "" { + shell, shellppid, err := getNameAndItsPpid(os.Getppid()) + if err != nil { + return "cmd", err // defaulting to cmd + } + if strings.Contains(strings.ToLower(shell), "powershell") { + return "powershell", nil + } else if strings.Contains(strings.ToLower(shell), "cmd") { + return "cmd", nil + } else { + shell, _, err := getNameAndItsPpid(shellppid) + if err != nil { + return "cmd", err // defaulting to cmd + } + if strings.Contains(strings.ToLower(shell), "powershell") { + return "powershell", nil + } else if strings.Contains(strings.ToLower(shell), "cmd") { + return "cmd", nil + } else { + fmt.Printf("You can further specify your shell with either 'cmd' or 'powershell' with the --shell flag.\n\n") + return "cmd", nil // this could be either powershell or cmd, defaulting to cmd + } + } + } + + if os.Getenv("__fish_bin_dir") != "" { + return "fish", nil + } + + return filepath.Base(shell), nil +}