oxidized 0.29.1 → 0.30.0

Sign up to get free protection for your applications and to get access to all the features.
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