oxidized 0.30.1 → 0.32.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (111) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +3 -4
  3. data/.github/workflows/stale.yml +4 -2
  4. data/.rubocop.yml +18 -3
  5. data/.rubocop_todo.yml +4 -11
  6. data/CHANGELOG.md +93 -1
  7. data/CONTRIBUTING.md +5 -0
  8. data/Dockerfile +84 -20
  9. data/README.md +5 -21
  10. data/Rakefile +31 -2
  11. data/docs/Configuration.md +50 -14
  12. data/docs/Creating-Models.md +75 -4
  13. data/docs/DeviceSimulation.md +184 -0
  14. data/docs/Hooks.md +39 -5
  15. data/docs/Issues.md +97 -0
  16. data/docs/Model-Notes/APC_AOS.md +29 -16
  17. data/docs/Model-Notes/Cumulus.md +5 -0
  18. data/docs/Model-Notes/FSOS.md +6 -0
  19. data/docs/Model-Notes/FortiOS.md +21 -5
  20. data/docs/Model-Notes/HPEAruba.md +31 -0
  21. data/docs/Model-Notes/OS6.md +10 -0
  22. data/docs/Model-Notes/RouterOS.md +15 -0
  23. data/docs/Model-Notes/SikluMHTG.md +7 -0
  24. data/docs/ModelUnitTests.md +186 -0
  25. data/docs/Outputs.md +2 -0
  26. data/docs/Release.md +18 -15
  27. data/docs/Sources.md +21 -0
  28. data/docs/Supported-OS-Types.md +14 -7
  29. data/docs/Troubleshooting.md +35 -0
  30. data/examples/podman-compose/Makefile +59 -17
  31. data/examples/podman-compose/README.md +63 -27
  32. data/examples/podman-compose/docker-compose.yml +11 -2
  33. data/examples/podman-compose/gitserver/.gitignore +1 -0
  34. data/examples/podman-compose/gitserver/Dockerfile +14 -0
  35. data/examples/podman-compose/model-simulation/Dockerfile-model +1 -1
  36. data/examples/podman-compose/model-simulation/asternos.sh +2 -0
  37. data/examples/podman-compose/oxidized-config/.gitignore +2 -0
  38. data/examples/podman-compose/oxidized-config/config +1 -1
  39. data/examples/podman-compose/oxidized-config/config_csv-file +46 -0
  40. data/examples/podman-compose/oxidized-config/config_csv-gitserver +56 -0
  41. data/examples/podman-compose/oxidized-ssh/.gitignore +1 -0
  42. data/extra/device2yaml.rb +245 -0
  43. data/extra/gitdiff-msteams.sh +32 -5
  44. data/extra/nagios_check_failing_nodes.rb +1 -1
  45. data/extra/rest_client.rb +1 -1
  46. data/lib/oxidized/config.rb +8 -2
  47. data/lib/oxidized/hook/githubrepo.rb +37 -7
  48. data/lib/oxidized/hook/slackdiff.rb +29 -7
  49. data/lib/oxidized/input/http.rb +1 -0
  50. data/lib/oxidized/input/ssh.rb +13 -5
  51. data/lib/oxidized/input/telnet.rb +1 -1
  52. data/lib/oxidized/manager.rb +17 -16
  53. data/lib/oxidized/model/aos7.rb +2 -0
  54. data/lib/oxidized/model/aoscx.rb +16 -2
  55. data/lib/oxidized/model/aosw.rb +8 -2
  56. data/lib/oxidized/model/apc_aos.rb +1 -1
  57. data/lib/oxidized/model/arubainstant.rb +90 -0
  58. data/lib/oxidized/model/asa.rb +2 -1
  59. data/lib/oxidized/model/asyncos.rb +1 -1
  60. data/lib/oxidized/model/audiocodes.rb +2 -2
  61. data/lib/oxidized/model/cnos.rb +13 -10
  62. data/lib/oxidized/model/cumulus.rb +19 -2
  63. data/lib/oxidized/model/dlink.rb +1 -0
  64. data/lib/oxidized/model/dlinknextgen.rb +3 -0
  65. data/lib/oxidized/model/edgecos.rb +2 -1
  66. data/lib/oxidized/model/enterprise_sonic.rb +46 -0
  67. data/lib/oxidized/model/eos.rb +2 -0
  68. data/lib/oxidized/model/f5os.rb +17 -0
  69. data/lib/oxidized/model/firewareos.rb +10 -1
  70. data/lib/oxidized/model/fortios.rb +24 -1
  71. data/lib/oxidized/model/fsos.rb +5 -1
  72. data/lib/oxidized/model/garderos.rb +43 -0
  73. data/lib/oxidized/model/h3c.rb +1 -1
  74. data/lib/oxidized/model/ibos.rb +1 -0
  75. data/lib/oxidized/model/ios.rb +20 -12
  76. data/lib/oxidized/model/iosxr.rb +1 -1
  77. data/lib/oxidized/model/junos.rb +1 -1
  78. data/lib/oxidized/model/kornfeldos.rb +33 -0
  79. data/lib/oxidized/model/lenovonos.rb +2 -0
  80. data/lib/oxidized/model/linuxgeneric.rb +1 -1
  81. data/lib/oxidized/model/model.rb +2 -2
  82. data/lib/oxidized/model/netgear.rb +1 -1
  83. data/lib/oxidized/model/nodegrid.rb +1 -1
  84. data/lib/oxidized/model/nsxdfw.rb +30 -0
  85. data/lib/oxidized/model/nxos.rb +2 -1
  86. data/lib/oxidized/model/os6.rb +48 -0
  87. data/lib/oxidized/model/rgos.rb +1 -1
  88. data/lib/oxidized/model/riverbed.rb +104 -0
  89. data/lib/oxidized/model/routeros.rb +2 -2
  90. data/lib/oxidized/model/saos.rb +18 -1
  91. data/lib/oxidized/model/siklumhtg.rb +22 -0
  92. data/lib/oxidized/model/sonicos.rb +8 -2
  93. data/lib/oxidized/model/tplink.rb +1 -0
  94. data/lib/oxidized/model/uplinkolt.rb +46 -0
  95. data/lib/oxidized/model/vyatta.rb +2 -2
  96. data/lib/oxidized/model/xos.rb +7 -0
  97. data/lib/oxidized/node.rb +30 -18
  98. data/lib/oxidized/nodes.rb +13 -5
  99. data/lib/oxidized/output/file.rb +45 -42
  100. data/lib/oxidized/output/git.rb +185 -160
  101. data/lib/oxidized/output/gitcrypt.rb +188 -186
  102. data/lib/oxidized/output/http.rb +53 -51
  103. data/lib/oxidized/output/output.rb +6 -4
  104. data/lib/oxidized/source/csv.rb +44 -49
  105. data/lib/oxidized/source/http.rb +63 -81
  106. data/lib/oxidized/source/jsonfile.rb +63 -0
  107. data/lib/oxidized/source/source.rb +73 -18
  108. data/lib/oxidized/source/sql.rb +66 -59
  109. data/lib/oxidized/version.rb +2 -2
  110. data/oxidized.gemspec +25 -18
  111. metadata +115 -21
@@ -15,12 +15,31 @@
15
15
  # timeout: 120
16
16
  #
17
17
  # Add webhook to your MS Teams channel and set the next variable to the full url
18
+ # If INCLUDE_GITHUB_LINK is set to true, there will be a button that links to GITURL in the bottom of each teams post. the commit id is added at the end of the url.
19
+ # MAXSIZE is set to respect the 28 KB limit for teams webhooks, but you might need to change it if you modify the Adaptive Card
18
20
 
19
21
  weburl="https://contoso.webhook.office.com/webhookb2/etc etc etc"
22
+ GITURL="https://github.example.com/My-org/oxidized/commit/"
23
+ INCLUDE_GITHUB_LINK=false
24
+ # Max size for summary text.
25
+ MAXSIZE=24500
26
+
27
+ if [ "$INCLUDE_GITHUB_LINK" = true ]; then
28
+ github_action=",
29
+ { \"type\": \"ActionSet\",
30
+ \"actions\":
31
+ [
32
+ {
33
+ \"type\": \"Action.OpenUrl\",
34
+ \"title\": \"Click to see diff in github\",
35
+ \"url\": \"${GITURL}${OX_REPO_COMMITREF}\"
36
+ }
37
+ ]
38
+ }"
39
+ fi
20
40
 
21
41
  postdata()
22
42
  {
23
- COMMIT=$(git --bare --git-dir="${OX_REPO_NAME}" show --pretty='' --no-color "${OX_REPO_COMMITREF}" | jq --raw-input --slurp --compact-output)
24
43
  cat <<EOF
25
44
  {
26
45
  "type":"message",
@@ -77,7 +96,7 @@ postdata()
77
96
  "size": "small"
78
97
  }
79
98
  ]
80
- }
99
+ }${github_action}
81
100
  ]
82
101
  }
83
102
  }
@@ -86,6 +105,14 @@ postdata()
86
105
  EOF
87
106
  }
88
107
 
89
- curl -i \
90
- -H "Content-Type: application/json" \
91
- -X POST --data "$(postdata)" "${weburl}"
108
+ COMMIT=$(git --bare --git-dir="${OX_REPO_NAME}" show --pretty='' --no-color "${OX_REPO_COMMITREF}" | jq --raw-input --slurp --compact-output)
109
+ URL=""
110
+
111
+ size=$(postdata | wc -c)
112
+ if [ "$size" -gt "$MAXSIZE" ]; then
113
+ COMMIT=$(git --bare --git-dir="${OX_REPO_NAME}" show --pretty='' --no-color "${OX_REPO_COMMITREF}" | head -c $MAXSIZE)
114
+ COMMIT+="$NEWLINE...$NEWLINE Shortened because of length"
115
+ COMMIT=$(echo "${COMMIT}" | jq --raw-input --slurp --compact-output )
116
+ fi
117
+
118
+ curl -i -H "Content-Type: application/json" -X POST --data "$(postdata)" "${weburl}"
@@ -10,7 +10,7 @@ pending = false
10
10
  critical_nodes = []
11
11
  pending_nodes = []
12
12
 
13
- json = JSON.parse(open("http://localhost:8888/nodes.json").read)
13
+ json = JSON.parse(URI("http://localhost:8888/nodes.json").open(&:read))
14
14
  json.each do |node|
15
15
  next if !ARGV.empty? && (ARGV[0] != node['name'])
16
16
 
data/extra/rest_client.rb CHANGED
@@ -6,7 +6,7 @@ module Oxidized
6
6
  require 'asetus'
7
7
 
8
8
  class Config
9
- Root = Root = ENV['OXIDIZED_HOME'] || File.join(Dir.home, '.config', 'oxidized')
9
+ Root = ENV['OXIDIZED_HOME'] || File.join(Dir.home, '.config', 'oxidized')
10
10
  end
11
11
 
12
12
  CFGS = Asetus.new name: 'oxidized', load: false, key_to_s: true
@@ -17,7 +17,9 @@ module Oxidized
17
17
  def self.load(cmd_opts = {})
18
18
  usrdir = File.expand_path(cmd_opts[:home_dir] || Oxidized::Config::ROOT)
19
19
  cfgfile = cmd_opts[:config_file] || 'config'
20
- asetus = Asetus.new(name: 'oxidized', load: false, key_to_s: true, usrdir: usrdir, cfgfile: cfgfile)
20
+ # configuration file with full path as a class instance variable
21
+ @configfile = File.join(usrdir, cfgfile)
22
+ asetus = Asetus.new(name: 'oxidized', load: false, usrdir: usrdir, cfgfile: cfgfile)
21
23
  Oxidized.asetus = asetus
22
24
 
23
25
  asetus.default.username = 'username'
@@ -65,13 +67,17 @@ module Oxidized
65
67
  raise InvalidConfig, "Error loading config: #{e.message}"
66
68
  end
67
69
 
68
- raise NoConfig, 'edit ~/.config/oxidized/config' if asetus.create
70
+ raise NoConfig, "edit #{@configfile}" if asetus.create
69
71
 
70
72
  # override if comand line flag given
71
73
  asetus.cfg.debug = cmd_opts[:debug] if cmd_opts[:debug]
72
74
 
73
75
  asetus
74
76
  end
77
+
78
+ class << self
79
+ attr_reader :configfile
80
+ end
75
81
  end
76
82
 
77
83
  class << self
@@ -1,15 +1,21 @@
1
+ require 'rugged'
2
+
1
3
  class GithubRepo < Oxidized::Hook
2
4
  def validate_cfg!
3
5
  raise KeyError, 'hook.remote_repo is required' unless cfg.has_key?('remote_repo')
4
6
  end
5
7
 
6
8
  def run_hook(ctx)
9
+ unless ctx.node.repo
10
+ log "Oxidized output is not git, can't push to remote", :error
11
+ return
12
+ end
7
13
  repo = Rugged::Repository.new(ctx.node.repo)
8
14
  creds = credentials(ctx.node)
9
15
  url = remote_repo(ctx.node)
10
16
 
11
17
  if url.nil? || url.empty?
12
- log "No repository defined for #{ctx.node.group}/#{ctx.node.name}", :debug
18
+ log "No repository defined for #{ctx.node.group}/#{ctx.node.name}", :error
13
19
  return
14
20
  end
15
21
 
@@ -23,21 +29,39 @@ class GithubRepo < Oxidized::Hook
23
29
  end
24
30
  remote = repo.remotes['origin']
25
31
 
26
- fetch_and_merge_remote(repo, creds)
27
-
28
- remote.push([repo.head.name], credentials: creds)
32
+ begin
33
+ fetch_and_merge_remote(repo, creds)
34
+ remote.push([repo.head.name], credentials: creds)
35
+ rescue Rugged::NetworkError => e
36
+ if e.message == 'unsupported URL protocol'
37
+ log "Rugged does not support the git URL '#{url}'.", :warn
38
+ unless Rugged.features.include?(:ssh)
39
+ log 'You may need to install Rugged with ssh support ' \
40
+ '(gem install rugged -- --with-ssh)', :warn
41
+ end
42
+ end
43
+ # re-raise exception for the calling method
44
+ raise
45
+ end
29
46
  end
30
47
 
31
48
  def fetch_and_merge_remote(repo, creds)
32
49
  result = repo.fetch('origin', [repo.head.name], credentials: creds)
33
50
  log result.inspect, :debug
34
51
 
35
- unless result[:total_deltas].positive?
36
- log "nothing received after fetch", :debug
52
+ their_branch = remote_branch(repo)
53
+
54
+ unless their_branch
55
+ log 'remote branch does not exist yet, nothing to merge', :debug
37
56
  return
38
57
  end
39
58
 
40
- their_branch = repo.branches["origin/master"]
59
+ result = repo.merge_analysis(their_branch.target_id)
60
+
61
+ if result.include? :up_to_date
62
+ log 'nothing to merge', :debug
63
+ return
64
+ end
41
65
 
42
66
  log "merging fetched branch #{their_branch.name}", :debug
43
67
 
@@ -97,4 +121,10 @@ class GithubRepo < Oxidized::Hook
97
121
  cfg.remote_repo[node.group].url
98
122
  end
99
123
  end
124
+
125
+ # Returns a Rugged::Branch to the remote branch or nil if it doen't exist
126
+ def remote_branch(repo)
127
+ head_branch = repo.branches[repo.head.name]
128
+ repo.branches['origin/' + head_branch.name]
129
+ end
100
130
  end
@@ -1,4 +1,6 @@
1
- require 'slack'
1
+ require 'slack_ruby_client'
2
+ require 'uri'
3
+ require 'net/http'
2
4
 
3
5
  # defaults to posting a diff, if messageformat is supplied them a message will be posted too
4
6
  # diff defaults to true
@@ -9,6 +11,30 @@ class SlackDiff < Oxidized::Hook
9
11
  raise KeyError, 'hook.channel is required' unless cfg.has_key?('channel')
10
12
  end
11
13
 
14
+ def slack_upload(client, title, content, channel)
15
+ log "Posting diff as snippet to #{channel}"
16
+ upload_dest = client.files_getUploadURLExternal(filename: "change",
17
+ length: content.length,
18
+ snippet_type: "diff")
19
+ file_uri = URI.parse(upload_dest[:upload_url])
20
+
21
+ http = Net::HTTP.new(file_uri.host, file_uri.port)
22
+ http.use_ssl = true
23
+
24
+ request = Net::HTTP::Post.new(file_uri.request_uri, { Host: file_uri.host })
25
+ request.body = content
26
+ response = http.request(request)
27
+
28
+ raise 'Slack file upload failed' unless response.is_a? Net::HTTPSuccess
29
+
30
+ files = [{
31
+ id: upload_dest[:file_id],
32
+ title: title
33
+ }]
34
+ client.files_completeUploadExternal(channel_id: channel,
35
+ files: files.to_json)
36
+ end
37
+
12
38
  def run_hook(ctx)
13
39
  return unless ctx.node
14
40
  return unless ctx.event.to_s == "post_store"
@@ -26,12 +52,8 @@ class SlackDiff < Oxidized::Hook
26
52
  diff = gitoutput.get_diff ctx.node, ctx.node.group, ctx.commitref, nil
27
53
  unless diff == "no diffs"
28
54
  title = "#{ctx.node.name} #{ctx.node.group} #{ctx.node.model.class.name.to_s.downcase}"
29
- log "Posting diff as snippet to #{cfg.channel}"
30
- client.files_upload(channels: cfg.channel, as_user: true,
31
- content: diff[:patch].lines.to_a[4..-1].join,
32
- filetype: "diff",
33
- title: title,
34
- filename: "change")
55
+ content = diff[:patch].lines.to_a[4..-1].join
56
+ slack_upload(client, title, content, cfg.channel)
35
57
  end
36
58
  end
37
59
  # message custom formatted - optional
@@ -2,6 +2,7 @@ module Oxidized
2
2
  require "oxidized/input/cli"
3
3
  require "net/http"
4
4
  require "json"
5
+ require "net/http/digest_auth"
5
6
 
6
7
  class HTTP < Input
7
8
  include Input::CLI
@@ -18,10 +18,14 @@ module Oxidized
18
18
 
19
19
  def connect(node)
20
20
  @node = node
21
- @output = ''
21
+ @output = String.new('')
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
+ if Oxidized.config.input.debug?
25
+ logfile = Oxidized::Config::LOG + "/#{@node.ip}-ssh"
26
+ @log = File.open(logfile, 'w')
27
+ Oxidized.logger.debug "lib/oxidized/input/ssh.rb: I/O Debuging to #{logfile}"
28
+ end
25
29
 
26
30
  Oxidized.logger.debug "lib/oxidized/input/ssh.rb: Connecting to #{@node.name}"
27
31
  @ssh = Net::SSH.start(@node.ip, @node.auth[:username], make_ssh_opts)
@@ -41,7 +45,11 @@ module Oxidized
41
45
  end
42
46
 
43
47
  def cmd(cmd, expect = node.prompt)
44
- Oxidized.logger.debug "lib/oxidized/input/ssh.rb #{cmd} @ #{node.name} with expect: #{expect.inspect}"
48
+ Oxidized.logger.debug "lib/oxidized/input/ssh.rb #{cmd.dump} @ #{node.name} with expect: #{expect.inspect}"
49
+ if Oxidized.config.input.debug?
50
+ @log.puts "sent #{cmd.dump}"
51
+ @log.flush
52
+ end
45
53
  cmd_output = if @exec
46
54
  @ssh.exec! cmd
47
55
  else
@@ -78,7 +86,7 @@ module Oxidized
78
86
  @ses = ssh.open_channel do |ch|
79
87
  ch.on_data do |_ch, data|
80
88
  if Oxidized.config.input.debug?
81
- @log.print data
89
+ @log.puts "received #{data.dump}"
82
90
  @log.flush
83
91
  end
84
92
  @output << data
@@ -101,7 +109,7 @@ module Oxidized
101
109
  end
102
110
 
103
111
  def cmd_shell(cmd, expect_re)
104
- @output = ''
112
+ @output = String.new('')
105
113
  @ses.send_data cmd + "\n"
106
114
  @ses.process
107
115
  expect expect_re if expect_re
@@ -63,7 +63,7 @@ module Oxidized
63
63
  def disconnect
64
64
  disconnect_cli
65
65
  @telnet.close
66
- rescue Errno::ECONNRESET
66
+ rescue Errno::ECONNRESET, IOError
67
67
  # This exception is intented and therefore not handled here
68
68
  ensure
69
69
  @log.close if Oxidized.config.input.debug?
@@ -5,15 +5,16 @@ module Oxidized
5
5
  require 'oxidized/source/source'
6
6
  class Manager
7
7
  class << self
8
- def load(dir, file)
8
+ def load(dir, file, namespace)
9
9
  require File.join dir, file + '.rb'
10
- klass = nil
11
- [Oxidized, Object].each do |mod|
12
- klass = mod.constants.find { |const| const.to_s.casecmp(file).zero? }
13
- klass ||= mod.constants.find { |const| const.to_s.downcase == 'oxidized' + file.downcase }
14
- klass = mod.const_get klass if klass
15
- break if klass
16
- end
10
+
11
+ # Search the object to load in namespace
12
+ klass = namespace.constants.find { |const| const.to_s.casecmp(file).zero? }
13
+
14
+ return false unless klass
15
+
16
+ klass = namespace.const_get klass
17
+
17
18
  i = klass.new
18
19
  i.setup if i.respond_to? :setup
19
20
  { file => klass }
@@ -33,32 +34,32 @@ module Oxidized
33
34
  end
34
35
 
35
36
  def add_input(name)
36
- loader @input, Config::INPUT_DIR, "input", name
37
+ loader @input, Config::INPUT_DIR, "input", name, Oxidized
37
38
  end
38
39
 
39
40
  def add_output(name)
40
- loader @output, Config::OUTPUT_DIR, "output", name
41
+ loader @output, Config::OUTPUT_DIR, "output", name, Oxidized::Output
41
42
  end
42
43
 
43
44
  def add_source(name)
44
- loader @source, Config::SOURCE_DIR, "source", name
45
+ loader @source, Config::SOURCE_DIR, "source", name, Oxidized::Source
45
46
  end
46
47
 
47
48
  def add_model(name)
48
- loader @model, Config::MODEL_DIR, "model", name
49
+ loader @model, Config::MODEL_DIR, "model", name, Object
49
50
  end
50
51
 
51
52
  def add_hook(name)
52
- loader @hook, Config::HOOK_DIR, "hook", name
53
+ loader @hook, Config::HOOK_DIR, "hook", name, Object
53
54
  end
54
55
 
55
56
  private
56
57
 
57
58
  # if local version of file exists, load it, else load global - return falsy value if nothing loaded
58
- def loader(hash, global_dir, local_dir, name)
59
+ def loader(hash, global_dir, local_dir, name, namespace)
59
60
  dir = File.join(Config::ROOT, local_dir)
60
- map = Manager.load(dir, name) if File.exist? File.join(dir, name + ".rb")
61
- map ||= Manager.load(global_dir, name)
61
+ map = Manager.load(dir, name, namespace) if File.exist? File.join(dir, name + ".rb")
62
+ map ||= Manager.load(global_dir, name, namespace)
62
63
  hash.merge!(map) if map
63
64
  end
64
65
  end
@@ -4,6 +4,8 @@ class AOS7 < Oxidized::Model
4
4
  # Alcatel-Lucent Operating System Version 7 (Linux based)
5
5
  # used in OmniSwitch 6900/10k
6
6
 
7
+ prompt /^([\w.@-]+ ?[#>]\s?)$/
8
+
7
9
  comment '! '
8
10
 
9
11
  cmd :all do |cfg, cmdstring|
@@ -56,9 +56,23 @@ class Aoscx < Oxidized::Model
56
56
  cmd 'show environment' do |cfg|
57
57
  cfg.gsub! /^(LC.*\s+)\d+\s+$/, '\\1<hidden>'
58
58
  cfg.gsub! /^(\d\/\d\/\d.*\s+)\d+\s+$/, '\\1<hidden>'
59
- cfg.gsub! /^(\d+\/\S+\s+\S+\s+)\d+\.\d+\s+C(.*)/, '\\1<hidden>\\2'
59
+ cfg.gsub! /^(\d+\/?\S+\s+\S+\s+)\d+\.\d+\s+C\s+(.*)/, '\\1<hidden> \\2'
60
60
  cfg.gsub! /^(LC.*\s+)\d+\.\d+\s+(C.*)$/, '\\1 <hidden> \\2'
61
- cfg.gsub! /^(\S+\s+\S+\s+\s+\S+\s+)(slow|normal|medium|fast|max)(\s+\S+\s+\S+\s+)\d+/, '\\1<speed>\\3<rpm>'
61
+ cfg.gsub! /^(\S+\s+\S+\s+\s+\S+\s+)(slow|normal|medium|fast|max)\s+(\S+\s+\S+\s+)\d+[[:blank:]]+/, '\\1<speed> \\3<rpm>'
62
+ # match show environment power-consumption on VSF or standadlone, non-chassis and non-6400 switch, e.g. "2 6300M 48G 4SFP56 Swch 156.00 155.94"
63
+ cfg.gsub! /^(\d+\s+.+\s+)(\s{2}\d{2}\.\d{2}|\s{1}\d{3}\.\d{2}|\d{4}\.\d{2})(\s+)(\s{2}\d{2}\.\d{2}|\s{1}\d{3}\.\d{2}|\d{4}\.\d{2})$/, '\\1<power>\\3<power>'
64
+ # match show environment power-consumption on 6400 or chassis switches, e.g. "1/4 line-card-module R0X39A 6400 48p 1GbE CL4 PoE 4SFP56 Mod 54 W"
65
+ cfg.gsub! /^(\d+\/?\d*\s+.+\s+)(\s{1,4}\d{1,3})\sW\s*$/, '\\1<power>'
66
+ # match show environment power-consumption on 6400 or chassis switches, e.g. "Module Total Power Usage 13000 W", match up to a 5-digit number and keep table formatting.
67
+ cfg.gsub! /^(Module|Chassis)\s(Total\sPower\sUsage)(\s+)\s(\s{4}\d{1}|\s{3}\d{2}|\s{2}\d{3}|\s{1}\d{4}|\d{5})\sW\s*$/, '\\1 <power>'
68
+ # match show environment power-consumption on 6400 or chassis switches, e.g. "Chassis Total Power Usage 13000 W", match up to a 5-digit number and keep table formatting.
69
+ cfg.gsub! /^(Chassis\sTotal\sPower\sUsage)(\s+)(\s{4}\d{1}|\s{3}\d{2}|\s{2}\d{3}|\s{1}\d{4}|\d{5})\sW\s*$/, '\\1\\2<power>'
70
+ # match show environment power-consumption on 8400 or chassis switches, up to a 5-digit number, example matches:
71
+ # e.g. "Chassis Total Power Allocated (total of all max wattages) 4130 W"
72
+ # e.g. "Chassis Total Power Unallocated 15860 W"
73
+ cfg.gsub! /^(Chassis\sTotal\sPower\s)(Allocated|Unallocated)(\s|\s\(total of all max wattages\))(\s+)(\s{4}\d{1}|\s{3}\d{2}|\s{2}\d{3}|\s{1}\d{4}|\d{5})\sW\s*$/, '\\1\\2\\3\\4<power>'
74
+ # match Total Power Consumption:
75
+ cfg.gsub! /^([t|T]otal\s[p|P]ower\s[c|C]onsumption\s+)(\d+\.\d\d)$/, '\\1<power>'
62
76
  comment cfg
63
77
  end
64
78
 
@@ -12,7 +12,13 @@ class AOSW < Oxidized::Model
12
12
  # All IAPs connected to a Instant Controller will have the same config output. Only the controller needs to be monitored.
13
13
 
14
14
  comment '# '
15
- prompt /^([\w\(:.@-]+(\)?\s?)[#>]\s?)$/
15
+ # see /spec/model/aosw_spec.rb for prompt examples
16
+ prompt /^\(?[\w\:.@-]+\)? ?[*^]?(\[[\w\/]+\] ?)?[#>] ?$/
17
+
18
+ # Ignore cariage returns - also for the prompt
19
+ expect "\r" do |data, re|
20
+ data.gsub re, ''
21
+ end
16
22
 
17
23
  cmd :all do |cfg|
18
24
  cfg.cut_both
@@ -111,7 +117,7 @@ class AOSW < Oxidized::Model
111
117
  next if line =~ /Output \d Config/i
112
118
  next if line =~ /(Tachometers|Temperatures|Voltages)/
113
119
  next if line =~ /((Card|CPU) Temperature|Chassis Fan|VMON1[0-9])/
114
- next if line =~ /[0-9]+\s+(RPMS?|m?V|C)/i
120
+ next if line =~ /[0-9]+\s+(RPMS?|m?V|C|W)/i
115
121
 
116
122
  out << line.strip
117
123
  end
@@ -6,6 +6,6 @@ class Apc_aos < Oxidized::Model # rubocop:disable Naming/ClassAndModuleCamelCase
6
6
  cfg
7
7
  end
8
8
 
9
- cfg :ftp do
9
+ cfg :ftp, :scp do
10
10
  end
11
11
  end
@@ -0,0 +1,90 @@
1
+ class ArubaInstant < Oxidized::Model
2
+ using Refinements
3
+
4
+ # Aruba IAP, Instant Controller
5
+
6
+ comment '# '
7
+ prompt(/^ ?[\w\:.@-]+[#>] $/)
8
+
9
+ cmd :all do |cfg|
10
+ # Remove command echo and prompt
11
+ cfg.cut_both
12
+ end
13
+
14
+ cmd :secret do |cfg|
15
+ cfg.gsub!(/ipsec (\S+)$/, 'ipsec <secret removed>')
16
+ cfg.gsub!(/community (\S+)$/, 'community <secret removed>')
17
+ cfg.gsub!(/^(snmp-server host [\d.]+ version 2c) \S+ (.*)$/, '\1 <secret removed> \2')
18
+ # MAS format: mgmt-user <username> <accesslevel> <password hash>
19
+ # IAP format (root user): mgmt-user <username> <password hash>
20
+ # IAP format: mgmt-user <username> <password hash> <access level>
21
+ 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
22
+ cfg.gsub!(/mgmt-user (\S+) (\S+)( (read-only|guest-mgmt))?$/, 'mgmt-user \1 <secret removed> \3') # IAP
23
+ cfg.gsub!(/key (\S+)$/, 'key <secret removed>')
24
+ cfg.gsub!(/wpa-passphrase (\S+)$/, 'wpa-passphrase <secret removed>')
25
+ cfg.gsub!(/bkup-passwords (\S+)$/, 'bkup-passwords <secret removed>')
26
+ cfg.gsub!(/user (\S+) (\S+) (\S+)$/, 'user \1 <secret removed> \3')
27
+ cfg.gsub!(/virtual-controller-key (\S+)$/, 'virtual-controller-key <secret removed>')
28
+ cfg.gsub!(/^(hash-mgmt-user .* password \S+) \S+( usertype .*)?$/, '\1 <secret removed>\2')
29
+ cfg
30
+ end
31
+
32
+ # get software version
33
+ cmd 'show version' do |cfg|
34
+ out = ''
35
+ cfg.each_line do |line|
36
+ next if line =~ /^(Switch|AP) uptime is /
37
+
38
+ next if line =~ /^Reboot Time and Cause/
39
+
40
+ out += line
41
+ end
42
+ comment out
43
+ end
44
+
45
+ # Get serial number
46
+ cmd 'show activate status' do |cfg|
47
+ out = ''
48
+ cfg.each_line do |line|
49
+ next if line =~ /^Activate /
50
+
51
+ next if line =~ /^Provision interval/
52
+
53
+ next if line =~ /^Cloud Activation Key/
54
+
55
+ out += line
56
+ end
57
+ comment out + "\n"
58
+ end
59
+
60
+ # Get controlled WLAN-AP
61
+ cmd 'show aps' do |cfg|
62
+ out = ''
63
+ cfg.each_line do |line|
64
+ out += if line.match?(/^Name/)
65
+ line.sub(/^(Name +IP Address +).*(Type +IPv6 Address +).*(Serial #).*$/, '\1\2\3')
66
+ else
67
+ line.sub(/^(\S+ +\S+ +)(?:\S+ +){3}(\S+ +\S+ +)(?:\S+ +){2}(\S+) +.*$/, '\1\2\3')
68
+ end
69
+ end
70
+ comment out + "\n"
71
+ end
72
+
73
+ cmd 'show running-config no-encrypt'
74
+
75
+ cfg :telnet do
76
+ username(/^User:\s*/)
77
+ password(/^Password:\s*/)
78
+ end
79
+
80
+ cfg :telnet, :ssh do
81
+ if vars :enable
82
+ post_login do
83
+ cmd "enable", /^[pP]assword:/
84
+ cmd vars(:enable)
85
+ end
86
+ end
87
+ pre_logout 'exit' if vars :enable
88
+ pre_logout 'exit'
89
+ end
90
+ end
@@ -15,7 +15,7 @@ class ASA < Oxidized::Model
15
15
  cfg.gsub! /enable password (\S+) (.*)/, 'enable password <secret hidden> \2'
16
16
  cfg.gsub! /^passwd (\S+) (.*)/, 'passwd <secret hidden> \2'
17
17
  cfg.gsub! /username (\S+) password (\S+) (.*)/, 'username \1 password <secret hidden> \3'
18
- cfg.gsub! /(ikev[12] ((remote|local)-authentication )?pre-shared-key) (\S+)/, '\1 <secret hidden>'
18
+ cfg.gsub! /(ikev[12] ((remote|local)-authentication )?pre-shared-key( hex)?) (\S+)/, '\1 <secret hidden>'
19
19
  cfg.gsub! /^(aaa-server TACACS\+? \(\S+\) host[^\n]*\n(\s+[^\n]+\n)*\skey) \S+$/mi, '\1 <secret hidden>'
20
20
  cfg.gsub! /^(aaa-server \S+ \(\S+\) host[^\n]*\n(\s+[^\n]+\n)*\s+key) \S+$/mi, '\1 <secret hidden>'
21
21
  cfg.gsub! /ldap-login-password (\S+)/, 'ldap-login-password <secret hidden>'
@@ -38,6 +38,7 @@ class ASA < Oxidized::Model
38
38
  cfg = cfg.join
39
39
  cfg.gsub! /^Configuration has not been modified since last system restart.*\n/, ''
40
40
  cfg.gsub! /^Configuration last modified by.*\n/, ''
41
+ cfg.gsub! /^Start-up time.*\n/, ''
41
42
  comment cfg
42
43
  end
43
44
 
@@ -2,7 +2,7 @@ class AsyncOS < Oxidized::Model
2
2
  using Refinements
3
3
 
4
4
  # ESA prompt "(mail.example.com)> " or "mail.example.com> "
5
- prompt /^\r*([(]?[\w. ]+[)]?[#>]\s+)$/
5
+ prompt /^\r*\(?[\w.\- ]+\)?[#>]\s+$/
6
6
  comment '! '
7
7
 
8
8
  # Select passphrase display option
@@ -12,14 +12,14 @@ class AudioCodes < Oxidized::Model
12
12
  data.sub re, ''
13
13
  end
14
14
 
15
- cmd 'show running-config' do |cfg|
15
+ cmd "show running-config\r\n" do |cfg|
16
16
  cfg
17
17
  end
18
18
 
19
19
  cfg :ssh do
20
20
  username /^login as:\s$/
21
21
  password /^.+password:\s$/
22
- pre_logout 'exit'
22
+ pre_logout "exit\r\n"
23
23
  end
24
24
 
25
25
  cfg :telnet do
@@ -4,18 +4,20 @@ class CNOS < Oxidized::Model
4
4
 
5
5
  comment '! '
6
6
 
7
- cmd :all do |cfg|
8
- cfg.each_line.to_a[0..-2].join
7
+ cmd :secret do |cfg|
8
+ cfg.gsub! /^(snmp-server community).*/, '\\1 <configuration removed>'
9
+ cfg.gsub! /^(username .+ (password|secret) \d) .+/, '\\1 <secret hidden>'
10
+ cfg.gsub! /^(enable (password|secret)( level \d+)?( \d)?) .+/, '\\1 <secret hidden>'
11
+ cfg
9
12
  end
10
13
 
11
- cmd 'show running-config' do |cfg|
12
- cfg.gsub!(/(snmp-server community )(\S+)/, '\1<hidden>')
13
- cfg.gsub!(/key type private.+key string end/m, '<private key hidden>')
14
- cfg
14
+ cmd :all do |cfg|
15
+ cfg = cfg.delete("\r")
16
+ cfg.cut_both
15
17
  end
16
18
 
17
19
  cmd 'show version' do |cfg|
18
- cfg.gsub! /^(.* uptime is ).*\n/, '\1'
20
+ cfg = cfg.each_line.reject { |line| line.match /\ uptime\ is\ / }.join
19
21
  comment cfg
20
22
  end
21
23
 
@@ -23,9 +25,10 @@ class CNOS < Oxidized::Model
23
25
  comment cfg
24
26
  end
25
27
 
26
- cfg :telnet do
27
- username /^Username:/
28
- password /^Password:/
28
+ cmd 'show running-config' do |cfg|
29
+ # remove empty lines
30
+ cfg = cfg.each_line.reject { |line| line.match /^[\r\n\s\u0000#]+$/ }.join
31
+ cfg
29
32
  end
30
33
 
31
34
  cfg :telnet, :ssh do