You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Follow-up to #80. The transient updater (#92-#101) ships rollback code with unit-test coverage (9 shell-level tests in apps/respawner/entrypoint.test.ts using stubbed docker/curl), but the rolled_back / rollback_failed paths have NOT been live-validated. Production smoke from #80 only covered:
success (three in-place self-updates: 0.39.0 → 0.40.0 via manual, 0.40.0 → 0.40.1, 0.40.1 → 0.41.0, 0.41.0 → 0.42.0 via moor_update_apply)
Production smoke CANNOT safely cover the post-replacement failure path because it requires a deliberately broken image that pulls successfully but fails at docker compose up --wait or /api/health.
Scope
Spin up a staging VM (any cheap VPS, or a local docker-compose-on-laptop install) running moor at a version that has moor_update_apply. Build and push two deliberately-broken test images to a registry (or load locally), then validate:
Acceptance
rolled_back happy path. Push an image whose /api/health listener binds only 127.0.0.1:3000 (not 0.0.0.0). Compose's in-container healthcheck (docker-compose.yml:17, uses localhost:3000) passes, so compose up --wait succeeds and moor is replaced. The respawner's cross-container poll (entrypoint.sh:297, hits http://moor:3000/api/health over the Docker bridge) gets connection refused, times out after 60s, and triggers the rollback path. This exercises the post-replacement health-poll branch specifically (vs. the compose-up-fail branch a health 500 image would hit, which is the same rolled_back outcome via a different code path). Run moor_update_apply({ target_digest: <broken digest> }). Expect:
audit row state=rolled_back
error_log mentions "health check did not pass" (from apply phase)
rollback_error null
moor container's resolved .Image (the sha256:... image id) back on the previous value. .Config.Image stays ghcr.io/caiopizzol/moor:latest after rollback because the respawner retags :latest to prev_image_id in place (entrypoint.sh:194); only the resolved image id reveals which content :latest now points to.
drain stays enabled (operator must moor_drain_disable)
rollback_failed (harder to provoke). One way: between the apply pull and the up step, delete the previous image locally. The rollback's docker tag <prev_image_id> :latest will fail because the image is gone. Expect:
state=rollback_failed
error_log carries the original apply failure
rollback_error carries "docker tag failed" with the missing-image error
drain stays enabled
operator can manually docker compose up -d moor to recover with the bad latest tag, OR pull a known-good version explicitly
crashed via timeout. Apply with a deliberately broken image, kill the respawner container mid-flight (e.g., docker kill moor-respawner-<id>). Wait 30+ min. Expect:
audit row state transitions in_progress → crashed via startup/periodic sweep
error_log notes the grace-window expiry
drain stays enabled
Out of scope
Adding rollback validation to CI. The setup cost outweighs the value for a flow that runs on production hosts once or twice a release.
Each of the three acceptance scenarios is recorded in a comment on this issue with the exact docker/moor commands run and the resulting moor_update_audit output. Then close.
Follow-up to #80. The transient updater (#92-#101) ships rollback code with unit-test coverage (9 shell-level tests in apps/respawner/entrypoint.test.ts using stubbed docker/curl), but the rolled_back / rollback_failed paths have NOT been live-validated. Production smoke from #80 only covered:
success(three in-place self-updates: 0.39.0 → 0.40.0 via manual, 0.40.0 → 0.40.1, 0.40.1 → 0.41.0, 0.41.0 → 0.42.0 via moor_update_apply)failedpre-replacement (nonexistent target_digest → pull error captured before container swap)Production smoke CANNOT safely cover the post-replacement failure path because it requires a deliberately broken image that pulls successfully but fails at
docker compose up --waitor/api/health.Scope
Spin up a staging VM (any cheap VPS, or a local docker-compose-on-laptop install) running moor at a version that has
moor_update_apply. Build and push two deliberately-broken test images to a registry (or load locally), then validate:Acceptance
rolled_backhappy path. Push an image whose/api/healthlistener binds only127.0.0.1:3000(not0.0.0.0). Compose's in-container healthcheck (docker-compose.yml:17, useslocalhost:3000) passes, socompose up --waitsucceeds and moor is replaced. The respawner's cross-container poll (entrypoint.sh:297, hitshttp://moor:3000/api/healthover the Docker bridge) gets connection refused, times out after 60s, and triggers the rollback path. This exercises the post-replacement health-poll branch specifically (vs. the compose-up-fail branch ahealth 500image would hit, which is the samerolled_backoutcome via a different code path). Runmoor_update_apply({ target_digest: <broken digest> }). Expect:rolled_back.Image(thesha256:...image id) back on the previous value..Config.Imagestaysghcr.io/caiopizzol/moor:latestafter rollback because the respawner retags:latesttoprev_image_idin place (entrypoint.sh:194); only the resolved image id reveals which content:latestnow points to..update-rollback-<audit_id>.ymlor.update-override-<audit_id>.ymlis generated underworking_diror/app/data(moor_update_apply leaks compose overrides into the next container's config_files label (label pollution + local :latest drift) #105: respawner no longer appends its own-f; retag of:latesttoprev_image_idis the whole rollback mechanism)moor_drain_disable)rollback_failed(harder to provoke). One way: between the apply pull and the up step, delete the previous image locally. The rollback'sdocker tag <prev_image_id> :latestwill fail because the image is gone. Expect:rollback_faileddocker compose up -d moorto recover with the bad latest tag, OR pull a known-good version explicitlycrashedvia timeout. Apply with a deliberately broken image, kill the respawner container mid-flight (e.g.,docker kill moor-respawner-<id>). Wait 30+ min. Expect:Out of scope
Done when
Each of the three acceptance scenarios is recorded in a comment on this issue with the exact docker/moor commands run and the resulting
moor_update_auditoutput. Then close.