aboutsummaryrefslogtreecommitdiff
path: root/kolla/template
diff options
context:
space:
mode:
Diffstat (limited to 'kolla/template')
-rw-r--r--kolla/template/methods.py189
1 files changed, 158 insertions, 31 deletions
diff --git a/kolla/template/methods.py b/kolla/template/methods.py
index 8bb7a7fc3..fcef7ae11 100644
--- a/kolla/template/methods.py
+++ b/kolla/template/methods.py
@@ -45,6 +45,22 @@ echo 'enabled=1' >>/etc/yum.repos.d/{repo}.repo"
DNF_REPO_GPGCHECK = " && echo 'repo_gpgcheck={repo_gpgcheck}' \
>>/etc/yum.repos.d/{repo}.repo"
+BACKUP_DIR = '/tmp/kolla-repos-backup' # nosec B108
+APT_BACKUP = (
+ 'mkdir -p {backup_dir}'
+ ' && cp /etc/apt/sources.list.d/{repo}.sources'
+ ' {backup_dir}/{repo}.sources 2>/dev/null || true'
+ ' && touch {backup_dir}/{repo}.enabled'
+ ' && '
+)
+RPM_BACKUP = (
+ 'mkdir -p {backup_dir}'
+ ' && cp /etc/yum.repos.d/{file}'
+ ' {backup_dir}/{file} 2>/dev/null || true'
+ ' && touch {backup_dir}/{file}.enabled'
+ ' && '
+)
+
def debian_package_install(packages, clean_package_cache=True):
"""Jinja utility method for building debian-based package install command.
@@ -99,6 +115,41 @@ def debian_package_install(packages, clean_package_cache=True):
return ' && '.join(cmds)
+def _load_repos(repos_yaml_override=None):
+ """Load default repos.yaml and merge an optional override file."""
+ default_repofile = os.path.dirname(
+ os.path.realpath(__file__)) + '/repos.yaml'
+ with open(default_repofile, 'r') as f:
+ repo_data = yaml.safe_load(f)
+
+ if repos_yaml_override:
+ with open(repos_yaml_override, 'r') as f:
+ for section, repos in yaml.safe_load(f).items():
+ if section in repo_data:
+ repo_data[section].update(repos)
+ else:
+ repo_data[section] = repos
+
+ return repo_data
+
+
+def _build_repo_list(repo_data, base_package_type, base_distro, base_arch):
+ """Flatten repo_data sections for the current distro/arch into one dict."""
+ result = {}
+ for section in (base_package_type, base_distro,
+ '%s-%s' % (base_distro, base_arch)):
+ for repo_name, repo_info in repo_data.get(section, {}).items():
+ if repo_name in result:
+ merged = {**result[repo_name], **repo_info}
+ if any(k in merged for k in
+ ('baseurl', 'metalink', 'mirrorlist', 'url')):
+ merged.pop('distro', None)
+ result[repo_name] = merged
+ else:
+ result[repo_name] = repo_info
+ return result
+
+
@pass_context
def handle_repos(context, reponames, mode):
"""Generate Dockerfile RUN commands to enable or disable package repos.
@@ -120,19 +171,9 @@ def handle_repos(context, reponames, mode):
if not isinstance(reponames, list):
raise TypeError("First argument should be a list of repositories")
- default_repofile = os.path.dirname(
- os.path.realpath(__file__)) + '/repos.yaml'
- with open(default_repofile, 'r') as repos_file:
- repo_data = yaml.safe_load(repos_file)
- default_repo_data = {k: dict(v) for k, v in repo_data.items()}
-
- if context.get('repos_yaml'):
- with open(context.get('repos_yaml'), 'r') as repos_file:
- for section, repos in yaml.safe_load(repos_file).items():
- if section in repo_data:
- repo_data[section].update(repos)
- else:
- repo_data[section] = repos
+ repos_yaml = context.get('repos_yaml')
+ repo_data = _load_repos(repos_yaml)
+ default_repo_data = _load_repos()
base_package_type = context.get('base_package_type')
base_distro = context.get('base_distro')
@@ -141,30 +182,23 @@ def handle_repos(context, reponames, mode):
openstack_release_codename = context.get('openstack_release_codename')
commands = ''
-
- def _build_repo_list(data):
- result = {}
- for section in (base_package_type, base_distro,
- '%s-%s' % (base_distro, base_arch)):
- for repo_name, repo_info in data.get(section, {}).items():
- if repo_name in result:
- merged = {**result[repo_name], **repo_info}
- if any(k in merged for k in
- ('baseurl', 'metalink', 'mirrorlist', 'url')):
- merged.pop('distro', None)
- result[repo_name] = merged
- else:
- result[repo_name] = repo_info
- return result
+ backed_up_files = set()
try:
- repo_list = _build_repo_list(repo_data)
+ repo_list = _build_repo_list(
+ repo_data, base_package_type, base_distro, base_arch)
except KeyError:
# NOTE(hrw): Fallback to distro list
repo_list = repo_data[base_distro]
- if base_package_type == 'rpm' and context.get('repos_yaml'):
- default_repo_list = _build_repo_list(default_repo_data)
+ # NOTE: repos_yaml overrides replace a repo's whole entry (see
+ # _load_repos), so an override that doesn't restate 'file_group' would
+ # otherwise lose it. Keep the default list around to fall back to the
+ # distro's real on-disk layout for repos sharing a file.
+ default_repo_list = _build_repo_list(
+ default_repo_data, base_package_type, base_distro, base_arch)
+
+ if base_package_type == 'rpm' and repos_yaml:
distro_overridden = {r for r, d in repo_list.items()
if not d.get('distro')
and default_repo_list.get(r, {}).get('distro')}
@@ -192,6 +226,21 @@ def handle_repos(context, reponames, mode):
if base_package_type == 'rpm':
if mode == 'enable':
if not _repo.get('distro'):
+ if _repo.get('build_only'):
+ # NOTE: some distros (e.g. Rocky's baseos,
+ # appstream and crb) share a single .repo file,
+ # so back up that shared file, not one named
+ # after this repo, or the original content is
+ # lost once handle_repos rewrites the file.
+ repo_file = (
+ _repo.get('file_group')
+ or default_repo_list.get(repo, {}).get(
+ 'file_group')
+ or '{}.repo'.format(repo))
+ if repo_file not in backed_up_files:
+ commands += RPM_BACKUP.format(
+ backup_dir=BACKUP_DIR, file=repo_file)
+ backed_up_files.add(repo_file)
commands += DNF_REMOVE_EXISTING.format(
name=_repo['name'])
commands += " && "
@@ -252,6 +301,13 @@ def handle_repos(context, reponames, mode):
elif base_package_type == "deb":
if mode == "enable" and not _repo.get('distro'):
+ if _repo.get('build_only'):
+ sources_file = (
+ '/etc/apt/sources.list.d/{}.sources'.format(repo))
+ if sources_file not in backed_up_files:
+ commands += APT_BACKUP.format(
+ backup_dir=BACKUP_DIR, repo=repo)
+ backed_up_files.add(sources_file)
gpg_key = _repo['gpg_key']
signed_by = gpg_key if gpg_key.startswith('/') \
else '/etc/kolla/apt-keys/' + gpg_key
@@ -286,5 +342,76 @@ def handle_repos(context, reponames, mode):
return commands
+def get_cleanup_commands(repos_yaml, base_package_type, base_distro,
+ base_arch):
+ """Return a RUN command string restoring or removing build_only repos.
+
+ Repos marked build_only: true are backed up before being overwritten in
+ handle_repos(). This function generates the matching restore commands so
+ that mirror URLs are not baked into the final image. Repos that had no
+ prior file (no backup exists) are deleted instead of restored.
+
+ Returns an empty string when there are no build_only repos.
+ """
+ repo_data = _load_repos(repos_yaml)
+ repo_list = _build_repo_list(
+ repo_data, base_package_type, base_distro, base_arch)
+ default_repo_list = _build_repo_list(
+ _load_repos(), base_package_type, base_distro, base_arch)
+
+ cleanup_cmds = []
+ restored_file_groups = set()
+ for repo_name, repo_info in repo_list.items():
+ if not repo_info.get('build_only'):
+ continue
+ if base_package_type == 'rpm' and not repo_info.get('distro'):
+ own_file = '{}.repo'.format(repo_name)
+ file_group = (
+ repo_info.get('file_group')
+ or default_repo_list.get(repo_name, {}).get('file_group')
+ or own_file)
+ if file_group != own_file:
+ # handle_repos always writes this repo's override to
+ # own_file, regardless of where the original repo lived,
+ # so it always needs dropping on its own.
+ cleanup_cmds.append(
+ 'rm -f /etc/yum.repos.d/{}'.format(own_file))
+ if file_group in restored_file_groups:
+ continue
+ restored_file_groups.add(file_group)
+ cleanup_cmds.append(
+ '[ -f {backup_dir}/{file}.enabled ]'
+ ' && ( mv {backup_dir}/{file}'
+ ' /etc/yum.repos.d/{file} 2>/dev/null'
+ ' || rm -f /etc/yum.repos.d/{file} )'
+ ' || true'.format(
+ backup_dir=BACKUP_DIR, file=file_group))
+ elif base_package_type == 'deb' and not repo_info.get('distro'):
+ cleanup_cmds.append(
+ '[ -f {backup_dir}/{repo}.enabled ]'
+ ' && ( mv {backup_dir}/{repo}.sources'
+ ' /etc/apt/sources.list.d/{repo}.sources 2>/dev/null'
+ ' || rm -f /etc/apt/sources.list.d/{repo}.sources )'
+ ' || true'.format(
+ backup_dir=BACKUP_DIR, repo=repo_name))
+
+ if not cleanup_cmds:
+ return ''
+
+ cleanup_cmds.append('rm -rf {}'.format(BACKUP_DIR))
+ return 'RUN ' + ' \\\n && '.join(cleanup_cmds)
+
+
+@pass_context
+def cleanup_repos(context):
+ """Jinja2-callable wrapper around get_cleanup_commands()."""
+ return get_cleanup_commands(
+ context.get('repos_yaml'),
+ context.get('base_package_type'),
+ context.get('base_distro'),
+ context.get('base_arch'),
+ )
+
+
def raise_error(msg: str) -> t.NoReturn:
raise Exception(msg)