oxidized 0.29.1 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/codeql.yml +4 -4
  3. data/.github/workflows/publishdocker.yml +3 -0
  4. data/.github/workflows/ruby.yml +1 -1
  5. data/.github/workflows/stale.yml +4 -1
  6. data/.rubocop.yml +6 -3
  7. data/.rubocop_todo.yml +10 -93
  8. data/CHANGELOG.md +66 -4
  9. data/CONTRIBUTING.md +174 -0
  10. data/Dockerfile +12 -1
  11. data/README.md +18 -36
  12. data/bin/oxidized +2 -5
  13. data/docs/Hooks.md +37 -1
  14. data/docs/Model-Notes/APC_AOS.md +52 -0
  15. data/docs/Model-Notes/FSOS.md +11 -0
  16. data/docs/Model-Notes/FortiOS.md +28 -0
  17. data/docs/Model-Notes/README.md +1 -20
  18. data/docs/Model-Notes/XGS4600-Zyxel.md +5 -0
  19. data/docs/Ruby-API.md +1 -1
  20. data/docs/Sources.md +13 -0
  21. data/docs/Supported-OS-Types.md +178 -270
  22. data/docs/Troubleshooting.md +16 -0
  23. data/examples/podman-compose/Makefile +61 -0
  24. data/examples/podman-compose/README.md +58 -0
  25. data/examples/podman-compose/docker-compose.yml +21 -0
  26. data/examples/podman-compose/model-simulation/Dockerfile-model +13 -0
  27. data/examples/podman-compose/model-simulation/asternos.sh +34 -0
  28. data/examples/podman-compose/oxidized-config/.gitignore +8 -0
  29. data/examples/podman-compose/oxidized-config/config +46 -0
  30. data/examples/podman-compose/oxidized-config/router.db +1 -0
  31. data/examples/podman-compose/oxidized-ssh/README.md +14 -0
  32. data/extra/rest_client.rb +2 -2
  33. data/extra/syslog.rb +2 -2
  34. data/lib/oxidized/cli.rb +6 -4
  35. data/lib/oxidized/config.rb +17 -14
  36. data/lib/oxidized/core.rb +22 -3
  37. data/lib/oxidized/hook.rb +3 -3
  38. data/lib/oxidized/input/exec.rb +1 -1
  39. data/lib/oxidized/input/ftp.rb +2 -2
  40. data/lib/oxidized/input/http.rb +32 -8
  41. data/lib/oxidized/input/input.rb +1 -1
  42. data/lib/oxidized/input/scp.rb +52 -0
  43. data/lib/oxidized/input/ssh.rb +10 -7
  44. data/lib/oxidized/input/telnet.rb +3 -2
  45. data/lib/oxidized/input/tftp.rb +1 -1
  46. data/lib/oxidized/jobs.rb +11 -1
  47. data/lib/oxidized/manager.rb +6 -6
  48. data/lib/oxidized/model/acos.rb +1 -1
  49. data/lib/oxidized/model/addpack.rb +26 -0
  50. data/lib/oxidized/model/adtran.rb +5 -1
  51. data/lib/oxidized/model/adva.rb +2 -2
  52. data/lib/oxidized/model/aoscx.rb +2 -1
  53. data/lib/oxidized/model/apc_aos.rb +2 -1
  54. data/lib/oxidized/model/aricentiss.rb +7 -0
  55. data/lib/oxidized/model/asternos.rb +22 -0
  56. data/lib/oxidized/model/asyncos.rb +2 -2
  57. data/lib/oxidized/model/awplus.rb +2 -2
  58. data/lib/oxidized/model/bdcom.rb +1 -0
  59. data/lib/oxidized/model/c4cmts.rb +1 -2
  60. data/lib/oxidized/model/ciscosma.rb +1 -1
  61. data/lib/oxidized/model/ciscosmb.rb +6 -1
  62. data/lib/oxidized/model/comware.rb +2 -2
  63. data/lib/oxidized/model/cumulus.rb +1 -1
  64. data/lib/oxidized/model/dellx.rb +1 -1
  65. data/lib/oxidized/model/dlink.rb +4 -2
  66. data/lib/oxidized/model/dlinknextgen.rb +51 -0
  67. data/lib/oxidized/model/dnos.rb +3 -0
  68. data/lib/oxidized/model/edgecos.rb +1 -1
  69. data/lib/oxidized/model/eltex.rb +2 -0
  70. data/lib/oxidized/model/enterasys800.rb +1 -1
  71. data/lib/oxidized/model/eos.rb +1 -1
  72. data/lib/oxidized/model/firebrick.rb +2 -2
  73. data/lib/oxidized/model/firewareos.rb +1 -1
  74. data/lib/oxidized/model/fortios.rb +9 -2
  75. data/lib/oxidized/model/fsos.rb +44 -0
  76. data/lib/oxidized/model/ios.rb +1 -1
  77. data/lib/oxidized/model/iosxr.rb +2 -2
  78. data/lib/oxidized/model/junos.rb +3 -2
  79. data/lib/oxidized/model/mimosab11.rb +34 -0
  80. data/lib/oxidized/model/ml66.rb +33 -0
  81. data/lib/oxidized/model/model.rb +3 -3
  82. data/lib/oxidized/model/netgear.rb +1 -1
  83. data/lib/oxidized/model/netscaler.rb +1 -1
  84. data/lib/oxidized/model/nxos.rb +4 -3
  85. data/lib/oxidized/model/ocnos.rb +42 -0
  86. data/lib/oxidized/model/onefinity.rb +18 -0
  87. data/lib/oxidized/model/openbsd.rb +1 -1
  88. data/lib/oxidized/model/opengear.rb +36 -1
  89. data/lib/oxidized/model/opnsense.rb +1 -1
  90. data/lib/oxidized/model/panos.rb +2 -0
  91. data/lib/oxidized/model/pfsense.rb +1 -0
  92. data/lib/oxidized/model/procurve.rb +3 -1
  93. data/lib/oxidized/model/rgos.rb +33 -0
  94. data/lib/oxidized/model/routeros.rb +10 -8
  95. data/lib/oxidized/model/slxos.rb +2 -2
  96. data/lib/oxidized/model/sonicos.rb +18 -17
  97. data/lib/oxidized/model/sros.rb +3 -3
  98. data/lib/oxidized/model/tplink.rb +4 -3
  99. data/lib/oxidized/model/truenas.rb +2 -1
  100. data/lib/oxidized/model/vrp.rb +3 -1
  101. data/lib/oxidized/model/vyatta.rb +6 -0
  102. data/lib/oxidized/model/zynos.rb +67 -3
  103. data/lib/oxidized/model/zynosadsl.rb +14 -0
  104. data/lib/oxidized/model/zynosgs.rb +2 -0
  105. data/lib/oxidized/model/zynosmgs.rb +32 -0
  106. data/lib/oxidized/node.rb +7 -7
  107. data/lib/oxidized/nodes.rb +17 -12
  108. data/lib/oxidized/output/file.rb +1 -1
  109. data/lib/oxidized/output/git.rb +5 -3
  110. data/lib/oxidized/output/gitcrypt.rb +4 -3
  111. data/lib/oxidized/signals.rb +44 -0
  112. data/lib/oxidized/source/csv.rb +1 -1
  113. data/lib/oxidized/source/http.rb +26 -5
  114. data/lib/oxidized/source/source.rb +2 -2
  115. data/lib/oxidized/source/sql.rb +3 -3
  116. data/lib/oxidized/version.rb +2 -2
  117. data/lib/oxidized/worker.rb +8 -1
  118. data/lib/oxidized.rb +3 -2
  119. data/lib/refinements.rb +1 -1
  120. data/oxidized.gemspec +6 -3
  121. 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,8 @@
1
+ # Ignore local configurations, which will override the git config
2
+ config.local
3
+ router.db.local
4
+
5
+ # Ignore logs, retrieved configs...
6
+ configs/
7
+ crash
8
+ logs/
@@ -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 => error
18
- raise InvalidConfig, "Error loading config: #{error.message}"
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 => error
47
- raise InvalidConfig, "Error loading config: #{error.message}"
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 => error
15
- crash error
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::Crash}"
33
- File.open Config::Crash, 'w' do |file|
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
@@ -4,18 +4,20 @@ module Oxidized
4
4
  class InvalidConfig < OxidizedError; end
5
5
 
6
6
  class Config
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
- InputDir = File.join Directory, %w[lib oxidized input]
11
- OutputDir = File.join Directory, %w[lib oxidized output]
12
- ModelDir = File.join Directory, %w[lib oxidized model]
13
- SourceDir = File.join Directory, %w[lib oxidized source]
14
- HookDir = File.join Directory, %w[lib oxidized hook]
15
- Sleep = 1
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
- asetus = Asetus.new(name: 'oxidized', load: false, key_to_s: true, usrdir: Oxidized::Config::Root)
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::Root, 'pid')
42
+ asetus.default.pid = File.join(Oxidized::Config::ROOT, 'pid')
40
43
 
41
- asetus.default.crash.directory = File.join(Oxidized::Config::Root, 'crashes')
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 => error
62
- raise InvalidConfig, "Error loading config: #{error.message}"
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.size.zero?
15
+ raise NoNodesFound, 'source returns no usable nodes' if nodes.empty?
16
16
 
17
17
  @worker = Worker.new nodes
18
- trap('HUP') { nodes.load }
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
- @worker.work while sleep Config::Sleep
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
- Events = %i[
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 Events.include? event
35
+ unless EVENTS.include? event
36
36
  raise ArgumentError,
37
- "unknown event #{event}, available: #{Events.join ','}"
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")
@@ -6,7 +6,7 @@ module Oxidized
6
6
 
7
7
  def connect(node)
8
8
  @node = node
9
- @log = File.open(Oxidized::Config::Log + "/#{@node.ip}-exec", "w") if Oxidized.config.input.debug?
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
 
@@ -4,7 +4,7 @@ module Oxidized
4
4
  require_relative 'cli'
5
5
 
6
6
  class FTP < Input
7
- RescueFail = {
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::Log + "/#{@node.ip}-ftp", 'w') if Oxidized.config.input.debug?
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]
@@ -12,7 +12,7 @@ module Oxidized
12
12
  @username = nil
13
13
  @password = nil
14
14
  @headers = {}
15
- @log = File.open(Oxidized::Config::Log + "/#{@node.ip}-http", "w") if Oxidized.config.input.debug?
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
- req = Net::HTTP::Get.new(uri)
53
- req.basic_auth @username, @password unless @username.nil?
54
- @headers.each do |header, value|
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
- res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https", verify_mode: ssl_verify) do |http|
59
- http.request(req)
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
@@ -4,7 +4,7 @@ module Oxidized
4
4
  class Input
5
5
  include Oxidized::Config::Vars
6
6
 
7
- RescueFail = {
7
+ RESCUE_FAIL = {
8
8
  debug: [
9
9
  Errno::ECONNREFUSED
10
10
  ],
@@ -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
@@ -4,7 +4,7 @@ module Oxidized
4
4
  require 'timeout'
5
5
  require 'oxidized/input/cli'
6
6
  class SSH < Input
7
- RescueFail = {
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::Log + "/#{@node.ip}-ssh", 'w') if Oxidized.config.input.debug?
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
- @ssh.exec! cmd
47
- else
48
- cmd_shell(cmd, expect).gsub(/\r\n/, "\n")
49
- end
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
- RescueFail = {}.freeze
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::Log + "/#{@node.ip}-telnet", 'w') if Oxidized.config.input.debug?
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?
@@ -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::Log + "/#{@node.ip}-tftp", 'w') if Oxidized.config.input.debug?
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
- @want += 1 if (Time.now.utc - @last) > MAX_INTER_JOB_GAP
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
@@ -33,30 +33,30 @@ module Oxidized
33
33
  end
34
34
 
35
35
  def add_input(name)
36
- loader @input, Config::InputDir, "input", name
36
+ loader @input, Config::INPUT_DIR, "input", name
37
37
  end
38
38
 
39
39
  def add_output(name)
40
- loader @output, Config::OutputDir, "output", name
40
+ loader @output, Config::OUTPUT_DIR, "output", name
41
41
  end
42
42
 
43
43
  def add_source(name)
44
- loader @source, Config::SourceDir, "source", name
44
+ loader @source, Config::SOURCE_DIR, "source", name
45
45
  end
46
46
 
47
47
  def add_model(name)
48
- loader @model, Config::ModelDir, "model", name
48
+ loader @model, Config::MODEL_DIR, "model", name
49
49
  end
50
50
 
51
51
  def add_hook(name)
52
- loader @hook, Config::HookDir, "hook", name
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::Root, local_dir)
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
@@ -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(/Content:/).last.strip
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