github.com/inflatablewoman/deis@v1.0.1-0.20141111034523-a4511c46a6ce/deisctl/backend/fleet/ssh.go (about)

     1  package fleet
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"os/exec"
     7  	"strings"
     8  	"syscall"
     9  
    10  	"github.com/coreos/fleet/machine"
    11  	"github.com/coreos/fleet/ssh"
    12  )
    13  
    14  // runCommand will attempt to run a command on a given machine. It will attempt
    15  // to SSH to the machine if it is identified as being remote.
    16  func runCommand(cmd string, machID string) (retcode int) {
    17  	var err error
    18  	if machine.IsLocalMachineID(machID) {
    19  		err, retcode = runLocalCommand(cmd)
    20  		if err != nil {
    21  			fmt.Printf("Error running local command: %v\n", err)
    22  		}
    23  	} else {
    24  		ms, err := machineState(machID)
    25  		if err != nil || ms == nil {
    26  			fmt.Printf("Error getting machine IP: %v\n", err)
    27  		} else {
    28  			err, retcode = runRemoteCommand(cmd, ms.PublicIP)
    29  			if err != nil {
    30  				fmt.Printf("Error running remote command: %v\n", err)
    31  			}
    32  		}
    33  	}
    34  	return
    35  }
    36  
    37  // runLocalCommand runs the given command locally and returns any error encountered and the exit code of the command
    38  func runLocalCommand(cmd string) (error, int) {
    39  	cmdSlice := strings.Split(cmd, " ")
    40  	osCmd := exec.Command(cmdSlice[0], cmdSlice[1:]...)
    41  	osCmd.Stderr = os.Stderr
    42  	osCmd.Stdout = os.Stdout
    43  	osCmd.Start()
    44  	err := osCmd.Wait()
    45  	if err != nil {
    46  		// Get the command's exit status if we can
    47  		if exiterr, ok := err.(*exec.ExitError); ok {
    48  			if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
    49  				return nil, status.ExitStatus()
    50  			}
    51  		}
    52  		// Otherwise, generic command error
    53  		return err, -1
    54  	}
    55  	return nil, 0
    56  }
    57  
    58  // runRemoteCommand runs the given command over SSH on the given IP, and returns
    59  // any error encountered and the exit status of the command
    60  func runRemoteCommand(cmd string, addr string) (err error, exit int) {
    61  	var sshClient *ssh.SSHForwardingClient
    62  	if tun := getTunnelFlag(); tun != "" {
    63  		sshClient, err = ssh.NewTunnelledSSHClient("core", tun, addr, getChecker(), false)
    64  	} else {
    65  		sshClient, err = ssh.NewSSHClient("core", addr, getChecker(), false)
    66  	}
    67  	if err != nil {
    68  		return err, -1
    69  	}
    70  
    71  	defer sshClient.Close()
    72  
    73  	return ssh.Execute(sshClient, cmd)
    74  }
    75  
    76  func machineState(machID string) (*machine.MachineState, error) {
    77  	machines, err := cAPI.Machines()
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  	for _, ms := range machines {
    82  		if ms.ID == machID {
    83  			return &ms, nil
    84  		}
    85  	}
    86  	return nil, nil
    87  }
    88  
    89  // cachedMachineState makes a best-effort to retrieve the MachineState of the given machine ID.
    90  // It memoizes MachineState information for the life of a fleetctl invocation.
    91  // Any error encountered retrieving the list of machines is ignored.
    92  func cachedMachineState(machID string) (ms *machine.MachineState) {
    93  	if machineStates == nil {
    94  		machineStates = make(map[string]*machine.MachineState)
    95  		ms, err := cAPI.Machines()
    96  		if err != nil {
    97  			return nil
    98  		}
    99  		for i, m := range ms {
   100  			machineStates[m.ID] = &ms[i]
   101  		}
   102  	}
   103  	return machineStates[machID]
   104  }