diff --git a/server/events/project_command_builder.go b/server/events/project_command_builder.go index f2362fc170..2d23513232 100644 --- a/server/events/project_command_builder.go +++ b/server/events/project_command_builder.go @@ -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 { diff --git a/server/events/project_command_builder_test.go b/server/events/project_command_builder_test.go index 85ff284bd4..a79c592788 100644 --- a/server/events/project_command_builder_test.go +++ b/server/events/project_command_builder_test.go @@ -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, +- dir: dir1`, + ExpectedCtxs: 0, + ExpectedClones: Never(), + ModifiedFiles: []string{"dir2/main.tf"}, + }, + { + AtlantisYAML: ` +version: 3 +parallel_plan: true`, + ExpectedCtxs: 0, + ExpectedClones: Once(), + ModifiedFiles: []string{"README.md"}, + }, } - 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(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) {