fix(cli): validate --input-csv Service cells and bound Count by IAmJSD · Pull Request #2281 · LeanerCloud/cloud-commitments-cli · GitHub
Skip to content

fix(cli): validate --input-csv Service cells and bound Count - #2281

Draft
IAmJSD wants to merge 2 commits into
LeanerCloud:mainfrom
IAmJSD:fix/issue-379
Draft

IAmJSD wants to merge 2 commits into
LeanerCloud:mainfrom
IAmJSD:fix/issue-379

Conversation

@IAmJSD

@IAmJSD IAmJSD commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator

Summary

Most of this issue is already fixed on main. #2145 made Term and PaymentOption required and checks each row against the allow-lists validatePaymentAndTerm uses. It also rejects a Count below 1. Two parts of the issue were still open, and this PR fixes them:

  1. Service cell (audit A10-007 in the issue body). parseCSVRecord passed the Service cell straight to common.ServiceType. Values like the --services alias elasticsearch, RDS, rds (trailing space), the savingsplans umbrella or savingsplans-compute all loaded without error. The purchase loop then found no client for them in createServiceClient and skipped the row with Service client not yet implemented. The run bought less than the file asked for and still ended normally.
  2. Count upper bound (from the issue's suggested fix). A per-row Count had no ceiling.

Fix

  • Service is now parsed with the existing parseCSVEnum. The allowed values are the canonical slugs from getAllServices(), which are the names writeMultiServiceCSV writes, so a report from one run still loads as input to the next. A blank or unknown value fails the whole load with the row and column, the same way a bad Term or PaymentOption does. Matching is exact, like the Term/PaymentOption checks from fix(cli): validate CSV headers, encoding and numeric bounds #2145. Flag aliases are rejected on purpose rather than normalized: the report never writes them, and rejecting them keeps this input path strict.
  • parseCSVCount now rejects Count > MaxReasonableInstances (10000). This is the same ceiling validateFlags already puts on --max-instances and --override-count.
  • docs/cli/README.md (--input-csv row) and CHANGELOG.md are updated.

Closes #379

Verification

All of this is unit and fixture evidence on macOS, using temporary CSV files. The end-to-end test runs runToolFromCSV as a dry run against the repo's isolateAWSEnv fake endpoint. No real cloud account was used.

New tests in cmd/multi_service_csv_strict_test.go:

  • TestLoadRecommendationsFromCSV_ServiceValues: blank, whitespace, elasticsearch, rds , RDS, savingsplans, savingsplans-compute and dynamodb each fail with line 3 and column 1 "Service".
  • TestLoadRecommendationsFromCSV_ServiceAccepted: every service in getAllServices() still loads.
  • TestLoadRecommendationsFromCSV_CountUpperBound: Count = 10000 loads and 10001 fails on column 4 "Count".
  • TestRunToolFromCSV_RejectsUnknownService: an elasticsearch row stops runToolFromCSV before any AWS request (awsHits == 0) and before any report is written.

Fail before, at origin/main c134fc7 with only the tests applied:

--- FAIL: TestLoadRecommendationsFromCSV_ServiceValues   (all 8 subtests: "An error is expected but got nil")
--- PASS: TestLoadRecommendationsFromCSV_ServiceAccepted
--- FAIL: TestLoadRecommendationsFromCSV_CountUpperBound
--- FAIL: TestRunToolFromCSV_RejectsUnknownService       (run proceeded and called the fake RDS endpoint)

Pass after, at the PR head: all four pass.

Commands:

go build -o <tmp>/cudly ./cmd
go vet ./... && go vet -tags integration ./...
AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null go test -short -race -count=1 ./...   # ok (cmd, 120s)
golangci-lint v2.10.1 run ./...                     # 0 issues
gocyclo -over 10 -ignore "_test\.go" cmd/           # silent
bash scripts/gosec-hook.sh <changed files>          # 0 issues
markdownlint-cli 0.47.0 CHANGELOG.md docs/cli/README.md   # clean
git-secrets --scan (fresh temp repo, isolated HOME, setup-git-secrets.sh + --register-aws)   # clean
bash scripts/test-git-secrets-allowlist.sh          # 44 PASS, 0 FAIL

The tests ran with AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null because the default profile on the dev machine is invalid. The new tests don't need this (isolateAWSEnv already sets them).

Not covered: no real --input-csv purchase against an AWS account.

Scope notes

  • This is a breaking change for hand-edited CSVs that use --services aliases (elasticsearch, savingsplans-*, savingsplans) or a different case in the Service column. Those rows were already never purchased. They were skipped mid-run, and now the load fails with an error naming the cell.
  • If the maintainer prefers to normalize aliases through parseServices instead of rejecting them, that is a small follow-up. The savingsplans umbrella has no single service, so it would still need to be rejected.
  • Leading and trailing spaces in Service are rejected rather than trimmed, to match the exact matching that Term and PaymentOption use.

Labels: I can't edit labels on this repo. Could a maintainer please copy the issue's triage labels to this PR: bug, triaged, priority/p3, severity/low, urgency/eventually, impact/few, effort/s, type/bug.

🤖 Generated with Claude Code

Term and PaymentOption cells are already checked against allow-lists
(LeanerCloud#2145), but the Service cell was cast straight to a ServiceType. A
--services alias such as "elasticsearch", "RDS", "rds " or the
"savingsplans" umbrella loaded fine, then the purchase loop found no
service client for it and skipped the row with "Service client not yet
implemented", so the run quietly bought less than the file asked for.

Service is now parsed with parseCSVEnum against the canonical slugs from
getAllServices() (what the CSV report writes), failing the load with the
row and column. Count is also capped at MaxReasonableInstances, the
ceiling validateFlags already applies to --max-instances and
--override-count.

Closes LeanerCloud#379

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(cli): --input-csv Term and PaymentOption fields bypass validateFlags — bad values cause confusing API errors

1 participant