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,58 @@
|
|
1
|
+
module Xlogin
|
2
|
+
module Contexts
|
3
|
+
|
4
|
+
class << self
|
5
|
+
def from_example(example)
|
6
|
+
example_group = example.example_group
|
7
|
+
|
8
|
+
node_resource = find_resource(Resources::NodeResource, example_group)
|
9
|
+
command_resource = find_resource(Resources::CommandResource, example_group)
|
10
|
+
|
11
|
+
return nil if node_resource.nil? && command_resource.nil?
|
12
|
+
return NodeContext.new(node_resource.session) if command_resource.nil?
|
13
|
+
|
14
|
+
CommandContext.new(node_resource.session, command_resource.text)
|
15
|
+
end
|
16
|
+
|
17
|
+
def find_resource(klass, example_group)
|
18
|
+
arg = example_group.metadata[:description_args][0]
|
19
|
+
return arg if arg.is_a?(klass)
|
20
|
+
|
21
|
+
parent = example_group.parent_groups[1]
|
22
|
+
find_resource(klass, parent) if parent
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class NodeContext
|
27
|
+
attr_reader :session
|
28
|
+
|
29
|
+
def initialize(session)
|
30
|
+
@session = session
|
31
|
+
end
|
32
|
+
|
33
|
+
def name
|
34
|
+
@session.name
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class CommandContext
|
39
|
+
class << self
|
40
|
+
def response_cache
|
41
|
+
@cache ||= {}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_reader :session, :command
|
46
|
+
|
47
|
+
def initialize(session, command)
|
48
|
+
@session = session
|
49
|
+
@command = command
|
50
|
+
end
|
51
|
+
|
52
|
+
def response
|
53
|
+
self.class.response_cache[[@session.name, @command]] ||= @session.cmd(@command)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Xlogin
|
2
|
+
module Resources
|
3
|
+
|
4
|
+
class NodeResource
|
5
|
+
def initialize(name)
|
6
|
+
@name = name
|
7
|
+
end
|
8
|
+
|
9
|
+
def session
|
10
|
+
@session ||= Xlogin.get(@name)
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
"Node '#{session.name}'"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class CommandResource
|
19
|
+
attr_reader :text
|
20
|
+
|
21
|
+
def initialize(text)
|
22
|
+
@text = text
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
"with command '#{text}'"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
module Xlogin
|
4
|
+
module Session
|
5
|
+
|
6
|
+
attr_reader :name
|
7
|
+
|
8
|
+
def configure(**opts)
|
9
|
+
@name = opts[:node]
|
10
|
+
@node = opts[:node]
|
11
|
+
@port = opts[:port]
|
12
|
+
@userinfo = opts[:userinfo].split(':')
|
13
|
+
raise Xlogin::GeneralError.new('Argument error.') unless @node && @port
|
14
|
+
|
15
|
+
@methods = opts[:methods] || {}
|
16
|
+
@prompts = opts[:prompts] || [[/[$%#>] ?\z/n, nil]]
|
17
|
+
@timeout = opts[:timeout] || 60
|
18
|
+
|
19
|
+
@loglist = [opts[:log]].flatten.compact
|
20
|
+
@logger = update_logger
|
21
|
+
end
|
22
|
+
|
23
|
+
def enable_log(out = $stdout)
|
24
|
+
enabled = @loglist.include?(out)
|
25
|
+
unless enabled
|
26
|
+
@loglist.push(out)
|
27
|
+
update_logger
|
28
|
+
end
|
29
|
+
|
30
|
+
if block_given?
|
31
|
+
yield
|
32
|
+
disable_log(out) unless enabled
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def disable_log(out = $stdout)
|
37
|
+
enabled = @loglist.include?(out)
|
38
|
+
if enabled
|
39
|
+
@loglist.delete(out)
|
40
|
+
update_logger
|
41
|
+
end
|
42
|
+
|
43
|
+
if block_given?
|
44
|
+
yield
|
45
|
+
enable_log(out) if enabled
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
def update_logger
|
51
|
+
loglist = [@loglist].flatten.uniq.map do |log|
|
52
|
+
case log
|
53
|
+
when String
|
54
|
+
log = File.open(log, 'a+')
|
55
|
+
log.binmode
|
56
|
+
log.sync = true
|
57
|
+
log
|
58
|
+
when IO, StringIO
|
59
|
+
log
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
@logger = lambda { |c| loglist.compact.each { |o| o.syswrite c } }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/xlogin/ssh.rb
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'net/ssh'
|
2
|
+
require 'xlogin/session'
|
3
|
+
|
4
|
+
module Xlogin
|
5
|
+
class Ssh
|
6
|
+
include Session
|
7
|
+
|
8
|
+
def initialize(**opts)
|
9
|
+
configure(opts.merge(port: opts[:port] || 22))
|
10
|
+
|
11
|
+
begin
|
12
|
+
username, password = *@userinfo
|
13
|
+
|
14
|
+
@ssh = Net::SSH.start(
|
15
|
+
@node,
|
16
|
+
username,
|
17
|
+
:port => @port,
|
18
|
+
:timeout => @timeout,
|
19
|
+
:password => password,
|
20
|
+
)
|
21
|
+
rescue TimeoutError
|
22
|
+
raise TimeoutError, 'timed out while opening a connection to the host'
|
23
|
+
rescue
|
24
|
+
raise
|
25
|
+
end
|
26
|
+
|
27
|
+
@buf = ''
|
28
|
+
@eof = false
|
29
|
+
@channel = nil
|
30
|
+
|
31
|
+
@ssh.open_channel do |channel|
|
32
|
+
channel.on_data { |ch, data| @buf << data }
|
33
|
+
channel.on_close { @eof = true }
|
34
|
+
|
35
|
+
channel.request_pty do |ch, success|
|
36
|
+
raise 'Failed to open ssh pty' unless success
|
37
|
+
end
|
38
|
+
|
39
|
+
channel.send_channel_request('shell') do |ch, success|
|
40
|
+
raise 'Failed to open ssh shell' unless success
|
41
|
+
|
42
|
+
@channel = ch
|
43
|
+
waitfor
|
44
|
+
return
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
@ssh.loop
|
49
|
+
end
|
50
|
+
|
51
|
+
def close
|
52
|
+
@channel.close if @channel
|
53
|
+
@ssh.close if @ssh
|
54
|
+
end
|
55
|
+
|
56
|
+
def waitfor(opts = nil)
|
57
|
+
time_out = @timeout
|
58
|
+
waittime = @timeout
|
59
|
+
|
60
|
+
case opts
|
61
|
+
when Hash
|
62
|
+
prompt = if opts.has_key?('Match')
|
63
|
+
opts['Match']
|
64
|
+
elsif opts.has_key?('Prompt')
|
65
|
+
opts['Prompt']
|
66
|
+
elsif opts.has_key?('String')
|
67
|
+
Regexp.new( Regexp.quote(opts['String']) )
|
68
|
+
end
|
69
|
+
time_out = opts['Timeout'] if opts.has_key?('Timeout')
|
70
|
+
waittime = opts['Waittime'] if opts.has_key?('Waittime')
|
71
|
+
else
|
72
|
+
prompt = opts || Regexp.union(*@prompts.map(&:first))
|
73
|
+
end
|
74
|
+
|
75
|
+
buf = ''
|
76
|
+
rest = ''
|
77
|
+
line = ''
|
78
|
+
sock = @ssh.transport.socket
|
79
|
+
|
80
|
+
until sock.available == 0 && @buf == "" && prompt != line && (@eof || (!sock.closed? && !IO::select([sock], nil, nil, waittime)))
|
81
|
+
if sock.available == 0 && @buf == "" && prompt !~ line && !IO::select([sock], nil, nil, time_out)
|
82
|
+
raise Net::ReadTimeout, 'timed out while waiting for more data'
|
83
|
+
end
|
84
|
+
|
85
|
+
process_connection
|
86
|
+
if @buf != ''
|
87
|
+
buf = rest + @buf
|
88
|
+
rest = ''
|
89
|
+
|
90
|
+
if pt = buf.rindex(/\r\z/no)
|
91
|
+
buf = buf[0...pt]
|
92
|
+
rest = buf[pt..-1]
|
93
|
+
end
|
94
|
+
|
95
|
+
@buf = ''
|
96
|
+
line += buf
|
97
|
+
@logger.call(buf)
|
98
|
+
elsif @eof
|
99
|
+
break
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
_, process = @prompts.find { |r, p| r =~ line && p }
|
104
|
+
if process
|
105
|
+
instance_eval(&process)
|
106
|
+
line += waitfor(opts)
|
107
|
+
end
|
108
|
+
line
|
109
|
+
end
|
110
|
+
|
111
|
+
def print(string)
|
112
|
+
@channel.send_data(string)
|
113
|
+
process_connection
|
114
|
+
end
|
115
|
+
|
116
|
+
def puts(string)
|
117
|
+
print(string + "\n")
|
118
|
+
end
|
119
|
+
|
120
|
+
def cmd(opts)
|
121
|
+
match = Regexp.union(*@prompts.map(&:first))
|
122
|
+
time_out = @timeout
|
123
|
+
|
124
|
+
if opts.kind_of?(Hash)
|
125
|
+
string = opts['String']
|
126
|
+
match = opts['Match'] if opts.has_key?('Match')
|
127
|
+
time_out = opts['Timeout'] if opts.has_key?('Timeout')
|
128
|
+
else
|
129
|
+
string = opts
|
130
|
+
end
|
131
|
+
|
132
|
+
puts(string)
|
133
|
+
waitfor('Prompt' => match, 'Timeout' => time_out)
|
134
|
+
end
|
135
|
+
|
136
|
+
def interact!
|
137
|
+
raise 'Not implemented'
|
138
|
+
end
|
139
|
+
|
140
|
+
private
|
141
|
+
def process_connection
|
142
|
+
begin
|
143
|
+
@channel.connection.process(0)
|
144
|
+
rescue IOError
|
145
|
+
@eof = true
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'io/console'
|
2
|
+
require 'net/telnet'
|
3
|
+
require 'xlogin/session'
|
4
|
+
|
5
|
+
module Xlogin
|
6
|
+
class Telnet < Net::Telnet
|
7
|
+
|
8
|
+
include Session
|
9
|
+
|
10
|
+
def initialize(**opts)
|
11
|
+
configure(opts.merge(port: opts[:port] || 23))
|
12
|
+
|
13
|
+
super(
|
14
|
+
'Host' => @node,
|
15
|
+
'Port' => @port,
|
16
|
+
'Timeout' => @timeout,
|
17
|
+
'Prompt' => Regexp.union(*@prompts.map(&:first))
|
18
|
+
)
|
19
|
+
|
20
|
+
login(*@userinfo) if respond_to?(:login) && !@userinfo.empty?
|
21
|
+
end
|
22
|
+
|
23
|
+
def waitfor(*expect)
|
24
|
+
if expect.compact.empty?
|
25
|
+
super(Regexp.union(*@prompts.map(&:first)), &@logger)
|
26
|
+
else
|
27
|
+
line = super(*expect, &@logger)
|
28
|
+
_, process = @prompts.find { |r, p| r =~ line && p }
|
29
|
+
if process
|
30
|
+
instance_eval(&process)
|
31
|
+
line += waitfor(*expect)
|
32
|
+
end
|
33
|
+
line
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def interact!
|
38
|
+
$stdin.raw!
|
39
|
+
disable_log($stdout)
|
40
|
+
|
41
|
+
loop do
|
42
|
+
rs, _ = IO.select([$stdin, @sock])
|
43
|
+
rs.each do |fh|
|
44
|
+
case fh
|
45
|
+
when $stdin
|
46
|
+
bs = ''
|
47
|
+
begin
|
48
|
+
bs = fh.read_nonblock(1)
|
49
|
+
if bs == "\e"
|
50
|
+
bs << fh.read_nonblock(3)
|
51
|
+
bs << fh.read_nonblock(2)
|
52
|
+
end
|
53
|
+
rescue IO::WaitReadable
|
54
|
+
end
|
55
|
+
|
56
|
+
raise EOFError if bs == "\u001D" # <Ctrl-]> for quit
|
57
|
+
@sock.syswrite(bs)
|
58
|
+
when @sock
|
59
|
+
begin
|
60
|
+
bs = fh.readpartial(1024)
|
61
|
+
$stdout.syswrite(bs)
|
62
|
+
@logger.call(bs)
|
63
|
+
rescue Errno::EAGAIN
|
64
|
+
retry
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
rescue EOFError
|
70
|
+
$stdout.puts "\r\n", "Conneciton closed."
|
71
|
+
self.close
|
72
|
+
ensure
|
73
|
+
$stdin.cooked!
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
data/xlogin.gemspec
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'xlogin/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "xlogin"
|
8
|
+
spec.version = Xlogin::VERSION
|
9
|
+
spec.authors = ["haccht"]
|
10
|
+
spec.email = ["haccht@users.noreply.github.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{rancid clogin alternative}
|
13
|
+
spec.description = %q{login to any devices with ease.}
|
14
|
+
spec.homepage = "https://github.com/haccht/xlogin"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
18
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
19
|
+
if spec.respond_to?(:metadata)
|
20
|
+
spec.metadata['allowed_push_host'] = "https://rubygems.org"
|
21
|
+
else
|
22
|
+
raise "RubyGems 2.0 or newer is required to protect against " \
|
23
|
+
"public gem pushes."
|
24
|
+
end
|
25
|
+
|
26
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
27
|
+
f.match(%r{^(test|spec|features)/})
|
28
|
+
end
|
29
|
+
spec.bindir = "exe"
|
30
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
31
|
+
spec.require_paths = ["lib"]
|
32
|
+
|
33
|
+
spec.add_development_dependency "bundler", "~> 1.13"
|
34
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
35
|
+
spec.add_development_dependency "net-ssh-gateway"
|
36
|
+
end
|
metadata
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: xlogin
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- haccht
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-04-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.13'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.13'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: net-ssh-gateway
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: login to any devices with ease.
|
56
|
+
email:
|
57
|
+
- haccht@users.noreply.github.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- CODE_OF_CONDUCT.md
|
64
|
+
- Gemfile
|
65
|
+
- LICENSE.txt
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- bin/cmd_exec
|
69
|
+
- bin/console
|
70
|
+
- bin/setup
|
71
|
+
- bin/xlogin
|
72
|
+
- lib/xlogin.rb
|
73
|
+
- lib/xlogin/delegator.rb
|
74
|
+
- lib/xlogin/firmware.rb
|
75
|
+
- lib/xlogin/firmware_factory.rb
|
76
|
+
- lib/xlogin/firmwares/vyos.rb
|
77
|
+
- lib/xlogin/gateway.rb
|
78
|
+
- lib/xlogin/rake_task.rb
|
79
|
+
- lib/xlogin/rspec.rb
|
80
|
+
- lib/xlogin/rspec/context.rb
|
81
|
+
- lib/xlogin/rspec/resource.rb
|
82
|
+
- lib/xlogin/session.rb
|
83
|
+
- lib/xlogin/ssh.rb
|
84
|
+
- lib/xlogin/telnet.rb
|
85
|
+
- lib/xlogin/version.rb
|
86
|
+
- xlogin.gemspec
|
87
|
+
homepage: https://github.com/haccht/xlogin
|
88
|
+
licenses:
|
89
|
+
- MIT
|
90
|
+
metadata:
|
91
|
+
allowed_push_host: https://rubygems.org
|
92
|
+
post_install_message:
|
93
|
+
rdoc_options: []
|
94
|
+
require_paths:
|
95
|
+
- lib
|
96
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
requirements: []
|
107
|
+
rubyforge_project:
|
108
|
+
rubygems_version: 2.4.5.2
|
109
|
+
signing_key:
|
110
|
+
specification_version: 4
|
111
|
+
summary: rancid clogin alternative
|
112
|
+
test_files: []
|