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.
- data/.gitignore +35 -0
- data/Gemfile +25 -0
- data/Guardfile +15 -0
- data/LICENSE +28 -0
- data/README.md +218 -0
- data/Rakefile +12 -0
- data/lib/rbeapi.rb +32 -0
- data/lib/rbeapi/api.rb +135 -0
- data/lib/rbeapi/api/aaa.rb +410 -0
- data/lib/rbeapi/api/dns.rb +198 -0
- data/lib/rbeapi/api/interfaces.rb +1193 -0
- data/lib/rbeapi/api/ipinterfaces.rb +328 -0
- data/lib/rbeapi/api/logging.rb +157 -0
- data/lib/rbeapi/api/mlag.rb +519 -0
- data/lib/rbeapi/api/ntp.rb +201 -0
- data/lib/rbeapi/api/ospf.rb +214 -0
- data/lib/rbeapi/api/prefixlists.rb +98 -0
- data/lib/rbeapi/api/radius.rb +317 -0
- data/lib/rbeapi/api/radius.rb.old +399 -0
- data/lib/rbeapi/api/routemaps.rb +100 -0
- data/lib/rbeapi/api/snmp.rb +427 -0
- data/lib/rbeapi/api/staticroutes.rb +88 -0
- data/lib/rbeapi/api/stp.rb +381 -0
- data/lib/rbeapi/api/switchports.rb +272 -0
- data/lib/rbeapi/api/system.rb +87 -0
- data/lib/rbeapi/api/tacacs.rb +236 -0
- data/lib/rbeapi/api/varp.rb +181 -0
- data/lib/rbeapi/api/vlans.rb +338 -0
- data/lib/rbeapi/client.rb +454 -0
- data/lib/rbeapi/eapilib.rb +334 -0
- data/lib/rbeapi/netdev/snmp.rb +370 -0
- data/lib/rbeapi/utils.rb +70 -0
- data/lib/rbeapi/version.rb +37 -0
- data/rbeapi.gemspec +32 -0
- data/spec/fixtures/dut.conf +5 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/support/fixtures.rb +114 -0
- data/spec/support/shared_examples_for_api_modules.rb +124 -0
- data/spec/system/api_ospf_interfaces_spec.rb +58 -0
- data/spec/system/api_ospf_spec.rb +111 -0
- data/spec/system/api_varp_interfaces_spec.rb +60 -0
- data/spec/system/api_varp_spec.rb +44 -0
- data/spec/system/rbeapi/api/dns_spec.rb +77 -0
- data/spec/system/rbeapi/api/interfaces_base_spec.rb +94 -0
- data/spec/system/rbeapi/api/interfaces_ethernet_spec.rb +135 -0
- data/spec/system/rbeapi/api/interfaces_portchannel_spec.rb +188 -0
- data/spec/system/rbeapi/api/interfaces_vxlan_spec.rb +115 -0
- data/spec/system/rbeapi/api/ipinterfaces_spec.rb +97 -0
- data/spec/system/rbeapi/api/logging_spec.rb +65 -0
- data/spec/system/rbeapi/api/mlag_interfaces_spec.rb +80 -0
- data/spec/system/rbeapi/api/mlag_spec.rb +94 -0
- data/spec/system/rbeapi/api/ntp_spec.rb +76 -0
- data/spec/system/rbeapi/api/snmp_spec.rb +68 -0
- data/spec/system/rbeapi/api/stp_instances_spec.rb +61 -0
- data/spec/system/rbeapi/api/stp_interfaces_spec.rb +71 -0
- data/spec/system/rbeapi/api/stp_spec.rb +57 -0
- data/spec/system/rbeapi/api/switchports_spec.rb +135 -0
- data/spec/system/rbeapi/api/system_spec.rb +38 -0
- data/spec/system/rbeapi/api/vlans_spec.rb +121 -0
- 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
|