chef-apply 0.1.2 → 0.1.15
Sign up to get free protection for your applications and to get access to all the features.
- 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
|