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
@@ -1,430 +0,0 @@
1
- # encoding: utf-8
2
- #
3
- # MACROS
4
- # these are methods available when eval'ing a value in the .json configuration
5
- #
6
- # This module is included in Config::Object
7
- #
8
-
9
- require 'base32'
10
-
11
- module LeapCli; module Config
12
- module Macros
13
- ##
14
- ## NODES
15
- ##
16
-
17
- #
18
- # the list of all the nodes
19
- #
20
- def nodes
21
- global.nodes
22
- end
23
-
24
- #
25
- # grab an environment appropriate provider
26
- #
27
- def provider
28
- global.env(@node.environment).provider
29
- end
30
-
31
- #
32
- # returns a list of nodes that match the same environment
33
- #
34
- # if @node.environment is not set, we return other nodes
35
- # where environment is not set.
36
- #
37
- def nodes_like_me
38
- nodes[:environment => @node.environment]
39
- end
40
-
41
- ##
42
- ## FILES
43
- ##
44
-
45
- class FileMissing < Exception
46
- attr_accessor :path, :options
47
- def initialize(path, options={})
48
- @path = path
49
- @options = options
50
- end
51
- def to_s
52
- @path
53
- end
54
- end
55
-
56
- #
57
- # inserts the contents of a file
58
- #
59
- def file(filename, options={})
60
- if filename.is_a? Symbol
61
- filename = [filename, @node.name]
62
- end
63
- filepath = Path.find_file(filename)
64
- if filepath
65
- if filepath =~ /\.erb$/
66
- ERB.new(File.read(filepath, :encoding => 'UTF-8'), nil, '%<>').result(binding)
67
- else
68
- File.read(filepath, :encoding => 'UTF-8')
69
- end
70
- else
71
- raise FileMissing.new(Path.named_path(filename), options)
72
- ""
73
- end
74
- end
75
-
76
- #
77
- # like #file, but allow missing files
78
- #
79
- def try_file(filename)
80
- return file(filename)
81
- rescue FileMissing
82
- return nil
83
- end
84
-
85
- #
86
- # returns what the file path will be, once the file is rsynced to the server.
87
- # an internal list of discovered file paths is saved, in order to rsync these files when needed.
88
- #
89
- # notes:
90
- #
91
- # * argument 'path' is relative to Path.provider/files or Path.provider_base/files
92
- # * the path returned by this method is absolute
93
- # * the path stored for use later by rsync is relative to Path.provider
94
- # * if the path does not exist locally, but exists in provider_base, then the default file from
95
- # provider_base is copied locally. this is required for rsync to work correctly.
96
- #
97
- def file_path(path)
98
- if path.is_a? Symbol
99
- path = [path, @node.name]
100
- end
101
- actual_path = Path.find_file(path)
102
- if actual_path.nil?
103
- Util::log 2, :skipping, "file_path(\"#{path}\") because there is no such file."
104
- nil
105
- else
106
- if actual_path =~ /^#{Regexp.escape(Path.provider_base)}/
107
- # if file is under Path.provider_base, we must copy the default file to
108
- # to Path.provider in order for rsync to be able to sync the file.
109
- local_provider_path = actual_path.sub(/^#{Regexp.escape(Path.provider_base)}/, Path.provider)
110
- FileUtils.mkdir_p File.dirname(local_provider_path), :mode => 0700
111
- FileUtils.install actual_path, local_provider_path, :mode => 0600
112
- Util.log :created, Path.relative_path(local_provider_path)
113
- actual_path = local_provider_path
114
- end
115
- if File.directory?(actual_path) && actual_path !~ /\/$/
116
- actual_path += '/' # ensure directories end with /, important for building rsync command
117
- end
118
- relative_path = Path.relative_path(actual_path)
119
- @node.file_paths << relative_path
120
- @node.manager.provider.hiera_sync_destination + '/' + relative_path
121
- end
122
- end
123
-
124
- #
125
- # inserts a named secret, generating it if needed.
126
- #
127
- # manager.export_secrets should be called later to capture any newly generated secrets.
128
- #
129
- # +length+ is the character length of the generated password.
130
- #
131
- def secret(name, length=32)
132
- @manager.secrets.set(name, Util::Secret.generate(length), @node[:environment])
133
- end
134
-
135
- # inserts a base32 encoded secret
136
- def base32_secret(name, length=20)
137
- @manager.secrets.set(name, Base32.encode(Util::Secret.generate(length)), @node[:environment])
138
- end
139
-
140
- # Picks a random obfsproxy port from given range
141
- def rand_range(name, range)
142
- @manager.secrets.set(name, rand(range), @node[:environment])
143
- end
144
-
145
- #
146
- # inserts an hexidecimal secret string, generating it if needed.
147
- #
148
- # +bit_length+ is the bits in the secret, (ie length of resulting hex string will be bit_length/4)
149
- #
150
- def hex_secret(name, bit_length=128)
151
- @manager.secrets.set(name, Util::Secret.generate_hex(bit_length), @node[:environment])
152
- end
153
-
154
- #
155
- # return a fingerprint for a x509 certificate
156
- #
157
- def fingerprint(filename)
158
- "SHA256: " + X509.fingerprint("SHA256", Path.named_path(filename))
159
- end
160
-
161
- ##
162
- ## HOSTS
163
- ##
164
-
165
- #
166
- # records the list of hosts that are encountered for this node
167
- #
168
- def hostnames(nodes)
169
- @referenced_nodes ||= ObjectList.new
170
- if nodes.is_a? Config::Object
171
- nodes = ObjectList.new nodes
172
- end
173
- nodes.each_node do |node|
174
- @referenced_nodes[node.name] ||= node
175
- end
176
- return nodes.values.collect {|node| node.domain.name}
177
- end
178
-
179
- #
180
- # Generates entries needed for updating /etc/hosts on a node (as a hash).
181
- #
182
- # Argument `nodes` can be nil or a list of nodes. If nil, only include the
183
- # IPs of the other nodes this @node as has encountered (plus all mx nodes).
184
- #
185
- # Also, for virtual machines, we use the local address if this @node is in
186
- # the same location as the node in question.
187
- #
188
- # We include the ssh public key for each host, so that the hash can also
189
- # be used to generate the /etc/ssh/known_hosts
190
- #
191
- def hosts_file(nodes=nil)
192
- if nodes.nil?
193
- if @referenced_nodes && @referenced_nodes.any?
194
- nodes = @referenced_nodes
195
- nodes = nodes.merge(nodes_like_me[:services => 'mx']) # all nodes always need to communicate with mx nodes.
196
- end
197
- end
198
- return nil unless nodes
199
- hosts = {}
200
- my_location = @node['location'] ? @node['location']['name'] : nil
201
- nodes.each_node do |node|
202
- hosts[node.name] = {'ip_address' => node.ip_address, 'domain_internal' => node.domain.internal, 'domain_full' => node.domain.full}
203
- node_location = node['location'] ? node['location']['name'] : nil
204
- if my_location == node_location
205
- if facts = @node.manager.facts[node.name]
206
- if facts['ec2_public_ipv4']
207
- hosts[node.name]['ip_address'] = facts['ec2_public_ipv4']
208
- end
209
- end
210
- end
211
- host_pub_key = Util::read_file([:node_ssh_pub_key,node.name])
212
- if host_pub_key
213
- hosts[node.name]['host_pub_key'] = host_pub_key
214
- end
215
- end
216
- hosts
217
- end
218
-
219
- ##
220
- ## STUNNEL
221
- ##
222
-
223
- #
224
- # stunnel configuration for the client side.
225
- #
226
- # +node_list+ is a ObjectList of nodes running stunnel servers.
227
- #
228
- # +port+ is the real port of the ultimate service running on the servers
229
- # that the client wants to connect to.
230
- #
231
- # About ths stunnel puppet names:
232
- #
233
- # * accept_port is the port on localhost to which local clients
234
- # can connect. it is auto generated serially.
235
- # * connect_port is the port on the stunnel server to connect to.
236
- # it is auto generated from the +port+ argument.
237
- #
238
- # The network looks like this:
239
- #
240
- # |------ stunnel client ---------------| |--------- stunnel server -----------------------|
241
- # consumer app -> localhost:accept_port -> server:connect_port -> server:port -> service app
242
- #
243
- # generates an entry appropriate to be passed directly to
244
- # create_resources(stunnel::service, hiera('..'), defaults)
245
- #
246
- def stunnel_client(node_list, port, options={})
247
- @next_stunnel_port ||= 4000
248
- hostnames(node_list) # record the hosts
249
- node_list.values.inject(Config::ObjectList.new) do |hsh, node|
250
- if node.name != self.name || options[:include_self]
251
- hsh["#{node.name}_#{port}"] = Config::Object[
252
- 'accept_port', @next_stunnel_port,
253
- 'connect', node.domain.internal,
254
- 'connect_port', stunnel_port(port)
255
- ]
256
- @next_stunnel_port += 1
257
- end
258
- hsh
259
- end
260
- end
261
-
262
- #
263
- # generates a stunnel server entry.
264
- #
265
- # +port+ is the real port targeted service.
266
- #
267
- def stunnel_server(port)
268
- {"accept" => stunnel_port(port), "connect" => "127.0.0.1:#{port}"}
269
- end
270
-
271
- #
272
- # maps a real port to a stunnel port (used as the connect_port in the client config
273
- # and the accept_port in the server config)
274
- #
275
- def stunnel_port(port)
276
- port = port.to_i
277
- if port < 50000
278
- return port + 10000
279
- else
280
- return port - 10000
281
- end
282
- end
283
-
284
- ##
285
- ## HAPROXY
286
- ##
287
-
288
- #
289
- # creates a hash suitable for configuring haproxy. the key is the node name of the server we are proxying to.
290
- #
291
- # * node_list - a hash of nodes for the haproxy servers
292
- # * stunnel_client - contains the mappings to local ports for each server node.
293
- # * non_stunnel_port - in case self is included in node_list, the port to connect to.
294
- #
295
- # 1000 weight is used for nodes in the same location.
296
- # 100 otherwise.
297
- #
298
- def haproxy_servers(node_list, stunnel_clients, non_stunnel_port=nil)
299
- default_weight = 10
300
- local_weight = 100
301
-
302
- # record the hosts_file
303
- hostnames(node_list)
304
-
305
- # create a simple map for node name -> local stunnel accept port
306
- accept_ports = stunnel_clients.inject({}) do |hsh, stunnel_entry|
307
- name = stunnel_entry.first.sub /_[0-9]+$/, ''
308
- hsh[name] = stunnel_entry.last['accept_port']
309
- hsh
310
- end
311
-
312
- # if one the nodes in the node list is ourself, then there will not be a stunnel to it,
313
- # but we need to include it anyway in the haproxy config.
314
- if node_list[self.name] && non_stunnel_port
315
- accept_ports[self.name] = non_stunnel_port
316
- end
317
-
318
- # create the first pass of the servers hash
319
- servers = node_list.values.inject(Config::ObjectList.new) do |hsh, node|
320
- weight = default_weight
321
- if self['location'] && node['location']
322
- if self.location['name'] == node.location['name']
323
- weight = local_weight
324
- end
325
- end
326
- hsh[node.name] = Config::Object[
327
- 'backup', false,
328
- 'host', 'localhost',
329
- 'port', accept_ports[node.name] || 0,
330
- 'weight', weight
331
- ]
332
- hsh
333
- end
334
-
335
- # if there are some local servers, make the others backup
336
- if servers.detect{|k,v| v.weight == local_weight}
337
- servers.each do |k,server|
338
- server['backup'] = server['weight'] == default_weight
339
- end
340
- end
341
-
342
- return servers
343
- end
344
-
345
- ##
346
- ## SSH
347
- ##
348
-
349
- #
350
- # Creates a hash from the ssh key info in users directory, for use in
351
- # updating authorized_keys file. Additionally, the 'monitor' public key is
352
- # included, which is used by the monitor nodes to run particular commands
353
- # remotely.
354
- #
355
- def authorized_keys
356
- hash = {}
357
- keys = Dir.glob(Path.named_path([:user_ssh, '*']))
358
- keys.sort.each do |keyfile|
359
- ssh_type, ssh_key = File.read(keyfile, :encoding => 'UTF-8').strip.split(" ")
360
- name = File.basename(File.dirname(keyfile))
361
- hash[name] = {
362
- "type" => ssh_type,
363
- "key" => ssh_key
364
- }
365
- end
366
- ssh_type, ssh_key = File.read(Path.named_path(:monitor_pub_key), :encoding => 'UTF-8').strip.split(" ")
367
- hash[Leap::Platform.monitor_username] = {
368
- "type" => ssh_type,
369
- "key" => ssh_key
370
- }
371
- hash
372
- end
373
-
374
- #
375
- # this is not currently used, because we put key information in the 'hosts' hash.
376
- # see 'hosts_file()'
377
- #
378
- # def known_hosts_file(nodes=nil)
379
- # if nodes.nil?
380
- # if @referenced_nodes && @referenced_nodes.any?
381
- # nodes = @referenced_nodes
382
- # end
383
- # end
384
- # return nil unless nodes
385
- # entries = []
386
- # nodes.each_node do |node|
387
- # hostnames = [node.name, node.domain.internal, node.domain.full, node.ip_address].join(',')
388
- # pub_key = Util::read_file([:node_ssh_pub_key,node.name])
389
- # if pub_key
390
- # entries << [hostnames, pub_key].join(' ')
391
- # end
392
- # end
393
- # entries.join("\n")
394
- # end
395
-
396
- ##
397
- ## UTILITY
398
- ##
399
-
400
- class AssertionFailed < Exception
401
- attr_accessor :assertion
402
- def initialize(assertion)
403
- @assertion = assertion
404
- end
405
- def to_s
406
- @assertion
407
- end
408
- end
409
-
410
- def assert(assertion)
411
- if instance_eval(assertion)
412
- true
413
- else
414
- raise AssertionFailed.new(assertion)
415
- end
416
- end
417
-
418
- #
419
- # wrap something that might fail in `try`. e.g.
420
- #
421
- # "= try{ nodes[:services => 'tor'].first.ip_address } "
422
- #
423
- def try(&block)
424
- yield
425
- rescue NoMethodError
426
- nil
427
- end
428
-
429
- end
430
- end; end
@@ -1,7 +0,0 @@
1
- module LeapCli
2
-
3
- PUPPET_DESTINATION = '/srv/leap'
4
- INITIALIZED_FILE = "#{PUPPET_DESTINATION}/initialized"
5
- DEFAULT_TAGS = ['leap_base','leap_service']
6
-
7
- end
@@ -1,19 +0,0 @@
1
- # run 'rake update-requirements' to generate this file.
2
- module LeapCli
3
- REQUIREMENTS = [
4
- "provider.ca.name",
5
- "provider.ca.server_certificates.bit_size",
6
- "provider.ca.server_certificates.digest",
7
- "provider.ca.server_certificates.life_span",
8
- "common.x509.use",
9
- "provider.domain",
10
- "provider.name",
11
- "provider.ca.server_certificates.bit_size",
12
- "provider.ca.server_certificates.digest",
13
- "provider.ca.name",
14
- "provider.ca.bit_size",
15
- "provider.ca.life_span",
16
- "provider.ca.client_certificates.unlimited_prefix",
17
- "provider.ca.client_certificates.limited_prefix"
18
- ]
19
- end
@@ -1,122 +0,0 @@
1
- require 'stringio'
2
- require 'gli/commands/help_modules/arg_name_formatter'
3
-
4
- #
5
- # adaption of RdocDocumentListener to use Markdown
6
- # see http://rtomayko.github.com/ronn/ronn-format.7
7
- #
8
-
9
- module GLI
10
- module Commands
11
- class MarkdownDocumentListener
12
-
13
- def initialize(global_options,options,arguments)
14
- @io = STDOUT #File.new(File.basename($0) + ".rdoc",'w')
15
- @nest = ''
16
- @arg_name_formatter = GLI::Commands::HelpModules::ArgNameFormatter.new
17
- end
18
-
19
- def beginning
20
- end
21
-
22
- # Called when processing has completed
23
- def ending
24
- #@io.close
25
- end
26
-
27
- # Gives you the program description
28
- def program_desc(desc)
29
- @io.puts "== #{File.basename($0)} - #{desc}"
30
- @io.puts
31
- end
32
-
33
- def program_long_desc(desc)
34
- @io.puts desc
35
- @io.puts
36
- end
37
-
38
- # Gives you the program version
39
- def version(version)
40
- @io.puts "v#{version}"
41
- @io.puts
42
- end
43
-
44
- def options
45
- if @nest.size == 0
46
- @io.puts "=== Global Options"
47
- else
48
- @io.puts "#{@nest}=== Options"
49
- end
50
- end
51
-
52
- # Gives you a flag in the current context
53
- def flag(name,aliases,desc,long_desc,default_value,arg_name,must_match,type)
54
- invocations = ([name] + Array(aliases)).map { |_| add_dashes(_) }.join('|')
55
- usage = "#{invocations} #{arg_name || 'arg'}"
56
- @io.puts "#{@nest}=== #{usage}"
57
- @io.puts
58
- @io.puts String(desc).strip
59
- @io.puts
60
- @io.puts "[Default Value] #{default_value || 'None'}"
61
- @io.puts "[Must Match] #{must_match.to_s}" unless must_match.nil?
62
- @io.puts String(long_desc).strip
63
- @io.puts
64
- end
65
-
66
- # Gives you a switch in the current context
67
- def switch(name,aliases,desc,long_desc,negetable)
68
- if negetable
69
- name = "[no-]#{name}" if name.to_s.length > 1
70
- aliases = aliases.map { |_| _.to_s.length > 1 ? "[no-]#{_}" : _ }
71
- end
72
- invocations = ([name] + aliases).map { |_| add_dashes(_) }.join('|')
73
- @io.puts "#{@nest}=== #{invocations}"
74
- @io.puts String(desc).strip
75
- @io.puts
76
- @io.puts String(long_desc).strip
77
- @io.puts
78
- end
79
-
80
- def end_options
81
- end
82
-
83
- def commands
84
- @io.puts "#{@nest}=== Commands"
85
- @nest = "#{@nest}="
86
- end
87
-
88
- # Gives you a command in the current context and creates a new context of this command
89
- def command(name,aliases,desc,long_desc,arg_name,arg_options)
90
- @io.puts "#{@nest}=== Command: <tt>#{([name] + aliases).join('|')} #{@arg_name_formatter.format(arg_name,arg_options)}</tt>"
91
- @io.puts String(desc).strip
92
- @io.puts
93
- @io.puts String(long_desc).strip
94
- @nest = "#{@nest}="
95
- end
96
-
97
- # Ends a command, and "pops" you back up one context
98
- def end_command(name)
99
- @nest.gsub!(/=$/,'')
100
- end
101
-
102
- # Gives you the name of the current command in the current context
103
- def default_command(name)
104
- @io.puts "[Default Command] #{name}" unless name.nil?
105
- end
106
-
107
- def end_commands
108
- @nest.gsub!(/=$/,'')
109
- end
110
-
111
- private
112
-
113
- def add_dashes(name)
114
- name = "-#{name}"
115
- name = "-#{name}" if name.length > 2
116
- name
117
- end
118
-
119
-
120
- end
121
- end
122
- end