Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
340fb1c
issue-876: spec (proposal/design/specs/tasks)
rustyrazorblade Aug 14, 2026
13a50d7
fix(hosts): surface per-host failures from parallel withHosts
rustyrazorblade Aug 14, 2026
5ea9205
refactor(packer): extract install-cassandra-version as a standalone s…
rustyrazorblade Aug 14, 2026
45aff47
fix(cassandra): fail use-cassandra when the version is not installed
rustyrazorblade Aug 14, 2026
88ae803
feat(cassandra): add lazy flag to CassandraVersion
rustyrazorblade Aug 14, 2026
dc92a7b
feat(cassandra): add 'cassandra install <version>' command
rustyrazorblade Aug 14, 2026
ddca9b1
feat(cassandra): show lazily-declared versions in 'cassandra list'
rustyrazorblade Aug 14, 2026
bb9531e
docs: document 'cassandra install' and lazy version declarations
rustyrazorblade Aug 14, 2026
edd5000
fix(packer): check the cassandra.in.sh snippet before downloading, cl…
rustyrazorblade Aug 14, 2026
c028511
refactor(cassandra): make installCommand private
rustyrazorblade Aug 14, 2026
a903925
fix(ssh): keep remote output when a command exits non-zero
rustyrazorblade Aug 14, 2026
c1cf8d1
fix(cassandra): decide already-installed from disk, not the node's ve…
rustyrazorblade Aug 14, 2026
ad33a27
test(packer): unit-test the install script and the bake-time loop
rustyrazorblade Aug 14, 2026
a1b72b7
refactor(hosts): key parallel results by position, not alias
rustyrazorblade Aug 14, 2026
4884b3f
docs(issue-876): correct D2 — yaml presence is not the idempotency si…
rustyrazorblade Aug 14, 2026
6243792
fix(ssh): redact credentials at the boundary, not per call site
rustyrazorblade Aug 14, 2026
acf053d
fix(cassandra): declare by value, never persist a credential, never r…
rustyrazorblade Aug 14, 2026
76cc646
fix(hosts): report every parallel host failure, not just the first
rustyrazorblade Aug 14, 2026
0e3fb29
fix(packer): clean up the working directory on every exit path
rustyrazorblade Aug 14, 2026
18f57a3
ci: run the Cassandra script tests where the repo runs its other bash…
rustyrazorblade Aug 14, 2026
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
7 changes: 5 additions & 2 deletions .github/workflows/packer-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ on:
- ".github/workflows/packer-lint.yml"
workflow_dispatch:

permissions:
contents: read

jobs:
validate-packer:
name: Validate Packer HCL Files
Expand All @@ -22,7 +25,7 @@ jobs:
uses: actions/checkout@v7

- name: Setup Packer
uses: hashicorp/setup-packer@main
uses: hashicorp/setup-packer@v3.1.0
with:
version: "latest"

Expand Down Expand Up @@ -51,7 +54,7 @@ jobs:
uses: actions/checkout@v7

- name: Run ShellCheck
uses: ludeeus/action-shellcheck@master
uses: ludeeus/action-shellcheck@v2.0.0
with:
scandir: "./packer"
severity: error
Expand Down
19 changes: 19 additions & 0 deletions .github/workflows/packer-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,25 @@ jobs:
- name: Run ref-resolution unit tests
run: bash .github/cassandra-image/resolve-ref.test.sh

test-cassandra-install-scripts:
name: Test Cassandra install/use script logic
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v7

- name: Install yq
run: sudo snap install yq

- name: Test install-cassandra-version
run: bash packer/cassandra/bin/install-cassandra-version.test.sh

- name: Test the bake-time install loop
run: bash packer/cassandra/install/install_cassandra.test.sh

- name: Test use-cassandra
run: bash packer/cassandra/bin/use-cassandra.test.sh

test-individual-scripts:
name: Test Individual Scripts
runs-on: ubuntu-latest
Expand Down
32 changes: 32 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,38 @@ tasks.register<Exec>("testCassandraResolveRef") {
commandLine = listOf("bash", ".github/cassandra-image/resolve-ref.test.sh")
}

// Unit-test install-cassandra-version's decisions (argument handling, already-installed no-op,
// source-build guards, JDK selection). sudo/curl/git/dpkg are stubbed; no Docker, no network.
tasks.register<Exec>("testCassandraInstallScript") {
group = "Verification"
description = "Unit-test the install-cassandra-version script"
workingDir = file(".")
commandLine = listOf("bash", "packer/cassandra/bin/install-cassandra-version.test.sh")
}

// Unit-test the bake-time loop's version resolution (yq-driven flags, lazy skip). Sources
// install_cassandra.sh without INSTALL_CASSANDRA, so none of its effects run.
tasks.register<Exec>("testCassandraInstallLoop") {
group = "Verification"
description = "Unit-test the bake-time Cassandra version install loop"
workingDir = file(".")
commandLine = listOf("bash", "packer/cassandra/install/install_cassandra.test.sh")
}

// Unit-test use-cassandra's guard against selecting a version the node never installed.
tasks.register<Exec>("testCassandraUseScript") {
group = "Verification"
description = "Unit-test the use-cassandra script"
workingDir = file(".")
commandLine = listOf("bash", "packer/cassandra/bin/use-cassandra.test.sh")
}

tasks.register("testCassandraScripts") {
group = "Verification"
description = "Run all Cassandra shell script unit tests"
dependsOn("testCassandraInstallScript", "testCassandraInstallLoop", "testCassandraUseScript")
}

tasks.register<Exec>("testPackerScript") {
group = "Verification"
description = "Test a specific packer script (use -Pscript=path/to/script.sh)"
Expand Down
12 changes: 10 additions & 2 deletions docs/development/building-cassandra-refs.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,16 @@ Pin the release's tarball URL in `packer/cassandra/cassandra_versions.yaml`:
python: "3.11.9"
```

`install_cassandra.sh` downloads the URL and expects it to unpack into a single
top-level `*cassandra*` directory, which the `ant artifacts` tarball satisfies.
`install-cassandra-version` downloads the URL and expects it to unpack into a
single top-level `*cassandra*` directory, which the `ant artifacts` tarball
satisfies. The same script runs at AMI bake time and at runtime, so you can skip
the rebuild entirely and install the tarball onto a running cluster:

```bash
easy-db-lab cassandra install my-branch \
--url https://github.com/<owner>/<repo>/releases/download/.../apache-cassandra-<version>-<short-sha>-bin.tar.gz \
--java 17
```

## Image assembly

Expand Down
30 changes: 30 additions & 0 deletions docs/reference/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,33 @@ easy-db-lab cassandra use <version> [options]

Versions: 3.0, 3.11, 4.0, 4.1, 5.0, 5.0-HEAD, 6.0-HEAD, trunk

Fails if the version is not installed on a targeted node — install it first with
`cassandra install`.

### cassandra install

Install an additional Cassandra version onto a running cluster, without rebuilding the AMI.

```bash
easy-db-lab cassandra install <version> [options]
```

| Option | Description | Default |
|--------|-------------|---------|
| `--url` | Tarball URL (`.tar.gz`) or git repository URL (with `--branch`) | from the declared entry |
| `--branch` | Git branch to clone and build, requires `--url` | from the declared entry |
| `--java`, `-j` | Java version to build and run this version with | from the declared entry |
| `--python` | Python version cqlsh runs under | `3.11.9` |
| `--ant-flags` | Extra flags passed to ant when building from a branch | from the declared entry |
| `--hosts` | Filter to specific hosts | all Cassandra nodes |

Each option falls back to the version's `cassandra_versions.yaml` entry when not supplied, so a
declared version needs no options at all. Installing a version that is already installed on a node
is a no-op, and a failure on any node exits non-zero with the reason reported against that node.

See [Configuring Cassandra](../user-guide/installing-cassandra.md#custom-builds) for the full
workflow.

### cassandra write-config

Generate a new configuration patch file.
Expand Down Expand Up @@ -295,6 +322,9 @@ easy-db-lab cassandra list

**Aliases:** `ls`

Versions installed on the node are listed first. A version declared with `lazy: true` that is not
installed on that node is listed too, marked `(declared, not installed)`.

---

## Cassandra Stress Commands
Expand Down
74 changes: 72 additions & 2 deletions docs/user-guide/installing-cassandra.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,59 @@ Configuration is located at `/etc/cassandra-sidecar/cassandra-sidecar.yaml` on e
## Custom Builds

To run a custom Cassandra build (your own fork, a feature branch, or a prebuilt
tarball), add a version entry and rebuild the AMI. easy-db-lab bakes every listed
version into the image — there is no separate build-from-path command.
tarball), you can either install it onto a cluster that is already running, or
bake it into the AMI so every future cluster has it.

### Install onto a running cluster

```bash
easy-db-lab cassandra install <version>
easy-db-lab cassandra use <version>
```

This downloads (or clones and builds) the version on every Cassandra node and
records it in each node's version list, so `cassandra use` works afterward. It
never rebuilds the AMI, so nothing is preserved across a `down`/`up` cycle —
install it again on the new cluster.

Where the install parameters come from:

- **A declared entry** (the default): whatever you declared in your profile's
extras directory, described below. Nothing else to pass.
- **CLI options**, for a genuinely one-off test with no file to edit:

```bash
easy-db-lab cassandra install my-build \
--url https://example.com/apache-cassandra-my-build-bin.tar.gz \
--java 11
```

Or from a branch, built with ant on each node:

```bash
easy-db-lab cassandra install my-build \
--url https://github.com/myuser/cassandra.git \
--branch my-feature-branch \
--java 11 \
--ant-flags "-Duse.jdk11=true"
```

An option you pass overrides the declared entry's value for that field;
anything you leave out falls back to the declared entry. `--python` defaults
to `3.11.9`.

Useful details:

- `--hosts db0,db1` installs on a subset of nodes only.
- Installing a version a node already has is a no-op.
- If a node fails, the command exits non-zero and names the node and the reason;
nodes that succeeded keep their install.
- A branch build runs a full `ant` build on every targeted node, which is
CPU-heavy and takes several minutes. It is safe while Cassandra is running —
the install only writes to `/usr/local/cassandra/<version>` — but it is not
free. Tarball installs are much cheaper.

### Bake into the AMI

You don't edit the repository's `cassandra_versions.yaml`. Instead, drop one or
more YAML files into your profile's extras directory:
Expand Down Expand Up @@ -234,6 +285,25 @@ easy-db-lab build-cassandra
easy-db-lab cassandra use my-build
```

### Declaring a version without baking it

Add `lazy: true` to an entry to declare a version without spending AMI build
time on it:

```yaml
- version: "my-build"
java: "11"
python: "3.11.9"
url: "https://github.com/myuser/cassandra.git"
branch: "my-feature-branch"
lazy: true
```

The AMI build skips the download/clone/build for that entry, but the entry
still ships in the image. `easy-db-lab cassandra list` shows it as
`(declared, not installed)`, and `easy-db-lab cassandra install my-build`
installs it on a running cluster without you re-specifying any of its fields.

## Next Steps

- [Tutorial](tutorial.md) - Complete walkthrough
Expand Down
2 changes: 2 additions & 0 deletions openspec/changes/issue-876/.openspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-08-14
Loading
Loading