-
-
Notifications
You must be signed in to change notification settings - Fork 347
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: relationtuple parse command (#490)
This command parses the relation tuple format used in the docs. It greatly improves the experience when copying something from the documentation. It can especially be used to pipe relation tuples into other commands, e.g.: ```shell echo "messages:02y_15_4w350m3#decypher@john" | \ keto relation-tuple parse - --format json | \ keto relation-tuple create - ```
- Loading branch information
Showing
7 changed files
with
334 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package relationtuple | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"os" | ||
"strings" | ||
|
||
"github.com/ory/x/cmdx" | ||
"github.com/spf13/cobra" | ||
|
||
"github.com/ory/keto/internal/relationtuple" | ||
) | ||
|
||
func newParseCmd() *cobra.Command { | ||
cmd := &cobra.Command{ | ||
Use: "parse", | ||
Short: "Parse human readable relation tuples.", | ||
Long: "Parse human readable relation tuples as used in the documentation. Supports various output formats. Especially useful for piping into other commands by using `--format json`.", | ||
Args: cobra.MinimumNArgs(1), | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
var rts []*relationtuple.InternalRelationTuple | ||
for _, fn := range args { | ||
rtss, err := parseFile(cmd, fn) | ||
if err != nil { | ||
return err | ||
} | ||
rts = append(rts, rtss...) | ||
} | ||
|
||
if len(rts) == 1 { | ||
cmdx.PrintRow(cmd, rts[0]) | ||
return nil | ||
} | ||
cmdx.PrintTable(cmd, relationtuple.NewRelationCollection(rts)) | ||
return nil | ||
}, | ||
} | ||
|
||
cmdx.RegisterFormatFlags(cmd.Flags()) | ||
|
||
return cmd | ||
} | ||
|
||
func parseFile(cmd *cobra.Command, fn string) ([]*relationtuple.InternalRelationTuple, error) { | ||
var f io.Reader | ||
if fn == "-" { | ||
// set human readable filename here for debug and error messages | ||
fn = "stdin" | ||
f = cmd.InOrStdin() | ||
} else { | ||
ff, err := os.Open(fn) | ||
if err != nil { | ||
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not open file %s: %v\n", fn, err) | ||
return nil, cmdx.FailSilently(cmd) | ||
} | ||
defer ff.Close() | ||
f = ff | ||
} | ||
|
||
fc, err := io.ReadAll(f) | ||
if err != nil { | ||
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could read file %s: %v\n", fn, err) | ||
return nil, cmdx.FailSilently(cmd) | ||
} | ||
|
||
parts := strings.Split(string(fc), "\n") | ||
rts := make([]*relationtuple.InternalRelationTuple, 0, len(parts)) | ||
for i, row := range parts { | ||
row = strings.TrimSpace(row) | ||
// ignore comments and empty lines | ||
if row == "" || strings.HasPrefix(row, "//") { | ||
continue | ||
} | ||
|
||
rt, err := (&relationtuple.InternalRelationTuple{}).FromString(row) | ||
if err != nil { | ||
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "Could not decode %s:%d\n %s\n\n%v\n", fn, i+1, row, err) | ||
return nil, cmdx.FailSilently(cmd) | ||
} | ||
rts = append(rts, rt) | ||
} | ||
|
||
return rts, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
package relationtuple | ||
|
||
import ( | ||
"bytes" | ||
"os" | ||
"path/filepath" | ||
"testing" | ||
|
||
"github.com/spf13/cobra" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/ory/keto/internal/relationtuple" | ||
) | ||
|
||
// the command delegates most of the functionality to the parseFile helper, so we test that | ||
func TestParseCmdParseFile(t *testing.T) { | ||
for _, tc := range []struct { | ||
input, name string | ||
expected []*relationtuple.InternalRelationTuple | ||
}{ | ||
{ | ||
name: "single basic tuple", | ||
input: "nspace:obj#rel@sub\n", | ||
expected: []*relationtuple.InternalRelationTuple{{ | ||
Namespace: "nspace", | ||
Object: "obj", | ||
Relation: "rel", | ||
Subject: &relationtuple.SubjectID{ID: "sub"}, | ||
}}, | ||
}, | ||
{ | ||
name: "multiple tuples", | ||
input: `nspace:obj1#rel@sub1 | ||
nspace:obj2#rel@sub2 | ||
nspace:obj2#rel@(nspace:obj2#rel)`, | ||
expected: []*relationtuple.InternalRelationTuple{ | ||
{ | ||
Namespace: "nspace", | ||
Object: "obj1", | ||
Relation: "rel", | ||
Subject: &relationtuple.SubjectID{ID: "sub1"}, | ||
}, | ||
{ | ||
Namespace: "nspace", | ||
Object: "obj2", | ||
Relation: "rel", | ||
Subject: &relationtuple.SubjectID{ID: "sub2"}, | ||
}, | ||
{ | ||
Namespace: "nspace", | ||
Object: "obj2", | ||
Relation: "rel", | ||
Subject: &relationtuple.SubjectSet{ | ||
Namespace: "nspace", | ||
Object: "obj2", | ||
Relation: "rel", | ||
}, | ||
}, | ||
}, | ||
}, | ||
{ | ||
name: "crap around tuples", | ||
input: `// foo comment | ||
nspace:obj#rel@sub | ||
// also indentation and trailing spaces | ||
nspace:indent#rel@sub `, | ||
expected: []*relationtuple.InternalRelationTuple{ | ||
{ | ||
Namespace: "nspace", | ||
Object: "obj", | ||
Relation: "rel", | ||
Subject: &relationtuple.SubjectID{ID: "sub"}, | ||
}, | ||
{ | ||
Namespace: "nspace", | ||
Object: "indent", | ||
Relation: "rel", | ||
Subject: &relationtuple.SubjectID{ID: "sub"}, | ||
}, | ||
}, | ||
}, | ||
} { | ||
t.Run("case="+tc.name, func(t *testing.T) { | ||
cmd := &cobra.Command{} | ||
cmd.SetIn(bytes.NewBufferString(tc.input)) | ||
|
||
actual, err := parseFile(cmd, "-") | ||
require.NoError(t, err) | ||
assert.Equal(t, tc.expected, actual) | ||
}) | ||
} | ||
|
||
t.Run("case=reads from fs", func(t *testing.T) { | ||
dir := t.TempDir() | ||
fn := filepath.Join(dir, "test.tuples") | ||
require.NoError(t, os.WriteFile(fn, []byte(` | ||
nspace:obj1#rel@sub1 | ||
nspace:obj2#rel@sub2`), 0600)) | ||
|
||
actual, err := parseFile(&cobra.Command{}, fn) | ||
require.NoError(t, err) | ||
assert.Equal(t, []*relationtuple.InternalRelationTuple{ | ||
{ | ||
Namespace: "nspace", | ||
Object: "obj1", | ||
Relation: "rel", | ||
Subject: &relationtuple.SubjectID{ID: "sub1"}, | ||
}, | ||
{ | ||
Namespace: "nspace", | ||
Object: "obj2", | ||
Relation: "rel", | ||
Subject: &relationtuple.SubjectID{ID: "sub2"}, | ||
}, | ||
}, actual) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
16 changes: 5 additions & 11 deletions
16
contrib/docs-code-samples/simple-access-check-guide/00-write-direct-access/cli.sh
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,8 @@ | ||
#!/bin/bash | ||
set -euo pipefail | ||
|
||
relationtuple=' | ||
{ | ||
"namespace": "messages", | ||
"object": "02y_15_4w350m3", | ||
"relation": "decypher", | ||
"subject": "john" | ||
}' | ||
|
||
keto relation-tuple create <(echo "$relationtuple") >/dev/null \ | ||
&& echo "Successfully created tuple" \ | ||
|| echo "Encountered error" | ||
echo "messages:02y_15_4w350m3#decypher@john" | \ | ||
keto relation-tuple parse - --format json | \ | ||
keto relation-tuple create - >/dev/null \ | ||
&& echo "Successfully created tuple" \ | ||
|| echo "Encountered error" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters