From 7e0783740742f7f50efe4d6f7dd3aae7b6ef5a75 Mon Sep 17 00:00:00 2001 From: Nic Cope Date: Mon, 23 Jan 2023 21:30:44 -0800 Subject: [PATCH 1/2] Document the alpha Composition Functions feature Signed-off-by: Nic Cope --- .../composition-functions-xfn-runner.png | Bin 0 -> 26900 bytes .../guides/composition-functions.md | 994 ++++++++++++++++++ 2 files changed, 994 insertions(+) create mode 100644 content/knowledge-base/guides/composition-functions-xfn-runner.png create mode 100644 content/knowledge-base/guides/composition-functions.md diff --git a/content/knowledge-base/guides/composition-functions-xfn-runner.png b/content/knowledge-base/guides/composition-functions-xfn-runner.png new file mode 100644 index 0000000000000000000000000000000000000000..03a07e9888e2af6f070a30f66a9f23a733d9f5ef GIT binary patch literal 26900 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 literal 0 HcmV?d00001 diff --git a/content/knowledge-base/guides/composition-functions.md b/content/knowledge-base/guides/composition-functions.md new file mode 100644 index 00000000..df40d06e --- /dev/null +++ b/content/knowledge-base/guides/composition-functions.md @@ -0,0 +1,994 @@ +--- +title: Composition Functions +state: alpha +--- + +Composition Functions allow you to supplement or replace your Compositions with +advanced logic. You can build a Function using general purpose programming +languages such as Go or Python, or relevant tools such as Helm, kustomize, or +CUE. Functions compliment contemporary "Patch and Transform" (P&T) style +Composition. It's possible to use only P&T, only Functions, or a mix of both in +the same Composition. + +```yaml +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: example +spec: + compositeTypeRef: + apiVersion: database.example.org/v1alpha1 + kind: XPostgreSQLInstance + functions: + - name: my-cool-Function + type: Container + container: + image: xpkg.upbound.io/my-cool-Function:0.1.0 +``` + +A Composition Function is a short-lived OCI container that tells Crossplane how +to reconcile a Composite Resource (XR). The preceding example shows a minimal +`Composition` that uses a Composition Function. Note that it has a `functions` +array rather than the typical P&T style array of `resources`. + +## Enabling functions + +To enable support for Composition Functions you must: + + * Enable the alpha feature flag in Crossplane. + * Deploy a runner that's responsible for running Functions. + +```shell +kubectl create namespace crossplane-system +helm install crossplane --namespace crossplane-system crossplane-stable/crossplane \ + --set "args={--debug,--enable-composition-functions}" \ + --set "xfn.enabled=true" \ + --set "xfn.args={--debug}" +``` + +The preceding Helm command installs Crossplane with the Composition Functions +feature flag enabled, and with the reference _xfn_ Composition Function runner +deployed as a sidecar pod. Confirm Composition Functions were enabled by looking +for a log line: + +```shell +$ kubectl -n crossplane-system logs -l app=crossplane +{"level":"info","ts":1674535093.36186,"logger":"crossplane","msg":"Alpha feature enabled","flag":"EnableAlphaCompositionFunctions"} +``` + +You should see the log line emitted shortly after Crossplane starts. + + +## Using functions + +To use Composition Functions you must: + +1. Find one or more Composition Functions, or write your own. +2. Create a `Composition` that uses your Functions. +3. Create an XR that uses your `Composition`. + +Your XRs, claims, and providers don't need to be updated or otherwise aware +of Composition Functions to use them. They need only use a `Composition` that +includes one or more entries in its `spec.functions` array. + +Composition Functions are designed to be run in a pipeline, so you can 'stack' +several of them together. Each Function is passed the output of the previous +Function as its input. Functions can also be used in conjunction with P&T +Composition (a `spec.resources` array). + +In the following example P&T Composition composes an RDS instance. A pipeline of +(hypothetical) Composition Functions then mutates the desired RDS instance by +adding a randomly generated password, and composes an RDS security group. + +```yaml +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: example +spec: + compositeTypeRef: + apiVersion: database.example.org/v1alpha1 + kind: XPostgreSQLInstance + resources: + - name: rds-instance + base: + apiVersion: rds.aws.upbound.io/v1beta1 + kind: Instance + spec: + forProvider: + dbName: exmaple + instanceClass: db.t3.micro + region: us-west-2 + skipFinalSnapshot: true + username: exampleuser + engine: postgres + engineVersion: "12" + patches: + - fromFieldPath: spec.parameters.storageGB + toFieldPath: spec.forProvider.allocatedStorage + connectionDetails: + - type: FromFieldPath + name: username + fromFieldPath: spec.forProvider.username + - type: FromConnectionSecretKey + name: password + fromConnectionSecretKey: attribute.password + functions: + - name: rds-instance-password + type: Container + container: + image: xpkg.upbound.io/provider-aws-xfns/random-rds-password:v0.1.0 + - name: compose-dbsecuritygroup + type: Container + container: + image: xpkg.upbound.io/example-org/compose-rds-securitygroup:v0.9.0 +``` + + +Use `kubectl explain` to explore the configuration options available when using +Composition Functions, or take a look at the following example. + +{{< expand "View Composition Function configuration options" >}} +```shell +$ kubectl explain composition.spec.functions +KIND: Composition +VERSION: apiextensions.crossplane.io/v1 + +RESOURCE: Functions <[]Object> + +DESCRIPTION: + Functions is list of Composition Functions that will be used when a + composite resource referring to this composition is created. At least one + of resources and Functions must be specified. If both are specified the + resources will be rendered first, then passed to the Functions for further + processing. THIS IS AN ALPHA FIELD. Do not use it in production. It is not + honored unless the relevant Crossplane feature flag is enabled, and may be + changed or removed without notice. + + A Function represents a Composition Function. + +FIELDS: + config <> + Config is an optional, arbitrary Kubernetes resource (i.e. a resource with + an apiVersion and kind) that will be passed to the Composition Function as + the 'config' block of its FunctionIO. + + container + Container configuration of this Function. + + name -required- + Name of this Function. Must be unique within its Composition. + + type -required- + Type of this Function. +``` +{{< /expand >}} + +{{< expand "An example of most Composition Function configuration options" >}} +```yaml +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: example +spec: + compositeTypeRef: + apiVersion: database.example.org/v1alpha1 + kind: XPostgreSQLInstance + functions: + - name: my-cool-Function + # Currently only Container is supported. Other types may be added in future. + type: Container + # Configuration specific to type: Container. + container: + # The OCI image to pull and run. + image: xkpg.io/my-cool-Function:0.1.0 + # Whether to pull the Function image Never, Always, or IfNotPresent. + imagePullPolicy: IfNotPresent + # Note that only resource limits are supported - not requests. + # The Function will be run with the specified resource limits, specified + # in Kubernetes-style resource.Quantity form. + resources: + limits: + # Defaults to 128Mi + memory: 64Mi + # Defaults to 100m (a 10th of a core) + cpu: 250m + # Defaults to 'Isolated' - an isolated network namespace with no network + # access. Use 'Runner' to allow a Function access to the runner's (the xfn + # container's) network namespace. + network: + policy: Runner + # How long the Function may run before it's killed. Defaults to 20s. + # Keep in mind the Function pipeline is typically invoked once every + # 30 to 60 seconds - sometimes more frequently during error conditions. + timeout: 30s + # An arbitrary Kubernetes resource. Passed to the Function as the config + # block of its FunctionIO. Doesn't need to exist as a Custom Resource (CR), + # since this resource doesn't exist by itself in the API server but must be + # a valid Kubernetes resource (have an apiVersion and kind). + config: + apiVersion: database.example.org/v1alpha1 + kind: Config + metadata: + name: cloudsql + spec: + version: POSTGRES_9_6 +``` +{{< /expand >}} + +Use `kubectl describe ` to debug Composition Functions. Look +for status conditions and events. Most Functions will emit events associated +with the XR if they experience issues. + +## Building a function + + Crossplane doesn't have opinions about how a Composition Function is + implemented. Functions must: + + * Be packaged as an OCI image, where the `ENTRYPOINT` is the Function. + * Accept input in the form of a `FunctionIO` document on stdin. + * Return the `FunctionIO` they were passed, optionally mutated, on stdout. + * Run within the constraints specified by the Composition that includes them, + such as timeouts, compute, network access. + +This means Functions may be written using a general purpose programming language +like Python, Go, or TypeScript. They may also be implemented using a shell +script, or an existing tool like Helm or Kustomize. + +### FunctionIO + +When a Composition Function runner like `xfn` runs your Function it will write +`FunctionIO` to its stdin. A `FunctionIO` is a Kubernetes style YAML manifest. +It's not a custom resource (it never gets created in the API server) but it +follows Kubernetes conventions. + +A `FunctionIO` consists of: + +* An optional, arbitrary `config` object. +* The `observed` state of the XR and any existing composed resources. +* The `desired` state of the XR and any composed resources. +* Optional `results` of the Function pipeline. + +Here's a brief example of a `FunctionIO`: + +```yaml +apiVersion: apiextensions.crossplane.io/v1alpha1 +kind: FunctionIO +config: + apiVersion: database.example.org/v1alpha1 + kind: Config + metadata: + name: cloudsql + spec: + version: POSTGRES_9_6 +observed: + composite: + resource: + apiVersion: database.example.org/v1alpha1 + kind: XPostgreSQLInstance + metadata: + name: platform-ref-gcp-db-p9wrj + connectionDetails: + - name: privateIP + value: 10.135.0.3 + resources: + - name: db-instance + resource: + apiVersion: sql.gcp.upbound.io/v1beta1 + kind: DatabaseInstance + metadata: + name: platform-ref-gcp-db-p9wrj-tvvtg + connectionDetails: + - name: privateIP + value: 10.135.0.3 +desired: + composite: + resource: + apiVersion: database.example.org/v1alpha1 + kind: XPostgreSQLInstance + metadata: + name: platform-ref-gcp-db-p9wrj + connectionDetails: + - name: privateIP + value: 10.135.0.3 + resources: + - name: db-instance + resource: + apiVersion: sql.gcp.upbound.io/v1beta1 + kind: DatabaseInstance + metadata: + name: platform-ref-gcp-db-p9wrj-tvvtg + - name: db-user + resource: + apiVersion: sql.gcp.upbound.io/v1beta1 + kind: User + metadata: + name: platform-ref-gcp-db-p9wrj-z8lpz + connectionDetails: + - name: password + type: FromValue + value: very-secret + readinessChecks: + - type: None +results: +- severity: Normal + message: "Successfully composed GCP SQL user" +``` + +The `config` object is copied from the `Composition`. It will match what's +passed as your Function's `config` in the `Functions` array. It must be a valid +Kubernetes object - have an `apiVersion` and `kind`. + +The `observed` state of the XR and any existing composed resources reflects the +observed state at the beginning of a reconcile, before any Composition happens. +Your Function will only see composite and composed resources that _actually +exist_ in the API server in the `observed` state. The `observed` state also +includes any observed connection details. + +The `desired` state of the XR and composed resources is how your Function tells +Crossplane what it should do. Crossplane 'bootstraps' the initial desired state +passed to a Function pipeline with: + +* A copy of the observed state of the XR. +* A copy of the observed state of any existing composed resources. +* Any new composed resources or modifications to observed resources produced + from the `resources` array. + +When adding a new desired resource to the `desired.resources` array you don't +need to: + +* Update the XR's resource references. +* Add any composition annotations like `crossplane.io/composite-resource-name`. +* Set the XR as a controller/owner reference of the desired resource. + +Crossplane will take care of all of these for you. It won't do anything else, +including setting a sensible `metadata.name` for the new composed resource - +this is up to your Function. + +Finally, the `results` array allows your Function to surface events and debug +logs on the XR. Results support the following severities: + +* `Normal` emits a debug log and a `Normal` event associated with the XR. +* `Warning` emits a debug log and a `Warning` event associated with the XR. +* `Fatal` stops the Composition process before applying any changes. + +When Crossplane encounters a `Fatal` result it will finish running the +Composition Function pipeline. Crossplane will then return an error without +applying any changes to the API server. Crossplane surfaces this error as a +`Warning` event, a debug log, and by setting the `Synced` status condition of +the XR to "False". + +The preceding example is heavily edited for brevity. Expand the following +example for a more detailed, realistic, and commented example of a `FunctionIO`. + +{{< expand "A more detailed example" >}} +In this example a `XPostgreSQLInstance` XR has one existing composed resource - +`db-instance`. The composition Function returns a `desired` object with one new +composed resource, a `db-user`, to tell Crossplane it should also create a +database user. + +```yaml +apiVersion: apiextensions.crossplane.io/v1alpha1 +kind: FunctionIO +config: + apiVersion: database.example.org/v1alpha1 + kind: Config + metadata: + name: cloudsql + spec: + version: POSTGRES_9_6 +observed: + # The observed state of the Composite Resource. + composite: + resource: + apiVersion: database.example.org/v1alpha1 + kind: XPostgreSQLInstance + metadata: + creationTimestamp: "2023-01-27T23:47:12Z" + finalizers: + - composite.apiextensions.crossplane.io + generateName: platform-ref-gcp-db- + generation: 5 + labels: + crossplane.io/claim-name: platform-ref-gcp-db + crossplane.io/claim-namespace: default + crossplane.io/composite: platform-ref-gcp-db-p9wrj + name: platform-ref-gcp-db-p9wrj + resourceVersion: "6817" + uid: 96623f41-be2e-4eda-84d4-9668b48e284d + spec: + claimRef: + apiVersion: database.example.org/v1alpha1 + kind: PostgreSQLInstance + name: platform-ref-gcp-db + namespace: default + compositionRef: + name: xpostgresqlinstances.database.example.org + compositionRevisionRef: + name: xpostgresqlinstances.database.example.org-eb6c684 + compositionUpdatePolicy: Automatic + parameters: + storageGB: 10 + resourceRefs: + - apiVersion: sql.gcp.upbound.io/v1beta1 + kind: DatabaseInstance + name: platform-ref-gcp-db-p9wrj-tvvtg + writeConnectionSecretToRef: + name: 96623f41-be2e-4eda-84d4-9668b48e284d + namespace: upbound-system + status: + conditions: + - lastTransitionTime: "2023-01-27T23:47:12Z" + reason: ReconcileSuccess + status: "True" + type: Synced + - lastTransitionTime: "2023-01-28T00:09:12Z" + reason: Creating + status: "False" + type: Ready + connectionDetails: + lastPublishedTime: "2023-01-28T00:08:12Z" + # Any observed Composite Resource connection details. + connectionDetails: + - name: privateIP + value: 10.135.0.3 + # The observed state of any existing Composed Resources. + resources: + - name: db-instance + resource: + apiVersion: sql.gcp.upbound.io/v1beta1 + kind: DatabaseInstance + metadata: + annotations: + crossplane.io/composition-resource-name: db-instance + crossplane.io/external-name: platform-ref-gcp-db-p9wrj-tvvtg + creationTimestamp: "2023-01-27T23:47:12Z" + finalizers: + - finalizer.managedresource.crossplane.io + generateName: platform-ref-gcp-db-p9wrj- + generation: 80 + labels: + crossplane.io/claim-name: platform-ref-gcp-db + crossplane.io/claim-namespace: default + crossplane.io/composite: platform-ref-gcp-db-p9wrj + name: platform-ref-gcp-db-p9wrj-tvvtg + ownerReferences: + - apiVersion: database.example.org/v1alpha1 + blockOwnerDeletion: true + controller: true + kind: XPostgreSQLInstance + name: platform-ref-gcp-db-p9wrj + uid: 96623f41-be2e-4eda-84d4-9668b48e284d + resourceVersion: "7992" + uid: 43919834-fdce-427e-85d9-d03eab9501f1 + spec: + forProvider: + databaseVersion: POSTGRES_13 + deletionProtection: false + project: example + region: us-west2 + settings: + - diskSize: 10 + ipConfiguration: + - privateNetwork: projects/example/global/networks/platform-ref-gcp-cluster + privateNetworkRef: + name: platform-ref-gcp-cluster + tier: db-f1-micro + providerConfigRef: + name: default + writeConnectionSecretToRef: + name: 96623f41-be2e-4eda-84d4-9668b48e284d-gcp-postgresql + namespace: upbound-system + status: + atProvider: + connectionName: example:us-west2:platform-ref-gcp-db-p9wrj-tvvtg + firstIpAddress: 34.102.103.85 + id: platform-ref-gcp-db-p9wrj-tvvtg + privateIpAddress: 10.135.0.3 + publicIpAddress: 34.102.103.85 + settings: + - version: 1 + conditions: + - lastTransitionTime: "2023-01-28T00:07:30Z" + reason: Available + status: "True" + type: Ready + - lastTransitionTime: "2023-01-27T23:47:14Z" + reason: ReconcileSuccess + status: "True" + type: Synced + # Any observed composed resource connection details. + connectionDetails: + - name: privateIP + value: 10.135.0.3 +desired: + # The observed state of the Composite Resource. + composite: + resource: + apiVersion: database.example.org/v1alpha1 + kind: XPostgreSQLInstance + metadata: + creationTimestamp: "2023-01-27T23:47:12Z" + finalizers: + - composite.apiextensions.crossplane.io + generateName: platform-ref-gcp-db- + generation: 5 + labels: + crossplane.io/claim-name: platform-ref-gcp-db + crossplane.io/claim-namespace: default + crossplane.io/composite: platform-ref-gcp-db-p9wrj + name: platform-ref-gcp-db-p9wrj + resourceVersion: "6817" + uid: 96623f41-be2e-4eda-84d4-9668b48e284d + spec: + claimRef: + e apiVersion: database.example.org/v1alpha1 + kind: PostgreSQLInstance + name: platform-ref-gcp-db + namespace: default + compositionRef: + name: xpostgresqlinstances.database.example.org + compositionRevisionRef: + name: xpostgresqlinstances.database.example.org-eb6c684 + compositionUpdatePolicy: Automatic + parameters: + storageGB: 10 + resourceRefs: + - apiVersion: sql.gcp.upbound.io/v1beta1 + kind: DatabaseInstance + name: platform-ref-gcp-db-p9wrj-tvvtg + writeConnectionSecretToRef: + name: 96623f41-be2e-4eda-84d4-9668b48e284d + namespace: upbound-system + status: + conditions: + - lastTransitionTime: "2023-01-27T23:47:12Z" + reason: ReconcileSuccess + status: "True" + type: Synced + - lastTransitionTime: "2023-01-28T00:09:12Z" + reason: Creating + status: "False" + type: Ready + connectionDetails: + lastPublishedTime: "2023-01-28T00:08:12Z" + # Any desired Composite Resource connection details. Your Composition + # Function can add new entries to this array and Crossplane will record them + # as the XR's connection details. + connectionDetails: + - name: privateIP + value: 10.135.0.3 + # The desired composed resources. + resources: + # This db-instance matches the entry in observed. Functions must include any + # observed resources in their desired resources array. If you omit an observed + # resource from the desired resources array Crossplane will delete it. + # Crossplane will 'bootstrap' the desired state passed to the Function + # pipeline by copying all observed resources into the desired resources array. + - name: db-instance + resource: + apiVersion: sql.gcp.upbound.io/v1beta1 + kind: DatabaseInstance + metadata: + annotations: + crossplane.io/composition-resource-name: DBInstance + crossplane.io/external-name: platform-ref-gcp-db-p9wrj-tvvtg + creationTimestamp: "2023-01-27T23:47:12Z" + finalizers: + - finalizer.managedresource.crossplane.io + generateName: platform-ref-gcp-db-p9wrj- + generation: 80 + labels: + crossplane.io/claim-name: platform-ref-gcp-db + crossplane.io/claim-namespace: default + crossplane.io/composite: platform-ref-gcp-db-p9wrj + name: platform-ref-gcp-db-p9wrj-tvvtg + ownerReferences: + - apiVersion: database.example.org/v1alpha1 + blockOwnerDeletion: true + controller: true + kind: XPostgreSQLInstance + name: platform-ref-gcp-db-p9wrj + uid: 96623f41-be2e-4eda-84d4-9668b48e284d + resourceVersion: "7992" + uid: 43919834-fdce-427e-85d9-d03eab9501f1 + spec: + forProvider: + databaseVersion: POSTGRES_13 + deletionProtection: false + project: example + region: us-west2 + settings: + - diskSize: 10 + ipConfiguration: + - privateNetwork: projects/example/global/networks/platform-ref-gcp-cluster + privateNetworkRef: + name: platform-ref-gcp-cluster + tier: db-f1-micro + providerConfigRef: + name: default + writeConnectionSecretToRef: + name: 96623f41-be2e-4eda-84d4-9668b48e284d-gcp-postgresql + namespace: upbound-system + status: + atProvider: + connectionName: example:us-west2:platform-ref-gcp-db-p9wrj-tvvtg + firstIpAddress: 34.102.103.85 + id: platform-ref-gcp-db-p9wrj-tvvtg + privateIpAddress: 10.135.0.3 + publicIpAddress: 34.102.103.85 + settings: + - version: 1 + conditions: + - lastTransitionTime: "2023-01-28T00:07:30Z" + reason: Available + status: "True" + type: Ready + - lastTransitionTime: "2023-01-27T23:47:14Z" + reason: ReconcileSuccess + status: "True" + type: Synced + # This db-user is a desired composed resource that doesn't yet exist. This + # Composition Function is requesting it be created. + - name: db-user + resource: + apiVersion: sql.gcp.upbound.io/v1beta1 + kind: User + metadata: + annotations: + crossplane.io/composition-resource-name: db-user + crossplane.io/external-name: platform-ref-gcp-db-p9wrj-z8lpz + creationTimestamp: "2023-01-27T23:47:12Z" + finalizers: + - finalizer.managedresource.crossplane.io + generateName: platform-ref-gcp-db-p9wrj- + generation: 115 + labels: + crossplane.io/claim-name: platform-ref-gcp-db + crossplane.io/claim-namespace: default + crossplane.io/composite: platform-ref-gcp-db-p9wrj + name: platform-ref-gcp-db-p9wrj-z8lpz + ownerReferences: + - apiVersion: database.example.org/v1alpha1 + blockOwnerDeletion: true + controller: true + kind: XPostgreSQLInstance + name: platform-ref-gcp-db-p9wrj + uid: 96623f41-be2e-4eda-84d4-9668b48e284d + resourceVersion: "9951" + uid: ab5dafbe-2bc8-47ea-8b5b-9bcb40183e45 + spec: + forProvider: + instance: platform-ref-gcp-db-p9wrj-tvvtg + project: example + providerConfigRef: + name: default + # Any desired connection details for the new db-user composed resource. + # Desired connection details can be FromValue, FromFieldPath, or + # FromConnectionSecretKey, just like their P&T Composition equivalents. + connectionDetails: + - name: password + type: FromValue + value: very-secret + # Any desired readiness checks for the new db-user composed resource. + # Desired readiness checks can be NonEmpty, MatchString, MatchInteger, or + # None, just like their P&T Composition equivalents. + readinessChecks: + - type: None +# An optional array of results. +results: +- severity: Normal + message: "Successfully composed GCP SQL user" +``` +{{< /expand >}} + +### An example Function + +You can write a Composition Function using any programming language that can be +containerized, or existing tools like Helm or Kustomize. + +Here's a Python Composition Function that doesn't create any new desired +resources, but instead annotates any existing desired resources with a quote. +Because this function accesses the internet it needs to be run with the `Runner` +network policy. + +```python +import sys + +import requests +import yaml + +ANNOTATION_KEY_AUTHOR = "quotable.io/author" +ANNOTATION_KEY_QUOTE = "quotable.io/quote" + + +def get_quote() -> tuple[str, str]: + """Get a quote from quotable.io""" + rsp = requests.get("https://api.quotable.io/random") + rsp.raise_for_status() + j = rsp.json() + return (j["author"], j["content"]) + + +def read_Functionio() -> dict: + """Read the FunctionIO from stdin.""" + return yaml.load(sys.stdin.read(), yaml.Loader) + + +def write_Functionio(Functionio: dict): + """Write the FunctionIO to stdout and exit.""" + sys.stdout.write(yaml.dump(Functionio)) + sys.exit(0) + + +def result_warning(Functionio: dict, message: str): + """Add a warning result to the supplied FunctionIO.""" + if "results" not in Functionio: + Functionio["results"] = [] + Functionio["results"].append({"severity": "Warning", "message": message}) + + +def main(): + """Annotate all desired composed resources with a quote from quotable.io""" + try: + Functionio = read_Functionio() + except yaml.parser.ParserError as err: + sys.stdout.write("cannot parse FunctionIO: {}\n".format(err)) + sys.exit(1) + + # Return early if there are no desired resources to annotate. + if "desired" not in Functionio or "resources" not in Functionio["desired"]: + write_Functionio(Functionio) + + # If we can't get our quote, add a warning and return early. + try: + quote, author = get_quote() + except requests.exceptions.RequestException as err: + result_warning(Functionio, "Cannot get quote: {}".format(err)) + write_Functionio(Functionio) + + # Annotate all desired resources with our quote. + for r in Functionio["desired"]["resources"]: + if "resource" not in r: + # This shouldn't happen - add a warning and continue. + result_warning( + Functionio, + "Desired resource {name} missing resource body".format( + name=r.get("name", "unknown") + ), + ) + continue + + if "metadata" not in r["resource"]: + r["resource"]["metadata"] = {} + + if "annotations" not in r["resource"]["metadata"]: + r["resource"]["metadata"]["annotations"] = {} + + if ANNOTATION_KEY_QUOTE in r["resource"]["metadata"]["annotations"]: + continue + + r["resource"]["metadata"]["annotations"][ANNOTATION_KEY_AUTHOR] = author + r["resource"]["metadata"]["annotations"][ANNOTATION_KEY_QUOTE] = quote + + write_Functionio(Functionio) + + +if __name__ == "__main__": + main() +``` + +Building this function requires its `requirements.txt` and a `Dockerfile`: + +{{< expand "The Function's requirements" >}} +``` +certifi==2022.12.7 +charset-normalizer==3.0.1 +click==8.1.3 +idna==3.4 +pathspec==0.10.3 +platformdirs==2.6.2 +PyYAML==6.0 +requests==2.28.2 +tomli==2.0.1 +urllib3==1.26.14 +``` +{{< /expand >}} + +{{< expand "The Function's Dockerfile" >}} +```Dockerfile +FROM debian:11-slim AS build +RUN apt-get update && \ + apt-get install --no-install-suggests --no-install-recommends --yes python3-venv && \ + python3 -m venv /venv && \ + /venv/bin/pip install --upgrade pip setuptools wheel + +FROM build AS build-venv +COPY requirements.txt /requirements.txt +RUN /venv/bin/pip install --disable-pip-version-check -r /requirements.txt + +FROM gcr.io/distroless/python3-debian11 +COPY --from=build-venv /venv /venv +COPY . /app +WORKDIR /app +ENTRYPOINT ["/venv/bin/python3", "main.py"] +``` +{{< /expand >}} + +Create and push the Function just like you would any Docker image: + +```shell +# Build the Function. +$ docker build . +Sending build context to Docker daemon 38.99MB +Step 1/10 : FROM debian:11-slim AS build + ---> 4810399f6c13 +Step 2/10 : RUN apt-get update && apt-get install --no-install-suggests --no-install-recommends --yes python3-venv gcc && python3 -m venv /venv && /venv/bin/pip install --upgrade pip setuptools wheel + ---> Using cache + ---> 9b34960c88d7 +Step 3/10 : FROM build AS build-venv + ---> 9b34960c88d7 +Step 4/10 : COPY requirements.txt /requirements.txt + ---> Using cache + ---> fae19dad52af +Step 5/10 : RUN /venv/bin/pip install --disable-pip-version-check -r /requirements.txt + ---> Using cache + ---> f4b811c75812 +Step 6/10 : FROM gcr.io/distroless/python3-debian11 + ---> 2a0e74a2b005 +Step 7/10 : COPY --from=build-venv /venv /venv + ---> Using cache + ---> cf727d3f20d3 +Step 8/10 : COPY . /app + ---> a044aef45e32 +Step 9/10 : WORKDIR /app + ---> Running in d08a6144815b +Removing intermediate container d08a6144815b + ---> 7250f5aa653e +Step 10/10 : ENTRYPOINT ["/venv/bin/python3", "main.py"] + ---> Running in 3f4d9dc55bad +Removing intermediate container 3f4d9dc55bad + ---> bfd2f920c591 +Successfully built bfd2f920c591 + +# Tag the Function. +$ docker tag bfd2f920c591 example-org/xfn-quotable-simple:v0.1.0 + +# Push the Function. +$ docker push xpkg.upbound.io/example-org/xfn-quotable-simple:v0.1.0 +The push refers to repository [xpkg.upbound.io/example-org/xfn-quotable-simple] +cf6d94b88843: Pushed +77646fd315d2: Mounted from example-org/xfn-quotable +50630ee42b6e: Mounted from example-org/xfn-quotable +7e2cf97ed8c4: Mounted from example-org/xfn-quotable +96e320b34b54: Mounted from example-org/xfn-quotable +fba4381f2bb7: Mounted from example-org/xfn-quotable +v0.1.0: digest: sha256:d8a6404e5fe38936aa8dadd861fea35ede0aded6168d501052f91cdabab0135e size: 1584 +``` + +You can now use this Function in your Composition. The following example will +create an `RDSInstance` using P&T Composition, then run the Function to annotate +it with a quote. + +```yaml +apiVersion: apiextensions.crossplane.io/v1 +kind: Composition +metadata: + name: example +spec: + compositeTypeRef: + apiVersion: database.example.org/v1alpha1 + kind: XPostgreSQLInstance + resources: + - name: rds-instance + base: + apiVersion: rds.aws.upbound.io/v1beta1 + kind: Instance + spec: + forProvider: + dbName: example + instanceClass: db.t3.micro + region: us-west-2 + skipFinalSnapshot: true + username: exampleuser + engine: postgres + engineVersion: "12" + patches: + - fromFieldPath: spec.parameters.storageGB + toFieldPath: spec.forProvider.allocatedStorage + connectionDetails: + - type: FromFieldPath + name: username + fromFieldPath: spec.forProvider.username + - type: FromConnectionSecretKey + name: password + fromConnectionSecretKey: attribute.password + functions: + - name: quotable + type: Container + container: + image: xpkg.upbound.io/example-org/xfn-quotable-simple:v0.1.0 + network: + policy: Runner +``` + +### Tips for new functions + +Here are some things to keep in mind when building a Composition Function: + +* Your Function may be running as part of a pipeline. This means your Function + _must_ pass through any desired state that it's unconcerned with. If your + Function is passed a desired composed resource and doesn't return that + composed resource in its output, it will be deleted. Crossplane considers the + desired state of the XR and any composed resources to be whatever `FunctionIO` + is returned by the last Function in the pipeline. +* Crossplane won't set a `metadata.name` for your desired resources resources. + It's a good practice to match P&T Composition's behavior by setting + `metadata.generateName: "name-of-the-xr-"` for any new desired resources. +* Don't add new entries to the desired resources array every time your function + is invoked. Remember to check whether your desired resource is already in the + `observed` and/or `desired` objects. You may need to update it rather than + create it. +* Don't bypass providers. Composition Functions are designed to tell Crossplane + how to orchestrate managed resources - not to directly orchestrate external + systems. +* Include your function name and version in any results you return to aid in + debugging. +* Write tests for your function. Pass it a `FunctionIO` on stdin in and ensure + it returns the expected `FunctionIO` on stdout. +* Keep your Functions fast and lightweight. Remember that Crossplane runs them + approximately once every 30-60 seconds. + +## The xfn runner + +Composition Function runners are designed to be pluggable. Each time Crossplane +needs to invoke a Composition Function it makes a gRPC call to a configurable +endpoint. The default, reference Composition Function runner is named `xfn`. + +{{< hint "note" >}} +The default runner endpoint is `unix-abstract:crossplane/fn/default.sock`. It's +possible to run Functions using a different endpoint, for example: + +```yaml + functions: + - name: my-cool-Function + type: Container + container: + image: xkpg.io/my-cool-Function:0.1.0 + runner: + endpoint: unix-abstract:/your/custom/runner.sock +``` + +Currently Crossplane uses unauthenticated, unencrypted gRPC requests to run +Functions, so requests shouldn't be sent over the network. Encryption and +authentication will be added in a future release. +{{< /hint >}} + +`xfn` runs as a sidecar container within the Crossplane pod. It runs each +Composition Function as a nested [rootless container][rootless-containers]. + +{{< img src="master/guides/composition-functions-xfn-runner.png" alt="Crossplane running Functions using xfn via gRPC" size="tiny" >}} + +The Crossplane Helm chart deploys `xfn` with: + +* The [`Unconfined` seccomp profile][kubernetes-seccomp]. +* The `CAP_SETUID` and `CAP_SETGID` capabilities. + +The `Unconfined` seccomp profile allows Crossplane to make required syscalls +such as `unshare` and `mount` that are not allowed by most `RuntimeDefault` +profiles. It's possible to run `xfn` with nearly the same restrictions as most +`RuntimeDefault` profiles by authoring a custom `Localhost` profile. Refer to +the [seccomp documentation][kubernetes-seccomp] for information on how to do so. + +Granting `CAP_SETUID` and `CAP_SETGID` allows `xfn` to create Function +containers that support up to 65,536 UIDs and GIDs. If `xfn` is run without +these capabilities it will be restricted to creating Function containers that +support only UID and GID 0. + +Regardless of capabilities `xfn` always runs each Composition Function as an +unprivileged user. That user will appear to be root inside the Composition +Function container thanks to [`user_namespaces(7)`]. + +[rootless-containers]: https://rootlesscontaine.rs +[kubernetes-seccomp]: https://kubernetes.io/docs/tutorials/security/seccomp/ +[`user_namespaces(7)`]: https://man7.org/linux/man-pages/man7/user_namespaces.7.html \ No newline at end of file From 8af5cbaa28fafd06c248b233dad88773c668572a Mon Sep 17 00:00:00 2001 From: Nic Cope Date: Tue, 31 Jan 2023 15:28:48 -0800 Subject: [PATCH 2/2] Highlight requirements.txt as Python Signed-off-by: Nic Cope --- content/knowledge-base/guides/composition-functions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/knowledge-base/guides/composition-functions.md b/content/knowledge-base/guides/composition-functions.md index df40d06e..39b12a8b 100644 --- a/content/knowledge-base/guides/composition-functions.md +++ b/content/knowledge-base/guides/composition-functions.md @@ -781,7 +781,7 @@ if __name__ == "__main__": Building this function requires its `requirements.txt` and a `Dockerfile`: {{< expand "The Function's requirements" >}} -``` +```python certifi==2022.12.7 charset-normalizer==3.0.1 click==8.1.3