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
5 # http://www.apache.org/licenses/LICENSE-2.0
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
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
28 from keystoneauth1 import loading
30 keystone_client_avail = False
32 keystone_client_avail = True
35 import heatclient.client
37 heat_client_avail = False
39 heat_client_avail = True
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
52 Test the heat-translator translation from command line as:
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)
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.
67 conf_file = ConfigProvider.get_translator_logging_file()
68 logging.config.fileConfig(conf_file)
69 log = logging.getLogger("heat-translator")
72 class TranslatorShell(object):
74 SUPPORTED_TYPES = ['tosca']
75 TOSCA_CSAR_META_DIR = "TOSCA-Metadata"
77 def get_parser(self, argv):
78 parser = argparse.ArgumentParser(prog="heat-translator")
80 parser.add_argument('--template-file',
83 help=_('Template file to load.'))
85 parser.add_argument('--output-file',
87 help=_('Where to store the output file. If not '
88 'passed, it will be printed to stdin.'))
90 parser.add_argument('--template-type',
91 metavar='<input-template-type>',
92 choices=self.SUPPORTED_TYPES,
94 help=(_('Template type to parse. Choose between '
95 '%s.') % self.SUPPORTED_TYPES))
97 parser.add_argument('--parameters',
98 metavar='<param1=val1;param2=val2;...>',
99 help=_('Optional input parameters.'))
101 parser.add_argument('--validate-only',
104 help=_('Only validate input template, do not '
105 'perform translation.'))
107 parser.add_argument('--deploy',
110 help=_('Whether to deploy the generated template '
113 parser.add_argument('--stack-name',
114 metavar='<stack-name>',
116 help=_('The name to use for the Heat stack when '
117 'deploy the generated template.'))
119 self._append_global_identity_args(parser, argv)
123 def _append_global_identity_args(self, parser, argv):
124 if not keystone_client_avail:
127 loading.register_session_argparse_arguments(parser)
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)
135 def main(self, argv):
137 parser = self.get_parser(argv)
138 (args, args_list) = parser.parse_known_args(argv)
140 template_file = args.template_file
141 template_type = args.template_type
142 output_file = args.output_file
143 validate_only = args.validate_only
145 stack_name = args.stack_name
149 parsed_params = self._parse_parameters(args.parameters)
151 a_file = os.path.isfile(template_file)
152 a_url = UrlUtils.validate_url(template_file) if not a_file else False
155 ToscaTemplate(template_file, parsed_params, a_file)
156 msg = (_('The input "%(template_file)s" successfully passed '
157 'validation.') % {'template_file': template_file})
160 if keystone_client_avail:
163 loading.load_auth_from_argparse_arguments(args)
166 loading.load_session_from_argparse_arguments(
171 images.SESSION = keystone_session
172 flavors.SESSION = keystone_session
174 keystone_session = None
176 translator = self._get_translator(template_type,
178 parsed_params, a_file,
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'))
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,
196 self._write_output(translator, output_file)
198 msg = (_('The path %(template_file)s is not a valid '
199 'file or URL.') % {'template_file': template_file})
202 raise ValueError(msg)
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',
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}
217 tpl = yaml.safe_load(translator.translate())
219 # get all the values for get_file from a translated template
221 utils.get_dict_value(tpl, "get_file", get_files)
224 for file in get_files:
225 with codecs.open(file, encoding='utf-8', errors='strict') \
229 tpl['heat_template_version'] = str(tpl['heat_template_version'])
230 self._create_stack(heat_client=heat_client,
231 stack_name=heat_stack_name,
233 parameters=parameters,
236 def _create_stack(self, heat_client, stack_name, template, parameters,
239 heat_client.stacks.create(stack_name=stack_name,
241 parameters=parameters,
244 def _parse_parameters(self, parameter_list):
247 # Parameters are semi-colon separated
248 inputs = parameter_list.replace('"', '').split(';')
249 # Each parameter should be an assignment
251 keyvalue = param.split('=')
252 # Validate the parameter has both a name and value
253 msg = _("'%(param)s' is not a well-formed parameter.") % {
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:
260 raise ValueError(msg)
261 # Add the valid parameter to the dictionary
262 parsed_inputs[keyvalue[0]] = keyvalue[1]
265 raise ValueError(msg)
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)
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}
281 translator = TOSCATranslator(tosca, parsed_params, deploy,
283 log.debug(_('Translating the tosca template.'))
286 def _write_output(self, translator, output_file=None):
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:
294 print(translator.translate())
300 TranslatorShell().main(args)
303 if __name__ == '__main__':