func TestGetCommitBranchHash(t *testing.T) { Setup() defer Teardown() commit, err := database.GetCommitBranchHash("develop", "5f32ec7b08dfe3a097c1a5316de5b5069fb35ff9", 2) if err != nil { t.Error(err) } if commit.ID != 5 { t.Errorf("Exepected ID %d, got %d", 5, commit.ID) } if commit.Branch != "develop" { t.Errorf("Exepected Branch %s, got %s", "develop", commit.Branch) } if commit.Hash != "5f32ec7b08dfe3a097c1a5316de5b5069fb35ff9" { t.Errorf("Exepected Hash %s, got %s", "5f32ec7b08dfe3a097c1a5316de5b5069fb35ff9", commit.Hash) } if commit.Status != "Success" { t.Errorf("Exepected Status %s, got %s", "Success", commit.Status) } }
// Returns the combined stdout / stderr for an individual Build. func BuildStatus(w http.ResponseWriter, r *http.Request, repo *Repo) error { branch := r.FormValue("branch") if branch == "" { branch = "master" } hash := r.FormValue(":commit") labl := r.FormValue(":label") // get the commit from the database commit, err := database.GetCommitBranchHash(branch, hash, repo.ID) if err != nil { return err } // get the build from the database build, err := database.GetBuildSlug(labl, commit.ID) if err != nil { return err } build_result := BuildResult{build.Status} return RenderJson(w, build_result) }
// Display a specific Commit. func CommitShow(w http.ResponseWriter, r *http.Request, u *User, repo *Repo) error { branch := r.FormValue("branch") if branch == "" { branch = "master" } hash := r.FormValue(":commit") labl := r.FormValue(":label") // get the commit from the database commit, err := database.GetCommitBranchHash(branch, hash, repo.ID) if err != nil { return err } // get the builds from the database. a commit can have // multiple sub-builds (or matrix builds) builds, err := database.ListBuilds(commit.ID) if err != nil { return err } admin, err := database.IsRepoAdmin(u, repo) if err != nil { return err } data := struct { User *User Repo *Repo Commit *Commit Build *Build Builds []*Build Token string IsAdmin bool }{u, repo, commit, builds[0], builds, "", admin} // get the specific build requested by the user. instead // of a database round trip, we can just loop through the // list and extract the requested build. for _, b := range builds { if b.Slug == labl { data.Build = b break } } // generate a token to connect with the websocket // handler and stream output, if the build is running. data.Token = channel.Token(fmt.Sprintf( "%s/%s/%s/commit/%s/%s/builds/%s", repo.Host, repo.Owner, repo.Name, commit.Branch, commit.Hash, builds[0].Slug)) // render the repository template. return RenderTemplate(w, "repo_commit.html", &data) }
// CommitRebuild re-queues a previously built commit. It finds the existing // commit and build and injects them back into the queue. If the commit // doesn't exist or has no builds, or if a build label has been passed but // can't be located, it prints an error. Otherwise, it adds the build/commit // to the queue and redirects back to the commit page. func (h *CommitRebuildHandler) CommitRebuild(w http.ResponseWriter, r *http.Request, u *User, repo *Repo) error { hash := r.FormValue(":commit") labl := r.FormValue(":label") host := r.FormValue(":host") branch := r.FormValue("branch") if branch == "" { branch = "master" } // get the commit from the database commit, err := database.GetCommitBranchHash(branch, hash, repo.ID) if err != nil { return err } // get the builds from the database. a commit can have // multiple sub-builds (or matrix builds) builds, err := database.ListBuilds(commit.ID) if err != nil { return err } build := builds[0] if labl != "" { // get the specific build requested by the user. instead // of a database round trip, we can just loop through the // list and extract the requested build. build = nil for _, b := range builds { if b.Slug == labl { build = b break } } } if build == nil { return fmt.Errorf("Could not find build: %s", labl) } h.queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build}) if labl != "" { http.Redirect(w, r, fmt.Sprintf("/%s/%s/%s/commit/%s/build/%s?branch=%s", host, repo.Owner, repo.Name, hash, labl, branch), http.StatusSeeOther) } else { http.Redirect(w, r, fmt.Sprintf("/%s/%s/%s/commit/%s?branch=%s", host, repo.Owner, repo.Name, hash, branch), http.StatusSeeOther) } return nil }
// Returns the combined stdout / stderr for an individual Build. func BuildOut(w http.ResponseWriter, r *http.Request, u *User, repo *Repo) error { branch := r.FormValue("branch") if branch == "" { branch = "master" } hash := r.FormValue(":commit") labl := r.FormValue(":label") // get the commit from the database commit, err := database.GetCommitBranchHash(branch, hash, repo.ID) if err != nil { return err } // get the build from the database build, err := database.GetBuildSlug(labl, commit.ID) if err != nil { return err } return RenderText(w, build.Stdout, http.StatusOK) }
// Processes a generic POST-RECEIVE GitHub hook and // attempts to trigger a build. func (h *HookHandler) HookGithub(w http.ResponseWriter, r *http.Request) error { // handle github ping if r.Header.Get("X-Github-Event") == "ping" { return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK) } // if this is a pull request route // to a different handler if r.Header.Get("X-Github-Event") == "pull_request" { h.PullRequestHookGithub(w, r) return nil } // get the payload of the message // this should contain a json representation of the // repository and commit details payload := r.FormValue("payload") // parse the github Hook payload hook, err := github.ParseHook([]byte(payload)) if err != nil { println("could not parse hook:", err.Error()) return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) } // make sure this is being triggered because of a commit // and not something like a tag deletion or whatever if hook.IsTag() || hook.IsGithubPages() || hook.IsHead() == false || hook.IsDeleted() { return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK) } // get the repo from the URL repoId := r.FormValue("id") // get the repo from the database, return error if not found repo, err := database.GetRepoSlug(repoId) if err != nil { return RenderText(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) } // Get the user that owns the repository user, err := database.GetUser(repo.UserID) if err != nil { return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) } // Verify that the commit doesn't already exist. // We should never build the same commit twice. _, err = database.GetCommitBranchHash(hook.Branch(), hook.Head.Id, repo.ID) if err != nil && err != sql.ErrNoRows { println("commit already exists") return RenderText(w, http.StatusText(http.StatusBadGateway), http.StatusBadGateway) } // we really only need: // * repo owner // * repo name // * repo host (github) // * commit hash // * commit timestamp // * commit branch // * commit message // * commit author // * pull request // once we have this data we could just send directly to the queue // and let it handle everything else commit := &Commit{} commit.RepoID = repo.ID commit.Branch = hook.Branch() commit.Hash = hook.Head.Id commit.Status = "Pending" commit.Created = time.Now().UTC() // extract the author and message from the commit // this is kind of experimental, since I don't know // what I'm doing here. if hook.Head != nil && hook.Head.Author != nil { commit.Message = hook.Head.Message commit.Timestamp = hook.Head.Timestamp commit.SetAuthor(hook.Head.Author.Email) } else if hook.Commits != nil && len(hook.Commits) > 0 && hook.Commits[0].Author != nil { commit.Message = hook.Commits[0].Message commit.Timestamp = hook.Commits[0].Timestamp commit.SetAuthor(hook.Commits[0].Author.Email) } // get the github settings from the database settings := database.SettingsMust() // get the drone.yml file from GitHub client := github.New(user.GithubToken) client.ApiUrl = settings.GitHubApiUrl content, err := client.Contents.FindRef(repo.Owner, repo.Name, ".drone.yml", commit.Hash) if err != nil { msg := "No .drone.yml was found in this repository. You need to add one.\n" if err := saveFailedBuild(commit, msg); err != nil { return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) } // decode the content. Note: Not sure this will ever happen...it basically means a GitHub API issue raw, err := content.DecodeContent() if err != nil { msg := "Could not decode the yaml from GitHub. Check that your .drone.yml is a valid yaml file.\n" if err := saveFailedBuild(commit, msg); err != nil { return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) } // parse the build script buildscript, err := script.ParseBuild(raw, repo.Params) if err != nil { msg := "Could not parse your .drone.yml file. It needs to be a valid drone yaml file.\n\n" + err.Error() + "\n" if err := saveFailedBuild(commit, msg); err != nil { return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) } // save the commit to the database if err := database.SaveCommit(commit); err != nil { return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } // save the build to the database build := &Build{} build.Slug = "1" // TODO build.CommitID = commit.ID build.Created = time.Now().UTC() build.Status = "Pending" if err := database.SaveBuild(build); err != nil { return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } // notify websocket that a new build is pending //realtime.CommitPending(repo.UserID, repo.TeamID, repo.ID, commit.ID, repo.Private) //realtime.BuildPending(repo.UserID, repo.TeamID, repo.ID, commit.ID, build.ID, repo.Private) h.queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build, Script: buildscript}) //Push(repo, commit, build, buildscript) // OK! return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK) }