leap_cli 1.7.4 → 1.8
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.
- checksums.yaml +4 -4
- data/bin/leap +6 -13
- data/lib/leap/platform.rb +2 -0
- data/lib/leap_cli.rb +2 -1
- data/lib/leap_cli/bootstrap.rb +197 -0
- data/lib/leap_cli/commands/common.rb +61 -0
- data/lib/leap_cli/commands/new.rb +5 -1
- data/lib/leap_cli/commands/pre.rb +1 -66
- data/lib/leap_cli/config/environment.rb +180 -0
- data/lib/leap_cli/config/manager.rb +100 -197
- data/lib/leap_cli/config/node.rb +2 -2
- data/lib/leap_cli/config/object.rb +56 -43
- data/lib/leap_cli/config/object_list.rb +6 -3
- data/lib/leap_cli/config/provider.rb +11 -0
- data/lib/leap_cli/config/secrets.rb +14 -1
- data/lib/leap_cli/config/tag.rb +2 -2
- data/lib/leap_cli/leapfile.rb +1 -0
- data/lib/leap_cli/log.rb +1 -0
- data/lib/leap_cli/logger.rb +16 -12
- data/lib/leap_cli/markdown_document_listener.rb +3 -1
- data/lib/leap_cli/path.rb +12 -0
- data/lib/leap_cli/remote/leap_plugin.rb +9 -34
- data/lib/leap_cli/remote/puppet_plugin.rb +0 -40
- data/lib/leap_cli/remote/tasks.rb +9 -34
- data/lib/leap_cli/ssh_key.rb +5 -2
- data/lib/leap_cli/version.rb +2 -2
- metadata +5 -18
- data/lib/leap_cli/commands/ca.rb +0 -518
- data/lib/leap_cli/commands/clean.rb +0 -16
- data/lib/leap_cli/commands/compile.rb +0 -340
- data/lib/leap_cli/commands/db.rb +0 -65
- data/lib/leap_cli/commands/deploy.rb +0 -368
- data/lib/leap_cli/commands/env.rb +0 -76
- data/lib/leap_cli/commands/facts.rb +0 -100
- data/lib/leap_cli/commands/inspect.rb +0 -144
- data/lib/leap_cli/commands/list.rb +0 -132
- data/lib/leap_cli/commands/node.rb +0 -165
- data/lib/leap_cli/commands/node_init.rb +0 -169
- data/lib/leap_cli/commands/ssh.rb +0 -220
- data/lib/leap_cli/commands/test.rb +0 -74
- data/lib/leap_cli/commands/user.rb +0 -136
- data/lib/leap_cli/commands/util.rb +0 -50
- data/lib/leap_cli/commands/vagrant.rb +0 -197
@@ -1,165 +0,0 @@
|
|
1
|
-
#
|
2
|
-
# fyi: the `node init` command lives in node_init.rb,
|
3
|
-
# but all other `node x` commands live here.
|
4
|
-
#
|
5
|
-
|
6
|
-
autoload :IPAddr, 'ipaddr'
|
7
|
-
|
8
|
-
module LeapCli; module Commands
|
9
|
-
|
10
|
-
##
|
11
|
-
## COMMANDS
|
12
|
-
##
|
13
|
-
|
14
|
-
desc 'Node management'
|
15
|
-
command [:node, :n] do |node|
|
16
|
-
node.desc 'Create a new configuration file for a node named NAME.'
|
17
|
-
node.long_desc ["If specified, the optional argument SEED can be used to seed values in the node configuration file.",
|
18
|
-
"The format is property_name:value.",
|
19
|
-
"For example: `leap node add web1 ip_address:1.2.3.4 services:webapp`.",
|
20
|
-
"To set nested properties, property name can contain '.', like so: `leap node add web1 ssh.port:44`",
|
21
|
-
"Separeate multiple values for a single property with a comma, like so: `leap node add mynode services:webapp,dns`"].join("\n\n")
|
22
|
-
node.arg_name 'NAME [SEED]' # , :optional => false, :multiple => false
|
23
|
-
node.command :add do |add|
|
24
|
-
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
|
25
|
-
add.action do |global_options,options,args|
|
26
|
-
# argument sanity checks
|
27
|
-
name = args.first
|
28
|
-
assert_valid_node_name!(name, options[:local])
|
29
|
-
assert_files_missing! [:node_config, name]
|
30
|
-
|
31
|
-
# create and seed new node
|
32
|
-
node = Config::Node.new(manager)
|
33
|
-
if options[:local]
|
34
|
-
node['ip_address'] = pick_next_vagrant_ip_address
|
35
|
-
end
|
36
|
-
seed_node_data(node, args[1..-1])
|
37
|
-
validate_ip_address(node)
|
38
|
-
begin
|
39
|
-
write_file! [:node_config, name], node.dump_json + "\n"
|
40
|
-
node['name'] = name
|
41
|
-
if file_exists? :ca_cert, :ca_key
|
42
|
-
generate_cert_for_node(manager.reload_node!(node))
|
43
|
-
end
|
44
|
-
rescue LeapCli::ConfigError => exc
|
45
|
-
remove_node_files(name)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
node.desc 'Renames a node file, and all its related files.'
|
51
|
-
node.arg_name 'OLD_NAME NEW_NAME'
|
52
|
-
node.command :mv do |mv|
|
53
|
-
mv.action do |global_options,options,args|
|
54
|
-
node = get_node_from_args(args)
|
55
|
-
new_name = args.last
|
56
|
-
assert_valid_node_name!(new_name, node.vagrant?)
|
57
|
-
ensure_dir [:node_files_dir, new_name]
|
58
|
-
Leap::Platform.node_files.each do |path|
|
59
|
-
rename_file! [path, node.name], [path, new_name]
|
60
|
-
end
|
61
|
-
remove_directory! [:node_files_dir, node.name]
|
62
|
-
rename_node_facts(node.name, new_name)
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
node.desc 'Removes all the files related to the node named NAME.'
|
67
|
-
node.arg_name 'NAME' #:optional => false #, :multiple => false
|
68
|
-
node.command :rm do |rm|
|
69
|
-
rm.action do |global_options,options,args|
|
70
|
-
node = get_node_from_args(args)
|
71
|
-
remove_node_files(node.name)
|
72
|
-
if node.vagrant?
|
73
|
-
vagrant_command("destroy --force", [node.name])
|
74
|
-
end
|
75
|
-
remove_node_facts(node.name)
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
##
|
81
|
-
## PUBLIC HELPERS
|
82
|
-
##
|
83
|
-
|
84
|
-
def get_node_from_args(args, options={})
|
85
|
-
node_name = args.first
|
86
|
-
node = manager.node(node_name)
|
87
|
-
if node.nil? && options[:include_disabled]
|
88
|
-
node = manager.disabled_node(node_name)
|
89
|
-
end
|
90
|
-
assert!(node, "Node '#{node_name}' not found.")
|
91
|
-
node
|
92
|
-
end
|
93
|
-
|
94
|
-
def seed_node_data(node, args)
|
95
|
-
args.each do |seed|
|
96
|
-
key, value = seed.split(':')
|
97
|
-
value = format_seed_value(value)
|
98
|
-
assert! key =~ /^[0-9a-z\._]+$/, "illegal characters used in property '#{key}'"
|
99
|
-
if key =~ /\./
|
100
|
-
key_parts = key.split('.')
|
101
|
-
final_key = key_parts.pop
|
102
|
-
current_object = node
|
103
|
-
key_parts.each do |key_part|
|
104
|
-
current_object[key_part] ||= Config::Object.new
|
105
|
-
current_object = current_object[key_part]
|
106
|
-
end
|
107
|
-
current_object[final_key] = value
|
108
|
-
else
|
109
|
-
node[key] = value
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
def remove_node_files(node_name)
|
115
|
-
(Leap::Platform.node_files + [:node_files_dir]).each do |path|
|
116
|
-
remove_file! [path, node_name]
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
#
|
121
|
-
# conversions:
|
122
|
-
#
|
123
|
-
# "x,y,z" => ["x","y","z"]
|
124
|
-
#
|
125
|
-
# "22" => 22
|
126
|
-
#
|
127
|
-
# "5.1" => 5.1
|
128
|
-
#
|
129
|
-
def format_seed_value(v)
|
130
|
-
if v =~ /,/
|
131
|
-
v = v.split(',')
|
132
|
-
v.map! do |i|
|
133
|
-
i = i.to_i if i.to_i.to_s == i
|
134
|
-
i = i.to_f if i.to_f.to_s == i
|
135
|
-
i
|
136
|
-
end
|
137
|
-
else
|
138
|
-
v = v.to_i if v.to_i.to_s == v
|
139
|
-
v = v.to_f if v.to_f.to_s == v
|
140
|
-
end
|
141
|
-
return v
|
142
|
-
end
|
143
|
-
|
144
|
-
def validate_ip_address(node)
|
145
|
-
IPAddr.new(node['ip_address'])
|
146
|
-
rescue ArgumentError
|
147
|
-
bail! do
|
148
|
-
if node['ip_address']
|
149
|
-
log :invalid, "ip_address #{node['ip_address'].inspect}"
|
150
|
-
else
|
151
|
-
log :missing, "ip_address"
|
152
|
-
end
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
def assert_valid_node_name!(name, local=false)
|
157
|
-
assert! name, 'No <node-name> specified.'
|
158
|
-
if local
|
159
|
-
assert! name =~ /^[0-9a-z]+$/, "illegal characters used in node name '#{name}' (note: Vagrant does not allow hyphens or underscores)"
|
160
|
-
else
|
161
|
-
assert! name =~ /^[0-9a-z-]+$/, "illegal characters used in node name '#{name}' (note: Linux does not allow underscores)"
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
|
-
end; end
|
@@ -1,169 +0,0 @@
|
|
1
|
-
#
|
2
|
-
# Node initialization.
|
3
|
-
# Most of the fun stuff is in tasks.rb.
|
4
|
-
#
|
5
|
-
|
6
|
-
module LeapCli; module Commands
|
7
|
-
|
8
|
-
desc 'Node management'
|
9
|
-
command :node do |node|
|
10
|
-
node.desc 'Bootstraps a node or nodes, setting up SSH keys and installing prerequisite packages'
|
11
|
-
node.long_desc "This command prepares a server to be used with the LEAP Platform by saving the server's SSH host key, " +
|
12
|
-
"copying the authorized_keys file, installing packages that are required for deploying, and registering important facts. " +
|
13
|
-
"Node init must be run before deploying to a server, and the server must be running and available via the network. " +
|
14
|
-
"This command only needs to be run once, but there is no harm in running it multiple times."
|
15
|
-
node.arg_name 'FILTER'
|
16
|
-
node.command :init do |init|
|
17
|
-
init.switch 'echo', :desc => 'If set, passwords are visible as you type them (default is hidden)', :negatable => false
|
18
|
-
init.flag :port, :desc => 'Override the default SSH port.', :arg_name => 'PORT'
|
19
|
-
init.flag :ip, :desc => 'Override the default SSH IP address.', :arg_name => 'IPADDRESS'
|
20
|
-
|
21
|
-
init.action do |global,options,args|
|
22
|
-
assert! args.any?, 'You must specify a FILTER'
|
23
|
-
finished = []
|
24
|
-
manager.filter!(args).each_node do |node|
|
25
|
-
is_node_alive(node, options)
|
26
|
-
save_public_host_key(node, global, options) unless node.vagrant?
|
27
|
-
update_compiled_ssh_configs
|
28
|
-
ssh_connect_options = connect_options(options).merge({:bootstrap => true, :echo => options[:echo]})
|
29
|
-
ssh_connect(node, ssh_connect_options) do |ssh|
|
30
|
-
if node.vagrant?
|
31
|
-
ssh.install_insecure_vagrant_key
|
32
|
-
end
|
33
|
-
ssh.install_authorized_keys
|
34
|
-
ssh.install_prerequisites
|
35
|
-
unless node.vagrant?
|
36
|
-
ssh.leap.log(:checking, "SSH host keys") do
|
37
|
-
ssh.leap.capture(get_ssh_keys_cmd) do |response|
|
38
|
-
update_local_ssh_host_keys(node, response[:data]) if response[:exitcode] == 0
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
ssh.leap.log(:updating, "facts") do
|
43
|
-
ssh.leap.capture(facter_cmd) do |response|
|
44
|
-
if response[:exitcode] == 0
|
45
|
-
update_node_facts(node.name, response[:data])
|
46
|
-
else
|
47
|
-
log :failed, "to run facter on #{node.name}"
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
finished << node.name
|
53
|
-
end
|
54
|
-
log :completed, "initialization of nodes #{finished.join(', ')}"
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
private
|
60
|
-
|
61
|
-
##
|
62
|
-
## PRIVATE HELPERS
|
63
|
-
##
|
64
|
-
|
65
|
-
def is_node_alive(node, options)
|
66
|
-
address = options[:ip] || node.ip_address
|
67
|
-
port = options[:port] || node.ssh.port
|
68
|
-
log :connecting, "to node #{node.name}"
|
69
|
-
assert_run! "nc -zw3 #{address} #{port}",
|
70
|
-
"Failed to reach #{node.name} (address #{address}, port #{port}). You can override the configured IP address and port with --ip or --port."
|
71
|
-
end
|
72
|
-
|
73
|
-
#
|
74
|
-
# saves the public ssh host key for node into the provider directory.
|
75
|
-
#
|
76
|
-
# see `man sshd` for the format of known_hosts
|
77
|
-
#
|
78
|
-
def save_public_host_key(node, global, options)
|
79
|
-
log :fetching, "public SSH host key for #{node.name}"
|
80
|
-
address = options[:ip] || node.ip_address
|
81
|
-
port = options[:port] || node.ssh.port
|
82
|
-
host_keys = get_public_keys_for_ip(address, port)
|
83
|
-
pub_key_path = Path.named_path([:node_ssh_pub_key, node.name])
|
84
|
-
|
85
|
-
if Path.exists?(pub_key_path)
|
86
|
-
if host_keys.include? SshKey.load(pub_key_path)
|
87
|
-
log :trusted, "- Public SSH host key for #{node.name} matches previously saved key", :indent => 1
|
88
|
-
else
|
89
|
-
bail! do
|
90
|
-
log :error, "The public SSH host keys we just fetched for #{node.name} doesn't match what we have saved previously.", :indent => 1
|
91
|
-
log "Delete the file #{pub_key_path} if you really want to remove the trusted SSH host key.", :indent => 2
|
92
|
-
end
|
93
|
-
end
|
94
|
-
else
|
95
|
-
known_key = host_keys.detect{|k|k.in_known_hosts?(node.name, node.ip_address, node.domain.name)}
|
96
|
-
if known_key
|
97
|
-
log :trusted, "- Public SSH host key for #{node.name} is trusted (key found in your ~/.ssh/known_hosts)"
|
98
|
-
else
|
99
|
-
public_key = SshKey.pick_best_key(host_keys)
|
100
|
-
if public_key.nil?
|
101
|
-
bail!("We got back #{host_keys.size} host keys from #{node.name}, but we can't support any of them.")
|
102
|
-
else
|
103
|
-
say(" This is the SSH host key you got back from node \"#{node.name}\"")
|
104
|
-
say(" Type -- #{public_key.bits} bit #{public_key.type.upcase}")
|
105
|
-
say(" Fingerprint -- " + public_key.fingerprint)
|
106
|
-
say(" Public Key -- " + public_key.key)
|
107
|
-
if !global[:yes] && !agree(" Is this correct? ")
|
108
|
-
bail!
|
109
|
-
else
|
110
|
-
known_key = public_key
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
114
|
-
puts
|
115
|
-
write_file! [:node_ssh_pub_key, node.name], known_key.to_s
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
#
|
120
|
-
# Get the public host keys for a host using ssh-keyscan.
|
121
|
-
# Return an array of SshKey objects, one for each key.
|
122
|
-
#
|
123
|
-
def get_public_keys_for_ip(address, port=22)
|
124
|
-
assert_bin!('ssh-keyscan')
|
125
|
-
output = assert_run! "ssh-keyscan -p #{port} #{address}", "Could not get the public host key from #{address}:#{port}. Maybe sshd is not running?"
|
126
|
-
if output.empty?
|
127
|
-
bail! :failed, "ssh-keyscan returned empty output."
|
128
|
-
end
|
129
|
-
|
130
|
-
if output =~ /No route to host/
|
131
|
-
bail! :failed, 'ssh-keyscan: no route to %s' % address
|
132
|
-
else
|
133
|
-
keys = SshKey.parse_keys(output)
|
134
|
-
if keys.empty?
|
135
|
-
bail! "ssh-keyscan got zero host keys back (that we understand)! Output was: #{output}"
|
136
|
-
else
|
137
|
-
return keys
|
138
|
-
end
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
# run on the server to generate a string suitable for passing to SshKey.parse_keys()
|
143
|
-
def get_ssh_keys_cmd
|
144
|
-
"/bin/grep ^HostKey /etc/ssh/sshd_config | /usr/bin/awk '{print $2 \".pub\"}' | /usr/bin/xargs /bin/cat"
|
145
|
-
end
|
146
|
-
|
147
|
-
#
|
148
|
-
# Sometimes the ssh host keys on the server will be better than what we have
|
149
|
-
# stored locally. In these cases, ask the user if they want to upgrade.
|
150
|
-
#
|
151
|
-
def update_local_ssh_host_keys(node, remote_keys_string)
|
152
|
-
remote_keys = SshKey.parse_keys(remote_keys_string)
|
153
|
-
return unless remote_keys.any?
|
154
|
-
current_key = SshKey.load(Path.named_path([:node_ssh_pub_key, node.name]))
|
155
|
-
best_key = SshKey.pick_best_key(remote_keys)
|
156
|
-
return unless best_key && current_key
|
157
|
-
if current_key != best_key
|
158
|
-
say(" One of the SSH host keys for node '#{node.name}' is better than what you currently have trusted.")
|
159
|
-
say(" Current key: #{current_key.summary}")
|
160
|
-
say(" Better key: #{best_key.summary}")
|
161
|
-
if agree(" Do you want to use the better key? ")
|
162
|
-
write_file! [:node_ssh_pub_key, node.name], best_key.to_s
|
163
|
-
end
|
164
|
-
else
|
165
|
-
log(3, "current host key does not need updating")
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
end; end
|
@@ -1,220 +0,0 @@
|
|
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.flag 'ssh', :desc => "Pass through raw options to ssh (e.g. `--ssh '-F ~/sshconfig'`)."
|
7
|
-
c.flag 'port', :arg_name => 'SSH_PORT', :desc => 'Override default SSH port used when trying to connect to the server. Same as `--ssh "-p SSH_PORT"`.'
|
8
|
-
c.action do |global_options,options,args|
|
9
|
-
exec_ssh(:ssh, options, args)
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
desc 'Log in to the specified node with an interactive shell using mosh (requires node to have mosh.enabled set to true).'
|
14
|
-
arg_name 'NAME'
|
15
|
-
command :mosh do |c|
|
16
|
-
c.flag 'ssh', :desc => "Pass through raw options to ssh (e.g. `--ssh '-F ~/sshconfig'`)."
|
17
|
-
c.flag 'port', :arg_name => 'SSH_PORT', :desc => 'Override default SSH port used when trying to connect to the server. Same as `--ssh "-p SSH_PORT"`.'
|
18
|
-
c.action do |global_options,options,args|
|
19
|
-
exec_ssh(:mosh, options, args)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
desc 'Creates an SSH port forward (tunnel) to the node NAME. REMOTE_PORT is the port on the remote node that the tunnel will connect to. LOCAL_PORT is the optional port on your local machine. For example: `leap tunnel couch1:5984`.'
|
24
|
-
arg_name '[LOCAL_PORT:]NAME:REMOTE_PORT'
|
25
|
-
command :tunnel do |c|
|
26
|
-
c.flag 'ssh', :desc => "Pass through raw options to ssh (e.g. --ssh '-F ~/sshconfig')."
|
27
|
-
c.flag 'port', :arg_name => 'SSH_PORT', :desc => 'Override default SSH port used when trying to connect to the server. Same as `--ssh "-p SSH_PORT"`.'
|
28
|
-
c.action do |global_options,options,args|
|
29
|
-
local_port, node, remote_port = parse_tunnel_arg(args.first)
|
30
|
-
options[:ssh] = [options[:ssh], "-N -L 127.0.0.1:#{local_port}:0.0.0.0:#{remote_port}"].join(' ')
|
31
|
-
log("Forward port localhost:#{local_port} to #{node.name}:#{remote_port}")
|
32
|
-
if is_port_available?(local_port)
|
33
|
-
exec_ssh(:ssh, options, [node.name])
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
desc 'Secure copy from FILE1 to FILE2. Files are specified as NODE_NAME:FILE_PATH. For local paths, omit "NODE_NAME:".'
|
39
|
-
arg_name 'FILE1 FILE2'
|
40
|
-
command :scp do |c|
|
41
|
-
c.switch :r, :desc => 'Copy recursively'
|
42
|
-
c.action do |global_options, options, args|
|
43
|
-
if args.size != 2
|
44
|
-
bail!('You must specificy both FILE1 and FILE2')
|
45
|
-
end
|
46
|
-
from, to = args
|
47
|
-
if (from !~ /:/ && to !~ /:/) || (from =~ /:/ && to =~ /:/)
|
48
|
-
bail!('One FILE must be remote and the other local.')
|
49
|
-
end
|
50
|
-
src_node_name = src_file_path = src_node = nil
|
51
|
-
dst_node_name = dst_file_path = dst_node = nil
|
52
|
-
if from =~ /:/
|
53
|
-
src_node_name, src_file_path = from.split(':')
|
54
|
-
src_node = get_node_from_args([src_node_name], :include_disabled => true)
|
55
|
-
dst_file_path = to
|
56
|
-
else
|
57
|
-
dst_node_name, dst_file_path = to.split(':')
|
58
|
-
dst_node = get_node_from_args([dst_node_name], :include_disabled => true)
|
59
|
-
src_file_path = from
|
60
|
-
end
|
61
|
-
exec_scp(options, src_node, src_file_path, dst_node, dst_file_path)
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
protected
|
66
|
-
|
67
|
-
#
|
68
|
-
# allow for ssh overrides of all commands that use ssh_connect
|
69
|
-
#
|
70
|
-
def connect_options(options)
|
71
|
-
connect_options = {:ssh_options=>{}}
|
72
|
-
if options[:port]
|
73
|
-
connect_options[:ssh_options][:port] = options[:port]
|
74
|
-
end
|
75
|
-
if options[:ip]
|
76
|
-
connect_options[:ssh_options][:host_name] = options[:ip]
|
77
|
-
end
|
78
|
-
return connect_options
|
79
|
-
end
|
80
|
-
|
81
|
-
def ssh_config_help_message
|
82
|
-
puts ""
|
83
|
-
puts "Are 'too many authentication failures' getting you down?"
|
84
|
-
puts "Then we have the solution for you! Add something like this to your ~/.ssh/config file:"
|
85
|
-
puts " Host *.#{manager.provider.domain}"
|
86
|
-
puts " IdentityFile ~/.ssh/id_rsa"
|
87
|
-
puts " IdentitiesOnly=yes"
|
88
|
-
puts "(replace `id_rsa` with the actual private key filename that you use for this provider)"
|
89
|
-
end
|
90
|
-
|
91
|
-
require 'socket'
|
92
|
-
def is_port_available?(port)
|
93
|
-
TCPServer.open('127.0.0.1', port) {}
|
94
|
-
true
|
95
|
-
rescue Errno::EACCES
|
96
|
-
bail!("You don't have permission to bind to port #{port}.")
|
97
|
-
rescue Errno::EADDRINUSE
|
98
|
-
bail!("Local port #{port} is already in use. Specify LOCAL_PORT to pick another.")
|
99
|
-
rescue Exception => exc
|
100
|
-
bail!(exc.to_s)
|
101
|
-
end
|
102
|
-
|
103
|
-
private
|
104
|
-
|
105
|
-
def exec_ssh(cmd, cli_options, args)
|
106
|
-
node = get_node_from_args(args, :include_disabled => true)
|
107
|
-
port = node.ssh.port
|
108
|
-
options = ssh_config(node)
|
109
|
-
username = 'root'
|
110
|
-
if LeapCli.log_level >= 3
|
111
|
-
options << "-vv"
|
112
|
-
elsif LeapCli.log_level >= 2
|
113
|
-
options << "-v"
|
114
|
-
end
|
115
|
-
if cli_options[:port]
|
116
|
-
port = cli_options[:port]
|
117
|
-
end
|
118
|
-
if cli_options[:ssh]
|
119
|
-
options << cli_options[:ssh]
|
120
|
-
end
|
121
|
-
ssh = "ssh -l #{username} -p #{port} #{options.join(' ')}"
|
122
|
-
if cmd == :ssh
|
123
|
-
command = "#{ssh} #{node.domain.full}"
|
124
|
-
elsif cmd == :mosh
|
125
|
-
command = "MOSH_TITLE_NOPREFIX=1 mosh --ssh \"#{ssh}\" #{node.domain.full}"
|
126
|
-
end
|
127
|
-
log 2, command
|
128
|
-
|
129
|
-
# exec the shell command in a subprocess
|
130
|
-
pid = fork { exec "#{command}" }
|
131
|
-
|
132
|
-
Signal.trap("SIGINT") do
|
133
|
-
Process.kill("KILL", pid)
|
134
|
-
Process.wait(pid)
|
135
|
-
exit(0)
|
136
|
-
end
|
137
|
-
|
138
|
-
# wait for shell to exit so we can grab the exit status
|
139
|
-
_, status = Process.waitpid2(pid)
|
140
|
-
|
141
|
-
if status.exitstatus == 255
|
142
|
-
ssh_config_help_message
|
143
|
-
elsif status.exitstatus != 0
|
144
|
-
exit(status.exitstatus)
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
def exec_scp(cli_options, src_node, src_file_path, dst_node, dst_file_path)
|
149
|
-
node = src_node || dst_node
|
150
|
-
options = ssh_config(node)
|
151
|
-
port = node.ssh.port
|
152
|
-
username = 'root'
|
153
|
-
options << "-r" if cli_options[:r]
|
154
|
-
scp = "scp -P #{port} #{options.join(' ')}"
|
155
|
-
if src_node
|
156
|
-
command = "#{scp} #{username}@#{src_node.domain.full}:#{src_file_path} #{dst_file_path}"
|
157
|
-
elsif dst_node
|
158
|
-
command = "#{scp} #{src_file_path} #{username}@#{dst_node.domain.full}:#{dst_file_path}"
|
159
|
-
end
|
160
|
-
log 2, command
|
161
|
-
|
162
|
-
# exec the shell command in a subprocess
|
163
|
-
pid = fork { exec "#{command}" }
|
164
|
-
|
165
|
-
Signal.trap("SIGINT") do
|
166
|
-
Process.kill("KILL", pid)
|
167
|
-
Process.wait(pid)
|
168
|
-
exit(0)
|
169
|
-
end
|
170
|
-
|
171
|
-
# wait for shell to exit so we can grab the exit status
|
172
|
-
_, status = Process.waitpid2(pid)
|
173
|
-
exit(status.exitstatus)
|
174
|
-
end
|
175
|
-
|
176
|
-
#
|
177
|
-
# SSH command line -o options. See `man ssh_config`
|
178
|
-
#
|
179
|
-
# NOTES:
|
180
|
-
#
|
181
|
-
# The option 'HostKeyAlias=#{node.name}' is oddly incompatible with ports in
|
182
|
-
# known_hosts file, so we must not use this or non-standard ports break.
|
183
|
-
#
|
184
|
-
def ssh_config(node)
|
185
|
-
options = [
|
186
|
-
"-o 'HostName=#{node.ip_address}'",
|
187
|
-
"-o 'GlobalKnownHostsFile=#{path(:known_hosts)}'",
|
188
|
-
"-o 'UserKnownHostsFile=/dev/null'"
|
189
|
-
]
|
190
|
-
if node.vagrant?
|
191
|
-
options << "-i #{vagrant_ssh_key_file}" # use the universal vagrant insecure key
|
192
|
-
options << "-o IdentitiesOnly=yes" # force the use of the insecure vagrant key
|
193
|
-
options << "-o 'StrictHostKeyChecking=no'" # blindly accept host key and don't save it
|
194
|
-
# (since userknownhostsfile is /dev/null)
|
195
|
-
else
|
196
|
-
options << "-o 'StrictHostKeyChecking=yes'"
|
197
|
-
end
|
198
|
-
if !node.supported_ssh_host_key_algorithms.empty?
|
199
|
-
options << "-o 'HostKeyAlgorithms=#{node.supported_ssh_host_key_algorithms}'"
|
200
|
-
end
|
201
|
-
return options
|
202
|
-
end
|
203
|
-
|
204
|
-
def parse_tunnel_arg(arg)
|
205
|
-
if arg.count(':') == 1
|
206
|
-
node_name, remote = arg.split(':')
|
207
|
-
local = nil
|
208
|
-
elsif arg.count(':') == 2
|
209
|
-
local, node_name, remote = arg.split(':')
|
210
|
-
else
|
211
|
-
bail!('Argument NAME:REMOTE_PORT required.')
|
212
|
-
end
|
213
|
-
node = get_node_from_args([node_name], :include_disabled => true)
|
214
|
-
remote = remote.to_i
|
215
|
-
local = local || remote
|
216
|
-
local = local.to_i
|
217
|
-
return [local, node, remote]
|
218
|
-
end
|
219
|
-
|
220
|
-
end; end
|