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