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_valid=False):
73 if sub_mapped_node_template is None:
74 ExceptionCollector.start()
76 self.input_path = None
79 self.sub_mapped_node_template = sub_mapped_node_template
80 self.nested_tosca_tpls_with_topology = {}
81 self.nested_tosca_templates_with_topology = []
82 self.no_required_paras_valid = no_required_paras_valid
84 self.input_path = path
85 self.path = self._get_path(path)
87 self.tpl = YAML_LOADER(self.path, self.a_file)
89 msg = (_('Both path and yaml_dict_tpl arguments were '
90 'provided. Using path and ignoring yaml_dict_tpl.'))
95 self.tpl = yaml_dict_tpl
97 ExceptionCollector.appendException(
98 ValueError(_('No path or yaml_dict_tpl was provided. '
99 'There is nothing to parse.')))
102 self.parsed_params = parsed_params
103 self._validate_field()
104 self.version = self._tpl_version()
105 self.relationship_types = self._tpl_relationship_types()
106 self.description = self._tpl_description()
107 self.topology_template = self._topology_template()
108 self.repositories = self._tpl_repositories()
109 if self.topology_template.tpl:
110 self.inputs = self._inputs()
111 self.relationship_templates = self._relationship_templates()
112 self.nodetemplates = self._nodetemplates()
113 self.outputs = self._outputs()
114 self.policies = self._policies()
115 self._handle_nested_tosca_templates_with_topology()
116 self.graph = ToscaGraph(self.nodetemplates)
118 if sub_mapped_node_template is None:
119 ExceptionCollector.stop()
120 self.verify_template()
122 def _topology_template(self):
123 return TopologyTemplate(self._tpl_topology_template(),
124 self._get_all_custom_defs(),
125 self.relationship_types,
127 self.sub_mapped_node_template)
130 return self.topology_template.inputs
132 def _nodetemplates(self):
133 return self.topology_template.nodetemplates
135 def _relationship_templates(self):
136 return self.topology_template.relationship_templates
139 return self.topology_template.outputs
141 def _tpl_version(self):
142 return self.tpl.get(DEFINITION_VERSION)
144 def _tpl_description(self):
145 desc = self.tpl.get(DESCRIPTION)
149 def _tpl_imports(self):
150 return self.tpl.get(IMPORTS)
152 def _tpl_repositories(self):
153 repositories = self.tpl.get(REPOSITORIES)
156 for name, val in repositories.items():
157 reposits = Repository(name, val)
158 reposit.append(reposits)
161 def _tpl_relationship_types(self):
162 return self._get_custom_types(RELATIONSHIP_TYPES)
164 def _tpl_relationship_templates(self):
165 topology_template = self._tpl_topology_template()
166 return topology_template.get(RELATIONSHIP_TEMPLATES)
168 def _tpl_topology_template(self):
169 return self.tpl.get(TOPOLOGY_TEMPLATE)
172 return self.topology_template.policies
174 def _get_all_custom_defs(self, imports=None):
175 types = [IMPORTS, NODE_TYPES, CAPABILITY_TYPES, RELATIONSHIP_TYPES,
176 DATA_TYPES, INTERFACE_TYPES, POLICY_TYPES, GROUP_TYPES]
177 custom_defs_final = {}
178 custom_defs = self._get_custom_types(types, imports)
180 custom_defs_final.update(custom_defs)
181 if custom_defs.get(IMPORTS):
182 import_defs = self._get_all_custom_defs(
183 custom_defs.get(IMPORTS))
184 custom_defs_final.update(import_defs)
186 # As imports are not custom_types, removing from the dict
187 custom_defs_final.pop(IMPORTS, None)
188 return custom_defs_final
190 def _get_custom_types(self, type_definitions, imports=None):
191 """Handle custom types defined in imported template files
193 This method loads the custom type definitions referenced in "imports"
194 section of the TOSCA YAML template.
199 if not isinstance(type_definitions, list):
200 type_defs.append(type_definitions)
202 type_defs = type_definitions
205 imports = self._tpl_imports()
208 custom_service = toscaparser.imports.\
209 ImportsLoader(imports, self.path,
212 nested_tosca_tpls = custom_service.get_nested_tosca_tpls()
213 self._update_nested_tosca_tpls_with_topology(nested_tosca_tpls)
215 custom_defs = custom_service.get_custom_defs()
219 # Handle custom types defined in current template file
220 for type_def in type_defs:
221 if type_def != IMPORTS:
222 inner_custom_types = self.tpl.get(type_def) or {}
223 if inner_custom_types:
224 custom_defs.update(inner_custom_types)
227 def _update_nested_tosca_tpls_with_topology(self, nested_tosca_tpls):
228 for tpl in nested_tosca_tpls:
229 filename, tosca_tpl = list(tpl.items())[0]
230 if (tosca_tpl.get(TOPOLOGY_TEMPLATE) and
231 filename not in list(
232 self.nested_tosca_tpls_with_topology.keys())):
233 self.nested_tosca_tpls_with_topology.update(tpl)
235 def _handle_nested_tosca_templates_with_topology(self):
236 for fname, tosca_tpl in self.nested_tosca_tpls_with_topology.items():
237 for nodetemplate in self.nodetemplates:
238 if self._is_sub_mapped_node(nodetemplate, tosca_tpl):
239 parsed_params = self._get_params_for_nested_template(
242 cache_exeptions = deepcopy(ExceptionCollector.exceptions)
243 cache_exeptions_state = \
244 deepcopy(ExceptionCollector.collecting)
245 nested_template = None
247 nrpv = self.no_required_paras_valid
248 nested_template = ToscaTemplate(
249 path=fname, parsed_params=parsed_params,
250 sub_mapped_node_template=nodetemplate,
251 no_required_paras_valid=nrpv)
252 except ValidationError as e:
253 msg = _(' ===== nested service template ===== ')
258 ExceptionCollector.exceptions = deepcopy(cache_exeptions)
259 ExceptionCollector.collecting = \
260 deepcopy(cache_exeptions_state)
262 if nested_template and \
263 nested_template._has_substitution_mappings():
264 # Record the nested templates in top level template
265 self.nested_tosca_templates_with_topology.\
266 append(nested_template)
267 # Set the substitution toscatemplate for mapped node
268 nodetemplate.sub_mapping_tosca_template = \
271 def _validate_field(self):
272 version = self._tpl_version()
274 ExceptionCollector.appendException(
275 MissingRequiredFieldError(what='Template',
276 required=DEFINITION_VERSION))
278 self._validate_version(version)
279 self.version = version
281 for name in self.tpl:
282 if (name not in SECTIONS and
283 name not in self.ADDITIONAL_SECTIONS.get(version, ())):
284 ExceptionCollector.appendException(
285 UnknownFieldError(what='Template', field=name))
287 def _validate_version(self, version):
288 if version not in self.VALID_TEMPLATE_VERSIONS:
289 ExceptionCollector.appendException(
290 InvalidTemplateVersion(
292 valid_versions=', '. join(self.VALID_TEMPLATE_VERSIONS)))
294 if (version != 'tosca_simple_yaml_1_0' and
295 version != 'tosca_simple_yaml_1_1'):
296 update_definitions(version)
298 def _get_path(self, path):
299 if path.lower().endswith('.yaml') or path.lower().endswith('.yml'):
301 elif path.lower().endswith(('.zip', '.csar')):
303 csar = CSAR(path, self.a_file)
306 self.a_file = True # the file has been decompressed locally
307 return os.path.join(csar.temp_dir, csar.get_main_template())
309 ExceptionCollector.appendException(
310 ValueError(_('"%(path)s" is not a valid file.')
313 def verify_template(self):
314 if ExceptionCollector.exceptionsCaught():
315 if self.no_required_paras_valid:
316 ExceptionCollector.removeException(
317 MissingRequiredParameterError)
320 raise ValidationError(
321 message=(_('\nThe input "%(path)s" failed validation with '
322 'the following error(s): \n\n\t')
323 % {'path': self.input_path}) +
324 '\n\t'.join(ExceptionCollector.getExceptionsReport()))
326 raise ValidationError(
327 message=_('\nThe pre-parsed input failed validation with '
328 'the following error(s): \n\n\t') +
329 '\n\t'.join(ExceptionCollector.getExceptionsReport()))
332 msg = (_('The input "%(path)s" successfully passed '
333 'validation.') % {'path': self.input_path})
335 msg = _('The pre-parsed input successfully passed validation.')
339 def _is_sub_mapped_node(self, nodetemplate, tosca_tpl):
340 """Return True if the nodetemple is substituted."""
341 if (nodetemplate and not nodetemplate.substitution_mapped and
342 self.get_sub_mapping_node_type(tosca_tpl) == nodetemplate.type
343 and len(nodetemplate.interfaces) < 1):
348 def _get_params_for_nested_template(self, nodetemplate):
349 """Return total params for nested_template."""
350 parsed_params = deepcopy(self.parsed_params) \
351 if self.parsed_params else {}
353 for pname in nodetemplate.get_properties():
354 parsed_params.update({pname:
355 nodetemplate.get_property_value(pname)})
358 def get_sub_mapping_node_type(self, tosca_tpl):
359 """Return substitution mappings node type."""
361 return TopologyTemplate.get_sub_mapping_node_type(
362 tosca_tpl.get(TOPOLOGY_TEMPLATE))
364 def _has_substitution_mappings(self):
365 """Return True if the template has valid substitution mappings."""
366 return self.topology_template is not None and \
367 self.topology_template.substitution_mappings is not None
369 def has_nested_templates(self):
370 """Return True if the tosca template has nested templates."""
371 return self.nested_tosca_templates_with_topology is not None and \
372 len(self.nested_tosca_templates_with_topology) >= 1