oxidized 0.29.1 → 0.30.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/codeql.yml +4 -4
- data/.github/workflows/publishdocker.yml +3 -0
- data/.github/workflows/ruby.yml +1 -1
- data/.github/workflows/stale.yml +4 -1
- data/.rubocop.yml +6 -3
- data/.rubocop_todo.yml +10 -93
- data/CHANGELOG.md +66 -4
- data/CONTRIBUTING.md +174 -0
- data/Dockerfile +12 -1
- data/README.md +18 -36
- data/bin/oxidized +2 -5
- data/docs/Hooks.md +37 -1
- data/docs/Model-Notes/APC_AOS.md +52 -0
- data/docs/Model-Notes/FSOS.md +11 -0
- data/docs/Model-Notes/FortiOS.md +28 -0
- data/docs/Model-Notes/README.md +1 -20
- data/docs/Model-Notes/XGS4600-Zyxel.md +5 -0
- data/docs/Ruby-API.md +1 -1
- data/docs/Sources.md +13 -0
- data/docs/Supported-OS-Types.md +178 -270
- data/docs/Troubleshooting.md +16 -0
- data/examples/podman-compose/Makefile +61 -0
- data/examples/podman-compose/README.md +58 -0
- data/examples/podman-compose/docker-compose.yml +21 -0
- data/examples/podman-compose/model-simulation/Dockerfile-model +13 -0
- data/examples/podman-compose/model-simulation/asternos.sh +34 -0
- data/examples/podman-compose/oxidized-config/.gitignore +8 -0
- data/examples/podman-compose/oxidized-config/config +46 -0
- data/examples/podman-compose/oxidized-config/router.db +1 -0
- data/examples/podman-compose/oxidized-ssh/README.md +14 -0
- data/extra/rest_client.rb +2 -2
- data/extra/syslog.rb +2 -2
- data/lib/oxidized/cli.rb +6 -4
- data/lib/oxidized/config.rb +17 -14
- data/lib/oxidized/core.rb +22 -3
- data/lib/oxidized/hook.rb +3 -3
- data/lib/oxidized/input/exec.rb +1 -1
- data/lib/oxidized/input/ftp.rb +2 -2
- data/lib/oxidized/input/http.rb +32 -8
- data/lib/oxidized/input/input.rb +1 -1
- data/lib/oxidized/input/scp.rb +52 -0
- data/lib/oxidized/input/ssh.rb +10 -7
- data/lib/oxidized/input/telnet.rb +3 -2
- data/lib/oxidized/input/tftp.rb +1 -1
- data/lib/oxidized/jobs.rb +11 -1
- data/lib/oxidized/manager.rb +6 -6
- data/lib/oxidized/model/acos.rb +1 -1
- data/lib/oxidized/model/addpack.rb +26 -0
- data/lib/oxidized/model/adtran.rb +5 -1
- data/lib/oxidized/model/adva.rb +2 -2
- data/lib/oxidized/model/aoscx.rb +2 -1
- data/lib/oxidized/model/apc_aos.rb +2 -1
- data/lib/oxidized/model/aricentiss.rb +7 -0
- data/lib/oxidized/model/asternos.rb +22 -0
- data/lib/oxidized/model/asyncos.rb +2 -2
- data/lib/oxidized/model/awplus.rb +2 -2
- 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/comware.rb +2 -2
- data/lib/oxidized/model/cumulus.rb +1 -1
- data/lib/oxidized/model/dellx.rb +1 -1
- data/lib/oxidized/model/dlink.rb +4 -2
- data/lib/oxidized/model/dlinknextgen.rb +51 -0
- data/lib/oxidized/model/dnos.rb +3 -0
- data/lib/oxidized/model/edgecos.rb +1 -1
- data/lib/oxidized/model/eltex.rb +2 -0
- data/lib/oxidized/model/enterasys800.rb +1 -1
- data/lib/oxidized/model/eos.rb +1 -1
- data/lib/oxidized/model/firebrick.rb +2 -2
- data/lib/oxidized/model/firewareos.rb +1 -1
- data/lib/oxidized/model/fortios.rb +9 -2
- data/lib/oxidized/model/fsos.rb +44 -0
- data/lib/oxidized/model/ios.rb +1 -1
- data/lib/oxidized/model/iosxr.rb +2 -2
- data/lib/oxidized/model/junos.rb +3 -2
- data/lib/oxidized/model/mimosab11.rb +34 -0
- data/lib/oxidized/model/ml66.rb +33 -0
- data/lib/oxidized/model/model.rb +3 -3
- data/lib/oxidized/model/netgear.rb +1 -1
- data/lib/oxidized/model/netscaler.rb +1 -1
- data/lib/oxidized/model/nxos.rb +4 -3
- 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/opnsense.rb +1 -1
- data/lib/oxidized/model/panos.rb +2 -0
- data/lib/oxidized/model/pfsense.rb +1 -0
- data/lib/oxidized/model/procurve.rb +3 -1
- data/lib/oxidized/model/rgos.rb +33 -0
- data/lib/oxidized/model/routeros.rb +10 -8
- data/lib/oxidized/model/slxos.rb +2 -2
- data/lib/oxidized/model/sonicos.rb +18 -17
- data/lib/oxidized/model/sros.rb +3 -3
- data/lib/oxidized/model/tplink.rb +4 -3
- data/lib/oxidized/model/truenas.rb +2 -1
- data/lib/oxidized/model/vrp.rb +3 -1
- data/lib/oxidized/model/vyatta.rb +6 -0
- data/lib/oxidized/model/zynos.rb +67 -3
- data/lib/oxidized/model/zynosadsl.rb +14 -0
- data/lib/oxidized/model/zynosgs.rb +2 -0
- data/lib/oxidized/model/zynosmgs.rb +32 -0
- data/lib/oxidized/node.rb +7 -7
- data/lib/oxidized/nodes.rb +17 -12
- data/lib/oxidized/output/file.rb +1 -1
- data/lib/oxidized/output/git.rb +5 -3
- data/lib/oxidized/output/gitcrypt.rb +4 -3
- data/lib/oxidized/signals.rb +44 -0
- data/lib/oxidized/source/csv.rb +1 -1
- data/lib/oxidized/source/http.rb +26 -5
- data/lib/oxidized/source/source.rb +2 -2
- data/lib/oxidized/source/sql.rb +3 -3
- data/lib/oxidized/version.rb +2 -2
- data/lib/oxidized/worker.rb +8 -1
- data/lib/oxidized.rb +3 -2
- data/lib/refinements.rb +1 -1
- data/oxidized.gemspec +6 -3
- metadata +77 -9
@@ -0,0 +1,21 @@
|
|
1
|
+
services:
|
2
|
+
oxidized:
|
3
|
+
# Choose the image that you want to test
|
4
|
+
# image: docker.io/oxidized/oxidized:0.29.1
|
5
|
+
image: docker.io/oxidized/oxidized:latest
|
6
|
+
ports:
|
7
|
+
- 127.0.0.1:8042:8888/tcp
|
8
|
+
environment:
|
9
|
+
# Reload hosts list once per day
|
10
|
+
CONFIG_RELOAD_INTERVAL: 86400
|
11
|
+
# Needed when you push to a remote git repository
|
12
|
+
OXIDIZED_SSH_PASSPHRASE: xxxxPassphasexxxx
|
13
|
+
volumes:
|
14
|
+
- ./oxidized-config:/home/oxidized/.config/oxidized
|
15
|
+
- ./oxidized-ssh:/home/oxidized/.ssh
|
16
|
+
# This is a simulated network device for the example to work out of the box
|
17
|
+
asternos-device:
|
18
|
+
image: localhost/local/model
|
19
|
+
volumes:
|
20
|
+
- ./model-simulation/asternos.sh:/home/oxidized/.profile
|
21
|
+
- ./model-simulation/asternos.sh:/home/admin/.profile
|
@@ -0,0 +1,13 @@
|
|
1
|
+
FROM docker.io/phusion/baseimage:jammy-1.0.2
|
2
|
+
|
3
|
+
# Use baseimage-docker's init system.
|
4
|
+
CMD ["/sbin/my_init"]
|
5
|
+
|
6
|
+
# enable ssh
|
7
|
+
RUN rm -f /etc/service/sshd/down
|
8
|
+
RUN /etc/my_init.d/00_regen_ssh_host_keys.sh
|
9
|
+
|
10
|
+
# Add users to login. The password is "oxidized"
|
11
|
+
RUN useradd -m oxidized -p '$y$j9T$UoDYxDiE.8iBGmoaD/acn1$kVvYvoEIJdKUmIKFVBRYKLIVzmEBP1RKrCM6Vfx.V55' -s /bin/bash
|
12
|
+
RUN useradd -m admin -p '$y$j9T$UoDYxDiE.8iBGmoaD/acn1$kVvYvoEIJdKUmIKFVBRYKLIVzmEBP1RKrCM6Vfx.V55' -s /bin/bash
|
13
|
+
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# if running bash
|
2
|
+
if [ -n "$BASH_VERSION" ]; then
|
3
|
+
# include .bashrc if it exists
|
4
|
+
if [ -f "$HOME/.bashrc" ]; then
|
5
|
+
. "$HOME/.bashrc"
|
6
|
+
fi
|
7
|
+
fi
|
8
|
+
|
9
|
+
# Display a MOTD
|
10
|
+
cat << EOF
|
11
|
+
This is the welcome message of this device
|
12
|
+
it is muliline
|
13
|
+
End of the MOTD
|
14
|
+
EOF
|
15
|
+
|
16
|
+
function show() {
|
17
|
+
if [ "$*" == "version" ]; then
|
18
|
+
echo "Version 1.2.3"
|
19
|
+
elif [ "$*" == "runningconfiguration all" ]; then
|
20
|
+
cat << EOF
|
21
|
+
! begin of the configuration
|
22
|
+
! this is the running config
|
23
|
+
!
|
24
|
+
I have no idea how a configuration in asternos looks like ;-)
|
25
|
+
!
|
26
|
+
! End of the Configuration
|
27
|
+
EOF
|
28
|
+
else
|
29
|
+
echo "command 'show $*' not implemented"
|
30
|
+
fi
|
31
|
+
}
|
32
|
+
|
33
|
+
PS1="asternos$"
|
34
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
---
|
2
|
+
username: oxidized
|
3
|
+
password: oxidized
|
4
|
+
resolve_dns: true
|
5
|
+
interval: 3600
|
6
|
+
use_syslog: false
|
7
|
+
debug: false
|
8
|
+
threads: 30
|
9
|
+
use_max_threads: true
|
10
|
+
timeout: 20
|
11
|
+
retries: 3
|
12
|
+
prompt: !ruby/regexp /^([\w.@-]+[#>]\s?)$/
|
13
|
+
rest: 127.0.0.1:8888
|
14
|
+
next_adds_job: false
|
15
|
+
vars: {}
|
16
|
+
groups: {}
|
17
|
+
group_map: {}
|
18
|
+
models: {}
|
19
|
+
pid: "~/.config/oxidized/pid"
|
20
|
+
crash:
|
21
|
+
directory: "~/.config/oxidized/crashes"
|
22
|
+
hostnames: false
|
23
|
+
stats:
|
24
|
+
history_size: 10
|
25
|
+
input:
|
26
|
+
default: ssh
|
27
|
+
debug: false
|
28
|
+
ssh:
|
29
|
+
secure: false
|
30
|
+
ftp:
|
31
|
+
passive: true
|
32
|
+
utf8_encoded: true
|
33
|
+
output:
|
34
|
+
default: file
|
35
|
+
file:
|
36
|
+
directory: "~/.config/oxidized/configs/"
|
37
|
+
source:
|
38
|
+
default: csv
|
39
|
+
csv:
|
40
|
+
file: "~/.config/oxidized/router.db"
|
41
|
+
delimiter: !ruby/regexp /:/
|
42
|
+
map:
|
43
|
+
name: 0
|
44
|
+
model: 1
|
45
|
+
ip: 2
|
46
|
+
gpg: false
|
@@ -0,0 +1 @@
|
|
1
|
+
asternos-device:asternos
|
@@ -0,0 +1,14 @@
|
|
1
|
+
This is `~/.ssh/` of the user oxidized inside the oxidized container.
|
2
|
+
|
3
|
+
## What you need here for the hook githubrepo
|
4
|
+
You can store the SSH key needed to access a remote Git repository here. Here is
|
5
|
+
an example how to generate this key.
|
6
|
+
```shell
|
7
|
+
ssh-keygen -q -t ed25519 -C "Oxidized Push Key@`hostname`" -N "YOURPASSPHRASE" -m PEM -f oxidized-key
|
8
|
+
```
|
9
|
+
|
10
|
+
You also need to store the public keys of the remote git server in known_hosts. If you do not,
|
11
|
+
oxidized will refuse to push to the remote Git with the error `#<Rugged::SshError: invalid or unknown remote ssh hostkey>`, see Issue #2753.
|
12
|
+
```shell
|
13
|
+
ssh-keyscan git-server.example.com > known_hosts
|
14
|
+
```
|
data/extra/rest_client.rb
CHANGED
@@ -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
|
data/extra/syslog.rb
CHANGED
@@ -43,8 +43,8 @@ module Oxidized
|
|
43
43
|
|
44
44
|
begin
|
45
45
|
CFGS.load
|
46
|
-
rescue StandardError =>
|
47
|
-
raise InvalidConfig, "Error loading config: #{
|
46
|
+
rescue StandardError => e
|
47
|
+
raise InvalidConfig, "Error loading config: #{e.message}"
|
48
48
|
ensure
|
49
49
|
CFG = CFGS.cfg # convenienence, instead of Config.cfg.password, CFG.password
|
50
50
|
end
|
data/lib/oxidized/cli.rb
CHANGED
@@ -11,8 +11,8 @@ module Oxidized
|
|
11
11
|
begin
|
12
12
|
Oxidized.logger.info "Oxidized starting, running as pid #{$PROCESS_ID}"
|
13
13
|
Oxidized.new
|
14
|
-
rescue StandardError =>
|
15
|
-
crash
|
14
|
+
rescue StandardError => e
|
15
|
+
crash e
|
16
16
|
raise
|
17
17
|
end
|
18
18
|
end
|
@@ -29,8 +29,8 @@ module Oxidized
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def crash(error)
|
32
|
-
Oxidized.logger.fatal "Oxidized crashed, crashfile written in #{Config::
|
33
|
-
File.open Config::
|
32
|
+
Oxidized.logger.fatal "Oxidized crashed, crashfile written in #{Config::CRASH}"
|
33
|
+
File.open Config::CRASH, 'w' do |file|
|
34
34
|
file.puts '-' * 50
|
35
35
|
file.puts Time.now.utc
|
36
36
|
file.puts error.message + ' [' + error.class.to_s + ']'
|
@@ -44,6 +44,8 @@ module Oxidized
|
|
44
44
|
opts = Slop.parse do |opt|
|
45
45
|
opt.on '-d', '--debug', 'turn on debugging'
|
46
46
|
opt.on '--daemonize', 'Daemonize/fork the process'
|
47
|
+
opt.string '--home-dir', 'Oxidized home dir', default: nil
|
48
|
+
opt.string '--config-file', 'Oxidized config file', default: nil
|
47
49
|
opt.on '-h', '--help', 'show usage' do
|
48
50
|
puts opt
|
49
51
|
exit
|
data/lib/oxidized/config.rb
CHANGED
@@ -4,18 +4,20 @@ module Oxidized
|
|
4
4
|
class InvalidConfig < OxidizedError; end
|
5
5
|
|
6
6
|
class Config
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
7
|
+
ROOT = ENV['OXIDIZED_HOME'] || File.join(Dir.home, '.config', 'oxidized')
|
8
|
+
CRASH = File.join(ENV['OXIDIZED_LOGS'] || ROOT, 'crash')
|
9
|
+
LOG = File.join(ENV['OXIDIZED_LOGS'] || ROOT, 'logs')
|
10
|
+
INPUT_DIR = File.join Directory, %w[lib oxidized input]
|
11
|
+
OUTPUT_DIR = File.join Directory, %w[lib oxidized output]
|
12
|
+
MODEL_DIR = File.join Directory, %w[lib oxidized model]
|
13
|
+
SOURCE_DIR = File.join Directory, %w[lib oxidized source]
|
14
|
+
HOOK_DIR = File.join Directory, %w[lib oxidized hook]
|
15
|
+
SLEEP = 1
|
16
16
|
|
17
17
|
def self.load(cmd_opts = {})
|
18
|
-
|
18
|
+
usrdir = File.expand_path(cmd_opts[:home_dir] || Oxidized::Config::ROOT)
|
19
|
+
cfgfile = cmd_opts[:config_file] || 'config'
|
20
|
+
asetus = Asetus.new(name: 'oxidized', load: false, key_to_s: true, usrdir: usrdir, cfgfile: cfgfile)
|
19
21
|
Oxidized.asetus = asetus
|
20
22
|
|
21
23
|
asetus.default.username = 'username'
|
@@ -25,6 +27,7 @@ module Oxidized
|
|
25
27
|
asetus.default.interval = 3600
|
26
28
|
asetus.default.use_syslog = false
|
27
29
|
asetus.default.debug = false
|
30
|
+
asetus.default.run_once = false
|
28
31
|
asetus.default.threads = 30
|
29
32
|
asetus.default.use_max_threads = false
|
30
33
|
asetus.default.timeout = 20
|
@@ -36,9 +39,9 @@ module Oxidized
|
|
36
39
|
asetus.default.groups = {} # group level configuration
|
37
40
|
asetus.default.group_map = {} # map aliases of groups to names
|
38
41
|
asetus.default.models = {} # model level configuration
|
39
|
-
asetus.default.pid = File.join(Oxidized::Config::
|
42
|
+
asetus.default.pid = File.join(Oxidized::Config::ROOT, 'pid')
|
40
43
|
|
41
|
-
asetus.default.crash.directory = File.join(Oxidized::Config::
|
44
|
+
asetus.default.crash.directory = File.join(Oxidized::Config::ROOT, 'crashes')
|
42
45
|
asetus.default.crash.hostnames = false
|
43
46
|
|
44
47
|
asetus.default.stats.history_size = 10
|
@@ -58,8 +61,8 @@ module Oxidized
|
|
58
61
|
|
59
62
|
begin
|
60
63
|
asetus.load # load system+user configs, merge to Config.cfg
|
61
|
-
rescue StandardError =>
|
62
|
-
raise InvalidConfig, "Error loading config: #{
|
64
|
+
rescue StandardError => e
|
65
|
+
raise InvalidConfig, "Error loading config: #{e.message}"
|
63
66
|
end
|
64
67
|
|
65
68
|
raise NoConfig, 'edit ~/.config/oxidized/config' if asetus.create
|
data/lib/oxidized/core.rb
CHANGED
@@ -12,10 +12,18 @@ module Oxidized
|
|
12
12
|
Oxidized.mgr = Manager.new
|
13
13
|
Oxidized.hooks = HookManager.from_config(Oxidized.config)
|
14
14
|
nodes = Nodes.new
|
15
|
-
raise NoNodesFound, 'source returns no usable nodes' if nodes.
|
15
|
+
raise NoNodesFound, 'source returns no usable nodes' if nodes.empty?
|
16
16
|
|
17
17
|
@worker = Worker.new nodes
|
18
|
-
|
18
|
+
@need_reload = false
|
19
|
+
|
20
|
+
# If we receive a SIGHUP, queue a reload of the state
|
21
|
+
reload_proc = proc do
|
22
|
+
@need_reload = true
|
23
|
+
end
|
24
|
+
Signals.register_signal('HUP', reload_proc)
|
25
|
+
|
26
|
+
# Initialize REST API and webUI if requested
|
19
27
|
if Oxidized.config.rest?
|
20
28
|
begin
|
21
29
|
require 'oxidized/web'
|
@@ -31,9 +39,20 @@ module Oxidized
|
|
31
39
|
|
32
40
|
private
|
33
41
|
|
42
|
+
def reload
|
43
|
+
Oxidized.logger.info("Reloading node list and log files")
|
44
|
+
@worker.reload
|
45
|
+
Oxidized.logger.reopen
|
46
|
+
@need_reload = false
|
47
|
+
end
|
48
|
+
|
34
49
|
def run
|
35
50
|
Oxidized.logger.debug "lib/oxidized/core.rb: Starting the worker..."
|
36
|
-
|
51
|
+
loop do
|
52
|
+
reload if @need_reload
|
53
|
+
@worker.work
|
54
|
+
sleep Config::SLEEP
|
55
|
+
end
|
37
56
|
end
|
38
57
|
end
|
39
58
|
end
|
data/lib/oxidized/hook.rb
CHANGED
@@ -19,7 +19,7 @@ module Oxidized
|
|
19
19
|
# RegisteredHook is a container for a Hook instance
|
20
20
|
RegisteredHook = Struct.new(:name, :hook)
|
21
21
|
|
22
|
-
|
22
|
+
EVENTS = %i[
|
23
23
|
node_success
|
24
24
|
node_fail
|
25
25
|
post_store
|
@@ -32,9 +32,9 @@ module Oxidized
|
|
32
32
|
end
|
33
33
|
|
34
34
|
def register(event, name, hook_type, cfg)
|
35
|
-
unless
|
35
|
+
unless EVENTS.include? event
|
36
36
|
raise ArgumentError,
|
37
|
-
"unknown event #{event}, available: #{
|
37
|
+
"unknown event #{event}, available: #{EVENTS.join ','}"
|
38
38
|
end
|
39
39
|
|
40
40
|
Oxidized.mgr.add_hook(hook_type) || raise("cannot load hook '#{hook_type}', not found")
|
data/lib/oxidized/input/exec.rb
CHANGED
@@ -6,7 +6,7 @@ module Oxidized
|
|
6
6
|
|
7
7
|
def connect(node)
|
8
8
|
@node = node
|
9
|
-
@log = File.open(Oxidized::Config::
|
9
|
+
@log = File.open(Oxidized::Config::LOG + "/#{@node.ip}-exec", "w") if Oxidized.config.input.debug?
|
10
10
|
@node.model.cfg["exec"].each { |cb| instance_exec(&cb) }
|
11
11
|
end
|
12
12
|
|
data/lib/oxidized/input/ftp.rb
CHANGED
@@ -4,7 +4,7 @@ module Oxidized
|
|
4
4
|
require_relative 'cli'
|
5
5
|
|
6
6
|
class FTP < Input
|
7
|
-
|
7
|
+
RESCUE_FAIL = {
|
8
8
|
debug: [
|
9
9
|
# Net::SSH::Disconnect,
|
10
10
|
],
|
@@ -18,7 +18,7 @@ module Oxidized
|
|
18
18
|
def connect(node)
|
19
19
|
@node = node
|
20
20
|
@node.model.cfg['ftp'].each { |cb| instance_exec(&cb) }
|
21
|
-
@log = File.open(Oxidized::Config::
|
21
|
+
@log = File.open(Oxidized::Config::LOG + "/#{@node.ip}-ftp", 'w') if Oxidized.config.input.debug?
|
22
22
|
@ftp = Net::FTP.new(@node.ip)
|
23
23
|
@ftp.passive = Oxidized.config.input.ftp.passive
|
24
24
|
@ftp.login @node.auth[:username], @node.auth[:password]
|
data/lib/oxidized/input/http.rb
CHANGED
@@ -12,7 +12,7 @@ module Oxidized
|
|
12
12
|
@username = nil
|
13
13
|
@password = nil
|
14
14
|
@headers = {}
|
15
|
-
@log = File.open(Oxidized::Config::
|
15
|
+
@log = File.open(Oxidized::Config::LOG + "/#{@node.ip}-http", "w") if Oxidized.config.input.debug?
|
16
16
|
@node.model.cfg["http"].each { |cb| instance_exec(&cb) }
|
17
17
|
|
18
18
|
return true unless @main_page && defined?(login)
|
@@ -49,18 +49,42 @@ module Oxidized
|
|
49
49
|
def get_http(path)
|
50
50
|
schema = @secure ? "https://" : "http://"
|
51
51
|
uri = URI("#{schema}#{@node.ip}#{path}")
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
req.add_field(header, value)
|
56
|
-
end
|
52
|
+
|
53
|
+
Oxidized.logger.debug "Making request to: #{uri}"
|
54
|
+
|
57
55
|
ssl_verify = Oxidized.config.input.http.ssl_verify? ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
|
58
|
-
|
59
|
-
|
56
|
+
|
57
|
+
res = make_request(uri, ssl_verify)
|
58
|
+
|
59
|
+
if res.code == '401' && res['www-authenticate']&.include?('Digest')
|
60
|
+
uri.user = @username
|
61
|
+
uri.password = @password
|
62
|
+
Oxidized.logger.debug "Server requires Digest authentication"
|
63
|
+
auth = Net::HTTP::DigestAuth.new.auth_header(uri, res['www-authenticate'], 'GET')
|
64
|
+
|
65
|
+
res = make_request(uri, ssl_verify, 'Authorization' => auth)
|
66
|
+
elsif @username && @password
|
67
|
+
Oxidized.logger.debug "Falling back to Basic authentication"
|
68
|
+
res = make_request(uri, ssl_verify, 'Authorization' => basic_auth_header)
|
60
69
|
end
|
70
|
+
|
71
|
+
Oxidized.logger.debug "Response code: #{res.code}"
|
61
72
|
res.body
|
62
73
|
end
|
63
74
|
|
75
|
+
def make_request(uri, ssl_verify, extra_headers = {})
|
76
|
+
Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https", verify_mode: ssl_verify) do |http|
|
77
|
+
req = Net::HTTP::Get.new(uri)
|
78
|
+
@headers.merge(extra_headers).each { |header, value| req.add_field(header, value) }
|
79
|
+
Oxidized.logger.debug "Sending request with headers: #{@headers.merge(extra_headers)}"
|
80
|
+
http.request(req)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def basic_auth_header
|
85
|
+
"Basic " + ["#{@username}:#{@password}"].pack('m').delete("\r\n")
|
86
|
+
end
|
87
|
+
|
64
88
|
def log(str)
|
65
89
|
@log&.write(str)
|
66
90
|
end
|
data/lib/oxidized/input/input.rb
CHANGED
@@ -0,0 +1,52 @@
|
|
1
|
+
module Oxidized
|
2
|
+
require 'net/ssh'
|
3
|
+
require 'net/scp'
|
4
|
+
require 'timeout'
|
5
|
+
require_relative 'cli'
|
6
|
+
|
7
|
+
class SCP < Input
|
8
|
+
RESCUE_FAIL = {
|
9
|
+
debug: [
|
10
|
+
# Net::SSH::Disconnect,
|
11
|
+
],
|
12
|
+
warn: [
|
13
|
+
# RuntimeError,
|
14
|
+
# Net::SSH::AuthenticationFailed,
|
15
|
+
]
|
16
|
+
}.freeze
|
17
|
+
include Input::CLI
|
18
|
+
|
19
|
+
def connect(node)
|
20
|
+
@node = node
|
21
|
+
@node.model.cfg['scp'].each { |cb| instance_exec(&cb) }
|
22
|
+
@log = File.open(Oxidized::Config::LOG + "/#{@node.ip}-scp", 'w') if Oxidized.config.input.debug?
|
23
|
+
@ssh = Net::SSH.start(@node.ip, @node.auth[:username], password: @node.auth[:password])
|
24
|
+
connected?
|
25
|
+
end
|
26
|
+
|
27
|
+
def connected?
|
28
|
+
@ssh && (not @ssh.closed?)
|
29
|
+
end
|
30
|
+
|
31
|
+
def cmd(file)
|
32
|
+
Oxidized.logger.debug "SCP: #{file} @ #{@node.name}"
|
33
|
+
@ssh.scp.download!(file)
|
34
|
+
end
|
35
|
+
|
36
|
+
def send(my_proc)
|
37
|
+
my_proc.call
|
38
|
+
end
|
39
|
+
|
40
|
+
def output
|
41
|
+
""
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def disconnect
|
47
|
+
@ssh.close
|
48
|
+
ensure
|
49
|
+
@log.close if Oxidized.config.input.debug?
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/oxidized/input/ssh.rb
CHANGED
@@ -4,7 +4,7 @@ module Oxidized
|
|
4
4
|
require 'timeout'
|
5
5
|
require 'oxidized/input/cli'
|
6
6
|
class SSH < Input
|
7
|
-
|
7
|
+
RESCUE_FAIL = {
|
8
8
|
debug: [
|
9
9
|
Net::SSH::Disconnect
|
10
10
|
],
|
@@ -21,7 +21,7 @@ module Oxidized
|
|
21
21
|
@output = ''
|
22
22
|
@pty_options = { term: "vt100" }
|
23
23
|
@node.model.cfg['ssh'].each { |cb| instance_exec(&cb) }
|
24
|
-
@log = File.open(Oxidized::Config::
|
24
|
+
@log = File.open(Oxidized::Config::LOG + "/#{@node.ip}-ssh", 'w') if Oxidized.config.input.debug?
|
25
25
|
|
26
26
|
Oxidized.logger.debug "lib/oxidized/input/ssh.rb: Connecting to #{@node.name}"
|
27
27
|
@ssh = Net::SSH.start(@node.ip, @node.auth[:username], make_ssh_opts)
|
@@ -42,11 +42,13 @@ module Oxidized
|
|
42
42
|
|
43
43
|
def cmd(cmd, expect = node.prompt)
|
44
44
|
Oxidized.logger.debug "lib/oxidized/input/ssh.rb #{cmd} @ #{node.name} with expect: #{expect.inspect}"
|
45
|
-
if @exec
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
45
|
+
cmd_output = if @exec
|
46
|
+
@ssh.exec! cmd
|
47
|
+
else
|
48
|
+
cmd_shell(cmd, expect).gsub("\r\n", "\n")
|
49
|
+
end
|
50
|
+
# Make sure we return a String
|
51
|
+
cmd_output.to_s
|
50
52
|
end
|
51
53
|
|
52
54
|
def send(data)
|
@@ -66,6 +68,7 @@ module Oxidized
|
|
66
68
|
# if disconnect does not disconnect us, give up after timeout
|
67
69
|
Timeout.timeout(Oxidized.config.timeout) { @ssh.loop }
|
68
70
|
rescue Errno::ECONNRESET, Net::SSH::Disconnect, IOError
|
71
|
+
# These exceptions are intented and therefore not handled here
|
69
72
|
ensure
|
70
73
|
@log.close if Oxidized.config.input.debug?
|
71
74
|
(@ssh.close rescue true) unless @ssh.closed?
|
@@ -2,7 +2,7 @@ module Oxidized
|
|
2
2
|
require 'net/telnet'
|
3
3
|
require 'oxidized/input/cli'
|
4
4
|
class Telnet < Input
|
5
|
-
|
5
|
+
RESCUE_FAIL = {}.freeze
|
6
6
|
include Input::CLI
|
7
7
|
attr_reader :telnet
|
8
8
|
|
@@ -10,7 +10,7 @@ module Oxidized
|
|
10
10
|
@node = node
|
11
11
|
@timeout = Oxidized.config.timeout
|
12
12
|
@node.model.cfg['telnet'].each { |cb| instance_exec(&cb) }
|
13
|
-
@log = File.open(Oxidized::Config::
|
13
|
+
@log = File.open(Oxidized::Config::LOG + "/#{@node.ip}-telnet", 'w') if Oxidized.config.input.debug?
|
14
14
|
port = vars(:telnet_port) || 23
|
15
15
|
|
16
16
|
telnet_opts = {
|
@@ -64,6 +64,7 @@ module Oxidized
|
|
64
64
|
disconnect_cli
|
65
65
|
@telnet.close
|
66
66
|
rescue Errno::ECONNRESET
|
67
|
+
# This exception is intented and therefore not handled here
|
67
68
|
ensure
|
68
69
|
@log.close if Oxidized.config.input.debug?
|
69
70
|
(@telnet.close rescue true) unless @telnet.sock.closed?
|
data/lib/oxidized/input/tftp.rb
CHANGED
@@ -16,7 +16,7 @@ module Oxidized
|
|
16
16
|
@node = node
|
17
17
|
|
18
18
|
@node.model.cfg['tftp'].each { |cb| instance_exec(&cb) }
|
19
|
-
@log = File.open(Oxidized::Config::
|
19
|
+
@log = File.open(Oxidized::Config::LOG + "/#{@node.ip}-tftp", 'w') if Oxidized.config.input.debug?
|
20
20
|
@tftp = Net::TFTP.new @node.ip
|
21
21
|
end
|
22
22
|
|
data/lib/oxidized/jobs.rb
CHANGED
@@ -44,6 +44,14 @@ module Oxidized
|
|
44
44
|
@want = @max if @want > @max
|
45
45
|
end
|
46
46
|
|
47
|
+
def increment
|
48
|
+
# Increments the job count if safe to do so, which means:
|
49
|
+
# a) less threads running than the total amount of nodes
|
50
|
+
# b) we want less than the max specified number of threads
|
51
|
+
|
52
|
+
@want = [(@want + 1), @nodes.size, @max].min
|
53
|
+
end
|
54
|
+
|
47
55
|
def work
|
48
56
|
# if a) we want less or same amount of threads as we now running
|
49
57
|
# and b) we want less threads running than the total amount of nodes
|
@@ -51,7 +59,9 @@ module Oxidized
|
|
51
59
|
# then we want one more thread (rationale is to fix hanging thread causing HOLB)
|
52
60
|
return unless @want <= size && @want < @nodes.size
|
53
61
|
|
54
|
-
|
62
|
+
return unless @want <= size
|
63
|
+
|
64
|
+
increment if (Time.now.utc - @last) > MAX_INTER_JOB_GAP
|
55
65
|
end
|
56
66
|
end
|
57
67
|
end
|
data/lib/oxidized/manager.rb
CHANGED
@@ -33,30 +33,30 @@ module Oxidized
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def add_input(name)
|
36
|
-
loader @input, Config::
|
36
|
+
loader @input, Config::INPUT_DIR, "input", name
|
37
37
|
end
|
38
38
|
|
39
39
|
def add_output(name)
|
40
|
-
loader @output, Config::
|
40
|
+
loader @output, Config::OUTPUT_DIR, "output", name
|
41
41
|
end
|
42
42
|
|
43
43
|
def add_source(name)
|
44
|
-
loader @source, Config::
|
44
|
+
loader @source, Config::SOURCE_DIR, "source", name
|
45
45
|
end
|
46
46
|
|
47
47
|
def add_model(name)
|
48
|
-
loader @model, Config::
|
48
|
+
loader @model, Config::MODEL_DIR, "model", name
|
49
49
|
end
|
50
50
|
|
51
51
|
def add_hook(name)
|
52
|
-
loader @hook, Config::
|
52
|
+
loader @hook, Config::HOOK_DIR, "hook", name
|
53
53
|
end
|
54
54
|
|
55
55
|
private
|
56
56
|
|
57
57
|
# if local version of file exists, load it, else load global - return falsy value if nothing loaded
|
58
58
|
def loader(hash, global_dir, local_dir, name)
|
59
|
-
dir = File.join(Config::
|
59
|
+
dir = File.join(Config::ROOT, local_dir)
|
60
60
|
map = Manager.load(dir, name) if File.exist? File.join(dir, name + ".rb")
|
61
61
|
map ||= Manager.load(global_dir, name)
|
62
62
|
hash.merge!(map) if map
|
data/lib/oxidized/model/acos.rb
CHANGED
@@ -72,7 +72,7 @@ class ACOS < Oxidized::Model
|
|
72
72
|
out << "! partition: #{partition}"
|
73
73
|
arules.each do |name|
|
74
74
|
cmd("show aflex #{name} partition #{partition}") do |cfg|
|
75
|
-
content = cfg.split(
|
75
|
+
content = cfg.split("Content:").last.strip
|
76
76
|
out << "aflex create #{name}"
|
77
77
|
out << content
|
78
78
|
out << ".\n"
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class AddPack < Oxidized::Model
|
2
|
+
# Used in AddPack Voip, such as AP100B, AP100_G2, AP700, AP1000, AP1100F
|
3
|
+
|
4
|
+
using Refinements
|
5
|
+
PROMPT = /^.*[>#]\s?$/
|
6
|
+
|
7
|
+
expect /-- [Mm]ore --/ do |data, re|
|
8
|
+
send ' '
|
9
|
+
data.sub re, ''
|
10
|
+
end
|
11
|
+
|
12
|
+
prompt PROMPT
|
13
|
+
cmd 'enable'
|
14
|
+
|
15
|
+
cmd 'show running-config' do |cfg|
|
16
|
+
cfg.gsub! /^Building configuration.../, ''
|
17
|
+
cfg.gsub! /^*show running-config/, ''
|
18
|
+
cfg.gsub! PROMPT, ''
|
19
|
+
cfg
|
20
|
+
end
|
21
|
+
|
22
|
+
cfg :telnet do
|
23
|
+
username /[Ll]ogin:\s?/
|
24
|
+
password /[Pp]assword:\s?/
|
25
|
+
end
|
26
|
+
end
|