oxidized 0.34.3 → 0.35.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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/codeql.yml +4 -4
  3. data/.github/workflows/publishdocker.yml +82 -11
  4. data/.github/workflows/ruby.yml +1 -1
  5. data/.github/workflows/stale.yml +1 -1
  6. data/.rubocop.yml +1 -0
  7. data/.rubocop_todo.yml +1 -1
  8. data/CHANGELOG.md +39 -1
  9. data/Dockerfile +74 -66
  10. data/README.md +2 -2
  11. data/Rakefile +21 -3
  12. data/docs/Configuration.md +92 -0
  13. data/docs/Creating-Models.md +85 -19
  14. data/docs/Ruby-API.md +52 -0
  15. data/docs/Supported-OS-Types.md +4 -1
  16. data/extra/oxidized.runit +1 -1
  17. data/lib/oxidized/core.rb +2 -1
  18. data/lib/oxidized/input/http.rb +9 -2
  19. data/lib/oxidized/input/scp.rb +3 -3
  20. data/lib/oxidized/input/ssh.rb +3 -3
  21. data/lib/oxidized/input/telnet.rb +2 -2
  22. data/lib/oxidized/model/adva.rb +1 -1
  23. data/lib/oxidized/model/airfiber.rb +1 -1
  24. data/lib/oxidized/model/aoscx.rb +29 -44
  25. data/lib/oxidized/model/aosw.rb +1 -1
  26. data/lib/oxidized/model/arubainstant.rb +18 -6
  27. data/lib/oxidized/model/asternos.rb +1 -1
  28. data/lib/oxidized/model/axos.rb +1 -1
  29. data/lib/oxidized/model/comtrol.rb +1 -1
  30. data/lib/oxidized/model/cumulus.rb +2 -7
  31. data/lib/oxidized/model/eatonnetwork.rb +5 -0
  32. data/lib/oxidized/model/eltex.rb +1 -1
  33. data/lib/oxidized/model/enterasys800.rb +1 -1
  34. data/lib/oxidized/model/fabricos.rb +1 -1
  35. data/lib/oxidized/model/fsos.rb +1 -1
  36. data/lib/oxidized/model/garderos.rb +1 -6
  37. data/lib/oxidized/model/ironware.rb +1 -1
  38. data/lib/oxidized/model/mlnxos.rb +3 -10
  39. data/lib/oxidized/model/model.rb +101 -12
  40. data/lib/oxidized/model/netgear.rb +1 -0
  41. data/lib/oxidized/model/netonix.rb +1 -1
  42. data/lib/oxidized/model/netscaler.rb +1 -1
  43. data/lib/oxidized/model/nxos.rb +9 -9
  44. data/lib/oxidized/model/onefinity.rb +1 -1
  45. data/lib/oxidized/model/opnsense.rb +8 -0
  46. data/lib/oxidized/model/perle.rb +35 -0
  47. data/lib/oxidized/model/pfsense.rb +8 -0
  48. data/lib/oxidized/model/saos10.rb +1 -1
  49. data/lib/oxidized/model/siklu.rb +1 -1
  50. data/lib/oxidized/model/siklumhtg.rb +1 -1
  51. data/lib/oxidized/model/sixwind.rb +1 -1
  52. data/lib/oxidized/model/slxos.rb +1 -1
  53. data/lib/oxidized/model/sonicos.rb +1 -1
  54. data/lib/oxidized/model/speedtouch.rb +1 -1
  55. data/lib/oxidized/model/telco.rb +1 -1
  56. data/lib/oxidized/model/tnsr.rb +7 -3
  57. data/lib/oxidized/model/trango.rb +1 -1
  58. data/lib/oxidized/model/ucs.rb +1 -1
  59. data/lib/oxidized/model/voltaire.rb +1 -1
  60. data/lib/oxidized/model/vyatta.rb +1 -1
  61. data/lib/oxidized/model/vyos.rb +34 -0
  62. data/lib/oxidized/model/zhoneolt.rb +1 -1
  63. data/lib/oxidized/model/zynoscli.rb +1 -1
  64. data/lib/oxidized/node.rb +6 -1
  65. data/lib/oxidized/source/http.rb +10 -2
  66. data/lib/oxidized/version.rb +2 -2
  67. data/oxidized.gemspec +4 -5
  68. metadata +13 -29
  69. data/lib/oxidized/model/timos.rb +0 -10
@@ -1,27 +1,20 @@
1
1
  class MLNXOS < Oxidized::Model
2
2
  using Refinements
3
3
 
4
- prompt /^\r?(\e.+\e>\r)?\S* \[\S+: (master|standby)\] [#>] $/
4
+ prompt /^\r?\S* \[\S+: (master|standby)\] [#>] $/
5
5
  comment '## '
6
+ clean :escape_codes
6
7
 
7
8
  # Pager Handling
8
9
  # "Normal" pager: "lines 183-204 "
9
10
  # Last pager: "lines 256-269/269 (END) "
10
- expect /\e\[7mlines \d+-\d+( |\/\d+ \(END\) )/ do |data, re|
11
+ expect /lines \d+-\d+( |\/\d+ \(END\) )/ do |data, re|
11
12
  send ' '
12
13
  data.sub re, ''
13
14
  end
14
15
 
15
- # Remove ANSI escape codes
16
- expect /\e\[[0-?]*[ -\/]*[@-~]\r?/ do |data, re|
17
- data.sub re, ''
18
- end
19
-
20
16
  cmd :all do |cfg|
21
- cfg.gsub! "\e[m", '' # Remove reset formating
22
- cfg.gsub! "\e[K", '' # Remove erase in line
23
17
  cfg.gsub! /.\x08/, '' # Remove Backspace char
24
- cfg.gsub! "\r", '' # Remove Cariage Return
25
18
  cfg.gsub! /^CPU load averages:\s.+/, '' # Omit constantly changing CPU info
26
19
  cfg.gsub! /^System memory:\s.+/, '' # Omit constantly changing memory info
27
20
  cfg.gsub! /^Uptime:\s.+/, '' # Omit constantly changing uptime info
@@ -9,18 +9,22 @@ module Oxidized
9
9
 
10
10
  include Oxidized::Config::Vars
11
11
 
12
+ # rubocop:disable Style/FormatStringToken
13
+ METADATA_DEFAULT = "%{comment}Fetched by Oxidized with model %{model} " \
14
+ "from host %{name} [%{ip}]\n".freeze
15
+ # rubocop:enable Style/FormatStringToken
16
+
12
17
  class << self
13
18
  def inherited(klass)
14
19
  super
15
20
  if klass.superclass == Oxidized::Model
16
- # rubocop:disable Style/RedundantParentheses
17
- klass.instance_variable_set '@cmd', (Hash.new { |h, k| h[k] = [] })
18
- klass.instance_variable_set '@cfg', (Hash.new { |h, k| h[k] = [] })
19
- klass.instance_variable_set '@procs', (Hash.new { |h, k| h[k] = [] })
20
- # rubocop:enable Style/RedundantParentheses
21
+ klass.instance_variable_set('@cmd', Hash.new { |h, k| h[k] = [] })
22
+ klass.instance_variable_set('@cfg', Hash.new { |h, k| h[k] = [] })
23
+ klass.instance_variable_set('@procs', Hash.new { |h, k| h[k] = [] })
21
24
  klass.instance_variable_set '@expect', []
22
25
  klass.instance_variable_set '@comment', nil
23
26
  klass.instance_variable_set '@prompt', nil
27
+ klass.instance_variable_set '@metadata', {}
24
28
  else # we're subclassing some existing model, take its variables
25
29
  instance_variables.each do |var|
26
30
  iv = instance_variable_get(var)
@@ -58,11 +62,23 @@ module Oxidized
58
62
  if cmd_arg.instance_of?(Symbol)
59
63
  process_args_block(@cmd[cmd_arg], args, block)
60
64
  else
61
- process_args_block(@cmd[:cmd], args, [cmd_arg, block])
65
+ # Normal command
66
+ process_args_block(@cmd[:cmd], args,
67
+ { cmd: cmd_arg, args: args, block: block })
62
68
  end
63
69
  logger.debug "Added #{cmd_arg} to the commands list"
64
70
  end
65
71
 
72
+ def metadata(position, value = nil, &block)
73
+ return unless %i[top bottom].include? position
74
+
75
+ if block_given?
76
+ @metadata[position] = block
77
+ else
78
+ @metadata[position] = value
79
+ end
80
+ end
81
+
66
82
  def cmds
67
83
  @cmd
68
84
  end
@@ -75,11 +91,33 @@ module Oxidized
75
91
  @expect
76
92
  end
77
93
 
94
+ def clean(what)
95
+ case what
96
+ when :escape_codes
97
+ ansi_escape_regex = /
98
+ \r? # Optional carriage return at start
99
+ \e # ESC character - starts escape sequence
100
+ (?: # Non-capturing group for different sequence types:
101
+ # Type 1: CSI (Control Sequence Introducer)
102
+ \[ # Literal '[' - starts CSI sequence
103
+ [0-?]* # Parameter bytes: digits (0-9), semicolon, colon, etc.
104
+ [ -\/]* # Intermediate bytes: space through slash characters
105
+ [@-~] # Final byte: determines the actual command
106
+ | # OR
107
+ # Type 2: Simple escape
108
+ [=>] # Single character commands after ESC
109
+ )
110
+ \r? # Optional carriage return at end
111
+ /x
112
+ expect ansi_escape_regex do |data, re|
113
+ data.gsub re, ''
114
+ end
115
+ end
116
+ end
117
+
78
118
  # calls the block at the end of the model, prepending the output of the
79
119
  # block to the output string
80
120
  #
81
- # @author Saku Ytti <saku@ytti.fi>
82
- # @since 0.0.39
83
121
  # @yield expects block which should return [String]
84
122
  # @return [void]
85
123
  def pre(**args, &block)
@@ -89,8 +127,6 @@ module Oxidized
89
127
  # calls the block at the end of the model, adding the output of the block
90
128
  # to the output string
91
129
  #
92
- # @author Saku Ytti <saku@ytti.fi>
93
- # @since 0.0.39
94
130
  # @yield expects block which should return [String]
95
131
  # @return [void]
96
132
  def post(**args, &block)
@@ -109,6 +145,9 @@ module Oxidized
109
145
  if block.instance_of?(Array)
110
146
  target.reject! { |k, _| k == block[0] }
111
147
  target.push(block)
148
+ elsif block.instance_of?(Hash)
149
+ target.reject! { |item| item[:cmd] == block[:cmd] }
150
+ target.push(block)
112
151
  else
113
152
  target.replace([block])
114
153
  end
@@ -139,6 +178,44 @@ module Oxidized
139
178
  process_cmd_output out, string
140
179
  end
141
180
 
181
+ def metadata(position)
182
+ return unless %i[top bottom].include? position
183
+
184
+ model_metadata = self.class.instance_variable_get(:@metadata)
185
+ var_position = { top: "metadata_top", bottom: "metadata_bottom" }
186
+ if model_metadata[:top] || model_metadata[:bottom]
187
+ # the model defines metadata at :top ot :bottom, use the model
188
+ value = model_metadata[position]
189
+ value.is_a?(Proc) ? instance_eval(&value) : interpolate_string(value)
190
+ elsif vars("metadata_top") || vars("metadata_bottom")
191
+ # vars defines metadata_top or metadata bottom, use the vars
192
+ interpolate_string(vars(var_position[position]))
193
+ elsif position == :top
194
+ # default: use METADATA_DEFAULT for top
195
+ interpolate_string(METADATA_DEFAULT)
196
+ end
197
+ end
198
+
199
+ def interpolate_string(template)
200
+ return nil unless template
201
+
202
+ time = Time.now
203
+ template_variables = {
204
+ model: self.class.name,
205
+ name: node.name,
206
+ ip: node.ip,
207
+ group: node.group,
208
+ comment: self.class.comment,
209
+ year: time.year,
210
+ month: "%02d" % time.month,
211
+ day: "%02d" % time.day,
212
+ hour: "%02d" % time.hour,
213
+ minute: "%02d" % time.min,
214
+ second: "%02d" % time.sec
215
+ }
216
+ template % template_variables
217
+ end
218
+
142
219
  def output
143
220
  @input.output
144
221
  end
@@ -171,19 +248,31 @@ module Oxidized
171
248
  def get
172
249
  logger.debug 'Collecting commands\' outputs'
173
250
  outputs = Outputs.new
174
- procs = self.class.procs
175
- self.class.cmds[:cmd].each do |command, block|
251
+ self.class.cmds[:cmd].each do |data|
252
+ command = data[:cmd]
253
+ args = data[:args]
254
+ block = data[:block]
255
+
256
+ next if args.include?(:if) && !instance_exec(&args[:if])
257
+
176
258
  out = cmd command, &block
177
259
  return false unless out
178
260
 
179
261
  outputs << out
180
262
  end
263
+ procs = self.class.procs
181
264
  procs[:pre].each do |pre_proc|
182
265
  outputs.unshift process_cmd_output(instance_eval(&pre_proc), '')
183
266
  end
184
267
  procs[:post].each do |post_proc|
185
268
  outputs << process_cmd_output(instance_eval(&post_proc), '')
186
269
  end
270
+ if vars("metadata") == true
271
+ metadata_top = metadata(:top)
272
+ metadata_bottom = metadata(:bottom)
273
+ outputs.unshift metadata_top if metadata_top
274
+ outputs << metadata_bottom if metadata_bottom
275
+ end
187
276
  outputs
188
277
  end
189
278
 
@@ -3,6 +3,7 @@ class Netgear < Oxidized::Model
3
3
 
4
4
  comment '!'
5
5
  prompt /^\(?[\w \-+.]+\)? ?[#>] ?$/
6
+ clean :escape_codes
6
7
 
7
8
  # Handle pager for "show version" on old Netgear models: #2394
8
9
  expect /^--More-- or \(q\)uit$/ do |data, re|
@@ -1,7 +1,7 @@
1
1
  class Netonix < Oxidized::Model
2
2
  using Refinements
3
3
 
4
- prompt /^[\w\s\(\).@_\/:-]+#/
4
+ prompt /^[\w\s().@_\/:-]+#/
5
5
 
6
6
  cmd :all do |cfg|
7
7
  cfg.cut_both
@@ -1,7 +1,7 @@
1
1
  class NetScaler < Oxidized::Model
2
2
  using Refinements
3
3
 
4
- prompt /^(.*[\w\.-]*>\s?)$/
4
+ prompt /^(.*[\w.-]*>\s?)$/
5
5
  comment '# '
6
6
 
7
7
  cmd :all do |cfg|
@@ -1,12 +1,11 @@
1
1
  class NXOS < Oxidized::Model
2
2
  using Refinements
3
3
 
4
- prompt /^(\r?[\w.@_()-]+[#]\s?)$/
4
+ prompt /^(\r?[\w.@_()-]+\#\s?)$/
5
5
  comment '! '
6
6
 
7
- def filter(cfg)
8
- cfg.gsub! /\r\n?/, "\n"
9
- cfg.gsub! prompt, ''
7
+ cmd :all do |cfg|
8
+ cfg.cut_both
10
9
  end
11
10
 
12
11
  cmd :secret do |cfg|
@@ -21,21 +20,22 @@ class NXOS < Oxidized::Model
21
20
  end
22
21
 
23
22
  cmd 'show version' do |cfg|
24
- cfg = filter cfg
25
23
  cfg = cfg.each_line.take_while { |line| not line.match(/uptime|bootflash:\s+\d+\skB|sysmgrcli_show_flash_size/i) }
26
- comment cfg.join
24
+ comment cfg.join + "\n"
27
25
  end
28
26
 
29
27
  cmd 'show inventory all' do |cfg|
30
- cfg = filter cfg
28
+ if cfg.include? "% Invalid parameter detected at '^' marker."
29
+ # 'show inventory all' isn't supported on older versions (See Issue #3657)
30
+ cfg = cmd 'show inventory'
31
+ end
31
32
  comment cfg
32
33
  end
33
34
 
34
35
  cmd 'show running-config' do |cfg|
35
- cfg = filter cfg
36
36
  cfg.gsub! /^(show run.*)$/, '! \1'
37
37
  cfg.gsub! /^!Time:[^\n]*\n/, ''
38
- cfg.gsub! /^[\w.@_()-]+[#].*$/, ''
38
+ cfg.gsub! /^[\w.@_()-]+\#.*$/, ''
39
39
  cfg
40
40
  end
41
41
 
@@ -3,7 +3,7 @@ class OneFinity < Oxidized::Model
3
3
 
4
4
  # Fujitsu 1finity
5
5
 
6
- prompt /(\r?[\w.@_()-]+[>]\s?)$/
6
+ prompt /(\r?[\w.@_()-]+>\s?)$/
7
7
 
8
8
  cmd :all do |cfg|
9
9
  cfg.each_line.to_a[1..-3].join
@@ -22,6 +22,14 @@ class OpnSense < Oxidized::Model
22
22
  xmlcomment version
23
23
  end
24
24
 
25
+ metadata :bottom do
26
+ xmlcomment interpolate_string(
27
+ vars("metadata_bottom") ||
28
+ vars("metadata_top") ||
29
+ Oxidized::Model::METADATA_DEFAULT
30
+ )
31
+ end
32
+
25
33
  cfg :ssh do
26
34
  exec true
27
35
  pre_logout 'exit'
@@ -0,0 +1,35 @@
1
+ class Perle < Oxidized::Model
2
+ using Refinements
3
+
4
+ prompt /^[\w.-]+#/
5
+ comment '! '
6
+
7
+ cmd :all do |cfg|
8
+ cfg.cut_both
9
+ end
10
+
11
+ cmd 'show version verbose' do |cfg|
12
+ comment cfg
13
+ end
14
+
15
+ cmd 'show system hardware' do |cfg|
16
+ comment cfg + "\n"
17
+ end
18
+
19
+ cmd 'show interfaces transceiver' do |cfg|
20
+ out = []
21
+ cfg.each_line do |line|
22
+ out << line if line =~ /SFP Information/
23
+ out << line if line =~ /Vendor Name/
24
+ out << line if line =~ /Vendor Serial Number/
25
+ end
26
+ comment out.join + "\n"
27
+ end
28
+
29
+ cmd 'show running-config'
30
+
31
+ cfg :ssh do
32
+ post_login 'terminal length 0'
33
+ pre_logout 'exit'
34
+ end
35
+ end
@@ -26,6 +26,14 @@ class PfSense < Oxidized::Model
26
26
  xmlcomment "PFsense #{version}"
27
27
  end
28
28
 
29
+ metadata :bottom do
30
+ xmlcomment interpolate_string(
31
+ vars("metadata_bottom") ||
32
+ vars("metadata_top") ||
33
+ Oxidized::Model::METADATA_DEFAULT
34
+ )
35
+ end
36
+
29
37
  cfg :ssh do
30
38
  exec true
31
39
  pre_logout 'exit'
@@ -3,7 +3,7 @@ class SAOS10 < Oxidized::Model
3
3
  # Ciena SAOS switch
4
4
  # used for 10.x devices
5
5
 
6
- prompt /^[\w\-]+\*?> ?$/
6
+ prompt /^[\w-]+\*?> ?$/
7
7
  comment '# '
8
8
 
9
9
  cmd :all do |cfg|
@@ -3,7 +3,7 @@ class Siklu < Oxidized::Model
3
3
 
4
4
  # Siklu EtherHaul #
5
5
 
6
- prompt /^[\^M\s]{0,}[\w\-\s\.\"]+>$/
6
+ prompt /^[\^M\s]{0,}[\w\-\s."]+>$/
7
7
 
8
8
  cmd 'copy startup-configuration display' do |cfg|
9
9
  cfg.each_line.to_a[2..2].join
@@ -4,7 +4,7 @@ class SikluMHTG < Oxidized::Model
4
4
  # Siklu MultiHaul TG#
5
5
  # Requires source to define the model as SikluMHTG #
6
6
 
7
- prompt /^\r?MH-[TN]\d{3}[\@][\w]{2,8}>$/
7
+ prompt /^\r?MH-[TN]\d{3}@\w{2,8}>$/
8
8
 
9
9
  expect /--More--/ do |data, re|
10
10
  send ' '
@@ -1,7 +1,7 @@
1
1
  class SixWind < Oxidized::Model
2
2
  using Refinements
3
3
 
4
- prompt /^[\w\s\(\).@_\/:-]+[>] $/
4
+ prompt /^[\w\s().@_\/:-]+> $/
5
5
  comment '# '
6
6
 
7
7
  cmd :all do |cfg|
@@ -16,7 +16,7 @@ class SLXOS < Oxidized::Model
16
16
  cfg.gsub! /.*Power Usage.*/, '' # remove unwanted lines power usage
17
17
  cfg.gsub! /^Update:.*$/, '' # remove unwanted current date
18
18
  cfg.gsub! /Time A(live|wake).*/, '' # remove unwanted lines time alive/awake
19
- cfg.gsub! /([\[]*)1([\]]*)<->([\[]*)2([\]]*)(<->([\[]*)3([\]]*))*/, ''
19
+ cfg.gsub! /(\[*)1(\]*)<->(\[*)2(\]*)(<->(\[*)3(\]*))*/, ''
20
20
 
21
21
  comment cfg
22
22
  end
@@ -3,7 +3,7 @@ class SonicOS < Oxidized::Model
3
3
 
4
4
  # Applies to Sonicwall NSA series firewalls
5
5
 
6
- prompt /^\w+@[\w\-]+[>]\(?.+\)?\s?/
6
+ prompt /^\w+@[\w-]+>\(?.+\)?\s?/
7
7
  comment '! '
8
8
 
9
9
  # Accept policiy message (see Issue #3339). Tested on 6.5 and 7.1
@@ -1,7 +1,7 @@
1
1
  class SpeedTouch < Oxidized::Model
2
2
  using Refinements
3
3
 
4
- prompt /([\w{}=]+[>])$/
4
+ prompt /([\w{}=]+>)$/
5
5
  comment '! '
6
6
 
7
7
  expect /login$/ do
@@ -3,7 +3,7 @@ class TELCO < Oxidized::Model
3
3
 
4
4
  # Telco Systems T-Marc 3306
5
5
 
6
- prompt /^(\r?[\w.@_()-]+[#]\s?)$/
6
+ prompt /^(\r?[\w.@_()-]+\#\s?)$/
7
7
  comment '! '
8
8
 
9
9
  cmd :all do |cfg|
@@ -13,14 +13,16 @@ class TNSR < Oxidized::Model
13
13
  # received "\r\nVersion: 25.02-2\r\n\r\nFor information see 'show documentation'\r\n\r\n"
14
14
  # received "\rbgp01 tnsr# "
15
15
  # when the --More-- pager is the last line of paged output, the prompt contains \x08\x20\x08 orphans on the begin
16
+ # on older version (23.06) --More-- pager is sometimes misplaced
16
17
  # you can look for context into spec simulation:
17
- # spec/model/data/tnsr:TNSR_24.10-3_short-config:simulation.yaml
18
- # spec/model/data/tnsr:TNSR_25.02-2_long-config-and-pager-at-last-line:simulation.yaml
18
+ # spec/model/data/tnsr#TNSR_24.10-3_short-config#simulation.yaml
19
+ # spec/model/data/tnsr#TNSR_25.02-2_long-config-and-pager-at-last-line#simulation.yaml
20
+ # spec/model/data/tnsr#TNSR_23.06-3_with-misplaced-pager#simulation.yaml
19
21
  prompt /^((\x08{8}\x20{8}\x08{8})?\r?[\w-]+\stnsr#\s?)$/
20
22
 
21
23
  comment '! '
22
24
 
23
- expect /^--More--/ do |data, re|
25
+ expect /^--More--|--More--$/ do |data, re|
24
26
  send ' '
25
27
  data.sub re, ''
26
28
  end
@@ -40,6 +42,8 @@ class TNSR < Oxidized::Model
40
42
  end
41
43
 
42
44
  cmd 'show version all' do |cfg|
45
+ # for older tnsr versions
46
+ cfg = cmd('show version') if cfg.to_s =~ /^CLI syntax error:.+Unknown command$/
43
47
  comment cfg
44
48
  end
45
49
 
@@ -14,7 +14,7 @@ class Trango < Oxidized::Model
14
14
  out << ("opmode " + Regexp.last_match[1])
15
15
  out << ("defaultopmode " + Regexp.last_match[2])
16
16
  end
17
- out << ("power " + Regexp.last_match[1]) if line =~ /\[Tx Power\] ([\-\d]+) dBm/
17
+ out << ("power " + Regexp.last_match[1]) if line =~ /\[Tx Power\] ([-\d]+) dBm/
18
18
  out << ("freq " + Regexp.last_match[1] + ' ' + Regexp.last_match[2]) if line =~ /\[Active Channel\] (\d+) (v|h)/
19
19
  out << ("peerid " + Regexp.last_match[1]) if line =~ /\[Peer ID\] ([A-F0-9]+)/
20
20
  out << ("utype " + Regexp.last_match[1]) if line =~ /\[Unit Type\] (\S+)/
@@ -1,7 +1,7 @@
1
1
  class UCS < Oxidized::Model
2
2
  using Refinements
3
3
 
4
- prompt /^(\r?[\w.@_()-]+[#]\s?)$/
4
+ prompt /^(\r?[\w.@_()-]+\#\s?)$/
5
5
  comment '! '
6
6
 
7
7
  cmd 'show version brief' do |cfg|
@@ -5,7 +5,7 @@ class VOLTAIRE < Oxidized::Model
5
5
  comment '## '
6
6
 
7
7
  # Pager Handling
8
- expect /.+lines\s\d+-\d+([\s]|\/\d+\s\(END\)\s).+$/ do |data, re|
8
+ expect /.+lines\s\d+-\d+(\s|\/\d+\s\(END\)\s).+$/ do |data, re|
9
9
  send ' '
10
10
  data.sub re, ''
11
11
  end
@@ -1,7 +1,7 @@
1
1
  class Vyatta < Oxidized::Model
2
2
  using Refinements
3
3
 
4
- # Brocade Vyatta / VyOS model #
4
+ # Brocade Vyatta model #
5
5
 
6
6
  prompt /@.*(:~\$|>)\s/
7
7
 
@@ -0,0 +1,34 @@
1
+ class Vyos < Oxidized::Model
2
+ using Refinements
3
+
4
+ # VyOS model #
5
+ # Vyos is a Fork of Vyatta and is being actively developed.
6
+ # https://vyos.org/
7
+
8
+ prompt /^\S+@\S+(:~\$|>) $/
9
+ clean :escape_codes
10
+
11
+ cmd :all do |cfg|
12
+ cfg.lines.to_a[1..-2].join
13
+ end
14
+
15
+ cmd :secret do |cfg|
16
+ cfg.gsub! /secret (\S+).*/, 'secret <secret removed>'
17
+ cfg.gsub! /password (\S+).*/, 'password <secret removed>'
18
+ cfg.gsub! /community (\S+)/, 'community <secret removed>'
19
+ cfg.gsub! /private key (\S+).*/, 'private key <secret removed>'
20
+ # password in URLs like protocol://user:password@domain.tld/
21
+ cfg.gsub! /([a-z]+:\/\/[^:\s]+:)\S+@/, '\1<secret removed>@'
22
+ cfg
23
+ end
24
+
25
+ cmd 'show version' do |cfg|
26
+ comment cfg
27
+ end
28
+
29
+ cmd 'show configuration commands | no-more'
30
+
31
+ cfg :ssh do
32
+ pre_logout 'exit'
33
+ end
34
+ end
@@ -5,7 +5,7 @@ class ZhoneOLT < Oxidized::Model
5
5
 
6
6
  # the prompt can be anything on zhone, but it defaults to 'zXX>' and we
7
7
  # always use hostname>
8
- prompt /^(\r*[\w.@():-]+[>]\s?)$/
8
+ prompt /^(\r*[\w.@():-]+>\s?)$/
9
9
  comment '# '
10
10
 
11
11
  cmd :secret do |cfg|
@@ -30,7 +30,7 @@ class ZyNOSCLI < Oxidized::Model
30
30
  expect /[pP]assword:\s?$/
31
31
  send vars(:enable) + "\n"
32
32
  end
33
- expect /^.+[#]$/
33
+ expect /^.+\#$/
34
34
  end
35
35
  end
36
36
  pre_logout 'exit'
data/lib/oxidized/node.rb CHANGED
@@ -7,7 +7,7 @@ module Oxidized
7
7
  class Node
8
8
  include SemanticLogger::Loggable
9
9
 
10
- attr_reader :name, :ip, :model, :input, :output, :group, :auth, :prompt, :vars, :last, :repo
10
+ attr_reader :name, :ip, :model, :input, :output, :group, :auth, :prompt, :timeout, :vars, :last, :repo
11
11
  attr_accessor :running, :user, :email, :msg, :from, :stats, :retry, :err_type, :err_reason
12
12
  alias running? running
13
13
 
@@ -27,6 +27,7 @@ module Oxidized
27
27
  @output = resolve_output opt
28
28
  @auth = resolve_auth opt
29
29
  @prompt = resolve_prompt opt
30
+ @timeout = resolve_timeout opt
30
31
  @vars = opt[:vars] || {}
31
32
  @stats = Stats.new
32
33
  @retry = 0
@@ -156,6 +157,10 @@ module Oxidized
156
157
  opt[:prompt] || @model.prompt || Oxidized.config.prompt
157
158
  end
158
159
 
160
+ def resolve_timeout(opt)
161
+ resolve_key :timeout, opt, Oxidized.config.timeout
162
+ end
163
+
159
164
  def resolve_auth(opt)
160
165
  # Resolve configured username/password
161
166
  {
@@ -50,14 +50,22 @@ module Oxidized
50
50
  end
51
51
 
52
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
53
57
  loop do
54
- node_data += string_navigate_object(data, @cfg.hosts_location) if @cfg.hosts_location?
58
+ # Check for the next URL *before* trying to fetch/add
55
59
  break if data[next_key].nil?
56
60
 
57
- new_uri = URI.parse(data[next_key]) if data.has_key?(next_key)
61
+ # Fetch the next page
62
+ new_uri = URI.parse(data[next_key]) # Safe to parse now
58
63
  data = JSON.parse(read_http(new_uri, node_want))
64
+
65
+ # Add nodes from the *newly fetched* batch
59
66
  node_data += string_navigate_object(data, @cfg.hosts_location) if @cfg.hosts_location?
60
67
  end
68
+
61
69
  node_data
62
70
  end
63
71
 
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Oxidized
4
- VERSION = '0.34.3'
5
- VERSION_FULL = '0.34.3'
4
+ VERSION = '0.35.0'
5
+ VERSION_FULL = '0.35.0'
6
6
  def self.version_set
7
7
  version_full = %x(git describe --tags).chop rescue ""
8
8
  version = %x(git describe --tags --abbrev=0).chop rescue ""
data/oxidized.gemspec CHANGED
@@ -46,20 +46,19 @@ Gem::Specification.new do |s|
46
46
  s.add_dependency 'syslog', '~> 0.3.0'
47
47
  s.add_dependency 'syslog_protocol', '~> 0.9.2'
48
48
 
49
- s.add_development_dependency 'bundler', '~> 2.2'
50
49
  # ruby-git 4.0 requests ruby >= 3.2, we stick to ruby >= 3.1 (Ubuntu Noble/Debian Bookworm)
51
50
  s.add_development_dependency 'git', '>= 2.0', '< 3.2.0'
52
- s.add_development_dependency 'minitest', '~> 5.25.4'
51
+ s.add_development_dependency 'minitest', '~> 5.26.0'
53
52
  s.add_development_dependency 'mocha', '~> 2.1'
54
53
  s.add_development_dependency 'pry', '~> 0.15.0'
55
54
  s.add_development_dependency 'rake', '~> 13.0'
56
- s.add_development_dependency 'rubocop', '~> 1.79.1'
55
+ s.add_development_dependency 'rubocop', '~> 1.81.0'
57
56
  s.add_development_dependency 'rubocop-minitest', '~> 0.38.0'
58
57
  s.add_development_dependency 'rubocop-rake', '~> 0.7.0'
59
58
  s.add_development_dependency 'rubocop-sequel', '~> 0.4.0'
60
59
  s.add_development_dependency 'simplecov', '~> 0.22.0'
61
60
 
62
61
  # Dependencies on optional libraries, used for unit tests & development
63
- s.add_development_dependency 'oxidized-web', '~> 0.17.1'
64
- s.add_development_dependency 'sequel', '>= 5.63.0', '< 5.96.0'
62
+ s.add_development_dependency 'oxidized-web', '>= 0.17.1'
63
+ s.add_development_dependency 'sequel', '>= 5.63.0', '< 5.100.0'
65
64
  end