oxidized 0.9.0 → 0.10.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/.travis.yml +3 -0
- data/CHANGELOG.md +6 -0
- data/Gemfile.lock +41 -0
- data/README.md +64 -35
- data/Rakefile +7 -9
- data/bin/console +9 -0
- data/bin/oxidized +1 -1
- data/extra/nagios_check_failing_nodes.rb +4 -4
- data/extra/oxidized-report-git-commits +80 -0
- data/extra/rvm.oxidized.upstart +18 -0
- data/lib/oxidized.rb +45 -2
- data/lib/oxidized/cli.rb +54 -4
- data/lib/oxidized/config.rb +45 -39
- data/lib/oxidized/config/vars.rb +8 -3
- data/lib/oxidized/core.rb +5 -12
- data/lib/oxidized/hook.rb +4 -4
- data/lib/oxidized/hook/exec.rb +2 -0
- data/lib/oxidized/hook/githubrepo.rb +57 -0
- data/lib/oxidized/input/cli.rb +2 -1
- data/lib/oxidized/input/ftp.rb +3 -3
- data/lib/oxidized/input/ssh.rb +9 -9
- data/lib/oxidized/input/telnet.rb +3 -3
- data/lib/oxidized/job.rb +3 -3
- data/lib/oxidized/model/fortios.rb +1 -1
- data/lib/oxidized/model/junos.rb +8 -10
- data/lib/oxidized/model/model.rb +3 -2
- data/lib/oxidized/model/powerconnect.rb +11 -6
- data/lib/oxidized/model/saos.rb +24 -0
- data/lib/oxidized/node.rb +16 -16
- data/lib/oxidized/nodes.rb +12 -12
- data/lib/oxidized/output/file.rb +15 -3
- data/lib/oxidized/output/git.rb +10 -7
- data/lib/oxidized/source/csv.rb +7 -7
- data/lib/oxidized/source/http.rb +1 -1
- data/lib/oxidized/source/source.rb +1 -1
- data/lib/oxidized/source/sql.rb +7 -7
- data/lib/oxidized/string.rb +1 -1
- data/lib/oxidized/version.rb +3 -0
- data/lib/oxidized/worker.rb +11 -9
- data/oxidized.gemspec +12 -3
- metadata +75 -7
- data/lib/oxidized/log.rb +0 -22
- data/spec/nodes_spec.rb +0 -46
data/lib/oxidized/config.rb
CHANGED
@@ -11,47 +11,53 @@ module Oxidized
|
|
11
11
|
SourceDir = File.join Directory, %w(lib oxidized source)
|
12
12
|
HookDir = File.join Directory, %w(lib oxidized hook)
|
13
13
|
Sleep = 1
|
14
|
+
|
15
|
+
def self.load(cmd_opts={})
|
16
|
+
asetus = Asetus.new(name: 'oxidized', load: false, key_to_s: true)
|
17
|
+
Oxidized.asetus = asetus
|
18
|
+
|
19
|
+
asetus.default.username = 'username'
|
20
|
+
asetus.default.password = 'password'
|
21
|
+
asetus.default.model = 'junos'
|
22
|
+
asetus.default.interval = 3600
|
23
|
+
asetus.default.use_syslog = false
|
24
|
+
asetus.default.debug = false
|
25
|
+
asetus.default.threads = 30
|
26
|
+
asetus.default.timeout = 20
|
27
|
+
asetus.default.retries = 3
|
28
|
+
asetus.default.prompt = /^([\w.@-]+[#>]\s?)$/
|
29
|
+
asetus.default.rest = '127.0.0.1:8888' # or false to disable
|
30
|
+
asetus.default.vars = {} # could be 'enable'=>'enablePW'
|
31
|
+
asetus.default.groups = {} # group level configuration
|
32
|
+
|
33
|
+
asetus.default.input.default = 'ssh, telnet'
|
34
|
+
asetus.default.input.debug = false # or String for session log file
|
35
|
+
asetus.default.input.ssh.secure = false # complain about changed certs
|
36
|
+
|
37
|
+
asetus.default.output.default = 'file' # file, git
|
38
|
+
asetus.default.source.default = 'csv' # csv, sql
|
39
|
+
|
40
|
+
asetus.default.model_map = {
|
41
|
+
'cisco' => 'ios',
|
42
|
+
'juniper' => 'junos',
|
43
|
+
}
|
44
|
+
|
45
|
+
begin
|
46
|
+
asetus.load # load system+user configs, merge to Config.cfg
|
47
|
+
rescue => error
|
48
|
+
raise InvalidConfig, "Error loading config: #{error.message}"
|
49
|
+
end
|
50
|
+
|
51
|
+
raise NoConfig, 'edit ~/.config/oxidized/config' if asetus.create
|
52
|
+
|
53
|
+
# override if comand line flag given
|
54
|
+
asetus.cfg.debug = cmd_opts[:debug] if cmd_opts[:debug]
|
55
|
+
|
56
|
+
asetus
|
57
|
+
end
|
14
58
|
end
|
59
|
+
|
15
60
|
class << self
|
16
61
|
attr_accessor :mgr, :Hooks
|
17
62
|
end
|
18
|
-
CFGS = Asetus.new :name=>'oxidized', :load=>false, :key_to_s=>true
|
19
|
-
CFGS.default.username = 'username'
|
20
|
-
CFGS.default.password = 'password'
|
21
|
-
CFGS.default.model = 'junos'
|
22
|
-
CFGS.default.interval = 3600
|
23
|
-
CFGS.default.log = File.join Config::Root, 'log'
|
24
|
-
CFGS.default.debug = false
|
25
|
-
CFGS.default.threads = 30
|
26
|
-
CFGS.default.timeout = 20
|
27
|
-
CFGS.default.retries = 3
|
28
|
-
CFGS.default.prompt = /^([\w.@-]+[#>]\s?)$/
|
29
|
-
CFGS.default.rest = '127.0.0.1:8888' # or false to disable
|
30
|
-
CFGS.default.vars = {} # could be 'enable'=>'enablePW'
|
31
|
-
CFGS.default.groups = {} # group level configuration
|
32
|
-
|
33
|
-
CFGS.default.input.default = 'ssh, telnet'
|
34
|
-
CFGS.default.input.debug = false # or String for session log file
|
35
|
-
CFGS.default.input.ssh.secure = false # complain about changed certs
|
36
|
-
|
37
|
-
CFGS.default.output.default = 'file' # file, git
|
38
|
-
CFGS.default.source.default = 'csv' # csv, sql
|
39
|
-
|
40
|
-
CFGS.default.model_map = {
|
41
|
-
'cisco' => 'ios',
|
42
|
-
'juniper' => 'junos',
|
43
|
-
}
|
44
|
-
|
45
|
-
begin
|
46
|
-
CFGS.load # load system+user configs, merge to Config.cfg
|
47
|
-
rescue => error
|
48
|
-
raise InvalidConfig, "Error loading config: #{error.message}"
|
49
|
-
ensure
|
50
|
-
CFG = CFGS.cfg # convenienence, instead of Config.cfg.password, CFG.password
|
51
|
-
end
|
52
|
-
|
53
|
-
Log.level = Logger::INFO unless CFG.debug
|
54
|
-
raise NoConfig, 'edit ~/.config/oxidized/config' if CFGS.create
|
55
|
-
Log.file = CFG.log if CFG.log
|
56
|
-
|
57
63
|
end
|
data/lib/oxidized/config/vars.rb
CHANGED
@@ -2,9 +2,14 @@ module Oxidized::Config::Vars
|
|
2
2
|
# convenience method for accessing node, group or global level user variables
|
3
3
|
# nil values will be ignored
|
4
4
|
def vars name
|
5
|
-
r =
|
6
|
-
|
7
|
-
|
5
|
+
r = @node.vars[name] unless @node.vars.nil?
|
6
|
+
if Oxidized.config.groups.has_key?(@node.group)
|
7
|
+
if Oxidized.config.groups[@node.group].vars.has_key?(name.to_s)
|
8
|
+
r ||= Oxidized.config.groups[@node.group].vars[name.to_s]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
r ||= Oxidized.config.vars[name.to_s] if Oxidized.config.vars.has_key?(name.to_s)
|
8
12
|
r
|
9
13
|
end
|
10
14
|
end
|
15
|
+
|
data/lib/oxidized/core.rb
CHANGED
@@ -1,12 +1,4 @@
|
|
1
1
|
module Oxidized
|
2
|
-
require 'oxidized/log'
|
3
|
-
require 'oxidized/string'
|
4
|
-
require 'oxidized/config'
|
5
|
-
require 'oxidized/config/vars'
|
6
|
-
require 'oxidized/worker'
|
7
|
-
require 'oxidized/nodes'
|
8
|
-
require 'oxidized/manager'
|
9
|
-
require 'oxidized/hook'
|
10
2
|
class << self
|
11
3
|
def new *args
|
12
4
|
Core.new args
|
@@ -18,18 +10,19 @@ module Oxidized
|
|
18
10
|
|
19
11
|
def initialize args
|
20
12
|
Oxidized.mgr = Manager.new
|
21
|
-
Oxidized.Hooks = HookManager.from_config
|
13
|
+
Oxidized.Hooks = HookManager.from_config(Oxidized.config)
|
22
14
|
nodes = Nodes.new
|
23
15
|
raise NoNodesFound, 'source returns no usable nodes' if nodes.size == 0
|
24
16
|
@worker = Worker.new nodes
|
25
17
|
trap('HUP') { nodes.load }
|
26
|
-
if
|
18
|
+
if Oxidized.config.rest?
|
27
19
|
begin
|
28
20
|
require 'oxidized/web'
|
29
21
|
rescue LoadError
|
30
|
-
raise OxidizedError, 'oxidized-web not found: sudo gem install oxidized-web -
|
22
|
+
raise OxidizedError, 'oxidized-web not found: sudo gem install oxidized-web - \
|
23
|
+
or disable web support by setting "rest: false" in your configuration'
|
31
24
|
end
|
32
|
-
@rest = API::Web.new nodes,
|
25
|
+
@rest = API::Web.new nodes, Oxidized.config.rest
|
33
26
|
@rest.run
|
34
27
|
end
|
35
28
|
run
|
data/lib/oxidized/hook.rb
CHANGED
@@ -46,7 +46,7 @@ class HookManager
|
|
46
46
|
hook.cfg = cfg
|
47
47
|
|
48
48
|
@registered_hooks[event] << RegisteredHook.new(name, hook)
|
49
|
-
|
49
|
+
Oxidized.logger.debug "Hook #{name.inspect} registered #{hook.class} for event #{event.inspect}"
|
50
50
|
end
|
51
51
|
|
52
52
|
def handle event, ctx_params={}
|
@@ -57,7 +57,7 @@ class HookManager
|
|
57
57
|
begin
|
58
58
|
r_hook.hook.run_hook ctx
|
59
59
|
rescue => e
|
60
|
-
|
60
|
+
Oxidized.logger.error "Hook #{r_hook.name} (#{r_hook.hook}) failed " +
|
61
61
|
"(#{e.inspect}) for event #{event.inspect}"
|
62
62
|
end
|
63
63
|
end
|
@@ -66,7 +66,7 @@ end
|
|
66
66
|
|
67
67
|
# Hook abstract base class
|
68
68
|
class Hook
|
69
|
-
|
69
|
+
attr_reader :cfg
|
70
70
|
|
71
71
|
def initialize
|
72
72
|
end
|
@@ -81,7 +81,7 @@ class Hook
|
|
81
81
|
end
|
82
82
|
|
83
83
|
def log(msg, level=:info)
|
84
|
-
|
84
|
+
Oxidized.logger.send(level, "#{self.class.name}: #{msg}")
|
85
85
|
end
|
86
86
|
|
87
87
|
end
|
data/lib/oxidized/hook/exec.rb
CHANGED
@@ -0,0 +1,57 @@
|
|
1
|
+
class GithubRepo < Oxidized::Hook
|
2
|
+
def validate_cfg!
|
3
|
+
cfg.has_key?('remote_repo') or raise KeyError, 'remote_repo is required'
|
4
|
+
end
|
5
|
+
|
6
|
+
def run_hook(ctx)
|
7
|
+
repo = Rugged::Repository.new(Oxidized.config.output.git.repo)
|
8
|
+
log "Pushing local repository(#{repo.path})..."
|
9
|
+
remote = repo.remotes['origin'] || repo.remotes.create('origin', cfg.remote_repo)
|
10
|
+
log "to remote: #{remote.url}"
|
11
|
+
|
12
|
+
fetch_and_merge_remote(repo)
|
13
|
+
|
14
|
+
remote.push([repo.head.name], credentials: credentials)
|
15
|
+
end
|
16
|
+
|
17
|
+
def fetch_and_merge_remote(repo)
|
18
|
+
result = repo.fetch('origin', [repo.head.name], credentials: credentials)
|
19
|
+
log result.inspect, :debug
|
20
|
+
|
21
|
+
unless result[:total_deltas] > 0
|
22
|
+
log "nothing recieved after fetch", :debug
|
23
|
+
return
|
24
|
+
end
|
25
|
+
|
26
|
+
their_branch = repo.branches["origin/master"]
|
27
|
+
|
28
|
+
log "merging fetched branch #{their_branch.name}", :debug
|
29
|
+
|
30
|
+
merge_index = repo.merge_commits(repo.head.target_id, their_branch.target_id)
|
31
|
+
|
32
|
+
if merge_index.conflicts?
|
33
|
+
log("Conflicts detected, skipping Rugged::Commit.create", :warn)
|
34
|
+
return
|
35
|
+
end
|
36
|
+
|
37
|
+
Rugged::Commit.create(repo, {
|
38
|
+
parents: [repo.head.target, their_branch.target],
|
39
|
+
tree: merge_index.write_tree(repo),
|
40
|
+
message: "Merge remote-tracking branch '#{their_branch.name}'",
|
41
|
+
update_ref: "HEAD"
|
42
|
+
})
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def credentials
|
48
|
+
@credentials ||= if cfg.has_key?('username') && cfg.has_key?('password')
|
49
|
+
log "Using https auth", :debug
|
50
|
+
Rugged::Credentials::UserPassword.new(username: cfg.username, password: cfg.password)
|
51
|
+
else
|
52
|
+
log "Using ssh auth", :debug
|
53
|
+
Rugged::Credentials::SshKeyFromAgent.new(username: 'git')
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
data/lib/oxidized/input/cli.rb
CHANGED
data/lib/oxidized/input/ftp.rb
CHANGED
@@ -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::Crash + "-#{@node.ip}-ftp", 'w') if
|
21
|
+
@log = File.open(Oxidized::Config::Crash + "-#{@node.ip}-ftp", 'w') if Oxidized.config.input.debug?
|
22
22
|
@ftp = Net::FTP.new @node.ip, @node.auth[:username], @node.auth[:password]
|
23
23
|
connected?
|
24
24
|
end
|
@@ -28,7 +28,7 @@ module Oxidized
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def cmd file
|
31
|
-
|
31
|
+
Oxidized.logger.debug "FTP: #{file} @ #{@node.name}"
|
32
32
|
@ftp.getbinaryfile file, nil
|
33
33
|
end
|
34
34
|
|
@@ -47,7 +47,7 @@ module Oxidized
|
|
47
47
|
@ftp.close
|
48
48
|
#rescue Errno::ECONNRESET, IOError
|
49
49
|
ensure
|
50
|
-
@log.close if
|
50
|
+
@log.close if Oxidized.config.input.debug?
|
51
51
|
end
|
52
52
|
|
53
53
|
end
|
data/lib/oxidized/input/ssh.rb
CHANGED
@@ -19,11 +19,11 @@ module Oxidized
|
|
19
19
|
@node = node
|
20
20
|
@output = ''
|
21
21
|
@node.model.cfg['ssh'].each { |cb| instance_exec(&cb) }
|
22
|
-
secure =
|
23
|
-
@log = File.open(Oxidized::Config::Crash + "-#{@node.ip}-ssh", 'w') if
|
22
|
+
secure = Oxidized.config.input.ssh.secure
|
23
|
+
@log = File.open(Oxidized::Config::Crash + "-#{@node.ip}-ssh", 'w') if Oxidized.config.input.debug?
|
24
24
|
port = vars(:ssh_port) || 22
|
25
25
|
@ssh = Net::SSH.start @node.ip, @node.auth[:username], :port => port.to_i,
|
26
|
-
:password => @node.auth[:password], :timeout =>
|
26
|
+
:password => @node.auth[:password], :timeout => Oxidized.config.timeout,
|
27
27
|
:paranoid => secure,
|
28
28
|
:auth_methods => %w(none publickey password keyboard-interactive),
|
29
29
|
:number_of_password_prompts => 0
|
@@ -42,8 +42,8 @@ module Oxidized
|
|
42
42
|
@ssh and not @ssh.closed?
|
43
43
|
end
|
44
44
|
|
45
|
-
def cmd cmd, expect
|
46
|
-
|
45
|
+
def cmd cmd, expect=node.prompt
|
46
|
+
Oxidized.logger.debug "SSH: #{cmd} @ #{node.name}"
|
47
47
|
if @exec
|
48
48
|
@ssh.exec! cmd
|
49
49
|
else
|
@@ -64,17 +64,17 @@ module Oxidized
|
|
64
64
|
def disconnect
|
65
65
|
disconnect_cli
|
66
66
|
# if disconnect does not disconnect us, give up after timeout
|
67
|
-
Timeout::timeout(
|
67
|
+
Timeout::timeout(Oxidized.config.timeout) { @ssh.loop }
|
68
68
|
rescue Errno::ECONNRESET, Net::SSH::Disconnect, IOError
|
69
69
|
ensure
|
70
|
-
@log.close if
|
70
|
+
@log.close if Oxidized.config.input.debug?
|
71
71
|
(@ssh.close rescue true) unless @ssh.closed?
|
72
72
|
end
|
73
73
|
|
74
74
|
def shell_open ssh
|
75
75
|
@ses = ssh.open_channel do |ch|
|
76
76
|
ch.on_data do |_ch, data|
|
77
|
-
@log.print data if
|
77
|
+
@log.print data if Oxidized.config.input.debug?
|
78
78
|
@output << data
|
79
79
|
@output = @node.model.expects @output
|
80
80
|
end
|
@@ -109,7 +109,7 @@ module Oxidized
|
|
109
109
|
end
|
110
110
|
|
111
111
|
def expect regexp
|
112
|
-
Timeout::timeout(
|
112
|
+
Timeout::timeout(Oxidized.config.timeout) do
|
113
113
|
@ssh.loop(0.1) do
|
114
114
|
sleep 0.1
|
115
115
|
not @output.match regexp
|
@@ -8,7 +8,7 @@ module Oxidized
|
|
8
8
|
|
9
9
|
def connect node
|
10
10
|
@node = node
|
11
|
-
@timeout =
|
11
|
+
@timeout = Oxidized.config.timeout
|
12
12
|
@node.model.cfg['telnet'].each { |cb| instance_exec(&cb) }
|
13
13
|
port = vars(:telnet_port) || 23
|
14
14
|
|
@@ -16,7 +16,7 @@ module Oxidized
|
|
16
16
|
'Port' => port.to_i,
|
17
17
|
'Timeout' => @timeout,
|
18
18
|
'Model' => @node.model }
|
19
|
-
opt['Output_log'] = Oxidized::Config::Crash + "-#{@node.ip}-telnet" if
|
19
|
+
opt['Output_log'] = Oxidized::Config::Crash + "-#{@node.ip}-telnet" if Oxidized.config.input.debug?
|
20
20
|
|
21
21
|
@telnet = Net::Telnet.new opt
|
22
22
|
if @node.auth[:username] and @node.auth[:username].length > 0
|
@@ -37,7 +37,7 @@ module Oxidized
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def cmd cmd, expect=@node.prompt
|
40
|
-
|
40
|
+
Oxidized.logger.debug "Telnet: #{cmd} @#{@node.name}"
|
41
41
|
args = { 'String' => cmd }
|
42
42
|
args.merge!({ 'Match' => expect, 'Timeout' => @timeout }) if expect
|
43
43
|
@telnet.cmd args
|
data/lib/oxidized/job.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
module Oxidized
|
2
2
|
class Job < Thread
|
3
3
|
attr_reader :start, :end, :status, :time, :node, :config
|
4
|
-
def initialize
|
4
|
+
def initialize(node)
|
5
5
|
@node = node
|
6
6
|
@start = Time.now.utc
|
7
|
-
super do
|
8
|
-
@status, @config = node.run
|
7
|
+
super do
|
8
|
+
@status, @config = @node.run
|
9
9
|
@end = Time.now.utc
|
10
10
|
@time = @end - @start
|
11
11
|
end
|
data/lib/oxidized/model/junos.rb
CHANGED
@@ -3,7 +3,7 @@ class JunOS < Oxidized::Model
|
|
3
3
|
comment '# '
|
4
4
|
|
5
5
|
def telnet
|
6
|
-
@input.class.to_s.match
|
6
|
+
@input.class.to_s.match(/Telnet/)
|
7
7
|
end
|
8
8
|
|
9
9
|
cmd :all do |cfg|
|
@@ -12,16 +12,16 @@ class JunOS < Oxidized::Model
|
|
12
12
|
cfg.lines.map { |line| line.rstrip }.join("\n") + "\n"
|
13
13
|
end
|
14
14
|
|
15
|
-
cmd :secret do |cfg|
|
16
|
-
cfg.gsub!
|
17
|
-
cfg.gsub!
|
15
|
+
cmd :secret do |cfg|
|
16
|
+
cfg.gsub!(/encrypted-password (\S+).*/, '<secret removed>')
|
17
|
+
cfg.gsub!(/community (\S+) {/, 'community <hidden> {')
|
18
18
|
cfg
|
19
19
|
end
|
20
20
|
|
21
21
|
cmd 'show configuration | display omit'
|
22
22
|
|
23
23
|
cmd 'show version' do |cfg|
|
24
|
-
@model = $1 if cfg.match
|
24
|
+
@model = $1 if cfg.match(/^Model: (\S+)/)
|
25
25
|
comment cfg
|
26
26
|
end
|
27
27
|
|
@@ -34,13 +34,11 @@ class JunOS < Oxidized::Model
|
|
34
34
|
out
|
35
35
|
end
|
36
36
|
|
37
|
-
cmd
|
38
|
-
comment cfg
|
39
|
-
end
|
37
|
+
cmd('show chassis hardware') { |cfg| comment cfg }
|
40
38
|
|
41
39
|
cfg :telnet do
|
42
|
-
username
|
43
|
-
password
|
40
|
+
username(/^login:/)
|
41
|
+
password(/^Password:/)
|
44
42
|
end
|
45
43
|
|
46
44
|
cfg :ssh do
|