feat(ssh-key): add --replace flag to ssh-key add

Description

glab ssh-key add fails with fingerprint_sha256: [has already been taken] when the key is already registered on the account, even when that key has expired. The API has no update endpoint for SSH keys, so re-registering a key meant running glab ssh-key list --show-id, glab ssh-key delete <id>, and then glab ssh-key add again.

This MR adds a --replace flag to glab ssh-key add:

  • It parses the public key being added and computes its SHA256 fingerprint.
  • It goes through all SSH keys on the account (paginated) and looks for a key with the same fingerprint.
  • If it finds one, it deletes that key and then adds the new key with the given title, usage type, and expiration date.
  • If no key matches, it just adds the key.
  • If the add fails after the delete, the error tells you the ID of the key that was deleted, so you're not left wondering where it went.

Without --replace, nothing changes: no extra API calls, same error on duplicates.

Smaller changes along the way:

  • The add command now passes cmd.Context() to the client-go calls through gitlab.WithContext.
  • The flag registrations now go through a local fl variable, which is the repo convention.
glab ssh-key add ~/.ssh/id_ed25519.pub -t "laptop" --expires-at 2027-01-01 --replace

Related to #8577 (closed)

This covers only the --replace part of the issue. Not in this MR, could be follow-ups:

  • Showing the key ID in glab ssh-key list by default. Today you need --show-id.
  • A single rotate command that generates a key, registers it, removes the expired one, and updates Git's user.signingkey.

How has this been tested?

  • New table-driven unit tests in internal/commands/ssh-key/add/add_test.go that use client-go mocks (gitlabtesting). They cover:
    • Replace when a key matches.
    • No matching key.
    • A match on the second page.
    • A key that can't be parsed.
    • List, delete, and add failures.
    • The no-flag path, which makes no list or delete calls.
  • The existing Test_AddSSHKey cases still pass. Their AddSSHKey mock expectations now accept the context request option.
  • go test ./internal/commands/ssh-key/..., go build ./..., golangci-lint run ./internal/commands/ssh-key/..., and go run ./scripts/comment-overlap (no findings in the changed files).
  • make gen-docs, with the regenerated docs/source/ssh-key/add.md committed.

Screenshots (if appropriate):

n/a

Merge request reports

Loading
Loading