mirror of
https://github.com/d9pouces/id_configurator.git
synced 2026-11-09 01:47:21 +01:00
586f091eeb
Signed-off-by: d9pouces <github@19pouces.net>
127 lines
4.5 KiB
Python
127 lines
4.5 KiB
Python
"""Configure YUM/DNF repositories and trust their GPG keys.
|
|
|
|
DNF is a drop-in successor to YUM and reads the exact same `.repo` files
|
|
under `/etc/yum.repos.d/`, so a single module covers both.
|
|
|
|
If `<src>/etc/yum.repos.d` exists, every `.repo` file already there is
|
|
copied into `<dst>` with all of its sections disabled (`enabled=0`), before
|
|
the repositories declared in the configuration are written as new, enabled
|
|
`.repo` files (so a configured repository reusing an existing file name
|
|
ends up enabled, taking precedence over the disabled copy). Each `.repo`
|
|
file is written referencing its `gpg-url` (used verbatim as `gpgkey=`,
|
|
exactly as YUM/DNF expect: `https://`, or a `file://` path already present
|
|
on the target system). Separately, each GPG key declared in
|
|
`configurations.yum.keys` is validated with `gpg` (catching a malformed key
|
|
early) and written to `/etc/pki/rpm-gpg/RPM-GPG-KEY-<name>`, the
|
|
conventional location such keys live at on RPM-based systems. `gpg` is only
|
|
used here to validate the key; the actual trust decision is left to
|
|
YUM/DNF, which import it from that file the first time the repository is
|
|
used (as `rpm --import` would).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import base64
|
|
import configparser
|
|
import logging
|
|
import shutil
|
|
import subprocess
|
|
import tempfile
|
|
from io import StringIO
|
|
|
|
from id_configurator.config import NamedContent, YumRepository
|
|
from id_configurator.context import Context
|
|
from id_configurator.modules.base import Module, register
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
_REPOS_DIR = "etc/yum.repos.d"
|
|
_RPM_GPG_DIR = "etc/pki/rpm-gpg"
|
|
|
|
|
|
def _disable_repo_content(content: str) -> str:
|
|
parser = configparser.ConfigParser(interpolation=None)
|
|
parser.optionxform = str
|
|
parser.read_string(content)
|
|
for section in parser.sections():
|
|
parser[section]["enabled"] = "0"
|
|
buffer = StringIO()
|
|
parser.write(buffer, space_around_delimiters=False)
|
|
return buffer.getvalue()
|
|
|
|
|
|
def _disable_existing_repos(context: Context) -> None:
|
|
for entry in context.iter_src_dir(_REPOS_DIR):
|
|
if entry.suffix != ".repo":
|
|
continue
|
|
disabled = _disable_repo_content(entry.read_text(encoding="utf-8"))
|
|
context.write_file(f"{_REPOS_DIR}/{entry.name}", disabled.encode("utf-8"))
|
|
logger.info("disabled existing yum/dnf repository file %s", entry.name)
|
|
|
|
|
|
def _write_repo_file(context: Context, repository: YumRepository) -> None:
|
|
lines = [
|
|
f"[{repository.name}]",
|
|
f"name={repository.name}",
|
|
f"baseurl={repository.index_url}",
|
|
"enabled=1",
|
|
]
|
|
if repository.gpg_url:
|
|
lines.append("gpgcheck=1")
|
|
lines.append(f"gpgkey={repository.gpg_url}")
|
|
else:
|
|
lines.append("gpgcheck=0")
|
|
context.write_file(
|
|
f"{_REPOS_DIR}/{repository.name}.repo",
|
|
("\n".join(lines) + "\n").encode("utf-8"),
|
|
)
|
|
logger.info("added yum/dnf repository %s", repository.name)
|
|
|
|
|
|
def _validate_gpg_key(name: str, content: bytes) -> None:
|
|
with tempfile.TemporaryDirectory() as gnupg_home:
|
|
path = shutil.which("gpg")
|
|
if path is not None:
|
|
result = subprocess.run(
|
|
[path, "--homedir", gnupg_home, "--batch", "--import"],
|
|
input=content,
|
|
capture_output=True,
|
|
check=False,
|
|
)
|
|
else:
|
|
raise RuntimeError(
|
|
"gpg is not available; the gnupg package must be installed in the environment running id-configurator"
|
|
)
|
|
if result.returncode != 0:
|
|
raise RuntimeError(
|
|
f"invalid GPG key {name}: {result.stderr.decode('utf-8', 'replace')}"
|
|
)
|
|
|
|
|
|
def _import_key(context: Context, key: NamedContent) -> None:
|
|
content = base64.b64decode(key.content)
|
|
_validate_gpg_key(key.name, content)
|
|
context.write_file(f"{_RPM_GPG_DIR}/RPM-GPG-KEY-{key.name}", content)
|
|
logger.info("imported yum/dnf GPG key %s", key.name)
|
|
|
|
|
|
@register
|
|
class YumModule(Module):
|
|
name = "yum"
|
|
|
|
def is_relevant(self, context: Context) -> bool:
|
|
yum = context.config.configurations.yum
|
|
existing_repo_files = (
|
|
entry
|
|
for entry in context.iter_src_dir(_REPOS_DIR)
|
|
if entry.suffix == ".repo"
|
|
)
|
|
return bool(yum.repositories) or bool(yum.keys) or any(existing_repo_files)
|
|
|
|
def apply(self, context: Context) -> None:
|
|
_disable_existing_repos(context)
|
|
for repository in context.config.configurations.yum.repositories:
|
|
_write_repo_file(context, repository)
|
|
for key in context.config.configurations.yum.keys:
|
|
_import_key(context, key)
|