Skip to content

Commit e62e12b

Browse files
Merge pull request #3721 from ytti/feat/3597-refactor-ssh-scp
Refactor input/ssh and input/scp into SSHBase
2 parents 85b292c + 87e4a2d commit e62e12b

16 files changed

Lines changed: 251 additions & 183 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
1212

1313
### Changed
1414
- Refactored models: Use `keep_lines` and `reject_lines` in aosw, arubainstant, asa, efos, firelinuxos, fsos, ironware, mlnxos and perle to (@robertcheramy)
15+
- Refactor SSH and SCP into a common class SSHBase. Fixes #3597 (@robertcheramy)
16+
- SSH#disconnect directly calls @ssh.close, as #close waits for the channels to be closed anyway. This might have side effects, please open an issue if you observe a regression. (@robertcheramy)
1517
- Modified models to support store mode on significant changes: ios, fortios, perle (@robertcheramy)
1618

1719
### Fixed

lib/oxidized/input/exec.rb

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
module Oxidized
2-
require "oxidized/input/cli"
3-
42
class Exec < Input
5-
include Input::CLI
6-
73
def connect(node)
84
@node = node
95
@log = File.open(Oxidized::Config::LOG + "/#{@node.ip}-exec", "w") if Oxidized.config.input.debug?

lib/oxidized/input/ftp.rb

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,7 @@
11
module Oxidized
22
require 'net/ftp'
33
require 'timeout'
4-
require_relative 'cli'
5-
64
class FTP < Input
7-
RESCUE_FAIL = {
8-
debug: [
9-
# Net::SSH::Disconnect,
10-
],
11-
warn: [
12-
# RuntimeError,
13-
# Net::SSH::AuthenticationFailed,
14-
]
15-
}.freeze
16-
include Input::CLI
17-
185
def connect(node) # rubocop:disable Naming/PredicateMethod
196
@node = node
207
@node.model.cfg['ftp'].each { |cb| instance_exec(&cb) }

lib/oxidized/input/http.rb

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
module Oxidized
2-
require "oxidized/input/cli"
32
require "net/http"
43
require "json"
54
require "net/http/digest_auth"
65

76
class HTTP < Input
8-
include Input::CLI
9-
107
def connect(node)
118
@node = node
129
@secure = false

lib/oxidized/input/input.rb

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,28 @@
1+
require_relative 'cli'
2+
13
module Oxidized
24
class PromptUndetect < OxidizedError; end
35

46
class Input
57
include SemanticLogger::Loggable
68
include Oxidized::Config::Vars
9+
include Oxidized::Input::CLI
710

811
RESCUE_FAIL = {
9-
debug: [
10-
Errno::ECONNREFUSED
11-
],
12-
warn: [
13-
IOError,
14-
PromptUndetect,
15-
Timeout::Error,
16-
Errno::ECONNRESET,
17-
Errno::EHOSTUNREACH,
18-
Errno::ENETUNREACH,
19-
Errno::EPIPE,
20-
Errno::ETIMEDOUT
21-
]
12+
Errno::ECONNREFUSED => :debug,
13+
IOError => :warn,
14+
PromptUndetect => :warn,
15+
Timeout::Error => :warn,
16+
Errno::ECONNRESET => :warn,
17+
Errno::EHOSTUNREACH => :warn,
18+
Errno::ENETUNREACH => :warn,
19+
Errno::EPIPE => :warn,
20+
Errno::ETIMEDOUT => :warn
2221
}.freeze
22+
23+
# Returns a hash mapping exception classes to their log level
24+
def self.rescue_fail
25+
RESCUE_FAIL.dup
26+
end
2327
end
2428
end

lib/oxidized/input/scp.rb

Lines changed: 5 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -2,53 +2,15 @@ module Oxidized
22
require 'net/ssh'
33
require 'net/scp'
44
require 'timeout'
5-
require_relative 'cli'
5+
require_relative 'sshbase'
66

7-
class SCP < Input
7+
class SCP < SSHBase
88
RESCUE_FAIL = {
9-
debug: [
10-
Net::SSH::Disconnect,
11-
Net::SSH::ConnectionTimeout
12-
],
13-
warn: [
14-
Net::SCP::Error,
15-
Net::SSH::HostKeyUnknown,
16-
Net::SSH::AuthenticationFailed,
17-
Timeout::Error
18-
]
9+
Net::SCP::Error => :warn
1910
}.freeze
20-
include Input::CLI
2111

22-
def connect(node) # rubocop:disable Naming/PredicateMethod
23-
@node = node
24-
@node.model.cfg['scp'].each { |cb| instance_exec(&cb) }
25-
@log = File.open(Oxidized::Config::LOG + "/#{@node.ip}-scp", 'w') if Oxidized.config.input.debug?
26-
@ssh = Net::SSH.start(@node.ip, @node.auth[:username], make_ssh_opts)
27-
connected?
28-
end
29-
30-
def make_ssh_opts
31-
secure = Oxidized.config.input.scp.secure?
32-
ssh_opts = {
33-
number_of_password_prompts: 0,
34-
verify_host_key: secure ? :always : :never,
35-
append_all_supported_algorithms: true,
36-
password: @node.auth[:password],
37-
timeout: @node.timeout,
38-
port: (vars(:ssh_port) || 22).to_i,
39-
forward_agent: false
40-
}
41-
42-
# Use our logger for Net::SSH
43-
ssh_logger = SemanticLogger[Net::SSH]
44-
ssh_logger.level = Oxidized.config.input.debug? ? :debug : :fatal
45-
ssh_opts[:logger] = ssh_logger
46-
47-
ssh_opts
48-
end
49-
50-
def connected?
51-
@ssh && (not @ssh.closed?)
12+
def self.rescue_fail
13+
super.merge(RESCUE_FAIL)
5214
end
5315

5416
def cmd(file)
@@ -57,25 +19,5 @@ def cmd(file)
5719
@ssh.scp.download!(file)
5820
end
5921
end
60-
61-
def send(my_proc)
62-
my_proc.call
63-
end
64-
65-
def output
66-
""
67-
end
68-
69-
private
70-
71-
def disconnect
72-
Timeout.timeout(@node.timeout) do
73-
@ssh.close
74-
end
75-
rescue Timeout::Error
76-
logger.debug "#{@node.name} timed out while disconnecting"
77-
ensure
78-
@log.close if Oxidized.config.input.debug?
79-
end
8022
end
8123
end

lib/oxidized/input/ssh.rb

Lines changed: 9 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,18 @@ module Oxidized
22
require 'net/ssh'
33
require 'net/ssh/proxy/command'
44
require 'timeout'
5-
require 'oxidized/input/cli'
6-
class SSH < Input
5+
require_relative 'sshbase'
6+
7+
class SSH < SSHBase
8+
class NoShell < OxidizedError; end
9+
710
RESCUE_FAIL = {
8-
debug: [
9-
Net::SSH::Disconnect
10-
],
11-
warn: [
12-
RuntimeError,
13-
Net::SSH::AuthenticationFailed
14-
]
11+
RuntimeError => :warn
1512
}.freeze
16-
include Input::CLI
1713

18-
class NoShell < OxidizedError; end
14+
def self.rescue_fail
15+
super.merge(RESCUE_FAIL)
16+
end
1917

2018
def connect(node) # rubocop:disable Naming/PredicateMethod
2119
@node = node
@@ -41,10 +39,6 @@ def connect(node) # rubocop:disable Naming/PredicateMethod
4139
connected?
4240
end
4341

44-
def connected?
45-
@ssh && (not @ssh.closed?)
46-
end
47-
4842
def cmd(cmd, expect = node.prompt)
4943
logger.debug "Sending '#{cmd.dump}' @ #{node.name} with expect: #{expect.inspect}"
5044
if Oxidized.config.input.debug?
@@ -76,20 +70,6 @@ def pty_options(hash)
7670

7771
private
7872

79-
def disconnect
80-
disconnect_cli
81-
# if disconnect does not disconnect us, give up after timeout
82-
Timeout.timeout(@node.timeout) { @ssh.loop }
83-
rescue Errno::ECONNRESET, Net::SSH::Disconnect, IOError => e
84-
logger.debug 'The other side closed the connection while ' \
85-
"disconnecting, raising #{e.class} with #{e.message}"
86-
rescue Timeout::Error
87-
logger.debug "#{@node.name} timed out while disconnecting"
88-
ensure
89-
@log.close if Oxidized.config.input.debug?
90-
(@ssh.close rescue true) unless @ssh.closed? # rubocop:disable Style/RedundantParentheses
91-
end
92-
9373
def shell_open(ssh)
9474
@ses = ssh.open_channel do |ch|
9575
ch.on_data do |_ch, data|
@@ -137,48 +117,5 @@ def expect(*regexps)
137117
end
138118
end
139119
end
140-
141-
def make_ssh_opts
142-
secure = Oxidized.config.input.ssh.secure?
143-
ssh_opts = {
144-
number_of_password_prompts: 0,
145-
keepalive: vars(:ssh_no_keepalive) ? false : true,
146-
verify_host_key: secure ? :always : :never,
147-
append_all_supported_algorithms: true,
148-
password: @node.auth[:password],
149-
timeout: @node.timeout,
150-
port: (vars(:ssh_port) || 22).to_i,
151-
forward_agent: false
152-
}
153-
154-
auth_methods = vars(:auth_methods) || %w[none publickey password]
155-
ssh_opts[:auth_methods] = auth_methods
156-
logger.debug "AUTH METHODS::#{auth_methods}"
157-
158-
ssh_opts[:proxy] = make_ssh_proxy_command(vars(:ssh_proxy), vars(:ssh_proxy_port), secure) if vars(:ssh_proxy)
159-
160-
ssh_opts[:keys] = [vars(:ssh_keys)].flatten if vars(:ssh_keys)
161-
ssh_opts[:kex] = vars(:ssh_kex).split(/,\s*/) if vars(:ssh_kex)
162-
ssh_opts[:encryption] = vars(:ssh_encryption).split(/,\s*/) if vars(:ssh_encryption)
163-
ssh_opts[:host_key] = vars(:ssh_host_key).split(/,\s*/) if vars(:ssh_host_key)
164-
ssh_opts[:hmac] = vars(:ssh_hmac).split(/,\s*/) if vars(:ssh_hmac)
165-
166-
# Use our logger for Net::SSH
167-
ssh_logger = SemanticLogger[Net::SSH]
168-
ssh_logger.level = Oxidized.config.input.debug? ? :debug : :fatal
169-
ssh_opts[:logger] = ssh_logger
170-
171-
ssh_opts
172-
end
173-
174-
def make_ssh_proxy_command(proxy_host, proxy_port, secure)
175-
return nil unless !proxy_host.nil? && !proxy_host.empty?
176-
177-
proxy_command = "ssh "
178-
proxy_command += "-o StrictHostKeyChecking=no " unless secure
179-
proxy_command += "-p #{proxy_port} " if proxy_port
180-
proxy_command += "#{proxy_host} -W [%h]:%p"
181-
Net::SSH::Proxy::Command.new(proxy_command)
182-
end
183120
end
184121
end

0 commit comments

Comments
 (0)