From f067818aa54d89dbf8230cc4226d3f6bb99aba01 Mon Sep 17 00:00:00 2001 From: Luke Kysow Date: Sun, 1 Jul 2018 23:48:32 +0200 Subject: [PATCH] Test project command runner. --- .../mocks/matchers/webhooks_applyresult.go | 20 + server/events/mocks/mock_step_runner.go | 88 +++ server/events/mocks/mock_webhooks_sender.go | 81 +++ server/events/models/models.go | 5 +- server/events/project_command_builder.go | 22 +- server/events/project_command_builder_test.go | 6 +- server/events/project_command_runner.go | 33 +- server/events/project_command_runner_test.go | 673 +++++++++++------- .../mocks/matchers/models_pullrequest.go | 20 + .../runtime/mocks/matchers/models_repo.go | 20 + .../mocks/mock_pull_approved_checker.go | 84 +++ .../events/runtime/pull_approved_checker.go | 2 + server/events/working_dir_locker.go | 16 +- server/events/working_dir_locker_test.go | 16 +- server/events_controller_e2e_test.go | 11 +- server/server.go | 20 +- 16 files changed, 780 insertions(+), 337 deletions(-) create mode 100644 server/events/mocks/matchers/webhooks_applyresult.go create mode 100644 server/events/mocks/mock_step_runner.go create mode 100644 server/events/mocks/mock_webhooks_sender.go create mode 100644 server/events/runtime/mocks/matchers/models_pullrequest.go create mode 100644 server/events/runtime/mocks/matchers/models_repo.go create mode 100644 server/events/runtime/mocks/mock_pull_approved_checker.go diff --git a/server/events/mocks/matchers/webhooks_applyresult.go b/server/events/mocks/matchers/webhooks_applyresult.go new file mode 100644 index 000000000..2a643f603 --- /dev/null +++ b/server/events/mocks/matchers/webhooks_applyresult.go @@ -0,0 +1,20 @@ +package matchers + +import ( + "reflect" + + "github.com/petergtz/pegomock" + webhooks "github.com/runatlantis/atlantis/server/events/webhooks" +) + +func AnyWebhooksApplyResult() webhooks.ApplyResult { + pegomock.RegisterMatcher(pegomock.NewAnyMatcher(reflect.TypeOf((*(webhooks.ApplyResult))(nil)).Elem())) + var nullValue webhooks.ApplyResult + return nullValue +} + +func EqWebhooksApplyResult(value webhooks.ApplyResult) webhooks.ApplyResult { + pegomock.RegisterMatcher(&pegomock.EqMatcher{Value: value}) + var nullValue webhooks.ApplyResult + return nullValue +} diff --git a/server/events/mocks/mock_step_runner.go b/server/events/mocks/mock_step_runner.go new file mode 100644 index 000000000..5668e90ff --- /dev/null +++ b/server/events/mocks/mock_step_runner.go @@ -0,0 +1,88 @@ +// Automatically generated by pegomock. DO NOT EDIT! +// Source: github.com/runatlantis/atlantis/server/events (interfaces: StepRunner) + +package mocks + +import ( + "reflect" + + pegomock "github.com/petergtz/pegomock" + models "github.com/runatlantis/atlantis/server/events/models" +) + +type MockStepRunner struct { + fail func(message string, callerSkip ...int) +} + +func NewMockStepRunner() *MockStepRunner { + return &MockStepRunner{fail: pegomock.GlobalFailHandler} +} + +func (mock *MockStepRunner) Run(ctx models.ProjectCommandContext, extraArgs []string, path string) (string, error) { + params := []pegomock.Param{ctx, extraArgs, path} + result := pegomock.GetGenericMockFrom(mock).Invoke("Run", params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) + var ret0 string + var ret1 error + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].(string) + } + if result[1] != nil { + ret1 = result[1].(error) + } + } + return ret0, ret1 +} + +func (mock *MockStepRunner) VerifyWasCalledOnce() *VerifierStepRunner { + return &VerifierStepRunner{mock, pegomock.Times(1), nil} +} + +func (mock *MockStepRunner) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierStepRunner { + return &VerifierStepRunner{mock, invocationCountMatcher, nil} +} + +func (mock *MockStepRunner) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierStepRunner { + return &VerifierStepRunner{mock, invocationCountMatcher, inOrderContext} +} + +type VerifierStepRunner struct { + mock *MockStepRunner + invocationCountMatcher pegomock.Matcher + inOrderContext *pegomock.InOrderContext +} + +func (verifier *VerifierStepRunner) Run(ctx models.ProjectCommandContext, extraArgs []string, path string) *StepRunner_Run_OngoingVerification { + params := []pegomock.Param{ctx, extraArgs, path} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Run", params) + return &StepRunner_Run_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type StepRunner_Run_OngoingVerification struct { + mock *MockStepRunner + methodInvocations []pegomock.MethodInvocation +} + +func (c *StepRunner_Run_OngoingVerification) GetCapturedArguments() (models.ProjectCommandContext, []string, string) { + ctx, extraArgs, path := c.GetAllCapturedArguments() + return ctx[len(ctx)-1], extraArgs[len(extraArgs)-1], path[len(path)-1] +} + +func (c *StepRunner_Run_OngoingVerification) GetAllCapturedArguments() (_param0 []models.ProjectCommandContext, _param1 [][]string, _param2 []string) { + params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) + if len(params) > 0 { + _param0 = make([]models.ProjectCommandContext, len(params[0])) + for u, param := range params[0] { + _param0[u] = param.(models.ProjectCommandContext) + } + _param1 = make([][]string, len(params[1])) + for u, param := range params[1] { + _param1[u] = param.([]string) + } + _param2 = make([]string, len(params[2])) + for u, param := range params[2] { + _param2[u] = param.(string) + } + } + return +} diff --git a/server/events/mocks/mock_webhooks_sender.go b/server/events/mocks/mock_webhooks_sender.go new file mode 100644 index 000000000..dfd588807 --- /dev/null +++ b/server/events/mocks/mock_webhooks_sender.go @@ -0,0 +1,81 @@ +// Automatically generated by pegomock. DO NOT EDIT! +// Source: github.com/runatlantis/atlantis/server/events (interfaces: WebhooksSender) + +package mocks + +import ( + "reflect" + + pegomock "github.com/petergtz/pegomock" + webhooks "github.com/runatlantis/atlantis/server/events/webhooks" + logging "github.com/runatlantis/atlantis/server/logging" +) + +type MockWebhooksSender struct { + fail func(message string, callerSkip ...int) +} + +func NewMockWebhooksSender() *MockWebhooksSender { + return &MockWebhooksSender{fail: pegomock.GlobalFailHandler} +} + +func (mock *MockWebhooksSender) Send(log *logging.SimpleLogger, res webhooks.ApplyResult) error { + params := []pegomock.Param{log, res} + result := pegomock.GetGenericMockFrom(mock).Invoke("Send", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()}) + var ret0 error + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].(error) + } + } + return ret0 +} + +func (mock *MockWebhooksSender) VerifyWasCalledOnce() *VerifierWebhooksSender { + return &VerifierWebhooksSender{mock, pegomock.Times(1), nil} +} + +func (mock *MockWebhooksSender) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierWebhooksSender { + return &VerifierWebhooksSender{mock, invocationCountMatcher, nil} +} + +func (mock *MockWebhooksSender) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierWebhooksSender { + return &VerifierWebhooksSender{mock, invocationCountMatcher, inOrderContext} +} + +type VerifierWebhooksSender struct { + mock *MockWebhooksSender + invocationCountMatcher pegomock.Matcher + inOrderContext *pegomock.InOrderContext +} + +func (verifier *VerifierWebhooksSender) Send(log *logging.SimpleLogger, res webhooks.ApplyResult) *WebhooksSender_Send_OngoingVerification { + params := []pegomock.Param{log, res} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Send", params) + return &WebhooksSender_Send_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type WebhooksSender_Send_OngoingVerification struct { + mock *MockWebhooksSender + methodInvocations []pegomock.MethodInvocation +} + +func (c *WebhooksSender_Send_OngoingVerification) GetCapturedArguments() (*logging.SimpleLogger, webhooks.ApplyResult) { + log, res := c.GetAllCapturedArguments() + return log[len(log)-1], res[len(res)-1] +} + +func (c *WebhooksSender_Send_OngoingVerification) GetAllCapturedArguments() (_param0 []*logging.SimpleLogger, _param1 []webhooks.ApplyResult) { + params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) + if len(params) > 0 { + _param0 = make([]*logging.SimpleLogger, len(params[0])) + for u, param := range params[0] { + _param0[u] = param.(*logging.SimpleLogger) + } + _param1 = make([]webhooks.ApplyResult, len(params[1])) + for u, param := range params[1] { + _param1[u] = param.(webhooks.ApplyResult) + } + } + return +} diff --git a/server/events/models/models.go b/server/events/models/models.go index e54e42f54..db4eb1c82 100644 --- a/server/events/models/models.go +++ b/server/events/models/models.go @@ -238,7 +238,6 @@ type ProjectCommandContext struct { // CommentArgs are the extra arguments appended to comment, // ex. atlantis plan -- -target=resource - CommentArgs []string - Workspace string - RequireApprovalOverride bool + CommentArgs []string + Workspace string } diff --git a/server/events/project_command_builder.go b/server/events/project_command_builder.go index 549bbb317..cff97ed38 100644 --- a/server/events/project_command_builder.go +++ b/server/events/project_command_builder.go @@ -29,7 +29,6 @@ type DefaultProjectCommandBuilder struct { VCSClient vcs.ClientProxy WorkingDir WorkingDir WorkingDirLocker WorkingDirLocker - RequireApproval bool AllowRepoConfig bool AllowRepoConfigFlag string } @@ -180,17 +179,16 @@ func (p *DefaultProjectCommandBuilder) buildProjectCommandCtx(ctx *CommandContex } return models.ProjectCommandContext{ - BaseRepo: ctx.BaseRepo, - HeadRepo: ctx.HeadRepo, - Pull: ctx.Pull, - User: ctx.User, - Log: ctx.Log, - CommentArgs: cmd.Flags, - Workspace: workspace, - RepoRelDir: dir, - ProjectConfig: projCfg, - GlobalConfig: globalCfg, - RequireApprovalOverride: p.RequireApproval, + BaseRepo: ctx.BaseRepo, + HeadRepo: ctx.HeadRepo, + Pull: ctx.Pull, + User: ctx.User, + Log: ctx.Log, + CommentArgs: cmd.Flags, + Workspace: workspace, + RepoRelDir: dir, + ProjectConfig: projCfg, + GlobalConfig: globalCfg, }, nil } diff --git a/server/events/project_command_builder_test.go b/server/events/project_command_builder_test.go index 1a50de40b..e9278a204 100644 --- a/server/events/project_command_builder_test.go +++ b/server/events/project_command_builder_test.go @@ -190,7 +190,7 @@ projects: When(vcsClient.GetModifiedFiles(baseRepo, pull)).ThenReturn([]string{"main.tf"}, nil) builder := &events.DefaultProjectCommandBuilder{ - WorkingDirLocker: events.NewDefaultAtlantisWorkingDirLocker(), + WorkingDirLocker: events.NewDefaultWorkingDirLocker(), WorkingDir: workingDir, ParserValidator: &yaml.ParserValidator{}, VCSClient: vcsClient, @@ -216,7 +216,6 @@ projects: 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.RepoRelDir) @@ -412,7 +411,7 @@ projects: When(vcsClient.GetModifiedFiles(baseRepo, pull)).ThenReturn([]string{"main.tf"}, nil) builder := &events.DefaultProjectCommandBuilder{ - WorkingDirLocker: events.NewDefaultAtlantisWorkingDirLocker(), + WorkingDirLocker: events.NewDefaultWorkingDirLocker(), WorkingDir: workingDir, ParserValidator: &yaml.ParserValidator{}, VCSClient: vcsClient, @@ -446,7 +445,6 @@ projects: 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.RepoRelDir) diff --git a/server/events/project_command_runner.go b/server/events/project_command_runner.go index 8f74af6a9..1da06d1ad 100644 --- a/server/events/project_command_runner.go +++ b/server/events/project_command_runner.go @@ -34,8 +34,16 @@ type LockURLGenerator interface { GenerateLockURL(lockID string) string } +//go:generate pegomock generate -m --use-experimental-model-gen --package mocks -o mocks/mock_step_runner.go StepRunner + +type StepRunner interface { + Run(ctx models.ProjectCommandContext, extraArgs []string, path string) (string, error) +} + +//go:generate pegomock generate -m --use-experimental-model-gen --package mocks -o mocks/mock_webhooks_sender.go WebhooksSender + type WebhooksSender interface { - Send(log *logging.SimpleLogger, result webhooks.ApplyResult) error + Send(log *logging.SimpleLogger, res webhooks.ApplyResult) error } // PlanSuccess is the result of a successful plan. @@ -52,16 +60,17 @@ type ProjectCommandRunner interface { } type DefaultProjectCommandRunner struct { - Locker ProjectLocker - LockURLGenerator LockURLGenerator - InitStepRunner runtime.InitStepRunner - PlanStepRunner runtime.PlanStepRunner - ApplyStepRunner runtime.ApplyStepRunner - RunStepRunner runtime.RunStepRunner - PullApprovedChecker runtime.PullApprovedChecker - WorkingDir WorkingDir - Webhooks WebhooksSender - WorkingDirLocker WorkingDirLocker + Locker ProjectLocker + LockURLGenerator LockURLGenerator + InitStepRunner StepRunner + PlanStepRunner StepRunner + ApplyStepRunner StepRunner + RunStepRunner StepRunner + PullApprovedChecker runtime.PullApprovedChecker + WorkingDir WorkingDir + Webhooks WebhooksSender + WorkingDirLocker WorkingDirLocker + RequireApprovalOverride bool } func (p *DefaultProjectCommandRunner) Plan(ctx models.ProjectCommandContext) ProjectCommandResult { @@ -160,7 +169,7 @@ func (p *DefaultProjectCommandRunner) Apply(ctx models.ProjectCommandContext) Pr if ctx.ProjectConfig != nil { applyRequirements = ctx.ProjectConfig.ApplyRequirements } - if ctx.RequireApprovalOverride { + if p.RequireApprovalOverride { applyRequirements = []string{raw.ApprovedApplyRequirement} } for _, req := range applyRequirements { diff --git a/server/events/project_command_runner_test.go b/server/events/project_command_runner_test.go index 8d7e63682..85e2e2826 100644 --- a/server/events/project_command_runner_test.go +++ b/server/events/project_command_runner_test.go @@ -13,277 +13,402 @@ // package events_test -// -//import ( -// "errors" -// "testing" -// -// "github.com/mohae/deepcopy" -// . "github.com/petergtz/pegomock" -// "github.com/runatlantis/atlantis/server/events" -// "github.com/runatlantis/atlantis/server/events/locking" -// lmocks "github.com/runatlantis/atlantis/server/events/locking/mocks" -// "github.com/runatlantis/atlantis/server/events/mocks" -// "github.com/runatlantis/atlantis/server/events/models" -// rmocks "github.com/runatlantis/atlantis/server/events/run/mocks" -// tmocks "github.com/runatlantis/atlantis/server/events/terraform/mocks" -// vcsmocks "github.com/runatlantis/atlantis/server/events/vcs/mocks" -// "github.com/runatlantis/atlantis/server/events/vcs/mocks/matchers" -// "github.com/runatlantis/atlantis/server/logging" -// . "github.com/runatlantis/atlantis/testing" -//) -// -//var planCtx = events.CommandContext{ -// Command: &events.Command{ -// Name: events.Plan, -// Workspace: "workspace", -// RepoRelDir: "", -// }, -// Log: logging.NewNoopLogger(), -// BaseRepo: models.Repo{}, -// HeadRepo: models.Repo{}, -// Pull: models.PullRequest{}, -// User: models.User{ -// Username: "anubhavmishra", -// }, -//} -// -//func TestExecute_ModifiedFilesErr(t *testing.T) { -// t.Log("If GetModifiedFiles returns an error we return an error") -// p, _, _ := setupPlanExecutorTest(t) -// When(p.VCSClient.GetModifiedFiles(matchers.AnyModelsRepo(), matchers.AnyModelsPullRequest())).ThenReturn(nil, errors.New("err")) -// r := p.Execute(&planCtx) -// -// Assert(t, r.Error != nil, "exp .Error to be set") -// Equals(t, "getting modified files: err", r.Error.Error()) -//} -// -//func TestExecute_NoModifiedProjects(t *testing.T) { -// t.Log("If there are no modified projects we return a failure") -// p, _, _ := setupPlanExecutorTest(t) -// // We don't need to actually mock VCSClient.GetModifiedFiles because by -// // default it will return an empty slice which is what we want for this test. -// r := p.Execute(&planCtx) -// -// Equals(t, "No Terraform files were modified.", r.Failure) -//} -// -//func TestExecute_CloneErr(t *testing.T) { -// t.Log("If WorkingDir.Clone returns an error we return an error") -// p, _, _ := setupPlanExecutorTest(t) -// When(p.VCSClient.GetModifiedFiles(matchers.AnyModelsRepo(), matchers.AnyModelsPullRequest())).ThenReturn([]string{"file.tf"}, nil) -// When(p.Workspace.Clone(planCtx.Log, planCtx.BaseRepo, planCtx.HeadRepo, planCtx.Pull, "workspace")).ThenReturn("", errors.New("err")) -// r := p.Execute(&planCtx) -// -// Assert(t, r.Error != nil, "exp .Error to be set") -// Equals(t, "err", r.Error.Error()) -//} -// -//func TestExecute_DirectoryAndWorkspaceSet(t *testing.T) { -// t.Log("Test that we run plan in the right directory and workspace if they're set") -// p, runner, _ := setupPlanExecutorTest(t) -// ctx := deepcopy.Copy(planCtx).(events.CommandContext) -// ctx.Log = logging.NewNoopLogger() -// ctx.Command.RepoRelDir = "dir1/dir2" -// ctx.Command.Workspace = "workspace-flag" -// -// When(p.Workspace.Clone(ctx.Log, ctx.BaseRepo, ctx.HeadRepo, ctx.Pull, "workspace-flag")). -// ThenReturn("/tmp/clone-repo", nil) -// When(p.ProjectPreExecute.Execute(&ctx, "/tmp/clone-repo", models.Project{RepoFullName: "", RepoRelDir: "dir1/dir2"})). -// ThenReturn(events.PreExecuteResult{ -// LockResponse: locking.TryLockResponse{ -// LockKey: "key", -// }, -// }) -// r := p.Execute(&ctx) -// -// runner.VerifyWasCalledOnce().RunCommandWithVersion( -// ctx.Log, -// "/tmp/clone-repo/dir1/dir2", -// []string{"plan", "-refresh", "-no-color", "-out", "/tmp/clone-repo/dir1/dir2/workspace-flag.tfplan", "-var", "atlantis_user=anubhavmishra"}, -// nil, -// "workspace-flag", -// ) -// Assert(t, len(r.ProjectResults) == 1, "exp one project result") -// result := r.ProjectResults[0] -// Assert(t, result.PlanSuccess != nil, "exp plan success to not be nil") -// Equals(t, "", result.PlanSuccess.TerraformOutput) -// Equals(t, "lockurl-key", result.PlanSuccess.LockURL) -//} -// -//func TestExecute_AddedArgs(t *testing.T) { -// t.Log("Test that we include extra-args added to the comment in the plan command") -// p, runner, _ := setupPlanExecutorTest(t) -// ctx := deepcopy.Copy(planCtx).(events.CommandContext) -// ctx.Log = logging.NewNoopLogger() -// ctx.Command.CommentArgs = []string{"\"-target=resource\"", "\"-var\"", "\"a=b\"", "\";\"", "\"echo\"", "\"hi\""} -// -// When(p.VCSClient.GetModifiedFiles(matchers.AnyModelsRepo(), matchers.AnyModelsPullRequest())).ThenReturn([]string{"file.tf"}, nil) -// When(p.Workspace.Clone(ctx.Log, ctx.BaseRepo, ctx.HeadRepo, ctx.Pull, "workspace")). -// ThenReturn("/tmp/clone-repo", nil) -// When(p.ProjectPreExecute.Execute(&ctx, "/tmp/clone-repo", models.Project{RepoFullName: "", RepoRelDir: "."})). -// ThenReturn(events.PreExecuteResult{ -// LockResponse: locking.TryLockResponse{ -// LockKey: "key", -// }, -// }) -// r := p.Execute(&ctx) -// -// runner.VerifyWasCalledOnce().RunCommandWithVersion( -// ctx.Log, -// "/tmp/clone-repo", -// []string{ -// "plan", -// "-refresh", -// "-no-color", -// "-out", -// "/tmp/clone-repo/workspace.tfplan", -// "-var", -// "atlantis_user=anubhavmishra", -// // NOTE: extra args should be quoted to prevent an attacker from -// // appending malicious commands. -// "\"-target=resource\"", -// "\"-var\"", -// "\"a=b\"", -// "\";\"", -// "\"echo\"", -// "\"hi\"", -// }, -// nil, -// "workspace", -// ) -// Assert(t, len(r.ProjectResults) == 1, "exp one project result") -// result := r.ProjectResults[0] -// Assert(t, result.PlanSuccess != nil, "exp plan success to not be nil") -// Equals(t, "", result.PlanSuccess.TerraformOutput) -// Equals(t, "lockurl-key", result.PlanSuccess.LockURL) -//} -// -//func TestExecute_Success(t *testing.T) { -// t.Log("If there are no errors, the plan should be returned") -// p, runner, _ := setupPlanExecutorTest(t) -// When(p.VCSClient.GetModifiedFiles(matchers.AnyModelsRepo(), matchers.AnyModelsPullRequest())).ThenReturn([]string{"file.tf"}, nil) -// When(p.Workspace.Clone(planCtx.Log, planCtx.BaseRepo, planCtx.HeadRepo, planCtx.Pull, "workspace")). -// ThenReturn("/tmp/clone-repo", nil) -// When(p.ProjectPreExecute.Execute(&planCtx, "/tmp/clone-repo", models.Project{RepoFullName: "", RepoRelDir: "."})). -// ThenReturn(events.PreExecuteResult{ -// LockResponse: locking.TryLockResponse{ -// LockKey: "key", -// }, -// }) -// -// r := p.Execute(&planCtx) -// -// runner.VerifyWasCalledOnce().RunCommandWithVersion( -// planCtx.Log, -// "/tmp/clone-repo", -// []string{"plan", "-refresh", "-no-color", "-out", "/tmp/clone-repo/workspace.tfplan", "-var", "atlantis_user=anubhavmishra"}, -// nil, -// "workspace", -// ) -// Assert(t, len(r.ProjectResults) == 1, "exp one project result") -// result := r.ProjectResults[0] -// Assert(t, result.PlanSuccess != nil, "exp plan success to not be nil") -// Equals(t, "", result.PlanSuccess.TerraformOutput) -// Equals(t, "lockurl-key", result.PlanSuccess.LockURL) -//} -// -//func TestExecute_PreExecuteResult(t *testing.T) { -// t.Log("If DefaultProjectPreExecutor.Execute returns a ProjectResult we should return it") -// p, _, _ := setupPlanExecutorTest(t) -// When(p.VCSClient.GetModifiedFiles(matchers.AnyModelsRepo(), matchers.AnyModelsPullRequest())).ThenReturn([]string{"file.tf"}, nil) -// When(p.Workspace.Clone(planCtx.Log, planCtx.BaseRepo, planCtx.HeadRepo, planCtx.Pull, "workspace")). -// ThenReturn("/tmp/clone-repo", nil) -// projectResult := events.ProjectResult{ -// Failure: "failure", -// } -// When(p.ProjectPreExecute.Execute(&planCtx, "/tmp/clone-repo", models.Project{RepoFullName: "", RepoRelDir: "."})). -// ThenReturn(events.PreExecuteResult{ProjectResult: projectResult}) -// r := p.Execute(&planCtx) -// -// Assert(t, len(r.ProjectResults) == 1, "exp one project result") -// result := r.ProjectResults[0] -// Equals(t, "failure", result.Failure) -//} -// -//func TestExecute_MultiProjectFailure(t *testing.T) { -// t.Log("If is an error planning in one project it should be returned. It shouldn't affect another project though.") -// p, runner, locker := setupPlanExecutorTest(t) -// // Two projects have been modified so we should run plan in two paths. -// When(p.VCSClient.GetModifiedFiles(matchers.AnyModelsRepo(), matchers.AnyModelsPullRequest())).ThenReturn([]string{"path1/file.tf", "path2/file.tf"}, nil) -// When(p.Workspace.Clone(planCtx.Log, planCtx.BaseRepo, planCtx.HeadRepo, planCtx.Pull, "workspace")). -// ThenReturn("/tmp/clone-repo", nil) -// -// // Both projects will succeed in the PreExecute stage. -// When(p.ProjectPreExecute.Execute(&planCtx, "/tmp/clone-repo", models.Project{RepoFullName: "", RepoRelDir: "path1"})). -// ThenReturn(events.PreExecuteResult{LockResponse: locking.TryLockResponse{LockKey: "key1"}}) -// When(p.ProjectPreExecute.Execute(&planCtx, "/tmp/clone-repo", models.Project{RepoFullName: "", RepoRelDir: "path2"})). -// ThenReturn(events.PreExecuteResult{LockResponse: locking.TryLockResponse{LockKey: "key2"}}) -// -// // The first project will fail when running plan -// When(runner.RunCommandWithVersion( -// planCtx.Log, -// "/tmp/clone-repo/path1", -// []string{"plan", "-refresh", "-no-color", "-out", "/tmp/clone-repo/path1/workspace.tfplan", "-var", "atlantis_user=anubhavmishra"}, -// nil, -// "workspace", -// )).ThenReturn("", errors.New("path1 err")) -// // The second will succeed. We don't need to stub it because by default it -// // will return a nil error. -// r := p.Execute(&planCtx) -// -// // We expect Unlock to be called for the failed project. -// locker.VerifyWasCalledOnce().Unlock("key1") -// -// // So at the end we expect the first project to return an error and the second to be successful. -// Assert(t, len(r.ProjectResults) == 2, "exp two project results") -// result1 := r.ProjectResults[0] -// Assert(t, result1.Error != nil, "exp err to not be nil") -// Equals(t, "path1 err\n", result1.Error.Error()) -// -// result2 := r.ProjectResults[1] -// Assert(t, result2.PlanSuccess != nil, "exp plan success to not be nil") -// Equals(t, "", result2.PlanSuccess.TerraformOutput) -// Equals(t, "lockurl-key2", result2.PlanSuccess.LockURL) -//} -// -//func TestExecute_PostPlanCommands(t *testing.T) { -// t.Log("Should execute post-plan commands and return if there is an error") -// p, _, _ := setupPlanExecutorTest(t) -// When(p.VCSClient.GetModifiedFiles(matchers.AnyModelsRepo(), matchers.AnyModelsPullRequest())).ThenReturn([]string{"file.tf"}, nil) -// When(p.Workspace.Clone(planCtx.Log, planCtx.BaseRepo, planCtx.HeadRepo, planCtx.Pull, "workspace")). -// ThenReturn("/tmp/clone-repo", nil) -// When(p.ProjectPreExecute.Execute(&planCtx, "/tmp/clone-repo", models.Project{RepoFullName: "", RepoRelDir: "."})). -// ThenReturn(events.PreExecuteResult{ -// ProjectConfig: events.ProjectConfig{PostPlan: []string{"post-plan"}}, -// }) -// When(p.Run.Execute(planCtx.Log, []string{"post-plan"}, "/tmp/clone-repo", "workspace", nil, "post_plan")). -// ThenReturn("", errors.New("err")) -// -// r := p.Execute(&planCtx) -// -// Assert(t, len(r.ProjectResults) == 1, "exp one project result") -// result := r.ProjectResults[0] -// Assert(t, result.Error != nil, "exp plan error to not be nil") -// Equals(t, "running post plan commands: err", result.Error.Error()) -//} -// -//func setupPlanExecutorTest(t *testing.T) (*events.PlanExecutor, *tmocks.MockClient, *lmocks.MockLocker) { -// RegisterMockTestingT(t) -// vcsProxy := vcsmocks.NewMockClientProxy() -// w := mocks.NewMockWorkingDir() -// ppe := mocks.NewMockProjectPreExecutor() -// runner := tmocks.NewMockClient() -// locker := lmocks.NewMockLocker() -// run := rmocks.NewMockRunner() -// p := events.PlanExecutor{ -// VCSClient: vcsProxy, -// ProjectFinder: &events.DefaultProjectFinder{}, -// Workspace: w, -// ProjectPreExecute: ppe, -// Terraform: runner, -// Locker: locker, -// Run: run, -// } -// return &p, runner, locker -//} +import ( + "os" + "strings" + "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" + mocks2 "github.com/runatlantis/atlantis/server/events/runtime/mocks" + "github.com/runatlantis/atlantis/server/events/yaml/valid" + "github.com/runatlantis/atlantis/server/logging" + . "github.com/runatlantis/atlantis/testing" +) + +func TestDefaultProjectCommandRunner_Plan(t *testing.T) { + cases := []struct { + description string + projCfg *valid.Project + globalCfg *valid.Config + expSteps []string + expOut string + }{ + { + description: "use defaults", + projCfg: nil, + globalCfg: nil, + expSteps: []string{"init", "plan"}, + expOut: "init\nplan", + }, + { + description: "no workflow, use defaults", + projCfg: &valid.Project{ + Dir: ".", + }, + globalCfg: &valid.Config{ + Version: 2, + Projects: []valid.Project{ + { + Dir: ".", + }, + }, + }, + expSteps: []string{"init", "plan"}, + expOut: "init\nplan", + }, + { + description: "workflow without plan stage set", + projCfg: &valid.Project{ + Dir: ".", + Workflow: String("myworkflow"), + }, + globalCfg: &valid.Config{ + Version: 2, + Projects: []valid.Project{ + { + Dir: ".", + }, + }, + Workflows: map[string]valid.Workflow{ + "myworkflow": { + Apply: &valid.Stage{ + Steps: nil, + }, + }, + }, + }, + expSteps: []string{"init", "plan"}, + expOut: "init\nplan", + }, + { + description: "workflow with custom plan stage", + projCfg: &valid.Project{ + Dir: ".", + Workflow: String("myworkflow"), + }, + globalCfg: &valid.Config{ + Version: 2, + Projects: []valid.Project{ + { + Dir: ".", + }, + }, + Workflows: map[string]valid.Workflow{ + "myworkflow": { + Plan: &valid.Stage{ + Steps: []valid.Step{ + { + StepName: "run", + }, + { + StepName: "apply", + }, + { + StepName: "plan", + }, + { + StepName: "init", + }, + }, + }, + }, + }, + }, + expSteps: []string{"run", "apply", "plan", "init"}, + expOut: "run\napply\nplan\ninit", + }, + } + + for _, c := range cases { + t.Run(strings.Join(c.expSteps, ","), func(t *testing.T) { + RegisterMockTestingT(t) + mockInit := mocks.NewMockStepRunner() + mockPlan := mocks.NewMockStepRunner() + mockApply := mocks.NewMockStepRunner() + mockRun := mocks.NewMockStepRunner() + mockWorkingDir := mocks.NewMockWorkingDir() + mockLocker := mocks.NewMockProjectLocker() + + runner := events.DefaultProjectCommandRunner{ + Locker: mockLocker, + LockURLGenerator: mockURLGenerator{}, + InitStepRunner: mockInit, + PlanStepRunner: mockPlan, + ApplyStepRunner: mockApply, + RunStepRunner: mockRun, + PullApprovedChecker: nil, + WorkingDir: mockWorkingDir, + Webhooks: nil, + WorkingDirLocker: events.NewDefaultWorkingDirLocker(), + } + + repoDir := "/tmp/mydir" + When(mockWorkingDir.Clone( + matchers.AnyPtrToLoggingSimpleLogger(), + matchers.AnyModelsRepo(), + matchers.AnyModelsRepo(), + matchers.AnyModelsPullRequest(), + AnyString(), + )).ThenReturn(repoDir, nil) + When(mockLocker.TryLock( + matchers.AnyPtrToLoggingSimpleLogger(), + matchers.AnyModelsPullRequest(), + matchers.AnyModelsUser(), + AnyString(), + matchers.AnyModelsProject(), + )).ThenReturn(&events.TryLockResponse{ + LockAcquired: true, + LockKey: "lock-key", + }, nil) + + ctx := models.ProjectCommandContext{ + Log: logging.NewNoopLogger(), + ProjectConfig: c.projCfg, + Workspace: "default", + GlobalConfig: c.globalCfg, + RepoRelDir: ".", + } + When(mockInit.Run(ctx, nil, repoDir)).ThenReturn("init", nil) + When(mockPlan.Run(ctx, nil, repoDir)).ThenReturn("plan", nil) + When(mockApply.Run(ctx, nil, repoDir)).ThenReturn("apply", nil) + When(mockRun.Run(ctx, nil, repoDir)).ThenReturn("run", nil) + + res := runner.Plan(ctx) + + Assert(t, res.PlanSuccess != nil, "exp plan success") + Equals(t, "https://lock-key", res.PlanSuccess.LockURL) + Equals(t, c.expOut, res.PlanSuccess.TerraformOutput) + + for _, step := range c.expSteps { + switch step { + case "init": + mockInit.VerifyWasCalledOnce().Run(ctx, nil, repoDir) + case "plan": + mockPlan.VerifyWasCalledOnce().Run(ctx, nil, repoDir) + case "apply": + mockApply.VerifyWasCalledOnce().Run(ctx, nil, repoDir) + case "run": + mockRun.VerifyWasCalledOnce().Run(ctx, nil, repoDir) + } + } + }) + } +} + +func TestDefaultProjectCommandRunner_ApplyNotCloned(t *testing.T) { + mockWorkingDir := mocks.NewMockWorkingDir() + runner := &events.DefaultProjectCommandRunner{ + WorkingDir: mockWorkingDir, + } + ctx := models.ProjectCommandContext{} + When(mockWorkingDir.GetWorkingDir(ctx.BaseRepo, ctx.Pull, ctx.Workspace)).ThenReturn("", os.ErrNotExist) + + res := runner.Apply(ctx) + ErrEquals(t, "project has not been cloned–did you run plan?", res.Error) +} + +func TestDefaultProjectCommandRunner_ApplyNotApproved(t *testing.T) { + RegisterMockTestingT(t) + mockWorkingDir := mocks.NewMockWorkingDir() + mockApproved := mocks2.NewMockPullApprovedChecker() + runner := &events.DefaultProjectCommandRunner{ + WorkingDir: mockWorkingDir, + PullApprovedChecker: mockApproved, + WorkingDirLocker: events.NewDefaultWorkingDirLocker(), + RequireApprovalOverride: true, + } + ctx := models.ProjectCommandContext{} + When(mockWorkingDir.GetWorkingDir(ctx.BaseRepo, ctx.Pull, ctx.Workspace)).ThenReturn("/tmp/mydir", nil) + When(mockApproved.PullIsApproved(ctx.BaseRepo, ctx.Pull)).ThenReturn(false, nil) + + res := runner.Apply(ctx) + Equals(t, "Pull request must be approved before running apply.", res.Failure) +} + +func TestDefaultProjectCommandRunner_Apply(t *testing.T) { + cases := []struct { + description string + projCfg *valid.Project + globalCfg *valid.Config + expSteps []string + expOut string + }{ + { + description: "use defaults", + projCfg: nil, + globalCfg: nil, + expSteps: []string{"apply"}, + expOut: "apply", + }, + { + description: "no workflow, use defaults", + projCfg: &valid.Project{ + Dir: ".", + }, + globalCfg: &valid.Config{ + Version: 2, + Projects: []valid.Project{ + { + Dir: ".", + }, + }, + }, + expSteps: []string{"apply"}, + expOut: "apply", + }, + { + description: "no workflow, approval required, use defaults", + projCfg: &valid.Project{ + Dir: ".", + ApplyRequirements: []string{"approved"}, + }, + globalCfg: &valid.Config{ + Version: 2, + Projects: []valid.Project{ + { + Dir: ".", + ApplyRequirements: []string{"approved"}, + }, + }, + }, + expSteps: []string{"approved", "apply"}, + expOut: "apply", + }, + { + description: "workflow without apply stage set", + projCfg: &valid.Project{ + Dir: ".", + Workflow: String("myworkflow"), + }, + globalCfg: &valid.Config{ + Version: 2, + Projects: []valid.Project{ + { + Dir: ".", + }, + }, + Workflows: map[string]valid.Workflow{ + "myworkflow": { + Plan: &valid.Stage{ + Steps: nil, + }, + }, + }, + }, + expSteps: []string{"apply"}, + expOut: "apply", + }, + { + description: "workflow with custom apply stage", + projCfg: &valid.Project{ + Dir: ".", + Workflow: String("myworkflow"), + }, + globalCfg: &valid.Config{ + Version: 2, + Projects: []valid.Project{ + { + Dir: ".", + }, + }, + Workflows: map[string]valid.Workflow{ + "myworkflow": { + Apply: &valid.Stage{ + Steps: []valid.Step{ + { + StepName: "run", + }, + { + StepName: "apply", + }, + { + StepName: "plan", + }, + { + StepName: "init", + }, + }, + }, + }, + }, + }, + expSteps: []string{"run", "apply", "plan", "init"}, + expOut: "run\napply\nplan\ninit", + }, + } + + for _, c := range cases { + t.Run(strings.Join(c.expSteps, ","), func(t *testing.T) { + RegisterMockTestingT(t) + mockInit := mocks.NewMockStepRunner() + mockPlan := mocks.NewMockStepRunner() + mockApply := mocks.NewMockStepRunner() + mockRun := mocks.NewMockStepRunner() + mockApproved := mocks2.NewMockPullApprovedChecker() + mockWorkingDir := mocks.NewMockWorkingDir() + mockLocker := mocks.NewMockProjectLocker() + mockSender := mocks.NewMockWebhooksSender() + + runner := events.DefaultProjectCommandRunner{ + Locker: mockLocker, + LockURLGenerator: mockURLGenerator{}, + InitStepRunner: mockInit, + PlanStepRunner: mockPlan, + ApplyStepRunner: mockApply, + RunStepRunner: mockRun, + PullApprovedChecker: mockApproved, + WorkingDir: mockWorkingDir, + Webhooks: mockSender, + WorkingDirLocker: events.NewDefaultWorkingDirLocker(), + } + + repoDir := "/tmp/mydir" + When(mockWorkingDir.GetWorkingDir( + matchers.AnyModelsRepo(), + matchers.AnyModelsPullRequest(), + AnyString(), + )).ThenReturn(repoDir, nil) + + ctx := models.ProjectCommandContext{ + Log: logging.NewNoopLogger(), + ProjectConfig: c.projCfg, + Workspace: "default", + GlobalConfig: c.globalCfg, + RepoRelDir: ".", + } + When(mockInit.Run(ctx, nil, repoDir)).ThenReturn("init", nil) + When(mockPlan.Run(ctx, nil, repoDir)).ThenReturn("plan", nil) + When(mockApply.Run(ctx, nil, repoDir)).ThenReturn("apply", nil) + When(mockRun.Run(ctx, nil, repoDir)).ThenReturn("run", nil) + When(mockApproved.PullIsApproved(ctx.BaseRepo, ctx.Pull)).ThenReturn(true, nil) + + res := runner.Apply(ctx) + Equals(t, c.expOut, res.ApplySuccess) + + for _, step := range c.expSteps { + switch step { + case "approved": + mockApproved.VerifyWasCalledOnce().PullIsApproved(ctx.BaseRepo, ctx.Pull) + case "init": + mockInit.VerifyWasCalledOnce().Run(ctx, nil, repoDir) + case "plan": + mockPlan.VerifyWasCalledOnce().Run(ctx, nil, repoDir) + case "apply": + mockApply.VerifyWasCalledOnce().Run(ctx, nil, repoDir) + case "run": + mockRun.VerifyWasCalledOnce().Run(ctx, nil, repoDir) + } + } + }) + } +} + +type mockURLGenerator struct{} + +func (m mockURLGenerator) GenerateLockURL(lockID string) string { + return "https://" + lockID +} diff --git a/server/events/runtime/mocks/matchers/models_pullrequest.go b/server/events/runtime/mocks/matchers/models_pullrequest.go new file mode 100644 index 000000000..d8b146baa --- /dev/null +++ b/server/events/runtime/mocks/matchers/models_pullrequest.go @@ -0,0 +1,20 @@ +package matchers + +import ( + "reflect" + + "github.com/petergtz/pegomock" + models "github.com/runatlantis/atlantis/server/events/models" +) + +func AnyModelsPullRequest() models.PullRequest { + pegomock.RegisterMatcher(pegomock.NewAnyMatcher(reflect.TypeOf((*(models.PullRequest))(nil)).Elem())) + var nullValue models.PullRequest + return nullValue +} + +func EqModelsPullRequest(value models.PullRequest) models.PullRequest { + pegomock.RegisterMatcher(&pegomock.EqMatcher{Value: value}) + var nullValue models.PullRequest + return nullValue +} diff --git a/server/events/runtime/mocks/matchers/models_repo.go b/server/events/runtime/mocks/matchers/models_repo.go new file mode 100644 index 000000000..3f8e699eb --- /dev/null +++ b/server/events/runtime/mocks/matchers/models_repo.go @@ -0,0 +1,20 @@ +package matchers + +import ( + "reflect" + + "github.com/petergtz/pegomock" + models "github.com/runatlantis/atlantis/server/events/models" +) + +func AnyModelsRepo() models.Repo { + pegomock.RegisterMatcher(pegomock.NewAnyMatcher(reflect.TypeOf((*(models.Repo))(nil)).Elem())) + var nullValue models.Repo + return nullValue +} + +func EqModelsRepo(value models.Repo) models.Repo { + pegomock.RegisterMatcher(&pegomock.EqMatcher{Value: value}) + var nullValue models.Repo + return nullValue +} diff --git a/server/events/runtime/mocks/mock_pull_approved_checker.go b/server/events/runtime/mocks/mock_pull_approved_checker.go new file mode 100644 index 000000000..69f1e4a50 --- /dev/null +++ b/server/events/runtime/mocks/mock_pull_approved_checker.go @@ -0,0 +1,84 @@ +// Automatically generated by pegomock. DO NOT EDIT! +// Source: github.com/runatlantis/atlantis/server/events/runtime (interfaces: PullApprovedChecker) + +package mocks + +import ( + "reflect" + + pegomock "github.com/petergtz/pegomock" + models "github.com/runatlantis/atlantis/server/events/models" +) + +type MockPullApprovedChecker struct { + fail func(message string, callerSkip ...int) +} + +func NewMockPullApprovedChecker() *MockPullApprovedChecker { + return &MockPullApprovedChecker{fail: pegomock.GlobalFailHandler} +} + +func (mock *MockPullApprovedChecker) PullIsApproved(baseRepo models.Repo, pull models.PullRequest) (bool, error) { + params := []pegomock.Param{baseRepo, pull} + result := pegomock.GetGenericMockFrom(mock).Invoke("PullIsApproved", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) + var ret0 bool + var ret1 error + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].(bool) + } + if result[1] != nil { + ret1 = result[1].(error) + } + } + return ret0, ret1 +} + +func (mock *MockPullApprovedChecker) VerifyWasCalledOnce() *VerifierPullApprovedChecker { + return &VerifierPullApprovedChecker{mock, pegomock.Times(1), nil} +} + +func (mock *MockPullApprovedChecker) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierPullApprovedChecker { + return &VerifierPullApprovedChecker{mock, invocationCountMatcher, nil} +} + +func (mock *MockPullApprovedChecker) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierPullApprovedChecker { + return &VerifierPullApprovedChecker{mock, invocationCountMatcher, inOrderContext} +} + +type VerifierPullApprovedChecker struct { + mock *MockPullApprovedChecker + invocationCountMatcher pegomock.Matcher + inOrderContext *pegomock.InOrderContext +} + +func (verifier *VerifierPullApprovedChecker) PullIsApproved(baseRepo models.Repo, pull models.PullRequest) *PullApprovedChecker_PullIsApproved_OngoingVerification { + params := []pegomock.Param{baseRepo, pull} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "PullIsApproved", params) + return &PullApprovedChecker_PullIsApproved_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type PullApprovedChecker_PullIsApproved_OngoingVerification struct { + mock *MockPullApprovedChecker + methodInvocations []pegomock.MethodInvocation +} + +func (c *PullApprovedChecker_PullIsApproved_OngoingVerification) GetCapturedArguments() (models.Repo, models.PullRequest) { + baseRepo, pull := c.GetAllCapturedArguments() + return baseRepo[len(baseRepo)-1], pull[len(pull)-1] +} + +func (c *PullApprovedChecker_PullIsApproved_OngoingVerification) GetAllCapturedArguments() (_param0 []models.Repo, _param1 []models.PullRequest) { + params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) + if len(params) > 0 { + _param0 = make([]models.Repo, len(params[0])) + for u, param := range params[0] { + _param0[u] = param.(models.Repo) + } + _param1 = make([]models.PullRequest, len(params[1])) + for u, param := range params[1] { + _param1[u] = param.(models.PullRequest) + } + } + return +} diff --git a/server/events/runtime/pull_approved_checker.go b/server/events/runtime/pull_approved_checker.go index 86945fce3..e77aa2acb 100644 --- a/server/events/runtime/pull_approved_checker.go +++ b/server/events/runtime/pull_approved_checker.go @@ -4,6 +4,8 @@ import ( "github.com/runatlantis/atlantis/server/events/models" ) +//go:generate pegomock generate -m --use-experimental-model-gen --package mocks -o mocks/mock_pull_approved_checker.go PullApprovedChecker + type PullApprovedChecker interface { PullIsApproved(baseRepo models.Repo, pull models.PullRequest) (bool, error) } diff --git a/server/events/working_dir_locker.go b/server/events/working_dir_locker.go index 8c45891c7..6c5559b41 100644 --- a/server/events/working_dir_locker.go +++ b/server/events/working_dir_locker.go @@ -36,20 +36,20 @@ type WorkingDirLocker interface { Unlock(repoFullName, workspace string, pullNum int) } -// DefaultAtlantisWorkingDirLocker implements WorkingDirLocker. -type DefaultAtlantisWorkingDirLocker struct { +// DefaultWorkingDirLocker implements WorkingDirLocker. +type DefaultWorkingDirLocker struct { mutex sync.Mutex locks map[string]interface{} } -// NewDefaultAtlantisWorkingDirLocker is a constructor. -func NewDefaultAtlantisWorkingDirLocker() *DefaultAtlantisWorkingDirLocker { - return &DefaultAtlantisWorkingDirLocker{ +// NewDefaultWorkingDirLocker is a constructor. +func NewDefaultWorkingDirLocker() *DefaultWorkingDirLocker { + return &DefaultWorkingDirLocker{ locks: make(map[string]interface{}), } } -func (d *DefaultAtlantisWorkingDirLocker) TryLock(repoFullName string, workspace string, pullNum int) (func(), error) { +func (d *DefaultWorkingDirLocker) TryLock(repoFullName string, workspace string, pullNum int) (func(), error) { d.mutex.Lock() defer d.mutex.Unlock() @@ -67,12 +67,12 @@ func (d *DefaultAtlantisWorkingDirLocker) TryLock(repoFullName string, workspace } // Unlock unlocks the repo, pull and workspace. -func (d *DefaultAtlantisWorkingDirLocker) Unlock(repoFullName, workspace string, pullNum int) { +func (d *DefaultWorkingDirLocker) Unlock(repoFullName, workspace string, pullNum int) { d.mutex.Lock() defer d.mutex.Unlock() delete(d.locks, d.key(repoFullName, workspace, pullNum)) } -func (d *DefaultAtlantisWorkingDirLocker) key(repo string, workspace string, pull int) string { +func (d *DefaultWorkingDirLocker) key(repo string, workspace string, pull int) string { return fmt.Sprintf("%s/%s/%d", repo, workspace, pull) } diff --git a/server/events/working_dir_locker_test.go b/server/events/working_dir_locker_test.go index 1083ba422..52274af3a 100644 --- a/server/events/working_dir_locker_test.go +++ b/server/events/working_dir_locker_test.go @@ -24,7 +24,7 @@ var repo = "repo/owner" var workspace = "default" func TestTryLock(t *testing.T) { - locker := events.NewDefaultAtlantisWorkingDirLocker() + locker := events.NewDefaultWorkingDirLocker() // The first lock should succeed. unlockFn, err := locker.TryLock(repo, workspace, 1) @@ -43,7 +43,7 @@ func TestTryLock(t *testing.T) { } func TestTryLockDifferentWorkspaces(t *testing.T) { - locker := events.NewDefaultAtlantisWorkingDirLocker() + locker := events.NewDefaultWorkingDirLocker() t.Log("a lock for the same repo and pull but different workspace should succeed") _, err := locker.TryLock(repo, workspace, 1) @@ -59,7 +59,7 @@ func TestTryLockDifferentWorkspaces(t *testing.T) { } func TestTryLockDifferentRepo(t *testing.T) { - locker := events.NewDefaultAtlantisWorkingDirLocker() + locker := events.NewDefaultWorkingDirLocker() t.Log("a lock for a different repo but the same workspace and pull should succeed") _, err := locker.TryLock(repo, workspace, 1) @@ -76,7 +76,7 @@ func TestTryLockDifferentRepo(t *testing.T) { } func TestTryLockDifferentPulls(t *testing.T) { - locker := events.NewDefaultAtlantisWorkingDirLocker() + locker := events.NewDefaultWorkingDirLocker() t.Log("a lock for a different pull but the same repo and workspace should succeed") _, err := locker.TryLock(repo, workspace, 1) @@ -93,7 +93,7 @@ func TestTryLockDifferentPulls(t *testing.T) { } func TestUnlock(t *testing.T) { - locker := events.NewDefaultAtlantisWorkingDirLocker() + locker := events.NewDefaultWorkingDirLocker() t.Log("unlocking should work") unlockFn, err := locker.TryLock(repo, workspace, 1) @@ -104,7 +104,7 @@ func TestUnlock(t *testing.T) { } func TestUnlockDifferentWorkspaces(t *testing.T) { - locker := events.NewDefaultAtlantisWorkingDirLocker() + locker := events.NewDefaultWorkingDirLocker() t.Log("unlocking should work for different workspaces") unlockFn1, err1 := locker.TryLock(repo, workspace, 1) Ok(t, err1) @@ -120,7 +120,7 @@ func TestUnlockDifferentWorkspaces(t *testing.T) { } func TestUnlockDifferentRepos(t *testing.T) { - locker := events.NewDefaultAtlantisWorkingDirLocker() + locker := events.NewDefaultWorkingDirLocker() t.Log("unlocking should work for different repos") unlockFn1, err1 := locker.TryLock(repo, workspace, 1) Ok(t, err1) @@ -137,7 +137,7 @@ func TestUnlockDifferentRepos(t *testing.T) { } func TestUnlockDifferentPulls(t *testing.T) { - locker := events.NewDefaultAtlantisWorkingDirLocker() + locker := events.NewDefaultWorkingDirLocker() t.Log("unlocking should work for different pulls") unlockFn1, err1 := locker.TryLock(repo, workspace, 1) Ok(t, err1) diff --git a/server/events_controller_e2e_test.go b/server/events_controller_e2e_test.go index b50215b3e..d69471cb2 100644 --- a/server/events_controller_e2e_test.go +++ b/server/events_controller_e2e_test.go @@ -236,23 +236,23 @@ func setupE2E(t *testing.T) (server.EventsController, *vcsmocks.MockClientProxy, } defaultTFVersion := terraformClient.Version() - locker := events.NewDefaultAtlantisWorkingDirLocker() + locker := events.NewDefaultWorkingDirLocker() commandRunner := &events.DefaultCommandRunner{ ProjectCommandRunner: &events.DefaultProjectCommandRunner{ Locker: projectLocker, LockURLGenerator: &mockLockURLGenerator{}, - InitStepRunner: runtime.InitStepRunner{ + InitStepRunner: &runtime.InitStepRunner{ TerraformExecutor: terraformClient, DefaultTFVersion: defaultTFVersion, }, - PlanStepRunner: runtime.PlanStepRunner{ + PlanStepRunner: &runtime.PlanStepRunner{ TerraformExecutor: terraformClient, DefaultTFVersion: defaultTFVersion, }, - ApplyStepRunner: runtime.ApplyStepRunner{ + ApplyStepRunner: &runtime.ApplyStepRunner{ TerraformExecutor: terraformClient, }, - RunStepRunner: runtime.RunStepRunner{ + RunStepRunner: &runtime.RunStepRunner{ DefaultTFVersion: defaultTFVersion, }, PullApprovedChecker: e2eVCSClient, @@ -277,7 +277,6 @@ func setupE2E(t *testing.T) (server.EventsController, *vcsmocks.MockClientProxy, WorkingDirLocker: locker, AllowRepoConfigFlag: "allow-repo-config", AllowRepoConfig: true, - RequireApproval: false, }, } diff --git a/server/server.go b/server/server.go index 0d7c59593..7adafb8a9 100644 --- a/server/server.go +++ b/server/server.go @@ -190,7 +190,7 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) { return nil, err } lockingClient := locking.NewClient(boltdb) - workingDirLocker := events.NewDefaultAtlantisWorkingDirLocker() + workingDirLocker := events.NewDefaultWorkingDirLocker() workingDir := &events.FileWorkspace{ DataDir: userConfig.DataDir, } @@ -239,31 +239,31 @@ func NewServer(userConfig UserConfig, config Config) (*Server, error) { VCSClient: vcsClient, WorkingDir: workingDir, WorkingDirLocker: workingDirLocker, - RequireApproval: userConfig.RequireApproval, AllowRepoConfig: userConfig.AllowRepoConfig, AllowRepoConfigFlag: config.AllowRepoConfigFlag, }, ProjectCommandRunner: &events.DefaultProjectCommandRunner{ Locker: projectLocker, LockURLGenerator: router, - InitStepRunner: runtime.InitStepRunner{ + InitStepRunner: &runtime.InitStepRunner{ TerraformExecutor: terraformClient, DefaultTFVersion: defaultTfVersion, }, - PlanStepRunner: runtime.PlanStepRunner{ + PlanStepRunner: &runtime.PlanStepRunner{ TerraformExecutor: terraformClient, DefaultTFVersion: defaultTfVersion, }, - ApplyStepRunner: runtime.ApplyStepRunner{ + ApplyStepRunner: &runtime.ApplyStepRunner{ TerraformExecutor: terraformClient, }, - RunStepRunner: runtime.RunStepRunner{ + RunStepRunner: &runtime.RunStepRunner{ DefaultTFVersion: defaultTfVersion, }, - PullApprovedChecker: vcsClient, - WorkingDir: workingDir, - Webhooks: webhooksManager, - WorkingDirLocker: workingDirLocker, + PullApprovedChecker: vcsClient, + WorkingDir: workingDir, + Webhooks: webhooksManager, + WorkingDirLocker: workingDirLocker, + RequireApprovalOverride: userConfig.RequireApproval, }, } repoWhitelist := &events.RepoWhitelistChecker{