@aifabrix/builder 2.44.6 → 2.45.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/.cursor/rules/cli-layout.mdc +7 -3
- package/.cursor/rules/docs-rules.mdc +3 -1
- package/integration/hubspot-test/test-artifacts/wizard-hubspot-credential-real.yaml +1 -1
- package/integration/hubspot-test/test-artifacts/wizard-hubspot-env-vars.yaml +1 -1
- package/integration/hubspot-test/wizard-hubspot-e2e.yaml +1 -1
- package/jest.projects.js +100 -1
- package/lib/api/agent-metadata-validation.api.js +93 -0
- package/lib/api/audit-api-helpers.js +68 -0
- package/lib/api/audit.api.js +119 -0
- package/lib/api/auth-cache.api.js +44 -0
- package/lib/api/certificates.api.js +4 -4
- package/lib/api/credential.api.js +2 -2
- package/lib/api/dataplane-health.api.js +137 -0
- package/lib/api/dataplane-sync.api.js +27 -0
- package/lib/api/datasources-core.api.js +21 -18
- package/lib/api/datasources-extended.api.js +22 -22
- package/lib/api/dimensions.api.js +3 -0
- package/lib/api/external-systems.api.js +13 -13
- package/lib/api/governance-scenarios.api.js +39 -0
- package/lib/api/groups.api.js +79 -0
- package/lib/api/index.js +45 -2
- package/lib/api/pipeline.api.js +4 -4
- package/lib/api/protection.api.js +157 -0
- package/lib/api/records-bulk.api.js +30 -0
- package/lib/api/records-search-parse.js +49 -0
- package/lib/api/records-search.api.js +29 -0
- package/lib/api/types/agent-metadata-validation.types.js +67 -0
- package/lib/api/types/audit.types.js +72 -0
- package/lib/api/types/dataplane-health.types.js +27 -0
- package/lib/api/types/dimensions.types.js +2 -0
- package/lib/api/types/governance-scenarios.types.js +39 -0
- package/lib/api/types/protection.types.js +69 -0
- package/lib/api/types/records-bulk.types.js +35 -0
- package/lib/api/types/records-search.types.js +28 -0
- package/lib/api/user-groups.api.js +50 -0
- package/lib/api/users.api.js +95 -0
- package/lib/api/validation-run.api.js +3 -3
- package/lib/api/validation-runner.js +3 -3
- package/lib/api/wizard-platform.api.js +3 -3
- package/lib/api/wizard.api.js +21 -21
- package/lib/app/deploy.js +34 -19
- 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/index.js +4 -2
- package/lib/cli/infra-guided.js +62 -28
- package/lib/cli/installation-log-command.js +73 -0
- package/lib/cli/setup-app.help.js +50 -2
- package/lib/cli/setup-app.js +44 -25
- package/lib/cli/setup-app.test-commands.js +80 -21
- package/lib/cli/setup-auth.js +124 -52
- package/lib/cli/setup-external-system.js +37 -3
- 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-help-after.js +45 -0
- package/lib/cli/setup-utility-resolve.js +132 -0
- package/lib/cli/setup-utility.js +148 -116
- package/lib/commands/app-logs.js +81 -33
- package/lib/commands/auth-config.js +116 -18
- package/lib/commands/auth-status-dataplane-version.js +231 -0
- package/lib/commands/auth-status-display.js +94 -0
- package/lib/commands/auth-status.js +70 -6
- package/lib/commands/credential-push.js +10 -5
- package/lib/commands/datasource-load-export-cli.js +178 -0
- package/lib/commands/datasource-test-trust-cli.js +114 -0
- package/lib/commands/datasource-unified-test-cli.js +25 -17
- package/lib/commands/datasource-unified-test-cli.options.js +18 -3
- package/lib/commands/datasource-unified-test-e2e-cli-helpers.js +5 -5
- package/lib/commands/datasource-verify-audit-cli.js +96 -0
- package/lib/commands/datasource.js +68 -1
- package/lib/commands/dimension-value.js +43 -13
- package/lib/commands/dimension.js +38 -7
- package/lib/commands/identity-apply-sync.js +131 -0
- package/lib/commands/identity-group-cli.js +146 -0
- package/lib/commands/identity-membership-role-cache.js +175 -0
- package/lib/commands/identity-shared.js +94 -0
- package/lib/commands/identity-user-cli.js +158 -0
- package/lib/commands/identity.js +43 -0
- package/lib/commands/login.js +46 -16
- package/lib/commands/protection-cli-leaves.js +286 -0
- package/lib/commands/protection.js +50 -0
- package/lib/commands/repair.js +13 -3
- 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/test-e2e-external.js +40 -9
- package/lib/commands/test-governance-cli-options.js +47 -0
- package/lib/commands/test-governance-command-action.js +55 -0
- package/lib/commands/test-governance-external.js +88 -0
- package/lib/commands/test-trust-cli-options.js +32 -0
- package/lib/commands/test-trust-command-action.js +118 -0
- package/lib/commands/test-trust-external.js +127 -0
- 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 +38 -15
- package/lib/commands/wizard-core.js +3 -0
- package/lib/core/audit-logger.js +1 -34
- package/lib/core/config-admin-email.js +56 -0
- package/lib/core/config-device-dataplane.js +185 -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/datasource/agent-trust-debug-log.js +45 -0
- package/lib/datasource/agent-trust-fetch.js +115 -0
- package/lib/datasource/agent-trust-map.js +101 -0
- package/lib/datasource/agent-trust-mock.js +44 -0
- package/lib/datasource/agent-trust-run.js +93 -0
- package/lib/datasource/agent-trust-timeout.js +25 -0
- package/lib/datasource/audit-evidence-display.js +80 -0
- package/lib/datasource/audit-evidence-extract-debug.js +103 -0
- package/lib/datasource/audit-evidence-extract.js +177 -0
- package/lib/datasource/audit-evidence-matrix-rows.js +158 -0
- package/lib/datasource/audit-evidence-operation-aliases.js +63 -0
- package/lib/datasource/audit-evidence-poll.js +109 -0
- package/lib/datasource/audit-evidence-run.js +177 -0
- package/lib/datasource/audit-evidence-verify-helpers.js +167 -0
- package/lib/datasource/audit-evidence-verify-matrix.js +177 -0
- package/lib/datasource/audit-evidence-verify.js +108 -0
- package/lib/datasource/bulk-loader-run-batches.js +88 -0
- package/lib/datasource/bulk-loader-service-retry.js +52 -0
- package/lib/datasource/bulk-loader-service.js +127 -0
- package/lib/datasource/capability/run-capability-dimension.js +2 -1
- package/lib/datasource/datasource-exporter-resolve.js +82 -0
- package/lib/datasource/datasource-exporter-service.js +144 -0
- package/lib/datasource/datasource-identifier.js +148 -0
- package/lib/datasource/datasource-load-export-context.js +92 -0
- package/lib/datasource/integration-context.js +6 -4
- package/lib/datasource/local-data-paths.js +145 -0
- package/lib/datasource/log-cleaner.js +241 -0
- package/lib/datasource/log-viewer.js +111 -6
- package/lib/datasource/record-file-parser.js +85 -0
- package/lib/datasource/record-mapper.js +129 -0
- package/lib/datasource/resolve-app.js +74 -55
- package/lib/datasource/test-e2e.js +26 -2
- package/lib/datasource/unified-validation-run-resolve.js +45 -12
- package/lib/datasource/unified-validation-run.js +43 -17
- package/lib/datasource/validate.js +37 -20
- package/lib/external-system/deploy.js +5 -1
- package/lib/external-system/test-system-level.js +25 -4
- package/lib/external-system/test.js +3 -1
- package/lib/governance/governance-pack-loader.js +70 -0
- package/lib/governance/governance-run-display.js +119 -0
- package/lib/identity/identity-apply-core.js +108 -0
- package/lib/identity/identity-apply-phases.js +244 -0
- package/lib/identity/identity-apply-service.js +77 -0
- package/lib/identity/identity-csv-parser.js +114 -0
- package/lib/internal/fs-real-sync.js +29 -1
- package/lib/internal/node-fs.js +2 -0
- package/lib/protection/auth-context.js +45 -0
- package/lib/protection/convert-batch.js +90 -0
- package/lib/protection/load.js +41 -0
- package/lib/protection/paths.js +158 -0
- package/lib/protection/preflight-datasource-ready.js +46 -0
- package/lib/protection/protection-create-probe-lines.js +142 -0
- package/lib/protection/protection-create-probes.js +95 -0
- package/lib/protection/protection-create-scaffold.js +281 -0
- package/lib/protection/protection-display-helpers.js +445 -0
- package/lib/protection/protection-display.js +215 -0
- package/lib/protection/protection-preset-registry.js +97 -0
- package/lib/protection/report-exit.js +62 -0
- package/lib/protection/resolve.js +202 -0
- package/lib/protection/run-commands-validate.js +130 -0
- package/lib/protection/run-commands.js +216 -0
- package/lib/protection/run-protection-create-helpers.js +134 -0
- package/lib/protection/run-protection-create-probes-flow.js +43 -0
- package/lib/protection/run-protection-create.js +191 -0
- package/lib/protection/scope.js +22 -0
- package/lib/protection/sync-after-upload.js +64 -0
- package/lib/protection/upload-batch.js +127 -0
- package/lib/protection/validate-batch.js +113 -0
- package/lib/protection/validate-local.js +65 -0
- package/lib/resolvers/dimension-file.js +29 -4
- package/lib/schema/application-schema.json +4 -0
- package/lib/schema/external-datasource.schema.json +47 -13
- package/lib/schema/flag-map-validation-run.json +2 -2
- package/lib/schema/infra.parameter.yaml +10 -0
- package/lib/schema/protection.schema.json +374 -0
- package/lib/schema/type/credential-field-registry.json +636 -0
- package/lib/schema/wizard-config.schema.json +1 -1
- package/lib/utils/agent-trust-run-display.js +231 -0
- package/lib/utils/agent-trust-run-exit.js +59 -0
- package/lib/utils/agent-trust-wait-ui.js +94 -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/cli-exit-codes.js +30 -0
- package/lib/utils/cli-sync-options.js +33 -0
- 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/credential-push-ui.js +90 -0
- package/lib/utils/dataplane-cli-version-gate.js +202 -0
- package/lib/utils/dataplane-cli-version-help.js +74 -0
- package/lib/utils/datasource-export-display.js +137 -0
- package/lib/utils/datasource-load-display.js +174 -0
- package/lib/utils/datasource-load-export-ui.js +120 -0
- package/lib/utils/datasource-test-run-capability-scope.js +3 -3
- package/lib/utils/datasource-test-run-display.js +3 -3
- package/lib/utils/datasource-test-run-tty-log.js +5 -1
- 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/help-builder.js +30 -24
- 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 +184 -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/semver-compare.js +127 -0
- package/lib/utils/system-builder-root.js +10 -2
- package/lib/utils/upload-pipeline-ui.js +91 -0
- 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/lib/utils/validation-poll-ui.js +14 -7
- package/lib/validation/wizard-config-validator.js +58 -19
- package/package.json +3 -1
- package/templates/applications/dataplane/application.yaml +4 -0
- package/templates/applications/dataplane/env.template +13 -1
- package/templates/applications/dataplane/rbac.yaml +4 -0
- package/templates/applications/miso-controller/application.yaml +2 -0
- package/templates/applications/miso-controller/env.template +27 -29
- package/templates/external-system/README.md.hbs +56 -8
- package/.npmrc.token +0 -1
|
@@ -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
|
@@ -25,6 +25,8 @@ const { getDeploymentAuth, requireBearerForDataplanePipeline } = require('../uti
|
|
|
25
25
|
const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
|
|
26
26
|
const { getIntegrationPath } = require('../utils/paths');
|
|
27
27
|
const { pushCredentialSecrets } = require('../utils/credential-secrets-env');
|
|
28
|
+
const { runWithCredentialPushSpinner } = require('../utils/credential-push-ui');
|
|
29
|
+
const { runWithUploadPipelineSpinner } = require('../utils/upload-pipeline-ui');
|
|
28
30
|
const {
|
|
29
31
|
buildResolvedEnvMapForIntegration,
|
|
30
32
|
resolveConfigurationValues,
|
|
@@ -49,6 +51,7 @@ const {
|
|
|
49
51
|
const { maybeSyncSystemCertificationFromDataplane } = require('../certification/sync-system-certification');
|
|
50
52
|
const { cliOptsSkipCertSync } = require('../certification/cli-cert-sync-skip');
|
|
51
53
|
const { maybeSyncOpenApiFilesForMcp } = require('./repair-openapi-sync');
|
|
54
|
+
const { assertDataplaneCliVersionCompatible } = require('../utils/dataplane-cli-version-gate');
|
|
52
55
|
|
|
53
56
|
/**
|
|
54
57
|
* Validates system-key format (same as download).
|
|
@@ -170,13 +173,17 @@ async function resolvePayloadConfiguration(systemKey, payload) {
|
|
|
170
173
|
* @param {string} systemKey - System key
|
|
171
174
|
* @param {Object} payload - Upload payload
|
|
172
175
|
*/
|
|
173
|
-
async function pushAndLogCredentialSecrets(dataplaneUrl, authConfig, systemKey, payload) {
|
|
176
|
+
async function pushAndLogCredentialSecrets(dataplaneUrl, authConfig, systemKey, payload, uiOpts = {}) {
|
|
174
177
|
const envFilePath = path.join(getIntegrationPath(systemKey), '.env');
|
|
175
|
-
const pushResult = await
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
178
|
+
const pushResult = await runWithCredentialPushSpinner(
|
|
179
|
+
() =>
|
|
180
|
+
pushCredentialSecrets(dataplaneUrl, authConfig, {
|
|
181
|
+
envFilePath,
|
|
182
|
+
appName: systemKey,
|
|
183
|
+
payload
|
|
184
|
+
}),
|
|
185
|
+
{ systemKey, json: uiOpts.json === true }
|
|
186
|
+
);
|
|
180
187
|
if (pushResult.pushed > 0) {
|
|
181
188
|
const keyList = pushResult.keys?.length ? ` (${pushResult.keys.join(', ')})` : '';
|
|
182
189
|
logger.log(
|
|
@@ -279,8 +286,10 @@ async function buildValidatedUploadManifestPayload(systemKey, _opts = {}) {
|
|
|
279
286
|
}
|
|
280
287
|
|
|
281
288
|
/**
|
|
282
|
-
* Upload local `integration/<systemKey>/openapi/*.json` specs when present so
|
|
283
|
-
* each datasource `openapi.documentKey` (same behavior as repair --api OpenAPI sync).
|
|
289
|
+
* Upload local `integration/<systemKey>/openapi/*.json` specs when present so dataplane can store
|
|
290
|
+
* vendor OpenAPI keyed by each datasource `openapi.documentKey` (same behavior as repair --api OpenAPI sync).
|
|
291
|
+
* Call this **after** a successful pipeline publish: list/upload endpoints require the external system row.
|
|
292
|
+
* First publish still succeeds because pipeline materializes internal specs from `openapi.operations` when needed.
|
|
284
293
|
*
|
|
285
294
|
* @param {string} systemKey
|
|
286
295
|
* @param {Object} manifest
|
|
@@ -362,8 +371,13 @@ async function handlePublicationAndFollowups(ctx) {
|
|
|
362
371
|
* @param {Object} payload
|
|
363
372
|
*/
|
|
364
373
|
async function runUploadPublishAndSummary(systemKey, options, manifest, payload) {
|
|
365
|
-
const { dataplaneUrl, authConfig, environment } = await resolveDataplaneAndAuth(systemKey
|
|
374
|
+
const { dataplaneUrl, authConfig, environment } = await resolveDataplaneAndAuth(systemKey, {
|
|
375
|
+
silent: options.silentResolve === true
|
|
376
|
+
});
|
|
366
377
|
requireBearerForDataplanePipeline(authConfig);
|
|
378
|
+
const controllerUrl = await resolveControllerUrl();
|
|
379
|
+
// Plan 142.0: gate dataplane API entrypoints by the dataplane-advertised minimum Builder CLI version.
|
|
380
|
+
await assertDataplaneCliVersionCompatible(dataplaneUrl, { controllerUrl });
|
|
367
381
|
logger.log('');
|
|
368
382
|
logger.log(sectionTitle('Target'));
|
|
369
383
|
logger.log(SEP);
|
|
@@ -373,10 +387,18 @@ async function runUploadPublishAndSummary(systemKey, options, manifest, payload)
|
|
|
373
387
|
if (options.verbose) {
|
|
374
388
|
await maybeRunVerboseServerValidation(dataplaneUrl, authConfig, payload);
|
|
375
389
|
}
|
|
376
|
-
await pushAndLogCredentialSecrets(dataplaneUrl, authConfig, systemKey, payload
|
|
377
|
-
|
|
390
|
+
await pushAndLogCredentialSecrets(dataplaneUrl, authConfig, systemKey, payload, {
|
|
391
|
+
json: options.json === true
|
|
392
|
+
});
|
|
378
393
|
|
|
379
|
-
const rawRes = await
|
|
394
|
+
const rawRes = await runWithUploadPipelineSpinner(
|
|
395
|
+
async() => {
|
|
396
|
+
const res = await runUploadValidatePublish(dataplaneUrl, authConfig, payload);
|
|
397
|
+
await logAndSyncLocalOpenApiForUpload(systemKey, manifest);
|
|
398
|
+
return res;
|
|
399
|
+
},
|
|
400
|
+
{ systemKey, json: options.json === true }
|
|
401
|
+
);
|
|
380
402
|
await handlePublicationAndFollowups({
|
|
381
403
|
systemKey,
|
|
382
404
|
options,
|
|
@@ -387,6 +409,7 @@ async function runUploadPublishAndSummary(systemKey, options, manifest, payload)
|
|
|
387
409
|
authConfig,
|
|
388
410
|
rawRes
|
|
389
411
|
});
|
|
412
|
+
return { dataplaneUrl, authConfig, environment };
|
|
390
413
|
}
|
|
391
414
|
|
|
392
415
|
/**
|
|
@@ -398,7 +421,7 @@ async function runUploadPublishAndSummary(systemKey, options, manifest, payload)
|
|
|
398
421
|
* @param {boolean} [options.probe] - POST validation/run after publish
|
|
399
422
|
* @param {number} [options.probeTimeout] - Probe timeout ms (default 120000)
|
|
400
423
|
* @param {boolean} [options.minimal] - Short summary only
|
|
401
|
-
* @returns {Promise<void>}
|
|
424
|
+
* @returns {Promise<{ dataplaneUrl: string, authConfig: Object, environment: string }|void>}
|
|
402
425
|
* @throws {Error} If validation or API calls fail
|
|
403
426
|
*/
|
|
404
427
|
async function uploadExternalSystem(systemKey, options = {}) {
|
|
@@ -415,9 +438,9 @@ async function uploadExternalSystem(systemKey, options = {}) {
|
|
|
415
438
|
logger.log(
|
|
416
439
|
chalk.gray(` System: ${manifest.key}, version: ${payload.version}, datasources: ${payload.dataSources.length}`)
|
|
417
440
|
);
|
|
418
|
-
return;
|
|
441
|
+
return undefined;
|
|
419
442
|
}
|
|
420
|
-
|
|
443
|
+
return runUploadPublishAndSummary(systemKey, options, manifest, payload);
|
|
421
444
|
}
|
|
422
445
|
|
|
423
446
|
module.exports = {
|
|
@@ -525,6 +525,9 @@ async function setupDataplaneAndAuth(options, appName) {
|
|
|
525
525
|
' aifabrix login'
|
|
526
526
|
);
|
|
527
527
|
}
|
|
528
|
+
// Plan 142.0: gate wizard dataplane calls by the dataplane-advertised minimum Builder CLI version.
|
|
529
|
+
const { assertDataplaneCliVersionCompatible } = require('../utils/dataplane-cli-version-gate');
|
|
530
|
+
await assertDataplaneCliVersionCompatible(dataplaneUrl, { controllerUrl });
|
|
528
531
|
return { dataplaneUrl, authConfig };
|
|
529
532
|
}
|
|
530
533
|
|
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
|
+
};
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persist dataplane version metadata under `device.<controllerUrl>` in
|
|
3
|
+
* `~/.aifabrix/config.yaml` for the Builder CLI compatibility gate (plan 142.0).
|
|
4
|
+
*
|
|
5
|
+
* Only public dataplane fields are written here. Token / refresh-token entries
|
|
6
|
+
* on the same device entry are preserved untouched.
|
|
7
|
+
*
|
|
8
|
+
* Schema additions (kebab-case, matching `developer-id` style):
|
|
9
|
+
* - `dataplane-version` ← health `version`
|
|
10
|
+
* - `dataplane-min-cli-version` ← health `minBuilderCliVersion` (omitted when not enforced)
|
|
11
|
+
* - `dataplane-checked-at` ← ISO timestamp for cache TTL (default 5 min)
|
|
12
|
+
*
|
|
13
|
+
* @fileoverview Cache dataplane health under existing `device.<controllerUrl>` entry
|
|
14
|
+
* @author AI Fabrix Team
|
|
15
|
+
* @version 2.0.0
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
'use strict';
|
|
19
|
+
|
|
20
|
+
const { getConfig, saveConfig, normalizeControllerUrl } = require('./config');
|
|
21
|
+
|
|
22
|
+
const DEFAULT_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Resolve the device entry key matching `controllerUrl`, taking URL
|
|
26
|
+
* normalization into account so callers do not need to worry about trailing
|
|
27
|
+
* slashes or scheme differences.
|
|
28
|
+
* @private
|
|
29
|
+
* @param {Object} deviceMap - `config.device` map
|
|
30
|
+
* @param {string} normalizedUrl
|
|
31
|
+
* @returns {string|null} The key under `config.device` or null when missing
|
|
32
|
+
*/
|
|
33
|
+
function findDeviceKey(deviceMap, normalizedUrl) {
|
|
34
|
+
if (!deviceMap || typeof deviceMap !== 'object') return null;
|
|
35
|
+
if (deviceMap[normalizedUrl]) return normalizedUrl;
|
|
36
|
+
for (const storedUrl of Object.keys(deviceMap)) {
|
|
37
|
+
if (normalizeControllerUrl(storedUrl) === normalizedUrl) {
|
|
38
|
+
return storedUrl;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Read the dataplane cache currently persisted under `device.<controllerUrl>`.
|
|
46
|
+
*
|
|
47
|
+
* @async
|
|
48
|
+
* @param {string} controllerUrl
|
|
49
|
+
* @returns {Promise<{
|
|
50
|
+
* version?: string,
|
|
51
|
+
* minBuilderCliVersion?: string,
|
|
52
|
+
* checkedAt?: string
|
|
53
|
+
* }|null>} The cached snapshot, or null when no entry exists
|
|
54
|
+
* @throws {Error} If controllerUrl is missing
|
|
55
|
+
*/
|
|
56
|
+
async function getDeviceDataplaneVersions(controllerUrl) {
|
|
57
|
+
if (!controllerUrl || typeof controllerUrl !== 'string') {
|
|
58
|
+
throw new Error('controllerUrl is required and must be a string');
|
|
59
|
+
}
|
|
60
|
+
const config = await getConfig();
|
|
61
|
+
const normalizedUrl = normalizeControllerUrl(controllerUrl);
|
|
62
|
+
const key = findDeviceKey(config.device, normalizedUrl);
|
|
63
|
+
if (!key) return null;
|
|
64
|
+
const entry = config.device[key];
|
|
65
|
+
if (!entry || typeof entry !== 'object') return null;
|
|
66
|
+
const snapshot = {};
|
|
67
|
+
if (typeof entry['dataplane-version'] === 'string') {
|
|
68
|
+
snapshot.version = entry['dataplane-version'];
|
|
69
|
+
}
|
|
70
|
+
if (typeof entry['dataplane-min-cli-version'] === 'string') {
|
|
71
|
+
snapshot.minBuilderCliVersion = entry['dataplane-min-cli-version'];
|
|
72
|
+
}
|
|
73
|
+
if (typeof entry['dataplane-checked-at'] === 'string') {
|
|
74
|
+
snapshot.checkedAt = entry['dataplane-checked-at'];
|
|
75
|
+
}
|
|
76
|
+
if (Object.keys(snapshot).length === 0) return null;
|
|
77
|
+
return snapshot;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Apply the `dataplane-version` field to a device entry in-place.
|
|
82
|
+
* @private
|
|
83
|
+
* @param {Object} entry
|
|
84
|
+
* @param {string|null|undefined} version
|
|
85
|
+
*/
|
|
86
|
+
function applyDataplaneVersionField(entry, version) {
|
|
87
|
+
if (typeof version === 'string' && version.trim()) {
|
|
88
|
+
entry['dataplane-version'] = version.trim();
|
|
89
|
+
} else if (version === null || version === '') {
|
|
90
|
+
delete entry['dataplane-version'];
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Apply the `dataplane-min-cli-version` field to a device entry in-place.
|
|
96
|
+
* Treat absent / empty as "no enforcement" → clear the cached floor so an
|
|
97
|
+
* older value never lingers after the operator removes the env variable.
|
|
98
|
+
* @private
|
|
99
|
+
* @param {Object} entry
|
|
100
|
+
* @param {string|null|undefined} minCli
|
|
101
|
+
*/
|
|
102
|
+
function applyMinCliField(entry, minCli) {
|
|
103
|
+
if (typeof minCli === 'string' && minCli.trim()) {
|
|
104
|
+
entry['dataplane-min-cli-version'] = minCli.trim();
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (minCli === null || minCli === '' || minCli === undefined) {
|
|
108
|
+
delete entry['dataplane-min-cli-version'];
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Compute the timestamp persisted as `dataplane-checked-at`.
|
|
114
|
+
* @private
|
|
115
|
+
* @param {string|undefined} checkedAt
|
|
116
|
+
* @returns {string} ISO timestamp
|
|
117
|
+
*/
|
|
118
|
+
function resolveCheckedAt(checkedAt) {
|
|
119
|
+
if (typeof checkedAt === 'string' && checkedAt.trim()) {
|
|
120
|
+
return checkedAt.trim();
|
|
121
|
+
}
|
|
122
|
+
return new Date().toISOString();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Persist dataplane version metadata under the device entry for `controllerUrl`.
|
|
127
|
+
*
|
|
128
|
+
* Token / refresh-token entries are preserved; only the `dataplane-*` keys are
|
|
129
|
+
* written. When `versions.minBuilderCliVersion` is empty/undefined the cache
|
|
130
|
+
* key is removed (dataplane does not enforce a minimum).
|
|
131
|
+
*
|
|
132
|
+
* @async
|
|
133
|
+
* @param {string} controllerUrl - Controller URL (will be normalized)
|
|
134
|
+
* @param {{
|
|
135
|
+
* version?: string,
|
|
136
|
+
* minBuilderCliVersion?: string|null,
|
|
137
|
+
* checkedAt?: string
|
|
138
|
+
* }} versions
|
|
139
|
+
* @returns {Promise<void>}
|
|
140
|
+
* @throws {Error} If controllerUrl is missing
|
|
141
|
+
*/
|
|
142
|
+
async function updateDeviceDataplaneVersions(controllerUrl, versions = {}) {
|
|
143
|
+
if (!controllerUrl || typeof controllerUrl !== 'string') {
|
|
144
|
+
throw new Error('controllerUrl is required and must be a string');
|
|
145
|
+
}
|
|
146
|
+
const config = await getConfig();
|
|
147
|
+
if (!config.device || typeof config.device !== 'object') {
|
|
148
|
+
config.device = {};
|
|
149
|
+
}
|
|
150
|
+
const normalizedUrl = normalizeControllerUrl(controllerUrl);
|
|
151
|
+
const key = findDeviceKey(config.device, normalizedUrl) || normalizedUrl;
|
|
152
|
+
if (!config.device[key] || typeof config.device[key] !== 'object') {
|
|
153
|
+
config.device[key] = {};
|
|
154
|
+
}
|
|
155
|
+
const entry = config.device[key];
|
|
156
|
+
|
|
157
|
+
applyDataplaneVersionField(entry, versions.version);
|
|
158
|
+
applyMinCliField(entry, versions.minBuilderCliVersion);
|
|
159
|
+
entry['dataplane-checked-at'] = resolveCheckedAt(versions.checkedAt);
|
|
160
|
+
|
|
161
|
+
await saveConfig(config);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* True when the cached snapshot is older than the configured TTL (or missing).
|
|
166
|
+
*
|
|
167
|
+
* @param {{checkedAt?: string}|null} snapshot
|
|
168
|
+
* @param {number} [ttlMs] - TTL in milliseconds (defaults to 5 minutes)
|
|
169
|
+
* @param {Date|number} [now] - Override "now" for tests
|
|
170
|
+
* @returns {boolean}
|
|
171
|
+
*/
|
|
172
|
+
function isCacheStale(snapshot, ttlMs = DEFAULT_CACHE_TTL_MS, now = Date.now()) {
|
|
173
|
+
if (!snapshot || !snapshot.checkedAt) return true;
|
|
174
|
+
const checkedAtMs = Date.parse(snapshot.checkedAt);
|
|
175
|
+
if (Number.isNaN(checkedAtMs)) return true;
|
|
176
|
+
const reference = typeof now === 'number' ? now : now.getTime();
|
|
177
|
+
return reference - checkedAtMs >= ttlMs;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
module.exports = {
|
|
181
|
+
getDeviceDataplaneVersions,
|
|
182
|
+
updateDeviceDataplaneVersions,
|
|
183
|
+
isCacheStale,
|
|
184
|
+
DEFAULT_CACHE_TTL_MS
|
|
185
|
+
};
|