github.com/greenboxal/deis@v1.12.1/deisctl/backend/fleet/ssh.go (about)

     1  package fleet
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"os/exec"
     7  	"strings"
     8  	"syscall"
     9  	"time"
    10  
    11  	"github.com/coreos/fleet/machine"
    12  	"github.com/coreos/fleet/ssh"
    13  )
    14  
    15  // SSH opens an interactive shell to a machine in the cluster
    16  func (c *FleetClient) SSH(name string) error {
    17  	sshClient, _, err := c.sshConnect(name)
    18  	if err != nil {
    19  		return err
    20  	}
    21  
    22  	defer sshClient.Close()
    23  	err = ssh.Shell(sshClient)
    24  	return err
    25  }
    26  
    27  func (c *FleetClient) SSHExec(name, cmd string) error {
    28  
    29  	conn, ms, err := c.sshConnect(name)
    30  	if err != nil {
    31  		return err
    32  	}
    33  
    34  	fmt.Printf("Executing '%s' on %s\n", cmd, ms.PublicIP)
    35  
    36  	err, _ = ssh.Execute(conn, cmd)
    37  	return err
    38  }
    39  
    40  func (c *FleetClient) sshConnect(name string) (*ssh.SSHForwardingClient, *machine.MachineState, error) {
    41  
    42  	timeout := time.Duration(Flags.SSHTimeout*1000) * time.Millisecond
    43  
    44  	ms, err := c.machineState(name)
    45  	if err != nil {
    46  		return nil, nil, err
    47  	}
    48  
    49  	// If name isn't a machine ID, try it as a unit instead
    50  	if ms == nil {
    51  		units, err := c.Units(name)
    52  
    53  		if err != nil {
    54  			return nil, nil, err
    55  		}
    56  
    57  		machID, err := c.findUnit(units[0])
    58  
    59  		if err != nil {
    60  			return nil, nil, err
    61  		}
    62  
    63  		ms, err = c.machineState(machID)
    64  
    65  		if err != nil || ms == nil {
    66  			return nil, nil, err
    67  		}
    68  	}
    69  
    70  	addr := ms.PublicIP
    71  
    72  	if tun := getTunnelFlag(); tun != "" {
    73  		sshClient, err := ssh.NewTunnelledSSHClient("core", tun, addr, getChecker(), false, timeout)
    74  		return sshClient, ms, err
    75  	}
    76  	sshClient, err := ssh.NewSSHClient("core", addr, getChecker(), false, timeout)
    77  	return sshClient, ms, err
    78  }
    79  
    80  // runCommand will attempt to run a command on a given machine. It will attempt
    81  // to SSH to the machine if it is identified as being remote.
    82  func (c *FleetClient) runCommand(cmd string, machID string) (retcode int) {
    83  	var err error
    84  	if machine.IsLocalMachineID(machID) {
    85  		retcode, err = c.runner.LocalCommand(cmd)
    86  		if err != nil {
    87  			fmt.Fprintf(c.errWriter, "Error running local command: %v\n", err)
    88  		}
    89  	} else {
    90  		ms, err := c.machineState(machID)
    91  		if err != nil || ms == nil {
    92  			fmt.Fprintf(c.errWriter, "Error getting machine IP: %v\n", err)
    93  		} else {
    94  			sshTimeout := time.Duration(Flags.SSHTimeout*1000) * time.Millisecond
    95  			retcode, err = c.runner.RemoteCommand(cmd, ms.PublicIP, sshTimeout)
    96  			if err != nil {
    97  				fmt.Fprintf(c.errWriter, "Error running remote command: %v\n", err)
    98  			}
    99  		}
   100  	}
   101  	return
   102  }
   103  
   104  type commandRunner interface {
   105  	LocalCommand(string) (int, error)
   106  	RemoteCommand(string, string, time.Duration) (int, error)
   107  }
   108  
   109  type sshCommandRunner struct{}
   110  
   111  // runLocalCommand runs the given command locally and returns any error encountered and the exit code of the command
   112  func (sshCommandRunner) LocalCommand(cmd string) (int, error) {
   113  	cmdSlice := strings.Split(cmd, " ")
   114  	osCmd := exec.Command(cmdSlice[0], cmdSlice[1:]...)
   115  	osCmd.Stderr = os.Stderr
   116  	osCmd.Stdout = os.Stdout
   117  	osCmd.Start()
   118  	err := osCmd.Wait()
   119  	if err != nil {
   120  		// Get the command's exit status if we can
   121  		if exiterr, ok := err.(*exec.ExitError); ok {
   122  			if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
   123  				return status.ExitStatus(), nil
   124  			}
   125  		}
   126  		// Otherwise, generic command error
   127  		return -1, err
   128  	}
   129  	return 0, nil
   130  }
   131  
   132  // runRemoteCommand runs the given command over SSH on the given IP, and returns
   133  // any error encountered and the exit status of the command
   134  func (sshCommandRunner) RemoteCommand(cmd string, addr string, timeout time.Duration) (exit int, err error) {
   135  	var sshClient *ssh.SSHForwardingClient
   136  	if tun := getTunnelFlag(); tun != "" {
   137  		sshClient, err = ssh.NewTunnelledSSHClient("core", tun, addr, getChecker(), false, timeout)
   138  	} else {
   139  		sshClient, err = ssh.NewSSHClient("core", addr, getChecker(), false, timeout)
   140  	}
   141  	if err != nil {
   142  		return -1, err
   143  	}
   144  
   145  	defer sshClient.Close()
   146  
   147  	err, exit = ssh.Execute(sshClient, cmd)
   148  	return
   149  }
   150  
   151  // findUnits returns the machine ID of a running unit
   152  func (c *FleetClient) findUnit(name string) (machID string, err error) {
   153  	u, err := c.Fleet.Unit(name)
   154  	switch {
   155  	case err != nil:
   156  		return "", fmt.Errorf("Error retrieving Unit %s: %v", name, err)
   157  	case suToGlobal(*u):
   158  		return "", fmt.Errorf("Unable to connect to global unit %s.\n", name)
   159  	case u == nil:
   160  		return "", fmt.Errorf("Unit %s does not exist.\n", name)
   161  	case u.CurrentState == "":
   162  		return "", fmt.Errorf("Unit %s does not appear to be running.\n", name)
   163  	}
   164  
   165  	return u.MachineID, nil
   166  }
   167  
   168  func (c *FleetClient) machineState(machID string) (*machine.MachineState, error) {
   169  	machines, err := c.Fleet.Machines()
   170  	if err != nil {
   171  		return nil, err
   172  	}
   173  	for _, ms := range machines {
   174  		if ms.ID == machID {
   175  			return &ms, nil
   176  		}
   177  	}
   178  	return nil, nil
   179  }
   180  
   181  // cachedMachineState makes a best-effort to retrieve the MachineState of the given machine ID.
   182  // It memoizes MachineState information for the life of a fleetctl invocation.
   183  // Any error encountered retrieving the list of machines is ignored.
   184  func (c *FleetClient) cachedMachineState(machID string) (ms *machine.MachineState) {
   185  	if c.machineStates == nil {
   186  		c.machineStates = make(map[string]*machine.MachineState)
   187  		ms, err := c.Fleet.Machines()
   188  		if err != nil {
   189  			return nil
   190  		}
   191  		for i, m := range ms {
   192  			c.machineStates[m.ID] = &ms[i]
   193  		}
   194  	}
   195  	return c.machineStates[machID]
   196  }