cloudron 5.12.0 → 5.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cloudron +2 -2
- package/bin/cloudron-appstore +4 -0
- package/eslint.config.js +21 -0
- package/package.json +9 -14
- package/src/actions.js +78 -92
- package/src/appstore-actions.js +58 -28
- package/src/backup-tools.js +9 -7
- package/src/build-actions.js +22 -22
- package/src/line-stream.js +30 -0
- package/src/readline.js +21 -0
- package/src/superagent.js +222 -0
- package/test/test.js +1 -1
package/src/actions.js
CHANGED
|
@@ -4,25 +4,22 @@ const assert = require('assert'),
|
|
|
4
4
|
config = require('./config.js'),
|
|
5
5
|
ejs = require('ejs'),
|
|
6
6
|
{ exit, locateManifest } = require('./helper.js'),
|
|
7
|
-
EventSource = require('eventsource'),
|
|
7
|
+
{ EventSource } = require('eventsource'),
|
|
8
8
|
fs = require('fs'),
|
|
9
9
|
https = require('https'),
|
|
10
|
+
LineStream = require('./line-stream.js'),
|
|
10
11
|
manifestFormat = require('cloudron-manifestformat'),
|
|
11
12
|
os = require('os'),
|
|
12
13
|
path = require('path'),
|
|
13
|
-
|
|
14
|
-
ProgressStream = require('progress-stream'),
|
|
15
|
-
readlineSync = require('readline-sync'),
|
|
14
|
+
readline = require('./readline.js'),
|
|
16
15
|
safe = require('safetydance'),
|
|
17
16
|
spawn = require('child_process').spawn,
|
|
18
17
|
semver = require('semver'),
|
|
19
|
-
|
|
20
|
-
superagent = require('superagent'),
|
|
18
|
+
superagent = require('./superagent.js'),
|
|
21
19
|
Table = require('easy-table'),
|
|
22
20
|
tar = require('tar-fs'),
|
|
23
21
|
timers = require('timers/promises'),
|
|
24
|
-
zlib = require('zlib')
|
|
25
|
-
_ = require('underscore');
|
|
22
|
+
zlib = require('zlib');
|
|
26
23
|
|
|
27
24
|
exports = module.exports = {
|
|
28
25
|
list,
|
|
@@ -82,7 +79,7 @@ function createRequest(method, apiPath, options) {
|
|
|
82
79
|
let url = `https://${adminFqdn}${apiPath}`;
|
|
83
80
|
if (url.includes('?')) url += '&'; else url += '?';
|
|
84
81
|
url += `access_token=${token}`;
|
|
85
|
-
const request = superagent(method, url);
|
|
82
|
+
const request = superagent.request(method, url);
|
|
86
83
|
if (!rejectUnauthorized) request.disableTLSCerts();
|
|
87
84
|
request.retry(3);
|
|
88
85
|
request.ok(() => true);
|
|
@@ -91,9 +88,9 @@ function createRequest(method, apiPath, options) {
|
|
|
91
88
|
|
|
92
89
|
// error for the request module
|
|
93
90
|
function requestError(response) {
|
|
94
|
-
if (response.
|
|
91
|
+
if (response.status === 401) return 'Invalid token. Use cloudron login again.';
|
|
95
92
|
|
|
96
|
-
return `${response.
|
|
93
|
+
return `${response.status} message: ${response.body.message || JSON.stringify(response.body)}`; // body is sometimes just a string like in 401
|
|
97
94
|
}
|
|
98
95
|
|
|
99
96
|
async function selectDomain(location, options) {
|
|
@@ -103,7 +100,7 @@ async function selectDomain(location, options) {
|
|
|
103
100
|
const { adminFqdn } = requestOptions(options);
|
|
104
101
|
|
|
105
102
|
const response = await createRequest('GET', '/api/v1/domains', options);
|
|
106
|
-
if (response.
|
|
103
|
+
if (response.status !== 200) throw new Error(`Failed to list domains: ${requestError(response)}`);
|
|
107
104
|
|
|
108
105
|
const domains = response.body.domains;
|
|
109
106
|
|
|
@@ -137,7 +134,7 @@ async function stopActiveTask(app, options) {
|
|
|
137
134
|
console.log(`Stopping app's current active task ${app.taskId}`);
|
|
138
135
|
|
|
139
136
|
const response = await createRequest('POST', `/api/v1/tasks/${app.taskId}/stop`, options);
|
|
140
|
-
if (response.
|
|
137
|
+
if (response.status !== 204) throw `Failed to stop active task: ${requestError(response)}`;
|
|
141
138
|
}
|
|
142
139
|
|
|
143
140
|
async function selectAppWithRepository(repository, options) {
|
|
@@ -145,7 +142,7 @@ async function selectAppWithRepository(repository, options) {
|
|
|
145
142
|
assert.strictEqual(typeof options, 'object');
|
|
146
143
|
|
|
147
144
|
const response = await createRequest('GET', '/api/v1/apps', options);
|
|
148
|
-
if (response.
|
|
145
|
+
if (response.status !== 200) throw new Error(`Failed to install app: ${requestError(response)}`);
|
|
149
146
|
|
|
150
147
|
const matchingApps = response.body.apps.filter(function (app) {
|
|
151
148
|
return !app.appStoreId && app.manifest.dockerImage.startsWith(repository); // never select apps from the store
|
|
@@ -163,7 +160,7 @@ async function selectAppWithRepository(repository, options) {
|
|
|
163
160
|
|
|
164
161
|
let index = -1;
|
|
165
162
|
while (true) {
|
|
166
|
-
index = parseInt(
|
|
163
|
+
index = parseInt(await readline.question('Choose app [0-' + (matchingApps.length-1) + ']: ', {}), 10);
|
|
167
164
|
if (isNaN(index) || index < 0 || index > matchingApps.length-1) console.log('Invalid selection');
|
|
168
165
|
else break;
|
|
169
166
|
}
|
|
@@ -193,12 +190,12 @@ async function getApp(options) {
|
|
|
193
190
|
return result;
|
|
194
191
|
} else if (app.match(/.{8}-.{4}-.{4}-.{4}-.{8}/)) { // it is an id
|
|
195
192
|
const response = await createRequest('GET', `/api/v1/apps/${app}`, options);
|
|
196
|
-
if (response.
|
|
193
|
+
if (response.status !== 200) throw new Error(`Failed to get app: ${requestError(response)}`);
|
|
197
194
|
|
|
198
195
|
return response.body;
|
|
199
196
|
} else { // it is a location
|
|
200
197
|
const response = await createRequest('GET', '/api/v1/apps', options);
|
|
201
|
-
if (response.
|
|
198
|
+
if (response.status !== 200) throw new Error(`Failed to get apps: ${requestError(response)}`);
|
|
202
199
|
|
|
203
200
|
const match = response.body.apps.filter(function (m) { return m.subdomain === app || m.location === app || m.fqdn === app; });
|
|
204
201
|
if (match.length == 0) throw new Error(`App at location ${app} not found`);
|
|
@@ -218,7 +215,7 @@ async function waitForHealthy(appId, options) {
|
|
|
218
215
|
while (true) {
|
|
219
216
|
await timers.setTimeout(1000);
|
|
220
217
|
const response = await createRequest('GET', `/api/v1/apps/${appId}`, options);
|
|
221
|
-
if (response.
|
|
218
|
+
if (response.status !== 200) throw new Error(`Failed to get app: ${requestError(response)}`);
|
|
222
219
|
|
|
223
220
|
// do not check installation state here. it can be pending_backup etc (this is a bug in box code)
|
|
224
221
|
if (response.body.health === 'healthy') return;
|
|
@@ -241,7 +238,7 @@ async function waitForTask(taskId, options) {
|
|
|
241
238
|
|
|
242
239
|
while (true) {
|
|
243
240
|
const response = await createRequest('GET', `/api/v1/tasks/${taskId}`, options);
|
|
244
|
-
if (response.
|
|
241
|
+
if (response.status !== 200) throw new Error(`Failed to get task: ${requestError(response)}`);
|
|
245
242
|
|
|
246
243
|
if (!response.body.active) return response.body;
|
|
247
244
|
|
|
@@ -275,7 +272,7 @@ async function waitForFinishInstallation(appId, taskId, options) {
|
|
|
275
272
|
await waitForTask(taskId, options);
|
|
276
273
|
|
|
277
274
|
const response = await createRequest('GET', `/api/v1/apps/${appId}`, options);
|
|
278
|
-
if (response.
|
|
275
|
+
if (response.status !== 200) throw new Error(`Failed to get app: ${requestError(response)}`);
|
|
279
276
|
|
|
280
277
|
if (response.body.installationState !== 'installed') throw new Error(`Installation failed: ${response.body.error ? response.body.error.message : ''}`);
|
|
281
278
|
|
|
@@ -292,7 +289,7 @@ async function waitForFinishBackup(appId, taskId, options) {
|
|
|
292
289
|
if (result.error) throw new Error(`Backup failed: ${result.error.message}`);
|
|
293
290
|
|
|
294
291
|
const response = await createRequest('GET', `/api/v1/apps/${appId}`, options);
|
|
295
|
-
if (response.
|
|
292
|
+
if (response.status !== 200) throw new Error(`Failed to get app: ${requestError(response)}`);
|
|
296
293
|
}
|
|
297
294
|
|
|
298
295
|
async function stopApp(app, options) {
|
|
@@ -300,7 +297,7 @@ async function stopApp(app, options) {
|
|
|
300
297
|
assert.strictEqual(typeof options, 'object');
|
|
301
298
|
|
|
302
299
|
const response = await createRequest('POST', `/api/v1/apps/${app.id}/stop`, options);
|
|
303
|
-
if (response.
|
|
300
|
+
if (response.status !== 202) throw `Failed to stop app: ${requestError(response)}`;
|
|
304
301
|
|
|
305
302
|
await waitForTask(response.body.taskId, options);
|
|
306
303
|
}
|
|
@@ -310,7 +307,7 @@ async function startApp(app, options) {
|
|
|
310
307
|
assert.strictEqual(typeof options, 'object');
|
|
311
308
|
|
|
312
309
|
const response = await createRequest('POST', `/api/v1/apps/${app.id}/start`, options);
|
|
313
|
-
if (response.
|
|
310
|
+
if (response.status !== 202) throw `Failed to start app: ${requestError(response)}`;
|
|
314
311
|
|
|
315
312
|
await waitForTask(response.body.taskId, options);
|
|
316
313
|
}
|
|
@@ -320,7 +317,7 @@ async function restartApp(app, options) {
|
|
|
320
317
|
assert.strictEqual(typeof options, 'object');
|
|
321
318
|
|
|
322
319
|
const response = await createRequest('POST', `/api/v1/apps/${app.id}/restart`, options);
|
|
323
|
-
if (response.
|
|
320
|
+
if (response.status !== 202) throw `Failed to restart app: ${requestError(response)}`;
|
|
324
321
|
|
|
325
322
|
await waitForTask(response.body.taskId, options);
|
|
326
323
|
}
|
|
@@ -334,7 +331,7 @@ async function authenticate(adminFqdn, username, password, options) {
|
|
|
334
331
|
let totpToken;
|
|
335
332
|
|
|
336
333
|
const { rejectUnauthorized, askForTotpToken } = options;
|
|
337
|
-
if (askForTotpToken) totpToken =
|
|
334
|
+
if (askForTotpToken) totpToken = await readline.question('2FA Token: ', {});
|
|
338
335
|
|
|
339
336
|
const request = superagent.post(`https://${adminFqdn}/api/v1/auth/login`)
|
|
340
337
|
.timeout(60000)
|
|
@@ -354,13 +351,13 @@ async function authenticate(adminFqdn, username, password, options) {
|
|
|
354
351
|
}
|
|
355
352
|
}
|
|
356
353
|
|
|
357
|
-
if (response.
|
|
354
|
+
if (response.status !== 200) throw new Error(`Login failed: Status code: ${requestError(response)}`);
|
|
358
355
|
|
|
359
356
|
return response.body.accessToken;
|
|
360
357
|
}
|
|
361
358
|
|
|
362
359
|
async function login(adminFqdn, localOptions, cmd) {
|
|
363
|
-
if (!adminFqdn) adminFqdn =
|
|
360
|
+
if (!adminFqdn) adminFqdn = await readline.question('Cloudron Domain (e.g. my.example.com): ', {});
|
|
364
361
|
if (!adminFqdn) return exit('');
|
|
365
362
|
|
|
366
363
|
if (adminFqdn.indexOf('https://') === 0) adminFqdn = adminFqdn.slice('https://'.length);
|
|
@@ -389,8 +386,8 @@ async function login(adminFqdn, localOptions, cmd) {
|
|
|
389
386
|
}
|
|
390
387
|
|
|
391
388
|
if (!token) {
|
|
392
|
-
const username = options.username ||
|
|
393
|
-
const password = options.password ||
|
|
389
|
+
const username = options.username || await readline.question('Username: ', {});
|
|
390
|
+
const password = options.password || await readline.question('Password: ', { noEchoBack: true });
|
|
394
391
|
|
|
395
392
|
const [error, result] = await safe(authenticate(adminFqdn, username, password, { rejectUnauthorized, askForTotpToken: false }));
|
|
396
393
|
if (error) return exit(`Failed to login: ${error.message}`);
|
|
@@ -428,7 +425,7 @@ async function list(localOptions, cmd) {
|
|
|
428
425
|
const options = cmd.optsWithGlobals();
|
|
429
426
|
const [error, response] = await safe(createRequest('GET', '/api/v1/apps', options));
|
|
430
427
|
if (error) return exit(error);
|
|
431
|
-
if (response.
|
|
428
|
+
if (response.status !== 200) return exit(`Failed to list apps: ${requestError(response)}`);
|
|
432
429
|
|
|
433
430
|
let apps = response.body.apps;
|
|
434
431
|
|
|
@@ -450,7 +447,7 @@ async function list(localOptions, cmd) {
|
|
|
450
447
|
|
|
451
448
|
const response = result.value;
|
|
452
449
|
|
|
453
|
-
if (response.
|
|
450
|
+
if (response.status !== 200) return exit(`Failed to list app: ${requestError(response)}`);
|
|
454
451
|
response.body.location = response.body.location || response.body.subdomain; // LEGACY support
|
|
455
452
|
|
|
456
453
|
const detailedApp = response.body;
|
|
@@ -458,7 +455,7 @@ async function list(localOptions, cmd) {
|
|
|
458
455
|
t.cell('Id', detailedApp.id);
|
|
459
456
|
t.cell('Location', detailedApp.fqdn);
|
|
460
457
|
t.cell('Manifest Id', (detailedApp.manifest.id || 'customapp') + '@' + detailedApp.manifest.version);
|
|
461
|
-
|
|
458
|
+
let prettyState;
|
|
462
459
|
if (detailedApp.installationState === 'installed') {
|
|
463
460
|
prettyState = (detailedApp.debugMode ? 'debug' : detailedApp.runState);
|
|
464
461
|
} else if (detailedApp.installationState === 'error') {
|
|
@@ -481,18 +478,19 @@ async function querySecondaryDomains(app, manifest, options) {
|
|
|
481
478
|
|
|
482
479
|
for (const env in manifest.httpPorts) {
|
|
483
480
|
const defaultDomain = (app && app.secondaryDomains && app.secondaryDomains[env]) ? app.secondaryDomains[env] : (manifest.httpPorts[env].defaultValue || '');
|
|
484
|
-
const input =
|
|
481
|
+
const input = await readline.question(`${manifest.httpPorts[env].description} (default: "${defaultDomain}"): `, {});
|
|
485
482
|
secondaryDomains[env] = await selectDomain(input, options);
|
|
486
483
|
}
|
|
487
484
|
return secondaryDomains;
|
|
488
485
|
}
|
|
489
486
|
|
|
490
|
-
function queryPortBindings(app, manifest) {
|
|
487
|
+
async function queryPortBindings(app, manifest) {
|
|
491
488
|
const portBindings = {};
|
|
492
|
-
const allPorts =
|
|
489
|
+
const allPorts = Object.assign({}, manifest.tcpPorts, manifest.udpPorts);
|
|
490
|
+
|
|
493
491
|
for (const env in allPorts) {
|
|
494
492
|
const defaultPort = (app && app.portBindings && app.portBindings[env]) ? app.portBindings[env] : (allPorts[env].defaultValue || '');
|
|
495
|
-
const port =
|
|
493
|
+
const port = await readline.question(allPorts[env].description + ' (default ' + env + '=' + defaultPort + '. "x" to disable): ', {});
|
|
496
494
|
if (port === '') {
|
|
497
495
|
portBindings[env] = defaultPort;
|
|
498
496
|
} else if (isNaN(parseInt(port, 10))) {
|
|
@@ -515,7 +513,7 @@ async function downloadManifest(appstoreId) {
|
|
|
515
513
|
|
|
516
514
|
const [error, response] = await safe(superagent.get(url).ok(() => true));
|
|
517
515
|
if (error) throw new Error(`Failed to list apps from appstore: ${error.message}`);
|
|
518
|
-
if (response.
|
|
516
|
+
if (response.status !== 200) throw new Error(`Failed to get app info from store: ${requestError(response)}`);
|
|
519
517
|
|
|
520
518
|
return { manifest: response.body.manifest, manifestFilePath: null /* manifest file path */ };
|
|
521
519
|
}
|
|
@@ -559,7 +557,7 @@ async function install(localOptions, cmd) {
|
|
|
559
557
|
manifest.dockerImage = image;
|
|
560
558
|
}
|
|
561
559
|
|
|
562
|
-
const location = options.location ||
|
|
560
|
+
const location = options.location || await readline.question('Location: ', {});
|
|
563
561
|
if (!location) return exit('');
|
|
564
562
|
|
|
565
563
|
const domainObject = await selectDomain(location, options);
|
|
@@ -604,10 +602,10 @@ async function install(localOptions, cmd) {
|
|
|
604
602
|
ports[tmp[0]] = parseInt(tmp[1], 10);
|
|
605
603
|
});
|
|
606
604
|
} else {
|
|
607
|
-
ports = queryPortBindings(null /* existing app */, manifest);
|
|
605
|
+
ports = await queryPortBindings(null /* existing app */, manifest);
|
|
608
606
|
}
|
|
609
607
|
} else { // just put in defaults
|
|
610
|
-
const allPorts =
|
|
608
|
+
const allPorts = Object.assign({}, manifest.tcpPorts, manifest.udpPorts);
|
|
611
609
|
for (const portName in allPorts) {
|
|
612
610
|
ports[portName] = allPorts[portName].defaultValue;
|
|
613
611
|
}
|
|
@@ -649,7 +647,7 @@ async function install(localOptions, cmd) {
|
|
|
649
647
|
|
|
650
648
|
const request = createRequest('POST', '/api/v1/apps/install', options);
|
|
651
649
|
const response = await request.send(data);
|
|
652
|
-
if (response.
|
|
650
|
+
if (response.status !== 202) return exit(`Failed to install app: ${requestError(response)}`);
|
|
653
651
|
|
|
654
652
|
const appId = response.body.id;
|
|
655
653
|
|
|
@@ -669,7 +667,7 @@ async function setLocation(localOptions, cmd) {
|
|
|
669
667
|
const app = await getApp(options);
|
|
670
668
|
if (!app) return exit(NO_APP_FOUND_ERROR_STRING);
|
|
671
669
|
|
|
672
|
-
if (!options.location) options.location =
|
|
670
|
+
if (!options.location) options.location = await readline.question(`Enter new location (default: ${app.fqdn}): `, { });
|
|
673
671
|
const location = options.location || app.subdomain;
|
|
674
672
|
|
|
675
673
|
const domainObject = await selectDomain(location, options);
|
|
@@ -734,7 +732,7 @@ async function setLocation(localOptions, cmd) {
|
|
|
734
732
|
ports[tmp[0]] = parseInt(tmp[1], 10);
|
|
735
733
|
});
|
|
736
734
|
} else {
|
|
737
|
-
ports = queryPortBindings(app, app.manifest);
|
|
735
|
+
ports = await queryPortBindings(app, app.manifest);
|
|
738
736
|
}
|
|
739
737
|
|
|
740
738
|
for (const port in ports) {
|
|
@@ -746,7 +744,7 @@ async function setLocation(localOptions, cmd) {
|
|
|
746
744
|
|
|
747
745
|
const request = createRequest('POST', `/api/v1/apps/${app.id}/configure/location`, options);
|
|
748
746
|
const response = await request.send(data);
|
|
749
|
-
if (response.
|
|
747
|
+
if (response.status !== 202) return exit(`Failed to configure app: ${requestError(response)}`);
|
|
750
748
|
|
|
751
749
|
await waitForTask(response.body.taskId, options);
|
|
752
750
|
await waitForHealthy(app.id, options);
|
|
@@ -788,10 +786,10 @@ async function update(localOptions, cmd) {
|
|
|
788
786
|
|
|
789
787
|
if (app.error && (app.error.installationState === 'pending_install')) { // install had failed. call repair to re-install
|
|
790
788
|
apiPath = `/api/v1/apps/${app.id}/repair`;
|
|
791
|
-
data =
|
|
789
|
+
data = Object.assign(data, { manifest });
|
|
792
790
|
} else {
|
|
793
791
|
apiPath = `/api/v1/apps/${app.id}/update`;
|
|
794
|
-
data =
|
|
792
|
+
data = Object.assign(data, {
|
|
795
793
|
appStoreId: options.appstoreId || '', // note case change
|
|
796
794
|
manifest: manifest,
|
|
797
795
|
skipBackup: !options.backup,
|
|
@@ -802,7 +800,7 @@ async function update(localOptions, cmd) {
|
|
|
802
800
|
|
|
803
801
|
const request = createRequest('POST', apiPath, options);
|
|
804
802
|
const response = await request.send(data);
|
|
805
|
-
if (response.
|
|
803
|
+
if (response.status !== 202) return exit(`Failed to update app: ${requestError(response)}`);
|
|
806
804
|
|
|
807
805
|
process.stdout.write('\n => ' + 'Waiting for app to be updated ');
|
|
808
806
|
|
|
@@ -831,7 +829,7 @@ async function debug(args, localOptions, cmd) {
|
|
|
831
829
|
|
|
832
830
|
const request = createRequest('POST', `/api/v1/apps/${app.id}/configure/debug_mode`, options);
|
|
833
831
|
const response = await request.send(data);
|
|
834
|
-
if (response.
|
|
832
|
+
if (response.status !== 202) return exit(`Failed to set debug mode: ${requestError(response)}`);
|
|
835
833
|
|
|
836
834
|
await waitForTask(response.body.taskId, options);
|
|
837
835
|
|
|
@@ -845,7 +843,7 @@ async function debug(args, localOptions, cmd) {
|
|
|
845
843
|
|
|
846
844
|
const request2 = createRequest('POST', `/api/v1/apps/${app.id}/configure/memory_limit`, options);
|
|
847
845
|
const response2 = await request2.send({ memoryLimit });
|
|
848
|
-
if (response2.
|
|
846
|
+
if (response2.status !== 202) return exit(`Failed to set memory limit: ${requestError(response2)}`);
|
|
849
847
|
|
|
850
848
|
await waitForTask(response2.body.taskId, options);
|
|
851
849
|
console.log('\n\nDone');
|
|
@@ -865,7 +863,7 @@ async function repair(localOptions, cmd) {
|
|
|
865
863
|
|
|
866
864
|
const request = createRequest('POST', `/api/v1/apps/${app.id}/repair`, options);
|
|
867
865
|
const response = await request.send(data);
|
|
868
|
-
if (response.
|
|
866
|
+
if (response.status !== 202) return exit(`Failed to set repair mode: ${requestError(response)}`);
|
|
869
867
|
|
|
870
868
|
process.stdout.write('\n => ' + 'Waiting for app to be repaired ');
|
|
871
869
|
|
|
@@ -898,13 +896,13 @@ async function uninstall(localOptions, cmd) {
|
|
|
898
896
|
await stopActiveTask(app, options);
|
|
899
897
|
|
|
900
898
|
const response = await createRequest('POST', `/api/v1/apps/${app.id}/uninstall`, options);
|
|
901
|
-
if (response.
|
|
899
|
+
if (response.status !== 202) return exit(`Failed to uninstall app: ${requestError(response)}`);
|
|
902
900
|
|
|
903
901
|
process.stdout.write('\n => ' + 'Waiting for app to be uninstalled ');
|
|
904
902
|
|
|
905
903
|
await waitForTask(response.body.taskId, options);
|
|
906
904
|
const response2 = await createRequest('GET', `/api/v1/apps/${app.id}`, options);
|
|
907
|
-
if (response2.
|
|
905
|
+
if (response2.status === 404) {
|
|
908
906
|
console.log('\n\nApp %s successfully uninstalled.', app.fqdn);
|
|
909
907
|
} else if (response2.body.installationState === 'error') {
|
|
910
908
|
console.log('\n\nApp uninstallation failed.\n');
|
|
@@ -954,13 +952,13 @@ async function logs(localOptions, cmd) {
|
|
|
954
952
|
|
|
955
953
|
if (tail) {
|
|
956
954
|
const url = `${apiPath}?access_token=${token}&lines=10&format=json`;
|
|
957
|
-
|
|
955
|
+
const es = new EventSource(url, { rejectUnauthorized }); // not sure why this is needed
|
|
958
956
|
|
|
959
|
-
es.
|
|
957
|
+
es.addEventListener('message', function (e) { // e { type, data, lastEventId }. lastEventId is the timestamp
|
|
960
958
|
logPrinter(JSON.parse(e.data));
|
|
961
959
|
});
|
|
962
960
|
|
|
963
|
-
es.
|
|
961
|
+
es.addEventListener('error', function (error) {
|
|
964
962
|
if (error.status === 401) return exit('Please login first');
|
|
965
963
|
if (error.status === 412) exit('Logs currently not available.');
|
|
966
964
|
exit(error);
|
|
@@ -970,17 +968,17 @@ async function logs(localOptions, cmd) {
|
|
|
970
968
|
|
|
971
969
|
const req = superagent.get(url, { rejectUnauthorized });
|
|
972
970
|
req.on('response', function (response) {
|
|
973
|
-
if (response.
|
|
971
|
+
if (response.status !== 200) return exit(`Failed to get logs: ${requestError(response)}`);
|
|
974
972
|
});
|
|
975
973
|
req.on('error', (error) => exit(`Pipe error: ${error.message}`));
|
|
976
974
|
|
|
977
|
-
const
|
|
978
|
-
|
|
979
|
-
.on('
|
|
975
|
+
const lineStream = new LineStream();
|
|
976
|
+
lineStream
|
|
977
|
+
.on('line', (line) => { logPrinter(JSON.parse(line)); } )
|
|
980
978
|
.on('error', (error) => exit(`JSON parse error: ${error.message}`))
|
|
981
979
|
.on('end', process.exit);
|
|
982
980
|
|
|
983
|
-
req.pipe(
|
|
981
|
+
req.pipe(lineStream);
|
|
984
982
|
}
|
|
985
983
|
}
|
|
986
984
|
|
|
@@ -1005,13 +1003,13 @@ async function inspect(localOptions, cmd) {
|
|
|
1005
1003
|
try {
|
|
1006
1004
|
const options = cmd.optsWithGlobals();
|
|
1007
1005
|
const response = await createRequest('GET', '/api/v1/apps', options);
|
|
1008
|
-
if (response.
|
|
1006
|
+
if (response.status !== 200) return exit(`Failed to list apps: ${requestError(response)}`);
|
|
1009
1007
|
|
|
1010
1008
|
const apps = [];
|
|
1011
1009
|
|
|
1012
1010
|
for (const app of response.body.apps) {
|
|
1013
1011
|
const response2 = await createRequest('GET', `/api/v1/apps/${app.id}`, options);
|
|
1014
|
-
if (response2.
|
|
1012
|
+
if (response2.status !== 200) return exit(`Failed to list app: ${requestError(response2)}`);
|
|
1015
1013
|
response2.body.location = response2.body.location || response2.body.subdomain; // LEGACY support
|
|
1016
1014
|
apps.push(response2.body);
|
|
1017
1015
|
}
|
|
@@ -1072,7 +1070,7 @@ async function backupCreate(localOptions, cmd) {
|
|
|
1072
1070
|
if (!app) return exit(NO_APP_FOUND_ERROR_STRING);
|
|
1073
1071
|
|
|
1074
1072
|
const response = await createRequest('POST', `/api/v1/apps/${app.id}/backup`, options);
|
|
1075
|
-
if (response.
|
|
1073
|
+
if (response.status !== 202) return exit(`Failed to start backup: ${requestError(response)}`);
|
|
1076
1074
|
|
|
1077
1075
|
// FIXME: this should be waitForHealthCheck but the box code incorrectly modifies the installationState
|
|
1078
1076
|
await waitForFinishBackup(app.id, response.body.taskId, options);
|
|
@@ -1089,7 +1087,7 @@ async function backupList(localOptions, cmd) {
|
|
|
1089
1087
|
if (!app) return exit(NO_APP_FOUND_ERROR_STRING);
|
|
1090
1088
|
|
|
1091
1089
|
const response = await createRequest('GET', `/api/v1/apps/${app.id}/backups`, options);
|
|
1092
|
-
if (response.
|
|
1090
|
+
if (response.status !== 200) return exit(`Failed to list backups: ${requestError(response)}`);
|
|
1093
1091
|
|
|
1094
1092
|
if (options.raw) return console.log(JSON.stringify(response.body.backups, null, 4));
|
|
1095
1093
|
|
|
@@ -1100,7 +1098,7 @@ async function backupList(localOptions, cmd) {
|
|
|
1100
1098
|
return backup;
|
|
1101
1099
|
}).sort(function (a, b) { return b.creationTime - a.creationTime; });
|
|
1102
1100
|
|
|
1103
|
-
|
|
1101
|
+
const t = new Table();
|
|
1104
1102
|
|
|
1105
1103
|
response.body.backups.forEach(function (backup) {
|
|
1106
1104
|
t.cell('Id', backup.id);
|
|
@@ -1119,7 +1117,7 @@ async function backupList(localOptions, cmd) {
|
|
|
1119
1117
|
|
|
1120
1118
|
async function getLastBackup(app, options) {
|
|
1121
1119
|
const response = await createRequest('GET', `/api/v1/apps/${app.id}/backups`, options);
|
|
1122
|
-
if (response.
|
|
1120
|
+
if (response.status !== 200) throw new Error(`Failed to list backups: ${requestError(response)}`);
|
|
1123
1121
|
if (response.body.backups.length === 0) throw new Error('No backups');
|
|
1124
1122
|
|
|
1125
1123
|
response.body.backups = response.body.backups.map(function (backup) {
|
|
@@ -1145,7 +1143,7 @@ async function restore(localOptions, cmd) {
|
|
|
1145
1143
|
|
|
1146
1144
|
const request = createRequest('POST', `/api/v1/apps/${app.id}/restore`, options);
|
|
1147
1145
|
const response = await request.send({ backupId });
|
|
1148
|
-
if (response.
|
|
1146
|
+
if (response.status !== 202) return exit(`Failed to restore app: ${requestError(response)}`);
|
|
1149
1147
|
|
|
1150
1148
|
// FIXME: this should be waitForHealthCheck but the box code incorrectly modifies the installationState
|
|
1151
1149
|
await waitForFinishInstallation(app.id, response.body.taskId, options);
|
|
@@ -1204,7 +1202,7 @@ async function importApp(localOptions, cmd) {
|
|
|
1204
1202
|
}
|
|
1205
1203
|
|
|
1206
1204
|
const response = await createRequest('POST', `/api/v1/apps/${app.id}/import`, options);
|
|
1207
|
-
if (response.
|
|
1205
|
+
if (response.status !== 202) return exit(`Failed to import app: ${requestError(response)}`);
|
|
1208
1206
|
|
|
1209
1207
|
await waitForFinishInstallation(app.id, response.body.taskId, options);
|
|
1210
1208
|
console.log('\n\nApp is restored');
|
|
@@ -1225,7 +1223,7 @@ async function exportApp(localOptions, cmd) {
|
|
|
1225
1223
|
|
|
1226
1224
|
const request = createRequest('POST', `/api/v1/apps/${app.id}/export`, options);
|
|
1227
1225
|
const response = await request.send(data);
|
|
1228
|
-
if (response.
|
|
1226
|
+
if (response.status !== 202) return exit(`Failed to export app: ${requestError(response)}`);
|
|
1229
1227
|
|
|
1230
1228
|
await waitForFinishInstallation(app.id, response.body.taskId, options);
|
|
1231
1229
|
console.log('\n\nApp is exported');
|
|
@@ -1242,9 +1240,9 @@ async function clone(localOptions, cmd) {
|
|
|
1242
1240
|
|
|
1243
1241
|
if (!options.backup) return exit('Use --backup to specify the backup id');
|
|
1244
1242
|
|
|
1245
|
-
const location = options.location ||
|
|
1243
|
+
const location = options.location || await readline.question('Cloned app location: ', {});
|
|
1246
1244
|
const secondaryDomains = await querySecondaryDomains(app, app.manifest, options);
|
|
1247
|
-
const ports = queryPortBindings(app, app.manifest);
|
|
1245
|
+
const ports = await queryPortBindings(app, app.manifest);
|
|
1248
1246
|
const backupId = options.backup;
|
|
1249
1247
|
|
|
1250
1248
|
const domainObject = await selectDomain(location, options);
|
|
@@ -1258,7 +1256,7 @@ async function clone(localOptions, cmd) {
|
|
|
1258
1256
|
};
|
|
1259
1257
|
const request = createRequest('POST', `/api/v1/apps/${app.id}/clone`, options);
|
|
1260
1258
|
const response = await request.send(data);
|
|
1261
|
-
if (response.
|
|
1259
|
+
if (response.status !== 201) return exit(`Failed to list apps: ${requestError(response)}`);
|
|
1262
1260
|
|
|
1263
1261
|
// FIXME: this should be waitForHealthCheck but the box code incorrectly modifies the installationState
|
|
1264
1262
|
console.log('App cloned as id ' + response.body.id);
|
|
@@ -1319,12 +1317,12 @@ async function exec(args, localOptions, cmd) {
|
|
|
1319
1317
|
|
|
1320
1318
|
const request = createRequest('POST', `/api/v1/apps/${app.id}/exec`, options);
|
|
1321
1319
|
const response = await request.send({ cmd: args, tty, lang });
|
|
1322
|
-
if (response.
|
|
1320
|
+
if (response.status !== 200) return exit(`Failed to create exec: ${requestError(response)}`);
|
|
1323
1321
|
const execId = response.body.id;
|
|
1324
1322
|
|
|
1325
1323
|
async function exitWithCode() {
|
|
1326
1324
|
const response2 = await createRequest('GET', `/api/v1/apps/${app.id}/exec/${execId}`, options);
|
|
1327
|
-
if (response2.
|
|
1325
|
+
if (response2.status !== 200) return exit(`Failed to get exec code: ${requestError(response2)}`);
|
|
1328
1326
|
|
|
1329
1327
|
process.exit(response2.body.exitCode);
|
|
1330
1328
|
}
|
|
@@ -1417,19 +1415,7 @@ function push(localDir, remote, localOptions, cmd) {
|
|
|
1417
1415
|
if (local === '-') {
|
|
1418
1416
|
localOptions._stdin = process.stdin;
|
|
1419
1417
|
} else if (stat) {
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
localOptions._stdin = progress;
|
|
1423
|
-
fs.createReadStream(local).pipe(progress);
|
|
1424
|
-
|
|
1425
|
-
const bar = new ProgressBar('Uploading [:bar] :percent: :etas', {
|
|
1426
|
-
complete: '=',
|
|
1427
|
-
incomplete: ' ',
|
|
1428
|
-
width: 100,
|
|
1429
|
-
total: stat.size
|
|
1430
|
-
});
|
|
1431
|
-
|
|
1432
|
-
progress.on('progress', function (p) { bar.update(p.percentage / 100); /* bar.tick(p.transferred - bar.curr); */ });
|
|
1418
|
+
localOptions._stdin = fs.createReadStream(local);
|
|
1433
1419
|
} else {
|
|
1434
1420
|
exit('local file ' + local + ' does not exist');
|
|
1435
1421
|
}
|
|
@@ -1533,7 +1519,7 @@ async function setEnv(app, env, options) {
|
|
|
1533
1519
|
|
|
1534
1520
|
const request = createRequest('POST', `/api/v1/apps/${app.id}/configure/env`, options);
|
|
1535
1521
|
const response = await request.send({ env });
|
|
1536
|
-
if (response.
|
|
1522
|
+
if (response.status !== 202) return exit(`Failed to set env: ${requestError(response)}`);
|
|
1537
1523
|
|
|
1538
1524
|
await waitForTask(response.body.taskId, options);
|
|
1539
1525
|
console.log('\n');
|
|
@@ -1546,7 +1532,7 @@ async function envSet(envVars, localOptions, cmd) {
|
|
|
1546
1532
|
if (!app) return exit(NO_APP_FOUND_ERROR_STRING);
|
|
1547
1533
|
|
|
1548
1534
|
const response = await createRequest('GET', `/api/v1/apps/${app.id}`, options);
|
|
1549
|
-
if (response.
|
|
1535
|
+
if (response.status !== 200) return exit(`Failed to get app: ${requestError(response)}`);
|
|
1550
1536
|
|
|
1551
1537
|
const env = response.body.env;
|
|
1552
1538
|
envVars.forEach(envVar => {
|
|
@@ -1568,7 +1554,7 @@ async function envUnset(envNames, localOptions, cmd) {
|
|
|
1568
1554
|
if (!app) return exit(NO_APP_FOUND_ERROR_STRING);
|
|
1569
1555
|
|
|
1570
1556
|
const response = await createRequest('GET', `/api/v1/apps/${app.id}`, options);
|
|
1571
|
-
if (response.
|
|
1557
|
+
if (response.status !== 200) return exit(`Failed to get app: ${requestError(response)}`);
|
|
1572
1558
|
|
|
1573
1559
|
const env = response.body.env;
|
|
1574
1560
|
envNames.forEach(name => delete env[name]);
|
|
@@ -1586,7 +1572,7 @@ async function envList(localOptions, cmd) {
|
|
|
1586
1572
|
if (!app) return exit(NO_APP_FOUND_ERROR_STRING);
|
|
1587
1573
|
|
|
1588
1574
|
const response = await createRequest('GET', `/api/v1/apps/${app.id}`, options);
|
|
1589
|
-
if (response.
|
|
1575
|
+
if (response.status !== 200) return exit(`Failed to get app: ${requestError(response)}`);
|
|
1590
1576
|
|
|
1591
1577
|
const t = new Table();
|
|
1592
1578
|
|
|
@@ -1614,7 +1600,7 @@ async function envGet(envName, localOptions, cmd) {
|
|
|
1614
1600
|
if (!app) return exit(NO_APP_FOUND_ERROR_STRING);
|
|
1615
1601
|
|
|
1616
1602
|
const response = await createRequest('GET', `/api/v1/apps/${app.id}`, options);
|
|
1617
|
-
if (response.
|
|
1603
|
+
if (response.status !== 200) return exit(`Failed to get app: ${requestError(response)}`);
|
|
1618
1604
|
|
|
1619
1605
|
console.log(response.body.env[envName]);
|
|
1620
1606
|
} catch (error) {
|