gitea.tea/cmd/deploykeys/create.go
Ross Golder e499d0843c
feat: Add 'tea deploy-keys' command for repository deploy key management
Adds a top-level 'tea deploy-keys' command under ENTITIES, providing
list, create, delete, and detail-by-id operations over the existing
Gitea REST API at /repos/{owner}/{repo}/keys.

- New CmdDeployKeys registered alongside tea webhooks/branches/etc.
- Subcommands: list (with pagination + --fingerprint filter),
  create (<title> --key|--key-file [--read-only|--read-write]),
  delete (<key-id> --confirm|-y), and a default <key-id> detail view.
- The Gitea API has no edit endpoint; 'change' is documented as
  delete+create. No update subcommand is added.
- modules/print/deploy_key.go: DeployKeysList and DeployKeyDetails.
- Unit tests covering command metadata, flag wiring, key-material
  resolution (--key vs --key-file, mutual exclusion, missing file),
  delete confirmation logic, key-id parsing, and prompt formatting.
- README/CHANGELOG/CLI.md updated.

All four Gitea SDK methods used (CreateDeployKey, DeleteDeployKey,
GetDeployKey, ListDeployKeys) are already present in
code.gitea.io/sdk/gitea v0.23.2; no go.mod changes.

Refs #1066.

Signed-off-by: Ross Golder <ross@golder.org>
(cherry picked from commit 384540c91f9703d1e1b37dd6a8e51f502cc5f216)
2026-09-05 09:52:56 +07:00

103 lines
2.6 KiB
Go

// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package deploykeys
import (
stdctx "context"
"errors"
"fmt"
"os"
gitea "gitea.dev/sdk"
"gitea.dev/tea/cmd/flags"
"gitea.dev/tea/modules/context"
"github.com/urfave/cli/v3"
)
// CmdDeployKeysCreate represents a sub command of deploy-keys to create a deploy key
var CmdDeployKeysCreate = cli.Command{
Name: "create",
Aliases: []string{"add", "c"},
Usage: "Create a deploy key",
Description: "Create a deploy key in a repository. To replace an existing key, run 'delete' followed by 'create'.",
ArgsUsage: "<key-title>",
Action: runDeployKeysCreate,
Flags: append([]cli.Flag{
&cli.StringFlag{
Name: "key",
Usage: "inline armored SSH public key",
},
&cli.StringFlag{
Name: "key-file",
Usage: "path to a file containing the armored SSH public key (e.g. ~/.ssh/id_ed25519.pub)",
},
&cli.BoolFlag{
Name: "read-only",
Usage: "restrict the key to read-only access (default)",
Value: true,
},
&cli.BoolFlag{
Name: "read-write",
Usage: "grant the key read/write access",
},
}, flags.AllDefaultFlags...),
}
func runDeployKeysCreate(ctx stdctx.Context, cmd *cli.Command) error {
if cmd.Args().Len() == 0 {
return errors.New("key title is required")
}
c, err := context.InitCommand(cmd)
if err != nil {
return err
}
if err := c.Ensure(context.CtxRequirement{RemoteRepo: true}); err != nil {
return err
}
client := c.Login.Client()
keyMaterial, err := resolveKeyMaterial(cmd.String("key"), cmd.String("key-file"))
if err != nil {
return err
}
readOnly := cmd.Bool("read-only")
if cmd.IsSet("read-write") {
readOnly = !cmd.Bool("read-write")
}
key, _, err := client.CreateDeployKey(ctx, c.Owner, c.Repo, gitea.CreateKeyOption{
Title: cmd.Args().First(),
Key: keyMaterial,
ReadOnly: readOnly,
})
if err != nil {
return err
}
fmt.Printf("Deploy key %d created (title: %s)\n", key.ID, key.Title)
return nil
}
// resolveKeyMaterial returns the SSH public key from --key (inline) or --key-file.
// The two flags are mutually exclusive; exactly one must be supplied.
func resolveKeyMaterial(inline, file string) (string, error) {
switch {
case inline != "" && file != "":
return "", errors.New("--key and --key-file are mutually exclusive")
case inline == "" && file == "":
return "", errors.New("either --key or --key-file is required")
case inline != "":
return inline, nil
default:
data, err := os.ReadFile(file)
if err != nil {
return "", fmt.Errorf("reading key file %q: %w", file, err)
}
return string(data), nil
}
}