xssh 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,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d8f00bf065bb847338cd02ef7580cd737de4ffe01dbbbb72d4cc8dde8738699a
4
+ data.tar.gz: 7606a3ce9ae3d4beb07a25a114e180f5dfd5fdd074ae11129dfd74bccba735bb
5
+ SHA512:
6
+ metadata.gz: db7cfff9fae3731aaee8620e778930559e82ccf18eb388616803f9538f8da9f63711dfd8f90a7f96402936fac83bd37cc229bf10944197f7c5d23ad2814c5335
7
+ data.tar.gz: 2aa211dc8d3151b96e7b029bbbc35c03cf6273f3eadc0d69ad8441b45ae238d8784c8f6e300b3fc4640d468b8cd75069e9ef797f602d56942a339a724bd9efec
@@ -0,0 +1,71 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ .vagrant
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /log/
11
+
12
+ ### https://raw.github.com/github/gitignore/6c87d249af5f2b3f8ab65ae0a2648682ee4e8a2d/Ruby.gitignore
13
+
14
+ *.gem
15
+ *.rbc
16
+ /.config
17
+ /coverage/
18
+ /InstalledFiles
19
+ /pkg/
20
+ /spec/reports/
21
+ /spec/examples.txt
22
+ /test/tmp/
23
+ /test/version_tmp/
24
+ /tmp/
25
+
26
+ # Used by dotenv library to load environment variables.
27
+ # .env
28
+
29
+ # Ignore Byebug command history file.
30
+ .byebug_history
31
+
32
+ ## Specific to RubyMotion:
33
+ .dat*
34
+ .repl_history
35
+ build/
36
+ *.bridgesupport
37
+ build-iPhoneOS/
38
+ build-iPhoneSimulator/
39
+
40
+ ## Specific to RubyMotion (use of CocoaPods):
41
+ #
42
+ # We recommend against adding the Pods directory to your .gitignore. However
43
+ # you should judge for yourself, the pros and cons are mentioned at:
44
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
45
+ #
46
+ # vendor/Pods/
47
+
48
+ ## Documentation cache and generated files:
49
+ /.yardoc/
50
+ /_yardoc/
51
+ /doc/
52
+ /rdoc/
53
+
54
+ ## Environment normalization:
55
+ /.bundle/
56
+ /vendor/bundle
57
+ /lib/bundler/man/
58
+
59
+ # for a library or gem, you might want to ignore these files since the code is
60
+ # intended to run in multiple environments; otherwise, check them in:
61
+ # Gemfile.lock
62
+ # .ruby-version
63
+ # .ruby-gemset
64
+
65
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
66
+ .rvmrc
67
+
68
+ # Used by RuboCop. Remote config files pulled in from inherit_from directive.
69
+ # .rubocop-https?--*
70
+
71
+
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in xssh.gemspec
4
+ gemspec
5
+
6
+ gem "rake", "~> 12.0"
@@ -0,0 +1,77 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ xssh (0.1.0)
5
+ bcrypt_pbkdf
6
+ ed25519
7
+ net-ssh
8
+ net-ssh-gateway
9
+
10
+ GEM
11
+ remote: https://rubygems.org/
12
+ specs:
13
+ ast (2.4.1)
14
+ backport (1.1.2)
15
+ bcrypt_pbkdf (1.0.1)
16
+ benchmark (0.1.0)
17
+ e2mmap (0.1.0)
18
+ ed25519 (1.2.4)
19
+ jaro_winkler (1.5.4)
20
+ maruku (0.7.3)
21
+ mini_portile2 (2.4.0)
22
+ net-ssh (6.1.0)
23
+ net-ssh-gateway (2.0.0)
24
+ net-ssh (>= 4.0.0)
25
+ nokogiri (1.10.10)
26
+ mini_portile2 (~> 2.4.0)
27
+ parallel (1.19.2)
28
+ parser (2.7.2.0)
29
+ ast (~> 2.4.1)
30
+ rainbow (3.0.0)
31
+ rake (12.3.3)
32
+ regexp_parser (1.8.1)
33
+ reverse_markdown (2.0.0)
34
+ nokogiri
35
+ rexml (3.2.4)
36
+ rubocop (0.93.0)
37
+ parallel (~> 1.10)
38
+ parser (>= 2.7.1.5)
39
+ rainbow (>= 2.2.2, < 4.0)
40
+ regexp_parser (>= 1.8)
41
+ rexml
42
+ rubocop-ast (>= 0.6.0)
43
+ ruby-progressbar (~> 1.7)
44
+ unicode-display_width (>= 1.4.0, < 2.0)
45
+ rubocop-ast (0.7.1)
46
+ parser (>= 2.7.1.5)
47
+ ruby-progressbar (1.10.1)
48
+ solargraph (0.39.17)
49
+ backport (~> 1.1)
50
+ benchmark
51
+ bundler (>= 1.17.2)
52
+ e2mmap
53
+ jaro_winkler (~> 1.5)
54
+ maruku (~> 0.7, >= 0.7.3)
55
+ nokogiri (~> 1.9, >= 1.9.1)
56
+ parser (~> 2.3)
57
+ reverse_markdown (>= 1.0.5, < 3)
58
+ rubocop (~> 0.52)
59
+ thor (~> 1.0)
60
+ tilt (~> 2.0)
61
+ yard (~> 0.9, >= 0.9.24)
62
+ thor (1.0.1)
63
+ tilt (2.0.10)
64
+ unicode-display_width (1.7.0)
65
+ yard (0.9.25)
66
+
67
+ PLATFORMS
68
+ ruby
69
+
70
+ DEPENDENCIES
71
+ bundler
72
+ rake (~> 12.0)
73
+ solargraph
74
+ xssh!
75
+
76
+ BUNDLED WITH
77
+ 2.1.4
@@ -0,0 +1,36 @@
1
+ # Xssh
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/xssh`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'xssh'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle install
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install xssh
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/xssh.
36
+
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "xssh"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+
5
+ require 'xssh/version'
6
+ require 'xssh/factory'
7
+ require 'xssh/template'
8
+
9
+ module Xssh
10
+ class Error < StandardError; end
11
+ class << self
12
+ def get(name, **opts, &block)
13
+ opts = factory.inventory[name].merge(opts)
14
+ type = opts[:type]
15
+ template = factory.templates[type]
16
+ session = template.build(name, **opts)
17
+
18
+ return session unless block
19
+
20
+ yield session
21
+ begin
22
+ session.close
23
+ rescue StandardError
24
+ nil
25
+ end
26
+ rescue StandardError => e
27
+ raise Error, e
28
+ end
29
+
30
+ def list(**query)
31
+ factory.query(query)
32
+ end
33
+
34
+ def find(**query)
35
+ list(**query).first
36
+ end
37
+
38
+ def configure(&block)
39
+ instance_eval(&block)
40
+ end
41
+
42
+ def source(*args)
43
+ args.flatten.each do |src|
44
+ case src
45
+ when /\.y(a)?ml$/
46
+ factory.yaml(src)
47
+ when String
48
+ if File.exist?(src)
49
+ factory.dsl(IO.read(src))
50
+ else
51
+ factory.dsl(src)
52
+ end
53
+ when Hash
54
+ name = src.delete(:name)
55
+ factory.set_source(name, **src)
56
+ when Array
57
+ src.each do |s|
58
+ name = s.delete(:name)
59
+ factory.set_source(name, **s)
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ def template(*templates, **opts, &block)
66
+ name = opts[:type]
67
+
68
+ if block
69
+ template = factory.templates[name] || Template.new(name)
70
+ template.instance_eval(&block)
71
+ factory.templates[name] = template
72
+ return
73
+ end
74
+
75
+ templates = templates.map { |path| Dir.exist?(path) ? Dir.glob(File.join(path, '*.rb')) : path }.flatten
76
+ templates.each do |path|
77
+ name ||= File.basename(path, '.rb').scan(/\w+/).join('_').to_sym
78
+ text = IO.read(path)
79
+ template = factory.templates[name] || Template.new(name)
80
+ template.instance_eval(text)
81
+
82
+ factory.templates[name] = template
83
+ end
84
+ end
85
+
86
+ private
87
+
88
+ def factory
89
+ @factory ||= Factory.instance
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,90 @@
1
+ require 'optparse'
2
+ require 'parallel'
3
+ require 'stringio'
4
+
5
+ module Xssh
6
+ class CLI
7
+
8
+ DEFAULT_INVENTORY = File.join(ENV['HOME'], '.xsshrc')
9
+ DEFAULT_TEMPLATE = File.join(ENV['HOME'], '.xssh.d')
10
+
11
+ def self.run(args = ARGV)
12
+ Xssh::CLI.new.run(args)
13
+ end
14
+
15
+ def hello
16
+ puts "hello"
17
+ end
18
+
19
+ def list(config)
20
+ puts Xssh.list(name: config[:name]).map{ |e| e[:name] }.sort.uniq
21
+ end
22
+
23
+ def tty(config)
24
+ info = Xssh.find(name: config[:name])
25
+ puts "Trying #{info[:name]}...", "Escape character is '^]'."
26
+
27
+ session, _ = exec(config.merge(patterns: info[:name], jobs: 1))
28
+ session.interact!
29
+ end
30
+
31
+ def exec(config)
32
+ Signal.trap(:INT){ exit 1 }
33
+
34
+ info = Xssh.find(name: config[:name])
35
+ width = info[:name].length
36
+ raise "No host found: `#{info[:name]}`" if info.empty?
37
+
38
+ buffer = StringIO.new
39
+ prefix = "#{info[:name].to_s.ljust(width)} |"
40
+ session = nil
41
+
42
+ begin
43
+ loggers = []
44
+ loggers << buffer
45
+ loggers << $stdout
46
+ loggers << File.expand_path(File.join(config[:logdir], "#{info[:name]}.log"), ENV['PWD']) if config[:logdir]
47
+
48
+ session = Xssh.get(info[:name], **info.merge(log: loggers))
49
+ rescue => e
50
+ raise e
51
+ end
52
+
53
+ session
54
+ end
55
+ def run(args)
56
+ config = Hash.new
57
+ config[:env] = []
58
+ config[:inventory] = []
59
+ config[:template] = []
60
+ config[:command] = []
61
+ config[:runner] = self.method(:tty)
62
+
63
+ parser = OptionParser.new
64
+ parser.banner = "#{File.basename($0)} HOST_PATTERN [Options]"
65
+ parser.version = Xssh::VERSION
66
+
67
+ parser.on('-i PATH', '--inventory', String, 'The PATH to the inventory file.') { |v| config[:inventory] << v }
68
+ parser.on('-t PATH', '--template', String, 'The PATH to the template file or directory.'){ |v| config[:template] << v }
69
+ parser.on('-L PATH', '--log-dir', String, 'The PATH to the log directory.') { |v| config[:logdir] = v }
70
+
71
+ parser.on('-l', '--list', TrueClass, 'List the inventory.') { |v| config[:runner] = self.method(:list) }
72
+ #parser.on('-e COMMAND', '--exec', String, 'Execute commands and quit.'){ |v| config[:runner] = self.method(:exec); config[:command] << v }
73
+
74
+ config[:name] = parser.parse!(args).first
75
+ config[:inventory] << DEFAULT_INVENTORY if config[:inventory].empty?
76
+ config[:template] << DEFAULT_TEMPLATE if config[:template].empty?
77
+
78
+ Xssh.configure do
79
+ source *config[:inventory].map{ |e| File.expand_path(e, ENV['PWD']) }
80
+ template *config[:template].map { |e| File.expand_path(e, ENV['PWD']) }
81
+ end
82
+
83
+ raise Xssh::Error.new("Invalid host : '#{config[:name]}'") if Xssh.list(**config.slice(:name)).empty?
84
+ config[:runner].call(config)
85
+ rescue => e
86
+ $stderr.puts e, '', parser
87
+ exit 1
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,305 @@
1
+ require 'net/ssh'
2
+ require 'net/ssh/gateway'
3
+
4
+ module Xssh
5
+ class Connection
6
+ attr_reader :ssh, :channel
7
+
8
+ CR = "\015"
9
+ LF = "\012"
10
+ EOL = CR + LF
11
+
12
+ class TinyFactory
13
+ def initialize(sock)
14
+ @sock = sock
15
+ end
16
+ def open(host, port)
17
+ s = @sock
18
+ @sock = nil
19
+ s
20
+ end
21
+ end
22
+
23
+ def initialize(template, name, **opts, &blk) # :yield: mesg
24
+ opts[:host_name] ||= name
25
+ opts[:port] ||= 22
26
+ opts[:prompt] ||= /[$%#>] \z/n
27
+ opts[:timeout] ||= 10
28
+ opts[:waittime] ||= 0
29
+ opts[:terminator] ||= LF
30
+ opts[:ptyoptions] ||= {}
31
+ opts[:binmode] ||= false
32
+ opts[:timeout] ||= template.timeout
33
+ opts[:log] ||= []
34
+
35
+ @opts = opts
36
+ @name = name
37
+ @max_retry = opts[:max_retry] || 1
38
+ @loggers = [@opts[:log]].flatten.uniq.reduce({}){ |a, e| a.merge(e => build_log(e) ) }
39
+
40
+ @opts[:delegator]&.each do |name, bk|
41
+ define_singleton_method(name, &bk)
42
+ end
43
+
44
+ if @opts[:relay]
45
+ relay = @opts[:relay]
46
+ info = Xssh.factory.inventory[relay]
47
+ @opts[:port] = Net::SSH::Gateway.new(relay, nil, info)
48
+ end
49
+
50
+ unless (true == @opts[:binmode] or false == @opts[:binmode])
51
+ raise ArgumentError, "Binmode option must be true or false"
52
+ end
53
+
54
+ if @opts[:proxy]
55
+ opts = @opts.slice(:host, :port, :user, :password, :timeout)
56
+ opts[:proxy] = TinyFactory.new(@opts[:proxy])
57
+
58
+ @ssh = Net::SSH.start(nil, nil, opts)
59
+ @close_all = true
60
+ else
61
+
62
+ begin
63
+ ssh_options = opts.slice(*Net::SSH::VALID_OPTIONS)
64
+
65
+ if @opts[:config]
66
+ ssh_options.delete(:host_name)
67
+ @ssh = Net::SSH.start(@opts[:host_name], nil, ssh_options)
68
+ else
69
+ @ssh = Net::SSH.start(name, nil, ssh_options)
70
+ end
71
+
72
+ @close_all = true
73
+ rescue TimeoutError
74
+ raise TimeoutError, "timed out while opening a connection to the host"
75
+ rescue
76
+ write_log($ERROR_INFO.to_s + "\n")
77
+ raise
78
+ end
79
+ end
80
+
81
+ start_ssh_connection(&blk)
82
+ rescue StandardError => err
83
+ retry if (@max_retry -= 1) > 0
84
+ raise err
85
+ end
86
+
87
+ def start_ssh_connection(&blk)
88
+ @buf = ""
89
+ @eof = false
90
+ @channel = nil
91
+ @ssh.open_channel do |channel|
92
+ channel.on_data { |ch,data| @buf << data }
93
+ channel.on_extended_data { |ch,type,data| @buf << data if type == 1 }
94
+ channel.on_close { @eof = true }
95
+ channel.request_pty(@opts[:ptyoptions]) { |ch,success|
96
+ if success == false
97
+ raise "Failed to open ssh pty"
98
+ end
99
+ }
100
+ channel.send_channel_request("shell") { |ch, success|
101
+ if success
102
+ @channel = ch
103
+ waitfor(&blk)
104
+ return
105
+ else
106
+ raise "Failed to open ssh shell"
107
+ end
108
+ }
109
+ end
110
+ @ssh.loop
111
+ end
112
+
113
+ def close
114
+ @loggers.each do |_, logger|
115
+ next if [$stdout, $stderr, nil].include?(logger)
116
+ logger.close
117
+ end
118
+
119
+ @channel.close if @channel
120
+ @channel = nil
121
+ @ssh.close if @close_all and @ssh
122
+ end
123
+
124
+ def binmode(mode = nil)
125
+ case mode
126
+ when nil
127
+ @opts[:binmode]
128
+ when true, false
129
+ @opts[:binmode] = mode
130
+ else
131
+ raise ArgumentError, "argument must be true or false"
132
+ end
133
+ end
134
+
135
+ def binmode=(mode)
136
+ if (true == mode or false == mode)
137
+ @opts[:binmode] = mode
138
+ else
139
+ raise ArgumentError, "argument must be true or false"
140
+ end
141
+ end
142
+
143
+ def waitfor(prompt = @opts[:prompt], **opts) # :yield: recvdata
144
+ opts[:timeout] ||= @opts[:timeout]
145
+ opts[:waittime] ||= @opts[:waittime]
146
+ opts[:faileof] ||= @opts[:faileof]
147
+ prompt = Regexp.new( Regexp.quote(prompt) ) if prompt.kind_of?(String)
148
+
149
+ opts[:timeout] = nil unless opts[:timeout]
150
+
151
+ line = ''
152
+ buf = ''
153
+ rest = ''
154
+ sock = @ssh.transport.socket
155
+
156
+ until @ssh.transport.socket.available == 0 && @buf == "" && prompt === line && (@eof || (!sock.closed? && !IO::select([sock], nil, nil, opts[:waittime])))
157
+ if @buf == '' && (@ssh.transport.socket.available == 0) && !(prompt === line) && !IO::select([sock], nil, nil, opts[:timeout])
158
+ raise Net::ReadTimeout, "timed out while waiting for more data"
159
+ end
160
+
161
+ _process_ssh
162
+
163
+ if @buf != ""
164
+ c = @buf; @buf = ""
165
+ buf = rest + c
166
+ rest = ''
167
+
168
+ unless @opts[:binmode]
169
+ if pt = buf.rindex(/\r\z/no)
170
+ buf = buf[0 ... pt]
171
+ rest = buf[pt .. -1]
172
+ end
173
+ buf.gsub!(/#{EOL}/no, "\n")
174
+ end
175
+
176
+ line += buf
177
+
178
+ unless buf == @string
179
+ write_log(buf)
180
+ end
181
+
182
+ elsif @eof # End of file reached
183
+ break if prompt === line
184
+ raise EOFError if opts[:faileof]
185
+ if line == ''
186
+ line = nil
187
+ yield nil if block_given?
188
+ end
189
+ break
190
+ end
191
+ end
192
+ line
193
+ end
194
+
195
+ def write(string)
196
+ @channel.send_data string
197
+ _process_ssh
198
+ end
199
+
200
+ def print(string)
201
+ if @opts[:binmode]
202
+ self.write(string)
203
+ else
204
+ self.write(string.gsub(/\n/n, @opts[:terminator]))
205
+ end
206
+ end
207
+
208
+ def prompt= val
209
+ @opts[:prompt] = val
210
+ end
211
+
212
+ def puts(string)
213
+ @string = string + "\n"
214
+ self.print(string + "\n")
215
+ end
216
+
217
+ def cmd(string, **opts) # :yield: recvdata
218
+ opts[:match] ||= @opts[:prompt]
219
+ opts[:timeout] ||= @opts[:timeout]
220
+ opts[:faileof] ||= @opts[:faileof]
221
+
222
+ self.puts(string)
223
+
224
+ if block_given?
225
+ waitfor(**opts){|c| yield c }
226
+ else
227
+ waitfor(**opts)
228
+ end
229
+ end
230
+
231
+ def interact!
232
+ loop do
233
+ com = $stdin.gets.strip
234
+
235
+ if com == "exit"
236
+ close
237
+ exit 0
238
+ else
239
+ self.puts(com)
240
+ waitfor
241
+ end
242
+ end
243
+ end
244
+
245
+ def enable_log(log = $stdout)
246
+ @loggers.update(log => build_log(log))
247
+ if block_given?
248
+ yield
249
+ disable_log(log)
250
+ end
251
+ end
252
+
253
+ def disable_log(log = $stdout)
254
+ @loggers.delete(log)
255
+ if block_given?
256
+ yield
257
+ enable_log(log)
258
+ end
259
+ end
260
+
261
+ private
262
+ def write_log(text)
263
+ @loggers.each{ |_, logger| logger.syswrite(text) if logger }
264
+ end
265
+
266
+ def build_log(log)
267
+ case log
268
+ when String
269
+ FileUtils.mkdir_p(File.dirname(log))
270
+ File.open(log, 'a+').tap do |logger|
271
+ logger.binmode
272
+ logger.sync = true
273
+ end
274
+ when IO, StringIO
275
+ log
276
+ end
277
+ end
278
+
279
+ def logger(mes)
280
+ yield mes if block_given?
281
+ return unless @opts[:log]
282
+
283
+ unless @log
284
+ @log =
285
+ case @opts[:log]
286
+ when IO then @opts[:log]
287
+ when String then File.open(@opts[:log], 'a+')
288
+ end
289
+ @log.sync = true
290
+ @log.binmode
291
+ end
292
+
293
+ @log.write(mes)
294
+ end
295
+
296
+ def _process_ssh
297
+ begin
298
+ @channel.connection.process(0)
299
+ rescue IOError
300
+ @eof = true
301
+ end
302
+ end
303
+
304
+ end # class Telnet
305
+ end # module SSH
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'singleton'
4
+ require 'uri'
5
+
6
+ module Xssh
7
+ class Factory
8
+ include Singleton
9
+ attr_accessor :inventory
10
+ attr_accessor :templates
11
+
12
+ def initialize
13
+ @inventory = {}
14
+ @templates = {}
15
+ end
16
+
17
+ def set_source(name, **args)
18
+ args[:name] = name
19
+ @inventory[name] = args
20
+ end
21
+
22
+ def set_template(name, klass)
23
+ @templates[name] = klass
24
+ end
25
+
26
+ def query(q)
27
+ list = inventory.map { |_k, v| v }
28
+ return list if q.empty?
29
+
30
+ list.select do |h|
31
+ q.any? do |k, v|
32
+ case v
33
+ when Regexp
34
+ h[k] =~ v
35
+ else
36
+ h[k] == v
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ def yaml(file)
43
+ raise Error, 'not cofnig file' unless File.exist?(file)
44
+
45
+ srcs = YAML.load_file(file)
46
+ srcs.each do |src|
47
+ src = src.inject({}) { |h, (k, v)| h = h.merge(k.to_sym => v) }
48
+ name = src.delete(:host)
49
+ set_source(name, **src)
50
+ end
51
+ end
52
+
53
+ def dsl(str)
54
+ instance_eval(str)
55
+ end
56
+
57
+ def method_missing(method, *args, **options)
58
+ super unless args.size == 2
59
+
60
+ name = args.shift
61
+ uri = URI.parse(args.shift)
62
+
63
+ options[:type] = method
64
+ options[:uri] = uri
65
+ options[:name] = name
66
+ options[:host_name] = uri.host
67
+ options[:user] = uri.user
68
+ options[:password] = uri.password
69
+
70
+ set_source(name, **options)
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'xssh/connection'
4
+
5
+ module Xssh
6
+ class Template
7
+ DEFAULT_TIMEOUT = 10
8
+ DEFAULT_PROMPT = /[$%#>] ?\z/n.freeze
9
+
10
+ attr_reader :methods
11
+ attr_reader :prompts
12
+ attr_reader :timeout
13
+
14
+ def initialize(name)
15
+ @name = name
16
+ @prompts = []
17
+ @timeout = DEFAULT_TIMEOUT
18
+ @methods = {}
19
+ end
20
+
21
+ def prompt(expect = nil, &block)
22
+ return [[DEFAULT_PROMPT, nil]] if expect.nil? && @prompts.empty?
23
+
24
+ @prompts << [Regexp.new(expect.to_s), block] if expect
25
+ @prompts
26
+ end
27
+
28
+ def prompt_patten
29
+ Regexp.union(prompt.map(&:first))
30
+ end
31
+
32
+ def bind(name, &block)
33
+ @methods[name] = block
34
+ end
35
+
36
+ def build(name, **opts)
37
+ klass = Class.new(Xssh.const_get('Connection'))
38
+ klass.class_exec(self) do |template|
39
+ template.methods.each { |name, block| define_method(name, &block) }
40
+ end
41
+ klass.new(self, name, **opts)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,3 @@
1
+ module Xssh
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,43 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "xssh/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "xssh"
8
+ spec.version = Xssh::VERSION
9
+ spec.authors = ["ito.toshifumi"]
10
+ spec.email = ["ymdtshm@gmail.com"]
11
+
12
+ spec.summary = %q{Multiple SSH logins}
13
+ spec.description = %q{Multiple SSH logins gem}
14
+ spec.homepage = "https://github.com/Gen-Arch/xssh"
15
+
16
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
17
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
18
+ if spec.respond_to?(:metadata)
19
+ spec.metadata["homepage_uri"] = spec.homepage
20
+ spec.metadata["source_code_uri"] = "https://github.com/Gen-Arch/xssh"
21
+ else
22
+ raise "RubyGems 2.0 or newer is required to protect against " \
23
+ "public gem pushes."
24
+ end
25
+
26
+ # Specify which files should be added to the gem when it is released.
27
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
28
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
29
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
30
+ end
31
+ spec.bindir = "exe"
32
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
33
+ spec.require_paths = ["lib"]
34
+
35
+ spec.add_runtime_dependency "net-ssh"
36
+ spec.add_runtime_dependency "net-ssh-gateway"
37
+ spec.add_runtime_dependency "ed25519"
38
+ spec.add_runtime_dependency "bcrypt_pbkdf"
39
+
40
+ spec.add_development_dependency "bundler"
41
+ spec.add_development_dependency "rake"
42
+ spec.add_development_dependency "solargraph"
43
+ end
metadata ADDED
@@ -0,0 +1,156 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: xssh
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - ito.toshifumi
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-10-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: net-ssh
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: net-ssh-gateway
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: ed25519
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bcrypt_pbkdf
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: bundler
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: solargraph
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: Multiple SSH logins gem
112
+ email:
113
+ - ymdtshm@gmail.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".gitignore"
119
+ - Gemfile
120
+ - Gemfile.lock
121
+ - README.md
122
+ - Rakefile
123
+ - bin/console
124
+ - bin/setup
125
+ - lib/xssh.rb
126
+ - lib/xssh/cli.rb
127
+ - lib/xssh/connection.rb
128
+ - lib/xssh/factory.rb
129
+ - lib/xssh/template.rb
130
+ - lib/xssh/version.rb
131
+ - xssh.gemspec
132
+ homepage: https://github.com/Gen-Arch/xssh
133
+ licenses: []
134
+ metadata:
135
+ homepage_uri: https://github.com/Gen-Arch/xssh
136
+ source_code_uri: https://github.com/Gen-Arch/xssh
137
+ post_install_message:
138
+ rdoc_options: []
139
+ require_paths:
140
+ - lib
141
+ required_ruby_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ required_rubygems_version: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ requirements: []
152
+ rubygems_version: 3.1.2
153
+ signing_key:
154
+ specification_version: 4
155
+ summary: Multiple SSH logins
156
+ test_files: []