Skip to content

Commit a5276a9

Browse files
authored
Merge pull request #236 from puppetlabs/puppet_runonce-lang
Improve handling of unsuitable LANG settings
2 parents ee611d7 + 5846e95 commit a5276a9

8 files changed

+292
-4
lines changed

files/task_helper.sh

+272
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
#!/bin/bash
2+
#=============================================================================
3+
# Bash Task Helper
4+
#
5+
# This helper allows shell script authors to easily write Bolt tasks which
6+
# return useful output, including success, failure, and key-value return data.
7+
#
8+
# - Set your script shebang line to bash
9+
# - Source this script in the second line of your task
10+
# - For a task parameter "input", you may reference its value using ${input}
11+
# - Use `task-output "key" "value"` to set return data strings
12+
# - Use `task-succeed "message"`, or `task-fail "message"` to end the task
13+
# - The task helper reserves two file descriptors in order to manage and
14+
# process script output into valid task JSON. Consumers MUST NOT use or
15+
# redirect the reserved file descriptors 6 and 7.
16+
# - The task helper sets up a default EXIT trap. If consumers trap EXIT
17+
# themselves, they MUST call `task-exit` at the end of their trap to trigger
18+
# the helper's output finalization routine.
19+
# - When debugging, optionally call `task-verbose-output` before exiting. It
20+
# is recommended only to use this function call when debugging.
21+
#
22+
# Output:
23+
#
24+
# If the script exits with a non-zero exit code, or calls task-fail, all output
25+
# will be returned to Bolt, including any keys set by `task-output`, and the
26+
# message given to `task-fail` (if given).
27+
#
28+
# If the script exits successfully, or if task-succeed is called, no output
29+
# will be returned except output specifically designated by the user via
30+
# `task-output` and/or `task-succeed` function calls.
31+
#
32+
# Examples:
33+
#
34+
# #!/bin/bash
35+
# source "$(dirname $0)/../../bash_task_helper/files/task_helper.sh"
36+
#
37+
# echo "this output will be visible, but not set any key-value data"
38+
#
39+
# VAR=$(date)
40+
# task-output "timestamp" "${VAR}"
41+
#
42+
# task-succeed "demonstration task successful"
43+
#
44+
#=============================================================================
45+
46+
47+
# Public: Set status=error, set a message, and exit the task
48+
#
49+
# This function ends the task. The task will return as failed. The function
50+
# accepts an argument to set the task's return message, and an optional exit
51+
# code to use.
52+
#
53+
# $1 - Message. A text string message to return in the task's `message` key.
54+
# $2 - Exit code. A non-zero integer to use as the task's exit code.
55+
#
56+
# Examples
57+
#
58+
# task-fail
59+
# task-fail "task failed because of reasons"
60+
# task-fail "task failed because of reasons" "127"
61+
#
62+
task-fail() {
63+
task-output "status" "error"
64+
_task_exit_string="$1"
65+
task-exit ${2:-1}
66+
}
67+
68+
# DEPRECATED
69+
fail() {
70+
task-output "_deprecation_warning" "WARN: bash_task_helper fail() is deprecated. Please use task-fail() instead."
71+
task-fail "$@"
72+
}
73+
74+
# Public: Set status=success, set a message, and exit the task
75+
#
76+
# This function ends the task. The task will return as successful. The function
77+
# accepts an argument to set the task's return message.
78+
#
79+
# $1 - Message. A text string message to return in the task's `message` key.
80+
#
81+
# Examples
82+
#
83+
# task-succeed
84+
# task-succeed "task completed successfully"
85+
#
86+
task-succeed() {
87+
task-output "status" "success"
88+
_task_exit_string="$1"
89+
task-exit 0
90+
}
91+
92+
# DEPRECATED
93+
success() {
94+
task-output "_deprecation_warning" "WARN: bash_task_helper: use of success() is deprecated. Please use task-succeed() instead."
95+
task-succeed "$@"
96+
}
97+
98+
# Public: Set a task output key to a string value
99+
#
100+
# Takes a key argument and a value argument, and ensures that upon task exit
101+
# the key and value will be returned as part of the task output.
102+
#
103+
# $1 - Output key. Should contain only characters that match [A-Za-z0-9-_]
104+
# $2 - Output value. Should be a string. Will be json-escaped.
105+
#
106+
# Examples
107+
#
108+
# task-output "message" "an armadilo crossed the street"
109+
# task-output "maximum" "100"
110+
#
111+
task-output() {
112+
local key="${1}"
113+
local value=$(echo -n "$2" | task-json-escape)
114+
115+
# Try to find an index for the key
116+
for i in "${!_task_output_keys[@]}"; do
117+
[[ "${_task_output_keys[$i]}" = "${key}" ]] && break
118+
done
119+
120+
# If there's an index, set its value. Otherwise, add a new key
121+
if [[ "${_task_output_keys[$i]}" = "${key}" ]]; then
122+
_task_output_values[$i]="${value}"
123+
else
124+
_task_output_keys=("${_task_output_keys[@]}" "${key}")
125+
_task_output_values=("${_task_output_values[@]}" "${value}")
126+
fi
127+
}
128+
129+
# Public: Set the task to always return full output
130+
#
131+
# Tasks normally do not return all output if the task returns successfully. If
132+
# this function is invoked, the task will return all output regardless of exit
133+
# code.
134+
#
135+
# $1 - true or false. Defaults to true. Pass false to turn verbose output off.
136+
#
137+
# Examples
138+
#
139+
# task-verbose-output
140+
#
141+
task-verbose-output() {
142+
_task_verbose_output=${1:-true}
143+
}
144+
145+
# Public: read text on stdin and output the text json-escaped
146+
#
147+
# A filter command which does its best to json-escape text input. Because the
148+
# function is constrained to rely only on lowest-common-denominator posix
149+
# utilities, it may not be able to fully escape all text on all platforms.
150+
#
151+
# Examples
152+
#
153+
# printf "a string\nwith newlines\n" | task-json-escape
154+
# task-json-escape < file.txt
155+
#
156+
task-json-escape() {
157+
# This is imperfect, and will miss some characters. If we can figure out a
158+
# way to get iconv to catch more character types, we might improve that.
159+
# 1. Replace backslashes with escape sequences
160+
# 2. Replace unicode characters (if possible) with system iconv
161+
# 3. Replace other required characters with escape sequences
162+
# Note that this includes two control-characters specifically
163+
# 4. Escape newlines (1/2): Replace all newlines with literal tabs
164+
# 5. Escape newlines (2/2): Replace all literal tabs with newline escape sequences
165+
# 6. Delete any remaining non-printable lines from the stream
166+
sed -e 's/\\/\\/g' \
167+
| { iconv -t ASCII --unicode-subst="\u%04x" || cat; } \
168+
| sed -e 's/"/\\"/' \
169+
-e 's/\//\\\//g' \
170+
-e "s/$(printf '\b')/\\\b/" \
171+
-e "s/$(printf '\f')/\\\f/" \
172+
-e 's/\r/\\r/g' \
173+
-e 's/\t/\\t/g' \
174+
-e "s/$(printf "\x1b")/\\\u001b/g" \
175+
-e "s/$(printf "\x0f")/\\\u000f/g" \
176+
| tr '\n' '\t' \
177+
| sed 's/\t/\\n/g' \
178+
| tr -cd '\11\12\15\40-\176'
179+
}
180+
181+
# Public: Print json task return data on task exit
182+
#
183+
# This function is called by a task helper EXIT trap. It will print json task
184+
# return data on task termination. The return data will include all output
185+
# keys set using task-output, and all uncaptured stdout/stderr output produced
186+
# by the script. This function should not be directly invoked, except inside a
187+
# user-created EXIT trap.
188+
#
189+
# $1 - Exit code to terminate script with. Defaults to $?.
190+
#
191+
# Examples
192+
#
193+
# task-exit
194+
# task-exit 1
195+
#
196+
task-exit() {
197+
# Record the exit code
198+
local exit_code=${1:-$?}
199+
local output
200+
201+
# Unset the trap
202+
trap - EXIT
203+
204+
# If appropriate, set an _output value. By default, if the task is
205+
# successful, full script output is suppressed. If the user passed a message
206+
# to task-succeed, that will still be returned as _output. If the task does
207+
# not exit successfully, or if the task is running in verbose mode, then full
208+
# output is returned (including a task-fail user message, if there is one)
209+
if [ "$exit_code" -ne 0 -o "$_task_verbose_output" = 'true' ]; then
210+
# Print the exit string, then set _output to everything that the script has printed
211+
echo -n "$_task_exit_string"
212+
task-output '_output' "$(cat "${_output_tmpfile}")"
213+
elif [ ! -z "$_task_exit_string" ]; then
214+
# Set _output to just the exit string
215+
task-output '_output' "${_task_exit_string}"
216+
fi
217+
218+
# Reset outputs
219+
exec 1>&6
220+
exec 2>&7
221+
222+
# Print JSON to stdout
223+
printf '{\n'
224+
for i in "${!_task_output_keys[@]}"; do
225+
# Print each key-value pair
226+
printf ' "%s": "%s"' "${_task_output_keys[$i]}" "${_task_output_values[$i]}"
227+
# Print a comma unless it's the last key-value
228+
[ ! "$(($i + 1))" -eq "${#_task_output_keys[@]}" ] && printf ','
229+
# Print a newline
230+
printf '\n'
231+
done
232+
printf '}\n'
233+
234+
# Remove the output tempfile
235+
rm "$_output_tmpfile"
236+
237+
# Resume an orderly exit
238+
exit "$exit_code"
239+
}
240+
241+
# Test for colors. If unavailable, unset variables are ok
242+
if tput colors &>/dev/null; then
243+
green="$(tput setaf 2)"
244+
red="$(tput setaf 1)"
245+
reset="$(tput sgr0)"
246+
fi
247+
248+
# Use indirection to munge PT_ environment variables
249+
# e.g. "$PT_version" becomes "$version"
250+
for v in ${!PT_*}; do
251+
declare "${v#*PT_}"="${!v}"
252+
done
253+
254+
# Set up variables to record task outputs
255+
_task_output_keys=()
256+
_task_output_values=()
257+
_task_exit_string=''
258+
_task_verbose_output=false
259+
260+
# Redirect all output (stdin, stderr) to a tempfile, and trap EXIT. Upon exit,
261+
# print a Bolt task return JSON string, with the full contents of the tempfile
262+
# in the "_output" key.
263+
#
264+
# Note: file descriptors 6 and 7 are used to save original stdout/stderr. These
265+
# were chosen as the file descriptors least likely to be used by shell
266+
# script task authors. Client scripts MUST NOT use these descriptors.
267+
_output_tmpfile="$(mktemp)"
268+
trap task-exit EXIT
269+
exec 6>&1 \
270+
7>&2 \
271+
1>> "$_output_tmpfile" \
272+
2>&1

tasks/agent_upgrade.sh

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
#!/bin/bash
22

3+
# Try and ensure locale is correctly configured
4+
[ -z "${LANG}" ] && export LANG=$(localectl status | sed -n 's/.* LANG=\(.*\)/\1/p')
5+
36
export USER=$(id -un)
47
export HOME=$(getent passwd "$USER" | cut -d : -f 6)
58
export PATH="/opt/puppetlabs/bin:${PATH}"

tasks/enable_replica.sh

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
#!/bin/bash
22

3+
# Try and ensure locale is correctly configured
4+
[ -z "${LANG}" ] && export LANG=$(localectl status | sed -n 's/.* LANG=\(.*\)/\1/p')
5+
36
USER=$(id -un)
47
HOME=$(getent passwd "$USER" | cut -d : -f 6)
58

tasks/pe_install.json

+3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
}
2020
},
2121
"input_method": "environment",
22+
"files": [
23+
"peadm/files/task_helper.sh"
24+
],
2225
"implementations": [
2326
{"name": "pe_install.sh"}
2427
]

tasks/pe_install.sh

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
#!/bin/bash
2+
source "$(dirname $0)/../../peadm/files/task_helper.sh"
3+
4+
# Try and ensure locale is correctly configured
5+
[ -z "${LANG}" ] && export LANG=$(localectl status | sed -n 's/.* LANG=\(.*\)/\1/p')
26

37
# This stanza configures PuppetDB to quickly fail on start. This is desirable
48
# in situations where PuppetDB WILL fail, such as when PostgreSQL is not yet
@@ -20,10 +24,6 @@ pedir=$(tar -tf "$PT_tarball" | head -n 1 | xargs dirname)
2024

2125
tar -C "$tgzdir" -xzf "$PT_tarball"
2226

23-
export LANG=en_US.UTF-8
24-
export LANGUAGE=en_US.UTF-8
25-
export LC_ALL=en_US.UTF-8
26-
2727
if [ ! -z "$PT_peconf" ]; then
2828
/bin/bash "${tgzdir}/${pedir}/puppet-enterprise-installer" -y -c "$PT_peconf"
2929
else

tasks/provision_replica.sh

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
#!/bin/bash
22

3+
# Try and ensure locale is correctly configured
4+
[ -z "${LANG}" ] && export LANG=$(localectl status | sed -n 's/.* LANG=\(.*\)/\1/p')
5+
36
export USER=$(id -un)
47
export HOME=$(getent passwd "$USER" | cut -d : -f 6)
58
export PATH="/opt/puppetlabs/bin:${PATH}"

tasks/puppet_runonce.sh

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
#!/bin/bash
22

3+
# Try and ensure locale is correctly configured
4+
[ -z "${LANG}" ] && export LANG=$(localectl status | sed -n 's/.* LANG=\(.*\)/\1/p')
5+
6+
# Parse noop parameter
37
[ "$PT_noop" = "true" ] && NOOP_FLAG="--noop" || unset NOOP_FLAG
48

59
# Wait for up to five minutes for an in-progress Puppet agent run to complete

tasks/wait_until_service_ready.sh

100644100755
File mode changed.

0 commit comments

Comments
 (0)