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(
241 nested_template = ToscaTemplate(
242 path=fname, parsed_params=parsed_params,
243 yaml_dict_tpl=tosca_tpl,
244 sub_mapped_node_template=nodetemplate,
245 no_required_paras_valid=self.no_required_paras_valid)
246 if nested_template._has_substitution_mappings():
247 # Record the nested templates in top level template
248 self.nested_tosca_templates_with_topology.\
249 append(nested_template)
250 # Set the substitution toscatemplate for mapped node
251 nodetemplate.sub_mapping_tosca_template = \
254 def _validate_field(self):
255 version = self._tpl_version()
257 ExceptionCollector.appendException(
258 MissingRequiredFieldError(what='Template',
259 required=DEFINITION_VERSION))
261 self._validate_version(version)
262 self.version = version
264 for name in self.tpl:
265 if (name not in SECTIONS and
266 name not in self.ADDITIONAL_SECTIONS.get(version, ())):
267 ExceptionCollector.appendException(
268 UnknownFieldError(what='Template', field=name))
270 def _validate_version(self, version):
271 if version not in self.VALID_TEMPLATE_VERSIONS:
272 ExceptionCollector.appendException(
273 InvalidTemplateVersion(
275 valid_versions=', '. join(self.VALID_TEMPLATE_VERSIONS)))
277 if (version != 'tosca_simple_yaml_1_0' and
278 version != 'tosca_simple_yaml_1_1'):
279 update_definitions(version)
281 def _get_path(self, path):
282 if path.lower().endswith('.yaml') or path.lower().endswith('.yml'):
284 elif path.lower().endswith(('.zip', '.csar')):
286 csar = CSAR(path, self.a_file)
289 self.a_file = True # the file has been decompressed locally
290 return os.path.join(csar.temp_dir, csar.get_main_template())
292 ExceptionCollector.appendException(
293 ValueError(_('"%(path)s" is not a valid file.')
296 def verify_template(self):
297 if ExceptionCollector.exceptionsCaught():
298 if self.no_required_paras_valid:
299 ExceptionCollector.removeException(
300 MissingRequiredParameterError)
303 raise ValidationError(
304 message=(_('\nThe input "%(path)s" failed validation with '
305 'the following error(s): \n\n\t')
306 % {'path': self.input_path}) +
307 '\n\t'.join(ExceptionCollector.getExceptionsReport()))
309 raise ValidationError(
310 message=_('\nThe pre-parsed input failed validation with '
311 'the following error(s): \n\n\t') +
312 '\n\t'.join(ExceptionCollector.getExceptionsReport()))
315 msg = (_('The input "%(path)s" successfully passed '
316 'validation.') % {'path': self.input_path})
318 msg = _('The pre-parsed input successfully passed validation.')
322 def _is_sub_mapped_node(self, nodetemplate, tosca_tpl):
323 """Return True if the nodetemple is substituted."""
324 if (nodetemplate and not nodetemplate.substitution_mapped and
325 self.get_sub_mapping_node_type(tosca_tpl) == nodetemplate.type
326 and len(nodetemplate.interfaces) < 1):
331 def _get_params_for_nested_template(self, nodetemplate):
332 """Return total params for nested_template."""
333 parsed_params = deepcopy(self.parsed_params) \
334 if self.parsed_params else {}
336 for pname in nodetemplate.get_properties():
337 parsed_params.update({pname:
338 nodetemplate.get_property_value(pname)})
341 def get_sub_mapping_node_type(self, tosca_tpl):
342 """Return substitution mappings node type."""
344 return TopologyTemplate.get_sub_mapping_node_type(
345 tosca_tpl.get(TOPOLOGY_TEMPLATE))
347 def _has_substitution_mappings(self):
348 """Return True if the template has valid substitution mappings."""
349 return self.topology_template is not None and \
350 self.topology_template.substitution_mappings is not None
352 def has_nested_templates(self):
353 """Return True if the tosca template has nested templates."""
354 return self.nested_tosca_templates_with_topology is not None and \
355 len(self.nested_tosca_templates_with_topology) >= 1