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.
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