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 toscaparser.common.exception import ExceptionCollector
18 from toscaparser.common.exception import InvalidTemplateVersion
19 from toscaparser.common.exception import MissingRequiredFieldError
20 from toscaparser.common.exception import UnknownFieldError
21 from toscaparser.common.exception import ValidationError
22 from toscaparser.elements.entity_type import update_definitions
23 from toscaparser.extensions.exttools import ExtTools
24 import toscaparser.imports
25 from toscaparser.prereq.csar import CSAR
26 from toscaparser.repositories import Repository
27 from toscaparser.topology_template import TopologyTemplate
28 from toscaparser.tpl_relationship_graph import ToscaGraph
29 from toscaparser.utils.gettextutils import _
30 import toscaparser.utils.yamlparser
33 # TOSCA template key names
34 SECTIONS = (DEFINITION_VERSION, DEFAULT_NAMESPACE, TEMPLATE_NAME,
35 TOPOLOGY_TEMPLATE, TEMPLATE_AUTHOR, TEMPLATE_VERSION,
36 DESCRIPTION, IMPORTS, DSL_DEFINITIONS, NODE_TYPES,
37 RELATIONSHIP_TYPES, RELATIONSHIP_TEMPLATES,
38 CAPABILITY_TYPES, ARTIFACT_TYPES, DATA_TYPES,
39 POLICY_TYPES, GROUP_TYPES, REPOSITORIES) = \
40 ('tosca_definitions_version', 'tosca_default_namespace',
41 'template_name', 'topology_template', 'template_author',
42 'template_version', 'description', 'imports', 'dsl_definitions',
43 'node_types', 'relationship_types', 'relationship_templates',
44 'capability_types', 'artifact_types', 'data_types',
45 'policy_types', 'group_types', 'repositories')
46 # Sections that are specific to individual template definitions
47 SPECIAL_SECTIONS = (METADATA) = ('metadata')
49 log = logging.getLogger("tosca.model")
51 YAML_LOADER = toscaparser.utils.yamlparser.load_yaml
54 class ToscaTemplate(object):
57 VALID_TEMPLATE_VERSIONS = ['tosca_simple_yaml_1_0']
59 VALID_TEMPLATE_VERSIONS.extend(exttools.get_versions())
61 ADDITIONAL_SECTIONS = {'tosca_simple_yaml_1_0': SPECIAL_SECTIONS}
63 ADDITIONAL_SECTIONS.update(exttools.get_sections())
65 '''Load the template data.'''
66 def __init__(self, path=None, parsed_params=None, a_file=True,
67 yaml_dict_tpl=None, submaped_node_template=None):
68 ExceptionCollector.start()
70 self.input_path = None
73 self.submaped_node_template = submaped_node_template
74 self.nested_tosca_tpls = {}
75 self.nested_tosca_templates = []
77 self.input_path = path
78 self.path = self._get_path(path)
80 self.tpl = YAML_LOADER(self.path, self.a_file)
82 msg = (_('Both path and yaml_dict_tpl arguments were '
83 'provided. Using path and ignoring yaml_dict_tpl.'))
88 self.tpl = yaml_dict_tpl
90 ExceptionCollector.appendException(
91 ValueError(_('No path or yaml_dict_tpl was provided. '
92 'There is nothing to parse.')))
95 self.parsed_params = parsed_params
96 self._validate_field()
97 self.version = self._tpl_version()
98 self.relationship_types = self._tpl_relationship_types()
99 self.description = self._tpl_description()
100 self.topology_template = self._topology_template()
101 self.repositories = self._tpl_repositories()
102 if self.topology_template.tpl:
103 self.inputs = self._inputs()
104 self.relationship_templates = self._relationship_templates()
105 self.nodetemplates = self._nodetemplates()
106 self.outputs = self._outputs()
107 self._handle_nested_topo_templates()
108 self.graph = ToscaGraph(self.nodetemplates)
110 ExceptionCollector.stop()
111 self.verify_template()
113 def _topology_template(self):
114 return TopologyTemplate(self._tpl_topology_template(),
115 self._get_all_custom_defs(),
116 self.relationship_types,
118 self.submaped_node_template)
121 return self.topology_template.inputs
123 def _nodetemplates(self):
124 return self.topology_template.nodetemplates
126 def _relationship_templates(self):
127 return self.topology_template.relationship_templates
130 return self.topology_template.outputs
132 def _tpl_version(self):
133 return self.tpl.get(DEFINITION_VERSION)
135 def _tpl_description(self):
136 desc = self.tpl.get(DESCRIPTION)
140 def _tpl_imports(self):
141 return self.tpl.get(IMPORTS)
143 def _tpl_repositories(self):
144 repositories = self.tpl.get(REPOSITORIES)
147 for name, val in repositories.items():
148 reposits = Repository(name, val)
149 reposit.append(reposits)
152 def _tpl_relationship_types(self):
153 return self._get_custom_types(RELATIONSHIP_TYPES)
155 def _tpl_relationship_templates(self):
156 topology_template = self._tpl_topology_template()
157 return topology_template.get(RELATIONSHIP_TEMPLATES)
159 def _tpl_topology_template(self):
160 return self.tpl.get(TOPOLOGY_TEMPLATE)
162 def _get_all_custom_defs(self, imports=None):
163 types = [IMPORTS, NODE_TYPES, CAPABILITY_TYPES, RELATIONSHIP_TYPES,
164 DATA_TYPES, POLICY_TYPES, GROUP_TYPES]
165 custom_defs_final = {}
166 custom_defs = self._get_custom_types(types, imports)
168 custom_defs_final.update(custom_defs)
169 if custom_defs.get(IMPORTS):
170 import_defs = self._get_all_custom_defs(
171 custom_defs.get(IMPORTS))
172 custom_defs_final.update(import_defs)
174 # As imports are not custom_types, removing from the dict
175 custom_defs_final.pop(IMPORTS, None)
176 return custom_defs_final
178 def _get_custom_types(self, type_definitions, imports=None):
179 """Handle custom types defined in imported template files
181 This method loads the custom type definitions referenced in "imports"
182 section of the TOSCA YAML template.
187 if not isinstance(type_definitions, list):
188 type_defs.append(type_definitions)
190 type_defs = type_definitions
193 imports = self._tpl_imports()
197 toscaparser.imports.ImportsLoader(imports, self.path,
200 nested_topo_tpls = custom_service.get_nested_topo_tpls()
201 self._update_nested_topo_tpls(nested_topo_tpls)
203 custom_defs = custom_service.get_custom_defs()
207 # Handle custom types defined in current template file
208 for type_def in type_defs:
209 if type_def != IMPORTS:
210 inner_custom_types = self.tpl.get(type_def) or {}
211 if inner_custom_types:
212 custom_defs.update(inner_custom_types)
215 def _update_nested_topo_tpls(self, nested_topo_tpls):
216 for tpl in nested_topo_tpls:
217 filename, tosca_tpl = list(tpl.items())[0]
218 if (tosca_tpl.get(TOPOLOGY_TEMPLATE) and
219 filename not in list(self.nested_tosca_tpls.keys())):
220 self.nested_tosca_tpls.update(tpl)
222 def _handle_nested_topo_templates(self):
223 for filename, tosca_tpl in self.nested_tosca_tpls.items():
224 for nodetemplate in self.nodetemplates:
225 if self._is_substitution_mapped_node(nodetemplate, tosca_tpl):
226 nested_template = ToscaTemplate(
227 path=filename, parsed_params=self.parsed_params,
228 yaml_dict_tpl=tosca_tpl,
229 submaped_node_template=nodetemplate)
230 if nested_template.has_substitution_mappings():
231 filenames = [tpl.path for tpl in
232 self.nested_tosca_templates]
233 if filename not in filenames:
234 self.nested_tosca_templates.append(nested_template)
236 def _validate_field(self):
237 version = self._tpl_version()
239 ExceptionCollector.appendException(
240 MissingRequiredFieldError(what='Template',
241 required=DEFINITION_VERSION))
243 self._validate_version(version)
244 self.version = version
246 for name in self.tpl:
247 if (name not in SECTIONS and
248 name not in self.ADDITIONAL_SECTIONS.get(version, ())):
249 ExceptionCollector.appendException(
250 UnknownFieldError(what='Template', field=name))
252 def _validate_version(self, version):
253 if version not in self.VALID_TEMPLATE_VERSIONS:
254 ExceptionCollector.appendException(
255 InvalidTemplateVersion(
257 valid_versions=', '. join(self.VALID_TEMPLATE_VERSIONS)))
259 if version != 'tosca_simple_yaml_1_0':
260 update_definitions(version)
262 def _get_path(self, path):
263 if path.lower().endswith('.yaml'):
265 elif path.lower().endswith(('.zip', '.csar')):
267 csar = CSAR(path, self.a_file)
270 self.a_file = True # the file has been decompressed locally
271 return os.path.join(csar.temp_dir, csar.get_main_template())
273 ExceptionCollector.appendException(
274 ValueError(_('"%(path)s" is not a valid file.')
277 def verify_template(self):
278 if ExceptionCollector.exceptionsCaught():
280 raise ValidationError(
281 message=(_('\nThe input "%(path)s" failed validation with '
282 'the following error(s): \n\n\t')
283 % {'path': self.input_path}) +
284 '\n\t'.join(ExceptionCollector.getExceptionsReport()))
286 raise ValidationError(
287 message=_('\nThe pre-parsed input failed validation with '
288 'the following error(s): \n\n\t') +
289 '\n\t'.join(ExceptionCollector.getExceptionsReport()))
292 msg = (_('The input "%(path)s" successfully passed '
293 'validation.') % {'path': self.input_path})
295 msg = _('The pre-parsed input successfully passed validation.')
299 def _is_substitution_mapped_node(self, nodetemplate, tosca_tpl):
300 """Return True if the nodetemple is substituted."""
301 if (nodetemplate and not nodetemplate.substitution_mapped and
302 self.get_submaped_node_type(tosca_tpl) == nodetemplate.type and
303 len(nodetemplate.interfaces) < 1):
308 def get_submaped_node_type(self, tosca_tpl):
309 """Return substitution mappings node type."""
311 return TopologyTemplate.get_submaped_node_type(
312 tosca_tpl.get(TOPOLOGY_TEMPLATE))
314 def has_substitution_mappings(self):
315 """Return True if the template has valid substitution mappings."""
316 return self.topology_template is not None and \
317 self.topology_template.substitution_mappings is not None
319 def has_nested_templates(self):
320 """Return True if the tosca template has nested templates."""
321 return self.nested_tosca_templates is not None and \
322 len(self.nested_tosca_templates) >= 1