mirror of
https://github.com/d9pouces/id_configurator.git
synced 2026-11-09 01:47:21 +01:00
cea45c97ff
Signed-off-by: d9pouces <github@19pouces.net>
367 lines
12 KiB
Python
367 lines
12 KiB
Python
from id_configurator.config import Configuration
|
|
from id_configurator.context import Context
|
|
from id_configurator.modules.apt import AptModule
|
|
|
|
KEY_CONTENT = (
|
|
"-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
|
|
"\n"
|
|
"mQENBGqh08IBCAC0\n"
|
|
"-----END PGP PUBLIC KEY BLOCK-----\n"
|
|
)
|
|
|
|
SOURCE_CONTENT = {
|
|
"uuid": "12345678-1234-5678-1234-567812345678",
|
|
"organisation": "InterDiode",
|
|
"configurations": {
|
|
"apt": {
|
|
"sources": [
|
|
{
|
|
"name": "interdiode",
|
|
"uris": ["http://example.invalid/debian"],
|
|
"suites": ["stable"],
|
|
"signed-by": KEY_CONTENT,
|
|
}
|
|
],
|
|
}
|
|
},
|
|
}
|
|
|
|
|
|
def make_config() -> Configuration:
|
|
return Configuration.model_validate(SOURCE_CONTENT)
|
|
|
|
|
|
def make_config_with(source_extra: dict, name: str = "interdiode") -> Configuration:
|
|
data = {
|
|
"uuid": "12345678-1234-5678-1234-567812345678",
|
|
"organisation": "InterDiode",
|
|
"configurations": {
|
|
"apt": {
|
|
"sources": [
|
|
{
|
|
"name": name,
|
|
"uris": ["http://example.invalid/debian"],
|
|
"suites": ["stable"],
|
|
**source_extra,
|
|
}
|
|
],
|
|
}
|
|
},
|
|
}
|
|
return Configuration.model_validate(data)
|
|
|
|
|
|
def test_is_relevant_false_without_src(tmp_path) -> None:
|
|
context = Context(config=make_config(), dst=tmp_path / "dst", src=None)
|
|
assert AptModule().is_relevant(context) is False
|
|
|
|
|
|
def test_is_relevant_false_without_apt_dir(tmp_path) -> None:
|
|
src = tmp_path / "src"
|
|
src.mkdir()
|
|
context = Context(config=make_config(), dst=tmp_path / "dst", src=src)
|
|
assert AptModule().is_relevant(context) is False
|
|
|
|
|
|
def test_is_relevant_true_with_apt_dir(tmp_path) -> None:
|
|
src = tmp_path / "src"
|
|
(src / "etc/apt").mkdir(parents=True)
|
|
context = Context(config=make_config(), dst=tmp_path / "dst", src=src)
|
|
assert AptModule().is_relevant(context) is True
|
|
|
|
|
|
def test_apply_copies_existing_sources_unchanged_without_uri_to_replace(
|
|
tmp_path,
|
|
) -> None:
|
|
src = tmp_path / "src"
|
|
apt_dir = src / "etc/apt"
|
|
sources_list_d = apt_dir / "sources.list.d"
|
|
sources_list_d.mkdir(parents=True)
|
|
|
|
(apt_dir / "sources.list").write_text(
|
|
"# already disabled\ndeb http://example.invalid/legacy stable main\n\n",
|
|
encoding="utf-8",
|
|
)
|
|
(sources_list_d / "extra.list").write_text(
|
|
"deb http://example.invalid/extra stable main\n", encoding="utf-8"
|
|
)
|
|
(sources_list_d / "docker.sources").write_text(
|
|
"Types: deb\nURIs: http://example.invalid/docker\nSuites: stable\nEnabled: yes\n"
|
|
"\n"
|
|
"Types: deb\nURIs: http://example.invalid/docker2\nSuites: stable\n"
|
|
"\n\n",
|
|
encoding="utf-8",
|
|
)
|
|
(sources_list_d / "ignored.txt").write_text("not a source file\n", encoding="utf-8")
|
|
|
|
dst = tmp_path / "dst"
|
|
context = Context(config=make_config(), dst=dst, src=src)
|
|
|
|
AptModule().apply(context)
|
|
|
|
# No configured source declares a uri_to_replace, so none of the existing
|
|
# entries are disabled: they are copied through unchanged.
|
|
top_level = (dst / "etc/apt/sources.list").read_text(encoding="utf-8")
|
|
assert (
|
|
top_level
|
|
== "# already disabled\ndeb http://example.invalid/legacy stable main\n\n"
|
|
)
|
|
|
|
extra_list = (dst / "etc/apt/sources.list.d/extra.list").read_text(encoding="utf-8")
|
|
assert extra_list == "deb http://example.invalid/extra stable main\n"
|
|
|
|
docker_sources = (dst / "etc/apt/sources.list.d/docker.sources").read_text(
|
|
encoding="utf-8"
|
|
)
|
|
assert docker_sources == (
|
|
"Types: deb\nURIs: http://example.invalid/docker\nSuites: stable\nEnabled: yes"
|
|
"\n\n"
|
|
"Types: deb\nURIs: http://example.invalid/docker2\nSuites: stable\n"
|
|
)
|
|
|
|
assert not (dst / "etc/apt/sources.list.d/ignored.txt").exists()
|
|
|
|
configured_source = (dst / "etc/apt/sources.list.d/interdiode.sources").read_text(
|
|
encoding="utf-8"
|
|
)
|
|
assert configured_source == (
|
|
"Types: deb\nURIs: http://example.invalid/debian\nSuites: stable\n"
|
|
"Signed-By:\n"
|
|
" -----BEGIN PGP PUBLIC KEY BLOCK-----\n"
|
|
" .\n"
|
|
" mQENBGqh08IBCAC0\n"
|
|
" -----END PGP PUBLIC KEY BLOCK-----\n"
|
|
"Enabled: yes\n"
|
|
)
|
|
|
|
|
|
def test_apply_disables_only_existing_sources_matching_uri_to_replace(
|
|
tmp_path,
|
|
) -> None:
|
|
src = tmp_path / "src"
|
|
apt_dir = src / "etc/apt"
|
|
sources_list_d = apt_dir / "sources.list.d"
|
|
sources_list_d.mkdir(parents=True)
|
|
|
|
(apt_dir / "sources.list").write_text(
|
|
"deb http://example.invalid/legacy stable main\n", encoding="utf-8"
|
|
)
|
|
(sources_list_d / "extra.list").write_text(
|
|
"deb http://example.invalid/extra stable main\n", encoding="utf-8"
|
|
)
|
|
(sources_list_d / "docker.sources").write_text(
|
|
"Types: deb\nURIs: http://example.invalid/extra\nSuites: stable\nEnabled: yes\n"
|
|
"\n"
|
|
"Types: deb\nURIs: http://example.invalid/docker2\nSuites: stable\n"
|
|
"\n\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
dst = tmp_path / "dst"
|
|
context = Context(
|
|
config=make_config_with({"uri-to-replace": "http://example.invalid/extra"}),
|
|
dst=dst,
|
|
src=src,
|
|
)
|
|
|
|
AptModule().apply(context)
|
|
|
|
# The legacy source doesn't match the pattern, so it stays enabled...
|
|
top_level = (dst / "etc/apt/sources.list").read_text(encoding="utf-8")
|
|
assert top_level == "deb http://example.invalid/legacy stable main\n"
|
|
|
|
# ...but every entry whose URI matches is disabled, regardless of format.
|
|
extra_list = (dst / "etc/apt/sources.list.d/extra.list").read_text(encoding="utf-8")
|
|
assert extra_list == "# deb http://example.invalid/extra stable main\n"
|
|
|
|
docker_sources = (dst / "etc/apt/sources.list.d/docker.sources").read_text(
|
|
encoding="utf-8"
|
|
)
|
|
assert docker_sources == (
|
|
"Types: deb\nURIs: http://example.invalid/extra\nSuites: stable\nEnabled: no"
|
|
"\n\n"
|
|
"Types: deb\nURIs: http://example.invalid/docker2\nSuites: stable\n"
|
|
)
|
|
|
|
|
|
def test_apply_without_existing_top_level_sources_list(tmp_path) -> None:
|
|
src = tmp_path / "src"
|
|
(src / "etc/apt").mkdir(parents=True)
|
|
dst = tmp_path / "dst"
|
|
context = Context(config=make_config(), dst=dst, src=src)
|
|
|
|
AptModule().apply(context)
|
|
|
|
assert not (dst / "etc/apt/sources.list").exists()
|
|
assert (dst / "etc/apt/sources.list.d/interdiode.sources").exists()
|
|
|
|
|
|
def test_apply_uses_signed_by_file_when_no_key_content(tmp_path) -> None:
|
|
dst = tmp_path / "dst"
|
|
context = Context(
|
|
config=make_config_with({"signed-by-file": "/etc/apt/keyrings/existing.gpg"}),
|
|
dst=dst,
|
|
src=None,
|
|
)
|
|
|
|
AptModule().apply(context)
|
|
|
|
configured_source = (dst / "etc/apt/sources.list.d/interdiode.sources").read_text(
|
|
encoding="utf-8"
|
|
)
|
|
assert configured_source == (
|
|
"Types: deb\nURIs: http://example.invalid/debian\nSuites: stable\n"
|
|
"Signed-By: /etc/apt/keyrings/existing.gpg\nEnabled: yes\n"
|
|
)
|
|
|
|
|
|
def test_apply_signed_by_takes_precedence_over_signed_by_file(tmp_path) -> None:
|
|
dst = tmp_path / "dst"
|
|
context = Context(
|
|
config=make_config_with(
|
|
{
|
|
"signed-by": KEY_CONTENT,
|
|
"signed-by-file": "/etc/apt/keyrings/existing.gpg",
|
|
}
|
|
),
|
|
dst=dst,
|
|
src=None,
|
|
)
|
|
|
|
AptModule().apply(context)
|
|
|
|
configured_source = (dst / "etc/apt/sources.list.d/interdiode.sources").read_text(
|
|
encoding="utf-8"
|
|
)
|
|
assert "Signed-By:\n -----BEGIN PGP PUBLIC KEY BLOCK-----" in configured_source
|
|
assert "existing.gpg" not in configured_source
|
|
|
|
|
|
def test_apply_without_signed_by_writes_no_field(tmp_path) -> None:
|
|
dst = tmp_path / "dst"
|
|
context = Context(config=make_config_with({}), dst=dst, src=None)
|
|
|
|
AptModule().apply(context)
|
|
|
|
configured_source = (dst / "etc/apt/sources.list.d/interdiode.sources").read_text(
|
|
encoding="utf-8"
|
|
)
|
|
assert configured_source == (
|
|
"Types: deb\nURIs: http://example.invalid/debian\nSuites: stable\nEnabled: yes\n"
|
|
)
|
|
|
|
|
|
def test_apply_skips_source_when_uri_to_replace_matches_nothing(tmp_path) -> None:
|
|
src = tmp_path / "src"
|
|
apt_dir = src / "etc/apt"
|
|
apt_dir.mkdir(parents=True)
|
|
(apt_dir / "sources.list").write_text(
|
|
"deb http://example.invalid/legacy stable main\n", encoding="utf-8"
|
|
)
|
|
|
|
dst = tmp_path / "dst"
|
|
context = Context(
|
|
config=make_config_with({"uri-to-replace": "http://other.invalid/*"}),
|
|
dst=dst,
|
|
src=src,
|
|
)
|
|
|
|
AptModule().apply(context)
|
|
|
|
assert not (dst / "etc/apt/sources.list.d/interdiode.sources").exists()
|
|
|
|
|
|
def test_apply_writes_source_when_uri_to_replace_matches_existing_uri(
|
|
tmp_path,
|
|
) -> None:
|
|
src = tmp_path / "src"
|
|
apt_dir = src / "etc/apt"
|
|
apt_dir.mkdir(parents=True)
|
|
(apt_dir / "sources.list").write_text(
|
|
"deb http://legacy.invalid/debian stable main\n", encoding="utf-8"
|
|
)
|
|
|
|
dst = tmp_path / "dst"
|
|
context = Context(
|
|
config=make_config_with({"uri-to-replace": "http://legacy.invalid/*"}),
|
|
dst=dst,
|
|
src=src,
|
|
)
|
|
|
|
AptModule().apply(context)
|
|
|
|
assert (dst / "etc/apt/sources.list.d/interdiode.sources").exists()
|
|
|
|
|
|
def test_apply_skips_source_when_uri_to_replace_set_without_src(tmp_path) -> None:
|
|
dst = tmp_path / "dst"
|
|
context = Context(
|
|
config=make_config_with({"uri-to-replace": "http://*"}), dst=dst, src=None
|
|
)
|
|
|
|
AptModule().apply(context)
|
|
|
|
assert not (dst / "etc/apt/sources.list.d/interdiode.sources").exists()
|
|
|
|
|
|
def test_apply_matches_uri_to_replace_across_list_line_variants(tmp_path) -> None:
|
|
src = tmp_path / "src"
|
|
apt_dir = src / "etc/apt"
|
|
sources_list_d = apt_dir / "sources.list.d"
|
|
sources_list_d.mkdir(parents=True)
|
|
|
|
(apt_dir / "sources.list").write_text(
|
|
"# a comment\n"
|
|
"\n"
|
|
"garbage line here\n"
|
|
"deb [unterminated bracket with no closing marker\n"
|
|
"deb [arch=amd64 trusted=yes] http://example.invalid/extra stable main\n",
|
|
encoding="utf-8",
|
|
)
|
|
(sources_list_d / "ignored.txt").write_text("not a source file\n", encoding="utf-8")
|
|
|
|
data = {
|
|
"uuid": "12345678-1234-5678-1234-567812345678",
|
|
"organisation": "InterDiode",
|
|
"configurations": {
|
|
"apt": {
|
|
"sources": [
|
|
{
|
|
"name": "first",
|
|
"uris": ["http://example.invalid/first"],
|
|
"suites": ["stable"],
|
|
"uri-to-replace": "http://example.invalid/extra",
|
|
},
|
|
{
|
|
"name": "second",
|
|
"uris": ["http://example.invalid/second"],
|
|
"suites": ["stable"],
|
|
"uri-to-replace": "http://example.invalid/extra",
|
|
},
|
|
],
|
|
}
|
|
},
|
|
}
|
|
dst = tmp_path / "dst"
|
|
context = Context(config=Configuration.model_validate(data), dst=dst, src=src)
|
|
|
|
AptModule().apply(context)
|
|
|
|
assert (dst / "etc/apt/sources.list.d/first.sources").exists()
|
|
assert (dst / "etc/apt/sources.list.d/second.sources").exists()
|
|
|
|
|
|
def test_apply_sanitizes_source_name_with_slash(tmp_path) -> None:
|
|
dst = tmp_path / "dst"
|
|
context = Context(
|
|
config=make_config_with({"signed-by": KEY_CONTENT}, name="../../evil"),
|
|
dst=dst,
|
|
src=None,
|
|
)
|
|
|
|
AptModule().apply(context)
|
|
|
|
source_file = dst / "etc/apt/sources.list.d/.._.._evil.sources"
|
|
assert source_file.exists()
|
|
assert not (dst / "etc/apt/sources.list.d/evil.sources").exists()
|