Add v2 interface support for nodes env (#836)
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
ARG BUILDER_IMAGE=fission/builder
|
||||
FROM ${BUILDER_IMAGE}
|
||||
# default variant is the official alpine node image (much smaller than the standard image)
|
||||
FROM node:8-alpine
|
||||
|
||||
ARG NODE_ENV
|
||||
ENV NODE_ENV $NODE_ENV
|
||||
|
||||
COPY --from=0 /builder /builder
|
||||
ADD build.sh /usr/local/bin/build
|
||||
RUN chmod +x /usr/local/bin/build
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
cd ${SRC_PKG}
|
||||
npm install && cp -r ${SRC_PKG} ${DEPLOY_PKG}
|
||||
@@ -6,6 +6,10 @@
|
||||
{
|
||||
"name": "Soam Vasani",
|
||||
"email": "soamvasani@platform9.com"
|
||||
},
|
||||
{
|
||||
"name": "Gary Yeap",
|
||||
"email": "contact@garyyeap.com"
|
||||
}
|
||||
],
|
||||
"description": "NodeJS run container for the fission framework",
|
||||
@@ -20,7 +24,7 @@
|
||||
"morgan": "*",
|
||||
"mz": "~2.7.0",
|
||||
"request": "^2.81.0",
|
||||
"request-promise-native": "^1.0.3",
|
||||
"underscore": ">=1.8.3"
|
||||
"underscore": ">=1.8.3",
|
||||
"request-promise-native": "^1.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,56 +7,86 @@ const express = require('express');
|
||||
const app = express();
|
||||
const bodyParser = require('body-parser');
|
||||
const morgan = require('morgan');
|
||||
const argv = require('minimist')(process.argv.slice(1));// Command line opts
|
||||
|
||||
// Command line opts
|
||||
const argv = require('minimist')(process.argv.slice(1));
|
||||
if (!argv.codepath) {
|
||||
argv.codepath = "/userfunc/user";
|
||||
console.log("Codepath defaulting to ", argv.codepath);
|
||||
}
|
||||
if (!argv.port) {
|
||||
console.log("Port defaulting to 8888");
|
||||
argv.port = 8888;
|
||||
}
|
||||
|
||||
|
||||
// Node resolves module paths according to a file's location. We load
|
||||
// the file from argv.codepath, but tell users to put dependencies in
|
||||
// the server's package.json; this means the function's dependencies
|
||||
// are in /usr/src/app/node_modules. We could be smarter and have the
|
||||
// function deps in the right place in argv.codepath; but for now we
|
||||
// just symlink the function's node_modules to the server's
|
||||
// node_modules.
|
||||
fs.symlinkSync('/usr/src/app/node_modules', `${path.dirname(argv.codepath)}/node_modules`);
|
||||
|
||||
// User function. Starts out undefined.
|
||||
let userFunction;
|
||||
|
||||
//
|
||||
// Specialize this server to a given user function. The user function
|
||||
// is read from argv.codepath; it's expected to be placed there by the
|
||||
// fission runtime.
|
||||
//
|
||||
function specialize(req, res) {
|
||||
// Make sure we're a generic container. (No reuse of containers.
|
||||
// Once specialized, the container remains specialized.)
|
||||
if (userFunction) {
|
||||
res.status(400).send("Not a generic container");
|
||||
return;
|
||||
}
|
||||
|
||||
function loadFunction(modulepath, funcname) {
|
||||
// Read and load the code. It's placed there securely by the fission runtime.
|
||||
try {
|
||||
var startTime = process.hrtime();
|
||||
userFunction = require(argv.codepath);
|
||||
var elapsed = process.hrtime(startTime);
|
||||
let startTime = process.hrtime();
|
||||
// support v1 codepath and v2 entrypoint like 'foo', '', 'index.hello'
|
||||
let userFunction = funcname ? require(modulepath)[funcname] : require(modulepath);
|
||||
let elapsed = process.hrtime(startTime);
|
||||
console.log(`user code loaded in ${elapsed[0]}sec ${elapsed[1]/1000000}ms`);
|
||||
return userFunction;
|
||||
} catch(e) {
|
||||
console.error(`user code load error: ${e}`);
|
||||
res.status(500).send(JSON.stringify(e));
|
||||
return;
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
function withEnsureGeneric(func) {
|
||||
return function(req, res) {
|
||||
// Make sure we're a generic container. (No reuse of containers.
|
||||
// Once specialized, the container remains specialized.)
|
||||
if (userFunction) {
|
||||
res.status(400).send("Not a generic container");
|
||||
return;
|
||||
}
|
||||
|
||||
func(req, res);
|
||||
}
|
||||
}
|
||||
|
||||
function isFunction(func) {
|
||||
return func && func.constructor && func.call && func.apply;
|
||||
}
|
||||
|
||||
function specializeV2(req, res) {
|
||||
// for V2 entrypoint, 'filename.funcname' => ['filename', 'funcname']
|
||||
const entrypoint = req.body.functionName ? req.body.functionName.split('.') : [];
|
||||
// for V2, filepath is dynamic path
|
||||
const modulepath = path.join(req.body.filepath, entrypoint[0] || '');
|
||||
const result = loadFunction(modulepath, entrypoint[1]);
|
||||
|
||||
if(isFunction(result)){
|
||||
userFunction = result;
|
||||
res.status(202).send();
|
||||
} else {
|
||||
res.status(500).send(JSON.stringify(result));
|
||||
}
|
||||
}
|
||||
|
||||
function specialize(req, res) {
|
||||
// Specialize this server to a given user function. The user function
|
||||
// is read from argv.codepath; it's expected to be placed there by the
|
||||
// fission runtime.
|
||||
//
|
||||
const modulepath = argv.codepath || '/userfunc/user';
|
||||
|
||||
// Node resolves module paths according to a file's location. We load
|
||||
// the file from argv.codepath, but tell users to put dependencies in
|
||||
// the server's package.json; this means the function's dependencies
|
||||
// are in /usr/src/app/node_modules. We could be smarter and have the
|
||||
// function deps in the right place in argv.codepath; b ut for now we
|
||||
// just symlink the function's node_modules to the server's
|
||||
// node_modules.
|
||||
fs.symlinkSync('/usr/src/app/node_modules', `${path.dirname(modulepath)}/node_modules`);
|
||||
|
||||
const result = loadFunction(modulepath);
|
||||
|
||||
if(isFunction(result)){
|
||||
userFunction = result;
|
||||
res.status(202).send();
|
||||
} else {
|
||||
res.status(500).send(JSON.stringify(result));
|
||||
}
|
||||
res.status(202).send();
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +98,8 @@ app.use(bodyParser.json());
|
||||
app.use(bodyParser.raw());
|
||||
app.use(bodyParser.text({ type : "text/*" }));
|
||||
|
||||
app.post('/specialize', specialize);
|
||||
app.post('/specialize', withEnsureGeneric(specialize));
|
||||
app.post('/v2/specialize', withEnsureGeneric(specializeV2));
|
||||
|
||||
// Generic route -- all http requests go to the user function.
|
||||
app.all('/', function (req, res) {
|
||||
|
||||
+51
-44
@@ -1,19 +1,19 @@
|
||||
# Fission Node.js Examples
|
||||
|
||||
This is V2 example, check [here](README_V1.md) for V1.
|
||||
|
||||
This directory contains several examples to get you started using Node.js with Fission.
|
||||
|
||||
## Environment
|
||||
|
||||
Before running any of these functions, make sure you have created a `nodejs` Fission environment:
|
||||
|
||||
```bash
|
||||
# Create an environment with default nodejs images
|
||||
$ fission env create --name nodeenv --image fission/node-env:latest --builder fission/node-builder:latest
|
||||
# Create zip file from our example
|
||||
$ zip -jr nodejs.zip nodejs/
|
||||
# Create a package with the zip file
|
||||
$ fission pkg create --sourcearchive nodejs.zip --env nodeenv
|
||||
```
|
||||
$ fission env create --name nodejs --image fission/node-env
|
||||
```
|
||||
|
||||
Note: The default `fission/node-env` image is based on Alpine, which is much smaller than the main Debian Node image (65MB vs 680MB) while still being suitable for most use cases.
|
||||
If you need to use the full Debian image use the `fission/node-env-debian` image instead.
|
||||
See the [official Node docker hub repo](https://hub.docker.com/_/node/) for considerations
|
||||
relating to this choice.
|
||||
|
||||
## Function signature
|
||||
|
||||
@@ -27,28 +27,54 @@ module.exports = async function(context) {
|
||||
headers: {
|
||||
'Foo': 'Bar'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Since it is an `async` function, you can `await` `Promise`s, as demonstrated in the `stock.js` function.
|
||||
|
||||
## hello.js
|
||||
|
||||
This is a basic "Hello, World!" example. It simply returns a status of `200` and text body.
|
||||
|
||||
### Usage
|
||||
Since it is an `async` function, you can `await` `Promise`s, as demonstrated in the `weather.js` function.
|
||||
|
||||
```bash
|
||||
# Upload your function code to fission
|
||||
$ fission function create --name hello --env nodejs --code hello.js
|
||||
# Create a function
|
||||
$ fission fn create --name hello --pkg [pkgname] --entrypoint "hello"
|
||||
|
||||
# Map GET /hello to your new function
|
||||
$ fission route create --method GET --url /hello --function hello
|
||||
# Test the function
|
||||
$ fission fn test --name hello
|
||||
```
|
||||
|
||||
# Run the function.
|
||||
$ curl http://$FISSION_ROUTER/hello
|
||||
Hello, world!
|
||||
## index.js
|
||||
|
||||
This file does nothing but for demonstrating `require` feature.
|
||||
|
||||
### Usage
|
||||
```bash
|
||||
# Create a function, you can skip `--entrypoint` as node will look for `index.js` by default
|
||||
$ fission fn create --name index --pkg [pkgname]
|
||||
|
||||
# Test the function
|
||||
$ fission fn test --name index
|
||||
```
|
||||
|
||||
## multi-entry.js
|
||||
|
||||
This is a multiple exports example. There are two exports: entry1 and entry2
|
||||
|
||||
### Usage
|
||||
```bash
|
||||
# Create a function for entry1
|
||||
$ fission fn create --name entry1 --pkg [pkgname] --entrypoint "multi-entry.entry1"
|
||||
|
||||
# Test the function
|
||||
$ fission fn test --name entry1
|
||||
|
||||
# Create a function for entry2
|
||||
$ fission fn create --name entry2 --pkg [pkgname] --entrypoint "multi-entry.entry2"
|
||||
|
||||
# Test the function
|
||||
$ fission fn test --name entry2
|
||||
```
|
||||
|
||||
## hello-callback.js
|
||||
@@ -60,8 +86,8 @@ This is a basic "Hello, World!" example implemented with the legacy callback imp
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
# Upload your function code to fission
|
||||
$ fission function create --name hello-callback --env nodejs --code hello-callback.js
|
||||
# Create a function
|
||||
$ fission fn create --name hello-callback --pkg [pkgname] --entrypoint "hello-callback"
|
||||
|
||||
# Map GET /hello-callback to your new function
|
||||
$ fission route create --method GET --url /hello-callback --function hello-callback
|
||||
@@ -71,25 +97,6 @@ $ curl http://$FISSION_ROUTER/hello-callback
|
||||
Hello, world!
|
||||
```
|
||||
|
||||
## stock.js
|
||||
|
||||
This is a basic example of how you can easily use asynchronous requests in your functions. By default, the Node.js environment makes the [`request-promise-native`](https://github.com/request/request-promise-native) library available. In this example, the Google Finance API is used to determine when a stock was last traded.
|
||||
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
# Upload your function code to fission
|
||||
$ fission function create --name stock --env nodejs --code stock.js
|
||||
|
||||
# Map GET /stock to your new function
|
||||
$ fission route create --method POST --url /stock --function stock
|
||||
|
||||
# Run the function.
|
||||
$ curl -H "Content-Type: application/json" -X POST -d '{"symbol":"AAPL"}' http://$FISSION_ROUTER/stock
|
||||
|
||||
{"text":"AAPL last traded at 138.99"}
|
||||
```
|
||||
|
||||
## kubeEventsSlack.js
|
||||
|
||||
This example watches Kubernetes events and sends them to a Slack channel. To use this, create an incoming webhook for your Slack channel, and replace the `slackWebhookPath` in the example code.
|
||||
@@ -98,7 +105,7 @@ This example watches Kubernetes events and sends them to a Slack channel. To use
|
||||
|
||||
```bash
|
||||
# Upload your function code to fission
|
||||
$ fission fn create --name kubeEventsSlack --env nodejs --code kubeEventsSlack.js
|
||||
$ fission fn create --name kubeEventsSlack --pkg [pkgname] --entrypoint "hello-callback"
|
||||
|
||||
# Watch all services in the default namespace:
|
||||
$ fission watch create --function kubeEventsSlack --type service --ns default
|
||||
@@ -112,7 +119,7 @@ In this example, the Yahoo Weather API is used to current weather at a given loc
|
||||
|
||||
```bash
|
||||
# Upload your function code to fission
|
||||
$ fission function create --name weather --env nodejs --code weather.js
|
||||
$ fission function create --name weather --pkg [pkgname] --entrypoint "weather"
|
||||
|
||||
# Map GET /stock to your new function
|
||||
$ fission route create --method POST --url /weather --function weather
|
||||
@@ -121,4 +128,4 @@ $ fission route create --method POST --url /weather --function weather
|
||||
$ curl -H "Content-Type: application/json" -X POST -d '{"location":"Sieteiglesias, Spain"}' http://$FISSION_ROUTER/weather
|
||||
|
||||
{"text":"It is 2 celsius degrees in Sieteiglesias, Spain and Mostly Clear"}
|
||||
```
|
||||
```
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
# Fission Node.js Examples
|
||||
|
||||
This directory contains several examples to get you started using Node.js with Fission.
|
||||
|
||||
## Environment
|
||||
|
||||
Before running any of these functions, make sure you have created a `nodejs` Fission environment:
|
||||
|
||||
```
|
||||
$ fission env create --name nodejs --image fission/node-env
|
||||
```
|
||||
|
||||
Note: The default `fission/node-env` image is based on Alpine, which is much smaller than the main Debian Node image (65MB vs 680MB) while still being suitable for most use cases.
|
||||
If you need to use the full Debian image use the `fission/node-env-debian` image instead.
|
||||
See the [official Node docker hub repo](https://hub.docker.com/_/node/) for considerations
|
||||
relating to this choice.
|
||||
|
||||
## Function signature
|
||||
|
||||
Every Node.js function has the same basic form:
|
||||
|
||||
```javascript
|
||||
module.exports = async function(context) {
|
||||
return {
|
||||
status: 200,
|
||||
body: 'Your body here',
|
||||
headers: {
|
||||
'Foo': 'Bar'
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Since it is an `async` function, you can `await` `Promise`s, as demonstrated in the `weather.js` function.
|
||||
|
||||
## hello.js
|
||||
|
||||
This is a basic "Hello, World!" example. It simply returns a status of `200` and text body.
|
||||
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
# Upload your function code to fission
|
||||
$ fission function create --name hello --env nodejs --code hello.js
|
||||
|
||||
# Map GET /hello to your new function
|
||||
$ fission route create --method GET --url /hello --function hello
|
||||
|
||||
# Run the function.
|
||||
$ curl http://$FISSION_ROUTER/hello
|
||||
Hello, world!
|
||||
```
|
||||
|
||||
## hello-callback.js
|
||||
|
||||
This is a basic "Hello, World!" example implemented with the legacy callback implementation. If you declare your function with two arguments (`context`, `callback`), a callback taking three arguments (`status`, `body`, `headers`) is provided.
|
||||
|
||||
⚠️️ Callback support is only provided for backwards compatibility! We recommend that you use `async` functions instead.
|
||||
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
# Upload your function code to fission
|
||||
$ fission function create --name hello-callback --env nodejs --code hello-callback.js
|
||||
|
||||
# Map GET /hello-callback to your new function
|
||||
$ fission route create --method GET --url /hello-callback --function hello-callback
|
||||
|
||||
# Run the function.
|
||||
$ curl http://$FISSION_ROUTER/hello-callback
|
||||
Hello, world!
|
||||
```
|
||||
|
||||
## kubeEventsSlack.js
|
||||
|
||||
This example watches Kubernetes events and sends them to a Slack channel. To use this, create an incoming webhook for your Slack channel, and replace the `slackWebhookPath` in the example code.
|
||||
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
# Upload your function code to fission
|
||||
$ fission fn create --name kubeEventsSlack --env nodejs --code kubeEventsSlack.js
|
||||
|
||||
# Watch all services in the default namespace:
|
||||
$ fission watch create --function kubeEventsSlack --type service --ns default
|
||||
```
|
||||
|
||||
## weather.js
|
||||
|
||||
In this example, the Yahoo Weather API is used to current weather at a given location.
|
||||
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
# Upload your function code to fission
|
||||
$ fission function create --name weather --env nodejs --code weather.js
|
||||
|
||||
# Map GET /stock to your new function
|
||||
$ fission route create --method POST --url /weather --function weather
|
||||
|
||||
# Run the function.
|
||||
$ curl -H "Content-Type: application/json" -X POST -d '{"location":"Sieteiglesias, Spain"}' http://$FISSION_ROUTER/weather
|
||||
|
||||
{"text":"It is 2 celsius degrees in Sieteiglesias, Spain and Mostly Clear"}
|
||||
```
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
module.exports = function(context, callback) {
|
||||
callback(200, "Hello, world!\n");
|
||||
callback(200, "Hello, world callback!\n");
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('./hello');
|
||||
@@ -0,0 +1,13 @@
|
||||
module.exports.entry1 = async function(context) {
|
||||
return {
|
||||
status: 200,
|
||||
body: "Hello, entry 1!\n"
|
||||
};
|
||||
}
|
||||
|
||||
module.exports.entry2 = async function(context) {
|
||||
return {
|
||||
status: 200,
|
||||
body: "Hello, entry 2!\n"
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "fission-nodejs-example",
|
||||
"version": "0.1.0",
|
||||
"author": "Soam Vasani",
|
||||
"contributors": [
|
||||
{
|
||||
"name": "Soam Vasani",
|
||||
"email": "soamvasani@platform9.com"
|
||||
},
|
||||
{
|
||||
"name": "Gary Yeap",
|
||||
"email": "contact@garyyeap.com"
|
||||
}
|
||||
],
|
||||
"description": "Nodejs example for Fission framework",
|
||||
"engines": {
|
||||
"node": ">=7.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"body-parser": "*",
|
||||
"co": "~4.6.0",
|
||||
"express": "*",
|
||||
"minimist": "*",
|
||||
"morgan": "*",
|
||||
"mz": "~2.7.0",
|
||||
"request": "^2.81.0",
|
||||
"request-promise-native": "^1.0.3",
|
||||
"underscore": ">=1.8.3"
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const rp = require('request-promise-native');
|
||||
|
||||
module.exports = async function (context) {
|
||||
const body = context.request.body;
|
||||
console.log(`body = ${body}`);
|
||||
const symbol = body['text'].split(' ')[1];
|
||||
|
||||
console.log(`Got symbol: ${symbol}`);
|
||||
|
||||
if (!symbol) {
|
||||
return {
|
||||
status: 400,
|
||||
body: {
|
||||
text: 'You must provide a stock symbol.'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await rp(`http://finance.google.com/finance/info?q=NYSE:${symbol}`);
|
||||
const parsed = JSON.parse(response.slice(3));
|
||||
const lastTrade = parsed[0]['l_cur'];
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
text: `${symbol} last traded at ${lastTrade}`
|
||||
},
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return {
|
||||
status: 500,
|
||||
body: e
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user