Skip to content

Recipes & tips

Worked examples that combine the pieces from the previous pages, followed by a grab-bag of things worth knowing.

Recipes

Switch between environments safely

Set up a profile per instance once, then pin one for a whole terminal session so you can't fire a command at the wrong place:

1
2
3
4
5
6
7
waldur-cli login --profile prod    --api-url https://waldur.example.com  --token ...
waldur-cli login --profile staging --api-url https://staging.example.com --token ...

# in a given terminal:
export WALDUR_PROFILE=staging
waldur-cli whoami            # confirm before doing anything
waldur-cli team customer list

Build a report from a shell loop

--format tsv is made for while read loops — no jq, no header to skip:

1
2
3
waldur-cli team customer list --format tsv | while IFS=$'\t' read -r uuid name abbr state; do
    printf '%-40s %s\n' "$name" "$uuid"
done

For anything structured, --format json piped to jq — or --jmespath to skip jq entirely:

1
2
3
# projects per customer, as CSV
waldur-cli team project list --format json \
  --jmespath '[].[customer_name, name]' | jq -r '.[] | @csv'

Provision a VPC end-to-end

A complete, CLI-only lifecycle for an OpenStack tenant (VPC):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 1. find the tenant offering + its plan + your project
OFFERING=$(waldur-cli marketplace offering list \
  --filter type=OpenStack.Tenant --filter name_exact="Demo OpenStack" \
  --format json --jmespath '[0].url')
PLAN=$(waldur-cli marketplace offering list \
  --filter name_exact="Demo OpenStack" \
  --format json --jmespath '[0].plans[0].url')
PROJECT=$(waldur-cli team project list \
  --filter name_exact="My Project" --format json --jmespath '[0].url')

# 2. provision (waits for the order to complete, prints the resource)
waldur-cli openstack tenant provision --request "$(cat <<JSON
{
  "offering": $OFFERING, "project": $PROJECT, "plan": $PLAN,
  "attributes": {"name": "my-vpc"},
  "limits": {"cores": 8, "ram": 16384, "storage": 102400},
  "accepting_terms_of_service": true
}
JSON
)" --format json --jmespath '{name: name, tenant: resource_uuid, mrid: uuid}'

# 3. later, tear it down by its marketplace_resource_uuid (the `mrid`/`uuid` above)
waldur-cli openstack tenant terminate <marketplace_resource_uuid>

Instances need a few things a VPC doesn't

An instance order references a flavor, an image, and a subnet, which live inside a tenant. Look the first two up with openstack flavor list / openstack image list (see below); the CLI can also list/get the tenant's networks, subnets, and security groups, but it doesn't expose network creation — provision a tenant without the skip_creation_of_default_* attributes to get a default network + subnet you can point an instance at.

Pick a flavor and image for an instance

Flavors and images are read-only catalog data scoped to a tenant. The flavor filters accept range comparisons, so "smallest flavor that meets my requirements" is one call — no client-side filtering needed:

1
2
3
4
5
6
7
# smallest flavor with at least 4 cores and 8 GB RAM, in this tenant
waldur-cli openstack flavor list \
  --filter tenant_uuid=<tenant-uuid> \
  --filter cores__gte=4 --filter ram__gte=8192 \
  --order cores --limit 1

waldur-cli openstack image list --filter tenant_uuid=<tenant-uuid>

ram/disk are in MB. Each also takes __lte (and an exact cores=/ram=/disk=), so you can bracket a range from both ends. Feed the chosen rows' url fields straight into an instance provision body:

1
2
3
4
FLAVOR=$(waldur-cli openstack flavor list --filter name_exact=m1.medium \
  --format json --jmespath '[0].url')
IMAGE=$(waldur-cli openstack image list --filter name_exact="Ubuntu 24.04" \
  --format json --jmespath '[0].url')

SSH into an instance

There's no dedicated ssh command — Waldur doesn't broker a tunnel or manage private keys (it only stores the public key's name/fingerprint), so there's nothing for a wrapper to add over the system ssh binary beyond looking up the address. That's one --jmespath away:

1
ssh ubuntu@$(waldur-cli openstack instance get <uuid> --jmespath 'external_ips[0]')

Swap the username for whatever the instance's image actually uses (ubuntu, centos, cloud-user, ...) — the API doesn't expose it. If the instance has no floating IP (no public address, or you're not on the same network as its internal_ips), there's no way in via SSH at all; waldur-cli openstack instance get <uuid> --web opens its HomePort page, which may offer a browser-based console instead.

Clean up in bulk from a filtered list

delete and every bodyless action verb read UUIDs from stdin when none are given as arguments, and list --format ndjson emits one JSON object per line -- so filtering and acting compose directly, no jq -r .uuid in between:

1
2
3
4
5
6
7
# Delete every errored volume
waldur-cli openstack volume list --format ndjson --filter state=ERRED \
  | waldur-cli openstack volume delete

# Stop every running instance in a project
waldur-cli openstack instance list --format ndjson --filter state=OK --project <uuid> \
  | waldur-cli openstack instance stop

Each UUID is attempted independently -- one failure is reported to stderr and the rest of the batch still runs, with the command only exiting non-zero afterward if something failed. Sanity-check first with --dry-run (add it to the second command in the pipeline) or by piping through --jmespath for a quick count before committing to the real thing.

Feed live inventory to an LLM

Minimise tokens: fetch only the fields that matter, and render as TOON.

1
2
3
waldur-cli openstack instance list \
  --fields uuid,name,state,project_name \
  --format toon

Tips & tricks

  • --generate-skeleton is the fastest way to learn a request body. Before writing any create/update/provision JSON, generate the skeleton — it lists every writable field with a typed placeholder, straight from the live schema.

  • Filter on the server, shape on the client. --filter cuts bytes over the wire; --jmespath restructures what's left. Reach for --filter first (it's cheaper), then --jmespath for the exact shape.

  • --fields speeds up big lists. For a resource with large objects, --fields uuid,name can dramatically cut transfer time — the server sends only what you ask for.

  • --format ndjson for big lists. It streams -- printing each page as it arrives -- so a large list starts producing output immediately instead of going quiet until every page is fetched. Pipes straight into jq -c, a shell while read loop, or an agent processing results incrementally.

  • --filter query=<text> is full-text search. On resources that support it (customers, projects, users, …), the query filter searches across fields, unlike the exact-match field filters.

  • schema emits an OpenAPI-for-the-CLI. If you're building an LLM agent that drives waldur-cli, parsing --help text is fragile. waldur-cli schema outputs a complete JSON description of the command surface — paths, parameters, typed filter keys, and request skeletons — that frameworks can directly ingest as a tool specification. Use --compact if you have a tight context budget and only need paths and descriptions.

  • api is the escape hatch for anything not wired up as a typed command yet. waldur-cli api <METHOD> <PATH> calls any endpoint directly, using the same --api-url/--token/--profile credentials as everything else — same transport (retries, --http-timeout, --debug tracing), no schema validation:

1
2
3
waldur-cli api GET /api/customers/ --jmespath '[].uuid'
waldur-cli api POST /api/some-endpoint/ --request '{"key": "value"}'
waldur-cli api DELETE /api/customers/<uuid>/ --dry-run

Useful for a Waldur endpoint the CLI hasn't generated a command for yet, or quick one-off debugging — a malformed --request only fails server-side, the same as curl would. For anything the CLI does have a typed command for, prefer that instead: it validates the request locally and gets --fields/--filter/--order/table output for free.

  • whoami before anything destructive. One command confirms which instance and identity your credentials currently resolve to — cheap insurance before a delete/terminate.

  • --dry-run before a mutation you're unsure of. It validates and prints the exact request (with any defaults filled in) without sending it — see Managing resources. Great for building up a create/provision body iteratively, or confirming a scripted delete targets what you think it does.

  • --debug shows the actual requests. One line per HTTP call (method, URL, status, timing) on stderr, regardless of --format — invaluable for understanding what a command does or diagnosing a server-side rejection. See Troubleshooting.

  • stdout is always clean. Errors and --debug go to stderr; stdout only ever carries successful output. So ... 2>/dev/null or ... > out.json always gives you exactly the result, and a failed command never pollutes a pipe with half a result.

  • Install shell completions. Tab-completion for groups, resources, verbs, and flags makes the three-level command tree far quicker to navigate — see Troubleshooting.

  • Update easily. Run waldur-cli update to seamlessly download and install the latest release from GitHub, avoiding manual un-tarring or re-running installer scripts.

  • --no-wait for fire-and-forget provisioning. Submitting many orders? --no-wait returns each order immediately; marketplace order wait <uuid> --jmespath "state=='done'" (or any resource's own wait) picks up the polling later, from the same script or a different one entirely -- see Waiting on anything, not just orders.