#!/usr/bin/env python3 """Exercise a release bundle without developer plugin paths or checksum overrides.""" import hashlib import json import os from pathlib import Path import shutil import signal import socket import subprocess import sys import tempfile import tarfile import time import urllib.request BUNDLE = ("fabro", "sandbox-driver-host", "sandbox-driver-docker", "sandbox-driver-daytona") def output(result): return output(result) def workflow(fabro, root, expect_tampered=False, after_first_run=None): root.mkdir() config = root / "settings.toml" config.write_text( '_version = 1\n[server.auth]\nmethods = ["dev-token"]\n' f'[server.storage]\nroot = "{root / "storage"}"\n' ) graph = root / "workflow.fabro" graph.write_text( 'digraph smoke { start [shape=Mdiamond]; exit [shape=Msquare]; ' 'check [shape=parallelogram, script="printf bundle-smoke-ok"]; ' 'start -> check -> exit; }\n' ) (root / "workflow.toml").write_text( '_version = 1\n[workflow]\ngraph = "workflow.fabro"\n' '[run.environment]\nid = "local"\n' ) with socket.socket() as listener: listener.bind(("127.0.0.1", 0)) port = listener.getsockname()[1] server_url = f"http://127.0.0.1:{port}" token = "fabro_dev_" + "ab" * 32 # Keep credentials, proxy settings and developer checksum overrides out. env = { "PATH": "/usr/bin:/bin:/usr/sbin:/sbin", "FABRO_HOME": str(root / "home"), "FABRO_SERVER": server_url, "FABRO_DEV_TOKEN": token, "SESSION_SECRET": "0123456789abcdef" * 4, "FABRO_NO_UPGRADE_CHECK": "true", "FABRO_TELEMETRY": "off", "FABRO_SUPPRESS_OPEN_BROWSER": "1", "FABRO_HTTP_PROXY_POLICY": "disabled", "NO_COLOR": "1", "GIT_CONFIG_NOSYSTEM": "1", "GIT_CONFIG_GLOBAL": "/dev/null", } def run(*args): return subprocess.run( [str(fabro), *args], cwd=root, env=env, capture_output=True, text=True, timeout=120, ) def run_workflow(): return run("run", str(root / "workflow.toml"), "--environment", "local", "--auto-approve") log_path = root / "server.log" with log_path.open("w") as log: server = subprocess.Popen( [str(fabro), "server", "start", "--foreground", "--no-web", "--config", str(config), "--bind", f"127.0.0.1:{port}"], cwd=root, env=env, stdout=log, stderr=log, start_new_session=True, ) try: client = urllib.request.build_opener(urllib.request.ProxyHandler({})) deadline = time.monotonic() + 60 while True: try: with client.open(server_url + "/health", timeout=1) as response: if response.status == 200: break except OSError: # Startup connection failures are expected; retry until the deadline. pass if server.poll() is not None or time.monotonic() >= deadline: raise RuntimeError("server did not start:\n" + log_path.read_text()) time.sleep(0.1) login = run("auth", "login", "--dev-token", token) if login.returncode: raise RuntimeError(output(login)) result = run_workflow() transcript = output(result) if expect_tampered: if result.returncode == 0 or "checksum" not in transcript.lower(): raise RuntimeError("tampered plugin was not rejected for its checksum:\n" + transcript) elif result.returncode: raise RuntimeError("packaged workflow failed:\n" + transcript + log_path.read_text()) if after_first_run: after_first_run() result = run_workflow() if result.returncode: raise RuntimeError("running server lost its original plugin bundle after an upgrade:\n" + output(result)) graph_result = run("graph", str(root / "workflow.toml")) if graph_result.returncode or "