Skip to content
This repository has been archived by the owner on Dec 9, 2024. It is now read-only.

Commit

Permalink
Add more docs for service binding and fix bugs.
Browse files Browse the repository at this point in the history
  • Loading branch information
jthomas committed Jun 28, 2018
1 parent 7ffcbef commit f11184c
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 79 deletions.
50 changes: 49 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -604,7 +604,7 @@ resources:
hello: world
```

*Explicit packages support the following properties: `parameters`, `annotations` and `shared`.*
*Explicit packages support the following properties: `parameters`, `annotations`, `services` and `shared`.*

### Binding Packages

Expand All @@ -627,6 +627,50 @@ resources:

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
Expand All @@ -645,6 +689,10 @@ functions:
foo: bar // default parameters
annotations:
foo: bar // action annotations
bind:
- service:
name: cloud-object-storage
instance: my-cos-storage
```

## Writing Sequences
Expand Down
73 changes: 28 additions & 45 deletions compile/servicebindings/README.md
Original file line number Diff line number Diff line change
@@ -1,66 +1,49 @@
# Compile Packages
# Service Bindings

This plugins compiles the packages in `serverless.yaml` to corresponding [OpenWhisk Packages](https://github.com/openwhisk/openwhisk/blob/master/docs/packages.md)
definitions.
This plugin binds IBM Cloud platform service credentials to actions and packages in `serverless.yaml`.

## How it works

`Compile Packages` hooks into the [`package:compileEvents`](/lib/plugins/deploy) lifecycle.
`Compile Service Bindings` hooks into the [`package:compileEvents`](/lib/plugins/deploy) lifecycle.

It loops over all packages which are defined in `serverless.yaml`.
***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.***

### Implicit Packages
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.

Actions can be assigned to packages by setting the function `name` with a package reference.
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:
foo:
handler: handler.foo
name: "myPackage/foo"
bar:
handler: handler.bar
name: "myPackage/bar"
my_function:
handler: file_name.handler
bind:
- service:
name: cloud-object-storage
instance: my-cos-storage
```
In this example, two new actions (`foo` & `bar`) will be created using the `myPackage` package.
The `service` configuration supports the following properties.

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.
- `name`: identifier for the cloud service
- `instance`: instance name for service (*optional*)
- `key`: key name for instance and service (*optional*)

### Explicit Packages
*If the `instance` or `key` properties are missing, the first available instance and key found will be used.*

Packages can also be defined explicitly to set shared configuration parameters. Default package parameters are merged into event parameters for each invocation.
Binding services removes the need to manually create default parameters for service keys from platform services.

```yaml
functions:
foo:
handler: handler.foo
name: "myPackage/foo"
resources:
packages:
myPackage:
parameters:
hello: world
```
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/).

### 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.
Packages defined in the `resources` section can bind services using the same configuration properties.

```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).
myPackage:
bind:
- service:
name: cloud-object-storage
instance: my-cos-storage
```
22 changes: 16 additions & 6 deletions compile/servicebindings/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,10 @@ class OpenWhiskCompileServiceBindings {
return props.name || `${this.serverless.service.service}_${name}`;
}

parseServiceBindings(action, properties) {
const name = { action: this.calculateFunctionName(action, properties) }
parseServiceBindings(name, properties) {
const bindings = properties.bind || []
const servicebindings = bindings.filter(b => b.service)
.map(b => Object.assign(b.service, name))
.map(b => Object.assign(b.service, { action: name } ))

const serviceNames = new Set()

Expand All @@ -42,14 +41,21 @@ class OpenWhiskCompileServiceBindings {

compileFnServiceBindings() {
return this.serverless.service.getAllFunctions()
.map(name => this.parseServiceBindings(name, this.serverless.service.getFunction(name)))
.map(name => {
const fnObj = this.serverless.service.getFunction(name)
const fnName = this.calculateFunctionName(name, fnObj)
return this.parseServiceBindings(fnName, fnObj)
})
.filter(sbs => sbs.length > 0)
}

compilePkgServiceBindings() {
const manifestResources = this.serverless.service.resources || {}
const packages = manifestResources.packages || {}

return Object.keys(packages).map(name => this.parseServiceBindings(name, packages[name]))
return Object.keys(packages)
.map(name => this.parseServiceBindings(name, packages[name]))
.filter(sbs => sbs.length > 0)
}

compileServiceBindings() {
Expand All @@ -58,7 +64,11 @@ class OpenWhiskCompileServiceBindings {
const fnServiceBindings = this.compileFnServiceBindings()
const pkgServiceBindings = this.compilePkgServiceBindings()

this.serverless.service.bindings = [].concat(...pkgServiceBindings, ...fnServiceBindings)
this.serverless.service.bindings = {
fns: fnServiceBindings,
packages: pkgServiceBindings
}

return BbPromise.resolve();
}
}
Expand Down
35 changes: 18 additions & 17 deletions compile/servicebindings/tests/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ describe('OpenWhiskCompileServiceBindings', () => {
it('should return array with single service binding property', () => {
const action = 'fnName'
const service = { name: 'my-service', instance: 'my-instance', key: 'mykey' }
const response = { action: `serviceName_fnName`, name: 'my-service', instance: 'my-instance', key: 'mykey' }
const response = { action: `fnName`, name: 'my-service', instance: 'my-instance', key: 'mykey' }
const result = openwhiskCompileServiceBindings.parseServiceBindings(action, {bind: [{ service }]})
expect(result).to.deep.equal([response])
})
Expand Down Expand Up @@ -93,13 +93,13 @@ describe('OpenWhiskCompileServiceBindings', () => {
service.getFunction = name => fns[name]

const services = [
{ action: 'serviceName_a', name: 'service-name-a' },
{ action: 'serviceName_b', name: 'service-name-b', instance: 'instance-name' },
{ action: 'serviceName_c', name: 'service-name-a' },
{ action: 'serviceName_c', name: 'service-name-b' }
[{ action: 'serviceName_a', name: 'service-name-a' }],
[{ action: 'serviceName_b', name: 'service-name-b', instance: 'instance-name' }],
[{ action: 'serviceName_c', name: 'service-name-a' }, { action: 'serviceName_c', name: 'service-name-b' }]
]
return openwhiskCompileServiceBindings.compileServiceBindings().then(result => {
expect(service.bindings).to.deep.equal(services)
expect(service.bindings.fns).to.deep.equal(services)
expect(service.bindings.packages).to.deep.equal([])
})
})

Expand All @@ -112,9 +112,10 @@ describe('OpenWhiskCompileServiceBindings', () => {
service.getAllFunctions = () => Object.keys(fns)
service.getFunction = name => fns[name]

const services = [ { action: 'some_name', name: 'service-name-a' } ]
const services = [ [{ action: 'some_name', name: 'service-name-a' }] ]
return openwhiskCompileServiceBindings.compileServiceBindings().then(result => {
expect(service.bindings).to.deep.equal(services)
expect(service.bindings.fns).to.deep.equal(services)
expect(service.bindings.packages).to.deep.equal([])
})
})

Expand All @@ -129,14 +130,14 @@ describe('OpenWhiskCompileServiceBindings', () => {
}

const services = [
{ action: 'serviceName_a', name: 'service-name-a' },
{ action: 'serviceName_b', name: 'service-name-b', instance: 'instance-name' },
{ action: 'serviceName_c', name: 'service-name-a' },
{ action: 'serviceName_c', name: 'service-name-b' }
[{ action: 'a', name: 'service-name-a' }],
[{ action: 'b', name: 'service-name-b', instance: 'instance-name' }],
[{ action: 'c', name: 'service-name-a' }, { action: 'c', name: 'service-name-b' }]
]

return openwhiskCompileServiceBindings.compileServiceBindings().then(() => {
expect(service.bindings).to.deep.equal(services);
expect(service.bindings.packages).to.deep.equal(services);
expect(service.bindings.fns).to.deep.equal([]);
});
});

Expand All @@ -153,10 +154,10 @@ describe('OpenWhiskCompileServiceBindings', () => {
service.getAllFunctions = () => Object.keys(fns)
service.getFunction = name => fns[name]

const services = [
{ action: 'serviceName_a', name: 'service-name-a' },
{ action: 'serviceName_b', name: 'service-name-b', instance: 'instance-name' }
]
const services = {
packages: [[{ action: 'a', name: 'service-name-a' }]],
fns: [[{ action: 'serviceName_b', name: 'service-name-b', instance: 'instance-name' }]]
}

return openwhiskCompileServiceBindings.compileServiceBindings().then(() => {
expect(service.bindings).to.deep.equal(services);
Expand Down
14 changes: 8 additions & 6 deletions deploy/lib/deployServiceBindings.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const { spawn } = require('child_process');
module.exports = {
configureServiceBinding(binding) {
if (this.options.verbose) {
this.serverless.cli.log(`Configuring Service Binding: ${binding}`);
this.serverless.cli.log(`Configuring Service Binding: ${JSON.stringify(binding)}`);
}

return new Promise((resolve, reject) => {
Expand Down Expand Up @@ -56,7 +56,7 @@ module.exports = {
return reject(err)
}
if (this.options.verbose) {
this.serverless.cli.log(`Configured Service Binding: ${binding}`);
this.serverless.cli.log(`Configured Service Binding: ${JSON.stringify(binding)}`);
}
resolve()
});
Expand All @@ -66,16 +66,18 @@ module.exports = {
configureServiceBindings() {
const bindings = this.getServiceBindings();

if (bindings.length) {
if (bindings.fns.length || bindings.packages.length) {
this.serverless.cli.log('Configuring Service Bindings...');
}

return BbPromise.all(
bindings.map(sb => this.configureServiceBinding(sb))
);
bindings.packages.map(sbs => BbPromise.mapSeries(sbs, sb => this.configureServiceBinding(sb)))
).then(() => BbPromise.all(
bindings.fns.map(sbs => BbPromise.mapSeries(sbs, sb => this.configureServiceBinding(sb)))
));
},

getServiceBindings() {
return this.serverless.service.bindings || [];
return this.serverless.service.bindings || { fns: [], packages: [] } ;
}
};
8 changes: 4 additions & 4 deletions deploy/tests/deployServiceBindings.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ describe('deployServiceBindings', () => {

describe('#configureServiceBindings()', () => {
it('should call binding command for each binding and return when all finish', () => {
const bindings = [{name: 'a'}, {name: 'a'}, {name: 'a'}]
openwhiskDeploy.serverless.service.bindings = bindings
const bindings = [[{name: 'a'}, {name: 'a'}, {name: 'a'}]]
openwhiskDeploy.serverless.service.bindings = { fns: bindings, packages: bindings }
sandbox.stub(openwhiskDeploy, 'configureServiceBinding', () => {
return Promise.resolve();
});
Expand All @@ -56,8 +56,8 @@ describe('deployServiceBindings', () => {
});

it('should reject when function handler fails to deploy with error message', () => {
const bindings = [{name: 'a'}, {name: 'a'}, {name: 'a'}]
openwhiskDeploy.serverless.service.bindings = bindings
const bindings = [[{name: 'a'}, {name: 'a'}, {name: 'a'}]]
openwhiskDeploy.serverless.service.bindings = { fns: bindings, packages: bindings }
const err = { message: 'some reason' };
sandbox.stub(openwhiskDeploy, 'configureServiceBinding', () => {
return Promise.reject(err);
Expand Down

0 comments on commit f11184c

Please sign in to comment.