// ResetPassword sends request to broome to send password reset email func ResetPassword(email string) error { res, err := http.Get(Host + strings.Replace(ResetPasswordPath, "{email}", email, -1)) if err != nil { return errors.NewStackError(err) } defer res.Body.Close() resetRes := new(responses.Res) decoder := json.NewDecoder(res.Body) err = decoder.Decode(resetRes) if err != nil { return errors.NewStackError(err) } if resetRes.Status == "success" { return nil } if strings.Contains(resetRes.Error(), "not found") { return errors.ErrInvalidEmail } return errors.NewStackError(errors.ErrResetRequest) }
// Download retrieves the contents of a service. func Download(url string) (io.Reader, error) { var buf bytes.Buffer res, err := http.Get("http://" + url) if err != nil { return nil, errors.NewStackError(err) } defer res.Body.Close() if res.StatusCode != 200 { downloadRes := new(responses.Res) decoder := json.NewDecoder(res.Body) err = decoder.Decode(downloadRes) if err != nil { return nil, errors.NewStackError(err) } return nil, errors.NewStackError(downloadRes) } _, err = io.Copy(&buf, res.Body) if err != nil { return nil, errors.NewStackError(err) } return &buf, nil }
// GetDeveloper retrieves the developer for the given token. func GetDeveloper(token string) (*schemas.Developer, error) { res, err := http.Get(Host + strings.Replace(MePath, "{token}", token, -1)) if err != nil { return nil, errors.NewStackError(err) } defer res.Body.Close() // Decode json response. devRes := new(responses.DeveloperRes) decoder := json.NewDecoder(res.Body) err = decoder.Decode(devRes) if err != nil { return nil, errors.NewStackError(err) } // Found so return the developer. if devRes.Status == "found" { return devRes.Developer, nil } if strings.Contains(devRes.Error(), "Invalid Token") { return nil, errors.ErrInvalidToken } // Non "found" status indicates error. return nil, errors.NewStackError(devRes) }
// Close disables the terminals raw input. func (term *Terminal) Close() error { if term.isTerm { ret, _, err := setConsoleMode.Call(term.In.Fd(), uintptr(term.origMode)) if ret == 0 { return errors.NewStackError(err) } } return nil }
// CreateDeveloper creates a new developer. func CreateDeveloper(name, email, password string) (*schemas.Developer, error) { var body bytes.Buffer bodyReq := &LoginReq{Name: name, Email: email, Password: password} encoder := json.NewEncoder(&body) err := encoder.Encode(bodyReq) if err != nil { return nil, errors.NewStackError(err) } res, err := http.Post(Host+CreateDeveloperPath, "application/json", &body) if err != nil { return nil, errors.NewStackError(err) } defer res.Body.Close() // Decode json response. createRes := new(responses.DeveloperRes) decoder := json.NewDecoder(res.Body) err = decoder.Decode(createRes) if err != nil { return nil, errors.NewStackError(err) } // Created, just return token. if createRes.Status == "created" { return createRes.Developer, nil } // If the error is about developer existing, don't create stack error. if strings.Contains(createRes.Error(), "email already exists") { return nil, errors.ErrDeveloperExists } // Check for license issues. if strings.Contains(createRes.Error(), "License expired.") || strings.Contains(createRes.Error(), "License user limit reached.") { return nil, createRes } // Non "created" status indicates error, just return invalid. return nil, errors.NewStackError(createRes) }
// DevPing checks to see if the api is up and running and updates // the developers lastActive field. func DevPing(token string) error { endpoint := Host + strings.Replace(CheckPath, "{token}", token, -1) res, err := http.Get(endpoint) if err != nil { return errors.NewStackError(err) } defer res.Body.Close() return nil }
// simplePrompt is a fallback prompt without line editing support. func (term *Terminal) simplePrompt(prefix string) (string, error) { term.Out.Write([]byte(prefix)) line, err := inReader.ReadString('\n') line = strings.TrimRight(line, "\r\n ") line = strings.TrimLeft(line, " ") if err != nil { err = errors.NewStackError(err) } return line, err }
// TerminalSize retrieves the cols/rows for the terminal connected to out. func TerminalSize(out *os.File) (int, int, error) { csbi := new(consoleScreenBufferInfo) ret, _, err := getConsoleScreenBufferInfo.Call(out.Fd(), uintptr(unsafe.Pointer(csbi))) if ret == 0 { return 0, 0, errors.NewStackError(err) } // Results are always off by one. cols := csbi.window.right - csbi.window.left + 1 rows := csbi.window.bottom - csbi.window.top + 1 return int(cols), int(rows), nil }
// GetTokenByLogin creates a token for the given devs email. func GetTokenByLogin(email, password string) (string, error) { var body bytes.Buffer bodyReq := &LoginReq{Email: email, Password: password} encoder := json.NewEncoder(&body) err := encoder.Encode(bodyReq) if err != nil { return "", errors.NewStackError(err) } res, err := http.Post(Host+CreateTokenPath, "application/json", &body) if err != nil { return "", errors.NewStackError(err) } defer res.Body.Close() // Decode json response. createRes := new(responses.CreateTokenRes) decoder := json.NewDecoder(res.Body) err = decoder.Decode(createRes) if err != nil { return "", errors.NewStackError(err) } // Created, just return token. if createRes.Status == "created" { return createRes.Token, nil } // Check for license issues. if strings.Contains(createRes.Error(), "License expired.") || strings.Contains(createRes.Error(), "License user limit reached.") { return "", createRes } // Non "created" status indicates error, just return invalid. return "", errors.ErrInvalidLogin }
// NewTerminal creates a terminal and sets it to raw input mode. func NewTerminal() (*Terminal, error) { if inReader == nil { inReader = bufio.NewReader(os.Stdin) } term := &Terminal{In: os.Stdin, Out: os.Stdout} err := syscall.GetConsoleMode(syscall.Handle(term.In.Fd()), &term.origMode) if err != nil { return term, nil } mode := term.origMode term.isTerm = true // Set new mode flags. mode &^= (echoInputFlag | insertModeFlag | lineInputFlag | mouseInputFlag | processedInputFlag | windowInputFlag) ret, _, err := setConsoleMode.Call(term.In.Fd(), uintptr(mode)) if ret == 0 { return nil, errors.NewStackError(err) } return term, nil }
// prompt reads from in and parses ansi escapes writing to buf. func (term *Terminal) prompt(buf *Buffer, in io.Reader) (string, error) { cols, _, err := TerminalSize(buf.Out) if err != nil { return "", err } buf.Cols = cols input := bufio.NewReader(in) err = buf.Refresh() if err != nil { return "", errors.NewStackError(err) } for { char, _, err := input.ReadRune() if err != nil { return buf.String(), errors.NewStackError(err) } switch char { default: // Insert characters in the buffer. err = buf.Insert(char) if err != nil { return buf.String(), errors.NewStackError(err) } case tabKey, ctrlA, ctrlB, ctrlE, ctrlF, ctrlG, ctrlH, ctrlJ, ctrlK, ctrlN, ctrlO, ctrlP, ctrlQ, ctrlR, ctrlS, ctrlT, ctrlU, ctrlV, ctrlW, ctrlX, ctrlY, ctrlZ: // Skip. continue case returnKey, ctrlD: // Finished, return the buffer contents. err = buf.EndLine() if err != nil { err = errors.NewStackError(err) } return buf.String(), err case ctrlC: // Finished, return CTRL+C error. err = buf.EndLine() if err != nil { err = errors.NewStackError(err) } else { err = errors.ErrCTRLC } return buf.String(), err case backKey: // Backspace. err = buf.DelLeft() if err != nil { return buf.String(), errors.NewStackError(err) } case ctrlL: // Clear screen. err = buf.ClsScreen() if err != nil { return buf.String(), errors.NewStackError(err) } case escKey: // Functions like arrows, home, etc. esc := make([]byte, 2) _, err = input.Read(esc) if err != nil { return buf.String(), errors.NewStackError(err) } // Home, end. if esc[0] == 'O' { switch esc[1] { case 'H': // Home. err = buf.Start() if err != nil { return buf.String(), errors.NewStackError(err) } case 'F': // End. err = buf.End() if err != nil { return buf.String(), errors.NewStackError(err) } } continue } // Arrows, delete, pgup, pgdown, insert. if esc[0] == '[' { switch esc[1] { case 'A', 'B': // Up, down. continue case 'C': // Right. err = buf.Right() if err != nil { return buf.String(), errors.NewStackError(err) } case 'D': // Left. err = buf.Left() if err != nil { return buf.String(), errors.NewStackError(err) } } // Delete, pgup, pgdown, insert. if esc[1] > '0' && esc[1] < '7' { extEsc := make([]byte, 3) _, err = input.Read(extEsc) if err != nil { return buf.String(), errors.NewStackError(err) } if extEsc[0] == '~' { switch esc[1] { case '2', '5', '6': // Insert, pgup, pgdown. continue case '3': // Delete. err = buf.Del() if err != nil { return buf.String(), errors.NewStackError(err) } } } } } } } }
// Shell opens a shell connection on the servives ssh address. func Shell(app *schemas.Application, service *schemas.Service) error { // Make sure we're in raw mode. termState, err := terminal.MakeRaw(int(os.Stdin.Fd())) if err != nil { if prompt.IsNotTerminal(err) { return errors.ErrIORedirection } return errors.NewStackError(err) } defer terminal.Restore(int(os.Stdin.Fd()), termState) // Get terminal size. cols, rows, err := terminal.GetSize(int(os.Stdout.Fd())) if err != nil { if prompt.IsNotTerminal(err) { return errors.ErrIORedirection } return errors.NewStackError(err) } // Open an SSH connection to the address. config := &ssh.ClientConfig{User: "******", Auth: []ssh.AuthMethod{ ssh.Password("password"), }} client, err := ssh.Dial("tcp", service.SSHAddr, config) if err != nil { return errors.NewStackError(err) } defer client.Close() // Start a session on the client. session, err := client.NewSession() if err != nil { return errors.NewStackError(err) } defer session.Close() session.Stdout = prompt.NewAnsiWriter(os.Stdout) session.Stderr = prompt.NewAnsiWriter(os.Stderr) // Create a stdin pipe copying os.Stdin to it. stdin, err := session.StdinPipe() if err != nil { return errors.NewStackError(err) } defer stdin.Close() go func() { io.Copy(stdin, prompt.NewAnsiReader(os.Stdin)) }() log.Println("magenta", "Welcome to Bowery Services.") log.Println("magenta", "---------------------------------------------") log.Println("magenta", "Name:", service.Name) log.Println("magenta", "Application:", app.ID) log.Println("magenta", "Time:", time.Now()) log.Println("magenta", "---------------------------------------------") // Start a shell session. termModes := ssh.TerminalModes{ ssh.ECHO: 1, ssh.TTY_OP_ISPEED: 14400, ssh.TTY_OP_OSPEED: 14400, } err = session.RequestPty("xterm", rows, cols, termModes) if err == nil { err = session.Shell() } if err != nil { return errors.NewStackError(err) } // Wait for the session. err = session.Wait() if err != nil && err != io.EOF { // Ignore the error if it's an ExitError with an empty message, // this occurs when you do CTRL+c and then run exit cmd which isn't an // actual error. waitMsg, ok := err.(*ssh.ExitError) if ok && waitMsg.Msg() == "" { return nil } return errors.NewStackError(err) } return nil }
func updateRun(keen *keen.Client, rollbar *rollbar.Client, args ...string) int { keen.AddEvent("cli update", map[string]string{"installed": version.Version}) ver, err := api.GetVersion() if err != nil { rollbar.Report(err) return 1 } if ver == version.Version { log.Println("", "Bowery is up to date.") return 0 } log.Println("yellow", "Bowery is out of date. Updating to", ver, "now...") newVer, releaseNotes, err := api.DownloadNewVersion(ver) if err != nil { rollbar.Report(err) return 1 } exec, err := osext.Executable() if err != nil { rollbar.Report(errors.NewStackError(err)) return 1 } tempExec := filepath.Join(filepath.Dir(exec), ".old_bowery"+filepath.Ext(exec)) // Open exec, should fail if execing somewhere else. file, err := os.Open(exec) if err != nil { rollbar.Report(errors.ErrUpdatePerm) return 1 } file.Close() // Create the temp exec file to test io permissions. file, err = os.Create(tempExec) if err != nil { rollbar.Report(errors.ErrUpdatePerm) return 1 } file.Close() // Remove it which also removes any previous executables. err = os.RemoveAll(tempExec) if err != nil { rollbar.Report(err) return 1 } // Move the exec to a temp file so we can write the new one. err = os.Rename(exec, tempExec) if err != nil { rollbar.Report(err) return 1 } file, err = os.OpenFile(exec, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777) if err != nil { rollbar.Report(err) return 1 } defer file.Close() _, err = io.Copy(file, newVer) if err != nil { rollbar.Report(err) return 1 } log.Println("magenta", "Updated bowery to version", ver+".") if notes, err := ioutil.ReadAll(releaseNotes); err != nil { log.Println("", string(notes)) return 0 } else { rollbar.Report(err) return 1 } return 0 }
// Upload sends an upload request to a satellite endpoint, including // a tar upload file if given. func Upload(url, serviceName string, file *os.File) error { var body bytes.Buffer writer := multipart.NewWriter(&body) // Write file to multipart body if given. if file != nil { part, err := writer.CreateFormFile("file", "upload") if err != nil { return errors.NewStackError(err) } _, err = io.Copy(part, file) if err != nil { return err } } // Get current app and add fields for init, build, test, and start. state, err := db.GetState() if err != nil { return err } service := state.Config[serviceName] if service != nil { err = writer.WriteField("init", service.Init) if err == nil { err = writer.WriteField("build", service.Build) } if err == nil { err = writer.WriteField("test", service.Test) } if err == nil { err = writer.WriteField("start", service.Start) } if err == nil { err = writer.WriteField("path", service.Path) } if err == nil && service.Env != nil { envData, err := json.Marshal(service.Env) if err != nil { return err } err = writer.WriteField("env", string(envData)) } } if err == nil { err = writer.Close() } if err != nil { return err } res, err := http.Post("http://"+url, writer.FormDataContentType(), &body) if err != nil { if responses.IsRefusedConn(err) { err = errors.ErrSyncFailed } return errors.NewStackError(err) } defer res.Body.Close() // Decode json response. uploadRes := new(responses.Res) decoder := json.NewDecoder(res.Body) err = decoder.Decode(uploadRes) if err != nil { return errors.NewStackError(err) } // Created, so no error. if uploadRes.Status == "created" { return nil } return errors.NewStackError(uploadRes) }
// Update updates the given name with the status and path. func Update(url, serviceName, fullPath, name, status string) error { var body bytes.Buffer // Create writer, and write form fields. writer := multipart.NewWriter(&body) err := writer.WriteField("type", status) if err == nil { err = writer.WriteField("path", path.Join(strings.Split(name, string(filepath.Separator))...)) } if err != nil { return errors.NewStackError(err) } // Attach file if update or create. if status == "update" || status == "create" { file, err := os.Open(fullPath) if err != nil { return err } defer file.Close() stat, err := file.Stat() if err != nil { return err } // Add file mode to write with. err = writer.WriteField("mode", strconv.FormatUint(uint64(stat.Mode().Perm()), 10)) if err != nil { return errors.NewStackError(err) } part, err := writer.CreateFormFile("file", "upload") if err != nil { return errors.NewStackError(err) } _, err = io.Copy(part, file) if err != nil { return err } } // Get current app and add fields for init, build, test, and start. state, err := db.GetState() if err != nil { return err } if service := state.Config[serviceName]; service != nil { writer.WriteField("init", service.Init) writer.WriteField("build", service.Build) writer.WriteField("test", service.Test) writer.WriteField("start", service.Start) if service.Env != nil { envData, err := json.Marshal(service.Env) if err != nil { return err } err = writer.WriteField("env", string(envData)) } } if err = writer.Close(); err != nil { return err } req, err := http.NewRequest("PUT", "http://"+url, &body) if err != nil { return errors.NewStackError(err) } req.Header.Set("Content-Type", writer.FormDataContentType()) res, err := http.DefaultClient.Do(req) if err != nil { if responses.IsRefusedConn(err) { err = errors.ErrSyncFailed } return errors.NewStackError(err) } defer res.Body.Close() // Decode json response. uploadRes := new(responses.Res) decoder := json.NewDecoder(res.Body) err = decoder.Decode(uploadRes) if err != nil { return errors.NewStackError(err) } // Created, so no error. if uploadRes.Status == "updated" { return nil } return errors.NewStackError(uploadRes) }