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
@@ -1,60 +1,60 @@
1
1
  module Oxidized
2
- class CSV < Source
3
- def initialize
4
- @cfg = Oxidized.config.source.csv
5
- super
6
- end
7
-
8
- def setup
9
- if @cfg.empty?
10
- Oxidized.asetus.user.source.csv.file = File.join(Config::Root, 'router.db')
11
- Oxidized.asetus.user.source.csv.delimiter = /:/
12
- Oxidized.asetus.user.source.csv.map.name = 0
13
- Oxidized.asetus.user.source.csv.map.model = 1
14
- Oxidized.asetus.user.source.csv.gpg = false
15
- Oxidized.asetus.save :user
16
- raise NoConfig, 'no source csv config, edit ~/.config/oxidized/config'
2
+ module Source
3
+ class CSV < Source
4
+ def initialize
5
+ @cfg = Oxidized.config.source.csv
6
+ super
17
7
  end
18
- require 'gpgme' if @cfg.gpg?
19
- end
20
-
21
- def load(_node_want = nil)
22
- nodes = []
23
- open_file.each_line do |line|
24
- next if line =~ /^\s*#/
25
-
26
- data = line.chomp.split(@cfg.delimiter, -1)
27
- next if data.empty?
28
8
 
29
- # map node parameters
30
- keys = {}
31
- @cfg.map.each do |key, position|
32
- keys[key.to_sym] = node_var_interpolate data[position]
9
+ def setup
10
+ if @cfg.empty?
11
+ Oxidized.asetus.user.source.csv.file = File.join(Config::ROOT, 'router.db')
12
+ Oxidized.asetus.user.source.csv.delimiter = /:/
13
+ Oxidized.asetus.user.source.csv.map.name = 0
14
+ Oxidized.asetus.user.source.csv.map.model = 1
15
+ Oxidized.asetus.user.source.csv.gpg = false
16
+ Oxidized.asetus.save :user
17
+ raise NoConfig, "no source csv config, edit #{Oxidized::Config.configfile}"
33
18
  end
34
- keys[:model] = map_model keys[:model] if keys.has_key? :model
35
- keys[:group] = map_group keys[:group] if keys.has_key? :group
19
+ require 'gpgme' if @cfg.gpg?
36
20
 
37
- # map node specific vars
38
- vars = {}
39
- @cfg.vars_map.each do |key, position|
40
- vars[key.to_sym] = node_var_interpolate data[position]
41
- end
42
- keys[:vars] = vars unless vars.empty?
21
+ # map.name is mandatory
22
+ return if @cfg.map.has_key?('name')
43
23
 
44
- nodes << keys
24
+ raise InvalidConfig, "map/name is a mandatory source attribute, edit #{Oxidized::Config.configfile}"
45
25
  end
46
- nodes
47
- end
48
26
 
49
- private
50
-
51
- def open_file
52
- file = File.expand_path(@cfg.file)
53
- if @cfg.gpg?
54
- crypto = GPGME::Crypto.new password: @cfg.gpg_password
55
- crypto.decrypt(File.open(file)).to_s
56
- else
57
- File.open(file)
27
+ def load(_node_want = nil)
28
+ nodes = []
29
+ open_file.each_line do |line|
30
+ next if line =~ /^\s*#/
31
+
32
+ data = line.chomp.split(@cfg.delimiter, -1)
33
+ next if data.empty?
34
+
35
+ # map node parameters
36
+ keys = {}
37
+ @cfg.map.each do |key, position|
38
+ keys[key.to_sym] = node_var_interpolate data[position]
39
+ end
40
+ keys[:model] = map_model keys[:model] if keys.has_key? :model
41
+ keys[:group] = map_group keys[:group] if keys.has_key? :group
42
+
43
+ # map node specific vars
44
+ vars = {}
45
+ @cfg.vars_map.each do |key, position|
46
+ vars[key.to_s] = node_var_interpolate data[position]
47
+ end
48
+ keys[:vars] = vars unless vars.empty?
49
+
50
+ keys = Oxidized.hooks.source_node_transform(node: keys,
51
+ node_raw: data,
52
+ context: self)
53
+ next if keys.nil?
54
+
55
+ nodes << keys
56
+ end
57
+ nodes
58
58
  end
59
59
  end
60
60
  end
@@ -1,81 +1,94 @@
1
1
  module Oxidized
2
- class HTTP < Source
3
- def initialize
4
- @cfg = Oxidized.config.source.http
5
- super
6
- end
2
+ module Source
3
+ require "oxidized/source/jsonfile"
4
+ class HTTP < JSONFile
5
+ def initialize
6
+ super
7
+ @cfg = Oxidized.config.source.http
8
+ end
7
9
 
8
- def setup
9
- return unless @cfg.url.empty?
10
+ def setup
11
+ if @cfg.empty?
12
+ Oxidized.asetus.user.source.http.url = 'https://url/api'
13
+ Oxidized.asetus.user.source.http.map.name = 'name'
14
+ Oxidized.asetus.user.source.http.map.model = 'model'
15
+ Oxidized.asetus.save :user
10
16
 
11
- raise NoConfig, 'no source http url config, edit ~/.config/oxidized/config'
12
- end
13
-
14
- require "net/http"
15
- require "net/https"
16
- require "uri"
17
- require "json"
18
-
19
- def load(node_want = nil)
20
- nodes = []
21
- data = JSON.parse(read_http(node_want))
22
- data = string_navigate(data, @cfg.hosts_location) if @cfg.hosts_location?
23
- data.each do |node|
24
- next if node.empty?
25
-
26
- # map node parameters
27
- keys = {}
28
- @cfg.map.each do |key, want_position|
29
- keys[key.to_sym] = node_var_interpolate string_navigate(node, want_position)
17
+ raise NoConfig, "No source http config, edit #{Oxidized::Config.configfile}"
30
18
  end
31
- keys[:model] = map_model keys[:model] if keys.has_key? :model
32
- keys[:group] = map_group keys[:group] if keys.has_key? :group
33
19
 
34
- # map node specific vars
35
- vars = {}
36
- @cfg.vars_map.each do |key, want_position|
37
- vars[key.to_sym] = node_var_interpolate string_navigate(node, want_position)
20
+ # check for mandatory attributes
21
+ if !@cfg.has_key?('url')
22
+ raise InvalidConfig, "url is a mandatory http source attribute, edit #{Oxidized::Config.configfile}"
23
+ elsif !@cfg.map.has_key?('name')
24
+ raise InvalidConfig, "map/name is a mandatory source attribute, edit #{Oxidized::Config.configfile}"
38
25
  end
39
- keys[:vars] = vars unless vars.empty?
40
-
41
- nodes << keys
42
26
  end
43
- nodes
44
- end
45
27
 
46
- private
28
+ require "net/http"
29
+ require "net/https"
30
+ require "uri"
31
+ require "json"
47
32
 
48
- def string_navigate(object, wants)
49
- wants = wants.split(".").map do |want|
50
- head, match, _tail = want.partition(/\[\d+\]/)
51
- match.empty? ? head : [head, match[1..-2].to_i]
52
- end
53
- wants.flatten.each do |want|
54
- object = object[want] if object.respond_to? :each
33
+ def load(node_want = nil)
34
+ uri = URI.parse(@cfg.url)
35
+ data = JSON.parse(read_http(uri, node_want))
36
+ node_data = data
37
+ node_data = string_navigate_object(data, @cfg.hosts_location) if @cfg.hosts_location?
38
+ node_data = pagination(data, node_want) if @cfg.pagination?
39
+
40
+ transform_json(node_data)
55
41
  end
56
- object
57
- end
58
42
 
59
- def read_http(node_want)
60
- uri = URI.parse(@cfg.url)
61
- http = Net::HTTP.new(uri.host, uri.port)
62
- http.use_ssl = true if uri.scheme == 'https'
63
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE unless @cfg.secure
43
+ private
44
+
45
+ def pagination(data, node_want)
46
+ node_data = []
47
+ unless @cfg.pagination_key_name?
48
+ raise Oxidized::OxidizedError,
49
+ "if using pagination, 'pagination_key_name' setting must be set"
50
+ end
51
+
52
+ next_key = @cfg.pagination_key_name
53
+ # Process the initial batch first
54
+ node_data += string_navigate_object(data, @cfg.hosts_location) if @cfg.hosts_location?
55
+
56
+ # Now loop only for subsequent pages
57
+ loop do
58
+ # Check for the next URL *before* trying to fetch/add
59
+ break if data[next_key].nil?
64
60
 
65
- # Add read_timeout to handle case of big list of nodes (default value is 60 seconds)
66
- http.read_timeout = Integer(@cfg.read_timeout) if @cfg.has_key? "read_timeout"
61
+ # Fetch the next page
62
+ new_uri = URI.parse(data[next_key]) # Safe to parse now
63
+ data = JSON.parse(read_http(new_uri, node_want))
64
+
65
+ # Add nodes from the *newly fetched* batch
66
+ node_data += string_navigate_object(data, @cfg.hosts_location) if @cfg.hosts_location?
67
+ end
67
68
 
68
- # map headers
69
- headers = {}
70
- @cfg.headers.each do |header, value|
71
- headers[header] = value
69
+ node_data
72
70
  end
73
71
 
74
- req_uri = uri.request_uri
75
- req_uri = "#{req_uri}/#{node_want}" if node_want
76
- request = Net::HTTP::Get.new(req_uri, headers)
77
- request.basic_auth(@cfg.user, @cfg.pass) if @cfg.user? && @cfg.pass?
78
- http.request(request).body
72
+ def read_http(uri, node_want)
73
+ http = Net::HTTP.new(uri.host, uri.port)
74
+ http.use_ssl = true if uri.scheme == 'https'
75
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE unless @cfg.secure
76
+
77
+ # Add read_timeout to handle case of big list of nodes (default value is 60 seconds)
78
+ http.read_timeout = Integer(@cfg.read_timeout) if @cfg.has_key? "read_timeout"
79
+
80
+ # map headers
81
+ headers = {}
82
+ @cfg.headers.each do |header, value|
83
+ headers[header] = value
84
+ end
85
+
86
+ req_uri = uri.request_uri
87
+ req_uri = "#{req_uri}/#{node_want}" if node_want
88
+ request = Net::HTTP::Get.new(req_uri, headers)
89
+ request.basic_auth(@cfg.user, @cfg.pass) if @cfg.user? && @cfg.pass?
90
+ http.request(request).body
91
+ end
79
92
  end
80
93
  end
81
94
  end
@@ -0,0 +1,68 @@
1
+ module Oxidized
2
+ module Source
3
+ class JSONFile < Source
4
+ require "json"
5
+ def initialize
6
+ @cfg = Oxidized.config.source.jsonfile
7
+ super
8
+ end
9
+
10
+ def setup
11
+ if @cfg.empty?
12
+ Oxidized.asetus.user.source.jsonfile.file = File.join(Oxidized::Config::ROOT,
13
+ 'router.json')
14
+ Oxidized.asetus.user.source.jsonfile.map.name = "name"
15
+ Oxidized.asetus.user.source.jsonfile.map.model = "model"
16
+ Oxidized.asetus.user.source.jsonfile.gpg = false
17
+ Oxidized.asetus.save :user
18
+ raise NoConfig, "No source json config, edit #{Oxidized::Config.configfile}"
19
+ end
20
+ require 'gpgme' if @cfg.gpg?
21
+
22
+ # map.name is mandatory
23
+ return if @cfg.map.has_key?('name')
24
+
25
+ raise InvalidConfig, "map/name is a mandatory source attribute, edit #{Oxidized::Config.configfile}"
26
+ end
27
+
28
+ def load(*)
29
+ data = JSON.parse(open_file.read)
30
+ data = string_navigate_object(data, @cfg.hosts_location) if @cfg.hosts_location?
31
+
32
+ transform_json(data)
33
+ end
34
+
35
+ private
36
+
37
+ def transform_json(data)
38
+ nodes = []
39
+ data.each do |node|
40
+ next if node.empty?
41
+
42
+ # map node parameters
43
+ keys = {}
44
+ @cfg.map.each do |key, want_position|
45
+ keys[key.to_sym] = node_var_interpolate string_navigate_object(node, want_position)
46
+ end
47
+ keys[:model] = map_model keys[:model] if keys.has_key? :model
48
+ keys[:group] = map_group keys[:group] if keys.has_key? :group
49
+
50
+ # map node specific vars
51
+ vars = {}
52
+ @cfg.vars_map.each do |key, want_position|
53
+ vars[key.to_s] = node_var_interpolate string_navigate_object(node, want_position)
54
+ end
55
+ keys[:vars] = vars unless vars.empty?
56
+
57
+ keys = Oxidized.hooks.source_node_transform(node: keys,
58
+ node_raw: node,
59
+ context: self)
60
+ next if keys.nil?
61
+
62
+ nodes << keys
63
+ end
64
+ nodes
65
+ end
66
+ end
67
+ end
68
+ end
@@ -1,26 +1,81 @@
1
1
  module Oxidized
2
- class Source
3
- class NoConfig < OxidizedError; end
2
+ module Source
3
+ class Source
4
+ class NoConfig < OxidizedError; end
4
5
 
5
- def initialize
6
- @model_map = (Oxidized.config.model_map || {})
7
- @group_map = (Oxidized.config.group_map || {})
8
- end
6
+ def initialize
7
+ @model_map = Oxidized.config.model_map || {}
8
+ @group_map = Oxidized.config.group_map || {}
9
+ end
9
10
 
10
- def map_model(model)
11
- @model_map.has_key?(model) ? @model_map[model] : model
12
- end
11
+ # common code of #map_model and #map_group
12
+ def map_value(map_hash, original_value)
13
+ map_hash.each do |key, new_value|
14
+ mthd = key.instance_of?(Regexp) ? :match : :eql?
15
+ return new_value if original_value.send(mthd, key)
16
+ end
17
+ original_value
18
+ end
13
19
 
14
- def map_group(group)
15
- @group_map.has_key?(group) ? @group_map[group] : group
16
- end
20
+ # search a match for model in the configuration and returns it.
21
+ # If no match is found, return model
22
+ #
23
+ # model can be matched against a string or a regexp:
24
+ #
25
+ # model_map:
26
+ # cisco: ios
27
+ # juniper: junos
28
+ # !ruby/regexp /procurve/: procurve
29
+ def map_model(model)
30
+ map_value(@model_map, model)
31
+ end
32
+
33
+ # search a match for group in the configuration and returns it.
34
+ # If no match is found, return group
35
+ #
36
+ # group can be matched against a string or a regexp:
37
+ #
38
+ # group_map:
39
+ # alias1: groupA
40
+ # alias2: groupA
41
+ # alias3: groupB
42
+ # alias4: groupB
43
+ # !ruby/regexp /specialgroup/: groupS
44
+ # aliasN: groupZ
45
+ def map_group(group)
46
+ map_value(@group_map, group)
47
+ end
48
+
49
+ def node_var_interpolate(var)
50
+ case var
51
+ when "nil" then nil
52
+ when "false" then false
53
+ when "true" then true
54
+ else var
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ def open_file
61
+ file = File.expand_path(@cfg.file)
62
+ if @cfg.gpg?
63
+ crypto = GPGME::Crypto.new password: @cfg.gpg_password
64
+ crypto.decrypt(File.open(file)).to_s
65
+ else
66
+ File.open(file)
67
+ end
68
+ end
17
69
 
18
- def node_var_interpolate(var)
19
- case var
20
- when "nil" then nil
21
- when "false" then false
22
- when "true" then true
23
- else var
70
+ def string_navigate_object(object, wants)
71
+ wants = wants.split(".").map do |want|
72
+ head, match, _tail = want.partition(/\[\d+\]/)
73
+ match.empty? ? head : [head, match[1..-2].to_i]
74
+ end
75
+ wants.flatten.each do |want|
76
+ object = object[want] if object.respond_to? :each
77
+ end
78
+ object
24
79
  end
25
80
  end
26
81
  end
@@ -1,75 +1,87 @@
1
1
  module Oxidized
2
- class SQL < Source
3
- begin
4
- require 'sequel'
5
- rescue LoadError
6
- raise OxidizedError, 'sequel not found: sudo gem install sequel'
7
- end
2
+ module Source
3
+ class SQL < Source
4
+ begin
5
+ require 'sequel'
6
+ rescue LoadError
7
+ raise OxidizedError, 'sequel not found: sudo gem install sequel'
8
+ end
8
9
 
9
- def setup
10
- return unless @cfg.empty?
10
+ def setup
11
+ if @cfg.empty?
12
+ Oxidized.asetus.user.source.sql.adapter = 'sqlite'
13
+ Oxidized.asetus.user.source.sql.database = File.join(Config::ROOT, 'sqlite.db')
14
+ Oxidized.asetus.user.source.sql.table = 'devices'
15
+ Oxidized.asetus.user.source.sql.map.name = 'name'
16
+ Oxidized.asetus.user.source.sql.map.model = 'rancid'
17
+ Oxidized.asetus.save :user
18
+ raise NoConfig, "No source sql config, edit #{Oxidized::Config.configfile}"
19
+ end
11
20
 
12
- Oxidized.asetus.user.source.sql.adapter = 'sqlite'
13
- Oxidized.asetus.user.source.sql.database = File.join(Config::Root, 'sqlite.db')
14
- Oxidized.asetus.user.source.sql.table = 'devices'
15
- Oxidized.asetus.user.source.sql.map.name = 'name'
16
- Oxidized.asetus.user.source.sql.map.model = 'rancid'
17
- Oxidized.asetus.save :user
18
- raise NoConfig, 'no source sql config, edit ~/.config/oxidized/config'
19
- end
21
+ # map.name is mandatory
22
+ return if @cfg.map.has_key?('name')
20
23
 
21
- def load(node_want = nil)
22
- nodes = []
23
- db = connect
24
- query = db[@cfg.table.to_sym]
25
- query = query.with_sql(@cfg.query) if @cfg.query?
24
+ raise InvalidConfig, "map/name is a mandatory source attribute, edit #{Oxidized::Config.configfile}"
25
+ end
26
26
 
27
- query = query.where(@cfg.map.name.to_sym => node_want) if node_want
27
+ def load(node_want = nil)
28
+ nodes = []
29
+ db = connect
30
+ query = db[@cfg.table.to_sym]
31
+ query = query.with_sql(@cfg.query) if @cfg.query?
28
32
 
29
- query.each do |node|
30
- # map node parameters
31
- keys = {}
32
- @cfg.map.each { |key, sql_column| keys[key.to_sym] = node_var_interpolate node[sql_column.to_sym] }
33
- keys[:model] = map_model keys[:model] if keys.has_key? :model
34
- keys[:group] = map_group keys[:group] if keys.has_key? :group
33
+ query = query.where(@cfg.map.name.to_sym => node_want) if node_want
35
34
 
36
- # map node specific vars
37
- vars = {}
38
- @cfg.vars_map.each do |key, sql_column|
39
- vars[key.to_sym] = node_var_interpolate node[sql_column.to_sym]
40
- end
41
- keys[:vars] = vars unless vars.empty?
35
+ query.each do |node|
36
+ # map node parameters
37
+ keys = {}
38
+ @cfg.map.each { |key, sql_column| keys[key.to_sym] = node_var_interpolate node[sql_column.to_sym] }
39
+ keys[:model] = map_model keys[:model] if keys.has_key? :model
40
+ keys[:group] = map_group keys[:group] if keys.has_key? :group
41
+
42
+ # map node specific vars
43
+ vars = {}
44
+ @cfg.vars_map.each do |key, sql_column|
45
+ vars[key.to_s] = node_var_interpolate node[sql_column.to_sym]
46
+ end
47
+ keys[:vars] = vars unless vars.empty?
42
48
 
43
- nodes << keys
49
+ keys = Oxidized.hooks.source_node_transform(node: keys,
50
+ node_raw: node.to_hash,
51
+ context: self)
52
+ next if keys.nil?
53
+
54
+ nodes << keys
55
+ end
56
+ db.disconnect
57
+ nodes
44
58
  end
45
- db.disconnect
46
- nodes
47
- end
48
59
 
49
- private
60
+ private
50
61
 
51
- def initialize
52
- super
53
- @cfg = Oxidized.config.source.sql
54
- end
62
+ def initialize
63
+ super
64
+ @cfg = Oxidized.config.source.sql
65
+ end
55
66
 
56
- def connect
57
- options = {
58
- adapter: @cfg.adapter,
59
- host: @cfg.host?,
60
- user: @cfg.user?,
61
- password: @cfg.password?,
62
- database: @cfg.database,
63
- ssl_mode: @cfg.ssl_mode?
64
- }
65
- if @cfg.with_ssl?
66
- options.merge!(sslca: @cfg.ssl_ca?,
67
- sslcert: @cfg.ssl_cert?,
68
- sslkey: @cfg.ssl_key?)
67
+ def connect
68
+ options = {
69
+ adapter: @cfg.adapter,
70
+ host: @cfg.host?,
71
+ user: @cfg.user?,
72
+ password: @cfg.password?,
73
+ database: @cfg.database,
74
+ ssl_mode: @cfg.ssl_mode?
75
+ }
76
+ if @cfg.with_ssl?
77
+ options.merge!(sslca: @cfg.ssl_ca?,
78
+ sslcert: @cfg.ssl_cert?,
79
+ sslkey: @cfg.ssl_key?)
80
+ end
81
+ Sequel.connect(options)
82
+ rescue Sequel::AdapterNotFound => e
83
+ raise OxidizedError, "SQL adapter gem not installed: " + e.message
69
84
  end
70
- Sequel.connect(options)
71
- rescue Sequel::AdapterNotFound => error
72
- raise OxidizedError, "SQL adapter gem not installed: " + error.message
73
85
  end
74
86
  end
75
87
  end
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Oxidized
2
- VERSION = '0.29.1'.freeze
3
- VERSION_FULL = '0.29.1'.freeze
4
+ VERSION = '0.37.0'
5
+ VERSION_FULL = '0.37.0'
4
6
  def self.version_set
5
7
  version_full = %x(git describe --tags).chop rescue ""
6
8
  version = %x(git describe --tags --abbrev=0).chop rescue ""
@@ -12,8 +14,8 @@ module Oxidized
12
14
  const_set(:VERSION, version)
13
15
  const_set(:VERSION_FULL, version_full)
14
16
  file = File.readlines(__FILE__)
15
- file[1] = " VERSION = '%s'.freeze\n" % VERSION
16
- file[2] = " VERSION_FULL = '%s'.freeze\n" % VERSION_FULL
17
+ file[3] = " VERSION = '%s'\n" % VERSION
18
+ file[4] = " VERSION_FULL = '%s'\n" % VERSION_FULL
17
19
  File.write(__FILE__, file.join)
18
20
  end
19
21
  end