222 lines
5.9 KiB
Python
Executable file
222 lines
5.9 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
"""
|
|
Handy Debian nginx module management helper
|
|
"""
|
|
|
|
import argparse
|
|
import os
|
|
import re
|
|
import shutil
|
|
import sys
|
|
import tarfile
|
|
import tempfile
|
|
|
|
from subprocess import call
|
|
|
|
import debian.deb822 as deb822
|
|
|
|
DEBIAN_PATH = os.path.dirname(os.path.realpath(__file__))
|
|
MODULES_PATH = os.path.join(DEBIAN_PATH, 'modules')
|
|
CTRL_PATH = os.path.join(MODULES_PATH, 'control')
|
|
CTRL = [p for p in deb822.Deb822.iter_paragraphs(open(CTRL_PATH))]
|
|
|
|
|
|
def ctrl(name):
|
|
"Return deb822 paragraph for the selected module"
|
|
global CTRL
|
|
|
|
for m in CTRL:
|
|
if m['Module'] == name:
|
|
return m
|
|
|
|
|
|
def update_ctrl():
|
|
"Save current control to disk"
|
|
global CTRL
|
|
|
|
new_ctrl = CTRL_PATH + '.new'
|
|
with open(new_ctrl, 'w') as c:
|
|
for m in CTRL:
|
|
c.write(m.dump() + '\n')
|
|
|
|
os.rename(new_ctrl, CTRL_PATH)
|
|
|
|
|
|
def prompt(query):
|
|
"""
|
|
Ask the given query and wait for an y/N answer.
|
|
|
|
:query: The query
|
|
:returns: The yes (True) or no (False) answer
|
|
"""
|
|
sys.stdout.write('%s [y/N]: ' % query)
|
|
choice = input().lower()
|
|
|
|
return choice == 'y'
|
|
|
|
|
|
def commit(module, version):
|
|
"""
|
|
Git-commit the given module's upgraded source.
|
|
|
|
:param module: module name
|
|
:param version: The updated module version
|
|
"""
|
|
module_path = os.path.join(MODULES_PATH, module)
|
|
|
|
git_add_cmd = ['git', 'add', '--all', module_path, CTRL_PATH]
|
|
call(git_add_cmd)
|
|
|
|
commit_msg = '%s: Upgrade to %s' % (module, version)
|
|
git_commit_cmd = [
|
|
'git', 'commit', '-m', commit_msg,
|
|
'--', module_path, CTRL_PATH]
|
|
call(git_commit_cmd)
|
|
|
|
|
|
def unpack(module, version, exclude):
|
|
"Unpack upstream tar file into module path"
|
|
|
|
tar_gz = os.path.join(
|
|
"..", "libnginx-mod-%s-%s.tar.gz" % (module, version))
|
|
if not os.path.exists(tar_gz):
|
|
print("%s doesn't exist!" % tar_gz)
|
|
exit(1)
|
|
|
|
module_path = os.path.join(MODULES_PATH, module)
|
|
tmpdir = tempfile.mkdtemp(prefix="libnginx-mod-%s-%s." % (module, version))
|
|
with tarfile.open(tar_gz) as tar:
|
|
members = tar.getmembers()
|
|
|
|
# stip top-level directory from tar
|
|
topdir = members[0]
|
|
if not topdir or not topdir.isdir():
|
|
print("No top directory found!")
|
|
exit(1)
|
|
|
|
topdir = members[0].path
|
|
if any(topdir not in ti.path for ti in members):
|
|
print("Not all files are under the top directory '%s'" % topdir)
|
|
exit(1)
|
|
|
|
def remove_topdir_prefix(tarinfo):
|
|
"Strip top directory"
|
|
tarinfo.path = os.path.relpath(tarinfo.path, topdir)
|
|
return tarinfo
|
|
members = [remove_topdir_prefix(tarinfo) for tarinfo in members]
|
|
tar.extractall(path=tmpdir, members=members)
|
|
|
|
shutil.rmtree(module_path)
|
|
shutil.move(tmpdir, module_path)
|
|
|
|
for e in exclude:
|
|
print("Removing %s..." % e)
|
|
abspath = os.path.join(module_path, e)
|
|
if os.path.isdir(abspath):
|
|
shutil.rmtree(abspath)
|
|
else:
|
|
try:
|
|
os.remove(abspath)
|
|
except FileNotFoundError as e:
|
|
# if it not exists, ignore it
|
|
pass
|
|
|
|
|
|
def cmd_uupdate(args):
|
|
"Internal uupdate helper, upgrades & commits the given module"
|
|
c = ctrl(args.module)
|
|
|
|
exclude = []
|
|
if 'Files-Excluded' in c:
|
|
exclude = c['Files-Excluded'].split()
|
|
|
|
unpack(args.module, args.upstream_version, exclude)
|
|
|
|
# change ctrl version
|
|
c['Version'] = args.upstream_version
|
|
update_ctrl()
|
|
|
|
commit(args.module, args.upstream_version)
|
|
|
|
|
|
def cmd_uscan(args):
|
|
"""
|
|
Run uscan for each nginx module listed in debian/modules/control.
|
|
|
|
If a new version is found, it will be downloaded & unpacked to the proper
|
|
debian/modules/MODULE path.
|
|
|
|
After the upgrade, all files listed in the Files-Excluded: control field
|
|
will be removed, debian/modules/control version will be updated and all
|
|
changes will be commited to git.
|
|
"""
|
|
global CTRL
|
|
|
|
# For every module that has watchfile...
|
|
for mi in CTRL:
|
|
sys.stdout.write('Uscanning %s: ' % mi['Module'])
|
|
sys.stdout.flush()
|
|
|
|
watchfile = os.path.join(MODULES_PATH, 'watch', mi['Module'])
|
|
|
|
if not os.path.isfile(watchfile):
|
|
print('no watchfile available.')
|
|
continue
|
|
|
|
# Check
|
|
uscan_check_cmd = [
|
|
'uscan', '--upstream-version', mi['Version'],
|
|
'--watchfile', watchfile, '--package',
|
|
mi['Module']
|
|
]
|
|
if call(uscan_check_cmd) != 0:
|
|
print('up-to-date')
|
|
continue
|
|
|
|
# Ask
|
|
if not prompt('Do you want to upgrade %s?' % mi['Module']):
|
|
continue
|
|
|
|
# Upgrade
|
|
uscan_upgrade_cmd = [
|
|
'uscan', '--upstream-version', mi['Version'],
|
|
'--watchfile', watchfile, '--no-symlink'
|
|
]
|
|
if call(uscan_upgrade_cmd, stdout=open(os.devnull, 'w')) != 0:
|
|
print("Uscan for %s failed!" % mi['Module'])
|
|
exit(1)
|
|
|
|
|
|
def main():
|
|
"""ngxmod main"""
|
|
|
|
parser = argparse.ArgumentParser(
|
|
description=__doc__)
|
|
cmds = parser.add_subparsers(title="Commands")
|
|
|
|
import textwrap
|
|
uscan_cmd = cmds.add_parser(
|
|
"uscan",
|
|
help="scan/watch upstream nginx module sources for new releases",
|
|
description=textwrap.dedent(cmd_uscan.__doc__),
|
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
uscan_cmd.set_defaults(func=cmd_uscan)
|
|
|
|
uupdate_cmd = cmds.add_parser(
|
|
"uupdate",
|
|
help=cmd_uupdate.__doc__,
|
|
description=cmd_uupdate.__doc__)
|
|
uupdate_cmd.add_argument("module", metavar="MODULE", help="Module name")
|
|
uupdate_cmd.add_argument(
|
|
"--upstream-version",
|
|
required=True,
|
|
metavar="VERSION",
|
|
help="Upstream version")
|
|
uupdate_cmd.set_defaults(func=cmd_uupdate)
|
|
|
|
args = parser.parse_args()
|
|
args.func(args)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|