// Helper method for saving a failed build or commit in the case where it never starts to build. // This can happen if the yaml is bad or doesn't exist. func saveFailedBuild(commit *Commit, msg string) error { // Set the commit to failed commit.Status = "Failure" commit.Created = time.Now().UTC() commit.Finished = commit.Created commit.Duration = 0 if err := database.SaveCommit(commit); err != nil { return err } // save the build to the database build := &Build{} build.Slug = "1" // TODO: This should not be hardcoded build.CommitID = commit.ID build.Created = time.Now().UTC() build.Finished = build.Created commit.Duration = 0 build.Status = "Failure" build.Stdout = msg if err := database.SaveBuild(build); err != nil { return err } // TODO: Should the status be Error instead of Failure? // TODO: Do we need to update the branch table too? return nil }
func TestSaveBbuild(t *testing.T) { Setup() defer Teardown() // get the build we plan to update build, err := database.GetBuild(1) if err != nil { t.Error(err) } // update fields build.Status = "Failing" // update the database if err := database.SaveBuild(build); err != nil { t.Error(err) } // get the updated build updatedBuild, err := database.GetBuild(1) if err != nil { t.Error(err) } if build.ID != updatedBuild.ID { t.Errorf("Exepected ID %d, got %d", updatedBuild.ID, build.ID) } if build.Slug != updatedBuild.Slug { t.Errorf("Exepected Slug %s, got %s", updatedBuild.Slug, build.Slug) } if build.Status != updatedBuild.Status { t.Errorf("Exepected Status %s, got %s", updatedBuild.Status, build.Status) } }
// execute will execute the build task and persist // the results to the datastore. func (w *worker) execute(task *BuildTask) error { // we need to be sure that we can recover // from any sort panic that could occur // to avoid brining down the entire application defer func() { if e := recover(); e != nil { task.Build.Finished = time.Now().UTC() task.Commit.Finished = time.Now().UTC() task.Build.Duration = task.Build.Finished.Unix() - task.Build.Started.Unix() task.Commit.Duration = task.Build.Finished.Unix() - task.Build.Started.Unix() task.Commit.Status = "Error" task.Build.Status = "Error" database.SaveBuild(task.Build) database.SaveCommit(task.Commit) } }() // update commit and build status task.Commit.Status = "Started" task.Build.Status = "Started" task.Build.Started = time.Now().UTC() task.Commit.Started = time.Now().UTC() // persist the commit to the database if err := database.SaveCommit(task.Commit); err != nil { return err } // persist the build to the database if err := database.SaveBuild(task.Build); err != nil { return err } // get settings settings, _ := database.GetSettings() // notification context context := ¬ify.Context{ Repo: task.Repo, Commit: task.Commit, Host: settings.URL().String(), } // send all "started" notifications if task.Script.Notifications != nil { task.Script.Notifications.Send(context) } // Send "started" notification to Github if err := updateGitHubStatus(task.Repo, task.Commit); err != nil { log.Printf("error updating github status: %s\n", err.Error()) } // make sure a channel exists for the repository, // the commit, and the commit output (TODO) reposlug := fmt.Sprintf("%s/%s/%s", task.Repo.Host, task.Repo.Owner, task.Repo.Name) commitslug := fmt.Sprintf("%s/%s/%s/commit/%s", task.Repo.Host, task.Repo.Owner, task.Repo.Name, task.Commit.Hash) consoleslug := fmt.Sprintf("%s/%s/%s/commit/%s/builds/%s", task.Repo.Host, task.Repo.Owner, task.Repo.Name, task.Commit.Hash, task.Build.Slug) channel.Create(reposlug) channel.Create(commitslug) channel.CreateStream(consoleslug) // notify the channels that the commit and build started channel.SendJSON(reposlug, task.Commit) channel.SendJSON(commitslug, task.Build) var buf = &bufferWrapper{channel: consoleslug} // append private parameters to the environment // variable section of the .drone.yml file, iff // this is not a pull request (for security purposes) if task.Repo.Params != nil && len(task.Commit.PullRequest) == 0 { for k, v := range task.Repo.Params { task.Script.Env = append(task.Script.Env, k+"="+v) } } defer func() { // update the status of the commit using the // GitHub status API. if err := updateGitHubStatus(task.Repo, task.Commit); err != nil { log.Printf("error updating github status: %s\n", err.Error()) } }() // execute the build passed, buildErr := w.runBuild(task, buf) task.Build.Finished = time.Now().UTC() task.Commit.Finished = time.Now().UTC() task.Build.Duration = task.Build.Finished.UnixNano() - task.Build.Started.UnixNano() task.Commit.Duration = task.Build.Finished.UnixNano() - task.Build.Started.UnixNano() task.Commit.Status = "Success" task.Build.Status = "Success" task.Build.Stdout = buf.buf.String() // if exit code != 0 set to failure if passed { task.Commit.Status = "Failure" task.Build.Status = "Failure" if buildErr != nil && task.Build.Stdout == "" { // TODO: If you wanted to have very friendly error messages, you could do that here task.Build.Stdout = buildErr.Error() + "\n" } } // persist the build to the database if err := database.SaveBuild(task.Build); err != nil { return err } // persist the commit to the database if err := database.SaveCommit(task.Commit); err != nil { return err } // notify the channels that the commit and build finished channel.SendJSON(reposlug, task.Commit) channel.SendJSON(commitslug, task.Build) channel.Close(consoleslug) // send all "finished" notifications if task.Script.Notifications != nil { task.Script.Notifications.Send(context) } return nil }
// Processes a generic POST-RECEIVE hook and // attempts to trigger a build. func Hook(w http.ResponseWriter, r *http.Request) error { // if this is a pull request route // to a different handler if r.Header.Get("X-Github-Event") == "pull_request" { PullRequestHook(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") 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.GetCommitHash(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 drone.yml file from GitHub client := github.New(user.GithubToken) 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) 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) 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) }
func PullRequestHook(w http.ResponseWriter, r *http.Request) { // get the payload of the message // this should contain a json representation of the // repository and commit details payload := r.FormValue("payload") println("GOT PR HOOK") println(payload) hook, err := github.ParsePullRequestHook([]byte(payload)) if err != nil { RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } // ignore these if hook.Action != "opened" && hook.Action != "synchronize" { RenderText(w, http.StatusText(http.StatusOK), http.StatusOK) return } // 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 { RenderText(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } // Get the user that owns the repository user, err := database.GetUser(repo.UserID) if err != nil { RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } // Verify that the commit doesn't already exist. // We should enver build the same commit twice. _, err = database.GetCommitHash(hook.PullRequest.Head.Sha, repo.ID) if err != nil && err != sql.ErrNoRows { RenderText(w, http.StatusText(http.StatusBadGateway), http.StatusBadGateway) return } /////////////////////////////////////////////////////// commit := &Commit{} commit.RepoID = repo.ID commit.Branch = hook.PullRequest.Head.Ref commit.Hash = hook.PullRequest.Head.Sha commit.Status = "Pending" commit.Created = time.Now().UTC() commit.Gravatar = hook.PullRequest.User.GravatarId commit.PullRequest = strconv.Itoa(hook.Number) commit.Message = hook.PullRequest.Title // label := p.PullRequest.Head.Labe // get the drone.yml file from GitHub client := github.New(user.GithubToken) content, err := client.Contents.FindRef(repo.Owner, repo.Name, ".drone.yml", commit.Hash) // TODO should this really be the hash?? if err != nil { println(err.Error()) RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } // decode the content raw, err := content.DecodeContent() if err != nil { RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } // parse the build script buildscript, err := script.ParseBuild(raw) if err != nil { // TODO if the YAML is invalid we should create a commit record // with an ERROR status so that the user knows why a build wasn't // triggered in the system RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) return } // save the commit to the database if err := database.SaveCommit(commit); err != nil { RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } // 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 { RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } // notify websocket that a new build is pending // TODO we should, for consistency, just put this inside Queue.Add() queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build, Script: buildscript}) // OK! RenderText(w, http.StatusText(http.StatusOK), http.StatusOK) }
// execute will execute the build task and persist // the results to the datastore. func (b *BuildTask) execute() error { // we need to be sure that we can recover // from any sort panic that could occur // to avoid brining down the entire application defer func() { if e := recover(); e != nil { b.Build.Finished = time.Now().UTC() b.Commit.Finished = time.Now().UTC() b.Build.Duration = b.Build.Finished.Unix() - b.Build.Started.Unix() b.Commit.Duration = b.Build.Finished.Unix() - b.Build.Started.Unix() b.Commit.Status = "Error" b.Build.Status = "Error" database.SaveBuild(b.Build) database.SaveCommit(b.Commit) } }() // update commit and build status b.Commit.Status = "Started" b.Build.Status = "Started" b.Build.Started = time.Now().UTC() b.Commit.Started = time.Now().UTC() // persist the commit to the database if err := database.SaveCommit(b.Commit); err != nil { return err } // persist the build to the database if err := database.SaveBuild(b.Build); err != nil { return err } // get settings settings, _ := database.GetSettings() // notification context context := ¬ification.Context{ Repo: b.Repo, Commit: b.Commit, Host: settings.URL().String(), } // send all "started" notifications if b.Script.Notifications != nil { b.Script.Notifications.Send(context) } // make sure a channel exists for the repository, // the commit, and the commit output (TODO) reposlug := fmt.Sprintf("%s/%s/%s", b.Repo.Host, b.Repo.Owner, b.Repo.Name) commitslug := fmt.Sprintf("%s/%s/%s/commit/%s", b.Repo.Host, b.Repo.Owner, b.Repo.Name, b.Commit.Hash) consoleslug := fmt.Sprintf("%s/%s/%s/commit/%s/builds/%s", b.Repo.Host, b.Repo.Owner, b.Repo.Name, b.Commit.Hash, b.Build.Slug) channel.Create(reposlug) channel.Create(commitslug) channel.CreateStream(consoleslug) // notify the channels that the commit and build started channel.SendJSON(reposlug, b.Commit) channel.SendJSON(commitslug, b.Build) var buf = &bufferWrapper{channel: consoleslug} // append private parameters to the environment // variable section of the .drone.yml file if b.Repo.Params != nil { for k, v := range b.Repo.Params { b.Script.Env = append(b.Script.Env, k+"="+v) } } // execute the build builder := bldr.Builder{} builder.Build = b.Script builder.Repo = &r.Repo{Path: b.Repo.URL, Branch: b.Commit.Branch, Commit: b.Commit.Hash, PR: b.Commit.PullRequest, Dir: filepath.Join("/var/cache/drone/src", b.Repo.Slug)} builder.Key = []byte(b.Repo.PrivateKey) builder.Stdout = buf builder.Timeout = 300 * time.Minute defer func() { // update the status of the commit using the // GitHub status API. if err := updateGitHubStatus(b.Repo, b.Commit); err != nil { log.Printf("error updating github status: %s\n", err.Error()) } }() buildErr := builder.Run() b.Build.Finished = time.Now().UTC() b.Commit.Finished = time.Now().UTC() b.Build.Duration = b.Build.Finished.UnixNano() - b.Build.Started.UnixNano() b.Commit.Duration = b.Build.Finished.UnixNano() - b.Build.Started.UnixNano() b.Commit.Status = "Success" b.Build.Status = "Success" b.Build.Stdout = buf.buf.String() // if exit code != 0 set to failure if builder.BuildState == nil || builder.BuildState.ExitCode != 0 { b.Commit.Status = "Failure" b.Build.Status = "Failure" if buildErr != nil && b.Build.Stdout == "" { // TODO: If you wanted to have very friendly error messages, you could do that here b.Build.Stdout = buildErr.Error() + "\n" } } // persist the build to the database if err := database.SaveBuild(b.Build); err != nil { return err } // persist the commit to the database if err := database.SaveCommit(b.Commit); err != nil { return err } // notify the channels that the commit and build finished channel.SendJSON(reposlug, b.Commit) channel.SendJSON(commitslug, b.Build) channel.Close(consoleslug) // add the smtp address to the notificaitons //if b.Script.Notifications != nil && b.Script.Notifications.Email != nil { // b.Script.Notifications.Email.SetServer(settings.SmtpServer, settings.SmtpPort, // settings.SmtpUsername, settings.SmtpPassword, settings.SmtpAddress) //} // send all "finished" notifications if b.Script.Notifications != nil { b.sendEmail(context) // send email from queue, not from inside /build/script package b.Script.Notifications.Send(context) } return nil }
// Processes a generic POST-RECEIVE Bitbucket hook and // attempts to trigger a build. func (h *BitbucketHandler) Hook(w http.ResponseWriter, r *http.Request) error { // get the payload from the request payload := r.FormValue("payload") // parse the post-commit hook hook, err := bitbucket.ParseHook([]byte(payload)) if err != nil { return err } // 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.GetCommitHash(hook.Commits[len(hook.Commits)-1].Hash, repo.ID) if err != nil && err != sql.ErrNoRows { return RenderText(w, http.StatusText(http.StatusBadGateway), http.StatusBadGateway) } commit := &Commit{} commit.RepoID = repo.ID commit.Branch = hook.Commits[len(hook.Commits)-1].Branch commit.Hash = hook.Commits[len(hook.Commits)-1].Hash commit.Status = "Pending" commit.Created = time.Now().UTC() commit.Message = hook.Commits[len(hook.Commits)-1].Message commit.Timestamp = time.Now().UTC().String() commit.SetAuthor(hook.Commits[len(hook.Commits)-1].Author) // get the github settings from the database settings := database.SettingsMust() // create the Bitbucket client client := bitbucket.New( settings.BitbucketKey, settings.BitbucketSecret, user.BitbucketToken, user.BitbucketSecret, ) // get the yaml from the database raw, err := client.Sources.Find(repo.Owner, repo.Name, commit.Hash, ".drone.yml") if err != nil { return RenderText(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) } // parse the build script buildscript, err := script.ParseBuild([]byte(raw.Data), 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) } // send the build to the queue h.queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build, Script: buildscript}) // OK! return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK) }
func (g *GitlabHandler) PullRequestHook(p *gogitlab.HookPayload, repo *Repo, user *User) error { obj := p.ObjectAttributes // Gitlab may trigger multiple hooks upon updating merge requests status // only build when it was just opened and the merge hasn't been checked yet. if !(obj.State == "opened" && obj.MergeStatus == "unchecked") { fmt.Println("Ignore GitLab Merge Requests") return nil } settings := database.SettingsMust() client := gogitlab.NewGitlab(settings.GitlabApiUrl, g.apiPath, user.GitlabToken) // GitLab merge-requests hook doesn't include repository data. // Have to fetch it manually src, err := client.RepoBranch(strconv.Itoa(obj.SourceProjectId), obj.SourceBranch) if err != nil { return err } _, err = database.GetCommitHash(src.Commit.Id, repo.ID) if err != nil && err != sql.ErrNoRows { fmt.Println("commit already exists") return err } commit := &Commit{} commit.RepoID = repo.ID commit.Branch = src.Name commit.Hash = src.Commit.Id commit.Status = "Pending" commit.Created = time.Now().UTC() commit.PullRequest = strconv.Itoa(obj.IId) commit.Message = src.Commit.Message commit.Timestamp = src.Commit.AuthoredDateRaw commit.SetAuthor(src.Commit.Author.Email) buildscript, err := client.RepoRawFile(strconv.Itoa(obj.SourceProjectId), commit.Hash, ".drone.yml") 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 fmt.Errorf("Failed to save build: %q", err) } return fmt.Errorf("Error to fetch build script: %q", err) } // save the commit to the database if err := database.SaveCommit(commit); err != nil { return fmt.Errorf("Failed to save commit: %q", err) } // save the build to the database build := &Build{} build.Slug = "1" // TODO build.CommitID = commit.ID build.Created = time.Now().UTC() build.Status = "Pending" build.BuildScript = string(buildscript) if err := database.SaveBuild(build); err != nil { return fmt.Errorf("Failed to save build: %q", err) } g.queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build}) return nil }
func (g *GitlabHandler) Hook(w http.ResponseWriter, r *http.Request) error { rID := r.FormValue("id") repo, err := database.GetRepoSlug(rID) if err != nil { return RenderText(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) } user, err := database.GetUser(repo.UserID) if err != nil { return RenderText(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) } payload, _ := ioutil.ReadAll(r.Body) parsed, err := gogitlab.ParseHook(payload) if err != nil { return err } if parsed.ObjectKind == "merge_request" { fmt.Println(string(payload)) if err := g.PullRequestHook(parsed, repo, user); err != nil { return err } return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK) } if len(parsed.After) == 0 { return RenderText(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) } _, err = database.GetCommitHash(parsed.After, repo.ID) if err != nil && err != sql.ErrNoRows { fmt.Println("commit already exists") return RenderText(w, http.StatusText(http.StatusBadGateway), http.StatusBadGateway) } commit := &Commit{} commit.RepoID = repo.ID commit.Branch = parsed.Branch() commit.Hash = parsed.After commit.Status = "Pending" commit.Created = time.Now().UTC() head := parsed.Head() commit.Message = head.Message commit.Timestamp = head.Timestamp if head.Author != nil { commit.SetAuthor(head.Author.Email) } else { commit.SetAuthor(parsed.UserName) } // get the github settings from the database settings := database.SettingsMust() // get the drone.yml file from GitHub client := gogitlab.NewGitlab(settings.GitlabApiUrl, g.apiPath, user.GitlabToken) buildscript, err := client.RepoRawFile(ns(repo.Owner, repo.Name), commit.Hash, ".drone.yml") 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) } // 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" build.BuildScript = string(buildscript) if err := database.SaveBuild(build); err != nil { return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } g.queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build}) // OK! return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK) }
// Processes a generic POST-RECEIVE Gogs hook and // attempts to trigger a build. func (h *GogsHandler) Hook(w http.ResponseWriter, r *http.Request) error { defer r.Body.Close() payloadbytes, err := ioutil.ReadAll(r.Body) if err != nil { println(err.Error()) return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) } fmt.Printf("body is => %s\n", string(payloadbytes)) payload, err := ParseHook(payloadbytes) if err != nil { println(err.Error()) return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) } fmt.Printf("payload parsed\n") // Verify that the commit doesn't already exist. // We should never build the same commit twice. _, err = database.GetCommitHash(payload.Commits[0].Id, payload.Repo.Id) if err != nil && err != sql.ErrNoRows { return RenderText(w, http.StatusText(http.StatusBadGateway), http.StatusBadGateway) } fmt.Printf("commit hash checked\n") // Save repo to the database if needed var urlParts = strings.Split(payload.Repo.Url, "/") repo, err := setupRepo(urlParts, payload) if err != nil { println(err.Error()) return RenderText(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) } commit := &Commit{} commit.RepoID = repo.ID commit.Branch = payload.Branch() commit.Hash = payload.Commits[0].Id commit.Status = "Pending" commit.Created = time.Now().UTC() commit.Message = payload.Commits[0].Message commit.Timestamp = time.Now().UTC().String() commit.SetAuthor(payload.Commits[0].Author.Name) fmt.Printf("commit struct created\n") // save the commit to the database if err := database.SaveCommit(commit); err != nil { return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } fmt.Printf("commit struct saved\n") var service_endpoint = urlParts[2] if os.Getenv("GOGS_URL") != "" { service_endpoint = os.Getenv("GOGS_URL") } // GET .drone.yml file var droneYmlUrl = fmt.Sprintf(droneYmlUrlPattern, service_endpoint, urlParts[3], urlParts[4], commit.Hash) println("droneYmlUrl is ", droneYmlUrl) ymlGetResponse, err := http.Get(droneYmlUrl) var buildYml = "" if err != nil { println(err.Error()) return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } else { defer ymlGetResponse.Body.Close() yml, err := ioutil.ReadAll(ymlGetResponse.Body) if err != nil { println(err.Error()) return RenderText(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } buildYml = string(yml) println("yml from http get: ", buildYml) } // parse the build script var repoParams = map[string]string{} println("parsing yml") buildscript, err := script.ParseBuild([]byte(buildYml), repoParams) 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) } fmt.Printf("build script parsed\n") // 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) } println("build saved") // send the build to the queue h.queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build, Script: buildscript}) fmt.Printf("build task added to queue\n") // OK! return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK) }
func Setup() { // create an in-memory database database.Init("sqlite3", ":memory:") // create dummy user data user1 := User{ Password: "******", Name: "Brad Rydzewski", Email: "*****@*****.**", Gravatar: "8c58a0be77ee441bb8f8595b7f1b4e87", Token: "123", Admin: true} user2 := User{ Password: "******", Name: "Thomas Burke", Email: "*****@*****.**", Gravatar: "c62f7126273f7fa786274274a5dec8ce", Token: "456", Admin: false} user3 := User{ Password: "******", Name: "Carlos Morales", Email: "*****@*****.**", Gravatar: "c2180a539620d90d68eaeb848364f1c2", Token: "789", Admin: false} database.SaveUser(&user1) database.SaveUser(&user2) database.SaveUser(&user3) // create dummy team data team1 := Team{ Slug: "drone", Name: "Drone", Email: "*****@*****.**", Gravatar: "8c58a0be77ee441bb8f8595b7f1b4e87"} team2 := Team{ Slug: "github", Name: "Github", Email: "*****@*****.**", Gravatar: "61024896f291303615bcd4f7a0dcfb74"} team3 := Team{ Slug: "golang", Name: "Golang", Email: "*****@*****.**", Gravatar: "991695cc770c6b8354b68cd18c280b95"} database.SaveTeam(&team1) database.SaveTeam(&team2) database.SaveTeam(&team3) // create team membership data database.SaveMember(user1.ID, team1.ID, RoleOwner) database.SaveMember(user2.ID, team1.ID, RoleAdmin) database.SaveMember(user3.ID, team1.ID, RoleWrite) database.SaveMember(user1.ID, team2.ID, RoleOwner) database.SaveMember(user2.ID, team2.ID, RoleAdmin) database.SaveMember(user3.ID, team2.ID, RoleWrite) database.SaveMember(user1.ID, team3.ID, RoleRead) // create dummy repo data repo1 := Repo{ Slug: "github.com/drone/drone", Host: "github.com", Owner: "drone", Name: "drone", Private: true, Disabled: false, SCM: "git", URL: "[email protected]:drone/drone.git", Username: "******", Password: "******", PublicKey: "public key", PrivateKey: "private key", UserID: user1.ID, TeamID: team1.ID, } repo2 := Repo{ Slug: "bitbucket.org/drone/test", Host: "bitbucket.org", Owner: "drone", Name: "test", Private: false, Disabled: false, SCM: "hg", URL: "https://bitbucket.org/drone/test", Username: "******", Password: "******", PublicKey: "public key", PrivateKey: "private key", UserID: user1.ID, TeamID: team1.ID, } repo3 := Repo{ Slug: "bitbucket.org/brydzewski/test", Host: "bitbucket.org", Owner: "brydzewski", Name: "test", Private: false, Disabled: false, SCM: "hg", URL: "https://bitbucket.org/brydzewski/test", Username: "******", Password: "******", PublicKey: "public key", PrivateKey: "private key", UserID: user2.ID, } database.SaveRepo(&repo1) database.SaveRepo(&repo2) database.SaveRepo(&repo3) commit1 := Commit{ RepoID: repo1.ID, Status: "Success", Hash: "4f4c4594be6d6ddbc1c0dd521334f7ecba92b608", Branch: "master", Author: user1.Email, Gravatar: user1.Gravatar, Message: "commit message", } commit2 := Commit{ RepoID: repo1.ID, Status: "Failure", Hash: "0eb2fa13e9f4139e803b6ad37831708d4786c74a", Branch: "master", Author: user1.Email, Gravatar: user1.Gravatar, Message: "commit message", } commit3 := Commit{ RepoID: repo1.ID, Status: "Failure", Hash: "60a7fe87ccf01d0152e53242528399e05acaf047", Branch: "dev", Author: user1.Email, Gravatar: user1.Gravatar, Message: "commit message", } commit4 := Commit{ RepoID: repo2.ID, Status: "Success", Hash: "a4078d1e9a0842cdd214adbf0512578799a4f2ba", Branch: "master", Author: user1.Email, Gravatar: user1.Gravatar, Message: "commit message", } // create dummy commit data database.SaveCommit(&commit1) database.SaveCommit(&commit2) database.SaveCommit(&commit3) database.SaveCommit(&commit4) // create dummy build data database.SaveBuild(&Build{CommitID: commit1.ID, Slug: "node_0.10", Status: "Success", Duration: 60}) database.SaveBuild(&Build{CommitID: commit1.ID, Slug: "node_0.09", Status: "Success", Duration: 70}) database.SaveBuild(&Build{CommitID: commit2.ID, Slug: "node_0.10", Status: "Success", Duration: 10}) database.SaveBuild(&Build{CommitID: commit2.ID, Slug: "node_0.09", Status: "Failure", Duration: 65}) database.SaveBuild(&Build{CommitID: commit3.ID, Slug: "node_0.10", Status: "Failure", Duration: 50}) database.SaveBuild(&Build{CommitID: commit3.ID, Slug: "node_0.09", Status: "Failure", Duration: 55}) }