mirror of
https://git.vectorsigma.ru/public/atlantis.git
synced 2026-08-03 21:48:52 +00:00
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.
970 lines
28 KiB
Go
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 }
|