Skip to content

Commit

Permalink
feat(iamcel): add join function
Browse files Browse the repository at this point in the history
Join function combines two resource names, using [resourcename.Join]
from aip-go.

[resourcename.Join]: https://pkg.go.dev/go.einride.tech/aip/resourcename#Join
  • Loading branch information
radhus committed Oct 23, 2023
1 parent f76af83 commit 6fa647e
Show file tree
Hide file tree
Showing 9 changed files with 170 additions and 3 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,10 @@ Tests `caller`s permissions against any `resources`. This test asserts that the

Resolves an ancestor of `resource` using `pattern`. An input of `ancestor("foo/1/bar/2", "foo/{foo}")` will yield the result `"foo/1"`.

#### [`join(parent string, resource string) string`](./iamcel/join.go)

Joins a `resource` name with a `parent` resource name. An input of `join("foo/1", "bar/2")` will yield the result `"foo/1/bar/2"`.

#### [`caller.member(kind string) string`](./iamcel/member.go)

Returns the first IAM member value from the caller's member list which matches the member kind, or fails if there are no such kind.
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ require (
cloud.google.com/go/longrunning v0.5.1
cloud.google.com/go/spanner v1.51.0
github.com/google/cel-go v0.18.1
go.einride.tech/aip v0.62.0
go.einride.tech/aip v0.63.0
go.einride.tech/spanner-aip v0.53.0
google.golang.org/api v0.146.0
google.golang.org/genproto v0.0.0-20230913181813-007df8e322eb
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
go.einride.tech/aip v0.62.0 h1:DVHT0kgIhHfEqcbTUZ/tKTc+YButvOuTVT4JQFWDGo0=
go.einride.tech/aip v0.62.0/go.mod h1:YVrCQRL7SCB5Mv7i2ZF1R6vkLPh844RQBCLrrLcefaU=
go.einride.tech/aip v0.63.0 h1:1u2ppyKS9hj++bp+q4rOEma7dAbG7ZPl6tSBLf8t+wo=
go.einride.tech/aip v0.63.0/go.mod h1:kK5nO4xh3JoniXp64dxgT474Egmr5L7SlsGD6xvP6fU=
go.einride.tech/spanner-aip v0.53.0 h1:t9thNf7sAA5nDGmRdAXIv9ekerVboUASPSXm67BnC8I=
go.einride.tech/spanner-aip v0.53.0/go.mod h1:ckdDj396+YDxDWjnrifR4pDBnvV7gfgjUGCOznqdq1Q=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
Expand Down
1 change: 1 addition & 0 deletions iamauthz/after.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ func NewAfterMethodAuthorization(
iamcel.NewTestAnyFunctionImplementation(options, permissionTester),
iamcel.NewAncestorFunctionImplementation(),
iamcel.NewMemberFunctionImplementation(),
iamcel.NewJoinFunctionImplementation(),
),
)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions iamauthz/before.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ func NewBeforeMethodAuthorization(
iamcel.NewTestAnyFunctionImplementation(options, permissionTester),
iamcel.NewAncestorFunctionImplementation(),
iamcel.NewMemberFunctionImplementation(),
iamcel.NewJoinFunctionImplementation(),
),
)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions iamcel/after.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func NewAfterEnv(method protoreflect.MethodDescriptor) (*cel.Env, error) {
NewTestAnyFunctionDeclaration(),
NewAncestorFunctionDeclaration(),
NewMemberFunctionDeclaration(),
NewJoinFunctionDeclaration(),
),
)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions iamcel/before.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func NewBeforeEnv(method protoreflect.MethodDescriptor) (*cel.Env, error) {
NewTestAnyFunctionDeclaration(),
NewAncestorFunctionDeclaration(),
NewMemberFunctionDeclaration(),
NewJoinFunctionDeclaration(),
),
)
if err != nil {
Expand Down
46 changes: 46 additions & 0 deletions iamcel/join.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package iamcel

import (
"github.com/google/cel-go/checker/decls"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"github.com/google/cel-go/interpreter/functions"
"go.einride.tech/aip/resourcename"
expr "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
)

// JoinFunction is the name of the CEL descendant function.
const JoinFunction = "join"

const joinFunctionOverload = "join_string_string"

// NewJoinFunctionDeclaration creates a new declaration for the descendant function.
func NewJoinFunctionDeclaration() *expr.Decl {
return decls.NewFunction(
JoinFunction,
// TODO: if ever possible in CEL-go, declare this as a variadic function.
decls.NewOverload(
joinFunctionOverload,
[]*expr.Type{decls.String, decls.String},
decls.String,
),
)
}

// NewJoinFunctionImplementation creates a new implementation for the descendant function.
func NewJoinFunctionImplementation() *functions.Overload {
return &functions.Overload{
Operator: joinFunctionOverload,
Binary: func(parentVal, childVal ref.Val) ref.Val {
parent, ok := parentVal.Value().(string)
if !ok {
return types.NewErr("parent: unexpected type of arg 1, expected string but got %T", parentVal.Value())
}
child, ok := childVal.Value().(string)
if !ok {
return types.NewErr("child: unexpected type of arg 2, expected string but got %T", childVal.Value())
}
return types.String(resourcename.Join(parent, child))
},
}
}
113 changes: 113 additions & 0 deletions iamcel/join_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package iamcel

import (
"testing"

"github.com/google/cel-go/cel"
"gotest.tools/v3/assert"
)

func TestJoinFunction(t *testing.T) {
env, err := cel.NewEnv(cel.Declarations(NewJoinFunctionDeclaration()))
assert.NilError(t, err)
t.Run("ok", func(t *testing.T) {
ast, issues := env.Compile(`join('parent/1', 'child/2')`)
assert.NilError(t, issues.Err())
//nolint: staticcheck // TODO: migrate to new top-level API
program, err := env.Program(ast, cel.Functions(NewJoinFunctionImplementation()))
assert.NilError(t, err)
result, _, err := program.Eval(map[string]interface{}(nil))
assert.NilError(t, err)
assert.Equal(t, "parent/1/child/2", result.Value().(string))
})
t.Run("root parent", func(t *testing.T) {
ast, issues := env.Compile(`join('/', 'child/2')`)
assert.NilError(t, issues.Err())
//nolint: staticcheck // TODO: migrate to new top-level API
program, err := env.Program(ast, cel.Functions(NewJoinFunctionImplementation()))
assert.NilError(t, err)
result, _, err := program.Eval(map[string]interface{}(nil))
assert.NilError(t, err)
assert.Equal(t, "child/2", result.Value().(string))
})
t.Run("root child", func(t *testing.T) {
ast, issues := env.Compile(`join('parent/1', '/')`)
assert.NilError(t, issues.Err())
//nolint: staticcheck // TODO: migrate to new top-level API
program, err := env.Program(ast, cel.Functions(NewJoinFunctionImplementation()))
assert.NilError(t, err)
result, _, err := program.Eval(map[string]interface{}(nil))
assert.NilError(t, err)
assert.Equal(t, "parent/1", result.Value().(string))
})
t.Run("root parent and child", func(t *testing.T) {
ast, issues := env.Compile(`join('/', '/')`)
assert.NilError(t, issues.Err())
//nolint: staticcheck // TODO: migrate to new top-level API
program, err := env.Program(ast, cel.Functions(NewJoinFunctionImplementation()))
assert.NilError(t, err)
result, _, err := program.Eval(map[string]interface{}(nil))
assert.NilError(t, err)
assert.Equal(t, "/", result.Value().(string))
})
t.Run("empty parent", func(t *testing.T) {
ast, issues := env.Compile(`join('', 'child/2')`)
assert.NilError(t, issues.Err())
//nolint: staticcheck // TODO: migrate to new top-level API
program, err := env.Program(ast, cel.Functions(NewJoinFunctionImplementation()))
assert.NilError(t, err)
result, _, err := program.Eval(map[string]interface{}(nil))
assert.NilError(t, err)
assert.Equal(t, "child/2", result.Value().(string))
})
t.Run("empty child", func(t *testing.T) {
ast, issues := env.Compile(`join('parent/1', '')`)
assert.NilError(t, issues.Err())
//nolint: staticcheck // TODO: migrate to new top-level API
program, err := env.Program(ast, cel.Functions(NewJoinFunctionImplementation()))
assert.NilError(t, err)
result, _, err := program.Eval(map[string]interface{}(nil))
assert.NilError(t, err)
assert.Equal(t, "parent/1", result.Value().(string))
})
t.Run("parent slash suffix", func(t *testing.T) {
ast, issues := env.Compile(`join('parent/1/', 'child/2')`)
assert.NilError(t, issues.Err())
//nolint: staticcheck // TODO: migrate to new top-level API
program, err := env.Program(ast, cel.Functions(NewJoinFunctionImplementation()))
assert.NilError(t, err)
result, _, err := program.Eval(map[string]interface{}(nil))
assert.NilError(t, err)
assert.Equal(t, "parent/1/child/2", result.Value().(string))
})
t.Run("child slash suffix", func(t *testing.T) {
ast, issues := env.Compile(`join('parent/1', 'child/2/')`)
assert.NilError(t, issues.Err())
//nolint: staticcheck // TODO: migrate to new top-level API
program, err := env.Program(ast, cel.Functions(NewJoinFunctionImplementation()))
assert.NilError(t, err)
result, _, err := program.Eval(map[string]interface{}(nil))
assert.NilError(t, err)
assert.Equal(t, "parent/1/child/2", result.Value().(string))
})
t.Run("parent slash prefix", func(t *testing.T) {
ast, issues := env.Compile(`join('/parent/1', 'child/2')`)
assert.NilError(t, issues.Err())
//nolint: staticcheck // TODO: migrate to new top-level API
program, err := env.Program(ast, cel.Functions(NewJoinFunctionImplementation()))
assert.NilError(t, err)
result, _, err := program.Eval(map[string]interface{}(nil))
assert.NilError(t, err)
assert.Equal(t, "parent/1/child/2", result.Value().(string))
})
t.Run("child slash prefix", func(t *testing.T) {
ast, issues := env.Compile(`join('parent/1', '/child/2')`)
assert.NilError(t, issues.Err())
//nolint: staticcheck // TODO: migrate to new top-level API
program, err := env.Program(ast, cel.Functions(NewJoinFunctionImplementation()))
assert.NilError(t, err)
result, _, err := program.Eval(map[string]interface{}(nil))
assert.NilError(t, err)
assert.Equal(t, "parent/1/child/2", result.Value().(string))
})
}

0 comments on commit 6fa647e

Please sign in to comment.