rbeapi 0.3.0 → 0.4.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 (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}"])