feat: enhance Docker image tagging and add publication functionality

Signed-off-by: d9pouces <github@19pouces.net>
This commit is contained in:
d9pouces
2026-09-04 21:58:28 +02:00
parent 26fcdd08e4
commit 22367224d7
4 changed files with 172 additions and 7 deletions
+4 -4
View File
@@ -93,11 +93,12 @@ def build_docker_image(
"""Build the Docker image.""" """Build the Docker image."""
if push and not registry: if push and not registry:
raise ValueError("--push requires --registry to be set.") raise ValueError("--push requires --registry to be set.")
id_configurator_version = config.get_project_version() version = config.get_project_version()
name = config.get_project_name()
values = config.get_parameters() values = config.get_parameters()
package_build = DOCKER_FILE_BUILDER.format(**values) package_build = DOCKER_FILE_BUILDER.format(**values)
v_tag = f"id_configurator:{id_configurator_version}" v_tag = f"{name}:{version}"
l_tag = "id_configurator:latest" l_tag = f"{name}:latest"
image_install = DOCKER_FILE_INSTALL.format(**values) image_install = DOCKER_FILE_INSTALL.format(**values)
image_build = DOCKER_FILE_RUNTIME.format(**values) image_build = DOCKER_FILE_RUNTIME.format(**values)
with ( with (
@@ -136,7 +137,6 @@ def build_docker_image(
typer.echo(" ".join(cmd)) typer.echo(" ".join(cmd))
subprocess.check_call(cmd, cwd=config.source_dir) subprocess.check_call(cmd, cwd=config.source_dir)
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
print(package_build + image_install + image_build)
return False return False
return True return True
+4
View File
@@ -38,6 +38,10 @@ class SourceConfig:
python_versions.append(matcher.group(1)) python_versions.append(matcher.group(1))
return python_versions return python_versions
def get_project_name(self) -> str:
"""Get the name of InterDiode from the pyproject.toml file."""
return self.raw_metadata["project"]["name"]
def get_project_version(self) -> str: def get_project_version(self) -> str:
"""Get the version of InterDiode from the pyproject.toml file.""" """Get the version of InterDiode from the pyproject.toml file."""
return self.raw_metadata["project"]["version"] return self.raw_metadata["project"]["version"]
+129
View File
@@ -0,0 +1,129 @@
import json
import logging
import os
import subprocess
import tempfile
import typer
from id_tools.config import SourceConfig
app = typer.Typer(
help="Publish the documentation and release announcements to external destinations."
)
logger = logging.getLogger(__name__)
@app.command(name="sign-docker", help="Signe l'image Docker sur le registre")
def sign_docker_image(
registry: str = typer.Option(
"interdiode", help="Registre cible pour l'image Docker"
),
docker_login: str = typer.Option(
"", envvar="DOCKER_HUB_LOGIN", help="Nom d'utilisateur Docker"
),
docker_token: str = typer.Option(
"", envvar="DOCKER_HUB_TOKEN", help="Jeton d'accès Docker"
),
cosign_password: str = typer.Option(
"", envvar="COSIGN_PASSWORD", help="Mot de passe de la clé privée Cosign"
),
cosign_private_key: str = typer.Option(
"", envvar="COSIGN_PRIVATE_KEY", help="Contenu de la clé privée Cosign"
),
):
config = SourceConfig()
version = config.get_project_version()
image = config.get_project_name()
v_tag = f"{image}:{version}"
l_tag = f"{image}:latest"
destinations = [f"{registry}/{v_tag}", f"{registry}/{l_tag}"]
if docker_login and docker_token:
cmd = ["docker", "login", "-u", docker_login, "--password-stdin"]
subprocess.run(
cmd, input=docker_token, text=True, check=True, cwd=config.source_dir
)
digest_references = {
get_reference_from_tag(destination) for destination in destinations
}
if cosign_password and cosign_private_key:
with (
tempfile.NamedTemporaryFile(mode="w", suffix=".key", delete=True) as key_fd,
tempfile.TemporaryDirectory() as tmp_dir,
):
key_fd.write(cosign_private_key)
key_fd.flush()
cosign_env = {**os.environ, "COSIGN_PASSWORD": cosign_password}
trivy_report = os.path.join(tmp_dir, "trivy_report.json")
for digest_reference in digest_references:
# noinspection SpellCheckingInspection
cmd = [
"trivy",
"image",
"--format",
"json",
"--output",
trivy_report,
digest_reference,
]
subprocess.run(cmd, text=True, check=True, cwd=config.source_dir)
cmd = [
"cosign",
"attest",
"--key",
key_fd.name,
"--type",
"vuln",
"--predicate",
trivy_report,
digest_reference,
]
subprocess.run(cmd, text=True, check=True, cwd=config.source_dir)
cmd = [
"cosign",
"sign",
"--yes",
"--recursive",
"--key",
key_fd.name,
digest_reference,
]
subprocess.check_call(cmd, env=cosign_env)
cmd = [
"cosign",
"verify-attestation",
"--key",
config.cosign_key_url,
"--type",
"vuln",
digest_reference,
]
subprocess.check_call(cmd, env=cosign_env)
cmd = [
"cosign",
"verify",
"--key",
config.cosign_key_url,
digest_reference,
]
subprocess.check_call(cmd, env=cosign_env)
typer.echo(f"✓ Image signée : {registry}/{v_tag} et {registry}/{l_tag}")
def get_reference_from_tag(destination: str) -> str:
# noinspection SpellCheckingInspection
cmd = [
"docker",
"buildx",
"imagetools",
"inspect",
destination,
"--format",
"{{json .Manifest}}",
]
result = subprocess.run(cmd, check=True, capture_output=True, text=True)
manifest = json.loads(result.stdout)
image_name = destination.rsplit(":", 1)[0]
reference = f"{image_name}@{manifest['digest']}"
return reference
+35 -3
View File
@@ -1,5 +1,5 @@
[tox] [tox]
env_list = py313,py314,coverage env_list = py313,py314,coverage,packaging,publish,module-python
isolated_build = true isolated_build = true
requires = requires =
tox>=4 tox>=4
@@ -26,6 +26,38 @@ commands =
python3 -m coverage xml -o .tox/coverage.xml --ignore python3 -m coverage xml -o .tox/coverage.xml --ignore
python3 -m coverage report -m --fail-under=94 --ignore --skip-covered python3 -m coverage report -m --fail-under=94 --ignore --skip-covered
[testenv:packaging]
package = skip
change_dir = {tox_root}
allowlist_externals =
docker
dependency_groups = dev
description =
Generate the docker images for the different platform
depends =
update
commands =
python3 -m id_tools build docker --platform linux/amd64,linux/arm64/v8
[testenv:publish]
package = skip
change_dir = {tox_root}
dependency_groups = dev
description =
Build and publish the image to Docker Hub.
The version is taken from the pyproject.toml file.
Make sure that you have the rights to upload the image to Docker Hub.
commands =
python3 -m uv sync --all-groups --active
python3 -m id_tools build docker --platform linux/amd64,linux/arm64/v8 --push
python3 -m id_tools publication sign-docker
docker =
passenv =
COSIGN_PASSWORD
COSIGN_PRIVATE_KEY
DOCKER_HUB_LOGIN
DOCKER_HUB_TOKEN
[docker:pypi-repo] [docker:pypi-repo]
dockerfile = {tox_root}/tests/dockerfiles/repos/pypi-repo.Dockerfile dockerfile = {tox_root}/tests/dockerfiles/repos/pypi-repo.Dockerfile
expose = expose =
@@ -45,5 +77,5 @@ docker =
allowlist_externals = allowlist_externals =
docker docker
commands = commands =
python3 -m id_tools test render-config tests/configs/Python.yaml.tmpl {toxworkdir}/configs/Python.yaml python3 -m id_tools test render-config {tox_root}/tests/configs/Python.yaml.tmpl {env_dir}/configs/Python.yaml
docker buildx build --platform linux/amd64 --add-host=host.docker.internal:host-gateway --build-context generated-config={toxworkdir}/configs -f tests/dockerfiles/Python.Dockerfile -t id-configurator-python . docker buildx build --platform linux/amd64 --add-host=host.docker.internal:host-gateway --build-context generated-config={env_dir}/configs -f {tox_root}/tests/dockerfiles/Python.Dockerfile -t id-configurator-python .