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,334 @@
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 'net/http'
33
+ require 'json'
34
+
35
+ require 'net_http_unix'
36
+
37
+ module Rbeapi
38
+
39
+ module Eapilib
40
+
41
+ DEFAULT_HTTP_PORT = 80
42
+ DEFAULT_HTTPS_PORT = 443
43
+ DEFAULT_HTTP_LOCAL_PORT = 8080
44
+ DEFAULT_HTTP_TIMEOUT = 10
45
+ DEFAULT_HTTP_PATH = '/command-api'
46
+ DEFAULT_UNIX_SOCKET = '/var/run/command-api.sock'
47
+
48
+ class EapiError < StandardError
49
+
50
+ attr_accessor :commands
51
+
52
+ ##
53
+ # Base error class for generating exceptions. The EapiError class
54
+ # provides one property for holding the set of commands issued
55
+ # when the error was generated
56
+ #
57
+ # @param [String] :message The error message to return from raising
58
+ # the exception
59
+ def initalize(message)
60
+ @message = message
61
+ @commands = nil
62
+ super(message)
63
+ end
64
+ end
65
+
66
+ class CommandError < EapiError
67
+
68
+ attr_reader :error_code
69
+ attr_reader :error_text
70
+
71
+ ##
72
+ # A CommandError exception is raised in response to an eAPI call that
73
+ # returns a failure message. The exception contains the eAPI error
74
+ # code and error text.
75
+ #
76
+ # @param [String] :message The error message to return from raising
77
+ # this exception
78
+ # @param [Integer] :code The error code associated with the error
79
+ # messsage to be raised
80
+ # @param [Array] :commands The list of commands that were used in the
81
+ # eAPI request message
82
+ def initialize(message, code, commands = nil)
83
+ @error_code = code
84
+ @error_text = message
85
+ @commands = commands
86
+ message = "Error [#{code}]: #{message}"
87
+ super(message)
88
+ end
89
+ end
90
+
91
+ class ConnectionError < EapiError
92
+
93
+ attr_accessor :connection_type
94
+
95
+ ##
96
+ # A ConnectionError exception is raised when the connection object
97
+ # is unable to establish a connection with eAPI.
98
+ #
99
+ # @param [String] :message The error message to return from raising
100
+ # this exception
101
+ # @param [String] :connection_type The optional connection_type of
102
+ # the instance
103
+ # @param [Array] :commands The list of commands that were used in the
104
+ # eAPI request message
105
+ def initialize(message, connection_type = nil, commands = nil)
106
+ @connection_type = connection_type
107
+ @commands = commands
108
+ super(message)
109
+ end
110
+ end
111
+
112
+ class EapiConnection
113
+
114
+ attr_reader :error
115
+
116
+ ##
117
+ # The EapiConnection provides a base class for building eAPI connection
118
+ # instances with a specific transport for connecting to Arista EOS
119
+ # devices. This class handles sending and receiving eAPI calls using
120
+ # JSON-RPC over HTTP. This class should not need to be directly
121
+ # instantiated.
122
+ #
123
+ # @param [Net::HTTP] :transport The HTTP transport to use for sending
124
+ # and receive eAPI request and response messages
125
+ def initialize(transport)
126
+ @transport = transport
127
+ @error = nil
128
+ end
129
+
130
+ ##
131
+ # Configures the connection authentication values (username and
132
+ # and password). The authentication values are used to authenticate
133
+ # the eAPI connection. Using authentication is only required for
134
+ # connections that use Http or Https transports
135
+ #
136
+ # @param [String] :username The username to use to authenticate to
137
+ # eAPI with
138
+ # @param [String] :password The password to use to authenticate to
139
+ # eAPI with
140
+ def authentication(username, password)
141
+ @username = username
142
+ @password = password
143
+ end
144
+
145
+ ##
146
+ # Generates the eAPI JSON request message.
147
+ #
148
+ # @example eAPI Request
149
+ # {
150
+ # "jsonrpc": "2.0",
151
+ # "method": "runCmds",
152
+ # "params": {
153
+ # "version": 1,
154
+ # "cmds": [
155
+ # <commands>
156
+ # ],
157
+ # "format": [json, text],
158
+ # }
159
+ # "id": <reqid>
160
+ # }
161
+ #
162
+ # @param [Array] :commands The ordered set of commands that should
163
+ # be included in the eAPI request
164
+ # @param [Hash] :opts Optional keyword arguments
165
+ # @option :opts [String] :id The value to use for the eAPI request
166
+ # id. If not provided,the object_id for the connection instance
167
+ # will be used
168
+ # @option :opts [String] :format The encoding formation to pass in
169
+ # the eAPI request. Valid values are json or text. The default
170
+ # value is json
171
+ #
172
+ # @return [Hash] Returns a Ruby hash of the request message that is
173
+ # suitable to be JSON encoded and sent to the desitination node
174
+ def request(commands, opts = {})
175
+ id = opts.fetch(:reqid, self.object_id)
176
+ format = opts.fetch(:format, 'json')
177
+ cmds = [*commands]
178
+ params = { 'version' => 1, 'cmds' => cmds, 'format' => format }
179
+ { 'jsonrpc' => '2.0', 'method' => 'runCmds',
180
+ 'params' => params, 'id' => id }
181
+ end
182
+
183
+ ##
184
+ # This method will send the request to the node over the specified
185
+ # transport and return a response message with the contents from
186
+ # the eAPI response. eAPI responds to request messages with either
187
+ # a success message or failure message.
188
+ #
189
+ # @example eAPI Response - success
190
+ # {
191
+ # "jsonrpc": "2.0",
192
+ # "result": [
193
+ # {},
194
+ # {},
195
+ # {
196
+ # "warnings": [
197
+ # <message>
198
+ # ]
199
+ # },
200
+ # ],
201
+ # "id": <reqid>
202
+ # }
203
+ #
204
+ # @example eAPI Response - failure
205
+ # {
206
+ # "jsonrpc": "2.0",
207
+ # "error": {
208
+ # "code": <int>,
209
+ # "message": <string>,
210
+ # "data": [
211
+ # {},
212
+ # {},
213
+ # {
214
+ # "errors": [
215
+ # <message>
216
+ # ]
217
+ # }
218
+ # ]
219
+ # },
220
+ # "id": <reqid>
221
+ # }
222
+ #
223
+ # @param [Hash] :data A hash containing the body of the request
224
+ # message. This should be a valid eAPI request message.
225
+ #
226
+ # @return [Hash] returns the response message as a Ruby hash object
227
+ #
228
+ # @raises [CommandError] Raised if an eAPI failure response is return
229
+ # from the destination node.
230
+ def send(data)
231
+ request = Net::HTTP::Post.new('/command-api')
232
+ request.body = JSON.dump(data)
233
+ request.basic_auth @username, @password
234
+
235
+ begin
236
+ @transport.open_timeout = DEFAULT_HTTP_TIMEOUT
237
+ response = @transport.request(request)
238
+ decoded = JSON(response.body)
239
+
240
+ if decoded.include?('error')
241
+ code = decoded['error']['code']
242
+ msg = decoded['error']['message']
243
+ fail CommandError.new(msg, code)
244
+ end
245
+ rescue Timeout::Error
246
+ raise ConnectionError, 'unable to connect to eAPI'
247
+ end
248
+
249
+ return decoded
250
+ end
251
+
252
+ ##
253
+ # Executes the commands on the destination node and returns the
254
+ # response from the node.
255
+ #
256
+ # @param [Array] :commands The ordered list of commandst to execute
257
+ # on the destination node.
258
+ # @param [Hash] :opts Optional keyword arguments
259
+ # @option :opts [String] :encoding Used to specify the encoding to be
260
+ # used for the response. Valid encoding values are json or text
261
+ #
262
+ # @returns [Array<Hash>] This method will return the array of responses
263
+ # for each command executed on the node.
264
+ #
265
+ # @raises [CommandError] Raises a CommandError if rescued from the
266
+ # send method and adds the list of commands to the exception message
267
+ #
268
+ # @raises [ConnectionError] Raises a ConnectionError if resuced and
269
+ # adds the list of commands to the exception message
270
+ def execute(commands, opts = {})
271
+ begin
272
+ @error = nil
273
+ request = request(commands, opts)
274
+ response = send request
275
+ return response['result']
276
+ rescue ConnectionError, CommandError => exc
277
+ exc.commands = commands
278
+ @error = exc
279
+ raise
280
+ end
281
+ end
282
+ end
283
+
284
+ class SocketEapiConnection < EapiConnection
285
+ def initialize(opts = {})
286
+ path = opts.fetch(:path, DEFAULT_UNIX_SOCKET)
287
+ transport = NetX::HTTPUnix.new("unix://#{path}")
288
+ super(transport)
289
+ end
290
+ end
291
+
292
+ class HttpEapiConnection < EapiConnection
293
+ def initialize(opts = {})
294
+ port = opts.fetch(:port, DEFAULT_HTTP_PORT)
295
+ host = opts.fetch(:host, 'localhost')
296
+
297
+ transport = Net::HTTP.new(host, port.to_i)
298
+ super(transport)
299
+
300
+ user = opts.fetch(:username, 'admin')
301
+ pass = opts.fetch(:password, '')
302
+ authentication(user, pass)
303
+ end
304
+ end
305
+
306
+ class HttpLocalEapiConnection < EapiConnection
307
+ def initialize(opts = {})
308
+ port = opts.fetch(:port, DEFAULT_HTTP_LOCAL_PORT)
309
+ transport = Net::HTTP.new('localhost', port)
310
+ super(transport)
311
+
312
+ user = opts.fetch(:username, 'admin')
313
+ pass = opts.fetch(:password, '')
314
+ authentication(user, pass)
315
+ end
316
+ end
317
+
318
+ class HttpsEapiConnection < EapiConnection
319
+ def initialize(opts = {})
320
+ host = opts.fetch(:host, 'localhost')
321
+ port = opts.fetch(:port, DEFAULT_HTTPS_PORT)
322
+
323
+ transport = Net::HTTP.new(host, port)
324
+ transport.use_ssl = true
325
+ transport.verify_mode = OpenSSL::SSL::VERIFY_NONE
326
+ super(transport)
327
+
328
+ user = opts.fetch(:username, 'admin')
329
+ pass = opts.fetch(:password, '')
330
+ authentication(user, pass)
331
+ end
332
+ end
333
+ end
334
+ end
@@ -0,0 +1,370 @@
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
+
33
+ require 'rbeapi/api'
34
+
35
+ module Rbeapi
36
+
37
+ module Netdev
38
+
39
+ ##
40
+ # The Netdev class is a straight port of the original PuppetX netdev
41
+ # code that existed prior to rbeapi. This should be considered a legacy
42
+ # implementation that will go away as the functions get merged into
43
+ # rbeapi.
44
+ #
45
+ # This class should NOT be used for any further development.
46
+ # YE BE WARNED!
47
+ #
48
+ class Snmp < Rbeapi::Api::Entity
49
+ # snmp_notification_receivers obtains a list of all the snmp
50
+ # notification receivers and returns them as an Array of resource
51
+ # hashes suitable for the provider's new class method. This command
52
+ # maps the `show snmp host` command to an array of resource hashes.
53
+ #
54
+ # @api public
55
+ #
56
+ # @return [Array<Hash<Symbol,Object>>] Array of resource hashes.
57
+ def snmp_notification_receivers
58
+ cmd = 'show snmp host'
59
+ result = node.enable(cmd)
60
+ text = result.first[:result]['output']
61
+ parse_snmp_hosts(text)
62
+ end
63
+
64
+ ##
65
+ # parse_snmp_hosts parses the raw text from the `show snmp host`
66
+ # command and returns an Array of resource hashes.
67
+ #
68
+ # rubocop:disable Metrics/MethodLength
69
+ #
70
+ # @param [String] text The text of the `show snmp host` output, e.g.
71
+ # for three hosts:
72
+ #
73
+ # ```
74
+ # Notification host: 127.0.0.1 udp-port: 162 type: trap
75
+ # user: public security model: v3 noauth
76
+ #
77
+ # Notification host: 127.0.0.1 udp-port: 162 type: trap
78
+ # user: smtpuser security model: v3 auth
79
+ #
80
+ # Notification host: 127.0.0.2 udp-port: 162 type: trap
81
+ # user: private security model: v2c
82
+ #
83
+ # Notification host: 127.0.0.3 udp-port: 162 type: trap
84
+ # user: public security model: v1
85
+ #
86
+ # Notification host: 127.0.0.4 udp-port: 10162 type: inform
87
+ # user: private security model: v2c
88
+ #
89
+ # Notification host: 127.0.0.4 udp-port: 162 type: trap
90
+ # user: priv@te security model: v1
91
+ #
92
+ # Notification host: 127.0.0.4 udp-port: 162 type: trap
93
+ # user: public security model: v1
94
+ #
95
+ # Notification host: 127.0.0.4 udp-port: 20162 type: trap
96
+ # user: private security model: v1
97
+ #
98
+ # ```
99
+ #
100
+ # @api private
101
+ #
102
+ # @return [Array<Hash<Symbol,Object>>] Array of resource hashes.
103
+ def parse_snmp_hosts(text)
104
+ re = /host: ([^\s]+)\s+.*?port: (\d+)\s+type: (\w+)\s*user: (.*?)\s+security model: (.*?)\n/m # rubocop:disable Metrics/LineLength
105
+ text.scan(re).map do |(host, port, type, username, auth)|
106
+ resource_hash = { name: host, ensure: :present, port: port.to_i }
107
+ sec_match = /^v3 (\w+)/.match(auth)
108
+ resource_hash[:security] = sec_match[1] if sec_match
109
+ ver_match = /^(v\d)/.match(auth) # first 2 characters
110
+ resource_hash[:version] = ver_match[1] if ver_match
111
+ resource_hash[:type] = /trap/.match(type) ? :traps : :informs
112
+ resource_hash[:username] = username
113
+ resource_hash
114
+ end
115
+ end
116
+ # rubocop:enable Metrics/MethodLength
117
+
118
+ ##
119
+ # snmp_notification_receiver_set takes a resource hash and configures a
120
+ # SNMP notification host on the target device. In practice this method
121
+ # usually creates a resource because nearly all of the properties can
122
+ # vary and are components of a resource identifier.
123
+ #
124
+ # @option opts [String] :name ('127.0.0.1') The hostname or ip address
125
+ # of the snmp notification receiver host.
126
+ #
127
+ # @option opts [String] :username ('public') The SNMP username, or
128
+ # community, to use for authentication.
129
+ #
130
+ # @option opts [Fixnum] :port (162) The UDP port of the receiver.
131
+ #
132
+ # @option opts [Symbol] :version (:v3) The version, :v1, :v2, or :v3
133
+ #
134
+ # @option opts [Symbol] :type (:traps) The notification type, :traps or
135
+ # :informs.
136
+ #
137
+ # @option opts [Symbol] :security (:auth) The security mode, :auth,
138
+ # :noauth, or :priv
139
+ #
140
+ # @api public
141
+ #
142
+ # @return [Boolean]
143
+ def snmp_notification_receiver_set(opts = {})
144
+ configure snmp_notification_receiver_cmd(opts)
145
+ end
146
+
147
+ ##
148
+ # snmp_notification_receiver_cmd builds a command given a resource
149
+ # hash.
150
+ #
151
+ # @return [String]
152
+ def snmp_notification_receiver_cmd(opts = {})
153
+ host = opts[:name].split(':').first
154
+ version = /\d+/.match(opts[:version]).to_s
155
+ version.sub!('2', '2c')
156
+ cmd = "snmp-server host #{host}"
157
+ cmd << " #{opts[:type] || :traps}"
158
+ cmd << " version #{version}"
159
+ cmd << " #{opts[:security] || :noauth}" if version == '3'
160
+ cmd << " #{opts[:username]}"
161
+ cmd << " udp-port #{opts[:port]}"
162
+ cmd
163
+ end
164
+ private :snmp_notification_receiver_cmd
165
+
166
+ ##
167
+ # snmp_notification_receiver_remove removes an snmp-server host from
168
+ # the target device.
169
+ #
170
+ # @option opts [String] :name ('127.0.0.1') The hostname or ip address
171
+ # of the snmp notification receiver host.
172
+ #
173
+ # @option opts [String] :username ('public') The SNMP username, or
174
+ # community, to use for authentication.
175
+ #
176
+ # @option opts [Fixnum] :port (162) The UDP port of the receiver.
177
+ #
178
+ # @option opts [Symbol] :version (:v3) The version, :v1, :v2, or :v3
179
+ #
180
+ # @option opts [Symbol] :type (:traps) The notification type, :traps or
181
+ # :informs.
182
+ #
183
+ # @option opts [Symbol] :security (:auth) The security mode, :auth,
184
+ # :noauth, or :priv
185
+ #
186
+ # @api public
187
+ #
188
+ # @return [Boolean]
189
+ def snmp_notification_receiver_remove(opts = {})
190
+ cmd = 'no ' << snmp_notification_receiver_cmd(opts)
191
+ configure cmd
192
+ end
193
+
194
+ ##
195
+ # snmp_users retrieves all of the SNMP users defined on the target
196
+ # device and returns an Array of Hash objects suitable for use as a
197
+ # resource hash to the provider's initializer method.
198
+ #
199
+ # @api public
200
+ #
201
+ # @return [Array<Hash<Symbol,Object>>] Array of resource hashes.
202
+ def snmp_users
203
+ cmd = 'show snmp user'
204
+ result = node.enable(cmd)
205
+ text = result.first[:result]['output']
206
+ users = parse_snmp_users(text)
207
+ users.each do |h|
208
+ cmd = "snmp-server user #{h[:name]} #{h[:roles]} #{h[:version]}"
209
+ password = snmp_user_password_hash(config, cmd)[:auth]
210
+ h[:password] = password if password
211
+ end
212
+ end
213
+
214
+ ##
215
+ # parse_snmp_users takes the text output from the `show snmp user` EAPI
216
+ # command and parses the text into structured data suitable for use as
217
+ # a resource has to the provider initializer method.
218
+ #
219
+ # ```
220
+ #
221
+ # User name : jeff
222
+ # Security model : v3
223
+ # Engine ID : f5717f00420008177800
224
+ # Authentication : SHA
225
+ # Privacy : AES-128
226
+ # Group : developers
227
+ #
228
+ # User name : nigel
229
+ # Security model : v2c
230
+ # Group : sysops (not configured)
231
+ #
232
+ # User name : nigel
233
+ # Security model : v3
234
+ # Engine ID : f5717f00420008177800
235
+ # Authentication : SHA
236
+ # Privacy : AES-128
237
+ # Group : sysops
238
+ # ```
239
+ #
240
+ # rubocop:disable Metrics/CyclomaticComplexity
241
+ # rubocop:disable Metrics/MethodLength
242
+ #
243
+ # @param [String] text The text to parse
244
+ #
245
+ # @api private
246
+ #
247
+ # @return [Array<Hash<Symbol,Object>>] Array of resource hashes.
248
+ def parse_snmp_users(text)
249
+ text.split("\n\n").map do |user_s|
250
+ user_s.scan(/^(\w+).*?: (.*)/).each_with_object({}) do |(h, v), m|
251
+ key = SNMP_USER_PARAM[h.downcase.intern] || h.downcase.intern
252
+ m[key] = case key
253
+ when :privacy then /AES/.match(v) ? :aes128 : :des
254
+ when :version then v.sub('v2c', 'v2').intern
255
+ when :auth then v.downcase.intern
256
+ when :roles then v.sub(/ \(.*?\)/, '')
257
+ else v.downcase
258
+ end
259
+ end
260
+ end
261
+ end
262
+ # rubocop:enable Metrics/MethodLength
263
+
264
+ # Map SNMP headings from `show snmp user` to snmp_user parameter names
265
+ SNMP_USER_PARAM = {
266
+ user: :name,
267
+ engine: :engine_id,
268
+ security: :version,
269
+ authentication: :auth,
270
+ privacy: :privacy,
271
+ group: :roles
272
+ }
273
+
274
+ ##
275
+ # snmp_user_set creates or updates an SNMP user account on the target
276
+ # device.
277
+ #
278
+ # rubocop:disable Metrics/MethodLength
279
+ #
280
+ # @option opts [String] :name ('johndoe') The username
281
+ #
282
+ # @option opts [Array] :roles (['developers']) The group, as an Array,
283
+ # this user is associated with.
284
+ #
285
+ # @option opts [Symbol] :version (:v2) The snmp version for this user
286
+ # account.
287
+ #
288
+ # @option opts [Symbol] :auth (:sha) The authentication digest method
289
+ #
290
+ # @option opts [Symbol] :privacy (:aes) The encryption scheme for
291
+ # privacy.
292
+ #
293
+ # @option opts [String] :password ('abc123') The password to
294
+ # configure for authentication and privacy.
295
+ #
296
+ # @api public
297
+ #
298
+ # @return [Hash<Symbol,Object>] Updated properties, e.g. the password
299
+ # hash which is idempotent.
300
+ def snmp_user_set(opts = {})
301
+ group = [*opts[:roles]].first
302
+ fail ArgumentError, 'at least one role is required' unless group
303
+ version = opts[:version].to_s.sub('v2', 'v2c')
304
+ cmd = user_cmd = "snmp-server user #{opts[:name]} #{group} #{version}"
305
+ if opts[:password] && version == 'v3'
306
+ privacy = opts[:privacy].to_s.scan(/aes|des/).first
307
+ fail ArgumentError,
308
+ 'privacy is required when managing passwords' unless privacy
309
+ cmd += " auth #{opts[:auth] || 'sha'} #{opts[:password]} "\
310
+ "priv #{privacy} #{opts[:password]}"
311
+ end
312
+ configure cmd
313
+ hash = snmp_user_password_hash(running_config, user_cmd)
314
+ { password: hash[:auth] }
315
+ end
316
+ # rubocop:enable Metrics/MethodLength
317
+
318
+ ##
319
+ # snmp_user_destroy removes an SNMP user from the target device
320
+ #
321
+ # @option opts [String] :name ('johndoe') The username
322
+ #
323
+ # @option opts [Array] :roles (['developers']) The group, as an Array,
324
+ # this user is associated with.
325
+ #
326
+ # @option opts [Symbol] :version (:v2) The snmp version for this user
327
+ # account.
328
+ #
329
+ # @option opts [Symbol] :auth (:sha) The authentication digest method
330
+ #
331
+ # @option opts [Symbol] :privacy (:aes) The encryption scheme for
332
+ # privacy.
333
+ #
334
+ # @option opts [String] :password ('abc123') The password to
335
+ # configure for authentication and privacy.
336
+ #
337
+ # @api public
338
+ #
339
+ # @return [Hash<Symbol,Object>] Updated properties, e.g. the password
340
+ # hash which is idempotent.
341
+ #
342
+ # @return [String]
343
+ def snmp_user_destroy(opts = {})
344
+ group = [*opts[:roles]].first
345
+ version = opts[:version].to_s.sub('v2', 'v2c')
346
+ cmd = "no snmp-server user #{opts[:name]} #{group} #{version}"
347
+ configure cmd
348
+ {}
349
+ end
350
+
351
+ ##
352
+ # snmp_user_password obtains the password hash from the device in order
353
+ # to provide an idempotent configuration value.
354
+ #
355
+ # @param [String] running_config The text of the current running
356
+ # configuration.
357
+ #
358
+ # @param [String] user_cmd The prefix of the command that identifies
359
+ # the user in the running-config. e.g. ('snmp-server user jeff
360
+ # developers v3')
361
+ #
362
+ # @return [Hash<Symbol,String>] The hashes for :auth and :privacy
363
+ def snmp_user_password_hash(running_config, user_cmd)
364
+ regexp = /#{user_cmd} .*?auth \w+\s+(.*?)\s+priv \w+\s+(.*?)\s/
365
+ (auth_hash, priv_hash) = running_config.scan(regexp).first
366
+ { auth: auth_hash, privacy: priv_hash }
367
+ end
368
+ end
369
+ end
370
+ end