leap_cli 1.5.6 → 1.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. data/bin/leap +29 -6
  2. data/lib/leap/platform.rb +36 -1
  3. data/lib/leap_cli/commands/ca.rb +97 -20
  4. data/lib/leap_cli/commands/compile.rb +49 -8
  5. data/lib/leap_cli/commands/db.rb +13 -4
  6. data/lib/leap_cli/commands/deploy.rb +138 -29
  7. data/lib/leap_cli/commands/env.rb +76 -0
  8. data/lib/leap_cli/commands/facts.rb +10 -3
  9. data/lib/leap_cli/commands/inspect.rb +2 -2
  10. data/lib/leap_cli/commands/list.rb +10 -10
  11. data/lib/leap_cli/commands/node.rb +7 -132
  12. data/lib/leap_cli/commands/node_init.rb +169 -0
  13. data/lib/leap_cli/commands/pre.rb +4 -27
  14. data/lib/leap_cli/commands/ssh.rb +152 -0
  15. data/lib/leap_cli/commands/test.rb +22 -13
  16. data/lib/leap_cli/commands/user.rb +12 -4
  17. data/lib/leap_cli/commands/vagrant.rb +4 -4
  18. data/lib/leap_cli/config/filter.rb +175 -0
  19. data/lib/leap_cli/config/manager.rb +130 -61
  20. data/lib/leap_cli/config/node.rb +32 -0
  21. data/lib/leap_cli/config/object.rb +69 -44
  22. data/lib/leap_cli/config/object_list.rb +44 -39
  23. data/lib/leap_cli/config/secrets.rb +24 -12
  24. data/lib/leap_cli/config/tag.rb +7 -0
  25. data/lib/{core_ext → leap_cli/core_ext}/boolean.rb +0 -0
  26. data/lib/{core_ext → leap_cli/core_ext}/hash.rb +0 -0
  27. data/lib/{core_ext → leap_cli/core_ext}/json.rb +0 -0
  28. data/lib/{core_ext → leap_cli/core_ext}/nil.rb +0 -0
  29. data/lib/{core_ext → leap_cli/core_ext}/string.rb +0 -0
  30. data/lib/leap_cli/core_ext/yaml.rb +29 -0
  31. data/lib/leap_cli/exceptions.rb +24 -0
  32. data/lib/leap_cli/leapfile.rb +60 -10
  33. data/lib/{lib_ext → leap_cli/lib_ext}/capistrano_connections.rb +0 -0
  34. data/lib/{lib_ext → leap_cli/lib_ext}/gli.rb +0 -0
  35. data/lib/leap_cli/log.rb +1 -1
  36. data/lib/leap_cli/logger.rb +18 -1
  37. data/lib/leap_cli/markdown_document_listener.rb +1 -1
  38. data/lib/leap_cli/override/json.rb +11 -0
  39. data/lib/leap_cli/path.rb +20 -6
  40. data/lib/leap_cli/remote/leap_plugin.rb +2 -2
  41. data/lib/leap_cli/remote/puppet_plugin.rb +1 -1
  42. data/lib/leap_cli/remote/rsync_plugin.rb +1 -1
  43. data/lib/leap_cli/remote/tasks.rb +1 -1
  44. data/lib/leap_cli/ssh_key.rb +63 -1
  45. data/lib/leap_cli/util/remote_command.rb +19 -2
  46. data/lib/leap_cli/util/secret.rb +1 -1
  47. data/lib/leap_cli/util/x509.rb +3 -2
  48. data/lib/leap_cli/util.rb +11 -3
  49. data/lib/leap_cli/version.rb +2 -2
  50. data/lib/leap_cli.rb +24 -14
  51. data/vendor/certificate_authority/lib/certificate_authority/certificate.rb +85 -29
  52. data/vendor/certificate_authority/lib/certificate_authority/distinguished_name.rb +5 -0
  53. data/vendor/certificate_authority/lib/certificate_authority/extensions.rb +406 -41
  54. data/vendor/certificate_authority/lib/certificate_authority/key_material.rb +0 -34
  55. data/vendor/certificate_authority/lib/certificate_authority/serial_number.rb +6 -0
  56. data/vendor/certificate_authority/lib/certificate_authority/signing_request.rb +36 -1
  57. metadata +25 -24
  58. data/lib/leap_cli/commands/shell.rb +0 -89
  59. data/lib/leap_cli/config/macros.rb +0 -430
  60. data/lib/leap_cli/constants.rb +0 -7
  61. data/lib/leap_cli/requirements.rb +0 -19
  62. data/lib/lib_ext/markdown_document_listener.rb +0 -122
@@ -20,6 +20,16 @@ module LeapCli
20
20
 
21
21
  def initialize
22
22
  @environments = {} # hash of `Environment` objects, keyed by name.
23
+
24
+ # load macros and other custom ruby in provider base
25
+ platform_ruby_files = Dir[Path.provider_base + '/lib/*.rb']
26
+ if platform_ruby_files.any?
27
+ $: << Path.provider_base + '/lib'
28
+ platform_ruby_files.each do |rb_file|
29
+ require rb_file
30
+ end
31
+ end
32
+ Config::Object.send(:include, LeapCli::Macro)
23
33
  end
24
34
 
25
35
  ##
@@ -54,9 +64,24 @@ module LeapCli
54
64
  e
55
65
  end
56
66
 
57
- def services; env('default').services; end
58
- def tags; env('default').tags; end
59
- def provider; env('default').provider; end
67
+ #
68
+ # The default accessors for services, tags, and provider.
69
+ # For these defaults, use 'default' environment, or whatever
70
+ # environment is pinned.
71
+ #
72
+ def services
73
+ env(default_environment).services
74
+ end
75
+ def tags
76
+ env(default_environment).tags
77
+ end
78
+ def provider
79
+ env(default_environment).provider
80
+ end
81
+
82
+ def default_environment
83
+ LeapCli.leapfile.environment
84
+ end
60
85
 
61
86
  ##
62
87
  ## IMPORT EXPORT
@@ -80,8 +105,8 @@ module LeapCli
80
105
  @secrets = load_json( Path.named_path(:secrets_config, @provider_dir), Config::Secrets)
81
106
  @common.inherit_from! @base_common
82
107
 
83
- # load provider services, tags, and provider.json, DEFAULT environment
84
- log 3, :loading, 'default environment.........'
108
+ # For the default environment, load provider services, tags, and provider.json
109
+ log 3, :loading, 'default environment...'
85
110
  env('default') do |e|
86
111
  e.services = load_all_json(Path.named_path([:service_config, '*'], @provider_dir), Config::Tag, :no_dots => true)
87
112
  e.tags = load_all_json(Path.named_path([:tag_config, '*'], @provider_dir), Config::Tag, :no_dots => true)
@@ -92,28 +117,54 @@ module LeapCli
92
117
  validate_provider(e.provider)
93
118
  end
94
119
 
95
- # load provider services, tags, and provider.json, OTHER environments
120
+ # create a special '_all_' environment, used for tracking the union
121
+ # of all the environments
122
+ env('_all_') do |e|
123
+ e.services = Config::ObjectList.new
124
+ e.tags = Config::ObjectList.new
125
+ e.provider = Config::Provider.new
126
+ e.services.inherit_from! env('default').services
127
+ e.tags.inherit_from! env('default').tags
128
+ e.provider.inherit_from! env('default').provider
129
+ end
130
+
131
+ # For each defined environment, load provider services, tags, and provider.json.
96
132
  environment_names.each do |ename|
97
133
  next unless ename
98
- log 3, :loading, '%s environment.........' % ename
134
+ log 3, :loading, '%s environment...' % ename
99
135
  env(ename) do |e|
100
136
  e.services = load_all_json(Path.named_path([:service_env_config, '*', ename], @provider_dir), Config::Tag)
101
137
  e.tags = load_all_json(Path.named_path([:tag_env_config, '*', ename], @provider_dir), Config::Tag)
102
138
  e.provider = load_json( Path.named_path([:provider_env_config, ename], @provider_dir), Config::Provider)
103
- e.services.inherit_from! env.services
104
- e.tags.inherit_from! env.tags
105
- e.provider.inherit_from! env.provider
139
+ e.services.inherit_from! env('default').services
140
+ e.tags.inherit_from! env('default').tags
141
+ e.provider.inherit_from! env('default').provider
106
142
  validate_provider(e.provider)
107
143
  end
108
144
  end
109
145
 
146
+ # apply inheritance
110
147
  @nodes.each do |name, node|
111
148
  Util::assert! name =~ /^[0-9a-z-]+$/, "Illegal character(s) used in node name '#{name}'"
112
149
  @nodes[name] = apply_inheritance(node)
113
150
  end
114
151
 
115
- unless options[:include_disabled]
116
- remove_disabled_nodes
152
+ # do some node-list post-processing
153
+ cleanup_node_lists(options)
154
+
155
+ # apply control files
156
+ @nodes.each do |name, node|
157
+ control_files(node).each do |file|
158
+ begin
159
+ node.eval_file file
160
+ rescue ConfigError => exc
161
+ if options[:continue_on_error]
162
+ exc.log
163
+ else
164
+ raise exc
165
+ end
166
+ end
167
+ end
117
168
  end
118
169
  end
119
170
 
@@ -178,35 +229,19 @@ module LeapCli
178
229
  # returns a node list consisting only of nodes that satisfy the filter criteria.
179
230
  #
180
231
  # filter: condition [condition] [condition] [+condition]
181
- # condition: [node_name | service_name | tag_name]
232
+ # condition: [node_name | service_name | tag_name | environment_name]
182
233
  #
183
234
  # if conditions is prefixed with +, then it works like an AND. Otherwise, it works like an OR.
184
235
  #
185
- def filter(filters)
186
- if filters.empty?
187
- return nodes
188
- end
189
- if filters[0] =~ /^\+/
190
- # don't let the first filter have a + prefix
191
- filters[0] = filters[0][1..-1]
192
- end
193
-
194
- node_list = Config::ObjectList.new
195
- filters.each do |filter|
196
- if filter =~ /^\+/
197
- keep_list = nodes_for_name(filter[1..-1])
198
- node_list.delete_if do |name, node|
199
- if keep_list[name]
200
- false
201
- else
202
- true
203
- end
204
- end
205
- else
206
- node_list.merge!(nodes_for_name(filter))
207
- end
208
- end
209
- return node_list
236
+ # args:
237
+ # filter -- array of filter terms, one per item
238
+ #
239
+ # options:
240
+ # :local -- if :local is false and the filter is empty, then local nodes are excluded.
241
+ # :nopin -- if true, ignore environment pinning
242
+ #
243
+ def filter(filters=nil, options={})
244
+ Filter.new(filters, options, self).nodes()
210
245
  end
211
246
 
212
247
  #
@@ -248,6 +283,28 @@ module LeapCli
248
283
  @nodes[node.name] = apply_inheritance!(node)
249
284
  end
250
285
 
286
+ #
287
+ # returns all the partial data for the specified partial path.
288
+ # partial path is always relative to provider root, but there must be multiple files
289
+ # that match because provider root might be the base provider or the local provider.
290
+ #
291
+ def partials(partial_path)
292
+ @partials ||= {}
293
+ if @partials[partial_path].nil?
294
+ [Path.provider_base, Path.provider].each do |provider_dir|
295
+ path = File.join(provider_dir, partial_path)
296
+ if File.exists?(path)
297
+ @partials[partial_path] ||= []
298
+ @partials[partial_path] << load_json(path, Config::Object)
299
+ end
300
+ end
301
+ if @partials[partial_path].nil?
302
+ raise RuntimeError, 'no such partial path `%s`' % partial_path, caller
303
+ end
304
+ end
305
+ @partials[partial_path]
306
+ end
307
+
251
308
  private
252
309
 
253
310
  def load_all_json(pattern, object_class, options={})
@@ -358,7 +415,6 @@ module LeapCli
358
415
  raise LeapCli::ConfigError.new(node, "error " + msg) if throw_exceptions
359
416
  else
360
417
  new_node.deep_merge!(service)
361
- self.services[node_service].node_list.add(name, new_node)
362
418
  end
363
419
  end
364
420
  end
@@ -376,7 +432,6 @@ module LeapCli
376
432
  raise LeapCli::ConfigError.new(node, "error " + msg) if throw_exceptions
377
433
  else
378
434
  new_node.deep_merge!(tag)
379
- self.tags[node_tag].node_list.add(name, new_node)
380
435
  end
381
436
  end
382
437
  end
@@ -390,45 +445,59 @@ module LeapCli
390
445
  apply_inheritance(node, true)
391
446
  end
392
447
 
393
- def remove_disabled_nodes
448
+ #
449
+ # does some final clean at the end of loading nodes.
450
+ # this includes removing disabled nodes, and populating
451
+ # the services[x].node_list and tags[x].node_list
452
+ #
453
+ def cleanup_node_lists(options)
394
454
  @disabled_nodes = Config::ObjectList.new
395
455
  @nodes.each do |name, node|
396
- unless node.enabled
397
- log 2, :skipping, "disabled node #{name}."
398
- @nodes.delete(name)
399
- @disabled_nodes[name] = node
456
+ if node.enabled || options[:include_disabled]
400
457
  if node['services']
401
458
  node['services'].to_a.each do |node_service|
402
- self.services[node_service].node_list.delete(node.name)
459
+ env(node.environment).services[node_service].node_list.add(node.name, node)
460
+ env('_all_').services[node_service].node_list.add(node.name, node)
403
461
  end
404
462
  end
405
463
  if node['tags']
406
464
  node['tags'].to_a.each do |node_tag|
407
- self.tags[node_tag].node_list.delete(node.name)
465
+ env(node.environment).tags[node_tag].node_list.add(node.name, node)
466
+ env('_all_').tags[node_tag].node_list.add(node.name, node)
408
467
  end
409
468
  end
469
+ elsif !options[:include_disabled]
470
+ log 2, :skipping, "disabled node #{name}."
471
+ @nodes.delete(name)
472
+ @disabled_nodes[name] = node
410
473
  end
411
474
  end
412
475
  end
413
476
 
477
+ def validate_provider(provider)
478
+ # nothing yet.
479
+ end
414
480
 
415
481
  #
416
- # returns a set of nodes corresponding to a single name, where name could be a node name, service name, or tag name.
482
+ # returns a list of 'control' files for this node.
483
+ # a control file is like a service or a tag JSON file, but it contains
484
+ # raw ruby code that gets evaluated in the context of the node.
485
+ # Yes, this entirely breaks our functional programming model
486
+ # for JSON generation.
417
487
  #
418
- def nodes_for_name(name)
419
- if node = self.nodes[name]
420
- Config::ObjectList.new(node)
421
- elsif service = self.services[name]
422
- service.node_list
423
- elsif tag = self.tags[name]
424
- tag.node_list
425
- else
426
- {}
488
+ def control_files(node)
489
+ files = []
490
+ [Path.provider_base, @provider_dir].each do |provider_dir|
491
+ [['services', :service_config], ['tags', :tag_config]].each do |attribute, path_sym|
492
+ node[attribute].each do |attr_value|
493
+ path = Path.named_path([path_sym, "#{attr_value}.rb"], provider_dir).sub(/\.json$/,'')
494
+ if File.exists?(path)
495
+ files << path
496
+ end
497
+ end
498
+ end
427
499
  end
428
- end
429
-
430
- def validate_provider(provider)
431
- # nothing yet.
500
+ return files
432
501
  end
433
502
 
434
503
  end
@@ -33,6 +33,29 @@ module LeapCli; module Config
33
33
  return vagrant_range.include?(ip_address)
34
34
  end
35
35
 
36
+ #
37
+ # Return a hash table representation of ourselves, with the key equal to the @node.name,
38
+ # and the value equal to the fields specified in *keys.
39
+ #
40
+ # Also, the result is flattened to a single hash, so a key of 'a.b' becomes 'a_b'
41
+ #
42
+ # compare to Object#pick(*keys). This method is the sames as Config::ObjectList#pick_fields,
43
+ # but works on a single node.
44
+ #
45
+ # Example:
46
+ #
47
+ # node.pick('domain.internal') =>
48
+ #
49
+ # {
50
+ # 'node1': {
51
+ # 'domain_internal': 'node1.example.i'
52
+ # }
53
+ # }
54
+ #
55
+ def pick_fields(*keys)
56
+ {@node.name => self.pick(*keys)}
57
+ end
58
+
36
59
  #
37
60
  # can be overridden by the platform.
38
61
  # returns a list of node names that should be tested before this node
@@ -40,6 +63,15 @@ module LeapCli; module Config
40
63
  def test_dependencies
41
64
  []
42
65
  end
66
+
67
+ # returns a string list of supported ssh host key algorithms for this node.
68
+ # or an empty string if it could not be determined
69
+ def supported_ssh_host_key_algorithms
70
+ @host_key_algo ||= SshKey.supported_host_key_algorithms(
71
+ Util.read_file([:node_ssh_pub_key, @node.name])
72
+ )
73
+ end
74
+
43
75
  end
44
76
 
45
77
  end; end
@@ -8,8 +8,6 @@ if $ruby_version < [1,9]
8
8
  end
9
9
  require 'ya2yaml' # pure ruby yaml
10
10
 
11
- require 'leap_cli/config/macros'
12
-
13
11
  module LeapCli
14
12
  module Config
15
13
  #
@@ -20,8 +18,6 @@ module LeapCli
20
18
  #
21
19
  class Object < Hash
22
20
 
23
- include Config::Macros
24
-
25
21
  attr_reader :node
26
22
  attr_reader :manager
27
23
  alias :global :manager
@@ -44,7 +40,7 @@ module LeapCli
44
40
  #
45
41
  def dump_yaml
46
42
  evaluate(@node)
47
- ya2yaml(:syck_compatible => true)
43
+ sorted_ya2yaml(:syck_compatible => true)
48
44
  end
49
45
 
50
46
  #
@@ -68,6 +64,11 @@ module LeapCli
68
64
  get(key)
69
65
  end
70
66
 
67
+ # Overrride some default methods in Hash that are likely to
68
+ # be used as attributes.
69
+ alias_method :hkey, :key
70
+ def key; get('key'); end
71
+
71
72
  #
72
73
  # make hash addressable like an object (e.g. obj['name'] available as obj.name)
73
74
  #
@@ -134,7 +135,18 @@ module LeapCli
134
135
  #
135
136
  def deep_merge!(object, prefer_self=false)
136
137
  object.each do |key,new_value|
137
- old_value = self.fetch key, nil
138
+ if self.has_key?('+'+key)
139
+ mode = :add
140
+ old_value = self.fetch '+'+key, nil
141
+ self.delete('+'+key)
142
+ elsif self.has_key?('-'+key)
143
+ mode = :subtract
144
+ old_value = self.fetch '-'+key, nil
145
+ self.delete('-'+key)
146
+ else
147
+ mode = :normal
148
+ old_value = self.fetch key, nil
149
+ end
138
150
 
139
151
  # clean up boolean
140
152
  new_value = true if new_value == "true"
@@ -160,6 +172,18 @@ module LeapCli
160
172
  elsif new_value.is_a?(Array) && !old_value.is_a?(Array)
161
173
  (value = (new_value.dup << old_value).compact.uniq).delete('REQUIRED')
162
174
 
175
+ # merge two arrays
176
+ elsif old_value.is_a?(Array) && new_value.is_a?(Array)
177
+ if mode == :add
178
+ value = (old_value + new_value).sort.uniq
179
+ elsif mode == :subtract
180
+ value = new_value - old_value
181
+ elsif prefer_self
182
+ value = old_value
183
+ else
184
+ value = new_value
185
+ end
186
+
163
187
  # catch errors
164
188
  elsif type_mismatch?(old_value, new_value)
165
189
  raise 'Type mismatch. Cannot merge %s (%s) with %s (%s). Key is "%s", name is "%s".' % [
@@ -168,7 +192,7 @@ module LeapCli
168
192
  key, self.class
169
193
  ]
170
194
 
171
- # merge strings, numbers, and sometimes arrays
195
+ # merge simple strings & numbers
172
196
  else
173
197
  if prefer_self
174
198
  value = old_value
@@ -206,6 +230,10 @@ module LeapCli
206
230
  end
207
231
  end
208
232
 
233
+ def eval_file(filename)
234
+ evaluate_ruby(filename, File.read(filename))
235
+ end
236
+
209
237
  protected
210
238
 
211
239
  #
@@ -246,45 +274,42 @@ module LeapCli
246
274
  # (`key` is just passed for debugging purposes)
247
275
  #
248
276
  def evaluate_ruby(key, value)
249
- result = nil
250
- if LeapCli.log_level >= 2
251
- result = self.instance_eval(value)
252
- else
253
- begin
254
- result = self.instance_eval(value)
255
- rescue SystemStackError => exc
256
- Util::log 0, :error, "while evaluating node '#{self.name}'"
257
- Util::log 0, "offending key: #{key}", :indent => 1
258
- Util::log 0, "offending string: #{value}", :indent => 1
259
- Util::log 0, "STACK OVERFLOW, BAILING OUT. There must be an eval loop of death (variables with circular dependencies).", :indent => 1
260
- raise SystemExit.new(1)
261
- rescue FileMissing => exc
262
- Util::bail! do
263
- if exc.options[:missing]
264
- Util::log :missing, exc.options[:missing].gsub('$node', self.name)
265
- else
266
- Util::log :error, "while evaluating node '#{self.name}'"
267
- Util::log "offending key: #{key}", :indent => 1
268
- Util::log "offending string: #{value}", :indent => 1
269
- Util::log "error message: no file '#{exc}'", :indent => 1
270
- end
271
- end
272
- rescue AssertionFailed => exc
273
- Util.bail! do
274
- Util::log :failed, "assertion while evaluating node '#{self.name}'"
275
- Util::log 'assertion: %s' % exc.assertion, :indent => 1
276
- Util::log "offending key: #{key}", :indent => 1
277
- end
278
- rescue SyntaxError, StandardError => exc
279
- Util::bail! do
280
- Util::log :error, "while evaluating node '#{self.name}'"
281
- Util::log "offending key: #{key}", :indent => 1
282
- Util::log "offending string: #{value}", :indent => 1
283
- Util::log "error message: #{exc.inspect}", :indent => 1
284
- end
277
+ self.instance_eval(value, key, 1)
278
+ rescue ConfigError => exc
279
+ raise exc # pass through
280
+ rescue SystemStackError => exc
281
+ Util::log 0, :error, "while evaluating node '#{self.name}'"
282
+ Util::log 0, "offending key: #{key}", :indent => 1
283
+ Util::log 0, "offending string: #{value}", :indent => 1
284
+ Util::log 0, "STACK OVERFLOW, BAILING OUT. There must be an eval loop of death (variables with circular dependencies).", :indent => 1
285
+ raise SystemExit.new(1)
286
+ rescue FileMissing => exc
287
+ Util::bail! do
288
+ if exc.options[:missing]
289
+ Util::log :missing, exc.options[:missing].gsub('$node', self.name).gsub('$file', exc.path)
290
+ else
291
+ Util::log :error, "while evaluating node '#{self.name}'"
292
+ Util::log "offending key: #{key}", :indent => 1
293
+ Util::log "offending string: #{value}", :indent => 1
294
+ Util::log "error message: no file '#{exc}'", :indent => 1
285
295
  end
296
+ raise exc if DEBUG
297
+ end
298
+ rescue AssertionFailed => exc
299
+ Util.bail! do
300
+ Util::log :failed, "assertion while evaluating node '#{self.name}'"
301
+ Util::log 'assertion: %s' % exc.assertion, :indent => 1
302
+ Util::log "offending key: #{key}", :indent => 1
303
+ raise exc if DEBUG
304
+ end
305
+ rescue SyntaxError, StandardError => exc
306
+ Util::bail! do
307
+ Util::log :error, "while evaluating node '#{self.name}'"
308
+ Util::log "offending key: #{key}", :indent => 1
309
+ Util::log "offending string: #{value}", :indent => 1
310
+ Util::log "error message: #{exc.inspect}", :indent => 1
311
+ raise exc if DEBUG
286
312
  end
287
- return result
288
313
  end
289
314
 
290
315
  private
@@ -28,42 +28,22 @@ module LeapCli
28
28
  # nodes[:public_dns => true]
29
29
  # all nodes with public dns
30
30
  #
31
- # nodes[:services => 'openvpn', :services => 'tor']
31
+ # nodes[:services => 'openvpn', 'location.country_code' => 'US']
32
+ # all nodes with services containing 'openvpn' OR country code of US
33
+ #
34
+ # Sometimes, you want to do an OR condition with multiple conditions
35
+ # for the same field. Since hash keys must be unique, you can use
36
+ # an array representation instead:
37
+ #
38
+ # nodes[[:services, 'openvpn'], [:services, 'tor']]
32
39
  # nodes with openvpn OR tor service
33
40
  #
34
41
  # nodes[:services => 'openvpn'][:tags => 'production']
35
42
  # nodes with openvpn AND are production
36
43
  #
37
44
  def [](key)
38
- if key.is_a? Hash
39
- results = Config::ObjectList.new
40
- key.each do |field, match_value|
41
- field = field.is_a?(Symbol) ? field.to_s : field
42
- match_value = match_value.is_a?(Symbol) ? match_value.to_s : match_value
43
- if match_value.is_a?(String) && match_value =~ /^!/
44
- operator = :not_equal
45
- match_value = match_value.sub(/^!/, '')
46
- else
47
- operator = :equal
48
- end
49
- each do |name, config|
50
- value = config[field]
51
- if value.is_a? Array
52
- if operator == :equal && value.include?(match_value)
53
- results[name] = config
54
- elsif operator == :not_equal && !value.include?(match_value)
55
- results[name] = config
56
- end
57
- else
58
- if operator == :equal && value == match_value
59
- results[name] = config
60
- elsif operator == :not_equal && value != match_value
61
- results[name] = config
62
- end
63
- end
64
- end
65
- end
66
- results
45
+ if key.is_a?(Hash) || key.is_a?(Array)
46
+ filter(key)
67
47
  else
68
48
  super key.to_s
69
49
  end
@@ -81,15 +61,40 @@ module LeapCli
81
61
  end
82
62
  end
83
63
 
84
- # def <<(object)
85
- # if object.is_a? Config::ObjectList
86
- # self.merge!(object)
87
- # elsif object['name']
88
- # self[object['name']] = object
89
- # else
90
- # raise ArgumentError.new('argument must be a Config::Object or a Config::ObjectList')
91
- # end
92
- # end
64
+ #
65
+ # filters this object list, producing a new list.
66
+ # filter is an array or a hash. see []
67
+ #
68
+ def filter(filter)
69
+ results = Config::ObjectList.new
70
+ filter.each do |field, match_value|
71
+ field = field.is_a?(Symbol) ? field.to_s : field
72
+ match_value = match_value.is_a?(Symbol) ? match_value.to_s : match_value
73
+ if match_value.is_a?(String) && match_value =~ /^!/
74
+ operator = :not_equal
75
+ match_value = match_value.sub(/^!/, '')
76
+ else
77
+ operator = :equal
78
+ end
79
+ each do |name, config|
80
+ value = config[field]
81
+ if value.is_a? Array
82
+ if operator == :equal && value.include?(match_value)
83
+ results[name] = config
84
+ elsif operator == :not_equal && !value.include?(match_value)
85
+ results[name] = config
86
+ end
87
+ else
88
+ if operator == :equal && value == match_value
89
+ results[name] = config
90
+ elsif operator == :not_equal && value != match_value
91
+ results[name] = config
92
+ end
93
+ end
94
+ end
95
+ end
96
+ results
97
+ end
93
98
 
94
99
  def add(name, object)
95
100
  self[name] = object
@@ -13,6 +13,11 @@ module LeapCli; module Config
13
13
  @discovered_keys = {}
14
14
  end
15
15
 
16
+ # we can't use fetch() or get(), since those already have special meanings
17
+ def retrieve(key, environment=nil)
18
+ self.fetch(environment||'default', {})[key.to_s]
19
+ end
20
+
16
21
  def set(key, value, environment=nil)
17
22
  environment ||= 'default'
18
23
  key = key.to_s
@@ -23,22 +28,29 @@ module LeapCli; module Config
23
28
  end
24
29
 
25
30
  #
26
- # if only_discovered_keys is true, then we will only export
27
- # those secrets that have been discovered and the prior ones will be cleaned out.
31
+ # if clean is true, then only secrets that have been discovered
32
+ # during this run will be exported.
33
+ #
34
+ # if environment is also pinned, then we will clean those secrets
35
+ # just for that environment.
28
36
  #
29
- # this should only be triggered when all nodes have been processed, otherwise
30
- # secrets that are actually in use will get mistakenly removed.
37
+ # the clean argument should only be used when all nodes have
38
+ # been processed, otherwise secrets that are actually in use will
39
+ # get mistakenly removed.
31
40
  #
32
- def dump_json(only_discovered_keys=false)
33
- if only_discovered_keys
41
+ def dump_json(clean=false)
42
+ pinned_env = LeapCli.leapfile.environment
43
+ if clean
34
44
  self.each_key do |environment|
35
- self[environment].each_key do |key|
36
- unless @discovered_keys[environment] && @discovered_keys[environment][key]
37
- self[environment].delete(key)
45
+ if pinned_env.nil? || pinned_env == environment
46
+ self[environment].each_key do |key|
47
+ unless @discovered_keys[environment] && @discovered_keys[environment][key]
48
+ self[environment].delete(key)
49
+ end
50
+ end
51
+ if self[environment].empty?
52
+ self.delete(environment)
38
53
  end
39
- end
40
- if self[environment].empty?
41
- self.delete(environment)
42
54
  end
43
55
  end
44
56
  end
@@ -13,6 +13,13 @@ module LeapCli; module Config
13
13
  super(manager)
14
14
  @node_list = Config::ObjectList.new
15
15
  end
16
+
17
+ # don't copy the node list pointer when this object is dup'ed.
18
+ def initialize_copy(orig)
19
+ super
20
+ @node_list = Config::ObjectList.new
21
+ end
22
+
16
23
  end
17
24
 
18
25
  end; end
File without changes