rbeapi 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|