xlogin 0.5.14 → 0.6.1
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/xlogin +0 -7
- data/lib/xlogin.rb +27 -33
- data/lib/xlogin/cli.rb +47 -61
- data/lib/xlogin/delegator.rb +35 -43
- data/lib/xlogin/{firmware_factory.rb → factory.rb} +35 -43
- data/lib/xlogin/rake_task.rb +54 -125
- data/lib/xlogin/session.rb +106 -82
- data/lib/xlogin/ssh.rb +1 -15
- data/lib/xlogin/telnet.rb +13 -12
- data/lib/xlogin/template.rb +52 -0
- data/lib/xlogin/version.rb +1 -1
- metadata +4 -5
- data/lib/xlogin/firmware.rb +0 -64
- data/lib/xlogin/gateway.rb +0 -35
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 07769f0169bc78d592d032d9e1fead22b33f486a
|
4
|
+
data.tar.gz: 701a5526bd5592fc5018970a4854c75e9f100333
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 867923f469e68e5ddaf5dd7b657f0331094cedccbc1040d452d83ea96da7aef1b643916d543f1ae68a71e825ad390e1d27d559b281be1d1579f23d94dd418516
|
7
|
+
data.tar.gz: ff5c63bca0182e256274611bcb62d026c9a41b997d4157ad98bbe0eba75a24857b621dc749aac9bb4b6c75797e95fa8eb5ed644640c1bb074587a0ab807ea577
|
data/bin/xlogin
CHANGED
@@ -1,12 +1,5 @@
|
|
1
1
|
#! /usr/bin/env ruby
|
2
2
|
|
3
|
-
appdir = File.dirname(File.symlink?(__FILE__)? File.readlink(__FILE__) : __FILE__)
|
4
|
-
|
5
|
-
require 'bundler'
|
6
|
-
ENV['BUNDLE_GEMFILE'] = File.join(appdir, '..', 'Gemfile')
|
7
|
-
|
8
|
-
Bundler.require
|
9
|
-
|
10
3
|
require 'xlogin'
|
11
4
|
require 'xlogin/cli'
|
12
5
|
|
data/lib/xlogin.rb
CHANGED
@@ -1,62 +1,62 @@
|
|
1
1
|
$:.unshift File.dirname(__FILE__)
|
2
2
|
|
3
|
-
require 'xlogin/
|
4
|
-
require 'xlogin/firmware_factory'
|
3
|
+
require 'xlogin/factory'
|
5
4
|
require 'xlogin/version'
|
6
5
|
|
7
6
|
module Xlogin
|
8
7
|
|
9
|
-
|
8
|
+
DEFAULT_INVENTORY_FILE = File.join(ENV['HOME'], '.xloginrc')
|
10
9
|
DEFAULT_TEMPLATE_DIR = File.join(ENV['HOME'], '.xlogin.d')
|
11
10
|
BUILTIN_TEMPLATE_FILES = Dir.glob(File.join(File.dirname(__FILE__), 'xlogin', 'templates', '*.rb'))
|
12
11
|
|
13
|
-
class
|
12
|
+
class SessionNotFound < StandardError; end
|
14
13
|
class TemplateNotFound < StandardError; end
|
15
14
|
class AuthorizationError < StandardError; end
|
16
15
|
|
17
16
|
class << self
|
18
17
|
|
19
|
-
def
|
20
|
-
@
|
18
|
+
def factory
|
19
|
+
@factory ||= Xlogin::Factory.instance
|
21
20
|
end
|
22
21
|
|
23
|
-
def
|
24
|
-
|
25
|
-
end
|
22
|
+
def get(hostname, args = {})
|
23
|
+
session = factory.build_from_hostname(hostname, args)
|
26
24
|
|
27
|
-
|
28
|
-
|
25
|
+
if block_given?
|
26
|
+
begin yield session ensure session.close end
|
27
|
+
else
|
28
|
+
session
|
29
|
+
end
|
29
30
|
end
|
30
31
|
|
31
|
-
def
|
32
|
-
instance_eval(&block)
|
32
|
+
def configure(&block)
|
33
|
+
instance_eval(&block) if block
|
33
34
|
|
34
|
-
|
35
|
+
source(DEFAULT_INVENTORY_FILE) if factory.list.empty?
|
35
36
|
if factory.list_templates.empty?
|
36
|
-
unless
|
37
|
+
unless Dir.exist?(DEFAULT_TEMPLATE_DIR)
|
37
38
|
FileUtils.mkdir_p(DEFAULT_TEMPLATE_DIR)
|
38
39
|
Xlogin::BUILTIN_TEMPLATE_FILES.each { |file| FileUtils.cp(file, DEFAULT_TEMPLATE_DIR) }
|
39
40
|
end
|
40
|
-
|
41
|
+
template_dir(DEFAULT_TEMPLATE_DIR)
|
41
42
|
end
|
42
43
|
end
|
43
44
|
|
44
|
-
def
|
45
|
-
|
45
|
+
def authorized?
|
46
|
+
@authorized == true
|
46
47
|
end
|
47
48
|
|
48
|
-
|
49
|
-
|
49
|
+
private
|
50
|
+
def authorize(boolean = false, &block)
|
51
|
+
@authorized = boolean == true || (block && block.call == true)
|
50
52
|
end
|
51
53
|
|
52
|
-
def
|
53
|
-
|
54
|
+
def source(source_file)
|
55
|
+
factory.source(source_file)
|
56
|
+
end
|
54
57
|
|
55
|
-
|
56
|
-
|
57
|
-
else
|
58
|
-
session
|
59
|
-
end
|
58
|
+
def template(*template_files)
|
59
|
+
factory.set_template(*template_files)
|
60
60
|
end
|
61
61
|
|
62
62
|
def template_dir(*template_dirs)
|
@@ -65,12 +65,6 @@ module Xlogin
|
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
68
|
-
def configure(name)
|
69
|
-
template = factory.get_template(name) || Xlogin::Firmware.new
|
70
|
-
yield template if block_given?
|
71
|
-
factory.set_template(name, template)
|
72
|
-
end
|
73
|
-
|
74
68
|
end
|
75
69
|
|
76
70
|
end
|
data/lib/xlogin/cli.rb
CHANGED
@@ -1,12 +1,9 @@
|
|
1
1
|
#! /usr/bin/env ruby
|
2
2
|
|
3
|
-
require 'fileutils'
|
4
3
|
require 'optparse'
|
5
4
|
require 'ostruct'
|
6
5
|
require 'parallel'
|
7
|
-
require 'readline'
|
8
6
|
require 'stringio'
|
9
|
-
require 'thread'
|
10
7
|
|
11
8
|
module Xlogin
|
12
9
|
class CLI
|
@@ -14,21 +11,29 @@ module Xlogin
|
|
14
11
|
DEFAULT_INVENTORY_PATH = File.join(ENV['HOME'], '.xloginrc')
|
15
12
|
DEFAULT_TEMPLATE_DIR = File.join(ENV['HOME'], '.xlogin.d')
|
16
13
|
|
14
|
+
def self.run(args = ARGV)
|
15
|
+
config = getopts(args)
|
16
|
+
client = Xlogin::CLI.new
|
17
|
+
|
18
|
+
task = config.task.downcase.tr('-', '_')
|
19
|
+
Xlogin::CLI.usage("Task not defined - #{task}") unless client.respond_to?(task)
|
20
|
+
client.method(task).call(config)
|
21
|
+
end
|
22
|
+
|
17
23
|
def self.getopts(args)
|
18
24
|
config = OpenStruct.new(
|
19
|
-
|
25
|
+
task: 'tty',
|
20
26
|
inventory: DEFAULT_INVENTORY_PATH,
|
21
27
|
parallels: 5,
|
22
28
|
templates: [],
|
23
|
-
hostexprs: [],
|
24
29
|
hostlist: [],
|
25
30
|
)
|
26
31
|
|
27
32
|
parser = OptionParser.new
|
28
33
|
parser.banner += ' HOST-PATTERN'
|
29
34
|
|
30
|
-
parser.on('-
|
31
|
-
parser.on('-a
|
35
|
+
parser.on('-m TASK', '--task', String, 'Execute the TASK(default: tty).') { |v| config.task = v }
|
36
|
+
parser.on('-a ARGS', '--args', String, 'The ARGS to pass to the task.') { |v| config.args = v }
|
32
37
|
|
33
38
|
parser.on('-i PATH', '--inventory', String, 'The PATH to the inventory file (default: $HOME/.xloginrc).') { |v| config.inventory = v }
|
34
39
|
parser.on('-t PATH', '--template', String, 'The PATH to the template file.') { |v| config.templates << v }
|
@@ -36,66 +41,48 @@ module Xlogin
|
|
36
41
|
parser.on('-l [DIRECTORY]', '--log', String, 'The DIRECTORY to the output log file (default: $PWD/log).') { |v| config.logdir = v || Dir.pwd }
|
37
42
|
|
38
43
|
parser.on('-p NUM', '--parallels', Integer, 'The NUM of the threads. (default: 5).') { |v| config.parallels = v }
|
39
|
-
parser.on('-e', '--enable', TrueClass, 'Try to gain enable priviledge.') { |v| config.
|
44
|
+
parser.on('-e', '--enable', TrueClass, 'Try to gain enable priviledge.') { |v| config.enable = v }
|
40
45
|
parser.on('-y', '--assume-yes', TrueClass, 'Always answer "yes" if confirmed.') { |v| config.assume_yes = v }
|
41
46
|
parser.on('-h', '--help', 'Show this message.') { Xlogin::CLI.usage }
|
42
47
|
|
43
48
|
self.class.module_eval do
|
44
49
|
define_method(:usage) do |message = nil|
|
45
|
-
puts message
|
50
|
+
puts message if message
|
46
51
|
puts parser.to_s
|
47
52
|
exit 1
|
48
53
|
end
|
49
54
|
end
|
50
55
|
|
51
|
-
config.
|
52
|
-
|
53
|
-
|
54
|
-
Xlogin.init do
|
56
|
+
config.templates += Dir.glob(File.join(DEFAULT_TEMPLATE_DIR, '*.rb')) if config.templates.empty?
|
57
|
+
Xlogin.configure do
|
55
58
|
source(config.inventory)
|
56
59
|
template(*config.templates)
|
57
60
|
authorize(config.assume_yes)
|
58
61
|
end
|
59
62
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
end
|
64
|
-
|
65
|
-
def self.run(args = ARGV)
|
66
|
-
config = getopts(args)
|
67
|
-
client = Xlogin::CLI.new
|
68
|
-
func = config.func.gsub(/([a-z\d])([A-Z])/, '\1_\2').gsub(/[-_]+/, '_').downcase
|
69
|
-
|
70
|
-
Xlogin::CLI.usage("Function not found - #{config.func}") unless client.respond_to?(func)
|
71
|
-
client.method(func).call(config)
|
72
|
-
end
|
73
|
-
|
74
|
-
def list(config)
|
75
|
-
if config.hostexprs.empty?
|
76
|
-
factory = Xlogin::FirmwareFactory.instance
|
77
|
-
config.hostlist = factory.list
|
63
|
+
config.hostlist += parser.parse(args).flat_map do |target|
|
64
|
+
hostlist = Xlogin.factory.list(target)
|
65
|
+
hostlist.tap { |e| raise "Invalid inventory - #{target}" if e.empty? }
|
78
66
|
end
|
79
67
|
|
80
|
-
|
81
|
-
|
82
|
-
puts matrix
|
68
|
+
config.parallels = [config.parallels, config.hostlist.size].min
|
69
|
+
config
|
83
70
|
end
|
84
71
|
|
85
72
|
def tty(config)
|
86
|
-
|
73
|
+
config.hostlist = [config.hostlist.shift].compact
|
74
|
+
Xlogin::CLI.usage('Invalid inventory.') if config.hostlist.empty?
|
87
75
|
|
88
76
|
puts "Trying #{config.hostlist.first[:name]}..."
|
89
77
|
puts "Escape character is '^]'."
|
90
78
|
|
91
|
-
config.hostlist = [config.hostlist.shift]
|
92
79
|
login(config) do |session|
|
93
80
|
session.interact!
|
94
81
|
end
|
95
82
|
end
|
96
83
|
|
97
|
-
def
|
98
|
-
Xlogin::CLI.usage('Invalid
|
84
|
+
def exec(config)
|
85
|
+
Xlogin::CLI.usage('Invalid inventory.') if config.hostlist.empty?
|
99
86
|
|
100
87
|
login(config) do |session|
|
101
88
|
command_lines = ['', *config.args.split(';')]
|
@@ -103,8 +90,8 @@ module Xlogin
|
|
103
90
|
end
|
104
91
|
end
|
105
92
|
|
106
|
-
def
|
107
|
-
Xlogin::CLI.usage('Invalid
|
93
|
+
def load(config)
|
94
|
+
Xlogin::CLI.usage('Invalid inventory.') if config.hostlist.empty?
|
108
95
|
|
109
96
|
login(config) do |session|
|
110
97
|
command_lines = ['', *IO.readlines(config.args.to_s)]
|
@@ -112,39 +99,38 @@ module Xlogin
|
|
112
99
|
end
|
113
100
|
end
|
114
101
|
|
102
|
+
def list(config)
|
103
|
+
config.hostlist += Xlogin.factory.list('all') if config.hostlist.empty?
|
104
|
+
width = config.hostlist.map { |e| e[:name].length }.max
|
105
|
+
puts config.hostlist.map { |e| "#{e[:name].to_s.ljust(width)} #{e[:type]}" }.sort
|
106
|
+
end
|
107
|
+
|
115
108
|
private
|
116
109
|
def login(config, &block)
|
117
|
-
|
118
|
-
|
110
|
+
display = Mutex.new
|
111
|
+
buffer = StringIO.new
|
119
112
|
|
120
|
-
|
121
|
-
Parallel.map(config.hostlist, in_thread: config.parallels) do |host|
|
113
|
+
Parallel.map(config.hostlist, in_thread: config.parallels) do |hostinfo|
|
122
114
|
begin
|
123
|
-
hostname =
|
124
|
-
buffer = StringIO.new
|
115
|
+
hostname = hostinfo[:name]
|
125
116
|
|
126
117
|
loggers = []
|
127
118
|
loggers << buffer if config.parallels != 1
|
128
119
|
loggers << $stdout if config.parallels == 1
|
129
120
|
loggers << File.join(config.logdir, "#{hostname}.log") if config.logdir
|
130
121
|
|
131
|
-
session = Xlogin.
|
132
|
-
session.enable(session.
|
133
|
-
block.call(session)
|
134
|
-
|
135
|
-
if config.parallels > 1
|
136
|
-
output = buffer.string.lines.map { |line| "#{hostname}: #{line}" }.join
|
137
|
-
display.synchronize { $stdout.puts output }
|
138
|
-
end
|
122
|
+
session = Xlogin.factory.build(hostinfo.merge(log: loggers))
|
123
|
+
session.enable(session.opts.enable) if config.enable && session.respond_to?(:enable)
|
139
124
|
|
125
|
+
block.call(session)
|
140
126
|
rescue => e
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
127
|
+
lines = (config.parallels > 1)? "\n#{hostname}\t[Error] #{e}" : "\n[Error] #{e}"
|
128
|
+
display.synchronize { $stderr.puts lines }
|
129
|
+
end
|
130
|
+
|
131
|
+
if config.parallels > 1
|
132
|
+
lines = buffer.string.lines.map { |line| "#{hostname}\t" + line.gsub("\r", '') }
|
133
|
+
display.synchronize { $stdout.puts lines }
|
148
134
|
end
|
149
135
|
end
|
150
136
|
end
|
data/lib/xlogin/delegator.rb
CHANGED
@@ -1,61 +1,53 @@
|
|
1
1
|
require 'uri'
|
2
2
|
|
3
3
|
module Xlogin
|
4
|
-
class
|
5
|
-
|
6
|
-
module
|
7
|
-
def
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
session = super(uri, opts)
|
27
|
-
end
|
4
|
+
class Template
|
5
|
+
|
6
|
+
module RelayTemplate
|
7
|
+
def build(uri, **params)
|
8
|
+
login_host = params.delete(:relay)
|
9
|
+
return super(uri, **params) unless login_host
|
10
|
+
|
11
|
+
login_info = Xlogin.factory.get(login_host)
|
12
|
+
login_os = Xlogin.factory.get_template(login_info[:type])
|
13
|
+
login_uri = URI(login_info[:uri])
|
14
|
+
|
15
|
+
login = @methods.fetch(:login)
|
16
|
+
delegate = @methods.fetch(:delegate)
|
17
|
+
|
18
|
+
relay_uri = URI(uri.to_s)
|
19
|
+
userinfo_cache = relay_uri.userinfo.dup
|
20
|
+
relay_uri.userinfo = ''
|
21
|
+
|
22
|
+
session = login_os.build(relay_uri, **params)
|
23
|
+
session.instance_exec(*userinfo_cache.split(':'), &login)
|
24
|
+
session.instance_exec(login_uri, **params, &delegate)
|
25
|
+
session
|
28
26
|
end
|
29
27
|
end
|
30
28
|
|
31
|
-
prepend
|
29
|
+
prepend RelayTemplate
|
30
|
+
|
32
31
|
|
33
32
|
### Usage:
|
34
33
|
## Write xloginrc file
|
35
34
|
#
|
36
|
-
# vyos
|
37
|
-
#
|
35
|
+
# vyos 'vyos01', 'telnet://user:pass@host:port'
|
36
|
+
# relay_srv 'vyos01::relay', 'telnet://relay_user:relay_pass@relay_host:relay_port', relay: 'vyos01'
|
38
37
|
#
|
39
38
|
## Write firmware definition
|
40
39
|
#
|
41
40
|
# require 'timeout'
|
42
|
-
#
|
43
|
-
#
|
44
|
-
# username
|
45
|
-
#
|
46
|
-
#
|
47
|
-
# end
|
41
|
+
# login do |*args|
|
42
|
+
# username, password = *args
|
43
|
+
# waitfor(/login:\s*\z/) && puts(username)
|
44
|
+
# waitfor(/Password:\s*\z/) && puts(password)
|
45
|
+
# end
|
48
46
|
#
|
49
|
-
#
|
50
|
-
#
|
51
|
-
#
|
52
|
-
#
|
53
|
-
# login(*uri.userinfo.split(':'))
|
54
|
-
# end
|
55
|
-
# rescue Timeout::Error
|
56
|
-
# end
|
57
|
-
# end
|
58
|
-
#end
|
47
|
+
# delegate do |uri, opts|
|
48
|
+
# cmd("telnet #{uri.host}")
|
49
|
+
# login(*uri.userinfo.split(':'))
|
50
|
+
# end
|
59
51
|
|
60
52
|
end
|
61
53
|
end
|
@@ -1,10 +1,8 @@
|
|
1
|
-
require 'uri'
|
2
1
|
require 'singleton'
|
3
|
-
require '
|
4
|
-
require 'xlogin/firmware'
|
2
|
+
require 'xlogin/template'
|
5
3
|
|
6
4
|
module Xlogin
|
7
|
-
class
|
5
|
+
class Factory
|
8
6
|
|
9
7
|
include Singleton
|
10
8
|
|
@@ -14,40 +12,18 @@ module Xlogin
|
|
14
12
|
@group = nil
|
15
13
|
end
|
16
14
|
|
17
|
-
def load_template_files(*files)
|
18
|
-
files.each do |file|
|
19
|
-
file = File.expand_path(file)
|
20
|
-
next unless File.exist?(file) && file =~ /.rb$/
|
21
|
-
|
22
|
-
name = File.basename(file, '.rb').scan(/\w+/).join.downcase
|
23
|
-
Xlogin.configure(name) { |firmware| firmware.instance_eval(IO.read(file)) }
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
def get_template(name)
|
28
|
-
@templates[name.to_s.downcase]
|
29
|
-
end
|
30
|
-
|
31
|
-
def set_template(name, template)
|
32
|
-
@templates[name.to_s.downcase] = template
|
33
|
-
end
|
34
|
-
|
35
|
-
def list_templates
|
36
|
-
@templates.keys
|
37
|
-
end
|
38
|
-
|
39
15
|
def source(file)
|
40
16
|
file = File.expand_path(file)
|
41
|
-
|
42
|
-
instance_eval(IO.read(file))
|
17
|
+
instance_eval(IO.read(file)) if File.exist?(file)
|
43
18
|
end
|
44
19
|
|
45
20
|
def get(name)
|
46
21
|
@database[name]
|
47
22
|
end
|
48
23
|
|
49
|
-
def set(**
|
50
|
-
|
24
|
+
def set(**params)
|
25
|
+
name = params[:name]
|
26
|
+
@database[name] = params if name
|
51
27
|
end
|
52
28
|
|
53
29
|
def list(name = nil)
|
@@ -56,34 +32,50 @@ module Xlogin
|
|
56
32
|
@database.values_at(*keys)
|
57
33
|
end
|
58
34
|
|
35
|
+
def get_template(name)
|
36
|
+
@templates[name.to_s.downcase] ||= Xlogin::Template.new
|
37
|
+
end
|
38
|
+
|
39
|
+
def set_template(*files)
|
40
|
+
files.each do |file|
|
41
|
+
file = File.expand_path(file)
|
42
|
+
next unless File.exist?(file) && file =~ /.rb$/
|
43
|
+
|
44
|
+
name = File.basename(file, '.rb').scan(/\w+/).join('_')
|
45
|
+
template = get_template(name)
|
46
|
+
template.instance_eval(IO.read(file))
|
47
|
+
@templates[name.to_s.downcase] = template
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def list_templates
|
52
|
+
@templates.keys
|
53
|
+
end
|
54
|
+
|
59
55
|
def group(group_name)
|
60
56
|
current_group = @group
|
61
|
-
@group = [current_group, group_name].compact.join(':')
|
57
|
+
@group = [current_group, group_name.to_s].compact.join(':')
|
62
58
|
yield
|
63
59
|
@group = current_group
|
64
60
|
end
|
65
61
|
|
66
|
-
def build(
|
67
|
-
type = args.delete(:type)
|
62
|
+
def build(type:, uri:, **params)
|
68
63
|
template = get_template(type)
|
69
|
-
raise Xlogin::
|
70
|
-
|
71
|
-
uri = args.delete(:uri)
|
72
|
-
opts = args.reduce({}) { |a, (k, v)| a.merge(k.to_s.downcase.to_sym => v) }
|
73
|
-
raise Xlogin::HostNotFound.new("connection not defined: '#{arg}'") unless uri
|
64
|
+
raise Xlogin::SessionNotFound.new("Target not defined") unless uri
|
65
|
+
raise Xlogin::TemplateNotFound.new("Template not found: '#{type}'") unless template
|
74
66
|
|
75
|
-
template.
|
67
|
+
template.build(uri, **params)
|
76
68
|
end
|
77
69
|
|
78
|
-
def build_from_hostname(hostname, **
|
70
|
+
def build_from_hostname(hostname, **params)
|
79
71
|
hostinfo = get(hostname)
|
80
|
-
raise Xlogin::
|
72
|
+
raise Xlogin::SessionNotFound.new("Host not found: '#{hostname}'") unless hostinfo
|
81
73
|
|
82
|
-
build(hostinfo.merge(
|
74
|
+
build(hostinfo.merge(**params))
|
83
75
|
end
|
84
76
|
|
85
77
|
def method_missing(method_name, *args, &block)
|
86
|
-
super unless caller_locations.first.label
|
78
|
+
super unless caller_locations.first.label == 'source' and args.size >= 2
|
87
79
|
|
88
80
|
type = method_name.to_s.downcase
|
89
81
|
name = [@group, args.shift].compact.join(':')
|
data/lib/xlogin/rake_task.rb
CHANGED
@@ -2,6 +2,7 @@ require 'rake'
|
|
2
2
|
require 'rake/tasklib'
|
3
3
|
require 'readline'
|
4
4
|
require 'thread'
|
5
|
+
require 'stringio'
|
5
6
|
|
6
7
|
module Xlogin
|
7
8
|
class RakeTask < Rake::TaskLib
|
@@ -9,167 +10,95 @@ module Xlogin
|
|
9
10
|
class << self
|
10
11
|
include Rake::DSL
|
11
12
|
|
12
|
-
def mutex
|
13
|
-
@mutex ||= Mutex.new
|
14
|
-
end
|
15
|
-
|
16
|
-
def load(file, &block)
|
17
|
-
Xlogin.factory.source(file)
|
18
|
-
hostnames = Xlogin.factory.list.map { |e| e[:name] }
|
19
|
-
bulk(hostnames, &block)
|
20
|
-
end
|
21
|
-
|
22
13
|
def bulk(names, &block)
|
23
|
-
|
24
|
-
|
25
|
-
duplicate = namecount.keys.select { |name| namecount[name] > 1 }
|
26
|
-
raise ArgumentError.new("Duplicate hosts found - #{duplicate.join(', ')}") unless duplicate.empty?
|
27
|
-
|
28
|
-
current_namespace do
|
29
|
-
description = Rake.application.last_description || "Run '#{RakeTask.current_namespace}'"
|
14
|
+
current_namespace do |path|
|
15
|
+
description = Rake.application.last_description
|
30
16
|
|
17
|
+
names = names.map(&:strip).grep(/^\s*[^#]/).uniq
|
31
18
|
names.each do |name|
|
32
|
-
desc "#{
|
19
|
+
desc description || "Run '#{path}:#{name}'"
|
33
20
|
RakeTask.new(name, &block)
|
34
21
|
end
|
35
22
|
end
|
36
23
|
end
|
37
24
|
|
38
25
|
def current_namespace
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
26
|
+
Rake.application.current_scope.path.tap do |path|
|
27
|
+
if path.empty?
|
28
|
+
path = self.name.split('::').first.downcase
|
29
|
+
namespace(path) { yield(path) } if block_given?
|
30
|
+
else
|
31
|
+
yield(path) if block_given?
|
32
|
+
end
|
46
33
|
end
|
47
|
-
|
48
|
-
path
|
49
34
|
end
|
50
35
|
end
|
51
36
|
|
52
37
|
|
53
38
|
attr_reader :name
|
54
|
-
attr_accessor :
|
55
|
-
attr_accessor :
|
39
|
+
attr_accessor :lock
|
40
|
+
attr_accessor :log
|
56
41
|
attr_accessor :silent
|
57
|
-
attr_accessor :lockfile
|
58
|
-
attr_accessor :logfile
|
59
|
-
attr_accessor :uncomment
|
60
42
|
|
61
43
|
def initialize(name)
|
62
|
-
@name
|
63
|
-
@
|
64
|
-
@
|
65
|
-
@taskrunner = nil
|
66
|
-
|
67
|
-
@fail_on_error = true
|
68
|
-
@silent = Rake.application.options.silent
|
69
|
-
@lockfile = nil
|
70
|
-
@logfile = nil
|
71
|
-
@uncomment = false
|
44
|
+
@name = name
|
45
|
+
@runner = nil
|
46
|
+
@silent ||= Rake.application.options.silent
|
72
47
|
|
73
48
|
yield(self) if block_given?
|
74
|
-
|
49
|
+
define_task
|
75
50
|
end
|
76
51
|
|
77
52
|
def start(&block)
|
78
|
-
@
|
79
|
-
end
|
80
|
-
|
81
|
-
def safe_puts(*messages, **opts)
|
82
|
-
opts[:io] ||= $stdout
|
83
|
-
return if @silent && !opts[:force]
|
84
|
-
RakeTask.mutex.synchronize do
|
85
|
-
messages.flat_map { |message| message.to_s.lines }.each do |line|
|
86
|
-
line.gsub!("\r", "")
|
87
|
-
opts[:io].puts (Rake.application.options.always_multitask)? "#{name}\t#{line}" : line
|
88
|
-
end
|
89
|
-
end
|
53
|
+
@runner = block
|
90
54
|
end
|
91
55
|
|
92
56
|
private
|
93
|
-
def
|
94
|
-
RakeTask.current_namespace do
|
95
|
-
desc Rake.application.last_description || "Run '#{
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
mkdir_p(File.dirname(lockfile), verbose: Rake.application.options.trace)
|
104
|
-
touch(lockfile, verbose: Rake.application.options.trace)
|
57
|
+
def define_task
|
58
|
+
RakeTask.current_namespace do |path|
|
59
|
+
desc Rake.application.last_description || "Run '#{path}:#{name}'"
|
60
|
+
|
61
|
+
if lock
|
62
|
+
task(name => lock)
|
63
|
+
file(lock) do
|
64
|
+
invoke
|
65
|
+
mkdir_p(File.dirname(lock), verbose: Rake.application.options.trace)
|
66
|
+
touch(lock, verbose: Rake.application.options.trace)
|
105
67
|
end
|
106
68
|
else
|
107
|
-
task(name)
|
108
|
-
run_task
|
109
|
-
end
|
69
|
+
task(name) { invoke }
|
110
70
|
end
|
111
71
|
end
|
112
72
|
end
|
113
73
|
|
114
|
-
def
|
115
|
-
|
74
|
+
def invoke
|
75
|
+
loggers = []
|
116
76
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
if logfile
|
121
|
-
mkdir_p(File.dirname(logfile), verbose: Rake.application.options.trace)
|
122
|
-
loggers << logfile
|
77
|
+
if log
|
78
|
+
mkdir_p(File.dirname(log), verbose: Rake.application.options.trace)
|
79
|
+
loggers << log
|
123
80
|
end
|
124
81
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
@taskrunner.call(@session) if @taskrunner && @session
|
82
|
+
if Rake.application.options.always_multitask
|
83
|
+
buffer = StringIO.new
|
84
|
+
loggers << buffer unless silent
|
85
|
+
|
86
|
+
begin
|
87
|
+
session = Xlogin.factory.build_from_hostname(name, log: loggers)
|
88
|
+
@runner.call(session)
|
89
|
+
$stdout.puts buffer.string.lines.map { |line| "#{name}\t" + line.gsub("\r", '') }
|
90
|
+
rescue => e
|
91
|
+
$stdout.puts buffer.string.lines.map { |line| "#{name}\t" + line.gsub("\r", '') }
|
92
|
+
$stderr.puts "#{name}\t#{e}"
|
137
93
|
end
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
message = super(*args)
|
147
|
-
safe_puts(message, io: $stdout) if Rake.application.options.always_multitask
|
148
|
-
message = yield(message) if block_given?
|
149
|
-
message
|
150
|
-
end
|
151
|
-
|
152
|
-
def readline(command)
|
153
|
-
prompt = StringIO.new
|
154
|
-
safe_puts(cmd('').lines.last, io: prompt, force: true)
|
155
|
-
|
156
|
-
my_command = RakeTask.mutex.synchronize do
|
157
|
-
Readline.pre_input_hook = lambda do
|
158
|
-
Readline.insert_text(command)
|
159
|
-
Readline.redisplay
|
160
|
-
end
|
161
|
-
|
162
|
-
Readline.readline("\e[E\e[K#{prompt.string.chomp}", true)
|
163
|
-
end
|
164
|
-
|
165
|
-
case my_command.strip
|
166
|
-
when /^\s*#/, ''
|
167
|
-
# do nothing and skip this command
|
168
|
-
when command.strip
|
169
|
-
cmd(my_command)
|
170
|
-
else
|
171
|
-
cmd(my_command)
|
172
|
-
readline(command)
|
94
|
+
else
|
95
|
+
loggers << $stdout unless silent
|
96
|
+
|
97
|
+
begin
|
98
|
+
session = Xlogin.factory.build_from_hostname(name, log: loggers)
|
99
|
+
@runner.call(session)
|
100
|
+
rescue => e
|
101
|
+
$stderr.puts e
|
173
102
|
end
|
174
103
|
end
|
175
104
|
end
|
data/lib/xlogin/session.rb
CHANGED
@@ -1,124 +1,148 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
1
|
+
require 'fileutils'
|
2
|
+
require 'net/ssh/gateway'
|
3
|
+
require 'ostruct'
|
4
|
+
require 'readline'
|
3
5
|
require 'stringio'
|
4
6
|
|
5
7
|
module Xlogin
|
6
|
-
module
|
8
|
+
module SessionModule
|
7
9
|
|
8
|
-
attr_reader :opts
|
9
10
|
attr_accessor :name
|
11
|
+
attr_accessor :opts
|
10
12
|
|
11
|
-
def
|
12
|
-
@
|
13
|
-
@
|
14
|
-
@
|
15
|
-
@port = @opts[:port]
|
16
|
-
@userinfo = @opts[:userinfo].to_s
|
17
|
-
raise ArgumentError.new('device hostname or port not specified.') unless @host && @port
|
13
|
+
def initialize(template, uri, **params)
|
14
|
+
@template = template
|
15
|
+
@scheme = uri.scheme
|
16
|
+
@opts = OpenStruct.new(params)
|
18
17
|
|
19
|
-
@
|
20
|
-
@
|
18
|
+
@host = uri.host
|
19
|
+
@name = uri.host
|
20
|
+
@port = uri.port
|
21
|
+
@port ||= case @scheme
|
22
|
+
when 'ssh' then 22
|
23
|
+
when 'telnet' then 23
|
24
|
+
end
|
21
25
|
|
22
|
-
@
|
23
|
-
@
|
26
|
+
@username, @password = uri.userinfo.to_s.split(':')
|
27
|
+
raise ArgumentError.new('Device hostname or port not specified.') unless @host && @port
|
28
|
+
|
29
|
+
@output_logs = opts.log
|
30
|
+
@output_loggers = prebuild_loggers
|
31
|
+
|
32
|
+
ssh_tunnel(opts.via) if opts.via
|
33
|
+
max_retry = opts.retry || 1
|
34
|
+
|
35
|
+
begin
|
36
|
+
return super(
|
37
|
+
'Host' => @host,
|
38
|
+
'Port' => @port,
|
39
|
+
'Username' => @username,
|
40
|
+
'Password' => @password,
|
41
|
+
'Timeout' => @template.timeout,
|
42
|
+
'Prompt' => Regexp.union(*@template.prompt.map(&:first)),
|
43
|
+
)
|
44
|
+
rescue => e
|
45
|
+
$stdout.puts e
|
46
|
+
retry if (max_retry -= 1) > 0
|
47
|
+
raise e
|
48
|
+
end
|
24
49
|
end
|
25
50
|
|
26
|
-
def
|
27
|
-
|
28
|
-
# write bind(:enable) proc in the firmware template to override.
|
51
|
+
def prompt
|
52
|
+
cmd('').lines.last.chomp
|
29
53
|
end
|
30
54
|
|
31
|
-
def
|
32
|
-
|
55
|
+
def puts(line)
|
56
|
+
line = instance_exec(line, &@template.interrupt) if @template.interrupt
|
57
|
+
super(line)
|
33
58
|
end
|
34
59
|
|
35
|
-
def
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
end
|
41
|
-
else
|
42
|
-
line = super(*expect) do |recvdata|
|
43
|
-
@logger.call(recvdata)
|
44
|
-
block.call(recvdata) if block
|
45
|
-
end
|
46
|
-
|
47
|
-
_, process = @prompts.find { |r, p| r =~ line && p }
|
48
|
-
if process
|
49
|
-
instance_eval(&process)
|
50
|
-
line += waitfor(*expect, &block)
|
51
|
-
end
|
52
|
-
line
|
53
|
-
end
|
60
|
+
def method_missing(name, *args, &block)
|
61
|
+
process = @template.methods[name]
|
62
|
+
super unless process
|
63
|
+
|
64
|
+
instance_exec(*args, &process)
|
54
65
|
end
|
55
66
|
|
56
|
-
def
|
57
|
-
|
67
|
+
def respond_to_missing?(name, _)
|
68
|
+
@template.methods[name]
|
58
69
|
end
|
59
70
|
|
60
|
-
def
|
61
|
-
|
71
|
+
def waitfor(*expect, &block)
|
72
|
+
return waitfor(Regexp.union(*@template.prompt.map(&:first)), &block) if expect.empty?
|
62
73
|
|
63
|
-
|
64
|
-
|
65
|
-
yield self
|
66
|
-
ensure
|
67
|
-
@mutex.unlock if @mutex.locked?
|
74
|
+
line = super(*expect) do |recvdata|
|
75
|
+
output(recvdata, &block)
|
68
76
|
end
|
69
|
-
end
|
70
77
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
renew if respond_to?(:renew)
|
76
|
-
raise e if (max_retry -= 1) < 0
|
77
|
-
retry
|
78
|
+
_, process = @template.prompt.find { |r, p| r =~ line && p }
|
79
|
+
if process
|
80
|
+
instance_eval(&process)
|
81
|
+
line += waitfor(*expect, &block)
|
78
82
|
end
|
83
|
+
|
84
|
+
line
|
79
85
|
end
|
80
86
|
|
81
|
-
def
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
update_logger
|
86
|
-
end
|
87
|
+
def dup
|
88
|
+
uri = URI::Generic.build(@scheme, [@username, @password].compact.join(':'), @host, @port)
|
89
|
+
self.class.new(@template, uri, **opts.to_h)
|
90
|
+
end
|
87
91
|
|
92
|
+
def enable_log(out = $stdout)
|
93
|
+
@output_loggers = prebuild_loggers(@output_logs + [out])
|
88
94
|
if block_given?
|
89
95
|
yield
|
90
|
-
|
96
|
+
@output_loggers = prebuild_loggers
|
91
97
|
end
|
92
98
|
end
|
93
99
|
|
94
100
|
def disable_log(out = $stdout)
|
95
|
-
|
96
|
-
if enabled
|
97
|
-
@loglist.delete(out)
|
98
|
-
update_logger
|
99
|
-
end
|
100
|
-
|
101
|
+
@output_loggers = prebuild_loggers(@output_logs - [out])
|
101
102
|
if block_given?
|
102
103
|
yield
|
103
|
-
|
104
|
+
@output_loggers = prebuild_loggers
|
104
105
|
end
|
105
106
|
end
|
106
107
|
|
107
108
|
private
|
108
|
-
def
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
109
|
+
def ssh_tunnel(gateway)
|
110
|
+
gateway_uri = URI(gateway)
|
111
|
+
username, password = *gateway_uri.userinfo.split(':')
|
112
|
+
|
113
|
+
case gateway_uri.scheme
|
114
|
+
when 'ssh'
|
115
|
+
gateway = Net::SSH::Gateway.new(
|
116
|
+
gateway_uri.host,
|
117
|
+
username,
|
118
|
+
password: password,
|
119
|
+
port: gateway_uri.port || 22
|
120
|
+
)
|
121
|
+
|
122
|
+
@port = gateway.open(@host, @port)
|
123
|
+
@host = '127.0.0.1'
|
119
124
|
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def output(text, &block)
|
128
|
+
[*@output_loggers, block].compact.each { |logger| logger.call(text) }
|
129
|
+
end
|
120
130
|
|
121
|
-
|
131
|
+
def prebuild_loggers(output_logs = @output_logs)
|
132
|
+
[output_logs].flatten.compact.uniq.map do |output_log|
|
133
|
+
logger = case output_log
|
134
|
+
when String
|
135
|
+
FileUtils.mkdir_p(File.dirname(output_log))
|
136
|
+
File.open(output_log, 'a+').tap do |file|
|
137
|
+
file.binmode
|
138
|
+
file.sync = true
|
139
|
+
end
|
140
|
+
when IO, StringIO
|
141
|
+
output_log
|
142
|
+
end
|
143
|
+
lambda { |c| logger.syswrite c if logger }
|
144
|
+
end
|
122
145
|
end
|
146
|
+
|
123
147
|
end
|
124
148
|
end
|
data/lib/xlogin/ssh.rb
CHANGED
@@ -4,21 +4,7 @@ require 'xlogin/session'
|
|
4
4
|
module Xlogin
|
5
5
|
class Ssh < Net::SSH::Telnet
|
6
6
|
|
7
|
-
include
|
8
|
-
|
9
|
-
def initialize(**opts)
|
10
|
-
configure_session(opts.merge(port: opts[:port] || 22))
|
11
|
-
username, password = @userinfo.split(':')
|
12
|
-
|
13
|
-
super(
|
14
|
-
'Host' => @host,
|
15
|
-
'Port' => @port,
|
16
|
-
'Username' => username,
|
17
|
-
'Password' => password,
|
18
|
-
'Timeout' => @timeout,
|
19
|
-
'Prompt' => Regexp.union(*@prompts.map(&:first))
|
20
|
-
)
|
21
|
-
end
|
7
|
+
include SessionModule
|
22
8
|
|
23
9
|
def interact!
|
24
10
|
raise 'Not implemented'
|
data/lib/xlogin/telnet.rb
CHANGED
@@ -5,19 +5,20 @@ require 'xlogin/session'
|
|
5
5
|
module Xlogin
|
6
6
|
class Telnet < Net::Telnet
|
7
7
|
|
8
|
-
|
8
|
+
prepend SessionModule
|
9
9
|
|
10
|
-
|
11
|
-
|
10
|
+
alias_method :telnet_login, :login
|
11
|
+
undef_method :login
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
'Prompt' => Regexp.union(*@prompts.map(&:first))
|
18
|
-
)
|
13
|
+
def initialize(params)
|
14
|
+
username = params.delete('Username')
|
15
|
+
password = params.delete('Password')
|
16
|
+
super(params)
|
19
17
|
|
20
|
-
|
18
|
+
if username || password
|
19
|
+
return login(username, password) if respond_to?(:login)
|
20
|
+
telnet_login(username, password)
|
21
|
+
end
|
21
22
|
end
|
22
23
|
|
23
24
|
def interact!
|
@@ -39,13 +40,13 @@ module Xlogin
|
|
39
40
|
rescue IO::WaitReadable
|
40
41
|
end
|
41
42
|
|
42
|
-
raise EOFError if bs == "\u001D" # <Ctrl-]>
|
43
|
+
raise EOFError if bs == "\u001D" # <Ctrl-]> to force quit
|
43
44
|
@sock.syswrite(bs)
|
44
45
|
when @sock
|
45
46
|
begin
|
46
47
|
bs = fh.readpartial(1024)
|
47
48
|
$stdout.syswrite(bs)
|
48
|
-
|
49
|
+
output(bs)
|
49
50
|
rescue Errno::EAGAIN
|
50
51
|
retry
|
51
52
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'xlogin/ssh'
|
3
|
+
require 'xlogin/telnet'
|
4
|
+
|
5
|
+
module Xlogin
|
6
|
+
class Template
|
7
|
+
|
8
|
+
DEFAULT_TIMEOUT = 10
|
9
|
+
DEFAULT_PROMPT = /[$%#>] ?\z/n
|
10
|
+
BINDABLE_METHODS = %i( login logout enable delegate )
|
11
|
+
|
12
|
+
attr_reader :methods
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@timeout = DEFAULT_TIMEOUT
|
16
|
+
@prompts = Array.new
|
17
|
+
@methods = Hash.new
|
18
|
+
@interrupt = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def timeout(val = nil)
|
22
|
+
@timeout = val.to_i if val
|
23
|
+
@timeout
|
24
|
+
end
|
25
|
+
|
26
|
+
def prompt(expect = nil, &block)
|
27
|
+
return [[DEFAULT_PROMPT, nil]] if expect.nil? && @prompts.empty?
|
28
|
+
@prompts << [Regexp.new(expect.to_s), block] if expect
|
29
|
+
@prompts
|
30
|
+
end
|
31
|
+
|
32
|
+
def bind(name = nil, &block)
|
33
|
+
@methods[name] = block
|
34
|
+
end
|
35
|
+
|
36
|
+
def interrupt(&block)
|
37
|
+
return @interrupt unless block
|
38
|
+
@interrupt = block
|
39
|
+
end
|
40
|
+
|
41
|
+
def build(uri, **params)
|
42
|
+
uri = URI(uri.to_s)
|
43
|
+
klass = Class.new(Xlogin.const_get(uri.scheme.capitalize))
|
44
|
+
klass.new(self, uri, **params)
|
45
|
+
end
|
46
|
+
|
47
|
+
def method_missing(name, *, &block)
|
48
|
+
super unless BINDABLE_METHODS.include? name
|
49
|
+
bind(name) { |*args| instance_exec(*args, &block) }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/xlogin/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: xlogin
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- haccht
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-10-
|
11
|
+
date: 2017-10-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: net-telnet
|
@@ -130,13 +130,12 @@ files:
|
|
130
130
|
- lib/xlogin.rb
|
131
131
|
- lib/xlogin/cli.rb
|
132
132
|
- lib/xlogin/delegator.rb
|
133
|
-
- lib/xlogin/
|
134
|
-
- lib/xlogin/firmware_factory.rb
|
135
|
-
- lib/xlogin/gateway.rb
|
133
|
+
- lib/xlogin/factory.rb
|
136
134
|
- lib/xlogin/rake_task.rb
|
137
135
|
- lib/xlogin/session.rb
|
138
136
|
- lib/xlogin/ssh.rb
|
139
137
|
- lib/xlogin/telnet.rb
|
138
|
+
- lib/xlogin/template.rb
|
140
139
|
- lib/xlogin/templates/ios.rb
|
141
140
|
- lib/xlogin/templates/iosxr.rb
|
142
141
|
- lib/xlogin/templates/junos.rb
|
data/lib/xlogin/firmware.rb
DELETED
@@ -1,64 +0,0 @@
|
|
1
|
-
require 'uri'
|
2
|
-
require 'xlogin/ssh'
|
3
|
-
require 'xlogin/telnet'
|
4
|
-
|
5
|
-
module Xlogin
|
6
|
-
class Firmware
|
7
|
-
|
8
|
-
def initialize
|
9
|
-
@timeout = 5
|
10
|
-
@prompts = Array.new
|
11
|
-
|
12
|
-
@hook = nil
|
13
|
-
@bind = Hash.new
|
14
|
-
end
|
15
|
-
|
16
|
-
def timeout(val)
|
17
|
-
@timeout = val.to_i
|
18
|
-
end
|
19
|
-
|
20
|
-
def prompt(expect, &block)
|
21
|
-
@prompts << [Regexp.new(expect.to_s), block]
|
22
|
-
end
|
23
|
-
|
24
|
-
def hook(&block)
|
25
|
-
@hook = block
|
26
|
-
end
|
27
|
-
|
28
|
-
def bind(name, &block)
|
29
|
-
@bind[name] = block
|
30
|
-
end
|
31
|
-
|
32
|
-
def run(uri, opts = {})
|
33
|
-
uri = URI(uri.to_s)
|
34
|
-
klass = Class.new(Xlogin.const_get(uri.scheme.capitalize))
|
35
|
-
|
36
|
-
@bind.each do |name, block|
|
37
|
-
klass.class_exec(name.to_sym, block) do |name, block|
|
38
|
-
undef_method(name) if method_defined?(name)
|
39
|
-
define_method(name, &block)
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
if @hook
|
44
|
-
klass.class_exec(@hook) do |cmdhook|
|
45
|
-
alias_method :pass, :puts
|
46
|
-
define_method(:puts) do |command|
|
47
|
-
instance_exec(command, &cmdhook)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
session = klass.new(
|
53
|
-
{
|
54
|
-
host: uri.host,
|
55
|
-
port: uri.port,
|
56
|
-
userinfo: uri.userinfo,
|
57
|
-
timeout: @timeout,
|
58
|
-
prompts: @prompts,
|
59
|
-
}.merge(opts)
|
60
|
-
)
|
61
|
-
end
|
62
|
-
|
63
|
-
end
|
64
|
-
end
|
data/lib/xlogin/gateway.rb
DELETED
@@ -1,35 +0,0 @@
|
|
1
|
-
begin
|
2
|
-
require 'uri'
|
3
|
-
require 'net/ssh/gateway'
|
4
|
-
|
5
|
-
module Xlogin
|
6
|
-
module Session
|
7
|
-
|
8
|
-
alias_method :original_configure_session, :configure_session
|
9
|
-
def configure_session(**opts)
|
10
|
-
original_configure_session(**opts)
|
11
|
-
|
12
|
-
if uri = opts[:via]
|
13
|
-
gateway = URI(uri)
|
14
|
-
username, password = *gateway.userinfo.split(':')
|
15
|
-
|
16
|
-
case gateway.scheme
|
17
|
-
when 'ssh'
|
18
|
-
@gateway = Net::SSH::Gateway.new(
|
19
|
-
gateway.host,
|
20
|
-
username,
|
21
|
-
password: password,
|
22
|
-
port: gateway.port || 22
|
23
|
-
)
|
24
|
-
|
25
|
-
@port = @gateway.open(@host, @port)
|
26
|
-
@host = '127.0.0.1'
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
end
|
33
|
-
rescue LoadError
|
34
|
-
$stderr.puts "WARN: 'gateway' option is not supported in your environment."
|
35
|
-
end
|