From f1ea036bd22e2209aaf5641b2ea256b271175615 Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Tue, 14 Apr 2026 23:07:23 +0200 Subject: [PATCH] fix(ci): resolve test-development, nextcloud, and container CA failures - plugins/lookup/config.py: honor default by setting skip_missing_app=not strict so lookup('config', app, path, default) returns the default when the app is missing from the applications dict, instead of raising during early constructor phases. Fixes 5 test-development jobs. - roles/web-app-nextcloud/files/playwright.spec.js: replace unsupported .toSatisfy() with a boolean check inside expect.poll() + .toBe(true). Fixes Deploy web-app-nextcloud. - roles/sys-svc-container/files/container.py: when the target image has no ENTRYPOINT, inject the internal CA as a volume plus NODE_EXTRA_CA_CERTS, SSL_CERT_FILE, REQUESTS_CA_BUNDLE and CURL_CA_BUNDLE instead of falling back to a plain 'docker run' without CA trust. Fixes npm UNABLE_TO_VERIFY_LEAF_SIGNATURE inside Microsoft Playwright images for moodle, prometheus and onlyoffice deploys. - tests/unit/roles/sys-svc-container/files/test_container.py: cover the new no-ENTRYPOINT CA-injection path. Note: web-app-mini-qr 'lookup ghcr.io: no such host' is not addressed here and likely needs a separate investigation of CoreDNS forwarding on the CI runner. Co-Authored-By: Claude Sonnet 4.6 --- plugins/lookup/config.py | 2 +- roles/sys-svc-container/files/container.py | 26 ++++++++- .../files/playwright.spec.js | 8 +-- .../sys-svc-container/files/test_container.py | 56 +++++++++++++++++++ 4 files changed, 84 insertions(+), 8 deletions(-) diff --git a/plugins/lookup/config.py b/plugins/lookup/config.py index 58c15c6c76..389c28a384 100644 --- a/plugins/lookup/config.py +++ b/plugins/lookup/config.py @@ -53,7 +53,7 @@ class LookupModule(LookupBase): config_path=config_path, strict=strict, default=default_value, - skip_missing_app=False, + skip_missing_app=not strict, ) # lookup plugins must return a list diff --git a/roles/sys-svc-container/files/container.py b/roles/sys-svc-container/files/container.py index 8fbd24e651..9fcc63cbbf 100644 --- a/roles/sys-svc-container/files/container.py +++ b/roles/sys-svc-container/files/container.py @@ -367,9 +367,31 @@ def container_run(argv: List[str], debug: bool, with_ca: bool) -> int: if not ep: warn( "Image has no ENTRYPOINT and none was provided. " - "Running without CA wrapper." + "Injecting CA env vars without entrypoint wrapper." + ) + # Inject CA cert as a volume + env vars so Node.js (NODE_EXTRA_CA_CERTS), + # curl (CURL_CA_BUNDLE), Python requests (REQUESTS_CA_BUNDLE), and other + # tools can trust the internal CA even without a wrapper entrypoint. + ca_inject_opts: List[str] = [ + "-v", + f"{ca_host}:{ca_container}:ro", + "-e", + f"CA_TRUST_CERT={ca_container}", + "-e", + f"CA_TRUST_NAME={trust_name}", + "-e", + f"NODE_EXTRA_CA_CERTS={ca_container}", + "-e", + f"SSL_CERT_FILE={ca_container}", + "-e", + f"REQUESTS_CA_BUNDLE={ca_container}", + "-e", + f"CURL_CA_BUNDLE={ca_container}", + ] + return exec_docker( + ["docker", "run"] + run_opts + ca_inject_opts + [image] + user_args, + debug=debug, ) - return exec_docker(["docker", "run", *argv], debug=debug) final_cmd.extend(ep) final_cmd.extend(user_args) diff --git a/roles/web-app-nextcloud/files/playwright.spec.js b/roles/web-app-nextcloud/files/playwright.spec.js index 3caff74c5c..667e11cc6b 100644 --- a/roles/web-app-nextcloud/files/playwright.spec.js +++ b/roles/web-app-nextcloud/files/playwright.spec.js @@ -419,17 +419,15 @@ test("dashboard to nextcloud login", async ({ page }) => { async () => { const iframeHandle = await nextcloudIframe.elementHandle(); const iframeFrame = iframeHandle ? await iframeHandle.contentFrame() : null; - - return iframeFrame ? iframeFrame.url() : ""; + const currentUrl = iframeFrame ? iframeFrame.url() : ""; + return currentUrl.includes(expectedOidcAuthUrl) || currentUrl.startsWith(expectedNextcloudBaseUrl); }, { timeout: 60_000, message: `Expected Nextcloud iframe to load either the Keycloak OIDC login or the standalone Nextcloud login page` } ) - .toSatisfy((currentUrl) => { - return currentUrl.includes(expectedOidcAuthUrl) || currentUrl.startsWith(expectedNextcloudBaseUrl); - }); + .toBe(true); await enterNextcloudLoginThroughVisibleEntryPoint( page, diff --git a/tests/unit/roles/sys-svc-container/files/test_container.py b/tests/unit/roles/sys-svc-container/files/test_container.py index 5bda81ea39..5c47a0822f 100644 --- a/tests/unit/roles/sys-svc-container/files/test_container.py +++ b/tests/unit/roles/sys-svc-container/files/test_container.py @@ -324,6 +324,62 @@ class TestContainerWrapper(unittest.TestCase): os.environ.clear() os.environ.update(old_env) + def test_container_run_injects_ca_env_when_image_has_no_entrypoint(self): + """ + When with_ca=True, CA env is available, but the image has no ENTRYPOINT, + container_run() must inject CA env vars and volume without an entrypoint wrapper. + """ + old_env = dict(os.environ) + calls = [] + + def fake_exec_docker(cmd, debug=False): + calls.append(("exec_docker", cmd, debug)) + return 0 + + def fake_try_inspect_no_ep(image, pull_policy): + return [] # no entrypoint + + orig_exec_docker = container.exec_docker + orig_try_inspect = container.try_inspect_entrypoint_with_pull + try: + with tempfile.TemporaryDirectory() as td: + td_path = Path(td) + ca_file = td_path / "root-ca.crt" + wrapper_file = td_path / "with-ca-trust.sh" + ca_file.write_text("dummy-ca\n", encoding="utf-8") + wrapper_file.write_text("#!/bin/sh\necho ok\n", encoding="utf-8") + + os.environ["CA_TRUST_CERT_HOST"] = str(ca_file) + os.environ["CA_TRUST_WRAPPER_HOST"] = str(wrapper_file) + os.environ["CA_TRUST_NAME"] = "test-ca" + + container.exec_docker = fake_exec_docker + container.try_inspect_entrypoint_with_pull = fake_try_inspect_no_ep + + argv = ["--rm", "playwright:v1.0", "/bin/bash", "-lc", "npm install"] + rc = container.container_run(argv, debug=False, with_ca=True) + + self.assertEqual(rc, 0) + self.assertEqual(len(calls), 1) + cmd = calls[0][1] + # Must be a docker run command + self.assertEqual(cmd[0], "docker") + self.assertEqual(cmd[1], "run") + # Must contain NODE_EXTRA_CA_CERTS injection + cmd_str = " ".join(cmd) + self.assertIn("NODE_EXTRA_CA_CERTS", cmd_str) + self.assertIn("/tmp/infinito/ca/root-ca.crt", cmd_str) + # Must NOT use --entrypoint (no wrapper) + self.assertNotIn("--entrypoint", cmd_str) + # Image and original command must appear + self.assertIn("playwright:v1.0", cmd_str) + self.assertIn("npm install", cmd_str) + finally: + container.exec_docker = orig_exec_docker + container.try_inspect_entrypoint_with_pull = orig_try_inspect + os.environ.clear() + os.environ.update(old_env) + def test_container_run_with_ca_false_always_plain_docker_run(self): """ When with_ca=False, container_run() must always call plain container run (no env needed).