@aifabrix/builder 2.45.0 → 2.46.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/cli-layout.mdc +2 -0
- package/.cursor/rules/cli-product-neutral.mdc +43 -0
- package/.cursor/rules/docs-rules.mdc +3 -1
- package/.cursor/rules/project-rules.mdc +1 -0
- package/README.md +43 -17
- package/integration/hubspot-test/wizard-hubspot-e2e.yaml +1 -1
- package/jest.config.default.js +2 -1
- package/jest.config.isolated.js +2 -1
- package/jest.global-live-fabrix-hooks.js +36 -0
- package/jest.isolated-projects.js +130 -0
- package/jest.projects.js +69 -121
- package/lib/api/agent-metadata-validation.api.js +93 -0
- package/lib/api/api-response-errors.js +124 -0
- package/lib/api/audit-api-helpers.js +68 -0
- package/lib/api/audit.api.js +119 -0
- package/lib/api/auth-cache.api.js +44 -0
- package/lib/api/certificates.api.js +4 -4
- package/lib/api/credential.api.js +2 -2
- package/lib/api/dataplane-health.api.js +137 -0
- package/lib/api/dataplane-sync.api.js +27 -0
- package/lib/api/datasources-core.api.js +21 -18
- package/lib/api/datasources-extended.api.js +22 -22
- package/lib/api/dimensions.api.js +3 -0
- package/lib/api/external-systems.api.js +13 -13
- package/lib/api/governance-scenario-pack.api.js +159 -0
- package/lib/api/governance-scenarios.api.js +39 -0
- package/lib/api/groups.api.js +79 -0
- package/lib/api/index.js +88 -5
- package/lib/api/lifecycle.api.js +77 -0
- package/lib/api/pipeline.api.js +7 -5
- package/lib/api/protection.api.js +157 -0
- package/lib/api/records-bulk.api.js +43 -0
- package/lib/api/records-search-parse.js +49 -0
- package/lib/api/records-search.api.js +29 -0
- package/lib/api/types/agent-metadata-validation.types.js +67 -0
- package/lib/api/types/audit.types.js +72 -0
- package/lib/api/types/dataplane-health.types.js +27 -0
- package/lib/api/types/dimensions.types.js +2 -0
- package/lib/api/types/governance-scenarios.types.js +39 -0
- package/lib/api/types/protection.types.js +69 -0
- package/lib/api/types/records-bulk.types.js +35 -0
- package/lib/api/types/records-search.types.js +28 -0
- package/lib/api/user-groups.api.js +50 -0
- package/lib/api/users.api.js +95 -0
- package/lib/api/validation-run.api.js +3 -3
- package/lib/api/validation-runner.js +5 -3
- package/lib/api/wizard-platform.api.js +3 -3
- package/lib/api/wizard.api.js +21 -21
- package/lib/app/deploy-config.js +4 -1
- package/lib/app/deploy.js +34 -19
- package/lib/app/down.js +20 -1
- package/lib/app/push.js +4 -5
- package/lib/app/register.js +7 -4
- package/lib/app/run-env-compose.js +84 -30
- package/lib/build/index.js +5 -0
- package/lib/certification/sync-system-certification.js +13 -6
- package/lib/cli/index.js +8 -2
- package/lib/cli/infra-guided-auth.js +109 -0
- package/lib/cli/infra-guided.js +91 -69
- package/lib/cli/setup-app.help.js +19 -2
- package/lib/cli/setup-app.js +58 -23
- package/lib/cli/setup-app.test-commands.js +28 -21
- package/lib/cli/setup-auth.js +55 -3
- package/lib/cli/setup-certification-commands.js +147 -0
- package/lib/cli/setup-certification.help.js +141 -0
- package/lib/cli/setup-environment.js +19 -2
- package/lib/cli/setup-external-system-help.js +62 -0
- package/lib/cli/setup-external-system.js +62 -6
- package/lib/cli/setup-infra.js +9 -1
- package/lib/cli/setup-platform.js +6 -1
- package/lib/cli/setup-secrets.js +33 -1
- package/lib/cli/setup-utility-help-after.js +65 -0
- package/lib/cli/setup-utility-repair.js +198 -0
- package/lib/cli/setup-utility-resolve.js +42 -6
- package/lib/cli/setup-utility-validate.js +197 -0
- package/lib/cli/setup-utility.js +41 -258
- package/lib/commands/auth-status-client-tokens.js +214 -0
- package/lib/commands/auth-status-dataplane-version.js +269 -0
- package/lib/commands/auth-status-display.js +94 -0
- package/lib/commands/auth-status.js +111 -53
- package/lib/commands/credential-push.js +10 -5
- package/lib/commands/datasource-load-export-cli.js +178 -0
- package/lib/commands/datasource-test-trust-cli.js +118 -0
- package/lib/commands/datasource-unified-test-cli.js +31 -20
- package/lib/commands/datasource-unified-test-cli.options.js +22 -3
- package/lib/commands/datasource-unified-test-e2e-cli-helpers.js +5 -5
- package/lib/commands/datasource-verify-audit-cli.js +96 -0
- package/lib/commands/datasource.js +68 -1
- package/lib/commands/dev-show-display.js +8 -1
- package/lib/commands/dimension-value.js +43 -13
- package/lib/commands/dimension.js +38 -7
- package/lib/commands/governance-cli-shared.js +94 -0
- package/lib/commands/governance-command-action.js +132 -0
- package/lib/commands/governance-download-external.js +34 -0
- package/lib/commands/governance-generate-external.js +71 -0
- package/lib/commands/governance-upload-external.js +36 -0
- package/lib/commands/governance-verify-external.js +64 -0
- package/lib/commands/governance.js +144 -0
- package/lib/commands/identity-apply-sync.js +131 -0
- package/lib/commands/identity-group-cli.js +146 -0
- package/lib/commands/identity-membership-role-cache.js +175 -0
- package/lib/commands/identity-shared.js +94 -0
- package/lib/commands/identity-user-cli.js +158 -0
- package/lib/commands/identity.js +43 -0
- package/lib/commands/lifecycle-command-action.js +123 -0
- package/lib/commands/lifecycle-external.js +268 -0
- package/lib/commands/login.js +56 -17
- package/lib/commands/protection-cli-leaves.js +286 -0
- package/lib/commands/protection.js +50 -0
- package/lib/commands/repair-auth-apply.js +127 -0
- package/lib/commands/repair-changed-files.js +41 -0
- package/lib/commands/repair-datasource-keys.js +4 -1
- package/lib/commands/repair-datasource-run.js +59 -0
- package/lib/commands/repair-datasource-test-payload.js +259 -0
- package/lib/commands/repair-datasource.js +1 -51
- package/lib/commands/repair-env-template.js +64 -13
- package/lib/commands/repair-external-run.js +216 -0
- package/lib/commands/repair-openapi-sync.js +23 -0
- package/lib/commands/repair-persist.js +4 -0
- package/lib/commands/repair-rbac.js +52 -1
- package/lib/commands/repair-steps.js +145 -0
- package/lib/commands/repair.js +29 -286
- package/lib/commands/secrets-get.js +184 -0
- package/lib/commands/secrets-remove-all.js +3 -1
- package/lib/commands/setup-image-refresh.js +146 -0
- package/lib/commands/setup-modes.js +158 -162
- package/lib/commands/setup-platform-auth.js +76 -0
- package/lib/commands/setup-prompts-admin.js +244 -0
- package/lib/commands/setup-prompts-platform-mode.js +67 -0
- package/lib/commands/setup-prompts-validators.js +36 -0
- package/lib/commands/setup-prompts.js +24 -76
- package/lib/commands/setup-run-context.js +45 -0
- package/lib/commands/setup-spinners.js +39 -0
- package/lib/commands/setup.js +27 -5
- package/lib/commands/teardown.js +39 -0
- package/lib/commands/test-e2e-external.js +61 -14
- package/lib/commands/test-governance-cli-options.js +47 -0
- package/lib/commands/test-governance-external.js +67 -0
- package/lib/commands/test-trust-cli-options.js +32 -0
- package/lib/commands/test-trust-external.js +126 -0
- package/lib/commands/up-common.js +25 -13
- package/lib/commands/up-dataplane.js +50 -13
- package/lib/commands/upload-publish-context.js +75 -0
- package/lib/commands/upload-sidecar-followups.js +89 -0
- package/lib/commands/upload.js +108 -105
- package/lib/commands/verify-governance-combined-external.js +61 -0
- package/lib/commands/verify-governance-command-action.js +64 -0
- package/lib/commands/verify-governance-db.js +102 -0
- package/lib/commands/verify-governance-external.js +77 -0
- package/lib/commands/verify-governance-result-builders.js +71 -0
- package/lib/commands/verify-operations-command-action.js +68 -0
- package/lib/commands/verify-operations-external.js +268 -0
- package/lib/commands/verify-operations-persist.js +75 -0
- package/lib/commands/verify-operations-step-plan.js +68 -0
- package/lib/commands/verify-operations-steps.js +111 -0
- package/lib/commands/verify-trust-command-action.js +70 -0
- package/lib/commands/verify-trust-external.js +67 -0
- package/lib/commands/wizard-config-normalizer.js +43 -23
- package/lib/commands/wizard-core.js +5 -0
- package/lib/core/admin-password-generator.js +38 -0
- package/lib/core/admin-secrets-bundle.js +111 -0
- package/lib/core/admin-secrets-env-overlay.js +62 -0
- package/lib/core/admin-secrets.js +32 -4
- package/lib/core/config-attach-extensions.js +3 -0
- package/lib/core/config-device-dataplane.js +185 -0
- package/lib/core/config-platform-controller.js +44 -0
- package/lib/core/config-setup-installation-profile.js +42 -0
- package/lib/core/config-setup-platform-mode.js +46 -0
- package/lib/core/config.js +46 -36
- package/lib/core/secrets-admin-env.js +6 -2
- package/lib/core/secrets-ensure.js +4 -0
- package/lib/core/secrets-env-content.js +54 -48
- package/lib/core/secrets-env-write.js +10 -1
- package/lib/core/secrets-infra-placeholder-sync.js +2 -0
- package/lib/core/setup-installation-profile.js +43 -0
- package/lib/core/setup-platform-mode.js +55 -0
- package/lib/datasource/agent-trust-debug-log.js +45 -0
- package/lib/datasource/agent-trust-fetch.js +115 -0
- package/lib/datasource/agent-trust-map.js +101 -0
- package/lib/datasource/agent-trust-mock.js +44 -0
- package/lib/datasource/agent-trust-run.js +95 -0
- package/lib/datasource/agent-trust-timeout.js +25 -0
- package/lib/datasource/audit-evidence-display.js +80 -0
- package/lib/datasource/audit-evidence-extract-debug.js +115 -0
- package/lib/datasource/audit-evidence-extract.js +180 -0
- package/lib/datasource/audit-evidence-matrix-rows.js +158 -0
- package/lib/datasource/audit-evidence-operation-aliases.js +63 -0
- package/lib/datasource/audit-evidence-poll.js +109 -0
- package/lib/datasource/audit-evidence-run.js +177 -0
- package/lib/datasource/audit-evidence-verify-helpers.js +167 -0
- package/lib/datasource/audit-evidence-verify-matrix.js +205 -0
- package/lib/datasource/audit-evidence-verify.js +108 -0
- package/lib/datasource/bulk-loader-run-batches.js +100 -0
- package/lib/datasource/bulk-loader-service-retry.js +52 -0
- package/lib/datasource/bulk-loader-service.js +127 -0
- package/lib/datasource/capability/run-capability-dimension.js +2 -1
- package/lib/datasource/datasource-exporter-resolve.js +82 -0
- package/lib/datasource/datasource-exporter-service.js +144 -0
- package/lib/datasource/datasource-identifier.js +148 -0
- package/lib/datasource/datasource-load-export-context.js +95 -0
- package/lib/datasource/integration-context.js +6 -4
- package/lib/datasource/local-data-paths.js +145 -0
- package/lib/datasource/log-cleaner.js +241 -0
- package/lib/datasource/log-viewer.js +111 -6
- package/lib/datasource/record-file-parser.js +85 -0
- package/lib/datasource/record-mapper.js +129 -0
- package/lib/datasource/resolve-app.js +74 -55
- package/lib/datasource/test-e2e.js +29 -2
- package/lib/datasource/unified-validation-run-body.js +3 -1
- package/lib/datasource/unified-validation-run-resolve.js +45 -12
- package/lib/datasource/unified-validation-run.js +47 -18
- package/lib/datasource/validate.js +37 -20
- package/lib/deployment/environment-poll-status.js +138 -0
- package/lib/deployment/environment.js +42 -34
- package/lib/external-system/download.js +21 -20
- package/lib/external-system/generator.js +5 -1
- package/lib/external-system/integration-auth-context.js +45 -0
- package/lib/external-system/test-auth.js +12 -5
- package/lib/external-system/test-system-level.js +25 -4
- package/lib/external-system/test.js +9 -7
- package/lib/generator/external-controller-manifest.js +15 -0
- package/lib/governance/governance-cli-errors.js +96 -0
- package/lib/governance/governance-download-display.js +38 -0
- package/lib/governance/governance-pack-loader.js +70 -0
- package/lib/governance/governance-run-display.js +309 -0
- package/lib/identity/identity-apply-core.js +108 -0
- package/lib/identity/identity-apply-phases.js +244 -0
- package/lib/identity/identity-apply-service.js +77 -0
- package/lib/identity/identity-csv-parser.js +114 -0
- package/lib/infrastructure/admin-secrets-apply.js +84 -0
- package/lib/infrastructure/admin-secrets-ensure.js +130 -0
- package/lib/infrastructure/admin-secrets-resolve.js +95 -0
- package/lib/infrastructure/admin-secrets-sync.js +91 -0
- package/lib/infrastructure/helpers.js +23 -129
- package/lib/infrastructure/index.js +5 -6
- package/lib/infrastructure/orphan-infra-docker-teardown.js +11 -5
- package/lib/internal/fs-real-sync.js +29 -1
- package/lib/lifecycle/certification-progress.js +231 -0
- package/lib/lifecycle/dataplane-ui-url.js +145 -0
- package/lib/lifecycle/exit-codes.js +24 -0
- package/lib/lifecycle/json-envelope.js +25 -0
- package/lib/lifecycle/lifecycle-governance-options.js +51 -0
- package/lib/lifecycle/logger-quiet.js +29 -0
- package/lib/lifecycle/open-api-published.js +59 -0
- package/lib/lifecycle/operations-readiness-metrics.js +101 -0
- package/lib/lifecycle/product-model.js +196 -0
- package/lib/lifecycle/report-display.js +471 -0
- package/lib/lifecycle/scenario-upload.js +191 -0
- package/lib/lifecycle/verify-step-progress.js +87 -0
- package/lib/parameters/infra-kv-discovery.js +27 -2
- package/lib/protection/auth-context.js +51 -0
- package/lib/protection/convert-batch.js +90 -0
- package/lib/protection/load.js +41 -0
- package/lib/protection/paths.js +158 -0
- package/lib/protection/preflight-datasource-ready.js +46 -0
- package/lib/protection/protection-create-probe-lines.js +142 -0
- package/lib/protection/protection-create-probes.js +95 -0
- package/lib/protection/protection-create-scaffold.js +281 -0
- package/lib/protection/protection-display-helpers.js +445 -0
- package/lib/protection/protection-display.js +215 -0
- package/lib/protection/protection-preset-registry.js +97 -0
- package/lib/protection/report-exit.js +62 -0
- package/lib/protection/resolve.js +202 -0
- package/lib/protection/run-commands-validate.js +130 -0
- package/lib/protection/run-commands.js +216 -0
- package/lib/protection/run-protection-create-helpers.js +134 -0
- package/lib/protection/run-protection-create-probes-flow.js +43 -0
- package/lib/protection/run-protection-create.js +191 -0
- package/lib/protection/scope.js +22 -0
- package/lib/protection/sync-after-upload.js +64 -0
- package/lib/protection/upload-batch.js +127 -0
- package/lib/protection/validate-batch.js +113 -0
- package/lib/protection/validate-local.js +65 -0
- package/lib/resolvers/dimension-file.js +29 -4
- package/lib/schema/application-schema.json +1 -1
- package/lib/schema/datasource-test-run.schema.json +89 -6
- package/lib/schema/external-datasource.schema.json +2636 -2588
- package/lib/schema/external-system.schema.json +1089 -761
- package/lib/schema/flag-map-validation-run.json +2 -2
- package/lib/schema/infra.parameter.yaml +45 -0
- package/lib/schema/protection.schema.json +374 -0
- package/lib/schema/type/credential-field-registry.json +642 -0
- package/lib/schema/wizard-config.schema.json +1 -1
- package/lib/utils/agent-trust-run-display.js +231 -0
- package/lib/utils/agent-trust-run-exit.js +59 -0
- package/lib/utils/agent-trust-wait-ui.js +94 -0
- package/lib/utils/aifabrix-test-runtime-guard.js +125 -0
- package/lib/utils/api.js +55 -31
- package/lib/utils/app-relative-path-env.js +112 -0
- package/lib/utils/cli-exit-codes.js +30 -0
- package/lib/utils/cli-secrets-error-format.js +83 -21
- package/lib/utils/cli-sync-options.js +33 -0
- package/lib/utils/cli-utils.js +16 -4
- package/lib/utils/compose-generate-docker-compose.js +4 -0
- package/lib/utils/compose-traefik-ingress-base.js +16 -1
- package/lib/utils/config-secrets-path.js +46 -0
- package/lib/utils/controller-url.js +88 -1
- package/lib/utils/credential-push-ui.js +90 -0
- package/lib/utils/credential-secrets-env.js +137 -9
- package/lib/utils/dataplane-cli-version-gate.js +202 -0
- package/lib/utils/dataplane-cli-version-help.js +74 -0
- package/lib/utils/dataplane-resolver.js +129 -2
- package/lib/utils/datasource-export-display.js +137 -0
- package/lib/utils/datasource-load-display.js +174 -0
- package/lib/utils/datasource-load-export-ui.js +120 -0
- package/lib/utils/datasource-test-run-capability-scope.js +3 -3
- package/lib/utils/datasource-test-run-display.js +3 -3
- package/lib/utils/datasource-test-run-tty-log.js +5 -1
- package/lib/utils/declarative-url-ports.js +3 -3
- package/lib/utils/deployment-auth-mode.js +41 -0
- package/lib/utils/developer-volume-id.js +33 -0
- package/lib/utils/env-copy.js +14 -9
- package/lib/utils/env-kv-orphan-lines.js +43 -0
- package/lib/utils/error-formatters/network-errors.js +13 -4
- package/lib/utils/external-env-template.js +1 -0
- package/lib/utils/external-system-readiness-core.js +10 -0
- package/lib/utils/external-system-readiness-display.js +80 -23
- package/lib/utils/health-check.js +4 -1
- package/lib/utils/help-builder.js +35 -26
- package/lib/utils/keycloak-realm-ready.js +101 -0
- package/lib/utils/load-cip-capacity-display-config.js +61 -15
- package/lib/utils/manifest-source-emit.js +24 -0
- package/lib/utils/pipeline-upload-auth-retry.js +82 -0
- package/lib/utils/platform-controller-url.js +156 -0
- package/lib/utils/postgres-platform-bootstrap.js +199 -0
- package/lib/utils/postgres-wipe.js +9 -2
- package/lib/utils/resolve-env-mode.js +47 -0
- package/lib/utils/secrets-generator.js +31 -25
- package/lib/utils/secrets-missing-error.js +164 -0
- package/lib/utils/semver-compare.js +127 -0
- package/lib/utils/service-url-label.js +81 -0
- package/lib/utils/setup-quiet-output.js +25 -0
- package/lib/utils/template-helpers.js +15 -0
- package/lib/utils/token-manager-client-credentials.js +201 -0
- package/lib/utils/token-manager-messages.js +8 -0
- package/lib/utils/token-manager-refresh.js +12 -2
- package/lib/utils/token-manager.js +86 -105
- package/lib/utils/upload-pipeline-ui.js +98 -0
- package/lib/utils/upload-sync-options.js +42 -0
- package/lib/utils/url-declarative-public-base.js +21 -3
- package/lib/utils/url-declarative-vdir-inactive-env.js +14 -4
- package/lib/utils/validation-poll-ui.js +22 -7
- package/lib/utils/validation-run-stage.js +45 -0
- package/lib/validation/wizard-config-validator.js +58 -19
- package/package.json +1 -1
- package/templates/applications/dataplane/application.yaml +2 -1
- package/templates/applications/dataplane/env.template +36 -1
- package/templates/applications/dataplane/rbac.yaml +4 -0
- package/templates/applications/miso-controller/env.template +16 -0
- package/templates/external-system/README.md.hbs +116 -11
- package/templates/python/docker-compose.hbs +19 -18
- package/templates/typescript/docker-compose.hbs +18 -17
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Identity apply and sync commands
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const logger = require('../utils/logger');
|
|
10
|
+
const { formatBlockingError, formatSuccessLine } = require('../utils/cli-layout-chalk');
|
|
11
|
+
const {
|
|
12
|
+
resolveControllerAndAuth,
|
|
13
|
+
resolveEnvKey,
|
|
14
|
+
requireNonEmpty,
|
|
15
|
+
unwrapControllerData,
|
|
16
|
+
throwIfApiFailed,
|
|
17
|
+
assertSyncStatsOk
|
|
18
|
+
} = require('./identity-shared');
|
|
19
|
+
const { clearAuthCache } = require('../api/auth-cache.api');
|
|
20
|
+
const { fullSyncToDataplane } = require('../api/dataplane-sync.api');
|
|
21
|
+
const { runIdentityApply } = require('../identity/identity-apply-service');
|
|
22
|
+
|
|
23
|
+
function printJson(data) {
|
|
24
|
+
logger.log(JSON.stringify(data, null, 2));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {import('commander').Command} identityCmd
|
|
29
|
+
*/
|
|
30
|
+
function setupIdentityApplyCommand(identityCmd) {
|
|
31
|
+
identityCmd
|
|
32
|
+
.command('apply')
|
|
33
|
+
.description('Apply users/groups/memberships from a CSV file (users.csv shape)')
|
|
34
|
+
.requiredOption('--file <path>', 'CSV file path')
|
|
35
|
+
.option('-e, --env <key>', 'Environment for --sync')
|
|
36
|
+
.option('--filter-prefix <prefix>', 'Only rows whose Id starts with this prefix')
|
|
37
|
+
.option('--dry-run', 'Parse and plan only; no writes')
|
|
38
|
+
.option('--sync', 'Run dataplane full sync after apply')
|
|
39
|
+
.option('--purge-cache', 'Clear controller cache before sync')
|
|
40
|
+
.option('--allow-empty-sync', 'Do not fail when sync reports usersProcessed=0')
|
|
41
|
+
.option('--json', 'JSON summary')
|
|
42
|
+
.action(async(options) => {
|
|
43
|
+
try {
|
|
44
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
45
|
+
const envKey = options.sync ? await resolveEnvKey(options) : undefined;
|
|
46
|
+
const summary = await runIdentityApply(controllerUrl, authConfig, {
|
|
47
|
+
filePath: requireNonEmpty(options.file, 'file'),
|
|
48
|
+
filterPrefix: options.filterPrefix,
|
|
49
|
+
dryRun: Boolean(options.dryRun),
|
|
50
|
+
purgeCache: Boolean(options.purgeCache),
|
|
51
|
+
sync: Boolean(options.sync),
|
|
52
|
+
envKey,
|
|
53
|
+
allowEmptySync: Boolean(options.allowEmptySync)
|
|
54
|
+
});
|
|
55
|
+
if (options.json) {
|
|
56
|
+
printJson(summary);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
logger.log(formatSuccessLine('Identity apply finished'));
|
|
60
|
+
logger.log(
|
|
61
|
+
` groups: ${summary.groupsProcessed} processed (${summary.groupsCreated} created)`
|
|
62
|
+
);
|
|
63
|
+
logger.log(` users: ${summary.usersProcessed} processed (${summary.usersCreated} created)`);
|
|
64
|
+
logger.log(
|
|
65
|
+
` memberships: ${summary.membershipsProcessed} processed (${summary.membershipsCreated} created, ${summary.membershipsSkipped} skipped)`
|
|
66
|
+
);
|
|
67
|
+
if (summary.syncStats) {
|
|
68
|
+
logger.log(
|
|
69
|
+
` sync: usersProcessed=${summary.syncStats.usersProcessed ?? 0} groupsProcessed=${summary.syncStats.groupsProcessed ?? 0}`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
} catch (e) {
|
|
73
|
+
logger.error(formatBlockingError(e.message));
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @param {import('commander').Command} identityCmd
|
|
81
|
+
*/
|
|
82
|
+
function setupIdentitySyncCommand(identityCmd) {
|
|
83
|
+
identityCmd
|
|
84
|
+
.command('sync')
|
|
85
|
+
.description('Full identity sync from controller to dataplane')
|
|
86
|
+
.option('-e, --env <key>', 'Environment key')
|
|
87
|
+
.option('--purge-cache', 'Clear controller cache before sync')
|
|
88
|
+
.option('--allow-empty-sync', 'Do not fail when usersProcessed=0')
|
|
89
|
+
.option('--json', 'JSON stats')
|
|
90
|
+
.action(async(options) => runIdentitySyncAction(options));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @param {Object} options
|
|
95
|
+
* @returns {Promise<void>}
|
|
96
|
+
*/
|
|
97
|
+
async function maybePurgeCache(controllerUrl, authConfig, purgeCache) {
|
|
98
|
+
if (!purgeCache) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const cacheRes = await clearAuthCache(controllerUrl, authConfig);
|
|
102
|
+
throwIfApiFailed(cacheRes);
|
|
103
|
+
logger.log(formatSuccessLine('Controller cache cleared'));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function runIdentitySyncAction(options) {
|
|
107
|
+
try {
|
|
108
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
109
|
+
const envKey = await resolveEnvKey(options);
|
|
110
|
+
await maybePurgeCache(controllerUrl, authConfig, options.purgeCache);
|
|
111
|
+
const res = await fullSyncToDataplane(controllerUrl, authConfig, envKey);
|
|
112
|
+
throwIfApiFailed(res);
|
|
113
|
+
const stats = unwrapControllerData(res);
|
|
114
|
+
assertSyncStatsOk(stats, { allowEmptySync: options.allowEmptySync });
|
|
115
|
+
if (options.json) {
|
|
116
|
+
printJson(stats);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
logger.log(formatSuccessLine(`Identity synced to dataplane (${envKey})`));
|
|
120
|
+
logger.log(` usersProcessed=${stats?.usersProcessed ?? 0}`);
|
|
121
|
+
logger.log(` groupsProcessed=${stats?.groupsProcessed ?? 0}`);
|
|
122
|
+
} catch (e) {
|
|
123
|
+
logger.error(formatBlockingError(e.message));
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
module.exports = {
|
|
129
|
+
setupIdentityApplyCommand,
|
|
130
|
+
setupIdentitySyncCommand
|
|
131
|
+
};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Identity group subcommands
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const logger = require('../utils/logger');
|
|
10
|
+
const { formatBlockingError, formatSuccessLine, headerKeyValue } = require('../utils/cli-layout-chalk');
|
|
11
|
+
const {
|
|
12
|
+
resolveControllerAndAuth,
|
|
13
|
+
requireNonEmpty,
|
|
14
|
+
unwrapControllerData,
|
|
15
|
+
throwIfApiFailed
|
|
16
|
+
} = require('./identity-shared');
|
|
17
|
+
const { listGroups, getGroup, createGroup, listGroupMembers } = require('../api/groups.api');
|
|
18
|
+
|
|
19
|
+
function printJson(data) {
|
|
20
|
+
logger.log(JSON.stringify(data, null, 2));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function registerGroupCreate(groupCmd) {
|
|
24
|
+
groupCmd
|
|
25
|
+
.command('create')
|
|
26
|
+
.description('Create a group')
|
|
27
|
+
.requiredOption('--name <name>', 'Group name (unique key)')
|
|
28
|
+
.requiredOption('--display-name <name>', 'Display name')
|
|
29
|
+
.option('--description <text>', 'Description')
|
|
30
|
+
.action(async(options) => {
|
|
31
|
+
try {
|
|
32
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
33
|
+
const res = await createGroup(controllerUrl, authConfig, {
|
|
34
|
+
name: requireNonEmpty(options.name, 'name'),
|
|
35
|
+
displayName: requireNonEmpty(options.displayName, 'display-name'),
|
|
36
|
+
description: options.description
|
|
37
|
+
});
|
|
38
|
+
throwIfApiFailed(res);
|
|
39
|
+
const row = unwrapControllerData(res);
|
|
40
|
+
logger.log(formatSuccessLine(`Group created: ${row.id} (${row.name})`));
|
|
41
|
+
} catch (e) {
|
|
42
|
+
logger.error(formatBlockingError(e.message));
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function registerGroupList(groupCmd) {
|
|
49
|
+
groupCmd
|
|
50
|
+
.command('list')
|
|
51
|
+
.description('List groups')
|
|
52
|
+
.option('--page <n>', 'Page', (v) => parseInt(v, 10))
|
|
53
|
+
.option('--page-size <n>', 'Page size', (v) => parseInt(v, 10))
|
|
54
|
+
.option('--search <text>', 'Search groups')
|
|
55
|
+
.option('--json', 'JSON output')
|
|
56
|
+
.action(async(options) => {
|
|
57
|
+
try {
|
|
58
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
59
|
+
const res = await listGroups(controllerUrl, authConfig, {
|
|
60
|
+
page: options.page,
|
|
61
|
+
pageSize: options.pageSize,
|
|
62
|
+
search: options.search
|
|
63
|
+
});
|
|
64
|
+
throwIfApiFailed(res);
|
|
65
|
+
const data = unwrapControllerData(res);
|
|
66
|
+
if (options.json) {
|
|
67
|
+
printJson(data);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const items = Array.isArray(data) ? data : data?.data || [];
|
|
71
|
+
items.forEach((g) => logger.log(` ${g.id} ${g.name}`));
|
|
72
|
+
logger.log('');
|
|
73
|
+
} catch (e) {
|
|
74
|
+
logger.error(formatBlockingError(e.message));
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function registerGroupGet(groupCmd) {
|
|
81
|
+
groupCmd
|
|
82
|
+
.command('get <idOrName>')
|
|
83
|
+
.description('Get group by id or name')
|
|
84
|
+
.option('--json', 'JSON output')
|
|
85
|
+
.action(async(idOrName, options) => {
|
|
86
|
+
try {
|
|
87
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
88
|
+
const res = await getGroup(controllerUrl, authConfig, requireNonEmpty(idOrName, 'idOrName'));
|
|
89
|
+
throwIfApiFailed(res);
|
|
90
|
+
const row = unwrapControllerData(res);
|
|
91
|
+
if (options.json) {
|
|
92
|
+
printJson(row);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
logger.log(headerKeyValue('Id:', row.id));
|
|
96
|
+
logger.log(headerKeyValue('Name:', row.name));
|
|
97
|
+
logger.log('');
|
|
98
|
+
} catch (e) {
|
|
99
|
+
logger.error(formatBlockingError(e.message));
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function registerGroupMembers(groupCmd) {
|
|
106
|
+
groupCmd
|
|
107
|
+
.command('members <idOrName>')
|
|
108
|
+
.description('List members of a group')
|
|
109
|
+
.option('--json', 'JSON output')
|
|
110
|
+
.action(async(idOrName, options) => {
|
|
111
|
+
try {
|
|
112
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
113
|
+
const res = await listGroupMembers(
|
|
114
|
+
controllerUrl,
|
|
115
|
+
authConfig,
|
|
116
|
+
requireNonEmpty(idOrName, 'idOrName')
|
|
117
|
+
);
|
|
118
|
+
throwIfApiFailed(res);
|
|
119
|
+
const data = unwrapControllerData(res);
|
|
120
|
+
if (options.json) {
|
|
121
|
+
printJson(data);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const items = Array.isArray(data) ? data : data?.data || [];
|
|
125
|
+
items.forEach((m) => logger.log(` ${m.userId || m.id} ${m.email || ''}`));
|
|
126
|
+
logger.log('');
|
|
127
|
+
} catch (e) {
|
|
128
|
+
logger.error(formatBlockingError(e.message));
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* @param {import('commander').Command} groupCmd
|
|
136
|
+
*/
|
|
137
|
+
function setupIdentityGroupCommands(groupCmd) {
|
|
138
|
+
registerGroupCreate(groupCmd);
|
|
139
|
+
registerGroupList(groupCmd);
|
|
140
|
+
registerGroupGet(groupCmd);
|
|
141
|
+
registerGroupMembers(groupCmd);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
module.exports = {
|
|
145
|
+
setupIdentityGroupCommands
|
|
146
|
+
};
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Identity membership, role, and cache subcommands
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const logger = require('../utils/logger');
|
|
10
|
+
const { formatBlockingError, formatSuccessLine } = require('../utils/cli-layout-chalk');
|
|
11
|
+
const {
|
|
12
|
+
resolveControllerAndAuth,
|
|
13
|
+
resolveEnvKey,
|
|
14
|
+
requireNonEmpty,
|
|
15
|
+
parseGroupsList,
|
|
16
|
+
unwrapControllerData,
|
|
17
|
+
throwIfApiFailed
|
|
18
|
+
} = require('./identity-shared');
|
|
19
|
+
const { addUserToGroup, removeUserFromGroup } = require('../api/user-groups.api');
|
|
20
|
+
const { listEnvironmentRoles, updateRoleGroups } = require('../api/environments.api');
|
|
21
|
+
const { clearAuthCache, invalidateAuthCache } = require('../api/auth-cache.api');
|
|
22
|
+
|
|
23
|
+
function printJson(data) {
|
|
24
|
+
logger.log(JSON.stringify(data, null, 2));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {import('commander').Command} memCmd
|
|
29
|
+
*/
|
|
30
|
+
function setupIdentityMembershipCommands(memCmd) {
|
|
31
|
+
memCmd
|
|
32
|
+
.command('add <userId> <groupId>')
|
|
33
|
+
.description('Add user to group (controller ids)')
|
|
34
|
+
.action(async(userId, groupId) => {
|
|
35
|
+
try {
|
|
36
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
37
|
+
const res = await addUserToGroup(
|
|
38
|
+
controllerUrl,
|
|
39
|
+
authConfig,
|
|
40
|
+
requireNonEmpty(userId, 'userId'),
|
|
41
|
+
requireNonEmpty(groupId, 'groupId'),
|
|
42
|
+
{}
|
|
43
|
+
);
|
|
44
|
+
throwIfApiFailed(res);
|
|
45
|
+
logger.log(formatSuccessLine(`Membership created: ${userId} → ${groupId}`));
|
|
46
|
+
} catch (e) {
|
|
47
|
+
logger.error(formatBlockingError(e.message));
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
memCmd
|
|
53
|
+
.command('remove <userId> <groupId>')
|
|
54
|
+
.description('Remove user from group')
|
|
55
|
+
.action(async(userId, groupId) => {
|
|
56
|
+
try {
|
|
57
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
58
|
+
const res = await removeUserFromGroup(
|
|
59
|
+
controllerUrl,
|
|
60
|
+
authConfig,
|
|
61
|
+
requireNonEmpty(userId, 'userId'),
|
|
62
|
+
requireNonEmpty(groupId, 'groupId')
|
|
63
|
+
);
|
|
64
|
+
throwIfApiFailed(res);
|
|
65
|
+
logger.log(formatSuccessLine(`Membership removed: ${userId} → ${groupId}`));
|
|
66
|
+
} catch (e) {
|
|
67
|
+
logger.error(formatBlockingError(e.message));
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @param {import('commander').Command} roleCmd
|
|
75
|
+
*/
|
|
76
|
+
function setupIdentityRoleCommands(roleCmd) {
|
|
77
|
+
roleCmd
|
|
78
|
+
.command('list')
|
|
79
|
+
.description('List roles and group mappings for an environment')
|
|
80
|
+
.requiredOption('-e, --env <key>', 'Environment key (dev, tst, pro)')
|
|
81
|
+
.option('--json', 'JSON output')
|
|
82
|
+
.action(async(options) => {
|
|
83
|
+
try {
|
|
84
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
85
|
+
const envKey = await resolveEnvKey(options);
|
|
86
|
+
const res = await listEnvironmentRoles(controllerUrl, envKey, authConfig);
|
|
87
|
+
throwIfApiFailed(res);
|
|
88
|
+
const data = unwrapControllerData(res);
|
|
89
|
+
if (options.json) {
|
|
90
|
+
printJson(data);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const items = Array.isArray(data) ? data : data?.data || data?.roles || [];
|
|
94
|
+
(items || []).forEach((r) => {
|
|
95
|
+
const groups = Array.isArray(r.groups) ? r.groups.join(', ') : '';
|
|
96
|
+
logger.log(` ${r.value || r.role || r.name} → ${groups || '—'}`);
|
|
97
|
+
});
|
|
98
|
+
logger.log('');
|
|
99
|
+
} catch (e) {
|
|
100
|
+
logger.error(formatBlockingError(e.message));
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
roleCmd
|
|
106
|
+
.command('set-groups <roleValue>')
|
|
107
|
+
.description('Map role to groups in an environment')
|
|
108
|
+
.requiredOption('-e, --env <key>', 'Environment key')
|
|
109
|
+
.requiredOption('--groups <names>', 'Comma-separated group names')
|
|
110
|
+
.action(async(roleValue, options) => {
|
|
111
|
+
try {
|
|
112
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
113
|
+
const envKey = await resolveEnvKey(options);
|
|
114
|
+
const groups = parseGroupsList(options.groups);
|
|
115
|
+
const res = await updateRoleGroups(
|
|
116
|
+
controllerUrl,
|
|
117
|
+
envKey,
|
|
118
|
+
requireNonEmpty(roleValue, 'roleValue'),
|
|
119
|
+
authConfig,
|
|
120
|
+
groups
|
|
121
|
+
);
|
|
122
|
+
throwIfApiFailed(res);
|
|
123
|
+
logger.log(formatSuccessLine(`Role ${roleValue} mapped to ${groups.length} group(s) in ${envKey}`));
|
|
124
|
+
} catch (e) {
|
|
125
|
+
logger.error(formatBlockingError(e.message));
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* @param {import('commander').Command} cacheCmd
|
|
133
|
+
*/
|
|
134
|
+
function setupIdentityCacheCommands(cacheCmd) {
|
|
135
|
+
cacheCmd
|
|
136
|
+
.command('clear')
|
|
137
|
+
.description('Clear all controller auth/RBAC cache entries')
|
|
138
|
+
.action(async() => {
|
|
139
|
+
try {
|
|
140
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
141
|
+
const res = await clearAuthCache(controllerUrl, authConfig);
|
|
142
|
+
throwIfApiFailed(res);
|
|
143
|
+
logger.log(formatSuccessLine('Controller cache cleared'));
|
|
144
|
+
} catch (e) {
|
|
145
|
+
logger.error(formatBlockingError(e.message));
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
cacheCmd
|
|
151
|
+
.command('invalidate')
|
|
152
|
+
.description('Invalidate controller cache entries by pattern')
|
|
153
|
+
.requiredOption('--pattern <pattern>', 'Cache key pattern (e.g. permissions:*)')
|
|
154
|
+
.action(async(options) => {
|
|
155
|
+
try {
|
|
156
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
157
|
+
const res = await invalidateAuthCache(
|
|
158
|
+
controllerUrl,
|
|
159
|
+
authConfig,
|
|
160
|
+
requireNonEmpty(options.pattern, 'pattern')
|
|
161
|
+
);
|
|
162
|
+
throwIfApiFailed(res);
|
|
163
|
+
logger.log(formatSuccessLine(`Cache invalidated for pattern: ${options.pattern}`));
|
|
164
|
+
} catch (e) {
|
|
165
|
+
logger.error(formatBlockingError(e.message));
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
module.exports = {
|
|
172
|
+
setupIdentityMembershipCommands,
|
|
173
|
+
setupIdentityRoleCommands,
|
|
174
|
+
setupIdentityCacheCommands
|
|
175
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Shared auth/helpers for identity CLI commands
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
10
|
+
const { normalizeControllerUrl, resolveEnvironment } = require('../core/config');
|
|
11
|
+
const { getOrRefreshDeviceToken } = require('../utils/token-manager');
|
|
12
|
+
const {
|
|
13
|
+
unwrapControllerData,
|
|
14
|
+
throwIfApiFailed,
|
|
15
|
+
assertSyncStatsOk,
|
|
16
|
+
apiErrorMessage
|
|
17
|
+
} = require('../identity/identity-apply-core');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @returns {Promise<{ controllerUrl: string, authConfig: Object }>}
|
|
21
|
+
*/
|
|
22
|
+
async function resolveControllerAndAuth() {
|
|
23
|
+
const controllerUrl = await resolveControllerUrl();
|
|
24
|
+
if (!controllerUrl) {
|
|
25
|
+
throw new Error('Controller URL is required. Run "aifabrix login" first.');
|
|
26
|
+
}
|
|
27
|
+
const normalized = normalizeControllerUrl(controllerUrl);
|
|
28
|
+
const deviceToken = await getOrRefreshDeviceToken(normalized);
|
|
29
|
+
if (!deviceToken || !deviceToken.token) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`Not authenticated for controller: ${controllerUrl}. Run "aifabrix login" and try again.`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
controllerUrl: deviceToken.controller || normalized,
|
|
36
|
+
authConfig: { type: 'bearer', token: deviceToken.token }
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param {Object} [options]
|
|
42
|
+
* @returns {Promise<string>}
|
|
43
|
+
*/
|
|
44
|
+
async function resolveEnvKey(options = {}) {
|
|
45
|
+
const fromFlag = options.env || options.environment;
|
|
46
|
+
if (fromFlag) {
|
|
47
|
+
return String(fromFlag).trim();
|
|
48
|
+
}
|
|
49
|
+
const env = await resolveEnvironment();
|
|
50
|
+
return env || 'dev';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param {string} raw
|
|
55
|
+
* @param {string} label
|
|
56
|
+
* @returns {string}
|
|
57
|
+
*/
|
|
58
|
+
function requireNonEmpty(raw, label) {
|
|
59
|
+
const s = String(raw || '').trim();
|
|
60
|
+
if (!s) {
|
|
61
|
+
throw new Error(`${label} is required.`);
|
|
62
|
+
}
|
|
63
|
+
return s;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @param {string} groupsArg
|
|
68
|
+
* @returns {string[]}
|
|
69
|
+
*/
|
|
70
|
+
function parseGroupsList(groupsArg) {
|
|
71
|
+
const raw = String(groupsArg || '').trim();
|
|
72
|
+
if (!raw) {
|
|
73
|
+
throw new Error('--groups is required (comma-separated group names).');
|
|
74
|
+
}
|
|
75
|
+
const parts = raw
|
|
76
|
+
.split(',')
|
|
77
|
+
.map((g) => g.trim())
|
|
78
|
+
.filter(Boolean);
|
|
79
|
+
if (parts.length === 0) {
|
|
80
|
+
throw new Error('At least one group name is required in --groups.');
|
|
81
|
+
}
|
|
82
|
+
return parts;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = {
|
|
86
|
+
resolveControllerAndAuth,
|
|
87
|
+
resolveEnvKey,
|
|
88
|
+
requireNonEmpty,
|
|
89
|
+
parseGroupsList,
|
|
90
|
+
unwrapControllerData,
|
|
91
|
+
throwIfApiFailed,
|
|
92
|
+
assertSyncStatsOk,
|
|
93
|
+
apiErrorMessage
|
|
94
|
+
};
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Identity user subcommands
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
const logger = require('../utils/logger');
|
|
11
|
+
const { formatBlockingError, formatSuccessLine, headerKeyValue } = require('../utils/cli-layout-chalk');
|
|
12
|
+
const {
|
|
13
|
+
resolveControllerAndAuth,
|
|
14
|
+
requireNonEmpty,
|
|
15
|
+
unwrapControllerData,
|
|
16
|
+
throwIfApiFailed
|
|
17
|
+
} = require('./identity-shared');
|
|
18
|
+
const {
|
|
19
|
+
listUsers,
|
|
20
|
+
getUser,
|
|
21
|
+
createUser,
|
|
22
|
+
listUserGroups
|
|
23
|
+
} = require('../api/users.api');
|
|
24
|
+
|
|
25
|
+
function printJson(data) {
|
|
26
|
+
logger.log(JSON.stringify(data, null, 2));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function registerUserCreate(userCmd) {
|
|
30
|
+
userCmd
|
|
31
|
+
.command('create')
|
|
32
|
+
.description('Create a user on the controller')
|
|
33
|
+
.requiredOption('--email <email>', 'User email')
|
|
34
|
+
.option('--first-name <name>', 'First name')
|
|
35
|
+
.option('--last-name <name>', 'Last name')
|
|
36
|
+
.option('--display-name <name>', 'Display name')
|
|
37
|
+
.option('--username <name>', 'Username')
|
|
38
|
+
.action(async(options) => {
|
|
39
|
+
try {
|
|
40
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
41
|
+
const res = await createUser(controllerUrl, authConfig, {
|
|
42
|
+
email: requireNonEmpty(options.email, 'email'),
|
|
43
|
+
firstName: options.firstName,
|
|
44
|
+
lastName: options.lastName,
|
|
45
|
+
displayName: options.displayName,
|
|
46
|
+
username: options.username,
|
|
47
|
+
status: 'active'
|
|
48
|
+
});
|
|
49
|
+
throwIfApiFailed(res);
|
|
50
|
+
const row = unwrapControllerData(res);
|
|
51
|
+
logger.log(formatSuccessLine(`User created: ${row.id} (${row.email})`));
|
|
52
|
+
} catch (e) {
|
|
53
|
+
logger.error(formatBlockingError(e.message));
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function registerUserList(userCmd) {
|
|
60
|
+
userCmd
|
|
61
|
+
.command('list')
|
|
62
|
+
.description('List users')
|
|
63
|
+
.option('--page <n>', 'Page', (v) => parseInt(v, 10))
|
|
64
|
+
.option('--page-size <n>', 'Page size', (v) => parseInt(v, 10))
|
|
65
|
+
.option('--search <text>', 'Search users')
|
|
66
|
+
.option('--json', 'JSON output')
|
|
67
|
+
.action(async(options) => {
|
|
68
|
+
try {
|
|
69
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
70
|
+
const res = await listUsers(controllerUrl, authConfig, {
|
|
71
|
+
page: options.page,
|
|
72
|
+
pageSize: options.pageSize,
|
|
73
|
+
search: options.search
|
|
74
|
+
});
|
|
75
|
+
throwIfApiFailed(res);
|
|
76
|
+
const data = unwrapControllerData(res);
|
|
77
|
+
if (options.json) {
|
|
78
|
+
printJson(data);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const items = Array.isArray(data) ? data : data?.data || [];
|
|
82
|
+
logger.log(chalk.bold(`\nUsers (${items.length} shown):\n`));
|
|
83
|
+
items.forEach((u) => logger.log(` ${u.id} ${u.email}`));
|
|
84
|
+
logger.log('');
|
|
85
|
+
} catch (e) {
|
|
86
|
+
logger.error(formatBlockingError(e.message));
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function registerUserGet(userCmd) {
|
|
93
|
+
userCmd
|
|
94
|
+
.command('get <idOrEmail>')
|
|
95
|
+
.description('Get user by id or email')
|
|
96
|
+
.option('--json', 'JSON output')
|
|
97
|
+
.action(async(idOrEmail, options) => {
|
|
98
|
+
try {
|
|
99
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
100
|
+
const res = await getUser(controllerUrl, authConfig, requireNonEmpty(idOrEmail, 'idOrEmail'));
|
|
101
|
+
throwIfApiFailed(res);
|
|
102
|
+
const row = unwrapControllerData(res);
|
|
103
|
+
if (options.json) {
|
|
104
|
+
printJson(row);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
logger.log(headerKeyValue('Id:', row.id));
|
|
108
|
+
logger.log(headerKeyValue('Email:', row.email));
|
|
109
|
+
logger.log('');
|
|
110
|
+
} catch (e) {
|
|
111
|
+
logger.error(formatBlockingError(e.message));
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function registerUserGroups(userCmd) {
|
|
118
|
+
userCmd
|
|
119
|
+
.command('groups <idOrEmail>')
|
|
120
|
+
.description('List groups for a user')
|
|
121
|
+
.option('--json', 'JSON output')
|
|
122
|
+
.action(async(idOrEmail, options) => {
|
|
123
|
+
try {
|
|
124
|
+
const { controllerUrl, authConfig } = await resolveControllerAndAuth();
|
|
125
|
+
const res = await listUserGroups(
|
|
126
|
+
controllerUrl,
|
|
127
|
+
authConfig,
|
|
128
|
+
requireNonEmpty(idOrEmail, 'idOrEmail')
|
|
129
|
+
);
|
|
130
|
+
throwIfApiFailed(res);
|
|
131
|
+
const data = unwrapControllerData(res);
|
|
132
|
+
if (options.json) {
|
|
133
|
+
printJson(data);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const items = Array.isArray(data) ? data : data?.data || [];
|
|
137
|
+
items.forEach((g) => logger.log(` ${g.id || g.groupId} ${g.name || ''}`));
|
|
138
|
+
logger.log('');
|
|
139
|
+
} catch (e) {
|
|
140
|
+
logger.error(formatBlockingError(e.message));
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* @param {import('commander').Command} userCmd
|
|
148
|
+
*/
|
|
149
|
+
function setupIdentityUserCommands(userCmd) {
|
|
150
|
+
registerUserCreate(userCmd);
|
|
151
|
+
registerUserList(userCmd);
|
|
152
|
+
registerUserGet(userCmd);
|
|
153
|
+
registerUserGroups(userCmd);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
module.exports = {
|
|
157
|
+
setupIdentityUserCommands
|
|
158
|
+
};
|