cisco_node_utils 1.1.0 → 1.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 (202) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/CHANGELOG.md +126 -1
  4. data/README.md +19 -12
  5. data/Rakefile +1 -0
  6. data/bin/git/hooks/commit-msg/enforce_style +8 -0
  7. data/cisco_node_utils.gemspec +4 -3
  8. data/docs/README-develop-best-practices.md +127 -109
  9. data/docs/README-develop-node-utils-APIs.md +47 -39
  10. data/docs/template-router.rb +3 -7
  11. data/lib/.rubocop.yml +4 -4
  12. data/lib/cisco_node_utils.rb +1 -1
  13. data/lib/cisco_node_utils/aaa_authentication_login.rb +96 -0
  14. data/lib/cisco_node_utils/aaa_authentication_login_service.rb +133 -0
  15. data/lib/cisco_node_utils/aaa_authorization_service.rb +150 -0
  16. data/lib/cisco_node_utils/ace.rb +196 -0
  17. data/lib/cisco_node_utils/acl.rb +100 -0
  18. data/lib/cisco_node_utils/bgp.rb +301 -163
  19. data/lib/cisco_node_utils/bgp_af.rb +187 -19
  20. data/lib/cisco_node_utils/bgp_neighbor.rb +18 -33
  21. data/lib/cisco_node_utils/bgp_neighbor_af.rb +25 -48
  22. data/lib/cisco_node_utils/cisco_cmn_utils.rb +23 -4
  23. data/lib/cisco_node_utils/cmd_ref/README_YAML.md +593 -0
  24. data/lib/cisco_node_utils/cmd_ref/aaa_auth_login_service.yaml +22 -0
  25. data/lib/cisco_node_utils/cmd_ref/aaa_authentication_login.yaml +31 -0
  26. data/lib/cisco_node_utils/cmd_ref/aaa_authorization_service.yaml +22 -0
  27. data/lib/cisco_node_utils/cmd_ref/acl.yaml +43 -0
  28. data/lib/cisco_node_utils/cmd_ref/bgp.yaml +242 -0
  29. data/lib/cisco_node_utils/cmd_ref/bgp_af.yaml +164 -0
  30. data/lib/cisco_node_utils/cmd_ref/bgp_neighbor.yaml +131 -0
  31. data/lib/cisco_node_utils/cmd_ref/bgp_neighbor_af.yaml +179 -0
  32. data/lib/cisco_node_utils/cmd_ref/dnsclient.yaml +34 -0
  33. data/lib/cisco_node_utils/cmd_ref/evpn_vni.yaml +42 -0
  34. data/lib/cisco_node_utils/cmd_ref/fabricpath.yaml +172 -0
  35. data/lib/cisco_node_utils/cmd_ref/fabricpath_topology.yaml +35 -0
  36. data/lib/cisco_node_utils/cmd_ref/feature.yaml +42 -0
  37. data/lib/cisco_node_utils/cmd_ref/fex.yaml +9 -0
  38. data/lib/cisco_node_utils/cmd_ref/images.yaml +7 -0
  39. data/lib/cisco_node_utils/cmd_ref/interface.yaml +339 -0
  40. data/lib/cisco_node_utils/cmd_ref/interface_channel_group.yaml +28 -0
  41. data/lib/cisco_node_utils/cmd_ref/interface_ospf.yaml +61 -0
  42. data/lib/cisco_node_utils/cmd_ref/interface_portchannel.yaml +54 -0
  43. data/lib/cisco_node_utils/cmd_ref/interface_service_vni.yaml +32 -0
  44. data/lib/cisco_node_utils/cmd_ref/inventory.yaml +45 -0
  45. data/lib/cisco_node_utils/cmd_ref/memory.yaml +13 -0
  46. data/lib/cisco_node_utils/cmd_ref/ntp_config.yaml +7 -0
  47. data/lib/cisco_node_utils/cmd_ref/ntp_server.yaml +14 -0
  48. data/lib/cisco_node_utils/cmd_ref/ospf.yaml +74 -0
  49. data/lib/cisco_node_utils/cmd_ref/overlay_global.yaml +33 -0
  50. data/lib/cisco_node_utils/cmd_ref/pim.yaml +40 -0
  51. data/lib/cisco_node_utils/cmd_ref/portchannel_global.yaml +69 -0
  52. data/lib/cisco_node_utils/cmd_ref/radius_global.yaml +25 -0
  53. data/lib/cisco_node_utils/cmd_ref/radius_server.yaml +64 -0
  54. data/lib/cisco_node_utils/cmd_ref/radius_server_group.yaml +14 -0
  55. data/lib/cisco_node_utils/cmd_ref/show_system.yaml +5 -0
  56. data/lib/cisco_node_utils/cmd_ref/show_version.yaml +72 -0
  57. data/lib/cisco_node_utils/cmd_ref/snmp_community.yaml +23 -0
  58. data/lib/cisco_node_utils/cmd_ref/snmp_group.yaml +7 -0
  59. data/lib/cisco_node_utils/cmd_ref/snmp_notification_receiver.yaml +50 -0
  60. data/lib/cisco_node_utils/cmd_ref/snmp_server.yaml +51 -0
  61. data/lib/cisco_node_utils/cmd_ref/snmp_user.yaml +55 -0
  62. data/lib/cisco_node_utils/cmd_ref/snmpnotification.yaml +11 -0
  63. data/lib/cisco_node_utils/cmd_ref/syslog_server.yaml +18 -0
  64. data/lib/cisco_node_utils/cmd_ref/syslog_settings.yaml +7 -0
  65. data/lib/cisco_node_utils/cmd_ref/system.yaml +6 -0
  66. data/lib/cisco_node_utils/cmd_ref/tacacs_server.yaml +49 -0
  67. data/lib/cisco_node_utils/cmd_ref/tacacs_server_group.yaml +33 -0
  68. data/lib/cisco_node_utils/cmd_ref/tacacs_server_host.yaml +35 -0
  69. data/lib/cisco_node_utils/cmd_ref/vdc.yaml +38 -0
  70. data/lib/cisco_node_utils/cmd_ref/virtual_service.yaml +6 -0
  71. data/lib/cisco_node_utils/cmd_ref/vlan.yaml +56 -0
  72. data/lib/cisco_node_utils/cmd_ref/vni.yaml +76 -0
  73. data/lib/cisco_node_utils/cmd_ref/vpc.yaml +197 -0
  74. data/lib/cisco_node_utils/cmd_ref/vrf.yaml +88 -0
  75. data/lib/cisco_node_utils/cmd_ref/vtp.yaml +38 -0
  76. data/lib/cisco_node_utils/cmd_ref/vxlan_vtep.yaml +60 -0
  77. data/lib/cisco_node_utils/cmd_ref/vxlan_vtep_vni.yaml +39 -0
  78. data/lib/cisco_node_utils/cmd_ref/yum.yaml +13 -0
  79. data/lib/cisco_node_utils/command_reference.rb +359 -187
  80. data/lib/cisco_node_utils/configparser_lib.rb +1 -1
  81. data/lib/cisco_node_utils/dns_domain.rb +19 -5
  82. data/lib/cisco_node_utils/domain_name.rb +4 -8
  83. data/lib/cisco_node_utils/evpn_vni.rb +157 -0
  84. data/lib/cisco_node_utils/fabricpath_global.rb +388 -0
  85. data/lib/cisco_node_utils/fabricpath_topology.rb +150 -0
  86. data/lib/cisco_node_utils/feature.rb +111 -0
  87. data/lib/cisco_node_utils/interface.rb +390 -97
  88. data/lib/cisco_node_utils/interface_channel_group.rb +124 -0
  89. data/lib/cisco_node_utils/interface_ospf.rb +11 -34
  90. data/lib/cisco_node_utils/interface_portchannel.rb +157 -0
  91. data/lib/cisco_node_utils/interface_service_vni.rb +132 -0
  92. data/lib/cisco_node_utils/name_server.rb +1 -1
  93. data/lib/cisco_node_utils/node.rb +55 -249
  94. data/lib/cisco_node_utils/node_util.rb +5 -1
  95. data/lib/cisco_node_utils/ntp_config.rb +2 -2
  96. data/lib/cisco_node_utils/ntp_server.rb +14 -5
  97. data/lib/cisco_node_utils/overlay_global.rb +153 -0
  98. data/lib/cisco_node_utils/pim.rb +124 -0
  99. data/lib/cisco_node_utils/pim_group_list.rb +108 -0
  100. data/lib/cisco_node_utils/pim_rp_address.rb +102 -0
  101. data/lib/cisco_node_utils/platform.rb +8 -9
  102. data/lib/cisco_node_utils/portchannel_global.rb +277 -0
  103. data/lib/cisco_node_utils/radius_global.rb +9 -19
  104. data/lib/cisco_node_utils/radius_server.rb +31 -41
  105. data/lib/cisco_node_utils/radius_server_group.rb +117 -0
  106. data/lib/cisco_node_utils/router_ospf.rb +1 -1
  107. data/lib/cisco_node_utils/router_ospf_vrf.rb +14 -19
  108. data/lib/cisco_node_utils/snmp_notification_receiver.rb +158 -0
  109. data/lib/cisco_node_utils/snmpcommunity.rb +3 -5
  110. data/lib/cisco_node_utils/snmpgroup.rb +1 -1
  111. data/lib/cisco_node_utils/snmpnotification.rb +57 -0
  112. data/lib/cisco_node_utils/snmpserver.rb +8 -17
  113. data/lib/cisco_node_utils/snmpuser.rb +67 -28
  114. data/lib/cisco_node_utils/syslog_server.rb +3 -9
  115. data/lib/cisco_node_utils/syslog_settings.rb +2 -10
  116. data/lib/cisco_node_utils/tacacs_server.rb +9 -14
  117. data/lib/cisco_node_utils/tacacs_server_group.rb +145 -0
  118. data/lib/cisco_node_utils/tacacs_server_host.rb +5 -9
  119. data/lib/cisco_node_utils/vdc.rb +88 -0
  120. data/lib/cisco_node_utils/version.rb +5 -2
  121. data/lib/cisco_node_utils/vlan.rb +71 -8
  122. data/lib/cisco_node_utils/vni.rb +227 -0
  123. data/lib/cisco_node_utils/vpc.rb +377 -0
  124. data/lib/cisco_node_utils/vrf.rb +60 -9
  125. data/lib/cisco_node_utils/vrf_af.rb +191 -0
  126. data/lib/cisco_node_utils/vtp.rb +8 -6
  127. data/lib/cisco_node_utils/vxlan_vtep.rb +151 -0
  128. data/lib/cisco_node_utils/vxlan_vtep_vni.rb +234 -0
  129. data/lib/cisco_node_utils/yum.rb +1 -1
  130. data/tests/.rubocop.yml +1 -1
  131. data/tests/basetest.rb +16 -7
  132. data/tests/ciscotest.rb +55 -13
  133. data/tests/cmd_config.yaml +2 -2
  134. data/tests/platform_info.rb +3 -2
  135. data/tests/test_aaa_authentication_login.rb +219 -0
  136. data/tests/test_aaa_authentication_login_service.rb +759 -0
  137. data/tests/test_aaa_authorization_service.rb +1041 -0
  138. data/tests/test_ace.rb +160 -0
  139. data/tests/test_acl.rb +176 -0
  140. data/tests/test_bgp_af.rb +269 -13
  141. data/tests/test_bgp_neighbor.rb +38 -40
  142. data/tests/test_bgp_neighbor_af.rb +92 -32
  143. data/tests/test_command_config.rb +5 -5
  144. data/tests/test_command_reference.rb +284 -101
  145. data/tests/test_dns_domain.rb +1 -1
  146. data/tests/test_domain_name.rb +1 -1
  147. data/tests/test_evpn_vni.rb +106 -0
  148. data/tests/test_fabricpath_global.rb +243 -0
  149. data/tests/test_fabricpath_topology.rb +98 -0
  150. data/tests/test_interface.rb +292 -74
  151. data/tests/test_interface_channel_group.rb +74 -0
  152. data/tests/test_interface_ospf.rb +9 -4
  153. data/tests/test_interface_portchannel.rb +105 -0
  154. data/tests/test_interface_service_vni.rb +232 -0
  155. data/tests/test_interface_svi.rb +77 -62
  156. data/tests/test_interface_switchport.rb +17 -5
  157. data/tests/test_name_server.rb +1 -1
  158. data/tests/test_node.rb +1 -1
  159. data/tests/test_node_ext.rb +10 -20
  160. data/tests/test_ntp_config.rb +1 -1
  161. data/tests/test_ntp_server.rb +18 -6
  162. data/tests/test_overlay_global.rb +102 -0
  163. data/tests/test_pim.rb +177 -0
  164. data/tests/test_pim_group_list.rb +181 -0
  165. data/tests/test_pim_rp_address.rb +153 -0
  166. data/tests/test_platform.rb +3 -3
  167. data/tests/test_portchannel_global.rb +202 -0
  168. data/tests/test_radius_global.rb +1 -1
  169. data/tests/test_radius_server.rb +92 -57
  170. data/tests/test_radius_server_group.rb +149 -0
  171. data/tests/test_router_bgp.rb +283 -112
  172. data/tests/test_router_ospf.rb +2 -2
  173. data/tests/test_router_ospf_vrf.rb +4 -4
  174. data/tests/test_snmp_notification_receiver.rb +167 -0
  175. data/tests/test_snmpcommunity.rb +1 -1
  176. data/tests/test_snmpgroup.rb +1 -1
  177. data/tests/test_snmpnotification.rb +72 -0
  178. data/tests/test_snmpserver.rb +29 -105
  179. data/tests/test_snmpuser.rb +32 -30
  180. data/tests/test_syslog_server.rb +36 -10
  181. data/tests/test_syslog_settings.rb +1 -1
  182. data/tests/test_tacacs_server.rb +1 -1
  183. data/tests/test_tacacs_server_group.rb +405 -0
  184. data/tests/test_tacacs_server_host.rb +1 -1
  185. data/tests/test_vdc.rb +78 -0
  186. data/tests/test_vlan.rb +74 -19
  187. data/tests/test_vlan_mt_full.rb +95 -0
  188. data/tests/test_vni.rb +106 -0
  189. data/tests/test_vpc.rb +361 -0
  190. data/tests/test_vrf.rb +172 -29
  191. data/tests/test_vtp.rb +1 -1
  192. data/tests/test_vxlan_vtep.rb +214 -0
  193. data/tests/test_vxlan_vtep_vni.rb +201 -0
  194. data/tests/test_yum.rb +1 -1
  195. metadata +120 -11
  196. data/lib/cisco_node_utils/README_YAML.md +0 -325
  197. data/lib/cisco_node_utils/command_reference_common.yaml +0 -1051
  198. data/lib/cisco_node_utils/command_reference_common_bgp.yaml +0 -535
  199. data/lib/cisco_node_utils/command_reference_n3064.yaml +0 -13
  200. data/lib/cisco_node_utils/command_reference_n7k.yaml +0 -52
  201. data/lib/cisco_node_utils/command_reference_n9k.yaml +0 -26
  202. data/tests/platform_info.yaml +0 -10
@@ -0,0 +1,88 @@
1
+ # vrf.yaml
2
+ ---
3
+ _template:
4
+ config_get: "show running all | section '^vrf context'"
5
+ config_get_token: '/^vrf context <vrf>$/'
6
+ config_get_token_append:
7
+ - '/^address-family <afi> <safi>$/'
8
+ config_set: 'vrf context <vrf>'
9
+ config_set_append:
10
+ - 'address-family <afi> <safi>'
11
+
12
+ address_family:
13
+ config_set: ['vrf context <vrf>', '<state> address-family <afi> <safi>']
14
+
15
+ all_vrf_afs:
16
+ multiple: true
17
+ config_get_token_append: '/^address-family (\S+) (\S+)$/'
18
+
19
+ all_vrfs:
20
+ multiple: true
21
+ config_get_token: '/^vrf context (.*)/'
22
+
23
+ create:
24
+ config_set: 'vrf context <vrf>'
25
+
26
+ description:
27
+ config_get_token_append: '/^description (.*)/'
28
+ config_set_append: '<state> description <desc>'
29
+ kind: string
30
+ default_value: ''
31
+
32
+ destroy:
33
+ config_set: 'no vrf context <vrf>'
34
+
35
+ route_distinguisher:
36
+ config_get_token_append: '/^rd (\S+)$/'
37
+ config_set_append: '<state> rd <rd>'
38
+ default_value: ''
39
+
40
+ route_target_both_auto:
41
+ kind: boolean
42
+ config_get_token_append: '/^route-target both auto$/'
43
+ config_set_append: '<state> route-target both auto'
44
+ default_value: false
45
+
46
+ route_target_both_auto_evpn:
47
+ kind: boolean
48
+ config_get_token_append: '/^route-target both auto evpn$/'
49
+ config_set_append: '<state> route-target both auto evpn'
50
+ default_value: false
51
+
52
+ route_target_export:
53
+ multiple: true
54
+ config_get_token_append: '/^route-target export (\S+)$/'
55
+ config_set_append: '<state> route-target export <community>'
56
+ default_value: []
57
+
58
+ route_target_export_evpn:
59
+ multiple: true
60
+ config_get_token_append: '/^route-target export (\S+) evpn$/'
61
+ config_set_append: '<state> route-target export <community> evpn'
62
+ default_value: []
63
+
64
+ route_target_import:
65
+ multiple: true
66
+ config_get_token_append: '/^route-target import (\S+)$/'
67
+ config_set_append: '<state> route-target import <community>'
68
+ default_value: []
69
+
70
+ route_target_import_evpn:
71
+ multiple: true
72
+ config_get_token_append: '/^route-target import (\S+) evpn$/'
73
+ config_set_append: '<state> route-target import <community> evpn'
74
+ default_value: []
75
+
76
+ shutdown:
77
+ kind: boolean
78
+ config_get_token_append: '/^shutdown$/'
79
+ config_set_append: '<state> shutdown'
80
+ default_value: false
81
+
82
+ vni: # TBD Should this move to the vni provider as vrf_vni?
83
+ # MT-lite only
84
+ /N9/:
85
+ kind: int
86
+ config_get_token_append: '/^vni (\d+)$/'
87
+ config_set_append: '<state> vni <id>'
88
+ default_value: false
@@ -0,0 +1,38 @@
1
+ # vtp
2
+ ---
3
+ domain:
4
+ config_get: "show vtp status"
5
+ config_get_token: "domain_name"
6
+ config_set: "vtp domain %s"
7
+
8
+ feature:
9
+ kind: boolean
10
+ config_get: "show running vtp"
11
+ config_get_token: '/^feature vtp$/'
12
+ config_set: "%s feature vtp"
13
+ default_value: false
14
+
15
+ filename:
16
+ config_get: "show running vtp"
17
+ config_get_token: '/vtp file (\S+)/'
18
+ config_set: "%s vtp file %s"
19
+ default_value: "bootflash:/vlan.dat"
20
+
21
+ password:
22
+ config_get: "show vtp password"
23
+ config_get_token: "passwd"
24
+ config_set: "%s vtp password %s"
25
+ default_value: ""
26
+
27
+ version:
28
+ kind: int
29
+ config_get: "show vtp status"
30
+ config_get_token: '/VTP\s+version\s+running\s+:\s+(\d+)/'
31
+ config_set: "vtp version %s"
32
+ default_value: 1
33
+ /N7/:
34
+ test_config_result:
35
+ 3: 3
36
+ else:
37
+ test_config_result:
38
+ 3: "Cisco::CliError"
@@ -0,0 +1,60 @@
1
+ # vxlan_vtep
2
+ ---
3
+ _exclude: [/N(3|5|6)/]
4
+
5
+ _template:
6
+ config_get: "show running-config interface all | section 'interface nve'"
7
+ config_get_token: '/^interface <name>$/i'
8
+ config_set: 'interface <name>'
9
+
10
+ all_interfaces:
11
+ multiple:
12
+ config_get_token: '/^interface (.*)$/'
13
+
14
+ host_reachability:
15
+ /N(7|9)/:
16
+ config_get_token_append: '/^host-reachability protocol (\S+)/'
17
+ config_set_append: '<state> host-reachability protocol <proto>'
18
+ default_value: 'flood'
19
+
20
+ mt_full_support:
21
+ # This is only used for determining support for Multi-Tenancy Full
22
+ kind: boolean
23
+ /N(7)/:
24
+ default_only: true
25
+ else:
26
+ # this feature is always off on these platforms and cannot be changed
27
+ default_only: false
28
+
29
+ mt_lite_support:
30
+ # This is only used for determining support for Multi-Tenancy Lite
31
+ kind: boolean
32
+ /N9/:
33
+ default_only: true
34
+ else:
35
+ # this feature is always off on these platforms and cannot be changed
36
+ default_only: false
37
+
38
+ shutdown:
39
+ kind: boolean
40
+ config_get_token_append: '/^no shutdown$/'
41
+ config_set_append: '<state> shutdown'
42
+ default_value: true
43
+
44
+ source_intf:
45
+ config_get_token_append: '/^source\-interface (\S+)$/'
46
+ config_set_append: '<state> source-interface <lpbk_intf>'
47
+ default_value: ''
48
+
49
+ vni:
50
+ config_set_append: '<state> member vni <vni> <assoc_vrf>'
51
+
52
+ vni_with_vrf:
53
+ kind: boolean
54
+ config_get_token_append: '/^member vni <vni> associate-vrf$/'
55
+ default_value: false
56
+
57
+ vni_without_vrf:
58
+ kind: boolean
59
+ config_get_token_append: '/^member vni <vni>$/'
60
+ default_value: false
@@ -0,0 +1,39 @@
1
+ # vxlan_vtep_vni
2
+ ---
3
+ _exclude: [/N(3|5|6)/]
4
+
5
+ _template:
6
+ config_get: "show running-config interface all | section 'interface nve'"
7
+ config_get_token: '/^interface <name>$/i'
8
+ config_get_token_append:
9
+ - '/^member vni <vni> ?(associate-vrf)?$/'
10
+ config_set: 'interface <name>'
11
+ config_set_append:
12
+ - 'member vni <vni> <assoc_vrf>'
13
+
14
+ all_vnis:
15
+ multiple:
16
+ config_get_token_append: '/^member vni (\d+|\d+-\d+) ?(associate-vrf)?$/'
17
+
18
+ ingress_replication:
19
+ kind: string
20
+ config_get_token_append: '/^ingress-replication protocol (\S+)$/'
21
+ config_set_append: '<state> ingress-replication protocol <protocol>'
22
+ default_value: ''
23
+
24
+ multicast_group:
25
+ config_get_token_append: '/^mcast-group (\S+)\s?(\S+)?$/'
26
+ config_set_append: '<state> mcast-group <ip_start> <ip_end>'
27
+ default_value: ''
28
+
29
+ peer_list:
30
+ multiple:
31
+ config_get_token_append: ['/^ingress-replication protocol static$/', '/^peer-ip (\S+)$/']
32
+ config_set_append: ['ingress-replication protocol static', '<state> peer-ip <peer>']
33
+ default_value: []
34
+
35
+ suppress_arp:
36
+ kind: boolean
37
+ config_get_token_append: '/^suppress-arp$/'
38
+ config_set_append: '<state> suppress-arp'
39
+ default_value: false
@@ -0,0 +1,13 @@
1
+ # yum
2
+ ---
3
+ install:
4
+ config_set: "install add %s %s activate"
5
+
6
+ query:
7
+ multiple: true
8
+ config_get: "show install packages"
9
+ # pass in the pkg name, retrieve version
10
+ config_get_token: '/^%s\S*\s+(\S+)\s+(?:installed|@\S+)/'
11
+
12
+ remove:
13
+ config_set: "install deactivate %s"
@@ -1,6 +1,6 @@
1
1
  # CommandReference module for testing.
2
2
  #
3
- # Copyright (c) 2014-2015 Cisco and/or its affiliates.
3
+ # Copyright (c) 2014-2016 Cisco and/or its affiliates.
4
4
  #
5
5
  # Licensed under the Apache License, Version 2.0 (the "License");
6
6
  # you may not use this file except in compliance with the License.
@@ -16,81 +16,154 @@
16
16
 
17
17
  require 'yaml'
18
18
 
19
- module CommandReference
20
- # Helper class to match product id with reference files.
21
- class CommandPlatformFile
22
- attr_reader :regex, :file
23
-
24
- def initialize(match_expression, reference_file)
25
- self.regex = match_expression
26
- self.file = reference_file
27
- end
28
-
29
- def regex=(expression)
30
- if expression.is_a? Regexp
31
- @regex = expression
32
- else
33
- fail ArgumentError
34
- end
35
- end
36
-
37
- def file=(file)
38
- if file.is_a? String
39
- @file = file
40
- else
41
- fail ArgumentError
42
- end
43
- end
44
-
45
- def match(product)
46
- @regex.match(product)
47
- end
48
- end
49
-
19
+ module Cisco
50
20
  # Control a reference for an attribute.
51
21
  class CmdRef
52
- attr_reader :feature
53
- attr_reader :name
54
- attr_reader :sources
55
- attr_reader :hash
22
+ attr_reader :feature, :name, :hash
23
+ attr_reader :auto_default, :multiple, :kind, :default_only
24
+ alias_method :auto_default?, :auto_default
25
+ alias_method :default_only?, :default_only
26
+ alias_method :multiple?, :multiple
27
+
28
+ 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)
33
+
34
+ def self.keys
35
+ KEYS
36
+ end
56
37
 
57
- # rubocop:disable Style/ClassVars
58
- @@keys = %w(default_value
59
- config_set config_set_append
60
- config_get config_get_token config_get_token_append
61
- test_config_get test_config_get_regex test_config_result)
62
- # rubocop:enable Style/ClassVars
38
+ KINDS = %w(boolean int string)
63
39
 
64
- def initialize(feature, name, ref, source)
65
- fail ArgumentError, "'#{ref}' is not a hash." unless ref.is_a? Hash
40
+ # Construct a CmdRef describing the given (feature, name) pair.
41
+ # Param "values" is a hash with keys as described in KEYS.
42
+ # Param "file" is for debugging purposes only.
43
+ def initialize(feature, name, values, file)
44
+ fail ArgumentError, "'#{values}' is not a hash." unless values.is_a? Hash
66
45
 
67
46
  @feature = feature
68
47
  @name = name
69
48
  @hash = {}
49
+ @auto_default = true
50
+ @default_only = false
51
+ @multiple = false
52
+ @kind = nil
70
53
 
71
- @sources = []
72
- merge(ref, source)
73
- end
74
-
75
- # Overwrite values from more specific references.
76
- def merge(values, file)
77
54
  values.each do |key, value|
78
- unless @@keys.include?(key)
55
+ unless KEYS.include?(key)
79
56
  fail "Unrecognized key #{key} for #{feature}, #{name} in #{file}"
80
57
  end
81
- if value.nil?
82
- # Some attributes can store an explicit nil.
83
- # Others treat this as unset (allowing a platform to override common).
84
- if key == 'default_value'
85
- @hash[key] = value
86
- else
87
- @hash.delete(key)
88
- 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'
65
+ @auto_default = value ? true : false
66
+ elsif key == 'default_only'
67
+ @default_only = true
68
+ # default_value overrides default_only
69
+ @hash['default_value'] ||= preprocess_value(value)
70
+ elsif key == 'multiple'
71
+ @multiple = boolean_default_true(value)
72
+ elsif key == 'kind'
73
+ fail "Unknown 'kind': '#{value}'" unless KINDS.include?(value)
74
+ @kind = value.to_sym
89
75
  else
90
- @hash[key] = value
76
+ # default_value overrides default_only
77
+ @default_only = false if key == 'default_value'
78
+ @hash[key] = preprocess_value(value)
79
+ end
80
+ end
81
+
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' }
87
+ end
88
+ end
89
+
90
+ # Property with an implicit value of 'true' if no value is given
91
+ def boolean_default_true(value)
92
+ value.nil? || value
93
+ end
94
+
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))
105
+ else
106
+ # simple static token(s)
107
+ value = preprocess_value(value)
108
+ define_singleton_method key.to_sym, -> { value }
109
+ end
110
+ end
111
+
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)
123
+ end
124
+ if result.empty?
125
+ fail ArgumentError,
126
+ "Arguments given to #{config_key} yield empty result"
127
+ end
128
+ preprocess_value(result)
129
+ end
130
+ end
131
+
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}"
140
+ end
141
+ # Fill in the parameters
142
+ result = value.map do |line|
143
+ sprintf(line, *args.shift(line.scan(/%/).length))
91
144
  end
145
+ preprocess_value(result)
92
146
  end
93
- @sources << file
147
+ end
148
+
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
165
+ end
166
+ value
94
167
  end
95
168
 
96
169
  def convert_to_constant(value)
@@ -104,7 +177,11 @@ module CommandReference
104
177
  # to a Constant.
105
178
  if value.is_a?(String) && !value.empty?
106
179
  if value[0].chr == value[0].chr.upcase
107
- value = Object.const_get(value) if Object.const_defined?(value)
180
+ begin
181
+ value = Object.const_get(value) if Object.const_defined?(value)
182
+ rescue NameError
183
+ debug("'#{value}' is not a constant")
184
+ end
108
185
  end
109
186
  end
110
187
  value
@@ -116,13 +193,25 @@ module CommandReference
116
193
  end
117
194
 
118
195
  def method_missing(method_name, *args, &block)
119
- super(method_name, *args, &block) unless @@keys.include?(method_name.to_s)
120
- method_name = method_name.to_s
121
- unless @hash.include?(method_name)
122
- fail IndexError, "No #{method_name} defined for #{@feature}, #{@name}"
196
+ if KEYS.include?(method_name.to_s)
197
+ # ref.foo -> return @hash[foo] or fail IndexError
198
+ method_name = method_name.to_s
199
+ unless @hash.include?(method_name)
200
+ if @default_only
201
+ fail UnsupportedError.new(@feature, @name, method_name)
202
+ end
203
+ fail IndexError, "No #{method_name} defined for #{@feature}, #{@name}"
204
+ end
205
+ # puts("get #{method_name}: '#{@hash[method_name]}'")
206
+ @hash[method_name]
207
+ elsif method_name.to_s[-1] == '?' && \
208
+ KEYS.include?(method_name.to_s[0..-2])
209
+ # ref.foo? -> return true if @hash[foo], else false
210
+ method_name = method_name.to_s[0..-2]
211
+ @hash.include?(method_name)
212
+ else
213
+ super(method_name, *args, &block)
123
214
  end
124
- # puts("get #{method_name}: '#{@hash[method_name]}'")
125
- @hash[method_name]
126
215
  end
127
216
 
128
217
  # Print useful debugging information about the object.
@@ -132,11 +221,29 @@ module CommandReference
132
221
  @hash.each { |key, value| str << " #{key}: #{value}\n" }
133
222
  str
134
223
  end
224
+ end
135
225
 
136
- # Check that all necessary values have been populated.
137
- def valid?
138
- return false unless @feature && @name
139
- true
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
+ # Placeholder for known but explicitly excluded entry
243
+ # For these, we have an implied default_only value of nil.
244
+ class UnsupportedCmdRef < CmdRef
245
+ def initialize(feature, name, file)
246
+ super(feature, name, { 'default_only' => nil }, file)
140
247
  end
141
248
  end
142
249
 
@@ -150,46 +257,26 @@ module CommandReference
150
257
  @@debug = value # rubocop:disable Style/ClassVars
151
258
  end
152
259
 
153
- attr_reader :files, :product_id
260
+ attr_reader :cli, :files, :platform, :product_id
154
261
 
155
262
  # Constructor.
156
- # Normal usage is to pass product_id only, in which case all usual YAML
263
+ # Normal usage is to pass product, platform, cli, in which case usual YAML
157
264
  # files will be located then the list will be filtered down to only those
158
- # matching the given product_id.
265
+ # matching the given settings.
159
266
  # For testing purposes (only!) you can pass an explicit list of files to
160
267
  # load instead. This list will NOT be filtered further by product_id.
161
- def initialize(product_id, files=nil)
162
- @product_id = product_id
268
+ def initialize(product: nil,
269
+ platform: nil,
270
+ cli: false,
271
+ files: nil)
272
+ @product_id = product
273
+ @platform = platform
274
+ @cli = cli
163
275
  @hash = {}
164
276
  if files
165
277
  @files = files
166
278
  else
167
- @files = []
168
- # Hashes are unordered in Ruby 1.8.7, so instead, we use an array
169
- # of objects.
170
- # rubocop:disable Metrics/LineLength
171
- platforms = [
172
- CommandPlatformFile.new(//,
173
- File.join(File.dirname(__FILE__),
174
- 'command_reference_common.yaml')),
175
- CommandPlatformFile.new(//,
176
- File.join(File.dirname(__FILE__),
177
- 'command_reference_common_bgp.yaml')),
178
- CommandPlatformFile.new(/N9K/,
179
- File.join(File.dirname(__FILE__),
180
- 'command_reference_n9k.yaml')),
181
- CommandPlatformFile.new(/N7K/,
182
- File.join(File.dirname(__FILE__),
183
- 'command_reference_n7k.yaml')),
184
- CommandPlatformFile.new(/C3064/,
185
- File.join(File.dirname(__FILE__),
186
- 'command_reference_n3064.yaml')),
187
- ]
188
- # rubocop:enable Metrics/LineLength
189
- # Build array
190
- platforms.each do |reference|
191
- @files << reference.file if reference.match(@product_id)
192
- end
279
+ @files = Dir.glob(__dir__ + '/cmd_ref/*.yaml')
193
280
  end
194
281
 
195
282
  build_cmd_ref
@@ -202,48 +289,43 @@ module CommandReference
202
289
  debug "Product: #{@product_id}"
203
290
  debug "Files being used: #{@files.join(', ')}"
204
291
 
205
- reference_yaml = {}
206
-
207
292
  @files.each do |file|
208
- debug "Processing file '#{file}'"
209
- reference_yaml = load_yaml(file)
210
-
211
- reference_yaml.each do |feature, names|
212
- if names.nil? || names.empty?
213
- fail "No names under feature #{feature}: #{names}"
214
- elsif @hash[feature].nil?
215
- @hash[feature] = {}
293
+ feature = File.basename(file).split('.')[0]
294
+ debug "Processing file '#{file}' as feature '#{feature}'"
295
+ feature_hash = load_yaml(file)
296
+ if feature_hash.empty?
297
+ debug "Feature #{feature} is empty"
298
+ next
299
+ end
300
+ feature_hash = filter_hash(feature_hash)
301
+ if feature_hash.empty?
302
+ debug "Feature #{feature} is excluded"
303
+ @hash[feature] = UnsupportedCmdRef.new(feature, nil, file)
304
+ next
305
+ end
306
+
307
+ base_hash = {}
308
+ if feature_hash.key?('_template')
309
+ base_hash = CommandReference.hash_merge(feature_hash['_template'])
310
+ end
311
+
312
+ feature_hash.each do |name, value|
313
+ fail "No entries under '#{name}' in '#{file}'" if value.nil?
314
+ @hash[feature] ||= {}
315
+ if value.empty?
316
+ @hash[feature][name] = UnsupportedCmdRef.new(feature, name, file)
216
317
  else
217
- debug " Merging feature '#{feature}' retrieved from '#{file}'."
218
- end
219
- names.each do |name, values|
220
- debug " Processing feature '#{feature}' name '#{name}'"
221
- if @hash[feature][name].nil?
222
- begin
223
- @hash[feature][name] = CmdRef.new(feature, name, values, file)
224
- rescue ArgumentError => e
225
- raise "Invalid data for '#{feature}', '#{name}': #{e}"
226
- end
227
- else
228
- debug " Merging feature '#{feature}' name '#{name}' " \
229
- "from '#{file}'."
230
- @hash[feature][name].merge(values, file)
231
- end
318
+ values = CommandReference.hash_merge(value, base_hash.clone)
319
+ @hash[feature][name] = CmdRef.new(feature, name, values, file)
232
320
  end
233
321
  end
234
322
  end
235
-
236
- fail 'Missing values in CommandReference.' unless valid?
237
323
  end
238
324
 
239
325
  # Get the command reference
240
326
  def lookup(feature, name)
241
- begin
242
- value = @hash[feature][name]
243
- rescue NoMethodError
244
- # happens if @hash[feature] doesn't exist
245
- value = nil
246
- end
327
+ value = @hash[feature]
328
+ value = value[name] if value.is_a? Hash
247
329
  fail IndexError, "No CmdRef defined for #{feature}, #{name}" if value.nil?
248
330
  value
249
331
  end
@@ -257,24 +339,143 @@ module CommandReference
257
339
  puts "DEBUG: #{text}" if @@debug
258
340
  end
259
341
 
342
+ KNOWN_FILTERS = %w(cli_nexus)
343
+
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
348
+ elsif KNOWN_FILTERS.include?(key)
349
+ return false if key.match(/cli/) && !cli
350
+ return Regexp.new(platform.to_s) =~ key ? true : false
351
+ else
352
+ return :unknown
353
+ end
354
+ end
355
+
356
+ # Helper method
357
+ # 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
360
+ # Returns the filtered hash (possibly empty)
361
+ def self.filter_hash(hash,
362
+ platform: nil,
363
+ product_id: nil,
364
+ cli: false,
365
+ allow_unknown_keys: true)
366
+ result = {}
367
+
368
+ exclude = hash.delete('_exclude') || []
369
+ exclude.each do |value|
370
+ if key_match(value, platform, product_id, cli) == true
371
+ debug 'Exclude this product (#{product_id}, #{value})'
372
+ return result
373
+ end
374
+ end
375
+
376
+ # to_inspect: sub-keys we want to recurse into
377
+ to_inspect = []
378
+ # regexp_match: did we find a product_id regexp that matches?
379
+ regexp_match = false
380
+
381
+ hash.each do |key, value|
382
+ if CmdRef.keys.include?(key)
383
+ result[key] = value
384
+ elsif key != 'else'
385
+ match = key_match(key, platform, product_id, cli)
386
+ next if match == false
387
+ if match == :unknown
388
+ fail "Unrecognized key '#{key}'" unless allow_unknown_keys
389
+ end
390
+ regexp_match = true if match == true
391
+ to_inspect << key
392
+ end
393
+ end
394
+ # If we didn't find any platform regexp match,
395
+ # and an 'else' sub-hash is provided, descend into 'else'
396
+ to_inspect << 'else' if hash.key?('else') && !regexp_match
397
+ # Recurse! Sub-hashes can override the base hash
398
+ to_inspect.each do |key|
399
+ unless hash[key].is_a?(Hash)
400
+ result[key] = hash[key]
401
+ next
402
+ end
403
+ begin
404
+ result[key] = filter_hash(hash[key],
405
+ platform: platform,
406
+ product_id: product_id,
407
+ cli: cli,
408
+ allow_unknown_keys: false)
409
+ rescue RuntimeError => e
410
+ raise "[#{key}]: #{e}"
411
+ end
412
+ end
413
+ result
414
+ end
415
+
416
+ def filter_hash(input_hash)
417
+ CommandReference.filter_hash(input_hash,
418
+ platform: platform,
419
+ product_id: product_id,
420
+ cli: cli)
421
+ end
422
+
423
+ # Helper method
424
+ # Given a suitably filtered Hash of command reference data, does:
425
+ # - Inherit data from the given base_hash (if any) and extend/override it
426
+ # 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
+ def self.hash_merge(input_hash, base_hash=nil)
430
+ result = base_hash
431
+ result ||= {}
432
+ # to_inspect: sub-hashes we want to recurse into
433
+ to_inspect = []
434
+
435
+ input_hash.each do |key, value|
436
+ 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
445
+ elsif value.is_a?(Hash)
446
+ to_inspect << value
447
+ else
448
+ fail "Unexpected non-hash data: #{value}"
449
+ end
450
+ end
451
+ # Recurse! Sub-hashes can override the base hash
452
+ to_inspect.each do |hash|
453
+ result = hash_merge(hash, result)
454
+ end
455
+ result
456
+ end
457
+
458
+ # Helper method.
459
+ # Combines the two given values (either or both of which may be arrays)
460
+ # into a single combined array
461
+ # value_append('foo', 'bar') ==> ['foo', 'bar']
462
+ # value_append('foo', ['bar', 'baz']) ==> ['foo', 'bar', 'baz']
463
+ def self.value_append(base_value, new_value)
464
+ base_value = [base_value] unless base_value.is_a?(Array)
465
+ new_value = [new_value] unless new_value.is_a?(Array)
466
+ base_value + new_value
467
+ end
468
+
260
469
  def mapping?(node)
261
- # Need to handle both Syck::Map and Psych::Nodes::Mapping
262
470
  node.class.ancestors.any? { |name| /Map/ =~ name.to_s }
263
471
  end
264
472
  private :mapping?
265
473
 
266
474
  def get_keys_values_from_map(node)
267
- if node.class.ancestors.any? { |name| /Psych/ =~ name.to_s }
268
- # A Psych::Node::Mapping instance has an Array of children in
269
- # the format [key1, val1, key2, val2]
270
- key_children = node.children.select.each_with_index { |_, i| i.even? }
271
- val_children = node.children.select.each_with_index { |_, i| i.odd? }
272
- else
273
- # Syck::Map nodes have a .children method but it doesn't work :-(
274
- # Instead we access the node.value which is a hash.
275
- key_children = node.value.keys
276
- val_children = node.value.values
277
- end
475
+ # A Psych::Node::Mapping instance has an Array of children in
476
+ # the format [key1, val1, key2, val2]
477
+ key_children = node.children.select.each_with_index { |_, i| i.even? }
478
+ val_children = node.children.select.each_with_index { |_, i| i.odd? }
278
479
  debug "children of #{node} mapping: #{key_children}, #{val_children}"
279
480
  [key_children, val_children]
280
481
  end
@@ -291,9 +492,7 @@ module CommandReference
291
492
  # @param parents String describing parents of this node, for messages
292
493
  def validate_yaml(node, filename, depth=0, parents=nil)
293
494
  return unless node && (mapping?(node) || node.children)
294
- # Psych wraps everything in a Document instance, while
295
- # Syck does not. To keep the "depth" counting consistent,
296
- # we need to ignore Documents.
495
+ # Psych wraps everything in a Document instance, which we ignore.
297
496
  unless node.class.ancestors.any? { |name| /Document/ =~ name.to_s }
298
497
  depth += 1
299
498
  end
@@ -308,7 +507,7 @@ module CommandReference
308
507
  end
309
508
 
310
509
  # For Mappings, we validate more extensively:
311
- # 1. no duplicate keys are allowed (Syck/Psych don't catch this)
510
+ # 1. no duplicate keys are allowed (Psych doesn't catch this)
312
511
  # 2. Features must be listed in alphabetical order for maintainability
313
512
 
314
513
  # Take advantage of our known YAML structure to assign labels by depth
@@ -329,10 +528,6 @@ module CommandReference
329
528
  fail msg
330
529
  end
331
530
 
332
- =begin
333
- # Syck does not necessarily preserve ordering of keys in a mapping even during
334
- # the initial parsing stage. To avoid spurious failures, this is disabled
335
- # for now. Fixing this may require restructuring our YAML...
336
531
  # Enforce alphabetical ordering of features (only).
337
532
  # We can extend this later to enforce ordering of names if desired
338
533
  # by checking at depth 2 as well.
@@ -340,12 +535,11 @@ module CommandReference
340
535
  last_key = nil
341
536
  key_arr.each do |key|
342
537
  if last_key && key < last_key
343
- raise RuntimeError, "features out of order (#{last_key} > #{key})"
538
+ fail "features out of order in #{filename}: (#{last_key} > #{key})"
344
539
  end
345
540
  last_key = key
346
541
  end
347
542
  end
348
- =end
349
543
 
350
544
  # Recurse to the children. We get a little fancy here so as to be able
351
545
  # to provide more meaningful debug/error messages, such as:
@@ -363,16 +557,13 @@ module CommandReference
363
557
  private :validate_yaml
364
558
 
365
559
  # Read in yaml file.
560
+ # The expectation is that a file corresponds to a feature
366
561
  def load_yaml(yaml_file)
367
562
  fail "File #{yaml_file} doesn't exist." unless File.exist?(yaml_file)
368
563
  # Parse YAML file into a tree of nodes
369
564
  # Psych::SyntaxError doesn't inherit from StandardError in some versions,
370
565
  # so we want to explicitly catch it if using Psych.
371
- if defined?(::Psych::SyntaxError)
372
- rescue_errors = [::StandardError, ::Psych::SyntaxError]
373
- else
374
- rescue_errors = [::StandardError]
375
- end
566
+ rescue_errors = [::StandardError, ::Psych::SyntaxError]
376
567
  yaml_parsed = File.open(yaml_file, 'r') do |f|
377
568
  begin
378
569
  YAML.parse(f)
@@ -380,30 +571,11 @@ module CommandReference
380
571
  raise "unable to parse #{yaml_file}: #{e}"
381
572
  end
382
573
  end
383
- if yaml_parsed
384
- # Validate the node tree
385
- validate_yaml(yaml_parsed, yaml_file)
386
- # If validation passed, convert the node tree to a Ruby Hash.
387
- return yaml_parsed.transform
388
- else
389
- # if yaml_file is empty, YAML.parse() returns false.
390
- # Change this to an empty hash.
391
- return {}
392
- end
393
- end
394
-
395
- # Check that all resources were pulled in correctly.
396
- def valid?
397
- complete_status = true
398
- @hash.each_value do |names|
399
- names.each_value do |ref|
400
- status = ref.valid?
401
- debug('Reference does not contain all supported values:' \
402
- "\n#{ref}") unless status
403
- complete_status = (status && complete_status)
404
- end
405
- end
406
- complete_status
574
+ return {} unless yaml_parsed
575
+ # Validate the node tree
576
+ validate_yaml(yaml_parsed, yaml_file)
577
+ # If validation passed, convert the node tree to a Ruby Hash.
578
+ yaml_parsed.transform
407
579
  end
408
580
 
409
581
  def to_s