From 222cabc8d05a1f185fc24fcc7a4890960fc55896 Mon Sep 17 00:00:00 2001 From: Eryk Skierkowski Date: Sat, 26 Oct 2024 22:27:23 +0200 Subject: [PATCH] Merged the current main branch and fixed tests --- examples/device-simulation/device2yaml.rb | 4 +- extra/rest_client.rb | 2 +- extra/syslog.rb | 2 +- lib/oxidized/config.rb | 4 +- lib/oxidized/core.rb | 4 +- lib/oxidized/error/gitcrypterror.rb | 2 +- lib/oxidized/error/giterror.rb | 2 +- lib/oxidized/hook/githubrepo.rb | 86 ++++----- lib/oxidized/input/http.rb | 2 +- lib/oxidized/input/ssh.rb | 4 +- lib/oxidized/model/aosw.rb | 10 +- lib/oxidized/model/firewareos.rb | 24 +-- lib/oxidized/nodes.rb | 4 +- lib/oxidized/output/file.rb | 66 +++---- lib/oxidized/source/csv.rb | 90 ++++----- lib/oxidized/source/http.rb | 223 +++++++++++----------- lib/oxidized/source/jsonfile.rb | 56 +++--- lib/oxidized/source/sql.rb | 8 +- spec/config_spec.rb | 2 +- spec/model/aoscx_spec.rb | 2 +- spec/model/asa_spec.rb | 4 +- spec/model/firewareos_spec.rb | 12 +- spec/model/ios_spec.rb | 4 +- spec/model/linuxgeneric_spec.rb | 2 +- spec/output/file_spec.rb | 6 +- spec/source/csv_spec.rb | 12 +- spec/source/http_spec.rb | 8 +- spec/source/jsonfile_spec.rb | 12 +- spec/source/sql_spec.rb | 10 +- 29 files changed, 329 insertions(+), 338 deletions(-) diff --git a/examples/device-simulation/device2yaml.rb b/examples/device-simulation/device2yaml.rb index 05a6d6fa4..e2be90612 100755 --- a/examples/device-simulation/device2yaml.rb +++ b/examples/device-simulation/device2yaml.rb @@ -178,10 +178,10 @@ def cleanup print data.gsub("\e", '\e') end ch.request_pty(term: 'vt100') do |_ch, success_pty| - raise NoShell, "Can't get PTY" unless success_pty + raise Error::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 + raise Error::NoShell, "Can't get shell" unless success_shell end end ch.on_extended_data do |_ch, _type, data| diff --git a/extra/rest_client.rb b/extra/rest_client.rb index fb4ae336f..206ab39e4 100644 --- a/extra/rest_client.rb +++ b/extra/rest_client.rb @@ -15,7 +15,7 @@ class Config begin CFGS.load rescue StandardError => e - raise InvalidConfig, "Error loading config: #{e.message}" + raise Error::InvalidConfig, "Error loading config: #{e.message}" end restcfg = CFGS.cfg.rest diff --git a/extra/syslog.rb b/extra/syslog.rb index 456ac7695..0c19622b4 100755 --- a/extra/syslog.rb +++ b/extra/syslog.rb @@ -44,7 +44,7 @@ class Config begin CFGS.load rescue StandardError => e - raise InvalidConfig, "Error loading config: #{e.message}" + raise Error::InvalidConfig, "Error loading config: #{e.message}" ensure CFG = CFGS.cfg # convenienence, instead of Config.cfg.password, CFG.password end diff --git a/lib/oxidized/config.rb b/lib/oxidized/config.rb index eff610cd1..3dda4435a 100644 --- a/lib/oxidized/config.rb +++ b/lib/oxidized/config.rb @@ -107,13 +107,13 @@ def self.load(cmd_opts = {}) begin asetus.load # load system+user configs, merge to Config.cfg rescue StandardError => e - raise InvalidConfig, "Error loading config: #{e.message}" + raise Error::InvalidConfig, "Error loading config: #{e.message}" end # @!visibility private # If the configuration is being created for the first time, raise NoConfig - raise NoConfig, "edit #{@configfile}" if asetus.create + raise Error::NoConfig, "edit #{@configfile}" if asetus.create # @!visibility private # Override debug setting if provided in the command line options diff --git a/lib/oxidized/core.rb b/lib/oxidized/core.rb index 0a3566a58..b98471092 100644 --- a/lib/oxidized/core.rb +++ b/lib/oxidized/core.rb @@ -25,7 +25,7 @@ def initialize(_args) Oxidized.mgr = Manager.new Oxidized.hooks = HookManager.from_config(Oxidized.config) nodes = Nodes.new - raise NoNodesFound, 'source returns no usable nodes' if nodes.empty? + raise Error::NoNodesFound, 'source returns no usable nodes' if nodes.empty? @worker = Worker.new nodes @need_reload = false @@ -43,7 +43,7 @@ def initialize(_args) begin require 'oxidized/web' rescue LoadError - raise OxidizedError, 'oxidized-web not found: sudo gem install oxidized-web - \ + raise Error::OxidizedError, 'oxidized-web not found: sudo gem install oxidized-web - \ or disable web support by setting "rest: false" in your configuration' end @rest = API::Web.new nodes, Oxidized.config.rest diff --git a/lib/oxidized/error/gitcrypterror.rb b/lib/oxidized/error/gitcrypterror.rb index 063fb52a9..239d541e2 100644 --- a/lib/oxidized/error/gitcrypterror.rb +++ b/lib/oxidized/error/gitcrypterror.rb @@ -8,7 +8,7 @@ class GitCryptError < OxidizedError; end begin require 'git' rescue LoadError - raise OxidizedError, 'git not found: sudo gem install git' + raise Error::OxidizedError, 'git not found: sudo gem install git' end end end diff --git a/lib/oxidized/error/giterror.rb b/lib/oxidized/error/giterror.rb index ec68704b7..b1b87fe88 100644 --- a/lib/oxidized/error/giterror.rb +++ b/lib/oxidized/error/giterror.rb @@ -9,7 +9,7 @@ class GitError < OxidizedError; end begin require 'rugged' rescue LoadError - raise OxidizedError, 'rugged not found: sudo gem install rugged' + raise Error::OxidizedError, 'rugged not found: sudo gem install rugged' end end end diff --git a/lib/oxidized/hook/githubrepo.rb b/lib/oxidized/hook/githubrepo.rb index 59e37100b..168363a97 100644 --- a/lib/oxidized/hook/githubrepo.rb +++ b/lib/oxidized/hook/githubrepo.rb @@ -45,15 +45,15 @@ def run_hook(ctx) return end - log "Pushing local repository(#{repo.path})..." - log "to remote: #{url}" + log "Pushing local repository(#{repo.path})..." + log "to remote: #{url}" - if repo.remotes['origin'].nil? - repo.remotes.create('origin', url) - elsif repo.remotes['origin'].url != url - repo.remotes.set_url('origin', url) - end - remote = repo.remotes['origin'] + if repo.remotes['origin'].nil? + repo.remotes.create('origin', url) + elsif repo.remotes['origin'].url != url + repo.remotes.set_url('origin', url) + end + remote = repo.remotes['origin'] begin fetch_and_merge_remote(repo, creds) @@ -80,19 +80,19 @@ def fetch_and_merge_remote(repo, creds) result = repo.fetch('origin', [repo.head.name], credentials: creds) log result.inspect, :debug - their_branch = remote_branch(repo) + their_branch = remote_branch(repo) - unless their_branch - log 'remote branch does not exist yet, nothing to merge', :debug - return - end + unless their_branch + log 'remote branch does not exist yet, nothing to merge', :debug + return + end - result = repo.merge_analysis(their_branch.target_id) + result = repo.merge_analysis(their_branch.target_id) - if result.include? :up_to_date - log 'nothing to merge', :debug - return - end + if result.include? :up_to_date + log 'nothing to merge', :debug + return + end log "merging fetched branch #{their_branch.name}", :debug @@ -164,32 +164,32 @@ def remote_repo(node) cfg.remote_repo[node.group].url end end - end - end - def rugged_sshkey(args = {}) - git_user = args[:git_user] - privkey = args[:privkey] - pubkey = args[:pubkey] || (privkey + '.pub') - Rugged::Credentials::SshKey.new(username: git_user, - publickey: File.expand_path(pubkey), - privatekey: File.expand_path(privkey), - passphrase: ENV.fetch("OXIDIZED_SSH_PASSPHRASE", nil)) - end + def rugged_sshkey(args = {}) + git_user = args[:git_user] + privkey = args[:privkey] + pubkey = args[:pubkey] || (privkey + '.pub') + Rugged::Credentials::SshKey.new(username: git_user, + publickey: File.expand_path(pubkey), + privatekey: File.expand_path(privkey), + passphrase: ENV.fetch("OXIDIZED_SSH_PASSPHRASE", nil)) + end - def remote_repo(node) - if node.group.nil? || cfg.remote_repo.is_a?(String) - cfg.remote_repo - elsif cfg.remote_repo[node.group].is_a?(String) - cfg.remote_repo[node.group] - elsif cfg.remote_repo[node.group].url.is_a?(String) - cfg.remote_repo[node.group].url - end - end + def remote_repo(node) + if node.group.nil? || cfg.remote_repo.is_a?(String) + cfg.remote_repo + elsif cfg.remote_repo[node.group].is_a?(String) + cfg.remote_repo[node.group] + elsif cfg.remote_repo[node.group].url.is_a?(String) + cfg.remote_repo[node.group].url + end + end - # Returns a Rugged::Branch to the remote branch or nil if it doen't exist - def remote_branch(repo) - head_branch = repo.branches[repo.head.name] - repo.branches['origin/' + head_branch.name] + # Returns a Rugged::Branch to the remote branch or nil if it doen't exist + def remote_branch(repo) + head_branch = repo.branches[repo.head.name] + repo.branches['origin/' + head_branch.name] + end + end end -end \ No newline at end of file +end diff --git a/lib/oxidized/input/http.rb b/lib/oxidized/input/http.rb index 7457b4268..cf2f9852c 100644 --- a/lib/oxidized/input/http.rb +++ b/lib/oxidized/input/http.rb @@ -30,7 +30,7 @@ def connect(node) begin require "mechanize" rescue LoadError - raise OxidizedError, "mechanize not found: sudo gem install mechanize" + raise Error::OxidizedError, "mechanize not found: sudo gem install mechanize" end @m = Mechanize.new diff --git a/lib/oxidized/input/ssh.rb b/lib/oxidized/input/ssh.rb index 7b96056eb..acdc37f2c 100644 --- a/lib/oxidized/input/ssh.rb +++ b/lib/oxidized/input/ssh.rb @@ -127,10 +127,10 @@ def shell_open(ssh) @output = @node.model.expects @output end ch.request_pty(@pty_options) do |_ch, success_pty| - raise NoShell, "Can't get PTY" unless success_pty + raise Error::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 + raise Error::NoShell, "Can't get shell" unless success_shell end end end diff --git a/lib/oxidized/model/aosw.rb b/lib/oxidized/model/aosw.rb index af1dd997c..61956a034 100644 --- a/lib/oxidized/model/aosw.rb +++ b/lib/oxidized/model/aosw.rb @@ -16,11 +16,11 @@ class AOSW < Oxidized::Models::Model # Support for Mobility Access Switches tested with S2500-48P & S2500-24P running 7.4.1.4_54199 and S2500-24P running 7.4.1.7_57823 # All IAPs connected to a Instant Controller will have the same config output. Only the controller needs to be monitored. - comment '# ' - - # @!visibility private - # see /spec/model/aosw_spec.rb for prompt examples - prompt /^\(?[\w\:.@-]+\)? ?[*^]?(\[[\w\/]+\] ?)?[#>] ?$/ + comment '# ' + + # @!visibility private + # see /spec/model/aosw_spec.rb for prompt examples + prompt /^\(?[\w\:.@-]+\)? ?[*^]?(\[[\w\/]+\] ?)?[#>] ?$/ # @!visibility private # Ignore cariage returns - also for the prompt diff --git a/lib/oxidized/model/firewareos.rb b/lib/oxidized/model/firewareos.rb index 9e383a9a4..85175b9bf 100644 --- a/lib/oxidized/model/firewareos.rb +++ b/lib/oxidized/model/firewareos.rb @@ -3,18 +3,18 @@ module Models class FirewareOS < Oxidized::Models::Model using Refinements - # @!visibility private - # matched prompts: - # [FAULT]WG> - # WG> - # WG> - # [FAULT]WG> - # [FAULT]WG> - # WG> - - prompt /^\[?\w*\]?\w*?(?:<[\w-]+>)*(#|>)\s*$/ - - comment '-- ' + # @!visibility private + # matched prompts: + # [FAULT]WG> + # WG> + # WG> + # [FAULT]WG> + # [FAULT]WG> + # WG> + + prompt /^\[?\w*\]?\w*?(?:<[\w-]+>)*(#|>)\s*$/ + + comment '-- ' cmd :all do |cfg| cfg.cut_both diff --git a/lib/oxidized/nodes.rb b/lib/oxidized/nodes.rb index 245262254..0ea29f98a 100644 --- a/lib/oxidized/nodes.rb +++ b/lib/oxidized/nodes.rb @@ -262,10 +262,10 @@ def update_nodes(nodes) def yield_node_output(node_name) with_lock do node = find { |n| n.name == node_name } - raise(NodeNotFound, "unable to find '#{node_name}'") if node.nil? + raise(Error::NodeNotFound, "unable to find '#{node_name}'") if node.nil? output = node.output.new - raise NotSupported unless output.respond_to? :fetch + raise Error::NotSupported unless output.respond_to? :fetch yield node, output end diff --git a/lib/oxidized/output/file.rb b/lib/oxidized/output/file.rb index 2ad4eeed9..74a9d94ed 100644 --- a/lib/oxidized/output/file.rb +++ b/lib/oxidized/output/file.rb @@ -9,44 +9,44 @@ class OxidizedFile < Output attr_reader :commitref - # Initializes the OxidizedFile instance. - # - # @return [void] - def initialize - super - @cfg = Oxidized.config.output.file - end + # Initializes the OxidizedFile instance. + # + # @return [void] + 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 Error::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 + 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 + 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 # Sets up the output directory for configuration files. # diff --git a/lib/oxidized/source/csv.rb b/lib/oxidized/source/csv.rb index 973b07d73..604a98a23 100644 --- a/lib/oxidized/source/csv.rb +++ b/lib/oxidized/source/csv.rb @@ -1,23 +1,24 @@ module Oxidized - # Manages the source of configuration data from a CSV file. - # - # This class handles loading device configurations from a specified - # CSV file, including mapping of node parameters and support for - # variable interpolation. - class CSV < Oxidized::Source::Source - # Initializes a new instance of the CSV class. + module Source + # Manages the source of configuration data from a CSV file. # - # This constructor sets up the configuration for the CSV source. - def initialize - @cfg = Oxidized.config.source.csv - super - end + # This class handles loading device configurations from a specified + # CSV file, including mapping of node parameters and support for + # variable interpolation. + class CSV < Source + # Initializes a new instance of the CSV class. + # + # This constructor sets up the configuration for the CSV source. + def initialize + @cfg = Oxidized.config.source.csv + super + end # Sets up the CSV source configuration. # # Raises an exception if the CSV configuration is empty. # - # @raise [NoConfig] If no source CSV configuration is found. + # @raise [Error::NoConfig] If no source CSV configuration is found. def setup if @cfg.empty? Oxidized.asetus.user.source.csv.file = File.join(Config::ROOT, 'router.db') @@ -26,47 +27,48 @@ def setup Oxidized.asetus.user.source.csv.map.model = 1 Oxidized.asetus.user.source.csv.gpg = false Oxidized.asetus.save :user - raise NoConfig, "no source csv config, edit #{Oxidized::Config.configfile}" + raise Error::NoConfig, "no source csv config, edit #{Oxidized::Config.configfile}" end require 'gpgme' if @cfg.gpg? - + # map.name is mandatory return if @cfg.map.has_key?('name') - - raise InvalidConfig, "map/name is a mandatory source attribute, edit #{Oxidized::Config.configfile}" - # Loads the data from the configured CSV file. - # - # @param _node_want [String, nil] Optional specific node to load (not used). - # @return [Array] An array of hashes representing the nodes, - # with parameters mapped according to the configuration. - def load(_node_want = nil) - nodes = [] - open_file.each_line do |line| - next if line =~ /^\s*#/ + raise Error::InvalidConfig, "map/name is a mandatory source attribute, edit #{Oxidized::Config.configfile}" - data = line.chomp.split(@cfg.delimiter, -1) - next if data.empty? + # Loads the data from the configured CSV file. + # + # @param _node_want [String, nil] Optional specific node to load (not used). + # @return [Array] An array of hashes representing the nodes, + # with parameters mapped according to the configuration. + def load(_node_want = nil) + nodes = [] + open_file.each_line do |line| + next if line =~ /^\s*#/ - # @!visibility private - # map node parameters - keys = {} - @cfg.map.each do |key, position| - keys[key.to_sym] = node_var_interpolate data[position] - end - keys[:model] = map_model keys[:model] if keys.has_key? :model - keys[:group] = map_group keys[:group] if keys.has_key? :group + data = line.chomp.split(@cfg.delimiter, -1) + next if data.empty? - # map node specific vars - vars = {} - @cfg.vars_map.each do |key, position| - vars[key.to_sym] = node_var_interpolate data[position] - end - keys[:vars] = vars unless vars.empty? + # @!visibility private + # map node parameters + keys = {} + @cfg.map.each do |key, position| + keys[key.to_sym] = node_var_interpolate data[position] + end + keys[:model] = map_model keys[:model] if keys.has_key? :model + keys[:group] = map_group keys[:group] if keys.has_key? :group - nodes << keys + # map node specific vars + vars = {} + @cfg.vars_map.each do |key, position| + vars[key.to_sym] = node_var_interpolate data[position] + end + keys[:vars] = vars unless vars.empty? + + nodes << keys + end + nodes end - nodes end end end diff --git a/lib/oxidized/source/http.rb b/lib/oxidized/source/http.rb index f9ab37fd8..18908dd5e 100644 --- a/lib/oxidized/source/http.rb +++ b/lib/oxidized/source/http.rb @@ -1,97 +1,38 @@ module Oxidized module Source - require "oxidized/source/jsonfile" - - # Manages the source of configuration data via HTTP requests. - # - # This class extends the JSONFile source and handles loading device - # configurations from a specified HTTP URL, including support for pagination - # and custom headers. - class HTTP < JSONFile - # Initializes a new instance of the HTTP class. - # - # This constructor sets up the configuration for the HTTP source. - def initialize - super - @cfg = Oxidized.config.source.http - end - - def setup - Oxidized.setup_logger - if @cfg.empty? - Oxidized.asetus.user.source.http.url = 'https://url/api' - Oxidized.asetus.user.source.http.map.name = 'name' - Oxidized.asetus.user.source.http.map.model = 'model' - Oxidized.asetus.save :user - - raise NoConfig, "No source http config, edit #{Oxidized::Config.configfile}" - end - - # check for mandatory attributes - if !@cfg.has_key?('url') - raise InvalidConfig, "url is a mandatory http source attribute, edit #{Oxidized::Config.configfile}" - elsif !@cfg.map.has_key?('name') - raise InvalidConfig, "map/name is a mandatory source attribute, edit #{Oxidized::Config.configfile}" - end - end - - require "net/http" - require "net/https" - require "uri" - require "json" - - def load(node_want = nil) - uri = URI.parse(@cfg.url) - data = JSON.parse(read_http(uri, node_want)) - node_data = data - node_data = string_navigate_object(data, @cfg.hosts_location) if @cfg.hosts_location? - node_data = pagination(data, node_want) if @cfg.pagination? - - transform_json(node_data) - end - - private - - def pagination(data, node_want) - node_data = [] - raise Oxidized::OxidizedError, "if using pagination, 'pagination_key_name' setting must be set" unless @cfg.pagination_key_name? - - next_key = @cfg.pagination_key_name - loop do - node_data += string_navigate_object(data, @cfg.hosts_location) if @cfg.hosts_location? - break if data[next_key].nil? - - new_uri = URI.parse(data[next_key]) if data.has_key?(next_key) - data = JSON.parse(read_http(new_uri, node_want)) - node_data += string_navigate_object(data, @cfg.hosts_location) if @cfg.hosts_location? - end - node_data - end - - def read_http(uri, node_want) - http = Net::HTTP.new(uri.host, uri.port) - http.use_ssl = true if uri.scheme == 'https' - http.verify_mode = OpenSSL::SSL::VERIFY_NONE unless @cfg.secure + require "oxidized/source/jsonfile" - # Add read_timeout to handle case of big list of nodes (default value is 60 seconds) - http.read_timeout = Integer(@cfg.read_timeout) if @cfg.has_key? "read_timeout" - - # map headers - headers = {} - @cfg.headers.each do |header, value| - headers[header] = value + # Manages the source of configuration data via HTTP requests. + # + # This class extends the JSONFile source and handles loading device + # configurations from a specified HTTP URL, including support for pagination + # and custom headers. + class HTTP < JSONFile + # Initializes a new instance of the HTTP class. + # + # This constructor sets up the configuration for the HTTP source. + def initialize + super + @cfg = Oxidized.config.source.http end - # Sets up the HTTP source configuration. - # - # Raises an exception if the URL configuration is empty. - # - # @raise [NoConfig] If no source HTTP URL configuration is found. def setup Oxidized.setup_logger - return unless @cfg.url.empty? + if @cfg.empty? + Oxidized.asetus.user.source.http.url = 'https://url/api' + Oxidized.asetus.user.source.http.map.name = 'name' + Oxidized.asetus.user.source.http.map.model = 'model' + Oxidized.asetus.save :user - raise NoConfig, 'no source http url config, edit ~/.config/oxidized/config' + raise Error::NoConfig, "No source http config, edit #{Oxidized::Config.configfile}" + end + + # check for mandatory attributes + if !@cfg.has_key?('url') + raise Error::InvalidConfig, "url is a mandatory http source attribute, edit #{Oxidized::Config.configfile}" + elsif !@cfg.map.has_key?('name') + raise Error::InvalidConfig, "map/name is a mandatory source attribute, edit #{Oxidized::Config.configfile}" + end end require "net/http" @@ -99,11 +40,6 @@ def setup require "uri" require "json" - # Loads the data from the configured HTTP URL. - # - # @param node_want [String, nil] Optional specific node to load. - # @return [Array] An array of hashes representing the nodes, - # with parameters mapped according to the configuration. def load(node_want = nil) uri = URI.parse(@cfg.url) data = JSON.parse(read_http(uri, node_want)) @@ -116,16 +52,9 @@ def load(node_want = nil) private - # Handles pagination of the HTTP data if configured. - # - # @param data [Hash] The data received from the HTTP request. - # @param node_want [String, nil] Optional specific node to load. - # @return [Array] An array of hashes representing the nodes, - # with parameters mapped according to the configuration. - # @raise [Oxidized::OxidizedError] If pagination key name is not set. def pagination(data, node_want) node_data = [] - raise Oxidized::OxidizedError, "if using pagination, 'pagination_key_name' setting must be set" unless @cfg.pagination_key_name? + raise Oxidized::Error::OxidizedError, "if using pagination, 'pagination_key_name' setting must be set" unless @cfg.pagination_key_name? next_key = @cfg.pagination_key_name loop do @@ -139,32 +68,104 @@ def pagination(data, node_want) node_data end - # Makes an HTTP GET request to the specified URI and returns the response body. - # - # @param uri [URI] The URI to request data from. - # @param node_want [String, nil] Optional specific node to load. - # @return [String] The response body from the HTTP request. def read_http(uri, node_want) http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true if uri.scheme == 'https' http.verify_mode = OpenSSL::SSL::VERIFY_NONE unless @cfg.secure - # @!visibility private # Add read_timeout to handle case of big list of nodes (default value is 60 seconds) http.read_timeout = Integer(@cfg.read_timeout) if @cfg.has_key? "read_timeout" - # @!visibility private # map headers headers = {} @cfg.headers.each do |header, value| headers[header] = value end - req_uri = uri.request_uri - req_uri = "#{req_uri}/#{node_want}" if node_want - request = Net::HTTP::Get.new(req_uri, headers) - request.basic_auth(@cfg.user, @cfg.pass) if @cfg.user? && @cfg.pass? - http.request(request).body + # Sets up the HTTP source configuration. + # + # Raises an exception if the URL configuration is empty. + # + # @raise [NoConfig] If no source HTTP URL configuration is found. + def setup + Oxidized.setup_logger + return unless @cfg.url.empty? + + raise Error::NoConfig, 'no source http url config, edit ~/.config/oxidized/config' + end + + require "net/http" + require "net/https" + require "uri" + require "json" + + # Loads the data from the configured HTTP URL. + # + # @param node_want [String, nil] Optional specific node to load. + # @return [Array] An array of hashes representing the nodes, + # with parameters mapped according to the configuration. + def load(node_want = nil) + uri = URI.parse(@cfg.url) + data = JSON.parse(read_http(uri, node_want)) + node_data = data + node_data = string_navigate_object(data, @cfg.hosts_location) if @cfg.hosts_location? + node_data = pagination(data, node_want) if @cfg.pagination? + + transform_json(node_data) + end + + private + + # Handles pagination of the HTTP data if configured. + # + # @param data [Hash] The data received from the HTTP request. + # @param node_want [String, nil] Optional specific node to load. + # @return [Array] An array of hashes representing the nodes, + # with parameters mapped according to the configuration. + # @raise [Oxidized::OxidizedError] If pagination key name is not set. + def pagination(data, node_want) + node_data = [] + raise Oxidized::Error::OxidizedError, "if using pagination, 'pagination_key_name' setting must be set" unless @cfg.pagination_key_name? + + next_key = @cfg.pagination_key_name + loop do + node_data += string_navigate_object(data, @cfg.hosts_location) if @cfg.hosts_location? + break if data[next_key].nil? + + new_uri = URI.parse(data[next_key]) if data.has_key?(next_key) + data = JSON.parse(read_http(new_uri, node_want)) + node_data += string_navigate_object(data, @cfg.hosts_location) if @cfg.hosts_location? + end + node_data + end + + # Makes an HTTP GET request to the specified URI and returns the response body. + # + # @param uri [URI] The URI to request data from. + # @param node_want [String, nil] Optional specific node to load. + # @return [String] The response body from the HTTP request. + def read_http(uri, node_want) + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl = true if uri.scheme == 'https' + http.verify_mode = OpenSSL::SSL::VERIFY_NONE unless @cfg.secure + + # @!visibility private + # Add read_timeout to handle case of big list of nodes (default value is 60 seconds) + http.read_timeout = Integer(@cfg.read_timeout) if @cfg.has_key? "read_timeout" + + # @!visibility private + # map headers + headers = {} + @cfg.headers.each do |header, value| + headers[header] = value + end + + req_uri = uri.request_uri + req_uri = "#{req_uri}/#{node_want}" if node_want + request = Net::HTTP::Get.new(req_uri, headers) + request.basic_auth(@cfg.user, @cfg.pass) if @cfg.user? && @cfg.pass? + http.request(request).body + end end end end diff --git a/lib/oxidized/source/jsonfile.rb b/lib/oxidized/source/jsonfile.rb index c7b01daf4..43683967a 100644 --- a/lib/oxidized/source/jsonfile.rb +++ b/lib/oxidized/source/jsonfile.rb @@ -1,53 +1,41 @@ module Oxidized module Source - # Manages the source of configuration data from a JSON file. - # - # This class handles loading device configurations from a specified JSON file, - # including variable interpolation and mapping of node parameters. - class JSONFile < Source - require "json" - # Initializes a new instance of the JSONFile class. + # Manages the source of configuration data from a JSON file. # - # This constructor sets up the configuration for the JSON file source. - def initialize - @cfg = Oxidized.config.source.jsonfile - super - end - - def setup - if @cfg.empty? - Oxidized.asetus.user.source.jsonfile.file = File.join(Oxidized::Config::ROOT, - 'router.json') - Oxidized.asetus.user.source.jsonfile.map.name = "name" - Oxidized.asetus.user.source.jsonfile.map.model = "model" - Oxidized.asetus.user.source.jsonfile.gpg = false - Oxidized.asetus.save :user - raise NoConfig, "No source json config, edit #{Oxidized::Config.configfile}" + # This class handles loading device configurations from a specified JSON file, + # including variable interpolation and mapping of node parameters. + class JSONFile < Source + require "json" + # Initializes a new instance of the JSONFile class. + # + # This constructor sets up the configuration for the JSON file source. + def initialize + @cfg = Oxidized.config.source.jsonfile + super end - require 'gpgme' if @cfg.gpg? - - # map.name is mandatory - return if @cfg.map.has_key?('name') - - raise InvalidConfig, "map/name is a mandatory source attribute, edit #{Oxidized::Config.configfile}" - end # Sets up the JSON file source configuration. # # If no configuration is provided, it initializes default settings # and raises an exception if the configuration is still empty. # - # @raise [NoConfig] If no source JSON configuration is found. + # @raise [Error::NoConfig] If no source JSON configuration is found. def setup if @cfg.empty? - Oxidized.asetus.user.source.jsonfile.file = File.join(Config::Root, 'router.json') + Oxidized.asetus.user.source.jsonfile.file = File.join(Oxidized::Config::ROOT, + 'router.json') Oxidized.asetus.user.source.jsonfile.map.name = "name" Oxidized.asetus.user.source.jsonfile.map.model = "model" Oxidized.asetus.user.source.jsonfile.gpg = false Oxidized.asetus.save :user - raise NoConfig, 'No source json config, edit ~/.config/oxidized/config' + raise Error::NoConfig, "No source json config, edit #{Oxidized::Config.configfile}" end require 'gpgme' if @cfg.gpg? + + # map.name is mandatory + return if @cfg.map.has_key?('name') + + raise Error::InvalidConfig, "map/name is a mandatory source attribute, edit #{Oxidized::Config.configfile}" end # Loads the data from the configured JSON file. @@ -61,7 +49,7 @@ def load(*) transform_json(data) end - private + private # Transforms the parsed JSON data into a structured format for nodes. # @@ -93,6 +81,6 @@ def transform_json(data) end nodes end + end end - end end diff --git a/lib/oxidized/source/sql.rb b/lib/oxidized/source/sql.rb index c8b12190e..4b55a4d13 100644 --- a/lib/oxidized/source/sql.rb +++ b/lib/oxidized/source/sql.rb @@ -6,7 +6,7 @@ class SQL < Source begin require 'sequel' rescue LoadError - raise OxidizedError, 'sequel not found: sudo gem install sequel' + raise Error::OxidizedError, 'sequel not found: sudo gem install sequel' end # Sets up the SQL source configuration with default values if none are provided. @@ -21,13 +21,13 @@ def setup Oxidized.asetus.user.source.sql.map.name = 'name' Oxidized.asetus.user.source.sql.map.model = 'rancid' Oxidized.asetus.save :user - raise NoConfig, "No source sql config, edit #{Oxidized::Config.configfile}" + raise Error::NoConfig, "No source sql config, edit #{Oxidized::Config.configfile}" end # map.name is mandatory return if @cfg.map.has_key?('name') - raise InvalidConfig, "map/name is a mandatory source attribute, edit #{Oxidized::Config.configfile}" + raise Error::InvalidConfig, "map/name is a mandatory source attribute, edit #{Oxidized::Config.configfile}" end # Loads nodes from the SQL database based on the specified configuration. @@ -92,7 +92,7 @@ def connect end Sequel.connect(options) rescue Sequel::AdapterNotFound => e - raise OxidizedError, "SQL adapter gem not installed: " + e.message + raise Error::OxidizedError, "SQL adapter gem not installed: " + e.message end end end diff --git a/spec/config_spec.rb b/spec/config_spec.rb index 3314435bf..af683ca44 100644 --- a/spec/config_spec.rb +++ b/spec/config_spec.rb @@ -7,7 +7,7 @@ Asetus.any_instance.expects(:load) Asetus.any_instance.expects(:create).returns(true) - err = _(-> { Oxidized::Config.load }).must_raise Oxidized::NoConfig + err = _(-> { Oxidized::Config.load }).must_raise Oxidized::Error::NoConfig # We cannot test if the environment variable OXIDIZED_HOME is properly used. # Oxidized::Config uses OXIDIZED_HOME at loading (require 'oxidized/config'), # so we have no chance to manipulate it within this test (oxidized/condig can diff --git a/spec/model/aoscx_spec.rb b/spec/model/aoscx_spec.rb index 09ff50a04..c71cacad8 100644 --- a/spec/model/aoscx_spec.rb +++ b/spec/model/aoscx_spec.rb @@ -9,7 +9,7 @@ end it 'matches different prompts' do - _('LAB-SW1234# ').must_match Aoscx.prompt + _('LAB-SW1234# ').must_match Oxidized::Models::Aoscx.prompt end it 'runs on R8N85A (C6000-48G-CL4) with PL.10.08.1010' do diff --git a/spec/model/asa_spec.rb b/spec/model/asa_spec.rb index c8177c52a..83bd8f80d 100644 --- a/spec/model/asa_spec.rb +++ b/spec/model/asa_spec.rb @@ -9,8 +9,8 @@ end it 'matches different prompts' do - _("\rLAB-ASA12-Oxidized-IPv6> ").must_match ASA.prompt - _("\rLAB-ASA12-Oxidized-IPv6# ").must_match ASA.prompt + _("\rLAB-ASA12-Oxidized-IPv6> ").must_match Oxidized::Models::ASA.prompt + _("\rLAB-ASA12-Oxidized-IPv6# ").must_match Oxidized::Models::ASA.prompt end it 'runs on 5515 with version 9.12(4)67' do diff --git a/spec/model/firewareos_spec.rb b/spec/model/firewareos_spec.rb index 9e40815ce..8ccbc8f9f 100644 --- a/spec/model/firewareos_spec.rb +++ b/spec/model/firewareos_spec.rb @@ -15,11 +15,11 @@ end it "matches different prompts" do - _('[FAULT]WG>').must_match FirewareOS.prompt - _('WG>').must_match FirewareOS.prompt - _('WG>').must_match FirewareOS.prompt - _('[FAULT]WG>').must_match FirewareOS.prompt - _('[FAULT]WG>').must_match FirewareOS.prompt - _('WG>').must_match FirewareOS.prompt + _('[FAULT]WG>').must_match Oxidized::Models::FirewareOS.prompt + _('WG>').must_match Oxidized::Models::FirewareOS.prompt + _('WG>').must_match Oxidized::Models::FirewareOS.prompt + _('[FAULT]WG>').must_match Oxidized::Models::FirewareOS.prompt + _('[FAULT]WG>').must_match Oxidized::Models::FirewareOS.prompt + _('WG>').must_match Oxidized::Models::FirewareOS.prompt end end diff --git a/spec/model/ios_spec.rb b/spec/model/ios_spec.rb index 1e16f1452..3880de3e6 100644 --- a/spec/model/ios_spec.rb +++ b/spec/model/ios_spec.rb @@ -9,8 +9,8 @@ end it 'matches different prompts' do - _('LAB-SW123_9200L#').must_match IOS.prompt - _('OXIDIZED-WLC1#').must_match IOS.prompt + _('LAB-SW123_9200L#').must_match Oxidized::Models::IOS.prompt + _('OXIDIZED-WLC1#').must_match Oxidized::Models::IOS.prompt end it 'runs on C9200L-24P-4G with IOS-XE 17.09.04a' do diff --git a/spec/model/linuxgeneric_spec.rb b/spec/model/linuxgeneric_spec.rb index df876c650..d181faa3e 100644 --- a/spec/model/linuxgeneric_spec.rb +++ b/spec/model/linuxgeneric_spec.rb @@ -9,6 +9,6 @@ end it 'matches different prompts' do - _('robert@gap:~$ ').must_match LinuxGeneric.prompt + _('robert@gap:~$ ').must_match Oxidized::Models::LinuxGeneric.prompt end end diff --git a/spec/output/file_spec.rb b/spec/output/file_spec.rb index 3aa5170cd..3f7fa0125 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::OxidizedFile' do describe '#setup' do it 'raises Oxidized::NoConfig when no config is provided' do Asetus.any_instance.expects(:load) @@ -13,9 +13,9 @@ Oxidized::Config.load({ home_dir: '/cfg_path/' }) Oxidized.config.output.file = '' - oxidized_file = Oxidized::OxidizedFile.new + oxidized_file = Oxidized::Output::OxidizedFile.new - err = _(-> { oxidized_file.setup }).must_raise Oxidized::NoConfig + err = _(-> { oxidized_file.setup }).must_raise Oxidized::Error::NoConfig _(err.message).must_match(/^no output file config, edit \/cfg_path\/config$/) end end diff --git a/spec/source/csv_spec.rb b/spec/source/csv_spec.rb index 2156ad5ae..b637f0dc3 100644 --- a/spec/source/csv_spec.rb +++ b/spec/source/csv_spec.rb @@ -1,7 +1,7 @@ require_relative '../spec_helper' require 'oxidized/source/csv' -describe Oxidized::CSV do +describe Oxidized::Source::CSV do describe '#setup' do before(:each) do Asetus.any_instance.expects(:load) @@ -10,25 +10,25 @@ # Set :home_dir to make sure the OXIDIZED_HOME environment variable is not used Oxidized::Config.load({ home_dir: '/cfg_path/' }) - @source = Oxidized::CSV.new + @source = Oxidized::Source::CSV.new end - it 'raises Oxidized::NoConfig when no config is provided' do + it 'raises Oxidized::Error::NoConfig when no config is provided' do # we do not want to create the config for real Asetus.any_instance.expects(:save) Oxidized.config.source.csv = '' - err = _(-> { @source.setup }).must_raise Oxidized::NoConfig + err = _(-> { @source.setup }).must_raise Oxidized::Error::NoConfig _(err.message).must_equal 'no source csv config, edit /cfg_path/config' end - it 'raises Oxidized::InvalidConfig when name is not provided' do + it 'raises Oxidized::Error::InvalidConfig when name is not provided' do Asetus.any_instance.expects(:save).never Oxidized.config.source.csv.map.ip = 0 - err = _(-> { @source.setup }).must_raise Oxidized::InvalidConfig + err = _(-> { @source.setup }).must_raise Oxidized::Error::InvalidConfig _(err.message).must_equal 'map/name is a mandatory source attribute, edit /cfg_path/config' end diff --git a/spec/source/http_spec.rb b/spec/source/http_spec.rb index faf2d67ee..5874b264b 100644 --- a/spec/source/http_spec.rb +++ b/spec/source/http_spec.rb @@ -15,7 +15,7 @@ # Set :home_dir to make sure the OXIDIZED_HOME environment variable is not used Oxidized::Config.load({ home_dir: '/cfg_path/' }) - @source = Oxidized::HTTP.new + @source = Oxidized::Source::HTTP.new end it 'raises Oxidized::NoConfig when no config is provided' do @@ -24,7 +24,7 @@ Oxidized.config.source.http = '' - err = _(-> { @source.setup }).must_raise Oxidized::NoConfig + err = _(-> { @source.setup }).must_raise Oxidized::Error::NoConfig _(err.message).must_equal 'No source http config, edit /cfg_path/config' end @@ -32,7 +32,7 @@ Asetus.any_instance.expects(:save).never Oxidized.config.source.http.secure = 'false' - err = _(-> { @source.setup }).must_raise Oxidized::InvalidConfig + err = _(-> { @source.setup }).must_raise Oxidized::Error::InvalidConfig _(err.message).must_equal 'url is a mandatory http source attribute, edit /cfg_path/config' end @@ -40,7 +40,7 @@ Asetus.any_instance.expects(:save).never Oxidized.config.source.http.url = 'https://localhost/' - err = _(-> { @source.setup }).must_raise Oxidized::InvalidConfig + err = _(-> { @source.setup }).must_raise Oxidized::Error::InvalidConfig _(err.message).must_equal 'map/name is a mandatory source attribute, edit /cfg_path/config' end diff --git a/spec/source/jsonfile_spec.rb b/spec/source/jsonfile_spec.rb index a835d02a6..c179e1974 100644 --- a/spec/source/jsonfile_spec.rb +++ b/spec/source/jsonfile_spec.rb @@ -1,7 +1,7 @@ require_relative '../spec_helper' require 'oxidized/source/jsonfile' -describe Oxidized::JSONFile do +describe Oxidized::Source::JSONFile do describe '#setup' do before(:each) do Asetus.any_instance.expects(:load) @@ -10,25 +10,25 @@ # Set :home_dir to make sure the OXIDIZED_HOME environment variable is not used Oxidized::Config.load({ home_dir: '/cfg_path/' }) - @source = Oxidized::JSONFile.new + @source = Oxidized::Source::JSONFile.new end - it 'raises Oxidized::NoConfig when no config is provided' do + it 'raises Oxidized::Error::NoConfig when no config is provided' do # we do not want to create the config for real Asetus.any_instance.expects(:save) Oxidized.config.source.json = '' - err = _(-> { @source.setup }).must_raise Oxidized::NoConfig + err = _(-> { @source.setup }).must_raise Oxidized::Error::NoConfig _(err.message).must_equal 'No source json config, edit /cfg_path/config' end - it 'raises Oxidized::InvalidConfig when name is not provided' do + it 'raises Oxidized::Error::InvalidConfig when name is not provided' do Asetus.any_instance.expects(:save).never Oxidized.config.source.jsonfile.file = '/cfg_path/router.json' - err = _(-> { @source.setup }).must_raise Oxidized::InvalidConfig + err = _(-> { @source.setup }).must_raise Oxidized::Error::InvalidConfig _(err.message).must_equal 'map/name is a mandatory source attribute, edit /cfg_path/config' end diff --git a/spec/source/sql_spec.rb b/spec/source/sql_spec.rb index 186e036e2..d38e0adcf 100644 --- a/spec/source/sql_spec.rb +++ b/spec/source/sql_spec.rb @@ -1,7 +1,7 @@ require_relative '../spec_helper' require 'oxidized/source/sql' -describe Oxidized::SQL do +describe Oxidized::Source::SQL do describe '#setup' do before(:each) do Asetus.any_instance.expects(:load) @@ -10,16 +10,16 @@ # Set :home_dir to make sure the OXIDIZED_HOME environment variable is not used Oxidized::Config.load({ home_dir: '/cfg_path/' }) - @source = Oxidized::SQL.new + @source = Oxidized::Source::SQL.new end - it 'raises Oxidized::NoConfig when no config is provided' do + it 'raises Oxidized::Error::NoConfig when no config is provided' do # we do not want to create the config for real Asetus.any_instance.expects(:save) Oxidized.config.source.sql = '' - err = _(-> { @source.setup }).must_raise Oxidized::NoConfig + err = _(-> { @source.setup }).must_raise Oxidized::Error::NoConfig _(err.message).must_equal 'No source sql config, edit /cfg_path/config' end @@ -28,7 +28,7 @@ Oxidized.config.source.sql.table = 'nodes' - err = _(-> { @source.setup }).must_raise Oxidized::InvalidConfig + err = _(-> { @source.setup }).must_raise Oxidized::Error::InvalidConfig _(err.message).must_equal 'map/name is a mandatory source attribute, edit /cfg_path/config' end