diff --git a/CHANGELOG.md b/CHANGELOG.md index 944edc68a..fd9d81338 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,7 +36,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). - edgecos.rb: hide temperature and fan speed (@dhooper6430) - cnos: show information before config, remove secrets only when told to do so (@robje) - Updated slackdiff.rb to use new files.getUploadURLExternal slack file upload API instead of deprecated files.upload (@varesa) -- Updated source files to reference a Source module to avoid namespace duplication +- Updated source/output files to reference a Source/Outputb module to avoid namespace duplication (@laf, @robertcheramy) ### Fixed - fixed error for ibos when remove_secret is set (@dminuoso) diff --git a/lib/oxidized/manager.rb b/lib/oxidized/manager.rb index 0858814fd..9116245bc 100644 --- a/lib/oxidized/manager.rb +++ b/lib/oxidized/manager.rb @@ -5,19 +5,16 @@ module Oxidized require 'oxidized/source/source' class Manager class << self - def load(dir, file) + def load(dir, file, namespace) require File.join dir, file + '.rb' - klass = nil - # Search the object in the different namespaces - # - Most objects are in the namespace Oxidized - # - Source objects have their own namespace (Oxidized::Source) - # - Models have no namespace (Object) - [Oxidized, Oxidized::Source, Object].each do |mod| - klass = mod.constants.find { |const| const.to_s.casecmp(file).zero? } - klass ||= mod.constants.find { |const| const.to_s.downcase == 'oxidized' + file.downcase } - klass = mod.const_get klass if klass - break if klass - end + + # Search the object to load in namespace + klass = namespace.constants.find { |const| const.to_s.casecmp(file).zero? } + + return false unless klass + + klass = namespace.const_get klass + i = klass.new i.setup if i.respond_to? :setup { file => klass } @@ -37,32 +34,32 @@ def initialize end def add_input(name) - loader @input, Config::INPUT_DIR, "input", name + loader @input, Config::INPUT_DIR, "input", name, Oxidized end def add_output(name) - loader @output, Config::OUTPUT_DIR, "output", name + loader @output, Config::OUTPUT_DIR, "output", name, Oxidized::Output end def add_source(name) - loader @source, Config::SOURCE_DIR, "source", name + loader @source, Config::SOURCE_DIR, "source", name, Oxidized::Source end def add_model(name) - loader @model, Config::MODEL_DIR, "model", name + loader @model, Config::MODEL_DIR, "model", name, Object end def add_hook(name) - loader @hook, Config::HOOK_DIR, "hook", name + loader @hook, Config::HOOK_DIR, "hook", name, Object end private # if local version of file exists, load it, else load global - return falsy value if nothing loaded - def loader(hash, global_dir, local_dir, name) + def loader(hash, global_dir, local_dir, name, namespace) dir = File.join(Config::ROOT, local_dir) - map = Manager.load(dir, name) if File.exist? File.join(dir, name + ".rb") - map ||= Manager.load(global_dir, name) + map = Manager.load(dir, name, namespace) if File.exist? File.join(dir, name + ".rb") + map ||= Manager.load(global_dir, name, namespace) hash.merge!(map) if map end end diff --git a/lib/oxidized/output/file.rb b/lib/oxidized/output/file.rb index c84f604d1..e770148ee 100644 --- a/lib/oxidized/output/file.rb +++ b/lib/oxidized/output/file.rb @@ -1,55 +1,58 @@ module Oxidized - class OxidizedFile < Output - require 'fileutils' + module Output + # ruby's File class must be accessed with ::File to avoid name conflicts + class File < Output + require 'fileutils' - attr_reader :commitref + attr_reader :commitref - def initialize - super - @cfg = Oxidized.config.output.file - end + def initialize + super + @cfg = Oxidized.config.output.file + end - def setup - return unless @cfg.empty? + def setup + return unless @cfg.empty? - Oxidized.asetus.user.output.file.directory = File.join(Config::ROOT, 'configs') - Oxidized.asetus.save :user - raise NoConfig, "no output file config, edit #{Oxidized::Config.configfile}" - end + Oxidized.asetus.user.output.file.directory = ::File.join(Config::ROOT, 'configs') + Oxidized.asetus.save :user + raise NoConfig, "no output file config, edit #{Oxidized::Config.configfile}" + end - def store(node, outputs, opt = {}) - file = File.expand_path @cfg.directory - file = File.join File.dirname(file), opt[:group] if opt[:group] - FileUtils.mkdir_p file - file = File.join file, node - File.write(file, outputs.to_cfg) - @commitref = file - end + def store(node, outputs, opt = {}) + file = ::File.expand_path @cfg.directory + file = ::File.join ::File.dirname(file), opt[:group] if opt[:group] + FileUtils.mkdir_p file + file = ::File.join file, node + ::File.write(file, outputs.to_cfg) + @commitref = file + end - def fetch(node, group) - cfg_dir = File.expand_path @cfg.directory - node_name = node.name - - if group # group is explicitly defined by user - cfg_dir = File.join File.dirname(cfg_dir), group - File.read File.join(cfg_dir, node_name) - elsif File.exist? File.join(cfg_dir, node_name) # node configuration file is stored on base directory - File.read File.join(cfg_dir, node_name) - else - path = Dir.glob(File.join(File.dirname(cfg_dir), '**', node_name)).first # fetch node in all groups - File.read path + def fetch(node, group) + cfg_dir = ::File.expand_path @cfg.directory + node_name = node.name + + if group # group is explicitly defined by user + cfg_dir = ::File.join ::File.dirname(cfg_dir), group + ::File.read ::File.join(cfg_dir, node_name) + elsif ::File.exist? ::File.join(cfg_dir, node_name) # node configuration file is stored on base directory + ::File.read ::File.join(cfg_dir, node_name) + else + path = Dir.glob(::File.join(::File.dirname(cfg_dir), '**', node_name)).first # fetch node in all groups + ::File.read path + end + rescue Errno::ENOENT + nil end - rescue Errno::ENOENT - nil - end - def version(_node, _group) - # not supported - [] - end + def version(_node, _group) + # not supported + [] + end - def get_version(_node, _group, _oid) - 'not supported' + def get_version(_node, _group, _oid) + 'not supported' + end end end end diff --git a/lib/oxidized/output/git.rb b/lib/oxidized/output/git.rb index 4237071da..e3f2d1586 100644 --- a/lib/oxidized/output/git.rb +++ b/lib/oxidized/output/git.rb @@ -1,218 +1,220 @@ module Oxidized - class Git < Output - using Refinements - - class GitError < OxidizedError; end - begin - require 'rugged' - rescue LoadError - raise OxidizedError, 'rugged not found: sudo gem install rugged' - end + module Output + class Git < Output + using Refinements - attr_reader :commitref + class GitError < OxidizedError; end + begin + require 'rugged' + rescue LoadError + raise OxidizedError, 'rugged not found: sudo gem install rugged' + end - def initialize - super - @cfg = Oxidized.config.output.git - end + attr_reader :commitref - def setup - if @cfg.empty? - Oxidized.asetus.user.output.git.user = 'Oxidized' - Oxidized.asetus.user.output.git.email = 'o@example.com' - Oxidized.asetus.user.output.git.repo = File.join(Config::ROOT, 'oxidized.git') - Oxidized.asetus.save :user - raise NoConfig, "no output git config, edit #{Oxidized::Config.configfile}" + def initialize + super + @cfg = Oxidized.config.output.git end - if @cfg.repo.respond_to?(:each) - @cfg.repo.each do |group, repo| - @cfg.repo["#{group}="] = File.expand_path repo + def setup + if @cfg.empty? + Oxidized.asetus.user.output.git.user = 'Oxidized' + Oxidized.asetus.user.output.git.email = 'o@example.com' + Oxidized.asetus.user.output.git.repo = File.join(Config::ROOT, 'oxidized.git') + Oxidized.asetus.save :user + raise NoConfig, "no output git config, edit #{Oxidized::Config.configfile}" end - else - @cfg.repo = File.expand_path @cfg.repo - end - end - def store(file, outputs, opt = {}) - @msg = opt[:msg] - @user = opt[:user] || @cfg.user - @email = opt[:email] || @cfg.email - @opt = opt - @commitref = nil - repo = @cfg.repo - - outputs.types.each do |type| - type_cfg = '' - type_repo = File.join(File.dirname(repo), type + '.git') - outputs.type(type).each do |output| - (type_cfg << output; next) unless output.name # rubocop:disable Style/Semicolon - type_file = file + '--' + output.name - if @cfg.type_as_directory? - type_file = type + '/' + type_file - type_repo = repo + if @cfg.repo.respond_to?(:each) + @cfg.repo.each do |group, repo| + @cfg.repo["#{group}="] = File.expand_path repo end - update type_repo, type_file, output + else + @cfg.repo = File.expand_path @cfg.repo end - update type_repo, file, type_cfg end - update repo, file, outputs.to_cfg - end + def store(file, outputs, opt = {}) + @msg = opt[:msg] + @user = opt[:user] || @cfg.user + @email = opt[:email] || @cfg.email + @opt = opt + @commitref = nil + repo = @cfg.repo + + outputs.types.each do |type| + type_cfg = '' + type_repo = File.join(File.dirname(repo), type + '.git') + outputs.type(type).each do |output| + (type_cfg << output; next) unless output.name # rubocop:disable Style/Semicolon + type_file = file + '--' + output.name + if @cfg.type_as_directory? + type_file = type + '/' + type_file + type_repo = repo + end + update type_repo, type_file, output + end + update type_repo, file, type_cfg + end - # Returns the configuration of group/node_name - # - # #fetch is called by Nodes#fetch - # Nodes#fetch creates a new Output object each time, so we cannot - # store the repo index in memory. But as we keep the repo index up - # to date on disk in #update_repo, we can read it from disk instead of - # rebuilding it each time. - def fetch(node, group) - repo, path = yield_repo_and_path(node, group) - repo = Rugged::Repository.new repo - # Read the index from disk - index = repo.index - - repo.read(index.get(path)[:oid]).data - rescue StandardError - 'node not found' - end + update repo, file, outputs.to_cfg + end - # give a hash of all oid revision for the given node, and the date of the commit - def version(node, group) - repo, path = yield_repo_and_path(node, group) - - repo = Rugged::Repository.new repo - walker = Rugged::Walker.new(repo) - walker.sorting(Rugged::SORT_DATE) - walker.push(repo.head.target.oid) - i = -1 - tab = [] - walker.each do |commit| - # Diabled rubocop because the suggested .empty? does not work here. - next if commit.diff(paths: [path]).size.zero? # rubocop:disable Style/ZeroLengthPredicate - - hash = {} - hash[:date] = commit.time.to_s - hash[:oid] = commit.oid - hash[:author] = commit.author - hash[:message] = commit.message - tab[i += 1] = hash + # Returns the configuration of group/node_name + # + # #fetch is called by Nodes#fetch + # Nodes#fetch creates a new Output object each time, so we cannot + # store the repo index in memory. But as we keep the repo index up + # to date on disk in #update_repo, we can read it from disk instead of + # rebuilding it each time. + def fetch(node, group) + repo, path = yield_repo_and_path(node, group) + repo = Rugged::Repository.new repo + # Read the index from disk + index = repo.index + + repo.read(index.get(path)[:oid]).data + rescue StandardError + 'node not found' end - walker.reset - tab - rescue StandardError - 'node not found' - end - # give the blob of a specific revision - def get_version(node, group, oid) - repo, path = yield_repo_and_path(node, group) - repo = Rugged::Repository.new repo - repo.blob_at(oid, path).content - rescue StandardError - 'version not found' - end + # give a hash of all oid revision for the given node, and the date of the commit + def version(node, group) + repo, path = yield_repo_and_path(node, group) - # give a hash with the patch of a diff between 2 revision and the stats (added and deleted lines) - def get_diff(node, group, oid1, oid2) - diff_commits = nil - repo, = yield_repo_and_path(node, group) - repo = Rugged::Repository.new repo - commit = repo.lookup(oid1) - - if oid2 - commit_old = repo.lookup(oid2) - diff = repo.diff(commit_old, commit) - diff.each do |patch| - if /#{node.name}\s+/ =~ patch.to_s.lines.first - diff_commits = { patch: patch.to_s, stat: patch.stat } - break - end + repo = Rugged::Repository.new repo + walker = Rugged::Walker.new(repo) + walker.sorting(Rugged::SORT_DATE) + walker.push(repo.head.target.oid) + i = -1 + tab = [] + walker.each do |commit| + # Diabled rubocop because the suggested .empty? does not work here. + next if commit.diff(paths: [path]).size.zero? # rubocop:disable Style/ZeroLengthPredicate + + hash = {} + hash[:date] = commit.time.to_s + hash[:oid] = commit.oid + hash[:author] = commit.author + hash[:message] = commit.message + tab[i += 1] = hash end - else - stat = commit.parents[0].diff(commit).stat - stat = [stat[1], stat[2]] - patch = commit.parents[0].diff(commit).patch - diff_commits = { patch: patch, stat: stat } + walker.reset + tab + rescue StandardError + 'node not found' end - diff_commits - rescue StandardError - 'no diffs' - end + # give the blob of a specific revision + def get_version(node, group, oid) + repo, path = yield_repo_and_path(node, group) + repo = Rugged::Repository.new repo + repo.blob_at(oid, path).content + rescue StandardError + 'version not found' + end - private + # give a hash with the patch of a diff between 2 revision and the stats (added and deleted lines) + def get_diff(node, group, oid1, oid2) + diff_commits = nil + repo, = yield_repo_and_path(node, group) + repo = Rugged::Repository.new repo + commit = repo.lookup(oid1) + + if oid2 + commit_old = repo.lookup(oid2) + diff = repo.diff(commit_old, commit) + diff.each do |patch| + if /#{node.name}\s+/ =~ patch.to_s.lines.first + diff_commits = { patch: patch.to_s, stat: patch.stat } + break + end + end + else + stat = commit.parents[0].diff(commit).stat + stat = [stat[1], stat[2]] + patch = commit.parents[0].diff(commit).patch + diff_commits = { patch: patch, stat: stat } + end - def yield_repo_and_path(node, group) - repo, path = node.repo, node.name + diff_commits + rescue StandardError + 'no diffs' + end - path = "#{group}/#{node.name}" if group && !group.empty? && @cfg.single_repo? + private - [repo, path] - end + def yield_repo_and_path(node, group) + repo, path = node.repo, node.name - def update(repo, file, data) - return if data.empty? + path = "#{group}/#{node.name}" if group && !group.empty? && @cfg.single_repo? - if @opt[:group] - if @cfg.single_repo? - file = File.join @opt[:group], file - else - repo = if repo.is_a?(::String) - File.join File.dirname(repo), @opt[:group] + '.git' - else - repo[@opt[:group]] - end - end + [repo, path] end - begin - repo = Rugged::Repository.new repo - update_repo repo, file, data - rescue Rugged::OSError, Rugged::RepositoryError => e + def update(repo, file, data) + return if data.empty? + + if @opt[:group] + if @cfg.single_repo? + file = File.join @opt[:group], file + else + repo = if repo.is_a?(::String) + File.join File.dirname(repo), @opt[:group] + '.git' + else + repo[@opt[:group]] + end + end + end + begin - Rugged::Repository.init_at repo, :bare - rescue StandardError => create_error - raise GitError, "first '#{e.message}' was raised while opening git repo, then '#{create_error.message}' was while trying to create git repo" + repo = Rugged::Repository.new repo + update_repo repo, file, data + rescue Rugged::OSError, Rugged::RepositoryError => e + begin + Rugged::Repository.init_at repo, :bare + rescue StandardError => create_error + raise GitError, "first '#{e.message}' was raised while opening git repo, then '#{create_error.message}' was while trying to create git repo" + end + retry end - retry end - end - # Uploads data into file in the repo - # - # @param [String] file: the file to save the configuration to - # @param [String] data: the configuration to save - # @param [Rugged::Repository] repo: the git repository to use - # - # If Oxidized.config.output.git.single_repo = false (which is the default), - # there will one repository for each group. - # - # update_repo caches the index on disk. An index is usually used in a - # working directory and not in a bare repository, which confuses users. - # The alternative would be to rebuild the index each time, which a little - # time consuming. Caching the index in memory is difficult because a new - # Output object is created each time #store is called. - def update_repo(repo, file, data) - oid_old = repo.blob_at(repo.head.target_id, file) rescue nil - return false if oid_old && (oid_old.content.b == data.b) - - oid = repo.write data, :blob - # Read the index from disk - index = repo.index - index.add path: file, oid: oid, mode: 0o100644 - - repo.config['user.name'] = @user - repo.config['user.email'] = @email - @commitref = Rugged::Commit.create(repo, - tree: index.write_tree(repo), - message: @msg, - parents: repo.empty? ? [] : [repo.head.target].compact, - update_ref: 'HEAD') - - index.write - true + # Uploads data into file in the repo + # + # @param [String] file: the file to save the configuration to + # @param [String] data: the configuration to save + # @param [Rugged::Repository] repo: the git repository to use + # + # If Oxidized.config.output.git.single_repo = false (which is the default), + # there will one repository for each group. + # + # update_repo caches the index on disk. An index is usually used in a + # working directory and not in a bare repository, which confuses users. + # The alternative would be to rebuild the index each time, which a little + # time consuming. Caching the index in memory is difficult because a new + # Output object is created each time #store is called. + def update_repo(repo, file, data) + oid_old = repo.blob_at(repo.head.target_id, file) rescue nil + return false if oid_old && (oid_old.content.b == data.b) + + oid = repo.write data, :blob + # Read the index from disk + index = repo.index + index.add path: file, oid: oid, mode: 0o100644 + + repo.config['user.name'] = @user + repo.config['user.email'] = @email + @commitref = Rugged::Commit.create(repo, + tree: index.write_tree(repo), + message: @msg, + parents: repo.empty? ? [] : [repo.head.target].compact, + update_ref: 'HEAD') + + index.write + true + end end end end diff --git a/lib/oxidized/output/gitcrypt.rb b/lib/oxidized/output/gitcrypt.rb index 03e91fe3d..b23ec44a3 100644 --- a/lib/oxidized/output/gitcrypt.rb +++ b/lib/oxidized/output/gitcrypt.rb @@ -1,227 +1,229 @@ module Oxidized - class GitCrypt < Output - using Refinements - class GitCryptError < OxidizedError; end - begin - require 'git' - rescue LoadError - raise OxidizedError, 'git not found: sudo gem install git' - end - - attr_reader :commitref + module Output + class GitCrypt < Output + using Refinements + class GitCryptError < OxidizedError; end + begin + require 'git' + rescue LoadError + raise OxidizedError, 'git not found: sudo gem install git' + end - def initialize - super - @cfg = Oxidized.config.output.gitcrypt - @gitcrypt_cmd = "/usr/bin/git-crypt" - @gitcrypt_init = @gitcrypt_cmd + " init" - @gitcrypt_unlock = @gitcrypt_cmd + " unlock" - @gitcrypt_lock = @gitcrypt_cmd + " lock" - @gitcrypt_adduser = @gitcrypt_cmd + " add-gpg-user --trusted " - end + attr_reader :commitref - def setup - if @cfg.empty? - Oxidized.asetus.user.output.gitcrypt.user = 'Oxidized' - Oxidized.asetus.user.output.gitcrypt.email = 'o@example.com' - Oxidized.asetus.user.output.gitcrypt.repo = File.join(Config::ROOT, 'oxidized.git') - Oxidized.asetus.save :user - raise NoConfig, "no output git config, edit #{Oxidized::Config.configfile}" + def initialize + super + @cfg = Oxidized.config.output.gitcrypt + @gitcrypt_cmd = "/usr/bin/git-crypt" + @gitcrypt_init = @gitcrypt_cmd + " init" + @gitcrypt_unlock = @gitcrypt_cmd + " unlock" + @gitcrypt_lock = @gitcrypt_cmd + " lock" + @gitcrypt_adduser = @gitcrypt_cmd + " add-gpg-user --trusted " end - if @cfg.repo.respond_to?(:each) - @cfg.repo.each do |group, repo| - @cfg.repo["#{group}="] = File.expand_path repo + def setup + if @cfg.empty? + Oxidized.asetus.user.output.gitcrypt.user = 'Oxidized' + Oxidized.asetus.user.output.gitcrypt.email = 'o@example.com' + Oxidized.asetus.user.output.gitcrypt.repo = File.join(Config::ROOT, 'oxidized.git') + Oxidized.asetus.save :user + raise NoConfig, "no output git config, edit #{Oxidized::Config.configfile}" + end + + if @cfg.repo.respond_to?(:each) + @cfg.repo.each do |group, repo| + @cfg.repo["#{group}="] = File.expand_path repo + end + else + @cfg.repo = File.expand_path @cfg.repo end - else - @cfg.repo = File.expand_path @cfg.repo end - end - def crypt_init(repo) - repo.chdir do - system(@gitcrypt_init) - @cfg.users.each do |user| - system("#{@gitcrypt_adduser} #{user}") + def crypt_init(repo) + repo.chdir do + system(@gitcrypt_init) + @cfg.users.each do |user| + system("#{@gitcrypt_adduser} #{user}") + end + File.write(".gitattributes", "* filter=git-crypt diff=git-crypt\n.gitattributes !filter !diff") + repo.add(".gitattributes") + repo.commit("Initial commit: crypt all config files") end - File.write(".gitattributes", "* filter=git-crypt diff=git-crypt\n.gitattributes !filter !diff") - repo.add(".gitattributes") - repo.commit("Initial commit: crypt all config files") end - end - def lock(repo) - repo.chdir do - system(@gitcrypt_lock) + def lock(repo) + repo.chdir do + system(@gitcrypt_lock) + end end - end - def unlock(repo) - repo.chdir do - system(@gitcrypt_unlock) + def unlock(repo) + repo.chdir do + system(@gitcrypt_unlock) + end end - end - def store(file, outputs, opt = {}) - @msg = opt[:msg] - @user = opt[:user] || @cfg.user - @email = opt[:email] || @cfg.email - @opt = opt - @commitref = nil - repo = @cfg.repo - - outputs.types.each do |type| - type_cfg = '' - type_repo = File.join(File.dirname(repo), type + '.git') - outputs.type(type).each do |output| - (type_cfg << output; next) unless output.name # rubocop:disable Style/Semicolon - type_file = file + '--' + output.name - if @cfg.type_as_directory? - type_file = type + '/' + type_file - type_repo = repo + def store(file, outputs, opt = {}) + @msg = opt[:msg] + @user = opt[:user] || @cfg.user + @email = opt[:email] || @cfg.email + @opt = opt + @commitref = nil + repo = @cfg.repo + + outputs.types.each do |type| + type_cfg = '' + type_repo = File.join(File.dirname(repo), type + '.git') + outputs.type(type).each do |output| + (type_cfg << output; next) unless output.name # rubocop:disable Style/Semicolon + type_file = file + '--' + output.name + if @cfg.type_as_directory? + type_file = type + '/' + type_file + type_repo = repo + end + update type_repo, type_file, output end - update type_repo, type_file, output + update type_repo, file, type_cfg end - update type_repo, file, type_cfg - end - update repo, file, outputs.to_cfg - end + update repo, file, outputs.to_cfg + end - def fetch(node, group) - repo, path = yield_repo_and_path(node, group) - repo = Git.open repo - unlock repo - index = repo.index - # Empty repo ? - raise 'Empty git repo' if File.exist?(index.path) - - File.read path - lock repo - rescue StandardError - 'node not found' - end + def fetch(node, group) + repo, path = yield_repo_and_path(node, group) + repo = Git.open repo + unlock repo + index = repo.index + # Empty repo ? + raise 'Empty git repo' if File.exist?(index.path) + + File.read path + lock repo + rescue StandardError + 'node not found' + end - # give a hash of all oid revision for the given node, and the date of the commit - def version(node, group) - repo, path = yield_repo_and_path(node, group) - - repo = Git.open repo - unlock repo - walker = repo.log.path(path) - i = -1 - tab = [] - walker.each do |commit| - hash = {} - hash[:date] = commit.date.to_s - hash[:oid] = commit.objectish - hash[:author] = commit.author - hash[:message] = commit.message - tab[i += 1] = hash + # give a hash of all oid revision for the given node, and the date of the commit + def version(node, group) + repo, path = yield_repo_and_path(node, group) + + repo = Git.open repo + unlock repo + walker = repo.log.path(path) + i = -1 + tab = [] + walker.each do |commit| + hash = {} + hash[:date] = commit.date.to_s + hash[:oid] = commit.objectish + hash[:author] = commit.author + hash[:message] = commit.message + tab[i += 1] = hash + end + walker.reset + tab + rescue StandardError + 'node not found' end - walker.reset - tab - rescue StandardError - 'node not found' - end - # give the blob of a specific revision - def get_version(node, group, oid) - repo, path = yield_repo_and_path(node, group) - repo = Git.open repo - unlock repo - repo.gtree(oid).files[path].contents - rescue StandardError - 'version not found' - ensure - lock repo - end + # give the blob of a specific revision + def get_version(node, group, oid) + repo, path = yield_repo_and_path(node, group) + repo = Git.open repo + unlock repo + repo.gtree(oid).files[path].contents + rescue StandardError + 'version not found' + ensure + lock repo + end - # give a hash with the patch of a diff between 2 revision and the stats (added and deleted lines) - def get_diff(node, group, oid1, oid2) - diff_commits = nil - repo, _path = yield_repo_and_path(node, group) - repo = Git.open repo - unlock repo - commit = repo.gcommit(oid1) - - if oid2 - commit_old = repo.gcommit(oid2) - diff = repo.diff(commit_old, commit) - stats = [diff.stats[:files][node.name][:insertions], diff.stats[:files][node.name][:deletions]] - diff.each do |patch| - if /#{node.name}\s+/ =~ patch.patch.to_s.lines.first - diff_commits = { patch: patch.patch.to_s, stat: stats } - break + # give a hash with the patch of a diff between 2 revision and the stats (added and deleted lines) + def get_diff(node, group, oid1, oid2) + diff_commits = nil + repo, _path = yield_repo_and_path(node, group) + repo = Git.open repo + unlock repo + commit = repo.gcommit(oid1) + + if oid2 + commit_old = repo.gcommit(oid2) + diff = repo.diff(commit_old, commit) + stats = [diff.stats[:files][node.name][:insertions], diff.stats[:files][node.name][:deletions]] + diff.each do |patch| + if /#{node.name}\s+/ =~ patch.patch.to_s.lines.first + diff_commits = { patch: patch.patch.to_s, stat: stats } + break + end end + else + stat = commit.parents[0].diff(commit).stats + stat = [stat[:files][node.name][:insertions], stat[:files][node.name][:deletions]] + patch = commit.parents[0].diff(commit).patch + diff_commits = { patch: patch, stat: stat } end - else - stat = commit.parents[0].diff(commit).stats - stat = [stat[:files][node.name][:insertions], stat[:files][node.name][:deletions]] - patch = commit.parents[0].diff(commit).patch - diff_commits = { patch: patch, stat: stat } + lock repo + diff_commits + rescue StandardError + 'no diffs' + ensure + lock repo end - lock repo - diff_commits - rescue StandardError - 'no diffs' - ensure - lock repo - end - - private - def yield_repo_and_path(node, group) - repo, path = node.repo, node.name + private - path = "#{group}/#{node.name}" if group && @cfg.single_repo? + def yield_repo_and_path(node, group) + repo, path = node.repo, node.name - [repo, path] - end + path = "#{group}/#{node.name}" if group && @cfg.single_repo? - def update(repo, file, data) - return if data.empty? + [repo, path] + end - if @opt[:group] - if @cfg.single_repo? - file = File.join @opt[:group], file - else - repo = if repo.is_a?(::String) - File.join File.dirname(repo), @opt[:group] + '.git' - else - repo[@opt[:group]] - end + def update(repo, file, data) + return if data.empty? + + if @opt[:group] + if @cfg.single_repo? + file = File.join @opt[:group], file + else + repo = if repo.is_a?(::String) + File.join File.dirname(repo), @opt[:group] + '.git' + else + repo[@opt[:group]] + end + end end - end - begin - update_repo repo, file, data, @msg, @user, @email - rescue Git::GitExecuteError, ArgumentError => e - Oxidized.logger.debug "open_error #{e} #{file}" begin - grepo = Git.init repo - crypt_init grepo - rescue StandardError => create_error - raise GitCryptError, "first '#{e.message}' was raised while opening git repo, then '#{create_error.message}' was while trying to create git repo" + update_repo repo, file, data, @msg, @user, @email + rescue Git::GitExecuteError, ArgumentError => e + Oxidized.logger.debug "open_error #{e} #{file}" + begin + grepo = Git.init repo + crypt_init grepo + rescue StandardError => create_error + raise GitCryptError, "first '#{e.message}' was raised while opening git repo, then '#{create_error.message}' was while trying to create git repo" + end + retry end - retry end - end - def update_repo(repo, file, data, msg, user, email) - grepo = Git.open repo - grepo.config('user.name', user) - grepo.config('user.email', email) - grepo.chdir do - unlock grepo - File.write(file, data) - grepo.add(file) - if grepo.status[file].nil? || !grepo.status[file].type.nil? - grepo.commit(msg) - @commitref = grepo.log(1).first.objectish - true + def update_repo(repo, file, data, msg, user, email) + grepo = Git.open repo + grepo.config('user.name', user) + grepo.config('user.email', email) + grepo.chdir do + unlock grepo + File.write(file, data) + grepo.add(file) + if grepo.status[file].nil? || !grepo.status[file].type.nil? + grepo.commit(msg) + @commitref = grepo.log(1).first.objectish + true + end + lock grepo end - lock grepo end end end diff --git a/lib/oxidized/output/http.rb b/lib/oxidized/output/http.rb index af9cb5fe7..d22416171 100644 --- a/lib/oxidized/output/http.rb +++ b/lib/oxidized/output/http.rb @@ -1,61 +1,63 @@ module Oxidized - class Http < Output - attr_reader :commitref + module Output + class Http < Output + attr_reader :commitref - def initialize - super - @cfg = Oxidized.config.output.http - end + def initialize + super + @cfg = Oxidized.config.output.http + end - def setup - return unless @cfg.empty? + def setup + return unless @cfg.empty? - CFGS.user.output.http.user = 'Oxidized' - CFGS.user.output.http.pasword = 'secret' - CFGS.user.output.http.url = 'http://localhost/web-api/oxidized' - CFGS.save :user - raise NoConfig, "no output http config, edit #{Oxidized::Config.configfile}" - end + Oxidized.asetus.user.output.http.user = 'Oxidized' + Oxidized.asetus.user.output.http.pasword = 'secret' + Oxidized.asetus.user.output.http.url = 'http://localhost/web-api/oxidized' + Oxidized.asetus.save :user + raise NoConfig, "no output http config, edit #{Oxidized::Config.configfile}" + end - require "net/http" - require "uri" - require "json" - - def store(node, outputs, opt = {}) - @commitref = nil - uri = URI.parse @cfg.url - http = Net::HTTP.new uri.host, uri.port - # http.use_ssl = true if uri.scheme = 'https' - req = Net::HTTP::Post.new(uri.request_uri, 'Content-Type' => 'application/json') - req.basic_auth @cfg.user, @cfg.password - req.body = generate_json(node, outputs, opt) - response = http.request req - - case response.code.to_i - when 200 || 201 - Oxidized.logger.info "Configuration http backup complete for #{node}" - p [:success] - when (400..499) - Oxidized.logger.info "Configuration http backup for #{node} failed status: #{response.body}" - p [:bad_request] - when (500..599) - p [:server_problems] - Oxidized.logger.info "Configuration http backup for #{node} failed status: #{response.body}" + require "net/http" + require "uri" + require "json" + + def store(node, outputs, opt = {}) + @commitref = nil + uri = URI.parse @cfg.url + http = Net::HTTP.new uri.host, uri.port + # http.use_ssl = true if uri.scheme = 'https' + req = Net::HTTP::Post.new(uri.request_uri, 'Content-Type' => 'application/json') + req.basic_auth @cfg.user, @cfg.password + req.body = generate_json(node, outputs, opt) + response = http.request req + + case response.code.to_i + when 200 || 201 + Oxidized.logger.info "Configuration http backup complete for #{node}" + p [:success] + when (400..499) + Oxidized.logger.info "Configuration http backup for #{node} failed status: #{response.body}" + p [:bad_request] + when (500..599) + p [:server_problems] + Oxidized.logger.info "Configuration http backup for #{node} failed status: #{response.body}" + end end - end - private - - def generate_json(node, outputs, opt) - JSON.pretty_generate( - 'msg' => opt[:msg], - 'user' => opt[:user], - 'email' => opt[:email], - 'group' => opt[:group], - 'node' => node, - 'config' => outputs.to_cfg - # actually we need to also iterate outputs, for other types like in gitlab. But most people don't use 'type' functionality. - ) + private + + def generate_json(node, outputs, opt) + JSON.pretty_generate( + 'msg' => opt[:msg], + 'user' => opt[:user], + 'email' => opt[:email], + 'group' => opt[:group], + 'node' => node, + 'config' => outputs.to_cfg + # actually we need to also iterate outputs, for other types like in gitlab. But most people don't use 'type' functionality. + ) + end end end end diff --git a/lib/oxidized/output/output.rb b/lib/oxidized/output/output.rb index 742daf3da..a10e0f524 100644 --- a/lib/oxidized/output/output.rb +++ b/lib/oxidized/output/output.rb @@ -1,9 +1,11 @@ module Oxidized - class Output - class NoConfig < OxidizedError; end + module Output + class Output + class NoConfig < OxidizedError; end - def cfg_to_str(cfg) - cfg.select { |h| h[:type] == 'cfg' }.map { |h| h[:data] }.join + def cfg_to_str(cfg) + cfg.select { |h| h[:type] == 'cfg' }.map { |h| h[:data] }.join + end end end end diff --git a/spec/manager_spec.rb b/spec/manager_spec.rb index 87e13b116..f51975ed6 100644 --- a/spec/manager_spec.rb +++ b/spec/manager_spec.rb @@ -13,6 +13,21 @@ Oxidized.config.source.csv.map.name = '0' result = Oxidized.mgr.add_source('csv') _(result['csv']).must_equal Oxidized::Source::CSV + result = Oxidized.mgr.add_source('http') + _(result['http']).must_equal Oxidized::Source::HTTP + end + + it 'differentiates between http classes' do + Oxidized.config.source.http.url = 'http://localhost/nodes' + result = Oxidized.mgr.add_source('http') + _(result['http']).must_equal Oxidized::Source::HTTP + + result = Oxidized.mgr.add_input('http') + _(result['http']).must_equal Oxidized::HTTP + + Oxidized.config.output.http.url = 'http://localhost/nodes' + result = Oxidized.mgr.add_output('http') + _(result['http']).must_equal Oxidized::Output::Http end it 'returns nil when the source is not available' do @@ -20,4 +35,59 @@ _(result).must_be_nil end end + + describe '#add_input' do + it 'loads an input when available' do + result = Oxidized.mgr.add_input('http') + _(result['http']).must_equal Oxidized::HTTP + end + + it 'returns nil when the input is not available' do + result = Oxidized.mgr.add_input('XXX') + _(result).must_be_nil + end + end + + describe '#add_output' do + it 'loads http output' do + Oxidized.config.output.http.url = 'http://localhost/nodes' + result = Oxidized.mgr.add_output('http') + _(result['http']).must_equal Oxidized::Output::Http + end + + it 'loads file output' do + Oxidized.config.output.file.directory = '/some/path' + result = Oxidized.mgr.add_output('file') + _(result['file']).must_equal Oxidized::Output::File + end + + it 'returns nil when the output is not available' do + result = Oxidized.mgr.add_output('XXX') + _(result).must_be_nil + end + end + + describe '#add_model' do + it 'loads a model when available' do + result = Oxidized.mgr.add_model('ios') + _(result['ios']).must_equal IOS + end + + it 'returns nil when the model is not available' do + result = Oxidized.mgr.add_model('XXX') + _(result).must_be_nil + end + end + + describe '#add_hook' do + it 'loads a hook when available' do + result = Oxidized.mgr.add_hook('githubrepo') + _(result['githubrepo']).must_equal GithubRepo + end + + it 'returns nil when the hook is not available' do + result = Oxidized.mgr.add_hook('XXX') + _(result).must_be_nil + end + end end diff --git a/spec/output/file_spec.rb b/spec/output/file_spec.rb index 3aa5170cd..f83c0d825 100644 --- a/spec/output/file_spec.rb +++ b/spec/output/file_spec.rb @@ -1,7 +1,7 @@ require_relative '../spec_helper' require 'oxidized/output/file' -describe 'Oxidized::OxidizedFile' do +describe 'Oxidized::Output::File' do describe '#setup' do it 'raises Oxidized::NoConfig when no config is provided' do Asetus.any_instance.expects(:load) @@ -13,7 +13,7 @@ Oxidized::Config.load({ home_dir: '/cfg_path/' }) Oxidized.config.output.file = '' - oxidized_file = Oxidized::OxidizedFile.new + oxidized_file = Oxidized::Output::File.new err = _(-> { oxidized_file.setup }).must_raise Oxidized::NoConfig _(err.message).must_match(/^no output file config, edit \/cfg_path\/config$/) diff --git a/spec/output/git_spec.rb b/spec/output/git_spec.rb index 1c0e6b9b2..8a30fcd43 100644 --- a/spec/output/git_spec.rb +++ b/spec/output/git_spec.rb @@ -1,7 +1,7 @@ require_relative '../spec_helper' require 'oxidized/output/git' -describe Oxidized::Git do +describe Oxidized::Output::Git do describe '#yield_repo_and_path' do # Note that #yield_repo_and_path is private, so we can not call it directy # we use @git.send(:yield_repo_and_path, ...) to bypass the protection @@ -14,7 +14,7 @@ @mock_node.expect(:repo, '/tmp/oxidized.git') @mock_node.expect(:name, 'switch-42') - @git = Oxidized::Git.new + @git = Oxidized::Output::Git.new end it 'accepts group = nil' do