Skip to content

feat: manage personal API keys with --personal - #1312

Merged
mbevc1 merged 8 commits into
mainfrom
20261028_feat_user_keys
Oct 29, 2026
Merged

mbevc1 merged 8 commits into
mainfrom
20261028_feat_user_keys

Conversation

@mbevc1

@mbevc1 mbevc1 commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Adds a --personal flag to kosli list/get/create/rotate/delete api-key, so you can manage your own personal API keys from the CLI and not only service account keys. The server endpoints were added in kosli-dev/server#7733. --personal can't be combined with --service-account, one of the two is required, and --org isn't needed for personal keys.

kosli list api-keys --personal --api-token $TOKEN
kosli create api-key --personal -d "CI"
kosli rotate api-key KEY-ID --personal
kosli delete api-key KEY-ID --personal

Related to #925

Checklist

@github-actions github-actions Bot added go Pull requests that update go code feat labels Oct 28, 2026
@claude

claude Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @mbevc1's task in 1m 41s —— View job


PR review: --personal for kosli … api-key

  • Static checks
  • Read the diff and the tests
  • Post the review

Static checks: go build ./..., go vet and go mod tidy all pass, and go.mod/go.sum didn't change.

Verdict: Looks good to merge. I found no bugs. The earlier --personal=false problem is fixed in bd93ed8. apiKeyOwner.validate() now checks the flag's actual value instead of whether it was passed. All five commands share the check, and TestPersonalFalseNeedsServiceAccount covers it on every one of them. Thanks also for correcting me about --service-account "": the empty-flag guard does reject it.

What's done well

  • apiKeyOwner keeps things in one place (cmd/kosli/apiKey.go:17-48). Flag setup, owner/org checks and URL building used to be repeated in five commands. They now live in one type, and each command's run() gets simpler (o.owner.url(keyID, "rotate")).
  • Org isn't required for personal keys, and dry-run tests without --org prove it.
  • The output says "personal" when it should. The delete confirmation prompt and the success message both name personal keys, and the tests check the exact text.
  • The help text explains what's surprising. It covers keys created with another API key: they expire no later than that key, and they're revoked or deleted along with it. It also points to the profile page if you need a key that lasts longer.
  • Tests cover both paths: dry-run URL checks plus httpfake checks of real responses for list, get, create, rotate (several keys) and delete. The empty-flag audit spec is updated too.

Small, optional points

  1. Empty key ID. This was already true before the PR, and --personal makes it a bit more serious. url.JoinPath drops empty parts, so kosli delete api-key "" --personal -y sends DELETE /api/v2/user/api-keys, the collection URL, and get api-key "" sends a list request. The server probably returns 405 or 404, but a quick check in the commands that take key IDs would be safer. For example, in deleteApiKey.go/rotateApiKey.go/getApiKey.go:
    for _, id := range args {
        if strings.TrimSpace(id) == "" {
            return fmt.Errorf("API key ID must not be empty")
        }
    }
    I couldn't run the binary here to confirm the request it actually sends. I'm going by how JoinPath behaves. Fix this →
  2. The docs no longer mark --service-account as required. Because RequireFlags(cmd, "service-account") was removed, the generated docs drop the required marker. The flag descriptions now say "Either this or --personal is required", which covers it. I'm only mentioning it so the docs diff isn't a surprise.
  3. A personal-key confirmation test with --yes. Optional: one more test case where --personal --yes skips the prompt. The service-account path already has one.

Comment thread cmd/kosli/apiKey.go Outdated
The server now exposes /api/v2/user/api-keys for a user's own keys.
`--personal` targets them instead of a service account's keys; it is
mutually exclusive with `--service-account`, one of the two is
required, and `--org` is not needed since personal keys belong to the
user rather than an org.

The owner selection lives in a shared apiKeyOwner so the other api-key
commands can take the same flag.
Reads one of the caller's own keys from /api/v2/user/api-keys/KEY-ID,
using the same --personal/--service-account selection as list.
Posts to /api/v2/user/api-keys. The help notes the server's rules for
keys created with another API key: they are revoked with that key and
expire no later than it.
Posts to /api/v2/user/api-keys/KEY-ID/rotate for each key. The help
notes that a request made with an API key can rotate only that key and
keys created with it, and cannot extend expiry past the calling key's.
Deletes /api/v2/user/api-keys/KEY-ID for each key. The confirmation
prompt and success message name the keys as personal rather than
pointing at a service account. The help notes that deleting a key also
deletes the keys created with it, and that a request made with an API
key can delete only that key and keys created with it.
The api-key commands gained a --personal flag, which the coverage test
requires the audit to cover. The audit's --personal runs drop
--service-account, which the two flags cannot be given together, and
get, rotate and delete act on a personal key created for the run.
The rotate help said extending a personal key's expiry needs the Kosli web UI, but the profile page cannot rotate keys; it can create one, which is the way to get a key that outlives the calling key.
Cobra's one-required flag group counts --personal=false as given, so
`kosli list api-keys --personal=false` passed validation with no owner
and sent its request to /api/v2/service-accounts/<org>/api-keys, the
empty service account segment dropped by url.JoinPath. The server
would read that as a service account named "api-keys".

The owner is now checked in each command's PreRunE instead of by the
flag group, giving one clear error whether neither flag is given or
--personal is false: "--service-account is required unless --personal
is set". The two flags stay mutually exclusive.
@mbevc1
mbevc1 force-pushed the 20261028_feat_user_keys branch from 3d9722b to bd93ed8 Compare October 28, 2026 22:43
@mbevc1
mbevc1 marked this pull request as ready for review October 28, 2026 22:46
@mbevc1
mbevc1 enabled auto-merge (squash) October 28, 2026 22:46
@mbevc1
mbevc1 merged commit 3405c9e into main Oct 29, 2026
27 checks passed
@mbevc1
mbevc1 deleted the 20261028_feat_user_keys branch October 29, 2026 01:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat go Pull requests that update go code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants