cisco_node_utils 1.2.0 → 1.3.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 (255) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +13 -0
  5. data/.travis.yml +4 -1
  6. data/CHANGELOG.md +81 -2
  7. data/CONTRIBUTING.md +2 -17
  8. data/Gemfile +5 -0
  9. data/README.md +92 -47
  10. data/Rakefile +23 -1
  11. data/bin/git/hooks/hook_lib +7 -0
  12. data/bin/git/hooks/pre-commit/check_unstaged_changes +18 -0
  13. data/bin/git/hooks/pre-commit/rubocop +7 -2
  14. data/bin/git/hooks/pre-commit/validate-diffs +18 -4
  15. data/bin/git/hooks/pre-commit/validate-yaml +18 -0
  16. data/bin/git/update-hooks +64 -6
  17. data/cisco_node_utils.gemspec +9 -6
  18. data/docs/README-develop-best-practices.md +149 -50
  19. data/docs/README-develop-node-utils-APIs.md +92 -42
  20. data/docs/README-maintainers.md +7 -4
  21. data/docs/README-test-execution.md +57 -0
  22. data/docs/cisco_node_utils.yaml.example +30 -0
  23. data/docs/template-router.rb +4 -0
  24. data/ext/mkrf_conf.rb +63 -0
  25. data/lib/.rubocop.yml +2 -2
  26. data/lib/cisco_node_utils.rb +5 -0
  27. data/lib/cisco_node_utils/aaa_authentication_login.rb +5 -6
  28. data/lib/cisco_node_utils/aaa_authorization_service.rb +1 -1
  29. data/lib/cisco_node_utils/ace.rb +165 -12
  30. data/lib/cisco_node_utils/acl.rb +2 -1
  31. data/lib/cisco_node_utils/bgp.rb +184 -21
  32. data/lib/cisco_node_utils/bgp_af.rb +94 -249
  33. data/lib/cisco_node_utils/bgp_neighbor.rb +94 -14
  34. data/lib/cisco_node_utils/bgp_neighbor_af.rb +75 -8
  35. data/lib/cisco_node_utils/bridge_domain.rb +183 -0
  36. data/lib/cisco_node_utils/bridge_domain_vni.rb +206 -0
  37. data/lib/cisco_node_utils/cisco_cmn_utils.rb +85 -2
  38. data/lib/cisco_node_utils/client.rb +35 -0
  39. data/lib/cisco_node_utils/client/client.rb +234 -0
  40. data/lib/cisco_node_utils/client/grpc.rb +33 -0
  41. data/lib/cisco_node_utils/client/grpc/client.rb +311 -0
  42. data/lib/cisco_node_utils/client/grpc/ems.proto +148 -0
  43. data/lib/cisco_node_utils/client/grpc/ems.rb +111 -0
  44. data/lib/cisco_node_utils/client/grpc/ems_services.rb +49 -0
  45. data/lib/cisco_node_utils/client/nxapi.rb +31 -0
  46. data/lib/cisco_node_utils/client/nxapi/client.rb +305 -0
  47. data/lib/cisco_node_utils/client/utils.rb +164 -0
  48. data/lib/cisco_node_utils/cmd_ref/README_YAML.md +222 -254
  49. data/lib/cisco_node_utils/cmd_ref/aaa_auth_login_service.yaml +11 -8
  50. data/lib/cisco_node_utils/cmd_ref/aaa_authentication_login.yaml +22 -15
  51. data/lib/cisco_node_utils/cmd_ref/aaa_authorization_service.yaml +11 -8
  52. data/lib/cisco_node_utils/cmd_ref/acl.yaml +21 -16
  53. data/lib/cisco_node_utils/cmd_ref/bgp.yaml +239 -109
  54. data/lib/cisco_node_utils/cmd_ref/bgp_af.yaml +114 -55
  55. data/lib/cisco_node_utils/cmd_ref/bgp_neighbor.yaml +76 -52
  56. data/lib/cisco_node_utils/cmd_ref/bgp_neighbor_af.yaml +106 -62
  57. data/lib/cisco_node_utils/cmd_ref/bridge_domain.yaml +71 -0
  58. data/lib/cisco_node_utils/cmd_ref/bridge_domain_vni.yaml +33 -0
  59. data/lib/cisco_node_utils/cmd_ref/dnsclient.yaml +35 -14
  60. data/lib/cisco_node_utils/cmd_ref/encapsulation.yaml +25 -0
  61. data/lib/cisco_node_utils/cmd_ref/evpn_vni.yaml +23 -17
  62. data/lib/cisco_node_utils/cmd_ref/fabricpath.yaml +94 -83
  63. data/lib/cisco_node_utils/cmd_ref/fabricpath_topology.yaml +22 -17
  64. data/lib/cisco_node_utils/cmd_ref/feature.yaml +76 -26
  65. data/lib/cisco_node_utils/cmd_ref/images.yaml +3 -2
  66. data/lib/cisco_node_utils/cmd_ref/interface.yaml +381 -153
  67. data/lib/cisco_node_utils/cmd_ref/interface_channel_group.yaml +21 -11
  68. data/lib/cisco_node_utils/cmd_ref/interface_ospf.yaml +21 -21
  69. data/lib/cisco_node_utils/cmd_ref/interface_portchannel.yaml +30 -21
  70. data/lib/cisco_node_utils/cmd_ref/interface_service_vni.yaml +18 -13
  71. data/lib/cisco_node_utils/cmd_ref/inventory.yaml +26 -31
  72. data/lib/cisco_node_utils/cmd_ref/itd_device_group.yaml +83 -0
  73. data/lib/cisco_node_utils/cmd_ref/itd_service.yaml +119 -0
  74. data/lib/cisco_node_utils/cmd_ref/memory.yaml +17 -6
  75. data/lib/cisco_node_utils/cmd_ref/ntp_config.yaml +10 -3
  76. data/lib/cisco_node_utils/cmd_ref/ntp_server.yaml +17 -5
  77. data/lib/cisco_node_utils/cmd_ref/ospf.yaml +33 -29
  78. data/lib/cisco_node_utils/cmd_ref/overlay_global.yaml +12 -10
  79. data/lib/cisco_node_utils/cmd_ref/pim.yaml +16 -19
  80. data/lib/cisco_node_utils/cmd_ref/portchannel_global.yaml +40 -25
  81. data/lib/cisco_node_utils/cmd_ref/radius_global.yaml +17 -12
  82. data/lib/cisco_node_utils/cmd_ref/radius_server.yaml +71 -35
  83. data/lib/cisco_node_utils/cmd_ref/radius_server_group.yaml +10 -5
  84. data/lib/cisco_node_utils/cmd_ref/show_system.yaml +6 -2
  85. data/lib/cisco_node_utils/cmd_ref/show_version.yaml +47 -43
  86. data/lib/cisco_node_utils/cmd_ref/snmp_community.yaml +13 -11
  87. data/lib/cisco_node_utils/cmd_ref/snmp_group.yaml +4 -2
  88. data/lib/cisco_node_utils/cmd_ref/snmp_notification_receiver.yaml +23 -21
  89. data/lib/cisco_node_utils/cmd_ref/snmp_server.yaml +26 -22
  90. data/lib/cisco_node_utils/cmd_ref/snmp_user.yaml +19 -17
  91. data/lib/cisco_node_utils/cmd_ref/snmpnotification.yaml +18 -6
  92. data/lib/cisco_node_utils/cmd_ref/stp_global.yaml +234 -0
  93. data/lib/cisco_node_utils/cmd_ref/syslog_server.yaml +24 -9
  94. data/lib/cisco_node_utils/cmd_ref/syslog_settings.yaml +5 -3
  95. data/lib/cisco_node_utils/cmd_ref/system.yaml +4 -3
  96. data/lib/cisco_node_utils/cmd_ref/tacacs_server.yaml +22 -20
  97. data/lib/cisco_node_utils/cmd_ref/tacacs_server_group.yaml +27 -15
  98. data/lib/cisco_node_utils/cmd_ref/tacacs_server_host.yaml +45 -16
  99. data/lib/cisco_node_utils/cmd_ref/vdc.yaml +21 -11
  100. data/lib/cisco_node_utils/cmd_ref/virtual_service.yaml +3 -2
  101. data/lib/cisco_node_utils/cmd_ref/vlan.yaml +60 -32
  102. data/lib/cisco_node_utils/cmd_ref/vpc.yaml +118 -101
  103. data/lib/cisco_node_utils/cmd_ref/vrf.yaml +54 -58
  104. data/lib/cisco_node_utils/cmd_ref/vrf_af.yaml +118 -0
  105. data/lib/cisco_node_utils/cmd_ref/vtp.yaml +19 -25
  106. data/lib/cisco_node_utils/cmd_ref/vxlan_vtep.yaml +28 -18
  107. data/lib/cisco_node_utils/cmd_ref/vxlan_vtep_vni.yaml +34 -17
  108. data/lib/cisco_node_utils/cmd_ref/yum.yaml +6 -4
  109. data/lib/cisco_node_utils/command_reference.rb +261 -142
  110. data/lib/cisco_node_utils/constants.rb +33 -0
  111. data/lib/cisco_node_utils/encapsulation.rb +112 -0
  112. data/lib/cisco_node_utils/environment.rb +102 -0
  113. data/lib/cisco_node_utils/evpn_vni.rb +5 -3
  114. data/lib/cisco_node_utils/exceptions.rb +111 -0
  115. data/lib/cisco_node_utils/fabricpath_global.rb +52 -35
  116. data/lib/cisco_node_utils/fabricpath_topology.rb +44 -57
  117. data/lib/cisco_node_utils/feature.rb +165 -3
  118. data/lib/cisco_node_utils/interface.rb +1051 -260
  119. data/lib/cisco_node_utils/interface_channel_group.rb +11 -10
  120. data/lib/cisco_node_utils/interface_ospf.rb +1 -2
  121. data/lib/cisco_node_utils/interface_portchannel.rb +4 -12
  122. data/lib/cisco_node_utils/interface_service_vni.rb +7 -7
  123. data/lib/cisco_node_utils/itd_device_group.rb +248 -0
  124. data/lib/cisco_node_utils/itd_device_group_node.rb +144 -0
  125. data/lib/cisco_node_utils/itd_service.rb +523 -0
  126. data/lib/cisco_node_utils/logger.rb +75 -0
  127. data/lib/cisco_node_utils/node.rb +62 -192
  128. data/lib/cisco_node_utils/node_util.rb +56 -10
  129. data/lib/cisco_node_utils/overlay_global.rb +2 -2
  130. data/lib/cisco_node_utils/pim.rb +2 -13
  131. data/lib/cisco_node_utils/pim_group_list.rb +1 -1
  132. data/lib/cisco_node_utils/pim_rp_address.rb +1 -1
  133. data/lib/cisco_node_utils/platform.rb +52 -21
  134. data/lib/cisco_node_utils/portchannel_global.rb +89 -19
  135. data/lib/cisco_node_utils/radius_server.rb +168 -37
  136. data/lib/cisco_node_utils/router_ospf.rb +20 -35
  137. data/lib/cisco_node_utils/router_ospf_vrf.rb +4 -4
  138. data/lib/cisco_node_utils/snmpserver.rb +1 -6
  139. data/lib/cisco_node_utils/snmpuser.rb +6 -4
  140. data/lib/cisco_node_utils/stp_global.rb +676 -0
  141. data/lib/cisco_node_utils/syslog_server.rb +77 -18
  142. data/lib/cisco_node_utils/syslog_settings.rb +1 -1
  143. data/lib/cisco_node_utils/tacacs_server_group.rb +8 -4
  144. data/lib/cisco_node_utils/tacacs_server_host.rb +115 -25
  145. data/lib/cisco_node_utils/vdc.rb +12 -0
  146. data/lib/cisco_node_utils/version.rb +1 -1
  147. data/lib/cisco_node_utils/vlan.rb +147 -29
  148. data/lib/cisco_node_utils/vpc.rb +55 -3
  149. data/lib/cisco_node_utils/vrf.rb +72 -11
  150. data/lib/cisco_node_utils/vrf_af.rb +114 -29
  151. data/lib/cisco_node_utils/vtp.rb +34 -52
  152. data/lib/cisco_node_utils/vxlan_vtep.rb +34 -8
  153. data/lib/cisco_node_utils/vxlan_vtep_vni.rb +36 -4
  154. data/lib/minitest/environment_plugin.rb +31 -0
  155. data/lib/minitest/log_level_plugin.rb +41 -0
  156. data/spec/client_spec.rb +7 -0
  157. data/spec/environment_spec.rb +263 -0
  158. data/spec/grpc_client_spec.rb +23 -0
  159. data/spec/isolate/all_clients_spec.rb +9 -0
  160. data/spec/isolate/grpc_only_spec.rb +16 -0
  161. data/spec/isolate/no_clients_spec.rb +26 -0
  162. data/spec/isolate/nxapi_only_spec.rb +16 -0
  163. data/spec/nxapi_client_spec.rb +42 -0
  164. data/spec/schema.yaml +75 -0
  165. data/spec/shared_examples_for_clients.rb +14 -0
  166. data/spec/spec_helper.rb +91 -0
  167. data/spec/whitespace_spec.rb +10 -0
  168. data/spec/yaml_spec.rb +42 -0
  169. data/tests/.rubocop.yml +2 -2
  170. data/tests/CSCuxdublin-1.0.0-7.0.3.I3.1.lib32_n9000.rpm +0 -0
  171. data/tests/basetest.rb +96 -36
  172. data/tests/ciscotest.rb +220 -12
  173. data/tests/cmd_config.yaml +71 -49
  174. data/tests/cmd_config_invalid.yaml +1 -1
  175. data/tests/test_aaa_authentication_login.rb +1 -0
  176. data/tests/test_aaa_authentication_login_service.rb +9 -0
  177. data/tests/test_aaa_authorization_service.rb +173 -367
  178. data/tests/test_ace.rb +171 -100
  179. data/tests/test_acl.rb +10 -1
  180. data/tests/test_bgp_af.rb +395 -728
  181. data/tests/test_bgp_neighbor.rb +274 -115
  182. data/tests/test_bgp_neighbor_af.rb +178 -77
  183. data/tests/test_bridge_domain.rb +191 -0
  184. data/tests/test_bridge_domain_vni.rb +116 -0
  185. data/tests/test_client_utils.rb +111 -0
  186. data/tests/test_command_config.rb +9 -5
  187. data/tests/test_command_reference.rb +380 -102
  188. data/tests/test_dns_domain.rb +13 -3
  189. data/tests/test_domain_name.rb +13 -3
  190. data/tests/test_encapsulation.rb +77 -0
  191. data/tests/test_evpn_vni.rb +25 -7
  192. data/tests/test_fabricpath_global.rb +167 -163
  193. data/tests/test_fabricpath_topology.rb +12 -33
  194. data/tests/test_feature.rb +215 -0
  195. data/tests/test_grpc.rb +166 -0
  196. data/tests/test_interface.rb +585 -344
  197. data/tests/test_interface_bdi.rb +80 -0
  198. data/tests/test_interface_channel_group.rb +6 -3
  199. data/tests/test_interface_ospf.rb +26 -24
  200. data/tests/test_interface_portchannel.rb +1 -0
  201. data/tests/test_interface_private_vlan.rb +724 -0
  202. data/tests/test_interface_service_vni.rb +37 -66
  203. data/tests/test_interface_svi.rb +98 -101
  204. data/tests/test_interface_switchport.rb +419 -549
  205. data/tests/test_itd_device_group.rb +145 -0
  206. data/tests/test_itd_device_group_node.rb +199 -0
  207. data/tests/test_itd_service.rb +298 -0
  208. data/tests/test_logger.rb +43 -0
  209. data/tests/test_name_server.rb +11 -2
  210. data/tests/test_node.rb +16 -75
  211. data/tests/test_node_ext.rb +174 -163
  212. data/tests/test_node_util.rb +119 -0
  213. data/tests/test_ntp_config.rb +5 -1
  214. data/tests/test_ntp_server.rb +2 -2
  215. data/tests/test_nxapi.rb +221 -0
  216. data/tests/test_overlay_global.rb +47 -38
  217. data/tests/test_pim.rb +2 -0
  218. data/tests/test_pim_group_list.rb +2 -0
  219. data/tests/test_pim_rp_address.rb +2 -0
  220. data/tests/test_platform.rb +86 -39
  221. data/tests/test_portchannel_global.rb +211 -135
  222. data/tests/test_radius_global.rb +13 -5
  223. data/tests/test_radius_server.rb +256 -104
  224. data/tests/test_radius_server_group.rb +2 -0
  225. data/tests/test_router_bgp.rb +781 -485
  226. data/tests/test_router_ospf.rb +26 -103
  227. data/tests/test_router_ospf_vrf.rb +52 -57
  228. data/tests/test_snmp_notification_receiver.rb +2 -0
  229. data/tests/test_snmpcommunity.rb +2 -0
  230. data/tests/test_snmpgroup.rb +2 -0
  231. data/tests/test_snmpnotification.rb +40 -21
  232. data/tests/test_snmpserver.rb +2 -0
  233. data/tests/test_snmpuser.rb +2 -0
  234. data/tests/test_stp_global.rb +563 -0
  235. data/tests/test_syslog_server.rb +32 -8
  236. data/tests/test_syslog_settings.rb +22 -9
  237. data/tests/test_tacacs_server.rb +32 -27
  238. data/tests/test_tacacs_server_group.rb +100 -45
  239. data/tests/test_tacacs_server_host.rb +135 -43
  240. data/tests/test_vdc.rb +2 -16
  241. data/tests/test_vlan.rb +106 -54
  242. data/tests/test_vlan_mt_full.rb +11 -21
  243. data/tests/test_vlan_private.rb +669 -0
  244. data/tests/test_vpc.rb +312 -159
  245. data/tests/test_vrf.rb +122 -113
  246. data/tests/test_vrf_af.rb +238 -0
  247. data/tests/test_vtp.rb +58 -102
  248. data/tests/test_vxlan_vtep.rb +38 -17
  249. data/tests/test_vxlan_vtep_vni.rb +61 -9
  250. data/tests/test_yum.rb +49 -25
  251. metadata +122 -36
  252. data/lib/cisco_node_utils/cmd_ref/fex.yaml +0 -9
  253. data/lib/cisco_node_utils/cmd_ref/vni.yaml +0 -76
  254. data/lib/cisco_node_utils/vni.rb +0 -227
  255. data/tests/test_vni.rb +0 -106
@@ -1,13 +1,15 @@
1
1
  # yum
2
2
  ---
3
+ _exclude: [ios_xr]
4
+
3
5
  install:
4
- config_set: "install add %s %s activate"
6
+ set_value: "install add %s %s activate"
5
7
 
6
8
  query:
7
9
  multiple: true
8
- config_get: "show install packages"
10
+ get_command: "show install packages"
9
11
  # pass in the pkg name, retrieve version
10
- config_get_token: '/^%s\S*\s+(\S+)\s+(?:installed|@\S+)/'
12
+ get_value: '/^%s\S*\s+(\S+)\s+(?:installed|@\S+)/'
11
13
 
12
14
  remove:
13
- config_set: "install deactivate %s"
15
+ set_value: "install deactivate %s"
@@ -1,5 +1,3 @@
1
- # CommandReference module for testing.
2
- #
3
1
  # Copyright (c) 2014-2016 Cisco and/or its affiliates.
4
2
  #
5
3
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,6 +12,7 @@
14
12
  # See the License for the specific language governing permissions and
15
13
  # limitations under the License.
16
14
 
15
+ require_relative 'exceptions'
17
16
  require 'yaml'
18
17
 
19
18
  module Cisco
@@ -26,16 +25,16 @@ module Cisco
26
25
  alias_method :multiple?, :multiple
27
26
 
28
27
  KEYS = %w(default_value default_only
29
- config_set config_set_append
30
- config_get config_get_token config_get_token_append
31
- auto_default multiple kind
32
- test_config_get test_config_get_regex test_config_result)
28
+ data_format context value
29
+ get_data_format get_command get_context get_value
30
+ set_data_format set_context set_value
31
+ auto_default multiple kind)
33
32
 
34
33
  def self.keys
35
34
  KEYS
36
35
  end
37
36
 
38
- KINDS = %w(boolean int string)
37
+ KINDS = %w(boolean int string symbol)
39
38
 
40
39
  # Construct a CmdRef describing the given (feature, name) pair.
41
40
  # Param "values" is a hash with keys as described in KEYS.
@@ -45,46 +44,113 @@ module Cisco
45
44
 
46
45
  @feature = feature
47
46
  @name = name
48
- @hash = {}
49
47
  @auto_default = true
50
48
  @default_only = false
51
49
  @multiple = false
52
50
  @kind = nil
53
51
 
52
+ values_to_hash(values, file)
53
+
54
+ if @hash['get_value'] || @hash['get_command']
55
+ define_helper('getter',
56
+ data_format: @hash['get_data_format'] || :cli,
57
+ command: @hash['get_command'],
58
+ context: @hash['get_context'] || [],
59
+ value: @hash['get_value'])
60
+ end
61
+ if @hash['set_value'] # rubocop:disable Style/GuardClause
62
+ define_helper('setter',
63
+ data_format: @hash['set_data_format'] || :cli,
64
+ context: @hash['set_context'] || [],
65
+ values: @hash['set_value'])
66
+ end
67
+ end
68
+
69
+ def values_to_hash(values, file)
70
+ @hash = {}
54
71
  values.each do |key, value|
55
72
  unless KEYS.include?(key)
56
73
  fail "Unrecognized key #{key} for #{feature}, #{name} in #{file}"
57
74
  end
58
- if key == 'config_get_token' || key == 'config_set'
59
- # For simplicity, these are ALWAYS arrays
60
- value = [value] unless value.is_a?(Array)
61
- define_getter(key, value)
62
- # We intentionally do this *after* the define_getter() call
63
- @hash[key] = preprocess_value(value)
64
- elsif key == 'auto_default'
75
+ case key
76
+ when 'auto_default'
65
77
  @auto_default = value ? true : false
66
- elsif key == 'default_only'
78
+ when 'data_format', 'get_data_format', 'set_data_format'
79
+ @hash[key] = value.to_sym
80
+ when 'default_only'
67
81
  @default_only = true
68
82
  # default_value overrides default_only
69
- @hash['default_value'] ||= preprocess_value(value)
70
- elsif key == 'multiple'
83
+ @hash['default_value'] ||= value
84
+ when 'multiple'
71
85
  @multiple = boolean_default_true(value)
72
- elsif key == 'kind'
86
+ when 'kind'
73
87
  fail "Unknown 'kind': '#{value}'" unless KINDS.include?(value)
74
88
  @kind = value.to_sym
75
89
  else
76
90
  # default_value overrides default_only
77
91
  @default_only = false if key == 'default_value'
78
- @hash[key] = preprocess_value(value)
92
+ @hash[key] = value
79
93
  end
80
94
  end
81
95
 
82
- if @default_only # rubocop:disable Style/GuardClause
83
- %w(config_get_token config_set).each do |key|
84
- instance_eval "undef #{key}" if @hash.key?(key)
85
- end
86
- @hash.delete_if { |key, _| key != 'default_value' }
96
+ # Inherit general to specific if needed
97
+ if @hash.key?('data_format')
98
+ @hash['get_data_format'] = @hash['data_format'] \
99
+ unless @hash.key?('get_data_format')
100
+ @hash['set_data_format'] = @hash['data_format'] \
101
+ unless @hash.key?('set_data_format')
87
102
  end
103
+ if @hash.key?('context')
104
+ @hash['get_context'] = @hash['context'] unless @hash.key?('get_context')
105
+ @hash['set_context'] = @hash['context'] unless @hash.key?('set_context')
106
+ end
107
+ if @hash.key?('value')
108
+ @hash['get_value'] = @hash['value'] unless @hash.key?('get_value')
109
+ @hash['set_value'] = @hash['value'] unless @hash.key?('set_value')
110
+ end
111
+
112
+ @hash.delete_if { |key, _| key != 'default_value' } if @default_only
113
+ end
114
+
115
+ # Does this instance have a valid getter() function?
116
+ # Will be overridden at initialization if so.
117
+ def getter?
118
+ !@hash['getter'].nil?
119
+ end
120
+
121
+ # Does this instance have a valid setter() function?
122
+ # Will be overridden at initialization if so.
123
+ def setter?
124
+ !@hash['setter'].nil?
125
+ end
126
+
127
+ # Default getter method.
128
+ # Will be overridden at initialization if the relevant parameters are set.
129
+ #
130
+ # A non-trivial implementation of this method will take args *or* kwargs,
131
+ # and will return a hash of the form:
132
+ # {
133
+ # data_format: :cli,
134
+ # command: string or nil,
135
+ # context: array<string> or array<regexp>, perhaps empty
136
+ # value: string or regexp,
137
+ # }
138
+ def getter(*args, **kwargs) # rubocop:disable Lint/UnusedMethodArgument
139
+ fail UnsupportedError.new(@feature, @name, 'getter')
140
+ end
141
+
142
+ # Default setter method.
143
+ # Will be overridden at initialization if the relevant parameters are set.
144
+ #
145
+ # A non-trivial implementation of this method will take args *or* kwargs,
146
+ # and will return a hash of the form:
147
+ # {
148
+ # data_format: :cli,
149
+ # context: array<string>, perhaps empty
150
+ # values: array<string>,
151
+ # }
152
+ def setter(*args, **kwargs) # rubocop:disable Lint/UnusedMethodArgument
153
+ fail UnsupportedError.new(@feature, @name, 'setter')
88
154
  end
89
155
 
90
156
  # Property with an implicit value of 'true' if no value is given
@@ -92,78 +158,119 @@ module Cisco
92
158
  value.nil? || value
93
159
  end
94
160
 
95
- # Create a getter method for the given key.
96
- # This getter method will automatically handle wildcard arguments.
97
- def define_getter(key, value)
98
- return unless value.is_a?(Array)
99
- if value.any? { |item| item.is_a?(String) && /<\S+>/ =~ item }
100
- # Key-value substitution
101
- define_singleton_method(key.to_sym, key_substitutor(key, value))
102
- elsif value.any? { |item| item.is_a?(String) && /%/ =~ item }
103
- # printf-style substitution
104
- define_singleton_method(key.to_sym, printf_substitutor(key, value))
161
+ def key_substitutor(item, kwargs)
162
+ result = item
163
+ kwargs.each do |key, value|
164
+ result = result.sub("<#{key}>", value.to_s)
165
+ end
166
+ unsub = result[/<(\S+)>/, 1]
167
+ fail ArgumentError, \
168
+ "No value specified for '#{unsub}' in '#{result}'" if unsub
169
+ result
170
+ end
171
+
172
+ def printf_substitutor(item, args)
173
+ item = sprintf(item, *args.shift(item.scan(/%/).length))
174
+ [item, args]
175
+ end
176
+
177
+ # Create a helper method for generating the getter/setter values.
178
+ # This method will automatically handle wildcard arguments.
179
+ def define_helper(method_name, base_hash)
180
+ # Which kind of wildcards (if any) do we need to support?
181
+ combined = []
182
+ base_hash.each_value do |v|
183
+ combined += v if v.is_a?(Array)
184
+ combined << v if v.is_a?(String)
185
+ end
186
+ key_value = combined.any? { |i| i.is_a?(String) && /<\S+>/ =~ i }
187
+ printf = combined.any? { |i| i.is_a?(String) && /%/ =~ i }
188
+
189
+ if key_value && printf
190
+ fail 'Invalid mixture of key-value and printf wildcards ' \
191
+ "in #{method_name}: #{combined}"
192
+ elsif key_value
193
+ define_key_value_helper(method_name, base_hash)
194
+ elsif printf
195
+ arg_count = combined.join.scan(/%/).length
196
+ define_printf_helper(method_name, base_hash, arg_count)
105
197
  else
106
198
  # simple static token(s)
107
- value = preprocess_value(value)
108
- define_singleton_method key.to_sym, -> { value }
199
+ define_static_helper(method_name, base_hash)
109
200
  end
201
+ @hash[method_name] = true
110
202
  end
111
203
 
112
- # curried function to define a getter method body that performs key-value
113
- # substitution
114
- def key_substitutor(config_key, value)
115
- lambda do |**args|
116
- result = []
117
- value.each do |line|
118
- replace = line.scan(/<(\S+)>/).flatten.map(&:to_sym)
119
- replace.each do |item|
120
- line = line.sub("<#{item}>", args[item].to_s) if args.key?(item)
121
- end
122
- result.push(line) unless /<\S+>/.match(line)
204
+ def define_key_value_helper(method_name, base_hash)
205
+ # Key-value substitution
206
+ define_singleton_method method_name.to_sym do |*args, **kwargs|
207
+ unless args.empty?
208
+ fail ArgumentError, "#{method_name} requires keyword args, not "\
209
+ 'positional args'
123
210
  end
124
- if result.empty?
125
- fail ArgumentError,
126
- "Arguments given to #{config_key} yield empty result"
211
+ result = {}
212
+ base_hash.each do |k, v|
213
+ if v.is_a?(String)
214
+ v = key_substitutor(v, kwargs)
215
+ elsif v.is_a?(Array)
216
+ output = []
217
+ v.each do |line|
218
+ # Check for (?) flag indicating optional param
219
+ optional_line = line[/^\(\?\)(.*)/, 1]
220
+ if optional_line
221
+ begin
222
+ line = key_substitutor(optional_line, kwargs)
223
+ rescue ArgumentError # Unsubstituted key - OK to skip this line
224
+ next
225
+ end
226
+ else
227
+ line = key_substitutor(line, kwargs)
228
+ end
229
+ output.push(line)
230
+ end
231
+ v = output
232
+ end
233
+ result[k] = v
127
234
  end
128
- preprocess_value(result)
235
+ result
129
236
  end
130
237
  end
131
238
 
132
- # curried function to define a getter method body that performs
133
- # printf-style substitution
134
- def printf_substitutor(config_key, value)
135
- arg_c = value.join.scan(/%/).length
136
- lambda do |*args|
137
- unless args.length == arg_c
138
- fail ArgumentError,
139
- "Given #{args.length} args, but #{config_key} requires #{arg_c}"
239
+ def define_printf_helper(method_name, base_hash, arg_count)
240
+ define_singleton_method method_name.to_sym do |*args, **kwargs|
241
+ unless kwargs.empty?
242
+ fail ArgumentError, "#{method_name} requires positional args, not " \
243
+ 'keyword args'
140
244
  end
141
- # Fill in the parameters
142
- result = value.map do |line|
143
- sprintf(line, *args.shift(line.scan(/%/).length))
245
+ unless args.length == arg_count
246
+ fail ArgumentError, 'wrong number of arguments ' \
247
+ "(#{args.length} for #{arg_count})"
144
248
  end
145
- preprocess_value(result)
249
+
250
+ result = {}
251
+ base_hash.each do |k, v|
252
+ if v.is_a?(String)
253
+ v, args = printf_substitutor(v, args)
254
+ elsif v.is_a?(Array)
255
+ output = []
256
+ v.each do |line|
257
+ line, args = printf_substitutor(line, args)
258
+ output.push(line)
259
+ end
260
+ v = output
261
+ end
262
+ result[k] = v
263
+ end
264
+ result
146
265
  end
147
266
  end
148
267
 
149
- # Helper method.
150
- # Converts a regexp-like string (or array thereof) into a proper
151
- # Regexp object (or array thereof)
152
- def preprocess_value(value)
153
- if value.is_a?(Array)
154
- # Recurse!
155
- return value.map { |item| preprocess_value(item) }
156
- elsif value.is_a?(String)
157
- # Some 'Strings' in YAML are actually intended to be regexps
158
- if value[0] == '/' && value[-1] == '/'
159
- # '/foo/' => %r{foo}
160
- return Regexp.new(value[1..-2])
161
- elsif value[0] == '/' && value[-2..-1] == '/i'
162
- # '/foo/i' => %r{foo}i
163
- return Regexp.new(value[1..-3], Regexp::IGNORECASE)
164
- end
268
+ def define_static_helper(method_name, base_hash)
269
+ # rubocop:disable Lint/UnusedBlockArgument
270
+ define_singleton_method method_name.to_sym do |*args, **kwargs|
271
+ base_hash
165
272
  end
166
- value
273
+ # rubocop:enable Lint/UnusedBlockArgument
167
274
  end
168
275
 
169
276
  def convert_to_constant(value)
@@ -187,11 +294,6 @@ module Cisco
187
294
  value
188
295
  end
189
296
 
190
- def test_config_result(value)
191
- result = @hash['test_config_result'][value]
192
- convert_to_constant(result)
193
- end
194
-
195
297
  def method_missing(method_name, *args, &block)
196
298
  if KEYS.include?(method_name.to_s)
197
299
  # ref.foo -> return @hash[foo] or fail IndexError
@@ -223,22 +325,6 @@ module Cisco
223
325
  end
224
326
  end
225
327
 
226
- # Exception class raised when a particular feature/attribute
227
- # is explicitly excluded on the given node.
228
- class UnsupportedError < RuntimeError
229
- def initialize(feature, name, oper=nil, msg=nil)
230
- @feature = feature
231
- @name = name
232
- @oper = oper
233
- message = "Feature '#{feature}'"
234
- message += ", attribute '#{name}'" unless name.nil?
235
- message += ", operation '#{oper}'" unless oper.nil?
236
- message += ' is unsupported on this node'
237
- message += ": #{msg}" unless msg.nil?
238
- super(message)
239
- end
240
- end
241
-
242
328
  # Placeholder for known but explicitly excluded entry
243
329
  # For these, we have an implied default_only value of nil.
244
330
  class UnsupportedCmdRef < CmdRef
@@ -257,21 +343,21 @@ module Cisco
257
343
  @@debug = value # rubocop:disable Style/ClassVars
258
344
  end
259
345
 
260
- attr_reader :cli, :files, :platform, :product_id
346
+ attr_reader :data_formats, :files, :platform, :product_id
261
347
 
262
348
  # Constructor.
263
- # Normal usage is to pass product, platform, cli, in which case usual YAML
264
- # files will be located then the list will be filtered down to only those
265
- # matching the given settings.
349
+ # Normal usage is to pass product, platform, data_formats,
350
+ # in which case usual YAML files will be located then the list
351
+ # will be filtered down to only those matching the given settings.
266
352
  # For testing purposes (only!) you can pass an explicit list of files to
267
353
  # load instead. This list will NOT be filtered further by product_id.
268
- def initialize(product: nil,
269
- platform: nil,
270
- cli: false,
271
- files: nil)
354
+ def initialize(product: nil,
355
+ platform: nil,
356
+ data_formats: [],
357
+ files: nil)
272
358
  @product_id = product
273
359
  @platform = platform
274
- @cli = cli
360
+ @data_formats = data_formats
275
361
  @hash = {}
276
362
  if files
277
363
  @files = files
@@ -285,7 +371,6 @@ module Cisco
285
371
  # Build complete reference hash.
286
372
  def build_cmd_ref
287
373
  # Example id's: N3K-C3048TP-1GE, N3K-C3064PQ-10GE, N7K-C7009, N7K-C7009
288
-
289
374
  debug "Product: #{@product_id}"
290
375
  debug "Files being used: #{@files.join(', ')}"
291
376
 
@@ -297,7 +382,11 @@ module Cisco
297
382
  debug "Feature #{feature} is empty"
298
383
  next
299
384
  end
300
- feature_hash = filter_hash(feature_hash)
385
+ begin
386
+ feature_hash = filter_hash(feature_hash)
387
+ rescue RuntimeError => e
388
+ raise "#{file}: #{e}"
389
+ end
301
390
  if feature_hash.empty?
302
391
  debug "Feature #{feature} is excluded"
303
392
  @hash[feature] = UnsupportedCmdRef.new(feature, nil, file)
@@ -322,6 +411,12 @@ module Cisco
322
411
  end
323
412
  end
324
413
 
414
+ def supports?(feature, property=nil)
415
+ value = @hash[feature]
416
+ value = value[property] if value.is_a?(Hash) && property
417
+ !(value.is_a?(UnsupportedCmdRef) || value.nil?)
418
+ end
419
+
325
420
  # Get the command reference
326
421
  def lookup(feature, name)
327
422
  value = @hash[feature]
@@ -339,15 +434,30 @@ module Cisco
339
434
  puts "DEBUG: #{text}" if @@debug
340
435
  end
341
436
 
342
- KNOWN_FILTERS = %w(cli_nexus)
437
+ KNOWN_PLATFORMS = %w(C3064 C3132 C3172 N3k N5k N6k N7k N8k N9k XRv9k)
438
+
439
+ def self.platform_to_filter(platform)
440
+ if KNOWN_PLATFORMS.include?(platform)
441
+ case platform
442
+ when 'XRv9k'
443
+ /XRV9/
444
+ else
445
+ Regexp.new platform.tr('k', '')
446
+ end
447
+ else
448
+ fail IndexError, "Unknown platform key '#{platform}'"
449
+ end
450
+ end
451
+
452
+ KNOWN_FILTERS = %w(nexus ios_xr cli nxapi_structured)
343
453
 
344
- def self.key_match(key, platform, product_id, cli)
345
- if key[0] == '/' && key[-1] == '/'
346
- # It's a product-id regexp. Does it match our given product_id?
347
- return Regexp.new(key[1..-2]) =~ product_id ? true : false
454
+ def self.key_match(key, platform, product_id, data_formats)
455
+ if KNOWN_PLATFORMS.include?(key)
456
+ return platform_to_filter(key) =~ product_id ? true : false
348
457
  elsif KNOWN_FILTERS.include?(key)
349
- return false if key.match(/cli/) && !cli
350
- return Regexp.new(platform.to_s) =~ key ? true : false
458
+ return true if data_formats && data_formats.include?(key.to_sym)
459
+ return true if key == platform.to_s
460
+ return false
351
461
  else
352
462
  return :unknown
353
463
  end
@@ -355,20 +465,22 @@ module Cisco
355
465
 
356
466
  # Helper method
357
467
  # Given a Hash of command reference data as read from YAML, does:
358
- # - Filter out any API-specific data not applicable to this API
359
- # - Filter any platform-specific data not applicable to this product_id
468
+ # - Delete any platform-specific data not applicable to this platform
469
+ # - Delete any product-specific data not applicable to this product_id
470
+ # - Delete any data-model-specific data not supported by this node
360
471
  # Returns the filtered hash (possibly empty)
361
472
  def self.filter_hash(hash,
362
473
  platform: nil,
363
474
  product_id: nil,
364
- cli: false,
475
+ data_formats: nil,
365
476
  allow_unknown_keys: true)
366
477
  result = {}
367
478
 
368
- exclude = hash.delete('_exclude') || []
479
+ exclude = hash['_exclude'] || []
369
480
  exclude.each do |value|
370
- if key_match(value, platform, product_id, cli) == true
371
- debug 'Exclude this product (#{product_id}, #{value})'
481
+ # We don't allow exclusion by data_format - just platform/product
482
+ if key_match(value, platform, product_id, nil) == true
483
+ debug "Exclude this product (#{product_id}, #{value})"
372
484
  return result
373
485
  end
374
486
  end
@@ -379,10 +491,11 @@ module Cisco
379
491
  regexp_match = false
380
492
 
381
493
  hash.each do |key, value|
494
+ next if key == '_exclude'
382
495
  if CmdRef.keys.include?(key)
383
496
  result[key] = value
384
497
  elsif key != 'else'
385
- match = key_match(key, platform, product_id, cli)
498
+ match = key_match(key, platform, product_id, data_formats)
386
499
  next if match == false
387
500
  if match == :unknown
388
501
  fail "Unrecognized key '#{key}'" unless allow_unknown_keys
@@ -404,9 +517,10 @@ module Cisco
404
517
  result[key] = filter_hash(hash[key],
405
518
  platform: platform,
406
519
  product_id: product_id,
407
- cli: cli,
520
+ data_formats: data_formats,
408
521
  allow_unknown_keys: false)
409
522
  rescue RuntimeError => e
523
+ # Recursively wrap the error as needed to provide context
410
524
  raise "[#{key}]: #{e}"
411
525
  end
412
526
  end
@@ -415,18 +529,17 @@ module Cisco
415
529
 
416
530
  def filter_hash(input_hash)
417
531
  CommandReference.filter_hash(input_hash,
418
- platform: platform,
419
- product_id: product_id,
420
- cli: cli)
532
+ platform: platform,
533
+ product_id: product_id,
534
+ data_formats: data_formats)
421
535
  end
422
536
 
423
537
  # Helper method
424
538
  # Given a suitably filtered Hash of command reference data, does:
425
539
  # - Inherit data from the given base_hash (if any) and extend/override it
426
540
  # with the given input data.
427
- # - Append 'config_set_append' data to any existing 'config_set' data
428
- # - Append 'config_get_token_append' data to 'config_get_token', ditto
429
541
  def self.hash_merge(input_hash, base_hash=nil)
542
+ return base_hash if input_hash.nil?
430
543
  result = base_hash
431
544
  result ||= {}
432
545
  # to_inspect: sub-hashes we want to recurse into
@@ -434,16 +547,11 @@ module Cisco
434
547
 
435
548
  input_hash.each do |key, value|
436
549
  if CmdRef.keys.include?(key)
437
- if key == 'config_set_append'
438
- result['config_set'] = value_append(result['config_set'], value)
439
- elsif key == 'config_get_token_append'
440
- result['config_get_token'] = value_append(
441
- result['config_get_token'], value)
442
- else
443
- result[key] = value
444
- end
550
+ result[key] = value
445
551
  elsif value.is_a?(Hash)
446
552
  to_inspect << value
553
+ elsif value.nil?
554
+ next
447
555
  else
448
556
  fail "Unexpected non-hash data: #{value}"
449
557
  end
@@ -579,7 +687,18 @@ module Cisco
579
687
  end
580
688
 
581
689
  def to_s
582
- @hash.each_value { |names| names.each_value(&:to_s) }
690
+ @num_features ||= @hash.values.length
691
+ @num_attributes ||= @hash.values.inject(0) do |sum, n|
692
+ sum + (n.is_a?(Hash) ? n.values.length : 1)
693
+ end
694
+ "CommandReference describing #{@num_features} features " \
695
+ "with #{@num_attributes} attributes in total"
696
+ end
697
+
698
+ def inspect
699
+ "CommandReference for '#{product_id}' " \
700
+ "(platform:'#{platform}', data formats:#{data_formats}) " \
701
+ "based on #{files.length} files"
583
702
  end
584
703
  end
585
704
  end