Skip to content

feat(go): GitOps diff/export, pipeline-friendly runs, jq output, completion, watch and init - #59

Merged
achoimet merged 12 commits into
mainfrom
feat/go-cli-ergonomics
Sep 25, 2026
Merged

achoimet merged 12 commits into
mainfrom
feat/go-cli-ergonomics

Conversation

@achoimet

@achoimet achoimet commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

Builds on #58 (the Go rewrite) with features for pipelines, GitOps and everyday use. Stacked on spike/go-cli. Merge #58 first; GitHub then retargets this to main.

Pipelines

  • experiment run --wait cancels the run when interrupted. Ctrl-C, or the SIGTERM a CI runner sends when a job is cancelled, now cancels the run on the platform, then the CLI exits with 130 or 143. Before, an aborted pipeline left the attack running. --keep-running-on-interrupt keeps the old behaviour.
  • --timeout 30m cancels a run that takes too long and fails the command.
  • --report steadybit.xml writes a JUnit report with one test case per step: failed steps are failures, errored steps errors, steps that never ran are skipped. A .json file name gives JSON instead.
  • --show-steps prints step progress. The default output is unchanged.
  • GitHub Actions: a Markdown summary of every run is added to $GITHUB_STEP_SUMMARY.
  • -t json|yaml on every listing prints the platform's items instead of the table.
  • A global --jq filters any JSON a command prints, with gh --jq semantics (strings raw). It runs in-process with gojq, so jq isn't needed.
  • action.yml: uses: steadybit/cli@v6 installs the release binary for the runner and checks it against checksums.txt. It is verified on Linux, macOS and Windows runners after each release.
  • README: a GitHub Actions example and a GitLab job with a JUnit report.

GitOps

  • <kind> diff -f files for experiments, schedules, services and service profiles prints a unified diff between each file and the platform. It exits with 2 on drift, 0 in sync, and 1 on errors. Fields the platform fills in with defaults aren't reported, and neither is the key or id of a file matched by externalId or name.
  • apply --dry-run on each kind reports what an apply would create or update.
  • export --team X -d dir writes a team's experiments, schedules and services, plus the custom service profiles they use, one kind per directory. It only writes files and never deletes any.
  • apply -d dir and diff -d dir work on the whole project, in dependency order: profiles, services, experiments, schedules.

Everyday use

  • Dynamic shell completion: experiment keys (the team first, then that team's experiments), team keys, and template, schedule, service and profile ids, each with its name. It is silent and offers nothing without access, with a 5-second limit.
  • --profile <name> on every command.
  • execution watch -i ID | -k KEY follows a run live: redrawn in place on a terminal, one line per change in a pipe. It never cancels anything.
  • experiment init asks for a template, its placeholders, the team and the environment, creates the experiment and writes it to a file. Without a terminal it refuses and points to apply --template.

Also fixed

  • experiment delete was missing from the Go port. It is fixed on feat!: rewrite the CLI in Go, with the client generated from the OpenAPI spec #58 too (be9dc5f), and a test now pins every command of the TypeScript CLI.
  • Flaky tests: a route like /schedules/* also matched /schedules/v2, depending on random map order, and the shared rate limiter paced the test suite. The fake platform now prefers the most specific route and doesn't pace requests.
  • Tables ignore colour codes when sizing columns.

Testing

  • Unit and command tests for everything above (go test ./..., run repeatedly with -count=5). The container e2e suite passes 20/20.
  • Live on dev (team ADM), with resources created for the test and deleted afterwards:
    • experiment init through a real terminal;
    • a real SIGINT during run --wait: the platform showed the run as CANCELED, and the CLI exited with 130;
    • --timeout with --report: the report shows the cancelled run with real durations;
    • a completed run with --report and a GitHub summary;
    • execution watch -k following a run to its end.
  • Read-only checks on dev:
    • diff and --dry-run on real experiments, services and profiles;
    • export --team GITHUB (101 experiments, 5 schedules, 1 service) followed by diff -d: all 107 files match;
    • -t, --jq and completion.

…w-steps

Interrupting `run --wait` (Ctrl-C, or a CI runner's SIGTERM) now cancels
the run it started before exiting with 130 or 143, so an aborted pipeline
no longer leaves an attack running; --keep-running-on-interrupt keeps the
previous behaviour. --timeout cancels a run that takes too long.
--report writes JUnit (or JSON) with a test case per step, and a
Markdown summary goes to $GITHUB_STEP_SUMMARY in GitHub Actions.
Listings print the platform's items, in its order, with -t json or yaml
instead of a table. --jq filters whatever JSON a command prints, strings
raw and values as JSON like gh --jq; jq itself is not needed.
`uses: steadybit/cli@v5` installs the release binary for the runner,
checked against checksums.txt. Released versions are verified on Linux,
macOS and Windows runners after publishing. The README shows the action,
a GitLab job with a JUnit report, and -t/--jq.
…es and profiles

`<kind> diff -f files` prints a unified diff between each file and the
platform and exits with 2 when they differ, so a pipeline can detect
drift. Fields the platform fills in with defaults, and the key or id of a
file matched by externalId or name, are not reported as differences.
`apply --dry-run` reports what an apply would create or update.
`export --team X -d dir` writes a team's experiments, schedules and
services, and the custom profiles they use, one kind per directory.
`apply -d dir` applies them in dependency order (with --dry-run), and
`diff -d dir` reports drift for all of them. Exporting a team from dev
and diffing it straight back reports no differences for 107 files.
--profile <name> uses a configured profile for one command. Shell
completion now offers experiment keys (team first, then its experiments),
team keys, and template, schedule, service and profile ids with their
names; without access it offers nothing and prints nothing.
`execution watch -i ID | -k KEY` follows a run live, redrawn in place on a
terminal and a line per change otherwise; it never cancels the run.
`experiment init` creates an experiment from a template by asking for its
placeholders, team and environment, and writes it to a file.

`experiment delete` had been left out of the Go port; it is back, and a
test now pins every command of the TypeScript CLI.

A timed-out run is reported once the platform has cancelled it, steps cut
short count as errors, and table columns ignore colour codes. The fake
platform prefers the most specific route and no longer paces requests.
# Conflicts:
#	internal/cli/experiment.go
#	internal/experiment/experiment_test.go
Base automatically changed from spike/go-cli to main September 25, 2026 13:33
@achoimet
achoimet merged commit 4004dc1 into main Sep 25, 2026
8 checks passed
@achoimet
achoimet deleted the feat/go-cli-ergonomics branch September 25, 2026 14:02
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 25, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant