summaryrefslogtreecommitdiff
path: root/vendor/github.com/authzed/zed/internal/cmd/schema.go
diff options
context:
space:
mode:
authormo khan <mo@mokhan.ca>2025-07-22 17:35:49 -0600
committermo khan <mo@mokhan.ca>2025-07-22 17:35:49 -0600
commit20ef0d92694465ac86b550df139e8366a0a2b4fa (patch)
tree3f14589e1ce6eb9306a3af31c3a1f9e1af5ed637 /vendor/github.com/authzed/zed/internal/cmd/schema.go
parent44e0d272c040cdc53a98b9f1dc58ae7da67752e6 (diff)
feat: connect to spicedb
Diffstat (limited to 'vendor/github.com/authzed/zed/internal/cmd/schema.go')
-rw-r--r--vendor/github.com/authzed/zed/internal/cmd/schema.go319
1 files changed, 319 insertions, 0 deletions
diff --git a/vendor/github.com/authzed/zed/internal/cmd/schema.go b/vendor/github.com/authzed/zed/internal/cmd/schema.go
new file mode 100644
index 0000000..bbe52f3
--- /dev/null
+++ b/vendor/github.com/authzed/zed/internal/cmd/schema.go
@@ -0,0 +1,319 @@
+package cmd
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "io"
+ "os"
+ "strings"
+
+ "github.com/ccoveille/go-safecast"
+ "github.com/jzelinskie/cobrautil/v2"
+ "github.com/jzelinskie/stringz"
+ "github.com/rs/zerolog/log"
+ "github.com/spf13/cobra"
+ "golang.org/x/term"
+
+ v1 "github.com/authzed/authzed-go/proto/authzed/api/v1"
+ "github.com/authzed/spicedb/pkg/caveats/types"
+ "github.com/authzed/spicedb/pkg/diff"
+ "github.com/authzed/spicedb/pkg/schemadsl/compiler"
+ "github.com/authzed/spicedb/pkg/schemadsl/generator"
+ "github.com/authzed/spicedb/pkg/schemadsl/input"
+
+ "github.com/authzed/zed/internal/client"
+ "github.com/authzed/zed/internal/commands"
+ "github.com/authzed/zed/internal/console"
+)
+
+func registerAdditionalSchemaCmds(schemaCmd *cobra.Command) {
+ 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")
+
+ schemaCmd.AddCommand(schemaWriteCmd)
+ schemaWriteCmd.Flags().Bool("json", false, "output as JSON")
+ schemaWriteCmd.Flags().String("schema-definition-prefix", "", "prefix to add to the schema's definition(s) before writing")
+
+ schemaCmd.AddCommand(schemaDiffCmd)
+}
+
+var schemaWriteCmd = &cobra.Command{
+ Use: "write <file?>",
+ Args: commands.ValidationWrapper(cobra.MaximumNArgs(1)),
+ Short: "Write a schema file (.zed or stdin) to the current permissions system",
+ ValidArgsFunction: commands.FileExtensionCompletions("zed"),
+ RunE: schemaWriteCmdFunc,
+}
+
+var schemaCopyCmd = &cobra.Command{
+ Use: "copy <src context> <dest context>",
+ 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 <before file> <after file>",
+ Short: "Diff two schema files",
+ Args: commands.ValidationWrapper(cobra.ExactArgs(2)),
+ RunE: schemaDiffCmdFunc,
+}
+
+func schemaDiffCmdFunc(_ *cobra.Command, args []string) error {
+ beforeBytes, err := os.ReadFile(args[0])
+ if err != nil {
+ return fmt.Errorf("failed to read before schema file: %w", err)
+ }
+
+ afterBytes, err := os.ReadFile(args[1])
+ if err != nil {
+ return fmt.Errorf("failed to read after schema file: %w", err)
+ }
+
+ before, err := compiler.Compile(
+ compiler.InputSchema{Source: input.Source(args[0]), SchemaString: string(beforeBytes)},
+ compiler.AllowUnprefixedObjectType(),
+ )
+ if err != nil {
+ return err
+ }
+
+ after, err := compiler.Compile(
+ compiler.InputSchema{Source: input.Source(args[1]), SchemaString: string(afterBytes)},
+ compiler.AllowUnprefixedObjectType(),
+ )
+ if err != nil {
+ return err
+ }
+
+ dbefore := diff.NewDiffableSchemaFromCompiledSchema(before)
+ dafter := diff.NewDiffableSchemaFromCompiledSchema(after)
+
+ schemaDiff, err := diff.DiffSchemas(dbefore, dafter, types.Default.TypeSet)
+ if err != nil {
+ return err
+ }
+
+ for _, ns := range schemaDiff.AddedNamespaces {
+ console.Printf("Added definition: %s\n", ns)
+ }
+
+ for _, ns := range schemaDiff.RemovedNamespaces {
+ console.Printf("Removed definition: %s\n", ns)
+ }
+
+ for nsName, ns := range schemaDiff.ChangedNamespaces {
+ console.Printf("Changed definition: %s\n", nsName)
+ for _, delta := range ns.Deltas() {
+ console.Printf("\t %s: %s\n", delta.Type, delta.RelationName)
+ }
+ }
+
+ for _, caveat := range schemaDiff.AddedCaveats {
+ console.Printf("Added caveat: %s\n", caveat)
+ }
+
+ for _, caveat := range schemaDiff.RemovedCaveats {
+ console.Printf("Removed caveat: %s\n", caveat)
+ }
+
+ return nil
+}
+
+func schemaCopyCmdFunc(cmd *cobra.Command, args []string) error {
+ _, secretStore := client.DefaultStorage()
+ srcClient, err := client.NewClientForContext(cmd, args[0], secretStore)
+ if err != nil {
+ return err
+ }
+
+ destClient, err := client.NewClientForContext(cmd, args[1], secretStore)
+ if err != nil {
+ return err
+ }
+
+ readRequest := &v1.ReadSchemaRequest{}
+ log.Trace().Interface("request", readRequest).Msg("requesting schema read")
+
+ readResp, err := srcClient.ReadSchema(cmd.Context(), readRequest)
+ if err != nil {
+ log.Fatal().Err(err).Msg("failed to read schema")
+ }
+ log.Trace().Interface("response", readResp).Msg("read schema")
+
+ prefix, err := determinePrefixForSchema(cmd.Context(), cobrautil.MustGetString(cmd, "schema-definition-prefix"), nil, &readResp.SchemaText)
+ if err != nil {
+ return err
+ }
+
+ schemaText, err := rewriteSchema(readResp.SchemaText, prefix)
+ if err != nil {
+ return err
+ }
+
+ writeRequest := &v1.WriteSchemaRequest{Schema: schemaText}
+ log.Trace().Interface("request", writeRequest).Msg("writing schema")
+
+ resp, err := destClient.WriteSchema(cmd.Context(), writeRequest)
+ if err != nil {
+ log.Fatal().Err(err).Msg("failed to write schema")
+ }
+ log.Trace().Interface("response", resp).Msg("wrote schema")
+
+ if cobrautil.MustGetBool(cmd, "json") {
+ prettyProto, err := commands.PrettyProto(resp)
+ if err != nil {
+ log.Fatal().Err(err).Msg("failed to convert schema to JSON")
+ }
+
+ console.Println(string(prettyProto))
+ return nil
+ }
+
+ return nil
+}
+
+func schemaWriteCmdFunc(cmd *cobra.Command, args []string) error {
+ intFd, err := safecast.ToInt(uint(os.Stdout.Fd()))
+ if err != nil {
+ return err
+ }
+ if len(args) == 0 && term.IsTerminal(intFd) {
+ return fmt.Errorf("must provide file path or contents via stdin")
+ }
+
+ client, err := client.NewClient(cmd)
+ if err != nil {
+ return err
+ }
+ var schemaBytes []byte
+ switch len(args) {
+ case 1:
+ schemaBytes, err = os.ReadFile(args[0])
+ if err != nil {
+ return fmt.Errorf("failed to read schema file: %w", err)
+ }
+ log.Trace().Str("schema", string(schemaBytes)).Str("file", args[0]).Msg("read schema from file")
+ case 0:
+ schemaBytes, err = io.ReadAll(os.Stdin)
+ if err != nil {
+ return fmt.Errorf("failed to read schema file: %w", err)
+ }
+ log.Trace().Str("schema", string(schemaBytes)).Msg("read schema from stdin")
+ default:
+ panic("schemaWriteCmdFunc called with incorrect number of arguments")
+ }
+
+ if len(schemaBytes) == 0 {
+ return errors.New("attempted to write empty schema")
+ }
+
+ prefix, err := determinePrefixForSchema(cmd.Context(), cobrautil.MustGetString(cmd, "schema-definition-prefix"), client, nil)
+ if err != nil {
+ return err
+ }
+
+ schemaText, err := rewriteSchema(string(schemaBytes), prefix)
+ if err != nil {
+ return err
+ }
+
+ request := &v1.WriteSchemaRequest{Schema: schemaText}
+ log.Trace().Interface("request", request).Msg("writing schema")
+
+ resp, err := client.WriteSchema(cmd.Context(), request)
+ if err != nil {
+ log.Fatal().Err(err).Msg("failed to write schema")
+ }
+ log.Trace().Interface("response", resp).Msg("wrote schema")
+
+ if cobrautil.MustGetBool(cmd, "json") {
+ prettyProto, err := commands.PrettyProto(resp)
+ if err != nil {
+ log.Fatal().Err(err).Msg("failed to convert schema to JSON")
+ }
+
+ console.Println(string(prettyProto))
+ return nil
+ }
+
+ return nil
+}
+
+// rewriteSchema rewrites the given existing schema to include the specified prefix on all definitions.
+func rewriteSchema(existingSchemaText string, definitionPrefix string) (string, error) {
+ if definitionPrefix == "" {
+ return existingSchemaText, nil
+ }
+
+ compiled, err := compiler.Compile(
+ compiler.InputSchema{Source: input.Source("schema"), SchemaString: existingSchemaText},
+ compiler.ObjectTypePrefix(definitionPrefix),
+ compiler.SkipValidation(),
+ )
+ if err != nil {
+ return "", err
+ }
+
+ generated, _, err := generator.GenerateSchema(compiled.OrderedDefinitions)
+ return generated, err
+}
+
+// determinePrefixForSchema determines the prefix to be applied to a schema that will be written.
+//
+// If specifiedPrefix is non-empty, it is returned immediately.
+// If existingSchema is non-nil, it is parsed for the prefix.
+// Otherwise, the client is used to retrieve the existing schema (if any), and the prefix is retrieved from there.
+func determinePrefixForSchema(ctx context.Context, specifiedPrefix string, client client.Client, existingSchema *string) (string, error) {
+ if specifiedPrefix != "" {
+ return specifiedPrefix, nil
+ }
+
+ var schemaText string
+ if existingSchema != nil {
+ schemaText = *existingSchema
+ } else {
+ readSchemaText, err := commands.ReadSchema(ctx, client)
+ if err != nil {
+ return "", nil
+ }
+ schemaText = readSchemaText
+ }
+
+ // If there is no schema found, return the empty string.
+ if schemaText == "" {
+ return "", nil
+ }
+
+ // Otherwise, compile the schema and grab the prefixes of the namespaces defined.
+ found, err := compiler.Compile(
+ compiler.InputSchema{Source: input.Source("schema"), SchemaString: schemaText},
+ compiler.AllowUnprefixedObjectType(),
+ compiler.SkipValidation(),
+ )
+ if err != nil {
+ return "", err
+ }
+
+ foundPrefixes := make([]string, 0, len(found.OrderedDefinitions))
+ for _, def := range found.OrderedDefinitions {
+ if strings.Contains(def.GetName(), "/") {
+ parts := strings.Split(def.GetName(), "/")
+ foundPrefixes = append(foundPrefixes, parts[0])
+ } else {
+ foundPrefixes = append(foundPrefixes, "")
+ }
+ }
+
+ prefixes := stringz.Dedup(foundPrefixes)
+ if len(prefixes) == 1 {
+ prefix := prefixes[0]
+ log.Debug().Str("prefix", prefix).Msg("found schema definition prefix")
+ return prefix, nil
+ }
+
+ return "", nil
+}