Skip to content

Commit

Permalink
feat(generate): generate pkl testing
Browse files Browse the repository at this point in the history
  • Loading branch information
toniphan21 committed Jun 9, 2024
1 parent be65811 commit 9f28e15
Show file tree
Hide file tree
Showing 7 changed files with 835 additions and 1 deletion.
13 changes: 13 additions & 0 deletions cmd/generate/generate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package generate

import "github.com/spf13/cobra"

var GenerateCmd = &cobra.Command{
Use: "generate",
Short: "Generate code",
Long: "Generate code to facilitate testing, modeling and working with OpenFGA.",
}

func init() {
GenerateCmd.AddCommand(pklCmd)
}
85 changes: 85 additions & 0 deletions cmd/generate/pkl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package generate

import (
"encoding/json"
"fmt"
"github.com/openfga/cli/internal/authorizationmodel"
"github.com/openfga/cli/internal/cmdutils"
"github.com/openfga/cli/internal/generate"
"github.com/openfga/cli/internal/output"
openfga "github.com/openfga/go-sdk"
"github.com/spf13/cobra"
)

var pklCmd = &cobra.Command{
Use: "pkl",
Short: "Generate pkl test utilities",
Long: "Generate pkl test utilities based on the given model",
Example: `fga generate pkl --file=model.json
fga generate --file=fga.mod
fga generate '{"type_definitions":[{"type":"user"},{"type":"document","relations":{"can_view":{"this":{}}},"metadata":{"relations":{"can_view":{"directly_related_user_types":[{"type":"user"}]}}}}],"schema_version":"1.1"}' --format=json
fga generate --file=fga.mod --out=testing
fga generate --file=fga.mod --out=testing --config='{"user": {"base_type_name": "Awesome"}}'`, //nolint:lll
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
clientConfig := cmdutils.GetClientConfig(cmd)

_, err := clientConfig.GetFgaClient()
if err != nil {
return fmt.Errorf("failed to initialize FGA Client due to %w", err)
}

var inputModel string
if err := authorizationmodel.ReadFromInputFileOrArg(
cmd,
args,
"file",
false,
&inputModel,
openfga.PtrString(""),
&writeInputFormat); err != nil {
return err //nolint:wrapcheck
}

authModel := authorizationmodel.AuthzModel{}

err = authModel.ReadModelFromString(inputModel, writeInputFormat)
if err != nil {
return err //nolint:wrapcheck
}

out, err := cmd.Flags().GetString("out")
if err != nil {
return fmt.Errorf("failed to parse output directory due to %w", err)
}
config, err := cmd.Flags().GetString("config")
if err != nil {
return fmt.Errorf("failed to parse config due to %w", err)
}
cn := make(map[string]generate.PklConventionConfig)
err = json.Unmarshal([]byte(config), &cn)
if err != nil {
return fmt.Errorf("failed to parse config content due to %w", err)
}

g := &generate.PklGenerator{
Model: authModel.TypeDefinitions,
Convention: &generate.PklConvention{Config: cn},
}
files, err := g.Generate()
err = files.SaveAll(out)
if err != nil {
return fmt.Errorf("failed to save generated files due to %w", err)
}
return output.Display(fmt.Sprintf("generated files in directory %v successfully", out))
},
}

var writeInputFormat = authorizationmodel.ModelFormatDefault

func init() {
pklCmd.Flags().String("out", "testing", "Output of testing directory")
pklCmd.Flags().String("config", "{}", "Generator configurations")
pklCmd.Flags().String("file", "", "File Name. The file should have the model in the JSON or DSL format")
pklCmd.Flags().Var(&writeInputFormat, "format", `Authorization model input format. Can be "fga", "json", or "modular"`) //nolint:lll
}
2 changes: 2 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/viper"

"github.com/openfga/cli/cmd/generate"
"github.com/openfga/cli/cmd/model"
"github.com/openfga/cli/cmd/query"
"github.com/openfga/cli/cmd/store"
Expand Down Expand Up @@ -81,6 +82,7 @@ func init() {
rootCmd.AddCommand(model.ModelCmd)
rootCmd.AddCommand(tuple.TupleCmd)
rootCmd.AddCommand(query.QueryCmd)
rootCmd.AddCommand(generate.GenerateCmd)
}

// initConfig reads in config file and ENV variables if set.
Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.22.3
require (
github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1
github.com/hashicorp/go-multierror v1.1.1
github.com/iancoleman/strcase v0.3.0
github.com/mattn/go-isatty v0.0.20
github.com/muesli/mango-cobra v1.2.0
github.com/muesli/roff v0.1.0
Expand All @@ -19,6 +20,7 @@ require (
github.com/spf13/viper v1.19.0
github.com/stretchr/testify v1.9.0
go.uber.org/mock v0.4.0
golang.org/x/exp v0.0.0-20240529005216-23cca8864a10
google.golang.org/protobuf v1.34.1
gopkg.in/yaml.v3 v3.0.1
)
Expand Down Expand Up @@ -70,7 +72,6 @@ require (
go.opentelemetry.io/proto/otlp v1.2.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/exp v0.0.0-20240529005216-23cca8864a10 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.20.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT
github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI=
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
Expand Down
94 changes: 94 additions & 0 deletions internal/generate/generate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package generate

import (
"errors"
"fmt"
"os"
"path"
)

type GeneratedFile struct {
Path string
Content string
OverrideIfExists bool
}

type GeneratedFiles struct {
Files []GeneratedFile
}

func (f *GeneratedFiles) SaveAll(out string) error {
for _, file := range f.Files {
if err := file.Save(out); err != nil {
return err
}
}
return nil
}

func (f *GeneratedFile) Save(out string) error {
if f.OverrideIfExists {
if err := f.writeFile(out, f.Path, f.Content); err != nil {
return err
}
} else {
if err := f.writeFileIfNotExists(out, f.Path, f.Content); err != nil {
return err
}
}
return nil
}

func (f *GeneratedFile) writeFileIfNotExists(out, filePath, content string) error {
pwd, err := os.Getwd()
if err != nil {
return fmt.Errorf("failed to get Working Directory %v", err)

Check failure on line 45 in internal/generate/generate.go

View workflow job for this annotation

GitHub Actions / Lints

do not define dynamic errors, use wrapped static errors instead: "fmt.Errorf(\"failed to get Working Directory %v\", err)" (err113)
}

file := path.Join(pwd, out, filePath)
if _, err = os.Stat(file); errors.Is(err, os.ErrNotExist) {
dir := path.Dir(file)
err := os.MkdirAll(dir, os.ModePerm)
if err != nil {
return err
}

f, err := os.Create(file)
if err != nil {
return err
}
defer f.Close()

_, err = f.WriteString(content)
if err != nil {
return err
}
}
return nil
}

func (f *GeneratedFile) writeFile(out, filePath, content string) error {
pwd, err := os.Getwd()
if err != nil {
return fmt.Errorf("failed to get Working Directory %v", err)

Check failure on line 73 in internal/generate/generate.go

View workflow job for this annotation

GitHub Actions / Lints

do not define dynamic errors, use wrapped static errors instead: "fmt.Errorf(\"failed to get Working Directory %v\", err)" (err113)
}

fp := path.Join(pwd, out, filePath)
dir := path.Dir(fp)
err = os.MkdirAll(dir, os.ModePerm)
if err != nil {
return err
}

file, err := os.OpenFile(fp, os.O_RDWR|os.O_CREATE|os.O_TRUNC, os.FileMode(0644))
if err != nil {
return fmt.Errorf("failed to open or create file %v", err)

Check failure on line 85 in internal/generate/generate.go

View workflow job for this annotation

GitHub Actions / Lints

do not define dynamic errors, use wrapped static errors instead: "fmt.Errorf(\"failed to open or create file %v\", err)" (err113)
}
defer file.Close()

_, err = file.WriteString(content)
if err != nil {
return err
}
return nil
}
Loading

0 comments on commit 9f28e15

Please sign in to comment.