oxidized 0.9.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -0
  3. data/CHANGELOG.md +6 -0
  4. data/Gemfile.lock +41 -0
  5. data/README.md +64 -35
  6. data/Rakefile +7 -9
  7. data/bin/console +9 -0
  8. data/bin/oxidized +1 -1
  9. data/extra/nagios_check_failing_nodes.rb +4 -4
  10. data/extra/oxidized-report-git-commits +80 -0
  11. data/extra/rvm.oxidized.upstart +18 -0
  12. data/lib/oxidized.rb +45 -2
  13. data/lib/oxidized/cli.rb +54 -4
  14. data/lib/oxidized/config.rb +45 -39
  15. data/lib/oxidized/config/vars.rb +8 -3
  16. data/lib/oxidized/core.rb +5 -12
  17. data/lib/oxidized/hook.rb +4 -4
  18. data/lib/oxidized/hook/exec.rb +2 -0
  19. data/lib/oxidized/hook/githubrepo.rb +57 -0
  20. data/lib/oxidized/input/cli.rb +2 -1
  21. data/lib/oxidized/input/ftp.rb +3 -3
  22. data/lib/oxidized/input/ssh.rb +9 -9
  23. data/lib/oxidized/input/telnet.rb +3 -3
  24. data/lib/oxidized/job.rb +3 -3
  25. data/lib/oxidized/model/fortios.rb +1 -1
  26. data/lib/oxidized/model/junos.rb +8 -10
  27. data/lib/oxidized/model/model.rb +3 -2
  28. data/lib/oxidized/model/powerconnect.rb +11 -6
  29. data/lib/oxidized/model/saos.rb +24 -0
  30. data/lib/oxidized/node.rb +16 -16
  31. data/lib/oxidized/nodes.rb +12 -12
  32. data/lib/oxidized/output/file.rb +15 -3
  33. data/lib/oxidized/output/git.rb +10 -7
  34. data/lib/oxidized/source/csv.rb +7 -7
  35. data/lib/oxidized/source/http.rb +1 -1
  36. data/lib/oxidized/source/source.rb +1 -1
  37. data/lib/oxidized/source/sql.rb +7 -7
  38. data/lib/oxidized/string.rb +1 -1
  39. data/lib/oxidized/version.rb +3 -0
  40. data/lib/oxidized/worker.rb +11 -9
  41. data/oxidized.gemspec +12 -3
  42. metadata +75 -7
  43. data/lib/oxidized/log.rb +0 -22
  44. data/spec/nodes_spec.rb +0 -46
@@ -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
@@ -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 = @node.vars[name] unless @node.vars.nil?
6
- r ||= Oxidized::CFG.groups[@node.group].vars[name.to_s] if Oxidized::CFG.groups.has_key?(@node.group)
7
- r ||= Oxidized::CFG.vars[name.to_s] if Oxidized::CFG.vars.has_key?(name.to_s)
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 CFG
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 CFG.rest?
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 - or disable web support by setting "rest: false" in your configuration'
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, CFG.rest
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
- Log.debug "Hook #{name.inspect} registered #{hook.class} for event #{event.inspect}"
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
- Log.error "Hook #{r_hook.name} (#{r_hook.hook}) failed " +
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
- attr_accessor :cfg
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
- Log.send(level, "#{self.class.name}: #{msg}")
84
+ Oxidized.logger.send(level, "#{self.class.name}: #{msg}")
85
85
  end
86
86
 
87
87
  end
@@ -71,6 +71,8 @@ class Exec < Oxidized::Hook
71
71
  "OX_NODE_MSG" => ctx.node.msg.to_s,
72
72
  "OX_NODE_GROUP" => ctx.node.group.to_s,
73
73
  "OX_EVENT" => ctx.event.to_s,
74
+ "OX_REPO_COMMITREF" => ctx.commitref.to_s,
75
+ "OX_REPO_NAME" => ctx.node.repo.to_s,
74
76
  )
75
77
  end
76
78
  if ctx.job
@@ -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
@@ -1,6 +1,7 @@
1
1
  module Oxidized
2
2
  class Input
3
3
  module CLI
4
+ attr_reader :node
4
5
 
5
6
  def initialize
6
7
  @post_login = []
@@ -10,7 +11,7 @@ module Oxidized
10
11
 
11
12
  def get
12
13
  connect_cli
13
- d = @node.model.get
14
+ d = node.model.get
14
15
  disconnect
15
16
  d
16
17
  end
@@ -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 CFG.input.debug?
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
- Log.debug "FTP: #{file} @ #{@node.name}"
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 CFG.input.debug?
50
+ @log.close if Oxidized.config.input.debug?
51
51
  end
52
52
 
53
53
  end
@@ -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 = CFG.input.ssh.secure
23
- @log = File.open(Oxidized::Config::Crash + "-#{@node.ip}-ssh", 'w') if CFG.input.debug?
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 => CFG.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=@node.prompt
46
- Log.debug "SSH: #{cmd} @ #{@node.name}"
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(CFG.timeout) { @ssh.loop }
67
+ Timeout::timeout(Oxidized.config.timeout) { @ssh.loop }
68
68
  rescue Errno::ECONNRESET, Net::SSH::Disconnect, IOError
69
69
  ensure
70
- @log.close if CFG.input.debug?
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 CFG.input.debug?
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(CFG.timeout) do
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 = CFG.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 CFG.input.debug?
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
- Log.debug "Telnet: #{cmd} @#{@node.name}"
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 node
4
+ def initialize(node)
5
5
  @node = node
6
6
  @start = Time.now.utc
7
- super do |node|
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
@@ -2,7 +2,7 @@ class FortiOS < Oxidized::Model
2
2
 
3
3
  comment '# '
4
4
 
5
- prompt /^([-\w\.]+(\s[\(\w\-\.\)]+)?\s?[#>]\s?)$/
5
+ prompt /^([-\w\.]+(\s[\(\w\-\.\)]+)?\~?\s?[#>]\s?)$/
6
6
 
7
7
  cmd :all do |cfg, cmdstring|
8
8
  new_cfg = comment "COMMAND: #{cmdstring}\n"
@@ -3,7 +3,7 @@ class JunOS < Oxidized::Model
3
3
  comment '# '
4
4
 
5
5
  def telnet
6
- @input.class.to_s.match /Telnet/
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! /encrypted-password (\S+).*/, '<secret removed>'
17
- cfg.gsub! /community (\S+) {/, 'community <hidden> {'
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 /^Model: (\S+)/
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 'show chassis hardware' do |cfg|
38
- comment cfg
39
- end
37
+ cmd('show chassis hardware') { |cfg| comment cfg }
40
38
 
41
39
  cfg :telnet do
42
- username /^login:/
43
- password /^Password:/
40
+ username(/^login:/)
41
+ password(/^Password:/)
44
42
  end
45
43
 
46
44
  cfg :ssh do