Hide stack trace shown on YAML parse error by default

Fixes https://github.com/containers/podman-compose/issues/1139

Signed-off-by: Yusuke Matsubara <whym@whym.org>
This commit is contained in:
Yusuke Matsubara
2025-06-26 20:45:00 +09:00
committed by Povilas Kanapickas
parent b06224389e
commit 764efd360c
5 changed files with 87 additions and 3 deletions

View File

@ -0,0 +1 @@
Hide the stack trace on a YAML parse error.

View File

@ -1904,6 +1904,15 @@ def rec_merge(target: dict[str, Any], *sources: dict[str, Any]) -> dict[str, Any
return ret
def load_yaml_or_die(file_path: str, stream: Any) -> dict[str, Any]:
try:
return yaml.safe_load(stream)
except yaml.scanner.ScannerError as e:
log.fatal("Compose file contains an error:\n%s", e)
log.info("Compose file %s contains an error:", file_path, exc_info=e)
sys.exit(1)
def resolve_extends(
services: dict[str, Any], service_names: list[str], environ: dict[str, Any]
) -> None:
@ -1920,7 +1929,7 @@ def resolve_extends(
if filename.startswith("./"):
filename = filename[2:]
with open(filename, "r", encoding="utf-8") as f:
content = yaml.safe_load(f) or {}
content = load_yaml_or_die(filename, f) or {}
if "services" in content:
content = content["services"]
subdirectory = os.path.dirname(filename)
@ -2229,10 +2238,10 @@ class PodmanCompose:
break
if filename.strip().split('/')[-1] == '-':
content = yaml.safe_load(sys.stdin)
content = load_yaml_or_die(filename, sys.stdin)
else:
with open(filename, "r", encoding="utf-8") as f:
content = yaml.safe_load(f)
content = load_yaml_or_die(filename, f)
# log(filename, json.dumps(content, indent = 2))
if not isinstance(content, dict):
sys.stderr.write(

View File

@ -0,0 +1,4 @@
version: "3"
services:foo
web1:
image: busybox

View File

@ -0,0 +1,4 @@
version: "3"
services:
web1:
image: busybox

View File

@ -0,0 +1,66 @@
# SPDX-License-Identifier: GPL-2.0
import os
import unittest
from tests.integration.test_utils import RunSubprocessMixin
from tests.integration.test_utils import podman_compose_path
from tests.integration.test_utils import test_path
def bad_compose_yaml_path() -> str:
base_path = os.path.join(test_path(), "parse-error")
return os.path.join(base_path, "docker-compose-error.yml")
def good_compose_yaml_path() -> str:
base_path = os.path.join(test_path(), "parse-error")
return os.path.join(base_path, "docker-compose.yml")
class TestComposeBuildParseError(unittest.TestCase, RunSubprocessMixin):
def test_no_error(self) -> None:
try:
_, err = self.run_subprocess_assert_returncode(
[podman_compose_path(), "-f", good_compose_yaml_path(), "config"], 0
)
self.assertEqual(b"", err)
finally:
self.run_subprocess([
podman_compose_path(),
"-f",
bad_compose_yaml_path(),
"down",
])
def test_simple_parse_error(self) -> None:
try:
_, err = self.run_subprocess_assert_returncode(
[podman_compose_path(), "-f", bad_compose_yaml_path(), "config"], 1
)
self.assertIn(b"could not find expected ':'", err)
self.assertNotIn(b"\nTraceback (most recent call last):\n", err)
finally:
self.run_subprocess([
podman_compose_path(),
"-f",
bad_compose_yaml_path(),
"down",
])
def test_verbose_parse_error_contains_stack_trace(self) -> None:
try:
_, err = self.run_subprocess_assert_returncode(
[podman_compose_path(), "--verbose", "-f", bad_compose_yaml_path(), "config"], 1
)
self.assertIn(b"\nTraceback (most recent call last):\n", err)
finally:
self.run_subprocess([
podman_compose_path(),
"-f",
bad_compose_yaml_path(),
"down",
])