fix(template): replace click.confirm with input() to avoid ^M on Linux terminals
click.confirm() doesn't strip \r — "y\r" doesn't match "y" and the prompt
loops or fails. Replace with raw input().strip().rstrip('\r').lower() which
handles both \n and \r\n line endings.
Also add --recreate-snapshot flag to skip the interactive prompt entirely
(useful for scripted re-provisioning).
Update snapshot_exists_skip/recreate tests to monkeypatch builtins.input
instead of using CliRunner input=.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -366,6 +366,8 @@ def _run_with_heartbeat(
|
|||||||
help="Store build-user credentials in the system keyring after provisioning.")
|
help="Store build-user credentials in the system keyring after provisioning.")
|
||||||
@click.option("--credential-target", default="BuildVMGuest", show_default=True,
|
@click.option("--credential-target", default="BuildVMGuest", show_default=True,
|
||||||
help="Keyring target name for the stored credential.")
|
help="Keyring target name for the stored credential.")
|
||||||
|
@click.option("--recreate-snapshot", is_flag=True,
|
||||||
|
help="Delete and recreate the snapshot if it already exists (no prompt).")
|
||||||
def template_prepare_win(
|
def template_prepare_win(
|
||||||
vmx: str,
|
vmx: str,
|
||||||
guest_ip: str,
|
guest_ip: str,
|
||||||
@@ -382,6 +384,7 @@ def template_prepare_win(
|
|||||||
auth: str,
|
auth: str,
|
||||||
store_credential: bool,
|
store_credential: bool,
|
||||||
credential_target: str,
|
credential_target: str,
|
||||||
|
recreate_snapshot: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Provision a Windows template VM via pypsrp (Linux-host compatible).
|
"""Provision a Windows template VM via pypsrp (Linux-host compatible).
|
||||||
|
|
||||||
@@ -665,7 +668,12 @@ Register-ScheduledTask -TaskName 'CI-StaticIp' `
|
|||||||
existing = backend.list_snapshots(handle)
|
existing = backend.list_snapshots(handle)
|
||||||
if snapshot in existing:
|
if snapshot in existing:
|
||||||
click.echo(f"[prepare-win] Snapshot '{snapshot}' already exists.")
|
click.echo(f"[prepare-win] Snapshot '{snapshot}' already exists.")
|
||||||
if not click.confirm(f"Delete and recreate '{snapshot}'?", default=False):
|
if recreate_snapshot:
|
||||||
|
do_recreate = True
|
||||||
|
else:
|
||||||
|
raw = input(f"[prepare-win] Delete and recreate '{snapshot}'? [y/N] ")
|
||||||
|
do_recreate = raw.strip().rstrip("\r").lower() in ("y", "yes")
|
||||||
|
if not do_recreate:
|
||||||
click.echo("[prepare-win] Keeping existing snapshot. Provisioning complete.")
|
click.echo("[prepare-win] Keeping existing snapshot. Provisioning complete.")
|
||||||
return
|
return
|
||||||
click.echo(f"[prepare-win] Deleting snapshot '{snapshot}' ...")
|
click.echo(f"[prepare-win] Deleting snapshot '{snapshot}' ...")
|
||||||
|
|||||||
@@ -613,8 +613,8 @@ def test_prepare_win_snapshot_exists_skip(
|
|||||||
backend.list_snapshots.return_value = ["BaseClean"]
|
backend.list_snapshots.return_value = ["BaseClean"]
|
||||||
transport = _make_transport()
|
transport = _make_transport()
|
||||||
_patch_prepare(monkeypatch, backend, transport)
|
_patch_prepare(monkeypatch, backend, transport)
|
||||||
|
monkeypatch.setattr("builtins.input", lambda _prompt="": "N")
|
||||||
|
|
||||||
# User declines deletion with "N"
|
|
||||||
result = CliRunner().invoke(
|
result = CliRunner().invoke(
|
||||||
cli,
|
cli,
|
||||||
[
|
[
|
||||||
@@ -624,7 +624,6 @@ def test_prepare_win_snapshot_exists_skip(
|
|||||||
"--admin-password", "adminpass",
|
"--admin-password", "adminpass",
|
||||||
"--build-password", "buildpass",
|
"--build-password", "buildpass",
|
||||||
],
|
],
|
||||||
input="N\n",
|
|
||||||
)
|
)
|
||||||
assert result.exit_code == 0, result.output
|
assert result.exit_code == 0, result.output
|
||||||
assert "Keeping existing snapshot" in result.output
|
assert "Keeping existing snapshot" in result.output
|
||||||
@@ -645,6 +644,7 @@ def test_prepare_win_snapshot_exists_recreate(
|
|||||||
monkeypatch.setattr(tmpl_mod, "_wait_tcp", lambda *_: True)
|
monkeypatch.setattr(tmpl_mod, "_wait_tcp", lambda *_: True)
|
||||||
monkeypatch.setattr(tmpl_mod, "_wait_winrm", lambda *_: None)
|
monkeypatch.setattr(tmpl_mod, "_wait_winrm", lambda *_: None)
|
||||||
monkeypatch.setattr(tmpl_mod.time, "sleep", lambda _: None)
|
monkeypatch.setattr(tmpl_mod.time, "sleep", lambda _: None)
|
||||||
|
monkeypatch.setattr("builtins.input", lambda _prompt="": "Y")
|
||||||
|
|
||||||
def _sp_run(cmd: list[str], **_: object) -> None:
|
def _sp_run(cmd: list[str], **_: object) -> None:
|
||||||
sp_calls.append(cmd)
|
sp_calls.append(cmd)
|
||||||
@@ -660,13 +660,44 @@ def test_prepare_win_snapshot_exists_recreate(
|
|||||||
"--admin-password", "adminpass",
|
"--admin-password", "adminpass",
|
||||||
"--build-password", "buildpass",
|
"--build-password", "buildpass",
|
||||||
],
|
],
|
||||||
input="Y\n",
|
|
||||||
)
|
)
|
||||||
assert result.exit_code == 0, result.output
|
assert result.exit_code == 0, result.output
|
||||||
assert any("deleteSnapshot" in " ".join(c) for c in sp_calls)
|
assert any("deleteSnapshot" in " ".join(c) for c in sp_calls)
|
||||||
assert any("snapshot" in " ".join(c) and "deleteSnapshot" not in " ".join(c) for c in sp_calls)
|
assert any("snapshot" in " ".join(c) and "deleteSnapshot" not in " ".join(c) for c in sp_calls)
|
||||||
|
|
||||||
|
|
||||||
|
def test_prepare_win_recreate_snapshot_flag(
|
||||||
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||||
|
) -> None:
|
||||||
|
"""--recreate-snapshot skips the interactive prompt and deletes+recreates."""
|
||||||
|
vmx = _setup_prepare_env(tmp_path)
|
||||||
|
monkeypatch.chdir(tmp_path)
|
||||||
|
backend = _make_backend([True, False])
|
||||||
|
backend.list_snapshots.return_value = ["BaseClean"]
|
||||||
|
transport = _make_transport()
|
||||||
|
sp_calls: list[list[str]] = []
|
||||||
|
_patch_prepare(monkeypatch, backend, transport)
|
||||||
|
|
||||||
|
def _sp_run(cmd: list[str], **_: object) -> None:
|
||||||
|
sp_calls.append(cmd)
|
||||||
|
|
||||||
|
monkeypatch.setattr(tmpl_mod.subprocess, "run", _sp_run)
|
||||||
|
|
||||||
|
result = CliRunner().invoke(
|
||||||
|
cli,
|
||||||
|
[
|
||||||
|
"template", "prepare-win",
|
||||||
|
"--vmx", str(vmx),
|
||||||
|
"--ip", "192.168.1.100",
|
||||||
|
"--admin-password", "adminpass",
|
||||||
|
"--build-password", "buildpass",
|
||||||
|
"--recreate-snapshot",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
assert result.exit_code == 0, result.output
|
||||||
|
assert any("deleteSnapshot" in " ".join(c) for c in sp_calls)
|
||||||
|
|
||||||
|
|
||||||
def test_prepare_win_skip_flags(
|
def test_prepare_win_skip_flags(
|
||||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user