├── examples ├── hello-config │ ├── data │ │ ├── text │ │ └── rv.json │ ├── test │ │ ├── mock │ │ │ └── values.yaml │ │ └── unit │ │ │ └── test-hello-secret.js │ ├── faas.json │ ├── lib │ │ └── index.js │ ├── package.json │ └── README.md ├── hello-secret │ ├── data │ │ ├── text │ │ └── rv.json │ ├── test │ │ ├── mock │ │ │ └── values.yaml │ │ └── unit │ │ │ └── test-hello-secret.js │ ├── faas.json │ ├── lib │ │ └── index.js │ ├── package.json │ └── README.md ├── kafka-producer │ ├── data │ │ ├── kafka │ │ │ ├── host │ │ │ └── topic │ │ └── xbem │ │ │ ├── sec │ │ │ ├── sasl.json │ │ │ └── dial.json │ │ │ └── cfg │ │ │ ├── amqp.json │ │ │ └── bind.json │ ├── package.json │ ├── faas.json │ ├── lib │ │ └── index.js │ └── README.md ├── weather │ ├── advanced_ui │ │ ├── data │ │ │ ├── config │ │ │ │ └── posturl │ │ │ └── private │ │ │ │ └── openweatherapikey │ │ ├── package.json │ │ ├── faas.json │ │ ├── lib │ │ │ ├── views │ │ │ │ └── index.ejs │ │ │ ├── index.js │ │ │ └── public │ │ │ │ ├── js │ │ │ │ └── app.js │ │ │ │ └── css │ │ │ │ └── style.css │ │ └── README.md │ └── basic_ui │ │ ├── private │ │ └── openweatherapikey │ │ ├── data │ │ └── private │ │ │ └── openweatherapikey │ │ ├── package.json │ │ ├── faas.json │ │ ├── lib │ │ ├── views │ │ │ └── index.ejs │ │ ├── public │ │ │ └── css │ │ │ │ └── style.css │ │ └── index.js │ │ └── README.md ├── slack-classify-image │ ├── data │ │ └── services │ │ │ ├── slacktoken │ │ │ └── leonardoapikey │ ├── package.json │ ├── faas.json │ ├── lib │ │ ├── slack-event.js │ │ └── classify-image.js │ └── README.md ├── hello-oauth │ ├── data │ │ └── verification.json │ ├── faas.json │ ├── package.json │ ├── lib │ │ ├── index.js │ │ └── auth.js │ └── README.md ├── s4sdk │ ├── data │ │ └── s4 │ │ │ └── credentials │ ├── faas.json │ ├── lib │ │ └── handler.js │ ├── package.json │ └── README.md ├── s3uploader │ ├── data │ │ └── s3 │ │ │ └── credentials.json │ ├── faas.json │ ├── package.json │ ├── lib │ │ └── index.js │ └── README.md ├── amqp-echo │ ├── data │ │ └── xbem │ │ │ └── cfg │ │ │ ├── amqp.json │ │ │ └── bind.json │ ├── lib │ │ └── index.js │ ├── faas.json │ ├── package.json │ └── README.md ├── qrcode-producer │ ├── faas.json │ ├── package.json │ ├── lib │ │ └── iso-time.js │ ├── README.md │ └── test │ │ └── test-iso-code.js ├── call-other-function │ ├── package.json │ ├── faas.json │ ├── README.md │ ├── lib │ │ └── main.js │ └── test │ │ └── test-call-other-function.js ├── hello-timer │ ├── package.json │ ├── lib │ │ └── index.js │ ├── faas.json │ ├── test │ │ └── unit │ │ │ └── test-hello-timer.js │ └── README.md ├── hello-world-jwt-auth │ ├── package.json │ ├── lib │ │ ├── call-fn.js │ │ └── index.js │ ├── faas.json │ └── README.md ├── hello-oauth-xsuaa │ ├── faas.json │ ├── data │ │ └── credentials.json │ ├── package.json │ ├── lib │ │ ├── index.js │ │ └── auth.js │ ├── test │ │ └── requests.http │ └── README.md └── ce-coffee │ ├── package.json │ ├── lib │ └── index.js │ ├── faas.json │ ├── test │ └── test-fnc.js │ ├── simulate │ └── config.js │ └── README.md ├── .gitignore ├── test ├── echo │ ├── test.http │ ├── package.json │ ├── README.md │ └── server.js └── nanokube │ └── README.md ├── .gitattributes ├── .reuse └── dep5 ├── README.md └── LICENSES └── Apache-2.0.txt /examples/hello-config/data/text: -------------------------------------------------------------------------------- 1 | Hello World! -------------------------------------------------------------------------------- /examples/hello-secret/data/text: -------------------------------------------------------------------------------- 1 | Hello World! -------------------------------------------------------------------------------- /examples/kafka-producer/data/kafka/host: -------------------------------------------------------------------------------- 1 | x -------------------------------------------------------------------------------- /examples/kafka-producer/data/kafka/topic: -------------------------------------------------------------------------------- 1 | x -------------------------------------------------------------------------------- /examples/weather/advanced_ui/data/config/posturl: -------------------------------------------------------------------------------- 1 | xxx -------------------------------------------------------------------------------- /examples/slack-classify-image/data/services/slacktoken: -------------------------------------------------------------------------------- 1 | xxx -------------------------------------------------------------------------------- /examples/weather/basic_ui/private/openweatherapikey: -------------------------------------------------------------------------------- 1 | xxx -------------------------------------------------------------------------------- /examples/slack-classify-image/data/services/leonardoapikey: -------------------------------------------------------------------------------- 1 | xxx -------------------------------------------------------------------------------- /examples/weather/advanced_ui/data/private/openweatherapikey: -------------------------------------------------------------------------------- 1 | xxx -------------------------------------------------------------------------------- /examples/weather/basic_ui/data/private/openweatherapikey: -------------------------------------------------------------------------------- 1 | xxx -------------------------------------------------------------------------------- /examples/kafka-producer/data/xbem/sec/sasl.json: -------------------------------------------------------------------------------- 1 | { 2 | "mechanism": "", 3 | "user": "", 4 | "password": "" 5 | } -------------------------------------------------------------------------------- /examples/hello-config/test/mock/values.yaml: -------------------------------------------------------------------------------- 1 | config-values: 2 | cfg1: 3 | rv.json: 4 | Info: 5 | Success: Nice Test! 6 | -------------------------------------------------------------------------------- /examples/hello-oauth/data/verification.json: -------------------------------------------------------------------------------- 1 | { 2 | "verificationkey": "-----BEGIN PUBLIC KEY----------END PUBLIC KEY-----" 3 | } -------------------------------------------------------------------------------- /examples/hello-secret/test/mock/values.yaml: -------------------------------------------------------------------------------- 1 | secret-values: 2 | sec1: 3 | rv.json: 4 | Info: 5 | Success: Nice Test! 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _* 2 | build/ 3 | node_modules/ 4 | deploy/ 5 | _gen/ 6 | gen/ 7 | .metadata 8 | .idea 9 | npm-debug.log 10 | package-lock.json -------------------------------------------------------------------------------- /examples/s4sdk/data/s4/credentials: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://xxx-api.s4hana.ondemand.com", 3 | "username": "xxx", 4 | "password": "xxx" 5 | } -------------------------------------------------------------------------------- /test/echo/test.http: -------------------------------------------------------------------------------- 1 | POST http://localhost:8080/ 2 | Content-Type: application/json 3 | 4 | { 5 | "id": 999, 6 | "value": "content" 7 | } 8 | 9 | ### 10 | -------------------------------------------------------------------------------- /examples/hello-config/data/rv.json: -------------------------------------------------------------------------------- 1 | { 2 | "Info": { 3 | "Success": "Demo", 4 | "Failure": "Todo" 5 | }, 6 | "Code": { 7 | "Success": "A", 8 | "Failure": "X" 9 | } 10 | } -------------------------------------------------------------------------------- /examples/hello-secret/data/rv.json: -------------------------------------------------------------------------------- 1 | { 2 | "Info": { 3 | "Success": "Demo", 4 | "Failure": "Todo" 5 | }, 6 | "Code": { 7 | "Success": "A", 8 | "Failure": "X" 9 | } 10 | } -------------------------------------------------------------------------------- /examples/s3uploader/data/s3/credentials.json: -------------------------------------------------------------------------------- 1 | { 2 | "bucket": "x", 3 | "access_key_id": "x", 4 | "secret_access_key": "x", 5 | "host": "x", 6 | "region": "x", 7 | "uri": "x", 8 | "username": "x" 9 | } -------------------------------------------------------------------------------- /examples/amqp-echo/data/xbem/cfg/amqp.json: -------------------------------------------------------------------------------- 1 | { 2 | "incoming": { 3 | "inp01": { "sourceAddress": "queue:test01" } 4 | }, 5 | "outgoing": { 6 | "out01": { "targetAddress": "topic:echo" }, 7 | "err01": { "targetAddress": "topic:echo/failed" } 8 | } 9 | } -------------------------------------------------------------------------------- /examples/kafka-producer/data/xbem/cfg/amqp.json: -------------------------------------------------------------------------------- 1 | { 2 | "incoming": { 3 | "inp01": { 4 | "sourceAddress": "queue:kafkapush" 5 | } 6 | }, 7 | "outgoing": { 8 | "err01": { 9 | "targetAddress": "topic:echo/failed" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | 3 | *.md text 4 | *.js text 5 | 6 | *.pdf binary 7 | *.pptx binary 8 | *.jar binary 9 | *.png binary 10 | *.jpg binary 11 | *.PNG binary 12 | *.JPG binary 13 | *.so binary 14 | *.dll binary 15 | *.node binary 16 | *.exe binary 17 | -------------------------------------------------------------------------------- /test/echo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "echo", 3 | "version": "0.0.1", 4 | "description": "Simple ", 5 | "author": "faas@sap.com", 6 | "dependencies": { 7 | "body-parser": "^1.17.2", 8 | "express": "^4.15.3", 9 | "morgan": "^1.8.2" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/echo/README.md: -------------------------------------------------------------------------------- 1 | # Simple echo server for testing 2 | 3 | First, run npm install in directory `test/echo`. 4 | 5 | Then execute `server.js`, providing optionally a fixed port number. 6 | 7 | ``` 8 | node server.js 8080 9 | ``` 10 | 11 | The file `test.http` provides a test request. 12 | 13 | -------------------------------------------------------------------------------- /examples/kafka-producer/data/xbem/sec/dial.json: -------------------------------------------------------------------------------- 1 | { 2 | "uri": "wss://enterprise-messaging-messaging-gateway.cfapps.xxx.ondemand.com/protocols/amqp10ws", 3 | "oa2": { 4 | "endpoint": "https://functions-sub.authentication.eu10.hana.ondemand.com/oauth/token", 5 | "client": "xxx", 6 | "secret": "xxx" 7 | } 8 | } -------------------------------------------------------------------------------- /examples/amqp-echo/data/xbem/cfg/bind.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": [ 3 | { 4 | "filter" : { "incoming": "inp01" }, 5 | "action" : { "function": "amqp-echo", "failure": "reject", "content": "application/octet-stream" }, 6 | "replyTo": { "outgoing": "out01" }, 7 | "errorTo": { "outgoing": "err01" } 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /examples/qrcode-producer/faas.json: -------------------------------------------------------------------------------- 1 | { 2 | "project": "qrcode-producer", 3 | "version": "0.0.1", 4 | "runtime": "nodejs10", 5 | "library": "./lib", 6 | "functions": { 7 | "build-qrcode": { 8 | "module": "iso-time.js" 9 | } 10 | }, 11 | "triggers": { 12 | "build-qrcode": { 13 | "type": "HTTP", 14 | "function": "build-qrcode" 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /examples/kafka-producer/data/xbem/cfg/bind.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": [ 3 | { 4 | "filter": { 5 | "incoming": "inp01" 6 | }, 7 | "action": { 8 | "function": "kafkapush", 9 | "failure": "reject", 10 | "content": "application/octet-stream" 11 | }, 12 | "errorTo": { 13 | "outgoing": "err01" 14 | } 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /examples/call-other-function/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "engines": { 3 | "node": ">=8.10" 4 | }, 5 | "dependencies": {}, 6 | "devDependencies": { 7 | "@sap/faas": ">=0.6.0", 8 | "mocha": "=7.0.0" 9 | }, 10 | "files": [ 11 | "lib", 12 | "faas.json", 13 | "package.json" 14 | ], 15 | "scripts": { 16 | "test": "npm run all-tests", 17 | "all-tests": "node ./node_modules/mocha/bin/_mocha test/**/test*.js --colors -csdlJson --slow 200" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/slack-classify-image/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@slack/client": "5.0.2", 4 | "request": "2.88.2" 5 | }, 6 | "devDependencies": { 7 | "@sap/faas": ">=0.7.0" 8 | }, 9 | "files": [ 10 | "data", 11 | "lib", 12 | "faas.json", 13 | "package.json" 14 | ], 15 | "scripts": { 16 | "test": "npm run all-tests", 17 | "all-tests": "node ./node_modules/mocha/bin/_mocha test/**/test*.js --colors -csdlJson --slow 200" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/call-other-function/faas.json: -------------------------------------------------------------------------------- 1 | { 2 | "project": "chain", 3 | "version": "0.0.1", 4 | "runtime": "nodejs10", 5 | "library": "./lib", 6 | "functions": { 7 | "chain-func1": { 8 | "module": "main.js", 9 | "handler": "f1" 10 | }, 11 | "chain-func2": { 12 | "module": "main.js", 13 | "handler": "f2" 14 | } 15 | }, 16 | "triggers": { 17 | "chain-simple": { 18 | "type": "HTTP", 19 | "function": "chain-func1" 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /examples/hello-secret/faas.json: -------------------------------------------------------------------------------- 1 | { 2 | "project": "hello-secret", 3 | "version": "0.0.1", 4 | "runtime": "nodejs10", 5 | "library": "./lib", 6 | "secrets": { 7 | "sec1": { 8 | "source": "./data" 9 | } 10 | }, 11 | "functions": { 12 | "hello-secret": { 13 | "module": "index.js", 14 | "secrets": [ 15 | "sec1" 16 | ] 17 | } 18 | }, 19 | "triggers": { 20 | "demo": { 21 | "type": "HTTP", 22 | "function": "hello-secret" 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /examples/qrcode-producer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "engines": { 3 | "node": ">=8.10" 4 | }, 5 | "dependencies": { 6 | "qrcode": "1.3.3" 7 | }, 8 | "devDependencies": { 9 | "@sap/faas": ">=0.7.0", 10 | "mocha": "=7.0.0" 11 | }, 12 | "files": [ 13 | "lib", 14 | "faas.json", 15 | "package.json" 16 | ], 17 | "scripts": { 18 | "test": "npm run all-tests", 19 | "all-tests": "node ./node_modules/mocha/bin/_mocha test/**/test*.js --colors -csdlJson --slow 200" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/s3uploader/faas.json: -------------------------------------------------------------------------------- 1 | { 2 | "project": "aws-uploader", 3 | "version": "0.0.1", 4 | "runtime": "nodejs8", 5 | "library": "./lib", 6 | "secrets": { 7 | "s3": { 8 | "source": "./data/s3" 9 | } 10 | }, 11 | "functions": { 12 | "uploader": { 13 | "module": "index.js", 14 | "secrets": [ 15 | "s3" 16 | ] 17 | } 18 | }, 19 | "triggers": { 20 | "uploader-trigger": { 21 | "type": "HTTP", 22 | "function": "uploader" 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /examples/hello-config/faas.json: -------------------------------------------------------------------------------- 1 | { 2 | "project": "hello-config", 3 | "version": "0.0.1", 4 | "runtime": "nodejs10", 5 | "library": "./lib", 6 | "configs": { 7 | "cfg1": { 8 | "source": "./data" 9 | } 10 | }, 11 | "functions": { 12 | "hello-config": { 13 | "module": "index.js", 14 | "configs": [ 15 | "cfg1" 16 | ] 17 | } 18 | }, 19 | "triggers": { 20 | "demo-config": { 21 | "type": "HTTP", 22 | "function": "hello-config" 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /examples/amqp-echo/lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * @namespace Faas 4 | * @typedef {import("@sap/faas").Faas.Event} Faas.Event 5 | * @typedef {import("@sap/faas").Faas.Context} Faas.Context 6 | */ 7 | 8 | /** 9 | * @param {Faas.Event} event 10 | * @param {Faas.Context} context 11 | * @return {Promise<*>|*} 12 | */ 13 | module.exports = function (event, context) { 14 | if (event.ce) { 15 | return event.sendResponseEvent(event.ce); 16 | } else { 17 | return event.data; 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /examples/hello-oauth/faas.json: -------------------------------------------------------------------------------- 1 | { 2 | "project": "hello-oauth", 3 | "version": "0.0.1", 4 | "runtime": "nodejs10", 5 | "library": "./lib", 6 | "secrets": { 7 | "sec-oauth": { 8 | "source" : "./data" 9 | } 10 | }, 11 | "configs": {}, 12 | "functions": { 13 | "fun-oauth": { 14 | "module": "index.js", 15 | "secrets": ["sec-oauth"], 16 | "configs": [] 17 | } 18 | }, 19 | "triggers": { 20 | "trig-oauth": { 21 | "type": "HTTP", 22 | "function": "fun-oauth" 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /examples/hello-config/lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * @namespace Faas 4 | * @typedef {import("@sap/faas").Faas.Event} Faas.Event 5 | * @typedef {import("@sap/faas").Faas.Context} Faas.Context 6 | */ 7 | 8 | /** 9 | * @param {Faas.Event} event 10 | * @param {Faas.Context} context 11 | * @return {Promise<*>} 12 | */ 13 | module.exports = async function (event, context) { 14 | const text = await context.getConfigValueString('cfg1', 'text'); 15 | const rval = await context.getConfigValueJSON('cfg1', 'rv.json'); 16 | return rval.Info.Success; 17 | }; 18 | -------------------------------------------------------------------------------- /examples/hello-secret/lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * @namespace Faas 4 | * @typedef {import("@sap/faas").Faas.Event} Faas.Event 5 | * @typedef {import("@sap/faas").Faas.Context} Faas.Context 6 | */ 7 | 8 | /** 9 | * @param {Faas.Event} event 10 | * @param {Faas.Context} context 11 | * @return {Promise<*>} 12 | */ 13 | module.exports = async function (event, context) { 14 | const text = await context.getSecretValueString('sec1', 'text'); 15 | const rval = await context.getSecretValueJSON('sec1', 'rv.json'); 16 | return rval.Info.Success; 17 | }; 18 | -------------------------------------------------------------------------------- /examples/s4sdk/faas.json: -------------------------------------------------------------------------------- 1 | { 2 | "project": "s4sdk-business-partner", 3 | "version": "0.0.1", 4 | "runtime": "nodejs10", 5 | "library": "./lib", 6 | "secrets": { 7 | "s4-destination": { 8 | "source": "./data/s4" 9 | } 10 | }, 11 | "functions": { 12 | "s4sdk-business-partner": { 13 | "module": "handler.js", 14 | "secrets": [ 15 | "s4-destination" 16 | ] 17 | } 18 | }, 19 | "triggers": { 20 | "s4sdk-business-partner": { 21 | "type": "HTTP", 22 | "function": "s4sdk-business-partner" 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /examples/hello-timer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "1.0.0", 4 | "keywords": [ 5 | "faas", 6 | "example" 7 | ], 8 | "engines": { 9 | "node": ">=8.11.3" 10 | }, 11 | "dependencies": {}, 12 | "devDependencies": { 13 | "@sap/faas": ">=0.7.0", 14 | "mocha": "=7.0.0" 15 | }, 16 | "files": [ 17 | "lib", 18 | "faas.json", 19 | "package.json" 20 | ], 21 | "scripts": { 22 | "test": "npm run all-tests", 23 | "all-tests": "node ./node_modules/mocha/bin/_mocha test/**/test*.js --colors -csdlJson --slow 200" 24 | } 25 | } -------------------------------------------------------------------------------- /examples/hello-world-jwt-auth/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "1.0.0", 4 | "keywords": [ 5 | "faas", 6 | "example" 7 | ], 8 | "engines": { 9 | "node": ">=8.11.3" 10 | }, 11 | "dependencies": {}, 12 | "devDependencies": { 13 | "@sap/faas": ">=0.7.0" 14 | }, 15 | "files": [ 16 | "data", 17 | "lib", 18 | "faas.json", 19 | "package.json" 20 | ], 21 | "scripts": { 22 | "test": "npm run all-tests", 23 | "all-tests": "node ./node_modules/mocha/bin/_mocha test/**/test*.js --colors -csdlJson --slow 200" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/hello-oauth-xsuaa/faas.json: -------------------------------------------------------------------------------- 1 | { 2 | "project": "hello-oauth-xsuaa", 3 | "version": "0.0.1", 4 | "runtime": "nodejs10", 5 | "library": "./lib", 6 | "secrets": { 7 | "sec-oauth": { 8 | "source": "./data" 9 | } 10 | }, 11 | "configs": {}, 12 | "functions": { 13 | "fun-oauth-xsuaa": { 14 | "module": "index.js", 15 | "secrets": [ 16 | "sec-oauth" 17 | ], 18 | "configs": [] 19 | } 20 | }, 21 | "triggers": { 22 | "trig-oauth-xsuaa": { 23 | "type": "HTTP", 24 | "function": "fun-oauth-xsuaa" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /examples/hello-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "1.0.0", 4 | "keywords": [ 5 | "faas", 6 | "example" 7 | ], 8 | "engines": { 9 | "node": ">=8.11.3" 10 | }, 11 | "dependencies": {}, 12 | "devDependencies": { 13 | "@sap/faas": ">=0.7.0", 14 | "mocha": "=7.0.0" 15 | }, 16 | "files": [ 17 | "data", 18 | "lib", 19 | "faas.json", 20 | "package.json" 21 | ], 22 | "scripts": { 23 | "test": "npm run all-tests", 24 | "all-tests": "node ./node_modules/mocha/bin/_mocha test/**/test*.js --colors -csdlJson --slow 200" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/hello-secret/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "1.0.0", 4 | "keywords": [ 5 | "faas", 6 | "example" 7 | ], 8 | "engines": { 9 | "node": ">=8.11.3" 10 | }, 11 | "dependencies": {}, 12 | "devDependencies": { 13 | "@sap/faas": ">=0.7.0", 14 | "mocha": "=7.0.0" 15 | }, 16 | "files": [ 17 | "data", 18 | "lib", 19 | "faas.json", 20 | "package.json" 21 | ], 22 | "scripts": { 23 | "test": "npm run all-tests", 24 | "all-tests": "node ./node_modules/mocha/bin/_mocha test/**/test*.js --colors -csdlJson --slow 200" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/s3uploader/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.0.1", 4 | "keywords": [ 5 | "faas", 6 | "example" 7 | ], 8 | "engines": { 9 | "node": ">=8.12.0" 10 | }, 11 | "dependencies": { 12 | "aws-sdk": "^2.553.0" 13 | }, 14 | "devDependencies": { 15 | "@sap/faas": "^0.7.2", 16 | "jsdoc": "=3.6.2" 17 | }, 18 | "files": [ 19 | "lib", 20 | "faas.json", 21 | "package.json" 22 | ], 23 | "scripts": { 24 | "test": "npm run all-tests", 25 | "all-tests": "node ./node_modules/mocha/bin/_mocha test/**/test*.js --colors -csdlJson --slow 200" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/amqp-echo/faas.json: -------------------------------------------------------------------------------- 1 | { 2 | "project": "amqp-echo", 3 | "version": "0.0.1", 4 | "runtime": "nodejs10", 5 | "library": "./lib", 6 | "configs": { 7 | "amqp-echo": { 8 | "source": "./data/xbem/cfg" 9 | } 10 | }, 11 | "functions": { 12 | "amqp-echo": { 13 | "module": "index.js" 14 | } 15 | }, 16 | "services": { 17 | "my-echo-srv": { 18 | "type": "x", 19 | "instance": "y", 20 | "key": "z" 21 | } 22 | }, 23 | "triggers": { 24 | "amqp-echo": { 25 | "type": "AMQP", 26 | "service": "my-echo-srv", 27 | "config": "amqp-echo" 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /examples/hello-timer/lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * @namespace Faas 4 | * @typedef {import("@sap/faas").Faas.Event} Faas.Event 5 | * @typedef {import("@sap/faas").Faas.Context} Faas.Context 6 | */ 7 | 8 | /** 9 | * @param {Faas.Event} event 10 | * @param {Faas.Context} context 11 | * @return {Promise<*>} 12 | */ 13 | module.exports = async function (event, context) { 14 | const nowUTC = (new Date()).toISOString(); 15 | if (event.ce) { 16 | console.log(event.ce.source, event.ce.time.toISOString()); 17 | } else { 18 | console.log('run at', nowUTC); 19 | } 20 | return nowUTC; 21 | }; 22 | 23 | -------------------------------------------------------------------------------- /examples/kafka-producer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "1.0.0", 4 | "keywords": [ 5 | "faas", 6 | "example" 7 | ], 8 | "engines": { 9 | "node": ">=8.11.3" 10 | }, 11 | "devDependencies": { 12 | "@sap/faas": ">=0.7.2", 13 | "mocha": "=7.0.0" 14 | }, 15 | "files": [ 16 | "data", 17 | "lib", 18 | "faas.json", 19 | "package.json" 20 | ], 21 | "scripts": { 22 | "test": "npm run all-tests", 23 | "all-tests": "node ./node_modules/mocha/bin/_mocha test/**/test*.js --colors -csdlJson --slow 200" 24 | }, 25 | "dependencies": { 26 | "kafkajs": "^1.12.0" 27 | } 28 | } -------------------------------------------------------------------------------- /examples/slack-classify-image/faas.json: -------------------------------------------------------------------------------- 1 | { 2 | "project": "slack", 3 | "version": "0.0.1", 4 | "runtime": "nodejs10", 5 | "library": "./lib", 6 | "secrets": { 7 | "slack-classify-image": { 8 | "source": "./data/services" 9 | } 10 | }, 11 | "functions": { 12 | "slack-handler": { 13 | "module": "slack-event.js" 14 | }, 15 | "slack-classify": { 16 | "module": "classify-image.js", 17 | "secrets": [ 18 | "slack-classify-image" 19 | ] 20 | } 21 | }, 22 | "triggers": { 23 | "slack-handler": { 24 | "type": "HTTP", 25 | "function": "slack-handler" 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /examples/hello-world-jwt-auth/lib/call-fn.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * @namespace Faas 4 | * @typedef {import("@sap/faas").Faas.Event} Faas.Event 5 | * @typedef {import("@sap/faas").Faas.Context} Faas.Context 6 | */ 7 | 8 | /** 9 | * @param {Faas.Event} event 10 | * @param {Faas.Context} context 11 | * @return {Promise<*>} 12 | */ 13 | module.exports = async function (event, context) { 14 | const result = await context.callFunction('hello-world', { 15 | type: 'text/plain', 16 | data: (typeof event.data === 'string' ? event.data : (new Date()).toISOString()) + ' >> ' + context.funcName 17 | }); 18 | return result.data; 19 | }; -------------------------------------------------------------------------------- /examples/slack-classify-image/lib/slack-event.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * @namespace Faas 4 | * @typedef {import("@sap/faas").Faas.Event} Faas.Event 5 | * @typedef {import("@sap/faas").Faas.Context} Faas.Context 6 | */ 7 | 8 | /** 9 | * @param {Faas.Event} event 10 | * @param {Faas.Context} context 11 | * @return {Promise<*>|*} 12 | */ 13 | module.exports = function (event, context) { 14 | if (event.data.challenge) { 15 | return { challenge: event.data.challenge }; 16 | } 17 | context.callFunction('slack-classify', { // do not await 18 | type: 'application/json', 19 | data: event.data 20 | }); 21 | }; 22 | 23 | -------------------------------------------------------------------------------- /examples/amqp-echo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "1.0.0", 4 | "keywords": [ 5 | "faas", 6 | "example" 7 | ], 8 | "engines": { 9 | "node": ">=8.11.3" 10 | }, 11 | "dependencies": {}, 12 | "devDependencies": { 13 | "@sap/faas": ">=0.7.0", 14 | "jsdoc": "=3.6.2", 15 | "mocha": "=7.0.0", 16 | "sinon": "=7.3.2" 17 | }, 18 | "files": [ 19 | "data", 20 | "lib", 21 | "faas.json", 22 | "package.json" 23 | ], 24 | "scripts": { 25 | "test": "npm run all-tests", 26 | "all-tests": "node ./node_modules/mocha/bin/_mocha test/**/test*.js --colors -csdlJson --slow 200" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/s4sdk/lib/handler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * @namespace Faas 4 | * @typedef {import("@sap/faas").Faas.Event} Faas.Event 5 | * @typedef {import("@sap/faas").Faas.Context} Faas.Context 6 | */ 7 | 8 | const { BusinessPartner } = require('@sap/cloud-sdk-vdm-business-partner-service'); 9 | 10 | /** 11 | * @param {Faas.Event} event 12 | * @param {Faas.Context} context 13 | * @return {Promise<*>} 14 | */ 15 | module.exports = async function (event, context) { 16 | return BusinessPartner.requestBuilder() 17 | .getAll() 18 | .top(5) 19 | .execute(await context.getSecretValueJSON('s4-destination', 'credentials')); 20 | }; 21 | -------------------------------------------------------------------------------- /examples/hello-oauth-xsuaa/data/credentials.json: -------------------------------------------------------------------------------- 1 | { 2 | "tenantmode": "", 3 | "sburl": "https://.authentication.sap.hana.ondemand.com", 4 | "clientid": "", 5 | "xsappname": "", 6 | "clientsecret": "", 7 | "url": "https://.authentication.sap.hana.ondemand.com", 8 | "uaadomain": "authentication.sap.hana.ondemand.com", 9 | "verificationkey": "-----BEGIN PUBLIC KEY----------END PUBLIC KEY-----", 10 | "apiurl": "https://api.authentication.sap.hana.ondemand.com", 11 | "identityzone": "", 12 | "identityzoneid": "", 13 | "tenantid": "" 14 | } -------------------------------------------------------------------------------- /examples/s4sdk/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "1.0.0", 4 | "keywords": [ 5 | "faas", 6 | "example" 7 | ], 8 | "engines": { 9 | "node": ">=8.11.3" 10 | }, 11 | "devDependencies": { 12 | "@sap/faas": ">=0.7.0", 13 | "mocha": "=7.0.0" 14 | }, 15 | "files": [ 16 | "data", 17 | "lib", 18 | "faas.json", 19 | "package.json" 20 | ], 21 | "scripts": { 22 | "test": "npm run all-tests", 23 | "all-tests": "node ./node_modules/mocha/bin/_mocha test/**/test*.js --colors -csdlJson --slow 200" 24 | }, 25 | "dependencies": { 26 | "@sap/cloud-sdk-vdm-business-partner-service": "^1.15.0" 27 | } 28 | } -------------------------------------------------------------------------------- /examples/weather/basic_ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "1.0.0", 4 | "keywords": [ 5 | "faas", 6 | "example" 7 | ], 8 | "engines": { 9 | "node": ">=8.11.3" 10 | }, 11 | "dependencies": { 12 | "ejs": "^3.0.1", 13 | "request": "^2.88.0" 14 | }, 15 | "devDependencies": { 16 | "@sap/faas": ">=0.7.0", 17 | "mocha": "=7.0.0" 18 | }, 19 | "files": [ 20 | "data", 21 | "lib", 22 | "faas.json", 23 | "package.json" 24 | ], 25 | "scripts": { 26 | "test": "npm run all-tests", 27 | "all-tests": "node ./node_modules/mocha/bin/_mocha test/**/test*.js --colors -csdlJson --slow 200" 28 | } 29 | } -------------------------------------------------------------------------------- /examples/weather/advanced_ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "1.0.0", 4 | "keywords": [ 5 | "faas", 6 | "example" 7 | ], 8 | "engines": { 9 | "node": ">=8.11.3" 10 | }, 11 | "dependencies": { 12 | "ejs": "^3.0.1", 13 | "request": "^2.88.0" 14 | }, 15 | "devDependencies": { 16 | "@sap/faas": ">=0.7.0", 17 | "mocha": "=7.0.0" 18 | }, 19 | "files": [ 20 | "data", 21 | "lib", 22 | "faas.json", 23 | "package.json" 24 | ], 25 | "scripts": { 26 | "test": "npm run all-tests", 27 | "all-tests": "node ./node_modules/mocha/bin/_mocha test/**/test*.js --colors -csdlJson --slow 200" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/hello-oauth/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "1.0.0", 4 | "description": "sap faas example", 5 | "keywords": [ 6 | "faas", 7 | "example" 8 | ], 9 | "engines": { 10 | "node": ">=10.17.0" 11 | }, 12 | "dependencies": { 13 | "jsonwebtoken": "8.5.1" 14 | }, 15 | "devDependencies": { 16 | "@sap/faas": ">=0.7.0", 17 | "mocha": "=7.0.0" 18 | }, 19 | "files": [ 20 | "data", 21 | "lib", 22 | "faas.json", 23 | "package.json" 24 | ], 25 | "scripts": { 26 | "test": "npm run all-tests", 27 | "all-tests": "node ./node_modules/mocha/bin/_mocha test/**/test*.js --colors -csdlJson --slow 200" 28 | } 29 | } -------------------------------------------------------------------------------- /examples/hello-oauth-xsuaa/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "1.0.0", 4 | "description": "sap faas example", 5 | "keywords": [ 6 | "faas", 7 | "example" 8 | ], 9 | "engines": { 10 | "node": ">=10.17.0" 11 | }, 12 | "dependencies": { 13 | "jsonwebtoken": "8.5.1" 14 | }, 15 | "devDependencies": { 16 | "@sap/faas": ">=0.7.0", 17 | "mocha": "=7.0.0" 18 | }, 19 | "files": [ 20 | "data", 21 | "lib", 22 | "faas.json", 23 | "package.json" 24 | ], 25 | "scripts": { 26 | "test": "npm run all-tests", 27 | "all-tests": "node ./node_modules/mocha/bin/_mocha test/**/test*.js --colors -csdlJson --slow 200" 28 | } 29 | } -------------------------------------------------------------------------------- /examples/ce-coffee/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "1.0.0", 4 | "keywords": [ 5 | "faas", 6 | "example" 7 | ], 8 | "engines": { 9 | "node": ">=8.11.3" 10 | }, 11 | "dependencies": {}, 12 | "devDependencies": { 13 | "@sap/faas": ">=0.7.7", 14 | "@sap/xb-msg-amqp-v100": ">=0.9.38", 15 | "jsdoc": "=3.6.2", 16 | "mocha": "=7.0.0", 17 | "sinon": "=7.3.2" 18 | }, 19 | "files": [ 20 | "data", 21 | "lib", 22 | "faas.json", 23 | "package.json" 24 | ], 25 | "scripts": { 26 | "test": "npm run all-tests", 27 | "all-tests": "node ./node_modules/mocha/bin/_mocha test/**/test*.js --colors -csdlJson --slow 200" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/call-other-function/README.md: -------------------------------------------------------------------------------- 1 | # Example: Function calling another Function 2 | 3 | A function __`chain-func1`__ will call another function __`chain-func2`__. 4 | 5 | ## Deployment 6 | Deploy the complete __`call-other-function`__ sample as a project. 7 | ``` 8 | $ xfsrt-cli faas project deploy -v 9 | ``` 10 | 11 | ## Test 12 | The output received after executing the [deployment](#Deployment) step contains the trigger endpoint. 13 | 14 | Invoke the __`chain-func1`__ function via invoking the HTTP trigger URL. 15 | 16 | 17 | ## License 18 | Copyright (c) 2020 SAP SE or an SAP affiliate company. All rights reserved. 19 | This file is licensed under the Apache Software License, v. 2 except as noted otherwise in the [LICENSE file](../LICENSE.txt). -------------------------------------------------------------------------------- /examples/hello-timer/faas.json: -------------------------------------------------------------------------------- 1 | { 2 | "project": "hello-timer", 3 | "version": "0.0.1", 4 | "runtime": "nodejs10", 5 | "library": "./lib", 6 | "functions": { 7 | "hello-timer": { 8 | "module": "index.js" 9 | } 10 | }, 11 | "triggers": { 12 | "timer1": { 13 | "type": "Timer", 14 | "schedule": "15s", 15 | "timezone": "", 16 | "function": "hello-timer" 17 | }, 18 | "timer2": { 19 | "type": "Timer", 20 | "schedule": "0/15 * * * * *", 21 | "timezone": "", 22 | "function": "hello-timer" 23 | }, 24 | "timer3": { 25 | "type": "Timer", 26 | "schedule": "45 14 * * *", 27 | "timezone": "Europe/Berlin", 28 | "function": "hello-timer" 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /examples/kafka-producer/faas.json: -------------------------------------------------------------------------------- 1 | { 2 | "project": "kafkamessaging", 3 | "version": "0.0.1", 4 | "runtime": "nodejs10", 5 | "library": "./lib", 6 | "configs": { 7 | "kafka": { 8 | "source": "./data/kafka" 9 | }, 10 | "xbemcfg": { 11 | "source": "./data/xbem/cfg" 12 | } 13 | }, 14 | "secrets": { 15 | "xbemsec": { 16 | "source": "./data/xbem/sec" 17 | } 18 | }, 19 | "functions": { 20 | "publisher": { 21 | "module": "index.js", 22 | "handler": "publisher", 23 | "configs": [ 24 | "kafka" 25 | ] 26 | } 27 | }, 28 | "triggers": { 29 | "kafkapush": { 30 | "type": "AMQP", 31 | "config": "xbemcfg", 32 | "secret": "xbemsec" 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /examples/weather/basic_ui/faas.json: -------------------------------------------------------------------------------- 1 | { 2 | "project": "weather", 3 | "version": "0.0.1", 4 | "runtime": "nodejs10", 5 | "library": "./lib", 6 | "secrets": { 7 | "credentials": { 8 | "source": "./data/private" 9 | } 10 | }, 11 | "functions": { 12 | "getweather": { 13 | "module": "index.js", 14 | "handler": "getWeather" 15 | }, 16 | "postweather": { 17 | "module": "index.js", 18 | "handler": "postWeather", 19 | "secrets": [ 20 | "credentials" 21 | ] 22 | } 23 | }, 24 | "triggers": { 25 | "get": { 26 | "type": "HTTP", 27 | "function": "getweather" 28 | }, 29 | "post": { 30 | "type": "HTTP", 31 | "function": "postweather" 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /examples/hello-oauth-xsuaa/lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * @namespace Faas 4 | * @typedef {import("@sap/faas").Faas.Event} Faas.Event 5 | * @typedef {import("@sap/faas").Faas.Context} Faas.Context 6 | */ 7 | 8 | const auth = require('./auth'); 9 | 10 | /** 11 | * @param {Faas.Event} event 12 | * @param {Faas.Context} context 13 | * @return {Promise<*>} 14 | */ 15 | module.exports = async function (event, context) { 16 | let params; 17 | try { 18 | params = await auth.validate(event, context); 19 | } catch (error) { 20 | return error.message; 21 | } 22 | 23 | // start with application logic 24 | 25 | console.log('jwt token payload: ' + JSON.stringify(params, null, 4)); 26 | return { my: 'data' }; 27 | }; 28 | -------------------------------------------------------------------------------- /examples/hello-oauth/lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * @namespace Faas 4 | * @typedef {import("@sap/faas").Faas.Event} Faas.Event 5 | * @typedef {import("@sap/faas").Faas.Context} Faas.Context 6 | */ 7 | 8 | const auth = require('./auth'); 9 | 10 | /** 11 | * @param {Faas.Event} event 12 | * @param {Faas.Context} context 13 | * @return {Promise<*>} 14 | */ 15 | module.exports = async function (event, context) { 16 | let params; 17 | try { 18 | params = await auth.validate(event, context); 19 | } catch (error) { 20 | return error.message; 21 | } 22 | 23 | // start with application logic 24 | 25 | console.log('jwt token payload: ' + JSON.stringify(params, null, 4)); 26 | return { my: 'data' }; 27 | }; 28 | 29 | -------------------------------------------------------------------------------- /examples/qrcode-producer/lib/iso-time.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * @namespace Faas 4 | * @typedef {import("@sap/faas").Faas.Event} Faas.Event 5 | * @typedef {import("@sap/faas").Faas.Context} Faas.Context 6 | */ 7 | 8 | const qr = require('qrcode'); 9 | 10 | /** 11 | * @param {Faas.Event} event 12 | * @param {Faas.Context} context 13 | * @return {Promise<*>} 14 | */ 15 | module.exports = async function (event, context) { 16 | return new Promise((resolve, reject) => { 17 | const nowUTC = (new Date()).toISOString(); 18 | 19 | const stream = event.getResponseStream('image/png'); 20 | stream.on('finish', resolve); 21 | stream.on('error', reject); 22 | 23 | qr.toFileStream(stream, nowUTC); 24 | }); 25 | }; 26 | 27 | -------------------------------------------------------------------------------- /examples/hello-timer/test/unit/test-hello-timer.js: -------------------------------------------------------------------------------- 1 | /*jshint mocha:true*/ 2 | 'use strict'; 3 | 4 | const assert = require('assert'); 5 | const faas = require('@sap/faas'); 6 | 7 | describe('hello timer example', () => { 8 | 9 | // ************************************************************************************************ 10 | 11 | it('using default values', (done) => { 12 | faas.test(done, 13 | { 14 | }, 15 | async (context) => { 16 | const result = await context.callFunction('hello-timer', {}); 17 | assert.equal(result.type, 'text/plain; charset=utf-8'); 18 | } 19 | ); 20 | }); 21 | 22 | // ************************************************************************************************ 23 | 24 | }); 25 | 26 | -------------------------------------------------------------------------------- /examples/hello-world-jwt-auth/faas.json: -------------------------------------------------------------------------------- 1 | { 2 | "project": "hello-world-jwt-auth", 3 | "version": "0.0.1", 4 | "runtime": "nodejs10", 5 | "library": "./lib", 6 | "services": { 7 | "xsuaa-srv": {} 8 | }, 9 | "functions": { 10 | "call-hello-world": { 11 | "module": "call-fn.js" 12 | }, 13 | "hello-world": { 14 | "module": "index.js" 15 | } 16 | }, 17 | "triggers": { 18 | "jwt-auth": { 19 | "type": "HTTP", 20 | "function": "hello-world", 21 | "auth": { 22 | "type": "xsuaa", 23 | "service": "xsuaa-srv" 24 | } 25 | }, 26 | "no-auth": { 27 | "type": "HTTP", 28 | "function": "hello-world" 29 | }, 30 | "call-hello-world": { 31 | "type": "HTTP", 32 | "function": "call-hello-world" 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /examples/hello-world-jwt-auth/lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * @namespace Faas 4 | * @typedef {import("@sap/faas").Faas.Event} Faas.Event 5 | * @typedef {import("@sap/faas").Faas.Context} Faas.Context 6 | */ 7 | 8 | /** 9 | * @param {Faas.Event} event 10 | * @param {Faas.Context} context 11 | * @return {Promise<*>} 12 | */ 13 | module.exports = async function (event, context) { 14 | let msg; 15 | if (event.auth && event.auth.type === '') { 16 | msg = 'hello world w/o auth'; 17 | } else { 18 | msg = 'hello world authenticated'; 19 | } 20 | console.log(msg); 21 | let decodedJsonWebToken = event.decodeJsonWebToken(); 22 | return { 23 | "message": msg, 24 | "caller-data": event.data, 25 | "caller-token-client-id": decodedJsonWebToken.payload["client_id"] 26 | }; 27 | }; 28 | -------------------------------------------------------------------------------- /examples/qrcode-producer/README.md: -------------------------------------------------------------------------------- 1 | # Example: QR Code Producer 2 | 3 | This example deploys a function which generates the current timestamp as an QR code. 4 | The QR code is displayed in a browser window. 5 | 6 | ## Deployment 7 | Deploy the project: 8 | ```bash 9 | xfsrt-cli faas project deploy 10 | ``` 11 | 12 | ## Test 13 | The output received after executing the [deployment](#Deployment) step contains the trigger endpoint. 14 | 15 | Invoke the function `build-qrcode` via invoking the HTTP trigger URL. 16 | 17 | The returned function output should the QR code of the current timestamp. 18 | 19 | 20 | ## License 21 | Copyright (c) 2020 SAP SE or an SAP affiliate company. All rights reserved. 22 | This file is licensed under the Apache Software License, v. 2 except as noted otherwise in the [LICENSE file](../LICENSE.txt). 23 | -------------------------------------------------------------------------------- /examples/hello-oauth-xsuaa/test/requests.http: -------------------------------------------------------------------------------- 1 | ### 2 | 3 | // Get an Json Web Token (JWT) using your xsuaa credentials 4 | // Replace token endpoint (/oauth/token), client id and secret with real values 5 | 6 | POST https://xxx.authentication.sap.hana.ondemand.com/oauth/token 7 | Authorization: Basic 8 | Content-Type: application/x-www-form-urlencoded 9 | 10 | grant_type=client_credentials&scope= 11 | 12 | ### 13 | 14 | // Call the function using the token 15 | // Local (see "faas-sdk run"): http://localhost:8080/fun-oauth/ 16 | // Cloud (Serverless Runtime): https://xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx-faas-http.tenant.prod-ng.test-functions.xfs.cloud.sap/fun-oauth/ 17 | 18 | GET https://xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx-faas-http.tenant.prod-ng.test-functions.xfs.cloud.sap/trig-oauth/ 19 | Authorization: Bearer 20 | 21 | ### -------------------------------------------------------------------------------- /examples/qrcode-producer/test/test-iso-code.js: -------------------------------------------------------------------------------- 1 | /*jshint mocha:true*/ 2 | 'use strict'; 3 | 4 | const assert = require('assert'); 5 | const faas = require('@sap/faas'); 6 | 7 | describe('valid build requests', () => { 8 | 9 | // ************************************************************************************************ 10 | 11 | it('current time in iso format', (done) => { 12 | faas.test(done, 13 | { 14 | 'deploy-values' : '' 15 | }, 16 | async (context) => { 17 | const result = await context.callFunction('build-qrcode', {}); 18 | assert.equal(result.type, 'image/png'); 19 | assert.ok(result.data.length > 0); 20 | } 21 | ); 22 | }); 23 | 24 | // ************************************************************************************************ 25 | 26 | }); 27 | 28 | -------------------------------------------------------------------------------- /examples/weather/advanced_ui/faas.json: -------------------------------------------------------------------------------- 1 | { 2 | "project": "weather", 3 | "version": "0.0.1", 4 | "runtime": "nodejs10", 5 | "library": "./lib", 6 | "secrets": { 7 | "credentials": { 8 | "source": "./data/private" 9 | } 10 | }, 11 | "configs": { 12 | "configs": { 13 | "source": "./data/config" 14 | } 15 | }, 16 | "functions": { 17 | "getweather": { 18 | "module": "index.js", 19 | "handler": "getWeather", 20 | "configs": [ 21 | "configs" 22 | ] 23 | }, 24 | "postweather": { 25 | "module": "index.js", 26 | "handler": "postWeather", 27 | "secrets": [ 28 | "credentials" 29 | ] 30 | } 31 | }, 32 | "triggers": { 33 | "get": { 34 | "type": "HTTP", 35 | "function": "getweather" 36 | }, 37 | "post": { 38 | "type": "HTTP", 39 | "function": "postweather" 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /examples/call-other-function/lib/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * @namespace Faas 4 | * @typedef {import("@sap/faas").Faas.Event} Faas.Event 5 | * @typedef {import("@sap/faas").Faas.Context} Faas.Context 6 | */ 7 | 8 | /** 9 | * @param {Faas.Event} event 10 | * @param {Faas.Context} context 11 | * @return {Promise<*>} 12 | */ 13 | async function first(event, context) { 14 | const result = await context.callFunction('chain-func2', { 15 | type: 'text/plain', 16 | data: (typeof event.data === 'string' ? event.data : (new Date()).toISOString()) + ' >> ' + context.funcName 17 | }); 18 | return result.data; 19 | } 20 | 21 | /** 22 | * @param {Faas.Event} event 23 | * @param {Faas.Context} context 24 | * @return {Promise<*>} 25 | */ 26 | async function second(event, context) { 27 | return event.data + ' >> ' + context.funcName; 28 | } 29 | 30 | module.exports = { 31 | f1: first, 32 | f2: second 33 | }; 34 | 35 | -------------------------------------------------------------------------------- /examples/hello-secret/README.md: -------------------------------------------------------------------------------- 1 | # Example: hello-secret 2 | 3 | This example deploys a function which extracts information from a secret. 4 | 5 | ## Deployment 6 | First, create a deployment file to provide credentials. 7 | Run inside the project directory: 8 | ```bash 9 | faas-sdk init-values -y values.yaml 10 | ``` 11 | Update the generated file. And finally, deploy the project as usual: 12 | ```bash 13 | xfsrt-cli faas project deploy -y ./deploy/values.yaml -v 14 | ``` 15 | 16 | ### Test 17 | The output received after executing the [deployment](#Deployment) step contains the trigger URL. 18 | 19 | Invoke the function `hello-secret` via invoking the HTTP trigger URL. 20 | 21 | The returned function output should be identical to the value specified in `values.yaml` under `secret-values`->`sec1`->`rv.json`->`Info`->`Success` 22 | 23 | 24 | ## License 25 | Copyright (c) 2020 SAP SE or an SAP affiliate company. All rights reserved. 26 | This file is licensed under the Apache Software License, v. 2 except as noted otherwise in the [LICENSE file](../LICENSE.txt). -------------------------------------------------------------------------------- /examples/hello-config/README.md: -------------------------------------------------------------------------------- 1 | # Example: hello-secret 2 | 3 | This example deploys a function which extracts information from a secret. 4 | 5 | ## Deployment 6 | First, create a deployment file to provide credentials. 7 | Run inside the project directory: 8 | ```bash 9 | faas-sdk init-values -y values.yaml 10 | ``` 11 | Update the generated file. And finally, deploy the project as usual: 12 | ```bash 13 | xfsrt-cli faas project deploy -y ./deploy/values.yaml -v 14 | ``` 15 | 16 | ### Test 17 | The output received after executing the [deployment](#Deployment) step contains the trigger endpoint. 18 | 19 | Invoke the function `hello-secret` via invoking the HTTP trigger URL. 20 | 21 | The returned function output should be identical to the value specified in `values.yaml` under `secret-values`->`sec1`->`rv.json`->`Info`->`Success` 22 | 23 | 24 | ## License 25 | Copyright (c) 2020 SAP SE or an SAP affiliate company. All rights reserved. 26 | This file is licensed under the Apache Software License, v. 2 except as noted otherwise in the [LICENSE file](../LICENSE.txt). -------------------------------------------------------------------------------- /examples/call-other-function/test/test-call-other-function.js: -------------------------------------------------------------------------------- 1 | /*jshint mocha:true*/ 2 | 'use strict'; 3 | 4 | const assert = require('assert'); 5 | const faas = require('@sap/faas'); 6 | 7 | describe('call other function example', () => { 8 | 9 | // ************************************************************************************************ 10 | 11 | it('chain-func1 calls chain-func2', (done) => { 12 | faas.test(done, 13 | {}, 14 | async (context) => { 15 | const source = (new Date().toISOString()); 16 | const result = await context.callFunction('chain-func1', { 17 | type: 'text/plain', 18 | data: source 19 | }); 20 | assert.strictEqual(result.type, 'text/plain; charset=utf-8'); 21 | assert.strictEqual(result.data, source + ' >> chain-func1 >> chain-func2'); 22 | } 23 | ); 24 | }); 25 | 26 | // ************************************************************************************************ 27 | 28 | }); -------------------------------------------------------------------------------- /examples/weather/basic_ui/lib/views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Test 7 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |
19 | 20 | 21 |
22 | <% if(weather !== null){ %> 23 |

<%= weather %>

24 | <% } %> 25 | 26 | <% if(error !== null){ %> 27 |

<%= error %>

28 | <% } %> 29 |
30 |
31 | 32 | 33 | -------------------------------------------------------------------------------- /examples/ce-coffee/lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * @namespace Faas 4 | * @typedef {import("@sap/faas").Faas.Event} Faas.Event 5 | * @typedef {import("@sap/faas").Faas.Context} Faas.Context 6 | */ 7 | 8 | /** 9 | * @param {Faas.Event} event 10 | * @param {Faas.Context} context 11 | * @return {Promise<*>|*} 12 | */ 13 | module.exports = function(event, context) { 14 | 15 | if (!event.ce) { 16 | return event.setBadRequest(); 17 | } 18 | console.log(event.ce.source, event.ce.type); 19 | 20 | const reply = Object.assign({}, event.ce); 21 | switch (event.ce.type) { 22 | case 'com.sap.coffee.required': 23 | reply.type = 'com.sap.coffee.produced'; 24 | break; 25 | case 'com.sap.coffee.produced': 26 | reply.type = 'com.sap.coffee.consumed'; 27 | break; 28 | case 'com.sap.coffee.consumed': 29 | return undefined; // return no content 30 | default: 31 | return event.setBadRequest(); 32 | } 33 | 34 | return event.sendResponseEvent(reply); 35 | 36 | }; 37 | -------------------------------------------------------------------------------- /examples/weather/advanced_ui/lib/views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Weatherapp 7 | 8 | 11 | 12 | 13 | 14 |
15 |
16 |

Simple Weather App

17 |
18 | 19 | 20 | 21 |
22 |
23 |
24 |
25 |
26 |
    27 |
    28 |
    29 |
    30 |
    31 |
    32 |
    33 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /test/nanokube/README.md: -------------------------------------------------------------------------------- 1 | # To debug function locally 2 | The helper files based on `kubeless` have been removed. Please use: 3 | * `faas-sdk` to implement Node.js function projects 4 | * `xfsrt-cli` to upload projects to the cloud 5 | 6 | More details can be found [here](https://github.wdf.sap.corp/faas/faas-runtime-nodejs/blob/master/README.md). 7 | 8 | ## Installation 9 | Add the SAP NPM Registry to your npm configuration for all `@sap` scoped modules. 10 | ```bash 11 | npm config set "@sap:registry=https://npm.sap.com" 12 | ``` 13 | 14 | Installation or update: 15 | * Linux 16 | ```bash 17 | sudo npm install @sap/faas -g 18 | ``` 19 | * Windows (as usual user) 20 | ```bash 21 | npm install @sap/faas -g 22 | ``` 23 | 24 | And run: 25 | ``` 26 | faas-sdk version 27 | ``` 28 | to test successful installation. 29 | 30 | ## Advantages 31 | You will be able to: 32 | * init projects 33 | * init deployment files 34 | * run and debug your functions locally 35 | * test secret definitions as well 36 | * test with deployment files 37 | * write unit tests for functions using the runtime API 38 | * work with a local IDE of your choice 39 | -------------------------------------------------------------------------------- /examples/hello-timer/README.md: -------------------------------------------------------------------------------- 1 | # Example: hello-timer 2 | 3 | This example deploys a function `hello-timer` which logs the execution time according to three different schedules. 4 | The schedule for the function invocation is defined with a cron expression for each trigger. 5 | The function is triggered by the timers `timer1`, `timer2`, `timer3` and logs the corresponding name of the trigger and timestamp of the invocation. 6 | 7 | ## Deployment 8 | ```bash 9 | xfsrt-cli faas project deploy -v 10 | ``` 11 | 12 | ## Test 13 | In order to verify that the function `hello-timer` is invoked according to the configurations of the three timer triggers, view the logs using: 14 | ``` 15 | xfsrt-cli faas project logs --functions hello-timer 16 | ``` 17 | 18 | Expected result: 19 | * You should be able to see that e.g. `timer1` causes log entries of format `"/default/sap.xfs.faas/timer1 ` to be written every 15 seconds. 20 | 21 | ## License 22 | Copyright (c) 2020 SAP SE or an SAP affiliate company. All rights reserved. 23 | This file is licensed under the Apache Software License, v. 2 except as noted otherwise in the [LICENSE file](../LICENSE.txt). -------------------------------------------------------------------------------- /examples/ce-coffee/faas.json: -------------------------------------------------------------------------------- 1 | { 2 | "project": "ce-coffee", 3 | "version": "0.0.1", 4 | "runtime": "nodejs10", 5 | "library": "./lib", 6 | "services": { 7 | "my-ems": { 8 | "type": "enterprise-messaging", 9 | "instance": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 10 | "key": "xxx" 11 | } 12 | }, 13 | "functions": { 14 | "ce-coffee-handler": { 15 | "module": "index.js" 16 | } 17 | }, 18 | "triggers": { 19 | "my-ce": { 20 | "type": "CloudEvents", 21 | "service": "my-ems", 22 | "rules": [ 23 | { 24 | "ce-source": "", 25 | "ce-type": "com.sap.coffee.required", 26 | "function": "ce-coffee-handler", 27 | "failure": "accept" 28 | }, 29 | { 30 | "ce-source": "", 31 | "ce-type": "com.sap.coffee.produced", 32 | "function": "ce-coffee-handler", 33 | "failure": "accept" 34 | }, 35 | { 36 | "ce-source": "", 37 | "ce-type": "com.sap.coffee.consumed", 38 | "function": "ce-coffee-handler", 39 | "failure": "accept" 40 | } 41 | ] 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /examples/kafka-producer/lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * @namespace Faas 4 | * @typedef {import("@sap/faas").Faas.Event} Faas.Event 5 | * @typedef {import("@sap/faas").Faas.Context} Faas.Context 6 | */ 7 | 8 | 9 | const { Kafka } = require('kafkajs'); 10 | 11 | /** 12 | * @param {Faas.Event} event 13 | * @param {Faas.Context} context 14 | * @return {Promise} 15 | */ 16 | async function publisher(event, context) { 17 | 18 | const kafkaHost = await context.getConfigValueString('kafka', 'host'); 19 | const topic = await context.getConfigValueString('kafka', 'topic'); 20 | 21 | const message = event.data; 22 | 23 | 24 | const kafka = new Kafka({ 25 | clientId: 'my-app', 26 | brokers: [kafkaHost] 27 | }); 28 | 29 | // The client connects to a Kafka broker 30 | const producer = kafka.producer(); 31 | 32 | const run = async () => { 33 | // Producing 34 | await producer.connect(); 35 | await producer.send({ 36 | topic: topic, 37 | messages: [ 38 | { value: message }, 39 | ], 40 | }); 41 | 42 | }; 43 | 44 | run().catch(console.error); 45 | 46 | } 47 | 48 | module.exports = { 49 | publisher, 50 | }; -------------------------------------------------------------------------------- /examples/ce-coffee/test/test-fnc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @namespace Faas 5 | * @typedef {import("@sap/faas").Faas.test} test 6 | */ 7 | 8 | const assert = require('assert'); 9 | const {describe, it} = require('mocha'); 10 | const {test} = require('@sap/faas'); 11 | 12 | describe('call other function example', () => { 13 | 14 | // ************************************************************************************************ 15 | 16 | it('shall call ce-coffee-handler', (done) => { 17 | test(done, 18 | {}, 19 | async (context) => { 20 | const result = await context.callFunction('ce-coffee-handler', { 21 | type: 'application/cloudevents+json', 22 | data: { 23 | specversion: '1.0', 24 | source: 'sap/faas/demo', 25 | type: 'com.sap.coffee.produced', 26 | id: 'demo', 27 | cause: 'demo', 28 | subject: '', 29 | data: 'espresso', 30 | datacontenttype: 'text/plain' 31 | } 32 | }); 33 | assert.strictEqual(result.type, 'application/cloudevents+json'); 34 | } 35 | ); 36 | }); 37 | 38 | // ************************************************************************************************ 39 | 40 | }); -------------------------------------------------------------------------------- /examples/ce-coffee/simulate/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * The exported object defines the options for the message client. 5 | * Redefine the credentials 6 | * An example program of the client is running. 7 | * Feel free to use the client within your own code. 8 | */ 9 | 10 | module.exports = { 11 | uri: 'wss://enterprise-messaging-messaging-gateway.cfapps.sap.hana.ondemand.com/protocols/amqp10ws', 12 | oa2: { 13 | endpoint: 'https://xxx.authentication.sap.hana.ondemand.com/oauth/token', 14 | client: 'xxx', 15 | secret: 'xxx' 16 | }, 17 | tune: { 18 | ostreamPayloadCopyLimit : 1500 19 | }, 20 | data: { 21 | message : { 22 | payload: { 23 | chunks: [ 24 | Buffer.from('espresso') 25 | ], 26 | properties: { 27 | 'cloudEvents:specversion': '1.0', 28 | 'cloudEvents:source': 'urn:sap:topicNS:sap/test/cs', 29 | 'cloudEvents:type': 'com.sap.coffee.required', 30 | 'cloudEvents:id': 'demo', 31 | 'cloudEvents:cause': 'demo', 32 | 'cloudEvents:subject': '' 33 | }, 34 | type: 'plain/text' 35 | }, 36 | }, 37 | 38 | target : 'topic:sap/test/cs/ce/com/sap/coffee/produced', 39 | 40 | maxCount : 10, 41 | logCount : 1 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /examples/s3uploader/lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * @namespace Faas 4 | * @typedef {import("@sap/faas").Faas.Event} Faas.Event 5 | * @typedef {import("@sap/faas").Faas.Context} Faas.Context 6 | */ 7 | 8 | const AWS = require('aws-sdk'); 9 | 10 | /** 11 | * @param {Faas.Event} event 12 | * @param {Faas.Context} context 13 | * @return {Promise} 14 | */ 15 | module.exports = async function (event, context) { 16 | 17 | let credentials = await context.getSecretValueJSON('s3', 'credentials.json'); 18 | 19 | let s3 = new AWS.S3({ 20 | accessKeyId: credentials.access_key_id, 21 | secretAccessKey: credentials.secret_access_key 22 | }); 23 | 24 | // let body = (event.data && typeof event.data === 'object') ? JSON.stringify(event.data) : event.data; 25 | const contentType = event.getContentType(); 26 | let body; 27 | switch (contentType) { 28 | case 'application/json': 29 | body = JSON.stringify(event.data); 30 | break; 31 | case 'text/plain': 32 | body = event.data; 33 | break; 34 | default: 35 | body = Buffer.from(event.data, 'binary'); 36 | } 37 | let params = { 38 | Bucket: credentials.bucket, 39 | Key: Date.now().toString(), 40 | Body: body, 41 | }; 42 | try { 43 | return await s3.upload(params).promise(); 44 | } catch (e) { 45 | console.log(e); 46 | return e; 47 | } 48 | }; -------------------------------------------------------------------------------- /examples/s3uploader/README.md: -------------------------------------------------------------------------------- 1 | # AWS S3 - Uploader 2 | In this example, an uploader function is deployed. It is triggered by an HTTP trigger, extracts the payload from the request body which it then uploads to a configurable S3 bucket with a timestamp of the current time as its key. 3 | 4 | 5 | ## Requirements 6 | * Access/Credentials for an Amazon Web Services (AWS) S3 bucket, either through a custom account or by using the [`SAP Object Store`](https://help.sap.com/viewer/2ee77ef7ea4648f9ab2c54ee3aef0a29/Cloud/en-US/84eb69a421294ba0a407579490413dfa.html). 7 | 8 | ## Deployment 9 | First, create a deployment file to provide credentials. 10 | 11 | Run inside the project directory: 12 | ```bash 13 | faas-sdk init-values -y values.yaml 14 | ``` 15 | Update the generated file. 16 | Make the following adjustment: 17 | * specify the account credentials for the S3 bucket `s3`->`credentials.json`. 18 | 19 | 20 | Finally, deploy the project as usual. 21 | Run inside the project directory: 22 | ```bash 23 | xfsrt-cli faas project deploy -y ./deploy/values.yaml -v 24 | ``` 25 | 26 | ### Test 27 | The output received after executing the [deployment](#Deployment) step contains the trigger URL. 28 | 29 | Invoke the function `uploader` via HTTP POST request with the HTTP trigger URL, using e.g. `cURL`: 30 | ```bash 31 | curl --header "Content-Type: application/json" --request POST --data '{"hello":"world"}' 32 | ``` 33 | The output should be a JSON containing metadata information about the S3 upload. 34 | -------------------------------------------------------------------------------- /test/echo/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const http = require('http'); 4 | const express = require('express'); 5 | const morgan = require('morgan'); 6 | const bodyParser = require('body-parser'); 7 | 8 | const bodySizeLimit = 1000000; 9 | const bodyMimeTypes = '*/*'; 10 | const localHostPort = process.argv.length > 2 ? parseInt(process.argv[2]) : 0; 11 | 12 | morgan.token('remote-port', (req, res) => req.connection.remotePort); 13 | 14 | const app = express(); 15 | app.use(morgan(':remote-addr :remote-port :method :url :status - :res[content-length] bytes - :res[content-type] - :response-time ms')); 16 | app.use(bodyParser.raw({limit: `${bodySizeLimit}mb`, type:bodyMimeTypes})); 17 | app.use(bodyParser.json({limit: `${bodySizeLimit}mb`})); 18 | app.use(bodyParser.urlencoded({limit:`${bodySizeLimit}mb`, extended: true})); 19 | 20 | app.all('*', (req, res) => { 21 | switch (req.method) { 22 | case 'OPTIONS': 23 | res.header('Access-Control-Allow-Methods', req.headers['access-control-request-method']); 24 | res.header('Access-Control-Allow-Headers', req.headers['access-control-request-headers']); 25 | res.end(); 26 | break; 27 | case 'POST': 28 | res.header('content-type', req.headers['content-type']); 29 | res.status(200).send(req.body); 30 | break; 31 | default: 32 | res.status(500).send(http.STATUS_CODES[500]); 33 | } 34 | }); 35 | 36 | const srv = app.listen(localHostPort); 37 | console.log(`http server listening at port ${srv.address().port}`); 38 | 39 | -------------------------------------------------------------------------------- /examples/s4sdk/README.md: -------------------------------------------------------------------------------- 1 | # S4SDK - Business Partner 2 | The `BusinessPartner` example demonstrated here is described in the [Blog - sap-s4hana-cloud-sdk-for-javascript-beta](https://blogs.sap.com/2018/10/02/writing-an-example-application-using-the-sap-s4hana-cloud-sdk-for-javascript-beta/). 3 | 4 | ## Requirements 5 | * Access/Credentials for an `S4/HANA Cloud` system that provides a `BusinessPartner` OData Service. Alternatively, set up a mock server described [here](https://sap.github.io/cloud-s4-sdk-book/pages/mock-odata.html) and deploy it remotely on your account. Make sure you are logged into your subaccount (using `cf login`) 6 | 7 | ## Deployment 8 | First, create a deployment file to provide credentials. 9 | 10 | Run inside the project directory: 11 | ```bash 12 | faas-sdk init-values -y values.yaml 13 | ``` 14 | Update the generated file. 15 | Make the following adjustment: 16 | * specify the secret credentials for the S4/HANA system under `s4-destination`->`credentials`. 17 | 18 | If the mock server is used, authorization to access is not in place, so user and password can be left out. Only the URL has to be specified which is returned on the console upon deploying the mock server. 19 | 20 | 21 | Finally, deploy the project as usual. 22 | Run inside the project directory: 23 | ```bash 24 | xfsrt-cli faas project deploy -y ./deploy/values.yaml -v 25 | ``` 26 | 27 | ### Test 28 | The output received after executing the [deployment](#Deployment) step contains the trigger URL. 29 | 30 | Invoke the function `s4sdk-business-partner` via invoking the HTTP trigger URL. 31 | 32 | The output should be the first five elements of the `BusinessPartner` Service. 33 | -------------------------------------------------------------------------------- /.reuse/dep5: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: byd-mobile-samples 3 | Upstream-Contact: Alexander Patterer (alexander.patterer@sap.com) 4 | Source: https://github.com/SAP-samples/byd-mobile-samples 5 | Disclaimer: The code in this project may include calls to APIs (“API Calls”) of 6 | SAP or third-party products or services developed outside of this project 7 | (“External Products”). 8 | “APIs” means application programming interfaces, as well as their respective 9 | specifications and implementing code that allows software to communicate with 10 | other software. 11 | API Calls to External Products are not licensed under the open source license 12 | that governs this project. The use of such API Calls and related External 13 | Products are subject to applicable additional agreements with the relevant 14 | provider of the External Products. In no event shall the open source license 15 | that governs this project grant any rights in or to any External Products,or 16 | alter, expand or supersede any terms of the applicable additional agreements. 17 | If you have a valid license agreement with SAP for the use of a particular SAP 18 | External Product, then you may make use of any API Calls included in this 19 | project’s code for that SAP External Product, subject to the terms of such 20 | license agreement. If you do not have a valid license agreement for the use of 21 | a particular SAP External Product, then you may only make use of any API Calls 22 | in this project for that SAP External Product for your internal, non-productive 23 | and non-commercial test and evaluation of such API Calls. Nothing herein grants 24 | you any rights to use or access any SAP External Product, or provide any third 25 | parties the right to use of access any SAP External Product, through API Calls. 26 | 27 | Files: * 28 | Copyright: 2019 SAP SE or an SAP affiliate company and byd-mobile-samples contributors 29 | License: Apache-2.0 30 | -------------------------------------------------------------------------------- /examples/weather/basic_ui/lib/public/css/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | Styles from this codepen: 3 | https://codepen.io/official_naveen/pen/rgknI 4 | */ 5 | 6 | body { 7 | width: 800px; 8 | margin: 0 auto; 9 | font-family: 'Open Sans', sans-serif; 10 | } 11 | .container { 12 | width: 600px; 13 | margin: 0 auto; 14 | } 15 | fieldset { 16 | display: block; 17 | -webkit-margin-start: 0px; 18 | -webkit-margin-end: 0px; 19 | -webkit-padding-before: 0em; 20 | -webkit-padding-start: 0em; 21 | -webkit-padding-end: 0em; 22 | -webkit-padding-after: 0em; 23 | border: 0px; 24 | border-image-source: initial; 25 | border-image-slice: initial; 26 | border-image-width: initial; 27 | border-image-outset: initial; 28 | border-image-repeat: initial; 29 | min-width: -webkit-min-content; 30 | padding: 30px; 31 | } 32 | .ghost-input, p { 33 | display: block; 34 | font-weight:300; 35 | width: 100%; 36 | font-size: 25px; 37 | border:0px; 38 | outline: none; 39 | width: 100%; 40 | -webkit-box-sizing: border-box; 41 | -moz-box-sizing: border-box; 42 | box-sizing: border-box; 43 | color: #4b545f; 44 | background: #fff; 45 | font-family: Open Sans,Verdana; 46 | padding: 10px 15px; 47 | margin: 30px 0px; 48 | -webkit-transition: all 0.1s ease-in-out; 49 | -moz-transition: all 0.1s ease-in-out; 50 | -ms-transition: all 0.1s ease-in-out; 51 | -o-transition: all 0.1s ease-in-out; 52 | transition: all 0.1s ease-in-out; 53 | } 54 | .ghost-input:focus { 55 | border-bottom:1px solid #ddd; 56 | } 57 | .ghost-button { 58 | background-color: transparent; 59 | border:2px solid #ddd; 60 | padding:10px 30px; 61 | width: 100%; 62 | min-width: 350px; 63 | -webkit-transition: all 0.1s ease-in-out; 64 | -moz-transition: all 0.1s ease-in-out; 65 | -ms-transition: all 0.1s ease-in-out; 66 | -o-transition: all 0.1s ease-in-out; 67 | transition: all 0.1s ease-in-out; 68 | } 69 | .ghost-button:hover { 70 | border:2px solid #515151; 71 | } 72 | p { 73 | color: #E64A19; 74 | } -------------------------------------------------------------------------------- /examples/weather/basic_ui/README.md: -------------------------------------------------------------------------------- 1 | # Example: weather with basic ui 2 | 3 | This example deploys two functions that represent a simple webpage. It allows the user to provide the name of a city and gives back the temperature in that city. 4 | 5 | The implementation uses the [OpenWeatherMap API](https://openweathermap.org/api) to retrieve the data and was adapted based upon [this tutorial](https://codeburst.io/build-a-weather-website-in-30-minutes-with-node-js-express-openweather-a317f904897b). 6 | 7 | 8 | ## Requirements 9 | 10 | * You have to create a free account on [OpenWeatherMap.org](https://openweathermap.org/guide). Follow the guide and create an API key which you will need in the [Deployment](#Deployment) section. 11 | 12 | 13 | ## Deployment 14 | First, create a deployment file to provide credentials. 15 | Run inside the project directory: 16 | ```bash 17 | faas-sdk init-values -y values.yaml 18 | ``` 19 | Update the generated file with your API key. And finally, deploy the project as usual: 20 | ```bash 21 | xfsrt-cli faas project deploy -y ./deploy/values.yaml -v 22 | ``` 23 | The deployment contains two functions with each of them having an HTTP trigger attached: `getweather` which is triggered by the `GET` request of the start page, `postweather` for the `POST` request when the form is submitted. 24 | 25 | ### Test 26 | The output received after executing the [deployment](#Deployment) step contains the trigger URLs. 27 | 28 | Copy the URL of the `post` trigger. Navigate to [HTML template file](./lib/views/index.ejs) and paste the URL as the value of the `action` attribute inside the `form` tag. 29 | 30 | Re-deploy the project. 31 | 32 | Invoke the function `getweather` via invoking the `get` HTTP trigger URL in a Browser. 33 | 34 | You should see a web page showing a form to fill in with the name of a city of your choice. Once you click submit, you will get back the current temperature in that city. 35 | 36 | 37 | ## License 38 | Copyright (c) 2020 SAP SE or an SAP affiliate company. All rights reserved. 39 | This file is licensed under the Apache Software License, v. 2 except as noted otherwise in the [LICENSE file](../LICENSE.txt). 40 | -------------------------------------------------------------------------------- /examples/weather/advanced_ui/README.md: -------------------------------------------------------------------------------- 1 | # Example: weather with advanced ui 2 | 3 | This example deploys two functions that represent a simple webpage. It allows the user to provide the name of a city and gives back the temperature in that city. 4 | 5 | The implementation uses the [OpenWeatherMap API](https://openweathermap.org/api) to retrieve the data and was adapted based upon [this tutorial](https://webdesign.tutsplus.com/tutorials/build-a-simple-weather-app-with-vanilla-javascript--cms-33893). 6 | 7 | 8 | ## Requirements 9 | 10 | * You have to create a free account on [OpenWeatherMap.org](openweathermap.org/guide). Follow the guide and create an API key which you will need in the [Deployment](#Deployment) section. 11 | 12 | 13 | ## Deployment 14 | First, create a deployment file to provide credentials. 15 | Run inside the project directory: 16 | ```bash 17 | faas-sdk init-values -y values.yaml 18 | ``` 19 | Update the generated file with your API key. And finally, deploy the project as usual: 20 | ```bash 21 | xfsrt-cli faas project deploy -y ./deploy/values.yaml -v 22 | ``` 23 | The deployment contains two functions with each of them having an HTTP trigger attached: `getweather` which is triggered by the `GET` request of the start page, `postweather` for the `POST` request when the form is submitted. 24 | 25 | ### Test 26 | The output received after executing the [deployment](#Deployment) step contains the trigger URLs. 27 | 28 | Copy the URL of the `post` trigger. Update your configuration in [values.yaml](.deploy/values.yaml) with the URL. 29 | 30 | Re-deploy the project. 31 | 32 | Invoke the function `getweather` via invoking the `get` HTTP trigger URL in a Browser. 33 | 34 | You should see a web page showing a form to fill in with the name of a city of your choice. Once you click submit, the weather for the chosen city will be displayed. In this way, you can add multiple cities and you are also able 35 | to remove the corresponding tile by clicking the delete button in the top right corner. 36 | 37 | 38 | ## License 39 | Copyright (c) 2020 SAP SE or an SAP affiliate company. All rights reserved. 40 | This file is licensed under the Apache Software License, v. 2 except as noted otherwise in the [LICENSE file](../LICENSE.txt). -------------------------------------------------------------------------------- /examples/slack-classify-image/README.md: -------------------------------------------------------------------------------- 1 | # Example: Classify an Image posted in Slack with Leonardo 2 | 3 | A function is triggered by an image upload to a Slack channel. The function receives an event about the image upload. It then invokes the __`SAP Leonardo`__ image classification API with the image and waits for the response. The response itself contains a prediction about the possible content of the image. The function then posts the prediction to the channel. 4 | 5 | ## Requirements 6 | 7 | * A Slack application, for example within a test workspace, is required. 8 | The Slack application should define a bot user and be subscribed to the __`file_shared`__ bot event. 9 | Bot events are used, because image classification should be limited to channels where the bot user is invited. 10 | 11 | 12 | ## Deployment 13 | First, create a deployment file to provide credentials. 14 | Run inside the project directory: 15 | ```bash 16 | faas-sdk init-values -y values.yaml 17 | ``` 18 | Update the generated file: 19 | * An api key for the image classification can be obtained at [Leonardo image classification overview](https://api.sap.com/api/image_classification_api/overview). 20 | * Also specify the Slack bot user token. 21 | 22 | And finally, deploy the project : 23 | ```bash 24 | xfsrt-cli faas project deploy -y ./deploy/values.yaml -v 25 | ``` 26 | 27 | ## Configure Slack Endpoint 28 | Go to the Configuration UI of your Slack application, navigate to __`Event Subscriptions`__ and toggle __`Enable Events`__. Configure the HTTP trigger URL as the corresponding slack bot event request endpoint URL. 29 | The HTTP trigger URL can be retrieved from: 30 | ``` 31 | xfsrt-cli faas project get slack 32 | ``` 33 | The output contains the trigger URL. 34 | 35 | ## Test 36 | Install the Slack application to your workspace. In Slack, create a channel. Invite the bot to your channel. 37 | Click on the attachment upload button inside the channel, select an image of your choice and upload it. 38 | As a response, you should get an `SAP Leonardo` powered probability estimation of what is contained in the image. 39 | 40 | ## License 41 | Copyright (c) 2020 SAP SE or an SAP affiliate company. All rights reserved. 42 | This file is licensed under the Apache Software License, v. 2 except as noted otherwise in the [LICENSE file](../LICENSE.txt). 43 | -------------------------------------------------------------------------------- /examples/hello-oauth/README.md: -------------------------------------------------------------------------------- 1 | # Example: hello-oauth 2 | 3 | This examples deploys a function that when triggered does a `OAuth` token based validation of the request. 4 | If the check confirms that the `Authorization` header carries a valid JSON Web Tokens (`JWT`) token, the function logs the token payload to the console. 5 | 6 | ## Requirements 7 | To run this sample, a custom token validation setup is required. 8 | 9 | For demo purposes the following can be used to generate your own `JWT` (assuming you are in UNIX system environment). 10 | Run outside of the project directory: 11 | ```bash 12 | ssh-keygen -t rsa -b 4096 -m PEM -f jwtRS256.key 13 | # Don't add passphrase 14 | openssl rsa -in jwtRS256.key -pubout -outform PEM -out jwtRS256.key.pub 15 | ``` 16 | This generates two files: 17 | * `jwtRS256.key`: the private key 18 | * `jwtRS256.key.pub`: the public key which you are required to provide as a secret in the [deployment](#Deployment) step 19 | 20 | Go to [jwt.io](https://jwt.io) and choose `RS256` as Algorithm in the dropdown menu. 21 | Paste your public key and private key in the corresponding fields under `Verify Signature`. 22 | This will generate a `JWT` on the left panel that you should save for the final [testing](#Test). 23 | 24 | ## Deployment 25 | 26 | First, create a deployment file to provide the custom verfication key. 27 | Run inside the project directory: 28 | ```bash 29 | faas-sdk init-values -y values.yaml 30 | ``` 31 | Update the generated file with the public key you generated above. And finally, deploy the project: 32 | ```bash 33 | xfsrt-cli faas project deploy -y ./deploy/values.yaml -v 34 | ``` 35 | 36 | ### Test 37 | The output received after executing the [deployment](#Deployment) step contains the trigger URL. 38 | 39 | Setup your HTTP request according to this to trigger the function: 40 | ``` 41 | GET 42 | Authorization: Bearer 43 | ``` 44 | 45 | Check the function log on the command line 46 | ```bash 47 | xfsrt-cli faas project logs hello-oauth --functions fun-oauth 48 | ``` 49 | 50 | The output should contain the token payload. 51 | 52 | ## License 53 | Copyright (c) 2020 SAP SE or an SAP affiliate company. All rights reserved. 54 | This file is licensed under the Apache Software License, v. 2 except as noted otherwise in the [LICENSE file](../LICENSE.txt). -------------------------------------------------------------------------------- /examples/hello-oauth-xsuaa/README.md: -------------------------------------------------------------------------------- 1 | # Example: hello-oauth-xsuaa 2 | 3 | This examples deploys a function that when triggered does a `OAuth` token based validation of the request. 4 | The check confirms that the `Authorization` header carries a valid JSON Web Token (`JWT`), and additionally does `XSUAA` specific validation of the provided credentials information. If all the validations succeed, the function logs the token payload. 5 | 6 | ## Requirements 7 | To run this sample, an instance of __`Authorization & Trust Management @SAP CP`__ of Service Plan `application` is required. 8 | 9 | ## Deployment 10 | 11 | First, create a deployment file to provide credentials. 12 | Run inside the project directory: 13 | ```bash 14 | faas-sdk init-values -y values.yaml 15 | ``` 16 | Update the generated file with your specific __`Authorization & Trust Management`__ instance credentials. And finally, deploy the project: 17 | ```bash 18 | xfsrt-cli faas project deploy -y ./deploy/values.yaml -v 19 | ``` 20 | 21 | ### Test 22 | First, get a `JWT` using your `XSUAA` credentials. 23 | In order to do that, setup an HTTP request like the following: 24 | 25 | ``` 26 | POST https://xxx.authentication.sap.hana.ondemand.com/oauth/token 27 | Content-Type: application/x-www-form-urlencoded 28 | 29 | client_id: 30 | client_secret: 31 | grant_type:client_credentials 32 | response_type:token 33 | ``` 34 | 35 | Replace the token endpoint (`/oauth/token`), client id and secret with the real values corresponding to your __`Authorization & Trust Management`__ instance. 36 | 37 | Once you have retrieved the token, the HTTP trigger URL can be retrieved from: 38 | ```bash 39 | xfsrt-cli faas project get hello-oauth-xsuaa 40 | ``` 41 | The output contains the trigger endpoint. 42 | 43 | Call the function `fun-oauth-xsuaa` using the endpoint with the token: 44 | 45 | ``` 46 | GET 47 | Authorization: Bearer 48 | ``` 49 | 50 | Check the function log on the command line 51 | ```bash 52 | xfsrt-cli faas project logs -n hello-oauth --functions fun-oauth 53 | ``` 54 | 55 | The output should contain the token payload. 56 | 57 | ## License 58 | Copyright (c) 2020 SAP SE or an SAP affiliate company. All rights reserved. 59 | This file is licensed under the Apache Software License, v. 2 except as noted otherwise in the [LICENSE file](../LICENSE.txt). -------------------------------------------------------------------------------- /examples/kafka-producer/README.md: -------------------------------------------------------------------------------- 1 | # Example: Kafka Producer 2 | 3 | A function is triggered by messages via AMQP trigger. 4 | It will produce corresponding messages to a configured topic of a Kafka broker instance. 5 | 6 | ## Deployment 7 | 8 | ### Requirements 9 | * Make sure you have downloaded and gone through the [Kafka Quickstart](https://kafka.apache.org/quickstart) 10 | * A working instance of __`Enterprise Messaging@SAP CP`__ in a space of your subaccount. 11 | * In your messaging instance, have two queues set up for: 12 | 13 | __i__ incoming messages (`inp01`): which are consumed inside the function 14 | 15 | __ii__ one where errors are directed to (`err01`) 16 | 17 | * One topic for addressing: 18 | 19 | __iii__ error messages 20 | 21 | ### Preliminary step 22 | Create a deployment file to provide credentials for topic and queue names and for the Kafka setup. 23 | Run inside the project directory: 24 | ```bash 25 | faas-sdk init-values -y values.yaml 26 | ``` 27 | 28 | Make the following adjustments: 29 | 30 | With respect to __`Enterprise Messaging@SAP CP`__: 31 | 32 | * `sourceAddress` of `inp01` should point to the queue referred to in __i__ 33 | * `targetAddress` of `err01` should point to the topic referred to in __iii__ 34 | 35 | With respect to the Kafka setup: 36 | * `topic`: the topic on which the producer sends the messages 37 | * `host`: the host url of the Kafka broker 38 | 39 | ### Deployment 40 | Finally, deploy the project as usual. 41 | Run inside the project directory: 42 | ```bash 43 | xfsrt-cli faas project deploy -y ./deploy/values.yaml 44 | ``` 45 | 46 | ### Test 47 | Test the setup by sending a sample message to the queue named in `inp01`. Because the amqp trigger `kafkapush` is watching the queue, it will trigger the function `publisher` which connects to the configured Kafka broker, subscribes to the topic and produces messages on it. Check if everything is working correctly by verifying that the messages produced to the topic can be consumed. Use the [consumer script](https://kafka.apache.org/quickstart#quickstart_consume) with your connection credentials in order to see the produced messages. 48 | 49 | 50 | ## License 51 | Copyright (c) 2020 SAP SE or an SAP affiliate company. All rights reserved. 52 | This file is licensed under the Apache Software License, v. 2 except as noted otherwise in the [LICENSE file](../LICENSE.txt). -------------------------------------------------------------------------------- /examples/weather/advanced_ui/lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * @namespace Faas 4 | * @typedef {import("@sap/faas").Faas.Event} Faas.Event 5 | * @typedef {import("@sap/faas").Faas.Context} Faas.Context 6 | */ 7 | 8 | const ejs = require('ejs'); 9 | const request = require('request'); 10 | const fs = require('fs'); 11 | 12 | let app = require('./public/js/app'); 13 | 14 | /** 15 | * @param {Faas.Event} event 16 | * @param {Faas.Context} context 17 | * @return {Promise} 18 | */ 19 | async function getWeather(event, context) { 20 | const [htmlContent, cssContent, posturl] = await Promise.all([readFile(__dirname + '/views/index.ejs'), readFile(__dirname + '/public/css/style.css'), context.getConfigValueString('configs', 'posturl')]); 21 | 22 | console.log(posturl); 23 | let jsUIContent = `let url = "${posturl}"; (${app.toString()})()`; 24 | let htmlRenderized = ejs.render(htmlContent, { cssRender: cssContent, appjsRender: jsUIContent }); 25 | event.setResponseType('text/html'); 26 | return htmlRenderized; 27 | }; 28 | 29 | 30 | /** 31 | * @param {Faas.Event} event 32 | * @param {Faas.Context} context 33 | * @return {Promise} 34 | */ 35 | async function postWeather(event, context) { 36 | let city = event.data.city; 37 | let apiKey = await context.getSecretValueString('credentials', 'openweatherapikey'); 38 | let url = `http://api.openweathermap.org/data/2.5/weather?q=${city}&units=metric&appid=${apiKey}`; 39 | 40 | 41 | const result = await new Promise(function (resolve, reject) { 42 | request(url, function (error, response, body) { 43 | // in addition to parsing the value, deal with possible errors 44 | if (error) { 45 | console.log(error); 46 | resolve({}); 47 | } else { 48 | resolve(body); 49 | } 50 | }); 51 | }); 52 | return result; 53 | }; 54 | 55 | let readFile = (filename) => { 56 | return new Promise(function (resolve, reject) { 57 | fs.readFile(filename, 'utf8', function (err, data) { 58 | if (err) { 59 | reject(err); 60 | } else { 61 | resolve(data); 62 | } 63 | }); 64 | }); 65 | }; 66 | 67 | module.exports = { 68 | getWeather: getWeather, 69 | postWeather: postWeather 70 | }; 71 | 72 | -------------------------------------------------------------------------------- /examples/hello-config/test/unit/test-hello-secret.js: -------------------------------------------------------------------------------- 1 | /*jshint mocha:true*/ 2 | 'use strict'; 3 | 4 | const assert = require('assert'); 5 | const faas = require('@sap/faas'); 6 | 7 | describe('hello secret example', () => { 8 | 9 | // ************************************************************************************************ 10 | 11 | it('using default values', (done) => { 12 | faas.test(done, 13 | { 14 | }, 15 | async (context) => { 16 | const result = await context.callFunction('hello-config', {}); 17 | assert.equal(result.type, 'text/plain; charset=utf-8'); 18 | assert.equal(result.data, 'Demo'); 19 | } 20 | ); 21 | }); 22 | 23 | // ************************************************************************************************ 24 | 25 | it('using deploy values', (done) => { 26 | faas.test(done, 27 | { 28 | 'deploy-values': '../mock/values.yaml' 29 | }, 30 | async (context) => { 31 | const result = await context.callFunction('hello-config', {}); 32 | assert.equal(result.type, 'text/plain; charset=utf-8'); 33 | assert.equal(result.data, 'Nice Test!'); 34 | } 35 | ); 36 | }); 37 | 38 | // ************************************************************************************************ 39 | 40 | it('read config text', (done) => { 41 | faas.test(done, 42 | { 43 | }, 44 | async (context) => { 45 | assert.equal(await context.getConfigValueString('cfg1', 'text'), 'Hello World!'); 46 | } 47 | ); 48 | }); 49 | 50 | // ************************************************************************************************ 51 | 52 | it('read config json', (done) => { 53 | faas.test(done, 54 | { 55 | }, 56 | async (context) => { 57 | assert.deepStrictEqual(await context.getConfigValueJSON('cfg1', 'rv.json'), { 58 | "Info": { 59 | "Success": "Demo", 60 | "Failure": "Todo" 61 | }, 62 | "Code": { 63 | "Success": "A", 64 | "Failure": "X" 65 | } 66 | }); 67 | } 68 | ); 69 | }); 70 | 71 | // ************************************************************************************************ 72 | 73 | }); 74 | 75 | -------------------------------------------------------------------------------- /examples/hello-secret/test/unit/test-hello-secret.js: -------------------------------------------------------------------------------- 1 | /*jshint mocha:true*/ 2 | 'use strict'; 3 | 4 | const assert = require('assert'); 5 | const faas = require('@sap/faas'); 6 | 7 | describe('hello secret example', () => { 8 | 9 | // ************************************************************************************************ 10 | 11 | it('using default values', (done) => { 12 | faas.test(done, 13 | { 14 | }, 15 | async (context) => { 16 | const result = await context.callFunction('hello-secret', {}); 17 | assert.equal(result.type, 'text/plain; charset=utf-8'); 18 | assert.equal(result.data, 'Demo'); 19 | } 20 | ); 21 | }); 22 | 23 | // ************************************************************************************************ 24 | 25 | it('using deploy values', (done) => { 26 | faas.test(done, 27 | { 28 | 'deploy-values': '../mock/values.yaml' 29 | }, 30 | async (context) => { 31 | const result = await context.callFunction('hello-secret', {}); 32 | assert.equal(result.type, 'text/plain; charset=utf-8'); 33 | assert.equal(result.data, 'Nice Test!'); 34 | } 35 | ); 36 | }); 37 | 38 | // ************************************************************************************************ 39 | 40 | it('read secret text', (done) => { 41 | faas.test(done, 42 | { 43 | }, 44 | async (context) => { 45 | assert.equal(await context.getSecretValueString('sec1', 'text'), 'Hello World!'); 46 | } 47 | ); 48 | }); 49 | 50 | // ************************************************************************************************ 51 | 52 | it('read secret json', (done) => { 53 | faas.test(done, 54 | { 55 | }, 56 | async (context) => { 57 | assert.deepStrictEqual(await context.getSecretValueJSON('sec1', 'rv.json'), { 58 | "Info": { 59 | "Success": "Demo", 60 | "Failure": "Todo" 61 | }, 62 | "Code": { 63 | "Success": "A", 64 | "Failure": "X" 65 | } 66 | }); 67 | } 68 | ); 69 | }); 70 | 71 | // ************************************************************************************************ 72 | 73 | }); 74 | 75 | -------------------------------------------------------------------------------- /examples/hello-oauth/lib/auth.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * @typedef {import("@sap/faas").Faas.Event} Faas.Event 4 | * @typedef {import("@sap/faas").Faas.Context} Faas.Context 5 | */ 6 | 7 | const jwt = require('jsonwebtoken'); 8 | const PEM_KEY_STR_BEGIN = '-----BEGIN PUBLIC KEY-----'; 9 | const PEM_KEY_STR_END = '-----END PUBLIC KEY-----'; 10 | const PEM_KEY_LEN_BEGIN = PEM_KEY_STR_BEGIN.length; 11 | const PEM_KEY_LEN_END = PEM_KEY_STR_END.length; 12 | 13 | module.exports = { 14 | validate 15 | }; 16 | 17 | /** 18 | * Validates JWT 19 | * @param {Faas.Event} event 20 | * @param {Faas.Context} context 21 | * @return {Promise} 22 | */ 23 | async function validate(event, context) { 24 | let publicKey; 25 | try { 26 | publicKey = await context.getSecretValueJSON('sec-oauth', 'verification.json'); 27 | } catch (error) { 28 | console.log("could not get secret value:\n" + error); 29 | return error; 30 | } 31 | return new Promise((resolve, reject) => { 32 | switch (event.auth.type) { 33 | case 'Bearer': 34 | break; 35 | case 'Basic': 36 | event.setUnauthorized(); 37 | reject(new Error('"Basic" authentication not supported')); 38 | return; 39 | default: 40 | event.setUnauthorized(); 41 | reject(new Error("no valid authentication header found")); 42 | return; 43 | } 44 | 45 | jwt.verify(event.auth.credentials, preparePublicKey(publicKey.verificationkey), {}, (error, decoded) => { 46 | if (error) { 47 | event.setUnauthorized(); 48 | reject(new Error('authentication failed')); 49 | return; 50 | } 51 | 52 | resolve(decoded); 53 | return; 54 | }); 55 | }); 56 | } 57 | 58 | function preparePublicKey(keyStr) { 59 | 60 | if (keyStr.indexOf('\n') !== -1) { 61 | //already contains newlines -> no further processing 62 | return keyStr; 63 | } 64 | 65 | if (!keyStr.startsWith(PEM_KEY_STR_BEGIN) || !keyStr.endsWith(PEM_KEY_STR_END)) { 66 | //No public key definition - give up 67 | return keyStr; 68 | } 69 | 70 | // restore new lines 71 | 72 | const seg = [PEM_KEY_STR_BEGIN]; 73 | let off = PEM_KEY_LEN_BEGIN; 74 | let end = keyStr.length - PEM_KEY_LEN_END; 75 | for (; off < end; off += 64) { 76 | seg.push(keyStr.substring(off, off + Math.min(64, end - off))); 77 | } 78 | seg.push(PEM_KEY_STR_END); 79 | return seg.join('\n'); 80 | } -------------------------------------------------------------------------------- /examples/amqp-echo/README.md: -------------------------------------------------------------------------------- 1 | # Example: AMQP echo 2 | 3 | A function is triggered by messages via AMQP trigger. 4 | It will return (echo) the received payload. 5 | According to the trigger configuration the function result will be sent as a new message using a given topic. 6 | 7 | ## Deployment 8 | 9 | ### Requirements 10 | * Make sure you are logged into your `Cloud Foundry` subaccount (using `cf login`) 11 | * A working instance of __`Enterprise Messaging @SAP CP`__ in a space of above mentioned subaccount. 12 | * In your messaging instance, have three queues set up for: 13 | 14 | __i__ incoming messages (`inp01`): which are consumed inside the function 15 | 16 | __ii__ messages returned by the function (`out01`) 17 | 18 | __iii__ one where errors are directed to (`err01`) 19 | 20 | * Two topics for addressing: 21 | 22 | __iv__ messages produced by the function 23 | 24 | __v__ error messages 25 | 26 | ### Preliminary step 27 | For this example we will register a service using the xfsrt-cli. This will simplify credential handling required for interacting with enterprise messaging inside the function: 28 | 29 | Given the __`Enterprise Messaging`__ service instance name and binding, run the following command: 30 | 31 | ```bash 32 | xfsrt-cli faas service register -s -b 33 | ``` 34 | 35 | Create a deployment file to provide credentials, topics and queue names. 36 | Run inside the project directory: 37 | ```bash 38 | faas-sdk init-values -y values.yaml 39 | ``` 40 | Update the generated file: 41 | * Make the following adjustments: 42 | * `sourceAddress` of `inp01` should point to the queue referred to in __i__ 43 | * `targetAddress` of `out01` should point to the topic referred to in __iv__ 44 | * `targetAddress` of `err01` should point to the topic referred to in __v__ 45 | 46 | ### Deployment 47 | Finally, deploy the project as usual. 48 | Run inside the project directory: 49 | ```bash 50 | xfsrt-cli faas project deploy -y ./deploy/values.yaml 51 | ``` 52 | 53 | ### Test 54 | Test the setup by sending a sample message to queue `inp01`. Because the amqp trigger `amqp-echo` is watching the queue, it will trigger the function `amqp-echo` which simply returns the received message payload. According to the configuration, the returned result is sent to the topic defined in `out01`. Check if everything is working correctly by verifying that the queue subscribed to the topic received the message. If the returned function result was an error, the message was be sent to topic `err01` and the subscribed queue received it. 55 | 56 | 57 | ## License 58 | Copyright (c) 2020 SAP SE or an SAP affiliate company. All rights reserved. 59 | This file is licensed under the Apache Software License, v. 2 except as noted otherwise in the [LICENSE file](../LICENSE.txt). -------------------------------------------------------------------------------- /examples/weather/basic_ui/lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * @namespace Faas 4 | * @typedef {import("@sap/faas").Faas.Event} Faas.Event 5 | * @typedef {import("@sap/faas").Faas.Context} Faas.Context 6 | */ 7 | 8 | const ejs = require('ejs'); 9 | const request = require('request'); 10 | const fs = require('fs'); 11 | 12 | /** 13 | * @param {Faas.Event} event 14 | * @param {Faas.Context} context 15 | * @return {Promise<*>} 16 | */ 17 | async function getWeather(event, context) { 18 | let [htmlContent, cssContent] = await Promise.all([readFile(__dirname + '/views/index.ejs'), readFile(__dirname + '/public/css/style.css')]); 19 | let htmlRenderized = ejs.render(htmlContent, { cssRender: cssContent, weather: null, error: null }); 20 | event.setResponseType('text/html'); 21 | return htmlRenderized; 22 | } 23 | 24 | /** 25 | * @param {Faas.Event} event 26 | * @param {Faas.Context} context 27 | * @return {Promise<*>} 28 | */ 29 | async function postWeather(event, context) { 30 | let [htmlContent, cssContent] = await Promise.all([readFile(__dirname + '/views/index.ejs'), readFile(__dirname + '/public/css/style.css')]); 31 | 32 | let city = event.data.city; 33 | let apiKey = await context.getSecretValueString('credentials', 'openweatherapikey'); 34 | // req.body.city; 35 | let url = `http://api.openweathermap.org/data/2.5/weather?q=${city}&units=metric&appid=${apiKey}`; 36 | 37 | const htmlOutput = await new Promise(function (resolve, reject) { 38 | request(url, function (error, response, body) { 39 | // in addition to parsing the value, deal with possible errors 40 | if (error) { 41 | console.log(error); 42 | let htmlRenderized = ejs.render(htmlContent, { weather: null, error: 'Error, please try again', cssRender: cssContent }); 43 | resolve(htmlRenderized); 44 | } else { 45 | console.log(response); 46 | let weather = JSON.parse(body); 47 | let htmlRenderized; 48 | if (weather.main === undefined) { 49 | htmlRenderized = ejs.render(htmlContent, { weather: null, error: 'Error, please try again', cssRender: cssContent }); 50 | } else { 51 | let weatherText = `It's ${weather.main.temp} degrees in ${weather.name}!`; 52 | htmlRenderized = ejs.render(htmlContent, { weather: weatherText, error: null, cssRender: cssContent }); 53 | } 54 | resolve(htmlRenderized); 55 | } 56 | }); 57 | }); 58 | event.setResponseType('text/html'); 59 | return htmlOutput; 60 | } 61 | 62 | let readFile = (filename) => { 63 | return new Promise(function (resolve, reject) { 64 | fs.readFile(filename, 'utf8', function (err, data) { 65 | if (err) { 66 | reject(err); 67 | } else { 68 | resolve(data); 69 | } 70 | }); 71 | }); 72 | }; 73 | 74 | module.exports = { 75 | getWeather: getWeather, 76 | postWeather: postWeather 77 | }; 78 | 79 | -------------------------------------------------------------------------------- /examples/ce-coffee/README.md: -------------------------------------------------------------------------------- 1 | # Example: CloudEvent Echo 2 | 3 | The project contains a coffee event handler function and one CloudEvents trigger. 4 | It handles the following event types: 5 | * `com.sap.coffee.required` replying with `com.sap.coffee.produced` 6 | * `com.sap.coffee.produced` replying with `com.sap.coffee.consumed` 7 | * `com.sap.coffee.consumed` not sending any reply 8 | 9 | It carefully avoids sending the same events it receives as this would immediately lead to cyclic event (message) processing as in this example also the event source will be identical. 10 | 11 | ## Requirements 12 | * Login to your `Cloud Foundry` (CF) subaccount (using `cf login`), 13 | * Login to your `FaaS` instance (using `xsfrt login`), 14 | * Select an __`SAP CP Enterprise Messaging`__ (EM) service in your CF space. 15 | 16 | ## Deployment 17 | Register your EM service in your FaaS environment. This will copy credentials from CF service key to a secure store inside FaaS. 18 | So far this is a manual step, an administrative one: 19 | ```bash 20 | xfsrt-cli faas service register -s -b 21 | ``` 22 | Check the result by running. 23 | ```bash 24 | xfsrt-cli faas service list 25 | ``` 26 | This command will list all registred services from your currently connected FaaS environment. 27 | Keep the console window open, you may want to double-check the data in the next step. 28 | 29 | Create a deployment file. 30 | It will apply data to your project while it is deployed, any secret, config or service data that you would __not__ like to see inside git. 31 | By default (if you just provide a file name) the file is created in sub-folder `deploy` which shall be ignored by git. 32 | However, you can also specify an relative or absolute path anywhere on your machine. 33 | 34 | Run inside the project directory: 35 | ```bash 36 | faas-sdk init-values -y values.yaml 37 | ``` 38 | 39 | The file contains now all defaults from your project. Update the generated file: 40 | * Provide your registered service `instance` (uuid) 41 | * Provide your registered service `key` to use (key name) 42 | * You may want to double-check the output of command `xsfrt faas service list` 43 | 44 | Optionally run `npm install` for project `devDependencies`: 45 | * to run a unit test for the function 46 | * to run and debug the function locally 47 | * to see code proposals and better understand the function parameters 48 | 49 | Finally, deploy the project as usual. 50 | Run inside the project directory: 51 | ```bash 52 | xfsrt-cli faas project deploy -y values.yaml 53 | ``` 54 | 55 | ### Test 56 | Test the setup by sending sample events. 57 | Use for example [`@sap/xb-msg-amqp-v100`](https://github.wdf.sap.corp/xs2-xb/xb-msg-amqp-v100), it is already registered under devDependencies: 58 | 59 | * Edit the file `./simulate/config.js`, replace `xxx` by real credentials, further settings as needed 60 | * Run `npm install` to get a local copy of the messaging client 61 | * Run `node ./node_modules/@sap/xb-msg-amqp-v100/examples/producer.js ../../../../simulate/config.js` 62 | 63 | The messaging client project contains more examples. 64 | 65 | ## License 66 | Copyright (c) 2020 SAP SE or an SAP affiliate company. All rights reserved. 67 | This file is licensed under the Apache Software License, v. 2 except as noted otherwise in the [LICENSE file](../LICENSE.txt). -------------------------------------------------------------------------------- /examples/weather/advanced_ui/lib/public/js/app.js: -------------------------------------------------------------------------------- 1 | /*SEARCH BY USING A CITY NAME (e.g. athens) OR A COMMA-SEPARATED CITY NAME ALONG WITH THE COUNTRY CODE (e.g. athens,gr)*/ 2 | module.exports = function () { 3 | const form = document.querySelector(".top-banner form"); 4 | const input = document.querySelector(".top-banner input"); 5 | const msg = document.querySelector(".top-banner .msg"); 6 | const list = document.querySelector(".ajax-section .cities"); 7 | 8 | 9 | form.addEventListener("submit", e => { 10 | e.preventDefault(); 11 | let inputVal = input.value; 12 | 13 | //check if there's already a city 14 | const listItems = list.querySelectorAll(".ajax-section .city"); 15 | const listItemsArray = Array.from(listItems); 16 | 17 | if (listItemsArray.length > 0) { 18 | const filteredArray = listItemsArray.filter(el => { 19 | let content = ""; 20 | if (inputVal.includes(",")) { 21 | if (inputVal.split(",")[1].length > 2) { 22 | inputVal = inputVal.split(",")[0]; 23 | content = el 24 | .querySelector(".city-name span") 25 | .textContent.toLowerCase(); 26 | } else { 27 | content = el.querySelector(".city-name").dataset.name.toLowerCase(); 28 | } 29 | } else { 30 | //athens 31 | content = el.querySelector(".city-name span").textContent.toLowerCase(); 32 | } 33 | return content == inputVal.toLowerCase(); 34 | }); 35 | 36 | if (filteredArray.length > 0) { 37 | msg.textContent = `You already know the weather for ${ 38 | filteredArray[0].querySelector(".city-name span").textContent 39 | } ...otherwise be more specific by providing the country code as well 😉`; 40 | form.reset(); 41 | input.focus(); 42 | return; 43 | } 44 | } 45 | 46 | //ajax here 47 | 48 | fetch(url, { 49 | method: 'POST', headers: { 50 | 'Content-Type': 'application/json' 51 | }, 52 | body: JSON.stringify({ city: inputVal }) 53 | }) 54 | .then(response => response.json()) 55 | .then(data => { 56 | const { main, name, sys, weather } = data; 57 | const icon = `https://openweathermap.org/img/wn/${ 58 | weather[0]["icon"] 59 | }@2x.png`; 60 | 61 | const li = document.createElement("li"); 62 | li.classList.add("city"); 63 | const markup = ` 64 |

    65 | ${name} 66 | ${sys.country} 67 |

    68 |
    ${Math.round(main.temp)}°C
    69 |
    70 | ${
71 |                     weather[0][ 73 |
    ${weather[0]["description"]}
    74 |
    75 | `; 76 | li.innerHTML = markup; 77 | list.appendChild(li); 78 | 79 | //remove element on delete button click 80 | let deleteButton = document.createElement("button"); 81 | deleteButton.classList.add("delete-btn"); 82 | li.appendChild(deleteButton); 83 | deleteButton.onclick = function () { 84 | li.remove(); 85 | }; 86 | }) 87 | .catch(() => { 88 | msg.textContent = "Please search for a valid city 😩"; 89 | }); 90 | 91 | msg.textContent = ""; 92 | form.reset(); 93 | input.focus(); 94 | }); 95 | }; 96 | 97 | 98 | -------------------------------------------------------------------------------- /examples/hello-world-jwt-auth/README.md: -------------------------------------------------------------------------------- 1 | # Example: hello-world-jwt-auth 2 | 3 | This examples deploys two functions and showcases the usage of a Json Web Token (JWT) for authentication purposes. 4 | The first function `hello-world` has two different HTTP triggers attached to it. The first trigger `jwt-auth` has authentication enabled. Once deployed, the authentication service will look for a JWT in the authorization header of the HTTP request. If present, it will try to validate the JWT against a pre-configured custom `XSUAA` instance and will let the request pass when the validity of the token was successfully confirmed. 5 | 6 | The other trigger `no-auth` has no additional authentication enabled. This is to show that a function can be triggered by several triggers, either with authentication or without. 7 | 8 | As a third showcase, another function, `call-hello-world`, is deployed to call `hello-world` - again without any authentication being necessary. 9 | 10 | 11 | ## Requirements 12 | To run this sample, an instance of __`Authorization & Trust Management @SAP CP`__ of Service Plan `application` is required. 13 | 14 | In order to be able to make use of the instance for authentication within the project, the instance has to be registered by doing: 15 | 16 | ```bash 17 | xfsrt-cli faas service register 18 | ``` 19 | 20 | In the response output, choose the `XSUAA` instance that you want to use for authentication. 21 | 22 | ## Deployment 23 | 24 | First, create a deployment file to provide credentials. 25 | Run inside the project directory: 26 | ```bash 27 | faas-sdk init-values -y values.yaml 28 | ``` 29 | Update the generated file with your specific __`Authorization & Trust Management`__ instance credentials under `service-values`. And finally, deploy the project: 30 | ```bash 31 | xfsrt-cli faas project deploy -y ./deploy/values.yaml -v 32 | ``` 33 | 34 | ### Test 35 | First, obtain a JWT using your `XSUAA` credentials. 36 | In order to do that, setup an HTTP request like the following: 37 | 38 | ``` 39 | POST https://xxx.authentication.sap.hana.ondemand.com/oauth/token 40 | Content-Type: application/x-www-form-urlencoded 41 | 42 | client_id: 43 | client_secret: 44 | grant_type:client_credentials 45 | response_type:token 46 | ``` 47 | 48 | Replace the token endpoint (`/oauth/token`), client id and secret with the real values corresponding to your __`Authorization & Trust Management`__ instance. 49 | 50 | Once you have retrieved the token, the HTTP trigger URL can be retrieved from: 51 | ```bash 52 | xfsrt-cli faas project get hello-world-jwt-auth 53 | ``` 54 | The output contains the trigger endpoint for `jwt-auth`. 55 | 56 | Call the function `hello-world` using the endpoint with the token: 57 | 58 | ``` 59 | GET 60 | Authorization: Bearer 61 | ``` 62 | 63 | The JSON output should contain information about the authentication method and the token you just passed to the request. 64 | 65 | You can also check the function log on the command line 66 | ```bash 67 | xfsrt-cli faas project logs hello-world-jwt-auth --functions hello-world 68 | ``` 69 | The output should contain a log stating that the function was called with authentication. 70 | 71 | The endpoint of the other HTTP trigger `no-auth` for the function `hello-world` can also be found using the command mentioned above. 72 | Calling the function using this endpoint, you should be able to trigger the function immediately and see the output without any authentication step beforehand. 73 | 74 | Finally, you can also trigger the function `hello-world` using the other function `call-hello-world` which has its own HTTP trigger `call-hello-world`. When using the corresponding endpoint, the function `call-hello-world` will invoke `hello-world`, passing on the time of invocation and its name in the payload to `hello-world`. As before, the invocation does not require authentication and again retrieving the log of `hello-world` will confirm this as well. 75 | 76 | ## License 77 | Copyright (c) 2020 SAP SE or an SAP affiliate company. All rights reserved. 78 | This file is licensed under the Apache Software License, v. 2 except as noted otherwise in the [LICENSE file](../LICENSE.txt). -------------------------------------------------------------------------------- /examples/slack-classify-image/lib/classify-image.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** @typedef {import("@sap/faas").Faas.Event} Faas.Event 3 | * @typedef {import("@sap/faas").Faas.Context} Faas.Context 4 | */ 5 | 6 | const request = require('request'); 7 | const SlackWebClient = require('@slack/client').WebClient; 8 | 9 | module.exports = classifyImage; 10 | 11 | /** 12 | * @param {Faas.Event} event 13 | * @param {Faas.Context} context 14 | * @return {Promise} 15 | */ 16 | async function classifyImage(event, context) { 17 | 18 | // https://api.sap.com/api/image_classification_api/overview 19 | const LEONARDO_API_KEY = await context.getSecretValueString('slack-classify-image', 'leonardoapikey'); 20 | const LEONARDO_ENDPOINT = 'https://sandbox.api.sap.com/mlfs/api/v2/image/classification'; 21 | const leonardo = { 22 | endpoint: LEONARDO_ENDPOINT, 23 | apiKey: LEONARDO_API_KEY, 24 | }; 25 | 26 | // https://api.slack.com/slack-apps#creating_apps 27 | const SLACK_CLIENT_TOKEN = await context.getSecretValueString('slack-classify-image', 'slacktoken'); 28 | const slack = { 29 | client: new SlackWebClient(SLACK_CLIENT_TOKEN), 30 | token: SLACK_CLIENT_TOKEN, 31 | }; 32 | 33 | let file, cType; 34 | 35 | switch (event.data.event.type) { 36 | case 'file_shared': // https://api.slack.com/events/file_shared 37 | return slack.client.files.info({ file: event.data.event.file_id, count: 0 }) 38 | // check created file 39 | .then((fileDesc) => new Promise((resolve, reject) => { 40 | file = fileDesc.file; 41 | request({ 42 | url: file.url_private, 43 | encoding: null, 44 | headers: { 'Authorization': 'Bearer ' + slack.token } 45 | }, (err, response, body) => { 46 | if (err) reject(err); 47 | cType = response.headers['content-type']; 48 | if (!cType.startsWith('image')) reject(new Error(`file mime type ${cType}not supported`)); 49 | 50 | resolve(body); 51 | }); 52 | })) 53 | // post final image to leonardo 54 | .then((imgFinal) => new Promise((resolve, reject) => { 55 | request.post({ 56 | url: leonardo.endpoint, 57 | headers: { 58 | 'Accept': 'application/json', 59 | 'APIKey': leonardo.apiKey, 60 | }, 61 | formData: { 62 | files: { 63 | value: imgFinal, 64 | options: { 65 | contentType: cType, 66 | filename: file.name 67 | } 68 | } 69 | }, 70 | preambleCRLF: true, 71 | postambleCRLF: true, 72 | 73 | }, (err, response, body) => { 74 | if (err) reject(err); 75 | if (response.statusCode === 200) { 76 | resolve(JSON.parse(body)); 77 | } 78 | else { 79 | reject(); 80 | } 81 | }); 82 | })) 83 | 84 | // create slack message from leonardo prediction 85 | .then((data) => { 86 | const results = (data.predictions.length === 0) ? [] : data.predictions[0].results.reduce(function (result, current) { 87 | result.push(`${current.label} with score ${current.score}`); 88 | return result; 89 | }, []); 90 | const message = `Leonardo thinks image ${file.name} contains ${results.length ? ':\n' + results.join('\n') : ' nothing'}`; 91 | 92 | return slack.client.chat.postMessage({ 93 | channel: file.channels[0], 94 | text: message 95 | }); 96 | }) 97 | 98 | .catch((err) => { 99 | console.log('error during image classification ' + err); 100 | }); 101 | default: 102 | console.log(`unknown event "${event.data.event.type}" received`); 103 | } 104 | 105 | } 106 | 107 | -------------------------------------------------------------------------------- /examples/hello-oauth-xsuaa/lib/auth.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * @typedef {import("@sap/faas").Faas.Event} Faas.Event 4 | * @typedef {import("@sap/faas").Faas.Context} Faas.Context 5 | */ 6 | 7 | const jwt = require('jsonwebtoken'); 8 | const SAP_JWT_TRUST_ACL = JSON.stringify([{ clientid: '*', identityzone: '*' }]); 9 | const PEM_KEY_STR_BEGIN = '-----BEGIN PUBLIC KEY-----'; 10 | const PEM_KEY_STR_END = '-----END PUBLIC KEY-----'; 11 | const PEM_KEY_LEN_BEGIN = PEM_KEY_STR_BEGIN.length; 12 | const PEM_KEY_LEN_END = PEM_KEY_STR_END.length; 13 | 14 | module.exports = { 15 | validate 16 | }; 17 | 18 | /** 19 | * Validates JWT 20 | * @param {Faas.Event} event 21 | * @param {Faas.Context} context 22 | * @return {Promise} 23 | */ 24 | async function validate(event, context) { 25 | let config; 26 | try { 27 | config = await context.getSecretValueJSON('sec-oauth', 'credentials.json'); 28 | } catch (error) { 29 | console.log("could not get secret value:\n" + error); 30 | return error; 31 | } 32 | return new Promise((resolve, reject) => { 33 | switch (event.auth.type) { 34 | case 'Bearer': 35 | break; 36 | case 'Basic': 37 | event.setUnauthorized(); 38 | reject(new Error(`"Basic" authentication not supported`)); 39 | return; 40 | default: 41 | event.setUnauthorized(); 42 | reject(new Error(`no valid authentication header found`)); 43 | return; 44 | } 45 | jwt.verify(event.auth.credentials, preparePublicKey(config.verificationkey), {}, (error, decoded) => { 46 | if (error) { 47 | event.setUnauthorized(); 48 | reject(new Error(`token invalid`)); 49 | return; 50 | } 51 | const sapError = offlineValidationSap(decoded, config); 52 | if (sapError) { 53 | event.setUnauthorized(); 54 | reject(new Error(`token invalid in regards to SAP-specific validation ${sapError}`)); 55 | return; 56 | } 57 | resolve(decoded); 58 | }); 59 | reject(new Error('authentication failed')); 60 | }); 61 | } 62 | 63 | function preparePublicKey(keyStr) { 64 | 65 | if (keyStr.indexOf('\n') !== -1) { 66 | //already contains newlines -> no further processing 67 | return keyStr; 68 | } 69 | 70 | if (!keyStr.startsWith(PEM_KEY_STR_BEGIN) || !keyStr.endsWith(PEM_KEY_STR_END)) { 71 | //No public key definition - give up 72 | return keyStr; 73 | } 74 | 75 | // restore new lines 76 | 77 | const seg = [PEM_KEY_STR_BEGIN]; 78 | let off = PEM_KEY_LEN_BEGIN; 79 | let end = keyStr.length - PEM_KEY_LEN_END; 80 | for (; off < end; off += 64) { 81 | seg.push(keyStr.substring(off, off + Math.min(64, end - off))); 82 | } 83 | seg.push(PEM_KEY_STR_END); 84 | return seg.join('\n'); 85 | } 86 | 87 | function offlineValidationSap(decodedToken, config) { 88 | if (!decodedToken.cid) { 89 | return new Error('Client Id not contained in access token. Giving up!'); 90 | } 91 | if (!decodedToken.zid) { 92 | return new Error('Identity Zone not contained in access token. Giving up!'); 93 | } 94 | 95 | let subscriptionsAllowed = false; 96 | if (decodedToken.cid.indexOf('!t') !== -1 || decodedToken.cid.indexOf('!b') !== -1) { 97 | subscriptionsAllowed = true; 98 | } 99 | if ((decodedToken.cid === config.clientid) && (decodedToken.zid === config.identityzoneid || subscriptionsAllowed === true)) { 100 | return null; 101 | } else if (SAP_JWT_TRUST_ACL) { 102 | let parsedACL; 103 | try { 104 | parsedACL = JSON.parse(SAP_JWT_TRUST_ACL); 105 | } catch (err) { 106 | return new Error(`JWT trust ACL (ACL SAP_JWT_TRUST_ACL):\n${SAP_JWT_TRUST_ACL}\ncould not be parsed successfully.\nError: ${err.message}`); 107 | } 108 | let foundMatch = false; 109 | for (const aclEntry of parsedACL) { 110 | if (((decodedToken.cid === aclEntry.clientid) || ('*' === aclEntry.clientid)) && ((decodedToken.zid === aclEntry.identityzone) || ('*' === aclEntry.identityzone))) { 111 | foundMatch = true; 112 | break; 113 | } 114 | } 115 | if (!foundMatch) { 116 | return new Error(`No match found in JWT trust ACL, found client "${decodedToken.cid}" and identity zone "${decodedToken.zid}", expected client "${config.clientid}" and identity zone "${config.identityzoneid}"`); 117 | } 118 | } else { 119 | if (decodedToken.cid !== config.clientid) { 120 | return new Error(`Client ID mismatch, found "${decodedToken.cid}", expected "${config.clientid}"`); 121 | } else { 122 | return new Error(`Identity zone mismatch, found "${decodedToken.zid}", expected "${config.identityzoneid}"`); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /examples/weather/advanced_ui/lib/public/css/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --bg_main: #0a1f44; 3 | --text_light: #fff; 4 | --text_med: #53627c; 5 | --text_dark: #1e2432; 6 | --red: #ff1e42; 7 | --darkred: #c3112d; 8 | --orange: #ff8c00; 9 | } 10 | 11 | * { 12 | margin: 0; 13 | padding: 0; 14 | box-sizing: border-box; 15 | font-weight: normal; 16 | } 17 | 18 | button { 19 | cursor: pointer; 20 | } 21 | 22 | input { 23 | -webkit-appearance: none; 24 | } 25 | 26 | button, 27 | input { 28 | border: none; 29 | background: none; 30 | outline: none; 31 | color: inherit; 32 | } 33 | 34 | img { 35 | display: block; 36 | max-width: 100%; 37 | height: auto; 38 | } 39 | 40 | ul { 41 | list-style: none; 42 | } 43 | 44 | li .delete-btn { 45 | border:1px solid transparent; 46 | background-color: transparent; 47 | vertical-align: middle; 48 | position: absolute; 49 | top: 0px; 50 | right: 0px; 51 | 52 | } 53 | 54 | .delete-btn:after { 55 | content: "X"; 56 | display: block; 57 | 58 | position: absolute; 59 | top: 10px; 60 | right: 0px; 61 | 62 | width: 15px; 63 | height: 15px; 64 | position: absolute; 65 | background-color: #aaa; 66 | z-index:1; 67 | right: 35px; 68 | float: right; 69 | margin: auto; 70 | padding: 2px; 71 | border-radius: 50%; 72 | text-align: center; 73 | color: white; 74 | font-weight: normal; 75 | font-size: 12px; 76 | box-shadow: 0 0 2px #aaa; 77 | cursor: pointer; 78 | } 79 | 80 | body { 81 | font: 1rem/1.3 "Roboto", sans-serif; 82 | background: -webkit-linear-gradient(to right, #314755, #26a0da); /* Chrome 10-25, Safari 5.1-6 */ 83 | background: linear-gradient(to right, #314755, #26a0da); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */ 84 | color: var(--text_dark); 85 | padding: 50px; 86 | } 87 | 88 | /*CUSTOM VARIABLES HERE*/ 89 | 90 | .top-banner { 91 | color: var(--text_light); 92 | } 93 | 94 | .heading { 95 | font-weight: bold; 96 | font-size: 4rem; 97 | letter-spacing: 0.02em; 98 | padding: 0 0 30px 0; 99 | } 100 | 101 | .top-banner form { 102 | position: relative; 103 | display: flex; 104 | align-items: center; 105 | } 106 | 107 | .top-banner form input { 108 | font-size: 2rem; 109 | height: 40px; 110 | padding: 5px 5px 10px; 111 | border-bottom: 1px solid; 112 | } 113 | 114 | .top-banner form input::placeholder { 115 | color: currentColor; 116 | } 117 | 118 | .top-banner form button { 119 | font-size: 1rem; 120 | font-weight: bold; 121 | letter-spacing: 0.1em; 122 | padding: 15px 20px; 123 | margin-left: 15px; 124 | border-radius: 5px; 125 | background: var(--red); 126 | transition: background 0.3s ease-in-out; 127 | } 128 | 129 | .top-banner form button:hover { 130 | background: var(--darkred); 131 | } 132 | 133 | .top-banner form .msg { 134 | position: absolute; 135 | bottom: -40px; 136 | left: 0; 137 | max-width: 450px; 138 | min-height: 40px; 139 | } 140 | 141 | @media screen and (max-width: 700px) { 142 | .top-banner form { 143 | flex-direction: column; 144 | } 145 | 146 | .top-banner form input, 147 | .top-banner form button { 148 | width: 100%; 149 | } 150 | 151 | .top-banner form button { 152 | margin: 20px 0 0 0; 153 | } 154 | 155 | .top-banner form .msg { 156 | position: static; 157 | max-width: none; 158 | min-height: 0; 159 | margin-top: 10px; 160 | } 161 | } 162 | 163 | .ajax-section { 164 | margin: 50px 0 20px; 165 | } 166 | 167 | .ajax-section .cities { 168 | display: grid; 169 | grid-gap: 32px 20px; 170 | grid-template-columns: repeat(4, 1fr); 171 | } 172 | 173 | @media screen and (max-width: 1000px) { 174 | .ajax-section .cities { 175 | grid-template-columns: repeat(3, 1fr); 176 | } 177 | } 178 | 179 | @media screen and (max-width: 700px) { 180 | .ajax-section .cities { 181 | grid-template-columns: repeat(2, 1fr); 182 | } 183 | } 184 | 185 | @media screen and (max-width: 500px) { 186 | .ajax-section .cities { 187 | grid-template-columns: repeat(1, 1fr); 188 | } 189 | } 190 | 191 | /*CUSTOM VARIABLES HERE*/ 192 | 193 | .ajax-section .city { 194 | position: relative; 195 | padding: 40px 10%; 196 | border-radius: 20px; 197 | background: var(--text_light); 198 | color: var(--text_med); 199 | } 200 | 201 | .ajax-section .city::after { 202 | content: ’’; 203 | width: 90%; 204 | height: 50px; 205 | position: absolute; 206 | bottom: -12px; 207 | left: 5%; 208 | z-index: -1; 209 | opacity: 0.3; 210 | border-radius: 20px; 211 | background: var(--text_light); 212 | } 213 | 214 | .ajax-section figcaption { 215 | margin-top: 10px; 216 | text-transform: uppercase; 217 | letter-spacing: 0.05em; 218 | } 219 | 220 | .ajax-section .city-temp { 221 | font-size: 5rem; 222 | font-weight: bold; 223 | margin-top: 10px; 224 | color: var(--text_dark); 225 | } 226 | 227 | .ajax-section .city sup { 228 | font-size: 0.5em; 229 | } 230 | 231 | .ajax-section .city-name sup { 232 | padding: 0.2em 0.6em; 233 | border-radius: 30px; 234 | color: var(--text_light); 235 | background: var(--orange); 236 | } 237 | 238 | .ajax-section .city-icon { 239 | margin-top: 10px; 240 | width: 100px; 241 | height: 100px; 242 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](https://img.shields.io/badge/STATUS-NOT%20CURRENTLY%20MAINTAINED-red.svg?longCache=true&style=flat) 2 | 3 | # Important Notice 4 | This public repository is read-only and no longer maintained. For the latest sample code repositories, visit the [SAP Samples](https://github.com/SAP-samples) organization. 5 | 6 | # Functions Node.js - Samples for SAP Cloud Platform Serverless Runtime 7 | 8 | [![REUSE status](https://api.reuse.software/badge/github.com/SAP-samples/cloud-function-nodejs-samples)](https://api.reuse.software/info/github.com/SAP-samples/cloud-function-nodejs-samples) 9 | 10 | 11 | ## Description 12 | In the following the capabilities of the sub component **Functions** of the **SAP Cloud Platform Serverless Runtime** (in short: **Functions**) will be demonstrated. Functions provide a cloud-based serverless framework for the development of decoupled and resilient applications and integration flows (using SAP Cloud Integration) to support asynchronous communication principles. 13 | Direct integration with SAP S/4HANA Business Event Handling allows efficient development of innovative and scaling extensions. 14 | 15 | This repository provides documentation and samples of how to implement functions (Node.js) on **SAP Cloud Platform** in the Cloud Foundry environment. Details on each sample application and the covered scenario are described in the table _List of content and sample projects_ below. 16 | 17 | For more details about **Functions** take a look at [Functions on SAP Help portal](https://help.sap.com/viewer/bf7b2ff68518427c85b30ac3184ad215/Cloud/en-US/7b8cc2b0e8d141d6aa37c7dff4d70b82.html). 18 | 19 | ## List of content and sample projects 20 | 21 | |Sample/Content|Scenario|Scenario Description| 22 | |---|---|---| 23 | |[amqp-echo](./examples/amqp-echo)| Basic `AMQP 1.0` example | An echo function with messaging | 24 | |[ce-coffee](./examples/ce-coffee)| Basic `CloudEvents` example | A `CloudEvents` producing function attached to a `CloudEvents` trigger | 25 | |[call-other-function](./examples/call-other-function)| Basic example | A function calls another function | 26 | |[hello-world-jwt-auth](./examples/hello-world-jwt-auth) | OAuth based token validation by HTTP trigger | A valid JWT has to be presented to an HTTP trigger before the function can be triggered | 27 | |[hello-oauth](./examples/hello-oauth) | Basic `OAuth` example | The function is triggered by an HTTP request and does `OAuth` validation of the token using a pre-defined public key | 28 | |[hello-oauth-xsuaa](./examples/hello-oauth-xsuaa) | Advanced `OAuth` example using custom `XSUAA` | The function is triggred by an HTTP request and does `OAuth` validation of the token using a custom `XSUAA` instance | 29 | |[hello-secret](./examples/hello-secret) | Basic example | A function extracts data from a secret | 30 | |[hello-timer](./examples/hello-timer)| Basic example | A function that is triggered according to a CRON expression based schedule | 31 | |[qrcode-producer](./examples/qrcode-producer)| Basic example | A function produces the current timestamp as QR code | 32 | |[s4sdk](./examples/s4sdk)| Advanced example | A function leverages the `SAP Cloud SDK for JavaScript` to interact with the `BusinessPartner` API exposed by an `SAP S/4HANA` system | 33 | |[slack-classify-image](./examples/slack-classify-image)| Advanced example, requires Slack integration | An image post in Slack triggers a function. The function classifies the image via `SAP Leonardo` | 34 | |[weather](./examples/weather)| Advanced example, requires `OpenWeatherMap` account | Two functions representing a simple web page that handles user input and displays the result | 35 | |[kafka-producer](./examples/kafka-producer)| Advanced example, requires Kafka broker instance | A function is triggered by a message and produces a message on a Kafka topic | 36 | 37 | ## Requirements 38 | To run the samples, make sure you have completed the [initial setup](https://help.sap.com/viewer/bf7b2ff68518427c85b30ac3184ad215/Cloud/en-US/80f67e476a8447378a72b3fcfbce8f3e.html) first. 39 | 40 | 1. Install Node.js 41 | 42 | Download and install [Node.js](https://nodejs.org/en/download/)(includes npm). 43 | The Node.js version must be >= 8.12.x. 44 | 45 | 2. Install __xfsrt-cli__ 46 | 47 | Download the binary either from Nexus or from the [CP Tools Page](https://tools.hana.ondemand.com/#cloud). 48 | 49 | 3. Install __faas-sdk__ 50 | 51 | Add the SAP NPM Registry to your npm configuration for all `@sap` scoped modules. 52 | ```bash 53 | npm config set "@sap:registry=https://npm.sap.com" 54 | ``` 55 | 56 | Installation or update: 57 | * Linux 58 | ```bash 59 | sudo npm install @sap/faas -g 60 | ```` 61 | * Windows (as usual user) 62 | ```bash 63 | npm install @sap/faas -g 64 | ```` 65 | 66 | Finally, run: 67 | ```bash 68 | faas-sdk version 69 | ``` 70 | to test successful installation. 71 | 72 | 4. Install CloudFoundry command line tools (CF CLI) 73 | 74 | [Download and install the Cloud Foundry CLI](https://docs.cloudfoundry.org/cf-cli/install-go-cli.html). 75 | 76 | Run the command `cf login`, and [login to your Cloud Foundry environment](https://developers.sap.com/tutorials/hcp-cf-getting-started.html). 77 | 78 | Make sure your **SAP Cloud Platform Serverless Runtime** [service instance](https://help.sap.com/viewer/bf7b2ff68518427c85b30ac3184ad215/Cloud/en-US/06f5c1adeb304f37b88d51dcd30d9a1b.html) and [service key](https://help.sap.com/viewer/bf7b2ff68518427c85b30ac3184ad215/Cloud/en-US/8400ccd0efc94c3096a9468c1e5f63ce.html) exist. 79 | 80 | Further necessary configuration and settings are dependent on the specific sample and are documented there. 81 | 82 | ## Download and Installation 83 | 84 | To download and install the samples just [clone](https://gist.github.com/derhuerst/1b15ff4652a867391f03) this repository using this command: 85 | ```bash 86 | git clone https://github.com/SAP/cloud-function-nodejs-samples 87 | ``` 88 | 89 | For details on how to configure and run the samples please take a look into the README in the corresponding samples directory. 90 | 91 | The file __faas.json__ in each sample directory is used as a manifest. It defines secrets, functions and triggers 92 | for one single project. 93 | 94 | ## Debugging using `Visual Studio Code` 95 | 96 | For debugging purposes, the following template `launch.json` can be adapted and used: 97 | ```json 98 | { 99 | "version": "0.2.0", 100 | "configurations": [ 101 | { 102 | "type": "node", 103 | "request": "launch", 104 | "name": "Launch Program", 105 | "skipFiles": [ 106 | "/**" 107 | ], 108 | "program": "${workspaceFolder}/examples//node_modules/@sap/faas/lib/cli.js", 109 | "cwd": "${workspaceFolder}/examples/", 110 | "args": [ 111 | "run", 112 | "-y", 113 | "values.yaml" 114 | ] 115 | } 116 | ] 117 | } 118 | ``` 119 | 120 | ## Support 121 | This project is _as-is_ with no support, no changes being made. 122 | 123 | ## License 124 | Copyright (c) 2020 SAP SE or an SAP affiliate company. All rights reserved. This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](LICENSES/Apache-2.0.txt) file. 125 | -------------------------------------------------------------------------------- /LICENSES/Apache-2.0.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------