creole/bin/Maj-Auto

455 lines
18 KiB
Python
Executable File

#! /usr/bin/python
# -*- coding: utf-8 -*-
#
##########################################################################
# Maj-Auto - Manage automatique update of EOLE server
# Copyright © 2013 Pôle de compétences EOLE <eole@ac-dijon.fr>
#
# 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()