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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0e757e206fe886bf5b5f4ed56e8b7a47cf6837c9
4
- data.tar.gz: 43ea824dfd4a8131b795b81bc1fd342de46155e9
3
+ metadata.gz: 07769f0169bc78d592d032d9e1fead22b33f486a
4
+ data.tar.gz: 701a5526bd5592fc5018970a4854c75e9f100333
5
5
  SHA512:
6
- metadata.gz: 235201b50907672f266709c6036802d3c3fbbdfa96436f4eb0ee24b40268bb273ceba8ac77598c3bbf01faa242ae12fc889524bc658edefb1c3968f5933a3e4f
7
- data.tar.gz: 4851153be8d9c4208daf7283e35646f1d557830a2bb82540d031ce479589d6f56040035d43ad62e206c26df9c50bbab9bb9ea46cdc1160c266eeab8120c64791
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/firmware'
4
- require 'xlogin/firmware_factory'
3
+ require 'xlogin/factory'
5
4
  require 'xlogin/version'
6
5
 
7
6
  module Xlogin
8
7
 
9
- DEFAULT_SOURCE_FILE = File.join(ENV['HOME'], '.xloginrc')
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 HostNotFound < StandardError; end
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 authorize(boolean = false, &block)
20
- @authorized = boolean == true || (block && block.call == true)
18
+ def factory
19
+ @factory ||= Xlogin::Factory.instance
21
20
  end
22
21
 
23
- def authorized?
24
- @authorized == true
25
- end
22
+ def get(hostname, args = {})
23
+ session = factory.build_from_hostname(hostname, args)
26
24
 
27
- def factory
28
- @factory ||= Xlogin::FirmwareFactory.instance
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 init(&block)
32
- instance_eval(&block)
32
+ def configure(&block)
33
+ instance_eval(&block) if block
33
34
 
34
- factory.source(DEFAULT_SOURCE_FILE) if factory.list.empty?
35
+ source(DEFAULT_INVENTORY_FILE) if factory.list.empty?
35
36
  if factory.list_templates.empty?
36
- unless File.exist?(DEFAULT_TEMPLATE_DIR)
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
- factory.load_template_files(*Dir.glob(File.join(DEFAULT_TEMPLATE_DIR, '*.rb')))
41
+ template_dir(DEFAULT_TEMPLATE_DIR)
41
42
  end
42
43
  end
43
44
 
44
- def source(source_file)
45
- factory.source(source_file)
45
+ def authorized?
46
+ @authorized == true
46
47
  end
47
48
 
48
- def template(*template_files)
49
- factory.load_template_files(*template_files)
49
+ private
50
+ def authorize(boolean = false, &block)
51
+ @authorized = boolean == true || (block && block.call == true)
50
52
  end
51
53
 
52
- def get(hostname, args = {})
53
- session = factory.build_from_hostname(hostname, args)
54
+ def source(source_file)
55
+ factory.source(source_file)
56
+ end
54
57
 
55
- if block_given?
56
- begin yield session ensure session.close end
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
- func: 'tty',
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('-f FUNCTION', '--func', String, 'Execute the FUNCTION (default: tty).') { |v| config.func = v }
31
- parser.on('-a ARGUMENTS', '--args', String, 'The ARGUMENTS to pass to the function.') { |v| config.args = v }
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.autoenable = v }
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, '' if 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.hostexprs = parser.parse(args)
52
- config.templates = Dir.glob(File.join(DEFAULT_TEMPLATE_DIR, '*.rb')) if config.templates.empty?
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
- factory = Xlogin::FirmwareFactory.instance
61
- config.hostlist += config.hostexprs.flat_map { |expr| factory.list(expr) }
62
- config
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
- length = config.hostlist.map { |e| e[:name].length }.max
81
- matrix = config.hostlist.map { |e| "#{e[:name].to_s.ljust(length)} #{e[:type]}" }.sort
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
- Xlogin::CLI.usage('Invalid parametors: ' + config.hostexprs.join(' ')) if config.hostlist.empty?
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 command(config)
98
- Xlogin::CLI.usage('Invalid parametors') if config.hostlist.empty? or config.args.nil?
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 command_load(config)
107
- Xlogin::CLI.usage('Invalid parametors') if config.hostlist.empty? or config.args.nil?
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
- FileUtils.mkdir_p(config.logdir) if config.logdir
118
- config.parallels = [config.parallels, config.hostlist.size].min
110
+ display = Mutex.new
111
+ buffer = StringIO.new
119
112
 
120
- display = Mutex.new
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 = host[:name]
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.get(hostname, log: loggers)
132
- session.enable(session.enable_password) if config.autoenable && session.respond_to?(:enable)
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
- if config.parallels > 1
142
- output = "\n#{hostname}: [Error] #{e}"
143
- display.synchronize { $stderr.puts output }
144
- else
145
- output = "\n[Error] #{e}"
146
- display.synchronize { $stderr.puts output }
147
- end
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
@@ -1,61 +1,53 @@
1
1
  require 'uri'
2
2
 
3
3
  module Xlogin
4
- class Firmware
5
-
6
- module FirmwareDelegator
7
- def run(uri, opts = {})
8
- uri = URI(uri.to_s)
9
-
10
- if hostname = opts.delete(:delegate)
11
- target = Xlogin.factory.get(hostname)
12
- target_os = Xlogin.factory.get_template(target[:type])
13
- target_uri = URI(target[:uri])
14
-
15
- login = @methods.fetch(:login)
16
- delegate = @methods.fetch(:delegate)
17
-
18
- userinfo = uri.userinfo.dup
19
- uri.userinfo = ''
20
-
21
- session = target_os.run(uri, opts)
22
- session.instance_exec(*userinfo.split(':'), &login)
23
- session.instance_exec(target_uri, opts, &delegate)
24
- session
25
- else
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 FirmwareDelegator
29
+ prepend RelayTemplate
30
+
32
31
 
33
32
  ### Usage:
34
33
  ## Write xloginrc file
35
34
  #
36
- # vyos 'vyos01', 'telnet://user:pass@host:port'
37
- # consolesrv 'vyos01::console', 'telnet://console_user:console_pass@console_host:console_port', delegate: 'vyos01'
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
- # Xlogin.configure :consolesrv do |os|
43
- # os.bind(:login) do |*args|
44
- # username, password = *args
45
- # waitfor(/login:\s*\z/) && puts(username)
46
- # waitfor(/Password:\s*\z/) && puts(password)
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
- # os.bind(:delegate) do |uri, opts|
50
- # begin
51
- # waittime = 3
52
- # Timeout.timeout(waittime) do
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 'stringio'
4
- require 'xlogin/firmware'
2
+ require 'xlogin/template'
5
3
 
6
4
  module Xlogin
7
- class FirmwareFactory
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
- return unless File.exist?(file)
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(**opts)
50
- @database[opts[:name]] = opts
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(args)
67
- type = args.delete(:type)
62
+ def build(type:, uri:, **params)
68
63
  template = get_template(type)
69
- raise Xlogin::TemplateNotFound.new("template not found: '#{type}'") unless template
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.dup.run(uri, opts)
67
+ template.build(uri, **params)
76
68
  end
77
69
 
78
- def build_from_hostname(hostname, **args)
70
+ def build_from_hostname(hostname, **params)
79
71
  hostinfo = get(hostname)
80
- raise Xlogin::HostNotFound.new("host not found: '#{hostname}'") unless hostinfo
72
+ raise Xlogin::SessionNotFound.new("Host not found: '#{hostname}'") unless hostinfo
81
73
 
82
- build(hostinfo.merge(args))
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 =~ /source/ and args.size >= 2
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(':')
@@ -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
- names = names.map(&:strip).grep(/^\s*[^#]/)
24
- namecount = names.uniq.inject({}) { |a, e| a.merge(e => names.count(e)) }
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 "#{description} (#{name})"
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
- path = Rake.application.current_scope.path
40
-
41
- if path.empty?
42
- path = self.name.split('::').first.downcase
43
- namespace(path) { yield } if block_given?
44
- else
45
- yield if block_given?
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 :xlogin_opts
55
- attr_accessor :fail_on_error
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 = name
63
- @xlogin_opts = Xlogin.factory.get(name) || {}
64
- @session = nil
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
- define
49
+ define_task
75
50
  end
76
51
 
77
52
  def start(&block)
78
- @taskrunner = block
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 define
94
- RakeTask.current_namespace do
95
- desc Rake.application.last_description || "Run '#{RakeTask.current_namespace}'"
96
- Rake.application.last_description = nil if uncomment
97
-
98
- if lockfile
99
- task(name => lockfile)
100
- file(lockfile) do
101
- run_task
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) do
108
- run_task
109
- end
69
+ task(name) { invoke }
110
70
  end
111
71
  end
112
72
  end
113
73
 
114
- def run_task
115
- raise ArgumentError.new("missing xlogin_opts to connect to #{name}") unless @xlogin_opts[:type] && @xlogin_opts[:uri]
74
+ def invoke
75
+ loggers = []
116
76
 
117
- loggers = [@xlogin_opts[:log]].flatten.compact
118
- loggers << $stdout unless silent || Rake.application.options.always_multitask
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
- @xlogin_opts[:log] = loggers unless loggers.empty?
126
-
127
- begin
128
- @session = Xlogin.factory.build(@xlogin_opts)
129
- if @session && @taskrunner
130
- @session.extend(SessionExt)
131
-
132
- # pass RakeTask#safe_puts method to the session instance.
133
- method_proc = method(:safe_puts)
134
- @session.define_singleton_method(:safe_puts) { |*args| method_proc.call(*args) }
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
- rescue => e
139
- raise e if fail_on_error
140
- safe_puts(e, io: $stderr, force: true)
141
- end
142
- end
143
-
144
- module SessionExt
145
- def cmd(*args)
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
@@ -1,124 +1,148 @@
1
- require 'thread'
2
- require 'timeout'
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 Session
8
+ module SessionModule
7
9
 
8
- attr_reader :opts
9
10
  attr_accessor :name
11
+ attr_accessor :opts
10
12
 
11
- def configure_session(**opts)
12
- @opts = opts.dup
13
- @host = @opts[:host]
14
- @name = @opts[:name] || @host
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
- @prompts = @opts[:prompts] || [[/[$%#>] ?\z/n, nil]]
20
- @timeout = @opts[:timeout] || 10
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
- @loglist = [@opts[:log]].flatten.compact
23
- @logger = update_logger
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 enable(*args)
27
- # do nothing here.
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 enable_password
32
- opts[:enablepw]
55
+ def puts(line)
56
+ line = instance_exec(line, &@template.interrupt) if @template.interrupt
57
+ super(line)
33
58
  end
34
59
 
35
- def waitfor(*expect, &block)
36
- if expect.compact.empty?
37
- super(Regexp.union(*@prompts.map(&:first))) do |recvdata|
38
- @logger.call(recvdata)
39
- block.call(recvdata) if block
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 dup(**args)
57
- self.class.new(**@opts.merge(args))
67
+ def respond_to_missing?(name, _)
68
+ @template.methods[name]
58
69
  end
59
70
 
60
- def lock(timeout: @timeout)
61
- @mutex ||= Mutex.new
71
+ def waitfor(*expect, &block)
72
+ return waitfor(Regexp.union(*@template.prompt.map(&:first)), &block) if expect.empty?
62
73
 
63
- begin
64
- Timeout.timeout(timeout) { @mutex.lock }
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
- def with_retry(max_retry: 1)
72
- begin
73
- yield self
74
- rescue => e
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 enable_log(out = $stdout)
82
- enabled = @loglist.include?(out)
83
- unless enabled
84
- @loglist.push(out)
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
- disable_log(out) unless enabled
96
+ @output_loggers = prebuild_loggers
91
97
  end
92
98
  end
93
99
 
94
100
  def disable_log(out = $stdout)
95
- enabled = @loglist.include?(out)
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
- enable_log(out) if enabled
104
+ @output_loggers = prebuild_loggers
104
105
  end
105
106
  end
106
107
 
107
108
  private
108
- def update_logger
109
- loglist = [@loglist].flatten.uniq.map do |log|
110
- case log
111
- when String
112
- log = File.open(log, 'a+')
113
- log.binmode
114
- log.sync = true
115
- log
116
- when IO, StringIO
117
- log
118
- end
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
- @logger = lambda { |c| loglist.compact.each { |o| o.syswrite c } }
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 Session
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
- include Session
8
+ prepend SessionModule
9
9
 
10
- def initialize(**opts)
11
- configure_session(opts.merge(port: opts[:port] || 23))
10
+ alias_method :telnet_login, :login
11
+ undef_method :login
12
12
 
13
- super(
14
- 'Host' => @host,
15
- 'Port' => @port,
16
- 'Timeout' => @timeout,
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
- login(*@userinfo.split(':')) if respond_to?(:login) && !@userinfo.empty?
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-]> for quit
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
- @logger.call(bs)
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
@@ -1,3 +1,3 @@
1
1
  module Xlogin
2
- VERSION = "0.5.14"
2
+ VERSION = "0.6.1"
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.5.14
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-12 00:00:00.000000000 Z
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/firmware.rb
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
@@ -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
@@ -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