@@ -283,6 +283,34 @@ kubectl get -f fn.yaml
283283NAME INSTALLED HEALTHY PACKAGE AGE
284284function-kcl True True xpkg.crossplane.io/crossplane-contrib/function-kcl:v0.11.6 6s
285285` ` `
286+ {{< /tab>}}
287+
288+ {{< tab "Pythonic" >}}
289+
290+ Create this composition function to install [Pythonic](https://github.com/crossplane-contrib/function-pythonic?tab=readme-ov-file#function-pythonic) support:
291+
292+ ` ` ` yaml
293+ apiVersion: pkg.crossplane.io/v1
294+ kind: Function
295+ metadata:
296+ name: function-pythonic
297+ spec:
298+ package: xpkg.crossplane.io/crossplane-contrib/function-pythonic:v0.3.0
299+ ` ` `
300+
301+ Save the function as `fn.yaml` and apply it :
302+
303+ ` ` ` shell
304+ kubectl apply -f fn.yaml
305+ ` ` `
306+
307+ Check that Crossplane installed the function :
308+
309+ ` ` ` shell {copy-lines="1"}
310+ kubectl get -f fn.yaml
311+ NAME INSTALLED HEALTHY PACKAGE AGE
312+ function-pythonic True True xpkg.crossplane.io/crossplane-contrib/function-pythonic:v0.3.0 1m
313+ ` ` `
286314{{< /tab >}}
287315
288316{{< /tabs >}}
@@ -786,6 +814,67 @@ spec:
786814
787815{{< /tab >}}
788816
817+ {{< tab "Pythonic" >}}
818+
819+ ` ` ` yaml {label="comp-pythonic"}
820+ apiVersion: apiextensions.crossplane.io/v1
821+ kind: Composition
822+ metadata:
823+ name: useraccesskeys-pythonic
824+ spec:
825+ compositeTypeRef:
826+ apiVersion: example.org/v1alpha1
827+ kind: UserAccessKey
828+ mode: Pipeline
829+ pipeline:
830+ - step: render-pythonic
831+ functionRef:
832+ name: function-pythonic
833+ input:
834+ apiVersion: pythonic.fn.crossplane.io/v1alpha1
835+ kind: Composite
836+ composite: |
837+ class Composite(BaseComposite):
838+ def compose(self):
839+ self.connectionSecret = self.spec.writeConnectionSecretToRef
840+
841+ user = self.resources.user('iam.aws.m.upbound.io/v1beta1', 'User')
842+ user.spec.forProvider = {}
843+
844+ for ix in range(2):
845+ key = self.resources[f"access-key-{ix}"]('iam.aws.m.upbound.io/v1beta1', 'AccessKey')
846+ key.spec.forProvider.user = user.status.atProvider.id
847+ key.spec.writeConnectionSecretToRef.name = f"{self.metadata.name}-accesskey-{ix}"
848+ self.connection[f"user-{ix}"] = key.connection.username
849+ self.connection[f"password-{ix}"] = key.connection.password
850+ ` ` `
851+
852+ <!-- vale write-good.Passive = NO -->
853+ <!-- vale Google.WordList = NO -->
854+ **How this Composition exposes connection details:**
855+
856+ * Each composed {{<hover label="comp-pythonic" line="26">}}AccessKey{{</hover>}} has
857+ {{<hover label="comp-pythonic" line="28">}}writeConnectionSecretToRef{{</hover>}} set. This
858+ tells each AccessKey to write its credentials to an individual Secret.
859+ * Crossplane observes the connection details from each `AccessKey` and makes them
860+ available to the composition when the function runs.
861+ * The Secret reads `AccessKey`'s connection details via
862+ {{<hover label="comp-pythonic" line="29">}}connection.username{{</hover>}} and
863+ {{<hover label="comp-pythonic" line="30">}}connection.password{{</hover>}}.
864+ * The function establishes the connection `Secret` name from the XR
865+ {{<hover label="comp-pythonic" line="20">}}spec.writeConnectionSecretToRef{{</hover>}}
866+ if it exists.
867+ * The function automatically includes a `Secret` object in the XR's composed
868+ resources that represents the XR's aggregated connection details.
869+ * You don't need to create or compose this `Secret` yourself, it's done
870+ automatically for you.
871+ * In `function-pythonic`, connection details base64 encoding and decoding is handled
872+ automatically for you.
873+ <!-- vale Google.WordList = YES -->
874+ <!-- vale write-good.Passive = YES -->
875+
876+ {{< /tab >}}
877+
789878{{< /tabs >}}
790879
791880Save the composition as `composition.yaml` and apply it :
@@ -955,6 +1044,28 @@ You don't need to manually compose a `Secret` resource yourself.
9551044 needed. Use patches to configure these values using data from the XR if
9561045 needed.
9571046
1047+ # ## Automatic aggregation (`function-pythonic`)
1048+
1049+ ` function-pythonic` automatically observes connection details from
1050+ composed resources and creates the aggregated connection secret to
1051+ maintain backward compatibility with v1 behavior.
1052+
1053+ You don't need to manually compose a `Secret` resource yourself.
1054+
1055+ 1. **Compose resources** : Create composed resources as usual in your
1056+ composition, such as IAM `User` and `AccessKeys`. These resources expose
1057+ their connection details in a `Secret`.
1058+
1059+ 2. **Set `writeConnectionSecretToRef`** : Each composed resource that should have
1060+ connection details stored should have their `resource.spec.writeConnectionSecretToRef` set
1061+ in the composition.
1062+
1063+ 3. **Define `connection`** : For each composed resource, assign the connection secret
1064+ values wanted to the aggregated secret using `self.connection[key] = resource.connection[key]`.
1065+
1066+ 4. **Configure the `Secret`** : Set the XR `self.connectionSecret` fields
1067+ to override the aggregated secret's default name and namespace.
1068+
9581069# # Troubleshooting
9591070
9601071# ## Composite resource's connection details Secret is empty
@@ -995,8 +1106,8 @@ For example, `function-python` requires you to convert connection details to
9951106base64-encoded strings, while connection details in `function-go-templating` and
9961107` function-kcl` are already encoded this way and require no conversion logic.
9971108
998- ` function-patch-and-transform` handles encoding when automatically creating the
999- composed connection secret.
1109+ ` function-patch-and-transform` and `function-pythonic` handle encoding when automatically
1110+ creating the composed connection secret.
10001111<!-- vale write-good.Weasel = YES -->
10011112<!-- vale Google.Colons = YES -->
10021113
0 commit comments