func (i *Instance) Run(command string, s *Streams) error { if s == nil { s = &Streams{} } var sc *ssh.Client err := sshAttempts.Run(func() (err error) { if s.Stderr != nil { fmt.Fprintf(s.Stderr, "Attempting to ssh to %s:22...\n", i.IP) } sc, err = i.DialSSH() return }) if err != nil { return err } defer sc.Close() sess, err := sc.NewSession() sess.Stdin = s.Stdin sess.Stdout = s.Stdout sess.Stderr = s.Stderr if err := sess.Run(command); err != nil { return fmt.Errorf("failed to run command on %s: %s", i.IP, err) } return nil }
func (c *BaseCluster) bootstrap() error { c.SendLog("Running bootstrap") if c.SSHKey == nil { return errors.New("No SSHKey found") } // bootstrap only needs to run on one instance ipAddress := c.InstanceIPs[0] sshConfig, err := c.sshConfig() if err != nil { return nil } attempts := 0 maxAttempts := 3 var sshConn *ssh.Client for { sshConn, err = ssh.Dial("tcp", ipAddress+":22", sshConfig) if err != nil { if attempts < maxAttempts { attempts += 1 time.Sleep(time.Second) continue } return err } break } defer sshConn.Close() sess, err := sshConn.NewSession() if err != nil { return err } stdout, err := sess.StdoutPipe() if err != nil { return err } sess.Stderr = os.Stderr if err := sess.Start(fmt.Sprintf("CLUSTER_DOMAIN=%s flynn-host bootstrap --json", c.Domain.Name)); err != nil { c.uploadDebugInfo(sshConfig, ipAddress) return err } var keyData struct { Key string `json:"data"` } var loginTokenData struct { Token string `json:"data"` } var controllerCertData struct { Pin string `json:"pin"` CACert string `json:"ca_cert"` } output := json.NewDecoder(stdout) for { var stepRaw json.RawMessage if err := output.Decode(&stepRaw); err != nil { if err == io.EOF { break } return err } var step stepInfo if err := json.Unmarshal(stepRaw, &step); err != nil { return err } if step.State == "error" { c.uploadDebugInfo(sshConfig, ipAddress) return fmt.Errorf("bootstrap: %s %s error: %s", step.ID, step.Action, step.Error) } c.SendLog(fmt.Sprintf("%s: %s", step.ID, step.State)) if step.State != "done" { continue } switch step.ID { case "controller-key": if err := json.Unmarshal(*step.Data, &keyData); err != nil { return err } case "controller-cert": if err := json.Unmarshal(*step.Data, &controllerCertData); err != nil { return err } case "dashboard-login-token": if err := json.Unmarshal(*step.Data, &loginTokenData); err != nil { return err } case "log-complete": break } } if keyData.Key == "" || controllerCertData.Pin == "" { return err } c.ControllerKey = keyData.Key c.ControllerPin = controllerCertData.Pin c.CACert = controllerCertData.CACert c.DashboardLoginToken = loginTokenData.Token if err := c.saveField("ControllerKey", c.ControllerKey); err != nil { return err } if err := c.saveField("ControllerPin", c.ControllerPin); err != nil { return err } if err := c.saveField("CACert", c.CACert); err != nil { return err } if err := c.saveField("DashboardLoginToken", c.DashboardLoginToken); err != nil { return err } if err := sess.Wait(); err != nil { return err } return nil }
func (c *BaseCluster) instanceRunCmdWithClient(cmd string, sshConn *ssh.Client, user, ipAddress string) error { c.SendLog(fmt.Sprintf("Running `%s` on %s", cmd, ipAddress)) sudoPrompt := "<SUDO_PROMPT>" cmd = strings.Replace(cmd, "sudo ", fmt.Sprintf("sudo -S --prompt='%s\n' ", sudoPrompt), -1) sess, err := sshConn.NewSession() if err != nil { return err } stdin, err := sess.StdinPipe() if err != nil { return err } stdout, err := sess.StdoutPipe() if err != nil { return err } stderr, err := sess.StderrPipe() if err != nil { return err } if err = sess.Start(cmd); err != nil { return err } doneChan := make(chan struct{}) defer close(doneChan) go func() { scanner := bufio.NewScanner(stdout) for scanner.Scan() { select { case _, _ = <-doneChan: return default: } c.SendLog(scanner.Text()) } }() go func() { passwordPrompt := func(msg string, useCache bool) { c.passwordPromptMtx.Lock() var password string var ok bool if useCache { password, ok = c.passwordCache[ipAddress] } if !ok || password == "" { password = c.PromptProtectedInput(msg) c.passwordCache[ipAddress] = password } else { c.SendLog("Using cached password") } if _, err := fmt.Fprintf(stdin, "%s\n", password); err != nil { c.SendLog(err.Error()) } c.passwordPromptMtx.Unlock() } scanner := bufio.NewScanner(stderr) var prevLine string for scanner.Scan() { select { case _, _ = <-doneChan: return default: } line := scanner.Text() c.SendLog(line) msg := fmt.Sprintf("Please enter your sudo password for %s@%s", user, ipAddress) if prevLine == sudoPrompt && line == "Sorry, try again." { passwordPrompt(fmt.Sprintf("%s\n%s", line, msg), false) } else if line == sudoPrompt { passwordPrompt(msg, true) } prevLine = line } }() return sess.Wait() }
func (c *BaseCluster) instanceRunCmdWithClient(cmd string, sshConn *ssh.Client, user, ipAddress string) error { c.SendLog(fmt.Sprintf("Running `%s` on %s", cmd, ipAddress)) sudoPrompt := "<SUDO_PROMPT>" cmd = strings.Replace(cmd, "sudo ", fmt.Sprintf("sudo -S --prompt='%s\n' ", sudoPrompt), -1) sess, err := sshConn.NewSession() if err != nil { return err } stdin, err := sess.StdinPipe() if err != nil { return err } stdout, err := sess.StdoutPipe() if err != nil { return err } stderr, err := sess.StderrPipe() if err != nil { return err } if err = sess.Start(cmd); err != nil { return err } doneChan := make(chan struct{}) defer close(doneChan) go func() { scanner := bufio.NewScanner(stdout) var prevLine string for scanner.Scan() { select { case _, _ = <-doneChan: return default: } line := scanner.Text() c.SendLog(line) // Handle prompt to remove Flynn when installing using --clean flynnRemovePromptMsgs := []string{ "About to stop Flynn and remove all existing data", "Are you sure this is what you want?", } if strings.Contains(prevLine, flynnRemovePromptMsgs[0]) && strings.Contains(line, flynnRemovePromptMsgs[1]) { answer := "no" if c.YesNoPrompt("If Flynn is already installed, it will be stopped and removed along with all data before installing the latest version. Would you like to proceed?") { answer = "yes" } else { c.Abort() } if _, err := fmt.Fprintf(stdin, "%s\n", answer); err != nil { c.SendLog(err.Error()) } } prevLine = line } }() go func() { passwordPrompt := func(msg string, useCache bool) { c.passwordPromptMtx.Lock() var password string var ok bool if useCache { password, ok = c.passwordCache[ipAddress] } if !ok || password == "" { password = c.PromptProtectedInput(msg) c.passwordCache[ipAddress] = password } else { c.SendLog("Using cached password") } if _, err := fmt.Fprintf(stdin, "%s\n", password); err != nil { c.SendLog(err.Error()) } c.passwordPromptMtx.Unlock() } scanner := bufio.NewScanner(stderr) var prevLine string for scanner.Scan() { select { case _, _ = <-doneChan: return default: } line := scanner.Text() c.SendLog(line) msg := fmt.Sprintf("Please enter your sudo password for %s@%s", user, ipAddress) if prevLine == sudoPrompt && line == "Sorry, try again." { passwordPrompt(fmt.Sprintf("%s\n%s", line, msg), false) } else if line == sudoPrompt { passwordPrompt(msg, true) } prevLine = line } }() return sess.Wait() }