Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions .github/workflows/baseimage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@ on:
paths:
- docker/Dockerfile.ci
- docker/Dockerfile.ci-nightly
- docker/Dockerfile.mongo-rs
- .github/workflows/baseimage.yml
push:
branches:
- main
paths:
- docker/Dockerfile.ci
- docker/Dockerfile.ci-nightly
- docker/Dockerfile.mongo-rs
- .github/workflows/baseimage.yml
workflow_dispatch:

Expand Down Expand Up @@ -106,6 +108,60 @@ jobs:
cache-from: type=gha
cache-to: type=gha,mode=max

#
# The mongo service image, and it is a service image rather than a base:
# nothing builds inside it. It is mongo:8.0 with --replSet on the command
# line, because a workflow `services:` block cannot pass a command and the
# HA cache sync needs an oplog to watch. It rides along here because this
# is where a Dockerfile in this repo gets published, not because it has
# anything to do with the build environment.
#
# Same immutable <date>-<sha> tag as the other two, from the same run, so
# adopting a set of images is one bump rather than three.
#
- name: Build and push the mongo replica-set image
uses: docker/build-push-action@v5
with:
context: .
file: docker/Dockerfile.mongo-rs
push: ${{ github.event_name != 'pull_request' }}
tags: quay.io/seamware/mongo-rs:${{ steps.tags.outputs.date_sha }}
cache-from: type=gha
cache-to: type=gha,mode=max

#
# The property every consumer depends on, asserted here so a consumer does
# not discover it: the image comes up, the set INITIATES, and isMaster then
# reports a setName - which is the exact probe coraine's harness uses to
# decide whether the HA tests are in the run set.
#
- name: Verify the mongo image is an initiable replica set
if: github.event_name != 'pull_request'
run: |
set -eu
img=quay.io/seamware/mongo-rs:${{ steps.tags.outputs.date_sha }}
docker run -d --name mongo-rs-probe "$img"
trap 'docker rm -f mongo-rs-probe >/dev/null 2>&1 || true' EXIT

for i in $(seq 1 30); do
docker exec mongo-rs-probe mongosh --quiet --eval 'db.runCommand({ping:1}).ok' >/dev/null 2>&1 && break
[ "$i" = 30 ] && { echo "mongod never answered"; exit 1; }
sleep 2
done

docker exec mongo-rs-probe mongosh --quiet --eval \
'rs.initiate({_id: "rs0", members: [{_id: 0, host: "localhost:27017"}]})'

for i in $(seq 1 30); do
[ "$(docker exec mongo-rs-probe mongosh --quiet --eval 'db.adminCommand({isMaster:1}).ismaster')" = true ] && break
[ "$i" = 30 ] && { echo "the set never elected a primary"; exit 1; }
sleep 2
done

name=$(docker exec mongo-rs-probe mongosh --quiet --eval 'db.adminCommand({isMaster:1}).setName')
[ "$name" = rs0 ] || { echo "setName is '$name', expected rs0"; exit 1; }
echo "single-node replica set rs0 is PRIMARY"

#
# The Dockerfile's last layer already asserts the environment is complete.
# This repeats it against the built image so the check also covers a
Expand All @@ -132,6 +188,7 @@ jobs:
echo "Published:"
echo "- \`quay.io/seamware/${{ env.IMAGE_NAME }}:${{ steps.tags.outputs.date_sha }}\`"
echo "- \`quay.io/seamware/${{ env.IMAGE_NAME }}-nightly:${{ steps.tags.outputs.date_sha }}\`"
echo "- \`quay.io/seamware/mongo-rs:${{ steps.tags.outputs.date_sha }}\`"
echo
echo "To adopt, in coraine: set the image tag in .github/workflows/{ci,nightly}.yml to \`${{ steps.tags.outputs.date_sha }}\`"
else
Expand Down
40 changes: 40 additions & 0 deletions docker/Dockerfile.mongo-rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Dockerfile.mongo-rs - mongo:8.0, started as a single-node REPLICA SET.
#
# WHAT THIS IS: the stock upstream image with one thing changed - the command.
# No packages, no configuration, no data. If it ever needs a second line of
# content, it is the wrong shape and should be reconsidered.
#
# WHY IT EXISTS: the HA cache sync (`--high-availability mongo`) rides on a
# mongo CHANGE STREAM, and a change stream reads the oplog - which a standalone
# mongod does not have. The harness detects that (corTestParams.sh probes
# isMaster.setName) and leaves ha_cache_sync.test out of the run set on a
# standalone, silently: nothing in the log says the HA paths went unexercised.
#
# Every CI job ran a standalone, so nothing ever entered them - 108 lines and
# 10 functions of coraine, which the published coverage figure then reported as
# untested CODE rather than as an environment we had not stood up.
#
# WHY IT CANNOT BE DONE IN THE WORKFLOW: a GitHub Actions `services:` block
# passes docker-create OPTIONS but not a COMMAND, so `--replSet` cannot be
# appended to mongo:8.0 from a workflow, and the job container has mongosh but
# no mongod of its own to start instead. Two lines here, and every consumer
# keeps using an ordinary service container.
#
# WHAT THIS IMAGE DELIBERATELY DOES NOT DO: initiate the set. A mongod started
# with --replSet and never initiated answers every write with
# NotYetInitialized, so somebody must call rs.initiate() once it is up - but the
# member host has to be the name the CLIENTS use to reach it, and this image
# cannot know that name. It is one mongosh call in the consumer, beside the
# wait-for-database it already does.
#
# ONE MEMBER is the whole point. This is not a test of replication; it is the
# smallest thing that has an oplog.
#
FROM mongo:8.0

#
# The upstream entrypoint prepends `mongod` only when the command starts with a
# dash, so the binary is named explicitly. --bind_ip_all because a replica-set
# member is addressed by the name its clients use, not by localhost.
#
CMD ["mongod", "--replSet", "rs0", "--bind_ip_all"]
Loading