github.com/technosophos/deis@v1.7.1-0.20150915173815-f9005256004b/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 }