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

Commit 6fe6c1a

Browse files
committed
Adding Ruby support 💎
1 parent da15fc1 commit 6fe6c1a

File tree

5 files changed

+205
-1
lines changed

5 files changed

+205
-1
lines changed

README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,51 @@ def endpoint(params):
302302
303303
If you want to return an error message, return an object with an `error` property with the message.
304304

305+
## Writing Functions - Ruby
306+
307+
Here's an `hello.rb` file containing an example handler function.
308+
309+
```ruby
310+
def main(args)
311+
name = args["name"] || "stranger"
312+
greeting = "Hello #{name}!"
313+
puts greeting
314+
{ "greeting" => greeting }
315+
end
316+
```
317+
318+
In the `serverless.yaml` file, the `handler` property is used to denote the source file and function name of the serverless function.
319+
320+
```yaml
321+
functions:
322+
my_function:
323+
handler: hello.main
324+
runtime: ruby
325+
```
326+
327+
### Request Properties
328+
329+
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).
330+
331+
```ruby
332+
def main(args)
333+
name = args["name"] || "stranger"
334+
...
335+
```
336+
337+
### Function Return Values
338+
339+
The handler must return a hash from the function call.
340+
341+
```ruby
342+
def main(args)
343+
...
344+
{ "greeting" => greeting }
345+
end
346+
```
347+
348+
If you want to return an error message, return an `error` property string in the return hash.
349+
305350
## Writing Functions - Swift
306351

307352
Here's an `index.swift` file containing an example handler function.

compile/functions/runtimes/index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const Swift = require('./swift')
88
const Php = require('./php')
99
const Sequence = require('./sequence')
1010
const Java = require('./java')
11+
const Ruby = require('./ruby')
1112

1213
class Runtimes {
1314
constructor(serverless) {
@@ -20,7 +21,8 @@ class Runtimes {
2021
new Swift(serverless),
2122
new Php(serverless),
2223
new Sequence(serverless),
23-
new Java(serverless)
24+
new Java(serverless),
25+
new Ruby(serverless)
2426
];
2527
}
2628

compile/functions/runtimes/ruby.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use strict';
2+
3+
const BaseRuntime = require('./base')
4+
5+
class Ruby extends BaseRuntime {
6+
constructor (serverless) {
7+
super(serverless)
8+
this.kind = 'ruby'
9+
this.extension = '.rb'
10+
}
11+
12+
processActionPackage (handlerFile, zip) {
13+
return zip.file(handlerFile).async('nodebuffer').then(data => {
14+
zip.remove(handlerFile)
15+
return zip.file('main.rb', data)
16+
})
17+
}
18+
}
19+
20+
module.exports = Ruby

compile/functions/runtimes/tests/all.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ require('./php');
1010
require('./binary');
1111
require('./sequence');
1212
require('./java');
13+
require('./ruby');
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
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 Ruby = require('../ruby');
10+
const JSZip = require('jszip');
11+
const fs = require('fs-extra');
12+
13+
describe('Ruby', () => {
14+
let serverless;
15+
let ruby;
16+
let sandbox;
17+
18+
beforeEach(() => {
19+
sandbox = sinon.sandbox.create();
20+
serverless = {classes: {Error}, service: {}, getProvider: sandbox.spy()};
21+
serverless.service.provider = { name: 'openwhisk' };
22+
ruby = new Ruby(serverless);
23+
});
24+
25+
afterEach(() => {
26+
sandbox.restore();
27+
});
28+
29+
describe('#match()', () => {
30+
it('should match with explicit runtime', () => {
31+
serverless.service.provider.runtime = 'python';
32+
expect(ruby.match({runtime: 'ruby', handler: 'file.func'})).to.equal(true)
33+
});
34+
35+
it('should match with provider runtime', () => {
36+
serverless.service.provider.runtime = 'ruby';
37+
expect(ruby.match({handler: 'file.func'})).to.equal(true)
38+
});
39+
40+
it('should not match when wrong explicit runtime', () => {
41+
expect(ruby.match({runtime: 'python', handler: 'file.func'})).to.equal(false)
42+
});
43+
44+
it('should not match when wrong provider runtime', () => {
45+
serverless.service.provider.runtime = 'python';
46+
expect(ruby.match({handler: 'file.func'})).to.equal(false)
47+
});
48+
49+
it('should not match when missing handler', () => {
50+
expect(ruby.match({})).to.equal(false)
51+
});
52+
});
53+
54+
describe('#exec()', () => {
55+
it('should return ruby exec definition', () => {
56+
const fileContents = 'some file contents';
57+
const handler = 'handler.some_func';
58+
59+
const exec = { main: 'some_func', kind: 'ruby:default', code: new Buffer(fileContents) };
60+
sandbox.stub(ruby, 'generateActionPackage', (functionObj) => {
61+
expect(functionObj.handler).to.equal(handler);
62+
return Promise.resolve(new Buffer(fileContents));
63+
});
64+
return expect(ruby.exec({ handler, runtime: 'ruby'}))
65+
.to.eventually.deep.equal(exec);
66+
})
67+
68+
it('should support using custom image', () => {
69+
const fileContents = 'some file contents';
70+
const handler = 'handler.some_func';
71+
72+
const exec = { main: 'some_func', image: 'blah', kind: 'blackbox', code: new Buffer(fileContents) };
73+
sandbox.stub(ruby, 'generateActionPackage', (functionObj) => {
74+
expect(functionObj.handler).to.equal(handler);
75+
return Promise.resolve(new Buffer(fileContents));
76+
});
77+
return expect(ruby.exec({ handler, image: 'blah', runtime: 'ruby:7.1' }))
78+
.to.eventually.deep.equal(exec);
79+
})
80+
});
81+
82+
describe('#generateActionPackage()', () => {
83+
it('should throw error for missing handler file', () => {
84+
expect(() => ruby.generateActionPackage({handler: 'does_not_exist.main'}))
85+
.to.throw(Error, 'Function handler (does_not_exist.rb) does not exist.');
86+
})
87+
88+
it('should read service artifact and add main.rb for handler', () => {
89+
ruby.serverless.service.package = {artifact: '/path/to/zip_file.zip'};
90+
ruby.isValidFile = () => true
91+
const zip = new JSZip();
92+
const source = 'def main(args)\nname = args["name"] || "stranger"\ngreeting = "Hello #{name}!"\n{ "greeting" => greeting }\nend'
93+
zip.file("handler.rb", source);
94+
95+
return zip.generateAsync({type:"nodebuffer"}).then(zipped => {
96+
sandbox.stub(fs, 'readFile', (path, cb) => {
97+
expect(path).to.equal('/path/to/zip_file.zip');
98+
cb(null, zipped);
99+
});
100+
return ruby.generateActionPackage({handler: 'handler.main'}).then(data => {
101+
return JSZip.loadAsync(new Buffer(data, 'base64')).then(zip => {
102+
expect(zip.file("handler.rb")).to.be.equal(null)
103+
return zip.file("main.rb").async("string").then(main => {
104+
expect(main).to.be.equal(source)
105+
})
106+
})
107+
})
108+
});
109+
})
110+
111+
it('should handle service artifact for individual function handler', () => {
112+
const functionObj = {handler: 'handler.main', package: { artifact: '/path/to/zip_file.zip'}}
113+
ruby.serverless.service.package = {individually: true};
114+
ruby.isValidFile = () => true
115+
116+
const zip = new JSZip();
117+
const source = 'def main(args)\nname = args["name"] || "stranger"\ngreeting = "Hello #{name}!"\n{ "greeting" => greeting }\nend'
118+
zip.file("handler.rb", source);
119+
120+
return zip.generateAsync({type:"nodebuffer"}).then(zipped => {
121+
sandbox.stub(fs, 'readFile', (path, cb) => {
122+
expect(path).to.equal('/path/to/zip_file.zip');
123+
cb(null, zipped);
124+
});
125+
return ruby.generateActionPackage(functionObj).then(data => {
126+
return JSZip.loadAsync(new Buffer(data, 'base64')).then(zip => {
127+
expect(zip.file("handler.rb")).to.be.equal(null)
128+
return zip.file("main.rb").async("string").then(main => {
129+
expect(main).to.be.equal(source)
130+
})
131+
})
132+
})
133+
});
134+
});
135+
})
136+
});

0 commit comments

Comments
 (0)