-
Notifications
You must be signed in to change notification settings - Fork 8
/
run.sh
executable file
·208 lines (187 loc) · 8.58 KB
/
run.sh
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
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
set -e
if ! [ -z "$BASH" ] ; then
# Only Bash supports this feature
set -o pipefail
fi
# Set this after checking for the BASH variable
set -u
# This checks if we're running on MacOS
_kernel_name="$(uname -s)"
case "${_kernel_name}" in
darwin*|Darwin*)
# It's MacOS.
# Mac doesn't support the "-d" flag for base64 decoding,
# so we have to use the full "--decode" flag instead.
_decode_flag="--decode"
# Mac doesn't support the "-w" flag for base64 wrapping,
# and it isn't needed because by default it doens't break lines.
_wrap_flag="" ;;
*)
# It's NOT MacOS.
# Not all Linux base64 installs (e.g. BusyBox) support the full
# "--decode" flag. So, we use "-d" here, since it's supported
# by everything except MacOS.
_decode_flag="-d"
# All non-Mac installs need this to be specified to prevent line
# wrapping, which adds newlines that we don't want.
_wrap_flag="-w0" ;;
esac
# This checks if the "-n" flag is supported on this shell, and sets vars accordingly
if [ "`echo -n`" = "-n" ] ; then
_echo_n=""
_echo_c="\c"
else
_echo_n="-n"
_echo_c=""
fi
_raw_input="$(cat)"
# We know that all of the inputs are base64-encoded, and "|" is not a valid base64 character, so therefore it
# cannot possibly be included in the stdin.
IFS="|"
set -o noglob
set -- $_raw_input""
_execution_id="$(echo "${2}" | base64 $_decode_flag)"
_directory="$(echo "${3}" | base64 $_decode_flag)"
_environment="$(echo "${4}" | base64 $_decode_flag)"
_timeout="$(echo "${5}" | base64 $_decode_flag)"
_exit_on_nonzero="$(echo "${6}" | base64 $_decode_flag)"
_exit_on_stderr="$(echo "${7}" | base64 $_decode_flag)"
_exit_on_timeout="$(echo "${8}" | base64 $_decode_flag)"
_debug="$(echo "${9}" | base64 $_decode_flag)"
_command_b64="${10}"
_shell="$(echo "${11}" | base64 $_decode_flag)"
# Generate a random/unique ID if an ID wasn't explicitly set
if [ "$_execution_id" = " " ] ; then
# We try many different strategies for generating a random number, hoping that at least one will succeed,
# since each OS/shell supports a different combination.
if [ -e /proc/sys/kernel/random/uuid ] ; then
_execution_id="$(cat /proc/sys/kernel/random/uuid)"
elif [ -e /dev/urandom ] ; then
# Using head instead of cat here seems odd, and it is, but it deals with a pipefail
# issue on MacOS that occurs if you use cat instead.
_execution_id="$(head -c 10000 /dev/urandom | LC_ALL=C tr -dc '[:alnum:]' | head -c 40)"
elif [ -e /dev/random ] ; then
_execution_id="$(head -c 10000 /dev/random | LC_ALL=C tr -dc '[:alnum:]' | head -c 40)"
else
_execution_id="$RANDOM-$RANDOM-$RANDOM-$RANDOM"
fi
fi
# The filenames to direct output to
_stderrfile="$_directory/$_execution_id.stderr"
_stdoutfile="$_directory/$_execution_id.stdout"
_debugfile="$_directory/$_execution_id.debug"
if [ $_debug = "true" ] ; then echo "Arguments loaded" > "$_debugfile"; fi
# Split the env var input on semicolons. We use semicolons because we know
# that neither the base64-encoded name or value will contain a semicolon.
IFS=";"
set -o noglob
set -- $_environment""
for _env in "$@"; do
if [ -z "$_env" ] ; then
continue
fi
# For each env var, split it on a colon. We use colons because we know
# that neither the env var name nor the base64-encoded value will contain
# a colon.
_key="$(echo $_echo_n "${_env}${_echo_c}" | cut -d':' -f1 | base64 $_decode_flag)"
_val="$(echo $_echo_n "${_env}${_echo_c}" | cut -d':' -f2 | base64 $_decode_flag)"
export "$_key"="$_val"
done
# A command to run at the very end of the input script. This forces the script to
# always exit with the exit code of the last command that returned an exit code.
_cmd_suffix=<<EOF
exit $?
EOF
# This is a custom function that executes the command, but interrupts it with a SIGALARM
# if it runs for too long. Not all operating systems have "timeout" built in, so
# we need to have a custom function that simulates it.
perl_timeout() { perl -e 'alarm shift; exec @ARGV' "$@"; }
# Run the command, but don't exit this script on an error
_timed_out="false"
if [ $_timeout -eq 0 ] ; then
# No timeout is set, so run the command without a timeout
if [ $_debug = "true" ] ; then echo "Starting process with no timeout" >> "$_debugfile"; fi
set +e
2>"$_stderrfile" >"$_stdoutfile" $_shell -c "$(echo "${_command_b64}" | base64 $_decode_flag)${_cmd_suffix}"
_exitcode=$?
set -e
else
# Add a prefix to the command, which wraps the commands in a block
_cmd_prefix=<<EOF
{
EOF
# Extend the suffix after the command, which redirects all stderr to a new descriptor
# We do this so that when timeout alarm signals cause the shell to print the signal
# description, it doesn't get captured into the stderr that we actually want.
_cmd_suffix=<<EOF
$_cmd_suffix
} 2>&3
EOF
# Default to using the built-in timeout command
_timeout_cmd="timeout"
if ! command -v $_timeout_cmd >/dev/null 2>/dev/null; then
# If it doesn't exist though, use the custom Perl one we created
_timeout_cmd="perl_timeout"
fi
# There is a timeout set, so run the command with it
if [ $_debug = "true" ] ; then echo "Starting process with a $_timeout second timeout" >> "$_debugfile"; fi
set +e
$_timeout_cmd $_timeout 3>"$_stderrfile" >"$_stdoutfile" $_shell -c "${_cmd_prefix}$(echo "${_command_b64}" | base64 $_decode_flag)${_cmd_suffix}"
_exitcode=$?
set -e
# Check if it timed out. 142 is the exit code from a Perl alarm signal, 124 is the exit code from most built-in
# "timeout" commands, and 143 is the exit code from the Busybox "timeout" command.
if [ $_exitcode -eq 142 ] || [ $_exitcode -eq 124 ] || [ $_exitcode -eq 143 ] ; then
if [ $_debug = "true" ] ; then echo "Process timed out after $_timeout seconds" >> "$_debugfile"; fi
_timed_out="true"
fi
fi
if [ $_debug = "true" ] ; then echo "Execution complete" >> "$_debugfile"; fi
# Read the stderr and stdout files
if [ $_debug = "true" ] ; then echo "Reading stdout file" >> "$_debugfile"; fi
_stdout="$(cat "$_stdoutfile")"
if [ $_debug = "true" ] ; then echo "Reading stderr file" >> "$_debugfile"; fi
_stderr="$(cat "$_stderrfile")"
if [ $_debug = "true" ] ; then echo "Finished reading output files" >> "$_debugfile"; fi
# Delete the files, unless we're using debug mode
if [ "$_debug" != "true" ] ; then
if [ $_debug = "true" ] ; then echo "Deleting stdout and stderr files" >> "$_debugfile"; fi
rm "$_stderrfile"
rm "$_stdoutfile"
fi
# Check if the execution timed out
if [ "$_timed_out" = "true" ] ; then
if [ "$_exit_on_timeout" = "true" ] ; then
if [ $_debug = "true" ] ; then echo "Failing due to a timeout error" >> "$_debugfile"; fi
>&2 echo $_echo_n "Execution timed out after $_timeout seconds${_echo_c}"
exit 1
else
_exitcode="null"
fi
fi
# If we want to kill Terraform on a non-zero exit code and the exit code was non-zero, OR
# we want to kill Terraform on a non-empty stderr and the stderr was non-empty
if ( [ "$_exit_on_nonzero" = "true" ] && [ "$_exitcode" != "null" ] && [ $_exitcode -ne 0 ] ) || ( [ "$_exit_on_stderr" = "true" ] && ! [ -z "$_stderr" ] ) ; then
# If there was a stderr, write it out as an error
if ! [ -z "$_stderr" ] ; then
if [ $_debug = "true" ] && [ "$_exit_on_stderr" = "true" ] ; then echo "Failing due to presence of stderr output" >> "$_debugfile"; fi
>&2 echo $_echo_n "${_stderr}${_echo_c}"
fi
# If a non-zero exit code was given, exit with it
if ( [ "$_exitcode" != "null" ] && [ "$_exitcode" -ne 0 ] ); then
if [ $_debug = "true" ] && [ "$_exit_on_nonzero" = "true" ] ; then echo "Failing due to a non-zero exit code ($_exitcode)" >> "$_debugfile"; fi
exit $_exitcode
fi
if [ $_debug = "true" ] ; then echo -e "\nStdout:\n$_stdout" >> "$_debugfile"; fi
if [ $_debug = "true" ] ; then echo -e "\nStderr:\n$_stderr" >> "$_debugfile"; fi
# Otherwise, exit with a default non-zero exit code
exit 1
fi
# Base64-encode the stdout and stderr for transmission back to Terraform
_stdout_b64=$(echo $_echo_n "${_stdout}${_echo_c}" | base64 $_wrap_flag)
_stderr_b64=$(echo $_echo_n "${_stderr}${_echo_c}" | base64 $_wrap_flag)
_exitcode_b64=$(echo $_echo_n "${_exitcode}${_echo_c}" | base64 $_wrap_flag)
# Echo a JSON string that Terraform can parse as the result
echo $_echo_n "{\"stdout\": \"$_stdout_b64\", \"stderr\": \"$_stderr_b64\", \"exitcode\": \"$_exitcode_b64\"}${_echo_c}"
if [ $_debug = "true" ] ; then echo "Done!" >> "$_debugfile"; fi
exit 0