feat(controls): manage controls from the CLI#986
Conversation
Add `kosli create control <identifier> --name <name> [--description]`
which POSTs to /api/v2/controls/{org}, the first slice of CLI parity for
the controls API (#5742).
Also fix 409 handling in the shared HTTP client: 409 is retried by
default (transient lock conflicts), but control creation uses 409 for a
duplicate identifier, a permanent error. Add a per-request
RequestParams.DisableConflictRetry (threaded via request context) so
control-create surfaces the duplicate error immediately instead of
retrying and reporting "giving up after N attempts". Lock-conflict
retries are preserved for all other requests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add `kosli list controls` which GETs /api/v2/controls/{org} and renders
the HAL list response as a table or JSON (--output). Supports pagination
via --page and --page-limit, with an empty-state message and a page
footer (#5742).
Tests cover the populated/empty cases (table and JSON), page-limit
capping, the echoed pagination params, a page beyond the data, and the
positive-integer validation for --page and --page-limit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ontrols' Wire the list controls command to the API's filtering query params: --search (name/identifier substring), --tag (repeatable, 'key' or 'key:value') and --archived (list archived instead of active) (#5742). Add ArchiveControl and TagControl test helpers and cover each filter: search by identifier, tag-only matches, archived excluded by default, and --archived returning archived controls. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add "control"/"controls" to the tag command's accepted resource types
(the server already supports tagging controls via
/api/v2/tags/{org}/control/{id}) and document it with an example.
Tests cover adding, removing and the plural resource-type alias on a
control (#5742).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add `kosli get control <identifier>` which GETs
/api/v2/controls/{org}/{identifier} and renders the control as a table
(identifier, name, description, version, archived, created by/at, tags,
links, referencing policies) or JSON via --output (#5742).
Tests cover argument validation, fetching an existing control as table
and JSON, and the clear error for a non-existing control.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rol' Add `kosli archive control <identifier>` (POST .../archive) and a new `unarchive` command group with `kosli unarchive control <identifier>` (POST .../unarchive) (#5742). Tests cover argument validation, the success messages, the resulting archived/active state (verified via get control), and the clear error when the control does not exist. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…hive Cover the remaining unhappy paths for control commands (#5742): - create control with an invalid identifier surfaces the server's validation error verbatim - tagging a non-existing control gives a clear "does not exist" error - unarchiving a non-existing control gives a clear "does not exist" error Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Guard against drift between the CLI's hand-written request payload structs and the Kosli API by fetching the live OpenAPI schema from the test server and asserting, per registered case, that every json field the CLI sends exists in the schema component and every required schema property is sent by the CLI. Optional schema properties the CLI does not expose are logged, not failed. Seeded with create control (ControlPostInput) and create environment (CreateEnvironmentPutInput) to prove the registry generalises beyond controls; adding an entity is a one-line registry change (#5742). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Claude finished @pbeckham's task in 3m 31s —— View job Code review —
|
Satisfy the errcheck linter by handling the deferred Body.Close return value. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ol table Address PR review on the get control table printer: - sort tags and links keys before printing so output is deterministic (matching tag.go), instead of relying on random map iteration order - guard version, archived and created_by with the same != nil check used for description and created_at, so an explicit null never renders as a Go error verb Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The controls feature is gated server-side behind the is-controls-enabled flag and published as beta in the API schema, so mark the create, list, get, archive and unarchive control commands beta (visible, with the beta banner in --help), consistent with other beta CLI commands. Add a lifecycle test asserting all five stay beta while flag-gated (#5742). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop Hidden and the docHidden annotation from attest decision so it appears in --help and the generated docs, keeping the betaCLIAnnotation so it still shows the beta banner. Update the lifecycle test to assert the command is beta and visible. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- drop the "control is archived/active afterwards" cases from the archive/unarchive tests: verifying persisted state is the server's responsibility, and the existing archiveFlow/archiveEnvironment tests assert only the success message and error cases - add --search, --tag and --archived examples to list controls help - move listControlsResponse up with the other type declarations Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
What
Adds CLI parity with the released controls API so controls can be created, inspected, tagged and archived without the UI. Implements the command surface from the Definition of Ready in kosli-dev/server#5742.
Commands
kosli create control <id> --name <name> [--description]POST /api/v2/controls/{org}kosli list controls [--search] [--tag] [--archived] [--page/--page-limit] [--output]GET /api/v2/controls/{org}kosli get control <id> [--output]GET /api/v2/controls/{org}/{id}kosli archive control <id>POST /api/v2/controls/{org}/{id}/archivekosli unarchive control <id>(newunarchivecommand group)POST /api/v2/controls/{org}/{id}/unarchivekosli tag control <id> --set/--unsetPATCH /api/v2/tags/{org}/control/{id}Notable changes
internal/requests): the shared client retries 409 (transient lock conflicts), which turned a duplicate-identifier 409 on control-create into a confusing "giving up after N attempts". Added a per-requestDisableConflictRetry(threaded via request context) so control-create surfaces the real error immediately; lock-conflict retries are preserved everywhere else.kosli tagnow acceptscontrol/controlsas a resource type (the server already supported it).Testing
Every command has happy- and unhappy-path coverage: argument validation, required flags, duplicate-409, not-found, invalid-identifier (server validation surfaced verbatim), pagination bounds, filter correctness (search/tag/archived), and archived/active state verified via
get. All green against the local server from a clean DB.Out of scope (noted for follow-up)
: map[...]suffix on API error messages is the shared client's formatting for anymessage+errorsresponse — cosmetic, repo-wide, not controls-specific.--link/linkson create control andpolicieson environment are not exposed by the CLI yet; the contract test logs these as coverage gaps (not failures).Refs kosli-dev/server#5742
🤖 Generated with Claude Code