@aifabrix/builder 2.37.9 → 2.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor/rules/project-rules.mdc +3 -0
- package/README.md +19 -0
- package/integration/hubspot/hubspot-deploy.json +1 -5
- package/integration/hubspot/hubspot-system.json +0 -3
- package/lib/api/applications.api.js +29 -1
- package/lib/api/auth.api.js +14 -0
- package/lib/api/credentials.api.js +34 -0
- package/lib/api/datasources-core.api.js +16 -1
- package/lib/api/datasources-extended.api.js +18 -1
- package/lib/api/deployments.api.js +32 -0
- package/lib/api/environments.api.js +11 -0
- package/lib/api/external-systems.api.js +16 -1
- package/lib/api/pipeline.api.js +12 -4
- package/lib/api/service-users.api.js +41 -0
- package/lib/api/types/applications.types.js +1 -1
- package/lib/api/types/deployments.types.js +1 -1
- package/lib/api/types/pipeline.types.js +1 -1
- package/lib/api/types/service-users.types.js +24 -0
- package/lib/api/wizard.api.js +40 -1
- package/lib/app/deploy.js +86 -21
- package/lib/app/rotate-secret.js +3 -1
- package/lib/app/run-helpers.js +35 -2
- package/lib/app/show-display.js +30 -11
- package/lib/app/show.js +34 -8
- package/lib/cli/index.js +4 -0
- package/lib/cli/setup-app.js +40 -0
- package/lib/cli/setup-credential-deployment.js +72 -0
- package/lib/cli/setup-infra.js +3 -3
- package/lib/cli/setup-service-user.js +52 -0
- package/lib/cli/setup-utility.js +1 -25
- package/lib/commands/app-down.js +80 -0
- package/lib/commands/app-logs.js +146 -0
- package/lib/commands/app.js +24 -1
- package/lib/commands/credential-list.js +104 -0
- package/lib/commands/deployment-list.js +184 -0
- package/lib/commands/service-user.js +193 -0
- package/lib/commands/up-common.js +74 -5
- package/lib/commands/up-dataplane.js +13 -7
- package/lib/commands/up-miso.js +17 -24
- package/lib/core/templates.js +2 -2
- package/lib/external-system/deploy.js +79 -15
- package/lib/generator/builders.js +8 -27
- package/lib/generator/external-controller-manifest.js +5 -4
- package/lib/generator/index.js +16 -14
- package/lib/generator/split.js +1 -0
- package/lib/generator/wizard.js +4 -1
- package/lib/schema/application-schema.json +6 -14
- package/lib/schema/deployment-rules.yaml +121 -0
- package/lib/schema/external-system.schema.json +0 -16
- package/lib/utils/app-register-config.js +10 -12
- package/lib/utils/app-run-containers.js +2 -1
- package/lib/utils/compose-generator.js +2 -1
- package/lib/utils/deployment-errors.js +10 -0
- package/lib/utils/environment-checker.js +25 -6
- package/lib/utils/help-builder.js +0 -1
- package/lib/utils/image-version.js +209 -0
- package/lib/utils/schema-loader.js +1 -1
- package/lib/utils/variable-transformer.js +7 -33
- package/lib/validation/external-manifest-validator.js +1 -1
- package/package.json +1 -1
- package/templates/applications/README.md.hbs +1 -3
- package/templates/applications/dataplane/Dockerfile +2 -2
- package/templates/applications/dataplane/README.md +20 -6
- package/templates/applications/dataplane/env.template +31 -2
- package/templates/applications/dataplane/rbac.yaml +1 -1
- package/templates/applications/dataplane/variables.yaml +7 -4
- package/templates/applications/keycloak/Dockerfile +3 -3
- package/templates/applications/keycloak/README.md +14 -4
- package/templates/applications/keycloak/env.template +17 -2
- package/templates/applications/keycloak/variables.yaml +2 -1
- package/templates/applications/miso-controller/README.md +1 -3
- package/templates/applications/miso-controller/env.template +85 -25
- package/templates/applications/miso-controller/rbac.yaml +15 -0
- package/templates/applications/miso-controller/variables.yaml +24 -23
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
const path = require('path');
|
|
12
12
|
const fs = require('fs');
|
|
13
|
+
const yaml = require('js-yaml');
|
|
13
14
|
const chalk = require('chalk');
|
|
14
15
|
const logger = require('../utils/logger');
|
|
15
16
|
const pathsUtil = require('../utils/paths');
|
|
@@ -30,8 +31,74 @@ async function ensureTemplateAtPath(appName, targetAppPath) {
|
|
|
30
31
|
return true;
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Resolve the directory (folder) that would contain the .env file for envOutputPath.
|
|
36
|
+
* @param {string} envOutputPath - Value from build.envOutputPath (e.g. ../../.env)
|
|
37
|
+
* @param {string} variablesPath - Path to variables.yaml
|
|
38
|
+
* @returns {string} Absolute path to the folder that would contain the output .env file
|
|
39
|
+
*/
|
|
40
|
+
function getEnvOutputPathFolder(envOutputPath, variablesPath) {
|
|
41
|
+
const variablesDir = path.dirname(variablesPath);
|
|
42
|
+
const resolvedFile = path.resolve(variablesDir, envOutputPath);
|
|
43
|
+
return path.dirname(resolvedFile);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Validates envOutputPath: if the target folder does not exist, patches variables.yaml to set envOutputPath to null.
|
|
48
|
+
* Used by up-platform, up-miso, up-dataplane so we do not keep a path that points outside an existing tree.
|
|
49
|
+
*
|
|
50
|
+
* @param {string} appName - Application name (e.g. keycloak, miso-controller, dataplane)
|
|
51
|
+
*/
|
|
52
|
+
function validateEnvOutputPathFolderOrNull(appName) {
|
|
53
|
+
if (!appName || typeof appName !== 'string') return;
|
|
54
|
+
const pathsToPatch = [pathsUtil.getBuilderPath(appName)];
|
|
55
|
+
const cwdBuilderPath = path.join(process.cwd(), 'builder', appName);
|
|
56
|
+
if (path.resolve(cwdBuilderPath) !== path.resolve(pathsToPatch[0])) {
|
|
57
|
+
pathsToPatch.push(cwdBuilderPath);
|
|
58
|
+
}
|
|
59
|
+
const envOutputPathLine = /^(\s*envOutputPath:)\s*.*$/m;
|
|
60
|
+
const replacement = '$1 null # deploy only, no copy';
|
|
61
|
+
for (const appPath of pathsToPatch) {
|
|
62
|
+
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
63
|
+
if (!fs.existsSync(variablesPath)) continue;
|
|
64
|
+
try {
|
|
65
|
+
const content = fs.readFileSync(variablesPath, 'utf8');
|
|
66
|
+
const variables = yaml.load(content);
|
|
67
|
+
const value = variables?.build?.envOutputPath;
|
|
68
|
+
if (value === null || value === undefined || value === '') continue;
|
|
69
|
+
const folder = getEnvOutputPathFolder(String(value).trim(), variablesPath);
|
|
70
|
+
if (fs.existsSync(folder)) continue;
|
|
71
|
+
const newContent = content.replace(envOutputPathLine, replacement);
|
|
72
|
+
fs.writeFileSync(variablesPath, newContent, 'utf8');
|
|
73
|
+
} catch (err) {
|
|
74
|
+
logger.warn(chalk.yellow(`Could not validate envOutputPath in ${variablesPath}: ${err.message}`));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Patches a single variables.yaml to set build.envOutputPath to null for deploy-only.
|
|
81
|
+
*
|
|
82
|
+
* @param {string} variablesPath - Path to variables.yaml
|
|
83
|
+
* @param {RegExp} envOutputPathLine - Regex for envOutputPath line
|
|
84
|
+
* @param {string} replacement - Replacement string
|
|
85
|
+
*/
|
|
86
|
+
function patchOneVariablesFileForDeployOnly(variablesPath, envOutputPathLine, replacement) {
|
|
87
|
+
const content = fs.readFileSync(variablesPath, 'utf8');
|
|
88
|
+
if (!envOutputPathLine.test(content)) return;
|
|
89
|
+
const variables = yaml.load(content);
|
|
90
|
+
const value = variables?.build?.envOutputPath;
|
|
91
|
+
if (value !== null && value !== undefined && value !== '') {
|
|
92
|
+
const folder = getEnvOutputPathFolder(String(value).trim(), variablesPath);
|
|
93
|
+
if (fs.existsSync(folder)) return;
|
|
94
|
+
}
|
|
95
|
+
const newContent = content.replace(envOutputPathLine, replacement);
|
|
96
|
+
fs.writeFileSync(variablesPath, newContent, 'utf8');
|
|
97
|
+
}
|
|
98
|
+
|
|
33
99
|
/**
|
|
34
100
|
* Patches variables.yaml to set build.envOutputPath to null for deploy-only (no local code).
|
|
101
|
+
* Only patches when the target folder does NOT exist; when folder exists, keeps the value.
|
|
35
102
|
* Use when running up-miso/up-platform so we do not copy .env to repo paths or show that message.
|
|
36
103
|
* Patches both primary builder path and cwd/builder if different.
|
|
37
104
|
*
|
|
@@ -50,10 +117,7 @@ function patchEnvOutputPathForDeployOnly(appName) {
|
|
|
50
117
|
const variablesPath = path.join(appPath, 'variables.yaml');
|
|
51
118
|
if (!fs.existsSync(variablesPath)) continue;
|
|
52
119
|
try {
|
|
53
|
-
|
|
54
|
-
if (!envOutputPathLine.test(content)) continue;
|
|
55
|
-
content = content.replace(envOutputPathLine, replacement);
|
|
56
|
-
fs.writeFileSync(variablesPath, content, 'utf8');
|
|
120
|
+
patchOneVariablesFileForDeployOnly(variablesPath, envOutputPathLine, replacement);
|
|
57
121
|
} catch (err) {
|
|
58
122
|
logger.warn(chalk.yellow(`Could not patch envOutputPath in ${variablesPath}: ${err.message}`));
|
|
59
123
|
}
|
|
@@ -99,4 +163,9 @@ async function ensureAppFromTemplate(appName) {
|
|
|
99
163
|
return primaryCopied;
|
|
100
164
|
}
|
|
101
165
|
|
|
102
|
-
module.exports = {
|
|
166
|
+
module.exports = {
|
|
167
|
+
ensureAppFromTemplate,
|
|
168
|
+
patchEnvOutputPathForDeployOnly,
|
|
169
|
+
validateEnvOutputPathFolderOrNull,
|
|
170
|
+
getEnvOutputPathFolder
|
|
171
|
+
};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AI Fabrix Builder - Up Dataplane Command
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Always local deployment: registers or rotates dataplane app in dev, sends
|
|
5
|
+
* deployment manifest to Miso Controller, then runs the dataplane app locally
|
|
6
|
+
* (same as aifabrix deploy dataplane --deployment=local). If app is already
|
|
7
|
+
* registered, uses rotate-secret; otherwise registers.
|
|
7
8
|
*
|
|
8
9
|
* @fileoverview up-dataplane command implementation
|
|
9
10
|
* @author AI Fabrix Team
|
|
@@ -23,7 +24,7 @@ const { registerApplication } = require('../app/register');
|
|
|
23
24
|
const { rotateSecret } = require('../app/rotate-secret');
|
|
24
25
|
const { checkApplicationExists } = require('../utils/app-existence');
|
|
25
26
|
const app = require('../app');
|
|
26
|
-
const { ensureAppFromTemplate } = require('./up-common');
|
|
27
|
+
const { ensureAppFromTemplate, validateEnvOutputPathFolderOrNull } = require('./up-common');
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
30
|
* Register or rotate dataplane: if app exists in controller, rotate secret; otherwise register.
|
|
@@ -64,7 +65,8 @@ function buildDataplaneImageRef(registry) {
|
|
|
64
65
|
|
|
65
66
|
/**
|
|
66
67
|
* Handle up-dataplane command: ensure logged in, environment dev, ensure dataplane,
|
|
67
|
-
* register or rotate (if already registered),
|
|
68
|
+
* register or rotate (if already registered), deploy (send manifest to controller),
|
|
69
|
+
* then run dataplane app locally (always local deployment).
|
|
68
70
|
*
|
|
69
71
|
* @async
|
|
70
72
|
* @function handleUpDataplane
|
|
@@ -80,7 +82,7 @@ async function handleUpDataplane(options = {}) {
|
|
|
80
82
|
if (builderDir) {
|
|
81
83
|
process.env.AIFABRIX_BUILDER_DIR = builderDir;
|
|
82
84
|
}
|
|
83
|
-
logger.log(chalk.blue('Starting up-dataplane (register/rotate, deploy dataplane
|
|
85
|
+
logger.log(chalk.blue('Starting up-dataplane (register/rotate, deploy, then run dataplane locally)...\n'));
|
|
84
86
|
|
|
85
87
|
const [controllerUrl, environmentKey] = await Promise.all([resolveControllerUrl(), resolveEnvironment()]);
|
|
86
88
|
const authConfig = await checkAuthentication(controllerUrl, environmentKey);
|
|
@@ -95,6 +97,8 @@ async function handleUpDataplane(options = {}) {
|
|
|
95
97
|
logger.log(chalk.green('✓ Logged in and environment is dev'));
|
|
96
98
|
|
|
97
99
|
await ensureAppFromTemplate('dataplane');
|
|
100
|
+
// If envOutputPath target folder does not exist, set envOutputPath to null
|
|
101
|
+
validateEnvOutputPathFolderOrNull('dataplane');
|
|
98
102
|
|
|
99
103
|
await registerOrRotateDataplane(options, controllerUrl, environmentKey, authConfig);
|
|
100
104
|
|
|
@@ -102,8 +106,10 @@ async function handleUpDataplane(options = {}) {
|
|
|
102
106
|
const deployOpts = { imageOverride, image: imageOverride, registryMode: options.registryMode };
|
|
103
107
|
|
|
104
108
|
await app.deployApp('dataplane', deployOpts);
|
|
109
|
+
logger.log('');
|
|
110
|
+
await app.runApp('dataplane', {});
|
|
105
111
|
|
|
106
|
-
logger.log(chalk.green('\n✓ up-dataplane complete. Dataplane is registered
|
|
112
|
+
logger.log(chalk.green('\n✓ up-dataplane complete. Dataplane is registered, deployed in dev, and running locally.'));
|
|
107
113
|
}
|
|
108
114
|
|
|
109
115
|
module.exports = { handleUpDataplane, buildDataplaneImageRef };
|
package/lib/commands/up-miso.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AI Fabrix Builder - Up Miso Command
|
|
3
3
|
*
|
|
4
|
-
* Installs keycloak
|
|
4
|
+
* Installs keycloak and miso-controller from images (no build). For dataplane, use up-dataplane.
|
|
5
5
|
* Assumes infra is up; sets dev secrets and resolves (no force; existing .env values preserved).
|
|
6
6
|
*
|
|
7
7
|
* @fileoverview up-miso command implementation
|
|
@@ -19,19 +19,16 @@ const secrets = require('../core/secrets');
|
|
|
19
19
|
const infra = require('../infrastructure');
|
|
20
20
|
const app = require('../app');
|
|
21
21
|
const { saveLocalSecret } = require('../utils/local-secrets');
|
|
22
|
-
const { ensureAppFromTemplate, patchEnvOutputPathForDeployOnly } = require('./up-common');
|
|
22
|
+
const { ensureAppFromTemplate, patchEnvOutputPathForDeployOnly, validateEnvOutputPathFolderOrNull } = require('./up-common');
|
|
23
23
|
|
|
24
24
|
/** Keycloak base port (from templates/applications/keycloak/variables.yaml) */
|
|
25
25
|
const KEYCLOAK_BASE_PORT = 8082;
|
|
26
26
|
/** Miso controller base port (dev-config app base) */
|
|
27
27
|
const MISO_BASE_PORT = 3000;
|
|
28
|
-
/** Dataplane base port (from templates/applications/dataplane/variables.yaml) */
|
|
29
|
-
const _DATAPLANE_BASE_PORT = 3001;
|
|
30
|
-
|
|
31
28
|
/**
|
|
32
|
-
* Parse --image options array into map { keycloak?: string, 'miso-controller'?: string
|
|
33
|
-
* @param {string[]|string} imageOpts - Option value(s) e.g. ['keycloak=reg/k:v1', 'miso-controller=reg/m:v1'
|
|
34
|
-
* @returns {{ keycloak?: string, 'miso-controller'?: string
|
|
29
|
+
* Parse --image options array into map { keycloak?: string, 'miso-controller'?: string }
|
|
30
|
+
* @param {string[]|string} imageOpts - Option value(s) e.g. ['keycloak=reg/k:v1', 'miso-controller=reg/m:v1']
|
|
31
|
+
* @returns {{ keycloak?: string, 'miso-controller'?: string }}
|
|
35
32
|
*/
|
|
36
33
|
function parseImageOptions(imageOpts) {
|
|
37
34
|
const map = {};
|
|
@@ -50,7 +47,7 @@ function parseImageOptions(imageOpts) {
|
|
|
50
47
|
|
|
51
48
|
/**
|
|
52
49
|
* Build full image ref from registry and app variables (registry/name:tag)
|
|
53
|
-
* @param {string} appName - keycloak
|
|
50
|
+
* @param {string} appName - keycloak or miso-controller
|
|
54
51
|
* @param {string} registry - Registry URL
|
|
55
52
|
* @returns {string} Full image reference
|
|
56
53
|
*/
|
|
@@ -67,7 +64,7 @@ function buildImageRefFromRegistry(appName, registry) {
|
|
|
67
64
|
}
|
|
68
65
|
|
|
69
66
|
/**
|
|
70
|
-
* Set URL secrets and resolve keycloak + miso-controller
|
|
67
|
+
* Set URL secrets and resolve keycloak + miso-controller (no force; existing .env preserved)
|
|
71
68
|
* @async
|
|
72
69
|
* @param {number} devIdNum - Developer ID number
|
|
73
70
|
*/
|
|
@@ -79,12 +76,11 @@ async function setMisoSecretsAndResolve(devIdNum) {
|
|
|
79
76
|
logger.log(chalk.green('✓ Set keycloak and miso-controller URL secrets'));
|
|
80
77
|
await secrets.generateEnvFile('keycloak', undefined, 'docker', false, true);
|
|
81
78
|
await secrets.generateEnvFile('miso-controller', undefined, 'docker', false, true);
|
|
82
|
-
|
|
83
|
-
logger.log(chalk.green('✓ Resolved keycloak, miso-controller, and dataplane'));
|
|
79
|
+
logger.log(chalk.green('✓ Resolved keycloak and miso-controller'));
|
|
84
80
|
}
|
|
85
81
|
|
|
86
82
|
/**
|
|
87
|
-
* Build run options and run keycloak, miso-controller
|
|
83
|
+
* Build run options and run keycloak, then miso-controller
|
|
88
84
|
* @async
|
|
89
85
|
* @param {Object} options - Commander options (image, registry, registryMode)
|
|
90
86
|
*/
|
|
@@ -92,27 +88,23 @@ async function runMisoApps(options) {
|
|
|
92
88
|
const imageMap = parseImageOptions(options.image);
|
|
93
89
|
const keycloakImage = imageMap.keycloak || (options.registry ? buildImageRefFromRegistry('keycloak', options.registry) : undefined);
|
|
94
90
|
const misoImage = imageMap['miso-controller'] || (options.registry ? buildImageRefFromRegistry('miso-controller', options.registry) : undefined);
|
|
95
|
-
const dataplaneImage = imageMap.dataplane || (options.registry ? buildImageRefFromRegistry('dataplane', options.registry) : undefined);
|
|
96
91
|
const keycloakRunOpts = { image: keycloakImage, registry: options.registry, registryMode: options.registryMode, skipEnvOutputPath: true, skipInfraCheck: true };
|
|
97
92
|
const misoRunOpts = { image: misoImage, registry: options.registry, registryMode: options.registryMode, skipEnvOutputPath: true, skipInfraCheck: true };
|
|
98
|
-
const dataplaneRunOpts = { image: dataplaneImage, registry: options.registry, registryMode: options.registryMode, skipEnvOutputPath: true, skipInfraCheck: true };
|
|
99
93
|
logger.log(chalk.blue('Starting keycloak...'));
|
|
100
94
|
await app.runApp('keycloak', keycloakRunOpts);
|
|
101
95
|
logger.log(chalk.blue('Starting miso-controller...'));
|
|
102
96
|
await app.runApp('miso-controller', misoRunOpts);
|
|
103
|
-
logger.log(chalk.blue('Starting dataplane...'));
|
|
104
|
-
await app.runApp('dataplane', dataplaneRunOpts);
|
|
105
97
|
}
|
|
106
98
|
|
|
107
99
|
/**
|
|
108
|
-
* Handle up-miso command: ensure infra, ensure app dirs, set secrets, resolve (preserve existing .env), run keycloak
|
|
100
|
+
* Handle up-miso command: ensure infra, ensure app dirs, set secrets, resolve (preserve existing .env), run keycloak and miso-controller.
|
|
109
101
|
*
|
|
110
102
|
* @async
|
|
111
103
|
* @function handleUpMiso
|
|
112
104
|
* @param {Object} options - Commander options
|
|
113
105
|
* @param {string} [options.registry] - Override registry for all apps
|
|
114
106
|
* @param {string} [options.registryMode] - Override registry mode (acr|external)
|
|
115
|
-
* @param {string[]|string} [options.image] - Override images e.g. keycloak=reg/k:v1, miso-controller=reg/m:v1
|
|
107
|
+
* @param {string[]|string} [options.image] - Override images e.g. keycloak=reg/k:v1, miso-controller=reg/m:v1
|
|
116
108
|
* @returns {Promise<void>}
|
|
117
109
|
* @throws {Error} If infra not up or any step fails
|
|
118
110
|
*/
|
|
@@ -121,7 +113,7 @@ async function handleUpMiso(options = {}) {
|
|
|
121
113
|
if (builderDir) {
|
|
122
114
|
process.env.AIFABRIX_BUILDER_DIR = builderDir;
|
|
123
115
|
}
|
|
124
|
-
logger.log(chalk.blue('Starting up-miso (keycloak + miso-controller
|
|
116
|
+
logger.log(chalk.blue('Starting up-miso (keycloak + miso-controller from images)...\n'));
|
|
125
117
|
// Strict: only this developer's infra (same as status), so up-miso and status agree
|
|
126
118
|
const health = await infra.checkInfraHealth(undefined, { strict: true });
|
|
127
119
|
const allHealthy = Object.values(health).every(status => status === 'healthy');
|
|
@@ -131,17 +123,18 @@ async function handleUpMiso(options = {}) {
|
|
|
131
123
|
logger.log(chalk.green('✓ Infrastructure is up'));
|
|
132
124
|
await ensureAppFromTemplate('keycloak');
|
|
133
125
|
await ensureAppFromTemplate('miso-controller');
|
|
134
|
-
|
|
126
|
+
// If envOutputPath target folder does not exist, set envOutputPath to null
|
|
127
|
+
validateEnvOutputPathFolderOrNull('keycloak');
|
|
128
|
+
validateEnvOutputPathFolderOrNull('miso-controller');
|
|
135
129
|
// Deploy-only: do not copy .env to repo paths; patch variables so envOutputPath is null
|
|
136
130
|
patchEnvOutputPathForDeployOnly('keycloak');
|
|
137
131
|
patchEnvOutputPathForDeployOnly('miso-controller');
|
|
138
|
-
patchEnvOutputPathForDeployOnly('dataplane');
|
|
139
132
|
const developerId = await config.getDeveloperId();
|
|
140
133
|
const devIdNum = typeof developerId === 'string' ? parseInt(developerId, 10) : developerId;
|
|
141
134
|
await setMisoSecretsAndResolve(devIdNum);
|
|
142
135
|
await runMisoApps(options);
|
|
143
|
-
logger.log(chalk.green('\n✓ up-miso complete. Keycloak
|
|
144
|
-
chalk.gray('\n Run onboarding and register Keycloak from the miso-controller repo if needed.'));
|
|
136
|
+
logger.log(chalk.green('\n✓ up-miso complete. Keycloak and miso-controller are running.') +
|
|
137
|
+
chalk.gray('\n Run onboarding and register Keycloak from the miso-controller repo if needed. Use \'aifabrix up-dataplane\' for dataplane.'));
|
|
145
138
|
}
|
|
146
139
|
|
|
147
140
|
module.exports = { handleUpMiso, parseImageOptions };
|
package/lib/core/templates.js
CHANGED
|
@@ -35,7 +35,6 @@ function generateExternalSystemVariables(appName, displayName, config) {
|
|
|
35
35
|
type: 'external'
|
|
36
36
|
},
|
|
37
37
|
deployment: {
|
|
38
|
-
controllerUrl: '',
|
|
39
38
|
environment: 'dev'
|
|
40
39
|
},
|
|
41
40
|
externalIntegration: {
|
|
@@ -83,7 +82,8 @@ function buildWebappVariables(appName, displayName, config, imageName, imageTag)
|
|
|
83
82
|
key: appName,
|
|
84
83
|
displayName: displayName,
|
|
85
84
|
description: `${appName.replace(/-/g, ' ')} application`,
|
|
86
|
-
type: appType
|
|
85
|
+
type: appType,
|
|
86
|
+
version: config.version || '1.0.0'
|
|
87
87
|
},
|
|
88
88
|
image: {
|
|
89
89
|
name: imageName,
|
|
@@ -13,10 +13,87 @@ const chalk = require('chalk');
|
|
|
13
13
|
const { getDeploymentAuth } = require('../utils/token-manager');
|
|
14
14
|
const logger = require('../utils/logger');
|
|
15
15
|
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
16
|
+
const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
|
|
17
|
+
const { getExternalSystem } = require('../api/external-systems.api');
|
|
16
18
|
const { generateControllerManifest } = require('../generator/external-controller-manifest');
|
|
17
19
|
const { validateExternalSystemComplete } = require('../validation/validate');
|
|
18
20
|
const { displayValidationResults } = require('../validation/validate-display');
|
|
19
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Displays API and MCP documentation URLs from dataplane when available
|
|
24
|
+
* @async
|
|
25
|
+
* @function displayDeploymentDocs
|
|
26
|
+
* @param {string} controllerUrl - Controller base URL
|
|
27
|
+
* @param {string} environment - Environment key
|
|
28
|
+
* @param {Object} authConfig - Authentication configuration
|
|
29
|
+
* @param {string} systemKey - External system key
|
|
30
|
+
*/
|
|
31
|
+
async function displayDeploymentDocs(controllerUrl, environment, authConfig, systemKey) {
|
|
32
|
+
try {
|
|
33
|
+
const dataplaneUrl = await resolveDataplaneUrl(controllerUrl, environment, authConfig);
|
|
34
|
+
const res = await getExternalSystem(dataplaneUrl, systemKey, authConfig);
|
|
35
|
+
const sys = res?.data || res;
|
|
36
|
+
if (!sys) return;
|
|
37
|
+
|
|
38
|
+
const apiDocumentUrl = sys.apiDocumentUrl;
|
|
39
|
+
const mcpServerUrl = sys.mcpServerUrl;
|
|
40
|
+
const openApiDocsPageUrl = sys.openApiDocsPageUrl;
|
|
41
|
+
|
|
42
|
+
const urls = [];
|
|
43
|
+
if (apiDocumentUrl && typeof apiDocumentUrl === 'string') {
|
|
44
|
+
urls.push({ label: 'API Docs', url: apiDocumentUrl });
|
|
45
|
+
}
|
|
46
|
+
if (mcpServerUrl && typeof mcpServerUrl === 'string') {
|
|
47
|
+
urls.push({ label: 'MCP Server', url: mcpServerUrl });
|
|
48
|
+
}
|
|
49
|
+
if (openApiDocsPageUrl && typeof openApiDocsPageUrl === 'string') {
|
|
50
|
+
urls.push({ label: 'OpenAPI Docs Page', url: openApiDocsPageUrl });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (urls.length > 0) {
|
|
54
|
+
logger.log(chalk.blue('\nDocumentation:'));
|
|
55
|
+
urls.forEach(({ label, url }) => {
|
|
56
|
+
logger.log(chalk.blue(` ${label}: ${url}`));
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
} catch (_err) {
|
|
60
|
+
// Silently ignore: dataplane may be unreachable or docs not configured
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Deploys via controller and displays success summary with docs
|
|
66
|
+
* @async
|
|
67
|
+
* @function executeDeployAndDisplay
|
|
68
|
+
* @param {Object} manifest - Controller manifest
|
|
69
|
+
* @param {string} controllerUrl - Controller base URL
|
|
70
|
+
* @param {string} environment - Environment key
|
|
71
|
+
* @param {Object} authConfig - Authentication configuration
|
|
72
|
+
* @param {Object} options - Deployment options
|
|
73
|
+
* @returns {Promise<Object>} Deployment result
|
|
74
|
+
*/
|
|
75
|
+
async function executeDeployAndDisplay(manifest, controllerUrl, environment, authConfig, options) {
|
|
76
|
+
const deployer = require('../deployment/deployer');
|
|
77
|
+
const pollOpts = {
|
|
78
|
+
poll: options.poll,
|
|
79
|
+
pollInterval: options.pollInterval !== undefined ? options.pollInterval : 500,
|
|
80
|
+
pollMaxAttempts: options.pollMaxAttempts,
|
|
81
|
+
...options
|
|
82
|
+
};
|
|
83
|
+
const result = await deployer.deployToController(
|
|
84
|
+
manifest,
|
|
85
|
+
controllerUrl,
|
|
86
|
+
environment,
|
|
87
|
+
authConfig,
|
|
88
|
+
pollOpts
|
|
89
|
+
);
|
|
90
|
+
logger.log(chalk.green('\n✅ External system deployed successfully!'));
|
|
91
|
+
logger.log(chalk.blue(`System: ${manifest.key}`));
|
|
92
|
+
logger.log(chalk.blue(`Datasources: ${manifest.dataSources.length}`));
|
|
93
|
+
await displayDeploymentDocs(controllerUrl, environment, authConfig, manifest.key);
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
|
|
20
97
|
/**
|
|
21
98
|
* Prepares deployment configuration (auth, controller URL, environment)
|
|
22
99
|
* @async
|
|
@@ -75,26 +152,13 @@ async function deployExternalSystem(appName, options = {}) {
|
|
|
75
152
|
const { environment, controllerUrl, authConfig } = await prepareDeploymentConfig(appName, options);
|
|
76
153
|
|
|
77
154
|
// Step 3: Deploy via controller pipeline (same as regular apps)
|
|
78
|
-
|
|
79
|
-
const deployer = require('../deployment/deployer');
|
|
80
|
-
const result = await deployer.deployToController(
|
|
155
|
+
const result = await executeDeployAndDisplay(
|
|
81
156
|
manifest,
|
|
82
157
|
controllerUrl,
|
|
83
158
|
environment,
|
|
84
159
|
authConfig,
|
|
85
|
-
|
|
86
|
-
poll: options.poll,
|
|
87
|
-
pollInterval: options.pollInterval !== undefined ? options.pollInterval : 500,
|
|
88
|
-
pollMaxAttempts: options.pollMaxAttempts,
|
|
89
|
-
...options
|
|
90
|
-
}
|
|
160
|
+
options
|
|
91
161
|
);
|
|
92
|
-
|
|
93
|
-
// Display success summary
|
|
94
|
-
logger.log(chalk.green('\n✅ External system deployed successfully!'));
|
|
95
|
-
logger.log(chalk.blue(`System: ${manifest.key}`));
|
|
96
|
-
logger.log(chalk.blue(`Datasources: ${manifest.dataSources.length}`));
|
|
97
|
-
|
|
98
162
|
return result;
|
|
99
163
|
} catch (error) {
|
|
100
164
|
let message = `Failed to deploy external system: ${error.message}`;
|
|
@@ -126,11 +126,17 @@ function buildAuthentication(rbac) {
|
|
|
126
126
|
* @returns {Object} App metadata
|
|
127
127
|
*/
|
|
128
128
|
function buildAppMetadata(appName, variables) {
|
|
129
|
+
const rawVersion = variables.app?.version;
|
|
130
|
+
const version =
|
|
131
|
+
rawVersion !== undefined && rawVersion !== null && String(rawVersion).trim()
|
|
132
|
+
? String(rawVersion).trim()
|
|
133
|
+
: '1.0.0';
|
|
129
134
|
return {
|
|
130
135
|
key: variables.app?.key || appName,
|
|
131
136
|
displayName: variables.app?.displayName || appName,
|
|
132
137
|
description: variables.app?.description || '',
|
|
133
|
-
type: variables.app?.type || 'webapp'
|
|
138
|
+
type: variables.app?.type || 'webapp',
|
|
139
|
+
version
|
|
134
140
|
};
|
|
135
141
|
}
|
|
136
142
|
|
|
@@ -256,25 +262,6 @@ function validateBuildFields(build) {
|
|
|
256
262
|
return Object.keys(buildConfig).length > 0 ? buildConfig : null;
|
|
257
263
|
}
|
|
258
264
|
|
|
259
|
-
/**
|
|
260
|
-
* Validates and transforms deployment fields
|
|
261
|
-
* @function validateDeploymentFields
|
|
262
|
-
* @param {Object} deployment - Deployment configuration
|
|
263
|
-
* @returns {Object|null} Validated deployment config or null
|
|
264
|
-
*/
|
|
265
|
-
function validateDeploymentFields(deployment) {
|
|
266
|
-
if (!deployment) {
|
|
267
|
-
return null;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
const deploymentConfig = {};
|
|
271
|
-
if (deployment.controllerUrl && deployment.controllerUrl.trim() && deployment.controllerUrl.startsWith('https://')) {
|
|
272
|
-
deploymentConfig.controllerUrl = deployment.controllerUrl;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
return Object.keys(deploymentConfig).length > 0 ? deploymentConfig : null;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
265
|
/**
|
|
279
266
|
* Adds optional fields to deployment manifest
|
|
280
267
|
* @function buildOptionalFields
|
|
@@ -333,11 +320,6 @@ function addValidatedConfigSections(deployment, variables) {
|
|
|
333
320
|
if (build) {
|
|
334
321
|
deployment.build = build;
|
|
335
322
|
}
|
|
336
|
-
|
|
337
|
-
const deploymentConfig = validateDeploymentFields(variables.deployment);
|
|
338
|
-
if (deploymentConfig) {
|
|
339
|
-
deployment.deployment = deploymentConfig;
|
|
340
|
-
}
|
|
341
323
|
}
|
|
342
324
|
|
|
343
325
|
/**
|
|
@@ -375,12 +357,11 @@ function buildOptionalFields(deployment, variables, rbac) {
|
|
|
375
357
|
* Builds deployment manifest structure
|
|
376
358
|
* @param {string} appName - Application name
|
|
377
359
|
* @param {Object} variables - Variables configuration
|
|
378
|
-
* @param {string} deploymentKey - Deployment key
|
|
379
360
|
* @param {Array} configuration - Environment configuration
|
|
380
361
|
* @param {Object|null} rbac - RBAC configuration
|
|
381
362
|
* @returns {Object} Deployment manifest
|
|
382
363
|
*/
|
|
383
|
-
function buildManifestStructure(appName, variables,
|
|
364
|
+
function buildManifestStructure(appName, variables, configuration, rbac) {
|
|
384
365
|
const registryMode = variables.image?.registryMode || 'external';
|
|
385
366
|
const filteredConfiguration = filterConfigurationByRegistryMode(configuration, registryMode);
|
|
386
367
|
const deployment = buildBaseDeployment(appName, variables, filteredConfiguration);
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
|
|
12
12
|
const path = require('path');
|
|
13
13
|
const { detectAppType } = require('../utils/paths');
|
|
14
|
-
const { generateDeploymentKeyFromJson } = require('../core/key-generator');
|
|
15
14
|
const { loadSystemFile, loadDatasourceFiles } = require('./external');
|
|
16
15
|
const { loadVariables, loadRbac } = require('./helpers');
|
|
17
16
|
|
|
@@ -97,7 +96,7 @@ async function loadSystemWithRbac(appPath, schemaBasePath, systemFile) {
|
|
|
97
96
|
*
|
|
98
97
|
* @example
|
|
99
98
|
* const manifest = await generateControllerManifest('my-hubspot');
|
|
100
|
-
* // Returns: { key, displayName, description, type: "external", system: {...}, dataSources: [...]
|
|
99
|
+
* // Returns: { key, displayName, description, type: "external", system: {...}, dataSources: [...] }
|
|
101
100
|
*/
|
|
102
101
|
async function generateControllerManifest(appName, options = {}) {
|
|
103
102
|
if (!appName || typeof appName !== 'string') {
|
|
@@ -124,13 +123,15 @@ async function generateControllerManifest(appName, options = {}) {
|
|
|
124
123
|
const datasourceFiles = variables.externalIntegration.dataSources || [];
|
|
125
124
|
const datasourceJsons = await loadDatasourceFiles(appPath, schemaBasePath, datasourceFiles);
|
|
126
125
|
|
|
126
|
+
const appVersion = variables.app?.version || variables.externalIntegration?.version || '1.0.0';
|
|
127
|
+
|
|
127
128
|
// Build externalIntegration block (required by application schema for type: "external")
|
|
128
129
|
const externalIntegration = {
|
|
129
130
|
schemaBasePath: schemaBasePath,
|
|
130
131
|
systems: systemFiles,
|
|
131
132
|
dataSources: datasourceFiles,
|
|
132
133
|
autopublish: variables.externalIntegration.autopublish !== false, // default true
|
|
133
|
-
version:
|
|
134
|
+
version: appVersion
|
|
134
135
|
};
|
|
135
136
|
|
|
136
137
|
const manifest = {
|
|
@@ -138,6 +139,7 @@ async function generateControllerManifest(appName, options = {}) {
|
|
|
138
139
|
displayName: metadata.displayName,
|
|
139
140
|
description: metadata.description,
|
|
140
141
|
type: 'external',
|
|
142
|
+
version: appVersion,
|
|
141
143
|
externalIntegration: externalIntegration,
|
|
142
144
|
// Inline system and dataSources for atomic deployment (optional but recommended)
|
|
143
145
|
system: systemJson,
|
|
@@ -148,7 +150,6 @@ async function generateControllerManifest(appName, options = {}) {
|
|
|
148
150
|
requiresStorage: false
|
|
149
151
|
};
|
|
150
152
|
|
|
151
|
-
manifest.deploymentKey = generateDeploymentKeyFromJson(manifest);
|
|
152
153
|
return manifest;
|
|
153
154
|
}
|
|
154
155
|
|
package/lib/generator/index.js
CHANGED
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
|
|
12
12
|
const fs = require('fs');
|
|
13
13
|
const path = require('path');
|
|
14
|
-
const _keyGenerator = require('../core/key-generator');
|
|
15
14
|
const _validator = require('../validation/validator');
|
|
16
15
|
const builders = require('./builders');
|
|
17
16
|
const { detectAppType, getDeployJsonPath } = require('../utils/paths');
|
|
@@ -19,6 +18,7 @@ const splitFunctions = require('./split');
|
|
|
19
18
|
const { loadVariables, loadEnvTemplate, loadRbac, parseEnvironmentVariables } = require('./helpers');
|
|
20
19
|
const { generateExternalSystemApplicationSchema, splitExternalApplicationSchema } = require('./external');
|
|
21
20
|
const { generateControllerManifest } = require('./external-controller-manifest');
|
|
21
|
+
const { resolveVersionForApp } = require('../utils/image-version');
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Generates deployment JSON from application configuration files
|
|
@@ -65,21 +65,15 @@ function loadDeploymentConfigFiles(appPath, appType, appName) {
|
|
|
65
65
|
* @param {Object} variables - Variables configuration
|
|
66
66
|
* @param {Object} envTemplate - Environment template
|
|
67
67
|
* @param {Object} rbac - RBAC configuration
|
|
68
|
-
* @returns {Object} Deployment manifest
|
|
68
|
+
* @returns {Object} Deployment manifest
|
|
69
69
|
* @throws {Error} If validation fails
|
|
70
70
|
*/
|
|
71
71
|
function buildAndValidateDeployment(appName, variables, envTemplate, rbac) {
|
|
72
72
|
// Parse environment variables from template and merge portalInput from variables.yaml
|
|
73
73
|
const configuration = parseEnvironmentVariables(envTemplate, variables);
|
|
74
74
|
|
|
75
|
-
// Build deployment manifest
|
|
76
|
-
const deployment = builders.buildManifestStructure(appName, variables,
|
|
77
|
-
|
|
78
|
-
// Generate deploymentKey from the manifest object (excluding deploymentKey field)
|
|
79
|
-
const deploymentKey = _keyGenerator.generateDeploymentKeyFromJson(deployment);
|
|
80
|
-
|
|
81
|
-
// Add deploymentKey to manifest
|
|
82
|
-
deployment.deploymentKey = deploymentKey;
|
|
75
|
+
// Build deployment manifest (Controller computes deploymentKey from schema)
|
|
76
|
+
const deployment = builders.buildManifestStructure(appName, variables, configuration, rbac);
|
|
83
77
|
|
|
84
78
|
// Validate deployment JSON against schema
|
|
85
79
|
const validation = _validator.validateDeploymentJson(deployment);
|
|
@@ -114,10 +108,13 @@ async function buildDeploymentManifestInMemory(appName, options = {}) {
|
|
|
114
108
|
}
|
|
115
109
|
|
|
116
110
|
const { variables, envTemplate, rbac } = loadDeploymentConfigFiles(appPath, appType, appName);
|
|
111
|
+
const resolved = await resolveVersionForApp(appName, variables, { updateBuilder: false });
|
|
112
|
+
const variablesWithVersion = {
|
|
113
|
+
...variables,
|
|
114
|
+
app: { ...variables.app, version: resolved.version }
|
|
115
|
+
};
|
|
117
116
|
const configuration = parseEnvironmentVariables(envTemplate, variables);
|
|
118
|
-
const deployment = builders.buildManifestStructure(appName,
|
|
119
|
-
const deploymentKey = _keyGenerator.generateDeploymentKeyFromJson(deployment);
|
|
120
|
-
deployment.deploymentKey = deploymentKey;
|
|
117
|
+
const deployment = builders.buildManifestStructure(appName, variablesWithVersion, configuration, rbac);
|
|
121
118
|
|
|
122
119
|
return { deployment, appPath };
|
|
123
120
|
}
|
|
@@ -145,7 +142,12 @@ async function generateDeployJson(appName, options = {}) {
|
|
|
145
142
|
|
|
146
143
|
// Regular app: generate deployment manifest
|
|
147
144
|
const { variables, envTemplate, rbac, jsonPath } = loadDeploymentConfigFiles(appPath, appType, appName);
|
|
148
|
-
const
|
|
145
|
+
const resolved = await resolveVersionForApp(appName, variables, { updateBuilder: false });
|
|
146
|
+
const variablesWithVersion = {
|
|
147
|
+
...variables,
|
|
148
|
+
app: { ...variables.app, version: resolved.version }
|
|
149
|
+
};
|
|
150
|
+
const deployment = buildAndValidateDeployment(appName, variablesWithVersion, envTemplate, rbac);
|
|
149
151
|
|
|
150
152
|
// Write deployment JSON
|
|
151
153
|
const jsonContent = JSON.stringify(deployment, null, 2);
|
package/lib/generator/split.js
CHANGED
|
@@ -98,6 +98,7 @@ function extractAppSection(deployment) {
|
|
|
98
98
|
if (deployment.displayName) app.displayName = deployment.displayName;
|
|
99
99
|
if (deployment.description) app.description = deployment.description;
|
|
100
100
|
if (deployment.type) app.type = deployment.type;
|
|
101
|
+
if (deployment.version) app.version = deployment.version;
|
|
101
102
|
return app;
|
|
102
103
|
}
|
|
103
104
|
|
package/lib/generator/wizard.js
CHANGED
|
@@ -234,8 +234,11 @@ async function generateOrUpdateVariablesYaml(params) {
|
|
|
234
234
|
key: appName,
|
|
235
235
|
displayName: systemConfig.displayName || appName.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase()),
|
|
236
236
|
description: systemConfig.description || `External system integration for ${appName}`,
|
|
237
|
-
type: 'external'
|
|
237
|
+
type: 'external',
|
|
238
|
+
version: '1.0.0'
|
|
238
239
|
};
|
|
240
|
+
} else {
|
|
241
|
+
variables.app.version = variables.app.version || '1.0.0';
|
|
239
242
|
}
|
|
240
243
|
|
|
241
244
|
// Set deployment config if not present
|