Files
wehub-resource-sync 4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:45 +08:00

141 lines
4.7 KiB
Python

"""SSM provisioning and readiness checks for the gateway systemd service."""
from __future__ import annotations
import base64
import logging
import shlex
import time
from platform.deployment.aws.client import DEFAULT_REGION
from platform.deployment.aws.config import (
GATEWAY_HEALTH_MAX_ATTEMPTS,
GATEWAY_HEALTH_POLL_INTERVAL_SECONDS,
GATEWAY_READY_LOG_SENTINEL,
SSM_PROVISION_CMD_POLL_ATTEMPTS,
SSM_PROVISION_CMD_POLL_INTERVAL_SECONDS,
)
from platform.deployment.aws.ssm import run_ssm_shell_command
logger = logging.getLogger(__name__)
_ENV_DIR = "/etc/opensre"
_GATEWAY_ENV_PATH = f"{_ENV_DIR}/gateway.env"
_SERVICE_NAME = "opensre-gateway"
def _env_file_content(env_vars: dict[str, str]) -> str:
"""Return systemd EnvironmentFile content for the given variables."""
lines: list[str] = []
for key in sorted(env_vars):
value = env_vars[key]
if "\n" in value or "\r" in value:
raise ValueError(f"Environment variable {key} must not contain newlines")
lines.append(f"{key}={value}")
return "\n".join(lines) + "\n"
def _write_env_file_commands(path: str, content: str) -> list[str]:
"""Return shell commands that write ``content`` to ``path`` via base64."""
encoded = base64.b64encode(content.encode()).decode("ascii")
quoted_path = shlex.quote(path)
return [
f"mkdir -p {shlex.quote(_ENV_DIR)}",
f"echo {shlex.quote(encoded)} | base64 -d > {quoted_path}",
f"chmod 640 {quoted_path}",
f"chown root:opensre {quoted_path}",
]
def provision_gateway_via_ssm(
instance_id: str,
*,
env_vars: dict[str, str] | None = None,
region: str = DEFAULT_REGION,
) -> None:
"""Write the gateway env file and restart the systemd service via SSM.
The env file is transferred as base64 so values with special shell
characters (quotes, spaces, etc.) are never interpreted by the shell.
"""
gateway_env = {"MODE": "gateway", **(env_vars or {})}
env_content = _env_file_content(gateway_env)
# SSM AWS-RunShellScript runs via /bin/sh (dash on Ubuntu). pipefail is
# bash-only; use POSIX -eu so provisioning works on both AL2023 and Ubuntu.
commands = [
"set -eu",
*_write_env_file_commands(_GATEWAY_ENV_PATH, env_content),
f"systemctl restart {shlex.quote(_SERVICE_NAME)}",
]
result = run_ssm_shell_command(
instance_id=instance_id,
commands=commands,
region=region,
poll_interval=SSM_PROVISION_CMD_POLL_INTERVAL_SECONDS,
max_poll_attempts=SSM_PROVISION_CMD_POLL_ATTEMPTS,
)
status = result.get("status", "")
if status != "Success":
stderr = result.get("stderr", "").strip()
raise RuntimeError(
f"Failed to provision gateway on {instance_id}: "
f"status={status}, stderr={stderr or 'none'}"
)
def wait_for_gateway_ready(
instance_id: str,
*,
region: str = DEFAULT_REGION,
poll_interval: int = GATEWAY_HEALTH_POLL_INTERVAL_SECONDS,
max_attempts: int = GATEWAY_HEALTH_MAX_ATTEMPTS,
) -> None:
"""Poll until the opensre-gateway systemd service is active and has logged the ready sentinel.
Raises:
TimeoutError: When the service does not reach a ready state in time.
"""
service = shlex.quote(_SERVICE_NAME)
for attempt in range(max_attempts):
try:
result = run_ssm_shell_command(
instance_id=instance_id,
commands=[
f"systemctl is-active {service} || true",
f"journalctl -u {service} -n 200 --no-pager 2>/dev/null || true",
],
region=region,
)
stdout = result.get("stdout", "")
lines = [ln.strip() for ln in stdout.strip().splitlines() if ln.strip()]
service_active = bool(lines) and lines[0] == "active"
sentinel_found = GATEWAY_READY_LOG_SENTINEL in stdout
if service_active and sentinel_found:
logger.info(
"Gateway service ready on %s after %d attempts",
instance_id,
attempt + 1,
)
return
logger.debug(
"Gateway not ready yet (attempt %d/%d): active=%s sentinel=%s",
attempt + 1,
max_attempts,
service_active,
sentinel_found,
)
except Exception as exc: # noqa: BLE001
logger.debug("SSM gateway check attempt %d failed: %s", attempt + 1, exc)
if attempt < max_attempts - 1:
time.sleep(poll_interval)
raise TimeoutError(
f"Gateway service on {instance_id} did not become ready "
f"after {max_attempts * poll_interval}s"
)