@aifabrix/builder 2.42.1 → 2.43.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/README.md +1 -1
- package/bin/aifabrix.js +1 -1
- package/integration/hubspot-test/README.md +126 -0
- package/integration/{hubspot → hubspot-test}/application.json +6 -6
- package/integration/{hubspot → hubspot-test}/create-hubspot.js +5 -5
- package/integration/hubspot-test/env.template +4 -0
- package/integration/{hubspot/hubspot-datasource-company.json → hubspot-test/hubspot-test-datasource-company.json} +3 -2
- package/integration/{hubspot/hubspot-datasource-contact.json → hubspot-test/hubspot-test-datasource-contact.json} +3 -2
- package/integration/{hubspot/hubspot-datasource-deal.json → hubspot-test/hubspot-test-datasource-deal.json} +3 -2
- package/integration/{hubspot/hubspot-datasource-users.json → hubspot-test/hubspot-test-datasource-users.json} +3 -2
- package/integration/{hubspot/hubspot-deploy.json → hubspot-test/hubspot-test-deploy.json} +198 -21
- package/integration/{hubspot/hubspot-system.json → hubspot-test/hubspot-test-system.json} +8 -7
- package/integration/hubspot-test/rbac.json +166 -0
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-hubspot-credential-real.yaml +3 -3
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-hubspot-env-vars.yaml +2 -2
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-add-datasource.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-credential-create.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-credential-select.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-known-platform.yaml +1 -1
- package/integration/hubspot-test/test-artifacts/wizard-invalid-missing-source.yaml +2 -0
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-mode.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-openapi-file.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-openapi-url.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-source.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-dimension-array-test.yaml +1 -1
- package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-key-test.yaml +5 -0
- package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-path-test.yaml +5 -0
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-dimension-test.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-rbac-test.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-valid-for-rbac-yaml-test.yaml +1 -1
- package/integration/{hubspot → hubspot-test}/test.js +102 -59
- package/integration/{hubspot → hubspot-test}/wizard-hubspot-e2e.yaml +2 -2
- package/integration/{hubspot → hubspot-test}/wizard-hubspot-platform.yaml +1 -1
- package/lib/api/external-test.api.js +1 -1
- package/lib/api/service-users.api.js +111 -2
- package/lib/api/types/service-users.types.js +41 -0
- package/lib/app/register.js +3 -1
- package/lib/app/rotate-secret.js +3 -0
- package/lib/cli/setup-app.js +2 -2
- package/lib/cli/setup-auth.js +19 -11
- package/lib/cli/setup-dev.js +62 -32
- package/lib/cli/setup-environment.js +6 -21
- package/lib/cli/setup-infra.js +13 -0
- package/lib/cli/setup-secrets.js +45 -6
- package/lib/cli/setup-service-user.js +146 -20
- package/lib/cli/setup-utility.js +12 -0
- package/lib/commands/auth-config.js +4 -8
- package/lib/commands/datasource.js +46 -1
- package/lib/commands/dev-init.js +1 -1
- package/lib/commands/repair-env-template.js +14 -8
- package/lib/commands/repair-rbac.js +25 -19
- package/lib/commands/repair.js +96 -30
- package/lib/commands/secrets-remove.js +1 -1
- package/lib/commands/secrets-validate.js +17 -4
- package/lib/commands/service-user.js +231 -2
- package/lib/commands/up-common.js +25 -0
- package/lib/commands/up-dataplane.js +2 -2
- package/lib/core/admin-secrets.js +2 -0
- package/lib/core/config.js +7 -5
- package/lib/core/ensure-encryption-key.js +1 -3
- package/lib/core/secrets.js +32 -9
- package/lib/core/templates.js +1 -1
- package/lib/datasource/abac-validator.js +157 -0
- package/lib/datasource/field-reference-validator.js +74 -36
- package/lib/datasource/log-viewer.js +221 -0
- package/lib/datasource/resolve-app.js +109 -0
- package/lib/datasource/test-e2e.js +11 -20
- package/lib/datasource/test-integration.js +42 -22
- package/lib/datasource/validate.js +5 -2
- package/lib/external-system/generator.js +12 -8
- package/lib/external-system/test-system-level.js +1 -1
- package/lib/generator/external-controller-manifest.js +3 -3
- package/lib/generator/external.js +7 -7
- package/lib/generator/helpers.js +13 -9
- package/lib/generator/index.js +4 -4
- package/lib/generator/split.js +45 -10
- package/lib/generator/wizard.js +9 -6
- package/lib/infrastructure/helpers.js +50 -35
- package/lib/infrastructure/index.js +39 -23
- package/lib/schema/env-config.yaml +19 -2
- package/lib/schema/external-datasource.schema.json +11 -1
- package/lib/utils/app-config-resolver.js +23 -1
- package/lib/utils/config-paths.js +48 -4
- package/lib/utils/credential-secrets-env.js +16 -1
- package/lib/utils/env-map.js +7 -3
- package/lib/utils/error-formatter.js +37 -0
- package/lib/utils/external-env-template.js +180 -0
- package/lib/utils/external-system-display.js +43 -0
- package/lib/utils/external-system-validators.js +2 -2
- package/lib/utils/help-builder.js +3 -5
- package/lib/utils/local-secrets.js +26 -3
- package/lib/utils/paths.js +2 -1
- package/lib/utils/secrets-generator.js +2 -2
- package/lib/utils/secrets-utils.js +4 -0
- package/lib/utils/secure-file-permissions.js +91 -0
- package/lib/utils/token-manager.js +36 -3
- package/lib/utils/yaml-preserve.js +59 -1
- package/lib/validation/env-template-auth.js +50 -2
- package/lib/validation/external-manifest-validator.js +8 -0
- package/lib/validation/validate.js +8 -0
- package/lib/validation/validator.js +10 -13
- package/package.json +5 -1
- package/templates/applications/dataplane/env.template +5 -1
- package/templates/applications/miso-controller/application.yaml +1 -1
- package/templates/applications/miso-controller/env.template +13 -2
- package/templates/external-system/env.template.hbs +22 -0
- package/integration/hubspot/README.md +0 -102
- package/integration/hubspot/env.template +0 -4
- package/integration/hubspot/test-artifacts/wizard-invalid-missing-source.yaml +0 -2
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-key-test.yaml +0 -5
- package/integration/hubspot/test-artifacts/wizard-valid-for-dimension-path-test.yaml +0 -5
- /package/integration/{hubspot → hubspot-test}/companies.json +0 -0
- /package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-app-name.yaml +0 -0
- /package/integration/{hubspot → hubspot-test}/test-artifacts/wizard-invalid-missing-app.yaml +0 -0
- /package/integration/{hubspot → hubspot-test}/test-dataplane-down-helpers.js +0 -0
- /package/integration/{hubspot → hubspot-test}/test-dataplane-down-tests.js +0 -0
- /package/integration/{hubspot → hubspot-test}/test-dataplane-down.js +0 -0
package/lib/cli/setup-auth.js
CHANGED
|
@@ -60,23 +60,31 @@ function setupAuthSubcommands(program) {
|
|
|
60
60
|
process.exit(1);
|
|
61
61
|
}
|
|
62
62
|
};
|
|
63
|
-
const auth = program.command('auth')
|
|
64
|
-
|
|
65
|
-
.
|
|
66
|
-
.option('--
|
|
67
|
-
.action(authStatusHandler);
|
|
68
|
-
auth.command('config')
|
|
69
|
-
.description('Configure authentication settings (controller, environment)')
|
|
70
|
-
.option('--set-controller <url>', 'Set default controller URL')
|
|
71
|
-
.option('--set-environment <env>', 'Set default environment')
|
|
63
|
+
const auth = program.command('auth')
|
|
64
|
+
.description('Authentication status and config (controller, environment)')
|
|
65
|
+
.option('--set-controller <url>', 'Set default controller URL in config')
|
|
66
|
+
.option('--set-environment <env>', 'Set default environment in config')
|
|
72
67
|
.action(async(options) => {
|
|
73
68
|
try {
|
|
74
|
-
|
|
69
|
+
const setController = options.setController || options['set-controller'];
|
|
70
|
+
const setEnvironment = options.setEnvironment || options['set-environment'];
|
|
71
|
+
if (setController || setEnvironment) {
|
|
72
|
+
await handleAuthConfig({
|
|
73
|
+
setController,
|
|
74
|
+
setEnvironment
|
|
75
|
+
});
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
await handleAuthStatus(options);
|
|
75
79
|
} catch (error) {
|
|
76
|
-
handleCommandError(error, 'auth
|
|
80
|
+
handleCommandError(error, 'auth');
|
|
77
81
|
process.exit(1);
|
|
78
82
|
}
|
|
79
83
|
});
|
|
84
|
+
auth.command('status')
|
|
85
|
+
.description('Display authentication status for current controller and environment')
|
|
86
|
+
.option('--validate', 'Exit with code 1 when not authenticated (for scripted use, e.g. manual test setup)')
|
|
87
|
+
.action(authStatusHandler);
|
|
80
88
|
}
|
|
81
89
|
|
|
82
90
|
/**
|
package/lib/cli/setup-dev.js
CHANGED
|
@@ -73,51 +73,26 @@ async function handleSetFormat(format) {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
/**
|
|
76
|
-
* Register dev
|
|
76
|
+
* Register dev show and set-id commands.
|
|
77
77
|
* @param {Command} dev - dev subcommand group
|
|
78
78
|
*/
|
|
79
|
-
function
|
|
79
|
+
function setupDevShowAndSetId(dev) {
|
|
80
80
|
dev
|
|
81
|
-
.command('
|
|
82
|
-
.description('Show
|
|
83
|
-
.
|
|
84
|
-
.action(async(options) => {
|
|
81
|
+
.command('show')
|
|
82
|
+
.description('Show developer configuration (ports and config vars)')
|
|
83
|
+
.action(async() => {
|
|
85
84
|
try {
|
|
86
|
-
const setIdValue = options.setId || options['set-id'];
|
|
87
|
-
if (setIdValue) {
|
|
88
|
-
const digitsOnly = /^[0-9]+$/.test(setIdValue);
|
|
89
|
-
if (!digitsOnly) {
|
|
90
|
-
throw new Error('Developer ID must be a non-negative digit string (0 = default infra, > 0 = developer-specific)');
|
|
91
|
-
}
|
|
92
|
-
await config.setDeveloperId(setIdValue);
|
|
93
|
-
process.env.AIFABRIX_DEVELOPERID = setIdValue;
|
|
94
|
-
logger.log(chalk.green(`✓ Developer ID set to ${setIdValue}`));
|
|
95
|
-
await displayDevConfig(setIdValue);
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
85
|
const devId = await config.getDeveloperId();
|
|
99
86
|
await displayDevConfig(devId);
|
|
100
87
|
} catch (error) {
|
|
101
|
-
handleCommandError(error, 'dev
|
|
102
|
-
process.exit(1);
|
|
103
|
-
}
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
dev
|
|
107
|
-
.command('set-format <format>')
|
|
108
|
-
.description('Set default output format for download/convert (json | yaml); used when --format not passed')
|
|
109
|
-
.action(async(format) => {
|
|
110
|
-
try {
|
|
111
|
-
await handleSetFormat(format);
|
|
112
|
-
} catch (error) {
|
|
113
|
-
handleCommandError(error, 'dev set-format');
|
|
88
|
+
handleCommandError(error, 'dev show');
|
|
114
89
|
process.exit(1);
|
|
115
90
|
}
|
|
116
91
|
});
|
|
117
92
|
|
|
118
93
|
dev
|
|
119
94
|
.command('set-id <id>')
|
|
120
|
-
.description('Set developer ID (
|
|
95
|
+
.description('Set developer ID (0 = default infra, > 0 = developer-specific)')
|
|
121
96
|
.action(async(id) => {
|
|
122
97
|
try {
|
|
123
98
|
const digitsOnly = /^[0-9]+$/.test(id);
|
|
@@ -135,6 +110,61 @@ function setupDevConfigCommands(dev) {
|
|
|
135
110
|
});
|
|
136
111
|
}
|
|
137
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Register dev set-env-config, set-home and set-format commands.
|
|
115
|
+
* @param {Command} dev - dev subcommand group
|
|
116
|
+
*/
|
|
117
|
+
function setupDevPathAndFormatCommands(dev) {
|
|
118
|
+
dev
|
|
119
|
+
.command('set-env-config <filePath>')
|
|
120
|
+
.description('Set aifabrix-env-config path in config.yaml (pass empty to clear; path is not checked for existence)')
|
|
121
|
+
.action(async(filePath) => {
|
|
122
|
+
try {
|
|
123
|
+
const trimmed = (filePath || '').trim();
|
|
124
|
+
await config.setAifabrixEnvConfigPath(trimmed);
|
|
125
|
+
logger.log(trimmed === '' ? chalk.green('✓ Env config path cleared') : chalk.green(`✓ Env config path set to ${trimmed}`));
|
|
126
|
+
} catch (error) {
|
|
127
|
+
handleCommandError(error, 'dev set-env-config');
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
dev
|
|
133
|
+
.command('set-home <path>')
|
|
134
|
+
.description('Set aifabrix-home path in config.yaml (pass empty string to clear)')
|
|
135
|
+
.action(async(homePath) => {
|
|
136
|
+
try {
|
|
137
|
+
const trimmed = (homePath || '').trim();
|
|
138
|
+
await config.setAifabrixHomeOverride(trimmed);
|
|
139
|
+
logger.log(trimmed === '' ? chalk.green('✓ Home path cleared') : chalk.green(`✓ Home path set to ${trimmed}`));
|
|
140
|
+
} catch (error) {
|
|
141
|
+
handleCommandError(error, 'dev set-home');
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
dev
|
|
147
|
+
.command('set-format <format>')
|
|
148
|
+
.description('Set default output format for download/convert (json | yaml); used when --format not passed')
|
|
149
|
+
.action(async(format) => {
|
|
150
|
+
try {
|
|
151
|
+
await handleSetFormat(format);
|
|
152
|
+
} catch (error) {
|
|
153
|
+
handleCommandError(error, 'dev set-format');
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Register dev show, set-id, set-env-config, set-home and set-format commands.
|
|
161
|
+
* @param {Command} dev - dev subcommand group
|
|
162
|
+
*/
|
|
163
|
+
function setupDevConfigCommands(dev) {
|
|
164
|
+
setupDevShowAndSetId(dev);
|
|
165
|
+
setupDevPathAndFormatCommands(dev);
|
|
166
|
+
}
|
|
167
|
+
|
|
138
168
|
/**
|
|
139
169
|
* Register dev init and refresh commands.
|
|
140
170
|
* @param {Command} dev - dev subcommand group
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CLI environment deployment command setup (
|
|
2
|
+
* CLI environment deployment command setup (env deploy).
|
|
3
3
|
*
|
|
4
4
|
* @fileoverview Environment command definitions for AI Fabrix Builder CLI
|
|
5
5
|
* @author AI Fabrix Team
|
|
@@ -18,35 +18,20 @@ function setupEnvironmentCommands(program) {
|
|
|
18
18
|
const environmentDeploy = require('../deployment/environment');
|
|
19
19
|
await environmentDeploy.deployEnvironment(envKey, options);
|
|
20
20
|
} catch (error) {
|
|
21
|
-
handleCommandError(error, '
|
|
21
|
+
handleCommandError(error, 'env deploy');
|
|
22
22
|
process.exit(1);
|
|
23
23
|
}
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
-
const environment = program
|
|
27
|
-
.command('environment')
|
|
28
|
-
.description('Deploy and manage Miso Controller environments (dev, tst, pro, miso)');
|
|
29
|
-
|
|
30
26
|
const deployExamples = `
|
|
31
27
|
Examples:
|
|
32
|
-
$ aifabrix
|
|
33
|
-
$ aifabrix
|
|
34
|
-
$ aifabrix
|
|
35
|
-
|
|
36
|
-
environment
|
|
37
|
-
.command('deploy <env>')
|
|
38
|
-
.description('Deploy environment infrastructure in Miso Controller (run before deploy <app>)')
|
|
39
|
-
.option('--config <file>', 'Environment configuration file (optional if --preset is used)')
|
|
40
|
-
.option('--preset <size>', 'Environment size preset: s, m, l, xl (default: s)', 's')
|
|
41
|
-
.option('--skip-validation', 'Skip environment validation')
|
|
42
|
-
.option('--poll', 'Poll for deployment status', true)
|
|
43
|
-
.option('--no-poll', 'Do not poll for status')
|
|
44
|
-
.addHelpText('after', deployExamples)
|
|
45
|
-
.action(deployEnvHandler);
|
|
28
|
+
$ aifabrix env deploy dev
|
|
29
|
+
$ aifabrix env deploy tst --preset m
|
|
30
|
+
$ aifabrix env deploy dev --config ./env-config.json --no-poll`;
|
|
46
31
|
|
|
47
32
|
const env = program
|
|
48
33
|
.command('env')
|
|
49
|
-
.description('Deploy and manage Miso Controller environments (
|
|
34
|
+
.description('Deploy and manage Miso Controller environments (dev, tst, pro, miso)');
|
|
50
35
|
|
|
51
36
|
env
|
|
52
37
|
.command('deploy <env>')
|
package/lib/cli/setup-infra.js
CHANGED
|
@@ -17,6 +17,7 @@ const { resolveControllerUrl } = require('../utils/controller-url');
|
|
|
17
17
|
const { handleLogin } = require('../commands/login');
|
|
18
18
|
const { handleUpMiso } = require('../commands/up-miso');
|
|
19
19
|
const { handleUpDataplane } = require('../commands/up-dataplane');
|
|
20
|
+
const { cleanBuilderAppDirs } = require('../commands/up-common');
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* Persists optional service flag to config when explicitly set.
|
|
@@ -108,8 +109,12 @@ function setupUpPlatformCommand(program) {
|
|
|
108
109
|
.option('-r, --registry <url>', 'Override registry for all apps (e.g. myacr.azurecr.io)')
|
|
109
110
|
.option('--registry-mode <mode>', 'Override registry mode (acr|external)')
|
|
110
111
|
.option('-i, --image <key>=<value>', 'Override image (e.g. keycloak=myreg/k:v1, miso-controller=myreg/m:v1, dataplane=myreg/d:v1); can be repeated', (v, prev) => (prev || []).concat([v]))
|
|
112
|
+
.option('-f, --force', 'Clean builder/keycloak, builder/miso-controller, builder/dataplane and re-fetch from templates')
|
|
111
113
|
.action(async(options) => {
|
|
112
114
|
try {
|
|
115
|
+
if (options.force) {
|
|
116
|
+
await cleanBuilderAppDirs(['keycloak', 'miso-controller', 'dataplane']);
|
|
117
|
+
}
|
|
113
118
|
await handleUpMiso(options);
|
|
114
119
|
await handleUpDataplane(options);
|
|
115
120
|
} catch (error) {
|
|
@@ -137,8 +142,12 @@ function setupUpMisoCommand(program) {
|
|
|
137
142
|
.option('-r, --registry <url>', 'Override registry for all apps (e.g. myacr.azurecr.io)')
|
|
138
143
|
.option('--registry-mode <mode>', 'Override registry mode (acr|external)')
|
|
139
144
|
.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]))
|
|
145
|
+
.option('-f, --force', 'Clean builder/keycloak and builder/miso-controller and re-fetch from templates')
|
|
140
146
|
.action(async(options) => {
|
|
141
147
|
try {
|
|
148
|
+
if (options.force) {
|
|
149
|
+
await cleanBuilderAppDirs(['keycloak', 'miso-controller']);
|
|
150
|
+
}
|
|
142
151
|
await handleUpMiso(options);
|
|
143
152
|
} catch (error) {
|
|
144
153
|
handleCommandError(error, 'up-miso');
|
|
@@ -153,8 +162,12 @@ function setupUpDataplaneCommand(program) {
|
|
|
153
162
|
.option('-r, --registry <url>', 'Override registry for dataplane image')
|
|
154
163
|
.option('--registry-mode <mode>', 'Override registry mode (acr|external)')
|
|
155
164
|
.option('-i, --image <ref>', 'Override dataplane image reference (e.g. myreg/dataplane:latest)')
|
|
165
|
+
.option('-f, --force', 'Clean builder/dataplane and re-fetch from templates')
|
|
156
166
|
.action(async(options) => {
|
|
157
167
|
try {
|
|
168
|
+
if (options.force) {
|
|
169
|
+
await cleanBuilderAppDirs(['dataplane']);
|
|
170
|
+
}
|
|
158
171
|
await handleUpDataplane(options);
|
|
159
172
|
} catch (error) {
|
|
160
173
|
if (isAuthenticationError(error)) {
|
package/lib/cli/setup-secrets.js
CHANGED
|
@@ -6,12 +6,15 @@
|
|
|
6
6
|
* @version 2.0.0
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
const chalk = require('chalk');
|
|
9
10
|
const { handleCommandError } = require('../utils/cli-utils');
|
|
10
11
|
const { handleSecretsSet } = require('../commands/secrets-set');
|
|
11
12
|
const { handleSecretsList } = require('../commands/secrets-list');
|
|
12
13
|
const { handleSecretsRemove } = require('../commands/secrets-remove');
|
|
13
14
|
const { handleSecretsValidate } = require('../commands/secrets-validate');
|
|
14
15
|
const { handleSecure } = require('../commands/secure');
|
|
16
|
+
const config = require('../core/config');
|
|
17
|
+
const logger = require('../utils/logger');
|
|
15
18
|
|
|
16
19
|
function setupSecretValidateCommand(secretCmd) {
|
|
17
20
|
secretCmd
|
|
@@ -20,6 +23,7 @@ function setupSecretValidateCommand(secretCmd) {
|
|
|
20
23
|
.option('--naming', 'Check key names against *KeyVault convention')
|
|
21
24
|
.action(async(pathArg, options) => {
|
|
22
25
|
try {
|
|
26
|
+
await config.ensureSecretsEncryptionKey();
|
|
23
27
|
const result = await handleSecretsValidate(pathArg, options);
|
|
24
28
|
if (!result.valid) process.exit(1);
|
|
25
29
|
} catch (error) {
|
|
@@ -29,6 +33,25 @@ function setupSecretValidateCommand(secretCmd) {
|
|
|
29
33
|
});
|
|
30
34
|
}
|
|
31
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Registers the secure command on the program.
|
|
38
|
+
* @param {Command} program - Commander program instance
|
|
39
|
+
*/
|
|
40
|
+
function setupSecureCommand(program) {
|
|
41
|
+
program.command('secure')
|
|
42
|
+
.description('Encrypt secrets in secrets.local.yaml files for ISO 27001 compliance')
|
|
43
|
+
.option('--secrets-encryption <key>', 'Encryption key (32 bytes, hex or base64)')
|
|
44
|
+
.action(async(options) => {
|
|
45
|
+
try {
|
|
46
|
+
await config.ensureSecretsEncryptionKey();
|
|
47
|
+
await handleSecure(options);
|
|
48
|
+
} catch (error) {
|
|
49
|
+
handleCommandError(error, 'secure');
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
32
55
|
/**
|
|
33
56
|
* Sets up secrets and security commands
|
|
34
57
|
* @param {Command} program - Commander program instance
|
|
@@ -44,6 +67,7 @@ function setupSecretsCommands(program) {
|
|
|
44
67
|
.option('--shared', 'List shared secrets (from config aifabrix-secrets or remote API)')
|
|
45
68
|
.action(async(options) => {
|
|
46
69
|
try {
|
|
70
|
+
await config.ensureSecretsEncryptionKey();
|
|
47
71
|
await handleSecretsList(options);
|
|
48
72
|
} catch (error) {
|
|
49
73
|
handleCommandError(error, 'secret list');
|
|
@@ -57,6 +81,7 @@ function setupSecretsCommands(program) {
|
|
|
57
81
|
.option('--shared', 'Save to general secrets file (from config.yaml aifabrix-secrets) instead of user secrets')
|
|
58
82
|
.action(async(key, value, options) => {
|
|
59
83
|
try {
|
|
84
|
+
await config.ensureSecretsEncryptionKey();
|
|
60
85
|
await handleSecretsSet(key, value, options);
|
|
61
86
|
} catch (error) {
|
|
62
87
|
handleCommandError(error, 'secret set');
|
|
@@ -70,6 +95,7 @@ function setupSecretsCommands(program) {
|
|
|
70
95
|
.option('--shared', 'Remove from shared secrets (file or remote API)')
|
|
71
96
|
.action(async(key, options) => {
|
|
72
97
|
try {
|
|
98
|
+
await config.ensureSecretsEncryptionKey();
|
|
73
99
|
await handleSecretsRemove(key, options);
|
|
74
100
|
} catch (error) {
|
|
75
101
|
handleCommandError(error, 'secret remove');
|
|
@@ -77,16 +103,29 @@ function setupSecretsCommands(program) {
|
|
|
77
103
|
}
|
|
78
104
|
});
|
|
79
105
|
|
|
106
|
+
setupSecretSetSecretsFileCommand(secretCmd);
|
|
80
107
|
setupSecretValidateCommand(secretCmd);
|
|
108
|
+
setupSecureCommand(program);
|
|
109
|
+
}
|
|
81
110
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
111
|
+
/**
|
|
112
|
+
* Register secret set-secrets-file command.
|
|
113
|
+
* @param {Command} secretCmd - secret command group
|
|
114
|
+
*/
|
|
115
|
+
function setupSecretSetSecretsFileCommand(secretCmd) {
|
|
116
|
+
secretCmd
|
|
117
|
+
.command('set-secrets-file <path>')
|
|
118
|
+
.description('Set aifabrix-secrets path in config (local file or https URL; pass empty to clear; path/URL is not checked for existence)')
|
|
119
|
+
.action(async(secretsPath) => {
|
|
86
120
|
try {
|
|
87
|
-
|
|
121
|
+
const trimmed = (secretsPath || '').trim();
|
|
122
|
+
if (trimmed !== '' && trimmed.startsWith('http://')) {
|
|
123
|
+
throw new Error('Only https URLs are allowed for remote secrets');
|
|
124
|
+
}
|
|
125
|
+
await config.setSecretsPath(trimmed);
|
|
126
|
+
logger.log(trimmed === '' ? chalk.green('✓ Secrets file path cleared') : chalk.green(`✓ Secrets file path set to ${trimmed}`));
|
|
88
127
|
} catch (error) {
|
|
89
|
-
handleCommandError(error, '
|
|
128
|
+
handleCommandError(error, 'secret set-secrets-file');
|
|
90
129
|
process.exit(1);
|
|
91
130
|
}
|
|
92
131
|
});
|
|
@@ -10,28 +10,37 @@
|
|
|
10
10
|
const chalk = require('chalk');
|
|
11
11
|
const logger = require('../utils/logger');
|
|
12
12
|
const { handleCommandError } = require('../utils/cli-utils');
|
|
13
|
-
const {
|
|
13
|
+
const {
|
|
14
|
+
runServiceUserCreate,
|
|
15
|
+
runServiceUserList,
|
|
16
|
+
runServiceUserRotateSecret,
|
|
17
|
+
runServiceUserDelete,
|
|
18
|
+
runServiceUserUpdateGroups,
|
|
19
|
+
runServiceUserUpdateRedirectUris
|
|
20
|
+
} = require('../commands/service-user');
|
|
14
21
|
|
|
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 and manage service users (API clients) for integrations and CI')
|
|
23
|
-
.addHelpText('after', `
|
|
22
|
+
const HELP_AFTER = `
|
|
24
23
|
Service users are dedicated accounts for integrations, CI pipelines, or API clients.
|
|
25
|
-
|
|
24
|
+
Use: list (service-user:read), create (service-user:create), rotate-secret, update-groups, update-redirect-uris (service-user:update), delete (service-user:delete).
|
|
25
|
+
The controller returns a one-time clientSecret on create and rotate-secret—save it immediately; it cannot be retrieved again.
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
$ aifabrix service-user create -u
|
|
29
|
-
--redirect-uris
|
|
27
|
+
Examples:
|
|
28
|
+
$ aifabrix service-user create -u postman -e postman@aifabrix.dev \\
|
|
29
|
+
--redirect-uris https://oauth.pstmn.io/v1/callback --group-names AI-Fabrix-Platform-Admins
|
|
30
|
+
$ aifabrix service-user list
|
|
31
|
+
$ aifabrix service-user rotate-secret --id <uuid>
|
|
32
|
+
$ aifabrix service-user delete --id <uuid>
|
|
33
|
+
$ aifabrix service-user update-groups --id <uuid> --group-names Group1,Group2
|
|
34
|
+
$ aifabrix service-user update-redirect-uris --id <uuid> --redirect-uris https://app.example.com/callback
|
|
30
35
|
|
|
31
|
-
|
|
36
|
+
Run "aifabrix login" first.`;
|
|
37
|
+
|
|
38
|
+
function parseOptionalInt(val) {
|
|
39
|
+
return (val !== undefined && val !== null) ? parseInt(val, 10) : undefined;
|
|
40
|
+
}
|
|
32
41
|
|
|
33
|
-
|
|
34
|
-
|
|
42
|
+
function addCreateCommand(serviceUser) {
|
|
43
|
+
serviceUser.command('create')
|
|
35
44
|
.description('Create a service user and receive a one-time clientSecret (save it now; it will not be shown again)')
|
|
36
45
|
.option('--controller <url>', 'Controller base URL (default: from config)')
|
|
37
46
|
.option('-u, --username <username>', 'Service user username (required)')
|
|
@@ -41,15 +50,14 @@ Required: permission service-user:create on the controller. Run "aifabrix login"
|
|
|
41
50
|
.option('-d, --description <description>', 'Description for the service user')
|
|
42
51
|
.action(async(options) => {
|
|
43
52
|
try {
|
|
44
|
-
|
|
53
|
+
await runServiceUserCreate({
|
|
45
54
|
controller: options.controller,
|
|
46
55
|
username: options.username,
|
|
47
56
|
email: options.email,
|
|
48
57
|
redirectUris: options.redirectUris,
|
|
49
58
|
groupNames: options.groupNames,
|
|
50
59
|
description: options.description
|
|
51
|
-
};
|
|
52
|
-
await runServiceUserCreate(opts);
|
|
60
|
+
});
|
|
53
61
|
} catch (error) {
|
|
54
62
|
logger.error(chalk.red(`Error: ${error.message}`));
|
|
55
63
|
handleCommandError(error, 'service-user create');
|
|
@@ -58,4 +66,122 @@ Required: permission service-user:create on the controller. Run "aifabrix login"
|
|
|
58
66
|
});
|
|
59
67
|
}
|
|
60
68
|
|
|
69
|
+
function addListCommand(serviceUser) {
|
|
70
|
+
serviceUser.command('list')
|
|
71
|
+
.description('List service users (supports pagination and search)')
|
|
72
|
+
.option('--controller <url>', 'Controller base URL (default: from config)')
|
|
73
|
+
.option('--page <n>', 'Page number')
|
|
74
|
+
.option('--page-size <n>', 'Items per page')
|
|
75
|
+
.option('--search <term>', 'Search term')
|
|
76
|
+
.option('--sort <field>', 'Sort field/direction')
|
|
77
|
+
.option('--filter <expr>', 'Filter expression')
|
|
78
|
+
.action(async(options) => {
|
|
79
|
+
try {
|
|
80
|
+
await runServiceUserList({
|
|
81
|
+
controller: options.controller,
|
|
82
|
+
page: parseOptionalInt(options.page),
|
|
83
|
+
pageSize: parseOptionalInt(options.pageSize),
|
|
84
|
+
search: options.search,
|
|
85
|
+
sort: options.sort,
|
|
86
|
+
filter: options.filter
|
|
87
|
+
});
|
|
88
|
+
} catch (error) {
|
|
89
|
+
logger.error(chalk.red(`Error: ${error.message}`));
|
|
90
|
+
handleCommandError(error, 'service-user list');
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function addRotateSecretCommand(serviceUser) {
|
|
97
|
+
serviceUser.command('rotate-secret')
|
|
98
|
+
.description('Rotate (regenerate) secret for a service user; new secret shown once only')
|
|
99
|
+
.option('--controller <url>', 'Controller base URL (default: from config)')
|
|
100
|
+
.option('--id <uuid>', 'Service user ID (required)')
|
|
101
|
+
.action(async(options) => {
|
|
102
|
+
try {
|
|
103
|
+
await runServiceUserRotateSecret({ controller: options.controller, id: options.id });
|
|
104
|
+
} catch (error) {
|
|
105
|
+
logger.error(chalk.red(`Error: ${error.message}`));
|
|
106
|
+
handleCommandError(error, 'service-user rotate-secret');
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function addDeleteCommand(serviceUser) {
|
|
113
|
+
serviceUser.command('delete')
|
|
114
|
+
.description('Delete (deactivate) a service user')
|
|
115
|
+
.option('--controller <url>', 'Controller base URL (default: from config)')
|
|
116
|
+
.option('--id <uuid>', 'Service user ID (required)')
|
|
117
|
+
.action(async(options) => {
|
|
118
|
+
try {
|
|
119
|
+
await runServiceUserDelete({ controller: options.controller, id: options.id });
|
|
120
|
+
} catch (error) {
|
|
121
|
+
logger.error(chalk.red(`Error: ${error.message}`));
|
|
122
|
+
handleCommandError(error, 'service-user delete');
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function addUpdateGroupsCommand(serviceUser) {
|
|
129
|
+
serviceUser.command('update-groups')
|
|
130
|
+
.description('Update group assignments for a service user')
|
|
131
|
+
.option('--controller <url>', 'Controller base URL (default: from config)')
|
|
132
|
+
.option('--id <uuid>', 'Service user ID (required)')
|
|
133
|
+
.option('--group-names <names>', 'Comma-separated group names (required)')
|
|
134
|
+
.action(async(options) => {
|
|
135
|
+
try {
|
|
136
|
+
await runServiceUserUpdateGroups({
|
|
137
|
+
controller: options.controller,
|
|
138
|
+
id: options.id,
|
|
139
|
+
groupNames: options.groupNames
|
|
140
|
+
});
|
|
141
|
+
} catch (error) {
|
|
142
|
+
logger.error(chalk.red(`Error: ${error.message}`));
|
|
143
|
+
handleCommandError(error, 'service-user update-groups');
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function addUpdateRedirectUrisCommand(serviceUser) {
|
|
150
|
+
serviceUser.command('update-redirect-uris')
|
|
151
|
+
.description('Update redirect URIs for a service user (min 1)')
|
|
152
|
+
.option('--controller <url>', 'Controller base URL (default: from config)')
|
|
153
|
+
.option('--id <uuid>', 'Service user ID (required)')
|
|
154
|
+
.option('--redirect-uris <uris>', 'Comma-separated redirect URIs (required, min 1)')
|
|
155
|
+
.action(async(options) => {
|
|
156
|
+
try {
|
|
157
|
+
await runServiceUserUpdateRedirectUris({
|
|
158
|
+
controller: options.controller,
|
|
159
|
+
id: options.id,
|
|
160
|
+
redirectUris: options.redirectUris
|
|
161
|
+
});
|
|
162
|
+
} catch (error) {
|
|
163
|
+
logger.error(chalk.red(`Error: ${error.message}`));
|
|
164
|
+
handleCommandError(error, 'service-user update-redirect-uris');
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Sets up service-user commands
|
|
172
|
+
* @param {Command} program - Commander program instance
|
|
173
|
+
*/
|
|
174
|
+
function setupServiceUserCommands(program) {
|
|
175
|
+
const serviceUser = program
|
|
176
|
+
.command('service-user')
|
|
177
|
+
.description('Create and manage service users (API clients) for integrations and CI')
|
|
178
|
+
.addHelpText('after', HELP_AFTER);
|
|
179
|
+
addCreateCommand(serviceUser);
|
|
180
|
+
addListCommand(serviceUser);
|
|
181
|
+
addRotateSecretCommand(serviceUser);
|
|
182
|
+
addDeleteCommand(serviceUser);
|
|
183
|
+
addUpdateGroupsCommand(serviceUser);
|
|
184
|
+
addUpdateRedirectUrisCommand(serviceUser);
|
|
185
|
+
}
|
|
186
|
+
|
|
61
187
|
module.exports = { setupServiceUserCommands };
|
package/lib/cli/setup-utility.js
CHANGED
|
@@ -163,6 +163,7 @@ function setupRepairCommand(program) {
|
|
|
163
163
|
program.command('repair <app>')
|
|
164
164
|
.description('Repair external integration config: fix drift (file lists, app key, datasource alignment, rbac, manifest)')
|
|
165
165
|
.option('--auth <method>', 'Set authentication method (oauth2, aad, apikey, basic, queryParam, oidc, hmac, none); updates system file and env.template')
|
|
166
|
+
.option('--doc', 'Regenerate README.md from deployment manifest')
|
|
166
167
|
.option('--dry-run', 'Report changes only; do not write')
|
|
167
168
|
.option('--rbac', 'Ensure RBAC permissions per datasource and add default Admin/Reader roles if none exist')
|
|
168
169
|
.option('--expose', 'Set exposed.attributes on each datasource to all fieldMappings.attributes keys')
|
|
@@ -174,9 +175,18 @@ function setupRepairCommand(program) {
|
|
|
174
175
|
const { detectAppType } = require('../utils/paths');
|
|
175
176
|
const { appPath } = await detectAppType(appName);
|
|
176
177
|
logOfflinePathWhenType(appPath);
|
|
178
|
+
let format = 'yaml';
|
|
179
|
+
try {
|
|
180
|
+
const config = require('../core/config');
|
|
181
|
+
format = (await config.getFormat()) || format;
|
|
182
|
+
} catch (_) {
|
|
183
|
+
// use default yaml when config unavailable
|
|
184
|
+
}
|
|
177
185
|
const result = await repairExternalIntegration(appName, {
|
|
178
186
|
auth: options.auth,
|
|
187
|
+
doc: options.doc,
|
|
179
188
|
dryRun: options.dryRun,
|
|
189
|
+
format,
|
|
180
190
|
rbac: options.rbac,
|
|
181
191
|
expose: options.expose,
|
|
182
192
|
sync: options.sync,
|
|
@@ -187,6 +197,8 @@ function setupRepairCommand(program) {
|
|
|
187
197
|
result.changes.forEach(c => logger.log(chalk.gray(` ${c}`)));
|
|
188
198
|
} else if (result.updated) {
|
|
189
199
|
logger.log(chalk.green('\n✓ Repaired external integration config.'));
|
|
200
|
+
} else if (result.readmeRegenerated) {
|
|
201
|
+
logger.log(chalk.green('\n✓ Regenerated README.md from deployment manifest.'));
|
|
190
202
|
} else {
|
|
191
203
|
logger.log(chalk.gray('No changes needed; config already matches files on disk.'));
|
|
192
204
|
}
|
|
@@ -55,7 +55,7 @@ async function handleSetController(url) {
|
|
|
55
55
|
|
|
56
56
|
throw new Error(
|
|
57
57
|
`You have credentials for another controller (${loggedInControllerUrl}).\n` +
|
|
58
|
-
|
|
58
|
+
'To use a different controller either run "aifabrix login" with that controller, or run "aifabrix logout" first to clear credentials, then set the new controller with "aifabrix auth --set-controller <url>".'
|
|
59
59
|
);
|
|
60
60
|
} catch (error) {
|
|
61
61
|
logger.error(chalk.red(`✗ Failed to set controller URL: ${error.message}`));
|
|
@@ -80,8 +80,7 @@ async function handleSetEnvironment(environment) {
|
|
|
80
80
|
const controllerUrl = await getControllerUrl();
|
|
81
81
|
if (!controllerUrl) {
|
|
82
82
|
throw new Error(
|
|
83
|
-
'No controller URL found in config
|
|
84
|
-
'Please run "aifabrix login" first to set the controller URL.'
|
|
83
|
+
'No controller URL found in config. Run "aifabrix login" first, or set the controller with "aifabrix auth --set-controller <url>".'
|
|
85
84
|
);
|
|
86
85
|
}
|
|
87
86
|
|
|
@@ -89,8 +88,7 @@ async function handleSetEnvironment(environment) {
|
|
|
89
88
|
const isLoggedIn = await checkUserLoggedIn(controllerUrl);
|
|
90
89
|
if (!isLoggedIn) {
|
|
91
90
|
throw new Error(
|
|
92
|
-
`You are not logged in to controller ${controllerUrl}
|
|
93
|
-
'Please run "aifabrix login" first to authenticate with this controller.'
|
|
91
|
+
`You are not logged in to controller ${controllerUrl}. Run "aifabrix login" first to authenticate.`
|
|
94
92
|
);
|
|
95
93
|
}
|
|
96
94
|
|
|
@@ -117,9 +115,7 @@ async function handleSetEnvironment(environment) {
|
|
|
117
115
|
async function handleAuthConfig(options) {
|
|
118
116
|
if (!options.setController && !options.setEnvironment) {
|
|
119
117
|
throw new Error(
|
|
120
|
-
'No action specified. Use
|
|
121
|
-
' --set-controller <url>\n' +
|
|
122
|
-
' --set-environment <env>'
|
|
118
|
+
'No action specified. Use "aifabrix auth --set-controller <url>" or "aifabrix auth --set-environment <env>".'
|
|
123
119
|
);
|
|
124
120
|
}
|
|
125
121
|
if (options.setController) {
|