Showing preview only (509K chars total). Download the full file or copy to clipboard to get everything.
Repository: serverless/serverless-openwhisk
Branch: master
Commit: c83d8b71fb09
Files: 130
Total size: 474.5 KB
Directory structure:
gitextract_ir1tuu5t/
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .travis.yml
├── LICENSE.txt
├── README.md
├── compile/
│ ├── apigw/
│ │ ├── README.md
│ │ ├── index.js
│ │ └── tests/
│ │ └── index.js
│ ├── cloudant/
│ │ ├── README.md
│ │ ├── index.js
│ │ └── tests/
│ │ └── index.js
│ ├── functions/
│ │ ├── README.md
│ │ ├── index.js
│ │ ├── runtimes/
│ │ │ ├── base.js
│ │ │ ├── binary.js
│ │ │ ├── docker.js
│ │ │ ├── index.js
│ │ │ ├── java.js
│ │ │ ├── node.js
│ │ │ ├── php.js
│ │ │ ├── python.js
│ │ │ ├── ruby.js
│ │ │ ├── sequence.js
│ │ │ ├── swift.js
│ │ │ └── tests/
│ │ │ ├── all.js
│ │ │ ├── base.js
│ │ │ ├── binary.js
│ │ │ ├── docker.js
│ │ │ ├── index.js
│ │ │ ├── java.js
│ │ │ ├── node.js
│ │ │ ├── php.js
│ │ │ ├── python.js
│ │ │ ├── ruby.js
│ │ │ ├── sequence.js
│ │ │ └── swift.js
│ │ └── tests/
│ │ └── index.js
│ ├── message_hub/
│ │ ├── README.md
│ │ ├── index.js
│ │ └── tests/
│ │ └── index.js
│ ├── packages/
│ │ ├── README.md
│ │ ├── index.js
│ │ └── tests/
│ │ └── index.js
│ ├── rules/
│ │ ├── README.md
│ │ ├── index.js
│ │ └── tests/
│ │ └── index.js
│ ├── schedule/
│ │ ├── README.md
│ │ ├── index.js
│ │ └── tests/
│ │ └── index.js
│ ├── servicebindings/
│ │ ├── README.md
│ │ ├── index.js
│ │ └── tests/
│ │ └── index.js
│ └── triggers/
│ ├── README.md
│ ├── index.js
│ └── tests/
│ └── index.js
├── configCredentials/
│ ├── index.js
│ └── tests/
│ └── index.js
├── deploy/
│ ├── README.md
│ ├── index.js
│ ├── lib/
│ │ ├── deployApiGw.js
│ │ ├── deployFeeds.js
│ │ ├── deployFunctions.js
│ │ ├── deployPackages.js
│ │ ├── deployRules.js
│ │ ├── deployServiceBindings.js
│ │ ├── deployTriggers.js
│ │ ├── initializeResources.js
│ │ └── validate.js
│ └── tests/
│ ├── all.js
│ ├── deployApiGw.js
│ ├── deployFeeds.js
│ ├── deployFunctions.js
│ ├── deployPackages.js
│ ├── deployRules.js
│ ├── deployServiceBindings.js
│ ├── deployTriggers.js
│ ├── index.js
│ ├── initializeResources.js
│ ├── resources/
│ │ ├── swagger.json
│ │ ├── swagger_default_ns.json
│ │ ├── swagger_ns_paths.json
│ │ └── swagger_paths.json
│ └── validate.js
├── deployFunction/
│ ├── index.js
│ └── tests/
│ └── index.js
├── index.js
├── info/
│ ├── index.js
│ └── tests/
│ └── index.js
├── invoke/
│ ├── README.md
│ ├── index.js
│ └── tests/
│ └── index.js
├── invokeLocal/
│ ├── index.js
│ ├── invoke.py
│ └── tests/
│ ├── fixture/
│ │ └── handlerWithError.js
│ └── index.js
├── logs/
│ ├── index.js
│ └── tests/
│ └── index.js
├── package.json
├── provider/
│ ├── cliTokenManager.js
│ ├── credentials.js
│ ├── openwhiskProvider.js
│ └── tests/
│ ├── cliTokenManager.js
│ ├── credentials.js
│ ├── index.js
│ └── openwhiskProvider.js
├── remove/
│ ├── README.md
│ ├── index.js
│ ├── lib/
│ │ ├── removeFeeds.js
│ │ ├── removeFunctions.js
│ │ ├── removePackages.js
│ │ ├── removeRoutes.js
│ │ ├── removeRules.js
│ │ ├── removeTriggers.js
│ │ ├── setupResources.js
│ │ ├── util.js
│ │ └── validate.js
│ └── tests/
│ ├── all.js
│ ├── index.js
│ ├── removeFeeds.js
│ ├── removeFunctions.js
│ ├── removePackages.js
│ ├── removeRoutes.js
│ ├── removeRules.js
│ ├── removeTriggers.js
│ └── setupResources.js
├── tests/
│ └── all.js
├── tools/
│ └── travis/
│ ├── build.sh
│ └── setup.sh
└── utils/
└── index.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .eslintignore
================================================
coverage
node_modules
tmp
tmpdirs-serverless
================================================
FILE: .eslintrc.js
================================================
module.exports = {
"extends": "airbnb",
"plugins": [],
"rules": {
"func-names": "off",
// doesn't work in node v4 :(
"strict": "off",
"prefer-rest-params": "off",
"react/require-extension" : "off",
"import/no-extraneous-dependencies" : "off"
},
"env": {
"mocha": true
}
};
================================================
FILE: .gitignore
================================================
# Logs
*.log
npm-debug.log
# Runtime data
pids
*.pid
*.seed
dist
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules
package-lock.json
# IDE stuff
**/.idea
.vscode/
# OS stuff
.DS_Store
.tmp
# Serverless stuff
admin.env
.env
tmp
.coveralls.yml
tmpdirs-serverless
================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
- "11"
- "10"
- "8"
sudo: required
services:
- docker
before_install:
- cd $TRAVIS_BUILD_DIR
install:
- ./tools/travis/setup.sh
- cd $TRAVIS_BUILD_DIR
script:
- ./tools/travis/build.sh
- cd $TRAVIS_BUILD_DIR
- npm run report
================================================
FILE: LICENSE.txt
================================================
Copyright (c) 2018 Serverless, Inc. http://www.serverless.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
**📦 Archived - This repository is archived and preserved for reference only. No updates, issues, or pull requests will be accepted. If you have questions, please reach out to our support team.**
---
# Serverless Apache OpenWhisk Plugin
[](https://travis-ci.org/serverless/serverless-openwhisk)
[](https://codecov.io/gh/serverless/serverless-openwhisk)
This plugin enables support for the [Apache OpenWhisk platform](https://openwhisk.apache.org/) within the Serverless Framework.
## Getting Started
### Register account with Apache OpenWhisk
Before you can deploy your service to Apache OpenWhisk, you need to have an account registered with the platform.
- *Want to run the platform locally?* Please read the project's [*Quick Start*](https://github.com/openwhisk/openwhisk#quick-start) guide for deploying it locally.
- *Want to use a hosted provider?* Please [sign up](https://cloud.ibm.com/registration) for a free account with [IBM Cloud](https://cloud.ibm.com/) and then follow the instructions for getting access to [IBM Cloud Functions (Apache OpenWhisk)](https://cloud.ibm.com/openwhisk).
### Set up account credentials
Account credentials for OpenWhisk can be provided through a configuration file or environment variables. This plugin requires the API endpoint, namespace and authentication credentials.
**Do you want to use a configuration file for storing these values?** Please [follow the instructions](https://console.ng.bluemix.net/openwhisk/cli) for setting up the OpenWhisk command-line utility. This tool stores account credentials in the `.wskprops` file in the user's home directory. The plugin automatically extracts credentials from this file at runtime. No further configuration is needed.
**Do you want to use environment variables for credentials?** Use the following environment variables to be pass in account credentials. These values override anything extracted from the configuration file.
- *OW_APIHOST* - Platform endpoint, e.g. `openwhisk.ng.bluemix.net`
- *OW_AUTH* - Authentication key, e.g. `xxxxxx:yyyyy`
- *OW_NAMESPACE* - Namespace, defaults to user-provided credentials
- *OW_APIGW_ACCESS_TOKEN* - API gateway access token (optional)
- *OW_IAM_NAMESPACE_API_KEY* - IBM Cloud IAM API key (optional & overrides `auth`).
### Install Serverless Framework
```shell
$ npm install --global serverless
```
**_This framework plugin requires Node.js runtime version 6.0 or above._**
### Create Service From Template
Using the `create` command, you can create an example service from the [following template](https://github.com/serverless/serverless/tree/master/lib/plugins/create/templates/openwhisk-nodejs).
```shell
serverless create --template openwhisk-nodejs --path my_service
cd my_service
npm install
```
More service examples are available in the [`serverless-examples`](https://github.com/serverless/examples) repository.
**Using a self-hosted version of the platform?**
Ensure you set the `ignore_certs` option in the serverless.yaml prior to deployment.
```
provider:
name: openwhisk
ignore_certs: true
```
### Deploy Service
The sample service from the template can be deployed without modification.
```shell
serverless deploy
```
If the deployment succeeds, the following messages will be printed to the console.
```sh
$ serverless deploy
Serverless: Packaging service...
Serverless: Compiling Functions...
Serverless: Compiling API Gateway definitions...
Serverless: Compiling Rules...
Serverless: Compiling Triggers & Feeds...
Serverless: Deploying Functions...
Serverless: Deployment successful!
Service Information
platform: openwhisk.ng.bluemix.net
namespace: _
service: my_service
actions:
my_service-dev-hello
triggers:
**no triggers deployed***
rules:
**no rules deployed**
endpoints:
**no routes deployed**
web-actions:
**no web actions deployed**
```
### Test Service
Use the `invoke` command to test your newly deployed service.
```shell
$ serverless invoke --function hello
{
"payload": "Hello, World!"
}
$ serverless invoke --function hello --data '{"name": "OpenWhisk"}'
{
"payload": "Hello, OpenWhisk!"
}
```
*Add the `-v` or `--verbose` flag to show more [invocation details](https://github.com/apache/incubator-openwhisk/blob/master/docs/annotations.md#annotations-specific-to-activations), e.g. activation id and duration details.*
```shell
$ serverless invoke --function hello -v
=> action (<ACTION_NAME>) activation (<ID>) duration: 96ms (init: 83ms, wait: 35ms)
{
"payload": "Hello, OpenWhisk!"
}
```
## Writing Functions - Node.js
Here's an `index.js` file containing an example handler function.
```javascript
function main(params) {
const name = params.name || 'World';
return {payload: 'Hello, ' + name + '!'};
};
exports.main = main;
```
Modules [should return the function handler](https://github.com/openwhisk/openwhisk/blob/master/docs/actions.md#packaging-an-action-as-a-nodejs-module) as a custom property on the global `exports` object.
In the `serverless.yaml` file, the `handler` property is used to denote the source file and module property containing the serverless function.
```yaml
functions:
my_function:
handler: index.main
```
### Request Properties
OpenWhisk executes the handler function for each request. This function is called with a single argument, an object [containing the request properties](https://github.com/openwhisk/openwhisk/blob/master/docs/actions.md#passing-parameters-to-an-action).
```javascript
function main(params) {
const parameter = params.parameter_name;
...
};
```
### Function Return Values
The handler must return an object from the function call. Returning `undefined` or `null` will result in an error. If the handler is carrying out an [asynchronous task](https://github.com/openwhisk/openwhisk/blob/master/docs/actions.md#creating-asynchronous-actions), it can return a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
```javascript
// synchronous return
function main () {
return { payload: "..." }
}
// asychronous return
function main(args) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve({ done: true });
}, 2000);
})
}
```
If you want to return an error message, return an object with an `error` property with the message. Promise values that are rejected will be interpreted as runtime errors.
```javascript
// synchronous return
function main () {
return { error: "..." }
}
// asychronous return
function main(args) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
reject("error message");
}, 2000);
})
}
```
### Using NPM Modules
NPM modules must be [installed locally](https://github.com/openwhisk/openwhisk/blob/master/docs/actions.md#packaging-an-action-as-a-nodejs-module) in the `node_modules` directory before deployment. This directory will be packaged up in the deployment artefact. Any dependencies included in `node_modules` will be available through `require()` in the runtime environment.
OpenWhisk provides a number of popular NPM modules in the runtime environment. Using these modules doesn't require them to be included in the deployment package. See [this list](https://github.com/openwhisk/openwhisk/blob/master/docs/reference.md#javascript-runtime-environments) for full details of which modules are available.
```javascript
const leftPad = require("left-pad")
function pad_lines(args) {
const lines = args.lines || [];
return { padded: lines.map(l => leftPad(l, 30, ".")) }
};
exports.handler = pad_lines;
```
## Writing Functions - PHP
Here's an `index.php` file containing an example handler function.
```php
<?php
function main(array $args) : array
{
$name = $args["name"] ?? "stranger";
$greeting = "Hello $name!";
echo $greeting;
return ["greeting" => $greeting];
}
```
In the `serverless.yaml` file, the `handler` property is used to denote the source file and function name of the serverless function.
```yaml
functions:
my_function:
handler: index.main
runtime: php
```
### Request Properties
OpenWhisk executes the handler function for each request. This function is called with a single argument, an associative array [containing the request properties](https://github.com/openwhisk/openwhisk/blob/master/docs/actions.md#passing-parameters-to-an-action).
```php
function main(array $args) : array
{
$name = $args["name"] ?? "stranger";
...
}
```
### Function Return Values
The handler must return an associative array from the function call.
```php
func main(args: [String:Any]) -> [String:Any] {
...
return ["foo" => $bar];
}
```
If you want to return an error message, return an object with an `error` property with the message.
## Writing Functions - Python
Here's an `index.py` file containing an example handler function.
```python
def endpoint(params):
name = params.get("name", "stranger")
greeting = "Hello " + name + "!"
print(greeting)
return {"greeting": greeting}
```
In the `serverless.yaml` file, the `handler` property is used to denote the source file and module property containing the serverless function.
```yaml
functions:
my_function:
handler: index.endpoint
runtime: python:3
```
### Request Properties
OpenWhisk executes the handler function for each request. This function is called with a single argument, a dictionary [containing the request properties](https://github.com/openwhisk/openwhisk/blob/master/docs/actions.md#passing-parameters-to-an-action).
```python
def endpoint(params):
name = params.get("name", "stranger")
...
```
### Function Return Values
The handler must return a dictionary from the function call.
```python
def endpoint(params):
...
return {"foo": "bar"}
```
If you want to return an error message, return an object with an `error` property with the message.
## Writing Functions - Ruby
Here's an `hello.rb` file containing an example handler function.
```ruby
def main(args)
name = args["name"] || "stranger"
greeting = "Hello #{name}!"
puts greeting
{ "greeting" => greeting }
end
```
In the `serverless.yaml` file, the `handler` property is used to denote the source file and function name of the serverless function.
```yaml
functions:
my_function:
handler: hello.main
runtime: ruby
```
### Request Properties
OpenWhisk executes the handler function for each request. This function is called with a single argument, which is a hash [containing the request properties](https://github.com/openwhisk/openwhisk/blob/master/docs/actions.md#passing-parameters-to-an-action).
```ruby
def main(args)
name = args["name"] || "stranger"
...
```
### Function Return Values
The handler must return a hash from the function call.
```ruby
def main(args)
...
{ "greeting" => greeting }
end
```
If you want to return an error message, return an `error` property string in the return hash.
## Writing Functions - Swift
Here's an `index.swift` file containing an example handler function.
```swift
func main(args: [String:Any]) -> [String:Any] {
if let name = args["name"] as? String {
return [ "greeting" : "Hello \(name)!" ]
} else {
return [ "greeting" : "Hello stranger!" ]
}
}
```
In the `serverless.yaml` file, the `handler` property is used to denote the source file and module property containing the serverless function.
```yaml
functions:
my_function:
handler: index.main
runtime: swift
```
### Request Properties
OpenWhisk executes the handler function for each request. This function is called with a single argument, a dictionary [containing the request properties](https://github.com/openwhisk/openwhisk/blob/master/docs/actions.md#passing-parameters-to-an-action).
```swift
func main(args: [String:Any]) -> [String:Any] {
let prop = args["prop"] as? String
}
```
### Function Return Values
The handler must return a dictionary from the function call.
```swift
func main(args: [String:Any]) -> [String:Any] {
...
return ["foo": "bar"]
}
```
If you want to return an error message, return an object with an `error` property with the message.
### Codable Support
Swift 4 runtimes support [Codable types](https://developer.apple.com/documentation/swift/codable) to handle the converting between JSON input parameters and response types to native Swift types.
```swift
struct Employee: Codable {
let id: Int?
let name: String?
}
// codable main function
func main(input: Employee, respondWith: (Employee?, Error?) -> Void) -> Void {
// For simplicity, just passing same Employee instance forward
respondWith(input, nil)
}
```
### Pre-Compiled Swift Binaries
OpenWhisk supports creating Swift actions from a pre-compiled binary. This reduces startup time for Swift actions by removing the need for a dynamic compilation step.
In the `serverless.yaml` file, the `handler` property can refer to the zip file containing a binary file produced by the build.
```yaml
functions:
hello:
handler: action.zip
```
Compiling a single Swift file to a binary can be handled using this Docker command with the OpenWhisk Swift runtime image. `main.swift` is the file containing the swift code and `action.zip` is the zip archive produced.
```
docker run -i openwhisk/action-swift-v4.2 -compile main < main.swift > action.zip
```
Swift packages containing multiple source files with a package descriptor (`Package.swift` ) can be built using the following command.
```
zip - -r * | docker run -i openwhisk/action-swift-v4.2 -compile main > action.zip
```
## Writing Functions - Java
Here's an `src/main/java/HelloWorld.java` file containing an example handler function.
```java
import com.google.gson.JsonObject;
public class HelloWorld {
public static JsonObject main(JsonObject args) throws Exception {
final String name = args.getAsJsonPrimitive("name").getAsString();
final JsonObject response = new JsonObject();
response.addProperty("greeting", "Hello " + name + "!");
return response;
}
}
```
Here is a simple `pom.xml` file that will allow you to use Maven to build it. You will notice that `gson` is excluded from the uberjar. That is because OpenWhisk already provides this dependency.
```xml
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>hello</groupId>
<artifactId>hello-world</artifactId>
<version>1.0</version>
<dependencies>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<minimizeJar>true</minimizeJar>
<artifactSet>
<excludes>
<exclude>com.google.code.gson:gson</exclude>
</excludes>
</artifactSet>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
```
In the `serverless.yaml` file (see below), the `handler` property is the uberjar produced by calling `mvn clean package`, a colon, and then the fully qualified class name of the class with the main function. If you do not provide a class name after the jar, it will look for a class in the default package called `Main`.
```yaml
service: my-java-service
provider:
name: openwhisk
runtime: java
functions:
hello:
handler: target/hello-world-1.0.jar:HelloWorld
plugins:
- serverless-openwhisk
```
### Request Properties
OpenWhisk executes the handler function for each request. This function is called with a single argument, a `com.google.gson.JsonObject` [containing the request properties](https://github.com/openwhisk/openwhisk/blob/master/docs/actions.md#passing-parameters-to-an-action).
```java
import com.google.gson.JsonObject;
public class MyActionClass {
public static JsonObject main(JsonObject args) throws Exception
{
final String name = args.getAsJsonPrimitive("name").getAsString();
...
}
}
```
### Function Return Values
The handler must return an `com.google.gson.JsonObject` from the function call.
```java
import com.google.gson.JsonObject;
public class MyActionClass {
public static JsonObject main(JsonObject args) throws Exception
{
...
final JsonObject response = new JsonObject();
response.addProperty("greeting", "Hello " + name + "!");
return response;
}
}
```
If you want to return an error message, throw an exception.
## Writing Functions - Binary
OpenWhisk supports executing a compiled binary for the function handler. Using a Python wrapper, the file will be invoked within the `openwhisk/dockerskeleton` Docker container.
The binary must be compiled for the correct platform architecture and only link to shared libraries installed in the `openwhisk/dockerskeleton` runtime.
In the `serverless.yaml` file, the `handler` property is used to denote the binary file to upload.
```yaml
functions:
my_function:
handler: bin_file
runtime: binary
```
### Request Properties
OpenWhisk executes the binary file for each request. Event parameters are streamed to `stdio` as a JSON object string.
### Function Return Values
The handler must write a JSON object string with the response parameters to `stdout` before exiting.
If you want to return an error message, return an object with an `error` property with the message.
## Custom Runtime Images
OpenWhisk actions can use [custom Docker images as the runtime environment](https://medium.com/openwhisk/large-applications-on-openwhisk-bcf15bff94ec). This allows extra packages, libraries or tools to be pre-installed in the runtime environment. Using a custom runtime image, with extra libraries and dependencies built-in, is useful for overcoming the [maximum deployment size](https://github.com/apache/incubator-openwhisk/blob/master/docs/reference.md#system-limits) on actions.
*Images must implement the [API used by the platform](http://jamesthom.as/blog/2017/01/16/openwhisk-docker-actions/) to interact with runtime environments. Images must also be available on Docker Hub. OpenWhisk does not support private Docker registries.*
OpenWhisk publishes the [existing runtime images on Docker Hub](https://hub.docker.com/r/openwhisk/). Using these images in the `FROM` directive in the `Dockerfile` is an easy way to [create new images](https://docs.docker.com/engine/reference/commandline/build/) compatible with the platform.
In the `serverless.yaml` file, the `image` property is used to denote the custom runtime image.
```yaml
functions:
my_function:
handler: source.js
runtime: nodejs
image: dockerhub_user/image_name
```
*Node.js, Swift, Python and Binary runtimes support using a custom image property.*
## Writing Functions - Docker
OpenWhisk supports creating actions from public images on Docker Hub without handler files. These images are expected to support the platform API used to instantiate and invoke serverless functions.
All necessary files for execution must be provided within the image. Local source files will not be uploaded to the runtime environment.
In the `serverless.yaml` file, the `handler` property is used to denote the image label.
```yaml
functions:
my_function:
handler: repo/image_name
runtime: docker
```
## Working With Packages
OpenWhisk provides a concept called "packages" to manage related actions. Packages can contain multiple actions under a common identifier in a namespace. Configuration values needed by all actions in a package can be set as default properties on the package, rather than individually on each action.
*Packages are identified using the following format:* `/namespaceName/packageName/actionName`.
***Rules and triggers can not be created within packages.***
### Implicit Packages
Actions can be assigned to packages by setting the function `name` with a package reference.
```yaml
functions:
foo:
handler: handler.foo
name: "myPackage/foo"
bar:
handler: handler.bar
name: "myPackage/bar"
```
In this example, two new actions (`foo` & `bar`) will be created using the `myPackage` package.
Packages which do not exist will be automatically created during deployments. When using the `remove` command, any packages referenced in the `serverless.yml` will be deleted.
### Explicit Packages
Packages can also be defined explicitly to set shared configuration parameters. Default package parameters are merged into event parameters for each invocation.
```yaml
functions:
foo:
handler: handler.foo
name: "myPackage/foo"
resources:
packages:
myPackage:
name: optionalCustomName
parameters:
hello: world
```
*Explicit packages support the following properties: `name`, `parameters`, `annotations`, `services` and `shared`.*
### Binding Packages
OpenWhisk also supports "binding" external packages into your workspace. Bound packages can have default parameters set for shared actions.
For example, binding the `/whisk.system/cloudant` package into a new package allows you to set default values for the `username`, `password` and `dbname` properties. Actions from this package can then be invoked with having to pass these parameters in.
Define packages explicitly with a `binding` parameter to use this behaviour.
```yaml
resources:
packages:
mySamples:
binding: /whisk.system/cloudant
parameters:
username: bernie
password: sanders
dbname: vermont
```
For more details on package binding, please see the documentation [here](https://github.com/apache/incubator-openwhisk/blob/master/docs/packages.md#creating-and-using-package-bindings).
## Binding Services (IBM Cloud Functions)
***This feature requires the [IBM Cloud CLI](https://console.bluemix.net/docs/cli/reference/bluemix_cli/download_cli.html#download_install) and [IBM Cloud Functions plugin](https://console.bluemix.net/openwhisk/learn/cli) to be installed.***
IBM Cloud Functions supports [automatic binding of service credentials](https://console.bluemix.net/docs/openwhisk/binding_services.html#binding_services) to actions using the CLI.
Bound service credentials will be passed as the `__bx_creds` parameter in the invocation parameters.
This feature is also available through the `serverless.yaml` file using the `bind` property for each function.
```yaml
functions:
my_function:
handler: file_name.handler
bind:
- service:
name: cloud-object-storage
instance: my-cos-storage
```
The `service` configuration supports the following properties.
- `name`: identifier for the cloud service
- `instance`: instance name for service (*optional*)
- `key`: key name for instance and service (*optional*)
*If the `instance` or `key` properties are missing, the first available instance and key found will be used.*
Binding services removes the need to manually create default parameters for service keys from platform services.
More details on binding service credentials to actions can be found in the [official documentation](https://console.bluemix.net/docs/openwhisk/binding_services.html#binding_services) and [this blog post](http://jamesthom.as/blog/2018/06/05/binding-iam-services-to-ibm-cloud-functions/).
Packages defined in the `resources` section can bind services using the same configuration properties.
```yaml
resources:
packages:
myPackage:
bind:
- service:
name: cloud-object-storage
instance: my-cos-storage
```
## Runtime Configuration Properties
The following OpenWhisk configuration properties are supported for functions defined in
the `serverless.yaml` file.
```yaml
functions:
my_function:
handler: file_name.handler_func
name: "custom_function_name"
runtime: 'runtime_label' // defaults to nodejs:default
namespace: "..." // defaults to user-provided credentials
memory: 256 // 128 to 512 (MB).
timeout: 60 // 0.1 to 600 (seconds)
concurrency: 1 // 1 to 500, default is 1
parameters:
foo: bar // default parameters
annotations:
foo: bar // action annotations
bind:
- service:
name: cloud-object-storage
instance: my-cos-storage
```
## Writing Sequences
OpenWhisk supports a special type of serverless function called [sequences](https://github.com/openwhisk/openwhisk/blob/master/docs/actions.md#creating-action-sequences).
These functions are defined from a list of other serverless functions. Upon invocation, the platform executes each function in series. Request parameters are passed into the first function in the list. Each subsequent function call is passed the output from the previous step as input parameters. The last function's return value is returned as the response result.
Here's an example of the configuration to define a sequence function, composed of three other functions.
```yaml
functions:
my_function:
sequence:
- parse_input
- do_some_algorithm
- construct_output
```
*Sequence functions do not have a handler file defined. If you want to refer to functions not defined in the serverless project, use the fully qualified identifier e.g. /namespace/package/action_name*
## Connecting HTTP Endpoints
Functions can be bound to public URL endpoints using the [API Gateway service](https://github.com/openwhisk/openwhisk/blob/master/docs/apigateway.md). HTTP requests to configured endpoints will invoke functions on-demand. Requests parameters are passed as function arguments. Function return values are serialised as the JSON response body.
HTTP endpoints for functions can be configured through the `serverless.yaml` file.
```yaml
functions:
my_function:
handler: index.main
events:
- http: GET /api/greeting
```
HTTP event configuration also supports using explicit parameters.
- `method` - HTTP method (mandatory).
- `path` - URI path for API gateway (mandatory).
- `resp` - controls [web action content type](https://github.com/apache/incubator-openwhisk/blob/master/docs/webactions.md#additional-features), values include: `json`, `html`, `http`, `svg`or `text` (optional, defaults to `json`).
```yaml
functions:
my_function:
handler: index.main
events:
- http:
method: GET
path: /api/greeting
resp: http
```
API Gateway hosts serving the API endpoints will be shown during deployment.
```shell
$ serverless deploy
...
endpoints:
GET https://xxx-gws.api-gw.mybluemix.net/service_name/api/greeting --> service_name-dev-my_function
```
Calling the configured API endpoints will execute the deployed functions.
````shell
$ http get https://xxx-gws.api-gw.mybluemix.net/api/greeting?user="James Thomas"
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
Date: Mon, 19 Dec 2016 15:47:53 GMT
{
"message": "Hello James Thomas!"
}
````
Functions exposed through the API Gateway service are automatically converted into Web Actions during deployment. The framework [secures Web Actions for HTTP endpoints](https://github.com/apache/incubator-openwhisk/blob/master/docs/webactions.md#securing-web-actions) using the `require-whisk-auth` annotation. If the `require-whisk-auth` annotation is manually configured, the existing annotation value is used, otherwise a random token is automatically generated.
### URL Path Parameters
The API Gateway service [supports path parameters]() in user-defined HTTP paths. This allows functions to handle URL paths which include templated values, like resource identifiers.
Path parameters are identified using the `{param_name}` format in the URL path. The API Gateway sends the full matched path value in the `__ow_path` field of the event parameters.
```yaml
functions:
retrieve_users:
handler: users.get
events:
- http:
method: GET
path: /users/{id}
resp: http
```
This feature comes with the following restrictions:
- *Path parameters are only supported when `resp` is configured as`http`.*
- *Individual path parameter values are not included as separate event parameters. Users have to manually parse values from the full `__ow_path` value.*
### CORS Support
API Gateway endpoints automatically include CORS headers for all endpoints under the service base path. This property can be disabled by manually configuring the `resources.apigw.cors` property.
```yaml
resources:
apigw:
cors: false
```
### Application Authentication
API endpoints can be protected by API keys with a secret or API keys alone.
Setting the HTTP headers used to pass keys and secrets automatically enables API Gateway authentication.
This parameter configures the HTTP header containing the API key. Without the additional secret header, authentication uses an API key alone.
```yaml
resources:
apigw:
auth:
key: API-Key-Header
```
Adding the secret header parameter enables authentication using keys with secrets.
```yaml
resources:
apigw:
auth:
key: API-Key-Header
secret: API-Key-Secret-Header
```
*See the API Gateway [configuration panel](https://cloud.ibm.com/openwhisk/apimanagement) to manage API keys and secrets after authentication is enabled.*
### Application Authentication with OAuth
API endpoints can also be protected by an external OAuth providers.
OAuth tokens must be included as the Authorization header of each API request. Token will be validated with the specified token provider. If the token is invalid, requests are rejected with response code 401.
The following OAuth providers are supported: *[IBM Cloud App ID](https://cloud.ibm.com/catalog/services/app-id), Google, Facebook and Github.*
```yaml
resources:
apigw:
oauth:
provider: app-id || google || facebook || github
```
If the `app-id` provider is selected, the tenant identifier must be provided as an additional configuration token. This can be retrieved from the `tenantId` property of provisioned service credentials for the instance
```yaml
resources:
apigw:
oauth:
provider: app-id
tenant: uuid
```
*Application Authentication with keys (and secrets) and OAuth support are mutually exclusive configuration options.*
### Rate Limiting
API Gateways endpoints support rate limiting to reject excess traffic. When rate limiting is enabled, API calls falling outside of the limit will be rejected and response code 429 will be returned.
**Rate limiting is on a per-key basis and application authentication (without oauth) must be enabled.**
The leaky bucket algorithm is used to prevent sudden bursts of invocations of APIs. If the limit is set as 10 calls per minute, users will be restricted to 1 call every 6 seconds (60/10 = 6).
```yaml
resources:
apigw:
rate_limit:
rate: 100
unit: minute || second || hour || day
```
- `rate`: number of API calls per unit of time.
- `unit`: unit of time (*minute, second, hour, day*) used to threshold API calls with rate.
### Base Path
All API Gateway endpoints defined as HTTP events in the `serverless.yml` are deployed under the default base path (`/`). This basepath can be configured explicitly using the following parameter.
```yaml
resources:
apigw:
basepath: /api
```
### API Name
The service name is used as the API identifier in the API Gateway swagger files. This can be configured explicitly using the following parameter.
```yaml
resources:
apigw:
name: my-api-name
```
## Exporting Web Actions
Functions can be turned into "*web actions*" which return HTTP content without use of an API Gateway. This feature is enabled by setting an annotation (`web-export`) in the configuration file.
```yaml
functions:
my_function:
handler: index.main
annotations:
web-export: true
```
Functions with this annotation can be invoked through a URL template with the following parameters.
```
https://{APIHOST}/api/v1/web/{USER_NAMESPACE}/{PACKAGE}/{ACTION_NAME}.{TYPE}
```
- *APIHOST* - platform endpoint e.g. *openwhisk.ng.bluemix.net.*
- *USER_NAMESPACE* - this must be an explicit namespace and cannot use the default namespace (_).
- *PACKAGE* - action package or `default`.
- *ACTION_NAME* - default form `${servicename}-${space}-${name}`.
- *TYPE* - `.json`, `.html`, `.text` or `.http`.
Return values from the function are used to construct the HTTP response. The following parameters are supported.
1. `headers`: a JSON object where the keys are header-names and the values are string values for those headers (default is no headers).
2. `code`: a valid HTTP status code (default is 200 OK).
3. `body`: a string which is either plain text or a base64 encoded string (for binary data).
Here is an example of returning HTML content:
```
function main(args) {
var msg = "you didn't tell me who you are."
if (args.name) {
msg = `hello ${args.name}!`
}
return {body:
`<html><body><h3><center>${msg}</center></h3></body></html>`}
}
```
Here is an example of returning binary data:
```
function main() {
let png = <base 64 encoded string>
return {
headers: { "Content-Type": "image/png" },
body: png };
}
```
Functions can access request parameters using the following environment variables.
1. `__ow_method` - HTTP method of the request.
2. `__ow_headers` - HTTP request headers.
3. `__ow_path` - Unmatched URL path of the request.
4. `__ow_body` - Body entity from request.
5. `__ow_query` - Query parameters from the request.
**Full details on this feature are available in this [here](https://github.com/apache/incubator-openwhisk/blob/master/docs/webactions.md).**
## Scheduled Invocations
Functions can be set up to fire automatically using the [alarm package](https://github.com/openwhisk/openwhisk/blob/master/docs/catalog.md#using-the-alarm-package). This allows you to invoke functions with preset parameters at specific times (*12:00 each day*) or according to a schedule (*every ten minutes*).
Scheduled invocation for functions can be configured through the `serverless.yaml` file.
The `schedule` event configuration is controlled by a string, based on the UNIX crontab syntax, in the format `cron(X X X X X)`. This can either be passed in as a native string or through the `rate` parameter.
```yaml
functions:
my_function:
handler: index.main
events:
- schedule: cron(* * * * *) // fires each minute.
```
This above example generates a new trigger (`${service}_crawl_schedule_trigger`) and rule (`${service}_crawl_schedule_rule`) during deployment.
Other `schedule` event parameters can be manually configured, e.g trigger or rule names.
```yaml
functions:
aggregate:
handler: statistics.handler
events:
- schedule:
rate: cron(0 * * * *) // call once an hour
trigger: triggerName
rule: ruleName
max: 10000 // max invocations, default: 1000, max: 10000
params: // event params for invocation
hello: world
```
## IBM Message Hub Events
IBM Bluemix provides an "Apache Kafka"-as-a-Service called IBM Message Hub. Functions can be connected to fire when messages arrive on Kafka topics.
IBM Message Hub instances can be provisioned through the IBM Bluemix platform. OpenWhisk on Bluemix will export Message Hub service credentials bound to a package with the following name:
```
/${BLUEMIX_ORG}_${BLUEMIX_SPACE}/Bluemix_${SERVICE_NAME}_Credentials-1
```
Rather than having to manually define all the properties needed by the Message Hub trigger feed, you can reference a package to use instead. Credentials from the referenced package will be used when executing the trigger feed.
Developers only need to add the topic to listen to for each trigger.
```yaml
# serverless.yaml
functions:
index:
handler: users.main
events:
- message_hub:
package: /${BLUEMIX_ORG}_${BLUEMIX_SPACE}/Bluemix_${SERVICE_NAME}_Credentials-1
topic: my_kafka_topic
```
The plugin will create a trigger called `${serviceName}_${fnName}_messagehub_${topic}` and a rule called `${serviceName}_${fnName}_messagehub_${topic}_rule` to bind the function to the message hub events.
The trigger and rule names created can be set explicitly using the `trigger` and`rule` parameters.
Other functions can bind to the same trigger using the inline `trigger` event referencing this trigger name.
```yaml
# serverless.yaml
functions:
index:
handler: users.main
events:
- message_hub:
package: /${BLUEMIX_ORG}_${BLUEMIX_SPACE}/Bluemix_${SERVICE_NAME}_Credentials-1
topic: my_kafka_topic
trigger: log_events
rule: connect_index_to_kafka
another:
handler: users.another
events:
- trigger: log_events
```
### Using Manual Parameters
Parameters for the Message Hub event source can be defined explicitly, rather than using pulling credentials from a package.
```yaml
# serverless.yaml
functions:
index:
handler: users.main
events:
- message_hub:
topic: my_kafka_topic
brokers: afka01-prod01.messagehub.services.us-south.bluemix.net:9093
user: USERNAME
password: PASSWORD
admin_url: https://kafka-admin-prod01.messagehub.services.us-south.bluemix.net:443
json: true
binary_key: true
binary_value: true
```
`topic`, `brokers`, `user`, `password` and `admin_url` are mandatory parameters.
## Cloudant DB Events
IBM Cloudant provides a hosted NoSQL database, based upon CouchDB, running on IBM Bluemix. Functions can be connected to events fired when the database is updated. These events use the [CouchDB changes feed](http://guide.couchdb.org/draft/notifications.html) to follow database modifications.
IBM Cloudant instances can be provisioned through the IBM Bluemix platform. OpenWhisk on Bluemix will export Cloudant service credentials bound to a package with the following name:
```
/${BLUEMIX_ORG}_${BLUEMIX_SPACE}/Bluemix_${SERVICE_NAME}_Credentials-1
```
Rather than having to manually define all the properties needed by the [Cloudant trigger feed](https://github.com/openwhisk/openwhisk-package-cloudant#using-the-cloudant-package), you can reference a package to use instead. Credentials from the referenced package will be used when executing the trigger feed.
Developers only need to add the database name to follow for modifications.
```yaml
# serverless.yaml
functions:
index:
handler: users.main
events:
- cloudant:
package: /${BLUEMIX_ORG}_${BLUEMIX_SPACE}/Bluemix_${SERVICE_NAME}_Credentials-1
db: my_db_name
```
The plugin will create a trigger called `${serviceName}_${fnName}_cloudant_${topic}` and a rule called `${serviceName}_${fnName}_cloudant_${topic}_rule` to bind the function to the Cloudant update events.
The trigger and rule names created can be set explicitly using the `trigger` and`rule` parameters.
Other functions can bind to the same trigger using the inline `trigger` event referencing this trigger name.
### Using Manual Parameters
Parameters for the Cloudant event source can be defined explicitly, rather than using pulling credentials from a package.
```yaml
# serverless.yaml
functions:
index:
handler: users.main
events:
- cloudant: // basic auth example
host: xxx-yyy-zzz-bluemix.cloudant.com
username: USERNAME
password: PASSWORD
db: db_name
- cloudant: // iam auth example
host: xxx-yyy-zzz-bluemix.cloudant.com
iam_api_key: IAM_API_KEY
db: db_name
```
`username` and `password` or `iam_api_key` parameters can be used for authentication.
### Adding Optional Parameters
The following optional feed parameters are also supported:
* `max` - Maximum number of triggers to fire. Defaults to infinite.
* `filter` - Filter function defined on a design document.
* `query` - Optional query parameters for the filter function.
```yaml
# serverless.yaml
functions:
index:
handler: users.main
events:
- cloudant:
...
max: 10000
query:
status: new
filter: mailbox/by_status
```
## Custom Event Triggers
Functions are connected to event sources in OpenWhisk [using triggers and rules](https://github.com/openwhisk/openwhisk/blob/master/docs/triggers_rules.md). Triggers create a named event stream within the system. Triggers can be fired manually or connected to external data sources, like databases or message queues.
Rules set up a binding between triggers and serverless functions. With an active rule, each time a trigger is fired, the function will be executed with the trigger payload.
Event binding for functions can be configured through the `serverless.yaml` file.
```yaml
functions:
my_function:
handler: index.main
events:
- trigger: my_trigger
```
This configuration will create a trigger called `servicename-my_trigger` with an active rule binding `my_function` to this event stream.
### Customising Rules
Rule names default to the following format `servicename-trigger-to-action`. These names be explicitly set through configuration.
```yaml
functions:
my_function:
handler: index.main
events:
- trigger:
name: "my_trigger"
rule: "rule_name"
```
### Customing Triggers
Triggers can be defined as separate resources in the `serverless.yaml` file. This allows you to set up trigger properties like default parameters.
```yaml
functions:
my_function:
handler: index.main
events:
- trigger: my_trigger
resources:
triggers:
my_trigger:
parameters:
hello: world
```
### Trigger Feeds
Triggers can be bound to external event sources using the `feed` property. OpenWhisk [provides a catalogue](https://github.com/openwhisk/openwhisk/blob/master/docs/catalog.md) of third-party event sources bundled as [packages](https://github.com/openwhisk/openwhisk/blob/master/docs/packages.md#creating-and-using-trigger-feeds).
This example demonstrates setting up a trigger which uses the `/whisk.system/alarms/alarm` feed. The `alarm` feed will fire a trigger according to a user-supplied cron schedule.
```yaml
resources:
triggers:
alarm_trigger:
parameters:
hello: world
feed: /whisk.system/alarms/alarm
feed_parameters:
cron: '*/8 * * * * *'
```
## Commands
The following serverless commands are currently implemented for the OpenWhisk provider.
- `deploy` - [Deploy functions, triggers and rules for service](https://serverless.com/framework/docs/providers/openwhisk/cli-reference/deploy/).
- `invoke`- [Invoke deployed serverless function and show result](https://serverless.com/framework/docs/providers/openwhisk/cli-reference/invoke/).
- `invokeLocal`- [Invoke serverless functions locally and show result](https://serverless.com/framework/docs/providers/openwhisk/cli-reference/invoke#invoke-local).
- `remove` - [Remove functions, triggers and rules for service](https://serverless.com/framework/docs/providers/openwhisk/cli-reference/remove/).
- `logs` - [Display activation logs for deployed function](https://serverless.com/framework/docs/providers/openwhisk/cli-reference/logs/).
- `info` - [Display details on deployed functions, triggers and rules](https://serverless.com/framework/docs/providers/openwhisk/cli-reference/info/).
================================================
FILE: compile/apigw/README.md
================================================
# Compile API Gateway Endpoints
This plugins compiles the HTTP events bound to functions in `serverless.yaml` to
corresponding [OpenWhisk API Gateway endpoint](https://github.com/openwhisk/openwhisk/blob/master/docs/apigateway.md)
definitions.
## How it works
`Compile HTTP` hooks into the [`package:compileEvents`](/lib/plugins/deploy) lifecycle.
It loops over all functions which are defined in `serverless.yaml` looking for
the defined events. For each `http` event defined for the function, the
corresponding API gateway endpoint definition will be created.
## Examples
```yaml
# serverless.yaml
functions:
index:
handler: users.handler
events:
- http: GET /api/greeting
```
This definition will create a new endpoint, which binds the configured Action
(index) to the URL path (/api/greeting) and HTTP method (GET).
HTTP operation and path parameters can also be passed object properties on the
event object.
```yaml
# serverless.yaml
functions:
index:
handler: users.handler
events:
- http:
method: GET
basepath: /mypath
path: /api/greeting
resp: json
```
During deployment the endpoint configuration file will be uploaded to OpenWhisk.
Each user has a unique hostname which provides access to the configured API
endpoints. Invoking the endpoints on the gateway host will execute functions
on-demand.
API Gateway hosts serving the API endpoints will be shown during deployment.
```shell
$ serverless deploy
...
endpoints:
GET https://xxx-gws.api-gw.mybluemix.net/api/greeting --> index
```
Calling the configured API endpoints will execute the deployed functions.
````shell
$ http get https://xxx-gws.api-gw.mybluemix.net/api/greeting?user="James Thomas"
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
Date: Mon, 19 Dec 2016 15:47:53 GMT
{
"message": "Hello James Thomas!"
}
````
================================================
FILE: compile/apigw/index.js
================================================
'use strict';
const BbPromise = require('bluebird');
const crypto = require('crypto');
const { formatApiHost } = require('../../utils');
class OpenWhiskCompileHttpEvents {
constructor(serverless, options) {
this.serverless = serverless;
this.options = options;
this.provider = this.serverless.getProvider('openwhisk');
this.hooks = {
'before:package:compileEvents': this.setup.bind(this),
'before:package:compileFunctions': this.addWebAnnotations.bind(this),
'package:compileEvents': this.compileHttpEvents.bind(this)
};
}
setup() {
// This object will be used to store the endpoint definitions, passed directly to
// the OpenWhisk SDK during the deploy process.
this.serverless.service.apigw = {};
// Actions and Triggers referenced by Rules must used fully qualified identifiers (including namespace).
if (!this.serverless.service.provider.namespace) {
return this.provider.props().then(props => {
this.serverless.service.provider.namespace = props.namespace;
this.serverless.service.provider.host = props.apihost;
});
}
}
generateAuthString() {
return crypto.randomBytes(64).toString('hex')
}
// HTTP events need Web Actions enabled for those functions. Add
// annotation 'web-export' if it is not already present.
addWebAnnotations() {
const names = Object.keys(this.serverless.service.functions)
names.forEach(fnName => {
const f = this.serverless.service.functions[fnName]
const httpEvents = (f.events || []).filter(e => e.http)
if (httpEvents.length) {
if (!f.annotations) f.annotations = {}
f.annotations['web-export'] = true
if (!f.annotations.hasOwnProperty('require-whisk-auth')) {
f.annotations['require-whisk-auth'] = this.generateAuthString()
}
}
})
return BbPromise.resolve();
}
calculateFunctionName(functionName, functionObject) {
return functionObject.name || `${this.serverless.service.service}_${functionName}`;
}
calculateFunctionNameSpace(functionObject) {
return functionObject.namespace
|| this.serverless.service.provider.namespace
|| '_';
}
retrieveAuthKey(functionObject) {
const annotations = functionObject.annotations || {}
return annotations['require-whisk-auth']
}
//
// This method takes the rule definitions, parsed from the user's YAML file,
// and turns it into the OpenWhisk Rule resource object.
//
// These resource objects are passed to the OpenWhisk SDK to create the associated Rules
// during the deployment process.
//
// Parameter values will be parsed from the user's YAML definition, either as a value from
// the rule definition or the service provider defaults.
compileHttpEvent(funcName, funcObj, http) {
const options = this.parseHttpEvent(http);
options.namespace = this.calculateFunctionNameSpace(funcName, funcObj);
const name = this.calculateFunctionName(funcName, funcObj).split('/');
options.action = name.pop();
options.pkge = name.pop() || "default";
const secure_key = this.retrieveAuthKey(funcObj)
if (secure_key) {
options.secure_key = secure_key;
}
return options;
}
parseHttpEvent(httpEvent) {
if (httpEvent.path && httpEvent.method) {
return { relpath: httpEvent.path, operation: httpEvent.method, responsetype: httpEvent.resp || 'json' };
} else if (typeof httpEvent === 'string') {
const method_and_path = httpEvent.trim().split(' ');
if (method_and_path.length !== 2) {
throw new this.serverless.classes.Error(
`Incorrect HTTP event parameter value (${httpEvent}), must be string in form: HTTP_METHOD API_PATH e.g. GET /api/foo`);
}
return { operation: method_and_path[0], relpath: method_and_path[1], responsetype: httpEvent.resp || 'json' }
}
throw new this.serverless.classes.Error(
`Incorrect HTTP event parameter value (${httpEvent}), must be string ("GET /api/foo") or object ({method: "GET", path: "/api/foo"})`);
}
addAuthToSwagger(swagger, auth) {
if (!auth.key && auth.secret) {
throw new this.serverless.classes.Error(
"Missing mandatory resources.apigw.auth.key parameter. Must be defined to enable authentication."
)
}
swagger.security = [{ client_id: [] }]
const client_id = {
in: "header", name: auth.key,
type: "apiKey", "x-key-type": "clientId"
}
swagger.securityDefinitions = { client_id }
if (auth.secret) {
swagger.security[0].client_secret = []
swagger.securityDefinitions.client_secret = {
in: "header", name: auth.secret,
type: "apiKey", "x-key-type": "clientSecret"
}
}
}
addOAuthToSwagger(swagger, oauth) {
if (!oauth.provider) return
const providers_urls = {
google: "https://www.googleapis.com/oauth2/v3/tokeninfo",
facebook: "https://graph.facebook.com/debug_token",
github: "https://api.github.com/user",
}
if (oauth.provider !== 'app-id' && oauth.provider !== 'google'
&& oauth.provider !== 'facebook' && oauth.provider !== 'github') {
throw new this.serverless.classes.Error(
`OAuth defined with invalid provider (${oauth.provider}), must be: app-id, google, facebook, github.`
)
}
const security = {}
security[oauth.provider] = []
swagger.security = [ security ]
const definition = {
flow: "application", tokenUrl: "", type: "oauth2",
"x-provider": { name: oauth.provider },
"x-tokenintrospect": { url: null }
}
if (oauth.provider === 'app-id') {
if (!oauth.tenant) {
throw new this.serverless.classes.Error(
`OAuth provider app-id defined without tenant parameter`
)
}
definition['x-provider'].params = { tenantId: oauth.tenant }
} else {
definition['x-tokenintrospect'].url = providers_urls[oauth.provider]
}
const securityDefinitions = {}
securityDefinitions[oauth.provider] = definition
swagger.securityDefinitions = securityDefinitions
}
addRateLimitToSwagger(swagger, rate_limit) {
const rate = rate_limit.rate
const unit = rate_limit.unit
if (!rate) {
throw new this.serverless.classes.Error(
"Missing rate limit parameter: rate."
)
}
if (!unit) {
throw new this.serverless.classes.Error(
"Missing rate limit parameter: unit."
)
}
if (unit !== "minute" && unit !== "second" && unit !== "hour" && unit !== "day") {
throw new this.serverless.classes.Error(
"Invalid rate limit parameter: unit."
)
}
swagger["x-ibm-rate-limit"] = [{
rate: rate_limit.rate,
unit: rate_limit.unit,
units: 1
}]
}
generateSwagger(service, host, options, httpEvents) {
const paths = httpEvents.reduce((paths, httpEvent) => {
const path = paths[httpEvent.relpath] || {}
const operation = httpEvent.operation.toLowerCase()
path[operation] = this.compileSwaggerPath(httpEvent, host)
paths[httpEvent.relpath] = path
return paths
}, {})
const cases = httpEvents.map(httpEvent => this.compileSwaggerCaseSwitch(httpEvent, host))
const execute_body = { "operation-switch": { case: cases } }
const enabled = options.hasOwnProperty('cors') ? options.cors : true
const x_ibm_configuration = {
cors: { enabled },
assembly: { execute: [ execute_body ] }
}
const swagger = {
swagger: "2.0",
basePath: options.basepath || "/",
info: {
title: options.name || service,
version: "1.0"
},
paths,
"x-ibm-configuration": x_ibm_configuration
}
if (options.auth) {
this.addAuthToSwagger(swagger, options.auth)
}
if (options.oauth) {
this.addOAuthToSwagger(swagger, options.oauth)
}
if (options.rate_limit) {
this.addRateLimitToSwagger(swagger, options.rate_limit)
}
return swagger
}
compileSwaggerPath(httpEvent, host) {
const operationId = this.operationId(httpEvent)
const pathParameters = this.parsePathParameters(httpEvent.relpath)
const responses = { "200": { description: "A successful invocation response" } }
const webaction_url = this.webActionUrl(httpEvent, host)
const x_ow = {
action: httpEvent.action, namespace: httpEvent.namespace,
package: httpEvent.pkge, url: webaction_url
}
const swaggerPath = { operationId, responses, "x-openwhisk": x_ow }
if (pathParameters.length) {
swaggerPath.parameters = pathParameters.map(this.createPathParameter)
}
return swaggerPath
}
parsePathParameters (path) {
const regex = /{([^}]+)\}/g
const findAllParams = p => {
const ids = []
let id = regex.exec(p)
while (id) {
ids.push(id[1])
id = regex.exec(p)
}
return ids
}
return path.split('/')
.map(findAllParams)
.reduce((sum, el) => sum.concat(el), [])
}
createPathParameter (name) {
return {
name: name, in: 'path',
description: `Default description for '${name}'`,
required: true, type: 'string'
}
}
compileSwaggerCaseSwitch(httpEvent, host) {
const pathParameters = this.parsePathParameters(httpEvent.relpath)
const webaction_url = this.webActionUrl(httpEvent, host, !!pathParameters.length)
const operationId = this.operationId(httpEvent)
const header = {
set: "message.headers.X-Require-Whisk-Auth",
value: httpEvent.secure_key
}
const execute = [
{ "set-variable": { actions: [ header ] } },
{ invoke: { "target-url": webaction_url, verb: "keep" } }
]
const operations = [ operationId ]
const swaggerCaseSwitch = { execute, operations }
return swaggerCaseSwitch
}
webActionUrl(httpEvent, host, has_path_params) {
const url = `${formatApiHost(host)}/api/v1/web/${httpEvent.namespace}/${httpEvent.pkge}/${httpEvent.action}.${httpEvent.responsetype}${has_path_params ? '$(request.path)': ''}`
return url
}
operationId(httpEvent) {
return `${httpEvent.operation}-${httpEvent.relpath}`.toLowerCase()
}
compileFunctionHttpEvents(functionName, functionObject) {
if (!functionObject.events) return []
const events = functionObject.events
.filter(e => e.http)
.map(e => this.compileHttpEvent(functionName, functionObject, e.http))
if (events.length && this.options.verbose) {
this.serverless.cli.log(`Compiled API Gateway definition (${functionName}): ${JSON.stringify(events)}`);
}
return events
}
compileHttpEvents () {
this.serverless.cli.log('Compiling API Gateway definitions...');
const allFunctions = this.serverless.service.getAllFunctions()
const httpEvents = allFunctions.map(
functionName => this.compileFunctionHttpEvents(functionName, this.serverless.service.getFunction(functionName))
).reduce((a, b) => a.concat(b), [])
if (httpEvents.length) {
const service = this.serverless.service.service
const host = this.serverless.service.provider.host
const resources = this.serverless.service.resources || {}
const options = resources.apigw || {}
this.serverless.service.apigw.swagger = this.generateSwagger(service, host, options, httpEvents)
}
return BbPromise.resolve();
}
}
module.exports = OpenWhiskCompileHttpEvents;
================================================
FILE: compile/apigw/tests/index.js
================================================
'use strict';
const crypto = require('crypto');
const BbPromise = require('bluebird');
const expect = require('chai').expect;
const chaiAsPromised = require('chai-as-promised');
require('chai').use(chaiAsPromised);
const sinon = require('sinon');
const OpenWhiskCompileHttpEvents = require('../index');
describe('OpenWhiskCompileHttpEvents', () => {
let serverless;
let sandbox;
let openwhiskCompileHttpEvents;
beforeEach(() => {
sandbox = sinon.sandbox.create();
serverless = {classes: {Error}, service: {provider: {}, resources: {}, getAllFunctions: () => []}, getProvider: sandbox.spy()};
const options = {
stage: 'dev',
region: 'us-east-1',
};
openwhiskCompileHttpEvents = new OpenWhiskCompileHttpEvents(serverless, options);
serverless.service.service = 'serviceName';
serverless.service.provider = {
namespace: 'testing',
apihost: '',
auth: '',
};
serverless.cli = { consoleLog: () => {}, log: () => {} };
openwhiskCompileHttpEvents.setup();
});
afterEach(() => {
sandbox.restore();
});
describe('#addWebAnnotations()', () => {
it('should add annotations when http event present', () => {
openwhiskCompileHttpEvents.serverless.service.functions = {
a: { events: [ { http: true } ], annotations: {} },
b: { events: [ { http: true } ], annotations: { foo: 'bar' } },
c: { events: [ { http: true } ], annotations: { 'web-export': false } },
d: { events: [ { http: true } ] }
}
const auth_string = crypto.randomBytes(64).toString('hex');
sandbox.stub(openwhiskCompileHttpEvents, 'generateAuthString', () => auth_string);
return openwhiskCompileHttpEvents.addWebAnnotations().then(() => {
expect(openwhiskCompileHttpEvents.serverless.service.functions.a.annotations).to.deep.equal({ 'web-export': true, 'require-whisk-auth': auth_string })
expect(openwhiskCompileHttpEvents.serverless.service.functions.b.annotations).to.deep.equal({ foo: 'bar', 'web-export': true, 'require-whisk-auth': auth_string })
expect(openwhiskCompileHttpEvents.serverless.service.functions.c.annotations).to.deep.equal({ 'web-export': true, 'require-whisk-auth': auth_string })
expect(openwhiskCompileHttpEvents.serverless.service.functions.d.annotations).to.deep.equal({ 'web-export': true, 'require-whisk-auth': auth_string })
})
});
it('should not add auth annotation when annotation already present', () => {
openwhiskCompileHttpEvents.serverless.service.functions = {
a: { events: [ { http: true } ], annotations: { 'require-whisk-auth': false } },
b: { events: [ { http: true } ], annotations: { 'require-whisk-auth': true } },
c: { events: [ { http: true } ], annotations: { 'require-whisk-auth': 'some string' } }
}
return openwhiskCompileHttpEvents.addWebAnnotations().then(() => {
expect(openwhiskCompileHttpEvents.serverless.service.functions.a.annotations).to.deep.equal({ 'web-export': true, 'require-whisk-auth': false })
expect(openwhiskCompileHttpEvents.serverless.service.functions.b.annotations).to.deep.equal({ 'web-export': true, 'require-whisk-auth': true })
expect(openwhiskCompileHttpEvents.serverless.service.functions.c.annotations).to.deep.equal({ 'web-export': true, 'require-whisk-auth': 'some string' })
})
});
it('should ignore annotations when http event not present', () => {
openwhiskCompileHttpEvents.serverless.service.functions = {
a: { },
b: { events: [] },
c: { events: [], annotations: { hello: 'world', 'web-export': true } }
}
return openwhiskCompileHttpEvents.addWebAnnotations().then(() => {
expect(openwhiskCompileHttpEvents.serverless.service.functions.a.annotations).to.be.equal(undefined)
expect(openwhiskCompileHttpEvents.serverless.service.functions.b.annotations).to.be.equal(undefined)
expect(openwhiskCompileHttpEvents.serverless.service.functions.c.annotations).to.deep.equal({ hello: 'world', 'web-export': true })
})
});
})
describe('#compileHttpEvents()', () => {
it('should return empty swagger if functions has no http events', () =>
expect(openwhiskCompileHttpEvents.compileHttpEvents().then(() => {
expect(openwhiskCompileHttpEvents.serverless.service.apigw).to.deep.equal({});
})).to.eventually.be.fulfilled
);
it('should call compileFunctionEvents for each function with events', () => {
const stub = sinon.stub(openwhiskCompileHttpEvents, 'compileFunctionHttpEvents').returns([{foo: 'bar'}]);
sandbox.stub(openwhiskCompileHttpEvents.serverless.service, 'getAllFunctions', () => ["first", "second", "third"]);
sandbox.stub(openwhiskCompileHttpEvents, 'generateSwagger', () => ({"swagger": {}}));
const handler = name => ({events: {}})
openwhiskCompileHttpEvents.serverless.service.getFunction = handler;
return expect(openwhiskCompileHttpEvents.compileHttpEvents().then(() => {
expect(openwhiskCompileHttpEvents.serverless.service.apigw.swagger).to.deep.equal(
{swagger: {}}
);
expect(stub.calledThrice).to.be.equal(true);
})).to.eventually.be.fulfilled;
});
});
describe('#compileFunctionHttpEvents()', () => {
it('should not call compileHttpEvents when events parameter is missing', () => {
const stub = sinon.stub(openwhiskCompileHttpEvents, 'compileHttpEvent')
const events = openwhiskCompileHttpEvents.compileFunctionHttpEvents('name', {})
expect(events).to.deep.equal([]);
expect(stub.called).to.be.equal(false);
})
it('should not call compileHttpEvents when events list contains no events', () => {
const stub = sinon.stub(openwhiskCompileHttpEvents, 'compileHttpEvent')
const events = openwhiskCompileHttpEvents.compileFunctionHttpEvents('name', { events: [{"trigger": {}}] })
expect(events).to.deep.equal([]);
expect(stub.called).to.be.equal(false);
})
it('should call compileHttpEvents when events list contains triggers', () => {
const stub = sinon.stub(openwhiskCompileHttpEvents, 'compileHttpEvent').returns({})
const events = openwhiskCompileHttpEvents.compileFunctionHttpEvents('name', { events: [
{"http": true},
{"http": true},
{"http": true}
] })
expect(events).to.deep.equal([{}, {}, {}]);
expect(stub.calledThrice).to.be.equal(true);
})
it('should log event when verbose flag is used', () => {
openwhiskCompileHttpEvents.options.verbose = true
const log = sandbox.stub(openwhiskCompileHttpEvents.serverless.cli, 'log')
const clog = sandbox.stub(openwhiskCompileHttpEvents.serverless.cli, 'consoleLog')
const stub = sinon.stub(openwhiskCompileHttpEvents, 'compileHttpEvent').returns({ foo: 'bar' })
openwhiskCompileHttpEvents.compileFunctionHttpEvents('name', { events: [
{"http": true},
{"http": true},
{"http": true}
] })
expect(log.calledOnce).to.be.equal(true);
const result = JSON.stringify([{foo: "bar"}, {foo: "bar"}, {foo: "bar"}])
expect(log.args[0][0]).to.be.equal(`Compiled API Gateway definition (name): ${result}`);
})
});
describe('#compileHttpEvent()', () => {
it('should define http events from string property', () => {
openwhiskCompileHttpEvents.serverless.service.service = 'my-service'
openwhiskCompileHttpEvents.serverless.service.provider = {namespace: "sample_ns"};
const http = "GET /api/foo/bar"
const result = openwhiskCompileHttpEvents.compileHttpEvent('action-name', {}, http);
return expect(result).to.deep.equal({
relpath: '/api/foo/bar',
operation: 'GET',
pkge: 'default',
namespace: 'sample_ns',
action: 'my-service_action-name',
responsetype: 'json'
});
});
it('should define http events from string property with explicit package', () => {
openwhiskCompileHttpEvents.serverless.service.service = 'my-service'
openwhiskCompileHttpEvents.serverless.service.provider = {namespace: "sample_ns"};
const http = "GET /api/foo/bar"
const fnObj = { name: 'somePackage/actionName' }
const result = openwhiskCompileHttpEvents.compileHttpEvent('action-name', fnObj, http);
return expect(result).to.deep.equal({
relpath: '/api/foo/bar',
operation: 'GET',
pkge: 'somePackage',
namespace: 'sample_ns',
action: 'actionName',
responsetype: 'json'
});
});
it('should define http events from object property', () => {
openwhiskCompileHttpEvents.serverless.service.service = 'my-service'
openwhiskCompileHttpEvents.serverless.service.provider = {namespace: "sample_ns"};
const http = {path: "/api/foo/bar", method: "GET"}
const result = openwhiskCompileHttpEvents.compileHttpEvent('action-name', {}, http);
return expect(result).to.deep.equal({relpath: '/api/foo/bar', operation: 'GET', action: 'my-service_action-name', namespace: 'sample_ns', pkge: 'default', responsetype: 'json'});
});
it('should add secure auth key if present', () => {
openwhiskCompileHttpEvents.serverless.service.service = 'my-service'
openwhiskCompileHttpEvents.serverless.service.provider = {namespace: "sample_ns"};
const http = {path: "/api/foo/bar", method: "GET"}
const result = openwhiskCompileHttpEvents.compileHttpEvent('action-name', {
annotations: { 'require-whisk-auth': 'auth-token' }
}, http);
return expect(result).to.deep.equal({relpath: '/api/foo/bar', operation: 'GET', secure_key: 'auth-token', action: 'my-service_action-name', namespace: 'sample_ns', pkge: 'default', responsetype: 'json'});
});
it('should define http events with explicit response type', () => {
openwhiskCompileHttpEvents.serverless.service.service = 'my-service'
openwhiskCompileHttpEvents.serverless.service.provider = {namespace: "sample_ns"};
const http = {path: "/api/foo/bar", method: "GET", resp: 'http'}
const result = openwhiskCompileHttpEvents.compileHttpEvent('action-name', {}, http);
return expect(result).to.deep.equal({relpath: '/api/foo/bar', operation: 'GET', action: 'my-service_action-name', namespace: 'sample_ns', pkge: 'default', responsetype: 'http'});
});
it('should throw if http event value invalid', () => {
expect(() => openwhiskCompileHttpEvents.compileHttpEvent('', {}, 'OPERATION'))
.to.throw(Error, /Incorrect HTTP event/);
expect(() => openwhiskCompileHttpEvents.compileHttpEvent('', {}, {}))
.to.throw(Error, /Incorrect HTTP event/);
expect(() => openwhiskCompileHttpEvents.compileHttpEvent('', {}, {method: true}))
.to.throw(Error, /Incorrect HTTP event/);
expect(() => openwhiskCompileHttpEvents.compileHttpEvent('', {}, {path: true}))
.to.throw(Error, /Incorrect HTTP event/);
});
});
describe('#compileSwaggerPath()', () => {
it('should define swagger definition from http events', () => {
openwhiskCompileHttpEvents.serverless.service.service = 'my-service'
openwhiskCompileHttpEvents.serverless.service.provider = {namespace: "sample_ns"};
const httpEvent = {
relpath: '/api/foo/bar', operation: 'GET', secure_key: 'auth-token',
action: 'action-name', namespace: 'user@host.com_space', pkge: 'default', responsetype: 'json'
}
const host = 'openwhisk.somewhere.com'
const result = openwhiskCompileHttpEvents.compileSwaggerPath(httpEvent, host);
const expectedResult = {
operationId: "get-/api/foo/bar",
responses: {
"200": { description: "A successful invocation response" }
},
"x-openwhisk": {
action: "action-name",
namespace: "user@host.com_space",
package: "default",
url: "https://openwhisk.somewhere.com/api/v1/web/user@host.com_space/default/action-name.json"
}
}
return expect(result).to.deep.equal(expectedResult)
});
it('should define swagger definition from http events and respect specified protocol on api host', () => {
openwhiskCompileHttpEvents.serverless.service.service = 'my-service'
openwhiskCompileHttpEvents.serverless.service.provider = {namespace: "sample_ns"};
const httpEvent = {
relpath: '/api/foo/bar', operation: 'GET', secure_key: 'auth-token',
action: 'action-name', namespace: 'user@host.com_space', pkge: 'default', responsetype: 'json'
}
const host = 'http://openwhisk.somewhere.com'
const result = openwhiskCompileHttpEvents.compileSwaggerPath(httpEvent, host);
const expectedResult = {
operationId: "get-/api/foo/bar",
responses: {
"200": { description: "A successful invocation response" }
},
"x-openwhisk": {
action: "action-name",
namespace: "user@host.com_space",
package: "default",
url: "http://openwhisk.somewhere.com/api/v1/web/user@host.com_space/default/action-name.json"
}
}
return expect(result).to.deep.equal(expectedResult)
});
it('should define swagger definition with path parameters', () => {
openwhiskCompileHttpEvents.serverless.service.service = 'my-service'
openwhiskCompileHttpEvents.serverless.service.provider = {namespace: "sample_ns"};
const httpEvent = {
relpath: '/api/foo/{id}', operation: 'GET', secure_key: 'auth-token',
action: 'action-name', namespace: 'user@host.com_space', pkge: 'default', responsetype: 'http'
}
const host = 'openwhisk.somewhere.com'
const result = openwhiskCompileHttpEvents.compileSwaggerPath(httpEvent, host);
const expectedResult = {
operationId: "get-/api/foo/{id}",
parameters: [{
name: "id", in: "path",
description: "Default description for 'id'",
required: true, type: "string"
}],
responses: {
"200": { description: "A successful invocation response" }
},
"x-openwhisk": {
action: "action-name",
namespace: "user@host.com_space",
package: "default",
url: "https://openwhisk.somewhere.com/api/v1/web/user@host.com_space/default/action-name.http"
}
}
return expect(result).to.deep.equal(expectedResult)
});
});
describe('#compileSwaggerCaseSwitch()', () => {
it('should define swagger case statement from http events', () => {
openwhiskCompileHttpEvents.serverless.service.service = 'my-service'
openwhiskCompileHttpEvents.serverless.service.provider = {namespace: "sample_ns"};
const httpEvent = {
relpath: '/api/foo/bar', operation: 'GET', secure_key: 'auth-token',
action: 'action-name', namespace: 'user@host.com_space', pkge: 'default', responsetype: 'json'
}
const host = 'openwhisk.somewhere.com'
const result = openwhiskCompileHttpEvents.compileSwaggerCaseSwitch(httpEvent, host);
const expectedResult = {
execute: [{
"set-variable": {
actions: [{
set: "message.headers.X-Require-Whisk-Auth",
value: "auth-token"
}]
}
},
{
invoke: {
"target-url": "https://openwhisk.somewhere.com/api/v1/web/user@host.com_space/default/action-name.json",
"verb": "keep"
}
}
],
operations: [ "get-/api/foo/bar" ]
}
return expect(result).to.deep.equal(expectedResult)
});
it('should define swagger case statement from http events with path parameters', () => {
openwhiskCompileHttpEvents.serverless.service.service = 'my-service'
openwhiskCompileHttpEvents.serverless.service.provider = {namespace: "sample_ns"};
const httpEvent = {
relpath: '/api/foo/{id}', operation: 'GET', secure_key: 'auth-token',
action: 'action-name', namespace: 'user@host.com_space', pkge: 'default', responsetype: 'http'
}
const host = 'openwhisk.somewhere.com'
const result = openwhiskCompileHttpEvents.compileSwaggerCaseSwitch(httpEvent, host);
const expectedResult = {
execute: [{
"set-variable": {
actions: [{
set: "message.headers.X-Require-Whisk-Auth",
value: "auth-token"
}]
}
},
{
invoke: {
"target-url": "https://openwhisk.somewhere.com/api/v1/web/user@host.com_space/default/action-name.http$(request.path)",
"verb": "keep"
}
}
],
operations: [ "get-/api/foo/{id}" ]
}
return expect(result).to.deep.equal(expectedResult)
});
});
describe('#generateSwagger()', () => {
it('should generate APIGW swagger with paths and case statements from http event', () => {
openwhiskCompileHttpEvents.serverless.service.service = 'my-service'
openwhiskCompileHttpEvents.serverless.service.provider = {namespace: "sample_ns"};
const httpEvent = {
relpath: '/api/foo/bar', operation: 'GET', secure_key: 'auth-token',
action: 'action-name', namespace: 'user@host.com_space', pkge: 'default', responsetype: 'json'
}
const host = 'openwhisk.somewhere.com'
const service = "my-service"
const options = "false"
const swaggerPath = openwhiskCompileHttpEvents.compileSwaggerPath(httpEvent, host)
const swaggerCase = openwhiskCompileHttpEvents.compileSwaggerCaseSwitch(httpEvent, host)
const result = openwhiskCompileHttpEvents.generateSwagger(service, host, options, [ httpEvent ] );
const swaggerCases = result["x-ibm-configuration"]
.assembly.execute[0]["operation-switch"].case
expect(result.swagger).to.equal("2.0")
expect(result.basePath).to.equal("/")
expect(result.info.title).to.equal(service)
expect(result.info.version).to.equal("1.0")
expect(result.paths["/api/foo/bar"].get).to.deep.equal(swaggerPath)
expect(swaggerCases[0]).to.deep.equal(swaggerCase)
});
it('should generate APIGW swagger with multiple http events on same path', () => {
openwhiskCompileHttpEvents.serverless.service.service = 'my-service'
openwhiskCompileHttpEvents.serverless.service.provider = {namespace: "sample_ns"};
const gethttpEvent = {
relpath: '/api/foo/bar', operation: 'GET', secure_key: 'auth-token',
action: 'action-name', namespace: 'user@host.com_space', pkge: 'default', responsetype: 'json'
}
const posthttpEvent = {
relpath: '/api/foo/bar', operation: 'POST', secure_key: 'auth-token',
action: 'action-name', namespace: 'user@host.com_space', pkge: 'default', responsetype: 'json'
}
const host = 'openwhisk.somewhere.com'
const service = "my-service"
const options = "false"
const getswaggerPath = openwhiskCompileHttpEvents.compileSwaggerPath(gethttpEvent, host)
const getswaggerCase = openwhiskCompileHttpEvents.compileSwaggerCaseSwitch(gethttpEvent, host)
const postswaggerPath = openwhiskCompileHttpEvents.compileSwaggerPath(posthttpEvent, host)
const postswaggerCase = openwhiskCompileHttpEvents.compileSwaggerCaseSwitch(posthttpEvent, host)
const result = openwhiskCompileHttpEvents.generateSwagger(service, host, options, [ gethttpEvent, posthttpEvent ] );
const swaggerCases = result["x-ibm-configuration"]
.assembly.execute[0]["operation-switch"].case
expect(result.paths["/api/foo/bar"].get).to.deep.equal(getswaggerPath)
expect(result.paths["/api/foo/bar"].post).to.deep.equal(postswaggerPath)
expect(swaggerCases.length).to.equal(2)
expect(swaggerCases[0]).to.deep.equal(getswaggerCase)
expect(swaggerCases[1]).to.deep.equal(postswaggerCase)
});
it('should generate APIGW swagger with multiple http events on different paths', () => {
openwhiskCompileHttpEvents.serverless.service.service = 'my-service'
openwhiskCompileHttpEvents.serverless.service.provider = {namespace: "sample_ns"};
const gethttpEvent = {
relpath: '/api/foo/bar', operation: 'GET', secure_key: 'auth-token',
action: 'action-name', namespace: 'user@host.com_space', pkge: 'default', responsetype: 'json'
}
const posthttpEvent = {
relpath: '/api/foo/ccc', operation: 'POST', secure_key: 'auth-token',
action: 'action-name', namespace: 'user@host.com_space', pkge: 'default', responsetype: 'json'
}
const host = 'openwhisk.somewhere.com'
const service = "my-service"
const options = "false"
const getswaggerPath = openwhiskCompileHttpEvents.compileSwaggerPath(gethttpEvent, host)
const getswaggerCase = openwhiskCompileHttpEvents.compileSwaggerCaseSwitch(gethttpEvent, host)
const postswaggerPath = openwhiskCompileHttpEvents.compileSwaggerPath(posthttpEvent, host)
const postswaggerCase = openwhiskCompileHttpEvents.compileSwaggerCaseSwitch(posthttpEvent, host)
const result = openwhiskCompileHttpEvents.generateSwagger(service, host, options, [ gethttpEvent, posthttpEvent ] );
const swaggerCases = result["x-ibm-configuration"]
.assembly.execute[0]["operation-switch"].case
expect(result.paths["/api/foo/bar"].get).to.deep.equal(getswaggerPath)
expect(result.paths["/api/foo/ccc"].post).to.deep.equal(postswaggerPath)
expect(swaggerCases.length).to.equal(2)
expect(swaggerCases[0]).to.deep.equal(getswaggerCase)
expect(swaggerCases[1]).to.deep.equal(postswaggerCase)
});
it('should generate APIGW swagger with default API gateway options', () => {
openwhiskCompileHttpEvents.serverless.service.service = 'my-service'
openwhiskCompileHttpEvents.serverless.service.provider = {namespace: "sample_ns"};
const httpEvent = {
relpath: '/api/foo/bar', operation: 'GET', secure_key: 'auth-token',
action: 'action-name', namespace: 'user@host.com_space', pkge: 'default', responsetype: 'json'
}
const host = 'openwhisk.somewhere.com'
const service = "my-service"
const options = {}
const result = openwhiskCompileHttpEvents.generateSwagger(service, host, options, [ httpEvent ] );
expect(result["x-ibm-configuration"].cors.enabled).to.equal(true)
});
it('should generate APIGW swagger with custom CORS API gateway options', () => {
openwhiskCompileHttpEvents.serverless.service.service = 'my-service'
openwhiskCompileHttpEvents.serverless.service.provider = {namespace: "sample_ns"};
const httpEvent = {
relpath: '/api/foo/bar', operation: 'GET', secure_key: 'auth-token',
action: 'action-name', namespace: 'user@host.com_space', pkge: 'default', responsetype: 'json'
}
const host = 'openwhisk.somewhere.com'
const service = "my-service"
const options = { cors: false }
const result = openwhiskCompileHttpEvents.generateSwagger(service, host, options, [ httpEvent ] );
expect(result["x-ibm-configuration"].cors.enabled).to.equal(false)
});
it('should generate APIGW swagger with custom basepath API gateway option', () => {
openwhiskCompileHttpEvents.serverless.service.service = 'my-service'
openwhiskCompileHttpEvents.serverless.service.provider = {namespace: "sample_ns"};
const httpEvent = {
relpath: '/api/foo/bar', operation: 'GET', secure_key: 'auth-token',
action: 'action-name', namespace: 'user@host.com_space', pkge: 'default', responsetype: 'json'
}
const host = 'openwhisk.somewhere.com'
const service = "my-service"
const options = { basepath: "/api" }
const result = openwhiskCompileHttpEvents.generateSwagger(service, host, options, [ httpEvent ] );
expect(result.basePath).to.equal(options.basepath)
});
it('should generate APIGW swagger with custom API name option', () => {
openwhiskCompileHttpEvents.serverless.service.service = 'my-service'
openwhiskCompileHttpEvents.serverless.service.provider = {namespace: "sample_ns"};
const httpEvent = {
relpath: '/api/foo/bar', operation: 'GET', secure_key: 'auth-token',
action: 'action-name', namespace: 'user@host.com_space', pkge: 'default', responsetype: 'json'
}
const host = 'openwhisk.somewhere.com'
const service = "my-service"
const options = { name: "my-api" }
const result = openwhiskCompileHttpEvents.generateSwagger(service, host, options, [ httpEvent ] );
expect(result.info.title).to.equal(options.name)
});
it('should generate APIGW swagger with custom auth key API gateway options', () => {
openwhiskCompileHttpEvents.serverless.service.service = 'my-service'
openwhiskCompileHttpEvents.serverless.service.provider = {namespace: "sample_ns"};
const httpEvent = {
relpath: '/api/foo/bar', operation: 'GET', secure_key: 'auth-token',
action: 'action-name', namespace: 'user@host.com_space', pkge: 'default', responsetype: 'json'
}
const host = 'openwhisk.somewhere.com'
const service = "my-service"
const options = { auth: { key: "some-header-key" } }
const result = openwhiskCompileHttpEvents.generateSwagger(service, host, options, [ httpEvent ] );
expect(result.security).to.deep.equal([{"client_id": []}])
expect(result.securityDefinitions.client_id).to.deep.equal({"in": "header", type: "apiKey", "x-key-type": "clientId", name: "some-header-key" })
});
it('should generate APIGW swagger with custom auth key and secret API gateway options', () => {
openwhiskCompileHttpEvents.serverless.service.service = 'my-service'
openwhiskCompileHttpEvents.serverless.service.provider = {namespace: "sample_ns"};
const httpEvent = {
relpath: '/api/foo/bar', operation: 'GET', secure_key: 'auth-token',
action: 'action-name', namespace: 'user@host.com_space', pkge: 'default', responsetype: 'json'
}
const host = 'openwhisk.somewhere.com'
const service = "my-service"
const options = { auth: { key: "some-header-key", secret: "some-header-secret" } }
const result = openwhiskCompileHttpEvents.generateSwagger(service, host, options, [ httpEvent ] );
expect(result.security).to.deep.equal([{client_id: [], client_secret: []}])
expect(result.securityDefinitions.client_id).to.deep.equal({"in": "header", type: "apiKey", "x-key-type": "clientId", name: "some-header-key" })
expect(result.securityDefinitions.client_secret).to.deep.equal({"in": "header", type: "apiKey", "x-key-type": "clientSecret", name: "some-header-secret" })
});
it('should generate APIGW swagger with AppID OAuth provider API gateway options', () => {
openwhiskCompileHttpEvents.serverless.service.service = 'my-service'
openwhiskCompileHttpEvents.serverless.service.provider = {namespace: "sample_ns"};
const httpEvent = {
relpath: '/api/foo/bar', operation: 'GET', secure_key: 'auth-token',
action: 'action-name', namespace: 'user@host.com_space', pkge: 'default', responsetype: 'json'
}
const host = 'openwhisk.somewhere.com'
const service = "my-service"
const options = { oauth: { provider: "app-id", tenant: "some-id" } }
const result = openwhiskCompileHttpEvents.generateSwagger(service, host, options, [ httpEvent ] );
expect(result.security).to.deep.equal([{"app-id": []}])
expect(result.securityDefinitions["app-id"]).to.deep.equal({
flow: "application", tokenUrl: "", type: "oauth2",
"x-provider": {
name: "app-id", params: { tenantId: options.oauth.tenant }
},
"x-tokenintrospect": { "url": null }
})
});
it('should generate APIGW swagger with Google OAuth provider API gateway options', () => {
openwhiskCompileHttpEvents.serverless.service.service = 'my-service'
openwhiskCompileHttpEvents.serverless.service.provider = {namespace: "sample_ns"};
const httpEvent = {
relpath: '/api/foo/bar', operation: 'GET', secure_key: 'auth-token',
action: 'action-name', namespace: 'user@host.com_space', pkge: 'default', responsetype: 'json'
}
const host = 'openwhisk.somewhere.com'
const service = "my-service"
const options = { oauth: { provider: "google" } }
const result = openwhiskCompileHttpEvents.generateSwagger(service, host, options, [ httpEvent ] );
expect(result.security).to.deep.equal([{"google": []}])
expect(result.securityDefinitions["google"]).to.deep.equal({
flow: "application", tokenUrl: "", type: "oauth2",
"x-provider": { name: "google" },
"x-tokenintrospect": { url: "https://www.googleapis.com/oauth2/v3/tokeninfo"}
})
});
it('should generate APIGW swagger with Facebook OAuth provider API gateway options', () => {
openwhiskCompileHttpEvents.serverless.service.service = 'my-service'
openwhiskCompileHttpEvents.serverless.service.provider = {namespace: "sample_ns"};
const httpEvent = {
relpath: '/api/foo/bar', operation: 'GET', secure_key: 'auth-token',
action: 'action-name', namespace: 'user@host.com_space', pkge: 'default', responsetype: 'json'
}
const host = 'openwhisk.somewhere.com'
const service = "my-service"
const options = { oauth: { provider: "facebook" } }
const result = openwhiskCompileHttpEvents.generateSwagger(service, host, options, [ httpEvent ] );
expect(result.security).to.deep.equal([{"facebook": []}])
expect(result.securityDefinitions["facebook"]).to.deep.equal({
flow: "application", tokenUrl: "", type: "oauth2",
"x-provider": { name: "facebook" },
"x-tokenintrospect": { url: "https://graph.facebook.com/debug_token"}
})
});
it('should generate APIGW swagger with Github OAuth provider API gateway options', () => {
openwhiskCompileHttpEvents.serverless.service.service = 'my-service'
openwhiskCompileHttpEvents.serverless.service.provider = {namespace: "sample_ns"};
const httpEvent = {
relpath: '/api/foo/bar', operation: 'GET', secure_key: 'auth-token',
action: 'action-name', namespace: 'user@host.com_space', pkge: 'default', responsetype: 'json'
}
const host = 'openwhisk.somewhere.com'
const service = "my-service"
const options = { oauth: { provider: "github" } }
const result = openwhiskCompileHttpEvents.generateSwagger(service, host, options, [ httpEvent ] );
expect(result.security).to.deep.equal([{"github": []}])
expect(result.securityDefinitions["github"]).to.deep.equal({
flow: "application", tokenUrl: "", type: "oauth2",
"x-provider": { name: "github" },
"x-tokenintrospect": { url: "https://api.github.com/user"}
})
});
it('should generate APIGW swagger with rate limiting API gateway options', () => {
openwhiskCompileHttpEvents.serverless.service.service = 'my-service'
openwhiskCompileHttpEvents.serverless.service.provider = {namespace: "sample_ns"};
const httpEvent = {
relpath: '/api/foo/bar', operation: 'GET', secure_key: 'auth-token',
action: 'action-name', namespace: 'user@host.com_space', pkge: 'default', responsetype: 'json'
}
const host = 'openwhisk.somewhere.com'
const service = "my-service"
const options = { rate_limit: { rate: 1000, unit: "minute"} }
const result = openwhiskCompileHttpEvents.generateSwagger(service, host, options, [ httpEvent ] );
expect(result["x-ibm-rate-limit"]).to.deep.equal([{rate: 1000, unit: "minute", units: 1}])
});
it('should throw if API GW auth options are invalid', () => {
openwhiskCompileHttpEvents.serverless.service.service = 'my-service'
openwhiskCompileHttpEvents.serverless.service.provider = {namespace: "sample_ns"};
const httpEvent = {
relpath: '/api/foo/bar', operation: 'GET', secure_key: 'auth-token',
action: 'action-name', namespace: 'user@host.com_space', pkge: 'default', responsetype: 'json'
}
const host = 'openwhisk.somewhere.com'
const service = "my-service"
let options = { auth: { secret: "something" } }
expect(() => openwhiskCompileHttpEvents.generateSwagger(service, host, options, [ httpEvent ]))
.to.throw(Error, /Missing mandatory resources.apigw.auth.key/);
options = { rate_limit: { } }
expect(() => openwhiskCompileHttpEvents.generateSwagger(service, host, options, [ httpEvent ]))
.to.throw(Error, /Missing rate limit parameter: rate/);
options = { rate_limit: { rate: 1000 } }
expect(() => openwhiskCompileHttpEvents.generateSwagger(service, host, options, [ httpEvent ]))
.to.throw(Error, /Missing rate limit parameter: unit/);
options = { rate_limit: { rate: 1000, unit: 'blah' } }
expect(() => openwhiskCompileHttpEvents.generateSwagger(service, host, options, [ httpEvent ]))
.to.throw(Error, /Invalid rate limit parameter: unit/);
options = { oauth: { provider: 'blah' } }
expect(() => openwhiskCompileHttpEvents.generateSwagger(service, host, options, [ httpEvent ]))
.to.throw(Error, /OAuth defined with invalid provider/);
options = { oauth: { provider: 'app-id' } }
expect(() => openwhiskCompileHttpEvents.generateSwagger(service, host, options, [ httpEvent ]))
.to.throw(Error, /OAuth provider app-id defined without tenant parameter/);
});
});
});
================================================
FILE: compile/cloudant/README.md
================================================
# Cloudant Events
This plugins compiles the `cloudant` events in `serverless.yaml` to corresponding [OpenWhisk Cloudant Trigger Feeds](https://github.com/openwhisk/openwhisk-package-cloudant) definitions.
## How it works
`Compile Cloudant` hooks into the [`package:compileEvents`](/lib/plugins/deploy) lifecycle.
It loops over all schedule event which are defined in `serverless.yaml`.
### Using Package Parameters
IBM Cloudant instances can be provisioned through the IBM Bluemix platform. OpenWhisk on Bluemix will export Cloudant service credentials bound to a package with the following name:
```
/${BLUEMIX_ORG}_${BLUEMIX_SPACE}/Bluemix_${SERVICE_NAME}_Credentials-1
```
Rather than having to manually define all the properties needed by the Cloudant trigger feed, you can reference a package to use instead. Credentials from the referenced package will be used when executing the trigger feed.
Developers only need to add the database to listen to for each trigger.
```yaml
# serverless.yaml
functions:
index:
handler: users.main
events:
- cloudant:
package: /${BLUEMIX_ORG}_${BLUEMIX_SPACE}/Bluemix_${SERVICE_NAME}_Credentials-1
db: db_name
```
The plugin will create a trigger called `${serviceName}_${fnName}_cloudant_${db}` and a rule called `${serviceName}_${fnName}_cloudant_${db}_rule` to bind the function to the database update events.
The trigger and rule names created can be set explicitly using the `trigger` and `rule` parameters.
Other functions can bind to the same trigger using the inline `trigger` event referencing this trigger name.
```yaml
# serverless.yaml
functions:
index:
handler: users.main
events:
- cloudant:
package: /${BLUEMIX_ORG}_${BLUEMIX_SPACE}/Bluemix_${SERVICE_NAME}_Credentials-1
db: my_db
trigger: db_events
rule: connect_index_to_db
another:
handler: users.another
events:
- trigger: db_events
```
### Using Manual Parameters
Trigger feed parameters for the Cloudant event source can be defined explicitly, rather than using pulling credentials from a package.
```yaml
# serverless.yaml
functions:
index:
handler: users.main
events:
- cloudant:
host: xxx-yyy-zzz-bluemix.cloudant.com
username: USERNAME
password: PASSWORD
db: db_name
```
### Adding Optional Parameters
The following optional feed parameters are also supported:
* `max` - Maximum number of triggers to fire. Defaults to infinite.
* `filter` - Filter function defined on a design document.
* `query` - Optional query parameters for the filter function.
```yaml
# serverless.yaml
functions:
index:
handler: users.main
events:
- cloudant:
...
max: 10000
query:
status: new
filter: mailbox/by_status
```
================================================
FILE: compile/cloudant/index.js
================================================
'use strict';
const BbPromise = require('bluebird');
class OpenWhiskCompileCloudant {
constructor(serverless, options) {
this.serverless = serverless;
this.options = options;
this.provider = this.serverless.getProvider('openwhisk');
this.default_package = '/whisk.system/cloudant'
this.hooks = {
'before:package:compileEvents': () => BbPromise.bind(this)
.then(this.setup)
.then(this.processCloudantEvents)
};
}
setup() {
if (!this.serverless.service.resources) {
this.serverless.service.resources = {};
}
if (!this.serverless.service.resources.triggers) {
this.serverless.service.resources.triggers = {};
}
if (!this.serverless.service.resources.rules) {
this.serverless.service.resources.rules = {};
}
}
validateConfig (fnName, config) {
if (!config.db) {
throw new this.serverless.classes.Error(
`Cloudant event property (db) missing on function: ${fnName}`
)
}
if (!config.package) {
const config_properties = ['db', 'password', 'username', 'host']
if (!config.db) {
throw new this.serverless.classes.Error(
`Cloudant event property (db) missing on function: ${fnName}`
)
}
if (!config.host) {
throw new this.serverless.classes.Error(
`Cloudant event property (host) missing on function: ${fnName}`
)
}
const has_manual_auth = !!config.username && !!config.password
const has_iam_auth = !!config.iam_api_key
if (!has_manual_auth && !has_iam_auth) {
throw new this.serverless.classes.Error(
`Cloudant event authentication property (username & password or iam_api_key) missing on function: ${fnName}`
)
}
}
}
compileCloudantTrigger (fnName, config) {
this.validateConfig(fnName, config)
const name = config.trigger || this.defaultCloudantName(fnName, config.db)
const feed = `${config.package || this.default_package}/changes`
const feed_parameters = {
dbname: config.db
}
if (!config.package) {
if (config.iam_api_key) {
feed_parameters.iamApiKey = config.iam_api_key
} else {
feed_parameters.username = config.username
feed_parameters.password = config.password
}
feed_parameters.host = config.host
}
if (config.max) {
feed_parameters.maxTriggers = config.max
}
if (config.query) {
feed_parameters.query_params = config.query
}
if (config.filter) {
feed_parameters.filter = config.filter
}
return { name, content: { feed, feed_parameters } }
}
defaultCloudantName (fnName, db) {
return `${this.serverless.service.service}_${fnName}_cloudant_${db}`
}
processCloudantEvent (fnName, config) {
const fnObj = this.serverless.service.getFunction(fnName)
const trigger = this.compileCloudantTrigger(fnName, config)
const rule = config.rule || `${this.defaultCloudantName(fnName, config.db)}_rule`
fnObj.events.push({ trigger: { name: trigger.name, rule } })
this.serverless.service.resources.triggers[trigger.name] = trigger.content
}
processCloudantEvents () {
this.serverless.service.getAllFunctions().forEach(name => {
const fn = this.serverless.service.getFunction(name)
const events = (fn.events || []).filter(e => e.cloudant)
events.forEach(e => this.processCloudantEvent(name, e.cloudant))
})
}
}
module.exports = OpenWhiskCompileCloudant
================================================
FILE: compile/cloudant/tests/index.js
================================================
'use strict';
const expect = require('chai').expect;
const chaiAsPromised = require('chai-as-promised');
require('chai').use(chaiAsPromised);
const sinon = require('sinon');
const OpenWhiskCompileCloudant = require('../index');
describe('OpenWhiskCompileCloudant', () => {
let serverless;
let sandbox;
let openwhiskCompileCloudant;
beforeEach(() => {
sandbox = sinon.sandbox.create();
serverless = {classes: {Error}, service: {provider: {}, resources: {}, getAllFunctions: () => []}, getProvider: sandbox.spy()};
const options = {
stage: 'dev',
region: 'us-east-1',
};
openwhiskCompileCloudant = new OpenWhiskCompileCloudant(serverless, options);
serverless.service.service = 'serviceName';
serverless.service.provider = {
namespace: 'testing',
apihost: '',
auth: '',
};
serverless.cli = { log: () => {} };
openwhiskCompileCloudant.setup()
});
afterEach(() => {
sandbox.restore();
});
describe('#processCloudantEvents()', () => {
it('should call processCloudantEvent for each cloudant event.', () => {
const service = openwhiskCompileCloudant.serverless.service;
const fns = {
first: {
events: [{}, {cloudant: {package: 'testing_package', db: 'some_db'}}, {trigger: true}]
},
second: {
events: [{cloudant: {package: 'another_package', db: 'some_db'}}]
},
third: {}
}
service.getAllFunctions = () => Object.keys(fns)
service.getFunction = name => fns[name];
const spy = openwhiskCompileCloudant.processCloudantEvent = sinon.spy()
openwhiskCompileCloudant.processCloudantEvents()
expect(spy.calledTwice).to.be.equal(true)
expect(spy.withArgs("first", {package: 'testing_package', db: 'some_db'}).calledOnce).to.be.equal(true)
expect(spy.withArgs("second", {package: 'another_package', db: 'some_db'}).calledOnce).to.be.equal(true)
})
})
describe('#processCloudantEvents()', () => {
it('should create trigger & rules and update manifest resources.', () => {
const cloudant = { package: 'some_package', db: 'testing' }
const fnObj = { events: [{cloudant}] }
serverless.service.getFunction = () => fnObj
openwhiskCompileCloudant.compileCloudantTrigger = () => ({name: 'serviceName_fnName_cloudant_testing', content: { a: 1 }})
openwhiskCompileCloudant.processCloudantEvent("fnName", fnObj.events[0].cloudant)
expect(fnObj.events[1]).to.be.deep.equal({
trigger: { name: 'serviceName_fnName_cloudant_testing', rule: 'serviceName_fnName_cloudant_testing_rule' }
})
expect(serverless.service.resources.triggers).to.be.deep.equal({serviceName_fnName_cloudant_testing: {a: 1}})
})
})
describe('#compileCloudantTrigger()', () => {
it('should throw errors for missing db parameter.', () => {
expect(() => openwhiskCompileCloudant.compileCloudantTrigger('testing', {}))
.to.throw(Error, 'Cloudant event property (db) missing on function: testing');
})
it('should throw errors for missing host parameters without package', () => {
const config = { db: 'dbname', username: 'user', password: 'password' }
expect(() => openwhiskCompileCloudant.compileCloudantTrigger('testing', config))
.to.throw(Error, `Cloudant event property (host) missing on function: testing`);
})
it('should throw errors for missing username parameter without package', () => {
const config = { db: 'dbname', host: 'host.com', password: 'password' }
expect(() => openwhiskCompileCloudant.compileCloudantTrigger('testing', config))
.to.throw(Error, `Cloudant event authentication property (username & password or iam_api_key) missing on function: testing`);
})
it('should throw errors for missing password parameter without package', () => {
const config = { db: 'dbname', host: 'host.com', username: 'username' }
expect(() => openwhiskCompileCloudant.compileCloudantTrigger('testing', config))
.to.throw(Error, `Cloudant event authentication property (username & password or iam_api_key) missing on function: testing`);
})
it('should throw errors for missing authentication parameters without package', () => {
const config = { db: 'dbname', host: 'host.com' }
expect(() => openwhiskCompileCloudant.compileCloudantTrigger('testing', config))
.to.throw(Error, `Cloudant event authentication property (username & password or iam_api_key) missing on function: testing`);
})
it('should return trigger for cloudant provider using package.', () => {
const db = 'my_db', pkge = '/bluemixOrg_bluemixSpace/packageId'
const trigger = openwhiskCompileCloudant.compileCloudantTrigger('testing', { db, 'package': pkge })
expect(trigger).to.be.deep.equal({
name: `${serverless.service.service}_testing_cloudant_${db}`,
content: {
feed: `${pkge}/changes`,
feed_parameters: {
dbname: `${db}`
}
}
})
})
it('should return trigger for cloudant provider with manual username & password configuration.', () => {
const config = { db: 'dbname', username: 'user', password: 'password', host: 'hostname' }
const trigger = openwhiskCompileCloudant.compileCloudantTrigger('testing', config)
expect(trigger).to.be.deep.equal({
name: `${serverless.service.service}_testing_cloudant_${config.db}`,
content: {
feed: `/whisk.system/cloudant/changes`,
feed_parameters: {
username: config.username,
password: config.password,
host: config.host,
dbname: config.db
}
}
})
})
it('should return trigger for cloudant provider with manual iam api key configuration.', () => {
const config = { db: 'dbname', iam_api_key: 'api_key', host: 'hostname' }
const trigger = openwhiskCompileCloudant.compileCloudantTrigger('testing', config)
expect(trigger).to.be.deep.equal({
name: `${serverless.service.service}_testing_cloudant_${config.db}`,
content: {
feed: `/whisk.system/cloudant/changes`,
feed_parameters: {
iamApiKey: config.iam_api_key,
host: config.host,
dbname: config.db
}
}
})
})
it('should return trigger with optional configuration parameters.', () => {
const config = { db: 'dbname', username: 'user', password: 'password', host: 'hostname', max: 10000, query: { key: 'value' }, filter: 'some/view' }
const trigger = openwhiskCompileCloudant.compileCloudantTrigger('testing', config)
expect(trigger).to.be.deep.equal({
name: `${serverless.service.service}_testing_cloudant_${config.db}`,
content: {
feed: `/whisk.system/cloudant/changes`,
feed_parameters: {
username: config.username,
password: config.password,
host: config.host,
dbname: config.db,
maxTriggers: 10000,
filter: 'some/view',
query_params: {
key: 'value'
}
}
}
})
})
})
});
================================================
FILE: compile/functions/README.md
================================================
# Compile Functions
This plugins compiles the functions in `serverless.yaml` to corresponding [OpenWhisk Action](https://github.com/openwhisk/openwhisk/blob/master/docs/actions.md)
definitions.
## How it works
`Compile Functions` hooks into the [`package:compileFunctions`](/lib/plugins/deploy) lifecycle.
It loops over all functions which are defined in `serverless.yaml`.
Inside the function loop it creates corresponding OpenWhisk Action definition based on the settings
(e.g. function `name` property or service `defaults`) which are provided in the `serverless.yaml` file.
The function will be called `<serviceName>_<functionName>` by default but you
can specify an alternative name with the help of the functions `name` property.
The Action namespace defaults to the service provider namespace but can be set
manually, using the `namespace` parameter.
The functions `MemorySize` is set to `256`, `Timeout to `60` and `Runtime` to `nodejs`. You can overwrite those defaults by setting
corresponding entries in the function definition or server provider properties.
At the end all OpenWhisk Action definitions are merged inside the `serverless.service.actions` section.
### Action Rules
Action [Rules](https://github.com/openwhisk/openwhisk/blob/master/docs/triggers_rules.md), binding Actions to [Triggers](https://github.com/openwhisk/openwhisk/blob/master/docs/triggers_rules.md), can be defined using the `events` property.
```yaml
# serverless.yaml
functions:
index:
handler: users.main
events:
- triggers:
trigger: "myTriggerName"
```
This definition will create a new Rule, which binds the configured Action to the
Trigger.
More documentation on the Rules configuration can be found in the [`compileRules` plugin](../rules).
================================================
FILE: compile/functions/index.js
================================================
'use strict';
const BbPromise = require('bluebird');
const Runtimes = require('./runtimes/index.js')
class OpenWhiskCompileFunctions {
constructor(serverless, options) {
this.serverless = serverless;
this.options = options;
this.provider = this.serverless.getProvider('openwhisk');
this.runtimes = new Runtimes(serverless)
this.hooks = {
'before:package:createDeploymentArtifacts': () => BbPromise.bind(this)
.then(this.excludes)
.then(this.disableSeqPackaging),
'before:package:compileFunctions': this.setup.bind(this),
'package:compileFunctions': this.compileFunctions.bind(this),
};
}
// Ensure we don't bundle provider plugin with service artifact.
excludes() {
const exclude = this.serverless.service.package.exclude || [];
exclude.push("node_modules/serverless-openwhisk/**");
this.serverless.service.package.exclude = exclude;
}
disableSeqPackaging() {
this.serverless.service.getAllFunctions().forEach(functionName => {
const functionObject = this.serverless.service.getFunction(functionName);
if (functionObject.sequence) {
Object.assign(functionObject, { package: { disable: true } })
}
})
}
setup() {
// This object will be used to store the Action resources, passed directly to
// the OpenWhisk SDK during the deploy process.
this.serverless.service.actions = {};
}
calculateFunctionName(functionName, functionObject) {
return functionObject.name || `${this.serverless.service.service}_${functionName}`;
}
calculateFunctionNameSpace(functionName, functionObject) {
return functionObject.namespace || this.serverless.service.provider.namespace;
}
calculateMemorySize(functionObject) {
return functionObject.memory || this.serverless.service.provider.memory;
}
calculateConcurrency(functionObject) {
return functionObject.concurrency || this.serverless.service.provider.concurrency;
}
calculateTimeout(functionObject) {
return functionObject.timeout || this.serverless.service.provider.timeout;
}
calculateOverwrite(functionObject) {
let Overwrite = true;
if (functionObject.hasOwnProperty('overwrite')) {
Overwrite = functionObject.overwrite;
} else if (this.serverless.service.provider.hasOwnProperty('overwrite')) {
Overwrite = this.serverless.service.provider.overwrite;
}
return Overwrite;
}
compileFunctionAction(params) {
return {
actionName: params.FunctionName,
namespace: params.NameSpace,
overwrite: params.Overwrite,
action: {
exec: params.Exec,
limits: {
timeout: params.Timeout ? (params.Timeout * 1000) : undefined,
memory: params.MemorySize,
concurrency: params.Concurrency,
},
parameters: params.Parameters,
annotations: params.Annotations
},
};
}
// This method takes the function handler definition, parsed from the user's YAML file,
// and turns it into the OpenWhisk Action resource object.
//
// These resource objects are passed to the OpenWhisk SDK to create the associated Actions
// during the deployment process.
//
// Parameter values will be parsed from the user's YAML definition, either as a value from
// the function handler definition or the service provider defaults.
compileFunction(functionName, functionObject) {
return this.runtimes.exec(functionObject).then(Exec => {
const FunctionName = this.calculateFunctionName(functionName, functionObject);
const NameSpace = this.calculateFunctionNameSpace(functionName, functionObject);
const MemorySize = this.calculateMemorySize(functionObject);
const Timeout = this.calculateTimeout(functionObject);
const Overwrite = this.calculateOverwrite(functionObject);
const Concurrency = this.calculateConcurrency(functionObject);
// optional action parameters
const Parameters = Object.keys(functionObject.parameters || {})
.map(key => ({ key, value: functionObject.parameters[key] }));
// optional action annotations
const Annotations = this.constructAnnotations(functionObject.annotations);
return this.compileFunctionAction(
{ FunctionName, NameSpace, Overwrite, Exec, Timeout, MemorySize, Concurrency, Parameters, Annotations }
);
});
}
constructAnnotations (annotations) {
if (!annotations) return []
// finalise action parameters when exposing as external HTTP endpoint.
// mirrors behaviour from OpenWhisk CLI.
if (annotations['web-export']) {
annotations['final'] = true
}
const converted = Object.keys(annotations)
.map(key => ({ key, value: annotations[key] }));
return converted
}
logCompiledFunction (name, fn) {
const clone = JSON.parse(JSON.stringify(fn))
if (clone.action.exec.code) {
clone.action.exec.code = '<hidden>'
}
this.serverless.cli.log(`Compiled Function (${name}): ${JSON.stringify(clone)}`);
}
compileFunctions() {
this.serverless.cli.log('Compiling Functions...');
if (!this.serverless.service.actions) {
throw new this.serverless.classes.Error(
'Missing Resources section from OpenWhisk Resource Manager template');
}
const functionPromises = this.serverless.service.getAllFunctions().map((functionName) => {
const functionObject = this.serverless.service.getFunction(functionName);
if (!functionObject.handler && !functionObject.sequence) {
throw new this.serverless.classes
.Error(`Missing "handler" or "sequence" property in function ${functionName}`);
}
if (functionObject.handler && functionObject.sequence) {
throw new this.serverless.classes
.Error(`Found both "handler" and "sequence" properties in function ${functionName}, please choose one.`);
}
const functions = this.serverless.service.actions;
const err = () => {
throw new this.serverless.classes
.Error(`Unable to read handler file in function ${functionName}`);
};
let compileFn = this.compileFunction(functionName, functionObject)
.then(newFunction => (functions[functionName] = newFunction))
if (this.options.verbose) {
compileFn = compileFn.then(fn => this.logCompiledFunction(functionName, fn))
}
return compileFn.catch(err);
});
return BbPromise.all(functionPromises);
}
}
module.exports = OpenWhiskCompileFunctions;
================================================
FILE: compile/functions/runtimes/base.js
================================================
'use strict';
const fs = require('fs-extra')
const JSZip = require('jszip')
const BbPromise = require('bluebird')
class BaseRuntime {
constructor(serverless) {
this.serverless = serverless
}
match (functionObject) {
if (!functionObject.hasOwnProperty('handler')) return false
const runtime = this.calculateRuntime(functionObject)
return !!(runtime && runtime.startsWith(this.kind))
}
exec (functionObject) {
const main = this.calculateFunctionMain(functionObject);
const kind = this.calculateKind(functionObject);
const exec = { main, kind }
if (functionObject.hasOwnProperty('image')) {
exec.image = functionObject.image
}
return this.generateActionPackage(functionObject).then(code => Object.assign(exec, { code }))
}
calculateFunctionMain(functionObject) {
const splitted = functionObject.handler.split('.');
if (splitted.length < 2) {
return functionObject;
}
return splitted[splitted.length - 1];
}
calculateRuntime(functionObject) {
return functionObject.runtime || this.serverless.service.provider.runtime
}
calculateDefaultRuntime(functionObject) {
const runtime = this.calculateRuntime(functionObject)
return runtime.includes(':') ? runtime : `${runtime}:default`
}
calculateKind(functionObject) {
if (functionObject.hasOwnProperty('image')) return 'blackbox'
return this.calculateDefaultRuntime(functionObject)
}
isValidFile (handlerFile) {
return fs.existsSync(handlerFile)
}
convertHandlerToPath(functionHandler) {
const lastDot = functionHandler.lastIndexOf('.');
if (lastDot === -1) {
return functionHandler;
}
return functionHandler.substring(0, lastDot) + this.extension;
}
convertHandlerToPathInZip(functionHandler) {
let path = this.convertHandlerToPath(functionHandler);
while (path.startsWith('../')) {
path = path.substring(3);
}
return path;
}
generateActionPackage(functionObject) {
// Check that handler file exists
const handlerFile = this.convertHandlerToPath(functionObject.handler);
if (!this.isValidFile(handlerFile)) {
throw new this.serverless.classes.Error(`Function handler (${handlerFile}) does not exist.`)
}
// Generate action package
const handlerFileZipPath = this.convertHandlerToPathInZip(functionObject.handler);
return this.getArtifactZip(functionObject)
.then(zip => this.processActionPackage(handlerFileZipPath, zip))
.then(zip => zip.generateAsync(
{ type: 'nodebuffer', compression: 'DEFLATE', compressionOptions: { level: 9 } }
))
.then(buf => buf.toString('base64'));
}
getArtifactZip(functionObject) {
const artifactPath = this.getArtifactPath(functionObject)
const readFile = BbPromise.promisify(fs.readFile);
return readFile(artifactPath).then(zipBuffer => JSZip.loadAsync(zipBuffer))
}
getArtifactPath(functionObject) {
return this.serverless.service.package.individually ?
functionObject.package.artifact : this.serverless.service.package.artifact;
}
}
module.exports = BaseRuntime
================================================
FILE: compile/functions/runtimes/binary.js
================================================
'use strict';
const BaseRuntime = require('./base')
class Binary extends BaseRuntime {
constructor (serverless) {
super(serverless)
this.kind = 'binary'
}
exec (functionObject) {
const image = 'openwhisk/dockerskeleton'
const kind = 'blackbox'
return this.generateActionPackage(functionObject).then(code => ({ image, kind, code }))
}
processActionPackage (handlerFile, zip) {
return zip.file(handlerFile).async('nodebuffer').then(data => {
zip.remove(handlerFile)
return zip.file('exec', data)
})
}
calculateKind (functionObject) {
return `blackbox`
}
convertHandlerToPath (functionHandler) {
return functionHandler
}
}
module.exports = Binary
================================================
FILE: compile/functions/runtimes/docker.js
================================================
'use strict';
class Docker {
constructor(serverless) {
this.serverless = serverless
}
match (functionObject) {
if (!functionObject.hasOwnProperty('handler')) return false
return this.calculateRuntime(functionObject) === 'docker'
}
exec (functionObject) {
return { kind: 'blackbox', image: functionObject.handler }
}
calculateRuntime(functionObject) {
return functionObject.runtime || this.serverless.service.provider.runtime
}
}
module.exports = Docker
================================================
FILE: compile/functions/runtimes/index.js
================================================
'use strict';
const Binary = require('./binary')
const Docker = require('./docker')
const Node = require('./node')
const Python = require('./python')
const Swift = require('./swift')
const Php = require('./php')
const Sequence = require('./sequence')
const Java = require('./java')
const Ruby = require('./ruby')
class Runtimes {
constructor(serverless) {
this.serverless = serverless;
this.runtimes = [
new Binary(serverless),
new Docker(serverless),
new Node(serverless),
new Python(serverless),
new Swift(serverless),
new Php(serverless),
new Sequence(serverless),
new Java(serverless),
new Ruby(serverless)
];
}
exec (functionObj) {
const matched = this.runtimes.find(runtime => runtime.match(functionObj))
if (matched) return Promise.resolve(matched.exec(functionObj))
throw new this.serverless.classes.Error(
'This runtime is not currently supported by the OpenWhisk provider plugin.');
}
}
module.exports = Runtimes
================================================
FILE: compile/functions/runtimes/java.js
================================================
'use strict';
const fs = require('fs-extra');
const BbPromise = require('bluebird');
const BaseRuntime = require('./base');
const JSZip = require('jszip');
class Java extends BaseRuntime {
constructor(serverless) {
super(serverless);
this.kind = 'java';
this.extension = '.jar';
}
convertHandlerToPath(functionHandler) {
const lastColon = functionHandler.lastIndexOf(':');
if (lastColon === -1) {
return functionHandler;
}
return functionHandler.substring(0, lastColon);
}
// Main class has to be defined as an annotation, otherwise it will assume the main class is called 'Main'.
calculateFunctionMain(functionObject) {
if (functionObject.handler) {
const splitted = functionObject.handler.split(':');
if (splitted.length > 1 && splitted[splitted.length - 1]) {
return splitted[splitted.length - 1];
}
}
return 'Main';
}
// Ensure zip package used to deploy action has the correct artifacts for the runtime by only
// including the deployable JAR file.
processActionPackage(handlerFile, zip) {
return zip;
}
}
module.exports = Java;
================================================
FILE: compile/functions/runtimes/node.js
================================================
'use strict';
const BaseRuntime = require('./base')
class Node extends BaseRuntime {
constructor (serverless) {
super(serverless)
this.kind = 'nodejs'
this.extension = '.js'
}
calculateRuntime(functionObject) {
return super.calculateRuntime(functionObject) || 'nodejs:default'
}
processActionPackage (handlerFile, zip) {
zip.file("package.json", JSON.stringify({main: handlerFile}))
return zip
}
//We're handling a special case here which is that if TypeScript is being used
//we won't actually have a ".js" file (this.extension), instead we'll have a "ts"
//which should still be considered safe enough, or at least shouldn't
//completely stop the deployment process
isValidFile(handlerFile) {
return super.isValidFile(handlerFile) || this.isValidTypeScriptFile(handlerFile);
}
//Check for TypeScript version of handler file
isValidTypeScriptFile(handlerFile) {
//replaces the last occurance of `.js` with `.ts`, case insensitive
const typescriptHandlerFile = handlerFile.replace(/\.js$/gi, ".ts");
return super.isValidFile(typescriptHandlerFile);
}
}
module.exports = Node
================================================
FILE: compile/functions/runtimes/php.js
================================================
'use strict';
const BaseRuntime = require('./base')
class Php extends BaseRuntime {
constructor (serverless) {
super(serverless)
this.kind = 'php'
this.extension = '.php'
}
processActionPackage (handlerFile, zip) {
return zip.file(handlerFile).async('nodebuffer').then(data => {
zip.remove(handlerFile)
return zip.file('index.php', data)
})
}
}
module.exports = Php
================================================
FILE: compile/functions/runtimes/python.js
================================================
'use strict';
const BaseRuntime = require('./base')
class Python extends BaseRuntime {
constructor (serverless) {
super(serverless)
this.kind = 'python'
this.extension = '.py'
}
processActionPackage (handlerFile, zip) {
return zip.file(handlerFile).async('nodebuffer').then(data => {
zip.remove(handlerFile)
return zip.file('__main__.py', data)
})
}
calculateDefaultRuntime (functionObject) {
return this.calculateRuntime(functionObject)
}
}
module.exports = Python
================================================
FILE: compile/functions/runtimes/ruby.js
================================================
'use strict';
const BaseRuntime = require('./base')
class Ruby extends BaseRuntime {
constructor (serverless) {
super(serverless)
this.kind = 'ruby'
this.extension = '.rb'
}
processActionPackage (handlerFile, zip) {
return zip.file(handlerFile).async('nodebuffer').then(data => {
zip.remove(handlerFile)
return zip.file('main.rb', data)
})
}
}
module.exports = Ruby
================================================
FILE: compile/functions/runtimes/sequence.js
================================================
'use strict';
class Sequence {
constructor(serverless) {
this.serverless = serverless
}
match (functionObject) {
return functionObject.hasOwnProperty('sequence')
}
exec (functionObject) {
// sequence action names must be fully qualified.
// use default namespace if this is missing.
const components = functionObject.sequence.map(name => {
if (name.startsWith('/')) {
return name
}
const func = this.serverless.service.getFunction(name)
return `/_/${func.name}`
})
return { kind: 'sequence', components }
}
}
module.exports = Sequence
================================================
FILE: compile/functions/runtimes/swift.js
================================================
'use strict';
const fs = require('fs-extra')
const BaseRuntime = require('./base')
const JSZip = require("jszip")
class Swift extends BaseRuntime {
constructor (serverless) {
super(serverless)
this.kind = 'swift'
this.extension = '.swift'
}
convertHandlerToPath (functionHandler) {
if (this.isZipFile(functionHandler)) {
return functionHandler
}
return super.convertHandlerToPath(functionHandler)
}
calculateFunctionMain(functionObject) {
if (this.isZipFile(functionObject.handler)) {
return 'main'
}
return super.calculateFunctionMain(functionObject)
}
isZipFile (path) {
return path.endsWith('.zip')
}
readHandlerFile (path) {
const contents = fs.readFileSync(path)
const encoding = this.isZipFile(path) ? 'base64' : 'utf8'
return contents.toString(encoding)
}
exec (functionObject) {
const main = this.calculateFunctionMain(functionObject);
const kind = this.calculateKind(functionObject);
const handlerPath = this.convertHandlerToPath(functionObject.handler)
if (!this.isValidFile(handlerPath)) {
throw new this.serverless.classes.Error(`Function handler (${handlerPath}) does not exist.`)
}
const code = this.readHandlerFile(handlerPath)
const binary = this.isZipFile(handlerPath)
const exec = { main, kind, code, binary }
if (functionObject.hasOwnProperty('image')) {
exec.image = functionObject.image
}
return Promise.resolve(exec)
}
}
module.exports = Swift
================================================
FILE: compile/functions/runtimes/tests/all.js
================================================
'use strict';
require('./index');
require('./base');
require('./node');
require('./docker');
require('./python');
require('./swift');
require('./php');
require('./binary');
require('./sequence');
require('./java');
require('./ruby');
================================================
FILE: compile/functions/runtimes/tests/base.js
================================================
'use strict';
const expect = require('chai').expect;
const BaseRuntime = require('../base');
describe('Base', () => {
const base = new BaseRuntime();
base.extension = '.js';
describe('#calculateFunctionMain()', () => {
it('should extract the main function for a given file handler', () => {
const functionObject = { handler: 'index.main' };
const result = base.calculateFunctionMain(functionObject);
expect(result).to.equal('main');
});
it('should return the input for a given file handler without exported function', () => {
const functionObject = { handler: 'index' };
const result = base.calculateFunctionMain(functionObject);
expect(result).to.equal(functionObject);
});
it('should extract the main function for a given path handler', () => {
const functionObject = { handler: 'myFunction@0.1.0/index.main' };
const result = base.calculateFunctionMain(functionObject);
expect(result).to.equal('main');
});
it('should extract the main function for a given relative path handler', () => {
const functionObject = { handler: '../myFunction/index.main' };
const result = base.calculateFunctionMain(functionObject);
expect(result).to.equal('main');
});
});
describe('#convertHandlerToPathInZip()', () => {
it('should extract the path in zip for a given file handler', () => {
const result = base.convertHandlerToPathInZip('index.main');
expect(result).to.equal('index.js');
});
it('should return the input for a given file handler without exported function', () => {
const result = base.convertHandlerToPathInZip('index');
expect(result).to.equal('index');
});
it('should extract the path in zip for a given path handler', () => {
const result = base.convertHandlerToPathInZip('myFunction@0.1.0/index.main');
expect(result).to.equal('myFunction@0.1.0/index.js');
});
it('should extract the path in zip for a given relative path handler', () => {
const result = base.convertHandlerToPathInZip('../myFunction@0.1.0/index.main');
expect(result).to.equal('myFunction@0.1.0/index.js');
});
});
describe('#convertHandlerToPath()', () => {
it('should extract the path for a given file handler', () => {
const result = base.convertHandlerToPath('index.main');
expect(result).to.equal('index.js');
});
it('should return the input for a given file handler without exported function', () => {
const result = base.convertHandlerToPath('index');
expect(result).to.equal('index');
});
it('should extract the path for a given path handler', () => {
const result = base.convertHandlerToPath('myFunction@0.1.0/index.main');
expect(result).to.equal('myFunction@0.1.0/index.js');
});
it('should extract the path for a given relative path handler', () => {
const result = base.convertHandlerToPath('../myFunction@0.1.0/index.main');
expect(result).to.equal('../myFunction@0.1.0/index.js');
});
});
});
================================================
FILE: compile/functions/runtimes/tests/binary.js
================================================
'use strict';
const expect = require('chai').expect;
const chaiAsPromised = require('chai-as-promised');
require('chai').use(chaiAsPromised);
const sinon = require('sinon');
const Binary = require('../binary');
const JSZip = require("jszip");
const fs = require('fs-extra');
describe('Binary', () => {
let serverless;
let node;
let sandbox;
beforeEach(() => {
sandbox = sinon.sandbox.create();
serverless = {classes: {Error}, service: {}, getProvider: sandbox.spy()};
serverless.service.provider = { name: 'openwhisk' };
node = new Binary(serverless);
});
afterEach(() => {
sandbox.restore();
});
describe('#match()', () => {
it('should match with explicit runtime', () => {
serverless.service.provider.runtime = 'nodejs';
expect(node.match({runtime: 'binary', handler: 'bin_file'})).to.equal(true)
});
it('should match with provider runtime', () => {
serverless.service.provider.runtime = 'binary';
expect(node.match({handler: 'bin_file'})).to.equal(true)
});
it('should not match when wrong explicit runtime', () => {
expect(node.match({runtime: 'nodejs', handler: 'bin_file'})).to.equal(false)
});
it('should not match when wrong provider runtime', () => {
serverless.service.provider.runtime = 'nodejs';
expect(node.match({handler: 'bin_file'})).to.equal(false)
});
it('should not match default runtime', () => {
expect(node.match({handler: 'bin_file'})).to.equal(false)
});
it('should not match when missing handler', () => {
expect(node.match({})).to.equal(false)
});
});
describe('#exec()', () => {
it('should return binary exec definition', () => {
const fileContents = 'zip file contents';
const handler = 'bin_file';
const exec = { image: 'openwhisk/dockerskeleton', kind: 'blackbox', code: new Buffer(fileContents) };
sandbox.stub(node, 'generateActionPackage', (functionObj) => {
expect(functionObj.handler).to.equal(handler);
return Promise.resolve(new Buffer(fileContents));
});
return expect(node.exec({ handler, runtime: 'binary'}))
.to.eventually.deep.equal(exec);
})
});
describe('#generateActionPackage()', () => {
it('should throw error for missing handler file', () => {
expect(() => node.generateActionPackage({handler: 'does_not_exist'}))
.to.throw(Error, 'Function handler (does_not_exist) does not exist.');
})
it('should read service artifact and add package.json for handler', () => {
node.serverless.service.package = {artifact: '/path/to/zip_file.zip'};
node.isValidFile = () => true
const zip = new JSZip();
zip.file("handler", "blah blah blah");
return zip.generateAsync({type:"nodebuffer"}).then(zipped => {
sandbox.stub(fs, 'readFile', (path, cb) => {
expect(path).to.equal('/path/to/zip_file.zip');
cb(null, zipped);
});
return node.generateActionPackage({handler: 'handler'}).then(data => {
return JSZip.loadAsync(new Buffer(data, 'base64')).then(zip => {
expect(zip.file("handler")).to.be.equal(null)
return zip.file("exec").async("string").then(code => {
expect(code).to.be.equal('blah blah blah')
})
})
})
});
})
it('should handle service artifact for individual function handler', () => {
const functionObj = {handler: 'handler', package: { artifact: '/path/to/zip_file.zip'}}
node.serverless.service.package = {individually: true};
node.isValidFile = () => true
const zip = new JSZip();
zip.file("handler", "blah blah blah");
return zip.generateAsync({type:"nodebuffer"}).then(zipped => {
sandbox.stub(fs, 'readFile', (path, cb) => {
expect(path).to.equal('/path/to/zip_file.zip');
cb(null, zipped);
});
return node.generateActionPackage(functionObj).then(data => {
return JSZip.loadAsync(new Buffer(data, 'base64')).then(zip => {
expect(zip.file("handler")).to.be.equal(null)
return zip.file("exec").async("string").then(code => {
expect(code).to.be.equal('blah blah blah')
})
})
})
});
})
})
});
================================================
FILE: compile/functions/runtimes/tests/docker.js
================================================
'use strict';
const expect = require('chai').expect;
const chaiAsPromised = require('chai-as-promised');
require('chai').use(chaiAsPromised);
const sinon = require('sinon');
const Docker = require('../docker');
describe('Docker', () => {
let serverless;
let docker;
let sandbox;
beforeEach(() => {
sandbox = sinon.sandbox.create();
serverless = {classes: {Error}, service: {}, getProvider: sandbox.spy()};
serverless.service.provider = { name: 'openwhisk' };
docker = new Docker(serverless);
});
afterEach(() => {
sandbox.restore();
});
describe('#match()', () => {
it('should match with explicit runtime', () => {
serverless.service.provider.runtime = 'nodejs';
expect(docker.match({runtime: 'docker', handler: 'repo/image'})).to.equal(true)
});
it('should match with provider runtime', () => {
serverless.service.provider.runtime = 'docker';
expect(docker.match({handler: 'repo/image'})).to.equal(true)
});
it('should not match when wrong explicit runtime', () => {
expect(docker.match({runtime: 'nodejs', handler: 'repo/image'})).to.equal(false)
});
it('should not match when wrong provider runtime', () => {
serverless.service.provider.runtime = 'nodejs';
expect(docker.match({handler: 'repo/image'})).to.equal(false)
});
it('should not match default runtime', () => {
expect(docker.match({handler: 'repo/image'})).to.equal(false)
});
it('should not match when missing handler', () => {
expect(docker.match({})).to.equal(false)
});
});
describe('#exec()', () => {
it('should return docker definition for docker image handler', () => {
const handler = 'repo/image'
const exec = { kind: 'blackbox', image: 'repo/image' };
expect(docker.exec({ runtime: 'docker', handler })).to.deep.equal(exec);
});
});
});
================================================
FILE: compile/functions/runtimes/tests/index.js
================================================
'use strict';
const expect = require('chai').expect;
const chaiAsPromised = require('chai-as-promised');
require('chai').use(chaiAsPromised);
const sinon = require('sinon');
const Runtimes = require('../index');
describe('Runtimes', () => {
let serverless;
let runtimes;
let sandbox;
beforeEach(() => {
sandbox = sinon.sandbox.create();
serverless = {classes: {Error}, service: {}, getProvider: sandbox.spy()};
runtimes = new Runtimes(serverless);
});
afterEach(() => {
sandbox.restore();
});
describe('#exec()', () => {
it('should throw error for unknown runtime', () => {
expect(() => runtimes.exec({}))
.to.throw(Error, /This runtime is not currently supported/);
});
it('should execute and return thenable exec for thenable matching runtime', () => {
const result = { foo: 'bar' }
const match = sinon.stub().returns(true)
const exec = sinon.stub().returns(Promise.resolve(result))
runtimes.runtimes = [{ match, exec }]
return runtimes.exec({}).then(resp => {
expect(resp).to.deep.equal(result)
expect(match.called).to.equal(true)
expect(exec.called).to.equal(true)
})
});
it('should execute and return thenable exec for non-thenable matching runtime', () => {
const result = { foo: 'bar' }
const match = sinon.stub().returns(true)
const exec = sinon.stub().returns(result)
runtimes.runtimes = [{ match, exec }]
return runtimes.exec({}).then(resp => {
expect(resp).to.deep.equal(result)
expect(match.called).to.equal(true)
expect(exec.called).to.equal(true)
})
});
});
});
================================================
FILE: compile/functions/runtimes/tests/java.js
================================================
'use strict';
const expect = require('chai').expect;
const chaiAsPromised = require('chai-as-promised');
require('chai').use(chaiAsPromised);
const sinon = require('sinon');
const Java = require('../java');
const JSZip = require('jszip');
const fs = require('fs-extra');
describe('Java', () => {
let serverless;
let java;
let sandbox;
beforeEach(() => {
sandbox = sinon.sandbox.create();
serverless = { classes: { Error }, service: {}, getProvider: sandbox.spy() };
serverless.service.provider = { name: 'openwhisk' };
java = new Java(serverless);
});
afterEach(() => {
sandbox.restore();
});
describe('#match()', () => {
it('should match with explicit runtime', () => {
serverless.service.provider.runtime = 'python';
expect(java.match({ runtime: 'java', handler: 'file:func' })).to.equal(true);
});
it('should match with provider runtime', () => {
serverless.service.provider.runtime = 'java';
expect(java.match({ handler: 'file:func' })).to.equal(true);
});
it('should not match when wrong explicit runtime', () => {
expect(java.match({ runtime: 'python', handler: 'file:func' })).to.equal(false);
});
it('should not match when wrong provider runtime', () => {
serverless.service.provider.runtime = 'python';
expect(java.match({ handler: 'file:func' })).to.equal(false);
});
it('should not match when missing handler', () => {
expect(java.match({})).to.equal(false);
});
});
describe('#calculateFunctionMain()', () => {
it('should return Main when no main class is defined', () => {
expect(java.calculateFunctionMain({})).to.equal('Main');
});
it('should return Main when no main class is defined, but the file is', () => {
expect(java.calculateFunctionMain({ handler: 'target/my-jar.jar' })).to.equal('Main');
});
it('should return Main when no main class is defined, but there is a colon', () => {
expect(java.calculateFunctionMain({ handler: 'target/my-jar.jar:' })).to.equal('Main');
});
it('should return the provided class when a main class is defined', () => {
expect(
java.calculateFunctionMain({ handler: 'target/my-jar.jar:my.main.class.Name' })
).to.equal('my.main.class.Name');
});
});
describe('#exec()', () => {
it('should return java exec definition', () => {
const fileContents = 'some file contents';
const handler = 'target/my-jar.jar:my.main.class.Name';
const exec = {
main: 'my.main.class.Name',
kind: 'java:default',
code: new Buffer(fileContents),
};
sandbox.stub(java, 'generateActionPackage', functionObj => {
expect(functionObj.handler).to.equal(handler);
return Promise.resolve(new Buffer(fileContents));
});
return expect(java.exec({ handler, runtime: 'java' })).to.eventually.deep.equal(exec);
});
it('should return java exec definition with custom image', () => {
const fileContents = 'some file contents';
const handler = 'target/my-jar.jar:my.main.class.Name';
const exec = {
main: 'my.main.class.Name',
kind: 'blackbox',
image: 'foo',
code: new Buffer(fileContents),
};
sandbox.stub(java, 'generateActionPackage', functionObj => {
expect(functionObj.handler).to.equal(handler);
return Promise.resolve(new Buffer(fileContents));
});
return expect(java.exec({ handler, runtime: 'java', image: 'foo' })).to.eventually.deep.equal(
exec
);
});
});
describe('#convertHandlerToPath()', () => {
it('should return file path passed', () => {
expect(java.convertHandlerToPath('target/my-jar.jar')).to.be.equal('target/my-jar.jar');
});
it('should return file path passed, excluding the colon', () => {
expect(java.convertHandlerToPath('target/my-jar.jar:')).to.be.equal('target/my-jar.jar');
});
it('should return file path passed without the class', () => {
expect(java.convertHandlerToPath('target/my-jar.jar:my.main.class.Name')).to.be.equal(
'target/my-jar.jar'
);
});
});
describe('#generateActionPackage()', () => {
it('should throw error for missing handler file', () => {
expect(() =>
java.generateActionPackage({ handler: './does_not_exist/my-jar.jar:my.main.class.Name' })
).to.throw(Error, 'Function handler (./does_not_exist/my-jar.jar) does not exist.');
});
it('should create zip file with the Java jar file for the action', () => {
java.serverless.service.package = { artifact: '/path/to/zip_file.zip' };
java.isValidFile = () => true;
const zip = new JSZip();
const source = 'binary file contents';
zip.file('target/my-jar.jar', source);
return zip.generateAsync({ type: 'nodebuffer' }).then(zipped => {
sandbox.stub(fs, 'readFile', (path, cb) => {
cb(null, zipped);
});
return java
.generateActionPackage({ handler: 'target/my-jar.jar:my.main.class.Name' })
.then(data =>
JSZip.loadAsync(new Buffer(data, 'base64')).then(zip =>
zip
.file('target/my-jar.jar')
.async('string')
.then(contents => {
expect(contents).to.be.equal(source);
})
)
);
});
});
});
});
================================================
FILE: compile/functions/runtimes/tests/node.js
================================================
'use strict';
const expect = require('chai').expect;
const chaiAsPromised = require('chai-as-promised');
require('chai').use(chaiAsPromised);
const sinon = require('sinon');
const Node = require('../node');
const BaseRuntime = require('../base');
const JSZip = require("jszip");
const fs = require('fs-extra');
describe('Node', () => {
let serverless;
let node;
let sandbox;
beforeEach(() => {
sandbox = sinon.sandbox.create();
serverless = {classes: {Error}, service: {}, getProvider: sandbox.spy()};
serverless.service.provider = { name: 'openwhisk' };
node = new Node(serverless);
});
afterEach(() => {
sandbox.restore();
});
describe('#match()', () => {
it('should match with explicit runtime', () => {
serverless.service.provider.runtime = 'python';
expect(node.match({runtime: 'nodejs', handler: 'file.func'})).to.equal(true)
});
it('should match with provider runtime', () => {
serverless.service.provider.runtime = 'nodejs';
expect(node.match({handler: 'file.func'})).to.equal(true)
});
it('should match with default runtime', () => {
expect(node.match({handler: 'file.func'})).to.equal(true)
});
it('should not match when wrong explicit runtime', () => {
expect(node.match({runtime: 'python', handler: 'file.func'})).to.equal(false)
});
it('should not match when wrong provider runtime', () => {
serverless.service.provider.runtime = 'python';
expect(node.match({handler: 'file.func'})).to.equal(false)
});
it('should not match when missing handler', () => {
expect(node.match({})).to.equal(false)
});
});
describe('#exec()', () => {
it('should return default nodejs exec definition', () => {
const fileContents = 'some file contents';
const handler = 'handler.some_func';
const exec = { main: 'some_func', kind: 'nodejs:default', code: new Buffer(fileContents) };
sandbox.stub(node, 'generateActionPackage', (functionObj) => {
expect(functionObj.handler).to.equal(handler);
return Promise.resolve(new Buffer(fileContents));
});
return expect(node.exec({ handler }))
.to.eventually.deep.equal(exec);
})
it('should return custom nodejs exec definition', () => {
const fileContents = 'some file contents';
const handler = 'handler.some_func';
const exec = { main: 'some_func', kind: 'nodejs:6', code: new Buffer(fileContents) };
sandbox.stub(node, 'generateActionPackage', (functionObj) => {
expect(functionObj.handler).to.equal(handler);
return Promise.resolve(new Buffer(fileContents));
});
return expect(node.exec({ handler, runtime: 'nodejs:6' }))
.to.eventually.deep.equal(exec);
})
it('should support using custom image', () => {
const fileContents = 'some file contents';
const handler = 'handler.some_func';
const exec = { main: 'some_func', image: 'blah', kind: 'blackbox', code: new Buffer(fileContents) };
sandbox.stub(node, 'generateActionPackage', (functionObj) => {
expect(functionObj.handler).to.equal(handler);
return Promise.resolve(new Buffer(fileContents));
});
return expect(node.exec({ handler, image: 'blah', runtime: 'nodejs:6' }))
.to.eventually.deep.equal(exec);
})
});
describe('#isValidTypeScriptFile()', () => {
it('should report valid file path when a js path is passed that has a ts file instead', () => {
//We need to mock the node's `super` call, which is why we're using BaseRuntime
sandbox.stub(BaseRuntime.prototype, 'isValidFile', (path) => {
expect(path).to.equal('valid_typescript_handler_wrong_extension.ts');
return true;
});
expect(node.isValidTypeScriptFile('valid_typescript_handler_wrong_extension.js')).to.equal(true)
});
});
describe('#isValidFile()', () => {
it('should still allow a js file to be used for handler', () => {
//We need to mock the node's `super` call, which is why we're using BaseRuntime
sandbox.stub(BaseRuntime.prototype, 'isValidFile', (path) => {
expect(path).to.equal('valid_js_handler.js');
return true;
});
expect(node.isValidFile('valid_js_handler.js')).to.equal(true)
});
});
describe('#generateActionPackage()', () => {
it('should throw error for missing handler file', () => {
expect(() => node.generateActionPackage({handler: 'does_not_exist.main'}))
.to.throw(Error, 'Function handler (does_not_exist.js) does not exist.');
})
it('should read service artifact and add package.json for handler', () => {
node.serverless.service.package = {artifact: '/path/to/zip_file.zip'};
node.isValidFile = () => true
const zip = new JSZip();
zip.file("handler.js", "function main() { return {}; }");
zip.file("package.json", '{"main": "index.js"}')
return zip.generateAsync({type:"nodebuffer"}).then(zipped => {
sandbox.stub(fs, 'readFile', (path, cb) => {
expect(path).to.equal('/path/to/zip_file.zip');
cb(null, zipped);
});
return node.generateActionPackage({handler: 'handler.main'}).then(data => {
return JSZip.loadAsync(new Buffer(data, 'base64')).then(zip => {
return zip.file("package.json").async("string").then(package_json => {
expect(package_json).to.be.equal('{"main":"handler.js"}')
})
})
})
});
})
it('should read service artifact and add package.json for relative path handler', () => {
node.serverless.service.package = { artifact: '/path/to/zip_file.zip' };
node.isValidFile = () => true;
const zip = new JSZip();
zip.file('folder/handler.js', 'function main() { return {}; }');
zip.file('folder/package.json', '{"main": "index.js"}');
return zip.generateAsync({ type: 'nodebuffer' }).then(zipped => {
sandbox.stub(fs, 'readFile', (path, cb) => {
expect(path).to.equal('/path/to/zip_file.zip');
cb(null, zipped);
});
return node.generateActionPackage({ handler: '../folder/handler.main' }).then(data => {
return JSZip.loadAsync(new Buffer(data, 'base64')).then(actionPackage => {
return actionPackage.file('package.json').async('string').then(packageJson => {
expect(packageJson).to.be.equal('{"main":"folder/handler.js"}');
});
});
});
});
});
it('should handle service artifact for individual function handler', () => {
const functionObj = {handler: 'handler.main', package: { artifact: '/path/to/zip_file.zip'}}
node.serverless.service.package = {individually: true};
node.isValidFile = () => true
const zip = new JSZip();
zip.file("handler.js", "function main() { return {}; }");
zip.file("package.json", '{"main": "index.js"}')
return zip.generateAsync({type:"nodebuffer"}).then(zipped => {
sandbox.stub(fs, 'readFile', (path, cb) => {
expect(path).to.equal('/path/to/zip_file.zip');
cb(null, zipped);
});
return node.generateActionPackage(functionObj).then(data => {
return JSZip.loadAsync(new Buffer(data, 'base64')).then(zip => {
return zip.file("package.json").async("string").then(package_json => {
expect(package_json).to.be.equal('{"main":"handler.js"}')
})
})
})
});
})
})
});
================================================
FILE: compile/functions/runtimes/tests/php.js
================================================
'use strict';
const expect = require('chai').expect;
const chaiAsPromised = require('chai-as-promised');
require('chai').use(chaiAsPromised);
const sinon = require('sinon');
const Php = require('../php');
const JSZip = require("jszip");
const fs = require('fs-extra');
describe('Php', () => {
let serverless;
let php;
let sandbox;
beforeEach(() => {
sandbox = sinon.sandbox.create();
serverless = {classes: {Error}, service: {}, getProvider: sandbox.spy()};
serverless.service.provider = { name: 'openwhisk' };
php = new Php(serverless);
});
afterEach(() => {
sandbox.restore();
});
describe('#match()', () => {
it('should match with explicit runtime', () => {
serverless.service.provider.runtime = 'python';
expect(php.match({runtime: 'php', handler: 'file.func'})).to.equal(true)
});
it('should match with provider runtime', () => {
serverless.service.provider.runtime = 'php';
expect(php.match({handler: 'file.func'})).to.equal(true)
});
it('should not match when wrong explicit runtime', () => {
expect(php.match({runtime: 'python', handler: 'file.func'})).to.equal(false)
});
it('should not match when wrong provider runtime', () => {
serverless.service.provider.runtime = 'python';
expect(php.match({handler: 'file.func'})).to.equal(false)
});
it('should not match when missing handler', () => {
expect(php.match({})).to.equal(false)
});
});
describe('#exec()', () => {
it('should return php exec definition', () => {
const fileContents = 'some file contents';
const handler = 'handler.some_func';
const exec = { main: 'some_func', kind: 'php:default', code: new Buffer(fileContents) };
sandbox.stub(php, 'generateActionPackage', (functionObj) => {
expect(functionObj.handler).to.equal(handler);
return Promise.resolve(new Buffer(fileContents));
});
return expect(php.exec({ handler, runtime: 'php'}))
.to.eventually.deep.equal(exec);
})
it('should support using custom image', () => {
const fileContents = 'some file contents';
const handler = 'handler.some_func';
const exec = { main: 'some_func', image: 'blah', kind: 'blackbox', code: new Buffer(fileContents) };
sandbox.stub(php, 'generateActionPackage', (functionObj) => {
expect(functionObj.handler).to.equal(handler);
return Promise.resolve(new Buffer(fileContents));
});
return expect(php.exec({ handler, image: 'blah', runtime: 'php:7.1' }))
.to.eventually.deep.equal(exec);
})
});
describe('#generateActionPackage()', () => {
it('should throw error for missing handler file', () => {
expect(() => php.generateActionPackage({handler: 'does_not_exist.main'}))
.to.throw(Error, 'Function handler (does_not_exist.php) does not exist.');
})
it('should read service artifact and add index.php for handler', () => {
php.serverless.service.package = {artifact: '/path/to/zip_file.zip'};
php.isValidFile = () => true
const zip = new JSZip();
const source = '<?php\nfunction main(array $args) : array\n{\nreturn [];\n}'
zip.file("handler.php", source);
return zip.generateAsync({type:"nodebuffer"}).then(zipped => {
sandbox.stub(fs, 'readFile', (path, cb) => {
expect(path).to.equal('/path/to/zip_file.zip');
cb(null, zipped);
});
return php.generateActionPackage({handler: 'handler.main'}).then(data => {
return JSZip.loadAsync(new Buffer(data, 'base64')).then(zip => {
expect(zip.file("handler.php")).to.be.equal(null)
return zip.file("index.php").async("string").then(main => {
expect(main).to.be.equal(source)
})
})
})
});
})
it('should handle service artifact for individual function handler', () => {
const functionObj = {handler: 'handler.main', package: { artifact: '/path/to/zip_file.zip'}}
php.serverless.service.package = {individually: true};
php.isValidFile = () => true
const zip = new JSZip();
const source = '<?php\nfunction main(array $args) : array\n{\nreturn [];\n}'
zip.file("handler.php", source);
return zip.generateAsync({type:"nodebuffer"}).then(zipped => {
sandbox.stub(fs, 'readFile', (path, cb) => {
expect(path).to.equal('/path/to/zip_file.zip');
cb(null, zipped);
});
return php.generateActionPackage(functionObj).then(data => {
return JSZip.loadAsync(new Buffer(data, 'base64')).then(zip => {
expect(zip.file("handler.php")).to.be.equal(null)
return zip.file("index.php").async("string").then(main => {
expect(main).to.be.equal(source)
})
})
})
});
});
})
});
================================================
FILE: compile/functions/runtimes/tests/python.js
================================================
'use strict';
const expect = require('chai').expect;
const chaiAsPromised = require('chai-as-promised');
require('chai').use(chaiAsPromised);
const sinon = require('sinon');
const Python = require('../python');
const JSZip = require("jszip");
const fs = require('fs-extra');
describe('Python', () => {
let serverless;
let node;
let sandbox;
beforeEach(() => {
sandbox = sinon.sandbox.create();
serverless = {classes: {Error}, service: {}, getProvider: sandbox.spy()};
serverless.service.provider = { name: 'openwhisk' };
node = new Python(serverless);
});
afterEach(() => {
sandbox.restore();
});
describe('#match()', () => {
it('should match with explicit runtime', () => {
serverless.service.provider.runtime = 'nodejs';
expect(node.match({runtime: 'python', handler: 'file.func'})).to.equal(true)
});
it('should match with provider runtime', () => {
serverless.service.provider.runtime = 'python';
expect(node.match({handler: 'file.func'})).to.equal(true)
});
it('should not match when wrong explicit runtime', () => {
expect(node.match({runtime: 'nodejs', handler: 'file.func'})).to.equal(false)
});
it('should not match when wrong provider runtime', () => {
serverless.service.provider.runtime = 'nodejs';
expect(node.match({handler: 'file.func'})).to.equal(false)
});
it('should not match default runtime', () => {
expect(node.match({handler: 'file.func'})).to.equal(false)
});
it('should not match when missing handler', () => {
expect(node.match({})).to.equal(false)
});
});
describe('#exec()', () => {
it('should return python exec definition', () => {
const fileContents = 'some file contents';
const handler = 'handler.some_func';
const exec = { main: 'some_func', kind: 'python', code: new Buffer(fileContents) };
sandbox.stub(node, 'generateActionPackage', (functionObj) => {
expect(functionObj.handler).to.equal(handler);
return Promise.resolve(new Buffer(fileContents));
});
return expect(node.exec({ handler, runtime: 'python'}))
.to.eventually.deep.equal(exec);
})
it('should support using custom image', () => {
const fileContents = 'some file contents';
const handler = 'handler.some_func';
const exec = { main: 'some_func', image: 'blah', kind: 'blackbox', code: new Buffer(fileContents) };
sandbox.stub(node, 'generateActionPackage', (functionObj) => {
expect(functionObj.handler).to.equal(handler);
return Promise.resolve(new Buffer(fileContents));
});
return expect(node.exec({ handler, image: 'blah', runtime: 'python' }))
.to.eventually.deep.equal(exec);
})
});
describe('#generateActionPackage()', () => {
it('should throw error for missing handler file', () => {
expect(() => node.generateActionPackage({handler: 'does_not_exist.main'}))
.to.throw(Error, 'Function handler (does_not_exist.py) does not exist.');
})
it('should read service artifact and add __main__.py for handler', () => {
node.serverless.service.package = {artifact: '/path/to/zip_file.zip'};
node.isValidFile = () => true
const zip = new JSZip();
zip.file("handler.py", "def main(dict):\n\treturn {}");
return zip.generateAsync({type:"nodebuffer"}).then(zipped => {
sandbox.stub(fs, 'readFile', (path, cb) => {
expect(path).to.equal('/path/to/zip_file.zip');
cb(null, zipped);
});
return node.generateActionPackage({handler: 'handler.main'}).then(data => {
return JSZip.loadAsync(new Buffer(data, 'base64')).then(zip => {
expect(zip.file("handler.py")).to.be.equal(null)
return zip.file("__main__.py").async("string").then(package_json => {
expect(package_json).to.be.equal('def main(dict):\n\treturn {}')
})
})
})
});
})
it('should handle service artifact for individual function handler', () => {
const functionObj = {handler: 'handler.main', package: { artifact: '/path/to/zip_file.zip'}}
node.serverless.service.package = {individually: true};
node.isValidFile = () => true
const zip = new JSZip();
zip.file("handler.py", "def main(dict):\n\treturn {}");
return zip.generateAsync({type:"nodebuffer"}).then(zipped => {
sandbox.stub(fs, 'readFile', (path, cb) => {
expect(path).to.equal('/path/to/zip_file.zip');
cb(null, zipped);
});
return node.generateActionPackage(functionObj).then(data => {
return JSZip.loadAsync(new Buffer(data, 'base64')).then(zip => {
expect(zip.file("handler.py")).to.be.equal(null)
return zip.file("__main__.py").async("string").then(package_json => {
expect(package_json).to.be.equal('def main(dict):\n\treturn {}')
})
})
})
});
})
})
});
================================================
FILE: compile/functions/runtimes/tests/ruby.js
================================================
'use strict';
const expect = require('chai').expect;
const chaiAsPromised = require('chai-as-promised');
require('chai').use(chaiAsPromised);
const sinon = require('sinon');
const Ruby = require('../ruby');
const JSZip = require('jszip');
const fs = require('fs-extra');
describe('Ruby', () => {
let serverless;
let ruby;
let sandbox;
beforeEach(() => {
sandbox = sinon.sandbox.create();
serverless = {classes: {Error}, service: {}, getProvider: sandbox.spy()};
serverless.service.provider = { name: 'openwhisk' };
ruby = new Ruby(serverless);
});
afterEach(() => {
sandbox.restore();
});
describe('#match()', () => {
it('should match with explicit runtime', () => {
serverless.service.provider.runtime = 'python';
expect(ruby.match({runtime: 'ruby', handler: 'file.func'})).to.equal(true)
});
it('should match with provider runtime', () => {
serverless.service.provider.runtime = 'ruby';
expect(ruby.match({handler: 'file.func'})).to.equal(true)
});
it('should not match when wrong explicit runtime', () => {
expect(ruby.match({runtime: 'python', handler: 'file.func'})).to.equal(false)
});
it('should not match when wrong provider runtime', () => {
serverless.service.provider.runtime = 'python';
expect(ruby.match({handler: 'file.func'})).to.equal(false)
});
it('should not match when missing handler', () => {
expect(ruby.match({})).to.equal(false)
});
});
describe('#exec()', () => {
it('should return ruby exec definition', () => {
const fileContents = 'some file contents';
const handler = 'handler.some_func';
const exec = { main: 'some_func', kind: 'ruby:default', code: new Buffer(fileContents) };
sandbox.stub(ruby, 'generateActionPackage', (functionObj) => {
expect(functionObj.handler).to.equal(handler);
return Promise.resolve(new Buffer(fileContents));
});
return expect(ruby.exec({ handler, runtime: 'ruby'}))
.to.eventually.deep.equal(exec);
})
it('should support using custom image', () => {
const fileContents = 'some file contents';
const handler = 'handler.some_func';
const exec = { main: 'some_func', image: 'blah', kind: 'blackbox', code: new Buffer(fileContents) };
sandbox.stub(ruby, 'generateActionPackage', (functionObj) => {
expect(functionObj.handler).to.equal(handler);
return Promise.resolve(new Buffer(fileContents));
});
return expect(ruby.exec({ handler, image: 'blah', runtime: 'ruby:7.1' }))
.to.eventually.deep.equal(exec);
})
});
describe('#generateActionPackage()', () => {
it('should throw error for missing handler file', () => {
expect(() => ruby.generateActionPackage({handler: 'does_not_exist.main'}))
.to.throw(Error, 'Function handler (does_not_exist.rb) does not exist.');
})
it('should read service artifact and add main.rb for handler', () => {
ruby.serverless.service.package = {artifact: '/path/to/zip_file.zip'};
ruby.isValidFile = () => true
const zip = new JSZip();
const source = 'def main(args)\nname = args["name"] || "stranger"\ngreeting = "Hello #{name}!"\n{ "greeting" => greeting }\nend'
zip.file("handler.rb", source);
return zip.generateAsync({type:"nodebuffer"}).then(zipped => {
sandbox.stub(fs, 'readFile', (path, cb) => {
expect(path).to.equal('/path/to/zip_file.zip');
cb(null, zipped);
});
return ruby.generateActionPackage({handler: 'handler.main'}).then(data => {
return JSZip.loadAsync(new Buffer(data, 'base64')).then(zip => {
expect(zip.file("handler.rb")).to.be.equal(null)
return zip.file("main.rb").async("string").then(main => {
expect(main).to.be.equal(source)
})
})
})
});
})
it('should handle service artifact for individual function handler', () => {
const functionObj = {handler: 'handler.main', package: { artifact: '/path/to/zip_file.zip'}}
ruby.serverless.service.package = {individually: true};
ruby.isValidFile = () => true
const zip = new JSZip();
const source = 'def main(args)\nname = args["name"] || "stranger"\ngreeting = "Hello #{name}!"\n{ "greeting" => greeting }\nend'
zip.file("handler.rb", source);
return zip.generateAsync({type:"nodebuffer"}).then(zipped => {
sandbox.stub(fs, 'readFile', (path, cb) => {
expect(path).to.equal('/path/to/zip_file.zip');
cb(null, zipped);
});
return ruby.generateActionPackage(functionObj).then(data => {
return JSZip.loadAsync(new Buffer(data, 'base64')).then(zip => {
expect(zip.file("handler.rb")).to.be.equal(null)
return zip.file("main.rb").async("string").then(main => {
expect(main).to.be.equal(source)
})
})
})
});
});
})
});
================================================
FILE: compile/functions/runtimes/tests/sequence.js
================================================
'use strict';
const expect = require('chai').expect;
const chaiAsPromised = require('chai-as-promised');
require('chai').use(chaiAsPromised);
const sinon = require('sinon');
const Sequence = require('../sequence');
describe('Sequence', () => {
let serverless;
let sequence;
let sandbox;
beforeEach(() => {
sandbox = sinon.sandbox.create();
serverless = {classes: {Error}, service: {}, getProvider: sandbox.spy()};
serverless.service.provider = { name: 'openwhisk' };
sequence = new Sequence(serverless);
});
afterEach(() => {
sandbox.restore();
});
describe('#match()', () => {
it('should match function object with sequence property', () => {
expect(sequence.match({sequence: true})).to.equal(true)
});
it('should ignore function object without sequence property', () => {
expect(sequence.match({})).to.equal(false)
});
});
describe('#exec()', () => {
it('should return sequence definition for sequence function', () => {
const exec = { kind: 'sequence', components: ["/_/one", "/a/two", "/a/b/three"] };
sequence.serverless.service.getFunction = () => ({name: 'one'});
sequence.serverless.service.provider.namespace = 'namespace';
expect(sequence.exec({
sequence: ["one", "/a/two", "/a/b/three"]
})).to.deep.equal(exec);
});
});
});
================================================
FILE: compile/functions/runtimes/tests/swift.js
================================================
'use strict';
const expect = require('chai').expect;
const chaiAsPromised = require('chai-as-promised');
require('chai').use(chaiAsPromised);
const sinon = require('sinon');
const Swift = require('../swift');
const JSZip = require("jszip");
const fs = require('fs-extra');
describe('Swift', () => {
let serverless;
let node;
let sandbox;
beforeEach(() => {
sandbox = sinon.sandbox.create();
serverless = {classes: {Error}, service: {}, getProvider: sandbox.spy()};
serverless.service.provider = { name: 'openwhisk' };
node = new Swift(serverless);
});
afterEach(() => {
sandbox.restore();
});
describe('#match()', () => {
it('should match with explicit runtime', () => {
serverless.service.provider.runtime = 'nodejs';
expect(node.match({runtime: 'swift', handler: 'file.func'})).to.equal(true)
});
it('should match with provider runtime', () => {
serverless.service.provider.runtime = 'swift';
expect(node.match({handler: 'file.func'})).to.equal(true)
});
it('should not match when wrong explicit runtime', () => {
expect(node.match({runtime: 'nodejs', handler: 'file.func'})).to.equal(false)
});
it('should not match when wrong provider runtime', () => {
serverless.service.provider.runtime = 'nodejs';
expect(node.match({handler: 'file.func'})).to.equal(false)
});
it('should not match default runtime', () => {
expect(node.match({handler: 'file.func'})).to.equal(false)
});
it('should not match when missing handler', () => {
expect(node.match({})).to.equal(false)
});
});
describe('#exec()', () => {
it('should return swift exec with source file handler', () => {
const fileContents = 'some file contents';
const handler = 'handler.some_func';
node.isValidFile = () => true
sandbox.stub(fs, 'readFileSync', (path) => {
expect(path).to.equal('handler.swift');
return Buffer.from(fileContents)
});
const exec = { main: 'some_func', binary: false, kind: 'swift:default', code: fileContents };
return expect(node.exec({ handler, runtime: 'swift'}))
.to.eventually.deep.equal(exec);
})
it('should return swift exec with zip file handler', () => {
const handler = 'my_file.zip';
node.isValidFile = () => true
const zip = new JSZip();
const source = 'binary file contents'
zip.file("exec", source);
return zip.generateAsync({type:"nodebuffer"}).then(zipped => {
sandbox.stub(fs, 'readFileSync', (path) => {
expect(path).to.equal(handler);
return zipped
});
const b64 = zipped.toString('base64')
const exec = { main: 'main', binary: true, kind: 'swift:default', code: b64 };
return expect(node.exec({ handler, runtime: 'swift'}))
.to.eventually.deep.equal(exec);
})
})
});
describe('#convertHandlerToPath()', () => {
it('should return file path for swift function handlers', () => {
expect(node.convertHandlerToPath('file.func')).to.be.equal('file.swift')
})
it('should return file path for zip files', () => {
expect(node.convertHandlerToPath('my_file.zip')).to.be.equal('my_file.zip')
})
})
});
================================================
FILE: compile/functions/tests/index.js
================================================
'use strict';
const expect = require('chai').expect;
const chaiAsPromised = require('chai-as-promised');
require('chai').use(chaiAsPromised);
const sinon = require('sinon');
const OpenWhiskCompileFunctions = require('../index');
describe('OpenWhiskCompileFunctions', () => {
let serverless;
let openwhiskCompileFunctions;
let sandbox;
const openwhiskResourcesMockObject = {
first: {
actionName: 'first',
namespace: '',
action: {
exec: { kind: 'nodejs:default', code: 'function main() {};' },
},
},
second: {
actionName: 'second',
namespace: '',
action: {
exec: { kind: 'nodejs:default', code: 'function main() {};' },
},
},
};
beforeEach(() => {
sandbox = sinon.sandbox.create();
const options = {
stage: 'dev',
region: 'us-east-1',
};
serverless = {classes: {Error}, service: {}, getProvider: sandbox.spy()};
openwhiskCompileFunctions = new OpenWhiskCompileFunctions(serverless, options);
serverless.service.service = 'serviceName';
serverless.service.provider = {
namespace: '',
apihost: '',
auth: '',
};
serverless.service.provider = { name: 'openwhisk' };
serverless.cli = { consoleLog: () => {}, log: () => {} };
openwhiskCompileFunctions.setup();
});
afterEach(() => {
sandbox.restore();
});
describe('#disableSeqPackaging()', () => {
it('should add disable flag to sequences', () => {
const fns = {
first: { handler: 'foo.js' },
second: { handler: 'foo.js' },
seq: { sequence: [ 'first', 'second' ] }
}
openwhiskCompileFunctions.serverless.service.getAllFunctions = () => Object.keys(fns)
openwhiskCompileFunctions.serverless.service.getFunction = name => fns[name];
openwhiskCompileFunctions.disableSeqPackaging()
expect(fns.seq.package.disable).to.be.true
});
});
describe('#constructAnnotations()', () => {
it('should handle missing annotations', () => {
expect(openwhiskCompileFunctions.constructAnnotations())
.to.deep.equal([]);
})
it('should handle empty annotations', () => {
expect(openwhiskCompileFunctions.constructAnnotations({}))
.to.deep.equal([]);
})
it('should handle annotations present', () => {
expect(openwhiskCompileFunctions.constructAnnotations({
hello: 'world', foo: 'bar'
})).to.deep.equal([
{ key: 'hello', value: 'world' },
{ key: 'foo', value: 'bar' }
]);
})
it('should add final annotations if web-export is present', () => {
expect(openwhiskCompileFunctions.constructAnnotations({
hello: 'world', foo: 'bar', "web-export": true
})).to.deep.equal([
{ key: 'hello', value: 'world' },
{ key: 'foo', value: 'bar' },
{ key: 'web-export', value: true },
{ key: 'final', value: true }
]);
})
})
describe('#calculateFunctionNameSpace()', () => {
it('should return namespace from function object', () => {
expect(openwhiskCompileFunctions
.calculateFunctionNameSpace('testing', { namespace: 'testing' })
).to.equal('testing');
});
it('should return namespace from service provider', () => {
openwhiskCompileFunctions.serverless.service.provider = { namespace: 'testing' };
expect(openwhiskCompileFunctions.calculateFunctionNameSpace('testing', {}))
.to.equal('testing');
});
});
describe('#logCompiledFunction()', () => {
it('should log function contents with code to console.', () => {
const log = sandbox.stub(openwhiskCompileFunctions.serverless.cli, 'log')
const clog = sandbox.stub(openwhiskCompileFunctions.serverless.cli, 'consoleLog')
openwhiskCompileFunctions.logCompiledFunction('first', openwhiskResourcesMockObject.first)
expect(log.calledOnce).to.be.equal(true);
const clone = JSON.parse(JSON.stringify(openwhiskResourcesMockObject.first))
clone.action.exec.code = '<hidden>'
expect(log.args[0][0]).to.be.equal(`Compiled Function (first): ${JSON.stringify(clone)}`)
});
it('should log function contents without code to console.', () => {
const log = sandbox.stub(openwhiskCompileFunctions.serverless.cli, 'log')
const clog = sandbox.stub(openwhiskCompileFunctions.serverless.cli, 'consoleLog')
const clone = JSON.parse(JSON.stringify(openwhiskResourcesMockObject.first))
delete clone.action.exec.code
openwhiskCompileFunctions.logCompiledFunction('first', clone)
expect(log.calledOnce).to.be.equal(true);
expect(log.args[0][0]).to.be.equal(`Compiled Function (first): ${JSON.stringify(clone)}`)
});
});
describe('#compileFunctions()', () => {
it('should create action function with parsed parameters', () => {
let functionObject = {
handler: "foo.js",
name: "name",
namespace: "namespace",
overwrite: "overwrite",
memory: 123,
concurrency: 456,
timeout: 789,
parameters: {
hello: "world",
foo: "bar"
},
annotations: {
hello: "world",
foo: "bar"
}
};
openwhiskCompileFunctions.serverless.service.getAllFunctions = () => ['service_name'];
openwhiskCompileFunctions.serverless.service.getFunction = () => functionObject;
sandbox.stub(openwhiskCompileFunctions, 'runtimes', {
exec: () => Promise.resolve()
});
return openwhiskCompileFunctions.compileFunctions().then(functionActions => {
let functionAction = functionActions[0];
expect(functionAction.actionName).to.be.equal(functionObject.name);
expect(functionAction.namespace).to.be.equal(functionObject.namespace);
expect(functionAction.overwrite).to.be.equal(functionObject.overwrite);
expect(functionAction.action.limits.memory).to.be.equal(functionObject.memory);
expect(functionAction.action.limits.concurrency).to.be.equal(functionObject.concurrency);
expect(functionAction.action.limits.timeout).to.be.equal(functionObject.timeout * 1000);
let paramsAndAnnotations = [
{ key: 'hello', value: 'world' },
{ key: 'foo', value: 'bar' }
];
expect(functionAction.action.parameters).to.deep.equal(paramsAndAnnotations);
expect(functionAction.action.annotations).to.deep.equal(paramsAndAnnotations);
});
});
it('should not add implicit limits parameters', () => {
let functionObject = {
handler: "foo.js",
name: "name"
};
openwhiskCompileFunctions.serverless.service.getAllFunctions = () => ['service_name'];
openwhiskCompileFunctions.serverless.service.getFunction = () => functionObject;
sandbox.stub(openwhiskCompileFunctions, 'runtimes', {
exec: () => Promise.resolve()
});
return openwhiskCompileFunctions.compileFunctions().then(functionActions => {
let functionAction = functionActions[0];
expect(functionAction.actionName).to.be.equal(functionObject.name);
expect(functionAction.action.limits.memory).to.be.undefined;
expect(functionAction.action.limits.concurrency).to.be.undefined;
expect(functionAction.action.limits.timeout).to.be.undefined;
});
});
it('should throw an error if the resource section is not available', () => {
openwhiskCompileFunctions.serverless.service.actions = null;
expect(() => openwhiskCompileFunctions.compileFunctions())
.to.throw(Error, /Missing Resources section/);
});
it('should throw an error if function definition has handler and sequence', () => {
const f = { sequence: true, handler: true };
openwhiskCompileFunctions.serverless.service.getAllFunctions = () => ['service_name'];
openwhiskCompileFunctions.serverless.service.getFunction = () => f;
expect(() => openwhiskCompileFunctions.compileFunctions())
.to.throw(Error, /both "handler" and "sequence" properties/);
});
it('should throw an error if function definition is missing a handler or sequence', () => {
openwhiskCompileFunctions.serverless.service.getAllFunctions = () => ['service_name'];
openwhiskCompileFunctions.serverless.service.getFunction = () => ({});
expect(() => openwhiskCompileFunctions.compileFunctions())
.to.throw(Error, /Missing "handler" or "sequence"/);
});
it('should throw an error if unable to read function handler file', () => {
openwhiskCompileFunctions.serverless.service.getAllFunctions = () => ['service_name'];
const missing = { handler: 'missing.handler' };
openwhiskCompileFunctions.serverless.service.getFunction = () => missing;
sandbox.stub(openwhiskCompileFunctions, 'compileFunction', () => Promise.reject());
return expect(openwhiskCompileFunctions.compileFunctions()).to.be.rejected;
});
it('should create corresponding function resources', () => {
const keys = Object.keys(openwhiskResourcesMockObject);
const handler = function (name) {
return { handler: `${name}.handler` };
};
openwhiskCompileFunctions.serverless.service.getAllFunctions = () => keys;
openwhiskCompileFunctions.serverless.service.getFunction = name => handler(name);
const log = sandbox.stub(openwhiskCompileFunctions, 'logCompiledFunction')
const mock = openwhiskResourcesMockObject;
sandbox.stub(
openwhiskCompileFunctions, 'compileFunction', name => Promise.resolve(mock[name]));
const f = openwhiskCompileFunctions.serverless.service.actions;
return openwhiskCompileFunctions.compileFunctions().then(() => {
expect(f).to.deep.equal(openwhiskResourcesMockObject)
expect(log.called).to.be.equal(false);
});
});
it('should log compiled functions with verbose flag', () => {
const keys = Object.keys(openwhiskResourcesMockObject);
const handler = function (name) {
return { handler: `${name}.handler` };
};
openwhiskCompileFunctions.options.verbose = true;
openwhiskCompileFunctions.serverless.service.getAllFunctions = () => keys;
openwhiskCompileFunctions.serverless.service.getFunction = name => handler(name);
const log = sandbox.stub(openwhiskCompileFunctions, 'logCompiledFunction')
const mock = openwhiskResourcesMockObject;
sandbox.stub(
openwhiskCompileFunctions, 'compileFunction', name => Promise.resolve(mock[name]));
const f = openwhiskCompileFunctions.serverless.service.actions;
return openwhiskCompileFunctions.compileFunctions().then(() => {
expect(log.calledTwice).to.be.equal(true);
});
});
});
});
================================================
FILE: compile/message_hub/README.md
================================================
# Compile Triggers
This plugins compiles the `message_hub` events in `serverless.yaml` to corresponding [OpenWhisk Message Hub Trigger Feeds](https://github.com/openwhisk/openwhisk-package-kafka) definitions.
## How it works
`Compile Message Hub` hooks into the [`package:compileEvents`](/lib/plugins/deploy) lifecycle.
It loops over all schedule event which are defined in `serverless.yaml`.
### Using Package Parameters
IBM Message Hub instances can be provisioned through the IBM Bluemix platform.
OpenWhisk on Bluemix will export Message Hub service credentials bound to a
package with the following name:
```
/${BLUEMIX_ORG}_${BLUEMIX_SPACE}/Bluemix_${SERVICE_NAME}_Credentials-1
```
Rather than having to manually define all the properties needed by the Message
Hub trigger feed, you can reference a package to use instead. Credentials from
the referenced package will be used when executing the trigger feed.
Developers only need to add the topic to listen to for each trigger.
```yaml
# serverless.yaml
functions:
index:
handler: users.main
events:
- message_hub:
package: /${BLUEMIX_ORG}_${BLUEMIX_SPACE}/Bluemix_${SERVICE_NAME}_Credentials-1
topic: my_kafka_topic
```
The plugin will create a trigger called `${serviceName}_${fnName}_messagehub_${topic}`
and a rule called `${serviceName}_${fnName}_messagehub_${topic}_rule` to bind the function to
the message hub events.
The trigger and rule names created can be set explicitly using the `trigger` and
`rule` parameters.
Other functions can bind to the same trigger using the inline `trigger` event
referncing this trigger name.
```yaml
# serverless.yaml
functions:
index:
handler: users.main
events:
- message_hub:
package: /${BLUEMIX_ORG}_${BLUEMIX_SPACE}/Bluemix_${SERVICE_NAME}_Credentials-1
topic: my_kafka_topic
trigger: log_events
rule: connect_index_to_kafka
another:
handler: users.another
events:
- trigger: log_events
```
### Using Manual Parameters
Trigger feed parameters for the Message Hub event source can be defined
explicitly, rather than using pulling credentials from a package.
```yaml
# serverless.yaml
functions:
index:
handler: users.main
events:
- message_hub:
topic: my_kafka_topic
brokers: afka01-prod01.messagehub.services.us-south.bluemix.net:9093
user: USERNAME
password: PASSWORD
admin_url: https://kafka-admin-prod01.messagehub.services.us-south.bluemix.net:443
json: true
binary_key: true
binary_value: true
```
`topic`, `brokers`, `user`, `password` and `admin_url` are mandatory parameters.
================================================
FILE: compile/message_hub/index.js
================================================
'use strict';
const BbPromise = require('bluebird');
const config_properties = ['user', 'password', 'brokers', 'topic', 'admin_url']
class OpenWhiskCompileMessageHub {
constructor(serverless, options) {
this.serverless = serverless;
this.options = options;
this.provider = this.serverless.getProvider('openwhisk');
this.default_package = '/whisk.system/messaging'
this.hooks = {
'before:package:compileEvents': () => BbPromise.bind(this)
.then(this.setup)
.then(this.processMessageHubEvents)
};
}
setup() {
if (!this.serverless.service.resources) {
this.serverless.service.resources = {};
}
if (!this.serverless.service.resources.triggers) {
this.serverless.service.resources.triggers = {};
}
if (!this.serverless.service.resources.rules) {
this.serverless.service.resources.rules = {};
}
}
validateConfig (fnName, config) {
if (!config.topic) {
throw new this.serverless.classes.Error(
`Message Hub event property (topic) missing on function: ${fnName}`
)
}
if (!config.package) {
config_properties.forEach(prop => {
if (!config[prop]) {
throw new this.serverless.classes.Error(
`Message Hub event property (${prop}) missing on function: ${fnName}`
)
}
})
}
}
compileMessageHubTrigger (fnName, config) {
this.validateConfig(fnName, config)
const name = config.trigger || this.defaultMessageHubName(fnName, config.topic)
const feed = `${config.package || this.default_package}/messageHubFeed`
const feed_parameters = {
topic: config.topic,
isJSONData: config.json || false,
isBinaryKey: config.binary_key || false,
isBinaryValue: config.binary_value || false
}
if (!config.package) {
feed_parameters.user = config.user
feed_parameters.password = config.password
feed_parameters.kafka_brokers_sasl = Array.isArray(config.brokers) ? config.brokers.join(',') : config.brokers
feed_parameters.kafka_admin_url = config.admin_url
}
return { name, content: { feed, feed_parameters } }
}
defaultMessageHubName (fnName, topic) {
return `${this.serverless.service.service}_${fnName}_messagehub_${topic}`
}
processMessageHubEvent (fnName, config) {
const fnObj = this.serverless.service.getFunction(fnName)
const trigger = this.compileMessageHubTrigger(fnName, config)
const rule = config.rule || `${this.defaultMessageHubName(fnName, config.topic)}_rule`
fnObj.events.push({ trigger: { name: trigger.name, rule } })
this.serverless.service.resources.triggers[trigger.name] = trigger.content
}
processMessageHubEvents () {
this.serverless.service.getAllFunctions().forEach(name => {
const fn = this.serverless.service.getFunction(name)
const events = (fn.events || []).filter(e => e.message_hub)
events.forEach(e => this.processMessageHubEvent(name, e.message_hub))
})
}
}
module.exports = OpenWhiskCompileMessageHub
================================================
FILE: compile/message_hub/tests/index.js
================================================
'use strict';
const expect = require('chai').expect;
const chaiAsPromised = require('chai-as-promised');
require('chai').use(chaiAsPromised);
const sinon = require('sinon');
const OpenWhiskCompileMessageHub = require('../index');
describe('OpenWhiskCompileMessageHub', () => {
let serverless;
let sandbox;
let openwhiskCompileMessageHub;
beforeEach(() => {
sandbox = sinon.sandbox.create();
serverless = {classes: {Error}, service: {provider: {}, resources: {}, getAllFunctions: () => []}, getProvider: sandbox.spy()};
const options = {
stage: 'dev',
region: 'us-east-1',
};
openwhiskCompileMessageHub = new OpenWhiskCompileMessageHub(serverless, options);
serverless.service.service = 'serviceName';
serverless.service.provider = {
namespace: 'testing',
apihost: '',
auth: '',
};
serverless.cli = { log: () => {} };
openwhiskCompileMessageHub.setup()
});
afterEach(() => {
sandbox.restore();
});
describe('#processMessageHubEvents()', () => {
it('should call processMessageHubEvent for each message hub event.', () => {
const service = openwhiskCompileMessageHub.serverless.service;
const fns = {
first: {
events: [{}, {message_hub: {package: 'testing_package', topic: 'some_topic'}}, {trigger: true}]
},
second: {
events: [{message_hub: {package: 'another_package', topic: 'some_topic'}}]
},
third: {}
}
service.getAllFunctions = () => Object.keys(fns)
service.getFunction = name => fns[name];
const spy = openwhiskCompileMessageHub.processMessageHubEvent = sinon.spy()
openwhiskCompileMessageHub.processMessageHubEvents()
expect(spy.calledTwice).to.be.equal(true)
expect(spy.withArgs("first", {package: 'testing_package', topic: 'some_topic'}).calledOnce).to.be.equal(true)
expect(spy.withArgs("second", {package: 'another_package', topic: 'some_topic'}).calledOnce).to.be.equal(true)
})
})
describe('#processMessageHubEvents()', () => {
it('should create trigger & rules and update manifest resources.', () => {
const message_hub = { package: 'some_package', topic: 'testing' }
const fnObj = { events: [{message_hub}] }
serverless.service.getFunction = () => fnObj
openwhiskCompileMessageHub.compileMessageHubTrigger = () => ({name: 'serviceName_fnName_messagehub_testing', content: { a: 1 }})
openwhiskCompileMessageHub.processMessageHubEvent("fnName", fnObj.events[0].message_hub)
expect(fnObj.events[1]).to.be.deep.equal({
trigger: { name: 'serviceName_fnName_messagehub_testing', rule: 'serviceName_fnName_messagehub_testing_rule' }
})
expect(serverless.service.resources.triggers).to.be.deep.equal({serviceName_fnName_messagehub_testing: {a: 1}})
})
})
describe('#compileMessageHubTrigger()', () => {
it('should throw errors for missing topic parameter.', () => {
expect(() => openwhiskCompileMessageHub.compileMessageHubTrigger('testing', {}))
.to.throw(Error, 'Message Hub event property (topic) missing on function: testing');
})
it('should throw errors for missing mandatory parameters without package', () => {
const config = { topic: 'topic', user: 'user', password: 'password', admin_url: 'url', brokers: 'brokers' }
Object.keys(config).forEach(key => {
const cloned = Object.assign({}, config)
cloned[key] = ''
expect(() => openwhiskCompileMessageHub.compileMessageHubTrigger('testing', cloned))
.to.throw(Error, `Message Hub event property (${key}) missing on function: testing`);
})
})
it('should return trigger for message hub provider using package.', () => {
const topic = 'my_topic', pkge = '/bluemixOrg_bluemixSpace/packageId'
const trigger = openwhiskCompileMessageHub.compileMessageHubTrigger('testing', { topic, 'package': pkge })
expect(trigger).to.be.deep.equal({
name: `${serverless.service.service}_testing_messagehub_${topic}`,
content: {
feed: `${pkge}/messageHubFeed`,
feed_parameters: {
topic: `${topic}`,
isJSONData: false,
isBinaryKey: false,
isBinaryValue: false
}
}
})
})
it('should return trigger for message hub provider using package with options.', () => {
const topic = 'my_topic', pkge = '/bluemixOrg_bluemixSpace/packageId'
const trigger = openwhiskCompileMessageHub.compileMessageHubTrigger('testing', { json: true, binary_value: true, binary_key: true, topic, 'package': pkge })
expect(trigger).to.be.deep.equal({
name: `${serverless.service.service}_testing_messagehub_${topic}`,
content: {
feed: `${pkge}/messageHubFeed`,
feed_parameters: {
topic: `${topic}`,
isJSONData: true,
isBinaryKey: true,
isBinaryValue: true
}
}
})
})
it('should return trigger with minimum message hub config properties.', () => {
const config = { topic: 'topic', user: 'user', password: 'password', admin_url: 'url', brokers: 'brokers' }
const trigger = openwhiskCompileMessageHub.compileMessageHubTrigger('testing', config)
expect(trigger).to.be.deep.equal({
name: `${serverless.service.service}_testing_messagehub_${config.topic}`,
content: {
feed: `/whisk.system/messaging/messageHubFeed`,
feed_parameters: {
kafka_brokers_sasl: config.brokers,
user: config.user,
password: config.password,
topic: config.topic,
kafka_admin_url: config.admin_url,
isJSONData: false,
isBinaryKey: false,
isBinaryValue: false
}
}
})
})
it('should return trigger with optional message hub config properties.', () => {
const config = { json: true, binary_key: true, binary_value: true, topic: 'topic', user: 'user', password: 'password', admin_url: 'url', brokers: ['a', 'b', 'c'] }
const trigger = openwhiskCompileMessageHub.compileMessageHubTrigger('testing', config)
expect(trigger).to.be.deep.equal({
name: `${serverless.service.servic
gitextract_ir1tuu5t/
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── .travis.yml
├── LICENSE.txt
├── README.md
├── compile/
│ ├── apigw/
│ │ ├── README.md
│ │ ├── index.js
│ │ └── tests/
│ │ └── index.js
│ ├── cloudant/
│ │ ├── README.md
│ │ ├── index.js
│ │ └── tests/
│ │ └── index.js
│ ├── functions/
│ │ ├── README.md
│ │ ├── index.js
│ │ ├── runtimes/
│ │ │ ├── base.js
│ │ │ ├── binary.js
│ │ │ ├── docker.js
│ │ │ ├── index.js
│ │ │ ├── java.js
│ │ │ ├── node.js
│ │ │ ├── php.js
│ │ │ ├── python.js
│ │ │ ├── ruby.js
│ │ │ ├── sequence.js
│ │ │ ├── swift.js
│ │ │ └── tests/
│ │ │ ├── all.js
│ │ │ ├── base.js
│ │ │ ├── binary.js
│ │ │ ├── docker.js
│ │ │ ├── index.js
│ │ │ ├── java.js
│ │ │ ├── node.js
│ │ │ ├── php.js
│ │ │ ├── python.js
│ │ │ ├── ruby.js
│ │ │ ├── sequence.js
│ │ │ └── swift.js
│ │ └── tests/
│ │ └── index.js
│ ├── message_hub/
│ │ ├── README.md
│ │ ├── index.js
│ │ └── tests/
│ │ └── index.js
│ ├── packages/
│ │ ├── README.md
│ │ ├── index.js
│ │ └── tests/
│ │ └── index.js
│ ├── rules/
│ │ ├── README.md
│ │ ├── index.js
│ │ └── tests/
│ │ └── index.js
│ ├── schedule/
│ │ ├── README.md
│ │ ├── index.js
│ │ └── tests/
│ │ └── index.js
│ ├── servicebindings/
│ │ ├── README.md
│ │ ├── index.js
│ │ └── tests/
│ │ └── index.js
│ └── triggers/
│ ├── README.md
│ ├── index.js
│ └── tests/
│ └── index.js
├── configCredentials/
│ ├── index.js
│ └── tests/
│ └── index.js
├── deploy/
│ ├── README.md
│ ├── index.js
│ ├── lib/
│ │ ├── deployApiGw.js
│ │ ├── deployFeeds.js
│ │ ├── deployFunctions.js
│ │ ├── deployPackages.js
│ │ ├── deployRules.js
│ │ ├── deployServiceBindings.js
│ │ ├── deployTriggers.js
│ │ ├── initializeResources.js
│ │ └── validate.js
│ └── tests/
│ ├── all.js
│ ├── deployApiGw.js
│ ├── deployFeeds.js
│ ├── deployFunctions.js
│ ├── deployPackages.js
│ ├── deployRules.js
│ ├── deployServiceBindings.js
│ ├── deployTriggers.js
│ ├── index.js
│ ├── initializeResources.js
│ ├── resources/
│ │ ├── swagger.json
│ │ ├── swagger_default_ns.json
│ │ ├── swagger_ns_paths.json
│ │ └── swagger_paths.json
│ └── validate.js
├── deployFunction/
│ ├── index.js
│ └── tests/
│ └── index.js
├── index.js
├── info/
│ ├── index.js
│ └── tests/
│ └── index.js
├── invoke/
│ ├── README.md
│ ├── index.js
│ └── tests/
│ └── index.js
├── invokeLocal/
│ ├── index.js
│ ├── invoke.py
│ └── tests/
│ ├── fixture/
│ │ └── handlerWithError.js
│ └── index.js
├── logs/
│ ├── index.js
│ └── tests/
│ └── index.js
├── package.json
├── provider/
│ ├── cliTokenManager.js
│ ├── credentials.js
│ ├── openwhiskProvider.js
│ └── tests/
│ ├── cliTokenManager.js
│ ├── credentials.js
│ ├── index.js
│ └── openwhiskProvider.js
├── remove/
│ ├── README.md
│ ├── index.js
│ ├── lib/
│ │ ├── removeFeeds.js
│ │ ├── removeFunctions.js
│ │ ├── removePackages.js
│ │ ├── removeRoutes.js
│ │ ├── removeRules.js
│ │ ├── removeTriggers.js
│ │ ├── setupResources.js
│ │ ├── util.js
│ │ └── validate.js
│ └── tests/
│ ├── all.js
│ ├── index.js
│ ├── removeFeeds.js
│ ├── removeFunctions.js
│ ├── removePackages.js
│ ├── removeRoutes.js
│ ├── removeRules.js
│ ├── removeTriggers.js
│ └── setupResources.js
├── tests/
│ └── all.js
├── tools/
│ └── travis/
│ ├── build.sh
│ └── setup.sh
└── utils/
└── index.js
SYMBOL INDEX (298 symbols across 51 files)
FILE: compile/apigw/index.js
class OpenWhiskCompileHttpEvents (line 7) | class OpenWhiskCompileHttpEvents {
method constructor (line 8) | constructor(serverless, options) {
method setup (line 20) | setup() {
method generateAuthString (line 34) | generateAuthString() {
method addWebAnnotations (line 40) | addWebAnnotations() {
method calculateFunctionName (line 58) | calculateFunctionName(functionName, functionObject) {
method calculateFunctionNameSpace (line 62) | calculateFunctionNameSpace(functionObject) {
method retrieveAuthKey (line 68) | retrieveAuthKey(functionObject) {
method compileHttpEvent (line 82) | compileHttpEvent(funcName, funcObj, http) {
method parseHttpEvent (line 98) | parseHttpEvent(httpEvent) {
method addAuthToSwagger (line 114) | addAuthToSwagger(swagger, auth) {
method addOAuthToSwagger (line 136) | addOAuthToSwagger(swagger, oauth) {
method addRateLimitToSwagger (line 178) | addRateLimitToSwagger(swagger, rate_limit) {
method generateSwagger (line 205) | generateSwagger(service, host, options, httpEvents) {
method compileSwaggerPath (line 251) | compileSwaggerPath(httpEvent, host) {
method parsePathParameters (line 271) | parsePathParameters (path) {
method createPathParameter (line 288) | createPathParameter (name) {
method compileSwaggerCaseSwitch (line 296) | compileSwaggerCaseSwitch(httpEvent, host) {
method webActionUrl (line 316) | webActionUrl(httpEvent, host, has_path_params) {
method operationId (line 322) | operationId(httpEvent) {
method compileFunctionHttpEvents (line 326) | compileFunctionHttpEvents(functionName, functionObject) {
method compileHttpEvents (line 340) | compileHttpEvents () {
FILE: compile/cloudant/index.js
class OpenWhiskCompileCloudant (line 5) | class OpenWhiskCompileCloudant {
method constructor (line 6) | constructor(serverless, options) {
method setup (line 19) | setup() {
method validateConfig (line 33) | validateConfig (fnName, config) {
method compileCloudantTrigger (line 66) | compileCloudantTrigger (fnName, config) {
method defaultCloudantName (line 100) | defaultCloudantName (fnName, db) {
method processCloudantEvent (line 104) | processCloudantEvent (fnName, config) {
method processCloudantEvents (line 113) | processCloudantEvents () {
FILE: compile/functions/index.js
class OpenWhiskCompileFunctions (line 6) | class OpenWhiskCompileFunctions {
method constructor (line 7) | constructor(serverless, options) {
method excludes (line 23) | excludes() {
method disableSeqPackaging (line 29) | disableSeqPackaging() {
method setup (line 39) | setup() {
method calculateFunctionName (line 45) | calculateFunctionName(functionName, functionObject) {
method calculateFunctionNameSpace (line 49) | calculateFunctionNameSpace(functionName, functionObject) {
method calculateMemorySize (line 53) | calculateMemorySize(functionObject) {
method calculateConcurrency (line 57) | calculateConcurrency(functionObject) {
method calculateTimeout (line 61) | calculateTimeout(functionObject) {
method calculateOverwrite (line 65) | calculateOverwrite(functionObject) {
method compileFunctionAction (line 77) | compileFunctionAction(params) {
method compileFunction (line 103) | compileFunction(functionName, functionObject) {
method constructAnnotations (line 125) | constructAnnotations (annotations) {
method logCompiledFunction (line 140) | logCompiledFunction (name, fn) {
method compileFunctions (line 148) | compileFunctions() {
FILE: compile/functions/runtimes/base.js
class BaseRuntime (line 7) | class BaseRuntime {
method constructor (line 8) | constructor(serverless) {
method match (line 12) | match (functionObject) {
method exec (line 18) | exec (functionObject) {
method calculateFunctionMain (line 30) | calculateFunctionMain(functionObject) {
method calculateRuntime (line 38) | calculateRuntime(functionObject) {
method calculateDefaultRuntime (line 42) | calculateDefaultRuntime(functionObject) {
method calculateKind (line 47) | calculateKind(functionObject) {
method isValidFile (line 53) | isValidFile (handlerFile) {
method convertHandlerToPath (line 57) | convertHandlerToPath(functionHandler) {
method convertHandlerToPathInZip (line 65) | convertHandlerToPathInZip(functionHandler) {
method generateActionPackage (line 73) | generateActionPackage(functionObject) {
method getArtifactZip (line 91) | getArtifactZip(functionObject) {
method getArtifactPath (line 97) | getArtifactPath(functionObject) {
FILE: compile/functions/runtimes/binary.js
class Binary (line 5) | class Binary extends BaseRuntime {
method constructor (line 6) | constructor (serverless) {
method exec (line 11) | exec (functionObject) {
method processActionPackage (line 17) | processActionPackage (handlerFile, zip) {
method calculateKind (line 24) | calculateKind (functionObject) {
method convertHandlerToPath (line 28) | convertHandlerToPath (functionHandler) {
FILE: compile/functions/runtimes/docker.js
class Docker (line 3) | class Docker {
method constructor (line 4) | constructor(serverless) {
method match (line 8) | match (functionObject) {
method exec (line 13) | exec (functionObject) {
method calculateRuntime (line 17) | calculateRuntime(functionObject) {
FILE: compile/functions/runtimes/index.js
class Runtimes (line 13) | class Runtimes {
method constructor (line 14) | constructor(serverless) {
method exec (line 29) | exec (functionObj) {
FILE: compile/functions/runtimes/java.js
class Java (line 8) | class Java extends BaseRuntime {
method constructor (line 9) | constructor(serverless) {
method convertHandlerToPath (line 15) | convertHandlerToPath(functionHandler) {
method calculateFunctionMain (line 24) | calculateFunctionMain(functionObject) {
method processActionPackage (line 36) | processActionPackage(handlerFile, zip) {
FILE: compile/functions/runtimes/node.js
class Node (line 5) | class Node extends BaseRuntime {
method constructor (line 6) | constructor (serverless) {
method calculateRuntime (line 12) | calculateRuntime(functionObject) {
method processActionPackage (line 16) | processActionPackage (handlerFile, zip) {
method isValidFile (line 25) | isValidFile(handlerFile) {
method isValidTypeScriptFile (line 30) | isValidTypeScriptFile(handlerFile) {
FILE: compile/functions/runtimes/php.js
class Php (line 5) | class Php extends BaseRuntime {
method constructor (line 6) | constructor (serverless) {
method processActionPackage (line 12) | processActionPackage (handlerFile, zip) {
FILE: compile/functions/runtimes/python.js
class Python (line 5) | class Python extends BaseRuntime {
method constructor (line 6) | constructor (serverless) {
method processActionPackage (line 12) | processActionPackage (handlerFile, zip) {
method calculateDefaultRuntime (line 19) | calculateDefaultRuntime (functionObject) {
FILE: compile/functions/runtimes/ruby.js
class Ruby (line 5) | class Ruby extends BaseRuntime {
method constructor (line 6) | constructor (serverless) {
method processActionPackage (line 12) | processActionPackage (handlerFile, zip) {
FILE: compile/functions/runtimes/sequence.js
class Sequence (line 3) | class Sequence {
method constructor (line 4) | constructor(serverless) {
method match (line 8) | match (functionObject) {
method exec (line 12) | exec (functionObject) {
FILE: compile/functions/runtimes/swift.js
class Swift (line 7) | class Swift extends BaseRuntime {
method constructor (line 8) | constructor (serverless) {
method convertHandlerToPath (line 14) | convertHandlerToPath (functionHandler) {
method calculateFunctionMain (line 22) | calculateFunctionMain(functionObject) {
method isZipFile (line 30) | isZipFile (path) {
method readHandlerFile (line 34) | readHandlerFile (path) {
method exec (line 40) | exec (functionObject) {
FILE: compile/message_hub/index.js
class OpenWhiskCompileMessageHub (line 7) | class OpenWhiskCompileMessageHub {
method constructor (line 8) | constructor(serverless, options) {
method setup (line 21) | setup() {
method validateConfig (line 35) | validateConfig (fnName, config) {
method compileMessageHubTrigger (line 53) | compileMessageHubTrigger (fnName, config) {
method defaultMessageHubName (line 75) | defaultMessageHubName (fnName, topic) {
method processMessageHubEvent (line 79) | processMessageHubEvent (fnName, config) {
method processMessageHubEvents (line 88) | processMessageHubEvents () {
FILE: compile/packages/index.js
class OpenWhiskCompilePackages (line 5) | class OpenWhiskCompilePackages {
method constructor (line 6) | constructor(serverless, options) {
method setup (line 20) | setup() {
method renameManifestPackages (line 26) | renameManifestPackages() {
method mergeActionPackages (line 43) | mergeActionPackages() {
method getActionPackages (line 62) | getActionPackages() {
method calculatePackageName (line 76) | calculatePackageName(packageName, packageObject) {
method compilePackage (line 80) | compilePackage(name, params) {
method compilePackages (line 125) | compilePackages() {
FILE: compile/rules/index.js
class OpenWhiskCompileRules (line 5) | class OpenWhiskCompileRules {
method constructor (line 6) | constructor(serverless, options) {
method setup (line 17) | setup() {
method calculateFunctionName (line 31) | calculateFunctionName(functionName, functionObject) {
method calculateFunctionNameSpace (line 37) | calculateFunctionNameSpace(functionObject) {
method calculateTriggerName (line 41) | calculateTriggerName(triggerName) {
method generateDefaultRuleName (line 52) | generateDefaultRuleName(functionName, triggerName) {
method compileRule (line 65) | compileRule(funcName, funcObj, trigger) {
method compileFunctionRules (line 94) | compileFunctionRules(functionName, functionObject) {
method compileRules (line 108) | compileRules() {
FILE: compile/schedule/index.js
class OpenWhiskCompileSchedules (line 5) | class OpenWhiskCompileSchedules {
method constructor (line 6) | constructor(serverless, options) {
method setup (line 19) | setup() {
method parseScheduleRate (line 34) | parseScheduleRate (rate) {
method compileScheduleTrigger (line 46) | compileScheduleTrigger (fnName, schedule) {
method defaultScheduleRuleName (line 60) | defaultScheduleRuleName (triggerName, fnName) {
method processScheduleEvent (line 64) | processScheduleEvent (fnName, schedule) {
method processScheduleEvents (line 73) | processScheduleEvents () {
FILE: compile/servicebindings/index.js
class OpenWhiskCompileServiceBindings (line 5) | class OpenWhiskCompileServiceBindings {
method constructor (line 6) | constructor(serverless, options) {
method calculateFunctionName (line 16) | calculateFunctionName(name, props) {
method parseServiceBindings (line 20) | parseServiceBindings(name, properties) {
method compileFnServiceBindings (line 42) | compileFnServiceBindings() {
method compilePkgServiceBindings (line 52) | compilePkgServiceBindings() {
method compileServiceBindings (line 61) | compileServiceBindings() {
FILE: compile/triggers/index.js
class OpenWhiskCompileTriggers (line 5) | class OpenWhiskCompileTriggers {
method constructor (line 6) | constructor(serverless, options) {
method setup (line 19) | setup() {
method mergeEventTriggers (line 25) | mergeEventTriggers() {
method getEventTriggers (line 44) | getEventTriggers() {
method compileTriggerFeed (line 66) | compileTriggerFeed(trigger, feed, params) {
method compileTrigger (line 83) | compileTrigger(name, params) {
method compileTriggers (line 116) | compileTriggers() {
FILE: configCredentials/index.js
class OpenWhiskConfigCredentials (line 7) | class OpenWhiskConfigCredentials {
method constructor (line 8) | constructor(serverless, options) {
method configureCredentials (line 45) | configureCredentials() {
FILE: deploy/index.js
class OpenWhiskDeploy (line 13) | class OpenWhiskDeploy {
method constructor (line 14) | constructor(serverless, options) {
FILE: deploy/lib/deployApiGw.js
method deployRouteSwagger (line 6) | deployRouteSwagger(route) {
method replaceDefaultNamespace (line 25) | replaceDefaultNamespace(swagger) {
method deployRoutes (line 52) | deployRoutes() {
FILE: deploy/lib/deployFeeds.js
method deleteFeed (line 7) | deleteFeed(feed) {
method deployFeed (line 15) | deployFeed(feed) {
method deployFeeds (line 33) | deployFeeds() {
method getFeeds (line 46) | getFeeds() {
FILE: deploy/lib/deployFunctions.js
method deployFunctionHandler (line 6) | deployFunctionHandler(functionHandler) {
method deploySequences (line 25) | deploySequences() {
method deployFunctions (line 35) | deployFunctions() {
method deployActions (line 40) | deployActions(names) {
method filterActions (line 47) | filterActions(sequence) {
FILE: deploy/lib/deployPackages.js
method deployPackage (line 6) | deployPackage(pkge) {
method deployPackages (line 24) | deployPackages() {
method getPackages (line 36) | getPackages() {
FILE: deploy/lib/deployRules.js
method deployRule (line 6) | deployRule(rule) {
method enableRule (line 24) | enableRule(rule) {
method deployRules (line 34) | deployRules() {
method getRules (line 42) | getRules() {
FILE: deploy/lib/deployServiceBindings.js
method configureServiceBinding (line 7) | configureServiceBinding(binding) {
method configureServiceBindings (line 66) | configureServiceBindings() {
method getServiceBindings (line 80) | getServiceBindings() {
FILE: deploy/lib/deployTriggers.js
method deployTrigger (line 6) | deployTrigger(trigger) {
method deployTriggers (line 25) | deployTriggers() {
method getTriggers (line 37) | getTriggers(triggers) {
FILE: deploy/lib/initializeResources.js
method initializeResources (line 8) | initializeResources() {
FILE: deploy/lib/validate.js
method validate (line 6) | validate () {
FILE: deployFunction/index.js
class OpenWhiskDeployFunction (line 8) | class OpenWhiskDeployFunction {
method constructor (line 9) | constructor(serverless, options) {
method validate (line 32) | validate () {
method compileFunction (line 47) | compileFunction () {
method packageFunction (line 52) | packageFunction () {
method deployFunction (line 64) | deployFunction (data) {
method cleanup (line 77) | cleanup () {
FILE: index.js
class Index (line 28) | class Index {
method constructor (line 29) | constructor(serverless, options) {
FILE: info/index.js
class OpenWhiskInfo (line 7) | class OpenWhiskInfo {
method constructor (line 8) | constructor(serverless, options) {
method validate (line 31) | validate() {
method info (line 45) | info () {
method showServiceInfo (line 58) | showServiceInfo () {
method showActionsInfo (line 64) | showActionsInfo () {
method showWebActionsInfo (line 78) | showWebActionsInfo () {
method showResourcesInfo (line 103) | showResourcesInfo (resource) {
method showPackagesInfo (line 112) | showPackagesInfo () {
method showTriggersInfo (line 116) | showTriggersInfo () {
method showRulesInfo (line 120) | showRulesInfo () {
method showRoutesInfo (line 124) | showRoutesInfo () {
method logEndPoint (line 144) | logEndPoint (baseUrl, path, method, actionName) {
method logApiEndPoints (line 156) | logApiEndPoints (api) {
method consoleLog (line 173) | consoleLog (message) {
FILE: invoke/index.js
class OpenWhiskInvoke (line 14) | class OpenWhiskInvoke {
method constructor (line 15) | constructor(serverless, options) {
method validate (line 28) | validate() {
method readFileSync (line 79) | readFileSync(path) {
method getStdin (line 83) | getStdin() {
method validateParamOptions (line 87) | validateParamOptions() {
method invoke (line 100) | invoke() {
method formatErrMsg (line 121) | formatErrMsg (err) {
method isBlocking (line 134) | isBlocking() {
method isLogResult (line 138) | isLogResult() {
method log (line 142) | log(invocationReply) {
method logDetails (line 165) | logDetails(invocationReply) {
method consoleLog (line 182) | consoleLog(msg) {
FILE: invokeLocal/index.js
class OpenWhiskInvokeLocal (line 10) | class OpenWhiskInvokeLocal {
method constructor (line 11) | constructor(serverless, options) {
method validate (line 25) | validate() {
method mergePackageParams (line 67) | mergePackageParams() {
method loadEnvVars (line 82) | loadEnvVars() {
method calculateFunctionName (line 97) | calculateFunctionName(functionName, functionObject) {
method calculateFunctionNameSpace (line 103) | calculateFunctionNameSpace(functionObject) {
method invokeLocal (line 109) | invokeLocal() {
method invokeLocalNodeJs (line 133) | invokeLocalNodeJs(handlerPath, handlerName, params) {
method invokeLocalPython (line 170) | invokeLocalPython(handlerPath, handlerName, params) {
FILE: logs/index.js
class OpenWhiskLogs (line 8) | class OpenWhiskLogs {
method constructor (line 9) | constructor(serverless, options) {
method validate (line 22) | validate () {
method functionLogs (line 51) | functionLogs () {
method retrieveInvocationLogs (line 65) | retrieveInvocationLogs () {
method hasPathAnnotationWithName (line 84) | hasPathAnnotationWithName (annotations, name) {
method filterFunctionLogs (line 90) | filterFunctionLogs (logs) {
method showFunctionLogs (line 118) | showFunctionLogs (logs) {
method formatActivationLine (line 140) | formatActivationLine (activation) {
method formatLogLine (line 144) | formatLogLine (logLine) {
method consoleLog (line 157) | consoleLog(msg) {
FILE: provider/cliTokenManager.js
constant DEFAULT_CONFIG_LOCATION (line 12) | const DEFAULT_CONFIG_LOCATION = `.bluemix/config.json`
method constructor (line 19) | constructor(_exec = exec, _readFile = readFileSync) {
method getAuthHeader (line 25) | getAuthHeader () {
method refreshToken (line 35) | refreshToken () {
method readTokenFromConfig (line 49) | readTokenFromConfig (configPath = CliTokenManager.configFilePath()) {
method isTokenExpired (line 56) | isTokenExpired (token) {
method configFilePath (line 65) | static configFilePath (config_file = DEFAULT_CONFIG_LOCATION) {
FILE: provider/credentials.js
constant ENV_PARAMS (line 6) | const ENV_PARAMS = ['OW_APIHOST', 'OW_AUTH', 'OW_NAMESPACE', 'OW_APIGW_A...
function getWskPropsFile (line 8) | function getWskPropsFile() {
function readWskPropsFile (line 13) | function readWskPropsFile() {
function getWskProps (line 23) | function getWskProps() {
function getWskEnvProps (line 38) | function getWskEnvProps() {
method getWskProps (line 47) | getWskProps() {
FILE: provider/openwhiskProvider.js
class OpenwhiskProvider (line 15) | class OpenwhiskProvider {
method getProviderName (line 16) | static getProviderName() {
method constructor (line 20) | constructor(serverless) {
method client (line 30) | client() {
method props (line 50) | props() {
method hasValidCreds (line 59) | hasValidCreds(creds) {
method isIBMCloudIAMProps (line 71) | isIBMCloudIAMProps (props) {
FILE: remove/index.js
class OpenWhiskRemove (line 14) | class OpenWhiskRemove {
method constructor (line 15) | constructor(serverless, options) {
FILE: remove/lib/removeFeeds.js
method removeFeed (line 6) | removeFeed (feed) {
method removeTriggerFeed (line 13) | removeTriggerFeed(triggerName, params) {
method removeFeeds (line 28) | removeFeeds() {
FILE: remove/lib/removeFunctions.js
method removeFunctionHandler (line 6) | removeFunctionHandler(functionHandler) {
method removeFunction (line 13) | removeFunction(functionName) {
method removeFunctions (line 27) | removeFunctions() {
FILE: remove/lib/removePackages.js
method removePackageHandler (line 6) | removePackageHandler(pkge) {
method removePackage (line 13) | removePackage(name) {
method removePackages (line 24) | removePackages() {
FILE: remove/lib/removeRoutes.js
method hasRoutes (line 6) | hasRoutes() {
method basePath (line 15) | basePath() {
method removeRoutes (line 21) | removeRoutes() {
FILE: remove/lib/removeRules.js
method removeRule (line 7) | removeRule (ruleName) {
method removeRules (line 13) | removeRules() {
FILE: remove/lib/removeTriggers.js
method removeTriggerHandler (line 6) | removeTriggerHandler(Trigger) {
method removeTrigger (line 13) | removeTrigger(triggerName) {
method removeTriggers (line 24) | removeTriggers() {
FILE: remove/lib/setupResources.js
method initializeTriggers (line 6) | initializeTriggers () {
method getEventTriggers (line 23) | getEventTriggers() {
method initializeRules (line 49) | initializeRules() {
method getRuleName (line 59) | getRuleName(funcName, funcObj, trigger) {
method getScheduleRuleName (line 69) | getScheduleRuleName(funcName, funcObj, schedule) {
method getMessageHubRuleName (line 73) | getMessageHubRuleName(funcName, funcObj, config) {
method getCloudantRuleName (line 77) | getCloudantRuleName(funcName, funcObj, config) {
method getRuleNames (line 81) | getRuleNames(functionName, functionObject) {
method generateDefaultRuleName (line 103) | generateDefaultRuleName(functionName, triggerName) {
method setupResources (line 107) | setupResources () {
FILE: remove/lib/util.js
method handleOperationFailure (line 4) | handleOperationFailure (onProvider, errMsgTemplate) {
FILE: remove/lib/validate.js
method validate (line 6) | validate() {
FILE: utils/index.js
function formatApiHost (line 3) | function formatApiHost(apihost) {
Condensed preview — 130 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (509K chars).
[
{
"path": ".eslintignore",
"chars": 45,
"preview": "coverage\nnode_modules\ntmp\ntmpdirs-serverless\n"
},
{
"path": ".eslintrc.js",
"chars": 318,
"preview": "module.exports = {\n \"extends\": \"airbnb\",\n \"plugins\": [],\n \"rules\": {\n \"func-names\": \"off\",\n\n // doesn't work in"
},
{
"path": ".gitignore",
"chars": 689,
"preview": "# Logs\n*.log\nnpm-debug.log\n\n# Runtime data\npids\n*.pid\n*.seed\ndist\n\n# Directory for instrumented libs generated by jscove"
},
{
"path": ".travis.yml",
"chars": 276,
"preview": "language: node_js\nnode_js:\n - \"11\"\n - \"10\"\n - \"8\"\n\nsudo: required\n\nservices:\n - docker\n\nbefore_install:\n - cd $TRAVI"
},
{
"path": "LICENSE.txt",
"chars": 1086,
"preview": "Copyright (c) 2018 Serverless, Inc. http://www.serverless.com\n\nPermission is hereby granted, free of charge, to any pers"
},
{
"path": "README.md",
"chars": 44236,
"preview": "**📦 Archived - This repository is archived and preserved for reference only. No updates, issues, or pull requests will b"
},
{
"path": "compile/apigw/README.md",
"chars": 1949,
"preview": "# Compile API Gateway Endpoints\n\nThis plugins compiles the HTTP events bound to functions in `serverless.yaml` to\ncorres"
},
{
"path": "compile/apigw/index.js",
"chars": 11493,
"preview": "'use strict';\n\nconst BbPromise = require('bluebird');\nconst crypto = require('crypto');\nconst { formatApiHost } = requir"
},
{
"path": "compile/apigw/tests/index.js",
"chars": 33990,
"preview": "'use strict';\n\nconst crypto = require('crypto');\nconst BbPromise = require('bluebird');\nconst expect = require('chai').e"
},
{
"path": "compile/cloudant/README.md",
"chars": 3065,
"preview": "# Cloudant Events\n\nThis plugins compiles the `cloudant` events in `serverless.yaml` to corresponding [OpenWhisk Cloudant"
},
{
"path": "compile/cloudant/index.js",
"chars": 3529,
"preview": "'use strict';\n\nconst BbPromise = require('bluebird');\n\nclass OpenWhiskCompileCloudant {\n constructor(serverless, option"
},
{
"path": "compile/cloudant/tests/index.js",
"chars": 7271,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst chaiAsPromised = require('chai-as-promised');\n\nrequire('chai"
},
{
"path": "compile/functions/README.md",
"chars": 1799,
"preview": "# Compile Functions\n\nThis plugins compiles the functions in `serverless.yaml` to corresponding [OpenWhisk Action](https:"
},
{
"path": "compile/functions/index.js",
"chars": 6542,
"preview": "'use strict';\n\nconst BbPromise = require('bluebird');\nconst Runtimes = require('./runtimes/index.js')\n\nclass OpenWhiskCo"
},
{
"path": "compile/functions/runtimes/base.js",
"chars": 3132,
"preview": "'use strict';\n\nconst fs = require('fs-extra')\nconst JSZip = require('jszip')\nconst BbPromise = require('bluebird')\n\nclas"
},
{
"path": "compile/functions/runtimes/binary.js",
"chars": 719,
"preview": "'use strict';\n\nconst BaseRuntime = require('./base')\n\nclass Binary extends BaseRuntime {\n constructor (serverless) {\n "
},
{
"path": "compile/functions/runtimes/docker.js",
"chars": 495,
"preview": "'use strict';\n\nclass Docker {\n constructor(serverless) {\n this.serverless = serverless\n }\n\n match (functionObject)"
},
{
"path": "compile/functions/runtimes/index.js",
"chars": 1021,
"preview": "'use strict';\n\nconst Binary = require('./binary')\nconst Docker = require('./docker')\nconst Node = require('./node')\ncons"
},
{
"path": "compile/functions/runtimes/java.js",
"chars": 1138,
"preview": "'use strict';\n\nconst fs = require('fs-extra');\nconst BbPromise = require('bluebird');\nconst BaseRuntime = require('./bas"
},
{
"path": "compile/functions/runtimes/node.js",
"chars": 1152,
"preview": "'use strict';\n\nconst BaseRuntime = require('./base')\n\nclass Node extends BaseRuntime {\n constructor (serverless) {\n "
},
{
"path": "compile/functions/runtimes/php.js",
"chars": 410,
"preview": "'use strict';\n\nconst BaseRuntime = require('./base')\n\nclass Php extends BaseRuntime {\n constructor (serverless) {\n s"
},
{
"path": "compile/functions/runtimes/python.js",
"chars": 519,
"preview": "'use strict';\n\nconst BaseRuntime = require('./base')\n\nclass Python extends BaseRuntime {\n constructor (serverless) {\n "
},
{
"path": "compile/functions/runtimes/ruby.js",
"chars": 411,
"preview": "'use strict';\n\nconst BaseRuntime = require('./base')\n\nclass Ruby extends BaseRuntime {\n constructor (serverless) {\n "
},
{
"path": "compile/functions/runtimes/sequence.js",
"chars": 613,
"preview": "'use strict';\n\nclass Sequence {\n constructor(serverless) {\n this.serverless = serverless\n }\n\n match (functionObjec"
},
{
"path": "compile/functions/runtimes/swift.js",
"chars": 1524,
"preview": "'use strict';\n\nconst fs = require('fs-extra')\nconst BaseRuntime = require('./base')\nconst JSZip = require(\"jszip\")\n\nclas"
},
{
"path": "compile/functions/runtimes/tests/all.js",
"chars": 235,
"preview": "'use strict';\n\nrequire('./index');\nrequire('./base');\nrequire('./node');\nrequire('./docker');\nrequire('./python');\nrequi"
},
{
"path": "compile/functions/runtimes/tests/base.js",
"chars": 3072,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\n\nconst BaseRuntime = require('../base');\n\ndescribe('Base', () => {"
},
{
"path": "compile/functions/runtimes/tests/binary.js",
"chars": 4332,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst chaiAsPromised = require('chai-as-promised');\n\nrequire('chai"
},
{
"path": "compile/functions/runtimes/tests/docker.js",
"chars": 1893,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst chaiAsPromised = require('chai-as-promised');\n\nrequire('chai"
},
{
"path": "compile/functions/runtimes/tests/index.js",
"chars": 1679,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst chaiAsPromised = require('chai-as-promised');\n\nrequire('chai"
},
{
"path": "compile/functions/runtimes/tests/java.js",
"chars": 5451,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst chaiAsPromised = require('chai-as-promised');\n\nrequire('chai"
},
{
"path": "compile/functions/runtimes/tests/node.js",
"chars": 7568,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst chaiAsPromised = require('chai-as-promised');\n\nrequire('chai"
},
{
"path": "compile/functions/runtimes/tests/php.js",
"chars": 4910,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst chaiAsPromised = require('chai-as-promised');\n\nrequire('chai"
},
{
"path": "compile/functions/runtimes/tests/python.js",
"chars": 5031,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst chaiAsPromised = require('chai-as-promised');\n\nrequire('chai"
},
{
"path": "compile/functions/runtimes/tests/ruby.js",
"chars": 5031,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst chaiAsPromised = require('chai-as-promised');\n\nrequire('chai"
},
{
"path": "compile/functions/runtimes/tests/sequence.js",
"chars": 1360,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst chaiAsPromised = require('chai-as-promised');\n\nrequire('chai"
},
{
"path": "compile/functions/runtimes/tests/swift.js",
"chars": 3277,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst chaiAsPromised = require('chai-as-promised');\n\nrequire('chai"
},
{
"path": "compile/functions/tests/index.js",
"chars": 10823,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst chaiAsPromised = require('chai-as-promised');\n\nrequire('chai"
},
{
"path": "compile/message_hub/README.md",
"chars": 2867,
"preview": "# Compile Triggers\n\nThis plugins compiles the `message_hub` events in `serverless.yaml` to corresponding [OpenWhisk Mess"
},
{
"path": "compile/message_hub/index.js",
"chars": 3066,
"preview": "'use strict';\n\nconst BbPromise = require('bluebird');\n\nconst config_properties = ['user', 'password', 'brokers', 'topic'"
},
{
"path": "compile/message_hub/tests/index.js",
"chars": 6820,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst chaiAsPromised = require('chai-as-promised');\n\nrequire('chai"
},
{
"path": "compile/packages/README.md",
"chars": 2118,
"preview": "# Compile Packages\n\nThis plugins compiles the packages in `serverless.yaml` to corresponding [OpenWhisk Packages](https:"
},
{
"path": "compile/packages/index.js",
"chars": 4331,
"preview": "'use strict';\n\nconst BbPromise = require('bluebird');\n\nclass OpenWhiskCompilePackages {\n constructor(serverless, option"
},
{
"path": "compile/packages/tests/index.js",
"chars": 10960,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst chaiAsPromised = require('chai-as-promised');\n\nrequire('chai"
},
{
"path": "compile/rules/README.md",
"chars": 1540,
"preview": "# Compile Rules\n\nThis plugins compiles the triggers bound to functions in `serverless.yaml` to\ncorresponding [OpenWhisk "
},
{
"path": "compile/rules/index.js",
"chars": 4532,
"preview": "'use strict';\n\nconst BbPromise = require('bluebird');\n\nclass OpenWhiskCompileRules {\n constructor(serverless, options) "
},
{
"path": "compile/rules/tests/index.js",
"chars": 8533,
"preview": "'use strict';\n\nconst BbPromise = require('bluebird');\nconst expect = require('chai').expect;\nconst chaiAsPromised = requ"
},
{
"path": "compile/schedule/README.md",
"chars": 1691,
"preview": "# Compile Triggers\n\nThis plugins compiles the schedule events in `serverless.yaml` to corresponding [OpenWhisk Alarm Tri"
},
{
"path": "compile/schedule/index.js",
"chars": 2574,
"preview": "'use strict';\n\nconst BbPromise = require('bluebird');\n\nclass OpenWhiskCompileSchedules {\n constructor(serverless, optio"
},
{
"path": "compile/schedule/tests/index.js",
"chars": 6626,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst chaiAsPromised = require('chai-as-promised');\n\nrequire('chai"
},
{
"path": "compile/servicebindings/README.md",
"chars": 2046,
"preview": "# Service Bindings\n\nThis plugin binds IBM Cloud platform service credentials to actions and packages in `serverless.yaml"
},
{
"path": "compile/servicebindings/index.js",
"chars": 2160,
"preview": "'use strict';\n\nconst BbPromise = require('bluebird');\n\nclass OpenWhiskCompileServiceBindings {\n constructor(serverless,"
},
{
"path": "compile/servicebindings/tests/index.js",
"chars": 7179,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst chaiAsPromised = require('chai-as-promised');\n\nrequire('chai"
},
{
"path": "compile/triggers/README.md",
"chars": 1776,
"preview": "# Compile Triggers\n\nThis plugins compiles the triggers in `serverless.yaml` to corresponding [OpenWhisk Triggers](https:"
},
{
"path": "compile/triggers/index.js",
"chars": 4262,
"preview": "'use strict';\n\nconst BbPromise = require('bluebird');\n\nclass OpenWhiskCompileTriggers {\n constructor(serverless, option"
},
{
"path": "compile/triggers/tests/index.js",
"chars": 7964,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst chaiAsPromised = require('chai-as-promised');\n\nrequire('chai"
},
{
"path": "configCredentials/index.js",
"chars": 3091,
"preview": "'use strict';\n\nconst BbPromise = require('bluebird');\nconst path = require('path');\nconst fse = require('fs-extra');\n\ncl"
},
{
"path": "configCredentials/tests/index.js",
"chars": 5543,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst sinon = require('sinon');\nconst BbPromise = require('bluebir"
},
{
"path": "deploy/README.md",
"chars": 1534,
"preview": "# Deploy\n\nThis plugin (re)deploys the service to OpenWhisk.\n\n## How it works\n\n`Deploy` starts by hooking into the\n[`depl"
},
{
"path": "deploy/index.js",
"chars": 1484,
"preview": "'use strict';\n\nconst BbPromise = require('bluebird');\nconst initializeResources = require('./lib/initializeResources');\n"
},
{
"path": "deploy/lib/deployApiGw.js",
"chars": 2125,
"preview": "'use strict';\n\nconst BbPromise = require('bluebird');\n\nmodule.exports = {\n deployRouteSwagger(route) {\n return this."
},
{
"path": "deploy/lib/deployFeeds.js",
"chars": 1320,
"preview": "'use strict';\n\nconst BbPromise = require('bluebird');\n\nmodule.exports = {\n\n deleteFeed(feed) {\n return new Promise(("
},
{
"path": "deploy/lib/deployFunctions.js",
"chars": 1517,
"preview": "'use strict';\n\nconst BbPromise = require('bluebird');\n\nmodule.exports = {\n deployFunctionHandler(functionHandler) {\n "
},
{
"path": "deploy/lib/deployPackages.js",
"chars": 1008,
"preview": "'use strict';\n\nconst BbPromise = require('bluebird');\n\nmodule.exports = {\n deployPackage(pkge) {\n return this.provid"
},
{
"path": "deploy/lib/deployRules.js",
"chars": 1275,
"preview": "'use strict';\n\nconst BbPromise = require('bluebird');\n\nmodule.exports = {\n deployRule(rule) {\n return this.provider."
},
{
"path": "deploy/lib/deployServiceBindings.js",
"chars": 2442,
"preview": "'use strict';\n\nconst BbPromise = require('bluebird');\nconst { spawn } = require('child_process');\n\nmodule.exports = {\n "
},
{
"path": "deploy/lib/deployTriggers.js",
"chars": 1445,
"preview": "'use strict';\n\nconst BbPromise = require('bluebird');\n\nmodule.exports = {\n deployTrigger(trigger) {\n return this.pro"
},
{
"path": "deploy/lib/initializeResources.js",
"chars": 879,
"preview": "'use strict';\n\n// This class ensures that all the required authentication credentials\n// are available, either from the "
},
{
"path": "deploy/lib/validate.js",
"chars": 581,
"preview": "'use strict';\n\nconst BbPromise = require('bluebird');\n\nmodule.exports = {\n validate () {\n if (!this.serverless.confi"
},
{
"path": "deploy/tests/all.js",
"chars": 294,
"preview": "'use strict';\n\nrequire('./validate');\nrequire('./initializeResources');\nrequire('./deployFunctions');\nrequire('./deployR"
},
{
"path": "deploy/tests/deployApiGw.js",
"chars": 7541,
"preview": "'use strict';\nconst expect = require('chai').expect;\nconst OpenWhiskDeploy = require('../index');\nconst sinon = require("
},
{
"path": "deploy/tests/deployFeeds.js",
"chars": 5174,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst OpenWhiskDeploy = require('../index');\nconst sinon = require"
},
{
"path": "deploy/tests/deployFunctions.js",
"chars": 3433,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst OpenWhiskDeploy = require('../index');\nconst sinon = require"
},
{
"path": "deploy/tests/deployPackages.js",
"chars": 3056,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst OpenWhiskDeploy = require('../index');\nconst sinon = require"
},
{
"path": "deploy/tests/deployRules.js",
"chars": 5092,
"preview": "'use strict';\nconst expect = require('chai').expect;\nconst OpenWhiskDeploy = require('../index');\nconst sinon = require("
},
{
"path": "deploy/tests/deployServiceBindings.js",
"chars": 2313,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst OpenWhiskDeploy = require('../index');\nconst sinon = require"
},
{
"path": "deploy/tests/deployTriggers.js",
"chars": 4947,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst OpenWhiskDeploy = require('../index');\nconst sinon = require"
},
{
"path": "deploy/tests/index.js",
"chars": 2643,
"preview": "'use strict';\n\nconst OpenWhiskDeploy = require('../index');\nconst expect = require('chai').expect;\nconst BbPromise = req"
},
{
"path": "deploy/tests/initializeResources.js",
"chars": 2383,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst sinon = require('sinon');\nconst chaiAsPromised = require('ch"
},
{
"path": "deploy/tests/resources/swagger.json",
"chars": 2337,
"preview": "{\n \"swagger\": \"2.0\",\n \"basePath\": \"/testing\",\n \"info\": {\n \"title\": \"testing\",\n \"version\": \"1.0\"\n "
},
{
"path": "deploy/tests/resources/swagger_default_ns.json",
"chars": 2289,
"preview": "{\n \"swagger\": \"2.0\",\n \"basePath\": \"/testing\",\n \"info\": {\n \"title\": \"testing\",\n \"version\": \"1.0\"\n "
},
{
"path": "deploy/tests/resources/swagger_ns_paths.json",
"chars": 2352,
"preview": "{\n \"swagger\": \"2.0\",\n \"basePath\": \"/testing\",\n \"info\": {\n \"title\": \"testing\",\n \"version\": \"1.0\"\n "
},
{
"path": "deploy/tests/resources/swagger_paths.json",
"chars": 2304,
"preview": "{\n \"swagger\": \"2.0\",\n \"basePath\": \"/testing\",\n \"info\": {\n \"title\": \"testing\",\n \"version\": \"1.0\"\n "
},
{
"path": "deploy/tests/validate.js",
"chars": 1775,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst OpenWhiskDeploy = require('../index');\n\ndescribe('#validate("
},
{
"path": "deployFunction/index.js",
"chars": 3123,
"preview": "'use strict';\n\nconst BbPromise = require('bluebird');\nconst fs = require('fs-extra');\nconst path = require('path')\nconst"
},
{
"path": "deployFunction/tests/index.js",
"chars": 5450,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst chaiAsPromised = require('chai-as-promised');\nconst sinon = "
},
{
"path": "index.js",
"chars": 2506,
"preview": "'use strict';\n\n/*\nNOTE: this plugin is used to add all the differnet provider related plugins at once.\nThis way only one"
},
{
"path": "info/index.js",
"chars": 5392,
"preview": "'use strict';\n\nconst BbPromise = require('bluebird');\nconst chalk = require('chalk');\nconst { formatApiHost } = require("
},
{
"path": "info/tests/index.js",
"chars": 13991,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst chaiAsPromised = require('chai-as-promised');\nconst sinon = "
},
{
"path": "invoke/README.md",
"chars": 313,
"preview": "# Invoke\n\nThis plugin invokes an OpenWhisk Action.\n\n## How it works\n\n`Invoke` hooks into the [`invoke:invoke`](/lib/plug"
},
{
"path": "invoke/index.js",
"chars": 6110,
"preview": "'use strict';\n\nconst BbPromise = require('bluebird');\nconst chalk = require('chalk');\nconst path = require('path');\ncons"
},
{
"path": "invoke/tests/index.js",
"chars": 11134,
"preview": "'use strict';\n\nconst chalk = require('chalk');\nconst expect = require('chai').expect;\nconst chaiAsPromised = require('ch"
},
{
"path": "invokeLocal/index.js",
"chars": 6065,
"preview": "'use strict';\n\nconst BbPromise = require('bluebird');\nconst _ = require('lodash');\nconst path = require('path');\nconst c"
},
{
"path": "invokeLocal/invoke.py",
"chars": 904,
"preview": "#!/usr/bin/env python\n\nimport argparse\nimport json\nimport sys\nfrom time import time\nfrom importlib import import_module\n"
},
{
"path": "invokeLocal/tests/fixture/handlerWithError.js",
"chars": 330,
"preview": "'use strict';\n\nmodule.exports.withObj = () => {\n return { message: 'hello' }\n};\n\nmodule.exports.withError = () => {\n t"
},
{
"path": "invokeLocal/tests/index.js",
"chars": 13971,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst sinon = require('sinon');\nconst path = require('path');\ncons"
},
{
"path": "logs/index.js",
"chars": 4848,
"preview": "'use strict';\n\nconst BbPromise = require('bluebird');\nconst chalk = require('chalk');\nconst moment = require('moment');\n"
},
{
"path": "logs/tests/index.js",
"chars": 11034,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst chaiAsPromised = require('chai-as-promised');\nconst sinon = "
},
{
"path": "package.json",
"chars": 1412,
"preview": "{\n \"name\": \"serverless-openwhisk\",\n \"version\": \"0.18.4\",\n \"description\": \"OpenWhisk support for the Serverless Framew"
},
{
"path": "provider/cliTokenManager.js",
"chars": 2459,
"preview": "'use strict';\n\n\"use strict\";\n\nconst jws = require('jws');\nconst { exec } = require('child_process');\nconst { readFileSyn"
},
{
"path": "provider/credentials.js",
"chars": 1333,
"preview": "'use strict';\n\nconst path = require('path');\nconst fs = require('fs-extra');\n\nconst ENV_PARAMS = ['OW_APIHOST', 'OW_AUTH"
},
{
"path": "provider/openwhiskProvider.js",
"chars": 2603,
"preview": "'use strict';\n\nconst BbPromise = require('bluebird');\nconst openwhisk = require('openwhisk')\nconst IamTokenManager = req"
},
{
"path": "provider/tests/cliTokenManager.js",
"chars": 5750,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst sinon = require('sinon');\nconst fs = require('fs-extra');\nco"
},
{
"path": "provider/tests/credentials.js",
"chars": 3603,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst sinon = require('sinon');\nconst fs = require('fs-extra');\nco"
},
{
"path": "provider/tests/index.js",
"chars": 103,
"preview": "'use strict';\n\nrequire('./openwhiskProvider');\nrequire('./credentials');\nrequire('./cliTokenManager');\n"
},
{
"path": "provider/tests/openwhiskProvider.js",
"chars": 7536,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst sinon = require('sinon');\nconst openwhisk = require('openwhi"
},
{
"path": "remove/README.md",
"chars": 242,
"preview": "# Remove\n\nThis plugin removes the Action from OpenWhisk.\n\n## How it works\n\n`Remove` hooks into the [`remove:remove`](/li"
},
{
"path": "remove/index.js",
"chars": 1338,
"preview": "'use strict';\n\nconst BbPromise = require('bluebird');\nconst validate = require('./lib/validate');\nconst removePackages ="
},
{
"path": "remove/lib/removeFeeds.js",
"chars": 1312,
"preview": "'use strict';\n\nconst BbPromise = require('bluebird');\n\nmodule.exports = {\n removeFeed (feed) {\n const onProvider = o"
},
{
"path": "remove/lib/removeFunctions.js",
"chars": 997,
"preview": "'use strict';\n\nconst BbPromise = require('bluebird');\n\nmodule.exports = {\n removeFunctionHandler(functionHandler) {\n "
},
{
"path": "remove/lib/removePackages.js",
"chars": 987,
"preview": "'use strict';\n\nconst BbPromise = require('bluebird');\n\nmodule.exports = {\n removePackageHandler(pkge) {\n const onPro"
},
{
"path": "remove/lib/removeRoutes.js",
"chars": 911,
"preview": "'use strict';\n\nconst BbPromise = require('bluebird');\n\nmodule.exports = {\n hasRoutes() {\n return this.serverless.ser"
},
{
"path": "remove/lib/removeRules.js",
"chars": 580,
"preview": "'use strict';\n\nconst BbPromise = require('bluebird');\nconst Util = require('./util.js');\n\nmodule.exports = {\n removeRul"
},
{
"path": "remove/lib/removeTriggers.js",
"chars": 1040,
"preview": "'use strict';\n\nconst BbPromise = require('bluebird');\n\nmodule.exports = {\n removeTriggerHandler(Trigger) {\n const on"
},
{
"path": "remove/lib/setupResources.js",
"chars": 3712,
"preview": "'use strict';\n\nconst BbPromise = require('bluebird');\n\nmodule.exports = {\n initializeTriggers () {\n if (!this.server"
},
{
"path": "remove/lib/util.js",
"chars": 325,
"preview": "'use strict';\n\nmodule.exports = {\n handleOperationFailure (onProvider, errMsgTemplate) {\n return new Promise((resol"
},
{
"path": "remove/lib/validate.js",
"chars": 582,
"preview": "'use strict';\n\nconst BbPromise = require('bluebird');\n\nmodule.exports = {\n validate() {\n if (!this.serverless.config"
},
{
"path": "remove/tests/all.js",
"chars": 231,
"preview": "'use strict';\n\nrequire('./index');\nrequire('./removePackages');\nrequire('./removeFunctions');\nrequire('./removeTriggers'"
},
{
"path": "remove/tests/index.js",
"chars": 2002,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst BbPromise = require('bluebird');\nconst sinon = require('sino"
},
{
"path": "remove/tests/removeFeeds.js",
"chars": 4564,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst sinon = require('sinon');\nconst OpenWhiskRemove = require('."
},
{
"path": "remove/tests/removeFunctions.js",
"chars": 3668,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst sinon = require('sinon');\nconst OpenWhiskRemove = require('."
},
{
"path": "remove/tests/removePackages.js",
"chars": 3531,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst sinon = require('sinon');\nconst OpenWhiskRemove = require('."
},
{
"path": "remove/tests/removeRoutes.js",
"chars": 3572,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst sinon = require('sinon');\nconst OpenWhiskRemove = require('."
},
{
"path": "remove/tests/removeRules.js",
"chars": 2737,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst sinon = require('sinon');\nconst OpenWhiskRemove = require('."
},
{
"path": "remove/tests/removeTriggers.js",
"chars": 3600,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst sinon = require('sinon');\nconst OpenWhiskRemove = require('."
},
{
"path": "remove/tests/setupResources.js",
"chars": 2870,
"preview": "'use strict';\n\nconst expect = require('chai').expect;\nconst sinon = require('sinon');\nconst OpenWhiskRemove = require('."
},
{
"path": "tests/all.js",
"chars": 767,
"preview": "// OpenWhisk Plugins Tests\nrequire('../compile/servicebindings/tests');\nrequire('../compile/packages/tests');\nrequire('."
},
{
"path": "tools/travis/build.sh",
"chars": 553,
"preview": "#!/bin/bash\nSCRIPTDIR=$(cd $(dirname \"$0\") && pwd)\nHOMEDIR=\"$SCRIPTDIR/../../../\"\n\n# Start Serverless related runs -----"
},
{
"path": "tools/travis/setup.sh",
"chars": 176,
"preview": "#!/bin/bash\nSCRIPTDIR=$(cd $(dirname \"$0\") && pwd)\nHOMEDIR=\"$SCRIPTDIR/../../../\"\n\n# install node and npm\nsudo apt-get -"
},
{
"path": "utils/index.js",
"chars": 290,
"preview": "'use strict';\n\nfunction formatApiHost(apihost) {\n if (apihost && !(apihost.startsWith('http://') || apihost.startsWith("
}
]
About this extraction
This page contains the full source code of the serverless/serverless-openwhisk GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 130 files (474.5 KB), approximately 115.7k tokens, and a symbol index with 298 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.