mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 10:59:59 +00:00
Two CI failures on PR #1265 (run 40834164415):
- Chunk 0 / SuiteCRM#2 (swarm): the post-load flush included
sys-ctl-hlth-runtime on every play host, the NFS server too, which never
gets sys-svc-container and therefore has no `container` CLI. The unit
died with FileNotFoundError: 'container'. The role entry now runs only
where a container runtime exists: in compose mode, or on swarm nodes
('svc-swarm-node' in group_names), the same role-entry host scoping that
sys-ctl-hlth-csp and sys-ctl-hlth-webserver use with IS_STACK_HOST.
Workers stay covered because PHP replicas can run there.
- Workspace (ubuntu): tests/external/repository/test_urls_reachable.py
probed the literal "https://hub.docker.com/v2" from
utils/docker/image/runtime.py:20 and got HTTP 404. The constant is gone;
the endpoint is written as one f-string at the call site, the way
utils/update/docker.py:104 and utils/update/source.py do, which the URL
test skips as a template. With the scheme now visible in the literal,
ruff reported the noqa: S310 on urllib.request.Request as unused.
Both test files that imported unittest twice (CodeQL) now use one
`from unittest import ...` form.
The run_once queue leak that spreads the stack host's post-load queue to
every host is fixed separately on the updater line (70ed8a2e98); when it
meets main, sys-ctl-hlth-runtime moves into sys-svc-container's queue.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
215 lines
7.3 KiB
Python
215 lines
7.3 KiB
Python
from __future__ import annotations
|
|
|
|
import io
|
|
import json
|
|
import tempfile
|
|
from pathlib import Path
|
|
from unittest import TestCase, main, mock
|
|
|
|
from utils.docker.image import runtime
|
|
|
|
FLOORS = {"8.2": "8.2.35", "8.3": "8.3.36", "8.4": "8.4.27", "8.5": "8.5.12"}
|
|
|
|
|
|
def _php(version=None, lines=()):
|
|
return runtime.PhpRuntime(version=version, lines=tuple(lines))
|
|
|
|
|
|
class TestPhpRuntime(TestCase):
|
|
def test_reads_php_version_from_an_env_instruction(self):
|
|
self.assertEqual(
|
|
runtime.php_runtime(
|
|
["ENV PHP_INI_DIR=/usr/local/etc/php", "ENV PHP_VERSION=8.4.15"]
|
|
),
|
|
_php("8.4.15"),
|
|
)
|
|
|
|
def test_the_last_php_version_wins(self):
|
|
self.assertEqual(
|
|
runtime.php_runtime(
|
|
["ENV PHP_VERSION=8.3.1", "ENV PHP_VERSION=8.4.27"]
|
|
).version,
|
|
"8.4.27",
|
|
)
|
|
|
|
def test_alpine_packages_name_their_line(self):
|
|
found = runtime.php_runtime(
|
|
["RUN /bin/sh -c apk add php83 php83-fpm php83-ldap"]
|
|
)
|
|
self.assertEqual(found, _php(lines=["8.3"]))
|
|
|
|
def test_debian_packages_name_their_line(self):
|
|
found = runtime.php_runtime(
|
|
["RUN apt-get install -y php8.4-cli libapache2-mod-php8.4 php8.4-ldap"]
|
|
)
|
|
self.assertEqual(found, _php(lines=["8.4"]))
|
|
|
|
def test_an_unversioned_alpine_php7_package_names_its_major(self):
|
|
self.assertEqual(
|
|
runtime.php_runtime(["RUN apk add php7 php7-fpm"]), _php(lines=["7"])
|
|
)
|
|
|
|
def test_a_php_base_build_argument_names_its_line(self):
|
|
self.assertEqual(
|
|
runtime.php_runtime(
|
|
["ARG PHP_BASE=7.4", "RUN |1 PHP_BASE=7.4 /bin/bash -c build"]
|
|
),
|
|
_php(lines=["7.4"]),
|
|
)
|
|
|
|
def test_a_php_directory_outside_a_package_install_is_not_php(self):
|
|
self.assertIsNone(
|
|
runtime.php_runtime(
|
|
["COPY /app/snuffleupagus.so /usr/lib/php83/modules/ # buildkit"]
|
|
)
|
|
)
|
|
|
|
def test_a_pinned_alpine_package_names_its_line(self):
|
|
found = runtime.php_runtime(["RUN apk add --no-cache php84=8.4.27-r0 nginx;"])
|
|
self.assertEqual(found, _php(lines=["8.4"]))
|
|
|
|
def test_php_paths_are_not_packages(self):
|
|
self.assertIsNone(
|
|
runtime.php_runtime(
|
|
['RUN mkdir -p "$PHP_INI_DIR/conf.d" /usr/local/etc/php']
|
|
)
|
|
)
|
|
|
|
def test_an_image_without_php_has_no_runtime(self):
|
|
self.assertIsNone(
|
|
runtime.php_runtime(
|
|
["ENV PG_VERSION=17.2", "RUN apt-get install -y postgresql"]
|
|
)
|
|
)
|
|
|
|
|
|
class TestPhpFinding(TestCase):
|
|
def test_a_version_below_its_floor_is_a_finding(self):
|
|
self.assertIn(
|
|
"below the security floor 8.4.27",
|
|
runtime.php_finding(_php("8.4.15"), FLOORS),
|
|
)
|
|
|
|
def test_a_version_at_its_floor_is_fine(self):
|
|
self.assertIsNone(runtime.php_finding(_php("8.2.35"), FLOORS))
|
|
|
|
def test_php_8_1_is_end_of_life(self):
|
|
self.assertIn(
|
|
"end-of-life line 8.1", runtime.php_finding(_php("8.1.34"), FLOORS)
|
|
)
|
|
|
|
def test_a_line_newer_than_every_floor_is_fine(self):
|
|
self.assertIsNone(runtime.php_finding(_php("8.6.0"), FLOORS))
|
|
|
|
def test_a_distro_php7_is_end_of_life(self):
|
|
self.assertEqual(
|
|
runtime.php_finding(_php(lines=["7"]), FLOORS), "line 7 is end of life"
|
|
)
|
|
|
|
def test_a_distro_php_8_1_is_end_of_life(self):
|
|
self.assertEqual(
|
|
runtime.php_finding(_php(lines=["8.1"]), FLOORS), "line 8.1 is end of life"
|
|
)
|
|
|
|
def test_a_supported_distro_line_is_no_finding(self):
|
|
self.assertIsNone(runtime.php_finding(_php(lines=["8.3"]), FLOORS))
|
|
|
|
|
|
class TestLoadPhpFloors(TestCase):
|
|
def test_reads_the_php_floors_from_the_vars_file(self):
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
path = Path(tmp) / "main.yml"
|
|
path.write_text(
|
|
"HEALTH_RUNTIME_FLOORS:\n php:\n '8.4': '8.4.27'\n",
|
|
encoding="utf-8",
|
|
)
|
|
with mock.patch.object(runtime, "FLOORS_FILE", path):
|
|
self.assertEqual(runtime.load_php_floors(), {"8.4": "8.4.27"})
|
|
|
|
|
|
class TestBuildInstructions(TestCase):
|
|
def test_a_docker_hub_tag_is_read_through_the_hub_api(self):
|
|
with (
|
|
mock.patch.object(
|
|
runtime, "_hub_instructions", return_value=["ENV A=1"]
|
|
) as hub,
|
|
mock.patch.object(runtime, "config_blob") as blob,
|
|
):
|
|
found = runtime.build_instructions(
|
|
"docker.io/wordpress", "6.8", os_name="linux", architecture="amd64"
|
|
)
|
|
self.assertEqual(found, ["ENV A=1"])
|
|
hub.assert_called_once_with("library/wordpress", "6.8", "linux", "amd64")
|
|
blob.assert_not_called()
|
|
|
|
def test_another_registry_is_read_through_the_config_blob(self):
|
|
blob = {
|
|
"history": [{"created_by": "RUN apk add php84"}],
|
|
"config": {"Env": ["PHP_VERSION=8.4.27"]},
|
|
}
|
|
with mock.patch.object(runtime, "config_blob", return_value=blob):
|
|
found = runtime.build_instructions(
|
|
"ghcr.io/acme/app", "1.0", os_name="linux", architecture="amd64"
|
|
)
|
|
self.assertEqual(found, ["RUN apk add php84", "ENV PHP_VERSION=8.4.27"])
|
|
|
|
def test_a_docker_hub_digest_pin_is_read_through_the_config_blob(self):
|
|
with (
|
|
mock.patch.object(runtime, "_hub_instructions") as hub,
|
|
mock.patch.object(runtime, "config_blob", return_value=None),
|
|
):
|
|
found = runtime.build_instructions(
|
|
"docker.io/acme/app",
|
|
"sha256:" + "a" * 64,
|
|
os_name="linux",
|
|
architecture="amd64",
|
|
)
|
|
self.assertIsNone(found)
|
|
hub.assert_not_called()
|
|
|
|
|
|
class TestHubInstructions(TestCase):
|
|
def _respond(self, payload):
|
|
response = mock.MagicMock()
|
|
response.__enter__.return_value = io.BytesIO(json.dumps(payload).encode())
|
|
return mock.patch.object(
|
|
runtime.urllib.request, "urlopen", return_value=response
|
|
)
|
|
|
|
def test_picks_the_layers_of_the_requested_platform(self):
|
|
payload = [
|
|
{
|
|
"os": "linux",
|
|
"architecture": "arm64",
|
|
"layers": [{"instruction": "ENV PHP_VERSION=1"}],
|
|
},
|
|
{
|
|
"os": "linux",
|
|
"architecture": "amd64",
|
|
"layers": [{"instruction": "ENV PHP_VERSION=2"}],
|
|
},
|
|
]
|
|
with self._respond(payload):
|
|
found = runtime._hub_instructions("library/php", "8.4", "linux", "amd64")
|
|
self.assertEqual(found, ["ENV PHP_VERSION=2"])
|
|
|
|
def test_a_tag_without_the_platform_is_unanswered(self):
|
|
with self._respond([{"os": "linux", "architecture": "arm64", "layers": []}]):
|
|
self.assertIsNone(
|
|
runtime._hub_instructions("library/php", "8.4", "linux", "amd64")
|
|
)
|
|
|
|
def test_a_network_error_is_unanswered(self):
|
|
with mock.patch.object(
|
|
runtime.urllib.request,
|
|
"urlopen",
|
|
side_effect=runtime.urllib.error.URLError("down"),
|
|
):
|
|
self.assertIsNone(
|
|
runtime._hub_instructions("library/php", "8.4", "linux", "amd64")
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|