372 lines
9.8 KiB
Bash
372 lines
9.8 KiB
Bash
|
|
#!/usr/bin/env bash
|
||
|
|
set -euo pipefail
|
||
|
|
|
||
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||
|
|
HTTP_HELPER="${ROOT_DIR}/build/amduatd_http_unix"
|
||
|
|
USE_HTTP_HELPER=0
|
||
|
|
TMPDIR="${TMPDIR:-/tmp}"
|
||
|
|
mkdir -p "${TMPDIR}"
|
||
|
|
|
||
|
|
if ! command -v grep >/dev/null 2>&1; then
|
||
|
|
echo "skip: grep not found" >&2
|
||
|
|
exit 77
|
||
|
|
fi
|
||
|
|
if ! command -v awk >/dev/null 2>&1; then
|
||
|
|
echo "skip: awk not found" >&2
|
||
|
|
exit 77
|
||
|
|
fi
|
||
|
|
if command -v curl >/dev/null 2>&1; then
|
||
|
|
if curl --help 2>/dev/null | grep -q -- '--unix-socket'; then
|
||
|
|
USE_HTTP_HELPER=0
|
||
|
|
else
|
||
|
|
USE_HTTP_HELPER=1
|
||
|
|
fi
|
||
|
|
else
|
||
|
|
USE_HTTP_HELPER=1
|
||
|
|
fi
|
||
|
|
if [[ "${USE_HTTP_HELPER}" -eq 1 && ! -x "${HTTP_HELPER}" ]]; then
|
||
|
|
echo "skip: curl lacks --unix-socket support and helper missing" >&2
|
||
|
|
exit 77
|
||
|
|
fi
|
||
|
|
|
||
|
|
AMDUATD_BIN="${ROOT_DIR}/build/amduatd"
|
||
|
|
ASL_BIN="${ROOT_DIR}/vendor/amduat/build/amduat-asl"
|
||
|
|
|
||
|
|
if [[ ! -x "${AMDUATD_BIN}" || ! -x "${ASL_BIN}" ]]; then
|
||
|
|
echo "missing binaries; build amduatd and amduat-asl first" >&2
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
tmp_root="$(mktemp -d -p "${TMPDIR}" amduatd-fed-ingest-XXXXXX)"
|
||
|
|
tmp_keep="${AMDUATD_FED_INGEST_KEEP_TMP:-0}"
|
||
|
|
last_log="${TMPDIR}/amduatd-fed-ingest.last.log"
|
||
|
|
root_store="${tmp_root}/store"
|
||
|
|
root_ref="${tmp_root}/ref"
|
||
|
|
sock="${tmp_root}/amduatd.sock"
|
||
|
|
log="${tmp_root}/amduatd.log"
|
||
|
|
space_a="alpha"
|
||
|
|
space_b="beta"
|
||
|
|
|
||
|
|
show_log() {
|
||
|
|
if [[ -n "${log}" && -f "${log}" ]]; then
|
||
|
|
echo "daemon log: ${log}" >&2
|
||
|
|
cat "${log}" >&2
|
||
|
|
else
|
||
|
|
echo "daemon log missing: ${log}" >&2
|
||
|
|
fi
|
||
|
|
if [[ -f "${last_log}" ]]; then
|
||
|
|
echo "last log copy: ${last_log}" >&2
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
die() {
|
||
|
|
echo "$1" >&2
|
||
|
|
show_log
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
|
||
|
|
cleanup() {
|
||
|
|
if [[ -n "${pid:-}" ]]; then
|
||
|
|
kill "${pid}" >/dev/null 2>&1 || true
|
||
|
|
fi
|
||
|
|
if [[ -f "${log}" ]]; then
|
||
|
|
cp -f "${log}" "${last_log}" 2>/dev/null || true
|
||
|
|
fi
|
||
|
|
if [[ "${tmp_keep}" -eq 0 ]]; then
|
||
|
|
rm -rf "${tmp_root}"
|
||
|
|
else
|
||
|
|
echo "kept tmpdir: ${tmp_root}" >&2
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
trap cleanup EXIT
|
||
|
|
|
||
|
|
mkdir -p "${root_store}" "${root_ref}"
|
||
|
|
"${ASL_BIN}" index init --root "${root_store}"
|
||
|
|
"${ASL_BIN}" index init --root "${root_ref}"
|
||
|
|
|
||
|
|
"${AMDUATD_BIN}" --root "${root_store}" --sock "${sock}" \
|
||
|
|
--store-backend index --space "${space_a}" \
|
||
|
|
--fed-enable --fed-transport stub \
|
||
|
|
>"${log}" 2>&1 &
|
||
|
|
pid=$!
|
||
|
|
|
||
|
|
http_get() {
|
||
|
|
local sock_path="$1"
|
||
|
|
local path="$2"
|
||
|
|
shift 2
|
||
|
|
if [[ "${USE_HTTP_HELPER}" -eq 1 ]]; then
|
||
|
|
"${HTTP_HELPER}" --sock "${sock_path}" --method GET --path "${path}" "$@"
|
||
|
|
else
|
||
|
|
curl --silent --show-error --fail \
|
||
|
|
--unix-socket "${sock_path}" \
|
||
|
|
"$@" \
|
||
|
|
"http://localhost${path}"
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
http_get_allow() {
|
||
|
|
local sock_path="$1"
|
||
|
|
local path="$2"
|
||
|
|
shift 2
|
||
|
|
if [[ "${USE_HTTP_HELPER}" -eq 1 ]]; then
|
||
|
|
"${HTTP_HELPER}" --sock "${sock_path}" --method GET --path "${path}" \
|
||
|
|
--allow-status "$@"
|
||
|
|
else
|
||
|
|
curl --silent --show-error \
|
||
|
|
--unix-socket "${sock_path}" \
|
||
|
|
"$@" \
|
||
|
|
"http://localhost${path}"
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
http_post() {
|
||
|
|
local sock_path="$1"
|
||
|
|
local path="$2"
|
||
|
|
local data="$3"
|
||
|
|
shift 3
|
||
|
|
if [[ "${USE_HTTP_HELPER}" -eq 1 ]]; then
|
||
|
|
"${HTTP_HELPER}" --sock "${sock_path}" --method POST --path "${path}" \
|
||
|
|
--data "${data}" \
|
||
|
|
"$@"
|
||
|
|
else
|
||
|
|
curl --silent --show-error --fail \
|
||
|
|
--unix-socket "${sock_path}" \
|
||
|
|
"$@" \
|
||
|
|
--data-binary "${data}" \
|
||
|
|
"http://localhost${path}"
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
http_post_allow() {
|
||
|
|
local sock_path="$1"
|
||
|
|
local path="$2"
|
||
|
|
local data="$3"
|
||
|
|
shift 3
|
||
|
|
if [[ "${USE_HTTP_HELPER}" -eq 1 ]]; then
|
||
|
|
"${HTTP_HELPER}" --sock "${sock_path}" --method POST --path "${path}" \
|
||
|
|
--allow-status --data "${data}" \
|
||
|
|
"$@"
|
||
|
|
else
|
||
|
|
curl --silent --show-error \
|
||
|
|
--unix-socket "${sock_path}" \
|
||
|
|
"$@" \
|
||
|
|
--data-binary "${data}" \
|
||
|
|
"http://localhost${path}"
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
http_status() {
|
||
|
|
local sock_path="$1"
|
||
|
|
local method="$2"
|
||
|
|
local path="$3"
|
||
|
|
shift 3
|
||
|
|
if [[ "${USE_HTTP_HELPER}" -eq 1 ]]; then
|
||
|
|
echo ""
|
||
|
|
return 0
|
||
|
|
fi
|
||
|
|
curl --silent --show-error --output /dev/null --write-out '%{http_code}' \
|
||
|
|
--unix-socket "${sock_path}" \
|
||
|
|
-X "${method}" \
|
||
|
|
"$@" \
|
||
|
|
"http://localhost${path}"
|
||
|
|
}
|
||
|
|
|
||
|
|
wait_for_ready() {
|
||
|
|
local sock_path="$1"
|
||
|
|
local pid_val="$2"
|
||
|
|
local log_path="$3"
|
||
|
|
local i
|
||
|
|
for i in $(seq 1 100); do
|
||
|
|
if ! kill -0 "${pid_val}" >/dev/null 2>&1; then
|
||
|
|
if [[ -f "${log_path}" ]] && grep -q "bind: Operation not permitted" "${log_path}"; then
|
||
|
|
echo "skip: bind not permitted for unix socket" >&2
|
||
|
|
exit 77
|
||
|
|
fi
|
||
|
|
if [[ -f "${log_path}" ]]; then
|
||
|
|
cat "${log_path}" >&2
|
||
|
|
fi
|
||
|
|
return 1
|
||
|
|
fi
|
||
|
|
if [[ -S "${sock_path}" ]]; then
|
||
|
|
if http_get "${sock_path}" "/v1/meta" >/dev/null 2>&1; then
|
||
|
|
return 0
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
sleep 0.1
|
||
|
|
done
|
||
|
|
return 1
|
||
|
|
}
|
||
|
|
|
||
|
|
if ! wait_for_ready "${sock}" "${pid}" "${log}"; then
|
||
|
|
die "daemon not ready"
|
||
|
|
fi
|
||
|
|
|
||
|
|
payload="fed-ingest"
|
||
|
|
ref="$(
|
||
|
|
printf '%s' "${payload}" | "${ASL_BIN}" put --root "${root_ref}" \
|
||
|
|
--input - --input-format raw --ref-format hex \
|
||
|
|
| tail -n 1 | tr -d '\r\n'
|
||
|
|
)"
|
||
|
|
|
||
|
|
ingest_resp="$(
|
||
|
|
http_post "${sock}" "/v1/fed/ingest?record_type=artifact&ref=${ref}" \
|
||
|
|
"${payload}" \
|
||
|
|
--header "Content-Type: application/octet-stream" \
|
||
|
|
--header "X-Amduat-Space: ${space_a}"
|
||
|
|
)" || {
|
||
|
|
die "ingest artifact failed"
|
||
|
|
}
|
||
|
|
|
||
|
|
status="$(
|
||
|
|
printf '%s' "${ingest_resp}" \
|
||
|
|
| tr -d '\r\n' \
|
||
|
|
| awk 'match($0, /"status":"[^"]+"/) {print substr($0, RSTART+10, RLENGTH-11)}'
|
||
|
|
)"
|
||
|
|
applied="$(
|
||
|
|
printf '%s' "${ingest_resp}" \
|
||
|
|
| tr -d '\r\n' \
|
||
|
|
| awk 'match($0, /"applied":[^,}]+/) {print substr($0, RSTART+10, RLENGTH-10)}'
|
||
|
|
)"
|
||
|
|
if [[ "${status}" != "ok" || "${applied}" != "true" ]]; then
|
||
|
|
die "unexpected ingest response: ${ingest_resp}"
|
||
|
|
fi
|
||
|
|
|
||
|
|
fetched="$(http_get "${sock}" "/v1/artifacts/${ref}")"
|
||
|
|
if [[ "${fetched}" != "${payload}" ]]; then
|
||
|
|
die "artifact fetch mismatch"
|
||
|
|
fi
|
||
|
|
|
||
|
|
ingest_again="$(
|
||
|
|
http_post "${sock}" "/v1/fed/ingest?record_type=artifact&ref=${ref}" \
|
||
|
|
"${payload}" \
|
||
|
|
--header "Content-Type: application/octet-stream" \
|
||
|
|
--header "X-Amduat-Space: ${space_a}"
|
||
|
|
)"
|
||
|
|
status="$(
|
||
|
|
printf '%s' "${ingest_again}" \
|
||
|
|
| tr -d '\r\n' \
|
||
|
|
| awk 'match($0, /"status":"[^"]+"/) {print substr($0, RSTART+10, RLENGTH-11)}'
|
||
|
|
)"
|
||
|
|
applied="$(
|
||
|
|
printf '%s' "${ingest_again}" \
|
||
|
|
| tr -d '\r\n' \
|
||
|
|
| awk 'match($0, /"applied":[^,}]+/) {print substr($0, RSTART+10, RLENGTH-10)}'
|
||
|
|
)"
|
||
|
|
if [[ "${status}" != "already_present" && "${applied}" != "false" ]]; then
|
||
|
|
die "unexpected re-ingest response: ${ingest_again}"
|
||
|
|
fi
|
||
|
|
|
||
|
|
tombstone_resp="$(
|
||
|
|
http_post "${sock}" "/v1/fed/ingest" \
|
||
|
|
"{\"record_type\":\"tombstone\",\"ref\":\"${ref}\"}" \
|
||
|
|
--header "Content-Type: application/json" \
|
||
|
|
--header "X-Amduat-Space: ${space_a}"
|
||
|
|
)"
|
||
|
|
status="$(
|
||
|
|
printf '%s' "${tombstone_resp}" \
|
||
|
|
| tr -d '\r\n' \
|
||
|
|
| awk 'match($0, /"status":"[^"]+"/) {print substr($0, RSTART+10, RLENGTH-11)}'
|
||
|
|
)"
|
||
|
|
if [[ "${status}" != "ok" ]]; then
|
||
|
|
die "unexpected tombstone response: ${tombstone_resp}"
|
||
|
|
fi
|
||
|
|
|
||
|
|
http_get_allow "${sock}" "/v1/artifacts/${ref}" >/dev/null 2>&1 || true
|
||
|
|
|
||
|
|
tombstone_again="$(
|
||
|
|
http_post "${sock}" "/v1/fed/ingest" \
|
||
|
|
"{\"record_type\":\"tombstone\",\"ref\":\"${ref}\"}" \
|
||
|
|
--header "Content-Type: application/json" \
|
||
|
|
--header "X-Amduat-Space: ${space_a}"
|
||
|
|
)"
|
||
|
|
status="$(
|
||
|
|
printf '%s' "${tombstone_again}" \
|
||
|
|
| tr -d '\r\n' \
|
||
|
|
| awk 'match($0, /"status":"[^"]+"/) {print substr($0, RSTART+10, RLENGTH-11)}'
|
||
|
|
)"
|
||
|
|
if [[ "${status}" != "ok" && "${status}" != "already_present" ]]; then
|
||
|
|
die "unexpected tombstone repeat response: ${tombstone_again}"
|
||
|
|
fi
|
||
|
|
|
||
|
|
cap_resp="$(
|
||
|
|
http_post "${sock}" "/v1/capabilities" \
|
||
|
|
"{\"kind\":\"pointer_name\",\"target\":{\"name\":\"space/${space_a}/fed/records\"},\"expiry_seconds\":3600}" \
|
||
|
|
--header "Content-Type: application/json" \
|
||
|
|
--header "X-Amduat-Space: ${space_a}"
|
||
|
|
)"
|
||
|
|
cap_token="$(
|
||
|
|
printf '%s' "${cap_resp}" \
|
||
|
|
| tr -d '\r\n' \
|
||
|
|
| awk 'match($0, /"token":"[^"]+"/) {print substr($0, RSTART+9, RLENGTH-10)}'
|
||
|
|
)"
|
||
|
|
if [[ -z "${cap_token}" ]]; then
|
||
|
|
die "failed to mint capability: ${cap_resp}"
|
||
|
|
fi
|
||
|
|
|
||
|
|
wrong_space_resp="$(
|
||
|
|
http_post_allow "${sock}" "/v1/fed/ingest?record_type=artifact&ref=${ref}" \
|
||
|
|
"${payload}" \
|
||
|
|
--header "Content-Type: application/octet-stream" \
|
||
|
|
--header "X-Amduat-Space: ${space_b}" \
|
||
|
|
--header "X-Amduat-Capability: ${cap_token}"
|
||
|
|
)"
|
||
|
|
status="$(
|
||
|
|
printf '%s' "${wrong_space_resp}" \
|
||
|
|
| tr -d '\r\n' \
|
||
|
|
| awk 'match($0, /"status":"[^"]+"/) {print substr($0, RSTART+10, RLENGTH-11)}'
|
||
|
|
)"
|
||
|
|
if [[ "${status}" != "invalid" ]]; then
|
||
|
|
die "unexpected wrong-space response: ${wrong_space_resp}"
|
||
|
|
fi
|
||
|
|
|
||
|
|
if [[ "${USE_HTTP_HELPER}" -eq 0 ]]; then
|
||
|
|
code="$(http_status "${sock}" "POST" "/v1/fed/ingest?record_type=artifact&ref=${ref}" \
|
||
|
|
--header "Content-Type: application/octet-stream" \
|
||
|
|
--header "X-Amduat-Space: ${space_b}" \
|
||
|
|
--header "X-Amduat-Capability: ${cap_token}")"
|
||
|
|
if [[ "${code}" != "403" ]]; then
|
||
|
|
die "expected 403 for wrong-space capability, got ${code}"
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
|
||
|
|
kill "${pid}" >/dev/null 2>&1 || true
|
||
|
|
wait "${pid}" >/dev/null 2>&1 || true
|
||
|
|
pid=""
|
||
|
|
rm -f "${sock}"
|
||
|
|
|
||
|
|
"${AMDUATD_BIN}" --root "${root_store}" --sock "${sock}" \
|
||
|
|
--store-backend index --space "${space_a}" \
|
||
|
|
--fed-transport stub \
|
||
|
|
>"${log}" 2>&1 &
|
||
|
|
pid=$!
|
||
|
|
|
||
|
|
if ! wait_for_ready "${sock}" "${pid}" "${log}"; then
|
||
|
|
die "daemon (fed disabled) not ready"
|
||
|
|
fi
|
||
|
|
|
||
|
|
disabled_resp="$(
|
||
|
|
http_post_allow "${sock}" "/v1/fed/ingest?record_type=artifact&ref=${ref}" \
|
||
|
|
"${payload}" \
|
||
|
|
--header "Content-Type: application/octet-stream" \
|
||
|
|
--header "X-Amduat-Space: ${space_a}"
|
||
|
|
)"
|
||
|
|
status="$(
|
||
|
|
printf '%s' "${disabled_resp}" \
|
||
|
|
| tr -d '\r\n' \
|
||
|
|
| awk 'match($0, /"status":"[^"]+"/) {print substr($0, RSTART+10, RLENGTH-11)}'
|
||
|
|
)"
|
||
|
|
if [[ "${status}" != "error" ]]; then
|
||
|
|
die "unexpected disabled response: ${disabled_resp}"
|
||
|
|
fi
|
||
|
|
|
||
|
|
if [[ "${USE_HTTP_HELPER}" -eq 0 ]]; then
|
||
|
|
code="$(http_status "${sock}" "POST" "/v1/fed/ingest?record_type=artifact&ref=${ref}" \
|
||
|
|
--header "Content-Type: application/octet-stream" \
|
||
|
|
--header "X-Amduat-Space: ${space_a}")"
|
||
|
|
if [[ "${code}" != "503" ]]; then
|
||
|
|
die "expected 503 for federation disabled, got ${code}"
|
||
|
|
fi
|
||
|
|
fi
|
||
|
|
|
||
|
|
echo "ok"
|