Skip to content

feat(container): publish a Cassandra-backed ExtendDB image - #725

Draft
robinnsc wants to merge 1 commit into
mainfrom
feat/cassandra-image
Draft

robinnsc wants to merge 1 commit into
mainfrom
feat/cassandra-image

Conversation

@robinnsc

@robinnsc robinnsc commented Nov 1, 2026 •

Copy link
Copy Markdown
Collaborator

Stacked on #681 (which is stacked on #666): its diff is against
feat/catalog-migrations-cassandra-mongodb. #681 fixes #726, which this
image's upgrade path and smoke test depend on.

What

Adds extenddb/extenddb-cassandra to the container release pipeline, built,
smoke-tested, scanned, signed, mirrored and promoted the same way as
extenddb-mongodb.

  • Dockerfile.cassandra: Dockerfile.mongodb with --features cassandra,
    SOFTWARE-LICENSE-NOTICES-CASSANDRA.html and its own labels. Same pinned
    bases, apt-get upgrade, UID 10001, setuid bits stripped.
  • docker-compose.cassandra.yml: local reference stack, and what the release
    smoke test runs.
    • One cassandra:4.1 node (pinned digest, from the ECR Public mirror) with
      PasswordAuthenticator and CassandraAuthorizer enabled in
      cassandra.yaml, so init grants the application role only its
      permissions (feat(migrations): catalog migrations with a ledger in every store, for Cassandra and MongoDB #681). No host port.
    • cassandra-auth-init replaces the stock cassandra/cassandra password on
      first start. It hands passwords to cqlsh through 0600 credentials files on
      a tmpfs, never on a command line; refuses cassandra and any password
      cqlsh's configparser would not read back exactly; fails closed if a random
      password is accepted (authenticator not active); and reports a password
      that differs from the one the volume was first started with.
    • The superuser, application-role and admin passwords are required
      ${VAR:?} variables. ExtendDB is published on 127.0.0.1 only.
    • On later starts the bootstrap runs the migrate preview and runs
      migrate --yes only when it exits 3, so up on a running stack whose
      catalog is current succeeds instead of waiting for the running server.
  • ci/smoke-test-cassandra-container.sh: random passwords per run; image
    labels and --version; hardening; no Cassandra, cqlsh or JVM in the image;
    the stock and a random password refused with Bad credentials; the
    application role logs in with its configured password, can read the catalog
    and is refused ALTER ROLE on the superuser (Unauthorized); extenddb.toml
    holds the application password and not the superuser's; a DynamoDB round
    trip; persistence across restart; graceful SIGTERM; and the migrate branch
    after recreating the stack. Every compose up is bounded by
    COMPOSE_UP_TIMEOUT (600 s).
  • Workflows: release-image, promote-image, ghcr-mirror, sign-image and
    dockerhub-description gain extenddb-cassandra.
    • release-image's gate now refuses a tag that has no Dockerfile or smoke
      script for the chosen image, with a message naming the missing file,
      before cargo-about is installed. This replaces the MongoDB-only
      --backend check. For postgres and mongodb every tag gets the same gate
      result as on main. The licence step also takes --backend from env:
      instead of interpolating it into the script.
    • The smoke step dispatches on the image's variable with a case that fails
      closed.
    • The build cache scope is <image>-<arch> instead of <arch>, so the
      three images no longer replace each other's cache index.
    • promote-image validates the image from env: instead of interpolating
      the input into the script.
    • licenses.yml's push trigger also watches
      SOFTWARE-LICENSE-NOTICES-CASSANDRA.html (the PR check already ran it).
  • The "tool is absent" checks in ci/smoke-test-container.sh and
    ci/smoke-test-mongodb-container.sh used ! command -v, which never fails
    under sh -e. They now exit explicitly.
  • Docs: docker/README-cassandra.md (init, serve, certificate, replication
    factor, production, upgrade, reference stack),
    docker/dockerhub-overview-cassandra.md, README.md (which now also says
    SQLite has no production image and links docs/dev-image.md), and
    docs/cassandra-setup.md (a container image section, and a keyspace
    replication procedure that covers the keyspaces init creates at RF 1 and
    system_auth). Both image pages carry the backend's experimental notice,
    recommend CassandraAuthorizer, and say to use a cluster for ExtendDB
    alone under AllowAllAuthorizer, where the application role can read, drop
    and alter everything in the cluster.

Why

1.0 is the first release with a published Cassandra-backed artifact. Until
now the backend could only be built from source, and release-image had no
Cassandra option.

Testing done

On an x86_64 host, with the image built with docker build from this
branch on #681's head b8584d5 (amd64 only):

  • ci/smoke-test-cassandra-container.sh in prebuilt mode passes end to end
    at this commit, including the recreate step's migrate with the rotated
    superuser password, under CassandraAuthorizer.
  • A second docker compose up on a running, current stack exits 0 after 4 s
    (the preview reports up to date).

During review, on this branch before it was stacked on #681 (same image
content apart from #681's code, earlier compose file without the authorizer
and the preview step):

  • cassandra-auth-init against a scratch node: the stock password is
    refused; the first start rotates the password; a restart with a different
    password fails with the down -v message; a trailing non-breaking space and
    an embedded carriage return are refused before rotation; a password with
    ' " \ % $ # ; = [ ] and non-ASCII characters rotates and logs in; no
    credentials file is left on the container's writable layer; down leaves
    no anonymous volume.
  • The README's init, serve and certificate commands (only the image tag,
    the contact point and --network substituted) against a scratch
    password-authenticated node: init succeeds on a named volume, serve
    is healthy under --read-only --cap-drop ALL.
  • ALTER KEYSPACE system_auth ... from the replication procedure runs on
    Cassandra 4.1.12. On a single node it then makes the default superuser's
    logins fail with Cannot achieve consistency level QUORUM, which is why the
    guide caps the factor at the node count.
  • Trivy 0.76.0 with the release gate's flags on the saved image: no fixable
    high or critical findings.
  • ci/smoke-test-container.sh and ci/smoke-test-mongodb-container.sh pass
    against extenddb-postgres:0.1.14 and extenddb-mongodb:0.1.14; their new
    absence checks exit 1 when the tool is planted on PATH, where the old
    ones passed.
  • shellcheck clean on the three smoke scripts and the extracted auth-init
    script; actionlint 1.7.7 reports only the existing custom runner label.

Not tested: the arm64 build and smoke leg (the pinned Cassandra index includes
linux/arm64/v8), and any release workflow run, since no tag contains
Dockerfile.cassandra.

Not closed here

Maintainer setup

Before merge:

  1. Merge feat(migrations): one list of catalog migrations with a ledger in every store, for SQLite and PostgreSQL #666 and feat(migrations): catalog migrations with a ledger in every store, for Cassandra and MongoDB #681 first.
  2. Create the public Docker Hub repository extenddb/extenddb-cassandra, and
    give DOCKERHUB_TOKEN write access if it is scoped per repository. The
    merge push runs the Docker Hub description workflow for the new overview,
    and its validate job fails while the repository does not exist.
  3. Make sure no postgres or mongodb candidate is part-way through
    publish/sign/propagate: with the new cache scope a re-dispatch rebuilds
    cold, gets a different image ID, and publish refuses to overwrite the
    candidate (sign-image and ghcr-mirror can still finish it).

Before the first release-image dispatch for extenddb-cassandra:

  1. Create the ECR Public repository extenddb/extenddb-cassandra, and confirm
    the ExtendDBEcrPublicGitHubPublisher policy covers it.
  2. After the first propagate, link ghcr.io/extenddb/extenddb-cassandra to
    the repository and make it public before promoting.
  3. Decide whether 1.0 changes the backend's "experimental" status; the setup
    guide and both image pages carry it.

Checklist

  • All tests pass (cargo test --workspace) — not run: no Rust source changed
  • Code is formatted / clippy — not applicable to Rust; shellcheck and actionlint as above
  • I have added or updated tests for new functionality — ci/smoke-test-cassandra-container.sh
  • I have updated documentation if behavior changed
  • Breaking changes are noted below (if any)

ADR / RFC: n/a — packaging and release workflow only.

Breaking changes

None for users. For maintainers: the release build cache scope changes from
<arch> to <image>-<arch> (see setup step 3).


By submitting this pull request, I confirm that my contribution is made under
the terms of the Apache License 2.0 and I agree to the Developer Certificate of
Origin (DCO). See CONTRIBUTING.md for details.

Adds extenddb/extenddb-cassandra to the container release pipeline for the
1.0 release, built and released the same way as extenddb-mongodb.

- Dockerfile.cassandra: the MongoDB image's Dockerfile with the cassandra
  feature, its notices file and labels.
- docker-compose.cassandra.yml: local reference stack and what the release
  smoke test runs. One Cassandra 4.1 node with PasswordAuthenticator and
  CassandraAuthorizer, not published on the host; cassandra-auth-init
  replaces the stock superuser password on first start through 0600 cqlsh
  credentials files and fails closed if password authentication is off. All
  three passwords are required variables. Later starts run the migrate
  preview and run migrate --yes only when migrations are pending.
- ci/smoke-test-cassandra-container.sh: image identity and hardening, Cassandra
  authentication and exposure, the application role's credentials and
  grants, a DynamoDB
  round trip, persistence across restart, SIGTERM, and the migrate path after
  recreating the stack.
- release-image, promote-image, ghcr-mirror, sign-image and
  dockerhub-description gain extenddb-cassandra. release-image's gate now
  refuses a tag that has no Dockerfile or smoke script for the chosen image,
  and the build cache is scoped per image and architecture. The licences push
  trigger watches SOFTWARE-LICENSE-NOTICES-CASSANDRA.html.
- The two existing smoke scripts' "tool is absent" checks used `! command -v`,
  which never fails under `sh -e`; they now exit explicitly.
- Docs: docker/README-cassandra.md, the Docker Hub overview, README.md, and
  docs/cassandra-setup.md (container image section; keyspace replication
  procedure including system_auth).
@robinnsc
robinnsc force-pushed the feat/cassandra-image branch from 95f2762 to dfaa726 Compare November 1, 2026 09:44
@robinnsc
robinnsc changed the base branch from main to feat/catalog-migrations-cassandra-mongodb November 1, 2026 09:45
Base automatically changed from feat/catalog-migrations-cassandra-mongodb to main November 2, 2026 22:45
@robinnsc robinnsc added this to the 1.0 milestone Nov 3, 2026
@robinnsc robinnsc added the release-blocker Must ship for its milestone's release label Nov 3, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-blocker Must ship for its milestone's release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

storage-cassandra: migrate, verify and settings hang when the superuser password is not the stock one

1 participant