leap_cli 1.7.4 → 1.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/bin/leap +6 -13
  3. data/lib/leap/platform.rb +2 -0
  4. data/lib/leap_cli.rb +2 -1
  5. data/lib/leap_cli/bootstrap.rb +197 -0
  6. data/lib/leap_cli/commands/common.rb +61 -0
  7. data/lib/leap_cli/commands/new.rb +5 -1
  8. data/lib/leap_cli/commands/pre.rb +1 -66
  9. data/lib/leap_cli/config/environment.rb +180 -0
  10. data/lib/leap_cli/config/manager.rb +100 -197
  11. data/lib/leap_cli/config/node.rb +2 -2
  12. data/lib/leap_cli/config/object.rb +56 -43
  13. data/lib/leap_cli/config/object_list.rb +6 -3
  14. data/lib/leap_cli/config/provider.rb +11 -0
  15. data/lib/leap_cli/config/secrets.rb +14 -1
  16. data/lib/leap_cli/config/tag.rb +2 -2
  17. data/lib/leap_cli/leapfile.rb +1 -0
  18. data/lib/leap_cli/log.rb +1 -0
  19. data/lib/leap_cli/logger.rb +16 -12
  20. data/lib/leap_cli/markdown_document_listener.rb +3 -1
  21. data/lib/leap_cli/path.rb +12 -0
  22. data/lib/leap_cli/remote/leap_plugin.rb +9 -34
  23. data/lib/leap_cli/remote/puppet_plugin.rb +0 -40
  24. data/lib/leap_cli/remote/tasks.rb +9 -34
  25. data/lib/leap_cli/ssh_key.rb +5 -2
  26. data/lib/leap_cli/version.rb +2 -2
  27. metadata +5 -18
  28. data/lib/leap_cli/commands/ca.rb +0 -518
  29. data/lib/leap_cli/commands/clean.rb +0 -16
  30. data/lib/leap_cli/commands/compile.rb +0 -340
  31. data/lib/leap_cli/commands/db.rb +0 -65
  32. data/lib/leap_cli/commands/deploy.rb +0 -368
  33. data/lib/leap_cli/commands/env.rb +0 -76
  34. data/lib/leap_cli/commands/facts.rb +0 -100
  35. data/lib/leap_cli/commands/inspect.rb +0 -144
  36. data/lib/leap_cli/commands/list.rb +0 -132
  37. data/lib/leap_cli/commands/node.rb +0 -165
  38. data/lib/leap_cli/commands/node_init.rb +0 -169
  39. data/lib/leap_cli/commands/ssh.rb +0 -220
  40. data/lib/leap_cli/commands/test.rb +0 -74
  41. data/lib/leap_cli/commands/user.rb +0 -136
  42. data/lib/leap_cli/commands/util.rb +0 -50
  43. 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