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.
@@ -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
@@ -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