// Activate activates a repository by adding a Post-commit hook and // a Public Deploy key, if applicable. func (r *Bitbucket) Activate(user *model.User, repo *model.Repo, link string) error { var client = bitbucket.New( r.Client, r.Secret, user.Access, user.Secret, ) // parse the hostname from the hook, and use this // to name the ssh key var hookurl, err = url.Parse(link) if err != nil { return err } // if the repository is private we'll need // to upload a github key to the repository if repo.Private { // name the key var keyname = "drone@" + hookurl.Host var _, err = client.RepoKeys.CreateUpdate(repo.Owner, repo.Name, repo.PublicKey, keyname) if err != nil { return err } } // add the hook _, err = client.Brokers.CreateUpdate(repo.Owner, repo.Name, link, bitbucket.BrokerTypePost) return err }
// GetRepos fetches all repositories that the specified // user has access to in the remote system. func (r *Bitbucket) GetRepos(user *model.User) ([]*model.Repo, error) { var repos []*model.Repo var client = bitbucket.New( r.Client, r.Secret, user.Access, user.Secret, ) var list, err = client.Repos.List() if err != nil { return nil, err } var remote = r.GetKind() var hostname = r.GetHost() for _, item := range list { // for now we only support git repos if item.Scm != "git" { continue } // these are the urls required to clone the repository // TODO use the bitbucketurl.Host and bitbucketurl.Scheme instead of hardcoding // so that we can support Stash. var html = fmt.Sprintf("https://bitbucket.org/%s/%s", item.Owner, item.Slug) var clone = fmt.Sprintf("https://bitbucket.org/%s/%s.git", item.Owner, item.Slug) var ssh = fmt.Sprintf("[email protected]:%s/%s.git", item.Owner, item.Slug) var repo = model.Repo{ UserID: user.ID, Remote: remote, Host: hostname, Owner: item.Owner, Name: item.Slug, Private: item.Private, URL: html, CloneURL: clone, GitURL: clone, SSHURL: ssh, Role: &model.Perm{ Admin: true, Write: true, Read: true, }, } if repo.Private { repo.CloneURL = repo.SSHURL } repos = append(repos, &repo) } return repos, err }
// GetScript fetches the build script (.drone.yml) from the remote // repository and returns in string format. func (r *Bitbucket) GetScript(user *model.User, repo *model.Repo, hook *model.Hook) ([]byte, error) { var client = bitbucket.New( r.Client, r.Secret, user.Access, user.Secret, ) // get the yaml from the database var raw, err = client.Sources.Find(repo.Owner, repo.Name, hook.Sha, ".drone.yml") if err != nil { return nil, err } return []byte(raw.Data), nil }
// Deactivate removes a repository by removing all the post-commit hooks // which are equal to link and removing the SSH deploy key. func (r *Bitbucket) Deactivate(user *model.User, repo *model.Repo, link string) error { var client = bitbucket.New( r.Client, r.Secret, user.Access, user.Secret, ) title, err := GetKeyTitle(link) if err != nil { return err } if err := client.RepoKeys.DeleteName(repo.Owner, repo.Name, title); err != nil { return err } return client.Brokers.DeleteUrl(repo.Owner, repo.Name, link, bitbucket.BrokerTypePost) }
// 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) }
// Authorize handles Bitbucket API Authorization func (r *Bitbucket) Authorize(res http.ResponseWriter, req *http.Request) (*model.Login, error) { 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: httputil.GetScheme(req) + "://" + httputil.GetHost(req) + "/api/auth/bitbucket.org", ConsumerKey: r.Client, ConsumerSecret: r.Secret, } // get the oauth verifier verifier := req.FormValue("oauth_verifier") if len(verifier) == 0 { // Generate a Request Token requestToken, err := consumer.RequestToken() if err != nil { return nil, err } // add the request token as a signed cookie httputil.SetCookie(res, req, "bitbucket_token", requestToken.Encode()) url, _ := consumer.AuthorizeRedirect(requestToken) http.Redirect(res, req, url, http.StatusSeeOther) return nil, nil } // remove bitbucket token data once before redirecting // back to the application. defer httputil.DelCookie(res, req, "bitbucket_token") // get the tokens from the request requestTokenStr := httputil.GetCookie(req, "bitbucket_token") requestToken, err := oauth1.ParseRequestTokenStr(requestTokenStr) if err != nil { return nil, err } // exchange for an access token accessToken, err := consumer.AuthorizeToken(requestToken, verifier) if err != nil { return nil, err } // create the Bitbucket client client := bitbucket.New( r.Client, r.Secret, accessToken.Token(), accessToken.Secret(), ) // get the currently authenticated Bitbucket User user, err := client.Users.Current() if err != nil { return nil, err } // put the user data in the common format login := model.Login{ Login: user.User.Username, Access: accessToken.Token(), Secret: accessToken.Secret(), Name: user.User.DisplayName, } email, _ := client.Emails.FindPrimary(user.User.Username) if email != nil { login.Email = email.Email } return &login, nil }
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 }