80cb1cbd1c78abb0eb8a2219b09d80dc58a22004
[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 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
31
32
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')
48
49 log = logging.getLogger("tosca.model")
50
51 YAML_LOADER = toscaparser.utils.yamlparser.load_yaml
52
53
54 class ToscaTemplate(object):
55     exttools = ExtTools()
56
57     VALID_TEMPLATE_VERSIONS = ['tosca_simple_yaml_1_0']
58
59     VALID_TEMPLATE_VERSIONS.extend(exttools.get_versions())
60
61     ADDITIONAL_SECTIONS = {'tosca_simple_yaml_1_0': SPECIAL_SECTIONS}
62
63     ADDITIONAL_SECTIONS.update(exttools.get_sections())
64
65     '''Load the template data.'''
66     def __init__(self, path=None, parsed_params=None, a_file=True,
67                  yaml_dict_tpl=None, sub_mapped_node_template=None):
68         if sub_mapped_node_template is None:
69             ExceptionCollector.start()
70         self.a_file = a_file
71         self.input_path = None
72         self.path = None
73         self.tpl = None
74         self.sub_mapped_node_template = sub_mapped_node_template
75         self.nested_tosca_tpls_with_topology = {}
76         self.nested_tosca_templates_with_topology = []
77         if path:
78             self.input_path = path
79             self.path = self._get_path(path)
80             if self.path:
81                 self.tpl = YAML_LOADER(self.path, self.a_file)
82             if yaml_dict_tpl:
83                 msg = (_('Both path and yaml_dict_tpl arguments were '
84                          'provided. Using path and ignoring yaml_dict_tpl.'))
85                 log.info(msg)
86                 print(msg)
87         else:
88             if yaml_dict_tpl:
89                 self.tpl = yaml_dict_tpl
90             else:
91                 ExceptionCollector.appendException(
92                     ValueError(_('No path or yaml_dict_tpl was provided. '
93                                  'There is nothing to parse.')))
94
95         if self.tpl:
96             self.parsed_params = parsed_params
97             self._validate_field()
98             self.version = self._tpl_version()
99             self.relationship_types = self._tpl_relationship_types()
100             self.description = self._tpl_description()
101             self.topology_template = self._topology_template()
102             self.repositories = self._tpl_repositories()
103             if self.topology_template.tpl:
104                 self.inputs = self._inputs()
105                 self.relationship_templates = self._relationship_templates()
106                 self.nodetemplates = self._nodetemplates()
107                 self.outputs = self._outputs()
108                 self._handle_nested_tosca_templates_with_topology()
109                 self.graph = ToscaGraph(self.nodetemplates)
110
111         if sub_mapped_node_template is None:
112             ExceptionCollector.stop()
113         self.verify_template()
114
115     def _topology_template(self):
116         return TopologyTemplate(self._tpl_topology_template(),
117                                 self._get_all_custom_defs(),
118                                 self.relationship_types,
119                                 self.parsed_params,
120                                 self.sub_mapped_node_template)
121
122     def _inputs(self):
123         return self.topology_template.inputs
124
125     def _nodetemplates(self):
126         return self.topology_template.nodetemplates
127
128     def _relationship_templates(self):
129         return self.topology_template.relationship_templates
130
131     def _outputs(self):
132         return self.topology_template.outputs
133
134     def _tpl_version(self):
135         return self.tpl.get(DEFINITION_VERSION)
136
137     def _tpl_description(self):
138         desc = self.tpl.get(DESCRIPTION)
139         if desc:
140             return desc.rstrip()
141
142     def _tpl_imports(self):
143         return self.tpl.get(IMPORTS)
144
145     def _tpl_repositories(self):
146         repositories = self.tpl.get(REPOSITORIES)
147         reposit = []
148         if repositories:
149             for name, val in repositories.items():
150                 reposits = Repository(name, val)
151                 reposit.append(reposits)
152         return reposit
153
154     def _tpl_relationship_types(self):
155         return self._get_custom_types(RELATIONSHIP_TYPES)
156
157     def _tpl_relationship_templates(self):
158         topology_template = self._tpl_topology_template()
159         return topology_template.get(RELATIONSHIP_TEMPLATES)
160
161     def _tpl_topology_template(self):
162         return self.tpl.get(TOPOLOGY_TEMPLATE)
163
164     def _get_all_custom_defs(self, imports=None):
165         types = [IMPORTS, NODE_TYPES, CAPABILITY_TYPES, RELATIONSHIP_TYPES,
166                  DATA_TYPES, POLICY_TYPES, GROUP_TYPES]
167         custom_defs_final = {}
168         custom_defs = self._get_custom_types(types, imports)
169         if custom_defs:
170             custom_defs_final.update(custom_defs)
171             if custom_defs.get(IMPORTS):
172                 import_defs = self._get_all_custom_defs(
173                     custom_defs.get(IMPORTS))
174                 custom_defs_final.update(import_defs)
175
176         # As imports are not custom_types, removing from the dict
177         custom_defs_final.pop(IMPORTS, None)
178         return custom_defs_final
179
180     def _get_custom_types(self, type_definitions, imports=None):
181         """Handle custom types defined in imported template files
182
183         This method loads the custom type definitions referenced in "imports"
184         section of the TOSCA YAML template.
185         """
186
187         custom_defs = {}
188         type_defs = []
189         if not isinstance(type_definitions, list):
190             type_defs.append(type_definitions)
191         else:
192             type_defs = type_definitions
193
194         if not imports:
195             imports = self._tpl_imports()
196
197         if imports:
198             custom_service = \
199                 toscaparser.imports.ImportsLoader(imports, self.path,
200                                                   type_defs, self.tpl)
201
202             nested_tosca_tpls = custom_service.get_nested_tosca_tpls()
203             self._update_nested_tosca_tpls_with_topology(nested_tosca_tpls)
204
205             custom_defs = custom_service.get_custom_defs()
206             if not custom_defs:
207                 return
208
209         # Handle custom types defined in current template file
210         for type_def in type_defs:
211             if type_def != IMPORTS:
212                 inner_custom_types = self.tpl.get(type_def) or {}
213                 if inner_custom_types:
214                     custom_defs.update(inner_custom_types)
215         return custom_defs
216
217     def _update_nested_tosca_tpls_with_topology(self, nested_tosca_tpls):
218         for tpl in nested_tosca_tpls:
219             filename, tosca_tpl = list(tpl.items())[0]
220             if (tosca_tpl.get(TOPOLOGY_TEMPLATE) and
221                 filename not in list(
222                     self.nested_tosca_tpls_with_topology.keys())):
223                 self.nested_tosca_tpls_with_topology.update(tpl)
224
225     def _handle_nested_tosca_templates_with_topology(self):
226         for fname, tosca_tpl in self.nested_tosca_tpls_with_topology.items():
227             for nodetemplate in self.nodetemplates:
228                 if self._is_sub_mapped_node(nodetemplate, tosca_tpl):
229                     nested_template = ToscaTemplate(
230                         path=fname, parsed_params=self.parsed_params,
231                         yaml_dict_tpl=tosca_tpl,
232                         sub_mapped_node_template=nodetemplate)
233                     if nested_template.has_substitution_mappings():
234                         # Record the nested templates in top level template
235                         self.nested_tosca_templates_with_topology.\
236                             append(nested_template)
237                         # Set the substitution toscatemplate for mapped node
238                         nodetemplate.sub_mapping_tosca_template = \
239                             nested_template
240
241     def _validate_field(self):
242         version = self._tpl_version()
243         if not version:
244             ExceptionCollector.appendException(
245                 MissingRequiredFieldError(what='Template',
246                                           required=DEFINITION_VERSION))
247         else:
248             self._validate_version(version)
249             self.version = version
250
251         for name in self.tpl:
252             if (name not in SECTIONS and
253                name not in self.ADDITIONAL_SECTIONS.get(version, ())):
254                 ExceptionCollector.appendException(
255                     UnknownFieldError(what='Template', field=name))
256
257     def _validate_version(self, version):
258         if version not in self.VALID_TEMPLATE_VERSIONS:
259             ExceptionCollector.appendException(
260                 InvalidTemplateVersion(
261                     what=version,
262                     valid_versions=', '. join(self.VALID_TEMPLATE_VERSIONS)))
263         else:
264             if version != 'tosca_simple_yaml_1_0':
265                 update_definitions(version)
266
267     def _get_path(self, path):
268         if path.lower().endswith('.yaml'):
269             return path
270         elif path.lower().endswith(('.zip', '.csar')):
271             # a CSAR archive
272             csar = CSAR(path, self.a_file)
273             if csar.validate():
274                 csar.decompress()
275                 self.a_file = True  # the file has been decompressed locally
276                 return os.path.join(csar.temp_dir, csar.get_main_template())
277         else:
278             ExceptionCollector.appendException(
279                 ValueError(_('"%(path)s" is not a valid file.')
280                            % {'path': path}))
281
282     def verify_template(self):
283         if ExceptionCollector.exceptionsCaught():
284             if self.input_path:
285                 raise ValidationError(
286                     message=(_('\nThe input "%(path)s" failed validation with '
287                                'the following error(s): \n\n\t')
288                              % {'path': self.input_path}) +
289                     '\n\t'.join(ExceptionCollector.getExceptionsReport()))
290             else:
291                 raise ValidationError(
292                     message=_('\nThe pre-parsed input failed validation with '
293                               'the following error(s): \n\n\t') +
294                     '\n\t'.join(ExceptionCollector.getExceptionsReport()))
295         else:
296             if self.input_path:
297                 msg = (_('The input "%(path)s" successfully passed '
298                          'validation.') % {'path': self.input_path})
299             else:
300                 msg = _('The pre-parsed input successfully passed validation.')
301
302             log.info(msg)
303
304     def _is_sub_mapped_node(self, nodetemplate, tosca_tpl):
305         """Return True if the nodetemple is substituted."""
306         if (nodetemplate and not nodetemplate.substitution_mapped and
307                 self.get_sub_mapping_node_type(tosca_tpl) == nodetemplate.type
308                 and len(nodetemplate.interfaces) < 1):
309             return True
310         else:
311             return False
312
313     def get_sub_mapping_node_type(self, tosca_tpl):
314         """Return substitution mappings node type."""
315         if tosca_tpl:
316             return TopologyTemplate.get_sub_mapping_node_type(
317                 tosca_tpl.get(TOPOLOGY_TEMPLATE))
318
319     def has_substitution_mappings(self):
320         """Return True if the template has valid substitution mappings."""
321         return self.topology_template is not None and \
322             self.topology_template.substitution_mappings is not None
323
324     def has_nested_templates(self):
325         """Return True if the tosca template has nested templates."""
326         return self.nested_tosca_templates_with_topology is not None and \
327             len(self.nested_tosca_templates_with_topology) >= 1