[teamai] Push 87 resource(s) from XingfenD

This commit is contained in:
2026-09-10 16:10:45 +08:00
parent 425c9c078a
commit 65c04def51
1314 changed files with 211681 additions and 0 deletions
@@ -0,0 +1,157 @@
# Cobra Commands, Hooks, and Args Validators
## The Run\* lifecycle
Cobra commands have five run hooks. Cobra executes them in this fixed order:
```
PersistentPreRunE → PreRunE → RunE → PostRunE → PersistentPostRunE
```
Each `*E` hook returns `error`. The non-`*E` variants (`PersistentPreRun`, `PreRun`, `Run`, `PostRun`, `PersistentPostRun`) have signature `func(cmd *cobra.Command, args []string)` — they cannot signal failure without `os.Exit` or panic. **Always use the `*E` variants.**
### Which hook to use
| Hook | Scope | When to use |
| --- | --- | --- |
| `PersistentPreRunE` | Parent + all descendants | Config init, auth check, telemetry setup — must run before every subcommand |
| `PreRunE` | This command only | Validation that runs only for this command before `RunE` |
| `RunE` | This command only | Main handler — the primary business logic |
| `PostRunE` | This command only | Cleanup that runs only if `RunE` succeeded |
| `PersistentPostRunE` | Parent + all descendants | Global cleanup (close connections, flush buffers) |
### Inheritance rules
`PersistentPreRunE` defined on the root command runs before every subcommand. But if a child command defines **its own** `PersistentPreRunE`, it **replaces** (does not chain) the parent's hook. Call the parent explicitly if you need both:
```go
var childCmd = &cobra.Command{
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
// call parent's hook first
if err := rootCmd.PersistentPreRunE(cmd, args); err != nil {
return err
}
// child-specific logic
return nil
},
}
```
### Execution stops on first error
If `PersistentPreRunE` returns an error, cobra stops — `RunE` and later hooks never run. Use this for fail-fast auth checks.
## Args validators
Args validators run before `RunE`. Cobra prints a clear error message and exits without calling `RunE` when validation fails.
### Built-in validators
```go
cobra.NoArgs // fails if any positional args provided
cobra.ArbitraryArgs // accepts any number of args (default)
cobra.ExactArgs(n int) // requires exactly n args
cobra.MinimumNArgs(n int) // requires at least n args
cobra.MaximumNArgs(n int) // requires at most n args
cobra.RangeArgs(min, max int) // requires between min and max args
cobra.OnlyValidArgs // all args must be in ValidArgs list
cobra.ExactValidArgs(n int) // exactly n args, all in ValidArgs
```
### Composing validators with MatchAll
```go
var deleteCmd = &cobra.Command{
Use: "delete <resource>",
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
ValidArgs: []string{"pod", "service", "deployment"},
RunE: func(cmd *cobra.Command, args []string) error {
return doDelete(args[0])
},
}
```
### Custom validators
Signature: `func(cmd *cobra.Command, args []string) error`
```go
func validatePositiveInt(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return fmt.Errorf("requires exactly 1 arg, got %d", len(args))
}
n, err := strconv.Atoi(args[0])
if err != nil || n <= 0 {
return fmt.Errorf("argument must be a positive integer, got %q", args[0])
}
return nil
}
var cmd = &cobra.Command{
Args: validatePositiveInt,
RunE: func(cmd *cobra.Command, args []string) error { /* ... */ },
}
```
Combine custom validators with built-in ones using `MatchAll`:
```go
Args: cobra.MatchAll(cobra.MinimumNArgs(1), validateAllPositive),
```
## Command registration and ordering
```go
func init() {
// groups must be registered before AddCommand
rootCmd.AddGroup(&cobra.Group{ID: "core", Title: "Core Commands:"})
rootCmd.AddGroup(&cobra.Group{ID: "management", Title: "Management Commands:"})
serveCmd.GroupID = "core"
migrateCmd.GroupID = "management"
rootCmd.AddCommand(serveCmd, migrateCmd, versionCmd)
}
```
`versionCmd` has no `GroupID` — it appears in the default section.
## Annotations
Cobra supports arbitrary command annotations for framework-level metadata:
```go
var serveCmd = &cobra.Command{
Annotations: map[string]string{
"category": "network",
"requires-auth": "true",
},
}
// read in a middleware hook:
if serveCmd.Annotations["requires-auth"] == "true" {
// enforce auth
}
```
## Hidden and deprecated commands
```go
var internalCmd = &cobra.Command{
Hidden: true, // not shown in help, still executable
}
var oldCmd = &cobra.Command{
Deprecated: "use `newcmd` instead", // shown in help, prints warning on use
}
```
## cobra.CheckErr
`cobra.CheckErr(err)` is a convenience function: if `err != nil`, it prints the error to `cmd.ErrOrStderr()` and calls `os.Exit(1)`. Use it only in `main()` where you want a hard exit — not inside `RunE` where returning the error is preferred.
```go
func main() {
cobra.CheckErr(rootCmd.Execute())
}
```
@@ -0,0 +1,119 @@
# Cobra Shell Completions Reference
Cobra generates shell completion scripts for bash, zsh, fish, and PowerShell automatically. Subcommand names and flag names are completed for free. You add completions for flag values and positional arguments.
## Built-in completion command
Cobra registers a `completion` subcommand automatically:
```bash
myapp completion bash # generate bash script
myapp completion zsh # generate zsh script
myapp completion fish # generate fish script
myapp completion powershell
# Install (example for zsh):
myapp completion zsh > "${fpath[1]}/_myapp"
```
## ShellCompDirective
The `ShellCompDirective` controls shell behavior after your completion function returns:
| Directive | Meaning |
| --- | --- |
| `ShellCompDirectiveDefault` | Fall back to file completion after your results |
| `ShellCompDirectiveNoFileComp` | Disable file completion fallback |
| `ShellCompDirectiveNoSpace` | Don't add a space after the completion |
| `ShellCompDirectiveFilterFileExt(exts)` | Only show files with given extensions |
| `ShellCompDirectiveFilterDirs(dirs)` | Only show directories |
| `ShellCompDirectiveError` | Signal an error (show no completions) |
Combine with bitwise OR: `cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveNoSpace`.
Use `ShellCompDirectiveNoFileComp` whenever your list is exhaustive — it prevents the shell from appending irrelevant files.
## Static arg completions
```go
var getCmd = &cobra.Command{
Use: "get <resource>",
ValidArgs: []string{"pod", "service", "deployment", "configmap"},
Args: cobra.OnlyValidArgs,
RunE: func(cmd *cobra.Command, args []string) error { /* ... */ },
}
```
## Dynamic arg completions
```go
var getCmd = &cobra.Command{
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) > 0 {
// first arg already provided — no more completions
return nil, cobra.ShellCompDirectiveNoFileComp
}
resources, err := listResources(toComplete)
if err != nil {
return nil, cobra.ShellCompDirectiveError
}
return resources, cobra.ShellCompDirectiveNoFileComp
},
}
```
`toComplete` is the prefix the user has typed so far — filter your results by it for responsive completions.
## Flag value completions
```go
func init() {
rootCmd.RegisterFlagCompletionFunc("output", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return []string{"json\tJSON output", "yaml\tYAML output", "table\tTable output"}, cobra.ShellCompDirectiveNoFileComp
})
rootCmd.RegisterFlagCompletionFunc("namespace", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
ns, err := listNamespaces()
if err != nil {
return nil, cobra.ShellCompDirectiveError
}
return ns, cobra.ShellCompDirectiveNoFileComp
})
}
```
Descriptions after `\t` are shown in zsh and fish menus.
## Completion annotations
Mark a flag to complete as a file or directory:
```go
cmd.Flags().String("config", "", "config file")
cmd.MarkFlagFilename("config", "yaml", "yml", "json") // only those extensions
cmd.Flags().String("dir", "", "output directory")
cmd.MarkFlagDirname("dir")
```
## Testing completions
```go
func TestCompletion(t *testing.T) {
rootCmd.SetArgs([]string{"__complete", "get", ""})
buf := new(bytes.Buffer)
rootCmd.SetOut(buf)
rootCmd.Execute()
assert.Contains(t, buf.String(), "pod")
assert.Contains(t, buf.String(), "service")
}
```
`__complete` is cobra's internal completion request verb. Pass the partial args as additional arguments.
## Disabling the completion command
```go
rootCmd.CompletionOptions.DisableDefaultCmd = true // remove the completion subcommand
rootCmd.CompletionOptions.HiddenDefaultCmd = true // keep it but hide from help
```
@@ -0,0 +1,125 @@
# Cobra Flags Reference
Cobra delegates all flag parsing to `github.com/spf13/pflag`. `cobra.Command` exposes two `*pflag.FlagSet`s:
- `cmd.Flags()` — local flags, only available on this command.
- `cmd.PersistentFlags()` — inherited by all subcommands.
## Common flag types
```go
// String
cmd.Flags().String("name", "default", "description")
cmd.Flags().StringP("name", "n", "default", "description") // with shorthand
// With pointer binding (no Lookup needed later)
var name string
cmd.Flags().StringVar(&name, "name", "default", "description")
cmd.Flags().StringVarP(&name, "name", "n", "default", "description")
// Other types follow the same pattern:
cmd.Flags().Int / IntVar / IntVarP
cmd.Flags().Bool / BoolVar / BoolVarP
cmd.Flags().Float64 / Float64Var
cmd.Flags().Duration / DurationVar // parses "1h30m", "500ms"
cmd.Flags().StringSlice / StringSliceVar // comma-separated or repeated flags
cmd.Flags().StringArray / StringArrayVar // repeated flags only (no comma splitting)
cmd.Flags().IntSlice / IntSliceVar
cmd.Flags().StringToString // --label key=value --label k2=v2
```
## StringSlice vs StringArray
| Flag type | Input | Result |
| ------------- | --------------------- | --------------------------------- |
| `StringSlice` | `--tags a,b --tags c` | `["a", "b", "c"]` — commas split |
| `StringArray` | `--tags a,b --tags c` | `["a,b", "c"]` — commas NOT split |
Use `StringArray` when values may legitimately contain commas.
## Flag constraints
```go
// Fail if flag not provided
cmd.MarkFlagRequired("output")
// Fail if both provided
cmd.MarkFlagsMutuallyExclusive("json", "yaml", "table")
// Fail if none provided
cmd.MarkFlagsOneRequired("file", "stdin")
// Require flag only if another flag is set
cmd.MarkFlagsMutuallyExclusive("tls", "no-tls")
```
## Persistent flag patterns
```go
func init() {
// global flags on root
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default: $HOME/.myapp.yaml)")
rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "log level (debug, info, warn, error)")
// bind to viper immediately after defining
viper.BindPFlag("config", rootCmd.PersistentFlags().Lookup("config"))
viper.BindPFlag("log-level", rootCmd.PersistentFlags().Lookup("log-level"))
}
```
## Custom flag value types
Implement `pflag.Value` to parse arbitrary types:
```go
type enumValue struct {
val string
allowed []string
}
func (e *enumValue) String() string { return e.val }
func (e *enumValue) Type() string { return "enum" }
func (e *enumValue) Set(s string) error {
for _, a := range e.allowed {
if s == a {
e.val = s
return nil
}
}
return fmt.Errorf("must be one of %v", e.allowed)
}
var outputFmt = &enumValue{val: "table", allowed: []string{"table", "json", "yaml"}}
cmd.Flags().Var(outputFmt, "output", "output format (table, json, yaml)")
```
## Flag groups (required together)
Mark a set of flags that must all be provided if any one of them is provided:
```go
cmd.Flags().String("tls-cert", "", "TLS certificate file")
cmd.Flags().String("tls-key", "", "TLS key file")
cmd.MarkFlagsRequiredTogether("tls-cert", "tls-key")
```
## Accessing flag values
Prefer pointer binding (`StringVar`, `IntVar`, etc.) for type-safe access. When you need the flag post-parse:
```go
port, err := cmd.Flags().GetInt("port")
name, err := cmd.Flags().GetString("name")
tags, err := cmd.Flags().GetStringSlice("tags")
```
## Flag changed vs default
```go
if cmd.Flags().Changed("port") {
// user explicitly provided --port
// useful when distinguishing "user set 0" from "flag not provided"
}
```
`Changed()` is also how viper knows which flags are explicit overrides — it only promotes a flag to the highest precedence layer if `Changed()` is true.
@@ -0,0 +1,111 @@
# Cobra Documentation and Scaffolding Generators
## Doc generation
Cobra can generate documentation from your command tree in multiple formats. Import the `cobra/doc` sub-package:
```bash
go get github.com/spf13/cobra/doc
```
### Markdown
```go
import "github.com/spf13/cobra/doc"
err := doc.GenMarkdownTree(rootCmd, "/tmp/docs/")
// generates /tmp/docs/myapp.md, /tmp/docs/myapp_serve.md, etc.
// Single command
var buf bytes.Buffer
doc.GenMarkdown(rootCmd, &buf)
```
### Man pages
```go
header := &doc.GenManHeader{
Title: "MYAPP",
Section: "1",
Date: &time.Time{},
Source: "myapp v1.0.0",
Manual: "User Commands",
}
err := doc.GenManTree(rootCmd, header, "/usr/local/share/man/man1/")
```
### YAML
```go
err := doc.GenYamlTree(rootCmd, "/tmp/docs/")
```
### RST (reStructuredText)
```go
err := doc.GenReSTTree(rootCmd, "/tmp/docs/")
```
## cobra-cli scaffolder
`cobra-cli` generates command files and wires them into your project:
```bash
go get -tool github.com/spf13/cobra-cli@latest
# Initialize a new cobra project
go tool cobra-cli init myapp
# Add a subcommand
go tool cobra-cli add serve
go tool cobra-cli add migrate
# Add with a parent other than root
cobra-cli add list --parent serve
```
Generated files follow the standard pattern:
```go
// cmd/serve.go
var serveCmd = &cobra.Command{
Use: "serve",
Short: "A brief description of your command",
RunE: func(cmd *cobra.Command, args []string) error {
return nil
},
}
func init() {
rootCmd.AddCommand(serveCmd)
}
```
`cobra-cli` is optional — many teams write command files by hand following the same pattern.
## Help and usage template customization
Override the default help template:
```go
rootCmd.SetHelpTemplate(`
Usage: {{.UseLine}}
{{if .HasAvailableSubCommands}}
Commands:
{{range .Commands}}{{if .IsAvailableCommand}} {{rpad .Name .NamePadding }} {{.Short}}
{{end}}{{end}}{{end}}
Flags:
{{.LocalFlags.FlagUsages | trimRightSpace}}
`)
```
Override the usage function entirely:
```go
rootCmd.SetUsageFunc(func(cmd *cobra.Command) error {
fmt.Fprintf(cmd.OutOrStdout(), "Custom usage for %s\n", cmd.Name())
return nil
})
```
Common template functions available: `rpad`, `trimRightSpace`, `gt`, `eq`.
@@ -0,0 +1,154 @@
# Testing Cobra Commands
## Basic test pattern
```go
func TestServeCmd(t *testing.T) {
stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer)
rootCmd.SetOut(stdout)
rootCmd.SetErr(stderr)
rootCmd.SetArgs([]string{"serve", "--port", "9090", "--dry-run"})
err := rootCmd.Execute()
require.NoError(t, err)
assert.Contains(t, stdout.String(), "listening on :9090")
assert.Empty(t, stderr.String())
}
```
## Isolation between tests
Cobra accumulates flag state across `Execute()` calls on the same command instance. Tests must be isolated.
### Option 1: Re-create the command tree per test (recommended for unit tests)
```go
func newRootCmd() *cobra.Command {
root := &cobra.Command{Use: "myapp", SilenceUsage: true, SilenceErrors: true}
root.AddCommand(newServeCmd())
return root
}
func TestServeCmd(t *testing.T) {
root := newRootCmd()
root.SetArgs([]string{"serve", "--port", "9090"})
err := root.Execute()
require.NoError(t, err)
}
```
### Option 2: Reset flags between tests
```go
func TestWithReset(t *testing.T) {
t.Cleanup(func() {
rootCmd.ResetFlags()
// re-define flags if needed
})
}
```
Re-creating is safer — `ResetFlags` only clears the flag set, not subcommand state.
## Testing commands that write output
Commands must use `cmd.OutOrStdout()` / `cmd.ErrOrStderr()` instead of `os.Stdout` / `os.Stderr` for this to work.
```go
// In command handler:
func runServe(cmd *cobra.Command, args []string) error {
fmt.Fprintln(cmd.OutOrStdout(), "Server started")
fmt.Fprintln(cmd.ErrOrStderr(), "Debug: listening on port 8080")
return nil
}
// In test:
buf := new(bytes.Buffer)
rootCmd.SetOut(buf)
rootCmd.Execute()
assert.Contains(t, buf.String(), "Server started")
```
## Golden file tests
For commands with structured or lengthy output, use golden files:
```go
func TestOutputFormat(t *testing.T) {
buf := new(bytes.Buffer)
rootCmd.SetOut(buf)
rootCmd.SetArgs([]string{"list", "--output", "json"})
require.NoError(t, rootCmd.Execute())
golden := "testdata/list-json.golden"
if *update { // -update flag
os.WriteFile(golden, buf.Bytes(), 0644)
}
want, _ := os.ReadFile(golden)
assert.Equal(t, string(want), buf.String())
}
```
Run with `-update` to regenerate golden files after intentional output changes.
## Testing error paths
```go
func TestInvalidArgs(t *testing.T) {
stderr := new(bytes.Buffer)
rootCmd.SetErr(stderr)
rootCmd.SetArgs([]string{"delete"}) // missing required arg
err := rootCmd.Execute()
assert.Error(t, err)
assert.Contains(t, err.Error(), "accepts 1 arg")
}
```
## Table-driven command tests
```go
tests := []struct {
name string
args []string
wantOut string
wantErr bool
}{
{"no flags", []string{"serve"}, "listening on :8080", false},
{"custom port", []string{"serve", "--port", "9090"}, "listening on :9090", false},
{"invalid port", []string{"serve", "--port", "abc"}, "", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
root := newRootCmd() // fresh command tree per test
buf := new(bytes.Buffer)
root.SetOut(buf)
root.SetArgs(tt.args)
err := root.Execute()
if tt.wantErr {
assert.Error(t, err)
} else {
require.NoError(t, err)
assert.Contains(t, buf.String(), tt.wantOut)
}
})
}
```
## Testing completions
```go
func TestCompletion(t *testing.T) {
root := newRootCmd()
buf := new(bytes.Buffer)
root.SetOut(buf)
root.SetArgs([]string{"__complete", "delete", ""})
root.Execute()
assert.Contains(t, buf.String(), "pod")
assert.Contains(t, buf.String(), "service")
}
```