Files
atlantis/server/events/project_command_builder_test.go
Luke Kysow 74e9bbb82b Add automerge feature.
Automerging merges pull requests automatically if all plans have been
successfully applied.

* Save status of PR's to BoltDB so after each apply, we can check if
there are pending plans.
* Add new feature where we delete successful plans *unless* all plans
have succeeded *if* automerge is enabled. This was requested by users
because when automerge is enabled, they want to enforce that a pull
request's changes have been fully applied. They asked that plans not be
allowed to be applied "piecemeal" and instead, all plans must be
generated successfully prior to allowing any plans to be applied.
2019-02-06 16:27:43 -06:00

970 lines
28 KiB
Go

package events_test
import (
"io/ioutil"
"path/filepath"
"testing"
. "github.com/petergtz/pegomock"
"github.com/runatlantis/atlantis/server/events"
"github.com/runatlantis/atlantis/server/events/mocks"
"github.com/runatlantis/atlantis/server/events/mocks/matchers"
"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.NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
ParserValidator: &yaml.ParserValidator{},
VCSClient: vcsClient,
ProjectFinder: &events.DefaultProjectFinder{},
AllowRepoConfig: true,
PendingPlanFinder: &events.DefaultPendingPlanFinder{},
AllowRepoConfigFlag: "allow-repo-config",
CommentBuilder: &events.CommentParser{},
}
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, expCtx.projectConfig, actCtx.ProjectConfig)
Equals(t, expCtx.dir, actCtx.RepoRelDir)
Equals(t, expCtx.workspace, actCtx.Workspace)
}
})
}
}
// Test building a plan and apply command for one project.
func TestDefaultProjectCommandBuilder_BuildSinglePlanApplyCommand(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{
RepoRelDir: ".",
Flags: []string{"commentarg"},
Name: events.PlanCommand,
Workspace: "myworkspace",
},
AtlantisYAML: "",
ExpProjectConfig: nil,
ExpCommentArgs: []string{"commentarg"},
ExpWorkspace: "myworkspace",
ExpDir: ".",
},
{
Description: "no atlantis.yaml with project flag",
Cmd: events.CommentCommand{
RepoRelDir: ".",
Name: events.PlanCommand,
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{
RepoRelDir: ".",
Name: events.PlanCommand,
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{
RepoRelDir: ".",
Name: events.PlanCommand,
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{
RepoRelDir: ".",
Name: events.PlanCommand,
Workspace: "myworkspace",
},
AtlantisYAML: `
version: 2
projects:
- dir: .
workspace: notmyworkspace
apply_requirements: [approved]`,
ExpProjectConfig: nil,
ExpErr: "running commands in workspace \"myworkspace\" is not allowed because this directory is only configured for the following workspaces: notmyworkspace",
},
{
Description: "atlantis.yaml with projectname",
Cmd: events.CommentCommand{
Name: events.PlanCommand,
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 mergeable apply requirement",
Cmd: events.CommentCommand{
Name: events.PlanCommand,
ProjectName: "myproject",
},
AtlantisYAML: `
version: 2
projects:
- name: myproject
dir: .
workspace: myworkspace
apply_requirements: [mergeable]`,
ExpProjectConfig: &valid.Project{
Dir: ".",
Workspace: "myworkspace",
Autoplan: valid.Autoplan{
WhenModified: []string{"**/*.tf*"},
Enabled: true,
},
ApplyRequirements: []string{"mergeable"},
Name: String("myproject"),
},
ExpWorkspace: "myworkspace",
ExpDir: ".",
},
{
Description: "atlantis.yaml with mergeable and approved apply requirements",
Cmd: events.CommentCommand{
Name: events.PlanCommand,
ProjectName: "myproject",
},
AtlantisYAML: `
version: 2
projects:
- name: myproject
dir: .
workspace: myworkspace
apply_requirements: [mergeable, approved]`,
ExpProjectConfig: &valid.Project{
Dir: ".",
Workspace: "myworkspace",
Autoplan: valid.Autoplan{
WhenModified: []string{"**/*.tf*"},
Enabled: true,
},
ApplyRequirements: []string{"mergeable", "approved"},
Name: String("myproject"),
},
ExpWorkspace: "myworkspace",
ExpDir: ".",
},
{
Description: "atlantis.yaml with multiple dir/workspaces matching",
Cmd: events.CommentCommand{
Name: events.PlanCommand,
RepoRelDir: ".",
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.PlanCommand,
RepoRelDir: ".",
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.PlanCommand, events.ApplyCommand} {
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()
expWorkspace := c.Cmd.Workspace
if expWorkspace == "" {
expWorkspace = "default"
}
if cmdName == events.PlanCommand {
When(workingDir.Clone(logger, baseRepo, headRepo, pull, expWorkspace)).ThenReturn(tmpDir, nil)
} else {
When(workingDir.GetWorkingDir(baseRepo, pull, expWorkspace)).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.NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
ParserValidator: &yaml.ParserValidator{},
VCSClient: vcsClient,
ProjectFinder: &events.DefaultProjectFinder{},
AllowRepoConfig: true,
AllowRepoConfigFlag: "allow-repo-config",
CommentBuilder: &events.CommentParser{},
}
cmdCtx := &events.CommandContext{
BaseRepo: baseRepo,
HeadRepo: headRepo,
Pull: pull,
User: models.User{},
Log: logger,
}
var actCtxs []models.ProjectCommandContext
if cmdName == events.PlanCommand {
actCtxs, err = builder.BuildPlanCommands(cmdCtx, &c.Cmd)
} else {
actCtxs, err = builder.BuildApplyCommands(cmdCtx, &c.Cmd)
}
if c.ExpErr != "" {
ErrEquals(t, c.ExpErr, err)
return
}
Ok(t, err)
Equals(t, 1, len(actCtxs))
actCtx := actCtxs[0]
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, c.ExpProjectConfig, actCtx.ProjectConfig)
Equals(t, c.ExpDir, actCtx.RepoRelDir)
Equals(t, c.ExpWorkspace, actCtx.Workspace)
Equals(t, c.ExpCommentArgs, actCtx.CommentArgs)
})
}
}
}
// Test building plan command for multiple projects when the comment
// isn't for a specific project, i.e. atlantis plan and there's no atlantis.yaml.
// In this case we should just use the list of modified projects.
func TestDefaultProjectCommandBuilder_BuildMultiPlanNoAtlantisYAML(t *testing.T) {
RegisterMockTestingT(t)
tmpDir, cleanup := DirStructure(t, map[string]interface{}{
"project1": map[string]interface{}{
"main.tf": nil,
},
"project2": map[string]interface{}{
"main.tf": nil,
},
})
defer cleanup()
workingDir := mocks.NewMockWorkingDir()
When(workingDir.Clone(
matchers.AnyPtrToLoggingSimpleLogger(),
matchers.AnyModelsRepo(),
matchers.AnyModelsRepo(),
matchers.AnyModelsPullRequest(),
AnyString())).ThenReturn(tmpDir, nil)
vcsClient := vcsmocks.NewMockClientProxy()
When(vcsClient.GetModifiedFiles(matchers.AnyModelsRepo(), matchers.AnyModelsPullRequest())).ThenReturn([]string{"project1/main.tf", "project2/main.tf"}, nil)
builder := &events.DefaultProjectCommandBuilder{
WorkingDirLocker: events.NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
ParserValidator: &yaml.ParserValidator{},
VCSClient: vcsClient,
ProjectFinder: &events.DefaultProjectFinder{},
AllowRepoConfig: true,
AllowRepoConfigFlag: "allow-repo-config",
CommentBuilder: &events.CommentParser{},
}
ctxs, err := builder.BuildPlanCommands(&events.CommandContext{
BaseRepo: models.Repo{},
HeadRepo: models.Repo{},
Pull: models.PullRequest{},
User: models.User{},
Log: logging.NewNoopLogger(),
}, &events.CommentCommand{
RepoRelDir: "",
Flags: nil,
Name: events.PlanCommand,
Verbose: false,
Workspace: "",
ProjectName: "",
})
Ok(t, err)
Equals(t, 2, len(ctxs))
Equals(t, "project1", ctxs[0].RepoRelDir)
Equals(t, "default", ctxs[0].Workspace)
var nilProjectConfig *valid.Project
Equals(t, nilProjectConfig, ctxs[0].ProjectConfig)
Equals(t, "project2", ctxs[1].RepoRelDir)
Equals(t, "default", ctxs[1].Workspace)
Equals(t, nilProjectConfig, ctxs[1].ProjectConfig)
}
// Test building plan command for multiple projects when the comment
// isn't for a specific project, i.e. atlantis plan and there's no atlantis.yaml.
// In this case there are no modified files so there should be 0 plans.
func TestDefaultProjectCommandBuilder_BuildMultiPlanNoAtlantisYAMLNoModified(t *testing.T) {
RegisterMockTestingT(t)
tmpDir, cleanup := TempDir(t)
defer cleanup()
workingDir := mocks.NewMockWorkingDir()
When(workingDir.Clone(
matchers.AnyPtrToLoggingSimpleLogger(),
matchers.AnyModelsRepo(),
matchers.AnyModelsRepo(),
matchers.AnyModelsPullRequest(),
AnyString())).ThenReturn(tmpDir, nil)
vcsClient := vcsmocks.NewMockClientProxy()
When(vcsClient.GetModifiedFiles(matchers.AnyModelsRepo(), matchers.AnyModelsPullRequest())).ThenReturn([]string{}, nil)
builder := &events.DefaultProjectCommandBuilder{
WorkingDirLocker: events.NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
ParserValidator: &yaml.ParserValidator{},
VCSClient: vcsClient,
ProjectFinder: &events.DefaultProjectFinder{},
AllowRepoConfig: true,
AllowRepoConfigFlag: "allow-repo-config",
CommentBuilder: &events.CommentParser{},
}
ctxs, err := builder.BuildPlanCommands(&events.CommandContext{
BaseRepo: models.Repo{},
HeadRepo: models.Repo{},
Pull: models.PullRequest{},
User: models.User{},
Log: logging.NewNoopLogger(),
}, &events.CommentCommand{
RepoRelDir: "",
Flags: nil,
Name: events.PlanCommand,
Verbose: false,
Workspace: "",
ProjectName: "",
})
Ok(t, err)
Equals(t, 0, len(ctxs))
}
// Test building plan command for multiple projects when the comment
// isn't for a specific project, i.e. atlantis plan and there is an atlantis.yaml.
// In this case we should follow the when_modified section of the autoplan config.
func TestDefaultProjectCommandBuilder_BuildMultiPlanWithAtlantisYAML(t *testing.T) {
RegisterMockTestingT(t)
tmpDir, cleanup := DirStructure(t, map[string]interface{}{
"project1": map[string]interface{}{
"main.tf": nil,
},
"project2": map[string]interface{}{
"main.tf": nil,
},
"project3": map[string]interface{}{
"main.tf": nil,
},
})
defer cleanup()
yamlCfg := `version: 2
projects:
- dir: project1 # project1 uses the defaults
- dir: project2 # project2 has autoplan disabled but should use default when_modified
autoplan:
enabled: false
- dir: project3 # project3 has an empty when_modified
autoplan:
enabled: false
when_modified: []
`
err := ioutil.WriteFile(filepath.Join(tmpDir, yaml.AtlantisYAMLFilename), []byte(yamlCfg), 0600)
Ok(t, err)
workingDir := mocks.NewMockWorkingDir()
When(workingDir.Clone(
matchers.AnyPtrToLoggingSimpleLogger(),
matchers.AnyModelsRepo(),
matchers.AnyModelsRepo(),
matchers.AnyModelsPullRequest(),
AnyString())).ThenReturn(tmpDir, nil)
vcsClient := vcsmocks.NewMockClientProxy()
When(vcsClient.GetModifiedFiles(matchers.AnyModelsRepo(), matchers.AnyModelsPullRequest())).ThenReturn([]string{
"project1/main.tf", "project2/main.tf", "project3/main.tf",
}, nil)
builder := &events.DefaultProjectCommandBuilder{
WorkingDirLocker: events.NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
ParserValidator: &yaml.ParserValidator{},
VCSClient: vcsClient,
ProjectFinder: &events.DefaultProjectFinder{},
AllowRepoConfig: true,
AllowRepoConfigFlag: "allow-repo-config",
CommentBuilder: &events.CommentParser{},
}
ctxs, err := builder.BuildPlanCommands(&events.CommandContext{
BaseRepo: models.Repo{},
HeadRepo: models.Repo{},
Pull: models.PullRequest{},
User: models.User{},
Log: logging.NewNoopLogger(),
}, &events.CommentCommand{
RepoRelDir: "",
Flags: nil,
Name: events.PlanCommand,
Verbose: false,
Workspace: "",
ProjectName: "",
})
Ok(t, err)
Equals(t, 2, len(ctxs))
Equals(t, "project1", ctxs[0].RepoRelDir)
Equals(t, "default", ctxs[0].Workspace)
Equals(t, "project2", ctxs[1].RepoRelDir)
Equals(t, "default", ctxs[1].Workspace)
}
// Test building plan command for multiple projects when the comment
// isn't for a specific project, i.e. atlantis plan and there is an atlantis.yaml.
// In this case we should follow the when_modified section of the autoplan config.
func TestDefaultProjectCommandBuilder_BuildMultiPlanWithAtlantisYAMLWorkspaces(t *testing.T) {
RegisterMockTestingT(t)
tmpDir, cleanup := DirStructure(t, map[string]interface{}{
"main.tf": nil,
})
defer cleanup()
yamlCfg := `version: 2
projects:
- dir: .
workspace: staging
- dir: .
workspace: production
`
err := ioutil.WriteFile(filepath.Join(tmpDir, yaml.AtlantisYAMLFilename), []byte(yamlCfg), 0600)
Ok(t, err)
workingDir := mocks.NewMockWorkingDir()
When(workingDir.Clone(
matchers.AnyPtrToLoggingSimpleLogger(),
matchers.AnyModelsRepo(),
matchers.AnyModelsRepo(),
matchers.AnyModelsPullRequest(),
AnyString())).ThenReturn(tmpDir, nil)
vcsClient := vcsmocks.NewMockClientProxy()
When(vcsClient.GetModifiedFiles(matchers.AnyModelsRepo(), matchers.AnyModelsPullRequest())).ThenReturn([]string{"main.tf"}, nil)
builder := &events.DefaultProjectCommandBuilder{
WorkingDirLocker: events.NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
ParserValidator: &yaml.ParserValidator{},
VCSClient: vcsClient,
ProjectFinder: &events.DefaultProjectFinder{},
AllowRepoConfig: true,
AllowRepoConfigFlag: "allow-repo-config",
CommentBuilder: &events.CommentParser{},
}
ctxs, err := builder.BuildPlanCommands(&events.CommandContext{
BaseRepo: models.Repo{},
HeadRepo: models.Repo{},
Pull: models.PullRequest{},
User: models.User{},
Log: logging.NewNoopLogger(),
}, &events.CommentCommand{
RepoRelDir: "",
Flags: nil,
Name: events.PlanCommand,
Verbose: false,
Workspace: "",
ProjectName: "",
})
Ok(t, err)
Equals(t, 2, len(ctxs))
Equals(t, ".", ctxs[0].RepoRelDir)
Equals(t, "staging", ctxs[0].Workspace)
Equals(t, ".", ctxs[1].RepoRelDir)
Equals(t, "production", ctxs[1].Workspace)
}
// Test building apply command for multiple projects when the comment
// isn't for a specific project, i.e. atlantis apply.
// In this case we should apply all outstanding plans.
func TestDefaultProjectCommandBuilder_BuildMultiApply(t *testing.T) {
RegisterMockTestingT(t)
tmpDir, cleanup := DirStructure(t, map[string]interface{}{
"workspace1": map[string]interface{}{
"project1": map[string]interface{}{
"main.tf": nil,
"workspace.tfplan": nil,
},
"project2": map[string]interface{}{
"main.tf": nil,
"workspace.tfplan": nil,
},
},
"workspace2": map[string]interface{}{
"project1": map[string]interface{}{
"main.tf": nil,
"workspace.tfplan": nil,
},
"project2": map[string]interface{}{
"main.tf": nil,
"workspace.tfplan": nil,
},
},
})
defer cleanup()
// Initialize git repos in each workspace so that the .tfplan files get
// picked up.
runCmd(t, filepath.Join(tmpDir, "workspace1"), "git", "init")
runCmd(t, filepath.Join(tmpDir, "workspace2"), "git", "init")
workingDir := mocks.NewMockWorkingDir()
When(workingDir.GetPullDir(
matchers.AnyModelsRepo(),
matchers.AnyModelsPullRequest())).
ThenReturn(tmpDir, nil)
builder := &events.DefaultProjectCommandBuilder{
WorkingDirLocker: events.NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
ParserValidator: &yaml.ParserValidator{},
VCSClient: nil,
ProjectFinder: &events.DefaultProjectFinder{},
AllowRepoConfig: true,
AllowRepoConfigFlag: "allow-repo-config",
PendingPlanFinder: &events.DefaultPendingPlanFinder{},
CommentBuilder: &events.CommentParser{},
}
ctxs, err := builder.BuildApplyCommands(&events.CommandContext{
BaseRepo: models.Repo{},
HeadRepo: models.Repo{},
Pull: models.PullRequest{},
User: models.User{},
Log: logging.NewNoopLogger(),
}, &events.CommentCommand{
RepoRelDir: "",
Flags: nil,
Name: events.ApplyCommand,
Verbose: false,
Workspace: "",
ProjectName: "",
})
Ok(t, err)
Equals(t, 4, len(ctxs))
Equals(t, "project1", ctxs[0].RepoRelDir)
Equals(t, "workspace1", ctxs[0].Workspace)
Equals(t, "project2", ctxs[1].RepoRelDir)
Equals(t, "workspace1", ctxs[1].Workspace)
Equals(t, "project1", ctxs[2].RepoRelDir)
Equals(t, "workspace2", ctxs[2].Workspace)
Equals(t, "project2", ctxs[3].RepoRelDir)
Equals(t, "workspace2", ctxs[3].Workspace)
}
// Test that if repo config is disabled we error out if there's an atlantis.yaml
// file.
func TestDefaultProjectCommandBuilder_RepoConfigDisabled(t *testing.T) {
RegisterMockTestingT(t)
workingDir := mocks.NewMockWorkingDir()
tmpDir, cleanup := DirStructure(t, map[string]interface{}{
"pulldir": map[string]interface{}{
"workspace": map[string]interface{}{},
},
})
defer cleanup()
repoDir := filepath.Join(tmpDir, "pulldir/workspace")
err := ioutil.WriteFile(filepath.Join(repoDir, yaml.AtlantisYAMLFilename), nil, 0600)
Ok(t, err)
When(workingDir.Clone(
matchers.AnyPtrToLoggingSimpleLogger(),
matchers.AnyModelsRepo(),
matchers.AnyModelsRepo(),
matchers.AnyModelsPullRequest(),
AnyString())).ThenReturn(repoDir, nil)
When(workingDir.GetWorkingDir(
matchers.AnyModelsRepo(),
matchers.AnyModelsPullRequest(),
AnyString())).ThenReturn(repoDir, nil)
builder := &events.DefaultProjectCommandBuilder{
WorkingDirLocker: events.NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
ParserValidator: &yaml.ParserValidator{},
VCSClient: nil,
ProjectFinder: &events.DefaultProjectFinder{},
AllowRepoConfig: false,
AllowRepoConfigFlag: "allow-repo-config",
CommentBuilder: &events.CommentParser{},
}
ctx := &events.CommandContext{
BaseRepo: models.Repo{},
HeadRepo: models.Repo{},
Pull: models.PullRequest{},
User: models.User{},
Log: logging.NewNoopLogger(),
}
_, err = builder.BuildAutoplanCommands(ctx)
ErrEquals(t, "atlantis.yaml files not allowed because Atlantis is not running with --allow-repo-config", err)
commentCmd := &events.CommentCommand{
RepoRelDir: "",
Flags: nil,
Name: 0,
Verbose: false,
Workspace: "workspace",
ProjectName: "",
}
_, err = builder.BuildPlanCommands(ctx, commentCmd)
ErrEquals(t, "atlantis.yaml files not allowed because Atlantis is not running with --allow-repo-config", err)
_, err = builder.BuildApplyCommands(ctx, commentCmd)
ErrEquals(t, "atlantis.yaml files not allowed because Atlantis is not running with --allow-repo-config", err)
}
// Test that if a directory has a list of workspaces configured then we don't
// allow plans for other workspace names.
func TestDefaultProjectCommandBuilder_WrongWorkspaceName(t *testing.T) {
RegisterMockTestingT(t)
workingDir := mocks.NewMockWorkingDir()
tmpDir, cleanup := DirStructure(t, map[string]interface{}{
"pulldir": map[string]interface{}{
"notconfigured": map[string]interface{}{},
},
})
defer cleanup()
repoDir := filepath.Join(tmpDir, "pulldir/notconfigured")
yamlCfg := `version: 2
projects:
- dir: .
workspace: default
- dir: .
workspace: staging
`
err := ioutil.WriteFile(filepath.Join(repoDir, yaml.AtlantisYAMLFilename), []byte(yamlCfg), 0600)
Ok(t, err)
When(workingDir.Clone(
matchers.AnyPtrToLoggingSimpleLogger(),
matchers.AnyModelsRepo(),
matchers.AnyModelsRepo(),
matchers.AnyModelsPullRequest(),
AnyString())).ThenReturn(repoDir, nil)
When(workingDir.GetWorkingDir(
matchers.AnyModelsRepo(),
matchers.AnyModelsPullRequest(),
AnyString())).ThenReturn(repoDir, nil)
builder := &events.DefaultProjectCommandBuilder{
WorkingDirLocker: events.NewDefaultWorkingDirLocker(),
WorkingDir: workingDir,
ParserValidator: &yaml.ParserValidator{},
VCSClient: nil,
ProjectFinder: &events.DefaultProjectFinder{},
AllowRepoConfig: true,
AllowRepoConfigFlag: "allow-repo-config",
CommentBuilder: &events.CommentParser{},
}
ctx := &events.CommandContext{
BaseRepo: models.Repo{},
HeadRepo: models.Repo{},
Pull: models.PullRequest{},
User: models.User{},
Log: logging.NewNoopLogger(),
}
_, err = builder.BuildPlanCommands(ctx, &events.CommentCommand{
RepoRelDir: ".",
Flags: nil,
Name: events.PlanCommand,
Verbose: false,
Workspace: "notconfigured",
ProjectName: "",
})
ErrEquals(t, "running commands in workspace \"notconfigured\" is not allowed because this directory is only configured for the following workspaces: default, staging", err)
}
func String(v string) *string { return &v }