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============================================
45 from tests import cached_yaml as yaml
46 from tests.structures import Heat
52 0 -> 1 -> 2 -> too deep.
57 def check_for_invalid_nesting( # pylint: disable=too-many-branches
58 yml, yaml_file, dirpath
61 return a list of all nested files
63 if not hasattr(yml, "items"):
66 p = re.compile("^[A-z]*::[A-z]*::[A-z]*$")
68 for v in yml.values():
69 if isinstance(v, dict) and "type" in v:
71 if t.endswith(".yml") or t.endswith(".yaml"):
72 filepath = path.join(dirpath, t)
73 elif t == "OS::Heat::ResourceGroup":
74 rd = v["properties"]["resource_def"]
75 if not isinstance(rd, dict) or "type" not in rd:
76 invalid_nesting.append(yaml_file)
78 elif not p.match(rd["type"]):
79 filepath = path.join(dirpath, rd["type"])
85 with open(filepath) as fh:
87 except yaml.YAMLError as e:
88 invalid_nesting.append(filepath)
89 print(e) # pylint: disable=superfluous-parens
90 invalid_nesting.extend(check_for_invalid_nesting(yml, filepath, dirpath))
91 if isinstance(v, dict):
92 invalid_nesting.extend(check_for_invalid_nesting(v, yaml_file, dirpath))
93 elif isinstance(v, list):
95 invalid_nesting.extend(check_for_invalid_nesting(d, yaml_file, dirpath))
96 return invalid_nesting
99 def get_dict_of_nested_files(yml, dirpath):
101 key: resource id in yml which references a nested file.
102 value: the nested file name.
103 Nested files are either referenced through "type", or
104 for OS::Heat::ResourceGroup, through "resource_def type".
106 nested_files = get_type_nested_files(yml, dirpath)
107 nested_files.update(get_resourcegroup_nested_files(yml, dirpath))
111 def get_list_of_nested_files(yml, dirpath):
113 return a list of all nested files
116 if not hasattr(yml, "items"):
121 for v in yml.values():
122 if isinstance(v, dict) and "type" in v:
124 if t.endswith(".yml") or t.endswith(".yaml"):
125 filepath = path.join(dirpath, t)
126 if path.exists(filepath):
127 with open(filepath) as fh:
128 t_yml = yaml.load(fh)
129 nested_files.append(filepath)
130 nested_files.extend(get_list_of_nested_files(t_yml, 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 with open(filepath) as fh:
137 rdt_yml = yaml.load(fh)
138 nested_files.append(filepath)
139 nested_files.extend(get_list_of_nested_files(rdt_yml, dirpath))
140 if isinstance(v, dict):
141 nested_files.extend(get_list_of_nested_files(v, dirpath))
142 elif isinstance(v, list):
144 nested_files.extend(get_list_of_nested_files(d, dirpath))
148 def get_nesting(yaml_files):
149 """return bad, files, heat, depths
150 bad - list of error messages.
151 files - dict: key is filename, value is dict of nested files.
153 heat - dict,: key is filename, value is Heat instance.
154 depths - dict: key is filename, value is a depth tuple
157 file: template -> nested -> nested -> nested
164 for yaml_file in yaml_files:
165 dirname, basename = path.split(yaml_file)
166 h = Heat(filepath=yaml_file)
168 files[basename] = get_dict_of_nested_files(h.yml, dirname)
169 for filename in files:
170 depths[filename] = _get_nesting_depth_start(0, filename, files, [])
171 for depth in depths[filename]:
172 if depth[0] > MAX_DEPTH:
173 bad.append("{} {}".format(filename, str(depth[1])))
174 return bad, files, heat, depths
177 def _get_nesting_depth_start(depth, filename, files, context):
179 for rid, nf in files[filename].items():
180 depths.append(_get_nesting_depth(1, nf, files, context))
184 def _get_nesting_depth(depth, filename, files, context):
185 """Return a depth tuple (max_depth, current_context).
186 `context` is the list of filenames.
187 `depth` is the length of `context`.
188 Finds the max_depth of all the resources of `filename`.
189 current_context is the updated list of filenames
190 and max_depth is its length.
192 max_depth = depth + 1
193 current_context = context + [filename]
194 if depth <= MAX_DEPTH:
195 nested_filenames = files.get(filename, {})
197 max_depth, current_context = max(
198 _get_nesting_depth(depth + 1, nested_filename, files, current_context)
199 for nested_filename in nested_filenames.values()
201 return max_depth, current_context
204 def get_resourcegroup_nested_files(yml, dirpath):
207 key: key in yml which references a nested ResourceGroup file.
208 (resource->type is ResourceGroup
209 and resource->properties->resource_def->type is a yaml file)
210 value: the nested file name.
212 The keys are assumed to be unique across files.
213 A separate test checks for that.
216 if not hasattr(yml, "get"):
220 for rid, r in yml.get("resources", {}).items():
221 if isinstance(r, dict) and "type" in r:
224 if t == "OS::Heat::ResourceGroup":
225 rdt = r.get("properties", {}).get("resource_def", {}).get("type", None)
226 if rdt and (rdt.endswith(".yml") or rdt.endswith(".yaml")):
229 filepath = path.join(dirpath, nested_file)
230 if path.exists(filepath):
231 nested_files[rid] = nested_file
235 def get_type_nested_files(yml, dirpath):
238 key: key in yml which references a nested type file.
239 (the resource "type" is a yaml file.)
240 value: the nested file name.
242 The keys are assumed to be unique across files.
243 A separate test checks for that.
246 if not hasattr(yml, "get"):
250 for rid, r in yml.get("resources", {}).items():
251 if isinstance(r, dict) and "type" in r:
254 if t.endswith(".yml") or t.endswith(".yaml"):
257 filepath = path.join(dirpath, nested_file)
258 if path.exists(filepath):
259 nested_files[rid] = nested_file