Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handler should take clj body w/ optional headers & env as context #23

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
cf8c68a
Handler should take clj body w/ optional headers & env as context
ccfontes Aug 17, 2023
6f2540c
🔧 chore(.clj-kondo/config.edn): update linter configuration for tests…
ccfontes Aug 19, 2023
d868917
🚀 feat(bb.edn): remove unnecessary tasks key from bb.edn file
ccfontes Aug 20, 2023
20aa51f
🐛 fix(workflows): fix docker run command in faas_fn_build_invoke.yml
ccfontes Aug 20, 2023
9226377
🔧 chore(broken-link-checker.yml): update asciidoc-link-check version …
ccfontes Aug 20, 2023
d7668cc
🐛 fix(broken-link-checker.yml): fix typo in asciidoc-link-check packa…
ccfontes Aug 20, 2023
0666ef4
🔧 chore(faas_fn_build_invoke.yml): remove redundant docker logs commands
ccfontes Aug 20, 2023
9367ec9
🐛 fix(workflows): fix curl command in faas_fn_build_invoke.yml
ccfontes Aug 20, 2023
4909549
🔧 chore(faas_fn_build_invoke.yml): remove unnecessary docker logs and…
ccfontes Aug 20, 2023
18b25f8
🔧 chore(faas_fn_build_invoke.yml): stop and remove containers before …
ccfontes Aug 20, 2023
dd60921
🐛 fix(faas_fn_build_invoke.yml): increase retry count from 3 to 6 for…
ccfontes Aug 20, 2023
9009a69
🐛 fix(faas_fn_build_invoke.yml): add sleep command before starting co…
ccfontes Aug 20, 2023
7b3ef2d
Handler should take clj body w/ optional headers & env as context
ccfontes Aug 20, 2023
0abd8df
🐛 fix(faas_fn_build_invoke.yml): increase retry count for curl comman…
ccfontes Aug 20, 2023
32c4c3e
🐛 fix(faas_fn_build_invoke.yml): add sleep command before starting th…
ccfontes Aug 20, 2023
07c23f8
🔧 chore(faas_fn_build_invoke.yml): remove unnecessary docker stop and…
ccfontes Aug 20, 2023
e1cd4a2
🔧 chore(handler.clj): refactor handler function to include context pa…
ccfontes Aug 20, 2023
9a44c67
🔀 refactor(handler.clj): change the name of the `:keywords` environme…
ccfontes Aug 20, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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