leap_cli 1.5.6 → 1.6.2

Sign up to get free protection for your applications and to get access to all the features.
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