Compare commits

..

13 Commits

Author SHA1 Message Date
dependabot[bot] 3c00b2e4b0 chore(deps): bump certifi from 2026.7.22 to 2026.10.19
Bumps [certifi](https://github.com/certifi/python-certifi) from 2026.7.22 to 2026.10.19.
- [Commits](https://github.com/certifi/python-certifi/compare/2026.07.22...2026.10.19)

---
updated-dependencies:
- dependency-name: certifi
  dependency-version: 2026.10.19
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-21 01:14:42 +00:00
d9pouces 7dbc182de4 Merge pull request #14 from d9pouces/dev
Dev
2026-10-09 20:46:39 +02:00
d9pouces 0f019ac6ea feat: update repository names to support special characters in configuration files
Signed-off-by: d9pouces <github@19pouces.net>
2026-10-09 20:22:26 +02:00
d9pouces e58820ac98 feat: update repository names to support special characters in configuration files
Signed-off-by: d9pouces <github@19pouces.net>
2026-10-09 19:13:12 +02:00
d9pouces b5aec64fd8 Merge pull request #13 from d9pouces/dev
Dev
2026-10-07 08:40:55 +02:00
d9pouces ddfa9f75b1 Merge pull request #12 from d9pouces/dev
Dev
2026-10-07 07:44:10 +02:00
d9pouces 80f9bf3a83 Merge pull request #11 from d9pouces/dev
Dev
2026-10-05 22:53:48 +02:00
d9pouces 3e0bb3b04c Merge pull request #10 from d9pouces/dev
Dev
2026-10-04 15:09:07 +02:00
d9pouces 12f0ac46a6 Merge pull request #9 from d9pouces/dev
fix: add missing GPG packages to the Docker image
2026-10-03 11:26:09 +02:00
d9pouces e6a7d4d4d9 Merge pull request #8 from d9pouces/dev
Dev
2026-10-02 23:31:24 +02:00
d9pouces 35ac0c5033 Merge pull request #7 from d9pouces/dev
Dev
2026-09-22 19:38:14 +02:00
d9pouces 235df16f6f Merge pull request #2 from d9pouces/dev
Dev
2026-09-20 22:50:10 +02:00
d9pouces c9dbeb1a8b Merge pull request #1 from d9pouces/dev
Dev
2026-09-06 15:29:29 +02:00
14 changed files with 848 additions and 805 deletions
+4 -3
View File
@@ -62,7 +62,7 @@ from cryptography.hazmat.primitives.serialization import (
from id_configurator.config import JavaRepository, NamedContent from id_configurator.config import JavaRepository, NamedContent
from id_configurator.context import Context from id_configurator.context import Context
from id_configurator.modules.base import Module, register from id_configurator.modules.base import Module, register, sanitize_repository_name
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -170,17 +170,18 @@ def _build_maven_settings(repositories: list[JavaRepository]) -> ET.Element:
repositories_el = ET.SubElement(profile, "repositories") repositories_el = ET.SubElement(profile, "repositories")
plugin_repositories_el = ET.SubElement(profile, "pluginRepositories") plugin_repositories_el = ET.SubElement(profile, "pluginRepositories")
for repository in repositories: for repository in repositories:
name = sanitize_repository_name(repository.name)
for parent, tag in ( for parent, tag in (
(repositories_el, "repository"), (repositories_el, "repository"),
(plugin_repositories_el, "pluginRepository"), (plugin_repositories_el, "pluginRepository"),
): ):
entry = ET.SubElement(parent, tag) entry = ET.SubElement(parent, tag)
ET.SubElement(entry, "id").text = repository.name ET.SubElement(entry, "id").text = name
ET.SubElement(entry, "url").text = repository.url ET.SubElement(entry, "url").text = repository.url
active_profiles = ET.SubElement(settings, "activeProfiles") active_profiles = ET.SubElement(settings, "activeProfiles")
ET.SubElement(active_profiles, "activeProfile").text = "id-configurator" ET.SubElement(active_profiles, "activeProfile").text = "id-configurator"
mirror = ET.SubElement(ET.SubElement(settings, "mirrors"), "mirror") mirror = ET.SubElement(ET.SubElement(settings, "mirrors"), "mirror")
ET.SubElement(mirror, "id").text = repositories[0].name ET.SubElement(mirror, "id").text = sanitize_repository_name(repositories[0].name)
ET.SubElement(mirror, "url").text = repositories[0].url ET.SubElement(mirror, "url").text = repositories[0].url
ET.SubElement(mirror, "mirrorOf").text = "*" ET.SubElement(mirror, "mirrorOf").text = "*"
return settings return settings
+15 -2
View File
@@ -41,7 +41,19 @@ _DISTUTILS_GLOBS = ("usr/lib/python3.*", "usr/local/lib/python3.*")
def _toml_string(value: str) -> str: def _toml_string(value: str) -> str:
return json.dumps(value) """Render `value` as a TOML basic string.
JSON and TOML basic strings share the same escape syntax, so
`json.dumps` is a convenient way to produce one - *except* for
non-ASCII characters: with the default `ensure_ascii=True`, a
character outside the Basic Multilingual Plane (e.g. an emoji) comes
out as a UTF-16 surrogate pair (`\\uXXXX\\uXXXX`), which is valid JSON
but not valid TOML - TOML's `\\uXXXX` must itself be a Unicode scalar
value, not one half of a surrogate pair. `ensure_ascii=False` instead
leaves every non-ASCII character as a literal, raw UTF-8 character,
which TOML basic strings allow directly.
"""
return json.dumps(value, ensure_ascii=False)
def _trusted_host(repository: PythonRepository) -> str | None: def _trusted_host(repository: PythonRepository) -> str | None:
@@ -68,8 +80,9 @@ def _write_pip_conf(context: Context, repositories: list[PythonRepository]) -> N
def _write_uv_toml(context: Context, repositories: list[PythonRepository]) -> None: def _write_uv_toml(context: Context, repositories: list[PythonRepository]) -> None:
lines = ["native-tls = true", ""] lines = ["native-tls = true", ""]
for position, repository in enumerate(repositories): for position, repository in enumerate(repositories):
name = sanitize_repository_name(repository.name)
lines.append("[[index]]") lines.append("[[index]]")
lines.append(f"name = {_toml_string(repository.name)}") lines.append(f"name = {_toml_string(name)}")
lines.append(f"url = {_toml_string(repository.index_url)}") lines.append(f"url = {_toml_string(repository.index_url)}")
if position == 0: if position == 0:
lines.append("default = true") lines.append("default = true")
+1 -1
View File
@@ -44,7 +44,7 @@ maintainers = [
name = "id-configurator" name = "id-configurator"
readme = "README.md" readme = "README.md"
requires-python = ">=3.13" requires-python = ">=3.13"
version = "2026.10.6" version = "2026.10.9"
[project.scripts] [project.scripts]
id-configurator = "id_configurator:main" id-configurator = "id_configurator:main"
+1 -1
View File
@@ -10,7 +10,7 @@ configurations:
sources: [] sources: []
apk: apk:
repositories: repositories:
- name: demo - name: 'demo _.-/\$.:@;''"'
{% set scheme = 'APK_REPO_SCHEME' | env %} {% set scheme = 'APK_REPO_SCHEME' | env %}
url: {{ scheme }}://host.docker.internal:{{ ('APK_REPO_PORT' if scheme == 'https' else 'APK_REPO_HTTP_PORT') | env }}/ url: {{ scheme }}://host.docker.internal:{{ ('APK_REPO_PORT' if scheme == 'https' else 'APK_REPO_HTTP_PORT') | env }}/
url_to_replace: https://dl-cdn.alpinelinux.org/* url_to_replace: https://dl-cdn.alpinelinux.org/*
+1 -1
View File
@@ -25,7 +25,7 @@ configurations:
repositories: [] repositories: []
go: go:
repositories: repositories:
- name: demo - name: 'demo _.-/\$.:@;''"'
{% set scheme = 'GO_REPO_SCHEME' | env %} {% set scheme = 'GO_REPO_SCHEME' | env %}
url: {{ scheme }}://host.docker.internal:{{ ('GO_REPO_PORT' if scheme == 'https' else 'GO_REPO_HTTP_PORT') | env }}/ url: {{ scheme }}://host.docker.internal:{{ ('GO_REPO_PORT' if scheme == 'https' else 'GO_REPO_HTTP_PORT') | env }}/
ruby: ruby:
+1 -1
View File
@@ -21,7 +21,7 @@ configurations:
repositories: [] repositories: []
npm: npm:
repositories: repositories:
- name: demo - name: 'demo _.-/\$.:@;''"'
{% set scheme = 'NPM_REPO_SCHEME' | env %} {% set scheme = 'NPM_REPO_SCHEME' | env %}
url: {{ scheme }}://host.docker.internal:{{ ('NPM_REPO_PORT' if scheme == 'https' else 'NPM_REPO_HTTP_PORT') | env }}/ url: {{ scheme }}://host.docker.internal:{{ ('NPM_REPO_PORT' if scheme == 'https' else 'NPM_REPO_HTTP_PORT') | env }}/
rust: rust:
+1 -1
View File
@@ -29,6 +29,6 @@ configurations:
repositories: [] repositories: []
nuget: nuget:
repositories: repositories:
- name: demo - name: 'demo _.-/\$.:@;''"'
{% set scheme = 'NUGET_REPO_SCHEME' | env %} {% set scheme = 'NUGET_REPO_SCHEME' | env %}
url: {{ scheme }}://host.docker.internal:{{ ('NUGET_REPO_PORT' if scheme == 'https' else 'NUGET_REPO_HTTP_PORT') | env }}/index.json url: {{ scheme }}://host.docker.internal:{{ ('NUGET_REPO_PORT' if scheme == 'https' else 'NUGET_REPO_HTTP_PORT') | env }}/index.json
+1 -1
View File
@@ -19,7 +19,7 @@ configurations:
repositories: [] repositories: []
php: php:
repositories: repositories:
- name: demo - name: 'demo _.-/\$.:@;''"'
{% set scheme = 'PHP_REPO_SCHEME' | env %} {% set scheme = 'PHP_REPO_SCHEME' | env %}
url: {{ scheme }}://host.docker.internal:{{ ('PHP_REPO_PORT' if scheme == 'https' else 'PHP_REPO_HTTP_PORT') | env }}/ url: {{ scheme }}://host.docker.internal:{{ ('PHP_REPO_PORT' if scheme == 'https' else 'PHP_REPO_HTTP_PORT') | env }}/
npm: npm:
+1 -1
View File
@@ -15,7 +15,7 @@ configurations:
keys: [] keys: []
python: python:
repositories: repositories:
- name: demo - name: 'demo _.-/\$.:@;''"'
{% set scheme = 'PYPI_REPO_SCHEME' | env %} {% set scheme = 'PYPI_REPO_SCHEME' | env %}
index-url: {{ scheme }}://host.docker.internal:{{ ('PYPI_REPO_PORT' if scheme == 'https' else 'PYPI_REPO_HTTP_PORT') | env }}/simple/ index-url: {{ scheme }}://host.docker.internal:{{ ('PYPI_REPO_PORT' if scheme == 'https' else 'PYPI_REPO_HTTP_PORT') | env }}/simple/
java: java:
+1 -1
View File
@@ -27,7 +27,7 @@ configurations:
repositories: [] repositories: []
ruby: ruby:
repositories: repositories:
- name: demo - name: 'demo _.-/\$.:@;''"'
{% set scheme = 'RUBY_REPO_SCHEME' | env %} {% set scheme = 'RUBY_REPO_SCHEME' | env %}
url: {{ scheme }}://host.docker.internal:{{ ('RUBY_REPO_PORT' if scheme == 'https' else 'RUBY_REPO_HTTP_PORT') | env }}/ url: {{ scheme }}://host.docker.internal:{{ ('RUBY_REPO_PORT' if scheme == 'https' else 'RUBY_REPO_HTTP_PORT') | env }}/
nuget: nuget:
+1 -1
View File
@@ -23,7 +23,7 @@ configurations:
repositories: [] repositories: []
rust: rust:
repositories: repositories:
- name: demo - name: 'demo _.-/\$.:@;''"'
{% set scheme = 'CARGO_REPO_SCHEME' | env %} {% set scheme = 'CARGO_REPO_SCHEME' | env %}
url: {{ scheme }}://host.docker.internal:{{ ('CARGO_REPO_PORT' if scheme == 'https' else 'CARGO_REPO_HTTP_PORT') | env }}/ url: {{ scheme }}://host.docker.internal:{{ ('CARGO_REPO_PORT' if scheme == 'https' else 'CARGO_REPO_HTTP_PORT') | env }}/
is_sparse: true is_sparse: true
+1 -1
View File
@@ -12,7 +12,7 @@ configurations:
repositories: [] repositories: []
yum: yum:
repositories: repositories:
- name: demo - name: 'demo _.-/\$.:@;''"'
{% set scheme = 'YUM_REPO_SCHEME' | env %} {% set scheme = 'YUM_REPO_SCHEME' | env %}
index-url: {{ scheme }}://host.docker.internal:{{ ('YUM_REPO_PORT' if scheme == 'https' else 'YUM_REPO_HTTP_PORT') | env }}/ index-url: {{ scheme }}://host.docker.internal:{{ ('YUM_REPO_PORT' if scheme == 'https' else 'YUM_REPO_HTTP_PORT') | env }}/
index-url-to-replace: https://mirrors.rockylinux.org/* index-url-to-replace: https://mirrors.rockylinux.org/*
+29
View File
@@ -1,3 +1,5 @@
import tomllib
from id_configurator.config import Configuration from id_configurator.config import Configuration
from id_configurator.context import Context from id_configurator.context import Context
from id_configurator.modules.python import PythonModule from id_configurator.modules.python import PythonModule
@@ -76,6 +78,33 @@ def test_apply_single_repository(tmp_path) -> None:
assert not (dst / "usr/lib").exists() assert not (dst / "usr/lib").exists()
def test_apply_sanitizes_uv_index_name_with_non_bmp_characters(tmp_path) -> None:
data = {
"uuid": "12345678-1234-5678-1234-567812345678",
"organisation": "InterDiode",
"configurations": {
"python": {
"repositories": [
{"name": "demo 😀", "index-url": "https://example.invalid/simple"}
]
}
},
}
config = Configuration.model_validate(data)
dst = tmp_path / "dst"
context = Context(config=config, dst=dst, src=None)
PythonModule().apply(context)
uv_toml = (dst / "etc/uv/uv.toml").read_text(encoding="utf-8")
# The emoji isn't a plain identifier character, so sanitize_repository_name
# replaces it (and the space before it) with "_", same as every other
# non-ASCII or syntax-breaking character.
assert '"demo__"' in uv_toml
parsed = tomllib.loads(uv_toml)
assert parsed["index"][0]["name"] == "demo__"
def test_apply_adds_trusted_host_for_non_https_repository(tmp_path) -> None: def test_apply_adds_trusted_host_for_non_https_repository(tmp_path) -> None:
data = { data = {
"uuid": "12345678-1234-5678-1234-567812345678", "uuid": "12345678-1234-5678-1234-567812345678",
Generated
+790 -790
View File
File diff suppressed because it is too large Load Diff