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,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