puppet_x_eos_eapi 0.2.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 (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