rbeapi 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. data/.gitignore +35 -0
  2. data/Gemfile +25 -0
  3. data/Guardfile +15 -0
  4. data/LICENSE +28 -0
  5. data/README.md +218 -0
  6. data/Rakefile +12 -0
  7. data/lib/rbeapi.rb +32 -0
  8. data/lib/rbeapi/api.rb +135 -0
  9. data/lib/rbeapi/api/aaa.rb +410 -0
  10. data/lib/rbeapi/api/dns.rb +198 -0
  11. data/lib/rbeapi/api/interfaces.rb +1193 -0
  12. data/lib/rbeapi/api/ipinterfaces.rb +328 -0
  13. data/lib/rbeapi/api/logging.rb +157 -0
  14. data/lib/rbeapi/api/mlag.rb +519 -0
  15. data/lib/rbeapi/api/ntp.rb +201 -0
  16. data/lib/rbeapi/api/ospf.rb +214 -0
  17. data/lib/rbeapi/api/prefixlists.rb +98 -0
  18. data/lib/rbeapi/api/radius.rb +317 -0
  19. data/lib/rbeapi/api/radius.rb.old +399 -0
  20. data/lib/rbeapi/api/routemaps.rb +100 -0
  21. data/lib/rbeapi/api/snmp.rb +427 -0
  22. data/lib/rbeapi/api/staticroutes.rb +88 -0
  23. data/lib/rbeapi/api/stp.rb +381 -0
  24. data/lib/rbeapi/api/switchports.rb +272 -0
  25. data/lib/rbeapi/api/system.rb +87 -0
  26. data/lib/rbeapi/api/tacacs.rb +236 -0
  27. data/lib/rbeapi/api/varp.rb +181 -0
  28. data/lib/rbeapi/api/vlans.rb +338 -0
  29. data/lib/rbeapi/client.rb +454 -0
  30. data/lib/rbeapi/eapilib.rb +334 -0
  31. data/lib/rbeapi/netdev/snmp.rb +370 -0
  32. data/lib/rbeapi/utils.rb +70 -0
  33. data/lib/rbeapi/version.rb +37 -0
  34. data/rbeapi.gemspec +32 -0
  35. data/spec/fixtures/dut.conf +5 -0
  36. data/spec/spec_helper.rb +22 -0
  37. data/spec/support/fixtures.rb +114 -0
  38. data/spec/support/shared_examples_for_api_modules.rb +124 -0
  39. data/spec/system/api_ospf_interfaces_spec.rb +58 -0
  40. data/spec/system/api_ospf_spec.rb +111 -0
  41. data/spec/system/api_varp_interfaces_spec.rb +60 -0
  42. data/spec/system/api_varp_spec.rb +44 -0
  43. data/spec/system/rbeapi/api/dns_spec.rb +77 -0
  44. data/spec/system/rbeapi/api/interfaces_base_spec.rb +94 -0
  45. data/spec/system/rbeapi/api/interfaces_ethernet_spec.rb +135 -0
  46. data/spec/system/rbeapi/api/interfaces_portchannel_spec.rb +188 -0
  47. data/spec/system/rbeapi/api/interfaces_vxlan_spec.rb +115 -0
  48. data/spec/system/rbeapi/api/ipinterfaces_spec.rb +97 -0
  49. data/spec/system/rbeapi/api/logging_spec.rb +65 -0
  50. data/spec/system/rbeapi/api/mlag_interfaces_spec.rb +80 -0
  51. data/spec/system/rbeapi/api/mlag_spec.rb +94 -0
  52. data/spec/system/rbeapi/api/ntp_spec.rb +76 -0
  53. data/spec/system/rbeapi/api/snmp_spec.rb +68 -0
  54. data/spec/system/rbeapi/api/stp_instances_spec.rb +61 -0
  55. data/spec/system/rbeapi/api/stp_interfaces_spec.rb +71 -0
  56. data/spec/system/rbeapi/api/stp_spec.rb +57 -0
  57. data/spec/system/rbeapi/api/switchports_spec.rb +135 -0
  58. data/spec/system/rbeapi/api/system_spec.rb +38 -0
  59. data/spec/system/rbeapi/api/vlans_spec.rb +121 -0
  60. metadata +274 -0
@@ -0,0 +1,454 @@
1
+ #
2
+ # Copyright (c) 2014, Arista Networks, Inc.
3
+ # All rights reserved.
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are
7
+ # met:
8
+ #
9
+ # Redistributions of source code must retain the above copyright notice,
10
+ # this list of conditions and the following disclaimer.
11
+ #
12
+ # Redistributions in binary form must reproduce the above copyright
13
+ # notice, this list of conditions and the following disclaimer in the
14
+ # documentation and/or other materials provided with the distribution.
15
+ #
16
+ # Neither the name of Arista Networks nor the names of its
17
+ # contributors may be used to endorse or promote products derived from
18
+ # this software without specific prior written permission.
19
+ #
20
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23
+ # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ARISTA NETWORKS
24
+ # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
27
+ # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
28
+ # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
29
+ # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
30
+ # IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
+ #
32
+ require 'inifile'
33
+
34
+ require 'rbeapi/utils'
35
+ require 'rbeapi/eapilib'
36
+ require 'rbeapi/api'
37
+
38
+ ##
39
+ # Rbeapi toplevel namespace
40
+ module Rbeapi
41
+ ##
42
+ # Rbeapi::Client
43
+ module Client
44
+
45
+ class << self
46
+
47
+ DEFAULT_TRANSPORT = 'http'
48
+
49
+ TRANSPORTS = { 'http' => 'Rbeapi::Eapilib::HttpEapiConnection',
50
+ 'https' => 'Rbeapi::Eapilib::HttpsEapiConnection',
51
+ 'http_local' => 'Rbeapi::Eapilib::HttpLocalEapiConenction',
52
+ 'socket' => 'Rbeapi::Eapilib::SocketEapiConnection' }
53
+
54
+ ##
55
+ # Returns the currently loaded config object. This function will
56
+ # create a new instance of the config object if one doesn't already
57
+ # exist
58
+ #
59
+ # @return [Config] Returns an instance of Config used for working
60
+ # with the eapi.conf file
61
+ def config
62
+ return @config if @config
63
+ @config = Config.new()
64
+ return @config
65
+ end
66
+
67
+ ##
68
+ # load_config overrides the default conf file loaded in the config
69
+ # instances using the supplied conf argument as the conf file. This
70
+ # method will clear out an previously loaded configuration and replace
71
+ # all entries with the contects of the supplied file.
72
+ #
73
+ # @param [String] :conf The full path to the conf file to load into
74
+ # the config instance.
75
+ def load_config(conf)
76
+ config.read(conf)
77
+ end
78
+
79
+ ##
80
+ # Returns the configuration options for the named connection from the
81
+ # the loaded configuuration. The configuration name is specified as
82
+ # the string right of the colon in the section name.
83
+ #
84
+ # @param [String] :name The connection name to return from the loaded
85
+ # configuration
86
+ #
87
+ # @return [Hash, nil] This method will return the configuration hash for
88
+ # the named configuration if found. If the name is not found, then
89
+ # nil is returned
90
+ def config_for(name)
91
+ return config.get_connection(name)
92
+ end
93
+
94
+ ##
95
+ # Retrieves the node config form the loaded configuration file and
96
+ # returns a Rbeapi::Node instance for working with the remote node.
97
+ #
98
+ # @param [String] :name The named configuration to use for creating the
99
+ # connection to the remote node
100
+ #
101
+ # @return [Rbeapi::Node, nil] Returns an instance of Rbeapi::Node. If
102
+ # the named configuration is not found then nil is returned
103
+ def connect_to(name)
104
+ config = config_for(name)
105
+ return nil unless config
106
+ config = Rbeapi::Utils.transform_keys_to_symbols(config)
107
+ connection = connect config
108
+ Node.new(connection)
109
+ end
110
+
111
+ ##
112
+ # Builds a connection object to a remote node using the specified
113
+ # options and return an instance of Rbeapi::Connection. All
114
+ # configuration options can be passed via the :opts param or can be
115
+ # overridden using environment variables. Environment variables are
116
+ # specified by prepending EAPI to the option name. For instance to
117
+ # override the host param use EAPI_HOST.
118
+ #
119
+ # @param [Hash] :opts the options to create a message with
120
+ # @option :opts [String] :host The IP address or hostname of the remote
121
+ # eAPI endpint
122
+ # @option :opts [String] :username The username to use to authenticate
123
+ # the eAPI connection with
124
+ # @option :opts [String] :password The password to use to authenticate
125
+ # the eAPI connection with
126
+ # @option :opts [String] :enable_pwd The enable password (if defined) to
127
+ # pass to the remote node to enter priviledged mode
128
+ # @option :opts [String] :use_ssl Specifies whether or not to use the
129
+ # HTTP or HTTPS protocol
130
+ # @option :opts [String] :port The port to connect to. If not specified
131
+ # The port is automatically determined based on the protocol used
132
+ # (443 for https, 80 for http)
133
+ #
134
+ # @return [Rbeapi::Connection] Returns an instance of Rbeapi::Connection
135
+ # using the specified configuration options
136
+ def connect(opts = {})
137
+ transport = opts.fetch(:transport, DEFAULT_TRANSPORT)
138
+ make_connection(transport, opts)
139
+ end
140
+
141
+ ##
142
+ # Creates a connection instance that can either be used directly or
143
+ # passed to a Node instance.
144
+ #
145
+ # @params [String] :transport The name of the transport to create.
146
+ # @params [Hash] :opts The options used to create the transport
147
+ #
148
+ # @return [Rbeapi::EapiConnection] A instance of a connection object
149
+ def make_connection(transport, opts = {})
150
+ klass = TRANSPORTS.fetch(transport)
151
+ cls = Rbeapi::Utils.class_from_string(klass)
152
+ cls.new(opts)
153
+ end
154
+ end
155
+
156
+ class Config < IniFile
157
+
158
+ CONFIG_SEARCH_PATH = ['~/.eapi.conf', '/mnt/flash/eapi.conf']
159
+
160
+ ##
161
+ # The Config class holds the loaded configuration file data. It is a
162
+ # subclass of IniFile. The Config class will automatically search for
163
+ # a filename to load (if none provided) and load the data when the
164
+ # object is instantiated.
165
+ #
166
+ # @param [String] :filename The full path to the filename to load. If
167
+ # the filename is not provided, then this class will attempt to find
168
+ # a valid conf file using the CONFIG_SEARCH_PATH.
169
+ def initialize(opts = {})
170
+ super(parameter: ':')
171
+ @filename = opts.fetch(:filename, nil)
172
+ autoload
173
+ end
174
+
175
+ ##
176
+ # This private method automtically finds and loads the conf file
177
+ # into the instance using the class variable CONFIG_SEARCH_PATH. The
178
+ # connections should be retrieved using the get_connection method
179
+ #
180
+ # @param [Hash] :opts The options for specifying the message
181
+ # @option :opts [String] :filename The full path to the filename
182
+ # to load. Using this option eliminates the use of the
183
+ # search path
184
+ def autoload(opts = {})
185
+ search_path = CONFIG_SEARCH_PATH.dup
186
+ search_path.insert(0, ENV['EAPI_CONF']) if ENV.key?('EAPI_CONF')
187
+
188
+ path = opts[:filename] || search_path
189
+
190
+ path.each do |fn|
191
+ fn = File.expand_path(fn)
192
+ return read(fn) if File.exists?(fn)
193
+ end
194
+ end
195
+ private :autoload
196
+
197
+ ##
198
+ # This method will read the specified filename and load its contents
199
+ # into the instance. It will also add the default localhost entry
200
+ # if it doesn't exist in the conf file
201
+ #
202
+ # @param [String] :filename The full path to the filename to load
203
+ def read(filename)
204
+ super(filename: filename)
205
+ unless get_connection 'localhost'
206
+ add_connection('localhost', transport: 'socket')
207
+ end
208
+ end
209
+
210
+ ##
211
+ # This method will cause the config to be loaded. The process of
212
+ # finding the configuration will be repeated so it is possible a
213
+ # different conf file could be choosen if the original file was
214
+ # removed or a new file added higher on the search priority list
215
+ #
216
+ # @param [Hash] :opts The options for specifying the message
217
+ # @opton :opts [String] :filename The full path to the file to load
218
+ def reload(opts = {})
219
+ autoload opts
220
+ end
221
+
222
+ ##
223
+ # Returns the configuration for the connection specified
224
+ #
225
+ # @param [String] :name The name of the connection to return from
226
+ # the configuration. This should be the string right of the :
227
+ # in the config section header
228
+ #
229
+ # @return [nil, Hash<String, String> Returns a hash of the connection
230
+ # properities from the loaded config. This method will return nil
231
+ # if the connection name is not found.
232
+ def get_connection(name)
233
+ return nil unless sections.include? "connection:#{name}"
234
+ self["connection:#{name}"]
235
+ end
236
+
237
+ ##
238
+ # Adds a new connection section to the current configuration
239
+ #
240
+ # @param [String] :name The name of the connection to add to the
241
+ # configuration.
242
+ # @param [Hash] :values The properties for the connection
243
+ def add_connection(name, values)
244
+ self["connection:#{name}"] = values
245
+ end
246
+ end
247
+
248
+ class Node
249
+
250
+ attr_reader :connection
251
+
252
+ ##
253
+ # The Node object provies an instnace for sending and receiveing messages
254
+ # with a specific EOS device. The methods provided in this calss allow
255
+ # for handling both enable mode and config mode commands
256
+ #
257
+ # @param [Rbeapi::Eapilib::EapiConnection] :connection An instance of
258
+ # EapiConnection used to send and receive eAPI formatted messages
259
+ def initialize(connection)
260
+ @connection = connection
261
+ @autorefresh = true
262
+ end
263
+
264
+ ##
265
+ # Provides access the nodes running-configuration. This is a lazily
266
+ # loaded memoized property for working with the node configuration
267
+ #
268
+ # @return [String] The node's running-config as a string
269
+ def running_config
270
+ return @running_config if @running_config
271
+ @running_config = get_config(params: 'all', as_string: true)
272
+ return @running_config
273
+ end
274
+
275
+ ##
276
+ # Provides access to the nodes startup-configuration. This is a lazily
277
+ # loaded memoized prpoerty for working with the nodes startup config
278
+ #
279
+ # @return [String] The node's startup-config as a string
280
+ def startup_config
281
+ return @startup_config if @startup_config
282
+ @startup_config = get_config(config: 'startup-config', as_string: true)
283
+ return @startup_config
284
+ end
285
+
286
+ ##
287
+ # Configures the node instance to use an enable password. EOS can be
288
+ # configured to require a second layer of authentication when putting
289
+ # the session into enable mode. The password supplied will be used to
290
+ # authenticate the session to enable mode if necessary.
291
+ #
292
+ # @param [String] :password The value of the enable password
293
+ def enable_authentication(password)
294
+ @enablepwd = password
295
+ end
296
+
297
+ ##
298
+ # The config method is a convenience method that will handling putting
299
+ # the switch into config mode prior to executing commands. The method
300
+ # will insert 'config' at the top of the command stack and then pop
301
+ # the empty hash from the response output before return the array
302
+ # to the caller
303
+ #
304
+ # @param [Array<String>] commands An ordered list of commands to execute
305
+ #
306
+ # @return [Array<Hash>] ordered list of output from commands
307
+ def config(commands)
308
+ commands = [*commands] unless commands.respond_to?('each')
309
+
310
+ commands.insert(0, 'configure')
311
+ response = run_commands commands
312
+
313
+ refresh if @autorefresh
314
+
315
+ response.shift
316
+ response
317
+ end
318
+
319
+ ##
320
+ # The enable method is a convenience method that will handling putting
321
+ # the switch into priviledge mode prior to executing commands.
322
+ #
323
+ # @param [Array<String>] commands An ordered list of commands to execute
324
+ # @param [String] :encoding The encoding scheme to use for sending and
325
+ # receive eAPI messages. Valid values are json and text. The default
326
+ # value is json
327
+ #
328
+ # @return [Array<Hash>] ordered list of output from commands
329
+ def enable(commands, opts = {})
330
+ commands = [*commands] unless commands.respond_to?('each')
331
+
332
+ encoding = opts.fetch(:encoding, 'json')
333
+ strict = opts.fetch(:strict, false)
334
+
335
+ results = []
336
+ if strict
337
+ responses = run_commands(commands, encoding)
338
+ responses.each_with_index do |resp, idx|
339
+ results << make_response(commands[idx], resp, encoding)
340
+ end
341
+ else
342
+ commands.each do |cmd|
343
+ begin
344
+ response = run_commands(cmd, encoding)
345
+ results << make_response(cmd, response.first, encoding)
346
+ rescue Rbeapi::Eapilib::CommandError => exc
347
+ raise unless exc.error_code == 1003
348
+ response = run_commands(cmd, 'text')
349
+ results << make_response(cmd, response.first, encoding)
350
+ end
351
+ end
352
+ end
353
+ results
354
+ end
355
+
356
+ ##
357
+ # Returns a response object from a call to the enable method. This
358
+ # private method is an internal method to ensure consistency in the
359
+ # return message format
360
+ #
361
+ # @param [String] :command The command send to the node
362
+ # @param [Hash] :response The response returned from the eAPI call
363
+ # @param [String] :encoding The encoding scheme used in the response
364
+ # which should be either json or text
365
+ #
366
+ # @return [Hash] A Ruby hash object
367
+ def make_response(command, result, encoding)
368
+ { command: command, result: result, encoding: encoding }
369
+ end
370
+ private :make_response
371
+
372
+ ##
373
+ # This method will send the ordered list of commands to the destination
374
+ # node using the transport. It is also response for inserting enable
375
+ # onto the command stack and popping the enable result on the response
376
+ #
377
+ # @param [Array] :commands The ordered list of commands to send to the
378
+ # destination node.
379
+ # @param [String] :encoding The encoding scheme to use for sending and
380
+ # receive eAPI requests. This argument is optional. Valid values
381
+ # include json or text. The default is json
382
+ def run_commands(commands, encoding = 'json')
383
+ commands = [*commands] unless commands.respond_to?('each')
384
+ commands = commands.dup
385
+
386
+ if @enablepwd
387
+ commands.insert(0, { 'cmd' => 'enable', 'input' => @enablepwd })
388
+ else
389
+ commands.insert(0, 'enable')
390
+ end
391
+
392
+ response = @connection.execute(commands, format: encoding)
393
+ response.shift
394
+ response
395
+ end
396
+
397
+ ##
398
+ # This method will retrieve the specified configuration from the node
399
+ # and return it in full text.
400
+ #
401
+ # @param [Hash] opts the options to create a message with
402
+ # @option :opts [String] :config The configuration instance to return from
403
+ # the node. Valid values are 'running-config' and 'startup-config'. If
404
+ # no value is specified, then 'running-config' is used
405
+ # @ :opts [String] :param Additional parameters to append to the
406
+ # retrieving the configuration. Valid values depend on the config
407
+ # file requested
408
+ #
409
+ # running-config params
410
+ # all Configuration with defaults
411
+ # detail Detail configuration with defaults
412
+ # diffs Differences from startup-config
413
+ # interfaces Filter config to include only the given interfaces
414
+ # sanitized Sanitized Output
415
+ # section Display sections containing matching commands
416
+ #
417
+ # startup-config params
418
+ # errors Show information about the errors in startup-config
419
+ # interfaces Filter config to include only the given interfaces
420
+ # section Display sections containing matching commands
421
+ #
422
+ # @return [String] the specified configuration as text
423
+ def get_config(opts = {})
424
+ config = opts.fetch(:config, 'running-config')
425
+ params = opts.fetch(:params, '')
426
+ as_string = opts.fetch(:as_string, false)
427
+ result = run_commands("show #{config} #{params}", 'text')
428
+ return result.first['output'].strip.split("\n") unless as_string
429
+ result.first['output'].strip
430
+ end
431
+
432
+ ##
433
+ # Returns an API module for working with the active conifguraiton
434
+ # of the node
435
+ def api(name, opts = {})
436
+ path = opts.fetch(:path, 'rbeapi/api')
437
+ namespace = opts.fetch(:namespace, 'Rbeapi::Api')
438
+ require "#{path}/#{name}"
439
+ clsname = "#{namespace}::#{name.capitalize}"
440
+ cls = Rbeapi::Utils.class_from_string(clsname)
441
+ return cls.instance(self) if cls.respond_to?(:instance)
442
+ cls.new(self)
443
+ end
444
+
445
+ ##
446
+ # Forces both the running-config and startup-config to be refreshed on
447
+ # the next call to those properties.
448
+ def refresh
449
+ @running_config = nil
450
+ @startup_config = nil
451
+ end
452
+ end
453
+ end
454
+ end