oxidized 0.32.1 → 0.33.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.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE/bug_report.md +42 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +22 -0
- data/.github/ISSUE_TEMPLATE/support-request.md +36 -0
- data/.github/workflows/publishdocker.yml +35 -16
- data/.gitignore +1 -0
- data/.rubocop.yml +0 -2
- data/.rubocop_todo.yml +5 -31
- data/CHANGELOG.md +54 -2
- data/CONTRIBUTING.md +10 -7
- data/Dockerfile +37 -64
- data/README.md +4 -95
- data/Rakefile +8 -9
- data/docs/Configuration.md +139 -27
- data/docs/Docker.md +240 -0
- data/docs/Issues.md +17 -0
- data/docs/Model-Notes/EatonNetwork.md +18 -0
- data/docs/Model-Notes/HPEAruba.md +3 -2
- data/docs/Release.md +5 -3
- data/docs/Supported-OS-Types.md +3 -0
- data/docs/Troubleshooting.md +6 -1
- data/extra/rest_client.rb +1 -1
- data/lib/oxidized/config/vars.rb +18 -14
- data/lib/oxidized/config.rb +3 -1
- data/lib/oxidized/core.rb +26 -5
- data/lib/oxidized/input/http.rb +1 -1
- data/lib/oxidized/model/aos7.rb +3 -0
- data/lib/oxidized/model/eatonnetwork.rb +65 -0
- data/lib/oxidized/model/fortios.rb +3 -3
- data/lib/oxidized/model/ingate.rb +47 -0
- data/lib/oxidized/model/ios.rb +1 -0
- data/lib/oxidized/model/netgear.rb +6 -0
- data/lib/oxidized/model/powerconnect.rb +31 -10
- data/lib/oxidized/model/srosmd.rb +1 -1
- data/lib/oxidized/model/unifiap.rb +142 -0
- data/lib/oxidized/output/git.rb +82 -32
- data/lib/oxidized/output/gitcrypt.rb +3 -0
- data/lib/oxidized/version.rb +6 -4
- data/lib/oxidized/worker.rb +2 -5
- data/lib/refinements.rb +2 -0
- data/oxidized.gemspec +5 -6
- metadata +30 -30
- data/examples/podman-compose/Makefile +0 -103
- data/examples/podman-compose/README.md +0 -94
- data/examples/podman-compose/docker-compose.yml +0 -30
- data/examples/podman-compose/gitserver/.gitignore +0 -1
- data/examples/podman-compose/gitserver/Dockerfile +0 -14
- data/examples/podman-compose/model-simulation/Dockerfile-model +0 -13
- data/examples/podman-compose/model-simulation/asternos.sh +0 -36
- data/examples/podman-compose/oxidized-config/.gitignore +0 -10
- data/examples/podman-compose/oxidized-config/config +0 -46
- data/examples/podman-compose/oxidized-config/config_csv-file +0 -46
- data/examples/podman-compose/oxidized-config/config_csv-gitserver +0 -56
- data/examples/podman-compose/oxidized-config/router.db +0 -1
- data/examples/podman-compose/oxidized-ssh/.gitignore +0 -1
- data/examples/podman-compose/oxidized-ssh/README.md +0 -14
data/lib/oxidized/config/vars.rb
CHANGED
@@ -1,17 +1,21 @@
|
|
1
|
-
module Oxidized
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
1
|
+
module Oxidized
|
2
|
+
class Config
|
3
|
+
module Vars
|
4
|
+
# convenience method for accessing node, group or global level user variables
|
5
|
+
def vars(name)
|
6
|
+
model_name = @node.model.class.name.to_s.downcase
|
7
|
+
if @node.vars&.has_key?(name)
|
8
|
+
@node.vars[name]
|
9
|
+
elsif Oxidized.config.groups.has_key?(@node.group) && Oxidized.config.groups[@node.group].models.has_key(model_name) && Oxidized.config.groups[@node.group].models[model_name].vars.has_key?(name.to_s)
|
10
|
+
Oxidized.config.groups[@node.group].models[model_name].vars[name.to_s]
|
11
|
+
elsif Oxidized.config.groups.has_key?(@node.group) && Oxidized.config.groups[@node.group].vars.has_key?(name.to_s)
|
12
|
+
Oxidized.config.groups[@node.group].vars[name.to_s]
|
13
|
+
elsif Oxidized.config.models.has_key(model_name) && Oxidized.config.models[model_name].vars.has_key?(name.to_s)
|
14
|
+
Oxidized.config.models[model_name].vars[name.to_s]
|
15
|
+
elsif Oxidized.config.vars.has_key?(name.to_s)
|
16
|
+
Oxidized.config.vars[name.to_s]
|
17
|
+
end
|
18
|
+
end
|
15
19
|
end
|
16
20
|
end
|
17
21
|
end
|
data/lib/oxidized/config.rb
CHANGED
@@ -35,7 +35,6 @@ module Oxidized
|
|
35
35
|
asetus.default.timeout = 20
|
36
36
|
asetus.default.retries = 3
|
37
37
|
asetus.default.prompt = /^([\w.@-]+[#>]\s?)$/
|
38
|
-
asetus.default.rest = '127.0.0.1:8888' # or false to disable
|
39
38
|
asetus.default.next_adds_job = false # if true, /next adds job, so device is fetched immmeiately
|
40
39
|
asetus.default.vars = {} # could be 'enable'=>'enablePW'
|
41
40
|
asetus.default.groups = {} # group level configuration
|
@@ -43,6 +42,9 @@ module Oxidized
|
|
43
42
|
asetus.default.models = {} # model level configuration
|
44
43
|
asetus.default.pid = File.join(Oxidized::Config::ROOT, 'pid')
|
45
44
|
|
45
|
+
# Extentions
|
46
|
+
asetus.default.extensions['oxidized-web'].load = false
|
47
|
+
|
46
48
|
asetus.default.crash.directory = File.join(Oxidized::Config::ROOT, 'crashes')
|
47
49
|
asetus.default.crash.hostnames = false
|
48
50
|
|
data/lib/oxidized/core.rb
CHANGED
@@ -23,15 +23,36 @@ module Oxidized
|
|
23
23
|
end
|
24
24
|
Signals.register_signal('HUP', reload_proc)
|
25
25
|
|
26
|
-
#
|
27
|
-
|
26
|
+
# Load extensions, currently only oxidized-web
|
27
|
+
# We have different namespaces for oxidized-web, which needs to be
|
28
|
+
# adressed if we need a generic way to load extensions:
|
29
|
+
# - gem: oxidized-web
|
30
|
+
# - module: Oxidized::API
|
31
|
+
# - path: oxidized/web
|
32
|
+
# - entrypoint: Oxidized::API::Web.new(nodes, configuration)
|
33
|
+
|
34
|
+
# Initialize oxidized-web if requested
|
35
|
+
if Oxidized.config.has_key? 'rest'
|
36
|
+
Oxidized.logger.warn(
|
37
|
+
'configuration: "rest" is deprecated. Migrate to ' \
|
38
|
+
'"extensions.oxidized-web" and remove "rest" from the configuration'
|
39
|
+
)
|
40
|
+
configuration = Oxidized.config.rest
|
41
|
+
elsif Oxidized.config.extensions['oxidized-web'].load?
|
42
|
+
# This comment stops rubocop complaining about Style/IfUnlessModifier
|
43
|
+
configuration = Oxidized.config.extensions['oxidized-web']
|
44
|
+
end
|
45
|
+
|
46
|
+
if configuration
|
28
47
|
begin
|
29
48
|
require 'oxidized/web'
|
30
49
|
rescue LoadError
|
31
|
-
raise OxidizedError,
|
32
|
-
|
50
|
+
raise OxidizedError,
|
51
|
+
'oxidized-web not found: install it or disable it by ' \
|
52
|
+
'removing "rest" and "extensions.oxidized-web" from your ' \
|
53
|
+
'configuration'
|
33
54
|
end
|
34
|
-
@rest = API::Web.new nodes,
|
55
|
+
@rest = API::Web.new nodes, configuration
|
35
56
|
@rest.run
|
36
57
|
end
|
37
58
|
run
|
data/lib/oxidized/input/http.rb
CHANGED
@@ -59,7 +59,7 @@ module Oxidized
|
|
59
59
|
|
60
60
|
if res.code == '401' && res['www-authenticate']&.include?('Digest')
|
61
61
|
uri.user = @username
|
62
|
-
uri.password = @password
|
62
|
+
uri.password = URI.encode_www_form_component(@password)
|
63
63
|
Oxidized.logger.debug "Server requires Digest authentication"
|
64
64
|
auth = Net::HTTP::DigestAuth.new.auth_header(uri, res['www-authenticate'], 'GET')
|
65
65
|
|
data/lib/oxidized/model/aos7.rb
CHANGED
@@ -0,0 +1,65 @@
|
|
1
|
+
class EatonNetwork < Oxidized::Model
|
2
|
+
using Refinements
|
3
|
+
# Eaton Gigabit Network Card M3
|
4
|
+
|
5
|
+
# -p option is a passphrase used to encrypted parts of the config data, the
|
6
|
+
# encrypted data is nondeterministic and changes with each run. Use auth
|
7
|
+
# password as the passphrase.
|
8
|
+
#
|
9
|
+
# See docs/Model-Notes/EatonNetwork.md for more info
|
10
|
+
post do
|
11
|
+
# Get config in post to allow passing auth password to cmd.
|
12
|
+
cfg = cmd "save_configuration -p #{@node.auth[:password]}"
|
13
|
+
cfg
|
14
|
+
end
|
15
|
+
|
16
|
+
cmd :all do |cfg|
|
17
|
+
# `save_configuration` echos the command back, outputs date time info, with
|
18
|
+
# last line is the prompt again.
|
19
|
+
json_str = cfg.each_line.select { |line| line.match /^\{/ }.join
|
20
|
+
json = JSON.parse(json_str)
|
21
|
+
|
22
|
+
json['features']['userAndSessionManagement']['data']['settings']['all']['1.0']['local']['1.0']['predefinedAccounts'].each do |n|
|
23
|
+
n.delete('attemptLogin')
|
24
|
+
n['password'].delete('history')
|
25
|
+
end
|
26
|
+
json['features']['userAndSessionManagement']['data']['settings']['all']['1.0']['local']['1.0']['createdAccounts'].each do |n|
|
27
|
+
n.delete('attemptLogin')
|
28
|
+
n['password'].delete('history')
|
29
|
+
end
|
30
|
+
|
31
|
+
cfg = JSON.pretty_generate(json)
|
32
|
+
cfg
|
33
|
+
end
|
34
|
+
|
35
|
+
cmd :secret do |cfg|
|
36
|
+
# Re-parse json to remove secrets by json path
|
37
|
+
json = JSON.parse(cfg)
|
38
|
+
|
39
|
+
json.delete('passphrase')
|
40
|
+
json['features']['rms']['data']['settings'].delete('proxyUsername')
|
41
|
+
json['features']['rms']['data']['settings'].delete('proxyPassword')
|
42
|
+
json['features']['rms']['data']['settings'].delete('username')
|
43
|
+
json['features']['rms']['data']['settings'].delete('password')
|
44
|
+
json['features']['rms']['data']['settings'].delete('defaultPassword')
|
45
|
+
|
46
|
+
json['features']['smtp']['data']['dmeData'].delete('password')
|
47
|
+
|
48
|
+
json['features']['snmp']['data']['dmeData']['v3']['users'].each do |n|
|
49
|
+
n['auth'].delete('password')
|
50
|
+
n['priv'].delete('password')
|
51
|
+
end
|
52
|
+
|
53
|
+
json['features']['userAndSessionManagement']['data']['settings']['all']['1.0']['ldap']['1.0']['settings']['connectivity']['bind'].delete('password')
|
54
|
+
json['features']['userAndSessionManagement']['data']['settings']['all']['1.0']['radius']['1.0']['settings']['connectivity']['primaryServer'].delete('secret')
|
55
|
+
json['features']['userAndSessionManagement']['data']['settings']['all']['1.0']['radius']['1.0']['settings']['connectivity']['secondaryServer'].delete('secret')
|
56
|
+
|
57
|
+
cfg = JSON.pretty_generate(json)
|
58
|
+
cfg
|
59
|
+
end
|
60
|
+
|
61
|
+
cfg :ssh do
|
62
|
+
exec true
|
63
|
+
pre_logout 'logout'
|
64
|
+
end
|
65
|
+
end
|
@@ -3,7 +3,7 @@ class FortiOS < Oxidized::Model
|
|
3
3
|
|
4
4
|
comment '# '
|
5
5
|
|
6
|
-
prompt /^([-\w.~]+(\s[(\w\-.)]+)?~?\s?[#>$]\s?)$/
|
6
|
+
prompt /^(\(\w\) )?([-\w.~]+(\s[(\w\-.)]+)?~?\s?[#>$]\s?)$/
|
7
7
|
|
8
8
|
# When a post-login-banner is enabled, you have to press "a" to log in
|
9
9
|
expect /^\(Press\s'a'\sto\saccept\):/ do |data, re|
|
@@ -39,11 +39,11 @@ class FortiOS < Oxidized::Model
|
|
39
39
|
|
40
40
|
cmd 'get system status' do |cfg|
|
41
41
|
@vdom_enabled = cfg.match /Virtual domain configuration: (enable|multiple)/
|
42
|
-
cfg.gsub! /(System time:)
|
42
|
+
cfg.gsub! /(System time:).*/i, '\\1 <stripped>'
|
43
43
|
cfg.gsub! /(Cluster (?:uptime|state change time):).*/, '\\1 <stripped>'
|
44
44
|
cfg.gsub! /(Current Time\s+:\s+)(.*)/, '\1<stripped>'
|
45
45
|
cfg.gsub! /(Uptime:\s+)(.*)/, '\1<stripped>\3'
|
46
|
-
cfg.gsub! /(Last reboot:\s+)(.*)
|
46
|
+
cfg.gsub! /(Last reboot:\s+)(.*)/i, '\1<stripped>\3'
|
47
47
|
cfg.gsub! /(Disk Usage\s+:\s+)(.*)/, '\1<stripped>'
|
48
48
|
cfg.gsub! /(^\S+ (?:disk|DB):\s+)(.*)/, '\1<stripped>\3'
|
49
49
|
cfg.gsub! /(VM Registration:\s+)(.*)/, '\1<stripped>\3'
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class Ingate < Oxidized::Model
|
2
|
+
using Refinements
|
3
|
+
|
4
|
+
cfg_cb = lambda do
|
5
|
+
cfg = @m.post(
|
6
|
+
@main_url,
|
7
|
+
{
|
8
|
+
'page' => 'save',
|
9
|
+
'db.webgui.testmode/1/timelimit' => '30',
|
10
|
+
'db.webgui.testmode/__KEEP_ROWS_ALIVE' => '1',
|
11
|
+
'db.webgui.pending_apply/1/verbosity' => 'always',
|
12
|
+
'db.webgui.pending_apply/__KEEP_ROWS_ALIVE' => '1',
|
13
|
+
'action.admin.download_config_cli' => 'Save config to CLI file',
|
14
|
+
'upload.config_file;filename=type' => 'application/octet-stream',
|
15
|
+
'upload.clicmd_file;filename;type' => 'application/octet-stream',
|
16
|
+
'security' => '',
|
17
|
+
'got_complete_form' => 'yes'
|
18
|
+
},
|
19
|
+
'Accept' => 'application/x-config-database'
|
20
|
+
)
|
21
|
+
cfg.body
|
22
|
+
end
|
23
|
+
|
24
|
+
cmd cfg_cb do |cfg|
|
25
|
+
cfg.gsub! /^# Timestamp:.*$/, ''
|
26
|
+
cfg
|
27
|
+
end
|
28
|
+
|
29
|
+
cfg :http do
|
30
|
+
@secure = true
|
31
|
+
@main_page = "/"
|
32
|
+
define_singleton_method :login do
|
33
|
+
@main_url = URI::HTTP.build host: @node.ip, path: @main_page
|
34
|
+
@m.post(
|
35
|
+
@main_url,
|
36
|
+
{
|
37
|
+
'security_user' => @node.auth[:username],
|
38
|
+
'security_password' => @node.auth[:password],
|
39
|
+
'page' => 'login',
|
40
|
+
'goal' => 'save',
|
41
|
+
'got_complete_form' => 'yes',
|
42
|
+
'security' => ''
|
43
|
+
}
|
44
|
+
)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/oxidized/model/ios.rb
CHANGED
@@ -50,6 +50,7 @@ class IOS < Oxidized::Model
|
|
50
50
|
cfg.gsub! /^( +client \S+ server-key \d) (.*)$/, '\\1 <secret hidden>'
|
51
51
|
cfg.gsub! /^( +domain-password) \S+ ?(.*)/, '\\1 <secret hidden> \\2'
|
52
52
|
cfg.gsub! /^( +pre-shared-key).*/, '\\1 <configuration removed>'
|
53
|
+
cfg.gsub! /^(.*server-key(?: \d)?) \S+/, '\\1 <secret hidden>'
|
53
54
|
cfg
|
54
55
|
end
|
55
56
|
|
@@ -4,6 +4,12 @@ class Netgear < Oxidized::Model
|
|
4
4
|
comment '!'
|
5
5
|
prompt /^\(?[\w \-+.]+\)? ?[#>] ?$/
|
6
6
|
|
7
|
+
# Handle pager for "show version" on old Netgear models: #2394
|
8
|
+
expect /^--More-- or \(q\)uit$/ do |data, re|
|
9
|
+
send ' '
|
10
|
+
data.sub re, ''
|
11
|
+
end
|
12
|
+
|
7
13
|
cmd :secret do |cfg|
|
8
14
|
cfg.gsub!(/password (\S+)/, 'password <hidden>')
|
9
15
|
cfg.gsub!(/encrypted (\S+)/, 'encrypted <hidden>')
|
@@ -5,13 +5,15 @@ class PowerConnect < Oxidized::Model
|
|
5
5
|
|
6
6
|
comment '! '
|
7
7
|
|
8
|
-
expect
|
8
|
+
expect /\n\s*--More--\s+.*/ do |data, re| # Also grab the blank line above the --More--
|
9
9
|
send ' '
|
10
10
|
data.sub re, ''
|
11
11
|
end
|
12
12
|
|
13
|
+
# Filter all command output
|
13
14
|
cmd :all do |cfg|
|
14
|
-
cfg.
|
15
|
+
cfg.gsub! /\r+/, '' # Remove the CR characters echoed back from the commands
|
16
|
+
cfg.cut_tail # Drop the last line which is the next prompt
|
15
17
|
end
|
16
18
|
|
17
19
|
cmd :secret do |cfg|
|
@@ -32,7 +34,7 @@ class PowerConnect < Oxidized::Model
|
|
32
34
|
end
|
33
35
|
|
34
36
|
cmd 'show running-config' do |cfg|
|
35
|
-
cfg.sub(/^(sflow \S+ destination owner \S+ timeout )\d+$/, '! \1<timeout>')
|
37
|
+
cfg.sub(/^(sflow \S+ destination owner \S+ timeout )\d+$/, '! \1<timeout>') # Remove changing timeout
|
36
38
|
end
|
37
39
|
|
38
40
|
cfg :telnet, :ssh do
|
@@ -50,15 +52,21 @@ class PowerConnect < Oxidized::Model
|
|
50
52
|
end
|
51
53
|
end
|
52
54
|
|
53
|
-
post_login
|
54
|
-
|
55
|
-
|
56
|
-
|
55
|
+
post_login do
|
56
|
+
cmd "terminal datadump"
|
57
|
+
cmd "terminal length 0"
|
58
|
+
end
|
59
|
+
pre_logout do
|
60
|
+
send "exit\r"
|
61
|
+
sleep(0.25)
|
62
|
+
send "logout\r"
|
63
|
+
end
|
57
64
|
end
|
58
65
|
|
59
66
|
def clean(cfg)
|
60
67
|
out = []
|
61
|
-
skip_blocks = 0
|
68
|
+
len1 = len2 = skip_blocks = 0
|
69
|
+
|
62
70
|
cfg.each_line do |line|
|
63
71
|
# If this is a stackable switch we should skip this block of information
|
64
72
|
if line.match(/Up\sTime|Temperature|Power Suppl(ies|y)|Fans/i) && (@stackable == true)
|
@@ -71,9 +79,22 @@ class PowerConnect < Oxidized::Model
|
|
71
79
|
skip_blocks -= 1 if /\S/ !~ line
|
72
80
|
next
|
73
81
|
end
|
74
|
-
|
82
|
+
line = line.strip
|
83
|
+
# If the temps were not removed by skipping blocks, then mask them out wih XXX
|
84
|
+
# The most recent set of dashes has the spacing we want to match
|
85
|
+
if (match = line.match(/^(---+ +)(---+ +)/))
|
86
|
+
one, two = match.captures
|
87
|
+
len1 = one.length
|
88
|
+
len2 = two.length
|
89
|
+
end
|
90
|
+
# This can only be a temperature, right? ;-)
|
91
|
+
if (match = line.match(/^(\d{1,2}) {3,}\d+ (.*)$/))
|
92
|
+
one, two = match.captures
|
93
|
+
line = one.to_s + (' ' * (len1 - one.length)) + "XXX" + (' ' * (len2 - 3)) + two.to_s
|
94
|
+
end
|
95
|
+
out << line
|
75
96
|
end
|
76
|
-
out = out.reject { |line| line[/Up\sTime/] }
|
97
|
+
out = out.reject { |line| line[/Up\sTime/] } # Filter out Up Time
|
77
98
|
out = comment out.join "\n"
|
78
99
|
out << "\n"
|
79
100
|
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
class Unifiap < Oxidized::Model
|
2
|
+
using Refinements
|
3
|
+
|
4
|
+
# Ubiquiti Unifi AP circa 6.x
|
5
|
+
# Should also work for unfi switches, and airOS, maybe they could be combined.
|
6
|
+
# Since it relies on exec channels, because the interactive session wouldn't
|
7
|
+
# capture all of the system.cfg output, you can't use telnet with this model.
|
8
|
+
|
9
|
+
# Sometimes there's a handy info command that summarizes some device attributes,
|
10
|
+
# but it doesn't seem to be available in exec mode. So we try to build up a similar
|
11
|
+
# list by extracting tidbits from various places. AirOS doesn't have some of these
|
12
|
+
# files, so we # may have to fall back on other commands, or locations.
|
13
|
+
|
14
|
+
# First get the board model
|
15
|
+
cmd 'head -4 /etc/board.info' do |cfg|
|
16
|
+
@model = Regexp.last_match(1) if cfg =~ /board\.name=(\S+)/i
|
17
|
+
""
|
18
|
+
end
|
19
|
+
|
20
|
+
# and version
|
21
|
+
cmd 'cat /etc/version' do |cfg|
|
22
|
+
@version = Regexp.last_match(1) if cfg =~ /(\S+)$/i
|
23
|
+
""
|
24
|
+
end
|
25
|
+
|
26
|
+
# Now the Mac address
|
27
|
+
cmd 'ifconfig eth0' do |cfg|
|
28
|
+
@mac = Regexp.last_match(1) if cfg =~ /eth0\s+Link encap:Ethernet\s+HWaddr\s+(\w+:\w+:\w+:\w+:\w+:\w+)/i
|
29
|
+
""
|
30
|
+
end
|
31
|
+
|
32
|
+
# Next see if we can get our IP and host name out of /etc/hosts
|
33
|
+
cmd 'cat /etc/hosts' do |cfg|
|
34
|
+
cfg = cfg.split("\n").reject { |line| line[/^\s*(127|0000:0000:0000:0000:0000:0000:0000:0001|0:0:0:0:0:0:0:1|::1)/] }
|
35
|
+
cfg.select do |line|
|
36
|
+
if (match = line.match(/(\d+\.\d+\.\d+\.\d+)\s+(\S+)/))
|
37
|
+
@ip, @hostname = match.captures
|
38
|
+
end
|
39
|
+
end
|
40
|
+
""
|
41
|
+
end
|
42
|
+
|
43
|
+
# We check here to see if we succeeded with /etc/hosts. If not, then we try again with ifconfig, and /tmp/system.cfg
|
44
|
+
cmd do
|
45
|
+
unless @ip
|
46
|
+
cmd 'ifconfig br0' do |cfg|
|
47
|
+
@ip = Regexp.last_match(1) if cfg =~ /inet addr:\s*(\d+\.\d+\.\d+\.\d+)/i
|
48
|
+
end
|
49
|
+
|
50
|
+
unless @ip
|
51
|
+
cmd 'ifconfig eth0' do |cfg|
|
52
|
+
@ip = Regexp.last_match(1) if cfg =~ /inet addr:\s*(\d+\.\d+\.\d+\.\d+)/i
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
unless @hostname
|
58
|
+
cmd 'cat /tmp/system.cfg' do |cfg|
|
59
|
+
@hostname = Regexp.last_match(1) if cfg =~ /resolv.host.1.name=(\S+)/i
|
60
|
+
end
|
61
|
+
end
|
62
|
+
""
|
63
|
+
end
|
64
|
+
|
65
|
+
# Check if ntpclient is running
|
66
|
+
cmd 'ps wwww' do |cfg|
|
67
|
+
@ntpserver = Regexp.last_match(1) if cfg =~ /bin\/ntpclient.+-h\s*(\S+)/i
|
68
|
+
""
|
69
|
+
end
|
70
|
+
|
71
|
+
# If it's a Unifi device it may have NTP health indication
|
72
|
+
# If there are other places that Ubiquiti puts these status files, add them here.
|
73
|
+
cmd '[ -e /tmp/run/ntp.ready ] || [ -e /var/run/ntp.ready ] && echo "File(s) exist(s)" || echo "No such file"' do |cfg|
|
74
|
+
if cfg =~ /No such file/i
|
75
|
+
if @ntpserver
|
76
|
+
# Ok, now lets try getting the skew from the output of ntpclient
|
77
|
+
cmd "ntpclient -d -n -c 2 -i0 -h #{@ntpserver}" do |cfg|
|
78
|
+
@skew = ntpskew(cfg)
|
79
|
+
end
|
80
|
+
@sync = !@skew.nil? && @skew.to_f.abs < 1e6 ? "Synchronized" : "FAIL"
|
81
|
+
end
|
82
|
+
else
|
83
|
+
@ntpserver = true
|
84
|
+
@sync = "Synchronized"
|
85
|
+
end
|
86
|
+
""
|
87
|
+
end
|
88
|
+
|
89
|
+
# Now we can display it all as a banner
|
90
|
+
cmd do
|
91
|
+
out = []
|
92
|
+
out << "*************************"
|
93
|
+
out << "Model: #{@model}"
|
94
|
+
out << "Version: #{@version}"
|
95
|
+
out << "MAC Address: #{@mac}"
|
96
|
+
out << "IP Address: #{@ip}"
|
97
|
+
out << "Hostname: #{@hostname}"
|
98
|
+
out << "NTP: #{@sync}" if @ntpserver
|
99
|
+
out << "*************************"
|
100
|
+
comment out.join("\n") + "\n"
|
101
|
+
end
|
102
|
+
|
103
|
+
# Followed by the board info
|
104
|
+
cmd 'cat /etc/board.info' do |cfg|
|
105
|
+
cfg = "#\n# Board Info:\n#\n" + cfg
|
106
|
+
comment cfg
|
107
|
+
end
|
108
|
+
|
109
|
+
# Lastly the system config
|
110
|
+
cmd 'cat /tmp/system.cfg' do |cfg|
|
111
|
+
cfg = "#\n# System Config:\n#\n" + cfg
|
112
|
+
cfg + "\n"
|
113
|
+
end
|
114
|
+
|
115
|
+
cmd :secret do |cfg|
|
116
|
+
cfg.gsub! /^((?:users|snmp\.(?:user|community))\.\d+\.password)=.+/, "# \\1=<hidden>"
|
117
|
+
cfg
|
118
|
+
end
|
119
|
+
|
120
|
+
cfg :ssh do
|
121
|
+
exec true # Don't run shell, run each command in exec channel
|
122
|
+
end
|
123
|
+
|
124
|
+
# NTPskew: Return the skew in micro seconds from the ntpclient output
|
125
|
+
def ntpskew(cfg)
|
126
|
+
index = skew = nil
|
127
|
+
|
128
|
+
cfg.each_line do |line|
|
129
|
+
# Look for the header just before the stats line, and find which number is skew
|
130
|
+
if line.match(/^\s*[a-z]+\s+[a-z]+\s+[a-z]+\s+[a-z]+/i)
|
131
|
+
words = line.split
|
132
|
+
index = words.map(&:downcase).index("skew")
|
133
|
+
end
|
134
|
+
# Now look for the single stats line and grab the skew
|
135
|
+
if !index.nil? && line.match(/^\s*[\d.]+\s+[\d.]+\s+[\d.]+\s+[\d.]+/)
|
136
|
+
numbers = line.split
|
137
|
+
skew = numbers[index]
|
138
|
+
end
|
139
|
+
end
|
140
|
+
skew
|
141
|
+
end
|
142
|
+
end
|
data/lib/oxidized/output/git.rb
CHANGED
@@ -64,8 +64,8 @@ module Oxidized
|
|
64
64
|
# Returns the configuration of group/node_name
|
65
65
|
#
|
66
66
|
# #fetch is called by Nodes#fetch
|
67
|
-
# Nodes#fetch creates a new Output object each time, so
|
68
|
-
#
|
67
|
+
# Nodes#fetch creates a new Output object each time, so it not easy
|
68
|
+
# to cache the repo index in memory. But as we keep the repo index up
|
69
69
|
# to date on disk in #update_repo, we can read it from disk instead of
|
70
70
|
# rebuilding it each time.
|
71
71
|
def fetch(node, group)
|
@@ -79,29 +79,13 @@ module Oxidized
|
|
79
79
|
'node not found'
|
80
80
|
end
|
81
81
|
|
82
|
-
# give a hash of all oid
|
82
|
+
# give a hash of all oid revisions for the given node, and the date of
|
83
|
+
# the commit.
|
84
|
+
#
|
85
|
+
# Called by Nodes#version
|
83
86
|
def version(node, group)
|
84
|
-
|
85
|
-
|
86
|
-
repo = Rugged::Repository.new repo
|
87
|
-
walker = Rugged::Walker.new(repo)
|
88
|
-
walker.sorting(Rugged::SORT_DATE)
|
89
|
-
walker.push(repo.head.target.oid)
|
90
|
-
i = -1
|
91
|
-
tab = []
|
92
|
-
walker.each do |commit|
|
93
|
-
# Diabled rubocop because the suggested .empty? does not work here.
|
94
|
-
next if commit.diff(paths: [path]).size.zero? # rubocop:disable Style/ZeroLengthPredicate
|
95
|
-
|
96
|
-
hash = {}
|
97
|
-
hash[:date] = commit.time.to_s
|
98
|
-
hash[:oid] = commit.oid
|
99
|
-
hash[:author] = commit.author
|
100
|
-
hash[:message] = commit.message
|
101
|
-
tab[i += 1] = hash
|
102
|
-
end
|
103
|
-
walker.reset
|
104
|
-
tab
|
87
|
+
repo_path, node_path = yield_repo_and_path(node, group)
|
88
|
+
self.class.hash_list(node_path, repo_path)
|
105
89
|
rescue StandardError
|
106
90
|
'node not found'
|
107
91
|
end
|
@@ -143,6 +127,79 @@ module Oxidized
|
|
143
127
|
'no diffs'
|
144
128
|
end
|
145
129
|
|
130
|
+
# Return the list of oids for node_path in the repository repo_path
|
131
|
+
def self.hash_list(node_path, repo_path)
|
132
|
+
update_cache(repo_path)
|
133
|
+
@gitcache[repo_path][:nodes][node_path] || []
|
134
|
+
end
|
135
|
+
|
136
|
+
# Update @gitcache, a class instance variable, ensuring persistence
|
137
|
+
# by saving the cache independently of object instances
|
138
|
+
def self.update_cache(repo_path)
|
139
|
+
# initialize our cache as a class instance variable
|
140
|
+
@gitcache ||= {}
|
141
|
+
# When single_repo == false, we have multiple repositories
|
142
|
+
unless @gitcache[repo_path]
|
143
|
+
@gitcache[repo_path] = {}
|
144
|
+
@gitcache[repo_path][:nodes] = {}
|
145
|
+
@gitcache[repo_path][:last_commit] = nil
|
146
|
+
end
|
147
|
+
|
148
|
+
repo = Rugged::Repository.new repo_path
|
149
|
+
|
150
|
+
walker = Rugged::Walker.new(repo)
|
151
|
+
walker.sorting(Rugged::SORT_DATE)
|
152
|
+
walker.push(repo.head.target.oid)
|
153
|
+
|
154
|
+
# We store the commits into a temporary cache. It will be prepended
|
155
|
+
# to @gitcache to preserve the order of the commits.
|
156
|
+
cache = {}
|
157
|
+
walker.each do |commit|
|
158
|
+
if commit.oid == @gitcache[repo_path][:last_commit]
|
159
|
+
# we have reached the last cached commit, so we're done
|
160
|
+
break
|
161
|
+
end
|
162
|
+
|
163
|
+
commit.diff.each_delta do |delta|
|
164
|
+
next unless delta.added? || delta.modified?
|
165
|
+
|
166
|
+
hash = {}
|
167
|
+
# We keep :date for reverse compatibility on oxidized-web <= 0.15.1
|
168
|
+
hash[:date] = commit.time.to_s
|
169
|
+
# date as a Time instance for more flexibility in oxidized-web
|
170
|
+
hash[:time] = commit.time
|
171
|
+
hash[:oid] = commit.oid
|
172
|
+
hash[:author] = commit.author
|
173
|
+
hash[:message] = commit.message
|
174
|
+
|
175
|
+
filename = delta.new_file[:path]
|
176
|
+
if cache[filename]
|
177
|
+
cache[filename].append hash
|
178
|
+
else
|
179
|
+
cache[filename] = [hash]
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
cache.each_pair do |filename, hashlist|
|
185
|
+
if @gitcache[repo_path][:nodes][filename]
|
186
|
+
# using the splat operator (*) should be OK as hashlist should
|
187
|
+
# not be very big when working on deltas
|
188
|
+
@gitcache[repo_path][:nodes][filename].prepend(*hashlist)
|
189
|
+
else
|
190
|
+
@gitcache[repo_path][:nodes][filename] = hashlist
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# Store the most recent commit
|
195
|
+
@gitcache[repo_path][:last_commit] = repo.head.target.oid
|
196
|
+
end
|
197
|
+
|
198
|
+
# Currently only used in unit tests
|
199
|
+
def self.clear_cache
|
200
|
+
@gitcache = nil
|
201
|
+
end
|
202
|
+
|
146
203
|
private
|
147
204
|
|
148
205
|
def yield_repo_and_path(node, group)
|
@@ -181,14 +238,7 @@ module Oxidized
|
|
181
238
|
end
|
182
239
|
end
|
183
240
|
|
184
|
-
# Uploads data into file in the repo
|
185
|
-
#
|
186
|
-
# @param [String] file: the file to save the configuration to
|
187
|
-
# @param [String] data: the configuration to save
|
188
|
-
# @param [Rugged::Repository] repo: the git repository to use
|
189
|
-
#
|
190
|
-
# If Oxidized.config.output.git.single_repo = false (which is the default),
|
191
|
-
# there will one repository for each group.
|
241
|
+
# Uploads data into file in the repository repo
|
192
242
|
#
|
193
243
|
# update_repo caches the index on disk. An index is usually used in a
|
194
244
|
# working directory and not in a bare repository, which confuses users.
|
@@ -114,7 +114,10 @@ module Oxidized
|
|
114
114
|
tab = []
|
115
115
|
walker.each do |commit|
|
116
116
|
hash = {}
|
117
|
+
# We keep :date for reverse compatibility on oxidized-web <= 0.15.1
|
117
118
|
hash[:date] = commit.date.to_s
|
119
|
+
# date as a Time instance for more flexibility in oxidized-web
|
120
|
+
hash[:time] = commit.date
|
118
121
|
hash[:oid] = commit.objectish
|
119
122
|
hash[:author] = commit.author
|
120
123
|
hash[:message] = commit.message
|