test_kestra_local_contract.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. import json
  2. import subprocess
  3. from pathlib import Path
  4. ROOT = Path(__file__).resolve().parents[1]
  5. COMPOSE = ROOT / "deploy/docker/docker-compose.yml"
  6. KESTRA_CONFIG = ROOT / "deploy/docker/kestra/application.yml"
  7. POSTGRES_BOOTSTRAP = ROOT / "deploy/docker/postgres/init/000-init.sql"
  8. KESTRA_BOOTSTRAP = (
  9. ROOT / "deploy/docker/postgres/init/010-kestra-runtime.sql"
  10. )
  11. KESTRA_SOAK = ROOT / "scripts/validation/kestra_queue_soak.sh"
  12. def _compose_config():
  13. result = subprocess.run(
  14. [
  15. "docker",
  16. "compose",
  17. "-f",
  18. str(COMPOSE),
  19. "--profile",
  20. "kestra-mcp",
  21. "config",
  22. "--format",
  23. "json",
  24. ],
  25. cwd=ROOT,
  26. check=True,
  27. capture_output=True,
  28. text=True,
  29. )
  30. return json.loads(result.stdout)
  31. def test_kestra_and_mcp_images_are_immutable_and_network_is_isolated():
  32. config = _compose_config()
  33. kestra = config["services"]["kestra"]
  34. mcp = config["services"]["kestra-mcp"]
  35. assert kestra["image"] == "kestra/kestra:v1.3.20"
  36. assert "--worker-thread=16" in kestra["command"]
  37. assert mcp["image"] == (
  38. "ghcr.io/kestra-io/mcp-server-python@"
  39. "sha256:1fd62028f60914af31e244ddb0086ed5017420313e2c8547ea9c01b8d24dfc1d"
  40. )
  41. assert kestra["ports"] == [
  42. {
  43. "mode": "ingress",
  44. "target": 8080,
  45. "published": "18080",
  46. "host_ip": "127.0.0.1",
  47. "protocol": "tcp",
  48. }
  49. ]
  50. assert "ports" not in mcp
  51. assert set(kestra["networks"]) == {"dataops-test-net"}
  52. assert set(mcp["networks"]) == {"dataops-test-net"}
  53. assert mcp["profiles"] == ["kestra-mcp"]
  54. assert mcp["stdin_open"] is True
  55. def test_kestra_uses_its_own_database_and_mcp_has_a_restricted_tool_surface():
  56. config = _compose_config()
  57. kestra_environment = config["services"]["kestra"]["environment"]
  58. mcp_environment = config["services"]["kestra-mcp"]["environment"]
  59. assert kestra_environment["KESTRA_DB_NAME"] == "kestra"
  60. assert kestra_environment["KESTRA_DB_HOST"] == "postgres"
  61. assert kestra_environment["KESTRA_DB_USERNAME"] == "kestra_runtime"
  62. assert config["services"]["kestra"]["restart"] == "unless-stopped"
  63. assert (
  64. config["services"]["kestra"]["depends_on"]["kestra-db-init"][
  65. "condition"
  66. ]
  67. == "service_completed_successfully"
  68. )
  69. assert mcp_environment["KESTRA_BASE_URL"] == "http://kestra:8080/api/v1"
  70. assert mcp_environment["KESTRA_TENANT_ID"] == "main"
  71. assert mcp_environment["KESTRA_MCP_LOG_LEVEL"] == "ERROR"
  72. assert set(mcp_environment["KESTRA_MCP_DISABLED_TOOLS"].split(",")) == {
  73. "ee",
  74. "files",
  75. "kv",
  76. "namespace",
  77. }
  78. combined = json.dumps(
  79. {
  80. "kestra": config["services"]["kestra"],
  81. "kestra-mcp": config["services"]["kestra-mcp"],
  82. },
  83. sort_keys=True,
  84. )
  85. for forbidden in (
  86. "source-postgres",
  87. "source-mysql",
  88. "source-test-password",
  89. "source-root-test-password",
  90. "DATASOURCE_CREDENTIAL_MASTER_KEY",
  91. ):
  92. assert forbidden not in combined
  93. def test_kestra_configuration_and_fresh_database_bootstrap_are_present():
  94. assert KESTRA_CONFIG.is_file()
  95. source = KESTRA_CONFIG.read_text(encoding="utf-8")
  96. assert "datasources:" in source
  97. assert "postgres:" in source
  98. assert (
  99. "url: jdbc:postgresql://${KESTRA_DB_HOST}:5432/"
  100. "${KESTRA_DB_NAME}?tcpKeepAlive=true"
  101. ) in source
  102. assert "maximum-pool-size: 20" in source
  103. assert "minimum-idle: 4" in source
  104. assert "connection-test-query: SELECT 1" in source
  105. assert "basic-auth:" in source
  106. assert "${KESTRA_USERNAME}" in source
  107. assert "${KESTRA_PASSWORD}" in source
  108. assert "type: local" in source
  109. assert "tutorial-flows:" in source
  110. assert "enabled: false" in source
  111. bootstrap = POSTGRES_BOOTSTRAP.read_text(encoding="utf-8")
  112. assert "CREATE DATABASE kestra" in bootstrap
  113. dedicated = KESTRA_BOOTSTRAP.read_text(encoding="utf-8")
  114. assert "CREATE ROLE kestra_runtime LOGIN" in dedicated
  115. assert "ALTER DATABASE kestra OWNER TO kestra_runtime" in dedicated
  116. assert "pg_catalog" in dedicated
  117. soak = KESTRA_SOAK.read_text(encoding="utf-8")
  118. assert "http://127.0.0.1:8081/health" in soak
  119. assert "RestartCount" in soak
  120. assert "broken pipe" in soak
  121. def test_kestra_mcp_is_documented_as_stdio_not_an_http_endpoint():
  122. readme = (ROOT / "deploy/docker/README.md").read_text(encoding="utf-8")
  123. assert "http://127.0.0.1:18080" in readme
  124. assert "Kestra MCP" in readme
  125. assert "stdio" in readme
  126. assert "docker compose --profile kestra-mcp run --rm -T kestra-mcp" in readme