Skip to content

Commit

Permalink
feat(model): add transform command & support fga dsl output (#100)
Browse files Browse the repository at this point in the history
  • Loading branch information
rhamzeh committed Jul 28, 2023
2 parents 935c64f + 8dfc697 commit 7e692e7
Show file tree
Hide file tree
Showing 20 changed files with 878 additions and 68 deletions.
8 changes: 5 additions & 3 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,17 @@ linters-settings:
allow:
- $gostd
- github.com/mattn/go-isatty
- github.com/muesli/mango-cobra
- github.com/muesli/roff
- github.com/nwidger/jsoncolor
- github.com/oklog/ulid/v2
- github.com/openfga/cli
- github.com/openfga/go-sdk
- github.com/openfga/language
- github.com/openfga/openfga
- github.com/spf13/cobra
- github.com/spf13/pflag
- github.com/spf13/viper
- github.com/muesli/mango-cobra
- github.com/muesli/roff
- go.buf.build/openfga/go/openfga/api
- google.golang.org/protobuf/encoding/protojson
- gopkg.in/yaml.v3
Expand All @@ -52,9 +53,10 @@ linters-settings:
allow:
- $gostd
- github.com/golang/mock/gomock
- github.com/stretchr
- github.com/openfga/cli
- github.com/openfga/go-sdk
- github.com/openfga/openfga
- github.com/stretchr

tagliatelle:
case:
Expand Down
36 changes: 35 additions & 1 deletion cmd/model/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import (
"fmt"
"os"

"github.com/openfga/cli/internal/authorizationmodel"
"github.com/openfga/cli/internal/clierrors"
"github.com/openfga/cli/internal/cmdutils"
"github.com/openfga/cli/internal/fga"
"github.com/openfga/cli/internal/output"
Expand Down Expand Up @@ -52,6 +54,15 @@ func getModel(clientConfig fga.ClientConfig, fgaClient client.SdkClient) (*openf
return nil, fmt.Errorf("failed to get model %v due to %w", clientConfig.AuthorizationModelID, err)
}

if model.AuthorizationModel == nil {
// If there is no model, try to get the store
if _, err := fgaClient.GetStore(context.Background()).Execute(); err != nil {
return nil, fmt.Errorf("failed to get model %v due to %w", clientConfig.AuthorizationModelID, err)
}

return nil, fmt.Errorf("%w", clierrors.ErrAuthorizationModelNotFound)
}

return model, nil
}

Expand All @@ -74,13 +85,36 @@ var getCmd = &cobra.Command{
return err
}

return output.Display(*response) //nolint:wrapcheck
authModel := authorizationmodel.AuthzModel{}
authModel.Set(*response.AuthorizationModel)

fields, err := cmd.Flags().GetStringArray("field")
if err != nil {
return fmt.Errorf("failed to parse field array flag due to %w", err)
}

if getOutputFormat == authorizationmodel.ModelFormatJSON {
return output.Display(authModel.DisplayAsJSON(fields)) //nolint:wrapcheck
}

dslModel, err := authModel.DisplayAsDSL(fields)
if err != nil {
return fmt.Errorf("failed to display model due to %w", err)
}

fmt.Printf("%v", *dslModel)

return nil
},
}

var getOutputFormat = authorizationmodel.ModelFormatFGA

func init() {
getCmd.Flags().String("model-id", "", "Authorization Model ID")
getCmd.Flags().String("store-id", "", "Store ID")
getCmd.Flags().StringArray("field", []string{"model"}, "Fields to display, choices are: id, created_at and model") //nolint:lll
getCmd.Flags().Var(&getOutputFormat, "format", `Authorization model output format. Can be "fga" or "json"`)

if err := getCmd.MarkFlagRequired("store-id"); err != nil {
fmt.Printf("error setting flag as required - %v: %v\n", "cmd/models/get", err)
Expand Down
17 changes: 16 additions & 1 deletion cmd/model/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"fmt"
"os"

"github.com/openfga/cli/internal/authorizationmodel"
"github.com/openfga/cli/internal/cmdutils"
"github.com/openfga/cli/internal/output"
openfga "github.com/openfga/go-sdk"
Expand Down Expand Up @@ -86,13 +87,27 @@ var listCmd = &cobra.Command{
return err
}

return output.Display(*response) //nolint:wrapcheck
fields, err := cmd.Flags().GetStringArray("field")
if err != nil {
return fmt.Errorf("failed to parse field array flag due to %w", err)
}

models := authorizationmodel.AuthzModelList{}
authzModels := *response.AuthorizationModels
for index := 0; index < len(authzModels); index++ {
authModel := authorizationmodel.AuthzModel{}
authModel.Set(authzModels[index])
models.AuthorizationModels = append(models.AuthorizationModels, authModel.DisplayAsJSON(fields))
}

return output.Display(models) //nolint:wrapcheck
},
}

func init() {
listCmd.Flags().Int("max-pages", MaxModelsPagesLength, "Max number of pages to get.")
listCmd.Flags().String("store-id", "", "Store ID")
listCmd.Flags().StringArray("field", []string{"id", "created_at"}, "Fields to display, choices are: id, created_at and model") //nolint:lll

if err := listCmd.MarkFlagRequired("store-id"); err != nil {
fmt.Printf("error setting flag as required - %v: %v\n", "cmd/models/list", err)
Expand Down
1 change: 1 addition & 0 deletions cmd/model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,6 @@ func init() {
ModelCmd.AddCommand(listCmd)
ModelCmd.AddCommand(getCmd)
ModelCmd.AddCommand(validateCmd)
ModelCmd.AddCommand(transformCmd)
ModelCmd.PersistentFlags().String("store-id", "", "Store ID")
}
83 changes: 83 additions & 0 deletions cmd/model/transform.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
Copyright © 2023 OpenFGA
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package model

import (
"fmt"

"github.com/openfga/cli/internal/authorizationmodel"
"github.com/openfga/cli/internal/output"
openfga "github.com/openfga/go-sdk"
"github.com/spf13/cobra"
)

// transformCmd represents the transform command.
var transformCmd = &cobra.Command{
Use: "transform",
Short: "Transforms an authorization model",
Example: `fga model transform --file=model.json --from-json
fga model transform --file=model.fga
fga model transform '{"schema_version":"1.1,"type_definitions":[{"type":"user"}]} --from-json'`,
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
var inputModel string
if err := authorizationmodel.ReadFromInputFileOrArg(
cmd,
args,
"file",
false,
&inputModel,
openfga.PtrString(""),
&writeInputFormat); err != nil {
return err //nolint:wrapcheck
}

fields, err := cmd.Flags().GetStringArray("field")
if err != nil {
return fmt.Errorf("failed to parse field array flag due to %w", err)
}

authModel := authorizationmodel.AuthzModel{}
if transformInputFormat == authorizationmodel.ModelFormatJSON {
if err := authModel.ReadFromJSONString(inputModel); err != nil {
return err //nolint:wrapcheck
}

dslModel, err := authModel.DisplayAsDSL(fields)
if err != nil {
return fmt.Errorf("failed to transform model due to %w", err)
}
fmt.Printf("%v", *dslModel)

return nil
}

if err := authModel.ReadFromDSLString(inputModel); err != nil {
return err //nolint:wrapcheck
}

return output.Display(authModel.DisplayAsJSON(fields)) //nolint:wrapcheck
},
}

var transformInputFormat = authorizationmodel.ModelFormatFGA

func init() {
transformCmd.Flags().String("file", "", "File Name. The file should have the model in the JSON or DSL format")
transformCmd.Flags().Var(&transformInputFormat, "input-format", `Authorization model input format. Can be "fga" or "json"`) //nolint:lll
transformCmd.Flags().StringArray("field", []string{"id", "created_at", "model"}, "Fields to display, choices are: id, created_at and model") //nolint:lll
}
48 changes: 44 additions & 4 deletions cmd/model/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ import (
"time"

"github.com/oklog/ulid/v2"
"github.com/openfga/cli/internal/authorizationmodel"
"github.com/openfga/cli/internal/output"
openfga "github.com/openfga/go-sdk"
"github.com/openfga/openfga/pkg/typesystem"
"github.com/spf13/cobra"
pb "go.buf.build/openfga/go/openfga/api/openfga/v1"
Expand All @@ -35,13 +37,22 @@ type validationResult struct {
Error *string `json:"error,omitempty"`
}

func validate(inputModel string) validationResult {
func validate(inputModel authorizationmodel.AuthzModel) validationResult {
model := &pb.AuthorizationModel{}
output := validationResult{
IsValid: true,
}

err := protojson.Unmarshal([]byte(inputModel), model)
modelJSONString, err := inputModel.GetAsJSONString()
if err != nil {
output.IsValid = false
errorString := "unable to parse json input"
output.Error = &errorString

return output
}

err = protojson.Unmarshal([]byte(*modelJSONString), model)
if err != nil {
output.IsValid = false
errorString := "unable to parse json input"
Expand Down Expand Up @@ -81,13 +92,42 @@ var validateCmd = &cobra.Command{
Short: "Validate Authorization Model",
Long: "Validates that an authorization model is valid.",
Example: `fga model validate '{"schema_version":"1.1,"type_definitions":[{"type":"user"}]}'`,
Args: cobra.ExactArgs(1),
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
response := validate(args[0])
var inputModel string
if err := authorizationmodel.ReadFromInputFileOrArg(
cmd,
args,
"file",
false,
&inputModel,
openfga.PtrString(""),
&validateInputFormat); err != nil {
return err //nolint:wrapcheck
}

authModel := authorizationmodel.AuthzModel{}
var err error

if validateInputFormat == authorizationmodel.ModelFormatJSON {
err = authModel.ReadFromJSONString(inputModel)
} else {
err = authModel.ReadFromDSLString(inputModel)
}

if err != nil {
return err //nolint:wrapcheck
}

response := validate(authModel)

return output.Display(response) //nolint:wrapcheck
},
}

var validateInputFormat = authorizationmodel.ModelFormatDefault

func init() {
validateCmd.Flags().String("file", "", "File Name. The file should have the model in the JSON or DSL format")
validateCmd.Flags().Var(&validateInputFormat, "format", `Authorization model input format. Can be "fga" or "json"`)
}
8 changes: 7 additions & 1 deletion cmd/model/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"reflect"
"testing"

"github.com/openfga/cli/internal/authorizationmodel"
openfga "github.com/openfga/go-sdk"
)

Expand Down Expand Up @@ -80,7 +81,12 @@ func TestValidate(t *testing.T) {
t.Run(test.Name, func(t *testing.T) {
t.Parallel()

output := validate(test.Input)
model := authorizationmodel.AuthzModel{}
err := model.ReadFromJSONString(test.Input)
if err != nil {
return
}
output := validate(model)

if !reflect.DeepEqual(output, test.ExpectedOutput) {
t.Fatalf("Expect output %v actual %v", test.ExpectedOutput, output)
Expand Down
Loading

0 comments on commit 7e692e7

Please sign in to comment.