|
Packit Service |
751c4a |
#!/usr/bin/env python3
|
|
Packit Service |
a04d08 |
"""List pip dependencies or system package dependencies for cloud-init."""
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
# You might be tempted to rewrite this as a shell script, but you
|
|
Packit Service |
a04d08 |
# would be surprised to discover that things like 'egrep' or 'sed' may
|
|
Packit Service |
a04d08 |
# differ between Linux and *BSD.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
try:
|
|
Packit Service |
a04d08 |
from argparse import ArgumentParser
|
|
Packit Service |
a04d08 |
except ImportError:
|
|
Packit Service |
a04d08 |
raise RuntimeError(
|
|
Packit Service |
751c4a |
'Could not import argparse. Please install python3-argparse '
|
|
Packit Service |
a04d08 |
'package to continue')
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
import json
|
|
Packit Service |
a04d08 |
import os
|
|
Packit Service |
a04d08 |
import re
|
|
Packit Service |
a04d08 |
import subprocess
|
|
Packit Service |
a04d08 |
import sys
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
DEFAULT_REQUIREMENTS = 'requirements.txt'
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
# Map the appropriate package dir needed for each distro choice
|
|
Packit Service |
a04d08 |
DISTRO_PKG_TYPE_MAP = {
|
|
Packit Service |
a04d08 |
'centos': 'redhat',
|
|
Packit Service |
a04d08 |
'redhat': 'redhat',
|
|
Packit Service |
a04d08 |
'debian': 'debian',
|
|
Packit Service |
a04d08 |
'ubuntu': 'debian',
|
|
Packit Service |
a04d08 |
'opensuse': 'suse',
|
|
Packit Service |
a04d08 |
'suse': 'suse'
|
|
Packit Service |
a04d08 |
}
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
MAYBE_RELIABLE_YUM_INSTALL = [
|
|
Packit Service |
a04d08 |
'sh', '-c',
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
a04d08 |
error() { echo "$@" 1>&2; }
|
|
Packit Service |
751c4a |
configure_repos_for_proxy_use() {
|
|
Packit Service |
751c4a |
grep -q "^proxy=" /etc/yum.conf || return 0
|
|
Packit Service |
751c4a |
error ":: http proxy in use => forcing the use of fixed URLs in /etc/yum.repos.d/*.repo"
|
|
Packit Service |
751c4a |
sed -i --regexp-extended '/^#baseurl=/s/#// ; /^(mirrorlist|metalink)=/s/^/#/' /etc/yum.repos.d/*.repo
|
|
Packit Service |
751c4a |
sed -i 's/download\.fedoraproject\.org/dl.fedoraproject.org/g' /etc/yum.repos.d/*.repo
|
|
Packit Service |
751c4a |
}
|
|
Packit Service |
751c4a |
configure_repos_for_proxy_use
|
|
Packit Service |
a04d08 |
n=0; max=10;
|
|
Packit Service |
a04d08 |
bcmd="yum install --downloadonly --assumeyes --setopt=keepcache=1"
|
|
Packit Service |
a04d08 |
while n=$(($n+1)); do
|
|
Packit Service |
a04d08 |
error ":: running $bcmd $* [$n/$max]"
|
|
Packit Service |
a04d08 |
$bcmd "$@"
|
|
Packit Service |
a04d08 |
r=$?
|
|
Packit Service |
a04d08 |
[ $r -eq 0 ] && break
|
|
Packit Service |
a04d08 |
[ $n -ge $max ] && { error "gave up on $bcmd"; exit $r; }
|
|
Packit Service |
a04d08 |
nap=$(($n*5))
|
|
Packit Service |
a04d08 |
error ":: failed [$r] ($n/$max). sleeping $nap."
|
|
Packit Service |
a04d08 |
sleep $nap
|
|
Packit Service |
a04d08 |
done
|
|
Packit Service |
a04d08 |
error ":: running yum install --cacheonly --assumeyes $*"
|
|
Packit Service |
a04d08 |
yum install --cacheonly --assumeyes "$@"
|
|
Packit Service |
751c4a |
configure_repos_for_proxy_use
|
|
Packit Service |
a04d08 |
""",
|
|
Packit Service |
a04d08 |
'reliable-yum-install']
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
ZYPPER_INSTALL = [
|
|
Packit Service |
a04d08 |
'zypper', '--non-interactive', '--gpg-auto-import-keys', 'install',
|
|
Packit Service |
a04d08 |
'--auto-agree-with-licenses']
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
DRY_DISTRO_INSTALL_PKG_CMD = {
|
|
Packit Service |
a04d08 |
'centos': ['yum', 'install', '--assumeyes'],
|
|
Packit Service |
a04d08 |
'redhat': ['yum', 'install', '--assumeyes'],
|
|
Packit Service |
a04d08 |
}
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
DISTRO_INSTALL_PKG_CMD = {
|
|
Packit Service |
a04d08 |
'centos': MAYBE_RELIABLE_YUM_INSTALL,
|
|
Packit Service |
a04d08 |
'redhat': MAYBE_RELIABLE_YUM_INSTALL,
|
|
Packit Service |
a04d08 |
'debian': ['apt', 'install', '-y'],
|
|
Packit Service |
a04d08 |
'ubuntu': ['apt', 'install', '-y'],
|
|
Packit Service |
a04d08 |
'opensuse': ZYPPER_INSTALL,
|
|
Packit Service |
a04d08 |
'suse': ZYPPER_INSTALL,
|
|
Packit Service |
a04d08 |
}
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
# List of base system packages required to enable ci automation
|
|
Packit Service |
a04d08 |
CI_SYSTEM_BASE_PKGS = {
|
|
Packit Service |
a04d08 |
'common': ['make', 'sudo', 'tar'],
|
|
Packit Service |
751c4a |
'redhat': ['python3-tox'],
|
|
Packit Service |
751c4a |
'centos': ['python3-tox'],
|
|
Packit Service |
a04d08 |
'ubuntu': ['devscripts', 'python3-dev', 'libssl-dev', 'tox', 'sbuild'],
|
|
Packit Service |
a04d08 |
'debian': ['devscripts', 'python3-dev', 'libssl-dev', 'tox', 'sbuild']}
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
# JSON definition of distro-specific package dependencies
|
|
Packit Service |
a04d08 |
DISTRO_PKG_DEPS_PATH = "packages/pkg-deps.json"
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def get_parser():
|
|
Packit Service |
a04d08 |
"""Return an argument parser for this command."""
|
|
Packit Service |
a04d08 |
parser = ArgumentParser(description=__doc__)
|
|
Packit Service |
a04d08 |
parser.add_argument(
|
|
Packit Service |
a04d08 |
'-r', '--requirements-file', type=str, dest='req_files',
|
|
Packit Service |
a04d08 |
action='append', default=None,
|
|
Packit Service |
a04d08 |
help='pip-style requirements file [default=%s]' % DEFAULT_REQUIREMENTS)
|
|
Packit Service |
a04d08 |
parser.add_argument(
|
|
Packit Service |
a04d08 |
'-d', '--distro', type=str, choices=DISTRO_PKG_TYPE_MAP.keys(),
|
|
Packit Service |
a04d08 |
help='The name of the distro to generate package deps for.')
|
|
Packit Service |
751c4a |
deptype = parser.add_mutually_exclusive_group()
|
|
Packit Service |
751c4a |
deptype.add_argument(
|
|
Packit Service |
751c4a |
'-R', '--runtime-requires', action='store_true', default=False,
|
|
Packit Service |
751c4a |
dest='runtime_requires',
|
|
Packit Service |
751c4a |
help='Print only runtime required packages')
|
|
Packit Service |
751c4a |
deptype.add_argument(
|
|
Packit Service |
751c4a |
'-b', '--build-requires', action='store_true', default=False,
|
|
Packit Service |
751c4a |
dest='build_requires', help='Print only buildtime required packages')
|
|
Packit Service |
a04d08 |
parser.add_argument(
|
|
Packit Service |
a04d08 |
'--dry-run', action='store_true', default=False, dest='dry_run',
|
|
Packit Service |
a04d08 |
help='Dry run the install, making no package changes.')
|
|
Packit Service |
a04d08 |
parser.add_argument(
|
|
Packit Service |
a04d08 |
'-s', '--system-pkg-names', action='store_true', default=False,
|
|
Packit Service |
a04d08 |
dest='system_pkg_names',
|
|
Packit Service |
751c4a |
help='Generate distribution package names (python3-pkgname).')
|
|
Packit Service |
a04d08 |
parser.add_argument(
|
|
Packit Service |
a04d08 |
'-i', '--install', action='store_true', default=False,
|
|
Packit Service |
a04d08 |
dest='install',
|
|
Packit Service |
a04d08 |
help='When specified, install the required system packages.')
|
|
Packit Service |
a04d08 |
parser.add_argument(
|
|
Packit Service |
a04d08 |
'-t', '--test-distro', action='store_true', default=False,
|
|
Packit Service |
a04d08 |
dest='test_distro',
|
|
Packit Service |
a04d08 |
help='Additionally install continuous integration system packages '
|
|
Packit Service |
a04d08 |
'required for build and test automation.')
|
|
Packit Service |
a04d08 |
return parser
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def get_package_deps_from_json(topdir, distro):
|
|
Packit Service |
a04d08 |
"""Get a dict of build and runtime package requirements for a distro.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@param topdir: The root directory in which to search for the
|
|
Packit Service |
a04d08 |
DISTRO_PKG_DEPS_PATH json blob of package requirements information.
|
|
Packit Service |
a04d08 |
@param distro: The specific distribution shortname to pull dependencies
|
|
Packit Service |
a04d08 |
for.
|
|
Packit Service |
a04d08 |
@return: Dict containing "requires", "build-requires" and "rename" lists
|
|
Packit Service |
a04d08 |
for a given distribution.
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
a04d08 |
with open(os.path.join(topdir, DISTRO_PKG_DEPS_PATH), 'r') as stream:
|
|
Packit Service |
a04d08 |
deps = json.loads(stream.read())
|
|
Packit Service |
a04d08 |
if distro is None:
|
|
Packit Service |
a04d08 |
return {}
|
|
Packit Service |
751c4a |
if deps.get(distro): # If we have a specific distro defined, use it.
|
|
Packit Service |
751c4a |
return deps[distro]
|
|
Packit Service |
751c4a |
# Use generic distro dependency map via DISTRO_PKG_TYPE_MAP
|
|
Packit Service |
a04d08 |
return deps[DISTRO_PKG_TYPE_MAP[distro]]
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def parse_pip_requirements(requirements_path):
|
|
Packit Service |
a04d08 |
"""Return the pip requirement names from pip-style requirements_path."""
|
|
Packit Service |
a04d08 |
dep_names = []
|
|
Packit Service |
a04d08 |
with open(requirements_path, "r") as fp:
|
|
Packit Service |
a04d08 |
for line in fp:
|
|
Packit Service |
a04d08 |
line = line.strip()
|
|
Packit Service |
a04d08 |
if not line or line.startswith("#"):
|
|
Packit Service |
a04d08 |
continue
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
# remove pip-style markers
|
|
Packit Service |
a04d08 |
dep = line.split(';')[0]
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
# remove version requirements
|
|
Packit Service |
a04d08 |
if re.search('[>=.<]+', dep):
|
|
Packit Service |
a04d08 |
dep_names.append(re.split(r'[>=.<]+', dep)[0].strip())
|
|
Packit Service |
a04d08 |
else:
|
|
Packit Service |
a04d08 |
dep_names.append(dep)
|
|
Packit Service |
a04d08 |
return dep_names
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
751c4a |
def translate_pip_to_system_pkg(pip_requires, renames):
|
|
Packit Service |
a04d08 |
"""Translate pip package names to distro-specific package names.
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
@param pip_requires: List of versionless pip package names to translate.
|
|
Packit Service |
a04d08 |
@param renames: Dict containg special case renames from pip name to system
|
|
Packit Service |
a04d08 |
package name for the distro.
|
|
Packit Service |
a04d08 |
"""
|
|
Packit Service |
751c4a |
prefix = "python3-"
|
|
Packit Service |
a04d08 |
standard_pkg_name = "{0}{1}"
|
|
Packit Service |
a04d08 |
translated_names = []
|
|
Packit Service |
a04d08 |
for pip_name in pip_requires:
|
|
Packit Service |
a04d08 |
pip_name = pip_name.lower()
|
|
Packit Service |
a04d08 |
# Find a rename if present for the distro package and python version
|
|
Packit Service |
751c4a |
rename = renames.get(pip_name, "")
|
|
Packit Service |
a04d08 |
if rename:
|
|
Packit Service |
a04d08 |
translated_names.append(rename)
|
|
Packit Service |
a04d08 |
else:
|
|
Packit Service |
a04d08 |
translated_names.append(
|
|
Packit Service |
a04d08 |
standard_pkg_name.format(prefix, pip_name))
|
|
Packit Service |
a04d08 |
return translated_names
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def main(distro):
|
|
Packit Service |
a04d08 |
parser = get_parser()
|
|
Packit Service |
a04d08 |
args = parser.parse_args()
|
|
Packit Service |
a04d08 |
if 'CLOUD_INIT_TOP_D' in os.environ:
|
|
Packit Service |
a04d08 |
topd = os.path.realpath(os.environ.get('CLOUD_INIT_TOP_D'))
|
|
Packit Service |
a04d08 |
else:
|
|
Packit Service |
a04d08 |
topd = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
if args.test_distro:
|
|
Packit Service |
a04d08 |
# Give us all the system deps we need for continuous integration
|
|
Packit Service |
a04d08 |
if args.req_files:
|
|
Packit Service |
a04d08 |
sys.stderr.write(
|
|
Packit Service |
a04d08 |
"Parameter --test-distro overrides --requirements-file. Use "
|
|
Packit Service |
a04d08 |
"one or the other.\n")
|
|
Packit Service |
a04d08 |
sys.exit(1)
|
|
Packit Service |
a04d08 |
args.req_files = [os.path.join(topd, DEFAULT_REQUIREMENTS),
|
|
Packit Service |
a04d08 |
os.path.join(topd, 'test-' + DEFAULT_REQUIREMENTS)]
|
|
Packit Service |
a04d08 |
args.install = True
|
|
Packit Service |
a04d08 |
if args.req_files is None:
|
|
Packit Service |
a04d08 |
args.req_files = [os.path.join(topd, DEFAULT_REQUIREMENTS)]
|
|
Packit Service |
a04d08 |
if not os.path.isfile(args.req_files[0]):
|
|
Packit Service |
a04d08 |
sys.stderr.write("Unable to locate '%s' file that should "
|
|
Packit Service |
a04d08 |
"exist in cloud-init root directory." %
|
|
Packit Service |
a04d08 |
args.req_files[0])
|
|
Packit Service |
a04d08 |
sys.exit(1)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
bad_files = [r for r in args.req_files if not os.path.isfile(r)]
|
|
Packit Service |
a04d08 |
if bad_files:
|
|
Packit Service |
a04d08 |
sys.stderr.write(
|
|
Packit Service |
a04d08 |
"Unable to find requirements files: %s\n" % ','.join(bad_files))
|
|
Packit Service |
a04d08 |
sys.exit(1)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
pip_pkg_names = set()
|
|
Packit Service |
a04d08 |
for req_path in args.req_files:
|
|
Packit Service |
a04d08 |
pip_pkg_names.update(set(parse_pip_requirements(req_path)))
|
|
Packit Service |
a04d08 |
deps_from_json = get_package_deps_from_json(topd, args.distro)
|
|
Packit Service |
a04d08 |
renames = deps_from_json.get('renames', {})
|
|
Packit Service |
a04d08 |
translated_pip_names = translate_pip_to_system_pkg(
|
|
Packit Service |
751c4a |
pip_pkg_names, renames)
|
|
Packit Service |
a04d08 |
all_deps = []
|
|
Packit Service |
751c4a |
select_requires = [args.build_requires, args.runtime_requires]
|
|
Packit Service |
a04d08 |
if args.distro:
|
|
Packit Service |
751c4a |
if not any(select_requires):
|
|
Packit Service |
751c4a |
all_deps.extend(
|
|
Packit Service |
751c4a |
translated_pip_names + deps_from_json['requires'] +
|
|
Packit Service |
751c4a |
deps_from_json['build-requires'])
|
|
Packit Service |
751c4a |
else:
|
|
Packit Service |
751c4a |
if args.build_requires:
|
|
Packit Service |
751c4a |
all_deps.extend(deps_from_json['build-requires'])
|
|
Packit Service |
751c4a |
else:
|
|
Packit Service |
751c4a |
all_deps.extend(
|
|
Packit Service |
751c4a |
translated_pip_names + deps_from_json['requires'])
|
|
Packit Service |
a04d08 |
else:
|
|
Packit Service |
a04d08 |
if args.system_pkg_names:
|
|
Packit Service |
a04d08 |
all_deps = translated_pip_names
|
|
Packit Service |
a04d08 |
else:
|
|
Packit Service |
a04d08 |
all_deps = pip_pkg_names
|
|
Packit Service |
751c4a |
all_deps = sorted(all_deps)
|
|
Packit Service |
a04d08 |
if args.install:
|
|
Packit Service |
a04d08 |
pkg_install(all_deps, args.distro, args.test_distro, args.dry_run)
|
|
Packit Service |
a04d08 |
else:
|
|
Packit Service |
a04d08 |
print('\n'.join(all_deps))
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
def pkg_install(pkg_list, distro, test_distro=False, dry_run=False):
|
|
Packit Service |
a04d08 |
"""Install a list of packages using the DISTRO_INSTALL_PKG_CMD."""
|
|
Packit Service |
a04d08 |
if test_distro:
|
|
Packit Service |
a04d08 |
pkg_list = list(pkg_list) + CI_SYSTEM_BASE_PKGS['common']
|
|
Packit Service |
a04d08 |
distro_base_pkgs = CI_SYSTEM_BASE_PKGS.get(distro, [])
|
|
Packit Service |
a04d08 |
pkg_list += distro_base_pkgs
|
|
Packit Service |
a04d08 |
print('Installing deps: {0}{1}'.format(
|
|
Packit Service |
a04d08 |
'(dryrun)' if dry_run else '', ' '.join(pkg_list)))
|
|
Packit Service |
a04d08 |
install_cmd = []
|
|
Packit Service |
a04d08 |
if dry_run:
|
|
Packit Service |
a04d08 |
install_cmd.append('echo')
|
|
Packit Service |
a04d08 |
if os.geteuid() != 0:
|
|
Packit Service |
a04d08 |
install_cmd.append('sudo')
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
cmd = DISTRO_INSTALL_PKG_CMD[distro]
|
|
Packit Service |
a04d08 |
if dry_run and distro in DRY_DISTRO_INSTALL_PKG_CMD:
|
|
Packit Service |
a04d08 |
cmd = DRY_DISTRO_INSTALL_PKG_CMD[distro]
|
|
Packit Service |
a04d08 |
install_cmd.extend(cmd)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
if distro in ['centos', 'redhat']:
|
|
Packit Service |
a04d08 |
# CentOS and Redhat need epel-release to access oauthlib and jsonschema
|
|
Packit Service |
a04d08 |
subprocess.check_call(install_cmd + ['epel-release'])
|
|
Packit Service |
a04d08 |
if distro in ['suse', 'opensuse', 'redhat', 'centos']:
|
|
Packit Service |
a04d08 |
pkg_list.append('rpm-build')
|
|
Packit Service |
a04d08 |
subprocess.check_call(install_cmd + pkg_list)
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
if __name__ == "__main__":
|
|
Packit Service |
a04d08 |
parser = get_parser()
|
|
Packit Service |
a04d08 |
args = parser.parse_args()
|
|
Packit Service |
a04d08 |
sys.exit(main(args.distro))
|
|
Packit Service |
a04d08 |
|
|
Packit Service |
a04d08 |
# vi: ts=4 expandtab
|