commit: 691dc445082cc455159d699498a714d40754fedd Author: Devan Franchini <twitch153 <AT> gentoo <DOT> org> AuthorDate: Mon Jul 7 18:27:24 2014 +0000 Commit: Devan Franchini <twitch153 <AT> gentoo <DOT> org> CommitDate: Fri Aug 15 20:57:38 2014 +0000 URL: http://git.overlays.gentoo.org/gitweb/?p=proj/layman.git;a=commit;h=691dc445
Centralizes common archive overlay code This commit brings archive overlay code to one common place, in an attempt to make maintaining and adding archive overlay types to layman much easier. config.py: Alters the clean_tar option to clean_archive. tar.py: Removes doctest tests from tar.py as there is already an existing external.py test. --- layman/config.py | 2 +- layman/constants.py | 11 +++ layman/overlays/archive.py | 197 +++++++++++++++++++++++++++++++++++++++++++++ layman/overlays/tar.py | 179 ++++++---------------------------------- 4 files changed, 235 insertions(+), 154 deletions(-) diff --git a/layman/config.py b/layman/config.py index 4849d5e..91c179e 100644 --- a/layman/config.py +++ b/layman/config.py @@ -100,7 +100,7 @@ class BareConfig(object): 'check_official': 'Yes', 'conf_type': 'make.conf', 'require_repoconfig': 'Yes', - 'clean_tar': 'yes', + 'clean_archive': 'yes', 'make_conf' : '%(storage)s/make.conf', 'repos_conf': path([self.root, EPREFIX,'/etc/portage/repos.conf/layman.conf']), 'conf_module': ['make_conf', 'repos_conf'], diff --git a/layman/constants.py b/layman/constants.py index 24d1f36..c526cb6 100644 --- a/layman/constants.py +++ b/layman/constants.py @@ -66,3 +66,14 @@ SUCCEED = 0 COMPONENT_DEFAULTS = ['name', 'descriptions', 'owner', 'type', 'sources'] POSSIBLE_COMPONENTS = ['name', 'descriptions', 'homepage', 'owner', 'quality', 'priority', 'sources', 'branch', 'irc', 'feeds'] + + +############################################################################### +## +## Archive overlay possible file extensions +## +############################################################################### + +FILE_EXTENSIONS = {'Tar': ('bz2', 'gz', 'lzma', 'xz', 'Z', 'tgz', 'tbz', 'taz', + 'tlz', 'txz') + } diff --git a/layman/overlays/archive.py b/layman/overlays/archive.py new file mode 100644 index 0000000..68c8b47 --- /dev/null +++ b/layman/overlays/archive.py @@ -0,0 +1,197 @@ +#!/usr/bin/python +from __future__ import unicode_literals + +import os +import sys +import shutil +import tempfile + +import xml.etree.ElementTree as ET # Python 2.5 + +from layman.constants import MOUNT_TYPES +from layman.compatibility import fileopen +from layman.overlays.source import OverlaySource, require_supported +from layman.utils import path +from layman.version import VERSION +from sslfetch.connections import Connector + +USERAGENT = "Layman-" + VERSION + +class ArchiveOverlay(OverlaySource): + + type = 'Archive' + type_key = 'archive' + + def __init__(self, parent, config, _location, ignore = 0): + + super(ArchiveOverlay, self).__init__(parent, + config, _location, ignore) + + self.clean_archive = config['clean_archive'] + self.output = config['output'] + self.proxies = config.proxies + self.branch = self.parent.branch + self.mount_me = bool(self.type in MOUNT_TYPES) + + + def _fetch(self, base, archive_url, dest_dir): + ''' + Fetches overlay source archive. + + @params base: string of directory base for installed overlays. + @params archive_url: string of URL where archive is located. + @params dest_dir: string of destination of extracted archive. + @rtype tuple (str of package location, bool to clean_archive) + ''' + ext = self.get_extension() + + if 'file://' not in archive_url: + # set up ssl-fetch output map + connector_output = { + 'info': self.output.debug, + 'error': self.output.error, + 'kwargs-info': {'level': 2}, + 'kwargs-error': {'level': None}, + } + + fetcher = Connector(connector_output, self.proxies, USERAGENT) + + success, archive, timestamp = fetcher.fetch_content(archive_url) + + pkg = path([base, self.parent.name + ext]) + + try: + with fileopen(pkg, 'w+b') as out_file: + out_file.write(archive) + + except Exception as error: + raise Exception('Failed to store archive package in '\ + '%(pkg)s\nError was: %(error)s'\ + % ({'pkg': pkg, 'error': error})) + + else: + self.clean_archive = False + pkg = archive_url.replace('file://', '') + + return pkg + + + def _add_unchecked(self, base): + def try_to_wipe(folder): + if not os.path.exists(folder): + return + + try: + self.output.info('Deleting directory %(dir)s'\ + % ({'dir': folder}), 2) + shutil.rmtree(folder) + except Exception as error: + raise Exception('Failed to remove unneccessary archive '\ + 'structure %(dir)s\nError was: %(err)s'\ + % ({'dir': folder, 'err': error})) + + final_path = path([base, self.parent.name]) + try: + if not self.mount_me: + temp_path = tempfile.mkdtemp(dir=base) + else: + temp_path = final_path + if not os.path.exists(temp_path): + os.mkdir(temp_path) + pkg = self._fetch(base=base, archive_url=self.src, + dest_dir=temp_path) + result = self.post_fetch(pkg, temp_path) + if self.clean_archive: + os.unlink(pkg) + except Exception as error: + try_to_wipe(temp_path) + raise error + + if result == 0 and not self.mount_me: + if self.branch: + source = temp_path + os.path.sep + self.branch + else: + source = temp_path + + if os.path.exists(source): + if os.path.exists(final_path): + self.delete(base) + + try: + os.rename(source, final_path) + except Exception as error: + raise Exception('Failed to rename archive subdirectory '\ + '%(src)s to %(path)s\nError was: %(err)s'\ + % ({'src': source, 'path': final_path, 'err': error})) + os.chmod(final_path, 0o755) + else: + raise Exception('The given path (branch setting in the xml)\n'\ + ' %(src)s does not exist in this archive package!'\ + % ({'src': source})) + + if not self.mount_me: + try_to_wipe(temp_path) + + return result + + + def add(self, base): + ''' + Add overlay. + + @params base: string location where overlays are installed. + @rtype bool + ''' + + if not self.supported(): + return 1 + + target = path([base, self.parent.name]) + + if os.path.exists(target): + raise Exception('Directory %(dir)s already exists. Will not '\ + 'overwrite its contents!' % ({'dir': target})) + + return self.postsync( + self._add_unchecked(base), + cwd=target) + + + def sync(self, base): + ''' + Sync overlay. + + @params base: string location where overlays are installed. + @rtype bool + ''' + + if not self.supported(): + return 1 + + target = path([base, self.parent.name]) + + return self.postsync( + self._add_unchecked(base), + cwd=target) + + + def supported(self): + ''' + Determines if overlay type is supported. + + @rtype bool + ''' + + return self.is_supported() + + +if __name__ == '__main__': + import doctest + + # Ignore warnings here. We are just testing. + from warnings import filterwarnings, resetwarnings + filterwarnings('ignore') + + doctest.testmod(sys.modules[__name__]) + + resetwarnings() diff --git a/layman/overlays/tar.py b/layman/overlays/tar.py index 4999e20..7d7bf89 100644 --- a/layman/overlays/tar.py +++ b/layman/overlays/tar.py @@ -26,21 +26,11 @@ __version__ = "$Id: tar.py 310 2007-04-09 16:30:40Z wrobel $" # #------------------------------------------------------------------------------- -import os -import os.path import sys -import shutil -import tempfile -import xml.etree.ElementTree as ET # Python 2.5 - -from layman.compatibility import fileopen -from layman.overlays.source import OverlaySource, require_supported -from layman.utils import path -from layman.version import VERSION -from sslfetch.connections import Connector - -USERAGENT = "Layman" + VERSION +from layman.constants import FILE_EXTENSIONS +from layman.overlays.archive import ArchiveOverlay +from layman.overlays.source import require_supported #=============================================================================== # @@ -48,174 +38,57 @@ USERAGENT = "Layman" + VERSION # #------------------------------------------------------------------------------- -class TarOverlay(OverlaySource): - ''' Handles tar overlays. - - >>> from layman.output import Message - >>> import xml.etree.ElementTree as ET # Python 2.5 - >>> repo = ET.Element('repo') - >>> repo_name = ET.Element('name') - >>> repo_name.text = 'dummy' - >>> desc = ET.Element('description') - >>> desc.text = 'Dummy description' - >>> owner = ET.Element('owner') - >>> owner_email = ET.Element('email') - >>> owner_email.text = 'du...@example.org' - >>> owner[:] = [owner_email] - >>> source = ET.Element('source', type='tar') - >>> here = os.path.dirname(os.path.realpath(__file__)) - >>> source.text = 'file://' + here + '/../tests/testfiles/layman-test.tar.bz2' - >>> branch = ET.Element('branch') - >>> branch.text = 'layman-test' - >>> repo[:] = [repo_name, desc, owner, source, branch] - >>> from layman.config import BareConfig - >>> config = BareConfig() - >>> import tempfile - >>> testdir = tempfile.mkdtemp(prefix="laymantmp_") - >>> from layman.overlays.overlay import Overlay - >>> a = Overlay(config, repo) - >>> config['output'].set_colorize(False) - >>> a.add(testdir) - 0 - >>> os.listdir(testdir + '/dummy/') - ['layman-test'] - >>> sorted(os.listdir(testdir + '/dummy/layman-test/')) - ['app-admin', 'app-portage'] - >>> shutil.rmtree(testdir) - ''' +class TarOverlay(ArchiveOverlay): + '''Handles tar overlays.''' type = 'Tar' type_key = 'tar' - def __init__(self, parent, config, _location, ignore = 0): + def __init__(self, parent, config, _location, ignore=0): super(TarOverlay, self).__init__(parent, config, _location, ignore) self.output = config['output'] - self.proxies = config.proxies - self.branch = self.parent.branch - def _extract(self, base, tar_url, dest_dir): + def get_extension(self): + ''' + Determines tar file extension. + + @rtype str + ''' ext = '.tar.noidea' - clean_tar = self.config['clean_tar'] - for i in [('tar.%s' % e) for e in ('bz2', 'gz', 'lzma', 'xz', 'Z')] \ - + ['tgz', 'tbz', 'taz', 'tlz', 'txz']: + for i in [('tar.%s' % e) for e in FILE_EXTENSIONS[self.type]: candidate_ext = '.%s' % i if self.src.endswith(candidate_ext): ext = candidate_ext break - if 'file://' not in tar_url: - # setup the ssl-fetch output map - connector_output = { - 'info': self.output.debug, - 'error': self.output.error, - 'kwargs-info': {'level': 2}, - 'kwargs-error':{'level': None}, - } - - fetcher = Connector(connector_output, self.proxies, USERAGENT) + return ext - success, tar, timestamp = fetcher.fetch_content(tar_url) - pkg = path([base, self.parent.name + ext]) - - try: - with fileopen(pkg, 'w+b') as out_file: - out_file.write(tar) - - except Exception as error: - raise Exception('Failed to store tar package in ' - + pkg + '\nError was:' + str(error)) - else: - clean_tar = False - pkg = tar_url.replace('file://', '') + def post_fetch(self, pkg, dest_dir): + ''' + Extracts tar archive. + @params pkg: string location where tar archive is located. + @params dest_dir: string of destination of extracted archive. + @rtype bool + ''' # tar -v -x -f SOURCE -C TARGET args = ['-v', '-x', '-f', pkg, '-C', dest_dir] result = self.run_command(self.command(), args, cmd=self.type) - if clean_tar: - os.unlink(pkg) - return result - - def _add_unchecked(self, base): - def try_to_wipe(folder): - if not os.path.exists(folder): - return - - try: - self.output.info('Deleting directory "%s"' % folder, 2) - shutil.rmtree(folder) - except Exception as error: - raise Exception('Failed to remove unnecessary tar structure "' - + folder + '"\nError was:' + str(error)) - - final_path = path([base, self.parent.name]) - temp_path = tempfile.mkdtemp(dir=base) - try: - result = self._extract(base=base, tar_url=self.src, - dest_dir=temp_path) - except Exception as error: - try_to_wipe(temp_path) - raise error - - if result == 0: - if self.branch: - source = temp_path + '/' + self.branch - else: - source = temp_path - - if os.path.exists(source): - if os.path.exists(final_path): - self.delete(base) - - try: - os.rename(source, final_path) - except Exception as error: - raise Exception('Failed to rename tar subdirectory ' + - source + ' to ' + final_path + - '\nError was:' + str(error)) - os.chmod(final_path, 0o755) - else: - raise Exception('The given path (branch setting in the xml)\n' + \ - '"%(source)s" does not exist in the tar package!' % ({'source': source})) - - try_to_wipe(temp_path) return result - def add(self, base): - '''Add overlay.''' - - if not self.supported(): - return 1 - - target = path([base, self.parent.name]) - - if os.path.exists(target): - raise Exception('Directory ' + target + ' already exists.' +\ - ' Will not overwrite its contents!') - - return self.postsync( - self._add_unchecked(base), - cwd=target) - - def sync(self, base): - '''Sync overlay.''' - - if not self.supported(): - return 1 - - target = path([base, self.parent.name]) - return self.postsync( - self._add_unchecked(base), - cwd=target) + def is_supported(self): + ''' + Determines if overlay type is supported. - def supported(self): - '''Overlay type supported?''' + @rtype bool + ''' return require_supported( [(self.command(), 'tar', 'app-arch/tar'), ],