├── BUILD.md ├── Makefile ├── README.md ├── bootstrap ├── examples ├── README.md ├── aws-sdk-example │ ├── README.md │ ├── aws_sdk_example │ │ └── core.cljs │ ├── package-lock.json │ └── package.json ├── http-example │ ├── README.md │ ├── deps.edn │ ├── make_deps_layer.sh │ ├── make_lambda.sh │ └── src │ │ └── http │ │ └── core.cljs ├── lib-example │ ├── lib │ │ └── promesa-1.9.0.jar │ └── lib_example │ │ └── core.cljs ├── minimal │ ├── README.md │ └── my_package │ │ └── my_ns.cljs ├── node-deps-example │ ├── README.md │ ├── make_lambda.sh │ ├── meaning │ │ ├── index.js │ │ └── package.json │ ├── nodejs │ │ ├── package-lock.json │ │ └── package.json │ ├── package-lock.json │ ├── response.txt │ ├── run_local.sh │ ├── runtime_local.cljs │ └── test_require │ │ └── core.cljs └── serverless-example │ ├── README.md │ ├── my_package │ └── my_ns.cljs │ ├── package-lock.json │ ├── package.json │ └── serverless.yml ├── response.txt ├── runtime.cljs └── test ├── create_runtime.sh └── run_tests.sh /BUILD.md: -------------------------------------------------------------------------------- 1 | # Build 2 | 3 | This Lumo AWS Lambda runtime relies on a lumo binary to be built and deployed. 4 | 5 | ## Compiling Lumo with Musl 6 | 7 | In order to avoid library mismatch AWS suggests to statically compile binaries and this is exactly what we are going to do thanks to [Andrea Richiardi](https://github.com/arichiardi)'s [docker-lumo-musl](https://github.com/arichiardi/docker-lumo-musl). 8 | 9 | First things first, pull down [the docker image](https://cloud.docker.com/repository/docker/arichiardi/lumo-musl-ami): 10 | 11 | ```shell 12 | docker pull arichiardi/lumo-musl-ami 13 | ``` 14 | 15 | Second, clone `lumo`: 16 | 17 | ```shell 18 | git clone git@github.com:anmonteiro/lumo # anywhere on your filesystem 19 | ``` 20 | 21 | Finally, build using the image: 22 | 23 | ``` 24 | docker run -v /path/to/lumo:/lumo -v $HOME/.m2:/root/.m2 -v $HOME/.boot/cache:/.boot/cache --rm arichiardi/lumo-musl-ami 25 | ``` 26 | 27 | The `/root/.m2` and `/.boot/cache` mappings are optional but recommended for 28 | avoiding downloading dependencies multiple times. 29 | 30 | The (long) process will compile the lumo static binary under `/path/to/lumo/build`. 31 | 32 | ## Building the runtime and publishing it as a Lambda layer 33 | 34 | The supplied `Makefile` in this repo will take care of the details: 35 | 36 | ```shell 37 | make clean # (if necessary) 38 | LUMO_BIN_PATH=/path/to/lumo make # point to lumo binary from the step above 39 | make publish 40 | ``` 41 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | RUNTIME_PACKAGE = runtime.zip 2 | 3 | all: ${RUNTIME_PACKAGE} 4 | 5 | .PHONY: publish clean 6 | 7 | clean: 8 | rm -f ${RUNTIME_PACKAGE} 9 | 10 | ${RUNTIME_PACKAGE}: 11 | ifndef LUMO_BIN_PATH 12 | $(error LUMO_BIN_PATH is undefined) 13 | endif 14 | zip -j ${RUNTIME_PACKAGE} bootstrap ${LUMO_BIN_PATH} runtime.cljs 15 | 16 | publish: ${RUNTIME_PACKAGE} 17 | aws lambda publish-layer-version \ 18 | --layer-name lumo-runtime \ 19 | --zip-file fileb://${RUNTIME_PACKAGE} 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Custom runtime for running ClojureScript on AWS Lambda 2 | 3 | ### What is it? 4 | 5 | This custom runtime makes it easy to run ClojureScript on AWS Lambda, without having to compile to Javascript. 6 | 7 | It uses [Lumo](https://github.com/anmonteiro/lumo) to execute ClojureScript and supports both ClojureScript (jars) and NodeJS dependencies (`node_modules`). 8 | 9 | Also featured in a [`:clojureD`](https://clojured.de/) lightening talk: https://www.youtube.com/watch?v=fVtawjGbvOQ&feature=youtu.be 10 | 11 | ### Purpose 12 | 13 | The runtime was initially created to lower the barrier for executing ClojureScript in AWS Lambda: you can write code directly in the AWS Lambda console and execute it by pressing a button. See this short video tutorial: https://vimeo.com/391237884 14 | 15 | However, as it supports third party dependencies, both from NodeJS and ClojureScript, it also serves as an easy way of getting meaningful ClojureScript applications running in AWS Lambda. 16 | 17 | ### How do I get started? 18 | 19 | The [`minimal`](examples/minimal) example illustrates how to get going in the simplest way. You can either check out the code in this repo, or directly type the example into the editor of the AWS Lambda Console. 20 | 21 | Eventually you'll want to create more elaborate programs. Checkout the [README in the `examples` folder](examples) for an overview. 22 | 23 | ### Layer ARN 24 | 25 | The newest version of the runtime is deployed in the `eu-west-1` region, and has the following ARN: 26 | 27 | ``` 28 | arn:aws:lambda:eu-west-1:313836948343:layer:lumo-runtime:21 29 | ``` 30 | 31 | See the [`minimal`](examples/minimal) example for info on how to use a custom runtime. 32 | 33 | ### Building the runtime 34 | 35 | You can build and deploy the runtime yourself. See [`BUILD.md`](BUILD.md) for instructions. 36 | 37 | ### Background and acknowledgements 38 | 39 | This repo contains an implementation of a [custom AWS Lambda runtime](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html) 40 | that enables executions of [ClojureScript](http://clojurescript.org) code in AWS Lambda without any pre-compilation. 41 | 42 | It relies on the awesome [Lumo](https://github.com/anmonteiro/lumo) project, and 43 | was inspired by [this episode of The Repl podcast](https://www.therepl.net/episodes/14/). 44 | 45 | It's based on the [Tutorial from Amazon](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-walkthrough.html) 46 | as well as the [Node 10.x/11.x implementations from LambCI](https://github.com/lambci/node-custom-lambda). 47 | 48 | With the help of [Andrea Richiardi](https://github.com/arichiardi), it was grown from a mere proof of concept into a usable project. 49 | -------------------------------------------------------------------------------- /bootstrap: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | function join { local IFS="$1"; shift; echo "$*"; } 5 | 6 | bin_dir=${BIN_DIR:-/opt} 7 | app_parts=(${_HANDLER//\// }) 8 | lib_dir=${LIB_DIR:-lib} 9 | libs=$(join : $(find "$bin_dir/$lib_dir" $lib_dir -name '*.jar' 2>/dev/null)) 10 | classpath=".:${bin_dir}:${libs:-.}" 11 | 12 | NODE_PATH=/opt/nodejs/node_modules:/opt/node_modules "$bin_dir/lumo" --classpath "$classpath" -e "(require (quote ${app_parts[0]}))" -m runtime 13 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | ## Example code 2 | 3 | This directory contains various examples of using the custom runtime in AWS: 4 | 5 | #### [minimal](minimal) 6 | 7 | A minimal example of executing ClojureScript on AWS Lambda with detailed usage instructions. 8 | 9 | #### [aws-sdk-example](aws-sdk-example) 10 | 11 | Demonstrates how to communicates with S3 from ClojureScript, using the AWS SDK. 12 | 13 | #### [http-example](http-example) 14 | 15 | Demonstrates how to create a separate layer for dependencies (in this case jars) 16 | 17 | #### [node-deps-example](node-deps-example) 18 | 19 | Demonstrates how to consuming NodeJS dependencies 20 | 21 | #### [lib-example](lib-example) 22 | 23 | Demonstrates using 3rd party dependencies. 24 | 25 | TODO: docs 26 | 27 | #### [serverless-example](serverless-example) 28 | 29 | The [Serverless framework](https://serverless.com) also supports custom runtimes. 30 | This example shows how to easily deploy a small webservice on AWS Lambda and API Gateway that uses ClojureScript. 31 | -------------------------------------------------------------------------------- /examples/aws-sdk-example/README.md: -------------------------------------------------------------------------------- 1 | # AWS SDK Example 2 | Interact with AWS Services (S3 in this case) using the AWS NodeJS SDK, ClojureScript and 3 | the Lumo-cljs runtime. 4 | 5 | ## Prerequisites 6 | - a deployed Lumo-cljs runtime (we'll use `arn:aws:lambda:eu-west-1:313836948343:layer:lumo-runtime:13`) 7 | - suitable credentials for AWS 8 | - the aws-cli version 1.16 or newer 9 | - NodeJS version 10 or newer and NPM 10 | 11 | ## Create execution role (TODO) 12 | - must be able to execute Lambdas 13 | - must have read-access to S3 14 | 15 | ## Create a Lambda 16 | ``` 17 | $ npm i # install aws sdk 18 | 19 | $ zip -r function.zip aws_sdk_example node_modules 20 | 21 | $ aws lambda create-function --function-name lumo-s3 --zip-file fileb://function.zip --handler aws-sdk-example.core/list-buckets --runtime provided --role arn:aws:iam::313836948343:role/lambda-role 22 | 23 | 24 | 25 | ``` 26 | 27 | ## Update the Lambda code (if needed) 28 | 29 | ``` 30 | $ aws lambda update-function-code --region eu-west-1 --function-name lumo-s3 --zip-file fileb://function.zip 31 | ``` 32 | 33 | ## Set the runtime for the Lambda 34 | 35 | ``` 36 | $ aws lambda update-function-configuration --function-name lumo-s3 --layers arn:aws:lambda:eu-west-1:313836948343:layer:lumo-runtime:13 --region eu-west-1 37 | ``` 38 | 39 | ## Invoke the Lambda 40 | 41 | ``` 42 | $ aws lambda invoke --function-name lumo-s3 --region eu-west-1 --payload '{}' response.txt 43 | 44 | $ cat response.txt 45 | ``` 46 | 47 | You should get back a json object with all (accesible) S3 buckets. -------------------------------------------------------------------------------- /examples/aws-sdk-example/aws_sdk_example/core.cljs: -------------------------------------------------------------------------------- 1 | (ns aws-sdk-example.core) 2 | 3 | ;; important to do this outside the handler, 4 | ;; else we'll probably get a time-out -> 5 | ;; https://stackoverflow.com/a/54823567/202538 6 | (def aws (js/require "aws-sdk")) 7 | 8 | (defn list-buckets [_] 9 | (js/Promise. (fn [resolve reject] 10 | (.listBuckets (aws.S3.) 11 | (fn [err data] 12 | (if err 13 | (reject err) 14 | (resolve data))))))) 15 | -------------------------------------------------------------------------------- /examples/aws-sdk-example/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aws-sdk-example", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "aws-sdk": { 8 | "version": "2.492.0", 9 | "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.492.0.tgz", 10 | "integrity": "sha512-9Mj4nNXoxUehLaeoGE53eLOdOUtkQyTmkAkofK+gbXxfB+V1cXYngdUJwTaRpKx4OJHy4ntKoP+JRvvScD6XeA==", 11 | "requires": { 12 | "buffer": "4.9.1", 13 | "events": "1.1.1", 14 | "ieee754": "1.1.8", 15 | "jmespath": "0.15.0", 16 | "querystring": "0.2.0", 17 | "sax": "1.2.1", 18 | "url": "0.10.3", 19 | "uuid": "3.3.2", 20 | "xml2js": "0.4.19" 21 | } 22 | }, 23 | "base64-js": { 24 | "version": "1.3.0", 25 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", 26 | "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" 27 | }, 28 | "buffer": { 29 | "version": "4.9.1", 30 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", 31 | "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", 32 | "requires": { 33 | "base64-js": "^1.0.2", 34 | "ieee754": "^1.1.4", 35 | "isarray": "^1.0.0" 36 | } 37 | }, 38 | "events": { 39 | "version": "1.1.1", 40 | "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", 41 | "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" 42 | }, 43 | "ieee754": { 44 | "version": "1.1.8", 45 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", 46 | "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=" 47 | }, 48 | "isarray": { 49 | "version": "1.0.0", 50 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 51 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 52 | }, 53 | "jmespath": { 54 | "version": "0.15.0", 55 | "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", 56 | "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" 57 | }, 58 | "punycode": { 59 | "version": "1.3.2", 60 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", 61 | "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" 62 | }, 63 | "querystring": { 64 | "version": "0.2.0", 65 | "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", 66 | "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" 67 | }, 68 | "sax": { 69 | "version": "1.2.1", 70 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", 71 | "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" 72 | }, 73 | "url": { 74 | "version": "0.10.3", 75 | "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", 76 | "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", 77 | "requires": { 78 | "punycode": "1.3.2", 79 | "querystring": "0.2.0" 80 | } 81 | }, 82 | "uuid": { 83 | "version": "3.3.2", 84 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", 85 | "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" 86 | }, 87 | "xml2js": { 88 | "version": "0.4.19", 89 | "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", 90 | "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", 91 | "requires": { 92 | "sax": ">=0.6.0", 93 | "xmlbuilder": "~9.0.1" 94 | } 95 | }, 96 | "xmlbuilder": { 97 | "version": "9.0.7", 98 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", 99 | "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /examples/aws-sdk-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aws-sdk-example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "aws-sdk": "^2.490.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/http-example/README.md: -------------------------------------------------------------------------------- 1 | ## HTTP Example 2 | 3 | ### What is it? 4 | 5 | This example illustrates how to compute dependencies for a project and create a separate layer with the required jar-files. 6 | 7 | It consists of a simple namespace that will query the Wikipedia API and do a simple calculation on the result. 8 | 9 | The example depends on the [httpurr](https://github.com/funcool/httpurr) client, which in turn has a dependency 10 | (a transitive dependency from the perspective of the example code) on [promesa](https://docs.aws.amazon.com/lambda/latest/dg/lambda-intro-execution-role.html). 11 | 12 | Since Lumo (our runtime) doesn't generate classpaths or download dependencies, 13 | we can instead calculate the dependencies via [tools.deps](https://clojure.org/guides/deps_and_cli), by specifying them in a 14 | `deps.edn` file and invoking the `clj` cli. 15 | 16 | ### Separate layer 17 | 18 | Since our dependencies might not update very often, we can add them to AWS Lambda as a separate layer. This way, we can iterate faster on the application code, by not having to re-upload the jar-files when the dependencies don't change. 19 | 20 | ### How to use: 21 | 22 | There are two scripts with the source code: 23 | - [make_deps_layer.sh](make_deps_layer.sh): calculates the dependencies and creates a lambda-layer with the jar-files 24 | - [make_lambda.sh](make_lambda.sh): creates a lambda function and associates it with the runtime and the dependency-layer 25 | 26 | To get going: 27 | 1. run `make_deps_layer.sh` and copy the resulting `LayerVersionArn` 28 | 2. run `make_lambda.sh` to create the lambda-function, specifying the runtime, the layer from the first step 29 | and a [lambda execution role](https://docs.aws.amazon.com/lambda/latest/dg/lambda-intro-execution-role.html): 30 | 31 | ``` 32 | $ RUNTIME=arn:... LAYER_VERSION_ARN=arn:aws:lambda:...:http-example-deps:1 ROLE=arn:aws:iam:... ./make_lambda.sh 33 | ``` 34 | 35 | 3. invoke the lambda with a query: 36 | 37 | ``` 38 | $ aws lambda invoke --function-name http-example --payload '{"query":"clojure"}' response.txt 39 | ``` 40 | 41 | The `response.txt` will contain the function output - in this case the average word-count of the searched articles: 42 | 43 | ``` 44 | $ cat response.txt 45 | 2929.6 46 | ``` 47 | -------------------------------------------------------------------------------- /examples/http-example/deps.edn: -------------------------------------------------------------------------------- 1 | {:deps {funcool/httpurr {:mvn/version "2.0.0"}}} -------------------------------------------------------------------------------- /examples/http-example/make_deps_layer.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ########################################################## 4 | # This script creates a lambda layer with dependencies # 5 | # from a deps.edn by invoking clj to compute a classpath # 6 | ########################################################## 7 | 8 | set -euo pipefail 9 | 10 | mkdir -p lib 11 | 12 | deps=$(clj -Spath) 13 | 14 | # extract important parts of classpath (ignore src and org/clojure/*) 15 | jars=$(echo ${deps//:/ } \ 16 | | tr " " "\n" \ 17 | | grep -v src \ 18 | | grep -v 'org\/clojure' \ 19 | | tr "\n" " ") 20 | 21 | cp $jars lib 22 | 23 | filename=$(mktemp -u).zip 24 | 25 | zip -r $filename lib 26 | 27 | aws lambda publish-layer-version \ 28 | --layer-name http-example-deps \ 29 | --zip-file fileb://$filename 30 | -------------------------------------------------------------------------------- /examples/http-example/make_lambda.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | if [ -z "$ROLE" ]; then echo "ROLE required"; exit 1; fi 6 | if [ -z "$RUNTIME" ]; then echo "RUNTIME required"; exit 1; fi 7 | if [ -z "$LAYER_VERSION_ARN" ]; then echo "LAYER_VERSION_ARN required"; exit 1; fi 8 | 9 | filename=$(mktemp -u).zip 10 | 11 | ( cd src && zip -r $filename http ) 12 | 13 | fname=http-example 14 | 15 | aws lambda delete-function --function-name $fname || true 16 | aws lambda create-function --function-name $fname \ 17 | --runtime provided \ 18 | --role $ROLE --handler http.core/handler \ 19 | --timeout 30 \ 20 | --zip-file fileb://$filename 21 | 22 | aws lambda update-function-configuration --function-name $fname --layers "$RUNTIME" "$LAYER_VERSION_ARN" 23 | 24 | -------------------------------------------------------------------------------- /examples/http-example/src/http/core.cljs: -------------------------------------------------------------------------------- 1 | (ns http.core 2 | (:require [httpurr.client.node :as http])) 3 | 4 | (defn process [{{search "search"} "query"}] 5 | (/ (->> search 6 | (map #(get % "wordcount")) 7 | (apply +)) 8 | (count search))) 9 | 10 | (defn handler [{{query "query"} :event}] 11 | (let [q (js/encodeURIComponent query)] 12 | (-> (http/send! {:method :get 13 | :url (str "https://en.wikipedia.org/w/api.php?action=query&list=search&srsearch=" q "&format=json")}) 14 | (.then (comp js->clj js/JSON.parse str :body)) 15 | (.then process)))) 16 | -------------------------------------------------------------------------------- /examples/lib-example/lib/promesa-1.9.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grav/aws-lumo-cljs-runtime/262000b219a2a93a32f5dd38acf86d9d2645340b/examples/lib-example/lib/promesa-1.9.0.jar -------------------------------------------------------------------------------- /examples/lib-example/lib_example/core.cljs: -------------------------------------------------------------------------------- 1 | (ns lib-example.core 2 | (:require [promesa.core :as p])) 3 | 4 | (defn handler [input] 5 | (p/resolved input)) 6 | -------------------------------------------------------------------------------- /examples/minimal/README.md: -------------------------------------------------------------------------------- 1 | ## Minimal example 2 | 3 | This is a minimal example for the AWS Lumo ClojureScript runtime. It just output its input arguments. 4 | 5 | To get started executing ClojureScript in AWS Lambda, you can either use the AWS Lambda web-based console, 6 | or use the `aws` command line tool. 7 | 8 | ### Using the AWS Lambda web-based console 9 | 10 | If you want to quickly see the runtime in action, you can do so with the editor of the AWS Lambda console. 11 | 12 | You can check out this video that shows you how: https://vimeo.com/391237884, or follow these steps: 13 | 14 | #### 1. Log in to AWS and go to the Lambda service 15 | 16 | Pick the Ireland (`eu-west-1`) region, since there's already a deployed runtime in that region. 17 | 18 | #### 2. Create a new function 19 | 20 | Pick "Author from scratch", choose a function name, and select "Provide your own bootstrap" under "Runtime". Leave the "Permissions" part to the default. 21 | 22 | #### 3. Add the AWS Lumo CLJS runtime 23 | 24 | Click "Layers" in the Designer, click "Add a layer", click "Provide a layer version ARN", and paste the ARN of the runtime that's already deployed: 25 | 26 | ``` 27 | arn:aws:lambda:eu-west-1:313836948343:layer:lumo-runtime:20 28 | ```` 29 | 30 | #### 4. Write some code 31 | 32 | Click the function name in the Designer, scroll down to the editor, and create a new folder: `my_package`, by right-clicking the root-folder and picking "New folder". 33 | 34 | Then right-click the `my_package` folder and create a new file, `my_ns.cljs`. 35 | 36 | Double-click this file and paste the contents of the [`my_ns.cljs`](my_ns.cljs) file into it. Notice that the editor syntax-highlights the code, which is a nice detail! 37 | 38 | Then change the handler to `my-package.my-ns/my-handler`. This string is what the runtime uses to look up the 39 | entrypoint, and it uses dashes instead of namespaces. 40 | 41 | Then click the "Save" button in the top of the console. 42 | 43 | #### 5. Create some test-data and run the function 44 | 45 | Just click the "Test" button in the top of the console. You'll be prompted to create a test-event. The default's fine, so just pick a name for the event, and hit "Create". 46 | 47 | You'll need to click "Test" again, and probably scroll to the top of the console, to see the Execution result.If you expand it, you should see both the function output, and below that the log output. 48 | 49 | That's it! 50 | 51 | ### Using the `aws` command line tool 52 | 53 | Make sure you have the aws cli installed (`aws`). You'll need a version that supports Lambda layers 54 | (1.16 and onwards should work). 55 | 56 | You can find an installation guide in the [AWS documentation](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html). 57 | 58 | #### 1. Create a zip-file with the source code 59 | 60 | Clone the repo and go to the `minimal` folder: 61 | 62 | ``` 63 | cd grav/aws-lumo-cljs-runtime/examples/minimal 64 | 65 | zip -r function.zip my_package 66 | ``` 67 | 68 | #### 2. Create the Lambda function on AWS 69 | 70 | The `--handler` parameter must correspond to the directory structure of the ClojureScript code that you provide, 71 | so for this example, it's `my-package.my-ns/my-handler`. 72 | 73 | For the `--role` parameter, you must supply a role that can execute lambdas. 74 | See https://docs.aws.amazon.com/lambda/latest/dg/runtimes-walkthrough.html#runtimes-walkthrough-prereqs 75 | 76 | Execute the following, substituting the `--role` part with a real ARN: 77 | 78 | ``` 79 | aws lambda create-function --function-name minimal1 \ 80 | --zip-file fileb://function.zip --handler my-package.my-ns/my-handler \ 81 | --runtime provided --role arn:aws:iam::xxx:role/lambda-role 82 | --region eu-west-1 83 | ``` 84 | 85 | Notice that we pick the `eu-west-1` region, since there's already a deployed runtime in this region. 86 | 87 | #### 3. Add the runtime layer to the function 88 | 89 | The runtime needs to be added to the function as a layer. This is done using the runtime's ARN. 90 | 91 | A runtime is already deployed in the `eu-west-1` region, so you can use its ARN for your function, 92 | since it's created in the same region: 93 | 94 | ``` 95 | aws lambda update-function-configuration --function-name minimal1 \ 96 | -- region eu-west-1 97 | --layers arn:aws:lambda:eu-west-1:313836948343:layer:lumo-runtime:20 98 | 99 | ``` 100 | 101 | #### 4. Invoke the lambda 102 | You can now test the lambda function using: 103 | 104 | ``` 105 | aws lambda invoke --function-name minimal1 --region eu-west-1 --payload '{"foo":42}' response.txt 106 | ``` 107 | 108 | You should receive something like this in `response.txt`: 109 | 110 | ``` 111 | { 112 | "hello": "Hello from my-handler!", 113 | "input": { 114 | "event": { 115 | "foo": 42 116 | }, 117 | "context": { 118 | "aws-request-id": "b64259ce-03e0-11e9-8db3-1bbff8d08d21", 119 | "lambda-runtime-invoked-function-arn": "arn:aws:lambda:eu-west-1:xxx:function:minimal1" 120 | } 121 | } 122 | } 123 | ``` 124 | 125 | That's it! 126 | -------------------------------------------------------------------------------- /examples/minimal/my_package/my_ns.cljs: -------------------------------------------------------------------------------- 1 | (ns my-package.my-ns) 2 | 3 | (defn my-handler [{:keys [_event _context] 4 | :as input}] 5 | (println "Hello from ClojureScript!") 6 | {:hello "Hello from my-handler!" 7 | :input input}) 8 | -------------------------------------------------------------------------------- /examples/node-deps-example/README.md: -------------------------------------------------------------------------------- 1 | ## NodeJS Layer example 2 | 3 | This example illustrates how to use NodeJS dependencies. 4 | 5 | The runtime will look for dependencies in any `node_modules` directory in the lambda-code. 6 | 7 | In this example, the `nodejs` directory contains a package.json file that both specifies 8 | an external dependency (is-odd) as well as a local dependency (meaning-of-life). 9 | 10 | ### Running the example in AWS 11 | 12 | - create the lambda by specifying the runtime and a [lambda execution role](https://docs.aws.amazon.com/lambda/latest/dg/lambda-intro-execution-role.html): 13 | ``` 14 | $ RUNTIME=arn:... ROLE=arn:... ./make_lambda.sh 15 | ``` 16 | 17 | - invoke the lambda: 18 | ``` 19 | $ aws lambda invoke --function-name node-deps-example --payload '' response.txt 20 | ``` 21 | 22 | The `response.txt` should contain some json. 23 | 24 | ### Creating a separate layer for the NodeJS dependencies 25 | 26 | Creating a separate layer for dependencies can speed up the development process, 27 | since you do not need to upload dependencies every time your application code changes. 28 | 29 | The runtime will look for a `node_modules` directory in additional layers, 30 | so you just need to create a layer with the `node_modules` 31 | folder and associate the lambda-function with this layer. 32 | 33 | See the [`http-example`](../http-example) for details on how to do this. 34 | 35 | -------------------------------------------------------------------------------- /examples/node-deps-example/make_lambda.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | if [ -z "$ROLE" ]; then echo "ROLE required"; exit 1; fi 6 | if [ -z "$RUNTIME" ]; then echo "RUNTIME required"; exit 1; fi 7 | 8 | filename=$(mktemp -u).zip 9 | 10 | zip -r $filename test_require 11 | ( cd nodejs && zip -r $filename node_modules ) 12 | 13 | fname=node-deps-example 14 | 15 | aws lambda delete-function --function-name $fname || true 16 | aws lambda create-function --function-name $fname \ 17 | --runtime provided \ 18 | --role $ROLE --handler test-require.core/handler \ 19 | --timeout 30 \ 20 | --zip-file fileb://$filename 21 | 22 | aws lambda update-function-configuration --function-name $fname --layers "$RUNTIME" 23 | 24 | -------------------------------------------------------------------------------- /examples/node-deps-example/meaning/index.js: -------------------------------------------------------------------------------- 1 | exports.tell_me = function(){ return 42 } -------------------------------------------------------------------------------- /examples/node-deps-example/meaning/package.json: -------------------------------------------------------------------------------- 1 | {"name": "meaning-of-life", 2 | "version": "42.0.0"} -------------------------------------------------------------------------------- /examples/node-deps-example/nodejs/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "some-location", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "is-number": { 8 | "version": "6.0.0", 9 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-6.0.0.tgz", 10 | "integrity": "sha512-Wu1VHeILBK8KAWJUAiSZQX94GmOE45Rg6/538fKwiloUu21KncEkYGPqob2oSZ5mUT73vLGrHQjKw3KMPwfDzg==" 11 | }, 12 | "is-odd": { 13 | "version": "3.0.1", 14 | "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-3.0.1.tgz", 15 | "integrity": "sha512-CQpnWPrDwmP1+SMHXZhtLtJv90yiyVfluGsX5iNCVkrhQtU3TQHsUWPG9wkdk9Lgd5yNpAg9jQEo90CBaXgWMA==", 16 | "requires": { 17 | "is-number": "^6.0.0" 18 | } 19 | }, 20 | "meaning-of-life": { 21 | "version": "file:../meaning" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/node-deps-example/nodejs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "some-location", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "dependencies": { 7 | "is-odd": "^3.0.1", 8 | "meaning-of-life": "file:../meaning" 9 | }, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "author": "Mikkel Gravgaard (https://github.com/grav)", 14 | "license": "ISC" 15 | } 16 | -------------------------------------------------------------------------------- /examples/node-deps-example/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "requires": true, 3 | "lockfileVersion": 1, 4 | "dependencies": { 5 | "meaning-of-life": { 6 | "version": "file:meaning" 7 | }, 8 | "some-location": { 9 | "version": "file:some-location", 10 | "requires": { 11 | "meaning-of-life": "file:meaning" 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/node-deps-example/response.txt: -------------------------------------------------------------------------------- 1 | {"meaning-of-life-is-odd?":false} -------------------------------------------------------------------------------- /examples/node-deps-example/run_local.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | if [ -z "$LUMO_BIN_PATH" ]; then echo "LUMO_BIN_PATH required"; exit 1; fi 5 | 6 | echo "Installing node dependencies" 7 | ( cd nodejs && npm install ) 8 | 9 | NODE_PATH=./nodejs/node_modules "$LUMO_BIN_PATH" -m runtime-local 10 | -------------------------------------------------------------------------------- /examples/node-deps-example/runtime_local.cljs: -------------------------------------------------------------------------------- 1 | (ns runtime-local 2 | (:require [test-require.core :refer [meaning-of-life is-odd?]] 3 | http)) 4 | 5 | (defn -main 6 | [& _args] 7 | (assert (false? (is-odd? (meaning-of-life)))) 8 | (println "ok!")) 9 | -------------------------------------------------------------------------------- /examples/node-deps-example/test_require/core.cljs: -------------------------------------------------------------------------------- 1 | (ns test-require.core) 2 | 3 | (def fs (js/require "fs")) 4 | 5 | (def is-odd? (js/require "is-odd")) 6 | 7 | (defn meaning-of-life [] 8 | (.tell_me (js/require "meaning-of-life"))) 9 | 10 | (defn handler [_] 11 | {:meaning-of-life-is-odd? (-> (meaning-of-life) is-odd?)}) 12 | 13 | -------------------------------------------------------------------------------- /examples/serverless-example/README.md: -------------------------------------------------------------------------------- 1 | # Serverless example 2 | 3 | The [Serverless Framework](http://serverless.com) supports provided runtimes for AWS Lambda. 4 | 5 | This example shows how to easily deploy a small ClojureScript webservice with the [Lumo custom runtime](http://github.com/grav/aws-lumo-cljs-runtime). 6 | 7 | ## Usage 8 | 9 | Make sure you have AWS credentials adequate for deploying with Serverless (a root user will certainly do!), and simply issue the following: 10 | 11 | ```bash 12 | $ npm install serverless # install the framework as a node module 13 | $ npx serverless deploy # package and deploy to AWS 14 | ``` 15 | 16 | Serverless should do its magic and create a Lambda function and an API Gateway, and return an overview of the GET endpoint: 17 | 18 | ``` 19 | ... 20 | endpoints: 21 | GET - https://abc123.execute-api.eu-west-1.amazonaws.com/dev/hello 22 | functions: 23 | hello: runtime-provided-test-dev-hello 24 | ... 25 | ``` 26 | 27 | Now, try doing a cURL against the url. You should get some EDN back: 28 | ``` 29 | $ curl https://abc123.execute-api.eu-west-1.amazonaws.com/dev/hello 30 | 31 | {:hello "Hello from my-handler!", :input {:event {"resource" "/hello", "body" nil, ... 32 | ``` 33 | 34 | That's pretty much it! -------------------------------------------------------------------------------- /examples/serverless-example/my_package/my_ns.cljs: -------------------------------------------------------------------------------- 1 | (ns my-package.my-ns) 2 | 3 | (defn my-handler [input] 4 | (-> {:body (pr-str {:hello "Hello from my-handler!" 5 | :input input}) 6 | :statusCode 200 7 | :headers {"Content-Type" "application/edn"}} 8 | clj->js)) 9 | 10 | -------------------------------------------------------------------------------- /examples/serverless-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serverless-runtime-provided", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Mikkel Gravgaard (https://github.com/grav)", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "serverless": "^1.67.3" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/serverless-example/serverless.yml: -------------------------------------------------------------------------------- 1 | service: runtime-provided-test 2 | 3 | provider: 4 | name: aws 5 | region: eu-west-1 # the runtime is in this region, so we need to be as well 6 | runtime: provided 7 | logs: 8 | restApi: true # we'd like api gateway logging in CloudWatch 9 | 10 | functions: 11 | hello: 12 | handler: my-package.my-ns/my-handler # this is the entrypoint in the source 13 | layers: 14 | - arn:aws:lambda:eu-west-1:313836948343:layer:lumo-runtime:17 # public lumo-cljs runtime layer 15 | events: 16 | - http: GET hello # create an API Gateway resource 17 | 18 | package: 19 | exclude: 20 | - ./** # start by excluding everything 21 | include: 22 | - my_package/** # ... and just include cljs source 23 | 24 | -------------------------------------------------------------------------------- /response.txt: -------------------------------------------------------------------------------- 1 | {"meaning-of-life-is-odd?":false} -------------------------------------------------------------------------------- /runtime.cljs: -------------------------------------------------------------------------------- 1 | (ns runtime 2 | (:require clojure.string 3 | http)) 4 | 5 | (def runtime-path (str "http://" (.-AWS_LAMBDA_RUNTIME_API js/process.env) "/2018-06-01/runtime")) 6 | 7 | (defn successful? 8 | [status] 9 | (<= 200 status 299)) 10 | 11 | (defn request [{:keys [url method headers body] 12 | :or {method :get}}] 13 | (js/Promise. 14 | (fn [resolve reject] 15 | (let [headers (merge headers 16 | (when body 17 | {"Content-Length" (js/Buffer.byteLength body)})) 18 | request (http/request 19 | url 20 | (clj->js {:method (clojure.string/upper-case (name method)) 21 | :headers headers}) 22 | (fn [response] 23 | (let [s (atom nil)] 24 | (.on response "data" (fn [chunk] 25 | (swap! s conj (.toString chunk "utf8")))) 26 | (.on response "end" (fn [] 27 | (resolve {:body (apply str @s) 28 | :status (.-statusCode response) 29 | :headers (js->clj (.-headers response))}))) 30 | (.on response "error" reject))))] 31 | (.on request "error" reject) 32 | (when body 33 | (.write request body)) 34 | (.end request))))) 35 | 36 | (def handle 37 | (do (assert (.-_HANDLER js/process.env) "The _HANDLER env vars must contain the handler location.\n\nSee https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html\n") 38 | (eval (symbol (.-_HANDLER js/process.env))))) 39 | 40 | (defn post-error [{error :error 41 | {aws-request-id :aws-request-id} :context}] 42 | (let [url (str runtime-path "/invocation/" aws-request-id "/error") 43 | runtime-error #js {:errorType (.-name error) 44 | :errorMessage (.-message error) 45 | :stackTrace (-> (or (.-stack error) "") 46 | (.split "\n") 47 | (.slice 1))}] 48 | (-> (request {:url url 49 | :method :post 50 | :headers {"Content-Type" "application/json" 51 | "Lambda-Runtime-Function-Error-Type" (.-name error)} 52 | :body (js/JSON.stringify runtime-error)}) 53 | (.then (fn [{:keys [status] 54 | :as res}] 55 | ;; We treat all errors from the API as an unrecoverable error. This is 56 | ;; because the API returns 4xx errors for responses that are too long. In 57 | ;; that case, we simply log the output and fail. 58 | (assert (successful? status) (str "Error from Runtime API\n" 59 | (-> {:url url 60 | :response res 61 | :error runtime-error} 62 | (clj->js) 63 | (js/JSON.stringify nil 2)))) 64 | res))))) 65 | 66 | (defonce state (atom nil)) 67 | 68 | (defn -main 69 | [& args] 70 | (let [url (str runtime-path "/invocation/next")] 71 | (-> (request {:url url}) 72 | (.then (fn [{:keys [status body] 73 | {aws-request-id "lambda-runtime-aws-request-id" 74 | lambda-runtime-invoked-function-arn "lambda-runtime-invoked-function-arn"} :headers 75 | :as response}] 76 | 77 | ;; We treat all errors from the API as an unrecoverable error. This is 78 | ;; because the API returns 4xx errors for responses that are too long. In 79 | ;; that case, we simply log the output and fail. 80 | (assert (successful? status) (str "Error from Runtime API\n" 81 | (-> {:url url 82 | :response response 83 | :context @state} 84 | (clj->js) 85 | (js/JSON.stringify nil 2)))) 86 | 87 | (let [context {:aws-request-id aws-request-id 88 | :lambda-runtime-invoked-function-arn lambda-runtime-invoked-function-arn}] 89 | (swap! state assoc :context context) 90 | 91 | {:event (-> (js/JSON.parse body) 92 | js->clj) 93 | :context context}))) 94 | 95 | (.then handle) 96 | 97 | (.then (fn [response] 98 | (let [{:keys [aws-request-id]} (:context @state)] 99 | (request {:url (str runtime-path "/invocation/" aws-request-id "/response") 100 | :method :post 101 | :headers {"Content-Type" "application/json"} 102 | :body (-> (clj->js response) 103 | js/JSON.stringify)})))) 104 | (.then (fn [{:keys [status] 105 | :as response}] 106 | (assert (successful? status) (str "Error from Runtime API\n" 107 | (-> {:context @state 108 | :response response} 109 | (clj->js) 110 | (js/JSON.stringify nil 2)))))) 111 | (.catch (fn [err] 112 | (post-error {:error err 113 | :context (:context @state)}))) 114 | (.then -main)))) 115 | -------------------------------------------------------------------------------- /test/create_runtime.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euxo pipefail 4 | 5 | runtime_name=${RUNTIME_NAME:-lumo-runtime-test} 6 | zipfile=$(mktemp -u).zip 7 | 8 | zip -j $zipfile bootstrap ${LUMO_BIN_PATH} runtime.cljs 9 | aws lambda publish-layer-version \ 10 | --layer-name $runtime_name \ 11 | --zip-file fileb://$zipfile | jq -r '.LayerVersionArn' 12 | -------------------------------------------------------------------------------- /test/run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euxo pipefail 4 | 5 | runtime=$RUNTIME 6 | role=$ROLE 7 | 8 | # aws sdk 9 | fname=lumo-s3 10 | zipfile=$(mktemp -u).zip 11 | response_file=$(mktemp) 12 | 13 | ( cd examples/aws-sdk-example && 14 | rm -rf node_modules && npm install && 15 | zip -qr $zipfile aws_sdk_example node_modules 16 | ) 17 | 18 | aws lambda delete-function --function-name $fname 2> /dev/null || true 19 | aws lambda create-function --function-name $fname --zip-file fileb://$zipfile \ 20 | --runtime provided --role $role --handler aws-sdk-example.core/list-buckets 21 | aws lambda update-function-configuration --function-name $fname --layers "$runtime" 22 | aws lambda invoke --function-name $fname --payload '{}' $response_file | jq -e '.FunctionError | not' || ( cat $response_file && exit 1 ) 23 | 24 | # jar-dependency in code 25 | fname=lumo-dep-code 26 | zipfile=$(mktemp -u).zip 27 | response_file=$(mktemp) 28 | 29 | ( cd examples/lib-example && 30 | zip -qr $zipfile lib_example lib 31 | ) 32 | 33 | aws lambda delete-function --function-name $fname 2> /dev/null || true 34 | aws lambda create-function --function-name $fname --zip-file fileb://$zipfile \ 35 | --runtime provided --role $role --handler lib-example.core/handler 36 | aws lambda update-function-configuration --function-name $fname --layers "$runtime" 37 | aws lambda invoke --function-name $fname --payload '{}' $response_file | jq -e '.FunctionError | not' || ( cat $response_file && exit 1 ) 38 | 39 | 40 | # jar-dependency in separate layer 41 | 42 | fname=lumo-dep-layer 43 | fn_zipfile=$(mktemp -u).zip 44 | layer_zipfile=$(mktemp -u).zip 45 | response_file=$(mktemp) 46 | 47 | ( cd examples/lib-example && 48 | zip -qr $fn_zipfile lib_example 49 | zip -qr $layer_zipfile lib 50 | ) 51 | 52 | aws lambda delete-function --function-name $fname 2> /dev/null || true 53 | aws lambda create-function --function-name $fname --zip-file fileb://$fn_zipfile \ 54 | --runtime provided --role $role --handler lib-example.core/handler 55 | 56 | liblayer=`aws lambda publish-layer-version \ 57 | --layer-name lumo-dep-lib-layer \ 58 | --zip-file fileb://$layer_zipfile | jq -r '.LayerVersionArn'` 59 | 60 | aws lambda update-function-configuration --function-name $fname --layers "$runtime" "$liblayer" 61 | aws lambda invoke --function-name $fname --payload '{}' $response_file | jq -e '.FunctionError | not' || ( cat $response_file && exit 1 ) 62 | 63 | # node-dependency in code 64 | 65 | fname=lumo-node-example 66 | fn_zipfile=$(mktemp -u).zip 67 | response_file=$(mktemp) 68 | 69 | ( cd examples/node-deps-example && 70 | rm -rf nodejs/node_modules && 71 | npm install --prefix nodejs && 72 | zip -qr "$fn_zipfile" test_require && 73 | cd nodejs && zip -qr "$fn_zipfile" node_modules 74 | ) 75 | 76 | aws lambda delete-function --function-name $fname 2> /dev/null || true 77 | aws lambda create-function --function-name $fname --zip-file fileb://$fn_zipfile \ 78 | --runtime provided --role $role --handler test-require.core/handler 79 | 80 | aws lambda update-function-configuration --function-name $fname --layers "$runtime" 81 | aws lambda invoke --function-name $fname --payload '{}' $response_file | jq -e '.FunctionError | not' || ( cat $response_file && exit 1 ) 82 | 83 | # node-dependency in separate layer 84 | 85 | fname=lumo-node-layer 86 | fn_zipfile=$(mktemp -u).zip 87 | layer_zipfile=$(mktemp -u).zip 88 | response_file=$(mktemp) 89 | 90 | ( cd examples/node-deps-example && 91 | zip -qr $fn_zipfile test_require && 92 | rm -rf nodejs/node_modules && 93 | npm install --prefix nodejs && 94 | cd nodejs && 95 | zip -qr "$layer_zipfile" node_modules 96 | ) 97 | 98 | aws lambda delete-function --function-name $fname 2> /dev/null || true 99 | aws lambda create-function --function-name $fname --zip-file fileb://$fn_zipfile \ 100 | --runtime provided --role $role --handler test-require.core/handler 101 | 102 | liblayer=`aws lambda publish-layer-version \ 103 | --layer-name lumo-dep-lib-layer \ 104 | --zip-file fileb://$layer_zipfile | jq -r '.LayerVersionArn'` 105 | 106 | aws lambda update-function-configuration --function-name $fname --layers "$runtime" "$liblayer" 107 | aws lambda invoke --function-name $fname --payload '{}' $response_file | jq -e '.FunctionError | not' || ( cat $response_file && exit 1 ) 108 | --------------------------------------------------------------------------------