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

Commit 7ffcbef

Browse files
committed
Add new service binding command to manifest.
1 parent 13ac5e4 commit 7ffcbef

File tree

9 files changed

+458
-1
lines changed

9 files changed

+458
-1
lines changed

compile/servicebindings/README.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Compile Packages
2+
3+
This plugins compiles the packages in `serverless.yaml` to corresponding [OpenWhisk Packages](https://github.com/openwhisk/openwhisk/blob/master/docs/packages.md)
4+
definitions.
5+
6+
## How it works
7+
8+
`Compile Packages` hooks into the [`package:compileEvents`](/lib/plugins/deploy) lifecycle.
9+
10+
It loops over all packages which are defined in `serverless.yaml`.
11+
12+
### Implicit Packages
13+
14+
Actions can be assigned to packages by setting the function `name` with a package reference.
15+
16+
```yaml
17+
functions:
18+
foo:
19+
handler: handler.foo
20+
name: "myPackage/foo"
21+
bar:
22+
handler: handler.bar
23+
name: "myPackage/bar"
24+
```
25+
26+
In this example, two new actions (`foo` & `bar`) will be created using the `myPackage` package.
27+
28+
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.
29+
30+
### Explicit Packages
31+
32+
Packages can also be defined explicitly to set shared configuration parameters. Default package parameters are merged into event parameters for each invocation.
33+
34+
```yaml
35+
functions:
36+
foo:
37+
handler: handler.foo
38+
name: "myPackage/foo"
39+
40+
resources:
41+
packages:
42+
myPackage:
43+
parameters:
44+
hello: world
45+
```
46+
47+
### Binding Packages
48+
49+
OpenWhisk also supports "binding" external packages into your workspace. Bound packages can have default parameters set for shared actions.
50+
51+
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.
52+
53+
Define packages explicitly with a `binding` parameter to use this behaviour.
54+
55+
```yaml
56+
resources:
57+
packages:
58+
mySamples:
59+
binding: /whisk.system/cloudant
60+
parameters:
61+
username: bernie
62+
password: sanders
63+
dbname: vermont
64+
```
65+
66+
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).

compile/servicebindings/index.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
'use strict';
2+
3+
const BbPromise = require('bluebird');
4+
5+
class OpenWhiskCompileServiceBindings {
6+
constructor(serverless, options) {
7+
this.serverless = serverless;
8+
this.options = options;
9+
this.provider = this.serverless.getProvider('openwhisk');
10+
11+
this.hooks = {
12+
'package:compileEvents': this.compileServiceBindings.bind(this)
13+
};
14+
}
15+
16+
calculateFunctionName(name, props) {
17+
return props.name || `${this.serverless.service.service}_${name}`;
18+
}
19+
20+
parseServiceBindings(action, properties) {
21+
const name = { action: this.calculateFunctionName(action, properties) }
22+
const bindings = properties.bind || []
23+
const servicebindings = bindings.filter(b => b.service)
24+
.map(b => Object.assign(b.service, name))
25+
26+
const serviceNames = new Set()
27+
28+
for (let sb of servicebindings) {
29+
if (!sb.hasOwnProperty('name')) {
30+
throw new Error(`service binding missing name parameter: ${JSON.stringify(sb)}`)
31+
}
32+
33+
if (serviceNames.has(sb.name)) {
34+
throw new Error(`multiple bindings for same service not supported: ${sb.name}`)
35+
}
36+
37+
serviceNames.add(sb.name)
38+
}
39+
40+
return servicebindings
41+
}
42+
43+
compileFnServiceBindings() {
44+
return this.serverless.service.getAllFunctions()
45+
.map(name => this.parseServiceBindings(name, this.serverless.service.getFunction(name)))
46+
}
47+
48+
compilePkgServiceBindings() {
49+
const manifestResources = this.serverless.service.resources || {}
50+
const packages = manifestResources.packages || {}
51+
52+
return Object.keys(packages).map(name => this.parseServiceBindings(name, packages[name]))
53+
}
54+
55+
compileServiceBindings() {
56+
this.serverless.cli.log('Compiling Service Bindings...');
57+
58+
const fnServiceBindings = this.compileFnServiceBindings()
59+
const pkgServiceBindings = this.compilePkgServiceBindings()
60+
61+
this.serverless.service.bindings = [].concat(...pkgServiceBindings, ...fnServiceBindings)
62+
return BbPromise.resolve();
63+
}
64+
}
65+
66+
module.exports = OpenWhiskCompileServiceBindings;
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
'use strict';
2+
3+
const expect = require('chai').expect;
4+
const chaiAsPromised = require('chai-as-promised');
5+
6+
require('chai').use(chaiAsPromised);
7+
8+
const sinon = require('sinon');
9+
const OpenWhiskCompileServiceBindings = require('../index');
10+
11+
describe('OpenWhiskCompileServiceBindings', () => {
12+
let serverless;
13+
let sandbox;
14+
let openwhiskCompileServiceBindings;
15+
16+
beforeEach(() => {
17+
sandbox = sinon.sandbox.create();
18+
serverless = {classes: {Error}, service: {provider: {}, resources: {}, getAllFunctions: () => []}, getProvider: sandbox.spy()};
19+
const options = {
20+
stage: 'dev',
21+
region: 'us-east-1',
22+
};
23+
openwhiskCompileServiceBindings = new OpenWhiskCompileServiceBindings(serverless, options);
24+
serverless.service.service = 'serviceName';
25+
serverless.service.provider = {
26+
namespace: 'testing',
27+
apihost: '',
28+
auth: '',
29+
};
30+
31+
serverless.cli = { consoleLog: () => {}, log: () => {} };
32+
});
33+
34+
afterEach(() => {
35+
sandbox.restore();
36+
});
37+
38+
describe('#parseServiceBindings()', () => {
39+
it('should return empty array when missing service bindings', () => {
40+
const action = 'fnName'
41+
expect(openwhiskCompileServiceBindings.parseServiceBindings(action, {})).to.deep.equal([])
42+
expect(openwhiskCompileServiceBindings.parseServiceBindings(action, {bind: []})).to.deep.equal([])
43+
expect(openwhiskCompileServiceBindings.parseServiceBindings(action, {bind: [{}]})).to.deep.equal([])
44+
expect(openwhiskCompileServiceBindings.parseServiceBindings(action, {bind: [{blah: {}}]})).to.deep.equal([])
45+
})
46+
47+
it('should return array with single service binding property', () => {
48+
const action = 'fnName'
49+
const service = { name: 'my-service', instance: 'my-instance', key: 'mykey' }
50+
const response = { action: `serviceName_fnName`, name: 'my-service', instance: 'my-instance', key: 'mykey' }
51+
const result = openwhiskCompileServiceBindings.parseServiceBindings(action, {bind: [{ service }]})
52+
expect(result).to.deep.equal([response])
53+
})
54+
55+
it('should return array with multiple service binding properties', () => {
56+
const action = 'fnName'
57+
const service_a = { action: `serviceName_fnName`, name: 'my-service-a', instance: 'my-instance-a', key: 'mykey' }
58+
const service_b = { action: `serviceName_fnName`, name: 'my-service-b', instance: 'my-instance-b', key: 'mykey' }
59+
const service_c = { action: `serviceName_fnName`, name: 'my-service-c', instance: 'my-instance-c', key: 'mykey' }
60+
const services = [{ service: service_a }, { service: service_b }, { service: service_c } ]
61+
const result = openwhiskCompileServiceBindings.parseServiceBindings(action, {bind: services})
62+
expect(result).to.deep.equal([service_a, service_b, service_c])
63+
})
64+
65+
it('should throw an error if service binding is missing name', () => {
66+
const service = { instance: 'my-instance-a', key: 'mykey' }
67+
const action = 'fnName'
68+
const services = [{ service }]
69+
expect(() => openwhiskCompileServiceBindings.parseServiceBindings(action, {bind: services}))
70+
.to.throw(Error, /service binding missing name parameter/);
71+
});
72+
73+
it('should throw an error if multiple bindings for same service name', () => {
74+
const action = 'fnName'
75+
const service = { name: 'my-service', instance: 'my-instance-a', key: 'mykey' }
76+
const services = [{ service }, { service }]
77+
expect(() => openwhiskCompileServiceBindings.parseServiceBindings(action, {bind: services}))
78+
.to.throw(Error, /multiple bindings for same service not supported/);
79+
});
80+
})
81+
82+
describe('#compileServiceBindings()', () => {
83+
it('should return service bindings for simple functions', () => {
84+
const fns = {
85+
a: { bind: [{ service: { name: 'service-name-a' } }] },
86+
b: { bind: [{ service: { name: 'service-name-b', instance: 'instance-name' } }] },
87+
c: { bind: [{ service: { name: 'service-name-a' } }, { service: { name: 'service-name-b' } }] },
88+
d: { },
89+
}
90+
91+
const service = openwhiskCompileServiceBindings.serverless.service
92+
service.getAllFunctions = () => Object.keys(fns)
93+
service.getFunction = name => fns[name]
94+
95+
const services = [
96+
{ action: 'serviceName_a', name: 'service-name-a' },
97+
{ action: 'serviceName_b', name: 'service-name-b', instance: 'instance-name' },
98+
{ action: 'serviceName_c', name: 'service-name-a' },
99+
{ action: 'serviceName_c', name: 'service-name-b' }
100+
]
101+
return openwhiskCompileServiceBindings.compileServiceBindings().then(result => {
102+
expect(service.bindings).to.deep.equal(services)
103+
})
104+
})
105+
106+
it('should return service bindings for functions with explicit name', () => {
107+
const fns = {
108+
a: { name: 'some_name', bind: [{ service: { name: 'service-name-a' } }] }
109+
}
110+
111+
const service = openwhiskCompileServiceBindings.serverless.service
112+
service.getAllFunctions = () => Object.keys(fns)
113+
service.getFunction = name => fns[name]
114+
115+
const services = [ { action: 'some_name', name: 'service-name-a' } ]
116+
return openwhiskCompileServiceBindings.compileServiceBindings().then(result => {
117+
expect(service.bindings).to.deep.equal(services)
118+
})
119+
})
120+
121+
122+
it('should return service bindings for packages', () => {
123+
const service = openwhiskCompileServiceBindings.serverless.service
124+
service.resources.packages = {
125+
a: { bind: [{ service: { name: 'service-name-a' } }] },
126+
b: { bind: [{ service: { name: 'service-name-b', instance: 'instance-name' } }] },
127+
c: { bind: [{ service: { name: 'service-name-a' } }, { service: { name: 'service-name-b' } }] },
128+
d: { },
129+
}
130+
131+
const services = [
132+
{ action: 'serviceName_a', name: 'service-name-a' },
133+
{ action: 'serviceName_b', name: 'service-name-b', instance: 'instance-name' },
134+
{ action: 'serviceName_c', name: 'service-name-a' },
135+
{ action: 'serviceName_c', name: 'service-name-b' }
136+
]
137+
138+
return openwhiskCompileServiceBindings.compileServiceBindings().then(() => {
139+
expect(service.bindings).to.deep.equal(services);
140+
});
141+
});
142+
143+
it('should return service bindings for functions & packages', () => {
144+
const service = openwhiskCompileServiceBindings.serverless.service;
145+
service.resources.packages = {
146+
a: { bind: [{ service: { name: 'service-name-a' } }] }
147+
};
148+
149+
const fns = {
150+
b: { bind: [{ service: { name: 'service-name-b', instance: 'instance-name' } }] },
151+
}
152+
153+
service.getAllFunctions = () => Object.keys(fns)
154+
service.getFunction = name => fns[name]
155+
156+
const services = [
157+
{ action: 'serviceName_a', name: 'service-name-a' },
158+
{ action: 'serviceName_b', name: 'service-name-b', instance: 'instance-name' }
159+
]
160+
161+
return openwhiskCompileServiceBindings.compileServiceBindings().then(() => {
162+
expect(service.bindings).to.deep.equal(services);
163+
});
164+
165+
})
166+
})
167+
});

deploy/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const deployRules = require('./lib/deployRules');
88
const deployTriggers = require('./lib/deployTriggers');
99
const deployFeeds = require('./lib/deployFeeds');
1010
const deployApiGw = require('./lib/deployApiGw');
11+
const deployServiceBindings = require('./lib/deployServiceBindings');
1112

1213
class OpenWhiskDeploy {
1314
constructor(serverless, options) {
@@ -23,7 +24,8 @@ class OpenWhiskDeploy {
2324
deployApiGw,
2425
deployRules,
2526
deployTriggers,
26-
deployFeeds
27+
deployFeeds,
28+
deployServiceBindings
2729
);
2830

2931
this.hooks = {
@@ -37,6 +39,7 @@ class OpenWhiskDeploy {
3739
.then(this.deployTriggers)
3840
.then(this.deployFeeds)
3941
.then(this.deployRules)
42+
.then(this.configureServiceBindings)
4043
.then(() => this.serverless.cli.log('Deployment successful!')),
4144
};
4245
}

0 commit comments

Comments
 (0)