Skip to content
Open
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
1 change: 1 addition & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ linters:
- github.com/charmbracelet/lipgloss
- github.com/gocarina/gocsv
- github.com/hashicorp/go-multierror
- github.com/itchyny/gojq
- github.com/mattn/go-isatty
- github.com/muesli/mango-cobra
- github.com/muesli/roff
Expand Down
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ A cross-platform CLI to interact with an OpenFGA server
- [Usage](#usage)
- [Configuration](#configuration)
- [Custom Headers](#custom-headers)
- [Filtering Output](#filtering-output)
- [Commands](#commands)
- [Stores](#stores)
- [List All Stores](#list-stores)
Expand Down Expand Up @@ -205,6 +206,15 @@ custom-headers:
- "X-Request-ID: abc123"
```

#### Filtering Output

Commands that print JSON or YAML accept a `--jq` flag with a [jq](https://jqlang.org/manual/) expression, so you can pick fields out of the output without installing `jq`. The expression runs on the JSON form of the output. String results are printed without quotes, and other results in the command's output format (JSON, or YAML with `--output-format yaml`). Commands that print something else, like CSV, the model DSL or `version`, fail when `--jq` is set. Filters can read environment variables with `env.NAME`. The flag is only read from the command line, not from `FGA_JQ` or the configuration file.

```shell
export FGA_STORE_ID=$(fga store create --model model.fga --jq .store.id)
fga tuple read --output-format simple-json --jq '.[].object'
```

### Commands

#### Stores
Expand Down Expand Up @@ -260,7 +270,7 @@ fga store **create**
To automatically set the created store id as an environment variable that will then be used by the CLI, you can use the following command:

```bash
export FGA_STORE_ID=$(fga store create --model model.fga | jq -r .store.id)
export FGA_STORE_ID=$(fga store create --model model.fga --jq .store.id)
```
##### Import Store

Expand Down
3 changes: 3 additions & 0 deletions cmd/man.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@ import (
mcobra "github.com/muesli/mango-cobra"
"github.com/muesli/roff"
"github.com/spf13/cobra"

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

var manCmd = &cobra.Command{
Use: "man",
Annotations: map[string]string{output.JQUnsupportedAnnotation: "true"},
Short: "Generates manpages",
SilenceUsage: true,
DisableFlagsInUseLine: true,
Expand Down
4 changes: 4 additions & 0 deletions cmd/mapping/mapping.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,17 @@ import (
"github.com/charmbracelet/huh"
"github.com/mattn/go-isatty"
"github.com/spf13/cobra"

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

// MappingCmd is the root of the fga mapping command group.
var MappingCmd = &cobra.Command{
Use: "mapping",
Short: "Manage JSON-to-tuple mappings",
Long: "Validate, test, and run JSON-to-tuple mapping files.",
// The mapping commands print through their own writers, so --jq cannot filter them.
Annotations: map[string]string{output.JQUnsupportedAnnotation: "true"},
}

// promptMappingFile resolves the mapping file path for a command that accepts an
Expand Down
4 changes: 4 additions & 0 deletions cmd/model/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ var getCmd = &cobra.Command{
return output.Display(authModel.DisplayAsJSON(fields))
}

if output.JQFilterSet() {
return fmt.Errorf("%w, use --format json", output.ErrJQNotApplied)
}

dslModel, err := authModel.DisplayAsDSL(fields)
if err != nil {
return fmt.Errorf("failed to display model due to %w", err)
Expand Down
4 changes: 4 additions & 0 deletions cmd/model/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ var modelTestCmd = &cobra.Command{
return fmt.Errorf("failed to get verbose flag: %w", err)
}

if !verbose && output.JQFilterSet() {
return fmt.Errorf("%w, use --verbose to print the test results as JSON", output.ErrJQNotApplied)
}

suppressSummary, err := cmd.Flags().GetBool("suppress-summary")
if err != nil {
return fmt.Errorf("failed to get suppress-summary flag: %w", err)
Expand Down
4 changes: 4 additions & 0 deletions cmd/model/transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ fga model transform --file=fga.mod`,
transformOutputFormat = determineOutputFormat(transformInputFormat, transformOutputFormat)

if transformOutputFormat == authorizationmodel.ModelFormatFGA {
if output.JQFilterSet() {
return fmt.Errorf("%w, not with the DSL output", output.ErrJQNotApplied)
}

dslModel, err := authModel.DisplayAsDSL([]string{"model"})
if err != nil {
return fmt.Errorf("failed to transform model due to %w", err)
Expand Down
48 changes: 48 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/openfga/cli/cmd/store"
"github.com/openfga/cli/cmd/tuple"
"github.com/openfga/cli/internal/cmdutils"
"github.com/openfga/cli/internal/output"
)

var cfgFile string
Expand All @@ -41,6 +42,14 @@ var rootCmd = &cobra.Command{
Short: "OpenFGA CLI",
Long: ``,
SilenceUsage: true,
// Reject --jq before running commands that never print through output.Display.
PersistentPreRunE: func(cmd *cobra.Command, _ []string) error {
return checkJQSupported(cmd)
},
// Fallback for commands that only use output.Display for some of their output formats.
PersistentPostRunE: func(_ *cobra.Command, _ []string) error {
return output.CheckJQFilterApplied()
Comment thread
cvas22 marked this conversation as resolved.
Resolved
},
Comment thread
Siddhant-K-code marked this conversation as resolved.
Resolved
}

// Execute adds all child commands to the root command and sets flags appropriately.
Expand Down Expand Up @@ -71,6 +80,7 @@ func init() {
rootCmd.PersistentFlags().StringArray("api-scopes", []string{}, "API Scopes (repeat option for multiple values). Used in the Client Credentials flow") //nolint:lll
rootCmd.PersistentFlags().StringArray("custom-headers", []string{}, "Custom HTTP headers in 'Header: value' format (repeat option for multiple values)") //nolint:lll
rootCmd.PersistentFlags().Bool("debug", false, "Enable debug mode - can print more detailed information for debugging")
rootCmd.PersistentFlags().String("jq", "", "Filter the JSON or YAML output of a command using a jq expression, e.g. --jq '.store.id'")

_ = rootCmd.Flags().MarkHidden("debug")
rootCmd.MarkFlagsRequiredTogether(
Expand Down Expand Up @@ -129,5 +139,43 @@ func initConfig() {

viperInstance.AutomaticEnv() // read in environment variables that match

// Check this before BindViperToFlags, which marks the flags it sets as changed.
jqFromFlag := rootCmd.PersistentFlags().Changed("jq")

cmdutils.BindViperToFlags(rootCmd, viperInstance)

cobra.CheckErr(output.SetJQFilter(jqExpression(jqFromFlag)))
}

// checkJQSupported returns an error if --jq is set for a command that never prints through
// output.Display: one annotated with output.JQUnsupportedAnnotation, under such a command, or one
// of cobra's help and completion commands.
func checkJQSupported(cmd *cobra.Command) error {
if !output.JQFilterSet() {
return nil
}

for current := cmd; current != nil; current = current.Parent() {
switch {
case current.Annotations[output.JQUnsupportedAnnotation] == "true",
current.Name() == "help",
current.Name() == "completion",
current.Name() == cobra.ShellCompRequestCmd,
current.Name() == cobra.ShellCompNoDescRequestCmd:
return fmt.Errorf("%w, not by %s", output.ErrJQNotApplied, cmd.CommandPath())
}
}

return nil
}

// jqExpression returns the --jq flag. Values from FGA_JQ or the config file are ignored: a filter
// set there would apply to every command, and BindViperToFlags splits values on whitespace, which
// would keep only the last part of an expression such as ".a, .b".
func jqExpression(fromFlag bool) string {
if !fromFlag {
return ""
}

return rootCmd.PersistentFlags().Lookup("jq").Value.String()
}
6 changes: 5 additions & 1 deletion cmd/store/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,11 @@ var exportCmd = &cobra.Command{
return fmt.Errorf("failed to initialize FGA Client due to %w", err)
}

fileName, _ := cmd.Flags().GetString("output-file")
if fileName == "" && output.JQFilterSet() {
return fmt.Errorf("%w, not by store export without --output-file", output.ErrJQNotApplied)
}

maxTupleCount, _ := cmd.Flags().GetUint("max-tuples")

storeData, err := buildStoreData(cmd.Context(), clientConfig, fgaClient, maxTupleCount)
Expand All @@ -156,7 +161,6 @@ var exportCmd = &cobra.Command{
return fmt.Errorf("unable to marshal storedata yaml: %w", err)
}

fileName, _ := cmd.Flags().GetString("output-file")
if fileName == "" {
fmt.Println(string(storeYaml))

Expand Down
6 changes: 5 additions & 1 deletion cmd/tuple/read.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,11 @@ var readCmd = &cobra.Command{
return fmt.Errorf("failed to initialize FGA Client due to %w", err)
}

outputFormat, _ := cmd.Flags().GetString("output-format")
if outputFormat == "csv" && output.JQFilterSet() {
return fmt.Errorf("%w, not with --output-format csv", output.ErrJQNotApplied)
}

user, _ := cmd.Flags().GetString("user")
relation, _ := cmd.Flags().GetString("relation")
object, _ := cmd.Flags().GetString("object")
Expand Down Expand Up @@ -221,7 +226,6 @@ var readCmd = &cobra.Command{
}

simpleOutput, _ := cmd.Flags().GetBool("simple-output")
outputFormat, _ := cmd.Flags().GetString("output-format")

if outputFormat == "csv" {
records, err := response.toCsvDTO()
Expand Down
8 changes: 5 additions & 3 deletions cmd/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,17 @@ import (
"github.com/spf13/cobra"

"github.com/openfga/cli/internal/build"
"github.com/openfga/cli/internal/output"
)

var versionStr = fmt.Sprintf("v`%s` (commit: `%s`, date: `%s`)", build.Version, build.Commit, build.Date)

// versionCmd is the entrypoint for the `fga version“ command.
var versionCmd *cobra.Command = &cobra.Command{
Use: "version",
Short: "Reports the FGA CLI version",
Long: "Reports the FGA CLI version.",
Use: "version",
Annotations: map[string]string{output.JQUnsupportedAnnotation: "true"},
Short: "Reports the FGA CLI version",
Long: "Reports the FGA CLI version.",
RunE: func(_ *cobra.Command, _ []string) error {
fmt.Printf("fga version %s\n", versionStr)

Expand Down
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ toolchain go1.27.3
require (
github.com/charmbracelet/huh v1.0.0
github.com/charmbracelet/lipgloss v1.1.0
github.com/itchyny/gojq v0.12.20
github.com/mattn/go-isatty v0.0.24
github.com/muesli/mango-cobra v1.3.0
github.com/muesli/roff v0.1.0
Expand Down Expand Up @@ -46,6 +47,7 @@ require (
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/envoyproxy/protoc-gen-validate v1.3.3 // indirect
Expand All @@ -63,11 +65,12 @@ require (
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.4 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/itchyny/timefmt-go v0.1.9 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mattn/go-runewidth v0.0.31 // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
Expand Down
11 changes: 8 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ github.com/charmbracelet/x/xpty v0.1.2/go.mod h1:XK2Z0id5rtLWcpeNiMYBccNNBrP2IJn
github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM=
github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
Expand Down Expand Up @@ -148,6 +150,10 @@ github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVU
github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/itchyny/gojq v0.12.20 h1:mvxzKjEJDfeLlPc/15q5Kf6wLtEW2T399BL8xmOd9EQ=
github.com/itchyny/gojq v0.12.20/go.mod h1:cgT47OXUC/BWCBh9V4LuuTRSwFIJNR5loUyqlzbIJbw=
github.com/itchyny/timefmt-go v0.1.9 h1:J4j1qMqZbg5jycwT6zIKfRgZ9aqqMxqanaI6Rr9Ezs8=
github.com/itchyny/timefmt-go v0.1.9/go.mod h1:5E46Q+zj7vbTgWY8o5YkMeYb4I6GeWLFnetPy5oBrAI=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
Expand Down Expand Up @@ -187,8 +193,8 @@ github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsRe
github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.31 h1:1oCV4ReTlosDBc6rYRw5cL8S/4y8iv5oRl4SJ5IBgwo=
github.com/mattn/go-runewidth v0.0.31/go.mod h1:3qAiGCV4Koz/yuveO58qUefmUTRm8r0IGEXZ9jeHp/8=
github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY=
github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
Expand Down Expand Up @@ -258,7 +264,6 @@ github.com/prometheus/procfs v0.22.0 h1:6q9+/JL9IKAPbCmBrv9n5O5Ty3NKnciV5X7YGw0o
github.com/prometheus/procfs v0.22.0/go.mod h1:CvmFr/GVhIjIvWJZW3tgkODBQMRIf0EyWMQLHCHab58=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
Expand Down
Loading