Bug fixe version info print
[parser.git] / tosca2heat / tosca-parser / toscaparser / tosca_template.py
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
4 #
5 #         http://www.apache.org/licenses/LICENSE-2.0
6 #
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
11 #    under the License.
12
13
14 import logging
15 import os
16
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 MissingRequiredInputError
22 from toscaparser.common.exception import MissingRequiredOutputError
23 from toscaparser.common.exception import MissingRequiredParameterError
24 from toscaparser.common.exception import UnknownFieldError
25 from toscaparser.common.exception import ValidationError
26 from toscaparser.elements.entity_type import update_definitions
27 from toscaparser.extensions.exttools import ExtTools
28 import toscaparser.imports
29 from toscaparser.prereq.csar import CSAR
30 from toscaparser.repositories import Repository
31 from toscaparser.topology_template import TopologyTemplate
32 from toscaparser.tpl_relationship_graph import ToscaGraph
33 from toscaparser.utils.gettextutils import _
34 import toscaparser.utils.yamlparser
35
36
37 # TOSCA template key names
38 SECTIONS = (DEFINITION_VERSION, DEFAULT_NAMESPACE, TEMPLATE_NAME,
39             TOPOLOGY_TEMPLATE, TEMPLATE_AUTHOR, TEMPLATE_VERSION,
40             DESCRIPTION, IMPORTS, DSL_DEFINITIONS, NODE_TYPES,
41             RELATIONSHIP_TYPES, RELATIONSHIP_TEMPLATES,
42             CAPABILITY_TYPES, ARTIFACT_TYPES, DATA_TYPES, INTERFACE_TYPES,
43             POLICY_TYPES, GROUP_TYPES, REPOSITORIES) = \
44            ('tosca_definitions_version', 'tosca_default_namespace',
45             'template_name', 'topology_template', 'template_author',
46             'template_version', 'description', 'imports', 'dsl_definitions',
47             'node_types', 'relationship_types', 'relationship_templates',
48             'capability_types', 'artifact_types', 'data_types',
49             'interface_types', 'policy_types', 'group_types', 'repositories')
50 # Sections that are specific to individual template definitions
51 SPECIAL_SECTIONS = (METADATA) = ('metadata')
52
53 log = logging.getLogger("tosca.model")
54
55 YAML_LOADER = toscaparser.utils.yamlparser.load_yaml
56
57
58 class ToscaTemplate(object):
59     exttools = ExtTools()
60
61     VALID_TEMPLATE_VERSIONS = ['tosca_simple_yaml_1_0',
62                                'tosca_simple_yaml_1_1']
63
64     VALID_TEMPLATE_VERSIONS.extend(exttools.get_versions())
65
66     ADDITIONAL_SECTIONS = {'tosca_simple_yaml_1_0': SPECIAL_SECTIONS,
67                            'tosca_simple_yaml_1_1': SPECIAL_SECTIONS}
68
69     ADDITIONAL_SECTIONS.update(exttools.get_sections())
70
71     '''Load the template data.'''
72     def __init__(self, path=None, parsed_params=None, a_file=True,
73                  yaml_dict_tpl=None, sub_mapped_node_template=None,
74                  no_required_paras_check=False,
75                  debug_mode=False):
76         if sub_mapped_node_template is None:
77             ExceptionCollector.start()
78         self.a_file = a_file
79         self.input_path = None
80         self.path = None
81         self.tpl = None
82         self.sub_mapped_node_template = sub_mapped_node_template
83         self.nested_tosca_tpls_with_topology = {}
84         self.nested_tosca_templates_with_topology = []
85         self.no_required_paras_check = no_required_paras_check
86         self.debug_mode = debug_mode
87
88         if path:
89             self.input_path = path
90             self.path = self._get_path(path)
91             if self.path:
92                 self.tpl = YAML_LOADER(self.path, self.a_file)
93             if yaml_dict_tpl:
94                 msg = (_('Both path and yaml_dict_tpl arguments were '
95                          'provided. Using path and ignoring yaml_dict_tpl.'))
96                 log.info(msg)
97                 print(msg)
98         else:
99             if yaml_dict_tpl:
100                 self.tpl = yaml_dict_tpl
101             else:
102                 ExceptionCollector.appendException(
103                     ValueError(_('No path or yaml_dict_tpl was provided. '
104                                  'There is nothing to parse.')))
105
106         if self.tpl:
107             self.parsed_params = parsed_params
108             self._validate_field()
109             self.version = self._tpl_version()
110             self.relationship_types = self._tpl_relationship_types()
111             self.description = self._tpl_description()
112             self.topology_template = self._topology_template()
113             self.repositories = self._tpl_repositories()
114             if self.topology_template.tpl:
115                 self.inputs = self._inputs()
116                 self.relationship_templates = self._relationship_templates()
117                 self.nodetemplates = self._nodetemplates()
118                 self.outputs = self._outputs()
119                 self.policies = self._policies()
120                 self._handle_nested_tosca_templates_with_topology()
121                 self.graph = ToscaGraph(self.nodetemplates)
122
123         if sub_mapped_node_template is None:
124             ExceptionCollector.stop()
125         self.verify_template()
126
127     def _topology_template(self):
128         return TopologyTemplate(self._tpl_topology_template(),
129                                 self._get_all_custom_defs(),
130                                 self.relationship_types,
131                                 self.parsed_params,
132                                 self.sub_mapped_node_template)
133
134     def _inputs(self):
135         return self.topology_template.inputs
136
137     def _nodetemplates(self):
138         return self.topology_template.nodetemplates
139
140     def _relationship_templates(self):
141         return self.topology_template.relationship_templates
142
143     def _outputs(self):
144         return self.topology_template.outputs
145
146     def _tpl_version(self):
147         return self.tpl.get(DEFINITION_VERSION)
148
149     def _tpl_description(self):
150         desc = self.tpl.get(DESCRIPTION)
151         if desc:
152             return desc.rstrip()
153
154     def _tpl_imports(self):
155         return self.tpl.get(IMPORTS)
156
157     def _tpl_repositories(self):
158         repositories = self.tpl.get(REPOSITORIES)
159         reposit = []
160         if repositories:
161             for name, val in repositories.items():
162                 reposits = Repository(name, val)
163                 reposit.append(reposits)
164         return reposit
165
166     def _tpl_relationship_types(self):
167         return self._get_custom_types(RELATIONSHIP_TYPES)
168
169     def _tpl_relationship_templates(self):
170         topology_template = self._tpl_topology_template()
171         return topology_template.get(RELATIONSHIP_TEMPLATES)
172
173     def _tpl_topology_template(self):
174         return self.tpl.get(TOPOLOGY_TEMPLATE)
175
176     def _policies(self):
177         return self.topology_template.policies
178
179     def _get_all_custom_defs(self, imports=None):
180         types = [IMPORTS, NODE_TYPES, CAPABILITY_TYPES, RELATIONSHIP_TYPES,
181                  DATA_TYPES, INTERFACE_TYPES, POLICY_TYPES, GROUP_TYPES]
182         custom_defs_final = {}
183         custom_defs = self._get_custom_types(types, imports)
184         if custom_defs:
185             custom_defs_final.update(custom_defs)
186             if custom_defs.get(IMPORTS):
187                 import_defs = self._get_all_custom_defs(
188                     custom_defs.get(IMPORTS))
189                 custom_defs_final.update(import_defs)
190
191         # As imports are not custom_types, removing from the dict
192         custom_defs_final.pop(IMPORTS, None)
193         return custom_defs_final
194
195     def _get_custom_types(self, type_definitions, imports=None):
196         """Handle custom types defined in imported template files
197
198         This method loads the custom type definitions referenced in "imports"
199         section of the TOSCA YAML template.
200         """
201
202         custom_defs = {}
203         type_defs = []
204         if not isinstance(type_definitions, list):
205             type_defs.append(type_definitions)
206         else:
207             type_defs = type_definitions
208
209         if not imports:
210             imports = self._tpl_imports()
211
212         if imports:
213             custom_service = toscaparser.imports.\
214                 ImportsLoader(imports, self.path,
215                               type_defs, self.tpl)
216
217             nested_tosca_tpls = custom_service.get_nested_tosca_tpls()
218             self._update_nested_tosca_tpls_with_topology(nested_tosca_tpls)
219
220             custom_defs = custom_service.get_custom_defs()
221             if not custom_defs:
222                 return
223
224         # Handle custom types defined in current template file
225         for type_def in type_defs:
226             if type_def != IMPORTS:
227                 inner_custom_types = self.tpl.get(type_def) or {}
228                 if inner_custom_types:
229                     custom_defs.update(inner_custom_types)
230         return custom_defs
231
232     def _update_nested_tosca_tpls_with_topology(self, nested_tosca_tpls):
233         for tpl in nested_tosca_tpls:
234             filename, tosca_tpl = list(tpl.items())[0]
235             if (tosca_tpl.get(TOPOLOGY_TEMPLATE) and
236                 filename not in list(
237                     self.nested_tosca_tpls_with_topology.keys())):
238                 self.nested_tosca_tpls_with_topology.update(tpl)
239
240     def _handle_nested_tosca_templates_with_topology(self):
241         for fname, tosca_tpl in self.nested_tosca_tpls_with_topology.items():
242             for nodetemplate in self.nodetemplates:
243                 if self._is_sub_mapped_node(nodetemplate, tosca_tpl):
244                     parsed_params = self._get_params_for_nested_template(
245                         nodetemplate)
246
247                     cache_exeptions = deepcopy(ExceptionCollector.exceptions)
248                     cache_exeptions_state = \
249                         deepcopy(ExceptionCollector.collecting)
250                     nested_template = None
251                     try:
252                         nrpv = self.no_required_paras_check
253                         nested_template = ToscaTemplate(
254                             path=fname, parsed_params=parsed_params,
255                             sub_mapped_node_template=nodetemplate,
256                             no_required_paras_check=nrpv,
257                             debug_mode=self.debug_mode)
258                     except ValidationError as e:
259                         log.error(e.message)
260                         if self.debug_mode:
261                             print(e.message)
262                         else:
263                             raise e
264
265                     ExceptionCollector.exceptions = deepcopy(cache_exeptions)
266                     ExceptionCollector.collecting = \
267                         deepcopy(cache_exeptions_state)
268
269                     if nested_template and \
270                             nested_template._has_substitution_mappings():
271                         # Record the nested templates in top level template
272                         self.nested_tosca_templates_with_topology.\
273                             append(nested_template)
274                         # Set the substitution toscatemplate for mapped node
275                         nodetemplate.substitution_mapped = \
276                             nested_template
277
278     def _validate_field(self):
279         version = self._tpl_version()
280         if not version:
281             ExceptionCollector.appendException(
282                 MissingRequiredFieldError(what='Template',
283                                           required=DEFINITION_VERSION))
284         else:
285             self._validate_version(version)
286             self.version = version
287
288         for name in self.tpl:
289             if (name not in SECTIONS and
290                name not in self.ADDITIONAL_SECTIONS.get(version, ())):
291                 ExceptionCollector.appendException(
292                     UnknownFieldError(what='Template', field=name))
293
294     def _validate_version(self, version):
295         if version not in self.VALID_TEMPLATE_VERSIONS:
296             ExceptionCollector.appendException(
297                 InvalidTemplateVersion(
298                     what=version,
299                     valid_versions=', '. join(self.VALID_TEMPLATE_VERSIONS)))
300         else:
301             if (version != 'tosca_simple_yaml_1_0' and
302                     version != 'tosca_simple_yaml_1_1'):
303                 update_definitions(version)
304
305     def _get_path(self, path):
306         if path.lower().endswith('.yaml') or path.lower().endswith('.yml'):
307             return path
308         elif path.lower().endswith(('.zip', '.csar')):
309             # a CSAR archive
310             csar = CSAR(path, self.a_file)
311             if csar.validate():
312                 csar.decompress()
313                 self.a_file = True  # the file has been decompressed locally
314                 return os.path.join(csar.temp_dir, csar.get_main_template())
315         else:
316             ExceptionCollector.appendException(
317                 ValueError(_('"%(path)s" is not a valid file.')
318                            % {'path': path}))
319
320     def verify_template(self):
321         if self.no_required_paras_check:
322             ExceptionCollector.removeException(
323                 MissingRequiredParameterError)
324             ExceptionCollector.removeException(
325                 MissingRequiredInputError)
326             ExceptionCollector.removeException(
327                 MissingRequiredOutputError)
328         if ExceptionCollector.exceptionsCaught():
329             if self.input_path:
330                 exceptions = ValidationError(
331                     message=(_('\nThe input "%(path)s" failed validation with '
332                                'the following error(s): \n\n\t')
333                              % {'path': self.input_path}) +
334                     '\n\t'.join(ExceptionCollector.getExceptionsReport()))
335             else:
336                 exceptions = ValidationError(
337                     message=_('\nThe pre-parsed input failed validation with '
338                               'the following error(s): \n\n\t') +
339                     '\n\t'.join(ExceptionCollector.getExceptionsReport()))
340             if not self.debug_mode:
341                 raise exceptions
342             else:
343                 if self.sub_mapped_node_template:
344                     msg = _('======== nested service template ======== ')
345
346                 else:
347                     msg = _('======== main service template ======== ')
348                 print(msg)
349                 print(exceptions.message)
350                 log.error(msg)
351                 log.error(exceptions.message)
352         else:
353             if self.input_path:
354                 msg = (_('The input "%(path)s" successfully passed '
355                          'validation.') % {'path': self.input_path})
356             else:
357                 msg = _('The pre-parsed input successfully passed validation.')
358
359             log.info(msg)
360
361     def _is_sub_mapped_node(self, nodetemplate, tosca_tpl):
362         """Return True if the nodetemple is substituted."""
363         if (nodetemplate and not nodetemplate.substitution_mapped and
364                 self.get_sub_mapping_node_type(tosca_tpl) == nodetemplate.type
365                 and len(nodetemplate.interfaces) < 1):
366             return True
367         else:
368             return False
369
370     def _get_params_for_nested_template(self, nodetemplate):
371         """Return total params for nested_template."""
372         parsed_params = deepcopy(self.parsed_params) \
373             if self.parsed_params else {}
374         if nodetemplate:
375             for pname in nodetemplate.get_properties():
376                 parsed_params.update({pname:
377                                       nodetemplate.get_property_value(pname)})
378         return parsed_params
379
380     def get_sub_mapping_node_type(self, tosca_tpl):
381         """Return substitution mappings node type."""
382         if tosca_tpl:
383             return TopologyTemplate.get_sub_mapping_node_type(
384                 tosca_tpl.get(TOPOLOGY_TEMPLATE))
385
386     def _has_substitution_mappings(self):
387         """Return True if the template has valid substitution mappings."""
388         return self.topology_template is not None and \
389             self.topology_template.substitution_mappings is not None
390
391     def has_nested_templates(self):
392         """Return True if the tosca template has nested templates."""
393         return self.nested_tosca_templates_with_topology is not None and \
394             len(self.nested_tosca_templates_with_topology) >= 1