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 }