@aifabrix/builder 2.44.6 → 2.45.5
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 +7 -3
- package/.cursor/rules/docs-rules.mdc +3 -1
- package/integration/hubspot-test/test-artifacts/wizard-hubspot-credential-real.yaml +1 -1
- package/integration/hubspot-test/test-artifacts/wizard-hubspot-env-vars.yaml +1 -1
- package/integration/hubspot-test/wizard-hubspot-e2e.yaml +1 -1
- package/jest.projects.js +100 -1
- package/lib/api/agent-metadata-validation.api.js +93 -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-scenarios.api.js +39 -0
- package/lib/api/groups.api.js +79 -0
- package/lib/api/index.js +45 -2
- package/lib/api/pipeline.api.js +4 -4
- package/lib/api/protection.api.js +157 -0
- package/lib/api/records-bulk.api.js +30 -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 +3 -3
- package/lib/api/wizard-platform.api.js +3 -3
- package/lib/api/wizard.api.js +21 -21
- package/lib/app/deploy.js +34 -19
- package/lib/app/helpers.js +3 -3
- package/lib/app/index.js +3 -3
- package/lib/app/register.js +7 -6
- package/lib/app/restart-display.js +52 -21
- package/lib/app/rotate-secret.js +7 -6
- package/lib/app/run-helpers.js +15 -8
- package/lib/app/run.js +57 -9
- package/lib/app/show-display.js +7 -0
- package/lib/app/show.js +87 -5
- package/lib/build/index.js +9 -5
- package/lib/cli/index.js +4 -2
- package/lib/cli/infra-guided.js +62 -28
- package/lib/cli/installation-log-command.js +73 -0
- package/lib/cli/setup-app.help.js +50 -2
- package/lib/cli/setup-app.js +44 -25
- package/lib/cli/setup-app.test-commands.js +80 -21
- package/lib/cli/setup-auth.js +124 -52
- package/lib/cli/setup-external-system.js +37 -3
- package/lib/cli/setup-infra-up-dataplane-action.js +111 -0
- package/lib/cli/setup-infra-up-platform-action.js +131 -0
- package/lib/cli/setup-infra.js +60 -119
- package/lib/cli/setup-platform.js +1 -1
- package/lib/cli/setup-utility-help-after.js +45 -0
- package/lib/cli/setup-utility-resolve.js +132 -0
- package/lib/cli/setup-utility.js +148 -116
- package/lib/commands/app-logs.js +81 -33
- package/lib/commands/auth-config.js +116 -18
- package/lib/commands/auth-status-dataplane-version.js +231 -0
- package/lib/commands/auth-status-display.js +94 -0
- package/lib/commands/auth-status.js +70 -6
- 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 +114 -0
- package/lib/commands/datasource-unified-test-cli.js +25 -17
- package/lib/commands/datasource-unified-test-cli.options.js +18 -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/dimension-value.js +43 -13
- package/lib/commands/dimension.js +38 -7
- 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/login.js +46 -16
- package/lib/commands/protection-cli-leaves.js +286 -0
- package/lib/commands/protection.js +50 -0
- package/lib/commands/repair.js +13 -3
- package/lib/commands/setup-modes.js +19 -6
- package/lib/commands/setup-prompts.js +41 -8
- package/lib/commands/setup.js +114 -9
- package/lib/commands/teardown.js +54 -5
- package/lib/commands/test-e2e-external.js +40 -9
- package/lib/commands/test-governance-cli-options.js +47 -0
- package/lib/commands/test-governance-command-action.js +55 -0
- package/lib/commands/test-governance-external.js +88 -0
- package/lib/commands/test-trust-cli-options.js +32 -0
- package/lib/commands/test-trust-command-action.js +118 -0
- package/lib/commands/test-trust-external.js +127 -0
- package/lib/commands/up-common.js +48 -14
- package/lib/commands/up-dataplane.js +21 -18
- package/lib/commands/up-miso.js +12 -8
- package/lib/commands/upload.js +38 -15
- package/lib/commands/wizard-core.js +3 -0
- package/lib/core/audit-logger.js +1 -34
- package/lib/core/config-admin-email.js +56 -0
- package/lib/core/config-device-dataplane.js +185 -0
- package/lib/core/config-normalize.js +60 -0
- package/lib/core/config-registered-controller-urls.js +54 -0
- package/lib/core/config.js +33 -50
- package/lib/core/secrets-ensure-infra.js +1 -1
- package/lib/core/secrets-env-content.js +86 -90
- package/lib/core/secrets-env-declarative-expand.js +170 -0
- package/lib/core/secrets-env-write.js +2 -0
- package/lib/core/secrets-load.js +106 -102
- 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 +93 -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 +103 -0
- package/lib/datasource/audit-evidence-extract.js +177 -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 +177 -0
- package/lib/datasource/audit-evidence-verify.js +108 -0
- package/lib/datasource/bulk-loader-run-batches.js +88 -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 +92 -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 +26 -2
- package/lib/datasource/unified-validation-run-resolve.js +45 -12
- package/lib/datasource/unified-validation-run.js +43 -17
- package/lib/datasource/validate.js +37 -20
- package/lib/external-system/deploy.js +5 -1
- package/lib/external-system/test-system-level.js +25 -4
- package/lib/external-system/test.js +3 -1
- package/lib/governance/governance-pack-loader.js +70 -0
- package/lib/governance/governance-run-display.js +119 -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/internal/fs-real-sync.js +29 -1
- package/lib/internal/node-fs.js +2 -0
- package/lib/protection/auth-context.js +45 -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 +4 -0
- package/lib/schema/external-datasource.schema.json +47 -13
- package/lib/schema/flag-map-validation-run.json +2 -2
- package/lib/schema/infra.parameter.yaml +10 -0
- package/lib/schema/protection.schema.json +374 -0
- package/lib/schema/type/credential-field-registry.json +636 -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/app-config-resolver.js +24 -1
- package/lib/utils/applications-config-defaults.js +206 -0
- package/lib/utils/auth-config-validator.js +2 -12
- package/lib/utils/bash-secret-env.js +1 -1
- package/lib/utils/cli-exit-codes.js +30 -0
- package/lib/utils/cli-sync-options.js +33 -0
- package/lib/utils/compose-generate-docker-compose.js +111 -6
- package/lib/utils/compose-generator.js +17 -8
- package/lib/utils/controller-url.js +50 -7
- package/lib/utils/credential-push-ui.js +90 -0
- package/lib/utils/dataplane-cli-version-gate.js +202 -0
- package/lib/utils/dataplane-cli-version-help.js +74 -0
- 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/env-copy.js +99 -14
- package/lib/utils/env-template.js +5 -1
- package/lib/utils/health-check-url.js +18 -15
- package/lib/utils/health-check.js +7 -5
- package/lib/utils/help-builder.js +30 -24
- package/lib/utils/infra-optional-service-flags.js +69 -0
- package/lib/utils/installation-log-core.js +282 -0
- package/lib/utils/installation-log-record.js +237 -0
- package/lib/utils/installation-log.js +123 -0
- package/lib/utils/log-redaction.js +105 -0
- package/lib/utils/manifest-location.js +164 -0
- package/lib/utils/manifest-source-emit.js +184 -0
- package/lib/utils/paths.js +238 -89
- package/lib/utils/remote-secrets-loader.js +7 -1
- package/lib/utils/run-cli-flags.js +29 -0
- package/lib/utils/secrets-canonical.js +10 -3
- package/lib/utils/secrets-path.js +3 -4
- package/lib/utils/secrets-utils.js +20 -10
- package/lib/utils/semver-compare.js +127 -0
- package/lib/utils/system-builder-root.js +10 -2
- package/lib/utils/upload-pipeline-ui.js +91 -0
- package/lib/utils/url-declarative-public-base.js +80 -12
- package/lib/utils/url-declarative-resolve-build-urls.js +238 -0
- package/lib/utils/url-declarative-resolve-build.js +24 -393
- package/lib/utils/url-declarative-resolve-expand-token.js +189 -0
- package/lib/utils/url-declarative-resolve-load-doc.js +12 -3
- package/lib/utils/url-declarative-resolve-surface-state.js +102 -0
- package/lib/utils/url-declarative-resolve.js +47 -7
- package/lib/utils/url-declarative-runtime-base-path.js +21 -1
- package/lib/utils/urls-local-registry-scan.js +103 -0
- package/lib/utils/urls-local-registry.js +161 -90
- package/lib/utils/validation-poll-ui.js +14 -7
- package/lib/validation/wizard-config-validator.js +58 -19
- package/package.json +3 -1
- package/templates/applications/dataplane/application.yaml +4 -0
- package/templates/applications/dataplane/env.template +13 -1
- package/templates/applications/dataplane/rbac.yaml +4 -0
- package/templates/applications/miso-controller/application.yaml +2 -0
- package/templates/applications/miso-controller/env.template +27 -29
- package/templates/external-system/README.md.hbs +56 -8
- package/.npmrc.token +0 -1
|
@@ -4,40 +4,73 @@
|
|
|
4
4
|
* @version 2.0.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
const path = require('path');
|
|
8
7
|
const { getIntegrationPath } = require('../utils/paths');
|
|
9
8
|
const { resolveApplicationConfigPath } = require('../utils/app-config-resolver');
|
|
10
9
|
const { loadConfigFile } = require('../utils/config-format');
|
|
10
|
+
const {
|
|
11
|
+
datasourceIdentifierMatchesFile,
|
|
12
|
+
resolveDatasourceFilePath
|
|
13
|
+
} = require('./datasource-identifier');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {string} appPath
|
|
17
|
+
* @param {string} schemaBasePath
|
|
18
|
+
* @param {string[]} datasourceFiles
|
|
19
|
+
* @param {string} identifier
|
|
20
|
+
* @returns {string|null}
|
|
21
|
+
*/
|
|
22
|
+
function findDatasourceFileByIdentifier(appPath, schemaBasePath, datasourceFiles, identifier) {
|
|
23
|
+
const fsSync = require('fs');
|
|
24
|
+
for (const fileRef of datasourceFiles) {
|
|
25
|
+
if (!fileRef || typeof fileRef !== 'string') {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
const fullPath = resolveDatasourceFilePath(appPath, schemaBasePath, fileRef);
|
|
29
|
+
if (!fsSync.existsSync(fullPath)) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
const parsed = loadConfigFile(fullPath);
|
|
34
|
+
if (datasourceIdentifierMatchesFile(identifier, fileRef, parsed)) {
|
|
35
|
+
return fileRef;
|
|
36
|
+
}
|
|
37
|
+
} catch {
|
|
38
|
+
// skip unreadable or invalid files
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
11
43
|
|
|
12
44
|
/**
|
|
13
45
|
* @param {string} appKey
|
|
14
46
|
* @param {string} datasourceKey
|
|
15
47
|
* @param {Function} findDatasourceFileByKey - From integration-context
|
|
16
|
-
* @returns {{ datasource: Object, datasourcePath: string }}
|
|
48
|
+
* @returns {{ datasource: Object, datasourcePath: string, datasourceKey: string }}
|
|
17
49
|
*/
|
|
18
50
|
function loadDatasourceForApp(appKey, datasourceKey, findDatasourceFileByKey) {
|
|
19
51
|
const appPath = getIntegrationPath(appKey);
|
|
20
52
|
const config = loadConfigFile(resolveApplicationConfigPath(appPath));
|
|
21
53
|
const schemaBasePath = config.externalIntegration?.schemaBasePath || './';
|
|
22
54
|
const datasourceFiles = config.externalIntegration?.dataSources || [];
|
|
23
|
-
let datasourceFile =
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
55
|
+
let datasourceFile = findDatasourceFileByIdentifier(
|
|
56
|
+
appPath,
|
|
57
|
+
schemaBasePath,
|
|
58
|
+
datasourceFiles,
|
|
59
|
+
datasourceKey
|
|
60
|
+
);
|
|
27
61
|
if (!datasourceFile) {
|
|
28
62
|
datasourceFile = findDatasourceFileByKey(appPath, schemaBasePath, datasourceFiles, datasourceKey);
|
|
29
63
|
}
|
|
30
64
|
if (!datasourceFile) {
|
|
31
65
|
throw new Error(`Datasource '${datasourceKey}' not found in application config`);
|
|
32
66
|
}
|
|
33
|
-
const datasourcePath =
|
|
34
|
-
? path.join(schemaBasePath, datasourceFile)
|
|
35
|
-
: path.join(appPath, schemaBasePath, datasourceFile);
|
|
67
|
+
const datasourcePath = resolveDatasourceFilePath(appPath, schemaBasePath, datasourceFile);
|
|
36
68
|
const datasource = loadConfigFile(datasourcePath);
|
|
37
|
-
|
|
38
|
-
|
|
69
|
+
const canonicalKey = datasource.key;
|
|
70
|
+
if (!canonicalKey || typeof canonicalKey !== 'string') {
|
|
71
|
+
throw new Error(`Datasource file is missing a valid key: ${datasourcePath}`);
|
|
39
72
|
}
|
|
40
|
-
return { datasource, datasourcePath };
|
|
73
|
+
return { datasource, datasourcePath, datasourceKey: canonicalKey };
|
|
41
74
|
}
|
|
42
75
|
|
|
43
76
|
module.exports = { loadDatasourceForApp };
|
|
@@ -71,6 +71,40 @@ function loadDatasourceForAppExport(appKey, datasourceKey) {
|
|
|
71
71
|
return loadDatasourceForApp(appKey, datasourceKey, findDatasourceFileByKey);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
/**
|
|
75
|
+
* @async
|
|
76
|
+
* @param {string} appKey
|
|
77
|
+
* @param {Object} options
|
|
78
|
+
* @returns {Promise<{ authConfig: Object, dataplaneUrl: string }>}
|
|
79
|
+
*/
|
|
80
|
+
async function resolveValidationAuth(appKey, options) {
|
|
81
|
+
if (options.authConfig && (options.dataplaneUrl || options.dataplane)) {
|
|
82
|
+
return {
|
|
83
|
+
authConfig: options.authConfig,
|
|
84
|
+
dataplaneUrl: options.dataplaneUrl || options.dataplane
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const configObj = await getConfig();
|
|
88
|
+
const auth = await setupIntegrationTestAuth(appKey, options, configObj);
|
|
89
|
+
return { authConfig: auth.authConfig, dataplaneUrl: auth.dataplaneUrl };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @async
|
|
94
|
+
* @param {string} datasourceKey
|
|
95
|
+
* @param {Object} options
|
|
96
|
+
* @returns {Promise<Object>}
|
|
97
|
+
*/
|
|
98
|
+
async function prepareValidationRunContext(datasourceKey, options) {
|
|
99
|
+
const runType = options.runType || 'test';
|
|
100
|
+
const { appKey } = await resolveAppKeyForDatasource(datasourceKey, options.app);
|
|
101
|
+
const systemKey = await getSystemKeyFromAppKey(appKey);
|
|
102
|
+
const { datasource } = loadDatasourceForAppExport(appKey, datasourceKey);
|
|
103
|
+
const { authConfig, dataplaneUrl } = await resolveValidationAuth(appKey, options);
|
|
104
|
+
await maybeSyncDatasourceToDataplane({ options, authConfig, systemKey });
|
|
105
|
+
return { runType, systemKey, datasource, authConfig, dataplaneUrl };
|
|
106
|
+
}
|
|
107
|
+
|
|
74
108
|
/**
|
|
75
109
|
* Run unified validation for one persisted datasource.
|
|
76
110
|
* @async
|
|
@@ -83,41 +117,33 @@ async function runUnifiedDatasourceValidation(datasourceKey, options) {
|
|
|
83
117
|
if (!datasourceKey || typeof datasourceKey !== 'string') {
|
|
84
118
|
throw new Error('Datasource key is required');
|
|
85
119
|
}
|
|
86
|
-
const runType = options.runType || 'test';
|
|
87
|
-
const { appKey } = await resolveAppKeyForDatasource(datasourceKey, options.app);
|
|
88
|
-
const systemKey = await getSystemKeyFromAppKey(appKey);
|
|
89
|
-
const { datasource } = loadDatasourceForAppExport(appKey, datasourceKey);
|
|
90
|
-
|
|
91
|
-
const configObj = await getConfig();
|
|
92
|
-
const { authConfig, dataplaneUrl } = await setupIntegrationTestAuth(appKey, options, configObj);
|
|
93
|
-
|
|
94
|
-
await maybeSyncDatasourceToDataplane({ options, authConfig, systemKey });
|
|
95
120
|
|
|
121
|
+
const ctx = await prepareValidationRunContext(datasourceKey, options);
|
|
96
122
|
const useAsync = options.noAsync ? false : options.async !== false;
|
|
97
123
|
const timeoutMs = parseInt(String(options.timeout || '30000'), 10) || 30000;
|
|
98
|
-
|
|
99
124
|
const body = await buildUnifiedValidationBody({
|
|
100
|
-
systemKey,
|
|
125
|
+
systemKey: ctx.systemKey,
|
|
101
126
|
datasourceKey,
|
|
102
|
-
runType,
|
|
103
|
-
datasource,
|
|
127
|
+
runType: ctx.runType,
|
|
128
|
+
datasource: ctx.datasource,
|
|
104
129
|
payloadPath: options.payload,
|
|
105
130
|
useAsync,
|
|
106
131
|
options
|
|
107
132
|
});
|
|
108
133
|
|
|
109
134
|
const result = await postValidationRunAndOptionalPoll({
|
|
110
|
-
dataplaneUrl,
|
|
111
|
-
authConfig,
|
|
135
|
+
dataplaneUrl: ctx.dataplaneUrl,
|
|
136
|
+
authConfig: ctx.authConfig,
|
|
112
137
|
body,
|
|
113
138
|
timeoutMs,
|
|
114
139
|
useAsync,
|
|
115
140
|
noAsync: options.noAsync === true || options.async === false,
|
|
116
|
-
verbosePoll: options.verbose === true
|
|
141
|
+
verbosePoll: options.verbose === true,
|
|
142
|
+
pollSubject: datasourceKey
|
|
117
143
|
});
|
|
118
144
|
|
|
119
145
|
if (result && result.envelope) {
|
|
120
|
-
annotateMissingAutoRbacPermissions(result.envelope, datasourceKey, datasource);
|
|
146
|
+
annotateMissingAutoRbacPermissions(result.envelope, datasourceKey, ctx.datasource);
|
|
121
147
|
}
|
|
122
148
|
return result;
|
|
123
149
|
}
|
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
const path = require('path');
|
|
12
12
|
const fs = require('fs');
|
|
13
13
|
const pathsUtil = require('../utils/paths');
|
|
14
|
+
const {
|
|
15
|
+
datasourceIdentifierMatchesFile,
|
|
16
|
+
pickIntegrationAppForDatasourceIdentifier,
|
|
17
|
+
collectDatasourceFileRefsMatchingIdentifier
|
|
18
|
+
} = require('./datasource-identifier');
|
|
14
19
|
const { loadExternalDataSourceSchema } = require('../utils/schema-loader');
|
|
15
20
|
const { formatValidationErrors } = require('../utils/error-formatter');
|
|
16
21
|
const { validateFieldReferences } = require('./field-reference-validator');
|
|
@@ -46,16 +51,13 @@ function tryResolveAsExistingFile(identifier) {
|
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
/**
|
|
49
|
-
* Pick integration app folder for a datasource
|
|
54
|
+
* Pick integration app folder for a datasource identifier (longest matching prefix wins).
|
|
50
55
|
* @param {string} datasourceKey
|
|
51
56
|
* @param {string[]} appNames
|
|
52
57
|
* @returns {string|null}
|
|
53
58
|
*/
|
|
54
59
|
function pickIntegrationAppForDatasourceKey(datasourceKey, appNames) {
|
|
55
|
-
|
|
56
|
-
if (matches.length === 0) return null;
|
|
57
|
-
matches.sort((a, b) => b.length - a.length);
|
|
58
|
-
return matches[0];
|
|
60
|
+
return pickIntegrationAppForDatasourceIdentifier(datasourceKey, appNames);
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
/**
|
|
@@ -94,7 +96,7 @@ function collectJsonPathsMatchingDatasourceKey(dir, scanOrder, datasourceKey) {
|
|
|
94
96
|
} catch {
|
|
95
97
|
continue;
|
|
96
98
|
}
|
|
97
|
-
if (
|
|
99
|
+
if (datasourceIdentifierMatchesFile(datasourceKey, name, parsed)) {
|
|
98
100
|
hits.push(full);
|
|
99
101
|
}
|
|
100
102
|
}
|
|
@@ -102,21 +104,12 @@ function collectJsonPathsMatchingDatasourceKey(dir, scanOrder, datasourceKey) {
|
|
|
102
104
|
}
|
|
103
105
|
|
|
104
106
|
/**
|
|
105
|
-
*
|
|
107
|
+
* @param {string} appName
|
|
106
108
|
* @param {string} datasourceKey
|
|
107
|
-
* @returns {string}
|
|
109
|
+
* @returns {string}
|
|
108
110
|
* @throws {Error}
|
|
109
111
|
*/
|
|
110
|
-
function
|
|
111
|
-
const appNames = pathsUtil.listIntegrationAppNames();
|
|
112
|
-
const appName = pickIntegrationAppForDatasourceKey(datasourceKey, appNames);
|
|
113
|
-
if (!appName) {
|
|
114
|
-
throw new Error(
|
|
115
|
-
`No integration/<app>/ folder matches datasource key "${datasourceKey}". ` +
|
|
116
|
-
'Pass a path to the JSON file, or use a key whose prefix matches an app under integration/ ' +
|
|
117
|
-
'(e.g. app test-e2e-hubspot for key test-e2e-hubspot-users).'
|
|
118
|
-
);
|
|
119
|
-
}
|
|
112
|
+
function resolvePathFromIntegrationFolderScan(appName, datasourceKey) {
|
|
120
113
|
const dir = pathsUtil.getIntegrationPath(appName);
|
|
121
114
|
if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
|
|
122
115
|
throw new Error(`Integration folder not found: ${dir}`);
|
|
@@ -128,16 +121,40 @@ function resolvePathFromIntegrationDatasourceKey(datasourceKey) {
|
|
|
128
121
|
const hits = collectJsonPathsMatchingDatasourceKey(dir, scanOrder, datasourceKey);
|
|
129
122
|
if (hits.length === 0) {
|
|
130
123
|
throw new Error(
|
|
131
|
-
`No datasource JSON
|
|
124
|
+
`No datasource JSON matching "${datasourceKey}" under integration/${appName}/. ` +
|
|
132
125
|
`Checked: ${scanOrder.length ? scanOrder.join(', ') : '(no JSON candidates)'}`
|
|
133
126
|
);
|
|
134
127
|
}
|
|
135
128
|
if (hits.length > 1) {
|
|
136
|
-
throw new Error(`Ambiguous: multiple files
|
|
129
|
+
throw new Error(`Ambiguous: multiple files match "${datasourceKey}": ${hits.join(', ')}`);
|
|
137
130
|
}
|
|
138
131
|
return hits[0];
|
|
139
132
|
}
|
|
140
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Find integration JSON whose parsed `key` equals datasourceKey.
|
|
136
|
+
* @param {string} datasourceKey
|
|
137
|
+
* @returns {string} Absolute file path
|
|
138
|
+
* @throws {Error}
|
|
139
|
+
*/
|
|
140
|
+
function resolvePathFromIntegrationDatasourceKey(datasourceKey) {
|
|
141
|
+
const appNames = pathsUtil.listIntegrationAppNames();
|
|
142
|
+
const appName = pickIntegrationAppForDatasourceKey(datasourceKey, appNames);
|
|
143
|
+
if (!appName) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
`No integration/<app>/ folder matches datasource identifier "${datasourceKey}". ` +
|
|
146
|
+
'Pass a path to the JSON file, or use a datasource key or filename whose prefix matches an app under integration/ ' +
|
|
147
|
+
'(e.g. test-e2e-hubspot-companies or test-e2e-hubspot-datasource-companies for app test-e2e-hubspot).'
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
const manifestHits = collectDatasourceFileRefsMatchingIdentifier(appName, datasourceKey);
|
|
151
|
+
if (manifestHits.length === 1) {
|
|
152
|
+
const dir = pathsUtil.getIntegrationPath(appName);
|
|
153
|
+
return path.join(dir, manifestHits[0]);
|
|
154
|
+
}
|
|
155
|
+
return resolvePathFromIntegrationFolderScan(appName, datasourceKey);
|
|
156
|
+
}
|
|
157
|
+
|
|
141
158
|
/**
|
|
142
159
|
* @param {string} identifier - File path or datasource `key`
|
|
143
160
|
* @returns {string} Absolute path to JSON file
|
|
@@ -169,7 +169,7 @@ function logImmediateControllerDeploymentOutcome(deploymentOutcome) {
|
|
|
169
169
|
);
|
|
170
170
|
return;
|
|
171
171
|
}
|
|
172
|
-
logger.log(chalk.gray('
|
|
172
|
+
logger.log(chalk.gray(' Check the controller deployment job or logs for details.'));
|
|
173
173
|
}
|
|
174
174
|
|
|
175
175
|
async function executeDeployAndDisplay(manifest, controllerUrl, environment, authConfig, options) {
|
|
@@ -192,6 +192,10 @@ async function executeDeployAndDisplay(manifest, controllerUrl, environment, aut
|
|
|
192
192
|
const deploymentOutcome = parseControllerDeploymentOutcome(result);
|
|
193
193
|
logImmediateControllerDeploymentOutcome(deploymentOutcome);
|
|
194
194
|
|
|
195
|
+
if (!deploymentOutcome.ok) {
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
|
|
195
199
|
const ctx = await fetchDataplaneDeployReadiness(
|
|
196
200
|
controllerUrl,
|
|
197
201
|
environment,
|
|
@@ -16,7 +16,7 @@ const { getIntegrationPath } = require('../utils/paths');
|
|
|
16
16
|
const { runUnifiedDatasourceValidation } = require('../datasource/unified-validation-run');
|
|
17
17
|
|
|
18
18
|
async function runOneDatasourceKey(appName, datasourceKey, opts) {
|
|
19
|
-
const { environment, runType, debug, timeoutMs } = opts;
|
|
19
|
+
const { environment, runType, debug, timeoutMs, authConfig, dataplaneUrl } = opts;
|
|
20
20
|
try {
|
|
21
21
|
const uni = await runUnifiedDatasourceValidation(datasourceKey, {
|
|
22
22
|
app: appName,
|
|
@@ -26,7 +26,9 @@ async function runOneDatasourceKey(appName, datasourceKey, opts) {
|
|
|
26
26
|
verbose: false,
|
|
27
27
|
timeout: timeoutMs,
|
|
28
28
|
async: true,
|
|
29
|
-
noAsync: false
|
|
29
|
+
noAsync: false,
|
|
30
|
+
authConfig,
|
|
31
|
+
dataplaneUrl
|
|
30
32
|
});
|
|
31
33
|
|
|
32
34
|
if (uni.apiError) {
|
|
@@ -89,7 +91,17 @@ async function maybeWriteSystemRunLog(appName, systemKey, datasourceKeys, runTyp
|
|
|
89
91
|
* @param {number} [params.timeout] - Request timeout
|
|
90
92
|
* @returns {Promise<{success: boolean, datasourceResults: Object[]}>}
|
|
91
93
|
*/
|
|
92
|
-
async function runSystemLevelTest({
|
|
94
|
+
async function runSystemLevelTest({
|
|
95
|
+
appName,
|
|
96
|
+
systemKey,
|
|
97
|
+
datasourceKeys,
|
|
98
|
+
environment,
|
|
99
|
+
runType,
|
|
100
|
+
debug,
|
|
101
|
+
timeout,
|
|
102
|
+
authConfig,
|
|
103
|
+
dataplaneUrl
|
|
104
|
+
}) {
|
|
93
105
|
if (!Array.isArray(datasourceKeys) || datasourceKeys.length === 0) {
|
|
94
106
|
return { success: true, datasourceResults: [] };
|
|
95
107
|
}
|
|
@@ -100,7 +112,16 @@ async function runSystemLevelTest({ appName, systemKey, datasourceKeys, environm
|
|
|
100
112
|
// Keep sequential to avoid spiking external APIs / dataplane concurrency in CLI runs.
|
|
101
113
|
// (Plan allows parallel later, but UX needs stable logs first.)
|
|
102
114
|
// eslint-disable-next-line no-await-in-loop -- intentional sequential execution
|
|
103
|
-
results.push(
|
|
115
|
+
results.push(
|
|
116
|
+
await runOneDatasourceKey(appName, key, {
|
|
117
|
+
environment,
|
|
118
|
+
runType,
|
|
119
|
+
debug,
|
|
120
|
+
timeoutMs,
|
|
121
|
+
authConfig,
|
|
122
|
+
dataplaneUrl
|
|
123
|
+
})
|
|
124
|
+
);
|
|
104
125
|
}
|
|
105
126
|
|
|
106
127
|
const success = results.every(r => r.success);
|
|
@@ -486,7 +486,9 @@ async function testExternalSystemIntegration(appName, options = {}) {
|
|
|
486
486
|
environment: options.environment,
|
|
487
487
|
runType: 'integration',
|
|
488
488
|
debug: options.debug,
|
|
489
|
-
timeout
|
|
489
|
+
timeout,
|
|
490
|
+
authConfig,
|
|
491
|
+
dataplaneUrl
|
|
490
492
|
});
|
|
491
493
|
results.success = systemResult.success;
|
|
492
494
|
results.datasourceResults = systemResult.datasourceResults;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Load governance scenario pack YAML from integration folder
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const yaml = require('js-yaml');
|
|
12
|
+
const { getIntegrationPath } = require('../utils/paths');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Resolve pack file path for test-governance.
|
|
16
|
+
* @param {string} systemKey
|
|
17
|
+
* @param {Object} options
|
|
18
|
+
* @param {string} [options.pack] - Explicit path (absolute or relative to integration root)
|
|
19
|
+
* @param {string} [options.app] - Integration folder override
|
|
20
|
+
* @returns {string}
|
|
21
|
+
*/
|
|
22
|
+
function resolveGovernancePackPath(systemKey, options = {}) {
|
|
23
|
+
const integrationPath = options.app
|
|
24
|
+
? path.isAbsolute(options.app)
|
|
25
|
+
? options.app
|
|
26
|
+
: path.resolve(process.cwd(), options.app)
|
|
27
|
+
: getIntegrationPath(systemKey);
|
|
28
|
+
|
|
29
|
+
if (options.pack) {
|
|
30
|
+
const packPath = path.isAbsolute(options.pack)
|
|
31
|
+
? options.pack
|
|
32
|
+
: path.join(integrationPath, options.pack);
|
|
33
|
+
if (!fs.existsSync(packPath)) {
|
|
34
|
+
throw new Error(`Scenario pack not found: ${packPath}`);
|
|
35
|
+
}
|
|
36
|
+
return packPath;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const candidates = [
|
|
40
|
+
path.join(integrationPath, 'scenarios', 'default.yaml'),
|
|
41
|
+
path.join(integrationPath, 'scenarios', `${systemKey}-v1.yaml`)
|
|
42
|
+
];
|
|
43
|
+
for (const candidate of candidates) {
|
|
44
|
+
if (fs.existsSync(candidate)) {
|
|
45
|
+
return candidate;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
throw new Error(
|
|
49
|
+
`No scenario pack found under ${path.join(integrationPath, 'scenarios')}. ` +
|
|
50
|
+
'Use --pack <path> (e.g. scenarios/test-protection-v1.yaml).'
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @param {string} packPath
|
|
56
|
+
* @returns {Object}
|
|
57
|
+
*/
|
|
58
|
+
function loadGovernancePackYaml(packPath) {
|
|
59
|
+
const raw = fs.readFileSync(packPath, 'utf8');
|
|
60
|
+
const doc = yaml.load(raw);
|
|
61
|
+
if (!doc || typeof doc !== 'object') {
|
|
62
|
+
throw new Error(`Invalid scenario pack YAML: ${packPath}`);
|
|
63
|
+
}
|
|
64
|
+
return doc;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = {
|
|
68
|
+
resolveGovernancePackPath,
|
|
69
|
+
loadGovernancePackYaml
|
|
70
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview TTY display for governance scenario acceptance runs
|
|
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 {
|
|
12
|
+
sectionTitle,
|
|
13
|
+
headerKeyValue,
|
|
14
|
+
metadata: metaGray,
|
|
15
|
+
formatSuccessParagraph,
|
|
16
|
+
formatBlockingError
|
|
17
|
+
} = require('../utils/cli-test-layout-chalk');
|
|
18
|
+
|
|
19
|
+
const SEP = '────────────────────────────────';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {'pass'|'fail'} status
|
|
23
|
+
* @returns {string}
|
|
24
|
+
*/
|
|
25
|
+
function scenarioGlyph(status) {
|
|
26
|
+
return status === 'pass' ? chalk.green('✔') : chalk.red('✖');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {import('../api/types/governance-scenarios.types').GovernanceScenarioResult} scenario
|
|
31
|
+
* @param {boolean} verbose
|
|
32
|
+
*/
|
|
33
|
+
function logScenarioVerdict(scenario, verbose) {
|
|
34
|
+
logger.log(` ${scenarioGlyph(scenario.status)} ${scenario.verdict}`);
|
|
35
|
+
if (!verbose) return;
|
|
36
|
+
logger.log(
|
|
37
|
+
metaGray(
|
|
38
|
+
` Subject: ${scenario.subjectDisplayName} (${scenario.subjectUserId})`
|
|
39
|
+
)
|
|
40
|
+
);
|
|
41
|
+
if (scenario.subjectGroups && scenario.subjectGroups.length > 0) {
|
|
42
|
+
logger.log(metaGray(` Groups: ${scenario.subjectGroups.join(', ')}`));
|
|
43
|
+
}
|
|
44
|
+
logger.log(
|
|
45
|
+
metaGray(
|
|
46
|
+
` Visible keys: ${scenario.visibleKeyCount} · excluded ABAC: ${scenario.excludedAbac}` +
|
|
47
|
+
(scenario.auditRef ? ` · audit: ${scenario.auditRef}` : '')
|
|
48
|
+
)
|
|
49
|
+
);
|
|
50
|
+
if (scenario.unexpectedVisibleKeys && scenario.unexpectedVisibleKeys.length > 0) {
|
|
51
|
+
logger.log(
|
|
52
|
+
metaGray(` Unexpected: ${scenario.unexpectedVisibleKeys.join(', ')}`)
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
if (scenario.missingRequiredKeys && scenario.missingRequiredKeys.length > 0) {
|
|
56
|
+
logger.log(metaGray(` Missing: ${scenario.missingRequiredKeys.join(', ')}`));
|
|
57
|
+
}
|
|
58
|
+
if (scenario.fixHint) {
|
|
59
|
+
logger.log(metaGray(` Fix: ${scenario.fixHint}`));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @param {string} systemKey
|
|
65
|
+
* @param {import('../api/types/governance-scenarios.types').GovernanceScenariosRunResponse} result
|
|
66
|
+
* @param {{ verbose?: boolean, packPath?: string }} opts
|
|
67
|
+
*/
|
|
68
|
+
function displayGovernanceRunTTY(systemKey, result, opts = {}) {
|
|
69
|
+
const agg = result.summary.failed > 0 ? 'fail' : 'ok';
|
|
70
|
+
logger.log(SEP);
|
|
71
|
+
logger.log(sectionTitle('Governed search acceptance (key sets only — no record payload)'));
|
|
72
|
+
logger.log(headerKeyValue('System:', systemKey));
|
|
73
|
+
logger.log(headerKeyValue('Pack:', result.packKey));
|
|
74
|
+
if (opts.packPath) {
|
|
75
|
+
logger.log(metaGray(` File: ${opts.packPath}`));
|
|
76
|
+
}
|
|
77
|
+
logger.log(
|
|
78
|
+
headerKeyValue(
|
|
79
|
+
'Summary:',
|
|
80
|
+
`${result.summary.passed}/${result.summary.total} passed`
|
|
81
|
+
)
|
|
82
|
+
);
|
|
83
|
+
logger.log(metaGray(' Does not call vendor APIs (use test-e2e for connectivity).'));
|
|
84
|
+
logger.log('');
|
|
85
|
+
for (const scenario of result.scenarios || []) {
|
|
86
|
+
logScenarioVerdict(scenario, opts.verbose === true);
|
|
87
|
+
}
|
|
88
|
+
logger.log('');
|
|
89
|
+
if (agg === 'ok') {
|
|
90
|
+
logger.log(formatSuccessParagraph('All governance scenarios passed'));
|
|
91
|
+
} else {
|
|
92
|
+
logger.log(formatBlockingError(`${result.summary.failed} scenario(s) failed`));
|
|
93
|
+
}
|
|
94
|
+
logger.log(SEP);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @param {import('../api/types/governance-scenarios.types').GovernanceScenariosRunResponse} result
|
|
99
|
+
*/
|
|
100
|
+
function printGovernanceRunJson(result) {
|
|
101
|
+
logger.log(JSON.stringify(result, null, 2));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* @param {{ total: number, passed: number, failed: number }} summary
|
|
106
|
+
* @returns {number}
|
|
107
|
+
*/
|
|
108
|
+
function exitCodeFromGovernanceSummary(summary) {
|
|
109
|
+
if (!summary || summary.failed > 0) {
|
|
110
|
+
return 1;
|
|
111
|
+
}
|
|
112
|
+
return 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
module.exports = {
|
|
116
|
+
displayGovernanceRunTTY,
|
|
117
|
+
printGovernanceRunJson,
|
|
118
|
+
exitCodeFromGovernanceSummary
|
|
119
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Shared helpers for identity apply
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const { getUser, listUsers } = require('../api/users.api');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {Object} response
|
|
13
|
+
* @returns {any}
|
|
14
|
+
*/
|
|
15
|
+
function unwrapControllerData(response) {
|
|
16
|
+
if (!response || response.success === false) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
return response?.data?.data ?? response?.data ?? response;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {Object} response
|
|
24
|
+
* @returns {string}
|
|
25
|
+
*/
|
|
26
|
+
function apiErrorMessage(response) {
|
|
27
|
+
if (!response) {
|
|
28
|
+
return 'Request failed';
|
|
29
|
+
}
|
|
30
|
+
const err = response.error;
|
|
31
|
+
if (typeof err === 'string') {
|
|
32
|
+
return err;
|
|
33
|
+
}
|
|
34
|
+
if (err && typeof err === 'object') {
|
|
35
|
+
return err.formattedError || err.message || err.error || 'Request failed';
|
|
36
|
+
}
|
|
37
|
+
return response.formattedError || response.message || 'Request failed';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param {Object} response
|
|
42
|
+
* @throws {Error}
|
|
43
|
+
*/
|
|
44
|
+
function throwIfApiFailed(response) {
|
|
45
|
+
if (!response || response.success === false) {
|
|
46
|
+
throw new Error(apiErrorMessage(response));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @param {Object|null|undefined} stats
|
|
52
|
+
* @param {{ allowEmptySync?: boolean }} [opts]
|
|
53
|
+
* @throws {Error}
|
|
54
|
+
*/
|
|
55
|
+
function assertSyncStatsOk(stats, opts = {}) {
|
|
56
|
+
const syncErrors = Number(stats?.errors ?? 0);
|
|
57
|
+
if (syncErrors > 0) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
`Dataplane full sync reported ${syncErrors} error(s). ` +
|
|
60
|
+
`usersProcessed=${stats?.usersProcessed ?? 0} groupsProcessed=${stats?.groupsProcessed ?? 0}. ` +
|
|
61
|
+
'Check miso-controller logs for the sync request.'
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
const processed = Number(stats?.usersProcessed ?? 0);
|
|
65
|
+
if (processed === 0 && !opts.allowEmptySync) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
'Dataplane full sync completed with usersProcessed=0. Use --allow-empty-sync to accept, or fix identity on the controller.'
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @async
|
|
74
|
+
* @param {string} controllerUrl
|
|
75
|
+
* @param {Object} authConfig
|
|
76
|
+
* @param {{ csvId?: string, email: string }} userSpec
|
|
77
|
+
* @returns {Promise<Object|null>}
|
|
78
|
+
*/
|
|
79
|
+
async function findExistingUser(controllerUrl, authConfig, userSpec) {
|
|
80
|
+
const csvId = String(userSpec.csvId || '').trim();
|
|
81
|
+
if (csvId) {
|
|
82
|
+
const byId = await getUser(controllerUrl, authConfig, csvId);
|
|
83
|
+
const row = unwrapControllerData(byId);
|
|
84
|
+
if (row && row.id) {
|
|
85
|
+
return row;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const email = String(userSpec.email || '').trim();
|
|
89
|
+
const listed = await listUsers(controllerUrl, authConfig, {
|
|
90
|
+
search: email,
|
|
91
|
+
pageSize: 10,
|
|
92
|
+
page: 1
|
|
93
|
+
});
|
|
94
|
+
const items = unwrapControllerData(listed);
|
|
95
|
+
const list = Array.isArray(items) ? items : items?.data;
|
|
96
|
+
if (!Array.isArray(list)) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
return list.find((u) => String(u.email || '').toLowerCase() === email.toLowerCase()) || null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
module.exports = {
|
|
103
|
+
unwrapControllerData,
|
|
104
|
+
apiErrorMessage,
|
|
105
|
+
throwIfApiFailed,
|
|
106
|
+
assertSyncStatsOk,
|
|
107
|
+
findExistingUser
|
|
108
|
+
};
|