Release: dev → main (first public release) #258
@@ -1374,15 +1374,16 @@ jobs:
|
||||
# CID is empty until the app container exists, so this is safe to
|
||||
# arm now and still covers a failure before that point.
|
||||
CID=""
|
||||
CID_ALL=""
|
||||
cleanup() {
|
||||
rc=$?
|
||||
if [ -n "$CID" ]; then
|
||||
for c in $CID $CID_ALL; do
|
||||
# The log ONLY on failure — a boot that never answered must fail
|
||||
# with the reason visible rather than as a bare timeout (rule
|
||||
# 156), while a green run has nothing to say.
|
||||
[ $rc -eq 0 ] || docker logs "$CID" 2>&1 | tail -40
|
||||
docker rm -f "$CID" >/dev/null 2>&1 || true
|
||||
fi
|
||||
[ $rc -eq 0 ] || docker logs "$c" 2>&1 | tail -40
|
||||
docker rm -f "$c" >/dev/null 2>&1 || true
|
||||
done
|
||||
docker network rm "$NET" >/dev/null 2>&1 || true
|
||||
# Preserve the real status, which a trap ending on a successful
|
||||
# `docker rm` would otherwise mask.
|
||||
@@ -1511,6 +1512,51 @@ jobs:
|
||||
docker run --rm --network "$NET" "$CANDIDATE" shell -c \
|
||||
"python3 -c \"import urllib.request; print(urllib.request.urlopen('http://$WEB_IP:8080/api/health', timeout=5).read().decode())\""
|
||||
|
||||
# 4. THE DEFAULT ROLE. Everything above boots `web` explicitly, so
|
||||
# until now nothing had ever started the shape the single-
|
||||
# container layout actually runs — supervisord bringing up
|
||||
# hypercorn plus one celery process per lane. The milestone is
|
||||
# named for that shape and CI had never once executed it.
|
||||
#
|
||||
# Deliberately NO command: this is `docker run <image>` with
|
||||
# nothing after it, so it checks the Dockerfile's CMD and the
|
||||
# entrypoint's default together with the role itself. An adopter
|
||||
# who writes no `command:` gets exactly this.
|
||||
#
|
||||
# `healthcheck_all` is the right assertion and was itself never
|
||||
# run: it passes only when hypercorn answers AND every lane in
|
||||
# the table is answering the broker. A web-only check would go
|
||||
# green with every worker dead, which is the failure mode
|
||||
# consolidation creates.
|
||||
#
|
||||
# Cheap because ml ships at 0 slots and disabled, so nothing
|
||||
# loads a model — the lane answers `inspect` with its consumers
|
||||
# cancelled, which is what "healthy" means for a disabled lane.
|
||||
echo "smoke: the default role boots every lane under supervisord"
|
||||
CID_ALL=$(docker run -d $ENVOPTS "$CANDIDATE")
|
||||
lanes_up=""
|
||||
for i in $(seq 1 60); do
|
||||
if docker exec "$CID_ALL" python -m backend.app.scripts.healthcheck_all; then
|
||||
lanes_up=1
|
||||
break
|
||||
fi
|
||||
if [ "$(docker inspect -f '{{.State.Running}}' "$CID_ALL" 2>/dev/null)" != "true" ]; then
|
||||
echo "smoke: FAILED — the all-role container exited during boot." >&2
|
||||
exit 1
|
||||
fi
|
||||
sleep 3
|
||||
done
|
||||
if [ -z "$lanes_up" ]; then
|
||||
echo "smoke: FAILED — supervisord came up but the composite" >&2
|
||||
echo "smoke: healthcheck never passed: either hypercorn did not" >&2
|
||||
echo "smoke: answer or a lane is not consuming. Its log follows." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "smoke: every lane answered"
|
||||
# Say WHICH processes supervisord is running, so a lane that is
|
||||
# merely restart-looping is visible rather than inferred.
|
||||
docker exec "$CID_ALL" supervisorctl -c "${SUPERVISOR_CONF:-/tmp/supervisord.conf}" status || true
|
||||
|
||||
echo "smoke: all checks passed against $CANDIDATE, with egress blocked"
|
||||
|
||||
# Move the channel tags — the whole point of the gate.
|
||||
|
||||
+12
-1
@@ -127,4 +127,15 @@ ENV FC_VERSION=${FC_VERSION}
|
||||
EXPOSE 8080
|
||||
|
||||
ENTRYPOINT ["./entrypoint.sh"]
|
||||
CMD ["web"]
|
||||
# The DEFAULT is the whole application, not one lane of it.
|
||||
#
|
||||
# `docker run fabledcurator` with no command starts hypercorn plus every
|
||||
# worker lane under supervisord — the shape an adopter wants and the shape the
|
||||
# consolidated stack runs. It was `web`, which meant the single-container
|
||||
# layout only worked if you knew to ask for it by name, and a compose file
|
||||
# that forgot `command:` got a web server with nothing processing its queues:
|
||||
# a gallery that loads, accepts an import, and never finishes one.
|
||||
#
|
||||
# The multi-service stack is unaffected — every service there names its role
|
||||
# explicitly, which is exactly what makes it the multi-service stack.
|
||||
CMD ["all"]
|
||||
|
||||
@@ -59,10 +59,10 @@ services:
|
||||
|
||||
fabledcurator:
|
||||
image: git.fabledsword.com/bvandeusen/fabledcurator:latest
|
||||
# Everything: hypercorn plus one celery process per lane, under
|
||||
# supervisord, whose config is generated from the application's own lane
|
||||
# table so the two cannot disagree.
|
||||
command: ["all"]
|
||||
# No `command:`. Everything — hypercorn plus one celery process per lane
|
||||
# under supervisord — is what the image does by default, and supervisord's
|
||||
# config is generated from the application's own lane table so the two
|
||||
# cannot disagree. `command: ["all"]` still works and means the same thing.
|
||||
# tini as PID 1, in front of supervisord. supervisord reaps its own
|
||||
# children, but a container's PID 1 also inherits orphans from anywhere
|
||||
# below — celery's prefork pool and gallery-dl's subprocesses both make
|
||||
|
||||
+5
-1
@@ -1,7 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROLE="${1:-web}"
|
||||
# Defaults to the whole application (see the Dockerfile's CMD). Kept in step
|
||||
# with that CMD deliberately: they are two doors to the same decision, and a
|
||||
# disagreement between them would only show up as `docker run --entrypoint`
|
||||
# behaving differently from `docker run`.
|
||||
ROLE="${1:-all}"
|
||||
shift || true
|
||||
|
||||
case "$ROLE" in
|
||||
|
||||
Reference in New Issue
Block a user