cisco_node_utils 1.10.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -0
  3. data/CHANGELOG.md +25 -0
  4. data/lib/cisco_node_utils/bgp.rb +1 -1
  5. data/lib/cisco_node_utils/cisco_cmn_utils.rb +11 -3
  6. data/lib/cisco_node_utils/client/nxapi/client.rb +40 -6
  7. data/lib/cisco_node_utils/cmd_ref/acl.yaml +1 -1
  8. data/lib/cisco_node_utils/cmd_ref/banner.yaml +5 -1
  9. data/lib/cisco_node_utils/cmd_ref/feature.yaml +8 -1
  10. data/lib/cisco_node_utils/cmd_ref/interface_ospf.yaml +1 -1
  11. data/lib/cisco_node_utils/cmd_ref/interface_service_vni.yaml +5 -0
  12. data/lib/cisco_node_utils/cmd_ref/inventory.yaml +1 -1
  13. data/lib/cisco_node_utils/cmd_ref/ospf.yaml +7 -0
  14. data/lib/cisco_node_utils/cmd_ref/route_map.yaml +1 -1
  15. data/lib/cisco_node_utils/cmd_ref/vtp.yaml +1 -1
  16. data/lib/cisco_node_utils/cmd_ref/vxlan_vtep.yaml +30 -0
  17. data/lib/cisco_node_utils/cmd_ref/vxlan_vtep_vni.yaml +9 -2
  18. data/lib/cisco_node_utils/command_reference.rb +2 -2
  19. data/lib/cisco_node_utils/dhcp_relay_global.rb +1 -5
  20. data/lib/cisco_node_utils/feature.rb +30 -4
  21. data/lib/cisco_node_utils/interface.rb +3 -3
  22. data/lib/cisco_node_utils/interface_service_vni.rb +1 -1
  23. data/lib/cisco_node_utils/node.rb +11 -22
  24. data/lib/cisco_node_utils/node_util.rb +1 -1
  25. data/lib/cisco_node_utils/radius_global.rb +3 -3
  26. data/lib/cisco_node_utils/radius_server.rb +1 -2
  27. data/lib/cisco_node_utils/route_map.rb +1 -1
  28. data/lib/cisco_node_utils/router_ospf_vrf.rb +49 -5
  29. data/lib/cisco_node_utils/tacacs_global.rb +1 -2
  30. data/lib/cisco_node_utils/tacacs_server.rb +1 -2
  31. data/lib/cisco_node_utils/tacacs_server_host.rb +1 -2
  32. data/lib/cisco_node_utils/version.rb +1 -1
  33. data/lib/cisco_node_utils/vlan.rb +1 -1
  34. data/lib/cisco_node_utils/vxlan_vtep.rb +89 -4
  35. data/lib/cisco_node_utils/vxlan_vtep_vni.rb +32 -1
  36. data/spec/schema.yaml +2 -0
  37. data/tests/basetest.rb +40 -4
  38. data/tests/ciscotest.rb +15 -5
  39. data/tests/cmd_config.yaml +0 -2
  40. data/tests/test_acl.rb +1 -1
  41. data/tests/test_bgp_af.rb +6 -0
  42. data/tests/test_feature.rb +30 -4
  43. data/tests/test_interface.rb +5 -7
  44. data/tests/test_interface_ospf.rb +5 -1
  45. data/tests/test_interface_private_vlan.rb +18 -1
  46. data/tests/test_interface_svi.rb +1 -1
  47. data/tests/test_interface_switchport.rb +4 -7
  48. data/tests/test_node_ext.rb +1 -1
  49. data/tests/test_nxapi.rb +18 -8
  50. data/tests/test_radius_global.rb +3 -2
  51. data/tests/test_route_map.rb +2 -4
  52. data/tests/test_router_bgp.rb +10 -14
  53. data/tests/test_router_ospf_vrf.rb +61 -0
  54. data/tests/test_snmpserver.rb +1 -1
  55. data/tests/test_tacacs_global.rb +4 -2
  56. data/tests/test_upgrade.rb +1 -1
  57. data/tests/test_vrf.rb +2 -0
  58. data/tests/test_vrf_af.rb +2 -0
  59. data/tests/test_vtp.rb +6 -4
  60. data/tests/test_vxlan_vtep.rb +93 -1
  61. data/tests/test_vxlan_vtep_vni.rb +35 -1
  62. data/tests/yum_package.yaml +5 -0
  63. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: db160cf1dedda53f077ad5e3c246a7106be834ea
4
- data.tar.gz: 31eac56721204dbbf673355acaa1dd5591af3ed3
3
+ metadata.gz: 8500c855a9675c3c3ab5e581f06be1f67fb8074e
4
+ data.tar.gz: 0abf3dc4d719fa5282db36717571113f9ffdb617
5
5
  SHA512:
6
- metadata.gz: d6330230a4a619e0e497d5c79d5757bfe3141e53605550033fdcfcb9f4e8cfafd93e8aa2a457fd4989ff40c636001f6a070475736abf30c1cd72eb0a09126db5
7
- data.tar.gz: ac35c7431032dddce3c7fbc92c20028a6843fb25314e218925e1fa37610e6735e0a593e4aaa04ca2812182e20aa96a3d43205e436d5d2c655f850c828fddf78b
6
+ metadata.gz: 7ed3776cba64a9cbd4276a0c8034222a9d761f76f4a8b226e2af50fe8d1b1ac0c30d8cb698d1de1132e90435e7c20f6d05ea63216a379bb4da1913e5a6ee72b9
7
+ data.tar.gz: fc0a131d57d06626abdc777023b5a9637d15591997e8d88d05671f95a2efa8893b82c899fc4dc54e4f4407feac61befe7ba574a0c7438f3fe691cd1063605f60
@@ -7,6 +7,8 @@ rvm:
7
7
  - 2.2.2
8
8
  - 2.1.6
9
9
  - 2.0.0-p648 # specify non-clang version of ruby
10
+ before_install:
11
+ - gem install bundler -v '< 2'
10
12
 
11
13
  script:
12
14
  - bundle exec rake
@@ -1,6 +1,30 @@
1
1
  Changelog
2
2
  =========
3
3
 
4
+ ## [v2.0.0]
5
+
6
+ ### New Cisco Resources
7
+
8
+ ### Added
9
+ * Extend nxapi client for https support
10
+ * `use_ssl` will be true when `transport` is `https`
11
+ * now makes use of `port` for custom nxapi ports
12
+ * Extend router_ospf_vrf with attribute:
13
+ * `redistribute`
14
+ * `reset_instance` method to node. Allows a single instance of nodeutils to reset the environment cache.
15
+ * Extend vxlan_vtep_vni with attribute:
16
+ * `suppress_arp_disable`
17
+ * Extend vxlan_vtep with attributes:
18
+ * `global_suppress_arp`
19
+ * `global_ingress_replication_bgp`
20
+ * `global_mcast_group_l2`
21
+ * `global_mcast_group_l3`
22
+
23
+ ### Removed
24
+ * Removed cache in `node_util.node`, which gave every inheriting class it's own cache.
25
+
26
+ ### Changed
27
+
4
28
  ## [v1.10.0]
5
29
 
6
30
  ### New Cisco Resources
@@ -587,6 +611,7 @@ Cisco::Environment.add_env('default', env)
587
611
  [git-flow]: https://github.com/petervanderdoes/gitflow-avh
588
612
  [SimpleCov]: https://github.com/colszowka/simplecov
589
613
 
614
+ [v2.0.0]: https://github.com/cisco/cisco-network-node-utils/compare/v1.10.0...v2.0.0
590
615
  [v1.10.0]: https://github.com/cisco/cisco-network-node-utils/compare/v1.9.0...v1.10.0
591
616
  [v1.9.0]: https://github.com/cisco/cisco-network-node-utils/compare/v1.8.0...v1.9.0
592
617
  [v1.8.0]: https://github.com/cisco/cisco-network-node-utils/compare/v1.7.0...v1.8.0
@@ -827,7 +827,7 @@ module Cisco
827
827
  # explicit values when removing the rd command. These restrictions are
828
828
  # not not needed in I3 and newer images.
829
829
  Feature.nv_overlay_evpn_enable if
830
- Utils.nexus_i2_image || node.product_id[/N7/]
830
+ Utils.nexus_i2_image || node.product_id[/N[567]/]
831
831
 
832
832
  if rd == default_route_distinguisher
833
833
  return if route_distinguisher.empty?
@@ -113,6 +113,14 @@ module Cisco
113
113
  return true if Platform.chassis['pid'][ver_regexp]
114
114
  end
115
115
 
116
+ def self.fretta?
117
+ require_relative 'platform'
118
+ Platform.slots.each do |_x, row|
119
+ return true if row['pid'][/-R/]
120
+ end
121
+ false
122
+ end
123
+
116
124
  # Helper utility method for ip/prefix format networks.
117
125
  # For ip/prefix format '1.1.1.1/24' or '2000:123:38::34/64',
118
126
  # we need to mask the address using the prefix length so that they
@@ -362,9 +370,9 @@ module Cisco
362
370
  end # merge_range
363
371
 
364
372
  def self.add_quotes(value)
365
- return value if image_version?(/7.3/)
366
- value = "\"#{value}\"" unless
367
- value.start_with?('"') && value.end_with?('"')
373
+ return value if image_version?(/7.3.[0-1]/) || value.nil?
374
+ value = "'#{value}'" unless
375
+ value.start_with?('"', "'") && value.end_with?('"', "'")
368
376
  value
369
377
  end # add_quotes
370
378
 
@@ -2,7 +2,7 @@
2
2
  #
3
3
  # November 2014, Glenn F. Matthews
4
4
  #
5
- # Copyright (c) 2014-2016 Cisco and/or its affiliates.
5
+ # Copyright (c) 2014-2019 Cisco and/or its affiliates.
6
6
  #
7
7
  # Licensed under the Apache License, Version 2.0 (the "License");
8
8
  # you may not use this file except in compliance with the License.
@@ -60,7 +60,11 @@ class Cisco::Client::NXAPI < Cisco::Client
60
60
  else
61
61
  # Remote connection. This is primarily expected
62
62
  # when running e.g. from a Unix server as part of Minitest.
63
- @http = Net::HTTP.new(@host)
63
+ @transport = kwargs[:transport] || 'http'
64
+ @verify_mode = kwargs[:verify_mode] || 'none'
65
+ @port.nil? ? @port = '' : @port = ":#{@port}"
66
+ uri = URI.parse("#{@transport}://#{@host}#{@port}")
67
+ @http = Net::HTTP.new(uri.host, uri.port)
64
68
  end
65
69
  # The default read time out is 60 seconds, which may be too short for
66
70
  # scaled configuration to apply. Change it to 300 seconds, which is
@@ -205,14 +209,18 @@ class Cisco::Client::NXAPI < Cisco::Client
205
209
  request = build_http_request(type, command)
206
210
 
207
211
  # send the request and get the response
208
- debug("Sending HTTP request to NX-API at #{@http.address}:\n" \
212
+ debug("Sending #{@transport} request to NX-API at #{@http.address}:\n" \
209
213
  "#{request.to_hash}\n#{request.body}")
210
214
  read_timeout_check(request)
211
215
  tries = 2
212
216
  begin
213
- # Explicitly use http to avoid EOFError
214
- # http://stackoverflow.com/a/23080693
215
- @http.use_ssl = false
217
+ if @transport == 'https'
218
+ debug('Setting use_ssl to true')
219
+ @http.use_ssl = true
220
+ @http.verify_mode = handle_verify_mode(@verify_mode)
221
+ else
222
+ @http.use_ssl = false
223
+ end
216
224
  response = @http.request(request)
217
225
  rescue Errno::ECONNREFUSED, Errno::ECONNRESET
218
226
  emsg = 'Connection refused or reset. Is the NX-API feature enabled?'
@@ -268,6 +276,32 @@ class Cisco::Client::NXAPI < Cisco::Client
268
276
  end
269
277
  private :build_http_request
270
278
 
279
+ # Returns the OpenSSL verify mode based on the verify_mode arguments
280
+ #
281
+ # @raise if verify_mode param is not `peer`, `client-once`, `fail-no-peer`
282
+ # or `none`
283
+ #
284
+ # @param String verify mode to use
285
+ #
286
+ # @return OpenSSL::SSL verification mode
287
+ def handle_verify_mode(verify_mode)
288
+ case verify_mode
289
+ when 'peer'
290
+ OpenSSL::SSL::VERIFY_PEER
291
+ when 'client-once'
292
+ OpenSSL::SSL::VERIFY_CLIENT_ONCE
293
+ when 'fail-no-peer'
294
+ OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
295
+ when 'none'
296
+ OpenSSL::SSL::VERIFY_NONE
297
+ else
298
+ fail "#{verify_mode} is not a valid mode, " \
299
+ 'valid modes are: "none", "peer", ' \
300
+ '"client-once", "fail-no-peer"'
301
+ end
302
+ end
303
+ private :handle_verify_mode
304
+
271
305
  def handle_http_response(response)
272
306
  debug("HTTP Response: #{response.message}\n#{response.body}")
273
307
  case response
@@ -36,7 +36,7 @@ all_acls:
36
36
 
37
37
  fragments:
38
38
  # Note: The ACL 'fragments' keyword is independent of ACE 'fragments'
39
- _exclude: [N5k, N6k]
39
+ _exclude: [N5k, N6k, N3k-F, N9k-F]
40
40
  get_value: '/fragments (\S+)$/'
41
41
  set_value: '<state> fragments <action>'
42
42
  default_value: ~
@@ -1,7 +1,11 @@
1
1
  # banner
2
2
  ---
3
3
  motd:
4
- default_value: "User Access Verification\n"
4
+ N5k: &N6000
5
+ default_value: "Nexus 6000 Switch\n"
6
+ N6k: *N6000
7
+ else:
8
+ default_value: "User Access Verification\n"
5
9
  get_command: 'show banner motd'
6
10
  get_value: '/^.*$/m'
7
11
  set_value: '<state> banner motd <motd>'
@@ -31,8 +31,14 @@ fabric_forwarding:
31
31
  get_value: '/^feature fabric forwarding$/'
32
32
  set_value: "feature fabric forwarding"
33
33
 
34
+ fabricpath:
35
+ _exclude: [N3k, N3k-F, N9k-F, N9k]
36
+ get_command: "show feature-set"
37
+ get_value: '/^fabricpath[\s\d]+(\w+)/'
38
+ set_value: "<state> feature-set fabricpath"
39
+
34
40
  fex:
35
- _exclude: [C3064, C3132, N5k, N6k, N3k-F, N9k-F]
41
+ _exclude: [C3048, C3064, C3132, N5k, N6k, N3k-F, N9k-F]
36
42
  get_command: "show feature-set"
37
43
  get_value: '/^fex[\s\d]+(\w+)/'
38
44
  set_value: "<state> feature-set fex"
@@ -113,6 +119,7 @@ vni:
113
119
  set_value: 'feature vn-segment-vlan-based'
114
120
 
115
121
  vtp:
122
+ _exclude: [N3k-F, N9k-F]
116
123
  kind: boolean
117
124
  get_value: '/^feature vtp$/'
118
125
  set_value: "<state> feature vtp"
@@ -21,7 +21,7 @@ bfd:
21
21
  # no config at all so need to grab the optional
22
22
  # match to get the whole config for checking
23
23
  # the mode
24
- get_value: '/^\s*ip ospf bfd *(?:\S+)?$/'
24
+ get_value: '/^\s*ip ospf bfd *(?:\S*)$/'
25
25
  set_value: '%s ip ospf bfd %s'
26
26
  default_value: ~
27
27
 
@@ -11,6 +11,11 @@ _template:
11
11
  - 'interface <name>'
12
12
  - 'service instance <sid> vni'
13
13
 
14
+ all_interfaces:
15
+ multiple:
16
+ get_context: ~
17
+ get_value: '/^interface (.*)/'
18
+
14
19
  all_service_vni_ids:
15
20
  multiple:
16
21
  get_context:
@@ -41,5 +41,5 @@ versionid:
41
41
  nexus:
42
42
  get_value: ["name \"Chassis\"", "vendorid"]
43
43
  N5k: &vendorid5k
44
- get_value: ["name Chassis", "serialnum"]
44
+ get_value: ["name Chassis", "vendorid"]
45
45
  N6k: *vendorid5k
@@ -39,6 +39,13 @@ process_initialized:
39
39
  get_command: "show ip ospf stat"
40
40
  get_value: '/^No SAP is registered/' # Ospf is not initialized when seen
41
41
 
42
+ redistribute:
43
+ # no support yet for redistribute maximum-prefix
44
+ multiple: true
45
+ get_value: '/^redistribute (\S+ ?\S+?) route-map (\S+)$/'
46
+ set_value: '<state> redistribute <protocol> route-map <route_map>'
47
+ default_value: []
48
+
42
49
  router:
43
50
  multiple: true
44
51
  get_command: "show running ospf"
@@ -361,7 +361,7 @@ set_distance_local:
361
361
 
362
362
  # there is more than one space before delete on some platforms
363
363
  set_extcomm_list:
364
- get_value: '/^set extcomm-list (\S+)(?:\s+)? delete$/'
364
+ get_value: '/^set extcomm-list (\S+)\s+delete$/'
365
365
  set_value: "<state> set extcomm-list <list> delete"
366
366
  default_value: false
367
367
 
@@ -1,6 +1,6 @@
1
1
  # vtp
2
2
  ---
3
- _exclude: [ios_xr]
3
+ _exclude: [ios_xr, N3k-F, N9k-F]
4
4
 
5
5
  # VTP behaves differently across the various Nexus platforms and as a
6
6
  # result it's not currently possible to use a single 'show running'
@@ -12,6 +12,36 @@ all_interfaces:
12
12
  get_context: ~
13
13
  get_value: '/^interface (.*)$/'
14
14
 
15
+ global_ingress_replication_bgp:
16
+ _exclude: [N3k-F, N5k, N6k, N7k, N9k-F]
17
+ os_version: 'N9k:9.2'
18
+ kind: boolean
19
+ get_value: '/^global ingress-replication protocol bgp$/'
20
+ set_value: '<state> global ingress-replication protocol bgp'
21
+ default_value: false
22
+
23
+ global_mcast_group_l2:
24
+ _exclude: [N3k-F, N5k, N6k, N7k]
25
+ os_version: 'N9k, N9k-F:9.2'
26
+ get_value: '/^global mcast-group (\S+) L2$/'
27
+ set_value: '<state> global mcast-group <ip> L2'
28
+ default_value: ~
29
+
30
+ global_mcast_group_l3:
31
+ _exclude: [N3k-F, N5k, N6k, N7k, N9k-F]
32
+ os_version: 'N9k:9.2'
33
+ get_value: '/^global mcast-group (\S+) L3$/'
34
+ set_value: '<state> global mcast-group <ip> L3'
35
+ default_value: ~
36
+
37
+ global_suppress_arp:
38
+ _exclude: [N3k-F, N5k, N6k, N7k]
39
+ os_version: 'N9k, N9k-F:9.2'
40
+ kind: boolean
41
+ get_value: '/^global suppress-arp$/'
42
+ set_value: '<state> global suppress-arp'
43
+ default_value: false
44
+
15
45
  host_reachability:
16
46
  get_value: '/^host-reachability protocol (\S+)/'
17
47
  set_value: '<state> host-reachability protocol <proto>'
@@ -16,7 +16,7 @@ all_vnis:
16
16
  get_value: '/^member vni (\d+|\d+-\d+) ?(associate-vrf)?$/'
17
17
 
18
18
  ingress_replication:
19
- _exclude: [N5k, N6k, N7k]
19
+ _exclude: [N3k-F, N5k, N6k, N7k, N9k-F]
20
20
  kind: string
21
21
  get_value: '/^ingress-replication protocol (\S+)$/'
22
22
  set_value: '<state> ingress-replication protocol <protocol>'
@@ -35,7 +35,7 @@ multisite_ingress_replication:
35
35
  default_value: false
36
36
 
37
37
  peer_list:
38
- _exclude: [N5k, N6k, N7k]
38
+ _exclude: [N3k-F, N5k, N6k, N7k, N9k-F]
39
39
  multiple:
40
40
  get_context:
41
41
  - '/^interface <name>$/i'
@@ -55,6 +55,13 @@ suppress_arp:
55
55
  set_value: '<state> suppress-arp'
56
56
  default_value: false
57
57
 
58
+ suppress_arp_disable:
59
+ _exclude: [N3k-F, N5k, N6k, N7k]
60
+ os_version: 'N9k, N9k-F:9.2'
61
+ get_value: '/^suppress-arp disable$/'
62
+ set_value: '<state> suppress-arp disable'
63
+ default_value: ~
64
+
58
65
  suppress_uuc:
59
66
  _exclude: [N3k-F, N9k-F, N9k]
60
67
  os_version: 'N7k:8.1.1'
@@ -437,8 +437,8 @@ module Cisco
437
437
  puts "DEBUG: #{text}" if @@debug
438
438
  end
439
439
 
440
- KNOWN_PLATFORMS = %w(C3064 C3132 C3172 N35 N3k N3k-F N5k N6k N7k N9k N9k-F
441
- XRv9k)
440
+ KNOWN_PLATFORMS = %w(C3048 C3064 C3132 C3172 N35 N3k N3k-F N5k N6k N7k N9k
441
+ N9k-F XRv9k)
442
442
 
443
443
  def self.platform_to_filter(platform)
444
444
  if KNOWN_PLATFORMS.include?(platform)
@@ -172,7 +172,6 @@ module Cisco
172
172
 
173
173
  def ipv4_src_intf
174
174
  intf = config_get('dhcp_relay_global', 'ipv4_src_intf')
175
- # Normalize by downcasing and removing white space
176
175
  intf = intf.downcase.delete(' ') if intf
177
176
  intf
178
177
  end
@@ -207,10 +206,7 @@ module Cisco
207
206
  def ipv4_sub_option_circuit_id_string
208
207
  str = config_get('dhcp_relay_global', 'ipv4_sub_option_circuit_id_string')
209
208
  # Normalize by removing white space and add quotes
210
- if str
211
- str.strip!
212
- str = Utils.add_quotes(str)
213
- end
209
+ str.strip! if str.kind_of?(String)
214
210
  str
215
211
  end
216
212
 
@@ -80,6 +80,27 @@ module Cisco
80
80
  config_get('feature', 'fabric')
81
81
  end
82
82
 
83
+ # ---------------------------
84
+ def self.fabricpath_enable
85
+ # install feature-set and enable it
86
+ return if fabricpath_enabled?
87
+ config_set('feature', 'fabricpath', state: 'install') unless
88
+ fabricpath_installed?
89
+ config_set('feature', 'fabricpath', state: '')
90
+ end
91
+
92
+ def self.fabricpath_enabled?
93
+ config_get('feature', 'fabricpath') =~ /^enabled/
94
+ end
95
+
96
+ def self.fabricpath_installed?
97
+ config_get('feature', 'fabricpath') !~ /^uninstalled/
98
+ end
99
+
100
+ def self.fabricpath_supported?
101
+ config_get('feature', 'fabricpath')
102
+ end
103
+
83
104
  # ---------------------------
84
105
  def self.fabric_forwarding_enable
85
106
  return if fabric_forwarding_enabled?
@@ -183,7 +204,8 @@ module Cisco
183
204
  def self.nv_overlay_enable
184
205
  # Note: vdc platforms restrict this feature to F3 or newer linecards
185
206
  return if nv_overlay_enabled?
186
- config_set('feature', 'nv_overlay', state: '')
207
+ result = config_set('feature', 'nv_overlay', state: '')
208
+ cli_error_check(result)
187
209
  sleep 1
188
210
  end
189
211
 
@@ -311,14 +333,18 @@ module Cisco
311
333
  # instead just displays a STDOUT error message; thus NXAPI does not detect
312
334
  # the failure and we must catch it by inspecting the "body" hash entry
313
335
  # returned by NXAPI. This cli behavior is unlikely to change soon.
336
+ patterns = [
337
+ 'Hardware is not capable of supporting',
338
+ 'is unsupported on this node',
339
+ 'Feature NOT supported on this Platform',
340
+ ]
314
341
  fail result[2]['body'] if
315
342
  result[2].is_a?(Hash) &&
316
- /Hardware is not capable of supporting/.match(result[2]['body'].to_s)
343
+ result[2]['body'].to_s[Regexp.union(patterns)]
317
344
 
318
345
  # Some test environments get result as a string instead of a hash
319
346
  fail result if
320
- result.is_a?(String) &&
321
- /Hardware is not capable of supporting/.match(result)
347
+ result.is_a?(String) && result[Regexp.union(patterns)]
322
348
  end
323
349
 
324
350
  # ---------------------------