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 +4 -4
- data/Gemfile.lock +32 -0
- data/README.md +17 -5
- data/lib/xlogin/cli.rb +61 -74
- data/lib/xlogin/factory.rb +8 -18
- data/lib/xlogin/version.rb +1 -1
- data/lib/xlogin.rb +12 -16
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1d9a1b26c54a0a1729282e93dca1abcc8dfc1336
|
4
|
+
data.tar.gz: 41c331ddf6bf109f0ef85b79d79f0f841dbb787c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
-
|
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
|
31
|
-
|
32
|
-
|
33
|
-
parser.on('-
|
34
|
-
|
35
|
-
parser.on('-
|
36
|
-
parser.on('-
|
37
|
-
|
38
|
-
parser.on('-
|
39
|
-
|
40
|
-
parser.on('-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
54
|
-
|
55
|
-
|
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
|
-
|
61
|
-
hostlist
|
62
|
-
|
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
|
70
|
-
|
71
|
-
|
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
|
-
|
77
|
-
|
78
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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 <<
|
115
|
-
loggers <<
|
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
|
106
|
+
session.enable if config.enable && hostinfo[:enable]
|
120
107
|
|
121
108
|
block.call(session)
|
122
109
|
rescue => e
|
123
|
-
lines = (config.
|
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.
|
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
|
data/lib/xlogin/factory.rb
CHANGED
@@ -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
|
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(
|
33
|
-
|
34
|
-
|
35
|
-
@database.
|
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
|
39
|
+
raise TemplateError.new("Template file not found: #{file}") unless File.exist?(file)
|
41
40
|
name = File.basename(file, '.rb').scan(/\w+/).join('_')
|
42
|
-
|
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 =
|
75
|
+
name = args.shift
|
86
76
|
uri = args.shift
|
87
77
|
opts = args.shift || {}
|
88
78
|
set(type: type, name: name, uri: uri, **opts)
|
data/lib/xlogin/version.rb
CHANGED
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 =
|
51
|
+
def authorize(boolean = true, &block)
|
50
52
|
@authorized = boolean == true || (block && block.call == true)
|
51
53
|
end
|
52
54
|
|
53
|
-
def source(
|
54
|
-
factory.source(
|
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
|
-
|
61
|
+
load_templates(*files)
|
60
62
|
end
|
61
63
|
alias_method :template_dir, :template
|
62
64
|
|
63
|
-
def
|
64
|
-
|
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.
|
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-
|
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
|