diff --git a/cmd/minikube/cmd/env.go b/cmd/minikube/cmd/docker-env.go similarity index 55% rename from cmd/minikube/cmd/env.go rename to cmd/minikube/cmd/docker-env.go index 030aa410cb91..ccea698d3e3f 100644 --- a/cmd/minikube/cmd/env.go +++ b/cmd/minikube/cmd/docker-env.go @@ -26,10 +26,8 @@ import ( "os" "strconv" "strings" - "text/template" "github.com/docker/machine/libmachine/drivers" - "github.com/docker/machine/libmachine/shell" "github.com/docker/machine/libmachine/state" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -44,71 +42,25 @@ import ( "k8s.io/minikube/pkg/minikube/localpath" "k8s.io/minikube/pkg/minikube/machine" "k8s.io/minikube/pkg/minikube/out" + "k8s.io/minikube/pkg/minikube/shell" ) -var envTmpl = fmt.Sprintf("{{ .Prefix }}%s{{ .Delimiter }}{{ .DockerTLSVerify }}{{ .Suffix }}{{ .Prefix }}%s{{ .Delimiter }}{{ .DockerHost }}{{ .Suffix }}{{ .Prefix }}%s{{ .Delimiter }}{{ .DockerCertPath }}{{ .Suffix }}{{ .Prefix }}%s{{ .Delimiter }}{{ .MinikubeDockerdProfile }}{{ .Suffix }}{{ if .NoProxyVar }}{{ .Prefix }}{{ .NoProxyVar }}{{ .Delimiter }}{{ .NoProxyValue }}{{ .Suffix }}{{end}}{{ .UsageHint }}", constants.DockerTLSVerifyEnv, constants.DockerHostEnv, constants.DockerCertPathEnv, constants.MinikubeActiveDockerdEnv) +var dockerEnvTmpl = fmt.Sprintf("{{ .Prefix }}%s{{ .Delimiter }}{{ .DockerTLSVerify }}{{ .Suffix }}{{ .Prefix }}%s{{ .Delimiter }}{{ .DockerHost }}{{ .Suffix }}{{ .Prefix }}%s{{ .Delimiter }}{{ .DockerCertPath }}{{ .Suffix }}{{ .Prefix }}%s{{ .Delimiter }}{{ .MinikubeDockerdProfile }}{{ .Suffix }}{{ if .NoProxyVar }}{{ .Prefix }}{{ .NoProxyVar }}{{ .Delimiter }}{{ .NoProxyValue }}{{ .Suffix }}{{end}}{{ .UsageHint }}", constants.DockerTLSVerifyEnv, constants.DockerHostEnv, constants.DockerCertPathEnv, constants.MinikubeActiveDockerdEnv) -const ( - fishSetPfx = "set -gx " - fishSetSfx = "\"\n" - fishSetDelim = " \"" - - fishUnsetPfx = "set -e " - fishUnsetSfx = "\n" - - psSetPfx = "$Env:" - psSetSfx = "\"\n" - psSetDelim = " = \"" - - psUnsetPfx = `Remove-Item Env:\\` - psUnsetSfx = "\n" - - cmdSetPfx = "SET " - cmdSetSfx = "\n" - cmdSetDelim = "=" - - cmdUnsetPfx = "SET " - cmdUnsetSfx = "\n" - cmdUnsetDelim = "=" - - emacsSetPfx = "(setenv \"" - emacsSetSfx = "\")\n" - emacsSetDelim = "\" \"" - - emacsUnsetPfx = "(setenv \"" - emacsUnsetSfx = ")\n" - emacsUnsetDelim = "\" nil" - - bashSetPfx = "export " - bashSetSfx = "\"\n" - bashSetDelim = "=\"" - - bashUnsetPfx = "unset " - bashUnsetSfx = "\n" - - nonePfx = "" - noneSfx = "\n" - noneDelim = "=" -) - -// ShellConfig represents the shell config -type ShellConfig struct { - Prefix string - Delimiter string - Suffix string +// DockerShellConfig represents the shell config for Docker +type DockerShellConfig struct { + shell.Config DockerCertPath string DockerHost string DockerTLSVerify string MinikubeDockerdProfile string - UsageHint string NoProxyVar string NoProxyValue string } var ( noProxy bool - forceShell string - unset bool + dockerUnset bool defaultNoProxyGetter NoProxyGetter ) @@ -120,45 +72,18 @@ type NoProxyGetter interface { // EnvNoProxyGetter gets the no_proxy variable, using environment type EnvNoProxyGetter struct{} -func generateUsageHint(profile, sh string) string { +// dockerShellCfgSet generates context variables for "docker-env" +func dockerShellCfgSet(ec DockerEnvConfig, envMap map[string]string) *DockerShellConfig { + profile := ec.profile const usgPlz = "To point your shell to minikube's docker-daemon, run:" var usgCmd = fmt.Sprintf("minikube -p %s docker-env", profile) - var usageHintMap = map[string]string{ - "bash": fmt.Sprintf(` -# %s -# eval $(%s) -`, usgPlz, usgCmd), - "fish": fmt.Sprintf(` -# %s -# eval (%s) -`, usgPlz, usgCmd), - "powershell": fmt.Sprintf(`# %s -# & %s | Invoke-Expression -`, usgPlz, usgCmd), - "cmd": fmt.Sprintf(`REM %s -REM @FOR /f "tokens=*" %%i IN ('%s') DO @%%i -`, usgPlz, usgCmd), - "emacs": fmt.Sprintf(`;; %s -;; (with-temp-buffer (shell-command "%s" (current-buffer)) (eval-buffer)) -`, usgPlz, usgCmd), - } - - hint, ok := usageHintMap[sh] - if !ok { - return usageHintMap["bash"] - } - return hint -} - -// shellCfgSet generates context variables for "docker-env" -func shellCfgSet(ec EnvConfig, envMap map[string]string) *ShellConfig { - s := &ShellConfig{ - DockerCertPath: envMap[constants.DockerCertPathEnv], - DockerHost: envMap[constants.DockerHostEnv], - DockerTLSVerify: envMap[constants.DockerTLSVerifyEnv], - MinikubeDockerdProfile: envMap[constants.MinikubeActiveDockerdEnv], - UsageHint: generateUsageHint(ec.profile, ec.shell), + s := &DockerShellConfig{ + Config: *shell.CfgSet(ec.EnvConfig, usgPlz, usgCmd), } + s.DockerCertPath = envMap[constants.DockerCertPathEnv] + s.DockerHost = envMap[constants.DockerHostEnv] + s.DockerTLSVerify = envMap[constants.DockerTLSVerifyEnv] + s.MinikubeDockerdProfile = envMap[constants.MinikubeActiveDockerdEnv] if ec.noProxy { noProxyVar, noProxyValue := defaultNoProxyGetter.GetNoProxyVar() @@ -177,33 +102,6 @@ func shellCfgSet(ec EnvConfig, envMap map[string]string) *ShellConfig { s.NoProxyValue = noProxyValue } - switch ec.shell { - case "fish": - s.Prefix = fishSetPfx - s.Suffix = fishSetSfx - s.Delimiter = fishSetDelim - case "powershell": - s.Prefix = psSetPfx - s.Suffix = psSetSfx - s.Delimiter = psSetDelim - case "cmd": - s.Prefix = cmdSetPfx - s.Suffix = cmdSetSfx - s.Delimiter = cmdSetDelim - case "emacs": - s.Prefix = emacsSetPfx - s.Suffix = emacsSetSfx - s.Delimiter = emacsSetDelim - case "none": - s.Prefix = nonePfx - s.Suffix = noneSfx - s.Delimiter = noneDelim - s.UsageHint = "" - default: - s.Prefix = bashSetPfx - s.Suffix = bashSetSfx - s.Delimiter = bashSetDelim - } return s } @@ -236,7 +134,7 @@ func isDockerActive(d drivers.Driver) (bool, error) { return err == nil && s == "active", nil } -// envCmd represents the docker-env command +// dockerEnvCmd represents the docker-env command var dockerEnvCmd = &cobra.Command{ Use: "docker-env", Short: "Sets up docker env variables; similar to '$(docker-machine env)'", @@ -282,57 +180,59 @@ var dockerEnvCmd = &cobra.Command{ exit.WithError("Error getting host IP", err) } - ec := EnvConfig{ - profile: profile, - driver: host.DriverName, - shell: forceShell, - hostIP: hostIP, - certsDir: localpath.MakeMiniPath("certs"), - noProxy: noProxy, + sh := shell.EnvConfig{ + Shell: shell.ForceShell, + } + ec := DockerEnvConfig{ + EnvConfig: sh, + profile: profile, + driver: host.DriverName, + hostIP: hostIP, + certsDir: localpath.MakeMiniPath("certs"), + noProxy: noProxy, } - if ec.shell == "" { - ec.shell, err = shell.Detect() + if ec.Shell == "" { + ec.Shell, err = shell.Detect() if err != nil { exit.WithError("Error detecting shell", err) } } - if unset { - if err := unsetScript(ec, os.Stdout); err != nil { + if dockerUnset { + if err := dockerUnsetScript(ec, os.Stdout); err != nil { exit.WithError("Error generating unset output", err) } return } - if err := setScript(ec, os.Stdout); err != nil { + if err := dockerSetScript(ec, os.Stdout); err != nil { exit.WithError("Error generating set output", err) } }, } -// EnvConfig encapsulates all external inputs into shell generation -type EnvConfig struct { +// DockerEnvConfig encapsulates all external inputs into shell generation for Docker +type DockerEnvConfig struct { + shell.EnvConfig profile string - shell string driver string hostIP string certsDir string noProxy bool } -// setScript writes out a shell-compatible 'docker-env' script -func setScript(ec EnvConfig, w io.Writer) error { - tmpl := template.Must(template.New("envConfig").Parse(envTmpl)) +// dockerSetScript writes out a shell-compatible 'docker-env' script +func dockerSetScript(ec DockerEnvConfig, w io.Writer) error { envVars, err := dockerEnvVars(ec) if err != nil { return err } - return tmpl.Execute(w, shellCfgSet(ec, envVars)) + return shell.SetScript(ec.EnvConfig, w, dockerEnvTmpl, dockerShellCfgSet(ec, envVars)) } -// setScript writes out a shell-compatible 'docker-env unset' script -func unsetScript(ec EnvConfig, w io.Writer) error { +// dockerSetScript writes out a shell-compatible 'docker-env unset' script +func dockerUnsetScript(ec DockerEnvConfig, w io.Writer) error { vars := []string{ constants.DockerTLSVerifyEnv, constants.DockerHostEnv, @@ -347,29 +247,7 @@ func unsetScript(ec EnvConfig, w io.Writer) error { } } - var sb strings.Builder - switch ec.shell { - case "fish": - for _, v := range vars { - sb.WriteString(fmt.Sprintf("%s%s%s", fishUnsetPfx, v, fishUnsetSfx)) - } - case "powershell": - sb.WriteString(fmt.Sprintf("%s%s%s", psUnsetPfx, strings.Join(vars, " Env:\\\\"), psUnsetSfx)) - case "cmd": - for _, v := range vars { - sb.WriteString(fmt.Sprintf("%s%s%s%s", cmdUnsetPfx, v, cmdUnsetDelim, cmdUnsetSfx)) - } - case "emacs": - for _, v := range vars { - sb.WriteString(fmt.Sprintf("%s%s%s%s", emacsUnsetPfx, v, emacsUnsetDelim, emacsUnsetSfx)) - } - case "none": - sb.WriteString(fmt.Sprintf("%s%s%s", nonePfx, strings.Join(vars, " "), noneSfx)) - default: - sb.WriteString(fmt.Sprintf("%s%s%s", bashUnsetPfx, strings.Join(vars, " "), bashUnsetSfx)) - } - _, err := w.Write([]byte(sb.String())) - return err + return shell.UnsetScript(ec.EnvConfig, w, vars) } // dockerURL returns a the docker endpoint URL for an ip/port pair. @@ -378,7 +256,7 @@ func dockerURL(ip string, port int) string { } // dockerEnvVars gets the necessary docker env variables to allow the use of minikube's docker daemon -func dockerEnvVars(ec EnvConfig) (map[string]string, error) { +func dockerEnvVars(ec DockerEnvConfig) (map[string]string, error) { env := map[string]string{ constants.DockerTLSVerifyEnv: "1", constants.DockerHostEnv: dockerURL(ec.hostIP, constants.DockerDaemonPort), @@ -399,6 +277,6 @@ func dockerEnvVars(ec EnvConfig) (map[string]string, error) { func init() { defaultNoProxyGetter = &EnvNoProxyGetter{} 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") + dockerEnvCmd.Flags().StringVar(&shell.ForceShell, "shell", "", "Force environment to be configured for a specified shell: [fish, cmd, powershell, tcsh, bash, zsh], default is auto-detect") + dockerEnvCmd.Flags().BoolVarP(&dockerUnset, "unset", "u", false, "Unset variables instead of setting them") } diff --git a/cmd/minikube/cmd/env_test.go b/cmd/minikube/cmd/docker-env_test.go similarity index 79% rename from cmd/minikube/cmd/env_test.go rename to cmd/minikube/cmd/docker-env_test.go index 72a0018ac9b4..0e57057c81a6 100644 --- a/cmd/minikube/cmd/env_test.go +++ b/cmd/minikube/cmd/docker-env_test.go @@ -32,15 +32,17 @@ func (f FakeNoProxyGetter) GetNoProxyVar() (string, string) { return f.NoProxyVar, f.NoProxyValue } -func TestGenerateScripts(t *testing.T) { +func TestGenerateDockerScripts(t *testing.T) { var tests = []struct { - config EnvConfig + shell string + config DockerEnvConfig noProxyGetter *FakeNoProxyGetter wantSet string wantUnset string }{ { - EnvConfig{profile: "bash", shell: "bash", driver: "kvm2", hostIP: "127.0.0.1", certsDir: "/certs"}, + "bash", + DockerEnvConfig{profile: "bash", driver: "kvm2", hostIP: "127.0.0.1", certsDir: "/certs"}, nil, `export DOCKER_TLS_VERIFY="1" export DOCKER_HOST="tcp://127.0.0.1:2376" @@ -54,7 +56,8 @@ export MINIKUBE_ACTIVE_DOCKERD="bash" `, }, { - EnvConfig{profile: "ipv6", shell: "bash", driver: "kvm2", hostIP: "fe80::215:5dff:fe00:a903", certsDir: "/certs"}, + "bash", + DockerEnvConfig{profile: "ipv6", driver: "kvm2", hostIP: "fe80::215:5dff:fe00:a903", certsDir: "/certs"}, nil, `export DOCKER_TLS_VERIFY="1" export DOCKER_HOST="tcp://[fe80::215:5dff:fe00:a903]:2376" @@ -68,7 +71,8 @@ export MINIKUBE_ACTIVE_DOCKERD="ipv6" `, }, { - EnvConfig{profile: "fish", shell: "fish", driver: "kvm2", hostIP: "127.0.0.1", certsDir: "/certs"}, + "fish", + DockerEnvConfig{profile: "fish", driver: "kvm2", hostIP: "127.0.0.1", certsDir: "/certs"}, nil, `set -gx DOCKER_TLS_VERIFY "1" set -gx DOCKER_HOST "tcp://127.0.0.1:2376" @@ -85,7 +89,8 @@ set -e MINIKUBE_ACTIVE_DOCKERD `, }, { - EnvConfig{profile: "powershell", shell: "powershell", driver: "hyperv", hostIP: "192.168.0.1", certsDir: "/certs"}, + "powershell", + DockerEnvConfig{profile: "powershell", driver: "hyperv", hostIP: "192.168.0.1", certsDir: "/certs"}, nil, `$Env:DOCKER_TLS_VERIFY = "1" $Env:DOCKER_HOST = "tcp://192.168.0.1:2376" @@ -99,7 +104,8 @@ $Env:MINIKUBE_ACTIVE_DOCKERD = "powershell" `, }, { - EnvConfig{profile: "cmd", shell: "cmd", driver: "hyperv", hostIP: "192.168.0.1", certsDir: "/certs"}, + "cmd", + DockerEnvConfig{profile: "cmd", driver: "hyperv", hostIP: "192.168.0.1", certsDir: "/certs"}, nil, `SET DOCKER_TLS_VERIFY=1 SET DOCKER_HOST=tcp://192.168.0.1:2376 @@ -116,7 +122,8 @@ SET MINIKUBE_ACTIVE_DOCKERD= `, }, { - EnvConfig{profile: "emacs", shell: "emacs", driver: "hyperv", hostIP: "192.168.0.1", certsDir: "/certs"}, + "emacs", + DockerEnvConfig{profile: "emacs", driver: "hyperv", hostIP: "192.168.0.1", certsDir: "/certs"}, nil, `(setenv "DOCKER_TLS_VERIFY" "1") (setenv "DOCKER_HOST" "tcp://192.168.0.1:2376") @@ -132,7 +139,8 @@ SET MINIKUBE_ACTIVE_DOCKERD= `, }, { - EnvConfig{profile: "bash-no-proxy", shell: "bash", driver: "kvm2", hostIP: "127.0.0.1", certsDir: "/certs", noProxy: true}, + "bash", + DockerEnvConfig{profile: "bash-no-proxy", driver: "kvm2", hostIP: "127.0.0.1", certsDir: "/certs", noProxy: true}, &FakeNoProxyGetter{"NO_PROXY", "127.0.0.1"}, `export DOCKER_TLS_VERIFY="1" export DOCKER_HOST="tcp://127.0.0.1:2376" @@ -148,7 +156,8 @@ export NO_PROXY="127.0.0.1" `, }, { - EnvConfig{profile: "bash-no-proxy-lower", shell: "bash", driver: "kvm2", hostIP: "127.0.0.1", certsDir: "/certs", noProxy: true}, + "bash", + DockerEnvConfig{profile: "bash-no-proxy-lower", driver: "kvm2", hostIP: "127.0.0.1", certsDir: "/certs", noProxy: true}, &FakeNoProxyGetter{"no_proxy", "127.0.0.1"}, `export DOCKER_TLS_VERIFY="1" export DOCKER_HOST="tcp://127.0.0.1:2376" @@ -164,7 +173,8 @@ export no_proxy="127.0.0.1" `, }, { - EnvConfig{profile: "powershell-no-proxy-idempotent", shell: "powershell", driver: "hyperv", hostIP: "192.168.0.1", certsDir: "/certs", noProxy: true}, + "powershell", + DockerEnvConfig{profile: "powershell-no-proxy-idempotent", driver: "hyperv", hostIP: "192.168.0.1", certsDir: "/certs", noProxy: true}, &FakeNoProxyGetter{"no_proxy", "192.168.0.1"}, `$Env:DOCKER_TLS_VERIFY = "1" $Env:DOCKER_HOST = "tcp://192.168.0.1:2376" @@ -179,7 +189,8 @@ $Env:no_proxy = "192.168.0.1" `, }, { - EnvConfig{profile: "sh-no-proxy-add", shell: "bash", driver: "kvm2", hostIP: "127.0.0.1", certsDir: "/certs", noProxy: true}, + "bash", + DockerEnvConfig{profile: "sh-no-proxy-add", driver: "kvm2", hostIP: "127.0.0.1", certsDir: "/certs", noProxy: true}, &FakeNoProxyGetter{"NO_PROXY", "192.168.0.1,10.0.0.4"}, `export DOCKER_TLS_VERIFY="1" export DOCKER_HOST="tcp://127.0.0.1:2376" @@ -197,10 +208,11 @@ export NO_PROXY="192.168.0.1,10.0.0.4,127.0.0.1" } for _, tc := range tests { t.Run(tc.config.profile, func(t *testing.T) { + tc.config.EnvConfig.Shell = tc.shell defaultNoProxyGetter = tc.noProxyGetter var b []byte buf := bytes.NewBuffer(b) - if err := setScript(tc.config, buf); err != nil { + if err := dockerSetScript(tc.config, buf); err != nil { t.Errorf("setScript(%+v) error: %v", tc.config, err) } got := buf.String() @@ -209,7 +221,7 @@ export NO_PROXY="192.168.0.1,10.0.0.4,127.0.0.1" } buf = bytes.NewBuffer(b) - if err := unsetScript(tc.config, buf); err != nil { + if err := dockerUnsetScript(tc.config, buf); err != nil { t.Errorf("unsetScript(%+v) error: %v", tc.config, err) } got = buf.String() diff --git a/cmd/minikube/cmd/podman-env.go b/cmd/minikube/cmd/podman-env.go new file mode 100644 index 000000000000..5c4f6e06af66 --- /dev/null +++ b/cmd/minikube/cmd/podman-env.go @@ -0,0 +1,226 @@ +/* +Copyright 2020 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +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" + "io" + "os" + "os/exec" + "strings" + + "github.com/docker/machine/libmachine/drivers" + "github.com/docker/machine/libmachine/host" + "github.com/docker/machine/libmachine/ssh" + "github.com/docker/machine/libmachine/state" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "k8s.io/minikube/pkg/minikube/cluster" + "k8s.io/minikube/pkg/minikube/config" + "k8s.io/minikube/pkg/minikube/constants" + "k8s.io/minikube/pkg/minikube/driver" + "k8s.io/minikube/pkg/minikube/exit" + "k8s.io/minikube/pkg/minikube/machine" + "k8s.io/minikube/pkg/minikube/out" + "k8s.io/minikube/pkg/minikube/shell" +) + +var podmanEnvTmpl = fmt.Sprintf("{{ .Prefix }}%s{{ .Delimiter }}{{ .VarlinkBridge }}{{ .Suffix }}{{ .UsageHint }}", constants.PodmanVarlinkBridgeEnv) + +// PodmanShellConfig represents the shell config for Podman +type PodmanShellConfig struct { + shell.Config + VarlinkBridge string +} + +var ( + podmanUnset bool +) + +// podmanShellCfgSet generates context variables for "podman-env" +func podmanShellCfgSet(ec PodmanEnvConfig, envMap map[string]string) *PodmanShellConfig { + profile := ec.profile + const usgPlz = "To point your shell to minikube's podman service, run:" + var usgCmd = fmt.Sprintf("minikube -p %s podman-env", profile) + s := &PodmanShellConfig{ + Config: *shell.CfgSet(ec.EnvConfig, usgPlz, usgCmd), + } + s.VarlinkBridge = envMap[constants.PodmanVarlinkBridgeEnv] + + return s +} + +// isPodmanAvailable checks if Podman is available +func isPodmanAvailable(host *host.Host) (bool, error) { + // we need both "varlink bridge" and "podman varlink" + if _, err := host.RunSSHCommand("which varlink"); err != nil { + return false, err + } + if _, err := host.RunSSHCommand("which podman"); err != nil { + return false, err + } + return true, nil +} + +func createExternalSSHClient(d drivers.Driver) (*ssh.ExternalClient, error) { + sshBinaryPath, err := exec.LookPath("ssh") + if err != nil { + return &ssh.ExternalClient{}, err + } + + addr, err := d.GetSSHHostname() + if err != nil { + return &ssh.ExternalClient{}, err + } + + port, err := d.GetSSHPort() + if err != nil { + return &ssh.ExternalClient{}, err + } + + auth := &ssh.Auth{} + if d.GetSSHKeyPath() != "" { + auth.Keys = []string{d.GetSSHKeyPath()} + } + + return ssh.NewExternalClient(sshBinaryPath, d.GetSSHUsername(), addr, port, auth) +} + +// podmanEnvCmd represents the podman-env command +var podmanEnvCmd = &cobra.Command{ + Use: "podman-env", + Short: "Sets up podman env variables; similar to '$(podman-machine env)'", + Long: `Sets up podman env variables; similar to '$(podman-machine env)'.`, + Run: func(cmd *cobra.Command, args []string) { + api, err := machine.NewAPIClient() + if err != nil { + exit.WithError("Error getting client", err) + } + defer api.Close() + + profile := viper.GetString(config.MachineProfile) + cc, err := config.Load(profile) + if err != nil { + exit.WithError("Error getting config", err) + } + host, err := cluster.CheckIfHostExistsAndLoad(api, cc.Name) + if err != nil { + exit.WithError("Error getting host", err) + } + if host.Driver.DriverName() == driver.None { + exit.UsageT(`'none' driver does not support 'minikube podman-env' command`) + } + + hostSt, err := cluster.GetHostStatus(api, cc.Name) + if err != nil { + exit.WithError("Error getting host status", err) + } + if hostSt != state.Running.String() { + exit.WithCodeT(exit.Unavailable, `'{{.profile}}' is not running`, out.V{"profile": profile}) + } + ok, err := isPodmanAvailable(host) + if err != nil { + exit.WithError("Error getting service status", err) + } + + if !ok { + exit.WithCodeT(exit.Unavailable, `The podman service within '{{.profile}}' is not active`, out.V{"profile": profile}) + } + + client, err := createExternalSSHClient(host.Driver) + if err != nil { + exit.WithError("Error getting ssh client", err) + } + + sh := shell.EnvConfig{ + Shell: shell.ForceShell, + } + ec := PodmanEnvConfig{ + EnvConfig: sh, + profile: profile, + driver: host.DriverName, + client: client, + } + + if ec.Shell == "" { + ec.Shell, err = shell.Detect() + if err != nil { + exit.WithError("Error detecting shell", err) + } + } + + if podmanUnset { + if err := podmanUnsetScript(ec, os.Stdout); err != nil { + exit.WithError("Error generating unset output", err) + } + return + } + + if err := podmanSetScript(ec, os.Stdout); err != nil { + exit.WithError("Error generating set output", err) + } + }, +} + +// PodmanEnvConfig encapsulates all external inputs into shell generation for Podman +type PodmanEnvConfig struct { + shell.EnvConfig + profile string + driver string + client *ssh.ExternalClient +} + +// podmanSetScript writes out a shell-compatible 'podman-env' script +func podmanSetScript(ec PodmanEnvConfig, w io.Writer) error { + envVars, err := podmanEnvVars(ec) + if err != nil { + return err + } + return shell.SetScript(ec.EnvConfig, w, podmanEnvTmpl, podmanShellCfgSet(ec, envVars)) +} + +// podmanUnsetScript writes out a shell-compatible 'podman-env unset' script +func podmanUnsetScript(ec PodmanEnvConfig, w io.Writer) error { + vars := []string{ + constants.PodmanVarlinkBridgeEnv, + } + return shell.UnsetScript(ec.EnvConfig, w, vars) +} + +// podmanBridge returns the command to use in a var for accessing the podman varlink bridge over ssh +func podmanBridge(client *ssh.ExternalClient) string { + command := []string{client.BinaryPath} + command = append(command, client.BaseArgs...) + command = append(command, "--", "sudo", "varlink", "-A", `\'podman varlink \\\$VARLINK_ADDRESS\'`, "bridge") + return strings.Join(command, " ") +} + +// podmanEnvVars gets the necessary podman env variables to allow the use of minikube's podman service +func podmanEnvVars(ec PodmanEnvConfig) (map[string]string, error) { // nolint result 1 (error) is always nil + env := map[string]string{ + constants.PodmanVarlinkBridgeEnv: podmanBridge(ec.client), + } + return env, nil +} + +func init() { + podmanEnvCmd.Flags().StringVar(&shell.ForceShell, "shell", "", "Force environment to be configured for a specified shell: [fish, cmd, powershell, tcsh, bash, zsh], default is auto-detect") + podmanEnvCmd.Flags().BoolVarP(&podmanUnset, "unset", "u", false, "Unset variables instead of setting them") +} diff --git a/cmd/minikube/cmd/podman-env_test.go b/cmd/minikube/cmd/podman-env_test.go new file mode 100644 index 000000000000..2e090541fb77 --- /dev/null +++ b/cmd/minikube/cmd/podman-env_test.go @@ -0,0 +1,80 @@ +/* +Copyright 2020 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cmd + +import ( + "bytes" + "testing" + + "github.com/docker/machine/libmachine/ssh" + "github.com/google/go-cmp/cmp" +) + +func newFakeClient() *ssh.ExternalClient { + return &ssh.ExternalClient{ + BaseArgs: []string{"root@host"}, + BinaryPath: "/usr/bin/ssh", + } +} + +func TestGeneratePodmanScripts(t *testing.T) { + var tests = []struct { + shell string + config PodmanEnvConfig + noProxyGetter *FakeNoProxyGetter + wantSet string + wantUnset string + }{ + { + "bash", + PodmanEnvConfig{profile: "bash", driver: "kvm2", client: newFakeClient()}, + nil, + `export PODMAN_VARLINK_BRIDGE="/usr/bin/ssh root@host -- sudo varlink -A \'podman varlink \\\$VARLINK_ADDRESS\' bridge" + +# To point your shell to minikube's podman service, run: +# eval $(minikube -p bash podman-env) +`, + `unset PODMAN_VARLINK_BRIDGE +`, + }, + } + for _, tc := range tests { + t.Run(tc.config.profile, func(t *testing.T) { + tc.config.EnvConfig.Shell = tc.shell + defaultNoProxyGetter = tc.noProxyGetter + var b []byte + buf := bytes.NewBuffer(b) + if err := podmanSetScript(tc.config, buf); err != nil { + t.Errorf("setScript(%+v) error: %v", tc.config, err) + } + got := buf.String() + if diff := cmp.Diff(tc.wantSet, got); diff != "" { + t.Errorf("setScript(%+v) mismatch (-want +got):\n%s\n\nraw output:\n%s\nquoted: %q", tc.config, diff, got, got) + } + + buf = bytes.NewBuffer(b) + if err := podmanUnsetScript(tc.config, buf); err != nil { + t.Errorf("unsetScript(%+v) error: %v", tc.config, err) + } + got = buf.String() + if diff := cmp.Diff(tc.wantUnset, got); diff != "" { + t.Errorf("unsetScript(%+v) mismatch (-want +got):\n%s\n\nraw output:\n%s\nquoted: %q", tc.config, diff, got, got) + } + + }) + } +} diff --git a/cmd/minikube/cmd/root.go b/cmd/minikube/cmd/root.go index ef6fb3e29e1c..a83a2a446d84 100644 --- a/cmd/minikube/cmd/root.go +++ b/cmd/minikube/cmd/root.go @@ -180,6 +180,7 @@ func init() { Message: translate.T("Images Commands:"), Commands: []*cobra.Command{ dockerEnvCmd, + podmanEnvCmd, cacheCmd, }, }, diff --git a/pkg/minikube/constants/constants.go b/pkg/minikube/constants/constants.go index 72349d31c9f7..355c9319a06d 100644 --- a/pkg/minikube/constants/constants.go +++ b/pkg/minikube/constants/constants.go @@ -50,6 +50,8 @@ const ( // DockerDaemonEnvs has list of environment variables to control docker daemon shell is using MinikubeActiveDockerdEnv = "MINIKUBE_ACTIVE_DOCKERD" + // PodmanVarlinkBridgeEnv is used for podman settings + PodmanVarlinkBridgeEnv = "PODMAN_VARLINK_BRIDGE" ) var DockerDaemonEnvs = [3]string{DockerHostEnv, DockerTLSVerifyEnv, DockerCertPathEnv} diff --git a/pkg/minikube/shell/shell.go b/pkg/minikube/shell/shell.go new file mode 100644 index 000000000000..9e61a03e905a --- /dev/null +++ b/pkg/minikube/shell/shell.go @@ -0,0 +1,192 @@ +/* +Copyright 2020 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +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 shell + +import ( + "fmt" + "io" + "strings" + "text/template" + + "github.com/docker/machine/libmachine/shell" +) + +const ( + fishSetPfx = "set -gx " + fishSetSfx = "\"\n" + fishSetDelim = " \"" + + fishUnsetPfx = "set -e " + fishUnsetSfx = "\n" + + psSetPfx = "$Env:" + psSetSfx = "\"\n" + psSetDelim = " = \"" + + psUnsetPfx = `Remove-Item Env:\\` + psUnsetSfx = "\n" + + cmdSetPfx = "SET " + cmdSetSfx = "\n" + cmdSetDelim = "=" + + cmdUnsetPfx = "SET " + cmdUnsetSfx = "\n" + cmdUnsetDelim = "=" + + emacsSetPfx = "(setenv \"" + emacsSetSfx = "\")\n" + emacsSetDelim = "\" \"" + + emacsUnsetPfx = "(setenv \"" + emacsUnsetSfx = ")\n" + emacsUnsetDelim = "\" nil" + + bashSetPfx = "export " + bashSetSfx = "\"\n" + bashSetDelim = "=\"" + + bashUnsetPfx = "unset " + bashUnsetSfx = "\n" + + nonePfx = "" + noneSfx = "\n" + noneDelim = "=" +) + +// Config represents the shell config +type Config struct { + Prefix string + Delimiter string + Suffix string + UsageHint string +} + +var ( + // ForceShell forces a shell name + ForceShell string +) + +// Detect detects user's current shell. +func Detect() (string, error) { + return shell.Detect() +} + +func generateUsageHint(sh, usgPlz, usgCmd string) string { + var usageHintMap = map[string]string{ + "bash": fmt.Sprintf(` +# %s +# eval $(%s) +`, usgPlz, usgCmd), + "fish": fmt.Sprintf(` +# %s +# eval (%s) +`, usgPlz, usgCmd), + "powershell": fmt.Sprintf(`# %s +# & %s | Invoke-Expression +`, usgPlz, usgCmd), + "cmd": fmt.Sprintf(`REM %s +REM @FOR /f "tokens=*" %%i IN ('%s') DO @%%i +`, usgPlz, usgCmd), + "emacs": fmt.Sprintf(`;; %s +;; (with-temp-buffer (shell-command "%s" (current-buffer)) (eval-buffer)) +`, usgPlz, usgCmd), + } + + hint, ok := usageHintMap[sh] + if !ok { + return usageHintMap["bash"] + } + return hint +} + +// CfgSet generates context variables for shell +func CfgSet(ec EnvConfig, plz, cmd string) *Config { + s := &Config{ + UsageHint: generateUsageHint(ec.Shell, plz, cmd), + } + + switch ec.Shell { + case "fish": + s.Prefix = fishSetPfx + s.Suffix = fishSetSfx + s.Delimiter = fishSetDelim + case "powershell": + s.Prefix = psSetPfx + s.Suffix = psSetSfx + s.Delimiter = psSetDelim + case "cmd": + s.Prefix = cmdSetPfx + s.Suffix = cmdSetSfx + s.Delimiter = cmdSetDelim + case "emacs": + s.Prefix = emacsSetPfx + s.Suffix = emacsSetSfx + s.Delimiter = emacsSetDelim + case "none": + s.Prefix = nonePfx + s.Suffix = noneSfx + s.Delimiter = noneDelim + s.UsageHint = "" + default: + s.Prefix = bashSetPfx + s.Suffix = bashSetSfx + s.Delimiter = bashSetDelim + } + return s +} + +// EnvConfig encapsulates all external inputs into shell generation +type EnvConfig struct { + Shell string +} + +// SetScript writes out a shell-compatible set script +func SetScript(ec EnvConfig, w io.Writer, envTmpl string, data interface{}) error { + tmpl := template.Must(template.New("envConfig").Parse(envTmpl)) + return tmpl.Execute(w, data) +} + +// UnsetScript writes out a shell-compatible unset script +func UnsetScript(ec EnvConfig, w io.Writer, vars []string) error { + var sb strings.Builder + switch ec.Shell { + case "fish": + for _, v := range vars { + sb.WriteString(fmt.Sprintf("%s%s%s", fishUnsetPfx, v, fishUnsetSfx)) + } + case "powershell": + sb.WriteString(fmt.Sprintf("%s%s%s", psUnsetPfx, strings.Join(vars, " Env:\\\\"), psUnsetSfx)) + case "cmd": + for _, v := range vars { + sb.WriteString(fmt.Sprintf("%s%s%s%s", cmdUnsetPfx, v, cmdUnsetDelim, cmdUnsetSfx)) + } + case "emacs": + for _, v := range vars { + sb.WriteString(fmt.Sprintf("%s%s%s%s", emacsUnsetPfx, v, emacsUnsetDelim, emacsUnsetSfx)) + } + case "none": + sb.WriteString(fmt.Sprintf("%s%s%s", nonePfx, strings.Join(vars, " "), noneSfx)) + default: + sb.WriteString(fmt.Sprintf("%s%s%s", bashUnsetPfx, strings.Join(vars, " "), bashUnsetSfx)) + } + _, err := w.Write([]byte(sb.String())) + return err +} diff --git a/site/content/en/docs/Tasks/podman_service.md b/site/content/en/docs/Tasks/podman_service.md new file mode 100644 index 000000000000..87a5e1b678a4 --- /dev/null +++ b/site/content/en/docs/Tasks/podman_service.md @@ -0,0 +1,34 @@ +--- +title: "Using the Podman service" +linkTitle: "Using the Podman service" +weight: 6 +date: 2020-01-20 +description: > + How to access the Podman service within minikube +--- + +## Prerequisites + +You should be using minikube with the container runtime set to CRI-O. It uses the same storage as Podman. + +## Method 1: Without minikube registry addon + +When using a single VM of Kubernetes it's really handy to reuse the Podman service inside the VM; as this means you don't have to build on your host machine and push the image into a container registry - you can just build inside the same container storage as minikube which speeds up local experiments. + +To be able to work with the podman client on your mac/linux host use the podman-env command in your shell: + +```shell +eval $(minikube podman-env) +``` + +You should now be able to use podman on the command line on your host mac/linux machine talking to the podman service inside the minikube VM: + +```shell +podman-remote help +``` + +Remember to turn off the _imagePullPolicy:Always_, as otherwise Kubernetes won't use images you built locally. + +## Related Documentation + +- [docker_registry.md](Using the Docker registry)