Skip to content

Commit

Permalink
Merge pull request #4 from Oduig/bitbucket-support
Browse files Browse the repository at this point in the history
Add support for BitBucket repositories
  • Loading branch information
foosinn committed Aug 8, 2019
2 parents e7de57c + e48f89e commit 3ca1827
Show file tree
Hide file tree
Showing 26 changed files with 853 additions and 97 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
vendor/
drone-tree-config
.idea
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ Environment variables:
- `PLUGIN_ADDRESS`: Listen address for the plugins webserver. Defaults to `:3000`.
- `PLUGIN_SECRET`: Shared secret with drone. You can generate the token using `openssl rand -hex 16`.
- `GITHUB_TOKEN`: Github personal access token. Only needs repo rights. See [here][1].
- `GITHUB_SERVER`: Custom Github server for Github Enterprise
- `AUTH_SERVER`: Custom auth server (uses SERVER if empty)
- `SERVER`: Custom SCM server
- `BITBUCKET_CLIENT`: Alternative to GITHUB_TOKEN, same value as DRONE_BITBUCKET_CLIENT.
- `BITBUCKET_SECRET`: Alternative to GITHUB_TOKEN, same value as DRONE_BITBUCKET_SECRET.

If `PLUGIN_CONCAT` is not set, the first `.drone.yml` will be used.

Expand Down
31 changes: 20 additions & 11 deletions cmd/drone-tree-config/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,17 @@ import (

type (
spec struct {
Concat bool `envconfig:"PLUGIN_CONCAT"`
MaxDepth int `envconfig:"PLUGIN_MAXDEPTH" default:"2"`
Fallback bool `envconfig:"PLUGIN_FALLBACK"`
Debug bool `envconfig:"PLUGIN_DEBUG"`
Address string `envconfig:"PLUGIN_ADDRESS" default:":3000"`
Secret string `envconfig:"PLUGIN_SECRET"`
Token string `envconfig:"GITHUB_TOKEN"`
Server string `envconfig:"GITHUB_SERVER"`
Concat bool `envconfig:"PLUGIN_CONCAT"`
MaxDepth int `envconfig:"PLUGIN_MAXDEPTH" default:"2"`
Fallback bool `envconfig:"PLUGIN_FALLBACK"`
Debug bool `envconfig:"PLUGIN_DEBUG"`
Address string `envconfig:"PLUGIN_ADDRESS" default:":3000"`
Secret string `envconfig:"PLUGIN_SECRET"`
GitHubToken string `envconfig:"GITHUB_TOKEN"`
AuthServer string `envconfig:"AUTH_SERVER"`
Server string `envconfig:"SERVER"`
BitBucketClient string `envconfig:"BITBUCKET_CLIENT"`
BitBucketSecret string `envconfig:"BITBUCKET_SECRET"`
}
)

Expand All @@ -35,17 +38,23 @@ func main() {
if spec.Secret == "" {
logrus.Fatalln("missing secret key")
}
if spec.Token == "" {
logrus.Warnln("missing github token")
if spec.GitHubToken == "" && (spec.BitBucketClient == "" || spec.BitBucketSecret == "") {
logrus.Warnln("missing SCM credentials, e.g. GitHub token")
}
if spec.Address == "" {
spec.Address = ":3000"
}
if spec.AuthServer == "" {
spec.AuthServer = spec.Server
}

handler := config.Handler(
plugin.New(
spec.AuthServer,
spec.Server,
spec.Token,
spec.GitHubToken,
spec.BitBucketClient,
spec.BitBucketSecret,
spec.Concat,
spec.Fallback,
spec.MaxDepth,
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/google/uuid v1.1.1
github.com/kelseyhightower/envconfig v1.3.0
github.com/sirupsen/logrus v1.4.1
github.com/wbrefvem/go-bitbucket v0.0.0-20190128183802-fc08fd046abb
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 // indirect
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a
golang.org/x/sync v0.0.0-20190423024810-112230192c58 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/wbrefvem/go-bitbucket v0.0.0-20190128183802-fc08fd046abb h1:KrmaSo+FHWBt1H652w/uerwzKvQqh4H7Jgyxm4hz2BQ=
github.com/wbrefvem/go-bitbucket v0.0.0-20190128183802-fc08fd046abb/go.mod h1:Z91j2jYBApRjJ0zlXDCxPrrZR8ohkkd4g0n+Hqs1w0Q=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e h1:bRhVy7zSSasaqNksaRZiA5EEI+Ei4I1nO5Jh72wfHlg=
Expand Down
136 changes: 64 additions & 72 deletions plugin/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,40 @@ import (
"strconv"
"strings"

"github.com/bitsbeats/drone-tree-config/plugin/scm_clients"
"github.com/drone/drone-go/drone"
"github.com/drone/drone-go/plugin/config"

"github.com/google/go-github/github"
"github.com/google/uuid"
"github.com/sirupsen/logrus"
"golang.org/x/oauth2"
"gopkg.in/yaml.v2"
)

// New creates a drone plugin
func New(server, token string, concat bool, fallback bool, maxDepth int) config.Plugin {
return &plugin{
server: server,
token: token,
concat: concat,
fallback: fallback,
maxDepth: maxDepth,
func New(authServer string, server string, gitHubToken string, bitBucketClient string, bitBucketSecret string,
concat bool, fallback bool, maxDepth int) config.Plugin {
return &Plugin{
authServer: authServer,
server: server,
gitHubToken: gitHubToken,
bitBucketClient: bitBucketClient,
bitBucketSecret: bitBucketSecret,
concat: concat,
fallback: fallback,
maxDepth: maxDepth,
}
}

type (
plugin struct {
server string
token string
concat bool
fallback bool
maxDepth int
Plugin struct {
authServer string
server string
gitHubToken string
bitBucketClient string
bitBucketSecret string
concat bool
fallback bool
maxDepth int
}

droneConfig struct {
Expand All @@ -47,35 +53,38 @@ type (
request struct {
*config.Request
UUID uuid.UUID
Client *github.Client
Client scm_clients.ScmClient
}
)

var dedupRegex = regexp.MustCompile(`(?ms)(---[\s]*){2,}`)

// Find is called by dorne
func (p *plugin) Find(ctx context.Context, droneRequest *config.Request) (*drone.Config, error) {
uuid := uuid.New()
logrus.Infof("%s %s/%s started", uuid, droneRequest.Repo.Namespace, droneRequest.Repo.Name)
defer logrus.Infof("%s finished", uuid)

// connect to github
trans := oauth2.NewClient(ctx, oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: p.token},
))
var client *github.Client
if p.server == "" {
client = github.NewClient(trans)
func (p *Plugin) NewScmClient(uuid uuid.UUID, repo drone.Repo, ctx context.Context) scm_clients.ScmClient {
var scmClient scm_clients.ScmClient
var err error
if p.gitHubToken != "" {
scmClient, err = scm_clients.NewGitHubClient(uuid, p.server, p.gitHubToken, repo, ctx)
} else if p.bitBucketClient != "" {
scmClient, err = scm_clients.NewBitBucketClient(uuid, p.authServer, p.server, p.bitBucketClient, p.bitBucketSecret, repo)
} else {
var err error
client, err = github.NewEnterpriseClient(p.server, p.server, trans)
if err != nil {
logrus.Errorf("%s Unable to connect to Github: '%v'", uuid, err)
return nil, err
}
err = fmt.Errorf("no SCM credentials specified")
}
if err != nil {
logrus.Errorf("Unable to connect to SCM server.")
}
return scmClient
}

// Find is called by drone
func (p *Plugin) Find(ctx context.Context, droneRequest *config.Request) (*drone.Config, error) {
someUuid := uuid.New()
logrus.Infof("%s %s/%s started", someUuid, droneRequest.Repo.Namespace, droneRequest.Repo.Name)
defer logrus.Infof("%s finished", someUuid)

// connect to scm
client := p.NewScmClient(someUuid, droneRequest.Repo, ctx)

req := request{droneRequest, uuid, client}
req := request{droneRequest, someUuid, client}

// get changed files
changedFiles, err := p.getGithubChanges(ctx, &req)
Expand All @@ -86,7 +95,7 @@ func (p *plugin) Find(ctx context.Context, droneRequest *config.Request) (*drone
// get drone.yml for changed files or all of them if no changes/cron
configData := ""
if changedFiles != nil {
configData, err = p.getGithubConfigData(ctx, &req, changedFiles)
configData, err = p.getConfigDataForChanges(ctx, &req, changedFiles)
} else if req.Build.Trigger == "@cron" {
logrus.Warnf("%s @cron, rebuilding all", req.UUID)
configData, err = p.getAllConfigData(ctx, &req, "/", 0)
Expand All @@ -111,7 +120,7 @@ func (p *plugin) Find(ctx context.Context, droneRequest *config.Request) (*drone
}

// getGithubChanges tries to get a list of changed files from github
func (p *plugin) getGithubChanges(ctx context.Context, req *request) ([]string, error) {
func (p *Plugin) getGithubChanges(ctx context.Context, req *request) ([]string, error) {
var changedFiles []string

if req.Build.Trigger == "@cron" {
Expand All @@ -124,29 +133,22 @@ func (p *plugin) getGithubChanges(ctx context.Context, req *request) ([]string,
logrus.Errorf("%s unable to get pull request id %v", req.UUID, err)
return nil, err
}
opts := github.ListOptions{}
files, _, err := req.Client.PullRequests.ListFiles(ctx, req.Repo.Namespace, req.Repo.Name, pullRequestID, &opts)
changedFiles, err = req.Client.ChangedFilesInPullRequest(ctx, pullRequestID)
if err != nil {
logrus.Errorf("%s unable to fetch diff for Pull request %v", req.UUID, err)
return nil, err
}
for _, file := range files {
changedFiles = append(changedFiles, *file.Filename)
}
} else {
// use diff to get changed files
before := req.Build.Before
if before == "0000000000000000000000000000000000000000" || before == "" {
before = fmt.Sprintf("%s~1", req.Build.After)
}
changes, _, err := req.Client.Repositories.CompareCommits(ctx, req.Repo.Namespace, req.Repo.Name, before, req.Build.After)
var err error
changedFiles, err = req.Client.ChangedFilesInDiff(ctx, before, req.Build.After)
if err != nil {
logrus.Errorf("%s unable to fetch diff: '%v'", req.UUID, err)
return nil, err
}
for _, file := range changes.Files {
changedFiles = append(changedFiles, *file.Filename)
}
}

if len(changedFiles) > 0 {
Expand All @@ -159,21 +161,13 @@ func (p *plugin) getGithubChanges(ctx context.Context, req *request) ([]string,
}

// getGithubFile downloads a file from github
func (p *plugin) getGithubFile(ctx context.Context, req *request, file string) (content string, err error) {
func (p *Plugin) getGithubFile(ctx context.Context, req *request, file string) (content string, err error) {
logrus.Debugf("%s checking %s/%s %s", req.UUID, req.Repo.Namespace, req.Repo.Name, file)
ref := github.RepositoryContentGetOptions{Ref: req.Build.After}
data, _, _, err := req.Client.Repositories.GetContents(ctx, req.Repo.Namespace, req.Repo.Name, file, &ref)
if data == nil {
err = fmt.Errorf("failed to get %s: is not a file", file)
}
if err != nil {
return "", err
}
return data.GetContent()
return req.Client.GetFileContents(ctx, file, req.Build.After)
}

// getGithubDroneConfig downloads a drone config and validates it
func (p *plugin) getGithubDroneConfig(ctx context.Context, req *request, file string) (configData string, critical bool, err error) {
func (p *Plugin) getGithubDroneConfig(ctx context.Context, req *request, file string) (configData string, critical bool, err error) {
fileContent, err := p.getGithubFile(ctx, req, file)
if err != nil {
logrus.Debugf("%s skipping: unable to load file: %s %v", req.UUID, file, err)
Expand All @@ -196,8 +190,8 @@ func (p *plugin) getGithubDroneConfig(ctx context.Context, req *request, file st
return fileContent, false, nil
}

// getGithubConfigData scans a repository based on the changed files
func (p *plugin) getGithubConfigData(ctx context.Context, req *request, changedFiles []string) (configData string, err error) {
// getConfigDataForChanges scans a repository based on the changed files
func (p *Plugin) getConfigDataForChanges(ctx context.Context, req *request, changedFiles []string) (configData string, err error) {
// collect drone.yml files
configData = ""
cache := map[string]bool{}
Expand Down Expand Up @@ -241,10 +235,9 @@ func (p *plugin) getGithubConfigData(ctx context.Context, req *request, changedF
return configData, nil
}

// getAllConfigData searches for all or fist 'drone.yml' in the repo
func (p *plugin) getAllConfigData(ctx context.Context, req *request, dir string, depth int) (configData string, err error) {
ref := github.RepositoryContentGetOptions{Ref: req.Build.After}
_, ls, _, err := req.Client.Repositories.GetContents(ctx, req.Repo.Namespace, req.Repo.Name, dir, &ref)
// getAllConfigData searches for all or first 'drone.yml' in the repo
func (p *Plugin) getAllConfigData(ctx context.Context, req *request, dir string, depth int) (configData string, err error) {
ls, err := req.Client.GetFileListing(ctx, dir, req.Build.After)
if err != nil {
return "", err
}
Expand All @@ -255,15 +248,15 @@ func (p *plugin) getAllConfigData(ctx context.Context, req *request, dir string,
}
depth += 1

// check recursivly for drone.yml
// check recursively for drone.yml
configData = ""
for _, f := range ls {
var fileContent string
if *f.Type == "dir" {
fileContent, _ = p.getAllConfigData(ctx, req, *f.Path, depth)
} else if *f.Type == "file" && *f.Name == req.Repo.Config {
if f.Type == "dir" {
fileContent, _ = p.getAllConfigData(ctx, req, f.Path, depth)
} else if f.Type == "file" && f.Name == req.Repo.Config {
var critical bool
fileContent, critical, err = p.getGithubDroneConfig(ctx, req, *f.Path)
fileContent, critical, err = p.getGithubDroneConfig(ctx, req, f.Path)
if critical {
return "", err
}
Expand All @@ -277,12 +270,11 @@ func (p *plugin) getAllConfigData(ctx context.Context, req *request, dir string,
}

return configData, nil

}

// droneConfigAppend concats multiple 'drone.yml's to a multi-machine pipeline
// see https://docs.drone.io/user-guide/pipeline/multi-machine/
func (p *plugin) droneConfigAppend(droneConfig string, appends ...string) string {
func (p *Plugin) droneConfigAppend(droneConfig string, appends ...string) string {
for _, a := range appends {
a = strings.Trim(a, " \n")
if a != "" {
Expand Down
Loading

0 comments on commit 3ca1827

Please sign in to comment.