oxidized 0.29.1 → 0.37.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 (236) hide show
  1. checksums.yaml +4 -4
  2. data/.coderabbit.yaml +21 -0
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +45 -0
  4. data/.github/ISSUE_TEMPLATE/feature_request.md +22 -0
  5. data/.github/ISSUE_TEMPLATE/support-request.md +39 -0
  6. data/.github/workflows/codeql.yml +4 -4
  7. data/.github/workflows/publishdocker.yml +108 -13
  8. data/.github/workflows/ruby.yml +3 -12
  9. data/.github/workflows/stale.yml +6 -1
  10. data/.gitignore +2 -0
  11. data/.rubocop.yml +69 -13
  12. data/.rubocop_todo.yml +13 -143
  13. data/CHANGELOG.md +431 -4
  14. data/CONTRIBUTING.md +189 -0
  15. data/Dockerfile +89 -34
  16. data/README.md +59 -188
  17. data/Rakefile +58 -12
  18. data/bin/oxidized +2 -5
  19. data/docs/Configuration.md +382 -149
  20. data/docs/Creating-Models.md +262 -10
  21. data/docs/DeviceSimulation.md +196 -0
  22. data/docs/Docker.md +246 -0
  23. data/docs/Hooks.md +146 -51
  24. data/docs/Inputs.md +237 -0
  25. data/docs/Issues.md +123 -0
  26. data/docs/Model-Notes/APC.md +72 -0
  27. data/docs/Model-Notes/Cumulus.md +5 -0
  28. data/docs/Model-Notes/EatonNetwork.md +18 -0
  29. data/docs/Model-Notes/ExaLink.md +43 -0
  30. data/docs/Model-Notes/FSOS.md +17 -0
  31. data/docs/Model-Notes/Fortinet.md +75 -0
  32. data/docs/Model-Notes/GrandstreamHT8xx.md +8 -0
  33. data/docs/Model-Notes/HPEAruba.md +32 -0
  34. data/docs/Model-Notes/IvantiConnectSecure.md +59 -0
  35. data/docs/Model-Notes/OS6.md +10 -0
  36. data/docs/Model-Notes/README.md +1 -20
  37. data/docs/Model-Notes/RouterOS.md +28 -0
  38. data/docs/Model-Notes/SikluMHTG.md +7 -0
  39. data/docs/Model-Notes/TrueNAS.md +23 -0
  40. data/docs/Model-Notes/XGS4600-Zyxel.md +5 -0
  41. data/docs/ModelUnitTests.md +219 -0
  42. data/docs/Outputs.md +102 -5
  43. data/docs/Release.md +102 -0
  44. data/docs/Ruby-API.md +135 -2
  45. data/docs/Sources.md +34 -0
  46. data/docs/Supported-OS-Types.md +207 -270
  47. data/docs/Troubleshooting.md +43 -1
  48. data/extra/device2yaml.rb +259 -0
  49. data/extra/gitdiff-msteams.sh +32 -5
  50. data/extra/hooks/modelrules.rb +55 -0
  51. data/extra/hooks/modelrulesadvanced.rb +167 -0
  52. data/extra/hooks/srcipmap.rb +54 -0
  53. data/extra/nagios_check_failing_nodes.rb +1 -1
  54. data/extra/oxidized.runit +1 -1
  55. data/extra/rest_client.rb +5 -4
  56. data/extra/syslog.rb +11 -6
  57. data/lib/oxidized/cli/support.rb +152 -0
  58. data/lib/oxidized/cli.rb +21 -6
  59. data/lib/oxidized/config/vars.rb +33 -14
  60. data/lib/oxidized/config.rb +28 -17
  61. data/lib/oxidized/core.rb +50 -8
  62. data/lib/oxidized/hook/ciscosparkdiff.rb +11 -9
  63. data/lib/oxidized/hook/exec.rb +5 -4
  64. data/lib/oxidized/hook/githubrepo.rb +55 -18
  65. data/lib/oxidized/hook/noophook.rb +2 -2
  66. data/lib/oxidized/hook/slackdiff.rb +50 -14
  67. data/lib/oxidized/hook/xmppdiff.rb +9 -9
  68. data/lib/oxidized/hook.rb +68 -16
  69. data/lib/oxidized/input/cli.rb +8 -3
  70. data/lib/oxidized/input/debugtext.rb +40 -0
  71. data/lib/oxidized/input/debugyaml.rb +82 -0
  72. data/lib/oxidized/input/exec.rb +2 -11
  73. data/lib/oxidized/input/ftp.rb +2 -19
  74. data/lib/oxidized/input/http.rb +67 -17
  75. data/lib/oxidized/input/input.rb +35 -14
  76. data/lib/oxidized/input/scp.rb +27 -0
  77. data/lib/oxidized/input/ssh.rb +59 -83
  78. data/lib/oxidized/input/sshbase.rb +102 -0
  79. data/lib/oxidized/input/telnet.rb +20 -19
  80. data/lib/oxidized/input/tftp.rb +8 -8
  81. data/lib/oxidized/job.rb +18 -7
  82. data/lib/oxidized/jobs.rb +11 -1
  83. data/lib/oxidized/logger.rb +51 -0
  84. data/lib/oxidized/manager.rb +18 -17
  85. data/lib/oxidized/model/acos.rb +2 -1
  86. data/lib/oxidized/model/addpack.rb +26 -0
  87. data/lib/oxidized/model/adtran.rb +5 -1
  88. data/lib/oxidized/model/adva.rb +3 -3
  89. data/lib/oxidized/model/airfiber.rb +1 -1
  90. data/lib/oxidized/model/aos7.rb +11 -0
  91. data/lib/oxidized/model/aoscx.rb +37 -29
  92. data/lib/oxidized/model/aosw.rb +39 -29
  93. data/lib/oxidized/model/apc_aos.rb +7 -2
  94. data/lib/oxidized/model/apcaos.rb +39 -0
  95. data/lib/oxidized/model/aricentiss.rb +9 -2
  96. data/lib/oxidized/model/arubainstant.rb +93 -0
  97. data/lib/oxidized/model/asa.rb +11 -10
  98. data/lib/oxidized/model/asternos.rb +22 -0
  99. data/lib/oxidized/model/asyncos.rb +3 -3
  100. data/lib/oxidized/model/audiocodes.rb +2 -2
  101. data/lib/oxidized/model/awplus.rb +15 -12
  102. data/lib/oxidized/model/axos.rb +1 -1
  103. data/lib/oxidized/model/bdcom.rb +1 -0
  104. data/lib/oxidized/model/c4cmts.rb +1 -2
  105. data/lib/oxidized/model/ciscosma.rb +1 -1
  106. data/lib/oxidized/model/ciscosmb.rb +6 -1
  107. data/lib/oxidized/model/cnos.rb +13 -10
  108. data/lib/oxidized/model/comtrol.rb +1 -1
  109. data/lib/oxidized/model/comware.rb +5 -3
  110. data/lib/oxidized/model/cumulus.rb +14 -2
  111. data/lib/oxidized/model/defacto.rb +26 -0
  112. data/lib/oxidized/model/dellx.rb +1 -1
  113. data/lib/oxidized/model/dlink.rb +5 -2
  114. data/lib/oxidized/model/dlinknextgen.rb +55 -0
  115. data/lib/oxidized/model/dnos.rb +3 -0
  116. data/lib/oxidized/model/dslcommands.rb +93 -0
  117. data/lib/oxidized/model/dslsetup.rb +102 -0
  118. data/lib/oxidized/model/eatonnetwork.rb +70 -0
  119. data/lib/oxidized/model/edgecos.rb +3 -1
  120. data/lib/oxidized/model/edgeos.rb +7 -6
  121. data/lib/oxidized/model/edgeswitch.rb +3 -1
  122. data/lib/oxidized/model/efos.rb +41 -0
  123. data/lib/oxidized/model/eltex.rb +4 -2
  124. data/lib/oxidized/model/enterasys800.rb +2 -2
  125. data/lib/oxidized/model/enterprise_sonic.rb +46 -0
  126. data/lib/oxidized/model/eos.rb +3 -1
  127. data/lib/oxidized/model/exalink.rb +36 -0
  128. data/lib/oxidized/model/f5os.rb +17 -0
  129. data/lib/oxidized/model/fabricos.rb +2 -2
  130. data/lib/oxidized/model/fastiron.rb +5 -3
  131. data/lib/oxidized/model/firebrick.rb +2 -2
  132. data/lib/oxidized/model/firelinuxos.rb +13 -6
  133. data/lib/oxidized/model/firewareos.rb +10 -1
  134. data/lib/oxidized/model/fortigate.rb +160 -0
  135. data/lib/oxidized/model/fortios.rb +38 -48
  136. data/lib/oxidized/model/fsos.rb +46 -0
  137. data/lib/oxidized/model/gaiaos.rb +4 -4
  138. data/lib/oxidized/model/garderos.rb +38 -0
  139. data/lib/oxidized/model/grandstreamht8xx.rb +19 -0
  140. data/lib/oxidized/model/h3c.rb +1 -1
  141. data/lib/oxidized/model/ibos.rb +1 -0
  142. data/lib/oxidized/model/ingate.rb +47 -0
  143. data/lib/oxidized/model/ios.rb +57 -30
  144. data/lib/oxidized/model/iosxr.rb +2 -2
  145. data/lib/oxidized/model/ironware.rb +7 -5
  146. data/lib/oxidized/model/ivanti.rb +54 -0
  147. data/lib/oxidized/model/junos.rb +12 -5
  148. data/lib/oxidized/model/kornfeldos.rb +33 -0
  149. data/lib/oxidized/model/lenovonos.rb +2 -0
  150. data/lib/oxidized/model/linksyssrw.rb +3 -3
  151. data/lib/oxidized/model/linuxgeneric.rb +4 -2
  152. data/lib/oxidized/model/macros.rb +60 -0
  153. data/lib/oxidized/model/mimosab11.rb +34 -0
  154. data/lib/oxidized/model/ml66.rb +33 -0
  155. data/lib/oxidized/model/mlnxos.rb +18 -14
  156. data/lib/oxidized/model/model.rb +98 -103
  157. data/lib/oxidized/model/mtrlrfs.rb +1 -0
  158. data/lib/oxidized/model/ndms.rb +6 -0
  159. data/lib/oxidized/model/netgear.rb +14 -3
  160. data/lib/oxidized/model/netonix.rb +1 -1
  161. data/lib/oxidized/model/netscaler.rb +1 -1
  162. data/lib/oxidized/model/nodegrid.rb +1 -1
  163. data/lib/oxidized/model/nsxdfw.rb +31 -0
  164. data/lib/oxidized/model/nsxfirewall.rb +2 -1
  165. data/lib/oxidized/model/nxos.rb +20 -15
  166. data/lib/oxidized/model/ocnos.rb +42 -0
  167. data/lib/oxidized/model/onefinity.rb +18 -0
  168. data/lib/oxidized/model/openbsd.rb +1 -1
  169. data/lib/oxidized/model/opengear.rb +36 -1
  170. data/lib/oxidized/model/openwrt.rb +6 -6
  171. data/lib/oxidized/model/opnsense.rb +9 -1
  172. data/lib/oxidized/model/os6.rb +48 -0
  173. data/lib/oxidized/model/outputs.rb +5 -0
  174. data/lib/oxidized/model/panos.rb +2 -0
  175. data/lib/oxidized/model/perle.rb +41 -0
  176. data/lib/oxidized/model/pfsense.rb +9 -0
  177. data/lib/oxidized/model/powerconnect.rb +32 -11
  178. data/lib/oxidized/model/procurve.rb +6 -2
  179. data/lib/oxidized/model/qtech.rb +3 -1
  180. data/lib/oxidized/model/quantaos.rb +8 -6
  181. data/lib/oxidized/model/rgos.rb +33 -0
  182. data/lib/oxidized/model/riverbed.rb +104 -0
  183. data/lib/oxidized/model/routeros.rb +18 -11
  184. data/lib/oxidized/model/saos.rb +18 -1
  185. data/lib/oxidized/model/saos10.rb +38 -0
  186. data/lib/oxidized/model/siklu.rb +1 -1
  187. data/lib/oxidized/model/siklumhtg.rb +22 -0
  188. data/lib/oxidized/model/sixwind.rb +28 -0
  189. data/lib/oxidized/model/slxos.rb +3 -3
  190. data/lib/oxidized/model/smartbyte.rb +48 -0
  191. data/lib/oxidized/model/sonicos.rb +27 -20
  192. data/lib/oxidized/model/speedtouch.rb +1 -1
  193. data/lib/oxidized/model/sros.rb +3 -3
  194. data/lib/oxidized/model/srosmd.rb +1 -1
  195. data/lib/oxidized/model/supermicro.rb +1 -1
  196. data/lib/oxidized/model/telco.rb +1 -1
  197. data/lib/oxidized/model/tmos.rb +1 -0
  198. data/lib/oxidized/model/tnsr.rb +57 -0
  199. data/lib/oxidized/model/tplink.rb +5 -5
  200. data/lib/oxidized/model/trango.rb +4 -2
  201. data/lib/oxidized/model/truenas.rb +64 -3
  202. data/lib/oxidized/model/ucs.rb +1 -1
  203. data/lib/oxidized/model/unifiap.rb +144 -0
  204. data/lib/oxidized/model/uplinkolt.rb +46 -0
  205. data/lib/oxidized/model/voltaire.rb +1 -1
  206. data/lib/oxidized/model/voss.rb +3 -0
  207. data/lib/oxidized/model/vrp.rb +5 -1
  208. data/lib/oxidized/model/vyatta.rb +9 -3
  209. data/lib/oxidized/model/vyos.rb +37 -0
  210. data/lib/oxidized/model/xos.rb +10 -1
  211. data/lib/oxidized/model/zhoneolt.rb +4 -2
  212. data/lib/oxidized/model/zynos.rb +67 -3
  213. data/lib/oxidized/model/zynosadsl.rb +14 -0
  214. data/lib/oxidized/model/zynoscli.rb +1 -1
  215. data/lib/oxidized/model/zynosgs.rb +2 -0
  216. data/lib/oxidized/model/zynosmgs.rb +32 -0
  217. data/lib/oxidized/node.rb +99 -64
  218. data/lib/oxidized/nodes.rb +40 -19
  219. data/lib/oxidized/output/file.rb +80 -43
  220. data/lib/oxidized/output/git.rb +295 -151
  221. data/lib/oxidized/output/gitcrypt.rb +193 -185
  222. data/lib/oxidized/output/http.rb +63 -51
  223. data/lib/oxidized/output/output.rb +20 -4
  224. data/lib/oxidized/signals.rb +44 -0
  225. data/lib/oxidized/source/csv.rb +49 -49
  226. data/lib/oxidized/source/http.rb +76 -63
  227. data/lib/oxidized/source/jsonfile.rb +68 -0
  228. data/lib/oxidized/source/source.rb +73 -18
  229. data/lib/oxidized/source/sql.rb +71 -59
  230. data/lib/oxidized/version.rb +6 -4
  231. data/lib/oxidized/worker.rb +57 -29
  232. data/lib/oxidized.rb +4 -24
  233. data/lib/refinements.rb +21 -1
  234. data/oxidized.gemspec +45 -21
  235. metadata +248 -41
  236. data/lib/oxidized/model/timos.rb +0 -10
data/extra/syslog.rb CHANGED
@@ -8,8 +8,10 @@
8
8
  # set system syslog host SERVER interactive-commands notice
9
9
  # set system syslog host SERVER match "^mgd\[[0-9]+\]: UI_COMMIT: .*"
10
10
 
11
- # Ports < 1024 need extra privileges, use a port higher than this by setting the port option in your oxidized config file.
12
- # To use the default port for syslog (514) you shouldn't pass an argument, but you will need to allow this with:
11
+ # Ports < 1024 need extra privileges, use a port higher than this by setting the
12
+ # port option in your oxidized config file.
13
+ # To use the default port for syslog (514) you shouldn't pass an argument, but
14
+ # you will need to allow this with:
13
15
  # sudo setcap 'cap_net_bind_service=+ep' /usr/bin/ruby
14
16
 
15
17
  # Config options are:
@@ -43,8 +45,8 @@ module Oxidized
43
45
 
44
46
  begin
45
47
  CFGS.load
46
- rescue StandardError => error
47
- raise InvalidConfig, "Error loading config: #{error.message}"
48
+ rescue StandardError => e
49
+ raise InvalidConfig, "Error loading config: #{e.message}"
48
50
  ensure
49
51
  CFG = CFGS.cfg # convenienence, instead of Config.cfg.password, CFG.password
50
52
  end
@@ -52,6 +54,7 @@ module Oxidized
52
54
  class SyslogMonitor
53
55
  MSG = {
54
56
  ios: /%SYS-(SW[0-9]+-)?5-CONFIG_I:/,
57
+ iosxr: /%MGBL-SYS-5-CONFIG_I/,
55
58
  junos: 'UI_COMMIT:',
56
59
  eos: /%SYS-5-CONFIG_I:/,
57
60
  nxos: /%VSHD-5-VSHD_SYSLOG_CONFIG_I:/,
@@ -89,6 +92,7 @@ module Oxidized
89
92
  opts[:from] = log[-1][1..-2]
90
93
  opts
91
94
  end
95
+ alias iosxr ios
92
96
  alias nxos ios
93
97
  alias eos ios
94
98
 
@@ -116,7 +120,8 @@ module Oxidized
116
120
  def run(io)
117
121
  loop do
118
122
  log = select [io]
119
- log, ip = log.first.first, nil
123
+ log = log.first.first
124
+ ip = nil
120
125
  if @mode == :udp
121
126
  log, ip = log.recvfrom_nonblock 2000
122
127
  ip = ip.last
@@ -136,7 +141,7 @@ module Oxidized
136
141
  if Oxidized::CFG.syslogd.resolve == false
137
142
  ipaddr
138
143
  else
139
- name = (Resolv.getname ipaddr.to_s rescue ipaddr)
144
+ name = (Resolv.getname ipaddr.to_s rescue ipaddr) # rubocop:disable Style/RedundantParentheses
140
145
  Oxidized::CFG.syslogd.dns_map.each { |re, sub| name.sub! Regexp.new(re.to_s), sub }
141
146
  name
142
147
  end
@@ -0,0 +1,152 @@
1
+ require 'time'
2
+
3
+ module Oxidized
4
+ class CLI
5
+ module Support
6
+ SENSITIVE_NAME_RE = /(password|passphrase|secret|token|enable|
7
+ (private|api|access)_?key|
8
+ community|credential|auth
9
+ )/ix
10
+ ROOT_GEMS = %w[oxidized oxidized-web].freeze
11
+ EXPLICIT_ENV_KEYS = %w[
12
+ OXIDIZED_HOME
13
+ OXIDIZED_LOGS
14
+ CONFIG_RELOAD_INTERVAL
15
+ UPDATE_CA_CERTIFICATES
16
+ ].freeze
17
+
18
+ private
19
+
20
+ def show_support_details
21
+ print_intro
22
+ print_environment
23
+ print_config_files
24
+ print_rugged_support
25
+ print_installed_gems
26
+ end
27
+
28
+ def print_intro
29
+ os_release = read_os_release
30
+ runit_path = '/etc/service/oxidized/run'
31
+
32
+ puts '> :warning:'
33
+ puts '> The --support option is intended for diagnostic purposes and may include sensitive information.'
34
+ puts '> Remove any sensitive data before sharing this output.'
35
+ puts
36
+ puts '## Oxidized Support Data'
37
+ puts "- Timestamp: #{Time.now.utc.iso8601}"
38
+ puts "- Oxidized version: #{Oxidized::VERSION_FULL}"
39
+ puts "- OS release: #{os_release}" if os_release
40
+ puts "- Container hint (#{runit_path} exists): #{File.exist?(runit_path)}"
41
+ puts "- Ruby engine: #{defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby'}"
42
+ puts "- Ruby version: #{RUBY_VERSION}p#{RUBY_PATCHLEVEL} (#{RUBY_PLATFORM})"
43
+ puts "- Working directory: #{Dir.pwd}"
44
+ puts "- Gem paths: #{Gem.path.join(', ')}"
45
+ puts
46
+ end
47
+
48
+ def print_environment
49
+ puts '### Environment Variables'
50
+ keys = (ENV.keys.grep(/^OXIDIZED_/) + EXPLICIT_ENV_KEYS).uniq.sort
51
+
52
+ keys.each do |key|
53
+ next unless ENV.has_key?(key)
54
+
55
+ value = ENV.fetch(key)
56
+ puts key.match?(SENSITIVE_NAME_RE) ? "#{key}=[REDACTED]" : "#{key}=#{value}"
57
+ end
58
+
59
+ puts
60
+ end
61
+
62
+ def print_config_files
63
+ puts '### Configuration Files'
64
+ config_paths.each do |path|
65
+ config_path = File.expand_path(path)
66
+ exists = File.exist?(config_path)
67
+ puts "- #{config_path} exists: #{exists ? 'yes' : 'no'}"
68
+ next unless exists
69
+
70
+ print_sanitized_config(config_path)
71
+ end
72
+ puts
73
+ end
74
+
75
+ def print_rugged_support
76
+ puts '### Rugged'
77
+ begin
78
+ require 'rugged'
79
+ puts "- Rugged version: #{Rugged::VERSION}"
80
+
81
+ ssh_supported = Rugged.respond_to?(:features) && Rugged.features.include?(:ssh)
82
+ puts "- Rugged SSH support: #{ssh_supported}"
83
+ rescue LoadError
84
+ puts '- Rugged: not available'
85
+ puts '- Rugged SSH support: false'
86
+ end
87
+ puts
88
+ end
89
+
90
+ def print_installed_gems
91
+ puts '### Relevant Installed Gems'
92
+ relevant_gem_names.each do |name|
93
+ versions = Gem::Specification.find_all_by_name(name).sort_by(&:version).map { |s| s.version.to_s }
94
+ puts "- #{name} (#{versions.join(', ')})" unless versions.empty?
95
+ end
96
+ end
97
+
98
+ def relevant_gem_names
99
+ names = ROOT_GEMS.select { |name| Gem::Specification.any? { |s| s.name == name } }
100
+
101
+ root_specs = names.flat_map { |name| Gem::Specification.find_all_by_name(name) }
102
+ runtime_deps = root_specs.flat_map { |spec| spec.dependencies }
103
+ names.concat(runtime_deps.map(&:name))
104
+ names.sort.uniq
105
+ end
106
+
107
+ def config_paths
108
+ user_default = File.join(Dir.home, '.config', 'oxidized', 'config')
109
+ home_from_env = File.join(File.expand_path(Oxidized::Config::ROOT), 'config')
110
+
111
+ [
112
+ '/etc/oxidized/config',
113
+ user_default,
114
+ home_from_env
115
+ ].uniq
116
+ end
117
+
118
+ def print_sanitized_config(path)
119
+ content = File.read(path)
120
+ puts '```yaml'
121
+ content.each_line(chomp: true) do |line|
122
+ key, separator, = line.partition(':')
123
+
124
+ if separator.empty? || key.empty?
125
+ puts line
126
+ next
127
+ end
128
+
129
+ if key.match?(SENSITIVE_NAME_RE)
130
+ puts "#{key}: [REDACTED]"
131
+ else
132
+ puts line
133
+ end
134
+ end
135
+ puts '```'
136
+ rescue StandardError => e
137
+ puts " <failed to read: #{e.class}: #{e.message}>"
138
+ end
139
+
140
+ def read_os_release
141
+ return nil unless File.exist?('/etc/os-release')
142
+
143
+ line = File.foreach('/etc/os-release').find { |entry| entry.start_with?('PRETTY_NAME=') }
144
+ return nil unless line
145
+
146
+ line.split('=', 2).last.to_s.strip.gsub(/^"|"$/, '')
147
+ rescue StandardError
148
+ nil
149
+ end
150
+ end
151
+ end
152
+ end
data/lib/oxidized/cli.rb CHANGED
@@ -1,5 +1,11 @@
1
+ require 'semantic_logger'
2
+ require_relative 'cli/support'
3
+
1
4
  module Oxidized
2
5
  class CLI
6
+ include SemanticLogger::Loggable
7
+ include Support
8
+
3
9
  require 'slop'
4
10
  require 'oxidized'
5
11
  require 'English'
@@ -9,10 +15,10 @@ module Oxidized
9
15
  Process.daemon if @opts[:daemonize]
10
16
  write_pid
11
17
  begin
12
- Oxidized.logger.info "Oxidized starting, running as pid #{$PROCESS_ID}"
18
+ logger.info "Oxidized starting, running as pid #{$PROCESS_ID}"
13
19
  Oxidized.new
14
- rescue StandardError => error
15
- crash error
20
+ rescue StandardError => e
21
+ crash e
16
22
  raise
17
23
  end
18
24
  end
@@ -23,14 +29,14 @@ module Oxidized
23
29
  _args, @opts = parse_opts
24
30
 
25
31
  Config.load(@opts)
26
- Oxidized.setup_logger
32
+ Oxidized::Logger.setup
27
33
 
28
34
  @pidfile = File.expand_path(Oxidized.config.pid)
29
35
  end
30
36
 
31
37
  def crash(error)
32
- Oxidized.logger.fatal "Oxidized crashed, crashfile written in #{Config::Crash}"
33
- File.open Config::Crash, 'w' do |file|
38
+ logger.fatal "Oxidized crashed, crashfile written in #{Config::CRASH}"
39
+ File.open Config::CRASH, 'w' do |file|
34
40
  file.puts '-' * 50
35
41
  file.puts Time.now.utc
36
42
  file.puts error.message + ' [' + error.class.to_s + ']'
@@ -44,6 +50,9 @@ module Oxidized
44
50
  opts = Slop.parse do |opt|
45
51
  opt.on '-d', '--debug', 'turn on debugging'
46
52
  opt.on '--daemonize', 'Daemonize/fork the process'
53
+ opt.on '--support', 'show support diagnostics and exit'
54
+ opt.string '--home-dir', 'Oxidized home dir', default: nil
55
+ opt.string '--config-file', 'Oxidized config file', default: nil
47
56
  opt.on '-h', '--help', 'show usage' do
48
57
  puts opt
49
58
  exit
@@ -58,6 +67,12 @@ module Oxidized
58
67
  Kernel.exit
59
68
  end
60
69
  end
70
+
71
+ if opts[:support]
72
+ show_support_details
73
+ Kernel.exit
74
+ end
75
+
61
76
  [opts.arguments, opts]
62
77
  end
63
78
 
@@ -1,17 +1,36 @@
1
- module Oxidized::Config::Vars
2
- # convenience method for accessing node, group or global level user variables
3
- def vars(name)
4
- model_name = @node.model.class.name.to_s.downcase
5
- if @node.vars&.has_key?(name)
6
- @node.vars[name]
7
- elsif Oxidized.config.groups.has_key?(@node.group) && Oxidized.config.groups[@node.group].models.has_key(model_name) && Oxidized.config.groups[@node.group].models[model_name].vars.has_key?(name.to_s)
8
- Oxidized.config.groups[@node.group].models[model_name].vars[name.to_s]
9
- elsif Oxidized.config.groups.has_key?(@node.group) && Oxidized.config.groups[@node.group].vars.has_key?(name.to_s)
10
- Oxidized.config.groups[@node.group].vars[name.to_s]
11
- elsif Oxidized.config.models.has_key(model_name) && Oxidized.config.models[model_name].vars.has_key?(name.to_s)
12
- Oxidized.config.models[model_name].vars[name.to_s]
13
- elsif Oxidized.config.vars.has_key?(name.to_s)
14
- Oxidized.config.vars[name.to_s]
1
+ module Oxidized
2
+ class Config
3
+ module Vars
4
+ # convenience method for accessing node, group or global level user variables
5
+ def vars(name)
6
+ model_name = @node.model.class.name.to_s.downcase
7
+ groups = Oxidized.config.groups
8
+ models = Oxidized.config.models
9
+ group = groups[@node.group] if groups.has_key?(@node.group)
10
+ model = models[model_name] if models.has_key?(model_name)
11
+ group_model = group.models[model_name] if group&.models&.has_key?(model_name)
12
+
13
+ scopes = {
14
+ node: @node.vars,
15
+ group_model: group_model&.vars,
16
+ group: group&.vars,
17
+ model: model&.vars,
18
+ vars: Oxidized.config.vars
19
+ }
20
+
21
+ scopes.each do |scope_name, scope|
22
+ next unless scope&.has_key?(name.to_s)
23
+
24
+ val = scope[name.to_s]
25
+ if val.nil?
26
+ Oxidized.logger.debug "vars.rb: scope #{scope_name} has key #{name} with value nil, ignoring scope"
27
+ else
28
+ Oxidized.logger.debug "vars.rb: scope #{scope_name} has key #{name} with value #{val}, using scope"
29
+ return val
30
+ end
31
+ end
32
+ nil
33
+ end
15
34
  end
16
35
  end
17
36
  end
@@ -4,18 +4,22 @@ module Oxidized
4
4
  class InvalidConfig < OxidizedError; end
5
5
 
6
6
  class Config
7
- Root = ENV['OXIDIZED_HOME'] || File.join(Dir.home, '.config', 'oxidized')
8
- Crash = File.join(ENV['OXIDIZED_LOGS'] || Root, 'crash')
9
- Log = File.join(ENV['OXIDIZED_LOGS'] || Root, 'logs')
10
- InputDir = File.join Directory, %w[lib oxidized input]
11
- OutputDir = File.join Directory, %w[lib oxidized output]
12
- ModelDir = File.join Directory, %w[lib oxidized model]
13
- SourceDir = File.join Directory, %w[lib oxidized source]
14
- HookDir = File.join Directory, %w[lib oxidized hook]
15
- Sleep = 1
7
+ ROOT = ENV['OXIDIZED_HOME'] || File.join(Dir.home, '.config', 'oxidized')
8
+ CRASH = File.join(ENV['OXIDIZED_LOGS'] || ROOT, 'crash')
9
+ LOG = File.join(ENV['OXIDIZED_LOGS'] || ROOT, 'logs')
10
+ INPUT_DIR = File.join Directory, %w[lib oxidized input]
11
+ OUTPUT_DIR = File.join Directory, %w[lib oxidized output]
12
+ MODEL_DIR = File.join Directory, %w[lib oxidized model]
13
+ SOURCE_DIR = File.join Directory, %w[lib oxidized source]
14
+ HOOK_DIR = File.join Directory, %w[lib oxidized hook]
15
+ SLEEP = 1
16
16
 
17
17
  def self.load(cmd_opts = {})
18
- asetus = Asetus.new(name: 'oxidized', load: false, key_to_s: true, usrdir: Oxidized::Config::Root)
18
+ usrdir = File.expand_path(cmd_opts[:home_dir] || Oxidized::Config::ROOT)
19
+ cfgfile = cmd_opts[:config_file] || 'config'
20
+ # configuration file with full path as a class instance variable
21
+ @configfile = File.join(usrdir, cfgfile)
22
+ asetus = Asetus.new(name: 'oxidized', load: false, usrdir: usrdir, cfgfile: cfgfile)
19
23
  Oxidized.asetus = asetus
20
24
 
21
25
  asetus.default.username = 'username'
@@ -23,22 +27,25 @@ module Oxidized
23
27
  asetus.default.model = 'junos'
24
28
  asetus.default.resolve_dns = true # if false, don't resolve DNS to IP
25
29
  asetus.default.interval = 3600
26
- asetus.default.use_syslog = false
27
30
  asetus.default.debug = false
31
+ asetus.default.run_once = false
28
32
  asetus.default.threads = 30
29
33
  asetus.default.use_max_threads = false
30
34
  asetus.default.timeout = 20
35
+ asetus.default.timelimit = 300
31
36
  asetus.default.retries = 3
32
37
  asetus.default.prompt = /^([\w.@-]+[#>]\s?)$/
33
- asetus.default.rest = '127.0.0.1:8888' # or false to disable
34
38
  asetus.default.next_adds_job = false # if true, /next adds job, so device is fetched immmeiately
35
39
  asetus.default.vars = {} # could be 'enable'=>'enablePW'
36
40
  asetus.default.groups = {} # group level configuration
37
41
  asetus.default.group_map = {} # map aliases of groups to names
38
42
  asetus.default.models = {} # model level configuration
39
- asetus.default.pid = File.join(Oxidized::Config::Root, 'pid')
43
+ asetus.default.pid = File.join(Oxidized::Config::ROOT, 'pid')
40
44
 
41
- asetus.default.crash.directory = File.join(Oxidized::Config::Root, 'crashes')
45
+ # Extentions
46
+ asetus.default.extensions['oxidized-web'].load = false
47
+
48
+ asetus.default.crash.directory = File.join(Oxidized::Config::ROOT, 'crashes')
42
49
  asetus.default.crash.hostnames = false
43
50
 
44
51
  asetus.default.stats.history_size = 10
@@ -58,17 +65,21 @@ module Oxidized
58
65
 
59
66
  begin
60
67
  asetus.load # load system+user configs, merge to Config.cfg
61
- rescue StandardError => error
62
- raise InvalidConfig, "Error loading config: #{error.message}"
68
+ rescue StandardError => e
69
+ raise InvalidConfig, "Error loading config: #{e.message}"
63
70
  end
64
71
 
65
- raise NoConfig, 'edit ~/.config/oxidized/config' if asetus.create
72
+ raise NoConfig, "edit #{@configfile}" if asetus.create
66
73
 
67
74
  # override if comand line flag given
68
75
  asetus.cfg.debug = cmd_opts[:debug] if cmd_opts[:debug]
69
76
 
70
77
  asetus
71
78
  end
79
+
80
+ class << self
81
+ attr_reader :configfile
82
+ end
72
83
  end
73
84
 
74
85
  class << self
data/lib/oxidized/core.rb CHANGED
@@ -6,24 +6,56 @@ module Oxidized
6
6
  end
7
7
 
8
8
  class Core
9
+ include SemanticLogger::Loggable
10
+
9
11
  class NoNodesFound < OxidizedError; end
10
12
 
11
13
  def initialize(_args)
12
14
  Oxidized.mgr = Manager.new
13
15
  Oxidized.hooks = HookManager.from_config(Oxidized.config)
14
16
  nodes = Nodes.new
15
- raise NoNodesFound, 'source returns no usable nodes' if nodes.size.zero?
17
+ raise NoNodesFound, 'source returns no usable nodes' if nodes.empty?
16
18
 
17
19
  @worker = Worker.new nodes
18
- trap('HUP') { nodes.load }
19
- if Oxidized.config.rest?
20
+ @need_reload = false
21
+
22
+ # If we receive a SIGHUP, queue a reload of the state
23
+ reload_proc = proc do
24
+ @need_reload = true
25
+ end
26
+ Signals.register_signal('HUP', reload_proc)
27
+
28
+ # Load extensions, currently only oxidized-web
29
+ # We have different namespaces for oxidized-web, which needs to be
30
+ # adressed if we need a generic way to load extensions:
31
+ # - gem: oxidized-web
32
+ # - module: Oxidized::API
33
+ # - path: oxidized/web
34
+ # - entrypoint: Oxidized::API::Web.new(nodes, configuration)
35
+
36
+ # Initialize oxidized-web if requested
37
+ if Oxidized.config.has_key? 'rest'
38
+ logger.warn(
39
+ 'configuration: "rest" is deprecated. Migrate to ' \
40
+ '"extensions.oxidized-web" and remove "rest" from the configuration'
41
+ )
42
+ configuration = Oxidized.config.rest
43
+ elsif Oxidized.config.extensions &&
44
+ Oxidized.config.extensions['oxidized-web']&.load?
45
+ # This comment stops rubocop complaining about Style/IfUnlessModifier
46
+ configuration = Oxidized.config.extensions['oxidized-web']
47
+ end
48
+
49
+ if configuration
20
50
  begin
21
51
  require 'oxidized/web'
22
52
  rescue LoadError
23
- raise OxidizedError, 'oxidized-web not found: sudo gem install oxidized-web - \
24
- or disable web support by setting "rest: false" in your configuration'
53
+ raise OxidizedError,
54
+ 'oxidized-web not found: install it or disable it by ' \
55
+ 'removing "rest" and "extensions.oxidized-web" from your ' \
56
+ 'configuration'
25
57
  end
26
- @rest = API::Web.new nodes, Oxidized.config.rest
58
+ @rest = API::Web.new nodes, configuration
27
59
  @rest.run
28
60
  end
29
61
  run
@@ -31,9 +63,19 @@ module Oxidized
31
63
 
32
64
  private
33
65
 
66
+ def reload
67
+ logger.info("Reloading node list")
68
+ @worker.reload
69
+ @need_reload = false
70
+ end
71
+
34
72
  def run
35
- Oxidized.logger.debug "lib/oxidized/core.rb: Starting the worker..."
36
- @worker.work while sleep Config::Sleep
73
+ logger.debug "Starting the worker..."
74
+ loop do
75
+ reload if @need_reload
76
+ @worker.work
77
+ sleep Config::SLEEP
78
+ end
37
79
  end
38
80
  end
39
81
  end
@@ -14,30 +14,32 @@ class CiscoSparkDiff < Oxidized::Hook
14
14
  return unless ctx.node
15
15
  return unless ctx.event.to_s == "post_store"
16
16
 
17
- log "Connecting to Cisco Spark"
17
+ logger.info "Connecting to Cisco Spark"
18
18
  CiscoSpark.configure do |config|
19
19
  config.api_key = cfg.accesskey
20
20
  config.proxy = cfg.proxy if cfg.has_key?('proxy')
21
21
  end
22
22
  room = CiscoSpark::Room.new(id: cfg.space)
23
- log "Connected"
23
+ logger.info "Connected"
24
24
 
25
25
  if cfg.has_key?("diff") ? cfg.diff : true
26
26
  gitoutput = ctx.node.output.new
27
27
  diff = gitoutput.get_diff ctx.node, ctx.node.group, ctx.commitref, nil
28
28
  title = ctx.node.name.to_s
29
- log "Posting diff as snippet to #{cfg.space}"
30
- room.send_message CiscoSpark::Message.new(text: 'Device ' + title + ' modified:' + "\n" + diff[:patch].lines.to_a[4..-1].join)
29
+ logger.info "Posting diff as snippet to #{cfg.space}"
30
+ room.send_message CiscoSpark::Message.new(text: "Device #{title} modified:\n" +
31
+ diff[:patch].lines.to_a[4..-1].join)
31
32
  end
32
33
 
33
34
  if cfg.message?
34
- log cfg.message
35
- msg = cfg.message % { node: ctx.node.name.to_s, group: ctx.node.group.to_s, commitref: ctx.commitref, model: ctx.node.model.class.name.to_s.downcase }
36
- log msg
37
- log "Posting message to #{cfg.space}"
35
+ logger.info cfg.message
36
+ msg = cfg.message % { node: ctx.node.name.to_s, group: ctx.node.group.to_s, commitref: ctx.commitref,
37
+ model: ctx.node.model.class.name.to_s.downcase }
38
+ logger.info msg
39
+ logger.info "Posting message to #{cfg.space}"
38
40
  room.send_message CiscoSpark::Message.new(text: msg)
39
41
  end
40
42
 
41
- log "Finished"
43
+ logger.info "Finished"
42
44
  end
43
45
  end
@@ -28,7 +28,7 @@ class Exec < Oxidized::Hook
28
28
 
29
29
  def run_hook(ctx)
30
30
  env = make_env ctx
31
- log "Execute: #{@cmd.inspect}", :debug
31
+ logger.debug "Execute: #{@cmd.inspect}"
32
32
  th = Thread.new do
33
33
  run_cmd! env
34
34
  rescue StandardError => e
@@ -38,20 +38,21 @@ class Exec < Oxidized::Hook
38
38
  end
39
39
 
40
40
  def run_cmd!(env)
41
- pid, status = nil, nil
41
+ pid = nil
42
+ status = nil
42
43
  Timeout.timeout(@timeout) do
43
44
  pid = spawn env, @cmd, unsetenv_others: true
44
45
  pid, status = wait2 pid
45
46
  unless status.exitstatus.zero?
46
47
  msg = "#{@cmd.inspect} failed with exit value #{status.exitstatus}"
47
- log msg, :error
48
+ logger.error msg
48
49
  raise msg
49
50
  end
50
51
  end
51
52
  rescue Timeout::Error
52
53
  kill "TERM", pid
53
54
  msg = "#{@cmd} timed out"
54
- log msg, :error
55
+ logger.error msg
55
56
  raise Timeout::Error, msg
56
57
  end
57
58