diff --git a/internal/cmd/backup.go b/internal/cmd/backup.go index 7b2ca3bf..ad3407e6 100644 --- a/internal/cmd/backup.go +++ b/internal/cmd/backup.go @@ -49,8 +49,8 @@ func withErrorHandling(f cobraRunEFunc) cobraRunEFunc { } } -var ( - backupCmd = &cobra.Command{ +func registerBackupCmd(rootCmd *cobra.Command) { + backupCmd := &cobra.Command{ Use: "backup ", Short: "Create, restore, and inspect permissions system backups", Args: commands.ValidationWrapper(cobra.MaximumNArgs(1)), @@ -58,21 +58,21 @@ var ( RunE: withErrorHandling(backupCreateCmdFunc), } - backupCreateCmd = &cobra.Command{ + backupCreateCmd := &cobra.Command{ Use: "create ", 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 ", 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 ", Short: "Extract the schema from a backup file", Args: commands.ValidationWrapper(cobra.ExactArgs(1)), @@ -81,7 +81,7 @@ var ( }, } - backupParseRevisionCmd = &cobra.Command{ + backupParseRevisionCmd := &cobra.Command{ Use: "parse-revision ", Short: "Extract the revision from a backup file", Args: commands.ValidationWrapper(cobra.ExactArgs(1)), @@ -90,7 +90,7 @@ var ( }, } - backupParseRelsCmd = &cobra.Command{ + backupParseRelsCmd := &cobra.Command{ Use: "parse-relationships ", Short: "Extract the relationships from a backup file", Args: commands.ValidationWrapper(cobra.ExactArgs(1)), @@ -99,7 +99,7 @@ var ( }, } - backupRedactCmd = &cobra.Command{ + backupRedactCmd := &cobra.Command{ Use: "redact ", Short: "Redact a backup file to remove sensitive information", Args: commands.ValidationWrapper(cobra.ExactArgs(1)), @@ -107,9 +107,7 @@ var ( return backupRedactCmdFunc(cmd, args) }, } -) -func registerBackupCmd(rootCmd *cobra.Command) { rootCmd.AddCommand(backupCmd) registerBackupCreateFlags(backupCmd) diff --git a/internal/cmd/cmd.go b/internal/cmd/cmd.go index e9410bf0..795e6362 100644 --- a/internal/cmd/cmd.go +++ b/internal/cmd/cmd.go @@ -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. @@ -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 } diff --git a/internal/cmd/cmd_test.go b/internal/cmd/cmd_test.go index 69b94925..b9de1a30 100644 --- a/internal/cmd/cmd_test.go +++ b/internal/cmd/cmd_test.go @@ -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) @@ -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() diff --git a/internal/cmd/context.go b/internal/cmd/context.go index 766784b0..3cfcb192 100644 --- a/internal/cmd/context.go +++ b/internal/cmd/context.go @@ -16,56 +16,54 @@ import ( ) func registerContextCmd(rootCmd *cobra.Command) { - rootCmd.AddCommand(contextCmd) + contextCmd := &cobra.Command{ + Use: "context ", + 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 ", + Short: "Creates or overwrite a context", + Args: commands.ValidationWrapper(cobra.ExactArgs(3)), + ValidArgsFunction: cobra.NoFileCompletions, + RunE: contextSetCmdFunc, + } + contextRemoveCmd := &cobra.Command{ + Use: "remove ", + Short: "Removes a context", + Aliases: []string{"rm"}, + Args: commands.ValidationWrapper(cobra.ExactArgs(1)), + ValidArgsFunction: ContextGet, + RunE: contextRemoveCmdFunc, + } + + contextUseCmd := &cobra.Command{ + Use: "use ", + 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 ", - 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 ", - Short: "Creates or overwrite a context", - Args: commands.ValidationWrapper(cobra.ExactArgs(3)), - ValidArgsFunction: cobra.NoFileCompletions, - RunE: contextSetCmdFunc, -} - -var contextRemoveCmd = &cobra.Command{ - Use: "remove ", - Short: "Removes a context", - Aliases: []string{"rm"}, - Args: commands.ValidationWrapper(cobra.ExactArgs(1)), - ValidArgsFunction: ContextGet, - RunE: contextRemoveCmdFunc, -} - -var contextUseCmd = &cobra.Command{ - Use: "use ", - 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() diff --git a/internal/cmd/import.go b/internal/cmd/import.go index 687d42ef..872af7a8 100644 --- a/internal/cmd/import.go +++ b/internal/cmd/import.go @@ -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 ", - Short: "Imports schema and relationships from a file or url", - Example: ` + importCmd := &cobra.Command{ + Use: "import ", + Short: "Imports schema and relationships from a file or url", + Example: ` From a gist: zed import https://gist.github.com/ecordell/8e3b613a677e3c844742cf24421c08b6 @@ -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 { diff --git a/internal/cmd/mcp.go b/internal/cmd/mcp.go index e7ef3331..ac369829 100644 --- a/internal/cmd/mcp.go +++ b/internal/cmd/mcp.go @@ -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 ", + Short: "MCP (Model Context Protocol) server commands", + Long: `MCP (Model Context Protocol) server commands. -var mcpCmd = &cobra.Command{ - Use: "mcp ", - 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 { diff --git a/internal/cmd/preview.go b/internal/cmd/preview.go index 7f0230b4..cbe32311 100644 --- a/internal/cmd/preview.go +++ b/internal/cmd/preview.go @@ -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 ", + Short: "Experimental commands that have been made available for preview", + } - previewCmd.AddCommand(schemaCmd) + schemaCmd := &cobra.Command{ + Use: "schema ", + 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 ", - Short: "Experimental commands that have been made available for preview", -} - -var schemaCmd = &cobra.Command{ - Use: "schema ", - Short: "Manage schema for a permissions system", - Deprecated: "please use `zed schema compile`", -} diff --git a/internal/cmd/schema.go b/internal/cmd/schema.go index 27dbf33b..6a5cc292 100644 --- a/internal/cmd/schema.go +++ b/internal/cmd/schema.go @@ -40,7 +40,58 @@ func (rtc *realTermChecker) IsTerminal(fd int) bool { return term.IsTerminal(fd) } -func registerAdditionalSchemaCmds(schemaCmd *cobra.Command) { +func registerAdditionalSchemaCmds(schemaCmd *cobra.Command) *cobra.Command { + schemaWriteCmd := &cobra.Command{ + Use: "write ", + Args: commands.ValidationWrapper(cobra.MaximumNArgs(1)), + Short: "Write a schema file (.zed or stdin) to the current permissions system", + ValidArgsFunction: commands.FileExtensionCompletions("zed"), + Example: ` + Write from a file: + zed schema write schema.zed + Write from stdin: + cat schema.zed | zed schema write +`, + RunE: func(cmd *cobra.Command, args []string) error { + client, err := client.NewClient(cmd) + if err != nil { + return err + } + return schemaWriteCmdImpl(cmd, args, client, &realTermChecker{}) + }, + } + + schemaCopyCmd := &cobra.Command{ + Use: "copy ", + Short: "Copy a schema from one context into another", + Args: commands.ValidationWrapper(cobra.ExactArgs(2)), + ValidArgsFunction: ContextGet, + RunE: schemaCopyCmdFunc, + } + + schemaDiffCmd := &cobra.Command{ + Use: "diff ", + Short: "Diff two schema files", + Args: commands.ValidationWrapper(cobra.ExactArgs(2)), + RunE: schemaDiffCmdFunc, + } + + schemaCompileCmd := &cobra.Command{ + Use: "compile ", + Args: commands.ValidationWrapper(cobra.ExactArgs(1)), + Short: "Compile a schema that uses extended syntax into one that can be written to SpiceDB", + Example: ` + Write to stdout: + zed preview schema compile root.zed + Write to an output file: + zed preview schema compile root.zed --out compiled.zed + `, + ValidArgsFunction: commands.FileExtensionCompletions("zed"), + RunE: func(cmd *cobra.Command, args []string) error { + return schemaCompileCmdFunc(cmd, args, &realTermChecker{}) + }, + } + schemaCmd.AddCommand(schemaCopyCmd) schemaCopyCmd.Flags().Bool("json", false, "output as JSON") schemaCopyCmd.Flags().String("schema-definition-prefix", "", "prefix to add to the schema's definition(s) before writing") @@ -53,57 +104,8 @@ func registerAdditionalSchemaCmds(schemaCmd *cobra.Command) { schemaCmd.AddCommand(schemaCompileCmd) schemaCompileCmd.Flags().String("out", "", "output filepath; omitting writes to stdout") -} - -var schemaWriteCmd = &cobra.Command{ - Use: "write ", - Args: commands.ValidationWrapper(cobra.MaximumNArgs(1)), - Short: "Write a schema file (.zed or stdin) to the current permissions system", - ValidArgsFunction: commands.FileExtensionCompletions("zed"), - Example: ` - Write from a file: - zed schema write schema.zed - Write from stdin: - cat schema.zed | zed schema write -`, - RunE: func(cmd *cobra.Command, args []string) error { - client, err := client.NewClient(cmd) - if err != nil { - return err - } - return schemaWriteCmdImpl(cmd, args, client, &realTermChecker{}) - }, -} - -var schemaCopyCmd = &cobra.Command{ - Use: "copy ", - Short: "Copy a schema from one context into another", - Args: commands.ValidationWrapper(cobra.ExactArgs(2)), - ValidArgsFunction: ContextGet, - RunE: schemaCopyCmdFunc, -} - -var schemaDiffCmd = &cobra.Command{ - Use: "diff ", - Short: "Diff two schema files", - Args: commands.ValidationWrapper(cobra.ExactArgs(2)), - RunE: schemaDiffCmdFunc, -} -var schemaCompileCmd = &cobra.Command{ - Use: "compile ", - Args: commands.ValidationWrapper(cobra.ExactArgs(1)), - Short: "Compile a schema that uses extended syntax into one that can be written to SpiceDB", - Example: ` - Write to stdout: - zed preview schema compile root.zed - Write to an output file: - zed preview schema compile root.zed --out compiled.zed - `, - ValidArgsFunction: commands.FileExtensionCompletions("zed"), - RunE: func(cmd *cobra.Command, args []string) error { - return schemaCompileCmdFunc(cmd, args, &realTermChecker{}) - }, + return schemaCompileCmd } func schemaDiffCmdFunc(_ *cobra.Command, args []string) error { diff --git a/internal/cmd/validate.go b/internal/cmd/validate.go index 4ee2818c..78c8c431 100644 --- a/internal/cmd/validate.go +++ b/internal/cmd/validate.go @@ -48,16 +48,10 @@ var ( ) func registerValidateCmd(cmd *cobra.Command) { - validateCmd.Flags().Bool("force-color", false, "force color code output even in non-tty environments") - validateCmd.Flags().Bool("fail-on-warn", false, "treat warnings as errors during validation") - validateCmd.Flags().String("schema-type", "", "force validation according to specific schema syntax (\"\", \"composable\", \"standard\")") - cmd.AddCommand(validateCmd) -} - -var validateCmd = &cobra.Command{ - Use: "validate ", - Short: "Validates the given validation file (.yaml, .zaml) or schema file (.zed)", - Example: ` + validateCmd := &cobra.Command{ + Use: "validate ", + Short: "Validates the given validation file (.yaml, .zaml) or schema file (.zed)", + Example: ` From a local file (with prefix): zed validate file:///Users/zed/Downloads/authzed-x7izWU8_2Gw3.yaml @@ -75,25 +69,31 @@ var validateCmd = &cobra.Command{ From a devtools instance: zed validate https://localhost:8443/download`, - Args: commands.ValidationWrapper(cobra.MinimumNArgs(1)), - ValidArgsFunction: commands.FileExtensionCompletions("zed", "yaml", "zaml"), - PreRunE: validatePreRunE, - RunE: func(cmd *cobra.Command, filenames []string) error { - result, shouldExit, err := validateCmdFunc(cmd, filenames) - if err != nil { - return err - } - console.Print(result) - if shouldExit { - os.Exit(1) - } - return nil - }, + Args: commands.ValidationWrapper(cobra.MinimumNArgs(1)), + ValidArgsFunction: commands.FileExtensionCompletions("zed", "yaml", "zaml"), + PreRunE: validatePreRunE, + RunE: func(cmd *cobra.Command, filenames []string) error { + result, shouldExit, err := validateCmdFunc(cmd, filenames) + if err != nil { + return err + } + console.Print(result) + if shouldExit { + os.Exit(1) + } + return nil + }, + + // A schema that causes the parser/compiler to error will halt execution + // of this command with an error. In that case, we want to just display the error, + // rather than showing usage for this command. + SilenceUsage: true, + } - // A schema that causes the parser/compiler to error will halt execution - // of this command with an error. In that case, we want to just display the error, - // rather than showing usage for this command. - SilenceUsage: true, + validateCmd.Flags().Bool("force-color", false, "force color code output even in non-tty environments") + validateCmd.Flags().Bool("fail-on-warn", false, "treat warnings as errors during validation") + validateCmd.Flags().String("schema-type", "", "force validation according to specific schema syntax (\"\", \"composable\", \"standard\")") + cmd.AddCommand(validateCmd) } var validSchemaTypes = []string{"", "standard", "composable"} diff --git a/internal/commands/permission.go b/internal/commands/permission.go index d0171631..62bd0c0b 100644 --- a/internal/commands/permission.go +++ b/internal/commands/permission.go @@ -69,6 +69,61 @@ func consistencyFromCmd(cmd *cobra.Command) (c *v1.Consistency, err error) { } func RegisterPermissionCmd(rootCmd *cobra.Command) *cobra.Command { + permissionCmd := &cobra.Command{ + Use: "permission ", + Short: "Query the permissions in a permissions system", + Aliases: []string{"perm"}, + } + + checkBulkCmd := &cobra.Command{ + Use: "bulk ...", + Short: "Check permissions in bulk exist for resource-subject pairs", + Args: ValidationWrapper(cobra.MinimumNArgs(1)), + RunE: checkBulkCmdFunc, + } + + checkCmd := &cobra.Command{ + Use: "check ", + Short: "Check that a permission exists for a subject", + Args: ValidationWrapper(cobra.ExactArgs(3)), + ValidArgsFunction: GetArgs(ResourceID, Permission, SubjectID), + RunE: checkCmdFunc, + } + + expandCmd := &cobra.Command{ + Use: "expand ", + Short: "Expand the structure of a permission", + Args: ValidationWrapper(cobra.ExactArgs(2)), + ValidArgsFunction: cobra.NoFileCompletions, + RunE: expandCmdFunc, + } + + lookupResourcesCmd := &cobra.Command{ + Use: "lookup-resources ", + Short: "Enumerates the resources of a given type for which the subject has permission", + Args: ValidationWrapper(cobra.ExactArgs(3)), + ValidArgsFunction: GetArgs(ResourceType, Permission, SubjectID), + RunE: lookupResourcesCmdFunc, + } + + lookupCmd := &cobra.Command{ + Use: "lookup ", + Short: "Enumerates the resources of a given type for which the subject has permission", + Args: ValidationWrapper(cobra.ExactArgs(3)), + ValidArgsFunction: GetArgs(ResourceType, Permission, SubjectID), + RunE: lookupResourcesCmdFunc, + Deprecated: "prefer lookup-resources", + Hidden: true, + } + + lookupSubjectsCmd := &cobra.Command{ + Use: "lookup-subjects ", + Short: "Enumerates the subjects of a given type for which the subject has permission on the resource", + Args: ValidationWrapper(cobra.ExactArgs(3)), + ValidArgsFunction: GetArgs(ResourceID, Permission, SubjectTypeWithOptionalRelation), + RunE: lookupSubjectsCmdFunc, + } + rootCmd.AddCommand(permissionCmd) permissionCmd.AddCommand(checkCmd) @@ -120,61 +175,6 @@ func RegisterPermissionCmd(rootCmd *cobra.Command) *cobra.Command { return permissionCmd } -var permissionCmd = &cobra.Command{ - Use: "permission ", - Short: "Query the permissions in a permissions system", - Aliases: []string{"perm"}, -} - -var checkBulkCmd = &cobra.Command{ - Use: "bulk ...", - Short: "Check permissions in bulk exist for resource-subject pairs", - Args: ValidationWrapper(cobra.MinimumNArgs(1)), - RunE: checkBulkCmdFunc, -} - -var checkCmd = &cobra.Command{ - Use: "check ", - Short: "Check that a permission exists for a subject", - Args: ValidationWrapper(cobra.ExactArgs(3)), - ValidArgsFunction: GetArgs(ResourceID, Permission, SubjectID), - RunE: checkCmdFunc, -} - -var expandCmd = &cobra.Command{ - Use: "expand ", - Short: "Expand the structure of a permission", - Args: ValidationWrapper(cobra.ExactArgs(2)), - ValidArgsFunction: cobra.NoFileCompletions, - RunE: expandCmdFunc, -} - -var lookupResourcesCmd = &cobra.Command{ - Use: "lookup-resources ", - Short: "Enumerates the resources of a given type for which the subject has permission", - Args: ValidationWrapper(cobra.ExactArgs(3)), - ValidArgsFunction: GetArgs(ResourceType, Permission, SubjectID), - RunE: lookupResourcesCmdFunc, -} - -var lookupCmd = &cobra.Command{ - Use: "lookup ", - Short: "Enumerates the resources of a given type for which the subject has permission", - Args: ValidationWrapper(cobra.ExactArgs(3)), - ValidArgsFunction: GetArgs(ResourceType, Permission, SubjectID), - RunE: lookupResourcesCmdFunc, - Deprecated: "prefer lookup-resources", - Hidden: true, -} - -var lookupSubjectsCmd = &cobra.Command{ - Use: "lookup-subjects ", - Short: "Enumerates the subjects of a given type for which the subject has permission on the resource", - Args: ValidationWrapper(cobra.ExactArgs(3)), - ValidArgsFunction: GetArgs(ResourceID, Permission, SubjectTypeWithOptionalRelation), - RunE: lookupSubjectsCmdFunc, -} - func checkCmdFunc(cmd *cobra.Command, args []string) error { var objectNS, objectID string err := stringz.SplitExact(args[0], ":", &objectNS, &objectID) diff --git a/internal/commands/relationship.go b/internal/commands/relationship.go index 0471f856..9af24600 100644 --- a/internal/commands/relationship.go +++ b/internal/commands/relationship.go @@ -26,7 +26,60 @@ import ( "github.com/authzed/zed/internal/console" ) +const readCmdHelpLong = `Enumerates relationships matching the provided pattern. + +To filter returned relationships using a resource ID prefix, append a '%' to the resource ID: + +zed relationship read some-type:some-prefix-% +` + func RegisterRelationshipCmd(rootCmd *cobra.Command) *cobra.Command { + relationshipCmd := &cobra.Command{ + Use: "relationship ", + Short: "Query and mutate the relationships in a permissions system", + } + + createCmd := &cobra.Command{ + Use: "create ", + Short: "Create a relationship for a subject", + Args: ValidationWrapper(StdinOrExactArgs(3)), + ValidArgsFunction: GetArgs(ResourceID, Permission, SubjectTypeWithOptionalRelation), + RunE: writeRelationshipCmdFunc(v1.RelationshipUpdate_OPERATION_CREATE, os.Stdin), + } + + touchCmd := &cobra.Command{ + Use: "touch ", + Short: "Idempotently updates a relationship for a subject", + Args: ValidationWrapper(StdinOrExactArgs(3)), + ValidArgsFunction: GetArgs(ResourceID, Permission, SubjectTypeWithOptionalRelation), + RunE: writeRelationshipCmdFunc(v1.RelationshipUpdate_OPERATION_TOUCH, os.Stdin), + } + + deleteCmd := &cobra.Command{ + Use: "delete ", + Short: "Deletes a relationship", + Args: ValidationWrapper(StdinOrExactArgs(3)), + ValidArgsFunction: GetArgs(ResourceID, Permission, SubjectTypeWithOptionalRelation), + RunE: writeRelationshipCmdFunc(v1.RelationshipUpdate_OPERATION_DELETE, os.Stdin), + } + + readCmd := &cobra.Command{ + Use: "read ", + Short: "Enumerates relationships matching the provided pattern", + Long: readCmdHelpLong, + Args: ValidationWrapper(cobra.RangeArgs(1, 3)), + ValidArgsFunction: GetArgs(ResourceID, Permission, SubjectTypeWithOptionalRelation), + RunE: readRelationships, + } + + bulkDeleteCmd := &cobra.Command{ + Use: "bulk-delete ", + Short: "Deletes relationships matching the provided pattern en masse", + Args: ValidationWrapper(cobra.RangeArgs(1, 3)), + ValidArgsFunction: GetArgs(ResourceID, Permission, SubjectTypeWithOptionalRelation), + RunE: bulkDeleteRelationships, + } + rootCmd.AddCommand(relationshipCmd) relationshipCmd.AddCommand(createCmd) @@ -62,59 +115,6 @@ func RegisterRelationshipCmd(rootCmd *cobra.Command) *cobra.Command { return relationshipCmd } -var relationshipCmd = &cobra.Command{ - Use: "relationship ", - Short: "Query and mutate the relationships in a permissions system", -} - -var createCmd = &cobra.Command{ - Use: "create ", - Short: "Create a relationship for a subject", - Args: ValidationWrapper(StdinOrExactArgs(3)), - ValidArgsFunction: GetArgs(ResourceID, Permission, SubjectTypeWithOptionalRelation), - RunE: writeRelationshipCmdFunc(v1.RelationshipUpdate_OPERATION_CREATE, os.Stdin), -} - -var touchCmd = &cobra.Command{ - Use: "touch ", - Short: "Idempotently updates a relationship for a subject", - Args: ValidationWrapper(StdinOrExactArgs(3)), - ValidArgsFunction: GetArgs(ResourceID, Permission, SubjectTypeWithOptionalRelation), - RunE: writeRelationshipCmdFunc(v1.RelationshipUpdate_OPERATION_TOUCH, os.Stdin), -} - -var deleteCmd = &cobra.Command{ - Use: "delete ", - Short: "Deletes a relationship", - Args: ValidationWrapper(StdinOrExactArgs(3)), - ValidArgsFunction: GetArgs(ResourceID, Permission, SubjectTypeWithOptionalRelation), - RunE: writeRelationshipCmdFunc(v1.RelationshipUpdate_OPERATION_DELETE, os.Stdin), -} - -const readCmdHelpLong = `Enumerates relationships matching the provided pattern. - -To filter returned relationships using a resource ID prefix, append a '%' to the resource ID: - -zed relationship read some-type:some-prefix-% -` - -var readCmd = &cobra.Command{ - Use: "read ", - Short: "Enumerates relationships matching the provided pattern", - Long: readCmdHelpLong, - Args: ValidationWrapper(cobra.RangeArgs(1, 3)), - ValidArgsFunction: GetArgs(ResourceID, Permission, SubjectTypeWithOptionalRelation), - RunE: readRelationships, -} - -var bulkDeleteCmd = &cobra.Command{ - Use: "bulk-delete ", - Short: "Deletes relationships matching the provided pattern en masse", - Args: ValidationWrapper(cobra.RangeArgs(1, 3)), - ValidArgsFunction: GetArgs(ResourceID, Permission, SubjectTypeWithOptionalRelation), - RunE: bulkDeleteRelationships, -} - func StdinOrExactArgs(n int) cobra.PositionalArgs { return func(cmd *cobra.Command, args []string) error { if ok := isArgsViaFile(os.Stdin) && len(args) == 0; ok { diff --git a/internal/commands/schema.go b/internal/commands/schema.go index cb020747..e810ef46 100644 --- a/internal/commands/schema.go +++ b/internal/commands/schema.go @@ -17,28 +17,25 @@ import ( ) func RegisterSchemaCmd(rootCmd *cobra.Command) *cobra.Command { - rootCmd.AddCommand(schemaCmd) - - schemaCmd.AddCommand(schemaReadCmd) - schemaReadCmd.Flags().Bool("json", false, "output as JSON") - - return schemaCmd -} - -var ( - schemaCmd = &cobra.Command{ + schemaCmd := &cobra.Command{ Use: "schema ", Short: "Manage schema for a permissions system", } - schemaReadCmd = &cobra.Command{ + schemaReadCmd := &cobra.Command{ Use: "read", Short: "Read the schema of a permissions system", Args: ValidationWrapper(cobra.ExactArgs(0)), ValidArgsFunction: cobra.NoFileCompletions, RunE: schemaReadCmdFunc, } -) + + rootCmd.AddCommand(schemaCmd) + schemaCmd.AddCommand(schemaReadCmd) + schemaReadCmd.Flags().Bool("json", false, "output as JSON") + + return schemaCmd +} func schemaReadCmdFunc(cmd *cobra.Command, _ []string) error { client, err := client.NewClient(cmd) diff --git a/internal/commands/watch.go b/internal/commands/watch.go index 1e0313e9..1ff13e31 100644 --- a/internal/commands/watch.go +++ b/internal/commands/watch.go @@ -28,8 +28,15 @@ var ( ) func RegisterWatchCmd(rootCmd *cobra.Command) *cobra.Command { - rootCmd.AddCommand(watchCmd) + watchCmd := &cobra.Command{ + Use: "watch [object_types, ...] [start_cursor]", + Short: "Watches the stream of relationship updates and schema updates from the server", + Args: ValidationWrapper(cobra.RangeArgs(0, 2)), + RunE: watchCmdFunc, + Deprecated: "please use `zed relationships watch` instead", + } + rootCmd.AddCommand(watchCmd) watchCmd.Flags().StringSliceVar(&watchObjectTypes, "object_types", nil, "optional object types to watch updates for") watchCmd.Flags().StringVar(&watchRevision, "revision", "", "optional revision at which to start watching") watchCmd.Flags().BoolVar(&watchTimestamps, "timestamp", false, "shows timestamp of incoming update events") @@ -37,6 +44,13 @@ func RegisterWatchCmd(rootCmd *cobra.Command) *cobra.Command { } func RegisterWatchRelationshipCmd(parentCmd *cobra.Command) *cobra.Command { + watchRelationshipsCmd := &cobra.Command{ + Use: "watch [object_types, ...] [start_cursor]", + Short: "Watches the stream of relationship updates and schema updates from the server", + Args: ValidationWrapper(cobra.RangeArgs(0, 2)), + RunE: watchCmdFunc, + } + parentCmd.AddCommand(watchRelationshipsCmd) watchRelationshipsCmd.Flags().StringSliceVar(&watchObjectTypes, "object_types", nil, "optional object types to watch updates for") watchRelationshipsCmd.Flags().StringVar(&watchRevision, "revision", "", "optional revision at which to start watching") @@ -45,21 +59,6 @@ func RegisterWatchRelationshipCmd(parentCmd *cobra.Command) *cobra.Command { return watchRelationshipsCmd } -var watchCmd = &cobra.Command{ - Use: "watch [object_types, ...] [start_cursor]", - Short: "Watches the stream of relationship updates and schema updates from the server", - Args: ValidationWrapper(cobra.RangeArgs(0, 2)), - RunE: watchCmdFunc, - Deprecated: "please use `zed relationships watch` instead", -} - -var watchRelationshipsCmd = &cobra.Command{ - Use: "watch [object_types, ...] [start_cursor]", - Short: "Watches the stream of relationship updates and schema updates from the server", - Args: ValidationWrapper(cobra.RangeArgs(0, 2)), - RunE: watchCmdFunc, -} - func watchCmdFunc(cmd *cobra.Command, _ []string) error { client, err := client.NewClient(cmd) if err != nil {