This repository has been archived by the owner on Dec 9, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 46
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add new
service binding
command to manifest.
- Loading branch information
Showing
9 changed files
with
458 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
# Compile Packages | ||
|
||
This plugins compiles the packages in `serverless.yaml` to corresponding [OpenWhisk Packages](https://github.com/openwhisk/openwhisk/blob/master/docs/packages.md) | ||
definitions. | ||
|
||
## How it works | ||
|
||
`Compile Packages` hooks into the [`package:compileEvents`](/lib/plugins/deploy) lifecycle. | ||
|
||
It loops over all packages which are defined in `serverless.yaml`. | ||
|
||
### 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: | ||
parameters: | ||
hello: world | ||
``` | ||
|
||
### 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). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
'use strict'; | ||
|
||
const BbPromise = require('bluebird'); | ||
|
||
class OpenWhiskCompileServiceBindings { | ||
constructor(serverless, options) { | ||
this.serverless = serverless; | ||
this.options = options; | ||
this.provider = this.serverless.getProvider('openwhisk'); | ||
|
||
this.hooks = { | ||
'package:compileEvents': this.compileServiceBindings.bind(this) | ||
}; | ||
} | ||
|
||
calculateFunctionName(name, props) { | ||
return props.name || `${this.serverless.service.service}_${name}`; | ||
} | ||
|
||
parseServiceBindings(action, properties) { | ||
const name = { action: this.calculateFunctionName(action, properties) } | ||
const bindings = properties.bind || [] | ||
const servicebindings = bindings.filter(b => b.service) | ||
.map(b => Object.assign(b.service, name)) | ||
|
||
const serviceNames = new Set() | ||
|
||
for (let sb of servicebindings) { | ||
if (!sb.hasOwnProperty('name')) { | ||
throw new Error(`service binding missing name parameter: ${JSON.stringify(sb)}`) | ||
} | ||
|
||
if (serviceNames.has(sb.name)) { | ||
throw new Error(`multiple bindings for same service not supported: ${sb.name}`) | ||
} | ||
|
||
serviceNames.add(sb.name) | ||
} | ||
|
||
return servicebindings | ||
} | ||
|
||
compileFnServiceBindings() { | ||
return this.serverless.service.getAllFunctions() | ||
.map(name => this.parseServiceBindings(name, this.serverless.service.getFunction(name))) | ||
} | ||
|
||
compilePkgServiceBindings() { | ||
const manifestResources = this.serverless.service.resources || {} | ||
const packages = manifestResources.packages || {} | ||
|
||
return Object.keys(packages).map(name => this.parseServiceBindings(name, packages[name])) | ||
} | ||
|
||
compileServiceBindings() { | ||
this.serverless.cli.log('Compiling Service Bindings...'); | ||
|
||
const fnServiceBindings = this.compileFnServiceBindings() | ||
const pkgServiceBindings = this.compilePkgServiceBindings() | ||
|
||
this.serverless.service.bindings = [].concat(...pkgServiceBindings, ...fnServiceBindings) | ||
return BbPromise.resolve(); | ||
} | ||
} | ||
|
||
module.exports = OpenWhiskCompileServiceBindings; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
'use strict'; | ||
|
||
const expect = require('chai').expect; | ||
const chaiAsPromised = require('chai-as-promised'); | ||
|
||
require('chai').use(chaiAsPromised); | ||
|
||
const sinon = require('sinon'); | ||
const OpenWhiskCompileServiceBindings = require('../index'); | ||
|
||
describe('OpenWhiskCompileServiceBindings', () => { | ||
let serverless; | ||
let sandbox; | ||
let openwhiskCompileServiceBindings; | ||
|
||
beforeEach(() => { | ||
sandbox = sinon.sandbox.create(); | ||
serverless = {classes: {Error}, service: {provider: {}, resources: {}, getAllFunctions: () => []}, getProvider: sandbox.spy()}; | ||
const options = { | ||
stage: 'dev', | ||
region: 'us-east-1', | ||
}; | ||
openwhiskCompileServiceBindings = new OpenWhiskCompileServiceBindings(serverless, options); | ||
serverless.service.service = 'serviceName'; | ||
serverless.service.provider = { | ||
namespace: 'testing', | ||
apihost: '', | ||
auth: '', | ||
}; | ||
|
||
serverless.cli = { consoleLog: () => {}, log: () => {} }; | ||
}); | ||
|
||
afterEach(() => { | ||
sandbox.restore(); | ||
}); | ||
|
||
describe('#parseServiceBindings()', () => { | ||
it('should return empty array when missing service bindings', () => { | ||
const action = 'fnName' | ||
expect(openwhiskCompileServiceBindings.parseServiceBindings(action, {})).to.deep.equal([]) | ||
expect(openwhiskCompileServiceBindings.parseServiceBindings(action, {bind: []})).to.deep.equal([]) | ||
expect(openwhiskCompileServiceBindings.parseServiceBindings(action, {bind: [{}]})).to.deep.equal([]) | ||
expect(openwhiskCompileServiceBindings.parseServiceBindings(action, {bind: [{blah: {}}]})).to.deep.equal([]) | ||
}) | ||
|
||
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 result = openwhiskCompileServiceBindings.parseServiceBindings(action, {bind: [{ service }]}) | ||
expect(result).to.deep.equal([response]) | ||
}) | ||
|
||
it('should return array with multiple service binding properties', () => { | ||
const action = 'fnName' | ||
const service_a = { action: `serviceName_fnName`, name: 'my-service-a', instance: 'my-instance-a', key: 'mykey' } | ||
const service_b = { action: `serviceName_fnName`, name: 'my-service-b', instance: 'my-instance-b', key: 'mykey' } | ||
const service_c = { action: `serviceName_fnName`, name: 'my-service-c', instance: 'my-instance-c', key: 'mykey' } | ||
const services = [{ service: service_a }, { service: service_b }, { service: service_c } ] | ||
const result = openwhiskCompileServiceBindings.parseServiceBindings(action, {bind: services}) | ||
expect(result).to.deep.equal([service_a, service_b, service_c]) | ||
}) | ||
|
||
it('should throw an error if service binding is missing name', () => { | ||
const service = { instance: 'my-instance-a', key: 'mykey' } | ||
const action = 'fnName' | ||
const services = [{ service }] | ||
expect(() => openwhiskCompileServiceBindings.parseServiceBindings(action, {bind: services})) | ||
.to.throw(Error, /service binding missing name parameter/); | ||
}); | ||
|
||
it('should throw an error if multiple bindings for same service name', () => { | ||
const action = 'fnName' | ||
const service = { name: 'my-service', instance: 'my-instance-a', key: 'mykey' } | ||
const services = [{ service }, { service }] | ||
expect(() => openwhiskCompileServiceBindings.parseServiceBindings(action, {bind: services})) | ||
.to.throw(Error, /multiple bindings for same service not supported/); | ||
}); | ||
}) | ||
|
||
describe('#compileServiceBindings()', () => { | ||
it('should return service bindings for simple functions', () => { | ||
const fns = { | ||
a: { bind: [{ service: { name: 'service-name-a' } }] }, | ||
b: { bind: [{ service: { name: 'service-name-b', instance: 'instance-name' } }] }, | ||
c: { bind: [{ service: { name: 'service-name-a' } }, { service: { name: 'service-name-b' } }] }, | ||
d: { }, | ||
} | ||
|
||
const service = openwhiskCompileServiceBindings.serverless.service | ||
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' }, | ||
{ 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) | ||
}) | ||
}) | ||
|
||
it('should return service bindings for functions with explicit name', () => { | ||
const fns = { | ||
a: { name: 'some_name', bind: [{ service: { name: 'service-name-a' } }] } | ||
} | ||
|
||
const service = openwhiskCompileServiceBindings.serverless.service | ||
service.getAllFunctions = () => Object.keys(fns) | ||
service.getFunction = name => fns[name] | ||
|
||
const services = [ { action: 'some_name', name: 'service-name-a' } ] | ||
return openwhiskCompileServiceBindings.compileServiceBindings().then(result => { | ||
expect(service.bindings).to.deep.equal(services) | ||
}) | ||
}) | ||
|
||
|
||
it('should return service bindings for packages', () => { | ||
const service = openwhiskCompileServiceBindings.serverless.service | ||
service.resources.packages = { | ||
a: { bind: [{ service: { name: 'service-name-a' } }] }, | ||
b: { bind: [{ service: { name: 'service-name-b', instance: 'instance-name' } }] }, | ||
c: { bind: [{ service: { name: 'service-name-a' } }, { service: { name: 'service-name-b' } }] }, | ||
d: { }, | ||
} | ||
|
||
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' } | ||
] | ||
|
||
return openwhiskCompileServiceBindings.compileServiceBindings().then(() => { | ||
expect(service.bindings).to.deep.equal(services); | ||
}); | ||
}); | ||
|
||
it('should return service bindings for functions & packages', () => { | ||
const service = openwhiskCompileServiceBindings.serverless.service; | ||
service.resources.packages = { | ||
a: { bind: [{ service: { name: 'service-name-a' } }] } | ||
}; | ||
|
||
const fns = { | ||
b: { bind: [{ service: { name: 'service-name-b', instance: 'instance-name' } }] }, | ||
} | ||
|
||
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' } | ||
] | ||
|
||
return openwhiskCompileServiceBindings.compileServiceBindings().then(() => { | ||
expect(service.bindings).to.deep.equal(services); | ||
}); | ||
|
||
}) | ||
}) | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.