Skip to content

Commit

Permalink
Merge pull request #1158 from runatlantis/skip-clone
Browse files Browse the repository at this point in the history
Skip cloning PR repository in case of no projects were changed
  • Loading branch information
lkysow authored Aug 18, 2020
2 parents 49edc7d + cbe0b18 commit 0a67abb
Show file tree
Hide file tree
Showing 24 changed files with 423 additions and 103 deletions.
5 changes: 5 additions & 0 deletions cmd/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ const (
SilenceAllowlistErrorsFlag = "silence-allowlist-errors"
// SilenceWhitelistErrorsFlag is deprecated for SilenceAllowlistErrorsFlag.
SilenceWhitelistErrorsFlag = "silence-whitelist-errors"
SkipCloneNoChanges = "skip-clone-no-changes"
SlackTokenFlag = "slack-token"
SSLCertFileFlag = "ssl-cert-file"
SSLKeyFileFlag = "ssl-key-file"
Expand Down Expand Up @@ -319,6 +320,10 @@ var boolFlags = map[string]boolFlag{
" This writes secrets to disk and should only be enabled in a secure environment.",
defaultValue: false,
},
SkipCloneNoChanges: {
description: "Skips cloning the PR repo if there are no projects were changed in the PR.",
defaultValue: false,
},
}
var intFlags = map[string]intFlag{
PortFlag: {
Expand Down
1 change: 1 addition & 0 deletions cmd/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ var testFlags = map[string]interface{}{
SilenceForkPRErrorsFlag: true,
SilenceAllowlistErrorsFlag: true,
SilenceVCSStatusNoPlans: true,
SkipCloneNoChanges: true,
SlackTokenFlag: "slack-token",
SSLCertFileFlag: "cert-file",
SSLKeyFileFlag: "key-file",
Expand Down
1 change: 1 addition & 0 deletions server/events/matchers/models_pullrequest.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions server/events/matchers/models_repo.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions server/events/matchers/ptr_to_logging_simplelogger.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions server/events/mock_workingdir_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

60 changes: 44 additions & 16 deletions server/events/project_command_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,15 @@ type ProjectCommandBuilder interface {
// This class combines the data from the comment and any atlantis.yaml file or
// Atlantis server config and then generates a set of contexts.
type DefaultProjectCommandBuilder struct {
ParserValidator *yaml.ParserValidator
ProjectFinder ProjectFinder
VCSClient vcs.Client
WorkingDir WorkingDir
WorkingDirLocker WorkingDirLocker
GlobalCfg valid.GlobalCfg
PendingPlanFinder *DefaultPendingPlanFinder
CommentBuilder CommentBuilder
ParserValidator *yaml.ParserValidator
ProjectFinder ProjectFinder
VCSClient vcs.Client
WorkingDir WorkingDir
WorkingDirLocker WorkingDirLocker
GlobalCfg valid.GlobalCfg
PendingPlanFinder *DefaultPendingPlanFinder
CommentBuilder CommentBuilder
SkipCloneNoChanges bool
}

// See ProjectCommandBuilder.BuildAutoplanCommands.
Expand Down Expand Up @@ -101,8 +102,43 @@ func (p *DefaultProjectCommandBuilder) BuildApplyCommands(ctx *CommandContext, c
// buildPlanAllCommands builds plan contexts for all projects we determine were
// modified in this ctx.
func (p *DefaultProjectCommandBuilder) buildPlanAllCommands(ctx *CommandContext, commentFlags []string, verbose bool) ([]models.ProjectCommandContext, error) {
// We'll need the list of modified files.
modifiedFiles, err := p.VCSClient.GetModifiedFiles(ctx.BaseRepo, ctx.Pull)
if err != nil {
return nil, err
}
ctx.Log.Debug("%d files were modified in this pull request", len(modifiedFiles))

if p.SkipCloneNoChanges && p.VCSClient.SupportsSingleFileDownload(ctx.BaseRepo) {
hasRepoCfg, repoCfgData, err := p.VCSClient.DownloadRepoConfigFile(ctx.Pull)
if err != nil {
return nil, errors.Wrapf(err, "downloading %s", yaml.AtlantisYAMLFilename)
}

if hasRepoCfg {
repoCfg, err := p.ParserValidator.ParseRepoCfgData(repoCfgData, p.GlobalCfg, ctx.BaseRepo.ID())
if err != nil {
return nil, errors.Wrapf(err, "parsing %s", yaml.AtlantisYAMLFilename)
}
ctx.Log.Info("successfully parsed remote %s file", yaml.AtlantisYAMLFilename)
matchingProjects, err := p.ProjectFinder.DetermineProjectsViaConfig(ctx.Log, modifiedFiles, repoCfg, "")
if err != nil {
return nil, err
}
ctx.Log.Info("%d projects are changed on MR %q based on their when_modified config", len(matchingProjects), ctx.Pull.Num)
if len(matchingProjects) == 0 {
ctx.Log.Info("skipping repo clone since no project was modified")
return []models.ProjectCommandContext{}, nil
}
// NOTE: We discard this work here and end up doing it again after
// cloning to ensure all the return values are set properly with
// the actual clone directory.
}
}

// Need to lock the workspace we're about to clone to.
workspace := DefaultWorkspace

unlockFn, err := p.WorkingDirLocker.TryLock(ctx.BaseRepo.FullName, ctx.Pull.Num, workspace)
if err != nil {
ctx.Log.Warn("workspace was locked")
Expand All @@ -111,18 +147,10 @@ func (p *DefaultProjectCommandBuilder) buildPlanAllCommands(ctx *CommandContext,
ctx.Log.Debug("got workspace lock")
defer unlockFn()

// We'll need the list of modified files.
modifiedFiles, err := p.VCSClient.GetModifiedFiles(ctx.BaseRepo, ctx.Pull)
if err != nil {
return nil, err
}
ctx.Log.Debug("%d files were modified in this pull request", len(modifiedFiles))

repoDir, _, err := p.WorkingDir.Clone(ctx.Log, ctx.BaseRepo, ctx.HeadRepo, ctx.Pull, workspace)
if err != nil {
return nil, err
}

// Parse config file if it exists.
hasRepoCfg, err := p.ParserValidator.HasRepoCfg(repoDir)
if err != nil {
Expand Down
17 changes: 9 additions & 8 deletions server/events/project_command_builder_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -563,14 +563,15 @@ projects:
}

builder := &DefaultProjectCommandBuilder{
WorkingDirLocker: NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
ParserValidator: parser,
VCSClient: vcsClient,
ProjectFinder: &DefaultProjectFinder{},
PendingPlanFinder: &DefaultPendingPlanFinder{},
CommentBuilder: &CommentParser{},
GlobalCfg: globalCfg,
WorkingDirLocker: NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
ParserValidator: parser,
VCSClient: vcsClient,
ProjectFinder: &DefaultProjectFinder{},
PendingPlanFinder: &DefaultPendingPlanFinder{},
CommentBuilder: &CommentParser{},
GlobalCfg: globalCfg,
SkipCloneNoChanges: false,
}

// We run a test for each type of command.
Expand Down
149 changes: 98 additions & 51 deletions server/events/project_command_builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,14 +135,15 @@ projects:
}

builder := &events.DefaultProjectCommandBuilder{
WorkingDirLocker: events.NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
ParserValidator: &yaml.ParserValidator{},
VCSClient: vcsClient,
ProjectFinder: &events.DefaultProjectFinder{},
PendingPlanFinder: &events.DefaultPendingPlanFinder{},
CommentBuilder: &events.CommentParser{},
GlobalCfg: valid.NewGlobalCfg(false, false, false),
WorkingDirLocker: events.NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
ParserValidator: &yaml.ParserValidator{},
VCSClient: vcsClient,
ProjectFinder: &events.DefaultProjectFinder{},
PendingPlanFinder: &events.DefaultPendingPlanFinder{},
CommentBuilder: &events.CommentParser{},
GlobalCfg: valid.NewGlobalCfg(false, false, false),
SkipCloneNoChanges: false,
}

ctxs, err := builder.BuildAutoplanCommands(&events.CommandContext{
Expand Down Expand Up @@ -358,13 +359,14 @@ projects:
}

builder := &events.DefaultProjectCommandBuilder{
WorkingDirLocker: events.NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
ParserValidator: &yaml.ParserValidator{},
VCSClient: vcsClient,
ProjectFinder: &events.DefaultProjectFinder{},
CommentBuilder: &events.CommentParser{},
GlobalCfg: valid.NewGlobalCfg(true, false, false),
WorkingDirLocker: events.NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
ParserValidator: &yaml.ParserValidator{},
VCSClient: vcsClient,
ProjectFinder: &events.DefaultProjectFinder{},
CommentBuilder: &events.CommentParser{},
GlobalCfg: valid.NewGlobalCfg(true, false, false),
SkipCloneNoChanges: false,
}

var actCtxs []models.ProjectCommandContext
Expand Down Expand Up @@ -491,13 +493,14 @@ projects:
}

builder := &events.DefaultProjectCommandBuilder{
WorkingDirLocker: events.NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
ParserValidator: &yaml.ParserValidator{},
VCSClient: vcsClient,
ProjectFinder: &events.DefaultProjectFinder{},
CommentBuilder: &events.CommentParser{},
GlobalCfg: valid.NewGlobalCfg(true, false, false),
WorkingDirLocker: events.NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
ParserValidator: &yaml.ParserValidator{},
VCSClient: vcsClient,
ProjectFinder: &events.DefaultProjectFinder{},
CommentBuilder: &events.CommentParser{},
GlobalCfg: valid.NewGlobalCfg(true, false, false),
SkipCloneNoChanges: false,
}

ctxs, err := builder.BuildPlanCommands(
Expand Down Expand Up @@ -562,14 +565,15 @@ func TestDefaultProjectCommandBuilder_BuildMultiApply(t *testing.T) {
ThenReturn(tmpDir, nil)

builder := &events.DefaultProjectCommandBuilder{
WorkingDirLocker: events.NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
ParserValidator: &yaml.ParserValidator{},
VCSClient: nil,
ProjectFinder: &events.DefaultProjectFinder{},
PendingPlanFinder: &events.DefaultPendingPlanFinder{},
CommentBuilder: &events.CommentParser{},
GlobalCfg: valid.NewGlobalCfg(false, false, false),
WorkingDirLocker: events.NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
ParserValidator: &yaml.ParserValidator{},
VCSClient: nil,
ProjectFinder: &events.DefaultProjectFinder{},
PendingPlanFinder: &events.DefaultPendingPlanFinder{},
CommentBuilder: &events.CommentParser{},
GlobalCfg: valid.NewGlobalCfg(false, false, false),
SkipCloneNoChanges: false,
}

ctxs, err := builder.BuildApplyCommands(
Expand Down Expand Up @@ -630,13 +634,14 @@ projects:
AnyString())).ThenReturn(repoDir, nil)

builder := &events.DefaultProjectCommandBuilder{
WorkingDirLocker: events.NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
ParserValidator: &yaml.ParserValidator{},
VCSClient: nil,
ProjectFinder: &events.DefaultProjectFinder{},
CommentBuilder: &events.CommentParser{},
GlobalCfg: valid.NewGlobalCfg(true, false, false),
WorkingDirLocker: events.NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
ParserValidator: &yaml.ParserValidator{},
VCSClient: nil,
ProjectFinder: &events.DefaultProjectFinder{},
CommentBuilder: &events.CommentParser{},
GlobalCfg: valid.NewGlobalCfg(true, false, false),
SkipCloneNoChanges: false,
}

ctx := &events.CommandContext{
Expand Down Expand Up @@ -692,13 +697,14 @@ func TestDefaultProjectCommandBuilder_EscapeArgs(t *testing.T) {
When(vcsClient.GetModifiedFiles(matchers.AnyModelsRepo(), matchers.AnyModelsPullRequest())).ThenReturn([]string{"main.tf"}, nil)

builder := &events.DefaultProjectCommandBuilder{
WorkingDirLocker: events.NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
ParserValidator: &yaml.ParserValidator{},
VCSClient: vcsClient,
ProjectFinder: &events.DefaultProjectFinder{},
CommentBuilder: &events.CommentParser{},
GlobalCfg: valid.NewGlobalCfg(true, false, false),
WorkingDirLocker: events.NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
ParserValidator: &yaml.ParserValidator{},
VCSClient: vcsClient,
ProjectFinder: &events.DefaultProjectFinder{},
CommentBuilder: &events.CommentParser{},
GlobalCfg: valid.NewGlobalCfg(true, false, false),
SkipCloneNoChanges: false,
}

var actCtxs []models.ProjectCommandContext
Expand Down Expand Up @@ -856,13 +862,14 @@ projects:
AnyString())).ThenReturn(tmpDir, nil)

builder := &events.DefaultProjectCommandBuilder{
WorkingDirLocker: events.NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
VCSClient: vcsClient,
ParserValidator: &yaml.ParserValidator{},
ProjectFinder: &events.DefaultProjectFinder{},
CommentBuilder: &events.CommentParser{},
GlobalCfg: valid.NewGlobalCfg(true, false, false),
WorkingDirLocker: events.NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
VCSClient: vcsClient,
ParserValidator: &yaml.ParserValidator{},
ProjectFinder: &events.DefaultProjectFinder{},
CommentBuilder: &events.CommentParser{},
GlobalCfg: valid.NewGlobalCfg(true, false, false),
SkipCloneNoChanges: false,
}

actCtxs, err := builder.BuildPlanCommands(
Expand All @@ -887,3 +894,43 @@ projects:
})
}
}

// Test that we don't clone the repo if there were no changes based on the atlantis.yaml file.
func TestDefaultProjectCommandBuilder_SkipCloneNoChanges(t *testing.T) {
atlantisYAML := `
version: 3
projects:
- dir: dir1`

RegisterMockTestingT(t)
vcsClient := vcsmocks.NewMockClient()
When(vcsClient.GetModifiedFiles(matchers.AnyModelsRepo(), matchers.AnyModelsPullRequest())).ThenReturn([]string{"main.tf"}, nil)
When(vcsClient.SupportsSingleFileDownload(matchers.AnyModelsRepo())).ThenReturn(true)
When(vcsClient.DownloadRepoConfigFile(matchers.AnyModelsPullRequest())).ThenReturn(true, []byte(atlantisYAML), nil)
workingDir := mocks.NewMockWorkingDir()

builder := &events.DefaultProjectCommandBuilder{
WorkingDirLocker: events.NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
ParserValidator: &yaml.ParserValidator{},
VCSClient: vcsClient,
ProjectFinder: &events.DefaultProjectFinder{},
CommentBuilder: &events.CommentParser{},
GlobalCfg: valid.NewGlobalCfg(true, false, false),
SkipCloneNoChanges: true,
}

var actCtxs []models.ProjectCommandContext
var err error
actCtxs, err = builder.BuildAutoplanCommands(&events.CommandContext{
BaseRepo: models.Repo{},
HeadRepo: models.Repo{},
Pull: models.PullRequest{},
User: models.User{},
Log: nil,
PullMergeable: true,
})
Ok(t, err)
Equals(t, 0, len(actCtxs))
workingDir.VerifyWasCalled(Never()).Clone(matchers.AnyPtrToLoggingSimpleLogger(), matchers.AnyModelsRepo(), matchers.AnyModelsRepo(), matchers.AnyModelsPullRequest(), AnyString())
}
Loading

0 comments on commit 0a67abb

Please sign in to comment.