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

[FGA-98] Add schema convert and schema apply to FGA module #13

Merged
merged 6 commits into from
Aug 28, 2024
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
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ require (
github.com/pkg/errors v0.9.1
github.com/spf13/cobra v1.8.1
github.com/spf13/viper v1.19.0
github.com/workos/workos-go/v4 v4.16.0
github.com/workos/workos-go/v4 v4.21.0
)

require (
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/workos/workos-go/v4 v4.16.0 h1:7BPvVKHI8qhOY/lH4EbBY2CtQs1CDJxXDbfTnYNma3g=
github.com/workos/workos-go/v4 v4.16.0/go.mod h1:CwpXdAWhIE3SxV49qBVeYqWV8ojv0A0L9nM1xnho4/c=
github.com/workos/workos-go/v4 v4.21.0 h1:pEoAJzCsBPU46dL6/PwwwS5BrBV8LWOZQp0mERrRPCc=
atainter marked this conversation as resolved.
Show resolved Hide resolved
github.com/workos/workos-go/v4 v4.21.0/go.mod h1:CwpXdAWhIE3SxV49qBVeYqWV8ojv0A0L9nM1xnho4/c=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
Expand Down
166 changes: 166 additions & 0 deletions internal/cmd/fga.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/spf13/cobra"
"github.com/workos/workos-cli/internal/printer"
"github.com/workos/workos-go/v4/pkg/fga"
"github.com/workos/workos-go/v4/pkg/workos_errors"
)

var resourceTypesFile string
Expand Down Expand Up @@ -65,6 +66,15 @@ func init() {
queryCmd.Flags().String("order", "", "order in which a list of results should be returned (asc or desc)")
fgaCmd.AddCommand(queryCmd)

// schema
convertSchemaCMD.Flags().String("to", "json", "output to (schema or json)")
convertSchemaCMD.Flags().String("output", "pretty", "output pretty or raw. use raw for machine-readable output or writing to a file")
schemaCmd.AddCommand(convertSchemaCMD)
applySchemaCmd.Flags().BoolP("verbose", "v", false, "print extra details about the request")
applySchemaCmd.Flags().Bool("strict", false, "fail if there are warnings")
schemaCmd.AddCommand(applySchemaCmd)
fgaCmd.AddCommand(schemaCmd)

rootCmd.AddCommand(fgaCmd)
}

Expand Down Expand Up @@ -606,6 +616,162 @@ var queryCmd = &cobra.Command{
},
}

var schemaCmd = &cobra.Command{
Use: "schema",
Short: "Manage your schema",
Long: "A schema is a set of resource types and relations that define the structure of your application. Use this command to manage your schema.",
}

var convertSchemaCMD = &cobra.Command{
Use: "convert <input_file>",
Short: "Convert a schema to a JSON representation (or JSON to schema)",
Long: "Convert a schema to a JSON representation (or JSON to schema) that can be used to apply resource types.",
Example: `workos fga schema convert schema.txt -o json`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
to, err := cmd.Flags().GetString("to")
if err != nil {
return errors.Wrap(err, "invalid to flag")
}
output, err := cmd.Flags().GetString("output")
if err != nil {
return errors.Wrap(err, "invalid output flag")
}

bytes, err := os.ReadFile(args[0])
if err != nil {
return errors.Errorf("error reading input file: %v", err)
}

var response fga.ConvertSchemaResponse
switch to {
case "json":
schemaString := string(bytes)
response, err = fga.ConvertSchemaToResourceTypes(context.Background(), fga.ConvertSchemaToResourceTypesOpts{
Schema: schemaString,
})
if err != nil {
return convertSchemaError(err)
}
case "schema":
var resourceTypesWithVersion fga.ConvertResourceTypesToSchemaOpts
err = json.Unmarshal(bytes, &resourceTypesWithVersion)
if err != nil {
return errors.Errorf("error unmarshalling resource types: %v", err)
}
response, err = fga.ConvertResourceTypesToSchema(context.Background(), resourceTypesWithVersion)
if err != nil {
return convertSchemaError(err)
}
default:
return errors.Errorf("invalid conversion: %s", to)
}

switch output {
case "pretty":
printer.PrintMsg("Version:")
printer.PrintMsg(fmt.Sprintf("%s\n", response.Version))

if response.Warnings != nil {
printer.PrintMsg("Warnings:")
for _, warning := range response.Warnings {
printer.PrintMsg(warning.Message)
}
printer.PrintMsg("\n")
}

if response.Schema != nil {
printer.PrintMsg("Schema:")
printer.PrintMsg(*response.Schema)
}

if response.ResourceTypes != nil {
printer.PrintMsg("Resource Types:")
printer.PrintJson(response.ResourceTypes)
}
case "raw":
if response.Schema != nil {
printer.PrintMsg(*response.Schema)
} else {
printer.PrintJson(response.ResourceTypes)
}
default:
return errors.Errorf("invalid output: %s", output)
}
return nil
},
}

var applySchemaCmd = &cobra.Command{
Use: "apply <input_file>",
Short: "Apply a schema",
Long: "Apply a schema to create or update resource types.",
Example: `workos fga schema apply schema.txt`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
verbose, err := cmd.Flags().GetBool("verbose")
if err != nil {
return errors.Wrap(err, "invalid verbose flag")
}
strict, err := cmd.Flags().GetBool("strict")
if err != nil {
return errors.Wrap(err, "invalid strict flag")
}

bytes, err := os.ReadFile(args[0])
if err != nil {
return errors.Errorf("error reading input file: %v", err)
}
// Convert schema to resource types
response, err := fga.ConvertSchemaToResourceTypes(context.Background(), fga.ConvertSchemaToResourceTypesOpts{
Schema: string(bytes),
})
if err != nil {
return convertSchemaError(err)
}

if response.Warnings != nil {
printer.PrintMsg("Warnings:")
for _, warning := range response.Warnings {
printer.PrintMsg(warning.Message)
}
printer.PrintMsg("\n")
if strict {
return errors.New("error applying schema: warnings found (omit --strict to ignore)")
}
}

printer.PrintMsg("applying schema...")

if verbose {
printer.PrintJson(response.ResourceTypes)
}

ops := make([]fga.UpdateResourceTypeOpts, 0)
for _, rt := range response.ResourceTypes {
ops = append(ops, fga.UpdateResourceTypeOpts(rt))
}

_, err = fga.BatchUpdateResourceTypes(context.Background(), ops)
if err != nil {
return errors.Errorf("error applying schema: %v", err)
}

printer.PrintMsg("Schema applied")
return nil
},
}

func convertSchemaError(err error) error {
var target workos_errors.HTTPError
if errors.As(err, &target) {
if len(target.Errors) > 0 {
return errors.Errorf("error converting schema: %s\n\t%s", target.Message, strings.Join(target.Errors, "\n\t"))
}
}
return errors.Errorf("error converting schema: %v", err)
}

func warrantCheckAsString(w fga.WarrantCheck) (string, error) {
s := fmt.Sprintf(
"%s:%s %s %s:%s",
Expand Down
Loading