Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 2m12s
CD Pipeline / build-and-deploy (push) Successful in 7m4s
AWOOOI Harbor 110 Local Repair / workflow-shape (push) Successful in 1s
AWOOOI Harbor 110 Local Repair / harbor-110-local-repair (push) Successful in 19s
CD Pipeline / post-deploy-checks (push) Has been cancelled
256 lines
7.8 KiB
Python
256 lines
7.8 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
import json
|
|
import subprocess
|
|
import textwrap
|
|
from pathlib import Path
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[3]
|
|
SCRIPT = (
|
|
ROOT
|
|
/ "scripts"
|
|
/ "reboot-recovery"
|
|
/ "collect-windows99-vmx-source-backup-search.sh"
|
|
)
|
|
RECEIPT_BUILDER = (
|
|
ROOT
|
|
/ "scripts"
|
|
/ "reboot-recovery"
|
|
/ "build-windows99-vmx-source-backup-search-receipt.py"
|
|
)
|
|
|
|
|
|
def _write_executable(path: Path, text: str) -> None:
|
|
path.write_text(textwrap.dedent(text).lstrip())
|
|
path.chmod(0o755)
|
|
|
|
|
|
def _key_values(stdout: str) -> dict[str, str]:
|
|
values: dict[str, str] = {}
|
|
for line in stdout.splitlines():
|
|
if "=" not in line or line.startswith("HOST_SEARCH "):
|
|
continue
|
|
key, value = line.split("=", 1)
|
|
values[key] = value
|
|
return values
|
|
|
|
|
|
def test_backup_search_collector_is_no_secret_no_content_check_mode() -> None:
|
|
text = SCRIPT.read_text(encoding="utf-8")
|
|
|
|
assert "windows99_vmx_source_backup_search_collector_v1" in text
|
|
assert "BatchMode=yes" in text
|
|
assert "PreferredAuthentications=publickey" in text
|
|
assert "PasswordAuthentication=no" in text
|
|
assert "KbdInteractiveAuthentication=no" in text
|
|
assert "NumberOfPasswordPrompts=0" in text
|
|
assert "path_fingerprint_only=true" in text
|
|
assert "file_content_read=false" in text
|
|
assert "remote_write_performed=false" in text
|
|
for forbidden in [
|
|
"sshpass",
|
|
"PasswordAuthentication=yes",
|
|
"KbdInteractiveAuthentication=yes",
|
|
"scp ",
|
|
"sudo ",
|
|
"cat \"$candidate_path\"",
|
|
"Get-Content",
|
|
"Start-VM",
|
|
"vmrun start",
|
|
"shutdown",
|
|
"Restart-Computer",
|
|
"Set-Content",
|
|
"Copy-Item",
|
|
"Move-Item",
|
|
"Remove-Item",
|
|
]:
|
|
assert forbidden not in text
|
|
|
|
|
|
def test_backup_search_collector_emits_fingerprints_not_paths(tmp_path: Path) -> None:
|
|
fake_bin = tmp_path / "bin"
|
|
fake_bin.mkdir()
|
|
_write_executable(
|
|
fake_bin / "ssh",
|
|
"""
|
|
#!/usr/bin/env bash
|
|
target="${@: -2:1}"
|
|
if [[ "$target" == *"blocked"* ]]; then
|
|
exit 255
|
|
fi
|
|
printf '%s\n' 'fp-one'
|
|
printf '%s\n' 'fp-two'
|
|
""",
|
|
)
|
|
|
|
env = os.environ.copy()
|
|
env["PATH"] = f"{fake_bin}:{env['PATH']}"
|
|
env["WINDOWS99_VMX_BACKUP_SEARCH_KNOWN_HOSTS_FILE"] = str(
|
|
fake_bin / "known_hosts"
|
|
)
|
|
result = subprocess.run(
|
|
[
|
|
"bash",
|
|
str(SCRIPT),
|
|
"--hosts",
|
|
"ok=wooo@ok,blocked=wooo@blocked",
|
|
"--timeout",
|
|
"3",
|
|
],
|
|
cwd=ROOT,
|
|
env=env,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
|
|
assert result.returncode == 0
|
|
values = _key_values(result.stdout)
|
|
assert values["schema_version"] == "windows99_vmx_source_backup_search_collector_v1"
|
|
assert values["target_vm_alias"] == "111"
|
|
assert values["candidate_source_count"] == "2"
|
|
assert values["searched_host_count"] == "1"
|
|
assert values["blocked_host_count"] == "1"
|
|
assert "HOST_SEARCH alias=ok status=searched candidate_source_count=2" in result.stdout
|
|
assert "HOST_SEARCH alias=blocked status=blocked_ssh_batchmode_unavailable" in (
|
|
result.stdout
|
|
)
|
|
assert "path_fingerprints=fp-one,fp-two" in result.stdout
|
|
assert "192.168.0." not in result.stdout
|
|
assert "/home/wooo" not in result.stdout
|
|
assert "file_content_read=false" in result.stdout
|
|
assert "remote_write_performed=false" in result.stdout
|
|
assert "vm_power_change_performed=false" in result.stdout
|
|
|
|
|
|
def test_backup_search_collector_resolves_bare_aliases_and_blocks_unknown(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
fake_bin = tmp_path / "bin"
|
|
fake_bin.mkdir()
|
|
_write_executable(
|
|
fake_bin / "ssh",
|
|
"""
|
|
#!/usr/bin/env bash
|
|
target="${@: -2:1}"
|
|
if [[ "$target" == *"blocked"* ]]; then
|
|
exit 255
|
|
fi
|
|
printf '%s\n' 'fp-alias'
|
|
""",
|
|
)
|
|
|
|
env = os.environ.copy()
|
|
env["PATH"] = f"{fake_bin}:{env['PATH']}"
|
|
env["WINDOWS99_VMX_BACKUP_SEARCH_KNOWN_HOSTS_FILE"] = str(
|
|
fake_bin / "known_hosts"
|
|
)
|
|
env["WINDOWS99_VMX_BACKUP_SEARCH_HOST_DIRECTORY"] = (
|
|
"ok=wooo@ok,blocked=wooo@blocked"
|
|
)
|
|
result = subprocess.run(
|
|
[
|
|
"bash",
|
|
str(SCRIPT),
|
|
"--hosts",
|
|
"ok,blocked,missing",
|
|
"--timeout",
|
|
"3",
|
|
],
|
|
cwd=ROOT,
|
|
env=env,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
|
|
assert result.returncode == 0
|
|
values = _key_values(result.stdout)
|
|
assert values["candidate_source_count"] == "1"
|
|
assert values["searched_host_count"] == "1"
|
|
assert values["blocked_host_count"] == "2"
|
|
assert "HOST_SEARCH alias=ok status=searched candidate_source_count=1" in result.stdout
|
|
assert "HOST_SEARCH alias=blocked status=blocked_ssh_batchmode_unavailable" in (
|
|
result.stdout
|
|
)
|
|
assert "HOST_SEARCH alias=missing status=blocked_host_alias_unresolved" in (
|
|
result.stdout
|
|
)
|
|
|
|
|
|
def test_backup_search_receipt_builder_projects_collector_stdout_safely(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
collector_output = tmp_path / "collector.txt"
|
|
collector_output.write_text(
|
|
textwrap.dedent(
|
|
"""
|
|
schema_version=windows99_vmx_source_backup_search_collector_v1
|
|
search_hosts=110=wooo@192.168.0.110,120=wooo@192.168.0.120
|
|
secret_value_read=false
|
|
file_content_read=false
|
|
raw_path_output=false
|
|
HOST_SEARCH alias=110 status=blocked_ssh_batchmode_unavailable candidate_source_count=0 path_fingerprints=
|
|
HOST_SEARCH alias=120 status=searched candidate_source_count=2 path_fingerprints=abc12345,def67890
|
|
candidate_source_count=2
|
|
search_status=candidate_fingerprint_found_requires_internal_path_resolution
|
|
"""
|
|
).strip()
|
|
+ "\n",
|
|
encoding="utf-8",
|
|
)
|
|
locator_receipt = tmp_path / "locator.json"
|
|
locator_receipt.write_text(
|
|
json.dumps(
|
|
{
|
|
"schema_version": "windows99_vmx_source_locator_receipt_v1",
|
|
"collector_readback_present": True,
|
|
"locate_status": "blocked_missing_expected_vmx_source_no_candidate_found",
|
|
"candidate_source_count_known": True,
|
|
"candidate_source_count": 0,
|
|
"candidate_source_fingerprint_count": 0,
|
|
}
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
result = subprocess.run(
|
|
[
|
|
"python3",
|
|
str(RECEIPT_BUILDER),
|
|
"--collector-output",
|
|
str(collector_output),
|
|
"--locator-receipt",
|
|
str(locator_receipt),
|
|
"--generated-at",
|
|
"2026-07-09T17:10:00+08:00",
|
|
"--stdout",
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=True,
|
|
)
|
|
|
|
payload = json.loads(result.stdout)
|
|
assert payload["schema_version"] == "windows99_vmx_source_backup_search_receipt_v1"
|
|
assert payload["collector_readback_present"] is True
|
|
assert payload["searched_host_count"] == 1
|
|
assert payload["blocked_host_count"] == 2
|
|
assert payload["candidate_source_count"] == 2
|
|
assert payload["candidate_source_fingerprint_count"] == 2
|
|
assert payload["search_status"] == (
|
|
"candidate_fingerprint_found_requires_internal_path_resolution"
|
|
)
|
|
assert payload["raw_path_output"] is False
|
|
assert payload["path_fingerprint_only"] is True
|
|
assert payload["file_content_read"] is False
|
|
assert payload["secret_value_read"] is False
|
|
assert payload["remote_write_performed"] is False
|
|
assert payload["vm_power_change_performed"] is False
|
|
assert "192.168.0." not in result.stdout
|
|
assert "D:\\" not in result.stdout
|
|
assert "/Users/" not in result.stdout
|