Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support atlantis.yaml without defined projects #2300

Merged
merged 12 commits into from
Dec 1, 2022
35 changes: 24 additions & 11 deletions server/events/project_command_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,14 +230,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 {
ctx.Log.Info("No projects are defined in %s. Will resume automatic detection", config.AtlantisYAMLFilename)
} else {
csainty marked this conversation as resolved.
Show resolved Hide resolved
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
}
}
csainty marked this conversation as resolved.
Show resolved Hide resolved
// NOTE: We discard this work here and end up doing it again after
// cloning to ensure all the return values are set properly with
Expand Down Expand Up @@ -268,15 +272,20 @@ 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())
nitrocode marked this conversation as resolved.
Show resolved Hide resolved
repoCfg, err := p.ParserValidator.ParseRepoCfg(repoDir, p.GlobalCfg, ctx.Pull.BaseRepo.ID(), ctx.Pull.BaseBranch)
nitrocode marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, errors.Wrapf(err, "parsing %s", config.AtlantisYAMLFilename)
}
ctx.Log.Info("successfully parsed %s file", config.AtlantisYAMLFilename)
}

if hasRepoCfg && len(repoCfg.Projects) > 0 {
csainty marked this conversation as resolved.
Show resolved Hide resolved
matchingProjects, err := p.ProjectFinder.DetermineProjectsViaConfig(ctx.Log, modifiedFiles, repoCfg, repoDir)
if err != nil {
return nil, err
Expand All @@ -302,9 +311,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)
}
modifiedProjects := p.ProjectFinder.DetermineProjects(ctx.Log, modifiedFiles, ctx.Pull.BaseRepo.FullName, repoDir, p.AutoplanFileList)
ctx.Log.Info("automatically determined that there were %d projects modified in this pull request: %s", len(modifiedProjects), modifiedProjects)
for _, mp := range modifiedProjects {
Expand Down
146 changes: 96 additions & 50 deletions server/events/project_command_builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,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,
Expand Down Expand Up @@ -1045,60 +1072,79 @@ 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
}{
{
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,
- dir: dir1`,
ExpectedCtxs: 0,
ExpectedClones: Never(),
},
{
AtlantisYAML: `
version: 3
parallel_plan: true`,
ExpectedCtxs: 1,
ExpectedClones: Once(),
},
}
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, 0, len(actCtxs))
workingDir.VerifyWasCalled(Never()).Clone(matchers.AnyPtrToLoggingSimpleLogger(), matchers.AnyModelsRepo(), matchers.AnyModelsPullRequest(), AnyString())
for _, c := range cases {
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(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) {
Expand Down