#! /usr/bin/python # -*- coding: utf-8 -*- # ########################################################################## # Maj-Auto - Manage automatique update of EOLE server # Copyright © 2013 Pôle de compétences EOLE # # License CeCILL: # * in french: http://www.cecill.info/licences/Licence_CeCILL_V2-fr.html # * in english http://www.cecill.info/licences/Licence_CeCILL_V2-en.html ########################################################################## import sys import argparse import atexit import time import locale from os import unlink, environ, system from subprocess import Popen, PIPE from os.path import basename, isfile from creole import reconfigure, fonctionseole from creole.client import CreoleClient, TimeoutCreoleClientError, NotFoundError, CreoleClientError from creole.error import UserExit, UserExitError from creole.eoleversion import EOLE_RELEASE, LAST_RELEASE, EOLE_VERSION from pyeole.lock import acquire, release, is_locked from pyeole.log import init_logging, set_formatter from pyeole.ihm import question_ouinon, only_root, catch_signal from pyeole.encode import normalize from pyeole.pkg import EolePkg, _configure_sources_mirror, report from pyeole.diagnose import test_tcp from pyeole import scriptargs from pyeole.i18n import i18n _ = i18n('creole') #import logging log = None only_root() try: # FIXME : refactorer le système de lock de zephir-client (ref #6660) from zephir.lib_zephir import lock, unlock zephir_libs = True except Exception: zephir_libs = False def release_lock(): if zephir_libs: unlock('maj') if is_locked('majauto', level='system'): release('majauto', level='system') def user_exit(*args, **kwargs): """ sortie utilisateur "propre" """ log.warn(_(u'! Abandoning configuration !')) log.warn(_(u'System may be in an incoherent state.\n\n')) raise UserExitError() def parse_cmdline(): """Parse commande line. """ parser = argparse.ArgumentParser(prog='Maj-Auto|Query-Auto', description=_(u"Manage EOLE server automatic update"), parents=[scriptargs.logging('info')], add_help=False) parser.add_argument('-h', '--help', action='help', help=_(u"show this help message and exit")) parser.add_argument('-n', '--dry-run', action='store_true', help=_(u"run in dry-run mode (force to True when using Query-Auto).")) parser.add_argument('-f', '--force', action='store_true', help=_(u"bypass Zephir authorizations.")) parser.add_argument('-F', '--force-update', action='store_true', help=_(u"update your server without any confirmation.")) parser.add_argument('-s', '--simulate', action='store_true', help=_(u"ask apt-get to simulate packages installation")) # Level of upgrade maj_level = parser.add_mutually_exclusive_group() maj_level.add_argument('-C', '--candidat', default=False, action='store', nargs='*', choices=['eole', 'envole'], help=_(u"use testing packages.")) maj_level.add_argument('-D', '--devel', default=False, action='store', nargs='*', choices=['eole', 'envole'], help=_(u"use development packages.")) parser.add_argument('--release', help=argparse.SUPPRESS) # Action when upgrade is OK parser.add_argument('-r', '--reconfigure', action='store_true', help=_(u"run reconfigure on successful upgrade.")) parser.add_argument('-R', '--reboot', action='store_true', help=_(u"run reconfigure on successful upgrade and reboot if necessary (implies -r).")) parser.add_argument('--download', action='store_true', help=_(u'only download packages in cache.')) # Mirror selection parser.add_argument('-S', '--eole-mirror', help=_(u"EOLE repository server.")) parser.add_argument('-U', '--ubuntu-mirror', help=_(u"Ubuntu repository server.")) parser.add_argument('-V', '--envole-mirror', help=_(u"Envole repository server.")) parser.add_argument('-c', '--cdrom', action="store_true", help=_(u"use CDROM as source.")) # sortie EAD parser.add_argument('-W', action='store_true', help=_(u"specific output for EAD.")) # mode sans creoled parser.add_argument('-i', '--ignore', action='store_true', help=_(u"ignore local configuration if creoled not responding.")) opts = parser.parse_args() if getattr(opts, 'level', None) is None: opts.level = u'updates' if opts.verbose: opts.log_level = 'info' if opts.debug: opts.log_level = 'debug' if opts.reboot: opts.reconfigure = True return opts def main(): global log opts = parse_cmdline() if opts.W: # variable set for pyeole.ansiprint environ['ModeTxt'] = 'yes' reporting = not (opts.dry_run or opts.simulate or opts.download) if not reporting: z_proc = 'QUERY-MAJ' log = init_logging(name=basename(sys.argv[0]), level=opts.log_level) pkg_log = init_logging(name='pyeole.pkg', level=opts.log_level) diag_log = init_logging(name='pyeole.diagnose', level=opts.log_level) else: z_proc = 'MAJ' report_file = '/var/lib/eole/reports/rapport-maj.log' if isfile(report_file): unlink(report_file) log = init_logging(name=basename(sys.argv[0]), level=opts.log_level, filename=report_file) pkg_log = init_logging(name='pyeole.pkg', level=opts.log_level, filename=report_file) diag_log = init_logging(name='pyeole.diagnose', level=opts.log_level, filename=report_file) set_formatter(log, u'file', u'brief') set_formatter(log, u'file', u'with-levelname-date') set_formatter(pkg_log, u'file', u'with-levelname-date') set_formatter(diag_log, u'file', u'with-levelname-date') report(2) locale.setlocale(locale.LC_TIME, "fr_FR.utf8") log.info(_(u'Update at {0}').format(time.strftime("%A %d %B %Y %H:%M:%S"))) raised_err = None error_msg = None try: # gestion du ctrl+c catch_signal(user_exit) acquire('majauto', level='system') atexit.register(release_lock) client = CreoleClient() eole_level = 'stable' envole_level = 'stable' try: version = client.get_creole('eole_release') except (TimeoutCreoleClientError, NotFoundError, CreoleClientError) as err: if opts.ignore: version = EOLE_RELEASE else: raise err if opts.candidat is not False: z_level = " en candidate" # Gestion du niveau par dépôt (16110) if len(opts.candidat) == 0: # Si on ne précise aucun dépôt tout le monde va en candidat eole_level = 'proposed' envole_level = 'proposed' else: # Sinon on vérifie dépôt par dépôt, les dépôts non précisés restent en stable if 'eole' in opts.candidat: eole_level = 'proposed' if 'envole' in opts.candidat: envole_level = 'proposed' elif opts.devel is not False: z_level = " en devel" # Gestion du niveau par dépôt (16110) if len(opts.devel) == 0: # Si on ne précise aucun dépôt tout le monde vas en candidat eole_level = 'unstable' envole_level = 'unstable' else: # Sinon on vérifie dépôt par dépôt, les dépôts non précisés restent en stable if 'eole' in opts.devel: eole_level = 'unstable' if 'envole' in opts.devel: envole_level = 'unstable' else: z_level = "" if opts.release: current_release = int(EOLE_RELEASE.split('.')[-1]) new_release = opts.release.split('.') if len(new_release) != 3 or \ u'.'.join(new_release[0:2]) != EOLE_VERSION or \ int(new_release[2]) not in range(current_release+1, int(LAST_RELEASE) + 1): raise Exception(_('Unknown release number')) z_level += " en {0}".format(opts.release) version = opts.release if opts.cdrom: z_level += " via le CDROM" #distro = 'stable' fonctionseole.zephir("INIT", "Début{0}".format(z_level), z_proc) if zephir_libs and not fonctionseole.init_proc('MAJ'): if opts.force: fonctionseole.zephir("MSG", "Mise à jour forcée par l'utilisateur", z_proc) else: log.warn(_(u"Update is locked, please contact Zéphir administrator")) log.warn(_(u"Use -f option if you want to force execution")) raise UserExitError() lock('maj') PKGMGR = EolePkg('apt', ignore=opts.ignore) if opts.dry_run: PKGMGR.set_option('APT::Get::Simulate', 'true') try: module = client.get_creole('eole_module') except (TimeoutCreoleClientError, NotFoundError, CreoleClientError) as err: if opts.ignore: module = 'module' else: raise err try: uai = client.get_creole('numero_etab') except (TimeoutCreoleClientError, NotFoundError, CreoleClientError) as err: if opts.ignore: uai = None else: raise err head = "*** {0} {1}" if uai: head += " ({2})" head += " ***\n" log.info(head.format(module, version, uai)) if not opts.force_update: raising_level = u'' if opts.release: raising_level = _(u"(CHANGE RELEASE LEVEL)") elif u'unstable' in [eole_level, envole_level]: raising_level = _(u"(UNSTABLE VERSION)") elif u'proposed' in [eole_level, envole_level]: raising_level = _(u"(TESTING VERSION)") if raising_level != u'': log.warn(_(u"{0} - Raising update level may prevent " u"lowering back to stable version.").format(raising_level)) try: assert question_ouinon(_(u"Do you wish to proceed?")) == 'oui' fonctionseole.zephir("MSG", "Mise à jour{0} forcée par l'utilisateur".format(z_level), z_proc) except (AssertionError, EOFError) as err: log.warn(_(u"Cancelling!")) raise UserExit() PKGMGR.check() #serveurs à utiliser pour les dépôts Ubuntu et EOLE _configure_sources_mirror(PKGMGR.pkgmgr, ubuntu=opts.ubuntu_mirror, eole=opts.eole_mirror, envole=opts.envole_mirror, ignore=opts.ignore, cdrom=opts.cdrom, release=version, eole_level=eole_level, envole_level=envole_level) PKGMGR.update(silent=True) upgrades = PKGMGR.get_upgradable_list() install = 0 upgrade = 0 delete = 0 for container, packages in upgrades.items(): if not packages: continue for name, isInstalled, candidateVersion in packages: if isInstalled: if candidateVersion is None: delete += 1 else: upgrade += 1 else: install += 1 total_pkg = install+upgrade headers = [] if total_pkg == 0: log.info(_(u"Update successful.")) log.info(_(u"Nothing to install.")) fonctionseole.zephir("FIN", "Aucun paquet à installer{0}".format(z_level), z_proc) if reporting: report(3) sys.exit(0) headers.append(_(u"{0} new,", u"{0} news,", install).format(install)) headers.append(_(u"{0} upgrade,", u"{0} upgrades,", upgrade).format(upgrade)) headers.append(_(u"{0} delete", u"{0} deletes", delete).format(delete)) log.info(' '.join(headers)) for line in PKGMGR.list_upgrade(upgrades=upgrades): log.info(line) if opts.dry_run: fonctionseole.zephir("FIN", "{0} paquets à mettre à jour{1}".format(total_pkg, z_level), z_proc) sys.exit(0) if opts.download: for container, packages in upgrades.items(): if not packages: continue pkgs = [] for name, isInstalled, candidateVersion in packages: pkgs.append(name) PKGMGR.fetch_archives(container=container, packages=pkgs) fonctionseole.zephir("FIN", "{0} paquets téléchargés{1}".format(total_pkg, z_level), z_proc) elif opts.simulate: PKGMGR.dist_upgrade(simulate=opts.simulate) fonctionseole.zephir("FIN", "{0} paquets mis à jour (simulation){1}".format(total_pkg, z_level), z_proc) else: PKGMGR.download_upgrade() PKGMGR.dist_upgrade(simulate=opts.simulate) log.info(_(u"Update successful.")) fonctionseole.zephir("FIN", "{0} paquets mis à jour{1}".format(total_pkg, z_level), z_proc) if opts.release: ret_code = system('/usr/share/zephir/scripts/upgrade_distrib.py --auto') if ret_code != 0: error_msg = str('erreur à la mise à jour vers la release {0}'.format(opts.release)) else: log.info(_('Upgrade post Maj-Release, please wait')) release('majauto', level='system') cmd = ['/usr/bin/Maj-Auto', '-F'] process = Popen(cmd, stdin=PIPE, stderr=PIPE, stdout=PIPE, shell=False) ret_code = process.wait() if ret_code != 0: error_msg = str(_('error in post maj release')) if opts.reconfigure: # rechargement des modules python (#7832) # cf. http://code.activestate.com/recipes/81731-reloading-all-modules/ if globals().has_key('init_modules'): for m in [x for x in sys.modules.keys() if x not in init_modules]: del(sys.modules[m]) else: init_modules = sys.modules.keys() fonctionseole.zephir("MSG", "Reconfiguration automatique", z_proc) elif not opts.release: log.warn(_(u"At least one packages has been updated," u" use command [reconfigure] to apply modifications.")) fonctionseole.zephir("MSG", "Reconfiguration du serveur à planifier", z_proc) except (UserExit, UserExitError) as err: if reporting: report(1, 'Stopped by user') fonctionseole.zephir("FIN", "Abandon par l'utilisateur", z_proc) sys.exit(1) except (TimeoutCreoleClientError, NotFoundError, CreoleClientError) as err: clue = _(". If restarting creoled service does not help, try {} command with '-i' option.") error_msg = str(err) + clue.format('Query-Auto' if opts.dry_run else 'Maj-Auto') raised_err = err except Exception as err: error_msg = str(err) raised_err = err else: if reporting: report(0, reconf=opts.reconfigure) if error_msg is not None: fonctionseole.zephir("ERR", error_msg, z_proc, console=False) if reporting: if raised_err is not None: report(1, normalize(err)) else: report(1, error_msg) if log is None: # No logger defined, error in argument parsing raise if opts.log_level == 'debug' and raised_err is not None: log.error(err, exc_info=True) else: log.error(error_msg) sys.exit(1) if opts.reconfigure: try: reconfigure.main(force_options={'auto': opts.reboot, 'log_level': opts.log_level}, force_args=[], need_lock=False) except Exception as err: fonctionseole.zephir("ERR", str(err), z_proc, console=False) if reporting: report(1, normalize(err)) sys.exit(1) if __name__ == '__main__': main()