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

Add new command: ignite exec #232

Merged
merged 3 commits into from
Jul 29, 2019
Merged

Add new command: ignite exec #232

merged 3 commits into from
Jul 29, 2019

Conversation

BenTheElder
Copy link
Contributor

@BenTheElder BenTheElder commented Jul 21, 2019

This is a pretty simple / MVP implementation, however it is enough to perform basic exec (over ssh) against a running ignite VM.

TODO:

  • inherit out/err/in writers from the cobra command (other commands seem to be ignoring these inputs anyhow...)
  • docs (?)
  • support for setting environment variables (also we should consider other docker exec options? though -i already conflicts ...)
  • more extensive testing
  • consider porting ignite ssh to use the same packages

How this works:

  • We open an ssh connection to the VM and create a session with golang.org/x/crypto/ssh
  • We then request a pseudo tty in the session and wire up stdin / stdout / stderr to the host
  • Since ssh requires sending the command as a single string, args[1:] are converted to args[1] + strings.Join(shellEscape(args[2:...], " ") and run that way (pseudo code)

Additionally, the exit code from the command is preserved if possible.

$ bin/ignite exec centos echo '"hello world"'
"hello world"

$ bin/ignite exec centos -- /bin/bash -c 'echo hello world "$HOSTNAME"'
hello world 50a7f9aa26409ab3

Fixes #65

@BenTheElder
Copy link
Contributor Author

tracking issue: #142

@alexellis
Copy link
Contributor

This looks very useful Ben 👍 Out of interest, is it required for KinD to work?


func newSSHConfig(publicKey ssh.Signer, timeout uint32) *ssh.ClientConfig {
return &ssh.ClientConfig{
User: "root",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a scenario where root may not be the user wanted for ssh - VMs with "disable root login"?

Copy link
Contributor Author

@BenTheElder BenTheElder Jul 22, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so, but for the moment this matches ignite ssh which also assumes the user is root.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The disable root login option default in most distributions prevents password-based authentication, but SSH keys should work just fine. We should also open an issue about allowing user selection in ssh and exec.

}

// joinShellCommand joins command parts into a single string safe for passing to sh -c (or SSH)
func joinShellCommand(command []string) string {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rgee0 @matipan is this method/package any use to us with faas-cli or ofc-bootstrap?

@BenTheElder
Copy link
Contributor Author

This looks very useful Ben 👍 Out of interest, is it required for KinD to work?

kind requires some way to execute commands, we could implement this in kind some other way (driving SSH? Implementing this logic there?), but I think it's more generally useful to have this in ignite.


One other note vs docker exec, docker exec will treat everything after the container name/ID as part of the command. IE docker exec centos bash --version works with --version as a argument to bash, not docker vs this PR requiring ignite exec centos -- bash --version.

We could implement this but I think we'd need our own flag processing step, I don't think pflag / cobra alone can support this

return joined
}
for _, arg := range command[1:] {
// NOTE: we need to escape nested single quotes
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slightly stale comment from before using the shellescape package.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can go ahead and remove the comment then 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated to a less stale comment

Copy link
Contributor

@twelho twelho left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work, thanks!
Some nits, after those are fixed, let's go ahead and merge 👍
Remember to run make tidy again to update the doc descriptions after editing them.

"github.com/weaveworks/ignite/pkg/errutils"
)

// NewCmdExec exec's into a running vm
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vm -> VM

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


cmd := &cobra.Command{
Use: "exec <vm> <command...>",
Short: "exec command in a running vm",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"execute a command in a running VM"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Use: "exec <vm> <command...>",
Short: "exec command in a running vm",
Long: dedent.Dedent(`
exec command in a running VM using ssh and the private key created for it during generation.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Execute a command...
ssh -> SSH

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

}

func addExecFlags(fs *pflag.FlagSet, ef *run.ExecFlags) {
fs.StringVarP(&ef.IdentityFile, "identity", "i", "", "Override the vm's default identity file")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vm -> VM

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this matches the ssh command's flags exactly at the moment, should we update it there too?

api "github.com/weaveworks/ignite/pkg/apis/ignite"
"github.com/weaveworks/ignite/pkg/constants"
"github.com/weaveworks/ignite/pkg/util"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this blank line and run make tidy again 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

// 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 psuedo terminal failed: %v", err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

psuedo -> pseudo

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

// 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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Defaulting to xterm is fine for now, we could later also allow changing this via a flag. Anyways this is non-blocking for this initial implementation.

go io.Copy(os.Stderr, stderr)
stdout, err := session.StdoutPipe()
if err != nil {
return fmt.Errorf("failed to connect stderr: %v", err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stderr -> stdout

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

go io.Copy(os.Stdout, stdout)
stdin, err := session.StdinPipe()
if err != nil {
return fmt.Errorf("failed to connect stderr: %v", err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stderr -> stdin

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

return joined
}
for _, arg := range command[1:] {
// NOTE: we need to escape nested single quotes
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can go ahead and remove the comment then 👍

Copy link
Contributor Author

@BenTheElder BenTheElder left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed comments

"github.com/weaveworks/ignite/pkg/errutils"
)

// NewCmdExec exec's into a running vm
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


cmd := &cobra.Command{
Use: "exec <vm> <command...>",
Short: "exec command in a running vm",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Use: "exec <vm> <command...>",
Short: "exec command in a running vm",
Long: dedent.Dedent(`
exec command in a running VM using ssh and the private key created for it during generation.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

api "github.com/weaveworks/ignite/pkg/apis/ignite"
"github.com/weaveworks/ignite/pkg/constants"
"github.com/weaveworks/ignite/pkg/util"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

ExecFlags: ef,
command: command,
}
eo.vm, err = getVMForMatch(vmMatch)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

return fmt.Errorf("failed to dial: %v", err)
}

// run the command, DO NOT wrap this error as the caller can check for the command exit
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

// 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 psuedo terminal failed: %v", err)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

go io.Copy(os.Stderr, stderr)
stdout, err := session.StdoutPipe()
if err != nil {
return fmt.Errorf("failed to connect stderr: %v", err)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

go io.Copy(os.Stdout, stdout)
stdin, err := session.StdinPipe()
if err != nil {
return fmt.Errorf("failed to connect stderr: %v", err)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

return joined
}
for _, arg := range command[1:] {
// NOTE: we need to escape nested single quotes
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated to a less stale comment

@BenTheElder
Copy link
Contributor Author

rebased

Copy link
Contributor

@luxas luxas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks A LOT @BenTheElder ✨! This looks wonderful :)

LGTM

@luxas luxas added this to the v0.5.0 milestone Jul 29, 2019
@luxas luxas added the kind/feature Categorizes issue or PR as related to a new feature. label Jul 29, 2019
@luxas luxas self-assigned this Jul 29, 2019
@luxas
Copy link
Contributor

luxas commented Jul 29, 2019

We can follow-up the possible improvements here in an other issue later, but for now, merging this to get us started. Next, integrate with kind 😉

@luxas luxas merged commit 9ce303e into weaveworks:master Jul 29, 2019
@luxas luxas changed the title implement ignite exec Add new command: ignite exec Aug 6, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
kind/feature Categorizes issue or PR as related to a new feature.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Consider adding ignite exec [vm] [command]
4 participants