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 <noreply@anthropic.com>
This commit is contained in:
kevinveenbirkenbachandClaude Sonnet 4.6 committed 2026-04-14 23:07:23 +02:00
1 parent 5f5d79f111
commit f1ea036bd2
4 files changed
+84 -8

No files matched your search

+1 -1
View File
@@ -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
+24 -2
View File
@@ -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)
@@ -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,
@@ -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).