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 }