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.
- data/bin/leap +81 -0
- data/lib/core_ext/boolean.rb +14 -0
- data/lib/core_ext/hash.rb +35 -0
- data/lib/core_ext/json.rb +42 -0
- data/lib/core_ext/nil.rb +5 -0
- data/lib/core_ext/string.rb +14 -0
- data/lib/leap/platform.rb +52 -0
- data/lib/leap_cli/commands/ca.rb +430 -0
- data/lib/leap_cli/commands/clean.rb +16 -0
- data/lib/leap_cli/commands/compile.rb +134 -0
- data/lib/leap_cli/commands/deploy.rb +172 -0
- data/lib/leap_cli/commands/facts.rb +93 -0
- data/lib/leap_cli/commands/inspect.rb +140 -0
- data/lib/leap_cli/commands/list.rb +122 -0
- data/lib/leap_cli/commands/new.rb +126 -0
- data/lib/leap_cli/commands/node.rb +272 -0
- data/lib/leap_cli/commands/pre.rb +99 -0
- data/lib/leap_cli/commands/shell.rb +67 -0
- data/lib/leap_cli/commands/test.rb +55 -0
- data/lib/leap_cli/commands/user.rb +140 -0
- data/lib/leap_cli/commands/util.rb +50 -0
- data/lib/leap_cli/commands/vagrant.rb +201 -0
- data/lib/leap_cli/config/macros.rb +369 -0
- data/lib/leap_cli/config/manager.rb +369 -0
- data/lib/leap_cli/config/node.rb +37 -0
- data/lib/leap_cli/config/object.rb +336 -0
- data/lib/leap_cli/config/object_list.rb +174 -0
- data/lib/leap_cli/config/secrets.rb +43 -0
- data/lib/leap_cli/config/tag.rb +18 -0
- data/lib/leap_cli/constants.rb +7 -0
- data/lib/leap_cli/leapfile.rb +97 -0
- data/lib/leap_cli/load_paths.rb +15 -0
- data/lib/leap_cli/log.rb +166 -0
- data/lib/leap_cli/logger.rb +216 -0
- data/lib/leap_cli/markdown_document_listener.rb +134 -0
- data/lib/leap_cli/path.rb +84 -0
- data/lib/leap_cli/remote/leap_plugin.rb +204 -0
- data/lib/leap_cli/remote/puppet_plugin.rb +66 -0
- data/lib/leap_cli/remote/rsync_plugin.rb +35 -0
- data/lib/leap_cli/remote/tasks.rb +36 -0
- data/lib/leap_cli/requirements.rb +19 -0
- data/lib/leap_cli/ssh_key.rb +130 -0
- data/lib/leap_cli/util/remote_command.rb +110 -0
- data/lib/leap_cli/util/secret.rb +54 -0
- data/lib/leap_cli/util/x509.rb +32 -0
- data/lib/leap_cli/util.rb +431 -0
- data/lib/leap_cli/version.rb +9 -0
- data/lib/leap_cli.rb +46 -0
- data/lib/lib_ext/capistrano_connections.rb +16 -0
- data/lib/lib_ext/gli.rb +52 -0
- data/lib/lib_ext/markdown_document_listener.rb +122 -0
- data/vendor/certificate_authority/lib/certificate_authority/certificate.rb +200 -0
- data/vendor/certificate_authority/lib/certificate_authority/certificate_revocation_list.rb +77 -0
- data/vendor/certificate_authority/lib/certificate_authority/distinguished_name.rb +97 -0
- data/vendor/certificate_authority/lib/certificate_authority/extensions.rb +266 -0
- data/vendor/certificate_authority/lib/certificate_authority/key_material.rb +148 -0
- data/vendor/certificate_authority/lib/certificate_authority/ocsp_handler.rb +144 -0
- data/vendor/certificate_authority/lib/certificate_authority/pkcs11_key_material.rb +65 -0
- data/vendor/certificate_authority/lib/certificate_authority/revocable.rb +14 -0
- data/vendor/certificate_authority/lib/certificate_authority/serial_number.rb +10 -0
- data/vendor/certificate_authority/lib/certificate_authority/signing_entity.rb +16 -0
- data/vendor/certificate_authority/lib/certificate_authority/signing_request.rb +56 -0
- data/vendor/certificate_authority/lib/certificate_authority.rb +21 -0
- data/vendor/rsync_command/lib/rsync_command/ssh_options.rb +159 -0
- data/vendor/rsync_command/lib/rsync_command/thread_pool.rb +36 -0
- data/vendor/rsync_command/lib/rsync_command/version.rb +3 -0
- data/vendor/rsync_command/lib/rsync_command.rb +96 -0
- data/vendor/rsync_command/test/rsync_test.rb +74 -0
- data/vendor/rsync_command/test/ssh_options_test.rb +61 -0
- data/vendor/vagrant_ssh_keys/vagrant.key +27 -0
- data/vendor/vagrant_ssh_keys/vagrant.pub +1 -0
- 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
|