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, INTERFACE_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 'interface_types', '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.policies = self._policies()
110 self._handle_nested_tosca_templates_with_topology()
111 self.graph = ToscaGraph(self.nodetemplates)
113 if sub_mapped_node_template is None:
114 ExceptionCollector.stop()
115 self.verify_template()
117 def _topology_template(self):
118 return TopologyTemplate(self._tpl_topology_template(),
119 self._get_all_custom_defs(),
120 self.relationship_types,
122 self.sub_mapped_node_template)
125 return self.topology_template.inputs
127 def _nodetemplates(self):
128 return self.topology_template.nodetemplates
130 def _relationship_templates(self):
131 return self.topology_template.relationship_templates
134 return self.topology_template.outputs
136 def _tpl_version(self):
137 return self.tpl.get(DEFINITION_VERSION)
139 def _tpl_description(self):
140 desc = self.tpl.get(DESCRIPTION)
144 def _tpl_imports(self):
145 return self.tpl.get(IMPORTS)
147 def _tpl_repositories(self):
148 repositories = self.tpl.get(REPOSITORIES)
151 for name, val in repositories.items():
152 reposits = Repository(name, val)
153 reposit.append(reposits)
156 def _tpl_relationship_types(self):
157 return self._get_custom_types(RELATIONSHIP_TYPES)
159 def _tpl_relationship_templates(self):
160 topology_template = self._tpl_topology_template()
161 return topology_template.get(RELATIONSHIP_TEMPLATES)
163 def _tpl_topology_template(self):
164 return self.tpl.get(TOPOLOGY_TEMPLATE)
167 return self.topology_template.policies
169 def _get_all_custom_defs(self, imports=None):
170 types = [IMPORTS, NODE_TYPES, CAPABILITY_TYPES, RELATIONSHIP_TYPES,
171 DATA_TYPES, INTERFACE_TYPES, POLICY_TYPES, GROUP_TYPES]
172 custom_defs_final = {}
173 custom_defs = self._get_custom_types(types, imports)
175 custom_defs_final.update(custom_defs)
176 if custom_defs.get(IMPORTS):
177 import_defs = self._get_all_custom_defs(
178 custom_defs.get(IMPORTS))
179 custom_defs_final.update(import_defs)
181 # As imports are not custom_types, removing from the dict
182 custom_defs_final.pop(IMPORTS, None)
183 return custom_defs_final
185 def _get_custom_types(self, type_definitions, imports=None):
186 """Handle custom types defined in imported template files
188 This method loads the custom type definitions referenced in "imports"
189 section of the TOSCA YAML template.
194 if not isinstance(type_definitions, list):
195 type_defs.append(type_definitions)
197 type_defs = type_definitions
200 imports = self._tpl_imports()
203 custom_service = toscaparser.imports.\
204 ImportsLoader(imports, self.path,
207 nested_tosca_tpls = custom_service.get_nested_tosca_tpls()
208 self._update_nested_tosca_tpls_with_topology(nested_tosca_tpls)
210 custom_defs = custom_service.get_custom_defs()
214 # Handle custom types defined in current template file
215 for type_def in type_defs:
216 if type_def != IMPORTS:
217 inner_custom_types = self.tpl.get(type_def) or {}
218 if inner_custom_types:
219 custom_defs.update(inner_custom_types)
222 def _update_nested_tosca_tpls_with_topology(self, nested_tosca_tpls):
223 for tpl in nested_tosca_tpls:
224 filename, tosca_tpl = list(tpl.items())[0]
225 if (tosca_tpl.get(TOPOLOGY_TEMPLATE) and
226 filename not in list(
227 self.nested_tosca_tpls_with_topology.keys())):
228 self.nested_tosca_tpls_with_topology.update(tpl)
230 def _handle_nested_tosca_templates_with_topology(self):
231 for fname, tosca_tpl in self.nested_tosca_tpls_with_topology.items():
232 for nodetemplate in self.nodetemplates:
233 if self._is_sub_mapped_node(nodetemplate, tosca_tpl):
234 parsed_params = self._get_params_for_nested_template(
236 nested_template = ToscaTemplate(
237 path=fname, parsed_params=parsed_params,
238 yaml_dict_tpl=tosca_tpl,
239 sub_mapped_node_template=nodetemplate)
240 if nested_template._has_substitution_mappings():
241 # Record the nested templates in top level template
242 self.nested_tosca_templates_with_topology.\
243 append(nested_template)
244 # Set the substitution toscatemplate for mapped node
245 nodetemplate.sub_mapping_tosca_template = \
248 def _validate_field(self):
249 version = self._tpl_version()
251 ExceptionCollector.appendException(
252 MissingRequiredFieldError(what='Template',
253 required=DEFINITION_VERSION))
255 self._validate_version(version)
256 self.version = version
258 for name in self.tpl:
259 if (name not in SECTIONS and
260 name not in self.ADDITIONAL_SECTIONS.get(version, ())):
261 ExceptionCollector.appendException(
262 UnknownFieldError(what='Template', field=name))
264 def _validate_version(self, version):
265 if version not in self.VALID_TEMPLATE_VERSIONS:
266 ExceptionCollector.appendException(
267 InvalidTemplateVersion(
269 valid_versions=', '. join(self.VALID_TEMPLATE_VERSIONS)))
271 if version != 'tosca_simple_yaml_1_0':
272 update_definitions(version)
274 def _get_path(self, path):
275 if path.lower().endswith('.yaml') or path.lower().endswith('.yml'):
277 elif path.lower().endswith(('.zip', '.csar')):
279 csar = CSAR(path, self.a_file)
282 self.a_file = True # the file has been decompressed locally
283 return os.path.join(csar.temp_dir, csar.get_main_template())
285 ExceptionCollector.appendException(
286 ValueError(_('"%(path)s" is not a valid file.')
289 def verify_template(self):
290 if ExceptionCollector.exceptionsCaught():
292 raise ValidationError(
293 message=(_('\nThe input "%(path)s" failed validation with '
294 'the following error(s): \n\n\t')
295 % {'path': self.input_path}) +
296 '\n\t'.join(ExceptionCollector.getExceptionsReport()))
298 raise ValidationError(
299 message=_('\nThe pre-parsed input failed validation with '
300 'the following error(s): \n\n\t') +
301 '\n\t'.join(ExceptionCollector.getExceptionsReport()))
304 msg = (_('The input "%(path)s" successfully passed '
305 'validation.') % {'path': self.input_path})
307 msg = _('The pre-parsed input successfully passed validation.')
311 def _is_sub_mapped_node(self, nodetemplate, tosca_tpl):
312 """Return True if the nodetemple is substituted."""
313 if (nodetemplate and not nodetemplate.substitution_mapped and
314 self.get_sub_mapping_node_type(tosca_tpl) == nodetemplate.type
315 and len(nodetemplate.interfaces) < 1):
320 def _get_params_for_nested_template(self, nodetemplate):
321 """Return total params for nested_template."""
322 parsed_params = deepcopy(self.parsed_params) \
323 if self.parsed_params else {}
325 for pname in nodetemplate.get_properties():
326 parsed_params.update({pname:
327 nodetemplate.get_property_value(pname)})
330 def get_sub_mapping_node_type(self, tosca_tpl):
331 """Return substitution mappings node type."""
333 return TopologyTemplate.get_sub_mapping_node_type(
334 tosca_tpl.get(TOPOLOGY_TEMPLATE))
336 def _has_substitution_mappings(self):
337 """Return True if the template has valid substitution mappings."""
338 return self.topology_template is not None and \
339 self.topology_template.substitution_mappings is not None
341 def has_nested_templates(self):
342 """Return True if the tosca template has nested templates."""
343 return self.nested_tosca_templates_with_topology is not None and \
344 len(self.nested_tosca_templates_with_topology) >= 1