xssh 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []