152 lines
4.9 KiB
Bash
Executable File
152 lines
4.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
RELEASE_ROOT="${DOMAINCHECK_RELEASE_ROOT:-${ROOT_DIR}/release}"
|
|
PACKAGE_PATH="${1:-}"
|
|
HASH_PATH="${2:-}"
|
|
|
|
python3 - "${RELEASE_ROOT}" "${PACKAGE_PATH}" "${HASH_PATH}" <<'PY'
|
|
import hashlib
|
|
import json
|
|
import sys
|
|
import tarfile
|
|
import zipfile
|
|
from pathlib import Path
|
|
|
|
release_root = Path(sys.argv[1])
|
|
package_path_arg = sys.argv[2].strip()
|
|
hash_path_arg = sys.argv[3].strip()
|
|
|
|
|
|
def resolve_latest_package(root: Path) -> Path:
|
|
candidates = sorted(
|
|
[
|
|
item
|
|
for item in root.glob("*")
|
|
if item.is_file() and (item.name.endswith(".tar.gz") or item.name.endswith(".zip"))
|
|
],
|
|
key=lambda item: item.stat().st_mtime,
|
|
reverse=True,
|
|
)
|
|
if not candidates:
|
|
raise SystemExit("No release archive found")
|
|
return candidates[0]
|
|
|
|
|
|
def derive_hash_path(root: Path, package_path: Path) -> Path:
|
|
if package_path.name.endswith(".tar.gz"):
|
|
base_name = package_path.name[:-7]
|
|
else:
|
|
base_name = package_path.stem
|
|
candidate = root / f"{base_name}.sha256.txt"
|
|
if candidate.exists():
|
|
return candidate
|
|
candidates = sorted(root.glob("*.sha256.txt"), key=lambda item: item.stat().st_mtime, reverse=True)
|
|
if not candidates:
|
|
raise SystemExit("No hash file found")
|
|
return candidates[0]
|
|
|
|
|
|
def compute_sha256(path: Path) -> str:
|
|
sha = hashlib.sha256()
|
|
with path.open("rb") as handle:
|
|
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
|
sha.update(chunk)
|
|
return sha.hexdigest().lower()
|
|
|
|
|
|
def normalize_entry_names(names: list[str]) -> list[str]:
|
|
normalized: list[str] = []
|
|
for name in names:
|
|
item = name.replace("\\", "/")
|
|
while item.startswith("./"):
|
|
item = item[2:]
|
|
item = item.strip("/")
|
|
if item:
|
|
normalized.append(item)
|
|
return normalized
|
|
|
|
|
|
def read_archive_entries(path: Path) -> tuple[list[str], dict]:
|
|
if path.name.endswith(".zip"):
|
|
with zipfile.ZipFile(path, "r") as archive:
|
|
entry_names = normalize_entry_names(archive.namelist())
|
|
manifest = {}
|
|
if "release_manifest.json" in entry_names:
|
|
with archive.open("release_manifest.json", "r") as handle:
|
|
manifest = json.loads(handle.read().decode("utf-8"))
|
|
return entry_names, manifest
|
|
|
|
with tarfile.open(path, "r:*") as archive:
|
|
entry_names = normalize_entry_names(archive.getnames())
|
|
manifest = {}
|
|
if "release_manifest.json" in entry_names:
|
|
manifest_member = archive.extractfile("./release_manifest.json") or archive.extractfile("release_manifest.json")
|
|
if manifest_member is not None:
|
|
manifest = json.loads(manifest_member.read().decode("utf-8"))
|
|
return entry_names, manifest
|
|
|
|
|
|
package_path = Path(package_path_arg) if package_path_arg else resolve_latest_package(release_root)
|
|
if not package_path.exists():
|
|
raise SystemExit(f"archive not found: {package_path}")
|
|
|
|
hash_path = Path(hash_path_arg) if hash_path_arg else derive_hash_path(release_root, package_path)
|
|
if not hash_path.exists():
|
|
raise SystemExit(f"hash file not found: {hash_path}")
|
|
|
|
expected_hash = ""
|
|
expected_file = ""
|
|
for line in hash_path.read_text(encoding="utf-8").splitlines():
|
|
if line.startswith("sha256="):
|
|
expected_hash = line.split("=", 1)[1].strip().lower()
|
|
elif line.startswith("file="):
|
|
expected_file = line.split("=", 1)[1].strip()
|
|
|
|
if not expected_hash:
|
|
raise SystemExit(f"sha256 entry missing in {hash_path}")
|
|
|
|
actual_hash = compute_sha256(package_path)
|
|
entry_names, manifest = read_archive_entries(package_path)
|
|
entry_set = set(entry_names)
|
|
|
|
required_entries = [
|
|
"README_RELEASE.txt",
|
|
"release_manifest.json",
|
|
"domain-api/README.md",
|
|
"domain-web/README.md",
|
|
"scripts/package_domain_release.ps1",
|
|
"scripts/verify_domain_release.ps1",
|
|
"scripts/package_domain_release.sh",
|
|
"scripts/verify_domain_release.sh",
|
|
"scripts/smoke_test_stack.ps1",
|
|
]
|
|
|
|
missing_entries = [item for item in required_entries if item not in entry_set]
|
|
|
|
smoke_report = str(((manifest.get("smoke_test") or {}).get("report") or "")).strip()
|
|
if smoke_report and smoke_report not in entry_set:
|
|
missing_entries.append(smoke_report)
|
|
|
|
ok = (
|
|
actual_hash == expected_hash
|
|
and not missing_entries
|
|
and (not expected_file or expected_file == package_path.name)
|
|
)
|
|
|
|
report = {
|
|
"ok": ok,
|
|
"archive": str(package_path),
|
|
"hash_file": str(hash_path),
|
|
"expected_file": expected_file,
|
|
"actual_file": package_path.name,
|
|
"expected_sha256": expected_hash,
|
|
"actual_sha256": actual_hash,
|
|
"missing_entries": missing_entries,
|
|
"package_type": "tar.gz" if package_path.name.endswith(".tar.gz") else "zip",
|
|
}
|
|
print(json.dumps(report, ensure_ascii=False, indent=2))
|
|
raise SystemExit(0 if ok else 1)
|
|
PY
|