Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 8 additions & 10 deletions internal/cmd/backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,30 +49,30 @@ func withErrorHandling(f cobraRunEFunc) cobraRunEFunc {
}
}

var (
backupCmd = &cobra.Command{
func registerBackupCmd(rootCmd *cobra.Command) {
backupCmd := &cobra.Command{
Use: "backup <filename>",
Short: "Create, restore, and inspect permissions system backups",
Args: commands.ValidationWrapper(cobra.MaximumNArgs(1)),
// Create used to be on the root, so add it here for back-compat.
RunE: withErrorHandling(backupCreateCmdFunc),
}

backupCreateCmd = &cobra.Command{
backupCreateCmd := &cobra.Command{
Use: "create <filename>",
Short: "Backup a permission system to a file",
Args: commands.ValidationWrapper(cobra.MaximumNArgs(1)),
RunE: withErrorHandling(backupCreateCmdFunc),
}

backupRestoreCmd = &cobra.Command{
backupRestoreCmd := &cobra.Command{
Use: "restore <filename>",
Short: "Restore a permission system from a file",
Args: commands.ValidationWrapper(commands.StdinOrExactArgs(1)),
RunE: backupRestoreCmdFunc,
}

backupParseSchemaCmd = &cobra.Command{
backupParseSchemaCmd := &cobra.Command{
Use: "parse-schema <filename>",
Short: "Extract the schema from a backup file",
Args: commands.ValidationWrapper(cobra.ExactArgs(1)),
Expand All @@ -81,7 +81,7 @@ var (
},
}

backupParseRevisionCmd = &cobra.Command{
backupParseRevisionCmd := &cobra.Command{
Use: "parse-revision <filename>",
Short: "Extract the revision from a backup file",
Args: commands.ValidationWrapper(cobra.ExactArgs(1)),
Expand All @@ -90,7 +90,7 @@ var (
},
}

backupParseRelsCmd = &cobra.Command{
backupParseRelsCmd := &cobra.Command{
Use: "parse-relationships <filename>",
Short: "Extract the relationships from a backup file",
Args: commands.ValidationWrapper(cobra.ExactArgs(1)),
Expand All @@ -99,17 +99,15 @@ var (
},
}

backupRedactCmd = &cobra.Command{
backupRedactCmd := &cobra.Command{
Use: "redact <filename>",
Short: "Redact a backup file to remove sensitive information",
Args: commands.ValidationWrapper(cobra.ExactArgs(1)),
RunE: func(cmd *cobra.Command, args []string) error {
return backupRedactCmdFunc(cmd, args)
},
}
)

func registerBackupCmd(rootCmd *cobra.Command) {
rootCmd.AddCommand(backupCmd)
registerBackupCreateFlags(backupCmd)

Expand Down
4 changes: 2 additions & 2 deletions internal/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@ zed permission check --explain document:firstdoc writer user:emilia
registerImportCmd(rootCmd)
registerValidateCmd(rootCmd)
registerBackupCmd(rootCmd)
registerPreviewCmd(rootCmd)
registerMCPCmd(rootCmd)

// Register shared commands.
Expand All @@ -128,7 +127,8 @@ zed permission check --explain document:firstdoc writer user:emilia
commands.RegisterWatchRelationshipCmd(relCmd)

schemaCmd := commands.RegisterSchemaCmd(rootCmd)
registerAdditionalSchemaCmds(schemaCmd)
schemaCompileCmd := registerAdditionalSchemaCmds(schemaCmd)
registerPreviewCmd(rootCmd, schemaCompileCmd)

return rootCmd
}
Expand Down
26 changes: 23 additions & 3 deletions internal/cmd/cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,11 @@ func TestCommandOutput(t *testing.T) {
},
}

zl := cobrazerolog.New(cobrazerolog.WithPreRunLevel(zerolog.DebugLevel))

rootCmd := InitialiseRootCmd(zl)
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
zl := cobrazerolog.New(cobrazerolog.WithPreRunLevel(zerolog.DebugLevel))
rootCmd := InitialiseRootCmd(zl)

var flagErrorCalled bool
testFlagError := func(cmd *cobra.Command, err error) error {
require.ErrorContains(t, err, tt.flagErrorContains)
Expand All @@ -86,6 +86,26 @@ func TestCommandOutput(t *testing.T) {
}
}

// TestMultipleInitialiseRootCmd is a regression test to ensure that calling
// InitialiseRootCmd multiple times doesn't panic due to flag redefinition.
// This fixes issue #556.
func TestMultipleInitialiseRootCmd(t *testing.T) {
zl := cobrazerolog.New(cobrazerolog.WithPreRunLevel(zerolog.DebugLevel))

// Call InitialiseRootCmd multiple times to simulate what happens
// when tests run with -count=10
for i := 0; i < 10; i++ {
rootCmd := InitialiseRootCmd(zl)
require.NotNil(t, rootCmd)

// Execute the command with invalid args to trigger the command tree
// This ensures all commands and flags are properly initialized
os.Args = []string{"zed", "version", "--invalid-flag"}
err := rootCmd.ExecuteContext(t.Context())
require.Error(t, err) // We expect an error due to the invalid flag
}
}

func setupOutputForTest(t *testing.T, testFlagError func(cmd *cobra.Command, err error) error, args ...string) string {
t.Helper()

Expand Down
82 changes: 40 additions & 42 deletions internal/cmd/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,56 +16,54 @@ import (
)

func registerContextCmd(rootCmd *cobra.Command) {
rootCmd.AddCommand(contextCmd)
contextCmd := &cobra.Command{
Use: "context <subcommand>",
Short: "Manage configurations for connecting to SpiceDB deployments",
Aliases: []string{"ctx"},
}

contextListCmd := &cobra.Command{
Use: "list",
Short: "Lists all available contexts",
Aliases: []string{"ls"},
Args: commands.ValidationWrapper(cobra.ExactArgs(0)),
ValidArgsFunction: cobra.NoFileCompletions,
RunE: contextListCmdFunc,
}

contextSetCmd := &cobra.Command{
Use: "set <name> <endpoint> <api-token>",
Short: "Creates or overwrite a context",
Args: commands.ValidationWrapper(cobra.ExactArgs(3)),
ValidArgsFunction: cobra.NoFileCompletions,
RunE: contextSetCmdFunc,
}

contextRemoveCmd := &cobra.Command{
Use: "remove <system>",
Short: "Removes a context",
Aliases: []string{"rm"},
Args: commands.ValidationWrapper(cobra.ExactArgs(1)),
ValidArgsFunction: ContextGet,
RunE: contextRemoveCmdFunc,
}

contextUseCmd := &cobra.Command{
Use: "use <system>",
Short: "Sets a context as the current context",
Args: commands.ValidationWrapper(cobra.MaximumNArgs(1)),
ValidArgsFunction: ContextGet,
RunE: contextUseCmdFunc,
}

rootCmd.AddCommand(contextCmd)
contextCmd.AddCommand(contextListCmd)
contextListCmd.Flags().Bool("reveal-tokens", false, "display secrets in results")

contextCmd.AddCommand(contextSetCmd)
contextCmd.AddCommand(contextRemoveCmd)
contextCmd.AddCommand(contextUseCmd)
}

var contextCmd = &cobra.Command{
Use: "context <subcommand>",
Short: "Manage configurations for connecting to SpiceDB deployments",
Aliases: []string{"ctx"},
}

var contextListCmd = &cobra.Command{
Use: "list",
Short: "Lists all available contexts",
Aliases: []string{"ls"},
Args: commands.ValidationWrapper(cobra.ExactArgs(0)),
ValidArgsFunction: cobra.NoFileCompletions,
RunE: contextListCmdFunc,
}

var contextSetCmd = &cobra.Command{
Use: "set <name> <endpoint> <api-token>",
Short: "Creates or overwrite a context",
Args: commands.ValidationWrapper(cobra.ExactArgs(3)),
ValidArgsFunction: cobra.NoFileCompletions,
RunE: contextSetCmdFunc,
}

var contextRemoveCmd = &cobra.Command{
Use: "remove <system>",
Short: "Removes a context",
Aliases: []string{"rm"},
Args: commands.ValidationWrapper(cobra.ExactArgs(1)),
ValidArgsFunction: ContextGet,
RunE: contextRemoveCmdFunc,
}

var contextUseCmd = &cobra.Command{
Use: "use <system>",
Short: "Sets a context as the current context",
Args: commands.ValidationWrapper(cobra.MaximumNArgs(1)),
ValidArgsFunction: ContextGet,
RunE: contextUseCmdFunc,
}

func ContextGet(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
_, secretStore := client.DefaultStorage()
secrets, err := secretStore.Get()
Expand Down
28 changes: 14 additions & 14 deletions internal/cmd/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,10 @@ import (
)

func registerImportCmd(rootCmd *cobra.Command) {
rootCmd.AddCommand(importCmd)
importCmd.Flags().Int("batch-size", 1000, "import batch size")
importCmd.Flags().Int("workers", 1, "number of concurrent batching workers")
importCmd.Flags().Bool("schema", true, "import schema")
importCmd.Flags().Bool("relationships", true, "import relationships")
importCmd.Flags().String("schema-definition-prefix", "", "prefix to add to the schema's definition(s) before importing")
}

var importCmd = &cobra.Command{
Use: "import <url>",
Short: "Imports schema and relationships from a file or url",
Example: `
importCmd := &cobra.Command{
Use: "import <url>",
Short: "Imports schema and relationships from a file or url",
Example: `
From a gist:
zed import https://gist.github.com/ecordell/8e3b613a677e3c844742cf24421c08b6

Expand Down Expand Up @@ -61,8 +53,16 @@ var importCmd = &cobra.Command{
With schema definition prefix:
zed import --schema-definition-prefix=mypermsystem file:///Users/zed/Downloads/authzed-x7izWU8_2Gw3.yaml
`,
Args: commands.ValidationWrapper(cobra.ExactArgs(1)),
RunE: importCmdFunc,
Args: commands.ValidationWrapper(cobra.ExactArgs(1)),
RunE: importCmdFunc,
}

rootCmd.AddCommand(importCmd)
importCmd.Flags().Int("batch-size", 1000, "import batch size")
importCmd.Flags().Int("workers", 1, "number of concurrent batching workers")
importCmd.Flags().Bool("schema", true, "import schema")
importCmd.Flags().Bool("relationships", true, "import relationships")
importCmd.Flags().String("schema-definition-prefix", "", "prefix to add to the schema's definition(s) before importing")
}

func importCmdFunc(cmd *cobra.Command, args []string) error {
Expand Down
33 changes: 16 additions & 17 deletions internal/cmd/mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,27 @@ import (
)

func registerMCPCmd(rootCmd *cobra.Command) {
rootCmd.AddCommand(mcpCmd)
mcpCmd.AddCommand(mcpRunCmd)

mcpRunCmd.Flags().IntP("port", "p", 9999, "port for the HTTP streaming server")
}
mcpCmd := &cobra.Command{
Use: "mcp <subcommand>",
Short: "MCP (Model Context Protocol) server commands",
Long: `MCP (Model Context Protocol) server commands.

var mcpCmd = &cobra.Command{
Use: "mcp <subcommand>",
Short: "MCP (Model Context Protocol) server commands",
Long: `MCP (Model Context Protocol) server commands.

The MCP server provides tooling and resources for developing and debugging SpiceDB schema and relationships. The server runs an in-memory development instance of SpiceDB and does not connect to a running instance of SpiceDB.

To use with Claude Code, run ` + "`zed mcp experimental-run`" + ` to start the SpiceDB Dev MCP server and then run ` + "`claude mcp add --transport http spicedb \"http://localhost:9999/mcp\"`" + ` to add the server to your Claude Code integrations.`,
}
}

mcpRunCmd := &cobra.Command{
Use: "experimental-run",
Short: "Run the Experimental MCP server",
Args: commands.ValidationWrapper(cobra.ExactArgs(0)),
ValidArgsFunction: cobra.NoFileCompletions,
RunE: mcpRunCmdFunc,
}

var mcpRunCmd = &cobra.Command{
Use: "experimental-run",
Short: "Run the Experimental MCP server",
Args: commands.ValidationWrapper(cobra.ExactArgs(0)),
ValidArgsFunction: cobra.NoFileCompletions,
RunE: mcpRunCmdFunc,
rootCmd.AddCommand(mcpCmd)
mcpCmd.AddCommand(mcpRunCmd)
mcpRunCmd.Flags().IntP("port", "p", 9999, "port for the HTTP streaming server")
}

func mcpRunCmdFunc(cmd *cobra.Command, _ []string) error {
Expand Down
26 changes: 12 additions & 14 deletions internal/cmd/preview.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,19 @@ import (
"github.com/spf13/cobra"
)

func registerPreviewCmd(rootCmd *cobra.Command) {
rootCmd.AddCommand(previewCmd)
func registerPreviewCmd(rootCmd *cobra.Command, schemaCompileCmd *cobra.Command) {
previewCmd := &cobra.Command{
Use: "preview <subcommand>",
Short: "Experimental commands that have been made available for preview",
}

previewCmd.AddCommand(schemaCmd)
schemaCmd := &cobra.Command{
Use: "schema <subcommand>",
Short: "Manage schema for a permissions system",
Deprecated: "please use `zed schema compile`",
}

rootCmd.AddCommand(previewCmd)
previewCmd.AddCommand(schemaCmd)
schemaCmd.AddCommand(schemaCompileCmd)
}

var previewCmd = &cobra.Command{
Use: "preview <subcommand>",
Short: "Experimental commands that have been made available for preview",
}

var schemaCmd = &cobra.Command{
Use: "schema <subcommand>",
Short: "Manage schema for a permissions system",
Deprecated: "please use `zed schema compile`",
}
Loading
Loading