Class: Cisco::Client
- Inherits:
-
Object
- Object
- Cisco::Client
- Defined in:
- lib/cisco_node_utils/client.rb,
lib/cisco_node_utils/client/utils.rb,
lib/cisco_node_utils/client/client.rb
Overview
Base class for clients of various RPC formats
Defined Under Namespace
Constant Summary collapse
- @@clients =
rubocop:disable Style/ClassVars
[]
Instance Attribute Summary collapse
-
#cache_auto ⇒ Object
writeonly
Sets the attribute cache_auto.
-
#data_formats ⇒ Object
readonly
Returns the value of attribute data_formats.
-
#platform ⇒ Object
readonly
Returns the value of attribute platform.
Class Method Summary collapse
- .clients ⇒ Object
-
.create(environment_name = nil) ⇒ Object
Try to create an instance of an appropriate subclass.
-
.filter_cli(cli_output: nil, context: nil, value: nil) ⇒ [String]?
Helper function that subclasses may use with get(data_format: :cli) Method for working with hierarchical show command output such as “show running-config”.
-
.filter_data(data: nil, keys: nil) ⇒ Object
Helper method for get(data_format: :nxapi_structured).
-
.find_subconfig(body, regexp_query) ⇒ String?
Returns the subsection associated with the given line of config to retrieve the subsection appropriately, or nil if no such subsection exists.
- .handle_errors(errors) ⇒ Object
-
.munge_to_array(val) ⇒ Object
Make a best effort to convert a given input value to an Array.
-
.register_client(client) ⇒ Object
Each subclass should call this method to register itself.
-
.silence_warnings(&block) ⇒ Object
Helper method for calls into third-party code - suppresses Ruby warnings for the given block since we have no control over that code.
-
.to_regexp(input) ⇒ Object
Helper method for CLI getters.
- .validate_args(**kwargs) ⇒ Object
Instance Method Summary collapse
- #cache_auto? ⇒ Boolean
- #cache_enable=(enable) ⇒ Object
- #cache_enable? ⇒ Boolean
-
#cache_flush ⇒ Object
Clear the cache of CLI output results.
-
#get(data_format: :cli, command: nil, context: nil, value: nil, **_kwargs) ⇒ String, ...
Get the given state from the device.
-
#initialize(data_formats: [], platform: nil, **kwargs) ⇒ Client
constructor
A new instance of Client.
- #inspect ⇒ Object
- #munge_to_array(val) ⇒ Object
-
#set(data_format: :cli, context: nil, values: nil, **_kwargs) ⇒ Object
Configure the given state on the device.
- #supports?(data_format) ⇒ Boolean
- #to_s ⇒ Object
Constructor Details
#initialize(data_formats: [], platform: nil, **kwargs) ⇒ Client
Returns a new instance of Client.
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/cisco_node_utils/client/client.rb', line 42 def initialize(data_formats: [], platform: nil, **kwargs) if self.class == Cisco::Client fail NotImplementedError, 'Cisco::Client is an abstract class. ' \ "Instantiate one of #{@@clients} or use Cisco::Client.create() instead" end self.class.validate_args(**kwargs) @host = kwargs[:host] @port = kwargs[:port] @address = @port.nil? ? @host : "#{@host}:#{@port}" @username = kwargs[:username] @password = kwargs[:password] self.data_formats = data_formats self.platform = platform @cache_enable = true @cache_auto = true cache_flush end |
Instance Attribute Details
#cache_auto=(value) ⇒ Object (writeonly)
Sets the attribute cache_auto
151 152 153 |
# File 'lib/cisco_node_utils/client/client.rb', line 151 def cache_auto=(value) @cache_auto = value end |
#data_formats ⇒ Object
Returns the value of attribute data_formats.
40 41 42 |
# File 'lib/cisco_node_utils/client/client.rb', line 40 def data_formats @data_formats end |
#platform ⇒ Object
Returns the value of attribute platform.
40 41 42 |
# File 'lib/cisco_node_utils/client/client.rb', line 40 def platform @platform end |
Class Method Details
.clients ⇒ Object
31 32 33 |
# File 'lib/cisco_node_utils/client/client.rb', line 31 def self.clients @@clients end |
.create(environment_name = nil) ⇒ Object
Try to create an instance of an appropriate subclass
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
# File 'lib/cisco_node_utils/client/client.rb', line 85 def self.create(environment_name=nil) fail 'No client implementations available!' if clients.empty? debug "Trying to establish client connection. clients = #{clients}" environment = Cisco::Environment.environment(environment_name) host = environment[:host] errors = [] clients.each do |client_class| begin debug "Trying to connect to #{host} as #{client_class}" client = client_class.new(**environment) debug "#{client_class} connected successfully" return client rescue Cisco::ClientError, TypeError, ArgumentError => e debug "Unable to connect to #{host} as #{client_class}: #{e.}" debug e.backtrace.join("\n ") errors << e end end handle_errors(errors) end |
.filter_cli(cli_output: nil, context: nil, value: nil) ⇒ [String]?
Helper function that subclasses may use with get(data_format: :cli) Method for working with hierarchical show command output such as “show running-config”. Searches the given multi-line string for all matches to the given value query. If context is provided, the matches will be filtered to only those that are located “under” the given context sequence (as determined by indentation).
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'lib/cisco_node_utils/client/utils.rb', line 57 def self.filter_cli(cli_output: nil, context: nil, value: nil) return cli_output if cli_output.nil? context ||= [] context.each { |filter| cli_output = find_subconfig(cli_output, filter) } return nil if cli_output.nil? || cli_output.empty? return cli_output if value.nil? value = to_regexp(value) match = cli_output.scan(value) return nil if match.empty? # find matches and return as array of String if it only does one match. # Otherwise return array of array. match.flatten! if match[0].is_a?(Array) && match[0].length == 1 match end |
.filter_data(data: nil, keys: nil) ⇒ Object
Helper method for get(data_format: :nxapi_structured).
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/cisco_node_utils/client/utils.rb', line 141 def self.filter_data(data: nil, keys: nil) return nil if data.nil? || data.empty? keys ||= [] keys.each do |filter| # if filter is a Hash and data is an array, check each # array index (which should return another hash) to see if # it contains the matching key/value pairs specified in token, # and return the first match (or nil) if filter.kind_of?(Hash) fail "Expected Array, got #{data.class}" unless data.is_a? Array data = data.select { |x| filter.all? { |k, v| x[k] == v } } fail "Multiple matches found for #{filter}" if data.length > 1 fail "No match found for #{filter}" if data.length == 0 data = data[0] else # data is array or hash if data.is_a? Array final = [] data.each do |row| final << row[filter] end return final end fail "No key \"#{filter}\" in #{data}" unless data.key?(filter) data = data[filter] end end data end |
.find_subconfig(body, regexp_query) ⇒ String?
Returns the subsection associated with the given line of config to retrieve the subsection appropriately, or nil if no such subsection exists.
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'lib/cisco_node_utils/client/utils.rb', line 81 def self.find_subconfig(body, regexp_query) return nil if body.nil? || regexp_query.nil? regexp_query = to_regexp(regexp_query) rows = body.split("\n") match_row_index = rows.index { |row| regexp_query =~ row } return nil if match_row_index.nil? cur = match_row_index + 1 subconfig = [] until (/\A\s+.*/ =~ rows[cur]).nil? || cur == rows.length subconfig << rows[cur] cur += 1 end return nil if subconfig.empty? # Strip an appropriate minimal amount of leading whitespace from # all lines in the subconfig min_leading = subconfig.map { |line| line[/\A */].size }.min subconfig = subconfig.map { |line| line[min_leading..-1] } subconfig.join("\n") end |
.handle_errors(errors) ⇒ Object
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/cisco_node_utils/client/client.rb', line 106 def self.handle_errors(errors) # ClientError means we tried to connect but failed, # so it's 'more significant' than input validation errors. client_errors = errors.select { |e| e.kind_of? Cisco::ClientError } if !client_errors.empty? # Reraise the specific error if just one fail client_errors[0] if client_errors.length == 1 # Otherwise clump them together into a new error e_cls = client_errors[0].class unless client_errors.all? { |e| e.class == e_cls } e_cls = Cisco::ClientError end fail e_cls, ("Unable to establish any client connection:\n" + errors.each(&:message).join("\n")) elsif errors.any? { |e| e.kind_of? ArgumentError } fail ArgumentError, ("Invalid arguments:\n" + errors.each(&:message).join("\n")) elsif errors.any? { |e| e.kind_of? TypeError } fail TypeError, ("Invalid arguments:\n" + errors.each(&:message).join("\n")) end fail Cisco::ClientError, 'No client connected, but no errors were reported?' end |
.munge_to_array(val) ⇒ Object
Make a best effort to convert a given input value to an Array. Strings are split by newlines, and nil becomes an empty Array.
26 27 28 29 30 |
# File 'lib/cisco_node_utils/client/utils.rb', line 26 def self.munge_to_array(val) val = [] if val.nil? val = val.split("\n") if val.is_a?(String) val end |
.register_client(client) ⇒ Object
Each subclass should call this method to register itself.
36 37 38 |
# File 'lib/cisco_node_utils/client/client.rb', line 36 def self.register_client(client) @@clients << client end |
.silence_warnings(&block) ⇒ Object
Helper method for calls into third-party code - suppresses Ruby warnings for the given block since we have no control over that code.
173 174 175 176 177 178 179 |
# File 'lib/cisco_node_utils/client/utils.rb', line 173 def self.silence_warnings(&block) warn_level = $VERBOSE $VERBOSE = nil result = block.call $VERBOSE = warn_level result end |
.to_regexp(input) ⇒ Object
Helper method for CLI getters
Convert a string or array of strings to a Regexp or array thereof
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/cisco_node_utils/client/utils.rb', line 107 def self.to_regexp(input) if input.is_a?(Regexp) return input elsif input.is_a?(Array) return input.map { |item| to_regexp(item) } else # The string might be explicitly formatted as a regexp # Dynamically handle modifiers input.match(%r{(?<regex>^\/.*\/)(?<options>[imx]*)?}) do |m| = [] m['options'].each_char do |c| case c when 'i' << Regexp::IGNORECASE when 'm' << Regexp::MULTILINE when 'x' << Regexp::EXTENDED end end return Regexp.new(m['regex'][1..-2], .reduce(:|)) end # otherwise this value is a regular string # convert to case insensitive regex # 'foo' => %r{^foo$}i return Regexp.new("^#{input}$", Regexp::IGNORECASE) end end |
.validate_args(**kwargs) ⇒ Object
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/cisco_node_utils/client/client.rb', line 62 def self.validate_args(**kwargs) host = kwargs[:host] unless host.nil? fail TypeError, 'invalid address' unless host.is_a?(String) fail ArgumentError, 'empty address' if host.empty? end username = kwargs[:username] unless username.nil? fail TypeError, 'invalid username' unless username.is_a?(String) fail ArgumentError, 'empty username' if username.empty? end password = kwargs[:password] unless password.nil? fail TypeError, 'invalid password' unless password.is_a?(String) fail ArgumentError, 'empty password' if password.empty? end end |
Instance Method Details
#cache_auto? ⇒ Boolean
147 148 149 |
# File 'lib/cisco_node_utils/client/client.rb', line 147 def cache_auto? @cache_auto end |
#cache_enable=(enable) ⇒ Object
142 143 144 145 |
# File 'lib/cisco_node_utils/client/client.rb', line 142 def cache_enable=(enable) @cache_enable = enable cache_flush unless enable end |
#cache_enable? ⇒ Boolean
138 139 140 |
# File 'lib/cisco_node_utils/client/client.rb', line 138 def cache_enable? @cache_enable end |
#cache_flush ⇒ Object
Clear the cache of CLI output results.
If cache_auto is true (default) then this will be performed automatically whenever a set() is called, but providers may also call this to explicitly force the cache to be cleared.
158 159 160 |
# File 'lib/cisco_node_utils/client/client.rb', line 158 def cache_flush # to be implemented by subclasses end |
#get(data_format: :cli, command: nil, context: nil, value: nil, **_kwargs) ⇒ String, ...
Get the given state from the device.
Unlike set() this will not clear the CLI cache; multiple calls with the same parameters may return cached data rather than querying the device repeatedly.
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 |
# File 'lib/cisco_node_utils/client/client.rb', line 202 def get(data_format: :cli, command: nil, context: nil, value: nil, **_kwargs) # subclasses will generally want to call Client.munge_to_array() # on context and/or value before calling super() fail Cisco::RequestNotSupported unless self.supports?(data_format) Cisco::Logger.debug("Get state using data format '#{data_format}'") Cisco::Logger.debug(" executing command:\n #{command}") \ unless command.nil? || command.empty? Cisco::Logger.debug(" with context:\n #{context.join("\n ")}") \ unless context.nil? || context.empty? Cisco::Logger.debug(" to get value: #{value}") \ unless value.nil? # to be implemented by subclasses end |
#inspect ⇒ Object
134 135 136 |
# File 'lib/cisco_node_utils/client/client.rb', line 134 def inspect "<#{self.class} of #{@address}>" end |
#munge_to_array(val) ⇒ Object
32 33 34 |
# File 'lib/cisco_node_utils/client/utils.rb', line 32 def munge_to_array(val) self.class.munge_to_array(val) end |
#set(data_format: :cli, context: nil, values: nil, **_kwargs) ⇒ Object
Configure the given state on the device.
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
# File 'lib/cisco_node_utils/client/client.rb', line 171 def set(data_format: :cli, context: nil, values: nil, **_kwargs) # subclasses will generally want to call Client.munge_to_array() # on context and/or values before calling super() fail Cisco::RequestNotSupported unless self.supports?(data_format) cache_flush if cache_auto? Cisco::Logger.debug("Set state using data format '#{data_format}'") Cisco::Logger.debug(" with context:\n #{context.join("\n ")}") \ unless context.nil? || context.empty? Cisco::Logger.debug(" to value(s):\n #{values.join("\n ")}") \ unless values.nil? || values.empty? # to be implemented by subclasses end |
#supports?(data_format) ⇒ Boolean
80 81 82 |
# File 'lib/cisco_node_utils/client/client.rb', line 80 def supports?(data_format) data_formats.include?(data_format) end |
#to_s ⇒ Object
130 131 132 |
# File 'lib/cisco_node_utils/client/client.rb', line 130 def to_s @address.to_s end |