generated from crossplane/function-template-go
-
Notifications
You must be signed in to change notification settings - Fork 2
/
fn.go
136 lines (112 loc) · 4.06 KB
/
fn.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package main
import (
"context"
"regexp"
"github.com/google/cel-go/cel"
"google.golang.org/protobuf/types/known/structpb"
"github.com/crossplane/function-sdk-go/errors"
"github.com/crossplane/function-sdk-go/logging"
fnv1 "github.com/crossplane/function-sdk-go/proto/v1"
"github.com/crossplane/function-sdk-go/request"
"github.com/crossplane/function-sdk-go/response"
"github.com/crossplane-contrib/function-cel-filter/input/v1beta1"
)
// Function returns whatever response you ask it to.
type Function struct {
fnv1.UnimplementedFunctionRunnerServiceServer
log logging.Logger
env *cel.Env
}
// NewFunction creates a new Function with a CEL environment.
func NewFunction(log logging.Logger) (*Function, error) {
env, err := cel.NewEnv(
cel.Types(&fnv1.State{}, &structpb.Struct{}),
cel.Variable("observed", cel.ObjectType("apiextensions.fn.proto.v1.State")),
cel.Variable("desired", cel.ObjectType("apiextensions.fn.proto.v1.State")),
cel.Variable("context", cel.ObjectType("google.protobuf.Struct")),
)
return &Function{log: log, env: env}, errors.Wrap(err, "cannot create CEL environment")
}
// RunFunction runs the Function.
func (f *Function) RunFunction(_ context.Context, req *fnv1.RunFunctionRequest) (*fnv1.RunFunctionResponse, error) {
f.log.Info("Running function", "tag", req.GetMeta().GetTag())
rsp := response.To(req, response.DefaultTTL)
in := &v1beta1.Filters{}
if err := request.GetInput(req, in); err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot get filters from %T", req))
return rsp, nil
}
regexps := make([]*regexp.Regexp, len(in.Filters))
celexps := make([]bool, len(in.Filters))
for i := range in.Filters {
// Compile this filter's regular expression.
expr := "^" + in.Filters[i].Name + "$"
re, err := regexp.Compile(expr)
if err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot compile regular expression %q for filter %d", expr, i))
return rsp, nil
}
regexps[i] = re
// Evaluate this filter's CEL expression.
expr = in.Filters[i].Expression
include, err := Evaluate(f.env, req, in.Filters[i].Expression)
if err != nil {
response.Fatal(rsp, errors.Wrapf(err, "cannot evaluate CEL expression %q for filter %d", expr, i))
return rsp, nil
}
celexps[i] = include
}
for name := range rsp.GetDesired().GetResources() {
log := f.log.WithValues("resource-name", name)
for i := range in.Filters {
log = log.WithValues(
"filter-index", i,
"filter-name", in.Filters[i].Name,
"filter-expression", in.Filters[i].Expression,
)
if !regexps[i].MatchString(name) {
log.Debug("Not filtering desired composed resource: composed resource name does not match filter name")
continue
}
if include := celexps[i]; include {
log.Debug("Not filtering desired composed resource: CEL expression evaluated to true")
continue
}
log.Info("Filtering desired composed resource: CEL expression evaluated to false")
delete(rsp.GetDesired().GetResources(), name)
}
}
return rsp, nil
}
// Evaluate the supplied CEL expression.
func Evaluate(env *cel.Env, req *fnv1.RunFunctionRequest, expression string) (bool, error) {
ast, iss := env.Parse(expression)
if iss.Err() != nil {
return false, errors.Wrap(iss.Err(), "cannot parse expression")
}
// Type-check the expression for correctness.
checked, iss := env.Check(ast)
if iss.Err() != nil {
return false, errors.Wrap(iss.Err(), "cannot type-check expression")
}
if !checked.OutputType().IsExactType(cel.BoolType) {
return false, errors.Errorf("expression %q must return a boolean, but will return %s instead", expression, checked.OutputType())
}
program, err := env.Program(checked)
if err != nil {
return false, errors.Wrap(err, "cannot create CEL program")
}
result, _, err := program.Eval(map[string]any{
"observed": req.GetObserved(),
"desired": req.GetDesired(),
"context": req.GetContext(),
})
if err != nil {
return false, errors.Wrap(err, "cannot evaluate CEL program")
}
ret, ok := result.Value().(bool)
if !ok {
return false, errors.Wrap(err, "expression did not return a bool")
}
return ret, nil
}