Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for auto-prefixing to zed schema write and copy #96

Merged
merged 1 commit into from
Mar 11, 2022
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
71 changes: 6 additions & 65 deletions cmd/zed/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,10 @@ import (

v1 "github.com/authzed/authzed-go/proto/authzed/api/v1"
"github.com/authzed/authzed-go/v1"
"github.com/authzed/spicedb/pkg/schemadsl/compiler"
"github.com/authzed/spicedb/pkg/schemadsl/generator"
"github.com/authzed/spicedb/pkg/schemadsl/input"
"github.com/authzed/spicedb/pkg/tuple"
"github.com/jzelinskie/cobrautil"
"github.com/jzelinskie/stringz"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"

"github.com/authzed/zed/internal/decode"
"github.com/authzed/zed/internal/storage"
Expand Down Expand Up @@ -84,54 +78,6 @@ func importCmdFunc(cmd *cobra.Command, args []string) error {
return err
}

// Read the existing schema (if any) to get the prefix.
prefix := cobrautil.MustGetString(cmd, "schema-definition-prefix")
if prefix == "" {
request := &v1.ReadSchemaRequest{}
log.Trace().Interface("request", request).Msg("requesting schema read")

resp, err := client.ReadSchema(context.Background(), request)
if err != nil {
// If the schema was not found, then just use the empty prefix.
errStatus, ok := status.FromError(err)
if !ok || errStatus.Code() != codes.NotFound {
return err
}

log.Debug().Msg("no schema defined")
} else {
empty := ""
found, err := compiler.Compile([]compiler.InputSchema{
{Source: input.Source("schema"), SchemaString: resp.SchemaText},
}, &empty)
if err != nil {
return err
}

foundPrefixes := make([]string, 0, len(found))
for _, def := range found {
if strings.Contains(def.Name, "/") {
parts := strings.Split(def.Name, "/")
foundPrefixes = append(foundPrefixes, parts[0])
} else {
foundPrefixes = append(foundPrefixes, "")
}
}

prefixes := stringz.Dedup(foundPrefixes)
if len(prefixes) == 0 {
return fmt.Errorf("found no schema definition prefixes")
}

if len(prefixes) > 1 {
return fmt.Errorf("found multiple schema definition prefixes: %v", prefixes)
}

prefix = prefixes[0]
log.Debug().Str("prefix", prefix).Msg("found schema definition prefix")
}
}

u, err := url.Parse(args[0])
if err != nil {
return err
Expand All @@ -146,6 +92,11 @@ func importCmdFunc(cmd *cobra.Command, args []string) error {
return err
}

prefix, err := determinePrefixForSchema(cobrautil.MustGetString(cmd, "schema-definition-prefix"), client, nil)
if err != nil {
return err
}

if cobrautil.MustGetBool(cmd, "schema") {
if err := importSchema(client, p.Schema, prefix); err != nil {
return err
Expand All @@ -165,21 +116,11 @@ func importSchema(client *authzed.Client, schema string, definitionPrefix string
log.Info().Msg("importing schema")

// Recompile the schema with the specified prefix.
nsDefs, err := compiler.Compile([]compiler.InputSchema{
{Source: input.Source("schema"), SchemaString: schema},
}, &definitionPrefix)
schemaText, err := rewriteSchema(schema, definitionPrefix)
if err != nil {
return err
}

objectDefs := make([]string, 0, len(nsDefs))
for _, nsDef := range nsDefs {
objectDef, _ := generator.GenerateSource(nsDef)
objectDefs = append(objectDefs, objectDef)
}

schemaText := strings.Join(objectDefs, "\n\n")

// Write the recompiled and regenerated schema.
request := &v1.WriteSchemaRequest{Schema: schemaText}
log.Trace().Interface("request", request).Str("schema", schemaText).Msg("writing schema")
Expand Down
128 changes: 126 additions & 2 deletions cmd/zed/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,21 @@ import (
"fmt"
"io/ioutil"
"os"
"strings"

"github.com/TylerBrock/colorjson"
v1 "github.com/authzed/authzed-go/proto/authzed/api/v1"
"github.com/authzed/authzed-go/v1"
"github.com/authzed/spicedb/pkg/schemadsl/compiler"
"github.com/authzed/spicedb/pkg/schemadsl/generator"
"github.com/authzed/spicedb/pkg/schemadsl/input"
"github.com/jzelinskie/cobrautil"
"github.com/jzelinskie/stringz"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"golang.org/x/term"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"

Expand All @@ -30,9 +36,11 @@ func registerSchemaCmd(rootCmd *cobra.Command) {

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(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")
}

var (
Expand Down Expand Up @@ -147,7 +155,17 @@ func schemaWriteCmdFunc(cmd *cobra.Command, args []string) error {
return errors.New("attempted to write empty schema")
}

request := &v1.WriteSchemaRequest{Schema: string(schemaBytes)}
prefix, err := determinePrefixForSchema(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(context.Background(), request)
Expand Down Expand Up @@ -220,7 +238,17 @@ func schemaCopyCmdFunc(cmd *cobra.Command, args []string) error {
}
log.Trace().Interface("response", readResp).Msg("read schema")

writeRequest := &v1.WriteSchemaRequest{Schema: readResp.SchemaText}
prefix, err := determinePrefixForSchema(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(context.Background(), writeRequest)
Expand All @@ -241,3 +269,99 @@ func schemaCopyCmdFunc(cmd *cobra.Command, args []string) error {

return nil
}

// rewriteSchema rewrites the given existing schema to include the specified prefix on all definitions.
func rewriteSchema(existingSchemaText string, definitionPrefix string) (string, error) {
nsDefs, err := compiler.Compile([]compiler.InputSchema{
{Source: input.Source("schema"), SchemaString: existingSchemaText},
}, &definitionPrefix)
if err != nil {
return "", err
}

objectDefs := make([]string, 0, len(nsDefs))
for _, nsDef := range nsDefs {
objectDef, _ := generator.GenerateSource(nsDef)
objectDefs = append(objectDefs, objectDef)
}

return strings.Join(objectDefs, "\n\n"), nil
}

// readSchema calls read schema for the client and returns the schema found.
func readSchema(client *authzed.Client) (string, error) {
request := &v1.ReadSchemaRequest{}
log.Trace().Interface("request", request).Msg("requesting schema read")

resp, err := client.ReadSchema(context.Background(), request)
if err != nil {
errStatus, ok := status.FromError(err)
if !ok || errStatus.Code() != codes.NotFound {
return "", err
}

log.Debug().Msg("no schema defined")
return "", nil
}

return resp.SchemaText, nil
}

// 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(specifiedPrefix string, client *authzed.Client, existingSchema *string) (string, error) {
if specifiedPrefix != "" {
return specifiedPrefix, nil
}

var schemaText string
if existingSchema != nil {
schemaText = *existingSchema
} else {
readSchemaText, err := readSchema(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.
empty := ""
found, err := compiler.Compile([]compiler.InputSchema{
{Source: input.Source("schema"), SchemaString: schemaText},
}, &empty)
if err != nil {
return "", err
}

foundPrefixes := make([]string, 0, len(found))
for _, def := range found {
if strings.Contains(def.Name, "/") {
parts := strings.Split(def.Name, "/")
foundPrefixes = append(foundPrefixes, parts[0])
} else {
foundPrefixes = append(foundPrefixes, "")
}
}

prefixes := stringz.Dedup(foundPrefixes)
if len(prefixes) == 0 {
return "", fmt.Errorf("found no schema definition prefixes")
}

if len(prefixes) > 1 {
return "", fmt.Errorf("found multiple schema definition prefixes: %v", prefixes)
}

prefix := prefixes[0]
log.Debug().Str("prefix", prefix).Msg("found schema definition prefix")
return prefix, nil
}