455 lines
18 KiB
Plaintext
455 lines
18 KiB
Plaintext
|
#! /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()
|