@@ -171,6 +171,34 @@ from the tabs below.
171171
172172{{< tabs >}}
173173
174+ {{< tab "YAML" >}}
175+
176+ Create this composition function to install YAML support :
177+
178+ ` ` ` yaml
179+ apiVersion: pkg.crossplane.io/v1
180+ kind: Function
181+ metadata:
182+ name: function-patch-and-transform
183+ spec:
184+ package: xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.10.0
185+ ` ` `
186+
187+ Save the function as `fn.yaml` and apply it :
188+
189+ ` ` ` shell
190+ kubectl apply -f fn.yaml
191+ ` ` `
192+
193+ Check that Crossplane installed the function :
194+
195+ ` ` ` shell {copy-lines="1"}
196+ kubectl get -f fn.yaml
197+ NAME INSTALLED HEALTHY PACKAGE AGE
198+ function-patch-and-transform True True xpkg.crossplane.io/crossplane-contrib/function-patch-and-transform:v0.10.0 8s
199+ ` ` `
200+ {{< /tab >}}
201+
174202{{< tab "Templated YAML" >}}
175203Templated YAML is a good choice if you're used to writing
176204[Helm charts](https://helm.sh).
@@ -306,6 +334,124 @@ exposes their credentials as the composite resource's connection details `Secret
306334
307335{{< tabs >}}
308336
337+ {{< tab "YAML" >}}
338+
339+ ` ` ` yaml {label="comp-pt"}
340+ apiVersion: apiextensions.crossplane.io/v1
341+ kind: Composition
342+ metadata:
343+ name: useraccesskeys-patch-and-transform
344+ spec:
345+ compositeTypeRef:
346+ apiVersion: example.org/v1alpha1
347+ kind: UserAccessKey
348+ mode: Pipeline
349+ pipeline:
350+ - step: patch-and-transform
351+ functionRef:
352+ name: function-patch-and-transform
353+ input:
354+ apiVersion: pt.fn.crossplane.io/v1beta1
355+ kind: Resources
356+ writeConnectionSecretToRef:
357+ patches:
358+ - type: FromCompositeFieldPath
359+ fromFieldPath: spec.writeConnectionSecretToRef.name
360+ toFieldPath: name
361+ resources:
362+ - name: user
363+ base:
364+ apiVersion: iam.aws.m.upbound.io/v1beta1
365+ kind: User
366+ spec:
367+ forProvider: {}
368+ - name: accesskey-0
369+ base:
370+ apiVersion: iam.aws.m.upbound.io/v1beta1
371+ kind: AccessKey
372+ spec:
373+ forProvider:
374+ userSelector:
375+ matchControllerRef: true
376+ writeConnectionSecretToRef:
377+ name: accesskey-secret-0
378+ connectionDetails:
379+ - name: user-0
380+ type: FromConnectionSecretKey
381+ fromConnectionSecretKey: username
382+ - name: password-0
383+ type: FromConnectionSecretKey
384+ fromConnectionSecretKey: password
385+ patches:
386+ - type: FromCompositeFieldPath
387+ fromFieldPath: metadata.name
388+ toFieldPath: spec.writeConnectionSecretToRef.name
389+ transforms:
390+ - type: string
391+ string:
392+ type: Format
393+ fmt: "%s-accesskey-secret-0"
394+ - name: accesskey-1
395+ base:
396+ apiVersion: iam.aws.m.upbound.io/v1beta1
397+ kind: AccessKey
398+ spec:
399+ forProvider:
400+ userSelector:
401+ matchControllerRef: true
402+ writeConnectionSecretToRef:
403+ name: accesskey-secret-1
404+ connectionDetails:
405+ - name: user-1
406+ type: FromConnectionSecretKey
407+ fromConnectionSecretKey: username
408+ - name: password-1
409+ type: FromConnectionSecretKey
410+ fromConnectionSecretKey: password
411+ patches:
412+ - type: FromCompositeFieldPath
413+ fromFieldPath: metadata.name
414+ toFieldPath: spec.writeConnectionSecretToRef.name
415+ transforms:
416+ - type: string
417+ string:
418+ type: Format
419+ fmt: "%s-accesskey-secret-1"
420+ - step: ready
421+ functionRef:
422+ name: function-auto-ready
423+ ` ` `
424+
425+ <!-- vale write-good.Passive = NO -->
426+ <!-- vale Google.WordList = NO -->
427+ **How this Composition exposes connection details:**
428+
429+ * Each composed {{<hover label="comp-pt" line="32">}}AccessKey{{</hover>}} has
430+ {{<hover label="comp-pt" line="37">}}writeConnectionSecretToRef{{</hover>}} set. This
431+ tells each `AccessKey` to write its credentials to an individual `Secret`.
432+ * Each {{<hover label="comp-pt" line="32">}}AccessKey{{</hover>}} defines
433+ {{<hover label="comp-pt" line="39">}}connectionDetails{{</hover>}} that specify which
434+ keys from its connection secret should be included in the XR's
435+ aggregated connection details secret.
436+ * The {{<hover label="comp-pt" line="40">}}name{{</hover>}} field in each connection
437+ detail entry sets the key name in the aggregated secret.
438+ * The {{<hover label="comp-pt" line="42">}}fromConnectionSecretKey{{</hover>}} field
439+ specifies which key to read from the composed resource's individual connection secret.
440+ * The function's input includes a top-level
441+ {{<hover label="comp-pt" line="17">}}writeConnectionSecretToRef{{</hover>}} section
442+ that allows you to specify where to create the connection secret.
443+ * The {{<hover label="comp-pt" line="18">}}patches{{</hover>}} in the
444+ ` writeConnectionSecretToRef` section read the secret name from the XR's
445+ ` .spec.writeConnectionSecretToRef.name` field.
446+ * The function automatically includes a `Secret` object in the XR's composed
447+ resources that represents the XR's aggregated connection details.
448+ * You don't need to create or compose this `Secret` yourself, it's done
449+ automatically for you.
450+ <!-- vale Google.WordList = YES -->
451+ <!-- vale write-good.Passive = YES -->
452+
453+ {{< /tab >}}
454+
309455{{< tab "Templated YAML" >}}
310456
311457` ` ` yaml {label="comp-gotmpl"}
@@ -754,22 +900,27 @@ kubectl get secret -n default -l crossplane.io/composite=my-keys -o jsonpath='{.
754900
755901# # Understanding how composing connection details works
756902
757- The basic steps to expose connection details for a composite resource are :
903+ You can expose connection details for a composite resource using two approaches :
904+
905+ # ## Manual composition (most functions)
906+
907+ With functions like `function-go-templating`, `function-python`, `function-kcl`, and others,
908+ manually compose a `Secret` resource :
758909
7599101. **Compose resources** : Create composed resources as usual in your
760911 composition, such as IAM `User` and `AccessKeys`. These resources expose
761912 their connection details in a `Secret`.
762913
7639142. **Set `writeConnectionSecretToRef`** : Each composed resource that should have
764- connection details stored in their own individual `Secret` should have their
765- ` writeConnectionSecretToRef ` set in the composition.
915+ connection details stored should have their `writeConnectionSecretToRef` set
916+ in the composition.
766917
7679183. **Observed connection details** : Crossplane observes the actual state of
768919 each composed resource, including its connection details, and makes this data
769- available when it runs the function.
920+ available when the function runs .
770921
771- 4. **Compose the combined `Secret`** : With the observed connection details of
772- your composed resources in hand, compose a `Secret` resource that combines
922+ 4. **Compose the combined `Secret`** : Compose a `Secret` resource that reads
923+ from the observed connection details of your composed resources and combines
773924 the important connection details you want to expose for the XR. Consider
774925 allowing the consumer of the XR to specify the name they want this secret to
775926 have.
@@ -779,6 +930,30 @@ The basic steps to expose connection details for a composite resource are:
779930 Composition should handle these cases by checking if resources and
780931 their connection details exist before accessing them.
781932
933+ # ## Automatic aggregation (`function-patch-and-transform`)
934+
935+ ` function-patch-and-transform` automatically observes connection details from
936+ composed resources and creates the aggregated connection secret to
937+ maintain backward compatibility with v1 behavior.
938+
939+ You don't need to manually compose a `Secret` resource yourself.
940+
941+ 1. **Compose resources** : Create composed resources as usual in your
942+ composition, such as IAM `User` and `AccessKeys`. These resources expose
943+ their connection details in a `Secret`.
944+
945+ 2. **Set `writeConnectionSecretToRef`** : Each composed resource that should have
946+ connection details stored should have their `writeConnectionSecretToRef` set
947+ in the composition.
948+
949+ 3. **Define `connectionDetails`** : On each composed resource, define which
950+ connection secret keys to include in the aggregated secret using the
951+ ` connectionDetails` field.
952+
953+ 4. **Configure the `Secret`** : Add a `writeConnectionSecretToRef` section in the
954+ function's `input` to set the aggregated secret's name and namespace as
955+ needed. Use patches to configure these values using data from the XR if
956+ needed.
782957
783958# # Troubleshooting
784959
@@ -789,7 +964,8 @@ The basic steps to expose connection details for a composite resource are:
789964<!-- vale write-good.Weasel = NO -->
790965* Composed resources don't have `writeConnectionSecretToRef` set
791966* Composed resources aren't ready/healthy yet
792- * Not handling initial nil state correctly in the Composition
967+ * (`function-patch-and-transform`) Missing `connectionDetails` field on composed resources
968+ * (Manual composition) Not handling initial nil state correctly in the Composition
793969<!-- vale write-good.Weasel = YES -->
794970
795971<!-- vale write-good.Passive = NO -->
@@ -800,7 +976,10 @@ The basic steps to expose connection details for a composite resource are:
800976* Wait for composed resources to become ready (`kubectl get` and check the `READY` column)
801977* Verify the composed resource is actually producing connection details:
802978 ` kubectl get secret <composed-resource-secret-name> -o yaml`
803- * Add nil/empty checks in your Composition logic to safeguard access to data that may not exist yet
979+ * (`function-patch-and-transform`) Ensure each composed resource has a
980+ ` connectionDetails` section that maps the desired secret keys
981+ * (Manual composition) Add nil/empty checks in your Composition logic to
982+ safeguard access to data that may not exist yet
804983<!-- vale Google.WordList = YES -->
805984<!-- vale write-good.Passive = YES -->
806985
@@ -810,11 +989,14 @@ The basic steps to expose connection details for a composite resource are:
810989
811990**Cause:** Not encoding the combined secret data correctly in your Composition logic
812991
813- **Solution:** Ensure that your connection details data is correctly encoded for
814- the function you're using. For example, `function-python` requires you to
815- convert connection details to base64-encoded strings, while connection details
816- in `function-go-templating` and `function-kcl` are already encoded this way and
817- require no conversion logic.
992+ **Solution:** This only applies to manual composition approaches. Ensure that
993+ your connection details data is correctly encoded for the function you're using.
994+ For example, `function-python` requires you to convert connection details to
995+ base64-encoded strings, while connection details in `function-go-templating` and
996+ ` function-kcl` are already encoded this way and require no conversion logic.
997+
998+ ` function-patch-and-transform` handles encoding when automatically creating the
999+ composed connection secret.
8181000<!-- vale write-good.Weasel = YES -->
8191001<!-- vale Google.Colons = YES -->
8201002
0 commit comments