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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +24 -0
- data/LICENSE.txt +202 -0
- data/README.md +87 -0
- data/Rakefile +1 -0
- data/lib/puppet_x/eos/autoload.rb +57 -0
- data/lib/puppet_x/eos/eapi.rb +259 -0
- data/lib/puppet_x/eos/module_base.rb +37 -0
- data/lib/puppet_x/eos/modules/daemon.rb +109 -0
- data/lib/puppet_x/eos/modules/extension.rb +167 -0
- data/lib/puppet_x/eos/modules/interface.rb +180 -0
- data/lib/puppet_x/eos/modules/ipinterface.rb +133 -0
- data/lib/puppet_x/eos/modules/mlag.rb +268 -0
- data/lib/puppet_x/eos/modules/ntp.rb +129 -0
- data/lib/puppet_x/eos/modules/ospf.rb +129 -0
- data/lib/puppet_x/eos/modules/portchannel.rb +277 -0
- data/lib/puppet_x/eos/modules/radius.rb +367 -0
- data/lib/puppet_x/eos/modules/snmp.rb +177 -0
- data/lib/puppet_x/eos/modules/switchport.rb +255 -0
- data/lib/puppet_x/eos/modules/system.rb +138 -0
- data/lib/puppet_x/eos/modules/tacacs.rb +302 -0
- data/lib/puppet_x/eos/modules/vlan.rb +179 -0
- data/lib/puppet_x/eos/modules/vxlan.rb +132 -0
- data/lib/puppet_x/eos/provider.rb +71 -0
- data/lib/puppet_x/eos/version.rb +41 -0
- data/lib/puppet_x/net_dev/eos_api.rb +1011 -0
- data/lib/puppet_x/net_dev/eos_api/common_methods.rb +27 -0
- data/lib/puppet_x/net_dev/eos_api/snmp_methods.rb +647 -0
- data/lib/puppet_x/net_dev/eos_api/version.rb +8 -0
- data/lib/puppet_x_eos_eapi.rb +4 -0
- data/puppet_x_eos_eapi.gemspec +31 -0
- data/spec/fixtures/fixture_all_portchannel_modes.json +8 -0
- data/spec/fixtures/fixture_all_portchannels_detailed.json +15 -0
- data/spec/fixtures/fixture_create_vlan_error.json +17 -0
- data/spec/fixtures/fixture_create_vlan_success.json +12 -0
- data/spec/fixtures/fixture_eapi_conf.yaml +4 -0
- data/spec/fixtures/fixture_enable_configure_vlan_3111_name_foo.json +14 -0
- data/spec/fixtures/fixture_enable_configure_vlan_foo_name_bar.json +19 -0
- data/spec/fixtures/fixture_get_snmp_communities_non_existent_acl.yaml +2 -0
- data/spec/fixtures/fixture_get_snmp_location_westeros.json +5 -0
- data/spec/fixtures/fixture_portchannel_min_links_1.json +8 -0
- data/spec/fixtures/fixture_portchannel_min_links_2.json +8 -0
- data/spec/fixtures/fixture_running_config.yaml +1 -0
- data/spec/fixtures/fixture_running_configuration_radius_configured.yaml +30 -0
- data/spec/fixtures/fixture_running_configuration_radius_default.yaml +29 -0
- data/spec/fixtures/fixture_running_configuration_radius_server_groups.yaml +38 -0
- data/spec/fixtures/fixture_running_configuration_radius_servers.yaml +34 -0
- data/spec/fixtures/fixture_running_configuration_tacacs_configured.yaml +38 -0
- data/spec/fixtures/fixture_running_configuration_tacacs_default.yaml +38 -0
- data/spec/fixtures/fixture_running_configuration_tacacs_groups.yaml +1 -0
- data/spec/fixtures/fixture_running_configuration_tacacs_groups_3.yaml +43 -0
- data/spec/fixtures/fixture_running_configuration_tacacs_servers.yaml +41 -0
- data/spec/fixtures/fixture_s4_show_etherchannel_detailed.json +9 -0
- data/spec/fixtures/fixture_show_flowcontrol_et1.json +5 -0
- data/spec/fixtures/fixture_show_interfaces.json +297 -0
- data/spec/fixtures/fixture_show_interfaces_switchport_format_text.json +9 -0
- data/spec/fixtures/fixture_show_port_channel_summary_2_lags.json +9 -0
- data/spec/fixtures/fixture_show_port_channel_summary_static.json +9 -0
- data/spec/fixtures/fixture_show_snmp_community.yaml +2 -0
- data/spec/fixtures/fixture_show_snmp_contact_empty.json +5 -0
- data/spec/fixtures/fixture_show_snmp_contact_name.json +5 -0
- data/spec/fixtures/fixture_show_snmp_disabled.json +5 -0
- data/spec/fixtures/fixture_show_snmp_enabled.json +5 -0
- data/spec/fixtures/fixture_show_snmp_host.yaml +2 -0
- data/spec/fixtures/fixture_show_snmp_host_duplicates.yaml +2 -0
- data/spec/fixtures/fixture_show_snmp_host_more_duplicates.yaml +2 -0
- data/spec/fixtures/fixture_show_snmp_location_empty.json +5 -0
- data/spec/fixtures/fixture_show_snmp_trap.yaml +2 -0
- data/spec/fixtures/fixture_show_snmp_user.yaml +2 -0
- data/spec/fixtures/fixture_show_snmp_user_raw_text.yaml +1 -0
- data/spec/fixtures/fixture_show_vlan.json +37 -0
- data/spec/fixtures/fixture_show_vlan_3110.json +18 -0
- data/spec/fixtures/fixture_show_vlan_4000.json +18 -0
- data/spec/fixtures/fixture_snmp_host_opts.yaml +11 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/support/fixtures.rb +104 -0
- data/spec/unit/puppet_x/eos/eapi_spec.rb +182 -0
- data/spec/unit/puppet_x/eos/module_base_spec.rb +26 -0
- data/spec/unit/puppet_x/eos/modules/daemon_spec.rb +110 -0
- data/spec/unit/puppet_x/eos/modules/extension_spec.rb +197 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/daemon_getall.json +3 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/extension_getall.json +28 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/hostname.json +6 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/interface_getall.json +509 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/ipinterface_getall.json +56 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/mlag_get.json +21 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/mlag_get_interfaces.json +18 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/ntp_get.json +5 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/ospf_instance_getall.json +58 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/portchannel_get.json +54 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/portchannel_getlacpmode.json +5 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/portchannel_getmembers.json +5 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/portchannel_po1.json +7 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/snmp_get.json +14 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/switchport_get.json +5 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/switchport_get_et1.json +7 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/switchport_getall_interfaces.json +230 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/system_domain_list.json +5 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/system_domain_name.json +5 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/system_hostname.json +6 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/system_name_servers.json +5 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/vlan_getall.json +123 -0
- data/spec/unit/puppet_x/eos/modules/fixtures/vxlan_get.json +24 -0
- data/spec/unit/puppet_x/eos/modules/interface_spec.rb +281 -0
- data/spec/unit/puppet_x/eos/modules/ipinterface_spec.rb +143 -0
- data/spec/unit/puppet_x/eos/modules/mlag_spec.rb +349 -0
- data/spec/unit/puppet_x/eos/modules/ntp_spec.rb +136 -0
- data/spec/unit/puppet_x/eos/modules/ospf_spec.rb +143 -0
- data/spec/unit/puppet_x/eos/modules/portchannel_spec.rb +357 -0
- data/spec/unit/puppet_x/eos/modules/radius_spec.rb +509 -0
- data/spec/unit/puppet_x/eos/modules/snmp_spec.rb +202 -0
- data/spec/unit/puppet_x/eos/modules/switchport_get_et1.json +7 -0
- data/spec/unit/puppet_x/eos/modules/switchport_spec.rb +307 -0
- data/spec/unit/puppet_x/eos/modules/system_spec.rb +170 -0
- data/spec/unit/puppet_x/eos/modules/tacacs_spec.rb +448 -0
- data/spec/unit/puppet_x/eos/modules/vlan_spec.rb +244 -0
- data/spec/unit/puppet_x/eos/modules/vxlan_spec.rb +189 -0
- data/spec/unit/puppet_x/eos/provider_spec.rb +35 -0
- data/spec/unit/puppet_x/net_dev/eos_api/common_methods_spec.rb +34 -0
- data/spec/unit/puppet_x/net_dev/eos_api/snmp_methods_spec.rb +842 -0
- data/spec/unit/puppet_x/net_dev/eos_api_spec.rb +1000 -0
- 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
|