puppet_x_eos_eapi 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/Gemfile +24 -0
  4. data/LICENSE.txt +202 -0
  5. data/README.md +87 -0
  6. data/Rakefile +1 -0
  7. data/lib/puppet_x/eos/autoload.rb +57 -0
  8. data/lib/puppet_x/eos/eapi.rb +259 -0
  9. data/lib/puppet_x/eos/module_base.rb +37 -0
  10. data/lib/puppet_x/eos/modules/daemon.rb +109 -0
  11. data/lib/puppet_x/eos/modules/extension.rb +167 -0
  12. data/lib/puppet_x/eos/modules/interface.rb +180 -0
  13. data/lib/puppet_x/eos/modules/ipinterface.rb +133 -0
  14. data/lib/puppet_x/eos/modules/mlag.rb +268 -0
  15. data/lib/puppet_x/eos/modules/ntp.rb +129 -0
  16. data/lib/puppet_x/eos/modules/ospf.rb +129 -0
  17. data/lib/puppet_x/eos/modules/portchannel.rb +277 -0
  18. data/lib/puppet_x/eos/modules/radius.rb +367 -0
  19. data/lib/puppet_x/eos/modules/snmp.rb +177 -0
  20. data/lib/puppet_x/eos/modules/switchport.rb +255 -0
  21. data/lib/puppet_x/eos/modules/system.rb +138 -0
  22. data/lib/puppet_x/eos/modules/tacacs.rb +302 -0
  23. data/lib/puppet_x/eos/modules/vlan.rb +179 -0
  24. data/lib/puppet_x/eos/modules/vxlan.rb +132 -0
  25. data/lib/puppet_x/eos/provider.rb +71 -0
  26. data/lib/puppet_x/eos/version.rb +41 -0
  27. data/lib/puppet_x/net_dev/eos_api.rb +1011 -0
  28. data/lib/puppet_x/net_dev/eos_api/common_methods.rb +27 -0
  29. data/lib/puppet_x/net_dev/eos_api/snmp_methods.rb +647 -0
  30. data/lib/puppet_x/net_dev/eos_api/version.rb +8 -0
  31. data/lib/puppet_x_eos_eapi.rb +4 -0
  32. data/puppet_x_eos_eapi.gemspec +31 -0
  33. data/spec/fixtures/fixture_all_portchannel_modes.json +8 -0
  34. data/spec/fixtures/fixture_all_portchannels_detailed.json +15 -0
  35. data/spec/fixtures/fixture_create_vlan_error.json +17 -0
  36. data/spec/fixtures/fixture_create_vlan_success.json +12 -0
  37. data/spec/fixtures/fixture_eapi_conf.yaml +4 -0
  38. data/spec/fixtures/fixture_enable_configure_vlan_3111_name_foo.json +14 -0
  39. data/spec/fixtures/fixture_enable_configure_vlan_foo_name_bar.json +19 -0
  40. data/spec/fixtures/fixture_get_snmp_communities_non_existent_acl.yaml +2 -0
  41. data/spec/fixtures/fixture_get_snmp_location_westeros.json +5 -0
  42. data/spec/fixtures/fixture_portchannel_min_links_1.json +8 -0
  43. data/spec/fixtures/fixture_portchannel_min_links_2.json +8 -0
  44. data/spec/fixtures/fixture_running_config.yaml +1 -0
  45. data/spec/fixtures/fixture_running_configuration_radius_configured.yaml +30 -0
  46. data/spec/fixtures/fixture_running_configuration_radius_default.yaml +29 -0
  47. data/spec/fixtures/fixture_running_configuration_radius_server_groups.yaml +38 -0
  48. data/spec/fixtures/fixture_running_configuration_radius_servers.yaml +34 -0
  49. data/spec/fixtures/fixture_running_configuration_tacacs_configured.yaml +38 -0
  50. data/spec/fixtures/fixture_running_configuration_tacacs_default.yaml +38 -0
  51. data/spec/fixtures/fixture_running_configuration_tacacs_groups.yaml +1 -0
  52. data/spec/fixtures/fixture_running_configuration_tacacs_groups_3.yaml +43 -0
  53. data/spec/fixtures/fixture_running_configuration_tacacs_servers.yaml +41 -0
  54. data/spec/fixtures/fixture_s4_show_etherchannel_detailed.json +9 -0
  55. data/spec/fixtures/fixture_show_flowcontrol_et1.json +5 -0
  56. data/spec/fixtures/fixture_show_interfaces.json +297 -0
  57. data/spec/fixtures/fixture_show_interfaces_switchport_format_text.json +9 -0
  58. data/spec/fixtures/fixture_show_port_channel_summary_2_lags.json +9 -0
  59. data/spec/fixtures/fixture_show_port_channel_summary_static.json +9 -0
  60. data/spec/fixtures/fixture_show_snmp_community.yaml +2 -0
  61. data/spec/fixtures/fixture_show_snmp_contact_empty.json +5 -0
  62. data/spec/fixtures/fixture_show_snmp_contact_name.json +5 -0
  63. data/spec/fixtures/fixture_show_snmp_disabled.json +5 -0
  64. data/spec/fixtures/fixture_show_snmp_enabled.json +5 -0
  65. data/spec/fixtures/fixture_show_snmp_host.yaml +2 -0
  66. data/spec/fixtures/fixture_show_snmp_host_duplicates.yaml +2 -0
  67. data/spec/fixtures/fixture_show_snmp_host_more_duplicates.yaml +2 -0
  68. data/spec/fixtures/fixture_show_snmp_location_empty.json +5 -0
  69. data/spec/fixtures/fixture_show_snmp_trap.yaml +2 -0
  70. data/spec/fixtures/fixture_show_snmp_user.yaml +2 -0
  71. data/spec/fixtures/fixture_show_snmp_user_raw_text.yaml +1 -0
  72. data/spec/fixtures/fixture_show_vlan.json +37 -0
  73. data/spec/fixtures/fixture_show_vlan_3110.json +18 -0
  74. data/spec/fixtures/fixture_show_vlan_4000.json +18 -0
  75. data/spec/fixtures/fixture_snmp_host_opts.yaml +11 -0
  76. data/spec/spec_helper.rb +21 -0
  77. data/spec/support/fixtures.rb +104 -0
  78. data/spec/unit/puppet_x/eos/eapi_spec.rb +182 -0
  79. data/spec/unit/puppet_x/eos/module_base_spec.rb +26 -0
  80. data/spec/unit/puppet_x/eos/modules/daemon_spec.rb +110 -0
  81. data/spec/unit/puppet_x/eos/modules/extension_spec.rb +197 -0
  82. data/spec/unit/puppet_x/eos/modules/fixtures/daemon_getall.json +3 -0
  83. data/spec/unit/puppet_x/eos/modules/fixtures/extension_getall.json +28 -0
  84. data/spec/unit/puppet_x/eos/modules/fixtures/hostname.json +6 -0
  85. data/spec/unit/puppet_x/eos/modules/fixtures/interface_getall.json +509 -0
  86. data/spec/unit/puppet_x/eos/modules/fixtures/ipinterface_getall.json +56 -0
  87. data/spec/unit/puppet_x/eos/modules/fixtures/mlag_get.json +21 -0
  88. data/spec/unit/puppet_x/eos/modules/fixtures/mlag_get_interfaces.json +18 -0
  89. data/spec/unit/puppet_x/eos/modules/fixtures/ntp_get.json +5 -0
  90. data/spec/unit/puppet_x/eos/modules/fixtures/ospf_instance_getall.json +58 -0
  91. data/spec/unit/puppet_x/eos/modules/fixtures/portchannel_get.json +54 -0
  92. data/spec/unit/puppet_x/eos/modules/fixtures/portchannel_getlacpmode.json +5 -0
  93. data/spec/unit/puppet_x/eos/modules/fixtures/portchannel_getmembers.json +5 -0
  94. data/spec/unit/puppet_x/eos/modules/fixtures/portchannel_po1.json +7 -0
  95. data/spec/unit/puppet_x/eos/modules/fixtures/snmp_get.json +14 -0
  96. data/spec/unit/puppet_x/eos/modules/fixtures/switchport_get.json +5 -0
  97. data/spec/unit/puppet_x/eos/modules/fixtures/switchport_get_et1.json +7 -0
  98. data/spec/unit/puppet_x/eos/modules/fixtures/switchport_getall_interfaces.json +230 -0
  99. data/spec/unit/puppet_x/eos/modules/fixtures/system_domain_list.json +5 -0
  100. data/spec/unit/puppet_x/eos/modules/fixtures/system_domain_name.json +5 -0
  101. data/spec/unit/puppet_x/eos/modules/fixtures/system_hostname.json +6 -0
  102. data/spec/unit/puppet_x/eos/modules/fixtures/system_name_servers.json +5 -0
  103. data/spec/unit/puppet_x/eos/modules/fixtures/vlan_getall.json +123 -0
  104. data/spec/unit/puppet_x/eos/modules/fixtures/vxlan_get.json +24 -0
  105. data/spec/unit/puppet_x/eos/modules/interface_spec.rb +281 -0
  106. data/spec/unit/puppet_x/eos/modules/ipinterface_spec.rb +143 -0
  107. data/spec/unit/puppet_x/eos/modules/mlag_spec.rb +349 -0
  108. data/spec/unit/puppet_x/eos/modules/ntp_spec.rb +136 -0
  109. data/spec/unit/puppet_x/eos/modules/ospf_spec.rb +143 -0
  110. data/spec/unit/puppet_x/eos/modules/portchannel_spec.rb +357 -0
  111. data/spec/unit/puppet_x/eos/modules/radius_spec.rb +509 -0
  112. data/spec/unit/puppet_x/eos/modules/snmp_spec.rb +202 -0
  113. data/spec/unit/puppet_x/eos/modules/switchport_get_et1.json +7 -0
  114. data/spec/unit/puppet_x/eos/modules/switchport_spec.rb +307 -0
  115. data/spec/unit/puppet_x/eos/modules/system_spec.rb +170 -0
  116. data/spec/unit/puppet_x/eos/modules/tacacs_spec.rb +448 -0
  117. data/spec/unit/puppet_x/eos/modules/vlan_spec.rb +244 -0
  118. data/spec/unit/puppet_x/eos/modules/vxlan_spec.rb +189 -0
  119. data/spec/unit/puppet_x/eos/provider_spec.rb +35 -0
  120. data/spec/unit/puppet_x/net_dev/eos_api/common_methods_spec.rb +34 -0
  121. data/spec/unit/puppet_x/net_dev/eos_api/snmp_methods_spec.rb +842 -0
  122. data/spec/unit/puppet_x/net_dev/eos_api_spec.rb +1000 -0
  123. metadata +369 -0
@@ -0,0 +1,27 @@
1
+ # encoding: utf-8
2
+
3
+ module PuppetX
4
+ module NetDev
5
+ ##
6
+ class EosApi
7
+ ##
8
+ # CommonMethods implements common methods, such as returning the running
9
+ # config. This separation makes it easier to provide documentation and
10
+ # introspect where methods come from given an api instance.
11
+ module CommonMethods
12
+ ##
13
+ #
14
+ # @api private
15
+ #
16
+ # @return [String] the text of the running configuration
17
+ def running_config
18
+ prefix = %w(enable)
19
+ cmd = 'show running-config'
20
+ msg = 'show running configuration'
21
+ result = eapi_action([*prefix, cmd], msg, format: 'text')
22
+ result.last['output']
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,647 @@
1
+ # encoding: utf-8
2
+
3
+ module PuppetX
4
+ module NetDev
5
+ ##
6
+ class EosApi
7
+ ##
8
+ # SnmpMethods encapsulate the SNMP specific EOS API methods. This
9
+ # separation makes it easier to provide documentation and introspect
10
+ # where methods come from given an api instance.
11
+ module SnmpMethods
12
+ ##
13
+ # snmp_attributes retrieves the current state of the SNMP service on
14
+ # the device and returns data suitable for a provider instance.
15
+ #
16
+ # @return [Hash<Symbol,String>]
17
+ def snmp_attributes
18
+ rval = { name: 'settings', ensure: :present }
19
+ rval.merge!(snmp_location)
20
+ rval.merge!(snmp_enable)
21
+ rval.merge!(snmp_contact)
22
+ end
23
+
24
+ ##
25
+ # snmp_location obtains the configured SNMP location string from
26
+ # the device.
27
+ #
28
+ # @api private
29
+ #
30
+ # @return [Hash<Symbol,String>]
31
+ def snmp_location
32
+ cmd = 'show snmp location'
33
+ result = eapi_action(cmd, 'get snmp location')
34
+ location = result.first['location']
35
+ { location: location }
36
+ end
37
+
38
+ ##
39
+ # snmp_enable returns :true if SNMP is enabled on the device or :false
40
+ # otherwise as a Hash suitable for merge into `snmp_attributes`.
41
+ #
42
+ # @api private
43
+ #
44
+ # @return [Hash<Symbol,Symbol>] e.g. `{ enable: :true }`
45
+ def snmp_enable
46
+ cmd = 'show snmp'
47
+ result = eapi_action(cmd, 'get snmp status', format: 'text')
48
+ text = result.first['output']
49
+ enable = parse_snmp_enable(text)
50
+ { enable: enable }
51
+ end
52
+
53
+ ##
54
+ # parse_snmp_enable parses the text output of the `show snmp` command
55
+ # an returns :true or :false for the enabled state.
56
+ #
57
+ # @param [String] text The text of the snmp output, e.g. for a disabled
58
+ # SNMP service:
59
+ #
60
+ # SNMP agent enabled in VRFs: default
61
+ # SNMP agent disabled: no communities or users configured
62
+ #
63
+ # @api private
64
+ #
65
+ # @return [Symbol] :true or :false
66
+ def parse_snmp_enable(text)
67
+ disabled_regexp = /SNMP agent disabled:/m
68
+ enabled_regexp = /SNMP packets input/m
69
+
70
+ disabled_mdata = disabled_regexp.match(text)
71
+ return :false if disabled_mdata
72
+
73
+ enabled_mdata = enabled_regexp.match(text)
74
+ return :true if enabled_mdata
75
+
76
+ fail ArgumentError, 'could not parse text for SNMP enabled state'
77
+ end
78
+
79
+ ##
80
+ # snmp_contact returns the snmp contact string configured on the device.
81
+ #
82
+ # @api private
83
+ #
84
+ # @return [Hash<Symbol,Symbol>] e.g. `{ contact: 'Jane Doe' }`
85
+ def snmp_contact
86
+ cmd = 'show snmp contact'
87
+ result = eapi_action(cmd, 'get snmp contact')
88
+ contact = result.first['contact']
89
+ { contact: contact }
90
+ end
91
+
92
+ ##
93
+ # snmp_enable= disables or enables SNMP
94
+ #
95
+ # @param [Boolean] state enable SNMP if true, disable if false.
96
+ #
97
+ # @api public
98
+ def snmp_enable=(state)
99
+ cmd = %w(enable configure)
100
+ case state
101
+ when true
102
+ cmd << 'snmp-server community public ro'
103
+ when false
104
+ cmd << 'no snmp-server'
105
+ else
106
+ fail ArgumentError, "invalid state #{state.inspect}"
107
+ end
108
+
109
+ eapi_action(cmd, 'configure snmp') && true || false
110
+ end
111
+
112
+ ##
113
+ # snmp_contact= updates the SNMP contact on the target device.
114
+ #
115
+ # @param [String] contact The contact name, e.g. 'Jane Doe'
116
+ #
117
+ # @api public
118
+ #
119
+ # @return [Boolean] true or false
120
+ def snmp_contact=(contact)
121
+ cmd = %w(enable configure)
122
+ cmd << "snmp-server contact #{contact}"
123
+ eapi_action(cmd, 'set snmp contact') && true || false
124
+ end
125
+
126
+ ##
127
+ # snmp_location= updates the SNMP location on the target device.
128
+ #
129
+ # @param [String] location The location, e.g. 'Planet Earth'
130
+ #
131
+ # @api public
132
+ #
133
+ # @return [Boolean] true or false
134
+ def snmp_location=(location)
135
+ cmd = %w(enable configure)
136
+ cmd << "snmp-server location #{location}"
137
+ eapi_action(cmd, 'set snmp location') && true || false
138
+ end
139
+
140
+ ##
141
+ # snmp_communities retrieves all of the SNMP community strings defined
142
+ # on the target device and returns an Array of Hash objects suitable
143
+ # for use as a resource hash to the provider's initializer method.
144
+ #
145
+ # @param [String] buf Describe the string parameter here
146
+ #
147
+ # @api public
148
+ #
149
+ # @return [Array<Hash<Symbol,Object>>] Array of resource hashes.
150
+ def snmp_communities
151
+ cmd = 'show snmp community'
152
+ result = eapi_action(cmd, 'get snmp communities', format: 'text')
153
+ text = result.first['output']
154
+ parse_snmp_communities(text)
155
+ end
156
+
157
+ ##
158
+ # parse_snmp_communities takes the text output from the `show snmp
159
+ # community` EAPI command and parses the text into structured data
160
+ # suitable for use as a resource hash to the provider initializer
161
+ # method. An example of the output looks like:
162
+ #
163
+ # ```
164
+ # Community name: jeff
165
+ # Community access: read-write
166
+ # Access list: stest1
167
+ #
168
+ # Community name: jeff2
169
+ # Community access: read-write
170
+ # Access list: stest2 (non-existent)
171
+ #
172
+ # Community name: private
173
+ # Community access: read-write
174
+ #
175
+ # Community name: public
176
+ # Community access: read-only
177
+ # ```
178
+ #
179
+ # @param [String] text The text to parse
180
+ #
181
+ # @api private
182
+ #
183
+ # @return [Array<Hash<Symbol,Object>>] Array of resource hashes.
184
+ def parse_snmp_communities(text)
185
+ blocks = text.split("\n\n")
186
+ # (?:\s*\(.*?\)|\n|$) deals with trailing data after the value. e.g.
187
+ # an ACL might come back as `Access list: stest2 (non-existent)`
188
+ regexp = / (\w+): (\w.*?)(?:\s*\(.*?\)|\n|$)/
189
+ communities = blocks.map { |l| l.scan(regexp) }
190
+ communities.map do |pairs|
191
+ pairs.each_with_object({}) do |(key, val), resource_hash|
192
+ resource_hash.merge!(map_snmp_keys(key, val))
193
+ end
194
+ end
195
+ end
196
+
197
+ ##
198
+ # map_snmp_keys maps the keys and values parsed from the show snmp
199
+ # community raw text output into resource attributes and values.
200
+ #
201
+ # @api private
202
+ def map_snmp_keys(key, val)
203
+ case key
204
+ when 'name' then { name: val }
205
+ when 'list' then { acl: val }
206
+ when 'access'
207
+ group = case val
208
+ when 'read-write'; then 'rw'
209
+ when 'read-only'; then 'ro'
210
+ end
211
+ { group: group }
212
+ end
213
+ end
214
+ private :map_snmp_keys
215
+
216
+ ##
217
+ # snmp_community_set creates or updates an snmp community on the target
218
+ # device given a hash of attributes from the resource model.
219
+ #
220
+ # @option opts [String] :name ('public') The community name
221
+ #
222
+ # @option opts [Symbol] :group (:ro) :ro or :rw for read-only or
223
+ # read-write access control for the community name.
224
+ #
225
+ # @option opts [String] :acl ('stest1') The standard ACL name defined on
226
+ # the switch. This ACL is defined using the `ip access-list standard
227
+ # stest1` command.
228
+ #
229
+ # @api public
230
+ #
231
+ # @return [Boolean] true if the resource was successfully created
232
+ def snmp_community_set(opts)
233
+ prefix = %w(enable configure)
234
+ cmd = "snmp-server community #{opts[:name]}"
235
+ cmd << " #{opts[:group]}" if opts[:group]
236
+ cmd << " #{opts[:acl]}" if opts[:acl]
237
+ eapi_action([*prefix, cmd], 'define snmp community') && true || false
238
+ end
239
+
240
+ ##
241
+ # snmp_community_destroy deletes an SNMP community from the target
242
+ # device. given a hash of attributes from the resource model.
243
+ #
244
+ # @option opts [String] :name ('public') The community name
245
+ #
246
+ # @api public
247
+ #
248
+ # @return [Boolean] true if the resource was successfully created
249
+ def snmp_community_destroy(opts)
250
+ prefix = %w(enable configure)
251
+ cmd = "no snmp-server community #{opts[:name]}"
252
+ result = eapi_action([*prefix, cmd], 'destroy snmp community')
253
+ result && true || false
254
+ end
255
+
256
+ ##
257
+ # snmp_notifications returns an Array of resource hashes suitable for
258
+ # initializing new provider resources.
259
+ #
260
+ # @api public
261
+ #
262
+ # @return [Array<Hash<Symbol,Object>>] Array of resource hashes.
263
+ def snmp_notifications
264
+ cmd = 'show snmp trap'
265
+ result = eapi_action(cmd, 'get snmp traps', format: 'text')
266
+ text = result.first['output']
267
+ parse_snmp_traps(text)
268
+ end
269
+
270
+ ##
271
+ # parse_snmp_traps takes the raw text output of the `show snmp trap`
272
+ # command and parses the data into hases suitable for new provider
273
+ # instances.
274
+ #
275
+ # @param [String] text The raw text to process.
276
+ #
277
+ # @api private
278
+ #
279
+ # @return [Array<Hash<Symbol,Object>>] Array of resource hashes.
280
+ def parse_snmp_traps(text)
281
+ regexp = /(\w+)\s+([-_\w]+)\s+(\w+).*$/
282
+ triples = text.scan(regexp)
283
+ triples.shift # Header
284
+ triples.map do |triple|
285
+ {
286
+ name: format('%s %s', *triple),
287
+ enable: /yes/xi.match(triple[2]) ? :true : :false
288
+ }
289
+ end
290
+ end
291
+
292
+ ##
293
+ # snmp_notification_set configures a SNMP trap notification on the
294
+ # target device.
295
+ #
296
+ # @option opts [String] :name ('snmp link-down') The trap name with the
297
+ # type name as a prefix separated by a space. The special name 'all'
298
+ # will enable or disable all notifications.
299
+ #
300
+ # @option opts [Symbol] :enable (:true) :true to enable the
301
+ # notification, :false to disable the notification.
302
+ #
303
+ # @api public
304
+ #
305
+ # @return [Boolean] true if successful
306
+ def snmp_notification_set(opts)
307
+ prefix = %w(enable configure)
308
+ pre = opts[:enable] == :true ? '' : 'no '
309
+ suffix = opts[:name] == 'all' ? '' : " #{opts[:name]}"
310
+ cmd = pre << 'snmp-server enable traps' << suffix
311
+ result = eapi_action([*prefix, cmd], 'set snmp trap')
312
+ result && true || false
313
+ end
314
+
315
+ ##
316
+ # snmp_notification_receivers obtains a list of all the snmp
317
+ # notification receivers and returns them as an Array of resource
318
+ # hashes suitable for the provider's new class method. This command
319
+ # maps the `show snmp host` command to an array of resource hashes.
320
+ #
321
+ # @api public
322
+ #
323
+ # @return [Array<Hash<Symbol,Object>>] Array of resource hashes.
324
+ def snmp_notification_receivers
325
+ cmd = 'show snmp host'
326
+ msg = 'get snmp notification hosts'
327
+ result = eapi_action(cmd, msg, format: 'text')
328
+ text = result.first['output']
329
+ parse_snmp_hosts(text)
330
+ end
331
+
332
+ ##
333
+ # parse_snmp_hosts parses the raw text from the `show snmp host`
334
+ # command and returns an Array of resource hashes.
335
+ #
336
+ # rubocop:disable Metrics/MethodLength
337
+ #
338
+ # @param [String] text The text of the `show snmp host` output, e.g.
339
+ # for three hosts:
340
+ #
341
+ # ```
342
+ # Notification host: 127.0.0.1 udp-port: 162 type: trap
343
+ # user: public security model: v3 noauth
344
+ #
345
+ # Notification host: 127.0.0.1 udp-port: 162 type: trap
346
+ # user: smtpuser security model: v3 auth
347
+ #
348
+ # Notification host: 127.0.0.2 udp-port: 162 type: trap
349
+ # user: private security model: v2c
350
+ #
351
+ # Notification host: 127.0.0.3 udp-port: 162 type: trap
352
+ # user: public security model: v1
353
+ #
354
+ # Notification host: 127.0.0.4 udp-port: 10162 type: inform
355
+ # user: private security model: v2c
356
+ #
357
+ # Notification host: 127.0.0.4 udp-port: 162 type: trap
358
+ # user: priv@te security model: v1
359
+ #
360
+ # Notification host: 127.0.0.4 udp-port: 162 type: trap
361
+ # user: public security model: v1
362
+ #
363
+ # Notification host: 127.0.0.4 udp-port: 20162 type: trap
364
+ # user: private security model: v1
365
+ #
366
+ # ```
367
+ #
368
+ # @api private
369
+ #
370
+ # @return [Array<Hash<Symbol,Object>>] Array of resource hashes.
371
+ def parse_snmp_hosts(text)
372
+ re = /host: ([^\s]+)\s+.*?port: (\d+)\s+type: (\w+)\s*user: (.*?)\s+security model: (.*?)\n/m # rubocop:disable Metrics/LineLength
373
+ text.scan(re).map do |(host, port, type, username, auth)|
374
+ resource_hash = { name: host, ensure: :present, port: port.to_i }
375
+ sec_match = /^v3 (\w+)/.match(auth)
376
+ resource_hash[:security] = sec_match[1] if sec_match
377
+ ver_match = /^(v\d)/.match(auth) # first 2 characters
378
+ resource_hash[:version] = ver_match[1] if ver_match
379
+ resource_hash[:type] = /trap/.match(type) ? :traps : :informs
380
+ resource_hash[:username] = username
381
+ resource_hash
382
+ end
383
+ end
384
+ # rubocop:enable Metrics/MethodLength
385
+
386
+ ##
387
+ # snmp_notification_receiver_set takes a resource hash and configures a
388
+ # SNMP notification host on the target device. In practice this method
389
+ # usually creates a resource because nearly all of the properties can
390
+ # vary and are components of a resource identifier.
391
+ #
392
+ # @option opts [String] :name ('127.0.0.1') The hostname or ip address
393
+ # of the snmp notification receiver host.
394
+ #
395
+ # @option opts [String] :username ('public') The SNMP username, or
396
+ # community, to use for authentication.
397
+ #
398
+ # @option opts [Fixnum] :port (162) The UDP port of the receiver.
399
+ #
400
+ # @option opts [Symbol] :version (:v3) The version, :v1, :v2, or :v3
401
+ #
402
+ # @option opts [Symbol] :type (:traps) The notification type, :traps or
403
+ # :informs.
404
+ #
405
+ # @option opts [Symbol] :security (:auth) The security mode, :auth,
406
+ # :noauth, or :priv
407
+ #
408
+ # @api public
409
+ #
410
+ # @return [Boolean]
411
+ def snmp_notification_receiver_set(opts = {})
412
+ prefix = %w(enable configure)
413
+ cmd = snmp_notification_receiver_cmd(opts)
414
+ result = eapi_action([*prefix, cmd], 'set snmp host')
415
+ result ? true : false
416
+ end
417
+
418
+ ##
419
+ # snmp_notification_receiver_cmd builds a command given a resource
420
+ # hash.
421
+ #
422
+ # @return [String]
423
+ def snmp_notification_receiver_cmd(opts = {})
424
+ host = opts[:name].split(':').first
425
+ version = /\d+/.match(opts[:version]).to_s
426
+ version.sub!('2', '2c')
427
+ cmd = "snmp-server host #{host}"
428
+ cmd << " #{opts[:type] || :traps}"
429
+ cmd << " version #{version}"
430
+ cmd << " #{opts[:security] || :noauth}" if version == '3'
431
+ cmd << " #{opts[:username]}"
432
+ cmd << " udp-port #{opts[:port]}"
433
+ cmd
434
+ end
435
+ private :snmp_notification_receiver_cmd
436
+
437
+ ##
438
+ # snmp_notification_receiver_remove removes an snmp-server host from
439
+ # the target device.
440
+ #
441
+ # @option opts [String] :name ('127.0.0.1') The hostname or ip address
442
+ # of the snmp notification receiver host.
443
+ #
444
+ # @option opts [String] :username ('public') The SNMP username, or
445
+ # community, to use for authentication.
446
+ #
447
+ # @option opts [Fixnum] :port (162) The UDP port of the receiver.
448
+ #
449
+ # @option opts [Symbol] :version (:v3) The version, :v1, :v2, or :v3
450
+ #
451
+ # @option opts [Symbol] :type (:traps) The notification type, :traps or
452
+ # :informs.
453
+ #
454
+ # @option opts [Symbol] :security (:auth) The security mode, :auth,
455
+ # :noauth, or :priv
456
+ #
457
+ # @api public
458
+ #
459
+ # @return [Boolean]
460
+ def snmp_notification_receiver_remove(opts = {})
461
+ prefix = %w(enable configure)
462
+ cmd = 'no ' << snmp_notification_receiver_cmd(opts)
463
+ result = eapi_action([*prefix, cmd], 'remove snmp host')
464
+ result ? true : false
465
+ end
466
+
467
+ ##
468
+ # snmp_users retrieves all of the SNMP users defined on the target
469
+ # device and returns an Array of Hash objects suitable for use as a
470
+ # resource hash to the provider's initializer method.
471
+ #
472
+ # @api public
473
+ #
474
+ # @return [Array<Hash<Symbol,Object>>] Array of resource hashes.
475
+ def snmp_users
476
+ cmd = 'show snmp user'
477
+ result = eapi_action(cmd, 'get snmp users', format: 'text')
478
+ text = result.first['output']
479
+ users = parse_snmp_users(text)
480
+ text = running_config
481
+ users.each do |h|
482
+ cmd = "snmp-server user #{h[:name]} #{h[:roles]} #{h[:version]}"
483
+ password = snmp_user_password_hash(text, cmd)[:auth]
484
+ h[:password] = password if password
485
+ end
486
+ end
487
+
488
+ ##
489
+ # parse_snmp_users takes the text output from the `show snmp user` EAPI
490
+ # command and parses the text into structured data suitable for use as
491
+ # a resource has to the provider initializer method.
492
+ #
493
+ # ```
494
+ #
495
+ # User name : jeff
496
+ # Security model : v3
497
+ # Engine ID : f5717f00420008177800
498
+ # Authentication : SHA
499
+ # Privacy : AES-128
500
+ # Group : developers
501
+ #
502
+ # User name : nigel
503
+ # Security model : v2c
504
+ # Group : sysops (not configured)
505
+ #
506
+ # User name : nigel
507
+ # Security model : v3
508
+ # Engine ID : f5717f00420008177800
509
+ # Authentication : SHA
510
+ # Privacy : AES-128
511
+ # Group : sysops
512
+ # ```
513
+ #
514
+ # rubocop:disable Metrics/CyclomaticComplexity
515
+ # rubocop:disable Metrics/MethodLength
516
+ #
517
+ # @param [String] text The text to parse
518
+ #
519
+ # @api private
520
+ #
521
+ # @return [Array<Hash<Symbol,Object>>] Array of resource hashes.
522
+ def parse_snmp_users(text)
523
+ text.split("\n\n").map do |user_s|
524
+ user_s.scan(/^(\w+).*?: (.*)/).each_with_object({}) do |(h, v), m|
525
+ key = SNMP_USER_PARAM[h.downcase.intern] || h.downcase.intern
526
+ m[key] = case key
527
+ when :privacy then /AES/.match(v) ? :aes128 : :des
528
+ when :version then v.sub('v2c', 'v2').intern
529
+ when :auth then v.downcase.intern
530
+ when :roles then v.sub(/ \(.*?\)/, '')
531
+ else v.downcase
532
+ end
533
+ end
534
+ end
535
+ end
536
+ # rubocop:enable Metrics/MethodLength
537
+
538
+ # Map SNMP headings from `show snmp user` to snmp_user parameter names
539
+ SNMP_USER_PARAM = {
540
+ user: :name,
541
+ engine: :engine_id,
542
+ security: :version,
543
+ authentication: :auth,
544
+ privacy: :privacy,
545
+ group: :roles
546
+ }
547
+
548
+ ##
549
+ # snmp_user_set creates or updates an SNMP user account on the target
550
+ # device.
551
+ #
552
+ # rubocop:disable Metrics/MethodLength
553
+ #
554
+ # @option opts [String] :name ('johndoe') The username
555
+ #
556
+ # @option opts [Array] :roles (['developers']) The group, as an Array,
557
+ # this user is associated with.
558
+ #
559
+ # @option opts [Symbol] :version (:v2) The snmp version for this user
560
+ # account.
561
+ #
562
+ # @option opts [Symbol] :auth (:sha) The authentication digest method
563
+ #
564
+ # @option opts [Symbol] :privacy (:aes) The encryption scheme for
565
+ # privacy.
566
+ #
567
+ # @option opts [String] :password ('abc123') The password to
568
+ # configure for authentication and privacy.
569
+ #
570
+ # @api public
571
+ #
572
+ # @return [Hash<Symbol,Object>] Updated properties, e.g. the password
573
+ # hash which is idempotent.
574
+ def snmp_user_set(opts = {})
575
+ prefix = %w(enable configure)
576
+ group = [*opts[:roles]].first
577
+ fail ArgumentError, 'at least one role is required' unless group
578
+ version = opts[:version].to_s.sub('v2', 'v2c')
579
+ cmd = user_cmd = "snmp-server user #{opts[:name]} #{group} #{version}"
580
+ if opts[:password] && version == 'v3'
581
+ privacy = opts[:privacy].to_s.scan(/aes|des/).first
582
+ fail ArgumentError,
583
+ 'privacy is required when managing passwords' unless privacy
584
+ cmd += " auth #{opts[:auth] || 'sha'} #{opts[:password]} "\
585
+ "priv #{privacy} #{opts[:password]}"
586
+ end
587
+ eapi_action([*prefix, cmd], 'configure snmp user')
588
+ hash = snmp_user_password_hash(running_config, user_cmd)
589
+ { password: hash[:auth] }
590
+ end
591
+ # rubocop:enable Metrics/MethodLength
592
+
593
+ ##
594
+ # snmp_user_destroy removes an SNMP user from the target device
595
+ #
596
+ # @option opts [String] :name ('johndoe') The username
597
+ #
598
+ # @option opts [Array] :roles (['developers']) The group, as an Array,
599
+ # this user is associated with.
600
+ #
601
+ # @option opts [Symbol] :version (:v2) The snmp version for this user
602
+ # account.
603
+ #
604
+ # @option opts [Symbol] :auth (:sha) The authentication digest method
605
+ #
606
+ # @option opts [Symbol] :privacy (:aes) The encryption scheme for
607
+ # privacy.
608
+ #
609
+ # @option opts [String] :password ('abc123') The password to
610
+ # configure for authentication and privacy.
611
+ #
612
+ # @api public
613
+ #
614
+ # @return [Hash<Symbol,Object>] Updated properties, e.g. the password
615
+ # hash which is idempotent.
616
+ #
617
+ # @return [String]
618
+ def snmp_user_destroy(opts = {})
619
+ prefix = %w(enable configure)
620
+ group = [*opts[:roles]].first
621
+ version = opts[:version].to_s.sub('v2', 'v2c')
622
+ cmd = "no snmp-server user #{opts[:name]} #{group} #{version}"
623
+ eapi_action([*prefix, cmd], 'remove snmp user')
624
+ {}
625
+ end
626
+
627
+ ##
628
+ # snmp_user_password obtains the password hash from the device in order
629
+ # to provide an idempotent configuration value.
630
+ #
631
+ # @param [String] running_config The text of the current running
632
+ # configuration.
633
+ #
634
+ # @param [String] user_cmd The prefix of the command that identifies
635
+ # the user in the running-config. e.g. ('snmp-server user jeff
636
+ # developers v3')
637
+ #
638
+ # @return [Hash<Symbol,String>] The hashes for :auth and :privacy
639
+ def snmp_user_password_hash(running_config, user_cmd)
640
+ regexp = /#{user_cmd} .*?auth \w+\s+(.*?)\s+priv \w+\s+(.*?)\s/
641
+ (auth_hash, priv_hash) = running_config.scan(regexp).first
642
+ { auth: auth_hash, privacy: priv_hash }
643
+ end
644
+ end
645
+ end
646
+ end
647
+ end