// updateGitHubStatus is a helper function that will send // the build status to GitHub using the Status API. // see https://github.com/blog/1227-commit-status-api func updateGitHubStatus(repo *Repo, commit *Commit) error { // convert from drone status to github status var message, status string switch commit.Status { case "Success": status = "success" message = "The build succeeded on drone.io" case "Failure": status = "failure" message = "The build failed on drone.io" case "Pending": status = "pending" message = "The build is pending on drone.io" default: status = "error" message = "The build errored on drone.io" } // get the system settings settings, _ := database.GetSettings() // get the user from the database // since we need his / her GitHub token user, err := database.GetUser(repo.UserID) if err != nil { return err } client := github.New(user.GithubToken) return client.Repos.CreateStatus(repo.Owner, repo.Name, status, settings.URL().String(), message, commit.Hash) }
func LinkGithub(w http.ResponseWriter, r *http.Request, u *User) error { // get settings from database settings := database.SettingsMust() // github OAuth2 Data var oauth = oauth2.Client{ RedirectURL: settings.URL().String() + "/auth/login/github", AccessTokenURL: "https://" + settings.GitHubDomain + "/login/oauth/access_token", AuthorizationURL: "https://" + settings.GitHubDomain + "/login/oauth/authorize", ClientId: settings.GitHubKey, ClientSecret: settings.GitHubSecret, } // get the OAuth code code := r.FormValue("code") if len(code) == 0 { scope := "repo,repo:status,user:email" state := "FqB4EbagQ2o" redirect := oauth.AuthorizeRedirect(scope, state) http.Redirect(w, r, redirect, http.StatusSeeOther) return nil } // exchange code for an auth token token, err := oauth.GrantToken(code) if err != nil { log.Println("Error granting GitHub authorization token") return err } // create the client client := github.New(token.AccessToken) client.ApiUrl = settings.GitHubApiUrl // get the user information githubUser, err := client.Users.Current() if err != nil { log.Println("Error retrieving currently authenticated GitHub user") return err } // save the github token to the user account u.GithubToken = token.AccessToken u.GithubLogin = githubUser.Login if err := database.SaveUser(u); err != nil { log.Println("Error persisting user's GitHub auth token to the database") return err } http.Redirect(w, r, "/new/github.com", http.StatusSeeOther) return nil }
func RepoCreateGithub(w http.ResponseWriter, r *http.Request, u *User) error { teamName := r.FormValue("team") owner := r.FormValue("owner") name := r.FormValue("name") // get the github settings from the database settings := database.SettingsMust() // create the GitHub client client := github.New(u.GithubToken) githubRepo, err := client.Repos.Find(owner, name) if err != nil { return err } repo, err := NewGitHubRepo(owner, name, githubRepo.Private) if err != nil { return err } repo.UserID = u.ID repo.Private = githubRepo.Private // if the user chose to assign to a team account // we need to retrieve the team, verify the user // has access, and then set the team id. if len(teamName) > 0 { team, err := database.GetTeamSlug(teamName) if err != nil { log.Printf("error retrieving team %s", teamName) return err } // user must be an admin member of the team if ok, _ := database.IsMemberAdmin(u.ID, team.ID); !ok { return fmt.Errorf("Forbidden") } repo.TeamID = team.ID } // if the repository is private we'll need // to upload a github key to the repository if repo.Private { // name the key keyName := fmt.Sprintf("%s@%s", repo.Owner, settings.Domain) // create the github key, or update if one already exists _, err := client.RepoKeys.CreateUpdate(owner, name, repo.PublicKey, keyName) if err != nil { return fmt.Errorf("Unable to add Private Key to your GitHub repository") } } else { } // create a hook so that we get notified when code // is pushed to the repository and can execute a build. link := fmt.Sprintf("%s://%s/hook/github.com?id=%s", settings.Scheme, settings.Domain, repo.Slug) // add the hook if _, err := client.Hooks.CreateUpdate(owner, name, link); err != nil { return fmt.Errorf("Unable to add Hook to your GitHub repository. %s", err.Error()) } // Save to the database if err := database.SaveRepo(repo); err != nil { log.Print("error saving new repository to the database") return err } return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK) }
// 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) }
func RepoCreateGithubLimited(w http.ResponseWriter, r *http.Request, u *User) error { teamName := r.FormValue("team") owner := r.FormValue("owner") name := r.FormValue("name") readToken := r.FormValue("read-token") writeToken := r.FormValue("write-token") u.GithubToken = readToken u.GithubWriteToken = writeToken // get the github settings from the database settings := database.SettingsMust() fmt.Printf("got settings: %s\n", settings) // create the GitHub client rClient := github.New(readToken) wClient := github.New(writeToken) rClient.ApiUrl = settings.GitHubApiUrl wClient.ApiUrl = settings.GitHubApiUrl githubRepo, err := rClient.Repos.Find(owner, name) if err != nil { fmt.Printf("err1, %s\n", err) return err } repo, err := NewGitHubRepo(settings.GitHubDomain, owner, name, githubRepo.Private) if err != nil { fmt.Printf("err2, %s\n", err) return err } repo.URL = fmt.Sprintf("https://%s@%s/%s/%s", readToken, settings.GitHubDomain, owner, name) repo.UserID = u.ID repo.Private = githubRepo.Private // if the user chose to assign to a team account // we need to retrieve the team, verify the user // has access, and then set the team id. if len(teamName) > 0 { team, err := database.GetTeamSlug(teamName) if err != nil { return fmt.Errorf("Unable to find Team %s.", teamName) } // user must be an admin member of the team if ok, _ := database.IsMemberAdmin(u.ID, team.ID); !ok { return fmt.Errorf("Invalid permission to access Team %s.", teamName) } repo.TeamID = team.ID } // create a hook so that we get notified when code // is pushed to the repository and can execute a build. link := fmt.Sprintf("%s://%s/hook/github.com?id=%s", settings.Scheme, settings.Domain, repo.Slug) // add the hook if _, err := wClient.Hooks.CreateUpdate(owner, name, link); err != nil { return fmt.Errorf("Unable to add Hook to your GitHub repository. %s", err.Error()) } // Save to the database if err := database.SaveRepo(repo); err != nil { return fmt.Errorf("Error saving repository to the database. %s", err) } // Save user to the database if err := database.SaveUser(u); err != nil { return fmt.Errorf("Error saving user to the database. %s", err) } return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK) }