fix(ci): harden community image smoke
This commit is contained in:
@@ -134,6 +134,7 @@ class Client:
|
||||
def __init__(self, base_url: str, timeout_seconds: int) -> None:
|
||||
self.base_url = base_url.rstrip("/")
|
||||
self.timeout_seconds = timeout_seconds
|
||||
self.csrf_token: str | None = None
|
||||
cookie_jar = http.cookiejar.CookieJar()
|
||||
self.opener = urllib.request.build_opener(
|
||||
urllib.request.HTTPCookieProcessor(cookie_jar)
|
||||
@@ -156,6 +157,21 @@ class Client:
|
||||
request_headers = {"Accept": "application/json"}
|
||||
if headers:
|
||||
request_headers.update(headers)
|
||||
if (
|
||||
self.csrf_token
|
||||
and method not in ("GET", "HEAD", "OPTIONS")
|
||||
and (
|
||||
path_or_url.startswith("/api/admin/")
|
||||
or path_or_url.startswith("/api/auth/")
|
||||
)
|
||||
and path_or_url
|
||||
not in (
|
||||
"/api/auth/login",
|
||||
"/api/auth/bootstrap/complete",
|
||||
"/api/auth/session/csrf",
|
||||
)
|
||||
):
|
||||
request_headers.setdefault("x-csrf-token", self.csrf_token)
|
||||
if payload is not None:
|
||||
data = json.dumps(payload).encode("utf-8")
|
||||
request_headers["Content-Type"] = "application/json"
|
||||
@@ -203,12 +219,21 @@ def admin_path(workspace_id: str, suffix: str) -> str:
|
||||
|
||||
|
||||
def login(client: Client, email: str, password: str) -> None:
|
||||
client.request_json(
|
||||
response = client.request_json(
|
||||
"POST",
|
||||
"/api/auth/login",
|
||||
{"email": email, "password": password},
|
||||
expected=(200,),
|
||||
)
|
||||
session = require_object(response.body, "login")
|
||||
csrf_token = session.get("csrf_token")
|
||||
if (
|
||||
not isinstance(csrf_token, str)
|
||||
or not 32 <= len(csrf_token) <= 256
|
||||
or not all(character.isalnum() or character in "-_." for character in csrf_token)
|
||||
):
|
||||
raise safe_error("login", "invalid_csrf_token")
|
||||
client.csrf_token = csrf_token
|
||||
|
||||
|
||||
def resolve_workspace(
|
||||
@@ -326,6 +351,17 @@ def create_agent(client: Client, workspace_id: str, agent_slug: str) -> tuple[st
|
||||
raise safe_error("agent_create", "invalid_response") from error
|
||||
|
||||
|
||||
def agent_etag(client: Client, workspace_id: str, agent_id: str) -> str:
|
||||
response = client.request_json(
|
||||
"GET",
|
||||
admin_path(workspace_id, f"/agents/{agent_id}"),
|
||||
)
|
||||
etag = response.headers.get("ETag") if response.headers is not None else None
|
||||
if not isinstance(etag, str) or len(etag) > 128 or not etag.startswith('"') or not etag.endswith('"'):
|
||||
raise safe_error("agent_precondition", "invalid_response")
|
||||
return etag
|
||||
|
||||
|
||||
def edit_and_archive_operation(
|
||||
client: Client,
|
||||
workspace_id: str,
|
||||
@@ -377,11 +413,13 @@ def bind_and_publish_agent(
|
||||
"enabled": True,
|
||||
}
|
||||
],
|
||||
headers={"If-Match": agent_etag(client, workspace_id, agent_id)},
|
||||
)
|
||||
published = client.request_json(
|
||||
"POST",
|
||||
admin_path(workspace_id, f"/agents/{agent_id}/publish"),
|
||||
{"version": agent_version},
|
||||
headers={"If-Match": agent_etag(client, workspace_id, agent_id)},
|
||||
).body
|
||||
try:
|
||||
published_version = int(published["published_version"])
|
||||
@@ -436,8 +474,15 @@ def cleanup_smoke_assets(
|
||||
if agent_id:
|
||||
try:
|
||||
client.request_json(
|
||||
"DELETE",
|
||||
admin_path(workspace_id, f"/agents/{agent_id}"),
|
||||
"POST",
|
||||
admin_path(workspace_id, f"/agents/{agent_id}/unpublish"),
|
||||
headers={"If-Match": agent_etag(client, workspace_id, agent_id)},
|
||||
expected=(200, 404),
|
||||
)
|
||||
client.request_json(
|
||||
"POST",
|
||||
admin_path(workspace_id, f"/agents/{agent_id}/archive"),
|
||||
headers={"If-Match": agent_etag(client, workspace_id, agent_id)},
|
||||
expected=(200, 404),
|
||||
)
|
||||
except SmokeError as error:
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
port="${1:?port is required}"
|
||||
path="${2:?path is required}"
|
||||
|
||||
if [[ ! "$port" =~ ^[0-9]{1,5}$ ]] || (( port < 1 || port > 65535 )); then
|
||||
exit 64
|
||||
fi
|
||||
|
||||
if [[ "$path" != /* || "$path" == *$'\r'* || "$path" == *$'\n'* ]]; then
|
||||
exit 64
|
||||
fi
|
||||
|
||||
# Bash provides /dev/tcp without adding a network client package to the runtime
|
||||
# image. Compose still applies its own five-second timeout to the whole probe.
|
||||
exec 3<>"/dev/tcp/127.0.0.1/${port}"
|
||||
printf 'GET %s HTTP/1.1\r\nHost: 127.0.0.1\r\nConnection: close\r\n\r\n' "$path" >&3
|
||||
|
||||
IFS=$'\r' read -r -t 2 status <&3
|
||||
case "$status" in
|
||||
'HTTP/1.0 200 '*|'HTTP/1.1 200 '*) ;;
|
||||
*) exit 1 ;;
|
||||
esac
|
||||
Reference in New Issue
Block a user