diff options
-rw-r--r-- | README.md | 113 | ||||
-rwxr-xr-x | bin/oxidized | 21 | ||||
-rw-r--r-- | lib/oxidized/cli.rb | 48 | ||||
-rw-r--r-- | lib/oxidized/config.rb | 44 | ||||
-rw-r--r-- | lib/oxidized/config/bootstrap.rb | 33 | ||||
-rw-r--r-- | lib/oxidized/config/core.rb | 33 | ||||
-rw-r--r-- | lib/oxidized/config/defaults.rb | 14 | ||||
-rw-r--r-- | lib/oxidized/core.rb | 2 | ||||
-rw-r--r-- | lib/oxidized/input/ssh.rb | 14 | ||||
-rw-r--r-- | lib/oxidized/input/telnet.rb | 2 | ||||
-rw-r--r-- | lib/oxidized/node.rb | 4 | ||||
-rw-r--r-- | lib/oxidized/nodes.rb | 2 | ||||
-rw-r--r-- | lib/oxidized/output/file.rb | 15 | ||||
-rw-r--r-- | lib/oxidized/output/git.rb | 23 | ||||
-rw-r--r-- | lib/oxidized/output/output.rb | 1 | ||||
-rw-r--r-- | lib/oxidized/source/csv.rb | 27 | ||||
-rw-r--r-- | lib/oxidized/source/source.rb | 1 | ||||
-rw-r--r-- | lib/oxidized/source/sql.rb | 29 | ||||
-rw-r--r-- | oxidized.gemspec | 1 |
19 files changed, 221 insertions, 206 deletions
@@ -12,10 +12,10 @@ * early days, but try: 1. apt-get install libsqlite3-dev libssl-dev 2. gem install oxidized - 3. oxidized + 3. oxidized -d 4. vi ~/.config/oxidized/config 5. (maybe point to your rancid/router.db or copy it there) - 6. oxidized + 6. oxidized -d # API ## Input @@ -50,63 +50,78 @@ ### Configuration I use in one environment ``` -[rancid@lan-login1 /var/rancid/.config/oxidized]% cat config --- -:username: LANA -:password: LANAAAAAAA -:output: - :default: git - :git: - :user: Oxidized - :email: o@example.com - :repo: "/usr/local/lan/oxidized.git" -:source: - :default: sql - :sql: - :adapter: sqlite - :file: "/usr/local/lan/corona.db" - :table: device - :map: - :name: ptr - :model: model -[rancid@lan-login1 /var/rancid/.config/oxidized]% +username: LANA +password: LANAAAAAAA +output: + default: git + git: + user: Oxidized + email: o@example.com + repo: "/usr/local/lan/oxidized.git" +source: + default: sql + sql: + adapter: sqlite + file: "/usr/local/lan/corona.db" + table: device + map: + name: ptr + model: model ``` -### Configuration you end up after first run (and it'll crash on missing router.d file) +### Configuration you end up after first run +If you don't configure output and source, it'll further fill them with example +configs for your chosen output/source in subsequent runs ``` --- -:username: username -:password: password -:model: junos -:interval: 3600 -:log: "/var/rancid/.config/oxidized/log" -:debug: false -:threads: 30 -:timeout: 5 -:prompt: !ruby/regexp /^([\w.@-]+[#>]\s?)$/ -:rest: 0.0.0.0:8888 -:vars: - :enable: enablePW -:input: - :default: ssh, telnet - :ssh: - :secure: false -:output: - :default: git -:source: - :default: csv - :csv: - :file: "/var/rancid/.config/oxidized/router.db" - :delimiter: !ruby/regexp /:/ - :map: - :name: 0 - :model: 1 -:model_map: +username: username +password: password +model: junos +interval: 3600 +log: "/home/fisakytt/.config/oxidized/log" +debug: false +threads: 30 +timeout: 30 +prompt: !ruby/regexp /^([\w.@-]+[#>]\s?)$/ +rest: 0.0.0.0:8888 +vars: {} +input: + default: ssh, telnet + ssh: + secure: false +output: + default: git +source: + default: csv +model_map: cisco: ios juniper: junos ``` + +Output and Source could be: +``` +output: + default: git + git: + user: Oxidized + email: o@example.com + repo: "/home/fisakytt/.config/oxidized/oxidized.git" +source: + default: csv + csv: + file: "/home/fisakytt/.config/oxidized/router.db" + delimiter: !ruby/regexp /:/ + map: + name: 0 + model: 1 +``` which reads nodes from rancid compatible router.db maps their model names to model names oxidized expects, stores config in git, will try ssh first then telnet, wont crash on changed ssh keys Hopefully most of them are obvious, log is ignored if Syslog::Logger exists (>=2.0) and syslog is used instead. +System wide configurations can be stored in /etc/oxidized/config, this might be +useful for storing for example source information, if many users are using +oxs/Oxidized::Script, which would allow user specific config only to include +username+password diff --git a/bin/oxidized b/bin/oxidized index 36ab250..f2fb9ff 100755 --- a/bin/oxidized +++ b/bin/oxidized @@ -1,19 +1,12 @@ #!/usr/bin/env ruby -trap("INT") { exit } # sinatra will otherwise steak this from us +# FIX ME, killing oxidized needs -9 +trap("INT") { exit } # sinatra will otherwise steal this from us begin - require 'oxidized' - Process.daemon unless $DEBUG - Oxidized.new -rescue => e - open Oxidized::Config::Crash, 'w' do |file| - file.puts '-' * 50 - file.puts Time.now.utc - file.puts e.message + ' [' + e.class.to_s + ']' - file.puts '-' * 50 - file.puts e.backtrace - file.puts '-' * 50 - end - warn "ERROR: #{e}" + require 'oxidized/cli' + Oxidized::CLI.new.run +rescue => error + warn "#{error}" + raise if Oxidized::CFG.debug end diff --git a/lib/oxidized/cli.rb b/lib/oxidized/cli.rb new file mode 100644 index 0000000..720f405 --- /dev/null +++ b/lib/oxidized/cli.rb @@ -0,0 +1,48 @@ +module Oxidized + class CLI + require 'oxidized' + require 'slop' + class CLIError < OxidizedError; end + class NoConfig < CLIError; end + + def run + Process.daemon unless CFG.debug + begin + Oxidized.new + rescue => error + crash error + raise + end + end + + private + + def initialize + if CFGS.system.empty? and CFGS.user.empty? + CFGS.user = CFGS.default + CFGS.save :user + raise NoConfig, 'edit ~/.config/oxidized/config' + end + _args, opts = parse_opts + CFG.debug = true if opts[:debug] + end + + def crash error + open Config::Crash, 'w' do |file| + file.puts '-' * 50 + file.puts Time.now.utc + file.puts error.message + ' [' + error.class.to_s + ']' + file.puts '-' * 50 + file.puts error.backtrace + file.puts '-' * 50 + end + end + + def parse_opts + opts = Slop.new(:help=>true) do + on 'd', 'debug', 'turn on debugging' + end + [opts.parse!, opts] + end + end +end diff --git a/lib/oxidized/config.rb b/lib/oxidized/config.rb new file mode 100644 index 0000000..475ae14 --- /dev/null +++ b/lib/oxidized/config.rb @@ -0,0 +1,44 @@ +module Oxidized + require 'asetus' + class Config + Root = File.join ENV['HOME'], '.config', 'oxidized' + Crash = File.join Root, 'crash' + InputDir = File.join Directory, %w(lib oxidized input) + OutputDir = File.join Directory, %w(lib oxidized output) + ModelDir = File.join Directory, %w(lib oxidized model) + SourceDir = File.join Directory, %w(lib oxidized source) + Sleep = 1 + end + class << self + attr_accessor :mgr + end + CFGS = Asetus.new :name=>'oxidized', :load=>false, :key_to_s=>true + CFGS.default.username = 'username' + CFGS.default.password = 'password' + CFGS.default.model = 'junos' + CFGS.default.interval = 3600 + CFGS.default.log = File.join Config::Root, 'log' + CFGS.default.debug = false + CFGS.default.threads = 30 + CFGS.default.timeout = 30 + CFGS.default.prompt = /^([\w.@-]+[#>]\s?)$/ + CFGS.default.rest = '0.0.0.0:8888' # or false to disable + CFGS.default.vars = {} # could be 'enable'=>'enablePW' + + CFGS.default.input.default = 'ssh, telnet' + CFGS.default.input.ssh.secure = false + + CFGS.default.output.default = 'git' + CFGS.default.source.default = 'csv' + + CFGS.default.model_map = { + 'cisco' => 'ios', + 'juniper' => 'junos', + } + + CFGS.load # load system+user configs, merge to Config.cfg + CFG = CFGS.cfg # convenienence, instead of Config.cfg.password, CFG.password + + Log.level = Logger::INFO unless CFG.debug + Log.file = CFG.log if CFG.log +end diff --git a/lib/oxidized/config/bootstrap.rb b/lib/oxidized/config/bootstrap.rb deleted file mode 100644 index 008d88a..0000000 --- a/lib/oxidized/config/bootstrap.rb +++ /dev/null @@ -1,33 +0,0 @@ -module Oxidized - require 'fileutils' - FileUtils.mkdir_p Config::Root - CFG.username = 'username' - CFG.password = 'password' - CFG.model = 'junos' - CFG.interval = 3600 - CFG.log = File.join Config::Root, 'log' - CFG.debug = false - CFG.threads = 30 - CFG.timeout = 30 - CFG.prompt = /^([\w.@-]+[#>]\s?)$/ - CFG.rest = '0.0.0.0:8888' - CFG.vars = { - #:enable => 'enablePW', - } - CFG.input = { - :default => 'ssh, telnet', - :ssh => { - :secure => false, - } - } - CFG.output = { - :default => 'git', - } - CFG.source = { - :default => 'csv', - } - CFG.model_map = { - 'cisco' => 'ios', - 'juniper' => 'junos', - } -end diff --git a/lib/oxidized/config/core.rb b/lib/oxidized/config/core.rb deleted file mode 100644 index 59ada3a..0000000 --- a/lib/oxidized/config/core.rb +++ /dev/null @@ -1,33 +0,0 @@ -module Oxidized - require 'ostruct' - require 'yaml' - class Config < OpenStruct - require 'oxidized/config/defaults' - # @param file [string] configuration file location - def initialize file=File.join(Config::Root, 'config') - super() - @file = file.to_s - end - # load config from file or bootstrap with built-ins - def load - if File.exists? @file - cfg = YAML.load_file @file - marshal_load marshal_dump.merge(cfg) - else - save - end - end - def defaults - require 'oxidized/config/bootstrap' - end - # save config to file - def save - File.write @file, YAML.dump(marshal_dump) - end - end - CFG = Config.new - CFG.defaults - CFG.load - Log.level = Logger::INFO unless CFG.debug - Log.file = CFG.log if CFG.log -end diff --git a/lib/oxidized/config/defaults.rb b/lib/oxidized/config/defaults.rb deleted file mode 100644 index c30143f..0000000 --- a/lib/oxidized/config/defaults.rb +++ /dev/null @@ -1,14 +0,0 @@ -module Oxidized - class Config - Root = File.join ENV['HOME'], '.config', 'oxidized' - Crash = File.join Root, 'crash' - InputDir = File.join Directory, %w(lib oxidized input) - OutputDir = File.join Directory, %w(lib oxidized output) - ModelDir = File.join Directory, %w(lib oxidized model) - SourceDir = File.join Directory, %w(lib oxidized source) - Sleep = 1 - end - class << self - attr_accessor :mgr - end -end diff --git a/lib/oxidized/core.rb b/lib/oxidized/core.rb index 14de9ab..3c728b6 100644 --- a/lib/oxidized/core.rb +++ b/lib/oxidized/core.rb @@ -1,6 +1,6 @@ module Oxidized require 'oxidized/log' - require 'oxidized/config/core' + require 'oxidized/config' require 'oxidized/worker' require 'oxidized/nodes' require 'oxidized/manager' diff --git a/lib/oxidized/input/ssh.rb b/lib/oxidized/input/ssh.rb index dae1d74..570f8a8 100644 --- a/lib/oxidized/input/ssh.rb +++ b/lib/oxidized/input/ssh.rb @@ -12,14 +12,14 @@ module Oxidized Net::SSH::AuthenticationFailed, ], } - include CLI + include Input::CLI class NoShell < OxidizedError; end def connect node @node = node @output = '' @node.model.cfg['ssh'].each { |cb| instance_exec(&cb) } - secure = CFG.input[:ssh][:secure] + secure = CFG.input.ssh.secure @ssh = Net::SSH.start @node.ip, @node.auth[:username], :password => @node.auth[:password], :timeout => CFG.timeout, :paranoid => secure @@ -64,14 +64,14 @@ module Oxidized def shell_open ssh @ses = ssh.open_channel do |ch| - ch.on_data do |ch, data| + ch.on_data do |_ch, data| @output << data @output = @node.model.expects @output end - ch.request_pty do |ch, success| - raise NoShell, "Can't get PTY" unless success - ch.send_channel_request 'shell' do |ch, success| - raise NoShell, "Can't get shell" unless success + ch.request_pty do |_ch, success_pty| + raise NoShell, "Can't get PTY" unless success_pty + ch.send_channel_request 'shell' do |_ch, success_shell| + raise NoShell, "Can't get shell" unless success_shell end end end diff --git a/lib/oxidized/input/telnet.rb b/lib/oxidized/input/telnet.rb index 4e80ceb..218b13c 100644 --- a/lib/oxidized/input/telnet.rb +++ b/lib/oxidized/input/telnet.rb @@ -3,7 +3,7 @@ module Oxidized require 'oxidized/input/cli' class Telnet < Input RescueFail = {} - include CLI + include Input::CLI attr_reader :telnet def connect node diff --git a/lib/oxidized/node.rb b/lib/oxidized/node.rb index 0cb5cb3..3ac1c90 100644 --- a/lib/oxidized/node.rb +++ b/lib/oxidized/node.rb @@ -106,7 +106,7 @@ module Oxidized end def resolve_input opt - inputs = (opt[:input] or CFG.input[:default]) + inputs = (opt[:input] or CFG.input.default) inputs.split(/\s*,\s*/).map do |input| if not Oxidized.mgr.input[input] Oxidized.mgr.add_input input or raise MethodNotFound, "#{input} not found for node #{ip}" @@ -116,7 +116,7 @@ module Oxidized end def resolve_output opt - output = (opt[:output] or CFG.output[:default]) + output = (opt[:output] or CFG.output.default) if not Oxidized.mgr.output[output] Oxidized.mgr.add_output output or raise MethodNotFound, "#{output} not found for node #{ip}" end diff --git a/lib/oxidized/nodes.rb b/lib/oxidized/nodes.rb index 8dadaef..213f534 100644 --- a/lib/oxidized/nodes.rb +++ b/lib/oxidized/nodes.rb @@ -10,7 +10,7 @@ module Oxidized with_lock do new = [] node_want_ip = (IPAddr.new(node_want) rescue nil) if node_want - @source = CFG.source[:default] + @source = CFG.source.default Oxidized.mgr.add_source @source Oxidized.mgr.source[@source].new.load.each do |node| diff --git a/lib/oxidized/output/file.rb b/lib/oxidized/output/file.rb index ee8a9a6..f3443b4 100644 --- a/lib/oxidized/output/file.rb +++ b/lib/oxidized/output/file.rb @@ -3,20 +3,19 @@ class OxFile < Output require 'fileutils' def initialize - @cfg = CFG.output[:file] + @cfg = CFG.output.file end def setup - if not @cfg - CFG.output[:file] = { - :directory => File.join(Config::Root, 'configs') - } - CFG.save + if @cfg.empty? + CFGS.user.output.file.directory = File.join(Config::Root, 'configs') + CFGS.save :user + raise NoConfig, 'no output file config, edit ~/.config/oxidized/config' end end def store node, data, opt={} - file = @cfg[:directory] + file = @cfg.directory if opt[:group] file = File.join File.dirname(file), opt[:group] end @@ -26,7 +25,7 @@ class OxFile < Output end def fetch node, group - cfg_dir = @cfg[:directory] + cfg_dir = @cfg.directory if group # group is explicitly defined by user IO.readlines File.join(cfg_dir, group, node) else diff --git a/lib/oxidized/output/git.rb b/lib/oxidized/output/git.rb index b4fa4a8..ff9237b 100644 --- a/lib/oxidized/output/git.rb +++ b/lib/oxidized/output/git.rb @@ -5,25 +5,24 @@ class Git < Output include Grit def initialize - @cfg = CFG.output[:git] + @cfg = CFG.output.git end def setup - if not @cfg - CFG.output[:git] = { - :user => 'Oxidized', - :email => 'o@example.com', - :repo => File.join(Config::Root, 'oxidized.git') - } - CFG.save + if @cfg.empty? + CFGS.user.output.git.user = 'Oxidized' + CFGS.user.output.git.email = 'o@example.com' + CFGS.user.output.git.repo = File.join(Config::Root, 'oxidized.git') + CFGS.save :user + raise NoConfig, 'no output git config, edit ~/.config/oxidized/config' end end def store file, data, opt={} msg = opt[:msg] - user = (opt[:user] or @cfg[:user]) - email = (opt[:email] or @cfg[:email]) - repo = @cfg[:repo] + user = (opt[:user] or @cfg.user) + email = (opt[:email] or @cfg.email) + repo = @cfg.repo if opt[:group] repo = File.join File.dirname(repo), opt[:group] + '.git' end @@ -41,7 +40,7 @@ class Git < Output def fetch node, group begin - repo = Repo.new(@cfg[:repo]) + repo = Repo.new(@cfg.repo) (repo.tree / node).data rescue 'node not found' diff --git a/lib/oxidized/output/output.rb b/lib/oxidized/output/output.rb index 61cb2b5..54d616c 100644 --- a/lib/oxidized/output/output.rb +++ b/lib/oxidized/output/output.rb @@ -1,5 +1,6 @@ module Oxidized class Output + class NoConfig < OxidizedError; end class << self def inherited klass Oxidized.mgr.loader = { :class => klass } diff --git a/lib/oxidized/source/csv.rb b/lib/oxidized/source/csv.rb index cf73fc8..9d291c7 100644 --- a/lib/oxidized/source/csv.rb +++ b/lib/oxidized/source/csv.rb @@ -1,31 +1,28 @@ module Oxidized class CSV < Source def initialize - @cfg = CFG.source[:csv] + @cfg = CFG.source.csv super end def setup - if not @cfg - CFG.source[:csv] = { - :file => File.join(Config::Root, 'router.db'), - :delimiter => /:/, - :map => { - :name => 0, - :model => 1, - } - } - CFG.save + if @cfg.empty? + CFGS.user.source.csv.file = File.join(Config::Root, 'router.db') + CFGS.user.source.csv.delimiter = /:/ + CFGS.user.source.csv.map.name = 0 + CFGS.user.source.csv.map.model = 1 + CFGS.save :user + raise NoConfig, 'no source csv config, edit ~/.config/oxidized/config' end end def load nodes = [] - open(@cfg[:file]).each_line do |line| - data = line.chomp.split @cfg[:delimiter] + open(@cfg.file).each_line do |line| + data = line.chomp.split @cfg.delimiter keys = {} - @cfg[:map].each do |key, position| - keys[key] = data[position] + @cfg.map.each do |key, position| + keys[key.to_sym] = data[position] end keys[:model] = map_model keys[:model] if keys.key? :model nodes << keys diff --git a/lib/oxidized/source/source.rb b/lib/oxidized/source/source.rb index f5976a0..93c9b6f 100644 --- a/lib/oxidized/source/source.rb +++ b/lib/oxidized/source/source.rb @@ -1,5 +1,6 @@ module Oxidized class Source + class NoConfig < OxidizedError; end class << self def inherited klass Oxidized.mgr.loader = { :class => klass } diff --git a/lib/oxidized/source/sql.rb b/lib/oxidized/source/sql.rb index 385f632..f84a7a8 100644 --- a/lib/oxidized/source/sql.rb +++ b/lib/oxidized/source/sql.rb @@ -4,34 +4,31 @@ class SQL < Source def initialize super - @cfg = CFG.source[:sql] + @cfg = CFG.source.sql end def setup - if not @cfg - CFG.source[:sql] = { - :adapter => 'sqlite', - :file => File.join(Config::Root, 'sqlite.db'), - :table => 'devices', - :map => { - :name => 'name', - :model => 'rancid', - } - } - CFG.save + if @cfg.empty? + CFGS.user.source.sql.adapter = 'sqlite' + CFGS.user.source.sql.file = File.join(Config::Root, 'sqlite.db') + CFGS.user.source.sql.table = 'devices' + CFGS.user.source.sql.map.name = 'name' + CFGS.user.source.sql.map.model = 'rancid' + CFGS.save :user + raise NoConfig, 'no source sql config, edit ~/.config/oxidized/config' end end def load nodes = [] - db = case @cfg[:adapter] + db = case @cfg.adapter when 'sqlite' require 'sqlite3' - Sequel.sqlite @cfg[:file] + Sequel.sqlite @cfg.file end - db[@cfg[:table].to_sym].each do |node| + db[@cfg.table.to_sym].each do |node| keys = {} - @cfg[:map].each { |key, sql_column| keys[key] = node[sql_column.to_sym] } + @cfg.map.each { |key, sql_column| keys[key.to_sym] = node[sql_column.to_sym] } keys[:model] = map_model keys[:model] if keys.key? :model nodes << keys end diff --git a/oxidized.gemspec b/oxidized.gemspec index 93d2674..2921d4b 100644 --- a/oxidized.gemspec +++ b/oxidized.gemspec @@ -21,5 +21,6 @@ Gem::Specification.new do |s| s.add_dependency 'puma' s.add_dependency 'haml' s.add_dependency 'sass' + s.add_dependency 'slop' end |