Skip to content

Commit

Permalink
Handler to take clj payload w/ optional context arg w/ headers & env
Browse files Browse the repository at this point in the history
  • Loading branch information
ccfontes authored Aug 20, 2023
1 parent 7ca1505 commit fd06989
Show file tree
Hide file tree
Showing 18 changed files with 470 additions and 42 deletions.
6 changes: 5 additions & 1 deletion .clj-kondo/config.edn
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
{:linters
{:redefined-var {:level :off}
:duplicate-require {:level :off}
:namespace-name-mismatch {:level :off}}}
:namespace-name-mismatch {:level :off}}
:config-in-ns {tests {:linters {:unresolved-symbol {:exclude [(eg/eg)
(eg/ex)
(plumula.mimolette.alpha/defspec-test [spec-check-index])]}}}
index {:linters {:invalid-arity {:level :off}}}}}
2 changes: 1 addition & 1 deletion .github/workflows/broken-link-checker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
with:
node-version: 18
- name: Asciidoc-link-checker
run: npm install -g asciidoc-link-check
run: npm install -g asciidoc-link-check@1.0.15
- name: Run asciidoc-link-check
run: |
git clone https://github.com/${{ github.repository_owner }}/faas-bb.git
Expand Down
36 changes: 25 additions & 11 deletions .github/workflows/faas_fn_build_invoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,19 @@ jobs:
mkdir test
cd test
faas template pull https://github.com/${{ github.repository_owner }}/faas-bb#${{ github.head_ref || github.ref_name }}
faas new --lang bb-streaming my-bb-streaming-function --prefix ghcr.io/${{ github.repository_owner }}
faas build -f my-bb-streaming-function.yml
if [ "$(echo "Hello world" | docker run -i ghcr.io/${{ github.repository_owner }}/my-bb-streaming-function:latest ./index.clj)" != "Hello world" ]; then
exit 1
fi
faas new --lang bb my-bb-http-function --prefix ghcr.io/${{ github.repository_owner }}
faas build -f my-bb-http-function.yml
(docker stop my-bb-http-function || exit 0)
(docker rm my-bb-http-function || exit 0)
faas build -f my-bb-http-function.yml
docker run -d --name my-bb-http-function ghcr.io/${{ github.repository_owner }}/my-bb-http-function:latest ./index.clj
if [ "$(docker exec my-bb-http-function curl -X POST --data-raw "Hello world" --retry 3 --retry-delay 2 --retry-connrefused http://127.0.0.1:8082)" != "Hello world" ]; then
docker run -d --name my-bb-http-function ghcr.io/${{ github.repository_owner }}/my-bb-http-function:latest bb --main index
if [ "$(docker exec my-bb-http-function curl -X POST --data-raw "Hello world" --retry 6 --retry-delay 2 --retry-connrefused http://127.0.0.1:8082)" != "Hello world" ]; then
exit 2
fi
- name: Build provided Function examples and invoke them
Expand All @@ -42,19 +44,31 @@ jobs:
cd examples
faas template pull https://github.com/${{ github.repository_owner }}/faas-bb#${{ github.head_ref || github.ref_name }}
faas build
if [ "$(echo world | docker run -i ghcr.io/${{ github.repository_owner }}/bb-streaming-hello:latest ./index.clj)" != "Hello, world" ]; then
exit 1
exit 3
fi
if [ "$(echo '{"a" {"b" 10}}' | docker run -i ghcr.io/${{ github.repository_owner }}/bb-streaming-lib:latest ./index.clj)" != "[10]" ]; then
exit 2
exit 4
fi
if [ -n "$(echo '{"a" 10}' | docker run -i ghcr.io/${{ github.repository_owner }}/bb-streaming-lib:latest ./index.clj)" ]; then
exit 3
exit 5
fi
docker run -i ghcr.io/${{ github.repository_owner }}/bb-streaming-lib:latest function/handler-test.clj
(docker stop bb-http-hello || exit 0)
(docker rm bb-http-hello || exit 0)
docker run -d --name bb-http-hello ghcr.io/${{ github.repository_owner }}/bb-http-hello:latest ./index.clj
if [ "$(docker exec bb-http-hello curl -X POST --data-raw "world" --retry 3 --retry-delay 2 --retry-connrefused http://127.0.0.1:8082)" != "Hello, world" ]; then
exit 5
(docker stop bb-http-map || exit 0)
(docker rm bb-http-map || exit 0)
docker run -d --name bb-http-map ghcr.io/${{ github.repository_owner }}/bb-http-map:latest bb --main index
if [ "$(docker exec bb-http-map curl -X POST -d '{"foo": "bar", "spam": "eggs"}' -H 'Content-Type: application/json' --retry 3 --retry-delay 2 --retry-connrefused http://127.0.0.1:8082)" != '[["foo","spam"],["bar","eggs"]]' ]; then
exit 6
fi
(docker stop bb-http-map-context || exit 0)
(docker rm bb-http-map-context || exit 0)
docker run -d --name bb-http-map-context ghcr.io/${{ github.repository_owner }}/bb-http-map-context:latest bb --main index
if [ "$(docker exec bb-http-map-context curl -X POST -d '{"foo": "bar", "spam": "eggs"}' -H 'Content-Type: application/json' --retry 3 --retry-delay 2 --retry-connrefused http://127.0.0.1:8082)" != '[["foo","spam"],["bar","eggs"],"application/json","http://127.0.0.1:8082"]' ]; then
exit 7
fi
25 changes: 25 additions & 0 deletions .github/workflows/unit_tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Unit tests

on:
push:
branches:
- "*"
pull_request:

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout this repo in current branch
uses: actions/checkout@v3
with:
ref: ${{ github.head_ref || github.ref_name }}
- name: Set up JDK 8
uses: actions/setup-java@v1
with:
java-version: 8
- name: Install Babashka
run: bash < <(curl -s https://raw.githubusercontent.com/babashka/babashka/master/install)
- name: Run unit tests
run: bb tests.clj
working-directory: ${{ github.workspace }}/template/bb
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
examples/build
examples/template
.clj-kondo/.cache
112 changes: 93 additions & 19 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,17 @@
:url-ci-status-lychee-link: "{url-proj}/actions/workflows/broken-link-checker.yml"
:img-license: https://img.shields.io/badge/license-MIT-black.svg

= OpenFaaS Babashka template =

image:{img-ci-tests-status}[link={url-ci-status-tests}]
image:{img-ci-hadolint-status}[link={url-ci-status-hadolint}]
image:{img-ci-clj-kondo-status}[link={url-ci-status-clj-kondo}]
image:{img-ci-lychee-link-check-status}[link={url-ci-status-lychee-link}]
image:{img-license}[link=LICENSE]
= faas-bb image:{img-ci-tests-status}[link={url-ci-status-tests}] image:{img-ci-hadolint-status}[link={url-ci-status-hadolint}] image:{img-ci-clj-kondo-status}[link={url-ci-status-clj-kondo}] image:{img-ci-lychee-link-check-status}[link={url-ci-status-lychee-link}] image:{img-license}[link=LICENSE]

An https://github.com/openfaas[OpenFaaS] template for writing Functions in https://github.com/babashka/babashka[Babashka].

== Prerequisites ==
== Prerequisites

* https://docs.openfaas.com/cli/install/[OpenFaaS CLI]: makes the `faas` command available.

== Usage ==
== Usage

=== Pull OpenFaaS template ===
=== Pull OpenFaaS template

To create Babashka Functions with this template, use the following command *once*:
[source, bash]
Expand All @@ -34,7 +28,7 @@ faas template pull https://github.com/ccfontes/faas-bb
----
If you ever need to update the template, simply run the command above with the `--overwrite` flag.

=== Create a Babashka Function ===
=== Create a Babashka Function

Two languages are supported: `bb` (HTTP mode) and `bb-streaming`.

Expand All @@ -43,25 +37,105 @@ Create Babashka Functions as with the following command example:
----
faas new --lang bb my-bb-function
----
`of-watchdog` mode is HTTP. A new project is created for a function defined as `my-bb-function`. It will contain:
`of-watchdog` mode here is HTTP. A new project is created for a function defined as `my-bb-function`. It will contain:

* a `function.handler` namespace that is required for the template to work properly. The requirement for this namespace is to have a top-level function defined as `handler`.
* a `bb.edn` file specifying classpath to find `function.handler`.

== link:examples[Function examples] ==
Although the Function namespace is called `function.handler`, the files themselves can be named anything you want. Example:
[source, yml]
----
my-function:
handler: ./anything/my-function
...
----

The `handler` function can be defined with one or two arguments.

Defining `handler` with one argument containing the request payload:
[source, clojure]
----
This works for both `bb` and `bb-streaming` languages.
(defn handler [payload] ...)
----
Defining `handler` with two arguments containing the request payload and context:
[source, clojure]
----
(defn handler [payload context] ...)
----
This works for `bb` language only.

The `context` will contain a map of `:headers` and `:env` keys.

The `:headers` key will contain a map of the request headers, as such:
[source, clojure]
----
{:content-type "application/json"}
----

The `:env` map contains a map with the environment variables. Additional environment variables can be defined in the `stack.yml` file, as such:
[source, yml]
----
my-function:
lang: bb
handler: ./anything/my-function
image: ${DOCKER_REGISTRY_IMG_ORG_PATH}/my-function
environment:
MY_ENV1: foo
MY_ENV2: 2
----
The `:env` key will contain:
[source, clojure]
----
{:my-env1 "foo"
:my-env2 2}
----

When passing a JSON payload and using `bb` language, the payload will be automatically parsed as a Clojure map with keyword keys. There are cases where string keys are preferable, and it's possible to support them by setting `keywords: false` in the Function in `stack.yml`:
[source, yml]
----
my-function:
lang: bb
handler: ./anything/my-function
image: ${DOCKER_REGISTRY_IMG_ORG_PATH}/my-function
environment:
keywords: false
----


== link:examples[Function examples]

See the link:examples[examples] directory to find a fully working set of OpenFaaS Functions written in Babashka.

== Tests ==
== Tests

=== CI tests

Tests run in CI with Github Actions. Some commands link:.github/workflows/faas_fn_build_invoke.yml[can be found in a Github Actions workflow] to help you with testing your changes before pushing them to a topic branch.
All tests run in CI with Github Actions. Some commands link:.github/workflows/faas_fn_build_invoke.yml[can be found in a Github Actions workflow] to help you with testing your changes before pushing them to a topic branch.

== Contributing ==
=== Unit tests

Run locally the unit tests for `bb` language:
[source, bash]
----
cd template/bb
bb tests.clj
----
`tests.clj` is included with the template so you can test any changes you make to the template before using it.

== Contributing

Contributions are welcome! If you find a bug or have an idea for a new feature, please open an issue or submit a pull request.

== link:LICENSE[License] ==
The template may benefit from some common middleware functions, such as those offered in the https://github.com/ring-clojure/ring-defaults/blob/master/src/ring/middleware/defaults.clj[ring-defaults library]. Users are welcome to recommend integrating any middleware they think would be useful for handling common web application needs.

== Third party code

link:template/bb/ring/middleware/json.clj[ring.middleware.json] is derived from https://github.com/ring-clojure/ring-json/blob/master/src/ring/middleware/json.clj[ring-son] to work with Babashka, originally authored by James Reeves and used under the MIT license.

== link:LICENSE[License]

Copyright (c) 2023 Carlos da Cunha Fontes
Copyright (c) 2023 Carlos da Cunha Fontes.

The MIT License
This project is licensed under the MIT License. See link:LICENSE[LICENSE] for details.
1 change: 1 addition & 0 deletions examples/http/bb-map-context/bb.edn
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{:paths ["."]}
4 changes: 4 additions & 0 deletions examples/http/bb-map-context/handler.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
(ns function.handler)

(defn handler [content {:keys [headers env]}]
[(keys content) (vals content) (:content-type headers) (:upstream-url env)])
1 change: 1 addition & 0 deletions examples/http/bb-map/bb.edn
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{:paths ["."]}
4 changes: 4 additions & 0 deletions examples/http/bb-map/handler.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
(ns function.handler)

(defn handler [content]
[(keys content) (vals content)])
12 changes: 12 additions & 0 deletions examples/stack.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,15 @@ functions:
lang: bb
handler: ./http/bb-hello
image: ${DOCKER_REGISTRY_IMG_ORG_PATH}/bb-http-hello
bb-http-map:
lang: bb
handler: ./http/bb-map
image: ${DOCKER_REGISTRY_IMG_ORG_PATH}/bb-http-map
environment:
keywords: false
bb-http-map-context:
lang: bb
handler: ./http/bb-map-context
image: ${DOCKER_REGISTRY_IMG_ORG_PATH}/bb-http-map-context
environment:
keywords: true
1 change: 1 addition & 0 deletions template/bb/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ USER app
WORKDIR $HOME

COPY index.clj function/bb.edn ./
COPY ring ./ring
COPY function function

RUN bb prepare
Expand Down
2 changes: 2 additions & 0 deletions template/bb/bb.edn
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{:paths ["."]
:deps {eg/eg {:mvn/version "0.5.6-alpha"}}}
4 changes: 2 additions & 2 deletions template/bb/function/handler.clj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
(ns function.handler)

(defn handler [{:keys [body]}]
{:body body})
(defn handler [content]
content)
61 changes: 55 additions & 6 deletions template/bb/index.clj
Original file line number Diff line number Diff line change
@@ -1,9 +1,58 @@
#!/usr/bin/env bb
(ns index ^{:author "Carlos da Cunha Fontes"
:url "https://github.com/ccfontes/faas-bb"
:license {:name "Distributed under the MIT License"
:url "https://github.com/ccfontes/faas-bb/blob/main/LICENSE"}}
(:require
[clojure.walk :refer [keywordize-keys]]
[clojure.string :as str :refer [lower-case]]
[clojure.edn :as edn]
[org.httpkit.server :refer [run-server]]
[ring.middleware.json :as json-middleware]
[function.handler :as function]))

(require
'[function.handler :as function]
'[org.httpkit.server :refer [run-server]])
(defn read-string [s]
(try
(let [res (edn/read-string s)]
(if (symbol? res) (str res) res))
(catch Exception _
s)))

(run-server function/handler {:port 8082})
(defn keywords? [env-val]
(if-some [keywords (edn/read-string env-val)]
keywords
true))

@(promise)
(defn ->kebab-case [s]
(lower-case (str/replace s #"_" "-")))

(def fn-arg-cnt (comp count first :arglists meta))

(defn format-context [m]
(->> m
(map (fn [[k v]] [(->kebab-case k) (read-string v)]))
(into {})
(keywordize-keys)))

(defn ->context [headers env]
{:headers (format-context headers)
:env (format-context env)})

(def response {:status 200})

(defn ->handler [f-var env]
(let [f (var-get f-var)
faas-fn (case (fn-arg-cnt f-var)
1 (comp f :body)
2 #(f (:body %) (->context (:headers %) env)))]
(fn [request]
(merge {:body (faas-fn request)} response))))

(defn ->app [f-var env]
(-> (->handler f-var env)
(json-middleware/wrap-json-body {:keywords? (keywords? (get env "keywords"))})
(json-middleware/wrap-json-response)))

(defn -main []
(run-server (->app #'function/handler (System/getenv))
{:port 8082})
@(promise))
Loading

0 comments on commit fd06989

Please sign in to comment.