Skip to content

Commit 560807d

Browse files
authored
Merge pull request #130 from nokia/allow-recursion
2 parents 9c47d51 + 15a83ae commit 560807d

File tree

7 files changed

+189
-4
lines changed

7 files changed

+189
-4
lines changed

README.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,79 @@ data:
204204

205205
For more information, see the example in [context](example/context).
206206

207+
### Updating status or creating composed resources with the composite resource's type
208+
209+
This function applies special logic if a resource with the composite resource's type is found in the template.
210+
211+
If the resource name is not set (the `gotemplating.fn.crossplane.io/composition-resource-name` meta annotation is not present), then the function **does not create composed resources** with the composite resource's type. In this case only the composite resource's **status is updated**.
212+
213+
For example, the following composition does not create composed resources. Rather, it updates the composite resource's status to include `dummy: cool-status`.
214+
215+
```yaml
216+
apiVersion: apiextensions.crossplane.io/v1
217+
kind: Composition
218+
metadata:
219+
name: example-update-status
220+
spec:
221+
compositeTypeRef:
222+
apiVersion: example.crossplane.io/v1beta1
223+
kind: XR
224+
mode: Pipeline
225+
pipeline:
226+
- step: render-templates
227+
functionRef:
228+
name: function-go-templating
229+
input:
230+
apiVersion: gotemplating.fn.crossplane.io/v1beta1
231+
kind: GoTemplate
232+
source: Inline
233+
inline:
234+
template: |
235+
apiVersion: example.crossplane.io/v1beta1
236+
kind: XR
237+
status:
238+
dummy: cool-status
239+
```
240+
241+
On the other hand, if the resource name is set (using the `gotemplating.fn.crossplane.io/composition-resource-name` meta annotation), then the function **creates composed resources** with the composite resource's type.
242+
243+
For example, the following composition will create a composed resource:
244+
245+
```yaml
246+
apiVersion: apiextensions.crossplane.io/v1
247+
kind: Composition
248+
metadata:
249+
name: example-allow-recursion
250+
spec:
251+
compositeTypeRef:
252+
apiVersion: example.crossplane.io/v1beta1
253+
kind: XR
254+
mode: Pipeline
255+
pipeline:
256+
- step: render-templates
257+
functionRef:
258+
name: function-go-templating
259+
input:
260+
apiVersion: gotemplating.fn.crossplane.io/v1beta1
261+
kind: GoTemplate
262+
source: Inline
263+
inline:
264+
template: |
265+
apiVersion: example.crossplane.io/v1beta1
266+
kind: XR
267+
metadata:
268+
annotations:
269+
{{ setResourceNameAnnotation "recursive-xr" }}
270+
spec:
271+
compositionRef:
272+
name: example-other # make sure to avoid infinite recursion
273+
```
274+
275+
> [!WARNING]
276+
> This can lead to infinite recursion. Make sure to terminate the recursion by specifying a different `compositionRef` at some point.
277+
278+
For more information, see the example in [recursive](example/recursive).
279+
207280
## Additional functions
208281

209282
| Name | Description |
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
apiVersion: apiextensions.crossplane.io/v1
2+
kind: Composition
3+
metadata:
4+
name: example-recursive-real # defining the real composition
5+
spec:
6+
compositeTypeRef:
7+
apiVersion: example.crossplane.io/v1beta1
8+
kind: XR
9+
mode: Pipeline
10+
pipeline:
11+
- step: render-templates
12+
functionRef:
13+
name: function-go-templating
14+
input:
15+
apiVersion: gotemplating.fn.crossplane.io/v1beta1
16+
kind: GoTemplate
17+
source: Inline
18+
inline:
19+
template: |
20+
apiVersion: s3.aws.upbound.io/v1beta1
21+
kind: Bucket
22+
metadata:
23+
annotations:
24+
{{ setResourceNameAnnotation "bucket" }}
25+
spec:
26+
forProvider:
27+
region: {{ .observed.composite.resource.spec.region }}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
apiVersion: apiextensions.crossplane.io/v1
2+
kind: Composition
3+
metadata:
4+
name: example-recursive-wrapper # defining the wrapper composition
5+
spec:
6+
compositeTypeRef:
7+
apiVersion: example.crossplane.io/v1beta1
8+
kind: XR
9+
mode: Pipeline
10+
pipeline:
11+
- step: render-templates
12+
functionRef:
13+
name: function-go-templating
14+
input:
15+
apiVersion: gotemplating.fn.crossplane.io/v1beta1
16+
kind: GoTemplate
17+
source: Inline
18+
inline:
19+
template: |
20+
{{- range $i := until ( .observed.composite.resource.spec.count | int ) }}
21+
---
22+
apiVersion: example.crossplane.io/v1beta1
23+
kind: XR
24+
metadata:
25+
annotations:
26+
{{ setResourceNameAnnotation (print "test-xr-" $i) }}
27+
spec:
28+
compositionRef:
29+
name: example-recursive-real # instantiating the real composition
30+
region: {{ print "us-west-" (add $i 1) }}
31+
{{ end }}

example/recursive/functions.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
apiVersion: pkg.crossplane.io/v1beta1
2+
kind: Function
3+
metadata:
4+
name: function-go-templating
5+
spec:
6+
package: xpkg.upbound.io/crossplane-contrib/function-go-templating:v0.5.0

example/recursive/xr.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
apiVersion: example.crossplane.io/v1beta1
2+
kind: XR
3+
metadata:
4+
name: example
5+
spec:
6+
compositionRef:
7+
name: example-recursive-wrapper # instantiating the wrapper composition
8+
count: 2

fn.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,10 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequ
153153
cd.Resource.Unstructured = *obj.DeepCopy()
154154

155155
// TODO(ezgidemirel): Refactor to reduce cyclomatic complexity.
156-
// Update only the status of the desired composite resource.
157-
if cd.Resource.GetAPIVersion() == observedComposite.Resource.GetAPIVersion() && cd.Resource.GetKind() == observedComposite.Resource.GetKind() {
156+
// Handle if the composite resource appears in the rendered template.
157+
// Unless resource name annotation is present, update only the status of the desired composite resource.
158+
name, nameFound := obj.GetAnnotations()[annotationKeyCompositionResourceName]
159+
if cd.Resource.GetAPIVersion() == observedComposite.Resource.GetAPIVersion() && cd.Resource.GetKind() == observedComposite.Resource.GetKind() && !nameFound {
158160
dst := make(map[string]any)
159161
if err := desiredComposite.Resource.GetValueInto("status", &dst); err != nil && !fieldpath.IsNotFound(err) {
160162
response.Fatal(rsp, errors.Wrap(err, "cannot get desired composite status"))
@@ -251,8 +253,7 @@ func (f *Function) RunFunction(_ context.Context, req *fnv1beta1.RunFunctionRequ
251253
meta.RemoveAnnotations(cd.Resource, annotationKeyCompositionResourceName)
252254

253255
// Add resource to the desired composed resources map.
254-
name, found := obj.GetAnnotations()[annotationKeyCompositionResourceName]
255-
if !found {
256+
if !nameFound {
256257
response.Fatal(rsp, errors.Errorf("%q template is missing required %q annotation", obj.GetKind(), annotationKeyCompositionResourceName))
257258
return rsp, nil
258259
}

fn_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ var (
3838
xrWithStatus = `{"apiVersion":"example.org/v1","kind":"XR","metadata":{"name":"cool-xr"},"spec":{"count":2},"status":{"ready":"true"}}`
3939
xrWithNestedStatusFoo = `{"apiVersion":"example.org/v1","kind":"XR","metadata":{"name":"cool-xr"},"spec":{"count":2},"status":{"state":{"foo":"bar"}}}`
4040
xrWithNestedStatusBaz = `{"apiVersion":"example.org/v1","kind":"XR","metadata":{"name":"cool-xr"},"spec":{"count":2},"status":{"state":{"baz":"qux"}}}`
41+
xrRecursiveTmpl = `{"apiVersion":"example.org/v1","kind":"XR","metadata":{"annotations":{"gotemplating.fn.crossplane.io/composition-resource-name":"recursive-xr"},"name":"recursive-xr","labels":{"belongsTo":{{.observed.composite.resource.metadata.name|quote}}}},"spec":{"count":2}}`
4142

4243
extraResources = `{"apiVersion":"meta.gotemplating.fn.crossplane.io/v1alpha1","kind":"ExtraResources","requirements":{"cool-extra-resource":{"apiVersion":"example.org/v1","kind":"CoolExtraResource","matchName":"cool-extra-resource"}}}
4344
{"apiVersion":"meta.gotemplating.fn.crossplane.io/v1alpha1","kind":"ExtraResources","requirements":{"another-cool-extra-resource":{"apiVersion":"example.org/v1","kind":"CoolExtraResource","matchLabels":{"key": "value"}},"yet-another-cool-extra-resource":{"apiVersion":"example.org/v1","kind":"CoolExtraResource","matchName":"foo"}}}
@@ -381,6 +382,44 @@ func TestRunFunction(t *testing.T) {
381382
},
382383
},
383384
},
385+
"ResponseIsReturnedWithTemplatedXR": {
386+
reason: "The Function should return the desired composite resource and the composed templated XR resource.",
387+
args: args{
388+
req: &fnv1beta1.RunFunctionRequest{
389+
Meta: &fnv1beta1.RequestMeta{Tag: "status"},
390+
Input: resource.MustStructObject(
391+
&v1beta1.GoTemplate{
392+
Source: v1beta1.InlineSource,
393+
Inline: &v1beta1.TemplateSourceInline{Template: xrRecursiveTmpl},
394+
}),
395+
Observed: &fnv1beta1.State{
396+
Composite: &fnv1beta1.Resource{
397+
Resource: resource.MustStructJSON(xr),
398+
},
399+
},
400+
Desired: &fnv1beta1.State{
401+
Composite: &fnv1beta1.Resource{
402+
Resource: resource.MustStructJSON(xr),
403+
},
404+
},
405+
},
406+
},
407+
want: want{
408+
rsp: &fnv1beta1.RunFunctionResponse{
409+
Meta: &fnv1beta1.ResponseMeta{Tag: "status", Ttl: durationpb.New(response.DefaultTTL)},
410+
Desired: &fnv1beta1.State{
411+
Composite: &fnv1beta1.Resource{
412+
Resource: resource.MustStructJSON(xr),
413+
},
414+
Resources: map[string]*fnv1beta1.Resource{
415+
"recursive-xr": {
416+
Resource: resource.MustStructJSON(`{"apiVersion": "example.org/v1","kind":"XR","metadata":{"annotations":{},"name":"recursive-xr","labels":{"belongsTo":"cool-xr"}},"spec":{"count":2}}`),
417+
},
418+
},
419+
},
420+
},
421+
},
422+
},
384423
"ResponseIsReturnedWithTemplatingFS": {
385424
reason: "The Function should return the desired composite resource and the templated composed resources with FileSystem cd.",
386425
args: args{

0 commit comments

Comments
 (0)