Skip to content

Commit ee0dd5d

Browse files
authored
Add CLI flag --default-options for template options. (#563)
* Add CLI flag --default-options for template options. Signed-off-by: Bob Haddleton <bob.haddleton@nokia.com> * Fix lint error Signed-off-by: Bob Haddleton <bob.haddleton@nokia.com> --------- Signed-off-by: Bob Haddleton <bob.haddleton@nokia.com>
1 parent c9a3b58 commit ee0dd5d

File tree

4 files changed

+95
-20
lines changed

4 files changed

+95
-20
lines changed

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,10 @@ contain periods, hyphens and other special characters. Like Helm, this function
7373
also supports [Sprig template functions][sprig] as well as [additional functions](#additional-functions).
7474

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

7881
### Connection Details
7982

@@ -438,4 +441,4 @@ $ crossplane xpkg build -f package --embed-runtime-image=runtime
438441
[docker]: https://www.docker.com
439442
[cli]: https://docs.crossplane.io/latest/cli
440443
[extra-resources]: https://docs.crossplane.io/latest/concepts/composition-functions/#how-composition-functions-work
441-
[Connection Details Compositions guide]: https://docs.crossplane.io/latest/guides/connection-details-composition/
444+
[Connection Details Compositions guide]: https://docs.crossplane.io/latest/guides/connection-details-composition/

fn.go

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,10 @@ func (*osFS) Open(name string) (fs.File, error) {
4242
type Function struct {
4343
fnv1.UnimplementedFunctionRunnerServiceServer
4444

45-
log logging.Logger
46-
fsys fs.FS
47-
defaultSource string
45+
log logging.Logger
46+
fsys fs.FS
47+
defaultSource string
48+
defaultOptions string
4849
}
4950

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

96-
if in.Options != nil {
97-
f.log.Debug("setting template options", "options", *in.Options)
98-
err = safeApplyTemplateOptions(tmpl, *in.Options)
97+
if in.Options != nil || f.defaultOptions != "" {
98+
var o []string
99+
if in.Options != nil {
100+
o = *in.Options
101+
} else {
102+
o = strings.Split(f.defaultOptions, ",")
103+
}
104+
f.log.Debug("setting template options", "options", o)
105+
err = safeApplyTemplateOptions(tmpl, o)
99106
if err != nil {
100107
response.Fatal(rsp, errors.Wrap(err, "cannot apply template options"))
101108
return rsp, nil

fn_test.go

Lines changed: 72 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ metadata:
3030

3131
cd = `{"apiVersion":"example.org/v1","kind":"CD","metadata":{"annotations":{"gotemplating.fn.crossplane.io/composition-resource-name":"cool-cd"},"name":"cool-cd"}}`
3232
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}}}}}`
33-
cdMissingKeyTmpl = `{"apiVersion":"example.org/v1","kind":"CD","metadata":{"name":"cool-cd","labels":{"belongsTo":{{.missing | quote }}}}}`
33+
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 }}}}}`
3434
cdWrongTmpl = `{"apiVersion":"example.org/v1","kind":"CD","metadata":{"name":"cool-cd","labels":{"belongsTo":{{.invalid-key}}}}}`
3535
cdMissingKind = `{"apiVersion":"example.org/v1"}`
3636
cdMissingResourceName = `{"apiVersion":"example.org/v1","kind":"CD","metadata":{"name":"cool-cd"}}`
@@ -77,9 +77,10 @@ metadata:
7777

7878
func TestRunFunction(t *testing.T) {
7979
type args struct {
80-
ctx context.Context
81-
req *fnv1.RunFunctionRequest
82-
defaultSource string
80+
ctx context.Context
81+
req *fnv1.RunFunctionRequest
82+
defaultSource string
83+
defaultOptions string
8384
}
8485
type want struct {
8586
rsp *fnv1.RunFunctionResponse
@@ -1243,7 +1244,7 @@ func TestRunFunction(t *testing.T) {
12431244
},
12441245
},
12451246
"InvalidTemplateOption": {
1246-
reason: "The Function should return error when an invalid option is provided.",
1247+
reason: "The Function should return error when an invalid option is provided as input.",
12471248
args: args{
12481249
req: &fnv1.RunFunctionRequest{
12491250
Input: resource.MustStructObject(
@@ -1258,6 +1259,7 @@ func TestRunFunction(t *testing.T) {
12581259
},
12591260
},
12601261
},
1262+
defaultOptions: "missingkey=default",
12611263
},
12621264
want: want{
12631265
rsp: &fnv1.RunFunctionResponse{
@@ -1272,6 +1274,66 @@ func TestRunFunction(t *testing.T) {
12721274
},
12731275
},
12741276
},
1277+
"InvalidDefaultTemplateOption": {
1278+
reason: "The Function should return error when an invalid option is provided.",
1279+
args: args{
1280+
req: &fnv1.RunFunctionRequest{
1281+
Input: resource.MustStructObject(
1282+
&v1beta1.GoTemplate{
1283+
Source: v1beta1.InlineSource,
1284+
Inline: &v1beta1.TemplateSourceInline{Template: cdMissingKeyTmpl},
1285+
}),
1286+
Observed: &fnv1.State{
1287+
Composite: &fnv1.Resource{
1288+
Resource: resource.MustStructJSON(xr),
1289+
},
1290+
},
1291+
},
1292+
defaultOptions: "missingoption=nothing",
1293+
},
1294+
want: want{
1295+
rsp: &fnv1.RunFunctionResponse{
1296+
Meta: &fnv1.ResponseMeta{Ttl: durationpb.New(response.DefaultTTL)},
1297+
Results: []*fnv1.Result{
1298+
{
1299+
Severity: fnv1.Severity_SEVERITY_FATAL,
1300+
Message: "cannot apply template options: panic occurred while applying template options: unrecognized option: missingoption=nothing",
1301+
Target: fnv1.Target_TARGET_COMPOSITE.Enum(),
1302+
},
1303+
},
1304+
},
1305+
},
1306+
},
1307+
"DefaultTemplateOptionError": {
1308+
reason: "The Function should return error when a missing key is detected.",
1309+
args: args{
1310+
req: &fnv1.RunFunctionRequest{
1311+
Input: resource.MustStructObject(
1312+
&v1beta1.GoTemplate{
1313+
Source: v1beta1.InlineSource,
1314+
Inline: &v1beta1.TemplateSourceInline{Template: cdMissingKeyTmpl},
1315+
}),
1316+
Observed: &fnv1.State{
1317+
Composite: &fnv1.Resource{
1318+
Resource: resource.MustStructJSON(xr),
1319+
},
1320+
},
1321+
},
1322+
defaultOptions: "missingkey=error",
1323+
},
1324+
want: want{
1325+
rsp: &fnv1.RunFunctionResponse{
1326+
Meta: &fnv1.ResponseMeta{Ttl: durationpb.New(response.DefaultTTL)},
1327+
Results: []*fnv1.Result{
1328+
{
1329+
Severity: fnv1.Severity_SEVERITY_FATAL,
1330+
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
1331+
Target: fnv1.Target_TARGET_COMPOSITE.Enum(),
1332+
},
1333+
},
1334+
},
1335+
},
1336+
},
12751337
"CompositeResourceReadyTrue": {
12761338
reason: "The Function should return desired composite resource with True ready state.",
12771339
args: args{
@@ -1537,7 +1599,7 @@ func TestRunFunction(t *testing.T) {
15371599
Results: []*fnv1.Result{
15381600
{
15391601
Severity: fnv1.Severity_SEVERITY_FATAL,
1540-
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
1602+
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
15411603
Target: fnv1.Target_TARGET_COMPOSITE.Enum(),
15421604
},
15431605
},
@@ -1830,9 +1892,10 @@ func TestRunFunction(t *testing.T) {
18301892
for name, tc := range cases {
18311893
t.Run(name, func(t *testing.T) {
18321894
f := &Function{
1833-
log: logging.NewNopLogger(),
1834-
fsys: testdataFS,
1835-
defaultSource: tc.args.defaultSource,
1895+
log: logging.NewNopLogger(),
1896+
fsys: testdataFS,
1897+
defaultSource: tc.args.defaultSource,
1898+
defaultOptions: tc.args.defaultOptions,
18361899
}
18371900
rsp, err := f.RunFunction(tc.args.ctx, tc.args.req)
18381901

main.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type CLI struct {
1717
Insecure bool `help:"Run without mTLS credentials. If you supply this flag --tls-server-certs-dir will be ignored."`
1818
MaxRecvMessageSize int `default:"4" help:"Maximum size of received messages in MB."`
1919
DefaultSource string `default:"" env:"FUNCTION_GO_TEMPLATING_DEFAULT_SOURCE" help:"Default template source to use when input is not provided to the function."`
20+
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."`
2021
}
2122

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

2930
return function.Serve(
3031
&Function{
31-
log: log,
32-
fsys: &osFS{},
33-
defaultSource: c.DefaultSource,
32+
log: log,
33+
fsys: &osFS{},
34+
defaultSource: c.DefaultSource,
35+
defaultOptions: c.DefaultOptions,
3436
},
3537
function.Listen(c.Network, c.Address),
3638
function.MTLSCertificates(c.TLSCertsDir),

0 commit comments

Comments
 (0)