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,126 @@
1
+ require 'fileutils'
2
+
3
+ module LeapCli; module Commands
4
+
5
+ desc 'Creates a new provider instance in the specified directory, creating it if necessary.'
6
+ arg_name 'DIRECTORY'
7
+ skips_pre
8
+ command :new do |c|
9
+ c.flag 'name', :desc => "The name of the provider." #, :default_value => 'Example'
10
+ c.flag 'domain', :desc => "The primary domain of the provider." #, :default_value => 'example.org'
11
+ c.flag 'platform', :desc => "File path of the leap_platform directory." #, :default_value => '../leap_platform'
12
+ c.flag 'contacts', :desc => "Default email address contacts." #, :default_value => 'root'
13
+
14
+ c.action do |global, options, args|
15
+ directory = File.expand_path(args.first)
16
+ create_provider_directory(global, directory)
17
+ options[:domain] ||= ask("The primary domain of the provider: ") {|q| q.default = 'example.org'}
18
+ options[:name] ||= ask("The name of the provider: ") {|q| q.default = 'Example'}
19
+ options[:platform] ||= ask("File path of the leap_platform directory: ") {|q| q.default = File.expand_path('../leap_platform', directory)}
20
+ options[:contacts] ||= ask("Default email address contacts: ") {|q| q.default = 'root@' + options[:domain]}
21
+ options[:platform] = relative_path(options[:platform])
22
+ create_initial_provider_files(directory, global, options)
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ DEFAULT_REPO = 'https://leap.se/git/leap_platform.git'
29
+
30
+ #
31
+ # creates a new provider directory
32
+ #
33
+ def create_provider_directory(global, directory)
34
+ unless directory && directory.any?
35
+ help! "Directory name is required."
36
+ end
37
+ unless File.exists?(directory)
38
+ if global[:yes] || agree("Create directory #{directory}? ")
39
+ ensure_dir directory
40
+ else
41
+ bail! { log :missing, "directory #{directory}" }
42
+ end
43
+ end
44
+ Path.set_provider_path(directory)
45
+ end
46
+
47
+ #
48
+ # see provider with initial files
49
+ #
50
+ def create_initial_provider_files(directory, global, options)
51
+ Dir.chdir(directory) do
52
+ assert_files_missing! 'provider.json', 'common.json', 'Leapfile', :base => directory
53
+
54
+ platform_dir = File.expand_path(options[:platform], "./")
55
+ unless File.symlink?(platform_dir) || File.directory?(platform_dir)
56
+ if global[:yes] || agree("The platform directory \"#{platform_dir}\" does not exist.\nDo you want me to create it by cloning from the\ngit repository #{DEFAULT_REPO}? ")
57
+ assert_bin! 'git'
58
+ ensure_dir platform_dir
59
+ Dir.chdir(platform_dir) do
60
+ log :cloning, "leap_platform into #{platform_dir}"
61
+ pty_run "git clone --branch master #{DEFAULT_REPO} ."
62
+ pty_run 'git submodule update --init'
63
+ end
64
+ else
65
+ bail!
66
+ end
67
+ end
68
+ write_file! '.gitignore', GITIGNORE_CONTENT
69
+ write_file! 'provider.json', provider_content(options)
70
+ write_file! 'common.json', COMMON_CONTENT
71
+ write_file! 'Leapfile', leapfile_content(options)
72
+ ["nodes", "services", "tags"].each do |dir|
73
+ ensure_dir dir
74
+ end
75
+ log :completed, 'initialization'
76
+ end
77
+ end
78
+
79
+ def relative_path(path)
80
+ Pathname.new(path).relative_path_from(Pathname.new(Path.provider)).to_s
81
+ end
82
+
83
+ def leapfile_content(options)
84
+ %[@platform_directory_path = "#{options[:platform]}"\n# see https://leap.se/en/docs/platform/config for more options]
85
+ end
86
+
87
+ GITIGNORE_CONTENT = <<EOS
88
+ test/Vagrantfile
89
+ test/.vagrant
90
+ test/openvpn
91
+ test/cert
92
+ EOS
93
+
94
+ def provider_content(options)
95
+ %[//
96
+ // General service provider configuration.
97
+ //
98
+ {
99
+ "domain": "#{options[:domain]}",
100
+ "name": {
101
+ "en": "#{options[:name]}"
102
+ },
103
+ "description": {
104
+ "en": "You really should change this text"
105
+ },
106
+ "contacts": {
107
+ "default": "#{options[:contacts]}"
108
+ },
109
+ "languages": ["en"],
110
+ "default_language": "en",
111
+ "enrollment_policy": "open"
112
+ }
113
+ ]
114
+ end
115
+
116
+ COMMON_CONTENT = <<EOS
117
+ //
118
+ // Options put here are inherited by all nodes.
119
+ //
120
+ {
121
+ }
122
+ EOS
123
+
124
+ end; end
125
+
126
+
@@ -0,0 +1,272 @@
1
+ require 'net/ssh/known_hosts'
2
+ require 'tempfile'
3
+ require 'ipaddr'
4
+
5
+ module LeapCli; module Commands
6
+
7
+ ##
8
+ ## COMMANDS
9
+ ##
10
+
11
+ desc 'Node management'
12
+ command :node do |node|
13
+ node.desc 'Create a new configuration file for a node named NAME.'
14
+ node.long_desc ["If specified, the optional argument SEED can be used to seed values in the node configuration file.",
15
+ "The format is property_name:value.",
16
+ "For example: `leap node add web1 ip_address:1.2.3.4 services:webapp`.",
17
+ "To set nested properties, property name can contain '.', like so: `leap node add web1 ssh.port:44`",
18
+ "Separeate multiple values for a single property with a comma, like so: `leap node add mynode services:webapp,dns`"].join("\n\n")
19
+ node.arg_name 'NAME [SEED]' # , :optional => false, :multiple => false
20
+ node.command :add do |add|
21
+ add.switch :local, :desc => 'Make a local testing node (by automatically assigning the next available local IP address). Local nodes are run as virtual machines on your computer.', :negatable => false
22
+ add.action do |global_options,options,args|
23
+ # argument sanity checks
24
+ name = args.first
25
+ assert! name, 'No <node-name> specified.'
26
+ assert! name =~ /^[0-9a-z-]+$/, "illegal characters used in node name '#{name}'"
27
+ assert_files_missing! [:node_config, name]
28
+
29
+ # create and seed new node
30
+ node = Config::Node.new(manager)
31
+ if options[:local]
32
+ node['ip_address'] = pick_next_vagrant_ip_address
33
+ end
34
+ seed_node_data(node, args[1..-1])
35
+ validate_ip_address(node)
36
+
37
+ # write the file
38
+ write_file! [:node_config, name], node.dump_json + "\n"
39
+ node['name'] = name
40
+ if file_exists? :ca_cert, :ca_key
41
+ generate_cert_for_node(manager.reload_node(node))
42
+ end
43
+ end
44
+ end
45
+
46
+ node.desc 'Bootstraps a node or nodes, setting up SSH keys and installing prerequisite packages'
47
+ node.long_desc "This command prepares a server to be used with the LEAP Platform by saving the server's SSH host key, " +
48
+ "copying the authorized_keys file, installing packages that are required for deploying, and registering important facts. " +
49
+ "Node init must be run before deploying to a server, and the server must be running and available via the network. " +
50
+ "This command only needs to be run once, but there is no harm in running it multiple times."
51
+ node.arg_name 'FILTER' #, :optional => false, :multiple => false
52
+ node.command :init do |init|
53
+ init.switch 'echo', :desc => 'If set, passwords are visible as you type them (default is hidden)', :negatable => false
54
+ init.flag :port, :desc => 'Override the default SSH port.', :arg_name => 'PORT'
55
+ init.flag :ip, :desc => 'Override the default SSH IP address.', :arg_name => 'IPADDRESS'
56
+
57
+ init.action do |global,options,args|
58
+ assert! args.any?, 'You must specify a FILTER'
59
+ finished = []
60
+ manager.filter!(args).each_node do |node|
61
+ is_node_alive(node, options)
62
+ save_public_host_key(node, global, options) unless node.vagrant?
63
+ update_compiled_ssh_configs
64
+ ssh_connect_options = connect_options(options).merge({:bootstrap => true, :echo => options[:echo]})
65
+ ssh_connect(node, ssh_connect_options) do |ssh|
66
+ ssh.install_authorized_keys
67
+ ssh.install_prerequisites
68
+ ssh.leap.capture(facter_cmd) do |response|
69
+ if response[:exitcode] == 0
70
+ update_node_facts(node.name, response[:data])
71
+ else
72
+ log :failed, "to run facter on #{node.name}"
73
+ end
74
+ end
75
+ end
76
+ finished << node.name
77
+ end
78
+ log :completed, "initialization of nodes #{finished.join(', ')}"
79
+ end
80
+ end
81
+
82
+ node.desc 'Renames a node file, and all its related files.'
83
+ node.arg_name 'OLD_NAME NEW_NAME'
84
+ node.command :mv do |mv|
85
+ mv.action do |global_options,options,args|
86
+ node = get_node_from_args(args)
87
+ new_name = args.last
88
+ ensure_dir [:node_files_dir, new_name]
89
+ Leap::Platform.node_files.each do |path|
90
+ rename_file! [path, node.name], [path, new_name]
91
+ end
92
+ remove_directory! [:node_files_dir, node.name]
93
+ rename_node_facts(node.name, new_name)
94
+ end
95
+ end
96
+
97
+ node.desc 'Removes all the files related to the node named NAME.'
98
+ node.arg_name 'NAME' #:optional => false #, :multiple => false
99
+ node.command :rm do |rm|
100
+ rm.action do |global_options,options,args|
101
+ node = get_node_from_args(args)
102
+ (Leap::Platform.node_files + [:node_files_dir]).each do |path|
103
+ remove_file! [path, node.name]
104
+ end
105
+ if node.vagrant?
106
+ vagrant_command("destroy --force", [node.name])
107
+ end
108
+ remove_node_facts(node.name)
109
+ end
110
+ end
111
+ end
112
+
113
+ ##
114
+ ## PUBLIC HELPERS
115
+ ##
116
+
117
+ #
118
+ # generates the known_hosts file.
119
+ #
120
+ # we do a 'late' binding on the hostnames and ip part of the ssh pub key record in order to allow
121
+ # for the possibility that the hostnames or ip has changed in the node configuration.
122
+ #
123
+ def update_known_hosts
124
+ buffer = StringIO.new
125
+ buffer << "#\n"
126
+ buffer << "# This file is automatically generated by the command `leap`. You should NOT modify this file.\n"
127
+ buffer << "# Instead, rerun `leap node init` on whatever node is causing SSH problems.\n"
128
+ buffer << "#\n"
129
+ manager.nodes.keys.sort.each do |node_name|
130
+ node = manager.nodes[node_name]
131
+ hostnames = [node.name, node.domain.internal, node.domain.full, node.ip_address].join(',')
132
+ pub_key = read_file([:node_ssh_pub_key,node.name])
133
+ if pub_key
134
+ buffer << [hostnames, pub_key].join(' ')
135
+ buffer << "\n"
136
+ end
137
+ end
138
+ write_file!(:known_hosts, buffer.string)
139
+ end
140
+
141
+ def get_node_from_args(args, options={})
142
+ node_name = args.first
143
+ node = manager.node(node_name)
144
+ if node.nil? && options[:include_disabled]
145
+ node = manager.disabled_node(node_name)
146
+ end
147
+ assert!(node, "Node '#{node_name}' not found.")
148
+ node
149
+ end
150
+
151
+ private
152
+
153
+ ##
154
+ ## PRIVATE HELPERS
155
+ ##
156
+
157
+ #
158
+ # saves the public ssh host key for node into the provider directory.
159
+ #
160
+ # see `man sshd` for the format of known_hosts
161
+ #
162
+ def save_public_host_key(node, global, options)
163
+ log :fetching, "public SSH host key for #{node.name}"
164
+ address = options[:ip] || node.ip_address
165
+ port = options[:port] || node.ssh.port
166
+ public_key = get_public_key_for_ip(address, port)
167
+ pub_key_path = Path.named_path([:node_ssh_pub_key, node.name])
168
+ if Path.exists?(pub_key_path)
169
+ if public_key == SshKey.load(pub_key_path)
170
+ log :trusted, "- Public SSH host key for #{node.name} matches previously saved key", :indent => 1
171
+ else
172
+ bail! do
173
+ log :error, "The public SSH host key we just fetched for #{node.name} doesn't match what we have saved previously.", :indent => 1
174
+ log "Remove the file #{pub_key_path} if you really want to change it.", :indent => 2
175
+ end
176
+ end
177
+ elsif public_key.in_known_hosts?(node.name, node.ip_address, node.domain.name)
178
+ log :trusted, "- Public SSH host key for #{node.name} is trusted (key found in your ~/.ssh/known_hosts)"
179
+ else
180
+ puts
181
+ say("This is the SSH host key you got back from node \"#{node.name}\"")
182
+ say("Type -- #{public_key.bits} bit #{public_key.type.upcase}")
183
+ say("Fingerprint -- " + public_key.fingerprint)
184
+ say("Public Key -- " + public_key.key)
185
+ if !global[:yes] && !agree("Is this correct? ")
186
+ bail!
187
+ else
188
+ puts
189
+ write_file! [:node_ssh_pub_key, node.name], public_key.to_s
190
+ end
191
+ end
192
+ end
193
+
194
+ def get_public_key_for_ip(address, port=22)
195
+ assert_bin!('ssh-keyscan')
196
+ output = assert_run! "ssh-keyscan -p #{port} -t ecdsa #{address}", "Could not get the public host key from #{address}:#{port}. Maybe sshd is not running?"
197
+ line = output.split("\n").grep(/^[^#]/).first
198
+ if line =~ /No route to host/
199
+ bail! :failed, 'ssh-keyscan: no route to %s' % address
200
+ elsif line =~ /no hostkey alg/
201
+ bail! :failed, 'ssh-keyscan: no hostkey alg (must be missing an ecdsa public host key)'
202
+ end
203
+ assert! line, "Got zero host keys back!"
204
+ ip, key_type, public_key = line.split(' ')
205
+ return SshKey.load(public_key, key_type)
206
+ end
207
+
208
+ def is_node_alive(node, options)
209
+ address = options[:ip] || node.ip_address
210
+ port = options[:port] || node.ssh.port
211
+ log :connecting, "to node #{node.name}"
212
+ assert_run! "nc -zw3 #{address} #{port}",
213
+ "Failed to reach #{node.name} (address #{address}, port #{port}). You can override the configured IP address and port with --ip or --port."
214
+ end
215
+
216
+ def seed_node_data(node, args)
217
+ args.each do |seed|
218
+ key, value = seed.split(':')
219
+ value = format_seed_value(value)
220
+ assert! key =~ /^[0-9a-z\._]+$/, "illegal characters used in property '#{key}'"
221
+ if key =~ /\./
222
+ key_parts = key.split('.')
223
+ final_key = key_parts.pop
224
+ current_object = node
225
+ key_parts.each do |key_part|
226
+ current_object[key_part] ||= Config::Object.new
227
+ current_object = current_object[key_part]
228
+ end
229
+ current_object[final_key] = value
230
+ else
231
+ node[key] = value
232
+ end
233
+ end
234
+ end
235
+
236
+ #
237
+ # conversations:
238
+ #
239
+ # "x,y,z" => ["x","y","z"]
240
+ #
241
+ # "22" => 22
242
+ #
243
+ # "5.1" => 5.1
244
+ #
245
+ def format_seed_value(v)
246
+ if v =~ /,/
247
+ v = v.split(',')
248
+ v.map! do |i|
249
+ i = i.to_i if i.to_i.to_s == i
250
+ i = i.to_f if i.to_f.to_s == i
251
+ i
252
+ end
253
+ else
254
+ v = v.to_i if v.to_i.to_s == v
255
+ v = v.to_f if v.to_f.to_s == v
256
+ end
257
+ return v
258
+ end
259
+
260
+ def validate_ip_address(node)
261
+ IPAddr.new(node['ip_address'])
262
+ rescue ArgumentError
263
+ bail! do
264
+ if node['ip_address']
265
+ log :invalid, "ip_address #{node['ip_address'].inspect}"
266
+ else
267
+ log :missing, "ip_address"
268
+ end
269
+ end
270
+ end
271
+
272
+ end; end
@@ -0,0 +1,99 @@
1
+
2
+ #
3
+ # check to make sure we can find the root directory of the platform
4
+ #
5
+ module LeapCli; module Commands
6
+
7
+ desc 'Verbosity level 0..5'
8
+ arg_name 'LEVEL'
9
+ default_value '1'
10
+ flag [:v, :verbose]
11
+
12
+ desc 'Override default log file'
13
+ arg_name 'FILE'
14
+ default_value nil
15
+ flag :log
16
+
17
+ desc 'Display version number and exit'
18
+ switch :version, :negatable => false
19
+
20
+ desc 'Skip prompts and assume "yes"'
21
+ switch :yes, :negatable => false
22
+
23
+ pre do |global,command,options,args|
24
+ #
25
+ # set verbosity
26
+ #
27
+ LeapCli.log_level = global[:verbose].to_i
28
+ if LeapCli.log_level > 1
29
+ ENV['GLI_DEBUG'] = "true"
30
+ else
31
+ ENV['GLI_DEBUG'] = "false"
32
+ end
33
+
34
+ #
35
+ # load Leapfile
36
+ #
37
+ unless LeapCli.leapfile.load
38
+ bail! { log :missing, 'Leapfile in directory tree' }
39
+ end
40
+ Path.set_platform_path(LeapCli.leapfile.platform_directory_path)
41
+ Path.set_provider_path(LeapCli.leapfile.provider_directory_path)
42
+ if !Path.provider || !File.directory?(Path.provider)
43
+ bail! { log :missing, "provider directory '#{Path.provider}'" }
44
+ end
45
+ if !Path.platform || !File.directory?(Path.platform)
46
+ bail! { log :missing, "platform directory '#{Path.platform}'" }
47
+ end
48
+
49
+ if LeapCli.leapfile.platform_branch && LeapCli::Util.is_git_directory?(Path.platform)
50
+ branch = LeapCli::Util.current_git_branch(Path.platform)
51
+ if branch != LeapCli.leapfile.platform_branch
52
+ bail! "Wrong branch for #{Path.platform}. Was '#{branch}', should be '#{LeapCli.leapfile.platform_branch}'. Edit Leapfile to disable this check."
53
+ end
54
+ end
55
+
56
+ #
57
+ # set log file
58
+ #
59
+ LeapCli.log_file = global[:log] || LeapCli.leapfile.log
60
+ LeapCli::Util.log_raw(:log) { $0 + ' ' + ORIGINAL_ARGV.join(' ')}
61
+ log_version
62
+
63
+ #
64
+ # load all the nodes everything
65
+ #
66
+ manager
67
+
68
+ #
69
+ # check requirements
70
+ #
71
+ REQUIREMENTS.each do |key|
72
+ assert_config! key
73
+ end
74
+
75
+ end
76
+
77
+ private
78
+
79
+ #
80
+ # add a log entry for the leap command and leap platform versions
81
+ #
82
+ def log_version
83
+ if LeapCli.log_level >= 2
84
+ str = "leap command v#{LeapCli::VERSION}"
85
+ cli_dir = File.dirname(__FILE__)
86
+ if Util.is_git_directory?(cli_dir)
87
+ str << " (%s %s)" % [Util.current_git_branch(cli_dir), Util.current_git_commit(cli_dir)]
88
+ end
89
+ log 2, str
90
+ str = "leap platform v#{Leap::Platform.version}"
91
+ if Util.is_git_directory?(Path.platform)
92
+ str << " (%s %s)" % [Util.current_git_branch(Path.platform), Util.current_git_commit(Path.platform)]
93
+ end
94
+ log 2, str
95
+ end
96
+ end
97
+
98
+
99
+ end; end
@@ -0,0 +1,67 @@
1
+ module LeapCli; module Commands
2
+
3
+ desc 'Log in to the specified node with an interactive shell.'
4
+ arg_name 'NAME' #, :optional => false, :multiple => false
5
+ command :ssh do |c|
6
+ c.action do |global_options,options,args|
7
+ exec_ssh(:ssh, args)
8
+ end
9
+ end
10
+
11
+ desc 'Log in to the specified node with an interactive shell using mosh (requires node to have mosh.enabled set to true).'
12
+ arg_name 'NAME'
13
+ command :mosh do |c|
14
+ c.action do |global_options,options,args|
15
+ exec_ssh(:mosh, args)
16
+ end
17
+ end
18
+
19
+ protected
20
+
21
+ #
22
+ # allow for ssh overrides of all commands that use ssh_connect
23
+ #
24
+ def connect_options(options)
25
+ connect_options = {:ssh_options=>{}}
26
+ if options[:port]
27
+ connect_options[:ssh_options][:port] = options[:port]
28
+ end
29
+ if options[:ip]
30
+ connect_options[:ssh_options][:host_name] = options[:ip]
31
+ end
32
+ return connect_options
33
+ end
34
+
35
+ private
36
+
37
+ def exec_ssh(cmd, args)
38
+ node = get_node_from_args(args, :include_disabled => true)
39
+ options = [
40
+ "-o 'HostName=#{node.ip_address}'",
41
+ # "-o 'HostKeyAlias=#{node.name}'", << oddly incompatible with ports in known_hosts file, so we must not use this or non-standard ports break.
42
+ "-o 'GlobalKnownHostsFile=#{path(:known_hosts)}'",
43
+ "-o 'UserKnownHostsFile=/dev/null'"
44
+ ]
45
+ if node.vagrant?
46
+ options << "-i #{vagrant_ssh_key_file}"
47
+ options << "-o 'StrictHostKeyChecking=no'" # blindly accept host key and don't save it (since userknownhostsfile is /dev/null)
48
+ else
49
+ options << "-o 'StrictHostKeyChecking=yes'"
50
+ end
51
+ username = 'root'
52
+ if LeapCli.log_level >= 3
53
+ options << "-vv"
54
+ elsif LeapCli.log_level >= 2
55
+ options << "-v"
56
+ end
57
+ ssh = "ssh -l #{username} -p #{node.ssh.port} #{options.join(' ')}"
58
+ if cmd == :ssh
59
+ command = "#{ssh} #{node.name}"
60
+ elsif cmd == :mosh
61
+ command = "MOSH_TITLE_NOPREFIX=1 mosh --ssh \"#{ssh}\" #{node.name}"
62
+ end
63
+ log 2, command
64
+ exec "#{command}"
65
+ end
66
+
67
+ end; end
@@ -0,0 +1,55 @@
1
+ module LeapCli; module Commands
2
+
3
+ desc 'Run tests.'
4
+ command :test do |test|
5
+ test.desc 'Creates files needed to run tests.'
6
+ test.command :init do |init|
7
+ init.action do |global_options,options,args|
8
+ generate_test_client_openvpn_configs
9
+ end
10
+ end
11
+
12
+ test.desc 'Run tests.'
13
+ test.command :run do |run|
14
+ run.action do |global_options,options,args|
15
+ manager.filter!(args).each_node do |node|
16
+ ssh_connect(node) do |ssh|
17
+ ssh.run(test_cmd)
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ test.default_command :run
24
+ end
25
+
26
+ private
27
+
28
+ def test_cmd
29
+ "#{PUPPET_DESTINATION}/bin/run_tests"
30
+ end
31
+
32
+ #
33
+ # generates a whole bunch of openvpn configs that can be used to connect to different openvpn gateways
34
+ #
35
+ def generate_test_client_openvpn_configs
36
+ assert_config! 'provider.ca.client_certificates.unlimited_prefix'
37
+ assert_config! 'provider.ca.client_certificates.limited_prefix'
38
+ template = read_file! Path.find_file(:test_client_openvpn_template)
39
+ manager.environments.each do |env|
40
+ vpn_nodes = manager.nodes[:environment => env][:services => 'openvpn']['openvpn.allow_limited' => true]
41
+ if vpn_nodes.any?
42
+ generate_test_client_cert(provider.ca.client_certificates.limited_prefix) do |key, cert|
43
+ write_file! [:test_openvpn_config, [env, 'limited'].compact.join('_')], Util.erb_eval(template, binding)
44
+ end
45
+ end
46
+ vpn_nodes = manager.nodes[:environment => env][:services => 'openvpn']['openvpn.allow_unlimited' => true]
47
+ if vpn_nodes.any?
48
+ generate_test_client_cert(provider.ca.client_certificates.unlimited_prefix) do |key, cert|
49
+ write_file! [:test_openvpn_config, [env, 'unlimited'].compact.join('_')], Util.erb_eval(template, binding)
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ end; end