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============================================
38 # ECOMP is a trademark and service mark of AT&T Intellectual Property.
46 from tests import cached_yaml as yaml
47 from tests.structures import Heat
53 0 -> 1 -> 2 -> too deep.
58 def check_for_invalid_nesting( # pylint: disable=too-many-branches
59 yml, yaml_file, dirpath
62 return a list of all nested files
64 if not hasattr(yml, "items"):
67 p = re.compile("^[A-z]*::[A-z]*::[A-z]*$")
69 for v in yml.values():
70 if isinstance(v, dict) and "type" in v:
72 if t.endswith(".yml") or t.endswith(".yaml"):
73 filepath = path.join(dirpath, t)
74 elif t == "OS::Heat::ResourceGroup":
75 rd = v["properties"]["resource_def"]
76 if not isinstance(rd, dict) or "type" not in rd:
77 invalid_nesting.append(yaml_file)
79 elif not p.match(rd["type"]):
80 filepath = path.join(dirpath, rd["type"])
86 with open(filepath) as fh:
88 except yaml.YAMLError as e:
89 invalid_nesting.append(filepath)
90 print(e) # pylint: disable=superfluous-parens
91 invalid_nesting.extend(check_for_invalid_nesting(yml, filepath, dirpath))
92 if isinstance(v, dict):
93 invalid_nesting.extend(check_for_invalid_nesting(v, yaml_file, dirpath))
94 elif isinstance(v, list):
96 invalid_nesting.extend(check_for_invalid_nesting(d, yaml_file, dirpath))
97 return invalid_nesting
100 def get_dict_of_nested_files(yml, dirpath):
102 key: resource id in yml which references a nested file.
103 value: the nested file name.
104 Nested files are either referenced through "type", or
105 for OS::Heat::ResourceGroup, through "resource_def type".
107 nested_files = get_type_nested_files(yml, dirpath)
108 nested_files.update(get_resourcegroup_nested_files(yml, dirpath))
112 def get_list_of_nested_files(yml, dirpath):
114 return a list of all nested files
117 if not hasattr(yml, "items"):
122 for v in yml.values():
123 if isinstance(v, dict) and "type" in v:
125 if t.endswith(".yml") or t.endswith(".yaml"):
126 filepath = path.join(dirpath, t)
127 if path.exists(filepath):
128 with open(filepath) as fh:
129 t_yml = yaml.load(fh)
130 nested_files.append(filepath)
131 nested_files.extend(get_list_of_nested_files(t_yml, dirpath))
132 elif t == "OS::Heat::ResourceGroup":
133 rdt = v.get("properties", {}).get("resource_def", {}).get("type", None)
134 if rdt and (rdt.endswith(".yml") or rdt.endswith(".yaml")):
135 filepath = path.join(dirpath, rdt)
136 if path.exists(filepath):
137 with open(filepath) as fh:
138 rdt_yml = yaml.load(fh)
139 nested_files.append(filepath)
140 nested_files.extend(get_list_of_nested_files(rdt_yml, dirpath))
141 if isinstance(v, dict):
142 nested_files.extend(get_list_of_nested_files(v, dirpath))
143 elif isinstance(v, list):
145 nested_files.extend(get_list_of_nested_files(d, dirpath))
149 def get_nesting(yaml_files):
150 """return bad, files, heat, depths
151 bad - list of error messages.
152 files - dict: key is filename, value is dict of nested files.
154 heat - dict,: key is filename, value is Heat instance.
155 depths - dict: key is filename, value is a depth tuple
158 file: template -> nested -> nested -> nested
165 for yaml_file in yaml_files:
166 dirname, basename = path.split(yaml_file)
167 h = Heat(filepath=yaml_file)
169 files[basename] = get_dict_of_nested_files(h.yml, dirname)
170 for filename in files:
171 depths[filename] = _get_nesting_depth_start(0, filename, files, [])
172 for depth in depths[filename]:
173 if depth[0] > MAX_DEPTH:
174 bad.append("{} {}".format(filename, str(depth[1])))
175 return bad, files, heat, depths
178 def _get_nesting_depth_start(depth, filename, files, context):
180 for rid, nf in files[filename].items():
181 depths.append(_get_nesting_depth(1, nf, files, context))
185 def _get_nesting_depth(depth, filename, files, context):
186 """Return a depth tuple (max_depth, current_context).
187 `context` is the list of filenames.
188 `depth` is the length of `context`.
189 Finds the max_depth of all the resources of `filename`.
190 current_context is the updated list of filenames
191 and max_depth is its length.
193 max_depth = depth + 1
194 current_context = context + [filename]
195 if depth <= MAX_DEPTH:
196 nested_filenames = files.get(filename, {})
198 max_depth, current_context = max(
199 _get_nesting_depth(depth + 1, nested_filename, files, current_context)
200 for nested_filename in nested_filenames.values()
202 return max_depth, current_context
205 def get_resourcegroup_nested_files(yml, dirpath):
208 key: key in yml which references a nested ResourceGroup file.
209 (resource->type is ResourceGroup
210 and resource->properties->resource_def->type is a yaml file)
211 value: the nested file name.
213 The keys are assumed to be unique across files.
214 A separate test checks for that.
217 if not hasattr(yml, "get"):
221 for rid, r in yml.get("resources", {}).items():
222 if isinstance(r, dict) and "type" in r:
225 if t == "OS::Heat::ResourceGroup":
226 rdt = r.get("properties", {}).get("resource_def", {}).get("type", None)
227 if rdt and (rdt.endswith(".yml") or rdt.endswith(".yaml")):
230 filepath = path.join(dirpath, nested_file)
231 if path.exists(filepath):
232 nested_files[rid] = nested_file
236 def get_type_nested_files(yml, dirpath):
239 key: key in yml which references a nested type file.
240 (the resource "type" is a yaml file.)
241 value: the nested file name.
243 The keys are assumed to be unique across files.
244 A separate test checks for that.
247 if not hasattr(yml, "get"):
251 for rid, r in yml.get("resources", {}).items():
252 if isinstance(r, dict) and "type" in r:
255 if t.endswith(".yml") or t.endswith(".yaml"):
258 filepath = path.join(dirpath, nested_file)
259 if path.exists(filepath):
260 nested_files[rid] = nested_file