Skip to content

Commit df3b392

Browse files
committed
initial commit
1 parent 144ef0c commit df3b392

File tree

4 files changed

+322
-7
lines changed

4 files changed

+322
-7
lines changed

index.js

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,78 @@
11
/* jshint node: true */
22
'use strict';
33

4+
var Promise = require('ember-cli/lib/ext/promise');
5+
var fs = require('fs');
6+
var tunnelSsh = require('tunnel-ssh');
7+
var untildify = require('untildify');
8+
9+
var DeployPluginBase = require('ember-cli-deploy-plugin');
10+
11+
var MAX_PORT_NUMBER = 65535;
12+
var MIN_PORT_NUMBER = 49151;
13+
414
module.exports = {
5-
name: 'ember-cli-deploy-ssh-tunnel'
15+
name: 'ember-cli-deploy-ssh-tunnel',
16+
17+
createDeployPlugin: function(options) {
18+
var DeployPlugin = DeployPluginBase.extend({
19+
name: options.name,
20+
defaultConfig: {
21+
dstPort: 6379,
22+
srcPort: function() {
23+
var range = MAX_PORT_NUMBER - MIN_PORT_NUMBER + 1;
24+
return Math.floor(Math.random() * range) + MIN_PORT_NUMBER;
25+
},
26+
privateKeyPath: '~/.ssh/id_rsa',
27+
tunnelClient: function(context) {
28+
// if you want to provide your own ssh client to be used instead of one from this plugin
29+
return context.tunnelClient || tunnelSsh;
30+
}
31+
},
32+
33+
requiredConfig: ['host', 'username'],
34+
35+
willDeploy: function(/* context */) {
36+
var srcPort = this.readConfig('srcPort');
37+
38+
if (srcPort > MAX_PORT_NUMBER || srcPort < MIN_PORT_NUMBER) {
39+
throw 'Port ' + srcPort + ' is not available to open a SSH connection on.\n' + 'Please choose a port between ' + MIN_PORT_NUMBER + ' and ' + MAX_PORT_NUMBER + '.';
40+
}
41+
42+
var sshConfig = {
43+
host: this.readConfig('host'),
44+
dstPort: this.readConfig('dstPort'),
45+
username: this.readConfig('username'),
46+
localPort: srcPort,
47+
privateKey: this.readConfig('privateKeyPath')
48+
};
49+
50+
if (sshConfig.privateKey) {
51+
sshConfig.privateKey = fs.readFileSync(untildify(sshConfig.privateKey));
52+
}
53+
54+
var tunnel = this.readConfig('tunnelClient');
55+
56+
return new Promise(function (resolve, reject) {
57+
var sshTunnel = tunnel(sshConfig, function (error /*, result */) {
58+
if (error) {
59+
reject(error);
60+
} else {
61+
resolve({
62+
tunnel: {
63+
handler: sshTunnel,
64+
srcPort: srcPort
65+
}
66+
});
67+
}
68+
});
69+
});
70+
},
71+
72+
didDeploy: function(context) {
73+
context.tunnel.handler.close();
74+
}
75+
});
76+
return new DeployPlugin();
77+
}
678
};

package.json

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ember-cli-deploy-ssh-tunnel",
3-
"version": "0.0.0",
3+
"version": "0.0.1",
44
"description": "The default blueprint for ember-cli addons.",
55
"directories": {
66
"doc": "doc",
@@ -9,7 +9,7 @@
99
"scripts": {
1010
"start": "ember server",
1111
"build": "ember build",
12-
"test": "ember try:testall"
12+
"test": "node tests/runner.js"
1313
},
1414
"repository": "",
1515
"engines": {
@@ -19,6 +19,8 @@
1919
"license": "MIT",
2020
"devDependencies": {
2121
"broccoli-asset-rev": "^2.0.2",
22+
"chai": "^2.2.0",
23+
"chai-as-promised": "^5.0.0",
2224
"ember-cli": "1.13.1",
2325
"ember-cli-app-version": "0.4.0",
2426
"ember-cli-content-security-policy": "0.4.0",
@@ -34,15 +36,21 @@
3436
"ember-disable-proxy-controllers": "^1.0.0",
3537
"ember-export-application-global": "^1.0.2",
3638
"ember-disable-prototype-extensions": "^1.0.0",
37-
"ember-try": "0.0.6"
39+
"ember-try": "0.0.6",
40+
"mocha": "^2.2.4",
41+
"glob": "^5.0.14"
3842
},
3943
"keywords": [
40-
"ember-addon"
44+
"ember-addon",
45+
"ember-cli-deploy-plugin"
4146
],
4247
"dependencies": {
43-
"ember-cli-babel": "^5.0.0"
48+
"ember-cli-babel": "^5.0.0",
49+
"ember-cli-deploy-plugin": "0.1.3",
50+
"tunnel-ssh": "^1.0.1",
51+
"untildify": "^2.0.0"
4452
},
4553
"ember-addon": {
4654
"configPath": "tests/dummy/config"
4755
}
48-
}
56+
}

tests/runner.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
'use strict';
2+
3+
var glob = require('glob');
4+
var Mocha = require('mocha');
5+
6+
var mocha = new Mocha({
7+
reporter: 'spec'
8+
});
9+
10+
var arg = process.argv[2];
11+
var root = 'tests/';
12+
13+
function addFiles(mocha, files) {
14+
glob.sync(root + files).forEach(mocha.addFile.bind(mocha));
15+
}
16+
17+
addFiles(mocha, '/**/*-nodetest.js');
18+
19+
if (arg === 'all') {
20+
addFiles(mocha, '/**/*-nodetest-slow.js');
21+
}
22+
23+
mocha.run(function(failures) {
24+
process.on('exit', function() {
25+
process.exit(failures);
26+
});
27+
});

tests/unit/index-nodetest.js

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
'use strict';
2+
3+
var Promise = require('ember-cli/lib/ext/promise');
4+
var assert = require('ember-cli/tests/helpers/assert');
5+
var fs = require('fs');
6+
var path = require('path');
7+
8+
describe('ssh-tunnel plugin', function() {
9+
var subject, mockUi;
10+
11+
beforeEach(function() {
12+
subject = require('../../index');
13+
mockUi = {
14+
messages: [],
15+
write: function() { },
16+
writeLine: function(message) {
17+
this.messages.push(message);
18+
}
19+
};
20+
});
21+
22+
it('has a name', function() {
23+
var result = subject.createDeployPlugin({
24+
name: 'test-plugin'
25+
});
26+
27+
assert.equal(result.name, 'test-plugin');
28+
});
29+
30+
it('implements the correct hooks', function() {
31+
var result = subject.createDeployPlugin({
32+
name: 'test-plugin'
33+
});
34+
35+
assert.equal(typeof result.defaultConfig, 'object');
36+
assert.equal(typeof result.requiredConfig, 'object');
37+
assert.equal(typeof result.willDeploy, 'function');
38+
assert.equal(typeof result.didDeploy, 'function');
39+
});
40+
41+
describe('configure hook', function() {
42+
43+
it('resolves if config is ok', function() {
44+
var plugin = subject.createDeployPlugin({
45+
name: 'ssh-tunnel'
46+
});
47+
48+
var context = {
49+
ui: mockUi,
50+
config: {
51+
'ssh-tunnel': {
52+
host: 'example.com',
53+
username: 'ghedamat'
54+
}
55+
}
56+
};
57+
58+
plugin.beforeHook(context);
59+
plugin.configure(context);
60+
assert.ok(true); // it didn't throw
61+
});
62+
63+
describe('without providing config', function () {
64+
var plugin, context, config;
65+
66+
beforeEach(function() {
67+
plugin = subject.createDeployPlugin({
68+
name: 'ssh-tunnel'
69+
});
70+
});
71+
72+
it('raises about missing required config', function() {
73+
config = { };
74+
context = {
75+
ui: mockUi,
76+
config: config
77+
};
78+
plugin.beforeHook(context);
79+
assert.throws(function(error){
80+
plugin.configure(context);
81+
});
82+
var messages = mockUi.messages.reduce(function(previous, current) {
83+
if (/- Missing required config:\s.*/.test(current)) {
84+
previous.push(current);
85+
}
86+
87+
return previous;
88+
}, []);
89+
assert.equal(messages.length, 1);
90+
});
91+
92+
it('warns about missing optional config', function() {
93+
context = {
94+
ui: mockUi,
95+
config: {
96+
'ssh-tunnel': {
97+
host: 'example.com',
98+
username: 'ghedamat'
99+
}
100+
}
101+
};
102+
plugin.beforeHook(context);
103+
plugin.configure(context);
104+
var messages = mockUi.messages.reduce(function(previous, current) {
105+
if (/- Missing config:\s.*, using default:\s/.test(current)) {
106+
previous.push(current);
107+
}
108+
109+
return previous;
110+
}, []);
111+
assert.equal(messages.length, 4);
112+
});
113+
114+
it('adds default config to the config object', function() {
115+
context = {
116+
ui: mockUi,
117+
config: {
118+
'ssh-tunnel': {
119+
host: 'example.com',
120+
username: 'ghedamat'
121+
}
122+
}
123+
};
124+
plugin.beforeHook(context);
125+
plugin.configure(context);
126+
assert.isDefined(config['ssh-tunnel'].dstPort);
127+
assert.isDefined(config['ssh-tunnel'].srcPort);
128+
assert.isDefined(config['ssh-tunnel'].tunnelClient);
129+
assert.isDefined(config['ssh-tunnel'].privateKeyPath);
130+
});
131+
});
132+
});
133+
134+
describe('willDeploy hook', function() {
135+
var plugin;
136+
var context;
137+
138+
beforeEach(function() {
139+
plugin = subject.createDeployPlugin({
140+
name: 'ssh-tunnel'
141+
});
142+
143+
context = {
144+
ui: mockUi,
145+
config: {
146+
'ssh-tunnel': {
147+
host: 'example.com',
148+
username: 'ghedamat',
149+
srcPort: 50000,
150+
dstPort: 6379,
151+
tunnelClient: function() {
152+
return function(config, cbk) {
153+
process.nextTick(cbk, false, true);
154+
return {
155+
close: function() { }
156+
};
157+
};
158+
}
159+
}
160+
},
161+
};
162+
plugin.beforeHook(context);
163+
});
164+
165+
it('returns the updated context if tunnel is successful', function(done) {
166+
assert.isFulfilled(plugin.willDeploy(context)).then(function(result) {
167+
assert.isDefined(result.tunnel.handler);
168+
assert.isDefined(result.tunnel.srcPort);
169+
done();
170+
}).catch(function(reason) {
171+
done(reason.actual.stack);
172+
});
173+
});
174+
});
175+
176+
describe('didDeploy hook', function() {
177+
var plugin;
178+
var context;
179+
180+
beforeEach(function() {
181+
plugin = subject.createDeployPlugin({
182+
name: 'ssh-tunnel'
183+
});
184+
185+
context = {
186+
ui: mockUi,
187+
config: {
188+
'ssh-tunnel': {
189+
}
190+
}
191+
};
192+
plugin.beforeHook(context);
193+
});
194+
195+
it('closes the tunnel', function(done) {
196+
context.tunnel = {
197+
handler: {
198+
close: function() {
199+
assert.ok(true);
200+
done();
201+
}
202+
}
203+
};
204+
205+
var result = plugin.didDeploy(context);
206+
});
207+
});
208+
});

0 commit comments

Comments
 (0)