-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
Copy pathpre_workflow_hooks_command_runner.go
173 lines (146 loc) · 5.71 KB
/
pre_workflow_hooks_command_runner.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
package events
import (
"fmt"
"strings"
"github.com/google/uuid"
"github.com/runatlantis/atlantis/server/core/config/valid"
"github.com/runatlantis/atlantis/server/core/runtime"
"github.com/runatlantis/atlantis/server/events/command"
"github.com/runatlantis/atlantis/server/events/models"
"github.com/runatlantis/atlantis/server/events/vcs"
)
//go:generate pegomock generate --package mocks -o mocks/mock_pre_workflow_hook_url_generator.go PreWorkflowHookURLGenerator
// PreWorkflowHookURLGenerator generates urls to view the pre workflow progress.
type PreWorkflowHookURLGenerator interface {
GenerateProjectWorkflowHookURL(hookID string) (string, error)
}
//go:generate pegomock generate --package mocks -o mocks/mock_pre_workflows_hooks_command_runner.go PreWorkflowHooksCommandRunner
type PreWorkflowHooksCommandRunner interface {
RunPreHooks(ctx *command.Context, cmd *CommentCommand) error
}
// DefaultPreWorkflowHooksCommandRunner is the first step when processing a workflow hook commands.
type DefaultPreWorkflowHooksCommandRunner struct {
VCSClient vcs.Client
WorkingDirLocker WorkingDirLocker
WorkingDir WorkingDir
GlobalCfg valid.GlobalCfg
PreWorkflowHookRunner runtime.PreWorkflowHookRunner
CommitStatusUpdater CommitStatusUpdater
Router PreWorkflowHookURLGenerator
}
// RunPreHooks runs pre_workflow_hooks when PR is opened or updated.
func (w *DefaultPreWorkflowHooksCommandRunner) RunPreHooks(ctx *command.Context, cmd *CommentCommand) error {
preWorkflowHooks := make([]*valid.WorkflowHook, 0)
for _, repo := range w.GlobalCfg.Repos {
if repo.IDMatches(ctx.Pull.BaseRepo.ID()) && len(repo.PreWorkflowHooks) > 0 {
preWorkflowHooks = append(preWorkflowHooks, repo.PreWorkflowHooks...)
}
}
// short circuit any other calls if there are no pre-hooks configured
if len(preWorkflowHooks) == 0 {
return nil
}
ctx.Log.Info("Pre-workflow hooks configured, running...")
unlockFn, err := w.WorkingDirLocker.TryLock(ctx.Pull.BaseRepo.FullName, ctx.Pull.Num, DefaultWorkspace, DefaultRepoRelDir)
if err != nil {
return err
}
ctx.Log.Debug("got workspace lock")
defer unlockFn()
repoDir, _, err := w.WorkingDir.Clone(ctx.Log, ctx.HeadRepo, ctx.Pull, DefaultWorkspace)
if err != nil {
return err
}
var escapedArgs []string
if cmd != nil {
escapedArgs = escapeArgs(cmd.Flags)
}
// Update the plan or apply commit status to pending whilst the pre workflow hook is running
switch cmd.Name {
case command.Plan:
if err := w.CommitStatusUpdater.UpdateCombined(ctx.Log, ctx.Pull.BaseRepo, ctx.Pull, models.PendingCommitStatus, command.Plan); err != nil {
ctx.Log.Warn("unable to update plan commit status: %s", err)
}
case command.Apply:
if err := w.CommitStatusUpdater.UpdateCombined(ctx.Log, ctx.Pull.BaseRepo, ctx.Pull, models.PendingCommitStatus, command.Apply); err != nil {
ctx.Log.Warn("unable to update apply commit status: %s", err)
}
}
err = w.runHooks(
models.WorkflowHookCommandContext{
BaseRepo: ctx.Pull.BaseRepo,
HeadRepo: ctx.HeadRepo,
Log: ctx.Log,
Pull: ctx.Pull,
User: ctx.User,
Verbose: false,
EscapedCommentArgs: escapedArgs,
CommandName: cmd.Name.String(),
API: ctx.API,
},
preWorkflowHooks, repoDir)
if err != nil {
ctx.Log.Err("Error running pre-workflow hooks %s.", err)
return err
}
ctx.Log.Info("Pre-workflow hooks completed successfully")
return nil
}
func (w *DefaultPreWorkflowHooksCommandRunner) runHooks(
ctx models.WorkflowHookCommandContext,
preWorkflowHooks []*valid.WorkflowHook,
repoDir string,
) error {
for i, hook := range preWorkflowHooks {
ctx.HookDescription = hook.StepDescription
if ctx.HookDescription == "" {
ctx.HookDescription = fmt.Sprintf("Pre workflow hook #%d", i)
}
ctx.HookStepName = fmt.Sprintf("pre %s #%d", ctx.CommandName, i)
ctx.Log.Debug("Processing pre workflow hook '%s', Command '%s', Target commands [%s]",
ctx.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]",
ctx.HookDescription, ctx.CommandName, hook.Commands)
continue
}
ctx.Log.Debug("Running pre workflow hook: '%s'", ctx.HookDescription)
ctx.HookID = uuid.NewString()
shell := hook.Shell
if shell == "" {
ctx.Log.Debug("Setting shell to default: '%s'", shell)
shell = "sh"
}
shellArgs := hook.ShellArgs
if shellArgs == "" {
ctx.Log.Debug("Setting shellArgs to default: '%s'", shellArgs)
shellArgs = "-c"
}
url, err := w.Router.GenerateProjectWorkflowHookURL(ctx.HookID)
if err != nil && !ctx.API {
return err
}
if err := w.CommitStatusUpdater.UpdatePreWorkflowHook(ctx.Log, ctx.Pull, models.PendingCommitStatus, ctx.HookDescription, "", url); err != nil {
ctx.Log.Warn("unable to update pre workflow hook status: %s", err)
ctx.Log.Info("is api? %v", ctx.API)
if !ctx.API {
ctx.Log.Info("is api? %v", ctx.API)
return err
}
}
_, runtimeDesc, err := w.PreWorkflowHookRunner.Run(ctx, hook.RunCommand, shell, shellArgs, repoDir)
if err != nil {
if err := w.CommitStatusUpdater.UpdatePreWorkflowHook(ctx.Log, ctx.Pull, models.FailedCommitStatus, ctx.HookDescription, runtimeDesc, url); err != nil {
ctx.Log.Warn("unable to update pre workflow hook status: %s", err)
}
return err
}
if err := w.CommitStatusUpdater.UpdatePreWorkflowHook(ctx.Log, ctx.Pull, models.SuccessCommitStatus, ctx.HookDescription, runtimeDesc, url); err != nil {
ctx.Log.Warn("unable to update pre workflow hook status: %s", err)
if !ctx.API {
return err
}
}
}
return nil
}