xlogin 0.14.1 → 0.15.0
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/lib/xlogin.rb +4 -6
- data/lib/xlogin/cli.rb +31 -27
- data/lib/xlogin/factory.rb +37 -34
- data/lib/xlogin/rake_task.rb +18 -25
- data/lib/xlogin/session.rb +38 -45
- data/lib/xlogin/session_pool.rb +16 -21
- data/lib/xlogin/spec.rb +11 -18
- data/lib/xlogin/telnet.rb +2 -2
- data/lib/xlogin/template.rb +14 -24
- data/lib/xlogin/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cda6f2be936220ffb49b9d586d0e8ebcd248194bb0d16b7bc595ab3209da42da
|
4
|
+
data.tar.gz: 484a16a9c5f1e3eaa9d65f85de52e634ffb29a6b4e66f5461385c1f29c115ae6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0562b0ad712a00b3b2dd8d36b43c3fc15f1435492f9c025e374c52a1a92495575667476dfe1e679a57a6f396b5eba0bad2341d56c0a36454e70ff7737c656ac8
|
7
|
+
data.tar.gz: 716231e8c898ebb0d10b85a58c422f9815361e9c4303c803f2ca40c80fd83e957cd135b3f2db920ec12468e04e42d25f3c5b0213120e149451a62535b218b71b
|
data/lib/xlogin.rb
CHANGED
@@ -22,7 +22,7 @@ module Xlogin
|
|
22
22
|
|
23
23
|
class << self
|
24
24
|
def list(*patterns)
|
25
|
-
factory.
|
25
|
+
factory.list_hostinfo(*patterns)
|
26
26
|
end
|
27
27
|
|
28
28
|
def find(*patterns)
|
@@ -34,21 +34,19 @@ module Xlogin
|
|
34
34
|
when Hash then factory.build(**args.merge(**opts))
|
35
35
|
when String then factory.build_from_hostname(args, **opts)
|
36
36
|
else
|
37
|
-
raise
|
37
|
+
raise Xlogin::Error.new("Invalid argument: '#{args}'")
|
38
38
|
end
|
39
39
|
|
40
40
|
return session unless block
|
41
41
|
begin block.call(session) ensure session.close end
|
42
42
|
end
|
43
|
-
alias_method :create, :get
|
44
43
|
|
45
|
-
def
|
44
|
+
def pool(args, **opts, &block)
|
46
45
|
pool = factory.build_pool(args, **opts)
|
47
46
|
|
48
47
|
return pool unless block
|
49
48
|
begin block.call(pool) ensure pool.close end
|
50
49
|
end
|
51
|
-
alias_method :create_pool, :get_pool
|
52
50
|
|
53
51
|
def configure(&block)
|
54
52
|
instance_eval(&block)
|
@@ -59,7 +57,7 @@ module Xlogin
|
|
59
57
|
end
|
60
58
|
|
61
59
|
def generate_templates(dir)
|
62
|
-
FileUtils.mkdir_p(dir)
|
60
|
+
FileUtils.mkdir_p(dir)
|
63
61
|
builtin_templates = Dir.glob(File.join(File.dirname(__FILE__), 'xlogin', 'templates', '*.rb'))
|
64
62
|
builtin_templates.each{ |file| FileUtils.cp(file, DEFAULT_TEMPLATE_DIR) }
|
65
63
|
end
|
data/lib/xlogin/cli.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
#! /usr/bin/env ruby
|
2
|
-
|
3
1
|
require 'optparse'
|
4
2
|
require 'parallel'
|
5
3
|
require 'stringio'
|
@@ -7,30 +5,32 @@ require 'stringio'
|
|
7
5
|
module Xlogin
|
8
6
|
class CLI
|
9
7
|
|
10
|
-
|
11
|
-
|
8
|
+
DEFAULT_INVENTORY = File.join(ENV['HOME'], '.xloginrc')
|
9
|
+
DEFAULT_TEMPLATE = File.join(ENV['HOME'], '.xlogin.d')
|
12
10
|
|
13
11
|
def self.run(args = ARGV)
|
14
12
|
Xlogin::CLI.new.run(args)
|
15
13
|
end
|
16
14
|
|
17
15
|
def list(config)
|
18
|
-
puts Xlogin.list(*config[:
|
16
|
+
puts Xlogin.list(*config[:patterns]).map{ |e| e[:name] }.sort.uniq
|
19
17
|
end
|
20
18
|
|
21
19
|
def tty(config)
|
22
|
-
info = Xlogin.
|
20
|
+
info = Xlogin.find(*config[:patterns])
|
23
21
|
puts "Trying #{info[:name]}...", "Escape character is '^]'."
|
24
|
-
|
22
|
+
|
23
|
+
session, _ = exec(config.merge(patterns: info[:name], jobs: 1))
|
25
24
|
session.interact!
|
26
25
|
end
|
27
26
|
|
28
27
|
def exec(config)
|
29
|
-
Signal.trap(:INT){ exit
|
28
|
+
Signal.trap(:INT){ exit 1 }
|
30
29
|
|
31
30
|
jobs = config[:jobs] || 1
|
32
|
-
hosts = Xlogin.list(*config[:
|
31
|
+
hosts = Xlogin.list(*config[:patterns])
|
33
32
|
width = hosts.map{ |e| e[:name].length }.max
|
33
|
+
raise "No host found: `#{config[:patterns].join(', ')}`" if hosts.empty?
|
34
34
|
|
35
35
|
Parallel.map(hosts, in_threads: jobs) do |info|
|
36
36
|
buffer = StringIO.new
|
@@ -40,17 +40,17 @@ module Xlogin
|
|
40
40
|
begin
|
41
41
|
loggers = []
|
42
42
|
loggers << ((jobs > 1)? buffer : $stdout)
|
43
|
-
loggers << File.expand_path(File.join(config[:
|
43
|
+
loggers << File.expand_path(File.join(config[:logdir], "#{info[:name]}.log"), ENV['PWD']) if config[:logdir]
|
44
44
|
|
45
45
|
session = Xlogin.get(info.merge(log: loggers))
|
46
46
|
session.enable(session.config.enable) if session.config.enable && Xlogin.settings.enable?
|
47
47
|
|
48
|
-
command_lines =
|
48
|
+
command_lines = config[:command].flat_map { |e| e.to_s.split(';').map(&:strip) }
|
49
49
|
command_lines.each{ |line| session.cmd(line) }
|
50
50
|
|
51
|
-
buffer.string.lines.each{ |line| print prefix + line.gsub(
|
51
|
+
buffer.string.lines.each{ |line| print prefix + line.gsub(/^.*\r/, '') } if jobs > 1
|
52
52
|
rescue => e
|
53
|
-
buffer.string.lines.each{ |line| print prefix + line.gsub(
|
53
|
+
buffer.string.lines.each{ |line| print prefix + line.gsub(/^.*\r/, '') } if jobs > 1
|
54
54
|
raise e
|
55
55
|
end
|
56
56
|
|
@@ -60,34 +60,38 @@ module Xlogin
|
|
60
60
|
|
61
61
|
def run(args)
|
62
62
|
config = Hash.new
|
63
|
-
config[:env] =
|
63
|
+
config[:env] = []
|
64
|
+
config[:inventory] = []
|
65
|
+
config[:template] = []
|
66
|
+
config[:command] = []
|
64
67
|
config[:runner] = self.method(:tty)
|
65
|
-
config[:inventory] = DEFAULT_INVENTORY_FILE
|
66
|
-
config[:template] = DEFAULT_TEMPLATE_DIR
|
67
68
|
|
68
69
|
parser = OptionParser.new
|
69
70
|
parser.banner = "#{File.basename($0)} HOST_PATTERN [Options]"
|
70
71
|
parser.version = Xlogin::VERSION
|
71
72
|
|
72
|
-
parser.on('-i PATH', '--inventory', String, 'The PATH to the inventory file.')
|
73
|
-
parser.on('-t PATH', '--template', String, 'The PATH to the template file or directory.')
|
74
|
-
parser.on('-L PATH', '--log-dir', String, 'The PATH to the log directory.'){ |v| v
|
73
|
+
parser.on('-i PATH', '--inventory', String, 'The PATH to the inventory file.') { |v| config[:inventory] << v }
|
74
|
+
parser.on('-t PATH', '--template', String, 'The PATH to the template file or directory.'){ |v| config[:template] << v }
|
75
|
+
parser.on('-L PATH', '--log-dir', String, 'The PATH to the log directory.') { |v| config[:logdir] = v }
|
75
76
|
|
76
77
|
parser.on('-l', '--list', TrueClass, 'List the inventory.') { |v| config[:runner] = self.method(:list) }
|
77
|
-
parser.on('-e COMMAND', '--exec', String, 'Execute commands and quit.'){ |v| config[:runner] = self.method(:exec); v }
|
78
|
+
parser.on('-e COMMAND', '--exec', String, 'Execute commands and quit.'){ |v| config[:runner] = self.method(:exec); config[:command] << v }
|
78
79
|
|
79
|
-
parser.on('-E KEY=VAL', '--env', /(\w+=\w+)+/, 'Environment variables.')
|
80
|
-
parser.on('-j NUM', '--jobs', Integer, 'The NUM of jobs to execute in parallel.')
|
80
|
+
parser.on('-E KEY=VAL', '--env', /(\w+=\w+)+/, 'Environment variables.') { |v| config[:env] << v }
|
81
|
+
parser.on('-j NUM', '--jobs', Integer, 'The NUM of jobs to execute in parallel.'){ |v| config[:jobs] = v }
|
81
82
|
|
82
|
-
config[:
|
83
|
+
config[:patterns] = parser.parse!(args)
|
84
|
+
config[:inventory] << DEFAULT_INVENTORY if config[:inventory].empty?
|
85
|
+
config[:template] << DEFAULT_TEMPLATE if config[:template].empty?
|
83
86
|
|
84
87
|
Xlogin.configure do
|
85
|
-
set
|
86
|
-
|
87
|
-
|
88
|
+
set Hash[config[:env].map{ |v| v.split('=') }]
|
89
|
+
|
90
|
+
source *config[:inventory].map{ |e| File.expand_path(e, ENV['PWD']) }
|
91
|
+
template *config[:template].map { |e| File.expand_path(e, ENV['PWD']) }
|
88
92
|
end
|
89
|
-
raise "No host found: `#{args.join(', ')}`" if Xlogin.list(*config[:pattern]).empty?
|
90
93
|
|
94
|
+
raise Xlogin::Error.new("Invalid host pattern: '#{config[:patterns].join(' ')}'") if Xlogin.list(*config[:patterns]).empty?
|
91
95
|
config[:runner].call(config)
|
92
96
|
rescue => e
|
93
97
|
$stderr.puts e, '', parser
|
data/lib/xlogin/factory.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'addressable/uri'
|
2
|
+
require 'net/ssh/gateway'
|
2
3
|
require 'singleton'
|
3
4
|
require 'thread'
|
4
5
|
require 'xlogin/session_pool'
|
@@ -12,30 +13,30 @@ module Xlogin
|
|
12
13
|
def initialize
|
13
14
|
@inventory = Hash.new
|
14
15
|
@templates = Hash.new
|
15
|
-
@
|
16
|
+
@tunnels = Hash.new
|
16
17
|
@mutex = Mutex.new
|
17
18
|
end
|
18
19
|
|
19
|
-
def
|
20
|
-
@inventory[name] = (
|
20
|
+
def set_hostinfo(name, **opts)
|
21
|
+
@inventory[name] = (get_hostinfo(name) || {name: name}).merge(opts)
|
21
22
|
end
|
22
23
|
|
23
|
-
def
|
24
|
+
def get_hostinfo(name)
|
24
25
|
@inventory[name]
|
25
26
|
end
|
26
27
|
|
27
|
-
def
|
28
|
+
def list_hostinfo(*patterns)
|
28
29
|
return @inventory.values if patterns.empty?
|
29
30
|
|
30
|
-
values1 = patterns.map do |pattern|
|
31
|
+
values1 = patterns.compact.map do |pattern|
|
31
32
|
values2 = pattern.split(',').map do |entry|
|
32
33
|
key, val = entry.to_s.split(':')
|
33
34
|
key, val = 'name', key if val.nil?
|
34
35
|
@inventory.values.select{ |e| File.fnmatch(val, e[key.to_sym]) }
|
35
36
|
end
|
36
|
-
values2.reduce(&:&)
|
37
|
+
values2.reduce(&:&) || []
|
37
38
|
end
|
38
|
-
values1.reduce(&:|)
|
39
|
+
values1.reduce(&:|) || []
|
39
40
|
end
|
40
41
|
|
41
42
|
def set_template(name, text = nil, &block)
|
@@ -53,32 +54,40 @@ module Xlogin
|
|
53
54
|
@templates.keys
|
54
55
|
end
|
55
56
|
|
56
|
-
def open_tunnel(
|
57
|
+
def open_tunnel(name, host, port)
|
57
58
|
@mutex.synchronize do
|
58
|
-
unless @
|
59
|
-
|
60
|
-
case
|
59
|
+
unless @tunnels[name]
|
60
|
+
uri = Addressable::URI.parse(name)
|
61
|
+
case uri.scheme
|
61
62
|
when 'ssh'
|
62
|
-
username, password = *
|
63
|
-
|
64
|
-
|
63
|
+
username, password = *uri.userinfo.split(':')
|
64
|
+
gateway = Net::SSH::Gateway.new(
|
65
|
+
uri.host,
|
65
66
|
username,
|
66
67
|
password: password,
|
67
|
-
port:
|
68
|
+
port: uri.port || 22
|
68
69
|
)
|
70
|
+
|
71
|
+
@tunnels[name] = Struct.new('Tunnel', :gateway, :ports).new(gateway, [])
|
69
72
|
end
|
70
73
|
end
|
71
74
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
+
if tunnel = @tunnels[name]
|
76
|
+
port = tunnel.gateway.open(host, port)
|
77
|
+
host = '127.0.0.1'
|
78
|
+
tunnel.ports << port
|
79
|
+
end
|
80
|
+
return host, port
|
75
81
|
end
|
76
82
|
end
|
77
83
|
|
78
|
-
def close_tunnel(
|
84
|
+
def close_tunnel(name, port)
|
79
85
|
@mutex.synchronize do
|
80
|
-
|
81
|
-
|
86
|
+
if tunnel = @tunnels[name]
|
87
|
+
tunnel.ports.delete(port)
|
88
|
+
tunnel.gateway.close(port)
|
89
|
+
tunnel.gateway.shutdown! if tunnel.ports.empty?
|
90
|
+
end
|
82
91
|
end
|
83
92
|
end
|
84
93
|
|
@@ -94,31 +103,25 @@ module Xlogin
|
|
94
103
|
end
|
95
104
|
|
96
105
|
def build_from_hostname(args, **opts)
|
97
|
-
hostinfo =
|
106
|
+
hostinfo = get_hostinfo(args)
|
98
107
|
raise Xlogin::Error.new("Host not found: '#{args}'") unless hostinfo
|
99
108
|
|
100
|
-
build(hostinfo.merge(
|
109
|
+
build(**hostinfo.merge(**opts))
|
101
110
|
end
|
102
111
|
|
103
112
|
def method_missing(method_name, *args, **opts, &block)
|
104
|
-
super unless args.size == 2 &&
|
113
|
+
super unless args.size == 2 && Addressable::URI::URIREGEX =~ args[1]
|
105
114
|
|
106
|
-
type = method_name.to_s.downcase
|
107
115
|
name = args[0]
|
108
116
|
uri = args[1]
|
109
|
-
|
117
|
+
type = method_name.to_s.downcase
|
118
|
+
set_hostinfo(name.to_s, type: type, uri: uri, **opts)
|
110
119
|
end
|
111
120
|
|
112
121
|
private
|
113
122
|
def uri(**opts)
|
114
123
|
return Addressable::URI.parse(opts[:uri].strip) if opts.key?(:uri)
|
115
|
-
|
116
|
-
scheme = opts[:scheme].strip
|
117
|
-
address = opts.values_at(:host, :port).compact.map(&:strip).join(':')
|
118
|
-
userinfo = opts[:userinfo].strip
|
119
|
-
userinfo ||= opts.values_at(:username, :password).compact.map(&:strip).join(':')
|
120
|
-
|
121
|
-
Addressable::URI.parse("#{scheme}://" + [userinfo, address].compact.join('@'))
|
124
|
+
Addressable::URI.new(**opts)
|
122
125
|
rescue
|
123
126
|
raise Xlogin::Error.new("Invalid target - '#{opts}'")
|
124
127
|
end
|
data/lib/xlogin/rake_task.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
require 'time'
|
2
2
|
require 'rake'
|
3
3
|
require 'rake/tasklib'
|
4
|
-
require 'ostruct'
|
5
4
|
require 'stringio'
|
6
5
|
require 'colorize'
|
7
6
|
|
@@ -17,7 +16,7 @@ module Xlogin
|
|
17
16
|
|
18
17
|
task 'all' => hostnames unless opts[:all] == false
|
19
18
|
hostnames.each do |hostname|
|
20
|
-
desc "#{description}
|
19
|
+
desc "#{description} with #{hostname}"
|
21
20
|
RakeTask.new(hostname, &block)
|
22
21
|
end
|
23
22
|
end
|
@@ -32,8 +31,8 @@ module Xlogin
|
|
32
31
|
end
|
33
32
|
|
34
33
|
attr_reader :name
|
35
|
-
attr_accessor :lock
|
36
34
|
attr_accessor :log
|
35
|
+
attr_accessor :lock
|
37
36
|
attr_accessor :timeout
|
38
37
|
attr_accessor :silent
|
39
38
|
attr_accessor :fail_on_error
|
@@ -49,8 +48,8 @@ module Xlogin
|
|
49
48
|
define
|
50
49
|
end
|
51
50
|
|
52
|
-
def name_with_scope
|
53
|
-
[*Rake.application.current_scope.to_a.reverse, name].join(
|
51
|
+
def name_with_scope(separator = ':')
|
52
|
+
[*Rake.application.current_scope.to_a.reverse, name].join(separator)
|
54
53
|
end
|
55
54
|
|
56
55
|
def run(&block)
|
@@ -78,47 +77,41 @@ module Xlogin
|
|
78
77
|
end
|
79
78
|
|
80
79
|
def run_task
|
81
|
-
buffer
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
80
|
+
buffer = StringIO.new
|
81
|
+
loggers = []
|
82
|
+
loggers << log if log
|
83
|
+
loggers << $stdout if !silent && !Rake.application.options.always_multitask
|
84
|
+
loggers << buffer
|
86
85
|
|
87
|
-
session = Xlogin.get(name, log:
|
86
|
+
session = Xlogin.get(name, log: loggers, timeout: timeout)
|
88
87
|
|
89
88
|
@runner.call(session)
|
90
|
-
$stdout.print
|
89
|
+
$stdout.print log_text(buffer.string) if !silent && Rake.application.options.always_multitask
|
91
90
|
|
92
91
|
return true
|
93
92
|
rescue => e
|
94
93
|
RakeTask.shutdown! if fail_on_error
|
95
94
|
|
96
|
-
|
97
|
-
$stderr.print "\n"
|
98
|
-
$stderr.print "\n"
|
95
|
+
session.comment(e.to_s, prefix: "[ERROR]", chomp: true, color: :white, background: :red) if session
|
96
|
+
$stderr.print log_text(buffer.string + "\n").colorize(color: :light_red) if Rake.application.options.always_multitask
|
99
97
|
|
100
98
|
return false
|
101
99
|
ensure
|
102
100
|
session.close rescue nil
|
103
101
|
end
|
104
102
|
|
105
|
-
def
|
106
|
-
text.lines.map
|
107
|
-
"#{Time.now.iso8601} - #{name}\t|#{line.gsub(/^\s*[\r]+/, '')}"
|
108
|
-
end.join
|
103
|
+
def log_text(text)
|
104
|
+
text.lines.map{ |line| "#{Time.now.iso8601} - #{name}\t|#{line.gsub(/^.*\r/, '')}" }.join
|
109
105
|
end
|
110
106
|
|
111
107
|
end
|
112
108
|
|
113
109
|
module SessionModule
|
114
|
-
def log_message(text, prefix: "[INFO]", chomp: false, **color)
|
115
|
-
default_color = { color: :green }
|
116
110
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
log("#{prefix} #{text}".colorize(**default_color.merge(color)))
|
111
|
+
def comment(line, prefix: "[INFO]", chomp: false, **color)
|
112
|
+
write_log("#{prefix} #{line}".colorize({color: :light_white}.merge(**color)))
|
121
113
|
cmd('') unless chomp
|
122
114
|
end
|
115
|
+
|
123
116
|
end
|
124
117
|
end
|
data/lib/xlogin/session.rb
CHANGED
@@ -1,8 +1,4 @@
|
|
1
|
-
require 'addressable/uri'
|
2
|
-
require 'delegate'
|
3
1
|
require 'fileutils'
|
4
|
-
require 'net/ssh/gateway'
|
5
|
-
require 'ostruct'
|
6
2
|
require 'stringio'
|
7
3
|
require 'thread'
|
8
4
|
|
@@ -25,13 +21,11 @@ module Xlogin
|
|
25
21
|
@tunnel = opts[:tunnel] || opts[:via]
|
26
22
|
@config = ReadOnlyStruct.new(opts)
|
27
23
|
@template = template
|
28
|
-
@
|
29
|
-
|
30
|
-
max_retry = @config.retry || 1
|
24
|
+
@loggers = [@config.log].flatten.uniq.reduce({}){ |a, e| a.merge(e => build_log(e)) }
|
31
25
|
@host, @port = Xlogin.factory.open_tunnel(@tunnel, @host, @port) if @tunnel
|
32
26
|
|
33
|
-
|
34
|
-
|
27
|
+
max_retry = @config.retry || 1
|
28
|
+
username, password = uri.userinfo.to_s.split(':')
|
35
29
|
|
36
30
|
begin
|
37
31
|
args = Hash.new
|
@@ -43,8 +37,8 @@ module Xlogin
|
|
43
37
|
else
|
44
38
|
args['Host' ] = @host
|
45
39
|
args['Port' ] = @port
|
46
|
-
args['Username'] =
|
47
|
-
args['Password'] =
|
40
|
+
args['Username'] = username
|
41
|
+
args['Password'] = password
|
48
42
|
end
|
49
43
|
|
50
44
|
super(args)
|
@@ -64,43 +58,54 @@ module Xlogin
|
|
64
58
|
end
|
65
59
|
|
66
60
|
def prompt_pattern
|
67
|
-
Regexp.union(*@template.
|
61
|
+
Regexp.union(*@template.prompts.map(&:first))
|
68
62
|
end
|
69
63
|
|
70
64
|
def duplicate(type: @template.name, **args)
|
71
65
|
template = Xlogin::Factory.instance.get_template(type)
|
66
|
+
raise Xlogin::Error.new("Template not found: '#{type}'") unless template
|
67
|
+
|
72
68
|
template.build(@uri, **@config.to_h.merge(args))
|
73
69
|
end
|
74
70
|
|
75
|
-
def puts(
|
76
|
-
|
77
|
-
args.empty? ? super('', &block) : super(*args, &block)
|
71
|
+
def puts(string = '', &block)
|
72
|
+
super(string, &block)
|
78
73
|
end
|
79
74
|
|
80
|
-
def
|
81
|
-
|
82
|
-
|
75
|
+
def print(string = '', &block)
|
76
|
+
string = instance_exec(string, &@template.interrupt!) if @template.interrupt!
|
77
|
+
super(string, &block)
|
83
78
|
end
|
84
79
|
|
85
|
-
def
|
86
|
-
|
87
|
-
@mutex.synchronize do
|
88
|
-
@loggers.each do |_, logger|
|
89
|
-
next if logger.nil? || [$stdout, $stderr].include?(logger)
|
90
|
-
logger.close
|
91
|
-
end
|
80
|
+
def waitfor(*args, &block)
|
81
|
+
return waitfor(prompt_pattern) if args.empty?
|
92
82
|
|
93
|
-
|
94
|
-
|
83
|
+
line = super(*args) do |recv|
|
84
|
+
write_log(recv)
|
85
|
+
block.call(recv) if block
|
95
86
|
end
|
87
|
+
|
88
|
+
_, process = @template.prompts.find{ |r, p| r =~ line && p }
|
89
|
+
if process
|
90
|
+
instance_eval(&process)
|
91
|
+
line += waitfor(*args, &block)
|
92
|
+
end
|
93
|
+
|
94
|
+
return line
|
96
95
|
end
|
97
96
|
|
98
|
-
def
|
99
|
-
@loggers.each
|
97
|
+
def close
|
98
|
+
@loggers.each do |_, logger|
|
99
|
+
next if [$stdout, $stderr, nil].include?(logger)
|
100
|
+
logger.close
|
101
|
+
end
|
102
|
+
|
103
|
+
Xlogin.factory.close_tunnel(@tunnel, @port) if @tunnel
|
104
|
+
super
|
100
105
|
end
|
101
106
|
|
102
107
|
def enable_log(log = $stdout)
|
103
|
-
@loggers.update(log =>
|
108
|
+
@loggers.update(log => build_log(log))
|
104
109
|
if block_given?
|
105
110
|
yield
|
106
111
|
disable_log(log)
|
@@ -116,23 +121,11 @@ module Xlogin
|
|
116
121
|
end
|
117
122
|
|
118
123
|
private
|
119
|
-
def
|
120
|
-
|
121
|
-
line = __waitfor.call(*args) do |recv|
|
122
|
-
log(recv)
|
123
|
-
block.call(recv) if block
|
124
|
-
end
|
125
|
-
|
126
|
-
_, process = @template.prompt.find{ |r, p| r =~ line && p }
|
127
|
-
if process
|
128
|
-
instance_eval(&process)
|
129
|
-
line += _waitfor(*args, &block)
|
130
|
-
end
|
131
|
-
|
132
|
-
return line
|
124
|
+
def write_log(text)
|
125
|
+
@loggers.each{ |_, logger| logger.syswrite(text) if logger }
|
133
126
|
end
|
134
127
|
|
135
|
-
def
|
128
|
+
def build_log(log)
|
136
129
|
case log
|
137
130
|
when String
|
138
131
|
FileUtils.mkdir_p(File.dirname(log))
|
data/lib/xlogin/session_pool.rb
CHANGED
@@ -13,20 +13,12 @@ module Xlogin
|
|
13
13
|
@args = args
|
14
14
|
@opts = opts
|
15
15
|
|
16
|
-
@size = DEFAULT_POOL_SIZE
|
17
|
-
@idle = DEFAULT_POOL_IDLE
|
16
|
+
@size = @opts.delete(:pool_size) || DEFAULT_POOL_SIZE
|
17
|
+
@idle = @opts.delete(:pool_idle) || DEFAULT_POOL_IDLE
|
18
18
|
|
19
19
|
@mutex = Mutex.new
|
20
20
|
@queue = Queue.new
|
21
|
-
@
|
22
|
-
end
|
23
|
-
|
24
|
-
def size=(val)
|
25
|
-
@mutex.synchronize{ @size = val }
|
26
|
-
end
|
27
|
-
|
28
|
-
def idle=(val)
|
29
|
-
@mutex.synchronize{ @idle = val }
|
21
|
+
@count = 0
|
30
22
|
end
|
31
23
|
|
32
24
|
def with
|
@@ -37,7 +29,7 @@ module Xlogin
|
|
37
29
|
end
|
38
30
|
|
39
31
|
def close
|
40
|
-
|
32
|
+
until @queue.empty?
|
41
33
|
session, _, _ = @queue.deq
|
42
34
|
destroy(session)
|
43
35
|
end
|
@@ -45,37 +37,40 @@ module Xlogin
|
|
45
37
|
|
46
38
|
def deq
|
47
39
|
@mutex.synchronize do
|
48
|
-
if @queue.empty? && @
|
49
|
-
@
|
40
|
+
if @queue.empty? && @count < @size
|
41
|
+
@count += 1
|
50
42
|
return Xlogin.get(@args, **@opts)
|
51
43
|
end
|
52
44
|
end
|
53
45
|
|
54
|
-
session, last_used = @queue.deq
|
46
|
+
session, last_used, watch_dog = @queue.deq
|
47
|
+
|
48
|
+
watch_dog.kill
|
55
49
|
if Time.now - last_used > @idle
|
56
50
|
destroy(session)
|
57
51
|
return deq
|
58
52
|
end
|
59
53
|
|
60
54
|
begin
|
61
|
-
raise IOError if session
|
55
|
+
raise IOError if session&.sock&.closed?
|
56
|
+
return session
|
62
57
|
rescue IOError, EOFError, Errno::ECONNABORTED, Errno::ECONNREFUSED, Errno::ECONNRESET
|
63
58
|
destroy(session)
|
64
|
-
|
59
|
+
return deq
|
65
60
|
end
|
66
|
-
|
67
|
-
session
|
68
61
|
end
|
69
62
|
|
70
63
|
def enq(session)
|
71
64
|
last_used = Time.now
|
72
|
-
@
|
65
|
+
watch_dog = Thread.new(session){ |s| sleep(@idle * 1.5) && s.close rescue nil }
|
66
|
+
@queue.enq [session, last_used, watch_dog]
|
73
67
|
end
|
74
68
|
|
69
|
+
private
|
75
70
|
def destroy(session)
|
76
71
|
@mutex.synchronize do
|
77
72
|
session.close rescue nil
|
78
|
-
@
|
73
|
+
@count -= 1
|
79
74
|
end
|
80
75
|
end
|
81
76
|
|
data/lib/xlogin/spec.rb
CHANGED
@@ -2,7 +2,7 @@ module Xlogin
|
|
2
2
|
class ExpectationError < StandardError
|
3
3
|
|
4
4
|
def initialize(expect, actual)
|
5
|
-
super(
|
5
|
+
super(expect)
|
6
6
|
@actual = actual
|
7
7
|
end
|
8
8
|
|
@@ -14,28 +14,22 @@ module Xlogin
|
|
14
14
|
|
15
15
|
class Expectation
|
16
16
|
|
17
|
-
def initialize(
|
18
|
-
@
|
19
|
-
@args = args
|
17
|
+
def initialize(result)
|
18
|
+
@result = result
|
20
19
|
end
|
21
20
|
|
22
21
|
def to_match(regexp)
|
23
|
-
|
24
|
-
|
25
|
-
end
|
22
|
+
regexp = Regexp.new(regexp.to_s) unless regexp.kind_of?(Regexp)
|
23
|
+
return if @result =~ regexp
|
26
24
|
|
27
|
-
|
28
|
-
return unless match(regexp)
|
29
|
-
raise ExpectationError.new(@expect, @actual)
|
25
|
+
raise ExpectationError.new("Expected to match #{regexp}", @result)
|
30
26
|
end
|
31
27
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
@expect = regexp.inspect
|
28
|
+
def not_to_match(regexp)
|
29
|
+
regexp = Regexp.new(regexp.to_s) unless regexp.kind_of?(Regexp)
|
30
|
+
return if @result !~ regexp
|
36
31
|
|
37
|
-
|
38
|
-
@actual =~ regexp
|
32
|
+
raise ExpectationError.new("Expected not to match #{regexp}", @result)
|
39
33
|
end
|
40
34
|
|
41
35
|
end
|
@@ -43,9 +37,8 @@ module Xlogin
|
|
43
37
|
module SessionModule
|
44
38
|
|
45
39
|
def expect(*args)
|
46
|
-
Expectation.new(
|
40
|
+
Expectation.new(cmd(*args))
|
47
41
|
end
|
48
|
-
alias_method :exp, :expect
|
49
42
|
|
50
43
|
end
|
51
44
|
end
|
data/lib/xlogin/telnet.rb
CHANGED
@@ -38,7 +38,7 @@ module Xlogin
|
|
38
38
|
@sock.syswrite(bs)
|
39
39
|
when @sock
|
40
40
|
begin
|
41
|
-
|
41
|
+
write_log(fh.readpartial(1024))
|
42
42
|
rescue Errno::EAGAIN
|
43
43
|
retry
|
44
44
|
end
|
@@ -47,7 +47,7 @@ module Xlogin
|
|
47
47
|
end
|
48
48
|
rescue EOFError, Errno::ECONNRESET
|
49
49
|
$stdout.puts "\r\n", "Conneciton closed.", "\r\n"
|
50
|
-
|
50
|
+
close
|
51
51
|
ensure
|
52
52
|
$stdin.cooked!
|
53
53
|
end
|
data/lib/xlogin/template.rb
CHANGED
@@ -8,54 +8,44 @@ module Xlogin
|
|
8
8
|
DEFAULT_PROMPT = /[$%#>] ?\z/n
|
9
9
|
RESERVED_METHODS = %i( login logout enable disable )
|
10
10
|
|
11
|
-
attr_reader :name, :
|
11
|
+
attr_reader :name, :methods
|
12
12
|
|
13
13
|
def initialize(name)
|
14
14
|
@name = name
|
15
|
-
@scopes = Hash.new
|
16
|
-
@timeout = DEFAULT_TIMEOUT
|
17
15
|
@prompts = Array.new
|
18
16
|
@methods = Hash.new
|
17
|
+
@timeout = DEFAULT_TIMEOUT
|
19
18
|
@interrupt = nil
|
20
19
|
end
|
21
20
|
|
22
|
-
def
|
23
|
-
@
|
24
|
-
@timeout
|
21
|
+
def prompt(expect, &block)
|
22
|
+
@prompts << [Regexp.new(expect.to_s), block]
|
25
23
|
end
|
26
24
|
|
27
|
-
def
|
28
|
-
|
29
|
-
@prompts << [Regexp.new(expect.to_s), block] if expect
|
25
|
+
def prompts
|
26
|
+
@prompts << [DEFAULT_PROMPT, nil] if @prompts.empty?
|
30
27
|
@prompts
|
31
28
|
end
|
32
29
|
|
33
|
-
def
|
34
|
-
@
|
30
|
+
def bind(name, &block)
|
31
|
+
@methods[name] = block
|
35
32
|
end
|
36
33
|
|
37
|
-
def
|
38
|
-
@
|
34
|
+
def timeout(val = nil)
|
35
|
+
@timeout = val.to_i if val
|
36
|
+
@timeout
|
39
37
|
end
|
40
38
|
|
41
39
|
def interrupt!(&block)
|
42
|
-
|
43
|
-
@interrupt
|
40
|
+
@interrupt = block if block
|
41
|
+
@interrupt
|
44
42
|
end
|
45
43
|
|
46
44
|
def build(uri, **opts)
|
47
45
|
klass = Class.new(Xlogin.const_get(uri.scheme.capitalize))
|
48
46
|
klass.class_exec(self) do |template|
|
49
|
-
scopes = [*opts[:scope]].compact
|
50
|
-
scopes.each{ |scope| template.instance_eval(&template.scopes[scope]) }
|
51
|
-
|
52
47
|
template.methods.each do |name, block|
|
53
|
-
|
54
|
-
when 'enable'
|
55
|
-
define_method(name){ |args = nil| instance_exec(args || opts[:enable], &block) }
|
56
|
-
else
|
57
|
-
define_method(name, &block)
|
58
|
-
end
|
48
|
+
define_method(name, &block)
|
59
49
|
end
|
60
50
|
end
|
61
51
|
|
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.15.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- haccht
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-09-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: net-telnet
|