Skip to content

Commit

Permalink
Bitbucket support and DEPS_GIT_NAME and DEPS_GIT_EMAIL env vars
Browse files Browse the repository at this point in the history
  • Loading branch information
davegaeddert committed Sep 9, 2019
1 parent 476bee5 commit 3a55911
Show file tree
Hide file tree
Showing 6 changed files with 288 additions and 2 deletions.
28 changes: 28 additions & 0 deletions internal/ci/bitbucketpipelines/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package bitbucketpipelines

import (
"fmt"
"os"
)

type BitbucketPipelines struct {
}

func Is() bool {
return os.Getenv("BITBUCKET_BUILD_NUMBER") != ""
}

func (gitlab *BitbucketPipelines) Autoconfigure() error {
return nil
}

func (gitlab *BitbucketPipelines) Branch() string {
return ""
}

func GetProjectAPIURL() string {
if slug := os.Getenv("BITBUCKET_REPO_FULL_NAME"); slug != "" {
return fmt.Sprintf("https://api.bitbucket.org/2.0/repositories/%s", slug)
}
return ""
}
19 changes: 17 additions & 2 deletions internal/ci/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"os/exec"
"strings"

"github.com/dropseed/deps/internal/ci/bitbucketpipelines"
"github.com/dropseed/deps/internal/ci/circleci"
"github.com/dropseed/deps/internal/ci/generic"
"github.com/dropseed/deps/internal/ci/githubactions"
Expand All @@ -31,11 +32,25 @@ func NewCIProvider() CIProvider {
if gitlabci.Is() {
return &gitlabci.GitLabCI{}
}
if bitbucketpipelines.Is() {
return &bitbucketpipelines.BitbucketPipelines{}
}
return &generic.GenericCI{}
}

func BaseAutoconfigure() {
if cmd := exec.Command("git", "config", "user.name", "deps"); cmd != nil {

gitName := "deps"
gitEmail := "bot@dependencies.io"

if s := os.Getenv("DEPS_GIT_NAME"); s != "" {
gitName = s
}
if s := os.Getenv("DEPS_GIT_EMAIL"); s != "" {
gitEmail = s
}

if cmd := exec.Command("git", "config", "user.name", gitName); cmd != nil {
output.Event("Autoconfigure: %s", strings.Join(cmd.Args, " "))
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
Expand All @@ -44,7 +59,7 @@ func BaseAutoconfigure() {
}
}

if cmd := exec.Command("git", "config", "user.email", "bot@dependencies.io"); cmd != nil {
if cmd := exec.Command("git", "config", "user.email", gitEmail); cmd != nil {
output.Event("Autoconfigure: %s", strings.Join(cmd.Args, " "))
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
Expand Down
157 changes: 157 additions & 0 deletions internal/pullrequest/bitbucket/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package bitbucket

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"regexp"

"github.com/dropseed/deps/internal/config"
"github.com/dropseed/deps/internal/output"
"github.com/dropseed/deps/internal/schema"
)

type PullRequest struct {
Base string
Head string
Title string
Body string
Dependencies *schema.Dependencies
Config *config.Dependency

ProjectAPIURL string
APIUsername string
APIPassword string
}

func NewPullRequest(base string, head string, deps *schema.Dependencies, cfg *config.Dependency) (*PullRequest, error) {
apiURL, err := getProjectAPIURL()
if err != nil {
return nil, err
}

return &PullRequest{
Base: base,
Head: head,
Title: deps.Title,
Body: deps.Description,
Dependencies: deps,
Config: cfg,
ProjectAPIURL: apiURL,
APIUsername: getAPIUsername(),
APIPassword: getAPIPassword(),
}, nil
}

func (pr *PullRequest) request(verb string, url string, input []byte) (*http.Response, string, error) {
client := &http.Client{}

req, err := http.NewRequest(verb, url, bytes.NewBuffer(input))
if err != nil {
return nil, "", err
}

req.SetBasicAuth(pr.APIUsername, pr.APIPassword)
req.Header.Add("User-Agent", "deps")
req.Header.Set("Content-Type", "application/json")

resp, err := client.Do(req)
if err != nil {
return nil, "", err
}

body, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
return resp, string(body), err
}

// // Create will create the pull request on Bitbucket
func (pr *PullRequest) CreateOrUpdate() error {
output.Debug("Preparing to open Bitbucket pull request for %v\n", pr.ProjectAPIURL)

pullrequestMap := pr.getPullRequestOptions()
output.Debug("%+v\n", pullrequestMap)
pullrequestData, _ := json.Marshal(pullrequestMap)

url := pr.ProjectAPIURL + "/pullrequests"
output.Debug("Creating pull request at %s", url)

resp, body, err := pr.request("POST", url, pullrequestData)
if err != nil {
return err
}

if resp.StatusCode == 201 {
output.Event("Successfully created Bitbucket pull request for %v\n", pr.ProjectAPIURL)
return nil
} else if resp.StatusCode == 409 {
output.Event("pull request already exists")
var data map[string][]string
if err := json.Unmarshal([]byte(body), &data); err != nil {
return err
}

if message, hasMessage := data["message"]; hasMessage {
pattern := regexp.MustCompile("!(\\d+)")
matches := pattern.FindStringSubmatch(message[0])
// finds !18 and 18...
if len(matches) != 2 {
return errors.New("Unable to find ID for existing pull request to update")
}
mrID := matches[1]
return pr.update(mrID, pullrequestData)
}
}

return fmt.Errorf("Failed to create pull request: %s", body)
}

func (pr *PullRequest) update(id string, data []byte) error {
url := pr.ProjectAPIURL + "/pullrequests/" + id
resp, body, err := pr.request("PUT", url, data)
if err != nil {
return err
}
if resp.StatusCode >= 400 {
return fmt.Errorf("Error updating pull request:\n\n%s", body)
}
output.Success("Updated pull request %s", id)
return nil
}

func (pr *PullRequest) getPullRequestOptions() map[string]interface{} {
base := pr.Base
if target := pr.Config.GetSetting("bitbucket_destination"); target != nil {
base = target.(string)
}

pullrequestMap := make(map[string]interface{})
pullrequestMap["title"] = pr.Title
pullrequestMap["source"] = map[string]interface{}{
"branch": map[string]string{
"name": pr.Head,
},
}
pullrequestMap["destination"] = map[string]interface{}{
"branch": map[string]string{
"name": base,
},
}
pullrequestMap["description"] = pr.Body

otherFields := []string{
"close_source_branch",
"reviewers",
}

for _, f := range otherFields {
if s := pr.Config.GetSetting(fmt.Sprintf("bitbucket_%s", f)); s != nil {
pullrequestMap[f] = s
}
}

return pullrequestMap
}
30 changes: 30 additions & 0 deletions internal/pullrequest/bitbucket/repo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package bitbucket

import (
"errors"
)

type BitbucketRepo struct {
apiPassword string
apiUsername string
}

func NewRepo() *BitbucketRepo {
return &BitbucketRepo{
apiUsername: getAPIUsername(),
apiPassword: getAPIPassword(),
}
}

func (repo *BitbucketRepo) CheckRequirements() error {
if repo.apiPassword == "" {
return errors.New("Unable to find Bitbucket API password.\n\nVisit https://docs.dependencies.io/bitbucket for more information.")
}
if repo.apiUsername == "" {
return errors.New("Unable to find Bitbucket API username.\n\nVisit https://docs.dependencies.io/bitbucket for more information.")
}
return nil
}

func (repo *BitbucketRepo) Autoconfigure() {
}
38 changes: 38 additions & 0 deletions internal/pullrequest/bitbucket/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package bitbucket

import (
"errors"
"os"

"github.com/dropseed/deps/internal/ci/bitbucketpipelines"
)

func getAPIPassword() string {
if s := os.Getenv("DEPS_BITBUCKET_PASSWORD"); s != "" {
return s
}

return ""
}

func getAPIUsername() string {
if s := os.Getenv("DEPS_BITBUCKET_USERNAME"); s != "" {
return s
}

return ""
}

func getProjectAPIURL() (string, error) {
if s := os.Getenv("DEPS_BITBUCKET_REPO_API_URL"); s != "" {
return s, nil
}

if ciURL := bitbucketpipelines.GetProjectAPIURL(); ciURL != "" {
return ciURL, nil
}

// TODO otherwise from git remote?

return "", errors.New("Unable to determine Bitbucket API url for this project")
}
18 changes: 18 additions & 0 deletions internal/pullrequest/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import (

"github.com/dropseed/deps/internal/config"
"github.com/dropseed/deps/internal/git"
"github.com/dropseed/deps/internal/pullrequest/bitbucket"
"github.com/dropseed/deps/internal/pullrequest/github"
"github.com/dropseed/deps/internal/pullrequest/gitlab"
"github.com/dropseed/deps/internal/schema"
)

const GITHUB = "github"
const GITLAB = "gitlab"
const BITBUCKET = "bitbucket"

// PullrequestAdapter implements the basic Pullrequest functions
type PullrequestAdapter interface {
Expand All @@ -37,6 +39,10 @@ func NewRepo() RepoAdapter {
return gitlab.NewRepo()
}

if gitHost == BITBUCKET {
return bitbucket.NewRepo()
}

return nil
}

Expand All @@ -51,6 +57,10 @@ func NewPullrequest(base string, head string, deps *schema.Dependencies, cfg *co
return gitlab.NewMergeRequest(base, head, deps, cfg)
}

if gitHost == BITBUCKET {
return bitbucket.NewPullRequest(base, head, deps, cfg)
}

return nil, errors.New("Repo not found or not supported")
}

Expand All @@ -72,6 +82,10 @@ func gitHost() string {
return GITLAB
}

if strings.HasPrefix(remote, "https://bitbucket.org") || strings.HasPrefix(remote, "git@bitbucket.org:") {
return BITBUCKET
}

// More generic matching (github.example.com, etc. but could also accidently match gitlab.example.com/org/github-api)

if strings.Contains(remote, "github") {
Expand All @@ -82,5 +96,9 @@ func gitHost() string {
return GITLAB
}

if strings.Contains(remote, "bitbucket") {
return BITBUCKET
}

return ""
}

0 comments on commit 3a55911

Please sign in to comment.