@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,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Helpers for `runProtectionCreate` (auth, path checks, YAML).
|
|
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 { resolveControllerUrl } = require('../utils/controller-url');
|
|
12
|
+
const { getConfig } = require('../core/config');
|
|
13
|
+
const { writeConfigFile, jsonToYaml } = require('../utils/config-format');
|
|
14
|
+
const { normalizeControllerUrl } = require('../core/config');
|
|
15
|
+
const { getOrRefreshDeviceToken } = require('../utils/token-manager');
|
|
16
|
+
const { getProtectionRoot } = require('./paths');
|
|
17
|
+
const { assertNoDuplicateDatasourceKeys, listProtectionManifestPaths } = require('./resolve');
|
|
18
|
+
const { loadProtectionManifest } = require('./load');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @returns {Promise<{ controllerUrl: string, authConfig: Object }>}
|
|
22
|
+
*/
|
|
23
|
+
async function resolveControllerAuthForDimensions() {
|
|
24
|
+
const controllerUrl = await resolveControllerUrl();
|
|
25
|
+
if (!controllerUrl) {
|
|
26
|
+
throw new Error('Controller URL is required. Run "aifabrix login" first.');
|
|
27
|
+
}
|
|
28
|
+
const normalized = normalizeControllerUrl(controllerUrl);
|
|
29
|
+
const deviceToken = await getOrRefreshDeviceToken(normalized);
|
|
30
|
+
if (!deviceToken || !deviceToken.token) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`Not authenticated for controller: ${controllerUrl}. Run "aifabrix login" and try again.`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
controllerUrl: deviceToken.controller || normalized,
|
|
37
|
+
authConfig: { type: 'bearer', token: deviceToken.token }
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @param {string} root
|
|
43
|
+
* @param {string} datasourceKey
|
|
44
|
+
* @param {string} outputPath
|
|
45
|
+
*/
|
|
46
|
+
function assertNoOtherFileClaimsDatasource(root, datasourceKey, outputPath) {
|
|
47
|
+
const key = String(datasourceKey || '').trim();
|
|
48
|
+
const out = path.resolve(outputPath);
|
|
49
|
+
for (const filePath of listProtectionManifestPaths(root)) {
|
|
50
|
+
if (path.resolve(filePath) === out) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const m = loadProtectionManifest(filePath);
|
|
55
|
+
if (String(m?.spec?.datasourceKey || '').trim() === key) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`Another manifest already claims datasource "${key}": ${filePath}. Remove it or use a different key.`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
} catch (e) {
|
|
61
|
+
if (e.message && e.message.includes('Another manifest')) {
|
|
62
|
+
throw e;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @param {Object} manifest
|
|
70
|
+
* @returns {string}
|
|
71
|
+
*/
|
|
72
|
+
function protectionManifestToYaml(manifest) {
|
|
73
|
+
return protectionManifestToDisplayText(manifest, 'yaml');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @returns {Promise<'yaml'|'json'>}
|
|
78
|
+
*/
|
|
79
|
+
async function resolveProtectionOutputFormat() {
|
|
80
|
+
const config = await getConfig();
|
|
81
|
+
const raw = config && typeof config.format === 'string' ? config.format.trim().toLowerCase() : '';
|
|
82
|
+
return raw === 'json' ? 'json' : 'yaml';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @param {string} dsKey
|
|
87
|
+
* @param {boolean} force
|
|
88
|
+
* @returns {Promise<{ outputPath: string, format: 'yaml'|'json' }>}
|
|
89
|
+
*/
|
|
90
|
+
async function resolveOutputPathOrThrow(dsKey, force) {
|
|
91
|
+
const root = getProtectionRoot();
|
|
92
|
+
assertNoDuplicateDatasourceKeys(root);
|
|
93
|
+
const format = await resolveProtectionOutputFormat();
|
|
94
|
+
const ext = format === 'json' ? '.json' : '.yaml';
|
|
95
|
+
const outputPath = path.join(root, `${dsKey}${ext}`);
|
|
96
|
+
assertNoOtherFileClaimsDatasource(root, dsKey, outputPath);
|
|
97
|
+
if (fs.existsSync(outputPath) && !force) {
|
|
98
|
+
throw new Error(
|
|
99
|
+
`File already exists: ${outputPath}. Use --force to overwrite, or remove the file.`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
return { outputPath, format };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* @param {string} outputPath
|
|
107
|
+
* @param {Object} manifest
|
|
108
|
+
* @param {'yaml'|'json'} format
|
|
109
|
+
*/
|
|
110
|
+
function writeProtectionManifest(outputPath, manifest, format) {
|
|
111
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
112
|
+
writeConfigFile(outputPath, manifest, format);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @param {Object} manifest
|
|
117
|
+
* @returns {string}
|
|
118
|
+
*/
|
|
119
|
+
function protectionManifestToDisplayText(manifest, format = 'yaml') {
|
|
120
|
+
if (format === 'json') {
|
|
121
|
+
return `${JSON.stringify(manifest, null, 2)}\n`;
|
|
122
|
+
}
|
|
123
|
+
return `${jsonToYaml(manifest).trim()}\n`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
module.exports = {
|
|
127
|
+
resolveControllerAuthForDimensions,
|
|
128
|
+
assertNoOtherFileClaimsDatasource,
|
|
129
|
+
protectionManifestToYaml,
|
|
130
|
+
protectionManifestToDisplayText,
|
|
131
|
+
resolveProtectionOutputFormat,
|
|
132
|
+
resolveOutputPathOrThrow,
|
|
133
|
+
writeProtectionManifest
|
|
134
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Collect probe TTY lines for protection create.
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const {
|
|
10
|
+
probeDatasourceForProtectionCreate,
|
|
11
|
+
probeDimensionForProtectionCreate
|
|
12
|
+
} = require('./protection-create-probes');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {Object} dpCtx
|
|
16
|
+
* @param {Object} dimCtx
|
|
17
|
+
* @param {string} dsKey
|
|
18
|
+
* @param {string} dimensionKey
|
|
19
|
+
* @param {boolean} verbose
|
|
20
|
+
* @returns {Promise<{lines: string[], datasource: Object, dimension: Object}>}
|
|
21
|
+
*/
|
|
22
|
+
async function collectProtectionCreateProbeContext(dpCtx, dimCtx, dsKey, dimensionKey, verbose) {
|
|
23
|
+
const lines = [];
|
|
24
|
+
const dsProbe = await probeDatasourceForProtectionCreate(
|
|
25
|
+
dpCtx.dataplaneUrl,
|
|
26
|
+
dpCtx.authConfig,
|
|
27
|
+
dsKey,
|
|
28
|
+
{ verbose }
|
|
29
|
+
);
|
|
30
|
+
lines.push(...dsProbe.lines);
|
|
31
|
+
const dimProbe = await probeDimensionForProtectionCreate(
|
|
32
|
+
dimCtx.controllerUrl,
|
|
33
|
+
dimCtx.authConfig,
|
|
34
|
+
dimensionKey,
|
|
35
|
+
{ verbose }
|
|
36
|
+
);
|
|
37
|
+
lines.push(...dimProbe.lines);
|
|
38
|
+
return { lines, datasource: dsProbe.row, dimension: dimProbe.row };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = {
|
|
42
|
+
collectProtectionCreateProbeContext
|
|
43
|
+
};
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview `aifabrix protection create` — online probes, scaffold, write YAML.
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
const { resolveProtectionDataplaneContext } = require('./auth-context');
|
|
11
|
+
const { validateProtectionManifestLocal } = require('./validate-local');
|
|
12
|
+
const { buildPresetProtectionManifest } = require('./protection-create-scaffold');
|
|
13
|
+
const { requireProtectionPreset } = require('./protection-preset-registry');
|
|
14
|
+
const {
|
|
15
|
+
sectionTitle,
|
|
16
|
+
formatSuccessLine,
|
|
17
|
+
formatNextActions,
|
|
18
|
+
formatBlockingError,
|
|
19
|
+
metadata
|
|
20
|
+
} = require('../utils/cli-test-layout-chalk');
|
|
21
|
+
const {
|
|
22
|
+
resolveControllerAuthForDimensions,
|
|
23
|
+
protectionManifestToDisplayText,
|
|
24
|
+
resolveProtectionOutputFormat,
|
|
25
|
+
resolveOutputPathOrThrow,
|
|
26
|
+
writeProtectionManifest
|
|
27
|
+
} = require('./run-protection-create-helpers');
|
|
28
|
+
const { collectProtectionCreateProbeContext } = require('./run-protection-create-probes-flow');
|
|
29
|
+
|
|
30
|
+
const SEP = chalk.gray('────────────────────────────────────────');
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {Object} opts
|
|
34
|
+
* @param {string} dsKey
|
|
35
|
+
* @param {string} dimensionKey
|
|
36
|
+
* @param {Object} logger
|
|
37
|
+
*/
|
|
38
|
+
function logCreateHeader(opts, dsKey, dimensionKey, logger) {
|
|
39
|
+
logger.log(sectionTitle('Protection create'));
|
|
40
|
+
logger.log(SEP);
|
|
41
|
+
logger.log(metadata(`Environment: ${opts.environment}`));
|
|
42
|
+
logger.log(metadata(`Datasource key: ${dsKey}`));
|
|
43
|
+
logger.log(metadata(`Dimension key: ${dimensionKey}`));
|
|
44
|
+
if (opts.type) {
|
|
45
|
+
logger.log(metadata(`Type preset: ${opts.type}`));
|
|
46
|
+
}
|
|
47
|
+
logger.log('');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @param {string} dsKey
|
|
52
|
+
* @param {string} dimensionKey
|
|
53
|
+
* @param {Object} opts
|
|
54
|
+
* @param {Object} probeContext
|
|
55
|
+
* @returns {Object}
|
|
56
|
+
*/
|
|
57
|
+
function buildManifestForCreate(dsKey, dimensionKey, opts, probeContext) {
|
|
58
|
+
return buildPresetProtectionManifest({
|
|
59
|
+
datasourceKey: dsKey,
|
|
60
|
+
dimensionKey,
|
|
61
|
+
type: opts.type,
|
|
62
|
+
field: opts.field,
|
|
63
|
+
fkName: opts.fkName,
|
|
64
|
+
protectionKey: opts.protectionKey,
|
|
65
|
+
displayName: opts.displayName,
|
|
66
|
+
ruleKey: opts.ruleKey,
|
|
67
|
+
principalExpression: opts.principalExpression,
|
|
68
|
+
valueExpression: opts.valueExpression,
|
|
69
|
+
enabled: opts.disabled !== true,
|
|
70
|
+
datasource: probeContext.datasource,
|
|
71
|
+
dimension: probeContext.dimension
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @param {Object} manifest
|
|
77
|
+
* @param {Object} logger
|
|
78
|
+
*/
|
|
79
|
+
function assertLocalSchemaOrThrow(manifest, logger) {
|
|
80
|
+
const local = validateProtectionManifestLocal(manifest);
|
|
81
|
+
if (!local.valid) {
|
|
82
|
+
logger.error(formatBlockingError('Local schema validation failed after scaffold:'));
|
|
83
|
+
local.errors.forEach((e) => logger.error(chalk.gray(` ${e}`)));
|
|
84
|
+
throw new Error('Scaffolded manifest failed AJV');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @param {Object} logger
|
|
90
|
+
* @param {string[]} lines
|
|
91
|
+
*/
|
|
92
|
+
function logProbeLines(logger, lines) {
|
|
93
|
+
for (const line of lines) {
|
|
94
|
+
logger.log(line);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @param {Object} logger
|
|
100
|
+
*/
|
|
101
|
+
function logManifestSection(logger) {
|
|
102
|
+
logger.log(sectionTitle('Protection manifest'));
|
|
103
|
+
logger.log(SEP);
|
|
104
|
+
logger.log(formatSuccessLine('Local schema check passed (AJV)'));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* @param {Object} args
|
|
109
|
+
* @param {string} args.dsKey
|
|
110
|
+
* @param {boolean} args.dryRun
|
|
111
|
+
* @param {boolean} args.force
|
|
112
|
+
* @param {Object} args.manifest
|
|
113
|
+
* @param {Object} args.logger
|
|
114
|
+
* @returns {Promise<number>}
|
|
115
|
+
*/
|
|
116
|
+
async function finalizeProtectionCreateWrite({ dsKey, dryRun, force, manifest, logger }) {
|
|
117
|
+
const format = await resolveProtectionOutputFormat();
|
|
118
|
+
const body = protectionManifestToDisplayText(manifest, format);
|
|
119
|
+
if (dryRun) {
|
|
120
|
+
logger.log(metadata('Dry run: no file written'));
|
|
121
|
+
process.stdout.write(body);
|
|
122
|
+
return 0;
|
|
123
|
+
}
|
|
124
|
+
const { outputPath } = await resolveOutputPathOrThrow(dsKey, force);
|
|
125
|
+
writeProtectionManifest(outputPath, manifest, format);
|
|
126
|
+
logger.log(formatSuccessLine(`Wrote ${outputPath}`));
|
|
127
|
+
logger.log('');
|
|
128
|
+
logger.log(
|
|
129
|
+
formatNextActions([
|
|
130
|
+
`aifabrix protection validate ${dsKey}`,
|
|
131
|
+
`aifabrix protection upload ${dsKey}`
|
|
132
|
+
])
|
|
133
|
+
);
|
|
134
|
+
return 0;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* @param {string} datasourceKey
|
|
139
|
+
* @param {Object} opts
|
|
140
|
+
* @returns {{ dsKey: string, dimensionKey: string, preset: Object|null }}
|
|
141
|
+
*/
|
|
142
|
+
function resolveCreateKeys(datasourceKey, opts) {
|
|
143
|
+
const dsKey = String(datasourceKey || '').trim();
|
|
144
|
+
if (!dsKey) {
|
|
145
|
+
throw new Error('Datasource key is required');
|
|
146
|
+
}
|
|
147
|
+
const preset = requireProtectionPreset(opts.type);
|
|
148
|
+
const dimensionKey = String(opts.dimensionKey || preset?.dimensionKey || '').trim();
|
|
149
|
+
if (!dimensionKey) {
|
|
150
|
+
throw new Error('--dimension-key is required unless --type supplies a preset dimension');
|
|
151
|
+
}
|
|
152
|
+
return { dsKey, dimensionKey, preset };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* @param {string} datasourceKey
|
|
157
|
+
* @param {Object} opts
|
|
158
|
+
* @param {Object} logger
|
|
159
|
+
* @returns {Promise<number>}
|
|
160
|
+
*/
|
|
161
|
+
async function runProtectionCreate(datasourceKey, opts, logger) {
|
|
162
|
+
const { dsKey, dimensionKey, preset } = resolveCreateKeys(datasourceKey, opts);
|
|
163
|
+
const dryRun = opts.dryRun === true;
|
|
164
|
+
const force = opts.force === true;
|
|
165
|
+
const verbose = opts.verbose === true;
|
|
166
|
+
|
|
167
|
+
const dpCtx = await resolveProtectionDataplaneContext(opts);
|
|
168
|
+
const dimCtx = await resolveControllerAuthForDimensions();
|
|
169
|
+
|
|
170
|
+
logCreateHeader({ environment: dpCtx.environment, type: preset?.type }, dsKey, dimensionKey, logger);
|
|
171
|
+
|
|
172
|
+
const probeContext = await collectProtectionCreateProbeContext(
|
|
173
|
+
dpCtx,
|
|
174
|
+
dimCtx,
|
|
175
|
+
dsKey,
|
|
176
|
+
dimensionKey,
|
|
177
|
+
verbose
|
|
178
|
+
);
|
|
179
|
+
logProbeLines(logger, probeContext.lines);
|
|
180
|
+
|
|
181
|
+
const manifest = buildManifestForCreate(dsKey, dimensionKey, opts, probeContext);
|
|
182
|
+
assertLocalSchemaOrThrow(manifest, logger);
|
|
183
|
+
logManifestSection(logger);
|
|
184
|
+
|
|
185
|
+
return finalizeProtectionCreateWrite({ dsKey, dryRun, force, manifest, logger });
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
module.exports = {
|
|
189
|
+
runProtectionCreate,
|
|
190
|
+
resolveControllerAuthForDimensions: require('./run-protection-create-helpers').resolveControllerAuthForDimensions
|
|
191
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview `.protection` scope token for top-level CLI commands.
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const PROTECTION_SCOPE = '.protection';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {string} arg
|
|
13
|
+
* @returns {boolean}
|
|
14
|
+
*/
|
|
15
|
+
function isProtectionScope(arg) {
|
|
16
|
+
return String(arg || '').trim() === PROTECTION_SCOPE;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = {
|
|
20
|
+
PROTECTION_SCOPE,
|
|
21
|
+
isProtectionScope
|
|
22
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Trigger datasource sync after protection upload.
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const { createSyncJob, executeSyncJob } = require('../api/datasources-extended.api');
|
|
10
|
+
const { unwrapApiData } = require('../utils/external-system-readiness-core');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {string} dataplaneUrl
|
|
14
|
+
* @param {Object} authConfig
|
|
15
|
+
* @param {string} datasourceKey
|
|
16
|
+
* @returns {Promise<{ syncJobId: string|null, warning: string|null }>}
|
|
17
|
+
*/
|
|
18
|
+
async function syncDatasourceAfterProtectionUpload(dataplaneUrl, authConfig, datasourceKey) {
|
|
19
|
+
const key = String(datasourceKey || '').trim();
|
|
20
|
+
if (!key) {
|
|
21
|
+
return { syncJobId: null, warning: 'Missing datasource key for sync' };
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const created = unwrapApiData(
|
|
25
|
+
await createSyncJob(dataplaneUrl, key, authConfig, {})
|
|
26
|
+
);
|
|
27
|
+
const syncJobId =
|
|
28
|
+
created?.id || created?.syncJobId || created?.data?.id || null;
|
|
29
|
+
if (syncJobId) {
|
|
30
|
+
await executeSyncJob(dataplaneUrl, key, syncJobId, authConfig);
|
|
31
|
+
}
|
|
32
|
+
return { syncJobId: syncJobId ? String(syncJobId) : null, warning: null };
|
|
33
|
+
} catch (err) {
|
|
34
|
+
return {
|
|
35
|
+
syncJobId: null,
|
|
36
|
+
warning: err?.message || String(err)
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @param {string} dataplaneUrl
|
|
43
|
+
* @param {Object} authConfig
|
|
44
|
+
* @param {string[]} datasourceKeys
|
|
45
|
+
* @returns {Promise<Array<{ datasourceKey: string, syncJobId: string|null, warning: string|null }>>}
|
|
46
|
+
*/
|
|
47
|
+
async function syncUniqueDatasourcesAfterUpload(dataplaneUrl, authConfig, datasourceKeys) {
|
|
48
|
+
const unique = [...new Set(datasourceKeys.map((k) => String(k || '').trim()).filter(Boolean))];
|
|
49
|
+
const out = [];
|
|
50
|
+
for (const datasourceKey of unique) {
|
|
51
|
+
const result = await syncDatasourceAfterProtectionUpload(
|
|
52
|
+
dataplaneUrl,
|
|
53
|
+
authConfig,
|
|
54
|
+
datasourceKey
|
|
55
|
+
);
|
|
56
|
+
out.push({ datasourceKey, ...result });
|
|
57
|
+
}
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = {
|
|
62
|
+
syncDatasourceAfterProtectionUpload,
|
|
63
|
+
syncUniqueDatasourcesAfterUpload
|
|
64
|
+
};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Batch upload all manifests in `integration/.protection/` (yaml, yml, json).
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const { getProtectionRoot } = require('./paths');
|
|
11
|
+
const { loadProtectionManifest } = require('./load');
|
|
12
|
+
const { validateProtectionManifestLocal } = require('./validate-local');
|
|
13
|
+
const { resolveProtectionDataplaneContext } = require('./auth-context');
|
|
14
|
+
const { validateProtection, uploadProtection } = require('../api/protection.api');
|
|
15
|
+
const { exitCodeFromProtectionReport } = require('./report-exit');
|
|
16
|
+
const { preflightDatasourceReady } = require('./preflight-datasource-ready');
|
|
17
|
+
const { syncUniqueDatasourcesAfterUpload } = require('./sync-after-upload');
|
|
18
|
+
const { formatProtectionBatchUploadTTY } = require('./protection-display');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {string} manifestPath
|
|
22
|
+
* @param {Object} ctx
|
|
23
|
+
* @param {Object} opts
|
|
24
|
+
* @returns {Promise<{ stop: boolean, row: Object, uploadedKey?: string }>}
|
|
25
|
+
*/
|
|
26
|
+
async function processProtectionUploadFile(manifestPath, ctx, opts) {
|
|
27
|
+
const manifest = loadProtectionManifest(manifestPath);
|
|
28
|
+
const datasourceKey = String(manifest?.spec?.datasourceKey || '').trim();
|
|
29
|
+
const local = validateProtectionManifestLocal(manifest);
|
|
30
|
+
if (!local.valid) {
|
|
31
|
+
return {
|
|
32
|
+
stop: true,
|
|
33
|
+
row: {
|
|
34
|
+
datasourceKey: datasourceKey || path.basename(manifestPath),
|
|
35
|
+
ok: false,
|
|
36
|
+
error: local.errors.join('; ')
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
await preflightDatasourceReady(ctx.dataplaneUrl, ctx.authConfig, manifest);
|
|
43
|
+
} catch (err) {
|
|
44
|
+
return { stop: true, row: { datasourceKey, ok: false, error: err.message } };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const report = await validateProtection(ctx.dataplaneUrl, ctx.authConfig, manifest, {
|
|
48
|
+
strict: opts.warningsAsErrors === true
|
|
49
|
+
});
|
|
50
|
+
if (exitCodeFromProtectionReport(report, opts) !== 0) {
|
|
51
|
+
const firstFail = (report?.results || []).find((r) => String(r?.status).toUpperCase() === 'FAIL');
|
|
52
|
+
return {
|
|
53
|
+
stop: true,
|
|
54
|
+
row: {
|
|
55
|
+
datasourceKey,
|
|
56
|
+
ok: false,
|
|
57
|
+
error: firstFail?.message || 'Dataplane validation failed'
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (opts.dryRun) {
|
|
63
|
+
return { stop: false, row: { datasourceKey, ok: true, dryRun: true } };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
await uploadProtection(ctx.dataplaneUrl, ctx.authConfig, manifest);
|
|
67
|
+
return { stop: false, row: { datasourceKey, ok: true }, uploadedKey: datasourceKey };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @param {Object} opts
|
|
72
|
+
* @returns {Promise<{ ok: boolean, results: Object[], exitCode: number, syncResults: Object[] }>}
|
|
73
|
+
*/
|
|
74
|
+
async function runUploadProtectionBatch(opts = {}) {
|
|
75
|
+
const root = getProtectionRoot();
|
|
76
|
+
const { listProtectionManifestPaths } = require('./resolve');
|
|
77
|
+
const files = listProtectionManifestPaths(root);
|
|
78
|
+
const ctx = await resolveProtectionDataplaneContext(opts);
|
|
79
|
+
const results = [];
|
|
80
|
+
const uploadedDatasourceKeys = [];
|
|
81
|
+
|
|
82
|
+
for (const manifestPath of files) {
|
|
83
|
+
const { stop, row, uploadedKey } = await processProtectionUploadFile(manifestPath, ctx, opts);
|
|
84
|
+
results.push(row);
|
|
85
|
+
if (uploadedKey) uploadedDatasourceKeys.push(uploadedKey);
|
|
86
|
+
if (stop) break;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let syncResults = [];
|
|
90
|
+
if (!opts.dryRun && !opts.noSync && uploadedDatasourceKeys.length) {
|
|
91
|
+
syncResults = await syncUniqueDatasourcesAfterUpload(
|
|
92
|
+
ctx.dataplaneUrl,
|
|
93
|
+
ctx.authConfig,
|
|
94
|
+
uploadedDatasourceKeys
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const ok = results.length > 0 && results.every((r) => r.ok);
|
|
99
|
+
return {
|
|
100
|
+
ok,
|
|
101
|
+
results,
|
|
102
|
+
exitCode: ok ? 0 : 1,
|
|
103
|
+
root,
|
|
104
|
+
ctx,
|
|
105
|
+
syncResults
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* @param {Object} opts
|
|
111
|
+
* @param {Object} logger
|
|
112
|
+
* @returns {Promise<number>}
|
|
113
|
+
*/
|
|
114
|
+
async function uploadProtectionBatchCommand(opts, logger) {
|
|
115
|
+
const batch = await runUploadProtectionBatch(opts);
|
|
116
|
+
if (opts.json) {
|
|
117
|
+
process.stdout.write(JSON.stringify(batch, null, 2));
|
|
118
|
+
return batch.exitCode;
|
|
119
|
+
}
|
|
120
|
+
logger.log(formatProtectionBatchUploadTTY(batch.results, { folder: batch.root }));
|
|
121
|
+
return batch.exitCode;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
module.exports = {
|
|
125
|
+
runUploadProtectionBatch,
|
|
126
|
+
uploadProtectionBatchCommand
|
|
127
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Batch validate all manifests in `integration/.protection/` (resolved protection root).
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const { getProtectionRoot } = require('./paths');
|
|
11
|
+
const { listProtectionManifestPaths } = require('./resolve');
|
|
12
|
+
const { loadProtectionManifest } = require('./load');
|
|
13
|
+
const { validateProtectionManifestLocal } = require('./validate-local');
|
|
14
|
+
const { resolveProtectionDataplaneContext } = require('./auth-context');
|
|
15
|
+
const { validateProtection, simulateProtection } = require('../api/protection.api');
|
|
16
|
+
const { exitCodeFromProtectionReport } = require('./report-exit');
|
|
17
|
+
const { formatProtectionBatchValidateTTY } = require('./protection-display');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {string} manifestPath
|
|
21
|
+
* @param {Object|null} ctx
|
|
22
|
+
* @param {Object} opts
|
|
23
|
+
* @returns {Promise<{ ctx: Object|null, row: Object, stop: boolean }>}
|
|
24
|
+
*/
|
|
25
|
+
async function processProtectionValidateFile(manifestPath, ctx, opts) {
|
|
26
|
+
const manifest = loadProtectionManifest(manifestPath);
|
|
27
|
+
const datasourceKey = String(
|
|
28
|
+
manifest?.spec?.datasourceKey || path.basename(manifestPath, path.extname(manifestPath))
|
|
29
|
+
).trim();
|
|
30
|
+
const local = validateProtectionManifestLocal(manifest);
|
|
31
|
+
if (!local.valid) {
|
|
32
|
+
return {
|
|
33
|
+
ctx,
|
|
34
|
+
stop: opts.stopOnFirstFailure !== false,
|
|
35
|
+
row: { datasourceKey, manifestPath, ok: false, error: local.errors.join('; ') }
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (opts.skipDataplane) {
|
|
40
|
+
return { ctx, stop: false, row: { datasourceKey, manifestPath, ok: true } };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let nextCtx = ctx;
|
|
44
|
+
if (!nextCtx) {
|
|
45
|
+
nextCtx = await resolveProtectionDataplaneContext(opts);
|
|
46
|
+
}
|
|
47
|
+
const report = await validateProtection(nextCtx.dataplaneUrl, nextCtx.authConfig, manifest, {
|
|
48
|
+
strict: opts.warningsAsErrors === true
|
|
49
|
+
});
|
|
50
|
+
if (opts.simulate && exitCodeFromProtectionReport(report, opts) === 0) {
|
|
51
|
+
await simulateProtection(nextCtx.dataplaneUrl, nextCtx.authConfig, manifest, {
|
|
52
|
+
strict: opts.warningsAsErrors === true
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
const code = exitCodeFromProtectionReport(report, opts);
|
|
56
|
+
if (code !== 0) {
|
|
57
|
+
const firstFail = (report?.results || []).find((r) => String(r?.status).toUpperCase() === 'FAIL');
|
|
58
|
+
return {
|
|
59
|
+
ctx: nextCtx,
|
|
60
|
+
stop: opts.stopOnFirstFailure !== false,
|
|
61
|
+
row: {
|
|
62
|
+
datasourceKey,
|
|
63
|
+
manifestPath,
|
|
64
|
+
ok: false,
|
|
65
|
+
error: firstFail?.message || 'Dataplane validation failed',
|
|
66
|
+
report
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return { ctx: nextCtx, stop: false, row: { datasourceKey, manifestPath, ok: true, report } };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @param {Object} opts
|
|
75
|
+
* @returns {Promise<{ valid: boolean, results: Object[], exitCode: number }>}
|
|
76
|
+
*/
|
|
77
|
+
async function runValidateProtectionBatch(opts = {}) {
|
|
78
|
+
const root = getProtectionRoot();
|
|
79
|
+
const files = listProtectionManifestPaths(root);
|
|
80
|
+
const results = [];
|
|
81
|
+
let ctx = null;
|
|
82
|
+
|
|
83
|
+
for (const manifestPath of files) {
|
|
84
|
+
const outcome = await processProtectionValidateFile(manifestPath, ctx, opts);
|
|
85
|
+
ctx = outcome.ctx;
|
|
86
|
+
results.push(outcome.row);
|
|
87
|
+
if (outcome.stop) break;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const valid = results.every((r) => r.ok);
|
|
91
|
+
return { valid, results, exitCode: valid ? 0 : 1, root, ctx };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @param {Object} opts
|
|
96
|
+
* @param {Object} logger
|
|
97
|
+
* @returns {Promise<number>}
|
|
98
|
+
*/
|
|
99
|
+
async function validateProtectionBatchCommand(opts, logger) {
|
|
100
|
+
const batch = await runValidateProtectionBatch(opts);
|
|
101
|
+
if (opts.json) {
|
|
102
|
+
process.stdout.write(JSON.stringify(batch, null, 2));
|
|
103
|
+
return batch.exitCode;
|
|
104
|
+
}
|
|
105
|
+
logger.log(formatProtectionBatchValidateTTY(batch.results, { folder: batch.root }));
|
|
106
|
+
return batch.exitCode;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
module.exports = {
|
|
110
|
+
processProtectionValidateFile,
|
|
111
|
+
runValidateProtectionBatch,
|
|
112
|
+
validateProtectionBatchCommand
|
|
113
|
+
};
|