// WsUser will upgrade the connection to a Websocket and will stream // all events to the browser pertinent to the authenticated user. If the user // is not authenticated, only public events are streamed. func WsUser(c web.C, w http.ResponseWriter, r *http.Request) { var ctx = context.FromC(c) var user = ToUser(c) // upgrade the websocket ws, err := upgrader.Upgrade(w, r, nil) if err != nil { w.WriteHeader(http.StatusBadRequest) return } // register a channel for global events channel := pubsub.Register(ctx, "_global") sub := channel.Subscribe() ticker := time.NewTicker(pingPeriod) defer func() { ticker.Stop() sub.Close() ws.Close() }() go func() { for { select { case msg := <-sub.Read(): work, ok := msg.(*worker.Work) if !ok { break } // user must have read access to the repository // in order to pass this message along if role, err := datastore.GetPerm(ctx, user, work.Repo); err != nil || role.Read == false { break } ws.SetWriteDeadline(time.Now().Add(writeWait)) err := ws.WriteJSON(work) if err != nil { ws.Close() return } case <-sub.CloseNotify(): ws.Close() return case <-ticker.C: ws.SetWriteDeadline(time.Now().Add(writeWait)) err := ws.WriteMessage(websocket.PingMessage, []byte{}) if err != nil { ws.Close() return } } } }() readWebsocket(ws) }
func (d *Docker) Do(c context.Context, r *worker.Work) { // ensure that we can recover from any panics to // avoid bringing down the entire application. defer func() { if e := recover(); e != nil { log.Printf("%s: %s", e, debug.Stack()) } }() // mark the build as Started and update the database r.Commit.Status = model.StatusStarted r.Commit.Started = time.Now().UTC().Unix() datastore.PutCommit(c, r.Commit) // notify all listeners that the build is started commitc := pubsub.Register(c, "_global") commitc.Publish(r) stdoutc := pubsub.RegisterOpts(c, r.Commit.ID, pubsub.ConsoleOpts) defer pubsub.Unregister(c, r.Commit.ID) // create a special buffer that will also // write to a websocket channel buf := pubsub.NewBuffer(stdoutc) // parse the parameters and build script. The script has already // been parsed in the hook, so we can be confident it will succeed. // that being said, we should clean this up params, err := r.Repo.ParamMap() if err != nil { log.Printf("Error parsing PARAMS for %s/%s, Err: %s", r.Repo.Owner, r.Repo.Name, err.Error()) } script, err := script.ParseBuild(script.Inject(r.Commit.Config, params)) if err != nil { log.Printf("Error parsing YAML for %s/%s, Err: %s", r.Repo.Owner, r.Repo.Name, err.Error()) } // append private parameters to the environment // variable section of the .drone.yml file, iff // this is not a pull request (for security purposes) if params != nil && (r.Repo.Private || len(r.Commit.PullRequest) == 0) { for k, v := range params { script.Env = append(script.Env, k+"="+v) } } path := r.Repo.Host + "/" + r.Repo.Owner + "/" + r.Repo.Name repo := &repo.Repo{ Name: path, Path: r.Repo.CloneURL, Branch: r.Commit.Branch, Commit: r.Commit.Sha, PR: r.Commit.PullRequest, Dir: filepath.Join("/var/cache/drone/src", git.GitPath(script.Git, path)), Depth: git.GitDepth(script.Git), } // send all "started" notifications if script.Notifications == nil { script.Notifications = ¬ify.Notification{} } script.Notifications.Send(&model.Request{ User: r.User, Repo: r.Repo, Commit: r.Commit, Host: r.Host, }) // create an instance of the Docker builder builder := build.New(d.docker) builder.Build = script builder.Repo = repo builder.Stdout = buf builder.Key = []byte(r.Repo.PrivateKey) builder.Timeout = time.Duration(r.Repo.Timeout) * time.Second builder.Privileged = r.Repo.Privileged // run the build err = builder.Run() // update the build status based on the results // from the build runner. switch { case err != nil: r.Commit.Status = model.StatusError log.Printf("Error building %s, Err: %s", r.Commit.Sha, err) buf.WriteString(err.Error()) case builder.BuildState == nil: r.Commit.Status = model.StatusFailure case builder.BuildState.ExitCode != 0: r.Commit.Status = model.StatusFailure default: r.Commit.Status = model.StatusSuccess } // calcualte the build finished and duration details and // update the commit r.Commit.Finished = time.Now().UTC().Unix() r.Commit.Duration = (r.Commit.Finished - r.Commit.Started) datastore.PutCommit(c, r.Commit) blobstore.Put(c, filepath.Join(r.Repo.Host, r.Repo.Owner, r.Repo.Name, r.Commit.Branch, r.Commit.Sha), buf.Bytes()) // notify all listeners that the build is finished commitc.Publish(r) // send all "finished" notifications script.Notifications.Send(&model.Request{ User: r.User, Repo: r.Repo, Commit: r.Commit, Host: r.Host, }) }