Skip to content
This repository was archived by the owner on Jul 7, 2024. It is now read-only.

Commit

Permalink
Merge pull request #8 from salemove/base64-fns
Browse files Browse the repository at this point in the history
take-five authored Jun 23, 2024
2 parents c50312e + 593ac5a commit 9576b0e
Showing 8 changed files with 59 additions and 37 deletions.
45 changes: 31 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -25,8 +25,6 @@ spec:
spec:
source:
inline: |
import * as Base64 from 'base64';
export default (req, rsp) => {
const composite = req.observed.composite.resource;
@@ -41,8 +39,12 @@ spec:
});
if (req.observed.resources?.bucket) {
// expose some connection details, get value from a resource generated within this function
rsp.setConnectionDetails({ bucketName: req.observed.resources.bucket.resource.metadata.name });
// 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 });
@@ -62,7 +64,7 @@ kind: Function
metadata:
name: function-javascript
spec:
package: docker.io/salemove/crossplane-function-javascript:v0.1.0
package: docker.io/salemove/crossplane-function-javascript:v0.2.0
EOF
```

@@ -103,16 +105,23 @@ a default function. The exported function is called with 2 arguments:
* `response.setConnectionDetails(details)` - sets the desired composite resource
connection details.

Note that connection details from other observed resources are base64-encoded, and
if you want to use them in other composed resources, or in the composition connection
details, you need to decode them first:
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:
```javascript
import * as Base64 from 'base64';

export default function (req, rsp) {
// ...skip for brevity
const username = Base64.decode(req.observed.resources.user.connectionDetails.username);
rsp.setConnectionDetails({ username });
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
@@ -145,11 +154,18 @@ For convenience, the runtime includes some "faux" external packages:
console.error('Error');
}
```
* `base64` - includes functions for working with Base64 encoding:
* `btoa`, `atob` - functions for working with Base64 encoding:
```javascript
const enc = btoa('string');
const dec = atob(enc); // => 'string'
```

**NB!** Unlike functions [`Window.btoa()`][base64] and [`Window.atob()`][base64] available
in browsers, these functions work natively with UTF-8 strings and don't require additional
manipulations:
```javascript
import * as Base64 from 'base64';
const enc = Base64.encode('string');
const dec = Base64.decode(enc); // => 'string'
// this will work in your composition function, but won't work in browsers
btoa("a Ā 𐀀 文 🦄")
```
* `yaml` - includes functions for encoding and decoding objects into YAML format:
```javascript
@@ -188,3 +204,4 @@ $ make xpkg.build
[resp]: https://buf.build/crossplane/crossplane/docs/main:apiextensions.fn.proto.v1beta1#apiextensions.fn.proto.v1beta1.RunFunctionResponse
[esbuild]: https://esbuild.github.io/
[webpack]: https://webpack.js.org/
[base64]: https://developer.mozilla.org/en-US/docs/Glossary/Base64
3 changes: 1 addition & 2 deletions example/composition.yaml
Original file line number Diff line number Diff line change
@@ -23,7 +23,6 @@ spec:
source:
inline: |
import * as YAML from 'yaml';
import * as Base64 from 'base64';
export default (req, rsp) => {
console.log("request", JSON.stringify(req, null, 2));
@@ -46,7 +45,7 @@ spec:
region: region,
compositeRegion: req.observed.composite.resource.spec.region,
yamlTest: YAML.stringify({ foo: "bar", bax: [12, 23] }),
b64test: Base64.encode('abcdefgh')
b64test: btoa('abcdefgh')
}
}
});
2 changes: 1 addition & 1 deletion example/functions.yaml
Original file line number Diff line number Diff line change
@@ -8,4 +8,4 @@ metadata:
render.crossplane.io/runtime: Development
spec:
# This is ignored when using the Development runtime.
package: function-javascript
package: docker.io/salemove/crossplane-function-javascript:v0.2.0
2 changes: 1 addition & 1 deletion fn_test.go
Original file line number Diff line number Diff line change
@@ -152,7 +152,7 @@ func TestRunFunction(t *testing.T) {
forProvider: { region: req.observed.composite.resource.spec.region }
}
});
rsp.setConnectionDetails({ key: 'value' });
rsp.setConnectionDetails({ key: btoa('value') });
};`),
Observed: &fnv1beta1.State{
Composite: &fnv1beta1.Resource{
3 changes: 2 additions & 1 deletion internal/js/runtime.go
Original file line number Diff line number Diff line change
@@ -27,8 +27,9 @@ func NewRuntime() *Runtime {

registry := new(require.Registry)
registry.Enable(vm)
registry.RegisterNativeModule("base64", modules.Base64)
registry.RegisterNativeModule("yaml", modules.YAML)

modules.Base64.Enable(vm)
console.Enable(vm)

return &Runtime{vm: vm}
16 changes: 8 additions & 8 deletions internal/js/runtime_test.go
Original file line number Diff line number Diff line change
@@ -126,20 +126,20 @@ func TestRuntime_RunScript(t *testing.T) {
ok: false,
},
{
desc: "Base64.encode",
script: `import * as Base64 from "base64"; export default function() { return Base64.encode('abcd') }`,
expected: "YWJjZA==",
desc: "btoa",
script: `export default function() { return btoa('Hēłłõ, wöřłď') }`,
expected: "SMSTxYLFgsO1LCB3w7bFmcWCxI8=",
ok: true,
},
{
desc: "Base64.decode",
script: `import * as Base64 from "base64"; export default function() { return Base64.decode('YWJjZA==') }`,
expected: "abcd",
desc: "atob",
script: `export default function() { return atob('SMSTxYLFgsO1LCB3w7bFmcWCxI8=') }`,
expected: "Hēłłõ, wöřłď",
ok: true,
},
{
desc: "Base64.decode error",
script: `import * as Base64 from "base64"; export default function() { return Base64.decode('YWJjZA=') }`,
desc: "atob error",
script: `export default function() { return atob('YWJjZA=') }`,
ok: false,
},
}
16 changes: 8 additions & 8 deletions internal/modules/base64.go
Original file line number Diff line number Diff line change
@@ -10,20 +10,20 @@ import (
//
// Example (js):
//
// import * as Base64 from "base64";
// export default () => {
// return Base64.decode(Base64.encode("foo"))
// };
func Base64(runtime *goja.Runtime, module *goja.Object) {
o := module.Get("exports").(*goja.Object)
// const encoded = btoa('hello');
// const decoded = atob(encoded);
var Base64 = &Base64module{}

_ = o.Set("encode", func(call goja.FunctionCall) goja.Value {
type Base64module struct{}

func (b *Base64module) Enable(runtime *goja.Runtime) {
_ = runtime.Set("btoa", func(call goja.FunctionCall) goja.Value {
str := call.Argument(0).ToString().String()
result := base64.StdEncoding.EncodeToString([]byte(str))
return runtime.ToValue(result)
})

_ = o.Set("decode", func(call goja.FunctionCall) goja.Value {
_ = runtime.Set("atob", func(call goja.FunctionCall) goja.Value {
str := call.Argument(0).ToString().String()
result, err := base64.StdEncoding.DecodeString(str)
if err != nil {
9 changes: 7 additions & 2 deletions response.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package main

import (
"encoding/base64"

"dario.cat/mergo"

"github.com/crossplane/crossplane-runtime/pkg/fieldpath"
@@ -103,8 +105,11 @@ func (r *Response) UpdateCompositeStatus(status map[string]any) error {

// SetConnectionDetails sets the desired composite resource connection details
// in the function response.
func (r *Response) SetConnectionDetails(details resource.ConnectionDetails) {
r.desiredComposite.ConnectionDetails = details
func (r *Response) SetConnectionDetails(details map[string]string) {
for key, val := range details {
decoded, _ := base64.StdEncoding.DecodeString(val)
r.desiredComposite.ConnectionDetails[key] = decoded
}
}

func (r *Response) setFunctionResponse(rsp *fnv1beta1.RunFunctionResponse) error {

0 comments on commit 9576b0e

Please sign in to comment.