Skip to content

Commit 6446f02

Browse files
committed
fix: expand seed \n escapes in process_file_updates
expand seed escapes in process_file_updates like echo -e Signed-off-by: Elena German <elgerman@redhat.com>
1 parent a0c68a7 commit 6446f02

2 files changed

Lines changed: 79 additions & 1 deletion

File tree

scripts/python/tasks/internal/process_file_updates.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@
4646
PROG = "process_file_updates.py"
4747
FILE_UPDATES_SECRET_MOUNT_ENV = "FILE_UPDATES_SECRET_MOUNT"
4848
_REPLACEMENT_EXPRESSION = re.compile(r"^\|([^|\n]*)\|([^|\n]*)\|$")
49+
_SEED_ESCAPE = re.compile(r"\\([ntr\\])")
50+
_SEED_ESCAPE_REPLACEMENTS = {"n": "\n", "t": "\t", "r": "\r", "\\": "\\"}
4951

5052

5153
def _usage_text() -> str:
@@ -364,6 +366,14 @@ def resolve_target_file(repo_cwd: Path, entry_path: str) -> Path:
364366
return target_file
365367

366368

369+
def _expand_seed_content(seed: str) -> str:
370+
"""Expand backslash escapes in seed text like bash ``echo -e``."""
371+
return _SEED_ESCAPE.sub(
372+
lambda match: _SEED_ESCAPE_REPLACEMENTS[match.group(1)],
373+
seed,
374+
)
375+
376+
367377
def seed_target_file(entry: dict[str, Any], target_file: Path, repo_cwd: Path) -> None:
368378
"""Create or overwrite *target_file* when the path entry includes a seed value."""
369379
seed = entry.get("seed") or ""
@@ -374,6 +384,7 @@ def seed_target_file(entry: dict[str, Any], target_file: Path, repo_cwd: Path) -
374384
return
375385
logger.info("seed operation to perform")
376386
target_file.parent.mkdir(parents=True, exist_ok=True)
387+
seed = _expand_seed_content(seed)
377388
target_file.write_text(
378389
seed + ("\n" if not seed.endswith("\n") else ""),
379390
encoding="utf-8",

scripts/python/tasks/internal/test_process_file_updates.py

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
66
- ``replacements``: ``test_run_file_updates_creates_mr``,
77
``test_apply_replacements_for_entry_applies_replacement``
8-
- ``seed``: ``test_seed_target_file_writes_and_stages``
8+
- ``seed``: ``test_seed_target_file_writes_and_stages``,
9+
``test_seed_target_file_expands_echo_e_escapes``
910
- ``combo``: Tekton only (integration smoke)
1011
- ``replacements-idempotent``: ``test_run_file_updates_returns_existing_mr``,
1112
``test_find_existing_mr_with_same_diff``
@@ -767,6 +768,72 @@ def test_seed_target_file_writes_and_stages(tmp_path: Path) -> None:
767768
status.assert_called_once_with(repo)
768769

769770

771+
def test_seed_target_file_expands_echo_e_escapes(tmp_path: Path) -> None:
772+
"""E2E-style seeds with literal ``\\n`` expand like bash ``echo -e``."""
773+
repo = tmp_path / "repo"
774+
repo.mkdir()
775+
target = repo / "addons" / "my-addon.yaml"
776+
paths_json = json.dumps(
777+
[
778+
{
779+
"path": "addons/my-addon.yaml",
780+
"seed": "indexImage: \\nname: my-addon\\nrelatedImages: []",
781+
}
782+
]
783+
)
784+
seed = json.loads(paths_json)[0]["seed"]
785+
with (
786+
mock.patch.object(process_file_updates.vcs_git, "index_add_commit"),
787+
mock.patch.object(
788+
process_file_updates.vcs_git,
789+
"working_tree_status",
790+
return_value="",
791+
),
792+
):
793+
process_file_updates.seed_target_file(
794+
{"path": "addons/my-addon.yaml", "seed": seed},
795+
target,
796+
repo,
797+
)
798+
assert target.read_text(encoding="utf-8") == (
799+
"indexImage: \nname: my-addon\nrelatedImages: []\n"
800+
)
801+
802+
803+
def test_expand_seed_content_preserves_unicode() -> None:
804+
"""Non-ASCII seed text is not corrupted by escape expansion."""
805+
seed = "label: café\\nemoji: 🚀\\tend"
806+
assert process_file_updates._expand_seed_content(seed) == "label: café\nemoji: 🚀\tend"
807+
808+
809+
def test_expand_seed_content_leaves_unknown_escapes_and_trailing_backslash() -> None:
810+
"""Unrecognized escapes and trailing backslashes are left unchanged."""
811+
assert process_file_updates._expand_seed_content(r"path\x\z") == r"path\x\z"
812+
assert process_file_updates._expand_seed_content("trailing\\") == "trailing\\"
813+
assert process_file_updates._expand_seed_content(r"keep\\slash") == r"keep\slash"
814+
815+
816+
def test_seed_target_file_preserves_unicode_content(tmp_path: Path) -> None:
817+
"""Seeded files retain arbitrary Unicode after escape expansion."""
818+
repo = tmp_path / "repo"
819+
repo.mkdir()
820+
target = repo / "i18n.yaml"
821+
with (
822+
mock.patch.object(process_file_updates.vcs_git, "index_add_commit"),
823+
mock.patch.object(
824+
process_file_updates.vcs_git,
825+
"working_tree_status",
826+
return_value="",
827+
),
828+
):
829+
process_file_updates.seed_target_file(
830+
{"path": "i18n.yaml", "seed": "title: café\\nicon: 🚀"},
831+
target,
832+
repo,
833+
)
834+
assert target.read_text(encoding="utf-8") == "title: café\nicon: 🚀\n"
835+
836+
770837
def test_seed_target_file_propagates_git_add_failure(tmp_path: Path) -> None:
771838
"""``seed-error`` Tekton: git add failure propagates (``main`` maps to Failed)."""
772839
repo = tmp_path / "repo"

0 commit comments

Comments
 (0)