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

Commit 9463471

Browse files
authored
Merge pull request #54 from andresmgot/chain
Add support for chaining functions
2 parents afc8c28 + c08b6d3 commit 9463471

File tree

8 files changed

+299
-98
lines changed

8 files changed

+299
-98
lines changed

deploy/kubelessDeploy.js

Lines changed: 87 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -369,92 +369,98 @@ class KubelessDeploy {
369369
return new BbPromise((resolve, reject) => {
370370
_.each(this.serverless.service.functions, (description, name) => {
371371
const runtime = this.serverless.service.provider.runtime;
372-
const files = this.getRuntimeFilenames(runtime, description.handler);
373-
const connectionOptions = helpers.getConnectionOptions(
374-
helpers.loadKubeConfig(), {
375-
namespace: description.namespace ||
376-
this.serverless.service.provider.namespace,
377-
}
378-
);
379-
const thirdPartyResources = this.getThirdPartyResources(connectionOptions);
380-
thirdPartyResources.addResource('functions');
381-
this.getFunctionContent(files.handler)
382-
.then(functionContent => {
383-
this.getFunctionContent(files.deps)
384-
.catch(() => {
385-
// No requirements found
386-
})
387-
.then((requirementsContent) => {
388-
const events = !_.isEmpty(description.events) ?
389-
description.events :
390-
[{ http: { path: '/' } }];
391-
_.each(events, event => {
392-
const eventType = _.keys(event)[0];
393-
const funcs = getFunctionDescription(
394-
name,
395-
thirdPartyResources.namespaces.namespace,
396-
this.serverless.service.provider.runtime,
397-
requirementsContent,
398-
functionContent,
399-
description.handler,
400-
description.description,
401-
description.labels,
402-
description.environment,
403-
description.memorySize || this.serverless.service.provider.memorySize,
404-
eventType,
405-
event.trigger
406-
);
407-
let deploymentPromise = null;
408-
thirdPartyResources.ns.functions.get((err, functionsInfo) => {
409-
if (err) throw err;
410-
const existingFunction = _.find(functionsInfo.items, item => (
411-
_.isEqual(item.spec, funcs.spec)
412-
));
413-
if (existingFunction) {
414-
// The same function is already deployed, skipping the deployment
415-
this.serverless.cli.log(
416-
`Function ${name} has not changed. Skipping deployment`
417-
);
418-
deploymentPromise = new BbPromise(r => r(false));
419-
} else {
420-
deploymentPromise = this.deployFunctionAndWait(funcs, thirdPartyResources);
421-
}
422-
deploymentPromise.catch(deploymentErr => {
423-
errors.push(deploymentErr);
424-
})
425-
.then((deployed) => {
426-
if (!deployed) {
427-
// If there were an error with the deployment
428-
// don't try to add an ingress rule
429-
return new BbPromise((r) => r());
430-
}
431-
return this.addIngressRuleIfNecessary(
432-
name,
433-
eventType,
434-
event[eventType].path,
435-
connectionOptions.namespace
372+
if (description.handler) {
373+
const files = this.getRuntimeFilenames(runtime, description.handler);
374+
const connectionOptions = helpers.getConnectionOptions(
375+
helpers.loadKubeConfig(), {
376+
namespace: description.namespace ||
377+
this.serverless.service.provider.namespace,
378+
}
379+
);
380+
const thirdPartyResources = this.getThirdPartyResources(connectionOptions);
381+
thirdPartyResources.addResource('functions');
382+
this.getFunctionContent(files.handler)
383+
.then(functionContent => {
384+
this.getFunctionContent(files.deps)
385+
.catch(() => {
386+
// No requirements found
387+
})
388+
.then((requirementsContent) => {
389+
const events = !_.isEmpty(description.events) ?
390+
description.events :
391+
[{ http: { path: '/' } }];
392+
_.each(events, event => {
393+
const eventType = _.keys(event)[0];
394+
const funcs = getFunctionDescription(
395+
name,
396+
thirdPartyResources.namespaces.namespace,
397+
this.serverless.service.provider.runtime,
398+
requirementsContent,
399+
functionContent,
400+
description.handler,
401+
description.description,
402+
description.labels,
403+
description.environment,
404+
description.memorySize || this.serverless.service.provider.memorySize,
405+
eventType,
406+
event.trigger
407+
);
408+
let deploymentPromise = null;
409+
thirdPartyResources.ns.functions.get((err, functionsInfo) => {
410+
if (err) throw err;
411+
const existingFunction = _.find(functionsInfo.items, item => (
412+
_.isEqual(item.spec, funcs.spec)
413+
));
414+
if (existingFunction) {
415+
// The same function is already deployed, skipping the deployment
416+
this.serverless.cli.log(
417+
`Function ${name} has not changed. Skipping deployment`
436418
);
419+
deploymentPromise = new BbPromise(r => r(false));
420+
} else {
421+
deploymentPromise = this.deployFunctionAndWait(funcs, thirdPartyResources);
422+
}
423+
deploymentPromise.catch(deploymentErr => {
424+
errors.push(deploymentErr);
437425
})
438-
.catch(ingressErr => {
439-
errors.push(ingressErr);
440-
})
441-
.then(() => {
442-
counter++;
443-
if (counter === _.keys(this.serverless.service.functions).length) {
444-
if (_.isEmpty(errors)) {
445-
resolve();
446-
} else {
447-
reject(
448-
'Found errors while deploying the given functions:\n' +
449-
`${errors.join('\n')}`
450-
);
426+
.then((deployed) => {
427+
if (!deployed) {
428+
// If there were an error with the deployment
429+
// don't try to add an ingress rule
430+
return new BbPromise((r) => r());
451431
}
452-
}
453-
});
432+
return this.addIngressRuleIfNecessary(
433+
name,
434+
eventType,
435+
event[eventType].path,
436+
connectionOptions.namespace
437+
);
438+
})
439+
.catch(ingressErr => {
440+
errors.push(ingressErr);
441+
})
442+
.then(() => {
443+
counter++;
444+
if (counter === _.keys(this.serverless.service.functions).length) {
445+
if (_.isEmpty(errors)) {
446+
resolve();
447+
} else {
448+
reject(
449+
'Found errors while deploying the given functions:\n' +
450+
`${errors.join('\n')}`
451+
);
452+
}
453+
}
454+
});
455+
});
454456
});
455457
});
456-
});
457-
});
458+
});
459+
} else {
460+
this.serverless.cli.log(
461+
`Skipping deployment of ${name} since it doesn't have a handler`
462+
);
463+
}
458464
});
459465
});
460466
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Chaining Example
2+
3+
This example concatenates three different functions (capitalize, pad and reverse) and return its result.
4+
5+
```console
6+
$ npm install
7+
$ serverless deploy
8+
$ serverless invoke -f chained_seq -l --data 'hello world!'
9+
Serverless: Calling function: chained_seq...
10+
Serverless: Calling function: capitalize...
11+
Serverless: Calling function: pad...
12+
Serverless: Calling function: reverse...
13+
--------------------------------------------------------------------
14+
****!dlrow olleH****
15+
$ serverless remove
16+
```
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
'use strict';
2+
3+
const _ = require('lodash');
4+
5+
function getBody(req, callback) {
6+
let body = [];
7+
req.on('data', (d) => body.push(d));
8+
req.on('end', () => {
9+
body = Buffer.concat(body).toString();
10+
callback(body);
11+
});
12+
}
13+
module.exports = {
14+
capitalize(req, res) {
15+
getBody(req, (body) => {
16+
res.end(_.capitalize(body));
17+
});
18+
},
19+
pad(req, res) {
20+
getBody(req, (body) => {
21+
res.end(_.pad(body, 20, '*'));
22+
});
23+
},
24+
reverse(req, res) {
25+
getBody(req, (body) => {
26+
res.end(body.split('').reverse().join(''));
27+
});
28+
},
29+
};
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "chain",
3+
"version": "1.0.0",
4+
"description": "Example function for serverless kubeless",
5+
"dependencies": {
6+
"serverless-kubeless": "^0.1.9",
7+
"lodash": "^4.1.0"
8+
},
9+
"devDependencies": {},
10+
"scripts": {
11+
"test": "echo \"Error: no test specified\" && exit 1"
12+
},
13+
"author": "",
14+
"license": "Apache-2.0"
15+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
service: chain
2+
3+
provider:
4+
name: kubeless
5+
runtime: nodejs6
6+
7+
plugins:
8+
- serverless-kubeless
9+
10+
functions:
11+
capitalize:
12+
handler: handler.capitalize
13+
pad:
14+
handler: handler.pad
15+
reverse:
16+
handler: handler.reverse
17+
chained_seq:
18+
sequence:
19+
- capitalize
20+
- pad
21+
- reverse

invoke/kubelessInvoke.js

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -36,20 +36,21 @@ class KubelessInvoke {
3636
};
3737
}
3838

39-
getData() {
39+
getData(data) {
4040
let result = null;
41+
const d = data || this.options.data;
4142
try {
42-
if (!_.isEmpty(this.options.data)) {
43+
if (!_.isEmpty(d)) {
4344
try {
4445
// Try to parse data as JSON
4546
result = {
46-
body: JSON.parse(this.options.data),
47+
body: JSON.parse(d),
4748
json: true,
4849
};
4950
} catch (e) {
5051
// Assume data is a string
5152
result = {
52-
body: this.options.data,
53+
body: d,
5354
};
5455
}
5556
} else if (this.options.path) {
@@ -89,8 +90,8 @@ class KubelessInvoke {
8990
return BbPromise.resolve();
9091
}
9192

92-
invokeFunction() {
93-
const f = this.options.function;
93+
invokeFunction(func, data) {
94+
const f = func || this.options.function;
9495
this.serverless.cli.log(`Calling function: ${f}...`);
9596
const config = helpers.loadKubeConfig();
9697
const APIRootUrl = helpers.getKubernetesAPIURL(config);
@@ -102,8 +103,23 @@ class KubelessInvoke {
102103
helpers.getConnectionOptions(helpers.loadKubeConfig()),
103104
{ url }
104105
);
105-
const requestData = this.getData();
106-
106+
const requestData = this.getData(data);
107+
if (this.serverless.service.functions[f].sequence) {
108+
let promise = null;
109+
_.each(this.serverless.service.functions[f].sequence.slice(), sequenceFunction => {
110+
if (promise) {
111+
promise = promise.then(
112+
result => this.invokeFunction(sequenceFunction, result.body)
113+
);
114+
} else {
115+
promise = this.invokeFunction(sequenceFunction, data);
116+
}
117+
});
118+
return new BbPromise((resolve, reject) => promise.then(
119+
(response) => resolve(response),
120+
err => reject(err)
121+
));
122+
}
107123
return new BbPromise((resolve, reject) => {
108124
const parseReponse = (err, response) => {
109125
if (err) {
@@ -116,20 +132,21 @@ class KubelessInvoke {
116132
}
117133
};
118134
if (_.isEmpty(requestData)) {
119-
// There is no data to send, sending a GET request
135+
// There is no data to send, sending a GET request
120136
request.get(connectionOptions, parseReponse);
121137
} else {
122-
// Sending request data with a POST
138+
// Sending request data with a POST
123139
request.post(
124-
Object.assign(
125-
connectionOptions,
126-
requestData
127-
),
128-
parseReponse
129-
);
140+
Object.assign(
141+
connectionOptions,
142+
requestData
143+
),
144+
parseReponse
145+
);
130146
}
131147
});
132148
}
149+
133150
log(response) {
134151
if (this.options.log) {
135152
console.log('--------------------------------------------------------------------');

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "serverless-kubeless",
3-
"version": "0.1.9",
3+
"version": "0.1.10",
44
"description": "This plugin enables support for Kubeless within the [Serverless Framework](https://github.com/serverless).",
55
"main": "index.js",
66
"directories": {

0 commit comments

Comments
 (0)