diff --git a/commands/commands.go b/commands/commands.go index 6d4e5ac..e3e91f7 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -213,6 +213,10 @@ var Commands = []cli.Command{ Description: "Argument is a machine name.", Action: runCommand(cmdEnv), Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "varlink", + Usage: "Set varlink bridge, instead of podman variables", + }, cli.StringFlag{ Name: "shell", Usage: "Force environment to be configured for a specified shell: [fish, cmd, powershell, tcsh, emacs], default is auto-detect", diff --git a/commands/env.go b/commands/env.go index 9fe859c..4770477 100644 --- a/commands/env.go +++ b/commands/env.go @@ -14,7 +14,8 @@ import ( ) const ( - envTmpl = `{{ .Prefix }}PODMAN_USER{{ .Delimiter }}{{ .PodmanUser }}{{ .Suffix }}{{ .Prefix }}PODMAN_HOST{{ .Delimiter }}{{ .PodmanHost }}{{ .Suffix }}{{ .Prefix }}PODMAN_PORT{{ .Delimiter }}{{ .PodmanPort }}{{ .Suffix }}{{ .Prefix }}PODMAN_IDENTITY_FILE{{ .Delimiter }}{{ .IdentityFile }}{{ .Suffix }}{{ if .KnownHosts }}{{ .Prefix }}PODMAN_KNOWN_HOSTS{{ .Delimiter }}{{ .KnownHosts }}{{ .Suffix }}{{else}}{{ .Prefix }}PODMAN_IGNORE_HOSTS{{ .Delimiter }}true{{ .Suffix }}{{end}}{{ .Prefix }}PODMAN_MACHINE_NAME{{ .Delimiter }}{{ .MachineName }}{{ .Suffix }}{{ if .ComposePathsVar }}{{ .Prefix }}COMPOSE_CONVERT_WINDOWS_PATHS{{ .Delimiter }}true{{ .Suffix }}{{end}}{{ if .NoProxyVar }}{{ .Prefix }}{{ .NoProxyVar }}{{ .Delimiter }}{{ .NoProxyValue }}{{ .Suffix }}{{end}}{{ .UsageHint }}` + envTmpl = `{{ .Prefix }}PODMAN_USER{{ .Delimiter }}{{ .PodmanUser }}{{ .Suffix }}{{ .Prefix }}PODMAN_HOST{{ .Delimiter }}{{ .PodmanHost }}{{ .Suffix }}{{ .Prefix }}PODMAN_PORT{{ .Delimiter }}{{ .PodmanPort }}{{ .Suffix }}{{ .Prefix }}PODMAN_IDENTITY_FILE{{ .Delimiter }}{{ .IdentityFile }}{{ .Suffix }}{{ if .KnownHosts }}{{ .Prefix }}PODMAN_KNOWN_HOSTS{{ .Delimiter }}{{ .KnownHosts }}{{ .Suffix }}{{else}}{{ .Prefix }}PODMAN_IGNORE_HOSTS{{ .Delimiter }}true{{ .Suffix }}{{end}}{{ .Prefix }}PODMAN_MACHINE_NAME{{ .Delimiter }}{{ .MachineName }}{{ .Suffix }}{{ if .ComposePathsVar }}{{ .Prefix }}COMPOSE_CONVERT_WINDOWS_PATHS{{ .Delimiter }}true{{ .Suffix }}{{end}}{{ if .NoProxyVar }}{{ .Prefix }}{{ .NoProxyVar }}{{ .Delimiter }}{{ .NoProxyValue }}{{ .Suffix }}{{end}}{{ .UsageHint }}` + bridgeTmpl = `{{ .Prefix }}VARLINK_BRIDGE{{ .Delimiter }}{{ .VarlinkBridge }}{{ .Suffix }}{{ .UsageHint }}` ) var ( @@ -36,6 +37,7 @@ type ShellConfig struct { PodmanPort int IdentityFile string KnownHosts string + VarlinkBridge string UsageHint string MachineName string NoProxyVar string @@ -65,7 +67,11 @@ func cmdEnv(c CommandLine, api libmachine.API) error { } } - return executeTemplateStdout(shellCfg) + if c.Bool("varlink") { + return executeTemplateStdout(shellCfg, bridgeTmpl) + } else { + return executeTemplateStdout(shellCfg, envTmpl) + } } func shellCfgSet(c CommandLine, api libmachine.API) (*ShellConfig, error) { @@ -91,7 +97,7 @@ func shellCfgSet(c CommandLine, api libmachine.API) (*ShellConfig, error) { var shellCfg *ShellConfig hint := defaultUsageHinter.GenerateUsageHint(userShell, os.Args) - if host.Driver != nil { + if host.Driver != nil && c.Bool("varlink") == false { user := host.Driver.GetSSHUsername() if err != nil { @@ -123,6 +129,25 @@ func shellCfgSet(c CommandLine, api libmachine.API) (*ShellConfig, error) { UsageHint: hint, MachineName: host.Name, } + + } else if host.Driver != nil { + + client, err := host.CreateExternalRootSSHClient() + if err != nil { + return nil, err + } + + command := []string{client.BinaryPath} + command = append(command, client.BaseArgs...) + command = append(command, "varlink", "bridge") + bridge := strings.Join(command, " ") + + shellCfg = &ShellConfig{ + VarlinkBridge: bridge, + UsageHint: hint, + MachineName: host.Name, + } + } else { shellCfg = &ShellConfig{ UsageHint: hint, @@ -234,9 +259,9 @@ func shellCfgUnset(c CommandLine, api libmachine.API) (*ShellConfig, error) { return shellCfg, nil } -func executeTemplateStdout(shellCfg *ShellConfig) error { +func executeTemplateStdout(shellCfg *ShellConfig, strTmpl string) error { t := template.New("envConfig") - tmpl, err := t.Parse(envTmpl) + tmpl, err := t.Parse(strTmpl) if err != nil { return err } diff --git a/commands/env_test.go b/commands/env_test.go index c3b4766..84845a3 100644 --- a/commands/env_test.go +++ b/commands/env_test.go @@ -2,13 +2,16 @@ package commands import ( "os" + "strings" "testing" "github.com/boot2podman/machine/commands/commandstest" "github.com/boot2podman/machine/drivers/fakedriver" "github.com/boot2podman/machine/libmachine" + "github.com/boot2podman/machine/libmachine/drivers" "github.com/boot2podman/machine/libmachine/host" "github.com/boot2podman/machine/libmachine/libmachinetest" + "github.com/boot2podman/machine/libmachine/ssh" "github.com/boot2podman/machine/libmachine/state" "github.com/stretchr/testify/assert" ) @@ -549,3 +552,88 @@ func TestShellCfgUnset(t *testing.T) { os.Setenv(test.noProxyVar, "") } } + +type FakeRootSSHClientCreator struct { + rootclient *ssh.ExternalClient +} + +func (fsc *FakeRootSSHClientCreator) CreateSSHClient(d drivers.Driver) (ssh.Client, error) { + return nil, nil +} + +func (fsc *FakeRootSSHClientCreator) CreateExternalRootSSHClient(d drivers.Driver) (*ssh.ExternalClient, error) { + if fsc.rootclient == nil { + fsc.rootclient = &ssh.ExternalClient{} + } + return fsc.rootclient, nil +} + +func TestVarlink(t *testing.T) { + const ( + usageHint = "This is the varlink usage hint" + ) + + defer revertUsageHinter(defaultUsageHinter) + defaultUsageHinter = &SimpleUsageHintGenerator{usageHint} + + sshBinaryPath := "/usr/bin/ssh" + sshBaseArgs := "-F /dev/null -o LogLevel=quiet-o root@localhost" + + cc := FakeRootSSHClientCreator{rootclient: &ssh.ExternalClient{}} + cc.rootclient.BinaryPath = sshBinaryPath + cc.rootclient.BaseArgs = strings.Split(sshBaseArgs, " ") + + var tests = []struct { + description string + commandLine CommandLine + api libmachine.API + clientCreator host.SSHClientCreator + expectedShellCfg *ShellConfig + expectedErr error + }{ + { + description: "bash shell varlink happy path", + commandLine: &commandstest.FakeCommandLine{ + CliArgs: nil, + LocalFlags: &commandstest.FakeFlagger{ + Data: map[string]interface{}{ + "shell": "bash", + "varlink": true, + }, + }, + }, + api: &libmachinetest.FakeAPI{ + Hosts: []*host.Host{ + { + Name: defaultMachineName, + Driver: &fakedriver.Driver{ + MockState: state.Running, + MockIP: "127.0.0.1", + MockHostname: "localhost", + }, + }, + }, + }, + clientCreator: &cc, + expectedShellCfg: &ShellConfig{ + Prefix: "export ", + Delimiter: "=\"", + Suffix: "\"\n", + UsageHint: usageHint, + MachineName: defaultMachineName, + VarlinkBridge: sshBinaryPath + " " + sshBaseArgs + " varlink bridge", + }, + expectedErr: nil, + }, + } + + for _, test := range tests { + host.SetSSHClientCreator(test.clientCreator) + + t.Log(test.description) + + shellCfg, err := shellCfgSet(test.commandLine, test.api) + assert.Equal(t, test.expectedShellCfg, shellCfg) + assert.Equal(t, test.expectedErr, err) + } +} diff --git a/commands/ssh_test.go b/commands/ssh_test.go index c6214a2..6211353 100644 --- a/commands/ssh_test.go +++ b/commands/ssh_test.go @@ -26,6 +26,10 @@ func (fsc *FakeSSHClientCreator) CreateSSHClient(d drivers.Driver) (ssh.Client, return fsc.client, nil } +func (fsc *FakeSSHClientCreator) CreateExternalRootSSHClient(d drivers.Driver) (*ssh.ExternalClient, error) { + return nil, nil +} + func TestCmdSSH(t *testing.T) { testCases := []struct { commandLine CommandLine diff --git a/drivers/fakedriver/fakedriver.go b/drivers/fakedriver/fakedriver.go index 2b93212..c528ee6 100644 --- a/drivers/fakedriver/fakedriver.go +++ b/drivers/fakedriver/fakedriver.go @@ -10,9 +10,10 @@ import ( type Driver struct { *drivers.BaseDriver - MockState state.State - MockIP string - MockName string + MockState state.State + MockIP string + MockName string + MockHostname string } func (d *Driver) GetCreateFlags() []mcnflag.Flag { @@ -57,7 +58,7 @@ func (d *Driver) GetIP() (string, error) { } func (d *Driver) GetSSHHostname() (string, error) { - return "", nil + return d.MockHostname, nil } func (d *Driver) GetSSHKeyPath() string { diff --git a/libmachine/host/host.go b/libmachine/host/host.go index a48e64d..f748bd4 100644 --- a/libmachine/host/host.go +++ b/libmachine/host/host.go @@ -1,6 +1,7 @@ package host import ( + "os/exec" "regexp" "github.com/boot2podman/machine/libmachine/auth" @@ -22,6 +23,7 @@ var ( type SSHClientCreator interface { CreateSSHClient(d drivers.Driver) (ssh.Client, error) + CreateExternalRootSSHClient(d drivers.Driver) (*ssh.ExternalClient, error) } type StandardSSHClientCreator struct { @@ -86,6 +88,34 @@ func (creator *StandardSSHClientCreator) CreateSSHClient(d drivers.Driver) (ssh. return ssh.NewClient(d.GetSSHUsername(), addr, port, auth) } +func (h *Host) CreateExternalRootSSHClient() (*ssh.ExternalClient, error) { + return stdSSHClientCreator.CreateExternalRootSSHClient(h.Driver) +} + +func (creator *StandardSSHClientCreator) CreateExternalRootSSHClient(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, "root", addr, port, auth) +} + func (h *Host) runActionForState(action func() error, desiredState state.State) error { if drivers.MachineInState(h.Driver, desiredState)() { return mcnerror.ErrHostAlreadyInState{