mirror of https://github.com/docker/docs.git
Move code to use SSH "backends"
Default to shelling out to SSH when available. Signed-off-by: Nathan LeClaire <nathan.leclaire@gmail.com>
This commit is contained in:
parent
15e022219f
commit
2f78b7f92a
|
@ -1,29 +1,24 @@
|
||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"fmt"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/machine/log"
|
"github.com/docker/machine/log"
|
||||||
|
"github.com/docker/machine/state"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
"github.com/docker/machine/drivers"
|
|
||||||
"github.com/docker/machine/ssh"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func cmdSsh(c *cli.Context) {
|
func cmdSsh(c *cli.Context) {
|
||||||
var (
|
args := c.Args()
|
||||||
output ssh.Output
|
name := args.First()
|
||||||
err error
|
cmd := ""
|
||||||
)
|
|
||||||
|
|
||||||
if len(c.Args()) == 0 {
|
if name == "" {
|
||||||
log.Fatal("Error: Please specify a machine name.")
|
log.Fatal("Error: Please specify a machine name.")
|
||||||
}
|
}
|
||||||
|
|
||||||
name := c.Args().First()
|
|
||||||
|
|
||||||
certInfo := getCertPathInfo(c)
|
certInfo := getCertPathInfo(c)
|
||||||
defaultStore, err := getDefaultStore(
|
defaultStore, err := getDefaultStore(
|
||||||
c.GlobalString("storage-path"),
|
c.GlobalString("storage-path"),
|
||||||
|
@ -44,40 +39,50 @@ func cmdSsh(c *cli.Context) {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = host.GetURL()
|
currentState, err := host.Driver.GetState()
|
||||||
if err != nil {
|
|
||||||
if err == drivers.ErrHostIsNotRunning {
|
|
||||||
log.Fatalf("%s is not running. Please start this with docker-machine start %s", host.Name, host.Name)
|
|
||||||
} else {
|
|
||||||
log.Fatalf("Unexpected error getting machine url: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(c.Args()) == 1 {
|
|
||||||
err = host.CreateSSHShell()
|
|
||||||
} else {
|
|
||||||
var (
|
|
||||||
cmd string
|
|
||||||
args []string = c.Args()
|
|
||||||
)
|
|
||||||
|
|
||||||
for i, arg := range args {
|
|
||||||
if arg == "--" {
|
|
||||||
i++
|
|
||||||
cmd = strings.Join(args[i:], " ")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(cmd) == 0 {
|
|
||||||
cmd = strings.Join(args[1:], " ")
|
|
||||||
}
|
|
||||||
output, err = host.RunSSHCommand(cmd)
|
|
||||||
|
|
||||||
io.Copy(os.Stderr, output.Stderr)
|
|
||||||
io.Copy(os.Stdout, output.Stdout)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if currentState != state.Running {
|
||||||
|
log.Fatalf("Error: Cannot run SSH command: Host %q is not running", host.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop through the arguments and parse out a command which relies on
|
||||||
|
// flags if it exists, for instance an invocation of the form
|
||||||
|
// `docker-machine ssh dev -- df -h` would mandate this, otherwise we
|
||||||
|
// will accidentally trigger the codegangsta/cli help text because it
|
||||||
|
// thinks we are trying to specify codegangsta flags.
|
||||||
|
//
|
||||||
|
// TODO: I thought codegangsta/cli supported the flag parsing
|
||||||
|
// terminator manually, which would mitigate the need for this kind of
|
||||||
|
// hack. We should investigate.
|
||||||
|
for i, arg := range args {
|
||||||
|
if arg == "--" {
|
||||||
|
cmd = strings.Join(args[i+1:], " ")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// It is possible that the user has specified an appended command which
|
||||||
|
// does not rely on the flag parsing terminator, such as
|
||||||
|
// `docker-machine ssh dev ls`, so this block accounts for that case.
|
||||||
|
if len(cmd) == 0 {
|
||||||
|
cmd = strings.Join(args[1:], " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.Args()) == 1 {
|
||||||
|
err := host.CreateSSHShell()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
output, err := host.RunSSHCommand(cmd)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Print(output)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -913,6 +913,30 @@ cgroup 499.8M 0 499.8M 0% /sys/fs/cgroup
|
||||||
/mnt/sda1/var/lib/docker/aufs
|
/mnt/sda1/var/lib/docker/aufs
|
||||||
```
|
```
|
||||||
|
|
||||||
|
##### Different types of SSH
|
||||||
|
|
||||||
|
When Docker Machine is invoked, it will check to see if you have the venerable
|
||||||
|
`ssh` binary around locally and will attempt to use that for the SSH commands it
|
||||||
|
needs to run, whether they are a part of an operation such as creation or have
|
||||||
|
been requested by the user directly. If it does not find an external `ssh`
|
||||||
|
binary locally, it will default to using a native Go implementation from
|
||||||
|
[crypto/ssh](https://godoc.org/golang.org/x/crypto/ssh). This is useful in
|
||||||
|
situations where you may not have access to traditional UNIX tools, such as if
|
||||||
|
you are using Docker Machine on Windows without having msysgit installed
|
||||||
|
alongside of it.
|
||||||
|
|
||||||
|
In most situations, you will not have to worry about this implementation detail
|
||||||
|
and Docker Machine will act sensibly out of the box. However, if you
|
||||||
|
deliberately want to use the Go native version, you can do so with a global
|
||||||
|
command line flag / environment variable like so:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ docker-machine --native-ssh ssh dev
|
||||||
|
```
|
||||||
|
|
||||||
|
There are some variations in behavior between the two methods, so please report
|
||||||
|
any issues or inconsistencies if you come across them.
|
||||||
|
|
||||||
#### scp
|
#### scp
|
||||||
|
|
||||||
Copy files from your local host to a machine, from machine to machine, or from a
|
Copy files from your local host to a machine, from machine to machine, or from a
|
||||||
|
|
|
@ -256,7 +256,7 @@ func (c *ComputeUtil) executeCommands(commands []string, ip, sshKeyPath string)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := client.Run(command); err != nil {
|
if _, err := client.Output(command); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,17 +8,15 @@ import (
|
||||||
"github.com/docker/machine/utils"
|
"github.com/docker/machine/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RunSSHCommandFromDriver(d Driver, command string) (ssh.Output, error) {
|
func RunSSHCommandFromDriver(d Driver, command string) (string, error) {
|
||||||
var output ssh.Output
|
|
||||||
|
|
||||||
addr, err := d.GetSSHHostname()
|
addr, err := d.GetSSHHostname()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return output, err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
port, err := d.GetSSHPort()
|
port, err := d.GetSSHPort()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return output, err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
auth := &ssh.Auth{
|
auth := &ssh.Auth{
|
||||||
|
@ -27,11 +25,11 @@ func RunSSHCommandFromDriver(d Driver, command string) (ssh.Output, error) {
|
||||||
|
|
||||||
client, err := ssh.NewClient(d.GetSSHUsername(), addr, port, auth)
|
client, err := ssh.NewClient(d.GetSSHUsername(), addr, port, auth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return output, err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("About to run SSH command:\n%s", command)
|
log.Debugf("About to run SSH command:\n%s", command)
|
||||||
output, err = client.Run(command)
|
output, err := client.Output(command)
|
||||||
log.Debugf("SSH cmd err, output: %v: %s", err, output)
|
log.Debugf("SSH cmd err, output: %v: %s", err, output)
|
||||||
return output, err
|
return output, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -503,20 +503,15 @@ func (d *Driver) GetIP() (string, error) {
|
||||||
if s != state.Running {
|
if s != state.Running {
|
||||||
return "", drivers.ErrHostIsNotRunning
|
return "", drivers.ErrHostIsNotRunning
|
||||||
}
|
}
|
||||||
|
|
||||||
output, err := drivers.RunSSHCommandFromDriver(d, "ip addr show dev eth1")
|
output, err := drivers.RunSSHCommandFromDriver(d, "ip addr show dev eth1")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
var buf bytes.Buffer
|
log.Debugf("SSH returned: %s\nEND SSH\n", output)
|
||||||
if _, err := buf.ReadFrom(output.Stdout); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
out := buf.String()
|
|
||||||
log.Debugf("SSH returned: %s\nEND SSH\n", out)
|
|
||||||
// parse to find: inet 192.168.59.103/24 brd 192.168.59.255 scope global eth1
|
// parse to find: inet 192.168.59.103/24 brd 192.168.59.255 scope global eth1
|
||||||
lines := strings.Split(out, "\n")
|
lines := strings.Split(output, "\n")
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
vals := strings.Split(strings.TrimSpace(line), " ")
|
vals := strings.Split(strings.TrimSpace(line), " ")
|
||||||
if len(vals) >= 2 && vals[0] == "inet" {
|
if len(vals) >= 2 && vals[0] == "inet" {
|
||||||
|
@ -524,7 +519,7 @@ func (d *Driver) GetIP() (string, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", fmt.Errorf("No IP address found %s", out)
|
return "", fmt.Errorf("No IP address found %s", output)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) publicSSHKeyPath() string {
|
func (d *Driver) publicSSHKeyPath() string {
|
||||||
|
|
|
@ -2,10 +2,18 @@ package libmachine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrHostDoesNotExist = errors.New("Host does not exist")
|
|
||||||
ErrInvalidHostname = errors.New("Invalid hostname specified")
|
ErrInvalidHostname = errors.New("Invalid hostname specified")
|
||||||
ErrUnknownProviderType = errors.New("Unknown hypervisor type")
|
ErrUnknownProviderType = errors.New("Unknown hypervisor type")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ErrHostDoesNotExist struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrHostDoesNotExist) Error() string {
|
||||||
|
return fmt.Sprintf("Error: Host does not exist: %s", e.Name)
|
||||||
|
}
|
||||||
|
|
|
@ -25,7 +25,9 @@ func NewFilestore(rootPath string, caCert string, privateKey string) *Filestore
|
||||||
func (s Filestore) loadHost(name string) (*Host, error) {
|
func (s Filestore) loadHost(name string) (*Host, error) {
|
||||||
hostPath := filepath.Join(utils.GetMachineDir(), name)
|
hostPath := filepath.Join(utils.GetMachineDir(), name)
|
||||||
if _, err := os.Stat(hostPath); os.IsNotExist(err) {
|
if _, err := os.Stat(hostPath); os.IsNotExist(err) {
|
||||||
return nil, ErrHostDoesNotExist
|
return nil, ErrHostDoesNotExist{
|
||||||
|
Name: name,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
host := &Host{Name: name, StorePath: hostPath}
|
host := &Host{Name: name, StorePath: hostPath}
|
||||||
|
|
|
@ -137,26 +137,30 @@ func (h *Host) Create(name string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Host) RunSSHCommand(command string) (ssh.Output, error) {
|
func (h *Host) RunSSHCommand(command string) (string, error) {
|
||||||
return drivers.RunSSHCommandFromDriver(h.Driver, command)
|
return drivers.RunSSHCommandFromDriver(h.Driver, command)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Host) CreateSSHShell() error {
|
func (h *Host) CreateSSHClient() (ssh.Client, error) {
|
||||||
addr, err := h.Driver.GetSSHHostname()
|
addr, err := h.Driver.GetSSHHostname()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return ssh.ExternalClient{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
port, err := h.Driver.GetSSHPort()
|
port, err := h.Driver.GetSSHPort()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return ssh.ExternalClient{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
auth := &ssh.Auth{
|
auth := &ssh.Auth{
|
||||||
Keys: []string{h.Driver.GetSSHKeyPath()},
|
Keys: []string{h.Driver.GetSSHKeyPath()},
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := ssh.NewClient(h.Driver.GetSSHUsername(), addr, port, auth)
|
return ssh.NewClient(h.Driver.GetSSHUsername(), addr, port, auth)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Host) CreateSSHShell() error {
|
||||||
|
client, err := h.CreateSSHClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,6 @@ import (
|
||||||
"github.com/docker/machine/libmachine/provision/pkgaction"
|
"github.com/docker/machine/libmachine/provision/pkgaction"
|
||||||
"github.com/docker/machine/libmachine/swarm"
|
"github.com/docker/machine/libmachine/swarm"
|
||||||
"github.com/docker/machine/log"
|
"github.com/docker/machine/log"
|
||||||
"github.com/docker/machine/ssh"
|
|
||||||
"github.com/docker/machine/state"
|
"github.com/docker/machine/state"
|
||||||
"github.com/docker/machine/utils"
|
"github.com/docker/machine/utils"
|
||||||
)
|
)
|
||||||
|
@ -102,17 +101,7 @@ func (provisioner *Boot2DockerProvisioner) Package(name string, action pkgaction
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provisioner *Boot2DockerProvisioner) Hostname() (string, error) {
|
func (provisioner *Boot2DockerProvisioner) Hostname() (string, error) {
|
||||||
output, err := provisioner.SSHCommand(fmt.Sprintf("hostname"))
|
return provisioner.SSHCommand("hostname")
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
var so bytes.Buffer
|
|
||||||
if _, err := so.ReadFrom(output.Stdout); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return so.String(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provisioner *Boot2DockerProvisioner) SetHostname(hostname string) error {
|
func (provisioner *Boot2DockerProvisioner) SetHostname(hostname string) error {
|
||||||
|
@ -231,7 +220,7 @@ func (provisioner *Boot2DockerProvisioner) Provision(swarmOptions swarm.SwarmOpt
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provisioner *Boot2DockerProvisioner) SSHCommand(args string) (ssh.Output, error) {
|
func (provisioner *Boot2DockerProvisioner) SSHCommand(args string) (string, error) {
|
||||||
return drivers.RunSSHCommandFromDriver(provisioner.Driver, args)
|
return drivers.RunSSHCommandFromDriver(provisioner.Driver, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"github.com/docker/machine/libmachine/auth"
|
"github.com/docker/machine/libmachine/auth"
|
||||||
"github.com/docker/machine/libmachine/engine"
|
"github.com/docker/machine/libmachine/engine"
|
||||||
"github.com/docker/machine/libmachine/swarm"
|
"github.com/docker/machine/libmachine/swarm"
|
||||||
"github.com/docker/machine/ssh"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type GenericProvisioner struct {
|
type GenericProvisioner struct {
|
||||||
|
@ -25,17 +24,7 @@ type GenericProvisioner struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provisioner *GenericProvisioner) Hostname() (string, error) {
|
func (provisioner *GenericProvisioner) Hostname() (string, error) {
|
||||||
output, err := provisioner.SSHCommand("hostname")
|
return provisioner.SSHCommand("hostname")
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
var so bytes.Buffer
|
|
||||||
if _, err := so.ReadFrom(output.Stdout); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return so.String(), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provisioner *GenericProvisioner) SetHostname(hostname string) error {
|
func (provisioner *GenericProvisioner) SetHostname(hostname string) error {
|
||||||
|
@ -63,7 +52,7 @@ func (provisioner *GenericProvisioner) GetDockerOptionsDir() string {
|
||||||
return provisioner.DockerOptionsDir
|
return provisioner.DockerOptionsDir
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provisioner *GenericProvisioner) SSHCommand(args string) (ssh.Output, error) {
|
func (provisioner *GenericProvisioner) SSHCommand(args string) (string, error) {
|
||||||
return drivers.RunSSHCommandFromDriver(provisioner.Driver, args)
|
return drivers.RunSSHCommandFromDriver(provisioner.Driver, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package provision
|
package provision
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/docker/machine/drivers"
|
"github.com/docker/machine/drivers"
|
||||||
|
@ -9,7 +8,6 @@ import (
|
||||||
"github.com/docker/machine/libmachine/engine"
|
"github.com/docker/machine/libmachine/engine"
|
||||||
"github.com/docker/machine/libmachine/provision/pkgaction"
|
"github.com/docker/machine/libmachine/provision/pkgaction"
|
||||||
"github.com/docker/machine/libmachine/swarm"
|
"github.com/docker/machine/libmachine/swarm"
|
||||||
"github.com/docker/machine/ssh"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var provisioners = make(map[string]*RegisteredProvisioner)
|
var provisioners = make(map[string]*RegisteredProvisioner)
|
||||||
|
@ -52,7 +50,7 @@ type Provisioner interface {
|
||||||
GetDriver() drivers.Driver
|
GetDriver() drivers.Driver
|
||||||
|
|
||||||
// Short-hand for accessing an SSH command from the driver.
|
// Short-hand for accessing an SSH command from the driver.
|
||||||
SSHCommand(args string) (ssh.Output, error)
|
SSHCommand(args string) (string, error)
|
||||||
|
|
||||||
// Set the OS Release info depending on how it's represented
|
// Set the OS Release info depending on how it's represented
|
||||||
// internally
|
// internally
|
||||||
|
@ -69,19 +67,12 @@ func Register(name string, p *RegisteredProvisioner) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func DetectProvisioner(d drivers.Driver) (Provisioner, error) {
|
func DetectProvisioner(d drivers.Driver) (Provisioner, error) {
|
||||||
var (
|
osReleaseOut, err := drivers.RunSSHCommandFromDriver(d, "cat /etc/os-release")
|
||||||
osReleaseOut bytes.Buffer
|
|
||||||
)
|
|
||||||
catOsReleaseOutput, err := drivers.RunSSHCommandFromDriver(d, "cat /etc/os-release")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Error getting SSH command: %s", err)
|
return nil, fmt.Errorf("Error getting SSH command: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := osReleaseOut.ReadFrom(catOsReleaseOutput.Stdout); err != nil {
|
osReleaseInfo, err := NewOsRelease([]byte(osReleaseOut))
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
osReleaseInfo, err := NewOsRelease(osReleaseOut.Bytes())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Error parsing /etc/os-release file: %s", err)
|
return nil, fmt.Errorf("Error parsing /etc/os-release file: %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package provision
|
package provision
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -26,12 +25,7 @@ func installDockerGeneric(p Provisioner) error {
|
||||||
// install docker - until cloudinit we use ubuntu everywhere so we
|
// install docker - until cloudinit we use ubuntu everywhere so we
|
||||||
// just install it using the docker repos
|
// just install it using the docker repos
|
||||||
if output, err := p.SSHCommand("if ! type docker; then curl -sSL https://get.docker.com | sh -; fi"); err != nil {
|
if output, err := p.SSHCommand("if ! type docker; then curl -sSL https://get.docker.com | sh -; fi"); err != nil {
|
||||||
var buf bytes.Buffer
|
return fmt.Errorf("error installing docker: %s\n", output)
|
||||||
if _, err := buf.ReadFrom(output.Stderr); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("error installing docker: %s\n", buf.String())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
9
main.go
9
main.go
|
@ -8,6 +8,7 @@ import (
|
||||||
|
|
||||||
"github.com/docker/machine/commands"
|
"github.com/docker/machine/commands"
|
||||||
"github.com/docker/machine/log"
|
"github.com/docker/machine/log"
|
||||||
|
"github.com/docker/machine/ssh"
|
||||||
"github.com/docker/machine/utils"
|
"github.com/docker/machine/utils"
|
||||||
"github.com/docker/machine/version"
|
"github.com/docker/machine/version"
|
||||||
)
|
)
|
||||||
|
@ -58,6 +59,9 @@ func main() {
|
||||||
app.Email = "https://github.com/docker/machine"
|
app.Email = "https://github.com/docker/machine"
|
||||||
app.Before = func(c *cli.Context) error {
|
app.Before = func(c *cli.Context) error {
|
||||||
os.Setenv("MACHINE_STORAGE_PATH", c.GlobalString("storage-path"))
|
os.Setenv("MACHINE_STORAGE_PATH", c.GlobalString("storage-path"))
|
||||||
|
if c.GlobalBool("native-ssh") {
|
||||||
|
ssh.SetDefaultClient(ssh.Native)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
app.Commands = commands.Commands
|
app.Commands = commands.Commands
|
||||||
|
@ -100,6 +104,11 @@ func main() {
|
||||||
Usage: "Private key used in client TLS auth",
|
Usage: "Private key used in client TLS auth",
|
||||||
Value: "",
|
Value: "",
|
||||||
},
|
},
|
||||||
|
cli.BoolFlag{
|
||||||
|
EnvVar: "MACHINE_NATIVE_SSH",
|
||||||
|
Name: "native-ssh",
|
||||||
|
Usage: "Use the native (Go-based) SSH implementation.",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
app.Run(os.Args)
|
app.Run(os.Args)
|
||||||
|
|
189
ssh/client.go
189
ssh/client.go
|
@ -1,11 +1,10 @@
|
||||||
package ssh
|
package ssh
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/term"
|
"github.com/docker/docker/pkg/term"
|
||||||
"github.com/docker/machine/log"
|
"github.com/docker/machine/log"
|
||||||
|
@ -13,41 +12,108 @@ import (
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Client struct {
|
type Client interface {
|
||||||
Config *ssh.ClientConfig
|
Output(command string) (string, error)
|
||||||
|
Shell() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExternalClient struct {
|
||||||
|
BaseArgs []string
|
||||||
|
BinaryPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
type NativeClient struct {
|
||||||
|
Config ssh.ClientConfig
|
||||||
Hostname string
|
Hostname string
|
||||||
Port int
|
Port int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Auth struct {
|
||||||
|
Passwords []string
|
||||||
|
Keys []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type SSHClientType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
maxDialAttempts = 10
|
maxDialAttempts = 10
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewClient(user string, host string, port int, auth *Auth) (*Client, error) {
|
const (
|
||||||
config, err := NewConfig(user, auth)
|
External SSHClientType = "external"
|
||||||
|
Native SSHClientType = "native"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
baseSSHArgs = []string{
|
||||||
|
"-o", "IdentitiesOnly=yes",
|
||||||
|
"-o", "StrictHostKeyChecking=no",
|
||||||
|
"-o", "UserKnownHostsFile=/dev/null",
|
||||||
|
"-o", "LogLevel=quiet", // suppress "Warning: Permanently added '[localhost]:2022' (ECDSA) to the list of known hosts."
|
||||||
|
"-o", "ConnectionAttempts=3", // retry 3 times if SSH connection fails
|
||||||
|
"-o", "ConnectTimeout=10", // timeout after 10 seconds
|
||||||
|
}
|
||||||
|
defaultClientType SSHClientType = External
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetDefaultClient(clientType SSHClientType) {
|
||||||
|
// Allow over-riding of default client type, so that even if ssh binary
|
||||||
|
// is found in PATH we can still use the Go native implementation if
|
||||||
|
// desired.
|
||||||
|
switch clientType {
|
||||||
|
case External:
|
||||||
|
defaultClientType = External
|
||||||
|
case Native:
|
||||||
|
defaultClientType = Native
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(user string, host string, port int, auth *Auth) (Client, error) {
|
||||||
|
sshBinaryPath, err := exec.LookPath("ssh")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
if defaultClientType == External {
|
||||||
|
log.Fatal("Requested shellout SSH client type but no ssh binary available")
|
||||||
|
}
|
||||||
|
log.Debug("ssh binary not found, using native Go implementation")
|
||||||
|
return NewNativeClient(user, host, port, auth)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Client{
|
if defaultClientType == Native {
|
||||||
|
log.Debug("Using SSH client type: native")
|
||||||
|
return NewNativeClient(user, host, port, auth)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug("Using SSH client type: external")
|
||||||
|
return NewExternalClient(sshBinaryPath, user, host, port, auth)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNativeClient(user, host string, port int, auth *Auth) (Client, error) {
|
||||||
|
config, err := NewNativeConfig(user, auth)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error getting config for native Go SSH: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return NativeClient{
|
||||||
Config: config,
|
Config: config,
|
||||||
Hostname: host,
|
Hostname: host,
|
||||||
Port: port,
|
Port: port,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConfig(user string, auth *Auth) (*ssh.ClientConfig, error) {
|
func NewNativeConfig(user string, auth *Auth) (ssh.ClientConfig, error) {
|
||||||
var authMethods []ssh.AuthMethod
|
var (
|
||||||
|
authMethods []ssh.AuthMethod
|
||||||
|
)
|
||||||
|
|
||||||
for _, k := range auth.Keys {
|
for _, k := range auth.Keys {
|
||||||
key, err := ioutil.ReadFile(k)
|
key, err := ioutil.ReadFile(k)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return ssh.ClientConfig{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
privateKey, err := ssh.ParsePrivateKey(key)
|
privateKey, err := ssh.ParsePrivateKey(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return ssh.ClientConfig{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
authMethods = append(authMethods, ssh.PublicKeys(privateKey))
|
authMethods = append(authMethods, ssh.PublicKeys(privateKey))
|
||||||
|
@ -57,57 +123,47 @@ func NewConfig(user string, auth *Auth) (*ssh.ClientConfig, error) {
|
||||||
authMethods = append(authMethods, ssh.Password(p))
|
authMethods = append(authMethods, ssh.Password(p))
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ssh.ClientConfig{
|
return ssh.ClientConfig{
|
||||||
User: user,
|
User: user,
|
||||||
Auth: authMethods,
|
Auth: authMethods,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func dialSuccess(client *Client) func() bool {
|
func (client NativeClient) dialSuccess() bool {
|
||||||
return func() bool {
|
if _, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", client.Hostname, client.Port), &client.Config); err != nil {
|
||||||
if _, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", client.Hostname, client.Port), client.Config); err != nil {
|
|
||||||
log.Debugf("Error dialing TCP: %s", err)
|
log.Debugf("Error dialing TCP: %s", err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client *Client) Run(command string) (Output, error) {
|
func (client NativeClient) Output(command string) (string, error) {
|
||||||
var (
|
if err := utils.WaitFor(client.dialSuccess); err != nil {
|
||||||
output Output
|
return "", fmt.Errorf("Error attempting SSH client dial: %s", err)
|
||||||
stdout, stderr bytes.Buffer
|
|
||||||
)
|
|
||||||
|
|
||||||
if err := utils.WaitFor(dialSuccess(client)); err != nil {
|
|
||||||
return output, fmt.Errorf("Error attempting SSH client dial: %s", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", client.Hostname, client.Port), client.Config)
|
conn, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", client.Hostname, client.Port), &client.Config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return output, fmt.Errorf("Mysterious error dialing TCP for SSH (we already succeeded at least once) : %s", err)
|
return "", fmt.Errorf("Mysterious error dialing TCP for SSH (we already succeeded at least once) : %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
session, err := conn.NewSession()
|
session, err := conn.NewSession()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return output, fmt.Errorf("Error getting new session: %s", err)
|
return "", fmt.Errorf("Error getting new session: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
defer session.Close()
|
defer session.Close()
|
||||||
|
|
||||||
session.Stdout = &stdout
|
output, err := session.CombinedOutput(command)
|
||||||
session.Stderr = &stderr
|
|
||||||
|
|
||||||
output = Output{
|
return string(output), err
|
||||||
Stdout: &stdout,
|
|
||||||
Stderr: &stderr,
|
|
||||||
}
|
|
||||||
|
|
||||||
return output, session.Run(command)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client *Client) Shell() error {
|
func (client NativeClient) Shell() error {
|
||||||
conn, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", client.Hostname, client.Port), client.Config)
|
var (
|
||||||
|
termWidth, termHeight int
|
||||||
|
)
|
||||||
|
conn, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", client.Hostname, client.Port), &client.Config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -127,14 +183,10 @@ func (client *Client) Shell() error {
|
||||||
ssh.ECHO: 1,
|
ssh.ECHO: 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
var termWidth, termHeight int
|
|
||||||
|
|
||||||
fd := os.Stdin.Fd()
|
fd := os.Stdin.Fd()
|
||||||
|
|
||||||
if term.IsTerminal(fd) {
|
if term.IsTerminal(fd) {
|
||||||
var oldState *term.State
|
oldState, err := term.MakeRaw(fd)
|
||||||
|
|
||||||
oldState, err = term.MakeRaw(fd)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -164,12 +216,51 @@ func (client *Client) Shell() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type Auth struct {
|
func NewExternalClient(sshBinaryPath, user, host string, port int, auth *Auth) (ExternalClient, error) {
|
||||||
Passwords []string
|
client := ExternalClient{
|
||||||
Keys []string
|
BinaryPath: sshBinaryPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base args take care of settings some options for us, e.g. don't use
|
||||||
|
// the authorized hosts file.
|
||||||
|
args := baseSSHArgs
|
||||||
|
|
||||||
|
// Specify which private keys to use to authorize the SSH request.
|
||||||
|
for _, privateKeyPath := range auth.Keys {
|
||||||
|
args = append(args, "-i", privateKeyPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set which port to use for SSH.
|
||||||
|
args = append(args, "-p", fmt.Sprintf("%d", port))
|
||||||
|
|
||||||
|
// Set the user and hostname, e.g. ubuntu@12.34.56.78
|
||||||
|
args = append(args, fmt.Sprintf("%s@%s", user, host))
|
||||||
|
|
||||||
|
client.BaseArgs = args
|
||||||
|
|
||||||
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type Output struct {
|
func (client ExternalClient) Output(command string) (string, error) {
|
||||||
Stdout io.Reader
|
args := append(client.BaseArgs, command)
|
||||||
Stderr io.Reader
|
|
||||||
|
cmd := exec.Command(client.BinaryPath, args...)
|
||||||
|
log.Debug(cmd)
|
||||||
|
|
||||||
|
// Allow piping of local things to remote commands.
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
return string(output), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (client ExternalClient) Shell() error {
|
||||||
|
cmd := exec.Command(client.BinaryPath, client.BaseArgs...)
|
||||||
|
log.Debug(cmd)
|
||||||
|
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
return cmd.Run()
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
#!/usr/bin/env bats
|
||||||
|
|
||||||
|
load helpers
|
||||||
|
|
||||||
|
export DRIVER=virtualbox
|
||||||
|
export NAME="bats-$DRIVER-test"
|
||||||
|
export MACHINE_STORAGE_PATH=/tmp/machine-bats-test-$DRIVER
|
||||||
|
|
||||||
|
# Basic smoke test for SSH backends
|
||||||
|
|
||||||
|
@test "$DRIVER: create SSH test box" {
|
||||||
|
run machine create -d $DRIVER $NAME
|
||||||
|
[[ "$status" -eq 0 ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "$DRIVER: test external ssh backend" {
|
||||||
|
run machine ssh $NAME -- df -h
|
||||||
|
[[ "$status" -eq 0 ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "$DRIVER: test command did what it purported to -- external ssh" {
|
||||||
|
run machine ssh $NAME echo foo
|
||||||
|
[[ "$output" == "foo" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "$DRIVER: test native ssh backend" {
|
||||||
|
run machine --native-ssh ssh $NAME -- df -h
|
||||||
|
[[ "$status" -eq 0 ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "$DRIVER: test command did what it purported to -- native ssh" {
|
||||||
|
run machine --native-ssh ssh $NAME echo foo
|
||||||
|
[[ "$output" == "foo" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "$DRIVER: remove machine after ssh backend test" {
|
||||||
|
run machine rm -f $NAME
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cleanup of machine store should always be the last 'test'
|
||||||
|
@test "$DRIVER: cleanup" {
|
||||||
|
run rm -rf $MACHINE_STORAGE_PATH
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue