xlogin 0.7.16 → 0.8.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
  SHA1:
3
- metadata.gz: c724426e5a7a390655bde1100323aa82b3e7189e
4
- data.tar.gz: 2e1f06d01d84439fdbce0acd48487d1ffb6fec6d
3
+ metadata.gz: 1d9a1b26c54a0a1729282e93dca1abcc8dfc1336
4
+ data.tar.gz: 41c331ddf6bf109f0ef85b79d79f0f841dbb787c
5
5
  SHA512:
6
- metadata.gz: a878236af348f8b765472712079c09dc7847d6db5a0521c1913edd62fb4efd63cd24411386b320912c24ed504a56acf1b8a3267db8ba2c4ef2f63abf8869a234
7
- data.tar.gz: 6d48c122220ce879f30933a866773b9d2bd0879059cca7ea0419f7cb9b6aef6a08d6680f0e5629f8a44689e1efd4c0e85d1e6199775379e340c44908f325e6b8
6
+ metadata.gz: 5c472d2e2fbe03a9b2bcfc088fe3a5143ac0fa20253c53c07dc84604ebd4fdbab787343bbd8e06e1f5eea259d1ff8894055e6a7f3dc0e7ecc855d472584c660c
7
+ data.tar.gz: b46d85dca28cf7643e2c85fb517bf4c86a5a58418b76f88b3011436e2b4bdb04f1a2b1221c55a83923b3a33dcedfcbde2bd5de3f4591725e66a8725dae803636
data/Gemfile.lock ADDED
@@ -0,0 +1,32 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ xlogin (0.8.0)
5
+ net-ssh
6
+ net-ssh-gateway
7
+ net-ssh-telnet
8
+ net-telnet
9
+ parallel
10
+
11
+ GEM
12
+ remote: https://rubygems.org/
13
+ specs:
14
+ net-ssh (4.2.0)
15
+ net-ssh-gateway (2.0.0)
16
+ net-ssh (>= 4.0.0)
17
+ net-ssh-telnet (0.2.1)
18
+ net-ssh (>= 2.0.1)
19
+ net-telnet (0.1.1)
20
+ parallel (1.12.1)
21
+ rake (10.5.0)
22
+
23
+ PLATFORMS
24
+ ruby
25
+
26
+ DEPENDENCIES
27
+ bundler (~> 1.13)
28
+ rake (~> 10.0)
29
+ xlogin!
30
+
31
+ BUNDLED WITH
32
+ 1.16.1
data/README.md CHANGED
@@ -3,12 +3,12 @@ rancid clogin alternative.
3
3
 
4
4
  ## Usage
5
5
 
6
- ネットワークデバイスへのログインを自動化するツール群。
7
- `~/.xlogin.d/`へファームウェア毎の仕様を記述することで対象機器を拡大可能。
6
+ ネットワークデバイスへのログイン処理を自動化するツール群。  
7
+ `~/.xlogin.d/`にファームウェア毎のログイン仕様(template)を記述することで自働化対象機器を任意に設定可能。
8
+ templateの記述例は[lib/xlogin/templates](https://github.com/haccht/xlogin/tree/master/lib/xlogin/templates)を参照のこと。
8
9
 
9
-
10
- 各個別装置毎のログインのための認証情報は`~/.xloginrc`へ記述しておく。
11
- `.xloginrc`のフォーマットはDSL形式で下記の通り
10
+ 各個別装置毎のログインのための認証情報は`~/.xloginrc`へ記述する。  
11
+ `.xloginrc`のフォーマットはDSL形式で下記の通り。
12
12
 
13
13
  ~~~
14
14
  #hosttype hostname telnet_uri_scheme options
@@ -16,6 +16,18 @@ vyos 'vyos01', 'telnet://vagrant:vagrant@127.0.0.1:2200'
16
16
  vyos 'vyos02', 'telnet://vagrant:vagrant@127.0.0.1:2201'
17
17
  ~~~
18
18
 
19
+ 下記コマンドでvyos01へ自動ログインし、プロンプトをユーザに渡す。
20
+
21
+ ~~~sh
22
+ xlogin vyos01
23
+ ~~~
24
+
25
+ また下記コマンドでvyos01,vyos02へ同時に自動ログインし、コマンドを一括投入する。
26
+
27
+ ~~~sh
28
+ xlogin 'vyos*' exec 'show configuration command | no-more; exit' -j 2
29
+ ~~~
30
+
19
31
  ## Installation
20
32
 
21
33
  Add this line to your application's Gemfile:
data/lib/xlogin/cli.rb CHANGED
@@ -8,123 +8,110 @@ require 'stringio'
8
8
  module Xlogin
9
9
  class CLI
10
10
 
11
+ DEFAULT_INVENTORY_FILE = File.join(ENV['HOME'], '.xloginrc')
12
+ DEFAULT_TEMPLATE_DIR = File.join(ENV['HOME'], '.xlogin.d')
13
+
11
14
  def self.run(args = ARGV)
12
15
  config = getopts(args)
13
16
  client = Xlogin::CLI.new
14
-
15
- task = config.task.downcase.tr('-', '_')
16
- Xlogin::CLI.usage("Task not defined - #{task}") unless client.respond_to?(task)
17
- client.method(task).call(config)
17
+ client.method(config.taskname).call(config)
18
18
  end
19
19
 
20
20
  def self.getopts(args)
21
21
  config = OpenStruct.new(
22
- task: 'tty',
23
- hostlist: [],
24
- parallels: 1,
22
+ parallel: 1,
25
23
  inventory: nil,
26
24
  templates: [],
27
25
  )
28
26
 
29
27
  parser = OptionParser.new
30
- parser.banner += ' HOST-PATTERN'
31
-
32
- parser.on('-m TASK', '--task', String, 'Execute the TASK(default: tty).') { |v| config.task = v }
33
- parser.on('-a ARGS', '--args', String, 'The ARGS to pass to the task.') { |v| config.args = v }
34
-
35
- parser.on('-i PATH', '--inventory', String, 'The PATH to the inventory file (default: $HOME/.xloginrc).') { |v| config.inventory = v }
36
- parser.on('-t PATH', '--template', String, 'The PATH to the template file.') { |v| config.templates << v }
37
- parser.on('-T DIRECTORY', '--template-dir', String, 'The DIRECTORY to the template files.') { |v| config.templates += Dir.glob(File.join(v, '*.rb')) }
38
- parser.on('-l [DIRECTORY]', '--log', String, 'The DIRECTORY to the output log file (default: $PWD/log).') { |v| config.logdir = v || ENV['PWD'] }
39
-
40
- parser.on('-p NUM', '--parallels', Integer, 'The NUM of the threads. (default: 5).') { |v| config.parallels = v }
41
- parser.on('-e', '--enable', TrueClass, 'Try to gain enable priviledge.') { |v| config.enable = v }
42
- parser.on('-y', '--assume-yes', TrueClass, 'Always answer "yes" if confirmed.') { |v| config.assume_yes = v }
43
- parser.on('-h', '--help', 'Show this message.') { Xlogin::CLI.usage }
44
-
45
- self.class.module_eval do
46
- define_method(:usage) do |message = nil|
47
- puts message if message
48
- puts parser.to_s
49
- exit 1
28
+ parser.banner = "#{File.basename($0)} HOST [TASK ARGUMENTS] [Options]"
29
+ parser.version = Xlogin::VERSION
30
+
31
+ parser.on('-i PATH', '--inventory', String, 'The PATH to the inventory file(default: $HOME/.xloginrc).') { |v| config.inventory = v }
32
+ parser.on('-t PATH', '--template', String, 'The PATH to the template file.') { |v| config.templates << v }
33
+ parser.on('-T DIRECTORY', '--template-dir', String, 'The DIRECTORY of the template files.') { |v| config.templates += Dir.glob(File.join(v, '*.rb')) }
34
+ parser.on('-L [DIRECTORY]', '--log-dir', String, 'The DIRECTORY of the log files(default: $PWD).') { |v| config.logdir = v || '.' }
35
+
36
+ parser.on('-j NUM', '--jobs', Integer, 'The NUM of jobs to execute in parallel(default: 1).') { |v| config.parallel = v }
37
+ parser.on('-e', '--enable', TrueClass, 'Try to gain enable priviledge.') { |v| config.enable = v }
38
+ parser.on('-y', '--assume-yes', TrueClass, 'Automatically answer yes to prompts.') { |v| config.authorize = v }
39
+
40
+ begin
41
+ args = parser.parse!(args)
42
+ hostlist = args.shift
43
+ taskname = args.shift || 'tty'
44
+
45
+ Xlogin.configure do
46
+ config.inventory ||= DEFAULT_INVENTORY_FILE
47
+ if config.templates.empty?
48
+ generate_templates(DEFAULT_TEMPLATE_DIR) unless File.exist?(DEFAULT_TEMPLATE_DIR)
49
+ config.templates += Dir.glob(File.join(DEFAULT_TEMPLATE_DIR, '*.rb'))
50
+ end
51
+
52
+ authorize(config.authorize)
53
+ source(File.expand_path(config.inventory, ENV['PWD']))
54
+ load_templates(*config.templates.map { |file| File.expand_path(file, ENV['PWD']) })
50
55
  end
51
- end
52
56
 
53
- args = parser.parse(args)
54
- Xlogin.configure do
55
- template(*config.templates)
56
- source(config.inventory)
57
- authorize(config.assume_yes)
58
- end
57
+ config.hostlist = hostlist.to_s.split(',').flat_map { |pattern| Xlogin.factory.list(pattern) }
58
+ config.taskname = taskname.to_s.downcase.tr('-', '_')
59
+ config.arguments = args
59
60
 
60
- config.hostlist += args.flat_map do |target|
61
- hostlist = Xlogin.factory.list(target)
62
- hostlist.tap { |e| raise "Invalid inventory - #{target}" if e.empty? }
61
+ methods = Xlogin::CLI.instance_methods(false)
62
+ raise "No host found: `#{hostlist}`" if config.hostlist.empty?
63
+ raise "No task defined: `#{taskname}`" if config.taskname.empty? || methods.find_index(config.taskname.to_sym).nil?
64
+ rescue => e
65
+ $stderr.puts e, '', parser
66
+ exit 1
63
67
  end
64
68
 
65
- config.parallels = [config.parallels, config.hostlist.size].min
66
69
  config
67
70
  end
68
71
 
69
- def tty(config)
70
- config.hostlist = [config.hostlist.shift].compact
71
- Xlogin::CLI.usage('Invalid inventory.') if config.hostlist.empty?
72
-
73
- puts "Trying #{config.hostlist.first[:name]}..."
74
- puts "Escape character is '^]'."
72
+ def list(config)
73
+ width = config.hostlist.map { |e| e[:name].length }.max
74
+ $stdout.puts config.hostlist.map { |e| "#{e[:name].to_s.ljust(width)} #{e[:type]}" }.sort
75
+ end
75
76
 
76
- login(config) do |session|
77
- session.interact!
78
- end
77
+ def tty(config)
78
+ target = config.hostlist.sort_by { |e| e[:name] }.first
79
+ $stdout.puts "Trying #{target[:name]}...", "Escape character is '^]'."
80
+ config.hostlist = [target]
81
+ login(config) { |session| session.interact! }
79
82
  end
80
83
 
81
84
  def exec(config)
82
- Xlogin::CLI.usage('Invalid inventory.') if config.hostlist.empty?
83
-
84
- login(config) do |session|
85
- command_lines = ['', *config.args.split(';')]
86
- command_lines.each { |command| session.cmd(command) }
87
- end
85
+ command_lines = ['', *config.arguments.flat_map { |e| e.split(';') }].map(&:strip)
86
+ login(config) { |session| command_lines.each { |command| session.cmd(command) } }
88
87
  end
89
88
 
90
89
  def load(config)
91
- Xlogin::CLI.usage('Invalid inventory.') if config.hostlist.empty?
92
-
93
- login(config) do |session|
94
- command_lines = ['', *IO.readlines(config.args.to_s)]
95
- command_lines.each { |command| session.cmd(command) }
96
- end
97
- end
98
-
99
- def list(config)
100
- config.hostlist += Xlogin.factory.list('all') if config.hostlist.empty?
101
- width = config.hostlist.map { |e| e[:name].length }.max
102
- puts config.hostlist.map { |e| "#{e[:name].to_s.ljust(width)} #{e[:type]}" }.sort
90
+ command_lines = ['', *config.arguments.flat_map { |e| IO.readlines(e) }].map(&:strip)
91
+ login(config) { |session| command_lines.each { |command| session.cmd(command) } }
103
92
  end
104
93
 
105
94
  private
106
95
  def login(config, &block)
107
- buffer = StringIO.new
108
-
109
- Parallel.map(config.hostlist, in_thread: config.parallels) do |hostinfo|
96
+ Parallel.map(config.hostlist, in_threads: config.parallel) do |hostinfo|
110
97
  begin
98
+ buffer = StringIO.new
111
99
  hostname = hostinfo[:name]
112
100
 
113
101
  loggers = []
114
- loggers << buffer if config.parallels != 1
115
- loggers << $stdout if config.parallels == 1
116
- loggers << File.join(config.logdir, "#{hostname}.log") if config.logdir
102
+ loggers << ((config.parallel > 1)? buffer : $stdout)
103
+ loggers << File.expand_path(File.join(config.logdir, "#{hostname}.log"), ENV['PWD']) if config.logdir
117
104
 
118
105
  session = Xlogin.get(hostinfo.merge(log: loggers))
119
- session.enable if session.config.enable && config.enable
106
+ session.enable if config.enable && hostinfo[:enable]
120
107
 
121
108
  block.call(session)
122
109
  rescue => e
123
- lines = (config.parallels > 1)? ["\n#{hostname}\t| [Error] #{e}"] : ["\n[Error] #{e}"]
110
+ lines = (config.parallel > 1)? ["\n#{hostname}\t| [Error] #{e}"] : ["\n[Error] #{e}"]
124
111
  lines.each { |line| $stderr.print "#{line.chomp}\n" }
125
112
  end
126
113
 
127
- if config.parallels > 1
114
+ if config.parallel > 1
128
115
  lines = buffer.string.lines.map { |line| "#{hostname}\t| " + line.gsub("\r", '') }
129
116
  lines.each { |line| $stdout.print "#{line.chomp}\n" }
130
117
  end
@@ -10,12 +10,11 @@ module Xlogin
10
10
  def initialize
11
11
  @database = Hash.new
12
12
  @templates = Hash.new
13
- @group = nil
14
13
  end
15
14
 
16
15
  def source(*files)
17
16
  files.compact.each do |file|
18
- file = File.expand_path(file)
17
+ raise SessionError.new("Inventory file not found: #{file}") unless File.exist?(file)
19
18
  instance_eval(IO.read(file)) if File.exist?(file)
20
19
  end
21
20
  end
@@ -29,19 +28,17 @@ module Xlogin
29
28
  @database[name]
30
29
  end
31
30
 
32
- def list(name = nil)
33
- keys = @database.keys
34
- keys = keys.select { |key| key =~ /^#{name}(:|$)/ } unless name.nil? || name.to_s == 'all'
35
- @database.values_at(*keys)
31
+ def list(pattern = nil)
32
+ key, val = pattern.to_s.split(':')
33
+ key, val = 'name', (key || '*') if val.nil?
34
+ @database.values.select { |info| File.fnmatch(val, info[key.to_sym]) }
36
35
  end
37
36
 
38
37
  def source_template(*files)
39
38
  files.compact.each do |file|
40
- file = File.expand_path(file)
39
+ raise TemplateError.new("Template file not found: #{file}") unless File.exist?(file)
41
40
  name = File.basename(file, '.rb').scan(/\w+/).join('_')
42
- next unless File.exist?(file)
43
-
44
- set_template(name, IO.read(file))
41
+ set_template(name, IO.read(file)) if File.exist?(file)
45
42
  end
46
43
  end
47
44
 
@@ -59,13 +56,6 @@ module Xlogin
59
56
  @templates.keys
60
57
  end
61
58
 
62
- def group(group_name)
63
- current_group = @group
64
- @group = [current_group, group_name.to_s].compact.join(':')
65
- yield
66
- @group = current_group
67
- end
68
-
69
59
  def build(type:, uri:, **opts)
70
60
  template = get_template(type)
71
61
  template.build(uri, **opts)
@@ -82,7 +72,7 @@ module Xlogin
82
72
  super unless caller_locations.first.label == 'block in source' and args.size >= 2
83
73
 
84
74
  type = method_name.to_s.downcase
85
- name = [@group, args.shift].compact.join(':')
75
+ name = args.shift
86
76
  uri = args.shift
87
77
  opts = args.shift || {}
88
78
  set(type: type, name: name, uri: uri, **opts)
@@ -1,3 +1,3 @@
1
1
  module Xlogin
2
- VERSION = "0.7.16"
2
+ VERSION = "0.8.0"
3
3
  end
data/lib/xlogin.rb CHANGED
@@ -6,10 +6,6 @@ require 'xlogin/version'
6
6
 
7
7
  module Xlogin
8
8
 
9
- DEFAULT_INVENTORY_FILE = File.join(ENV['HOME'], '.xloginrc')
10
- DEFAULT_TEMPLATE_DIR = File.join(ENV['HOME'], '.xlogin.d')
11
- BUILTIN_TEMPLATE_FILES = Dir.glob(File.join(File.dirname(__FILE__), 'xlogin', 'templates', '*.rb'))
12
-
13
9
  class SessionError < StandardError; end
14
10
  class TemplateError < StandardError; end
15
11
  class AuthorizationError < StandardError; end
@@ -45,29 +41,29 @@ module Xlogin
45
41
  @authorized == true
46
42
  end
47
43
 
44
+ def generate_templates(dir)
45
+ FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
46
+ builtin_templates = Dir.glob(File.join(File.dirname(__FILE__), 'xlogin', 'templates', '*.rb'))
47
+ builtin_templates.each { |file| FileUtils.cp(file, DEFAULT_TEMPLATE_DIR) }
48
+ end
49
+
48
50
  private
49
- def authorize(boolean = false, &block)
51
+ def authorize(boolean = true, &block)
50
52
  @authorized = boolean == true || (block && block.call == true)
51
53
  end
52
54
 
53
- def source(source_file = nil)
54
- factory.source(source_file || DEFAULT_INVENTORY_FILE)
55
+ def source(*source_files)
56
+ factory.source(*source_files)
55
57
  end
56
58
 
57
59
  def template(*template_dirs)
58
60
  files = template_dirs.flat_map { |dir| Dir.glob(File.join(dir, '*.rb')) }
59
- load_template(*files)
61
+ load_templates(*files)
60
62
  end
61
63
  alias_method :template_dir, :template
62
64
 
63
- def load_template(*template_files)
64
- return factory.source_template(*template_files) unless template_files.empty?
65
-
66
- unless Dir.exist?(DEFAULT_TEMPLATE_DIR)
67
- FileUtils.mkdir_p(DEFAULT_TEMPLATE_DIR)
68
- BUILTIN_TEMPLATE_FILES.each { |file| FileUtils.cp(file, DEFAULT_TEMPLATE_DIR) }
69
- end
70
- template_dir(DEFAULT_TEMPLATE_DIR)
65
+ def load_templates(*template_files)
66
+ factory.source_template(*template_files)
71
67
  end
72
68
 
73
69
  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.7.16
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - haccht
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-02-15 00:00:00.000000000 Z
11
+ date: 2018-03-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: net-telnet
@@ -121,6 +121,7 @@ files:
121
121
  - ".gitignore"
122
122
  - CODE_OF_CONDUCT.md
123
123
  - Gemfile
124
+ - Gemfile.lock
124
125
  - LICENSE.txt
125
126
  - README.md
126
127
  - Rakefile