Skip to content

Commit 6b777c8

Browse files
committed
refactor deploy & migrations
1 parent b5dd9a8 commit 6b777c8

19 files changed

Lines changed: 299 additions & 142 deletions

File tree

gulpfile.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ gulp.task('watch', lazyRequireTask('./tasks/watch', {
119119

120120
gulp.task('deploy:init', lazyRequireTask('deploy/tasks/init'));
121121
gulp.task('deploy:build', lazyRequireTask('deploy/tasks/build'));
122+
gulp.task('deploy:migrate', lazyRequireTask('deploy/tasks/migrate'));
122123
gulp.task('deploy:update', lazyRequireTask('deploy/tasks/update'));
123124

124125
gulp.task("client:sync-resources", lazyRequireTask('./tasks/syncResources', {
@@ -186,7 +187,11 @@ gulp.on('stop', function() {
186187
mongoose.disconnect();
187188
});
188189

189-
gulp.on('err', function() {
190+
gulp.on('err', function(gulpErr) {
191+
if (gulpErr.err) {
192+
// cause
193+
console.error(gulpErr.err.message, gulpErr.err.stack, gulpErr.err.errors);
194+
}
190195
mongoose.disconnect();
191196
});
192197

handlers/imgur/models/imgurImage.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@ const schema = new Schema({
8686
}
8787
});
8888

89+
/**
90+
* Create ImgurImage from raw imgur response and save it.
91+
* @param response
92+
* @returns {*}
93+
*/
8994
schema.statics.createFromResponse = function*(response) {
9095

9196
if (!response.success) {
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
var fs = require('fs');
2+
var co = require('co');
3+
var path = require('path');
4+
var gutil = require('gulp-util');
5+
var dataUtil = require('lib/dataUtil');
6+
var mongoose = require('lib/mongoose');
7+
var yargs = require('yargs');
8+
9+
var User = require('users').User;
10+
var ImgurImage = require('imgur').ImgurImage;
11+
12+
exports.up = function*() {
13+
// mongoose definition changed, must access through the native driver
14+
var userObjects = yield function(callback) {
15+
User.collection.find({}).toArray(callback);
16+
};
17+
18+
yield ImgurImage.remove({});
19+
20+
for (var i = 0; i < userObjects.length; i++) {
21+
var user = userObjects[i];
22+
if (!user.photo) continue;
23+
console.log(user.photo);
24+
var imgurImage = yield ImgurImage.createFromResponse({ success: true, data: user.photo});
25+
yield function(callback) {
26+
User.collection.update(
27+
{_id: user._id},
28+
{$set: { photo: imgurImage.link } },
29+
callback
30+
);
31+
};
32+
}
33+
};
34+
35+
exports.down = function*() {
36+
37+
};

modules/deploy/lib/sshConnect.js

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,14 @@ module.exports = function*(host) {
2828

2929

3030
client.runInBuild = function(cmd, options) {
31-
gutil.log('>', cmd);
32-
3331
return sshExec(client, `cd ${config.deploy.buildPath}; ${cmd}`, options);
3432
};
3533

3634
client.run = function(cmd, options) {
37-
gutil.log('>', cmd);
38-
3935
return sshExec(client, cmd, options);
4036
};
4137

4238
client.runInTarget = function(cmd, options) {
43-
gutil.log('>', cmd);
44-
4539
return sshExec(client, `cd ${config.deploy.targetPath}; ${cmd}`, options);
4640
};
4741

modules/deploy/lib/sshExec.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
var gutil = require('gulp-util');
2+
13
/**
24
* Executes ssh command
35
* NB: DOES NOT KEEP STATE BETWEEN COMMANDS
@@ -9,10 +11,12 @@
911
module.exports = function*(client, cmd, options) {
1012

1113
options = options || {};
14+
15+
// certain commands like `git clone` require pty
1216
if (!("pty" in options)) options.pty = true;
1317

1418
var stream = yield function(callback) {
15-
// certain commands like `git clone` require pty
19+
gutil.log('sshExec', cmd);
1620
client.exec(cmd, options, callback);
1721
};
1822

modules/deploy/tasks/build.js

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ var sshExec = require('../lib/sshExec');
55
var config = require('config');
66
var gutil = require('gulp-util');
77

8-
// path/build must be initialized with:
9-
// git clone -b production --single-branch git://sub.domain.com/repo.git
8+
/**
9+
* Update prod build dir from master, rebuild and commit to prod
10+
* @returns {Function}
11+
*/
1012
module.exports = function() {
1113

1214
var args = require('yargs')
@@ -19,28 +21,30 @@ module.exports = function() {
1921

2022
return co(function*() {
2123

22-
var client = yield* sshConnect(args.host);
23-
24-
yield* client.runInBuild(`git reset --hard`);
25-
yield* client.runInBuild(`git fetch origin master`);
26-
yield* client.runInBuild(`git merge origin/master --no-edit`);
2724

28-
if (args.npm) {
29-
yield* reinstallModules();
30-
}
25+
var client = yield* sshConnect(args.host);
3126

32-
yield* client.runInBuild(`NODE_ENV=production ASSET_VERSIONING=file gulp build`);
33-
yield* client.runInBuild('git add --force public manifest');
27+
try {
28+
yield* client.runInBuild(`git reset --hard`);
29+
yield* client.runInBuild(`git fetch origin master`);
30+
yield* client.runInBuild(`git merge origin/master --no-edit`);
3431

35-
// if there's nothing to commit,
36-
// `git commit` would exit with status 1, stopping the deploy
37-
// so I commit only if there are changes
38-
yield* client.runInBuild('git diff-index --quiet HEAD && git commit -a -m deploy || exit 0');
32+
if (args.npm) {
33+
yield* reinstallModules();
34+
}
3935

40-
yield* client.runInBuild('git push origin production');
36+
yield* client.runInBuild(`NODE_ENV=production ASSET_VERSIONING=file gulp build`);
37+
yield* client.runInBuild('git add --force public manifest');
4138

42-
client.end();
39+
// if there's nothing to commit,
40+
// `git commit` would exit with status 1, stopping the deploy
41+
// so I commit only if there are changes
42+
yield* client.runInBuild('git diff-index --quiet HEAD && git commit -a -m deploy || exit 0');
4343

44+
yield* client.runInBuild('git push origin production');
45+
} finally {
46+
client.end();
47+
}
4448

4549
function* reinstallModules() {
4650
yield* client.runInBuild(`rm -rf node_modules`);

modules/deploy/tasks/init.js

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ var sshExec = require('../lib/sshExec');
44
var config = require('config');
55
var gutil = require('gulp-util');
66

7-
// path/build must be initialized with:
8-
// git clone -b production --single-branch git://sub.domain.com/repo.git
7+
/**
8+
* Init build & working copy.
9+
* Deletes and recreates everything.
10+
* @returns {Function}
11+
*/
912
module.exports = function() {
1013

1114
var args = require('yargs')
@@ -18,22 +21,30 @@ module.exports = function() {
1821

1922
var client = yield* sshConnect(args.host);
2023

21-
yield* client.run(`rm -rf ${config.deploy.buildPath}`);
22-
yield* client.run(`rm -rf ${config.deploy.targetPath}`);
24+
try {
25+
yield* client.run(`rm -rf ${config.deploy.buildPath}`);
2326

24-
// must make big --depth N, because `gut pull origin/master` may fail to auto-merge if depth is too small
25-
// for safety not using --depth at all
26-
// not using --single-branch, cause need master too
27-
yield* client.run(`git clone ${config.deploy.repo} ${config.deploy.buildPath}`);
28-
// now master & production branches are created,
29-
// using production for the build
30-
yield* client.runInBuild(`git checkout production`);
31-
//yield* client.runInBuild(`npm install`);
27+
// must make big --depth N, because `gut pull origin/master` may fail to auto-merge if depth is too small
28+
// for safety not using --depth at all
29+
// not using --single-branch, cause need master too
30+
yield* client.run(`git clone ${config.deploy.repo} ${config.deploy.buildPath}`);
31+
// now master & production branches are created,
32+
// using production for the build
33+
yield* client.runInBuild(`git checkout production`);
34+
//yield* client.runInBuild(`npm install`);
3235

33-
yield* client.run(`mkdir -p ${config.deploy.targetPath}`);
34-
yield* client.run(`cp -a ${config.deploy.buildPath} ${config.deploy.targetPath}`);
36+
yield* client.run(`rm -rf ${config.deploy.targetPath}`);
3537

36-
client.end();
38+
// create path to target dir
39+
yield* client.run(`mkdir -p ${config.deploy.targetPath}`);
40+
41+
// remove the target dir itself (will be copied)
42+
yield* client.run(`rmdir ${config.deploy.targetPath}`);
43+
44+
yield* client.run(`cp -a ${config.deploy.buildPath} ${config.deploy.targetPath}`);
45+
} finally {
46+
client.end();
47+
}
3748

3849
});
3950

modules/deploy/tasks/update.js

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ var sshExec = require('../lib/sshExec');
55
var config = require('config');
66
var gutil = require('gulp-util');
77

8+
/**
9+
* Update host working site to production
10+
* @returns {Function}
11+
*/
812
module.exports = function() {
913

1014
var args = require('yargs')
@@ -18,17 +22,22 @@ module.exports = function() {
1822

1923
var client = yield* sshConnect(args.host);
2024

21-
yield* client.runInTarget(`git reset --hard`);
22-
yield* client.runInTarget(`git fetch origin production`);
23-
yield* client.runInTarget(`git merge origin/production --no-edit`);
24-
25-
yield* client.runInTarget(`/usr/local/bin/pm2 startOrGracefulReload ecosystem.json --env production`);
26-
yield* client.runInTarget(`gulp cache:clean`);
27-
yield* client.runInTarget(`gulp cloudflare:clean | bunyan`);
28-
yield* client.runInTarget(`gulp config:nginx --prefix /etc/nginx --env production --root /js/javascript-nodejs --sslEnabled`);
29-
yield* client.runInTarget(`/etc/init.d/nginx reload`);
30-
31-
client.end();
25+
try {
26+
yield* client.runInTarget(`git reset --hard`);
27+
yield* client.runInTarget(`git fetch origin production`);
28+
yield* client.runInTarget(`git merge origin/production --no-edit`);
29+
30+
// if there any migrations, this will stop the server and apply them
31+
yield* client.runInTarget(`gulp deploy:migrate`);
32+
33+
yield* client.runInTarget(`/usr/local/bin/pm2 startOrGracefulReload ecosystem.json --env production --name js`);
34+
yield* client.runInTarget(`gulp cache:clean`);
35+
yield* client.runInTarget(`gulp cloudflare:clean | bunyan`);
36+
yield* client.runInTarget(`gulp config:nginx --prefix /etc/nginx --env production --root /js/javascript-nodejs --sslEnabled`);
37+
yield* client.runInTarget(`/etc/init.d/nginx reload`);
38+
} finally {
39+
client.end();
40+
}
3241

3342
});
3443

modules/migrate/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
exports.MigrationManager = require('./lib/migrationManager');
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
"use strict";
2+
3+
var path = require('path');
4+
var MigrationState = require('../models/migrationState');
5+
var migrationsRoot = require('config').migrationsRoot;
6+
var moment = require('moment');
7+
var glob = require('glob');
8+
var log = require('log')();
9+
10+
class MigrationManager {
11+
12+
*loadState() {
13+
// current migration date, 0 at start
14+
var migrationState = yield MigrationState.findOne({}).exec();
15+
if (!migrationState) {
16+
migrationState = yield MigrationState.create({
17+
currentMigration: 0
18+
});
19+
}
20+
21+
this.state = migrationState;
22+
}
23+
24+
/**
25+
* Looks for the next migration file
26+
* @param direction 1 or -1
27+
* @returns file path & date, or null if not exists
28+
*/
29+
findNextMigration(direction) {
30+
31+
var lastMigrationDate = this.state.currentMigration;
32+
33+
var migrationFiles = glob.sync(path.join(migrationsRoot, '*')).filter(function(migrationFile) {
34+
return parseInt(path.basename(migrationFile)); // only files like 20150505...
35+
});
36+
37+
if (!migrationFiles.length) {
38+
return;
39+
}
40+
41+
var migrationFile, migrationDate;
42+
43+
if (direction > 0) {
44+
for (var i = 0; i < migrationFiles.length; i++) {
45+
migrationFile = migrationFiles[i];
46+
migrationDate = parseInt(path.basename(migrationFile));
47+
if (migrationDate > lastMigrationDate) {
48+
return {
49+
date: migrationDate,
50+
file: migrationFile
51+
};
52+
}
53+
}
54+
55+
} else {
56+
for (var i = migrationFiles.length - 1; i >= 0; i--) {
57+
var migrationFile = migrationFiles[i];
58+
var migrationDate = parseInt(path.basename(migrationFile));
59+
if (migrationDate == lastMigrationDate) {
60+
return {
61+
date: migrationDate,
62+
file: migrationFile,
63+
datePrev: i > 0 ? parseInt(path.basename(migrationFiles[i - 1])) : 0
64+
};
65+
}
66+
}
67+
}
68+
69+
70+
}
71+
72+
*migrate(direction) {
73+
74+
var migration = this.findNextMigration(direction);
75+
if (!migration) {
76+
return false;
77+
}
78+
79+
log.debug("Apply migration", migration.file, direction);
80+
81+
var migrate = direction > 0 ? require(migration.file).up : require(migration.file).down;
82+
83+
yield *migrate();
84+
85+
yield this.state.persist({
86+
currentMigration: direction > 0 ? migration.date : migration.datePrev
87+
});
88+
89+
return true;
90+
91+
}
92+
93+
/**
94+
* Apply all up migrations one by one
95+
*/
96+
*migrateAllUp() {
97+
while(true) {
98+
var migrated = yield this.migrate(1);
99+
if (!migrated) break;
100+
}
101+
}
102+
103+
}
104+
105+
module.exports = MigrationManager;

0 commit comments

Comments
 (0)