test_p2_wp11_delivery_contract.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. from __future__ import annotations
  2. import hashlib
  3. import json
  4. import os
  5. import subprocess
  6. import tarfile
  7. from pathlib import Path
  8. ROOT = Path(__file__).resolve().parents[1]
  9. OPERATIONS = ROOT / "deployment/compose"
  10. def _package(output: Path) -> Path:
  11. completed = subprocess.run(
  12. [
  13. "bash",
  14. str(OPERATIONS / "package_offline.sh"),
  15. "--repo-root",
  16. str(ROOT),
  17. "--output-dir",
  18. str(output),
  19. "--version",
  20. "p2-wp11-contract",
  21. "--source-date-epoch",
  22. "1775251200",
  23. "--skip-images",
  24. ],
  25. cwd=ROOT,
  26. capture_output=True,
  27. text=True,
  28. )
  29. assert completed.returncode == 0, completed.stderr
  30. return output / "dataops-platform-offline-p2-wp11-contract.tar.gz"
  31. def test_package_is_reproducible_and_contains_trace_evidence(tmp_path: Path):
  32. first = _package(tmp_path / "first")
  33. second = _package(tmp_path / "second")
  34. assert hashlib.sha256(first.read_bytes()).digest() == hashlib.sha256(
  35. second.read_bytes()
  36. ).digest()
  37. with tarfile.open(first, "r:gz") as archive:
  38. names = set(archive.getnames())
  39. prefix = "dataops-platform-offline-p2-wp11-contract/"
  40. expected = {
  41. "environments/development.json",
  42. "environments/test.json",
  43. "environments/preproduction.json",
  44. "operations/product_engineering.py",
  45. "operations/reproducible_archive.py",
  46. "helm/dataops-platform/Chart.yaml",
  47. "helm/dataops-platform/values.schema.json",
  48. "enterprise/enterprise_delivery.py",
  49. "trace/OPENAPI.yaml",
  50. "trace/backend.cdx.json",
  51. "trace/frontend.cdx.json",
  52. "release-manifest.json",
  53. "release-manifest.txt",
  54. "checksums.sha256",
  55. }
  56. assert {prefix + item for item in expected} <= names
  57. def test_environment_files_match_their_profiles():
  58. for profile_path in sorted((ROOT / "deployment/environments").glob("*.json")):
  59. profile = json.loads(profile_path.read_text(encoding="utf-8"))
  60. env_path = profile_path.with_suffix(".env")
  61. values = dict(
  62. line.split("=", 1)
  63. for line in env_path.read_text(encoding="utf-8").splitlines()
  64. if line and not line.startswith("#")
  65. )
  66. assert values["DATAOPS_NETWORK_NAME"] == profile["network_name"]
  67. assert values["DATAOPS_ENVIRONMENT"] == profile["environment"]
  68. assert values["DATAOPS_DATA_NAMESPACE"] == profile["data_namespace"]
  69. for name, value in profile["ports"].items():
  70. assert values[name] == str(value)
  71. def test_preflight_and_install_accept_environment_profile():
  72. for script in ("preflight.sh", "install_offline.sh"):
  73. help_text = subprocess.run(
  74. ["bash", str(OPERATIONS / script), "--help"],
  75. cwd=ROOT,
  76. capture_output=True,
  77. text=True,
  78. check=True,
  79. ).stdout
  80. assert "--environment-profile" in help_text
  81. def test_install_rejects_image_head_that_differs_from_release_manifest():
  82. install = (OPERATIONS / "install_offline.sh").read_text(encoding="utf-8")
  83. preflight = (OPERATIONS / "preflight.sh").read_text(encoding="utf-8")
  84. common = (OPERATIONS / "wp13-common.sh").read_text(encoding="utf-8")
  85. assert "wp13_expected_migration_head" in common
  86. assert "installed image migration head does not match release manifest" in install
  87. assert "image migration head does not match release manifest" in preflight
  88. def test_preflight_fails_closed_when_running_image_head_is_stale(tmp_path: Path):
  89. bundle = tmp_path / "bundle"
  90. fake_bin = tmp_path / "bin"
  91. bundle.mkdir()
  92. fake_bin.mkdir()
  93. compose = bundle / "docker-compose.yml"
  94. compose.write_text("services: {}\n", encoding="utf-8")
  95. (bundle / "release-manifest.json").write_text(
  96. json.dumps({"migration": {"head": "20260802_460"}}),
  97. encoding="utf-8",
  98. )
  99. fake_docker = fake_bin / "docker"
  100. fake_docker.write_text(
  101. """#!/usr/bin/env bash
  102. set -euo pipefail
  103. arguments="$*"
  104. if [[ "${arguments}" == "ps -q --filter label=com.docker.compose.project=stale-project" ]]; then
  105. printf 'container-id\\n'
  106. elif [[ "${arguments}" == *" config --format json" ]]; then
  107. printf '{"services":{"backend":{"image":"fake-backend"}}}\\n'
  108. elif [[ "${arguments}" == "run --rm --network none --read-only --entrypoint alembic fake-backend -c alembic.ini heads" ]]; then
  109. printf '20260730_360 (head)\\n'
  110. elif [[ "${arguments}" == *" alembic -c alembic.ini current" ]]; then
  111. printf '20260730_360 (head)\\n'
  112. elif [[ "${arguments}" == *" alembic -c alembic.ini heads" ]]; then
  113. printf '20260730_360 (head)\\n'
  114. fi
  115. """,
  116. encoding="utf-8",
  117. )
  118. fake_docker.chmod(0o755)
  119. env = os.environ.copy()
  120. env["PATH"] = f"{fake_bin}:/usr/bin:/bin"
  121. completed = subprocess.run(
  122. [
  123. "bash",
  124. str(OPERATIONS / "preflight.sh"),
  125. "--bundle-root",
  126. str(bundle),
  127. "--compose-file",
  128. str(compose),
  129. "--project-name",
  130. "stale-project",
  131. "--skip-checksums",
  132. ],
  133. cwd=ROOT,
  134. env=env,
  135. capture_output=True,
  136. text=True,
  137. )
  138. assert completed.returncode != 0
  139. assert "image migration head does not match release manifest" in (
  140. completed.stdout + completed.stderr
  141. )
  142. def test_product_engineering_cli_is_read_only_by_default():
  143. for arguments in (
  144. ["validate-environments", "--profiles-dir", "deployment/environments"],
  145. ["manifest", "--repo-root", ".", "--version", "contract", "--source-date-epoch", "1775251200"],
  146. ["sbom", "--repo-root", ".", "--output-dir", "-"],
  147. ):
  148. completed = subprocess.run(
  149. [str(ROOT / ".venv/bin/python"), str(OPERATIONS / "product_engineering.py"), *arguments],
  150. cwd=ROOT,
  151. capture_output=True,
  152. text=True,
  153. )
  154. assert completed.returncode == 0, completed.stderr
  155. def test_rollback_remains_restore_based_and_never_downgrades_database():
  156. source = (OPERATIONS / "rollback.sh").read_text(encoding="utf-8")
  157. assert "restore.sh" in source
  158. assert "alembic downgrade" not in source