From 6f8401d57bdb6e1ce1bb48d6fb396ba31c35cb9b Mon Sep 17 00:00:00 2001 From: Luke Kysow Date: Fri, 29 Jun 2018 14:34:05 +0200 Subject: [PATCH] Test BuildAutoplanCommands --- server/events/comment_parser.go | 2 +- server/events/event_parser.go | 4 +- server/events/event_parser_test.go | 6 +- server/events/models/models.go | 1 - server/events/project_command_builder.go | 132 +++-- server/events/project_command_builder_test.go | 481 +++++++++++++++++- server/events/runtime/apply_step_runner.go | 4 +- .../events/runtime/apply_step_runner_test.go | 5 +- server/events/runtime/plan_step_runner.go | 4 +- .../events/runtime/plan_step_runner_test.go | 6 +- server/events/yaml/valid/valid.go | 7 +- 11 files changed, 535 insertions(+), 117 deletions(-) diff --git a/server/events/comment_parser.go b/server/events/comment_parser.go index c6f9f2439..46e30708c 100644 --- a/server/events/comment_parser.go +++ b/server/events/comment_parser.go @@ -215,7 +215,7 @@ func (e *CommentParser) Parse(comment string, vcsHost models.VCSHostType) Commen } return CommentParseResult{ - Command: NewCommand(dir, extraArgs, name, verbose, workspace, project), + Command: NewCommentCommand(dir, extraArgs, name, verbose, workspace, project), } } diff --git a/server/events/event_parser.go b/server/events/event_parser.go index 3bf4e7f15..0f82cd7af 100644 --- a/server/events/event_parser.go +++ b/server/events/event_parser.go @@ -85,8 +85,8 @@ func (c CommentCommand) String() string { return fmt.Sprintf("command=%q verbose=%t dir=%q workspace=%q project=%q flags=%q", c.Name.String(), c.Verbose, c.Dir, c.Workspace, c.ProjectName, strings.Join(c.Flags, ",")) } -// NewCommand constructs a Command, setting all missing fields to defaults. -func NewCommand(dir string, flags []string, name CommandName, verbose bool, workspace string, project string) *CommentCommand { +// NewCommentCommand constructs a CommentCommand, setting all missing fields to defaults. +func NewCommentCommand(dir string, flags []string, name CommandName, verbose bool, workspace string, project string) *CommentCommand { // If dir was an empty string, this will return '.'. validDir := path.Clean(dir) if validDir == "/" { diff --git a/server/events/event_parser_test.go b/server/events/event_parser_test.go index d7ef7fce7..ba558831b 100644 --- a/server/events/event_parser_test.go +++ b/server/events/event_parser_test.go @@ -344,19 +344,19 @@ func TestNewCommand_CleansDir(t *testing.T) { for _, c := range cases { t.Run(c.Dir, func(t *testing.T) { - cmd := events.NewCommand(c.Dir, nil, events.Plan, false, "workspace", "") + cmd := events.NewCommentCommand(c.Dir, nil, events.Plan, false, "workspace", "") Equals(t, c.ExpDir, cmd.Dir) }) } } func TestNewCommand_EmptyWorkspace(t *testing.T) { - cmd := events.NewCommand("dir", nil, events.Plan, false, "", "") + cmd := events.NewCommentCommand("dir", nil, events.Plan, false, "", "") Equals(t, "default", cmd.Workspace) } func TestNewCommand_AllFieldsSet(t *testing.T) { - cmd := events.NewCommand("dir", []string{"a", "b"}, events.Plan, true, "workspace", "project") + cmd := events.NewCommentCommand("dir", []string{"a", "b"}, events.Plan, true, "workspace", "project") Equals(t, events.CommentCommand{ Workspace: "workspace", Dir: "dir", diff --git a/server/events/models/models.go b/server/events/models/models.go index cc50f1dd1..a9a8455fd 100644 --- a/server/events/models/models.go +++ b/server/events/models/models.go @@ -237,6 +237,5 @@ type ProjectCommandContext struct { // ex. atlantis plan -- -target=resource CommentArgs []string Workspace string - ProjectName string RequireApprovalOverride bool } diff --git a/server/events/project_command_builder.go b/server/events/project_command_builder.go index a850d17b7..706118118 100644 --- a/server/events/project_command_builder.go +++ b/server/events/project_command_builder.go @@ -88,7 +88,6 @@ func (p *DefaultProjectCommandBuilder) BuildAutoplanCommands(ctx *CommandContext User: ctx.User, Log: ctx.Log, RepoRelPath: mp.Path, - ProjectName: "", ProjectConfig: nil, GlobalConfig: nil, CommentArgs: nil, @@ -108,10 +107,6 @@ func (p *DefaultProjectCommandBuilder) BuildAutoplanCommands(ctx *CommandContext // project config. for i := 0; i < len(matchingProjects); i++ { mp := matchingProjects[i] - var projectName string - if mp.Name != nil { - projectName = *mp.Name - } projCtxs = append(projCtxs, models.ProjectCommandContext{ BaseRepo: ctx.BaseRepo, HeadRepo: ctx.HeadRepo, @@ -121,7 +116,6 @@ func (p *DefaultProjectCommandBuilder) BuildAutoplanCommands(ctx *CommandContext CommentArgs: nil, Workspace: mp.Workspace, RepoRelPath: mp.Dir, - ProjectName: projectName, ProjectConfig: &mp, GlobalConfig: &config, }) @@ -144,100 +138,88 @@ func (p *DefaultProjectCommandBuilder) BuildPlanCommand(ctx *CommandContext, cmd return projCtx, err } - var projCfg *valid.Project - var globalCfg *valid.Spec - - // Parse config file if it exists. - config, err := p.ParserValidator.ReadConfig(repoDir) - if err != nil && !os.IsNotExist(err) { - return projCtx, err - } - hasAtlantisYAML := !os.IsNotExist(err) - if hasAtlantisYAML { - // If they've specified a project by name we look it up. Otherwise we - // use the dir and workspace. - if cmd.ProjectName != "" { - projCfg = config.FindProjectByName(cmd.ProjectName) - if projCfg == nil { - return projCtx, fmt.Errorf("no project with name %q configured", cmd.ProjectName) - } - } else { - projCfg = config.FindProject(cmd.Dir, cmd.Workspace) - } - globalCfg = &config - } - - if cmd.ProjectName != "" && !hasAtlantisYAML { - return projCtx, fmt.Errorf("cannot specify a project name unless an %s file exists to configure projects", yaml.AtlantisYAMLFilename) - } - - projCtx = models.ProjectCommandContext{ - BaseRepo: ctx.BaseRepo, - HeadRepo: ctx.HeadRepo, - Pull: ctx.Pull, - User: ctx.User, - Log: ctx.Log, - CommentArgs: cmd.Flags, - Workspace: cmd.Workspace, - RepoRelPath: cmd.Dir, - ProjectName: cmd.ProjectName, - ProjectConfig: projCfg, - GlobalConfig: globalCfg, - } - return projCtx, nil + return p.buildProjectCommandCtx(ctx, cmd, repoDir) } func (p *DefaultProjectCommandBuilder) BuildApplyCommand(ctx *CommandContext, cmd *CommentCommand) (models.ProjectCommandContext, error) { var projCtx models.ProjectCommandContext + unlockFn, err := p.WorkingDirLocker.TryLock(ctx.BaseRepo.FullName, cmd.Workspace, ctx.Pull.Num) + if err != nil { + return projCtx, err + } + defer unlockFn() + repoDir, err := p.WorkingDir.GetWorkingDir(ctx.BaseRepo, ctx.Pull, cmd.Workspace) if err != nil { return projCtx, err } - // todo: can deduplicate this between PlanViaComment - var projCfg *valid.Project - var globalCfg *valid.Spec + return p.buildProjectCommandCtx(ctx, cmd, repoDir) +} - // Parse config file if it exists. - config, err := p.ParserValidator.ReadConfig(repoDir) - if err != nil && !os.IsNotExist(err) { - return projCtx, err - } - hasAtlantisYAML := !os.IsNotExist(err) - if hasAtlantisYAML { - // If they've specified a project by name we look it up. Otherwise we - // use the dir and workspace. - if cmd.ProjectName != "" { - projCfg = config.FindProjectByName(cmd.ProjectName) - if projCfg == nil { - return projCtx, fmt.Errorf("no project with name %q configured", cmd.ProjectName) - } - } else { - projCfg = config.FindProject(cmd.Dir, cmd.Workspace) - } - globalCfg = &config +func (p *DefaultProjectCommandBuilder) buildProjectCommandCtx(ctx *CommandContext, cmd *CommentCommand, repoDir string) (models.ProjectCommandContext, error) { + projCfg, globalCfg, err := p.getCfg(cmd.ProjectName, cmd.Dir, cmd.Workspace, repoDir) + if err != nil { + return models.ProjectCommandContext{}, err } - if cmd.ProjectName != "" && !hasAtlantisYAML { - return projCtx, fmt.Errorf("cannot specify a project name unless an %s file exists to configure projects", yaml.AtlantisYAMLFilename) + // Override any dir/workspace defined on the comment with what was + // defined in config. This shouldn't matter since we don't allow comments + // with both project name and dir/workspace. + dir := cmd.Dir + workspace := cmd.Workspace + if projCfg != nil { + dir = projCfg.Dir + workspace = projCfg.Workspace } - projCtx = models.ProjectCommandContext{ + return models.ProjectCommandContext{ BaseRepo: ctx.BaseRepo, HeadRepo: ctx.HeadRepo, Pull: ctx.Pull, User: ctx.User, Log: ctx.Log, CommentArgs: cmd.Flags, - Workspace: cmd.Workspace, - RepoRelPath: cmd.Dir, - ProjectName: cmd.ProjectName, + Workspace: workspace, + RepoRelPath: dir, ProjectConfig: projCfg, GlobalConfig: globalCfg, RequireApprovalOverride: p.RequireApproval, + }, nil +} + +func (p *DefaultProjectCommandBuilder) getCfg(projectName string, dir string, workspace string, repoDir string) (*valid.Project, *valid.Spec, error) { + globalCfg, err := p.ParserValidator.ReadConfig(repoDir) + if err != nil && !os.IsNotExist(err) { + return nil, nil, err } - return projCtx, nil + hasAtlantisYAML := !os.IsNotExist(err) + if !hasAtlantisYAML && projectName != "" { + return nil, nil, fmt.Errorf("cannot specify a project name unless an %s file exists to configure projects", yaml.AtlantisYAMLFilename) + } + if !hasAtlantisYAML { + return nil, nil, nil + } + + // If they've specified a project by name we look it up. Otherwise we + // use the dir and workspace. + if projectName != "" { + projCfg := globalCfg.FindProjectByName(projectName) + if projCfg == nil { + return nil, nil, fmt.Errorf("no project with name %q is defined in %s", projectName, yaml.AtlantisYAMLFilename) + } + return projCfg, &globalCfg, nil + } + + projCfgs := globalCfg.FindProjectsByDirWorkspace(dir, workspace) + if len(projCfgs) == 0 { + return nil, nil, nil + } + if len(projCfgs) > 1 { + return nil, nil, fmt.Errorf("must specify project name: more than one project defined in %s matched dir: %q workspace: %q", yaml.AtlantisYAMLFilename, dir, workspace) + } + return &projCfgs[0], &globalCfg, nil } // matchingProjects returns the list of projects whose WhenModified fields match diff --git a/server/events/project_command_builder_test.go b/server/events/project_command_builder_test.go index 41ace8e02..5ef72a5a7 100644 --- a/server/events/project_command_builder_test.go +++ b/server/events/project_command_builder_test.go @@ -1,29 +1,458 @@ package events_test -//. "github.com/runatlantis/atlantis/testing" +import ( + "io/ioutil" + "path/filepath" + "testing" -//func TestBuildAutoplanCommands(t *testing.T) { -// tmpDir, cleanup := TempDir(t) -// defer cleanup() -// -// workspace := mocks.NewMockWorkingDir() -// vcsClient := vcsmocks.NewMockClientProxy() -// -// builder := &events.DefaultProjectCommandBuilder{ -// WorkingDirLocker: events.NewDefaultAtlantisWorkingDirLocker(), -// Workspace: workspace, -// ParserValidator: &yaml.ParserValidator{}, -// VCSClient: vcsClient, -// ProjectFinder: &events.DefaultProjectFinder{}, -// } -// -// // If autoplan is false, should return empty steps. -// ctxs, err := builder.BuildAutoplanCommands(&events.CommandContext{ -// BaseRepo: models.Repo{}, -// HeadRepo: models.Repo{}, -// Pull: models.PullRequest{}, -// User: models.User{}, -// Log: nil, -// }) -// Ok(t, err) -//} + . "github.com/petergtz/pegomock" + "github.com/runatlantis/atlantis/server/events" + "github.com/runatlantis/atlantis/server/events/mocks" + "github.com/runatlantis/atlantis/server/events/models" + vcsmocks "github.com/runatlantis/atlantis/server/events/vcs/mocks" + "github.com/runatlantis/atlantis/server/events/yaml" + "github.com/runatlantis/atlantis/server/events/yaml/valid" + "github.com/runatlantis/atlantis/server/logging" + . "github.com/runatlantis/atlantis/testing" +) + +func TestDefaultProjectCommandBuilder_BuildAutoplanCommands(t *testing.T) { + // exp defines what we will assert on. We don't check all fields in the + // actual contexts. + type exp struct { + projectConfig *valid.Project + dir string + workspace string + } + cases := []struct { + Description string + AtlantisYAML string + exp []exp + }{ + { + Description: "no atlantis.yaml", + AtlantisYAML: "", + exp: []exp{ + { + projectConfig: nil, + dir: ".", + workspace: "default", + }, + }, + }, + { + Description: "autoplan disabled", + AtlantisYAML: ` +version: 2 +projects: +- dir: . + autoplan: + enabled: false`, + exp: nil, + }, + { + Description: "simple atlantis.yaml", + AtlantisYAML: ` +version: 2 +projects: +- dir: . +`, + exp: []exp{ + { + projectConfig: &valid.Project{ + Dir: ".", + Workspace: "default", + Autoplan: valid.Autoplan{ + Enabled: true, + WhenModified: []string{"**/*.tf"}, + }, + }, + dir: ".", + workspace: "default", + }, + }, + }, + { + Description: "some projects disabled", + AtlantisYAML: ` +version: 2 +projects: +- dir: . + autoplan: + enabled: false +- dir: . + workspace: myworkspace + autoplan: + when_modified: ["main.tf"] +- dir: . + workspace: myworkspace2 +`, + exp: []exp{ + { + projectConfig: &valid.Project{ + Dir: ".", + Workspace: "myworkspace", + Autoplan: valid.Autoplan{ + Enabled: true, + WhenModified: []string{"main.tf"}, + }, + }, + dir: ".", + workspace: "myworkspace", + }, + { + projectConfig: &valid.Project{ + Dir: ".", + Workspace: "myworkspace2", + Autoplan: valid.Autoplan{ + Enabled: true, + WhenModified: []string{"**/*.tf"}, + }, + }, + dir: ".", + workspace: "myworkspace2", + }, + }, + }, + { + Description: "some projects disabled", + AtlantisYAML: ` +version: 2 +projects: +- dir: . + autoplan: + enabled: false +- dir: . + workspace: myworkspace + autoplan: + when_modified: ["main.tf"] +- dir: . + workspace: myworkspace2 +`, + exp: []exp{ + { + projectConfig: &valid.Project{ + Dir: ".", + Workspace: "myworkspace", + Autoplan: valid.Autoplan{ + Enabled: true, + WhenModified: []string{"main.tf"}, + }, + }, + dir: ".", + workspace: "myworkspace", + }, + { + projectConfig: &valid.Project{ + Dir: ".", + Workspace: "myworkspace2", + Autoplan: valid.Autoplan{ + Enabled: true, + WhenModified: []string{"**/*.tf"}, + }, + }, + dir: ".", + workspace: "myworkspace2", + }, + }, + }, + { + Description: "no projects modified", + AtlantisYAML: ` +version: 2 +projects: +- dir: mydir +`, + exp: nil, + }, + } + + for _, c := range cases { + t.Run(c.Description, func(t *testing.T) { + RegisterMockTestingT(t) + tmpDir, cleanup := TempDir(t) + defer cleanup() + + baseRepo := models.Repo{} + headRepo := models.Repo{} + pull := models.PullRequest{} + logger := logging.NewNoopLogger() + workingDir := mocks.NewMockWorkingDir() + When(workingDir.Clone(logger, baseRepo, headRepo, pull, "default")).ThenReturn(tmpDir, nil) + if c.AtlantisYAML != "" { + err := ioutil.WriteFile(filepath.Join(tmpDir, yaml.AtlantisYAMLFilename), []byte(c.AtlantisYAML), 0600) + Ok(t, err) + } + err := ioutil.WriteFile(filepath.Join(tmpDir, "main.tf"), nil, 0600) + Ok(t, err) + + vcsClient := vcsmocks.NewMockClientProxy() + When(vcsClient.GetModifiedFiles(baseRepo, pull)).ThenReturn([]string{"main.tf"}, nil) + + builder := &events.DefaultProjectCommandBuilder{ + WorkingDirLocker: events.NewDefaultAtlantisWorkingDirLocker(), + WorkingDir: workingDir, + ParserValidator: &yaml.ParserValidator{}, + VCSClient: vcsClient, + ProjectFinder: &events.DefaultProjectFinder{}, + } + + ctxs, err := builder.BuildAutoplanCommands(&events.CommandContext{ + BaseRepo: baseRepo, + HeadRepo: headRepo, + Pull: pull, + User: models.User{}, + Log: logger, + }) + Ok(t, err) + Equals(t, len(c.exp), len(ctxs)) + + for i, actCtx := range ctxs { + expCtx := c.exp[i] + Equals(t, baseRepo, actCtx.BaseRepo) + Equals(t, baseRepo, actCtx.HeadRepo) + Equals(t, pull, actCtx.Pull) + Equals(t, models.User{}, actCtx.User) + Equals(t, logger, actCtx.Log) + Equals(t, 0, len(actCtx.CommentArgs)) + Equals(t, false, actCtx.RequireApprovalOverride) + + Equals(t, expCtx.projectConfig, actCtx.ProjectConfig) + Equals(t, expCtx.dir, actCtx.RepoRelPath) + Equals(t, expCtx.workspace, actCtx.Workspace) + } + }) + } +} + +func TestDefaultProjectCommandBuilder_BuildPlanApplyCommand(t *testing.T) { + cases := []struct { + Description string + AtlantisYAML string + Cmd events.CommentCommand + ExpProjectConfig *valid.Project + ExpCommentArgs []string + ExpWorkspace string + ExpDir string + ExpErr string + }{ + { + Description: "no atlantis.yaml", + Cmd: events.CommentCommand{ + Dir: ".", + Flags: []string{"commentarg"}, + Name: events.Plan, + Workspace: "myworkspace", + }, + AtlantisYAML: "", + ExpProjectConfig: nil, + ExpCommentArgs: []string{"commentarg"}, + ExpWorkspace: "myworkspace", + ExpDir: ".", + }, + { + Description: "no atlantis.yaml with project flag", + Cmd: events.CommentCommand{ + Dir: ".", + Name: events.Plan, + ProjectName: "myproject", + }, + AtlantisYAML: "", + ExpErr: "cannot specify a project name unless an atlantis.yaml file exists to configure projects", + }, + { + Description: "simple atlantis.yaml", + Cmd: events.CommentCommand{ + Dir: ".", + Name: events.Plan, + Workspace: "myworkspace", + }, + AtlantisYAML: ` +version: 2 +projects: +- dir: . + workspace: myworkspace + apply_requirements: [approved]`, + ExpProjectConfig: &valid.Project{ + Dir: ".", + Workspace: "myworkspace", + Autoplan: valid.Autoplan{ + WhenModified: []string{"**/*.tf"}, + Enabled: true, + }, + ApplyRequirements: []string{"approved"}, + }, + ExpWorkspace: "myworkspace", + ExpDir: ".", + }, + { + Description: "atlantis.yaml wrong dir", + Cmd: events.CommentCommand{ + Dir: ".", + Name: events.Plan, + Workspace: "myworkspace", + }, + AtlantisYAML: ` +version: 2 +projects: +- dir: notroot + workspace: myworkspace + apply_requirements: [approved]`, + ExpProjectConfig: nil, + ExpWorkspace: "myworkspace", + ExpDir: ".", + }, + { + Description: "atlantis.yaml wrong workspace", + Cmd: events.CommentCommand{ + Dir: ".", + Name: events.Plan, + Workspace: "myworkspace", + }, + AtlantisYAML: ` +version: 2 +projects: +- dir: . + workspace: notmyworkspace + apply_requirements: [approved]`, + ExpProjectConfig: nil, + ExpWorkspace: "myworkspace", + ExpDir: ".", + }, + { + Description: "atlantis.yaml with projectname", + Cmd: events.CommentCommand{ + Name: events.Plan, + ProjectName: "myproject", + }, + AtlantisYAML: ` +version: 2 +projects: +- name: myproject + dir: . + workspace: myworkspace + apply_requirements: [approved]`, + ExpProjectConfig: &valid.Project{ + Dir: ".", + Workspace: "myworkspace", + Autoplan: valid.Autoplan{ + WhenModified: []string{"**/*.tf"}, + Enabled: true, + }, + ApplyRequirements: []string{"approved"}, + Name: String("myproject"), + }, + ExpWorkspace: "myworkspace", + ExpDir: ".", + }, + { + Description: "atlantis.yaml with multiple dir/workspaces matching", + Cmd: events.CommentCommand{ + Name: events.Plan, + Dir: ".", + Workspace: "myworkspace", + }, + AtlantisYAML: ` +version: 2 +projects: +- name: myproject + dir: . + workspace: myworkspace + apply_requirements: [approved] +- name: myproject2 + dir: . + workspace: myworkspace +`, + ExpErr: "must specify project name: more than one project defined in atlantis.yaml matched dir: \".\" workspace: \"myworkspace\"", + }, + { + Description: "atlantis.yaml with project flag not matching", + Cmd: events.CommentCommand{ + Name: events.Plan, + Dir: ".", + Workspace: "default", + ProjectName: "notconfigured", + }, + AtlantisYAML: ` +version: 2 +projects: +- dir: . +`, + ExpErr: "no project with name \"notconfigured\" is defined in atlantis.yaml", + }, + } + + for _, c := range cases { + // NOTE: we're testing both plan and apply here. + for _, cmdName := range []events.CommandName{events.Plan, events.Apply} { + t.Run(c.Description, func(t *testing.T) { + RegisterMockTestingT(t) + tmpDir, cleanup := TempDir(t) + defer cleanup() + + baseRepo := models.Repo{} + headRepo := models.Repo{} + pull := models.PullRequest{} + logger := logging.NewNoopLogger() + workingDir := mocks.NewMockWorkingDir() + if cmdName == events.Plan { + When(workingDir.Clone(logger, baseRepo, headRepo, pull, c.Cmd.Workspace)).ThenReturn(tmpDir, nil) + } else { + When(workingDir.GetWorkingDir(baseRepo, pull, c.Cmd.Workspace)).ThenReturn(tmpDir, nil) + } + if c.AtlantisYAML != "" { + err := ioutil.WriteFile(filepath.Join(tmpDir, yaml.AtlantisYAMLFilename), []byte(c.AtlantisYAML), 0600) + Ok(t, err) + } + err := ioutil.WriteFile(filepath.Join(tmpDir, "main.tf"), nil, 0600) + Ok(t, err) + + vcsClient := vcsmocks.NewMockClientProxy() + When(vcsClient.GetModifiedFiles(baseRepo, pull)).ThenReturn([]string{"main.tf"}, nil) + + builder := &events.DefaultProjectCommandBuilder{ + WorkingDirLocker: events.NewDefaultAtlantisWorkingDirLocker(), + WorkingDir: workingDir, + ParserValidator: &yaml.ParserValidator{}, + VCSClient: vcsClient, + ProjectFinder: &events.DefaultProjectFinder{}, + } + + cmdCtx := &events.CommandContext{ + BaseRepo: baseRepo, + HeadRepo: headRepo, + Pull: pull, + User: models.User{}, + Log: logger, + } + var actCtx models.ProjectCommandContext + + if cmdName == events.Plan { + actCtx, err = builder.BuildPlanCommand(cmdCtx, &c.Cmd) + } else { + actCtx, err = builder.BuildApplyCommand(cmdCtx, &c.Cmd) + } + + if c.ExpErr != "" { + ErrEquals(t, c.ExpErr, err) + return + } + + Ok(t, err) + Equals(t, baseRepo, actCtx.BaseRepo) + Equals(t, baseRepo, actCtx.HeadRepo) + Equals(t, pull, actCtx.Pull) + Equals(t, models.User{}, actCtx.User) + Equals(t, logger, actCtx.Log) + Equals(t, false, actCtx.RequireApprovalOverride) + + Equals(t, c.ExpProjectConfig, actCtx.ProjectConfig) + Equals(t, c.ExpDir, actCtx.RepoRelPath) + Equals(t, c.ExpWorkspace, actCtx.Workspace) + Equals(t, c.ExpCommentArgs, actCtx.CommentArgs) + }) + } + } +} + +func String(v string) *string { return &v } diff --git a/server/events/runtime/apply_step_runner.go b/server/events/runtime/apply_step_runner.go index 2cb3a50c1..caea2919c 100644 --- a/server/events/runtime/apply_step_runner.go +++ b/server/events/runtime/apply_step_runner.go @@ -17,8 +17,8 @@ type ApplyStepRunner struct { func (a *ApplyStepRunner) Run(ctx models.ProjectCommandContext, extraArgs []string, path string) (string, error) { // todo: move this to a common library planFileName := fmt.Sprintf("%s.tfplan", ctx.Workspace) - if ctx.ProjectName != "" { - planFileName = fmt.Sprintf("%s-%s", ctx.ProjectName, planFileName) + if ctx.ProjectConfig != nil && ctx.ProjectConfig.Name != nil { + planFileName = fmt.Sprintf("%s-%s", *ctx.ProjectConfig.Name, planFileName) } planFile := filepath.Join(path, planFileName) stat, err := os.Stat(planFile) diff --git a/server/events/runtime/apply_step_runner_test.go b/server/events/runtime/apply_step_runner_test.go index 4ebdc39c0..50c6693ee 100644 --- a/server/events/runtime/apply_step_runner_test.go +++ b/server/events/runtime/apply_step_runner_test.go @@ -81,10 +81,13 @@ func TestRun_AppliesCorrectProjectPlan(t *testing.T) { When(terraform.RunCommandWithVersion(matchers.AnyPtrToLoggingSimpleLogger(), AnyString(), AnyStringSlice(), matchers2.AnyPtrToGoVersionVersion(), AnyString())). ThenReturn("output", nil) + projectName := "projectname" output, err := o.Run(models.ProjectCommandContext{ Workspace: "default", RepoRelPath: ".", - ProjectName: "projectname", + ProjectConfig: &valid.Project{ + Name: &projectName, + }, CommentArgs: []string{"comment", "args"}, }, []string{"extra", "args"}, tmpDir) Ok(t, err) diff --git a/server/events/runtime/plan_step_runner.go b/server/events/runtime/plan_step_runner.go index f0e49b6ed..88f4abc2d 100644 --- a/server/events/runtime/plan_step_runner.go +++ b/server/events/runtime/plan_step_runner.go @@ -34,8 +34,8 @@ func (p *PlanStepRunner) Run(ctx models.ProjectCommandContext, extraArgs []strin // todo: move this to a common library planFileName := fmt.Sprintf("%s.tfplan", ctx.Workspace) - if ctx.ProjectName != "" { - planFileName = fmt.Sprintf("%s-%s", ctx.ProjectName, planFileName) + if ctx.ProjectConfig != nil && ctx.ProjectConfig.Name != nil { + planFileName = fmt.Sprintf("%s-%s", *ctx.ProjectConfig.Name, planFileName) } planFile := filepath.Join(path, planFileName) userVar := fmt.Sprintf("%s=%s", atlantisUserTFVar, ctx.User.Username) diff --git a/server/events/runtime/plan_step_runner_test.go b/server/events/runtime/plan_step_runner_test.go index 01dce930b..6c4688699 100644 --- a/server/events/runtime/plan_step_runner_test.go +++ b/server/events/runtime/plan_step_runner_test.go @@ -14,6 +14,7 @@ import ( matchers2 "github.com/runatlantis/atlantis/server/events/run/mocks/matchers" "github.com/runatlantis/atlantis/server/events/runtime" "github.com/runatlantis/atlantis/server/events/terraform/mocks" + "github.com/runatlantis/atlantis/server/events/yaml/valid" "github.com/runatlantis/atlantis/server/logging" . "github.com/runatlantis/atlantis/testing" ) @@ -282,13 +283,16 @@ func TestRun_UsesDiffPathForProject(t *testing.T) { expPlanArgs := []string{"plan", "-refresh", "-no-color", "-out", "/path/projectname-default.tfplan", "-var", "atlantis_user=username", "extra", "args", "comment", "args"} When(terraform.RunCommandWithVersion(logger, "/path", expPlanArgs, tfVersion, "default")).ThenReturn("output", nil) + projectName := "projectname" output, err := s.Run(models.ProjectCommandContext{ Log: logger, Workspace: "default", RepoRelPath: ".", - ProjectName: "projectname", User: models.User{Username: "username"}, CommentArgs: []string{"comment", "args"}, + ProjectConfig: &valid.Project{ + Name: &projectName, + }, }, []string{"extra", "args"}, "/path") Ok(t, err) Equals(t, "output", output) diff --git a/server/events/yaml/valid/valid.go b/server/events/yaml/valid/valid.go index fd182902a..8af0064be 100644 --- a/server/events/yaml/valid/valid.go +++ b/server/events/yaml/valid/valid.go @@ -31,13 +31,14 @@ func (s Spec) GetApplyStage(workflowName string) *Stage { return nil } -func (s Spec) FindProject(dir string, workspace string) *Project { +func (s Spec) FindProjectsByDirWorkspace(dir string, workspace string) []Project { + var ps []Project for _, p := range s.Projects { if p.Dir == dir && p.Workspace == workspace { - return &p + ps = append(ps, p) } } - return nil + return ps } func (s Spec) FindProjectByName(name string) *Project {