Experiment with DigitalOcean Functions

A few weeks ago I wrote about writing webhooks into Supabase tables. The fly.io app seems too much. A serverless version that is only run when needed feels a lot better. Supabase has something for that, but it only supports Typescript and I want to use Python.

So lets try DigitialOcean functions.

DO Functions have some caveats: no bring-your-own-domain support (so the url will stay ugly) and no option to run a real webframework (Flask, Django, Fastapi) as a function. They have their own solution for that: Apps. But without a free (serverless) plan. The functions have a free plan and that should be enough here.

First steps (using the cli) are:

  • Auth: doctl auth init

  • Install serverless: doctl serverless install

  • Connect to namespace (I created one in the UI before): doctl serverless connect

  • Copy project.yml.template to project.yml and set the 3 environment variables. This is to keep them out of the repository.

  • Deploy the app: doctl serverless deploy . --verbose-build

  • The function needs 512MB memory instead of the 256MB default! - This can be set in the DigitalOcean Functions UI for this function.

Development happens with one shell running and watching the logs: doctl serverless activations logs --follow

And in another shell the deployment (and curling) happens.

To test the function (but this does not have a valid signature to write the data into the database):

curl -X POST https://faas-fra1-XXXX.doserverless.co/api/v1/web/fn-AAAA-cd30-4430-b1cf-CCCC/ax-supabase/test \
--header "Content-Type: application/json" \
--data '{"id": "52ab6993-3f2e-46f7-b501-4fabdffa7178", "uid": "1001", "language": "en-GB", "collection_id": 1234,
"collection_name": "ax webhook example", "name": "Product 1001",
"text": "This is an example text for Product 1001.",
"text_modified": "2020-12-21T16:59:24.355771+00:00",
"html": "<p>This is an example text for Product 1001.</p>"}'

The real test is clicking on "generate" (or using the api) in the AX platform.

Challenges

The calculation of the signature is not so simple, because the function doesn't get the raw http call. The data fields given to the function are preprocessed and added as arguments. So the data structure has to be rebuild exactly in the way the AX platform does this. This may change and therefore break in the future.

Because everything is in the args given to the main method, all http attributes are in there, i.e. http method is in args.get("__ow_method"). To get the myax-signature the value is in args.get("__ow_headers").get("x-myax-signature") and is lowercased.

To install Python requirements, i.e. here supabase, a build.sh is needed that is called on deployment. The contents of my build.sh looks like this:

#!/bin/bash

set -e
virtualenv --without-pip virtualenv -p /usr/bin/python3.9
pip install supabase --target virtualenv/lib/python3.9/site-packages

This file has to be executable (a+x).

Conclusion

Overall the DigitalOcean Function does the same as the fly.io version, but without the need for an always running webapp. The code for this example is in this repository: https://github.com/mfa/ax-supabase-do.