Skip to content

Commit 27627b8

Browse files
committed
Added basic feature to auto deploy gems from Gemfile to aws layer and made it available to GEM_PATH of all functions
1 parent 2b20d09 commit 27627b8

File tree

8 files changed

+254
-41
lines changed

8 files changed

+254
-41
lines changed

.gitignore

Lines changed: 5 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22
logs
33
*.log
44
npm-debug.log*
5-
yarn-debug.log*
6-
yarn-error.log*
5+
76

87
# Runtime data
98
pids
@@ -17,45 +16,12 @@ lib-cov
1716
# Coverage directory used by tools like istanbul
1817
coverage
1918

20-
# nyc test coverage
21-
.nyc_output
22-
23-
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24-
.grunt
25-
26-
# Bower dependency directory (https://bower.io/)
27-
bower_components
28-
29-
# node-waf configuration
30-
.lock-wscript
31-
32-
# Compiled binary addons (https://nodejs.org/api/addons.html)
33-
build/Release
34-
3519
# Dependency directories
3620
node_modules/
37-
jspm_packages/
38-
39-
# TypeScript v1 declaration files
40-
typings/
41-
42-
# Optional npm cache directory
43-
.npm
44-
45-
# Optional eslint cache
46-
.eslintcache
47-
48-
# Optional REPL history
49-
.node_repl_history
50-
51-
# Output of 'npm pack'
52-
*.tgz
5321

54-
# Yarn Integrity file
55-
.yarn-integrity
22+
.vscode
5623

57-
# dotenv environment variables file
58-
.env
24+
package-lock.json
5925

60-
# next.js build output
61-
.next
26+
#OS
27+
.DS_Store

README.md

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,41 @@
1-
# serverless-ruby-gemfile
2-
Serverless plugin to bundle Ruby gems
1+
<h1><img height="75" src="https://user-images.githubusercontent.com/20145075/86084483-aa2d4b80-baba-11ea-938d-53d6b7e37896.png" alt="iOS resume application project app icon"></h1>
2+
3+
[![serverless](http://public.serverless.com/badges/v3.svg)](http://www.serverless.com) [![npm](https://img.shields.io/npm/v/serverless-ruby-layer.svg)](https://www.npmjs.com/package/serverless-ruby-layer)
4+
5+
A Serverless Plugin to bundle ruby gems from Gemfile and deploy it to lambda layer automatically while running `severless deploy`.
6+
7+
It auto configures the lamda layer and RUBY_PATH to the all the functions.
8+
9+
## Install
10+
11+
```shell
12+
npm install serverless-ruby-bundler
13+
```
14+
15+
## Simple Usage
16+
17+
*Include plugin in the `serverless.yml`*
18+
19+
```YML
20+
service: basic
21+
22+
plugins:
23+
- serverless-ruby-layer
24+
25+
provider:
26+
name: aws
27+
runtime: ruby2.5
28+
29+
functions:
30+
hello:
31+
handler: handler.hello
32+
```
33+
34+
*Gemfile*
35+
36+
```ruby
37+
source 'https://rubygems.org'
38+
gem 'httparty'
39+
```
40+
41+
Running `servleress deploy` automatically deploys the required gems as in Gemfile to AWS lambda layer and make the gems available to the `RUBY_PATH` of the functions `hello.handler`

examples/basic/Gemfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
source 'https://rubygems.org'
2+
gem 'httparty'

examples/basic/handler.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
require 'httparty'
2+
3+
def hello(event:, context:)
4+
body = HTTParty.get("https://github.com").body
5+
6+
{ statusCode: 200, body: body }
7+
end

examples/basic/serverless.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
service: basic
2+
3+
plugins:
4+
- serverless-ruby-layer
5+
6+
provider:
7+
name: aws
8+
runtime: ruby2.5
9+
10+
functions:
11+
hello:
12+
handler: handler.hello

index.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
'use strict';
2+
const Promise = require('bluebird');
3+
const { bundleGems } = require('./lib/bundle');
4+
5+
class ServerlessRubyBundler {
6+
get options() {
7+
const options = Object.assign(
8+
{},
9+
(this.serverless.service.custom &&
10+
this.serverless.service.custom.rubyLayer) ||
11+
{}
12+
);
13+
return options;
14+
}
15+
constructor(serverless) {
16+
this.serverless = serverless;
17+
this.servicePath = this.serverless.config.servicePath;
18+
this.warningLogged = false;
19+
20+
this.commands = {
21+
rubylayer: {
22+
lifecycleEvents: ['pack',],
23+
},
24+
};
25+
26+
const packRubyLayer = () => {
27+
return Promise.bind(this)
28+
.then(bundleGems);
29+
}
30+
31+
this.hooks = {
32+
'after:package:createDeploymentArtifacts': packRubyLayer,
33+
'rubylayer:pack': packRubyLayer,
34+
};
35+
36+
}
37+
38+
}
39+
40+
module.exports = ServerlessRubyBundler;

lib/bundle.js

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
const { spawnSync } = require('child_process');
2+
const path = require('path');
3+
const fs = require('fs-extra');
4+
const Promise = require('bluebird');
5+
var JSZip = require('jszip');
6+
7+
function runCommand(cmd,args,options) {
8+
const ps = spawnSync(cmd, args,options);
9+
if (ps.error) {
10+
console.log(ps.error.code)
11+
throw new Error(ps.error);
12+
} else if (ps.status !== 0) {
13+
throw new Error(ps.stderr);
14+
}
15+
return ps;
16+
}
17+
18+
function cleanBuild(){
19+
this.build_path = path.join(this.servicePath,'.serverless','build','ruby_layer')
20+
if (fs.pathExistsSync(this.build_path)){
21+
fs.rmdirSync(this.build_path, { recursive: true })
22+
}
23+
}
24+
25+
function bundleInstall(){
26+
this.serverless.cli.log(this.build_path)
27+
const gem_path = path.join(this.build_path,'Gemfile')
28+
fs.copySync(path.join(this.servicePath,'Gemfile'), gem_path )
29+
const bundle_args = ['bundle', 'install', '--path=.','--without', 'test', 'development']
30+
const options={cwd : this.build_path, encoding : 'utf8'}
31+
ps = runCommand("bundle",['-v'])
32+
if (ps.error && ps.error.code === 'ENOENT') {
33+
throw new Error('bundle command not found in local');
34+
}
35+
this.serverless.cli.log(bundle_args.slice(1,bundle_args.length))
36+
ps=runCommand(bundle_args[0],bundle_args.slice(1,bundle_args.length),options)
37+
this.serverless.cli.log(ps.stdout)
38+
}
39+
40+
function zipDir(folder_path,targetPath,zipOptions){
41+
zip = new JSZip()
42+
return addDirtoZip(zip, folder_path)
43+
.then(() => {
44+
return new Promise(resolve =>
45+
zip.generateNodeStream(zipOptions)
46+
.pipe(fs.createWriteStream(targetPath))
47+
.on('finish', resolve))
48+
.then(() => null)});
49+
}
50+
51+
function addDirtoZip(zip, dir_path){
52+
const dir_path_norm = path.normalize(dir_path)
53+
return fs.readdirAsync(dir_path_norm)
54+
.map(file_name => {
55+
return addFiletoZip(zip, dir_path_norm, file_name)
56+
});
57+
}
58+
59+
function addFiletoZip(zip, dir_path, file_name){
60+
const filePath = path.join(dir_path, file_name)
61+
return fs.statAsync(filePath)
62+
.then(stat => {
63+
if (stat.isDirectory()){
64+
return addDirtoZip(zip.folder(file_name), filePath);
65+
} else {
66+
const file_option = { date: stat.mtime, unixPermissions: stat.mode };
67+
return fs.readFileAsync(filePath)
68+
.then(data => zip.file(file_name, data, file_option));
69+
}
70+
});
71+
}
72+
73+
function zipBundleFolder() {
74+
this.gem_folder= fs.readdirSync(path.join(this.build_path,'ruby'))[0]
75+
fs.rmdirSync(path.join(this.build_path,'ruby',this.gem_folder,'cache'),{ recursive: true })
76+
const platform = process.platform == 'win32' ? 'DOS' : 'UNIX'
77+
return zipDir(path.join(this.build_path,'ruby'),
78+
path.join(this.build_path, 'gemLayer.zip'),
79+
{ platform: platform, compression: 'DEFLATE',
80+
compressionOptions: { level: 9 }});
81+
}
82+
83+
function configureLayer() {
84+
if (!this.serverless.service.layers) {
85+
this.serverless.service.layers = {};
86+
}
87+
88+
this.serverless.service.layers['gemLayer'] = Object.assign(
89+
{
90+
artifact: path.join(this.build_path, 'gemLayer.zip'),
91+
name: `${
92+
this.serverless.service.service
93+
}-${this.serverless.providers.aws.getStage()}-ruby-bundle`,
94+
description:
95+
'Ruby gem generated by serverless-ruby-bundler',
96+
compatibleRuntimes: [this.serverless.service.provider.runtime]
97+
},
98+
this.options.layer
99+
);
100+
101+
Object.keys(this.serverless.service.functions).forEach(funcName => {
102+
const function_ = this.serverless.service.getFunction(funcName)
103+
function_.environment={}
104+
function_.environment["GEM_PATH"]="/opt/"+this.gem_folder
105+
function_.layers = [{"Ref":"GemLayerLambdaLayer"}]
106+
})
107+
return Promise.resolve();
108+
}
109+
110+
function bundleGems() {
111+
return Promise.bind(this)
112+
.then(cleanBuild)
113+
.then(bundleInstall)
114+
.then(zipBundleFolder)
115+
.then(configureLayer)
116+
}
117+
118+
module.exports = { bundleGems};

package.json

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"name": "serverless-ruby-layer",
3+
"version": "1.0.0",
4+
"description": "Serverless Plugin to bundle gemfile as lamda layer",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "node test.js"
8+
},
9+
"repository": {
10+
"type": "git",
11+
"url": "git+https://github.com/navarasu/serverless-ruby-layer.git"
12+
},
13+
"keywords": [
14+
"serverless",
15+
"layer",
16+
"ruby"
17+
],
18+
"author": "Navarasu <nova@navarasu.coms>",
19+
"license": "MIT",
20+
"bugs": {
21+
"url": "https://github.com/navarasu/serverless-ruby-layer/issues"
22+
},
23+
"homepage": "https://github.com/navarasu/serverless-ruby-layer#readme",
24+
"dependencies": {
25+
"bluebird": "^3.5.5",
26+
"fs-extra": "^8.1.0",
27+
"jszip": "^3.1.0"
28+
}
29+
}

0 commit comments

Comments
 (0)