Merge "Add attributes of address to tosca.nodes.nfv.CP address"
[parser.git] / tosca2heat / tosca-parser / toscaparser / imports.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 import logging
14 import os
15
16 from toscaparser.common.exception import ExceptionCollector
17 from toscaparser.common.exception import MissingRequiredFieldError
18 from toscaparser.common.exception import UnknownFieldError
19 from toscaparser.common.exception import ValidationError
20 from toscaparser.elements.tosca_type_validation import TypeValidation
21 from toscaparser.utils.gettextutils import _
22 import toscaparser.utils.urlutils
23 import toscaparser.utils.yamlparser
24
25 YAML_LOADER = toscaparser.utils.yamlparser.load_yaml
26 log = logging.getLogger("tosca")
27
28
29 class ImportsLoader(object):
30
31     IMPORTS_SECTION = (FILE, REPOSITORY, NAMESPACE_URI, NAMESPACE_PREFIX) = \
32                       ('file', 'repository', 'namespace_uri',
33                        'namespace_prefix')
34
35     def __init__(self, importslist, path, type_definition_list=None,
36                  tpl=None):
37         self.importslist = importslist
38         self.custom_defs = {}
39         if not path and not tpl:
40             msg = _('Input tosca template is not provided.')
41             log.warning(msg)
42             ExceptionCollector.appendException(ValidationError(message=msg))
43         self.path = path
44         self.repositories = {}
45         if tpl and tpl.get('repositories'):
46             self.repositories = tpl.get('repositories')
47         self.type_definition_list = []
48         if type_definition_list:
49             if isinstance(type_definition_list, list):
50                 self.type_definition_list = type_definition_list
51             else:
52                 self.type_definition_list.append(type_definition_list)
53         self._validate_and_load_imports()
54
55     def get_custom_defs(self):
56         return self.custom_defs
57
58     def _validate_and_load_imports(self):
59         imports_names = set()
60
61         if not self.importslist:
62             msg = _('"imports" keyname is defined without including '
63                     'templates.')
64             log.error(msg)
65             ExceptionCollector.appendException(ValidationError(message=msg))
66             return
67
68         for import_def in self.importslist:
69             if isinstance(import_def, dict):
70                 for import_name, import_uri in import_def.items():
71                     if import_name in imports_names:
72                         msg = (_('Duplicate import name "%s" was found.') %
73                                import_name)
74                         log.error(msg)
75                         ExceptionCollector.appendException(
76                             ValidationError(message=msg))
77                     imports_names.add(import_name)
78
79                     custom_type = self._load_import_template(import_name,
80                                                              import_uri)
81                     namespace_prefix = None
82                     if isinstance(import_uri, dict):
83                         namespace_prefix = import_uri.get(
84                             self.NAMESPACE_PREFIX)
85                     if custom_type:
86                         TypeValidation(custom_type, import_def)
87                         self._update_custom_def(custom_type, namespace_prefix)
88             else:  # old style of imports
89                 custom_type = self._load_import_template(None,
90                                                          import_def)
91                 if custom_type:
92                     TypeValidation(
93                         custom_type, import_def)
94                     self._update_custom_def(custom_type, None)
95
96     def _update_custom_def(self, custom_type, namespace_prefix):
97         outer_custom_types = {}
98         for type_def in self.type_definition_list:
99             outer_custom_types = custom_type.get(type_def)
100             if outer_custom_types:
101                 if type_def == "imports":
102                     self.custom_defs.update({'imports': outer_custom_types})
103                 else:
104                     if namespace_prefix:
105                         prefix_custom_types = {}
106                         for type_def_key in outer_custom_types.keys():
107                             namespace_prefix_to_key = (namespace_prefix +
108                                                        "." + type_def_key)
109                             prefix_custom_types[namespace_prefix_to_key] = \
110                                 outer_custom_types[type_def_key]
111                         self.custom_defs.update(prefix_custom_types)
112                     else:
113                         self.custom_defs.update(outer_custom_types)
114
115     def _validate_import_keys(self, import_name, import_uri_def):
116         if self.FILE not in import_uri_def.keys():
117             log.warning(_('Missing keyname "file" in import "%(name)s".')
118                         % {'name': import_name})
119             ExceptionCollector.appendException(
120                 MissingRequiredFieldError(
121                     what='Import of template "%s"' % import_name,
122                     required=self.FILE))
123         for key in import_uri_def.keys():
124             if key not in self.IMPORTS_SECTION:
125                 log.warning(_('Unknown keyname "%(key)s" error in '
126                               'imported definition "%(def)s".')
127                             % {'key': key, 'def': import_name})
128                 ExceptionCollector.appendException(
129                     UnknownFieldError(
130                         what='Import of template "%s"' % import_name,
131                         field=key))
132
133     def _load_import_template(self, import_name, import_uri_def):
134         """Handle custom types defined in imported template files
135
136         This method loads the custom type definitions referenced in "imports"
137         section of the TOSCA YAML template by determining whether each import
138         is specified via a file reference (by relative or absolute path) or a
139         URL reference.
140
141         Possibilities:
142         +----------+--------+------------------------------+
143         | template | import | comment                      |
144         +----------+--------+------------------------------+
145         | file     | file   | OK                           |
146         | file     | URL    | OK                           |
147         | preparsed| file   | file must be a full path     |
148         | preparsed| URL    | OK                           |
149         | URL      | file   | file must be a relative path |
150         | URL      | URL    | OK                           |
151         +----------+--------+------------------------------+
152         """
153
154         short_import_notation = False
155         if isinstance(import_uri_def, dict):
156             self._validate_import_keys(import_name, import_uri_def)
157             file_name = import_uri_def.get(self.FILE)
158             repository = import_uri_def.get(self.REPOSITORY)
159         else:
160             file_name = import_uri_def
161             repository = None
162             short_import_notation = True
163
164         if not file_name:
165             msg = (_('A template file name is not provided with import '
166                      'definition "%(import_name)s".')
167                    % {'import_name': import_name})
168             log.error(msg)
169             ExceptionCollector.appendException(ValidationError(message=msg))
170             return
171
172         if toscaparser.utils.urlutils.UrlUtils.validate_url(file_name):
173             return YAML_LOADER(file_name, False)
174         elif not repository:
175             import_template = None
176             if self.path:
177                 if toscaparser.utils.urlutils.UrlUtils.validate_url(self.path):
178                     if os.path.isabs(file_name):
179                         msg = (_('Absolute file name "%(name)s" cannot be '
180                                  'used in a URL-based input template '
181                                  '"%(template)s".')
182                                % {'name': file_name, 'template': self.path})
183                         log.error(msg)
184                         ExceptionCollector.appendException(ImportError(msg))
185                         return
186                     import_template = toscaparser.utils.urlutils.UrlUtils.\
187                         join_url(self.path, file_name)
188                     a_file = False
189                 else:
190                     a_file = True
191                     main_a_file = os.path.isfile(self.path)
192
193                     if main_a_file:
194                         if os.path.isfile(file_name):
195                             import_template = file_name
196                         else:
197                             full_path = os.path.join(
198                                 os.path.dirname(os.path.abspath(self.path)),
199                                 file_name)
200                             if os.path.isfile(full_path):
201                                 import_template = full_path
202                             else:
203                                 file_path = file_name.rpartition("/")
204                                 dir_path = os.path.dirname(os.path.abspath(
205                                     self.path))
206                                 if file_path[0] != '' and dir_path.endswith(
207                                     file_path[0]):
208                                         import_template = dir_path + "/" +\
209                                             file_path[2]
210                                         if not os.path.isfile(import_template):
211                                             msg = (_('"%(import_template)s" is'
212                                                      'not a valid file')
213                                                    % {'import_template':
214                                                       import_template})
215                                             log.error(msg)
216                                             ExceptionCollector.appendException
217                                             (ValueError(msg))
218             else:  # template is pre-parsed
219                 if os.path.isabs(file_name) and os.path.isfile(file_name):
220                     a_file = True
221                     import_template = file_name
222                 else:
223                     msg = (_('Relative file name "%(name)s" cannot be used '
224                              'in a pre-parsed input template.')
225                            % {'name': file_name})
226                     log.error(msg)
227                     ExceptionCollector.appendException(ImportError(msg))
228                     return
229
230             if not import_template:
231                 log.error(_('Import "%(name)s" is not valid.') %
232                           {'name': import_uri_def})
233                 ExceptionCollector.appendException(
234                     ImportError(_('Import "%s" is not valid.') %
235                                 import_uri_def))
236                 return
237             return YAML_LOADER(import_template, a_file)
238
239         if short_import_notation:
240             log.error(_('Import "%(name)s" is not valid.') % import_uri_def)
241             ExceptionCollector.appendException(
242                 ImportError(_('Import "%s" is not valid.') % import_uri_def))
243             return
244
245         full_url = ""
246         if repository:
247             if self.repositories:
248                 for repo_name, repo_def in self.repositories.items():
249                     if repo_name == repository:
250                         # Remove leading, ending spaces and strip
251                         # the last character if "/"
252                         repo_url = ((repo_def['url']).strip()).rstrip("//")
253                         full_url = repo_url + "/" + file_name
254
255             if not full_url:
256                 msg = (_('referenced repository "%(n_uri)s" in import '
257                          'definition "%(tpl)s" not found.')
258                        % {'n_uri': repository, 'tpl': import_name})
259                 log.error(msg)
260                 ExceptionCollector.appendException(ImportError(msg))
261                 return
262
263         if toscaparser.utils.urlutils.UrlUtils.validate_url(full_url):
264             return YAML_LOADER(full_url, False)
265         else:
266             msg = (_('repository url "%(n_uri)s" is not valid in import '
267                      'definition "%(tpl)s".')
268                    % {'n_uri': repo_url, 'tpl': import_name})
269             log.error(msg)
270             ExceptionCollector.appendException(ImportError(msg))