Skip to content
This repository has been archived by the owner on Dec 7, 2023. It is now read-only.

Commit

Permalink
Merge pull request #572 from darkowlzz/interactive-exec
Browse files Browse the repository at this point in the history
Add interactive vm exec support
  • Loading branch information
stealthybox authored Apr 10, 2020
2 parents 321b39a + d040cd0 commit a474e49
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 28 deletions.
3 changes: 2 additions & 1 deletion cmd/ignite/cmd/vmcmd/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,6 @@ func NewCmdExec(out io.Writer, err io.Writer, in io.Reader) *cobra.Command {

func addExecFlags(fs *pflag.FlagSet, ef *run.ExecFlags) {
fs.StringVarP(&ef.IdentityFile, "identity", "i", "", "Override the vm's default identity file")
fs.Uint32VarP(&ef.Timeout, "timeout", "t", 10, "Timeout waiting for connection in seconds")
fs.Uint32Var(&ef.Timeout, "timeout", 10, "Timeout waiting for connection in seconds")
fs.BoolVarP(&ef.Tty, "tty", "t", false, "Allocate a pseudo-TTY")
}
45 changes: 19 additions & 26 deletions cmd/ignite/run/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package run

import (
"fmt"
"io"
"io/ioutil"
"net"
"os"
Expand All @@ -16,9 +15,11 @@ import (
shellescape "gopkg.in/alessio/shellescape.v1"
)

// ExecFlags contains the flags supported by the exec command.
type ExecFlags struct {
Timeout uint32
IdentityFile string
Tty bool
}

type execOptions struct {
Expand All @@ -27,6 +28,7 @@ type execOptions struct {
command []string
}

// NewExecOptions constructs and returns an execOptions.
func (ef *ExecFlags) NewExecOptions(vmMatch string, command ...string) (eo *execOptions, err error) {
eo = &execOptions{
ExecFlags: ef,
Expand All @@ -37,6 +39,7 @@ func (ef *ExecFlags) NewExecOptions(vmMatch string, command ...string) (eo *exec
return
}

// Exec executes command in a VM based on the provided execOptions.
func Exec(eo *execOptions) error {
// Check if the VM is running
if !eo.vm.Running() {
Expand Down Expand Up @@ -72,7 +75,7 @@ func Exec(eo *execOptions) error {

// Run the command, DO NOT wrap this error as the caller can check for the command exit
// code in the ssh.ExitError type
return runSSHCommand(client, eo.command)
return runSSHCommand(client, eo.Tty, eo.command)
}

func newSignerForKey(keyPath string) (ssh.Signer, error) {
Expand All @@ -96,40 +99,30 @@ func newSSHConfig(publicKey ssh.Signer, timeout uint32) *ssh.ClientConfig {
}
}

func runSSHCommand(client *ssh.Client, command []string) error {
func runSSHCommand(client *ssh.Client, tty bool, command []string) error {
// create a session for the command
session, err := client.NewSession()
if err != nil {
return fmt.Errorf("failed to create session: %v", err)
}
defer session.Close()

// get a pty
// TODO: should these be based on the host terminal?
// TODO: should we request something other than xterm?
// TODO: we should probably configure the terminal modes
modes := ssh.TerminalModes{}
if err := session.RequestPty("xterm", 80, 40, modes); err != nil {
return fmt.Errorf("request for pseudo terminal failed: %v", err)
if tty {
// get a pty
// TODO: should these be based on the host terminal?
// TODO: should we request something other than xterm?
// TODO: we should probably configure the terminal modes
modes := ssh.TerminalModes{}
if err := session.RequestPty("xterm", 80, 40, modes); err != nil {
return fmt.Errorf("request for pseudo terminal failed: %v", err)
}
}

// connect input / output
// Connect input / output
// TODO: these should come from the cobra command instead of hardcoding os.Stderr etc.
stderr, err := session.StderrPipe()
if err != nil {
return fmt.Errorf("failed to connect stderr: %v", err)
}
go io.Copy(os.Stderr, stderr)
stdout, err := session.StdoutPipe()
if err != nil {
return fmt.Errorf("failed to connect stdout: %v", err)
}
go io.Copy(os.Stdout, stdout)
stdin, err := session.StdinPipe()
if err != nil {
return fmt.Errorf("failed to connect stdin: %v", err)
}
go io.Copy(stdin, os.Stdin)
session.Stderr = os.Stderr
session.Stdout = os.Stdout
session.Stdin = os.Stdin

/*
Do not wrap this error so the caller can check for the exit code
Expand Down
3 changes: 2 additions & 1 deletion docs/cli/ignite/ignite_exec.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ ignite exec <vm> <command...> [flags]
```
-h, --help help for exec
-i, --identity string Override the vm's default identity file
-t, --timeout uint32 Timeout waiting for connection in seconds (default 10)
--timeout uint32 Timeout waiting for connection in seconds (default 10)
-t, --tty Allocate a pseudo-TTY
```

### Options inherited from parent commands
Expand Down
60 changes: 60 additions & 0 deletions e2e/vm_exec_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package e2e

import (
"fmt"
"os/exec"
"strings"
"testing"

"gotest.tools/assert"
)

func TestVMExecInteractive(t *testing.T) {
assert.Assert(t, e2eHome != "", "IGNITE_E2E_HOME should be set")

vmName := "e2e_test_ignite_exec_interactive"

runCmd := exec.Command(
igniteBin,
"run", "--name="+vmName,
"--ssh",
"weaveworks/ignite-ubuntu",
)
runOut, runErr := runCmd.CombinedOutput()

defer func() {
rmvCmd := exec.Command(
igniteBin,
"rm", "-f", vmName,
)
rmvOut, rmvErr := rmvCmd.CombinedOutput()
assert.Check(t, rmvErr, fmt.Sprintf("vm removal: \n%q\n%s", rmvCmd.Args, rmvOut))
}()

assert.Check(t, runErr, fmt.Sprintf("vm run: \n%q\n%s", runCmd.Args, runOut))

// Pass input data from host and write to a file inside the VM.
remoteFileName := "afile.txt"
inputContent := "foooo..."
input := strings.NewReader(inputContent)

execCmd := exec.Command(
igniteBin,
"exec", vmName,
"tee", remoteFileName,
)
execCmd.Stdin = input

execOut, execErr := execCmd.CombinedOutput()
assert.Check(t, execErr, fmt.Sprintf("exec: \n%q\n%s", execCmd.Args, execOut))

// Check the file content inside the VM.
catCmd := exec.Command(
igniteBin,
"exec", vmName,
"cat", remoteFileName,
)
catOut, catErr := catCmd.CombinedOutput()
assert.Check(t, catErr, fmt.Sprintf("cat: \n%q\n%s", catCmd.Args, catOut))
assert.Equal(t, string(catOut), inputContent, fmt.Sprintf("unexpected file content on host:\n\t(WNT): %q\n\t(GOT): %q", inputContent, string(catOut)))
}

0 comments on commit a474e49

Please sign in to comment.