xlogin 0.13.10 → 0.14.4
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 +5 -5
- data/lib/xlogin/cli.rb +35 -31
- data/lib/xlogin/factory.rb +49 -22
- data/lib/xlogin/rake_task.rb +19 -26
- data/lib/xlogin/session.rb +40 -67
- data/lib/xlogin/session_pool.rb +7 -7
- data/lib/xlogin/spec.rb +11 -18
- data/lib/xlogin/telnet.rb +2 -2
- data/lib/xlogin/template.rb +15 -25
- 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: cdbb386674deee491268f35a9d5ca46c721391068d693a68d9a3e289e0168f97
|
4
|
+
data.tar.gz: b6afba886c65c3744253d58e0d8eccda3c265cf168159d95f3504408962127ce
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: edf8a747bff259718090617554b0d0868a3a3f321c287b328be882ac4dc68f9cbd2943755e55a1772525647bbfc485e849ac56495bed49a92b3a76cfb5bb4d1d
|
7
|
+
data.tar.gz: 5d699b1d3982c7cf1a7bd4cd19ef174acf27885ac5e38fc4f10f746efd99b8e1e3deea523f52b901f9d4cdb80440aae5dd532abde1d41aa09c7548e07198a838
|
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,7 +34,7 @@ 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
|
@@ -59,16 +59,16 @@ module Xlogin
|
|
59
59
|
end
|
60
60
|
|
61
61
|
def generate_templates(dir)
|
62
|
-
FileUtils.mkdir_p(dir)
|
62
|
+
FileUtils.mkdir_p(dir)
|
63
63
|
builtin_templates = Dir.glob(File.join(File.dirname(__FILE__), 'xlogin', 'templates', '*.rb'))
|
64
|
-
builtin_templates.each
|
64
|
+
builtin_templates.each{ |file| FileUtils.cp(file, DEFAULT_TEMPLATE_DIR) }
|
65
65
|
end
|
66
66
|
|
67
|
-
private
|
68
67
|
def factory
|
69
68
|
@factory ||= Xlogin::Factory.instance
|
70
69
|
end
|
71
70
|
|
71
|
+
private
|
72
72
|
def set(opts = {})
|
73
73
|
@settings ||= {}
|
74
74
|
opts.each do |key, val|
|
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)
|
28
|
+
Signal.trap(:INT){ exit 1 }
|
30
29
|
|
31
30
|
jobs = config[:jobs] || 1
|
32
|
-
hosts = Xlogin.list(*config[:
|
33
|
-
width = hosts.map
|
31
|
+
hosts = Xlogin.list(*config[:patterns])
|
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
|
-
session.enable(
|
46
|
+
session.enable(session.config.enable) if session.config.enable && Xlogin.settings.enable?
|
47
47
|
|
48
|
-
command_lines =
|
49
|
-
command_lines.each
|
48
|
+
command_lines = config[:command].flat_map { |e| e.to_s.split(';').map(&:strip) }
|
49
|
+
command_lines.each{ |line| session.cmd(line) }
|
50
50
|
|
51
|
-
buffer.string.lines.each
|
51
|
+
buffer.string.lines.each{ |line| print prefix + line.gsub(/^.*\r/, '') } if jobs > 1
|
52
52
|
rescue => e
|
53
|
-
buffer.string.lines.each
|
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.')
|
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
|
-
parser.on('-l', '--list', TrueClass, 'List the inventory.')
|
77
|
-
parser.on('-e COMMAND', '--exec', String, 'Execute commands and quit.')
|
77
|
+
parser.on('-l', '--list', TrueClass, 'List the inventory.') { |v| config[:runner] = self.method(:list) }
|
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,5 +1,7 @@
|
|
1
1
|
require 'addressable/uri'
|
2
|
+
require 'net/ssh/gateway'
|
2
3
|
require 'singleton'
|
4
|
+
require 'thread'
|
3
5
|
require 'xlogin/session_pool'
|
4
6
|
require 'xlogin/template'
|
5
7
|
|
@@ -9,30 +11,32 @@ module Xlogin
|
|
9
11
|
include Singleton
|
10
12
|
|
11
13
|
def initialize
|
12
|
-
@inventory
|
13
|
-
@templates
|
14
|
+
@inventory = Hash.new
|
15
|
+
@templates = Hash.new
|
16
|
+
@gateways = Hash.new
|
17
|
+
@mutex = Mutex.new
|
14
18
|
end
|
15
19
|
|
16
|
-
def
|
17
|
-
@inventory[name] = (
|
20
|
+
def set_hostinfo(name, **opts)
|
21
|
+
@inventory[name] = (get_hostinfo(name) || {name: name}).merge(opts)
|
18
22
|
end
|
19
23
|
|
20
|
-
def
|
24
|
+
def get_hostinfo(name)
|
21
25
|
@inventory[name]
|
22
26
|
end
|
23
27
|
|
24
|
-
def
|
28
|
+
def list_hostinfo(*patterns)
|
25
29
|
return @inventory.values if patterns.empty?
|
26
30
|
|
27
|
-
values1 = patterns.map do |pattern|
|
31
|
+
values1 = patterns.compact.map do |pattern|
|
28
32
|
values2 = pattern.split(',').map do |entry|
|
29
33
|
key, val = entry.to_s.split(':')
|
30
34
|
key, val = 'name', key if val.nil?
|
31
|
-
@inventory.values.select
|
35
|
+
@inventory.values.select{ |e| File.fnmatch(val, e[key.to_sym]) }
|
32
36
|
end
|
33
|
-
values2.reduce(&:&)
|
37
|
+
values2.reduce(&:&) || []
|
34
38
|
end
|
35
|
-
values1.reduce(&:|)
|
39
|
+
values1.reduce(&:|) || []
|
36
40
|
end
|
37
41
|
|
38
42
|
def set_template(name, text = nil, &block)
|
@@ -50,6 +54,35 @@ module Xlogin
|
|
50
54
|
@templates.keys
|
51
55
|
end
|
52
56
|
|
57
|
+
def open_tunnel(tunnel, host, port)
|
58
|
+
@mutex.synchronize do
|
59
|
+
unless @gateways[tunnel]
|
60
|
+
gateway_uri = Addressable::URI.parse(tunnel)
|
61
|
+
case gateway_uri.scheme
|
62
|
+
when 'ssh'
|
63
|
+
username, password = *gateway_uri.userinfo.split(':')
|
64
|
+
@gateways[tunnel] = Net::SSH::Gateway.new(
|
65
|
+
gateway_uri.host,
|
66
|
+
username,
|
67
|
+
password: password,
|
68
|
+
port: gateway_uri.port || 22
|
69
|
+
)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
gateway = @gateways[tunnel]
|
74
|
+
return host, port unless gateway
|
75
|
+
return '127.0.0.1', gateway.open(host, port)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def close_tunnel(tunnel, port)
|
80
|
+
@mutex.synchronize do
|
81
|
+
gateway = @gateways[tunnel]
|
82
|
+
gateway.close(port) if gateway
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
53
86
|
def build(type:, **opts)
|
54
87
|
template = get_template(type)
|
55
88
|
raise Xlogin::Error.new("Template not found: '#{type}'") unless template
|
@@ -62,31 +95,25 @@ module Xlogin
|
|
62
95
|
end
|
63
96
|
|
64
97
|
def build_from_hostname(args, **opts)
|
65
|
-
hostinfo =
|
98
|
+
hostinfo = get_hostinfo(args)
|
66
99
|
raise Xlogin::Error.new("Host not found: '#{args}'") unless hostinfo
|
67
100
|
|
68
|
-
build(hostinfo.merge(
|
101
|
+
build(**hostinfo.merge(**opts))
|
69
102
|
end
|
70
103
|
|
71
104
|
def method_missing(method_name, *args, **opts, &block)
|
72
|
-
super unless args.size == 2 &&
|
105
|
+
super unless args.size == 2 && Addressable::URI::URIREGEX =~ args[1]
|
73
106
|
|
74
|
-
type = method_name.to_s.downcase
|
75
107
|
name = args[0]
|
76
108
|
uri = args[1]
|
77
|
-
|
109
|
+
type = method_name.to_s.downcase
|
110
|
+
set_hostinfo(name.to_s, type: type, uri: uri, **opts)
|
78
111
|
end
|
79
112
|
|
80
113
|
private
|
81
114
|
def uri(**opts)
|
82
115
|
return Addressable::URI.parse(opts[:uri].strip) if opts.key?(:uri)
|
83
|
-
|
84
|
-
scheme = opts[:scheme].strip
|
85
|
-
address = opts.values_at(:host, :port).compact.map(&:strip).join(':')
|
86
|
-
userinfo = opts[:userinfo].strip
|
87
|
-
userinfo ||= opts.values_at(:username, :password).compact.map(&:strip).join(':')
|
88
|
-
|
89
|
-
Addressable::URI.parse("#{scheme}://" + [userinfo, address].compact.join('@'))
|
116
|
+
Addressable::URI.new(**opts)
|
90
117
|
rescue
|
91
118
|
raise Xlogin::Error.new("Invalid target - '#{opts}'")
|
92
119
|
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
|
|
@@ -13,11 +12,11 @@ module Xlogin
|
|
13
12
|
|
14
13
|
def generate(*patterns, **opts, &block)
|
15
14
|
description = Rake.application.last_description
|
16
|
-
hostnames = Xlogin.list(*patterns).map
|
15
|
+
hostnames = Xlogin.list(*patterns).map{ |e| e[:name] }
|
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)
|
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 msg(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,14 +1,11 @@
|
|
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
|
|
9
5
|
module Xlogin
|
10
6
|
module SessionModule
|
11
7
|
|
8
|
+
attr_reader :config
|
12
9
|
attr_accessor :name
|
13
10
|
|
14
11
|
def initialize(template, uri, **opts)
|
@@ -21,16 +18,14 @@ module Xlogin
|
|
21
18
|
end
|
22
19
|
|
23
20
|
@name = opts[:name] || @host
|
24
|
-
@
|
21
|
+
@tunnel = opts[:tunnel] || opts[:via]
|
22
|
+
@config = ReadOnlyStruct.new(opts)
|
25
23
|
@template = template
|
26
|
-
@
|
24
|
+
@loggers = [@config.log].flatten.uniq.reduce({}){ |a, e| a.merge(e => build_log(e)) }
|
25
|
+
@host, @port = Xlogin.factory.open_tunnel(@tunnel, @host, @port) if @tunnel
|
27
26
|
|
28
|
-
forward = @config.forward || @config.via
|
29
|
-
ssh_tunnel(forward) if forward
|
30
27
|
max_retry = @config.retry || 1
|
31
|
-
|
32
|
-
@mutex = Mutex.new
|
33
|
-
@loggers = [@config.log].flatten.uniq.reduce({}) { |a, e| a.merge(e => build_logger(e)) }
|
28
|
+
username, password = uri.userinfo.to_s.split(':')
|
34
29
|
|
35
30
|
begin
|
36
31
|
args = Hash.new
|
@@ -42,8 +37,8 @@ module Xlogin
|
|
42
37
|
else
|
43
38
|
args['Host' ] = @host
|
44
39
|
args['Port' ] = @port
|
45
|
-
args['Username'] =
|
46
|
-
args['Password'] =
|
40
|
+
args['Username'] = username
|
41
|
+
args['Password'] = password
|
47
42
|
end
|
48
43
|
|
49
44
|
super(args)
|
@@ -63,47 +58,54 @@ module Xlogin
|
|
63
58
|
end
|
64
59
|
|
65
60
|
def prompt_pattern
|
66
|
-
Regexp.union(*@template.
|
61
|
+
Regexp.union(*@template.prompts.map(&:first))
|
67
62
|
end
|
68
63
|
|
69
64
|
def duplicate(type: @template.name, **args)
|
70
65
|
template = Xlogin::Factory.instance.get_template(type)
|
66
|
+
raise Xlogin::Error.new("Template not found: '#{type}'") unless template
|
67
|
+
|
71
68
|
template.build(@uri, **@config.to_h.merge(args))
|
72
69
|
end
|
73
70
|
|
74
|
-
def puts(
|
75
|
-
|
76
|
-
args.empty? ? super('', &block) : super(*args, &block)
|
71
|
+
def puts(string = '', &block)
|
72
|
+
super(string, &block)
|
77
73
|
end
|
78
74
|
|
79
|
-
def
|
80
|
-
|
81
|
-
|
75
|
+
def print(string = '', &block)
|
76
|
+
string = instance_exec(string, &@template.interrupt!) if @template.interrupt!
|
77
|
+
super(string, &block)
|
82
78
|
end
|
83
79
|
|
84
|
-
def
|
85
|
-
|
86
|
-
@mutex.synchronize do
|
87
|
-
@loggers.each do |_, logger|
|
88
|
-
next if logger.nil? || [$stdout, $stderr].include?(logger)
|
89
|
-
logger.close
|
90
|
-
end
|
80
|
+
def waitfor(*args, &block)
|
81
|
+
return waitfor(prompt_pattern) if args.empty?
|
91
82
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
83
|
+
line = super(*args) do |recv|
|
84
|
+
write_log(recv)
|
85
|
+
block.call(recv) if block
|
86
|
+
end
|
96
87
|
|
97
|
-
|
88
|
+
_, process = @template.prompts.find{ |r, p| r =~ line && p }
|
89
|
+
if process
|
90
|
+
instance_eval(&process)
|
91
|
+
line += waitfor(*args, &block)
|
98
92
|
end
|
93
|
+
|
94
|
+
return line
|
99
95
|
end
|
100
96
|
|
101
|
-
def
|
102
|
-
@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
|
103
105
|
end
|
104
106
|
|
105
107
|
def enable_log(log = $stdout)
|
106
|
-
@loggers.update(log =>
|
108
|
+
@loggers.update(log => build_log(log))
|
107
109
|
if block_given?
|
108
110
|
yield
|
109
111
|
disable_log(log)
|
@@ -119,40 +121,11 @@ module Xlogin
|
|
119
121
|
end
|
120
122
|
|
121
123
|
private
|
122
|
-
def
|
123
|
-
|
124
|
-
line = __waitfor.call(*args) do |recv|
|
125
|
-
log(recv)
|
126
|
-
block.call(recv) if block
|
127
|
-
end
|
128
|
-
|
129
|
-
_, process = @template.prompt.find { |r, p| r =~ line && p }
|
130
|
-
if process
|
131
|
-
instance_eval(&process)
|
132
|
-
line += _waitfor(*args, &block)
|
133
|
-
end
|
134
|
-
|
135
|
-
return line
|
136
|
-
end
|
137
|
-
|
138
|
-
def ssh_tunnel(gateway)
|
139
|
-
gateway_uri = Addressable::URI.parse(gateway)
|
140
|
-
case gateway_uri.scheme
|
141
|
-
when 'ssh'
|
142
|
-
username, password = *gateway_uri.userinfo.split(':')
|
143
|
-
@gateway = Net::SSH::Gateway.new(
|
144
|
-
gateway_uri.host,
|
145
|
-
username,
|
146
|
-
password: password,
|
147
|
-
port: gateway_uri.port || 22
|
148
|
-
)
|
149
|
-
|
150
|
-
@port = @gateway.open(@host, @port)
|
151
|
-
@host = '127.0.0.1'
|
152
|
-
end
|
124
|
+
def write_log(text)
|
125
|
+
@loggers.each{ |_, logger| logger.syswrite(text) if logger }
|
153
126
|
end
|
154
127
|
|
155
|
-
def
|
128
|
+
def build_log(log)
|
156
129
|
case log
|
157
130
|
when String
|
158
131
|
FileUtils.mkdir_p(File.dirname(log))
|
data/lib/xlogin/session_pool.rb
CHANGED
@@ -22,23 +22,23 @@ module Xlogin
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def size=(val)
|
25
|
-
@mutex.synchronize
|
25
|
+
@mutex.synchronize{ @size = val }
|
26
26
|
end
|
27
27
|
|
28
28
|
def idle=(val)
|
29
|
-
@mutex.synchronize
|
29
|
+
@mutex.synchronize{ @idle = val }
|
30
30
|
end
|
31
31
|
|
32
32
|
def with
|
33
33
|
session = deq
|
34
|
-
Thread.handle_interrupt(Exception => :immediate)
|
34
|
+
Thread.handle_interrupt(Exception => :immediate){ yield session }
|
35
35
|
ensure
|
36
36
|
enq session
|
37
37
|
end
|
38
38
|
|
39
39
|
def close
|
40
|
-
|
41
|
-
session, _
|
40
|
+
until @queue.empty?
|
41
|
+
session, _ = @queue.deq
|
42
42
|
destroy(session)
|
43
43
|
end
|
44
44
|
end
|
@@ -58,10 +58,10 @@ module Xlogin
|
|
58
58
|
end
|
59
59
|
|
60
60
|
begin
|
61
|
-
raise IOError if session
|
61
|
+
raise IOError if session&.sock&.closed?
|
62
62
|
rescue IOError, EOFError, Errno::ECONNABORTED, Errno::ECONNREFUSED, Errno::ECONNRESET
|
63
63
|
destroy(session)
|
64
|
-
|
64
|
+
return deq
|
65
65
|
end
|
66
66
|
|
67
67
|
session
|
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
|
|
@@ -64,7 +54,7 @@ module Xlogin
|
|
64
54
|
|
65
55
|
def method_missing(name, *, &block)
|
66
56
|
super unless RESERVED_METHODS.include?(name)
|
67
|
-
bind(name)
|
57
|
+
bind(name){ |*args| instance_exec(*args, &block) }
|
68
58
|
end
|
69
59
|
|
70
60
|
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.14.4
|
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-06-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: net-telnet
|