cisco_node_utils 0.9.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 +7 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +3 -0
- data/.rubocop_todo.yml +293 -0
- data/CHANGELOG.md +5 -0
- data/CONTRIBUTING.md +31 -0
- data/Gemfile +4 -0
- data/LICENSE +201 -0
- data/README.md +113 -0
- data/Rakefile +4 -0
- data/cisco_node_utils.gemspec +30 -0
- data/lib/cisco_node_utils.rb +33 -0
- data/lib/cisco_node_utils/README_YAML.md +333 -0
- data/lib/cisco_node_utils/cisco_cmn_utils.rb +92 -0
- data/lib/cisco_node_utils/command_reference.rb +415 -0
- data/lib/cisco_node_utils/command_reference_common.yaml +845 -0
- data/lib/cisco_node_utils/command_reference_n3064.yaml +13 -0
- data/lib/cisco_node_utils/command_reference_n7k.yaml +48 -0
- data/lib/cisco_node_utils/command_reference_n9k.yaml +35 -0
- data/lib/cisco_node_utils/configparser_lib.rb +196 -0
- data/lib/cisco_node_utils/interface.rb +501 -0
- data/lib/cisco_node_utils/interface_ospf.rb +241 -0
- data/lib/cisco_node_utils/node.rb +673 -0
- data/lib/cisco_node_utils/platform.rb +184 -0
- data/lib/cisco_node_utils/platform_info.rb +58 -0
- data/lib/cisco_node_utils/platform_info.yaml +10 -0
- data/lib/cisco_node_utils/router_ospf.rb +96 -0
- data/lib/cisco_node_utils/router_ospf_vrf.rb +258 -0
- data/lib/cisco_node_utils/snmpcommunity.rb +91 -0
- data/lib/cisco_node_utils/snmpgroup.rb +55 -0
- data/lib/cisco_node_utils/snmpserver.rb +150 -0
- data/lib/cisco_node_utils/snmpuser.rb +342 -0
- data/lib/cisco_node_utils/tacacs_server.rb +175 -0
- data/lib/cisco_node_utils/tacacs_server_host.rb +128 -0
- data/lib/cisco_node_utils/version.rb +17 -0
- data/lib/cisco_node_utils/vlan.rb +153 -0
- data/lib/cisco_node_utils/vtp.rb +127 -0
- data/lib/cisco_node_utils/yum.rb +84 -0
- data/tests/basetest.rb +93 -0
- data/tests/ciscotest.rb +136 -0
- data/tests/cmd_config.yaml +51 -0
- data/tests/cmd_config_invalid.yaml +16 -0
- data/tests/test_all_cisco.rb +46 -0
- data/tests/test_command_config.rb +192 -0
- data/tests/test_command_reference.rb +222 -0
- data/tests/test_interface.rb +1017 -0
- data/tests/test_interface_ospf.rb +763 -0
- data/tests/test_interface_svi.rb +267 -0
- data/tests/test_interface_switchport.rb +722 -0
- data/tests/test_node.rb +108 -0
- data/tests/test_node_ext.rb +450 -0
- data/tests/test_platform.rb +188 -0
- data/tests/test_router_ospf.rb +164 -0
- data/tests/test_router_ospf_vrf.rb +753 -0
- data/tests/test_snmpcommunity.rb +344 -0
- data/tests/test_snmpgroup.rb +71 -0
- data/tests/test_snmpserver.rb +443 -0
- data/tests/test_snmpuser.rb +803 -0
- data/tests/test_tacacs_server.rb +388 -0
- data/tests/test_tacacs_server_host.rb +391 -0
- data/tests/test_vlan.rb +264 -0
- data/tests/test_vtp.rb +319 -0
- data/tests/test_yum.rb +106 -0
- metadata +188 -0
@@ -0,0 +1,92 @@
|
|
1
|
+
# Common Utilities for Puppet Resources.
|
2
|
+
#
|
3
|
+
# Copyright (c) 2014-2015 Cisco and/or its affiliates.
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
module Cisco
|
18
|
+
# global constants
|
19
|
+
DEFAULT_INSTANCE_NAME = 'default'
|
20
|
+
|
21
|
+
class Encryption
|
22
|
+
# password encryption types
|
23
|
+
def Encryption.cli_to_symbol(cli)
|
24
|
+
case cli
|
25
|
+
when "0", 0
|
26
|
+
:cleartext
|
27
|
+
when "3", 3
|
28
|
+
:"3des" # yuck :-(
|
29
|
+
when "5", 5
|
30
|
+
:md5
|
31
|
+
when "6", 6
|
32
|
+
:aes
|
33
|
+
when "7", 7
|
34
|
+
:cisco_type_7
|
35
|
+
else
|
36
|
+
raise KeyError
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def Encryption.symbol_to_cli(symbol)
|
41
|
+
symbol = symbol.downcase if symbol.is_a? String
|
42
|
+
case symbol
|
43
|
+
when :cleartext, :none, "cleartext", "none", "0", 0
|
44
|
+
"0"
|
45
|
+
when :"3des", "3des", "3", 3
|
46
|
+
"3"
|
47
|
+
when :md5, "md5", "5", 5
|
48
|
+
"5"
|
49
|
+
when :aes, "aes", "6", 6
|
50
|
+
"6"
|
51
|
+
when :cisco_type_7, :type_7, "cisco_type_7", "type_7", "7", 7
|
52
|
+
"7"
|
53
|
+
else
|
54
|
+
raise KeyError
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class ChefUtils
|
60
|
+
def ChefUtils.generic_prop_set(klass, rlbname, props)
|
61
|
+
props.each do |prop|
|
62
|
+
klass.instance_eval {
|
63
|
+
# Helper Chef setter method, e.g.:
|
64
|
+
# if @new_resource.foo.nil?
|
65
|
+
# def_prop = @rlb.default_foo
|
66
|
+
# @new_resource.foo(def_prop)
|
67
|
+
# end
|
68
|
+
# current = @rlb.foo
|
69
|
+
# if current != @new_resource.foo
|
70
|
+
# converge_by("update foo '#{current}' => " +
|
71
|
+
# "'#{@new_resource.foo}'") do
|
72
|
+
# @rlb.foo=(@new_resource.foo)
|
73
|
+
# end
|
74
|
+
# end
|
75
|
+
if @new_resource.send(prop).nil?
|
76
|
+
def_prop = instance_variable_get(rlbname).send("default_#{prop}")
|
77
|
+
# Set resource to default if recipe property is not specified
|
78
|
+
@new_resource.send(prop, def_prop)
|
79
|
+
end
|
80
|
+
current = instance_variable_get(rlbname).send(prop)
|
81
|
+
if current != @new_resource.send(prop)
|
82
|
+
converge_by("update #{prop} '#{current}' => " +
|
83
|
+
"'#{@new_resource.send(prop)}'") do
|
84
|
+
instance_variable_get(rlbname).send("#{prop}=",
|
85
|
+
@new_resource.send(prop))
|
86
|
+
end
|
87
|
+
end
|
88
|
+
}
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end # class ChefUtils
|
92
|
+
end # module Cisco
|
@@ -0,0 +1,415 @@
|
|
1
|
+
# CommandReference module for testing.
|
2
|
+
#
|
3
|
+
# Copyright (c) 2014-2015 Cisco and/or its affiliates.
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
require 'yaml'
|
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
|
+
raise ArgumentError
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def file=(file)
|
38
|
+
if file.is_a? String
|
39
|
+
@file = file
|
40
|
+
else
|
41
|
+
raise ArgumentError
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def match(product)
|
46
|
+
@regex.match(product)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Control a reference for an attribute.
|
51
|
+
class CmdRef
|
52
|
+
attr_reader :feature
|
53
|
+
attr_reader :name
|
54
|
+
attr_reader :sources
|
55
|
+
attr_reader :hash
|
56
|
+
|
57
|
+
@@KEYS = %w(default_value
|
58
|
+
config_set config_set_append
|
59
|
+
config_get config_get_token config_get_token_append
|
60
|
+
test_config_get test_config_get_regex test_config_result)
|
61
|
+
|
62
|
+
def initialize(feature, name, ref, source)
|
63
|
+
raise ArgumentError, "'#{ref}' is not a hash." unless ref.is_a? Hash
|
64
|
+
|
65
|
+
@feature = feature
|
66
|
+
@name = name
|
67
|
+
@hash = {}
|
68
|
+
|
69
|
+
@sources = []
|
70
|
+
merge(ref, source)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Overwrite values from more specific references.
|
74
|
+
def merge(values, file)
|
75
|
+
values.each { |key, value|
|
76
|
+
unless @@KEYS.include?(key)
|
77
|
+
raise "Unrecognized key #{key} for #{feature}, #{name} in #{file}"
|
78
|
+
end
|
79
|
+
if value.nil?
|
80
|
+
# Some attributes can store an explicit nil.
|
81
|
+
# Others treat this as unset (allowing a platform to override common).
|
82
|
+
if key == "default_value"
|
83
|
+
@hash[key] = value
|
84
|
+
else
|
85
|
+
@hash.delete(key)
|
86
|
+
end
|
87
|
+
else
|
88
|
+
@hash[key] = value
|
89
|
+
end
|
90
|
+
}
|
91
|
+
@sources << file
|
92
|
+
end
|
93
|
+
|
94
|
+
def convert_to_constant(value)
|
95
|
+
# NOTE: This method is now deprecated and should not be used for future
|
96
|
+
# development.
|
97
|
+
#
|
98
|
+
# If value is a string and it is empty OR the first letter is lower case
|
99
|
+
# then leave value untouched.
|
100
|
+
# If value is a string and the first letter is uppercase this indicates
|
101
|
+
# that it could be a constant in Ruby so attempt to convert it to a Constant.
|
102
|
+
if value.is_a?(String) and not value.empty?
|
103
|
+
if value[0].chr == value[0].chr.upcase
|
104
|
+
begin
|
105
|
+
value = Object.const_get(value) if Object.const_defined?(value)
|
106
|
+
rescue NameError
|
107
|
+
# debug("#{value} looks like a constant but is not")
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
value
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_config_result(value)
|
115
|
+
result = @hash["test_config_result"][value]
|
116
|
+
convert_to_constant(result)
|
117
|
+
end
|
118
|
+
|
119
|
+
def method_missing(method_name, *args, &block)
|
120
|
+
super(method_name, *args, &block) unless @@KEYS.include?(method_name.to_s)
|
121
|
+
method_name = method_name.to_s
|
122
|
+
unless @hash.include?(method_name)
|
123
|
+
raise IndexError, "No #{method_name} defined for #{@feature}, #{@name}"
|
124
|
+
end
|
125
|
+
# puts("get #{method_name}: '#{@hash[method_name]}'")
|
126
|
+
@hash[method_name]
|
127
|
+
end
|
128
|
+
|
129
|
+
# Print useful debugging information about the object.
|
130
|
+
def to_s
|
131
|
+
str = ""
|
132
|
+
str << "Command: #{@feature} #{@name}\n"
|
133
|
+
@hash.each { |key, value|
|
134
|
+
str << " #{key}: #{value}\n"
|
135
|
+
}
|
136
|
+
str
|
137
|
+
end
|
138
|
+
|
139
|
+
# Check that all necessary values have been populated.
|
140
|
+
def valid?
|
141
|
+
return false unless @feature and @name
|
142
|
+
true
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# Builds reference hash for the platform specified in the product id.
|
147
|
+
class CommandReference
|
148
|
+
@@debug = false
|
149
|
+
|
150
|
+
def self.debug
|
151
|
+
@@debug
|
152
|
+
end
|
153
|
+
|
154
|
+
def self.debug=(value)
|
155
|
+
raise ArgumentError, "Debug must be boolean" unless value == true or value == false
|
156
|
+
@@debug = value
|
157
|
+
end
|
158
|
+
|
159
|
+
attr_reader :files, :product_id
|
160
|
+
|
161
|
+
# Constructor.
|
162
|
+
# Normal usage is to pass product_id only, in which case all usual YAML
|
163
|
+
# files will be located then the list will be filtered down to only those
|
164
|
+
# matching the given product_id.
|
165
|
+
# For testing purposes (only!) you can pass an explicit list of files to
|
166
|
+
# load instead. This list will NOT be filtered further by product_id.
|
167
|
+
def initialize(product_id, files=nil)
|
168
|
+
@product_id = product_id
|
169
|
+
@hash = {}
|
170
|
+
if files
|
171
|
+
@files = files
|
172
|
+
else
|
173
|
+
@files = []
|
174
|
+
# Hashes are unordered in Ruby 1.8.7. Instead, we use an array of objects.
|
175
|
+
platforms = [
|
176
|
+
CommandPlatformFile.new(//,
|
177
|
+
File.join(File.dirname(__FILE__),
|
178
|
+
"command_reference_common.yaml")),
|
179
|
+
CommandPlatformFile.new(/N9K/,
|
180
|
+
File.join(File.dirname(__FILE__),
|
181
|
+
"command_reference_n9k.yaml")),
|
182
|
+
CommandPlatformFile.new(/N7K/,
|
183
|
+
File.join(File.dirname(__FILE__),
|
184
|
+
"command_reference_n7k.yaml")),
|
185
|
+
CommandPlatformFile.new(/C3064/,
|
186
|
+
File.join(File.dirname(__FILE__),
|
187
|
+
"command_reference_n3064.yaml")),
|
188
|
+
]
|
189
|
+
# Build array
|
190
|
+
platforms.each { |reference|
|
191
|
+
@files << reference.file if reference.match(@product_id)
|
192
|
+
}
|
193
|
+
end
|
194
|
+
|
195
|
+
build_cmd_ref
|
196
|
+
end
|
197
|
+
|
198
|
+
# Build complete reference hash.
|
199
|
+
def build_cmd_ref
|
200
|
+
# Example id's: N3K-C3048TP-1GE, N3K-C3064PQ-10GE, N7K-C7009, N7K-C7009
|
201
|
+
|
202
|
+
debug "Product: #{@product_id}"
|
203
|
+
debug "Files being used: #{@files.join(', ')}"
|
204
|
+
|
205
|
+
reference_yaml = {}
|
206
|
+
|
207
|
+
@files.each { |file|
|
208
|
+
debug "Processing file '#{file}'"
|
209
|
+
reference_yaml = load_yaml(file)
|
210
|
+
|
211
|
+
reference_yaml.each { |feature, names|
|
212
|
+
if names.nil? or names.empty?
|
213
|
+
raise "No names under feature #{feature}: #{names}"
|
214
|
+
elsif @hash[feature].nil?
|
215
|
+
@hash[feature] = {}
|
216
|
+
else
|
217
|
+
debug " Merging feature '#{feature}' retrieved from '#{file}'."
|
218
|
+
end
|
219
|
+
names.each { |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}' from '#{file}'."
|
229
|
+
@hash[feature][name].merge(values, file)
|
230
|
+
end
|
231
|
+
}
|
232
|
+
}
|
233
|
+
}
|
234
|
+
|
235
|
+
raise "Missing values in CommandReference." unless valid?
|
236
|
+
end
|
237
|
+
|
238
|
+
# Get the command reference
|
239
|
+
def lookup(feature, name)
|
240
|
+
begin
|
241
|
+
value = @hash[feature][name]
|
242
|
+
rescue NoMethodError
|
243
|
+
# happens if @hash[feature] doesn't exist
|
244
|
+
value = nil
|
245
|
+
end
|
246
|
+
if value.nil?
|
247
|
+
raise IndexError, "No CmdRef defined for #{feature}, #{name}"
|
248
|
+
end
|
249
|
+
value
|
250
|
+
end
|
251
|
+
|
252
|
+
def empty?
|
253
|
+
@hash.empty?
|
254
|
+
end
|
255
|
+
|
256
|
+
# Print debug statements
|
257
|
+
def debug(text)
|
258
|
+
puts "DEBUG: #{text}" if @@debug
|
259
|
+
end
|
260
|
+
|
261
|
+
def is_mapping?(node)
|
262
|
+
# Need to handle both Syck::Map and Psych::Nodes::Mapping
|
263
|
+
node.class.ancestors.any? { |name| /Map/ =~ name.to_s }
|
264
|
+
end
|
265
|
+
private :is_mapping?
|
266
|
+
|
267
|
+
def get_keys_values_from_map(node)
|
268
|
+
if node.class.ancestors.any? { |name| /Psych/ =~ name.to_s }
|
269
|
+
# A Psych::Node::Mapping instance has an Array of children in
|
270
|
+
# the format [key1, val1, key2, val2]
|
271
|
+
key_children = node.children.select.each_with_index { |n, i| i.even? }
|
272
|
+
val_children = node.children.select.each_with_index { |n, i| i.odd? }
|
273
|
+
else
|
274
|
+
# Syck::Map nodes have a .children method but it doesn't work :-(
|
275
|
+
# Instead we access the node.value which is a hash.
|
276
|
+
key_children = node.value.keys
|
277
|
+
val_children = node.value.values
|
278
|
+
end
|
279
|
+
debug "children of #{node} mapping: #{key_children}, #{val_children}"
|
280
|
+
[key_children, val_children]
|
281
|
+
end
|
282
|
+
private :get_keys_values_from_map
|
283
|
+
|
284
|
+
# Validate the YAML node tree before converting it into Ruby
|
285
|
+
# data structures.
|
286
|
+
#
|
287
|
+
# @raise RuntimeError if the node tree is not valid by our constraints.
|
288
|
+
#
|
289
|
+
# @param node Node to be validated, then recurse to its children.
|
290
|
+
# @param filename File that YAML was parsed from, for messages
|
291
|
+
# @param depth Depth into the node tree
|
292
|
+
# @param parents String describing parents of this node, for messages
|
293
|
+
def validate_yaml(node, filename, depth=0, parents=nil)
|
294
|
+
return unless node and (is_mapping?(node) or node.children)
|
295
|
+
# Psych wraps everything in a Document instance, while
|
296
|
+
# Syck does not. To keep the "depth" counting consistent,
|
297
|
+
# we need to ignore Documents.
|
298
|
+
depth += 1 unless node.class.ancestors.any? { |name| /Document/ =~ name.to_s }
|
299
|
+
debug "Validating #{node.class} at depth #{depth}"
|
300
|
+
|
301
|
+
# No special validation for non-mapping nodes - just recurse
|
302
|
+
unless is_mapping?(node)
|
303
|
+
node.children.each { |child|
|
304
|
+
validate_yaml(child, filename, depth, parents)
|
305
|
+
}
|
306
|
+
return
|
307
|
+
end
|
308
|
+
|
309
|
+
# For Mappings, we validate more extensively:
|
310
|
+
# 1. no duplicate keys are allowed (Syck/Psych don't catch this)
|
311
|
+
# 2. Features must be listed in alphabetical order for maintainability
|
312
|
+
|
313
|
+
# Take advantage of our known YAML structure to assign labels by depth
|
314
|
+
label = %w(feature name param).fetch(depth, 'key')
|
315
|
+
|
316
|
+
# Get the key nodes and value nodes under this mapping
|
317
|
+
key_children, val_children = get_keys_values_from_map(node)
|
318
|
+
# Get an array of key names
|
319
|
+
key_arr = key_children.map(&:value)
|
320
|
+
|
321
|
+
# Make sure no duplicate key names.
|
322
|
+
# If searching from the start of the array finds a name at one index,
|
323
|
+
# but searching from the end of the array finds it at a different one,
|
324
|
+
# then we have a duplicate.
|
325
|
+
dup = key_arr.detect { |e| key_arr.index(e) != key_arr.rindex(e) }
|
326
|
+
if dup
|
327
|
+
msg = "Duplicate #{label} '#{dup}'#{parents} in #{filename}!"
|
328
|
+
raise msg
|
329
|
+
end
|
330
|
+
|
331
|
+
=begin
|
332
|
+
# Syck does not necessarily preserve ordering of keys in a mapping even during
|
333
|
+
# the initial parsing stage. To avoid spurious failures, this is disabled
|
334
|
+
# for now. Fixing this may require restructuring our YAML...
|
335
|
+
# Enforce alphabetical ordering of features (only).
|
336
|
+
# We can extend this later to enforce ordering of names if desired
|
337
|
+
# by checking at depth 2 as well.
|
338
|
+
if depth == 1
|
339
|
+
last_key = nil
|
340
|
+
key_arr.each do |key|
|
341
|
+
if last_key and key < last_key
|
342
|
+
raise RuntimeError, "features out of order (#{last_key} > #{key})"
|
343
|
+
end
|
344
|
+
last_key = key
|
345
|
+
end
|
346
|
+
end
|
347
|
+
=end
|
348
|
+
|
349
|
+
# Recurse to the children. We get a little fancy here so as to be able
|
350
|
+
# to provide more meaningful debug/error messages, such as:
|
351
|
+
# Duplicate param 'default_value' under feature 'foo', name 'bar'
|
352
|
+
key_children.zip(val_children).each do |key_node, val_node|
|
353
|
+
if parents
|
354
|
+
new_parents = parents + ", #{label} '#{key_node.value}'"
|
355
|
+
else
|
356
|
+
new_parents = " under #{label} '#{key_node.value}'"
|
357
|
+
end
|
358
|
+
validate_yaml(key_node, filename, depth, new_parents) # unnecessary?
|
359
|
+
validate_yaml(val_node, filename, depth, new_parents)
|
360
|
+
end
|
361
|
+
end
|
362
|
+
private :validate_yaml
|
363
|
+
|
364
|
+
# Read in yaml file.
|
365
|
+
def load_yaml(yaml_file)
|
366
|
+
raise "File #{yaml_file} doesn't exist." unless File.exist?(yaml_file)
|
367
|
+
# Parse YAML file into a tree of nodes
|
368
|
+
# Psych::SyntaxError doesn't inherit from StandardError in some versions,
|
369
|
+
# so we want to explicitly catch it if using Psych.
|
370
|
+
if defined?(::Psych::SyntaxError)
|
371
|
+
rescue_errors = [::StandardError, ::Psych::SyntaxError]
|
372
|
+
else
|
373
|
+
rescue_errors = [::StandardError]
|
374
|
+
end
|
375
|
+
yaml_parsed = File.open(yaml_file, 'r') do |f|
|
376
|
+
begin
|
377
|
+
YAML.parse(f)
|
378
|
+
rescue *rescue_errors => e
|
379
|
+
raise "unable to parse #{yaml_file}: #{e}"
|
380
|
+
end
|
381
|
+
end
|
382
|
+
if yaml_parsed
|
383
|
+
# Validate the node tree
|
384
|
+
validate_yaml(yaml_parsed, yaml_file)
|
385
|
+
# If validation passed, convert the node tree to a Ruby Hash.
|
386
|
+
return yaml_parsed.transform
|
387
|
+
else
|
388
|
+
# if yaml_file is empty, YAML.parse() returns false.
|
389
|
+
# Change this to an empty hash.
|
390
|
+
return {}
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
# Check that all resources were pulled in correctly.
|
395
|
+
def valid?
|
396
|
+
complete_status = true
|
397
|
+
@hash.each { |feature, names|
|
398
|
+
names.each { |name, ref|
|
399
|
+
status = ref.valid?
|
400
|
+
debug "Reference does not contain all supported values:\n#{ref}" unless status
|
401
|
+
complete_status = (status and complete_status)
|
402
|
+
}
|
403
|
+
}
|
404
|
+
complete_status
|
405
|
+
end
|
406
|
+
|
407
|
+
def to_s
|
408
|
+
@hash.each { |feature, names|
|
409
|
+
names.each { |name, ref|
|
410
|
+
ref.to_s
|
411
|
+
}
|
412
|
+
}
|
413
|
+
end
|
414
|
+
end
|
415
|
+
end
|