diff --git a/github/github.go b/github/github.go index 51e358857..5204a84a7 100644 --- a/github/github.go +++ b/github/github.go @@ -56,6 +56,7 @@ const ( var ( AcceptedPermLevels = []string{"write", "admin"} + DefaultConfigPaths = []string{".bulldozer.yml"} ) type UpdateStrategy string @@ -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 { @@ -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") } @@ -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() @@ -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 diff --git a/github/github_test.go b/github/github_test.go index bba618213..6f0767b6e 100644 --- a/github/github_test.go +++ b/github/github_test.go @@ -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 @@ -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() diff --git a/server/config/config.go b/server/config/config.go index 7c6f593fb..d628899a5 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -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 { diff --git a/server/endpoints/hook.go b/server/endpoints/hook.go index e8189c30b..db5643f47 100644 --- a/server/endpoints/hook.go +++ b/server/endpoints/hook.go @@ -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) @@ -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") diff --git a/server/server.go b/server/server.go index 8b6766b65..90600aa82 100644 --- a/server/server.go +++ b/server/server.go @@ -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)) }