rbeapi 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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