@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
package/lib/app/run-helpers.js
CHANGED
|
@@ -27,9 +27,13 @@ const composeGenerator = require('../utils/compose-generator');
|
|
|
27
27
|
const dockerUtils = require('../utils/docker');
|
|
28
28
|
const containerHelpers = require('../utils/app-run-containers');
|
|
29
29
|
const pathsUtil = require('../utils/paths');
|
|
30
|
+
const { resolveVersionForApp } = require('../utils/image-version');
|
|
30
31
|
|
|
31
32
|
const execAsync = promisify(exec);
|
|
32
33
|
|
|
34
|
+
/** Template apps (keycloak, miso-controller, dataplane) - never update variables.yaml when running */
|
|
35
|
+
const TEMPLATE_APP_KEYS = ['keycloak', 'miso-controller', 'dataplane'];
|
|
36
|
+
|
|
33
37
|
// Re-export container helper functions
|
|
34
38
|
const checkImageExists = containerHelpers.checkImageExists;
|
|
35
39
|
const checkContainerRunning = containerHelpers.checkContainerRunning;
|
|
@@ -138,6 +142,25 @@ async function validateAppConfiguration(appName) {
|
|
|
138
142
|
return appConfig;
|
|
139
143
|
}
|
|
140
144
|
|
|
145
|
+
/**
|
|
146
|
+
* Resolves version from image and updates builder/variables.yaml when running.
|
|
147
|
+
* Template apps (keycloak, miso-controller, dataplane) are never updated - variables.yaml stays pristine.
|
|
148
|
+
* @async
|
|
149
|
+
* @param {string} appName - Application name
|
|
150
|
+
* @param {Object} appConfig - Application configuration
|
|
151
|
+
* @param {boolean} debug - Enable debug logging
|
|
152
|
+
*/
|
|
153
|
+
async function resolveAndUpdateVersion(appName, appConfig, debug) {
|
|
154
|
+
const isTemplateApp = TEMPLATE_APP_KEYS.includes(appName);
|
|
155
|
+
const resolved = await resolveVersionForApp(appName, appConfig, {
|
|
156
|
+
updateBuilder: !isTemplateApp,
|
|
157
|
+
builderPath: pathsUtil.getBuilderPath(appName)
|
|
158
|
+
});
|
|
159
|
+
if (resolved.fromImage && resolved.updated && debug) {
|
|
160
|
+
logger.log(chalk.gray(`[DEBUG] Updated app.version to ${resolved.version} from image`));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
141
164
|
/**
|
|
142
165
|
* Checks prerequisites: Docker image and (optionally) infrastructure
|
|
143
166
|
* @async
|
|
@@ -163,10 +186,20 @@ async function checkPrerequisites(appName, appConfig, debug = false, skipInfraCh
|
|
|
163
186
|
}
|
|
164
187
|
logger.log(chalk.green(`✓ Image ${fullImageName} found`));
|
|
165
188
|
|
|
166
|
-
|
|
167
|
-
|
|
189
|
+
await resolveAndUpdateVersion(appName, appConfig, debug);
|
|
190
|
+
|
|
191
|
+
if (!skipInfraCheck) {
|
|
192
|
+
await checkInfraHealthOrThrow(debug);
|
|
168
193
|
}
|
|
194
|
+
}
|
|
169
195
|
|
|
196
|
+
/**
|
|
197
|
+
* Checks infrastructure health and throws if unhealthy
|
|
198
|
+
* @async
|
|
199
|
+
* @param {boolean} debug - Enable debug logging
|
|
200
|
+
* @throws {Error} If infrastructure is not healthy
|
|
201
|
+
*/
|
|
202
|
+
async function checkInfraHealthOrThrow(debug) {
|
|
170
203
|
logger.log(chalk.blue('Checking infrastructure health...'));
|
|
171
204
|
const infraHealth = await infra.checkInfraHealth();
|
|
172
205
|
if (debug) {
|
package/lib/app/show-display.js
CHANGED
|
@@ -31,14 +31,21 @@ function logApplicationRequired(a) {
|
|
|
31
31
|
logger.log(` Port: ${port}`);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
const APPLICATION_OPTIONAL_FIELDS = [
|
|
35
|
+
{ key: 'deploymentKey', label: 'Deployment' },
|
|
36
|
+
{ key: 'image', label: 'Image' },
|
|
37
|
+
{ key: 'registryMode', label: 'Registry' },
|
|
38
|
+
{ key: 'healthCheck', label: 'Health' },
|
|
39
|
+
{ key: 'build', label: 'Build' },
|
|
40
|
+
{ key: 'status', label: 'Status' },
|
|
41
|
+
{ key: 'url', label: 'URL' },
|
|
42
|
+
{ key: 'internalUrl', label: 'Internal URL' }
|
|
43
|
+
];
|
|
44
|
+
|
|
34
45
|
function logApplicationOptional(a) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (a.healthCheck !== undefined) logger.log(` Health: ${a.healthCheck ?? '—'}`);
|
|
39
|
-
if (a.build !== undefined) logger.log(` Build: ${a.build ?? '—'}`);
|
|
40
|
-
if (a.status !== undefined) logger.log(` Status: ${a.status ?? '—'}`);
|
|
41
|
-
if (a.url !== undefined) logger.log(` URL: ${a.url ?? '—'}`);
|
|
46
|
+
APPLICATION_OPTIONAL_FIELDS.forEach(({ key, label }) => {
|
|
47
|
+
if (a[key] !== undefined) logger.log(` ${(label + ':').padEnd(16)} ${a[key] ?? '—'}`);
|
|
48
|
+
});
|
|
42
49
|
}
|
|
43
50
|
|
|
44
51
|
function logApplicationFields(a) {
|
|
@@ -75,10 +82,15 @@ function logRolesSection(roles) {
|
|
|
75
82
|
});
|
|
76
83
|
}
|
|
77
84
|
|
|
78
|
-
function logPermissionsSection(permissions) {
|
|
79
|
-
|
|
85
|
+
function logPermissionsSection(permissions, opts = {}) {
|
|
86
|
+
const showWhenEmpty = opts.showWhenEmpty || false;
|
|
87
|
+
if (permissions.length === 0 && !showWhenEmpty) return;
|
|
80
88
|
logger.log('');
|
|
81
89
|
logger.log('🛡️ Permissions');
|
|
90
|
+
if (permissions.length === 0) {
|
|
91
|
+
logger.log(' (none)');
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
82
94
|
permissions.forEach((p) => {
|
|
83
95
|
const name = p.name ?? '—';
|
|
84
96
|
const roleList = (p.roles || []).join(', ');
|
|
@@ -186,8 +198,10 @@ function logExternalSystemSection(ext) {
|
|
|
186
198
|
/**
|
|
187
199
|
* Format and print human-readable show output (offline or online).
|
|
188
200
|
* @param {Object} summary - Unified summary (buildOfflineSummaryFromDeployJson or buildOnlineSummary)
|
|
201
|
+
* @param {Object} [options] - Display options
|
|
202
|
+
* @param {boolean} [options.permissionsOnly] - When true, output only source and Permissions section
|
|
189
203
|
*/
|
|
190
|
-
function display(summary) {
|
|
204
|
+
function display(summary, options = {}) {
|
|
191
205
|
const a = summary.application;
|
|
192
206
|
const roles = summary.roles ?? a.roles ?? [];
|
|
193
207
|
const permissions = summary.permissions ?? a.permissions ?? [];
|
|
@@ -197,9 +211,14 @@ function display(summary) {
|
|
|
197
211
|
const dbNames = Array.isArray(databases) ? databases.map((d) => (d && d.name) || d).filter(Boolean) : [];
|
|
198
212
|
|
|
199
213
|
logSourceAndHeader(summary);
|
|
214
|
+
if (options.permissionsOnly) {
|
|
215
|
+
logPermissionsSection(permissions, { showWhenEmpty: true });
|
|
216
|
+
logger.log('');
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
200
219
|
logApplicationSection(a, summary.isExternal);
|
|
201
220
|
logRolesSection(roles);
|
|
202
|
-
|
|
221
|
+
/* Permissions section shown only when --permissions is set (permissionsOnly mode) */
|
|
203
222
|
logAuthSection(authentication);
|
|
204
223
|
logConfigurationsSection(portalInputConfigurations);
|
|
205
224
|
logDatabasesSection(dbNames);
|
package/lib/app/show.js
CHANGED
|
@@ -418,6 +418,7 @@ function buildApplicationFromAppCfg(app, cfg, portalInputConfigurations) {
|
|
|
418
418
|
build: formatBuildForDisplay(cfg.build ?? app.build),
|
|
419
419
|
status: pickAppCfg('status', app, cfg, '—'),
|
|
420
420
|
url: pickAppCfg('url', app, cfg, '—'),
|
|
421
|
+
internalUrl: pickAppCfg('internalUrl', app, cfg, '—'),
|
|
421
422
|
roles: cfg.roles ?? app.roles,
|
|
422
423
|
permissions: cfg.permissions ?? app.permissions,
|
|
423
424
|
authentication: cfg.authentication ?? app.authentication,
|
|
@@ -485,9 +486,10 @@ function buildOnlineSummary(apiApp, controllerUrl, externalSystem) {
|
|
|
485
486
|
* Run show in offline mode: generate manifest (same as aifabrix json) and use it; else fall back to variables.yaml.
|
|
486
487
|
* @param {string} appKey - Application key
|
|
487
488
|
* @param {boolean} json - Output as JSON
|
|
489
|
+
* @param {boolean} [permissionsOnly] - When true, output only permissions
|
|
488
490
|
* @throws {Error} If variables.yaml not found or invalid YAML
|
|
489
491
|
*/
|
|
490
|
-
async function runOffline(appKey, json) {
|
|
492
|
+
async function runOffline(appKey, json, permissionsOnly) {
|
|
491
493
|
let summary;
|
|
492
494
|
|
|
493
495
|
try {
|
|
@@ -503,6 +505,16 @@ async function runOffline(appKey, json) {
|
|
|
503
505
|
}
|
|
504
506
|
|
|
505
507
|
if (json) {
|
|
508
|
+
if (permissionsOnly) {
|
|
509
|
+
const out = {
|
|
510
|
+
source: summary.source,
|
|
511
|
+
path: summary.path,
|
|
512
|
+
appKey: summary.appKey,
|
|
513
|
+
permissions: summary.permissions || []
|
|
514
|
+
};
|
|
515
|
+
logger.log(JSON.stringify(out, null, 2));
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
506
518
|
const out = {
|
|
507
519
|
source: summary.source,
|
|
508
520
|
path: summary.path,
|
|
@@ -519,7 +531,7 @@ async function runOffline(appKey, json) {
|
|
|
519
531
|
logger.log(JSON.stringify(out, null, 2));
|
|
520
532
|
return;
|
|
521
533
|
}
|
|
522
|
-
displayShow(summary);
|
|
534
|
+
displayShow(summary, { permissionsOnly: !!permissionsOnly });
|
|
523
535
|
}
|
|
524
536
|
|
|
525
537
|
async function resolveOnlineAuth(controllerUrl) {
|
|
@@ -550,7 +562,17 @@ async function fetchExternalSystemForOnline(controllerUrl, appKey, authConfig) {
|
|
|
550
562
|
}
|
|
551
563
|
}
|
|
552
564
|
|
|
553
|
-
function outputOnlineJson(summary) {
|
|
565
|
+
function outputOnlineJson(summary, permissionsOnly) {
|
|
566
|
+
if (permissionsOnly) {
|
|
567
|
+
const out = {
|
|
568
|
+
source: summary.source,
|
|
569
|
+
controllerUrl: summary.controllerUrl,
|
|
570
|
+
appKey: summary.appKey,
|
|
571
|
+
permissions: summary.permissions || []
|
|
572
|
+
};
|
|
573
|
+
logger.log(JSON.stringify(out, null, 2));
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
554
576
|
const out = {
|
|
555
577
|
source: summary.source,
|
|
556
578
|
controllerUrl: summary.controllerUrl,
|
|
@@ -562,6 +584,7 @@ function outputOnlineJson(summary) {
|
|
|
562
584
|
type: summary.application.type,
|
|
563
585
|
status: summary.application.status,
|
|
564
586
|
url: summary.application.url,
|
|
587
|
+
internalUrl: summary.application.internalUrl,
|
|
565
588
|
port: summary.application.port,
|
|
566
589
|
configuration: summary.application.configuration,
|
|
567
590
|
roles: summary.application.roles,
|
|
@@ -583,9 +606,10 @@ function outputOnlineJson(summary) {
|
|
|
583
606
|
* Run show in online mode: getApplication, optionally dataplane for external, display or JSON.
|
|
584
607
|
* @param {string} appKey - Application key
|
|
585
608
|
* @param {boolean} json - Output as JSON
|
|
609
|
+
* @param {boolean} [permissionsOnly] - When true, output only permissions
|
|
586
610
|
* @throws {Error} On auth failure, 404, or API error
|
|
587
611
|
*/
|
|
588
|
-
async function runOnline(appKey, json) {
|
|
612
|
+
async function runOnline(appKey, json, permissionsOnly) {
|
|
589
613
|
const controllerUrl = await resolveControllerUrl();
|
|
590
614
|
if (!controllerUrl) {
|
|
591
615
|
throw new Error('Controller URL is required for --online. Run aifabrix login to set the controller URL in config.yaml.');
|
|
@@ -599,10 +623,10 @@ async function runOnline(appKey, json) {
|
|
|
599
623
|
: null;
|
|
600
624
|
const summary = buildOnlineSummary(apiApp, authResult.actualControllerUrl, externalSystem);
|
|
601
625
|
if (json) {
|
|
602
|
-
outputOnlineJson(summary);
|
|
626
|
+
outputOnlineJson(summary, permissionsOnly);
|
|
603
627
|
return;
|
|
604
628
|
}
|
|
605
|
-
displayShow(summary);
|
|
629
|
+
displayShow(summary, { permissionsOnly: !!permissionsOnly });
|
|
606
630
|
}
|
|
607
631
|
|
|
608
632
|
/**
|
|
@@ -612,6 +636,7 @@ async function runOnline(appKey, json) {
|
|
|
612
636
|
* @param {Object} options - Options
|
|
613
637
|
* @param {boolean} [options.online] - Fetch from controller
|
|
614
638
|
* @param {boolean} [options.json] - Output as JSON
|
|
639
|
+
* @param {boolean} [options.permissions] - When true, output only permissions (app show --permissions)
|
|
615
640
|
* @throws {Error} If file missing/invalid (offline) or API/auth error (online)
|
|
616
641
|
*/
|
|
617
642
|
async function showApp(appKey, options = {}) {
|
|
@@ -621,11 +646,12 @@ async function showApp(appKey, options = {}) {
|
|
|
621
646
|
|
|
622
647
|
const online = Boolean(options.online);
|
|
623
648
|
const json = Boolean(options.json);
|
|
649
|
+
const permissions = Boolean(options.permissions);
|
|
624
650
|
|
|
625
651
|
if (online) {
|
|
626
|
-
await runOnline(appKey, json);
|
|
652
|
+
await runOnline(appKey, json, permissions);
|
|
627
653
|
} else {
|
|
628
|
-
await runOffline(appKey, json);
|
|
654
|
+
await runOffline(appKey, json, permissions);
|
|
629
655
|
}
|
|
630
656
|
}
|
|
631
657
|
|
package/lib/cli/index.js
CHANGED
|
@@ -20,6 +20,8 @@ const { setupSecretsCommands } = require('./setup-secrets');
|
|
|
20
20
|
const { setupExternalSystemCommands } = require('./setup-external-system');
|
|
21
21
|
const { setupAppCommands: setupAppManagementCommands } = require('../commands/app');
|
|
22
22
|
const { setupDatasourceCommands } = require('../commands/datasource');
|
|
23
|
+
const { setupCredentialDeploymentCommands } = require('./setup-credential-deployment');
|
|
24
|
+
const { setupServiceUserCommands } = require('./setup-service-user');
|
|
23
25
|
|
|
24
26
|
/**
|
|
25
27
|
* Sets up all CLI commands on the Commander program instance
|
|
@@ -33,6 +35,8 @@ function setupCommands(program) {
|
|
|
33
35
|
setupAppManagementCommands(program);
|
|
34
36
|
setupDatasourceCommands(program);
|
|
35
37
|
setupUtilityCommands(program);
|
|
38
|
+
setupCredentialDeploymentCommands(program);
|
|
39
|
+
setupServiceUserCommands(program);
|
|
36
40
|
setupExternalSystemCommands(program);
|
|
37
41
|
setupDevCommands(program);
|
|
38
42
|
setupSecretsCommands(program);
|
package/lib/cli/setup-app.js
CHANGED
|
@@ -173,6 +173,7 @@ See integration/hubspot/wizard-hubspot-e2e.yaml for an example.`)
|
|
|
173
173
|
.description('Run application locally')
|
|
174
174
|
.option('-p, --port <port>', 'Override local port')
|
|
175
175
|
.option('-d, --debug', 'Enable debug output with detailed container information')
|
|
176
|
+
.option('-t, --tag <tag>', 'Image tag to run (e.g. v1.0.0); overrides variables.yaml image.tag')
|
|
176
177
|
.action(async(appName, options) => {
|
|
177
178
|
try {
|
|
178
179
|
await app.runApp(appName, options);
|
|
@@ -182,6 +183,37 @@ See integration/hubspot/wizard-hubspot-e2e.yaml for an example.`)
|
|
|
182
183
|
}
|
|
183
184
|
});
|
|
184
185
|
|
|
186
|
+
program.command('logs <app>')
|
|
187
|
+
.description('Show application container logs (and optional env summary with secrets masked)')
|
|
188
|
+
.option('-f', 'Follow log stream')
|
|
189
|
+
.option('-t, --tail <lines>', 'Number of lines (default: 100); 0 = full list', '100')
|
|
190
|
+
.action(async(appName, options) => {
|
|
191
|
+
try {
|
|
192
|
+
const { runAppLogs } = require('../commands/app-logs');
|
|
193
|
+
const tailNum = parseInt(options.tail, 10);
|
|
194
|
+
await runAppLogs(appName, {
|
|
195
|
+
follow: options.f,
|
|
196
|
+
tail: Number.isNaN(tailNum) ? 100 : tailNum
|
|
197
|
+
});
|
|
198
|
+
} catch (error) {
|
|
199
|
+
handleCommandError(error, 'logs');
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
program.command('down-app <app>')
|
|
205
|
+
.description('Stop and remove application container; optionally remove volume and image')
|
|
206
|
+
.option('--volumes', 'Remove application Docker volume')
|
|
207
|
+
.action(async(appName, options) => {
|
|
208
|
+
try {
|
|
209
|
+
const { runDownAppWithImageRemoval } = require('../commands/app-down');
|
|
210
|
+
await runDownAppWithImageRemoval(appName, { volumes: options.volumes });
|
|
211
|
+
} catch (error) {
|
|
212
|
+
handleCommandError(error, 'down-app');
|
|
213
|
+
process.exit(1);
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
|
|
185
217
|
program.command('push <app>')
|
|
186
218
|
.description('Push image to Azure Container Registry')
|
|
187
219
|
.option('-r, --registry <registry>', 'ACR registry URL (overrides variables.yaml)')
|
|
@@ -197,6 +229,7 @@ See integration/hubspot/wizard-hubspot-e2e.yaml for an example.`)
|
|
|
197
229
|
|
|
198
230
|
program.command('deploy <app>')
|
|
199
231
|
.description('Deploy to Azure via Miso Controller')
|
|
232
|
+
.option('--deployment <target>', 'Deployment target: \'local\' (send manifest to controller, then run app locally) or \'cloud\' (deploy via Miso Controller only)', 'cloud')
|
|
200
233
|
.option('--type <type>', 'Application type: external to deploy from integration/<app> (no app register needed)')
|
|
201
234
|
.option('--client-id <id>', 'Client ID (overrides config)')
|
|
202
235
|
.option('--client-secret <secret>', 'Client Secret (overrides config)')
|
|
@@ -204,7 +237,14 @@ See integration/hubspot/wizard-hubspot-e2e.yaml for an example.`)
|
|
|
204
237
|
.option('--no-poll', 'Do not poll for status')
|
|
205
238
|
.action(async(appName, options) => {
|
|
206
239
|
try {
|
|
240
|
+
const target = (options.deployment || 'cloud').toLowerCase();
|
|
241
|
+
if (target !== 'local' && target !== 'cloud') {
|
|
242
|
+
throw new Error('Deployment target must be \'local\' or \'cloud\'');
|
|
243
|
+
}
|
|
207
244
|
await app.deployApp(appName, options);
|
|
245
|
+
if (target === 'local') {
|
|
246
|
+
await app.runApp(appName, options);
|
|
247
|
+
}
|
|
208
248
|
} catch (error) {
|
|
209
249
|
handleCommandError(error, 'deploy');
|
|
210
250
|
process.exit(1);
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI credential and deployment list command setup.
|
|
3
|
+
* Commands: credential list, deployment list.
|
|
4
|
+
*
|
|
5
|
+
* @fileoverview Credential and deployment list CLI definitions
|
|
6
|
+
* @author AI Fabrix Team
|
|
7
|
+
* @version 2.0.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const chalk = require('chalk');
|
|
11
|
+
const logger = require('../utils/logger');
|
|
12
|
+
const { handleCommandError } = require('../utils/cli-utils');
|
|
13
|
+
const { runCredentialList } = require('../commands/credential-list');
|
|
14
|
+
const { runDeploymentList } = require('../commands/deployment-list');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Sets up credential and deployment list commands
|
|
18
|
+
* @param {Command} program - Commander program instance
|
|
19
|
+
*/
|
|
20
|
+
function setupCredentialDeploymentCommands(program) {
|
|
21
|
+
const credential = program
|
|
22
|
+
.command('credential')
|
|
23
|
+
.description('Manage credentials');
|
|
24
|
+
|
|
25
|
+
credential
|
|
26
|
+
.command('list')
|
|
27
|
+
.description('List credentials from controller/dataplane (GET /api/v1/credential)')
|
|
28
|
+
.option('--controller <url>', 'Controller URL (default: from config)')
|
|
29
|
+
.option('--active-only', 'List only active credentials')
|
|
30
|
+
.option('--page-size <n>', 'Items per page', '50')
|
|
31
|
+
.action(async(options) => {
|
|
32
|
+
try {
|
|
33
|
+
const opts = {
|
|
34
|
+
controller: options.controller,
|
|
35
|
+
activeOnly: options.activeOnly,
|
|
36
|
+
pageSize: parseInt(options.pageSize, 10) || 50
|
|
37
|
+
};
|
|
38
|
+
await runCredentialList(opts);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
logger.error(chalk.red(`Error: ${error.message}`));
|
|
41
|
+
handleCommandError(error, 'credential list');
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const deployment = program
|
|
47
|
+
.command('deployment')
|
|
48
|
+
.description('List deployments');
|
|
49
|
+
|
|
50
|
+
deployment
|
|
51
|
+
.command('list')
|
|
52
|
+
.description('List last N deployments for current environment (default pageSize=50)')
|
|
53
|
+
.option('--controller <url>', 'Controller URL (default: from config)')
|
|
54
|
+
.option('--environment <env>', 'Environment key (default: from config)')
|
|
55
|
+
.option('--page-size <n>', 'Items per page', '50')
|
|
56
|
+
.action(async(options) => {
|
|
57
|
+
try {
|
|
58
|
+
const opts = {
|
|
59
|
+
controller: options.controller,
|
|
60
|
+
environment: options.environment,
|
|
61
|
+
pageSize: parseInt(options.pageSize, 10) || 50
|
|
62
|
+
};
|
|
63
|
+
await runDeploymentList(opts);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
logger.error(chalk.red(`Error: ${error.message}`));
|
|
66
|
+
handleCommandError(error, 'deployment list');
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = { setupCredentialDeploymentCommands };
|
package/lib/cli/setup-infra.js
CHANGED
|
@@ -83,10 +83,10 @@ function setupInfraCommands(program) {
|
|
|
83
83
|
});
|
|
84
84
|
|
|
85
85
|
program.command('up-miso')
|
|
86
|
-
.description('Install keycloak
|
|
86
|
+
.description('Install keycloak and miso-controller from images (no build). Infra must be up. For dataplane use up-dataplane. Uses auto-generated secrets for testing.')
|
|
87
87
|
.option('-r, --registry <url>', 'Override registry for all apps (e.g. myacr.azurecr.io)')
|
|
88
88
|
.option('--registry-mode <mode>', 'Override registry mode (acr|external)')
|
|
89
|
-
.option('-i, --image <key>=<value>', 'Override image (e.g. keycloak=myreg/k:v1, miso-controller=myreg/m:v1
|
|
89
|
+
.option('-i, --image <key>=<value>', 'Override image (e.g. keycloak=myreg/k:v1, miso-controller=myreg/m:v1); can be repeated', (v, prev) => (prev || []).concat([v]))
|
|
90
90
|
.action(async(options) => {
|
|
91
91
|
try {
|
|
92
92
|
await handleUpMiso(options);
|
|
@@ -97,7 +97,7 @@ function setupInfraCommands(program) {
|
|
|
97
97
|
});
|
|
98
98
|
|
|
99
99
|
program.command('up-dataplane')
|
|
100
|
-
.description('Register
|
|
100
|
+
.description('Register, deploy, then run dataplane app locally in dev (always local deployment; requires login, environment must be dev)')
|
|
101
101
|
.option('-r, --registry <url>', 'Override registry for dataplane image')
|
|
102
102
|
.option('--registry-mode <mode>', 'Override registry mode (acr|external)')
|
|
103
103
|
.option('-i, --image <ref>', 'Override dataplane image reference (e.g. myreg/dataplane:latest)')
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI service-user command setup.
|
|
3
|
+
* Command: service-user create (create service user, get one-time secret).
|
|
4
|
+
*
|
|
5
|
+
* @fileoverview Service user CLI definitions
|
|
6
|
+
* @author AI Fabrix Team
|
|
7
|
+
* @version 2.0.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const chalk = require('chalk');
|
|
11
|
+
const logger = require('../utils/logger');
|
|
12
|
+
const { handleCommandError } = require('../utils/cli-utils');
|
|
13
|
+
const { runServiceUserCreate } = require('../commands/service-user');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Sets up service-user commands
|
|
17
|
+
* @param {Command} program - Commander program instance
|
|
18
|
+
*/
|
|
19
|
+
function setupServiceUserCommands(program) {
|
|
20
|
+
const serviceUser = program
|
|
21
|
+
.command('service-user')
|
|
22
|
+
.description('Create service users for integrations (one-time secret on create)');
|
|
23
|
+
|
|
24
|
+
serviceUser
|
|
25
|
+
.command('create')
|
|
26
|
+
.description('Create a service user (username, email, redirectUris, groupIds); receive one-time clientSecret (save it now)')
|
|
27
|
+
.option('--controller <url>', 'Controller URL (default: from config)')
|
|
28
|
+
.option('-u, --username <username>', 'Service user username (required)')
|
|
29
|
+
.option('-e, --email <email>', 'Email address (required)')
|
|
30
|
+
.option('--redirect-uris <uris>', 'Comma-separated redirect URIs for OAuth2 (required, e.g. https://app.example.com/callback)')
|
|
31
|
+
.option('--group-names <names>', 'Comma-separated group names (required, e.g. AI-Fabrix-Developers)')
|
|
32
|
+
.option('-d, --description <description>', 'Optional description')
|
|
33
|
+
.action(async(options) => {
|
|
34
|
+
try {
|
|
35
|
+
const opts = {
|
|
36
|
+
controller: options.controller,
|
|
37
|
+
username: options.username,
|
|
38
|
+
email: options.email,
|
|
39
|
+
redirectUris: options.redirectUris,
|
|
40
|
+
groupNames: options.groupNames,
|
|
41
|
+
description: options.description
|
|
42
|
+
};
|
|
43
|
+
await runServiceUserCreate(opts);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
logger.error(chalk.red(`Error: ${error.message}`));
|
|
46
|
+
handleCommandError(error, 'service-user create');
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = { setupServiceUserCommands };
|
package/lib/cli/setup-utility.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CLI utility command setup (resolve, json, split-json,
|
|
2
|
+
* CLI utility command setup (resolve, json, split-json, show, validate, diff).
|
|
3
3
|
*
|
|
4
4
|
* @fileoverview Utility command definitions for AI Fabrix Builder CLI
|
|
5
5
|
* @author AI Fabrix Team
|
|
@@ -127,30 +127,6 @@ function setupUtilityCommands(program) {
|
|
|
127
127
|
}
|
|
128
128
|
});
|
|
129
129
|
|
|
130
|
-
program.command('genkey <app>')
|
|
131
|
-
.description('Generate deployment key')
|
|
132
|
-
.action(async(appName) => {
|
|
133
|
-
try {
|
|
134
|
-
const jsonPath = await generator.generateDeployJson(appName);
|
|
135
|
-
|
|
136
|
-
const jsonContent = fs.readFileSync(jsonPath, 'utf8');
|
|
137
|
-
const deployment = JSON.parse(jsonContent);
|
|
138
|
-
|
|
139
|
-
const key = deployment.deploymentKey;
|
|
140
|
-
|
|
141
|
-
if (!key) {
|
|
142
|
-
throw new Error('deploymentKey not found in generated JSON');
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
logger.log(`\nDeployment key for ${appName}:`);
|
|
146
|
-
logger.log(key);
|
|
147
|
-
logger.log(chalk.gray(`\nGenerated from: ${jsonPath}`));
|
|
148
|
-
} catch (error) {
|
|
149
|
-
handleCommandError(error, 'genkey');
|
|
150
|
-
process.exit(1);
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
|
|
154
130
|
program.command('show <appKey>')
|
|
155
131
|
.description('Show application info from local builder/ or integration/ (offline) or from controller (--online)')
|
|
156
132
|
.option('--online', 'Fetch application data from the controller')
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Down-app command – stop container, optionally volumes, then remove image if unused
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Down-app command implementation
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 2.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
const { exec } = require('child_process');
|
|
11
|
+
const { promisify } = require('util');
|
|
12
|
+
const logger = require('../utils/logger');
|
|
13
|
+
const config = require('../core/config');
|
|
14
|
+
const containerHelpers = require('../utils/app-run-containers');
|
|
15
|
+
const { downApp } = require('../app/down');
|
|
16
|
+
|
|
17
|
+
const execAsync = promisify(exec);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get image ID of a running container (sha or name:tag)
|
|
21
|
+
* @async
|
|
22
|
+
* @param {string} containerName - Docker container name
|
|
23
|
+
* @returns {Promise<string|null>} Image ID or null if container not found / not running
|
|
24
|
+
*/
|
|
25
|
+
async function getContainerImageId(containerName) {
|
|
26
|
+
try {
|
|
27
|
+
const { stdout } = await execAsync(
|
|
28
|
+
`docker inspect --format='{{.Image}}' ${containerName}`,
|
|
29
|
+
{ encoding: 'utf8' }
|
|
30
|
+
);
|
|
31
|
+
const id = (stdout && stdout.trim()) || null;
|
|
32
|
+
return id || null;
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Remove Docker image by ID; ignore "in use" or "no such image"
|
|
40
|
+
* @async
|
|
41
|
+
* @param {string} imageId - Image ID (sha or name:tag)
|
|
42
|
+
*/
|
|
43
|
+
async function removeImageIfUnused(imageId) {
|
|
44
|
+
if (!imageId) return;
|
|
45
|
+
try {
|
|
46
|
+
await execAsync(`docker rmi ${imageId}`);
|
|
47
|
+
logger.log(chalk.green(`✓ Image ${imageId} removed`));
|
|
48
|
+
} catch (err) {
|
|
49
|
+
const msg = (err && err.message) || '';
|
|
50
|
+
if (msg.includes('in use') || msg.includes('is being used')) {
|
|
51
|
+
logger.log(chalk.gray(`Image ${imageId} still in use by another container; not removed`));
|
|
52
|
+
} else if (msg.includes('No such image')) {
|
|
53
|
+
logger.log(chalk.gray(`Image ${imageId} not found (already removed)`));
|
|
54
|
+
} else {
|
|
55
|
+
logger.log(chalk.yellow(`Could not remove image: ${msg}`));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Run down-app: get image from container, stop/remove container (and optionally volume), then remove image if unused
|
|
62
|
+
* @async
|
|
63
|
+
* @param {string} appName - Application name
|
|
64
|
+
* @param {Object} options - { volumes: boolean }
|
|
65
|
+
* @returns {Promise<void>}
|
|
66
|
+
*/
|
|
67
|
+
async function runDownAppWithImageRemoval(appName, options = {}) {
|
|
68
|
+
const developerId = await config.getDeveloperId();
|
|
69
|
+
const containerName = containerHelpers.getContainerName(appName, developerId);
|
|
70
|
+
const imageId = await getContainerImageId(containerName);
|
|
71
|
+
|
|
72
|
+
await downApp(appName, options);
|
|
73
|
+
await removeImageIfUnused(imageId);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = {
|
|
77
|
+
runDownAppWithImageRemoval,
|
|
78
|
+
getContainerImageId,
|
|
79
|
+
removeImageIfUnused
|
|
80
|
+
};
|