Skip to content

Commit a473913

Browse files
committed
first commit
0 parents  commit a473913

7 files changed

+534
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/node_modules/

daemon

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/bin/bash
2+
# Deployment daemon
3+
# chkconfig: 345 20 80
4+
# description: Manages the komodo-web-deploy instance, basically just wraps "forever"
5+
# processname: ko-deploy
6+
7+
DAEMON_PATH="/home/deployment/komodo-web-deploy"
8+
DAEMON="node server.js"
9+
NAME="ko-deploy"
10+
USER="nathanr"
11+
12+
case "$1" in
13+
start)
14+
su $USER -c "$DAEMON_PATH/node_modules/forever/bin/forever stop $DAEMON_PATH/server.js"
15+
;;
16+
status)
17+
forever list
18+
;;
19+
stop)
20+
su $USER -c "$DAEMON_PATH/node_modules/forever/bin/forever stop $DAEMON_PATH/server.js"
21+
;;
22+
23+
restart)
24+
$0 stop
25+
$0 start
26+
;;
27+
28+
*)
29+
echo "Usage: $0 {status|start|stop|restart}"
30+
exit 1
31+
esac

deployment.log

+265
Large diffs are not rendered by default.

deployments.js

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
module.exports =
2+
{
3+
"komodo-website": // repository
4+
{
5+
"master": // branch
6+
{
7+
path: __dirname + "/../komodo-website-master"
8+
}
9+
}
10+
};

package.json

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"name": "komodo-web-deploy",
3+
"description": "Minimal deployment system for Komodo web apps",
4+
"version": "0.0.1",
5+
"private": true,
6+
"dependencies": {
7+
"express": "3.x",
8+
"cron": "~1.0.4",
9+
"winston": "~0.7.3",
10+
"forever": "~0.11.0"
11+
}
12+
}

readme.md

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
This is a small and very simple deployment script that allows us to automatically
2+
deploy eg. komodoide.com whenever we push to our deployment branches.
3+
4+
It is designed specifically with static sites in mind, and will only work if
5+
both deployer and deployment configurations are on the same server, in the same
6+
parent directory.
7+
8+
For a site to be valid for deployment it must have a deploy.js library in its
9+
root which exposes the following methods:
10+
11+
* init(logger)
12+
* run(params, done)
13+
* schedule(branch, cron, deploy) - optional, if you want to schedule deployments
14+
via cron
15+
16+
To learn more about how this works please look at [deploy.js] on the komodo-website repository
17+
18+
[deploy.js]: https://github.com/Komodo/komodo-website/blob/master/deploy.js

server.js

+197
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
var server = new function()
2+
{
3+
var express = require('express');
4+
var sys = require('sys');
5+
var fs = require('fs');
6+
var exec = require('child_process').exec;
7+
var CronJob = require('cron').CronJob;
8+
var winston = require('winston');
9+
10+
var deploying = {};
11+
var queued = {};
12+
var app;
13+
14+
// Run the logger, which dumps everything to stdout and deployment.log
15+
var logger = new winston.Logger({
16+
transports: [
17+
new (winston.transports.Console)({ level: 'debug', colorize: true }),
18+
new (winston.transports.File)({ filename: 'deployment.log', level: 'verbose', json: false })
19+
]
20+
});
21+
22+
var init = function ()
23+
{
24+
// Initialize App and define how we parse the request body
25+
app = express();
26+
27+
app.configure(function () {
28+
app.use(function (err, req, res, next)
29+
{
30+
logger.error(err.stack);
31+
next(err);
32+
});
33+
34+
app.use(express.json());
35+
app.use(express.urlencoded());
36+
app.use(express.multipart());
37+
});
38+
39+
// Bind our routes
40+
bindRoutes();
41+
42+
// Bind schedulers
43+
bindSchedulers();
44+
45+
// Launch the server
46+
var server = app.listen(8282, function()
47+
{
48+
logger.info('Listening on port %d', server.address().port);
49+
});
50+
51+
// Handle server related errors
52+
server.on("error", function (err)
53+
{
54+
logger.error('there was an error:', err.message);
55+
});
56+
};
57+
58+
var bindRoutes = function()
59+
{
60+
app.post('/hooks/push', routeHookPush);
61+
};
62+
63+
/**
64+
* Bind schedulers from sibling directories
65+
*/
66+
var bindSchedulers = function()
67+
{
68+
// Scan the parent dir
69+
var files = fs.readdirSync(__dirname + "/..");
70+
for (k in files)
71+
{
72+
var file = files[k];
73+
if (file[0] == '.') continue;
74+
75+
// Validate whether the current file/folder contains deploy.js
76+
var path = __dirname + "/../" + file
77+
if ( ! fs.existsSync(path + "/deploy.js")) continue;
78+
79+
// Validate whether this deployerRunner has a schedule method
80+
var deployerRunner = require(path + "/deploy.js");
81+
if ( ! ("schedule" in deployerRunner)) continue;
82+
83+
deployerRunner.init(logger);
84+
85+
// Retrieve the branch name of the current repo
86+
(function(path, deployerRunner)
87+
{
88+
exec('cd "'+path+'" && git rev-parse --abbrev-ref HEAD', function(err, stdo, stde)
89+
{
90+
if (err) return;
91+
92+
var branch = stdo.replace(/\s/g,'');
93+
94+
// Run the scheduler
95+
logger.debug("Running scheduler for " + path);
96+
deployerRunner.schedule(branch, CronJob, deploy);
97+
});
98+
})(path, deployerRunner);
99+
}
100+
};
101+
102+
/**
103+
* Github event hook for push events
104+
*/
105+
var routeHookPush = function(req, res)
106+
{
107+
logger.info("Received push event");
108+
109+
// Parse relevant info
110+
var payload = req.body;
111+
var repo = payload.repository.name;
112+
var branch = payload.ref.split("/").slice(-1).join("-");
113+
var deployerName = [repo,branch].join("-");
114+
115+
// Prepare object to be passed to deployerRunner
116+
var deployer = {
117+
name: deployerName,
118+
path: __dirname + "/../" + deployerName,
119+
repository: payload.repository.name,
120+
branch: payload.ref.split("/").slice(-1)[0]
121+
};
122+
123+
logger.debug("Deployer data", deployer);
124+
125+
// Validate whether we have a deployment for the current repo and branch
126+
fs.exists(deployer.path + "/deploy.js", function(exists)
127+
{
128+
logger.debug(deployer.path + " exists: " + exists);
129+
if ( ! exists) return;
130+
deploy(deployer);
131+
})
132+
133+
res.send('');
134+
};
135+
136+
/**
137+
* Perform a deployment
138+
*/
139+
var deploy = function(deployer)
140+
{
141+
// Add to queue if a deployment is already in progress for this repo+branch
142+
if (deployer.name in deploying)
143+
{
144+
logger.info("Queueing " + deployer.name);
145+
queued[deployer.name] = true;
146+
return;
147+
}
148+
149+
logger.info("Deploying " + deployer.name);
150+
deploying[deployer.name] = true;
151+
152+
// Perform a git pull on the targeted deployment so we can execute
153+
// the latest version of deploy.js
154+
exec('cd "'+deployer.path+'" && git pull', function(err, stdo, stde)
155+
{
156+
logger.debug(stdo);
157+
158+
if (err !== null)
159+
{
160+
logger.error("Git pull error ("+deployer.name+"): " + err);
161+
return;
162+
}
163+
164+
// Invoke the actual deployment script
165+
var deployerRunner = require(deployer.path + "/deploy.js");
166+
deployerRunner.init(logger);
167+
deployerRunner.run(deployer, function(err)
168+
{
169+
if (err)
170+
{
171+
logger.error("Error while deploying "+deployer.name+": " + err);
172+
}
173+
174+
// All done, unblock this deployer
175+
delete deploying[deployer.name];
176+
logger.info("Done deploying " + deployer.name);
177+
178+
// Run the job again if another deploy has
179+
// been scheduled for this deployer
180+
if (deployer.name in queued)
181+
{
182+
delete queued[deployer.name];
183+
deploy(deployer)
184+
}
185+
});
186+
});
187+
};
188+
189+
// Catch uncaught exceptions and restart the server
190+
process.on('uncaughtException', function (err) {
191+
logger.error('uncaughtException: ' + err.message, err.stack);
192+
process.exit(1); // Forever should restart our process (if we're running it through ./daemon)
193+
});
194+
195+
init();
196+
197+
}

0 commit comments

Comments
 (0)