Conversational UI with Oracle Digital Assistant and Fn Project. Part III, Moving to the cloud
In this post I am going to continue the story of implementing a conversational UI for FlexDeploy on top of Oracle Digital Assistant and
Fn Project. Today I am going to move the serverless API working around my chatbot to the cloud, so the entire solution is working in the cloud:
The API is implemented as a set of Fn functions collected into an Fn application. The beauty of Fn is that it’s just a bunch of Docker containers that can equally run on your laptop on your local Docker engine and somewhere in the cloud. Having said that I can run my Fn application on a K8s cluster from any cloud provider as it is described here. But today is not that day. Today I am going to run my serverless API on a brand new cloud service Oracle Functions which is built on top of Fn. The service is not general available yet, but I participate in the Limited Availability program so I have a trial access to it, I can play with it and blog about it. In this solution I had to get rid of the Fn Flow implemented here and get back to my original implementation as Fn Flow is not supported by Oracle Functions yet. I hope it will be soon as this is actually the best part.
So, having our OCI environment configured and havingOracle Functions service up and running (I am not reposting Oracle tutorial on that here), we need to configure our Fn CLI to be able to communicate with the service:
fn create context oracle_fn --provider oracle fn use context oracle_fn fn update context oracle.compartment-id MY_COMPARTMENT_ID fn update context api-url https://functions.us-phoenix-1.oraclecloud.com fn update context registry phx.ocir.io/flexagonoraclecloud/flexagon-repo fn update context oracle.profile oracle_fn
Ok, so now our Fn command line interface is talking to Oracle Functions. The next step is to create an application in theOracle Functions console:
Now we can deploy the Fn application toOracle Functions:
Eugenes-MacBook-Pro-3:fn fedor$ ls -l total 8 -rw-r--r--@ 1 fedor staff 12 Dec 4 15:41 app.yaml drwxr-xr-x 5 fedor staff 160 Feb 9 15:24 createsnapshotfn drwxr-xr-x 6 fedor staff 192 Feb 9 15:25 receiveFromBotFn drwxr-xr-x 6 fedor staff 192 Feb 9 15:25 sendToBotFn Eugenes-MacBook-Pro-3:fn fedor$ Eugenes-MacBook-Pro-3:fn fedor$ Eugenes-MacBook-Pro-3:fn fedor$ fn deploy --all
Having done that we can observe the application in theOracle Functions console:
The next step is to update API urls in the chatbot and on my laptop so the functions in the cloud are invoked instead of the previous local implementation. The urls can be retrieved with the following command:
fn list triggers odaapp
So far the migration from my laptop to Oracle Functions has been looking pretty nice and easy. But here is a little of pain. In order to invoke functions hosted in Oracle Functions with http requests, the requests should be signed so they can pass through the authentication. A node.js implementation of invoking a signed function call looks like this:
var fs = require('fs'); var https = require('https'); var os = require('os'); var httpSignature = require('http-signature'); var jsSHA = require("jssha"); var tenancyId = "ocid1.tenancy.oc1..aaaaaaaayonz5yhpr4vxqpbdof5rn7x5pfrlgjwjycwxasf4dkexiq"; var authUserId = "ocid1.user.oc1..aaaaaaaava2e3wd3cu6lew2sktd6by5hnz3d7prpgjho4oambterba"; var keyFingerprint = "88:3e:71:bb:a5:ea:68:b7:56:fa:3e:5d:ea:45:60:10"; var privateKeyPath = "/Users/fedor/.oci/functions_open.pem"; var privateKey = fs.readFileSync(privateKeyPath, 'ascii'); var identityDomain = "identity.us-ashburn-1.oraclecloud.com"; function sign(request, options) { var apiKeyId = options.tenancyId + "/" + options.userId + "/" + options.keyFingerprint; var headersToSign = [ "host", "date", "(request-target)" ]; var methodsThatRequireExtraHeaders = ["POST", "PUT"]; if(methodsThatRequireExtraHeaders.indexOf(request.method.toUpperCase()) !== -1) { options.body = options.body || ""; var shaObj = new jsSHA("SHA-256", "TEXT"); shaObj.update(options.body); request.setHeader("Content-Length", options.body.length); request.setHeader("x-content-sha256", shaObj.getHash('B64')); headersToSign = headersToSign.concat([ "content-type", "content-length", "x-content-sha256" ]); } httpSignature.sign(request, { key: options.privateKey, keyId: apiKeyId, headers: headersToSign }); var newAuthHeaderValue = request.getHeader("Authorization").replace("Signature ", "Signature version=\"1\","); request.setHeader("Authorization", newAuthHeaderValue); } function handleRequest(callback) { return function(response) { var responseBody = ""; response.on('data', function(chunk) { responseBody += chunk; }); response.on('end', function() { callback(JSON.parse(responseBody)); }); } } function createSnapshot(release) { var body = release; var options = { host: 'af4qyj7yhva.us-phoenix-1.functions.oci.oraclecloud.com', path: '/t/createsnapshotfn', method: 'POST', headers: { "Content-Type": "application/text", } }; var request = https.request(options, handleRequest(function(data) { console.log(data); })); sign(request, { body: body, privateKey: privateKey, keyFingerprint: keyFingerprint, tenancyId: tenancyId, userId: authUserId }); request.end(body); };
This approach should be used by Oracle Digital Assistant custom components and by the listener component on my laptop while invoking the serverless API hosted inOracle Functions.
That’s it!
Published on Java Code Geeks with permission by Eugene Fedorenko , partner at our JCG program. See the original article here: Conversational UI with Oracle Digital Assistant and Fn Project. Part III. Moving to the cloud. Opinions expressed by Java Code Geeks contributors are their own. |