#!/usr/bin/env bash # upload() has one path: ask headquarters to sign a PUT and send the bytes # straight to S3. There is no fallback through headquarters' own API any more, # so what these cases pin down is that the direct route stays unused - including # when a manifest still offers one, and when signing fails. Buffering whole # artifacts in a 512MB instance was the thing being removed, and it would come # back invisibly. # # tests/upload-stub.py plays both ends, so this needs no AWS and no network. set -uo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" TMP="$(mktemp -d)" STUB_PID="" cleanup() { [ -n "$STUB_PID" ] && kill "$STUB_PID" 2>/dev/null rm -rf "$TMP" } trap cleanup EXIT fail=0 check() { # if "${@:2}"; then echo "ok $1"; else echo "FAIL $1"; fail=1; fi } echo ok > "$TMP/mint-mode" : > "$TMP/requests" python3 "$ROOT/tests/upload-stub.py" "$TMP" & STUB_PID=$! for _ in $(seq 50); do [ -s "$TMP/port" ] && break; sleep 0.1; done [ -s "$TMP/port" ] || { echo "FAIL the stub never bound a port"; exit 1; } BASE="http://127.0.0.1:$(cat "$TMP/port")" echo 'a line of client log' > "$TMP/megamek.log" # Only .upload matters here; mq reads the rest of the manifest for other callers. manifest() { # printf '{"matchId":"m1","upload":%s}\n' "$1" > "$TMP/manifest.json" } # Source common.sh in a child shell so each case starts from a clean state. run_upload() { # MANIFEST="$TMP/manifest.json" ARENA_LOG_TAG=test \ bash -c 'set -uo pipefail; . "$0"; upload "$1" "$2"' \ "$ROOT/container/lib/common.sh" "$TMP/megamek.log" "$1" 2>&1 } both_targets="{\"diagnostics\":{\"method\":\"PUT\",\ \"urlTemplate\":\"$BASE/api/{name}\",\"signTemplate\":\"$BASE/sign/{name}\"}}" # --- a manifest still offering a direct target: it is ignored --------------- # This is the deploy order arena has to survive: it ships before headquarters # drops urlTemplate, so both fields arrive and only the signing one is read. manifest "$both_targets" signed_out="$(run_upload diagnostics)"; status=$? check "a signed upload succeeds" test "$status" -eq 0 check "the signing endpoint was asked for this artifact by name" \ grep -qx 'POST /sign/megamek.log' "$TMP/requests" check "the bytes went to the signed destination" \ grep -qx 'PUT /s3/megamek.log' "$TMP/requests" # The whole point of the change: nothing reaches the API's own route, even # though this manifest hands it one. check "and not through the API" \ bash -c "! grep -q '^PUT /api/' '$TMP/requests'" # A minted URL is a bearer credential for that key. common.sh's own rules say # log field names, not values - and curl quotes URLs in its error text, which is # why mint_upload_url drops curl's stderr. check "the signed URL never reaches the log" \ bash -c "! grep -q '/s3/' <<<\"\$1\"" _ "$signed_out" # --- minting fails: the upload fails with it -------------------------------- # It used to fall back to the API. Losing the artifact is the deliberate cost of # not having a slow route that can come back unnoticed - and the failure is in # the log, where reverting to the API was not. echo fail > "$TMP/mint-mode" : > "$TMP/requests" out="$(run_upload diagnostics)"; status=$? check "a minting failure fails the upload" test "$status" -ne 0 check "and says which key it was for" grep -q "could not mint an upload URL for 'diagnostics'" <<<"$out" check "and nothing is sent through the API" \ bash -c "! grep -q '^PUT ' '$TMP/requests'" check "with nothing claiming the file was uploaded" \ bash -c "! grep -q 'uploaded megamek.log' <<<\"\$1\"" _ "$out" # --- a manifest with a direct target and nothing to sign with --------------- # What an older headquarters sends. There is no route for it now, so the file is # kept rather than pushed through the API. 10-manifest.sh refuses such a # manifest at container start; this is upload() holding the line anyway. echo ok > "$TMP/mint-mode" : > "$TMP/requests" manifest "{\"diagnostics\":{\"method\":\"PUT\",\"urlTemplate\":\"$BASE/api/{name}\"}}" out="$(run_upload diagnostics)"; status=$? check "a manifest with no signing target uploads nothing" test "$status" -ne 0 check "and does not try to mint" bash -c "! grep -q '^POST ' '$TMP/requests'" check "and does not fall back to the direct target it was given" \ test ! -s "$TMP/requests" # --- no target at all ------------------------------------------------------- # The state every diagnostics artifact was in before headquarters carried the # key: the file is kept, and upload() reports that it did not send it. : > "$TMP/requests" manifest '{}' out="$(run_upload diagnostics)"; status=$? check "no target at all fails rather than looking sent" test "$status" -ne 0 check "and names the key that had none" \ grep -q "no upload target for 'diagnostics'" <<<"$out" check "having sent nothing anywhere" test ! -s "$TMP/requests" # --- the layout keys, and the ones they replaced ------------------------------ # headquarters names the layout, so an image that knows `raw/` has to work # against a control plane that has not deployed it yet, and vice versa. Each # caller names the key it wants and the key that layout replaced. run_class() { # MANIFEST="$TMP/manifest.json" ARENA_LOG_TAG=test \ bash -c 'set -uo pipefail; . "$0"; upload_class "$1" "$2" "$3" "$4" "$5"' \ "$ROOT/container/lib/common.sh" "$TMP/megamek.log" "$1" "$2" "$3" "$4" 2>&1 } : > "$TMP/requests" manifest "{\"socials\":{\"signTemplate\":\"$BASE/sign/derived/socials/{name}\"},\ \"screenshots\":{\"signTemplate\":\"$BASE/sign/shots/{name}\"}}" out="$(run_class socials share-card.png screenshots share-card.png)" check "a manifest that knows the layout uses it" \ grep -qx 'POST /sign/derived/socials/share-card.png' "$TMP/requests" check "and does not also write the old name" bash -c \ "! grep -qx 'POST /sign/shots/share-card.png' \"\$1/requests\"" _ "$TMP" : > "$TMP/requests" manifest "{\"screenshots\":{\"signTemplate\":\"$BASE/sign/shots/{name}\"}}" out="$(run_class socials share-card.png screenshots share-card.png)" check "a manifest without it falls back" \ grep -qx 'POST /sign/shots/share-card.png' "$TMP/requests" # An artifact with no older home is skipped rather than misfiled: there is no # right place for MegaMek's action log in a layout that predates `raw/`. : > "$TMP/requests" out="$(MANIFEST="$TMP/manifest.json" ARENA_LOG_TAG=test \ bash -c 'set -uo pipefail; . "$0"; upload_class "$1" raw game_actions_0.tsv' \ "$ROOT/container/lib/common.sh" "$TMP/megamek.log" 2>&1)" check "an artifact with no older home is not misfiled" bash -c \ "! grep -q 'POST /sign' \"\$1/requests\"" _ "$TMP" check "and says which key had no target" grep -q "no upload target for 'raw'" <<<"$out" exit "$fail"