Skip to content

Commit

Permalink
add --rebase-repo flag to support rebasing on master
Browse files Browse the repository at this point in the history
  • Loading branch information
sstarcher committed Dec 5, 2018
1 parent 284fa2e commit 6f95fb6
Show file tree
Hide file tree
Showing 11 changed files with 71 additions and 28 deletions.
6 changes: 6 additions & 0 deletions cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const (
PortFlag = "port"
RepoWhitelistFlag = "repo-whitelist"
RequireApprovalFlag = "require-approval"
RebaseRepo = "rebase-repo"
SilenceWhitelistErrorsFlag = "silence-whitelist-errors"
SSLCertFileFlag = "ssl-cert-file"
SSLKeyFileFlag = "ssl-key-file"
Expand Down Expand Up @@ -186,6 +187,11 @@ var boolFlags = []boolFlag{
description: "Require pull requests to be \"Approved\" before allowing the apply command to be run.",
defaultValue: false,
},
{
name: RebaseRepo,
description: "Prior to running any commands the pull request is rebased off of master.",
defaultValue: false,
},
{
name: SilenceWhitelistErrorsFlag,
description: "Silences the posting of whitelist error comments.",
Expand Down
5 changes: 3 additions & 2 deletions server/events/command_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type CommandContext struct {
HeadRepo models.Repo
Pull models.PullRequest
// User is the user that triggered this command.
User models.User
Log *logging.SimpleLogger
User models.User
Log *logging.SimpleLogger
RebaseRepo bool
}
23 changes: 13 additions & 10 deletions server/events/command_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,17 +69,19 @@ type DefaultCommandRunner struct {
AllowForkPRsFlag string
ProjectCommandBuilder ProjectCommandBuilder
ProjectCommandRunner ProjectCommandRunner
RebaseRepo bool
}

// RunAutoplanCommand runs plan when a pull request is opened or updated.
func (c *DefaultCommandRunner) RunAutoplanCommand(baseRepo models.Repo, headRepo models.Repo, pull models.PullRequest, user models.User) {
log := c.buildLogger(baseRepo.FullName, pull.Num)
ctx := &CommandContext{
User: user,
Log: log,
Pull: pull,
HeadRepo: headRepo,
BaseRepo: baseRepo,
User: user,
Log: log,
Pull: pull,
HeadRepo: headRepo,
BaseRepo: baseRepo,
RebaseRepo: c.RebaseRepo,
}
defer c.logPanics(ctx)
if !c.validateCtxAndComment(ctx) {
Expand Down Expand Up @@ -138,11 +140,12 @@ func (c *DefaultCommandRunner) RunCommentCommand(baseRepo models.Repo, maybeHead
return
}
ctx := &CommandContext{
User: user,
Log: log,
Pull: pull,
HeadRepo: headRepo,
BaseRepo: baseRepo,
User: user,
Log: log,
Pull: pull,
HeadRepo: headRepo,
BaseRepo: baseRepo,
RebaseRepo: c.RebaseRepo,
}
defer c.logPanics(ctx)
if !c.validateCtxAndComment(ctx) {
Expand Down
4 changes: 2 additions & 2 deletions server/events/mocks/mock_working_dir.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ func NewMockWorkingDir() *MockWorkingDir {
return &MockWorkingDir{fail: pegomock.GlobalFailHandler}
}

func (mock *MockWorkingDir) Clone(log *logging.SimpleLogger, baseRepo models.Repo, headRepo models.Repo, p models.PullRequest, workspace string) (string, error) {
params := []pegomock.Param{log, baseRepo, headRepo, p, workspace}
func (mock *MockWorkingDir) Clone(log *logging.SimpleLogger, baseRepo models.Repo, headRepo models.Repo, p models.PullRequest, rebase bool, workspace string) (string, error) {
params := []pegomock.Param{log, baseRepo, headRepo, p, rebase, workspace}
result := pegomock.GetGenericMockFrom(mock).Invoke("Clone", params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
var ret0 string
var ret1 error
Expand Down
2 changes: 2 additions & 0 deletions server/events/models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,8 @@ type ProjectCommandContext struct {
// ApplyCmd is the command that users should run to apply this plan. If
// this is an apply then this will be empty.
ApplyCmd string

RebaseRepo bool
}

// SplitRepoFullName splits a repo full name up into its owner and repo name
Expand Down
7 changes: 5 additions & 2 deletions server/events/project_command_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ func (p *DefaultProjectCommandBuilder) buildPlanAllCommands(ctx *CommandContext,
ctx.Log.Debug("got workspace lock")
defer unlockFn()

repoDir, err := p.WorkingDir.Clone(ctx.Log, ctx.BaseRepo, ctx.HeadRepo, ctx.Pull, workspace)
repoDir, err := p.WorkingDir.Clone(ctx.Log, ctx.BaseRepo, ctx.HeadRepo, ctx.Pull, ctx.RebaseRepo, workspace)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -143,6 +143,7 @@ func (p *DefaultProjectCommandBuilder) buildPlanAllCommands(ctx *CommandContext,
Verbose: verbose,
RePlanCmd: p.CommentBuilder.BuildPlanComment(mp.Path, DefaultWorkspace, "", commentFlags),
ApplyCmd: p.CommentBuilder.BuildApplyComment(mp.Path, DefaultWorkspace, ""),
RebaseRepo: ctx.RebaseRepo,
})
}
} else {
Expand Down Expand Up @@ -172,6 +173,7 @@ func (p *DefaultProjectCommandBuilder) buildPlanAllCommands(ctx *CommandContext,
Verbose: verbose,
RePlanCmd: p.CommentBuilder.BuildPlanComment(mp.Dir, mp.Workspace, mp.GetName(), commentFlags),
ApplyCmd: p.CommentBuilder.BuildApplyComment(mp.Dir, mp.Workspace, mp.GetName()),
RebaseRepo: ctx.RebaseRepo,
})
}
}
Expand All @@ -193,7 +195,7 @@ func (p *DefaultProjectCommandBuilder) buildProjectPlanCommand(ctx *CommandConte
defer unlockFn()

ctx.Log.Debug("cloning repository")
repoDir, err := p.WorkingDir.Clone(ctx.Log, ctx.BaseRepo, ctx.HeadRepo, ctx.Pull, workspace)
repoDir, err := p.WorkingDir.Clone(ctx.Log, ctx.BaseRepo, ctx.HeadRepo, ctx.Pull, ctx.RebaseRepo, workspace)
if err != nil {
return pcc, err
}
Expand Down Expand Up @@ -315,6 +317,7 @@ func (p *DefaultProjectCommandBuilder) buildProjectCommandCtx(ctx *CommandContex
GlobalConfig: globalCfg,
RePlanCmd: p.CommentBuilder.BuildPlanComment(repoRelDir, workspace, projectName, commentFlags),
ApplyCmd: p.CommentBuilder.BuildApplyComment(repoRelDir, workspace, projectName),
RebaseRepo: ctx.RebaseRepo,
}, nil
}

Expand Down
9 changes: 7 additions & 2 deletions server/events/project_command_builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ projects:
pull := models.PullRequest{}
logger := logging.NewNoopLogger()
workingDir := mocks.NewMockWorkingDir()
When(workingDir.Clone(logger, baseRepo, headRepo, pull, "default")).ThenReturn(tmpDir, nil)
When(workingDir.Clone(logger, baseRepo, headRepo, pull, false, "default")).ThenReturn(tmpDir, nil)
if c.AtlantisYAML != "" {
err := ioutil.WriteFile(filepath.Join(tmpDir, yaml.AtlantisYAMLFilename), []byte(c.AtlantisYAML), 0600)
Ok(t, err)
Expand Down Expand Up @@ -405,7 +405,7 @@ projects:
expWorkspace = "default"
}
if cmdName == events.PlanCommand {
When(workingDir.Clone(logger, baseRepo, headRepo, pull, expWorkspace)).ThenReturn(tmpDir, nil)
When(workingDir.Clone(logger, baseRepo, headRepo, pull, false, expWorkspace)).ThenReturn(tmpDir, nil)
} else {
When(workingDir.GetWorkingDir(baseRepo, pull, expWorkspace)).ThenReturn(tmpDir, nil)
}
Expand Down Expand Up @@ -487,6 +487,7 @@ func TestDefaultProjectCommandBuilder_BuildMultiPlanNoAtlantisYAML(t *testing.T)
matchers.AnyModelsRepo(),
matchers.AnyModelsRepo(),
matchers.AnyModelsPullRequest(),
AnyBool(),
AnyString())).ThenReturn(tmpDir, nil)
vcsClient := vcsmocks.NewMockClientProxy()
When(vcsClient.GetModifiedFiles(matchers.AnyModelsRepo(), matchers.AnyModelsPullRequest())).ThenReturn([]string{"project1/main.tf", "project2/main.tf"}, nil)
Expand Down Expand Up @@ -540,6 +541,7 @@ func TestDefaultProjectCommandBuilder_BuildMultiPlanNoAtlantisYAMLNoModified(t *
matchers.AnyModelsRepo(),
matchers.AnyModelsRepo(),
matchers.AnyModelsPullRequest(),
AnyBool(),
AnyString())).ThenReturn(tmpDir, nil)
vcsClient := vcsmocks.NewMockClientProxy()
When(vcsClient.GetModifiedFiles(matchers.AnyModelsRepo(), matchers.AnyModelsPullRequest())).ThenReturn([]string{}, nil)
Expand Down Expand Up @@ -610,6 +612,7 @@ projects:
matchers.AnyModelsRepo(),
matchers.AnyModelsRepo(),
matchers.AnyModelsPullRequest(),
AnyBool(),
AnyString())).ThenReturn(tmpDir, nil)
vcsClient := vcsmocks.NewMockClientProxy()
When(vcsClient.GetModifiedFiles(matchers.AnyModelsRepo(), matchers.AnyModelsPullRequest())).ThenReturn([]string{
Expand Down Expand Up @@ -674,6 +677,7 @@ projects:
matchers.AnyModelsRepo(),
matchers.AnyModelsRepo(),
matchers.AnyModelsPullRequest(),
AnyBool(),
AnyString())).ThenReturn(tmpDir, nil)
vcsClient := vcsmocks.NewMockClientProxy()
When(vcsClient.GetModifiedFiles(matchers.AnyModelsRepo(), matchers.AnyModelsPullRequest())).ThenReturn([]string{"main.tf"}, nil)
Expand Down Expand Up @@ -809,6 +813,7 @@ func TestDefaultProjectCommandBuilder_RepoConfigDisabled(t *testing.T) {
matchers.AnyModelsRepo(),
matchers.AnyModelsRepo(),
matchers.AnyModelsPullRequest(),
AnyBool(),
AnyString())).ThenReturn(repoDir, nil)
When(workingDir.GetWorkingDir(
matchers.AnyModelsRepo(),
Expand Down
6 changes: 4 additions & 2 deletions server/events/project_command_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,14 +135,16 @@ func (p *DefaultProjectCommandRunner) doPlan(ctx models.ProjectCommandContext) (
}
defer unlockFn()

// Clone is idempotent so okay to run even if the repo was already cloned.
repoDir, cloneErr := p.WorkingDir.Clone(ctx.Log, ctx.BaseRepo, ctx.HeadRepo, ctx.Pull, ctx.Workspace)
// Clone is idempotent when rebase is not true so okay to run even if the repo was already cloned.
ctx.Log.Err("WHAT IS REBASE %v", ctx.RebaseRepo)
repoDir, cloneErr := p.WorkingDir.Clone(ctx.Log, ctx.BaseRepo, ctx.HeadRepo, ctx.Pull, ctx.RebaseRepo, ctx.Workspace)
if cloneErr != nil {
if unlockErr := lockAttempt.UnlockFn(); unlockErr != nil {
ctx.Log.Err("error unlocking state after plan error: %v", unlockErr)
}
return nil, "", cloneErr
}

projAbsPath := filepath.Join(repoDir, ctx.RepoRelDir)

// Use default stage unless another workflow is defined in config
Expand Down
1 change: 1 addition & 0 deletions server/events/project_command_runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ func TestDefaultProjectCommandRunner_Plan(t *testing.T) {
matchers.AnyModelsRepo(),
matchers.AnyModelsRepo(),
matchers.AnyModelsPullRequest(),
AnyBool(),
AnyString(),
)).ThenReturn(repoDir, nil)
When(mockLocker.TryLock(
Expand Down
34 changes: 26 additions & 8 deletions server/events/working_dir.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const workingDirPrefix = "repos"
type WorkingDir interface {
// Clone git clones headRepo, checks out the branch and then returns the
// absolute path to the root of the cloned repo.
Clone(log *logging.SimpleLogger, baseRepo models.Repo, headRepo models.Repo, p models.PullRequest, workspace string) (string, error)
Clone(log *logging.SimpleLogger, baseRepo models.Repo, headRepo models.Repo, p models.PullRequest, rebase bool, workspace string) (string, error)
// GetWorkingDir returns the path to the workspace for this repo and pull.
// If workspace does not exist on disk, error will be of type os.IsNotExist.
GetWorkingDir(r models.Repo, p models.PullRequest, workspace string) (string, error)
Expand All @@ -60,6 +60,7 @@ func (w *FileWorkspace) Clone(
baseRepo models.Repo,
headRepo models.Repo,
p models.PullRequest,
rebase bool,
workspace string) (string, error) {
cloneDir := w.cloneDir(baseRepo, p, workspace)

Expand All @@ -72,27 +73,33 @@ func (w *FileWorkspace) Clone(
output, err := revParseCmd.CombinedOutput()
if err != nil {
log.Err("will re-clone repo, could not determine if was at correct commit: git rev-parse HEAD: %s: %s", err, string(output))
return w.forceClone(log, cloneDir, headRepo, p)
return w.forceClone(log, cloneDir, headRepo, p, rebase)
}
currCommit := strings.Trim(string(output), "\n")
// We're prefix matching here because BitBucket doesn't give us the full
// commit, only a 12 character prefix.
if strings.HasPrefix(currCommit, p.HeadCommit) {
log.Debug("repo is at correct commit %q so will not re-clone", p.HeadCommit)
return cloneDir, nil
if !rebase {
if strings.HasPrefix(currCommit, p.HeadCommit) {
log.Debug("repo is at correct commit %q so will not re-clone", p.HeadCommit)
return cloneDir, nil
}
log.Debug("repo was already cloned but is not at correct commit, wanted %q got %q", p.HeadCommit, currCommit)
} else {
log.Debug("rebase is specified so cloning")
}
log.Debug("repo was already cloned but is not at correct commit, wanted %q got %q", p.HeadCommit, currCommit)

// We'll fall through to re-clone.
}

// Otherwise we clone the repo.
return w.forceClone(log, cloneDir, headRepo, p)
return w.forceClone(log, cloneDir, headRepo, p, rebase)
}

func (w *FileWorkspace) forceClone(log *logging.SimpleLogger,
cloneDir string,
headRepo models.Repo,
p models.PullRequest) (string, error) {
p models.PullRequest,
rebase bool) (string, error) {

err := os.RemoveAll(cloneDir)
if err != nil {
Expand Down Expand Up @@ -122,6 +129,17 @@ func (w *FileWorkspace) forceClone(log *logging.SimpleLogger,
if err := checkoutCmd.Run(); err != nil {
return "", errors.Wrapf(err, "checking out branch %s", p.Branch)
}

if rebase {
// Rebase branch
log.Info("rebase branch onto master")
rebaseCmd := exec.Command("git", "rebase", "origin/master") // #nosec
rebaseCmd.Dir = cloneDir
if output, err := rebaseCmd.CombinedOutput(); err != nil {
return "", errors.Wrapf(err, "unable to rebase %s onto master %s", p.Branch, string(output))
}
}

return cloneDir, nil
}

Expand Down
2 changes: 2 additions & 0 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ type UserConfig struct {
GitlabWebhookSecret string `mapstructure:"gitlab-webhook-secret"`
LogLevel string `mapstructure:"log-level"`
Port int `mapstructure:"port"`
RebaseRepo bool `mapstructure:"rebase-repo"`
RepoWhitelist string `mapstructure:"repo-whitelist"`
// RequireApproval is whether to require pull request approval before
// allowing terraform apply's to be run.
Expand Down Expand Up @@ -263,6 +264,7 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) {
Logger: logger,
AllowForkPRs: userConfig.AllowForkPRs,
AllowForkPRsFlag: config.AllowForkPRsFlag,
RebaseRepo: userConfig.RebaseRepo,
ProjectCommandBuilder: &events.DefaultProjectCommandBuilder{
ParserValidator: &yaml.ParserValidator{},
ProjectFinder: &events.DefaultProjectFinder{},
Expand Down

0 comments on commit 6f95fb6

Please sign in to comment.