chef-core 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +201 -0
- data/i18n/errors/en.yml +394 -0
- data/lib/chef_core/cliux/status_reporter.rb +43 -0
- data/lib/chef_core/cliux/ui/error_printer.rb +266 -0
- data/lib/chef_core/cliux/ui/plain_text_element.rb +80 -0
- data/lib/chef_core/cliux/ui/plain_text_header.rb +48 -0
- data/lib/chef_core/cliux/ui/terminal/job.rb +41 -0
- data/lib/chef_core/cliux/ui/terminal.rb +103 -0
- data/lib/chef_core/cliux.rb +7 -0
- data/lib/chef_core/error.rb +49 -0
- data/lib/chef_core/errors/standard_error_resolver.rb +38 -0
- data/lib/chef_core/file_fetcher.rb +67 -0
- data/lib/chef_core/log.rb +42 -0
- data/lib/chef_core/target_host/linux.rb +63 -0
- data/lib/chef_core/target_host/windows.rb +62 -0
- data/lib/chef_core/target_host.rb +351 -0
- data/lib/chef_core/target_resolver.rb +221 -0
- data/lib/chef_core/telemeter/patch.rb +32 -0
- data/lib/chef_core/telemeter/sender.rb +123 -0
- data/lib/chef_core/telemeter.rb +157 -0
- data/lib/chef_core/text/error_translation.rb +82 -0
- data/lib/chef_core/text/text_wrapper.rb +87 -0
- data/lib/chef_core/text.rb +79 -0
- data/lib/chef_core/version.rb +20 -0
- data/lib/chef_core.rb +3 -0
- data/resources/chef_run_reporter.rb +40 -0
- metadata +307 -0
@@ -0,0 +1,266 @@
|
|
1
|
+
#
|
2
|
+
# Copyright:: Copyright (c) 2017 Chef Software Inc.
|
3
|
+
# License:: Apache License, Version 2.0
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
|
18
|
+
require "train/errors"
|
19
|
+
require "pastel"
|
20
|
+
require "chef_core/error"
|
21
|
+
require "chef_core/text"
|
22
|
+
require "chef_core/cliux/ui/terminal"
|
23
|
+
require "chef_core/errors/standard_error_resolver"
|
24
|
+
|
25
|
+
module ChefCore
|
26
|
+
module CLIUX
|
27
|
+
module UI
|
28
|
+
class ErrorPrinter
|
29
|
+
attr_reader :id, :pastel, :translation, :exception, :target_host, :config
|
30
|
+
|
31
|
+
# 't' is a convenience method for accessing error i18n error definitions.
|
32
|
+
# It also serves as a workaround to let us verify that correct text key
|
33
|
+
# lookups happen in unit tests.
|
34
|
+
def t
|
35
|
+
ChefCore::Text.errors
|
36
|
+
end
|
37
|
+
|
38
|
+
DEFAULT_ERROR_NO = "CHEFINT001".freeze
|
39
|
+
|
40
|
+
def self.show_error(e, config)
|
41
|
+
# Name is misleading - it's unwrapping but also doing further
|
42
|
+
# error resolution for common errors:
|
43
|
+
unwrapped = ChefCore::Errors::StandardErrorResolver.unwrap_exception(e)
|
44
|
+
if unwrapped.class == ChefCore::MultiJobFailure
|
45
|
+
capture_multiple_failures(unwrapped, config)
|
46
|
+
end
|
47
|
+
formatter = ErrorPrinter.new(wrapper: e,
|
48
|
+
exception: unwrapped,
|
49
|
+
config: config)
|
50
|
+
|
51
|
+
Terminal.output(formatter.format_error)
|
52
|
+
rescue => ex
|
53
|
+
dump_unexpected_error(ex)
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.capture_multiple_failures(e, config)
|
57
|
+
e.params << config[:error_output_path] # Tell the operator where to find this info
|
58
|
+
File.open(config[:error_output_path], "w") do |out|
|
59
|
+
e.jobs.each do |j|
|
60
|
+
wrapped = ChefCore::Errors::StandardErrorResolver.wrap_exception(j.exception, j.target_host)
|
61
|
+
ep = ErrorPrinter.new(wrapper: wrapped, config: config)
|
62
|
+
msg = ep.format_body().tr("\n", " ").gsub(/ {2,}/, " ").chomp.strip
|
63
|
+
out.write("Host: #{j.target_host.hostname} ")
|
64
|
+
if ep.exception.respond_to? :id
|
65
|
+
out.write("Error: #{ep.exception.id}: ")
|
66
|
+
else
|
67
|
+
out.write(": ")
|
68
|
+
end
|
69
|
+
out.write("#{msg}\n")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.write_backtrace(e, args, config)
|
75
|
+
formatter = ErrorPrinter.new(wrapper: e, config: config)
|
76
|
+
out = StringIO.new
|
77
|
+
formatter.add_backtrace_header(out, args)
|
78
|
+
formatter.add_formatted_backtrace(out)
|
79
|
+
formatter.save_backtrace(out, config)
|
80
|
+
rescue => ex
|
81
|
+
dump_unexpected_error(ex)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Use this to dump an an exception to output. useful
|
85
|
+
# if an error occurs in the error handling itself.
|
86
|
+
def self.dump_unexpected_error(e)
|
87
|
+
Terminal.output "INTERNAL ERROR"
|
88
|
+
Terminal.output "-=" * 30
|
89
|
+
Terminal.output "Message:"
|
90
|
+
Terminal.output e.message if e.respond_to?(:message)
|
91
|
+
Terminal.output "Backtrace:"
|
92
|
+
Terminal.output e.backtrace if e.respond_to?(:backtrace)
|
93
|
+
Terminal.output "=-" * 30
|
94
|
+
end
|
95
|
+
|
96
|
+
def initialize(wrapper: nil, config: nil, exception: nil)
|
97
|
+
@exception = exception || wrapper.contained_exception
|
98
|
+
@target_host = wrapper.target_host || target_host
|
99
|
+
@command = @exception.respond_to?(:command) ? @exception.command : nil
|
100
|
+
@pastel = Pastel.new
|
101
|
+
@content = StringIO.new
|
102
|
+
@config = config
|
103
|
+
@id = if @exception.kind_of? ChefCore::Error
|
104
|
+
@exception.id
|
105
|
+
else
|
106
|
+
DEFAULT_ERROR_NO
|
107
|
+
end
|
108
|
+
@translation = ChefCore::Text::ErrorTranslation.new(id)
|
109
|
+
rescue => e
|
110
|
+
ErrorPrinter.dump_unexpected_error(e)
|
111
|
+
exit! 128
|
112
|
+
end
|
113
|
+
|
114
|
+
def format_error
|
115
|
+
if translation.decorations
|
116
|
+
format_decorated
|
117
|
+
else
|
118
|
+
format_undecorated
|
119
|
+
end
|
120
|
+
@content.string
|
121
|
+
end
|
122
|
+
|
123
|
+
def format_undecorated
|
124
|
+
@content << "\n"
|
125
|
+
@content << format_body()
|
126
|
+
if @command
|
127
|
+
@content << "\n"
|
128
|
+
@content << @command.usage
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def format_decorated
|
133
|
+
@content << "\n"
|
134
|
+
@content << format_header()
|
135
|
+
@content << "\n\n"
|
136
|
+
@content << format_body()
|
137
|
+
@content << "\n"
|
138
|
+
@content << format_footer()
|
139
|
+
@content << "\n"
|
140
|
+
end
|
141
|
+
|
142
|
+
def format_header
|
143
|
+
pastel.decorate(@id, :bold)
|
144
|
+
end
|
145
|
+
|
146
|
+
def format_body
|
147
|
+
if exception.kind_of? ChefCore::Error
|
148
|
+
format_workstation_exception
|
149
|
+
elsif exception.kind_of? Train::Error
|
150
|
+
format_train_exception
|
151
|
+
else
|
152
|
+
format_other_exception
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def format_footer
|
157
|
+
if translation.log
|
158
|
+
if translation.stack
|
159
|
+
t.footer.both(config[:log_location], config[:stack_trace_path])
|
160
|
+
else
|
161
|
+
t.footer.log_only(config[:log_location])
|
162
|
+
end
|
163
|
+
else
|
164
|
+
if translation.stack
|
165
|
+
t.footer.stack_only
|
166
|
+
else
|
167
|
+
t.footer.neither
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def add_backtrace_header(out, args)
|
173
|
+
out.write("\n#{"-" * 80}\n")
|
174
|
+
out.print("#{Time.now}: Error encountered while running the following:\n")
|
175
|
+
out.print(" #{args.join(' ')}\n")
|
176
|
+
out.print("Backtrace:\n")
|
177
|
+
end
|
178
|
+
|
179
|
+
def save_backtrace(output, config)
|
180
|
+
File.open(config[:stack_trace_path], "ab+") do |f|
|
181
|
+
f.write(output.string)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def self.error_summary(e)
|
186
|
+
if e.kind_of? ChefCore::Error
|
187
|
+
# By convention, all of our defined messages have a short summary on the first line.
|
188
|
+
ChefCore::Text.errors.send(e.id).text(*e.params).split("\n").first
|
189
|
+
elsif e.kind_of? String
|
190
|
+
e
|
191
|
+
else
|
192
|
+
if e.respond_to? :message
|
193
|
+
e.message
|
194
|
+
else
|
195
|
+
ChefCore::Text.errors.UNKNOWN
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def format_workstation_exception
|
201
|
+
params = exception.params
|
202
|
+
t.send(@id).text(*params)
|
203
|
+
end
|
204
|
+
|
205
|
+
# TODO this gets moved to trainerrormapper or simply removed since
|
206
|
+
# many of these issues are now handled in the RemoteTarget::ConnectionFailure
|
207
|
+
def format_train_exception
|
208
|
+
backend, host = formatted_host()
|
209
|
+
if host.nil?
|
210
|
+
t.CHEFTRN002.text(exception.message)
|
211
|
+
else
|
212
|
+
t.CHEFTRN001.text(backend, host, exception.message)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def format_other_exception
|
217
|
+
t.send(DEFAULT_ERROR_NO).text(exception.message)
|
218
|
+
end
|
219
|
+
|
220
|
+
def formatted_host
|
221
|
+
return nil if target_host.nil?
|
222
|
+
cfg = target_host.config
|
223
|
+
port = cfg[:port].nil? ? "" : ":#{cfg[:port]}"
|
224
|
+
user = cfg[:user].nil? ? "" : "#{cfg[:user]}@"
|
225
|
+
"#{user}#{target_host.hostname}#{port}"
|
226
|
+
end
|
227
|
+
|
228
|
+
# mostly copied from
|
229
|
+
# https://gist.github.com/stanio/13d74294ca1868fed7fb
|
230
|
+
def add_formatted_backtrace(out)
|
231
|
+
_format_single(out, exception)
|
232
|
+
current_backtrace = exception.backtrace
|
233
|
+
cause = exception.cause
|
234
|
+
until cause.nil?
|
235
|
+
cause_trace = _unique_trace(cause.backtrace.to_a, current_backtrace)
|
236
|
+
out.print "Caused by: "
|
237
|
+
_format_single(out, cause, cause_trace)
|
238
|
+
backtrace_length = cause.backtrace.length
|
239
|
+
if backtrace_length > cause_trace.length
|
240
|
+
out.print "\t... #{backtrace_length - cause_trace.length} more"
|
241
|
+
end
|
242
|
+
out.print "\n"
|
243
|
+
current_backtrace = cause.backtrace
|
244
|
+
cause = cause.cause
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def _format_single(out, exception, backtrace = nil)
|
249
|
+
out.puts "#{exception.class}: #{exception.message}"
|
250
|
+
backtrace ||= exception.backtrace.to_a
|
251
|
+
backtrace.each { |trace| out.puts "\t#{trace}" }
|
252
|
+
end
|
253
|
+
|
254
|
+
def _unique_trace(backtrace1, backtrace2)
|
255
|
+
i = 1
|
256
|
+
while i <= backtrace1.size && i <= backtrace2.size
|
257
|
+
break if backtrace1[-i] != backtrace2[-i]
|
258
|
+
i += 1
|
259
|
+
end
|
260
|
+
backtrace1[0..-i]
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
#
|
2
|
+
# Copyright:: Copyright (c) 2017 Chef Software Inc.
|
3
|
+
# License:: Apache License, Version 2.0
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
|
18
|
+
module ChefCore
|
19
|
+
module CLIUX
|
20
|
+
module UI
|
21
|
+
class PlainTextElement
|
22
|
+
def initialize(format, opts)
|
23
|
+
@orig_format = format
|
24
|
+
@format = format
|
25
|
+
@output = opts[:output]
|
26
|
+
end
|
27
|
+
|
28
|
+
def run(&block)
|
29
|
+
yield
|
30
|
+
end
|
31
|
+
|
32
|
+
def update(params)
|
33
|
+
# Some of this is particular to our usage -
|
34
|
+
# prefix does not cause a text update, but does
|
35
|
+
# change the prefix for future messages.
|
36
|
+
if params.key?(:prefix)
|
37
|
+
@format = @orig_format.gsub(":prefix", params[:prefix])
|
38
|
+
return
|
39
|
+
end
|
40
|
+
|
41
|
+
if @succ
|
42
|
+
ind = "OK"
|
43
|
+
@succ = false
|
44
|
+
log_method = :info
|
45
|
+
elsif @err
|
46
|
+
ind = "ERR"
|
47
|
+
@err = false
|
48
|
+
log_method = :error
|
49
|
+
else
|
50
|
+
log_method = :debug
|
51
|
+
ind = " - "
|
52
|
+
end
|
53
|
+
|
54
|
+
# Since this is a generic type, we can replace any component
|
55
|
+
# name in this regex - but for now :spinner is the only component
|
56
|
+
# we're standing in for.
|
57
|
+
msg = @format.gsub(/:spinner/, ind)
|
58
|
+
params.each_pair do |k, v|
|
59
|
+
msg.gsub!(/:#{k}/, v)
|
60
|
+
end
|
61
|
+
ChefCore::Log.send(log_method, msg)
|
62
|
+
@output.puts(msg)
|
63
|
+
end
|
64
|
+
|
65
|
+
def error
|
66
|
+
@err = true
|
67
|
+
@succ = false
|
68
|
+
end
|
69
|
+
|
70
|
+
def success
|
71
|
+
@succ = true
|
72
|
+
@err = false
|
73
|
+
end
|
74
|
+
|
75
|
+
def auto_spin
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
#
|
2
|
+
# Copyright:: Copyright (c) 2017 Chef Software Inc.
|
3
|
+
# License:: Apache License, Version 2.0
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
require "thread"
|
18
|
+
require "chef_core/cliux/ui/plain_text_element"
|
19
|
+
|
20
|
+
module ChefCore
|
21
|
+
module CLIUX
|
22
|
+
module UI
|
23
|
+
class PlainTextHeader
|
24
|
+
def initialize(format, opts)
|
25
|
+
@format = format
|
26
|
+
@output = opts[:output]
|
27
|
+
@children = {}
|
28
|
+
@threads = []
|
29
|
+
end
|
30
|
+
|
31
|
+
def register(child_format, child_opts, &block)
|
32
|
+
child_opts[:output] = @output
|
33
|
+
child = PlainTextElement.new(child_format, child_opts)
|
34
|
+
@children[child] = block
|
35
|
+
end
|
36
|
+
|
37
|
+
def auto_spin
|
38
|
+
msg = @format.gsub(/:spinner/, " HEADER ")
|
39
|
+
@output.puts(msg)
|
40
|
+
@children.each do |child, block|
|
41
|
+
@threads << Thread.new { block.call(child) }
|
42
|
+
end
|
43
|
+
@threads.each { |thr| thr.join }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
#
|
2
|
+
# Copyright:: Copyright (c) 2018 Chef Software Inc.
|
3
|
+
# License:: Apache License, Version 2.0
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
|
18
|
+
module ChefCore
|
19
|
+
module CLIUX
|
20
|
+
module UI
|
21
|
+
class Terminal
|
22
|
+
class Job
|
23
|
+
attr_reader :proc, :prefix, :target_host, :exception
|
24
|
+
def initialize(prefix, target_host, &block)
|
25
|
+
@proc = block
|
26
|
+
@prefix = prefix
|
27
|
+
@target_host = target_host
|
28
|
+
@error = nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def run(reporter)
|
32
|
+
@proc.call(reporter)
|
33
|
+
rescue => e
|
34
|
+
reporter.error(e.to_s)
|
35
|
+
@exception = e
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
#
|
2
|
+
# Copyright:: Copyright (c) 2018 Chef Software Inc.
|
3
|
+
# License:: Apache License, Version 2.0
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
|
18
|
+
require "tty-spinner"
|
19
|
+
require "tty-cursor"
|
20
|
+
require "chef_core/log"
|
21
|
+
require "chef_core/cliux/status_reporter"
|
22
|
+
require "chef_core/cliux/ui/plain_text_element"
|
23
|
+
require "chef_core/cliux/ui/plain_text_header"
|
24
|
+
require "chef_core/cliux/ui/terminal/job"
|
25
|
+
|
26
|
+
module ChefCore
|
27
|
+
module CLIUX
|
28
|
+
module UI
|
29
|
+
class Terminal
|
30
|
+
class << self
|
31
|
+
# To support matching in test
|
32
|
+
attr_accessor :location, :enable_spinners
|
33
|
+
|
34
|
+
def init(location, enable_spinners: false)
|
35
|
+
@enable_spinners = enable_spinners
|
36
|
+
@location = location
|
37
|
+
end
|
38
|
+
|
39
|
+
def write(msg)
|
40
|
+
@location.write(msg)
|
41
|
+
end
|
42
|
+
|
43
|
+
def output(msg)
|
44
|
+
@location.puts msg
|
45
|
+
end
|
46
|
+
|
47
|
+
def render_parallel_jobs(header, jobs)
|
48
|
+
# Do not indent the topmost 'parent' spinner, but do indent child spinners
|
49
|
+
indent_style = { top: "",
|
50
|
+
middle: TTY::Spinner::Multi::DEFAULT_INSET[:middle],
|
51
|
+
bottom: TTY::Spinner::Multi::DEFAULT_INSET[:bottom] }
|
52
|
+
# @option options [Hash] :style
|
53
|
+
# keys :top :middle and :bottom can contain Strings that are used to
|
54
|
+
# indent the spinners. Ignored if message is blank
|
55
|
+
multispinner = get_multispinner.new("[:spinner] #{header}",
|
56
|
+
output: @location,
|
57
|
+
hide_cursor: true,
|
58
|
+
style: indent_style)
|
59
|
+
jobs.each do |job|
|
60
|
+
multispinner.register(spinner_prefix(job.prefix), hide_cursor: true) do |spinner|
|
61
|
+
reporter = StatusReporter.new(spinner, prefix: job.prefix, key: :status)
|
62
|
+
job.run(reporter)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
multispinner.auto_spin
|
66
|
+
ensure
|
67
|
+
# Spinners hide the cursor for better appearance, so we need to make sure
|
68
|
+
# we always bring it back
|
69
|
+
show_cursor
|
70
|
+
end
|
71
|
+
|
72
|
+
def render_job(initial_msg, job)
|
73
|
+
# TODO why do we have to pass prefix to both the spinner and the reporter?
|
74
|
+
spinner = get_spinner.new(spinner_prefix(job.prefix), output: @location, hide_cursor: true)
|
75
|
+
reporter = StatusReporter.new(spinner, prefix: job.prefix, key: :status)
|
76
|
+
reporter.update(initial_msg)
|
77
|
+
spinner.auto_spin
|
78
|
+
job.run(reporter)
|
79
|
+
end
|
80
|
+
|
81
|
+
def spinner_prefix(prefix)
|
82
|
+
spinner_msg = "[:spinner] "
|
83
|
+
spinner_msg += ":prefix " unless prefix.empty?
|
84
|
+
spinner_msg + ":status"
|
85
|
+
end
|
86
|
+
|
87
|
+
def get_multispinner
|
88
|
+
enable_spinners ? TTY::Spinner::Multi : PlainTextHeader
|
89
|
+
end
|
90
|
+
|
91
|
+
def get_spinner
|
92
|
+
# TODO bootstrap - these was as below, which seems backwards:
|
93
|
+
enable_spinners ? TTY::Spinner : PlainTextElement
|
94
|
+
end
|
95
|
+
|
96
|
+
def show_cursor
|
97
|
+
TTY::Cursor.show()
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
#
|
2
|
+
# Copyright:: Copyright (c) 2017 Chef Software Inc.
|
3
|
+
# License:: Apache License, Version 2.0
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
|
18
|
+
module ChefCore
|
19
|
+
class Error < StandardError
|
20
|
+
attr_reader :id, :params
|
21
|
+
def initialize(id, *params)
|
22
|
+
@id = id
|
23
|
+
@params = params || []
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
class WrappedError < StandardError
|
29
|
+
attr_accessor :target_host, :contained_exception
|
30
|
+
def initialize(e, target_host)
|
31
|
+
super(e.message)
|
32
|
+
@contained_exception = e
|
33
|
+
@target_host = target_host
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class MultiJobFailure < Error
|
38
|
+
attr_reader :jobs
|
39
|
+
def initialize(jobs)
|
40
|
+
super("CHEFMULTI001")
|
41
|
+
@jobs = jobs
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Provide a base type for internal usage errors that should not leak out
|
46
|
+
# but may anyway.
|
47
|
+
class APIError < Error
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module ChefCore
|
2
|
+
module Errors
|
3
|
+
# Provides mappings of common errors that we don't explicitly
|
4
|
+
# handle, but can offer expanded help text around.
|
5
|
+
class StandardErrorResolver
|
6
|
+
def self.resolve_exception(exception)
|
7
|
+
deps
|
8
|
+
case exception
|
9
|
+
when OpenSSL::SSL::SSLError
|
10
|
+
if exception.message =~ /SSL.*verify failed.*/
|
11
|
+
id = "CHEFNET002"
|
12
|
+
end
|
13
|
+
when SocketError then id = "CHEFNET001"
|
14
|
+
end
|
15
|
+
if id.nil?
|
16
|
+
exception
|
17
|
+
else
|
18
|
+
ChefCore::Error.new(id, exception.message)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.wrap_exception(original, target_host = nil)
|
23
|
+
resolved_exception = resolve_exception(original)
|
24
|
+
WrappedError.new(resolved_exception, target_host)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.unwrap_exception(wrapper)
|
28
|
+
resolve_exception(wrapper.contained_exception)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.deps
|
32
|
+
# Avoid loading additional includes until they're needed
|
33
|
+
require "socket"
|
34
|
+
require "openssl"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
#
|
2
|
+
# Copyright:: Copyright (c) 2017 Chef Software Inc.
|
3
|
+
# License:: Apache License, Version 2.0
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
|
18
|
+
require "net/http"
|
19
|
+
require "uri"
|
20
|
+
|
21
|
+
module ChefCore
|
22
|
+
class FileFetcher
|
23
|
+
class << self
|
24
|
+
# Simple fetcher of an http(s) url. Returns the local path
|
25
|
+
# of the downloaded file.
|
26
|
+
def fetch(cache_path, path)
|
27
|
+
FileUtils.mkdir_p(cache_path)
|
28
|
+
url = URI.parse(path)
|
29
|
+
name = File.basename(url.path)
|
30
|
+
local_path = File.join(cache_path, name)
|
31
|
+
|
32
|
+
# TODO header check for size or checksum?
|
33
|
+
return local_path if File.exist?(local_path)
|
34
|
+
|
35
|
+
download_file(url, local_path)
|
36
|
+
local_path
|
37
|
+
end
|
38
|
+
|
39
|
+
def download_file(url, local_path)
|
40
|
+
temp_path = "#{local_path}.downloading"
|
41
|
+
file = open(temp_path, "wb")
|
42
|
+
ChefCore::Log.debug "Downloading: #{temp_path}"
|
43
|
+
Net::HTTP.start(url.host) do |http|
|
44
|
+
begin
|
45
|
+
http.request_get(url.path) do |resp|
|
46
|
+
resp.read_body do |segment|
|
47
|
+
file.write(segment)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
rescue e
|
51
|
+
@error = true
|
52
|
+
raise
|
53
|
+
ensure
|
54
|
+
file.close()
|
55
|
+
# If any failures occurred, don't risk keeping
|
56
|
+
# an incomplete download that we'll see as 'cached'
|
57
|
+
if @error
|
58
|
+
FileUtils.rm_f(temp_path)
|
59
|
+
else
|
60
|
+
FileUtils.mv(temp_path, local_path)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|