oxidized 0.32.1 → 0.34.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.
Files changed (127) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +45 -0
  3. data/.github/ISSUE_TEMPLATE/feature_request.md +22 -0
  4. data/.github/ISSUE_TEMPLATE/support-request.md +39 -0
  5. data/.github/workflows/publishdocker.yml +35 -16
  6. data/.github/workflows/ruby.yml +4 -2
  7. data/.gitignore +2 -0
  8. data/.rubocop.yml +29 -8
  9. data/.rubocop_todo.yml +1 -60
  10. data/CHANGELOG.md +103 -2
  11. data/CONTRIBUTING.md +20 -10
  12. data/Dockerfile +37 -64
  13. data/README.md +47 -141
  14. data/Rakefile +9 -11
  15. data/docs/Configuration.md +236 -27
  16. data/docs/DeviceSimulation.md +19 -7
  17. data/docs/Docker.md +245 -0
  18. data/docs/Issues.md +27 -1
  19. data/docs/Model-Notes/EatonNetwork.md +18 -0
  20. data/docs/Model-Notes/HPEAruba.md +3 -2
  21. data/docs/ModelUnitTests.md +35 -25
  22. data/docs/Outputs.md +83 -2
  23. data/docs/Release.md +34 -24
  24. data/docs/Supported-OS-Types.md +7 -0
  25. data/docs/Troubleshooting.md +4 -13
  26. data/extra/device2yaml.rb +24 -9
  27. data/extra/rest_client.rb +3 -2
  28. data/extra/syslog.rb +8 -3
  29. data/lib/oxidized/cli.rb +7 -3
  30. data/lib/oxidized/config/vars.rb +22 -14
  31. data/lib/oxidized/config.rb +3 -2
  32. data/lib/oxidized/core.rb +30 -8
  33. data/lib/oxidized/hook/ciscosparkdiff.rb +11 -9
  34. data/lib/oxidized/hook/exec.rb +5 -4
  35. data/lib/oxidized/hook/githubrepo.rb +23 -17
  36. data/lib/oxidized/hook/noophook.rb +2 -2
  37. data/lib/oxidized/hook/slackdiff.rb +9 -8
  38. data/lib/oxidized/hook/xmppdiff.rb +9 -9
  39. data/lib/oxidized/hook.rb +10 -8
  40. data/lib/oxidized/input/cli.rb +8 -3
  41. data/lib/oxidized/input/exec.rb +1 -1
  42. data/lib/oxidized/input/ftp.rb +2 -2
  43. data/lib/oxidized/input/http.rb +6 -6
  44. data/lib/oxidized/input/input.rb +1 -0
  45. data/lib/oxidized/input/scp.rb +2 -2
  46. data/lib/oxidized/input/ssh.rb +21 -14
  47. data/lib/oxidized/input/telnet.rb +3 -3
  48. data/lib/oxidized/input/tftp.rb +1 -1
  49. data/lib/oxidized/job.rb +7 -4
  50. data/lib/oxidized/logger.rb +51 -0
  51. data/lib/oxidized/model/acos.rb +1 -0
  52. data/lib/oxidized/model/aos7.rb +9 -0
  53. data/lib/oxidized/model/aoscx.rb +2 -0
  54. data/lib/oxidized/model/aosw.rb +22 -17
  55. data/lib/oxidized/model/aricentiss.rb +2 -2
  56. data/lib/oxidized/model/asa.rb +3 -3
  57. data/lib/oxidized/model/awplus.rb +13 -10
  58. data/lib/oxidized/model/eatonnetwork.rb +65 -0
  59. data/lib/oxidized/model/edgecos.rb +2 -1
  60. data/lib/oxidized/model/edgeos.rb +7 -6
  61. data/lib/oxidized/model/edgeswitch.rb +3 -1
  62. data/lib/oxidized/model/efos.rb +41 -0
  63. data/lib/oxidized/model/eltex.rb +1 -1
  64. data/lib/oxidized/model/fabricos.rb +1 -1
  65. data/lib/oxidized/model/fastiron.rb +3 -1
  66. data/lib/oxidized/model/firelinuxos.rb +12 -3
  67. data/lib/oxidized/model/fortios.rb +5 -4
  68. data/lib/oxidized/model/gaiaos.rb +4 -4
  69. data/lib/oxidized/model/ingate.rb +47 -0
  70. data/lib/oxidized/model/ios.rb +16 -5
  71. data/lib/oxidized/model/ironware.rb +1 -1
  72. data/lib/oxidized/model/junos.rb +4 -0
  73. data/lib/oxidized/model/linksyssrw.rb +3 -3
  74. data/lib/oxidized/model/mlnxos.rb +14 -7
  75. data/lib/oxidized/model/model.rb +4 -3
  76. data/lib/oxidized/model/netgear.rb +8 -0
  77. data/lib/oxidized/model/nsxdfw.rb +2 -1
  78. data/lib/oxidized/model/nsxfirewall.rb +2 -1
  79. data/lib/oxidized/model/nxos.rb +2 -2
  80. data/lib/oxidized/model/openwrt.rb +6 -6
  81. data/lib/oxidized/model/powerconnect.rb +31 -10
  82. data/lib/oxidized/model/procurve.rb +3 -1
  83. data/lib/oxidized/model/qtech.rb +3 -1
  84. data/lib/oxidized/model/quantaos.rb +8 -6
  85. data/lib/oxidized/model/routeros.rb +3 -2
  86. data/lib/oxidized/model/saos10.rb +38 -0
  87. data/lib/oxidized/model/sixwind.rb +28 -0
  88. data/lib/oxidized/model/sonicos.rb +1 -1
  89. data/lib/oxidized/model/srosmd.rb +1 -1
  90. data/lib/oxidized/model/supermicro.rb +1 -1
  91. data/lib/oxidized/model/timos.rb +1 -1
  92. data/lib/oxidized/model/tmos.rb +1 -0
  93. data/lib/oxidized/model/tnsr.rb +53 -0
  94. data/lib/oxidized/model/trango.rb +3 -1
  95. data/lib/oxidized/model/unifiap.rb +144 -0
  96. data/lib/oxidized/model/vrp.rb +3 -1
  97. data/lib/oxidized/model/xos.rb +3 -1
  98. data/lib/oxidized/model/zhoneolt.rb +3 -1
  99. data/lib/oxidized/model/zynos.rb +3 -3
  100. data/lib/oxidized/node.rb +44 -27
  101. data/lib/oxidized/nodes.rb +8 -4
  102. data/lib/oxidized/output/file.rb +28 -0
  103. data/lib/oxidized/output/git.rb +148 -41
  104. data/lib/oxidized/output/gitcrypt.rb +18 -13
  105. data/lib/oxidized/output/http.rb +5 -4
  106. data/lib/oxidized/output/output.rb +14 -0
  107. data/lib/oxidized/source/http.rb +4 -2
  108. data/lib/oxidized/version.rb +6 -4
  109. data/lib/oxidized/worker.rb +13 -13
  110. data/lib/oxidized.rb +3 -24
  111. data/lib/refinements.rb +2 -0
  112. data/oxidized.gemspec +10 -8
  113. metadata +74 -41
  114. data/examples/podman-compose/Makefile +0 -103
  115. data/examples/podman-compose/README.md +0 -94
  116. data/examples/podman-compose/docker-compose.yml +0 -30
  117. data/examples/podman-compose/gitserver/.gitignore +0 -1
  118. data/examples/podman-compose/gitserver/Dockerfile +0 -14
  119. data/examples/podman-compose/model-simulation/Dockerfile-model +0 -13
  120. data/examples/podman-compose/model-simulation/asternos.sh +0 -36
  121. data/examples/podman-compose/oxidized-config/.gitignore +0 -10
  122. data/examples/podman-compose/oxidized-config/config +0 -46
  123. data/examples/podman-compose/oxidized-config/config_csv-file +0 -46
  124. data/examples/podman-compose/oxidized-config/config_csv-gitserver +0 -56
  125. data/examples/podman-compose/oxidized-config/router.db +0 -1
  126. data/examples/podman-compose/oxidized-ssh/.gitignore +0 -1
  127. data/examples/podman-compose/oxidized-ssh/README.md +0 -14
@@ -5,35 +5,35 @@ class XMPPDiff < Oxidized::Hook
5
5
  def connect
6
6
  @client = Jabber::Client.new(Jabber::JID.new(cfg.jid))
7
7
 
8
- log "Connecting to XMPP"
8
+ logger.info "Connecting to XMPP"
9
9
  begin
10
10
  Timeout.timeout(15) do
11
11
  begin
12
12
  @client.connect
13
13
  rescue StandardError => e
14
- log "Failed to connect to XMPP: #{e}"
14
+ logger.info "Failed to connect to XMPP: #{e}"
15
15
  end
16
16
  sleep 1
17
17
 
18
- log "Authenticating to XMPP"
18
+ logger.info "Authenticating to XMPP"
19
19
  @client.auth(cfg.password)
20
20
  sleep 1
21
21
 
22
- log "Connected to XMPP"
22
+ logger.info "Connected to XMPP"
23
23
 
24
24
  @muc = Jabber::MUC::SimpleMUCClient.new(@client)
25
25
  @muc.join(cfg.channel + "/" + cfg.nick)
26
26
 
27
- log "Joined #{cfg.channel}"
27
+ logger.info "Joined #{cfg.channel}"
28
28
  end
29
29
  rescue Timeout::Error
30
- log "timed out"
30
+ logger.info "timed out"
31
31
  @client = nil
32
32
  @muc = nil
33
33
  end
34
34
 
35
35
  @client.on_exception do
36
- log "XMPP connection aborted, reconnecting"
36
+ logger.info "XMPP connection aborted, reconnecting"
37
37
  @client = nil
38
38
  @muc = nil
39
39
  connect
@@ -66,14 +66,14 @@ class XMPPDiff < Oxidized::Hook
66
66
  # Maybe connecting failed, so only proceed if we actually joined the MUC
67
67
  unless @muc.nil?
68
68
  title = "#{ctx.node.name} #{ctx.node.group} #{ctx.node.model.class.name.to_s.downcase}"
69
- log "Posting diff as snippet to #{cfg.channel}"
69
+ logger.info "Posting diff as snippet to #{cfg.channel}"
70
70
 
71
71
  @muc.say(title + "\n\n" + diff[:patch].lines.to_a[4..-1].join)
72
72
  end
73
73
  end
74
74
  end
75
75
  rescue Timeout::Error
76
- log "timed out"
76
+ logger.info "timed out"
77
77
  end
78
78
  end
79
79
  end
data/lib/oxidized/hook.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  module Oxidized
2
2
  class HookManager
3
+ include SemanticLogger::Loggable
4
+
3
5
  class << self
4
6
  def from_config(cfg)
5
7
  mgr = new
@@ -14,7 +16,9 @@ module Oxidized
14
16
 
15
17
  # HookContext is passed to each hook. It can contain anything related to the
16
18
  # event in question. At least it contains the event name
17
- class HookContext < OpenStruct; end
19
+ # The argument keyword_init: true is needed for ruby < 3.2 and can be
20
+ # dropped with the support of ruby 3.1
21
+ HookContext = Struct.new(:event, :node, :job, :commitref, keyword_init: true)
18
22
 
19
23
  # RegisteredHook is a container for a Hook instance
20
24
  RegisteredHook = Struct.new(:name, :hook)
@@ -47,7 +51,7 @@ module Oxidized
47
51
  hook.cfg = cfg
48
52
 
49
53
  @registered_hooks[event] << RegisteredHook.new(name, hook)
50
- Oxidized.logger.debug "Hook #{name.inspect} registered #{hook.class} for event #{event.inspect}"
54
+ logger.debug "Hook #{name.inspect} registered #{hook.class} for event #{event.inspect}"
51
55
  end
52
56
 
53
57
  def handle(event, ctx_params = {})
@@ -57,14 +61,16 @@ module Oxidized
57
61
  @registered_hooks[event].each do |r_hook|
58
62
  r_hook.hook.run_hook ctx
59
63
  rescue StandardError => e
60
- Oxidized.logger.error "Hook #{r_hook.name} (#{r_hook.hook}) failed " \
61
- "(#{e.inspect}) for event #{event.inspect}"
64
+ logger.error "Hook #{r_hook.name} (#{r_hook.hook}) failed " \
65
+ "(#{e.inspect}) for event #{event.inspect}"
62
66
  end
63
67
  end
64
68
  end
65
69
 
66
70
  # Hook abstract base class
67
71
  class Hook
72
+ include SemanticLogger::Loggable
73
+
68
74
  attr_reader :cfg
69
75
 
70
76
  def cfg=(cfg)
@@ -75,9 +81,5 @@ module Oxidized
75
81
  def run_hook(_ctx)
76
82
  raise NotImplementedError
77
83
  end
78
-
79
- def log(msg, level = :info)
80
- Oxidized.logger.send(level, "#{self.class.name}: #{msg}")
81
- end
82
84
  end
83
85
  end
@@ -20,15 +20,16 @@ module Oxidized
20
20
  end
21
21
 
22
22
  def connect_cli
23
- Oxidized.logger.debug "lib/oxidized/input/cli.rb: Running post_login commands at #{node.name}"
23
+ logger.debug "Running post_login commands at #{node.name}"
24
24
  @post_login.each do |command, block|
25
- Oxidized.logger.debug "lib/oxidized/input/cli.rb: Running post_login command: #{command.inspect}, block: #{block.inspect} at #{node.name}"
25
+ logger.debug "Running post_login command: #{command.inspect}, " \
26
+ "block: #{block.inspect} at #{node.name}"
26
27
  block ? block.call : (cmd command)
27
28
  end
28
29
  end
29
30
 
30
31
  def disconnect_cli
31
- Oxidized.logger.debug "lib/oxidized/input/cli.rb Running pre_logout commands at #{node.name}"
32
+ logger.debug "Running pre_logout commands at #{node.name}"
32
33
  @pre_logout.each { |command, block| block ? block.call : (cmd command, nil) }
33
34
  end
34
35
 
@@ -52,6 +53,10 @@ module Oxidized
52
53
  @password || (@password = regex)
53
54
  end
54
55
 
56
+ def newline(newline_str = "\n")
57
+ @newline || (@newline = newline_str)
58
+ end
59
+
55
60
  def login
56
61
  match_re = [@node.prompt]
57
62
  match_re << @username if @username
@@ -11,7 +11,7 @@ module Oxidized
11
11
  end
12
12
 
13
13
  def cmd(cmd_str)
14
- Oxidized.logger.debug "EXEC: #{cmd_str} @ #{@node.name}"
14
+ logger.debug "EXEC: #{cmd_str} @ #{@node.name}"
15
15
  # I'd really like to do popen3 with separate arguments, but that would
16
16
  # require refactoring cmd to take parameters
17
17
  %x(#{cmd_str})
@@ -15,7 +15,7 @@ module Oxidized
15
15
  }.freeze
16
16
  include Input::CLI
17
17
 
18
- def connect(node)
18
+ def connect(node) # rubocop:disable Naming/PredicateMethod
19
19
  @node = node
20
20
  @node.model.cfg['ftp'].each { |cb| instance_exec(&cb) }
21
21
  @log = File.open(Oxidized::Config::LOG + "/#{@node.ip}-ftp", 'w') if Oxidized.config.input.debug?
@@ -30,7 +30,7 @@ module Oxidized
30
30
  end
31
31
 
32
32
  def cmd(file)
33
- Oxidized.logger.debug "FTP: #{file} @ #{@node.name}"
33
+ logger.debug "FTP: #{file} @ #{@node.name}"
34
34
  @ftp.getbinaryfile file, nil
35
35
  end
36
36
 
@@ -51,7 +51,7 @@ module Oxidized
51
51
  schema = @secure ? "https://" : "http://"
52
52
  uri = URI("#{schema}#{@node.ip}#{path}")
53
53
 
54
- Oxidized.logger.debug "Making request to: #{uri}"
54
+ logger.debug "Making request to: #{uri}"
55
55
 
56
56
  ssl_verify = Oxidized.config.input.http.ssl_verify? ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
57
57
 
@@ -59,17 +59,17 @@ module Oxidized
59
59
 
60
60
  if res.code == '401' && res['www-authenticate']&.include?('Digest')
61
61
  uri.user = @username
62
- uri.password = @password
63
- Oxidized.logger.debug "Server requires Digest authentication"
62
+ uri.password = URI.encode_www_form_component(@password)
63
+ logger.debug "Server requires Digest authentication"
64
64
  auth = Net::HTTP::DigestAuth.new.auth_header(uri, res['www-authenticate'], 'GET')
65
65
 
66
66
  res = make_request(uri, ssl_verify, 'Authorization' => auth)
67
67
  elsif @username && @password
68
- Oxidized.logger.debug "Falling back to Basic authentication"
68
+ logger.debug "Falling back to Basic authentication"
69
69
  res = make_request(uri, ssl_verify, 'Authorization' => basic_auth_header)
70
70
  end
71
71
 
72
- Oxidized.logger.debug "Response code: #{res.code}"
72
+ logger.debug "Response code: #{res.code}"
73
73
  res.body
74
74
  end
75
75
 
@@ -77,7 +77,7 @@ module Oxidized
77
77
  Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https", verify_mode: ssl_verify) do |http|
78
78
  req = Net::HTTP::Get.new(uri)
79
79
  @headers.merge(extra_headers).each { |header, value| req.add_field(header, value) }
80
- Oxidized.logger.debug "Sending request with headers: #{@headers.merge(extra_headers)}"
80
+ logger.debug "Sending request with headers: #{@headers.merge(extra_headers)}"
81
81
  http.request(req)
82
82
  end
83
83
  end
@@ -2,6 +2,7 @@ module Oxidized
2
2
  class PromptUndetect < OxidizedError; end
3
3
 
4
4
  class Input
5
+ include SemanticLogger::Loggable
5
6
  include Oxidized::Config::Vars
6
7
 
7
8
  RESCUE_FAIL = {
@@ -16,7 +16,7 @@ module Oxidized
16
16
  }.freeze
17
17
  include Input::CLI
18
18
 
19
- def connect(node)
19
+ def connect(node) # rubocop:disable Naming/PredicateMethod
20
20
  @node = node
21
21
  @node.model.cfg['scp'].each { |cb| instance_exec(&cb) }
22
22
  @log = File.open(Oxidized::Config::LOG + "/#{@node.ip}-scp", 'w') if Oxidized.config.input.debug?
@@ -29,7 +29,7 @@ module Oxidized
29
29
  end
30
30
 
31
31
  def cmd(file)
32
- Oxidized.logger.debug "SCP: #{file} @ #{@node.name}"
32
+ logger.debug "SCP: #{file} @ #{@node.name}"
33
33
  @ssh.scp.download!(file)
34
34
  end
35
35
 
@@ -16,7 +16,7 @@ module Oxidized
16
16
  include Input::CLI
17
17
  class NoShell < OxidizedError; end
18
18
 
19
- def connect(node)
19
+ def connect(node) # rubocop:disable Naming/PredicateMethod
20
20
  @node = node
21
21
  @output = String.new('')
22
22
  @pty_options = { term: "vt100" }
@@ -24,10 +24,10 @@ module Oxidized
24
24
  if Oxidized.config.input.debug?
25
25
  logfile = Oxidized::Config::LOG + "/#{@node.ip}-ssh"
26
26
  @log = File.open(logfile, 'w')
27
- Oxidized.logger.debug "lib/oxidized/input/ssh.rb: I/O Debuging to #{logfile}"
27
+ logger.debug "I/O Debuging to #{logfile}"
28
28
  end
29
29
 
30
- Oxidized.logger.debug "lib/oxidized/input/ssh.rb: Connecting to #{@node.name}"
30
+ logger.debug "Connecting to #{@node.name}"
31
31
  @ssh = Net::SSH.start(@node.ip, @node.auth[:username], make_ssh_opts)
32
32
  unless @exec
33
33
  shell_open @ssh
@@ -45,9 +45,9 @@ module Oxidized
45
45
  end
46
46
 
47
47
  def cmd(cmd, expect = node.prompt)
48
- Oxidized.logger.debug "lib/oxidized/input/ssh.rb #{cmd.dump} @ #{node.name} with expect: #{expect.inspect}"
48
+ logger.debug "Sending '#{cmd.dump}' @ #{node.name} with expect: #{expect.inspect}"
49
49
  if Oxidized.config.input.debug?
50
- @log.puts "sent #{cmd.dump}"
50
+ @log.puts "sent cmd #{@exec ? cmd.dump : (cmd + newline).dump}"
51
51
  @log.flush
52
52
  end
53
53
  cmd_output = if @exec
@@ -60,6 +60,10 @@ module Oxidized
60
60
  end
61
61
 
62
62
  def send(data)
63
+ if Oxidized.config.input.debug?
64
+ @log.puts "sent data #{data.dump}"
65
+ @log.flush
66
+ end
63
67
  @ses.send_data data
64
68
  end
65
69
 
@@ -75,8 +79,11 @@ module Oxidized
75
79
  disconnect_cli
76
80
  # if disconnect does not disconnect us, give up after timeout
77
81
  Timeout.timeout(Oxidized.config.timeout) { @ssh.loop }
78
- rescue Errno::ECONNRESET, Net::SSH::Disconnect, IOError
79
- # These exceptions are intented and therefore not handled here
82
+ rescue Errno::ECONNRESET, Net::SSH::Disconnect, IOError => e
83
+ logger.debug 'The other side closed the connection while ' \
84
+ "disconnecting, rasing #{e.class} with #{e.messages}"
85
+ rescue Timeout::Error
86
+ logger.debug "#{@node.name} timed out while disconnecting"
80
87
  ensure
81
88
  @log.close if Oxidized.config.input.debug?
82
89
  (@ssh.close rescue true) unless @ssh.closed?
@@ -110,7 +117,7 @@ module Oxidized
110
117
 
111
118
  def cmd_shell(cmd, expect_re)
112
119
  @output = String.new('')
113
- @ses.send_data cmd + "\n"
120
+ @ses.send_data cmd + newline
114
121
  @ses.process
115
122
  expect expect_re if expect_re
116
123
  @output
@@ -118,7 +125,7 @@ module Oxidized
118
125
 
119
126
  def expect(*regexps)
120
127
  regexps = [regexps].flatten
121
- Oxidized.logger.debug "lib/oxidized/input/ssh.rb: expecting #{regexps.inspect} at #{node.name}"
128
+ logger.debug "Expecting #{regexps.inspect} at #{node.name}"
122
129
  Timeout.timeout(Oxidized.config.timeout) do
123
130
  @ssh.loop(0.1) do
124
131
  sleep 0.1
@@ -145,7 +152,7 @@ module Oxidized
145
152
 
146
153
  auth_methods = vars(:auth_methods) || %w[none publickey password]
147
154
  ssh_opts[:auth_methods] = auth_methods
148
- Oxidized.logger.debug "AUTH METHODS::#{auth_methods}"
155
+ logger.debug "AUTH METHODS::#{auth_methods}"
149
156
 
150
157
  ssh_opts[:proxy] = make_ssh_proxy_command(vars(:ssh_proxy), vars(:ssh_proxy_port), secure) if vars(:ssh_proxy)
151
158
 
@@ -155,10 +162,10 @@ module Oxidized
155
162
  ssh_opts[:host_key] = vars(:ssh_host_key).split(/,\s*/) if vars(:ssh_host_key)
156
163
  ssh_opts[:hmac] = vars(:ssh_hmac).split(/,\s*/) if vars(:ssh_hmac)
157
164
 
158
- if Oxidized.config.input.debug?
159
- ssh_opts[:logger] = Oxidized.logger
160
- ssh_opts[:verbose] = Logger::DEBUG
161
- end
165
+ # Use our logger for Net:SSH
166
+ ssh_logger = SemanticLogger[Net::SSH]
167
+ ssh_logger.level = Oxidized.config.input.debug? ? :debug : :error
168
+ ssh_opts[:logger] = ssh_logger
162
169
 
163
170
  ssh_opts
164
171
  end
@@ -6,7 +6,7 @@ module Oxidized
6
6
  include Input::CLI
7
7
  attr_reader :telnet
8
8
 
9
- def connect(node)
9
+ def connect(node) # rubocop:disable Naming/PredicateMethod
10
10
  @node = node
11
11
  @timeout = Oxidized.config.timeout
12
12
  @node.model.cfg['telnet'].each { |cb| instance_exec(&cb) }
@@ -35,7 +35,7 @@ module Oxidized
35
35
  end
36
36
 
37
37
  def cmd(cmd_str, expect = @node.prompt)
38
- Oxidized.logger.debug "Telnet: #{cmd_str} @#{@node.name}"
38
+ logger.debug "Telnet: #{cmd_str} @#{@node.name}"
39
39
  return send(cmd_str + "\r\n") unless expect
40
40
 
41
41
  # create a string to be passed to oxidized_expect and modified _there_
@@ -78,7 +78,7 @@ module Net
78
78
  ## FIXME: we also need output (not sure I'm going to support this)
79
79
  attr_reader :output
80
80
 
81
- def oxidized_expect(options) ## rubocop:disable Metrics/PerceivedComplexity
81
+ def oxidized_expect(options)
82
82
  model = @options["Model"]
83
83
  @log = @options["Log"]
84
84
 
@@ -21,7 +21,7 @@ module Oxidized
21
21
  end
22
22
 
23
23
  def cmd(file)
24
- Oxidized.logger.debug "TFTP: #{file} @ #{@node.name}"
24
+ logger.debug "TFTP: #{file} @ #{@node.name}"
25
25
  config = StringIO.new
26
26
  @tftp.getbinary file, config
27
27
  config.rewind
data/lib/oxidized/job.rb CHANGED
@@ -1,16 +1,19 @@
1
1
  module Oxidized
2
2
  class Job < Thread
3
+ include SemanticLogger::Loggable
4
+
3
5
  attr_reader :start, :end, :status, :time, :node, :config
4
6
 
5
7
  def initialize(node)
6
- @node = node
7
- @start = Time.now.utc
8
+ @node = node
9
+ @start = Time.now.utc
10
+ self.name = "Oxidized::Job '#{@node.name}'"
8
11
  super do
9
- Oxidized.logger.debug "lib/oxidized/job.rb: Starting fetching process for #{@node.name} at #{Time.now.utc}"
12
+ logger.debug "Starting fetching process for #{@node.name} at #{Time.now.utc}"
10
13
  @status, @config = @node.run
11
14
  @end = Time.now.utc
12
15
  @time = @end - @start
13
- Oxidized.logger.debug "lib/oxidized/job.rb: Config fetched for #{@node.name} at #{@end}"
16
+ logger.debug "Config fetched for #{@node.name} at #{@end}"
14
17
  end
15
18
  end
16
19
  end
@@ -0,0 +1,51 @@
1
+ require 'semantic_logger'
2
+
3
+ module Oxidized
4
+ module Logger
5
+ include SemanticLogger::Loggable
6
+
7
+ def self.setup
8
+ config = Oxidized.config
9
+ FileUtils.mkdir_p(Config::LOG) unless File.directory?(Config::LOG)
10
+
11
+ SemanticLogger.add_signal_handler
12
+
13
+ if config.use_syslog?
14
+ SemanticLogger.add_appender(appender: :syslog)
15
+ logger.warn("The configuration 'use_syslog' is deprecated. " \
16
+ "Remove it and use 'logger' instead")
17
+ elsif config.log?
18
+ SemanticLogger.add_appender(file_name: File.expand_path(config.log))
19
+ logger.warn("The configuration 'log' is deprecated. " \
20
+ "Remove it and use 'logger' instead")
21
+ elsif config.logger?
22
+ SemanticLogger.default_level = config.logger.level if config.logger.level?
23
+ config.logger.appenders.each { |a| add_appender a } if config.logger.has_key?('appenders')
24
+ end
25
+
26
+ # No appenders configured
27
+ SemanticLogger.add_appender(io: $stderr) if SemanticLogger.appenders.empty?
28
+
29
+ return if %i[trace debug].include?(SemanticLogger.default_level)
30
+
31
+ SemanticLogger.default_level = :debug if config.debug?
32
+ end
33
+
34
+ def self.add_appender(appender)
35
+ case appender['type']
36
+ when 'file'
37
+ params = { file_name: File.expand_path(appender['file']) }
38
+ when 'stderr'
39
+ params = { io: $stderr }
40
+ when 'stdout'
41
+ params = { io: $stdout }
42
+ when 'syslog'
43
+ params = { appender: :syslog, application: "oxidized" }
44
+ else
45
+ raise InvalidConfig, "Unknown logger #{appender['type']}, edit #{Oxidized::Config.configfile}"
46
+ end
47
+ params[:level] = appender['level'] if appender.has_key?('level')
48
+ SemanticLogger.add_appender(**params)
49
+ end
50
+ end
51
+ end
@@ -20,6 +20,7 @@ class ACOS < Oxidized::Model
20
20
  cfg.gsub! /\s(Memory).*/, ' \\1 <removed>'
21
21
  cfg.gsub! /\s(Current time is).*/, ' \\1 <removed>'
22
22
  cfg.gsub! /\s(The system has been up).*/, ' \\1 <removed>'
23
+ cfg.gsub! /\s(Hardware: \d+ CPUs\(Stepping \d+\). Single \d+G drive. Free storage is).*/, ' \\1 <removed>'
23
24
  comment cfg
24
25
  end
25
26
 
@@ -26,14 +26,23 @@ class AOS7 < Oxidized::Model
26
26
  end
27
27
 
28
28
  cmd 'show hardware-info' do |cfg|
29
+ # Remove extra lines occuring when the command runs slow
30
+ cfg.gsub! /^Please wait...\n/, ''
31
+ cfg.gsub! /^\n\n\n/, "\n\n"
29
32
  comment cfg
30
33
  end
31
34
 
32
35
  cmd 'show running-directory' do |cfg|
36
+ # Remove extra lines occuring when the command runs slow
37
+ cfg.gsub! /^Please wait...\n/, ''
38
+ cfg.gsub! /^\n\n/, "\n"
33
39
  comment cfg
34
40
  end
35
41
 
36
42
  cmd 'show configuration snapshot' do |cfg|
43
+ # Remove extra lines occuring when the command runs slow
44
+ cfg.gsub! /^Please wait...\n/, ''
45
+ cfg.gsub! /^\n\n/, "\n"
37
46
  cfg
38
47
  end
39
48
 
@@ -58,6 +58,8 @@ class Aoscx < Oxidized::Model
58
58
  cfg.gsub! /^(\d\/\d\/\d.*\s+)\d+\s+$/, '\\1<hidden>'
59
59
  cfg.gsub! /^(\d+\/?\S+\s+\S+\s+)\d+\.\d+\s+C\s+(.*)/, '\\1<hidden> \\2'
60
60
  cfg.gsub! /^(LC.*\s+)\d+\.\d+\s+(C.*)$/, '\\1 <hidden> \\2'
61
+ # match show environment No speed shown for switches CX83xx, e.g. "PSU-1/1/1 N/A N/A N/A front-to-back ok 7360"
62
+ cfg.gsub! /^PSU(\S+\s+\S+\s+\s+\S+\s+)(slow|normal|medium|fast|max|N\/A)\s+(\S+\s+\S+\s+)\d+[[:blank:]]+/, '\\1<speed> \\3<rpm>'
61
63
  cfg.gsub! /^(\S+\s+\S+\s+\s+\S+\s+)(slow|normal|medium|fast|max)\s+(\S+\s+\S+\s+)\d+[[:blank:]]+/, '\\1<speed> \\3<rpm>'
62
64
  # match show environment power-consumption on VSF or standadlone, non-chassis and non-6400 switch, e.g. "2 6300M 48G 4SFP56 Swch 156.00 155.94"
63
65
  cfg.gsub! /^(\d+\s+.+\s+)(\s{2}\d{2}\.\d{2}|\s{1}\d{3}\.\d{2}|\d{4}\.\d{2})(\s+)(\s{2}\d{2}\.\d{2}|\s{1}\d{3}\.\d{2}|\d{4}\.\d{2})$/, '\\1<power>\\3<power>'
@@ -25,24 +25,25 @@ class AOSW < Oxidized::Model
25
25
  end
26
26
 
27
27
  cmd :secret do |cfg|
28
- cfg.gsub!(/secret (\S+)$/, 'secret <secret removed>')
29
- cfg.gsub!(/enable secret (\S+)$/, 'enable secret <secret removed>')
30
- cfg.gsub!(/PRE-SHARE (\S+)$/, 'PRE-SHARE <secret removed>')
31
- cfg.gsub!(/ipsec (\S+)$/, 'ipsec <secret removed>')
32
- cfg.gsub!(/community (\S+)$/, 'community <secret removed>')
28
+ cfg.gsub!(/secret (\S+)\s?$/, 'secret <secret removed>')
29
+ cfg.gsub!(/enable secret (\S+)\s?$/, 'enable secret <secret removed>')
30
+ cfg.gsub!(/PRE-SHARE (\S+)\s?$/, 'PRE-SHARE <secret removed>')
31
+ cfg.gsub!(/ipsec (\S+)\s?$/, 'ipsec <secret removed>')
32
+ cfg.gsub!(/community (\S+)\s?$/, 'community <secret removed>')
33
33
  cfg.gsub!(/ sha (\S+)/, ' sha <secret removed>')
34
34
  cfg.gsub!(/ des (\S+)/, ' des <secret removed>')
35
35
  cfg.gsub!(/mobility-manager (\S+) user (\S+) (\S+)/, 'mobility-manager \1 user \2 <secret removed>')
36
- cfg.gsub!(/mgmt-user (\S+) (root|guest-provisioning|network-operations|read-only|location-api-mgmt) (\S+)$/, 'mgmt-user \1 \2 <secret removed>') # MAS & Wireless Controler
37
- cfg.gsub!(/mgmt-user (\S+) (\S+)( (read-only|guest-mgmt))?$/, 'mgmt-user \1 <secret removed> \3') # IAP
36
+ cfg.gsub!(/mgmt-user (\S+) (root|guest-provisioning|network-operations|read-only|location-api-mgmt) (\S+)\s?$/, 'mgmt-user \1 \2 <secret removed>') # MAS & Wireless Controler
37
+ cfg.gsub!(/mgmt-user (\S+) (\S+)( (read-only|guest-mgmt))?\s?$/, 'mgmt-user \1 <secret removed> \3') # IAP
38
38
  # MAS format: mgmt-user <username> <accesslevel> <password hash>
39
39
  # IAP format (root user): mgmt-user <username> <password hash>
40
40
  # IAP format: mgmt-user <username> <password hash> <access level>
41
- cfg.gsub!(/key (\S+)$/, 'key <secret removed>')
42
- cfg.gsub!(/wpa-passphrase (\S+)$/, 'wpa-passphrase <secret removed>')
43
- cfg.gsub!(/bkup-passwords (\S+)$/, 'bkup-passwords <secret removed>')
44
- cfg.gsub!(/user (\S+) (\S+) (\S+)$/, 'user \1 <secret removed> \3')
45
- cfg.gsub!(/virtual-controller-key (\S+)$/, 'virtual-controller-key <secret removed>')
41
+ cfg.gsub!(/key (\S+)\s?$/, 'key <secret removed>')
42
+ cfg.gsub!(/vrrp-passphrase (\S+)\s?$/, 'vrrp-passphrase <secret removed>')
43
+ cfg.gsub!(/wpa-passphrase (\S+)\s?$/, 'wpa-passphrase <secret removed>')
44
+ cfg.gsub!(/bkup-passwords (\S+)\s?$/, 'bkup-passwords <secret removed>')
45
+ cfg.gsub!(/ap-console-password (\S+)\s?$/, 'ap-console-password <secret removed>')
46
+ cfg.gsub!(/virtual-controller-key (\S+)\s?$/, 'virtual-controller-key <secret removed>')
46
47
  cfg
47
48
  end
48
49
 
@@ -52,22 +53,26 @@ class AOSW < Oxidized::Model
52
53
  end
53
54
 
54
55
  cmd 'show inventory' do |cfg|
55
- cfg = "" if cfg =~ /(Invalid input detected at '\^' marker|Parse error)/ # Don't show for unsupported devices (IAP and MAS)
56
+ # Don't show for unsupported devices (IAP and MAS)
57
+ cfg = "" if cfg =~ /(Invalid input detected at '\^' marker|Parse error)/
56
58
  rstrip_cfg clean cfg
57
59
  end
58
60
 
59
61
  cmd 'show slots' do |cfg|
60
- cfg = "" if cfg =~ /(Invalid input detected at '\^' marker|Parse error)/ # Don't show for unsupported devices (IAP and MAS)
62
+ # Don't show for unsupported devices (IAP and MAS)
63
+ cfg = "" if cfg =~ /(Invalid input detected at '\^' marker|Parse error)/
61
64
  rstrip_cfg comment cfg
62
65
  end
63
66
 
64
67
  cmd 'show license' do |cfg|
65
- cfg = "" if cfg =~ /(Invalid input detected at '\^' marker|Parse error)/ # Don't show for unsupported devices (IAP and MAS)
68
+ # Don't show for unsupported devices (IAP and MAS)
69
+ cfg = "" if cfg =~ /(Invalid input detected at '\^' marker|Parse error)/
66
70
  rstrip_cfg comment cfg
67
71
  end
68
72
 
69
73
  cmd 'show license passphrase' do |cfg|
70
- cfg = "" if cfg.match /(Invalid input detected at '\^' marker|Parse error)/ # Don't show for unsupported devices (IAP and MAS)
74
+ # Don't show for unsupported devices (IAP and MAS)
75
+ cfg = "" if cfg.match /(Invalid input detected at '\^' marker|Parse error)/
71
76
  rstrip_cfg comment cfg
72
77
  end
73
78
 
@@ -117,7 +122,7 @@ class AOSW < Oxidized::Model
117
122
  next if line =~ /Output \d Config/i
118
123
  next if line =~ /(Tachometers|Temperatures|Voltages)/
119
124
  next if line =~ /((Card|CPU) Temperature|Chassis Fan|VMON1[0-9])/
120
- next if line =~ /[0-9]+\s+(RPMS?|m?V|C|W)/i
125
+ next if line =~ /[0-9.]{1,6}\s+(RPMS?|m?V|C|W)/i
121
126
 
122
127
  out << line.strip
123
128
  end
@@ -37,7 +37,7 @@ class AricentISS < Oxidized::Model
37
37
  cmd 'show system information' do |cfg|
38
38
  cfg.sub! /^Device Up Time.*\n/, ''
39
39
  cfg.delete! "\r"
40
- comment(cfg).gsub(/ +$/, '')
40
+ comment(cfg).rstrip
41
41
  end
42
42
 
43
43
  cmd 'show running-config' do |cfg|
@@ -53,6 +53,6 @@ class AricentISS < Oxidized::Model
53
53
  end
54
54
 
55
55
  l
56
- end.join.gsub(/ +$/, '')
56
+ end.join.rstrip
57
57
  end
58
58
  end
@@ -16,8 +16,7 @@ class ASA < Oxidized::Model
16
16
  cfg.gsub! /^passwd (\S+) (.*)/, 'passwd <secret hidden> \2'
17
17
  cfg.gsub! /username (\S+) password (\S+) (.*)/, 'username \1 password <secret hidden> \3'
18
18
  cfg.gsub! /(ikev[12] ((remote|local)-authentication )?pre-shared-key( hex)?) (\S+)/, '\1 <secret hidden>'
19
- cfg.gsub! /^(aaa-server TACACS\+? \(\S+\) host[^\n]*\n(\s+[^\n]+\n)*\skey) \S+$/mi, '\1 <secret hidden>'
20
- cfg.gsub! /^(aaa-server \S+ \(\S+\) host[^\n]*\n(\s+[^\n]+\n)*\s+key) \S+$/mi, '\1 <secret hidden>'
19
+ cfg.gsub! /^(aaa-server \S+(?: \(\S+\))? host \S+\n(?: [^\n]+\n)* +key) \S+$/mi, '\1 <secret hidden>'
21
20
  cfg.gsub! /ldap-login-password (\S+)/, 'ldap-login-password <secret hidden>'
22
21
  cfg.gsub! /^snmp-server host (.*) community (\S+)/, 'snmp-server host \1 community <secret hidden>'
23
22
  cfg.gsub! /^(failover key) .+/, '\1 <secret hidden>'
@@ -97,7 +96,8 @@ class ASA < Oxidized::Model
97
96
  contexts = systemcfg.scan(/^context (\S+)$/)
98
97
  files = systemcfg.scan(/config-url (\S+)$/)
99
98
  contexts.each_with_index do |cont, i|
100
- allcfg = allcfg + "\n\n----------========== [ CONTEXT " + cont.join(" ") + " FILE " + files[i].join(" ") + " ] ==========----------\n\n"
99
+ allcfg = allcfg + "\n\n----------========== [ CONTEXT " + cont.join(" ") +
100
+ " FILE " + files[i].join(" ") + " ] ==========----------\n\n"
101
101
  cmd "more " + files[i].join(" ") do |cfgcontext|
102
102
  allcfg = allcfg + "\n\n" + cfgcontext
103
103
  end
@@ -61,25 +61,28 @@ class AWPlus < Oxidized::Model
61
61
  cfg
62
62
  end
63
63
 
64
- # Config required for telnet to detect username prompt
64
+ # Config required for telnet to detect username & password prompt.
65
65
  cfg :telnet do
66
66
  username /login:\s/
67
+ password /^Password:\s/
67
68
  end
68
69
 
69
- # Main login config
70
+ # Config required for ssh to specify newline characters.
70
71
  cfg :telnet, :ssh do
72
+ newline "\r\n"
73
+
71
74
  post_login do
72
- if vars :enable
73
- send "enable\n"
74
- expect /^Password:\s/
75
- cmd vars(:enable) + "\r\n"
76
- else
77
- cmd 'enable' # Required for Priv-Exec users without enable PW to be put into "enable mode".
75
+ if vars(:enable) == true
76
+ cmd "enable"
77
+ elsif vars(:enable)
78
+ cmd "enable", /^[pP]assword:/
79
+ cmd vars(:enable)
78
80
  end
79
- # cmd 'terminal length 0' #set so the entire config is output without intervention.
81
+ # cmd 'terminal length 0' # set so the entire config is output without intervention.
80
82
  end
83
+
81
84
  pre_logout do
82
- # cmd 'terminal no length' #Sets term length back to default on exit.
85
+ # cmd 'terminal no length' # sets term length back to default on exit.
83
86
  send "exit\r\n"
84
87
  end
85
88
  end