From fbb898391ada9dd3eabb7968d897b7188007f349 Mon Sep 17 00:00:00 2001 From: X-Guardian Date: Mon, 28 Aug 2023 11:07:32 +0100 Subject: [PATCH 1/4] Add Workflow hook target filter --- runatlantis.io/docs/post-workflow-hooks.md | 21 +++ runatlantis.io/docs/pre-workflow-hooks.md | 21 +++ server/core/config/raw/workflow_step.go | 1 + .../post_workflow_hooks_command_runner.go | 11 ++ ...post_workflow_hooks_command_runner_test.go | 139 +++++++++++++++-- .../pre_workflow_hooks_command_runner.go | 10 ++ .../pre_workflow_hooks_command_runner_test.go | 140 ++++++++++++++++-- 7 files changed, 321 insertions(+), 22 deletions(-) diff --git a/runatlantis.io/docs/post-workflow-hooks.md b/runatlantis.io/docs/post-workflow-hooks.md index f980ac112a..f7563ade62 100644 --- a/runatlantis.io/docs/post-workflow-hooks.md +++ b/runatlantis.io/docs/post-workflow-hooks.md @@ -13,6 +13,27 @@ back to the PR as a comment. Post workflow hooks can only be specified in the Server-Side Repo Config under the `repos` key. +## Atlantis Command Targetting + +By default, the workflow hook will run when any command is processed by Atlantis. +This can be modified by specifying the `commands` key in the workflow hook containing a comma delimited list +of Atlantis commands that the hook should be run for. Detail of the Atlantis commands +can be found in [Using Atlantis](using-atlantis.md). + +### Example + +```yaml +repos: + - id: /.*/ + pre_workflow_hooks: + - run: ./plan-hook.sh + description: Plan Hook + commands: plan + - run: ./plan-apply-hook.sh + description: Plan & Apply Hook + commands: plan, apply +``` + ## Use Cases ### Cost estimation reporting diff --git a/runatlantis.io/docs/pre-workflow-hooks.md b/runatlantis.io/docs/pre-workflow-hooks.md index b4eb852bf3..a38ffc0b5c 100644 --- a/runatlantis.io/docs/pre-workflow-hooks.md +++ b/runatlantis.io/docs/pre-workflow-hooks.md @@ -20,6 +20,27 @@ Pre workflow hooks can only be specified in the Server-Side Repo Config under th workflows(`plan`, `apply`) even if a `run` command exits with an error. ::: +## Atlantis Command Targetting + +By default, the workflow hook will run when any command is processed by Atlantis. +This can be modified by specifying the `commands` key in the workflow hook containing a comma delimited list +of Atlantis commands that the hook should be run for. Detail of the Atlantis commands +can be found in [Using Atlantis](using-atlantis.md). + +### Example + +```yaml +repos: + - id: /.*/ + pre_workflow_hooks: + - run: ./plan-hook.sh + description: Plan Hook + commands: plan + - run: ./plan-apply-hook.sh + description: Plan & Apply Hook + commands: plan, apply +``` + ## Use Cases ### Dynamic Repo Config Generation diff --git a/server/core/config/raw/workflow_step.go b/server/core/config/raw/workflow_step.go index 16a4268b05..3a6411136e 100644 --- a/server/core/config/raw/workflow_step.go +++ b/server/core/config/raw/workflow_step.go @@ -78,6 +78,7 @@ func (s WorkflowHook) ToValid() *valid.WorkflowHook { StepDescription: s.StringVal["description"], Shell: s.StringVal["shell"], ShellArgs: s.StringVal["shellArgs"], + Commands: s.StringVal["commands"], } } diff --git a/server/events/post_workflow_hooks_command_runner.go b/server/events/post_workflow_hooks_command_runner.go index 6d8f7f5b4b..77fe09291d 100644 --- a/server/events/post_workflow_hooks_command_runner.go +++ b/server/events/post_workflow_hooks_command_runner.go @@ -2,6 +2,7 @@ package events import ( "fmt" + "strings" "github.com/google/uuid" "github.com/runatlantis/atlantis/server/core/config/valid" @@ -108,6 +109,16 @@ func (w *DefaultPostWorkflowHooksCommandRunner) runHooks( hookDescription = fmt.Sprintf("Post workflow hook #%d", i) } + ctx.Log.Debug("Processing post workflow hook '%s', Command '%s', Target commands [%s]", + hookDescription, ctx.CommandName, hook.Commands) + if hook.Commands != "" && !strings.Contains(hook.Commands, ctx.CommandName) { + ctx.Log.Debug("Skipping post workflow hook '%s' as command '%s' is not in Commands [%s]", + hookDescription, ctx.CommandName, hook.Commands) + continue + } else { + ctx.Log.Debug("Running post workflow hook: '%s'", hookDescription) + } + ctx.HookID = uuid.NewString() shell := hook.Shell if shell == "" { diff --git a/server/events/post_workflow_hooks_command_runner_test.go b/server/events/post_workflow_hooks_command_runner_test.go index 3a6a2a3e86..38cd5ee9ec 100644 --- a/server/events/post_workflow_hooks_command_runner_test.go +++ b/server/events/post_workflow_hooks_command_runner_test.go @@ -84,6 +84,18 @@ func TestRunPostHooks_Clone(t *testing.T) { ShellArgs: "-ce", } + testHookWithPlanCommand := valid.WorkflowHook{ + StepName: "test4", + RunCommand: "echo test4", + Commands: "plan", + } + + testHookWithPlanApplyCommands := valid.WorkflowHook{ + StepName: "test5", + RunCommand: "echo test5", + Commands: "plan, apply", + } + repoDir := "path/to/repo" result := "some result" runtimeDesc := "" @@ -99,10 +111,14 @@ func TestRunPostHooks_Clone(t *testing.T) { CommandName: "plan", } - cmd := &events.CommentCommand{ + planCmd := &events.CommentCommand{ Name: command.Plan, } + applyCmd := &events.CommentCommand{ + Name: command.Apply, + } + t.Run("success hooks in cfg", func(t *testing.T) { postWorkflowHooksSetup(t) @@ -129,7 +145,7 @@ func TestRunPostHooks_Clone(t *testing.T) { When(whPostWorkflowHookRunner.Run(Any[models.WorkflowHookCommandContext](), Eq(testHook.RunCommand), Any[string](), Any[string](), Eq(repoDir))).ThenReturn(result, runtimeDesc, nil) - err := postWh.RunPostHooks(ctx, cmd) + err := postWh.RunPostHooks(ctx, planCmd) Ok(t, err) whPostWorkflowHookRunner.VerifyWasCalledOnce().Run(Any[models.WorkflowHookCommandContext](), @@ -157,7 +173,7 @@ func TestRunPostHooks_Clone(t *testing.T) { postWh.GlobalCfg = globalCfg - err := postWh.RunPostHooks(ctx, cmd) + err := postWh.RunPostHooks(ctx, planCmd) Ok(t, err) @@ -184,7 +200,7 @@ func TestRunPostHooks_Clone(t *testing.T) { When(postWhWorkingDirLocker.TryLock(testdata.GithubRepo.FullName, newPull.Num, events.DefaultWorkspace, events.DefaultRepoRelDir)).ThenReturn(func() {}, errors.New("some error")) - err := postWh.RunPostHooks(ctx, cmd) + err := postWh.RunPostHooks(ctx, planCmd) Assert(t, err != nil, "error not nil") postWhWorkingDir.VerifyWasCalled(Never()).Clone(testdata.GithubRepo, newPull, events.DefaultWorkspace) @@ -216,7 +232,7 @@ func TestRunPostHooks_Clone(t *testing.T) { When(postWhWorkingDirLocker.TryLock(testdata.GithubRepo.FullName, newPull.Num, events.DefaultWorkspace, events.DefaultRepoRelDir)).ThenReturn(unlockFn, nil) When(postWhWorkingDir.Clone(testdata.GithubRepo, newPull, events.DefaultWorkspace)).ThenReturn(repoDir, false, errors.New("some error")) - err := postWh.RunPostHooks(ctx, cmd) + err := postWh.RunPostHooks(ctx, planCmd) Assert(t, err != nil, "error not nil") @@ -251,7 +267,7 @@ func TestRunPostHooks_Clone(t *testing.T) { When(whPostWorkflowHookRunner.Run(Any[models.WorkflowHookCommandContext](), Eq(testHook.RunCommand), Any[string](), Any[string](), Eq(repoDir))).ThenReturn(result, runtimeDesc, errors.New("some error")) - err := postWh.RunPostHooks(ctx, cmd) + err := postWh.RunPostHooks(ctx, planCmd) Assert(t, err != nil, "error not nil") Assert(t, *unlockCalled == true, "unlock function called") @@ -276,7 +292,7 @@ func TestRunPostHooks_Clone(t *testing.T) { }, } - cmd := &events.CommentCommand{ + planCmd := &events.CommentCommand{ Name: command.Plan, Flags: []string{"comment", "args"}, } @@ -291,7 +307,7 @@ func TestRunPostHooks_Clone(t *testing.T) { When(whPostWorkflowHookRunner.Run(Any[models.WorkflowHookCommandContext](), Eq(testHook.RunCommand), Any[string](), Any[string](), Eq(repoDir))).ThenReturn(result, runtimeDesc, nil) - err := postWh.RunPostHooks(ctx, cmd) + err := postWh.RunPostHooks(ctx, planCmd) Ok(t, err) whPostWorkflowHookRunner.VerifyWasCalledOnce().Run(Any[models.WorkflowHookCommandContext](), @@ -325,7 +341,7 @@ func TestRunPostHooks_Clone(t *testing.T) { When(whPostWorkflowHookRunner.Run(Any[models.WorkflowHookCommandContext](), Eq(testHookWithShell.RunCommand), Any[string](), Any[string](), Eq(repoDir))).ThenReturn(result, runtimeDesc, nil) - err := postWh.RunPostHooks(ctx, cmd) + err := postWh.RunPostHooks(ctx, planCmd) Ok(t, err) whPostWorkflowHookRunner.VerifyWasCalledOnce().Run(Any[models.WorkflowHookCommandContext](), @@ -359,7 +375,7 @@ func TestRunPostHooks_Clone(t *testing.T) { When(whPostWorkflowHookRunner.Run(Any[models.WorkflowHookCommandContext](), Eq(testHook.RunCommand), Any[string](), Any[string](), Eq(repoDir))).ThenReturn(result, runtimeDesc, nil) - err := postWh.RunPostHooks(ctx, cmd) + err := postWh.RunPostHooks(ctx, planCmd) Ok(t, err) whPostWorkflowHookRunner.VerifyWasCalledOnce().Run(Any[models.WorkflowHookCommandContext](), @@ -393,7 +409,7 @@ func TestRunPostHooks_Clone(t *testing.T) { When(whPostWorkflowHookRunner.Run(Any[models.WorkflowHookCommandContext](), Eq(testHookWithShellandShellArgs.RunCommand), Any[string](), Any[string](), Eq(repoDir))).ThenReturn(result, runtimeDesc, nil) - err := postWh.RunPostHooks(ctx, cmd) + err := postWh.RunPostHooks(ctx, planCmd) Ok(t, err) whPostWorkflowHookRunner.VerifyWasCalledOnce().Run(Any[models.WorkflowHookCommandContext](), @@ -401,4 +417,105 @@ func TestRunPostHooks_Clone(t *testing.T) { Assert(t, *unlockCalled == true, "unlock function called") }) + t.Run("Commands 'plan' set on webhook and plan command", func(t *testing.T) { + preWorkflowHooksSetup(t) + + var unlockCalled = newBool(false) + unlockFn := func() { + unlockCalled = newBool(true) + } + + globalCfg := valid.GlobalCfg{ + Repos: []valid.Repo{ + { + ID: testdata.GithubRepo.ID(), + PreWorkflowHooks: []*valid.WorkflowHook{ + &testHookWithPlanCommand, + }, + }, + }, + } + + preWh.GlobalCfg = globalCfg + + When(preWhWorkingDirLocker.TryLock(testdata.GithubRepo.FullName, newPull.Num, events.DefaultWorkspace, events.DefaultRepoRelDir)).ThenReturn(unlockFn, nil) + When(preWhWorkingDir.Clone(testdata.GithubRepo, newPull, events.DefaultWorkspace)).ThenReturn(repoDir, false, nil) + When(whPreWorkflowHookRunner.Run(Any[models.WorkflowHookCommandContext](), + Eq(testHookWithPlanCommand.RunCommand), Any[string](), Any[string](), Eq(repoDir))).ThenReturn(result, runtimeDesc, nil) + + err := preWh.RunPreHooks(ctx, planCmd) + + Ok(t, err) + whPreWorkflowHookRunner.VerifyWasCalledOnce().Run(Any[models.WorkflowHookCommandContext](), + Eq(testHookWithPlanCommand.RunCommand), Any[string](), Any[string](), Eq(repoDir)) + Assert(t, *unlockCalled == true, "unlock function called") + }) + + t.Run("Commands 'plan' set on webhook and non-plan command", func(t *testing.T) { + preWorkflowHooksSetup(t) + + var unlockCalled = newBool(false) + unlockFn := func() { + unlockCalled = newBool(true) + } + + globalCfg := valid.GlobalCfg{ + Repos: []valid.Repo{ + { + ID: testdata.GithubRepo.ID(), + PreWorkflowHooks: []*valid.WorkflowHook{ + &testHookWithPlanCommand, + }, + }, + }, + } + + preWh.GlobalCfg = globalCfg + + When(preWhWorkingDirLocker.TryLock(testdata.GithubRepo.FullName, newPull.Num, events.DefaultWorkspace, events.DefaultRepoRelDir)).ThenReturn(unlockFn, nil) + When(preWhWorkingDir.Clone(testdata.GithubRepo, newPull, events.DefaultWorkspace)).ThenReturn(repoDir, false, nil) + When(whPreWorkflowHookRunner.Run(Any[models.WorkflowHookCommandContext](), + Eq(testHookWithPlanCommand.RunCommand), Any[string](), Any[string](), Eq(repoDir))).ThenReturn(result, runtimeDesc, nil) + + err := preWh.RunPreHooks(ctx, applyCmd) + + Ok(t, err) + whPreWorkflowHookRunner.VerifyWasCalled(Never()).Run(Any[models.WorkflowHookCommandContext](), + Eq(testHookWithPlanCommand.RunCommand), Any[string](), Any[string](), Eq(repoDir)) + Assert(t, *unlockCalled == true, "unlock function called") + }) + + t.Run("Commands 'plan, apply' set on webhook and plan command", func(t *testing.T) { + preWorkflowHooksSetup(t) + + var unlockCalled = newBool(false) + unlockFn := func() { + unlockCalled = newBool(true) + } + + globalCfg := valid.GlobalCfg{ + Repos: []valid.Repo{ + { + ID: testdata.GithubRepo.ID(), + PreWorkflowHooks: []*valid.WorkflowHook{ + &testHookWithPlanApplyCommands, + }, + }, + }, + } + + preWh.GlobalCfg = globalCfg + + When(preWhWorkingDirLocker.TryLock(testdata.GithubRepo.FullName, newPull.Num, events.DefaultWorkspace, events.DefaultRepoRelDir)).ThenReturn(unlockFn, nil) + When(preWhWorkingDir.Clone(testdata.GithubRepo, newPull, events.DefaultWorkspace)).ThenReturn(repoDir, false, nil) + When(whPreWorkflowHookRunner.Run(Any[models.WorkflowHookCommandContext](), + Eq(testHookWithPlanApplyCommands.RunCommand), Any[string](), Any[string](), Eq(repoDir))).ThenReturn(result, runtimeDesc, nil) + + err := preWh.RunPreHooks(ctx, planCmd) + + Ok(t, err) + whPreWorkflowHookRunner.VerifyWasCalledOnce().Run(Any[models.WorkflowHookCommandContext](), + Eq(testHookWithPlanApplyCommands.RunCommand), Any[string](), Any[string](), Eq(repoDir)) + Assert(t, *unlockCalled == true, "unlock function called") + }) } diff --git a/server/events/pre_workflow_hooks_command_runner.go b/server/events/pre_workflow_hooks_command_runner.go index 5777f2a429..3b1737de6a 100644 --- a/server/events/pre_workflow_hooks_command_runner.go +++ b/server/events/pre_workflow_hooks_command_runner.go @@ -2,6 +2,7 @@ package events import ( "fmt" + "strings" "github.com/google/uuid" "github.com/runatlantis/atlantis/server/core/config/valid" @@ -105,6 +106,15 @@ func (w *DefaultPreWorkflowHooksCommandRunner) runHooks( hookDescription = fmt.Sprintf("Pre workflow hook #%d", i) } + ctx.Log.Debug("Processing pre workflow hook '%s', Command '%s', Target commands [%s]", + hookDescription, ctx.CommandName, hook.Commands) + if hook.Commands != "" && !strings.Contains(hook.Commands, ctx.CommandName) { + ctx.Log.Debug("Skipping pre workflow hook '%s' as command '%s' is not in Commands [%s]", + hookDescription, ctx.CommandName, hook.Commands) + continue + } else { + ctx.Log.Debug("Running pre workflow hook: '%s'", hookDescription) + } ctx.HookID = uuid.NewString() shell := hook.Shell if shell == "" { diff --git a/server/events/pre_workflow_hooks_command_runner_test.go b/server/events/pre_workflow_hooks_command_runner_test.go index 540c22a816..3156797f86 100644 --- a/server/events/pre_workflow_hooks_command_runner_test.go +++ b/server/events/pre_workflow_hooks_command_runner_test.go @@ -87,6 +87,18 @@ func TestRunPreHooks_Clone(t *testing.T) { ShellArgs: "-ce", } + testHookWithPlanCommand := valid.WorkflowHook{ + StepName: "test4", + RunCommand: "echo test4", + Commands: "plan", + } + + testHookWithPlanApplyCommands := valid.WorkflowHook{ + StepName: "test5", + RunCommand: "echo test5", + Commands: "plan, apply", + } + repoDir := "path/to/repo" result := "some result" runtimeDesc := "" @@ -101,10 +113,14 @@ func TestRunPreHooks_Clone(t *testing.T) { CommandName: "plan", } - cmd := &events.CommentCommand{ + planCmd := &events.CommentCommand{ Name: command.Plan, } + applyCmd := &events.CommentCommand{ + Name: command.Apply, + } + t.Run("success hooks in cfg", func(t *testing.T) { preWorkflowHooksSetup(t) @@ -131,7 +147,7 @@ func TestRunPreHooks_Clone(t *testing.T) { When(whPreWorkflowHookRunner.Run(Any[models.WorkflowHookCommandContext](), Eq(testHook.RunCommand), Any[string](), Any[string](), Eq(repoDir))).ThenReturn(result, runtimeDesc, nil) - err := preWh.RunPreHooks(ctx, cmd) + err := preWh.RunPreHooks(ctx, planCmd) Ok(t, err) whPreWorkflowHookRunner.VerifyWasCalledOnce().Run(Any[models.WorkflowHookCommandContext](), @@ -160,7 +176,7 @@ func TestRunPreHooks_Clone(t *testing.T) { preWh.GlobalCfg = globalCfg - err := preWh.RunPreHooks(ctx, cmd) + err := preWh.RunPreHooks(ctx, planCmd) Ok(t, err) @@ -187,7 +203,7 @@ func TestRunPreHooks_Clone(t *testing.T) { When(preWhWorkingDirLocker.TryLock(testdata.GithubRepo.FullName, newPull.Num, events.DefaultWorkspace, events.DefaultRepoRelDir)).ThenReturn(func() {}, errors.New("some error")) - err := preWh.RunPreHooks(ctx, cmd) + err := preWh.RunPreHooks(ctx, planCmd) Assert(t, err != nil, "error not nil") preWhWorkingDir.VerifyWasCalled(Never()).Clone(testdata.GithubRepo, newPull, events.DefaultWorkspace) @@ -218,7 +234,7 @@ func TestRunPreHooks_Clone(t *testing.T) { When(preWhWorkingDirLocker.TryLock(testdata.GithubRepo.FullName, newPull.Num, events.DefaultWorkspace, events.DefaultRepoRelDir)).ThenReturn(unlockFn, nil) When(preWhWorkingDir.Clone(testdata.GithubRepo, newPull, events.DefaultWorkspace)).ThenReturn(repoDir, false, errors.New("some error")) - err := preWh.RunPreHooks(ctx, cmd) + err := preWh.RunPreHooks(ctx, planCmd) Assert(t, err != nil, "error not nil") @@ -252,7 +268,7 @@ func TestRunPreHooks_Clone(t *testing.T) { When(whPreWorkflowHookRunner.Run(Any[models.WorkflowHookCommandContext](), Eq(testHook.RunCommand), Any[string](), Any[string](), Eq(repoDir))).ThenReturn(result, runtimeDesc, errors.New("some error")) - err := preWh.RunPreHooks(ctx, cmd) + err := preWh.RunPreHooks(ctx, planCmd) Assert(t, err != nil, "error not nil") Assert(t, *unlockCalled == true, "unlock function called") @@ -277,7 +293,7 @@ func TestRunPreHooks_Clone(t *testing.T) { }, } - cmd := &events.CommentCommand{ + planCmd := &events.CommentCommand{ Name: command.Plan, Flags: []string{"comment", "args"}, } @@ -291,7 +307,7 @@ func TestRunPreHooks_Clone(t *testing.T) { When(preWhWorkingDir.Clone(testdata.GithubRepo, newPull, events.DefaultWorkspace)).ThenReturn(repoDir, false, nil) When(whPreWorkflowHookRunner.Run(Any[models.WorkflowHookCommandContext](), Eq(testHook.RunCommand), Any[string](), Any[string](), Eq(repoDir))).ThenReturn(result, runtimeDesc, nil) - err := preWh.RunPreHooks(ctx, cmd) + err := preWh.RunPreHooks(ctx, planCmd) Ok(t, err) whPreWorkflowHookRunner.VerifyWasCalledOnce().Run(Any[models.WorkflowHookCommandContext](), Eq(testHook.RunCommand), Eq(defaultShell), Eq(defaultShellArgs), Eq(repoDir)) @@ -324,7 +340,7 @@ func TestRunPreHooks_Clone(t *testing.T) { When(whPreWorkflowHookRunner.Run(Any[models.WorkflowHookCommandContext](), Eq(testHookWithShell.RunCommand), Any[string](), Any[string](), Eq(repoDir))).ThenReturn(result, runtimeDesc, nil) - err := preWh.RunPreHooks(ctx, cmd) + err := preWh.RunPreHooks(ctx, planCmd) Ok(t, err) whPreWorkflowHookRunner.VerifyWasCalledOnce().Run(Any[models.WorkflowHookCommandContext](), @@ -358,7 +374,7 @@ func TestRunPreHooks_Clone(t *testing.T) { When(whPreWorkflowHookRunner.Run(Any[models.WorkflowHookCommandContext](), Eq(testHook.RunCommand), Any[string](), Any[string](), Eq(repoDir))).ThenReturn(result, runtimeDesc, nil) - err := preWh.RunPreHooks(ctx, cmd) + err := preWh.RunPreHooks(ctx, planCmd) Ok(t, err) whPreWorkflowHookRunner.VerifyWasCalledOnce().Run(Any[models.WorkflowHookCommandContext](), @@ -392,7 +408,7 @@ func TestRunPreHooks_Clone(t *testing.T) { When(whPreWorkflowHookRunner.Run(Any[models.WorkflowHookCommandContext](), Eq(testHookWithShellandShellArgs.RunCommand), Any[string](), Any[string](), Eq(repoDir))).ThenReturn(result, runtimeDesc, nil) - err := preWh.RunPreHooks(ctx, cmd) + err := preWh.RunPreHooks(ctx, planCmd) Ok(t, err) whPreWorkflowHookRunner.VerifyWasCalledOnce().Run(Any[models.WorkflowHookCommandContext](), @@ -400,4 +416,106 @@ func TestRunPreHooks_Clone(t *testing.T) { Eq(testHookWithShellandShellArgs.ShellArgs), Eq(repoDir)) Assert(t, *unlockCalled == true, "unlock function called") }) + + t.Run("Commands 'plan' set on webhook and plan command", func(t *testing.T) { + preWorkflowHooksSetup(t) + + var unlockCalled = newBool(false) + unlockFn := func() { + unlockCalled = newBool(true) + } + + globalCfg := valid.GlobalCfg{ + Repos: []valid.Repo{ + { + ID: testdata.GithubRepo.ID(), + PreWorkflowHooks: []*valid.WorkflowHook{ + &testHookWithPlanCommand, + }, + }, + }, + } + + preWh.GlobalCfg = globalCfg + + When(preWhWorkingDirLocker.TryLock(testdata.GithubRepo.FullName, newPull.Num, events.DefaultWorkspace, events.DefaultRepoRelDir)).ThenReturn(unlockFn, nil) + When(preWhWorkingDir.Clone(testdata.GithubRepo, newPull, events.DefaultWorkspace)).ThenReturn(repoDir, false, nil) + When(whPreWorkflowHookRunner.Run(Any[models.WorkflowHookCommandContext](), + Eq(testHookWithPlanCommand.RunCommand), Any[string](), Any[string](), Eq(repoDir))).ThenReturn(result, runtimeDesc, nil) + + err := preWh.RunPreHooks(ctx, planCmd) + + Ok(t, err) + whPreWorkflowHookRunner.VerifyWasCalledOnce().Run(Any[models.WorkflowHookCommandContext](), + Eq(testHookWithPlanCommand.RunCommand), Any[string](), Any[string](), Eq(repoDir)) + Assert(t, *unlockCalled == true, "unlock function called") + }) + + t.Run("Commands 'plan' set on webhook and non-plan command", func(t *testing.T) { + preWorkflowHooksSetup(t) + + var unlockCalled = newBool(false) + unlockFn := func() { + unlockCalled = newBool(true) + } + + globalCfg := valid.GlobalCfg{ + Repos: []valid.Repo{ + { + ID: testdata.GithubRepo.ID(), + PreWorkflowHooks: []*valid.WorkflowHook{ + &testHookWithPlanCommand, + }, + }, + }, + } + + preWh.GlobalCfg = globalCfg + + When(preWhWorkingDirLocker.TryLock(testdata.GithubRepo.FullName, newPull.Num, events.DefaultWorkspace, events.DefaultRepoRelDir)).ThenReturn(unlockFn, nil) + When(preWhWorkingDir.Clone(testdata.GithubRepo, newPull, events.DefaultWorkspace)).ThenReturn(repoDir, false, nil) + When(whPreWorkflowHookRunner.Run(Any[models.WorkflowHookCommandContext](), + Eq(testHookWithPlanCommand.RunCommand), Any[string](), Any[string](), Eq(repoDir))).ThenReturn(result, runtimeDesc, nil) + + err := preWh.RunPreHooks(ctx, applyCmd) + + Ok(t, err) + whPreWorkflowHookRunner.VerifyWasCalled(Never()).Run(Any[models.WorkflowHookCommandContext](), + Eq(testHookWithPlanCommand.RunCommand), Any[string](), Any[string](), Eq(repoDir)) + Assert(t, *unlockCalled == true, "unlock function called") + }) + + t.Run("Commands 'plan, apply' set on webhook and plan command", func(t *testing.T) { + preWorkflowHooksSetup(t) + + var unlockCalled = newBool(false) + unlockFn := func() { + unlockCalled = newBool(true) + } + + globalCfg := valid.GlobalCfg{ + Repos: []valid.Repo{ + { + ID: testdata.GithubRepo.ID(), + PreWorkflowHooks: []*valid.WorkflowHook{ + &testHookWithPlanApplyCommands, + }, + }, + }, + } + + preWh.GlobalCfg = globalCfg + + When(preWhWorkingDirLocker.TryLock(testdata.GithubRepo.FullName, newPull.Num, events.DefaultWorkspace, events.DefaultRepoRelDir)).ThenReturn(unlockFn, nil) + When(preWhWorkingDir.Clone(testdata.GithubRepo, newPull, events.DefaultWorkspace)).ThenReturn(repoDir, false, nil) + When(whPreWorkflowHookRunner.Run(Any[models.WorkflowHookCommandContext](), + Eq(testHookWithPlanApplyCommands.RunCommand), Any[string](), Any[string](), Eq(repoDir))).ThenReturn(result, runtimeDesc, nil) + + err := preWh.RunPreHooks(ctx, planCmd) + + Ok(t, err) + whPreWorkflowHookRunner.VerifyWasCalledOnce().Run(Any[models.WorkflowHookCommandContext](), + Eq(testHookWithPlanApplyCommands.RunCommand), Any[string](), Any[string](), Eq(repoDir)) + Assert(t, *unlockCalled == true, "unlock function called") + }) } From b99647cb2fefb7a4b374755d0c509bc35d2fbe26 Mon Sep 17 00:00:00 2001 From: X-Guardian Date: Mon, 28 Aug 2023 11:35:36 +0100 Subject: [PATCH 2/4] Fix post workflow hook example --- runatlantis.io/docs/post-workflow-hooks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runatlantis.io/docs/post-workflow-hooks.md b/runatlantis.io/docs/post-workflow-hooks.md index f7563ade62..aca89a08aa 100644 --- a/runatlantis.io/docs/post-workflow-hooks.md +++ b/runatlantis.io/docs/post-workflow-hooks.md @@ -25,7 +25,7 @@ can be found in [Using Atlantis](using-atlantis.md). ```yaml repos: - id: /.*/ - pre_workflow_hooks: + post_workflow_hooks: - run: ./plan-hook.sh description: Plan Hook commands: plan From 4cccf0132b54e0cac35d804021f93c329d93c29c Mon Sep 17 00:00:00 2001 From: X-Guardian Date: Mon, 28 Aug 2023 11:42:00 +0100 Subject: [PATCH 3/4] Update WokrflowHook in global_cfg --- server/core/config/valid/global_cfg.go | 1 + 1 file changed, 1 insertion(+) diff --git a/server/core/config/valid/global_cfg.go b/server/core/config/valid/global_cfg.go index a62625db92..e4a47c39cd 100644 --- a/server/core/config/valid/global_cfg.go +++ b/server/core/config/valid/global_cfg.go @@ -111,6 +111,7 @@ type WorkflowHook struct { StepDescription string Shell string ShellArgs string + Commands string } // DefaultApplyStage is the Atlantis default apply stage. From 6034342c06fee42b3af40e8ce481a4e0fd869743 Mon Sep 17 00:00:00 2001 From: X-Guardian Date: Mon, 28 Aug 2023 11:50:09 +0100 Subject: [PATCH 4/4] Fix linting --- server/events/post_workflow_hooks_command_runner.go | 3 +-- server/events/pre_workflow_hooks_command_runner.go | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/server/events/post_workflow_hooks_command_runner.go b/server/events/post_workflow_hooks_command_runner.go index 77fe09291d..6af9d6a499 100644 --- a/server/events/post_workflow_hooks_command_runner.go +++ b/server/events/post_workflow_hooks_command_runner.go @@ -115,10 +115,9 @@ func (w *DefaultPostWorkflowHooksCommandRunner) runHooks( ctx.Log.Debug("Skipping post workflow hook '%s' as command '%s' is not in Commands [%s]", hookDescription, ctx.CommandName, hook.Commands) continue - } else { - ctx.Log.Debug("Running post workflow hook: '%s'", hookDescription) } + ctx.Log.Debug("Running post workflow hook: '%s'", hookDescription) ctx.HookID = uuid.NewString() shell := hook.Shell if shell == "" { diff --git a/server/events/pre_workflow_hooks_command_runner.go b/server/events/pre_workflow_hooks_command_runner.go index 3b1737de6a..970d280b9b 100644 --- a/server/events/pre_workflow_hooks_command_runner.go +++ b/server/events/pre_workflow_hooks_command_runner.go @@ -112,9 +112,9 @@ func (w *DefaultPreWorkflowHooksCommandRunner) runHooks( ctx.Log.Debug("Skipping pre workflow hook '%s' as command '%s' is not in Commands [%s]", hookDescription, ctx.CommandName, hook.Commands) continue - } else { - ctx.Log.Debug("Running pre workflow hook: '%s'", hookDescription) } + + ctx.Log.Debug("Running pre workflow hook: '%s'", hookDescription) ctx.HookID = uuid.NewString() shell := hook.Shell if shell == "" {