@aifabrix/builder 2.47.4 → 2.49.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.
Files changed (93) hide show
  1. package/jest.isolated-projects.js +7 -0
  2. package/jest.projects.js +16 -0
  3. package/lib/api/applications.api.js +23 -0
  4. package/lib/api/dev.api.js +1 -1
  5. package/lib/api/index.js +5 -2
  6. package/lib/api/types/dev.types.js +4 -3
  7. package/lib/app/deploy-config.js +6 -4
  8. package/lib/app/register.js +21 -19
  9. package/lib/app/rotate-secret.js +39 -16
  10. package/lib/app/run-container-start.js +0 -9
  11. package/lib/app/run-helpers.js +8 -4
  12. package/lib/app/run-reload-sync.js +16 -18
  13. package/lib/app/run-stop-containers.js +48 -5
  14. package/lib/app/run.js +6 -3
  15. package/lib/app/show.js +2 -2
  16. package/lib/build/index.js +12 -49
  17. package/lib/build/integration-docker-staging.js +35 -0
  18. package/lib/build/standard-docker-build.js +113 -0
  19. package/lib/cli/infra-guided-auth.js +42 -6
  20. package/lib/cli/infra-guided-footers.js +164 -0
  21. package/lib/cli/infra-guided-spinners.js +57 -0
  22. package/lib/cli/infra-guided.js +41 -179
  23. package/lib/cli/setup-app.js +5 -1
  24. package/lib/cli/setup-dev.js +3 -2
  25. package/lib/cli/setup-infra.js +3 -2
  26. package/lib/cli/setup-platform.js +3 -0
  27. package/lib/commands/dev-init.js +18 -0
  28. package/lib/commands/login-device.js +6 -2
  29. package/lib/commands/login.js +4 -1
  30. package/lib/commands/setup-modes.js +19 -3
  31. package/lib/commands/setup-platform-auth.js +25 -0
  32. package/lib/commands/setup-prompts-admin.js +9 -0
  33. package/lib/commands/setup-prompts-platform-mode.js +11 -1
  34. package/lib/commands/setup.js +60 -7
  35. package/lib/commands/up-dataplane-bootstrap.js +149 -0
  36. package/lib/commands/up-dataplane-credentials.js +104 -6
  37. package/lib/commands/up-dataplane.js +74 -11
  38. package/lib/core/secrets-env-declarative-expand.js +35 -23
  39. package/lib/core/setup-platform-mode.js +59 -9
  40. package/lib/deployment/deployer-status.js +26 -3
  41. package/lib/deployment/deployer.js +7 -14
  42. package/lib/external-system/download-helpers.js +10 -1
  43. package/lib/infrastructure/compose.js +40 -12
  44. package/lib/infrastructure/services.js +15 -3
  45. package/lib/infrastructure/traefik-tls-dynamic.js +88 -0
  46. package/lib/parameters/infra-kv-discovery.js +4 -3
  47. package/lib/utils/aifabrix-runtime-config-dir.js +2 -1
  48. package/lib/utils/app-bootstrap-api.js +53 -0
  49. package/lib/utils/app-run-containers.js +14 -3
  50. package/lib/utils/applications-config-defaults.js +19 -3
  51. package/lib/utils/compose-generate-docker-compose.js +18 -6
  52. package/lib/utils/compose-generator.js +34 -15
  53. package/lib/utils/compose-traefik-ingress-base.js +13 -14
  54. package/lib/utils/config-paths.js +8 -3
  55. package/lib/utils/dev-ca-docker-augment.js +54 -0
  56. package/lib/utils/dev-user-groups.js +1 -0
  57. package/lib/utils/docker-daemon-tls-ca.js +363 -0
  58. package/lib/utils/docker-reload-mount.js +12 -1
  59. package/lib/utils/ensure-dev-certs-for-remote-docker.js +5 -0
  60. package/lib/utils/external-env-template.js +53 -18
  61. package/lib/utils/health-check-poll-ui.js +84 -0
  62. package/lib/utils/health-check-url.js +54 -34
  63. package/lib/utils/health-check.js +43 -16
  64. package/lib/utils/infra-optional-service-flags.js +31 -2
  65. package/lib/utils/mutagen.js +7 -4
  66. package/lib/utils/platform-controller-url.js +23 -1
  67. package/lib/utils/promote-dev-docker-image.js +170 -0
  68. package/lib/utils/refresh-remote-settings.js +67 -0
  69. package/lib/utils/remote-docker-env.js +22 -7
  70. package/lib/utils/remote-host-for-front-door.js +43 -0
  71. package/lib/utils/run-cli-flags.js +37 -1
  72. package/lib/utils/secrets-helpers.js +9 -14
  73. package/lib/utils/secrets-kv-refs.js +47 -3
  74. package/lib/utils/secrets-missing-error.js +1 -9
  75. package/lib/utils/token-manager-client-credentials.js +11 -1
  76. package/lib/utils/url-declarative-public-base.js +11 -4
  77. package/lib/utils/url-declarative-resolve-build-urls.js +11 -2
  78. package/lib/utils/url-declarative-resolve-expand-token.js +12 -1
  79. package/lib/utils/url-declarative-resolve-load-doc.js +30 -11
  80. package/lib/utils/url-declarative-resolve.js +2 -0
  81. package/lib/utils/urls-local-registry-scan.js +95 -10
  82. package/lib/utils/urls-local-registry.js +24 -1
  83. package/lib/utils/worker-sidecars.js +28 -1
  84. package/lib/validation/external-system-auth-rules.js +45 -7
  85. package/package.json +2 -1
  86. package/scripts/test-dataplane-bootstrap.js +331 -0
  87. package/templates/applications/dataplane/env.template +22 -5
  88. package/templates/applications/dataplane/rbac.yaml +11 -0
  89. package/templates/applications/miso-controller/application.yaml +1 -1
  90. package/templates/applications/miso-controller/env.template +3 -0
  91. package/templates/infra/compose.yaml.hbs +13 -9
  92. package/templates/python/docker-compose.hbs +3 -0
  93. package/templates/typescript/docker-compose.hbs +3 -0
@@ -29,7 +29,9 @@ const {
29
29
 
30
30
  const setupPrompts = require('./setup-prompts');
31
31
  const setupPlatformModePrompts = require('./setup-prompts-platform-mode');
32
- const { flagsForSetupPlatformMode } = require('../core/setup-platform-mode');
32
+ const {
33
+ applyPlatformTopologyFlagsToConfig
34
+ } = require('../core/setup-platform-mode');
33
35
  const setupModes = require('./setup-modes');
34
36
  const { syncPlatformControllerUrlsInConfig } = require('./setup-platform-auth');
35
37
  const installationLog = require('../utils/installation-log');
@@ -37,6 +39,11 @@ const { beginSetupCommandFlow, endSetupCommandFlow } = require('./setup-run-cont
37
39
 
38
40
  const MODE = setupPrompts.MODE;
39
41
 
42
+ const CLI_SETUP_MODES = Object.freeze({
43
+ FRESH: 'fresh',
44
+ UPDATE_IMAGES: 'update-images'
45
+ });
46
+
40
47
  const SEPARATOR = '────────────────────────────────────────';
41
48
 
42
49
  function title(text) {
@@ -58,14 +65,53 @@ async function maybePinDeveloperIdForFreshInstall(rawDeveloper) {
58
65
  logger.log(infoLine(`Developer ID pinned to ${dev}.`));
59
66
  }
60
67
 
68
+ /**
69
+ * @param {Object} options
70
+ * @returns {'fresh'|'update-images'|null}
71
+ */
72
+ function normalizeCliMode(options) {
73
+ const raw = options.mode ?? options.setupMode;
74
+ if (raw === undefined || raw === null || String(raw).trim() === '') {
75
+ return null;
76
+ }
77
+ const mode = String(raw).trim().toLowerCase();
78
+ if (mode === CLI_SETUP_MODES.FRESH || mode === CLI_SETUP_MODES.UPDATE_IMAGES) {
79
+ return mode;
80
+ }
81
+ throw new Error(`Invalid --mode "${raw}". Use fresh or update-images.`);
82
+ }
83
+
84
+ /**
85
+ * Non-interactive path when --mode is set (af-server automation).
86
+ * @param {Object} options
87
+ * @param {'fresh'|'update-images'} cliMode
88
+ * @returns {Promise<{ outcome: string, setupMode: string }>}
89
+ */
90
+ async function runSetupWithCliMode(options, cliMode) {
91
+ logSetupHeader();
92
+ logger.log(formatProgress('Detecting current installation state...'));
93
+ const running = await isInfraRunning();
94
+ if (cliMode === CLI_SETUP_MODES.FRESH) {
95
+ if (running) {
96
+ throw new Error(
97
+ 'Infrastructure is already running; use --mode update-images or run teardown first.'
98
+ );
99
+ }
100
+ await runFreshInstallFlow(options);
101
+ return { outcome: 'success', setupMode: CLI_SETUP_MODES.FRESH };
102
+ }
103
+ if (!running) {
104
+ throw new Error('No infrastructure running; use --mode fresh for first-time install.');
105
+ }
106
+ await dispatchMode(MODE.UPDATE_IMAGES);
107
+ logger.log(formatSuccessParagraph(`aifabrix setup complete (mode: ${cliMode}).`));
108
+ return { outcome: 'success', setupMode: cliMode };
109
+ }
110
+
61
111
  async function applySetupPlatformMode(options) {
62
112
  const mode = await setupPlatformModePrompts.promptSetupPlatformMode(options);
63
- await config.setSetupPlatformMode(mode);
64
- const derived = flagsForSetupPlatformMode(mode);
65
- // These are the user gates that control full-mode behavior.
66
113
  const cfg = await config.getConfig();
67
- cfg.traefik = derived.traefik;
68
- cfg.useEnvironmentScopedResources = derived.useEnvironmentScopedResources;
114
+ applyPlatformTopologyFlagsToConfig(cfg, mode);
69
115
  await config.saveConfig(cfg);
70
116
  await syncPlatformControllerUrlsInConfig();
71
117
  return mode;
@@ -149,6 +195,11 @@ async function dispatchMode(mode) {
149
195
  * @returns {Promise<{ outcome: string, setupMode: string|null }>}
150
196
  */
151
197
  async function runSetupMainPath(options, assumeYes) {
198
+ const cliMode = normalizeCliMode(options);
199
+ if (cliMode) {
200
+ return runSetupWithCliMode(options, cliMode);
201
+ }
202
+
152
203
  logSetupHeader();
153
204
  logger.log(formatProgress('Detecting current installation state...'));
154
205
  const running = await isInfraRunning();
@@ -272,5 +323,7 @@ async function handleSetup(options = {}) {
272
323
  module.exports = {
273
324
  handleSetup,
274
325
  isInfraRunning,
275
- dispatchMode
326
+ dispatchMode,
327
+ normalizeCliMode,
328
+ CLI_SETUP_MODES
276
329
  };
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Headless dataplane bootstrap provisioning (plan 197.0).
3
+ *
4
+ * @fileoverview up-dataplane bootstrap path when Docker + ENCRYPTION_KEY available
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const chalk = require('chalk');
12
+ const logger = require('../utils/logger');
13
+ const config = require('../core/config');
14
+ const dockerUtils = require('../utils/docker');
15
+ const { loadSecrets } = require('../core/secrets-load');
16
+ const { callBootstrapApi } = require('../utils/app-bootstrap-api');
17
+ const {
18
+ loadVariablesYaml,
19
+ extractAppConfiguration
20
+ } = require('../utils/app-register-config');
21
+ const { buildRegistrationData, saveLocalCredentials, applyLocalhostPortAdjustment } = require('../app/register');
22
+ const { loadDataplanePipelineCredentialsFromSecretsFile } = require('./up-dataplane-credentials');
23
+ const { formatSuccessLine } = require('../utils/cli-test-layout-chalk');
24
+
25
+ const ENCRYPTION_VAULT_KEYS = [
26
+ 'miso-controller-secrets-encryptionKeyVault',
27
+ 'secrets-encryptionKeyVault'
28
+ ];
29
+
30
+ /**
31
+ * @param {Record<string, unknown>} secrets
32
+ * @returns {string|null}
33
+ */
34
+ function pickBootstrapEncryptionKeyFromSecrets(secrets) {
35
+ if (!secrets || typeof secrets !== 'object') {
36
+ return null;
37
+ }
38
+ for (const vaultKey of ENCRYPTION_VAULT_KEYS) {
39
+ const raw = secrets[vaultKey];
40
+ if (raw === undefined || raw === null) {
41
+ continue;
42
+ }
43
+ const trimmed = String(raw).trim();
44
+ if (trimmed) {
45
+ return trimmed;
46
+ }
47
+ }
48
+ return null;
49
+ }
50
+
51
+ /**
52
+ * Resolve shared ENCRYPTION_KEY for bootstrap header (never log).
53
+ * Uses the same merged secrets store as kv:// resolution (local + aifabrix-secrets / remote API).
54
+ * Must match miso-controller `ENCRYPTION_KEY` (kv://miso-controller-secrets-encryptionKeyVault),
55
+ * not `secrets-encryption` in config.yaml (CLI file-encryption key only).
56
+ *
57
+ * @async
58
+ * @returns {Promise<string|null>}
59
+ */
60
+ async function resolveBootstrapEncryptionKey() {
61
+ await config.ensureSecretsEncryptionKey();
62
+ const secrets = await loadSecrets(undefined);
63
+ return pickBootstrapEncryptionKeyFromSecrets(secrets);
64
+ }
65
+
66
+ /**
67
+ * True when bootstrap path is eligible (dev + Docker + encryption key).
68
+ * @async
69
+ * @param {Object} [options]
70
+ * @returns {Promise<boolean>}
71
+ */
72
+ async function canUseBootstrapProvision(options = {}) {
73
+ if (options.allowNonDev === true) {
74
+ return false;
75
+ }
76
+ const cfg = await config.getConfig();
77
+ const environment = cfg && cfg.environment ? cfg.environment : 'dev';
78
+ if (environment !== 'dev') {
79
+ return false;
80
+ }
81
+ try {
82
+ await dockerUtils.ensureDockerAndCompose();
83
+ } catch {
84
+ return false;
85
+ }
86
+ const encryptionKey = await resolveBootstrapEncryptionKey();
87
+ return Boolean(encryptionKey);
88
+ }
89
+
90
+ /**
91
+ * Register dataplane via bootstrap endpoint (idempotent).
92
+ * @async
93
+ * @param {Object} options - up-dataplane options
94
+ * @param {string} controllerUrl
95
+ * @param {string} environmentKey
96
+ * @param {Function} buildDataplaneImageRefFn
97
+ * @returns {Promise<void>}
98
+ */
99
+ async function bootstrapDataplaneApplication(options, controllerUrl, environmentKey, buildDataplaneImageRefFn) {
100
+ const encryptionKey = await resolveBootstrapEncryptionKey();
101
+ if (!encryptionKey) {
102
+ throw new Error(
103
+ 'Bootstrap encryption key is not available. Ensure miso-controller-secrets-encryptionKeyVault ' +
104
+ 'exists in merged secrets (local secrets.local.yaml or aifabrix-secrets remote store) after up-miso resolve.'
105
+ );
106
+ }
107
+
108
+ const { variables } = await loadVariablesYaml('dataplane');
109
+ const appConfig = await extractAppConfiguration(variables, 'dataplane', options);
110
+ await applyLocalhostPortAdjustment(appConfig, controllerUrl, options);
111
+
112
+ const imageOverride = options.image || buildDataplaneImageRefFn(options);
113
+ const registerOpts = {
114
+ imageOverride,
115
+ image: imageOverride,
116
+ registryMode: options.registryMode,
117
+ envKey: environmentKey
118
+ };
119
+ const registrationData = buildRegistrationData(appConfig, registerOpts);
120
+
121
+ logger.log(chalk.blue('Provisioning dataplane via bootstrap (no device login)...'));
122
+ const responseData = await callBootstrapApi(
123
+ controllerUrl,
124
+ encryptionKey,
125
+ environmentKey,
126
+ registrationData
127
+ );
128
+
129
+ if (responseData.credentials && responseData.credentials.clientSecret) {
130
+ await saveLocalCredentials(responseData, controllerUrl);
131
+ logger.log(formatSuccessLine('Dataplane bootstrap credentials saved locally'));
132
+ } else if (responseData.status === 'already_provisioned') {
133
+ logger.log(formatSuccessLine('Dataplane already provisioned (bootstrap idempotent)'));
134
+ const localCredentials = await loadDataplanePipelineCredentialsFromSecretsFile();
135
+ if (!localCredentials) {
136
+ throw new Error(
137
+ 'Dataplane is registered on the controller but pipeline credentials are missing locally. ' +
138
+ 'Run fresh setup with volume wipe, or: aifabrix login && aifabrix app rotate-secret dataplane'
139
+ );
140
+ }
141
+ }
142
+ }
143
+
144
+ module.exports = {
145
+ canUseBootstrapProvision,
146
+ resolveBootstrapEncryptionKey,
147
+ bootstrapDataplaneApplication,
148
+ pickBootstrapEncryptionKeyFromSecrets
149
+ };
@@ -16,12 +16,22 @@ const { loadClientCredentials } = require('../utils/token-manager');
16
16
  const { rotateSecret } = require('../app/rotate-secret');
17
17
  const logger = require('../utils/logger');
18
18
  const { formatSuccessLine } = require('../utils/cli-test-layout-chalk');
19
+ const {
20
+ resolveLocalhostPlatformControllerUrl,
21
+ resolveCliPlatformControllerUrl
22
+ } = require('../utils/platform-controller-url');
19
23
 
20
24
  const DATAPLANE_APP_KEY = 'dataplane';
21
25
  const MISALIGN_ERROR =
22
26
  'Dataplane pipeline credentials are not aligned with the controller. ' +
23
27
  'Run: aifabrix app rotate-secret dataplane && aifabrix run dataplane';
24
28
 
29
+ const BOOTSTRAP_MISALIGN_ERROR =
30
+ 'Dataplane bootstrap credentials are not accepted by the controller after deploy. ' +
31
+ 'Ensure miso-controller has BOOTSTRAP_APPKEYS=dataplane and ENCRYPTION_KEY set, then re-run: aifabrix up-dataplane';
32
+
33
+ const BOOTSTRAP_PROBE_RETRY = { maxAttempts: 15, delayMs: 3000 };
34
+
25
35
  /**
26
36
  * @param {string} controllerUrl
27
37
  * @returns {string}
@@ -30,6 +40,31 @@ function normalizeControllerUrl(controllerUrl) {
30
40
  return String(controllerUrl || '').replace(/\/+$/, '');
31
41
  }
32
42
 
43
+ /**
44
+ * Controller bases to try for token probe (public Traefik, CLI-resolved, localhost).
45
+ * @async
46
+ * @param {string} controllerUrl
47
+ * @returns {Promise<string[]>}
48
+ */
49
+ async function resolveControllerUrlsForCredentialProbe(controllerUrl) {
50
+ const urls = [];
51
+ const add = (url) => {
52
+ const normalized = normalizeControllerUrl(url);
53
+ if (normalized && !urls.includes(normalized)) {
54
+ urls.push(normalized);
55
+ }
56
+ };
57
+
58
+ add(controllerUrl);
59
+ try {
60
+ add(await resolveCliPlatformControllerUrl());
61
+ add(await resolveLocalhostPlatformControllerUrl());
62
+ } catch {
63
+ // optional fallbacks only
64
+ }
65
+ return urls;
66
+ }
67
+
33
68
  /**
34
69
  * Exchange dataplane client id/secret for a controller token (probe only).
35
70
  * @async
@@ -52,17 +87,69 @@ async function probeDataplaneClientToken(controllerUrl, credentials) {
52
87
  return response.success === true;
53
88
  }
54
89
 
90
+ /**
91
+ * Load dataplane pipeline credentials from secrets.local.yaml only (ignore process env).
92
+ * @async
93
+ * @returns {Promise<{ clientId: string, clientSecret: string }|null>}
94
+ */
95
+ async function loadDataplanePipelineCredentialsFromSecretsFile() {
96
+ return loadClientCredentials(DATAPLANE_APP_KEY, { secretsFileOnly: true });
97
+ }
98
+
99
+ /**
100
+ * @param {number} ms
101
+ * @returns {Promise<void>}
102
+ */
103
+ function sleep(ms) {
104
+ return new Promise((resolve) => setTimeout(resolve, ms));
105
+ }
106
+
107
+ /**
108
+ * Probe token endpoint with retries (controller Redis may briefly serve stale pipeline config).
109
+ * @async
110
+ * @param {string} controllerUrl
111
+ * @param {number} [maxAttempts]
112
+ * @param {number} [delayMs]
113
+ * @returns {Promise<boolean>}
114
+ */
115
+ async function credentialsAcceptTokenWithRetry(controllerUrl, maxAttempts = 5, delayMs = 2000) {
116
+ for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
117
+ if (await credentialsAcceptToken(controllerUrl)) {
118
+ return true;
119
+ }
120
+ if (attempt < maxAttempts - 1 && delayMs > 0) {
121
+ await sleep(delayMs);
122
+ }
123
+ }
124
+ return false;
125
+ }
126
+
127
+ function resolveCredentialProbeRetry(options = {}) {
128
+ const retry = options.credentialProbeRetry || {};
129
+ const bootstrapDefaults = options.useBootstrap === true ? BOOTSTRAP_PROBE_RETRY : { maxAttempts: 5, delayMs: 2000 };
130
+ return {
131
+ maxAttempts: Number.isFinite(retry.maxAttempts) ? retry.maxAttempts : bootstrapDefaults.maxAttempts,
132
+ delayMs: Number.isFinite(retry.delayMs) ? retry.delayMs : bootstrapDefaults.delayMs
133
+ };
134
+ }
135
+
55
136
  /**
56
137
  * @async
57
138
  * @param {string} controllerUrl
58
139
  * @returns {Promise<boolean>}
59
140
  */
60
141
  async function credentialsAcceptToken(controllerUrl) {
61
- const credentials = await loadClientCredentials(DATAPLANE_APP_KEY);
142
+ const credentials = await loadDataplanePipelineCredentialsFromSecretsFile();
62
143
  if (!credentials) {
63
144
  return false;
64
145
  }
65
- return probeDataplaneClientToken(controllerUrl, credentials);
146
+ const urls = await resolveControllerUrlsForCredentialProbe(controllerUrl);
147
+ for (const url of urls) {
148
+ if (await probeDataplaneClientToken(url, credentials)) {
149
+ return true;
150
+ }
151
+ }
152
+ return false;
66
153
  }
67
154
 
68
155
  /**
@@ -75,7 +162,12 @@ async function credentialsAcceptToken(controllerUrl) {
75
162
  */
76
163
  async function alignDataplaneCredentialsAfterDeploy(options, controllerUrl) {
77
164
  const quiet = options.platformInstall === true || options.quiet === true;
78
- const aligned = await credentialsAcceptToken(controllerUrl);
165
+ const bootstrapPath = options.useBootstrap === true;
166
+ const { maxAttempts, delayMs } = resolveCredentialProbeRetry(options);
167
+
168
+ const aligned = bootstrapPath
169
+ ? await credentialsAcceptTokenWithRetry(controllerUrl, maxAttempts, delayMs)
170
+ : await credentialsAcceptToken(controllerUrl);
79
171
  if (aligned) {
80
172
  if (!quiet) {
81
173
  logger.log(formatSuccessLine('Dataplane pipeline credentials verified.'));
@@ -83,12 +175,16 @@ async function alignDataplaneCredentialsAfterDeploy(options, controllerUrl) {
83
175
  return;
84
176
  }
85
177
 
178
+ if (bootstrapPath) {
179
+ throw new Error(BOOTSTRAP_MISALIGN_ERROR);
180
+ }
181
+
86
182
  if (!quiet) {
87
183
  logger.log(chalk.blue('Aligning dataplane pipeline credentials with controller...'));
88
184
  }
89
- await rotateSecret(DATAPLANE_APP_KEY, { ...options, quiet });
185
+ await rotateSecret(DATAPLANE_APP_KEY, { ...options, quiet, exitOnFailure: false });
90
186
 
91
- const stillMisaligned = !(await credentialsAcceptToken(controllerUrl));
187
+ const stillMisaligned = !(await credentialsAcceptTokenWithRetry(controllerUrl, maxAttempts, delayMs));
92
188
  if (stillMisaligned) {
93
189
  throw new Error(MISALIGN_ERROR);
94
190
  }
@@ -100,5 +196,7 @@ async function alignDataplaneCredentialsAfterDeploy(options, controllerUrl) {
100
196
  module.exports = {
101
197
  alignDataplaneCredentialsAfterDeploy,
102
198
  probeDataplaneClientToken,
103
- credentialsAcceptToken
199
+ credentialsAcceptToken,
200
+ loadDataplanePipelineCredentialsFromSecretsFile,
201
+ resolveControllerUrlsForCredentialProbe
104
202
  };
@@ -35,6 +35,10 @@ const {
35
35
  refreshUrlsLocalRegistryForCurrentProject
36
36
  } = require('./up-common');
37
37
  const { alignDataplaneCredentialsAfterDeploy } = require('./up-dataplane-credentials');
38
+ const {
39
+ canUseBootstrapProvision,
40
+ bootstrapDataplaneApplication
41
+ } = require('./up-dataplane-bootstrap');
38
42
 
39
43
  const CONTROLLER_HEALTH_PATH = '/health';
40
44
 
@@ -120,7 +124,9 @@ async function resolveUpDataplaneAuthContext(options, environmentKey) {
120
124
  if (options.allowNonDev !== true) {
121
125
  assertDevEnvironmentForDataplane(cfg);
122
126
  }
123
- logger.log(formatSuccessLine(`Logged in (environment: ${environmentKey})`));
127
+ if (options.platformInstall !== true) {
128
+ logger.log(formatSuccessLine(`Logged in (environment: ${environmentKey})`));
129
+ }
124
130
  return { controllerUrl, authConfig };
125
131
  }
126
132
 
@@ -223,6 +229,57 @@ async function computeDataplaneRunOverrides(options) {
223
229
  return { runEnvKey, portOverride };
224
230
  }
225
231
 
232
+ /**
233
+ * Resolve controller URL and auth for bootstrap vs device-login paths.
234
+ * @async
235
+ * @param {Object} options
236
+ * @param {string} environmentKey
237
+ * @returns {Promise<{ controllerUrl: string, authConfig: Object|null, useBootstrap: boolean }>}
238
+ */
239
+ async function resolveUpDataplaneControllerContext(options, environmentKey) {
240
+ const useBootstrap = await canUseBootstrapProvision(options);
241
+ if (useBootstrap) {
242
+ if (options.skipInfraCheck !== true) {
243
+ await assertDevInfraUp();
244
+ }
245
+ const controllerUrl = await resolveControllerUrlWithHealthCheck();
246
+ const cfg = await config.getConfig();
247
+ if (options.allowNonDev !== true) {
248
+ assertDevEnvironmentForDataplane(cfg);
249
+ }
250
+ if (options.platformInstall !== true) {
251
+ logger.log(formatSuccessLine('Using bootstrap provision (no device login)'));
252
+ }
253
+ return { controllerUrl, authConfig: null, useBootstrap: true };
254
+ }
255
+
256
+ const authContext = await resolveUpDataplaneAuthContext(options, environmentKey);
257
+ return {
258
+ controllerUrl: authContext.controllerUrl,
259
+ authConfig: authContext.authConfig,
260
+ useBootstrap: false
261
+ };
262
+ }
263
+
264
+ /**
265
+ * Register/bootstrap, deploy manifest, align credentials.
266
+ * @async
267
+ */
268
+ async function provisionAndDeployDataplane(options, environmentKey, controllerUrl, useBootstrap, authConfig) {
269
+ if (useBootstrap) {
270
+ await bootstrapDataplaneApplication(options, controllerUrl, environmentKey, buildDataplaneImageRef);
271
+ } else {
272
+ await registerOrRotateDataplane(options, controllerUrl, environmentKey, authConfig);
273
+ }
274
+
275
+ await deployDataplaneToController({
276
+ ...options,
277
+ envKey: environmentKey,
278
+ ...(useBootstrap ? { deploymentAuth: 'client-credentials' } : {})
279
+ });
280
+ logger.log('');
281
+ }
282
+
226
283
  /**
227
284
  * Handle up-dataplane command: ensure logged in, environment dev, ensure dataplane,
228
285
  * register or rotate (if already registered), deploy (send manifest to controller),
@@ -239,30 +296,34 @@ async function computeDataplaneRunOverrides(options) {
239
296
  * @throws {Error} If infra not up, controller unavailable, not logged in, environment not dev, or any step fails
240
297
  */
241
298
  async function handleUpDataplane(options = {}) {
242
- logger.log(chalk.blue('Starting up-dataplane (register/rotate, deploy, then run dataplane locally)...\n'));
299
+ if (options.platformInstall !== true) {
300
+ logger.log(chalk.blue('Starting up-dataplane (register/rotate, deploy, then run dataplane locally)...\n'));
301
+ }
243
302
 
244
303
  const environmentKey =
245
304
  options && options.envKey
246
305
  ? String(options.envKey).trim().toLowerCase()
247
306
  : await resolveEnvironment();
248
- const { controllerUrl, authConfig } = await resolveUpDataplaneAuthContext(options, environmentKey);
307
+
308
+ const { controllerUrl, authConfig, useBootstrap } = await resolveUpDataplaneControllerContext(
309
+ options,
310
+ environmentKey
311
+ );
249
312
 
250
313
  await ensureDataplaneAppsFromTemplate();
251
- // If envOutputPath target folder does not exist, set envOutputPath to null
252
314
  validateEnvOutputPathFolderOrNull('dataplane');
253
-
254
315
  refreshUrlsLocalRegistryForCurrentProject();
255
316
 
256
317
  if (options.platformInstall !== true) {
257
318
  emitDataplaneManifestLineFromBuilder();
258
319
  }
259
320
 
260
- await registerOrRotateDataplane(options, controllerUrl, environmentKey, authConfig);
321
+ await provisionAndDeployDataplane(options, environmentKey, controllerUrl, useBootstrap, authConfig);
261
322
 
262
- await deployDataplaneToController({ ...options, envKey: environmentKey });
263
- logger.log('');
264
-
265
- await alignDataplaneCredentialsAfterDeploy(options, controllerUrl);
323
+ await alignDataplaneCredentialsAfterDeploy(
324
+ { ...options, useBootstrap },
325
+ controllerUrl
326
+ );
266
327
 
267
328
  const { runEnvKey, portOverride } = await computeDataplaneRunOverrides(options);
268
329
 
@@ -275,7 +336,9 @@ async function handleUpDataplane(options = {}) {
275
336
  skipManifestMetadataLine: true
276
337
  });
277
338
 
278
- logger.log(formatSuccessParagraph('up-dataplane complete. Dataplane is registered, deployed in dev, and running locally.'));
339
+ if (options.platformInstall !== true) {
340
+ logger.log(formatSuccessParagraph('up-dataplane complete. Dataplane is registered, deployed in dev, and running locally.'));
341
+ }
279
342
  }
280
343
 
281
344
  module.exports = { handleUpDataplane, buildDataplaneImageRef };
@@ -14,6 +14,30 @@ const { readAppEnvironmentScopedFlagForAppPath } = require('../utils/app-scoped-
14
14
  const { expandDeclarativeUrlsInEnvContent } = require('../utils/url-declarative-resolve');
15
15
  const { rewriteInactiveDeclarativeVdirPublicContent } = require('../utils/url-declarative-vdir-inactive-env');
16
16
  const { refreshUrlsLocalRegistryFromBuilder } = require('../utils/urls-local-registry');
17
+
18
+ /**
19
+ * @param {() => Promise<*>} getter
20
+ * @param {*} [fallback]
21
+ * @returns {Promise<*>}
22
+ */
23
+ async function tryConfigValue(getter, fallback = null) {
24
+ try {
25
+ return await getter();
26
+ } catch {
27
+ return fallback;
28
+ }
29
+ }
30
+
31
+ /**
32
+ * @param {() => Promise<*>} getter
33
+ * @returns {Promise<string|null>}
34
+ */
35
+ async function tryConfigTrimmedString(getter) {
36
+ const value = await tryConfigValue(getter, null);
37
+ const trimmed = value !== null && value !== undefined ? String(value).trim() : '';
38
+ return trimmed || null;
39
+ }
40
+
17
41
  /**
18
42
  * Config inputs for declarative url:// expansion.
19
43
  * @param {string} appPath
@@ -21,30 +45,16 @@ const { refreshUrlsLocalRegistryFromBuilder } = require('../utils/urls-local-reg
21
45
  */
22
46
  async function loadDeclarativeUrlExpandInputs(appPath) {
23
47
  const userCfg = await config.getConfig();
24
- let remoteServer = null;
25
- try {
26
- const rs = await config.getRemoteServer();
27
- if (rs && String(rs).trim()) {
28
- remoteServer = String(rs).trim();
29
- }
30
- } catch {
31
- remoteServer = null;
32
- }
33
- let developerIdRaw = null;
34
- try {
35
- developerIdRaw = await config.getDeveloperId();
36
- } catch {
37
- developerIdRaw = null;
38
- }
39
- let infraTlsEnabled = false;
40
- try {
41
- infraTlsEnabled = await config.getTlsEnabled();
42
- } catch {
43
- infraTlsEnabled = false;
44
- }
48
+ const [remoteServer, developerRootDomain, developerIdRaw, infraTlsEnabled] = await Promise.all([
49
+ tryConfigTrimmedString(() => config.getRemoteServer()),
50
+ tryConfigTrimmedString(() => config.getDeveloperRootDomain()),
51
+ tryConfigValue(() => config.getDeveloperId(), null),
52
+ tryConfigValue(() => config.getTlsEnabled(), false)
53
+ ]);
45
54
  return {
46
55
  userCfg,
47
56
  remoteServer,
57
+ developerRootDomain,
48
58
  developerIdRaw,
49
59
  infraTlsEnabled,
50
60
  appScopedFlag: readAppEnvironmentScopedFlagForAppPath(appPath)
@@ -60,7 +70,7 @@ async function loadDeclarativeUrlExpandInputs(appPath) {
60
70
  * @returns {Object}
61
71
  */
62
72
  function buildDeclarativeUrlExpandContextFromInputs(appName, variablesPath, environment, inputs) {
63
- const { userCfg, remoteServer, developerIdRaw, infraTlsEnabled, appScopedFlag } = inputs;
73
+ const { userCfg, remoteServer, developerRootDomain, developerIdRaw, infraTlsEnabled, appScopedFlag } = inputs;
64
74
  let projectRoot = null;
65
75
  try {
66
76
  const r = pathsUtil.getProjectRoot();
@@ -77,6 +87,7 @@ function buildDeclarativeUrlExpandContextFromInputs(appName, variablesPath, envi
77
87
  useEnvironmentScopedResources: Boolean(userCfg.useEnvironmentScopedResources),
78
88
  appEnvironmentScopedResources: appScopedFlag,
79
89
  remoteServer,
90
+ developerRootDomain,
80
91
  developerIdRaw,
81
92
  infraTlsEnabled,
82
93
  userCfg,
@@ -136,12 +147,13 @@ async function expandDeclarativeUrlsIfPresent(resolved, appName, appPath, variab
136
147
  if (envOnly || !variablesPath) {
137
148
  return resolved;
138
149
  }
139
- const { userCfg, remoteServer, developerIdRaw, infraTlsEnabled, appScopedFlag } =
150
+ const { userCfg, remoteServer, developerRootDomain, developerIdRaw, infraTlsEnabled, appScopedFlag } =
140
151
  await loadDeclarativeUrlExpandInputs(appPath);
141
152
  resolved = rewriteInactiveDeclarativeVdirPublicContent(resolved, variablesPath, userCfg);
142
153
  const ctx = buildDeclarativeUrlExpandContextFromInputs(appName, variablesPath, environment, {
143
154
  userCfg,
144
155
  remoteServer,
156
+ developerRootDomain,
145
157
  developerIdRaw,
146
158
  infraTlsEnabled,
147
159
  appScopedFlag