fix the command of testr
[parser.git] / tosca2heat / heat-translator / translator / shell.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 argparse
15 import codecs
16 import logging
17 import logging.config
18 import os
19 import six
20 import sys
21 import uuid
22 import yaml
23 import zipfile
24
25 # NOTE(aloga): As per upstream developers requirement this needs to work
26 # without the clients, therefore we need to pass if we cannot import them
27 try:
28     from keystoneauth1 import loading
29 except ImportError:
30     keystone_client_avail = False
31 else:
32     keystone_client_avail = True
33
34 try:
35     import heatclient.client
36 except ImportError:
37     heat_client_avail = False
38 else:
39     heat_client_avail = True
40
41
42 from toscaparser.tosca_template import ToscaTemplate
43 from toscaparser.utils.gettextutils import _
44 from toscaparser.utils.urlutils import UrlUtils
45 from translator.common import flavors
46 from translator.common import images
47 from translator.common import utils
48 from translator.conf.config import ConfigProvider
49 from translator.hot.tosca_translator import TOSCATranslator
50
51 """
52 Test the heat-translator translation from command line as:
53 #heat-translator
54   --template-file=<path to the YAML template>
55   --template-type=<type of template e.g. tosca>
56   --parameters="purpose=test"
57 Takes three user arguments,
58 1. Path to the file that needs to be translated (required)
59 2. type of translation (e.g. tosca) (optional)
60 3. Input parameters (optional)
61
62 In order to use heat-translator to only validate template,
63 without actual translation, pass --validate-only=true along with
64 other required arguments.
65
66 """
67 conf_file = ConfigProvider.get_translator_logging_file()
68 logging.config.fileConfig(conf_file)
69 log = logging.getLogger("heat-translator")
70
71
72 class TranslatorShell(object):
73
74     SUPPORTED_TYPES = ['tosca']
75     TOSCA_CSAR_META_DIR = "TOSCA-Metadata"
76
77     def get_parser(self, argv):
78         parser = argparse.ArgumentParser(prog="heat-translator")
79
80         parser.add_argument('--template-file',
81                             metavar='<filename>',
82                             required=True,
83                             help=_('Template file to load.'))
84
85         parser.add_argument('--output-file',
86                             metavar='<filename>',
87                             help=_('Where to store the output file. If not '
88                                    'passed, it will be printed to stdin.'))
89
90         parser.add_argument('--template-type',
91                             metavar='<input-template-type>',
92                             choices=self.SUPPORTED_TYPES,
93                             default='tosca',
94                             help=(_('Template type to parse. Choose between '
95                                     '%s.') % self.SUPPORTED_TYPES))
96
97         parser.add_argument('--parameters',
98                             metavar='<param1=val1;param2=val2;...>',
99                             help=_('Optional input parameters.'))
100
101         parser.add_argument('--validate-only',
102                             action='store_true',
103                             default=False,
104                             help=_('Only validate input template, do not '
105                                    'perform translation.'))
106
107         parser.add_argument('--deploy',
108                             action='store_true',
109                             default=False,
110                             help=_('Whether to deploy the generated template '
111                                    'or not.'))
112
113         parser.add_argument('--stack-name',
114                             metavar='<stack-name>',
115                             required=False,
116                             help=_('The name to use for the Heat stack when '
117                                    'deploy the generated template.'))
118
119         self._append_global_identity_args(parser, argv)
120
121         return parser
122
123     def _append_global_identity_args(self, parser, argv):
124         if not keystone_client_avail:
125             return
126
127         loading.register_session_argparse_arguments(parser)
128
129         default_auth_plugin = 'password'
130         if 'os-token' in argv:
131             default_auth_plugin = 'token'
132         loading.register_auth_argparse_arguments(
133             parser, argv, default=default_auth_plugin)
134
135     def main(self, argv):
136
137         parser = self.get_parser(argv)
138         (args, args_list) = parser.parse_known_args(argv)
139
140         template_file = args.template_file
141         template_type = args.template_type
142         output_file = args.output_file
143         validate_only = args.validate_only
144         deploy = args.deploy
145         stack_name = args.stack_name
146
147         parsed_params = {}
148         if args.parameters:
149             parsed_params = self._parse_parameters(args.parameters)
150
151         a_file = os.path.isfile(template_file)
152         a_url = UrlUtils.validate_url(template_file) if not a_file else False
153         if a_file or a_url:
154             if validate_only:
155                 ToscaTemplate(template_file, parsed_params, a_file)
156                 msg = (_('The input "%(template_file)s" successfully passed '
157                          'validation.') % {'template_file': template_file})
158                 print(msg)
159             else:
160                 if keystone_client_avail:
161                     try:
162                         keystone_auth = (
163                             loading.load_auth_from_argparse_arguments(args)
164                         )
165                         keystone_session = (
166                             loading.load_session_from_argparse_arguments(
167                                 args,
168                                 auth=keystone_auth
169                             )
170                         )
171                         images.SESSION = keystone_session
172                         flavors.SESSION = keystone_session
173                     except Exception:
174                         keystone_session = None
175
176                 translator = self._get_translator(template_type,
177                                                   template_file,
178                                                   parsed_params, a_file,
179                                                   deploy)
180
181                 if translator and deploy:
182                     if not keystone_client_avail or not heat_client_avail:
183                         raise RuntimeError(_('Could not find Heat or Keystone'
184                                              'client to deploy, aborting '))
185                     if not keystone_session:
186                         raise RuntimeError(_('Impossible to login with '
187                                              'Keystone to deploy on Heat, '
188                                              'please check your credentials'))
189
190                     file_name = os.path.basename(
191                         os.path.splitext(template_file)[0])
192                     self.deploy_on_heat(keystone_session, keystone_auth,
193                                         translator, stack_name, file_name,
194                                         parsed_params)
195
196                 self._write_output(translator, output_file)
197         else:
198             msg = (_('The path %(template_file)s is not a valid '
199                      'file or URL.') % {'template_file': template_file})
200
201             log.error(msg)
202             raise ValueError(msg)
203
204     def deploy_on_heat(self, session, auth, translator,
205                        stack_name, file_name, parameters):
206         endpoint = auth.get_endpoint(session, service_type="orchestration")
207         heat_client = heatclient.client.Client('1',
208                                                session=session,
209                                                auth=auth,
210                                                endpoint=endpoint)
211
212         heat_stack_name = stack_name if stack_name else \
213             'heat_' + file_name + '_' + str(uuid.uuid4()).split("-")[0]
214         msg = _('Deploy the generated template, the stack name is %(name)s.')\
215             % {'name': heat_stack_name}
216         log.debug(msg)
217         tpl = yaml.safe_load(translator.translate())
218
219         # get all the values for get_file from a translated template
220         get_files = []
221         utils.get_dict_value(tpl, "get_file", get_files)
222         files = {}
223         if get_files:
224             for file in get_files:
225                 with codecs.open(file, encoding='utf-8', errors='strict') \
226                     as f:
227                         text = f.read()
228                         files[file] = text
229         tpl['heat_template_version'] = str(tpl['heat_template_version'])
230         self._create_stack(heat_client=heat_client,
231                            stack_name=heat_stack_name,
232                            template=tpl,
233                            parameters=parameters,
234                            files=files)
235
236     def _create_stack(self, heat_client, stack_name, template, parameters,
237                       files):
238         if heat_client:
239             heat_client.stacks.create(stack_name=stack_name,
240                                       template=template,
241                                       parameters=parameters,
242                                       files=files)
243
244     def _parse_parameters(self, parameter_list):
245         parsed_inputs = {}
246
247         # Parameters are semi-colon separated
248         inputs = parameter_list.replace('"', '').split(';')
249         # Each parameter should be an assignment
250         for param in inputs:
251             keyvalue = param.split('=')
252             # Validate the parameter has both a name and value
253             msg = _("'%(param)s' is not a well-formed parameter.") % {
254                 'param': param}
255             if keyvalue.__len__() is 2:
256                 # Assure parameter name is not zero-length or whitespace
257                 stripped_name = keyvalue[0].strip()
258                 if not stripped_name:
259                     log.error(msg)
260                     raise ValueError(msg)
261                 # Add the valid parameter to the dictionary
262                 parsed_inputs[keyvalue[0]] = keyvalue[1]
263             else:
264                 log.error(msg)
265                 raise ValueError(msg)
266         return parsed_inputs
267
268     def _get_translator(self, sourcetype, path, parsed_params, a_file, deploy):
269         if sourcetype == "tosca":
270             log.debug(_('Loading the tosca template.'))
271             tosca = ToscaTemplate(path, parsed_params, a_file)
272             csar_dir = None
273             if deploy and zipfile.is_zipfile(path):
274                 # set CSAR directory to the root of TOSCA-Metadata
275                 csar_decompress = utils.decompress(path)
276                 csar_dir = os.path.join(csar_decompress,
277                                         self.TOSCA_CSAR_META_DIR)
278                 msg = _("'%(csar)s' is the location of decompressed "
279                         "CSAR file.") % {'csar': csar_dir}
280                 log.info(msg)
281             translator = TOSCATranslator(tosca, parsed_params, deploy,
282                                          csar_dir=csar_dir)
283             log.debug(_('Translating the tosca template.'))
284         return translator
285
286     def _write_output(self, translator, output_file=None):
287         if output_file:
288             path, filename = os.path.split(output_file)
289             yaml_files = translator.translate_to_yaml_files_dict(filename)
290             for name, content in six.iteritems(yaml_files):
291                 with open(os.path.join(path, name), 'w+') as f:
292                     f.write(content)
293         else:
294             print(translator.translate())
295
296
297 def main(args=None):
298     if args is None:
299         args = sys.argv[1:]
300     TranslatorShell().main(args)
301
302
303 if __name__ == '__main__':
304     main()