bolt 2.17.0 → 2.22.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of bolt might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Puppetfile +3 -1
- data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +20 -9
- data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +123 -0
- data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +6 -0
- data/bolt-modules/dir/lib/puppet/functions/dir/children.rb +35 -0
- data/lib/bolt/applicator.rb +19 -14
- data/lib/bolt/apply_result.rb +1 -1
- data/lib/bolt/bolt_option_parser.rb +68 -13
- data/lib/bolt/catalog.rb +12 -3
- data/lib/bolt/cli.rb +232 -47
- data/lib/bolt/config.rb +34 -13
- data/lib/bolt/config/options.rb +16 -1
- data/lib/bolt/config/transport/options.rb +16 -10
- data/lib/bolt/config/transport/ssh.rb +24 -10
- data/lib/bolt/executor.rb +21 -0
- data/lib/bolt/inventory/group.rb +3 -2
- data/lib/bolt/inventory/inventory.rb +4 -3
- data/lib/bolt/logger.rb +21 -0
- data/lib/bolt/module.rb +2 -1
- data/lib/bolt/outputter/rainbow.rb +9 -2
- data/lib/bolt/pal.rb +8 -2
- data/lib/bolt/pal/yaml_plan/evaluator.rb +23 -2
- data/lib/bolt/pal/yaml_plan/step.rb +24 -2
- data/lib/bolt/pal/yaml_plan/step/download.rb +38 -0
- data/lib/bolt/pal/yaml_plan/step/message.rb +30 -0
- data/lib/bolt/pal/yaml_plan/step/upload.rb +3 -3
- data/lib/bolt/plugin/module.rb +2 -4
- data/lib/bolt/plugin/puppetdb.rb +3 -2
- data/lib/bolt/project.rb +25 -11
- data/lib/bolt/puppetdb/client.rb +2 -0
- data/lib/bolt/puppetdb/config.rb +16 -0
- data/lib/bolt/result.rb +7 -0
- data/lib/bolt/shell/bash.rb +24 -4
- data/lib/bolt/shell/powershell.rb +10 -4
- data/lib/bolt/shell/powershell/snippets.rb +15 -6
- data/lib/bolt/transport/base.rb +24 -0
- data/lib/bolt/transport/docker.rb +8 -0
- data/lib/bolt/transport/docker/connection.rb +20 -2
- data/lib/bolt/transport/local/connection.rb +14 -1
- data/lib/bolt/transport/orch.rb +12 -0
- data/lib/bolt/transport/simple.rb +6 -0
- data/lib/bolt/transport/ssh.rb +7 -1
- data/lib/bolt/transport/ssh/connection.rb +9 -1
- data/lib/bolt/transport/ssh/exec_connection.rb +23 -2
- data/lib/bolt/transport/winrm/connection.rb +118 -8
- data/lib/bolt/util.rb +26 -11
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/transport_app.rb +3 -2
- data/lib/bolt_spec/bolt_context.rb +7 -2
- data/lib/bolt_spec/plans.rb +15 -2
- data/lib/bolt_spec/plans/action_stubs.rb +2 -1
- data/lib/bolt_spec/plans/action_stubs/download_stub.rb +66 -0
- data/lib/bolt_spec/plans/mock_executor.rb +14 -1
- data/lib/bolt_spec/run.rb +22 -0
- data/libexec/bolt_catalog +3 -2
- data/modules/secure_env_vars/plans/init.pp +20 -0
- metadata +8 -2
data/lib/bolt/catalog.rb
CHANGED
@@ -76,7 +76,15 @@ module Bolt
|
|
76
76
|
target = request['target']
|
77
77
|
plan_vars = shadow_vars('plan', request['plan_vars'], target['facts'])
|
78
78
|
target_vars = shadow_vars('target', target['variables'], target['facts'])
|
79
|
-
|
79
|
+
|
80
|
+
# Merge plan vars with target vars, while maintaining the order of the plan
|
81
|
+
# vars. It's critical that the order of plan vars is not changed, as Puppet
|
82
|
+
# will deserialize the variables in the order they appear. Variables may
|
83
|
+
# contain local references to variables that appear earlier in a plan. If
|
84
|
+
# these variables are moved before the variable they reference, Puppet will
|
85
|
+
# be unable to deserialize the data and raise an error.
|
86
|
+
topscope_vars = target_vars.reject { |k, _v| plan_vars.key?(k) }.merge(plan_vars)
|
87
|
+
|
80
88
|
env_conf = { modulepath: request['modulepath'],
|
81
89
|
facts: target['facts'],
|
82
90
|
variables: topscope_vars }
|
@@ -138,9 +146,10 @@ module Bolt
|
|
138
146
|
# That means the apply body either a) consists of just a
|
139
147
|
# NodeDefinition, b) consists of a BlockExpression which may
|
140
148
|
# contain NodeDefinitions, or c) doesn't contain NodeDefinitions.
|
141
|
-
definitions =
|
149
|
+
definitions = case ast
|
150
|
+
when Puppet::Pops::Model::BlockExpression
|
142
151
|
ast.statements.select { |st| st.is_a?(Puppet::Pops::Model::NodeDefinition) }
|
143
|
-
|
152
|
+
when Puppet::Pops::Model::NodeDefinition
|
144
153
|
[ast]
|
145
154
|
else
|
146
155
|
[]
|
data/lib/bolt/cli.rb
CHANGED
@@ -31,8 +31,8 @@ module Bolt
|
|
31
31
|
COMMANDS = { 'command' => %w[run],
|
32
32
|
'script' => %w[run],
|
33
33
|
'task' => %w[show run],
|
34
|
-
'plan' => %w[show run convert],
|
35
|
-
'file' => %w[upload],
|
34
|
+
'plan' => %w[show run convert new],
|
35
|
+
'file' => %w[download upload],
|
36
36
|
'puppetfile' => %w[install show-modules generate-types],
|
37
37
|
'secret' => %w[encrypt decrypt createkeys],
|
38
38
|
'inventory' => %w[show],
|
@@ -46,7 +46,6 @@ module Bolt
|
|
46
46
|
Bolt::Logger.initialize_logging
|
47
47
|
@logger = Logging.logger[self]
|
48
48
|
@argv = argv
|
49
|
-
@config = Bolt::Config.default
|
50
49
|
@options = {}
|
51
50
|
end
|
52
51
|
|
@@ -76,7 +75,23 @@ module Bolt
|
|
76
75
|
end
|
77
76
|
private :help?
|
78
77
|
|
78
|
+
# Wrapper method that is called by the Bolt executable. Parses the command and
|
79
|
+
# then loads the project and config. Once config is loaded, it completes the
|
80
|
+
# setup process by configuring Bolt and issuing warnings.
|
81
|
+
#
|
82
|
+
# This separation is needed since the Bolt::Outputter class that normally handles
|
83
|
+
# printing errors relies on config being loaded. All setup that happens before
|
84
|
+
# config is loaded will have errors printed directly to stdout, while all errors
|
85
|
+
# raised after config is loaded are handled by the outputter.
|
79
86
|
def parse
|
87
|
+
parse_command
|
88
|
+
load_config
|
89
|
+
finalize_setup
|
90
|
+
end
|
91
|
+
|
92
|
+
# Parses the command and validates options. All errors that are raised here
|
93
|
+
# are not handled by the outputter, as it relies on config being loaded.
|
94
|
+
def parse_command
|
80
95
|
parser = BoltOptionParser.new(options)
|
81
96
|
# This part aims to handle both `bolt <mode> --help` and `bolt help <mode>`.
|
82
97
|
remaining = handle_parser_errors { parser.permute(@argv) } unless @argv.empty?
|
@@ -109,9 +124,28 @@ module Bolt
|
|
109
124
|
end
|
110
125
|
options[:leftovers] = remaining
|
111
126
|
|
127
|
+
# Default to verbose for everything except plans
|
128
|
+
unless options.key?(:verbose)
|
129
|
+
options[:verbose] = options[:subcommand] != 'plan'
|
130
|
+
end
|
131
|
+
|
112
132
|
validate(options)
|
113
133
|
|
114
|
-
|
134
|
+
# Deprecation warnings can't be issued until after config is loaded, so
|
135
|
+
# store them for later.
|
136
|
+
@parser_deprecations = parser.deprecations
|
137
|
+
rescue Bolt::Error => e
|
138
|
+
fatal_error(e)
|
139
|
+
raise e
|
140
|
+
end
|
141
|
+
|
142
|
+
# Loads the project and configuration. All errors that are raised here are not
|
143
|
+
# handled by the outputter, as it relies on config being loaded.
|
144
|
+
def load_config
|
145
|
+
@config = if ENV['BOLT_PROJECT']
|
146
|
+
project = Bolt::Project.create_project(ENV['BOLT_PROJECT'], 'environment')
|
147
|
+
Bolt::Config.from_project(project, options)
|
148
|
+
elsif options[:configfile]
|
115
149
|
Bolt::Config.from_file(options[:configfile], options)
|
116
150
|
else
|
117
151
|
project = if options[:boltdir]
|
@@ -126,39 +160,30 @@ module Bolt
|
|
126
160
|
end
|
127
161
|
Bolt::Config.from_project(project, options)
|
128
162
|
end
|
163
|
+
rescue Bolt::Error => e
|
164
|
+
fatal_error(e)
|
165
|
+
raise e
|
166
|
+
end
|
129
167
|
|
168
|
+
# Completes the setup process by configuring Bolt and issuing warnings
|
169
|
+
def finalize_setup
|
130
170
|
Bolt::Logger.configure(config.log, config.color)
|
171
|
+
Bolt::Logger.analytics = analytics
|
131
172
|
|
132
173
|
# Logger must be configured before checking path case and project file, otherwise warnings will not display
|
133
|
-
|
134
|
-
|
174
|
+
config.check_path_case('modulepath', config.modulepath)
|
175
|
+
config.project.check_deprecated_file
|
135
176
|
|
136
177
|
# Log the file paths for loaded config files
|
137
178
|
config_loaded
|
138
179
|
|
139
180
|
# Display warnings created during parser and config initialization
|
140
|
-
parser.warnings.each { |warning| @logger.warn(warning[:msg]) }
|
141
181
|
config.warnings.each { |warning| @logger.warn(warning[:msg]) }
|
142
|
-
|
143
|
-
|
144
|
-
# After this step
|
145
|
-
# options[:target_args] will contain a string/array version of the targetting options this is passed to plans
|
146
|
-
# options[:targets] will contain a resolved set of Target objects
|
147
|
-
unless options[:subcommand] == 'puppetfile' ||
|
148
|
-
options[:subcommand] == 'secret' ||
|
149
|
-
options[:subcommand] == 'project' ||
|
150
|
-
options[:action] == 'show' ||
|
151
|
-
options[:action] == 'convert'
|
152
|
-
|
153
|
-
update_targets(options)
|
154
|
-
end
|
155
|
-
|
156
|
-
unless options.key?(:verbose)
|
157
|
-
# Default to verbose for everything except plans
|
158
|
-
options[:verbose] = options[:subcommand] != 'plan'
|
159
|
-
end
|
182
|
+
@parser_deprecations.each { |dep| Bolt::Logger.deprecation_warning(dep[:type], dep[:msg]) }
|
183
|
+
config.deprecations.each { |dep| Bolt::Logger.deprecation_warning(dep[:type], dep[:msg]) }
|
160
184
|
|
161
185
|
warn_inventory_overrides_cli(options)
|
186
|
+
|
162
187
|
options
|
163
188
|
rescue Bolt::Error => e
|
164
189
|
outputter.fatal_error(e)
|
@@ -233,6 +258,13 @@ module Bolt
|
|
233
258
|
"Option '--noop' may only be specified when running a task or applying manifest code"
|
234
259
|
end
|
235
260
|
|
261
|
+
if options[:env_vars]
|
262
|
+
unless %w[command script].include?(options[:subcommand]) && options[:action] == 'run'
|
263
|
+
raise Bolt::CLIError,
|
264
|
+
"Option '--env-var' may only be specified when running a command or script"
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
236
268
|
if options[:subcommand] == 'apply' && (options[:object] && options[:code])
|
237
269
|
raise Bolt::CLIError, "--execute is unsupported when specifying a manifest file"
|
238
270
|
end
|
@@ -251,6 +283,10 @@ module Bolt
|
|
251
283
|
raise Bolt::CLIError, "Must specify a value to #{options[:action]}"
|
252
284
|
end
|
253
285
|
|
286
|
+
if options[:subcommand] == 'plan' && options[:action] == 'new' && !options[:object]
|
287
|
+
raise Bolt::CLIError, "Must specify a plan name."
|
288
|
+
end
|
289
|
+
|
254
290
|
if options.key?(:debug) && options.key?(:log)
|
255
291
|
raise Bolt::CLIError, "Only one of '--debug' or '--log-level' may be specified"
|
256
292
|
end
|
@@ -281,12 +317,12 @@ module Bolt
|
|
281
317
|
def warn_inventory_overrides_cli(opts)
|
282
318
|
inventory_source = if ENV[Bolt::Inventory::ENVIRONMENT_VAR]
|
283
319
|
Bolt::Inventory::ENVIRONMENT_VAR
|
284
|
-
elsif
|
285
|
-
|
320
|
+
elsif config.inventoryfile && Bolt::Util.file_stat(config.inventoryfile)
|
321
|
+
config.inventoryfile
|
286
322
|
else
|
287
323
|
begin
|
288
|
-
Bolt::Util.file_stat(
|
289
|
-
|
324
|
+
Bolt::Util.file_stat(config.default_inventoryfile)
|
325
|
+
config.default_inventoryfile
|
290
326
|
rescue Errno::ENOENT
|
291
327
|
nil
|
292
328
|
end
|
@@ -315,9 +351,12 @@ module Bolt
|
|
315
351
|
exit!
|
316
352
|
end
|
317
353
|
|
318
|
-
|
319
|
-
|
320
|
-
|
354
|
+
# Initialize inventory and targets. Errors here are better to catch early.
|
355
|
+
# options[:target_args] will contain a string/array version of the targetting options this is passed to plans
|
356
|
+
# options[:targets] will contain a resolved set of Target objects
|
357
|
+
unless %w[project puppetfile secret].include?(options[:subcommand]) ||
|
358
|
+
%w[convert new show].include?(options[:action])
|
359
|
+
update_targets(options)
|
321
360
|
end
|
322
361
|
|
323
362
|
screen = "#{options[:subcommand]}_#{options[:action]}"
|
@@ -343,32 +382,37 @@ module Bolt
|
|
343
382
|
|
344
383
|
analytics.screen_view(screen, screen_view_fields)
|
345
384
|
|
346
|
-
|
347
|
-
|
385
|
+
case options[:action]
|
386
|
+
when 'show'
|
387
|
+
case options[:subcommand]
|
388
|
+
when 'task'
|
348
389
|
if options[:object]
|
349
390
|
show_task(options[:object])
|
350
391
|
else
|
351
392
|
list_tasks
|
352
393
|
end
|
353
|
-
|
394
|
+
when 'plan'
|
354
395
|
if options[:object]
|
355
396
|
show_plan(options[:object])
|
356
397
|
else
|
357
398
|
list_plans
|
358
399
|
end
|
359
|
-
|
400
|
+
when 'inventory'
|
360
401
|
if options[:detail]
|
361
402
|
show_targets
|
362
403
|
else
|
363
404
|
list_targets
|
364
405
|
end
|
365
|
-
|
406
|
+
when 'group'
|
366
407
|
list_groups
|
367
408
|
end
|
368
409
|
return 0
|
369
|
-
|
410
|
+
when 'show-modules'
|
370
411
|
list_modules
|
371
412
|
return 0
|
413
|
+
when 'convert'
|
414
|
+
convert_plan(options[:object])
|
415
|
+
return 0
|
372
416
|
end
|
373
417
|
|
374
418
|
message = 'There may be processes left executing on some nodes.'
|
@@ -379,18 +423,25 @@ module Bolt
|
|
379
423
|
|
380
424
|
case options[:subcommand]
|
381
425
|
when 'project'
|
382
|
-
|
426
|
+
case options[:action]
|
427
|
+
when 'init'
|
383
428
|
code = initialize_project
|
384
|
-
|
429
|
+
when 'migrate'
|
385
430
|
code = migrate_project
|
386
431
|
end
|
387
432
|
when 'plan'
|
388
|
-
|
433
|
+
case options[:action]
|
434
|
+
when 'new'
|
435
|
+
code = new_plan(options[:object])
|
436
|
+
when 'run'
|
437
|
+
code = run_plan(options[:object], options[:task_options], options[:target_args], options)
|
438
|
+
end
|
389
439
|
when 'puppetfile'
|
390
|
-
|
440
|
+
case options[:action]
|
441
|
+
when 'generate-types'
|
391
442
|
code = generate_types
|
392
|
-
|
393
|
-
code = install_puppetfile(
|
443
|
+
when 'install'
|
444
|
+
code = install_puppetfile(config.puppetfile_config, config.puppetfile, config.modulepath)
|
394
445
|
end
|
395
446
|
when 'secret'
|
396
447
|
code = Bolt::Secret.execute(plugins, outputter, options)
|
@@ -410,6 +461,7 @@ module Bolt
|
|
410
461
|
elapsed_time = Benchmark.realtime do
|
411
462
|
executor_opts = {}
|
412
463
|
executor_opts[:description] = options[:description] if options.key?(:description)
|
464
|
+
executor_opts[:env_vars] = options[:env_vars] if options.key?(:env_vars)
|
413
465
|
executor.subscribe(outputter)
|
414
466
|
executor.subscribe(log_outputter)
|
415
467
|
results =
|
@@ -431,11 +483,22 @@ module Bolt
|
|
431
483
|
src = options[:object]
|
432
484
|
dest = options[:leftovers].first
|
433
485
|
|
486
|
+
if src.nil?
|
487
|
+
raise Bolt::CLIError, "A source path must be specified"
|
488
|
+
end
|
489
|
+
|
434
490
|
if dest.nil?
|
435
491
|
raise Bolt::CLIError, "A destination path must be specified"
|
436
492
|
end
|
437
|
-
|
438
|
-
|
493
|
+
|
494
|
+
case options[:action]
|
495
|
+
when 'download'
|
496
|
+
dest = File.expand_path(dest, Dir.pwd)
|
497
|
+
executor.download_file(targets, src, dest, executor_opts)
|
498
|
+
when 'upload'
|
499
|
+
validate_file('source file', src, true)
|
500
|
+
executor.upload_file(targets, src, dest, executor_opts)
|
501
|
+
end
|
439
502
|
end
|
440
503
|
end
|
441
504
|
|
@@ -492,6 +555,118 @@ module Bolt
|
|
492
555
|
outputter.print_groups(groups)
|
493
556
|
end
|
494
557
|
|
558
|
+
def new_plan(plan_name)
|
559
|
+
@logger.warn("Command 'bolt plan new' is experimental and subject to changes.")
|
560
|
+
|
561
|
+
if config.project.name.nil?
|
562
|
+
raise Bolt::Error.new(
|
563
|
+
"Project directory '#{config.project.path}' is not a named project. Unable to create "\
|
564
|
+
"a project-level plan. To name a project, set the 'name' key in the 'bolt-project.yaml' "\
|
565
|
+
"configuration file.",
|
566
|
+
"bolt/unnamed-project-error"
|
567
|
+
)
|
568
|
+
end
|
569
|
+
|
570
|
+
if plan_name !~ Bolt::Module::CONTENT_NAME_REGEX
|
571
|
+
message = <<~MESSAGE.chomp
|
572
|
+
Invalid plan name '#{plan_name}'. Plan names are composed of one or more name segments
|
573
|
+
separated by double colons '::'.
|
574
|
+
|
575
|
+
Each name segment must begin with a lowercase letter, and may only include lowercase
|
576
|
+
letters, digits, and underscores.
|
577
|
+
|
578
|
+
Examples of valid plan names:
|
579
|
+
- #{config.project.name}
|
580
|
+
- #{config.project.name}::my_plan
|
581
|
+
MESSAGE
|
582
|
+
|
583
|
+
raise Bolt::ValidationError, message
|
584
|
+
end
|
585
|
+
|
586
|
+
prefix, *name_segments, basename = plan_name.split('::')
|
587
|
+
|
588
|
+
# If the plan name is just the project name, then create an 'init' plan.
|
589
|
+
# Otherwise, use the last name segment for the plan's filename.
|
590
|
+
basename ||= 'init'
|
591
|
+
|
592
|
+
unless prefix == config.project.name
|
593
|
+
message = "First segment of plan name '#{plan_name}' must match project name '#{config.project.name}'. "\
|
594
|
+
"Did you mean '#{config.project.name}::#{plan_name}'?"
|
595
|
+
|
596
|
+
raise Bolt::ValidationError, message
|
597
|
+
end
|
598
|
+
|
599
|
+
dir_path = config.project.plans_path.join(*name_segments)
|
600
|
+
|
601
|
+
%w[pp yaml].each do |ext|
|
602
|
+
next unless (path = config.project.plans_path + "#{basename}.#{ext}").exist?
|
603
|
+
raise Bolt::Error.new(
|
604
|
+
"A plan with the name '#{plan_name}' already exists at '#{path}', nothing to do.",
|
605
|
+
'bolt/existing-plan-error'
|
606
|
+
)
|
607
|
+
end
|
608
|
+
|
609
|
+
begin
|
610
|
+
FileUtils.mkdir_p(dir_path)
|
611
|
+
rescue Errno::EEXIST => e
|
612
|
+
raise Bolt::Error.new(
|
613
|
+
"#{e.message}; unable to create plan directory '#{dir_path}'",
|
614
|
+
'bolt/existing-file-error'
|
615
|
+
)
|
616
|
+
end
|
617
|
+
|
618
|
+
plan_path = dir_path + "#{basename}.yaml"
|
619
|
+
|
620
|
+
plan_template = <<~PLAN
|
621
|
+
# This is the structure of a simple plan. To learn more about writing
|
622
|
+
# YAML plans, see the documentation: http://pup.pt/bolt-yaml-plans
|
623
|
+
|
624
|
+
# The description sets the description of the plan that will appear
|
625
|
+
# in 'bolt plan show' output.
|
626
|
+
description: A plan created with bolt plan new
|
627
|
+
|
628
|
+
# The parameters key defines the parameters that can be passed to
|
629
|
+
# the plan.
|
630
|
+
parameters:
|
631
|
+
targets:
|
632
|
+
type: TargetSpec
|
633
|
+
description: A list of targets to run actions on
|
634
|
+
default: localhost
|
635
|
+
|
636
|
+
# The steps key defines the actions the plan will take in order.
|
637
|
+
steps:
|
638
|
+
- message: Hello from #{plan_name}
|
639
|
+
- name: command_step
|
640
|
+
command: whoami
|
641
|
+
targets: $targets
|
642
|
+
|
643
|
+
# The return key sets the return value of the plan.
|
644
|
+
return: $command_step
|
645
|
+
PLAN
|
646
|
+
|
647
|
+
begin
|
648
|
+
File.write(plan_path, plan_template)
|
649
|
+
rescue Errno::EACCES => e
|
650
|
+
raise Bolt::FileError.new(
|
651
|
+
"#{e.message}; unable to create plan",
|
652
|
+
plan_path
|
653
|
+
)
|
654
|
+
end
|
655
|
+
|
656
|
+
output = <<~OUTPUT
|
657
|
+
Created plan '#{plan_name}' at '#{plan_path}'
|
658
|
+
|
659
|
+
Show this plan with:
|
660
|
+
bolt plan show #{plan_name}
|
661
|
+
Run this plan with:
|
662
|
+
bolt plan run #{plan_name}
|
663
|
+
OUTPUT
|
664
|
+
|
665
|
+
outputter.print_message(output)
|
666
|
+
|
667
|
+
0
|
668
|
+
end
|
669
|
+
|
495
670
|
def run_plan(plan_name, plan_arguments, nodes, options)
|
496
671
|
unless nodes.empty?
|
497
672
|
if plan_arguments['nodes'] || plan_arguments['targets']
|
@@ -802,7 +977,7 @@ module Bolt
|
|
802
977
|
end
|
803
978
|
|
804
979
|
def rerun
|
805
|
-
@rerun ||= Bolt::Rerun.new(
|
980
|
+
@rerun ||= Bolt::Rerun.new(config.rerunfile, config.save_rerun)
|
806
981
|
end
|
807
982
|
|
808
983
|
def outputter
|
@@ -865,5 +1040,15 @@ module Bolt
|
|
865
1040
|
def incomplete_install?
|
866
1041
|
(Dir.children(Bolt::PAL::MODULES_PATH) - %w[aggregate canary puppetdb_fact]).empty?
|
867
1042
|
end
|
1043
|
+
|
1044
|
+
# Mimicks the output from Outputter::Human#fatal_error. This should be used to print
|
1045
|
+
# errors prior to config being loaded, as the outputter relies on config being loaded.
|
1046
|
+
def fatal_error(error)
|
1047
|
+
if $stdout.isatty
|
1048
|
+
$stdout.puts("\033[31m#{error.message}\033[0m")
|
1049
|
+
else
|
1050
|
+
$stdout.puts(error.message)
|
1051
|
+
end
|
1052
|
+
end
|
868
1053
|
end
|
869
1054
|
end
|