net-ssh-cli 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a2574b34a1dfb8f75b642937c966a385195fdcd7c193ee420221a89edc55d7ff
4
+ data.tar.gz: 78ca3a2820d14e8c6b491aa1bf2dafe6ba9cde2666068b07213343b2d7a0cb95
5
+ SHA512:
6
+ metadata.gz: fd586eee90f7ffc3e44e5d3ea634cef5f9c5748f388d9e12b51f0198a19009031cac6e366c0e48d61abdd38d8bc14ddf4c1225f234efa5977584fdaa09d18591
7
+ data.tar.gz: 3b60e1eedfc77a445f0d4c5063031bc2db41a29f76d30fb6c738f4e28a7407b2d93c4df84961f8162198f933296b7c63bcdb6edb8e99405646193997c9564ff7
data/.gitignore ADDED
@@ -0,0 +1,15 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+ *.swp
13
+ coverage
14
+ /*.gem
15
+ /Gemfile.lock
data/.gitlab-ci.yml ADDED
@@ -0,0 +1,20 @@
1
+ image: nni-hub.artifactory.safe-mgmt.swisscom.com/ruby-swisscom-certificates:2.6.0
2
+
3
+ variables:
4
+ http_proxy: "http://serverproxy.corproot.net:8080"
5
+ https_proxy: "http://serverproxy.corproot.net:8080"
6
+ HTTP_PROXY: "http://serverproxy.corproot.net:8080"
7
+ HTTPS_PROXY: "http://serverproxy.corproot.net:8080"
8
+ no_proxy: ".swissptt.ch"
9
+
10
+ stages:
11
+ - test
12
+
13
+ test:
14
+ stage: test
15
+ script:
16
+ - bundle install
17
+ - bundle exec rspec
18
+ # - bundle exec rubocop
19
+ tags:
20
+ - docker
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,9 @@
1
+ inherit_from: .rubocop_todo.yml
2
+ AllCops:
3
+ Exclude:
4
+ - 'bin/localhost'
5
+ Metrics/LineLength:
6
+ Max: 160
7
+ Metrics/BlockLength:
8
+ Max: 160
9
+
data/.rubocop_todo.yml ADDED
File without changes
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.6
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.5.0
7
+ before_install: gem install bundler -v 1.17.1
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+ gem 'foreman'
5
+ gem 'guard-rspec', require: false
6
+ gem 'rubocop', require: false
7
+ gem 'simplecov', require: false
8
+
9
+ # git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
10
+
11
+ # Specify your gem's dependencies in net-ssh-cli.gemspec
12
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A sample Guardfile
4
+ # More info at https://github.com/guard/guard#readme
5
+
6
+ ## Uncomment and set this to only include directories you want to watch
7
+ # directories %w(app lib config test spec features) \
8
+ # .select{|d| Dir.exist?(d) ? d : UI.warning("Directory #{d} does not exist")}
9
+
10
+ ## Note: if you are using the `directories` clause above and you are not
11
+ ## watching the project directory ('.'), then you will want to move
12
+ ## the Guardfile to a watched dir and symlink it back, e.g.
13
+ #
14
+ # $ mkdir config
15
+ # $ mv Guardfile config/
16
+ # $ ln -s config/Guardfile .
17
+ #
18
+ # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
19
+
20
+ # Note: The cmd option is now required due to the increasing number of ways
21
+ # rspec may be run, below are examples of the most common uses.
22
+ # * bundler: 'bundle exec rspec'
23
+ # * bundler binstubs: 'bin/rspec'
24
+ # * spring: 'bin/rspec' (This will use spring if running and you have
25
+ # installed the spring binstubs per the docs)
26
+ # * zeus: 'zeus rspec' (requires the server to be started separately)
27
+ # * 'just' rspec: 'rspec'
28
+
29
+ guard :rspec, cmd: 'bundle exec rspec' do
30
+ require 'guard/rspec/dsl'
31
+ dsl = Guard::RSpec::Dsl.new(self)
32
+
33
+ # Feel free to open issues for suggestions and improvements
34
+
35
+ # RSpec files
36
+ rspec = dsl.rspec
37
+ watch(rspec.spec_helper) { rspec.spec_dir }
38
+ watch(rspec.spec_support) { rspec.spec_dir }
39
+ watch(rspec.spec_files)
40
+
41
+ # Ruby files
42
+ ruby = dsl.ruby
43
+ dsl.watch_spec_files_for(ruby.lib_files)
44
+
45
+ # Rails files
46
+ rails = dsl.rails(view_extensions: %w[erb haml slim])
47
+ dsl.watch_spec_files_for(rails.app_files)
48
+ dsl.watch_spec_files_for(rails.views)
49
+
50
+ watch(rails.controllers) do |m|
51
+ [
52
+ rspec.spec.call("routing/#{m[1]}_routing"),
53
+ rspec.spec.call("controllers/#{m[1]}_controller"),
54
+ rspec.spec.call("acceptance/#{m[1]}")
55
+ ]
56
+ end
57
+
58
+ # Rails config changes
59
+ watch(rails.spec_helper) { rspec.spec_dir }
60
+ watch(rails.routes) { "#{rspec.spec_dir}/routing" }
61
+ watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
62
+
63
+ # Capybara features specs
64
+ watch(rails.view_dirs) { |m| rspec.spec.call("features/#{m[1]}") }
65
+ watch(rails.layouts) { |m| rspec.spec.call("features/#{m[1]}") }
66
+
67
+ # Turnip features and steps
68
+ watch(%r{^spec/acceptance/(.+)\.feature$})
69
+ watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
70
+ Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance'
71
+ end
72
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Fabian Stillhart
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/Procfile ADDED
@@ -0,0 +1 @@
1
+ guard: bundle exec guard
data/README.md ADDED
@@ -0,0 +1,91 @@
1
+ # Net::SSH::CLI
2
+
3
+ Adds another layer on top of Net::SSH for a proper handling of CLI sessions which last longer than one command. This is especially usefull for enterprise Switches and Routers.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'net-ssh-cli'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install net-ssh-cli
20
+
21
+ ## Features
22
+
23
+ - provides an abstraction on top of the text-stream of a long living CLI sessions
24
+ - tries to be highly configurable
25
+ - has methods like #cmd and #dialog for common usecases
26
+ - offers waiting operations like #read_till
27
+
28
+ ## Usage
29
+
30
+ ```ruby
31
+ Net::SSH.start('host', 'user', password: "password") do |ssh|
32
+ cli = ssh.open_cli_channel(default_prompt: /(\nuser@host):/m)
33
+ cli.cmd ""
34
+ # => "Last login: \nuser@host:"
35
+
36
+ cli.cmd "echo 'bananas'"
37
+ # => "echo 'bananas'\nbananas\nuser@host:"
38
+ end
39
+ ```
40
+
41
+ ```ruby
42
+ net_ssh = Net::SSH.start('host', 'user', password: "password")
43
+ cli = Net::SSH::CLI::Channel.new(net_ssh: net_ssh)
44
+ cli.cmd ""
45
+ ```
46
+
47
+ ```ruby
48
+ cli = Net::SSH::CLI::Channel.new(net_ssh_options: {host: 'host', user: 'user', password: 'password'})
49
+ cli.cmd ""
50
+ ```
51
+
52
+ ### #cmd
53
+ ```ruby
54
+ cli = ssh.open_cli_channel(default_prompt: /(\nuser@host):/m)
55
+ cli.cmd "echo 'bananas'"
56
+ # => "echo 'bananas'\nbananas\nuser@host:"
57
+ cli.cmd "echo 'bananas'", rm_command: true
58
+ # => "bananas\nuser@host:"
59
+ cli.cmd "echo 'bananas'", rm_prompt: true
60
+ # => "echo 'bananas'\nbananas"
61
+ cli.cmd "echo 'bananas'", rm_command: true, rm_prompt: true
62
+ # => "bananas"
63
+ ```
64
+
65
+ Remove the command and the prompt for #cmd & #dialog by default
66
+ ```ruby
67
+ cli = ssh.open_cli_channel(default_prompt: /(\nuser@host):/m, cmd_rm_command: true, cmd_rm_prompt: true)
68
+ cli.cmd "echo 'bananas'"
69
+ # => "bananas"
70
+ ```
71
+
72
+ ### #dialog
73
+ ```ruby
74
+ cli.dialog "echo 'are you sure?' && read -p 'yes|no>'", /\nyes|no>/
75
+ # => "echo 'are you sure?' && read -p 'yes|no>'\nyes|no>"
76
+ cli.cmd "yes"
77
+ ```
78
+
79
+ ## Development
80
+
81
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
82
+
83
+ 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).
84
+
85
+ ## Contributing
86
+
87
+ Bug reports and pull requests are welcome on GitHub at https://github.com/swisscom/net-ssh-cli.
88
+
89
+ ## License
90
+
91
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'net/ssh/cli'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start(__FILE__)
data/bin/localhost ADDED
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'net/ssh/cli'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+
16
+ def reload!
17
+ load __FILE__
18
+ end
19
+
20
+ $CLI = nil
21
+ begin
22
+ $CLI = Net::SSH::CLI::Channel.new(host: 'localhost', user: ENV['USER'], default_prompt: '@')
23
+ $CLI.open_channel
24
+ puts "\nPUTS #{$CLI.read}"
25
+ sleep 0.3
26
+ puts "\nPUTS #{$CLI.read}"
27
+ puts "\nPUTS #{$CLI.write "\n"}"
28
+ puts "\nPUTS TILL #{$CLI.read_till}"
29
+ puts "try $CLI.cmd"
30
+ puts File.read(__FILE__)
31
+ rescue StandardError => error
32
+ puts error.class
33
+ puts error.message
34
+ puts error.backtrace
35
+ ensure
36
+ IRB.start(__FILE__)
37
+ end
data/bin/setup ADDED
@@ -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,357 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/ssh/cli/version'
4
+ require 'net/ssh'
5
+ require 'active_support/core_ext/hash/indifferent_access'
6
+ require 'active_support/core_ext/object/blank'
7
+ require 'timeout'
8
+ require 'logger'
9
+
10
+ module Net
11
+ module SSH
12
+ module CLI
13
+ class Error < StandardError
14
+ class Pty < Error; end
15
+ class RequestShell < Error; end
16
+ class UndefinedMatch < Error; end
17
+ class OpenChannelTimeout < Error; end
18
+ class ReadTillTimeout < Error; end
19
+ end
20
+
21
+ def initialize(**opts)
22
+ options.merge!(opts)
23
+ self.net_ssh = options.delete(:net_ssh)
24
+ self.logger = options.delete(:logger) || Logger.new(STDOUT, level: Logger::WARN)
25
+ open_channel unless lazy
26
+ @with_prompt = []
27
+ end
28
+
29
+ attr_accessor :channel, :stdout, :stderr, :net_ssh, :logger
30
+
31
+ ## make everthing configurable!
32
+ #
33
+
34
+ DEFAULT = ActiveSupport::HashWithIndifferentAccess.new(
35
+ default_prompt: /^(\S+@\S+\s*)/,
36
+ process_time: 0.00001,
37
+ read_till_timeout: nil,
38
+ read_till_rm_prompt: false,
39
+ cmd_rm_prompt: false,
40
+ cmd_rm_command: false,
41
+ lazy: true
42
+ )
43
+
44
+ def default
45
+ @default ||= DEFAULT.clone
46
+ end
47
+
48
+ def default!(**defaults)
49
+ default.merge!(**defaults)
50
+ end
51
+
52
+ # don't even think about nesting hashes here
53
+ def options
54
+ @options ||= default
55
+ end
56
+
57
+ # don't even think about nesting hashes here
58
+ def options!(**opts)
59
+ options.merge!(opts)
60
+ end
61
+
62
+ # don't even think about nesting hashes here
63
+ def options=(opts)
64
+ @options = ActiveSupport::HashWithIndifferentAccess.new(opts)
65
+ end
66
+
67
+ %i[default_prompt process_time read_till_timeout cmd_rm_command cmd_rm_prompt read_till_rm_prompt lazy].each do |name|
68
+ define_method name do
69
+ options[name]
70
+ end
71
+ define_method "#{name}=" do |value|
72
+ options[name] = value
73
+ end
74
+ end
75
+
76
+ %i[process_stdout_procs process_stderr_procs named_prompts net_ssh_options open_channel_options].each do |name|
77
+ define_method name do
78
+ options[name] ||= ActiveSupport::HashWithIndifferentAccess.new
79
+ end
80
+ define_method "#{name}!" do |**opts|
81
+ send(name).merge!(**opts)
82
+ end
83
+ define_method "#{name}=" do |value|
84
+ options[name] = ActiveSupport::HashWithIndifferentAccess.new(value)
85
+ end
86
+ end
87
+
88
+ ## Net::SSH instance
89
+ #
90
+
91
+ def net_ssh
92
+ return @net_ssh if @net_ssh
93
+
94
+ logger.debug { 'Net:SSH #start' }
95
+ self.net_ssh = Net::SSH.start(net_ssh_options[:ip] || net_ssh_options[:host], net_ssh_options[:user] || ENV['USER'], net_ssh_options)
96
+ rescue StandardError => error
97
+ self.net_ssh = nil
98
+ raise
99
+ end
100
+ alias proxy net_ssh
101
+
102
+ def host
103
+ net_ssh_options[:host] || net_ssh_options[:hostname] || net_ssh_options[:ip] || @net_ssh&.host
104
+ end
105
+ alias to_s host
106
+ alias hostname host
107
+
108
+ def ip
109
+ net_ssh_options[:ip]
110
+ end
111
+
112
+ ## channel & stderr|stdout stream handling
113
+ #
114
+
115
+ def stdout
116
+ @stdout ||= String.new
117
+ end
118
+
119
+ def stdout!
120
+ var = stdout
121
+ self.stdout = String.new
122
+ var
123
+ end
124
+
125
+ def stderr
126
+ @stderr ||= String.new
127
+ end
128
+
129
+ def stderr!
130
+ var = stderr
131
+ self.stderr = String.new
132
+ var
133
+ end
134
+
135
+ def open_channel # cli_channel
136
+ ::Timeout.timeout(open_channel_options[:timeout], Error::OpenChannelTimeout) do
137
+ net_ssh.open_channel do |channel_|
138
+ logger.debug 'channel is open'
139
+ self.channel = channel_
140
+ channel_.request_pty do |_ch, success|
141
+ raise Error::Pty, "#{host || ip} Failed to open ssh pty" unless success
142
+ end
143
+ channel_.send_channel_request('shell') do |_ch, success|
144
+ raise Error::RequestShell, 'Failed to open ssh shell' unless success
145
+ end
146
+ channel_.on_data do |_ch, data|
147
+ process_stdout(data)
148
+ end
149
+ channel_.on_extended_data do |_ch, type, data|
150
+ process_stderr(data, type)
151
+ end
152
+ channel_.on_close do
153
+ close
154
+ end
155
+ end
156
+ until channel do process end
157
+ end
158
+ logger.debug 'channel is ready, running callbacks now'
159
+ read_till if open_channel_options[:after_read_till_prompt]
160
+ open_channel_options[:after_proc]&.call
161
+ process
162
+ rescue StandardError => error
163
+ close
164
+ raise
165
+ end
166
+
167
+ def process_stdout(data)
168
+ stdout << data
169
+ process # if we receive data, we probably receive more - improves performance
170
+ process_stdout_procs.each { |_name, a_proc| a_proc.call }
171
+ stdout
172
+ end
173
+
174
+ def process_stderr(data, _type)
175
+ stderr << data
176
+ process # if we receive data, we probably receive more - improves performance
177
+ process_stderr_procs.each { |_name, a_proc| a_proc.call }
178
+ stderr
179
+ end
180
+
181
+ def write(content = String.new)
182
+ raise Error, 'channel is not stablished or gone' unless channel
183
+
184
+ logger.debug { "#write #{content.inspect}" }
185
+ channel.send_data content
186
+ process
187
+ content
188
+ end
189
+
190
+ def write_n(content = String.new)
191
+ write content + "\n"
192
+ end
193
+
194
+ # returns the stdout buffer and empties it
195
+ def read
196
+ process
197
+ var = stdout!
198
+ logger.debug("#read: \n#{var}")
199
+ var
200
+ end
201
+
202
+ ## fancy prompt|prompt handling methods
203
+ #
204
+
205
+ def current_prompt
206
+ @with_prompt[-1] || default_prompt
207
+ end
208
+
209
+ def with_named_prompt(name)
210
+ raise Error::UndefinedMatch, "unknown named_prompt #{name}" unless named_prompts[name]
211
+
212
+ with_prompt(named_prompts[name]) do
213
+ yield
214
+ end
215
+ end
216
+
217
+ def detect_prompt(seconds: 5)
218
+ process(seconds)
219
+ self.default_prompt = read[/\n.*\n?\z/]
220
+ end
221
+
222
+ # prove a block where the default prompt changes
223
+ def with_prompt(prompt)
224
+ logger.debug { "#with_prompt: #{current_prompt.inspect} => #{prompt.inspect}" }
225
+ @with_prompt << prompt
226
+ yield
227
+ prompt
228
+ ensure
229
+ @with_prompt.delete_at(-1)
230
+ logger.debug { "#with_prompt: => #{current_prompt.inspect}" }
231
+ end
232
+
233
+ def read_till(prompt: current_prompt, timeout: read_till_timeout, **_opts)
234
+ raise Error::UndefinedMatch, 'no prompt given or default_prompt defined' unless prompt
235
+
236
+ ::Timeout.timeout(timeout, Error::ReadTillTimeout.new("output did not prompt #{prompt.inspect} within #{timeout}")) do
237
+ with_prompt(prompt) do
238
+ process until stdout[current_prompt]
239
+ end
240
+ end
241
+ read
242
+ end
243
+
244
+ def read_for(seconds:)
245
+ process
246
+ sleep seconds
247
+ process
248
+ read
249
+ end
250
+
251
+ def dialog(command, prompt, **opts)
252
+ pre_read = read
253
+ logger.debug { "#dialog ignores the following pre-output #{pre_read.inspect}" } if pre_read.present?
254
+ write command
255
+ output = read_till(prompt: prompt, **opts)
256
+ rm_prompt!(output, prompt: prompt, **opts)
257
+ rm_command!(output, command, prompt: prompt, **opts)
258
+ output
259
+ end
260
+
261
+ # 'read' first on purpuse as a feature. once you cmd you ignore what happend before. otherwise use read|write directly.
262
+ # this should avoid many horrible state issues where the prompt is not the last prompt
263
+ def cmd(command, **opts)
264
+ pre_read = read
265
+ logger.debug { "#cmd ignoring pre-read: #{pre_read.inspect}" } if pre_read.present?
266
+ write_n command
267
+ output = read_till(**opts)
268
+ rm_prompt!(output, **opts)
269
+ rm_command!(output, command, **opts)
270
+ output
271
+ end
272
+ alias command cmd
273
+
274
+ def cmds(commands, **opts)
275
+ commands.map { |command| [command, cmd(command, **opts)] }
276
+ end
277
+ alias commands cmds
278
+
279
+ def rm_command?(**opts)
280
+ opts[:rm_cmd].nil? ? cmd_rm_command : opts[:rm_cmd]
281
+ end
282
+
283
+ def rm_command!(output, command, **opts)
284
+ output[command + "\n"] = '' if rm_command?(opts) && output[command + "\n"]
285
+ end
286
+
287
+ def rm_prompt?(**opts)
288
+ opts[:rm_prompt].nil? ? cmd_rm_prompt : opts[:rm_prompt]
289
+ end
290
+
291
+ def rm_prompt!(output, **opts)
292
+ if rm_prompt?(opts)
293
+ prompt = opts[:prompt] || current_prompt
294
+ if output[prompt]
295
+ prompt.is_a?(Regexp) ? output[prompt, 1] = '' : output[prompt] = ''
296
+ end
297
+ end
298
+ end
299
+
300
+ ## NET::SSH
301
+ #
302
+
303
+ def process(time = process_time)
304
+ net_ssh.process(time)
305
+ rescue IOError => error
306
+ raise Error, error.message
307
+ end
308
+
309
+ def close
310
+ return unless net_ssh
311
+
312
+ net_ssh.cleanup_channel(channel) if channel
313
+ self.channel = nil
314
+ # ssh.close if ssh.channels.none? # should the connection be closed if the last channel gets closed?
315
+ end
316
+
317
+ def connect
318
+ open_channel unless channel
319
+ end
320
+
321
+ def reconnect
322
+ disconnect
323
+ connect
324
+ end
325
+
326
+ # feels wrong
327
+
328
+ def disconnect
329
+ close
330
+ net_ssh&.close
331
+ self.net_ssh = nil
332
+ end
333
+
334
+ def shutdown!
335
+ net_ssh&.shutdown!
336
+ end
337
+
338
+ private
339
+
340
+ end
341
+ end
342
+ end
343
+
344
+ class Net::SSH::CLI::Channel
345
+ include Net::SSH::CLI
346
+ def initialize(**options)
347
+ super
348
+ # open_channel
349
+ end
350
+ end
351
+
352
+ class Net::SSH::Connection::Session
353
+ attr_accessor :cli_channels
354
+ def open_cli_channel(**opts)
355
+ Net::SSH::CLI::Channel.new({ net_ssh: self, lazy: false }.merge(opts))
356
+ end
357
+ end
@@ -0,0 +1,7 @@
1
+ module Net
2
+ module SSH
3
+ module CLI
4
+ VERSION = "0.1.0"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'net/ssh/cli/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'net-ssh-cli'
9
+ spec.version = Net::SSH::CLI::VERSION
10
+ spec.authors = ['Fabian Stillhart']
11
+ spec.email = ['fabian.stillhart1@swisscom.com']
12
+
13
+ spec.summary = 'Net::SSH::CLI: A library to handle CLI Sessions'
14
+ spec.description = 'Net::SSH::CLI: A library to handle CLI Sessions. It allows you to write programs that invoke and interact with (long-running) CLI Sessions via NET::SSH.'
15
+ spec.homepage = 'https://github.com/swisscom/net-ssh-cli'
16
+ spec.license = 'MIT'
17
+
18
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
19
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
20
+ # if spec.respond_to?(:metadata)
21
+ # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
22
+
23
+ # spec.metadata["homepage_uri"] = spec.homepage
24
+ # spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here."
25
+ # spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
26
+ # else
27
+ # raise "RubyGems 2.0 or newer is required to protect against " \
28
+ # "public gem pushes."
29
+ # end
30
+
31
+ # Specify which files should be added to the gem when it is released.
32
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
33
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
34
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
35
+ end
36
+ spec.bindir = 'exe'
37
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
38
+ spec.require_paths = ['lib']
39
+
40
+ spec.add_development_dependency 'bundler', '~> 1.17'
41
+ spec.add_development_dependency 'rake', '~> 10.0'
42
+ spec.add_development_dependency 'rspec', '~> 3.0'
43
+ spec.add_dependency 'activesupport', '>= 4.0'
44
+ spec.add_dependency 'net-ssh', '>= 4.0'
45
+ end
metadata ADDED
@@ -0,0 +1,133 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: net-ssh-cli
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Fabian Stillhart
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-02-19 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.17'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.17'
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: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: activesupport
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '4.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '4.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: net-ssh
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '4.0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '4.0'
83
+ description: 'Net::SSH::CLI: A library to handle CLI Sessions. It allows you to write
84
+ programs that invoke and interact with (long-running) CLI Sessions via NET::SSH.'
85
+ email:
86
+ - fabian.stillhart1@swisscom.com
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - ".gitignore"
92
+ - ".gitlab-ci.yml"
93
+ - ".rspec"
94
+ - ".rubocop.yml"
95
+ - ".rubocop_todo.yml"
96
+ - ".ruby-version"
97
+ - ".travis.yml"
98
+ - Gemfile
99
+ - Guardfile
100
+ - LICENSE.txt
101
+ - Procfile
102
+ - README.md
103
+ - Rakefile
104
+ - bin/console
105
+ - bin/localhost
106
+ - bin/setup
107
+ - lib/net/ssh/cli.rb
108
+ - lib/net/ssh/cli/version.rb
109
+ - net-ssh-cli.gemspec
110
+ homepage: https://github.com/swisscom/net-ssh-cli
111
+ licenses:
112
+ - MIT
113
+ metadata: {}
114
+ post_install_message:
115
+ rdoc_options: []
116
+ require_paths:
117
+ - lib
118
+ required_ruby_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ required_rubygems_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ requirements: []
129
+ rubygems_version: 3.0.2
130
+ signing_key:
131
+ specification_version: 4
132
+ summary: 'Net::SSH::CLI: A library to handle CLI Sessions'
133
+ test_files: []