Skip to content

Commit f19013f

Browse files
committed
(GH-1759) Handle non-UTF-8 task output
Previously, we treated non-UTF-8 task output as simply non-JSON output, putting it in a hash under the `_output` key. This violated a key constraint of the task specification (task output must always be UTF-8) and caused problems when we tried to print the result as JSON (either as output or for logging). We now explicitly check for non-UTF-8 characters when interpreting the result and we throw away stdout entirely and replace it with an error if it is invalid. !bug * **Task output that contains invalid UTF-8 is now rejected** ([#1759](#1759)) Tasks are defined as returning UTF-8, but Bolt didn't handle the non-UTF-8 case explicitly, leading to messy error messages and stack traces. The error should now be clear and meaningful.
1 parent 9ea5be7 commit f19013f

File tree

2 files changed

+42
-10
lines changed

2 files changed

+42
-10
lines changed

lib/bolt/result.rb

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,23 @@ def self.for_command(target, stdout, stderr, exit_code, action, command)
4040
end
4141

4242
def self.for_task(target, stdout, stderr, exit_code, task)
43-
begin
44-
value = JSON.parse(stdout)
45-
unless value.is_a? Hash
46-
value = nil
47-
end
48-
rescue JSON::ParserError
49-
value = nil
50-
end
51-
value ||= { '_output' => stdout }
43+
stdout.force_encoding('utf-8') unless stdout.encoding == Encoding::UTF_8
44+
value = if stdout.valid_encoding?
45+
parse_hash(stdout) || { '_output' => stdout }
46+
else
47+
{ '_error' => { 'kind' => 'puppetlabs.tasks/task-error',
48+
'issue_code' => 'TASK_ERROR',
49+
'msg' => 'The task result contained invalid UTF-8 on stdout',
50+
'details' => {} } }
51+
end
52+
5253
if exit_code != 0 && value['_error'].nil?
5354
msg = if stdout.empty?
54-
"The task failed with exit code #{exit_code}:\n#{stderr}"
55+
if stderr.empty?
56+
"The task failed with exit code #{exit_code} and no output"
57+
else
58+
"The task failed with exit code #{exit_code} and no stdout, but stderr contained:\n#{stderr}"
59+
end
5560
else
5661
"The task failed with exit code #{exit_code}"
5762
end
@@ -63,6 +68,13 @@ def self.for_task(target, stdout, stderr, exit_code, task)
6368
new(target, value: value, action: 'task', object: task)
6469
end
6570

71+
def self.parse_hash(string)
72+
value = JSON.parse(string)
73+
value if value.is_a? Hash
74+
rescue JSON::ParserError
75+
nil
76+
end
77+
6678
def self.for_upload(target, source, destination)
6779
new(target, message: "Uploaded '#{source}' to '#{target.host}:#{destination}'", action: 'upload', object: source)
6880
end

spec/bolt/result_spec.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,5 +109,25 @@
109109
expect(result.value).to eq(obj)
110110
expect(result.error_hash).to eq(obj['_error'])
111111
end
112+
113+
it 'uses the unparsed value of stdout if it is not valid JSON' do
114+
stdout = 'just some string'
115+
result = Bolt::Result.for_task(target, stdout, '', 0, 'atask')
116+
expect(result.value).to eq('_output' => 'just some string')
117+
end
118+
119+
it 'generates an error for binary data' do
120+
stdout = "\xFC].\xF9\xA8\x85f\xDF{\x11d\xD5\x8E\xC6\xA6"
121+
result = Bolt::Result.for_task(target, stdout, '', 0, 'atask')
122+
expect(result.value.keys).to eq(['_error'])
123+
expect(result.error_hash['msg']).to match(/The task result contained invalid UTF-8/)
124+
end
125+
126+
it 'generates an error for non-UTF-8 output' do
127+
stdout = "☃".encode('utf-32')
128+
result = Bolt::Result.for_task(target, stdout, '', 0, 'atask')
129+
expect(result.value.keys).to eq(['_error'])
130+
expect(result.error_hash['msg']).to match(/The task result contained invalid UTF-8/)
131+
end
112132
end
113133
end

0 commit comments

Comments
 (0)