2 # ============LICENSE_START====================================================
3 # org.onap.vvp/validation-scripts
4 # ===================================================================
5 # Copyright © 2017 AT&T Intellectual Property. All rights reserved.
6 # ===================================================================
8 # Unless otherwise specified, all software contained herein is licensed
9 # under the Apache License, Version 2.0 (the "License");
10 # you may not use this software except in compliance with the License.
11 # You may obtain a copy of the License at
13 # http://www.apache.org/licenses/LICENSE-2.0
15 # Unless required by applicable law or agreed to in writing, software
16 # distributed under the License is distributed on an "AS IS" BASIS,
17 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 # See the License for the specific language governing permissions and
19 # limitations under the License.
23 # Unless otherwise specified, all documentation contained herein is licensed
24 # under the Creative Commons License, Attribution 4.0 Intl. (the "License");
25 # you may not use this documentation except in compliance with the License.
26 # You may obtain a copy of the License at
28 # https://creativecommons.org/licenses/by/4.0/
30 # Unless required by applicable law or agreed to in writing, documentation
31 # distributed under the License is distributed on an "AS IS" BASIS,
32 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
33 # See the License for the specific language governing permissions and
34 # limitations under the License.
36 # ============LICENSE_END============================================
42 from functools import lru_cache
43 from os import path, listdir
45 from tests import cached_yaml as yaml
46 from tests.structures import Heat
48 from tests.helpers import load_yaml
54 0 -> 1 -> 2 -> too deep.
59 def check_for_invalid_nesting( # pylint: disable=too-many-branches
60 yml, yaml_file, dirpath
63 return a list of all nested files
65 if not hasattr(yml, "items"):
68 p = re.compile("^[A-z]*::[A-z]*::[A-z]*$")
70 for v in yml.values():
71 if isinstance(v, dict) and "type" in v:
73 if t.endswith(".yml") or t.endswith(".yaml"):
74 filepath = path.join(dirpath, t)
75 elif t == "OS::Heat::ResourceGroup":
76 rd = v["properties"]["resource_def"]
77 if not isinstance(rd, dict) or "type" not in rd:
78 invalid_nesting.append(yaml_file)
80 elif not p.match(rd["type"]):
81 filepath = path.join(dirpath, rd["type"])
87 with open(filepath) as fh:
89 except yaml.YAMLError as e:
90 invalid_nesting.append(filepath)
91 print(e) # pylint: disable=superfluous-parens
92 invalid_nesting.extend(check_for_invalid_nesting(yml, filepath, dirpath))
93 if isinstance(v, dict):
94 invalid_nesting.extend(check_for_invalid_nesting(v, yaml_file, dirpath))
95 elif isinstance(v, list):
97 invalid_nesting.extend(check_for_invalid_nesting(d, yaml_file, dirpath))
98 return invalid_nesting
101 def get_dict_of_nested_files(yml, dirpath):
103 key: resource id in yml which references a nested file.
104 value: the nested file name.
105 Nested files are either referenced through "type", or
106 for OS::Heat::ResourceGroup, through "resource_def type".
108 nested_files = get_type_nested_files(yml, dirpath)
109 nested_files.update(get_resourcegroup_nested_files(yml, dirpath))
113 @lru_cache(maxsize=None)
114 def get_list_of_nested_files(yml_path, dirpath):
116 return a list of all nested files
119 yml = load_yaml(yml_path)
121 resources = yml.get("resources") or {}
123 for v in resources.values():
124 if isinstance(v, dict) and "type" in v:
126 if t.endswith(".yml") or t.endswith(".yaml"):
127 filepath = path.join(dirpath, t)
128 if path.exists(filepath):
129 nested_files.append(filepath)
130 nested_files.extend(get_list_of_nested_files(filepath, dirpath))
131 elif t == "OS::Heat::ResourceGroup":
132 rdt = v.get("properties", {}).get("resource_def", {}).get("type", None)
133 if rdt and (rdt.endswith(".yml") or rdt.endswith(".yaml")):
134 filepath = path.join(dirpath, rdt)
135 if path.exists(filepath):
136 nested_files.append(filepath)
137 nested_files.extend(get_list_of_nested_files(filepath, dirpath))
141 def get_nesting(yaml_files):
142 """return bad, files, heat, depths
143 bad - list of error messages.
144 files - dict: key is filename, value is dict of nested files.
146 heat - dict,: key is filename, value is Heat instance.
147 depths - dict: key is filename, value is a depth tuple
150 file: template -> nested -> nested -> nested
157 for yaml_file in yaml_files:
158 dirname, basename = path.split(yaml_file)
159 h = Heat(filepath=yaml_file)
161 files[basename] = get_dict_of_nested_files(h.yml, dirname)
162 for filename in files:
163 depths[filename] = _get_nesting_depth_start(0, filename, files, [])
164 for depth in depths[filename]:
165 if depth[0] > MAX_DEPTH:
166 bad.append("{} {}".format(filename, str(depth[1])))
167 return bad, files, heat, depths
170 def _get_nesting_depth_start(depth, filename, files, context):
172 for rid, nf in files[filename].items():
173 depths.append(_get_nesting_depth(1, nf, files, context))
177 def _get_nesting_depth(depth, filename, files, context):
178 """Return a depth tuple (max_depth, current_context).
179 `context` is the list of filenames.
180 `depth` is the length of `context`.
181 Finds the max_depth of all the resources of `filename`.
182 current_context is the updated list of filenames
183 and max_depth is its length.
185 max_depth = depth + 1
186 current_context = context + [filename]
187 if depth <= MAX_DEPTH:
188 nested_filenames = files.get(filename, {})
190 max_depth, current_context = max(
191 _get_nesting_depth(depth + 1, nested_filename, files, current_context)
192 for nested_filename in nested_filenames.values()
194 return max_depth, current_context
197 def get_resourcegroup_nested_files(yml, dirpath):
200 key: key in yml which references a nested ResourceGroup file.
201 (resource->type is ResourceGroup
202 and resource->properties->resource_def->type is a yaml file)
203 value: the nested file name.
205 The keys are assumed to be unique across files.
206 A separate test checks for that.
209 if not hasattr(yml, "get"):
213 for rid, r in yml.get("resources", {}).items():
214 if isinstance(r, dict) and "type" in r:
217 if t == "OS::Heat::ResourceGroup":
218 rdt = r.get("properties", {}).get("resource_def", {}).get("type", None)
219 if rdt and (rdt.endswith(".yml") or rdt.endswith(".yaml")):
222 filepath = path.join(dirpath, nested_file)
223 if path.exists(filepath):
224 nested_files[rid] = nested_file
228 def get_type_nested_files(yml, dirpath):
231 key: key in yml which references a nested type file.
232 (the resource "type" is a yaml file.)
233 value: the nested file name.
235 The keys are assumed to be unique across files.
236 A separate test checks for that.
239 if not hasattr(yml, "get"):
243 for rid, r in yml.get("resources", {}).items():
244 if isinstance(r, dict) and "type" in r:
247 if t.endswith(".yml") or t.endswith(".yaml"):
250 filepath = path.join(dirpath, nested_file)
251 if path.exists(filepath):
252 nested_files[rid] = nested_file
256 def get_nested_files(filenames):
258 returns all the nested files for a set of filenames
261 for filename in filenames:
262 if file_is_a_nested_template(filename):
263 nested_files.append(filename)
267 @lru_cache(maxsize=None)
268 def file_is_a_nested_template(file):
269 directory = path.dirname(file)
271 for filename in listdir(directory):
272 if filename.endswith(".yaml") or filename.endswith(".yml"):
273 filename = "{}/{}".format(directory, filename)
276 get_list_of_nested_files(filename, path.dirname(filename))
278 except yaml.YAMLError as e:
279 print(e) # pylint: disable=superfluous-parens
281 return file in nested_files