feat(controls): add 'kosli update control' command#988
Conversation
Adds the missing "update" of CRUD for controls, wrapping the released
PUT /api/v2/controls/{org}/{identifier} endpoint (body ControlPutInput).
- --name/-n, --description/-d, and repeatable --link (name=url)
- only flags the user set are sent, so omitted fields are left untouched
(matches the server's model_fields_set merge); --link replaces all links
- RequireAtLeastOneOfFlags gives an actionable error when no updatable
flag is provided; not-found and validation errors surface verbatim
- beta-gated (betaCLIAnnotation), consistent with the other control commands
Closes #6096
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Claude finished @pbeckham's task in 2m 26s —— View job Review:
|
| { | ||
| name: "updating a control's description works", | ||
| cmd: "update control update-me --description 'checks something new'" + suite.defaultKosliArguments, | ||
| golden: "control update-me was updated\n", | ||
| }, | ||
| { | ||
| name: "updating a control's links works", | ||
| cmd: "update control update-me --link runbook=https://example.com/runbook" + suite.defaultKosliArguments, | ||
| golden: "control update-me was updated\n", | ||
| }, | ||
| { | ||
| name: "updating multiple fields in one call works", | ||
| cmd: "update control update-me --name 'Another name' --description 'and a description'" + suite.defaultKosliArguments, | ||
| golden: "control update-me was updated\n", | ||
| }, |
There was a problem hiding this comment.
The core value of this command — that only the changed flags land in the PUT body (model_fields_set merge semantics) — is never actually asserted here. These tests hit the real local server and only check the success message, so a regression where, say, an empty description gets sent on a name-only update would pass silently.
Since dry-run pretty-prints the outgoing JSON payload (see internal/requests/requests_test.go:583), you can pin the selective-payload behavior cheaply with a dry-run case, e.g.:
{
name: "dry-run sends only the fields that were set",
cmd: "update control update-me --name 'Only name' --dry-run" + suite.defaultKosliArguments,
golden: "", // assert payload contains "name" but not "description"/"links"
},(You may need goldenRegex/Contains rather than an exact golden depending on the dry-run wrapper output.) Not blocking, but it would guard the one behavior most likely to break.
What
Adds
kosli update control CONTROL-IDENTIFIER— the missing update of CRUD for controls, wrapping the releasedPUT /api/v2/controls/{org}/{identifier}endpoint (bodyControlPutInput). Follows on from the controls CLI in #986.Flags:
--name/-n--description/-d--link(repeatable,name=url)How it matches the API semantics
ControlPutInputhas all-optional fields and the server merges based onmodel_fields_set— only keys present in the JSON body are touched. So:map[string]interface{}and a key is added only when the user set that flag (cmd.Flags().Changed(...)). Omitted flags leave that field unchanged;--linkreplaces the whole links map, matching the server.RequireAtLeastOneOfFlagsgives a clear client-side error before hitting the API.get/archive control.betaCLIAnnotation), consistent with the other control commands.update service-account), so the OpenAPI contract drift test needs no new entry.Testing
make test_integration_single TARGET=TestUpdateControlCommandTestSuite— green. Cases: arg validation, no-flags error, name/description/links/multi-field success, not-found error.make lint— 0 issues;make vet— clean.kosli update control --helprenders the beta notice and flags correctly.Closes kosli-dev/server#6096
🤖 Generated with Claude Code