rbeapi 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/CHANGELOG.md +16 -0
  2. data/Gemfile +3 -1
  3. data/Guardfile +2 -2
  4. data/README.md +35 -24
  5. data/Rakefile +48 -18
  6. data/gems/inifile/inifile.spec.tmpl +50 -14
  7. data/gems/net_http_unix/net_http_unix.spec.tmpl +48 -15
  8. data/gems/netaddr/netaddr.spec.tmpl +47 -14
  9. data/lib/rbeapi/api/bgp.rb +100 -4
  10. data/lib/rbeapi/api/interfaces.rb +4 -5
  11. data/lib/rbeapi/api/radius.rb +1 -1
  12. data/lib/rbeapi/api/routemaps.rb +405 -37
  13. data/lib/rbeapi/api/system.rb +21 -0
  14. data/lib/rbeapi/api/users.rb +361 -0
  15. data/lib/rbeapi/api/varp.rb +50 -22
  16. data/lib/rbeapi/api/vrrp.rb +1072 -0
  17. data/lib/rbeapi/client.rb +12 -4
  18. data/lib/rbeapi/eapilib.rb +1 -1
  19. data/lib/rbeapi/version.rb +1 -1
  20. data/rbeapi.spec.tmpl +57 -25
  21. data/spec/system/rbeapi/api/dns_spec.rb +2 -2
  22. data/spec/system/rbeapi/api/routemaps_spec.rb +344 -0
  23. data/spec/system/rbeapi/api/switchports_spec.rb +1 -1
  24. data/spec/system/rbeapi/api/system_spec.rb +44 -4
  25. data/spec/system/{api_varp_interfaces_spec.rb → rbeapi/api/varp_interfaces_spec.rb} +25 -16
  26. data/spec/system/rbeapi/api/varp_spec.rb +76 -0
  27. data/spec/unit/rbeapi/api/bgp/bgp_neighbors_spec.rb +2 -0
  28. data/spec/unit/rbeapi/api/bgp/bgp_spec.rb +54 -1
  29. data/spec/unit/rbeapi/api/interfaces/portchannel_spec.rb +1 -1
  30. data/spec/unit/rbeapi/api/routemaps/default_spec.rb +370 -0
  31. data/spec/unit/rbeapi/api/routemaps/fixture_routemaps.text +27 -0
  32. data/spec/unit/rbeapi/api/system/default_spec.rb +102 -0
  33. data/spec/unit/rbeapi/api/system/fixture_system.text +2 -0
  34. data/spec/unit/rbeapi/api/users/default_spec.rb +280 -0
  35. data/spec/unit/rbeapi/api/users/fixture_users.text +4 -0
  36. data/spec/unit/rbeapi/api/vrrp/default_spec.rb +582 -0
  37. data/spec/unit/rbeapi/api/vrrp/fixture_vrrp.text +186 -0
  38. metadata +28 -8
  39. data/spec/system/api_varp_spec.rb +0 -41
@@ -0,0 +1,361 @@
1
+ #
2
+ # Copyright (c) 2015, 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 'rbeapi/api'
33
+
34
+ ##
35
+ # Rbeapi toplevel namespace
36
+ module Rbeapi
37
+ ##
38
+ # Api is module namesapce for working with the EOS command API
39
+ module Api
40
+ ##
41
+ # The Users class provides configuration of local user resources for
42
+ # an EOS node.
43
+ class Users < Entity
44
+ def initialize(node)
45
+ super(node)
46
+ # The regex used here parses the running configuration to find all
47
+ # username entries. There is extra logic in the regular expression
48
+ # to store the username as 'user' and then creates a back reference
49
+ # to find a following configuration line that might contain the
50
+ # users sshkey.
51
+ @users_re = Regexp.new(/^username\s+(?<user>[^\s]+)\s+
52
+ privilege\s+(?<priv>\d+)
53
+ (\s+role\s+(?<role>\S+))?
54
+ (?:\s+(?<nopassword>(nopassword)))?
55
+ (\s+secret\s+(?<encryption>0|5|7|sha512)\s+
56
+ (?<secret>\S+))?.*$\n
57
+ (username\s+\k<user>\s+
58
+ sshkey\s+(?<sshkey>.*)$)?/x)
59
+
60
+ @encryption_map = { 'cleartext' => '0',
61
+ 'md5' => '5',
62
+ 'sha512' => 'sha512' }
63
+ end
64
+
65
+ ##
66
+ # get returns the local user configuration
67
+ #
68
+ # @example
69
+ # {
70
+ # name: <string>,
71
+ # privilege: <integer>,
72
+ # role: <string>,
73
+ # nopassword: <boolean>,
74
+ # encryption: <'cleartext', 'md5', 'sha512'>
75
+ # secret: <string>,
76
+ # sshkey: <string>
77
+ # }
78
+ #
79
+ # @param [String] name The user name to return a resource for from the
80
+ # nodes configuration
81
+ #
82
+ # @return [nil, Hash<Symbol, Object>] Returns the user resource as a
83
+ # Hash. If the specified user name is not found in the nodes current
84
+ # configuration a nil object is returned
85
+ def get(name)
86
+ # The regex used here parses the running configuration to find one
87
+ # username entry.
88
+ user_re = Regexp.new(/^username\s+(?<user>#{name})\s+
89
+ privilege\s+(?<priv>\d+)
90
+ (\s+role\s+(?<role>\S+))?
91
+ (?:\s+(?<nopassword>(nopassword)))?
92
+ (\s+secret\s+(?<encryption>0|5|7|sha512)\s+
93
+ (?<secret>\S+))?.*$\n
94
+ (username\s+#{name}\s+
95
+ sshkey\s+(?<sshkey>.*)$)?/x)
96
+ user = config.scan(user_re)
97
+ return nil unless user
98
+ parse_user_entry(user[0])
99
+ end
100
+
101
+ ##
102
+ # getall returns a collection of user resource hashes from the nodes
103
+ # running configuration. The user resource collection hash is keyed
104
+ # by the unique user name.
105
+ #
106
+ # @example
107
+ # [
108
+ # <username>: {
109
+ # name: <string>,
110
+ # privilege: <integer>,
111
+ # role: <string>,
112
+ # nopassword: <boolean>,
113
+ # encryption: <'cleartext', 'md5', 'sha512'>
114
+ # secret: <string>,
115
+ # sshkey: <string>
116
+ # },
117
+ # ...
118
+ # ]
119
+ #
120
+ # @return [Hash<Symbol, Object>] returns a hash that represents the
121
+ # entire user collection from the nodes running configuration. If
122
+ # there are no user names configured, this method will return an empty
123
+ # hash.
124
+ def getall
125
+ entries = config.scan(@users_re)
126
+ response = {}
127
+ entries.each do |user|
128
+ response[user[0]] = parse_user_entry(user)
129
+ end
130
+ response
131
+ end
132
+
133
+ ##
134
+ # parse_user_entry maps the tokens find to the hash entries.
135
+ #
136
+ # @api private
137
+ #
138
+ # @param [Array] :user An array of values returned from the regular
139
+ # expression scan of the nodes configuration.
140
+ #
141
+ # @return [Hash<Symbol, Object>] resource hash attribute
142
+ def parse_user_entry(user)
143
+ hsh = {}
144
+ hsh[:name] = user[0]
145
+ hsh[:privilege] = user[1].to_i
146
+ hsh[:role] = user[2]
147
+ hsh[:nopassword] = user[3] ? true : false
148
+ # Map the encryption value if set, if there is no mapping then
149
+ # just return the value.
150
+ if user[4]
151
+ @encryption_map.each do |key, value|
152
+ if value == user[4]
153
+ user[4] = key
154
+ break
155
+ end
156
+ end
157
+ end
158
+ hsh[:encryption] = user[4]
159
+ hsh[:secret] = user[5]
160
+ hsh[:sshkey] = user[6]
161
+ hsh
162
+ end
163
+ private :parse_user_entry
164
+
165
+ ##
166
+ # create will create a new user name resource in the nodes current
167
+ # configuration with the specified user name. Creating users require
168
+ # either a secret (password) or the nopassword keyword to be specified.
169
+ # Optional parameters can be passed in to initialize user name specific
170
+ # settings.
171
+ #
172
+ # @eos_version 4.13.7M
173
+ #
174
+ # @commands
175
+ # username <name> nopassword privilege <value> role <value>
176
+ # username <name> secret [0,5,sha512] <secret> ...
177
+ #
178
+ # @param [String] :name The name of the user to create
179
+ #
180
+ # @param [hash] :opts Optional keyword arguments
181
+ #
182
+ # @option :opts [Boolean] :nopassword Configures the user to be able to
183
+ # authenticate without a password challenge
184
+ #
185
+ # @option :opts [String] :secret The secret (password) to assign to this
186
+ # user
187
+ #
188
+ # @option :opts [String] :encryption Specifies how the secret is encoded.
189
+ # Valid values are "cleartext", "md5", "sha512". The default is
190
+ # "cleartext"
191
+ #
192
+ # @option :opts [String] :privilege The privilege value to assign to
193
+ # the user
194
+ #
195
+ # @option :opts [String] :role The role value to assign to the user
196
+ #
197
+ # @option :opts [String] :sshkey The sshkey value to assign to the user
198
+ #
199
+ # @return [Boolean] returns true if the command completed successfully
200
+ def create(name, opts = {})
201
+ cmd = "username #{name}"
202
+ cmd << " privilege #{opts[:privilege]}" if opts[:privilege]
203
+ cmd << " role #{opts[:role]}" if opts[:role]
204
+ if opts[:nopassword] == :true
205
+ cmd << ' nopassword'
206
+ else
207
+ # Map the encryption value if set, if there is no mapping then
208
+ # just return the value.
209
+ enc = opts.fetch(:encryption, 'cleartext')
210
+ unless @encryption_map[enc]
211
+ fail ArgumentError, "invalid encryption value: #{enc}"
212
+ end
213
+ enc = @encryption_map[enc]
214
+
215
+ unless opts[:secret]
216
+ fail ArgumentError,
217
+ 'secret must be specified if nopassword is false'
218
+ end
219
+ cmd << " secret #{enc} #{opts[:secret]}"
220
+ end
221
+ cmds = [cmd]
222
+ cmds << "username #{name} sshkey #{opts[:sshkey]}" if opts[:sshkey]
223
+ configure(cmds)
224
+ end
225
+
226
+ ##
227
+ # delete will delete an existing user name from the nodes current
228
+ # running configuration. If the delete method is called and the user
229
+ # name does not exist, this method will succeed.
230
+ #
231
+ # @eos_version 4.13.7M
232
+ #
233
+ # @commands
234
+ # no username <name>
235
+ #
236
+ # @param [String] :name The user name to delete from the node.
237
+ #
238
+ # @return [Boolean] returns true if the command completed successfully
239
+ def delete(name)
240
+ configure("no username #{name}")
241
+ end
242
+
243
+ ##
244
+ # default will configure the user name using the default keyword. This
245
+ # command has the same effect as deleting the user name from the nodes
246
+ # running configuration.
247
+ #
248
+ # @eos_version 4.13.7M
249
+ #
250
+ # @commands
251
+ # default username <name>
252
+ #
253
+ # @param [String] :name The user name to default in the nodes
254
+ # configuration.
255
+ #
256
+ # @return [Boolean] returns true if the command complete successfully
257
+ def default(name)
258
+ configure("default username #{name}")
259
+ end
260
+
261
+ ##
262
+ # set_privilege configures the user privilege value for the specified user
263
+ # name in the nodes running configuration. If enable is false in the
264
+ # opts keyword Hash then the name value is negated using the no
265
+ # keyword. If the default keyword is set to true, then the privilege value
266
+ # is defaulted using the default keyword. The default keyword takes
267
+ # precedence over the enable keyword
268
+ #
269
+ # @eos_version 4.13.7M
270
+ #
271
+ # @commands
272
+ # username <name> privilege <value>
273
+ # no username <name> privilege <value>
274
+ # default username <name> privilege <value>
275
+ #
276
+ # @param [String] :name The user name to default in the nodes
277
+ # configuration.
278
+ #
279
+ # @param [Hash] :opts Optional keyword arguments
280
+ #
281
+ # @option :opts [String] :value The privilege value to assign to the user
282
+ #
283
+ # @option :opts [Boolean] :enable If false then the command is
284
+ # negated. Default is true.
285
+ #
286
+ # @option :opts [Boolean] :default Configure the user privilege value
287
+ # using the default keyword
288
+ #
289
+ # @return [Boolean] returns true if the command completed successfully
290
+ def set_privilege(name, opts = {})
291
+ configure(command_builder("username #{name} privilege", opts))
292
+ end
293
+
294
+ ##
295
+ # set_role configures the user role value for the specified user
296
+ # name in the nodes running configuration. If enable is false in the
297
+ # opts keyword Hash then the name value is negated using the no
298
+ # keyword. If the default keyword is set to true, then the role value
299
+ # is defaulted using the default keyword. The default keyword takes
300
+ # precedence over the enable keyword
301
+ #
302
+ # @eos_version 4.13.7M
303
+ #
304
+ # @commands
305
+ # username <name> role <value>
306
+ # no username <name> role <value>
307
+ # default username <name> role <value>
308
+ #
309
+ # @param [String] :name The user name to default in the nodes
310
+ # configuration.
311
+ #
312
+ # @param [Hash] :opts Optional keyword arguments
313
+ #
314
+ # @option :opts [String] :value The role value to assign to the user
315
+ #
316
+ # @option :opts [Boolean] :enable If false then the command is
317
+ # negated. Default is true.
318
+ #
319
+ # @option :opts [Boolean] :default Configure the user role value
320
+ # using the default keyword
321
+ #
322
+ # @return [Boolean] returns true if the command completed successfully
323
+ def set_role(name, opts = {})
324
+ configure(command_builder("username #{name} role", opts))
325
+ end
326
+
327
+ ##
328
+ # set_sshkey configures the user sshkey value for the specified user
329
+ # name in the nodes running configuration. If enable is false in the
330
+ # opts keyword Hash then the name value is negated using the no
331
+ # keyword. If the default keyword is set to true, then the sshkey value
332
+ # is defaulted using the default keyword. The default keyword takes
333
+ # precedence over the enable keyword
334
+ #
335
+ # @eos_version 4.13.7M
336
+ #
337
+ # @commands
338
+ # username <name> sshkey <value>
339
+ # no username <name> sshkey <value>
340
+ # default username <name> sshkey <value>
341
+ #
342
+ # @param [String] :name The user name to default in the nodes
343
+ # configuration.
344
+ #
345
+ # @param [Hash] :opts Optional keyword arguments
346
+ #
347
+ # @option :opts [String] :value The sshkey value to assign to the user
348
+ #
349
+ # @option :opts [Boolean] :enable If false then the command is
350
+ # negated. Default is true.
351
+ #
352
+ # @option :opts [Boolean] :default Configure the user sshkey value
353
+ # using the default keyword
354
+ #
355
+ # @return [Boolean] returns true if the command completed successfully
356
+ def set_sshkey(name, opts = {})
357
+ configure(command_builder("username #{name} sshkey", opts))
358
+ end
359
+ end
360
+ end
361
+ end
@@ -54,15 +54,19 @@ module Rbeapi
54
54
  # key / value pairs.
55
55
  def get
56
56
  response = {}
57
+ response.merge!(parse_mac_address(config))
58
+ response[:interfaces] = interfaces.getall
59
+ response
60
+ end
57
61
 
58
- regex = /(?<=^ip\svirtual-router\smac-address\s)
59
- ((?:[a-f0-9]{2}:){5}[a-f0-9]{2})$/x
60
-
62
+ def parse_mac_address(config)
63
+ # ip virtual-router mac-address value will always
64
+ # be stored in aa:bb:cc:dd:ee:ff format
65
+ regex = /mac-address ((?:[a-f0-9]{2}:){5}[a-f0-9]{2})$/
61
66
  mdata = regex.match(config)
62
- response['mac_address'] = mdata.nil? ? '' : mdata[1]
63
- response['interfaces'] = interfaces.getall
64
- response
67
+ { mac_address: mdata.nil? ? '' : mdata[1] }
65
68
  end
69
+ private :parse_mac_address
66
70
 
67
71
  def interfaces
68
72
  return @interfaces if @interfaces
@@ -95,7 +99,6 @@ module Rbeapi
95
99
  #
96
100
  # Example
97
101
  # {
98
- # "name": <string>,
99
102
  # "addresses": array<string>
100
103
  # }
101
104
  #
@@ -108,8 +111,8 @@ module Rbeapi
108
111
  def get(name)
109
112
  config = get_block("^interface #{name}")
110
113
  return nil unless config
111
- addrs = config.scan(/(?<=\s{3}ip\svirtual-router\saddress\s).+$/)
112
- { 'addresses' => addrs }
114
+ response = parse_addresses(config)
115
+ response
113
116
  end
114
117
 
115
118
  ##
@@ -118,8 +121,8 @@ module Rbeapi
118
121
  #
119
122
  # Example
120
123
  # {
121
- # <name>: {...},
122
- # <name>: {...}
124
+ # "name": {...},
125
+ # "name": {...}
123
126
  # }
124
127
  #
125
128
  # @return [nil, Hash<String, String>] A Ruby hash that represents the
@@ -127,12 +130,20 @@ module Rbeapi
127
130
  # interfaces are configured.
128
131
  def getall
129
132
  interfaces = config.scan(/(?<=^interface\s)(Vl.+)$/)
130
- interfaces.first.each_with_object({}) do |name, resp|
131
- data = get(name)
132
- resp[name] = data if data
133
+ return nil unless interfaces
134
+
135
+ interfaces.each_with_object({}) do |name, resp|
136
+ data = get(name[0])
137
+ resp[name.first] = data if data
133
138
  end
134
139
  end
135
140
 
141
+ def parse_addresses(config)
142
+ addrs = config.scan(/(?<=\s{3}ip\svirtual-router\saddress\s).+$/)
143
+ { addresses: addrs }
144
+ end
145
+ private :parse_addresses
146
+
136
147
  ##
137
148
  # The set_addresses method assigns one or more virtual IPv4 address
138
149
  # to the specified VLAN interface. All existing addresses are
@@ -153,29 +164,46 @@ module Rbeapi
153
164
  value = opts[:value]
154
165
  enable = opts.fetch(:enable, true)
155
166
  default = opts[:default] || false
167
+ cmds = ["interface #{name}"]
156
168
 
157
169
  case default
158
170
  when true
159
- configure(["interface #{name}", 'default ip virtual-router address'])
171
+ cmds << 'default ip virtual-router address'
160
172
  when false
161
- get(name)['addresses'].each do |addr|
162
- result = remove_address(name, addr)
163
- return result unless result
164
- end
173
+ cmds << 'no ip virtual-router address'
165
174
  if enable
175
+ fail ArgumentError,
176
+ 'no values for addresses provided' unless value
166
177
  value.each do |addr|
167
- result = add_address(name, addr)
168
- return result unless result
178
+ cmds << "ip virtual-router address #{addr}"
169
179
  end
170
180
  end
171
181
  end
172
- true
182
+ configure(cmds)
173
183
  end
174
184
 
185
+ ##
186
+ # The add_address method assigns one virtual IPv4 address
187
+ #
188
+ # @param [String] :name The name of the interface. The
189
+ # name argument must be the full interface name. Valid interfaces
190
+ # are restricted to VLAN interfaces
191
+ # @param [string] :address The virtual router address to add
192
+ #
193
+ # @return [Boolean] True if the commands succeeds otherwise False
175
194
  def add_address(name, value)
176
195
  configure(["interface #{name}", "ip virtual-router address #{value}"])
177
196
  end
178
197
 
198
+ ##
199
+ # The remove_address method removes one virtual IPv4 address
200
+ #
201
+ # @param [String] :name The name of the interface. The
202
+ # name argument must be the full interface name. Valid interfaces
203
+ # are restricted to VLAN interfaces
204
+ # @param [string] :address The virtual router address to remove
205
+ #
206
+ # @return [Boolean] True if the commands succeeds otherwise False
179
207
  def remove_address(name, value)
180
208
  configure(["interface #{name}",
181
209
  "no ip virtual-router address #{value}"])