This repository has been archived by the owner on Dec 7, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 228
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
5181daa
commit e6fece6
Showing
8 changed files
with
256 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package cmd | ||
|
||
import ( | ||
"io" | ||
|
||
"github.com/spf13/cobra" | ||
"github.com/weaveworks/ignite/cmd/ignite/cmd/vmcmd" | ||
) | ||
|
||
// NewCmdExec is an alias for vmcmd.NewCmdExec | ||
func NewCmdExec(out io.Writer, err io.Writer, in io.Reader) *cobra.Command { | ||
return vmcmd.NewCmdExec(out, err, in) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package vmcmd | ||
|
||
import ( | ||
"io" | ||
|
||
"github.com/lithammer/dedent" | ||
"github.com/spf13/cobra" | ||
"github.com/spf13/pflag" | ||
"github.com/weaveworks/ignite/cmd/ignite/run" | ||
"github.com/weaveworks/ignite/pkg/errutils" | ||
) | ||
|
||
// NewCmdExec exec's into a running vm | ||
func NewCmdExec(out io.Writer, err io.Writer, in io.Reader) *cobra.Command { | ||
ef := &run.ExecFlags{} | ||
|
||
cmd := &cobra.Command{ | ||
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. | ||
If no private key was created or wanting to use a different identity file, | ||
use the identity file flag (-i, --identity) to override the used identity file. | ||
The given VM is matched by prefix based on its ID and name. | ||
`), | ||
Args: cobra.MinimumNArgs(2), | ||
Run: func(cmd *cobra.Command, args []string) { | ||
errutils.Check(func() error { | ||
eo, err := ef.NewExecOptions(args[0], args[1:]...) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return run.Exec(eo) | ||
}()) | ||
}, | ||
} | ||
|
||
addExecFlags(cmd.Flags(), ef) | ||
return cmd | ||
} | ||
|
||
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") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
package run | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"io/ioutil" | ||
"net" | ||
"os" | ||
"path" | ||
"time" | ||
|
||
api "github.com/weaveworks/ignite/pkg/apis/ignite" | ||
"github.com/weaveworks/ignite/pkg/constants" | ||
"github.com/weaveworks/ignite/pkg/util" | ||
|
||
"golang.org/x/crypto/ssh" | ||
shellescape "gopkg.in/alessio/shellescape.v1" | ||
) | ||
|
||
type ExecFlags struct { | ||
Timeout uint32 | ||
IdentityFile string | ||
} | ||
|
||
type execOptions struct { | ||
*ExecFlags | ||
vm *api.VM | ||
command []string | ||
} | ||
|
||
func (ef *ExecFlags) NewExecOptions(vmMatch string, command ...string) (eo *execOptions, err error) { | ||
eo = &execOptions{ | ||
ExecFlags: ef, | ||
command: command, | ||
} | ||
eo.vm, err = getVMForMatch(vmMatch) | ||
return | ||
} | ||
|
||
func Exec(eo *execOptions) error { | ||
// Check if the VM is running | ||
if !eo.vm.Running() { | ||
return fmt.Errorf("VM %q is not running", eo.vm.GetUID()) | ||
} | ||
|
||
// Get the IP address | ||
ipAddrs := eo.vm.Status.IPAddresses | ||
if len(ipAddrs) == 0 { | ||
return fmt.Errorf("VM %q has no usable IP addresses", eo.vm.GetUID()) | ||
} | ||
|
||
// If an external identity file is specified, use it instead of the internal one | ||
privKeyFile := eo.IdentityFile | ||
if len(privKeyFile) == 0 { | ||
privKeyFile = path.Join(eo.vm.ObjectPath(), fmt.Sprintf(constants.VM_SSH_KEY_TEMPLATE, eo.vm.GetUID())) | ||
if !util.FileExists(privKeyFile) { | ||
return fmt.Errorf("no private key found for VM %q", eo.vm.GetUID()) | ||
} | ||
} | ||
signer, err := newSignerForKey(privKeyFile) | ||
if err != nil { | ||
return fmt.Errorf("unable to create signer for private key: %v", err) | ||
} | ||
|
||
// Create an SSH client, and connect, we will use this to exec | ||
config := newSSHConfig(signer, eo.Timeout) | ||
client, err := ssh.Dial("tcp", net.JoinHostPort(ipAddrs[0].String(), "22"), config) | ||
if err != nil { | ||
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 | ||
// code in the ssh.ExitError type | ||
return runSSHCommand(client, eo.command) | ||
} | ||
|
||
func newSignerForKey(keyPath string) (ssh.Signer, error) { | ||
key, err := ioutil.ReadFile(keyPath) | ||
if err != nil { | ||
return nil, fmt.Errorf("unable to read private key: %v", err) | ||
} | ||
|
||
// Create the Signer for this private key. | ||
return ssh.ParsePrivateKey(key) | ||
} | ||
|
||
func newSSHConfig(publicKey ssh.Signer, timeout uint32) *ssh.ClientConfig { | ||
return &ssh.ClientConfig{ | ||
User: "root", | ||
Auth: []ssh.AuthMethod{ | ||
ssh.PublicKeys(publicKey), | ||
}, | ||
HostKeyCallback: ssh.InsecureIgnoreHostKey(), // TODO: use ssh.FixedPublicKey instead | ||
Timeout: time.Second * time.Duration(timeout), | ||
} | ||
} | ||
|
||
func runSSHCommand(client *ssh.Client, 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 psuedo terminal failed: %v", err) | ||
} | ||
|
||
// 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 stderr: %v", err) | ||
} | ||
go io.Copy(os.Stdout, stdout) | ||
stdin, err := session.StdinPipe() | ||
if err != nil { | ||
return fmt.Errorf("failed to connect stderr: %v", err) | ||
} | ||
go io.Copy(stdin, os.Stdin) | ||
|
||
/* | ||
Do not wrap this error so the caller can check for the exit code | ||
If the remote server does not send an exit status, an error of type *ExitMissingError is returned. | ||
If the command completes unsuccessfully or is interrupted by a signal, the error is of type *ExitError. | ||
Other error types may be returned for I/O problems. | ||
*/ | ||
return session.Run(joinShellCommand(command)) | ||
} | ||
|
||
// joinShellCommand joins command parts into a single string safe for passing to sh -c (or SSH) | ||
func joinShellCommand(command []string) string { | ||
joined := command[0] | ||
if len(command) == 1 { | ||
return joined | ||
} | ||
for _, arg := range command[1:] { | ||
// NOTE: we need to escape nested single quotes | ||
// https://stackoverflow.com/a/1315213 | ||
joined += " " + shellescape.Quote(arg) | ||
} | ||
return joined | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
## ignite exec | ||
|
||
exec command in a running vm | ||
|
||
### Synopsis | ||
|
||
|
||
exec command in a running VM using ssh and the private key created for it during generation. | ||
If no private key was created or wanting to use a different identity file, | ||
use the identity file flag (-i, --identity) to override the used identity file. | ||
The given VM is matched by prefix based on its ID and name. | ||
|
||
|
||
``` | ||
ignite exec <vm> <command...> [flags] | ||
``` | ||
|
||
### Options | ||
|
||
``` | ||
-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) | ||
``` | ||
|
||
### Options inherited from parent commands | ||
|
||
``` | ||
--log-level loglevel Specify the loglevel for the program (default info) | ||
-q, --quiet The quiet mode allows for machine-parsable output, by printing only IDs | ||
``` | ||
|
||
### SEE ALSO | ||
|
||
* [ignite](ignite.md) - ignite: easily run Firecracker VMs | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters