xlogin 0.1.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 +7 -0
- data/.gitignore +58 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +49 -0
- data/Rakefile +2 -0
- data/bin/cmd_exec +122 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/bin/xlogin +67 -0
- data/lib/xlogin.rb +50 -0
- data/lib/xlogin/delegator.rb +38 -0
- data/lib/xlogin/firmware.rb +83 -0
- data/lib/xlogin/firmware_factory.rb +76 -0
- data/lib/xlogin/firmwares/vyos.rb +27 -0
- data/lib/xlogin/gateway.rb +31 -0
- data/lib/xlogin/rake_task.rb +184 -0
- data/lib/xlogin/rspec.rb +80 -0
- data/lib/xlogin/rspec/context.rb +58 -0
- data/lib/xlogin/rspec/resource.rb +31 -0
- data/lib/xlogin/session.rb +66 -0
- data/lib/xlogin/ssh.rb +150 -0
- data/lib/xlogin/telnet.rb +77 -0
- data/lib/xlogin/version.rb +3 -0
- data/xlogin.gemspec +36 -0
- metadata +112 -0
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module Xlogin
|
4
|
+
class Firmware
|
5
|
+
|
6
|
+
module FirmwareDelegator
|
7
|
+
def run(uri, opts = {})
|
8
|
+
if hostname = opts.delete(:delegate)
|
9
|
+
delegatee = FirmwareFactory.new.find(hostname)
|
10
|
+
firmware = FirmwareFactory[delegatee[:type]]
|
11
|
+
|
12
|
+
firmware.on_exec do |args|
|
13
|
+
if args['String'].strip =~ /^kill-session(?:\(([\s\w]+)\))?$/
|
14
|
+
puts($1.to_s)
|
15
|
+
close
|
16
|
+
else
|
17
|
+
instance_exec(args, &@on_exec) if @on_exec
|
18
|
+
do_cmd(args)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
login_method = firmware.instance_eval { @methods[:login] }
|
23
|
+
firmware.bind(:login, &@methods[:login])
|
24
|
+
firmware.bind(:delegate, &@methods[:delegate])
|
25
|
+
|
26
|
+
session = firmware.run(uri, opts.merge(delegatee[:opts]))
|
27
|
+
session.delegate(URI(delegatee[:uri]), &login_method)
|
28
|
+
session
|
29
|
+
else
|
30
|
+
session = super(uri, opts)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
prepend FirmwareDelegator
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'xlogin/telnet'
|
3
|
+
|
4
|
+
module Xlogin
|
5
|
+
class Firmware
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@timeout = 5
|
9
|
+
@on_exec = nil
|
10
|
+
@prompts = Array.new
|
11
|
+
@methods = Hash.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def timeout(val)
|
15
|
+
@timeout = val.to_i
|
16
|
+
end
|
17
|
+
|
18
|
+
def on_exec(&block)
|
19
|
+
@on_exec = block
|
20
|
+
end
|
21
|
+
|
22
|
+
def prompt(expect, &block)
|
23
|
+
@prompts << [Regexp.new(expect.to_s), block]
|
24
|
+
end
|
25
|
+
|
26
|
+
def bind(name, &block)
|
27
|
+
@methods[name] = block
|
28
|
+
end
|
29
|
+
|
30
|
+
def run(uri, opts = {})
|
31
|
+
uri = URI(uri.to_s)
|
32
|
+
klass = Class.new(Xlogin.const_get(uri.scheme.capitalize))
|
33
|
+
klass.class_exec(@methods) do |methods|
|
34
|
+
methods.each { |m, _| undef_method(m) if method_defined?(m) }
|
35
|
+
end
|
36
|
+
|
37
|
+
@methods.each do |name, block|
|
38
|
+
klass.class_exec(name, block) do |name, block|
|
39
|
+
undef_method(name) if respond_to?(name)
|
40
|
+
define_method(name, &block)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
if @on_exec
|
45
|
+
klass.class_exec(@on_exec) do |on_exec|
|
46
|
+
alias_method :do_cmd, :cmd
|
47
|
+
define_method(:cmd) do |args|
|
48
|
+
args = {'String' => args.to_s} unless args.kind_of?(Hash)
|
49
|
+
instance_exec(args, &on_exec)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
if grant = opts.delete(:grant)
|
55
|
+
open, password, close = grant.split(':')
|
56
|
+
klass.class_exec(open, password, close) do |open, password, close|
|
57
|
+
alias_method "original_#{open}".to_sym, open.to_sym
|
58
|
+
define_method(open) do |arg = password, &block|
|
59
|
+
send("original_#{open}", arg)
|
60
|
+
if block
|
61
|
+
resp = block.call
|
62
|
+
cmd(close)
|
63
|
+
resp
|
64
|
+
end
|
65
|
+
end
|
66
|
+
alias_method :enable, open
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
klass.new(
|
71
|
+
{
|
72
|
+
node: uri.host,
|
73
|
+
port: uri.port,
|
74
|
+
userinfo: uri.userinfo,
|
75
|
+
timeout: @timeout,
|
76
|
+
prompts: @prompts,
|
77
|
+
methods: @methods,
|
78
|
+
}.merge(opts)
|
79
|
+
)
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'stringio'
|
3
|
+
require 'xlogin/firmware'
|
4
|
+
|
5
|
+
module Xlogin
|
6
|
+
class FirmwareFactory
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def [](name)
|
10
|
+
firmwares[name.to_s.downcase]
|
11
|
+
end
|
12
|
+
|
13
|
+
def register(name, firmware)
|
14
|
+
firmwares[name.to_s.downcase] = firmware
|
15
|
+
end
|
16
|
+
|
17
|
+
def register_file(file)
|
18
|
+
require file if file =~ /.rb$/
|
19
|
+
end
|
20
|
+
|
21
|
+
def register_dir(dir)
|
22
|
+
Dir.entries(dir).each do |file|
|
23
|
+
register_file(File.join(dir, file))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
def firmwares
|
29
|
+
@firmwares ||= Hash.new
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize
|
34
|
+
@database = Hash.new
|
35
|
+
|
36
|
+
SourceDir.compact.each do |dir|
|
37
|
+
source(File.join(dir, '.xloginrc'))
|
38
|
+
source(File.join(dir, '_xloginrc'))
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def source(db_file)
|
43
|
+
return unless File.exist?(db_file)
|
44
|
+
|
45
|
+
content = IO.read(db_file)
|
46
|
+
instance_eval(content)
|
47
|
+
end
|
48
|
+
|
49
|
+
def set(type, name, uri, opts = {})
|
50
|
+
@database[name] = { type: type, uri: uri, opts: opts }
|
51
|
+
end
|
52
|
+
|
53
|
+
def list
|
54
|
+
@database.map { |nodename, args| args.merge(name: nodename) }
|
55
|
+
end
|
56
|
+
|
57
|
+
def build(item, args = {})
|
58
|
+
item = item.kind_of?(Hash) ? item : @database[item]
|
59
|
+
item_uri = item[:uri]
|
60
|
+
firmware = Xlogin::FirmwareFactory[item[:type]]
|
61
|
+
raise Xlogin::GeneralError.new("Invalid target - #{nodename}") unless item && item_uri && firmware
|
62
|
+
|
63
|
+
opts = item[:opts] || {}
|
64
|
+
opts = opts.merge(args).reduce({}) { |a, (k, v)| a.merge(k.to_s.downcase.to_sym => v) }
|
65
|
+
firmware.run(item_uri, opts)
|
66
|
+
end
|
67
|
+
|
68
|
+
def method_missing(name, *args, &block)
|
69
|
+
firmware = Xlogin::FirmwareFactory[name]
|
70
|
+
super unless firmware && args.size >= 2
|
71
|
+
|
72
|
+
set(name, *args)
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
Xlogin.configure :vyos do |os|
|
2
|
+
os.prompt(/[$#] (?:\e\[K)?\z/n)
|
3
|
+
|
4
|
+
os.bind(:login) do |username, password|
|
5
|
+
waitfor(/login:\s/) && puts(username)
|
6
|
+
waitfor(/Password:\s/) && puts(password)
|
7
|
+
waitfor
|
8
|
+
end
|
9
|
+
|
10
|
+
os.bind(:config) do
|
11
|
+
begin
|
12
|
+
cmd('show configuration | no-more')
|
13
|
+
ensure
|
14
|
+
cmd('exit')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
os.bind(:save) do
|
19
|
+
begin
|
20
|
+
cmd('save')
|
21
|
+
ensure
|
22
|
+
cmd('exit')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
Xlogin.alias :edgeos, :vyos
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'net/ssh/gateway'
|
3
|
+
|
4
|
+
module Xlogin
|
5
|
+
module Session
|
6
|
+
|
7
|
+
alias_method :original_configure, :configure
|
8
|
+
def configure(**opts)
|
9
|
+
original_configure(**opts)
|
10
|
+
|
11
|
+
if uri = opts[:via]
|
12
|
+
gateway = URI(uri)
|
13
|
+
username, password = *gateway.userinfo.split(':')
|
14
|
+
|
15
|
+
case gateway.scheme
|
16
|
+
when 'ssh'
|
17
|
+
@gateway = Net::SSH::Gateway.new(
|
18
|
+
gateway.host,
|
19
|
+
username,
|
20
|
+
password: password,
|
21
|
+
port: gateway.port || 22
|
22
|
+
)
|
23
|
+
|
24
|
+
@port = @gateway.open(@node, @port)
|
25
|
+
@node = '127.0.0.1'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/tasklib'
|
3
|
+
require 'readline'
|
4
|
+
require 'thread'
|
5
|
+
|
6
|
+
module Xlogin
|
7
|
+
class RakeTask < Rake::TaskLib
|
8
|
+
|
9
|
+
class << self
|
10
|
+
include Rake::DSL
|
11
|
+
|
12
|
+
def bulk(names, &block)
|
13
|
+
names = names.map(&:strip).grep(/^\s*[^#]/)
|
14
|
+
|
15
|
+
namecount = names.uniq.inject({}) { |a, e| a.merge(e => names.count(e)) }
|
16
|
+
duplicate = namecount.keys.select { |name| namecount[name] > 1 }
|
17
|
+
raise Xlogin::GeneralError.new("Duplicate hosts found - #{duplicate.join(', ')}") unless duplicate.empty?
|
18
|
+
|
19
|
+
current_namespace do
|
20
|
+
description = Rake.application.last_description || "Run '#{RakeTask.current_namespace}'"
|
21
|
+
|
22
|
+
desc description
|
23
|
+
task all: names
|
24
|
+
|
25
|
+
names.each do |name|
|
26
|
+
desc "#{description} (#{name})"
|
27
|
+
RakeTask.new(name, &block)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def mutex
|
33
|
+
@mutex ||= Mutex.new
|
34
|
+
end
|
35
|
+
|
36
|
+
def current_namespace
|
37
|
+
path = Rake.application.current_scope.path
|
38
|
+
|
39
|
+
if path.empty?
|
40
|
+
path = self.name.split('::').first.downcase
|
41
|
+
namespace(path) { yield } if block_given?
|
42
|
+
else
|
43
|
+
yield if block_given?
|
44
|
+
end
|
45
|
+
|
46
|
+
path
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
attr_reader :name
|
52
|
+
attr_accessor :fail_on_error
|
53
|
+
attr_accessor :silent
|
54
|
+
attr_accessor :lockfile
|
55
|
+
attr_accessor :logfile
|
56
|
+
attr_accessor :timeout
|
57
|
+
attr_accessor :uncomment
|
58
|
+
|
59
|
+
def initialize(name, *args)
|
60
|
+
@name = name
|
61
|
+
@fail_on_error = true
|
62
|
+
@silent = false
|
63
|
+
@lockfile = nil
|
64
|
+
@logfile = nil
|
65
|
+
@timeout = nil
|
66
|
+
@uncomment = false
|
67
|
+
|
68
|
+
@session = nil
|
69
|
+
@taskrunner = nil
|
70
|
+
|
71
|
+
yield(self) if block_given?
|
72
|
+
define
|
73
|
+
end
|
74
|
+
|
75
|
+
def start(&block)
|
76
|
+
@taskrunner = block
|
77
|
+
end
|
78
|
+
|
79
|
+
def safe_puts(*messages, io: $stdout)
|
80
|
+
RakeTask.mutex.synchronize do
|
81
|
+
messages.flat_map { |message| message.to_s.lines }.each do |line|
|
82
|
+
line.gsub!("\r", "")
|
83
|
+
io.puts (Rake.application.options.always_multitask)? "#{name}\t#{line}" : line
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
def define
|
90
|
+
RakeTask.current_namespace do
|
91
|
+
description = Rake.application.last_description || "Run '#{RakeTask.current_namespace}'"
|
92
|
+
|
93
|
+
desc description
|
94
|
+
Rake.application.last_description = nil if uncomment
|
95
|
+
|
96
|
+
if lockfile
|
97
|
+
task(name => lockfile)
|
98
|
+
file(lockfile) do
|
99
|
+
run_task
|
100
|
+
|
101
|
+
mkdir_p(File.dirname(lockfile), verbose: Rake.application.options.trace)
|
102
|
+
touch(lockfile, verbose: Rake.application.options.trace)
|
103
|
+
end
|
104
|
+
else
|
105
|
+
task(name) do
|
106
|
+
run_task
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
[logfile, lockfile].each do |file|
|
111
|
+
next unless file
|
112
|
+
|
113
|
+
desc 'Remove any temporary products'
|
114
|
+
task 'clean' do
|
115
|
+
rm(file, verbose: true) if File.exist?(file)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def run_task
|
122
|
+
loggers = []
|
123
|
+
loggers << $stdout unless silent || Rake.application.options.silent || Rake.application.options.always_multitask
|
124
|
+
|
125
|
+
if logfile
|
126
|
+
mkdir_p(File.dirname(logfile), verbose: Rake.application.options.trace)
|
127
|
+
loggers << logfile if logfile
|
128
|
+
end
|
129
|
+
|
130
|
+
xlogin_opts = Hash.new
|
131
|
+
xlogin_opts[:log] = loggers unless loggers.empty?
|
132
|
+
xlogin_opts[:timeout] = timeout if timeout
|
133
|
+
|
134
|
+
@session = Xlogin.get(name, xlogin_opts)
|
135
|
+
@session.extend(SessionExt)
|
136
|
+
|
137
|
+
%i( safe_puts fail_on_error ).each do |name|
|
138
|
+
method_proc = method(name)
|
139
|
+
@session.define_singleton_method(name) { |*args| method_proc.call(*args) }
|
140
|
+
end
|
141
|
+
|
142
|
+
@taskrunner.call(@session) if @taskrunner
|
143
|
+
end
|
144
|
+
|
145
|
+
module SessionExt
|
146
|
+
def cmd(*args)
|
147
|
+
super(*args).tap do |message|
|
148
|
+
safe_puts(message, io: $stdout) unless Rake.application.options.silent || !Rake.application.options.always_multitask
|
149
|
+
break yield(message) if block_given?
|
150
|
+
end
|
151
|
+
rescue => e
|
152
|
+
raise e if fail_on_error
|
153
|
+
safe_puts("\n#{e}", io: $stderr)
|
154
|
+
cmd('')
|
155
|
+
end
|
156
|
+
|
157
|
+
def readline(command)
|
158
|
+
prompt = StringIO.new
|
159
|
+
safe_puts(cmd('').lines.last, io: prompt)
|
160
|
+
|
161
|
+
my_command = RakeTask.mutex.synchronize do
|
162
|
+
Readline.pre_input_hook = lambda do
|
163
|
+
Readline.insert_text(command)
|
164
|
+
Readline.redisplay
|
165
|
+
end
|
166
|
+
|
167
|
+
Readline.readline("\e[E\e[K#{prompt.string.chomp}", true)
|
168
|
+
end
|
169
|
+
|
170
|
+
case my_command.strip
|
171
|
+
when /^\s*#/, ''
|
172
|
+
# do nothing and skip this command
|
173
|
+
when command.strip
|
174
|
+
cmd(my_command)
|
175
|
+
else
|
176
|
+
cmd(my_command)
|
177
|
+
# retry thid command
|
178
|
+
readline(command)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
184
|
+
end
|
data/lib/xlogin/rspec.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
require 'xlogin/rspec/context'
|
5
|
+
require 'xlogin/rspec/resource'
|
6
|
+
|
7
|
+
module Xlogin
|
8
|
+
|
9
|
+
module RSpecResourceHelper
|
10
|
+
def node(name)
|
11
|
+
Resources::NodeResource.new(name)
|
12
|
+
end
|
13
|
+
|
14
|
+
def command(str)
|
15
|
+
Resources::CommandResource.new(str)
|
16
|
+
end
|
17
|
+
|
18
|
+
def backend
|
19
|
+
unless @backend
|
20
|
+
context = Xlogin::Contexts.from_example(self)
|
21
|
+
@backend = context.session if context && context.respond_to?(:session)
|
22
|
+
end
|
23
|
+
@backend
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
module RSpecHelper
|
28
|
+
def current_context
|
29
|
+
@context
|
30
|
+
end
|
31
|
+
|
32
|
+
def method_missing(name, *args, &block)
|
33
|
+
if current_context.respond_to?(name)
|
34
|
+
current_context.send(name, *args, &block)
|
35
|
+
else
|
36
|
+
super(name, *args, &block)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
include Xlogin::RSpecResourceHelper
|
45
|
+
module RSpec
|
46
|
+
Matchers.define :match do |expected|
|
47
|
+
match do |actual|
|
48
|
+
response = actual.to_s.lines.slice(1..-1).join
|
49
|
+
expected =~ response
|
50
|
+
end
|
51
|
+
|
52
|
+
failure_message do |actual|
|
53
|
+
message = StringIO.new
|
54
|
+
message.puts "Expect response to match #{expected.inspect}"
|
55
|
+
message.puts "Result:"
|
56
|
+
message.puts actual.to_s.lines.slice(1..-2).map { |line| " +#{line}" }
|
57
|
+
message.string
|
58
|
+
end
|
59
|
+
|
60
|
+
failure_message_when_negated do |actual|
|
61
|
+
message = StringIO.new
|
62
|
+
message.puts "Expect response not to match #{expected.inspect}"
|
63
|
+
message.puts "Result:"
|
64
|
+
message.puts actual.to_s.lines.slice(1..-2).map { |line| " +#{line}" }
|
65
|
+
message.string
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
configure do |config|
|
70
|
+
config.include Xlogin::RSpecHelper
|
71
|
+
|
72
|
+
config.before(:all) do
|
73
|
+
@context = Xlogin::Contexts.from_example(self.class)
|
74
|
+
end
|
75
|
+
|
76
|
+
config.before(:each) do
|
77
|
+
@context = Xlogin::Contexts.from_example(RSpec.current_example)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|