cisco_node_utils 1.0.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.rubocop.yml +81 -1
- data/.travis.yml +9 -0
- data/CHANGELOG.md +72 -6
- data/CONTRIBUTING.md +32 -7
- data/README.md +70 -7
- data/Rakefile +17 -0
- data/bin/check_metric_limits.rb +109 -0
- data/bin/git/hooks/commit-msg/enforce_style +81 -0
- data/bin/git/hooks/hook_lib +108 -0
- data/bin/git/hooks/hooks-wrapper +38 -0
- data/bin/git/hooks/post-flow-hotfix-start/update-version +24 -0
- data/bin/git/hooks/post-flow-release-finish/update-version +29 -0
- data/bin/git/hooks/post-flow-release-start/update-version +19 -0
- data/bin/git/hooks/post-merge/update-hooks +6 -0
- data/bin/git/hooks/post-rewrite/update-hooks +6 -0
- data/bin/git/hooks/pre-commit/rubocop +20 -0
- data/bin/git/hooks/pre-commit/validate-diffs +31 -0
- data/bin/git/hooks/pre-push/check-changelog +24 -0
- data/bin/git/hooks/pre-push/rubocop +7 -0
- data/bin/git/update-hooks +65 -0
- data/cisco_node_utils.gemspec +9 -3
- data/docs/README-develop-best-practices.md +404 -0
- data/docs/README-develop-node-utils-APIs.md +215 -365
- data/docs/README-maintainers.md +33 -3
- data/docs/template-router.rb +89 -91
- data/docs/template-test_router.rb +52 -55
- data/lib/.rubocop.yml +18 -0
- data/lib/cisco_node_utils.rb +2 -19
- data/lib/cisco_node_utils/README_YAML.md +1 -9
- data/lib/cisco_node_utils/bgp.rb +664 -0
- data/lib/cisco_node_utils/bgp_af.rb +530 -0
- data/lib/cisco_node_utils/bgp_neighbor.rb +425 -0
- data/lib/cisco_node_utils/bgp_neighbor_af.rb +709 -0
- data/lib/cisco_node_utils/cisco_cmn_utils.rb +59 -25
- data/lib/cisco_node_utils/command_reference.rb +72 -74
- data/lib/cisco_node_utils/command_reference_common.yaml +174 -9
- data/lib/cisco_node_utils/command_reference_common_bgp.yaml +535 -0
- data/lib/cisco_node_utils/command_reference_n7k.yaml +4 -0
- data/lib/cisco_node_utils/command_reference_n9k.yaml +0 -9
- data/lib/cisco_node_utils/configparser_lib.rb +152 -147
- data/lib/cisco_node_utils/dns_domain.rb +79 -0
- data/lib/cisco_node_utils/domain_name.rb +71 -0
- data/lib/cisco_node_utils/interface.rb +167 -161
- data/lib/cisco_node_utils/interface_ospf.rb +78 -81
- data/lib/cisco_node_utils/name_server.rb +64 -0
- data/lib/cisco_node_utils/node.rb +154 -198
- data/lib/cisco_node_utils/node_util.rb +61 -0
- data/lib/cisco_node_utils/ntp_config.rb +65 -0
- data/lib/cisco_node_utils/ntp_server.rb +76 -0
- data/lib/cisco_node_utils/platform.rb +174 -165
- data/lib/cisco_node_utils/radius_global.rb +146 -0
- data/lib/cisco_node_utils/radius_server.rb +295 -0
- data/lib/cisco_node_utils/router_ospf.rb +59 -63
- data/lib/cisco_node_utils/router_ospf_vrf.rb +226 -210
- data/lib/cisco_node_utils/snmpcommunity.rb +52 -58
- data/lib/cisco_node_utils/snmpgroup.rb +22 -23
- data/lib/cisco_node_utils/snmpserver.rb +99 -103
- data/lib/cisco_node_utils/snmpuser.rb +294 -274
- data/lib/cisco_node_utils/syslog_server.rb +92 -0
- data/lib/cisco_node_utils/syslog_settings.rb +69 -0
- data/lib/cisco_node_utils/tacacs_server.rb +137 -133
- data/lib/cisco_node_utils/tacacs_server_host.rb +84 -87
- data/lib/cisco_node_utils/version.rb +2 -1
- data/lib/cisco_node_utils/vlan.rb +28 -31
- data/lib/cisco_node_utils/vrf.rb +80 -0
- data/lib/cisco_node_utils/vtp.rb +100 -97
- data/lib/cisco_node_utils/yum.rb +15 -17
- data/tests/.rubocop.yml +15 -0
- data/tests/basetest.rb +81 -36
- data/tests/ciscotest.rb +38 -78
- data/{lib/cisco_node_utils → tests}/platform_info.rb +12 -8
- data/{lib/cisco_node_utils → tests}/platform_info.yaml +1 -1
- data/tests/test_bgp_af.rb +920 -0
- data/tests/test_bgp_neighbor.rb +403 -0
- data/tests/test_bgp_neighbor_af.rb +589 -0
- data/tests/test_command_config.rb +65 -62
- data/tests/test_command_reference.rb +31 -45
- data/tests/test_dns_domain.rb +113 -0
- data/tests/test_domain_name.rb +86 -0
- data/tests/test_interface.rb +424 -548
- data/tests/test_interface_ospf.rb +248 -432
- data/tests/test_interface_svi.rb +56 -79
- data/tests/test_interface_switchport.rb +196 -272
- data/tests/test_name_server.rb +85 -0
- data/tests/test_node.rb +7 -6
- data/tests/test_node_ext.rb +133 -186
- data/tests/test_ntp_config.rb +49 -0
- data/tests/test_ntp_server.rb +74 -0
- data/tests/test_platform.rb +58 -37
- data/tests/test_radius_global.rb +78 -0
- data/tests/test_radius_server.rb +185 -0
- data/tests/test_router_bgp.rb +838 -0
- data/tests/test_router_ospf.rb +49 -80
- data/tests/test_router_ospf_vrf.rb +274 -392
- data/tests/test_snmpcommunity.rb +128 -172
- data/tests/test_snmpgroup.rb +12 -14
- data/tests/test_snmpserver.rb +160 -189
- data/tests/test_snmpuser.rb +568 -717
- data/tests/test_syslog_server.rb +88 -0
- data/tests/test_syslog_settings.rb +54 -0
- data/tests/test_tacacs_server.rb +113 -148
- data/tests/test_tacacs_server_host.rb +108 -161
- data/tests/test_vlan.rb +63 -79
- data/tests/test_vrf.rb +92 -0
- data/tests/test_vtp.rb +108 -126
- data/tests/test_yum.rb +47 -41
- metadata +92 -56
- data/.rubocop_todo.yml +0 -293
- data/docs/.rubocop.yml +0 -13
- data/docs/template-feature.rb +0 -45
- data/docs/template-test_feature.rb +0 -51
- data/tests/test_all_cisco.rb +0 -46
@@ -1,6 +1,3 @@
|
|
1
|
-
#
|
2
|
-
# NXAPI implementation of Interface OSPF class
|
3
|
-
#
|
4
1
|
# March 2015, Alex Hunsberger
|
5
2
|
#
|
6
3
|
# Copyright (c) 2015 Cisco and/or its affiliates.
|
@@ -18,58 +15,56 @@
|
|
18
15
|
# limitations under the License.
|
19
16
|
|
20
17
|
require 'ipaddr'
|
21
|
-
|
22
|
-
|
18
|
+
require_relative 'node_util'
|
19
|
+
require_relative 'interface'
|
23
20
|
# Interestingly enough, interface OSPF configuration can exist completely
|
24
21
|
# independent of router OSPF configuration... so we don't need RouterOspf here.
|
25
22
|
|
26
23
|
module Cisco
|
27
|
-
class
|
24
|
+
# InterfaceOspf - node utility class for per-interface OSPF config management
|
25
|
+
class InterfaceOspf < NodeUtil
|
28
26
|
attr_reader :interface, :ospf_name
|
29
27
|
|
30
|
-
@@node = Node.instance
|
31
|
-
|
32
28
|
def initialize(int_name, ospf_name, area, create=true)
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
29
|
+
fail TypeError unless int_name.is_a? String
|
30
|
+
fail TypeError unless ospf_name.is_a? String
|
31
|
+
fail TypeError unless area.is_a? String
|
32
|
+
fail ArgumentError unless int_name.length > 0
|
33
|
+
fail ArgumentError unless ospf_name.length > 0
|
34
|
+
fail ArgumentError unless area.length > 0
|
39
35
|
|
40
36
|
# normalize
|
41
37
|
int_name = int_name.downcase
|
42
38
|
@interface = Interface.interfaces[int_name]
|
43
|
-
|
39
|
+
fail "interface #{int_name} does not exist" if @interface.nil?
|
44
40
|
|
45
41
|
@ospf_name = ospf_name
|
46
42
|
|
47
|
-
|
48
|
-
|
49
|
-
|
43
|
+
return unless create
|
44
|
+
# enable feature ospf if it isn't
|
45
|
+
RouterOspf.enable unless RouterOspf.enabled
|
50
46
|
|
51
|
-
|
52
|
-
|
53
|
-
end
|
47
|
+
config_set('interface_ospf', 'area', @interface.name,
|
48
|
+
'', @ospf_name, area)
|
54
49
|
end
|
55
50
|
|
56
51
|
# can't re-use Interface.interfaces because we need to filter based on
|
57
52
|
# "ip router ospf <name>", which Interface doesn't retrieve
|
58
|
-
def
|
59
|
-
|
53
|
+
def self.interfaces(ospf_name=nil)
|
54
|
+
fail TypeError unless ospf_name.is_a?(String) || ospf_name.nil?
|
60
55
|
ints = {}
|
61
56
|
|
62
|
-
intf_list =
|
57
|
+
intf_list = config_get('interface', 'all_interfaces')
|
63
58
|
return ints if intf_list.nil?
|
64
59
|
intf_list.each do |name|
|
65
|
-
match =
|
60
|
+
match = config_get('interface_ospf', 'area', name)
|
66
61
|
next if match.nil?
|
67
62
|
# should only be a single match under a given interface
|
68
63
|
match = match.first
|
69
64
|
# ip router ospf <name> area <area>
|
70
65
|
ospf = match[0]
|
71
66
|
area = match[1]
|
72
|
-
next unless ospf_name.nil?
|
67
|
+
next unless ospf_name.nil? || ospf == ospf_name
|
73
68
|
int = name.downcase
|
74
69
|
ints[int] = InterfaceOspf.new(int, ospf, area, false)
|
75
70
|
end
|
@@ -77,7 +72,7 @@ module Cisco
|
|
77
72
|
end
|
78
73
|
|
79
74
|
def area
|
80
|
-
match =
|
75
|
+
match = config_get('interface_ospf', 'area', @interface.name)
|
81
76
|
return nil if match.nil?
|
82
77
|
val = match[0][1]
|
83
78
|
# Coerce numeric area to the expected dot-decimal format.
|
@@ -86,79 +81,81 @@ module Cisco
|
|
86
81
|
end
|
87
82
|
|
88
83
|
def area=(a)
|
89
|
-
|
90
|
-
|
84
|
+
config_set('interface_ospf', 'area', @interface.name,
|
85
|
+
'', @ospf_name, a)
|
91
86
|
end
|
92
87
|
|
93
88
|
def destroy
|
94
|
-
|
95
|
-
|
89
|
+
config_set('interface_ospf', 'area', @interface.name,
|
90
|
+
'no', @ospf_name, area)
|
96
91
|
# Reset everything else back to default as well:
|
97
92
|
self.message_digest = default_message_digest
|
98
|
-
message_digest_key_set(default_message_digest_key_id,
|
93
|
+
message_digest_key_set(default_message_digest_key_id, '', '', '')
|
99
94
|
self.cost = default_cost
|
100
95
|
self.hello_interval = default_hello_interval
|
101
|
-
|
102
|
-
|
96
|
+
config_set('interface_ospf', 'dead_interval',
|
97
|
+
@interface.name, 'no', '')
|
103
98
|
self.passive_interface = default_passive_interface if passive_interface
|
104
99
|
end
|
105
100
|
|
106
101
|
def default_message_digest
|
107
|
-
|
102
|
+
config_get_default('interface_ospf', 'message_digest')
|
108
103
|
end
|
109
104
|
|
110
105
|
def message_digest
|
111
|
-
|
112
|
-
|
106
|
+
!config_get('interface_ospf', 'message_digest',
|
107
|
+
@interface.name).nil?
|
113
108
|
end
|
114
109
|
|
115
110
|
# interface %s
|
116
111
|
# %s ip ospf authentication message-digest
|
117
112
|
def message_digest=(enable)
|
118
|
-
|
119
|
-
|
113
|
+
config_set('interface_ospf', 'message_digest', @interface.name,
|
114
|
+
enable ? '' : 'no')
|
120
115
|
end
|
121
116
|
|
122
117
|
def default_message_digest_key_id
|
123
|
-
|
118
|
+
config_get_default('interface_ospf', 'message_digest_key_id')
|
124
119
|
end
|
125
120
|
|
126
121
|
def message_digest_key_id
|
127
|
-
match =
|
128
|
-
|
122
|
+
match = config_get('interface_ospf', 'message_digest_key_id',
|
123
|
+
@interface.name)
|
129
124
|
# regex in yaml returns an array result, use .first to get match
|
130
125
|
match.nil? ? default_message_digest_key_id : match.first.to_i
|
131
126
|
end
|
132
127
|
|
133
128
|
def default_message_digest_algorithm_type
|
134
|
-
|
135
|
-
|
129
|
+
config_get_default('interface_ospf',
|
130
|
+
'message_digest_alg_type').to_sym
|
136
131
|
end
|
137
132
|
|
138
133
|
def message_digest_algorithm_type
|
139
|
-
match =
|
140
|
-
|
134
|
+
match = config_get('interface_ospf', 'message_digest_alg_type',
|
135
|
+
@interface.name)
|
141
136
|
# regex in yaml returns an array result, use .first to get match
|
142
|
-
match.nil? ? default_message_digest_algorithm_type :
|
143
|
-
match.first.to_sym
|
137
|
+
match.nil? ? default_message_digest_algorithm_type : match.first.to_sym
|
144
138
|
end
|
145
139
|
|
146
140
|
def default_message_digest_encryption_type
|
147
141
|
Encryption.cli_to_symbol(
|
148
|
-
|
142
|
+
config_get_default('interface_ospf', 'message_digest_enc_type'))
|
149
143
|
end
|
150
144
|
|
151
145
|
def message_digest_encryption_type
|
152
|
-
match =
|
153
|
-
|
146
|
+
match = config_get('interface_ospf', 'message_digest_enc_type',
|
147
|
+
@interface.name)
|
154
148
|
# regex in yaml returns an array result, use .first to get match
|
155
|
-
match.nil?
|
149
|
+
if match.nil?
|
150
|
+
default_message_digest_encryption_type
|
151
|
+
else
|
156
152
|
Encryption.cli_to_symbol(match.first)
|
153
|
+
end
|
157
154
|
end
|
158
155
|
|
159
156
|
def message_digest_password
|
160
|
-
match =
|
161
|
-
|
157
|
+
match = config_get('interface_ospf', 'message_digest_password',
|
158
|
+
@interface.name)
|
162
159
|
match.nil? ? nil : match.first
|
163
160
|
end
|
164
161
|
|
@@ -167,89 +164,89 @@ module Cisco
|
|
167
164
|
def message_digest_key_set(keyid, algtype, enctype, enc)
|
168
165
|
current_keyid = message_digest_key_id
|
169
166
|
if keyid == default_message_digest_key_id && current_keyid != keyid
|
170
|
-
|
171
|
-
|
172
|
-
|
167
|
+
config_set('interface_ospf', 'message_digest_key_set',
|
168
|
+
@interface.name, 'no', current_keyid,
|
169
|
+
'', '', '')
|
173
170
|
elsif keyid != default_message_digest_key_id
|
174
|
-
|
175
|
-
|
171
|
+
fail TypeError unless enc.is_a?(String)
|
172
|
+
fail ArgumentError unless enc.length > 0
|
176
173
|
enctype = Encryption.symbol_to_cli(enctype)
|
177
|
-
|
178
|
-
|
174
|
+
config_set('interface_ospf', 'message_digest_key_set',
|
175
|
+
@interface.name, '', keyid, algtype, enctype, enc)
|
179
176
|
end
|
180
177
|
end
|
181
178
|
|
182
179
|
def cost
|
183
|
-
match =
|
180
|
+
match = config_get('interface_ospf', 'cost', @interface.name)
|
184
181
|
# regex in yaml returns an array result, use .first to get match
|
185
182
|
match.nil? ? default_cost : match.first.to_i
|
186
183
|
end
|
187
184
|
|
188
185
|
def default_cost
|
189
|
-
|
186
|
+
config_get_default('interface_ospf', 'cost')
|
190
187
|
end
|
191
188
|
|
192
189
|
# interface %s
|
193
190
|
# ip ospf cost %d
|
194
191
|
def cost=(c)
|
195
192
|
if c == default_cost
|
196
|
-
|
193
|
+
config_set('interface_ospf', 'cost', @interface.name, 'no', '')
|
197
194
|
else
|
198
|
-
|
195
|
+
config_set('interface_ospf', 'cost', @interface.name, '', c)
|
199
196
|
end
|
200
197
|
end
|
201
198
|
|
202
199
|
def hello_interval
|
203
|
-
match =
|
204
|
-
|
200
|
+
match = config_get('interface_ospf', 'hello_interval',
|
201
|
+
@interface.name)
|
205
202
|
# regex in yaml returns an array result, use .first to get match
|
206
203
|
match.nil? ? default_hello_interval : match.first.to_i
|
207
204
|
end
|
208
205
|
|
209
206
|
def default_hello_interval
|
210
|
-
|
207
|
+
config_get_default('interface_ospf', 'hello_interval')
|
211
208
|
end
|
212
209
|
|
213
210
|
# interface %s
|
214
211
|
# ip ospf hello-interval %d
|
215
212
|
def hello_interval=(interval)
|
216
|
-
|
217
|
-
|
213
|
+
config_set('interface_ospf', 'hello_interval',
|
214
|
+
@interface.name, '', interval.to_i)
|
218
215
|
end
|
219
216
|
|
220
217
|
def dead_interval
|
221
|
-
match =
|
222
|
-
|
218
|
+
match = config_get('interface_ospf', 'dead_interval',
|
219
|
+
@interface.name)
|
223
220
|
# regex in yaml returns an array result, use .first to get match
|
224
221
|
match.nil? ? default_dead_interval : match.first.to_i
|
225
222
|
end
|
226
223
|
|
227
224
|
def default_dead_interval
|
228
|
-
|
225
|
+
config_get_default('interface_ospf', 'dead_interval')
|
229
226
|
end
|
230
227
|
|
231
228
|
# interface %s
|
232
229
|
# ip ospf dead-interval %d
|
233
230
|
def dead_interval=(interval)
|
234
|
-
|
235
|
-
|
231
|
+
config_set('interface_ospf', 'dead_interval',
|
232
|
+
@interface.name, '', interval.to_i)
|
236
233
|
end
|
237
234
|
|
238
235
|
def default_passive_interface
|
239
|
-
|
236
|
+
config_get_default('interface_ospf', 'passive_interface')
|
240
237
|
end
|
241
238
|
|
242
239
|
def passive_interface
|
243
|
-
|
244
|
-
|
240
|
+
!config_get('interface_ospf', 'passive_interface',
|
241
|
+
@interface.name).nil?
|
245
242
|
end
|
246
243
|
|
247
244
|
# interface %s
|
248
245
|
# %s ip ospf passive-interface
|
249
246
|
def passive_interface=(enable)
|
250
|
-
|
251
|
-
|
252
|
-
|
247
|
+
fail TypeError unless enable == true || enable == false
|
248
|
+
config_set('interface_ospf', 'passive_interface', @interface.name,
|
249
|
+
enable ? '' : 'no')
|
253
250
|
end
|
254
251
|
end
|
255
252
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
#
|
2
|
+
# NXAPI implementation of NameServer class
|
3
|
+
#
|
4
|
+
# September 2015, Hunter Haugen
|
5
|
+
#
|
6
|
+
# Copyright (c) 2015 Cisco and/or its affiliates.
|
7
|
+
#
|
8
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
9
|
+
# you may not use this file except in compliance with the License.
|
10
|
+
# You may obtain a copy of the License at
|
11
|
+
#
|
12
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
13
|
+
#
|
14
|
+
# Unless required by applicable law or agreed to in writing, software
|
15
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
16
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
17
|
+
# See the License for the specific language governing permissions and
|
18
|
+
# limitations under the License.
|
19
|
+
#
|
20
|
+
# "group" is a standard SNMP term but in NXOS "role" is used to serve the
|
21
|
+
# purpose of group; thus this provider utility does not create snmp groups
|
22
|
+
# and is limited to reporting group (role) existence only.
|
23
|
+
|
24
|
+
require_relative 'node_util'
|
25
|
+
|
26
|
+
module Cisco
|
27
|
+
# NameServer - node utility class for DNS client name server config management
|
28
|
+
class NameServer < NodeUtil
|
29
|
+
attr_reader :name
|
30
|
+
|
31
|
+
def initialize(name, instantiate=true)
|
32
|
+
unless name.is_a? String
|
33
|
+
fail TypeError, "Expected a string, got a #{name.inspect}"
|
34
|
+
end
|
35
|
+
@name = name
|
36
|
+
create if instantiate
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.nameservers
|
40
|
+
hosts = config_get('dnsclient', 'name_server')
|
41
|
+
return {} if hosts.nil?
|
42
|
+
|
43
|
+
hash = {}
|
44
|
+
# Join and split because config_get returns array of strings separated by
|
45
|
+
# spaces (regexes are a subset of PDA)
|
46
|
+
hosts.join(' ').split(' ').each do |name|
|
47
|
+
hash[name] = NameServer.new(name, false)
|
48
|
+
end
|
49
|
+
hash
|
50
|
+
end
|
51
|
+
|
52
|
+
def ==(other)
|
53
|
+
name == other.name
|
54
|
+
end
|
55
|
+
|
56
|
+
def create
|
57
|
+
config_set('dnsclient', 'name_server', state: '', ip: @name)
|
58
|
+
end
|
59
|
+
|
60
|
+
def destroy
|
61
|
+
config_set('dnsclient', 'name_server', state: 'no', ip: @name)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -21,8 +21,9 @@
|
|
21
21
|
require 'singleton'
|
22
22
|
|
23
23
|
require 'cisco_nxapi'
|
24
|
-
|
24
|
+
require_relative 'command_reference'
|
25
25
|
|
26
|
+
# Add node management classes and APIs to the Cisco namespace.
|
26
27
|
module Cisco
|
27
28
|
# Error class raised by the config_set and config_get APIs if the
|
28
29
|
# device encounters an issue trying to act on the requested CLI.
|
@@ -33,7 +34,7 @@ module Cisco
|
|
33
34
|
attr_reader :command, :clierror, :previous
|
34
35
|
def initialize(command, clierror, previous)
|
35
36
|
@command = command
|
36
|
-
@clierror = clierror.rstrip
|
37
|
+
@clierror = clierror.rstrip if clierror.kind_of? String
|
37
38
|
@previous = previous
|
38
39
|
end
|
39
40
|
|
@@ -46,7 +47,6 @@ module Cisco
|
|
46
47
|
# Singleton representing the network node (switch/router) that is
|
47
48
|
# running this code. The singleton is lazily instantiated, meaning that
|
48
49
|
# it doesn't exist until some client requests it (with Node.instance())
|
49
|
-
|
50
50
|
class Node
|
51
51
|
include Singleton
|
52
52
|
|
@@ -61,14 +61,54 @@ module Cisco
|
|
61
61
|
#
|
62
62
|
# @raise [IndexError] if the given (feature, name) pair is not in the
|
63
63
|
# CommandReference data or if the data doesn't have values defined
|
64
|
-
# for the 'config_get' and 'config_get_token' fields.
|
64
|
+
# for the 'config_get' and (optional) 'config_get_token' fields.
|
65
65
|
# @raise [Cisco::CliError] if the given command is rejected by the device.
|
66
66
|
#
|
67
67
|
# @param feature [String]
|
68
68
|
# @param name [String]
|
69
|
-
# @return [String]
|
69
|
+
# @return [String, Hash, Array]
|
70
70
|
# @example config_get("show_version", "system_image")
|
71
|
-
|
71
|
+
# @example config_get("ospf", "router_id",
|
72
|
+
# {:name => "green", :vrf => "one"})
|
73
|
+
def config_get(feature, name, *args)
|
74
|
+
fail 'lazy_connect specified but did not request connect' unless @cmd_ref
|
75
|
+
ref = @cmd_ref.lookup(feature, name)
|
76
|
+
|
77
|
+
begin
|
78
|
+
token = build_config_get_token(feature, ref, args)
|
79
|
+
rescue IndexError, TypeError
|
80
|
+
# IndexError if value is not set, TypeError if set to nil explicitly
|
81
|
+
token = nil
|
82
|
+
end
|
83
|
+
if token.kind_of?(String)
|
84
|
+
if token[0] == '/' && token[-1] == '/'
|
85
|
+
fail RuntimeError unless args.length == token.scan(/%/).length
|
86
|
+
# convert string to regexp and replace %s with args
|
87
|
+
token = Regexp.new(sprintf(token, *args)[1..-2])
|
88
|
+
text = build_config_get(feature, ref, :ascii)
|
89
|
+
return Cisco.find_ascii(text, token)
|
90
|
+
else
|
91
|
+
hash = build_config_get(feature, ref, :structured)
|
92
|
+
return hash[token]
|
93
|
+
end
|
94
|
+
elsif token.kind_of?(Array)
|
95
|
+
# Array of /regexps/ -> ascii, array of strings/ints -> structured
|
96
|
+
if token[0].kind_of?(String) &&
|
97
|
+
token[0][0] == '/' &&
|
98
|
+
(token[0][-1] == '/' || token[0][-2..-1] == '/i')
|
99
|
+
|
100
|
+
token = token_str_to_regexp(token, args)
|
101
|
+
text = build_config_get(feature, ref, :ascii)
|
102
|
+
return Cisco.find_ascii(text, token[-1], *token[0..-2])
|
103
|
+
|
104
|
+
else
|
105
|
+
result = build_config_get(feature, ref, :structured)
|
106
|
+
return config_get_handle_structured(token, result)
|
107
|
+
end
|
108
|
+
elsif token.nil?
|
109
|
+
return show(ref.config_get, :structured)
|
110
|
+
end
|
111
|
+
fail TypeError("Unclear to handle config_get_token #{token}")
|
72
112
|
end
|
73
113
|
|
74
114
|
# Uses CommandReference to lookup the default value for a given
|
@@ -82,6 +122,9 @@ module Cisco
|
|
82
122
|
# @return [String]
|
83
123
|
# @example config_get_default("vtp", "file")
|
84
124
|
def config_get_default(feature, name)
|
125
|
+
fail 'lazy_connect specified but did not request connect' unless @cmd_ref
|
126
|
+
ref = @cmd_ref.lookup(feature, name)
|
127
|
+
ref.default_value
|
85
128
|
end
|
86
129
|
|
87
130
|
# Uses CommandReference to look up the given config command(s) of interest
|
@@ -95,7 +138,37 @@ module Cisco
|
|
95
138
|
# @param name [String]
|
96
139
|
# @param args [*String] zero or more args to be substituted into the cmdref.
|
97
140
|
# @example config_set("vtp", "domain", "example.com")
|
141
|
+
# @example config_set("ospf", "router_id",
|
142
|
+
# {:name => "green", :vrf => "one", :state => "",
|
143
|
+
# :router_id => "192.0.0.1"})
|
98
144
|
def config_set(feature, name, *args)
|
145
|
+
fail 'lazy_connect specified but did not request connect' unless @cmd_ref
|
146
|
+
ref = @cmd_ref.lookup(feature, name)
|
147
|
+
config_set = build_config_set(feature, ref, args)
|
148
|
+
if config_set.is_a?(String)
|
149
|
+
param_count = config_set.scan(/%/).length
|
150
|
+
elsif config_set.is_a?(Array)
|
151
|
+
param_count = config_set.join(' ').scan(/%/).length
|
152
|
+
else
|
153
|
+
fail TypeError, '%{config_set.class} not supported for config_set'
|
154
|
+
end
|
155
|
+
unless args[0].is_a? Hash
|
156
|
+
if param_count != args.length
|
157
|
+
fail ArgumentError, 'Wrong number of params - expected: ' \
|
158
|
+
"#{param_count} actual: #{args.length}"
|
159
|
+
end
|
160
|
+
end
|
161
|
+
if config_set.is_a?(String)
|
162
|
+
config(sprintf(config_set, *args))
|
163
|
+
elsif config_set.is_a?(Array)
|
164
|
+
new_config_set = []
|
165
|
+
config_set.each do |line|
|
166
|
+
param_count = line.scan(/%/).length
|
167
|
+
new_config_set << sprintf(line, *args.first(param_count))
|
168
|
+
args = args[param_count..-1]
|
169
|
+
end
|
170
|
+
config(new_config_set)
|
171
|
+
end
|
99
172
|
end
|
100
173
|
|
101
174
|
# Clear the cache of CLI output results.
|
@@ -104,25 +177,29 @@ module Cisco
|
|
104
177
|
# whenever a config_set() is called, but providers may also call this
|
105
178
|
# to explicitly force the cache to be cleared.
|
106
179
|
def cache_flush
|
180
|
+
@client.cache_flush
|
107
181
|
end
|
108
182
|
|
109
|
-
# END NODE API
|
110
183
|
# Here and below are implementation details and private APIs that most
|
111
184
|
# providers shouldn't need to know about or use.
|
112
185
|
|
113
186
|
attr_reader :cmd_ref, :client
|
114
187
|
|
115
188
|
# For unit testing - we won't know the node connection info at load time.
|
116
|
-
|
189
|
+
@lazy_connect = false
|
190
|
+
|
191
|
+
class << self
|
192
|
+
attr_reader :lazy_connect
|
193
|
+
end
|
117
194
|
|
118
|
-
|
119
|
-
|
195
|
+
class << self
|
196
|
+
attr_writer :lazy_connect
|
120
197
|
end
|
121
198
|
|
122
199
|
def initialize
|
123
200
|
@client = nil
|
124
201
|
@cmd_ref = nil
|
125
|
-
connect unless
|
202
|
+
connect unless self.class.lazy_connect
|
126
203
|
end
|
127
204
|
|
128
205
|
def to_s
|
@@ -141,13 +218,6 @@ module Cisco
|
|
141
218
|
@client.reload
|
142
219
|
end
|
143
220
|
|
144
|
-
# hidden as well
|
145
|
-
attr_reader :client
|
146
|
-
|
147
|
-
def cache_flush
|
148
|
-
@client.cache_flush
|
149
|
-
end
|
150
|
-
|
151
221
|
def cache_enable?
|
152
222
|
@client.cache_enable?
|
153
223
|
end
|
@@ -175,19 +245,19 @@ module Cisco
|
|
175
245
|
def token_str_to_regexp(token, args)
|
176
246
|
unless args[0].is_a? Hash
|
177
247
|
expected_args = token.join.scan(/%/).length
|
178
|
-
|
248
|
+
fail "Given #{args.length} args, but token #{token} requires " \
|
179
249
|
"#{expected_args}" unless args.length == expected_args
|
180
250
|
end
|
181
251
|
# replace all %s with *args
|
182
252
|
token.map! { |str| sprintf(str, *args.shift(str.scan(/%/).length)) }
|
183
253
|
# convert all to Regexp objects
|
184
|
-
token.map!
|
254
|
+
token.map! do |str|
|
185
255
|
if str[-2..-1] == '/i'
|
186
256
|
Regexp.new(str[1..-3], Regexp::IGNORECASE)
|
187
257
|
else
|
188
258
|
Regexp.new(str[1..-2])
|
189
259
|
end
|
190
|
-
|
260
|
+
end
|
191
261
|
token
|
192
262
|
end
|
193
263
|
|
@@ -210,7 +280,7 @@ module Cisco
|
|
210
280
|
replace = regexp.scan(/<(\S+)>/).flatten.map(&:to_sym)
|
211
281
|
replace.each do |item|
|
212
282
|
regexp = regexp.sub "<#{item}>",
|
213
|
-
|
283
|
+
values[item].to_s if values.key?(item)
|
214
284
|
end
|
215
285
|
# Only return lines that actually replaced ids or did not have any
|
216
286
|
# ids to replace. Implicit nil returned if not.
|
@@ -235,7 +305,7 @@ module Cisco
|
|
235
305
|
# @param ref [CommandReference::CmdRef]
|
236
306
|
# @return [String, Array]
|
237
307
|
def build_config_get_token(feature, ref, args)
|
238
|
-
|
308
|
+
fail 'lazy_connect specified but did not request connect' unless @cmd_ref
|
239
309
|
# Why clone token? A bug in some ruby versions caused token to convert
|
240
310
|
# to type Regexp unexpectedly. The clone hard copy resolved it.
|
241
311
|
|
@@ -247,7 +317,7 @@ module Cisco
|
|
247
317
|
# Use _template yaml entry if config_get_token_append
|
248
318
|
if ref.to_s[/config_get_token_append/]
|
249
319
|
# Get yaml feature template:
|
250
|
-
template = @cmd_ref.lookup(feature,
|
320
|
+
template = @cmd_ref.lookup(feature, '_template')
|
251
321
|
# Process config_get_token: from template:
|
252
322
|
token.push(replace_token_ids(template.config_get_token, options))
|
253
323
|
# Process config_get_token_append sequence: from template:
|
@@ -272,13 +342,13 @@ module Cisco
|
|
272
342
|
# @param type [Symbol]
|
273
343
|
# @return [String, Array]
|
274
344
|
def build_config_get(feature, ref, type)
|
275
|
-
|
345
|
+
fail 'lazy_connect specified but did not request connect' unless @cmd_ref
|
276
346
|
# Use feature name config_get string if present
|
277
347
|
# else use feature template: config_get
|
278
|
-
if ref.hash.key?(
|
348
|
+
if ref.hash.key?('config_get')
|
279
349
|
return show(ref.config_get, type)
|
280
350
|
else
|
281
|
-
template = @cmd_ref.lookup(feature,
|
351
|
+
template = @cmd_ref.lookup(feature, '_template')
|
282
352
|
return show(template.config_get, type)
|
283
353
|
end
|
284
354
|
end
|
@@ -291,7 +361,7 @@ module Cisco
|
|
291
361
|
# @param ref [CommandReference::CmdRef]
|
292
362
|
# @return [String, Array]
|
293
363
|
def build_config_set(feature, ref, args)
|
294
|
-
|
364
|
+
fail 'lazy_connect specified but did not request connect' unless @cmd_ref
|
295
365
|
# If the options are presented as type Hash process as
|
296
366
|
# key-value replacement pairs
|
297
367
|
return ref.config_set unless args[0].is_a?(Hash)
|
@@ -300,7 +370,7 @@ module Cisco
|
|
300
370
|
# Use _template yaml entry if config_set_append
|
301
371
|
if ref.to_s[/config_set_append/]
|
302
372
|
# Get yaml feature template:
|
303
|
-
template = @cmd_ref.lookup(feature,
|
373
|
+
template = @cmd_ref.lookup(feature, '_template')
|
304
374
|
# Process config_set: from template:
|
305
375
|
config_set.push(replace_token_ids(template.config_set, options))
|
306
376
|
# Process config_set_append sequence: from template:
|
@@ -317,146 +387,31 @@ module Cisco
|
|
317
387
|
config_set
|
318
388
|
end
|
319
389
|
|
320
|
-
#
|
321
|
-
#
|
322
|
-
#
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
ref = @cmd_ref.lookup(feature, name)
|
339
|
-
|
340
|
-
begin
|
341
|
-
token = build_config_get_token(feature, ref, args)
|
342
|
-
rescue IndexError, TypeError
|
343
|
-
# IndexError if value is not set, TypeError if set to nil explicitly
|
344
|
-
token = nil
|
345
|
-
end
|
346
|
-
if token.kind_of?(String) and token[0] == '/' and token[-1] == '/'
|
347
|
-
raise RuntimeError unless args.length == token.scan(/%/).length
|
348
|
-
# convert string to regexp and replace %s with args
|
349
|
-
token = Regexp.new(sprintf(token, *args)[1..-2])
|
350
|
-
text = build_config_get(feature, ref, :ascii)
|
351
|
-
return Cisco.find_ascii(text, token)
|
352
|
-
elsif token.kind_of?(String)
|
353
|
-
hash = build_config_get(feature, ref, :structured)
|
354
|
-
return hash[token]
|
355
|
-
|
356
|
-
elsif token.kind_of?(Array)
|
357
|
-
# Array of /regexps/ -> ascii, array of strings/ints -> structured
|
358
|
-
if token[0].kind_of?(String) and
|
359
|
-
token[0][0] == '/' and
|
360
|
-
(token[0][-1] == '/' or token[0][-2..-1] == '/i')
|
361
|
-
|
362
|
-
token = token_str_to_regexp(token, args)
|
363
|
-
text = build_config_get(feature, ref, :ascii)
|
364
|
-
return Cisco.find_ascii(text, token[-1], *token[0..-2])
|
365
|
-
|
366
|
-
else
|
367
|
-
result = build_config_get(feature, ref, :structured)
|
368
|
-
begin
|
369
|
-
token.each do |token|
|
370
|
-
# if token is a hash and result is an array, check each
|
371
|
-
# array index (which should return another hash) to see if
|
372
|
-
# it contains the matching key/value pairs specified in token,
|
373
|
-
# and return the first match (or nil)
|
374
|
-
if token.kind_of?(Hash)
|
375
|
-
raise "Expected array, got #{result.class}" unless result.kind_of?(Array)
|
376
|
-
result = result.select { |x| token.all? { |k, v| x[k] == v } }
|
377
|
-
raise "Multiple matches found for #{token}" if result.length > 1
|
378
|
-
raise "No match found for #{token}" if result.length == 0
|
379
|
-
result = result[0]
|
380
|
-
else # result is array or hash
|
381
|
-
raise "No key \"#{token}\" in #{result}" if result[token].nil?
|
382
|
-
result = result[token]
|
383
|
-
end
|
384
|
-
end
|
385
|
-
return result
|
386
|
-
rescue Exception => e
|
387
|
-
# TODO: logging user story, Syslog isn't available here
|
388
|
-
# Syslog.debug(e.message)
|
389
|
-
return nil
|
390
|
-
end
|
391
|
-
end
|
392
|
-
elsif token.nil?
|
393
|
-
return show(ref.config_get, :structured)
|
394
|
-
end
|
395
|
-
raise TypeError("Unclear to handle config_get_token #{token}")
|
396
|
-
end
|
397
|
-
|
398
|
-
# Uses CommandReference to lookup the default value for a given
|
399
|
-
# feature and feature property.
|
400
|
-
#
|
401
|
-
# @raise [IndexError] if the given (feature, name) pair is not in the
|
402
|
-
# CommandReference data or if the data doesn't have values defined
|
403
|
-
# for the 'default_value' field.
|
404
|
-
# @param feature [String]
|
405
|
-
# @param name [String]
|
406
|
-
# @return [String]
|
407
|
-
# @example config_get_default("vtp", "file")
|
408
|
-
def config_get_default(feature, name)
|
409
|
-
raise "lazy_connect specified but did not request connect" unless @cmd_ref
|
410
|
-
ref = @cmd_ref.lookup(feature, name)
|
411
|
-
ref.default_value
|
412
|
-
end
|
413
|
-
|
414
|
-
# Uses CommandReference to look up the given config command(s) of interest
|
415
|
-
# and then applies the configuration.
|
416
|
-
#
|
417
|
-
# @raise [IndexError] if no relevant cmd_ref config_set exists
|
418
|
-
# @raise [ArgumentError] if too many or too few args are provided.
|
419
|
-
# @raise [Cisco::CliError] if any command is rejected by the device.
|
420
|
-
#
|
421
|
-
# @param feature [String]
|
422
|
-
# @param name [String]
|
423
|
-
# @param args [*String] zero or more args to be substituted into the cmdref.
|
424
|
-
# @example config_set("vtp", "domain", "example.com")
|
425
|
-
# @example config_set("ospf", "router_id",
|
426
|
-
# {:name => "green", :vrf => "one", :state => "",
|
427
|
-
# :router_id => "192.0.0.1"})
|
428
|
-
def config_set(feature, name, *args)
|
429
|
-
raise "lazy_connect specified but did not request connect" unless @cmd_ref
|
430
|
-
ref = @cmd_ref.lookup(feature, name)
|
431
|
-
config_set = build_config_set(feature, ref, args)
|
432
|
-
if config_set.is_a?(String)
|
433
|
-
param_count = config_set.scan(/%/).length
|
434
|
-
elsif config_set.is_a?(Array)
|
435
|
-
param_count = config_set.join(" ").scan(/%/).length
|
436
|
-
else
|
437
|
-
raise TypeError, "%{config_set.class} not supported for config_set"
|
438
|
-
end
|
439
|
-
unless args[0].is_a? Hash
|
440
|
-
if param_count != args.length
|
441
|
-
raise ArgumentError.new("Wrong number of params - expected: " +
|
442
|
-
"#{param_count} actual: #{args.length}")
|
390
|
+
# Helper method for config_get().
|
391
|
+
# @param token [Array, Hash] lookup sequence
|
392
|
+
# @param result [Array, Hash] structured output from node
|
393
|
+
def config_get_handle_structured(token, result)
|
394
|
+
token.each do |t|
|
395
|
+
# if token is a hash and result is an array, check each
|
396
|
+
# array index (which should return another hash) to see if
|
397
|
+
# it contains the matching key/value pairs specified in token,
|
398
|
+
# and return the first match (or nil)
|
399
|
+
if t.kind_of?(Hash)
|
400
|
+
fail "Expected array, got #{result.class}" unless result.is_a? Array
|
401
|
+
result = result.select { |x| t.all? { |k, v| x[k] == v } }
|
402
|
+
fail "Multiple matches found for #{t}" if result.length > 1
|
403
|
+
fail "No match found for #{t}" if result.length == 0
|
404
|
+
result = result[0]
|
405
|
+
else # result is array or hash
|
406
|
+
fail "No key \"#{t}\" in #{result}" if result[t].nil?
|
407
|
+
result = result[t]
|
443
408
|
end
|
444
409
|
end
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
param_count = line.scan(/%/).length
|
451
|
-
if param_count > 0
|
452
|
-
new_config_set << sprintf(line, *args)
|
453
|
-
args = args[param_count..-1]
|
454
|
-
else
|
455
|
-
new_config_set << line
|
456
|
-
end
|
457
|
-
end
|
458
|
-
config(new_config_set)
|
459
|
-
end
|
410
|
+
result
|
411
|
+
rescue RuntimeError
|
412
|
+
# TODO: logging user story, Syslog isn't available here
|
413
|
+
# Syslog.debug(e.message)
|
414
|
+
nil
|
460
415
|
end
|
461
416
|
|
462
417
|
# Send a config command to the device.
|
@@ -465,6 +420,7 @@ module Cisco
|
|
465
420
|
#
|
466
421
|
# @raise [Cisco::CliError] if any command is rejected by the device.
|
467
422
|
def config(commands)
|
423
|
+
CiscoLogger.debug("CLI Sent to device: #{commands}")
|
468
424
|
@client.config(commands)
|
469
425
|
rescue CiscoNxapi::CliError => e
|
470
426
|
raise Cisco::CliError.new(e.input, e.clierror, e.previous)
|
@@ -483,52 +439,52 @@ module Cisco
|
|
483
439
|
|
484
440
|
# @return [String] such as "Cisco Nexus Operating System (NX-OS) Software"
|
485
441
|
def os
|
486
|
-
|
487
|
-
|
488
|
-
|
442
|
+
o = config_get('show_version', 'header')
|
443
|
+
fail 'failed to retrieve operating system information' if o.nil?
|
444
|
+
o.split("\n")[0]
|
489
445
|
end
|
490
446
|
|
491
447
|
# @return [String] such as "6.0(2)U5(1) [build 6.0(2)U5(0.941)]"
|
492
448
|
def os_version
|
493
|
-
config_get(
|
449
|
+
config_get('show_version', 'version')
|
494
450
|
end
|
495
451
|
|
496
452
|
# @return [String] such as "Nexus 3048 Chassis"
|
497
453
|
def product_description
|
498
|
-
config_get(
|
454
|
+
config_get('show_version', 'description')
|
499
455
|
end
|
500
456
|
|
501
457
|
# @return [String] such as "N3K-C3048TP-1GE"
|
502
458
|
def product_id
|
503
459
|
if @cmd_ref
|
504
|
-
return config_get(
|
460
|
+
return config_get('inventory', 'productid')
|
505
461
|
else
|
506
462
|
# We use this function to *find* the appropriate CommandReference
|
507
|
-
entries = show(
|
508
|
-
return entries[
|
463
|
+
entries = show('show inventory', :structured)
|
464
|
+
return entries['TABLE_inv']['ROW_inv'][0]['productid']
|
509
465
|
end
|
510
466
|
end
|
511
467
|
|
512
468
|
# @return [String] such as "V01"
|
513
469
|
def product_version_id
|
514
|
-
config_get(
|
470
|
+
config_get('inventory', 'versionid')
|
515
471
|
end
|
516
472
|
|
517
473
|
# @return [String] such as "FOC1722R0ET"
|
518
474
|
def product_serial_number
|
519
|
-
config_get(
|
475
|
+
config_get('inventory', 'serialnum')
|
520
476
|
end
|
521
477
|
|
522
478
|
# @return [String] such as "bxb-oa-n3k-7"
|
523
479
|
def host_name
|
524
|
-
config_get(
|
480
|
+
config_get('show_version', 'host_name')
|
525
481
|
end
|
526
482
|
|
527
483
|
# @return [String] such as "example.com"
|
528
484
|
def domain_name
|
529
|
-
result = config_get(
|
485
|
+
result = config_get('dnsclient', 'domain_name')
|
530
486
|
if result.nil?
|
531
|
-
return
|
487
|
+
return ''
|
532
488
|
else
|
533
489
|
return result[0]
|
534
490
|
end
|
@@ -537,8 +493,8 @@ module Cisco
|
|
537
493
|
# @return [Integer] System uptime, in seconds
|
538
494
|
def system_uptime
|
539
495
|
cache_flush
|
540
|
-
t = config_get(
|
541
|
-
|
496
|
+
t = config_get('show_system', 'uptime')
|
497
|
+
fail 'failed to retrieve system uptime' if t.nil?
|
542
498
|
t = t.shift
|
543
499
|
# time units: t = ["0", "23", "15", "49"]
|
544
500
|
t.map!(&:to_i)
|
@@ -548,8 +504,8 @@ module Cisco
|
|
548
504
|
|
549
505
|
# @return [String] timestamp of last reset time
|
550
506
|
def last_reset_time
|
551
|
-
output = config_get(
|
552
|
-
return
|
507
|
+
output = config_get('show_version', 'last_reset_time')
|
508
|
+
return '' if output.nil?
|
553
509
|
# NX-OS may provide leading/trailing whitespace:
|
554
510
|
# " Sat Oct 25 00:39:25 2014\n"
|
555
511
|
# so be sure to strip() it down to the actual string.
|
@@ -558,26 +514,26 @@ module Cisco
|
|
558
514
|
|
559
515
|
# @return [String] such as "Reset Requested by CLI command reload"
|
560
516
|
def last_reset_reason
|
561
|
-
config_get(
|
517
|
+
config_get('show_version', 'last_reset_reason')
|
562
518
|
end
|
563
519
|
|
564
520
|
# @return [Float] combined user/kernel CPU utilization
|
565
521
|
def system_cpu_utilization
|
566
|
-
output = config_get(
|
567
|
-
|
568
|
-
output[
|
522
|
+
output = config_get('system', 'resources')
|
523
|
+
fail 'failed to retrieve cpu utilization' if output.nil?
|
524
|
+
output['cpu_state_user'].to_f + output['cpu_state_kernel'].to_f
|
569
525
|
end
|
570
526
|
|
571
527
|
# @return [String] such as
|
572
528
|
# "bootflash:///n3000-uk9-kickstart.6.0.2.U5.0.941.bin"
|
573
529
|
def boot
|
574
|
-
config_get(
|
530
|
+
config_get('show_version', 'boot_image')
|
575
531
|
end
|
576
532
|
|
577
533
|
# @return [String] such as
|
578
534
|
# "bootflash:///n3000-uk9.6.0.2.U5.0.941.bin"
|
579
535
|
def system
|
580
|
-
config_get(
|
536
|
+
config_get('show_version', 'system_image')
|
581
537
|
end
|
582
538
|
end
|
583
539
|
|
@@ -598,8 +554,8 @@ module Cisco
|
|
598
554
|
# => 'example.com'
|
599
555
|
def find_one_ascii(body, regex_query, *parent_cfg)
|
600
556
|
matches = find_ascii(body, regex_query, *parent_cfg)
|
601
|
-
return
|
602
|
-
|
557
|
+
return '' if matches.nil?
|
558
|
+
fail RuntimeError if matches.length > 1
|
603
559
|
matches[0]
|
604
560
|
end
|
605
561
|
module_function :find_one_ascii
|
@@ -623,11 +579,11 @@ module Cisco
|
|
623
579
|
# bgp_afs = find_ascii(show_run_bgp, /^address-family (.*)/,
|
624
580
|
# /^router bgp #{ASN}/)
|
625
581
|
def find_ascii(body, regex_query, *parent_cfg)
|
626
|
-
return nil if body.nil?
|
582
|
+
return nil if body.nil? || regex_query.nil?
|
627
583
|
|
628
584
|
# get subconfig
|
629
585
|
parent_cfg.each { |p| body = find_subconfig(body, p) }
|
630
|
-
if body.nil?
|
586
|
+
if body.nil? || body.empty?
|
631
587
|
return nil
|
632
588
|
else
|
633
589
|
# find matches and return as array of String if it only does one
|
@@ -635,7 +591,7 @@ module Cisco
|
|
635
591
|
match = body.split("\n").map { |s| s.scan(regex_query) }
|
636
592
|
match = match.flatten(1)
|
637
593
|
return nil if match.empty?
|
638
|
-
match = match.flatten if match[0].is_a?(Array)
|
594
|
+
match = match.flatten if match[0].is_a?(Array) && match[0].length == 1
|
639
595
|
return match
|
640
596
|
end
|
641
597
|
end
|
@@ -649,16 +605,16 @@ module Cisco
|
|
649
605
|
# @return [String, nil] the subsection of body, de-indented
|
650
606
|
# appropriately, or nil if no such subsection exists.
|
651
607
|
def find_subconfig(body, regex_query)
|
652
|
-
return nil if body.nil?
|
608
|
+
return nil if body.nil? || regex_query.nil?
|
653
609
|
|
654
610
|
rows = body.split("\n")
|
655
611
|
match_row_index = rows.index { |row| regex_query =~ row }
|
656
612
|
return nil if match_row_index.nil?
|
657
613
|
|
658
|
-
cur = match_row_index+1
|
614
|
+
cur = match_row_index + 1
|
659
615
|
subconfig = []
|
660
616
|
|
661
|
-
until (/\A\s+.*/ =~ rows[cur]).nil?
|
617
|
+
until (/\A\s+.*/ =~ rows[cur]).nil? || cur == rows.length
|
662
618
|
subconfig << rows[cur]
|
663
619
|
cur += 1
|
664
620
|
end
|