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 UnknownFieldError
22 from toscaparser.common.exception import ValidationError
23 from toscaparser.elements.entity_type import update_definitions
24 from toscaparser.extensions.exttools import ExtTools
25 import toscaparser.imports
26 from toscaparser.prereq.csar import CSAR
27 from toscaparser.repositories import Repository
28 from toscaparser.topology_template import TopologyTemplate
29 from toscaparser.tpl_relationship_graph import ToscaGraph
30 from toscaparser.utils.gettextutils import _
31 import toscaparser.utils.yamlparser
34 # TOSCA template key names
35 SECTIONS = (DEFINITION_VERSION, DEFAULT_NAMESPACE, TEMPLATE_NAME,
36 TOPOLOGY_TEMPLATE, TEMPLATE_AUTHOR, TEMPLATE_VERSION,
37 DESCRIPTION, IMPORTS, DSL_DEFINITIONS, NODE_TYPES,
38 RELATIONSHIP_TYPES, RELATIONSHIP_TEMPLATES,
39 CAPABILITY_TYPES, ARTIFACT_TYPES, DATA_TYPES,
40 POLICY_TYPES, GROUP_TYPES, REPOSITORIES) = \
41 ('tosca_definitions_version', 'tosca_default_namespace',
42 'template_name', 'topology_template', 'template_author',
43 'template_version', 'description', 'imports', 'dsl_definitions',
44 'node_types', 'relationship_types', 'relationship_templates',
45 'capability_types', 'artifact_types', 'data_types',
46 'policy_types', 'group_types', 'repositories')
47 # Sections that are specific to individual template definitions
48 SPECIAL_SECTIONS = (METADATA) = ('metadata')
50 log = logging.getLogger("tosca.model")
52 YAML_LOADER = toscaparser.utils.yamlparser.load_yaml
55 class ToscaTemplate(object):
58 VALID_TEMPLATE_VERSIONS = ['tosca_simple_yaml_1_0']
60 VALID_TEMPLATE_VERSIONS.extend(exttools.get_versions())
62 ADDITIONAL_SECTIONS = {'tosca_simple_yaml_1_0': SPECIAL_SECTIONS}
64 ADDITIONAL_SECTIONS.update(exttools.get_sections())
66 '''Load the template data.'''
67 def __init__(self, path=None, parsed_params=None, a_file=True,
68 yaml_dict_tpl=None, sub_mapped_node_template=None):
69 if sub_mapped_node_template is None:
70 ExceptionCollector.start()
72 self.input_path = None
75 self.sub_mapped_node_template = sub_mapped_node_template
76 self.nested_tosca_tpls_with_topology = {}
77 self.nested_tosca_templates_with_topology = []
79 self.input_path = path
80 self.path = self._get_path(path)
82 self.tpl = YAML_LOADER(self.path, self.a_file)
84 msg = (_('Both path and yaml_dict_tpl arguments were '
85 'provided. Using path and ignoring yaml_dict_tpl.'))
90 self.tpl = yaml_dict_tpl
92 ExceptionCollector.appendException(
93 ValueError(_('No path or yaml_dict_tpl was provided. '
94 'There is nothing to parse.')))
97 self.parsed_params = parsed_params
98 self._validate_field()
99 self.version = self._tpl_version()
100 self.relationship_types = self._tpl_relationship_types()
101 self.description = self._tpl_description()
102 self.topology_template = self._topology_template()
103 self.repositories = self._tpl_repositories()
104 if self.topology_template.tpl:
105 self.inputs = self._inputs()
106 self.relationship_templates = self._relationship_templates()
107 self.nodetemplates = self._nodetemplates()
108 self.outputs = self._outputs()
109 self._handle_nested_tosca_templates_with_topology()
110 self.graph = ToscaGraph(self.nodetemplates)
112 if sub_mapped_node_template is None:
113 ExceptionCollector.stop()
114 self.verify_template()
116 def _topology_template(self):
117 return TopologyTemplate(self._tpl_topology_template(),
118 self._get_all_custom_defs(),
119 self.relationship_types,
121 self.sub_mapped_node_template)
124 return self.topology_template.inputs
126 def _nodetemplates(self):
127 return self.topology_template.nodetemplates
129 def _relationship_templates(self):
130 return self.topology_template.relationship_templates
133 return self.topology_template.outputs
135 def _tpl_version(self):
136 return self.tpl.get(DEFINITION_VERSION)
138 def _tpl_description(self):
139 desc = self.tpl.get(DESCRIPTION)
143 def _tpl_imports(self):
144 return self.tpl.get(IMPORTS)
146 def _tpl_repositories(self):
147 repositories = self.tpl.get(REPOSITORIES)
150 for name, val in repositories.items():
151 reposits = Repository(name, val)
152 reposit.append(reposits)
155 def _tpl_relationship_types(self):
156 return self._get_custom_types(RELATIONSHIP_TYPES)
158 def _tpl_relationship_templates(self):
159 topology_template = self._tpl_topology_template()
160 return topology_template.get(RELATIONSHIP_TEMPLATES)
162 def _tpl_topology_template(self):
163 return self.tpl.get(TOPOLOGY_TEMPLATE)
165 def _get_all_custom_defs(self, imports=None):
166 types = [IMPORTS, NODE_TYPES, CAPABILITY_TYPES, RELATIONSHIP_TYPES,
167 DATA_TYPES, POLICY_TYPES, GROUP_TYPES]
168 custom_defs_final = {}
169 custom_defs = self._get_custom_types(types, imports)
171 custom_defs_final.update(custom_defs)
172 if custom_defs.get(IMPORTS):
173 import_defs = self._get_all_custom_defs(
174 custom_defs.get(IMPORTS))
175 custom_defs_final.update(import_defs)
177 # As imports are not custom_types, removing from the dict
178 custom_defs_final.pop(IMPORTS, None)
179 return custom_defs_final
181 def _get_custom_types(self, type_definitions, imports=None):
182 """Handle custom types defined in imported template files
184 This method loads the custom type definitions referenced in "imports"
185 section of the TOSCA YAML template.
190 if not isinstance(type_definitions, list):
191 type_defs.append(type_definitions)
193 type_defs = type_definitions
196 imports = self._tpl_imports()
200 toscaparser.imports.ImportsLoader(imports, self.path,
203 nested_tosca_tpls = custom_service.get_nested_tosca_tpls()
204 self._update_nested_tosca_tpls_with_topology(nested_tosca_tpls)
206 custom_defs = custom_service.get_custom_defs()
210 # Handle custom types defined in current template file
211 for type_def in type_defs:
212 if type_def != IMPORTS:
213 inner_custom_types = self.tpl.get(type_def) or {}
214 if inner_custom_types:
215 custom_defs.update(inner_custom_types)
218 def _update_nested_tosca_tpls_with_topology(self, nested_tosca_tpls):
219 for tpl in nested_tosca_tpls:
220 filename, tosca_tpl = list(tpl.items())[0]
221 if (tosca_tpl.get(TOPOLOGY_TEMPLATE) and
222 filename not in list(
223 self.nested_tosca_tpls_with_topology.keys())):
224 self.nested_tosca_tpls_with_topology.update(tpl)
226 def _handle_nested_tosca_templates_with_topology(self):
227 for fname, tosca_tpl in self.nested_tosca_tpls_with_topology.items():
228 for nodetemplate in self.nodetemplates:
229 if self._is_sub_mapped_node(nodetemplate, tosca_tpl):
230 parsed_params = self._get_params_for_nested_template(
232 nested_template = ToscaTemplate(
233 path=fname, parsed_params=parsed_params,
234 yaml_dict_tpl=tosca_tpl,
235 sub_mapped_node_template=nodetemplate)
236 if nested_template.has_substitution_mappings():
237 # Record the nested templates in top level template
238 self.nested_tosca_templates_with_topology.\
239 append(nested_template)
240 # Set the substitution toscatemplate for mapped node
241 nodetemplate.sub_mapping_tosca_template = \
244 def _validate_field(self):
245 version = self._tpl_version()
247 ExceptionCollector.appendException(
248 MissingRequiredFieldError(what='Template',
249 required=DEFINITION_VERSION))
251 self._validate_version(version)
252 self.version = version
254 for name in self.tpl:
255 if (name not in SECTIONS and
256 name not in self.ADDITIONAL_SECTIONS.get(version, ())):
257 ExceptionCollector.appendException(
258 UnknownFieldError(what='Template', field=name))
260 def _validate_version(self, version):
261 if version not in self.VALID_TEMPLATE_VERSIONS:
262 ExceptionCollector.appendException(
263 InvalidTemplateVersion(
265 valid_versions=', '. join(self.VALID_TEMPLATE_VERSIONS)))
267 if version != 'tosca_simple_yaml_1_0':
268 update_definitions(version)
270 def _get_path(self, path):
271 if path.lower().endswith('.yaml'):
273 elif path.lower().endswith(('.zip', '.csar')):
275 csar = CSAR(path, self.a_file)
278 self.a_file = True # the file has been decompressed locally
279 return os.path.join(csar.temp_dir, csar.get_main_template())
281 ExceptionCollector.appendException(
282 ValueError(_('"%(path)s" is not a valid file.')
285 def verify_template(self):
286 if ExceptionCollector.exceptionsCaught():
288 raise ValidationError(
289 message=(_('\nThe input "%(path)s" failed validation with '
290 'the following error(s): \n\n\t')
291 % {'path': self.input_path}) +
292 '\n\t'.join(ExceptionCollector.getExceptionsReport()))
294 raise ValidationError(
295 message=_('\nThe pre-parsed input failed validation with '
296 'the following error(s): \n\n\t') +
297 '\n\t'.join(ExceptionCollector.getExceptionsReport()))
300 msg = (_('The input "%(path)s" successfully passed '
301 'validation.') % {'path': self.input_path})
303 msg = _('The pre-parsed input successfully passed validation.')
307 def _is_sub_mapped_node(self, nodetemplate, tosca_tpl):
308 """Return True if the nodetemple is substituted."""
309 if (nodetemplate and not nodetemplate.substitution_mapped and
310 self.get_sub_mapping_node_type(tosca_tpl) == nodetemplate.type
311 and len(nodetemplate.interfaces) < 1):
316 def _get_params_for_nested_template(self, nodetemplate):
317 """Return total params for nested_template."""
318 parsed_params = deepcopy(self.parsed_params) \
319 if self.parsed_params else {}
321 for pname in nodetemplate.get_properties():
322 parsed_params.update({pname:
323 nodetemplate.get_property_value(pname)})
326 def get_sub_mapping_node_type(self, tosca_tpl):
327 """Return substitution mappings node type."""
329 return TopologyTemplate.get_sub_mapping_node_type(
330 tosca_tpl.get(TOPOLOGY_TEMPLATE))
332 def has_substitution_mappings(self):
333 """Return True if the template has valid substitution mappings."""
334 return self.topology_template is not None and \
335 self.topology_template.substitution_mappings is not None
337 def has_nested_templates(self):
338 """Return True if the tosca template has nested templates."""
339 return self.nested_tosca_templates_with_topology is not None and \
340 len(self.nested_tosca_templates_with_topology) >= 1