chef-apply 0.1.2 → 0.1.15
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 +4 -4
- data/Gemfile.lock +14 -14
- data/README.md +29 -14
- data/i18n/en.yml +16 -340
- data/i18n/errors/en.yml +341 -0
- data/lib/chef_apply/action/base.rb +5 -1
- data/lib/chef_apply/action/generate_local_policy.rb +59 -0
- data/lib/chef_apply/action/generate_temp_cookbook.rb +86 -0
- data/lib/chef_apply/action/install_chef/base.rb +38 -16
- data/lib/chef_apply/cli.rb +104 -257
- data/lib/chef_apply/cli/help.rb +69 -0
- data/lib/chef_apply/cli/options.rb +147 -0
- data/lib/chef_apply/cli/validation.rb +99 -0
- data/lib/chef_apply/error.rb +4 -43
- data/lib/chef_apply/errors/standard_error_resolver.rb +45 -0
- data/lib/chef_apply/startup.rb +22 -0
- data/lib/chef_apply/temp_cookbook.rb +23 -12
- data/lib/chef_apply/text.rb +13 -45
- data/lib/chef_apply/text/text_wrapper.rb +86 -0
- data/lib/chef_apply/ui/error_printer.rb +3 -2
- data/lib/chef_apply/ui/terminal.rb +10 -8
- data/lib/chef_apply/version.rb +1 -1
- metadata +10 -3
- data/lib/chef_apply/cli_options.rb +0 -145
@@ -20,24 +20,15 @@ require "fileutils"
|
|
20
20
|
|
21
21
|
module ChefApply::Action::InstallChef
|
22
22
|
class Base < ChefApply::Action::Base
|
23
|
-
|
23
|
+
MIN_14_VERSION = Gem::Version.new("14.1.1")
|
24
|
+
MIN_13_VERSION = Gem::Version.new("13.10.0")
|
24
25
|
|
25
26
|
def perform_action
|
26
|
-
if target_host
|
27
|
+
if check_minimum_chef_version!(target_host) == :minimum_version_met
|
27
28
|
notify(:already_installed)
|
28
|
-
|
29
|
+
else
|
30
|
+
perform_local_install
|
29
31
|
end
|
30
|
-
raise ClientOutdated.new(target_host.installed_chef_version, MIN_CHEF_VERSION)
|
31
|
-
# NOTE: 2018-05-10 below is an intentionally dead code path that
|
32
|
-
# will get re-visited once we determine how we want automatic
|
33
|
-
# upgrades to behave.
|
34
|
-
# @upgrading = true
|
35
|
-
# perform_local_install
|
36
|
-
rescue ChefApply::TargetHost::ChefNotInstalled
|
37
|
-
if config[:check_only]
|
38
|
-
raise ClientNotInstalled.new()
|
39
|
-
end
|
40
|
-
perform_local_install
|
41
32
|
end
|
42
33
|
|
43
34
|
def name
|
@@ -116,6 +107,31 @@ module ChefApply::Action::InstallChef
|
|
116
107
|
remote_path
|
117
108
|
end
|
118
109
|
|
110
|
+
def check_minimum_chef_version!(target)
|
111
|
+
begin
|
112
|
+
installed_version = target.installed_chef_version
|
113
|
+
rescue ChefApply::TargetHost::ChefNotInstalled
|
114
|
+
if config[:check_only]
|
115
|
+
raise ClientNotInstalled.new()
|
116
|
+
end
|
117
|
+
return :client_not_installed
|
118
|
+
end
|
119
|
+
|
120
|
+
case
|
121
|
+
when installed_version >= Gem::Version.new("14.0.0") && installed_version < MIN_14_VERSION
|
122
|
+
raise Client14Outdated.new(installed_version, MIN_14_VERSION)
|
123
|
+
when installed_version >= Gem::Version.new("13.0.0") && installed_version < MIN_13_VERSION
|
124
|
+
raise Client13Outdated.new(installed_version, MIN_13_VERSION, MIN_14_VERSION)
|
125
|
+
when installed_version < Gem::Version.new("13.0.0")
|
126
|
+
# If they have Chef < 13.0.0 installed we want to show them the easiest upgrade path -
|
127
|
+
# Chef 13 first and then Chef 14 since most customers cannot make the leap directly
|
128
|
+
# to 14.
|
129
|
+
raise Client13Outdated.new(installed_version, MIN_13_VERSION, MIN_14_VERSION)
|
130
|
+
end
|
131
|
+
|
132
|
+
:minimum_version_met
|
133
|
+
end
|
134
|
+
|
119
135
|
def setup_remote_temp_path
|
120
136
|
raise NotImplementedError
|
121
137
|
end
|
@@ -129,9 +145,15 @@ module ChefApply::Action::InstallChef
|
|
129
145
|
def initialize(); super("CHEFINS002"); end
|
130
146
|
end
|
131
147
|
|
132
|
-
class
|
148
|
+
class Client13Outdated < ChefApply::ErrorNoLogs
|
149
|
+
def initialize(current_version, min_13_version, min_14_version)
|
150
|
+
super("CHEFINS003", current_version, min_13_version, min_14_version)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
class Client14Outdated < ChefApply::ErrorNoLogs
|
133
155
|
def initialize(current_version, target_version)
|
134
|
-
super("
|
156
|
+
super("CHEFINS004", current_version, target_version)
|
135
157
|
end
|
136
158
|
end
|
137
159
|
end
|
data/lib/chef_apply/cli.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
#
|
2
|
+
#p
|
2
3
|
# Copyright:: Copyright (c) 2018 Chef Software Inc.
|
3
4
|
# License:: Apache License, Version 2.0
|
4
5
|
#
|
@@ -15,30 +16,37 @@
|
|
15
16
|
# limitations under the License.
|
16
17
|
#
|
17
18
|
require "mixlib/cli"
|
18
|
-
|
19
|
+
|
20
|
+
require "chef_apply/config"
|
19
21
|
require "chef-config/config"
|
20
22
|
require "chef-config/logger"
|
21
23
|
|
22
|
-
require "chef_apply/
|
24
|
+
require "chef_apply/cli/validation"
|
25
|
+
require "chef_apply/cli/options"
|
26
|
+
require "chef_apply/cli/help"
|
23
27
|
require "chef_apply/action/converge_target"
|
28
|
+
require "chef_apply/action/generate_local_policy"
|
29
|
+
require "chef_apply/action/generate_temp_cookbook"
|
24
30
|
require "chef_apply/action/install_chef"
|
25
|
-
require "chef_apply/config"
|
26
31
|
require "chef_apply/error"
|
27
32
|
require "chef_apply/log"
|
28
|
-
require "chef_apply/recipe_lookup"
|
29
33
|
require "chef_apply/target_host"
|
30
34
|
require "chef_apply/target_resolver"
|
31
35
|
require "chef_apply/telemeter"
|
32
|
-
require "chef_apply/telemeter/sender"
|
33
|
-
require "chef_apply/temp_cookbook"
|
34
|
-
require "chef_apply/ui/terminal"
|
35
36
|
require "chef_apply/ui/error_printer"
|
36
|
-
require "chef_apply/
|
37
|
+
require "chef_apply/ui/terminal"
|
37
38
|
|
38
39
|
module ChefApply
|
39
40
|
class CLI
|
41
|
+
attr_reader :temp_cookbook, :archive_file_location, :target_hosts
|
42
|
+
|
40
43
|
include Mixlib::CLI
|
41
|
-
|
44
|
+
# Pulls in the CLI options and flags we have defined for this command.
|
45
|
+
include ChefApply::CLI::Options
|
46
|
+
# Argument validation and parsing behaviors
|
47
|
+
include ChefApply::CLI::Validation
|
48
|
+
# Help and version formatting
|
49
|
+
include ChefApply::CLI::Help
|
42
50
|
|
43
51
|
RC_OK = 0
|
44
52
|
RC_COMMAND_FAILED = 1
|
@@ -98,27 +106,11 @@ module ChefApply
|
|
98
106
|
show_version
|
99
107
|
else
|
100
108
|
validate_params(cli_arguments)
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
parsed_options).targets
|
105
|
-
temp_cookbook, initial_status_msg = generate_temp_cookbook(cli_arguments)
|
106
|
-
local_policy_path = nil
|
107
|
-
UI::Terminal.render_job(TS.generate_policyfile.generating) do |reporter|
|
108
|
-
local_policy_path = create_local_policy(temp_cookbook)
|
109
|
-
reporter.success(TS.generate_policyfile.success)
|
110
|
-
end
|
111
|
-
if target_hosts.length == 1
|
112
|
-
# Note: UX discussed determined that when running with a single target,
|
113
|
-
# we'll use multiple lines to display status for the target.
|
114
|
-
run_single_target(initial_status_msg, target_hosts[0], local_policy_path)
|
115
|
-
else
|
116
|
-
@multi_target = true
|
117
|
-
# Multi-target will use one line per target.
|
118
|
-
run_multi_target(initial_status_msg, target_hosts, local_policy_path)
|
119
|
-
end
|
109
|
+
target_hosts = resolve_targets(cli_arguments.shift, parsed_options)
|
110
|
+
render_cookbook_setup(cli_arguments)
|
111
|
+
render_converge(target_hosts)
|
120
112
|
end
|
121
|
-
rescue OptionParser::InvalidOption => e
|
113
|
+
rescue OptionParser::InvalidOption => e # from parse_options
|
122
114
|
# Using nil here is a bit gross but it prevents usage from printing.
|
123
115
|
ove = OptionValidationError.new("CHEFVAL010", nil,
|
124
116
|
e.message.split(":")[1].strip, # only want the flag
|
@@ -131,181 +123,47 @@ module ChefApply
|
|
131
123
|
temp_cookbook.delete unless temp_cookbook.nil?
|
132
124
|
end
|
133
125
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
if reporter.nil?
|
139
|
-
UI::Terminal.render_job(connect_message, prefix: "[#{target_host.config[:host]}]") do |rep|
|
140
|
-
do_connect(target_host, rep, :success)
|
141
|
-
end
|
142
|
-
else
|
143
|
-
reporter.update(connect_message)
|
144
|
-
do_connect(target_host, reporter, :update)
|
145
|
-
end
|
146
|
-
target_host
|
126
|
+
def resolve_targets(host_spec, opts)
|
127
|
+
@target_hosts = TargetResolver.new(host_spec,
|
128
|
+
opts.delete(:protocol),
|
129
|
+
opts).targets
|
147
130
|
end
|
148
131
|
|
149
|
-
def
|
150
|
-
|
151
|
-
|
152
|
-
UI::Terminal.render_job(TS.install_chef.verifying, prefix: prefix) do |reporter|
|
153
|
-
install(target_host, reporter)
|
132
|
+
def render_cookbook_setup(arguments)
|
133
|
+
UI::Terminal.render_job(TS.generate_temp_cookbook.generating) do |reporter|
|
134
|
+
@temp_cookbook = generate_temp_cookbook(arguments, reporter)
|
154
135
|
end
|
155
|
-
UI::Terminal.render_job(
|
156
|
-
|
136
|
+
UI::Terminal.render_job(TS.generate_temp_cookbook.generating) do |reporter|
|
137
|
+
@archive_file_location = generate_local_policy(reporter)
|
157
138
|
end
|
158
139
|
end
|
159
140
|
|
160
|
-
def
|
161
|
-
# Our multi-host UX does not show a line item per action,
|
162
|
-
# but rather a line-item per connection.
|
141
|
+
def render_converge(target_hosts)
|
163
142
|
jobs = target_hosts.map do |target_host|
|
164
|
-
#
|
143
|
+
# Each block will run in its own thread during render.
|
165
144
|
UI::Terminal::Job.new("[#{target_host.hostname}]", target_host) do |reporter|
|
166
145
|
connect_target(target_host, reporter)
|
167
|
-
reporter.update(TS.install_chef.verifying)
|
168
146
|
install(target_host, reporter)
|
169
|
-
reporter
|
170
|
-
converge(reporter, local_policy_path, target_host)
|
147
|
+
converge(reporter, archive_file_location, target_host)
|
171
148
|
end
|
172
149
|
end
|
173
|
-
|
150
|
+
header = TS.converge.header(target_hosts.length, temp_cookbook.descriptor, temp_cookbook.from)
|
151
|
+
UI::Terminal.render_parallel_jobs(header, jobs)
|
174
152
|
handle_job_failures(jobs)
|
175
153
|
end
|
176
154
|
|
177
|
-
#
|
178
|
-
#
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
if params.size < 2
|
184
|
-
raise OptionValidationError.new("CHEFVAL002", self)
|
185
|
-
end
|
186
|
-
if params.size == 2
|
187
|
-
# Trying to specify a recipe to run remotely, no properties
|
188
|
-
cb = params[1]
|
189
|
-
if File.exist?(cb)
|
190
|
-
# This is a path specification, and we know it is valid
|
191
|
-
elsif cb =~ /^#{CB_MATCHER}$/ || cb =~ /^#{CB_MATCHER}::#{CB_MATCHER}$/
|
192
|
-
# They are specifying a cookbook as 'cb_name' or 'cb_name::recipe'
|
193
|
-
else
|
194
|
-
raise OptionValidationError.new("CHEFVAL004", self, cb)
|
195
|
-
end
|
196
|
-
elsif params.size >= 3
|
197
|
-
properties = params[3..-1]
|
198
|
-
properties.each do |property|
|
199
|
-
unless property =~ PROPERTY_MATCHER
|
200
|
-
raise OptionValidationError.new("CHEFVAL003", self, property)
|
201
|
-
end
|
202
|
-
end
|
203
|
-
end
|
204
|
-
end
|
205
|
-
|
206
|
-
# Now that we are leveraging Chef locally we want to perform some initial setup of it
|
207
|
-
def configure_chef
|
208
|
-
ChefConfig.logger = ChefApply::Log
|
209
|
-
# Setting the config isn't enough, we need to ensure the logger is initialized
|
210
|
-
# or automatic initialization will still go to stdout
|
211
|
-
Chef::Log.init(ChefApply::Log)
|
212
|
-
Chef::Log.level = ChefApply::Log.level
|
213
|
-
end
|
214
|
-
|
215
|
-
def format_properties(string_props)
|
216
|
-
properties = {}
|
217
|
-
string_props.each do |a|
|
218
|
-
key, value = PROPERTY_MATCHER.match(a)[1..-1]
|
219
|
-
value = transform_property_value(value)
|
220
|
-
properties[key] = value
|
221
|
-
end
|
222
|
-
properties
|
223
|
-
end
|
224
|
-
|
225
|
-
# Incoming properties are always read as a string from the command line.
|
226
|
-
# Depending on their type we should transform them so we do not try and pass
|
227
|
-
# a string to a resource property that expects an integer or boolean.
|
228
|
-
def transform_property_value(value)
|
229
|
-
case value
|
230
|
-
when /^0/
|
231
|
-
# when it is a zero leading value like "0777" don't turn
|
232
|
-
# it into a number (this is a mode flag)
|
233
|
-
value
|
234
|
-
when /^\d+$/
|
235
|
-
value.to_i
|
236
|
-
when /(^(\d+)(\.)?(\d+)?)|(^(\d+)?(\.)(\d+))/
|
237
|
-
value.to_f
|
238
|
-
when /true/i
|
239
|
-
true
|
240
|
-
when /false/i
|
241
|
-
false
|
242
|
-
else
|
243
|
-
value
|
244
|
-
end
|
245
|
-
end
|
246
|
-
|
247
|
-
# The user will either specify a single resource on the command line, or a recipe.
|
248
|
-
# We need to parse out those two different situations
|
249
|
-
def generate_temp_cookbook(cli_arguments)
|
250
|
-
temp_cookbook = TempCookbook.new
|
251
|
-
if recipe_strategy?(cli_arguments)
|
252
|
-
recipe_specifier = cli_arguments.shift
|
253
|
-
ChefApply::Log.debug("Beginning to look for recipe specified as #{recipe_specifier}")
|
254
|
-
if File.file?(recipe_specifier)
|
255
|
-
ChefApply::Log.debug("#{recipe_specifier} is a valid path to a recipe")
|
256
|
-
recipe_path = recipe_specifier
|
257
|
-
else
|
258
|
-
rl = RecipeLookup.new(parsed_options[:cookbook_repo_paths])
|
259
|
-
cookbook_path_or_name, optional_recipe_name = rl.split(recipe_specifier)
|
260
|
-
cookbook = rl.load_cookbook(cookbook_path_or_name)
|
261
|
-
recipe_path = rl.find_recipe(cookbook, optional_recipe_name)
|
262
|
-
end
|
263
|
-
temp_cookbook.from_existing_recipe(recipe_path)
|
264
|
-
initial_status_msg = TS.converge.converging_recipe(recipe_specifier)
|
265
|
-
else
|
266
|
-
resource_type = cli_arguments.shift
|
267
|
-
resource_name = cli_arguments.shift
|
268
|
-
temp_cookbook.from_resource(resource_type, resource_name, format_properties(cli_arguments))
|
269
|
-
full_rs_name = "#{resource_type}[#{resource_name}]"
|
270
|
-
ChefApply::Log.debug("Converging resource #{full_rs_name} on target")
|
271
|
-
initial_status_msg = TS.converge.converging_resource(full_rs_name)
|
272
|
-
end
|
273
|
-
|
274
|
-
[temp_cookbook, initial_status_msg]
|
275
|
-
end
|
276
|
-
|
277
|
-
def recipe_strategy?(cli_arguments)
|
278
|
-
cli_arguments.size == 1
|
279
|
-
end
|
280
|
-
|
281
|
-
def create_local_policy(local_cookbook)
|
282
|
-
require "chef-dk/ui"
|
283
|
-
require "chef-dk/policyfile_services/export_repo"
|
284
|
-
require "chef-dk/policyfile_services/install"
|
285
|
-
policyfile_installer = ChefDK::PolicyfileServices::Install.new(
|
286
|
-
ui: ChefDK::UI.null(),
|
287
|
-
root_dir: local_cookbook.path
|
288
|
-
)
|
289
|
-
begin
|
290
|
-
policyfile_installer.run
|
291
|
-
rescue ChefDK::PolicyfileInstallError => e
|
292
|
-
raise PolicyfileInstallError.new(e)
|
293
|
-
end
|
294
|
-
lock_path = File.join(local_cookbook.path, "Policyfile.lock.json")
|
295
|
-
es = ChefDK::PolicyfileServices::ExportRepo.new(policyfile: lock_path,
|
296
|
-
root_dir: local_cookbook.path,
|
297
|
-
export_dir: File.join(local_cookbook.path, "export"),
|
298
|
-
archive: true,
|
299
|
-
force: true)
|
300
|
-
es.run
|
301
|
-
es.archive_file_location
|
155
|
+
# Accepts a target_host and establishes the connection to that host
|
156
|
+
# while providing visual feedback via the Terminal API.
|
157
|
+
def connect_target(target_host, reporter)
|
158
|
+
connect_message = T.status.connecting(target_host.user)
|
159
|
+
reporter.update(connect_message)
|
160
|
+
do_connect(target_host, reporter)
|
302
161
|
end
|
303
162
|
|
304
|
-
# Runs the InstallChef action and renders UI updates as
|
305
|
-
# the action reports back
|
306
163
|
def install(target_host, reporter)
|
307
|
-
installer = Action::InstallChef.instance_for_target(target_host, check_only: !parsed_options[:install])
|
308
164
|
context = TS.install_chef
|
165
|
+
reporter.update(context.verifying)
|
166
|
+
installer = Action::InstallChef.instance_for_target(target_host, check_only: !parsed_options[:install])
|
309
167
|
installer.run do |event, data|
|
310
168
|
case event
|
311
169
|
when :installing
|
@@ -320,39 +178,82 @@ module ChefApply
|
|
320
178
|
when :downloading
|
321
179
|
reporter.update(context.downloading)
|
322
180
|
when :already_installed
|
323
|
-
|
324
|
-
reporter.send(meth, context.already_present(target_host.installed_chef_version))
|
181
|
+
reporter.update(context.already_present(target_host.installed_chef_version))
|
325
182
|
when :install_complete
|
326
|
-
meth = @multi_target ? :update : :success
|
327
183
|
if installer.upgrading?
|
328
184
|
message = context.upgrade_success(target_host.installed_chef_version, installer.version_to_install)
|
329
185
|
else
|
330
186
|
message = context.install_success(installer.version_to_install)
|
331
187
|
end
|
332
|
-
reporter.
|
188
|
+
reporter.update(message)
|
189
|
+
else
|
190
|
+
handle_message(event, data, reporter)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
# Runs a GenerateCookbook action based on recipe/resource infoprovided
|
196
|
+
# and renders UI updates as the action reports back
|
197
|
+
def generate_temp_cookbook(arguments, reporter)
|
198
|
+
opts = if arguments.length == 1
|
199
|
+
{ recipe_spec: arguments.shift,
|
200
|
+
cookbook_repo_paths: parsed_options[:cookbook_repo_paths] }
|
201
|
+
else
|
202
|
+
{ resource_type: arguments.shift,
|
203
|
+
resource_name: arguments.shift,
|
204
|
+
resource_properties: properties_from_string(arguments) }
|
205
|
+
end
|
206
|
+
action = ChefApply::Action::GenerateTempCookbook.from_options(opts)
|
207
|
+
action.run do |event, data|
|
208
|
+
case event
|
209
|
+
when :generating
|
210
|
+
reporter.update(TS.generate_temp_cookbook.generating)
|
211
|
+
when :success
|
212
|
+
reporter.success(TS.generate_temp_cookbook.success)
|
213
|
+
else
|
214
|
+
handle_message(event, data, reporter)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
action.generated_cookbook
|
218
|
+
end
|
219
|
+
|
220
|
+
# Runs the GenerateLocalPolicy action and renders UI updates
|
221
|
+
# as the action reports back
|
222
|
+
def generate_local_policy(reporter)
|
223
|
+
action = Action::GenerateLocalPolicy.new(cookbook: temp_cookbook)
|
224
|
+
action.run do |event, data|
|
225
|
+
case event
|
226
|
+
when :generating
|
227
|
+
reporter.update(TS.generate_local_policy.generating)
|
228
|
+
when :exporting
|
229
|
+
reporter.update(TS.generate_local_policy.exporting)
|
230
|
+
when :success
|
231
|
+
reporter.success(TS.generate_local_policy.success)
|
333
232
|
else
|
334
233
|
handle_message(event, data, reporter)
|
335
234
|
end
|
336
235
|
end
|
236
|
+
action.archive_file_location
|
337
237
|
end
|
338
238
|
|
339
239
|
# Runs the Converge action and renders UI updates as
|
340
240
|
# the action reports back
|
341
241
|
def converge(reporter, local_policy_path, target_host)
|
242
|
+
reporter.update(TS.converge.converging(temp_cookbook.descriptor))
|
342
243
|
converge_args = { local_policy_path: local_policy_path, target_host: target_host }
|
343
244
|
converger = Action::ConvergeTarget.new(converge_args)
|
344
245
|
converger.run do |event, data|
|
345
246
|
case event
|
346
247
|
when :success
|
347
|
-
reporter.success(TS.converge.success)
|
248
|
+
reporter.success(TS.converge.success(temp_cookbook.descriptor))
|
348
249
|
when :converge_error
|
349
|
-
reporter.error(TS.converge.failure)
|
250
|
+
reporter.error(TS.converge.failure(temp_cookbook.descriptor))
|
350
251
|
when :creating_remote_policy
|
351
252
|
reporter.update(TS.converge.creating_remote_policy)
|
352
253
|
when :uploading_trusted_certs
|
353
254
|
reporter.update(TS.converge.uploading_trusted_certs)
|
354
255
|
when :running_chef
|
355
|
-
reporter.update(TS.converge.
|
256
|
+
reporter.update(TS.converge.converging(temp_cookbook.descriptor))
|
356
257
|
when :reboot
|
357
258
|
reporter.success(TS.converge.reboot)
|
358
259
|
else
|
@@ -362,13 +263,14 @@ module ChefApply
|
|
362
263
|
end
|
363
264
|
|
364
265
|
def handle_perform_error(e)
|
266
|
+
require "chef_apply/errors/standard_error_resolver"
|
365
267
|
id = e.respond_to?(:id) ? e.id : e.class.to_s
|
366
268
|
# TODO: This is currently sending host information for certain ssh errors
|
367
269
|
# post release we need to scrub this data. For now I'm redacting the
|
368
270
|
# whole message.
|
369
271
|
# message = e.respond_to?(:message) ? e.message : e.to_s
|
370
272
|
Telemeter.capture(:error, exception: { id: id, message: "redacted" })
|
371
|
-
wrapper = ChefApply::StandardErrorResolver.wrap_exception(e)
|
273
|
+
wrapper = ChefApply::Errors::StandardErrorResolver.wrap_exception(e)
|
372
274
|
capture_exception_backtrace(wrapper)
|
373
275
|
# Now that our housekeeping is done, allow user-facing handling/formatting
|
374
276
|
# in `run` to execute by re-raising
|
@@ -377,13 +279,16 @@ module ChefApply
|
|
377
279
|
|
378
280
|
# When running multiple jobs, exceptions are captured to the
|
379
281
|
# job to avoid interrupting other jobs in process. This function
|
380
|
-
# collects them and raises
|
381
|
-
#
|
382
|
-
# - if you're in the 'multi-job' path (eg, multiple targets) we handle
|
383
|
-
# all errors the same to provide a consistent UX when running with mulitiple targets.
|
282
|
+
# collects them and raises directly (in the case of just one job in the list)
|
283
|
+
# or raises a MultiJobFailure (when more than one job was being run)
|
384
284
|
def handle_job_failures(jobs)
|
385
285
|
failed_jobs = jobs.select { |j| !j.exception.nil? }
|
386
286
|
return if failed_jobs.empty?
|
287
|
+
if jobs.length == 1
|
288
|
+
# Don't provide a bad UX by showing a 'one or more jobs has failed'
|
289
|
+
# message when there was only one job.
|
290
|
+
raise jobs.first.exception
|
291
|
+
end
|
387
292
|
raise ChefApply::MultiJobFailure.new(failed_jobs)
|
388
293
|
end
|
389
294
|
|
@@ -399,72 +304,14 @@ module ChefApply
|
|
399
304
|
UI::ErrorPrinter.write_backtrace(e, @argv)
|
400
305
|
end
|
401
306
|
|
402
|
-
def
|
403
|
-
UI::Terminal.output format_help
|
404
|
-
end
|
405
|
-
|
406
|
-
def do_connect(target_host, reporter, update_method)
|
307
|
+
def do_connect(target_host, reporter)
|
407
308
|
target_host.connect!
|
408
|
-
reporter.
|
309
|
+
reporter.update(T.status.connected)
|
409
310
|
rescue StandardError => e
|
410
311
|
message = ChefApply::UI::ErrorPrinter.error_summary(e)
|
411
312
|
reporter.error(message)
|
412
313
|
raise
|
413
314
|
end
|
414
315
|
|
415
|
-
def format_help
|
416
|
-
help_text = banner.clone # This prevents us appending to the banner text
|
417
|
-
help_text << "\n"
|
418
|
-
help_text << format_flags
|
419
|
-
end
|
420
|
-
|
421
|
-
def format_flags
|
422
|
-
flag_text = "FLAGS:\n"
|
423
|
-
justify_length = 0
|
424
|
-
options.each_value do |spec|
|
425
|
-
justify_length = [justify_length, spec[:long].length + 4].max
|
426
|
-
end
|
427
|
-
options.sort.to_h.each_value do |flag_spec|
|
428
|
-
short = flag_spec[:short] || " "
|
429
|
-
short = short[0, 2] # We only want the flag portion, not the capture portion (if present)
|
430
|
-
if short == " "
|
431
|
-
short = " "
|
432
|
-
else
|
433
|
-
short = "#{short}, "
|
434
|
-
end
|
435
|
-
flags = "#{short}#{flag_spec[:long]}"
|
436
|
-
flag_text << " #{flags.ljust(justify_length)} "
|
437
|
-
ml_padding = " " * (justify_length + 8)
|
438
|
-
first = true
|
439
|
-
flag_spec[:description].split("\n").each do |d|
|
440
|
-
flag_text << ml_padding unless first
|
441
|
-
first = false
|
442
|
-
flag_text << "#{d}\n"
|
443
|
-
end
|
444
|
-
end
|
445
|
-
flag_text
|
446
|
-
end
|
447
|
-
|
448
|
-
def usage
|
449
|
-
T.usage
|
450
|
-
end
|
451
|
-
|
452
|
-
def show_version
|
453
|
-
UI::Terminal.output T.version.show(ChefApply::VERSION)
|
454
|
-
end
|
455
|
-
|
456
|
-
class OptionValidationError < ChefApply::ErrorNoLogs
|
457
|
-
attr_reader :command
|
458
|
-
def initialize(id, calling_command, *args)
|
459
|
-
super(id, *args)
|
460
|
-
# TODO - this is getting cumbersome - move them to constructor options hash in base
|
461
|
-
@decorate = false
|
462
|
-
@command = calling_command
|
463
|
-
end
|
464
|
-
end
|
465
|
-
|
466
|
-
class PolicyfileInstallError < ChefApply::Error
|
467
|
-
def initialize(cause_err); super("CHEFPOLICY001", cause_err.message); end
|
468
|
-
end
|
469
316
|
end
|
470
317
|
end
|