leap_cli 1.2.5

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 (72) hide show
  1. data/bin/leap +81 -0
  2. data/lib/core_ext/boolean.rb +14 -0
  3. data/lib/core_ext/hash.rb +35 -0
  4. data/lib/core_ext/json.rb +42 -0
  5. data/lib/core_ext/nil.rb +5 -0
  6. data/lib/core_ext/string.rb +14 -0
  7. data/lib/leap/platform.rb +52 -0
  8. data/lib/leap_cli/commands/ca.rb +430 -0
  9. data/lib/leap_cli/commands/clean.rb +16 -0
  10. data/lib/leap_cli/commands/compile.rb +134 -0
  11. data/lib/leap_cli/commands/deploy.rb +172 -0
  12. data/lib/leap_cli/commands/facts.rb +93 -0
  13. data/lib/leap_cli/commands/inspect.rb +140 -0
  14. data/lib/leap_cli/commands/list.rb +122 -0
  15. data/lib/leap_cli/commands/new.rb +126 -0
  16. data/lib/leap_cli/commands/node.rb +272 -0
  17. data/lib/leap_cli/commands/pre.rb +99 -0
  18. data/lib/leap_cli/commands/shell.rb +67 -0
  19. data/lib/leap_cli/commands/test.rb +55 -0
  20. data/lib/leap_cli/commands/user.rb +140 -0
  21. data/lib/leap_cli/commands/util.rb +50 -0
  22. data/lib/leap_cli/commands/vagrant.rb +201 -0
  23. data/lib/leap_cli/config/macros.rb +369 -0
  24. data/lib/leap_cli/config/manager.rb +369 -0
  25. data/lib/leap_cli/config/node.rb +37 -0
  26. data/lib/leap_cli/config/object.rb +336 -0
  27. data/lib/leap_cli/config/object_list.rb +174 -0
  28. data/lib/leap_cli/config/secrets.rb +43 -0
  29. data/lib/leap_cli/config/tag.rb +18 -0
  30. data/lib/leap_cli/constants.rb +7 -0
  31. data/lib/leap_cli/leapfile.rb +97 -0
  32. data/lib/leap_cli/load_paths.rb +15 -0
  33. data/lib/leap_cli/log.rb +166 -0
  34. data/lib/leap_cli/logger.rb +216 -0
  35. data/lib/leap_cli/markdown_document_listener.rb +134 -0
  36. data/lib/leap_cli/path.rb +84 -0
  37. data/lib/leap_cli/remote/leap_plugin.rb +204 -0
  38. data/lib/leap_cli/remote/puppet_plugin.rb +66 -0
  39. data/lib/leap_cli/remote/rsync_plugin.rb +35 -0
  40. data/lib/leap_cli/remote/tasks.rb +36 -0
  41. data/lib/leap_cli/requirements.rb +19 -0
  42. data/lib/leap_cli/ssh_key.rb +130 -0
  43. data/lib/leap_cli/util/remote_command.rb +110 -0
  44. data/lib/leap_cli/util/secret.rb +54 -0
  45. data/lib/leap_cli/util/x509.rb +32 -0
  46. data/lib/leap_cli/util.rb +431 -0
  47. data/lib/leap_cli/version.rb +9 -0
  48. data/lib/leap_cli.rb +46 -0
  49. data/lib/lib_ext/capistrano_connections.rb +16 -0
  50. data/lib/lib_ext/gli.rb +52 -0
  51. data/lib/lib_ext/markdown_document_listener.rb +122 -0
  52. data/vendor/certificate_authority/lib/certificate_authority/certificate.rb +200 -0
  53. data/vendor/certificate_authority/lib/certificate_authority/certificate_revocation_list.rb +77 -0
  54. data/vendor/certificate_authority/lib/certificate_authority/distinguished_name.rb +97 -0
  55. data/vendor/certificate_authority/lib/certificate_authority/extensions.rb +266 -0
  56. data/vendor/certificate_authority/lib/certificate_authority/key_material.rb +148 -0
  57. data/vendor/certificate_authority/lib/certificate_authority/ocsp_handler.rb +144 -0
  58. data/vendor/certificate_authority/lib/certificate_authority/pkcs11_key_material.rb +65 -0
  59. data/vendor/certificate_authority/lib/certificate_authority/revocable.rb +14 -0
  60. data/vendor/certificate_authority/lib/certificate_authority/serial_number.rb +10 -0
  61. data/vendor/certificate_authority/lib/certificate_authority/signing_entity.rb +16 -0
  62. data/vendor/certificate_authority/lib/certificate_authority/signing_request.rb +56 -0
  63. data/vendor/certificate_authority/lib/certificate_authority.rb +21 -0
  64. data/vendor/rsync_command/lib/rsync_command/ssh_options.rb +159 -0
  65. data/vendor/rsync_command/lib/rsync_command/thread_pool.rb +36 -0
  66. data/vendor/rsync_command/lib/rsync_command/version.rb +3 -0
  67. data/vendor/rsync_command/lib/rsync_command.rb +96 -0
  68. data/vendor/rsync_command/test/rsync_test.rb +74 -0
  69. data/vendor/rsync_command/test/ssh_options_test.rb +61 -0
  70. data/vendor/vagrant_ssh_keys/vagrant.key +27 -0
  71. data/vendor/vagrant_ssh_keys/vagrant.pub +1 -0
  72. metadata +345 -0
@@ -0,0 +1,369 @@
1
+ require 'json/pure'
2
+
3
+ if $ruby_version < [1,9]
4
+ require 'iconv'
5
+ end
6
+
7
+ module LeapCli
8
+ module Config
9
+
10
+ #
11
+ # A class to manage all the objects in all the configuration files.
12
+ #
13
+ class Manager
14
+
15
+ ##
16
+ ## ATTRIBUTES
17
+ ##
18
+
19
+ attr_reader :services, :tags, :nodes, :provider, :common, :secrets
20
+ attr_reader :base_services, :base_tags, :base_provider, :base_common
21
+
22
+ #
23
+ # returns the Hash of the contents of facts.json
24
+ #
25
+ def facts
26
+ @facts ||= JSON.parse(Util.read_file(:facts) || "{}")
27
+ end
28
+
29
+ #
30
+ # returns an Array of all the environments defined for this provider.
31
+ # the returned array includes nil (for the default environment)
32
+ #
33
+ def environments
34
+ @environments ||= [nil] + self.tags.collect {|name, tag| tag['environment']}.compact
35
+ end
36
+
37
+ ##
38
+ ## IMPORT EXPORT
39
+ ##
40
+
41
+ #
42
+ # load .json configuration files
43
+ #
44
+ def load(options = {})
45
+ @provider_dir = Path.provider
46
+
47
+ # load base
48
+ @base_services = load_all_json(Path.named_path([:service_config, '*'], Path.provider_base), Config::Tag)
49
+ @base_tags = load_all_json(Path.named_path([:tag_config, '*'], Path.provider_base), Config::Tag)
50
+ @base_common = load_json(Path.named_path(:common_config, Path.provider_base), Config::Object)
51
+ @base_provider = load_json(Path.named_path(:provider_config, Path.provider_base), Config::Object)
52
+
53
+ # load provider
54
+ provider_path = Path.named_path(:provider_config, @provider_dir)
55
+ common_path = Path.named_path(:common_config, @provider_dir)
56
+ Util::assert_files_exist!(provider_path, common_path)
57
+ @services = load_all_json(Path.named_path([:service_config, '*'], @provider_dir), Config::Tag)
58
+ @tags = load_all_json(Path.named_path([:tag_config, '*'], @provider_dir), Config::Tag)
59
+ @nodes = load_all_json(Path.named_path([:node_config, '*'], @provider_dir), Config::Node)
60
+ @common = load_json(common_path, Config::Object)
61
+ @provider = load_json(provider_path, Config::Object)
62
+ @secrets = load_json(Path.named_path(:secrets_config, @provider_dir), Config::Secrets)
63
+
64
+ # inherit
65
+ @services.inherit_from! base_services
66
+ @tags.inherit_from! base_tags
67
+ @common.inherit_from! base_common
68
+ @provider.inherit_from! base_provider
69
+ @nodes.each do |name, node|
70
+ Util::assert! name =~ /^[0-9a-z-]+$/, "Illegal character(s) used in node name '#{name}'"
71
+ @nodes[name] = apply_inheritance(node)
72
+ end
73
+
74
+ unless options[:include_disabled]
75
+ remove_disabled_nodes
76
+ end
77
+
78
+ # validate
79
+ validate_provider(@provider)
80
+ end
81
+
82
+ #
83
+ # save compiled hiera .yaml files
84
+ #
85
+ # if a node_list is specified, only update those .yaml files.
86
+ # otherwise, update all files, destroying files that are no longer used.
87
+ #
88
+ def export_nodes(node_list=nil)
89
+ updated_hiera = []
90
+ updated_files = []
91
+ existing_hiera = nil
92
+ existing_files = nil
93
+
94
+ unless node_list
95
+ node_list = self.nodes
96
+ existing_hiera = Dir.glob(Path.named_path([:hiera, '*'], @provider_dir))
97
+ existing_files = Dir.glob(Path.named_path([:node_files_dir, '*'], @provider_dir))
98
+ end
99
+
100
+ node_list.each_node do |node|
101
+ filepath = Path.named_path([:node_files_dir, node.name], @provider_dir)
102
+ hierapath = Path.named_path([:hiera, node.name], @provider_dir)
103
+ Util::write_file!(hierapath, node.dump)
104
+ updated_files << filepath
105
+ updated_hiera << hierapath
106
+ end
107
+
108
+ if @disabled_nodes
109
+ # make disabled nodes appear as if they are still active
110
+ @disabled_nodes.each_node do |node|
111
+ updated_files << Path.named_path([:node_files_dir, node.name], @provider_dir)
112
+ updated_hiera << Path.named_path([:hiera, node.name], @provider_dir)
113
+ end
114
+ end
115
+
116
+ # remove files that are no longer needed
117
+ if existing_hiera
118
+ (existing_hiera - updated_hiera).each do |filepath|
119
+ Util::remove_file!(filepath)
120
+ end
121
+ end
122
+ if existing_files
123
+ (existing_files - updated_files).each do |filepath|
124
+ Util::remove_directory!(filepath)
125
+ end
126
+ end
127
+ end
128
+
129
+ def export_secrets(clean_unused_secrets = false)
130
+ if @secrets.any?
131
+ Util.write_file!([:secrets_config, @provider_dir], @secrets.dump_json(clean_unused_secrets) + "\n")
132
+ end
133
+ end
134
+
135
+ ##
136
+ ## FILTERING
137
+ ##
138
+
139
+ #
140
+ # returns a node list consisting only of nodes that satisfy the filter criteria.
141
+ #
142
+ # filter: condition [condition] [condition] [+condition]
143
+ # condition: [node_name | service_name | tag_name]
144
+ #
145
+ # if conditions is prefixed with +, then it works like an AND. Otherwise, it works like an OR.
146
+ #
147
+ def filter(filters)
148
+ if filters.empty?
149
+ return nodes
150
+ end
151
+ if filters[0] =~ /^\+/
152
+ # don't let the first filter have a + prefix
153
+ filters[0] = filters[0][1..-1]
154
+ end
155
+
156
+ node_list = Config::ObjectList.new
157
+ filters.each do |filter|
158
+ if filter =~ /^\+/
159
+ keep_list = nodes_for_name(filter[1..-1])
160
+ node_list.delete_if do |name, node|
161
+ if keep_list[name]
162
+ false
163
+ else
164
+ true
165
+ end
166
+ end
167
+ else
168
+ node_list.merge!(nodes_for_name(filter))
169
+ end
170
+ end
171
+ return node_list
172
+ end
173
+
174
+ #
175
+ # same as filter(), but exits if there is no matching nodes
176
+ #
177
+ def filter!(filters)
178
+ node_list = filter(filters)
179
+ Util::assert! node_list.any?, "Could not match any nodes from '#{filters.join ' '}'"
180
+ return node_list
181
+ end
182
+
183
+ #
184
+ # returns a single Config::Object that corresponds to a Node.
185
+ #
186
+ def node(name)
187
+ @nodes[name]
188
+ end
189
+
190
+ #
191
+ # returns a single node that is disabled
192
+ #
193
+ def disabled_node(name)
194
+ @disabled_nodes[name]
195
+ end
196
+
197
+ #
198
+ # yields each node, in sorted order
199
+ #
200
+ def each_node(&block)
201
+ nodes.each_node &block
202
+ end
203
+
204
+ def reload_node(node)
205
+ @nodes[node.name] = apply_inheritance(node)
206
+ end
207
+
208
+ private
209
+
210
+ def load_all_json(pattern, object_class)
211
+ results = Config::ObjectList.new
212
+ Dir.glob(pattern).each do |filename|
213
+ obj = load_json(filename, object_class)
214
+ if obj
215
+ name = File.basename(filename).sub(/\.json$/,'')
216
+ obj['name'] ||= name
217
+ results[name] = obj
218
+ end
219
+ end
220
+ results
221
+ end
222
+
223
+ def load_json(filename, object_class)
224
+ if !File.exists?(filename)
225
+ return object_class.new(self)
226
+ end
227
+
228
+ log :loading, filename, 3
229
+
230
+ #
231
+ # Read a JSON file, strip out comments.
232
+ #
233
+ # UTF8 is the default encoding for JSON, but others are allowed:
234
+ # https://www.ietf.org/rfc/rfc4627.txt
235
+ #
236
+ buffer = StringIO.new
237
+ File.open(filename, "rb") do |f|
238
+ while (line = f.gets)
239
+ next if line =~ /^\s*\/\//
240
+ buffer << line
241
+ end
242
+ end
243
+
244
+ #
245
+ # force UTF-8
246
+ #
247
+ if $ruby_version >= [1,9]
248
+ string = buffer.string.force_encoding('utf-8')
249
+ else
250
+ string = Iconv.conv("UTF-8//IGNORE", "UTF-8", buffer.string)
251
+ end
252
+
253
+ # parse json
254
+ begin
255
+ hash = JSON.parse(string, :object_class => Hash, :array_class => Array) || {}
256
+ rescue SyntaxError, JSON::ParserError => exc
257
+ log 0, :error, 'in file "%s":' % filename
258
+ log 0, exc.to_s, :indent => 1
259
+ return nil
260
+ end
261
+ object = object_class.new(self)
262
+ object.deep_merge!(hash)
263
+ return object
264
+ end
265
+
266
+ #
267
+ # remove all the nesting from a hash.
268
+ #
269
+ # def flatten_hash(input = {}, output = {}, options = {})
270
+ # input.each do |key, value|
271
+ # key = options[:prefix].nil? ? "#{key}" : "#{options[:prefix]}#{options[:delimiter]||"_"}#{key}"
272
+ # if value.is_a? Hash
273
+ # flatten_hash(value, output, :prefix => key, :delimiter => options[:delimiter])
274
+ # else
275
+ # output[key] = value
276
+ # end
277
+ # end
278
+ # output.replace(input)
279
+ # output
280
+ # end
281
+
282
+ #
283
+ # makes a node inherit options from appropriate the common, service, and tag json files.
284
+ #
285
+ def apply_inheritance(node)
286
+ new_node = Config::Node.new(self)
287
+ name = node.name
288
+
289
+ # inherit from common
290
+ new_node.deep_merge!(@common)
291
+
292
+ # inherit from services
293
+ if node['services']
294
+ node['services'].to_a.each do |node_service|
295
+ service = @services[node_service]
296
+ if service.nil?
297
+ log 0, :error, 'in node "%s": the service "%s" does not exist.' % [node['name'], node_service]
298
+ else
299
+ new_node.deep_merge!(service)
300
+ service.node_list.add(name, new_node)
301
+ end
302
+ end
303
+ end
304
+
305
+ # inherit from tags
306
+ if node.vagrant?
307
+ node['tags'] = (node['tags'] || []).to_a + ['local']
308
+ end
309
+ if node['tags']
310
+ node['tags'].to_a.each do |node_tag|
311
+ tag = @tags[node_tag]
312
+ if tag.nil?
313
+ log 0, :error, 'in node "%s": the tag "%s" does not exist.' % [node['name'], node_tag]
314
+ else
315
+ new_node.deep_merge!(tag)
316
+ tag.node_list.add(name, new_node)
317
+ end
318
+ end
319
+ end
320
+
321
+ # inherit from node
322
+ new_node.deep_merge!(node)
323
+ return new_node
324
+ end
325
+
326
+ def remove_disabled_nodes
327
+ @disabled_nodes = Config::ObjectList.new
328
+ @nodes.each do |name, node|
329
+ unless node.enabled
330
+ log 2, :skipping, "disabled node #{name}."
331
+ @nodes.delete(name)
332
+ @disabled_nodes[name] = node
333
+ if node['services']
334
+ node['services'].to_a.each do |node_service|
335
+ @services[node_service].node_list.delete(node.name)
336
+ end
337
+ end
338
+ if node['tags']
339
+ node['tags'].to_a.each do |node_tag|
340
+ @tags[node_tag].node_list.delete(node.name)
341
+ end
342
+ end
343
+ end
344
+ end
345
+ end
346
+
347
+
348
+ #
349
+ # returns a set of nodes corresponding to a single name, where name could be a node name, service name, or tag name.
350
+ #
351
+ def nodes_for_name(name)
352
+ if node = self.nodes[name]
353
+ Config::ObjectList.new(node)
354
+ elsif service = self.services[name]
355
+ service.node_list
356
+ elsif tag = self.tags[name]
357
+ tag.node_list
358
+ else
359
+ {}
360
+ end
361
+ end
362
+
363
+ def validate_provider(provider)
364
+ # nothing yet.
365
+ end
366
+
367
+ end
368
+ end
369
+ end
@@ -0,0 +1,37 @@
1
+ #
2
+ # Configuration for a 'node' (a server in the provider's infrastructure)
3
+ #
4
+
5
+ require 'ipaddr'
6
+
7
+ module LeapCli; module Config
8
+
9
+ class Node < Object
10
+ attr_accessor :file_paths
11
+
12
+ def initialize(manager=nil)
13
+ super(manager)
14
+ @node = self
15
+ @file_paths = []
16
+ end
17
+
18
+ #
19
+ # returns true if this node has an ip address in the range of the vagrant network
20
+ #
21
+ def vagrant?
22
+ begin
23
+ vagrant_range = IPAddr.new LeapCli.leapfile.vagrant_network
24
+ rescue ArgumentError => exc
25
+ Util::bail! { Util::log :invalid, "ip address '#{@node.ip_address}' vagrant.network" }
26
+ end
27
+
28
+ begin
29
+ ip_address = IPAddr.new @node.get('ip_address')
30
+ rescue ArgumentError => exc
31
+ Util::log :warning, "invalid ip address '#{@node.get('ip_address')}' for node '#{@node.name}'"
32
+ end
33
+ return vagrant_range.include?(ip_address)
34
+ end
35
+ end
36
+
37
+ end; end