bolt 2.23.0 → 2.24.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/exe/bolt +1 -0
- data/guides/inventory.txt +19 -0
- data/guides/project.txt +22 -0
- data/lib/bolt/analytics.rb +4 -4
- data/lib/bolt/applicator.rb +4 -3
- data/lib/bolt/bolt_option_parser.rb +30 -6
- data/lib/bolt/cli.rb +55 -50
- data/lib/bolt/config.rb +1 -1
- data/lib/bolt/config/options.rb +4 -4
- data/lib/bolt/executor.rb +4 -2
- data/lib/bolt/inventory/group.rb +3 -3
- data/lib/bolt/logger.rb +3 -4
- data/lib/bolt/outputter/human.rb +10 -0
- data/lib/bolt/outputter/json.rb +11 -0
- data/lib/bolt/outputter/logger.rb +2 -2
- data/lib/bolt/outputter/rainbow.rb +15 -0
- data/lib/bolt/pal.rb +2 -2
- data/lib/bolt/pal/yaml_plan/transpiler.rb +11 -3
- data/lib/bolt/project_migrate.rb +138 -0
- data/lib/bolt/shell/bash.rb +7 -7
- data/lib/bolt/transport/docker/connection.rb +9 -9
- data/lib/bolt/transport/local/connection.rb +2 -2
- data/lib/bolt/transport/orch.rb +3 -3
- data/lib/bolt/transport/ssh/connection.rb +5 -5
- data/lib/bolt/transport/ssh/exec_connection.rb +4 -4
- data/lib/bolt/transport/winrm/connection.rb +8 -8
- data/lib/bolt/util.rb +1 -1
- data/lib/bolt/util/puppet_log_level.rb +4 -3
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/base_config.rb +1 -1
- data/lib/bolt_server/transport_app.rb +76 -0
- data/lib/bolt_spec/plans.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1e572a2c5aec9f127f48764b6a2fe903c91566c8aa0f3a1db33e40347bd1630b
|
4
|
+
data.tar.gz: 1445543fb941e0b8c12ec60900975a45e368a08e593ebe75c87ee946317f57b7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4cdee0a056e3c248a72ea3a4cb1872453611ca241d19ad3c98f5e0e89c550368ab19e34999e121f0e98c5e5cac353fed32eb174fb54fd149bd4558bd84e5706e
|
7
|
+
data.tar.gz: d505e8baf7c6ce5b21c20e27d7979c39c871e5bc69ea7bcf5d898ac94261850afea03028569ed60e192196d215837a2b339fef24231ea9491bc4901e93cba3dd
|
data/exe/bolt
CHANGED
@@ -0,0 +1,19 @@
|
|
1
|
+
TOPIC
|
2
|
+
inventory
|
3
|
+
|
4
|
+
DESCRIPTION
|
5
|
+
The inventory describes the targets that you run Bolt commands on, along
|
6
|
+
with any data and configuration for the targets. Targets in an inventory can
|
7
|
+
belong to one or more groups, allowing you to share data and configuration
|
8
|
+
across multiple targets and to specify multiple targets for your Bolt
|
9
|
+
commands without the need to list each target individually.
|
10
|
+
|
11
|
+
In most cases, Bolt loads the inventory from an inventory file in your Bolt
|
12
|
+
project. The inventory file is a YAML file named 'inventory.yaml'. Because
|
13
|
+
Bolt loads the inventory file from a Bolt project, you must have an existing
|
14
|
+
project configuration file named 'bolt-project.yaml' alongside the inventory
|
15
|
+
file.
|
16
|
+
|
17
|
+
DOCUMENTATION
|
18
|
+
https://pup.pt/bolt-inventory
|
19
|
+
https://pup.pt/bolt-inventory-reference
|
data/guides/project.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
TOPIC
|
2
|
+
project
|
3
|
+
|
4
|
+
DESCRIPTION
|
5
|
+
A Bolt project is a directory that serves as the launching point for Bolt
|
6
|
+
and allows you to create a shareable orchestration application. Projects
|
7
|
+
typically include a project configuration file, an inventory file, and any
|
8
|
+
content you use in your project workflow, such as tasks and plans.
|
9
|
+
|
10
|
+
When you run Bolt, it runs in the context of a project. If the directory you
|
11
|
+
run Bolt from is not a project, Bolt attempts to find a project by
|
12
|
+
traversing the parent directories. If Bolt is unable to find a project, it
|
13
|
+
runs from the default project, located at '~/.puppetlabs/bolt'.
|
14
|
+
|
15
|
+
A directory is only considered a Bolt project when it has a project
|
16
|
+
configuration file named 'bolt-project.yaml'. Bolt doesn't load project data
|
17
|
+
and content, including inventory files, unless the data and content are part
|
18
|
+
of a project.
|
19
|
+
|
20
|
+
DOCUMENTATION
|
21
|
+
https://pup.pt/bolt-projects
|
22
|
+
https://pup.pt/bolt-project-reference
|
data/lib/bolt/analytics.rb
CHANGED
@@ -161,9 +161,9 @@ module Bolt
|
|
161
161
|
# Handle analytics submission in the background to avoid blocking the
|
162
162
|
# app or polluting the log with errors
|
163
163
|
Concurrent::Future.execute(executor: @executor) do
|
164
|
-
@logger.
|
164
|
+
@logger.trace "Submitting analytics: #{JSON.pretty_generate(params)}"
|
165
165
|
@http.post(TRACKING_URL, params)
|
166
|
-
@logger.
|
166
|
+
@logger.trace "Completed analytics submission"
|
167
167
|
end
|
168
168
|
end
|
169
169
|
|
@@ -215,13 +215,13 @@ module Bolt
|
|
215
215
|
end
|
216
216
|
|
217
217
|
def screen_view(screen, **_kwargs)
|
218
|
-
@logger.
|
218
|
+
@logger.trace "Skipping submission of '#{screen}' screenview because analytics is disabled"
|
219
219
|
end
|
220
220
|
|
221
221
|
def report_bundled_content(mode, name); end
|
222
222
|
|
223
223
|
def event(category, action, **_kwargs)
|
224
|
-
@logger.
|
224
|
+
@logger.trace "Skipping submission of '#{category} #{action}' event because analytics is disabled"
|
225
225
|
end
|
226
226
|
|
227
227
|
def finish; end
|
data/lib/bolt/applicator.rb
CHANGED
@@ -27,7 +27,7 @@ module Bolt
|
|
27
27
|
@hiera_config = hiera_config ? validate_hiera_config(hiera_config) : nil
|
28
28
|
@apply_settings = apply_settings || {}
|
29
29
|
|
30
|
-
@pool = Concurrent::ThreadPoolExecutor.new(max_threads: max_compiles)
|
30
|
+
@pool = Concurrent::ThreadPoolExecutor.new(name: 'apply', max_threads: max_compiles)
|
31
31
|
@logger = Logging.logger[self]
|
32
32
|
end
|
33
33
|
|
@@ -217,6 +217,7 @@ module Bolt
|
|
217
217
|
r = @executor.log_action(description, targets) do
|
218
218
|
futures = targets.map do |target|
|
219
219
|
Concurrent::Future.execute(executor: @pool) do
|
220
|
+
Thread.current[:name] ||= Thread.current.name
|
220
221
|
@executor.with_node_logging("Compiling manifest block", [target]) do
|
221
222
|
compile(target, scope)
|
222
223
|
end
|
@@ -300,7 +301,7 @@ module Bolt
|
|
300
301
|
|
301
302
|
files.each do |file|
|
302
303
|
tar_path = Pathname.new(file).relative_path_from(parent)
|
303
|
-
@logger.
|
304
|
+
@logger.trace("Packing plugin #{file} to #{tar_path}")
|
304
305
|
stat = File.stat(file)
|
305
306
|
content = File.binread(file)
|
306
307
|
output.tar.add_file_simple(
|
@@ -314,7 +315,7 @@ module Bolt
|
|
314
315
|
end
|
315
316
|
|
316
317
|
duration = Time.now - start_time
|
317
|
-
@logger.
|
318
|
+
@logger.trace("Packed plugins in #{duration * 1000} ms")
|
318
319
|
|
319
320
|
output.close
|
320
321
|
Base64.encode64(sio.string)
|
@@ -61,6 +61,9 @@ module Bolt
|
|
61
61
|
{ flags: OPTIONS[:global],
|
62
62
|
banner: GROUP_HELP }
|
63
63
|
end
|
64
|
+
when 'guide'
|
65
|
+
{ flags: OPTIONS[:global] + %w[format],
|
66
|
+
banner: GUIDE_HELP }
|
64
67
|
when 'plan'
|
65
68
|
case action
|
66
69
|
when 'convert'
|
@@ -85,7 +88,7 @@ module Bolt
|
|
85
88
|
{ flags: OPTIONS[:global] + %w[modules],
|
86
89
|
banner: PROJECT_INIT_HELP }
|
87
90
|
when 'migrate'
|
88
|
-
{ flags: OPTIONS[:global] + %w[inventoryfile
|
91
|
+
{ flags: OPTIONS[:global] + %w[inventoryfile project configfile],
|
89
92
|
banner: PROJECT_MIGRATE_HELP }
|
90
93
|
else
|
91
94
|
{ flags: OPTIONS[:global],
|
@@ -164,6 +167,7 @@ module Bolt
|
|
164
167
|
command Run a command remotely
|
165
168
|
file Copy files between the controller and targets
|
166
169
|
group Show the list of groups in the inventory
|
170
|
+
guide View guides for Bolt concepts and features
|
167
171
|
inventory Show the list of targets an action would run on
|
168
172
|
plan Convert, create, show, and run Bolt plans
|
169
173
|
project Create and migrate Bolt projects
|
@@ -171,6 +175,9 @@ module Bolt
|
|
171
175
|
script Upload a local script and run it remotely
|
172
176
|
secret Create encryption keys and encrypt and decrypt values
|
173
177
|
task Show and run Bolt tasks
|
178
|
+
|
179
|
+
GUIDES
|
180
|
+
For a list of guides on Bolt's concepts and features, run 'bolt guide'.
|
174
181
|
HELP
|
175
182
|
|
176
183
|
APPLY_HELP = <<~HELP
|
@@ -289,6 +296,26 @@ module Bolt
|
|
289
296
|
Show the list of groups in the inventory.
|
290
297
|
HELP
|
291
298
|
|
299
|
+
GUIDE_HELP = <<~HELP
|
300
|
+
NAME
|
301
|
+
guide
|
302
|
+
|
303
|
+
USAGE
|
304
|
+
bolt guide [topic] [options]
|
305
|
+
|
306
|
+
DESCRIPTION
|
307
|
+
View guides for Bolt's concepts and features.
|
308
|
+
|
309
|
+
Omitting a topic will display a list of available guides,
|
310
|
+
while providing a topic will display the relevant guide.
|
311
|
+
|
312
|
+
EXAMPLES
|
313
|
+
View a list of available guides
|
314
|
+
bolt guide
|
315
|
+
View the 'project' guide page
|
316
|
+
bolt guide project
|
317
|
+
HELP
|
318
|
+
|
292
319
|
INVENTORY_HELP = <<~HELP
|
293
320
|
NAME
|
294
321
|
inventory
|
@@ -444,10 +471,7 @@ module Bolt
|
|
444
471
|
bolt project migrate [options]
|
445
472
|
|
446
473
|
DESCRIPTION
|
447
|
-
Migrate a Bolt project to the latest version.
|
448
|
-
|
449
|
-
Loads a Bolt project's inventory file and migrates it to the latest version. The
|
450
|
-
inventory file is modified in place and will not preserve comments or formatting.
|
474
|
+
Migrate a Bolt project to use current best practices and the latest version of configuration files.
|
451
475
|
HELP
|
452
476
|
|
453
477
|
PUPPETFILE_HELP = <<~HELP
|
@@ -863,7 +887,7 @@ module Bolt
|
|
863
887
|
end
|
864
888
|
define('--log-level LEVEL',
|
865
889
|
"Set the log level for the console. Available options are",
|
866
|
-
"debug, info,
|
890
|
+
"trace, debug, info, warn, error, fatal, any.") do |level|
|
867
891
|
@options[:log] = { 'console' => { 'level' => level } }
|
868
892
|
end
|
869
893
|
define('--plugin PLUGIN', 'Select the plugin to use') do |plug|
|
data/lib/bolt/cli.rb
CHANGED
@@ -20,6 +20,7 @@ require 'bolt/logger'
|
|
20
20
|
require 'bolt/outputter'
|
21
21
|
require 'bolt/puppetdb'
|
22
22
|
require 'bolt/plugin'
|
23
|
+
require 'bolt/project_migrate'
|
23
24
|
require 'bolt/pal'
|
24
25
|
require 'bolt/target'
|
25
26
|
require 'bolt/version'
|
@@ -38,7 +39,8 @@ module Bolt
|
|
38
39
|
'inventory' => %w[show],
|
39
40
|
'group' => %w[show],
|
40
41
|
'project' => %w[init migrate],
|
41
|
-
'apply' => %w[]
|
42
|
+
'apply' => %w[],
|
43
|
+
'guide' => %w[] }.freeze
|
42
44
|
|
43
45
|
attr_reader :config, :options
|
44
46
|
|
@@ -354,7 +356,7 @@ module Bolt
|
|
354
356
|
# Initialize inventory and targets. Errors here are better to catch early.
|
355
357
|
# options[:target_args] will contain a string/array version of the targetting options this is passed to plans
|
356
358
|
# options[:targets] will contain a resolved set of Target objects
|
357
|
-
unless %w[project puppetfile secret].include?(options[:subcommand]) ||
|
359
|
+
unless %w[project puppetfile secret guide].include?(options[:subcommand]) ||
|
358
360
|
%w[convert new show].include?(options[:action])
|
359
361
|
update_targets(options)
|
360
362
|
end
|
@@ -411,7 +413,7 @@ module Bolt
|
|
411
413
|
list_modules
|
412
414
|
return 0
|
413
415
|
when 'convert'
|
414
|
-
convert_plan(options[:object])
|
416
|
+
pal.convert_plan(options[:object])
|
415
417
|
return 0
|
416
418
|
end
|
417
419
|
|
@@ -422,12 +424,20 @@ module Bolt
|
|
422
424
|
end
|
423
425
|
|
424
426
|
case options[:subcommand]
|
427
|
+
when 'guide'
|
428
|
+
code = if options[:object]
|
429
|
+
show_guide(options[:object])
|
430
|
+
else
|
431
|
+
list_topics
|
432
|
+
end
|
425
433
|
when 'project'
|
426
434
|
case options[:action]
|
427
435
|
when 'init'
|
428
436
|
code = initialize_project
|
429
437
|
when 'migrate'
|
430
|
-
|
438
|
+
inv = config.inventoryfile
|
439
|
+
path = config.project.path
|
440
|
+
code = Bolt::ProjectMigrate.new(path, outputter, inv).migrate_project
|
431
441
|
end
|
432
442
|
when 'plan'
|
433
443
|
case options[:action]
|
@@ -903,49 +913,6 @@ module Bolt
|
|
903
913
|
end
|
904
914
|
end
|
905
915
|
|
906
|
-
def migrate_project
|
907
|
-
inventory_file = config.inventoryfile || config.default_inventoryfile
|
908
|
-
data = Bolt::Util.read_yaml_hash(inventory_file, 'inventory')
|
909
|
-
|
910
|
-
data.delete('version') if data['version'] != 2
|
911
|
-
|
912
|
-
migrated = migrate_group(data)
|
913
|
-
|
914
|
-
ok = File.write(inventory_file, data.to_yaml) if migrated
|
915
|
-
|
916
|
-
result = if migrated && ok
|
917
|
-
"Successfully migrated Bolt project to latest version."
|
918
|
-
elsif !migrated
|
919
|
-
"Bolt project already on latest version. Nothing to do."
|
920
|
-
else
|
921
|
-
"Could not migrate Bolt project to latest version."
|
922
|
-
end
|
923
|
-
outputter.print_message result
|
924
|
-
|
925
|
-
ok ? 0 : 1
|
926
|
-
end
|
927
|
-
|
928
|
-
# Walks an inventory hash and replaces all 'nodes' keys with 'targets' keys
|
929
|
-
# and all 'name' keys nested in a 'targets' hash with 'uri' keys. Data is
|
930
|
-
# modified in place.
|
931
|
-
def migrate_group(group)
|
932
|
-
migrated = false
|
933
|
-
if group.key?('nodes')
|
934
|
-
migrated = true
|
935
|
-
targets = group['nodes'].map do |target|
|
936
|
-
target['uri'] = target.delete('name') if target.is_a?(Hash)
|
937
|
-
target
|
938
|
-
end
|
939
|
-
group.delete('nodes')
|
940
|
-
group['targets'] = targets
|
941
|
-
end
|
942
|
-
(group['groups'] || []).each do |subgroup|
|
943
|
-
migrated_group = migrate_group(subgroup)
|
944
|
-
migrated ||= migrated_group
|
945
|
-
end
|
946
|
-
migrated
|
947
|
-
end
|
948
|
-
|
949
916
|
def install_puppetfile(config, puppetfile, modulepath)
|
950
917
|
require 'r10k/cli'
|
951
918
|
require 'bolt/r10k_log_proxy'
|
@@ -988,8 +955,46 @@ module Bolt
|
|
988
955
|
config.project)
|
989
956
|
end
|
990
957
|
|
991
|
-
|
992
|
-
|
958
|
+
# Collects the list of Bolt guides and maps them to their topics.
|
959
|
+
def guides
|
960
|
+
@guides ||= begin
|
961
|
+
root_path = File.expand_path(File.join(__dir__, '..', '..', 'guides'))
|
962
|
+
files = Dir.children(root_path).sort
|
963
|
+
|
964
|
+
files.each_with_object({}) do |file, guides|
|
965
|
+
next if file !~ /\.txt\z/
|
966
|
+
topic = File.basename(file, '.txt')
|
967
|
+
guides[topic] = File.join(root_path, file)
|
968
|
+
end
|
969
|
+
rescue SystemCallError => e
|
970
|
+
raise Bolt::FileError.new("#{e.message}: unable to load guides directory", root_path)
|
971
|
+
end
|
972
|
+
end
|
973
|
+
|
974
|
+
# Display the list of available Bolt guides.
|
975
|
+
def list_topics
|
976
|
+
outputter.print_topics(guides.keys)
|
977
|
+
0
|
978
|
+
end
|
979
|
+
|
980
|
+
# Display a specific Bolt guide.
|
981
|
+
def show_guide(topic)
|
982
|
+
if guides[topic]
|
983
|
+
analytics.event('Guide', 'known_topic', label: topic)
|
984
|
+
|
985
|
+
begin
|
986
|
+
guide = File.read(guides[topic])
|
987
|
+
rescue SystemCallError => e
|
988
|
+
raise Bolt::FileError("#{e.message}: unable to load guide page", filepath)
|
989
|
+
end
|
990
|
+
|
991
|
+
outputter.print_guide(guide, topic)
|
992
|
+
else
|
993
|
+
analytics.event('Guide', 'unknown_topic', label: topic)
|
994
|
+
outputter.print_message("Did not find guide for topic '#{topic}'.\n\n")
|
995
|
+
list_topics
|
996
|
+
end
|
997
|
+
0
|
993
998
|
end
|
994
999
|
|
995
1000
|
def validate_file(type, path, allow_dir = false)
|
@@ -1056,7 +1061,7 @@ module Bolt
|
|
1056
1061
|
msg = <<~MSG.chomp
|
1057
1062
|
Loaded configuration from: '#{config.config_files.join("', '")}'
|
1058
1063
|
MSG
|
1059
|
-
@logger.
|
1064
|
+
@logger.info(msg)
|
1060
1065
|
end
|
1061
1066
|
|
1062
1067
|
# Gem installs include the aggregate, canary, and puppetdb_fact modules, while
|
data/lib/bolt/config.rb
CHANGED
@@ -204,7 +204,7 @@ module Bolt
|
|
204
204
|
'compile-concurrency' => Etc.nprocessors,
|
205
205
|
'concurrency' => default_concurrency,
|
206
206
|
'format' => 'human',
|
207
|
-
'log' => { 'console' => {} },
|
207
|
+
'log' => { 'console' => {}, 'bolt-debug.log' => { 'level' => 'debug', 'append' => false } },
|
208
208
|
'plugin_hooks' => {},
|
209
209
|
'plugins' => {},
|
210
210
|
'puppetdb' => {},
|
data/lib/bolt/config/options.rb
CHANGED
@@ -186,8 +186,8 @@ module Bolt
|
|
186
186
|
"level" => {
|
187
187
|
description: "The type of information to log.",
|
188
188
|
type: String,
|
189
|
-
enum: %w[debug error info
|
190
|
-
_default: "warn
|
189
|
+
enum: %w[trace debug error info warn fatal any],
|
190
|
+
_default: "warn"
|
191
191
|
}
|
192
192
|
}
|
193
193
|
}
|
@@ -204,8 +204,8 @@ module Bolt
|
|
204
204
|
"level" => {
|
205
205
|
description: "The type of information to log.",
|
206
206
|
type: String,
|
207
|
-
enum: %w[debug error info
|
208
|
-
_default: "warn
|
207
|
+
enum: %w[trace debug error info warn fatal any],
|
208
|
+
_default: "warn"
|
209
209
|
}
|
210
210
|
}
|
211
211
|
},
|
data/lib/bolt/executor.rb
CHANGED
@@ -56,11 +56,12 @@ module Bolt
|
|
56
56
|
@reported_transports = Set.new
|
57
57
|
@subscribers = {}
|
58
58
|
@publisher = Concurrent::SingleThreadExecutor.new
|
59
|
+
@publisher.post { Thread.current[:name] = 'event-publisher' }
|
59
60
|
|
60
61
|
@noop = noop
|
61
62
|
@run_as = nil
|
62
63
|
@pool = if concurrency > 0
|
63
|
-
Concurrent::ThreadPoolExecutor.new(max_threads: concurrency)
|
64
|
+
Concurrent::ThreadPoolExecutor.new(name: 'exec', max_threads: concurrency)
|
64
65
|
else
|
65
66
|
Concurrent.global_immediate_executor
|
66
67
|
end
|
@@ -125,6 +126,7 @@ module Bolt
|
|
125
126
|
# Pass this argument through to avoid retaining a reference to a
|
126
127
|
# local variable that will change on the next iteration of the loop.
|
127
128
|
@pool.post(batch_promises) do |result_promises|
|
129
|
+
Thread.current[:name] ||= Thread.current.name
|
128
130
|
results = yield transport, batch
|
129
131
|
Array(results).each do |result|
|
130
132
|
result_promises[result.target].set(result)
|
@@ -241,7 +243,7 @@ module Bolt
|
|
241
243
|
|
242
244
|
@analytics&.event('Plan', 'yaml', plan_steps: steps, return_type: return_type)
|
243
245
|
rescue StandardError => e
|
244
|
-
@logger.
|
246
|
+
@logger.trace { "Failed to submit analytics event: #{e.message}" }
|
245
247
|
end
|
246
248
|
|
247
249
|
def with_node_logging(description, batch)
|
data/lib/bolt/inventory/group.rb
CHANGED
@@ -119,7 +119,7 @@ module Bolt
|
|
119
119
|
end
|
120
120
|
|
121
121
|
if contains_target?(t_name)
|
122
|
-
@logger.
|
122
|
+
@logger.debug("Ignoring duplicate target in #{@name}: #{target}")
|
123
123
|
return
|
124
124
|
end
|
125
125
|
|
@@ -200,14 +200,14 @@ module Bolt
|
|
200
200
|
# If this is an alias for an existing target, then add it to this group
|
201
201
|
elsif (canonical_name = aliases[string_target])
|
202
202
|
if contains_target?(canonical_name)
|
203
|
-
@logger.
|
203
|
+
@logger.debug("Ignoring duplicate target in #{@name}: #{canonical_name}")
|
204
204
|
else
|
205
205
|
@unresolved_targets[canonical_name] = { 'name' => canonical_name }
|
206
206
|
end
|
207
207
|
# If it's not the name or alias of an existing target, then make a
|
208
208
|
# new target using the string as the URI
|
209
209
|
elsif contains_target?(string_target)
|
210
|
-
@logger.
|
210
|
+
@logger.debug("Ignoring duplicate target in #{@name}: #{string_target}")
|
211
211
|
else
|
212
212
|
@unresolved_targets[string_target] = { 'uri' => string_target }
|
213
213
|
end
|
data/lib/bolt/logger.rb
CHANGED
@@ -14,13 +14,12 @@ module Bolt
|
|
14
14
|
# redefs, so skip it if it's already been initialized
|
15
15
|
return if Logging.initialized?
|
16
16
|
|
17
|
-
Logging.init :debug, :info, :notice, :warn, :error, :fatal, :any
|
17
|
+
Logging.init :trace, :debug, :info, :notice, :warn, :error, :fatal, :any
|
18
18
|
@mutex = Mutex.new
|
19
19
|
|
20
20
|
Logging.color_scheme(
|
21
21
|
'bolt',
|
22
22
|
lines: {
|
23
|
-
notice: :green,
|
24
23
|
warn: :yellow,
|
25
24
|
error: :red,
|
26
25
|
fatal: %i[white on_red]
|
@@ -81,7 +80,7 @@ module Bolt
|
|
81
80
|
|
82
81
|
def self.default_layout
|
83
82
|
Logging.layouts.pattern(
|
84
|
-
pattern: '%d %-6l %c
|
83
|
+
pattern: '%d %-6l [%T] [%c] %m\n',
|
85
84
|
date_pattern: '%Y-%m-%dT%H:%M:%S.%6N'
|
86
85
|
)
|
87
86
|
end
|
@@ -91,7 +90,7 @@ module Bolt
|
|
91
90
|
end
|
92
91
|
|
93
92
|
def self.default_file_level
|
94
|
-
:
|
93
|
+
:warn
|
95
94
|
end
|
96
95
|
|
97
96
|
# Explicitly check the log level names instead of the log level number, as levels
|
data/lib/bolt/outputter/human.rb
CHANGED
@@ -286,6 +286,16 @@ module Bolt
|
|
286
286
|
"details and parameters for a specific plan.")
|
287
287
|
end
|
288
288
|
|
289
|
+
def print_topics(topics)
|
290
|
+
print_message("Available topics are:")
|
291
|
+
print_message(topics.join("\n"))
|
292
|
+
print_message("\nUse `bolt guide <topic>` to view a specific guide.")
|
293
|
+
end
|
294
|
+
|
295
|
+
def print_guide(guide, _topic)
|
296
|
+
@stream.puts(guide)
|
297
|
+
end
|
298
|
+
|
289
299
|
def print_module_list(module_list)
|
290
300
|
module_list.each do |path, modules|
|
291
301
|
if (mod = modules.find { |m| m[:internal_module_group] })
|
data/lib/bolt/outputter/json.rb
CHANGED
@@ -83,6 +83,17 @@ module Bolt
|
|
83
83
|
@stream.puts result.to_json
|
84
84
|
end
|
85
85
|
|
86
|
+
def print_topics(topics)
|
87
|
+
print_table('topics' => topics)
|
88
|
+
end
|
89
|
+
|
90
|
+
def print_guide(guide, topic)
|
91
|
+
@stream.puts({
|
92
|
+
'topic' => topic,
|
93
|
+
'guide' => guide
|
94
|
+
}.to_json)
|
95
|
+
end
|
96
|
+
|
86
97
|
def print_puppetfile_result(success, puppetfile, moduledir)
|
87
98
|
@stream.puts({ "success": success,
|
88
99
|
"puppetfile": puppetfile,
|
@@ -40,13 +40,13 @@ module Bolt
|
|
40
40
|
|
41
41
|
def log_plan_start(event)
|
42
42
|
plan = event[:plan]
|
43
|
-
@logger.
|
43
|
+
@logger.info("Starting: plan #{plan}")
|
44
44
|
end
|
45
45
|
|
46
46
|
def log_plan_finish(event)
|
47
47
|
plan = event[:plan]
|
48
48
|
duration = event[:duration]
|
49
|
-
@logger.
|
49
|
+
@logger.info("Finished: plan #{plan} in #{duration.round(2)} sec")
|
50
50
|
end
|
51
51
|
end
|
52
52
|
end
|
@@ -86,6 +86,21 @@ module Bolt
|
|
86
86
|
total_msg << " in #{duration_to_string(elapsed_time)}" unless elapsed_time.nil?
|
87
87
|
@stream.puts colorize(:rainbow, total_msg)
|
88
88
|
end
|
89
|
+
|
90
|
+
def print_guide(guide, _topic)
|
91
|
+
@stream.puts colorize(:rainbow, guide)
|
92
|
+
end
|
93
|
+
|
94
|
+
def print_topics(topics)
|
95
|
+
content = String.new("Available topics are:\n")
|
96
|
+
content += topics.join("\n")
|
97
|
+
content += "\n\nUse `bolt guide <topic>` to view a specific guide."
|
98
|
+
@stream.puts colorize(:rainbow, content)
|
99
|
+
end
|
100
|
+
|
101
|
+
def print_message(message)
|
102
|
+
@stream.puts colorize(:rainbow, message)
|
103
|
+
end
|
89
104
|
end
|
90
105
|
end
|
91
106
|
end
|
data/lib/bolt/pal.rb
CHANGED
@@ -67,7 +67,7 @@ module Bolt
|
|
67
67
|
|
68
68
|
@logger = Logging.logger[self]
|
69
69
|
if modulepath && !modulepath.empty?
|
70
|
-
@logger.
|
70
|
+
@logger.debug("Loading modules from #{@modulepath.join(File::PATH_SEPARATOR)}")
|
71
71
|
end
|
72
72
|
|
73
73
|
@loaded = false
|
@@ -409,7 +409,7 @@ module Bolt
|
|
409
409
|
end
|
410
410
|
params[name] = { 'type' => type_str }
|
411
411
|
params[name]['sensitive'] = param.type_expr.instance_of?(Puppet::Pops::Types::PSensitiveType)
|
412
|
-
params[name]['default_value'] = param.value
|
412
|
+
params[name]['default_value'] = param.value unless param.value.nil?
|
413
413
|
params[name]['description'] = param.description if param.description
|
414
414
|
end
|
415
415
|
{
|
@@ -21,10 +21,18 @@ module Bolt
|
|
21
21
|
validate_path
|
22
22
|
|
23
23
|
plan_object = parse_plan
|
24
|
+
param_descriptions = plan_object.parameters.map do |param|
|
25
|
+
str = String.new("# @param #{param.name}")
|
26
|
+
str << " #{param.description}" if param.description
|
27
|
+
str
|
28
|
+
end.join("\n")
|
24
29
|
|
25
|
-
plan_string = String.new(
|
26
|
-
|
27
|
-
|
30
|
+
plan_string = String.new('')
|
31
|
+
plan_string << "# #{plan_object.description}\n" if plan_object.description
|
32
|
+
plan_string << "# WARNING: This is an autogenerated plan. It may not behave as expected.\n"
|
33
|
+
plan_string << "#{param_descriptions}\n" unless param_descriptions.empty?
|
34
|
+
|
35
|
+
plan_string << "plan #{plan_object.name}("
|
28
36
|
# Parameters are Bolt::PAL::YamlPlan::Parameter
|
29
37
|
plan_object.parameters&.each_with_index do |param, i|
|
30
38
|
plan_string << param.transpile
|
@@ -0,0 +1,138 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bolt
|
4
|
+
class ProjectMigrate
|
5
|
+
attr_reader :path, :project_file, :backup_dir, :outputter, :inventory_file, :config_file
|
6
|
+
|
7
|
+
# This init mostly makes testing easier
|
8
|
+
def initialize(path, outputter, configured_inventory = nil)
|
9
|
+
@path = Pathname.new(path).expand_path
|
10
|
+
@project_file = @path + 'bolt-project.yaml'
|
11
|
+
@config_file = @path + 'bolt.yaml'
|
12
|
+
@backup_dir = @path + '.bolt-bak'
|
13
|
+
@inventory_file = configured_inventory || @path + 'inventory.yaml'
|
14
|
+
@outputter = outputter
|
15
|
+
end
|
16
|
+
|
17
|
+
def migrate_project
|
18
|
+
inv_ok = inventory_1_to_2(inventory_file, outputter) if inventory_file.file?
|
19
|
+
config_ok = bolt_yaml_to_bolt_project(inventory_file, outputter)
|
20
|
+
inv_ok && config_ok ? 0 : 1
|
21
|
+
end
|
22
|
+
|
23
|
+
# This could be made public and used elsewhere if the need arises
|
24
|
+
private def backup_file(origin_path)
|
25
|
+
unless File.exist?(origin_path)
|
26
|
+
outputter.print_message "Could not find file #{origin_path}, skipping backup."
|
27
|
+
return
|
28
|
+
end
|
29
|
+
|
30
|
+
date = Time.new.strftime("%Y%m%d_%H%M%S%L")
|
31
|
+
FileUtils.mkdir_p(backup_dir)
|
32
|
+
|
33
|
+
filename = File.basename(origin_path)
|
34
|
+
backup_path = File.join(backup_dir, "#{filename}.#{date}.bak")
|
35
|
+
|
36
|
+
outputter.print_message "Backing up #{filename} from #{origin_path} to #{backup_path}"
|
37
|
+
|
38
|
+
begin
|
39
|
+
FileUtils.cp(origin_path, backup_path)
|
40
|
+
rescue StandardError => e
|
41
|
+
raise Bolt::FileError.new("#{e.message}; unable to create backup of #{filename}.", origin_path)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private def bolt_yaml_to_bolt_project(inventory_file, outputter)
|
46
|
+
# If bolt-project.yaml already exists
|
47
|
+
if project_file.file?
|
48
|
+
outputter.print_message "bolt-project.yaml already exists in Bolt "\
|
49
|
+
"project at #{path}. Skipping project file update."
|
50
|
+
|
51
|
+
# If bolt.yaml doesn't exist
|
52
|
+
elsif !config_file.file?
|
53
|
+
outputter.print_message "Could not find bolt.yaml in project at "\
|
54
|
+
"#{path}. Skipping project file update."
|
55
|
+
|
56
|
+
else
|
57
|
+
config_data = Bolt::Util.read_optional_yaml_hash(config_file, 'config')
|
58
|
+
transport_data, project_data = config_data.partition do |k, _|
|
59
|
+
Bolt::Config::INVENTORY_OPTIONS.keys.include?(k)
|
60
|
+
end.map(&:to_h)
|
61
|
+
|
62
|
+
if transport_data.any?
|
63
|
+
if File.exist?(inventory_file)
|
64
|
+
inventory_data = Bolt::Util.read_yaml_hash(inventory_file, 'inventory')
|
65
|
+
merged = Bolt::Util.deep_merge(transport_data, inventory_data['config'] || {})
|
66
|
+
inventory_data['config'] = merged
|
67
|
+
backup_file(inventory_file)
|
68
|
+
else
|
69
|
+
FileUtils.touch(inventory_file)
|
70
|
+
inventory_data = { 'config' => transport_data }
|
71
|
+
end
|
72
|
+
|
73
|
+
backup_file(config_file)
|
74
|
+
|
75
|
+
begin
|
76
|
+
outputter.print_message "Moving transportation configuration options "\
|
77
|
+
"'#{transport_data.keys.join(', ')}' from bolt.yaml to inventory.yaml"
|
78
|
+
File.write(inventory_file, inventory_data.to_yaml)
|
79
|
+
File.write(config_file, project_data.to_yaml)
|
80
|
+
rescue StandardError => e
|
81
|
+
raise Bolt::FileError.new("#{e.message}; unable to write inventory.", inventory_file)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
outputter.print_message "Renaming bolt.yaml to bolt-project.yaml"
|
86
|
+
FileUtils.mv(config_file, project_file)
|
87
|
+
outputter.print_message "Successfully updated project. Please add a "\
|
88
|
+
"'name' key to bolt-project.yaml to use project-level tasks and plans. "\
|
89
|
+
"Learn more about projects by running 'bolt guide project'."
|
90
|
+
# If nothing errored, this succeeded
|
91
|
+
true
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
private def inventory_1_to_2(inventory_file, outputter)
|
96
|
+
data = Bolt::Util.read_yaml_hash(inventory_file, 'inventory')
|
97
|
+
data.delete('version') if data['version'] != 2
|
98
|
+
migrated = migrate_group(data)
|
99
|
+
|
100
|
+
ok = if migrated
|
101
|
+
backup_file(inventory_file)
|
102
|
+
File.write(inventory_file, data.to_yaml)
|
103
|
+
end
|
104
|
+
|
105
|
+
result = if migrated && ok
|
106
|
+
"Successfully migrated Bolt inventory to the latest version."
|
107
|
+
elsif !migrated
|
108
|
+
"Bolt inventory is already on the latest version. Skipping inventory update."
|
109
|
+
else
|
110
|
+
"Could not migrate Bolt inventory to the latest version. See "\
|
111
|
+
"https://puppet.com/docs/bolt/latest/inventory_file_v2.html to manually update."
|
112
|
+
end
|
113
|
+
outputter.print_message(result)
|
114
|
+
ok
|
115
|
+
end
|
116
|
+
|
117
|
+
# Walks an inventory hash and replaces all 'nodes' keys with 'targets' keys
|
118
|
+
# and all 'name' keys nested in a 'targets' hash with 'uri' keys. Data is
|
119
|
+
# modified in place.
|
120
|
+
private def migrate_group(group)
|
121
|
+
migrated = false
|
122
|
+
if group.key?('nodes')
|
123
|
+
migrated = true
|
124
|
+
targets = group['nodes'].map do |target|
|
125
|
+
target['uri'] = target.delete('name') if target.is_a?(Hash)
|
126
|
+
target
|
127
|
+
end
|
128
|
+
group.delete('nodes')
|
129
|
+
group['targets'] = targets
|
130
|
+
end
|
131
|
+
(group['groups'] || []).each do |subgroup|
|
132
|
+
migrated_group = migrate_group(subgroup)
|
133
|
+
migrated ||= migrated_group
|
134
|
+
end
|
135
|
+
migrated
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
data/lib/bolt/shell/bash.rb
CHANGED
@@ -103,7 +103,7 @@ module Bolt
|
|
103
103
|
" using '#{execute_options[:interpreter]}' interpreter"
|
104
104
|
end
|
105
105
|
# log the arguments with sensitive data redacted, do NOT log unwrapped_arguments
|
106
|
-
logger.
|
106
|
+
logger.trace("Running '#{executable}' with #{arguments.to_json}#{interpreter_debug}")
|
107
107
|
# unpack any Sensitive data
|
108
108
|
arguments = unwrap_sensitive_args(arguments)
|
109
109
|
|
@@ -203,13 +203,13 @@ module Bolt
|
|
203
203
|
|
204
204
|
def handle_sudo_errors(err)
|
205
205
|
if err =~ /^#{conn.user} is not in the sudoers file\./
|
206
|
-
@logger.
|
206
|
+
@logger.trace { err }
|
207
207
|
raise Bolt::Node::EscalateError.new(
|
208
208
|
"User #{conn.user} does not have sudo permission on #{target}",
|
209
209
|
'SUDO_DENIED'
|
210
210
|
)
|
211
211
|
elsif err =~ /^Sorry, try again\./
|
212
|
-
@logger.
|
212
|
+
@logger.trace { err }
|
213
213
|
raise Bolt::Node::EscalateError.new(
|
214
214
|
"Sudo password for user #{conn.user} not recognized on #{target}",
|
215
215
|
'BAD_PASSWORD'
|
@@ -351,7 +351,7 @@ module Bolt
|
|
351
351
|
|
352
352
|
command_str = [sudo_str, env_decl, command_str].compact.join(' ')
|
353
353
|
|
354
|
-
@logger.
|
354
|
+
@logger.trace { "Executing `#{command_str}`" }
|
355
355
|
|
356
356
|
in_buffer = if !use_sudo && options[:stdin]
|
357
357
|
String.new(options[:stdin], encoding: 'binary')
|
@@ -431,16 +431,16 @@ module Bolt
|
|
431
431
|
result_output.exit_code = t.value.respond_to?(:exitstatus) ? t.value.exitstatus : t.value
|
432
432
|
|
433
433
|
if result_output.exit_code == 0
|
434
|
-
@logger.
|
434
|
+
@logger.trace { "Command `#{command_str}` returned successfully" }
|
435
435
|
else
|
436
|
-
@logger.
|
436
|
+
@logger.trace { "Command #{command_str} failed with exit code #{result_output.exit_code}" }
|
437
437
|
end
|
438
438
|
result_output
|
439
439
|
rescue StandardError
|
440
440
|
# Ensure we close stdin and kill the child process
|
441
441
|
inp&.close
|
442
442
|
t&.terminate if t&.alive?
|
443
|
-
@logger.
|
443
|
+
@logger.trace { "Command aborted" }
|
444
444
|
raise
|
445
445
|
end
|
446
446
|
|
@@ -12,7 +12,7 @@ module Bolt
|
|
12
12
|
@target = target
|
13
13
|
@logger = Logging.logger[target.safe_name]
|
14
14
|
@docker_host = @target.options['service-url']
|
15
|
-
@logger.
|
15
|
+
@logger.trace("Initializing docker connection to #{@target.safe_name}")
|
16
16
|
end
|
17
17
|
|
18
18
|
def connect
|
@@ -25,7 +25,7 @@ module Bolt
|
|
25
25
|
output = execute_local_docker_json_command('inspect', [output[index]["ID"]])
|
26
26
|
# Store the container information for later
|
27
27
|
@container_info = output[0]
|
28
|
-
@logger.
|
28
|
+
@logger.trace { "Opened session" }
|
29
29
|
true
|
30
30
|
rescue StandardError => e
|
31
31
|
raise Bolt::Node::ConnectError.new(
|
@@ -57,16 +57,16 @@ module Bolt
|
|
57
57
|
command_options << container_id
|
58
58
|
command_options.concat(command)
|
59
59
|
|
60
|
-
@logger.
|
60
|
+
@logger.trace { "Executing: exec #{command_options}" }
|
61
61
|
|
62
62
|
stdout_str, stderr_str, status = execute_local_docker_command('exec', command_options, options[:stdin])
|
63
63
|
|
64
64
|
# The actual result is the exitstatus not the process object
|
65
65
|
status = status.nil? ? -32768 : status.exitstatus
|
66
66
|
if status == 0
|
67
|
-
@logger.
|
67
|
+
@logger.trace { "Command returned successfully" }
|
68
68
|
else
|
69
|
-
@logger.
|
69
|
+
@logger.trace { "Command failed with exit code #{status}" }
|
70
70
|
end
|
71
71
|
stdout_str.force_encoding(Encoding::UTF_8)
|
72
72
|
stderr_str.force_encoding(Encoding::UTF_8)
|
@@ -75,12 +75,12 @@ module Bolt
|
|
75
75
|
stderr_str.gsub!("\r\n", "\n")
|
76
76
|
[stdout_str, stderr_str, status]
|
77
77
|
rescue StandardError
|
78
|
-
@logger.
|
78
|
+
@logger.trace { "Command aborted" }
|
79
79
|
raise
|
80
80
|
end
|
81
81
|
|
82
82
|
def write_remote_file(source, destination)
|
83
|
-
@logger.
|
83
|
+
@logger.trace { "Uploading #{source} to #{destination}" }
|
84
84
|
_, stdout_str, status = execute_local_docker_command('cp', [source, "#{container_id}:#{destination}"])
|
85
85
|
unless status.exitstatus.zero?
|
86
86
|
raise "Error writing file to container #{@container_id}: #{stdout_str}"
|
@@ -90,7 +90,7 @@ module Bolt
|
|
90
90
|
end
|
91
91
|
|
92
92
|
def write_remote_directory(source, destination)
|
93
|
-
@logger.
|
93
|
+
@logger.trace { "Uploading #{source} to #{destination}" }
|
94
94
|
_, stdout_str, status = execute_local_docker_command('cp', [source, "#{container_id}:#{destination}"])
|
95
95
|
unless status.exitstatus.zero?
|
96
96
|
raise "Error writing directory to container #{@container_id}: #{stdout_str}"
|
@@ -100,7 +100,7 @@ module Bolt
|
|
100
100
|
end
|
101
101
|
|
102
102
|
def download_remote_content(source, destination)
|
103
|
-
@logger.
|
103
|
+
@logger.trace { "Downloading #{source} to #{destination}" }
|
104
104
|
# Create the destination directory, otherwise copying a source directory with Docker will
|
105
105
|
# copy the *contents* of the directory.
|
106
106
|
# https://docs.docker.com/engine/reference/commandline/cp/
|
@@ -28,7 +28,7 @@ module Bolt
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def upload_file(source, dest)
|
31
|
-
@logger.
|
31
|
+
@logger.trace { "Uploading #{source} to #{dest}" }
|
32
32
|
if source.is_a?(StringIO)
|
33
33
|
Tempfile.create(File.basename(dest)) do |f|
|
34
34
|
f.write(source.read)
|
@@ -46,7 +46,7 @@ module Bolt
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def download_file(source, dest, _download)
|
49
|
-
@logger.
|
49
|
+
@logger.trace { "Downloading #{source} to #{dest}" }
|
50
50
|
# Create the destination directory for the target, or the
|
51
51
|
# copied file will have the target's name
|
52
52
|
FileUtils.mkdir_p(dest)
|
data/lib/bolt/transport/orch.rb
CHANGED
@@ -38,7 +38,7 @@ module Bolt
|
|
38
38
|
@connections.each_value do |conn|
|
39
39
|
conn.finish_plan(result)
|
40
40
|
rescue StandardError => e
|
41
|
-
@logger.
|
41
|
+
@logger.trace("Failed to finish plan on #{conn.key}: #{e.message}")
|
42
42
|
end
|
43
43
|
end
|
44
44
|
end
|
@@ -133,7 +133,7 @@ module Bolt
|
|
133
133
|
next unless File.file?(file)
|
134
134
|
|
135
135
|
tar_path = Pathname.new(file).relative_path_from(Pathname.new(directory))
|
136
|
-
@logger.
|
136
|
+
@logger.trace("Packing #{file} to #{tar_path}")
|
137
137
|
stat = File.stat(file)
|
138
138
|
content = File.binread(file)
|
139
139
|
output.tar.add_file_simple(
|
@@ -146,7 +146,7 @@ module Bolt
|
|
146
146
|
end
|
147
147
|
|
148
148
|
duration = Time.now - start_time
|
149
|
-
@logger.
|
149
|
+
@logger.trace("Packed upload in #{duration * 1000} ms")
|
150
150
|
|
151
151
|
output.close
|
152
152
|
io.string
|
@@ -28,7 +28,7 @@ module Bolt
|
|
28
28
|
|
29
29
|
@logger = Logging.logger[@target.safe_name]
|
30
30
|
@transport_logger = transport_logger
|
31
|
-
@logger.
|
31
|
+
@logger.trace("Initializing ssh connection to #{@target.safe_name}")
|
32
32
|
|
33
33
|
if target.options['private-key']&.instance_of?(String)
|
34
34
|
begin
|
@@ -131,7 +131,7 @@ module Bolt
|
|
131
131
|
|
132
132
|
@session = Net::SSH.start(target.host, @user, options)
|
133
133
|
validate_ssh_version
|
134
|
-
@logger.
|
134
|
+
@logger.trace { "Opened session" }
|
135
135
|
rescue Net::SSH::AuthenticationFailed => e
|
136
136
|
raise Bolt::Node::ConnectError.new(
|
137
137
|
e.message,
|
@@ -161,7 +161,7 @@ module Bolt
|
|
161
161
|
rescue Timeout::Error
|
162
162
|
@session.shutdown!
|
163
163
|
end
|
164
|
-
@logger.
|
164
|
+
@logger.trace { "Closed session" }
|
165
165
|
end
|
166
166
|
end
|
167
167
|
|
@@ -237,7 +237,7 @@ module Bolt
|
|
237
237
|
|
238
238
|
def upload_file(source, destination)
|
239
239
|
# Do not log wrapper script content
|
240
|
-
@logger.
|
240
|
+
@logger.trace { "Uploading #{source} to #{destination}" } unless source.is_a?(StringIO)
|
241
241
|
@session.scp.upload!(source, destination, recursive: true)
|
242
242
|
rescue StandardError => e
|
243
243
|
raise Bolt::Node::FileError.new(e.message, 'WRITE_ERROR')
|
@@ -245,7 +245,7 @@ module Bolt
|
|
245
245
|
|
246
246
|
def download_file(source, destination, _download)
|
247
247
|
# Do not log wrapper script content
|
248
|
-
@logger.
|
248
|
+
@logger.trace { "Downloading #{source} to #{destination}" }
|
249
249
|
@session.scp.download!(source, destination, recursive: true)
|
250
250
|
rescue StandardError => e
|
251
251
|
raise Bolt::Node::FileError.new(e.message, 'WRITE_ERROR')
|
@@ -66,7 +66,7 @@ module Bolt
|
|
66
66
|
end
|
67
67
|
|
68
68
|
def upload_file(source, dest)
|
69
|
-
@logger.
|
69
|
+
@logger.trace { "Uploading #{source} to #{dest}" } unless source.is_a?(StringIO)
|
70
70
|
|
71
71
|
cp_conf = @target.transport_config['copy-command'] || ["scp", "-r"]
|
72
72
|
cp_cmd = Array(cp_conf)
|
@@ -87,7 +87,7 @@ module Bolt
|
|
87
87
|
end
|
88
88
|
|
89
89
|
if stat.success?
|
90
|
-
@logger.
|
90
|
+
@logger.trace "Successfully uploaded #{source} to #{dest}"
|
91
91
|
else
|
92
92
|
message = "Could not copy file to #{dest}: #{err}"
|
93
93
|
raise Bolt::Node::FileError.new(message, 'COPY_ERROR')
|
@@ -95,7 +95,7 @@ module Bolt
|
|
95
95
|
end
|
96
96
|
|
97
97
|
def download_file(source, dest, _download)
|
98
|
-
@logger.
|
98
|
+
@logger.trace { "Downloading #{userhost}:#{source} to #{dest}" }
|
99
99
|
|
100
100
|
FileUtils.mkdir_p(dest)
|
101
101
|
|
@@ -108,7 +108,7 @@ module Bolt
|
|
108
108
|
_, err, stat = Open3.capture3(*cp_cmd)
|
109
109
|
|
110
110
|
if stat.success?
|
111
|
-
@logger.
|
111
|
+
@logger.trace "Successfully downloaded #{userhost}:#{source} to #{dest}"
|
112
112
|
else
|
113
113
|
message = "Could not copy file to #{dest}: #{err}"
|
114
114
|
raise Bolt::Node::FileError.new(message, 'COPY_ERROR')
|
@@ -19,7 +19,7 @@ module Bolt
|
|
19
19
|
# Build set of extensions from extensions config as well as interpreters
|
20
20
|
|
21
21
|
@logger = Logging.logger[@target.safe_name]
|
22
|
-
logger.
|
22
|
+
logger.trace("Initializing winrm connection to #{@target.safe_name}")
|
23
23
|
@transport_logger = transport_logger
|
24
24
|
end
|
25
25
|
|
@@ -55,7 +55,7 @@ module Bolt
|
|
55
55
|
|
56
56
|
@session = @connection.shell(:powershell)
|
57
57
|
@session.run('$PSVersionTable.PSVersion')
|
58
|
-
@logger.
|
58
|
+
@logger.trace { "Opened session" }
|
59
59
|
end
|
60
60
|
rescue Timeout::Error
|
61
61
|
# If we're using the default port with SSL, a timeout probably means the
|
@@ -97,11 +97,11 @@ module Bolt
|
|
97
97
|
def disconnect
|
98
98
|
@session&.close
|
99
99
|
@client&.disconnect!
|
100
|
-
@logger.
|
100
|
+
@logger.trace { "Closed session" }
|
101
101
|
end
|
102
102
|
|
103
103
|
def execute(command)
|
104
|
-
@logger.
|
104
|
+
@logger.trace { "Executing command: #{command}" }
|
105
105
|
|
106
106
|
inp = StringIO.new
|
107
107
|
# This transport doesn't accept stdin, so close the stream to ensure
|
@@ -134,12 +134,12 @@ module Bolt
|
|
134
134
|
"with 'ulimit -n 1024'. See https://puppet.com/docs/bolt/latest/bolt_known_issues.html for details."
|
135
135
|
raise Bolt::Error.new(msg, 'bolt/too-many-files')
|
136
136
|
rescue StandardError
|
137
|
-
@logger.
|
137
|
+
@logger.trace { "Command aborted" }
|
138
138
|
raise
|
139
139
|
end
|
140
140
|
|
141
141
|
def upload_file(source, destination)
|
142
|
-
@logger.
|
142
|
+
@logger.trace { "Uploading #{source} to #{destination}" }
|
143
143
|
if target.options['file-protocol'] == 'smb'
|
144
144
|
upload_file_smb(source, destination)
|
145
145
|
else
|
@@ -185,7 +185,7 @@ module Bolt
|
|
185
185
|
end
|
186
186
|
|
187
187
|
def download_file(source, destination, download)
|
188
|
-
@logger.
|
188
|
+
@logger.trace { "Downloading #{source} to #{destination}" }
|
189
189
|
if target.options['file-protocol'] == 'smb'
|
190
190
|
download_file_smb(source, destination)
|
191
191
|
else
|
@@ -257,7 +257,7 @@ module Bolt
|
|
257
257
|
status = @client.login
|
258
258
|
case status
|
259
259
|
when WindowsError::NTStatus::STATUS_SUCCESS
|
260
|
-
@logger.
|
260
|
+
@logger.trace { "Connected to #{@client.dns_host_name}" }
|
261
261
|
when WindowsError::NTStatus::STATUS_LOGON_FAILURE
|
262
262
|
raise Bolt::Node::ConnectError.new(
|
263
263
|
"SMB authentication failed for #{target.safe_name}",
|
data/lib/bolt/util.rb
CHANGED
@@ -13,7 +13,7 @@ module Bolt
|
|
13
13
|
msg = "Invalid content for #{file_name} file: #{path} should be a Hash or empty, not #{content.class}"
|
14
14
|
raise Bolt::FileError.new(msg, path)
|
15
15
|
end
|
16
|
-
logger.
|
16
|
+
logger.trace("Loaded #{file_name} from #{path}")
|
17
17
|
content
|
18
18
|
rescue Errno::ENOENT
|
19
19
|
raise Bolt::FileError.new("Could not read #{file_name} file: #{path}", path)
|
@@ -4,9 +4,10 @@ module Bolt
|
|
4
4
|
module Util
|
5
5
|
module PuppetLogLevel
|
6
6
|
MAPPING = {
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
# Demote Puppet's logs by one level, since Puppet is an implementation detail of Bolt
|
8
|
+
debug: :trace,
|
9
|
+
info: :debug,
|
10
|
+
notice: :info,
|
10
11
|
warning: :warn,
|
11
12
|
err: :error,
|
12
13
|
# The following are used by Puppet functions of the same name, and are all treated as
|
data/lib/bolt/version.rb
CHANGED
@@ -221,6 +221,56 @@ module BoltServer
|
|
221
221
|
plan_info
|
222
222
|
end
|
223
223
|
|
224
|
+
def build_puppetserver_uri(file_identifier, module_name, environment)
|
225
|
+
segments = file_identifier.split('/', 3)
|
226
|
+
if segments.size == 1
|
227
|
+
{
|
228
|
+
'path' => "/puppet/v3/file_content/tasks/#{module_name}/#{file_identifier}",
|
229
|
+
'params' => {
|
230
|
+
'environment' => environment
|
231
|
+
}
|
232
|
+
}
|
233
|
+
else
|
234
|
+
module_segment, mount_segment, name_segment = *segments
|
235
|
+
{
|
236
|
+
'path' => case mount_segment
|
237
|
+
when 'files'
|
238
|
+
"/puppet/v3/file_content/modules/#{module_segment}/#{name_segment}"
|
239
|
+
when 'tasks'
|
240
|
+
"/puppet/v3/file_content/tasks/#{module_segment}/#{name_segment}"
|
241
|
+
when 'lib'
|
242
|
+
"/puppet/v3/file_content/plugins/#{name_segment}"
|
243
|
+
end,
|
244
|
+
'params' => {
|
245
|
+
'environment' => environment
|
246
|
+
}
|
247
|
+
}
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def pe_task_info(pal, module_name, task_name, environment)
|
252
|
+
# Handle case where task name is simply module name with special `init` task
|
253
|
+
task_name = if task_name == 'init' || task_name.nil?
|
254
|
+
module_name
|
255
|
+
else
|
256
|
+
"#{module_name}::#{task_name}"
|
257
|
+
end
|
258
|
+
task = pal.get_task(task_name)
|
259
|
+
files = task.files.map do |file_hash|
|
260
|
+
{
|
261
|
+
'filename' => file_hash['name'],
|
262
|
+
'sha256' => Digest::SHA256.hexdigest(File.read(file_hash['path'])),
|
263
|
+
'size_bytes' => File.size(file_hash['path']),
|
264
|
+
'uri' => build_puppetserver_uri(file_hash['name'], module_name, environment)
|
265
|
+
}
|
266
|
+
end
|
267
|
+
{
|
268
|
+
'metadata' => task.metadata,
|
269
|
+
'name' => task.name,
|
270
|
+
'files' => files
|
271
|
+
}
|
272
|
+
end
|
273
|
+
|
224
274
|
get '/' do
|
225
275
|
200
|
226
276
|
end
|
@@ -351,6 +401,16 @@ module BoltServer
|
|
351
401
|
end
|
352
402
|
end
|
353
403
|
|
404
|
+
# Fetches the metadata for a single task
|
405
|
+
#
|
406
|
+
# @param environment [String] the environment to fetch the task from
|
407
|
+
get '/tasks/:module_name/:task_name' do
|
408
|
+
in_pe_pal_env(params['environment']) do |pal|
|
409
|
+
task_info = pe_task_info(pal, params[:module_name], params[:task_name], params['environment'])
|
410
|
+
[200, task_info.to_json]
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
354
414
|
# Fetches the list of plans for an environment, optionally fetching all metadata for each plan
|
355
415
|
#
|
356
416
|
# @param environment [String] the environment to fetch the list of plans from
|
@@ -375,6 +435,22 @@ module BoltServer
|
|
375
435
|
end
|
376
436
|
end
|
377
437
|
|
438
|
+
# Fetches the list of tasks for an environment
|
439
|
+
#
|
440
|
+
# @param environment [String] the environment to fetch the list of tasks from
|
441
|
+
get '/tasks' do
|
442
|
+
in_pe_pal_env(params['environment']) do |pal|
|
443
|
+
tasks = pal.list_tasks
|
444
|
+
tasks_response = tasks.map { |task_name, _description| { 'name' => task_name } }.to_json
|
445
|
+
|
446
|
+
# We structure this array of tasks to be an array of hashes so that it matches the structure
|
447
|
+
# returned by the puppetserver API that serves data like this. Structuring the output this way
|
448
|
+
# makes switching between puppetserver and bolt-server easier, which makes changes to switch
|
449
|
+
# to bolt-server smaller/simpler.
|
450
|
+
[200, tasks_response]
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
378
454
|
error 404 do
|
379
455
|
err = Bolt::Error.new("Could not find route #{request.path}",
|
380
456
|
'boltserver/not-found')
|
data/lib/bolt_spec/plans.rb
CHANGED
@@ -206,7 +206,7 @@ module BoltSpec
|
|
206
206
|
Puppet[:tasks] = true
|
207
207
|
|
208
208
|
# Ensure logger is initialized with Puppet levels so 'notice' works when running plan specs.
|
209
|
-
Logging.init :debug, :info, :notice, :warn, :error, :fatal, :any
|
209
|
+
Logging.init :trace, :debug, :info, :notice, :warn, :error, :fatal, :any
|
210
210
|
end
|
211
211
|
|
212
212
|
# Provided as a class so expectations can be placed on it.
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bolt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.24.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Puppet
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-08-
|
11
|
+
date: 2020-08-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: addressable
|
@@ -437,6 +437,8 @@ files:
|
|
437
437
|
- bolt-modules/prompt/lib/puppet/functions/prompt.rb
|
438
438
|
- bolt-modules/system/lib/puppet/functions/system/env.rb
|
439
439
|
- exe/bolt
|
440
|
+
- guides/inventory.txt
|
441
|
+
- guides/project.txt
|
440
442
|
- lib/bolt.rb
|
441
443
|
- lib/bolt/analytics.rb
|
442
444
|
- lib/bolt/applicator.rb
|
@@ -498,6 +500,7 @@ files:
|
|
498
500
|
- lib/bolt/plugin/puppetdb.rb
|
499
501
|
- lib/bolt/plugin/task.rb
|
500
502
|
- lib/bolt/project.rb
|
503
|
+
- lib/bolt/project_migrate.rb
|
501
504
|
- lib/bolt/puppetdb.rb
|
502
505
|
- lib/bolt/puppetdb/client.rb
|
503
506
|
- lib/bolt/puppetdb/config.rb
|