1 # Licensed under the Apache License, Version 2.0 (the "License"); you may
2 # not use this file except in compliance with the License. You may obtain
3 # a copy of the License at
5 # http://www.apache.org/licenses/LICENSE-2.0
7 # Unless required by applicable law or agreed to in writing, software
8 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10 # License for the specific language governing permissions and limitations
17 from copy import deepcopy
18 from toscaparser.common.exception import ExceptionCollector
19 from toscaparser.common.exception import InvalidTemplateVersion
20 from toscaparser.common.exception import MissingRequiredFieldError
21 from toscaparser.common.exception import MissingRequiredParameterError
22 from toscaparser.common.exception import UnknownFieldError
23 from toscaparser.common.exception import ValidationError
24 from toscaparser.elements.entity_type import update_definitions
25 from toscaparser.extensions.exttools import ExtTools
26 import toscaparser.imports
27 from toscaparser.prereq.csar import CSAR
28 from toscaparser.repositories import Repository
29 from toscaparser.topology_template import TopologyTemplate
30 from toscaparser.tpl_relationship_graph import ToscaGraph
31 from toscaparser.utils.gettextutils import _
32 import toscaparser.utils.yamlparser
35 # TOSCA template key names
36 SECTIONS = (DEFINITION_VERSION, DEFAULT_NAMESPACE, TEMPLATE_NAME,
37 TOPOLOGY_TEMPLATE, TEMPLATE_AUTHOR, TEMPLATE_VERSION,
38 DESCRIPTION, IMPORTS, DSL_DEFINITIONS, NODE_TYPES,
39 RELATIONSHIP_TYPES, RELATIONSHIP_TEMPLATES,
40 CAPABILITY_TYPES, ARTIFACT_TYPES, DATA_TYPES, INTERFACE_TYPES,
41 POLICY_TYPES, GROUP_TYPES, REPOSITORIES) = \
42 ('tosca_definitions_version', 'tosca_default_namespace',
43 'template_name', 'topology_template', 'template_author',
44 'template_version', 'description', 'imports', 'dsl_definitions',
45 'node_types', 'relationship_types', 'relationship_templates',
46 'capability_types', 'artifact_types', 'data_types',
47 'interface_types', 'policy_types', 'group_types', 'repositories')
48 # Sections that are specific to individual template definitions
49 SPECIAL_SECTIONS = (METADATA) = ('metadata')
51 log = logging.getLogger("tosca.model")
53 YAML_LOADER = toscaparser.utils.yamlparser.load_yaml
56 class ToscaTemplate(object):
59 VALID_TEMPLATE_VERSIONS = ['tosca_simple_yaml_1_0',
60 'tosca_simple_yaml_1_1']
62 VALID_TEMPLATE_VERSIONS.extend(exttools.get_versions())
64 ADDITIONAL_SECTIONS = {'tosca_simple_yaml_1_0': SPECIAL_SECTIONS,
65 'tosca_simple_yaml_1_1': SPECIAL_SECTIONS}
67 ADDITIONAL_SECTIONS.update(exttools.get_sections())
69 '''Load the template data.'''
70 def __init__(self, path=None, parsed_params=None, a_file=True,
71 yaml_dict_tpl=None, sub_mapped_node_template=None,
72 no_required_paras_check=False,
74 if sub_mapped_node_template is None:
75 ExceptionCollector.start()
77 self.input_path = None
80 self.sub_mapped_node_template = sub_mapped_node_template
81 self.nested_tosca_tpls_with_topology = {}
82 self.nested_tosca_templates_with_topology = []
83 self.no_required_paras_check = no_required_paras_check
84 self.debug_mode = debug_mode
87 self.input_path = path
88 self.path = self._get_path(path)
90 self.tpl = YAML_LOADER(self.path, self.a_file)
92 msg = (_('Both path and yaml_dict_tpl arguments were '
93 'provided. Using path and ignoring yaml_dict_tpl.'))
98 self.tpl = yaml_dict_tpl
100 ExceptionCollector.appendException(
101 ValueError(_('No path or yaml_dict_tpl was provided. '
102 'There is nothing to parse.')))
105 self.parsed_params = parsed_params
106 self._validate_field()
107 self.version = self._tpl_version()
108 self.relationship_types = self._tpl_relationship_types()
109 self.description = self._tpl_description()
110 self.topology_template = self._topology_template()
111 self.repositories = self._tpl_repositories()
112 if self.topology_template.tpl:
113 self.inputs = self._inputs()
114 self.relationship_templates = self._relationship_templates()
115 self.nodetemplates = self._nodetemplates()
116 self.outputs = self._outputs()
117 self.policies = self._policies()
118 self._handle_nested_tosca_templates_with_topology()
119 self.graph = ToscaGraph(self.nodetemplates)
121 if sub_mapped_node_template is None:
122 ExceptionCollector.stop()
123 self.verify_template()
125 def _topology_template(self):
126 return TopologyTemplate(self._tpl_topology_template(),
127 self._get_all_custom_defs(),
128 self.relationship_types,
130 self.sub_mapped_node_template)
133 return self.topology_template.inputs
135 def _nodetemplates(self):
136 return self.topology_template.nodetemplates
138 def _relationship_templates(self):
139 return self.topology_template.relationship_templates
142 return self.topology_template.outputs
144 def _tpl_version(self):
145 return self.tpl.get(DEFINITION_VERSION)
147 def _tpl_description(self):
148 desc = self.tpl.get(DESCRIPTION)
152 def _tpl_imports(self):
153 return self.tpl.get(IMPORTS)
155 def _tpl_repositories(self):
156 repositories = self.tpl.get(REPOSITORIES)
159 for name, val in repositories.items():
160 reposits = Repository(name, val)
161 reposit.append(reposits)
164 def _tpl_relationship_types(self):
165 return self._get_custom_types(RELATIONSHIP_TYPES)
167 def _tpl_relationship_templates(self):
168 topology_template = self._tpl_topology_template()
169 return topology_template.get(RELATIONSHIP_TEMPLATES)
171 def _tpl_topology_template(self):
172 return self.tpl.get(TOPOLOGY_TEMPLATE)
175 return self.topology_template.policies
177 def _get_all_custom_defs(self, imports=None):
178 types = [IMPORTS, NODE_TYPES, CAPABILITY_TYPES, RELATIONSHIP_TYPES,
179 DATA_TYPES, INTERFACE_TYPES, POLICY_TYPES, GROUP_TYPES]
180 custom_defs_final = {}
181 custom_defs = self._get_custom_types(types, imports)
183 custom_defs_final.update(custom_defs)
184 if custom_defs.get(IMPORTS):
185 import_defs = self._get_all_custom_defs(
186 custom_defs.get(IMPORTS))
187 custom_defs_final.update(import_defs)
189 # As imports are not custom_types, removing from the dict
190 custom_defs_final.pop(IMPORTS, None)
191 return custom_defs_final
193 def _get_custom_types(self, type_definitions, imports=None):
194 """Handle custom types defined in imported template files
196 This method loads the custom type definitions referenced in "imports"
197 section of the TOSCA YAML template.
202 if not isinstance(type_definitions, list):
203 type_defs.append(type_definitions)
205 type_defs = type_definitions
208 imports = self._tpl_imports()
211 custom_service = toscaparser.imports.\
212 ImportsLoader(imports, self.path,
215 nested_tosca_tpls = custom_service.get_nested_tosca_tpls()
216 self._update_nested_tosca_tpls_with_topology(nested_tosca_tpls)
218 custom_defs = custom_service.get_custom_defs()
222 # Handle custom types defined in current template file
223 for type_def in type_defs:
224 if type_def != IMPORTS:
225 inner_custom_types = self.tpl.get(type_def) or {}
226 if inner_custom_types:
227 custom_defs.update(inner_custom_types)
230 def _update_nested_tosca_tpls_with_topology(self, nested_tosca_tpls):
231 for tpl in nested_tosca_tpls:
232 filename, tosca_tpl = list(tpl.items())[0]
233 if (tosca_tpl.get(TOPOLOGY_TEMPLATE) and
234 filename not in list(
235 self.nested_tosca_tpls_with_topology.keys())):
236 self.nested_tosca_tpls_with_topology.update(tpl)
238 def _handle_nested_tosca_templates_with_topology(self):
239 for fname, tosca_tpl in self.nested_tosca_tpls_with_topology.items():
240 for nodetemplate in self.nodetemplates:
241 if self._is_sub_mapped_node(nodetemplate, tosca_tpl):
242 parsed_params = self._get_params_for_nested_template(
245 cache_exeptions = deepcopy(ExceptionCollector.exceptions)
246 cache_exeptions_state = \
247 deepcopy(ExceptionCollector.collecting)
248 nested_template = None
250 nrpv = self.no_required_paras_check
251 nested_template = ToscaTemplate(
252 path=fname, parsed_params=parsed_params,
253 sub_mapped_node_template=nodetemplate,
254 no_required_paras_check=nrpv,
255 debug_mode=self.debug_mode)
256 except ValidationError as e:
263 ExceptionCollector.exceptions = deepcopy(cache_exeptions)
264 ExceptionCollector.collecting = \
265 deepcopy(cache_exeptions_state)
267 if nested_template and \
268 nested_template._has_substitution_mappings():
269 # Record the nested templates in top level template
270 self.nested_tosca_templates_with_topology.\
271 append(nested_template)
272 # Set the substitution toscatemplate for mapped node
273 nodetemplate.substitution_mapped = \
276 def _validate_field(self):
277 version = self._tpl_version()
279 ExceptionCollector.appendException(
280 MissingRequiredFieldError(what='Template',
281 required=DEFINITION_VERSION))
283 self._validate_version(version)
284 self.version = version
286 for name in self.tpl:
287 if (name not in SECTIONS and
288 name not in self.ADDITIONAL_SECTIONS.get(version, ())):
289 ExceptionCollector.appendException(
290 UnknownFieldError(what='Template', field=name))
292 def _validate_version(self, version):
293 if version not in self.VALID_TEMPLATE_VERSIONS:
294 ExceptionCollector.appendException(
295 InvalidTemplateVersion(
297 valid_versions=', '. join(self.VALID_TEMPLATE_VERSIONS)))
299 if (version != 'tosca_simple_yaml_1_0' and
300 version != 'tosca_simple_yaml_1_1'):
301 update_definitions(version)
303 def _get_path(self, path):
304 if path.lower().endswith('.yaml') or path.lower().endswith('.yml'):
306 elif path.lower().endswith(('.zip', '.csar')):
308 csar = CSAR(path, self.a_file)
311 self.a_file = True # the file has been decompressed locally
312 return os.path.join(csar.temp_dir, csar.get_main_template())
314 ExceptionCollector.appendException(
315 ValueError(_('"%(path)s" is not a valid file.')
318 def verify_template(self):
319 if ExceptionCollector.exceptionsCaught():
320 if self.no_required_paras_check:
321 ExceptionCollector.removeException(
322 MissingRequiredParameterError)
325 exceptions = ValidationError(
326 message=(_('\nThe input "%(path)s" failed validation with '
327 'the following error(s): \n\n\t')
328 % {'path': self.input_path}) +
329 '\n\t'.join(ExceptionCollector.getExceptionsReport()))
331 exceptions = ValidationError(
332 message=_('\nThe pre-parsed input failed validation with '
333 'the following error(s): \n\n\t') +
334 '\n\t'.join(ExceptionCollector.getExceptionsReport()))
335 if not self.debug_mode:
338 if self.sub_mapped_node_template:
339 msg = _('======== nested service template ======== ')
342 msg = _('======== main service template ======== ')
344 print(exceptions.message)
346 log.error(exceptions.message)
349 msg = (_('The input "%(path)s" successfully passed '
350 'validation.') % {'path': self.input_path})
352 msg = _('The pre-parsed input successfully passed validation.')
356 def _is_sub_mapped_node(self, nodetemplate, tosca_tpl):
357 """Return True if the nodetemple is substituted."""
358 if (nodetemplate and not nodetemplate.substitution_mapped and
359 self.get_sub_mapping_node_type(tosca_tpl) == nodetemplate.type
360 and len(nodetemplate.interfaces) < 1):
365 def _get_params_for_nested_template(self, nodetemplate):
366 """Return total params for nested_template."""
367 parsed_params = deepcopy(self.parsed_params) \
368 if self.parsed_params else {}
370 for pname in nodetemplate.get_properties():
371 parsed_params.update({pname:
372 nodetemplate.get_property_value(pname)})
375 def get_sub_mapping_node_type(self, tosca_tpl):
376 """Return substitution mappings node type."""
378 return TopologyTemplate.get_sub_mapping_node_type(
379 tosca_tpl.get(TOPOLOGY_TEMPLATE))
381 def _has_substitution_mappings(self):
382 """Return True if the template has valid substitution mappings."""
383 return self.topology_template is not None and \
384 self.topology_template.substitution_mappings is not None
386 def has_nested_templates(self):
387 """Return True if the tosca template has nested templates."""
388 return self.nested_tosca_templates_with_topology is not None and \
389 len(self.nested_tosca_templates_with_topology) >= 1