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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bbc5c0255a1328038a84dc96b4e99c1b252c998ece79c03f7e23d22b7c5eba2d
4
- data.tar.gz: a605c1f6a0bb730e3aab19c13401c902416aee1e985a34a4d3132c014bc36f43
3
+ metadata.gz: cda6f2be936220ffb49b9d586d0e8ebcd248194bb0d16b7bc595ab3209da42da
4
+ data.tar.gz: 484a16a9c5f1e3eaa9d65f85de52e634ffb29a6b4e66f5461385c1f29c115ae6
5
5
  SHA512:
6
- metadata.gz: c077a17025b8131927de93b9602bc1d8957f4545b38bf2ec58c95d460faf3320d55770c8efc65cce4796eeb11fa5d3e4d3bcd7acbd9b35b44e7beffc1da55b5e
7
- data.tar.gz: 665cb8e5a6ebde87623135a31ca21d5f2e25408e31e4fc12560fa4ce6095459a686a32317c1a0dabb4eace83224b828999be069a5c84f225fa4dad9dc174a44b
6
+ metadata.gz: 0562b0ad712a00b3b2dd8d36b43c3fc15f1435492f9c025e374c52a1a92495575667476dfe1e679a57a6f396b5eba0bad2341d56c0a36454e70ff7737c656ac8
7
+ data.tar.gz: 716231e8c898ebb0d10b85a58c422f9815361e9c4303c803f2ca40c80fd83e957cd135b3f2db920ec12468e04e42d25f3c5b0213120e149451a62535b218b71b
@@ -22,7 +22,7 @@ module Xlogin
22
22
 
23
23
  class << self
24
24
  def list(*patterns)
25
- factory.list_inventory(*patterns)
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 SessionError.new("Invalid argument: '#{args}'")
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 get_pool(args, **opts, &block)
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) unless Dir.exist?(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
@@ -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
- DEFAULT_INVENTORY_FILE = File.join(ENV['HOME'], '.xloginrc')
11
- DEFAULT_TEMPLATE_DIR = File.join(ENV['HOME'], '.xlogin.d')
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[:pattern]).map{ |e| e[:name] }.sort.uniq
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.list(*config[:pattern]).shift
20
+ info = Xlogin.find(*config[:patterns])
23
21
  puts "Trying #{info[:name]}...", "Escape character is '^]'."
24
- session, _ = exec(config.merge(pattern: info[:name], jobs: 1))
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 0 }
28
+ Signal.trap(:INT){ exit 1 }
30
29
 
31
30
  jobs = config[:jobs] || 1
32
- hosts = Xlogin.list(*config[:pattern])
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[:"log-dir"], "#{info[:name]}.log"), ENV['PWD']) if config[:"log-dir"]
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 = ['', *config[:exec].to_s.split(';').map(&:strip)]
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("\r", '') } if jobs > 1
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("\r", '') } if jobs > 1
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[:pattern] = parser.parse!(args, into: 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 Hash[config[:env].map{ |v| v.split('=') }]
86
- source File.expand_path(config[:inventory], ENV['PWD'])
87
- template File.expand_path(config[:template], ENV['PWD'])
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
@@ -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
- @gateways = Hash.new
16
+ @tunnels = Hash.new
16
17
  @mutex = Mutex.new
17
18
  end
18
19
 
19
- def set_inventory(name, **opts)
20
- @inventory[name] = (get_inventory(name) || {name: name}).merge(opts)
20
+ def set_hostinfo(name, **opts)
21
+ @inventory[name] = (get_hostinfo(name) || {name: name}).merge(opts)
21
22
  end
22
23
 
23
- def get_inventory(name)
24
+ def get_hostinfo(name)
24
25
  @inventory[name]
25
26
  end
26
27
 
27
- def list_inventory(*patterns)
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(tunnel, host, port)
57
+ def open_tunnel(name, host, port)
57
58
  @mutex.synchronize do
58
- unless @gateways[tunnel]
59
- gateway_uri = Addressable::URI.parse(tunnel)
60
- case gateway_uri.scheme
59
+ unless @tunnels[name]
60
+ uri = Addressable::URI.parse(name)
61
+ case uri.scheme
61
62
  when 'ssh'
62
- username, password = *gateway_uri.userinfo.split(':')
63
- @gateways[tunnel] = Net::SSH::Gateway.new(
64
- gateway_uri.host,
63
+ username, password = *uri.userinfo.split(':')
64
+ gateway = Net::SSH::Gateway.new(
65
+ uri.host,
65
66
  username,
66
67
  password: password,
67
- port: gateway_uri.port || 22
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
- gateway = @gateways[tunnel]
73
- return host, port unless gateway
74
- return '127.0.0.1', gateway.open(host, port)
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(tunnel, port)
84
+ def close_tunnel(name, port)
79
85
  @mutex.synchronize do
80
- gateway = @gateways[tunnel]
81
- gateway.close(port) if gateway
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 = get_inventory(args)
106
+ hostinfo = get_hostinfo(args)
98
107
  raise Xlogin::Error.new("Host not found: '#{args}'") unless hostinfo
99
108
 
100
- build(hostinfo.merge(name: args, **opts))
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 && %r{^\S+://\S+} =~ args[1]
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
- set_inventory(name, type: type, uri: uri, **opts)
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
@@ -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} @#{hostname}" if opts[:all] == false
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 = StringIO.new
82
-
83
- logger = log ? [log] : []
84
- logger.push buffer
85
- logger.push $stdout if !silent && !Rake.application.options.always_multitask
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: logger, timeout: timeout)
86
+ session = Xlogin.get(name, log: loggers, timeout: timeout)
88
87
 
89
88
  @runner.call(session)
90
- $stdout.print format_log(buffer.string) if !silent && Rake.application.options.always_multitask
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
- buffer.puts("\n[ERROR] #{e}".colorize(color: :white, background: :red))
97
- $stderr.print "\n" + format_log(buffer.string.chomp).colorize(color: :light_red)
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 format_log(text)
106
- text.lines.map do |line|
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
- log("\n")
118
- log(Time.now.iso8601.colorize(**color) + ' ') if !Rake.application.options.always_multitask
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
@@ -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
- @username, @password = uri.userinfo.to_s.split(':')
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
- @mutex = Mutex.new
34
- @loggers = [@config.log].flatten.uniq.reduce({}){ |a, e| a.merge(e => build_logger(e)) }
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'] = @username
47
- args['Password'] = @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.prompt.map(&:first))
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(*args, &block)
76
- args = args.flat_map{ |arg| instance_exec(arg, &@template.interrupt!) }.compact if @template.interrupt!
77
- args.empty? ? super('', &block) : super(*args, &block)
71
+ def puts(string = '', &block)
72
+ super(string, &block)
78
73
  end
79
74
 
80
- def waitfor(*args, &block)
81
- args = [prompt_pattern] if args.empty?
82
- @mutex.synchronize{ _waitfor(*args, &block) }
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 close
86
- logout if respond_to?(:logout)
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
- Xlogin.factory.close_tunnel(@tunnel, @port) if @tunnel
94
- super
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 log(text)
99
- @loggers.each{ |_, logger| logger.syswrite(text) if logger }
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 => build_logger(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 _waitfor(*args, &block)
120
- __waitfor = method(:waitfor).super_method
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 build_logger(log)
128
+ def build_log(log)
136
129
  case log
137
130
  when String
138
131
  FileUtils.mkdir_p(File.dirname(log))
@@ -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
- @created = 0
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
- while @queue.empty?
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? && @created < @size
49
- @created += 1
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.sock.closed?
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
- session = deq
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
- @queue.enq [session, last_used]
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
- @created -= 1
73
+ @count -= 1
79
74
  end
80
75
  end
81
76
 
@@ -2,7 +2,7 @@ module Xlogin
2
2
  class ExpectationError < StandardError
3
3
 
4
4
  def initialize(expect, actual)
5
- super("Expected to match #{expect}")
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(session, *args)
18
- @session = session
19
- @args = args
17
+ def initialize(result)
18
+ @result = result
20
19
  end
21
20
 
22
21
  def to_match(regexp)
23
- return if match(regexp)
24
- raise ExpectationError.new(@expect, @actual)
25
- end
22
+ regexp = Regexp.new(regexp.to_s) unless regexp.kind_of?(Regexp)
23
+ return if @result =~ regexp
26
24
 
27
- def not_to_match(regexp)
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
- private
33
- def match(regexp)
34
- regexp = Regexp.new(regexp.to_s) unless regexp.kind_of?(Regexp)
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
- @actual ||= @session.cmd(*@args)
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(self, *args)
40
+ Expectation.new(cmd(*args))
47
41
  end
48
- alias_method :exp, :expect
49
42
 
50
43
  end
51
44
  end
@@ -38,7 +38,7 @@ module Xlogin
38
38
  @sock.syswrite(bs)
39
39
  when @sock
40
40
  begin
41
- log(fh.readpartial(1024))
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
- self.close
50
+ close
51
51
  ensure
52
52
  $stdin.cooked!
53
53
  end
@@ -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, :scopes, :methods
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 timeout(val = nil)
23
- @timeout = val.to_i if val
24
- @timeout
21
+ def prompt(expect, &block)
22
+ @prompts << [Regexp.new(expect.to_s), block]
25
23
  end
26
24
 
27
- def prompt(expect = nil, &block)
28
- return [[DEFAULT_PROMPT, nil]] if expect.nil? && @prompts.empty?
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 scope(name = nil, &block)
34
- @scopes[name] = block
30
+ def bind(name, &block)
31
+ @methods[name] = block
35
32
  end
36
33
 
37
- def bind(name = nil, &block)
38
- @methods[name] = block
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
- return @interrupt unless block
43
- @interrupt = block
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
- case name.to_s
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
 
@@ -1,3 +1,3 @@
1
1
  module Xlogin
2
- VERSION = "0.14.1"
2
+ VERSION = "0.15.0"
3
3
  end
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.14.1
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-04-27 00:00:00.000000000 Z
11
+ date: 2020-09-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: net-telnet