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