How to create Oracle SMS function from scratch
1 Install fn project
curl -LSs https://raw.githubusercontent.com/fnproject/cli/master/install | sh
2 Generate boilerplate for the future function called send_sms
In order to generate boilerplate for the javascript function (Node.js platform) execute:
fn init --runtime node <function_name> Example: fn init --runtime node send_sms
It gives you the following directory structure:
ls send_sms/ func.js func.yaml package.json
File structure of the Oracle function for Node.js:
- func.js - function code. Can be modified in order to support different SMS/Cloud provider. In order to use other SMS provider it's API or Node.js library shall be used. For example, bandwidth.com SMS provider has it's '@bandwidth/messaging' node.js library. For Twilio SMS provider lib is 'twilio'. Chosen lib has to be included as a dependency to package.json. If provider has only API available any http client for node.js can be used, like native or external (axios).
- func.yaml - configuration of the function, generated automatically. Can be extended by 'config' key, used to pass environmental variables to the function (TWILIO_LOG_LEVEL in the example). Additional info can be found here: https://fnproject.io/tutorials/basics/UsingRuntimeContext/#
- package.json - configuration of the Node.js application. Generated automatically. Have to be extended by required dependencies.
Dependencies shall be installed during function development. For example, for Twilio provider available lib is 'twilio'. To install 'twilio' dependency the following command shall be executed:
npm install twilio
After package installation (in our case 'twilio') newest available package version (in our case 3.82.2) is automatically added to package.json, e.g:
"dependencies": { "@fnproject/fdk": "^0.0.41", "twilio": "^3.82.2" }
If SMS provider provides only HTTP api, axios or native node.js http client can be installed and used.
After installing axios
npm install axios
package.json dependencies will look like this:
"dependencies": { "@fnproject/fdk": "^0.0.41", "axios": "^1.2.1" }
Axios can be used according to its docs: https://www.npmjs.com/package/axios
3 Modify these files according to provided example
Example of the Oracle SMS cloud function for the Twilio provider written in javascript (Node.js)
Provided example of the Oracle function implements described earlier SMS Function interface, e.g. it accepts input and returns output as HTTP response.
/* * Комерційна таємниця / Proprietary and confidential to PortaOne, Inc. */ const fdk = require('@fnproject/fdk'); const HTTP_CODE_OK = 200; const HTTP_CODE_BAD_REQUEST = 400; const HTTP_CODE_UNAUTHORIZED = 401; const HTTP_CODE_INTERNAL_ERROR = 500; /** * Send SMS via Twilio * @param input, object, request body * input.auth_info.login, string, The user login. Note: for the SMS provider Twilio, this is the Account SID value that can be found in the Account Info section. * input.auth_info.token, string, The user token. Note: for the SMS provider Twilio it can be found in the Account Info section. * input.src_phone, string, The phone number SMS are sent from. Note: for the SMS provider Twilio, this phone can be found in the Account Info section. * input.dst_phone, string, The phone number of the SMS reciever. * input.message, string, The text of the message to send. * @param ctx, object, context object ot the @fnproject/fdk, docs https://www.npmjs.com/package/@fnproject/fdk * * @return object, response body: * object.success, boolean, True in case of success, false - otherwise. * object.error, string, Contains error message in case of error. */ const sendSMS = async (input, ctx) => { const isTwilio = isTwilioInput(input); if (isTwilio.error) { return setStatus(isTwilio, ctx); } const { token, login, dst_phone, src_phone, message } = isTwilio; try { const client = require('twilio')(login, token); const result = await client.messages.create({ body: message, from: src_phone, to: dst_phone }); if (!result) { return setStatus({ error: 'Internal server error.' }, ctx); } if (result.errorCode) { return setStatus({ error: result.errorMessage, status: result.status || HTTP_CODE_INTERNAL_ERROR }, ctx); } return setStatus({ status: HTTP_CODE_OK }, ctx); } catch (error) { return setStatus({ error: error.toString(), status: error.status || HTTP_CODE_INTERNAL_ERROR }, ctx); } return setStatus(isTwilio, ctx); } /** * Check whether message body is correct for Twilio * @param input, object, request body, see sendSMS method input. * @return object: * object.login, string. * object.token, string. * object.src_phone, string. * object.dst_phone, string. * object.message, string. * object.error, string, Contains error message in case of validation error. * object.status, int, The HTTP error code. */ const isTwilioInput = (input) => { const { dst_phone, src_phone, message, auth_info } = input; if (!auth_info) { return { error: 'The auth info is missing.', status: HTTP_CODE_UNAUTHORIZED }; } const errors = { token: { error: 'The Twilio token is missing.', status: HTTP_CODE_UNAUTHORIZED }, login: { error: 'The login is missing. The login should be equal to the Twilio account SID.', status: HTTP_CODE_UNAUTHORIZED }, dst_phone: { error: 'The destination phone number is missing.', status: HTTP_CODE_BAD_REQUEST }, src_phone: { error: 'The source phone number is missing. This should be the Twilio phone number taken from the personal info of the account.', status: HTTP_CODE_BAD_REQUEST }, message: { error: 'The message is missing.', status: HTTP_CODE_BAD_REQUEST } }; for (let key in errors) { if ( !input[key] && !auth_info[key] ) { return errors[key]; } } const { login, token, password } = auth_info; return { token, login, dst_phone, src_phone, message }; } /** * Set HTTP response status and success flag * @param result, object, Response body * result.status, int, Status code of the resulting response. * result.error, string, The text of the error message in case of error. * @param ctx, object, Context object ot the @fnproject/fdk, docs https://www.npmjs.com/package/@fnproject/fdk. * @return result: * result.success, boolean, True in case of success, false - otherwise. * result.error, string, The text of the error message in case of error. */ const setStatus = (result, ctx) => { const gateway = ctx.httpGateway; if (result.error) { gateway.statusCode = result.status || HTTP_CODE_BAD_REQUEST; result.success = false; delete result.status; } else { gateway.statusCode = result.status || HTTP_CODE_OK; result.success = true; delete result.status; } return result; } fdk.handle(sendSMS);
schema_version: 20180708 name: send_sms version: 0.0.1 runtime: node build_image: fnproject/node:14-dev run_image: fnproject/node:14 entrypoint: node func.js config: TWILIO_LOG_LEVEL: none
{ "name": "send_sms", "version": "0.0.1", "description": "Function to send SMS via Twilio provider", "main": "func.js", "author": "PortaOne, Inc.", "license": "Apache-2.0", "dependencies": { "@fnproject/fdk": "^0.0.41", "twilio": "^3.82.2" } }
How to test created Oracle Function locally
Created Oracle function can be tested locally without having oci (Oracle Cloud Infrastructure) tool installed.
In order to test Oracle function docker must be installed. It can be done according to its doc: https://docs.docker.com/engine/install/ubuntu/ .
Oracle use fn project to run its functions. Docs: https://fnproject.io/tutorials/#TestandMonitorFunctions
Generic steps is following:
1 Check available contexts
fn list context CURRENT NAME PROVIDER API URL REGISTRY * PortaOne-Development oracle https://functions.eu-frankfurt-1.oraclecloud.com fra.ocir.io/porta1saas1production/twilio default default http://localhost:8080
2 Use default context:
fn use context default
3 Start local fn server in the background (or in the separate terminal window fn start, without &):
fn start &
4 Create local app
fn create app <app_name> Example: fn create app test_app
5 Go to the directory with created function
cd send_sms/
6 Deploy current function where you are located in the test_app locally
fn --verbose deploy --app <app_name> --local Example: fn --verbose deploy --app test_app --local
7 Check apps
fn list apps NAME ID test_app 01GMAS0Y65NG8G00GZJ0000001
8 Check function availability
fn list functions <app_name> Example: fn list functions test_app NAME IMAGE ID send_sms send_sms:0.0.3 01GMAS56Y9NG8G00GZJ0000001
9 Invoke created function via fn project cmd:
echo -n '{json input}' | fn invoke <app_name> <function_name> --content-type application/json Example: echo -n '{ "auth_info" : { "token": "bbaabe3cc16e3578ec10d047a7d00000", "password": null }, "src_phone": "+12069658727", "dst_phone": "+380663333333", "message": "Hello from oracle" }' | fn invoke test_app send_sms --content-type application/json {"error":"The login is missing. The login should be equal to the Twilio account SID.","success":false}
10 Invoke the same function via local endpoint.
10.1 Get endpoint
fn inspect function <app_name> <function_name> Example: fn inspect function test_app send_sms { "annotations": { "fnproject.io/fn/invokeEndpoint": "http://localhost:8080/invoke/01GMAS56Y9NG8G00GZJ0000001" }, "app_id": "01GMAS0Y65NG8G00GZJ0000001", "config": { "TWILIO_LOG_LEVEL": "none" }, "created_at": "2022-12-15T11:36:47.817Z", "id": "01GMAS56Y9NG8G00GZJ0000001", "idle_timeout": 30, "image": "send_sms:0.0.3", "memory": 128, "name": "send_sms", "timeout": 30, "updated_at": "2022-12-15T11:36:47.817Z" }
10.2 Invoke function via endpoint
curl -X "POST" -H "Content-Type: application/json" <function_endpoint> -d'{json_input}' Example: curl -X "POST" -H "Content-Type: application/json" http://localhost:8080/invoke/01GMAS56Y9NG8G00GZJ0000001 -d'{ "auth_info" : { "token": "bbaabe3cc16e3578ec10d047a7d08728", "password": null }, "src_phone": "+12069658727", "dst_phone": "+380995000000", "message": "Hello from oracle" }' {"error":"The login is missing. The login should be equal to the Twilio account SID.","success":false}
11 Stop local fn server:
fn stop