Skip to content
Merged
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
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,10 @@ contain periods, hyphens and other special characters. Like Helm, this function
also supports [Sprig template functions][sprig] as well as [additional functions](#additional-functions).

[Template options](https://pkg.go.dev/text/template#Template.Option) can be provided using the `Options`
property.
property. The default options are "missingkey=default". This can be
overridden by the `--default-options` CLI flag or the `FUNCTION_GO_TEMPLATING_DEFAULT_OPTIONS`
environment variable. Setting the default-options to "missingkey=error" will cause the template
engine to return an error when a missing key is detected, instead of setting the value to "<no value>".

### Connection Details

Expand Down Expand Up @@ -438,4 +441,4 @@ $ crossplane xpkg build -f package --embed-runtime-image=runtime
[docker]: https://www.docker.com
[cli]: https://docs.crossplane.io/latest/cli
[extra-resources]: https://docs.crossplane.io/latest/concepts/composition-functions/#how-composition-functions-work
[Connection Details Compositions guide]: https://docs.crossplane.io/latest/guides/connection-details-composition/
[Connection Details Compositions guide]: https://docs.crossplane.io/latest/guides/connection-details-composition/
19 changes: 13 additions & 6 deletions fn.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,10 @@ func (*osFS) Open(name string) (fs.File, error) {
type Function struct {
fnv1.UnimplementedFunctionRunnerServiceServer

log logging.Logger
fsys fs.FS
defaultSource string
log logging.Logger
fsys fs.FS
defaultSource string
defaultOptions string
}

type YamlErrorContext struct {
Expand Down Expand Up @@ -93,9 +94,15 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1.RunFunctionRequest)
return rsp, nil
}

if in.Options != nil {
f.log.Debug("setting template options", "options", *in.Options)
err = safeApplyTemplateOptions(tmpl, *in.Options)
if in.Options != nil || f.defaultOptions != "" {
var o []string
if in.Options != nil {
o = *in.Options
} else {
o = strings.Split(f.defaultOptions, ",")
}
f.log.Debug("setting template options", "options", o)
err = safeApplyTemplateOptions(tmpl, o)
if err != nil {
response.Fatal(rsp, errors.Wrap(err, "cannot apply template options"))
return rsp, nil
Expand Down
81 changes: 72 additions & 9 deletions fn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ metadata:

cd = `{"apiVersion":"example.org/v1","kind":"CD","metadata":{"annotations":{"gotemplating.fn.crossplane.io/composition-resource-name":"cool-cd"},"name":"cool-cd"}}`
cdTmpl = `{"apiVersion":"example.org/v1","kind":"CD","metadata":{"annotations":{"gotemplating.fn.crossplane.io/composition-resource-name":"cool-cd"},"name":"cool-cd","labels":{"belongsTo":{{.observed.composite.resource.metadata.name|quote}}}}}`
cdMissingKeyTmpl = `{"apiVersion":"example.org/v1","kind":"CD","metadata":{"name":"cool-cd","labels":{"belongsTo":{{.missing | quote }}}}}`
cdMissingKeyTmpl = `{"apiVersion":"example.org/v1","kind":"CD","metadata":{"annotations":{"gotemplating.fn.crossplane.io/composition-resource-name":"cool-cd"},"name":"cool-cd","labels":{"belongsTo":{{.missing | quote }}}}}`
cdWrongTmpl = `{"apiVersion":"example.org/v1","kind":"CD","metadata":{"name":"cool-cd","labels":{"belongsTo":{{.invalid-key}}}}}`
cdMissingKind = `{"apiVersion":"example.org/v1"}`
cdMissingResourceName = `{"apiVersion":"example.org/v1","kind":"CD","metadata":{"name":"cool-cd"}}`
Expand Down Expand Up @@ -77,9 +77,10 @@ metadata:

func TestRunFunction(t *testing.T) {
type args struct {
ctx context.Context
req *fnv1.RunFunctionRequest
defaultSource string
ctx context.Context
req *fnv1.RunFunctionRequest
defaultSource string
defaultOptions string
}
type want struct {
rsp *fnv1.RunFunctionResponse
Expand Down Expand Up @@ -1243,7 +1244,7 @@ func TestRunFunction(t *testing.T) {
},
},
"InvalidTemplateOption": {
reason: "The Function should return error when an invalid option is provided.",
reason: "The Function should return error when an invalid option is provided as input.",
args: args{
req: &fnv1.RunFunctionRequest{
Input: resource.MustStructObject(
Expand All @@ -1258,6 +1259,7 @@ func TestRunFunction(t *testing.T) {
},
},
},
defaultOptions: "missingkey=default",
},
want: want{
rsp: &fnv1.RunFunctionResponse{
Expand All @@ -1272,6 +1274,66 @@ func TestRunFunction(t *testing.T) {
},
},
},
"InvalidDefaultTemplateOption": {
reason: "The Function should return error when an invalid option is provided.",
args: args{
req: &fnv1.RunFunctionRequest{
Input: resource.MustStructObject(
&v1beta1.GoTemplate{
Source: v1beta1.InlineSource,
Inline: &v1beta1.TemplateSourceInline{Template: cdMissingKeyTmpl},
}),
Observed: &fnv1.State{
Composite: &fnv1.Resource{
Resource: resource.MustStructJSON(xr),
},
},
},
defaultOptions: "missingoption=nothing",
},
want: want{
rsp: &fnv1.RunFunctionResponse{
Meta: &fnv1.ResponseMeta{Ttl: durationpb.New(response.DefaultTTL)},
Results: []*fnv1.Result{
{
Severity: fnv1.Severity_SEVERITY_FATAL,
Message: "cannot apply template options: panic occurred while applying template options: unrecognized option: missingoption=nothing",
Target: fnv1.Target_TARGET_COMPOSITE.Enum(),
},
},
},
},
},
"DefaultTemplateOptionError": {
reason: "The Function should return error when a missing key is detected.",
args: args{
req: &fnv1.RunFunctionRequest{
Input: resource.MustStructObject(
&v1beta1.GoTemplate{
Source: v1beta1.InlineSource,
Inline: &v1beta1.TemplateSourceInline{Template: cdMissingKeyTmpl},
}),
Observed: &fnv1.State{
Composite: &fnv1.Resource{
Resource: resource.MustStructJSON(xr),
},
},
},
defaultOptions: "missingkey=error",
},
want: want{
rsp: &fnv1.RunFunctionResponse{
Meta: &fnv1.ResponseMeta{Ttl: durationpb.New(response.DefaultTTL)},
Results: []*fnv1.Result{
{
Severity: fnv1.Severity_SEVERITY_FATAL,
Message: "cannot execute template: template: manifests:1:180: executing \"manifests\" at <.missing>: map has no entry for key \"missing\"", //nolint:dupword // ignore test output strings
Target: fnv1.Target_TARGET_COMPOSITE.Enum(),
},
},
},
},
},
"CompositeResourceReadyTrue": {
reason: "The Function should return desired composite resource with True ready state.",
args: args{
Expand Down Expand Up @@ -1537,7 +1599,7 @@ func TestRunFunction(t *testing.T) {
Results: []*fnv1.Result{
{
Severity: fnv1.Severity_SEVERITY_FATAL,
Message: "cannot execute template: template: manifests:1:96: executing \"manifests\" at <.missing>: map has no entry for key \"missing\"", //nolint:dupword // ignore test output strings
Message: "cannot execute template: template: manifests:1:180: executing \"manifests\" at <.missing>: map has no entry for key \"missing\"", //nolint:dupword // ignore test output strings
Target: fnv1.Target_TARGET_COMPOSITE.Enum(),
},
},
Expand Down Expand Up @@ -1830,9 +1892,10 @@ func TestRunFunction(t *testing.T) {
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
f := &Function{
log: logging.NewNopLogger(),
fsys: testdataFS,
defaultSource: tc.args.defaultSource,
log: logging.NewNopLogger(),
fsys: testdataFS,
defaultSource: tc.args.defaultSource,
defaultOptions: tc.args.defaultOptions,
}
rsp, err := f.RunFunction(tc.args.ctx, tc.args.req)

Expand Down
8 changes: 5 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type CLI struct {
Insecure bool `help:"Run without mTLS credentials. If you supply this flag --tls-server-certs-dir will be ignored."`
MaxRecvMessageSize int `default:"4" help:"Maximum size of received messages in MB."`
DefaultSource string `default:"" env:"FUNCTION_GO_TEMPLATING_DEFAULT_SOURCE" help:"Default template source to use when input is not provided to the function."`
DefaultOptions string `default:"" env:"FUNCTION_GO_TEMPLATING_DEFAULT_OPTIONS" help:"Comma-separated default template options to use when input is not provided to the function."`
}

// Run this Function.
Expand All @@ -28,9 +29,10 @@ func (c *CLI) Run() error {

return function.Serve(
&Function{
log: log,
fsys: &osFS{},
defaultSource: c.DefaultSource,
log: log,
fsys: &osFS{},
defaultSource: c.DefaultSource,
defaultOptions: c.DefaultOptions,
},
function.Listen(c.Network, c.Address),
function.MTLSCertificates(c.TLSCertsDir),
Expand Down
Loading