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
44 changes: 36 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,40 @@
# CUDly CLI

The CLI discovers cloud commitment recommendations and can purchase AWS Reserved Instances, Savings Plans, and selected Azure and GCP commitments. Amazon RDS and ElastiCache are the tested AWS service paths. Other AWS services, Azure, and GCP support remain experimental.
CUDly is an open source CLI for discovering and purchasing AWS Reserved Instances and Savings Plans in a single command. It is dry-run by default: nothing is purchased until you pass `--purchase`. `configure-azure` and `configure-gcp` bootstrap credentials for the separate [self-hosted platform](https://github.com/LeanerCloud/cloud-commitments-platform); this CLI's own recommend-and-purchase workflow is AWS-only today. See [cloud setup](docs/cli/cloud-setup.md).

It is also built to be driven by an AI agent for the discovery and analysis side: searching recommendations, sizing a plan, filtering by account or region. The purchase step still needs a human to review the numbers before committing money. **`--yes` currently skips the confirmation prompt outright, including for a non-interactive caller** (a script, a CI job, an agent driving the CLI as a subprocess): see [Safety Features](#safety-features) and [#1943](https://github.com/LeanerCloud/cloud-commitments-cli/issues/1943) before wiring `--purchase --yes` into anything unattended.

The CLI depends on the published shared Go modules in [cloud-commitments-go](https://github.com/LeanerCloud/cloud-commitments-go), pinned to fixed versions in `go.mod`. No sibling checkout or parent workspace is needed for local development.

## Key Features

- **Dry-run by default** - `--purchase` is the only opt-in that moves money; a bare invocation only prints results and writes a CSV.
- **Grounded recommendations** - sized from AWS Cost Explorer's own recommendation and coverage data, not a locally-guessed baseline.
- **Multiple AWS services, one interface** - RDS, ElastiCache, EC2, OpenSearch, Redshift, MemoryDB, and Savings Plans through the same command and flags. See [Implementation Status](#implementation-status) for per-service maturity.
- **Coverage control** - purchase a percentage of what's recommended, or of actual historical usage via `--target-coverage`, instead of buying everything a provider suggests in one run.
- **CSV + audit log** - every dry run and every purchase is written to CSV and to a permanent JSONL audit log.

## Safety Features

1. **Dry-run by default** - no purchase without the explicit `--purchase` flag.
2. **Confirmation prompt** - `--purchase` prints a summary of instance count and estimated savings, then prompts for confirmation. `--yes` skips this prompt, including for a non-interactive caller - it is not currently an automation boundary. [#1943](https://github.com/LeanerCloud/cloud-commitments-cli/issues/1943) tracks closing that gap.
3. **Coverage and instance limits** - `--coverage`, `--target-coverage`, and `--max-instances` shape what a dry run recommends before there is anything to confirm.
4. **RDS extended-support filtering** - by default, recommendations for instances running an engine version in AWS Extended Support are excluded, since the surcharge can erase RI savings; pass `--include-extended-support` to include them.
5. **Audit log written per recommendation** - the audit log path is checked for writability before any cloud API call. Each recommendation then gets its own audit record: for a dry run, written as soon as its (local, no-API-call) result is generated; for a real purchase, written after that purchase call returns.
6. **Permanent CSV exports** of every dry run and every purchase.
7. **Duplicate-purchase dedup, with a caveat** - every path (`--services` and `--input-csv`) subtracts commitments purchased in the last 24 hours before sizing a recommendation. `--idempotency-window` doesn't change that fixed 24h lookback yet ([#1262](https://github.com/LeanerCloud/cloud-commitments-cli/issues/1262)), and if the existing-commitments API call itself fails, the run continues un-deduplicated with a warning ([#1941](https://github.com/LeanerCloud/cloud-commitments-cli/issues/1941)).

Full internals: [Purchase Safety](docs/cli/purchase-safety.md).

## Implementation Status

| AWS service | Status |
|---|---|
| RDS, ElastiCache | Production - the tested paths. |
| EC2, OpenSearch, Redshift, MemoryDB, Savings Plans | Experimental - implemented and functional, still accumulating real-world purchase validation. |

Azure and GCP are not part of this CLI's recommend-and-purchase workflow; `configure-azure` and `configure-gcp` only bootstrap credentials for the [self-hosted platform](https://github.com/LeanerCloud/cloud-commitments-platform).

## Build

Use the Go version declared in `go.mod`.
Expand All @@ -25,24 +56,21 @@ Preview RDS recommendations before enabling a purchase:
./cudly --services rds --profile default
```

Use `--purchase` to enable a purchase operation. Use `--yes` to skip its confirmation prompt. A terminal prompt is not an automation boundary. Read the purchase-safety guide before using this mode. The CLI's `--idempotency-window` flag does not prevent duplicate purchases in the CLI path; review the dry-run output and audit log before retrying.

Export a reviewable report when you need to share results:

```bash
./cudly --services rds --profile default --output recommendations.csv
```

All purchase operations can spend money. Check the account, region, quantity, and selected commitment before confirming.
All purchase operations can spend money. Check the account, region, quantity, and selected commitment before confirming - see [Safety Features](#safety-features) for what is and is not enforced today.

## Credentials and provider status

Use the provider's supported credential chain. For AWS, select a profile with `--profile` and validate access before a purchase. Follow the cloud setup guide for Azure and GCP.
Use the AWS SDK's supported credential chain. Select a profile with `--profile` and validate access before a purchase.

- Amazon RDS and ElastiCache are the tested AWS service paths.
- Other AWS service paths can change and are not covered by the same maturity claim.
- Azure and GCP support is experimental and can vary by service and account.
- Recommendation data and purchase APIs can change outside this repository.
- See [Implementation Status](#implementation-status) for per-service maturity.
- `configure-azure` and `configure-gcp` bootstrap credentials for the self-hosted platform, not for this CLI - see [cloud setup](docs/cli/cloud-setup.md).

## Related components

Expand Down
25 changes: 18 additions & 7 deletions docs/cli/purchase-safety.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Purchase Safety

CUDly is designed to be safe by default. Real purchases require multiple explicit opt-ins, and several mechanisms prevent duplicate or unintended buys.
CUDly is designed to be safe by default. Real purchases require an explicit `--purchase` opt-in, and several mechanisms - coverage limits, a duplicate-purchase check, RDS extended-support filtering, and a full audit trail - guard against unintended or repeated buys. See [Duplicate purchase prevention](#duplicate-purchase-prevention---idempotency-window) below for what that check does and does not cover.

## Automation and AI agents

An AI agent (or any other non-interactive caller) can safely drive discovery, sizing, and filtering: that reads recommendations and existing commitments (for example, `--target-coverage`'s coverage lookup and the duplicate check that runs before every purchase), but never purchases anything on its own. Purchasing is different. `--purchase --yes` executes a real purchase from any invocation - a script, a CI job, or an agent running `cudly` as a subprocess included - because `--yes` skips the confirmation prompt before the interactive-terminal check ever runs. There is currently no automation boundary on the purchase path; [#1943](https://github.com/LeanerCloud/cloud-commitments-cli/issues/1943) tracks closing that gap. Until it lands, treat `--purchase --yes` as unattended purchase automation, and keep it out of anything an agent can trigger on its own.

## The purchase decision: --purchase

Expand Down Expand Up @@ -42,8 +46,10 @@ cudly --input-csv recs.csv --purchase

When running in purchase mode (`isDryRun=false`), cudly prints a summary of the total instance count and estimated savings and prompts for confirmation before executing any purchase. Pass `--yes` to skip this prompt in automation.

`--yes` skips the prompt unconditionally - it is not gated on whether the process has a real, interactive terminal. A script, a CI job, or an agent driving `cudly` as a subprocess can pass `--yes` and execute a purchase exactly as a human at a terminal would. Treat `--purchase --yes` as fully unattended purchase automation, not as a convenience for a human who already confirmed elsewhere. See [#1943](https://github.com/LeanerCloud/cloud-commitments-cli/issues/1943) for the tracked work to close this gap.

```bash
# Unattended purchase (use with care):
# Unattended purchase (use with care - see the note above):
cudly --services rds --purchase --yes
```

Expand All @@ -53,7 +59,7 @@ cudly --services rds --purchase --yes
--audit-log string default: ./cudly-audit.jsonl
```

Every recommendation - whether purchased or dry-run - is written as a JSON line to the audit log file before any purchase API call is made. The audit record includes:
Every recommendation - whether purchased or dry-run - gets a JSON line in the audit log file: the audit log *path* is checked for writability before any cloud API call is made (see below), but each record itself is written right after that recommendation's purchase call returns (immediately, for a dry run). The audit record includes:

- Run ID (UUID that groups all purchases in a single invocation)
- Recommendation details (service, region, instance type, count, term, payment)
Expand All @@ -78,12 +84,16 @@ The default path (`./cudly-audit.jsonl`) writes to the current working directory
--idempotency-window string default: 24h
```

This flag is accepted as a Go duration string (e.g. `24h`, `48h`, `1h30m`). The CLI does not validate the string at startup; it stores the raw value but does not yet subtract previously-purchased commitments from new recommendations based on this window. The deduction logic runs in the server-side scheduler path (where the duration IS parsed), not the CLI purchase loop. Passing this flag in CLI invocations has no effect on which recommendations are purchased.
A duplicate check runs before every purchase, on both the `--services` and `--input-csv` paths: it fetches existing commitments and subtracts anything purchased in the last 24 hours from each recommendation's count, so a retried run doesn't buy the same capacity twice.

That 24-hour lookback is fixed. This flag is accepted as a Go duration string (e.g. `24h`, `48h`, `1h30m`) and stored, but its value is never read by the check - passing `--idempotency-window 72h` (or any other value) has no effect on which recommendations are purchased ([#1262](https://github.com/LeanerCloud/cloud-commitments-cli/issues/1262) tracks wiring it in).

If the existing-commitments lookup itself fails (a transient API error), the check is skipped for that batch and the run continues un-deduplicated, with a warning printed to the log rather than the run stopping ([#1941](https://github.com/LeanerCloud/cloud-commitments-cli/issues/1941)). Treat that warning as a signal to check the audit log for the run before trusting its purchase counts.

The audit status value `skipped_covered` (idempotency hit) is defined in the audit record schema for use by the server path and is not emitted by the CLI.
The audit status value `skipped_covered` (idempotency hit) is defined in the audit record schema for use by the server-side scheduler path and is not emitted by this CLI's dedup check.

```bash
# Accepted but currently has no effect on CLI recommendation deduction:
# The dedup check always runs with a fixed 24h lookback; this flag's value is not applied:
cudly --services rds --idempotency-window 72h
```

Expand Down Expand Up @@ -127,4 +137,5 @@ Before any real purchase run:
3. If using `--target-coverage`, verify `--rebuy-window-days` is set appropriately for your RI renewal cadence.
4. Narrow the scope with `--include-regions`, `--include-accounts`, or `--min-savings-pct` before buying across all services.
5. Consider `--max-instances` as a final safety cap for a first run.
6. Note that `--idempotency-window` does not prevent double-buying in the CLI path; use a dry-run review (run without `--purchase`) and audit-log inspection to guard against retried runs.
6. Note that `--idempotency-window`'s value is not applied - dedup always uses a fixed 24h lookback ([#1262](https://github.com/LeanerCloud/cloud-commitments-cli/issues/1262)) - and that a failed existing-commitments lookup lets the run proceed un-deduplicated with a warning ([#1941](https://github.com/LeanerCloud/cloud-commitments-cli/issues/1941)); watch the log for that warning and check the audit log afterward.
7. If an AI agent or other automation drives `cudly`, never pass `--yes` to it directly - have the agent hand off the dry-run recommendation to a human, who runs `--purchase` themselves. See [Automation and AI agents](#automation-and-ai-agents).
Loading