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.

Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/lib/bolt/cli.rb +32 -17
  3. data/lib/bolt/config.rb +12 -31
  4. data/lib/bolt/error.rb +18 -7
  5. data/lib/bolt/executor.rb +3 -3
  6. data/lib/bolt/inventory.rb +101 -0
  7. data/lib/bolt/inventory/group.rb +127 -0
  8. data/lib/bolt/node.rb +10 -9
  9. data/lib/bolt/node/ssh.rb +86 -69
  10. data/lib/bolt/node/winrm.rb +3 -3
  11. data/lib/bolt/outputter/human.rb +1 -1
  12. data/lib/bolt/pal.rb +17 -11
  13. data/lib/bolt/result.rb +9 -4
  14. data/lib/bolt/target.rb +26 -11
  15. data/lib/bolt/util.rb +54 -0
  16. data/lib/bolt/version.rb +1 -1
  17. data/modules/boltlib/lib/puppet/functions/fail_plan.rb +27 -0
  18. data/modules/boltlib/lib/puppet/functions/file_upload.rb +4 -4
  19. data/modules/boltlib/lib/puppet/functions/run_command.rb +3 -3
  20. data/modules/boltlib/lib/puppet/functions/run_plan.rb +18 -4
  21. data/modules/boltlib/lib/puppet/functions/run_script.rb +5 -2
  22. data/modules/boltlib/lib/puppet/functions/run_task.rb +3 -3
  23. data/vendored/puppet/lib/puppet/datatypes/error.rb +5 -3
  24. data/vendored/puppet/lib/puppet/datatypes/impl/error.rb +10 -12
  25. data/vendored/puppet/lib/puppet/defaults.rb +33 -25
  26. data/vendored/puppet/lib/puppet/etc.rb +2 -2
  27. data/vendored/puppet/lib/puppet/external/pson/pure/generator.rb +1 -1
  28. data/vendored/puppet/lib/puppet/external/pson/pure/parser.rb +1 -1
  29. data/vendored/puppet/lib/puppet/face/config.rb +45 -0
  30. data/vendored/puppet/lib/puppet/face/module/generate.rb +5 -0
  31. data/vendored/puppet/lib/puppet/functions/annotate.rb +1 -1
  32. data/vendored/puppet/lib/puppet/functions/any.rb +1 -1
  33. data/vendored/puppet/lib/puppet/generate/type.rb +1 -1
  34. data/vendored/puppet/lib/puppet/gettext/config.rb +2 -2
  35. data/vendored/puppet/lib/puppet/gettext/stubs.rb +1 -1
  36. data/vendored/puppet/lib/puppet/interface/action.rb +11 -0
  37. data/vendored/puppet/lib/puppet/interface/action_builder.rb +8 -0
  38. data/vendored/puppet/lib/puppet/network/authstore.rb +1 -1
  39. data/vendored/puppet/lib/puppet/network/http/connection.rb +1 -1
  40. data/vendored/puppet/lib/puppet/parameter/boolean.rb +1 -1
  41. data/vendored/puppet/lib/puppet/parser/ast/branch.rb +1 -1
  42. data/vendored/puppet/lib/puppet/parser/functions/new.rb +1 -1
  43. data/vendored/puppet/lib/puppet/parser/functions/reverse_each.rb +1 -1
  44. data/vendored/puppet/lib/puppet/pops/evaluator/compare_operator.rb +1 -1
  45. data/vendored/puppet/lib/puppet/pops/evaluator/evaluator_impl.rb +3 -3
  46. data/vendored/puppet/lib/puppet/pops/evaluator/literal_evaluator.rb +1 -1
  47. data/vendored/puppet/lib/puppet/pops/evaluator/runtime3_converter.rb +1 -1
  48. data/vendored/puppet/lib/puppet/pops/evaluator/runtime3_support.rb +1 -1
  49. data/vendored/puppet/lib/puppet/pops/issues.rb +1 -1
  50. data/vendored/puppet/lib/puppet/pops/loader/loader.rb +1 -1
  51. data/vendored/puppet/lib/puppet/pops/loader/loader_paths.rb +1 -1
  52. data/vendored/puppet/lib/puppet/pops/loader/module_loaders.rb +3 -5
  53. data/vendored/puppet/lib/puppet/pops/loader/puppet_resource_type_impl_instantiator.rb +1 -1
  54. data/vendored/puppet/lib/puppet/pops/loader/runtime3_type_loader.rb +1 -1
  55. data/vendored/puppet/lib/puppet/pops/loaders.rb +40 -1
  56. data/vendored/puppet/lib/puppet/pops/lookup/interpolation.rb +1 -1
  57. data/vendored/puppet/lib/puppet/pops/model/tree_dumper.rb +1 -1
  58. data/vendored/puppet/lib/puppet/pops/parser/epp_support.rb +1 -1
  59. data/vendored/puppet/lib/puppet/pops/parser/interpolation_support.rb +1 -1
  60. data/vendored/puppet/lib/puppet/pops/parser/lexer2.rb +1 -1
  61. data/vendored/puppet/lib/puppet/pops/parser/lexer_support.rb +2 -2
  62. data/vendored/puppet/lib/puppet/pops/parser/locatable.rb +1 -1
  63. data/vendored/puppet/lib/puppet/pops/parser/locator.rb +2 -6
  64. data/vendored/puppet/lib/puppet/pops/resource/param.rb +1 -1
  65. data/vendored/puppet/lib/puppet/pops/resource/resource_type_impl.rb +1 -1
  66. data/vendored/puppet/lib/puppet/pops/types/iterable.rb +1 -1
  67. data/vendored/puppet/lib/puppet/pops/types/tree_iterators.rb +5 -1
  68. data/vendored/puppet/lib/puppet/pops/types/type_acceptor.rb +1 -1
  69. data/vendored/puppet/lib/puppet/pops/types/type_calculator.rb +1 -1
  70. data/vendored/puppet/lib/puppet/pops/types/type_with_members.rb +1 -1
  71. data/vendored/puppet/lib/puppet/pops/types/types.rb +1 -1
  72. data/vendored/puppet/lib/puppet/provider/package/yum.rb +1 -1
  73. data/vendored/puppet/lib/puppet/provider/service/redhat.rb +3 -2
  74. data/vendored/puppet/lib/puppet/provider/service/systemd.rb +1 -1
  75. data/vendored/puppet/lib/puppet/provider/ssh_authorized_key/parsed.rb +1 -1
  76. data/vendored/puppet/lib/puppet/resource/type.rb +1 -1
  77. data/vendored/puppet/lib/puppet/settings/base_setting.rb +1 -1
  78. data/vendored/puppet/lib/puppet/settings/ini_file.rb +33 -12
  79. data/vendored/puppet/lib/puppet/ssl/certificate_request.rb +2 -2
  80. data/vendored/puppet/lib/puppet/transaction.rb +37 -14
  81. data/vendored/puppet/lib/puppet/type/cron.rb +1 -1
  82. data/vendored/puppet/lib/puppet/type/file/checksum.rb +6 -0
  83. data/vendored/puppet/lib/puppet/type/mount.rb +0 -9
  84. data/vendored/puppet/lib/puppet/util/character_encoding.rb +2 -2
  85. data/vendored/puppet/lib/puppet/util/network_device/cisco/device.rb +5 -5
  86. data/vendored/puppet/lib/puppet/util/rdoc/generators/puppet_generator.rb +1 -1
  87. data/vendored/puppet/lib/puppet/util/windows/process.rb +1 -1
  88. data/vendored/puppet/lib/puppet/vendor/pathspec/lib/pathspec/gitignorespec.rb +18 -18
  89. data/vendored/puppet/lib/puppet/vendor/semantic_puppet/lib/semantic_puppet/version.rb +2 -2
  90. data/vendored/puppet/lib/puppet/vendor/semantic_puppet/lib/semantic_puppet/version_range.rb +5 -5
  91. metadata +20 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8583fc13483530b30cda78e12c42f207e07995d7
4
- data.tar.gz: 13ec2527cf65cac99c03e7db2bf718279c289751
3
+ metadata.gz: 4c145190f4fb70b998c8fd501a368940c2d2db3c
4
+ data.tar.gz: 45949da83f3903d369a71f88730e8ef7413cdff7
5
5
  SHA512:
6
- metadata.gz: b3dcfd21a5279b2fdf9fa35b66ed39f7207674a8d0cfe284bea12990fec93c294159638a88b93544d7a5818a0c093bb2d07595509ce8e1f6211ed6d587f381de
7
- data.tar.gz: e823df525ff7ef1c0726af8a9d17482df286a0fc3f3fb0088a4b6aa48667fb0fa556b4ea188ad24837bdd293057d8046b07f300317ca72371c8e074d1b6b4688
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 'bolt/logger'
7
- require 'bolt/node'
8
- require 'bolt/version'
6
+ require 'optparse'
7
+ require 'bolt/config'
9
8
  require 'bolt/error'
10
9
  require 'bolt/executor'
11
- require 'bolt/target'
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 'io/console'
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] += parse_nodes(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) do |event|
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 :resultset
35
+ attr_reader :result_set
32
36
 
33
- def initialize(resultset, action, object)
37
+ def initialize(result_set, action, object)
34
38
  details = {
35
- action: action,
36
- object: object,
37
- failed_targets: resultset.error_set.names
39
+ 'action' => action,
40
+ 'object' => object,
41
+ 'result_set' => result_set
38
42
  }
39
- message = "Plan aborted: #{action} '#{object}' failed on #{details[:failed_targets].length} nodes"
43
+ message = "Plan aborted: #{action} '#{object}' failed on #{result_set.error_set.length} nodes"
40
44
  super(message, 'bolt/run-failure', details)
41
- @resultset = resultset
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, config: @config)
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