leap_cli 1.7.4 → 1.8
Sign up to get free protection for your applications and to get access to all the features.
- 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
|