Skip to content

Commit

Permalink
Add support for multiple config file paths (#44)
Browse files Browse the repository at this point in the history
Based on server configuration, the application will now look for the
repository configuration file in multiple paths until it finds one. If
no paths are specified, the existing .bulldozer.yml is used.

This commit makes the minimal amount of change necessary to get this
working. Ideally, there would be more refactoring to standardize config
passing and client construction, but I'm not ready to do that yet.
  • Loading branch information
bluekeyes authored Jul 12, 2018
1 parent 48398ec commit c80162a
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 23 deletions.
70 changes: 56 additions & 14 deletions github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ const (

var (
AcceptedPermLevels = []string{"write", "admin"}
DefaultConfigPaths = []string{".bulldozer.yml"}
)

type UpdateStrategy string
Expand All @@ -70,11 +71,21 @@ const (
UpdateStrategyAlways UpdateStrategy = "always"
)

type Option func(c *Client)

func WithConfigPaths(paths []string) Option {
return func(c *Client) {
c.configPaths = paths
}
}

type Client struct {
Logger *logrus.Entry
Ctx context.Context

*github.Client

configPaths []string
}

type BulldozerFile struct {
Expand All @@ -84,21 +95,35 @@ type BulldozerFile struct {
UpdateStrategy UpdateStrategy `yaml:"updateStrategy"`
}

func FromToken(c echo.Context, token string) *Client {
func FromToken(c echo.Context, token string, opts ...Option) *Client {
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: token},
)
tc := oauth2.NewClient(context.TODO(), ts)

client := github.NewClient(tc)
gh := github.NewClient(tc)

gh.BaseURL, _ = url.Parse(config.Instance.Github.APIURL)
gh.UserAgent = "bulldozer/" + version.Version()

client.BaseURL, _ = url.Parse(config.Instance.Github.APIURL)
client.UserAgent = "bulldozer/" + version.Version()
client := &Client{
Logger: log.FromContext(c),
Ctx: context.TODO(),
Client: gh,
}

return &Client{log.FromContext(c), context.TODO(), client}
for _, opt := range opts {
opt(client)
}

if len(client.configPaths) == 0 {
client.configPaths = DefaultConfigPaths
}

return client
}

func FromAuthHeader(c echo.Context, authHeader string) (*Client, error) {
func FromAuthHeader(c echo.Context, authHeader string, opts ...Option) (*Client, error) {
if authHeader == "" {
return nil, errors.New("authorization header not present")
}
Expand All @@ -109,18 +134,13 @@ func FromAuthHeader(c echo.Context, authHeader string) (*Client, error) {
}

token := parts[1]
return FromToken(c, token), nil
return FromToken(c, token, opts...), nil
}

func (client *Client) ConfigFile(repo *github.Repository, ref string) (*BulldozerFile, error) {
owner := repo.Owner.GetLogin()
name := repo.GetName()

repositoryContent, _, _, err := client.Repositories.GetContents(client.Ctx, owner, name, ".bulldozer.yml", &github.RepositoryContentGetOptions{
Ref: ref,
})
repositoryContent, err := client.findConfigFile(repo, ref)
if err != nil {
return nil, errors.Wrapf(err, "cannot get .bulldozer.yml for %s on ref %s", repo.GetFullName(), ref)
return nil, err
}

content, err := repositoryContent.GetContent()
Expand Down Expand Up @@ -155,6 +175,28 @@ func (client *Client) ConfigFile(repo *github.Repository, ref string) (*Bulldoze
return &bulldozerFile, nil
}

func (client *Client) findConfigFile(repo *github.Repository, ref string) (*github.RepositoryContent, error) {
owner := repo.Owner.GetLogin()
name := repo.GetName()

opts := github.RepositoryContentGetOptions{
Ref: ref,
}

for _, p := range client.configPaths {
content, _, _, err := client.Repositories.GetContents(client.Ctx, owner, name, p, &opts)
if err != nil {
if rerr, ok := err.(*github.ErrorResponse); ok && rerr.Response.StatusCode == http.StatusNotFound {
continue
}
return nil, errors.Wrapf(err, "cannot get %s for %s on ref %s", p, repo.GetFullName(), ref)
}
return content, nil
}

return nil, errors.Errorf("no configuration found for %s on ref %s", repo.GetFullName(), ref)
}

func (client *Client) MergeMethod(branch *github.PullRequestBranch) (string, error) {
logger := client.Logger

Expand Down
38 changes: 37 additions & 1 deletion github/github_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,14 @@ func setup() {
server = httptest.NewServer(mux)

logger := logrus.New().WithField("deliveryID", "randomDelivery")
client = &Client{logger, context.TODO(), github.NewClient(nil)}
client = &Client{
Logger: logger,
Ctx: context.TODO(),
Client: github.NewClient(nil),

configPaths: []string{".bulldozer.yml", ".palantir/bulldozer.yml"},
}

url, _ := url.Parse(server.URL + "/")
client.BaseURL = url
client.UploadURL = url
Expand Down Expand Up @@ -529,6 +536,35 @@ func TestConfigFileSuccess(t *testing.T) {
}
}

func TestConfigFileAlternatePathSuccess(t *testing.T) {
setup()
defer teardown()

mux.HandleFunc("/repos/o/r/contents/.palantir/bulldozer.yml", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `{
"type": "file",
"encoding": "base64",
"content": "bW9kZTogd2hpdGVsaXN0CnN0cmF0ZWd5OiBzcXVhc2gKZGVsZXRlQWZ0ZXJNZXJnZTogdHJ1ZQp1cGRhdGVTdHJhdGVneTogbGFiZWwK",
"name": ".palantir/bulldozer.yml",
"path": ".palantir/bulldozer.yml"
}`)
})

want := &BulldozerFile{
Mode: "whitelist",
MergeStrategy: "squash",
UpdateStrategy: UpdateStrategyLabel,
DeleteAfterMerge: true,
}
configFile, err := client.ConfigFile(fakeRepository("r"), "develop")
require.Nil(t, err)

if !reflect.DeepEqual(configFile, want) {
t.Errorf("ConfigFile returned %+v, want %+v", configFile, want)
}
}

func TestConfigFileInvalid(t *testing.T) {
setup()
defer teardown()
Expand Down
11 changes: 6 additions & 5 deletions server/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,12 @@ var (
)

type Startup struct {
Server Rest `yaml:"rest" validate:"required"`
Logging LoggingConfig `yaml:"logging" validate:"required,dive"`
Database *DatabaseConfig `yaml:"database" validate:"required,dive"`
Github *GithubConfig `yaml:"github" validate:"required,dive"`
AssetDir string `yaml:"assetDir" validate:"required"`
Server Rest `yaml:"rest" validate:"required"`
Logging LoggingConfig `yaml:"logging" validate:"required,dive"`
Database *DatabaseConfig `yaml:"database" validate:"required,dive"`
Github *GithubConfig `yaml:"github" validate:"required,dive"`
AssetDir string `yaml:"assetDir" validate:"required"`
ConfigPaths []string `yaml:"configPaths"`
}

type Rest struct {
Expand Down
4 changes: 2 additions & 2 deletions server/endpoints/hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import (
"github.com/palantir/bulldozer/server/config"
)

func Hook(db *sqlx.DB, secret string) echo.HandlerFunc {
func Hook(db *sqlx.DB, secret string, configPaths []string) echo.HandlerFunc {
return func(c echo.Context) error {
logger := log.FromContext(c)

Expand All @@ -56,7 +56,7 @@ func Hook(db *sqlx.DB, secret string) echo.HandlerFunc {
return errors.Wrapf(err, "cannot get user %s from database", dbRepo.EnabledBy)
}

ghClient := gh.FromToken(c, user.Token)
ghClient := gh.FromToken(c, user.Token, gh.WithConfigPaths(configPaths))

if !(result.Update || result.Merge) {
return c.String(http.StatusOK, "Not taking action")
Expand Down
2 changes: 1 addition & 1 deletion server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func registerEndpoints(startup *config.Startup, e *echo.Echo, db *sqlx.DB) {
e.POST("/api/repo/:owner/:name", endpoints.RepositoryEnable(db, startup.Github.WebHookURL, startup.Github.WebhookSecret))
e.DELETE("/api/repo/:owner/:name", endpoints.RepositoryDisable(db))

e.POST("/api/github/hook", endpoints.Hook(db, startup.Github.WebhookSecret))
e.POST("/api/github/hook", endpoints.Hook(db, startup.Github.WebhookSecret, startup.ConfigPaths))
e.GET("/api/auth/github/token", endpoints.Token(db))
}

Expand Down

0 comments on commit c80162a

Please sign in to comment.