Files
atlantis/server/events/post_workflow_hooks_command_runner_test.go
KevinSnyderCodes 5288389897 fix: allow projects in separate directories to run in parallel (#2131)
* fix: add path to DefaultWorkingDirLocker.TryLock()

In the release notes for v0.13.0: https://github.com/runatlantis/atlantis/blob/master/CHANGELOG.md#features-4

> Running in parallel is only supported if you're using workspaces to separate your projects. Projects in separate directories can not be run in parallel currently.

This commit adds `path` as an argument to `DefaultWorkingDirLocker.TryLock()` and includes `path` in the `workspaceKey` used to check if a project is locked. This should allow projects in separate directories to run in parallel.

To my knowledge, there is no functional reason that projects in separate directories cannot run in parallel.

All calls to `DefaultWorkingDirLocker.TryLock()` have been updated. A new unit test `TestTryLockWithDifferentPaths` has been added to test the behavior of locking two separate directories with the same workspace name.

* Add documntation for parallel_plan and parallel_apply options

Co-authored-by: Kevin Snyder <kevinsnyder@ip-192-168-4-61.ec2.internal>
Co-authored-by: Kevin Snyder <kevinsnyder@ip-10-60-10-94.ec2.internal>
2022-03-21 16:08:52 -07:00

220 lines
6.4 KiB
Go

package events_test
import (
"errors"
"testing"
runtime_mocks "github.com/runatlantis/atlantis/server/core/runtime/mocks"
. "github.com/petergtz/pegomock"
"github.com/runatlantis/atlantis/server/core/config/valid"
"github.com/runatlantis/atlantis/server/events"
"github.com/runatlantis/atlantis/server/events/command"
"github.com/runatlantis/atlantis/server/events/mocks"
"github.com/runatlantis/atlantis/server/events/models"
"github.com/runatlantis/atlantis/server/events/models/fixtures"
vcsmocks "github.com/runatlantis/atlantis/server/events/vcs/mocks"
"github.com/runatlantis/atlantis/server/logging"
. "github.com/runatlantis/atlantis/testing"
)
var postWh events.DefaultPostWorkflowHooksCommandRunner
var postWhWorkingDir *mocks.MockWorkingDir
var postWhWorkingDirLocker *mocks.MockWorkingDirLocker
var whPostWorkflowHookRunner *runtime_mocks.MockPostWorkflowHookRunner
func postWorkflowHooksSetup(t *testing.T) {
RegisterMockTestingT(t)
vcsClient := vcsmocks.NewMockClient()
postWhWorkingDir = mocks.NewMockWorkingDir()
postWhWorkingDirLocker = mocks.NewMockWorkingDirLocker()
whPostWorkflowHookRunner = runtime_mocks.NewMockPostWorkflowHookRunner()
postWh = events.DefaultPostWorkflowHooksCommandRunner{
VCSClient: vcsClient,
WorkingDirLocker: postWhWorkingDirLocker,
WorkingDir: postWhWorkingDir,
PostWorkflowHookRunner: whPostWorkflowHookRunner,
}
}
func TestRunPostHooks_Clone(t *testing.T) {
log := logging.NewNoopLogger(t)
var newPull = fixtures.Pull
newPull.BaseRepo = fixtures.GithubRepo
ctx := &command.Context{
Pull: newPull,
HeadRepo: fixtures.GithubRepo,
User: fixtures.User,
Log: log,
}
testHook := valid.WorkflowHook{
StepName: "test",
RunCommand: "some command",
}
pCtx := models.WorkflowHookCommandContext{
BaseRepo: fixtures.GithubRepo,
HeadRepo: fixtures.GithubRepo,
Pull: newPull,
Log: log,
User: fixtures.User,
Verbose: false,
}
repoDir := "path/to/repo"
result := "some result"
t.Run("success hooks in cfg", func(t *testing.T) {
postWorkflowHooksSetup(t)
var unlockCalled *bool = newBool(false)
unlockFn := func() {
unlockCalled = newBool(true)
}
globalCfg := valid.GlobalCfg{
Repos: []valid.Repo{
{
ID: fixtures.GithubRepo.ID(),
PostWorkflowHooks: []*valid.WorkflowHook{
&testHook,
},
},
},
}
postWh.GlobalCfg = globalCfg
When(postWhWorkingDirLocker.TryLock(fixtures.GithubRepo.FullName, newPull.Num, events.DefaultWorkspace, events.DefaultRepoRelDir)).ThenReturn(unlockFn, nil)
When(postWhWorkingDir.Clone(log, fixtures.GithubRepo, newPull, events.DefaultWorkspace)).ThenReturn(repoDir, false, nil)
When(whPostWorkflowHookRunner.Run(pCtx, testHook.RunCommand, repoDir)).ThenReturn(result, nil)
err := postWh.RunPostHooks(ctx)
Ok(t, err)
whPostWorkflowHookRunner.VerifyWasCalledOnce().Run(pCtx, testHook.RunCommand, repoDir)
Assert(t, *unlockCalled == true, "unlock function called")
})
t.Run("success hooks not in cfg", func(t *testing.T) {
postWorkflowHooksSetup(t)
globalCfg := valid.GlobalCfg{
Repos: []valid.Repo{
// one with hooks but mismatched id
{
ID: "id1",
PostWorkflowHooks: []*valid.WorkflowHook{
&testHook,
},
},
// one with the correct id but no hooks
{
ID: fixtures.GithubRepo.ID(),
PostWorkflowHooks: []*valid.WorkflowHook{},
},
},
}
postWh.GlobalCfg = globalCfg
err := postWh.RunPostHooks(ctx)
Ok(t, err)
whPostWorkflowHookRunner.VerifyWasCalled(Never()).Run(pCtx, testHook.RunCommand, repoDir)
postWhWorkingDirLocker.VerifyWasCalled(Never()).TryLock(fixtures.GithubRepo.FullName, newPull.Num, events.DefaultWorkspace)
postWhWorkingDir.VerifyWasCalled(Never()).Clone(log, fixtures.GithubRepo, newPull, events.DefaultWorkspace)
})
t.Run("error locking work dir", func(t *testing.T) {
postWorkflowHooksSetup(t)
globalCfg := valid.GlobalCfg{
Repos: []valid.Repo{
{
ID: fixtures.GithubRepo.ID(),
PostWorkflowHooks: []*valid.WorkflowHook{
&testHook,
},
},
},
}
postWh.GlobalCfg = globalCfg
When(postWhWorkingDirLocker.TryLock(fixtures.GithubRepo.FullName, newPull.Num, events.DefaultWorkspace, events.DefaultRepoRelDir)).ThenReturn(func() {}, errors.New("some error"))
err := postWh.RunPostHooks(ctx)
Assert(t, err != nil, "error not nil")
postWhWorkingDir.VerifyWasCalled(Never()).Clone(log, fixtures.GithubRepo, newPull, events.DefaultWorkspace)
whPostWorkflowHookRunner.VerifyWasCalled(Never()).Run(pCtx, testHook.RunCommand, repoDir)
})
t.Run("error cloning", func(t *testing.T) {
postWorkflowHooksSetup(t)
var unlockCalled *bool = newBool(false)
unlockFn := func() {
unlockCalled = newBool(true)
}
globalCfg := valid.GlobalCfg{
Repos: []valid.Repo{
{
ID: fixtures.GithubRepo.ID(),
PostWorkflowHooks: []*valid.WorkflowHook{
&testHook,
},
},
},
}
postWh.GlobalCfg = globalCfg
When(postWhWorkingDirLocker.TryLock(fixtures.GithubRepo.FullName, newPull.Num, events.DefaultWorkspace, events.DefaultRepoRelDir)).ThenReturn(unlockFn, nil)
When(postWhWorkingDir.Clone(log, fixtures.GithubRepo, newPull, events.DefaultWorkspace)).ThenReturn(repoDir, false, errors.New("some error"))
err := postWh.RunPostHooks(ctx)
Assert(t, err != nil, "error not nil")
whPostWorkflowHookRunner.VerifyWasCalled(Never()).Run(pCtx, testHook.RunCommand, repoDir)
Assert(t, *unlockCalled == true, "unlock function called")
})
t.Run("error running post hook", func(t *testing.T) {
postWorkflowHooksSetup(t)
var unlockCalled *bool = newBool(false)
unlockFn := func() {
unlockCalled = newBool(true)
}
globalCfg := valid.GlobalCfg{
Repos: []valid.Repo{
{
ID: fixtures.GithubRepo.ID(),
PostWorkflowHooks: []*valid.WorkflowHook{
&testHook,
},
},
},
}
postWh.GlobalCfg = globalCfg
When(postWhWorkingDirLocker.TryLock(fixtures.GithubRepo.FullName, newPull.Num, events.DefaultWorkspace, events.DefaultRepoRelDir)).ThenReturn(unlockFn, nil)
When(postWhWorkingDir.Clone(log, fixtures.GithubRepo, newPull, events.DefaultWorkspace)).ThenReturn(repoDir, false, nil)
When(whPostWorkflowHookRunner.Run(pCtx, testHook.RunCommand, repoDir)).ThenReturn(result, errors.New("some error"))
err := postWh.RunPostHooks(ctx)
Assert(t, err != nil, "error not nil")
Assert(t, *unlockCalled == true, "unlock function called")
})
}