1 | #!/usr/bin/env node
|
2 |
|
3 |
|
4 |
|
5 | const help=`Usage: skale [options] <command> [<args>]
|
6 |
|
7 | Create, run, deploy clustered node applications
|
8 |
|
9 | Commands:
|
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 |
|
16 | Options:
|
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 |
|
25 | const child_process = require('child_process');
|
26 | const fs = require('fs');
|
27 | const net = require('net');
|
28 |
|
29 | const 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 |
|
38 | var skale_port = 12346;
|
39 |
|
40 | if (argv.h || argv.help) {
|
41 | console.log(help);
|
42 | process.exit();
|
43 | }
|
44 | if (argv.V || argv.version) {
|
45 | var pkg = require('./package');
|
46 | console.log(pkg.name + '-' + pkg.version);
|
47 | process.exit();
|
48 | }
|
49 |
|
50 | const config = load(argv);
|
51 | const proto = config.ssl ? require('https') : require('http');
|
52 | const memory = argv.m || argv.memory || 4000;
|
53 | const worker = argv.w || argv.worker || 2;
|
54 |
|
55 | switch (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 |
|
83 | function 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 |
|
103 | var sc = require('skale-engine').context();
|
104 |
|
105 | sc.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.
|
114 | Pleas change directory to ${name}: "cd ${name}"
|
115 | To run your app: "skale run"
|
116 | To modify your app: edit ${name}.js`)
|
117 | }
|
118 |
|
119 | function die(err) {
|
120 | console.error(help);
|
121 | console.error(err);
|
122 | process.exit(1);
|
123 | }
|
124 |
|
125 | function 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 |
|
137 | function 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 |
|
148 | function 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 |
|
160 | function 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 |
|
167 | function 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 |
|
175 | function 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 |
|
186 | function 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 |
|
215 | function 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 | }
|