Add podman-env for connecting with podman-remote (#6351)

* Refactor: add the docker vars after the shell vars

* Move shell handling functions to separate package

* Rename env source file to docker-env like command

* Add podman-env for connecting with podman-remote

* Remove leading Shell prefix from the shell package

* Add some documentation describing podman service

* Address some podman-env issues pointed out by lint
pull/6582/head
Anders Björklund 2020-02-11 06:59:37 +01:00 committed by GitHub
parent a283fd695a
commit ff763db1e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 604 additions and 179 deletions

View File

@ -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")
}

View File

@ -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()

View File

@ -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")
}

View File

@ -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)
}
})
}
}

View File

@ -180,6 +180,7 @@ func init() {
Message: translate.T("Images Commands:"),
Commands: []*cobra.Command{
dockerEnvCmd,
podmanEnvCmd,
cacheCmd,
},
},

View File

@ -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}

192
pkg/minikube/shell/shell.go Normal file
View File

@ -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
}

View File

@ -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)