Synchronise the openstack bugs
[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):
68         ExceptionCollector.start()
69         self.a_file = a_file
70         self.input_path = None
71         self.path = None
72         self.tpl = None
73         self.nested_tosca_template = []
74         if path:
75             self.input_path = path
76             self.path = self._get_path(path)
77             if self.path:
78                 self.tpl = YAML_LOADER(self.path, self.a_file)
79             if yaml_dict_tpl:
80                 msg = (_('Both path and yaml_dict_tpl arguments were '
81                          'provided. Using path and ignoring yaml_dict_tpl.'))
82                 log.info(msg)
83                 print(msg)
84         else:
85             if yaml_dict_tpl:
86                 self.tpl = yaml_dict_tpl
87             else:
88                 ExceptionCollector.appendException(
89                     ValueError(_('No path or yaml_dict_tpl was provided. '
90                                  'There is nothing to parse.')))
91
92         if self.tpl:
93             self.parsed_params = parsed_params
94             self._validate_field()
95             self.version = self._tpl_version()
96             self.relationship_types = self._tpl_relationship_types()
97             self.description = self._tpl_description()
98             self.topology_template = self._topology_template()
99             self.repositories = self._tpl_repositories()
100             if self.topology_template.tpl:
101                 self.inputs = self._inputs()
102                 self.relationship_templates = self._relationship_templates()
103                 self.nodetemplates = self._nodetemplates()
104                 self.outputs = self._outputs()
105                 self.graph = ToscaGraph(self.nodetemplates)
106
107         ExceptionCollector.stop()
108         self.verify_template()
109
110     def _topology_template(self):
111         return TopologyTemplate(self._tpl_topology_template(),
112                                 self._get_all_custom_defs(),
113                                 self.relationship_types,
114                                 self.parsed_params)
115
116     def _inputs(self):
117         return self.topology_template.inputs
118
119     def _nodetemplates(self):
120         return self.topology_template.nodetemplates
121
122     def _relationship_templates(self):
123         return self.topology_template.relationship_templates
124
125     def _outputs(self):
126         return self.topology_template.outputs
127
128     def _tpl_version(self):
129         return self.tpl.get(DEFINITION_VERSION)
130
131     def _tpl_description(self):
132         desc = self.tpl.get(DESCRIPTION)
133         if desc:
134             return desc.rstrip()
135
136     def _tpl_imports(self):
137         return self.tpl.get(IMPORTS)
138
139     def _tpl_repositories(self):
140         repositories = self.tpl.get(REPOSITORIES)
141         reposit = []
142         if repositories:
143             for name, val in repositories.items():
144                 reposits = Repository(name, val)
145                 reposit.append(reposits)
146         return reposit
147
148     def _tpl_relationship_types(self):
149         return self._get_custom_types(RELATIONSHIP_TYPES)
150
151     def _tpl_relationship_templates(self):
152         topology_template = self._tpl_topology_template()
153         return topology_template.get(RELATIONSHIP_TEMPLATES)
154
155     def _tpl_topology_template(self):
156         return self.tpl.get(TOPOLOGY_TEMPLATE)
157
158     def _get_all_custom_defs(self, imports=None):
159         types = [IMPORTS, NODE_TYPES, CAPABILITY_TYPES, RELATIONSHIP_TYPES,
160                  DATA_TYPES, POLICY_TYPES, GROUP_TYPES]
161         custom_defs_final = {}
162         custom_defs = self._get_custom_types(types, imports)
163         if custom_defs:
164             custom_defs_final.update(custom_defs)
165             if custom_defs.get(IMPORTS):
166                 import_defs = self._get_all_custom_defs(
167                     custom_defs.get(IMPORTS))
168                 custom_defs_final.update(import_defs)
169
170         # As imports are not custom_types, removing from the dict
171         custom_defs_final.pop(IMPORTS, None)
172         return custom_defs_final
173
174     def _get_custom_types(self, type_definitions, imports=None):
175         """Handle custom types defined in imported template files
176
177         This method loads the custom type definitions referenced in "imports"
178         section of the TOSCA YAML template.
179         """
180
181         custom_defs = {}
182         type_defs = []
183         if not isinstance(type_definitions, list):
184             type_defs.append(type_definitions)
185         else:
186             type_defs = type_definitions
187
188         if not imports:
189             imports = self._tpl_imports()
190
191         if imports:
192             custom_service = toscaparser.imports.\
193                 ImportsLoader(imports, self.path,
194                               type_defs, self.tpl)
195
196             nested_topo_tpls = custom_service.get_nested_topo_tpls()
197             self._handle_nested_topo_tpls(nested_topo_tpls)
198
199             custom_defs = custom_service.get_custom_defs()
200             if not custom_defs:
201                 return
202
203         # Handle custom types defined in current template file
204         for type_def in type_defs:
205             if type_def != IMPORTS:
206                 inner_custom_types = self.tpl.get(type_def) or {}
207                 if inner_custom_types:
208                     custom_defs.update(inner_custom_types)
209         return custom_defs
210
211     def _handle_nested_topo_tpls(self, nested_topo_tpls):
212         for tpl in nested_topo_tpls:
213             filename, tosca_tpl = tpl.items()[0]
214             if tosca_tpl.get(TOPOLOGY_TEMPLATE):
215                 nested_template = ToscaTemplate(
216                     path=filename, parsed_params=self.parsed_params,
217                     yaml_dict_tpl=tosca_tpl)
218                 if nested_template.topology_template.substitution_mappings:
219                     self.nested_tosca_template.apend(nested_template)
220
221     def _validate_field(self):
222         version = self._tpl_version()
223         if not version:
224             ExceptionCollector.appendException(
225                 MissingRequiredFieldError(what='Template',
226                                           required=DEFINITION_VERSION))
227         else:
228             self._validate_version(version)
229             self.version = version
230
231         for name in self.tpl:
232             if (name not in SECTIONS and
233                name not in self.ADDITIONAL_SECTIONS.get(version, ())):
234                 ExceptionCollector.appendException(
235                     UnknownFieldError(what='Template', field=name))
236
237     def _validate_version(self, version):
238         if version not in self.VALID_TEMPLATE_VERSIONS:
239             ExceptionCollector.appendException(
240                 InvalidTemplateVersion(
241                     what=version,
242                     valid_versions=', '. join(self.VALID_TEMPLATE_VERSIONS)))
243         else:
244             if version != 'tosca_simple_yaml_1_0':
245                 update_definitions(version)
246
247     def _get_path(self, path):
248         if path.lower().endswith('.yaml'):
249             return path
250         elif path.lower().endswith(('.zip', '.csar')):
251             # a CSAR archive
252             csar = CSAR(path, self.a_file)
253             if csar.validate():
254                 csar.decompress()
255                 self.a_file = True  # the file has been decompressed locally
256                 return os.path.join(csar.temp_dir, csar.get_main_template())
257         else:
258             ExceptionCollector.appendException(
259                 ValueError(_('"%(path)s" is not a valid file.')
260                            % {'path': path}))
261
262     def verify_template(self):
263         if ExceptionCollector.exceptionsCaught():
264             if self.input_path:
265                 raise ValidationError(
266                     message=(_('\nThe input "%(path)s" failed validation with '
267                                'the following error(s): \n\n\t')
268                              % {'path': self.input_path}) +
269                     '\n\t'.join(ExceptionCollector.getExceptionsReport()))
270             else:
271                 raise ValidationError(
272                     message=_('\nThe pre-parsed input failed validation with '
273                               'the following error(s): \n\n\t') +
274                     '\n\t'.join(ExceptionCollector.getExceptionsReport()))
275         else:
276             if self.input_path:
277                 msg = (_('The input "%(path)s" successfully passed '
278                          'validation.') % {'path': self.input_path})
279             else:
280                 msg = _('The pre-parsed input successfully passed validation.')
281
282             log.info(msg)