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,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
@@ -0,0 +1,3 @@
1
+ module Xlogin
2
+ VERSION = "0.1.0"
3
+ 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: []