common-python-tasks 0.11.1__py3-none-any.whl → 0.12.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,27 @@
1
+ # Docker-in-Docker currently supports Debian Bookworm on amd64 and arm64.
2
+ USER root
3
+
4
+ # Optional exact APT version, including the epoch and distribution suffix.
5
+ ARG DIND_DOCKER_VERSION
6
+
7
+ COPY --from=cpt-extension-docker-in-docker --chmod=0755 install.sh \
8
+ /usr/local/share/cpt-dind-install.sh
9
+ RUN /usr/local/share/cpt-dind-install.sh \
10
+ && rm /usr/local/share/cpt-dind-install.sh
11
+
12
+ COPY --from=cpt-extension-docker-in-docker --chmod=0755 supervisor.sh \
13
+ /usr/local/bin/cpt-dind-supervisor
14
+
15
+ RUN mv /pkg/entrypoint.sh /pkg/application-entrypoint.sh \
16
+ && printf '%s\n' '#!/bin/sh' \
17
+ 'exec /usr/bin/tini -- /usr/local/bin/cpt-dind-supervisor "$@"' \
18
+ >/pkg/entrypoint.sh \
19
+ && chmod 0755 /pkg/entrypoint.sh
20
+
21
+ ENV DOCKER_HOST=unix:///var/run/docker.sock
22
+ VOLUME ["/var/lib/docker", "/var/lib/containerd"]
23
+
24
+ # The generic final and debug stages inherit this startup-user selection.
25
+ # The wrapper drops to py only for the application, after Docker is ready.
26
+ ARG RUNTIME_USER=root
27
+ USER ${RUNTIME_USER}
@@ -0,0 +1,39 @@
1
+ #!/bin/sh
2
+ set -eu
3
+
4
+ # shellcheck source=/dev/null
5
+ . /etc/os-release
6
+ if [ "$ID" != debian ] || [ "${VERSION_CODENAME:-}" != bookworm ]; then
7
+ echo >&2 'docker-in-docker requires Debian Bookworm; use slim-bookworm.'
8
+ exit 1
9
+ fi
10
+ case "$(dpkg --print-architecture)" in
11
+ amd64 | arm64) ;;
12
+ *)
13
+ echo >&2 'docker-in-docker supports amd64 and arm64 only.'
14
+ exit 1
15
+ ;;
16
+ esac
17
+
18
+ apt-get update
19
+ apt-get install -y --no-install-recommends \
20
+ bash ca-certificates curl iptables tini util-linux
21
+ install -m 0755 -d /etc/apt/keyrings
22
+ curl -fsSL https://download.docker.com/linux/debian/gpg \
23
+ -o /etc/apt/keyrings/docker.asc
24
+ chmod 0644 /etc/apt/keyrings/docker.asc
25
+ cat >/etc/apt/sources.list.d/docker.sources <<SOURCES
26
+ Types: deb
27
+ URIs: https://download.docker.com/linux/debian
28
+ Suites: bookworm
29
+ Components: stable
30
+ Architectures: $(dpkg --print-architecture)
31
+ Signed-By: /etc/apt/keyrings/docker.asc
32
+ SOURCES
33
+ apt-get update
34
+ apt-get install -y --no-install-recommends \
35
+ "docker-ce${DIND_DOCKER_VERSION:+=$DIND_DOCKER_VERSION}" \
36
+ "docker-ce-cli${DIND_DOCKER_VERSION:+=$DIND_DOCKER_VERSION}" \
37
+ containerd.io docker-buildx-plugin docker-compose-plugin
38
+ usermod -aG docker py
39
+ rm -rf /var/lib/apt/lists/*
@@ -0,0 +1,117 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ _prepare_host() {
5
+ if [[ $(id -u) != 0 ]]; then
6
+ echo >&2 'docker-in-docker must start as root with --privileged.'
7
+ return 1
8
+ fi
9
+ if [[ ! -w /sys/fs/cgroup ]]; then
10
+ echo >&2 'docker-in-docker needs writable cgroups; use --privileged.'
11
+ return 1
12
+ fi
13
+ if [[ -d /sys/kernel/security ]] &&
14
+ ! mountpoint -q /sys/kernel/security; then
15
+ mount -t securityfs none /sys/kernel/security
16
+ fi
17
+ _prepare_cgroups
18
+ if ! iptables -nL >/dev/null 2>&1 &&
19
+ iptables-legacy -nL >/dev/null 2>&1; then
20
+ update-alternatives --set iptables /usr/sbin/iptables-legacy
21
+ update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy
22
+ fi
23
+ # Only remove our daemon's PID file, never unrelated processes' files.
24
+ rm -f /var/run/docker.pid
25
+ }
26
+
27
+ _prepare_cgroups() {
28
+ [[ -f /sys/fs/cgroup/cgroup.controllers ]] || return 0
29
+ mkdir -p /sys/fs/cgroup/cpt-init
30
+ local attempt pid controllers
31
+ for ((attempt = 0; attempt < 5; attempt++)); do
32
+ while read -r pid; do
33
+ # Processes can disappear or become immovable while we move them.
34
+ printf '%s\n' "$pid" | tee \
35
+ /sys/fs/cgroup/cpt-init/cgroup.procs >/dev/null 2>&1 || true
36
+ done </sys/fs/cgroup/cgroup.procs
37
+ controllers=$(sed 's/[^ ]\+/+&/g' \
38
+ /sys/fs/cgroup/cgroup.controllers)
39
+ if printf '%s\n' "$controllers" | tee \
40
+ /sys/fs/cgroup/cgroup.subtree_control >/dev/null 2>&1; then
41
+ return 0
42
+ fi
43
+ sleep 0.1
44
+ done
45
+ echo >&2 'docker-in-docker could not enable nested cgroups.'
46
+ return 1
47
+ }
48
+
49
+ _stop_group() {
50
+ local pid=$1
51
+ [[ -n "$pid" ]] || return 0
52
+ kill -TERM -- "-$pid" 2>/dev/null || true
53
+ local attempt
54
+ for ((attempt = 0; attempt < 50; attempt++)); do
55
+ kill -0 -- "-$pid" 2>/dev/null || break
56
+ sleep 0.1
57
+ done
58
+ kill -KILL -- "-$pid" 2>/dev/null || true
59
+ wait "$pid" 2>/dev/null || true
60
+ }
61
+
62
+ _shutdown() {
63
+ trap '' TERM INT
64
+ _stop_group "$app_pid"
65
+ _stop_group "$daemon_pid"
66
+ }
67
+
68
+ _wait_for_docker() {
69
+ local deadline=$((SECONDS + DIND_STARTUP_TIMEOUT))
70
+ while ((SECONDS < deadline)); do
71
+ if ! kill -0 "$daemon_pid" 2>/dev/null; then
72
+ echo >&2 'docker-in-docker daemon exited during startup.'
73
+ return 1
74
+ fi
75
+ if timeout 1 docker --host "$DOCKER_HOST" info >/dev/null 2>&1; then
76
+ return 0
77
+ fi
78
+ sleep 0.2
79
+ done
80
+ echo >&2 'docker-in-docker timed out waiting for the daemon.'
81
+ return 1
82
+ }
83
+
84
+ _main() {
85
+ if [[ ! ${DIND_STARTUP_TIMEOUT:-60} =~ ^[1-9][0-9]{0,3}$ ]]; then
86
+ echo >&2 'DIND_STARTUP_TIMEOUT must be an integer from 1 to 9999.'
87
+ return 1
88
+ fi
89
+ export DIND_STARTUP_TIMEOUT=${DIND_STARTUP_TIMEOUT:-60}
90
+ export DIND_DOCKER_LOG_PATH=${DIND_DOCKER_LOG_PATH:-/var/log/docker.log}
91
+ export DOCKER_HOST=unix:///var/run/docker.sock
92
+ export container=docker
93
+ unset DOCKER_CONTEXT DOCKER_TLS_VERIFY DOCKER_CERT_PATH
94
+ _prepare_host
95
+ daemon_pid=''
96
+ app_pid=''
97
+ trap _shutdown EXIT
98
+ trap 'exit 143' TERM
99
+ trap 'exit 130' INT
100
+ setsid dockerd --host "$DOCKER_HOST" --group docker \
101
+ >>"$DIND_DOCKER_LOG_PATH" 2>&1 &
102
+ daemon_pid=$!
103
+ _wait_for_docker
104
+ setsid setpriv --reuid py --regid py --init-groups \
105
+ env HOME="$(getent passwd py | cut -d: -f6)" USER=py LOGNAME=py \
106
+ /pkg/application-entrypoint.sh "$@" &
107
+ app_pid=$!
108
+ local finished status=0
109
+ wait -n -p finished "$daemon_pid" "$app_pid" || status=$?
110
+ if [[ "$finished" == "$daemon_pid" ]]; then
111
+ echo >&2 'docker-in-docker daemon exited while the app was running.'
112
+ return 1
113
+ fi
114
+ return "$status"
115
+ }
116
+
117
+ _main "$@"
@@ -40,7 +40,7 @@ COPY . /tmp/build/
40
40
  # Export debug requirements when the project defines a debug dependency group
41
41
  RUN --mount=type=cache,target=/root/.cache/uv,id=uv-cache{{ CACHE_ID_SUFFIX }}{% for mount in UV_INDEX_SECRET_MOUNTS %} \
42
42
  --mount={{ mount }}{% endfor %} \
43
- uv export --group debug --no-hashes --format requirements-txt --output-file requirements-debug.txt
43
+ uv export --frozen --only-group debug --no-hashes --format requirements-txt --output-file requirements-debug.txt
44
44
  {% endif %}
45
45
 
46
46
  # Build the wheel, caching the uv cache directory to speed up subsequent builds.
@@ -133,10 +133,11 @@ LABEL git.commit=${GIT_COMMIT}
133
133
  # This entrypoint is deliberately not configurable via environment variables in order to
134
134
  # ensure that the container always uses the entrypoint selected at build time. If the
135
135
  # current package does not provide a console script, the entrypoint will default to `python`
136
- RUN echo "#!/bin/sh\n\n{{ ENTRYPOINT_COMMAND|default('python') }} \"\$@\"" >/pkg/entrypoint.sh \
136
+ RUN echo "#!/bin/sh\n\nexec {{ ENTRYPOINT_COMMAND|default('python') }} \"\$@\"" >/pkg/entrypoint.sh \
137
137
  && chmod +x /pkg/entrypoint.sh
138
138
 
139
- USER py
139
+ ARG RUNTIME_USER=py
140
+ USER ${RUNTIME_USER}
140
141
 
141
142
  {% if EXTENSION_CONTENT %}
142
143
  {{ EXTENSION_CONTENT }}
@@ -154,10 +155,10 @@ COPY --from=builder /tmp/build /tmp/build
154
155
 
155
156
  RUN --mount=type=cache,target=/root/.cache/pip,id=pip-cache{{ CACHE_ID_SUFFIX }} pip install --root-user-action=ignore -r /tmp/build/requirements-debug.txt
156
157
 
157
- USER py
158
+ USER ${RUNTIME_USER}
158
159
  {% endif %}
159
160
 
160
161
  # Final (default) image: explicitly use runtime as the final target so debug is not used unless requested
161
162
  FROM runtime AS final
162
163
 
163
- USER py
164
+ USER ${RUNTIME_USER}
@@ -227,6 +227,40 @@ def resolve_extension_content(descriptor: dict[str, str | None]) -> str:
227
227
  utils.fatal(f"Unknown extension descriptor source: {descriptor['source']}")
228
228
 
229
229
 
230
+ def resolve_extension_build_context(
231
+ descriptor: dict[str, str | None],
232
+ ) -> tuple[str, Path] | None:
233
+ """Return the named build context for a bundled extension's asset directory.
234
+
235
+ Args:
236
+ descriptor: Extension descriptor dictionary containing `source` and
237
+ `bundle_name` values.
238
+
239
+ Returns:
240
+ A Docker build-context name and its directory, or `None` when the
241
+ extension has no packaged assets.
242
+ """
243
+ if descriptor["source"] != "bundle":
244
+ return None
245
+ bundle_name = descriptor["bundle_name"]
246
+ out = utils.load_data_file(
247
+ f"{bundle_name}/Dockerfile",
248
+ type_identifier="dockerfile_extensions",
249
+ fatal_on_missing=False,
250
+ )
251
+ if out is None:
252
+ utils.fatal(f"Extension bundle not found: {bundle_name}")
253
+ bundle_directory = out[0].parent
254
+ if not bundle_directory.is_dir() or not any(
255
+ path.name != "Dockerfile" for path in bundle_directory.iterdir()
256
+ ):
257
+ return None
258
+ return (
259
+ f"cpt-extension-{re.sub(r'[^a-z0-9]+', '-', bundle_name.lower()).strip('-')}",
260
+ bundle_directory,
261
+ )
262
+
263
+
230
264
  def get_cache_id_suffix(no_cache: bool) -> str:
231
265
  """Return a cache-break suffix for Docker cache mount IDs.
232
266
  Args:
@@ -541,6 +541,7 @@ def build_image(
541
541
  resolve_container_docker_build_args,
542
542
  resolve_container_dockerfile_hook_path,
543
543
  resolve_container_dockerfile_path,
544
+ resolve_extension_build_context,
544
545
  resolve_extension_content,
545
546
  uv_index_secret_build_args,
546
547
  uv_index_secret_mounts,
@@ -571,6 +572,11 @@ def build_image(
571
572
  # Resolve all extension fragments up-front so we fail fast on missing
572
573
  # bundles or files and avoid calling resolution logic multiple times.
573
574
  resolved_fragments = [resolve_extension_content(desc) for desc in extensions]
575
+ extension_build_contexts = [
576
+ context
577
+ for desc in extensions
578
+ if (context := resolve_extension_build_context(desc)) is not None
579
+ ]
574
580
 
575
581
  resolved_docker_build_args = resolve_container_docker_build_args(
576
582
  docker_build_args,
@@ -740,7 +746,14 @@ def build_image(
740
746
  plain=plain,
741
747
  single_arch=single_arch,
742
748
  extra_build_args=merged_build_args or None,
743
- docker_build_args=resolved_docker_build_args,
749
+ docker_build_args=[
750
+ *resolved_docker_build_args,
751
+ *(
752
+ item
753
+ for name, path in extension_build_contexts
754
+ for item in ("--build-context", f"{name}={path}")
755
+ ),
756
+ ],
744
757
  dockerfile_hook_path=resolved_dockerfile_hook_path,
745
758
  )
746
759
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: common-python-tasks
3
- Version: 0.11.1
3
+ Version: 0.12.1
4
4
  Summary: Opinionated Poe the Poet tasks for Python package development.
5
5
  Project-URL: Homepage, http://github.com/ci-sourcerer/common-python-tasks
6
6
  Project-URL: Source, http://github.com/ci-sourcerer/common-python-tasks.git
@@ -42,7 +42,7 @@ Read the [complete documentation](https://ci-sourcerer.github.io/common-python-t
42
42
  Add the package as a development dependency.
43
43
 
44
44
  ```shell
45
- uv add --dev common-python-tasks==0.11.1
45
+ uv add --dev common-python-tasks==0.12.1
46
46
  ```
47
47
 
48
48
  Expose the default task set in `pyproject.toml`.
@@ -2,13 +2,15 @@ common_python_tasks/__init__.py,sha256=tqRLgOwmMQHDb1A84XbML9sWfzMymkSPhJjoF6aCH
2
2
  common_python_tasks/__main__.py,sha256=GpgSd_toMbEaWA_cZvgsZE7jo4XpzuI-awBVm2uhVyw,3512
3
3
  common_python_tasks/docker.py,sha256=_YW-hyHWlUK-kMCvI6QTgxSfmXQQKhYLOKRBPfUmtHA,24176
4
4
  common_python_tasks/docker_compose.py,sha256=NGgMei3oCYS0RbNaQXE-2veXFylO3XtZ7lU-PTtocik,21338
5
- common_python_tasks/env.py,sha256=KxXyvQcSe_zMy4drTWPX3-R_q7u4jlwVNMDkRaTta-M,20666
5
+ common_python_tasks/env.py,sha256=hSjWe8L1RXguLqIMh1IsgbkHa_64b1U8lBHqDIS3zko,21779
6
6
  common_python_tasks/git.py,sha256=0X472w_vHdtbrHHxCmJ85NoQjjogEEDs_0SRXfxbWi0,11719
7
7
  common_python_tasks/github.py,sha256=_LVanl-z4PsdLM94L8y7tkX-AZO2n3Qy39FRY7reDhA,21589
8
8
  common_python_tasks/project.py,sha256=PQ_GQicTrSQgVDB9cJ9E6pU0UhLPom2xou1sGFscr3E,17377
9
- common_python_tasks/tasks.py,sha256=U6CGlLVW_E-9Hofja21bUwiiISU05GdJbTTzI7Vwv2I,63796
9
+ common_python_tasks/tasks.py,sha256=vMCFXZKo-0Dj1TLmZ3lo6oFCAhJSEp637qZWhaWOmpA,64199
10
10
  common_python_tasks/utils.py,sha256=hR7w0Cb7uQ7ILyIegTQ4znWHVoWANlCgxrOnO-Rh5oE,20798
11
- common_python_tasks/data/dockerfile_extensions/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ common_python_tasks/data/dockerfile_extensions/docker-in-docker/Dockerfile,sha256=m7jeQyFe6mAv8AVGpM89NHljzG21ctcSyllbvoEL0Do,1021
12
+ common_python_tasks/data/dockerfile_extensions/docker-in-docker/install.sh,sha256=F87oojGFYT5F1mdcMchRNlUeI_viAO4ip04UHBC_8bQ,1224
13
+ common_python_tasks/data/dockerfile_extensions/docker-in-docker/supervisor.sh,sha256=l1R_ZzkcR0PhL21zgXZSV-tqTjV3KeG9i371Ux_1-ms,3768
12
14
  common_python_tasks/data/fastapi/alembic.ini.j2,sha256=dOiAmZngzX6eyVXeM6BhzgQmes2zwUEdKwOgm8mxjMs,985
13
15
  common_python_tasks/data/fastapi/compose-base.yml.j2,sha256=vDLPAdRJ66lsWZdsKbYqEvY_liXaHZ0ly4ljzqmJsyY,910
14
16
  common_python_tasks/data/fastapi/compose-db-debug.yml.j2,sha256=iREdSy4F8tJ5m9rlB6CROBkj9OtDFVuGphqIuDvHhww,1193
@@ -17,11 +19,11 @@ common_python_tasks/data/fastapi/compose-debug.yml.j2,sha256=sIH9IFvmRAszN6wSejp
17
19
  common_python_tasks/data/generic/.coveragerc,sha256=8zXwDq18wIKQV4NtW2NJYpo3nxCO4LsAChyluYTUoJc,85
18
20
  common_python_tasks/data/generic/.dockerignore,sha256=8kHwNa6Ct-6y3EvRjNuhOKfquuGEywq3P2BHIbG4uXo,53
19
21
  common_python_tasks/data/generic/Dockerfile.deps.j2,sha256=fBmcytyUd4LsC26y0IgZ09QPxrFp0gx0d3OlKNqZ-KM,663
20
- common_python_tasks/data/generic/Dockerfile.j2,sha256=hgLrIRU5xvS2xqw9Xm4kfkcBGHH-mghFPlWv3KSNaz4,6223
22
+ common_python_tasks/data/generic/Dockerfile.j2,sha256=UrR9T794V2ESNIkSVE7K5vEbx43NQiddfsWKG_bY6b4,6301
21
23
  common_python_tasks/data/generic/cliff.toml,sha256=po1MnEGEEgrpyBz3XLWr0h7emXb6ASX63lrU2r6o240,77
22
24
  common_python_tasks/data/generic/pytest.ini,sha256=RHak5QrLzKHvUBjFmaaiOYFrd_8YBRQubXEOT0NJfco,134
23
25
  common_python_tasks/data/generic/ruff.toml,sha256=njvd-5inSGmgY865SWCT4SR9IrKlzS7JQFZmAG2UT_E,53
24
- common_python_tasks-0.11.1.dist-info/METADATA,sha256=v1Gf_iMcyv-ZWpREpc9ji35TtQ9T8oyEmdKPga5yyak,3307
25
- common_python_tasks-0.11.1.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
26
- common_python_tasks-0.11.1.dist-info/licenses/LICENSE,sha256=RsSweM-DyzxFPx38fQOJqHrKyeT9Fiz8rO7yox-fOsw,1070
27
- common_python_tasks-0.11.1.dist-info/RECORD,,
26
+ common_python_tasks-0.12.1.dist-info/METADATA,sha256=p-x2WeHmobmT8MQV2xYAIhKIv3MFUiVkhcMQ5Dna1Ls,3307
27
+ common_python_tasks-0.12.1.dist-info/WHEEL,sha256=THafob7ofN-NsuMN7Mg4qZyHaQI7KkD-QlcQatYhXPo,87
28
+ common_python_tasks-0.12.1.dist-info/licenses/LICENSE,sha256=RsSweM-DyzxFPx38fQOJqHrKyeT9Fiz8rO7yox-fOsw,1070
29
+ common_python_tasks-0.12.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.32.0
2
+ Generator: hatchling 1.32.3
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
File without changes