func decryptKey(payload []byte, iv []byte, key []byte, newCypherFunc func([]byte) (cipher.Block, error)) ([]byte, error) { decryptedPayload := make([]byte, len(payload)) block, err := newCypherFunc(key) if err != nil { return nil, errgo.Mask(err) } decrypter := cipher.NewCBCDecrypter(block, iv) decrypter.CryptBlocks(decryptedPayload, payload) debug.Println("End of private key payload:", decryptedPayload[len(decryptedPayload)-50:]) debug.Println("Length of payload:", len(decryptedPayload)) decryptedPayload = PKCS5Or7Unpadding(decryptedPayload) debug.Println("Length of payload after trimming:", len(decryptedPayload)) return decryptedPayload, nil }
func (ctx *runContext) exitCode() (int, error) { if ctx.attachURL == "" { return -1, errgo.New("No attach URL to connect to") } req, err := http.NewRequest("GET", ctx.attachURL+"/wait", nil) if err != nil { return -1, errgo.Mask(err, errgo.Any) } req.SetBasicAuth("", config.AuthenticatedUser.AuthenticationToken) res, err := http.DefaultClient.Do(req) if err != nil { return -1, errgo.Mask(err) } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { return -1, errgo.Notef(err, "fail to read body when getting exit code") } debug.Println("exit code body:", string(body)) waitRes := map[string]int{} err = json.NewDecoder(bytes.NewBuffer(body)).Decode(&waitRes) if err != nil { return -1, errgo.Notef(err, "invalid response when getting exit code") } return waitRes["exit_code"], nil }
func loginWithSsh(identity string) error { debug.Println("Login through SSH, identity:", identity) client, _, err := netssh.Connect(identity) if err != nil { return errgo.Notef(err, "fail to connect to SSH server") } channel, reqs, err := client.OpenChannel("session", []byte{}) if err != nil { return errgo.Notef(err, "fail to open SSH channel") } defer client.Close() _, err = channel.SendRequest("*****@*****.**", false, []byte{}) if err != nil { return errgo.Notef(err, "SSH authentication request fails") } req := <-reqs if req == nil { return errgo.Newf("invalid response from auth request") } if req.Type != "*****@*****.**" { return errgo.Newf("invalid response from SSH server, type is %v", req.Type) } payload := req.Payload if len(payload) == 0 { return errgo.Newf("invalid response from SSH server") } return loginWithApiKey(string(payload)) }
func (a *CliAuthenticator) LoadAuth() (*scalingo.User, error) { file, err := os.OpenFile(C.AuthFile, os.O_RDONLY, 0600) if os.IsNotExist(err) { return Auth() } if err != nil { return nil, errgo.Mask(err, errgo.Any) } var authConfig auth.ConfigData if err := json.NewDecoder(file).Decode(&authConfig); err != nil { file.Close() return nil, errgo.Mask(err, errgo.Any) } file.Close() if authConfig.AuthDataVersion == "" { debug.Println("auth config should be updated") err = authConfig.MigrateToV1() if err != nil { return nil, errgo.Mask(err) } err = writeAuthFile(&authConfig) if err != nil { return nil, errgo.Mask(err) } debug.Println("auth config has been updated to V1") } var configPerHost auth.ConfigPerHostV1 err = json.Unmarshal(authConfig.AuthConfigPerHost, &configPerHost) if err != nil { return nil, errgo.Mask(err) } if user, ok := configPerHost[C.apiHost]; !ok { return Auth() } else { if user == nil { return Auth() } return user, nil } }
func FlagAppAutoComplete(c *cli.Context) bool { apps, err := appsList() if err != nil { debug.Println("fail to get apps list:", err) return false } for _, app := range apps { fmt.Println(app.Name) } return true }
func appsList() ([]*scalingo.App, error) { var ( err error apps []*scalingo.App ) apps, err = appsAutoCompleteCache() if err != nil { debug.Println("fail to get applications autocomplete cache make GET request", err) apps, err = scalingo.AppsList() if err != nil || len(apps) == 0 { return nil, errgo.Mask(err) } err = writeAppsAutoCompleteCache(apps) if err != nil { debug.Println("fail to write applications autocomplete cache", err) } } return apps, nil }
func (ctx *runContext) uploadFile(endpoint string, file string) error { body := new(bytes.Buffer) name := filepath.Base(file) multipartFile := multipart.NewWriter(body) writer, err := multipartFile.CreateFormFile("file", name) if err != nil { return errgo.Mask(err, errgo.Any) } fd, err := os.OpenFile(file, os.O_RDONLY, 0600) if err != nil { return errgo.Mask(err, errgo.Any) } _, err = stdio.Copy(writer, fd) if err != nil { return errgo.Mask(err, errgo.Any) } err = fd.Close() if err != nil { return errgo.Mask(err, errgo.Any) } err = multipartFile.Close() if err != nil { return errgo.Mask(err, errgo.Any) } req, err := http.NewRequest("POST", endpoint, body) if err != nil { return errgo.Mask(err, errgo.Any) } req.SetBasicAuth("", config.AuthenticatedUser.AuthenticationToken) req.Header.Set("Content-Type", multipartFile.FormDataContentType()) fmt.Fprintln(ctx.waitingTextOutputWriter, "Upload", file, "to container.") debug.Println("Endpoint:", req.URL) res, err := httpclient.Do(req) if err != nil { return errgo.Mask(err, errgo.Any) } defer res.Body.Close() if res.StatusCode != 200 { b, _ := ioutil.ReadAll(res.Body) return errgo.Newf("Invalid return code %v (%s)", res.Status, strings.TrimSpace(string(b))) } return nil }
func ParseJSON(res *http.Response, data interface{}) error { body, err := ioutil.ReadAll(res.Body) if err != nil { return errgo.Newf("fail to read body of request %v, %v", res.Request, err) } debug.Println(string(body)) err = json.Unmarshal(body, data) if err != nil { return errgo.Newf("fail to parse JSON of request %v, %v", res.Request, err) } return nil }
func scalingoRemotes(directory string) (gitremote.Remotes, error) { matchedRemotes := make(gitremote.Remotes, 0) remotes, err := gitremote.List(directory) if err != nil { return nil, err } for _, remote := range remotes { matched, err := regexp.Match(".*scalingo.com:.*.git", []byte(remote.URL)) if err == nil && matched { debug.Println("[AppDetect] GIT remote found:", remote) matchedRemotes = append(matchedRemotes, remote) } } return matchedRemotes, nil }
func HandleSignal(s os.Signal, socket net.Conn, runUrl string) { switch s { case syscall.SIGINT: socket.Write([]byte{0x03}) case syscall.SIGQUIT: socket.Write([]byte{0x1c}) case syscall.SIGTSTP: socket.Write([]byte{0x1a}) case syscall.SIGWINCH: err := updateTtySize(runUrl) if err != nil { debug.Println("WARN: Error when updating terminal size:", err) } } }
func streamLogs(logsRawURL string, filter string) error { var ( err error buffer [20480]byte event WSEvent ) logsURL, err := url.Parse(logsRawURL) if err != nil { return errgo.Mask(err, errgo.Any) } if logsURL.Scheme == "https" { logsURL.Scheme = "wss" } else { logsURL.Scheme = "ws" } logsURLString := fmt.Sprintf("%s&stream=true", logsURL.String()) if filter != "" { logsURLString = fmt.Sprintf("%s&filter=%s", logsURLString, filter) } conn, err := websocket.Dial(logsURLString, "", "http://scalingo-cli.local/"+config.Version) if err != nil { return errgo.Mask(err, errgo.Any) } for { n, err := conn.Read(buffer[:]) if err != nil { return errgo.Mask(err, errgo.Any) } debug.Println(string(buffer[:n])) err = json.Unmarshal(buffer[:n], &event) if err != nil { return errgo.Notef(err, "invalid JSON %v", string(buffer[:n])) } switch event.Type { case "ping": case "log": fmt.Println(strings.TrimSpace(event.Log)) } } }
func ScalingoRepo(directory string, remoteName string) (string, error) { remotes, err := gitremote.List(directory) if err != nil { return "", err } for i := 0; i < 2; i++ { for _, remote := range remotes { if remote.Name == remoteName { matched, err := regexp.Match(".*scalingo.com:.*.git", []byte(remote.URL)) if err == nil && matched { debug.Println("[AppDetect] GIT remote found:", remote) return filepath.Base(strings.TrimSuffix(remote.Repository(), ".git")), nil } } } remoteName = "scalingo-" + remoteName } return "", errgo.Newf("Scalingo GIT remote hasn't been found") }
func CurrentApp(c *cli.Context) string { var repoName string if c.GlobalString("app") != "<name>" { repoName = c.GlobalString("app") } else if c.String("app") != "<name>" { repoName = c.String("app") } else if os.Getenv("SCALINGO_APP") != "" { repoName = os.Getenv("SCALINGO_APP") } else if dir, ok := DetectGit(); ok { repoName, _ = ScalingoRepo(dir, c.GlobalString("remote")) } if repoName == "" { fmt.Println("Unable to find the application name, please use --app flag.") os.Exit(1) } debug.Println("[AppDetect] App name is", repoName) return repoName }
func ScalingoRepoComplete(dir string) []string { var repos []string remotes, err := scalingoRemotes(dir) if err != nil { debug.Println("[AppDetectCompletion] fail to get scalingo remotes in", dir) return repos } for _, remote := range remotes { if strings.HasPrefix(remote.Name, "scalingo-") { repos = append(repos, remote.Name[9:]) } else { repos = append(repos, remote.Name) } } return repos }
func NewRequestFailedError(res *http.Response, req *APIRequest) error { debug.Println("APIRequest Error:", res.StatusCode, req.Method, req.Endpoint) defer res.Body.Close() switch res.StatusCode { case 400: var badRequestError BadRequestError err := ParseJSON(res, &badRequestError) if err != nil { return errgo.Mask(err, errgo.Any) } return &RequestFailedError{res.StatusCode, badRequestError, req} case 401: return &RequestFailedError{res.StatusCode, errgo.New("unauthorized - you are not authorized to do this operation"), req} case 402: var paymentRequiredErr PaymentRequiredError err := ParseJSON(res, &paymentRequiredErr) if err != nil { return errgo.Mask(err, errgo.Any) } return &RequestFailedError{res.StatusCode, paymentRequiredErr, req} case 404: var notFoundErr NotFoundError err := ParseJSON(res, ¬FoundErr) if err != nil { return errgo.Mask(err, errgo.Any) } return &RequestFailedError{res.StatusCode, notFoundErr, req} case 422: var unprocessableError UnprocessableEntity err := ParseJSON(res, &unprocessableError) if err != nil { return errgo.Mask(err, errgo.Any) } return &RequestFailedError{res.StatusCode, unprocessableError, req} case 500: return &RequestFailedError{res.StatusCode, errgo.New("server internal error - our team has been notified"), req} default: return &RequestFailedError{res.StatusCode, fmt.Errorf("invalid status from server: %v", res.Status), req} } }
func Logs(appName string, stream bool, n int, filter string) error { err := checkFilter(appName, filter) if err != nil { return errgo.Mask(err, errgo.Any) } res, err := scalingo.LogsURL(appName) if err != nil { return errgo.Mask(err, errgo.Any) } defer res.Body.Close() if res.StatusCode != 200 { return errgo.Newf("fail to query logs: %s", res.Status) } body, err := ioutil.ReadAll(res.Body) if err != nil { return errgo.Mask(err, errgo.Any) } debug.Println("[API-Response] ", string(body)) logsRes := &LogsRes{} if err = json.Unmarshal(body, &logsRes); err != nil { return errgo.Mask(err, errgo.Any) } if err = dumpLogs(logsRes.LogsURL, n, filter); err != nil { return errgo.Mask(err, errgo.Any) } if stream { if err = streamLogs(logsRes.LogsURL, filter); err != nil { return errgo.Mask(err, errgo.Any) } } return nil }
func Scale(app string, sync bool, types []string) error { var ( size string containers []scalingo.Container modificator byte err error ) scaleParams := &scalingo.AppsScaleParams{} for _, t := range types { splitT := strings.Split(t, ":") if len(splitT) != 2 && len(splitT) != 3 { return errgo.Newf("%s is invalid, format is <type>:<amount>[:<size>]", t) } typeName, typeAmount := splitT[0], splitT[1] if len(splitT) == 3 { size = splitT[2] } if typeAmount[0] == '-' || typeAmount[0] == '+' { modificator = typeAmount[0] typeAmount = typeAmount[1:] if size != "" { return errgo.Newf("%s is invalid, can't use relative modificator with size, change the size first", t) } if containers == nil { c := config.ScalingoClient() containers, err = c.AppsPs(app) if err != nil { return errgo.Notef(err, "fail to get list of running containers") } debug.Println("get container list", containers) } } amount, err := strconv.ParseInt(typeAmount, 10, 32) if err != nil { return errgo.Newf("%s in %s should be an integer", typeAmount, t) } newContainerConfig := scalingo.Container{Name: typeName, Size: size} if modificator != 0 { for _, container := range containers { if container.Name == typeName { if modificator == '-' { newContainerConfig.Amount = container.Amount - int(amount) } else if modificator == '+' { newContainerConfig.Amount = container.Amount + int(amount) } break } } } else { newContainerConfig.Amount = int(amount) } scaleParams.Containers = append(scaleParams.Containers, newContainerConfig) } c := config.ScalingoClient() res, err := c.AppsScale(app, scaleParams) if err != nil { return errgo.Mask(err) } defer res.Body.Close() var scaleRes ScaleRes err = scalingo.ParseJSON(res, &scaleRes) if err != nil { return errgo.Mask(err) } fmt.Printf("Your application is being scaled to:\n") for _, ct := range scaleRes.Containers { fmt.Println(io.Indent(fmt.Sprintf("%s: %d - %s", ct.Name, ct.Amount, ct.Size), 2)) } if !sync { return nil } err = handleOperation(app, res) if err != nil { return errgo.Mask(err) } fmt.Println("Your application has been scaled.") return nil }
func Run(opts RunOpts) error { c := config.ScalingoClient() firstReadDone := make(chan struct{}) ctx := &runContext{ waitingTextOutputWriter: os.Stderr, stdinCopyFunc: stdio.Copy, stdoutCopyFunc: io.CopyWithFirstReadChan(firstReadDone), } if opts.Type != "" { processes, err := c.AppsPs(opts.App) if err != nil { return errgo.Mask(err) } for _, p := range processes { if p.Name == opts.Type { opts.Cmd = strings.Split(p.Command, " ") } } if strings.Join(opts.Cmd, "") == "" { return errgo.New("no such type") } } if opts.CmdEnv == nil { opts.CmdEnv = []string{} } if opts.Files == nil { opts.Files = []string{} } if opts.Silent { ctx.waitingTextOutputWriter = new(bytes.Buffer) } if opts.StdinCopyFunc != nil { ctx.stdinCopyFunc = opts.StdinCopyFunc } if opts.StdoutCopyFunc != nil { ctx.stdoutCopyFunc = opts.StdoutCopyFunc } env, err := ctx.buildEnv(opts.CmdEnv) if err != nil { return errgo.Mask(err, errgo.Any) } err = ctx.validateFiles(opts.Files) if err != nil { return errgo.Mask(err, errgo.Any) } res, err := c.Run(opts.App, opts.Cmd, env) if err != nil { return errgo.Mask(err, errgo.Any) } runStruct := make(map[string]interface{}) scalingo.ParseJSON(res, &runStruct) debug.Printf("%+v\n", runStruct) if res.StatusCode == http.StatusNotFound { return errgo.Newf("application %s not found", opts.App) } var ok bool ctx.attachURL, ok = runStruct["attach_url"].(string) if !ok { return errgo.New("unexpected answer from server") } debug.Println("Run Service URL is", ctx.attachURL) if len(opts.Files) > 0 { err := ctx.uploadFiles(ctx.attachURL+"/files", opts.Files) if err != nil { return err } } fmt.Fprintf(ctx.waitingTextOutputWriter, "-----> Connecting to container [%v-%v]... ", runStruct["container"].(map[string]interface{})["type"], runStruct["container"].(map[string]interface{})["type_index"], ) attachSpinner := io.NewSpinner(ctx.waitingTextOutputWriter) attachSpinner.PostHook = func() { var displayCmd string if opts.DisplayCmd != "" { displayCmd = opts.DisplayCmd } else { displayCmd = strings.Join(opts.Cmd, " ") } fmt.Fprintf(ctx.waitingTextOutputWriter, "\n-----> Process '%v' is starting... ", displayCmd) } go attachSpinner.Start() res, socket, err := ctx.connectToRunServer() if err != nil { return errgo.Mask(err, errgo.Any) } if res.StatusCode != http.StatusOK { return errgo.Newf("Fail to attach: %s", res.Status) } if term.IsATTY(os.Stdin) { if err := term.MakeRaw(os.Stdin); err != nil { return errgo.Mask(err, errgo.Any) } } stopSignalsMonitoring := make(chan bool) defer close(stopSignalsMonitoring) go func() { signals.CatchQuitSignals = false signals := run.NotifiedSignals() defer close(signals) go run.NofityTermSizeUpdate(signals) for { select { case s := <-signals: run.HandleSignal(s, socket, ctx.attachURL) case <-stopSignalsMonitoring: signal.Stop(signals) return } } }() attachSpinner.Stop() startSpinner := io.NewSpinnerWithStopChan(ctx.waitingTextOutputWriter, firstReadDone) startSpinner.PostHook = func() { fmt.Fprintf(ctx.waitingTextOutputWriter, "\n\n") } go startSpinner.Start() go func() { _, err := ctx.stdinCopyFunc(socket, os.Stdin) if err != nil { debug.Println("error after reading stdin", err) } else { // Send EOT when stdin returns // 'scalingo run < file' socket.Write([]byte("\x04")) } }() _, err = ctx.stdoutCopyFunc(os.Stdout, socket) stopSignalsMonitoring <- true if term.IsATTY(os.Stdin) { if err := term.Restore(os.Stdin); err != nil { return errgo.Mask(err, errgo.Any) } } exitCode, err := ctx.exitCode() if err != nil { return errgo.Mask(err, errgo.Any) } os.Exit(exitCode) return nil }
func Stream(appName string) error { c := config.ScalingoClient() app, err := c.AppsShow(appName) if err != nil { return errgo.Mask(err, errgo.Any) } debug.Println("Opening socket to: " + app.Links.DeploymentsStream) conn, err := c.DeploymentStream(app.Links.DeploymentsStream) if err != nil { return errgo.Mask(err, errgo.Any) } var event deployEvent oldStatus := "" for { err := websocket.JSON.Receive(conn, &event) if err != nil { conn.Close() if err == stdio.EOF { debug.Println("Remote server broke the connection, reconnecting") for err != nil { conn, err = c.DeploymentStream(app.Links.DeploymentsStream) time.Sleep(time.Second * 1) } continue } else { return errgo.Mask(err, errgo.Any) } } else { switch event.Type { case "ping": case "log": var logData logData err := json.Unmarshal(event.Data, &logData) if err != nil { config.C.Logger.Println(err) } else { fmt.Println("[LOG] " + strings.TrimSpace(logData.Content)) } case "status": var statusData statusData err := json.Unmarshal(event.Data, &statusData) if err != nil { config.C.Logger.Println(err) } else { if oldStatus == "" { fmt.Println("[STATUS] New status: " + statusData.Content) } else { fmt.Println("[STATUS] New status: " + oldStatus + " → " + statusData.Content) } oldStatus = statusData.Content } case "new": oldStatus = "" var newData map[string]*scalingo.Deployment err := json.Unmarshal(event.Data, &newData) if err != nil { config.C.Logger.Println(err) } else { fmt.Println("[NEW] New deploy: " + newData["deployment"].ID + " from " + newData["deployment"].User.Username) } } } } }
func CollaboratorsAddAutoComplete(c *cli.Context) error { var err error appName := CurrentAppCompletion(c) if appName == "" { return nil } apps, err := appsList() if err != nil { debug.Println("fail to get apps list:", err) return nil } currentAppCollaborators, err := scalingo.CollaboratorsList(appName) if err != nil { return nil } var apiError error = nil ch := make(chan string) var wg sync.WaitGroup wg.Add(len(apps)) for _, app := range apps { go func(app *scalingo.App) { defer wg.Done() appCollaborators, erro := scalingo.CollaboratorsList(app.Name) if erro != nil { config.C.Logger.Println(erro.Error()) apiError = erro return } for _, col := range appCollaborators { ch <- col.Email } }(app) } setEmails := make(map[string]bool) go func() { for content := range ch { setEmails[content] = true } }() wg.Wait() close(ch) if apiError != nil { return nil } for email, _ := range setEmails { isAlreadyCollaborator := false for _, currentAppCol := range currentAppCollaborators { if currentAppCol.Email == email { isAlreadyCollaborator = true } } if !isAlreadyCollaborator { fmt.Println(email) } } return nil }
func Run(opts RunOpts) error { if opts.CmdEnv == nil { opts.CmdEnv = []string{} } if opts.Files == nil { opts.Files = []string{} } if opts.StdinCopyFunc == nil { opts.StdinCopyFunc = io.Copy } if opts.StdoutCopyFunc == nil { opts.StdoutCopyFunc = io.Copy } env, err := buildEnv(opts.CmdEnv) if err != nil { return errgo.Mask(err, errgo.Any) } err = validateFiles(opts.Files) if err != nil { return errgo.Mask(err, errgo.Any) } res, err := api.Run(opts.App, opts.Cmd, env) if err != nil { return errgo.Mask(err, errgo.Any) } runStruct := make(map[string]interface{}) api.ParseJSON(res, &runStruct) debug.Printf("%+v\n", runStruct) if res.StatusCode == http.StatusNotFound { return errgo.Newf("application %s not found", opts.App) } attachURL, ok := runStruct["attach_url"].(string) if !ok { return errgo.New("unexpected answer from server") } debug.Println("Run Service URL is", attachURL) if len(opts.Files) > 0 { err := uploadFiles(attachURL+"/files", opts.Files) if err != nil { return err } } res, socket, err := connectToRunServer(attachURL) if err != nil { return errgo.Mask(err, errgo.Any) } if res.StatusCode != http.StatusOK { return errgo.Newf("Fail to attach: %s", res.Status) } if err := term.MakeRaw(os.Stdin); err != nil { return errgo.Mask(err, errgo.Any) } stopSignalsMonitoring := make(chan bool) defer close(stopSignalsMonitoring) go func() { signals.CatchQuitSignals = false signals := run.NotifiedSignals() defer close(signals) go run.NofityTermSizeUpdate(signals) for { select { case s := <-signals: run.HandleSignal(s, socket, attachURL) case <-stopSignalsMonitoring: signal.Stop(signals) return } } }() go opts.StdinCopyFunc(socket, os.Stdin) _, err = opts.StdinCopyFunc(os.Stdout, socket) stopSignalsMonitoring <- true if err := term.Restore(os.Stdin); err != nil { return errgo.Mask(err, errgo.Any) } return nil }
func Tunnel(app string, dbEnvVar string, identity string, port int) error { environ, err := api.VariablesListWithoutAlias(app) if err != nil { return errgo.Mask(err) } dbUrlStr := dbEnvVarValue(dbEnvVar, environ) if dbUrlStr == "" { return errgo.Newf("no such environment variable: %s", dbEnvVar) } dbUrl, err := url.Parse(dbUrlStr) if err != nil { return errgo.Notef(err, "invalid database 'URL': %s", dbUrlStr) } fmt.Printf("Building tunnel to %s\n", dbUrl.Host) var privateKeys []ssh.Signer if identity == "ssh-agent" { var agentConnection io.Closer privateKeys, agentConnection, err = sshkeys.ReadPrivateKeysFromAgent() if err != nil { return errgo.Mask(err) } defer agentConnection.Close() } if len(privateKeys) == 0 { identity = sshkeys.DefaultKeyPath privateKey, err := sshkeys.ReadPrivateKey(identity) if err != nil { return errgo.Mask(err) } privateKeys = append(privateKeys, privateKey) } debug.Println("Identity used:", identity) var client *ssh.Client for _, privateKey := range privateKeys { sshConfig := &ssh.ClientConfig{ User: "******", Auth: []ssh.AuthMethod{ssh.PublicKeys(privateKey)}, } client, err = ssh.Dial("tcp", config.C.SshHost, sshConfig) if err == nil { break } else { config.C.Logger.Println("Fail to connect to the SSH server", err) } } if client == nil { return errgo.Newf("No authentication method has succeeded, please use the flag '-i /path/to/private/key' to specify your private key") } tcpAddr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("localhost:%d", port)) if err != nil { return errgo.Mask(err) } sock, err := net.ListenTCP("tcp", tcpAddr) if err != nil { return errgo.Mask(err) } defer sock.Close() fmt.Printf("You can access your database on '%v'\n", sock.Addr()) go startIDGenerator() errs := make(chan error) for { select { case err := <-errs: return errgo.Mask(err) default: } connToTunnel, err := sock.Accept() if err != nil { return errgo.Mask(err) } go handleConnToTunnel(client, dbUrl, connToTunnel, errs) } }