Skip to content

Commit

Permalink
Implement importing store data
Browse files Browse the repository at this point in the history
Extract a common method to import and parse the store file

Support writing a store

Removed public write method

Add documentation

Rename method name

Fix lint issues

Update cmd/store/write.go

Update cmd/store/write.go

Update cmd/store/write.go

Update cmd/store/write.go

Change the write command to import

Fix documentation with import

Rename insert to import

Add support for store-id

rename variable to satisfy linter

Running gofumpt

Co-Authored-By: Raghd Hamzeh <[email protected]>
  • Loading branch information
poovamraj and rhamzeh committed Jan 5, 2024
1 parent 40cdeaf commit 69e5be8
Show file tree
Hide file tree
Showing 8 changed files with 188 additions and 29 deletions.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ A cross-platform CLI to interact with an OpenFGA server
- [Stores](#stores)
- [List All Stores](#list-stores)
- [Create a Store](#create-store)
- [Import a Store](#import-store)
- [Get a Store](#get-store)
- [Delete a Store](#delete-store)
- [Authorization Models](#authorization-models)
Expand Down Expand Up @@ -162,6 +163,7 @@ store-id: 01H0H015178Y2V4CX10C2KGHF4
| Description | command | parameters | example |
|---------------------------------|----------|--------------|----------------------------------------------------------|
| [Create a Store](#create-store) | `create` | `--name` | `fga store create --name="FGA Demo Store"` |
| [Import a Store](#import-store) | `import` | `--file` | `fga store import --file model.fga.yaml` |
| [List Stores](#list-stores) | `list` | | `fga store list` |
| [Get a Store](#get-store) | `get` | `--store-id` | `fga store get --store-id=01H0H015178Y2V4CX10C2KGHF4` |
| [Delete a Store](#delete-store) | `delete` | `--store-id` | `fga store delete --store-id=01H0H015178Y2V4CX10C2KGHF4` |
Expand Down Expand Up @@ -211,6 +213,21 @@ To automatically set the created store id as an environment variable that will t
```bash
export FGA_STORE_ID=$(fga store create --model model.fga | jq -r .store.id)
```
##### Import Store

###### Command
fga store **import**

###### Parameters
* `--file`: File containing the store.

###### Example
`fga store import --file model.fga.yaml`

###### Response
```json
{}
```

##### List Stores

Expand Down
15 changes: 3 additions & 12 deletions cmd/model/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (
"path"

"github.com/spf13/cobra"
"gopkg.in/yaml.v3"

"github.com/openfga/cli/internal/cmdutils"
"github.com/openfga/cli/internal/output"
Expand All @@ -48,17 +47,9 @@ var testCmd = &cobra.Command{
return err //nolint:wrapcheck
}

var storeData storetest.StoreData

testFile, err := os.Open(testsFileName)
if err != nil {
return fmt.Errorf("failed to read file %s due to %w", testsFileName, err)
}
decoder := yaml.NewDecoder(testFile)
decoder.KnownFields(true)
err = decoder.Decode(&storeData)
format, storeData, err := storetest.ReadFromFile(testsFileName, path.Dir(testsFileName))
if err != nil {
return fmt.Errorf("failed to unmarshal file %s due to %w", testsFileName, err)
return err //nolint:wrapcheck
}

verbose, err := cmd.Flags().GetBool("verbose")
Expand All @@ -69,7 +60,7 @@ var testCmd = &cobra.Command{
test, err := storetest.RunTests(
fgaClient,
storeData,
path.Dir(testsFileName),
format,
)
if err != nil {
return fmt.Errorf("error running tests due to %w", err)
Expand Down
4 changes: 2 additions & 2 deletions cmd/store/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func create(fgaClient client.SdkClient, storeName string) (*client.ClientCreateS
return store, nil
}

func createStoreWithModel(
func CreateStoreWithModel(
clientConfig fga.ClientConfig,
storeName string,
inputModel string,
Expand Down Expand Up @@ -121,7 +121,7 @@ export FGA_STORE_ID=$(fga store create --model Model.fga | jq -r .store.id)
return err //nolint:wrapcheck
}

response, err := createStoreWithModel(clientConfig, storeName, inputModel, createModelInputFormat)
response, err := CreateStoreWithModel(clientConfig, storeName, inputModel, createModelInputFormat)
if err != nil {
return err
}
Expand Down
108 changes: 108 additions & 0 deletions cmd/store/import.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
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 store

import (
"context"
"fmt"
"os"
"path"

"github.com/openfga/cli/cmd/model"
"github.com/openfga/cli/internal/authorizationmodel"

"github.com/spf13/cobra"

"github.com/openfga/cli/internal/cmdutils"
"github.com/openfga/cli/internal/output"
"github.com/openfga/cli/internal/storetest"
)

// importCmd represents the get command.
var importCmd = &cobra.Command{
Use: "import",
Short: "Import Store Data",
Long: `Import a store: updating the name, model and appending the global tuples`,
Example: "fga store import --file=model.fga.yaml",
RunE: func(cmd *cobra.Command, args []string) error {
clientConfig := cmdutils.GetClientConfig(cmd)

storeID, err := cmd.Flags().GetString("store-id")
if err != nil {
return fmt.Errorf("failed to get store-id %w", err)
}

fileName, err := cmd.Flags().GetString("file")
if err != nil {
return err //nolint:wrapcheck
}

format, storeData, err := storetest.ReadFromFile(fileName, path.Dir(fileName))
if err != nil {
return err //nolint:wrapcheck
}

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

if storeID == "" {
createStoreAndModelResponse, err := CreateStoreWithModel(clientConfig, storeData.Name, storeData.Model, format)
if err != nil {
return err
}
clientConfig.StoreID = createStoreAndModelResponse.Store.Id
} else {
authModel := authorizationmodel.AuthzModel{}
clientConfig.StoreID = storeID
if format == authorizationmodel.ModelFormatJSON {
err = authModel.ReadFromJSONString(storeData.Model)
} else {
err = authModel.ReadFromDSLString(storeData.Model)
}
if err != nil {
return err //nolint:wrapcheck
}

_, err := model.Write(fgaClient, authModel)
if err != nil {
return fmt.Errorf("failed to write model due to %w", err)
}
}
fgaClient, err = clientConfig.GetFgaClient()
if err != nil {
return fmt.Errorf("failed to initialize FGA Client due to %w", err)
}
_, err = fgaClient.WriteTuples(context.Background()).Body(storeData.Tuples).Execute()
if err != nil {
return err //nolint:wrapcheck
}

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

func init() {
importCmd.Flags().String("file", "", "File Name. The file should have the store")
importCmd.Flags().String("store-id", "", "Store ID")

if err := importCmd.MarkFlagRequired("file"); err != nil {
fmt.Printf("error setting flag as required - %v: %v\n", "cmd/models/write", err)
os.Exit(1)
}
}
1 change: 1 addition & 0 deletions cmd/store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,5 @@ func init() {
StoreCmd.AddCommand(listCmd)
StoreCmd.AddCommand(getCmd)
StoreCmd.AddCommand(deleteCmd)
StoreCmd.AddCommand(importCmd)
}
14 changes: 2 additions & 12 deletions internal/storetest/localstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,30 +72,20 @@ func initLocalStore(

func getLocalServerModelAndTuples(
storeData *StoreData,
basePath string,
format authorizationmodel.ModelFormat,
) (*server.Server, *authorizationmodel.AuthzModel, error) {
var fgaServer *server.Server

var authModel *authorizationmodel.AuthzModel

format, err := storeData.LoadModel(basePath)
if err != nil {
return nil, nil, err
}

err = storeData.LoadTuples(basePath)
if err != nil {
return nil, nil, err
}

if storeData.Model == "" {
return fgaServer, authModel, nil
}

// If we have at least one local test, initialize the local server
datastore := memory.New()

fgaServer, err = server.NewServerWithOpts(server.WithDatastore(datastore))
fgaServer, err := server.NewServerWithOpts(server.WithDatastore(datastore))
if err != nil {
return nil, nil, err //nolint:wrapcheck
}
Expand Down
52 changes: 52 additions & 0 deletions internal/storetest/read-from-input.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
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 storetest

import (
"fmt"
"os"

"github.com/openfga/cli/internal/authorizationmodel"

"gopkg.in/yaml.v3"
)

// ReadFromFile is used to read and parse the Store file.
func ReadFromFile(fileName string, basePath string) (authorizationmodel.ModelFormat, *StoreData, error) {
format := authorizationmodel.ModelFormatDefault

var storeData StoreData

testFile, err := os.Open(fileName)
if err != nil {
return format, nil, fmt.Errorf("failed to read file %s due to %w", fileName, err)
}

decoder := yaml.NewDecoder(testFile)
decoder.KnownFields(true)
err = decoder.Decode(&storeData)

if err != nil {
return format, nil, fmt.Errorf("failed to unmarshal file %s due to %w", fileName, err)
}

if format, err := storeData.LoadModel(basePath); err != nil {
return format, nil, err
}

return format, &storeData, nil
}
6 changes: 3 additions & 3 deletions internal/storetest/tests.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ func RunTest(

func RunTests(
fgaClient *client.OpenFgaClient,
storeData StoreData,
basePath string,
storeData *StoreData,
format authorizationmodel.ModelFormat,
) (TestResults, error) {
test := TestResults{}

fgaServer, authModel, err := getLocalServerModelAndTuples(&storeData, basePath)
fgaServer, authModel, err := getLocalServerModelAndTuples(storeData, format)
if err != nil {
return test, err
}
Expand Down

0 comments on commit 69e5be8

Please sign in to comment.