From ba3d8141eabf5c8d6b570d8314cd353981fa9f79 Mon Sep 17 00:00:00 2001 From: Josep Medialdea Date: Wed, 30 Nov 2022 20:19:34 +0100 Subject: [PATCH 1/2] Add -f flag to filter which directories to run apply/plan using pattern matching --- server/events/comment_parser.go | 32 ++++++++- server/events/comment_parser_test.go | 6 ++ server/events/event_parser.go | 6 +- server/events/event_parser_test.go | 8 ++- server/events/project_command_builder.go | 84 +++++++++++++++++++++--- 5 files changed, 123 insertions(+), 13 deletions(-) diff --git a/server/events/comment_parser.go b/server/events/comment_parser.go index 48af12d7bd..0f45c2aa75 100644 --- a/server/events/comment_parser.go +++ b/server/events/comment_parser.go @@ -41,6 +41,8 @@ const ( autoMergeDisabledFlagShort = "" verboseFlagLong = "verbose" verboseFlagShort = "" + filterFlagLong = "filter" + filterFlagShort = "f" atlantisExecutable = "atlantis" ) @@ -182,6 +184,7 @@ func (e *CommentParser) Parse(rawComment string, vcsHost models.VCSHostType) Com var verbose, autoMergeDisabled bool var flagSet *pflag.FlagSet var name command.Name + var filter string // Set up the flag parsing depending on the command. switch cmd { @@ -193,6 +196,7 @@ func (e *CommentParser) Parse(rawComment string, vcsHost models.VCSHostType) Com flagSet.StringVarP(&dir, dirFlagLong, dirFlagShort, "", "Which directory to run plan in relative to root of repo, ex. 'child/dir'.") flagSet.StringVarP(&project, projectFlagLong, projectFlagShort, "", fmt.Sprintf("Which project to run plan for. Refers to the name of the project configured in %s. Cannot be used at same time as workspace or dir flags.", config.AtlantisYAMLFilename)) flagSet.BoolVarP(&verbose, verboseFlagLong, verboseFlagShort, false, "Append Atlantis log to comment.") + flagSet.StringVarP(&filter, filterFlagLong, filterFlagShort, "", "Filter which directories to run plan based on the specified pattern. Cannot be used at same time as dir or project flags.") case command.Apply.String(): name = command.Apply flagSet = pflag.NewFlagSet(command.Apply.String(), pflag.ContinueOnError) @@ -202,6 +206,7 @@ func (e *CommentParser) Parse(rawComment string, vcsHost models.VCSHostType) Com flagSet.StringVarP(&project, projectFlagLong, projectFlagShort, "", fmt.Sprintf("Apply the plan for this project. Refers to the name of the project configured in %s. Cannot be used at same time as workspace or dir flags.", config.AtlantisYAMLFilename)) flagSet.BoolVarP(&autoMergeDisabled, autoMergeDisabledFlagLong, autoMergeDisabledFlagShort, false, "Disable automerge after apply.") flagSet.BoolVarP(&verbose, verboseFlagLong, verboseFlagShort, false, "Append Atlantis log to comment.") + flagSet.StringVarP(&filter, filterFlagLong, filterFlagShort, "", "Filter which directories to run apply based on the specified pattern. Cannot be used at same time as dir or project flags.") case command.ApprovePolicies.String(): name = command.ApprovePolicies flagSet = pflag.NewFlagSet(command.ApprovePolicies.String(), pflag.ContinueOnError) @@ -255,6 +260,11 @@ func (e *CommentParser) Parse(rawComment string, vcsHost models.VCSHostType) Com return CommentParseResult{CommentResponse: e.errMarkdown(err.Error(), cmd, flagSet)} } + filter, err = e.validateFilter(filter) + if err != nil { + return CommentParseResult{CommentResponse: e.errMarkdown(err.Error(), cmd, flagSet)} + } + // Use the same validation that Terraform uses: https://git.io/vxGhU. Plus // we also don't allow '..'. We don't want the workspace to contain a path // since we create files based on the name. @@ -262,6 +272,11 @@ func (e *CommentParser) Parse(rawComment string, vcsHost models.VCSHostType) Com return CommentParseResult{CommentResponse: e.errMarkdown(fmt.Sprintf("invalid workspace: %q", workspace), cmd, flagSet)} } + if filter != "" && (dir != "" || project != "") { + err := fmt.Sprintf("cannot use -%s/--%s at same time as -%s/--%s or -%s/--%s", filterFlagShort, filterFlagLong, dirFlagShort, dirFlagLong, projectFlagShort, projectFlagLong) + return CommentParseResult{CommentResponse: e.errMarkdown(err, cmd, flagSet)} + } + // If project is specified, dir or workspace should not be set. Since we // dir/workspace have defaults we can't detect if the user set the flag // to the default or didn't set the flag so there is an edge case here we @@ -273,7 +288,7 @@ func (e *CommentParser) Parse(rawComment string, vcsHost models.VCSHostType) Com } return CommentParseResult{ - Command: NewCommentCommand(dir, extraArgs, name, verbose, autoMergeDisabled, workspace, project), + Command: NewCommentCommand(dir, extraArgs, name, verbose, autoMergeDisabled, workspace, project, filter), } } @@ -354,6 +369,21 @@ func (e *CommentParser) validateDir(dir string) (string, error) { return validatedDir, nil } +func (e *CommentParser) validateFilter(filter string) (string, error) { + if filter == "" { + return filter, nil + } + + validatedFilter := strings.TrimPrefix(filter, "./") + + _, err := filepath.Match("", validatedFilter) + if err != nil { + return "", fmt.Errorf("invalid filter pattern %s", filter) + } + + return validatedFilter, nil +} + func (e *CommentParser) stringInSlice(a string, list []string) bool { for _, b := range list { if b == a { diff --git a/server/events/comment_parser_test.go b/server/events/comment_parser_test.go index c19c404d34..c39eabbafd 100644 --- a/server/events/comment_parser_test.go +++ b/server/events/comment_parser_test.go @@ -827,6 +827,9 @@ func TestParse_VCSUsername(t *testing.T) { var PlanUsage = `Usage of plan: -d, --dir string Which directory to run plan in relative to root of repo, ex. 'child/dir'. + -f, --filter string Filter which directories to run plan based on the + specified pattern. Cannot be used at same time as dir or + project flags. -p, --project string Which project to run plan for. Refers to the name of the project configured in atlantis.yaml. Cannot be used at same time as workspace or dir flags. @@ -838,6 +841,9 @@ var ApplyUsage = `Usage of apply: --auto-merge-disabled Disable automerge after apply. -d, --dir string Apply the plan for this directory, relative to root of repo, ex. 'child/dir'. + -f, --filter string Filter which directories to run apply based on the + specified pattern. Cannot be used at same time as dir + or project flags. -p, --project string Apply the plan for this project. Refers to the name of the project configured in atlantis.yaml. Cannot be used at same time as workspace or dir flags. diff --git a/server/events/event_parser.go b/server/events/event_parser.go index 3d8dc6550a..ed10880d9f 100644 --- a/server/events/event_parser.go +++ b/server/events/event_parser.go @@ -103,6 +103,9 @@ type CommentCommand struct { // project specified in an atlantis.yaml file. // If empty then the comment specified no project. ProjectName string + // Filter is the pattern that will filter which directories to run the command on. + // If empty then the comment specified no filter. + Filter string } // IsForSpecificProject returns true if the command is for a specific dir, workspace @@ -133,7 +136,7 @@ func (c CommentCommand) String() string { } // NewCommentCommand constructs a CommentCommand, setting all missing fields to defaults. -func NewCommentCommand(repoRelDir string, flags []string, name command.Name, verbose, autoMergeDisabled bool, workspace string, project string) *CommentCommand { +func NewCommentCommand(repoRelDir string, flags []string, name command.Name, verbose, autoMergeDisabled bool, workspace string, project string, filter string) *CommentCommand { // If repoRelDir was empty we want to keep it that way to indicate that it // wasn't specified in the comment. if repoRelDir != "" { @@ -150,6 +153,7 @@ func NewCommentCommand(repoRelDir string, flags []string, name command.Name, ver Workspace: workspace, AutoMergeDisabled: autoMergeDisabled, ProjectName: project, + Filter: filter, } } diff --git a/server/events/event_parser_test.go b/server/events/event_parser_test.go index 09de53d548..bd1ca346b5 100644 --- a/server/events/event_parser_test.go +++ b/server/events/event_parser_test.go @@ -729,14 +729,14 @@ func TestNewCommand_CleansDir(t *testing.T) { for _, c := range cases { t.Run(c.RepoRelDir, func(t *testing.T) { - cmd := events.NewCommentCommand(c.RepoRelDir, nil, command.Plan, false, false, "workspace", "") + cmd := events.NewCommentCommand(c.RepoRelDir, nil, command.Plan, false, false, "workspace", "", "") Equals(t, c.ExpDir, cmd.RepoRelDir) }) } } func TestNewCommand_EmptyDirWorkspaceProject(t *testing.T) { - cmd := events.NewCommentCommand("", nil, command.Plan, false, false, "", "") + cmd := events.NewCommentCommand("", nil, command.Plan, false, false, "", "", "") Equals(t, events.CommentCommand{ RepoRelDir: "", Flags: nil, @@ -744,11 +744,12 @@ func TestNewCommand_EmptyDirWorkspaceProject(t *testing.T) { Verbose: false, Workspace: "", ProjectName: "", + Filter: "", }, *cmd) } func TestNewCommand_AllFieldsSet(t *testing.T) { - cmd := events.NewCommentCommand("dir", []string{"a", "b"}, command.Plan, true, false, "workspace", "project") + cmd := events.NewCommentCommand("dir", []string{"a", "b"}, command.Plan, true, false, "workspace", "project", "pattern") Equals(t, events.CommentCommand{ Workspace: "workspace", RepoRelDir: "dir", @@ -756,6 +757,7 @@ func TestNewCommand_AllFieldsSet(t *testing.T) { Flags: []string{"a", "b"}, Name: command.Plan, ProjectName: "project", + Filter: "pattern", }, *cmd) } diff --git a/server/events/project_command_builder.go b/server/events/project_command_builder.go index f2362fc170..8d3d447e12 100644 --- a/server/events/project_command_builder.go +++ b/server/events/project_command_builder.go @@ -3,6 +3,7 @@ package events import ( "fmt" "os" + "path/filepath" "sort" "github.com/uber-go/tally" @@ -14,6 +15,7 @@ import ( "github.com/runatlantis/atlantis/server/core/config" "github.com/runatlantis/atlantis/server/events/command" + "github.com/runatlantis/atlantis/server/events/models" "github.com/runatlantis/atlantis/server/events/vcs" ) @@ -170,7 +172,7 @@ type DefaultProjectCommandBuilder struct { // See ProjectCommandBuilder.BuildAutoplanCommands. func (p *DefaultProjectCommandBuilder) BuildAutoplanCommands(ctx *command.Context) ([]command.ProjectContext, error) { - projCtxs, err := p.buildPlanAllCommands(ctx, nil, false) + projCtxs, err := p.buildPlanAllCommands(ctx, &CommentCommand{}) if err != nil { return nil, err } @@ -188,7 +190,7 @@ func (p *DefaultProjectCommandBuilder) BuildAutoplanCommands(ctx *command.Contex // See ProjectCommandBuilder.BuildPlanCommands. func (p *DefaultProjectCommandBuilder) BuildPlanCommands(ctx *command.Context, cmd *CommentCommand) ([]command.ProjectContext, error) { if !cmd.IsForSpecificProject() { - return p.buildPlanAllCommands(ctx, cmd.Flags, cmd.Verbose) + return p.buildPlanAllCommands(ctx, cmd) } pcc, err := p.buildProjectPlanCommand(ctx, cmd) return pcc, err @@ -217,7 +219,7 @@ func (p *DefaultProjectCommandBuilder) BuildVersionCommands(ctx *command.Context // buildPlanAllCommands builds plan contexts for all projects we determine were // modified in this ctx. -func (p *DefaultProjectCommandBuilder) buildPlanAllCommands(ctx *command.Context, commentFlags []string, verbose bool) ([]command.ProjectContext, error) { +func (p *DefaultProjectCommandBuilder) buildPlanAllCommands(ctx *command.Context, cmd *CommentCommand) ([]command.ProjectContext, error) { // We'll need the list of modified files. modifiedFiles, err := p.VCSClient.GetModifiedFiles(ctx.Pull.BaseRepo, ctx.Pull) if err != nil { @@ -288,7 +290,14 @@ func (p *DefaultProjectCommandBuilder) buildPlanAllCommands(ctx *command.Context if err != nil { return nil, err } - ctx.Log.Info("%d projects are to be planned based on their when_modified config", len(matchingProjects)) + + if cmd.Filter != "" { + filteredProjects, err := filterValidProjects(matchingProjects, cmd.Filter) + if err != nil { + return nil, err + } + matchingProjects = filteredProjects + } for _, mp := range matchingProjects { ctx.Log.Debug("determining config for project at dir: %q workspace: %q", mp.Dir, mp.Workspace) @@ -299,13 +308,13 @@ func (p *DefaultProjectCommandBuilder) buildPlanAllCommands(ctx *command.Context ctx, command.Plan, mergedCfg, - commentFlags, + cmd.Flags, repoDir, repoCfg.Automerge, mergedCfg.DeleteSourceBranchOnMerge, repoCfg.ParallelApply, repoCfg.ParallelPlan, - verbose, + cmd.IsVerbose(), )...) } } else { @@ -320,6 +329,15 @@ func (p *DefaultProjectCommandBuilder) buildPlanAllCommands(ctx *command.Context ctx.Log.Debug("moduleInfo for %s (matching %q) = %v", repoDir, p.AutoDetectModuleFiles, moduleInfo) modifiedProjects := p.ProjectFinder.DetermineProjects(ctx.Log, modifiedFiles, ctx.Pull.BaseRepo.FullName, repoDir, p.AutoplanFileList, moduleInfo) ctx.Log.Info("automatically determined that there were %d projects modified in this pull request: %s", len(modifiedProjects), modifiedProjects) + + if cmd.Filter != "" { + filteredProjects, err := filterProjects(modifiedProjects, cmd.Filter) + if err != nil { + return nil, err + } + modifiedProjects = filteredProjects + } + for _, mp := range modifiedProjects { ctx.Log.Debug("determining config for project at dir: %q", mp.Path) pWorkspace, err := p.ProjectFinder.DetermineWorkspaceFromHCL(ctx.Log, repoDir) @@ -333,13 +351,13 @@ func (p *DefaultProjectCommandBuilder) buildPlanAllCommands(ctx *command.Context ctx, command.Plan, pCfg, - commentFlags, + cmd.Flags, repoDir, DefaultAutomergeEnabled, pCfg.DeleteSourceBranchOnMerge, DefaultParallelApplyEnabled, DefaultParallelPlanEnabled, - verbose, + cmd.IsVerbose(), )...) } } @@ -470,6 +488,14 @@ func (p *DefaultProjectCommandBuilder) buildAllProjectCommands(ctx *command.Cont return nil, err } + if commentCmd.Filter != "" { + filteredPlans, err := filterPlans(plans, commentCmd.Filter) + if err != nil { + return nil, err + } + plans = filteredPlans + } + // use the default repository workspace because it is the only one guaranteed to have an atlantis.yaml, // other workspaces will not have the file if they are using pre_workflow_hooks to generate it dynamically defaultRepoDir, err := p.WorkingDir.GetWorkingDir(ctx.Pull.BaseRepo, ctx.Pull, DefaultWorkspace) @@ -661,3 +687,45 @@ func (p *DefaultProjectCommandBuilder) validateWorkspaceAllowed(repoCfg *valid.R return repoCfg.ValidateWorkspaceAllowed(repoRelDir, workspace) } + +func filterProjects(projects []models.Project, filter string) ([]models.Project, error) { + filteredProjects := make([]models.Project, 0, len(projects)) + for _, proj := range projects { + match, err := filepath.Match(filter, proj.Path) + if err != nil { + return nil, err + } + if match { + filteredProjects = append(filteredProjects, proj) + } + } + return filteredProjects, nil +} + +func filterValidProjects(projects []valid.Project, filter string) ([]valid.Project, error) { + filteredProjects := make([]valid.Project, 0, len(projects)) + for _, proj := range projects { + match, err := filepath.Match(filter, proj.Dir) + if err != nil { + return nil, err + } + if match { + filteredProjects = append(filteredProjects, proj) + } + } + return filteredProjects, nil +} + +func filterPlans(plans []PendingPlan, filter string) ([]PendingPlan, error) { + filteredPlans := make([]PendingPlan, 0, len(plans)) + for _, plan := range plans { + match, err := filepath.Match(filter, plan.RepoRelDir) + if err != nil { + return nil, err + } + if match { + filteredPlans = append(filteredPlans, plan) + } + } + return filteredPlans, nil +} From eeb6f3f409d1c819f29fdd316dca045df11cea75 Mon Sep 17 00:00:00 2001 From: Josep Medialdea Date: Mon, 5 Dec 2022 14:16:38 +0100 Subject: [PATCH 2/2] add support for filepath pattern matching to atlantis plan/apply -d flag --- server/events/comment_parser.go | 32 +----------------------- server/events/comment_parser_test.go | 6 ----- server/events/event_parser.go | 10 ++++---- server/events/event_parser_test.go | 8 +++--- server/events/project_command_builder.go | 26 +++++++++++-------- 5 files changed, 24 insertions(+), 58 deletions(-) diff --git a/server/events/comment_parser.go b/server/events/comment_parser.go index 0f45c2aa75..48af12d7bd 100644 --- a/server/events/comment_parser.go +++ b/server/events/comment_parser.go @@ -41,8 +41,6 @@ const ( autoMergeDisabledFlagShort = "" verboseFlagLong = "verbose" verboseFlagShort = "" - filterFlagLong = "filter" - filterFlagShort = "f" atlantisExecutable = "atlantis" ) @@ -184,7 +182,6 @@ func (e *CommentParser) Parse(rawComment string, vcsHost models.VCSHostType) Com var verbose, autoMergeDisabled bool var flagSet *pflag.FlagSet var name command.Name - var filter string // Set up the flag parsing depending on the command. switch cmd { @@ -196,7 +193,6 @@ func (e *CommentParser) Parse(rawComment string, vcsHost models.VCSHostType) Com flagSet.StringVarP(&dir, dirFlagLong, dirFlagShort, "", "Which directory to run plan in relative to root of repo, ex. 'child/dir'.") flagSet.StringVarP(&project, projectFlagLong, projectFlagShort, "", fmt.Sprintf("Which project to run plan for. Refers to the name of the project configured in %s. Cannot be used at same time as workspace or dir flags.", config.AtlantisYAMLFilename)) flagSet.BoolVarP(&verbose, verboseFlagLong, verboseFlagShort, false, "Append Atlantis log to comment.") - flagSet.StringVarP(&filter, filterFlagLong, filterFlagShort, "", "Filter which directories to run plan based on the specified pattern. Cannot be used at same time as dir or project flags.") case command.Apply.String(): name = command.Apply flagSet = pflag.NewFlagSet(command.Apply.String(), pflag.ContinueOnError) @@ -206,7 +202,6 @@ func (e *CommentParser) Parse(rawComment string, vcsHost models.VCSHostType) Com flagSet.StringVarP(&project, projectFlagLong, projectFlagShort, "", fmt.Sprintf("Apply the plan for this project. Refers to the name of the project configured in %s. Cannot be used at same time as workspace or dir flags.", config.AtlantisYAMLFilename)) flagSet.BoolVarP(&autoMergeDisabled, autoMergeDisabledFlagLong, autoMergeDisabledFlagShort, false, "Disable automerge after apply.") flagSet.BoolVarP(&verbose, verboseFlagLong, verboseFlagShort, false, "Append Atlantis log to comment.") - flagSet.StringVarP(&filter, filterFlagLong, filterFlagShort, "", "Filter which directories to run apply based on the specified pattern. Cannot be used at same time as dir or project flags.") case command.ApprovePolicies.String(): name = command.ApprovePolicies flagSet = pflag.NewFlagSet(command.ApprovePolicies.String(), pflag.ContinueOnError) @@ -260,11 +255,6 @@ func (e *CommentParser) Parse(rawComment string, vcsHost models.VCSHostType) Com return CommentParseResult{CommentResponse: e.errMarkdown(err.Error(), cmd, flagSet)} } - filter, err = e.validateFilter(filter) - if err != nil { - return CommentParseResult{CommentResponse: e.errMarkdown(err.Error(), cmd, flagSet)} - } - // Use the same validation that Terraform uses: https://git.io/vxGhU. Plus // we also don't allow '..'. We don't want the workspace to contain a path // since we create files based on the name. @@ -272,11 +262,6 @@ func (e *CommentParser) Parse(rawComment string, vcsHost models.VCSHostType) Com return CommentParseResult{CommentResponse: e.errMarkdown(fmt.Sprintf("invalid workspace: %q", workspace), cmd, flagSet)} } - if filter != "" && (dir != "" || project != "") { - err := fmt.Sprintf("cannot use -%s/--%s at same time as -%s/--%s or -%s/--%s", filterFlagShort, filterFlagLong, dirFlagShort, dirFlagLong, projectFlagShort, projectFlagLong) - return CommentParseResult{CommentResponse: e.errMarkdown(err, cmd, flagSet)} - } - // If project is specified, dir or workspace should not be set. Since we // dir/workspace have defaults we can't detect if the user set the flag // to the default or didn't set the flag so there is an edge case here we @@ -288,7 +273,7 @@ func (e *CommentParser) Parse(rawComment string, vcsHost models.VCSHostType) Com } return CommentParseResult{ - Command: NewCommentCommand(dir, extraArgs, name, verbose, autoMergeDisabled, workspace, project, filter), + Command: NewCommentCommand(dir, extraArgs, name, verbose, autoMergeDisabled, workspace, project), } } @@ -369,21 +354,6 @@ func (e *CommentParser) validateDir(dir string) (string, error) { return validatedDir, nil } -func (e *CommentParser) validateFilter(filter string) (string, error) { - if filter == "" { - return filter, nil - } - - validatedFilter := strings.TrimPrefix(filter, "./") - - _, err := filepath.Match("", validatedFilter) - if err != nil { - return "", fmt.Errorf("invalid filter pattern %s", filter) - } - - return validatedFilter, nil -} - func (e *CommentParser) stringInSlice(a string, list []string) bool { for _, b := range list { if b == a { diff --git a/server/events/comment_parser_test.go b/server/events/comment_parser_test.go index c39eabbafd..c19c404d34 100644 --- a/server/events/comment_parser_test.go +++ b/server/events/comment_parser_test.go @@ -827,9 +827,6 @@ func TestParse_VCSUsername(t *testing.T) { var PlanUsage = `Usage of plan: -d, --dir string Which directory to run plan in relative to root of repo, ex. 'child/dir'. - -f, --filter string Filter which directories to run plan based on the - specified pattern. Cannot be used at same time as dir or - project flags. -p, --project string Which project to run plan for. Refers to the name of the project configured in atlantis.yaml. Cannot be used at same time as workspace or dir flags. @@ -841,9 +838,6 @@ var ApplyUsage = `Usage of apply: --auto-merge-disabled Disable automerge after apply. -d, --dir string Apply the plan for this directory, relative to root of repo, ex. 'child/dir'. - -f, --filter string Filter which directories to run apply based on the - specified pattern. Cannot be used at same time as dir - or project flags. -p, --project string Apply the plan for this project. Refers to the name of the project configured in atlantis.yaml. Cannot be used at same time as workspace or dir flags. diff --git a/server/events/event_parser.go b/server/events/event_parser.go index ed10880d9f..dbd91255dc 100644 --- a/server/events/event_parser.go +++ b/server/events/event_parser.go @@ -103,9 +103,6 @@ type CommentCommand struct { // project specified in an atlantis.yaml file. // If empty then the comment specified no project. ProjectName string - // Filter is the pattern that will filter which directories to run the command on. - // If empty then the comment specified no filter. - Filter string } // IsForSpecificProject returns true if the command is for a specific dir, workspace @@ -115,6 +112,10 @@ func (c CommentCommand) IsForSpecificProject() bool { return c.RepoRelDir != "" || c.Workspace != "" || c.ProjectName != "" } +func (c CommentCommand) HasDirPatternMatching() bool { + return strings.Contains(c.RepoRelDir, "*") || strings.Contains(c.RepoRelDir, "?") || strings.Contains(c.RepoRelDir, "[") || strings.Contains(c.RepoRelDir, "]") +} + // CommandName returns the name of this command. func (c CommentCommand) CommandName() command.Name { return c.Name @@ -136,7 +137,7 @@ func (c CommentCommand) String() string { } // NewCommentCommand constructs a CommentCommand, setting all missing fields to defaults. -func NewCommentCommand(repoRelDir string, flags []string, name command.Name, verbose, autoMergeDisabled bool, workspace string, project string, filter string) *CommentCommand { +func NewCommentCommand(repoRelDir string, flags []string, name command.Name, verbose, autoMergeDisabled bool, workspace string, project string) *CommentCommand { // If repoRelDir was empty we want to keep it that way to indicate that it // wasn't specified in the comment. if repoRelDir != "" { @@ -153,7 +154,6 @@ func NewCommentCommand(repoRelDir string, flags []string, name command.Name, ver Workspace: workspace, AutoMergeDisabled: autoMergeDisabled, ProjectName: project, - Filter: filter, } } diff --git a/server/events/event_parser_test.go b/server/events/event_parser_test.go index bd1ca346b5..09de53d548 100644 --- a/server/events/event_parser_test.go +++ b/server/events/event_parser_test.go @@ -729,14 +729,14 @@ func TestNewCommand_CleansDir(t *testing.T) { for _, c := range cases { t.Run(c.RepoRelDir, func(t *testing.T) { - cmd := events.NewCommentCommand(c.RepoRelDir, nil, command.Plan, false, false, "workspace", "", "") + cmd := events.NewCommentCommand(c.RepoRelDir, nil, command.Plan, false, false, "workspace", "") Equals(t, c.ExpDir, cmd.RepoRelDir) }) } } func TestNewCommand_EmptyDirWorkspaceProject(t *testing.T) { - cmd := events.NewCommentCommand("", nil, command.Plan, false, false, "", "", "") + cmd := events.NewCommentCommand("", nil, command.Plan, false, false, "", "") Equals(t, events.CommentCommand{ RepoRelDir: "", Flags: nil, @@ -744,12 +744,11 @@ func TestNewCommand_EmptyDirWorkspaceProject(t *testing.T) { Verbose: false, Workspace: "", ProjectName: "", - Filter: "", }, *cmd) } func TestNewCommand_AllFieldsSet(t *testing.T) { - cmd := events.NewCommentCommand("dir", []string{"a", "b"}, command.Plan, true, false, "workspace", "project", "pattern") + cmd := events.NewCommentCommand("dir", []string{"a", "b"}, command.Plan, true, false, "workspace", "project") Equals(t, events.CommentCommand{ Workspace: "workspace", RepoRelDir: "dir", @@ -757,7 +756,6 @@ func TestNewCommand_AllFieldsSet(t *testing.T) { Flags: []string{"a", "b"}, Name: command.Plan, ProjectName: "project", - Filter: "pattern", }, *cmd) } diff --git a/server/events/project_command_builder.go b/server/events/project_command_builder.go index 47c0c67a86..3afc94ccd6 100644 --- a/server/events/project_command_builder.go +++ b/server/events/project_command_builder.go @@ -5,6 +5,7 @@ import ( "os" "path/filepath" "sort" + "strings" "github.com/uber-go/tally" @@ -189,7 +190,7 @@ func (p *DefaultProjectCommandBuilder) BuildAutoplanCommands(ctx *command.Contex // See ProjectCommandBuilder.BuildPlanCommands. func (p *DefaultProjectCommandBuilder) BuildPlanCommands(ctx *command.Context, cmd *CommentCommand) ([]command.ProjectContext, error) { - if !cmd.IsForSpecificProject() { + if !cmd.IsForSpecificProject() || (p.EnableRegExpCmd && cmd.HasDirPatternMatching()) { return p.buildPlanAllCommands(ctx, cmd) } pcc, err := p.buildProjectPlanCommand(ctx, cmd) @@ -198,7 +199,7 @@ func (p *DefaultProjectCommandBuilder) BuildPlanCommands(ctx *command.Context, c // See ProjectCommandBuilder.BuildApplyCommands. func (p *DefaultProjectCommandBuilder) BuildApplyCommands(ctx *command.Context, cmd *CommentCommand) ([]command.ProjectContext, error) { - if !cmd.IsForSpecificProject() { + if !cmd.IsForSpecificProject() || (p.EnableRegExpCmd && cmd.HasDirPatternMatching()) { return p.buildAllProjectCommands(ctx, cmd) } pac, err := p.buildProjectApplyCommand(ctx, cmd) @@ -299,8 +300,8 @@ func (p *DefaultProjectCommandBuilder) buildPlanAllCommands(ctx *command.Context return nil, err } - if cmd.Filter != "" { - filteredProjects, err := filterValidProjects(matchingProjects, cmd.Filter) + if p.EnableRegExpCmd && cmd.RepoRelDir != "" { + filteredProjects, err := filterValidProjects(matchingProjects, cmd.RepoRelDir) if err != nil { return nil, err } @@ -342,8 +343,8 @@ func (p *DefaultProjectCommandBuilder) buildPlanAllCommands(ctx *command.Context modifiedProjects := p.ProjectFinder.DetermineProjects(ctx.Log, modifiedFiles, ctx.Pull.BaseRepo.FullName, repoDir, p.AutoplanFileList, moduleInfo) ctx.Log.Info("automatically determined that there were %d projects modified in this pull request: %s", len(modifiedProjects), modifiedProjects) - if cmd.Filter != "" { - filteredProjects, err := filterProjects(modifiedProjects, cmd.Filter) + if p.EnableRegExpCmd && cmd.RepoRelDir != "" { + filteredProjects, err := filterProjects(modifiedProjects, cmd.RepoRelDir) if err != nil { return nil, err } @@ -500,8 +501,8 @@ func (p *DefaultProjectCommandBuilder) buildAllProjectCommands(ctx *command.Cont return nil, err } - if commentCmd.Filter != "" { - filteredPlans, err := filterPlans(plans, commentCmd.Filter) + if p.EnableRegExpCmd && commentCmd.RepoRelDir != "" { + filteredPlans, err := filterPlans(plans, commentCmd.RepoRelDir) if err != nil { return nil, err } @@ -701,9 +702,10 @@ func (p *DefaultProjectCommandBuilder) validateWorkspaceAllowed(repoCfg *valid.R } func filterProjects(projects []models.Project, filter string) ([]models.Project, error) { + trimmedFilter := strings.TrimPrefix(filter, "./") filteredProjects := make([]models.Project, 0, len(projects)) for _, proj := range projects { - match, err := filepath.Match(filter, proj.Path) + match, err := filepath.Match(trimmedFilter, proj.Path) if err != nil { return nil, err } @@ -715,9 +717,10 @@ func filterProjects(projects []models.Project, filter string) ([]models.Project, } func filterValidProjects(projects []valid.Project, filter string) ([]valid.Project, error) { + trimmedFilter := strings.TrimPrefix(filter, "./") filteredProjects := make([]valid.Project, 0, len(projects)) for _, proj := range projects { - match, err := filepath.Match(filter, proj.Dir) + match, err := filepath.Match(trimmedFilter, proj.Dir) if err != nil { return nil, err } @@ -729,9 +732,10 @@ func filterValidProjects(projects []valid.Project, filter string) ([]valid.Proje } func filterPlans(plans []PendingPlan, filter string) ([]PendingPlan, error) { + trimmedFilter := strings.TrimPrefix(filter, "./") filteredPlans := make([]PendingPlan, 0, len(plans)) for _, plan := range plans { - match, err := filepath.Match(filter, plan.RepoRelDir) + match, err := filepath.Match(trimmedFilter, plan.RepoRelDir) if err != nil { return nil, err }