bolt 0.16.0 → 0.16.1
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/lib/bolt/cli.rb +32 -17
- data/lib/bolt/config.rb +12 -31
- data/lib/bolt/error.rb +18 -7
- data/lib/bolt/executor.rb +3 -3
- data/lib/bolt/inventory.rb +101 -0
- data/lib/bolt/inventory/group.rb +127 -0
- data/lib/bolt/node.rb +10 -9
- data/lib/bolt/node/ssh.rb +86 -69
- data/lib/bolt/node/winrm.rb +3 -3
- data/lib/bolt/outputter/human.rb +1 -1
- data/lib/bolt/pal.rb +17 -11
- data/lib/bolt/result.rb +9 -4
- data/lib/bolt/target.rb +26 -11
- data/lib/bolt/util.rb +54 -0
- data/lib/bolt/version.rb +1 -1
- data/modules/boltlib/lib/puppet/functions/fail_plan.rb +27 -0
- data/modules/boltlib/lib/puppet/functions/file_upload.rb +4 -4
- data/modules/boltlib/lib/puppet/functions/run_command.rb +3 -3
- data/modules/boltlib/lib/puppet/functions/run_plan.rb +18 -4
- data/modules/boltlib/lib/puppet/functions/run_script.rb +5 -2
- data/modules/boltlib/lib/puppet/functions/run_task.rb +3 -3
- data/vendored/puppet/lib/puppet/datatypes/error.rb +5 -3
- data/vendored/puppet/lib/puppet/datatypes/impl/error.rb +10 -12
- data/vendored/puppet/lib/puppet/defaults.rb +33 -25
- data/vendored/puppet/lib/puppet/etc.rb +2 -2
- data/vendored/puppet/lib/puppet/external/pson/pure/generator.rb +1 -1
- data/vendored/puppet/lib/puppet/external/pson/pure/parser.rb +1 -1
- data/vendored/puppet/lib/puppet/face/config.rb +45 -0
- data/vendored/puppet/lib/puppet/face/module/generate.rb +5 -0
- data/vendored/puppet/lib/puppet/functions/annotate.rb +1 -1
- data/vendored/puppet/lib/puppet/functions/any.rb +1 -1
- data/vendored/puppet/lib/puppet/generate/type.rb +1 -1
- data/vendored/puppet/lib/puppet/gettext/config.rb +2 -2
- data/vendored/puppet/lib/puppet/gettext/stubs.rb +1 -1
- data/vendored/puppet/lib/puppet/interface/action.rb +11 -0
- data/vendored/puppet/lib/puppet/interface/action_builder.rb +8 -0
- data/vendored/puppet/lib/puppet/network/authstore.rb +1 -1
- data/vendored/puppet/lib/puppet/network/http/connection.rb +1 -1
- data/vendored/puppet/lib/puppet/parameter/boolean.rb +1 -1
- data/vendored/puppet/lib/puppet/parser/ast/branch.rb +1 -1
- data/vendored/puppet/lib/puppet/parser/functions/new.rb +1 -1
- data/vendored/puppet/lib/puppet/parser/functions/reverse_each.rb +1 -1
- data/vendored/puppet/lib/puppet/pops/evaluator/compare_operator.rb +1 -1
- data/vendored/puppet/lib/puppet/pops/evaluator/evaluator_impl.rb +3 -3
- data/vendored/puppet/lib/puppet/pops/evaluator/literal_evaluator.rb +1 -1
- data/vendored/puppet/lib/puppet/pops/evaluator/runtime3_converter.rb +1 -1
- data/vendored/puppet/lib/puppet/pops/evaluator/runtime3_support.rb +1 -1
- data/vendored/puppet/lib/puppet/pops/issues.rb +1 -1
- data/vendored/puppet/lib/puppet/pops/loader/loader.rb +1 -1
- data/vendored/puppet/lib/puppet/pops/loader/loader_paths.rb +1 -1
- data/vendored/puppet/lib/puppet/pops/loader/module_loaders.rb +3 -5
- data/vendored/puppet/lib/puppet/pops/loader/puppet_resource_type_impl_instantiator.rb +1 -1
- data/vendored/puppet/lib/puppet/pops/loader/runtime3_type_loader.rb +1 -1
- data/vendored/puppet/lib/puppet/pops/loaders.rb +40 -1
- data/vendored/puppet/lib/puppet/pops/lookup/interpolation.rb +1 -1
- data/vendored/puppet/lib/puppet/pops/model/tree_dumper.rb +1 -1
- data/vendored/puppet/lib/puppet/pops/parser/epp_support.rb +1 -1
- data/vendored/puppet/lib/puppet/pops/parser/interpolation_support.rb +1 -1
- data/vendored/puppet/lib/puppet/pops/parser/lexer2.rb +1 -1
- data/vendored/puppet/lib/puppet/pops/parser/lexer_support.rb +2 -2
- data/vendored/puppet/lib/puppet/pops/parser/locatable.rb +1 -1
- data/vendored/puppet/lib/puppet/pops/parser/locator.rb +2 -6
- data/vendored/puppet/lib/puppet/pops/resource/param.rb +1 -1
- data/vendored/puppet/lib/puppet/pops/resource/resource_type_impl.rb +1 -1
- data/vendored/puppet/lib/puppet/pops/types/iterable.rb +1 -1
- data/vendored/puppet/lib/puppet/pops/types/tree_iterators.rb +5 -1
- data/vendored/puppet/lib/puppet/pops/types/type_acceptor.rb +1 -1
- data/vendored/puppet/lib/puppet/pops/types/type_calculator.rb +1 -1
- data/vendored/puppet/lib/puppet/pops/types/type_with_members.rb +1 -1
- data/vendored/puppet/lib/puppet/pops/types/types.rb +1 -1
- data/vendored/puppet/lib/puppet/provider/package/yum.rb +1 -1
- data/vendored/puppet/lib/puppet/provider/service/redhat.rb +3 -2
- data/vendored/puppet/lib/puppet/provider/service/systemd.rb +1 -1
- data/vendored/puppet/lib/puppet/provider/ssh_authorized_key/parsed.rb +1 -1
- data/vendored/puppet/lib/puppet/resource/type.rb +1 -1
- data/vendored/puppet/lib/puppet/settings/base_setting.rb +1 -1
- data/vendored/puppet/lib/puppet/settings/ini_file.rb +33 -12
- data/vendored/puppet/lib/puppet/ssl/certificate_request.rb +2 -2
- data/vendored/puppet/lib/puppet/transaction.rb +37 -14
- data/vendored/puppet/lib/puppet/type/cron.rb +1 -1
- data/vendored/puppet/lib/puppet/type/file/checksum.rb +6 -0
- data/vendored/puppet/lib/puppet/type/mount.rb +0 -9
- data/vendored/puppet/lib/puppet/util/character_encoding.rb +2 -2
- data/vendored/puppet/lib/puppet/util/network_device/cisco/device.rb +5 -5
- data/vendored/puppet/lib/puppet/util/rdoc/generators/puppet_generator.rb +1 -1
- data/vendored/puppet/lib/puppet/util/windows/process.rb +1 -1
- data/vendored/puppet/lib/puppet/vendor/pathspec/lib/pathspec/gitignorespec.rb +18 -18
- data/vendored/puppet/lib/puppet/vendor/semantic_puppet/lib/semantic_puppet/version.rb +2 -2
- data/vendored/puppet/lib/puppet/vendor/semantic_puppet/lib/semantic_puppet/version_range.rb +5 -5
- metadata +20 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4c145190f4fb70b998c8fd501a368940c2d2db3c
|
4
|
+
data.tar.gz: 45949da83f3903d369a71f88730e8ef7413cdff7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4597004b2108d16ab6aaf401e0b5160ef711ce27fe487b1996457f3b007ffc522c438b6df3a4b540b7e7566b9bc3794ecee4474400dbf21393b132b5abf99134
|
7
|
+
data.tar.gz: fe62dba570d0e11ed31e2be2e490a8eaa347485edf9adca4798e6c731ae892f48ad767f6f3493f1f54f42df7468b091c9a8db057e6bc65b2037eb607817b3152
|
data/lib/bolt/cli.rb
CHANGED
@@ -1,18 +1,19 @@
|
|
1
1
|
require 'uri'
|
2
|
-
require 'optparse'
|
3
2
|
require 'benchmark'
|
4
3
|
require 'json'
|
4
|
+
require 'io/console'
|
5
5
|
require 'logging'
|
6
|
-
require '
|
7
|
-
require 'bolt/
|
8
|
-
require 'bolt/version'
|
6
|
+
require 'optparse'
|
7
|
+
require 'bolt/config'
|
9
8
|
require 'bolt/error'
|
10
9
|
require 'bolt/executor'
|
11
|
-
require 'bolt/
|
10
|
+
require 'bolt/inventory'
|
11
|
+
require 'bolt/logger'
|
12
|
+
require 'bolt/node'
|
12
13
|
require 'bolt/outputter'
|
13
|
-
require 'bolt/config'
|
14
14
|
require 'bolt/pal'
|
15
|
-
require '
|
15
|
+
require 'bolt/target'
|
16
|
+
require 'bolt/version'
|
16
17
|
|
17
18
|
module Bolt
|
18
19
|
class CLIError < Bolt::Error
|
@@ -135,8 +136,7 @@ HELP
|
|
135
136
|
'* protocol is `ssh` by default, may be `ssh` or `winrm`',
|
136
137
|
'* port defaults to `22` for SSH',
|
137
138
|
'* port defaults to `5985` or `5986` for WinRM, based on the --[no-]ssl setting') do |nodes|
|
138
|
-
results[:nodes]
|
139
|
-
results[:nodes].uniq!
|
139
|
+
results[:nodes] << get_arg_input(nodes).split(/[[:space:],]+/).reject(&:empty?)
|
140
140
|
end
|
141
141
|
end
|
142
142
|
opts.on('-u', '--user USER',
|
@@ -215,6 +215,10 @@ HELP
|
|
215
215
|
'Specify where to load the config file from') do |path|
|
216
216
|
results[:configfile] = path
|
217
217
|
end
|
218
|
+
opts.on('--inventoryfile INVENTORY_PATH',
|
219
|
+
'Specify where to load the invenotry file from') do |path|
|
220
|
+
results[:inventoryfile] = path
|
221
|
+
end
|
218
222
|
opts.on_tail('--[no-]tty',
|
219
223
|
"Request a pseudo TTY on nodes that support it") do |tty|
|
220
224
|
results[:tty] = tty
|
@@ -311,11 +315,6 @@ HELP
|
|
311
315
|
raise e
|
312
316
|
end
|
313
317
|
|
314
|
-
def parse_nodes(nodes)
|
315
|
-
list = get_arg_input(nodes)
|
316
|
-
Target.parse_urls(list)
|
317
|
-
end
|
318
|
-
|
319
318
|
def parse_params(params)
|
320
319
|
json = get_arg_input(params)
|
321
320
|
JSON.parse(json)
|
@@ -401,6 +400,15 @@ HELP
|
|
401
400
|
end
|
402
401
|
|
403
402
|
def execute(options)
|
403
|
+
message = nil
|
404
|
+
|
405
|
+
handler = Signal.trap :INT do |signo|
|
406
|
+
@logger.info(
|
407
|
+
"Exiting after receiving SIG#{Signal.signame(signo)} signal." << (message ? ' ' << message : '')
|
408
|
+
)
|
409
|
+
exit!
|
410
|
+
end
|
411
|
+
|
404
412
|
if options[:mode] == 'plan' || options[:mode] == 'task'
|
405
413
|
pal = Bolt::PAL.new(@config)
|
406
414
|
end
|
@@ -426,15 +434,18 @@ HELP
|
|
426
434
|
return 0
|
427
435
|
end
|
428
436
|
|
437
|
+
inventory = Bolt::Inventory.from_config(@config)
|
438
|
+
message = 'There may be processes left executing on some nodes.'
|
439
|
+
|
429
440
|
if options[:mode] == 'plan'
|
430
441
|
executor = Bolt::Executor.new(@config, options[:noop], true)
|
431
|
-
result = pal.run_plan(options[:object], options[:task_options], executor)
|
442
|
+
result = pal.run_plan(options[:object], options[:task_options], executor, inventory)
|
432
443
|
outputter.print_plan_result(result)
|
433
444
|
# An exception would have been raised if the plan failed
|
434
445
|
code = 0
|
435
446
|
else
|
436
447
|
executor = Bolt::Executor.new(@config, options[:noop])
|
437
|
-
targets = options[:nodes]
|
448
|
+
targets = inventory.get_targets(options[:nodes])
|
438
449
|
|
439
450
|
results = nil
|
440
451
|
outputter.print_head
|
@@ -458,7 +469,8 @@ HELP
|
|
458
469
|
pal.run_task(options[:object],
|
459
470
|
targets,
|
460
471
|
options[:task_options],
|
461
|
-
executor
|
472
|
+
executor,
|
473
|
+
inventory) do |event|
|
462
474
|
outputter.print_event(event)
|
463
475
|
end
|
464
476
|
when 'file'
|
@@ -482,6 +494,9 @@ HELP
|
|
482
494
|
rescue Bolt::Error => e
|
483
495
|
outputter.fatal_error(e)
|
484
496
|
raise e
|
497
|
+
ensure
|
498
|
+
# restore original signal handler
|
499
|
+
Signal.trap :INT, handler if handler
|
485
500
|
end
|
486
501
|
|
487
502
|
def validate_file(type, path)
|
data/lib/bolt/config.rb
CHANGED
@@ -6,6 +6,7 @@ module Bolt
|
|
6
6
|
Config = Struct.new(
|
7
7
|
:concurrency,
|
8
8
|
:format,
|
9
|
+
:inventoryfile,
|
9
10
|
:log_level,
|
10
11
|
:modulepath,
|
11
12
|
:transport,
|
@@ -69,40 +70,15 @@ module Bolt
|
|
69
70
|
[File.join(root_path, 'bolt.yaml'), File.join(root_path, 'bolt.yml')]
|
70
71
|
end
|
71
72
|
|
72
|
-
def read_config_file(path)
|
73
|
-
path_passed = path
|
74
|
-
if path.nil?
|
75
|
-
found_default = default_paths.select { |p| File.exist?(p) }
|
76
|
-
if found_default.size > 1
|
77
|
-
@logger.warn "Config files found at #{found_default.join(', ')}, using the first"
|
78
|
-
end
|
79
|
-
# Use first found, fall back to first default and try to load even if it didn't exist
|
80
|
-
path = found_default.first || default_paths.first
|
81
|
-
end
|
82
|
-
|
83
|
-
path = File.expand_path(path)
|
84
|
-
# safe_load doesn't work with psych in ruby 2.0
|
85
|
-
# The user controls the configfile so this isn't a problem
|
86
|
-
# rubocop:disable YAMLLoad
|
87
|
-
File.open(path, "r:UTF-8") { |f| YAML.load(f.read) }
|
88
|
-
rescue Errno::ENOENT
|
89
|
-
if path_passed
|
90
|
-
raise Bolt::CLIError, "Could not read config file: #{path}"
|
91
|
-
end
|
92
|
-
# In older releases of psych SyntaxError is not a subclass of Exception
|
93
|
-
rescue Psych::SyntaxError
|
94
|
-
raise Bolt::CLIError, "Could not parse config file: #{path}"
|
95
|
-
rescue Psych::Exception
|
96
|
-
raise Bolt::CLIError, "Could not parse config file: #{path}"
|
97
|
-
rescue IOError, SystemCallError
|
98
|
-
raise Bolt::CLIError, "Could not read config file: #{path}"
|
99
|
-
end
|
100
|
-
|
101
73
|
def update_from_file(data)
|
102
74
|
if data['modulepath']
|
103
75
|
self[:modulepath] = data['modulepath'].split(File::PATH_SEPARATOR)
|
104
76
|
end
|
105
77
|
|
78
|
+
if data['inventoryfile']
|
79
|
+
self[:inventoryfile] = data['inventoryfile']
|
80
|
+
end
|
81
|
+
|
106
82
|
if data['concurrency']
|
107
83
|
self[:concurrency] = data['concurrency']
|
108
84
|
end
|
@@ -166,12 +142,12 @@ module Bolt
|
|
166
142
|
end
|
167
143
|
|
168
144
|
def load_file(path)
|
169
|
-
data = read_config_file(path)
|
145
|
+
data = Bolt::Util.read_config_file(path, default_paths, 'config')
|
170
146
|
update_from_file(data) if data
|
171
147
|
end
|
172
148
|
|
173
149
|
def update_from_cli(options)
|
174
|
-
%i[concurrency transport format modulepath].each do |key|
|
150
|
+
%i[concurrency transport format modulepath inventoryfile].each do |key|
|
175
151
|
self[key] = options[key] if options[key]
|
176
152
|
end
|
177
153
|
|
@@ -200,6 +176,11 @@ module Bolt
|
|
200
176
|
end
|
201
177
|
end
|
202
178
|
|
179
|
+
def transport_conf
|
180
|
+
{ transport: self[:transport],
|
181
|
+
transports: self[:transports] }
|
182
|
+
end
|
183
|
+
|
203
184
|
def validate
|
204
185
|
TRANSPORTS.each do |transport|
|
205
186
|
self[:transports][transport]
|
data/lib/bolt/error.rb
CHANGED
@@ -25,20 +25,31 @@ module Bolt
|
|
25
25
|
def to_json(opts = nil)
|
26
26
|
to_h.to_json(opts)
|
27
27
|
end
|
28
|
+
|
29
|
+
def to_puppet_error
|
30
|
+
Puppet::DataTypes::Error.from_asserted_hash(to_h)
|
31
|
+
end
|
28
32
|
end
|
29
33
|
|
30
34
|
class RunFailure < Error
|
31
|
-
attr_reader :
|
35
|
+
attr_reader :result_set
|
32
36
|
|
33
|
-
def initialize(
|
37
|
+
def initialize(result_set, action, object)
|
34
38
|
details = {
|
35
|
-
action
|
36
|
-
object
|
37
|
-
|
39
|
+
'action' => action,
|
40
|
+
'object' => object,
|
41
|
+
'result_set' => result_set
|
38
42
|
}
|
39
|
-
message = "Plan aborted: #{action} '#{object}' failed on #{
|
43
|
+
message = "Plan aborted: #{action} '#{object}' failed on #{result_set.error_set.length} nodes"
|
40
44
|
super(message, 'bolt/run-failure', details)
|
41
|
-
@
|
45
|
+
@result_set = result_set
|
46
|
+
@error_code = 2
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class PlanFailure < Error
|
51
|
+
def initialize(*args)
|
52
|
+
super(*args)
|
42
53
|
@error_code = 2
|
43
54
|
end
|
44
55
|
end
|
data/lib/bolt/executor.rb
CHANGED
@@ -28,7 +28,7 @@ module Bolt
|
|
28
28
|
|
29
29
|
def from_targets(targets)
|
30
30
|
targets.map do |target|
|
31
|
-
Bolt::Node.from_target(target
|
31
|
+
Bolt::Node.from_target(target)
|
32
32
|
end
|
33
33
|
end
|
34
34
|
private :from_targets
|
@@ -150,7 +150,7 @@ module Bolt
|
|
150
150
|
r
|
151
151
|
end
|
152
152
|
|
153
|
-
def file_upload(targets, source, destination)
|
153
|
+
def file_upload(targets, source, destination, options = {})
|
154
154
|
nodes = from_targets(targets)
|
155
155
|
@logger.info("Starting file upload from #{source} to #{destination} on #{nodes.map(&:uri)}")
|
156
156
|
callback = block_given? ? Proc.new : nil
|
@@ -158,7 +158,7 @@ module Bolt
|
|
158
158
|
r = on(nodes, callback) do |node|
|
159
159
|
@logger.debug { "Uploading: '#{source}' to #{destination} on #{node.uri}" }
|
160
160
|
node_result = with_exception_handling(node) do
|
161
|
-
node.upload(source, destination)
|
161
|
+
node.upload(source, destination, options)
|
162
162
|
end
|
163
163
|
@logger.debug("Result on #{node.uri}: #{JSON.dump(node_result.value)}")
|
164
164
|
node_result
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'bolt/util'
|
3
|
+
require 'bolt/target'
|
4
|
+
require 'bolt/inventory/group'
|
5
|
+
|
6
|
+
module Bolt
|
7
|
+
class Inventory
|
8
|
+
class ValidationError < Bolt::Error
|
9
|
+
attr_accessor :path
|
10
|
+
def initialize(message, offending_group)
|
11
|
+
super(msg, 'bolt.inventory/validation-error')
|
12
|
+
@_message = message
|
13
|
+
@path = offending_group ? [offending_group] : []
|
14
|
+
end
|
15
|
+
|
16
|
+
def details
|
17
|
+
{ 'path' => path }
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_parent(parent_group)
|
21
|
+
@path << parent_group
|
22
|
+
end
|
23
|
+
|
24
|
+
def message
|
25
|
+
"#{@_message} for group at #{path}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.default_paths
|
30
|
+
[File.expand_path(File.join('~', '.puppetlabs', 'bolt', 'inventory.yaml'))]
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.from_config(config)
|
34
|
+
data = Bolt::Util.read_config_file(config[:inventoryfile], default_paths, 'inventory')
|
35
|
+
|
36
|
+
inventory = new(data, config)
|
37
|
+
inventory.validate
|
38
|
+
inventory
|
39
|
+
end
|
40
|
+
|
41
|
+
def initialize(data, config = nil)
|
42
|
+
@logger = Logging.logger[self]
|
43
|
+
# Config is saved to add config options to targets
|
44
|
+
@config = config || {}
|
45
|
+
@data = data ||= {}
|
46
|
+
@groups = Group.new(data.merge('name' => 'all'))
|
47
|
+
end
|
48
|
+
|
49
|
+
def validate
|
50
|
+
@groups.validate
|
51
|
+
end
|
52
|
+
|
53
|
+
def get_targets(targets)
|
54
|
+
targets = expand_targets(targets)
|
55
|
+
targets = if targets.is_a? Array
|
56
|
+
targets.flatten.uniq(&:name)
|
57
|
+
else
|
58
|
+
[targets]
|
59
|
+
end
|
60
|
+
targets.map { |t| update_target(t) }
|
61
|
+
end
|
62
|
+
|
63
|
+
# Should this be a public method?
|
64
|
+
def config_for(node_name)
|
65
|
+
data = @groups.data_for(node_name)
|
66
|
+
if data
|
67
|
+
Bolt::Util.symbolize_keys(data['config'])
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
#### PRIVATE ####
|
72
|
+
#
|
73
|
+
# For debugging only now
|
74
|
+
def groups_in(node_name)
|
75
|
+
@groups.data_for(node_name)['groups'] || {}
|
76
|
+
end
|
77
|
+
|
78
|
+
# Pass a target to get_targets for a public version of this
|
79
|
+
# Should this reconfigure configured targets?
|
80
|
+
def update_target(target)
|
81
|
+
inv_conf = config_for(target.host)
|
82
|
+
unless inv_conf
|
83
|
+
@logger.debug("Did not find #{target.host} in inventory")
|
84
|
+
inv_conf = {}
|
85
|
+
end
|
86
|
+
|
87
|
+
conf = Bolt::Util.deep_merge(@config.transport_conf, inv_conf)
|
88
|
+
target.update_conf(conf)
|
89
|
+
end
|
90
|
+
|
91
|
+
def expand_targets(targets)
|
92
|
+
if targets.is_a? Bolt::Target
|
93
|
+
targets
|
94
|
+
elsif targets.is_a? Array
|
95
|
+
targets.map { |tish| expand_targets(tish) }
|
96
|
+
elsif targets.is_a? String
|
97
|
+
Bolt::Target.new(targets)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module Bolt
|
2
|
+
class Inventory
|
3
|
+
# Group is a specific implementation of Inventory based on nested
|
4
|
+
# structred data.
|
5
|
+
class Group
|
6
|
+
attr_accessor :name, :nodes, :groups, :config, :rest
|
7
|
+
|
8
|
+
def initialize(data)
|
9
|
+
@name = data['name']
|
10
|
+
|
11
|
+
@nodes = if data['nodes']
|
12
|
+
data['nodes'].map do |n|
|
13
|
+
if n.is_a? String
|
14
|
+
{ 'name' => n }
|
15
|
+
else
|
16
|
+
n
|
17
|
+
end
|
18
|
+
end
|
19
|
+
else
|
20
|
+
[]
|
21
|
+
end
|
22
|
+
@config = data['config'] || {}
|
23
|
+
@groups = if data['groups']
|
24
|
+
data['groups'].map { |g| Group.new(g) }
|
25
|
+
else
|
26
|
+
[]
|
27
|
+
end
|
28
|
+
|
29
|
+
# this allows arbitrary info for the top level
|
30
|
+
@rest = data.reject { |k, _| %w[name nodes config groups].include? k }
|
31
|
+
end
|
32
|
+
|
33
|
+
def validate(used_names = [], depth = 0)
|
34
|
+
raise ValidationError.new("Group does not have a name", nil) unless @name
|
35
|
+
if used_names.include?(@name)
|
36
|
+
raise ValidationError.new("Tried to redefine group #{@name}", @name)
|
37
|
+
end
|
38
|
+
raise ValidationError.new("Invalid Group name #{@name}", @name) unless @name =~ /\A[a-z0-9_]+\Z/
|
39
|
+
|
40
|
+
raise ValidationError.new("Group #{@name} is too deeply nested", @name) if depth > 1
|
41
|
+
|
42
|
+
used_names << @name
|
43
|
+
|
44
|
+
@nodes.each do |n|
|
45
|
+
raise ValidationError.new("node #{n['name']} does not have a name", @name) unless n['name']
|
46
|
+
end
|
47
|
+
|
48
|
+
@groups.each do |g|
|
49
|
+
begin
|
50
|
+
g.validate(used_names, depth + 1)
|
51
|
+
rescue ValidationError => e
|
52
|
+
e.add_parent(@name)
|
53
|
+
raise e
|
54
|
+
end
|
55
|
+
end
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
|
59
|
+
# The data functions below expect and return nil or a hash of the schema
|
60
|
+
# { 'config' => Hash , groups => Array }
|
61
|
+
# As we add more options beyond config this schema will grow
|
62
|
+
def data_for(node_name)
|
63
|
+
data_merge(group_collect(node_name), node_collect(node_name))
|
64
|
+
end
|
65
|
+
|
66
|
+
def node_data(node_name)
|
67
|
+
if (data = @nodes.find { |n| n['name'] == node_name })
|
68
|
+
{ 'config' => data['config'] || {},
|
69
|
+
# groups come from group_data
|
70
|
+
'groups' => [] }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def group_data
|
75
|
+
{ 'config' => @config,
|
76
|
+
'groups' => [@name] }
|
77
|
+
end
|
78
|
+
|
79
|
+
def empty_data
|
80
|
+
{ 'config' => {},
|
81
|
+
'groups' => [] }
|
82
|
+
end
|
83
|
+
|
84
|
+
def data_merge(data1, data2)
|
85
|
+
if data2.nil? || data1.nil?
|
86
|
+
return data2 || data1
|
87
|
+
end
|
88
|
+
|
89
|
+
{
|
90
|
+
'config' => Bolt::Util.deep_merge(data1['config'], data2['config']),
|
91
|
+
'groups' => data2['groups'] + data1['groups']
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
def node_names
|
96
|
+
@_node_names ||= Set.new(nodes.map { |n| n['name'] })
|
97
|
+
end
|
98
|
+
|
99
|
+
def node_collect(node_name)
|
100
|
+
data = @groups.inject(nil) do |acc, g|
|
101
|
+
if (d = g.node_collect(node_name))
|
102
|
+
data_merge(d, acc)
|
103
|
+
else
|
104
|
+
acc
|
105
|
+
end
|
106
|
+
end
|
107
|
+
data_merge(node_data(node_name), data)
|
108
|
+
end
|
109
|
+
|
110
|
+
def group_collect(node_name)
|
111
|
+
data = @groups.inject(nil) do |acc, g|
|
112
|
+
if (d = g.data_for(node_name))
|
113
|
+
data_merge(d, acc)
|
114
|
+
else
|
115
|
+
acc
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
if data
|
120
|
+
data_merge(group_data, data)
|
121
|
+
elsif node_names.include?(node_name)
|
122
|
+
group_data
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|