feat: support atlantis.yaml without defined projects (#2300)

* Support atlantis.yaml without defined projects

* Update server/events/project_command_builder.go

* Update server/events/project_command_builder.go

* Correct test of no modified projects

* Flip if statement

* Remove duplicate check on hasRepoCfg

Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com>
This commit is contained in:
Chris Sainty
2022-12-01 20:03:27 +11:00
committed by GitHub
parent 733e51cbb3
commit e60dd843f4
2 changed files with 125 additions and 64 deletions

View File

@@ -237,14 +237,18 @@ func (p *DefaultProjectCommandBuilder) buildPlanAllCommands(ctx *command.Context
return nil, errors.Wrapf(err, "parsing %s", config.AtlantisYAMLFilename)
}
ctx.Log.Info("successfully parsed remote %s file", config.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 []command.ProjectContext{}, nil
if len(repoCfg.Projects) > 0 {
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 []command.ProjectContext{}, nil
}
} else {
ctx.Log.Info("No projects are defined in %s. Will resume automatic detection", config.AtlantisYAMLFilename)
}
// NOTE: We discard this work here and end up doing it again after
// cloning to ensure all the return values are set properly with
@@ -275,15 +279,19 @@ func (p *DefaultProjectCommandBuilder) buildPlanAllCommands(ctx *command.Context
}
var projCtxs []command.ProjectContext
var repoCfg valid.RepoCfg
if hasRepoCfg {
// If there's a repo cfg then we'll use it to figure out which projects
// If there's a repo cfg with projects then we'll use it to figure out which projects
// should be planed.
repoCfg, err := p.ParserValidator.ParseRepoCfg(repoDir, p.GlobalCfg, ctx.Pull.BaseRepo.ID(), ctx.Pull.BaseBranch)
repoCfg, err = p.ParserValidator.ParseRepoCfg(repoDir, p.GlobalCfg, ctx.Pull.BaseRepo.ID(), ctx.Pull.BaseBranch)
if err != nil {
return nil, errors.Wrapf(err, "parsing %s", config.AtlantisYAMLFilename)
}
ctx.Log.Info("successfully parsed %s file", config.AtlantisYAMLFilename)
}
if len(repoCfg.Projects) > 0 {
matchingProjects, err := p.ProjectFinder.DetermineProjectsViaConfig(ctx.Log, modifiedFiles, repoCfg, repoDir)
if err != nil {
return nil, err
@@ -309,9 +317,13 @@ func (p *DefaultProjectCommandBuilder) buildPlanAllCommands(ctx *command.Context
)...)
}
} else {
// If there is no config file, then we'll plan each project that
// If there is no config file or it specified no projects, then we'll plan each project that
// our algorithm determines was modified.
ctx.Log.Info("found no %s file", config.AtlantisYAMLFilename)
if hasRepoCfg {
ctx.Log.Info("No projects are defined in %s. Will resume automatic detection", config.AtlantisYAMLFilename)
} else {
ctx.Log.Info("found no %s file", config.AtlantisYAMLFilename)
}
// build a module index for projects that are explicitly included
moduleInfo, err := FindModuleProjects(repoDir, p.AutoDetectModuleFiles)
if err != nil {

View File

@@ -500,6 +500,33 @@ func TestDefaultProjectCommandBuilder_BuildPlanCommands(t *testing.T) {
},
},
},
"no projects in atlantis.yaml": {
DirStructure: map[string]interface{}{
"project1": map[string]interface{}{
"main.tf": nil,
},
"project2": map[string]interface{}{
"main.tf": nil,
},
},
AtlantisYAML: `
version: 3
parallel_plan: true
`,
ModifiedFiles: []string{"project1/main.tf", "project2/main.tf"},
Exp: []expCtxFields{
{
ProjectName: "",
RepoRelDir: "project1",
Workspace: "default",
},
{
ProjectName: "",
RepoRelDir: "project2",
Workspace: "default",
},
},
},
"no modified files": {
DirStructure: map[string]interface{}{
"main.tf": nil,
@@ -1074,61 +1101,83 @@ 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 := `
cases := []struct {
AtlantisYAML string
ExpectedCtxs int
ExpectedClones InvocationCountMatcher
ModifiedFiles []string
}{
{
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()
logger := logging.NewNoopLogger(t)
globalCfgArgs := valid.GlobalCfgArgs{
AllowRepoCfg: true,
MergeableReq: false,
ApprovedReq: false,
UnDivergedReq: false,
}
scope, _, _ := metrics.NewLoggingScope(logger, "atlantis")
builder := events.NewProjectCommandBuilder(
false,
&config.ParserValidator{},
&events.DefaultProjectFinder{},
vcsClient,
workingDir,
events.NewDefaultWorkingDirLocker(),
valid.NewGlobalCfgFromArgs(globalCfgArgs),
&events.DefaultPendingPlanFinder{},
&events.CommentParser{},
true,
false,
"",
"**/*.tf,**/*.tfvars,**/*.tfvars.json,**/terragrunt.hcl,**/.terraform.lock.hcl",
scope,
logger,
)
var actCtxs []command.ProjectContext
var err error
actCtxs, err = builder.BuildAutoplanCommands(&command.Context{
HeadRepo: models.Repo{},
Pull: models.PullRequest{},
User: models.User{},
Log: logger,
Scope: scope,
PullRequestStatus: models.PullReqStatus{
Mergeable: true,
- dir: dir1`,
ExpectedCtxs: 0,
ExpectedClones: Never(),
ModifiedFiles: []string{"dir2/main.tf"},
},
})
Ok(t, err)
Equals(t, 0, len(actCtxs))
workingDir.VerifyWasCalled(Never()).Clone(matchers.AnyPtrToLoggingSimpleLogger(), matchers.AnyModelsRepo(), matchers.AnyModelsPullRequest(), AnyString())
{
AtlantisYAML: `
version: 3
parallel_plan: true`,
ExpectedCtxs: 0,
ExpectedClones: Once(),
ModifiedFiles: []string{"README.md"},
},
}
for _, c := range cases {
RegisterMockTestingT(t)
vcsClient := vcsmocks.NewMockClient()
When(vcsClient.GetModifiedFiles(matchers.AnyModelsRepo(), matchers.AnyModelsPullRequest())).ThenReturn(c.ModifiedFiles, nil)
When(vcsClient.SupportsSingleFileDownload(matchers.AnyModelsRepo())).ThenReturn(true)
When(vcsClient.DownloadRepoConfigFile(matchers.AnyModelsPullRequest())).ThenReturn(true, []byte(c.AtlantisYAML), nil)
workingDir := mocks.NewMockWorkingDir()
logger := logging.NewNoopLogger(t)
globalCfgArgs := valid.GlobalCfgArgs{
AllowRepoCfg: true,
MergeableReq: false,
ApprovedReq: false,
UnDivergedReq: false,
}
scope, _, _ := metrics.NewLoggingScope(logger, "atlantis")
builder := events.NewProjectCommandBuilder(
false,
&config.ParserValidator{},
&events.DefaultProjectFinder{},
vcsClient,
workingDir,
events.NewDefaultWorkingDirLocker(),
valid.NewGlobalCfgFromArgs(globalCfgArgs),
&events.DefaultPendingPlanFinder{},
&events.CommentParser{},
true,
false,
"",
"**/*.tf,**/*.tfvars,**/*.tfvars.json,**/terragrunt.hcl,**/.terraform.lock.hcl",
scope,
logger,
)
var actCtxs []command.ProjectContext
var err error
actCtxs, err = builder.BuildAutoplanCommands(&command.Context{
HeadRepo: models.Repo{},
Pull: models.PullRequest{},
User: models.User{},
Log: logger,
Scope: scope,
PullRequestStatus: models.PullReqStatus{
Mergeable: true,
},
})
Ok(t, err)
Equals(t, c.ExpectedCtxs, len(actCtxs))
workingDir.VerifyWasCalled(c.ExpectedClones).Clone(matchers.AnyPtrToLoggingSimpleLogger(), matchers.AnyModelsRepo(), matchers.AnyModelsPullRequest(), AnyString())
}
}
func TestDefaultProjectCommandBuilder_WithPolicyCheckEnabled_BuildAutoplanCommand(t *testing.T) {