b5529f075afe58cb7cac6a5e70f1d66194b2badd
2 # Licensed under the Apache License, Version 2.0 (the "License"); you may
3 # not use this file except in compliance with the License. You may obtain
4 # a copy of the License at
6 # http://www.apache.org/licenses/LICENSE-2.0
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11 # License for the specific language governing permissions and limitations
14 # Copyright 2016 RIFT.io Inc
31 from rift
.mano
.tosca_translator
.common
.utils
import _
32 from rift
.mano
.tosca_translator
.common
.utils
import ChecksumUtils
33 from rift
.mano
.tosca_translator
.rwmano
.syntax
.mano_template
import ManoTemplate
34 from rift
.mano
.tosca_translator
.rwmano
.tosca_translator
import TOSCATranslator
36 from toscaparser
.tosca_template
import ToscaTemplate
40 Test the tosca translation from command line as:
42 --template-file=<path to the YAML template or CSAR>
43 --template-type=<type of template e.g. tosca>
44 --parameters="purpose=test"
45 --output_dir=<output directory>
48 Takes following user arguments,
49 . Path to the file that needs to be translated (required)
50 . Input parameters (optional)
51 . Write to output files in a dir (optional), else print on screen
52 . Create archive or not
54 In order to use translator to only validate template,
55 without actual translation, pass --validate-only along with
56 other required arguments.
61 class ToscaShellError(Exception):
65 class ToscaEntryFileError(ToscaShellError
):
69 class ToscaNoEntryDefinitionError(ToscaShellError
):
73 class ToscaEntryFileNotFoundError(ToscaShellError
):
77 class ToscaCreateArchiveError(ToscaShellError
):
81 class TranslatorShell(object):
83 SUPPORTED_TYPES
= ['tosca']
84 COPY_DIRS
= ['images']
85 SUPPORTED_INPUTS
= (YAML
, ZIP
) = ('yaml', 'zip')
87 def __init__(self
, log
=None):
90 def main(self
, raw_args
=None):
91 args
= self
._parse
_args
(raw_args
)
95 logging
.basicConfig(level
=logging
.DEBUG
)
97 logging
.basicConfig(level
=logging
.ERROR
)
98 self
.log
= logging
.getLogger("tosca-translator")
100 self
.template_file
= args
.template_file
104 parsed_params
= self
._parse
_parameters
(args
.parameters
)
112 if args
.validate_only
:
113 a_file
= os
.path
.isfile(args
.template_file
)
114 tpl
= ToscaTemplate(self
.template_file
, parsed_params
, a_file
)
115 self
.log
.debug(_('Template = {}').format(tpl
.__dict
__))
116 msg
= (_('The input {} successfully passed ' \
117 'validation.').format(self
.template_file
))
120 self
.use_gi
= not args
.no_gi
121 tpl
= self
._translate
("tosca", parsed_params
)
123 return self
._write
_output
(tpl
, args
.output_dir
)
130 self
.template_file
= template_file
132 # Check the input file
133 path
= os
.path
.abspath(template_file
)
135 a_file
= os
.path
.isfile(path
)
137 msg
= _("The path {0} is not a valid file.").format(template_file
)
139 raise ValueError(msg
)
142 self
.ftype
= self
._get
_file
_type
()
143 self
.log
.debug(_("Input file {0} is of type {1}").
144 format(path
, self
.ftype
))
146 self
.archive
= archive
152 tpl
= self
._translate
("tosca", {})
154 return self
._write
_output
(tpl
, output_dir
)
156 def _parse_args(self
, raw_args
=None):
157 parser
= argparse
.ArgumentParser(
158 description
='RIFT TOSCA translator for descriptors')
164 help="Template file to translate")
169 help="Directory to output")
172 "-p", "--parameters",
173 help="Input parameters")
178 help="Archive the translated files")
182 help="Do not use the YANG GI to generate descriptors",
187 help="Validate template, no translation",
192 help="Enable debug logging",
196 args
= parser
.parse_args(raw_args
)
198 args
= parser
.parse_args()
201 def _parse_parameters(self
, parameter_list
):
204 # Parameters are semi-colon separated
205 inputs
= parameter_list
.replace('"', '').split(';')
206 # Each parameter should be an assignment
208 keyvalue
= param
.split('=')
209 # Validate the parameter has both a name and value
210 msg
= _("'%(param)s' is not a well-formed parameter.") % {
212 if keyvalue
.__len
__() is 2:
213 # Assure parameter name is not zero-length or whitespace
214 stripped_name
= keyvalue
[0].strip()
215 if not stripped_name
:
217 raise ValueError(msg
)
218 # Add the valid parameter to the dictionary
219 parsed_inputs
[keyvalue
[0]] = keyvalue
[1]
222 raise ValueError(msg
)
225 def get_entry_file(self
):
226 # Extract the archive and get the entry file
227 if self
.ftype
== self
.YAML
:
231 if self
.ftype
== self
.ZIP
:
232 self
.tmpdir
= tempfile
.mkdtemp()
233 prevdir
= os
.getcwd()
235 with zipfile
.ZipFile(self
.in_file
) as zf
:
236 self
.prefix
= os
.path
.commonprefix(zf
.namelist())
237 self
.log
.debug(_("Zipfile prefix is {0}").
239 zf
.extractall(self
.tmpdir
)
241 # Set the execute bits on scripts as zipfile
242 # does not restore the permissions bits
243 os
.chdir(self
.tmpdir
)
244 for fname
in zf
.namelist():
245 if (fname
.startswith('scripts/') and
246 os
.path
.isfile(fname
)):
247 # Assume this is a script file
248 # Give all permissions to owner and read+execute
249 # for group and others
251 stat
.S_IRWXU|stat
.S_IRGRP|stat
.S_IXGRP|stat
.S_IROTH|stat
.S_IXOTH
)
253 # TODO (pjoseph): Use the below code instead of extract all
254 # once unzip is installed on launchpad VMs
255 # zfile = os.path.abspath(self.in_file)
256 # os.chdir(self.tmpdir)
257 # zip_cmd = "unzip {}".format(zfile)
258 # subprocess.check_call(zip_cmd,
259 # #stdout=subprocess.PIPE,
260 # #stderr=subprocess.PIPE,
263 except Exception as e
:
264 msg
= _("Exception extracting input file {0}: {1}"). \
265 format(self
.in_file
, e
)
267 self
.log
.exception(e
)
269 shutil
.rmtree(self
.tmpdir
)
271 raise ToscaEntryFileError(msg
)
273 os
.chdir(self
.tmpdir
)
276 # Goto the TOSAC Metadata file
277 prefix_dir
= os
.path
.join(self
.tmpdir
, self
.prefix
)
278 meta_file
= os
.path
.join(prefix_dir
, 'TOSCA-Metadata',
280 self
.log
.debug(_("Checking metadata file {0}").format(meta_file
))
281 if not os
.path
.exists(meta_file
):
282 self
.log
.error(_("Not able to find metadata file in archive"))
285 # Open the metadata file and get the entry file
286 with
open(meta_file
, 'r') as f
:
289 if 'Entry-Definitions' in meta
:
290 entry_file
= os
.path
.join(prefix_dir
,
291 meta
['Entry-Definitions'])
292 if os
.path
.exists(entry_file
):
293 self
.log
.debug(_("TOSCA entry file is {0}").
298 msg
= _("Unable to get the entry file: {0}"). \
301 raise ToscaEntryFileNotFoundError(msg
)
304 msg
= _("Did not find entry definition " \
305 "in metadata: {0}").format(meta
)
307 raise ToscaNoEntryDefinitionError(msg
)
309 except Exception as e
:
310 msg
= _('Exception parsing metadata file {0}: {1}'). \
313 self
.log
.exception(e
)
314 raise ToscaEntryFileError(msg
)
319 def _translate(self
, sourcetype
, parsed_params
):
322 # Check the input file
323 path
= os
.path
.abspath(self
.template_file
)
325 a_file
= os
.path
.isfile(path
)
327 msg
= _("The path {} is not a valid file."). \
328 format(self
.template_file
)
330 raise ValueError(msg
)
333 self
.ftype
= self
._get
_file
_type
()
334 self
.log
.debug(_("Input file {0} is of type {1}").
335 format(path
, self
.ftype
))
337 if sourcetype
== "tosca":
338 entry_file
= self
.get_entry_file()
340 self
.log
.debug(_('Loading the tosca template.'))
341 tosca
= ToscaTemplate(entry_file
, parsed_params
, True)
342 self
.log
.debug(_('TOSCA Template: {}').format(tosca
.__dict
__))
343 translator
= TOSCATranslator(self
.log
, tosca
, parsed_params
,
345 self
.log
.debug(_('Translating the tosca template.'))
346 output
= translator
.translate()
349 def _copy_supporting_files(self
, output_dir
, files
):
350 # Copy supporting files, if present in archive
352 # The files are refered relative to the definitions directory
353 arc_dir
= os
.path
.join(self
.tmpdir
,
356 prevdir
= os
.getcwd()
361 fpath
= os
.path
.abspath(fname
)
364 dest
= os
.path
.join(output_dir
, 'images')
366 dest
= os
.path
.join(output_dir
, 'scripts')
368 dest
= os
.path
.join(output_dir
, 'icons')
369 elif ty
== 'cloud_init':
370 dest
= os
.path
.join(output_dir
, 'cloud_init')
372 self
.log
.warn(_("Unknown file type {0} for {1}").
376 self
.log
.debug(_("File type {0} copy from {1} to {2}").
377 format(ty
, fpath
, dest
))
378 if os
.path
.exists(fpath
):
379 # Copy the files to the appropriate dir
380 self
.log
.debug(_("Copy file(s) {0} to {1}").
382 if os
.path
.isdir(fpath
):
383 # Copy directory structure like charm dir
384 shutil
.copytree(fpath
, dest
)
387 os
.makedirs(dest
, exist_ok
=True)
388 shutil
.copy2(fpath
, dest
)
391 self
.log
.warn(_("Could not find file {0} at {1}").
392 format(fname
, fpath
))
394 except Exception as e
:
395 self
.log
.error(_("Exception copying files {0}: {1}").
397 self
.log
.exception(e
)
402 def _create_checksum_file(self
, output_dir
):
403 # Create checkum for all files
405 for root
, dirs
, files
in os
.walk(output_dir
):
406 rel_dir
= root
.replace(output_dir
, '').lstrip('/')
409 fpath
= os
.path
.join(root
, f
)
410 # TODO (pjoseph): To be fixed when we can
411 # retrieve image files from Launchpad
412 if os
.path
.getsize(fpath
) != 0:
413 flist
[os
.path
.join(rel_dir
, f
)] = \
414 ChecksumUtils
.get_md5(fpath
)
415 self
.log
.debug(_("Files in output_dir: {}").format(flist
))
417 chksumfile
= os
.path
.join(output_dir
, 'checksums.txt')
418 with
open(chksumfile
, 'w') as c
:
419 for key
in sorted(flist
.keys()):
420 c
.write("{} {}\n".format(flist
[key
], key
))
422 def _create_archive(self
, desc_id
, output_dir
):
423 """Create a tar.gz archive for the descriptor"""
424 aname
= desc_id
+ '.tar.gz'
425 apath
= os
.path
.join(output_dir
, aname
)
426 self
.log
.debug(_("Generating archive: {}").format(apath
))
428 prevdir
= os
.getcwd()
431 # Generate the archive
432 tar_cmd
= "tar zcvf {} {}".format(apath
, desc_id
)
433 self
.log
.debug(_("Generate archive: {}").format(tar_cmd
))
436 subprocess
.check_call(tar_cmd
,
437 stdout
=subprocess
.PIPE
,
438 stderr
=subprocess
.PIPE
,
442 except subprocess
.CalledProcessError
as e
:
443 msg
= _("Error creating archive with {}: {}"). \
446 raise ToscaCreateArchiveError(msg
)
451 def _write_output(self
, output
, output_dir
=None):
455 output_dir
= os
.path
.abspath(output_dir
)
458 # Do the VNFDs first and then NSDs as later when
459 # loading in launchpad, VNFDs need to be loaded first
460 for key
in [ManoTemplate
.VNFD
, ManoTemplate
.NSD
]:
461 for desc
in output
[key
]:
463 desc_id
= desc
[ManoTemplate
.ID
]
464 # Create separate directories for each descriptors
465 # Use the descriptor id to avoid name clash
466 subdir
= os
.path
.join(output_dir
, desc_id
)
469 output_file
= os
.path
.join(subdir
,
470 desc
[ManoTemplate
.NAME
]+'.yml')
471 self
.log
.debug(_("Writing file {0}").
473 with
open(output_file
, 'w+') as f
:
474 f
.write(desc
[ManoTemplate
.YANG
])
476 if ManoTemplate
.FILES
in desc
:
477 self
._copy
_supporting
_files
(subdir
,
478 desc
[ManoTemplate
.FILES
])
481 # Create checksum file
482 self
._create
_checksum
_file
(subdir
)
483 out_files
.append(self
._create
_archive
(desc_id
,
485 # Remove the desc directory
486 shutil
.rmtree(subdir
)
488 print(_("Descriptor {0}:\n{1}").
489 format(desc
[ManoTemplate
.NAME
],
490 desc
[ManoTemplate
.YANG
]))
492 if output_dir
and self
.archive
:
493 # Return the list of archive files
496 def _get_file_type(self
):
497 m
= magic
.open(magic
.MAGIC_MIME
)
499 typ
= m
.file(self
.in_file
)
500 if typ
.startswith('text/plain'):
503 elif typ
.startswith('application/zip'):
506 msg
= _("The file {0} is not a supported type: {1}"). \
507 format(self
.in_file
, typ
)
509 raise ValueError(msg
)
512 def main(args
=None, log
=None):
513 TranslatorShell(log
=log
).main(raw_args
=args
)
516 if __name__
== '__main__':