@aifabrix/builder 2.61.2 → 2.63.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 (90) hide show
  1. package/lib/api/applications.api.js +22 -2
  2. package/lib/api/bootstrap-snapshot.api.js +115 -0
  3. package/lib/api/onboarding-status.api.js +117 -0
  4. package/lib/app/deploy.js +14 -5
  5. package/lib/app/register.js +24 -2
  6. package/lib/app/run-env-compose.js +44 -8
  7. package/lib/app/run-env-recovery.js +1 -0
  8. package/lib/app/run-helpers.js +1 -1
  9. package/lib/app/run.js +6 -4
  10. package/lib/build/standard-docker-build.js +4 -0
  11. package/lib/channels/channel-artifact-generator.js +13 -3
  12. package/lib/cli/infra-guided-footers.js +10 -8
  13. package/lib/cli/infra-guided.js +60 -12
  14. package/lib/cli/setup-infra-up-platform-action.js +14 -1
  15. package/lib/cli/setup-platform.js +20 -3
  16. package/lib/commands/auth-config.js +2 -0
  17. package/lib/commands/login.js +4 -0
  18. package/lib/commands/repair-datasource-apply.js +3 -1
  19. package/lib/commands/repair-datasource-legacy.js +94 -0
  20. package/lib/commands/setup-image-refresh.js +26 -0
  21. package/lib/commands/setup-modes.js +9 -2
  22. package/lib/commands/setup-onboarding-readiness.js +83 -0
  23. package/lib/commands/setup-prompts-platform-mode.js +34 -0
  24. package/lib/commands/setup.js +35 -10
  25. package/lib/commands/up-builder-api.js +24 -8
  26. package/lib/commands/up-common.js +16 -1
  27. package/lib/commands/up-dataplane-bootstrap.js +149 -16
  28. package/lib/commands/up-dataplane-credentials.js +21 -0
  29. package/lib/commands/up-dataplane.js +26 -34
  30. package/lib/commands/up-integration-server.js +112 -0
  31. package/lib/commands/wizard-config-normalizer.js +5 -1
  32. package/lib/core/admin-secrets-env-overlay.js +8 -3
  33. package/lib/core/environment-access-policy.js +121 -0
  34. package/lib/core/environment-mode-policy.js +177 -0
  35. package/lib/core/local-env-overrides.js +149 -0
  36. package/lib/core/secrets-ensure.js +4 -2
  37. package/lib/core/secrets-env-content.js +12 -9
  38. package/lib/core/secrets-env-write.js +1 -1
  39. package/lib/datasource/capability/capability-manifest.js +1 -0
  40. package/lib/deployment/installation/index.js +14 -1
  41. package/lib/deployment/installation/infra-catalog.js +3 -0
  42. package/lib/deployment/installation/local-environment-stage.js +285 -0
  43. package/lib/deployment/installation/local-installation.js +76 -0
  44. package/lib/deployment/installation/stage-input.js +5 -1
  45. package/lib/generator/builders.js +3 -1
  46. package/lib/generator/index.js +32 -9
  47. package/lib/generator/split-variables.js +2 -2
  48. package/lib/parameters/database-adoption.js +125 -0
  49. package/lib/parameters/physical-database-naming.js +136 -0
  50. package/lib/parameters/physical-secret-name.js +194 -0
  51. package/lib/parameters/rsa-secret-values.js +165 -0
  52. package/lib/programmatic/run-channel-install-artifacts.js +28 -2
  53. package/lib/programmatic/run-channel-package.js +3 -1
  54. package/lib/programmatic/workspace-context.js +37 -2
  55. package/lib/schema/infra.parameter.yaml +61 -27
  56. package/lib/schema/infrastructure-schema.json +213 -120
  57. package/lib/utils/compose-generate-docker-compose.js +2 -2
  58. package/lib/utils/compose-generator.js +4 -7
  59. package/lib/utils/compose-miso-env.js +2 -2
  60. package/lib/utils/compose-traefik-ingress-base.js +3 -4
  61. package/lib/utils/dataplane-dimension-abac-setting.js +120 -0
  62. package/lib/utils/declarative-url-ports.js +3 -0
  63. package/lib/utils/env-environment-file-paths.js +105 -0
  64. package/lib/utils/environment-scoped-resources.js +48 -14
  65. package/lib/utils/log-redaction.js +4 -1
  66. package/lib/utils/paths-system-builder-keys.js +7 -1
  67. package/lib/utils/paths.js +7 -2
  68. package/lib/utils/postgres-platform-bootstrap.js +35 -1
  69. package/lib/utils/redis-env-scope.js +21 -1
  70. package/lib/utils/registry-auth-sources.js +154 -0
  71. package/lib/utils/registry-credentials.js +131 -14
  72. package/lib/utils/secrets-generator.js +30 -21
  73. package/lib/utils/secrets-helpers.js +5 -7
  74. package/lib/utils/secrets-kv-scope.js +127 -28
  75. package/lib/utils/secrets-materialize-local.js +9 -0
  76. package/lib/utils/secrets-missing-error.js +35 -1
  77. package/lib/utils/token-manager.js +14 -8
  78. package/lib/utils/url-public-path-prefix.js +6 -10
  79. package/package.json +4 -2
  80. package/templates/README.md +6 -0
  81. package/templates/applications/builder-api/application.yaml +1 -1
  82. package/templates/applications/dataplane/application.yaml +2 -2
  83. package/templates/applications/dataplane/env.template +1 -1
  84. package/templates/applications/integration-server/application.yaml +91 -0
  85. package/templates/applications/integration-server/env.template +215 -0
  86. package/templates/applications/mirrored-templates.json +52 -0
  87. package/templates/applications/miso-controller/application.yaml +43 -1
  88. package/templates/applications/miso-controller/env.template +45 -39
  89. package/templates/marketplace/createUiDefinition.json +52 -4
  90. package/templates/marketplace/main.json +85 -7
@@ -23,10 +23,28 @@ const {
23
23
  resolveUpBuilderApiControllerContext,
24
24
  provisionAndDeployBuilderApi,
25
25
  refreshBuilderApiEnvAfterProvision,
26
- computeBuilderApiRunOverrides,
27
- resolveEnvironment
26
+ computeBuilderApiRunOverrides
28
27
  } = require('./up-builder-api-provision');
29
28
 
29
+ const BUILDER_API_ENVIRONMENT_KEY = 'miso';
30
+
31
+ /**
32
+ * Builder API is a platform application and is installed once in miso.
33
+ * The selected workload environment only controls which workloads it manages.
34
+ *
35
+ * @param {Object} options
36
+ * @returns {string}
37
+ */
38
+ function resolveBuilderApiEnvironmentKey(options) {
39
+ const requested = options && options.envKey
40
+ ? String(options.envKey).trim().toLowerCase()
41
+ : BUILDER_API_ENVIRONMENT_KEY;
42
+ if (requested !== BUILDER_API_ENVIRONMENT_KEY) {
43
+ throw new Error('Builder-api can only be installed in environment \'miso\'.');
44
+ }
45
+ return BUILDER_API_ENVIRONMENT_KEY;
46
+ }
47
+
30
48
  /**
31
49
  * @async
32
50
  * @param {Object} [options]
@@ -39,10 +57,7 @@ async function handleUpBuilderApi(options = {}) {
39
57
  );
40
58
  }
41
59
 
42
- const environmentKey =
43
- options && options.envKey
44
- ? String(options.envKey).trim().toLowerCase()
45
- : await resolveEnvironment();
60
+ const environmentKey = resolveBuilderApiEnvironmentKey(options);
46
61
 
47
62
  const { controllerUrl, authConfig, useBootstrap } = await resolveUpBuilderApiControllerContext(
48
63
  options,
@@ -63,6 +78,7 @@ async function handleUpBuilderApi(options = {}) {
63
78
  const { portOverride } = await computeBuilderApiRunOverrides(options);
64
79
 
65
80
  const runOpts = {
81
+ env: environmentKey,
66
82
  registry: options.registry,
67
83
  registryMode: options.registryMode,
68
84
  skipEnvOutputPath: true,
@@ -79,10 +95,10 @@ async function handleUpBuilderApi(options = {}) {
79
95
  if (options.platformInstall !== true) {
80
96
  logger.log(
81
97
  formatSuccessParagraph(
82
- 'up-builder-api complete. Builder-api is registered, deployed in dev, and running locally.'
98
+ 'up-builder-api complete. Builder-api is registered in miso and running locally.'
83
99
  )
84
100
  );
85
101
  }
86
102
  }
87
103
 
88
- module.exports = { handleUpBuilderApi };
104
+ module.exports = { handleUpBuilderApi, resolveBuilderApiEnvironmentKey };
@@ -331,12 +331,16 @@ const DATAPLANE_BUILDER_APP_KEYS = Object.freeze(['dataplane']);
331
331
  /** Builder API platform app key (154.2). */
332
332
  const BUILDER_API_APP_KEYS = Object.freeze(['builder-api']);
333
333
 
334
+ /** Optional integration server platform app key. */
335
+ const INTEGRATION_SERVER_APP_KEYS = Object.freeze(['integration-server']);
336
+
334
337
  /** Builder dirs removed on up-platform / setup force (includes Celery companions). */
335
338
  const PLATFORM_BUILDER_CLEAN_APP_KEYS = Object.freeze([
336
339
  'keycloak',
337
340
  'miso-controller',
338
341
  ...DATAPLANE_BUILDER_APP_KEYS,
339
- ...BUILDER_API_APP_KEYS
342
+ ...BUILDER_API_APP_KEYS,
343
+ ...INTEGRATION_SERVER_APP_KEYS
340
344
  ]);
341
345
 
342
346
  /**
@@ -375,6 +379,15 @@ async function ensureBuilderApiAppsFromTemplate(opts = {}) {
375
379
  return anyCopied;
376
380
  }
377
381
 
382
+ /**
383
+ * Ensure the optional integration server manifest exists under the system builder root.
384
+ * @param {object} [opts]
385
+ * @returns {Promise<boolean>}
386
+ */
387
+ async function ensureIntegrationServerAppsFromTemplate(opts = {}) {
388
+ return ensureAppFromTemplate(INTEGRATION_SERVER_APP_KEYS[0], opts);
389
+ }
390
+
378
391
  /**
379
392
  * Merge builder/packages `application.yaml` into `~/.aifabrix/urls.local.yaml`.
380
393
  * Same scan as the end of {@link prepareUrlsLocalRegistryForUpPlatform}; call after platform app
@@ -410,10 +423,12 @@ module.exports = {
410
423
  applyUpPlatformForceConfig,
411
424
  cleanBuilderAppDirs,
412
425
  BUILDER_API_APP_KEYS,
426
+ INTEGRATION_SERVER_APP_KEYS,
413
427
  DATAPLANE_BUILDER_APP_KEYS,
414
428
  PLATFORM_BUILDER_CLEAN_APP_KEYS,
415
429
  ensureAppFromTemplate,
416
430
  ensureBuilderApiAppsFromTemplate,
431
+ ensureIntegrationServerAppsFromTemplate,
417
432
  ensureDataplaneAppsFromTemplate,
418
433
  patchEnvOutputPathForDeployOnly,
419
434
  validateEnvOutputPathFolderOrNull,
@@ -19,14 +19,128 @@ const {
19
19
  extractAppConfiguration
20
20
  } = require('../utils/app-register-config');
21
21
  const { buildRegistrationData, saveLocalCredentials, applyLocalhostPortAdjustment } = require('../app/register');
22
- const { loadDataplanePipelineCredentialsFromSecretsFile } = require('./up-dataplane-credentials');
22
+ const {
23
+ loadDataplanePipelineCredentialsFromSecretsFile,
24
+ credentialsAcceptToken,
25
+ waitForDataplaneCredentials
26
+ } = require('./up-dataplane-credentials');
23
27
  const { formatSuccessLine } = require('../utils/cli-test-layout-chalk');
28
+ const { getToken } = require('../api/auth.api');
29
+ const { updateApplicationSelfStatus } = require('../api/applications.api');
30
+ const { tokenFrom } = require('../app/run-parameter-sync');
31
+ const { resolveDeclarativeShowUrlsForApp } = require('../core/secrets-env-declarative-expand');
32
+ const pathsUtil = require('../utils/paths');
24
33
 
25
34
  const ENCRYPTION_VAULT_KEYS = [
26
35
  'miso-controller-secrets-encryptionKeyVault',
27
36
  'secrets-encryptionKeyVault'
28
37
  ];
29
38
 
39
+ const BOOTSTRAP_CREDENTIAL_ERROR_CODE = 'DATAPLANE_BOOTSTRAP_CREDENTIALS_UNAVAILABLE';
40
+
41
+ /**
42
+ * Create a stable, secret-safe bootstrap credential error.
43
+ * @param {string} message
44
+ * @returns {Error}
45
+ */
46
+ function createBootstrapCredentialError(message) {
47
+ const error = new Error(message);
48
+ error.code = BOOTSTRAP_CREDENTIAL_ERROR_CODE;
49
+ return error;
50
+ }
51
+
52
+ /**
53
+ * @param {Object|null|undefined} credentials
54
+ * @returns {boolean}
55
+ */
56
+ function hasUsablePipelineCredentials(credentials) {
57
+ return Boolean(
58
+ credentials &&
59
+ typeof credentials.clientId === 'string' && credentials.clientId.trim() &&
60
+ typeof credentials.clientSecret === 'string' && credentials.clientSecret.trim()
61
+ );
62
+ }
63
+
64
+ /**
65
+ * Persist newly issued credentials and prove they can be loaded before deployment.
66
+ * @async
67
+ * @param {Object} responseData
68
+ * @param {string} controllerUrl
69
+ * @param {string} environmentKey
70
+ * @param {number} containerPort
71
+ * @returns {Promise<{status: string, credentialsAvailable: true}>}
72
+ */
73
+ async function persistBootstrapCredentials(responseData, controllerUrl, environmentKey, containerPort) {
74
+ try {
75
+ await saveLocalCredentials(responseData, controllerUrl, { required: true });
76
+ } catch {
77
+ throw createBootstrapCredentialError(
78
+ 'Dataplane bootstrap credentials could not be persisted locally; deployment was not started.'
79
+ );
80
+ }
81
+ const localCredentials = await loadDataplanePipelineCredentialsFromSecretsFile();
82
+ if (!hasUsablePipelineCredentials(localCredentials)) {
83
+ throw createBootstrapCredentialError(
84
+ 'Dataplane bootstrap credentials are not available from the supported local secret store; ' +
85
+ 'deployment was not started.'
86
+ );
87
+ }
88
+ await syncDataplaneApplicationUrls(
89
+ controllerUrl,
90
+ environmentKey,
91
+ responseData.credentials,
92
+ containerPort
93
+ );
94
+ if (!(await waitForDataplaneCredentials(controllerUrl))) {
95
+ throw createBootstrapCredentialError(
96
+ 'Dataplane bootstrap credentials were issued but the controller did not accept them in time. ' +
97
+ 'Deployment was not started; retry the command after checking the controller token endpoint.'
98
+ );
99
+ }
100
+ logger.log(formatSuccessLine('Dataplane bootstrap credentials saved locally'));
101
+ return { status: 'provisioned', credentialsAvailable: true };
102
+ }
103
+
104
+ /**
105
+ * Require existing supported local credentials for an idempotent response.
106
+ * @async
107
+ * @returns {Promise<{status: string, credentialsAvailable: true}>}
108
+ */
109
+ async function acceptAlreadyProvisionedDataplane(controllerUrl) {
110
+ const localCredentials = await loadDataplanePipelineCredentialsFromSecretsFile();
111
+ const accepted = hasUsablePipelineCredentials(localCredentials) &&
112
+ await credentialsAcceptToken(controllerUrl);
113
+ if (!accepted) {
114
+ throw createBootstrapCredentialError(
115
+ 'Dataplane is registered on the controller but pipeline credentials are missing locally. ' +
116
+ 'Use an authorized session to run: aifabrix app rotate-secret dataplane, then retry setup.'
117
+ );
118
+ }
119
+ logger.log(formatSuccessLine('Dataplane already provisioned (bootstrap idempotent)'));
120
+ return { status: 'already_provisioned', credentialsAvailable: true };
121
+ }
122
+
123
+ /**
124
+ * @async
125
+ * @param {Object} responseData
126
+ * @param {string} controllerUrl
127
+ * @param {string} environmentKey
128
+ * @param {number} containerPort
129
+ * @returns {Promise<{status: string, credentialsAvailable: true}>}
130
+ */
131
+ async function handleBootstrapResponse(responseData, controllerUrl, environmentKey, containerPort) {
132
+ if (hasUsablePipelineCredentials(responseData.credentials)) {
133
+ return persistBootstrapCredentials(responseData, controllerUrl, environmentKey, containerPort);
134
+ }
135
+ if (responseData.status === 'already_provisioned') {
136
+ return acceptAlreadyProvisionedDataplane(controllerUrl);
137
+ }
138
+ throw createBootstrapCredentialError(
139
+ 'Dataplane bootstrap returned no usable credentials or supported already-provisioned status; ' +
140
+ 'deployment was not started.'
141
+ );
142
+ }
143
+
30
144
  /**
31
145
  * @param {Record<string, unknown>} secrets
32
146
  * @returns {string|null}
@@ -87,6 +201,33 @@ async function canUseBootstrapProvision(options = {}) {
87
201
  return Boolean(encryptionKey);
88
202
  }
89
203
 
204
+ /**
205
+ * Persist canonical declarative URLs before Dataplane takes its bootstrap snapshot.
206
+ * @param {string} controllerUrl
207
+ * @param {string} environmentKey
208
+ * @param {{clientId: string, clientSecret: string}} credentials
209
+ * @param {number} containerPort
210
+ * @returns {Promise<void>}
211
+ */
212
+ async function syncDataplaneApplicationUrls(controllerUrl, environmentKey, credentials, containerPort) {
213
+ const appPath = pathsUtil.getBuilderPath('dataplane');
214
+ const variablesPath = pathsUtil.resolveApplicationConfigPath(appPath);
215
+ const urls = await resolveDeclarativeShowUrlsForApp('dataplane', appPath, variablesPath, 'docker');
216
+ if (!urls) {
217
+ throw new Error('Could not resolve canonical Dataplane public and internal URLs');
218
+ }
219
+ const tokenResponse = await getToken(credentials.clientId, credentials.clientSecret, controllerUrl);
220
+ const response = await updateApplicationSelfStatus(
221
+ controllerUrl,
222
+ environmentKey,
223
+ { type: 'client-token', token: tokenFrom(tokenResponse) },
224
+ { status: 'deploying', url: urls.publicUrl, internalUrl: urls.internalUrl, port: containerPort }
225
+ );
226
+ if (!response?.success) {
227
+ throw new Error('Controller rejected canonical Dataplane public and internal URLs');
228
+ }
229
+ }
230
+
90
231
  /**
91
232
  * Register dataplane via bootstrap endpoint (idempotent).
92
233
  * @async
@@ -94,7 +235,7 @@ async function canUseBootstrapProvision(options = {}) {
94
235
  * @param {string} controllerUrl
95
236
  * @param {string} environmentKey
96
237
  * @param {Function} buildDataplaneImageRefFn
97
- * @returns {Promise<void>}
238
+ * @returns {Promise<{status: string, credentialsAvailable: true}>}
98
239
  */
99
240
  async function bootstrapDataplaneApplication(options, controllerUrl, environmentKey, buildDataplaneImageRefFn) {
100
241
  const encryptionKey = await resolveBootstrapEncryptionKey();
@@ -107,6 +248,7 @@ async function bootstrapDataplaneApplication(options, controllerUrl, environment
107
248
 
108
249
  const { variables } = await loadVariablesYaml('dataplane');
109
250
  const appConfig = await extractAppConfiguration(variables, 'dataplane', options);
251
+ const containerPort = appConfig.port;
110
252
  await applyLocalhostPortAdjustment(appConfig, controllerUrl, options);
111
253
 
112
254
  const imageOverride = options.image || buildDataplaneImageRefFn(options);
@@ -126,24 +268,15 @@ async function bootstrapDataplaneApplication(options, controllerUrl, environment
126
268
  registrationData
127
269
  );
128
270
 
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
- }
271
+ return handleBootstrapResponse(responseData, controllerUrl, environmentKey, containerPort);
142
272
  }
143
273
 
144
274
  module.exports = {
145
275
  canUseBootstrapProvision,
146
276
  resolveBootstrapEncryptionKey,
147
277
  bootstrapDataplaneApplication,
148
- pickBootstrapEncryptionKeyFromSecrets
278
+ syncDataplaneApplicationUrls,
279
+ pickBootstrapEncryptionKeyFromSecrets,
280
+ hasUsablePipelineCredentials,
281
+ BOOTSTRAP_CREDENTIAL_ERROR_CODE
149
282
  };
@@ -193,8 +193,29 @@ async function alignDataplaneCredentialsAfterDeploy(options, controllerUrl) {
193
193
  }
194
194
  }
195
195
 
196
+ /**
197
+ * Wait until a freshly bootstrapped pair is accepted, before anything depends on it.
198
+ *
199
+ * The Controller can answer the bootstrap call before the pipeline client is usable, and
200
+ * the deploy that follows gets one attempt against one URL: validateDeployment retries only
201
+ * on a 5xx, so a 401 from a client that is not published yet ends the install. The align
202
+ * step already tolerates this, with 15 attempts across three bases, but it runs after the
203
+ * deploy — the forgiving check after the unforgiving one.
204
+ *
205
+ * @async
206
+ * @param {string} controllerUrl
207
+ * @param {Object} [retry] - Overrides for the bootstrap probe budget
208
+ * @returns {Promise<boolean>} True once the pair is accepted
209
+ */
210
+ async function waitForDataplaneCredentials(controllerUrl, retry = {}) {
211
+ const { maxAttempts, delayMs } = resolveCredentialProbeRetry({ useBootstrap: true, credentialProbeRetry: retry });
212
+ return credentialsAcceptTokenWithRetry(controllerUrl, maxAttempts, delayMs);
213
+ }
214
+
196
215
  module.exports = {
197
216
  alignDataplaneCredentialsAfterDeploy,
217
+ waitForDataplaneCredentials,
218
+ credentialsAcceptTokenWithRetry,
198
219
  probeDataplaneClientToken,
199
220
  credentialsAcceptToken,
200
221
  loadDataplanePipelineCredentialsFromSecretsFile,
@@ -227,6 +227,9 @@ async function deployDataplaneToController(options) {
227
227
  image: imageOverride,
228
228
  registryMode: options.registryMode,
229
229
  envKey: options.envKey ? String(options.envKey).trim().toLowerCase() : undefined,
230
+ // The bootstrap path has no device login, so it sets client-credentials explicitly.
231
+ // Dropping it here left the deploy in 'auto' mode.
232
+ deploymentAuth: options.deploymentAuth,
230
233
  // Guided up-platform already shows a top-level spinner for the dataplane step.
231
234
  // Avoid nested deploy polling spinners/logs.
232
235
  silentPoll: options.platformInstall === true
@@ -266,7 +269,8 @@ function emitDataplaneManifestLineFromBuilder() {
266
269
  }
267
270
 
268
271
  function computeDataplanePortOverride(runEnvKey, developerIdNum) {
269
- if (runEnvKey !== 'dev' && runEnvKey !== 'tst') {
272
+ const { isScopedRunEnvironmentKey } = require('../utils/environment-scoped-resources');
273
+ if (!isScopedRunEnvironmentKey(runEnvKey)) {
270
274
  return undefined;
271
275
  }
272
276
  return dataplaneDockerHostPort(3001, developerIdNum, runEnvKey);
@@ -340,21 +344,26 @@ async function provisionAndDeployDataplane(options, environmentKey, controllerUr
340
344
  logger.log('');
341
345
  }
342
346
 
343
- /**
344
- * Handle up-dataplane command: ensure logged in, environment dev, ensure dataplane,
345
- * register or rotate (if already registered), deploy (send manifest to controller),
346
- * then run dataplane app locally (always local deployment).
347
- *
348
- * @async
349
- * @function handleUpDataplane
350
- * @param {Object} options - Commander options
351
- * @param {string} [options.registry] - Override registry for dataplane
352
- * @param {string} [options.registryMode] - Override registry mode (acr|external)
353
- * @param {string} [options.image] - Override image reference for dataplane
354
- * @param {boolean} [options.skipInfraCheck] - When true, skip Postgres/Redis gate (caller already verified, e.g. guided up-dataplane).
355
- * @returns {Promise<void>}
356
- * @throws {Error} If infra not up, controller unavailable, not logged in, environment not dev, or any step fails
357
- */
347
+ async function runDataplaneLocally(options) {
348
+ const { runEnvKey, portOverride } = await computeDataplaneRunOverrides(options);
349
+ const runAppOpts = {
350
+ skipEnvOutputPath: true,
351
+ env: runEnvKey,
352
+ port: portOverride,
353
+ registry: options.registry || undefined,
354
+ base: options.base !== false,
355
+ skipManifestMetadataLine: true
356
+ };
357
+ if (options.platformInstall === true) {
358
+ if (typeof options.onPollText === 'function') {
359
+ runAppOpts.onPollText = options.onPollText;
360
+ } else {
361
+ runAppOpts.silentPoll = true;
362
+ }
363
+ }
364
+ await app.runApp('dataplane', runAppOpts);
365
+ }
366
+
358
367
  async function handleUpDataplane(options = {}) {
359
368
  if (options.platformInstall !== true) {
360
369
  logger.log(chalk.blue('Starting up-dataplane (register/rotate, deploy, then run dataplane locally)...\n'));
@@ -385,24 +394,7 @@ async function handleUpDataplane(options = {}) {
385
394
  controllerUrl
386
395
  );
387
396
 
388
- const { runEnvKey, portOverride } = await computeDataplaneRunOverrides(options);
389
-
390
- const runAppOpts = {
391
- skipEnvOutputPath: true,
392
- env: runEnvKey,
393
- port: portOverride,
394
- registry: options.registry || undefined,
395
- base: options.base !== false,
396
- skipManifestMetadataLine: true
397
- };
398
- if (options.platformInstall === true) {
399
- if (typeof options.onPollText === 'function') {
400
- runAppOpts.onPollText = options.onPollText;
401
- } else {
402
- runAppOpts.silentPoll = true;
403
- }
404
- }
405
- await app.runApp('dataplane', runAppOpts);
397
+ await runDataplaneLocally(options);
406
398
 
407
399
  if (options.platformInstall !== true) {
408
400
  logger.log(formatSuccessParagraph('up-dataplane complete. Dataplane is registered, deployed in dev, and running locally.'));
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Install the optional Integration Server as a miso-only platform application.
3
+ *
4
+ * @fileoverview Integration Server setup phase
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const app = require('../app');
10
+ const config = require('../core/config');
11
+ const { generateEnvFile } = require('../core/secrets');
12
+ const pathsUtil = require('../utils/paths');
13
+ const { loadConfigFile } = require('../utils/config-format');
14
+ const { resolveDockerImageRef } = require('../utils/resolve-docker-image-ref');
15
+ const { callBootstrapApi } = require('../utils/app-bootstrap-api');
16
+ const { loadVariablesYaml, extractAppConfiguration } = require('../utils/app-register-config');
17
+ const { buildRegistrationData, saveLocalCredentials, applyLocalhostPortAdjustment } = require('../app/register');
18
+ const { publishedHostPort, parseDeveloperIdNum } = require('../utils/declarative-url-ports');
19
+ const { resolveBootstrapEncryptionKey } = require('./up-dataplane-bootstrap');
20
+ const { resolveUpBuilderApiControllerContext } = require('./up-builder-api-provision');
21
+ const {
22
+ ensureIntegrationServerAppsFromTemplate,
23
+ refreshUrlsLocalRegistryForCurrentProject
24
+ } = require('./up-common');
25
+ const { resolveLocalOnboardingApps } = require('../core/local-env-overrides');
26
+
27
+ const APP_KEY = 'integration-server';
28
+ const ENVIRONMENT_KEY = 'miso';
29
+
30
+ async function isIntegrationServerSelected() {
31
+ const cfg = await config.getConfig();
32
+ return resolveLocalOnboardingApps(cfg).includes(APP_KEY);
33
+ }
34
+
35
+ function resolveIntegrationServerImageRef(options = {}) {
36
+ const appPath = pathsUtil.getBuilderPath(APP_KEY);
37
+ const manifestPath = pathsUtil.resolveApplicationConfigPath(appPath);
38
+ const variables = loadConfigFile(manifestPath) || {};
39
+ const { imageName, imageTag } = resolveDockerImageRef(APP_KEY, variables, {
40
+ registry: options.registry
41
+ });
42
+ return `${imageName}:${imageTag}`;
43
+ }
44
+
45
+ async function bootstrapIntegrationServer(options, controllerUrl) {
46
+ const encryptionKey = await resolveBootstrapEncryptionKey();
47
+ if (!encryptionKey) {
48
+ throw new Error('Bootstrap encryption key is unavailable for Integration Server installation.');
49
+ }
50
+ const { variables } = await loadVariablesYaml(APP_KEY);
51
+ const appConfig = await extractAppConfiguration(variables, APP_KEY, options);
52
+ await applyLocalhostPortAdjustment(appConfig, controllerUrl, options);
53
+ const image = options.image || resolveIntegrationServerImageRef(options);
54
+ const registrationData = buildRegistrationData(appConfig, {
55
+ image,
56
+ imageOverride: image,
57
+ registryMode: options.registryMode,
58
+ envKey: ENVIRONMENT_KEY
59
+ });
60
+ const response = await callBootstrapApi(
61
+ controllerUrl,
62
+ encryptionKey,
63
+ ENVIRONMENT_KEY,
64
+ registrationData
65
+ );
66
+ if (!response.credentials || !response.credentials.clientSecret) {
67
+ throw new Error('Integration Server bootstrap did not return its existing credentials.');
68
+ }
69
+ await saveLocalCredentials(response, controllerUrl);
70
+ }
71
+
72
+ async function runIntegrationServer(options) {
73
+ await generateEnvFile(APP_KEY, null, 'local', true);
74
+ const developerId = parseDeveloperIdNum(await config.getDeveloperId());
75
+ await app.runApp(APP_KEY, {
76
+ env: ENVIRONMENT_KEY,
77
+ registry: options.registry,
78
+ registryMode: options.registryMode,
79
+ image: options.image,
80
+ port: publishedHostPort(3006, developerId),
81
+ skipEnvOutputPath: true,
82
+ skipInfraCheck: true,
83
+ skipManifestMetadataLine: true,
84
+ silentPoll: true
85
+ });
86
+ }
87
+
88
+ async function handleUpIntegrationServer(options = {}) {
89
+ await ensureIntegrationServerAppsFromTemplate({ silent: true });
90
+ refreshUrlsLocalRegistryForCurrentProject();
91
+ const context = await resolveUpBuilderApiControllerContext(options, ENVIRONMENT_KEY);
92
+ if (!context.useBootstrap) {
93
+ throw new Error('Integration Server installation requires local bootstrap provisioning.');
94
+ }
95
+ await bootstrapIntegrationServer(options, context.controllerUrl);
96
+ const image = options.image || resolveIntegrationServerImageRef(options);
97
+ await app.deployApp(APP_KEY, {
98
+ image,
99
+ imageOverride: image,
100
+ registryMode: options.registryMode,
101
+ envKey: ENVIRONMENT_KEY,
102
+ deploymentAuth: 'client-credentials',
103
+ silentPoll: true
104
+ });
105
+ await runIntegrationServer({ ...options, image });
106
+ }
107
+
108
+ module.exports = {
109
+ handleUpIntegrationServer,
110
+ isIntegrationServerSelected,
111
+ resolveIntegrationServerImageRef
112
+ };
@@ -6,7 +6,9 @@
6
6
 
7
7
  const { repairKeyedDocument } = require('./repair-datasource-keyed-document');
8
8
 
9
- const ENTITY_TYPE_FALLBACK = 'record-storage';
9
+ const { repairLegacyDatasource } = require('./repair-datasource-legacy');
10
+
11
+ const ENTITY_TYPE_FALLBACK = 'recordStorage';
10
12
  const VALID_ENTITY_TYPES = new Set([
11
13
  'document-storage',
12
14
  'documentStorage',
@@ -16,6 +18,7 @@ const VALID_ENTITY_TYPES = new Set([
16
18
  'recordStorage',
17
19
  'message-service',
18
20
  'messageService',
21
+ 'serviceContract',
19
22
  'none'
20
23
  ]);
21
24
  const VALID_PORTAL_FIELDS = new Set(['text', 'textarea', 'select', 'json', 'boolean', 'number']);
@@ -89,6 +92,7 @@ function normalizeDatasourceConfig(datasourceConfig, openapiSpec) {
89
92
  if (datasourceConfig.entityType && !VALID_ENTITY_TYPES.has(datasourceConfig.entityType)) {
90
93
  datasourceConfig.entityType = ENTITY_TYPE_FALLBACK;
91
94
  }
95
+ repairLegacyDatasource(datasourceConfig);
92
96
  normalizePortalInput(datasourceConfig);
93
97
  normalizeCipOutputModes(datasourceConfig);
94
98
  repairKeyedDocument(datasourceConfig, { openapiSpec });
@@ -10,9 +10,10 @@
10
10
 
11
11
  /**
12
12
  * @param {Object.<string, string>} adminObj
13
+ * @param {string} [appName]
13
14
  * @returns {Object.<string, string>}
14
15
  */
15
- function buildPlatformEnvOverlayFromAdminSecrets(adminObj) {
16
+ function buildPlatformEnvOverlayFromAdminSecrets(adminObj, appName) {
16
17
  if (!adminObj || typeof adminObj !== 'object') return {};
17
18
  const platformPwd =
18
19
  adminObj.PLATFORM_ADMIN_PASSWORD ||
@@ -39,6 +40,9 @@ function buildPlatformEnvOverlayFromAdminSecrets(adminObj) {
39
40
  if (email) {
40
41
  overlay.ONBOARDING_ADMIN_EMAIL = email;
41
42
  }
43
+ if (appName === 'miso-controller' && adminObj.POSTGRES_PASSWORD) {
44
+ overlay.POSTGRES_ADMIN_PASSWORD = adminObj.POSTGRES_PASSWORD;
45
+ }
42
46
  return overlay;
43
47
  }
44
48
 
@@ -46,9 +50,10 @@ function buildPlatformEnvOverlayFromAdminSecrets(adminObj) {
46
50
  * Apply admin-secrets values over resolved app env (admin-secrets wins for platform keys).
47
51
  * @param {Object.<string, string>} envMap
48
52
  * @param {Object.<string, string>} adminObj
53
+ * @param {string} [appName]
49
54
  */
50
- function applyAdminSecretsPlatformOverlay(envMap, adminObj) {
51
- const overlay = buildPlatformEnvOverlayFromAdminSecrets(adminObj);
55
+ function applyAdminSecretsPlatformOverlay(envMap, adminObj, appName) {
56
+ const overlay = buildPlatformEnvOverlayFromAdminSecrets(adminObj, appName);
52
57
  for (const [key, value] of Object.entries(overlay)) {
53
58
  if (value !== undefined && value !== null && String(value).trim() !== '') {
54
59
  envMap[key] = String(value);