cloudron 5.11.3 → 5.11.5
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 +1 -21
- package/bin/cloudron-build +55 -0
- package/package.json +1 -2
- package/src/build-actions.js +166 -138
package/bin/cloudron
CHANGED
|
@@ -22,12 +22,6 @@ if (!semver.satisfies(process.version, require('../package.json').engines.node))
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
const program = new Command();
|
|
25
|
-
|
|
26
|
-
function collectArgs(value, collected) {
|
|
27
|
-
collected.push(value);
|
|
28
|
-
return collected;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
25
|
program.version(version);
|
|
32
26
|
|
|
33
27
|
// global options
|
|
@@ -39,6 +33,7 @@ program.option('--server <server>', 'Cloudron domain')
|
|
|
39
33
|
// these are separate binaries since global options are not applicable
|
|
40
34
|
program.command('appstore', 'Cloudron appstore commands');
|
|
41
35
|
program.command('repo', 'Cloudron repo commands');
|
|
36
|
+
program.command('build', 'Cloudron build commands');
|
|
42
37
|
|
|
43
38
|
const backupCommand = program.command('backup')
|
|
44
39
|
.description('App backup commands');
|
|
@@ -83,21 +78,6 @@ program.command('completion')
|
|
|
83
78
|
.description('Shows completion for your shell')
|
|
84
79
|
.action(completion);
|
|
85
80
|
|
|
86
|
-
// should probably move to separate binary since globals don't apply
|
|
87
|
-
program.command('build')
|
|
88
|
-
.description('Build an app')
|
|
89
|
-
.option('--build-arg <namevalue>', 'Build arg passed to docker. Can be used multiple times', collectArgs, [])
|
|
90
|
-
.option('--build-service-token <token>', 'Build service token')
|
|
91
|
-
.option('-f, --file <dockerfile>', 'Name of the Dockerfile')
|
|
92
|
-
.option('--set-repository [repository url]', 'Change the repository')
|
|
93
|
-
.option('--set-build-service [buildservice url]', 'Set build service app URL')
|
|
94
|
-
.option('--local', 'Build docker images locally')
|
|
95
|
-
.option('--no-cache', 'Do not use cache')
|
|
96
|
-
.option('--no-push', 'Do not push built image to registry')
|
|
97
|
-
.option('--raw', 'Raw output build log')
|
|
98
|
-
.option('--tag <docker image tag>', 'Docker image tag. Note that this does not include the repository name')
|
|
99
|
-
.action(buildActions.build);
|
|
100
|
-
|
|
101
81
|
program.command('cancel')
|
|
102
82
|
.description('Cancels any active or pending app task')
|
|
103
83
|
.option('--app <id/location>', 'App id or location')
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
const { program } = require('commander'),
|
|
6
|
+
buildActions = require('../src/build-actions.js');
|
|
7
|
+
|
|
8
|
+
program.version(require('../package.json').version);
|
|
9
|
+
|
|
10
|
+
function collectArgs(value, collected) {
|
|
11
|
+
collected.push(value);
|
|
12
|
+
return collected;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// global options
|
|
16
|
+
program.option('--server <server>', 'Cloudron domain')
|
|
17
|
+
.option('--token, --build-service-token <token>', 'Build service token')
|
|
18
|
+
.option('--url, --set-build-service [buildservice url]', 'Set build service URL. This build service is automatically used for future calls from this project');
|
|
19
|
+
|
|
20
|
+
program.command('build', { isDefault: true })
|
|
21
|
+
.description('Build an app. This is the default subcommand')
|
|
22
|
+
.option('--build-arg <namevalue>', 'Build arg passed to docker. Can be used multiple times', collectArgs, [])
|
|
23
|
+
.option('-f, --file <dockerfile>', 'Name of the Dockerfile')
|
|
24
|
+
.option('--set-repository [repository url]', 'Change the repository. This url is stored for future builds for this project. e.g registry/username/projectname')
|
|
25
|
+
.option('--local', 'Build docker images locally')
|
|
26
|
+
.option('--no-cache', 'Do not use cache')
|
|
27
|
+
.option('--no-push', 'Do not push built image to registry')
|
|
28
|
+
.option('--raw', 'Raw output build log')
|
|
29
|
+
.option('--tag <docker image tag>', 'Docker image tag. Note that this does not include the repository name')
|
|
30
|
+
.action(buildActions.build);
|
|
31
|
+
|
|
32
|
+
program.command('login')
|
|
33
|
+
.description('Login to the build service')
|
|
34
|
+
.option('-t, --token <token>', 'Build service token')
|
|
35
|
+
.action(buildActions.login);
|
|
36
|
+
|
|
37
|
+
program.command('logs')
|
|
38
|
+
.description('Build logs. This works only when using the Build Service')
|
|
39
|
+
.option('--id <buildid>', 'Build ID')
|
|
40
|
+
.option('--raw', 'Raw output build log')
|
|
41
|
+
.action(buildActions.logs);
|
|
42
|
+
|
|
43
|
+
program.command('push')
|
|
44
|
+
.description('Push the build image')
|
|
45
|
+
.option('--id <buildid>', 'Build ID')
|
|
46
|
+
.option('--repository [repository url]', 'Set repository to push to. e.g registry/username/projectname')
|
|
47
|
+
.option('--tag <docker image tag>', 'Docker image tag. Note that this does not include the repository name')
|
|
48
|
+
.action(buildActions.push);
|
|
49
|
+
|
|
50
|
+
program.command('status')
|
|
51
|
+
.description('Build status. This works only when using the Build Service')
|
|
52
|
+
.option('--id <buildid>', 'Build ID')
|
|
53
|
+
.action(buildActions.status);
|
|
54
|
+
|
|
55
|
+
program.parse(process.argv);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cloudron",
|
|
3
|
-
"version": "5.11.
|
|
3
|
+
"version": "5.11.5",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Cloudron Commandline Tool",
|
|
6
6
|
"main": "main.js",
|
|
@@ -25,7 +25,6 @@
|
|
|
25
25
|
"ejs": "^3.1.10",
|
|
26
26
|
"eventsource": "^2.0.2",
|
|
27
27
|
"micromatch": "^4.0.7",
|
|
28
|
-
"once": "^1.4.0",
|
|
29
28
|
"open": "^8.4.0",
|
|
30
29
|
"progress": "^2.0.3",
|
|
31
30
|
"progress-stream": "^2.0.0",
|
package/src/build-actions.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
exports = module.exports = {
|
|
4
|
-
|
|
4
|
+
login,
|
|
5
|
+
build,
|
|
6
|
+
logs,
|
|
7
|
+
status,
|
|
8
|
+
push
|
|
5
9
|
};
|
|
6
10
|
|
|
7
11
|
const assert = require('assert'),
|
|
@@ -11,7 +15,6 @@ const assert = require('assert'),
|
|
|
11
15
|
execSync = require('child_process').execSync,
|
|
12
16
|
exit = require('./helper.js').exit,
|
|
13
17
|
fs = require('fs'),
|
|
14
|
-
once = require('once'),
|
|
15
18
|
helper = require('./helper.js'),
|
|
16
19
|
manifestFormat = require('cloudron-manifestformat'),
|
|
17
20
|
micromatch = require('micromatch'),
|
|
@@ -20,78 +23,80 @@ const assert = require('assert'),
|
|
|
20
23
|
os = require('os'),
|
|
21
24
|
path = require('path'),
|
|
22
25
|
safe = require('safetydance'),
|
|
26
|
+
stream = require('stream/promises'),
|
|
23
27
|
tar = require('tar-fs'),
|
|
24
|
-
url = require('url')
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
requestFactory().end(function (error, result) {
|
|
32
|
-
if (error && !error.response) return callback(error);
|
|
33
|
-
if (result.statusCode === 400) return authenticateBuildService({ error: true }, superagentEndBuildService.bind(null, requestFactory, callback));
|
|
34
|
-
if (result.statusCode === 401) return authenticateBuildService({ error: true }, superagentEndBuildService.bind(null, requestFactory, callback));
|
|
35
|
-
if (result.statusCode === 403) return callback(new Error(result.type === 'application/javascript' ? JSON.stringify(result.body) : result.text));
|
|
36
|
-
callback(error, result);
|
|
37
|
-
});
|
|
28
|
+
url = require('url');
|
|
29
|
+
|
|
30
|
+
function requestError(response) {
|
|
31
|
+
if (response.statusCode === 401 || response.statusCode === 403) return 'Invalid token. Use cloudron build login again.';
|
|
32
|
+
|
|
33
|
+
return `${response.statusCode} message: ${response.body?.message || null}`;
|
|
38
34
|
}
|
|
39
35
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
36
|
+
// analyzes options and merges with any existing build service config
|
|
37
|
+
function getBuildServiceConfig(options) {
|
|
38
|
+
const buildService = config.getBuildServiceConfig();
|
|
39
|
+
if (!buildService.type) buildService.type = 'local'; // default
|
|
43
40
|
|
|
44
|
-
|
|
41
|
+
if (options.local) {
|
|
42
|
+
buildService.type = 'local';
|
|
43
|
+
} else if (options.setBuildService) { // stash for future use
|
|
44
|
+
buildService.token = null;
|
|
45
|
+
buildService.url = null;
|
|
46
|
+
buildService.type = 'remote';
|
|
45
47
|
|
|
46
|
-
|
|
48
|
+
let url;
|
|
49
|
+
if (typeof options.setBuildService === 'string') {
|
|
50
|
+
url = options.setBuildService;
|
|
51
|
+
} else {
|
|
52
|
+
url = readlineSync.question('Enter build service URL: ', { });
|
|
53
|
+
}
|
|
47
54
|
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
if (url.indexOf('://') === -1) url = `https://${url}`;
|
|
56
|
+
buildService.url = url;
|
|
57
|
+
}
|
|
50
58
|
|
|
51
|
-
|
|
52
|
-
buildServiceConfig.token = null;
|
|
53
|
-
config.setBuildServiceConfig(buildServiceConfig);
|
|
59
|
+
if (options.buildServiceToken) buildService.token = options.buildServiceToken;
|
|
54
60
|
|
|
55
|
-
|
|
56
|
-
if (error && !error.response) exit(error);
|
|
61
|
+
config.setBuildServiceConfig(buildService);
|
|
57
62
|
|
|
58
|
-
|
|
59
|
-
|
|
63
|
+
return buildService;
|
|
64
|
+
}
|
|
60
65
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
options.password = '';
|
|
66
|
+
async function login(options) {
|
|
67
|
+
assert.strictEqual(typeof options, 'object');
|
|
64
68
|
|
|
65
|
-
|
|
66
|
-
}
|
|
69
|
+
const buildServiceConfig = getBuildServiceConfig(options);
|
|
67
70
|
|
|
68
|
-
|
|
69
|
-
config.setBuildServiceConfig(buildServiceConfig);
|
|
71
|
+
console.log('Build Service login' + ` (${buildServiceConfig.url}):`);
|
|
70
72
|
|
|
71
|
-
|
|
73
|
+
const token = options.buildServiceToken || readlineSync.question('Token: ', {});
|
|
72
74
|
|
|
73
|
-
|
|
74
|
-
});
|
|
75
|
+
const response = await superagent.get(`${buildServiceConfig.url}/api/v1/profile`).query({ accessToken: token }).ok(() => true);
|
|
76
|
+
if (response.statusCode === 401 || response.statusCode === 403) return exit(`Authentication error: ${requestError(response)}`);
|
|
77
|
+
if (response.statusCode !== 200) return exit(`Unexpected response: ${requestError(response)}`);
|
|
78
|
+
|
|
79
|
+
buildServiceConfig.token = token;
|
|
80
|
+
config.setBuildServiceConfig(buildServiceConfig);
|
|
81
|
+
|
|
82
|
+
console.log('Login successful.');
|
|
75
83
|
}
|
|
76
84
|
|
|
77
|
-
function followBuildLog(buildId, raw
|
|
85
|
+
async function followBuildLog(buildId, raw) {
|
|
78
86
|
assert.strictEqual(typeof buildId, 'string');
|
|
79
87
|
assert.strictEqual(typeof raw, 'boolean');
|
|
80
88
|
|
|
81
|
-
// ensure callback is only ever called once
|
|
82
|
-
callback = once(callback);
|
|
83
|
-
|
|
84
89
|
// EventSource always requires http
|
|
85
90
|
let tmp = url.parse(config.getBuildServiceConfig().url);
|
|
86
91
|
if (tmp.protocol !== 'https:' && tmp.protocol !== 'http:') tmp = url.parse('http://' + config.getBuildServiceConfig().url);
|
|
87
92
|
|
|
88
|
-
|
|
89
|
-
|
|
93
|
+
const es = new EventSource(`${tmp.href}api/v1/builds/${buildId}/logstream?accessToken=${config.getBuildServiceConfig().token}`);
|
|
94
|
+
let prevId = null, prevWasStatus = false;
|
|
90
95
|
|
|
91
96
|
es.on('message', function (e) {
|
|
92
97
|
if (raw) return console.dir(e);
|
|
93
98
|
|
|
94
|
-
|
|
99
|
+
const data = safe.JSON.parse(e.data);
|
|
95
100
|
if (!data) return; // this is a bug in docker or our build server
|
|
96
101
|
|
|
97
102
|
if (data.status) { // image push log
|
|
@@ -131,25 +136,39 @@ function followBuildLog(buildId, raw, callback) {
|
|
|
131
136
|
console.error(data.error);
|
|
132
137
|
}
|
|
133
138
|
});
|
|
134
|
-
es.on('error', function (error) {
|
|
135
|
-
if (raw) console.dir(error);
|
|
136
139
|
|
|
137
|
-
|
|
138
|
-
|
|
140
|
+
let didConnect = false;
|
|
141
|
+
es.once('open', () => didConnect = true);
|
|
142
|
+
|
|
143
|
+
return new Promise((resolve, reject) => {
|
|
144
|
+
es.once('error', function (error) { // server close or network error or some interruption
|
|
145
|
+
if (raw) console.dir(error);
|
|
139
146
|
|
|
140
|
-
|
|
147
|
+
es.close();
|
|
148
|
+
if (didConnect) resolve(); else reject(new Error('Failed to connect'));
|
|
149
|
+
});
|
|
141
150
|
});
|
|
142
151
|
}
|
|
143
152
|
|
|
153
|
+
async function getStatus(buildId) {
|
|
154
|
+
const buildServiceConfig = config.getBuildServiceConfig();
|
|
155
|
+
|
|
156
|
+
const response2 = await superagent.get(`${buildServiceConfig.url}/api/v1/builds/${buildId}`)
|
|
157
|
+
.query({ accessToken: buildServiceConfig.token })
|
|
158
|
+
.ok(() => true);
|
|
159
|
+
if (response2.statusCode !== 200) throw new Error(`Failed to get status: ${requestError(response2)}`);
|
|
160
|
+
return response2.body.status;
|
|
161
|
+
}
|
|
162
|
+
|
|
144
163
|
function dockerignoreMatcher(dockerignorePath) {
|
|
145
|
-
|
|
164
|
+
let patterns = [];
|
|
146
165
|
|
|
147
166
|
if (fs.existsSync(dockerignorePath)) {
|
|
148
167
|
patterns = fs.readFileSync(dockerignorePath, 'utf8').split('\n');
|
|
149
168
|
|
|
150
169
|
patterns = patterns.filter(function (line) { return line[0] !== '#'; });
|
|
151
170
|
patterns = patterns.map(function (line) {
|
|
152
|
-
|
|
171
|
+
let l = line.trim();
|
|
153
172
|
|
|
154
173
|
while (l[0] === '/') l = l.slice(1);
|
|
155
174
|
while (l[l.length-1] === '/') l = l.slice(0, -1);
|
|
@@ -164,7 +183,7 @@ function dockerignoreMatcher(dockerignorePath) {
|
|
|
164
183
|
};
|
|
165
184
|
}
|
|
166
185
|
|
|
167
|
-
function buildLocal(manifest, sourceDir, appConfig, options) {
|
|
186
|
+
async function buildLocal(manifest, sourceDir, appConfig, options) {
|
|
168
187
|
let tag;
|
|
169
188
|
if (options.tag) {
|
|
170
189
|
tag = options.tag;
|
|
@@ -180,7 +199,7 @@ function buildLocal(manifest, sourceDir, appConfig, options) {
|
|
|
180
199
|
console.log('Building locally as %s', dockerImage);
|
|
181
200
|
console.log();
|
|
182
201
|
|
|
183
|
-
|
|
202
|
+
const buildArgsCmdLine = options.buildArg.map(function (a) { return `--build-arg "${a}"`; }).join(' ');
|
|
184
203
|
|
|
185
204
|
let dockerfile = 'Dockerfile';
|
|
186
205
|
if (options.file) dockerfile = options.file;
|
|
@@ -196,9 +215,9 @@ function buildLocal(manifest, sourceDir, appConfig, options) {
|
|
|
196
215
|
if (safe.error) exit('Failed to push image (are you logged in? if not, use "docker login")');
|
|
197
216
|
}
|
|
198
217
|
|
|
199
|
-
|
|
218
|
+
const result = safe.child_process.execSync(`docker inspect --format="{{index .RepoDigests 0}}" ${dockerImage}`, { encoding: 'utf8' });
|
|
200
219
|
if (safe.error) exit('Failed to inspect image');
|
|
201
|
-
|
|
220
|
+
const match = /.*@sha256:(.*)/.exec(result.trim());
|
|
202
221
|
if (!match) exit('Failed to detect sha256');
|
|
203
222
|
|
|
204
223
|
appConfig.dockerImage = `${dockerImage}`;
|
|
@@ -206,7 +225,7 @@ function buildLocal(manifest, sourceDir, appConfig, options) {
|
|
|
206
225
|
config.setAppConfig(sourceDir, appConfig);
|
|
207
226
|
}
|
|
208
227
|
|
|
209
|
-
function buildRemote(manifest, sourceDir, appConfig, options) {
|
|
228
|
+
async function buildRemote(manifest, sourceDir, appConfig, options) {
|
|
210
229
|
console.log('Using build service', config.getBuildServiceConfig().url);
|
|
211
230
|
|
|
212
231
|
let tag;
|
|
@@ -223,21 +242,21 @@ function buildRemote(manifest, sourceDir, appConfig, options) {
|
|
|
223
242
|
|
|
224
243
|
console.log('Building %s', dockerImage);
|
|
225
244
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
245
|
+
const sourceArchiveFilePath = path.join(os.tmpdir(), path.basename(sourceDir) + '.tar.gz');
|
|
246
|
+
const dockerignoreFilePath = path.join(sourceDir, '.dockerignore');
|
|
247
|
+
const ignoreMatcher = dockerignoreMatcher(dockerignoreFilePath);
|
|
229
248
|
|
|
230
249
|
console.log('Uploading source tarball...');
|
|
231
250
|
|
|
232
|
-
|
|
251
|
+
const tarStream = tar.pack(sourceDir, {
|
|
233
252
|
ignore: function (name) {
|
|
234
253
|
return ignoreMatcher(name.slice(sourceDir.length + 1)); // make name as relative path
|
|
235
254
|
}
|
|
236
|
-
}).pipe(fs.createWriteStream(sourceArchiveFilePath));
|
|
237
|
-
|
|
238
|
-
stream.on('error', function (error) {
|
|
239
|
-
exit('Failed to create application source archive: ' + error);
|
|
240
255
|
});
|
|
256
|
+
const sourceArchiveStream = fs.createWriteStream(sourceArchiveFilePath);
|
|
257
|
+
|
|
258
|
+
const [tarError] = await safe(stream.pipeline(tarStream, sourceArchiveStream));
|
|
259
|
+
if (tarError) return exit(`Could not tar: ${tarError.message}`);
|
|
241
260
|
|
|
242
261
|
let dockerfile = 'Dockerfile';
|
|
243
262
|
if (options.file) dockerfile = options.file;
|
|
@@ -251,49 +270,40 @@ function buildRemote(manifest, sourceDir, appConfig, options) {
|
|
|
251
270
|
buildArgsObject[key] = value;
|
|
252
271
|
});
|
|
253
272
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
// appConfig.dockerImageSha256 = match[1]; // stash this separately for now
|
|
284
|
-
config.setAppConfig(sourceDir, appConfig);
|
|
285
|
-
|
|
286
|
-
console.log(dockerImage);
|
|
287
|
-
console.log('\nBuild successful');
|
|
288
|
-
|
|
289
|
-
exit();
|
|
290
|
-
});
|
|
291
|
-
});
|
|
292
|
-
});
|
|
293
|
-
});
|
|
273
|
+
const buildServiceConfig = config.getBuildServiceConfig();
|
|
274
|
+
const response = await superagent.post(`${buildServiceConfig.url}/api/v1/builds`)
|
|
275
|
+
.query({ accessToken: buildServiceConfig.token, noCache: !options.cache, dockerfile: dockerfile, noPush: !options.push })
|
|
276
|
+
.field('dockerImageRepo', appConfig.repository)
|
|
277
|
+
.field('dockerImageTag', tag)
|
|
278
|
+
.field('buildArgs', JSON.stringify(buildArgsObject))
|
|
279
|
+
.attach('sourceArchive', sourceArchiveFilePath)
|
|
280
|
+
.ok(() => true);
|
|
281
|
+
if (response.statusCode === 413) return exit('Failed to build app. The app source is too large.\nPlease adjust your .dockerignore file to only include neccessary files.');
|
|
282
|
+
if (response.statusCode !== 201) return exit(`Failed to upload app for building: ${requestError(response)}`);
|
|
283
|
+
|
|
284
|
+
const buildId = response.body.id;
|
|
285
|
+
console.log(`BuildId: ${buildId}`);
|
|
286
|
+
|
|
287
|
+
const [logsError] = await safe(followBuildLog(buildId, !!options.raw));
|
|
288
|
+
if (logsError) console.log(`Failed to get logs: ${logsError.message}`);
|
|
289
|
+
|
|
290
|
+
const [statusError, status] = await safe(getStatus(buildId));
|
|
291
|
+
if (statusError) return exit(`Failed to get status: ${statusError.message}`);
|
|
292
|
+
if (status !== 'success') return exit('Failed to build app. See log output above.');
|
|
293
|
+
|
|
294
|
+
appConfig.dockerImage = dockerImage;
|
|
295
|
+
// appConfig.dockerImageSha256 = match[1]; // stash this separately for now
|
|
296
|
+
config.setAppConfig(sourceDir, appConfig);
|
|
297
|
+
|
|
298
|
+
console.log(`Docker image: ${dockerImage}`);
|
|
299
|
+
console.log('\nBuild successful');
|
|
300
|
+
|
|
301
|
+
exit();
|
|
294
302
|
}
|
|
295
303
|
|
|
296
|
-
function build(
|
|
304
|
+
async function build(localOptions, cmd) {
|
|
305
|
+
const options = cmd.optsWithGlobals();
|
|
306
|
+
|
|
297
307
|
// try to find the manifest of this project
|
|
298
308
|
const manifestFilePath = helper.locateManifest();
|
|
299
309
|
if (!manifestFilePath) return exit('No CloudronManifest.json found');
|
|
@@ -301,35 +311,11 @@ function build(options) {
|
|
|
301
311
|
const result = manifestFormat.parseFile(manifestFilePath);
|
|
302
312
|
if (result.error) return exit('Error in CloudronManifest.json: ' + result.error.message);
|
|
303
313
|
|
|
304
|
-
|
|
314
|
+
const manifest = result.manifest;
|
|
305
315
|
const sourceDir = path.dirname(manifestFilePath);
|
|
306
316
|
|
|
307
317
|
const appConfig = config.getAppConfig(sourceDir);
|
|
308
|
-
|
|
309
|
-
let buildService = config.getBuildServiceConfig();
|
|
310
|
-
if (!buildService.type) buildService.type = 'local'; // default
|
|
311
|
-
|
|
312
|
-
if (options.local) {
|
|
313
|
-
buildService.type = 'local';
|
|
314
|
-
} else if (options.setBuildService) {
|
|
315
|
-
buildService.token = null;
|
|
316
|
-
buildService.url = null;
|
|
317
|
-
buildService.type = 'remote';
|
|
318
|
-
|
|
319
|
-
let url;
|
|
320
|
-
if (typeof options.setBuildService === 'string') {
|
|
321
|
-
url = options.setBuildService;
|
|
322
|
-
} else {
|
|
323
|
-
url = readlineSync.question('Enter build service URL: ', { });
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
if (url.indexOf('://') === -1) url = `https://${url}`;
|
|
327
|
-
buildService.url = url;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
if (options.buildServiceToken) buildService.token = options.buildServiceToken;
|
|
331
|
-
|
|
332
|
-
config.setBuildServiceConfig(buildService);
|
|
318
|
+
const buildServiceConfig = getBuildServiceConfig(options);
|
|
333
319
|
|
|
334
320
|
let repository = appConfig.repository;
|
|
335
321
|
if (!repository || options.setRepository) {
|
|
@@ -345,11 +331,53 @@ function build(options) {
|
|
|
345
331
|
config.setAppConfig(sourceDir, appConfig);
|
|
346
332
|
}
|
|
347
333
|
|
|
348
|
-
if (
|
|
349
|
-
buildLocal(manifest, sourceDir, appConfig, options);
|
|
350
|
-
} else if (
|
|
351
|
-
buildRemote(manifest, sourceDir, appConfig, options);
|
|
334
|
+
if (buildServiceConfig.type === 'local') {
|
|
335
|
+
await buildLocal(manifest, sourceDir, appConfig, options);
|
|
336
|
+
} else if (buildServiceConfig.type === 'remote' && buildServiceConfig.url) {
|
|
337
|
+
await buildRemote(manifest, sourceDir, appConfig, options);
|
|
352
338
|
} else {
|
|
353
339
|
exit('Unknown build service type or missing build service url. Rerun with --reset-build-service');
|
|
354
340
|
}
|
|
355
341
|
}
|
|
342
|
+
|
|
343
|
+
async function logs(localOptions, cmd) {
|
|
344
|
+
const options = cmd.optsWithGlobals();
|
|
345
|
+
|
|
346
|
+
if (!options.id) return exit('buildId is required');
|
|
347
|
+
|
|
348
|
+
const [logsError] = await safe(followBuildLog(options.id, !!options.raw));
|
|
349
|
+
if (logsError) console.log(`Failed to get logs: ${logsError.message}`);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async function status(localOptions, cmd) {
|
|
353
|
+
const options = cmd.optsWithGlobals();
|
|
354
|
+
|
|
355
|
+
if (!options.id) return exit('buildId is required');
|
|
356
|
+
|
|
357
|
+
const [statusError, status] = await safe(getStatus(options.id));
|
|
358
|
+
if (statusError) return exit(`Failed to get status: ${statusError.message}`);
|
|
359
|
+
console.log(status);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
async function push(localOptions, cmd) {
|
|
363
|
+
const options = cmd.optsWithGlobals();
|
|
364
|
+
|
|
365
|
+
if (!options.id) return exit('buildId is required');
|
|
366
|
+
if (!options.repository) return exit('repository is required');
|
|
367
|
+
if (!options.tag) return exit('tag is required');
|
|
368
|
+
|
|
369
|
+
const buildServiceConfig = getBuildServiceConfig(options);
|
|
370
|
+
|
|
371
|
+
const response = await superagent.post(`${buildServiceConfig.url}/api/v1/builds/${options.id}/push`)
|
|
372
|
+
.query({ accessToken: buildServiceConfig.token })
|
|
373
|
+
.send({ dockerImageRepo: options.repository, dockerImageTag: options.tag })
|
|
374
|
+
.ok(() => true);
|
|
375
|
+
if (response.statusCode !== 201) return exit(`Failed to push: ${requestError(response)}`);
|
|
376
|
+
|
|
377
|
+
const [logsError] = await safe(followBuildLog(options.id, !!options.raw));
|
|
378
|
+
if (logsError) console.log(`Failed to get logs: ${logsError.message}`);
|
|
379
|
+
|
|
380
|
+
const [statusError, status] = await safe(getStatus(options.id));
|
|
381
|
+
if (statusError) return exit(`Failed to get status: ${statusError.message}`);
|
|
382
|
+
if (status !== 'success') return exit('Failed to push app. See log output above.');
|
|
383
|
+
}
|