UNPKG

6.38 kBJavaScriptView Raw
1#!/usr/bin/env node
2
3// Copyright 2016 Luca-SAS, licensed under the Apache License 2.0
4
5const help=`Usage: skale [options] <command> [<args>]
6
7Create, run, deploy clustered node applications
8
9Commands:
10 create <app> Create a new application
11 run [<args>...] Run application
12 deploy [<args>...] Deploy application
13 status print status of local skale cluster
14 stop Stop local skale cluster
15
16Options:
17 -h, --help Show help
18 -r, --remote run in the cloud instead of locally
19 -m, --memory MB set the memory space limit per worker (default 4000 MB)
20 -w, --worker num set the number of workers (default 2)
21 --reset Restart cluster and cluster log
22 -V, --version Show version
23`;
24
25const child_process = require('child_process');
26const fs = require('fs');
27const net = require('net');
28
29const argv = require('minimist')(process.argv.slice(2), {
30 string: [ 'c', 'config', 'H', 'host', 'k', 'key', 'p', 'port', 'm', 'memory', 'w', 'worker' ],
31 boolean: [ 'h', 'help', 'r', 'remote', 'V', 'version', 'reset' ],
32 default: {
33 H: 'skale.me', 'host': 'skale.me',
34 p: '8888', 'port': 8888
35 }
36});
37
38var skale_port = 12346;
39
40if (argv.h || argv.help) {
41 console.log(help);
42 process.exit();
43}
44if (argv.V || argv.version) {
45 var pkg = require('./package');
46 console.log(pkg.name + '-' + pkg.version);
47 process.exit();
48}
49
50const config = load(argv);
51const proto = config.ssl ? require('https') : require('http');
52const memory = argv.m || argv.memory || 4000;
53const worker = argv.w || argv.worker || 2;
54
55switch (argv._[0]) {
56 case 'create':
57 create(argv._[1]);
58 break;
59 case 'deploy':
60 deploy(argv._.splice(1));
61 break;
62 case 'run':
63 if (argv.r || argv.remote) run_remote(argv._.splice(1));
64 else if (argv.reset) {
65 stop_local_server(function () {
66 fs.rename('skale-server.log', 'skale-server.log.old', function () {
67 run_local(argv._.splice(1));
68 });
69 });
70 } else
71 run_local(argv._.splice(1));
72 break;
73 case 'status':
74 status_local();
75 break;
76 case 'stop':
77 stop_local_server();
78 break;
79 default:
80 die('Error: invalid command: ' + argv._[0]);
81}
82
83function create(name) {
84 console.log('create application ' + name);
85 try {
86 fs.mkdirSync(name);
87 } catch (error) {
88 die('skale create error: ' + error.message);
89 }
90 process.chdir(name);
91 const pkg = {
92 name: name,
93 version: '0.1.0',
94 private: true,
95 keywords: [ 'skale' ],
96 dependencies: {
97 'skale-engine': '^0.4.5'
98 }
99 };
100 fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2));
101 var src = `#!/usr/bin/env node
102
103var sc = require('skale-engine').context();
104
105sc.parallelize(['Hello world'])
106 .collect()
107 .on('data', console.log)
108 .on('end', sc.end);
109`;
110 fs.writeFileSync(name + '.js', src);
111 const npm = child_process.spawnSync('npm', ['install'], {stdio: 'inherit'});
112 if (npm.status) die('skale create error: npm install failed');
113 console.log(`Project ${name} is now ready.
114Pleas change directory to ${name}: "cd ${name}"
115To run your app: "skale run"
116To modify your app: edit ${name}.js`)
117}
118
119function die(err) {
120 console.error(help);
121 console.error(err);
122 process.exit(1);
123}
124
125function load(argv) {
126 var conf = {}, save = false;
127 var path = argv.c || argv.config || process.env.SKALE_CONFIG || process.env.HOME + '/.skalerc';
128 try { conf = JSON.parse(fs.readFileSync(path)); } catch (error) { save = true; }
129 conf.host = argv.H || argv.host || process.env.SKALE_HOST || conf.host;
130 conf.port = argv.p || argv.port || process.env.SKALE_PORT || conf.port;
131 conf.key = argv.k || argv.key || conf.key;
132 conf.ssl = argv.s || argv.ssl || (conf.ssl ? true : false);
133 if (save || argv._[0] == 'init') fs.writeFileSync(path, JSON.stringify(conf, null, 2));
134 return conf;
135}
136
137function start_skale(done) {
138 const out = fs.openSync('skale-server.log', 'a');
139 const err = fs.openSync('skale-server.log', 'a');
140 const child = child_process.spawn('node_modules/skale-engine/bin/server.js', ['-l', worker, '-m', memory], {
141 detached: true,
142 stdio: ['ignore', out, err]
143 });
144 child.unref();
145 try_connect(5, 1000, done);
146}
147
148function try_connect(nb_try, timeout, done) {
149 const sock = net.connect(skale_port);
150 sock.on('connect', function () {
151 sock.end();
152 done(null);
153 });
154 sock.on('error', function (err) {
155 if (--nb_try <= 0) return done('skale-server not ok');
156 setTimeout(function () { try_connect(nb_try, timeout, done); }, timeout);
157 });
158}
159
160function stop_local_server(done) {
161 const child = child_process.execFile('/usr/bin/pgrep', ['-f', 'skale-server ' + skale_port], function (err, pid) {
162 if (pid) process.kill(pid.trim());
163 if (done) done();
164 });
165}
166
167function status_local() {
168 const child = child_process.execFile('/bin/ps', ['ux'], function (err, out) {
169 const lines = out.split(/\r\n|\r|\n/);
170 for (var i = 0; i < lines.length; i++)
171 if (i == 0 || lines[i].match(/ skale-/)) console.log(lines[i].trim());
172 });
173}
174
175function run_local(args) {
176 const pkg = JSON.parse(fs.readFileSync('package.json'));
177 const cmd = pkg.name + '.js';
178 args.splice(0, 0, cmd);
179 try_connect(0, 0, function (err) {
180 if (!err) return run_app();
181 start_skale(run_app);
182 });
183 function run_app() { child = child_process.spawn('node', args, {stdio: 'inherit'}); }
184}
185
186function deploy(args) {
187 const pkg = JSON.parse(fs.readFileSync('package.json'));
188 fs.readFile(pkg.name + '.js', {encoding: 'utf8'}, function (err, data) {
189 if (err) throw err;
190
191 const postdata = JSON.stringify({pkg: pkg, src: data, args: args});
192
193 const options = {
194 hostname: config.host,
195 port: config.port,
196 path: '/deploy',
197 method: 'POST',
198 headers: {
199 'X-Auth': config.key,
200 'Content-Type': 'application/json',
201 'Content-Length': Buffer.byteLength(postdata)
202 }
203 };
204
205 const req = proto.request(options, function (res) {
206 res.setEncoding('utf8');
207 res.pipe(process.stdout);
208 });
209
210 req.on('error', function (err) {throw err;});
211 req.end(postdata);
212 });
213}
214
215function run_remote(args) {
216 const name = process.cwd().split('/').pop();
217 const postdata = JSON.stringify({name: name, args: args});
218
219 const options = {
220 hostname: config.host,
221 port: config.port,
222 path: '/run',
223 method: 'POST',
224 headers: {
225 'X-Auth': config.key,
226 'Content-Type': 'application/json',
227 'Content-Length': Buffer.byteLength(postdata)
228 }
229 };
230
231 const req = proto.request(options, function (res) {
232 res.setEncoding('utf8');
233 res.pipe(process.stdout);
234 });
235
236 req.on('error', function (err) {throw err;});
237 req.end(postdata);
238}