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.
- checksums.yaml +4 -4
- data/.coderabbit.yaml +21 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +45 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +22 -0
- data/.github/ISSUE_TEMPLATE/support-request.md +39 -0
- data/.github/workflows/codeql.yml +4 -4
- data/.github/workflows/publishdocker.yml +108 -13
- data/.github/workflows/ruby.yml +3 -12
- data/.github/workflows/stale.yml +6 -1
- data/.gitignore +2 -0
- data/.rubocop.yml +69 -13
- data/.rubocop_todo.yml +13 -143
- data/CHANGELOG.md +431 -4
- data/CONTRIBUTING.md +189 -0
- data/Dockerfile +89 -34
- data/README.md +59 -188
- data/Rakefile +58 -12
- data/bin/oxidized +2 -5
- data/docs/Configuration.md +382 -149
- data/docs/Creating-Models.md +262 -10
- data/docs/DeviceSimulation.md +196 -0
- data/docs/Docker.md +246 -0
- data/docs/Hooks.md +146 -51
- data/docs/Inputs.md +237 -0
- data/docs/Issues.md +123 -0
- data/docs/Model-Notes/APC.md +72 -0
- data/docs/Model-Notes/Cumulus.md +5 -0
- data/docs/Model-Notes/EatonNetwork.md +18 -0
- data/docs/Model-Notes/ExaLink.md +43 -0
- data/docs/Model-Notes/FSOS.md +17 -0
- data/docs/Model-Notes/Fortinet.md +75 -0
- data/docs/Model-Notes/GrandstreamHT8xx.md +8 -0
- data/docs/Model-Notes/HPEAruba.md +32 -0
- data/docs/Model-Notes/IvantiConnectSecure.md +59 -0
- data/docs/Model-Notes/OS6.md +10 -0
- data/docs/Model-Notes/README.md +1 -20
- data/docs/Model-Notes/RouterOS.md +28 -0
- data/docs/Model-Notes/SikluMHTG.md +7 -0
- data/docs/Model-Notes/TrueNAS.md +23 -0
- data/docs/Model-Notes/XGS4600-Zyxel.md +5 -0
- data/docs/ModelUnitTests.md +219 -0
- data/docs/Outputs.md +102 -5
- data/docs/Release.md +102 -0
- data/docs/Ruby-API.md +135 -2
- data/docs/Sources.md +34 -0
- data/docs/Supported-OS-Types.md +207 -270
- data/docs/Troubleshooting.md +43 -1
- data/extra/device2yaml.rb +259 -0
- data/extra/gitdiff-msteams.sh +32 -5
- data/extra/hooks/modelrules.rb +55 -0
- data/extra/hooks/modelrulesadvanced.rb +167 -0
- data/extra/hooks/srcipmap.rb +54 -0
- data/extra/nagios_check_failing_nodes.rb +1 -1
- data/extra/oxidized.runit +1 -1
- data/extra/rest_client.rb +5 -4
- data/extra/syslog.rb +11 -6
- data/lib/oxidized/cli/support.rb +152 -0
- data/lib/oxidized/cli.rb +21 -6
- data/lib/oxidized/config/vars.rb +33 -14
- data/lib/oxidized/config.rb +28 -17
- data/lib/oxidized/core.rb +50 -8
- data/lib/oxidized/hook/ciscosparkdiff.rb +11 -9
- data/lib/oxidized/hook/exec.rb +5 -4
- data/lib/oxidized/hook/githubrepo.rb +55 -18
- data/lib/oxidized/hook/noophook.rb +2 -2
- data/lib/oxidized/hook/slackdiff.rb +50 -14
- data/lib/oxidized/hook/xmppdiff.rb +9 -9
- data/lib/oxidized/hook.rb +68 -16
- data/lib/oxidized/input/cli.rb +8 -3
- data/lib/oxidized/input/debugtext.rb +40 -0
- data/lib/oxidized/input/debugyaml.rb +82 -0
- data/lib/oxidized/input/exec.rb +2 -11
- data/lib/oxidized/input/ftp.rb +2 -19
- data/lib/oxidized/input/http.rb +67 -17
- data/lib/oxidized/input/input.rb +35 -14
- data/lib/oxidized/input/scp.rb +27 -0
- data/lib/oxidized/input/ssh.rb +59 -83
- data/lib/oxidized/input/sshbase.rb +102 -0
- data/lib/oxidized/input/telnet.rb +20 -19
- data/lib/oxidized/input/tftp.rb +8 -8
- data/lib/oxidized/job.rb +18 -7
- data/lib/oxidized/jobs.rb +11 -1
- data/lib/oxidized/logger.rb +51 -0
- data/lib/oxidized/manager.rb +18 -17
- data/lib/oxidized/model/acos.rb +2 -1
- data/lib/oxidized/model/addpack.rb +26 -0
- data/lib/oxidized/model/adtran.rb +5 -1
- data/lib/oxidized/model/adva.rb +3 -3
- data/lib/oxidized/model/airfiber.rb +1 -1
- data/lib/oxidized/model/aos7.rb +11 -0
- data/lib/oxidized/model/aoscx.rb +37 -29
- data/lib/oxidized/model/aosw.rb +39 -29
- data/lib/oxidized/model/apc_aos.rb +7 -2
- data/lib/oxidized/model/apcaos.rb +39 -0
- data/lib/oxidized/model/aricentiss.rb +9 -2
- data/lib/oxidized/model/arubainstant.rb +93 -0
- data/lib/oxidized/model/asa.rb +11 -10
- data/lib/oxidized/model/asternos.rb +22 -0
- data/lib/oxidized/model/asyncos.rb +3 -3
- data/lib/oxidized/model/audiocodes.rb +2 -2
- data/lib/oxidized/model/awplus.rb +15 -12
- data/lib/oxidized/model/axos.rb +1 -1
- data/lib/oxidized/model/bdcom.rb +1 -0
- data/lib/oxidized/model/c4cmts.rb +1 -2
- data/lib/oxidized/model/ciscosma.rb +1 -1
- data/lib/oxidized/model/ciscosmb.rb +6 -1
- data/lib/oxidized/model/cnos.rb +13 -10
- data/lib/oxidized/model/comtrol.rb +1 -1
- data/lib/oxidized/model/comware.rb +5 -3
- data/lib/oxidized/model/cumulus.rb +14 -2
- data/lib/oxidized/model/defacto.rb +26 -0
- data/lib/oxidized/model/dellx.rb +1 -1
- data/lib/oxidized/model/dlink.rb +5 -2
- data/lib/oxidized/model/dlinknextgen.rb +55 -0
- data/lib/oxidized/model/dnos.rb +3 -0
- data/lib/oxidized/model/dslcommands.rb +93 -0
- data/lib/oxidized/model/dslsetup.rb +102 -0
- data/lib/oxidized/model/eatonnetwork.rb +70 -0
- data/lib/oxidized/model/edgecos.rb +3 -1
- data/lib/oxidized/model/edgeos.rb +7 -6
- data/lib/oxidized/model/edgeswitch.rb +3 -1
- data/lib/oxidized/model/efos.rb +41 -0
- data/lib/oxidized/model/eltex.rb +4 -2
- data/lib/oxidized/model/enterasys800.rb +2 -2
- data/lib/oxidized/model/enterprise_sonic.rb +46 -0
- data/lib/oxidized/model/eos.rb +3 -1
- data/lib/oxidized/model/exalink.rb +36 -0
- data/lib/oxidized/model/f5os.rb +17 -0
- data/lib/oxidized/model/fabricos.rb +2 -2
- data/lib/oxidized/model/fastiron.rb +5 -3
- data/lib/oxidized/model/firebrick.rb +2 -2
- data/lib/oxidized/model/firelinuxos.rb +13 -6
- data/lib/oxidized/model/firewareos.rb +10 -1
- data/lib/oxidized/model/fortigate.rb +160 -0
- data/lib/oxidized/model/fortios.rb +38 -48
- data/lib/oxidized/model/fsos.rb +46 -0
- data/lib/oxidized/model/gaiaos.rb +4 -4
- data/lib/oxidized/model/garderos.rb +38 -0
- data/lib/oxidized/model/grandstreamht8xx.rb +19 -0
- data/lib/oxidized/model/h3c.rb +1 -1
- data/lib/oxidized/model/ibos.rb +1 -0
- data/lib/oxidized/model/ingate.rb +47 -0
- data/lib/oxidized/model/ios.rb +57 -30
- data/lib/oxidized/model/iosxr.rb +2 -2
- data/lib/oxidized/model/ironware.rb +7 -5
- data/lib/oxidized/model/ivanti.rb +54 -0
- data/lib/oxidized/model/junos.rb +12 -5
- data/lib/oxidized/model/kornfeldos.rb +33 -0
- data/lib/oxidized/model/lenovonos.rb +2 -0
- data/lib/oxidized/model/linksyssrw.rb +3 -3
- data/lib/oxidized/model/linuxgeneric.rb +4 -2
- data/lib/oxidized/model/macros.rb +60 -0
- data/lib/oxidized/model/mimosab11.rb +34 -0
- data/lib/oxidized/model/ml66.rb +33 -0
- data/lib/oxidized/model/mlnxos.rb +18 -14
- data/lib/oxidized/model/model.rb +98 -103
- data/lib/oxidized/model/mtrlrfs.rb +1 -0
- data/lib/oxidized/model/ndms.rb +6 -0
- data/lib/oxidized/model/netgear.rb +14 -3
- data/lib/oxidized/model/netonix.rb +1 -1
- data/lib/oxidized/model/netscaler.rb +1 -1
- data/lib/oxidized/model/nodegrid.rb +1 -1
- data/lib/oxidized/model/nsxdfw.rb +31 -0
- data/lib/oxidized/model/nsxfirewall.rb +2 -1
- data/lib/oxidized/model/nxos.rb +20 -15
- data/lib/oxidized/model/ocnos.rb +42 -0
- data/lib/oxidized/model/onefinity.rb +18 -0
- data/lib/oxidized/model/openbsd.rb +1 -1
- data/lib/oxidized/model/opengear.rb +36 -1
- data/lib/oxidized/model/openwrt.rb +6 -6
- data/lib/oxidized/model/opnsense.rb +9 -1
- data/lib/oxidized/model/os6.rb +48 -0
- data/lib/oxidized/model/outputs.rb +5 -0
- data/lib/oxidized/model/panos.rb +2 -0
- data/lib/oxidized/model/perle.rb +41 -0
- data/lib/oxidized/model/pfsense.rb +9 -0
- data/lib/oxidized/model/powerconnect.rb +32 -11
- data/lib/oxidized/model/procurve.rb +6 -2
- data/lib/oxidized/model/qtech.rb +3 -1
- data/lib/oxidized/model/quantaos.rb +8 -6
- data/lib/oxidized/model/rgos.rb +33 -0
- data/lib/oxidized/model/riverbed.rb +104 -0
- data/lib/oxidized/model/routeros.rb +18 -11
- data/lib/oxidized/model/saos.rb +18 -1
- data/lib/oxidized/model/saos10.rb +38 -0
- data/lib/oxidized/model/siklu.rb +1 -1
- data/lib/oxidized/model/siklumhtg.rb +22 -0
- data/lib/oxidized/model/sixwind.rb +28 -0
- data/lib/oxidized/model/slxos.rb +3 -3
- data/lib/oxidized/model/smartbyte.rb +48 -0
- data/lib/oxidized/model/sonicos.rb +27 -20
- data/lib/oxidized/model/speedtouch.rb +1 -1
- data/lib/oxidized/model/sros.rb +3 -3
- data/lib/oxidized/model/srosmd.rb +1 -1
- data/lib/oxidized/model/supermicro.rb +1 -1
- data/lib/oxidized/model/telco.rb +1 -1
- data/lib/oxidized/model/tmos.rb +1 -0
- data/lib/oxidized/model/tnsr.rb +57 -0
- data/lib/oxidized/model/tplink.rb +5 -5
- data/lib/oxidized/model/trango.rb +4 -2
- data/lib/oxidized/model/truenas.rb +64 -3
- data/lib/oxidized/model/ucs.rb +1 -1
- data/lib/oxidized/model/unifiap.rb +144 -0
- data/lib/oxidized/model/uplinkolt.rb +46 -0
- data/lib/oxidized/model/voltaire.rb +1 -1
- data/lib/oxidized/model/voss.rb +3 -0
- data/lib/oxidized/model/vrp.rb +5 -1
- data/lib/oxidized/model/vyatta.rb +9 -3
- data/lib/oxidized/model/vyos.rb +37 -0
- data/lib/oxidized/model/xos.rb +10 -1
- data/lib/oxidized/model/zhoneolt.rb +4 -2
- data/lib/oxidized/model/zynos.rb +67 -3
- data/lib/oxidized/model/zynosadsl.rb +14 -0
- data/lib/oxidized/model/zynoscli.rb +1 -1
- data/lib/oxidized/model/zynosgs.rb +2 -0
- data/lib/oxidized/model/zynosmgs.rb +32 -0
- data/lib/oxidized/node.rb +99 -64
- data/lib/oxidized/nodes.rb +40 -19
- data/lib/oxidized/output/file.rb +80 -43
- data/lib/oxidized/output/git.rb +295 -151
- data/lib/oxidized/output/gitcrypt.rb +193 -185
- data/lib/oxidized/output/http.rb +63 -51
- data/lib/oxidized/output/output.rb +20 -4
- data/lib/oxidized/signals.rb +44 -0
- data/lib/oxidized/source/csv.rb +49 -49
- data/lib/oxidized/source/http.rb +76 -63
- data/lib/oxidized/source/jsonfile.rb +68 -0
- data/lib/oxidized/source/source.rb +73 -18
- data/lib/oxidized/source/sql.rb +71 -59
- data/lib/oxidized/version.rb +6 -4
- data/lib/oxidized/worker.rb +57 -29
- data/lib/oxidized.rb +4 -24
- data/lib/refinements.rb +21 -1
- data/oxidized.gemspec +45 -21
- metadata +248 -41
- data/lib/oxidized/model/timos.rb +0 -10
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'net/ssh'
|
|
5
|
+
require 'optparse'
|
|
6
|
+
require 'etc'
|
|
7
|
+
require 'timeout'
|
|
8
|
+
|
|
9
|
+
# This script logs in a network device and outputs a YAML file that can be
|
|
10
|
+
# used for model unit tests in spec/model/
|
|
11
|
+
# For more information, see docs/DeviceSimulation.md
|
|
12
|
+
|
|
13
|
+
################# Methods
|
|
14
|
+
# Runs cmd in the ssh session, either im exec mode or with a tty
|
|
15
|
+
# saves the output to @output
|
|
16
|
+
def ssh_exec(cmd)
|
|
17
|
+
puts "\n### Sending #{cmd.dump}..."
|
|
18
|
+
@output&.puts " #{@sequence_prepend_command}#{cmd.dump}: |-"
|
|
19
|
+
|
|
20
|
+
if @exec_mode
|
|
21
|
+
@ssh_output = @ssh.exec! cmd
|
|
22
|
+
else
|
|
23
|
+
@ses.send_data cmd
|
|
24
|
+
shell_wait
|
|
25
|
+
end
|
|
26
|
+
yaml_output(' ')
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Wait for the ssh command to be executed, with an idle timout @idle_timeout
|
|
30
|
+
# Pressing CTRL-C exits the script
|
|
31
|
+
# Pressing ESC termiates the idle timeout
|
|
32
|
+
def shell_wait
|
|
33
|
+
@ssh_output = ''
|
|
34
|
+
# ssh_output gets appended by chanel.on-data (below)
|
|
35
|
+
# We store the current length of @ssh_output in @ssh_output_length
|
|
36
|
+
# if @ssh_output.length is bigger than @ssh_output_length, we got new data
|
|
37
|
+
@ssh_output_length = 0
|
|
38
|
+
|
|
39
|
+
# Keep track of time for idle timeout
|
|
40
|
+
start_time = Time.now
|
|
41
|
+
|
|
42
|
+
# Loop & wait for @idle_timeout seconds after last output
|
|
43
|
+
# 0.1 means that the loop should run at least once per 0.1 second
|
|
44
|
+
@ssh.loop(0.1) do
|
|
45
|
+
# if @ssh_output is longer than our saved length, we got new output
|
|
46
|
+
if @ssh_output_length < @ssh_output.length
|
|
47
|
+
# reset the timer and save the new output length
|
|
48
|
+
start_time = Time.now
|
|
49
|
+
@ssh_output_length = @ssh_output.length
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# We wait for 0.1 seconds if a key was pressed
|
|
53
|
+
begin
|
|
54
|
+
Timeout.timeout(0.1) do
|
|
55
|
+
# Get input // this is a blocking call
|
|
56
|
+
char = $stdin.getch
|
|
57
|
+
# If ctrl-c is pressed, exit the script
|
|
58
|
+
if char == "\u0003"
|
|
59
|
+
puts '### CTRL-C pressed, exiting'
|
|
60
|
+
cleanup
|
|
61
|
+
exit
|
|
62
|
+
end
|
|
63
|
+
# If escape is pressed, terminate idle timeout
|
|
64
|
+
if char == "\e"
|
|
65
|
+
puts "\n### ESC pressed, skipping idle timeout"
|
|
66
|
+
return false
|
|
67
|
+
else
|
|
68
|
+
# if not, record the char and send the char through ssh
|
|
69
|
+
puts "\n### #{char.dump} pressed"
|
|
70
|
+
yaml_output(' ')
|
|
71
|
+
@output&.puts " #{@sequence_prepend_command}#{char.dump}: |-"
|
|
72
|
+
@ssh_output = ''
|
|
73
|
+
start_time = Time.now
|
|
74
|
+
@ssh_output_length = @ssh_output.length
|
|
75
|
+
|
|
76
|
+
@ses.send_data char
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
rescue Timeout::Error
|
|
80
|
+
# No key pressed
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# exit the loop when the @idle_timeout has been reached (false = exit)
|
|
84
|
+
Time.now - start_time < @idle_timeout
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def yaml_output(prepend = '')
|
|
89
|
+
# Now print the collected output to @output
|
|
90
|
+
firstline = true
|
|
91
|
+
|
|
92
|
+
prepend = @sequence_prepend_output + prepend
|
|
93
|
+
|
|
94
|
+
# each_line(chomp: true) would remove \r\n, so we prefer split
|
|
95
|
+
@ssh_output.split("\n", -1).each do |line|
|
|
96
|
+
# encode line and remove the first and the trailing double quote
|
|
97
|
+
line = line.dump[1..-2]
|
|
98
|
+
if firstline
|
|
99
|
+
# Make sure the leading space of the first line (if present)
|
|
100
|
+
# is coded with \0x20 or YAML block scalars won't work
|
|
101
|
+
line.sub!(/^\A /, '\x20')
|
|
102
|
+
firstline = false
|
|
103
|
+
end
|
|
104
|
+
# Make sure trailing white spaces are coded with \0x20
|
|
105
|
+
line.gsub!(/ $/, '\x20')
|
|
106
|
+
# prepend white spaces for the yaml block scalar
|
|
107
|
+
line = prepend + line
|
|
108
|
+
@output&.puts line
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def cleanup
|
|
113
|
+
(@ssh.close rescue true) unless @ssh.closed? # rubocop:disable Style/RedundantParentheses
|
|
114
|
+
@output&.close
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
################# Main loop
|
|
118
|
+
|
|
119
|
+
# Define options
|
|
120
|
+
options = {}
|
|
121
|
+
@sequence_prepend_command = '- '
|
|
122
|
+
@sequence_prepend_output = ' '
|
|
123
|
+
|
|
124
|
+
optparse = OptionParser.new do |opts|
|
|
125
|
+
opts.banner = <<~HEREDOC
|
|
126
|
+
Usages:
|
|
127
|
+
- device2yaml.rb [user@]host -i file [options]
|
|
128
|
+
- device2yaml.rb [user@]host -c "command1
|
|
129
|
+
command2
|
|
130
|
+
command3" [options]
|
|
131
|
+
|
|
132
|
+
-i and -c are mutualy exclusive, one must be specified
|
|
133
|
+
|
|
134
|
+
[options]:
|
|
135
|
+
HEREDOC
|
|
136
|
+
|
|
137
|
+
opts.on('-c', '--commands "command list"', 'specify the commands to be run') do |cmds|
|
|
138
|
+
options[:commands] = cmds
|
|
139
|
+
end
|
|
140
|
+
opts.on('-i', '--input file', 'Specify an input file for commands to be run') do |file|
|
|
141
|
+
options[:input] = file
|
|
142
|
+
end
|
|
143
|
+
opts.on('-o', '--output file', 'Specify an output YAML-file') do |file|
|
|
144
|
+
options[:output] = file
|
|
145
|
+
end
|
|
146
|
+
opts.on('-t', '--timeout value', Integer,
|
|
147
|
+
'Specify the idle timeout beween commands (default: 5 seconds)') do |timeout|
|
|
148
|
+
options[:timeout] = timeout
|
|
149
|
+
end
|
|
150
|
+
opts.on('-e', '--exec-mode', 'Run ssh in exec mode (without tty)') { @exec_mode = true }
|
|
151
|
+
opts.on('-u', '--unordered', 'The YAML simulation should not enforce an order of the commands') do
|
|
152
|
+
@sequence_prepend_command = ''
|
|
153
|
+
@sequence_prepend_output = ''
|
|
154
|
+
end
|
|
155
|
+
opts.on '-h', '--help', 'Print this help' do
|
|
156
|
+
puts opts
|
|
157
|
+
exit
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Catch and parse the first argument
|
|
162
|
+
if ARGV[0] && ARGV[0][0] != '-'
|
|
163
|
+
argument = ARGV.shift
|
|
164
|
+
if argument.include?('@')
|
|
165
|
+
ssh_user, ssh_host = argument.split('@')
|
|
166
|
+
else
|
|
167
|
+
ssh_user = Etc.getlogin
|
|
168
|
+
ssh_host = argument
|
|
169
|
+
end
|
|
170
|
+
else
|
|
171
|
+
puts 'Missing a host to connect to...'
|
|
172
|
+
puts
|
|
173
|
+
puts optparse
|
|
174
|
+
exit 1
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Parse the options
|
|
178
|
+
optparse.parse!
|
|
179
|
+
|
|
180
|
+
# Get the commands to be run against ssh_host
|
|
181
|
+
# ^ = xor = exclusive or
|
|
182
|
+
unless options[:commands].nil? ^ options[:input].nil?
|
|
183
|
+
puts "Please provide commands to be run against #{ssh_host} with either option -c or -i"
|
|
184
|
+
puts
|
|
185
|
+
puts optparse
|
|
186
|
+
exit 1
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
if options[:commands]
|
|
190
|
+
ssh_commands = []
|
|
191
|
+
options[:commands].each_line(chomp: true) { |command| ssh_commands << command }
|
|
192
|
+
elsif options[:input]
|
|
193
|
+
ssh_commands = File.read(options[:input]).split(/\n+|\r+/)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
puts "Running #{ssh_commands} on #{ssh_user}@#{ssh_host}"
|
|
197
|
+
# Add \n to each command
|
|
198
|
+
ssh_commands.map! { |s| s + "\n" }
|
|
199
|
+
|
|
200
|
+
# Defaut idle timeout: 5 seconds, as tests showed that 2 seconds is too short
|
|
201
|
+
@idle_timeout = options[:timeout] || 5
|
|
202
|
+
|
|
203
|
+
# We will use safe navifation (&.) to call the methods on @output only
|
|
204
|
+
# if @output is not nil
|
|
205
|
+
@output = options[:output] ? File.open(options[:output], 'w') : nil
|
|
206
|
+
|
|
207
|
+
@ssh = Net::SSH.start(ssh_host,
|
|
208
|
+
ssh_user,
|
|
209
|
+
{ timeout: 10,
|
|
210
|
+
append_all_supported_algorithms: true })
|
|
211
|
+
|
|
212
|
+
@ssh_output = ''
|
|
213
|
+
|
|
214
|
+
unless @exec_mode
|
|
215
|
+
@ses = @ssh.open_channel do |ch|
|
|
216
|
+
ch.on_data do |_ch, data|
|
|
217
|
+
@ssh_output += data
|
|
218
|
+
# Output the data to stdout for interactive control
|
|
219
|
+
# remove ANSI escape codes, as they can produce problems
|
|
220
|
+
# The code will be printed as '\e[123m' in the output
|
|
221
|
+
print data.gsub("\e", '\e')
|
|
222
|
+
end
|
|
223
|
+
ch.request_pty(term: 'vt100') do |_ch, success_pty|
|
|
224
|
+
raise "Can't get PTY" unless success_pty
|
|
225
|
+
|
|
226
|
+
ch.send_channel_request 'shell' do |_ch, success_shell|
|
|
227
|
+
raise "Can't get shell" unless success_shell
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
ch.on_extended_data do |_ch, _type, data|
|
|
231
|
+
$stderr.print "Error: #{data}\n"
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# YAML begin of file
|
|
237
|
+
@output&.puts '---'
|
|
238
|
+
|
|
239
|
+
if @exec_mode
|
|
240
|
+
# init prompt does not exist and is empty in exec mode
|
|
241
|
+
@output&.puts 'init_prompt:'
|
|
242
|
+
else
|
|
243
|
+
# get motd and first prompt
|
|
244
|
+
@output&.puts 'init_prompt: |-'
|
|
245
|
+
shell_wait
|
|
246
|
+
yaml_output ' '
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
@output&.puts 'commands:'
|
|
250
|
+
|
|
251
|
+
begin
|
|
252
|
+
ssh_commands.each do |cmd|
|
|
253
|
+
ssh_exec cmd
|
|
254
|
+
end
|
|
255
|
+
rescue Errno::ECONNRESET, Net::SSH::Disconnect, IOError => e
|
|
256
|
+
puts "### Connection closed with message: #{e.message}"
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
cleanup
|
data/extra/gitdiff-msteams.sh
CHANGED
|
@@ -15,12 +15,31 @@
|
|
|
15
15
|
# timeout: 120
|
|
16
16
|
#
|
|
17
17
|
# Add webhook to your MS Teams channel and set the next variable to the full url
|
|
18
|
+
# If INCLUDE_GITHUB_LINK is set to true, there will be a button that links to GITURL in the bottom of each teams post. the commit id is added at the end of the url.
|
|
19
|
+
# MAXSIZE is set to respect the 28 KB limit for teams webhooks, but you might need to change it if you modify the Adaptive Card
|
|
18
20
|
|
|
19
21
|
weburl="https://contoso.webhook.office.com/webhookb2/etc etc etc"
|
|
22
|
+
GITURL="https://github.example.com/My-org/oxidized/commit/"
|
|
23
|
+
INCLUDE_GITHUB_LINK=false
|
|
24
|
+
# Max size for summary text.
|
|
25
|
+
MAXSIZE=24500
|
|
26
|
+
|
|
27
|
+
if [ "$INCLUDE_GITHUB_LINK" = true ]; then
|
|
28
|
+
github_action=",
|
|
29
|
+
{ \"type\": \"ActionSet\",
|
|
30
|
+
\"actions\":
|
|
31
|
+
[
|
|
32
|
+
{
|
|
33
|
+
\"type\": \"Action.OpenUrl\",
|
|
34
|
+
\"title\": \"Click to see diff in github\",
|
|
35
|
+
\"url\": \"${GITURL}${OX_REPO_COMMITREF}\"
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
}"
|
|
39
|
+
fi
|
|
20
40
|
|
|
21
41
|
postdata()
|
|
22
42
|
{
|
|
23
|
-
COMMIT=$(git --bare --git-dir="${OX_REPO_NAME}" show --pretty='' --no-color "${OX_REPO_COMMITREF}" | jq --raw-input --slurp --compact-output)
|
|
24
43
|
cat <<EOF
|
|
25
44
|
{
|
|
26
45
|
"type":"message",
|
|
@@ -77,7 +96,7 @@ postdata()
|
|
|
77
96
|
"size": "small"
|
|
78
97
|
}
|
|
79
98
|
]
|
|
80
|
-
}
|
|
99
|
+
}${github_action}
|
|
81
100
|
]
|
|
82
101
|
}
|
|
83
102
|
}
|
|
@@ -86,6 +105,14 @@ postdata()
|
|
|
86
105
|
EOF
|
|
87
106
|
}
|
|
88
107
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
108
|
+
COMMIT=$(git --bare --git-dir="${OX_REPO_NAME}" show --pretty='' --no-color "${OX_REPO_COMMITREF}" | jq --raw-input --slurp --compact-output)
|
|
109
|
+
URL=""
|
|
110
|
+
|
|
111
|
+
size=$(postdata | wc -c)
|
|
112
|
+
if [ "$size" -gt "$MAXSIZE" ]; then
|
|
113
|
+
COMMIT=$(git --bare --git-dir="${OX_REPO_NAME}" show --pretty='' --no-color "${OX_REPO_COMMITREF}" | head -c $MAXSIZE)
|
|
114
|
+
COMMIT+="$NEWLINE...$NEWLINE Shortened because of length"
|
|
115
|
+
COMMIT=$(echo "${COMMIT}" | jq --raw-input --slurp --compact-output )
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
curl -i -H "Content-Type: application/json" -X POST --data "$(postdata)" "${weburl}"
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
### script in ~/config/oxidized/hook/modelrules.rb ## or OXDIZED_HOME equivalent
|
|
2
|
+
###
|
|
3
|
+
### router.db:
|
|
4
|
+
### router1:1.1.1.1:routeros::mikrotik
|
|
5
|
+
### router2:2.2.2.2:ios:switch:cisco
|
|
6
|
+
### router3:3.3.3.3:routeros:switch:mikrotik
|
|
7
|
+
###
|
|
8
|
+
### config:
|
|
9
|
+
### source:
|
|
10
|
+
### default: csv
|
|
11
|
+
### csv:
|
|
12
|
+
### file: "/Users/ytti/.config/oxidized/router.db"
|
|
13
|
+
### delimiter: !ruby/regexp /:/
|
|
14
|
+
### map:
|
|
15
|
+
### name: 0
|
|
16
|
+
### ip: 1
|
|
17
|
+
### model: 2
|
|
18
|
+
### group: 3
|
|
19
|
+
### hooks:
|
|
20
|
+
### somename:
|
|
21
|
+
### type: modelrules
|
|
22
|
+
### events: ["source_node_transform"]
|
|
23
|
+
### rules:
|
|
24
|
+
### - vendor: mikrotik
|
|
25
|
+
### group: switch
|
|
26
|
+
### model: eltex
|
|
27
|
+
###
|
|
28
|
+
### Nodes BEFORE script:
|
|
29
|
+
### {name: "router1", ip: "1.1.1.1", model: "routeros", group: ""}
|
|
30
|
+
### {name: "router2", ip: "2.2.2.2", model: "ios", group: "switch"}
|
|
31
|
+
### {name: "router3", ip: "3.3.3.3", model: "routeros", group: "switch"}
|
|
32
|
+
###
|
|
33
|
+
### Nodes AFTER script:
|
|
34
|
+
### {name: "router1", ip: "1.1.1.1", model: "routeros", group: ""}
|
|
35
|
+
### {name: "router2", ip: "2.2.2.2", model: "ios", group: "switch"}
|
|
36
|
+
### {name: "router3", ip: "3.3.3.3", model: "eltex", group: "switch"}
|
|
37
|
+
class ModelRules < Oxidized::Hook
|
|
38
|
+
def validate_cfg!
|
|
39
|
+
raise KeyError, 'hook.rules is required' unless cfg.has_key?('rules')
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def run_hook(ctx)
|
|
43
|
+
# node is the node[key] that we'd return without manipulation
|
|
44
|
+
node = ctx.node ## e.g. node[:ip], node[:model] - what ever config maps
|
|
45
|
+
|
|
46
|
+
## node_raw is source specific, in CSV it is just the field number, in HTTP it is JSON
|
|
47
|
+
vendor = ctx.node_raw[4]
|
|
48
|
+
|
|
49
|
+
cfg.rules.each do |rule|
|
|
50
|
+
node[:model] = rule['model'] if node[:group] == rule['group'] && vendor == rule['vendor']
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
node
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Advanced dynamic model selection hook for Oxidized
|
|
2
|
+
# --------------------------------------------------
|
|
3
|
+
# This hook allows you to assign Oxidized models based on any device attributes
|
|
4
|
+
# (name, vendor, group, type, IP, etc.) using flexible rules defined in the config.
|
|
5
|
+
# It uses the `source_node_transform` event and works with any source (CSV, HTTP, SQL).
|
|
6
|
+
#
|
|
7
|
+
# The hook is designed to be generic: you can specify any field that exists in the
|
|
8
|
+
# node data after source mapping (see `map` section in the source configuration).
|
|
9
|
+
# Rules are evaluated in order; the first matching rule assigns its `model`.
|
|
10
|
+
#
|
|
11
|
+
# ⚠️ IMPORTANT: For the hook to load correctly:
|
|
12
|
+
# - The filename must match the hook type (e.g., `modelrulesadvanced.rb`).
|
|
13
|
+
# - The class name must be the CamelCase version of the filename without underscores
|
|
14
|
+
# (e.g., filename `modelrulesadvanced.rb` → class `Modelrulesadvanced`).
|
|
15
|
+
# - In the Oxidized config, specify `type: modelrulesadvanced` (the filename without .rb).
|
|
16
|
+
#
|
|
17
|
+
# ⚠️ DOCKER NOTE: This hook was developed and tested in a Docker container.
|
|
18
|
+
# Ensure that your hooks directory inside the container is
|
|
19
|
+
# `/home/oxidized/.config/oxidized/hook` (singular "hook"), not "hooks".
|
|
20
|
+
# In docker-compose, mount your local hooks folder to this path, e.g.:
|
|
21
|
+
# volumes:
|
|
22
|
+
# - ./extra/hooks:/home/oxidized/.config/oxidized/hook
|
|
23
|
+
#
|
|
24
|
+
# Example scenario with NetBox:
|
|
25
|
+
# Suppose your NetBox instance returns devices with the following data (simplified):
|
|
26
|
+
# gw-001 | Mikrotik | RB750 | ro_ud | 192.168.0.167/24
|
|
27
|
+
# gw-002 | Mikrotik | RB750 | ro_ud | 192.168.0.177/24
|
|
28
|
+
# gw-003 | Mikrotik | RB750 | ro_bd | 192.168.0.178/24
|
|
29
|
+
# gw-004 | Mikrotik | RB750 | switch | 192.168.0.181/24
|
|
30
|
+
# gw-005 | Cisco | 2960 | switch | 192.168.0.179/24
|
|
31
|
+
# gw-006 | Mikrotik | RB750 | switch | 192.168.0.180/24
|
|
32
|
+
# gw-007 | Arista | 3456 | ro_ud | 192.168.0.190/24
|
|
33
|
+
#
|
|
34
|
+
# Desired model assignment:
|
|
35
|
+
# - gw-001 (exception by name) → asa
|
|
36
|
+
# - MikroTik in group `switch` with IP 192.168.0.180/24 → eltex
|
|
37
|
+
# - All other MikroTik → routeros
|
|
38
|
+
# - Cisco switches → ios
|
|
39
|
+
# - Arista devices → eos
|
|
40
|
+
#
|
|
41
|
+
# Example configuration (top-level in Oxidized config):
|
|
42
|
+
# ---
|
|
43
|
+
# username: oxidized_ssh_user
|
|
44
|
+
# password: oxidized_ssh_password
|
|
45
|
+
#
|
|
46
|
+
# hooks:
|
|
47
|
+
# modelrulesadvanced:
|
|
48
|
+
# type: modelrulesadvanced
|
|
49
|
+
# events: [source_node_transform]
|
|
50
|
+
# rules:
|
|
51
|
+
# - description: "Exception: gw-001 uses ASA model"
|
|
52
|
+
# name: gw-001
|
|
53
|
+
# model: asa
|
|
54
|
+
# - description: "Mikrotik switch with IP 192.168.0.180/24 uses eltex"
|
|
55
|
+
# vendor: Mikrotik
|
|
56
|
+
# group: switch
|
|
57
|
+
# ip: 192.168.0.180/24
|
|
58
|
+
# model: eltex
|
|
59
|
+
# - description: "All other Mikrotik devices"
|
|
60
|
+
# vendor: Mikrotik
|
|
61
|
+
# model: routeros
|
|
62
|
+
# - description: "Cisco switches"
|
|
63
|
+
# vendor: Cisco
|
|
64
|
+
# group: switch
|
|
65
|
+
# model: ios
|
|
66
|
+
# - description: "Arista devices"
|
|
67
|
+
# vendor: Arista
|
|
68
|
+
# model: eos
|
|
69
|
+
#
|
|
70
|
+
# resolve_dns: false
|
|
71
|
+
# interval: 3600
|
|
72
|
+
# rest: 0.0.0.0:8888
|
|
73
|
+
# debug: true # optional – enables detailed logging from the hook
|
|
74
|
+
#
|
|
75
|
+
# source:
|
|
76
|
+
# default: http
|
|
77
|
+
# http:
|
|
78
|
+
# url: http://netbox.test/api/dcim/devices/?status=active&has_primary_ip=true
|
|
79
|
+
# headers:
|
|
80
|
+
# Authorization: Token YOUR_API_TOKEN
|
|
81
|
+
# map:
|
|
82
|
+
# name: name
|
|
83
|
+
# vendor: device_type.manufacturer.name
|
|
84
|
+
# type: device_type.model
|
|
85
|
+
# group: role.slug
|
|
86
|
+
# ip: primary_ip.address
|
|
87
|
+
# secure: false
|
|
88
|
+
# hosts_location: results
|
|
89
|
+
# pagination: true
|
|
90
|
+
# pagination_key_name: next
|
|
91
|
+
#
|
|
92
|
+
# output:
|
|
93
|
+
# default: file
|
|
94
|
+
# file:
|
|
95
|
+
# directory: "/home/oxidized/.config/oxidized/configs"
|
|
96
|
+
#
|
|
97
|
+
# groups:
|
|
98
|
+
# ro_ud: {}
|
|
99
|
+
# ro_bd: {}
|
|
100
|
+
# switch: {}
|
|
101
|
+
#
|
|
102
|
+
# How it works:
|
|
103
|
+
# 1. Oxidized loads node data from the source (NetBox) and applies the `map`.
|
|
104
|
+
# 2. For each node, the `source_node_transform` event is triggered.
|
|
105
|
+
# 3. This hook receives a context object `ctx` containing:
|
|
106
|
+
# - `ctx.node` – the mapped node attributes (hash)
|
|
107
|
+
# - `ctx.node_raw` – the original source record (useful for unmapped fields)
|
|
108
|
+
# 4. The hook iterates through the rules defined in `cfg.rules`.
|
|
109
|
+
# 5. For each rule, it checks that every specified key (except `model` and `description`)
|
|
110
|
+
# matches the corresponding value in `ctx.node`. Comparison is case‑insensitive and
|
|
111
|
+
# strips surrounding spaces.
|
|
112
|
+
# 6. The first matching rule assigns its `model` to `ctx.node[:model]`.
|
|
113
|
+
# 7. If no rule matches, the node's `model` remains unchanged.
|
|
114
|
+
# 8. The modified (or original) node is returned; returning `nil` would exclude the node.
|
|
115
|
+
#
|
|
116
|
+
# Notes:
|
|
117
|
+
# - Rule order is crucial: place more specific rules (e.g., with IP or name) before generic ones.
|
|
118
|
+
# - The `description` field is optional and only appears in debug logs.
|
|
119
|
+
# - Any field present in `ctx.node` after mapping can be used as a match key.
|
|
120
|
+
# - For HTTP sources (NetBox), you can access additional fields via `ctx.node_raw["field"]`.
|
|
121
|
+
# - Debug logging requires `debug: true` in the global config.
|
|
122
|
+
|
|
123
|
+
class Modelrulesadvanced < Oxidized::Hook
|
|
124
|
+
# Validate that the hook configuration contains a 'rules' array.
|
|
125
|
+
def validate_cfg!
|
|
126
|
+
raise KeyError, "hook.rules is required" unless cfg.has_key?("rules")
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Main hook method – called for each node during source_node_transform event.
|
|
130
|
+
def run_hook(ctx)
|
|
131
|
+
node = ctx.node
|
|
132
|
+
rules = cfg.rules || []
|
|
133
|
+
|
|
134
|
+
matched_model = nil
|
|
135
|
+
rules.each_with_index do |rule, idx|
|
|
136
|
+
match = true
|
|
137
|
+
rule.each do |key, value|
|
|
138
|
+
next if %w[model description].include?(key)
|
|
139
|
+
|
|
140
|
+
# Retrieve the corresponding value from the node (symbol or string key)
|
|
141
|
+
node_value = node[key.to_sym] || node[key.to_s]
|
|
142
|
+
if node_value.to_s.strip.downcase != value.to_s.strip.downcase
|
|
143
|
+
match = false
|
|
144
|
+
break
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
next unless match
|
|
148
|
+
|
|
149
|
+
matched_model = rule["model"]
|
|
150
|
+
desc = rule["description"] ? " (#{rule['description']})" : ""
|
|
151
|
+
logger.debug "ModelRulesAdvanced: rule #{idx + 1}#{desc} matched -> #{matched_model}"
|
|
152
|
+
break
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
if matched_model
|
|
156
|
+
old_model = node[:model] || node["model"]
|
|
157
|
+
node[:model] = matched_model
|
|
158
|
+
logger.debug "ModelRulesAdvanced: changed model from #{old_model.inspect} to #{matched_model.inspect}"
|
|
159
|
+
else
|
|
160
|
+
# rubocop:disable Layout/LineLength
|
|
161
|
+
logger.debug "ModelRulesAdvanced: no rule matched, keeping existing model: #{node[:model] || node['model'].inspect}"
|
|
162
|
+
# rubocop:enable Layout/LineLength
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
node
|
|
166
|
+
end
|
|
167
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
### script in ~/config/oxidized/hook/srcipmap.rb ## or OXDIZED_HOME equivalent
|
|
2
|
+
###
|
|
3
|
+
### router.db:
|
|
4
|
+
### router1:1.1.1.1:cisco:c7200:10.10.10.1:somerole
|
|
5
|
+
### router2:2.2.2.2:juniper:mx80:10.10.10.2:wlc
|
|
6
|
+
### router3:3.3.3.3:juniper:mx2020:10.10.10.3:anotherrole
|
|
7
|
+
###
|
|
8
|
+
### config:
|
|
9
|
+
### source:
|
|
10
|
+
### default: csv
|
|
11
|
+
### csv:
|
|
12
|
+
### file: "/Users/ytti/.config/oxidized/router.db"
|
|
13
|
+
### delimiter: !ruby/regexp /:/
|
|
14
|
+
### map:
|
|
15
|
+
### name: 0
|
|
16
|
+
### ip: 1
|
|
17
|
+
### model: 2
|
|
18
|
+
### model_map:
|
|
19
|
+
### juniper: junos
|
|
20
|
+
### cisco: ios
|
|
21
|
+
### hooks:
|
|
22
|
+
### somename:
|
|
23
|
+
### type: srcipmap
|
|
24
|
+
### events: ["source_node_transform"]
|
|
25
|
+
###
|
|
26
|
+
###
|
|
27
|
+
###
|
|
28
|
+
### Nodes BEFORE script:
|
|
29
|
+
### {name: "router1", ip: "1.1.1.1", model: "ios"}
|
|
30
|
+
### {name: "router2", ip: "2.2.2.2", model: "junos"}
|
|
31
|
+
### {name: "router3", ip: "3.3.3.3", model: "junos"
|
|
32
|
+
###
|
|
33
|
+
### Nodes AFTER script:
|
|
34
|
+
### {name: "router1", ip: "1.1.1.1", model: "ios"}
|
|
35
|
+
### {name: "router2", ip: "10.10.10.2", model: "junos"}
|
|
36
|
+
### {name: "router3", ip: "3.3.3.3", model: "junos"}
|
|
37
|
+
|
|
38
|
+
class SrcIpMap < Oxidized::Hook
|
|
39
|
+
def run_hook(ctx)
|
|
40
|
+
# node is the node[key] that we'd return without manipulation
|
|
41
|
+
node = ctx.node
|
|
42
|
+
|
|
43
|
+
## node_raw is source specific, in CSV it is just the field number
|
|
44
|
+
_platform = ctx.node_raw[3]
|
|
45
|
+
oob_ip = ctx.node_raw[4]
|
|
46
|
+
role = ctx.node_raw[5]
|
|
47
|
+
|
|
48
|
+
### the magic
|
|
49
|
+
node[:ip] = oob_ip if role == 'wlc'
|
|
50
|
+
|
|
51
|
+
### remember to return the manipulated object, nil if you want to ignore loading node
|
|
52
|
+
node
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -10,7 +10,7 @@ pending = false
|
|
|
10
10
|
critical_nodes = []
|
|
11
11
|
pending_nodes = []
|
|
12
12
|
|
|
13
|
-
json = JSON.parse(
|
|
13
|
+
json = JSON.parse(URI("http://localhost:8888/nodes.json").open(&:read))
|
|
14
14
|
json.each do |node|
|
|
15
15
|
next if !ARGV.empty? && (ARGV[0] != node['name'])
|
|
16
16
|
|
data/extra/oxidized.runit
CHANGED
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
[ ! -d /home/oxidized/.config/oxidized ] && mkdir -p /home/oxidized/.config/oxidized
|
|
3
3
|
[ -f /home/oxidized/.config/oxidized/pid ] && rm /home/oxidized/.config/oxidized/pid
|
|
4
4
|
chown -R oxidized:oxidized /home/oxidized/.config/oxidized
|
|
5
|
-
exec
|
|
5
|
+
exec gosu oxidized oxidized
|
data/extra/rest_client.rb
CHANGED
|
@@ -6,7 +6,7 @@ module Oxidized
|
|
|
6
6
|
require 'asetus'
|
|
7
7
|
|
|
8
8
|
class Config
|
|
9
|
-
|
|
9
|
+
ROOT = ENV['OXIDIZED_HOME'] || File.join(Dir.home, '.config', 'oxidized')
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
CFGS = Asetus.new name: 'oxidized', load: false, key_to_s: true
|
|
@@ -14,8 +14,8 @@ module Oxidized
|
|
|
14
14
|
|
|
15
15
|
begin
|
|
16
16
|
CFGS.load
|
|
17
|
-
rescue StandardError =>
|
|
18
|
-
raise InvalidConfig, "Error loading config: #{
|
|
17
|
+
rescue StandardError => e
|
|
18
|
+
raise InvalidConfig, "Error loading config: #{e.message}"
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
restcfg = CFGS.cfg.rest
|
|
@@ -38,7 +38,8 @@ module Oxidized
|
|
|
38
38
|
|
|
39
39
|
def next(opt)
|
|
40
40
|
data = JSON.dump opt
|
|
41
|
-
|
|
41
|
+
headers = { 'content-type': 'application/json' }
|
|
42
|
+
@web.put PATH + '/node/next/' + opt[:name].to_s, data, headers
|
|
42
43
|
end
|
|
43
44
|
end
|
|
44
45
|
end
|