From 6806772ce79cf3cf74f41388746d8ac20cb8ee48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andy=20M=C3=A9ry?= Date: Tue, 5 Sep 2023 13:45:46 +0200 Subject: [PATCH 1/6] feat: add triggers getting started for all langages --- .../triggers-functions-offline-testing.yml | 96 +++ functions/triggers-getting-started/.gitignore | 13 + .../.terraform.lock.hcl | 44 ++ functions/triggers-getting-started/README.md | 3 + .../triggers-getting-started/go/cmd/main.go | 11 + functions/triggers-getting-started/go/go.mod | 7 + functions/triggers-getting-started/go/go.sum | 8 + .../triggers-getting-started/go/handler.go | 49 ++ functions/triggers-getting-started/main.tf | 85 +++ .../triggers-getting-started/messaging.tf | 17 + .../triggers-getting-started/node/.gitignore | 1 + .../triggers-getting-started/node/handler.js | 51 ++ .../node/package-lock.json | 693 ++++++++++++++++++ .../node/package.json | 15 + functions/triggers-getting-started/outputs.tf | 8 + .../triggers-getting-started/php/handler.php | 36 + .../triggers-getting-started/provider.tf | 13 + .../python/handler.py | 47 ++ .../python/requirements-dev.txt | 3 + .../triggers-getting-started/rust/.gitignore | 1 + .../triggers-getting-started/rust/Cargo.lock | 273 +++++++ .../triggers-getting-started/rust/Cargo.toml | 9 + .../triggers-getting-started/rust/src/lib.rs | 38 + .../tests/requirements.txt | 2 + .../tests/send_messages.py | 32 + .../tests/test_handler.py | 45 ++ 26 files changed, 1600 insertions(+) create mode 100644 .github/workflows/triggers-functions-offline-testing.yml create mode 100644 functions/triggers-getting-started/.gitignore create mode 100644 functions/triggers-getting-started/.terraform.lock.hcl create mode 100644 functions/triggers-getting-started/README.md create mode 100644 functions/triggers-getting-started/go/cmd/main.go create mode 100644 functions/triggers-getting-started/go/go.mod create mode 100644 functions/triggers-getting-started/go/go.sum create mode 100644 functions/triggers-getting-started/go/handler.go create mode 100644 functions/triggers-getting-started/main.tf create mode 100644 functions/triggers-getting-started/messaging.tf create mode 100644 functions/triggers-getting-started/node/.gitignore create mode 100644 functions/triggers-getting-started/node/handler.js create mode 100644 functions/triggers-getting-started/node/package-lock.json create mode 100644 functions/triggers-getting-started/node/package.json create mode 100644 functions/triggers-getting-started/outputs.tf create mode 100644 functions/triggers-getting-started/php/handler.php create mode 100644 functions/triggers-getting-started/provider.tf create mode 100644 functions/triggers-getting-started/python/handler.py create mode 100644 functions/triggers-getting-started/python/requirements-dev.txt create mode 100644 functions/triggers-getting-started/rust/.gitignore create mode 100644 functions/triggers-getting-started/rust/Cargo.lock create mode 100644 functions/triggers-getting-started/rust/Cargo.toml create mode 100644 functions/triggers-getting-started/rust/src/lib.rs create mode 100644 functions/triggers-getting-started/tests/requirements.txt create mode 100644 functions/triggers-getting-started/tests/send_messages.py create mode 100644 functions/triggers-getting-started/tests/test_handler.py diff --git a/.github/workflows/triggers-functions-offline-testing.yml b/.github/workflows/triggers-functions-offline-testing.yml new file mode 100644 index 0000000..7ea3471 --- /dev/null +++ b/.github/workflows/triggers-functions-offline-testing.yml @@ -0,0 +1,96 @@ +--- +name: triggers-offline-testing + +on: + push: + branches: [main] + pull_request: + branches: [main] + +env: + TRIGGERS_GETTING_STARTED_FOLDER: functions/triggers-getting-started + OFFLINE_SERVER_URL: http://localhost:8080 + +jobs: + trigger-node: + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install node + uses: actions/setup-node@v3 + with: + node-version: 20 + + - name: Install node dev requirements + run: | + cd ${{ env.TRIGGERS_GETTING_STARTED_FOLDER }}/node + npm install + + - name: Install python + uses: actions/setup-python@v4 + with: + python-version: "3.11" + + - name: Install testing requirements + run: pip install -r ${{ env.TRIGGERS_GETTING_STARTED_FOLDER }}/tests/requirements.txt + + - name: Run node offline testing + uses: BerniWittmann/background-server-action@v1 + with: + # The wait-on directive doesn't work with 405 on / with GETs + command: python ${{ env.TRIGGERS_GETTING_STARTED_FOLDER }}/tests/test_handler.py + start: npm start --prefix ${{ env.TRIGGERS_GETTING_STARTED_FOLDER }}/node + + trigger-go: + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install go + uses: actions/setup-go@v4 + with: + go-version: "1.20" + + - name: Install python + uses: actions/setup-python@v4 + with: + python-version: "3.11" + + - name: Install testing requirements + run: pip install -r ${{ env.TRIGGERS_GETTING_STARTED_FOLDER }}/tests/requirements.txt + + - name: Run go offline testing + uses: BerniWittmann/background-server-action@v1 + with: + # The wait-on directive doesn't work with 405 on / with GETs + command: python ${{ env.TRIGGERS_GETTING_STARTED_FOLDER }}/tests/test_handler.py + start: > + cd ${{ env.TRIGGERS_GETTING_STARTED_FOLDER }}/go && + go run ./cmd + + trigger-python: + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install python + uses: actions/setup-python@v4 + with: + python-version: "3.11" + + - name: Install python dev requirements + run: pip install -r ${{ env.TRIGGERS_GETTING_STARTED_FOLDER }}/python/requirements-dev.txt + + - name: Install testing requirements + run: pip install -r ${{ env.TRIGGERS_GETTING_STARTED_FOLDER }}/tests/requirements.txt + + - name: Run python offline testing + uses: BerniWittmann/background-server-action@v1 + with: + # The wait-on directive doesn't work with 405 on / with GETs + command: python ${{ env.TRIGGERS_GETTING_STARTED_FOLDER }}/tests/test_handler.py + start: python ${{ env.TRIGGERS_GETTING_STARTED_FOLDER }}/python/handler.py diff --git a/functions/triggers-getting-started/.gitignore b/functions/triggers-getting-started/.gitignore new file mode 100644 index 0000000..d760a24 --- /dev/null +++ b/functions/triggers-getting-started/.gitignore @@ -0,0 +1,13 @@ +# Local .terraform directories +**/.terraform/* + +# Generated files +*.zip + +# .tfstate files +*.tfstate +*.tfstate.* + +# Crash log files +crash.log +crash.*.log \ No newline at end of file diff --git a/functions/triggers-getting-started/.terraform.lock.hcl b/functions/triggers-getting-started/.terraform.lock.hcl new file mode 100644 index 0000000..04c2a1d --- /dev/null +++ b/functions/triggers-getting-started/.terraform.lock.hcl @@ -0,0 +1,44 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/archive" { + version = "2.4.0" + constraints = ">= 2.4.0" + hashes = [ + "h1:EtN1lnoHoov3rASpgGmh6zZ/W6aRCTgKC7iMwvFY1yc=", + "zh:18e408596dd53048f7fc8229098d0e3ad940b92036a24287eff63e2caec72594", + "zh:392d4216ecd1a1fd933d23f4486b642a8480f934c13e2cae3c13b6b6a7e34a7b", + "zh:655dd1fa5ca753a4ace21d0de3792d96fff429445717f2ce31c125d19c38f3ff", + "zh:70dae36c176aa2b258331ad366a471176417a94dd3b4985a911b8be9ff842b00", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:7d8c8e3925f1e21daf73f85983894fbe8868e326910e6df3720265bc657b9c9c", + "zh:a032ec0f0aee27a789726e348e8ad20778c3a1c9190ef25e7cff602c8d175f44", + "zh:b8e50de62ba185745b0fe9713755079ad0e9f7ac8638d204de6762cc36870410", + "zh:c8ad0c7697a3d444df21ff97f3473a8604c8639be64afe3f31b8ec7ad7571e18", + "zh:df736c5a2a7c3a82c5493665f659437a22f0baf8c2d157e45f4dd7ca40e739fc", + "zh:e8ffbf578a0977074f6d08aa8734e36c726e53dc79894cfc4f25fadc4f45f1df", + "zh:efea57ff23b141551f92b2699024d356c7ffd1a4ad62931da7ed7a386aef7f1f", + ] +} + +provider "registry.terraform.io/scaleway/scaleway" { + version = "2.27.0" + constraints = ">= 2.12.0" + hashes = [ + "h1:Og47Om5NlmVBxIowPf2eqTtnCB+eHmcdocIugrBxoAI=", + "zh:69703b786535ac43a47145e48875f7dd5da2de9b2868e35cb3053374f912cf5f", + "zh:75a62a02971545c216732b369d78cf277d4065e23d8ff453bd3ddc37ff87a2eb", + "zh:8bd985631980f60e96f197be254bee6ef7b431169025d615703d12eee74f8119", + "zh:910012fcded93f99768a5b66aba7bcd94a3fd94ffe95fdb2cc96b11b9cc3643c", + "zh:917b46a52647767afaa169abb6c2ddab4274b3fbcfa47ae66670335bd26c5a5c", + "zh:91a1ffc2c219a2db0226dc43a6279c85708d1d62ab4f2dbd4b59c6f43d7d38ab", + "zh:ab08103d5fff1817e295754ad8684883eb0652be89116a260f64db166a6b430b", + "zh:b972c3940f4fd0ae45b5ff6dee15fcd0f2065db3f51d8fe9b6b03825ff812ae3", + "zh:bac194f28d24a72499b2e92e3126475de1f6bd57709f972b5f2120e6bfc7ef3b", + "zh:bea0c0a52d2e6fdd2bb259c5bbfedd481a3117cbc480dcf832abb8dd169fd4d6", + "zh:e617974d3af9ac1d0eb9d9cbc9595342474e9ddf374e27d3e39554489cd0d10e", + "zh:eeca97061dbf09c887347467cd3ea2f41839550f8a47695f571ba721b6d18079", + "zh:fc521229e8a83b6e0bf4d65452484e242cf7eef073e4c63abe793934bebe4f7c", + "zh:fdc15b5e46fafb82acd5e3b0f1dda1743686b98d5c6ba40e34e91bae67f00c91", + ] +} diff --git a/functions/triggers-getting-started/README.md b/functions/triggers-getting-started/README.md new file mode 100644 index 0000000..8afe024 --- /dev/null +++ b/functions/triggers-getting-started/README.md @@ -0,0 +1,3 @@ +# Triggers Getting Started + +Simpple starter example for using triggers in all supported languages. \ No newline at end of file diff --git a/functions/triggers-getting-started/go/cmd/main.go b/functions/triggers-getting-started/go/cmd/main.go new file mode 100644 index 0000000..2f73b45 --- /dev/null +++ b/functions/triggers-getting-started/go/cmd/main.go @@ -0,0 +1,11 @@ +package main + +import ( + "triggers-getting-started/go" + "github.com/scaleway/serverless-functions-go/local" +) + +func main() { + // Replace "Handle" with your function handler name if necessary + local.ServeHandler(handler.Handle, local.WithPort(8080)) +} diff --git a/functions/triggers-getting-started/go/go.mod b/functions/triggers-getting-started/go/go.mod new file mode 100644 index 0000000..4016da5 --- /dev/null +++ b/functions/triggers-getting-started/go/go.mod @@ -0,0 +1,7 @@ +module triggers-getting-started/go + +go 1.19 + +require github.com/scaleway/serverless-functions-go v0.1.2 + +require github.com/google/uuid v1.3.0 // indirect diff --git a/functions/triggers-getting-started/go/go.sum b/functions/triggers-getting-started/go/go.sum new file mode 100644 index 0000000..cc000e7 --- /dev/null +++ b/functions/triggers-getting-started/go/go.sum @@ -0,0 +1,8 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/scaleway/serverless-functions-go v0.1.2 h1:UUToB+XXpLDG9l6c9c0ALhAs1jvb6rV2Lkyj2kZEYCo= +github.com/scaleway/serverless-functions-go v0.1.2/go.mod h1:23Hj74DYzTsVY3QgvsM0MGnx0+0OAAabTC06BOMJE58= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/functions/triggers-getting-started/go/handler.go b/functions/triggers-getting-started/go/handler.go new file mode 100644 index 0000000..b5d1e11 --- /dev/null +++ b/functions/triggers-getting-started/go/handler.go @@ -0,0 +1,49 @@ +package handler + +import ( + "fmt" + "io" + "math/big" + "net/http" + "strconv" +) + +func factorial(n int) *big.Int { + var f big.Int + f.MulRange(1, int64(n)) + return &f +} + +// This handle function comes frome our examples and is not modified at all. +func Handle(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + // SQS triggers are sent as POST requests. + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + // The SQS trigger sends the message content in the body. + body, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + n, err := strconv.Atoi(string(body)) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + result := factorial(n) + fmt.Printf("go: factorial of %d is %s\n", n, result.String()) + + _, err = io.WriteString(w, result.String()) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + w.Header().Set("Content-Type", "text/plain") +} diff --git a/functions/triggers-getting-started/main.tf b/functions/triggers-getting-started/main.tf new file mode 100644 index 0000000..b72ef73 --- /dev/null +++ b/functions/triggers-getting-started/main.tf @@ -0,0 +1,85 @@ +locals { + functions = { + "go" = { + path = "go" + runtime = "go120" + handler = "Handle" + } + "node" = { + path = "node" + runtime = "node20" + handler = "handler.handle" + } + "php" = { + path = "php" + runtime = "php82" + handler = "handler.handle" + } + "python" = { + path = "python" + runtime = "python311" + handler = "handler.handler" + } + "rust" = { + path = "rust" + runtime = "rust165" + handler = "handler" + } + } +} + +resource "scaleway_function_namespace" "main" { + name = "trigger-examples" +} + +data "archive_file" "function" { + for_each = local.functions + + type = "zip" + source_dir = "${path.module}/${each.value.path}" + output_path = "${path.module}/${each.value.path}.zip" +} + +resource "scaleway_function" "main" { + for_each = local.functions + + namespace_id = scaleway_function_namespace.main.id + name = each.key + runtime = each.value.runtime + handler = each.value.handler + + // As of 2023/09/04, only public functions can be triggered by a message queue + privacy = "public" + + zip_file = data.archive_file.function[each.key].output_path + zip_hash = data.archive_file.function[each.key].output_sha256 + deploy = true + + memory_limit = 2048 // 1120 mVCPUs + + min_scale = 0 + max_scale = 1 +} + +resource "scaleway_mnq_queue" "main" { + for_each = local.functions + + namespace_id = scaleway_mnq_namespace.main.id + name = "factorial-requests-${each.key}" + + sqs { + access_key = scaleway_mnq_credential.main.sqs_sns_credentials.0.access_key + secret_key = scaleway_mnq_credential.main.sqs_sns_credentials.0.secret_key + } +} + +resource "scaleway_function_trigger" "main" { + for_each = local.functions + + function_id = scaleway_function.main[each.key].id + name = "on-factorial-request" + sqs { + namespace_id = scaleway_mnq_namespace.main.id + queue = scaleway_mnq_queue.main[each.key].name + } +} diff --git a/functions/triggers-getting-started/messaging.tf b/functions/triggers-getting-started/messaging.tf new file mode 100644 index 0000000..186d40f --- /dev/null +++ b/functions/triggers-getting-started/messaging.tf @@ -0,0 +1,17 @@ + +resource "scaleway_mnq_namespace" "main" { + name = "serverless-examples" + protocol = "sqs_sns" +} + +resource "scaleway_mnq_credential" "main" { + name = "serverless-examples" + namespace_id = scaleway_mnq_namespace.main.id + sqs_sns_credentials { + permissions { + can_publish = true + can_receive = true + can_manage = true + } + } +} diff --git a/functions/triggers-getting-started/node/.gitignore b/functions/triggers-getting-started/node/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/functions/triggers-getting-started/node/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/functions/triggers-getting-started/node/handler.js b/functions/triggers-getting-started/node/handler.js new file mode 100644 index 0000000..07e9d84 --- /dev/null +++ b/functions/triggers-getting-started/node/handler.js @@ -0,0 +1,51 @@ +"use strict"; + +import { pathToFileURL } from "url"; + +/** + * Compute the factorial of a number + * @param {number} n + * @returns {number} The factorial of n + */ +function factorial(n) { + let result = 1; + for (let i = 2; i <= n; i++) { + result *= i; + } + return result; +} + +export function handle(event, context, callback) { + if (event["httpMethod"] !== "POST") { + return { + statusCode: 405, + body: "Method Not Allowed", + headers: { + "Content-Type": "text/plain", + }, + }; + } + + // The SQS trigger sends the message content in the body. + const n = parseInt(event["body"]); + console.log(`node: received ${n}`); + const result = factorial(n); + + console.log(`node: factorial of ${n} is ${result}`); + + return { + statusCode: 200, + body: result.toString(), + headers: { + "Content-Type": "text/plain", + }, + }; +} + +/* Module was not imported but called directly, so we can test locally. +This will not be executed on Scaleway Functions */ +if (import.meta.url === pathToFileURL(process.argv[1]).href) { + import("@scaleway/serverless-functions").then(scw_fnc_node => { + scw_fnc_node.serveHandler(handle, 8080); + }); +} diff --git a/functions/triggers-getting-started/node/package-lock.json b/functions/triggers-getting-started/node/package-lock.json new file mode 100644 index 0000000..07c6315 --- /dev/null +++ b/functions/triggers-getting-started/node/package-lock.json @@ -0,0 +1,693 @@ +{ + "name": "examples", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "examples", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@scaleway/serverless-functions": "^1.0.0" + } + }, + "node_modules/@fastify/ajv-compiler": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-3.5.0.tgz", + "integrity": "sha512-ebbEtlI7dxXF5ziNdr05mOY8NnDiPB1XvAlLHctRt/Rc+C3LCOVW5imUVX+mhvUhnNzmPBHewUkOFgGlCxgdAA==", + "dev": true, + "dependencies": { + "ajv": "^8.11.0", + "ajv-formats": "^2.1.1", + "fast-uri": "^2.0.0" + } + }, + "node_modules/@fastify/deepmerge": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@fastify/deepmerge/-/deepmerge-1.3.0.tgz", + "integrity": "sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A==", + "dev": true + }, + "node_modules/@fastify/error": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@fastify/error/-/error-3.3.0.tgz", + "integrity": "sha512-dj7vjIn1Ar8sVXj2yAXiMNCJDmS9MQ9XMlIecX2dIzzhjSHCyKo4DdXjXMs7wKW2kj6yvVRSpuQjOZ3YLrh56w==", + "dev": true + }, + "node_modules/@fastify/fast-json-stringify-compiler": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-4.3.0.tgz", + "integrity": "sha512-aZAXGYo6m22Fk1zZzEUKBvut/CIIQe/BapEORnxiD5Qr0kPHqqI69NtEMCme74h+at72sPhbkb4ZrLd1W3KRLA==", + "dev": true, + "dependencies": { + "fast-json-stringify": "^5.7.0" + } + }, + "node_modules/@fastify/url-data": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@fastify/url-data/-/url-data-5.3.1.tgz", + "integrity": "sha512-DbXNlGyxyKQZijNn+GE5qrlTyy8Hl77K+DZrp2MFcdaa0CuxpOQTTVzfV3Inx0otCVVtcjVAS7KrhAwQ2UHvSA==", + "dev": true, + "dependencies": { + "fast-uri": "^2.2.0", + "fastify-plugin": "^4.0.0" + } + }, + "node_modules/@scaleway/serverless-functions": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@scaleway/serverless-functions/-/serverless-functions-1.0.2.tgz", + "integrity": "sha512-rTfOTvGrVfi00JWckjP+JsJtBjvquQC6+RN3RCfEqYG9od38sLW++5uu9hcuOS+ZjxqkcF/+nPB2uUzmrxR6Rw==", + "dev": true, + "dependencies": { + "@fastify/url-data": "^5.3.1", + "@types/node": "^18.15.11", + "fastify": "^4.15.0" + } + }, + "node_modules/@types/node": { + "version": "18.17.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.14.tgz", + "integrity": "sha512-ZE/5aB73CyGqgQULkLG87N9GnyGe5TcQjv34pwS8tfBs1IkCh0ASM69mydb2znqd6v0eX+9Ytvk6oQRqu8T1Vw==", + "dev": true + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==", + "dev": true + }, + "node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", + "dev": true + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/avvio": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-8.2.1.tgz", + "integrity": "sha512-TAlMYvOuwGyLK3PfBb5WKBXZmXz2fVCgv23d6zZFdle/q3gPjmxBaeuC0pY0Dzs5PWMSgfqqEZkrye19GlDTgw==", + "dev": true, + "dependencies": { + "archy": "^1.0.0", + "debug": "^4.0.0", + "fastq": "^1.6.1" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/fast-content-type-parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-1.0.0.tgz", + "integrity": "sha512-Xbc4XcysUXcsP5aHUU7Nq3OwvHq97C+WnbkeIefpeYLX+ryzFJlU6OStFJhs6Ol0LkUGpcK+wL0JwfM+FCU5IA==", + "dev": true + }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", + "dev": true + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-json-stringify": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-5.8.0.tgz", + "integrity": "sha512-VVwK8CFMSALIvt14U8AvrSzQAwN/0vaVRiFFUVlpnXSnDGrSkOAO5MtzyN8oQNjLd5AqTW5OZRgyjoNuAuR3jQ==", + "dev": true, + "dependencies": { + "@fastify/deepmerge": "^1.0.0", + "ajv": "^8.10.0", + "ajv-formats": "^2.1.1", + "fast-deep-equal": "^3.1.3", + "fast-uri": "^2.1.0", + "rfdc": "^1.2.0" + } + }, + "node_modules/fast-querystring": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", + "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", + "dev": true, + "dependencies": { + "fast-decode-uri-component": "^1.0.1" + } + }, + "node_modules/fast-redact": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.3.0.tgz", + "integrity": "sha512-6T5V1QK1u4oF+ATxs1lWUmlEk6P2T9HqJG3e2DnHOdVgZy2rFJBoEnrIedcTXlkAHU/zKC+7KETJ+KGGKwxgMQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-uri": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-2.2.0.tgz", + "integrity": "sha512-cIusKBIt/R/oI6z/1nyfe2FvGKVTohVRfvkOhvx0nCEW+xf5NoCXjAHcWp93uOUBchzYcsvPlrapAdX1uW+YGg==", + "dev": true + }, + "node_modules/fastify": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-4.22.2.tgz", + "integrity": "sha512-rK8mF/1mZJHH6H/L22OhmilTgrp5XMkk3RHcSy03LC+TJ6+wLhbq+4U62bjns15VzIbBNgxTqAForBqtGAa0NQ==", + "dev": true, + "dependencies": { + "@fastify/ajv-compiler": "^3.5.0", + "@fastify/error": "^3.2.0", + "@fastify/fast-json-stringify-compiler": "^4.3.0", + "abstract-logging": "^2.0.1", + "avvio": "^8.2.1", + "fast-content-type-parse": "^1.0.0", + "fast-json-stringify": "^5.7.0", + "find-my-way": "^7.6.0", + "light-my-request": "^5.9.1", + "pino": "^8.12.0", + "process-warning": "^2.2.0", + "proxy-addr": "^2.0.7", + "rfdc": "^1.3.0", + "secure-json-parse": "^2.5.0", + "semver": "^7.5.0", + "tiny-lru": "^11.0.1" + } + }, + "node_modules/fastify-plugin": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-4.5.1.tgz", + "integrity": "sha512-stRHYGeuqpEZTL1Ef0Ovr2ltazUT9g844X5z/zEBFLG8RYlpDiOCIG+ATvYEp+/zmc7sN29mcIMp8gvYplYPIQ==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/find-my-way": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-7.6.2.tgz", + "integrity": "sha512-0OjHn1b1nCX3eVbm9ByeEHiscPYiHLfhei1wOUU9qffQkk98wE0Lo8VrVYfSGMgnSnDh86DxedduAnBf4nwUEw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-querystring": "^1.0.0", + "safe-regex2": "^2.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/light-my-request": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-5.10.0.tgz", + "integrity": "sha512-ZU2D9GmAcOUculTTdH9/zryej6n8TzT+fNGdNtm6SDp5MMMpHrJJkvAdE3c6d8d2chE9i+a//dS9CWZtisknqA==", + "dev": true, + "dependencies": { + "cookie": "^0.5.0", + "process-warning": "^2.0.0", + "set-cookie-parser": "^2.4.1" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz", + "integrity": "sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w==", + "dev": true + }, + "node_modules/pino": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-8.15.0.tgz", + "integrity": "sha512-olUADJByk4twxccmAxb1RiGKOSvddHugCV3wkqjyv+3Sooa2KLrmXrKEWOKi0XPCLasRR5jBXxioE1jxUa4KzQ==", + "dev": true, + "dependencies": { + "atomic-sleep": "^1.0.0", + "fast-redact": "^3.1.1", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "v1.0.0", + "pino-std-serializers": "^6.0.0", + "process-warning": "^2.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^3.1.0", + "thread-stream": "^2.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.0.0.tgz", + "integrity": "sha512-c7vo5OpW4wIS42hUVcT5REsL8ZljsUfBjqV/e2sFxmFEFZiq1XLUp5EYLtuDH6PEHq9W1egWqRbnLUP5FuZmOA==", + "dev": true, + "dependencies": { + "readable-stream": "^4.0.0", + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz", + "integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==", + "dev": true + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-warning": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-2.2.0.tgz", + "integrity": "sha512-/1WZ8+VQjR6avWOgHeEPd7SDQmFQ1B5mC1eRXsCm5TarlNmx/wCsa5GEaxGm05BORRtyG/Ex/3xq3TuRvq57qg==", + "dev": true + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "dev": true + }, + "node_modules/readable-stream": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.4.2.tgz", + "integrity": "sha512-Lk/fICSyIhodxy1IDK2HazkeGjSmezAWX2egdtJnYhtzKEsBPJowlI6F6LPb5tqIQILrMbx22S5o3GuJavPusA==", + "dev": true, + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "dev": true, + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ret": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz", + "integrity": "sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "dev": true + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-regex2": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-2.0.0.tgz", + "integrity": "sha512-PaUSFsUaNNuKwkBijoAPHAK6/eM6VirvyPWlZ7BAQy4D+hCvh4B6lIG+nPdhbFfIbP+gTGBcrdsOaUs0F+ZBOQ==", + "dev": true, + "dependencies": { + "ret": "~0.2.0" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", + "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", + "dev": true + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz", + "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==", + "dev": true + }, + "node_modules/sonic-boom": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.3.0.tgz", + "integrity": "sha512-LYxp34KlZ1a2Jb8ZQgFCK3niIHzibdwtwNUWKg0qQRzsDoJ3Gfgkf8KdBTFU3SkejDEIlWwnSnpVdOZIhFMl/g==", + "dev": true, + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "dev": true, + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/thread-stream": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.4.0.tgz", + "integrity": "sha512-xZYtOtmnA63zj04Q+F9bdEay5r47bvpo1CaNqsKi7TpoJHcotUez8Fkfo2RJWpW91lnnaApdpRbVwCWsy+ifcw==", + "dev": true, + "dependencies": { + "real-require": "^0.2.0" + } + }, + "node_modules/tiny-lru": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-11.0.1.tgz", + "integrity": "sha512-iNgFugVuQgBKrqeO/mpiTTgmBsTP0WL6yeuLfLs/Ctf0pI/ixGqIRm8sDCwMcXGe9WWvt2sGXI5mNqZbValmJg==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } +} diff --git a/functions/triggers-getting-started/node/package.json b/functions/triggers-getting-started/node/package.json new file mode 100644 index 0000000..8fc16b0 --- /dev/null +++ b/functions/triggers-getting-started/node/package.json @@ -0,0 +1,15 @@ +{ + "name": "triggers-getting-started", + "version": "1.0.0", + "description": "", + "main": "handler.js", + "scripts": { + "start": "node handler.js" + }, + "type": "module", + "author": "", + "license": "ISC", + "devDependencies": { + "@scaleway/serverless-functions": "^1.0.0" + } +} \ No newline at end of file diff --git a/functions/triggers-getting-started/outputs.tf b/functions/triggers-getting-started/outputs.tf new file mode 100644 index 0000000..7d6ec78 --- /dev/null +++ b/functions/triggers-getting-started/outputs.tf @@ -0,0 +1,8 @@ +output "sqs_access_key" { + value = scaleway_mnq_credential.main.sqs_sns_credentials.0.access_key +} + +output "sqs_secret_key" { + value = scaleway_mnq_credential.main.sqs_sns_credentials.0.secret_key + sensitive = true +} diff --git a/functions/triggers-getting-started/php/handler.php b/functions/triggers-getting-started/php/handler.php new file mode 100644 index 0000000..4d880a7 --- /dev/null +++ b/functions/triggers-getting-started/php/handler.php @@ -0,0 +1,36 @@ + 405, + "headers" => ["Content-Type" => "text/plain"], + "body" => "Method Not Allowed", + ]; + } + + # The content of the SQS message is passed in the body. + $n = intval($event["body"]); + $result = factorial($n); + + echo "php: factorial of $n is $result\n"; + + return [ + "statusCode" => 200, + "headers" => ["Content-Type" => "text/plain"], + "body" => $result, + ]; +} diff --git a/functions/triggers-getting-started/provider.tf b/functions/triggers-getting-started/provider.tf new file mode 100644 index 0000000..14ef505 --- /dev/null +++ b/functions/triggers-getting-started/provider.tf @@ -0,0 +1,13 @@ +terraform { + required_providers { + scaleway = { + source = "scaleway/scaleway" + version = ">= 2.12" + } + archive = { + source = "hashicorp/archive" + version = ">= 2.4" + } + } + required_version = ">= 0.13" +} diff --git a/functions/triggers-getting-started/python/handler.py b/functions/triggers-getting-started/python/handler.py new file mode 100644 index 0000000..e3e2cd1 --- /dev/null +++ b/functions/triggers-getting-started/python/handler.py @@ -0,0 +1,47 @@ +from typing import TYPE_CHECKING +import functools +import operator +import http + +if TYPE_CHECKING: + from scaleway_functions_python.framework.v1.hints import Context, Event, Response + + +def factorial(n: int) -> int: # pylint: disable=invalid-name + """Return the factorial of n >= 0.""" + return functools.reduce(operator.mul, range(1, n + 1), 1) + + +def handler(event: "Event", _context: "Context") -> "Response": + """Compute factorial of the number passed in the trigger message.""" + + if event["httpMethod"] != "POST": + # SQS triggers are sent as POST requests. + status = http.HTTPStatus.METHOD_NOT_ALLOWED + return { + "headers": {"Content-Type": "text/plain"}, + "statusCode": status.value, + "body": status.description, + } + + # The content of the SQS message is passed in the body. + n = int(event["body"]) # pylint: disable=invalid-name + result = factorial(n) + + print(f"python: factorial of {n} is {result}") + + return { + "headers": {"Content-Type": "text/plain"}, + "statusCode": http.HTTPStatus.OK.value, + # Because triggers are asynchronous, the response body is ignored. + # It's kept here when testing locally. + "body": str(result), + } + + +if __name__ == "__main__": + from scaleway_functions_python import local + + # Example usage: + # curl -X POST -d 5 http://localhost:8080 + local.serve_handler(handler) diff --git a/functions/triggers-getting-started/python/requirements-dev.txt b/functions/triggers-getting-started/python/requirements-dev.txt new file mode 100644 index 0000000..0c07318 --- /dev/null +++ b/functions/triggers-getting-started/python/requirements-dev.txt @@ -0,0 +1,3 @@ +scaleway_functions_python==0.2.0 +requests==2.28.2 +pytest==7.2.2 \ No newline at end of file diff --git a/functions/triggers-getting-started/rust/.gitignore b/functions/triggers-getting-started/rust/.gitignore new file mode 100644 index 0000000..2f7896d --- /dev/null +++ b/functions/triggers-getting-started/rust/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/functions/triggers-getting-started/rust/Cargo.lock b/functions/triggers-getting-started/rust/Cargo.lock new file mode 100644 index 0000000..a066719 --- /dev/null +++ b/functions/triggers-getting-started/rust/Cargo.lock @@ -0,0 +1,273 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "memchr" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "tokio" +version = "1.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +dependencies = [ + "backtrace", + "pin-project-lite", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "triggers-getting-started" +version = "0.1.0" +dependencies = [ + "hyper", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] diff --git a/functions/triggers-getting-started/rust/Cargo.toml b/functions/triggers-getting-started/rust/Cargo.toml new file mode 100644 index 0000000..50aa3a6 --- /dev/null +++ b/functions/triggers-getting-started/rust/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "triggers-getting-started" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +hyper = { version = "0.14", features = ["http1"] } diff --git a/functions/triggers-getting-started/rust/src/lib.rs b/functions/triggers-getting-started/rust/src/lib.rs new file mode 100644 index 0000000..691fd67 --- /dev/null +++ b/functions/triggers-getting-started/rust/src/lib.rs @@ -0,0 +1,38 @@ +use hyper::{Body, Request, Response, StatusCode}; + +// Reference: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.product +fn factorial(n: u64) -> u64 { + (1..=n).product() +} + +pub async fn handler(req: Request) -> Response { + if req.method() != hyper::Method::POST { + return Response::builder() + .status(StatusCode::METHOD_NOT_ALLOWED) + .header("Content-Type", "text/plain") + .body(Body::from("Method not allowed")) + .unwrap(); + } + + // The SQS trigger sends the message content in the body. + let body = hyper::body::to_bytes(req.into_body()).await.unwrap(); + let n = match String::from_utf8(body.to_vec()).unwrap().parse::() { + Ok(n) => n, + Err(e) => { + return Response::builder() + .status(StatusCode::BAD_REQUEST) + .header("Content-Type", "text/plain") + .body(Body::from(format!("Invalid number: {}", e))) + .unwrap(); + } + }; + + let result = factorial(n); + println!("rust: factorial of {} is {}", n, result); + + Response::builder() + .status(StatusCode::OK) + .header("Content-Type", "text/plain") + .body(Body::from(format!("{}", result))) + .unwrap() +} diff --git a/functions/triggers-getting-started/tests/requirements.txt b/functions/triggers-getting-started/tests/requirements.txt new file mode 100644 index 0000000..4598947 --- /dev/null +++ b/functions/triggers-getting-started/tests/requirements.txt @@ -0,0 +1,2 @@ +boto3~=1.28.40 +requests~=2.31.0 diff --git a/functions/triggers-getting-started/tests/send_messages.py b/functions/triggers-getting-started/tests/send_messages.py new file mode 100644 index 0000000..3f156f2 --- /dev/null +++ b/functions/triggers-getting-started/tests/send_messages.py @@ -0,0 +1,32 @@ +import os + +import boto3 + +# Credentials generated by terraform +AWS_ACCESS_KEY_ID = os.environ["AWS_ACCESS_KEY_ID"] +AWS_SECRET_ACCESS_KEY = os.environ["AWS_SECRET_ACCESS_KEY"] + +MAX_FACTORIAL_REQUESTS = 20 + +params = { + "endpoint_url": "http://sqs-sns.mnq.fr-par.scw.cloud", + "aws_access_key_id": AWS_ACCESS_KEY_ID, + "aws_secret_access_key": AWS_SECRET_ACCESS_KEY, + "region_name": "fr-par", +} + +client = boto3.client("sqs", **params) +sqs = boto3.resource("sqs", **params) + + +def main(): + queues = client.list_queues() + for queue_url in queues["QueueUrls"]: + queue = sqs.Queue(queue_url) + for i in range(MAX_FACTORIAL_REQUESTS): + # See: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sqs/queue/index.html + queue.send_message(MessageBody=str(i)) + + +if __name__ == "__main__": + main() diff --git a/functions/triggers-getting-started/tests/test_handler.py b/functions/triggers-getting-started/tests/test_handler.py new file mode 100644 index 0000000..f3cd361 --- /dev/null +++ b/functions/triggers-getting-started/tests/test_handler.py @@ -0,0 +1,45 @@ +import unittest +import requests +from requests.adapters import HTTPAdapter +from urllib3.util import Retry + +LOCAL_TESTING_URL = "http://localhost:8080" + +FACTORIALS = { + 0: 1, + 1: 1, + 2: 2, + 3: 6, + 4: 24, + 5: 120, +} + + +class TestHandler(unittest.TestCase): + """Sample test cases to run against the handler.""" + + @classmethod + def setUpClass(cls): + cls.session = requests.Session() + retries = Retry(total=5, backoff_factor=1, status_forcelist=[502, 503, 504]) + cls.session.mount("http://", HTTPAdapter(max_retries=retries)) + # Required for node offline testing + cls.session.headers["Content-Type"] = "text/plain" + + def test_factorial(self): + for factorial, expected in FACTORIALS.items(): + with self.subTest(f"factorial({factorial})"): + resp = self.session.post( + LOCAL_TESTING_URL, + data=str(factorial), + ) + self.assertEqual(resp.status_code, 200) + self.assertEqual(resp.text, str(expected)) + + def test_405_on_get(self): + resp = self.session.get(LOCAL_TESTING_URL) + self.assertEqual(resp.status_code, 405) + + +if __name__ == "__main__": + unittest.main() From e4bd8116de0c9f88abf98213847bedee7b481d4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andy=20M=C3=A9ry?= Date: Tue, 5 Sep 2023 14:51:56 +0200 Subject: [PATCH 2/6] docs: add README --- README.md | 11 ++--- functions/redis-tls/README.md | 2 +- functions/triggers-getting-started/README.md | 42 ++++++++++++++++++- .../triggers-getting-started/go/handler.go | 1 - functions/triggers-getting-started/main.tf | 2 +- .../triggers-getting-started/messaging.tf | 4 +- .../triggers-getting-started/php/handler.php | 2 - .../tests/send_messages.py | 4 +- 8 files changed, 54 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 09413d7..b5bb9b7 100644 --- a/README.md +++ b/README.md @@ -43,22 +43,23 @@ Table of Contents: | **[Python ChatBot](functions/python-dependencies/README.md)**
A chatbot example with ChatterBot. | python310 | [Serverless Framework] | | **[Python Dependencies](functions/python-chatbot/README.md)**
Example showing how to use Python requirements with Serverless Framework. | python310 | [Serverless Framework] | | **[Python MultiPart Upload to S3](functions/python-upload-file-s3-multipart/README.md)**
A function to upload file from form-data to S3. | python311 | [Python API Framework] | -| **[Python SQS Trigger Hello World](functions/python-sqs-trigger-hello-world/README.md)**
Trigger a function by sending a message to a SQS queue. | python311 | [Terraform] | -| **[Python SQS Trigger Async Worker](functions/python-sqs-trigger-async-worker/README.md)**
Use SQS queues and function triggers to scheule an async task from another function. | python311 | [Terraform] | +| **[Python SQS Trigger Hello World](functions/python-sqs-trigger-hello-world/README.md)**
Trigger a function by sending a message to a SQS queue. | python311 | [Terraform] | +| **[Python SQS Trigger Async Worker](functions/python-sqs-trigger-async-worker/README.md)**
Use SQS queues and function triggers to scheule an async task from another function. | python311 | [Terraform] | | **[Redis TLS](functions/redis-tls/README.md)**
How to connect a function to a Scaleway Redis cluster with TLS enabled. | python310 | [Terraform] | | **[Rust MNIST](functions/rust-mnist/README.md)**
A Rust function to recognize hand-written digits with a simple neural network. | rust165 | [Serverless Framework] | | **[PostgreSQL Python](functions/postgre-sql-python/README.md)**
A Python function to perform a query on a PostgreSQL managed database. | python310 | [Serverless Framework] | | **[Terraform Python](functions/terraform-python-example/README.md)**
A Python function deployed with Terraform. | python310 | [Terraform] | +| **[Triggers Getting Started](functions/triggers-getting-started/README.md)**
Simple SQS trigger example for all runtimes. | all | [Terraform] | | **[Typescript with Node runtime](functions/typescript-with-node/README.md)**
A Typescript function using Node runtime. | node18 | [Serverless Framework] | -| **[Serverless Gateway Python Example](functions/serverless-gateway-python/README.md)**
A Python serverless API using Serverless Gateway. | python310 | [Python API Framework] | +| **[Serverless Gateway Python Example](functions/serverless-gateway-python/README.md)**
A Python serverless API using Serverless Gateway. | python310 | [Python API Framework] | ### 📦 Containers | Example | Language | Deployment | |----------------------------------------------------------------------------------------------------------------------------------------------|--------------|------------------------| | **[Container Bash Script](containers/bash-scheduled-job/README.md)**
A Bash script runnning on a schedule using serverless containers. | Bash | [Serverless Framework] | -| **[Function Handler Java](containers/function-handler-java/README.md)**
A Java function handler deployed on CaaS. | Java | [Serverless Framework] | -| **[Nginx CORS Private](containers/nginx-cors-private-python/README.md)**
A Nginx proxy to allow CORS requests to a private container. | Python Flask | [Terraform] | +| **[Function Handler Java](containers/function-handler-java/README.md)**
A Java function handler deployed on CaaS. | Java | [Serverless Framework] | +| **[Nginx CORS Private](containers/nginx-cors-private-python/README.md)**
A Nginx proxy to allow CORS requests to a private container. | Python Flask | [Terraform] | ### 💜 Projects diff --git a/functions/redis-tls/README.md b/functions/redis-tls/README.md index 67b4d9f..ae86bbd 100644 --- a/functions/redis-tls/README.md +++ b/functions/redis-tls/README.md @@ -16,7 +16,7 @@ Everything is managed with Terraform. The terraform config files will also creat ```sh terraform init -terraform deploy +terraform apply ``` You should be able to see your function in the Scaleway console. diff --git a/functions/triggers-getting-started/README.md b/functions/triggers-getting-started/README.md index 8afe024..c48f08e 100644 --- a/functions/triggers-getting-started/README.md +++ b/functions/triggers-getting-started/README.md @@ -1,3 +1,43 @@ # Triggers Getting Started -Simpple starter example for using triggers in all supported languages. \ No newline at end of file +Simple starter examples that showcase using SQS triggers in all Scaleway Functions supported languages. + +In each example, a function is triggered by a SQS queue with a message containing a number. The function will then print the factorial of this number to the logs. + +## Requirements + +This example requires [Terraform](https://www.scaleway.com/en/docs/tutorials/terraform-quickstart/). + +## Setup + +The Terraform configuration will deploy a function for each language, showing how to use triggers with each language. + +It will also create a SQS queue per function to trigger it. + +```console +terraform init +terraform apply +``` + +You should be able to see your functions in the Scaleway console. + +## Running + +You can use the `tests/send_messages.py` script to send a message to the SQS queue of each function. + +```console +export AWS_ACCESS_KEY_ID=$(terraform output -raw sqs_access_key) +export AWS_SECRET_ACCESS_KEY=$(terraform output -raw sqs_secret_key) +python tests/send_messages.py +``` + +In Cockpit, you should see the functions being triggered and the result of the factorial being printed in the logs. + +```console +``` + +## Cleanup + +```console +terraform destroy +``` diff --git a/functions/triggers-getting-started/go/handler.go b/functions/triggers-getting-started/go/handler.go index b5d1e11..84624bf 100644 --- a/functions/triggers-getting-started/go/handler.go +++ b/functions/triggers-getting-started/go/handler.go @@ -44,6 +44,5 @@ func Handle(w http.ResponseWriter, r *http.Request) { return } - w.WriteHeader(http.StatusOK) w.Header().Set("Content-Type", "text/plain") } diff --git a/functions/triggers-getting-started/main.tf b/functions/triggers-getting-started/main.tf index b72ef73..594013d 100644 --- a/functions/triggers-getting-started/main.tf +++ b/functions/triggers-getting-started/main.tf @@ -29,7 +29,7 @@ locals { } resource "scaleway_function_namespace" "main" { - name = "trigger-examples" + name = "triggers-getting-started" } data "archive_file" "function" { diff --git a/functions/triggers-getting-started/messaging.tf b/functions/triggers-getting-started/messaging.tf index 186d40f..328a417 100644 --- a/functions/triggers-getting-started/messaging.tf +++ b/functions/triggers-getting-started/messaging.tf @@ -1,11 +1,11 @@ resource "scaleway_mnq_namespace" "main" { - name = "serverless-examples" + name = "triggers-getting-started" protocol = "sqs_sns" } resource "scaleway_mnq_credential" "main" { - name = "serverless-examples" + name = "triggers-getting-started" namespace_id = scaleway_mnq_namespace.main.id sqs_sns_credentials { permissions { diff --git a/functions/triggers-getting-started/php/handler.php b/functions/triggers-getting-started/php/handler.php index 4d880a7..9d083b3 100644 --- a/functions/triggers-getting-started/php/handler.php +++ b/functions/triggers-getting-started/php/handler.php @@ -1,7 +1,5 @@ Date: Tue, 5 Sep 2023 15:05:47 +0200 Subject: [PATCH 3/6] fix: add missing new lines --- functions/triggers-getting-started/.gitignore | 2 +- functions/triggers-getting-started/node/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/functions/triggers-getting-started/.gitignore b/functions/triggers-getting-started/.gitignore index d760a24..1d6ac01 100644 --- a/functions/triggers-getting-started/.gitignore +++ b/functions/triggers-getting-started/.gitignore @@ -10,4 +10,4 @@ # Crash log files crash.log -crash.*.log \ No newline at end of file +crash.*.log diff --git a/functions/triggers-getting-started/node/package.json b/functions/triggers-getting-started/node/package.json index 8fc16b0..96b216e 100644 --- a/functions/triggers-getting-started/node/package.json +++ b/functions/triggers-getting-started/node/package.json @@ -12,4 +12,4 @@ "devDependencies": { "@scaleway/serverless-functions": "^1.0.0" } -} \ No newline at end of file +} From ca00d4368cc015f01e19f79d9378dce08e40eafb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andy=20M=C3=A9ry?= Date: Tue, 5 Sep 2023 15:17:56 +0200 Subject: [PATCH 4/6] fix: left comment for cc/cv --- functions/triggers-getting-started/go/handler.go | 1 - 1 file changed, 1 deletion(-) diff --git a/functions/triggers-getting-started/go/handler.go b/functions/triggers-getting-started/go/handler.go index 84624bf..615ce19 100644 --- a/functions/triggers-getting-started/go/handler.go +++ b/functions/triggers-getting-started/go/handler.go @@ -14,7 +14,6 @@ func factorial(n int) *big.Int { return &f } -// This handle function comes frome our examples and is not modified at all. func Handle(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { // SQS triggers are sent as POST requests. From 976afe3dc9d152b65d95ac9cc43f26a026c54191 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andy=20M=C3=A9ry?= Date: Mon, 11 Sep 2023 09:46:09 +0200 Subject: [PATCH 5/6] fix: unecessary blank line Co-authored-by: norbjd --- functions/triggers-getting-started/messaging.tf | 1 - 1 file changed, 1 deletion(-) diff --git a/functions/triggers-getting-started/messaging.tf b/functions/triggers-getting-started/messaging.tf index 328a417..6d52853 100644 --- a/functions/triggers-getting-started/messaging.tf +++ b/functions/triggers-getting-started/messaging.tf @@ -1,4 +1,3 @@ - resource "scaleway_mnq_namespace" "main" { name = "triggers-getting-started" protocol = "sqs_sns" From 23e348aaaf90b45c469712f6f8c872bea53f1a1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andy=20M=C3=A9ry?= Date: Mon, 11 Sep 2023 10:45:30 +0200 Subject: [PATCH 6/6] docs: improve docs about retry policy and body handlind --- functions/triggers-getting-started/README.md | 15 +++++++++++++++ functions/triggers-getting-started/go/handler.go | 8 +++++++- functions/triggers-getting-started/main.tf | 3 +-- .../triggers-getting-started/node/handler.js | 4 ++++ .../triggers-getting-started/php/handler.php | 4 ++++ .../triggers-getting-started/python/handler.py | 6 ++++-- .../triggers-getting-started/rust/src/lib.rs | 7 ++++++- 7 files changed, 41 insertions(+), 6 deletions(-) diff --git a/functions/triggers-getting-started/README.md b/functions/triggers-getting-started/README.md index c48f08e..9cd63ac 100644 --- a/functions/triggers-getting-started/README.md +++ b/functions/triggers-getting-started/README.md @@ -34,8 +34,23 @@ python tests/send_messages.py In Cockpit, you should see the functions being triggered and the result of the factorial being printed in the logs. ```console +... +DEBUG - php: factorial of 17 is 355687428096000 source=user stream=stdout +2023-09-11 10:36:19.994 DEBUG - Function Triggered: / source=core +2023-09-11 10:36:19.993 DEBUG - php: factorial of 13 is 6227020800 source=user stream=stdout +2023-09-11 10:36:19.991 DEBUG - Function Triggered: / source=core +2023-09-11 10:36:19.977 DEBUG - php: factorial of 12 is 479001600 source=user stream=stdout +2023-09-11 10:36:19.976 DEBUG - php: factorial of 11 is 39916800 source=user stream=stdout +2023-09-11 10:36:19.975 DEBUG - php: factorial of 10 is 3628800 source=user stream=stdout +2023-09-11 10:36:19.964 DEBUG - Function Triggered: / source=core +2023-09-11 10:36:19.954 DEBUG - php: factorial of 3 is 6 source=user stream=stdout +2023-09-11 10:36:19.954 DEBUG - php: factorial of 4 is 24 source=user stream=stdout +2023-09-11 10:36:19.948 DEBUG - php: factorial of 0 is 1 source=user stream=stdout +... (truncated) ``` +Configuring and managing triggers is free, you only pay for the execution of your functions. For more information, please consult the [Scaleway Serverless pricing](https://www.scaleway.com/en/pricing/?tags=serverless). You will also be billed for the SQS queue usage when sending messages to it. + ## Cleanup ```console diff --git a/functions/triggers-getting-started/go/handler.go b/functions/triggers-getting-started/go/handler.go index 615ce19..c586df6 100644 --- a/functions/triggers-getting-started/go/handler.go +++ b/functions/triggers-getting-started/go/handler.go @@ -30,18 +30,24 @@ func Handle(w http.ResponseWriter, r *http.Request) { n, err := strconv.Atoi(string(body)) if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) + // Setting the status code to 200 will mark the message as processed. + http.Error(w, err.Error(), http.StatusOK) return } result := factorial(n) fmt.Printf("go: factorial of %d is %s\n", n, result.String()) + // Because triggers are asynchronous, the response body is ignored. + // It's kept here when testing locally. _, err = io.WriteString(w, result.String()) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } + // If the status code is not in the 2XX range, the message is considered + // failed and is retried. In total, there are 3 retries. + w.WriteHeader(http.StatusOK) w.Header().Set("Content-Type", "text/plain") } diff --git a/functions/triggers-getting-started/main.tf b/functions/triggers-getting-started/main.tf index 594013d..35eabaa 100644 --- a/functions/triggers-getting-started/main.tf +++ b/functions/triggers-getting-started/main.tf @@ -55,10 +55,9 @@ resource "scaleway_function" "main" { zip_hash = data.archive_file.function[each.key].output_sha256 deploy = true - memory_limit = 2048 // 1120 mVCPUs + memory_limit = 512 # MB / 280 mVCPU min_scale = 0 - max_scale = 1 } resource "scaleway_mnq_queue" "main" { diff --git a/functions/triggers-getting-started/node/handler.js b/functions/triggers-getting-started/node/handler.js index 07e9d84..dbef5e6 100644 --- a/functions/triggers-getting-started/node/handler.js +++ b/functions/triggers-getting-started/node/handler.js @@ -34,7 +34,11 @@ export function handle(event, context, callback) { console.log(`node: factorial of ${n} is ${result}`); return { + // If the status code is not in the 2XX range, the message is considered + // failed and is retried. In total, there are 3 retries. statusCode: 200, + // Because triggers are asynchronous, the response body is ignored. + // It's kept here when testing locally. body: result.toString(), headers: { "Content-Type": "text/plain", diff --git a/functions/triggers-getting-started/php/handler.php b/functions/triggers-getting-started/php/handler.php index 9d083b3..e2698bf 100644 --- a/functions/triggers-getting-started/php/handler.php +++ b/functions/triggers-getting-started/php/handler.php @@ -27,8 +27,12 @@ function handle($event, $context) echo "php: factorial of $n is $result\n"; return [ + // If the status code is not in the 2XX range, the message is considered + // failed and is retried. In total, there are 3 retries. "statusCode" => 200, "headers" => ["Content-Type" => "text/plain"], + // Because triggers are asynchronous, the response body is ignored. + // It's kept here when testing locally. "body" => $result, ]; } diff --git a/functions/triggers-getting-started/python/handler.py b/functions/triggers-getting-started/python/handler.py index e3e2cd1..27577d7 100644 --- a/functions/triggers-getting-started/python/handler.py +++ b/functions/triggers-getting-started/python/handler.py @@ -7,7 +7,7 @@ from scaleway_functions_python.framework.v1.hints import Context, Event, Response -def factorial(n: int) -> int: # pylint: disable=invalid-name +def factorial(n: int) -> int: # pylint: disable=invalid-name """Return the factorial of n >= 0.""" return functools.reduce(operator.mul, range(1, n + 1), 1) @@ -25,13 +25,15 @@ def handler(event: "Event", _context: "Context") -> "Response": } # The content of the SQS message is passed in the body. - n = int(event["body"]) # pylint: disable=invalid-name + n = int(event["body"]) # pylint: disable=invalid-name result = factorial(n) print(f"python: factorial of {n} is {result}") return { "headers": {"Content-Type": "text/plain"}, + # If the status code is not in the 2XX range, the message is considered + # failed and is retried. In total, there are 3 retries. "statusCode": http.HTTPStatus.OK.value, # Because triggers are asynchronous, the response body is ignored. # It's kept here when testing locally. diff --git a/functions/triggers-getting-started/rust/src/lib.rs b/functions/triggers-getting-started/rust/src/lib.rs index 691fd67..5c00454 100644 --- a/functions/triggers-getting-started/rust/src/lib.rs +++ b/functions/triggers-getting-started/rust/src/lib.rs @@ -20,7 +20,8 @@ pub async fn handler(req: Request) -> Response { Ok(n) => n, Err(e) => { return Response::builder() - .status(StatusCode::BAD_REQUEST) + // Setting the status code to 200 will mark the message as processed. + .status(StatusCode::OK) .header("Content-Type", "text/plain") .body(Body::from(format!("Invalid number: {}", e))) .unwrap(); @@ -31,8 +32,12 @@ pub async fn handler(req: Request) -> Response { println!("rust: factorial of {} is {}", n, result); Response::builder() + // If the status code is not in the 2XX range, the message is considered + // failed and is retried. In total, there are 3 retries. .status(StatusCode::OK) .header("Content-Type", "text/plain") + // Because triggers are asynchronous, the response body is ignored. + // It's kept here when testing locally. .body(Body::from(format!("{}", result))) .unwrap() }