func UserInvite(w http.ResponseWriter, r *http.Request) error { // generate the password reset token email := r.FormValue("email") token := authcookie.New(email, time.Now().Add(12*time.Hour), secret) // get settings hostname := database.SettingsMust().URL().String() emailEnabled := database.SettingsMust().SmtpServer != "" if !emailEnabled { // Email is not enabled, so must let the user know the signup link link := fmt.Sprintf("%v/register?token=%v", hostname, token) return RenderText(w, link, http.StatusOK) } // send data to template data := struct { Host string Email string Token string }{hostname, email, token} // send the email message async go mail.SendActivation(email, data) return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK) }
func SignUpPost(w http.ResponseWriter, r *http.Request) error { // if self-registration is disabled we should display an // error message to the user. if !database.SettingsMust().OpenInvitations { http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden) return nil } // generate the password reset token email := r.FormValue("email") token := authcookie.New(email, time.Now().Add(12*time.Hour), secret) // get the hostname from the database for use in the email hostname := database.SettingsMust().URL().String() // data used to generate the email template data := struct { Host string Email string Token string }{hostname, email, token} // send the email message async go mail.SendActivation(email, data) return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK) }
func ForgotPost(w http.ResponseWriter, r *http.Request) error { email := r.FormValue("email") // attempt to retrieve the user by email address user, err := database.GetUserEmail(email) if err != nil { log.Printf("could not find user %s to reset password. %s", email, err) // if we can't find the email, we still display // the template to the user. This prevents someone // from trying to guess passwords through trial & error return RenderTemplate(w, "forgot_sent.html", nil) } // hostname from settings hostname := database.SettingsMust().URL().String() // generate the password reset token token := passwordreset.NewToken(user.Email, 12*time.Hour, []byte(user.Password), secret) data := struct { Host string User *User Token string }{hostname, user, token} // send the email message async go func() { if err := mail.SendPassword(email, data); err != nil { log.Printf("error sending password reset email to %s. %s", email, err) } }() // render the template indicating a success return RenderTemplate(w, "forgot_sent.html", nil) }
func AdminSettingsUpdate(w http.ResponseWriter, r *http.Request, u *User) error { // get settings from database settings := database.SettingsMust() // update smtp settings settings.Domain = r.FormValue("Domain") settings.Scheme = r.FormValue("Scheme") // update bitbucket settings settings.BitbucketKey = r.FormValue("BitbucketKey") settings.BitbucketSecret = r.FormValue("BitbucketSecret") // update github settings settings.GitHubKey = r.FormValue("GitHubKey") settings.GitHubSecret = r.FormValue("GitHubSecret") settings.GitHubDomain = r.FormValue("GitHubDomain") settings.GitHubApiUrl = r.FormValue("GitHubApiUrl") // update gitlab settings settings.GitlabApiUrl = r.FormValue("GitlabApiUrl") glUrl, err := url.Parse(settings.GitlabApiUrl) if err != nil { return RenderError(w, err, http.StatusBadRequest) } settings.GitlabDomain = glUrl.Host // update smtp settings settings.SmtpServer = r.FormValue("SmtpServer") settings.SmtpPort = r.FormValue("SmtpPort") settings.SmtpAddress = r.FormValue("SmtpAddress") settings.SmtpUsername = r.FormValue("SmtpUsername") settings.SmtpPassword = r.FormValue("SmtpPassword") settings.OpenInvitations = (r.FormValue("OpenInvitations") == "on") // validate user input if err := settings.Validate(); err != nil { return RenderError(w, err, http.StatusBadRequest) } // persist changes if err := database.SaveSettings(settings); err != nil { return RenderError(w, err, http.StatusBadRequest) } // make sure the mail package is updated with the // latest client information. //mail.SetClient(&mail.SMTPClient{ // Host: settings.SmtpServer, // Port: settings.SmtpPort, // User: settings.SmtpUsername, // Pass: settings.SmtpPassword, // From: settings.SmtpAddress, //}) return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK) }
func RepoBadges(w http.ResponseWriter, r *http.Request, u *User, repo *Repo) error { // hostname from settings hostname := database.SettingsMust().URL().String() data := struct { Repo *Repo User *User Host string }{repo, u, hostname} return RenderTemplate(w, "repo_badges.html", &data) }
// Return an HTML form for the User to update the site settings. func AdminSettings(w http.ResponseWriter, r *http.Request, u *User) error { // get settings from database settings := database.SettingsMust() data := struct { User *User Settings *Settings }{u, settings} return RenderTemplate(w, "admin_settings.html", &data) }
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 }
// Invite a new Team Member. func TeamMemberInvite(w http.ResponseWriter, r *http.Request, u *User) error { teamParam := r.FormValue(":team") mailParam := r.FormValue("email") team, err := database.GetTeamSlug(teamParam) if err != nil { return RenderError(w, err, http.StatusNotFound) } if member, _ := database.IsMemberAdmin(u.ID, team.ID); !member { return fmt.Errorf("Forbidden") } // generate a token that is valid for 3 days to join the team token := authcookie.New(strconv.Itoa(int(team.ID)), time.Now().Add(72*time.Hour), secret) // hostname from settings hostname := database.SettingsMust().URL().String() emailEnabled := database.SettingsMust().SmtpServer != "" if !emailEnabled { // Email is not enabled, so must let the user know the signup link link := fmt.Sprintf("%v/accept?token=%v", hostname, token) return RenderText(w, link, http.StatusOK) } // send the invitation data := struct { User *User Team *Team Token string Host string }{u, team, token, hostname} // send email async go mail.SendInvitation(team.Name, mailParam, &data) return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK) }
func RepoAddGithubLimited(w http.ResponseWriter, r *http.Request, u *User) error { settings := database.SettingsMust() teams, err := database.ListTeams(u.ID) if err != nil { return err } data := struct { User *User Teams []*Team Settings *Settings }{u, teams, settings} // display the template for adding // a new GitHub repository with read only access. return RenderTemplate(w, "github_limited_add.html", &data) }
func (g *GitlabHandler) Link(w http.ResponseWriter, r *http.Request, u *User) error { token := strings.TrimSpace(r.FormValue("token")) if len(u.GitlabToken) == 0 || token != u.GitlabToken && len(token) > 0 { u.GitlabToken = token settings := database.SettingsMust() gl := gogitlab.NewGitlab(settings.GitlabApiUrl, g.apiPath, u.GitlabToken) _, err := gl.CurrentUser() if err != nil { return fmt.Errorf("Private Token is not valid: %q", err) } if err := database.SaveUser(u); err != nil { return RenderError(w, err, http.StatusBadRequest) } } http.Redirect(w, r, "/new/gitlab", http.StatusSeeOther) return nil }
func (g *GitlabHandler) newGitlabRepo(u *User, owner, name string) (*Repo, error) { settings := database.SettingsMust() gl := gogitlab.NewGitlab(settings.GitlabApiUrl, g.apiPath, u.GitlabToken) project, err := gl.Project(ns(owner, name)) if err != nil { return nil, err } var cloneUrl string if project.Public { cloneUrl = project.HttpRepoUrl } else { cloneUrl = project.SshRepoUrl } repo, err := NewRepo(settings.GitlabDomain, owner, name, ScmGit, cloneUrl) if err != nil { return nil, err } repo.UserID = u.ID repo.Private = !project.Public if repo.Private { // name the key keyName := fmt.Sprintf("%s@%s", repo.Owner, settings.Domain) // TODO: (fudanchii) check if we already opted to use UserKey // create the github key, or update if one already exists if err := gl.AddProjectDeployKey(ns(owner, name), keyName, repo.PublicKey); err != nil { return nil, fmt.Errorf("Unable to add Public Key to your GitLab repository.") } } link := fmt.Sprintf("%s://%s/hook/gitlab?id=%s", settings.Scheme, settings.Domain, repo.Slug) if err := gl.AddProjectHook(ns(owner, name), link, true, false, true); err != nil { return nil, fmt.Errorf("Unable to add Hook to your GitLab repository.") } return repo, err }
func (g *GitlabHandler) Add(w http.ResponseWriter, r *http.Request, u *User) error { settings := database.SettingsMust() teams, err := database.ListTeams(u.ID) if err != nil { return err } data := struct { User *User Teams []*Team Settings *Settings }{u, teams, settings} // if the user hasn't linked their GitLab account // render a different template if len(u.GitlabToken) == 0 { return RenderTemplate(w, "gitlab_link.html", &data) } // otherwise display the template for adding // a new GitLab repository. return RenderTemplate(w, "gitlab_add.html", &data) }
func RepoAddBitbucket(w http.ResponseWriter, r *http.Request, u *User) error { settings := database.SettingsMust() teams, err := database.ListTeams(u.ID) if err != nil { return err } data := struct { User *User Teams []*Team Settings *Settings }{u, teams, settings} // if the user hasn't linked their Bitbucket account // render a different template if len(u.BitbucketToken) == 0 { return RenderTemplate(w, "bitbucket_link.html", &data) } // otherwise display the template for adding // a new Bitbucket repository. return RenderTemplate(w, "bitbucket_add.html", &data) }
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) }
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) }
// 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) }
func RepoCreateBitbucket(w http.ResponseWriter, r *http.Request, u *User) error { teamName := r.FormValue("team") owner := r.FormValue("owner") name := r.FormValue("name") // get the bitbucket settings from the database settings := database.SettingsMust() // create the Bitbucket client client := bitbucket.New( settings.BitbucketKey, settings.BitbucketSecret, u.BitbucketToken, u.BitbucketSecret, ) bitbucketRepo, err := client.Repos.Find(owner, name) if err != nil { return fmt.Errorf("Unable to find Bitbucket repository %s/%s.", owner, name) } repo, err := NewBitbucketRepo(owner, name, bitbucketRepo.Private) if err != nil { return err } repo.UserID = u.ID repo.Private = bitbucketRepo.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 } // if the repository is private we'll need // to upload a bitbucket key to the repository if repo.Private { // name the key keyName := fmt.Sprintf("%s@%s", repo.Owner, settings.Domain) // create the bitbucket 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 Public Key to your Bitbucket repository: %s", err) } } 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/bitbucket.org?id=%s", settings.Scheme, settings.Domain, repo.Slug) // add the hook if _, err := client.Brokers.CreateUpdate(owner, name, link, bitbucket.BrokerTypePost); err != nil { return fmt.Errorf("Unable to add Hook to your Bitbucket 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) } return RenderText(w, http.StatusText(http.StatusOK), http.StatusOK) }
func LinkBitbucket(w http.ResponseWriter, r *http.Request, u *User) error { // get settings from database settings := database.SettingsMust() // bitbucket oauth1 consumer var consumer = oauth1.Consumer{ RequestTokenURL: "https://bitbucket.org/api/1.0/oauth/request_token/", AuthorizationURL: "https://bitbucket.org/!api/1.0/oauth/authenticate", AccessTokenURL: "https://bitbucket.org/api/1.0/oauth/access_token/", CallbackURL: settings.URL().String() + "/auth/login/bitbucket", ConsumerKey: settings.BitbucketKey, ConsumerSecret: settings.BitbucketSecret, } // get the oauth verifier verifier := r.FormValue("oauth_verifier") if len(verifier) == 0 { // Generate a Request Token requestToken, err := consumer.RequestToken() if err != nil { return err } // add the request token as a signed cookie SetCookie(w, r, "bitbucket_token", requestToken.Encode()) url, _ := consumer.AuthorizeRedirect(requestToken) http.Redirect(w, r, url, http.StatusSeeOther) return nil } // remove bitbucket token data once before redirecting // back to the application. defer DelCookie(w, r, "bitbucket_token") // get the tokens from the request requestTokenStr := GetCookie(r, "bitbucket_token") requestToken, err := oauth1.ParseRequestTokenStr(requestTokenStr) if err != nil { return err } // exchange for an access token accessToken, err := consumer.AuthorizeToken(requestToken, verifier) if err != nil { return err } // create the Bitbucket client client := bitbucket.New( settings.BitbucketKey, settings.BitbucketSecret, accessToken.Token(), accessToken.Secret(), ) // get the currently authenticated Bitbucket User user, err := client.Users.Current() if err != nil { return err } // update the user account u.BitbucketLogin = user.User.Username u.BitbucketToken = accessToken.Token() u.BitbucketSecret = accessToken.Secret() if err := database.SaveUser(u); err != nil { return err } http.Redirect(w, r, "/new/bitbucket.org", http.StatusSeeOther) return nil }
// 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") 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 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) }
func (h *HookHandler) PullRequestHookGithub(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.Author = hook.PullRequest.User.Login commit.PullRequest = strconv.Itoa(hook.Number) commit.Message = hook.PullRequest.Title // label := p.PullRequest.Head.Labe // 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) // 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, repo.Params) 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() h.queue.Add(&queue.BuildTask{Repo: repo, Commit: commit, Build: build, Script: buildscript}) // OK! RenderText(w, http.StatusText(http.StatusOK), http.StatusOK) }
// Tests the ability to create GitHub repositories. func Test_GitHubCreate(t *testing.T) { // seed the database with values SetupFixtures() defer TeardownFixtures() // mock request req := http.Request{} req.Form = url.Values{} // get user that will add repositories user, _ := database.GetUser(1) settings := database.SettingsMust() Convey("Given request to setup github repo", t, func() { Convey("When repository is public", func() { req.Form.Set("owner", "example") req.Form.Set("name", "public") req.Form.Set("team", "") res := httptest.NewRecorder() err := handler.RepoCreateGithub(res, &req, user) repo, _ := database.GetRepoSlug(settings.GitHubDomain + "/example/public") Convey("The repository is created", func() { So(err, ShouldBeNil) So(repo, ShouldNotBeNil) So(repo.ID, ShouldNotEqual, 0) So(repo.Owner, ShouldEqual, "example") So(repo.Name, ShouldEqual, "public") So(repo.Host, ShouldEqual, settings.GitHubDomain) So(repo.TeamID, ShouldEqual, 0) So(repo.UserID, ShouldEqual, user.ID) So(repo.Private, ShouldEqual, false) So(repo.SCM, ShouldEqual, "git") }) Convey("The repository is public", func() { So(repo.Private, ShouldEqual, false) }) }) Convey("When repository is private", func() { req.Form.Set("owner", "example") req.Form.Set("name", "private") req.Form.Set("team", "") res := httptest.NewRecorder() err := handler.RepoCreateGithub(res, &req, user) repo, _ := database.GetRepoSlug(settings.GitHubDomain + "/example/private") Convey("The repository is created", func() { So(err, ShouldBeNil) So(repo, ShouldNotBeNil) So(repo.ID, ShouldNotEqual, 0) }) Convey("The repository is private", func() { So(repo.Private, ShouldEqual, true) }) }) Convey("When repository is not found", func() { req.Form.Set("owner", "example") req.Form.Set("name", "notfound") req.Form.Set("team", "") res := httptest.NewRecorder() err := handler.RepoCreateGithub(res, &req, user) Convey("The result is an error", func() { So(err, ShouldNotBeNil) So(err.Error(), ShouldEqual, "Unable to find GitHub repository example/notfound.") }) Convey("The repository is not created", func() { _, err := database.GetRepoSlug("example/notfound") So(err, ShouldNotBeNil) So(err, ShouldEqual, sql.ErrNoRows) }) }) Convey("When repository hook is not writable", func() { req.Form.Set("owner", "example") req.Form.Set("name", "hookerr") req.Form.Set("team", "") res := httptest.NewRecorder() err := handler.RepoCreateGithub(res, &req, user) Convey("The result is an error", func() { So(err, ShouldNotBeNil) So(err.Error(), ShouldEqual, "Unable to add Hook to your GitHub repository.") }) Convey("The repository is not created", func() { _, err := database.GetRepoSlug("example/hookerr") So(err, ShouldNotBeNil) So(err, ShouldEqual, sql.ErrNoRows) }) }) Convey("When repository ssh key is not writable", func() { req.Form.Set("owner", "example") req.Form.Set("name", "keyerr") req.Form.Set("team", "") res := httptest.NewRecorder() err := handler.RepoCreateGithub(res, &req, user) Convey("The result is an error", func() { So(err, ShouldNotBeNil) So(err.Error(), ShouldEqual, "Unable to add Public Key to your GitHub repository.") }) Convey("The repository is not created", func() { _, err := database.GetRepoSlug("example/keyerr") So(err, ShouldNotBeNil) So(err, ShouldEqual, sql.ErrNoRows) }) }) Convey("When a team is provided", func() { req.Form.Set("owner", "example") req.Form.Set("name", "team") req.Form.Set("team", "drone") res := httptest.NewRecorder() // invoke handler err := handler.RepoCreateGithub(res, &req, user) team, _ := database.GetTeamSlug("drone") repo, _ := database.GetRepoSlug(settings.GitHubDomain + "/example/team") Convey("The repository is created", func() { So(err, ShouldBeNil) So(repo, ShouldNotBeNil) So(repo.ID, ShouldNotEqual, 0) }) Convey("The team should be set", func() { So(repo.TeamID, ShouldEqual, team.ID) }) }) Convey("When a team is not found", func() { req.Form.Set("owner", "example") req.Form.Set("name", "public") req.Form.Set("team", "faketeam") res := httptest.NewRecorder() err := handler.RepoCreateGithub(res, &req, user) Convey("The result is an error", func() { So(err, ShouldNotBeNil) So(err.Error(), ShouldEqual, "Unable to find Team faketeam.") }) }) Convey("When a team is forbidden", func() { req.Form.Set("owner", "example") req.Form.Set("name", "public") req.Form.Set("team", "golang") res := httptest.NewRecorder() err := handler.RepoCreateGithub(res, &req, user) Convey("The result is an error", func() { So(err, ShouldNotBeNil) So(err.Error(), ShouldEqual, "Invalid permission to access Team golang.") }) }) }) }