forked from gruntwork-io/terragrunt
-
Notifications
You must be signed in to change notification settings - Fork 2
/
extension_hook.go
184 lines (156 loc) · 5.52 KB
/
extension_hook.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
174
175
176
177
178
179
180
181
182
183
184
package config
import (
"fmt"
"github.com/gruntwork-io/terragrunt/errors"
"os"
"sort"
"strings"
"github.com/coveo/gotemplate/utils"
"github.com/gruntwork-io/terragrunt/options"
"github.com/gruntwork-io/terragrunt/shell"
"github.com/gruntwork-io/terragrunt/util"
)
// Hook is a definition of user command that should be executed as part of the terragrunt process
type Hook struct {
TerragruntExtensionBase `hcl:",squash"`
Command string `hcl:"command"`
Arguments []string `hcl:"arguments"`
ExpandArgs bool `hcl:"expand_args"`
OnCommands []string `hcl:"on_commands"`
IgnoreError bool `hcl:"ignore_error"`
BeforeImports bool `hcl:"before_imports"`
AfterInitState bool `hcl:"after_init_state"`
Order int `hcl:"order"`
ShellCommand bool `hcl:"shell_command"` // This indicates that the command is a shell command and output should not be redirected
}
func (hook Hook) itemType() (result string) { return HookList{}.argName() }
func (hook Hook) help() (result string) {
if hook.Description != "" {
result += fmt.Sprintf("\n%s\n", hook.Description)
}
result += fmt.Sprintf("\nCommand: %s %s\n", hook.Command, strings.Join(hook.Arguments, " "))
if hook.OnCommands != nil {
result += fmt.Sprintf("\nApplies on the following command(s): %s\n", strings.Join(hook.OnCommands, ", "))
}
if hook.OS != nil {
result += fmt.Sprintf("\nApplied only on the following OS: %s\n", strings.Join(hook.OS, ", "))
}
attributes := []string{
fmt.Sprintf("Order = %d", hook.Order),
fmt.Sprintf("Expand arguments = %v", hook.ExpandArgs),
fmt.Sprintf("Ignore error = %v", hook.IgnoreError),
}
result += fmt.Sprintf("\n%s\n", strings.Join(attributes, ", "))
return
}
func (hook *Hook) run(args ...interface{}) (result []interface{}, err error) {
logger := hook.logger()
if len(hook.OnCommands) > 0 && !util.ListContainsElement(hook.OnCommands, hook.options().Env[options.EnvCommand]) {
// The current command is not in the list of command on which the hook should be applied
return
}
if !hook.enabled() {
logger.Debugf("Hook %s skipped, executed only on %v", hook.Name, hook.OS)
return
}
hook.Command = strings.TrimSpace(hook.Command)
if len(hook.Command) == 0 {
logger.Debugf("Hook %s skipped, no command to execute", hook.Name)
return
}
cmd := shell.NewCmd(hook.options(), hook.Command).Args(hook.Arguments...)
if hook.ShellCommand {
// We must not redirect the stderr on shell command, doing so, remove the prompt
cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
}
if hook.ExpandArgs {
cmd = cmd.ExpandArgs()
}
if !utils.IsCommand(hook.Command) {
cmd.DisplayCommand = fmt.Sprintf("%s %s", hook.name(), strings.Join(hook.Arguments, " "))
}
if shouldBeApproved, approvalConfig := hook.config().ApprovalConfig.ShouldBeApproved(hook.Command); shouldBeApproved {
cmd = cmd.Expect(approvalConfig.ExpectStatements, approvalConfig.CompletedStatements)
}
err = cmd.Run()
return
}
func (base Hook) setState(err error) {
exitCode, errCode := shell.GetExitCode(err)
if errCode != nil {
exitCode = -1
}
base.options().SetStatus(exitCode, err)
}
// ----------------------- HookList -----------------------
//go:generate genny -in=extension_base_list.go -out=generated_hooks.go gen "GenericItem=Hook"
func (list HookList) argName() string { return "hooks" }
func (list HookList) sort() HookList {
sort.SliceStable(list, func(i, j int) bool { return list[i].Order < list[j].Order })
return list
}
// MergePrepend prepends elements from an imported list to the current list
func (list *HookList) MergePrepend(imported HookList) {
list.merge(imported, mergeModePrepend, "pre_hook")
}
// MergeAppend appends elements from an imported list to the current list
func (list *HookList) MergeAppend(imported HookList) {
list.merge(imported, mergeModeAppend, "post_hook")
}
// Filter returns a list of hook that match the supplied filter
func (list HookList) Filter(filter HookFilter) HookList {
result := make(HookList, 0, len(list))
for _, hook := range list.Enabled() {
if filter(hook) {
result = append(result, hook)
}
}
return result
}
// HookFilter is used to filter the hook on supplied criteria
type HookFilter func(Hook) bool
// BeforeImports is a filter function
var BeforeImports = func(hook Hook) bool { return hook.BeforeImports }
// BeforeInitState is a filter function
var BeforeInitState = func(hook Hook) bool { return !hook.AfterInitState && !hook.BeforeImports }
// AfterInitState is a filter function
var AfterInitState = func(hook Hook) bool { return hook.AfterInitState && !hook.BeforeImports }
// Run execute the content of the list
func (list HookList) Run(status error, args ...interface{}) (result []interface{}, err error) {
if len(list) == 0 {
return
}
list.sort()
var (
errs errorArray
errOccurred bool
)
for _, hook := range list {
if (status != nil || errOccurred) && !hook.IgnoreError {
continue
}
hook.logger().Infof("Running %s (%s): %s", hook.itemType(), hook.id(), hook.name())
hook.normalize()
temp, currentErr := hook.run(args...)
currentErr = shell.FilterPlanError(currentErr, hook.options().TerraformCliArgs[0])
if currentErr != nil {
if _, ok := currentErr.(errors.PlanWithChanges); ok {
errs = append(errs, currentErr)
} else {
errOccurred = true
errs = append(errs, fmt.Errorf("Error while executing %s(%s): %v", hook.itemType(), hook.id(), currentErr))
}
}
hook.setState(currentErr)
result = append(result, temp)
}
switch len(errs) {
case 0:
break
case 1:
err = errs[0]
default:
err = errs
}
return
}