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