mirror of
https://github.com/d9pouces/id_configurator.git
synced 2026-11-09 01:47:21 +01:00
feat: add support for trusted-host in pip configuration for non-HTTPS repositories
Signed-off-by: d9pouces <github@19pouces.net>
This commit is contained in:
@@ -3,6 +3,8 @@ PythonRepository entries instead of pypi.org, and to trust the root
|
|||||||
certificates installed by the certificates module.
|
certificates installed by the certificates module.
|
||||||
|
|
||||||
- pip: system-wide `/etc/pip.conf` (`index-url`/`extra-index-url`/`cert`).
|
- pip: system-wide `/etc/pip.conf` (`index-url`/`extra-index-url`/`cert`).
|
||||||
|
Any repository whose URL isn't HTTPS has its host added to
|
||||||
|
`trusted-host`, since pip otherwise refuses a plain HTTP index.
|
||||||
- uv: system-wide `/etc/uv/uv.toml` (`[[index]]` entries, `native-tls`).
|
- uv: system-wide `/etc/uv/uv.toml` (`[[index]]` entries, `native-tls`).
|
||||||
- poetry: `/root/.config/pypoetry/config.toml` (`repositories.*`/`certificates.*`).
|
- poetry: `/root/.config/pypoetry/config.toml` (`repositories.*`/`certificates.*`).
|
||||||
Poetry has no global "replace PyPI" switch: a project's `pyproject.toml`
|
Poetry has no global "replace PyPI" switch: a project's `pyproject.toml`
|
||||||
@@ -23,6 +25,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
from id_configurator.config import PythonRepository
|
from id_configurator.config import PythonRepository
|
||||||
from id_configurator.context import Context
|
from id_configurator.context import Context
|
||||||
@@ -41,11 +44,22 @@ def _toml_string(value: str) -> str:
|
|||||||
return json.dumps(value)
|
return json.dumps(value)
|
||||||
|
|
||||||
|
|
||||||
|
def _trusted_host(repository: PythonRepository) -> str | None:
|
||||||
|
parsed = urlparse(repository.index_url)
|
||||||
|
return None if parsed.scheme.lower() == "https" else parsed.hostname
|
||||||
|
|
||||||
|
|
||||||
def _write_pip_conf(context: Context, repositories: list[PythonRepository]) -> None:
|
def _write_pip_conf(context: Context, repositories: list[PythonRepository]) -> None:
|
||||||
lines = ["[global]", f"index-url = {repositories[0].index_url}"]
|
lines = ["[global]", f"index-url = {repositories[0].index_url}"]
|
||||||
if len(repositories) > 1:
|
if len(repositories) > 1:
|
||||||
lines.append("extra-index-url =")
|
lines.append("extra-index-url =")
|
||||||
lines.extend(f" {repository.index_url}" for repository in repositories[1:])
|
lines.extend(f" {repository.index_url}" for repository in repositories[1:])
|
||||||
|
trusted_hosts = dict.fromkeys(
|
||||||
|
host for repository in repositories if (host := _trusted_host(repository))
|
||||||
|
)
|
||||||
|
if trusted_hosts:
|
||||||
|
lines.append("trusted-host =")
|
||||||
|
lines.extend(f" {host}" for host in trusted_hosts)
|
||||||
lines.append(f"cert = {_CA_BUNDLE}")
|
lines.append(f"cert = {_CA_BUNDLE}")
|
||||||
context.write_file(_PIP_CONF, ("\n".join(lines) + "\n").encode("utf-8"))
|
context.write_file(_PIP_CONF, ("\n".join(lines) + "\n").encode("utf-8"))
|
||||||
logger.info("configured pip to use %s", repositories[0].index_url)
|
logger.info("configured pip to use %s", repositories[0].index_url)
|
||||||
|
|||||||
@@ -76,6 +76,84 @@ def test_apply_single_repository(tmp_path) -> None:
|
|||||||
assert not (dst / "usr/lib").exists()
|
assert not (dst / "usr/lib").exists()
|
||||||
|
|
||||||
|
|
||||||
|
def test_apply_adds_trusted_host_for_non_https_repository(tmp_path) -> None:
|
||||||
|
data = {
|
||||||
|
"uuid": "12345678-1234-5678-1234-567812345678",
|
||||||
|
"organisation": "InterDiode",
|
||||||
|
"configurations": {
|
||||||
|
"python": {
|
||||||
|
"repositories": [
|
||||||
|
{"name": "interdiode", "index-url": "http://example.invalid/simple"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
config = Configuration.model_validate(data)
|
||||||
|
dst = tmp_path / "dst"
|
||||||
|
context = Context(config=config, dst=dst, src=None)
|
||||||
|
|
||||||
|
PythonModule().apply(context)
|
||||||
|
|
||||||
|
pip_conf = (dst / "etc/pip.conf").read_text(encoding="utf-8")
|
||||||
|
assert pip_conf == (
|
||||||
|
"[global]\n"
|
||||||
|
"index-url = http://example.invalid/simple\n"
|
||||||
|
"trusted-host =\n"
|
||||||
|
" example.invalid\n"
|
||||||
|
"cert = /etc/ssl/certs/ca-certificates.crt\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_apply_adds_trusted_host_only_for_non_https_among_mixed_repositories(
|
||||||
|
tmp_path,
|
||||||
|
) -> None:
|
||||||
|
data = {
|
||||||
|
"uuid": "12345678-1234-5678-1234-567812345678",
|
||||||
|
"organisation": "InterDiode",
|
||||||
|
"configurations": {
|
||||||
|
"python": {
|
||||||
|
"repositories": [
|
||||||
|
{
|
||||||
|
"name": "interdiode",
|
||||||
|
"index-url": "https://example.invalid/simple",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "insecure",
|
||||||
|
"index-url": "http://insecure.invalid/simple",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
config = Configuration.model_validate(data)
|
||||||
|
dst = tmp_path / "dst"
|
||||||
|
context = Context(config=config, dst=dst, src=None)
|
||||||
|
|
||||||
|
PythonModule().apply(context)
|
||||||
|
|
||||||
|
pip_conf = (dst / "etc/pip.conf").read_text(encoding="utf-8")
|
||||||
|
assert pip_conf == (
|
||||||
|
"[global]\n"
|
||||||
|
"index-url = https://example.invalid/simple\n"
|
||||||
|
"extra-index-url =\n"
|
||||||
|
" http://insecure.invalid/simple\n"
|
||||||
|
"trusted-host =\n"
|
||||||
|
" insecure.invalid\n"
|
||||||
|
"cert = /etc/ssl/certs/ca-certificates.crt\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_apply_does_not_add_trusted_host_when_all_repositories_https(tmp_path) -> None:
|
||||||
|
config = Configuration.model_validate(ONE_REPO_CONFIG)
|
||||||
|
dst = tmp_path / "dst"
|
||||||
|
context = Context(config=config, dst=dst, src=None)
|
||||||
|
|
||||||
|
PythonModule().apply(context)
|
||||||
|
|
||||||
|
pip_conf = (dst / "etc/pip.conf").read_text(encoding="utf-8")
|
||||||
|
assert "trusted-host" not in pip_conf
|
||||||
|
|
||||||
|
|
||||||
def test_apply_multiple_repositories(tmp_path) -> None:
|
def test_apply_multiple_repositories(tmp_path) -> None:
|
||||||
config = Configuration.model_validate(TWO_REPOS_CONFIG)
|
config = Configuration.model_validate(TWO_REPOS_CONFIG)
|
||||||
dst = tmp_path / "dst"
|
dst = tmp_path / "dst"
|
||||||
|
|||||||
Reference in New Issue
Block a user