summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Dockerfile18
-rw-r--r--Gemfile1
-rw-r--r--README.md69
-rw-r--r--lib/oxidized/config.rb5
-rw-r--r--lib/oxidized/hook/slackdiff.rb30
-rw-r--r--lib/oxidized/model/aireos.rb2
-rw-r--r--lib/oxidized/model/aosw.rb28
-rw-r--r--lib/oxidized/model/comware.rb6
-rw-r--r--lib/oxidized/model/fabricos.rb3
-rw-r--r--lib/oxidized/model/fiberdriver.rb4
-rw-r--r--lib/oxidized/model/fortios.rb19
-rw-r--r--lib/oxidized/model/ios.rb8
-rw-r--r--lib/oxidized/model/ironware.rb9
-rw-r--r--lib/oxidized/model/nxos.rb15
-rw-r--r--lib/oxidized/model/opengear.rb2
-rw-r--r--lib/oxidized/model/procurve.rb5
-rw-r--r--lib/oxidized/model/routeros.rb4
-rw-r--r--lib/oxidized/model/timos.rb16
-rw-r--r--lib/oxidized/model/tplink.rb65
-rw-r--r--lib/oxidized/model/zhoneolt.rb52
-rw-r--r--lib/oxidized/node.rb5
-rw-r--r--lib/oxidized/nodes.rb3
-rw-r--r--lib/oxidized/source/csv.rb11
-rw-r--r--lib/oxidized/source/http.rb31
-rw-r--r--lib/oxidized/worker.rb5
25 files changed, 369 insertions, 47 deletions
diff --git a/Dockerfile b/Dockerfile
index 905825e..fb2a635 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -3,9 +3,23 @@ MAINTAINER Samer Abdel-Hafez <sam@arahant.net>
RUN add-apt-repository ppa:brightbox/ruby-ng && \
apt-get update && \
- apt-get install -y ruby2.3 ruby2.3-dev libsqlite3-dev libssl-dev pkg-config make cmake libssh2-1-dev
+ apt-get install -y ruby2.3 ruby2.3-dev libsqlite3-dev libssl-dev pkg-config make cmake libssh2-1-dev git g++
-RUN gem install oxidized oxidized-web --no-ri --no-rdoc
+RUN mkdir -p /tmp/oxidized
+COPY . /tmp/oxidized/
+WORKDIR /tmp/oxidized
+
+RUN gem build oxidized.gemspec
+RUN gem install oxidized-*.gem
+
+# web interface
+RUN gem install oxidized-web --no-ri --no-rdoc
+
+# dependencies for hooks
+RUN gem install aws-sdk
+RUN gem install slack-api
+
+RUN rm -rf /tmp/oxidized
RUN apt-get remove -y ruby-dev pkg-config make cmake
diff --git a/Gemfile b/Gemfile
index fa75df1..851fabc 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,3 +1,2 @@
source 'https://rubygems.org'
-
gemspec
diff --git a/README.md b/README.md
index 82c33a5..2f3defc 100644
--- a/README.md
+++ b/README.md
@@ -148,12 +148,16 @@ Oxidized is a network device configuration backup tool. It's a RANCID replacemen
* [Supermicro](lib/oxidized/model/supermicro.rb)
* Trango Systems
* [Trango](lib/oxidized/model/trango.rb)
+ * TPLink
+ * [TPLink](lib/oxidized/model/tplink.rb)
* Ubiquiti
* [AirOS](lib/oxidized/model/airos.rb)
* [Edgeos](lib/oxidized/model/edgeos.rb)
* [EdgeSwitch](lib/oxidized/model/edgeswitch.rb)
* Watchguard
* [Fireware OS](lib/oxidized/model/firewareos.rb)
+ * Zhone
+ * [Zhone (OLT and MX)](lib/oxidized/model/zhoneolt.rb)
* Zyxel
* [ZyNOS](lib/oxidized/model/zynos.rb)
@@ -186,6 +190,14 @@ gem install oxidized
gem install oxidized-script oxidized-web
```
+## Build from Git
+```shell
+git clone https://github.com/ytti/oxidized.git
+cd oxidized/
+gem build *.gemspec
+gem install pkg/*.gem
+```
+
# Configuration
Oxidized configuration is in YAML format. Configuration files are subsequently sourced from ```/etc/oxidized/config``` then ```~/.config/oxidized/config```. The hashes will be merged, this might be useful for storing source information in a system wide file and user specific configuration in the home directory (to only include a staff specific username and password). Eg. if many users are using ```oxs```, see [Oxidized::Script](https://github.com/ytti/oxidized-script).
@@ -233,12 +245,15 @@ oxidized
Now tell Oxidized where it finds a list of network devices to backup configuration from. You can either use CSV or SQLite as source. To create a CSV source add the following snippet:
+Note: If gpg is set to anything other than false it will attempt to decrypt the file contents
```
source:
default: csv
csv:
file: ~/.config/oxidized/router.db
delimiter: !ruby/regexp /:/
+ gpg: false
+ gpg_password: 'password'
map:
name: 0
model: 1
@@ -346,14 +361,18 @@ docker run -v /etc/oxidized:/root/.config/oxidized -p 8888:8888/tcp -e CONFIG_RE
## Cookbook
### Debugging
-In case a model plugin doesn't work correctly (ios, procurve, etc.), you can enable live debugging of SSH/Telnet sessions. Just add a ```debug``` option, specifying a log file destination to the ```input``` section.
+In case a model plugin doesn't work correctly (ios, procurve, etc.), you can enable live debugging of SSH/Telnet sessions. Just add a ```debug``` option containing the value true to the ```input``` section. The log files will be created depending on the parent directory of the logfile option.
-The following example will log an active ssh session to ```/home/fisakytt/.config/oxidized/log_input-ssh``` and telnet to ```log_input-telnet```. The file will be truncated on each consecutive ssh/telnet session, so you need to put a ```tailf``` or ```tail -f``` on that file!
+The following example will log an active ssh/telnet session ```/home/oxidized/.config/oxidized/log/<IP-Adress>-<PROTOCOL>```. The file will be truncated on each consecutive ssh/telnet session, so you need to put a ```tailf``` or ```tail -f``` on that file!
```
+log: /home/oxidized/.config/oxidized/log
+
+...
+
input:
default: ssh, telnet
- debug: /tmp/oxidized_log_input
+ debug: true
ssh:
secure: false
```
@@ -403,7 +422,7 @@ vars:
### Source: CSV
-One line per device, colon seperated.
+One line per device, colon seperated. If `ip` isn't present, a DNS lookup will be done against `name`. For large installations, setting `ip` will dramatically reduce startup time.
```
source:
@@ -413,11 +432,12 @@ source:
delimiter: !ruby/regexp /:/
map:
name: 0
- model: 1
- username: 2
- password: 3
+ ip: 1
+ model: 2
+ username: 3
+ password: 4
vars_map:
- enable: 4
+ enable: 5
```
### SSH Proxy Command
@@ -725,6 +745,16 @@ map:
group: 2
```
+### Triggered backups
+
+A node can be moved to head-of-queue via the REST API `GET/POST /node/next/[NODE]`.
+
+In the default configuration this node will be processed when the next job worker becomes available, it could take some time if existing backups are in progress. To execute moved jobs immediately a new job can be added:
+
+```
+next_adds_job: true
+```
+
# Hooks
You can define arbitrary number of hooks that subscribe different events. The hook system is modular and different kind of hook types can be enabled.
@@ -844,6 +874,29 @@ AWS SNS hook requires the following configuration keys:
Your AWS credentials should be stored in `~/.aws/credentials`.
+## Hook type: slackdiff
+
+The `slackdiff` hook posts colorized config diffs to a [Slack](http://www.slack.com) channel of your choice. It only triggers for `post_store` events.
+
+You will need to manually install the `slack-api` gem on your system:
+
+```
+gem install slack-api
+```
+
+Configuration example:
+
+``` yaml
+hooks:
+ slack:
+ type: slackdiff
+ events: [post_store]
+ token: SLACK_BOT_TOKEN
+ channel: "#network-changes"
+```
+
+Note the channel name must be in quotes.
+
# Ruby API
The following objects exist in Oxidized.
diff --git a/lib/oxidized/config.rb b/lib/oxidized/config.rb
index b6b5c40..aba8b63 100644
--- a/lib/oxidized/config.rb
+++ b/lib/oxidized/config.rb
@@ -28,8 +28,9 @@ module Oxidized
asetus.default.retries = 3
asetus.default.prompt = /^([\w.@-]+[#>]\s?)$/
asetus.default.rest = '127.0.0.1:8888' # or false to disable
- asetus.default.vars = {} # could be 'enable'=>'enablePW'
- asetus.default.groups = {} # group level configuration
+ asetus.default.next_adds_job = false # if true, /next adds job, so device is fetched immmeiately
+ asetus.default.vars = {} # could be 'enable'=>'enablePW'
+ asetus.default.groups = {} # group level configuration
asetus.default.pid = File.join(Oxidized::Config::Root, 'pid')
asetus.default.input.default = 'ssh, telnet'
diff --git a/lib/oxidized/hook/slackdiff.rb b/lib/oxidized/hook/slackdiff.rb
new file mode 100644
index 0000000..b37c9c4
--- /dev/null
+++ b/lib/oxidized/hook/slackdiff.rb
@@ -0,0 +1,30 @@
+require 'slack'
+
+class SlackDiff < Oxidized::Hook
+ def validate_cfg!
+ raise KeyError, 'hook.token is required' unless cfg.has_key?('token')
+ raise KeyError, 'hook.channel is required' unless cfg.has_key?('channel')
+ end
+
+ def run_hook(ctx)
+ if ctx.node
+ if ctx.event.to_s == "post_store"
+ log "Connecting to slack"
+ client = Slack::Client.new token: cfg.token
+ client.auth_test
+ log "Connected"
+ gitoutput = ctx.node.output.new
+ diff = gitoutput.get_diff ctx.node, ctx.node.group, ctx.commitref, nil
+ title = "#{ctx.node.name.to_s} #{ctx.node.group.to_s} #{ctx.node.model.class.name.to_s.downcase}"
+ log "Posting diff as snippet to #{cfg.channel}"
+ client.files_upload(channels: cfg.channel, as_user: true,
+ content: diff[:patch].lines.to_a[4..-1].join,
+ filetype: "diff",
+ title: title,
+ filename: "change"
+ )
+ log "Finished"
+ end
+ end
+ end
+end
diff --git a/lib/oxidized/model/aireos.rb b/lib/oxidized/model/aireos.rb
index 7e9d49e..7056e3f 100644
--- a/lib/oxidized/model/aireos.rb
+++ b/lib/oxidized/model/aireos.rb
@@ -44,7 +44,7 @@ class Aireos < Oxidized::Model
out = []
cfg.each_line do |line|
next if line.match /^\s*$/
- next if line.match /rogue adhoc alert [\da-f]{2}:/
+ next if line.match /rogue (adhoc|client) alert [\da-f]{2}:/
line = line[1..-1] if line[0] == "\r"
out << line.strip
end
diff --git a/lib/oxidized/model/aosw.rb b/lib/oxidized/model/aosw.rb
index 11d8442..a85ead7 100644
--- a/lib/oxidized/model/aosw.rb
+++ b/lib/oxidized/model/aosw.rb
@@ -1,45 +1,61 @@
class AOSW < Oxidized::Model
- # AOSW Aruba Wireless
+ # AOSW Aruba Wireless, IAP, Instant Controller and Mobility Access Switches
# Used in Alcatel OAW-4750 WLAN controller
# Also Dell controllers
+
+ # HPE Aruba Switches should use a different model as the software is based on the HP Procurve line.
+
+ # Support for IAP & Instant Controller tested with 115, 205, 215 & 325 running 6.4.4.8-4.2.4.5_57965
+ # Support for Mobility Access Switches tested with S2500-48P & S2500-24P running 7.4.1.4_54199 and S2500-24P running 7.4.1.7_57823
+ # All IAPs connected to a Instant Controller will have the same config output. Only the controller needs to be monitored.
comment '# '
- prompt /^\([^)]+\) [#>]/
+ prompt /^\(?.+\)?\s?[#>]/
cmd :all do |cfg|
cfg.each_line.to_a[1..-2].join
end
cmd :secret do |cfg|
+ cfg.gsub!(/secret (\S+)$/, 'secret <secret removed>')
+ cfg.gsub!(/enable secret (\S+)$/, 'enable secret <secret removed>')
cfg.gsub!(/PRE-SHARE (\S+)$/, 'PRE-SHARE <secret removed>')
cfg.gsub!(/ipsec (\S+)$/, 'ipsec <secret removed>')
cfg.gsub!(/community (\S+)$/, 'community <secret removed>')
cfg.gsub!(/ sha (\S+)/, ' sha <secret removed>')
cfg.gsub!(/ des (\S+)/, ' des <secret removed>')
cfg.gsub!(/mobility-manager (\S+) user (\S+) (\S+)/, 'mobility-manager \1 user \2 <secret removed>')
- cfg.gsub!(/mgmt-user (\S+) (\S+) (\S+)$/, 'mgmt-user \1 \2 <secret removed>')
+ cfg.gsub!(/mgmt-user (\S+) (root|guest\-provisioning|network\-operations|read\-only|location\-api\-mgmt) (\S+)$/, 'mgmt-user \1 \2 <secret removed>') #MAS & Wireless Controler
+ cfg.gsub!(/mgmt-user (\S+) (\S+)( (read\-only|guest\-mgmt))?$/, 'mgmt-user \1 <secret removed> \3') #IAP
+#MAS format: mgmt-user <username> <accesslevel> <password hash>
+#IAP format (root user): mgmt-user <username> <password hash>
+#IAP format: mgmt-user <username> <password hash> <access level>
cfg.gsub!(/key (\S+)$/, 'key <secret removed>')
- cfg.gsub!(/secret (\S+)$/, 'secret <secret removed>')
cfg.gsub!(/wpa-passphrase (\S+)$/, 'wpa-passphrase <secret removed>')
cfg.gsub!(/bkup-passwords (\S+)$/, 'bkup-passwords <secret removed>')
+ cfg.gsub!(/user (\S+) (\S+) (\S+)$/, 'user \1 <secret removed> \3')
+ cfg.gsub!(/virtual-controller-key (\S+)$/, 'virtual-controller-key <secret removed>')
cfg
end
cmd 'show version' do |cfg|
- cfg = cfg.each_line.select { |line| not line.match /Switch uptime/i }
+ cfg = cfg.each_line.select { |line| not line.match /(Switch|AP) uptime/i }
rstrip_cfg comment cfg.join
end
cmd 'show inventory' do |cfg|
+ cfg = "" if cfg.match /(Invalid input detected at '\^' marker|Parse error)/ #Don't show for unsupported devices (IAP and MAS)
rstrip_cfg clean cfg
end
cmd 'show slots' do |cfg|
+ cfg = "" if cfg.match /(Invalid input detected at '\^' marker|Parse error)/ #Don't show for unsupported devices (IAP and MAS)
rstrip_cfg comment cfg
end
cmd 'show license' do |cfg|
+ cfg = "" if cfg.match /(Invalid input detected at '\^' marker|Parse error)/ #Don't show for unsupported devices (IAP and MAS)
rstrip_cfg comment cfg
end
@@ -90,7 +106,7 @@ class AOSW < Oxidized::Model
next if line.match /Output \d Config/i
next if line.match /(Tachometers|Temperatures|Voltages)/
next if line.match /((Card|CPU) Temperature|Chassis Fan|VMON1[0-9])/
- next if line.match /[0-9]+ (RPMS?|m?V|C)/i
+ next if line.match /[0-9]+\s+(RPMS?|m?V|C)/i
out << line.strip
end
out = comment out.join "\n"
diff --git a/lib/oxidized/model/comware.rb b/lib/oxidized/model/comware.rb
index 27b70ae..d926854 100644
--- a/lib/oxidized/model/comware.rb
+++ b/lib/oxidized/model/comware.rb
@@ -18,6 +18,12 @@ class Comware < Oxidized::Model
cfg.each_line.to_a[1..-2].join
end
+ cmd :secret do |cfg|
+ cfg.gsub! /^( snmp-agent community).*/, '\\1 <configuration removed>'
+ cfg.gsub! /^( password hash).*/, '\\1 <configuration removed>'
+ cfg
+ end
+
cfg :telnet do
username /^Username:$/
password /^Password:$/
diff --git a/lib/oxidized/model/fabricos.rb b/lib/oxidized/model/fabricos.rb
index 7ab9fd5..e6d921e 100644
--- a/lib/oxidized/model/fabricos.rb
+++ b/lib/oxidized/model/fabricos.rb
@@ -7,10 +7,11 @@ class FabricOS < Oxidized::Model
comment '# '
cmd 'chassisShow' do |cfg|
- comment cfg
+ comment cfg.each_line.reject { |line| line.match /Time Awake:/ or line.match /Power Usage \(Watts\):/ or line.match /Time Alive:/ or line.match /Update:/ }.join
end
cmd 'configShow -all' do |cfg|
+ cfg = cfg.each_line.reject { |line| line.match /date = /}.join
cfg
end
diff --git a/lib/oxidized/model/fiberdriver.rb b/lib/oxidized/model/fiberdriver.rb
index 8f8eb07..abe8f68 100644
--- a/lib/oxidized/model/fiberdriver.rb
+++ b/lib/oxidized/model/fiberdriver.rb
@@ -11,6 +11,10 @@ class FiberDriver < Oxidized::Model
cmd "show running-config" do |cfg|
cfg.each_line.to_a[3..-1].join
+ cfg.gsub! /^Building configuration.*$/, ''
+ cfg.gsub! /^Current configuration:.*$$/, ''
+ cfg.gsub! /^! Configuration saved on .*$/, ''
+ cfg
end
cfg :ssh do
diff --git a/lib/oxidized/model/fortios.rb b/lib/oxidized/model/fortios.rb
index cd379b7..2415537 100644
--- a/lib/oxidized/model/fortios.rb
+++ b/lib/oxidized/model/fortios.rb
@@ -14,6 +14,12 @@ class FortiOS < Oxidized::Model
new_cfg << cfg.each_line.to_a[1..-2].map { |line| line.gsub(/(conf_file_ver=)(.*)/, '\1<stripped>\3') }.join
end
+ cmd :secret do |cfg|
+ cfg.gsub! /(set (?:passwd|password|psksecret)).*/, '\\1 <configuration removed>'
+ cfg.gsub! /(set private-key).*-+END ENCRYPTED PRIVATE KEY-*"$/m , '\\1 <configuration removed>'
+ cfg
+ end
+
cmd 'get system status' do |cfg|
@vdom_enabled = cfg.include? 'Virtual domain configuration: enable'
cfg.gsub!(/(System time: )(.*)/, '\1<stripped>\3')
@@ -25,14 +31,18 @@ class FortiOS < Oxidized::Model
cfg << cmd('config global') if @vdom_enabled
cfg << cmd('get hardware status') do |cfg|
- comment cfg
+ comment cfg
end
- cfg << cmd('diagnose autoupdate version') do |cfg|
- comment cfg
+ #default behaviour: include autoupdate output (backwards compatibility)
+ #do not include if variable "show_autoupdate" is set to false
+ if defined?(vars(:fortios_autoupdate)).nil? || vars(:fortios_autoupdate)
+ cfg << cmd('diagnose autoupdate version') do |cfg|
+ comment cfg.each_line.reject { |line| line.match /Last Update|Result/ }.join
+ end
end
- cfg << cmd('end') if @vdom_enabled
+cfg << cmd('end') if @vdom_enabled
cfg << cmd('show')
cfg.join "\n"
@@ -48,3 +58,4 @@ class FortiOS < Oxidized::Model
end
end
+
diff --git a/lib/oxidized/model/ios.rb b/lib/oxidized/model/ios.rb
index aa64847..94a3ab0 100644
--- a/lib/oxidized/model/ios.rb
+++ b/lib/oxidized/model/ios.rb
@@ -27,8 +27,8 @@ class IOS < Oxidized::Model
cfg.gsub! /username (\S+) privilege (\d+) (\S+).*/, '<secret hidden>'
cfg.gsub! /^username \S+ password \d \S+/, '<secret hidden>'
cfg.gsub! /^username \S+ secret \d \S+/, '<secret hidden>'
- cfg.gsub! /^enable password \d \S+/, '<secret hidden>'
- cfg.gsub! /^enable secret \d \S+/, '<secret hidden>'
+ cfg.gsub! /^enable (password|secret) \d \S+/, '<secret hidden>'
+ cfg.gsub! /^(\s+(?:password|secret)) (?:\d )?\S+/, '\\1 <secret hidden>'
cfg.gsub! /wpa-psk ascii \d \S+/, '<secret hidden>'
cfg.gsub! /key 7 \d.+/, '<secret hidden>'
cfg.gsub! /^tacacs-server key \d \S+/, '<secret hidden>'
@@ -59,8 +59,6 @@ class IOS < Oxidized::Model
end
cfg :telnet, :ssh do
- post_login 'terminal length 0'
- post_login 'terminal width 0'
# preferred way to handle additional passwords
if vars :enable
post_login do
@@ -68,6 +66,8 @@ class IOS < Oxidized::Model
cmd vars(:enable)
end
end
+ post_login 'terminal length 0'
+ post_login 'terminal width 0'
pre_logout 'exit'
end
diff --git a/lib/oxidized/model/ironware.rb b/lib/oxidized/model/ironware.rb
index db341d1..c7c2197 100644
--- a/lib/oxidized/model/ironware.rb
+++ b/lib/oxidized/model/ironware.rb
@@ -30,10 +30,11 @@ class IronWare < Oxidized::Model
end
cmd 'show chassis' do |cfg|
- cfg.encode!("UTF-8", :invalid => :replace) #sometimes ironware returns broken encoding
+ cfg.encode!("UTF-8", :invalid => :replace, :undef => :replace) #sometimes ironware returns broken encoding
cfg.gsub! /(^((.*)Current temp(.*))$)/, '' #remove unwanted lines current temperature
cfg.gsub! /Speed = [A-Z-]{2,6} \(\d{2,3}\%\)/, '' #remove unwanted lines Speed Fans
cfg.gsub! /current speed is [A-Z]{2,6} \(\d{2,3}\%\)/, ''
+ cfg.gsub! /([\[]*)1([\]]*)<->([\[]*)2([\]]*)(<->([\[]*)3([\]]*))*/, ''
cfg.gsub! /\d{2}\.\d deg-C/, 'XX.X deg-C'
if cfg.include? "TEMPERATURE"
sc = StringScanner.new cfg
@@ -73,16 +74,14 @@ class IronWare < Oxidized::Model
cfg :telnet, :ssh do
if vars :enable
post_login do
- send "enable\r\n"
+ send "enable\n"
cmd vars(:enable)
end
end
post_login ''
post_login 'skip-page-display'
post_login 'terminal length 0'
- pre_logout 'logout'
- pre_logout 'exit'
- pre_logout 'exit'
+ pre_logout "logout\nexit\nexit\n"
end
end
diff --git a/lib/oxidized/model/nxos.rb b/lib/oxidized/model/nxos.rb
index 6163724..e743415 100644
--- a/lib/oxidized/model/nxos.rb
+++ b/lib/oxidized/model/nxos.rb
@@ -3,6 +3,14 @@ class NXOS < Oxidized::Model
prompt /^(\r?[\w.@_()-]+[#]\s?)$/
comment '! '
+ cmd :secret do |cfg|
+ cfg.gsub! /^(snmp-server community).*/, '\\1 <configuration removed>'
+ cfg.gsub! /^(snmp-server user (\S+) (\S+) auth (\S+)) (\S+) (priv) (\S+)/, '\\1 <configuration removed> '
+ cfg.gsub! /^(username \S+ password \d) (\S+)/, '\\1 <secret hidden>'
+ cfg.gsub! /^(radius-server key).*/, '\\1 <secret hidden>'
+ cfg
+ end
+
cmd 'show version' do |cfg|
cfg = cfg.each_line.take_while { |line| not line.match(/uptime/i) }
comment cfg.join ""
@@ -16,8 +24,13 @@ class NXOS < Oxidized::Model
cfg.gsub! /^!Time:[^\n]*\n/, ''
end
- cfg :ssh do
+ cfg :ssh, :telnet do
post_login 'terminal length 0'
pre_logout 'exit'
end
+
+ cfg :telnet do
+ username /^login:/
+ password /^Password:/
+ end
end
diff --git a/lib/oxidized/model/opengear.rb b/lib/oxidized/model/opengear.rb
index 7f801f8..b7c697f 100644
--- a/lib/oxidized/model/opengear.rb
+++ b/lib/oxidized/model/opengear.rb
@@ -1,6 +1,8 @@
class OpenGear < Oxidized::Model
comment '# '
+
+ prompt /^(\$\s)?$/
cmd :secret do |cfg|
cfg.gsub!(/password (\S+)/, 'password <secret removed>')
diff --git a/lib/oxidized/model/procurve.rb b/lib/oxidized/model/procurve.rb
index c117df3..0b8b672 100644
--- a/lib/oxidized/model/procurve.rb
+++ b/lib/oxidized/model/procurve.rb
@@ -6,6 +6,11 @@ class Procurve < Oxidized::Model
comment '! '
+ # replace next line control sequence with a new line
+ expect /\eE/ do |data, re|
+ data.gsub re, "\n"
+ end
+
# replace all used vt100 control sequences
expect /\e\[\??\d+(;\d+)*[A-Za-z]/ do |data, re|
data.gsub re, ''
diff --git a/lib/oxidized/model/routeros.rb b/lib/oxidized/model/routeros.rb
index a92ad5e..5717100 100644
--- a/lib/oxidized/model/routeros.rb
+++ b/lib/oxidized/model/routeros.rb
@@ -6,6 +6,10 @@ class RouterOS < Oxidized::Model
comment cfg
end
+ cmd '/system package update print' do |cfg|
+ comment cfg
+ end
+
cmd '/export' do |cfg|
cfg.gsub! /\x1B\[([0-9]{1,3}((;[0-9]{1,3})*)?)?[m|K]/, '' # strip ANSI colours
cfg.gsub! /\\\r\n\s+/, '' # strip new line
diff --git a/lib/oxidized/model/timos.rb b/lib/oxidized/model/timos.rb
index d40e845..c230a8f 100644
--- a/lib/oxidized/model/timos.rb
+++ b/lib/oxidized/model/timos.rb
@@ -18,6 +18,8 @@ class TiMOS < Oxidized::Model
# Show the boot options file.
#
cmd 'show bof' do |cfg|
+ cfg.gsub! /# Finished .*/, ''
+ cfg.gsub! /# Generated .*/, ''
comment cfg
end
@@ -29,6 +31,8 @@ class TiMOS < Oxidized::Model
# Strip uptime.
#
cfg.sub! /^System Up Time.*\n/, ''
+ cfg.gsub! /# Finished .*/, ''
+ cfg.gsub! /# Generated .*/, ''
comment cfg
end
@@ -36,6 +40,8 @@ class TiMOS < Oxidized::Model
# Show the card state.
#
cmd 'show card state' do |cfg|
+ cfg.gsub! /# Finished .*/, ''
+ cfg.gsub! /# Generated .*/, ''
comment cfg
end
@@ -48,6 +54,8 @@ class TiMOS < Oxidized::Model
#
cfg.gsub! /\r/, ''
cfg.gsub! /[\b][\b][\b]/, "\n"
+ cfg.gsub! /# Finished .*/, ''
+ cfg.gsub! /# Generated .*/, ''
comment cfg
end
@@ -55,6 +63,8 @@ class TiMOS < Oxidized::Model
# Show the running debug configuration.
#
cmd 'show debug' do |cfg|
+ cfg.gsub! /# Finished .*/, ''
+ cfg.gsub! /# Generated .*/, ''
comment cfg
end
@@ -66,6 +76,8 @@ class TiMOS < Oxidized::Model
# Strip carriage returns.
#
cfg.gsub! /\r/, ''
+ cfg.gsub! /# Finished .*/, ''
+ cfg.gsub! /# Generated .*/, ''
comment cfg
end
@@ -77,6 +89,8 @@ class TiMOS < Oxidized::Model
# Strip carriage returns.
#
cfg.gsub! /\r/, ''
+ cfg.gsub! /# Finished .*/, ''
+ cfg.gsub! /# Generated .*/, ''
comment cfg
end
@@ -88,6 +102,8 @@ class TiMOS < Oxidized::Model
# Strip carriage returns.
#
cfg.gsub! /\r/, ''
+ cfg.gsub! /# Finished .*/, ''
+ cfg.gsub! /# Generated .*/, ''
end
cfg :telnet do
diff --git a/lib/oxidized/model/tplink.rb b/lib/oxidized/model/tplink.rb
new file mode 100644
index 0000000..bf13803
--- /dev/null
+++ b/lib/oxidized/model/tplink.rb
@@ -0,0 +1,65 @@
+class TPLink < Oxidized::Model
+
+ # tp-link prompt
+ prompt /^\r?([\w.@()-]+[#>]\s?)$/
+ comment '! '
+
+ # handle paging
+ # workaround for sometimes missing whitespaces with "\s?"
+ expect /Press\s?any\s?key\s?to\s?continue\s?\(Q\s?to\s?quit\)/ do |data, re|
+ send ' '
+ data.sub re, ''
+ end
+
+ # send carriage return because \n with the command is not enough
+ # checks if line ends with prompt >,# or \r,\nm otherwise send \r
+ expect /[^>#\r\n]$/ do |data, re|
+ send "\r"
+ data.sub re, ''
+ end
+
+ cmd :all do |cfg|
+ # normalize linefeeds
+ cfg.gsub! /(\r|\r\n|\n\r)/,"\n"
+ # remove empty lines
+ cfg.each_line.reject { |line| line.match /^[\r\n\s\u0000#]+$/ }.join
+ end
+
+ cmd :secret do |cfg|
+ cfg.gsub! /^(snmp-server community).*/, '\\1 <configuration removed>'
+ cfg.gsub! /secret (\d+) (\S+).*/, '<secret hidden>'
+ cfg
+ end
+
+ cmd 'show system-info' do |cfg|
+ comment cfg.each_line.to_a[3..-3].join
+ end
+
+ cmd 'show running-config' do |cfg|
+ lines = cfg.each_line.to_a[1..-1]
+ # cut config after "end"
+ lines[0..lines.index("end\n")].join
+ end
+
+ cfg :telnet, :ssh do
+ username /^User ?[nN]ame:/
+ password /^\r?Password:/
+ end
+
+ cfg :telnet, :ssh do
+ if vars :enable
+ post_login do
+ send "enable\r"
+ cmd vars(:enable)
+ end
+ end
+
+ pre_logout do
+ send "exit\r"
+ send "logout\r"
+ end
+
+ end
+
+end
+
diff --git a/lib/oxidized/model/zhoneolt.rb b/lib/oxidized/model/zhoneolt.rb
new file mode 100644
index 0000000..b60edb2
--- /dev/null
+++ b/lib/oxidized/model/zhoneolt.rb
@@ -0,0 +1,52 @@
+class ZhoneOLT < Oxidized::Model
+ # Zhone OLT/MetroE/DSL devices (ONT uses a completely different CLI)
+
+ # the prompt can be anything on zhone, but it defaults to 'zXX>' and we
+ # always use hostname>
+ prompt /^(\r*[\w.@():-]+[>]\s?)$/
+ comment '# '
+
+ cmd :secret do |cfg|
+ cfg.gsub! /^(set configsyncpasswd = ) \S+/, '\\1 <removed>'
+ cfg.gsub! /^(set user-pass = ) \S+/, '\\1 <removed>'
+ cfg.gsub! /^(set auth-key = ) \S+/, '\\1 <removed>'
+ cfg.gsub! /^(set priv-key = ) \S+/, '\\1 <removed>'
+ cfg.gsub! /^(set ftp-password = ) \S+/, '\\1 <removed>'
+ cfg.gsub! /^(set community-name = ) \S+/, '\\1 <removed>'
+ cfg.gsub! /^(set communityname = ) \S+/, '\\1 <removed>'
+ cfg
+ end
+
+ cmd :all do |cfg|
+ cfg.each_line.to_a[1..-2].map{|line|line.delete("\r").rstrip}.join("\n") + "\n"
+ end
+
+ cmd 'swversion' do |cfg|
+ comment cfg
+ end
+
+ cmd 'slots' do |cfg|
+ comment cfg
+ end
+
+ cmd 'eeshow card' do |cfg|
+ comment cfg
+ end
+
+ cmd 'ethrpshow' do |cfg|
+ cfg = cfg.each_line.select { |line| line.match /Vendor (Name|OUI|Part|Revision)|Serial Number|Manufacturing Date/ }.join
+ comment cfg
+ end
+
+ cmd 'dump console' do |cfg|
+ cfg = cfg.each_line.select { |line| not line.match /To Abort the operation enter Ctrl-C/ }.join
+ end
+
+ # zhone technically supports ssh, but it locks up a ton. Especially when
+ # showing large amounts of output, like "dump console"
+ cfg :telnet do
+ username /\r*login:/
+ password /\r*password:/
+ pre_logout 'logout'
+ end
+end
diff --git a/lib/oxidized/node.rb b/lib/oxidized/node.rb
index b13ce0e..6f89b56 100644
--- a/lib/oxidized/node.rb
+++ b/lib/oxidized/node.rb
@@ -10,8 +10,11 @@ module Oxidized
alias :running? :running
def initialize opt
Oxidized.logger.debug 'resolving DNS for %s...' % opt[:name]
+ # remove the prefix if an IP Address is provided with one as IPAddr converts it to a network address.
+ ip_addr, _ = opt[:ip].to_s.split("/")
+ Oxidized.logger.debug 'IPADDR %s' % ip_addr.to_s
@name = opt[:name]
- @ip = IPAddr.new(opt[:ip]).to_s rescue nil
+ @ip = IPAddr.new(ip_addr).to_s rescue nil
@ip ||= Resolv.new.getaddress @name
@group = opt[:group]
@input = resolve_input opt
diff --git a/lib/oxidized/nodes.rb b/lib/oxidized/nodes.rb
index f5a1ad0..6751c7a 100644
--- a/lib/oxidized/nodes.rb
+++ b/lib/oxidized/nodes.rb
@@ -4,7 +4,7 @@ module Oxidized
class Oxidized::NotSupported < OxidizedError; end
class Oxidized::NodeNotFound < OxidizedError; end
class Nodes < Array
- attr_accessor :source
+ attr_accessor :source, :jobs
alias :put :unshift
def load node_want=nil
with_lock do
@@ -73,6 +73,7 @@ module Oxidized
# set last job to nil so that the node is picked for immediate update
n.last = nil
put n
+ jobs.want += 1 if Oxidized.config.next_adds_job?
end
end
end
diff --git a/lib/oxidized/source/csv.rb b/lib/oxidized/source/csv.rb
index d498e0b..c1e310a 100644
--- a/lib/oxidized/source/csv.rb
+++ b/lib/oxidized/source/csv.rb
@@ -11,14 +11,23 @@ class CSV < Source
Oxidized.asetus.user.source.csv.delimiter = /:/
Oxidized.asetus.user.source.csv.map.name = 0
Oxidized.asetus.user.source.csv.map.model = 1
+ Oxidized.asetus.user.source.csv.gpg = false
Oxidized.asetus.save :user
raise NoConfig, 'no source csv config, edit ~/.config/oxidized/config'
end
+ require 'gpgme' if @cfg.gpg?
end
def load
nodes = []
- open(File.expand_path @cfg.file).each_line do |line|
+ file = File.expand_path(@cfg.file)
+ file = if @cfg.gpg?
+ crypto = GPGME::Crypto.new password: @cfg.gpg_password
+ crypto.decrypt(file).to_s
+ else
+ open(file)
+ end
+ file.each_line do |line|
next if line.match(/^\s*#/)
data = line.chomp.split(@cfg.delimiter, -1)
next if data.empty?
diff --git a/lib/oxidized/source/http.rb b/lib/oxidized/source/http.rb
index 4fd388b..6c12f29 100644
--- a/lib/oxidized/source/http.rb
+++ b/lib/oxidized/source/http.rb
@@ -29,25 +29,27 @@ class HTTP < Source
end
request = Net::HTTP::Get.new(uri.request_uri, headers)
- if (@cfg.user && @cfg.pass)
+ if (@cfg.user? && @cfg.pass?)
request.basic_auth(@cfg.user,@cfg.pass)
end
response = http.request(request)
data = JSON.parse(response.body)
- data.each do |line|
- next if line.empty?
+ data.each do |node|
+ next if node.empty?
# map node parameters
keys = {}
- @cfg.map.each do |key, position|
- keys[key.to_sym] = node_var_interpolate line[position]
+ @cfg.map.each do |key, want_position|
+ want_positions = want_position.split('.')
+ keys[key.to_sym] = node_var_interpolate node.dig(*want_positions)
end
keys[:model] = map_model keys[:model] if keys.key? :model
# map node specific vars
vars = {}
- @cfg.vars_map.each do |key, position|
- vars[key.to_sym] = node_var_interpolate line[position]
+ @cfg.vars_map.each do |key, want_position|
+ want_positions = want_position.split('.')
+ vars[key.to_sym] = node_var_interpolate node.dig(*want_positions)
end
keys[:vars] = vars unless vars.empty?
@@ -58,3 +60,18 @@ class HTTP < Source
end
end
+
+if RUBY_VERSION < '2.3'
+ class Hash
+ def dig(key, *rest)
+ value = self[key]
+ if value.nil? || rest.empty?
+ value
+ elsif value.respond_to?(:dig)
+ value.dig(*rest)
+ else # foo.bar.baz (bar exist but is not hash)
+ return nil
+ end
+ end
+ end
+end
diff --git a/lib/oxidized/worker.rb b/lib/oxidized/worker.rb
index 1952d01..80d65cb 100644
--- a/lib/oxidized/worker.rb
+++ b/lib/oxidized/worker.rb
@@ -3,8 +3,9 @@ module Oxidized
require 'oxidized/jobs'
class Worker
def initialize nodes
- @nodes = nodes
- @jobs = Jobs.new(Oxidized.config.threads, Oxidized.config.interval, @nodes)
+ @nodes = nodes
+ @jobs = Jobs.new(Oxidized.config.threads, Oxidized.config.interval, @nodes)
+ @nodes.jobs = @jobs
Thread.abort_on_exception = true
end