@aifabrix/builder 2.44.6 → 2.45.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/.cursor/rules/cli-layout.mdc +7 -3
- package/jest.projects.js +56 -0
- package/lib/app/helpers.js +3 -3
- package/lib/app/index.js +3 -3
- package/lib/app/register.js +7 -6
- package/lib/app/restart-display.js +52 -21
- package/lib/app/rotate-secret.js +7 -6
- package/lib/app/run-helpers.js +15 -8
- package/lib/app/run.js +57 -9
- package/lib/app/show-display.js +7 -0
- package/lib/app/show.js +87 -5
- package/lib/build/index.js +9 -5
- package/lib/cli/infra-guided.js +42 -27
- package/lib/cli/installation-log-command.js +73 -0
- package/lib/cli/setup-app.js +11 -1
- package/lib/cli/setup-auth.js +94 -49
- package/lib/cli/setup-infra-up-dataplane-action.js +111 -0
- package/lib/cli/setup-infra-up-platform-action.js +131 -0
- package/lib/cli/setup-infra.js +60 -119
- package/lib/cli/setup-platform.js +1 -1
- package/lib/cli/setup-utility-resolve.js +132 -0
- package/lib/cli/setup-utility.js +65 -51
- package/lib/commands/app-logs.js +81 -33
- package/lib/commands/auth-config.js +116 -18
- package/lib/commands/setup-modes.js +19 -6
- package/lib/commands/setup-prompts.js +41 -8
- package/lib/commands/setup.js +114 -9
- package/lib/commands/teardown.js +54 -5
- package/lib/commands/up-common.js +48 -14
- package/lib/commands/up-dataplane.js +21 -18
- package/lib/commands/up-miso.js +12 -8
- package/lib/commands/upload.js +5 -3
- package/lib/core/audit-logger.js +1 -34
- package/lib/core/config-admin-email.js +56 -0
- package/lib/core/config-normalize.js +60 -0
- package/lib/core/config-registered-controller-urls.js +54 -0
- package/lib/core/config.js +33 -50
- package/lib/core/secrets-ensure-infra.js +1 -1
- package/lib/core/secrets-env-content.js +86 -90
- package/lib/core/secrets-env-declarative-expand.js +170 -0
- package/lib/core/secrets-env-write.js +2 -0
- package/lib/core/secrets-load.js +106 -102
- package/lib/external-system/deploy.js +5 -1
- package/lib/internal/node-fs.js +2 -0
- package/lib/schema/application-schema.json +4 -0
- package/lib/schema/infra.parameter.yaml +10 -0
- package/lib/utils/app-config-resolver.js +24 -1
- package/lib/utils/applications-config-defaults.js +206 -0
- package/lib/utils/auth-config-validator.js +2 -12
- package/lib/utils/bash-secret-env.js +1 -1
- package/lib/utils/compose-generate-docker-compose.js +111 -6
- package/lib/utils/compose-generator.js +17 -8
- package/lib/utils/controller-url.js +50 -7
- package/lib/utils/env-copy.js +99 -14
- package/lib/utils/env-template.js +5 -1
- package/lib/utils/health-check-url.js +18 -15
- package/lib/utils/health-check.js +7 -5
- package/lib/utils/infra-optional-service-flags.js +69 -0
- package/lib/utils/installation-log-core.js +282 -0
- package/lib/utils/installation-log-record.js +237 -0
- package/lib/utils/installation-log.js +123 -0
- package/lib/utils/log-redaction.js +105 -0
- package/lib/utils/manifest-location.js +164 -0
- package/lib/utils/manifest-source-emit.js +162 -0
- package/lib/utils/paths.js +238 -89
- package/lib/utils/remote-secrets-loader.js +7 -1
- package/lib/utils/run-cli-flags.js +29 -0
- package/lib/utils/secrets-canonical.js +10 -3
- package/lib/utils/secrets-path.js +3 -4
- package/lib/utils/secrets-utils.js +20 -10
- package/lib/utils/system-builder-root.js +10 -2
- package/lib/utils/url-declarative-public-base.js +80 -12
- package/lib/utils/url-declarative-resolve-build-urls.js +238 -0
- package/lib/utils/url-declarative-resolve-build.js +24 -393
- package/lib/utils/url-declarative-resolve-expand-token.js +189 -0
- package/lib/utils/url-declarative-resolve-load-doc.js +12 -3
- package/lib/utils/url-declarative-resolve-surface-state.js +102 -0
- package/lib/utils/url-declarative-resolve.js +47 -7
- package/lib/utils/url-declarative-runtime-base-path.js +21 -1
- package/lib/utils/urls-local-registry-scan.js +103 -0
- package/lib/utils/urls-local-registry.js +161 -90
- package/package.json +3 -1
- package/templates/applications/dataplane/application.yaml +4 -0
- package/templates/applications/miso-controller/application.yaml +2 -0
- package/templates/applications/miso-controller/env.template +27 -29
- package/.npmrc.token +0 -1
package/lib/commands/setup.js
CHANGED
|
@@ -29,6 +29,7 @@ const {
|
|
|
29
29
|
|
|
30
30
|
const setupPrompts = require('./setup-prompts');
|
|
31
31
|
const setupModes = require('./setup-modes');
|
|
32
|
+
const installationLog = require('../utils/installation-log');
|
|
32
33
|
|
|
33
34
|
const MODE = setupPrompts.MODE;
|
|
34
35
|
|
|
@@ -57,19 +58,25 @@ async function runFreshInstallFlow(options) {
|
|
|
57
58
|
await maybePinDeveloperIdForFreshInstall(options.developer);
|
|
58
59
|
logger.log(infoLine('No infrastructure detected; starting fresh install.'));
|
|
59
60
|
const adminCreds = await setupPrompts.promptAdminCredentials();
|
|
61
|
+
await config.setAdminEmail(adminCreds.adminEmail);
|
|
60
62
|
await setupModes.runFreshInstall(adminCreds);
|
|
61
63
|
logger.log(formatSuccessParagraph('aifabrix setup complete (fresh install).'));
|
|
62
64
|
}
|
|
63
65
|
|
|
66
|
+
/**
|
|
67
|
+
* @param {boolean} assumeYes
|
|
68
|
+
* @returns {Promise<{ aborted: boolean, mode: string|null }>}
|
|
69
|
+
*/
|
|
64
70
|
async function runExistingInfraFlow(assumeYes) {
|
|
65
71
|
const mode = await setupPrompts.promptModeSelection();
|
|
66
72
|
const proceed = await setupPrompts.confirmDestructiveMode(mode, assumeYes);
|
|
67
73
|
if (!proceed) {
|
|
68
74
|
logger.log(chalk.yellow('Aborted by user.'));
|
|
69
|
-
return;
|
|
75
|
+
return { aborted: true, mode };
|
|
70
76
|
}
|
|
71
77
|
await dispatchMode(mode);
|
|
72
78
|
logger.log(formatSuccessParagraph(`aifabrix setup complete (mode: ${mode}).`));
|
|
79
|
+
return { aborted: false, mode };
|
|
73
80
|
}
|
|
74
81
|
|
|
75
82
|
/**
|
|
@@ -116,6 +123,89 @@ async function dispatchMode(mode) {
|
|
|
116
123
|
}
|
|
117
124
|
}
|
|
118
125
|
|
|
126
|
+
/**
|
|
127
|
+
* @param {Object} options
|
|
128
|
+
* @param {boolean} assumeYes
|
|
129
|
+
* @returns {Promise<{ outcome: string, setupMode: string|null }>}
|
|
130
|
+
*/
|
|
131
|
+
async function runSetupMainPath(options, assumeYes) {
|
|
132
|
+
logSetupHeader();
|
|
133
|
+
logger.log(formatProgress('Detecting current installation state...'));
|
|
134
|
+
const running = await isInfraRunning();
|
|
135
|
+
if (!running) {
|
|
136
|
+
await runFreshInstallFlow(options);
|
|
137
|
+
return { outcome: 'success', setupMode: 'fresh' };
|
|
138
|
+
}
|
|
139
|
+
const r = await runExistingInfraFlow(assumeYes);
|
|
140
|
+
const outcome = r.aborted ? 'aborted' : 'success';
|
|
141
|
+
return { outcome, setupMode: r.mode };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function readConfigSnapshotForSetupLog() {
|
|
145
|
+
try {
|
|
146
|
+
return await config.getConfig();
|
|
147
|
+
} catch {
|
|
148
|
+
return {};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function tryCollectPlatformImagesForLog(outcome) {
|
|
153
|
+
if (outcome !== 'success') return undefined;
|
|
154
|
+
try {
|
|
155
|
+
return installationLog.collectPlatformAppImages(['keycloak', 'miso-controller', 'dataplane'], {});
|
|
156
|
+
} catch {
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function readSetupLogUrlFields() {
|
|
162
|
+
let controllerUrl;
|
|
163
|
+
try {
|
|
164
|
+
controllerUrl = await installationLog.resolveControllerUrlForLog();
|
|
165
|
+
} catch {
|
|
166
|
+
controllerUrl = undefined;
|
|
167
|
+
}
|
|
168
|
+
let adminEmail;
|
|
169
|
+
try {
|
|
170
|
+
adminEmail = await installationLog.resolveAdminEmailPresence();
|
|
171
|
+
} catch {
|
|
172
|
+
adminEmail = 'unset';
|
|
173
|
+
}
|
|
174
|
+
return { controllerUrl, adminEmail };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* @param {Object} payload
|
|
179
|
+
* @param {string} payload.outcome
|
|
180
|
+
* @param {Date} payload.startedAt
|
|
181
|
+
* @param {Date} payload.completedAt
|
|
182
|
+
* @param {Object} payload.options
|
|
183
|
+
* @param {string|null} payload.setupMode
|
|
184
|
+
* @param {Error|null} payload.err
|
|
185
|
+
*/
|
|
186
|
+
async function appendSetupInstallationRecord(payload) {
|
|
187
|
+
const cfg = await readConfigSnapshotForSetupLog();
|
|
188
|
+
const platformApps = tryCollectPlatformImagesForLog(payload.outcome);
|
|
189
|
+
const { controllerUrl, adminEmail } = await readSetupLogUrlFields();
|
|
190
|
+
try {
|
|
191
|
+
await installationLog.appendInstallationRecord({
|
|
192
|
+
command: 'setup',
|
|
193
|
+
outcome: payload.outcome,
|
|
194
|
+
startedAt: payload.startedAt,
|
|
195
|
+
completedAt: payload.completedAt,
|
|
196
|
+
options: payload.options,
|
|
197
|
+
setupMode: payload.setupMode || undefined,
|
|
198
|
+
infra: cfg && typeof cfg === 'object' ? { cfg, options: {} } : undefined,
|
|
199
|
+
platformApps,
|
|
200
|
+
configExtra: { controllerUrl, adminEmail },
|
|
201
|
+
error: payload.err || undefined,
|
|
202
|
+
errorCode: payload.err && payload.err.code ? String(payload.err.code) : undefined
|
|
203
|
+
});
|
|
204
|
+
} catch {
|
|
205
|
+
// never block setup on log failure
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
119
209
|
/**
|
|
120
210
|
* Run the setup wizard / dispatcher.
|
|
121
211
|
*
|
|
@@ -129,17 +219,32 @@ async function dispatchMode(mode) {
|
|
|
129
219
|
*/
|
|
130
220
|
async function handleSetup(options = {}) {
|
|
131
221
|
const assumeYes = options.yes === true || options.assumeYes === true;
|
|
222
|
+
const startedAt = new Date();
|
|
223
|
+
let outcome = 'success';
|
|
224
|
+
let setupMode = null;
|
|
225
|
+
let err = null;
|
|
132
226
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
227
|
+
try {
|
|
228
|
+
const main = await runSetupMainPath(options, assumeYes);
|
|
229
|
+
outcome = main.outcome;
|
|
230
|
+
setupMode = main.setupMode;
|
|
231
|
+
} catch (e) {
|
|
232
|
+
outcome = 'failure';
|
|
233
|
+
err = e;
|
|
234
|
+
} finally {
|
|
235
|
+
await appendSetupInstallationRecord({
|
|
236
|
+
outcome,
|
|
237
|
+
startedAt,
|
|
238
|
+
completedAt: new Date(),
|
|
239
|
+
options,
|
|
240
|
+
setupMode,
|
|
241
|
+
err
|
|
242
|
+
});
|
|
243
|
+
}
|
|
137
244
|
|
|
138
|
-
if (
|
|
139
|
-
|
|
140
|
-
return;
|
|
245
|
+
if (err) {
|
|
246
|
+
throw err;
|
|
141
247
|
}
|
|
142
|
-
await runExistingInfraFlow(assumeYes);
|
|
143
248
|
}
|
|
144
249
|
|
|
145
250
|
module.exports = {
|
package/lib/commands/teardown.js
CHANGED
|
@@ -24,6 +24,7 @@ const chalk = require('chalk');
|
|
|
24
24
|
const ora = require('ora');
|
|
25
25
|
|
|
26
26
|
const config = require('../core/config');
|
|
27
|
+
const installationLog = require('../utils/installation-log');
|
|
27
28
|
const infra = require('../infrastructure');
|
|
28
29
|
const pathsUtil = require('../utils/paths');
|
|
29
30
|
const logger = require('../utils/logger');
|
|
@@ -193,6 +194,47 @@ function cleanFilesWithSpinner() {
|
|
|
193
194
|
return result;
|
|
194
195
|
}
|
|
195
196
|
|
|
197
|
+
async function appendTeardownAbortedLog(options) {
|
|
198
|
+
const t = new Date();
|
|
199
|
+
try {
|
|
200
|
+
await installationLog.appendInstallationRecord({
|
|
201
|
+
command: 'teardown',
|
|
202
|
+
outcome: 'aborted',
|
|
203
|
+
startedAt: t,
|
|
204
|
+
completedAt: t,
|
|
205
|
+
options
|
|
206
|
+
});
|
|
207
|
+
} catch {
|
|
208
|
+
// ignore
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function appendTeardownSuccessLog(options, startedAt) {
|
|
213
|
+
let cfg = {};
|
|
214
|
+
try {
|
|
215
|
+
cfg = await config.getConfig();
|
|
216
|
+
} catch {
|
|
217
|
+
cfg = {};
|
|
218
|
+
}
|
|
219
|
+
try {
|
|
220
|
+
await installationLog.appendInstallationRecord({
|
|
221
|
+
command: 'teardown',
|
|
222
|
+
outcome: 'success',
|
|
223
|
+
startedAt,
|
|
224
|
+
completedAt: new Date(),
|
|
225
|
+
options,
|
|
226
|
+
infra: { cfg, options: {} },
|
|
227
|
+
cleanup: { volumesRemoved: true, configPreserved: true },
|
|
228
|
+
configExtra: {
|
|
229
|
+
controllerUrl: await installationLog.resolveControllerUrlForLog(),
|
|
230
|
+
adminEmail: await installationLog.resolveAdminEmailPresence()
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
} catch {
|
|
234
|
+
// ignore
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
196
238
|
/**
|
|
197
239
|
* Run the teardown.
|
|
198
240
|
*
|
|
@@ -208,15 +250,22 @@ async function handleTeardown(options = {}) {
|
|
|
208
250
|
|
|
209
251
|
const ok = await confirmTeardown(assumeYes);
|
|
210
252
|
if (!ok) {
|
|
253
|
+
await appendTeardownAbortedLog(options);
|
|
211
254
|
logger.log(chalk.yellow('Aborted by user.'));
|
|
212
255
|
return;
|
|
213
256
|
}
|
|
214
|
-
await stopInfraQuietly();
|
|
215
|
-
const { removed, failed } = cleanFilesWithSpinner();
|
|
216
|
-
const devStr = await buildDeveloperLabel();
|
|
217
|
-
logTeardownFooter({ devStr, removedCount: removed.length, failedCount: failed.length });
|
|
218
257
|
|
|
219
|
-
|
|
258
|
+
const startedAt = new Date();
|
|
259
|
+
try {
|
|
260
|
+
await stopInfraQuietly();
|
|
261
|
+
const { removed, failed } = cleanFilesWithSpinner();
|
|
262
|
+
const devStr = await buildDeveloperLabel();
|
|
263
|
+
logTeardownFooter({ devStr, removedCount: removed.length, failedCount: failed.length });
|
|
264
|
+
|
|
265
|
+
logger.log(formatSuccessParagraph('aifabrix teardown complete.'));
|
|
266
|
+
} finally {
|
|
267
|
+
await appendTeardownSuccessLog(options, startedAt);
|
|
268
|
+
}
|
|
220
269
|
}
|
|
221
270
|
|
|
222
271
|
module.exports = {
|
|
@@ -22,6 +22,23 @@ const { copyTemplateFiles } = require('../validation/template');
|
|
|
22
22
|
const { ensureReadmeForAppPath, ensureReadmeForApp } = require('../app/readme');
|
|
23
23
|
const { refreshUrlsLocalRegistryFromBuilder } = require('../utils/urls-local-registry');
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* @param {string[]} entries
|
|
27
|
+
* @returns {string[]}
|
|
28
|
+
*/
|
|
29
|
+
function uniqueResolvedRoots(entries) {
|
|
30
|
+
const seen = new Set();
|
|
31
|
+
const out = [];
|
|
32
|
+
for (const e of entries) {
|
|
33
|
+
const r = path.resolve(e);
|
|
34
|
+
if (!seen.has(r)) {
|
|
35
|
+
seen.add(r);
|
|
36
|
+
out.push(r);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
|
|
25
42
|
/**
|
|
26
43
|
* Copy template to a target path if application config is missing there.
|
|
27
44
|
* After copy, generates README.md from templates/applications/README.md.hbs.
|
|
@@ -226,7 +243,11 @@ function isUnderAllowedBuilderRoot(resolvedAppPath, allowedRoots) {
|
|
|
226
243
|
async function cleanBuilderAppDirs(appNames, opts = {}) {
|
|
227
244
|
const silent = Boolean(opts.silent);
|
|
228
245
|
if (!Array.isArray(appNames) || appNames.length === 0) return;
|
|
229
|
-
const allowedRoots = [
|
|
246
|
+
const allowedRoots = uniqueResolvedRoots([
|
|
247
|
+
path.join(process.cwd(), 'builder'),
|
|
248
|
+
pathsUtil.getBuilderRoot(),
|
|
249
|
+
pathsUtil.getSystemBuilderRoot()
|
|
250
|
+
]);
|
|
230
251
|
const cleaned = [];
|
|
231
252
|
for (const appName of appNames) {
|
|
232
253
|
if (!appName || typeof appName !== 'string') continue;
|
|
@@ -253,8 +274,9 @@ async function cleanBuilderAppDirs(appNames, opts = {}) {
|
|
|
253
274
|
* Uses AIFABRIX_BUILDER_DIR when set (e.g. by up-miso/up-dataplane from config aifabrix-env-config).
|
|
254
275
|
* When `process.env.AIFABRIX_BUILDER_DIR` is set and the primary app path differs from
|
|
255
276
|
* `cwd/builder/<appName>`, also copies into `cwd/builder/<appName>` so the repo tree is not empty
|
|
256
|
-
* while using a custom builder root. Skips that extra copy
|
|
257
|
-
*
|
|
277
|
+
* while using a custom builder root. Skips that extra copy for platform apps (`keycloak`,
|
|
278
|
+
* `miso-controller`, `dataplane`) so they materialize only under the system builder root, and when
|
|
279
|
+
* no custom dir is in use.
|
|
258
280
|
*
|
|
259
281
|
* @async
|
|
260
282
|
* @function ensureAppFromTemplate
|
|
@@ -281,7 +303,8 @@ async function ensureAppFromTemplate(appName) {
|
|
|
281
303
|
const cwdBuilderPath = path.join(process.cwd(), 'builder', appName);
|
|
282
304
|
if (
|
|
283
305
|
envBuilderRoot &&
|
|
284
|
-
path.resolve(cwdBuilderPath) !== path.resolve(appPath)
|
|
306
|
+
path.resolve(cwdBuilderPath) !== path.resolve(appPath) &&
|
|
307
|
+
!pathsUtil.isSystemBuilderAppName(appName)
|
|
285
308
|
) {
|
|
286
309
|
const cwdCopied = await ensureTemplateAtPath(appName, cwdBuilderPath);
|
|
287
310
|
if (cwdCopied) {
|
|
@@ -295,22 +318,32 @@ async function ensureAppFromTemplate(appName) {
|
|
|
295
318
|
}
|
|
296
319
|
|
|
297
320
|
/**
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
301
|
-
|
|
321
|
+
* Merge builder/packages `application.yaml` into `~/.aifabrix/urls.local.yaml`.
|
|
322
|
+
* Same scan as the end of {@link prepareUrlsLocalRegistryForUpPlatform}; call after platform app
|
|
323
|
+
* templates exist (e.g. `up-miso`, `up-dataplane`) so declarative `url://` and tooling see ports/patterns.
|
|
324
|
+
*/
|
|
325
|
+
function refreshUrlsLocalRegistryForCurrentProject() {
|
|
326
|
+
try {
|
|
327
|
+
refreshUrlsLocalRegistryFromBuilder(pathsUtil.getProjectRoot());
|
|
328
|
+
} catch (error) {
|
|
329
|
+
logger.warn(chalk.yellow(`⚠ Could not refresh URLs registry: ${error.message}`));
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* For `aifabrix up-platform` only: materialize all three platform apps from templates if missing
|
|
335
|
+
* (paths follow cwd / material `builder/` only — no `AIFABRIX_BUILDER_DIR` override), then refresh
|
|
336
|
+
* `~/.aifabrix/urls.local.yaml` so declarative `url://` expansion (e.g. cross-references between
|
|
337
|
+
* miso-controller, dataplane, keycloak) sees every app's port and pattern before Keycloak or Miso
|
|
338
|
+
* resolve their `.env` files.
|
|
302
339
|
*
|
|
303
340
|
* @returns {Promise<void>}
|
|
304
341
|
*/
|
|
305
342
|
async function prepareUrlsLocalRegistryForUpPlatform() {
|
|
306
|
-
const builderDir = await config.getAifabrixBuilderDir();
|
|
307
|
-
if (builderDir) {
|
|
308
|
-
process.env.AIFABRIX_BUILDER_DIR = path.resolve(builderDir);
|
|
309
|
-
}
|
|
310
343
|
await ensureAppFromTemplate('keycloak');
|
|
311
344
|
await ensureAppFromTemplate('miso-controller');
|
|
312
345
|
await ensureAppFromTemplate('dataplane');
|
|
313
|
-
|
|
346
|
+
refreshUrlsLocalRegistryForCurrentProject();
|
|
314
347
|
}
|
|
315
348
|
|
|
316
349
|
module.exports = {
|
|
@@ -320,5 +353,6 @@ module.exports = {
|
|
|
320
353
|
patchEnvOutputPathForDeployOnly,
|
|
321
354
|
validateEnvOutputPathFolderOrNull,
|
|
322
355
|
getEnvOutputPathFolder,
|
|
323
|
-
prepareUrlsLocalRegistryForUpPlatform
|
|
356
|
+
prepareUrlsLocalRegistryForUpPlatform,
|
|
357
|
+
refreshUrlsLocalRegistryForCurrentProject
|
|
324
358
|
};
|
|
@@ -12,7 +12,6 @@ const { formatSuccessLine, formatSuccessParagraph } = require('../utils/cli-test
|
|
|
12
12
|
* @version 2.0.0
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
const path = require('path');
|
|
16
15
|
const readline = require('readline');
|
|
17
16
|
const chalk = require('chalk');
|
|
18
17
|
const pathsUtil = require('../utils/paths');
|
|
@@ -30,7 +29,7 @@ const { checkHealthEndpoint } = require('../utils/health-check');
|
|
|
30
29
|
const { validateControllerUrl } = require('../utils/auth-config-validator');
|
|
31
30
|
const app = require('../app');
|
|
32
31
|
const { assertDevInfraUp } = require('./dev-infra-gate');
|
|
33
|
-
const { ensureAppFromTemplate, validateEnvOutputPathFolderOrNull } = require('./up-common');
|
|
32
|
+
const { ensureAppFromTemplate, validateEnvOutputPathFolderOrNull, refreshUrlsLocalRegistryForCurrentProject } = require('./up-common');
|
|
34
33
|
|
|
35
34
|
const CONTROLLER_HEALTH_PATH = '/health';
|
|
36
35
|
|
|
@@ -168,14 +167,17 @@ function buildDataplaneImageRef(options = {}) {
|
|
|
168
167
|
}
|
|
169
168
|
}
|
|
170
169
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
170
|
+
function emitDataplaneManifestLineFromBuilder() {
|
|
171
|
+
const { emitSystemBuilderAppManifestLineIfTTY } = require('../utils/manifest-source-emit');
|
|
172
|
+
emitSystemBuilderAppManifestLineIfTTY(logger, 'dataplane');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function assertDevEnvironmentForDataplane(cfg) {
|
|
176
|
+
const environment = (cfg && cfg.environment) ? cfg.environment : 'dev';
|
|
177
|
+
if (environment !== 'dev') {
|
|
178
|
+
throw new Error(
|
|
179
|
+
'Dataplane is only supported in dev environment. Set with: aifabrix auth --set-environment dev.'
|
|
180
|
+
);
|
|
179
181
|
}
|
|
180
182
|
}
|
|
181
183
|
|
|
@@ -195,7 +197,6 @@ async function applyAifabrixBuilderDirEnv() {
|
|
|
195
197
|
* @throws {Error} If infra not up, controller unavailable, not logged in, environment not dev, or any step fails
|
|
196
198
|
*/
|
|
197
199
|
async function handleUpDataplane(options = {}) {
|
|
198
|
-
await applyAifabrixBuilderDirEnv();
|
|
199
200
|
logger.log(chalk.blue('Starting up-dataplane (register/rotate, deploy, then run dataplane locally)...\n'));
|
|
200
201
|
|
|
201
202
|
if (options.skipInfraCheck !== true) {
|
|
@@ -207,18 +208,19 @@ async function handleUpDataplane(options = {}) {
|
|
|
207
208
|
const authConfig = await checkAuthentication(controllerUrl, environmentKey, { throwOnFailure: true });
|
|
208
209
|
|
|
209
210
|
const cfg = await config.getConfig();
|
|
210
|
-
|
|
211
|
-
if (environment !== 'dev') {
|
|
212
|
-
throw new Error(
|
|
213
|
-
'Dataplane is only supported in dev environment. Set with: aifabrix auth --set-environment dev.'
|
|
214
|
-
);
|
|
215
|
-
}
|
|
211
|
+
assertDevEnvironmentForDataplane(cfg);
|
|
216
212
|
logger.log(formatSuccessLine('Logged in and environment is dev'));
|
|
217
213
|
|
|
218
214
|
await ensureAppFromTemplate('dataplane');
|
|
219
215
|
// If envOutputPath target folder does not exist, set envOutputPath to null
|
|
220
216
|
validateEnvOutputPathFolderOrNull('dataplane');
|
|
221
217
|
|
|
218
|
+
refreshUrlsLocalRegistryForCurrentProject();
|
|
219
|
+
|
|
220
|
+
if (options.platformInstall !== true) {
|
|
221
|
+
emitDataplaneManifestLineFromBuilder();
|
|
222
|
+
}
|
|
223
|
+
|
|
222
224
|
await registerOrRotateDataplane(options, controllerUrl, environmentKey, authConfig);
|
|
223
225
|
|
|
224
226
|
await deployDataplaneToController(options);
|
|
@@ -226,7 +228,8 @@ async function handleUpDataplane(options = {}) {
|
|
|
226
228
|
await app.runApp('dataplane', {
|
|
227
229
|
skipEnvOutputPath: true,
|
|
228
230
|
registry: options.registry || undefined,
|
|
229
|
-
base: options.base !== false
|
|
231
|
+
base: options.base !== false,
|
|
232
|
+
skipManifestMetadataLine: true
|
|
230
233
|
});
|
|
231
234
|
|
|
232
235
|
logger.log(formatSuccessParagraph('up-dataplane complete. Dataplane is registered, deployed in dev, and running locally.'));
|
package/lib/commands/up-miso.js
CHANGED
|
@@ -10,13 +10,16 @@ const { formatSuccessParagraph } = require('../utils/cli-test-layout-chalk');
|
|
|
10
10
|
* @version 2.0.0
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
const path = require('path');
|
|
14
13
|
const chalk = require('chalk');
|
|
15
14
|
const logger = require('../utils/logger');
|
|
16
|
-
const config = require('../core/config');
|
|
17
15
|
const app = require('../app');
|
|
18
16
|
const { assertDevInfraUp } = require('./dev-infra-gate');
|
|
19
|
-
const {
|
|
17
|
+
const {
|
|
18
|
+
ensureAppFromTemplate,
|
|
19
|
+
patchEnvOutputPathForDeployOnly,
|
|
20
|
+
validateEnvOutputPathFolderOrNull,
|
|
21
|
+
refreshUrlsLocalRegistryForCurrentProject
|
|
22
|
+
} = require('./up-common');
|
|
20
23
|
|
|
21
24
|
/**
|
|
22
25
|
* Parse --image options array into map { keycloak?: string, 'miso-controller'?: string }
|
|
@@ -51,7 +54,8 @@ async function runMisoApps(options) {
|
|
|
51
54
|
registryMode: options.registryMode,
|
|
52
55
|
skipEnvOutputPath: true,
|
|
53
56
|
skipInfraCheck: true,
|
|
54
|
-
base: useBaseImage
|
|
57
|
+
base: useBaseImage,
|
|
58
|
+
skipManifestMetadataLine: true
|
|
55
59
|
};
|
|
56
60
|
const keycloakRunOpts = { ...common };
|
|
57
61
|
if (imageMap.keycloak) {
|
|
@@ -61,9 +65,12 @@ async function runMisoApps(options) {
|
|
|
61
65
|
if (imageMap['miso-controller']) {
|
|
62
66
|
misoRunOpts.image = imageMap['miso-controller'];
|
|
63
67
|
}
|
|
68
|
+
const { emitSystemBuilderAppManifestLineIfTTY } = require('../utils/manifest-source-emit');
|
|
64
69
|
logger.log(chalk.blue('Starting keycloak...'));
|
|
70
|
+
emitSystemBuilderAppManifestLineIfTTY(logger, 'keycloak');
|
|
65
71
|
await app.runApp('keycloak', keycloakRunOpts);
|
|
66
72
|
logger.log(chalk.blue('Starting miso-controller...'));
|
|
73
|
+
emitSystemBuilderAppManifestLineIfTTY(logger, 'miso-controller');
|
|
67
74
|
await app.runApp('miso-controller', misoRunOpts);
|
|
68
75
|
}
|
|
69
76
|
|
|
@@ -80,10 +87,6 @@ async function runMisoApps(options) {
|
|
|
80
87
|
* @throws {Error} If infra not up or any step fails
|
|
81
88
|
*/
|
|
82
89
|
async function handleUpMiso(options = {}) {
|
|
83
|
-
const builderDir = await config.getAifabrixBuilderDir();
|
|
84
|
-
if (builderDir) {
|
|
85
|
-
process.env.AIFABRIX_BUILDER_DIR = path.resolve(builderDir);
|
|
86
|
-
}
|
|
87
90
|
logger.log(chalk.blue('Starting up-miso (keycloak + miso-controller from images)...\n'));
|
|
88
91
|
await assertDevInfraUp();
|
|
89
92
|
await ensureAppFromTemplate('keycloak');
|
|
@@ -94,6 +97,7 @@ async function handleUpMiso(options = {}) {
|
|
|
94
97
|
// Deploy-only: do not copy .env to repo paths; patch variables so envOutputPath is null
|
|
95
98
|
patchEnvOutputPathForDeployOnly('keycloak');
|
|
96
99
|
patchEnvOutputPathForDeployOnly('miso-controller');
|
|
100
|
+
refreshUrlsLocalRegistryForCurrentProject();
|
|
97
101
|
await runMisoApps(options);
|
|
98
102
|
logger.log(formatSuccessParagraph('up-miso complete. Keycloak and miso-controller are running.') +
|
|
99
103
|
chalk.gray('\n Run onboarding and register Keycloak from the miso-controller repo if needed. Use \'aifabrix up-dataplane\' for dataplane.'));
|
package/lib/commands/upload.js
CHANGED
|
@@ -279,8 +279,10 @@ async function buildValidatedUploadManifestPayload(systemKey, _opts = {}) {
|
|
|
279
279
|
}
|
|
280
280
|
|
|
281
281
|
/**
|
|
282
|
-
* Upload local `integration/<systemKey>/openapi/*.json` specs when present so
|
|
283
|
-
* each datasource `openapi.documentKey` (same behavior as repair --api OpenAPI sync).
|
|
282
|
+
* Upload local `integration/<systemKey>/openapi/*.json` specs when present so dataplane can store
|
|
283
|
+
* vendor OpenAPI keyed by each datasource `openapi.documentKey` (same behavior as repair --api OpenAPI sync).
|
|
284
|
+
* Call this **after** a successful pipeline publish: list/upload endpoints require the external system row.
|
|
285
|
+
* First publish still succeeds because pipeline materializes internal specs from `openapi.operations` when needed.
|
|
284
286
|
*
|
|
285
287
|
* @param {string} systemKey
|
|
286
288
|
* @param {Object} manifest
|
|
@@ -374,9 +376,9 @@ async function runUploadPublishAndSummary(systemKey, options, manifest, payload)
|
|
|
374
376
|
await maybeRunVerboseServerValidation(dataplaneUrl, authConfig, payload);
|
|
375
377
|
}
|
|
376
378
|
await pushAndLogCredentialSecrets(dataplaneUrl, authConfig, systemKey, payload);
|
|
377
|
-
await logAndSyncLocalOpenApiForUpload(systemKey, manifest);
|
|
378
379
|
|
|
379
380
|
const rawRes = await runUploadValidatePublish(dataplaneUrl, authConfig, payload);
|
|
381
|
+
await logAndSyncLocalOpenApiForUpload(systemKey, manifest);
|
|
380
382
|
await handlePublicationAndFollowups({
|
|
381
383
|
systemKey,
|
|
382
384
|
options,
|
package/lib/core/audit-logger.js
CHANGED
|
@@ -15,6 +15,7 @@ const fs = require('fs').promises;
|
|
|
15
15
|
const path = require('path');
|
|
16
16
|
const os = require('os');
|
|
17
17
|
const paths = require('../utils/paths');
|
|
18
|
+
const { maskSensitiveData } = require('../utils/log-redaction');
|
|
18
19
|
|
|
19
20
|
// Audit log file path (beside config.yaml / CLI system dir for compliance)
|
|
20
21
|
let auditLogPath = null;
|
|
@@ -51,40 +52,6 @@ async function getAuditLogPath() {
|
|
|
51
52
|
return auditLogPath;
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
/**
|
|
55
|
-
* Masks sensitive data in strings
|
|
56
|
-
* Prevents secrets, keys, and passwords from appearing in logs
|
|
57
|
-
*
|
|
58
|
-
* @param {string} value - Value to mask
|
|
59
|
-
* @returns {string} Masked value
|
|
60
|
-
*/
|
|
61
|
-
function maskSensitiveData(value) {
|
|
62
|
-
if (!value || typeof value !== 'string') {
|
|
63
|
-
return value;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Mask patterns: passwords, secrets, keys, tokens
|
|
67
|
-
const sensitivePatterns = [
|
|
68
|
-
{ pattern: /password[=:]\s*([^\s]+)/gi, replacement: 'password=***' },
|
|
69
|
-
{ pattern: /secret[=:]\s*([^\s]+)/gi, replacement: 'secret=***' },
|
|
70
|
-
{ pattern: /key[=:]\s*([^\s]+)/gi, replacement: 'key=***' },
|
|
71
|
-
{ pattern: /token[=:]\s*([^\s]+)/gi, replacement: 'token=***' },
|
|
72
|
-
{ pattern: /api[_-]?key[=:]\s*([^\s]+)/gi, replacement: 'api_key=***' }
|
|
73
|
-
];
|
|
74
|
-
|
|
75
|
-
let masked = value;
|
|
76
|
-
for (const { pattern, replacement } of sensitivePatterns) {
|
|
77
|
-
masked = masked.replace(pattern, replacement);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// If value looks like a hash/key (long hex string), mask it
|
|
81
|
-
if (/^[a-f0-9]{32,}$/i.test(masked.trim())) {
|
|
82
|
-
return '***';
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return masked;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
55
|
/**
|
|
89
56
|
* Creates an audit log entry with ISO 27001 compliance
|
|
90
57
|
*
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin email field in `config.yaml` (Keycloak / pgAdmin), written by `aifabrix setup`.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview config.yaml adminEmail helpers (keeps config.js under max-lines)
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {unknown} email
|
|
13
|
+
* @returns {string} Trimmed valid email
|
|
14
|
+
* @throws {Error} When empty or not a plausible email
|
|
15
|
+
*/
|
|
16
|
+
function validateAdminEmailForConfig(email) {
|
|
17
|
+
const value = String(email ?? '').trim();
|
|
18
|
+
if (!value) {
|
|
19
|
+
throw new Error('Admin email must be a non-empty string');
|
|
20
|
+
}
|
|
21
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
|
|
22
|
+
throw new Error('Admin email must be a valid email address');
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {() => Promise<Object>} getConfig
|
|
29
|
+
* @returns {Promise<string>}
|
|
30
|
+
*/
|
|
31
|
+
async function getAdminEmailFromConfig(getConfig) {
|
|
32
|
+
const cfg = await getConfig();
|
|
33
|
+
if (cfg && typeof cfg.adminEmail === 'string' && cfg.adminEmail.trim()) {
|
|
34
|
+
return cfg.adminEmail.trim();
|
|
35
|
+
}
|
|
36
|
+
return '';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @param {() => Promise<Object>} getConfig
|
|
41
|
+
* @param {(data: Object) => Promise<void>} saveConfig
|
|
42
|
+
* @param {string} email
|
|
43
|
+
* @returns {Promise<void>}
|
|
44
|
+
*/
|
|
45
|
+
async function setAdminEmailInConfig(getConfig, saveConfig, email) {
|
|
46
|
+
const trimmed = validateAdminEmailForConfig(email);
|
|
47
|
+
const cfg = await getConfig();
|
|
48
|
+
cfg.adminEmail = trimmed;
|
|
49
|
+
await saveConfig(cfg);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = {
|
|
53
|
+
validateAdminEmailForConfig,
|
|
54
|
+
getAdminEmailFromConfig,
|
|
55
|
+
setAdminEmailInConfig
|
|
56
|
+
};
|