shells 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +78 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/shells/bash_common.rb +170 -0
- data/lib/shells/errors.rb +57 -0
- data/lib/shells/pf_sense_common.rb +400 -0
- data/lib/shells/pf_sense_serial_session.rb +55 -0
- data/lib/shells/pf_sense_ssh_session.rb +56 -0
- data/lib/shells/serial_session.rb +184 -0
- data/lib/shells/shell_base.rb +846 -0
- data/lib/shells/ssh_session.rb +232 -0
- data/lib/shells/version.rb +5 -0
- data/lib/shells.rb +37 -0
- data/shells.gemspec +32 -0
- metadata +160 -0
@@ -0,0 +1,232 @@
|
|
1
|
+
require 'net/ssh'
|
2
|
+
require 'shells/shell_base'
|
3
|
+
require 'shells/bash_common'
|
4
|
+
|
5
|
+
module Shells
|
6
|
+
##
|
7
|
+
# Executes an SSH session with a host.
|
8
|
+
#
|
9
|
+
# The default setup of this class should work well with any bash-like shell.
|
10
|
+
# In particular, the +exec_prompt+ method sets the "PS1" environment variable, which should set the prompt the shell
|
11
|
+
# uses, and the +get_exit_code+ methods retrieves the value of the "$?" variable which should contain the exit code
|
12
|
+
# from the last action. Because there is a possibility that your shell does not utilize those methods, the
|
13
|
+
# +override_set_prompt+ and +override_get_exit_code+ options are available to change the behavior.
|
14
|
+
#
|
15
|
+
#
|
16
|
+
# Valid options:
|
17
|
+
# +host+::
|
18
|
+
# The name or IP address of the host to connect to. Defaults to 'localhost'.
|
19
|
+
# +port+::
|
20
|
+
# The port on the host to connect to. Defaults to 22.
|
21
|
+
# +user+::
|
22
|
+
# The user to login with. This option is required.
|
23
|
+
# +password+::
|
24
|
+
# The password to login with.
|
25
|
+
# If our public key is an authorized key on the host, the password is ignored.
|
26
|
+
# +prompt+::
|
27
|
+
# The prompt used to determine when processes finish execution.
|
28
|
+
# Defaults to '~~#', but if that doesn't work for some reason because it is valid output from one or more
|
29
|
+
# commands, you can change it to something else. It must be unique and cannot contain certain characters.
|
30
|
+
# The characters you should avoid are !, $, \, /, ", and ' because no attempt is made to escape them and the
|
31
|
+
# resulting prompt can very easily become something else entirely. If they are provided, they will be
|
32
|
+
# replaced to protect the shell from getting stuck.
|
33
|
+
# +shell+::
|
34
|
+
# If set to :shell, then the default shell is executed.
|
35
|
+
# If set to anything else, it is assumed to be the executable path to the shell you want to run.
|
36
|
+
# +quit+::
|
37
|
+
# If set, this defines the command to execute when quitting the session.
|
38
|
+
# The default is "exit" which will probably work most of the time.
|
39
|
+
# +retrieve_exit_code+::
|
40
|
+
# If set to a non-false value, then the default behavior will be to retrieve the exit code from the shell after
|
41
|
+
# executing a command. If set to a false or nil value, the default behavior will be to ignore the exit code
|
42
|
+
# from the shell. When retrieved, the exit code is stored in the +last_exit_code+ property.
|
43
|
+
# This option can be overridden by providing an alternate value to the +exec+ method on a case-by-case basis.
|
44
|
+
# +on_non_zero_exit_code+::
|
45
|
+
# If set to :ignore (the default) then non-zero exit codes will not cause errors. You will still be able to check
|
46
|
+
# the +last_exit_code+ property to determine if the command was successful.
|
47
|
+
# If set to :raise then non-zero exit codes will cause a Shells::NonZeroExitCode to be raised when a command exits
|
48
|
+
# with a non-zero return value.
|
49
|
+
# This option only comes into play when +retrieve_exit_code+ is set to a non-false value.
|
50
|
+
# This option can be overridden by providing an alternate value to the +exec+ method on a case-by-case basis.
|
51
|
+
# +silence_timeout+::
|
52
|
+
# When a command is executing, this is the maximum amount of time to wait for any feedback from the shell.
|
53
|
+
# If set to 0 (or less) there is no timeout.
|
54
|
+
# Unlike +command_timeout+ this value resets every time we receive feedback.
|
55
|
+
# This option can be overridden by providing an alternate value to the +exec+ method on a case-by-case basis.
|
56
|
+
# +command_timeout+::
|
57
|
+
# When a command is executing, this is the maximum amount of time to wait for the command to finish.
|
58
|
+
# If set to 0 (or less) there is no timeout.
|
59
|
+
# Unlike +silence_timeout+ this value does not reset when we receive feedback.
|
60
|
+
# This option can be overridden by providing an alternate value to the +exec+ method on a case-by-case basis.
|
61
|
+
# +connect_timeout+::
|
62
|
+
# This is the maximum amount of time to wait for the initial connection to the SSH shell.
|
63
|
+
# +override_set_prompt+::
|
64
|
+
# If provided, this must be set to either a command string that will set the prompt, or a Proc that accepts
|
65
|
+
# the shell as an argument.
|
66
|
+
# If set to a string, the string is sent to the shell and we wait up to two seconds for the prompt to appear.
|
67
|
+
# If that fails, we resend the string and wait one more time before failing.
|
68
|
+
# If set to a Proc, the Proc is called. If the Proc returns a false value, we fail. If the Proc returns
|
69
|
+
# a non-false value, we consider it successful.
|
70
|
+
# +override_get_exit_code+::
|
71
|
+
# If provided, this must be set to either a command string that will retrieve the exit code, or a Proc that
|
72
|
+
# accepts the shell as an argument.
|
73
|
+
# If set to a string, the string is sent to the shell and the output is parsed as an integer and used as the exit
|
74
|
+
# code.
|
75
|
+
# If set to a Proc, the Proc is called and the return value of the proc is used as the exit code.
|
76
|
+
#
|
77
|
+
# Shells::SshSession.new(
|
78
|
+
# host: '10.10.10.10',
|
79
|
+
# user: 'somebody',
|
80
|
+
# password: 'super-secret'
|
81
|
+
# ) do |shell|
|
82
|
+
# shell.exec('cd /usr/local/bin')
|
83
|
+
# user_bin_files = shell.exec('ls -A1').split("\n")
|
84
|
+
# @app_is_installed = user_bin_files.include?('my_app')
|
85
|
+
# end
|
86
|
+
#
|
87
|
+
class SshSession < Shells::ShellBase
|
88
|
+
|
89
|
+
include Shells::BashCommon
|
90
|
+
|
91
|
+
##
|
92
|
+
# The error raised when we failed to request a PTY.
|
93
|
+
class FailedToRequestPty < Shells::ShellError
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
##
|
98
|
+
# The error raised when we fail to start the shell on the PTY.
|
99
|
+
class FailedToStartShell < Shells::ShellError
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
protected
|
105
|
+
|
106
|
+
def validate_options #:nodoc:
|
107
|
+
options[:host] ||= 'localhost'
|
108
|
+
options[:port] ||= 22
|
109
|
+
options[:shell] ||= :shell
|
110
|
+
options[:quit] ||= 'exit'
|
111
|
+
options[:connect_timeout] ||= 5
|
112
|
+
|
113
|
+
raise InvalidOption, 'Missing host.' if options[:host].to_s.strip == ''
|
114
|
+
raise InvalidOption, 'Missing user.' if options[:user].to_s.strip == ''
|
115
|
+
end
|
116
|
+
|
117
|
+
def exec_shell(&block) #:nodoc:
|
118
|
+
|
119
|
+
ignore_io_error = false
|
120
|
+
begin
|
121
|
+
|
122
|
+
Net::SSH.start(
|
123
|
+
options[:host],
|
124
|
+
options[:user],
|
125
|
+
password: options[:password],
|
126
|
+
port: options[:port],
|
127
|
+
non_interactive: true,
|
128
|
+
timeout: options[:connect_timeout]
|
129
|
+
) do |ssh|
|
130
|
+
|
131
|
+
# open the channel
|
132
|
+
debug 'Opening channel...'
|
133
|
+
ssh.open_channel do |ch|
|
134
|
+
# request a PTY
|
135
|
+
debug 'Requesting PTY...'
|
136
|
+
ch.request_pty do |ch_pty, success_pty|
|
137
|
+
raise FailedToRequestPty unless success_pty
|
138
|
+
|
139
|
+
# pick a method to start the shell with.
|
140
|
+
meth = (options[:shell] == :shell) ? :send_channel_request : :exec
|
141
|
+
|
142
|
+
@channel = ch_pty
|
143
|
+
buffer_input
|
144
|
+
|
145
|
+
# start the shell
|
146
|
+
debug 'Starting shell...'
|
147
|
+
ch_pty.send(meth, options[:shell].to_s) do |ch_sh, success_sh|
|
148
|
+
raise FailedToStartShell unless success_sh
|
149
|
+
|
150
|
+
# give the shell a chance to get ready.
|
151
|
+
sleep 0.25
|
152
|
+
|
153
|
+
begin
|
154
|
+
# yield to the block
|
155
|
+
block.call
|
156
|
+
|
157
|
+
ensure
|
158
|
+
# send the exit command.
|
159
|
+
ignore_io_error = true
|
160
|
+
debug 'Closing connection...'
|
161
|
+
send_data options[:quit] + line_ending
|
162
|
+
end
|
163
|
+
|
164
|
+
@channel.wait
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
|
169
|
+
debug 'Waiting for channel to close...'
|
170
|
+
ch.wait
|
171
|
+
debug 'Channel has been closed.'
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
175
|
+
rescue IOError
|
176
|
+
unless ignore_io_error
|
177
|
+
raise
|
178
|
+
end
|
179
|
+
ensure
|
180
|
+
@channel = nil
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
184
|
+
|
185
|
+
def exec_prompt(&block) #:nodoc:
|
186
|
+
cmd = options[:override_set_prompt] || "PS1=\"#{options[:prompt]}\""
|
187
|
+
if cmd.respond_to?(:call)
|
188
|
+
raise Shells::FailedToSetPrompt unless cmd.call(self)
|
189
|
+
else
|
190
|
+
# set the prompt, wait up to 2 seconds for a response, then try one more time.
|
191
|
+
begin
|
192
|
+
exec cmd, command_timeout: 2, retrieve_exit_code: false
|
193
|
+
rescue Shells::CommandTimeout
|
194
|
+
begin
|
195
|
+
exec cmd, command_timeout: 2, retrieve_exit_code: false
|
196
|
+
rescue Shells::CommandTimeout
|
197
|
+
raise Shells::FailedToSetPrompt
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
# yield to the block
|
203
|
+
block.call
|
204
|
+
end
|
205
|
+
|
206
|
+
def send_data(data) #:nodoc:
|
207
|
+
@channel.send_data data
|
208
|
+
debug "Sent: (#{data.size} bytes) #{(data.size > 32 ? (data[0..30] + '...') : data).inspect}"
|
209
|
+
end
|
210
|
+
|
211
|
+
def loop(&block) #:nodoc:
|
212
|
+
@channel.connection.loop(&block)
|
213
|
+
end
|
214
|
+
|
215
|
+
def stdout_received(&block) #:nodoc:
|
216
|
+
@channel.on_data do |_,data|
|
217
|
+
debug "Received: (#{data.size} bytes) #{(data.size > 32 ? (data[0..30] + '...') : data).inspect}"
|
218
|
+
block.call data
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def stderr_received(&block) #:nodoc:
|
223
|
+
@channel.on_extended_data do |_, type, data|
|
224
|
+
if type == 1
|
225
|
+
debug "Received: (#{data.size} bytes) [E] #{(data.size > 32 ? (data[0..30] + '...') : data).inspect}"
|
226
|
+
block.call data
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
end
|
232
|
+
end
|
data/lib/shells.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'shells/version'
|
2
|
+
require 'shells/errors'
|
3
|
+
require 'shells/shell_base'
|
4
|
+
require 'shells/ssh_session'
|
5
|
+
require 'shells/serial_session'
|
6
|
+
require 'shells/pf_sense_ssh_session'
|
7
|
+
require 'shells/pf_sense_serial_session'
|
8
|
+
|
9
|
+
|
10
|
+
##
|
11
|
+
# A set of basic shell classes.
|
12
|
+
#
|
13
|
+
# All shell sessions can be accessed by class name without calling +new+.
|
14
|
+
# Shells::SshSession(host: ...)
|
15
|
+
# Shells::SerialSession(path: ...)
|
16
|
+
# Shells::PfSenseSshSession(host: ...)
|
17
|
+
# Shells::PfSenseSerialSession(path: ...)
|
18
|
+
#
|
19
|
+
module Shells
|
20
|
+
|
21
|
+
##
|
22
|
+
# Provides the ability for the Shells module to allow sessions to be instantiated without calling +new+.
|
23
|
+
def self.method_missing(m, *args, &block) #:nodoc:
|
24
|
+
|
25
|
+
is_const = const_defined?(m) rescue nil
|
26
|
+
|
27
|
+
if is_const
|
28
|
+
val = const_get(m)
|
29
|
+
if val.is_a?(Class) && Shells::ShellBase > val
|
30
|
+
return val.new(*args, &block)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
super
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
data/shells.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'shells/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'shells'
|
8
|
+
spec.version = Shells::VERSION
|
9
|
+
spec.authors = ['Beau Barker']
|
10
|
+
spec.email = ['beau@barkerest.com']
|
11
|
+
|
12
|
+
spec.summary = 'A set of simple shells for interacting with other devices.'
|
13
|
+
spec.homepage = 'http://www.barkerest.com/'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
|
+
f.match(%r{^(test|spec|features)/})
|
18
|
+
end
|
19
|
+
spec.bindir = 'exe'
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ['lib']
|
22
|
+
|
23
|
+
spec.add_dependency 'net-ssh', '~> 3.0.2'
|
24
|
+
spec.add_dependency 'rubyserial', '~> 0.4.0'
|
25
|
+
|
26
|
+
spec.add_development_dependency 'bundler', '~> 1.14'
|
27
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
28
|
+
spec.add_development_dependency 'minitest', '~> 5.0'
|
29
|
+
spec.add_development_dependency 'minitest-reporters'
|
30
|
+
spec.add_development_dependency 'pry'
|
31
|
+
end
|
32
|
+
|
metadata
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: shells
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.5
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Beau Barker
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-04-19 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: 3.0.2
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 3.0.2
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rubyserial
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.4.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.4.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.14'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.14'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: minitest
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '5.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '5.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: minitest-reporters
|
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: pry
|
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:
|
112
|
+
email:
|
113
|
+
- beau@barkerest.com
|
114
|
+
executables: []
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- ".gitignore"
|
119
|
+
- Gemfile
|
120
|
+
- LICENSE.txt
|
121
|
+
- README.md
|
122
|
+
- Rakefile
|
123
|
+
- bin/console
|
124
|
+
- bin/setup
|
125
|
+
- lib/shells.rb
|
126
|
+
- lib/shells/bash_common.rb
|
127
|
+
- lib/shells/errors.rb
|
128
|
+
- lib/shells/pf_sense_common.rb
|
129
|
+
- lib/shells/pf_sense_serial_session.rb
|
130
|
+
- lib/shells/pf_sense_ssh_session.rb
|
131
|
+
- lib/shells/serial_session.rb
|
132
|
+
- lib/shells/shell_base.rb
|
133
|
+
- lib/shells/ssh_session.rb
|
134
|
+
- lib/shells/version.rb
|
135
|
+
- shells.gemspec
|
136
|
+
homepage: http://www.barkerest.com/
|
137
|
+
licenses:
|
138
|
+
- MIT
|
139
|
+
metadata: {}
|
140
|
+
post_install_message:
|
141
|
+
rdoc_options: []
|
142
|
+
require_paths:
|
143
|
+
- lib
|
144
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
145
|
+
requirements:
|
146
|
+
- - ">="
|
147
|
+
- !ruby/object:Gem::Version
|
148
|
+
version: '0'
|
149
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
150
|
+
requirements:
|
151
|
+
- - ">="
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: '0'
|
154
|
+
requirements: []
|
155
|
+
rubyforge_project:
|
156
|
+
rubygems_version: 2.6.11
|
157
|
+
signing_key:
|
158
|
+
specification_version: 4
|
159
|
+
summary: A set of simple shells for interacting with other devices.
|
160
|
+
test_files: []
|