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']
61 VALID_TEMPLATE_VERSIONS.extend(exttools.get_versions())
63 ADDITIONAL_SECTIONS = {'tosca_simple_yaml_1_0': SPECIAL_SECTIONS}
65 ADDITIONAL_SECTIONS.update(exttools.get_sections())
67 '''Load the template data.'''
68 def __init__(self, path=None, parsed_params=None, a_file=True,
69 yaml_dict_tpl=None, sub_mapped_node_template=None,
70 no_required_paras_valid=False):
71 if sub_mapped_node_template is None:
72 ExceptionCollector.start()
74 self.input_path = None
77 self.sub_mapped_node_template = sub_mapped_node_template
78 self.nested_tosca_tpls_with_topology = {}
79 self.nested_tosca_templates_with_topology = []
80 self.no_required_paras_valid = no_required_paras_valid
82 self.input_path = path
83 self.path = self._get_path(path)
85 self.tpl = YAML_LOADER(self.path, self.a_file)
87 msg = (_('Both path and yaml_dict_tpl arguments were '
88 'provided. Using path and ignoring yaml_dict_tpl.'))
93 self.tpl = yaml_dict_tpl
95 ExceptionCollector.appendException(
96 ValueError(_('No path or yaml_dict_tpl was provided. '
97 'There is nothing to parse.')))
100 self.parsed_params = parsed_params
101 self._validate_field()
102 self.version = self._tpl_version()
103 self.relationship_types = self._tpl_relationship_types()
104 self.description = self._tpl_description()
105 self.topology_template = self._topology_template()
106 self.repositories = self._tpl_repositories()
107 if self.topology_template.tpl:
108 self.inputs = self._inputs()
109 self.relationship_templates = self._relationship_templates()
110 self.nodetemplates = self._nodetemplates()
111 self.outputs = self._outputs()
112 self.policies = self._policies()
113 self._handle_nested_tosca_templates_with_topology()
114 self.graph = ToscaGraph(self.nodetemplates)
116 if sub_mapped_node_template is None:
117 ExceptionCollector.stop()
118 self.verify_template()
120 def _topology_template(self):
121 return TopologyTemplate(self._tpl_topology_template(),
122 self._get_all_custom_defs(),
123 self.relationship_types,
125 self.sub_mapped_node_template)
128 return self.topology_template.inputs
130 def _nodetemplates(self):
131 return self.topology_template.nodetemplates
133 def _relationship_templates(self):
134 return self.topology_template.relationship_templates
137 return self.topology_template.outputs
139 def _tpl_version(self):
140 return self.tpl.get(DEFINITION_VERSION)
142 def _tpl_description(self):
143 desc = self.tpl.get(DESCRIPTION)
147 def _tpl_imports(self):
148 return self.tpl.get(IMPORTS)
150 def _tpl_repositories(self):
151 repositories = self.tpl.get(REPOSITORIES)
154 for name, val in repositories.items():
155 reposits = Repository(name, val)
156 reposit.append(reposits)
159 def _tpl_relationship_types(self):
160 return self._get_custom_types(RELATIONSHIP_TYPES)
162 def _tpl_relationship_templates(self):
163 topology_template = self._tpl_topology_template()
164 return topology_template.get(RELATIONSHIP_TEMPLATES)
166 def _tpl_topology_template(self):
167 return self.tpl.get(TOPOLOGY_TEMPLATE)
170 return self.topology_template.policies
172 def _get_all_custom_defs(self, imports=None):
173 types = [IMPORTS, NODE_TYPES, CAPABILITY_TYPES, RELATIONSHIP_TYPES,
174 DATA_TYPES, INTERFACE_TYPES, POLICY_TYPES, GROUP_TYPES]
175 custom_defs_final = {}
176 custom_defs = self._get_custom_types(types, imports)
178 custom_defs_final.update(custom_defs)
179 if custom_defs.get(IMPORTS):
180 import_defs = self._get_all_custom_defs(
181 custom_defs.get(IMPORTS))
182 custom_defs_final.update(import_defs)
184 # As imports are not custom_types, removing from the dict
185 custom_defs_final.pop(IMPORTS, None)
186 return custom_defs_final
188 def _get_custom_types(self, type_definitions, imports=None):
189 """Handle custom types defined in imported template files
191 This method loads the custom type definitions referenced in "imports"
192 section of the TOSCA YAML template.
197 if not isinstance(type_definitions, list):
198 type_defs.append(type_definitions)
200 type_defs = type_definitions
203 imports = self._tpl_imports()
206 custom_service = toscaparser.imports.\
207 ImportsLoader(imports, self.path,
210 nested_tosca_tpls = custom_service.get_nested_tosca_tpls()
211 self._update_nested_tosca_tpls_with_topology(nested_tosca_tpls)
213 custom_defs = custom_service.get_custom_defs()
217 # Handle custom types defined in current template file
218 for type_def in type_defs:
219 if type_def != IMPORTS:
220 inner_custom_types = self.tpl.get(type_def) or {}
221 if inner_custom_types:
222 custom_defs.update(inner_custom_types)
225 def _update_nested_tosca_tpls_with_topology(self, nested_tosca_tpls):
226 for tpl in nested_tosca_tpls:
227 filename, tosca_tpl = list(tpl.items())[0]
228 if (tosca_tpl.get(TOPOLOGY_TEMPLATE) and
229 filename not in list(
230 self.nested_tosca_tpls_with_topology.keys())):
231 self.nested_tosca_tpls_with_topology.update(tpl)
233 def _handle_nested_tosca_templates_with_topology(self):
234 for fname, tosca_tpl in self.nested_tosca_tpls_with_topology.items():
235 for nodetemplate in self.nodetemplates:
236 if self._is_sub_mapped_node(nodetemplate, tosca_tpl):
237 parsed_params = self._get_params_for_nested_template(
239 nested_template = ToscaTemplate(
240 path=fname, parsed_params=parsed_params,
241 yaml_dict_tpl=tosca_tpl,
242 sub_mapped_node_template=nodetemplate,
243 no_required_paras_valid=self.no_required_paras_valid)
244 if nested_template._has_substitution_mappings():
245 # Record the nested templates in top level template
246 self.nested_tosca_templates_with_topology.\
247 append(nested_template)
248 # Set the substitution toscatemplate for mapped node
249 nodetemplate.sub_mapping_tosca_template = \
252 def _validate_field(self):
253 version = self._tpl_version()
255 ExceptionCollector.appendException(
256 MissingRequiredFieldError(what='Template',
257 required=DEFINITION_VERSION))
259 self._validate_version(version)
260 self.version = version
262 for name in self.tpl:
263 if (name not in SECTIONS and
264 name not in self.ADDITIONAL_SECTIONS.get(version, ())):
265 ExceptionCollector.appendException(
266 UnknownFieldError(what='Template', field=name))
268 def _validate_version(self, version):
269 if version not in self.VALID_TEMPLATE_VERSIONS:
270 ExceptionCollector.appendException(
271 InvalidTemplateVersion(
273 valid_versions=', '. join(self.VALID_TEMPLATE_VERSIONS)))
275 if version != 'tosca_simple_yaml_1_0':
276 update_definitions(version)
278 def _get_path(self, path):
279 if path.lower().endswith('.yaml') or path.lower().endswith('.yml'):
281 elif path.lower().endswith(('.zip', '.csar')):
283 csar = CSAR(path, self.a_file)
286 self.a_file = True # the file has been decompressed locally
287 return os.path.join(csar.temp_dir, csar.get_main_template())
289 ExceptionCollector.appendException(
290 ValueError(_('"%(path)s" is not a valid file.')
293 def verify_template(self):
294 if ExceptionCollector.exceptionsCaught():
295 if self.no_required_paras_valid:
296 ExceptionCollector.removeException(
297 MissingRequiredParameterError)
300 raise ValidationError(
301 message=(_('\nThe input "%(path)s" failed validation with '
302 'the following error(s): \n\n\t')
303 % {'path': self.input_path}) +
304 '\n\t'.join(ExceptionCollector.getExceptionsReport()))
306 raise ValidationError(
307 message=_('\nThe pre-parsed input failed validation with '
308 'the following error(s): \n\n\t') +
309 '\n\t'.join(ExceptionCollector.getExceptionsReport()))
312 msg = (_('The input "%(path)s" successfully passed '
313 'validation.') % {'path': self.input_path})
315 msg = _('The pre-parsed input successfully passed validation.')
319 def _is_sub_mapped_node(self, nodetemplate, tosca_tpl):
320 """Return True if the nodetemple is substituted."""
321 if (nodetemplate and not nodetemplate.substitution_mapped and
322 self.get_sub_mapping_node_type(tosca_tpl) == nodetemplate.type
323 and len(nodetemplate.interfaces) < 1):
328 def _get_params_for_nested_template(self, nodetemplate):
329 """Return total params for nested_template."""
330 parsed_params = deepcopy(self.parsed_params) \
331 if self.parsed_params else {}
333 for pname in nodetemplate.get_properties():
334 parsed_params.update({pname:
335 nodetemplate.get_property_value(pname)})
338 def get_sub_mapping_node_type(self, tosca_tpl):
339 """Return substitution mappings node type."""
341 return TopologyTemplate.get_sub_mapping_node_type(
342 tosca_tpl.get(TOPOLOGY_TEMPLATE))
344 def _has_substitution_mappings(self):
345 """Return True if the template has valid substitution mappings."""
346 return self.topology_template is not None and \
347 self.topology_template.substitution_mappings is not None
349 def has_nested_templates(self):
350 """Return True if the tosca template has nested templates."""
351 return self.nested_tosca_templates_with_topology is not None and \
352 len(self.nested_tosca_templates_with_topology) >= 1