Merge "EntityTemplate has no property of parent_type"
[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.topology_template import TopologyTemplate
27 from toscaparser.tpl_relationship_graph import ToscaGraph
28 from toscaparser.utils.gettextutils import _
29 import toscaparser.utils.yamlparser
30
31
32 # TOSCA template key names
33 SECTIONS = (DEFINITION_VERSION, DEFAULT_NAMESPACE, TEMPLATE_NAME,
34             TOPOLOGY_TEMPLATE, TEMPLATE_AUTHOR, TEMPLATE_VERSION,
35             DESCRIPTION, IMPORTS, DSL_DEFINITIONS, NODE_TYPES,
36             RELATIONSHIP_TYPES, RELATIONSHIP_TEMPLATES,
37             CAPABILITY_TYPES, ARTIFACT_TYPES, DATA_TYPES,
38             POLICY_TYPES, GROUP_TYPES, REPOSITORIES) = \
39            ('tosca_definitions_version', 'tosca_default_namespace',
40             'template_name', 'topology_template', 'template_author',
41             'template_version', 'description', 'imports', 'dsl_definitions',
42             'node_types', 'relationship_types', 'relationship_templates',
43             'capability_types', 'artifact_types', 'data_types',
44             'policy_types', 'group_types', 'repositories')
45 # Sections that are specific to individual template definitions
46 SPECIAL_SECTIONS = (METADATA) = ('metadata')
47
48 log = logging.getLogger("tosca.model")
49
50 YAML_LOADER = toscaparser.utils.yamlparser.load_yaml
51
52
53 class ToscaTemplate(object):
54     exttools = ExtTools()
55
56     VALID_TEMPLATE_VERSIONS = ['tosca_simple_yaml_1_0']
57
58     VALID_TEMPLATE_VERSIONS.extend(exttools.get_versions())
59
60     ADDITIONAL_SECTIONS = {'tosca_simple_yaml_1_0': SPECIAL_SECTIONS}
61
62     ADDITIONAL_SECTIONS.update(exttools.get_sections())
63
64     '''Load the template data.'''
65     def __init__(self, path=None, parsed_params=None, a_file=True,
66                  yaml_dict_tpl=None):
67         ExceptionCollector.start()
68         self.a_file = a_file
69         self.input_path = None
70         self.path = None
71         self.tpl = None
72         self.nested_tosca_template = None
73         if path:
74             self.input_path = path
75             self.path = self._get_path(path)
76             if self.path:
77                 self.tpl = YAML_LOADER(self.path, self.a_file)
78             if yaml_dict_tpl:
79                 msg = (_('Both path and yaml_dict_tpl arguments were '
80                          'provided. Using path and ignoring yaml_dict_tpl.'))
81                 log.info(msg)
82                 print(msg)
83         else:
84             if yaml_dict_tpl:
85                 self.tpl = yaml_dict_tpl
86             else:
87                 ExceptionCollector.appendException(
88                     ValueError(_('No path or yaml_dict_tpl was provided. '
89                                  'There is nothing to parse.')))
90
91         if self.tpl:
92             self.parsed_params = parsed_params
93             self._validate_field()
94             self.version = self._tpl_version()
95             self.relationship_types = self._tpl_relationship_types()
96             self.description = self._tpl_description()
97             self.topology_template = self._topology_template()
98             if self.topology_template.tpl:
99                 self.inputs = self._inputs()
100                 self.relationship_templates = self._relationship_templates()
101                 self.nodetemplates = self._nodetemplates()
102                 self.outputs = self._outputs()
103                 self.graph = ToscaGraph(self.nodetemplates)
104
105         ExceptionCollector.stop()
106         self.verify_template()
107
108     def _topology_template(self):
109         return TopologyTemplate(self._tpl_topology_template(),
110                                 self._get_all_custom_defs(),
111                                 self.relationship_types,
112                                 self.parsed_params)
113
114     def _inputs(self):
115         return self.topology_template.inputs
116
117     def _nodetemplates(self):
118         return self.topology_template.nodetemplates
119
120     def _relationship_templates(self):
121         return self.topology_template.relationship_templates
122
123     def _outputs(self):
124         return self.topology_template.outputs
125
126     def _tpl_version(self):
127         return self.tpl.get(DEFINITION_VERSION)
128
129     def _tpl_description(self):
130         desc = self.tpl.get(DESCRIPTION)
131         if desc:
132             return desc.rstrip()
133
134     def _tpl_imports(self):
135         return self.tpl.get(IMPORTS)
136
137     def _tpl_relationship_types(self):
138         return self._get_custom_types(RELATIONSHIP_TYPES)
139
140     def _tpl_relationship_templates(self):
141         topology_template = self._tpl_topology_template()
142         return topology_template.get(RELATIONSHIP_TEMPLATES)
143
144     def _tpl_topology_template(self):
145         return self.tpl.get(TOPOLOGY_TEMPLATE)
146
147     def _get_all_custom_defs(self, imports=None):
148         types = [IMPORTS, NODE_TYPES, CAPABILITY_TYPES, RELATIONSHIP_TYPES,
149                  DATA_TYPES, POLICY_TYPES, GROUP_TYPES]
150         custom_defs_final = {}
151         custom_defs = self._get_custom_types(types, imports)
152         if custom_defs:
153             custom_defs_final.update(custom_defs)
154             if custom_defs.get(IMPORTS):
155                 import_defs = self._get_all_custom_defs(
156                     custom_defs.get(IMPORTS))
157                 custom_defs_final.update(import_defs)
158
159         # As imports are not custom_types, removing from the dict
160         custom_defs_final.pop(IMPORTS, None)
161         return custom_defs_final
162
163     def _get_custom_types(self, type_definitions, imports=None):
164         """Handle custom types defined in imported template files
165
166         This method loads the custom type definitions referenced in "imports"
167         section of the TOSCA YAML template.
168         """
169
170         custom_defs = {}
171         type_defs = []
172         if not isinstance(type_definitions, list):
173             type_defs.append(type_definitions)
174         else:
175             type_defs = type_definitions
176
177         if not imports:
178             imports = self._tpl_imports()
179
180         if imports:
181             custom_service = toscaparser.imports.\
182                 ImportsLoader(imports, self.path,
183                               type_defs, self.tpl)
184
185             nested_topo_tpls = custom_service.get_nested_topo_tpls()
186             self._handle_nested_topo_tpls(nested_topo_tpls)
187
188             custom_defs = custom_service.get_custom_defs()
189             if not custom_defs:
190                 return
191
192         # Handle custom types defined in current template file
193         for type_def in type_defs:
194             if type_def != IMPORTS:
195                 inner_custom_types = self.tpl.get(type_def) or {}
196                 if inner_custom_types:
197                     custom_defs.update(inner_custom_types)
198         return custom_defs
199
200     def _handle_nested_topo_tpls(self, nested_topo_tpls):
201         for tpl in nested_topo_tpls:
202             if tpl.get(TOPOLOGY_TEMPLATE):
203                 nested_tosca_template = ToscaTemplate(
204                     path=self.path, parsed_params=self.parsed_params,
205                     yaml_dict_tpl=nested_topo_tpls)
206                 self.nested_tosca_template.apend(nested_tosca_template)
207
208     def _validate_field(self):
209         version = self._tpl_version()
210         if not version:
211             ExceptionCollector.appendException(
212                 MissingRequiredFieldError(what='Template',
213                                           required=DEFINITION_VERSION))
214         else:
215             self._validate_version(version)
216             self.version = version
217
218         for name in self.tpl:
219             if (name not in SECTIONS and
220                name not in self.ADDITIONAL_SECTIONS.get(version, ())):
221                 ExceptionCollector.appendException(
222                     UnknownFieldError(what='Template', field=name))
223
224     def _validate_version(self, version):
225         if version not in self.VALID_TEMPLATE_VERSIONS:
226             ExceptionCollector.appendException(
227                 InvalidTemplateVersion(
228                     what=version,
229                     valid_versions=', '. join(self.VALID_TEMPLATE_VERSIONS)))
230         else:
231             if version != 'tosca_simple_yaml_1_0':
232                 update_definitions(version)
233
234     def _get_path(self, path):
235         if path.lower().endswith('.yaml'):
236             return path
237         elif path.lower().endswith(('.zip', '.csar')):
238             # a CSAR archive
239             csar = CSAR(path, self.a_file)
240             if csar.validate():
241                 csar.decompress()
242                 self.a_file = True  # the file has been decompressed locally
243                 return os.path.join(csar.temp_dir, csar.get_main_template())
244         else:
245             ExceptionCollector.appendException(
246                 ValueError(_('"%(path)s" is not a valid file.')
247                            % {'path': path}))
248
249     def verify_template(self):
250         if ExceptionCollector.exceptionsCaught():
251             if self.input_path:
252                 raise ValidationError(
253                     message=(_('\nThe input "%(path)s" failed validation with '
254                                'the following error(s): \n\n\t')
255                              % {'path': self.input_path}) +
256                     '\n\t'.join(ExceptionCollector.getExceptionsReport()))
257             else:
258                 raise ValidationError(
259                     message=_('\nThe pre-parsed input failed validation with '
260                               'the following error(s): \n\n\t') +
261                     '\n\t'.join(ExceptionCollector.getExceptionsReport()))
262         else:
263             if self.input_path:
264                 msg = (_('The input "%(path)s" successfully passed '
265                          'validation.') % {'path': self.input_path})
266             else:
267                 msg = _('The pre-parsed input successfully passed validation.')
268
269             log.info(msg)