UNPKG

10.7 kBJavaScriptView Raw
1#!/usr/bin/env node
2'use strict';
3
4// Copyright 2016 Luca-SAS, licensed under the Apache License 2.0
5
6var child_process = require('child_process');
7var fs = require('fs');
8var net = require('net');
9var DDPClient = require('ddp');
10var login = require('ddp-login');
11var netrc = require('netrc');
12
13var help = 'Usage: skale [options] <command> [<args>]\n' +
14'\n' +
15'Create, test, deploy, run clustered NodeJS applications\n' +
16'\n' +
17'Commands:\n' +
18' create <app> Create a new application\n' +
19' test [<args>...] Run application on local host\n' +
20' deploy [<args>...] Deploy application on skale cloud\n' +
21' run [<args>...] Run application on skale cloud\n' +
22' attach Attach to a running application\n' +
23' log Print log of an application\n' +
24' signup Create an account on skale cloud\n' +
25' status Print status of application on skale cloud\n' +
26' stop Stop application on skale cloud\n' +
27'\n' +
28'Options:\n' +
29' -d, --debug Enable debug traces\n' +
30' -f, --file Set program to run (default: package name)\n' +
31' --force Force action to occur, despite warning\n' +
32' -h, --help Print help and quit\n' +
33' -m, --memory MB Set the memory space limit per worker (default 4000 MB)\n' +
34' -r, --remote Run in the cloud instead of locally\n' +
35' -V, --version Print version and quit\n' +
36' -w, --worker num Set the number of workers (default 2)\n';
37
38var argv = require('minimist')(process.argv.slice(2), {
39 string: [
40 'c', 'config',
41 'f', 'file',
42 'm', 'memory',
43 'w', 'worker',
44 ],
45 boolean: [
46 'd', 'debug',
47 'force',
48 'h', 'help',
49 'V', 'version',
50 ],
51 default: {}
52});
53
54
55if (argv.h || argv.help) {
56 console.log(help);
57 process.exit();
58}
59if (argv.V || argv.version) {
60 var pkg = require('./package');
61 console.log(pkg.name + '-' + pkg.version);
62 process.exit();
63}
64if (argv.d || argv.debug) {
65 process.env.SKALE_DEBUG = 3;
66}
67
68var configPath = argv.c || argv.config || process.env.SKALE_CONFIG || process.env.HOME + '/.skalerc';
69var config = load_config(argv);
70var proto = config.ssl ? require('https') : require('http');
71var memory = argv.m || argv.memory || 4000;
72var worker = argv.w || argv.worker || 2;
73var rc = netrc();
74
75switch (argv._[0]) {
76 case 'attach':
77 attach();
78 break;
79 case 'create':
80 create(argv._[1]);
81 break;
82 case 'deploy':
83 deploy(argv._.splice(1));
84 break;
85 case 'log':
86 console.log('log: not implemented yet');
87 break;
88 case 'run':
89 run_remote(argv._.splice(1));
90 break;
91 case 'signup':
92 console.log('signup: not implemented yet');
93 break;
94 case 'status':
95 status();
96 break;
97 case 'stop':
98 stop();
99 break;
100 case 'test':
101 run_local(argv._.splice(1));
102 break;
103 default:
104 die('Error: invalid command: ' + argv._[0]);
105}
106
107function checkName(name) {
108 return /^[A-Za-z][A-Za-z0-9_-]+$/.test(name);
109}
110
111function create(name) {
112 if (!checkName(name)) die('skale create error: invalid name ' + name);
113 console.log('create application ' + name);
114 try {
115 fs.mkdirSync(name);
116 } catch (error) {
117 die('skale create error: ' + error.message);
118 }
119 process.chdir(name);
120 console.log('create local repository');
121 child_process.execSync('git init');
122
123 var pkg = {
124 name: name,
125 version: '0.1.0',
126 private: true,
127 keywords: [ 'skale' ],
128 dependencies: {
129 'skale-engine': '^0.6.1'
130 }
131 };
132 fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2));
133 var src = '#!/usr/bin/env node\n' +
134 '\n' +
135 'var sc = require(\'skale-engine\').context();\n' +
136 '\n' +
137 'sc.parallelize([\'Hello world\']).collect().then(function (res) {\n' +
138 ' console.log(sc.worker.length + \' workers, res:\', res);\n' +
139 ' sc.end();\n' +
140 '});\n';
141 fs.writeFileSync(name + '.js', src);
142 var gitIgnore = 'node_modules\nnpm-debug.log*\n.npm-install-changed.json\n';
143 fs.writeFileSync('.gitignore', gitIgnore);
144 var npm = child_process.spawnSync('npm', ['install'], {stdio: 'inherit'});
145 if (npm.status) die('skale create error: npm install failed');
146 console.log('Project ' + name + ' is now ready.\n' +
147 'Please change directory to ' + name + ': "cd ' + name + '"\n' +
148 'To run your app locally: "skale test"\n' +
149 'To modify your app: edit ' + name + '.js');
150}
151
152function die(err) {
153 console.error(err);
154 process.exit(1);
155}
156
157function load_config(argv) {
158 var conf = {}, save = false;
159 try { conf = JSON.parse(fs.readFileSync(configPath)); } catch (error) { save = true; }
160 process.env.SKALE_TOKEN = process.env.SKALE_TOKEN || conf.token;
161 if (save || argv._[0] == 'init') save_config(conf);
162 return conf;
163}
164
165function save_config(config) {
166 fs.writeFile(configPath, JSON.stringify(config, null, 2), function (err) {
167 if (err) die('Could node write ' + configPath + ':', err);
168 });
169}
170
171function status_local() {
172 var child = child_process.execFile('/bin/ps', ['ux'], function (err, out) {
173 var lines = out.split(/\r\n|\r|\n/);
174 for (var i = 0; i < lines.length; i++)
175 if (i == 0 || lines[i].match(/ skale-/)) console.log(lines[i].trim());
176 });
177}
178
179function run_local(args) {
180 var pkg = JSON.parse(fs.readFileSync('package.json'));
181 var cmd = argv.f || argv.file || pkg.name + '.js';
182 args.splice(0, 0, cmd);
183 child_process.spawn('node', args, {stdio: 'inherit'});
184}
185
186function skale_session(callback) {
187 var host = process.env.SKALE_SERVER || 'apps.skale.me';
188 var port = process.env.SKALE_PORT || 443;
189
190 var ddp = new DDPClient({
191 // All properties optional, defaults shown
192 host : host,
193 port : port,
194 ssl : !process.env.SKALE_NOSSL,
195 autoReconnect : true,
196 autoReconnectTimer: 500,
197 maintainCollections : true,
198 ddpVersion: '1', // ['1', 'pre2', 'pre1'] available
199 useSockJs: true,
200 url: 'wss://example.com/websocket'
201 });
202
203 ddp.connect(function (err, isreconnect) {
204 if (err) return callback(err, ddp, isreconnect);
205 login(ddp, {env: 'SKALE_TOKEN', retry: 2}, function (err, userInfo) {
206 if (err) return callback(err, ddp, isreconnect);
207 var token = userInfo.token;
208 if (userInfo.token != config.token) {
209 config.token = userInfo.token;
210 save_config(config);
211 }
212 return callback(err, ddp, isreconnect);
213 });
214 });
215}
216
217function deploy(args) {
218 skale_session(function (err, ddp, isreconnect) {
219 if (err) {
220 switch (err.reason) {
221 case 'User not found':
222 die('User not found');
223 default:
224 die(err.toString());
225 }
226 }
227 var pkg = JSON.parse(fs.readFileSync('package.json'));
228 var name = pkg.name;
229
230 ddp.call('etls.add', [{name: name}], function (err, res) {
231 if (err) die('Could not create application ' + name + ':', err);
232 var a = res.url.split('/');
233 var login = a[a.length - 2];
234 var host = a[2].replace(/:.*/, '');
235 var passwd = res.token;
236 rc[host] = {login: login, password: passwd};
237 netrc.save(rc);
238 child_process.exec('git remote remove skale; git remote add skale "' + res.url + '"; git add -A .; git commit -m "automatic commit"; git push skale master', function (err, stdout, stderr) {
239 if (err) die('deploy error: ' + err);
240 ddp.call('etls.deploy', [{name: name}], function (err, res) {
241 if (err) console.error(err);
242 else console.log(name + ' deployed');
243 ddp.close();
244 });
245 });
246 });
247 });
248}
249
250function run_remote(args) {
251 var diff = child_process.execSync('git diff skale/master');
252 if (diff.length) {
253 if (argv.force) console.error('Warning, running an obsolete version, you should deploy');
254 else die('Error: content has changed, deploy first or run --force');
255 }
256 skale_session(function (err, ddp, isreconnect) {
257 if (err) die('Could not connect:', err);
258 var pkg = JSON.parse(fs.readFileSync('package.json'));
259 var name = pkg.name;
260 var opt = {debug: process.env.SKALE_DEBUG};
261
262 ddp.call('etls.run', [{name: name, opt: opt}], function (err, res) {
263 if (err) die('run error:', err);
264 if (res.alreadyStarted) die('Error: application is already running, use "skale attach" or "skale stop"');
265 var taskId = res.taskId;
266 ddp.subscribe('task.withTaskId', [taskId], function () {});
267
268 var observer = ddp.observe('tasks');
269 observer.changed = function (id, oldFields, clearedFields, newFields) {
270 if (newFields.status && newFields.status != 'pending') ddp.close();
271 if (newFields.out) {
272 var olen = oldFields.out ? oldFields.out.length : 0;
273 var nlen = newFields.out.length;
274 for (var i = olen; i < nlen; i++) process.stdout.write(newFields.out[i] + '\n');
275 }
276 };
277 });
278 });
279}
280
281function attach() {
282 skale_session(function (err, ddp, isreconnect) {
283 if (err) die('Could not connect:', err);
284 var pkg = JSON.parse(fs.readFileSync('package.json'));
285 var name = pkg.name;
286 ddp.subscribe('etls.withName', [name], function (err) {
287 var etl = ddp.collections.etls[Object.keys(ddp.collections.etls)[0]];
288 if (!etl.running) die('Application is not running, use "skale log" or "skale run"');
289
290 ddp.subscribe('task.withTaskId', [etl.taskId], function () {
291 var task = ddp.collections.tasks[Object.keys(ddp.collections.tasks)[0]];
292 for (var i = 0; i < task.out.length; i++)
293 console.log(task.out[i]);
294 });
295
296 var observer = ddp.observe('tasks');
297 observer.changed = function (id, oldFields, clearedFields, newFields) {
298 if (newFields.status && newFields.status != 'pending') ddp.close();
299 if (newFields.out) {
300 var olen = oldFields.out ? oldFields.out.length : 0;
301 var nlen = newFields.out.length;
302 for (var i = olen; i < nlen; i++) process.stdout.write(newFields.out[i] + '\n');
303 }
304 };
305
306 });
307 });
308}
309
310function status() {
311 skale_session(function (err, ddp, isreconnect) {
312 if (err) die('could node connect:', err);
313 var pkg = JSON.parse(fs.readFileSync('package.json'));
314 var name = pkg.name;
315 ddp.subscribe('etls.withName', [name], function (err, data) {
316 if (!ddp.collections.etls) die('etl not found:', name);
317 var etl = ddp.collections.etls[Object.keys(ddp.collections.etls)[0]];
318 console.log(etl.name, 'status:', etl.running ? 'running' : 'exited');
319 ddp.close();
320 });
321 });
322}
323
324function stop() {
325 skale_session(function (err, ddp, isreconnect) {
326 if (err) die('could node connect:', err);
327 var pkg = JSON.parse(fs.readFileSync('package.json'));
328 var name = pkg.name;
329 ddp.call('etls.reset', [{name: name}], function (err, res) {
330 ddp.close();
331 });
332 });
333}