Class: Cisco::Client
- Inherits:
-
Object
- Object
- Cisco::Client
- Defined in:
- lib/util/client.rb,
lib/util/client/utils.rb,
lib/util/client/client.rb,
lib/util/client/grpc/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
-
#address ⇒ Object
readonly
Returns the value of attribute address.
-
#host ⇒ Object
readonly
Returns the value of attribute host.
-
#password ⇒ Object
readonly
Returns the value of attribute password.
-
#platform ⇒ Object
readonly
Returns the value of attribute platform.
-
#port ⇒ Object
readonly
Returns the value of attribute port.
-
#username ⇒ Object
readonly
Returns the value of attribute username.
Class Method Summary collapse
- .clients ⇒ Object
-
.create(client_class) ⇒ Object
Try to create an instance of the specified subclass.
- .environment(client_class) ⇒ Object
- .environment_name(client_class) ⇒ Object
-
.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”.
-
.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
-
#get(command: nil, **_kwargs) ⇒ String, ...
Get the given state from the device.
-
#initialize(platform: nil, **kwargs) ⇒ Client
constructor
A new instance of Client.
- #inspect ⇒ Object
- #munge_to_array(val) ⇒ Object
-
#set(values: nil, **_kwargs) ⇒ Object
Configure the given state on the device.
- #to_s ⇒ Object
Constructor Details
#initialize(platform: nil, **kwargs) ⇒ Client
Returns a new instance of Client.
38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/util/client/client.rb', line 38 def initialize(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.platform = platform end |
Instance Attribute Details
#address ⇒ Object (readonly)
Returns the value of attribute address.
36 37 38 |
# File 'lib/util/client/client.rb', line 36 def address @address end |
#host ⇒ Object (readonly)
Returns the value of attribute host.
36 37 38 |
# File 'lib/util/client/client.rb', line 36 def host @host end |
#password ⇒ Object (readonly)
Returns the value of attribute password.
36 37 38 |
# File 'lib/util/client/client.rb', line 36 def password @password end |
#platform ⇒ Object
Returns the value of attribute platform.
36 37 38 |
# File 'lib/util/client/client.rb', line 36 def platform @platform end |
#port ⇒ Object (readonly)
Returns the value of attribute port.
36 37 38 |
# File 'lib/util/client/client.rb', line 36 def port @port end |
#username ⇒ Object (readonly)
Returns the value of attribute username.
36 37 38 |
# File 'lib/util/client/client.rb', line 36 def username @username end |
Class Method Details
.clients ⇒ Object
27 28 29 |
# File 'lib/util/client/client.rb', line 27 def self.clients @@clients end |
.create(client_class) ⇒ Object
Try to create an instance of the specified subclass
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/util/client/client.rb', line 80 def self.create(client_class) env = environment(client_class) env_name = environment_name(client_class) fail Cisco::ClientError, "No client environment configured for '#{env_name}'" unless env host = env[:host] port = env[:port] debug "Trying to connect to #{host}:#{port} as #{client_class}" errors = [] begin client = client_class.new(**env) 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 handle_errors(errors) end |
.environment(client_class) ⇒ Object
75 76 77 |
# File 'lib/util/client/client.rb', line 75 def self.environment(client_class) Cisco::Util::Environment.environment(environment_name(client_class)) end |
.environment_name(client_class) ⇒ Object
71 72 73 |
# File 'lib/util/client/client.rb', line 71 def self.environment_name(client_class) client_class.name.split('::').last.downcase 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/util/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 |
.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/util/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
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/util/client/client.rb', line 102 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/util/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.
32 33 34 |
# File 'lib/util/client/client.rb', line 32 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.
129 130 131 132 133 134 135 |
# File 'lib/util/client/utils.rb', line 129 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 |
# File 'lib/util/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 if input[0] == '/' && input[-1] == '/' # '/foo/' => %r{foo} return Regexp.new(input[1..-2]) elsif input[0] == '/' && input[-2..-1] == '/i' # '/foo/i' => %r{foo}i return Regexp.new(input[1..-3], Regexp::IGNORECASE) else # 'foo' => %r{^foo$}i return Regexp.new("^#{input}$", Regexp::IGNORECASE) end end end |
.validate_args(**kwargs) ⇒ Object
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/util/client/client.rb', line 53 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
#get(command: nil, **_kwargs) ⇒ String, ...
Get the given state from the device.
153 154 155 156 157 158 159 160 |
# File 'lib/util/client/client.rb', line 153 def get(command: nil, **_kwargs) # subclasses will generally want to call Client.munge_to_array() # on value before calling super() Cisco::Logger.debug(" executing command:\n #{command}") \ unless command.nil? || command.empty? # to be implemented by subclasses end |
#inspect ⇒ Object
130 131 132 |
# File 'lib/util/client/client.rb', line 130 def inspect "<#{self.class} of #{@address}>" end |
#munge_to_array(val) ⇒ Object
32 33 34 |
# File 'lib/util/client/utils.rb', line 32 def munge_to_array(val) self.class.munge_to_array(val) end |
#set(values: nil, **_kwargs) ⇒ Object
Configure the given state on the device.
138 139 140 141 142 143 144 145 |
# File 'lib/util/client/client.rb', line 138 def set(values: nil, **_kwargs) # subclasses will generally want to call Client.munge_to_array() # on values before calling super() Cisco::Logger.debug("values: #{values})") \ unless values.nil? || values.empty? # to be implemented by subclasses end |
#to_s ⇒ Object
126 127 128 |
# File 'lib/util/client/client.rb', line 126 def to_s @address.to_s end |