With other words
How to write serverless function
for
SAP Cloud Platform Extension Center, serverless runtime
on local laptop
using
Command Line Client
This blog is part of a
series of tutorials explaining how to write serverless functions using the Functions-as-a-Service offering in
SAP Cloud Platform Serverless Runtime
Quicklinks:
Sample Code
Commands
Introduction
To develop serverless functions, based on
SAP Cloud Platform Serverless Runtime, it is not mandatory to use the browser tool
Extension Center.
You can develop functions in Node.js locally, using your favorite development environment, etc
Overview
1. Designtime
Create Project, Function and Trigger
2. Runtime
Deploy, use the function, view Logs
3. Appendix
Commands, Alternatives, Extension Center
Prerequisites
- Access to
SAP Cloud Platform, productive account. Trial isn't supported
- Instance of
Serverless Runtime (see Preparation blog)
- Cloud Foundry CLI installed locally (see same Preparation blog)
- Functions CLI available locally (see still same link)
-
Node.js installed
1. Designtime
Use your preferred development environment for Node.js, e.g.
this one
1.1. Create Project
We have to manually create the folder structure:
We create a root folder for our locally developed FaaS project:
C:\tmp_faaslocal
This name is only a folder name in file system. It doesn’t have impact on the name of the FaaS project which we’re going to deploy
In the root folder, we create:
faas.json
This is the manifest of the FaaS project. The name is fix and cannot be changed
package.json
The name of the descriptor of Node.js applications is fix as well
mylocalmodule.js
A javascript file containing our implementation
The project structure should look like this
No doubt, it is very simple
But we’ll see something even more simple below
package.json
Our Function-as-a-Service project is realized as executable Node.js module and the package.json descriptor file is required by the Node.js runtime.
Copy the following content into your
package.json file
{}
No doubt, it is very simple
Note:
No, it isn't a joke
faas.json
This is the main entry point of a function project. Reading this file is the first task of the command line tool
So this file must be located in the root folder of a functions project
Copy the following content into the file
{
"project": "localproject",
"version": "1",
"runtime": "nodejs10",
"library": ".",
"functions": {
"mylocalfunction": {
"module": "mylocalmodule.js",
"handler": "mylocalhandler"
}
},
"triggers": {
"mylocalhttptrigger": {
"type": "HTTP",
"function": "mylocalfunction"
}
}
}
We can see that a project manifest has several mandatory properties
project
As usual, everything has a name, so even a project has a name
And as usual, I’m giving silly names. That makes clear which names are fix and which names can be freely chosen by us.
BTW, I have to correct myself: there’s one exception: the manifest file has a silly name which wasn’t chosen freely by me…
version
Obviously, the version, blabla
runtime
Make sure to use only one of the supported runtimes
Which are the supported runtimes?
Currently:
nodejs8 and
nodejs10
How to know the current supported runtimes?
One thing for sure: my blog might not be always uptodate
So how to check?
Some suggestions:
Run
project check on command line, or try to deploy, or check the wizard in
Extension Center
What about docu?
Yes
library
Our first project is very simple, so we don’t need a library folder.
We just point to current directory
Normally, we would enter here the relative path to a folder containing the required javascript files
functions
Interesting: the definition of a function
First of all, there can be multiple functions in a project
Each function is identified by its name
BTW, function names do have restrictions, e.g. no uppercase, no special characters (remember the very “special” character of cats...)
module
We have enter the javascript filename, which is not only a silly javascript file, but at the same time it is a module in terms of Node.js
And even one more declaration is required:
handler
This is the name of a function in the javascript file. This function is the main entry point of the implementation
It is the code which is initially triggered by the FaaS runtime
This handler has to be exported such that it can be invoked by the runtime
triggers
There can be multiple triggers in a project
Each trigger is identified by its name
function
Each trigger is an independent artifact in terms of FaaS project
Therefore, a trigger has to point to a function
type
The value is a hardcoded type, I mean: here we cannot write any silly name
Supported types are: HTTP, Timer, AMQP, CloudEvents
I recommend the serious
docu page to learn which types are supported
Depending on the chosen type, there are more settings required in
faas.json
1.2. Create Function
Actually, we've already defined the function above, in the
faas.json.
Now we have to stick to the file name and also the handler name
mylocalmodule.js
Finally, this javascript file contains the actual implementation of the function in the Function-as-a-Service project (as you know, the “project” is also known as “Extension”)
This file contains the code
There can be more javascript files
But not today
Requirements:
The javascript file must contain javascript code
It must be a Node.js module which exports a function (the handler)
A soft requirement:
A function should be rather silly and simple and small (as I call it in my language. Please check out
pradeep.pandas blog:
THAT language is really noble and exquisite)
For today, just copy the following code:
module.exports = {
mylocalhandler: function (event, context) {
const s=' ',s2=s+s,s3=s2+s,h='-',s4=s2+s2,c=':',s5=s4+s,k=',',s1=s5+s5,v='|',b='\\',f='/',o='(',e=')',u='_',u2=u+u,d='.',u3=u2+u,i="'",u5=u3+u2,p=';',q='\"',t='`',z='^',r=s+'\n';
console.log(''+h+h+s+'miaow'+s+h+h+r) ;
return ""+r+s1+s1+v+b+s+f+b+r+s4+u2+s1+s4+v+k+b+o+u+b+u+r+s3+o+s+o+s1+s4+v+b+k+t+s3+t+h+z+d+r+s4+b+s+b+s1+s3+c+s4+t+h+i+s3+e+r+s5+b+s+b+s1+s3+b+s5+s3+p+r+s5+s+b+s+b+s1+s3+t+h+d+s3+k+i+r+s5+s2+b+s+b+s+u5+u5+u2+k+i+s2+o+r+s5+s3+p+s+i+s1+s5+s+p+r+s5+s3+b+s5+s5+s5+s2+f+u3+k+h+d+r+s5+s4+t+k+s4+k+u5+v+s2+p+i+u5+k+i+r+s5+s2+k+h+q+s+b+s2+c+s5+s+v+s+c+r+s5+s+o+s+d+h+q+s+b+s+t+d+u2+s3+v+s+v+r+s5+s2+b+u2+e+s2+t+d+u2+k+i+s2+v+u2+e+s2+'SSt';
}
}
It follows my requirement:
It is silly
Nevertheless, we can see:
We define a function
The name of that function corresponds to the handler property which we declared in our
faas.json file
At the same time, it is
exported in terms of Node.js
module
The function itself does something:
In our silly example, it just writes anything to the log
And the function has a return value
As such, when invoked via HTTP, we can see the return value as HTTP response in the browser
It doesn’t do anything else. It doesn’t take any benefit of the powerful Functions – API
No it doesn’t.
But nevertheless, it is nice
Hum...really...?
You should give it a try
Just to see the response
Summarizing:
A minimal Function project consists of
1 javascript file as module, with one exported handler-method
1 manifest file called
faas.json, containing declaration of function and trigger
1
package.json file in case of Node.js runtime
1.3. Create Trigger
This is a silly chapter.
Because we’ve already defined the trigger in the
faas.json
There’s nothing else to create
However, just a note:
We’ve defined an HTTP trigger
But how does that help us?
HTTP trigger means that any person can call (trigger) the function via HTTP
But also a tool can do that programmatically
Even an animal can do it (if it doesn’t sleep)
All of them need one thing:
The URL
And patience
The URL is created during deployment by the framework and can be seen afterwards in the details or in the log output
Note:
Without a trigger, the function is useless
It's like a cat in coma
The faCLI will refuse to deploy if no trigger is defined
2. Runtime
After we've created the required files locally, it is time to talk about the actual interesting topic of this blog:
How do we communicate with FaaS runtime (XFsr instance)?
How is our node-module being converted into a serverless function?
The answer is:
With the help of the command line tool
The following examples assume that you've prepared the CLI tool for FaaS and it is added to the PATH variable
2.1. Deploy
So now’s time to deploy our FaaS project to the FaaS runtime
As mentioned before (I don’t remember if I wrote it in a blog – or if I mentioned it while talking to a friend), the
SAP Cloud Platform Serverless Runtime offers a command line tool to interact with the Functions runtime:
The
SAP Cloud Platform Serverless Runtime CLI
In my language, I call it
FaaS CLI.
Is it OK?
This tool has a prerequisite:
The
Cloud Foundry Command Line Interface (I call it
cf CLI)
In my language:
fa CLI requires cf CLI
I mentioned that before, I know for sure, info can be found
here
OK, the faCLI interacts with a service instance on the SAP Cloud Platform.
As such, for communication with the Cloud Foundry environment of SAP Cloud Platform it uses the cfCLI internally
Let’s login with the cfCLI
cf login
It asks us to enter all the user credential and org stuff
Once we’re in the space where we created the service instance of
Serverless Runtime, we leave the cfCLI alone
We navigate to our folder where we created the FaaS project
cd C:\tmp_faaslocal
Now we use the faCLI to login to the FaaS runtime
xfsrt-cli login
The command prints the Cloud Foundry details where we just logged on.
It is useful, to make sure that we’re not deploying to wrong target space
Then it proposes the service instances found in that space
We don’t need to type any instance name, just enter the number
Convenient!
And even more:
If there’s only 1 instance, then we can just press Enter, no need to type the 1
Even more convenient!
The faCLI knows that we’re lazy (and reading this blog makes sleepy)
The deploy command is easy as well:
xfsrt-cli faas project deploy
Note:
The faCLI needs to know which project to deploy.
In our example, we’ve stepped into the project folder, so the faCLI just deploys that project
Convenient!
In other cases, the path to the project has to be entered via command parameter -p
The result of the deploy command:
potential error message, generic info and:
the info which we were waiting for.
Which info?
Sorry if I talk too much and you already forgot that you were waiting for an info
Sometimes I talk so much to my cat (silly conversations): so it forgets that it has caught a mouse – then I can liberate the mouse from such embarrassing situation (being caught by a sleepy pussycat)
OK, see below what we were waiting for:
Yes, we were waiting for the
URL of our function
Now we have it
So now we can call (trigger) our function
2.2. Use the Function
In a browser window, we invoke that URL.
The trigger is activated
The function wakes up and does something (in the log)
The function returns something, it is visible in the browser:
Is this the return value?
No, the response is locked
Come on...
No no, you have to try it yourself
2.3. View the log
To view the entry which we’ve written to the log, we can again use the faCLI
xfsrt-cli faas project logs
Again, a nice command and easy to remember.
Just make sure to jump into the project folder, same folder where the
faas.json is located
As a result, we can see that the function has done its job:
2.4. View the log again
Let’s play little bit with the
CAT sorry, the CLI
Make sure to trigger the function multiple times in the browser, to have some amount of log entries to look at
Try adding the param -t to the command:
xfsrt-cli faas project logs -t
It displays the timestamp of each log.
Sometimes, it is necessary to know, when a log was written, but mostly it is not required and disturbs.
Alternatively, for those who like typing
xfsrt-cli faas project logs --timestamps
I like this command, because I never use it….
Try another option:
xfsrt-cli faas project logs --tail 10
One of the possibilities to reduce the mostly crowded log entries
I like this flag and it’s easy to remember, like pulling the tail of a cat
With other words: tail means, you see just the remaining tail of the cat
The following log command mentions explicitly the project name and adds a filter to view only logs produced by the given function
xfsrt-cli faas project logs localproject --functions mylocalfunction
Finally, checkout more options with the help:
xfsrt-cli faas project logs -h
Summary
In this blog we've learned how to efficiently work on local machine instead of using browser based tool.
We deploy the project to the FaaS runtime (not to Cloud Foundry)
Basically, we only need to download the Command Line Client for FaaS
Links
Download
SAP Cloud Platform Serverless Runtime CLI from
https://tools.hana.ondemand.com/#cloud
Prerequisite:
Cloud Foundry CLI
Link
collection
Appendix 1: Useful Commands
Other than your cat, the CLI will always do what you want..
xfsrt-cli login requires login to CF
xfsrt-cli faas project deploy executed from project root folder
xfsrt-cli faas project get prints info, e.g. URL for HTTP trigger
xfsrt-cli faas project check runs some checks and prints errors, warnings, etc
xfsrt-cli faas project logs to view the logs
add -v to make the CLI more verbose
Appendix 2: Alternative Implementations
library folder
As mentioned, it is recommended to use a separate subfolder for the implementation file(s)
In that case, the property
library in
faas.json needs to be adapted accordingly
And the project structure would look like this:
shortcut for handler
If your function contains only one method, you can export without giving a name
module.exports = function(event, context) {
. . .
);
In that case, remove the "handler" property from your
faas.json
Appendix 3: Extension Center
If you have access to Extension Center (see
preparation), you can see that our deployed project appears there as an extension
Appendix 4: All Sample Project Files
In our example, all files are located in the same (root) directory
faas.json
{
"project": "localproject",
"version": "1",
"runtime": "nodejs10",
"library": ".",
"functions": {
"mylocalfunction": {
"module": "mylocalmodule.js",
"handler": "mylocalhandler"
}
},
"triggers": {
"mylocalhttptrigger": {
"type": "HTTP",
"function": "mylocalfunction"
}
}
}
package.json
{}
mylocalmodule.js
module.exports = {
mylocalhandler: function (event, context) {
const s=' ',s2=s+s,s3=s2+s,h='-',s4=s2+s2,c=':',s5=s4+s,k=',',s1=s5+s5,v='|',b='\\',f='/',o='(',e=')',u='_',u2=u+u,d='.',u3=u2+u,i="'",u5=u3+u2,p=';',q='\"',t='`',z='^',r=s+'\n'; console.log(''+h+h+s+'miaow'+s+h+h+r) ;
return ""+r+s1+s1+v+b+s+f+b+r+s4+u2+s1+s4+v+k+b+o+u+b+u+r+s3+o+s+o+s1+s4+v+b+k+t+s3+t+h+z+d+r+s4+b+s+b+s1+s3+c+s4+t+h+i+s3+e+r+s5+b+s+b+s1+s3+b+s5+s3+p+r+s5+s+b+s+b+s1+s3+t+h+d+s3+k+i+r+s5+s2+b+s+b+s+u5+u5+u2+k+i+s2+o+r+s5+s3+p+s+i+s1+s5+s+p+r+s5+s3+b+s5+s5+s5+s2+f+u3+k+h+d+r+s5+s4+t+k+s4+k+u5+v+s2+p+i+u5+k+i+r+s5+s2+k+h+q+s+b+s2+c+s5+s+v+s+c+r+s5+s+o+s+d+h+q+s+b+s+t+d+u2+s3+v+s+v+r+s5+s2+b+u2+e+s2+t+d+u2+k+i+s2+v+u2+e+s2+'SSt';
}
}