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
21 changes: 21 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -177,3 +177,24 @@ jobs:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
command: monitor

# The action downloads from the release, so it can only be checked once one exists.
verify-action:
if: startsWith(github.ref, 'refs/tags/v')
needs: release
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v7
- id: setup
uses: ./
with:
version: ${{ github.ref_name }}
- shell: bash
run: |
steadybit --version
test "$(steadybit --version)" = "${GITHUB_REF_NAME#v}"
test "${{ steps.setup.outputs.version }}" = "${GITHUB_REF_NAME#v}"
22 changes: 21 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,27 @@
before.
- Writing a new experiment's key back into a YAML file no longer rewrites the file: the key
is added at the top and comments, anchors and formatting are kept.
- Shell completion: `steadybit completion bash|zsh|fish|powershell`.
- Shell completion: `steadybit completion bash|zsh|fish|powershell`, which completes
experiment keys, team keys and the ids of templates, schedules, services and profiles
from the platform.
- **Interrupting `experiment run --wait` now cancels the run it started**, before exiting
with 130 (Ctrl-C) or 143 (SIGTERM, as CI runners send when a job is cancelled), so an
aborted pipeline no longer leaves an attack running. `--keep-running-on-interrupt` keeps
the previous behaviour.
- `experiment run --wait` gains `--timeout` (cancel and fail a run that takes too long),
`--report` (a JUnit report with a test case per step, or JSON) and `--show-steps`. In
GitHub Actions a summary of every run is added to the job summary.
- `diff` for experiments, schedules, services and service profiles shows how files differ
from the platform, and exits with 2 when they do; `apply --dry-run` reports what an
apply would change.
- `export --team X -d dir`, `apply -d dir` and `diff -d dir` keep a team's experiments,
schedules, services and custom service profiles in Git as one project.
- Every listing prints the platform's items with `-t json` or `-t yaml`, and `--jq` filters
the JSON any command prints, without jq installed.
- `execution watch` follows a run live; `experiment init` creates an experiment from a
template by asking for its placeholders.
- `--profile <name>` uses a configured profile for one command.
- A GitHub Action, `uses: steadybit/cli@v6`, installs the CLI on a runner.
- Errors about a missing required flag are worded differently (`required flag(s) "key" not
set`); they still exit with 1.

Expand Down
75 changes: 75 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,81 @@ steadybit service-profile list --origin custom
steadybit service-profile apply -f profile.yml
```

## Everyday use

```bash
steadybit experiment init # create an experiment from a template, answering its placeholders
steadybit execution watch -k ADM-1 # follow the latest run of an experiment live
steadybit experiment get -k ADM-1 --profile prod # use another configured profile for one command
```

Shell completion (`steadybit completion --help`) completes experiment keys, team keys and
the ids of templates, schedules, services and service profiles from the platform.

## GitOps

Keep a team's experiments, schedules, services and custom service profiles in Git:

```bash
steadybit export --team ADM -d ./chaos # write them as files
steadybit diff -d ./chaos # what differs from the platform; exits with 2 if anything does
steadybit apply -d ./chaos --dry-run # what an apply would create or update
steadybit apply -d ./chaos # profiles, services, experiments, then schedules
```

Each kind also has its own `diff`, and its `apply` a `--dry-run`, e.g.
`steadybit experiment diff -f ./experiments -R`. Fields the platform fills in with defaults
are not reported as differences.

## In CI

`experiment run --wait` fails the job when a run fails, and a few options make it fit
pipelines:

| Option | Does |
| ----------------------------- | ----------------------------------------------------------------------- |
| `--report steadybit.xml` | A JUnit report, one test case per step; `.json` for JSON |
| `--timeout 30m` | Cancels the run and fails when it has not ended in time |
| `--show-steps` | Prints each step's state as it changes |
| `--keep-running-on-interrupt` | Leaves the run going when the job is cancelled; by default it is stopped |

In GitHub Actions a summary of every run is added to the job summary.

### GitHub Actions

```yaml
- uses: steadybit/cli@v6
- run: steadybit experiment run -f ./experiments -R --yes --report steadybit.xml
env:
STEADYBIT_TOKEN: ${{ secrets.STEADYBIT_TOKEN }}
- uses: mikepenz/action-junit-report@v5
if: always()
with:
report_paths: steadybit.xml
```

### GitLab CI

```yaml
chaos:
image:
name: steadybit/cli:6
entrypoint: ['']
script:
- steadybit experiment run -f ./experiments -R --yes --report steadybit.xml
artifacts:
when: always
reports:
junit: steadybit.xml
```

Every listing prints the platform's items with `-t json` or `-t yaml`, and `--jq` filters
whatever JSON a command prints, without jq installed:

```bash
steadybit service list --team ADM --jq '.[] | "\(.id) \(.name)"'
```

## Container Image

You can also use the cli via our container image:
Expand Down
59 changes: 59 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# SPDX-License-Identifier: MIT
# SPDX-FileCopyrightText: 2026 Steadybit GmbH

name: Set up the Steadybit CLI
description: Installs the steadybit CLI on the runner, so later steps can run experiments.
author: Steadybit GmbH
branding:
icon: activity
color: green

inputs:
version:
description: The version to install, like 5.0.0, or latest.
default: latest

outputs:
version:
description: The installed version.
value: ${{ steps.install.outputs.version }}

runs:
using: composite
steps:
- id: install
shell: bash
env:
VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
case "${RUNNER_OS}" in
Linux) os=linux ;;
macOS) os=darwin ;;
Windows) os=windows ;;
*) echo "::error::Unsupported runner OS ${RUNNER_OS}"; exit 1 ;;
esac
case "${RUNNER_ARCH}" in
X64) arch=amd64 ;;
ARM64) arch=arm64 ;;
*) echo "::error::Unsupported runner architecture ${RUNNER_ARCH}"; exit 1 ;;
esac
ext=tar.gz
[ "$os" = windows ] && ext=zip
archive="steadybit_${os}_${arch}.${ext}"
if [ "$VERSION" = latest ]; then
base="https://github.com/steadybit/cli/releases/latest/download"
else
base="https://github.com/steadybit/cli/releases/download/v${VERSION#v}"
fi
dir="${RUNNER_TOOL_CACHE:-$RUNNER_TEMP}/steadybit-cli/${VERSION}/${arch}"
mkdir -p "$dir"
cd "$dir"
curl -fsSL --retry 3 -o "$archive" "$base/$archive"
curl -fsSL --retry 3 -o checksums.txt "$base/checksums.txt"
# Only the archive's own line; checksums.txt lists every platform.
grep " ${archive}\$" checksums.txt > "${archive}.sha256"
if command -v sha256sum >/dev/null; then sha256sum -c "${archive}.sha256"; else shasum -a 256 -c "${archive}.sha256"; fi
if [ "$ext" = zip ]; then unzip -oq "$archive" steadybit.exe; else tar -xzf "$archive" steadybit; fi
echo "$dir" >> "$GITHUB_PATH"
echo "version=$("$dir/steadybit" --version)" >> "$GITHUB_OUTPUT"
2 changes: 2 additions & 0 deletions cmd/steadybit/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"os"

"github.com/steadybit/cli/internal/cli"
// Imported for its signal handling, which every command needs from the start.
_ "github.com/steadybit/cli/internal/interrupt"
)

func main() {
Expand Down
10 changes: 7 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,34 @@ go 1.26.2
tool github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen

require (
github.com/itchyny/gojq v0.12.19
github.com/mattn/go-runewidth v0.0.30
github.com/oapi-codegen/runtime v1.7.0
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2
github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.9
github.com/stretchr/testify v1.12.1
go.yaml.in/yaml/v3 v3.0.5
golang.org/x/term v0.46.0
)

require (
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
github.com/clipperhouse/stringish v0.1.1 // indirect
github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect
github.com/getkin/kin-openapi v0.142.0 // indirect
github.com/go-openapi/jsonpointer v0.23.1 // indirect
github.com/go-openapi/swag/jsonname v0.26.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/mattn/go-runewidth v0.0.30 // indirect
github.com/itchyny/timefmt-go v0.1.8 // indirect
github.com/oapi-codegen/oapi-codegen/v2 v2.8.0 // indirect
github.com/oasdiff/yaml v0.1.1 // indirect
github.com/oasdiff/yaml3 v0.0.14 // indirect
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
github.com/speakeasy-api/jsonpath v0.6.3 // indirect
github.com/speakeasy-api/openapi v1.24.0 // indirect
github.com/spf13/pflag v1.0.9 // indirect
github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect
golang.org/x/mod v0.38.0 // indirect
golang.org/x/sync v0.22.0 // indirect
Expand Down
16 changes: 12 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvF
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY=
github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4=
github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down Expand Up @@ -40,15 +42,19 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/itchyny/gojq v0.12.19 h1:ttXA0XCLEMoaLOz5lSeFOZ6u6Q3QxmG46vfgI4O0DEs=
github.com/itchyny/gojq v0.12.19/go.mod h1:5galtVPDywX8SPSOrqjGxkBeDhSxEW1gSxoy7tn1iZY=
github.com/itchyny/timefmt-go v0.1.8 h1:1YEo1JvfXeAHKdjelbYr/uCuhkybaHCeTkH8Bo791OI=
github.com/itchyny/timefmt-go v0.1.8/go.mod h1:5E46Q+zj7vbTgWY8o5YkMeYb4I6GeWLFnetPy5oBrAI=
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
Expand Down Expand Up @@ -85,6 +91,8 @@ github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAl
github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
Expand Down
Loading
Loading