Skip to content

Commit 260b59a

Browse files
feat: image --delete --keep-latest for release-pipeline tag cleanup, and surface API error messages OD-710 (#52)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent 4dfa3dd commit 260b59a

15 files changed

Lines changed: 1567 additions & 95 deletions

‎.changeset/brave-errors-surface.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@codacy/codacy-cloud-cli": patch
3+
---
4+
5+
Show the error message Codacy actually returned instead of a generic status name. Failures that used to print `Error: Not Found` now print what went wrong — for example `Error: Could not find repository gh/my-org/my-repo (HTTP 404)`, `Error: Bad credentials (HTTP 401)`, or `Error: SBOM tag mismatch: expected 9.9.9, found 3.20 (HTTP 400)`. This affects every command. Where the API sends no explanation, the output is unchanged.
6+
7+
Only a body that plausibly *is* a message is used: not every error response is JSON, so a proxy or load balancer answering with an HTML error page falls back to the status name (`Error: Bad Gateway`) rather than dumping the page into the terminal. The same applies to `image --delete --keep-latest`, whose per-tag failure list reported `Bad Request` for every failure because it formats errors at its own call site.

‎.changeset/lucky-images-listing.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ Add `images` and `image` commands for container images with SBOMs uploaded to an
66

77
`codacy images <provider> <org>` lists images with their latest tag and last upload/generation dates.
88

9-
`codacy image <provider> <org> <image>` lists that image's tags (environment, repository, generated/uploaded/last-analysed dates), shows a single one with `-t, --tag <tag>`, and deletes with `-D, --delete` — the whole image on its own, or just one tag when combined with `--tag`. Under `--output json` a declined confirmation reports itself as `{"deleted": false, "aborted": true}` rather than a prose line, so stdout stays parseable. Deletes confirm first (`-y, --skip-confirmation` bypasses it for CI) and warn that deleting SBOM data temporarily zeroes Container Scanning metrics for the whole organization until the next nightly scan.
9+
`codacy image <provider> <org> <image>` lists that image's tags (environment, repository, generated/uploaded/last-analysed dates), shows a single one with `-t, --tag <tag>`, and deletes with `-D, --delete` — the whole image on its own, or just one tag when combined with `--tag`. Under `--output json` a declined confirmation reports itself as `{"deleted": false, "aborted": true}` rather than a prose line, so stdout stays parseable. Deletes confirm first (`-y, --skip-confirmation` bypasses it for CI).
1010

1111
Both commands require an account API token.

‎.changeset/olive-tags-cleanup.md‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
"@codacy/codacy-cloud-cli": minor
3+
---
4+
5+
Add `codacy image <provider> <org> <image> --delete --keep-latest <n>` to clean up old image tags, keeping the n most recently uploaded and deleting the rest. Intended as the cleanup step of a release pipeline, which runs before the SBOM upload.
6+
7+
`--dry-run` shows exactly which tags would be kept and deleted without deleting anything.
8+
9+
Under `--output json` the command emits one object after every delete has been attempted: `deleted` lists the tags that actually went and `failures` the ones that did not, and the exit code is non-zero when there are any. Deletes run one at a time and continue past failures, so a partial cleanup still frees space; the exit code is non-zero if any tag failed.
10+
11+
Confirmation applies in every output mode, including `--output json` — `-y, --skip-confirmation` is how a pipeline says yes ahead of time, and a declined prompt emits `{"deleted": [], "aborted": true}`. An empty or whitespace-only `--keep-latest` is rejected rather than read as `0`, so `--keep-latest "$KEEP_COUNT"` with the variable unset fails loudly instead of deleting every tag.
12+
13+
Because the organization tag cap counts image-and-tag pairs while `--keep-latest` applies per image, the command warns when keeping n tags across every image in the organization would exceed the default 1,000-tag cap, and says what the cap allows per image instead.

‎AGENTS.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ codacy-cloud-cli/
8585
- Route polling delays through the exported `timers.sleep` so tests can stub it (`vi.spyOn(timers, "sleep").mockResolvedValue()`).
8686
- Prefer that over calling `setTimeout`/`sleep` directly in a command, unless you have a clear reason not to.
8787
- Default cadence is `POLL_INTERVAL_MS` (10s), capped at `MAX_WAIT_MS` (20min).
88-
- **Error handling:** Use `try/catch` with the shared `handleError()` from `src/utils/error.ts`
88+
- **Error handling:** Use `try/catch` with the shared `handleError()` from `src/utils/error.ts`. It prints one red `Error: …` line and exits 1. For an `ApiError` it surfaces the **server's** message, not the generated client's static status name — `catchErrorCodes` fills `err.message` from its own `{400: 'Bad Request', …}` table, so printing that alone discarded the real explanation (`Could not find repository gh/org/repo` became `Error: Not Found`). The extraction is `apiErrorDetails(body)`, also used by `tools --import`'s failure table; the body is sanitized before printing, since error messages echo back user- and repository-supplied values. **A string body is only used when it plausibly is a message** — first line, at most 200 characters, not starting with `<` — because `ApiError.body` is `response.text()` for any non-JSON content type, so a proxy's HTML error page would otherwise be printed in full behind `Error: `; anything longer or markup-shaped falls back to the status name. A command that reports failures itself instead of calling `handleError` (the `--keep-latest` cleanup loop, which continues past a failure so it can list every tag that did not go) uses **`errorReason(err)`** for the same extraction without the `Error: ` prefix or the `(HTTP n)` suffix — never `err.message`, which is the client's static status table
8989
- **API base URL:** `https://app.codacy.com/api/v3` (configured in `src/index.ts` via `OpenAPI.BASE`)
9090
- **Proxy / TLS:** never hand-roll this. Outbound HTTP configuration is delegated to `configureProxy()` from `@codacy/tooling`, wrapped by `configureProxyFromEnv()` in `src/utils/proxy.ts` and called once at the top of `src/index.ts`. It installs a global `undici` dispatcher, so every `fetch` — the generated client and the CVE lookup alike — is covered without touching generated code. Keeping the implementation upstream is what keeps the environment contract identical to the Codacy Analysis CLI; a local reimplementation would drift. If proxy behavior needs to change, change it in `analysis-cli`'s `packages/tooling/src/proxy.ts` and bump the dependency here.
9191
- **Authentication — two token kinds.** Read `SPECS/repository-tokens.md` before touching auth or adding a command.

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ Supported providers: GitHub (`gh`), GitLab (`gl`), Bitbucket (`bb`).
156156
| `patterns [provider] [org] [repo] <tool>` | List patterns for a tool, or bulk enable/disable them |
157157
| `pattern [provider] [org] [repo] <tool> <id>` | Show a pattern, or enable, disable, or set parameters for it |
158158
| `images <provider> <org>` | List Docker images with SBOMs uploaded to an organization |
159-
| `image <provider> <org> <image>` | List an image's tags, show one tag, upload an SBOM, or delete a tag or the whole image |
159+
| `image <provider> <org> <image>` | List an image's tags, show one tag, upload an SBOM, or delete tags (one, all, or all but the newest n) |
160160

161161
Run `codacy <command> --help` for full argument and option details for any command.
162162

0 commit comments

Comments
 (0)