@aifabrix/builder 2.47.4 → 2.48.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/jest.isolated-projects.js +4 -0
- package/jest.projects.js +4 -0
- package/lib/api/types/dev.types.js +1 -1
- package/lib/app/register.js +20 -18
- package/lib/app/rotate-secret.js +39 -16
- package/lib/build/index.js +12 -49
- package/lib/build/integration-docker-staging.js +35 -0
- package/lib/build/standard-docker-build.js +113 -0
- package/lib/cli/setup-app.js +4 -0
- package/lib/commands/dev-init.js +18 -0
- package/lib/commands/setup-prompts-platform-mode.js +11 -1
- package/lib/commands/setup.js +1 -0
- package/lib/commands/up-dataplane-credentials.js +88 -5
- package/lib/core/setup-platform-mode.js +16 -9
- package/lib/parameters/infra-kv-discovery.js +4 -3
- package/lib/utils/aifabrix-runtime-config-dir.js +2 -1
- package/lib/utils/compose-generate-docker-compose.js +2 -1
- package/lib/utils/config-paths.js +2 -2
- package/lib/utils/dev-ca-docker-augment.js +54 -0
- package/lib/utils/docker-daemon-tls-ca.js +326 -0
- package/lib/utils/ensure-dev-certs-for-remote-docker.js +5 -0
- package/lib/utils/promote-dev-docker-image.js +170 -0
- package/lib/utils/remote-docker-env.js +22 -7
- package/lib/utils/secrets-helpers.js +9 -14
- package/lib/utils/secrets-kv-refs.js +47 -3
- package/lib/utils/secrets-missing-error.js +1 -9
- package/lib/utils/token-manager-client-credentials.js +11 -1
- package/lib/utils/worker-sidecars.js +28 -1
- package/package.json +1 -1
- package/.temp/jest-protection-convert/run-IFN2nv/.protection/hubspot-companies.json +0 -1
- package/.temp/jest-protection-convert/run-IvmKLO/.protection/hubspot-companies.yaml +0 -17
- package/.temp/jest-protection-convert/run-SdvblF/.protection/hubspot-companies.json +0 -1
- package/.temp/jest-protection-convert/run-b51TsW/.protection/hubspot-companies.json +0 -1
- package/.temp/jest-protection-convert/run-fiLmb1/.protection/hubspot-companies.json +0 -1
- package/.temp/jest-protection-convert/run-qYGHMU/.protection/hubspot-companies.yaml +0 -17
- package/.temp/jest-protection-fixtures/hubspot-Mq5re8/hubspot-companies.yaml +0 -17
- package/.temp/jest-protection-fixtures/hubspot-f3u4YL/hubspot-companies.yaml +0 -17
- package/.temp/jest-protection-fixtures/hubspot-iQtd1O/hubspot-companies.yaml +0 -17
- package/.temp/jest-protection-fixtures/hubspot-mdmz5M/hubspot-companies.yaml +0 -17
- package/.temp/jest-protection-fixtures/hubspot-qbKYxQ/hubspot-companies.yaml +0 -17
- package/.temp/jest-protection-fixtures/hubspot-yzlJy6/hubspot-companies.yaml +0 -17
- package/.temp/validation/summary.txt +0 -5
|
@@ -38,6 +38,10 @@ const ISOLATED_SUITE_ENTRIES = [
|
|
|
38
38
|
'application-frontdoor-paths-contract',
|
|
39
39
|
['**/tests/lib/templates/application-frontdoor-paths.contract.test.js']
|
|
40
40
|
],
|
|
41
|
+
[
|
|
42
|
+
'dataplane-env-template-contract',
|
|
43
|
+
['**/tests/lib/templates/dataplane-env-template.contract.test.js']
|
|
44
|
+
],
|
|
41
45
|
['database-secret-values', ['**/tests/lib/parameters/database-secret-values.test.js']],
|
|
42
46
|
['infra-parameter-validate', ['**/tests/lib/parameters/infra-parameter-validate.test.js']],
|
|
43
47
|
[
|
package/jest.projects.js
CHANGED
|
@@ -275,6 +275,10 @@ const defaultProject = {
|
|
|
275
275
|
'\\\\tests\\\\lib\\\\templates\\\\application-frontdoor-paths.contract.test.js',
|
|
276
276
|
'lib/templates/application-frontdoor-paths.contract.test.js',
|
|
277
277
|
'application-frontdoor-paths\\.contract\\.test\\.js',
|
|
278
|
+
'/tests/lib/templates/dataplane-env-template.contract.test.js',
|
|
279
|
+
'\\\\tests\\\\lib\\\\templates\\\\dataplane-env-template.contract.test.js',
|
|
280
|
+
'lib/templates/dataplane-env-template.contract.test.js',
|
|
281
|
+
'dataplane-env-template\\.contract\\.test\\.js',
|
|
278
282
|
'/tests/lib/core/admin-secrets.test.js',
|
|
279
283
|
'\\\\tests\\\\lib\\\\core\\\\admin-secrets.test.js',
|
|
280
284
|
'lib/core/admin-secrets.test.js',
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
* @property {string} aifabrix-env-config - Env config path
|
|
32
32
|
* @property {string} remote-server - Builder-server base URL
|
|
33
33
|
* @property {string} docker-endpoint - Docker API endpoint
|
|
34
|
-
* @property {boolean} [docker-tls-skip-verify] - When true
|
|
34
|
+
* @property {boolean} [docker-tls-skip-verify] - When true, Docker uses DOCKER_TLS_VERIFY=0 even if ca.pem exists (client mTLS unchanged)
|
|
35
35
|
* @property {string} sync-ssh-user - SSH user for Mutagen
|
|
36
36
|
* @property {string} sync-ssh-host - SSH host for Mutagen
|
|
37
37
|
*/
|
package/lib/app/register.js
CHANGED
|
@@ -78,38 +78,40 @@ function buildRegistrationData(appConfig, options) {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
/**
|
|
81
|
-
* Save credentials to local
|
|
81
|
+
* Save pipeline client credentials to ~/.aifabrix/secrets.local.yaml (always).
|
|
82
|
+
* env.template / in-memory resolve validation runs only for localhost controllers.
|
|
82
83
|
* @async
|
|
83
84
|
* @param {Object} responseData - Registration response data
|
|
84
85
|
* @param {string} apiUrl - API URL
|
|
85
86
|
*/
|
|
86
87
|
async function saveLocalCredentials(responseData, apiUrl) {
|
|
87
|
-
if (!isLocalhost(apiUrl)) {
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
88
|
const registeredAppKey = responseData.application.key;
|
|
92
89
|
const clientIdKey = `${registeredAppKey}-client-idKeyVault`;
|
|
93
90
|
const clientSecretKey = `${registeredAppKey}-client-secretKeyVault`;
|
|
91
|
+
const localhostController = isLocalhost(apiUrl);
|
|
94
92
|
|
|
95
93
|
try {
|
|
96
94
|
await saveLocalSecret(clientIdKey, responseData.credentials.clientId);
|
|
97
95
|
await saveLocalSecret(clientSecretKey, responseData.credentials.clientSecret);
|
|
98
96
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
97
|
+
if (localhostController) {
|
|
98
|
+
// Update env.template (kv:// refs only; no resolved values touch disk)
|
|
99
|
+
await updateEnvTemplate(registeredAppKey, clientIdKey, clientSecretKey, apiUrl);
|
|
100
|
+
|
|
101
|
+
// Resolve in-memory so any missing-secret / kv:// error surfaces here, but never
|
|
102
|
+
// materialize <appPath>/.env or envOutputPath — that is only done by `aifabrix resolve`.
|
|
103
|
+
try {
|
|
104
|
+
await generateEnvFile(registeredAppKey, null, 'local', true, { noWrite: true });
|
|
105
|
+
} catch (error) {
|
|
106
|
+
logger.warn(chalk.yellow(`⚠ Could not validate env resolution: ${error.message}`));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
logger.log(formatSuccessParagraph('Credentials saved to ~/.aifabrix/secrets.local.yaml'));
|
|
110
|
+
logger.log(formatSuccessLine('env.template updated with MISO_CLIENTID, MISO_CLIENTSECRET, and MISO_CONTROLLER_URL'));
|
|
111
|
+
logger.log(formatSuccessLine('Run "aifabrix resolve ' + registeredAppKey + '" to materialize an on-disk .env\n'));
|
|
112
|
+
} else {
|
|
113
|
+
logger.log(formatSuccessParagraph('Credentials saved to ~/.aifabrix/secrets.local.yaml\n'));
|
|
108
114
|
}
|
|
109
|
-
|
|
110
|
-
logger.log(formatSuccessParagraph('Credentials saved to ~/.aifabrix/secrets.local.yaml'));
|
|
111
|
-
logger.log(formatSuccessLine('env.template updated with MISO_CLIENTID, MISO_CLIENTSECRET, and MISO_CONTROLLER_URL'));
|
|
112
|
-
logger.log(formatSuccessLine('Run "aifabrix resolve ' + registeredAppKey + '" to materialize an on-disk .env\n'));
|
|
113
115
|
} catch (error) {
|
|
114
116
|
logger.warn(chalk.yellow(`⚠ Could not save credentials locally: ${error.message}`));
|
|
115
117
|
}
|
package/lib/app/rotate-secret.js
CHANGED
|
@@ -63,16 +63,34 @@ function validateEnvironment(environment) {
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
/**
|
|
67
|
+
* @param {number} code
|
|
68
|
+
* @param {string} message
|
|
69
|
+
* @param {{ exitOnFailure?: boolean }} [options]
|
|
70
|
+
* @throws {Error}
|
|
71
|
+
*/
|
|
72
|
+
function failRotation(code, message, options = {}) {
|
|
73
|
+
if (options.exitOnFailure === false) {
|
|
74
|
+
throw new Error(message);
|
|
75
|
+
}
|
|
76
|
+
logger.error(formatBlockingError(message));
|
|
77
|
+
process.exit(code);
|
|
78
|
+
}
|
|
79
|
+
|
|
66
80
|
/**
|
|
67
81
|
* Resolve controller URL and environment from config; exit if controller is missing.
|
|
68
82
|
* @async
|
|
83
|
+
* @param {{ exitOnFailure?: boolean }} [options]
|
|
69
84
|
* @returns {Promise<{controllerUrl: string, environment: string}>}
|
|
70
85
|
*/
|
|
71
|
-
async function resolveControllerAndEnvironment() {
|
|
86
|
+
async function resolveControllerAndEnvironment(options = {}) {
|
|
72
87
|
const controllerUrl = await resolveControllerUrl();
|
|
73
88
|
if (!controllerUrl) {
|
|
74
|
-
|
|
75
|
-
|
|
89
|
+
failRotation(
|
|
90
|
+
1,
|
|
91
|
+
'Controller URL is required. Run "aifabrix login" to set the controller URL in config.yaml',
|
|
92
|
+
options
|
|
93
|
+
);
|
|
76
94
|
}
|
|
77
95
|
const environment = await resolveEnvironment();
|
|
78
96
|
validateEnvironment(environment);
|
|
@@ -197,9 +215,10 @@ function displayRotationResults(appKey, environment, credentials, apiUrl, messag
|
|
|
197
215
|
* Get device token from provided controller URL
|
|
198
216
|
* @async
|
|
199
217
|
* @param {string} controllerUrl - Controller URL
|
|
218
|
+
* @param {{ exitOnFailure?: boolean }} [options]
|
|
200
219
|
* @returns {Promise<Object|null>} Object with token and controllerUrl, or null if failed
|
|
201
220
|
*/
|
|
202
|
-
async function getTokenFromUrl(controllerUrl) {
|
|
221
|
+
async function getTokenFromUrl(controllerUrl, options = {}) {
|
|
203
222
|
try {
|
|
204
223
|
const normalizedUrl = normalizeControllerUrl(controllerUrl);
|
|
205
224
|
const deviceToken = await getOrRefreshDeviceToken(normalizedUrl);
|
|
@@ -210,9 +229,7 @@ async function getTokenFromUrl(controllerUrl) {
|
|
|
210
229
|
};
|
|
211
230
|
}
|
|
212
231
|
} catch (error) {
|
|
213
|
-
|
|
214
|
-
logger.error(chalk.gray(`Error: ${error.message}`));
|
|
215
|
-
process.exit(1);
|
|
232
|
+
failRotation(1, `Failed to authenticate with controller: ${controllerUrl} (${error.message})`, options);
|
|
216
233
|
}
|
|
217
234
|
return null;
|
|
218
235
|
}
|
|
@@ -222,9 +239,13 @@ async function getTokenFromUrl(controllerUrl) {
|
|
|
222
239
|
* @param {string|null} token - Authentication token
|
|
223
240
|
* @param {string|null} controllerUrl - Controller URL
|
|
224
241
|
* @param {string} [providedUrl] - Original provided URL for error context
|
|
242
|
+
* @param {{ exitOnFailure?: boolean }} [options]
|
|
225
243
|
*/
|
|
226
|
-
function validateAuthToken(token, controllerUrl, providedUrl) {
|
|
244
|
+
function validateAuthToken(token, controllerUrl, providedUrl, options = {}) {
|
|
227
245
|
if (!token || !controllerUrl) {
|
|
246
|
+
if (options.exitOnFailure === false) {
|
|
247
|
+
throw new Error('No valid authentication found');
|
|
248
|
+
}
|
|
228
249
|
const formattedError = formatAuthenticationError({
|
|
229
250
|
controllerUrl: providedUrl || undefined,
|
|
230
251
|
message: 'No valid authentication found'
|
|
@@ -239,16 +260,17 @@ function validateAuthToken(token, controllerUrl, providedUrl) {
|
|
|
239
260
|
* @async
|
|
240
261
|
* @param {string} [controllerUrl] - Optional controller URL
|
|
241
262
|
* @param {Object} config - Configuration object
|
|
263
|
+
* @param {{ exitOnFailure?: boolean }} [options]
|
|
242
264
|
* @returns {Promise<Object>} Object with token and actualControllerUrl
|
|
243
265
|
* @throws {Error} If authentication fails
|
|
244
266
|
*/
|
|
245
|
-
async function getRotationAuthToken(controllerUrl, config) {
|
|
267
|
+
async function getRotationAuthToken(controllerUrl, config, options = {}) {
|
|
246
268
|
let token = null;
|
|
247
269
|
let actualControllerUrl = null;
|
|
248
270
|
|
|
249
271
|
// If controller URL provided, try to get device token
|
|
250
272
|
if (controllerUrl) {
|
|
251
|
-
const tokenResult = await getTokenFromUrl(controllerUrl);
|
|
273
|
+
const tokenResult = await getTokenFromUrl(controllerUrl, options);
|
|
252
274
|
if (tokenResult) {
|
|
253
275
|
token = tokenResult.token;
|
|
254
276
|
actualControllerUrl = tokenResult.controllerUrl;
|
|
@@ -264,7 +286,7 @@ async function getRotationAuthToken(controllerUrl, config) {
|
|
|
264
286
|
}
|
|
265
287
|
}
|
|
266
288
|
|
|
267
|
-
validateAuthToken(token, actualControllerUrl, controllerUrl);
|
|
289
|
+
validateAuthToken(token, actualControllerUrl, controllerUrl, options);
|
|
268
290
|
return { token, actualControllerUrl };
|
|
269
291
|
}
|
|
270
292
|
|
|
@@ -323,6 +345,9 @@ async function executeRotation(appKey, actualControllerUrl, environment, token,
|
|
|
323
345
|
|
|
324
346
|
if (!response.success) {
|
|
325
347
|
const formattedError = response.formattedError || formatApiError(response, actualControllerUrl);
|
|
348
|
+
if (options.exitOnFailure === false) {
|
|
349
|
+
throw new Error(formattedError || `Rotate secret failed for ${appKey}`);
|
|
350
|
+
}
|
|
326
351
|
logger.error(formattedError);
|
|
327
352
|
logger.error(chalk.gray(`\nController URL: ${actualControllerUrl}`));
|
|
328
353
|
process.exit(1);
|
|
@@ -351,16 +376,14 @@ async function rotateSecret(appKey, options = {}) {
|
|
|
351
376
|
const { ensureSecretsEncryptionKey } = require('../core/config');
|
|
352
377
|
await ensureSecretsEncryptionKey();
|
|
353
378
|
|
|
354
|
-
const { controllerUrl, environment } = await resolveControllerAndEnvironment();
|
|
379
|
+
const { controllerUrl, environment } = await resolveControllerAndEnvironment(options);
|
|
355
380
|
const config = await getConfig();
|
|
356
|
-
const { token, actualControllerUrl } = await getRotationAuthToken(controllerUrl, config);
|
|
381
|
+
const { token, actualControllerUrl } = await getRotationAuthToken(controllerUrl, config, options);
|
|
357
382
|
|
|
358
383
|
try {
|
|
359
384
|
await executeRotation(appKey, actualControllerUrl, environment, token, options);
|
|
360
385
|
} catch (error) {
|
|
361
|
-
|
|
362
|
-
logger.error(chalk.gray(`Error: ${error.message}`));
|
|
363
|
-
process.exit(1);
|
|
386
|
+
failRotation(1, `Failed to rotate secret via controller: ${actualControllerUrl} (${error.message})`, options);
|
|
364
387
|
}
|
|
365
388
|
}
|
|
366
389
|
|
package/lib/build/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const {
|
|
2
2
|
formatSuccessLine,
|
|
3
|
-
formatSuccessParagraph,
|
|
4
3
|
formatProgress,
|
|
5
4
|
formatWarningLine,
|
|
6
5
|
sectionTitle,
|
|
@@ -31,12 +30,10 @@ const secrets = require('../core/secrets');
|
|
|
31
30
|
const config = require('../core/config');
|
|
32
31
|
const logger = require('../utils/logger');
|
|
33
32
|
const dockerfileUtils = require('../utils/dockerfile-utils');
|
|
34
|
-
const dockerBuild = require('../utils/docker-build');
|
|
35
33
|
const buildCopy = require('../utils/build-copy');
|
|
36
34
|
const { resolveBuildDockerTagNames } = require('../utils/build-resolve-image');
|
|
37
35
|
const buildHelpers = require('../utils/build-helpers');
|
|
38
|
-
const {
|
|
39
|
-
const { resolveDockerBuildArgsForApp } = require('./docker-build-args');
|
|
36
|
+
const { runStandardDockerBuild } = require('./standard-docker-build');
|
|
40
37
|
|
|
41
38
|
/**
|
|
42
39
|
* Loads application config for an application
|
|
@@ -353,18 +350,6 @@ function prepareBuildContext(buildConfig, devDir) {
|
|
|
353
350
|
return contextPath;
|
|
354
351
|
}
|
|
355
352
|
|
|
356
|
-
/**
|
|
357
|
-
* Handle Dockerfile generation
|
|
358
|
-
* @async
|
|
359
|
-
* @param {string} appName - Application name
|
|
360
|
-
* @param {Object} params - Parameters
|
|
361
|
-
* @param {string} params.devDir - Developer directory
|
|
362
|
-
* @param {Object} params.buildConfig - Build configuration
|
|
363
|
-
* @param {string} params.contextPath - Build context path
|
|
364
|
-
* @param {Object} params.appConfig - Application configuration
|
|
365
|
-
* @param {Object} options - Build options
|
|
366
|
-
* @returns {Promise<string>} Dockerfile path
|
|
367
|
-
*/
|
|
368
353
|
async function handleDockerfileGeneration(appName, params, options, buildHelpers) {
|
|
369
354
|
const { devDir, buildConfig, contextPath, appConfig } = params;
|
|
370
355
|
const appDockerfilePath = path.join(devDir, 'Dockerfile');
|
|
@@ -393,38 +378,6 @@ async function handleDockerfileGeneration(appName, params, options, buildHelpers
|
|
|
393
378
|
}, generateDockerfile);
|
|
394
379
|
}
|
|
395
380
|
|
|
396
|
-
/**
|
|
397
|
-
* Standard Docker build path after CLI header is printed (non-external apps).
|
|
398
|
-
* @param {string} appName
|
|
399
|
-
* @param {Object} options
|
|
400
|
-
* @returns {Promise<string>} Built image ref e.g. name:tag
|
|
401
|
-
*/
|
|
402
|
-
async function runStandardDockerBuild(appName, options) {
|
|
403
|
-
const { buildConfig } = await buildHelpers.loadAndValidateConfig(appName);
|
|
404
|
-
const { emitManifestMetadataLineIfTTY } = require('../utils/manifest-source-emit');
|
|
405
|
-
const { appPath } = await detectAppType(appName);
|
|
406
|
-
emitManifestMetadataLineIfTTY(logger, { appKey: appName, appPath, envOnly: false, json: false });
|
|
407
|
-
const { devDir, effectiveImageName, imageName, appConfig } = await prepareDevDirectory(appName, buildConfig, options);
|
|
408
|
-
const contextPath = prepareBuildContext(buildConfig, devDir);
|
|
409
|
-
const dockerfilePath = await handleDockerfileGeneration(appName, {
|
|
410
|
-
devDir,
|
|
411
|
-
buildConfig,
|
|
412
|
-
contextPath,
|
|
413
|
-
appConfig
|
|
414
|
-
}, options, buildHelpers);
|
|
415
|
-
await ensureRunSecretsForApp(appName);
|
|
416
|
-
const buildArgs = await resolveDockerBuildArgsForApp(appName, contextPath);
|
|
417
|
-
const buildOptions = { ...options, buildArgs };
|
|
418
|
-
const tag = options.tag || 'latest';
|
|
419
|
-
if (typeof tag === 'string' && tag.includes(',')) {
|
|
420
|
-
throw new Error('Use a single image tag per build (comma-separated multiple tags are not supported).');
|
|
421
|
-
}
|
|
422
|
-
await dockerBuild.executeDockerBuildWithTag(effectiveImageName, imageName, dockerfilePath, contextPath, tag, buildOptions);
|
|
423
|
-
await postBuildTasks(appName, buildConfig);
|
|
424
|
-
logger.log(formatSuccessParagraph('Build completed successfully!'));
|
|
425
|
-
return `${effectiveImageName}:${tag}`;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
381
|
/**
|
|
429
382
|
* Builds a container image for the specified application
|
|
430
383
|
* Auto-detects runtime and generates Dockerfile if needed
|
|
@@ -458,4 +411,14 @@ async function buildApp(appName, options = {}) {
|
|
|
458
411
|
throw new Error(`Build failed: ${error.message}`);
|
|
459
412
|
}
|
|
460
413
|
}
|
|
461
|
-
module.exports = {
|
|
414
|
+
module.exports = {
|
|
415
|
+
loadVariablesYaml,
|
|
416
|
+
resolveContextPath,
|
|
417
|
+
detectLanguage,
|
|
418
|
+
generateDockerfile,
|
|
419
|
+
buildApp,
|
|
420
|
+
postBuildTasks,
|
|
421
|
+
prepareDevDirectory,
|
|
422
|
+
prepareBuildContext,
|
|
423
|
+
handleDockerfileGeneration
|
|
424
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-build integration manifest staging for Docker context.
|
|
3
|
+
* @fileoverview Runs scripts/stage_integration_for_docker.py when present in build context
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const fsSync = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const { promisify } = require('util');
|
|
11
|
+
const { exec } = require('child_process');
|
|
12
|
+
const logger = require('../utils/logger');
|
|
13
|
+
const {
|
|
14
|
+
formatProgress,
|
|
15
|
+
formatSuccessLine
|
|
16
|
+
} = require('../utils/cli-test-layout-chalk');
|
|
17
|
+
|
|
18
|
+
const run = promisify(exec);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* When repo ships scripts/stage_integration_for_docker.py, run it before docker build.
|
|
22
|
+
* @param {string} contextPath
|
|
23
|
+
* @returns {Promise<void>}
|
|
24
|
+
*/
|
|
25
|
+
async function maybeStageIntegrationForDockerBuild(contextPath) {
|
|
26
|
+
const scriptPath = path.join(contextPath, 'scripts/stage_integration_for_docker.py');
|
|
27
|
+
if (!fsSync.existsSync(scriptPath)) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
logger.log(formatProgress('Staging integration runtime manifests for Docker…'));
|
|
31
|
+
await run('python3 scripts/stage_integration_for_docker.py', { cwd: contextPath });
|
|
32
|
+
logger.log(formatSuccessLine('Integration staging ready'));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = { maybeStageIntegrationForDockerBuild };
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standard Docker build orchestration (dev-promote shortcut + full build).
|
|
3
|
+
* @fileoverview Extracted from lib/build/index.js for file-size limits
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const chalk = require('chalk');
|
|
9
|
+
const { detectAppType } = require('../utils/paths');
|
|
10
|
+
const logger = require('../utils/logger');
|
|
11
|
+
const dockerBuild = require('../utils/docker-build');
|
|
12
|
+
const buildHelpers = require('../utils/build-helpers');
|
|
13
|
+
const { ensureRunSecretsForApp } = require('../app/run-env-compose');
|
|
14
|
+
const { formatSuccessParagraph } = require('../utils/cli-test-layout-chalk');
|
|
15
|
+
const { resolveDockerBuildArgsForApp } = require('./docker-build-args');
|
|
16
|
+
const { maybeStageIntegrationForDockerBuild } = require('./integration-docker-staging');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Lazy require breaks circular dependency with lib/build/index.js.
|
|
20
|
+
* @returns {Object}
|
|
21
|
+
*/
|
|
22
|
+
function getBuildIndexDeps() {
|
|
23
|
+
return require('./index');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* When --from-dev --base: tag dev image as base and skip docker build when possible.
|
|
28
|
+
* @param {string} appName
|
|
29
|
+
* @param {Object} options
|
|
30
|
+
* @returns {Promise<string|null>} image ref when promote completed; null to continue full build
|
|
31
|
+
*/
|
|
32
|
+
async function tryCompleteDevPromoteBuild(appName, options) {
|
|
33
|
+
const { shouldAttemptDevPromote, tryPromoteDevImageToBase } = require('../utils/promote-dev-docker-image');
|
|
34
|
+
if (!shouldAttemptDevPromote(options)) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
const promotedRef = await tryPromoteDevImageToBase(appName, options);
|
|
38
|
+
if (!promotedRef) {
|
|
39
|
+
logger.log(chalk.blue('No local dev-scoped image found; building base image...'));
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const { buildConfig } = await buildHelpers.loadAndValidateConfig(appName);
|
|
43
|
+
const { postBuildTasks } = getBuildIndexDeps();
|
|
44
|
+
await postBuildTasks(appName, buildConfig);
|
|
45
|
+
logger.log(formatSuccessParagraph('Build completed successfully! (promoted from dev image)'));
|
|
46
|
+
return promotedRef;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Run docker build after workspace prep (shared by standard build path).
|
|
51
|
+
* @param {string} appName
|
|
52
|
+
* @param {Object} options
|
|
53
|
+
* @param {Object} buildConfig
|
|
54
|
+
* @returns {Promise<string>}
|
|
55
|
+
*/
|
|
56
|
+
async function executeDockerImageBuild(appName, options, buildConfig) {
|
|
57
|
+
const { emitManifestMetadataLineIfTTY } = require('../utils/manifest-source-emit');
|
|
58
|
+
const {
|
|
59
|
+
prepareDevDirectory,
|
|
60
|
+
prepareBuildContext,
|
|
61
|
+
handleDockerfileGeneration,
|
|
62
|
+
postBuildTasks
|
|
63
|
+
} = getBuildIndexDeps();
|
|
64
|
+
const { appPath } = await detectAppType(appName);
|
|
65
|
+
emitManifestMetadataLineIfTTY(logger, { appKey: appName, appPath, envOnly: false, json: false });
|
|
66
|
+
const { devDir, effectiveImageName, imageName, appConfig } = await prepareDevDirectory(
|
|
67
|
+
appName,
|
|
68
|
+
buildConfig,
|
|
69
|
+
options
|
|
70
|
+
);
|
|
71
|
+
const contextPath = prepareBuildContext(buildConfig, devDir);
|
|
72
|
+
await maybeStageIntegrationForDockerBuild(contextPath);
|
|
73
|
+
const dockerfilePath = await handleDockerfileGeneration(
|
|
74
|
+
appName,
|
|
75
|
+
{ devDir, buildConfig, contextPath, appConfig },
|
|
76
|
+
options,
|
|
77
|
+
buildHelpers
|
|
78
|
+
);
|
|
79
|
+
await ensureRunSecretsForApp(appName);
|
|
80
|
+
const buildArgs = await resolveDockerBuildArgsForApp(appName, contextPath);
|
|
81
|
+
const tag = options.tag || 'latest';
|
|
82
|
+
if (typeof tag === 'string' && tag.includes(',')) {
|
|
83
|
+
throw new Error('Use a single image tag per build (comma-separated multiple tags are not supported).');
|
|
84
|
+
}
|
|
85
|
+
await dockerBuild.executeDockerBuildWithTag(
|
|
86
|
+
effectiveImageName,
|
|
87
|
+
imageName,
|
|
88
|
+
dockerfilePath,
|
|
89
|
+
contextPath,
|
|
90
|
+
tag,
|
|
91
|
+
{ ...options, buildArgs }
|
|
92
|
+
);
|
|
93
|
+
await postBuildTasks(appName, buildConfig);
|
|
94
|
+
logger.log(formatSuccessParagraph('Build completed successfully!'));
|
|
95
|
+
return `${effectiveImageName}:${tag}`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Standard Docker build path after CLI header is printed (non-external apps).
|
|
100
|
+
* @param {string} appName
|
|
101
|
+
* @param {Object} options
|
|
102
|
+
* @returns {Promise<string>} Built image ref e.g. name:tag
|
|
103
|
+
*/
|
|
104
|
+
async function runStandardDockerBuild(appName, options) {
|
|
105
|
+
const promotedRef = await tryCompleteDevPromoteBuild(appName, options);
|
|
106
|
+
if (promotedRef) {
|
|
107
|
+
return promotedRef;
|
|
108
|
+
}
|
|
109
|
+
const { buildConfig } = await buildHelpers.loadAndValidateConfig(appName);
|
|
110
|
+
return executeDockerImageBuild(appName, options, buildConfig);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = { runStandardDockerBuild };
|
package/lib/cli/setup-app.js
CHANGED
|
@@ -326,6 +326,10 @@ function setupBuildRunLogsDownCommands(program) {
|
|
|
326
326
|
'--base',
|
|
327
327
|
'Build/run the manifest base image (no -devN suffix). Default local build uses a developer-scoped repository when developer-id > 0.'
|
|
328
328
|
)
|
|
329
|
+
.option(
|
|
330
|
+
'--from-dev',
|
|
331
|
+
'With --base: tag the existing developer-scoped image as the base image instead of rebuilding when possible'
|
|
332
|
+
)
|
|
329
333
|
.action(async(appName, options) => {
|
|
330
334
|
try {
|
|
331
335
|
const imageTag = await app.buildApp(appName, options);
|
package/lib/commands/dev-init.js
CHANGED
|
@@ -16,6 +16,7 @@ const {
|
|
|
16
16
|
getCertDir,
|
|
17
17
|
readClientCertPem,
|
|
18
18
|
readClientKeyPem,
|
|
19
|
+
readServerCaPem,
|
|
19
20
|
getCertValidNotAfter,
|
|
20
21
|
normalizePemNewlines,
|
|
21
22
|
mergeCaPemBlocks
|
|
@@ -40,6 +41,7 @@ const {
|
|
|
40
41
|
formatEnsureServerTrustedFailure
|
|
41
42
|
} = require('../utils/dev-init-health-messages');
|
|
42
43
|
const { getBadRequestHint, logCertTroubleshootingHint } = require('../utils/dev-init-cert-hints');
|
|
44
|
+
const { augmentAndSaveCaPemForRemoteDocker } = require('../utils/dev-ca-docker-augment');
|
|
43
45
|
|
|
44
46
|
/**
|
|
45
47
|
* Install dev CA into the OS trust store. On Linux without sudo, log and return false so the caller can use in-process PEM.
|
|
@@ -371,6 +373,11 @@ async function runDevInit(options) {
|
|
|
371
373
|
await saveCertAndConfig(configDir, devId, issueResponse.certificate, keyPem, caPem);
|
|
372
374
|
|
|
373
375
|
await applySettingsFromServer(baseUrl, devId, issueResponse, keyPem, serverCaPemForTls || undefined);
|
|
376
|
+
await augmentAndSaveCaPemForRemoteDocker(configDir, devId, caPem, {
|
|
377
|
+
settings: issueResponse.settings,
|
|
378
|
+
remoteServerUrl: baseUrl,
|
|
379
|
+
warn: (msg) => logger.log(chalk.yellow(` ⚠ ${msg}`))
|
|
380
|
+
});
|
|
374
381
|
await _runSshKeyRegistrationStep(baseUrl, issueResponse, keyPem, configDir, devId, serverCaPemForTls || undefined);
|
|
375
382
|
const sshInfo = await mergeDevSshConfigAfterInit(baseUrl, devId);
|
|
376
383
|
logOnboardingFinished(sshInfo);
|
|
@@ -413,6 +420,11 @@ async function runCertificateRefresh(auth) {
|
|
|
413
420
|
const caPem = mergeCaPemBlocks(serverCaPem, issueResponse.caCertificate, issueResponse.ca);
|
|
414
421
|
await saveCertAndConfig(configDir, devId, issueResponse.certificate, keyPem, caPem);
|
|
415
422
|
await applySettingsFromServer(auth.serverUrl, devId, issueResponse, keyPem, serverCaPem);
|
|
423
|
+
await augmentAndSaveCaPemForRemoteDocker(configDir, devId, caPem, {
|
|
424
|
+
settings: issueResponse.settings,
|
|
425
|
+
remoteServerUrl: auth.serverUrl,
|
|
426
|
+
warn: (msg) => logger.log(chalk.yellow(` ⚠ ${msg}`))
|
|
427
|
+
});
|
|
416
428
|
logger.log(formatSuccessLine('Certificate refreshed and config updated from server.\n'));
|
|
417
429
|
}
|
|
418
430
|
|
|
@@ -433,6 +445,12 @@ async function fetchAndMergeRemoteSettings(auth, clientCertPem, clientKeyPem, de
|
|
|
433
445
|
auth.serverCaPem || undefined
|
|
434
446
|
);
|
|
435
447
|
await config.mergeRemoteSettings(settings);
|
|
448
|
+
const existingCa = readServerCaPem(getCertDir(getConfigDirForPaths(), devId));
|
|
449
|
+
await augmentAndSaveCaPemForRemoteDocker(getConfigDirForPaths(), devId, existingCa, {
|
|
450
|
+
settings,
|
|
451
|
+
remoteServerUrl: auth.serverUrl,
|
|
452
|
+
warn: (msg) => logger.log(chalk.yellow(` ⚠ ${msg}`))
|
|
453
|
+
});
|
|
436
454
|
logger.log(formatSuccessLine('Config updated from server.\n'));
|
|
437
455
|
await displayDevConfig(devId);
|
|
438
456
|
}
|
|
@@ -9,12 +9,14 @@
|
|
|
9
9
|
'use strict';
|
|
10
10
|
|
|
11
11
|
const inquirer = require('inquirer');
|
|
12
|
+
const config = require('../core/config');
|
|
12
13
|
const {
|
|
13
14
|
normalizeSetupPlatformMode,
|
|
14
15
|
SETUP_PLATFORM_MODE,
|
|
15
16
|
SETUP_PLATFORM_MODE_CLI_VALUES,
|
|
16
17
|
getSetupPlatformModePromptChoices
|
|
17
18
|
} = require('../core/setup-platform-mode');
|
|
19
|
+
const { isRemoteServerConfigured } = require('../utils/applications-config-defaults');
|
|
18
20
|
|
|
19
21
|
/**
|
|
20
22
|
* Resolve platform mode from CLI options, if provided.
|
|
@@ -51,12 +53,20 @@ async function promptSetupPlatformMode(options = {}) {
|
|
|
51
53
|
const fromCli = resolvePlatformModeFromOptions(options);
|
|
52
54
|
if (fromCli) return fromCli;
|
|
53
55
|
|
|
56
|
+
const remoteServer = await config.getRemoteServer();
|
|
57
|
+
const choices = getSetupPlatformModePromptChoices({
|
|
58
|
+
remoteServerConfigured: isRemoteServerConfigured(remoteServer)
|
|
59
|
+
});
|
|
60
|
+
if (choices.length === 1) {
|
|
61
|
+
return SETUP_PLATFORM_MODE.SINGLE;
|
|
62
|
+
}
|
|
63
|
+
|
|
54
64
|
const { mode } = await inquirer.prompt([
|
|
55
65
|
{
|
|
56
66
|
type: 'list',
|
|
57
67
|
name: 'mode',
|
|
58
68
|
message: 'Platform mode:',
|
|
59
|
-
choices
|
|
69
|
+
choices,
|
|
60
70
|
default: SETUP_PLATFORM_MODE.SINGLE
|
|
61
71
|
}
|
|
62
72
|
]);
|
package/lib/commands/setup.js
CHANGED
|
@@ -65,6 +65,7 @@ async function applySetupPlatformMode(options) {
|
|
|
65
65
|
// These are the user gates that control full-mode behavior.
|
|
66
66
|
const cfg = await config.getConfig();
|
|
67
67
|
cfg.traefik = derived.traefik;
|
|
68
|
+
cfg.tlsEnabled = derived.tlsEnabled;
|
|
68
69
|
cfg.useEnvironmentScopedResources = derived.useEnvironmentScopedResources;
|
|
69
70
|
await config.saveConfig(cfg);
|
|
70
71
|
await syncPlatformControllerUrlsInConfig();
|