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.
- checksums.yaml +4 -4
- data/.gitignore +2 -1
- data/CHANGELOG.md +126 -1
- data/README.md +19 -12
- data/Rakefile +1 -0
- data/bin/git/hooks/commit-msg/enforce_style +8 -0
- data/cisco_node_utils.gemspec +4 -3
- data/docs/README-develop-best-practices.md +127 -109
- data/docs/README-develop-node-utils-APIs.md +47 -39
- data/docs/template-router.rb +3 -7
- data/lib/.rubocop.yml +4 -4
- data/lib/cisco_node_utils.rb +1 -1
- data/lib/cisco_node_utils/aaa_authentication_login.rb +96 -0
- data/lib/cisco_node_utils/aaa_authentication_login_service.rb +133 -0
- data/lib/cisco_node_utils/aaa_authorization_service.rb +150 -0
- data/lib/cisco_node_utils/ace.rb +196 -0
- data/lib/cisco_node_utils/acl.rb +100 -0
- data/lib/cisco_node_utils/bgp.rb +301 -163
- data/lib/cisco_node_utils/bgp_af.rb +187 -19
- data/lib/cisco_node_utils/bgp_neighbor.rb +18 -33
- data/lib/cisco_node_utils/bgp_neighbor_af.rb +25 -48
- data/lib/cisco_node_utils/cisco_cmn_utils.rb +23 -4
- data/lib/cisco_node_utils/cmd_ref/README_YAML.md +593 -0
- data/lib/cisco_node_utils/cmd_ref/aaa_auth_login_service.yaml +22 -0
- data/lib/cisco_node_utils/cmd_ref/aaa_authentication_login.yaml +31 -0
- data/lib/cisco_node_utils/cmd_ref/aaa_authorization_service.yaml +22 -0
- data/lib/cisco_node_utils/cmd_ref/acl.yaml +43 -0
- data/lib/cisco_node_utils/cmd_ref/bgp.yaml +242 -0
- data/lib/cisco_node_utils/cmd_ref/bgp_af.yaml +164 -0
- data/lib/cisco_node_utils/cmd_ref/bgp_neighbor.yaml +131 -0
- data/lib/cisco_node_utils/cmd_ref/bgp_neighbor_af.yaml +179 -0
- data/lib/cisco_node_utils/cmd_ref/dnsclient.yaml +34 -0
- data/lib/cisco_node_utils/cmd_ref/evpn_vni.yaml +42 -0
- data/lib/cisco_node_utils/cmd_ref/fabricpath.yaml +172 -0
- data/lib/cisco_node_utils/cmd_ref/fabricpath_topology.yaml +35 -0
- data/lib/cisco_node_utils/cmd_ref/feature.yaml +42 -0
- data/lib/cisco_node_utils/cmd_ref/fex.yaml +9 -0
- data/lib/cisco_node_utils/cmd_ref/images.yaml +7 -0
- data/lib/cisco_node_utils/cmd_ref/interface.yaml +339 -0
- data/lib/cisco_node_utils/cmd_ref/interface_channel_group.yaml +28 -0
- data/lib/cisco_node_utils/cmd_ref/interface_ospf.yaml +61 -0
- data/lib/cisco_node_utils/cmd_ref/interface_portchannel.yaml +54 -0
- data/lib/cisco_node_utils/cmd_ref/interface_service_vni.yaml +32 -0
- data/lib/cisco_node_utils/cmd_ref/inventory.yaml +45 -0
- data/lib/cisco_node_utils/cmd_ref/memory.yaml +13 -0
- data/lib/cisco_node_utils/cmd_ref/ntp_config.yaml +7 -0
- data/lib/cisco_node_utils/cmd_ref/ntp_server.yaml +14 -0
- data/lib/cisco_node_utils/cmd_ref/ospf.yaml +74 -0
- data/lib/cisco_node_utils/cmd_ref/overlay_global.yaml +33 -0
- data/lib/cisco_node_utils/cmd_ref/pim.yaml +40 -0
- data/lib/cisco_node_utils/cmd_ref/portchannel_global.yaml +69 -0
- data/lib/cisco_node_utils/cmd_ref/radius_global.yaml +25 -0
- data/lib/cisco_node_utils/cmd_ref/radius_server.yaml +64 -0
- data/lib/cisco_node_utils/cmd_ref/radius_server_group.yaml +14 -0
- data/lib/cisco_node_utils/cmd_ref/show_system.yaml +5 -0
- data/lib/cisco_node_utils/cmd_ref/show_version.yaml +72 -0
- data/lib/cisco_node_utils/cmd_ref/snmp_community.yaml +23 -0
- data/lib/cisco_node_utils/cmd_ref/snmp_group.yaml +7 -0
- data/lib/cisco_node_utils/cmd_ref/snmp_notification_receiver.yaml +50 -0
- data/lib/cisco_node_utils/cmd_ref/snmp_server.yaml +51 -0
- data/lib/cisco_node_utils/cmd_ref/snmp_user.yaml +55 -0
- data/lib/cisco_node_utils/cmd_ref/snmpnotification.yaml +11 -0
- data/lib/cisco_node_utils/cmd_ref/syslog_server.yaml +18 -0
- data/lib/cisco_node_utils/cmd_ref/syslog_settings.yaml +7 -0
- data/lib/cisco_node_utils/cmd_ref/system.yaml +6 -0
- data/lib/cisco_node_utils/cmd_ref/tacacs_server.yaml +49 -0
- data/lib/cisco_node_utils/cmd_ref/tacacs_server_group.yaml +33 -0
- data/lib/cisco_node_utils/cmd_ref/tacacs_server_host.yaml +35 -0
- data/lib/cisco_node_utils/cmd_ref/vdc.yaml +38 -0
- data/lib/cisco_node_utils/cmd_ref/virtual_service.yaml +6 -0
- data/lib/cisco_node_utils/cmd_ref/vlan.yaml +56 -0
- data/lib/cisco_node_utils/cmd_ref/vni.yaml +76 -0
- data/lib/cisco_node_utils/cmd_ref/vpc.yaml +197 -0
- data/lib/cisco_node_utils/cmd_ref/vrf.yaml +88 -0
- data/lib/cisco_node_utils/cmd_ref/vtp.yaml +38 -0
- data/lib/cisco_node_utils/cmd_ref/vxlan_vtep.yaml +60 -0
- data/lib/cisco_node_utils/cmd_ref/vxlan_vtep_vni.yaml +39 -0
- data/lib/cisco_node_utils/cmd_ref/yum.yaml +13 -0
- data/lib/cisco_node_utils/command_reference.rb +359 -187
- data/lib/cisco_node_utils/configparser_lib.rb +1 -1
- data/lib/cisco_node_utils/dns_domain.rb +19 -5
- data/lib/cisco_node_utils/domain_name.rb +4 -8
- data/lib/cisco_node_utils/evpn_vni.rb +157 -0
- data/lib/cisco_node_utils/fabricpath_global.rb +388 -0
- data/lib/cisco_node_utils/fabricpath_topology.rb +150 -0
- data/lib/cisco_node_utils/feature.rb +111 -0
- data/lib/cisco_node_utils/interface.rb +390 -97
- data/lib/cisco_node_utils/interface_channel_group.rb +124 -0
- data/lib/cisco_node_utils/interface_ospf.rb +11 -34
- data/lib/cisco_node_utils/interface_portchannel.rb +157 -0
- data/lib/cisco_node_utils/interface_service_vni.rb +132 -0
- data/lib/cisco_node_utils/name_server.rb +1 -1
- data/lib/cisco_node_utils/node.rb +55 -249
- data/lib/cisco_node_utils/node_util.rb +5 -1
- data/lib/cisco_node_utils/ntp_config.rb +2 -2
- data/lib/cisco_node_utils/ntp_server.rb +14 -5
- data/lib/cisco_node_utils/overlay_global.rb +153 -0
- data/lib/cisco_node_utils/pim.rb +124 -0
- data/lib/cisco_node_utils/pim_group_list.rb +108 -0
- data/lib/cisco_node_utils/pim_rp_address.rb +102 -0
- data/lib/cisco_node_utils/platform.rb +8 -9
- data/lib/cisco_node_utils/portchannel_global.rb +277 -0
- data/lib/cisco_node_utils/radius_global.rb +9 -19
- data/lib/cisco_node_utils/radius_server.rb +31 -41
- data/lib/cisco_node_utils/radius_server_group.rb +117 -0
- data/lib/cisco_node_utils/router_ospf.rb +1 -1
- data/lib/cisco_node_utils/router_ospf_vrf.rb +14 -19
- data/lib/cisco_node_utils/snmp_notification_receiver.rb +158 -0
- data/lib/cisco_node_utils/snmpcommunity.rb +3 -5
- data/lib/cisco_node_utils/snmpgroup.rb +1 -1
- data/lib/cisco_node_utils/snmpnotification.rb +57 -0
- data/lib/cisco_node_utils/snmpserver.rb +8 -17
- data/lib/cisco_node_utils/snmpuser.rb +67 -28
- data/lib/cisco_node_utils/syslog_server.rb +3 -9
- data/lib/cisco_node_utils/syslog_settings.rb +2 -10
- data/lib/cisco_node_utils/tacacs_server.rb +9 -14
- data/lib/cisco_node_utils/tacacs_server_group.rb +145 -0
- data/lib/cisco_node_utils/tacacs_server_host.rb +5 -9
- data/lib/cisco_node_utils/vdc.rb +88 -0
- data/lib/cisco_node_utils/version.rb +5 -2
- data/lib/cisco_node_utils/vlan.rb +71 -8
- data/lib/cisco_node_utils/vni.rb +227 -0
- data/lib/cisco_node_utils/vpc.rb +377 -0
- data/lib/cisco_node_utils/vrf.rb +60 -9
- data/lib/cisco_node_utils/vrf_af.rb +191 -0
- data/lib/cisco_node_utils/vtp.rb +8 -6
- data/lib/cisco_node_utils/vxlan_vtep.rb +151 -0
- data/lib/cisco_node_utils/vxlan_vtep_vni.rb +234 -0
- data/lib/cisco_node_utils/yum.rb +1 -1
- data/tests/.rubocop.yml +1 -1
- data/tests/basetest.rb +16 -7
- data/tests/ciscotest.rb +55 -13
- data/tests/cmd_config.yaml +2 -2
- data/tests/platform_info.rb +3 -2
- data/tests/test_aaa_authentication_login.rb +219 -0
- data/tests/test_aaa_authentication_login_service.rb +759 -0
- data/tests/test_aaa_authorization_service.rb +1041 -0
- data/tests/test_ace.rb +160 -0
- data/tests/test_acl.rb +176 -0
- data/tests/test_bgp_af.rb +269 -13
- data/tests/test_bgp_neighbor.rb +38 -40
- data/tests/test_bgp_neighbor_af.rb +92 -32
- data/tests/test_command_config.rb +5 -5
- data/tests/test_command_reference.rb +284 -101
- data/tests/test_dns_domain.rb +1 -1
- data/tests/test_domain_name.rb +1 -1
- data/tests/test_evpn_vni.rb +106 -0
- data/tests/test_fabricpath_global.rb +243 -0
- data/tests/test_fabricpath_topology.rb +98 -0
- data/tests/test_interface.rb +292 -74
- data/tests/test_interface_channel_group.rb +74 -0
- data/tests/test_interface_ospf.rb +9 -4
- data/tests/test_interface_portchannel.rb +105 -0
- data/tests/test_interface_service_vni.rb +232 -0
- data/tests/test_interface_svi.rb +77 -62
- data/tests/test_interface_switchport.rb +17 -5
- data/tests/test_name_server.rb +1 -1
- data/tests/test_node.rb +1 -1
- data/tests/test_node_ext.rb +10 -20
- data/tests/test_ntp_config.rb +1 -1
- data/tests/test_ntp_server.rb +18 -6
- data/tests/test_overlay_global.rb +102 -0
- data/tests/test_pim.rb +177 -0
- data/tests/test_pim_group_list.rb +181 -0
- data/tests/test_pim_rp_address.rb +153 -0
- data/tests/test_platform.rb +3 -3
- data/tests/test_portchannel_global.rb +202 -0
- data/tests/test_radius_global.rb +1 -1
- data/tests/test_radius_server.rb +92 -57
- data/tests/test_radius_server_group.rb +149 -0
- data/tests/test_router_bgp.rb +283 -112
- data/tests/test_router_ospf.rb +2 -2
- data/tests/test_router_ospf_vrf.rb +4 -4
- data/tests/test_snmp_notification_receiver.rb +167 -0
- data/tests/test_snmpcommunity.rb +1 -1
- data/tests/test_snmpgroup.rb +1 -1
- data/tests/test_snmpnotification.rb +72 -0
- data/tests/test_snmpserver.rb +29 -105
- data/tests/test_snmpuser.rb +32 -30
- data/tests/test_syslog_server.rb +36 -10
- data/tests/test_syslog_settings.rb +1 -1
- data/tests/test_tacacs_server.rb +1 -1
- data/tests/test_tacacs_server_group.rb +405 -0
- data/tests/test_tacacs_server_host.rb +1 -1
- data/tests/test_vdc.rb +78 -0
- data/tests/test_vlan.rb +74 -19
- data/tests/test_vlan_mt_full.rb +95 -0
- data/tests/test_vni.rb +106 -0
- data/tests/test_vpc.rb +361 -0
- data/tests/test_vrf.rb +172 -29
- data/tests/test_vtp.rb +1 -1
- data/tests/test_vxlan_vtep.rb +214 -0
- data/tests/test_vxlan_vtep_vni.rb +201 -0
- data/tests/test_yum.rb +1 -1
- metadata +120 -11
- data/lib/cisco_node_utils/README_YAML.md +0 -325
- data/lib/cisco_node_utils/command_reference_common.yaml +0 -1051
- data/lib/cisco_node_utils/command_reference_common_bgp.yaml +0 -535
- data/lib/cisco_node_utils/command_reference_n3064.yaml +0 -13
- data/lib/cisco_node_utils/command_reference_n7k.yaml +0 -52
- data/lib/cisco_node_utils/command_reference_n9k.yaml +0 -26
- 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-
|
|
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
|
|
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 :
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
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
|
-
|
|
65
|
-
|
|
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
|
|
55
|
+
unless KEYS.include?(key)
|
|
79
56
|
fail "Unrecognized key #{key} for #{feature}, #{name} in #{file}"
|
|
80
57
|
end
|
|
81
|
-
if
|
|
82
|
-
#
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
|
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
|
|
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(
|
|
162
|
-
|
|
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
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
|
|
218
|
-
|
|
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
|
-
|
|
242
|
-
|
|
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
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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,
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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
|