crossplane-function-javascript

command module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 24, 2024 License: Apache-2.0 Imports: 18 Imported by: 0

README

function-javascript

CI

A function for writing composition functions in ECMAScript/JavaScript.

Here's an example:

apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: function-javascript
spec:
  compositeTypeRef:
    apiVersion: example.crossplane.io/v1
    kind: XR
  mode: Pipeline
  pipeline:
  - step: run-the-template
    functionRef:
      name: function-javascript
    input:
      apiVersion: javascript.fn.crossplane.io/v1beta1
      kind: Input
      spec:
        source:
          inline: |
            export default (req, rsp) => {
              const composite = req.observed.composite.resource;

              rsp.setDesiredComposedResource('bucket', {
                apiVersion: 'example.org/v1alpha1',
                kind: 'Bucket',
                metadata: {
                  spec: {
                    region: composite.spec.region
                  }
                }
              });

              if (req.observed.resources?.bucket) {
                // Expose some connection details, get value from a resource generated within this function.
                // The function expects Base64-encoded strings. Use "btoa" function to encode plain strings.
                // ConnectionDetails from observed resources are already Base64-encoded.
                rsp.setConnectionDetails({
                  bucketName: btoa(req.observed.resources.bucket.resource.metadata.name) 
                });

                // patch composite resource status
                rsp.updateCompositeStatus({ bucketName: req.observed.resources.bucket.resource.metadata.name });
              }
            };
  - step: automatically-detect-ready-composed-resources
    functionRef:
       name: function-auto-ready

Install the JavaScript function to Cluster

cat <<EOF | kubectl apply -f -
apiVersion: pkg.crossplane.io/v1beta1
kind: Function
metadata:
  name: function-javascript
spec:
  package: docker.io/salemove/crossplane-function-javascript:v0.3.0
EOF

Using this function

At the moment, the function code can only be specified through Inline source.

The JavaScript runtime is based on Goja and expects the program to export a default function. The exported function is called with 2 arguments:

  • request - a RunFunctionRequest object converted into a nested plain map. This means that you can access the composite resource, any composed resources, and the function pipeline context using notation like:
    • request.observed.composite.resource.metadata.name
    • request.observed.resources.mywidget.resource.status.widgets
    • request.observed.resources.mywidget.connectionDetails
    • request.context["apiextensions.crossplane.io/environment"]
    • request.context["apiextensions.crossplane.io/extra-resources"].mywidget[0]
  • response - an object through which you can manipulate the function response. The object has the following methods:
    • response.setDesiredComposedResource(name, properties) - set the desired composed resource for the current function. The resource properties are passed as plain map.

      To mark a desired resource as ready, use the javascript.fn.crossplane.io/ready annotation:

      export default function (req, rsp) {
        rsp.setDesiredCompositeResource('bucket', {
          apiVersion: 'example.org/v1',
          kind: 'Bucket',
          metadata: {
            annotations: { 'javascript.fn.crossplane.io/ready': 'True' }
          },
          spec: {
            // ...skipped for brevity
          }
        });
      }
      
    • response.setConnectionDetails(details) - sets the desired composite resource connection details.

      Connection details values must be Base64-encoded, use function btoa to encode plain strings to Base64.

      Connection details from other observed resources are already Base64-encoded, so you can pass their values to setConnectionDetails function as is:

      export default function (req, rsp) {
        // ...skip for brevity
        const username = req.observed.resources.user.connectionDetails.username;
        const host = "localhost";
      
        rsp.setConnectionDetails({
          username,
          host: btoa(host)
        });
      }
      
    • response.updateCompositeStatus(properties) - merges the desired composite resource status in the function response.

      export default function (req, rsp) {
        // ...skip for brevity
        rsp.updateCompositeStatus({ userCount: 1, message: 'All good' })
      }
      

External dependencies

Because the function isn't based on Node.js or any other of the full-fledged JavaScript runtimes, it doesn't support external dependencies or Node.js modules. However, users can use ESBuild, or Webpack, or any other similar tool to bundle external dependencies into a single JavaScript file, and inject it into the composition pipeline as a single blob.

See external-dependencies example in the examples/ folder.

For convenience, the runtime includes some "faux" external packages:

  • console - implements some of the JavaScript's Console API static methods. The output is logged in the function container logs:

    console.log('Hello');
    
    export default function (req, resp) {
      console.debug('Request', JSON.stringify(req));
      console.info('Info');
      console.warn('Warning');
      console.error('Error');
    }
    
  • btoa, atob - functions for working with Base64 encoding:

    const enc = btoa('string');
    const dec = atob(enc); // => 'string'
    

    NB! Unlike functions Window.btoa() and Window.atob() available in browsers, these functions work natively with UTF-8 strings and don't require additional manipulations:

    // this will work in your composition function, but won't work in browsers
    btoa("a Ā 𐀀 文 🦄")
    

Code transpilation

Goja natively only supports ECMAScript 5.1 syntax, so in order to use modern syntax features, the source code must be transpiled into a ES 5.1 syntax. For convenience, transpilation is built-in into the function server and is enabled by default.

For large functions, however, this additional pre-processing can impact performance, so if the function is already written in ES 5.1 compatible syntax (or pre-processed before injecting the source into a Composition), you can disable server-side transpilation:

apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: function-javascript
spec:
  compositeTypeRef:
    apiVersion: example.crossplane.io/v1
    kind: XR
  mode: Pipeline
  pipeline:
  - step: run-the-template
    functionRef:
      name: function-javascript
    input:
      apiVersion: javascript.fn.crossplane.io/v1beta1
      kind: Input
      spec:
        source:
          transpile: false # <-- disable transpilation
          inline: |
            // source code

Developing this function

This function uses Go, Docker, and the Crossplane CLI to build functions.

# Run code generation - see input/generate.go
$ make generate

# Run tests - see fn_test.go
$ make test

# Build the function's runtime image - see Dockerfile
$ make img.build

# Build a function package - see package/crossplane.yaml
$ make xpkg.build

Documentation

Overview

Package main implements a Composition Function.

Directories

Path Synopsis
input
v1beta1
Package v1beta1 contains the input type for this Function +kubebuilder:object:generate=true +groupName=javascript.fn.crossplane.io +versionName=v1beta1
Package v1beta1 contains the input type for this Function +kubebuilder:object:generate=true +groupName=javascript.fn.crossplane.io +versionName=v1beta1
internal
js
Package js is a thin wrapper around Goja runtime.
Package js is a thin wrapper around Goja runtime.
modules
Package modules contains additional JS modules exported into the runtime
Package modules contains additional JS modules exported into the runtime

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL