@aifabrix/builder 2.45.5 → 2.47.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/project-rules.mdc +1 -0
- package/.npmrc.token +1 -0
- package/.temp/jest-protection-convert/run-IFN2nv/.protection/hubspot-companies.json +1 -0
- package/.temp/jest-protection-convert/run-IvmKLO/.protection/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-convert/run-SdvblF/.protection/hubspot-companies.json +1 -0
- package/.temp/jest-protection-convert/run-b51TsW/.protection/hubspot-companies.json +1 -0
- package/.temp/jest-protection-convert/run-fiLmb1/.protection/hubspot-companies.json +1 -0
- package/.temp/jest-protection-convert/run-qYGHMU/.protection/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-Mq5re8/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-f3u4YL/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-iQtd1O/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-mdmz5M/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-qbKYxQ/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-yzlJy6/hubspot-companies.yaml +17 -0
- package/.temp/validation/summary.txt +5 -0
- package/README.md +43 -17
- 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/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 +146 -0
- package/jest.projects.js +65 -136
- package/lib/api/api-response-errors.js +124 -0
- package/lib/api/dataplane-health.api.js +19 -7
- package/lib/api/governance-scenario-pack.api.js +159 -0
- package/lib/api/index.js +43 -3
- package/lib/api/lifecycle.api.js +98 -0
- package/lib/api/pipeline.api.js +3 -1
- package/lib/api/records-bulk.api.js +14 -1
- package/lib/api/resource-types.api.js +107 -0
- package/lib/api/runtime-configuration.api.js +72 -0
- package/lib/api/types/resource-types.types.js +45 -0
- package/lib/api/user-groups.api.js +9 -9
- package/lib/api/users.api.js +8 -8
- package/lib/api/validation-runner.js +3 -1
- package/lib/api/wizard.api.js +10 -2
- package/lib/app/deploy-config.js +4 -1
- package/lib/app/down.js +78 -27
- package/lib/app/index.js +2 -2
- 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/app/run-resolve-image.js +2 -1
- package/lib/app/run-stop-containers.js +113 -0
- package/lib/app/run.js +1 -31
- package/lib/app/show-display.js +35 -0
- package/lib/app/show.js +32 -1
- package/lib/build/index.js +22 -5
- package/lib/build/miso-controller-npm-sdk-guard.js +25 -0
- package/lib/certification/sync-system-certification.js +13 -6
- package/lib/channels/add-channel.js +348 -0
- package/lib/channels/channel-artifact-generator.js +141 -0
- package/lib/channels/channel-audit.js +101 -0
- package/lib/channels/channel-auth-context.js +37 -0
- package/lib/channels/channel-credential.js +200 -0
- package/lib/channels/channel-display-name.js +64 -0
- package/lib/channels/channel-external-system.js +106 -0
- package/lib/channels/channel-integration-generator.js +274 -0
- package/lib/channels/channel-probe.js +89 -0
- package/lib/channels/channel-runtime-resolver.js +152 -0
- package/lib/channels/channel-status-display.js +141 -0
- package/lib/channels/channel-targets.js +115 -0
- package/lib/cli/index.js +20 -4
- package/lib/cli/infra-guided-auth.js +109 -0
- package/lib/cli/infra-guided.js +84 -81
- package/lib/cli/setup-app.help.js +0 -31
- package/lib/cli/setup-app.js +82 -5
- package/lib/cli/setup-app.test-commands.js +5 -57
- package/lib/cli/setup-auth.js +25 -0
- 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 +25 -3
- package/lib/cli/setup-infra-up-dataplane-action.js +2 -2
- package/lib/cli/setup-infra-up-platform-action.js +2 -1
- 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 +25 -2
- package/lib/cli/setup-utility-repair.js +198 -0
- package/lib/cli/setup-utility-resolve.js +44 -5
- package/lib/cli/setup-utility-validate.js +197 -0
- package/lib/cli/setup-utility.js +4 -239
- package/lib/commands/app-logs.js +54 -26
- package/lib/commands/app.js +41 -0
- package/lib/commands/auth-status-client-tokens.js +214 -0
- package/lib/commands/auth-status-dataplane-version.js +142 -68
- package/lib/commands/auth-status.js +43 -54
- package/lib/commands/channel.js +58 -0
- package/lib/commands/datasource-test-trust-cli.js +27 -23
- package/lib/commands/datasource-unified-test-cli.js +6 -3
- package/lib/commands/datasource-unified-test-cli.options.js +5 -1
- package/lib/commands/datasource.js +8 -8
- package/lib/commands/dev-show-display.js +8 -1
- 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 +49 -8
- package/lib/commands/identity-group-cli.js +66 -26
- package/lib/commands/identity-membership-role-cache.js +58 -37
- package/lib/commands/identity-shared.js +22 -1
- package/lib/commands/identity-user-cli.js +80 -34
- package/lib/commands/lifecycle-command-action.js +123 -0
- package/lib/commands/lifecycle-external.js +268 -0
- package/lib/commands/login.js +11 -2
- package/lib/commands/repair-auth-apply.js +153 -0
- package/lib/commands/repair-auth-config.js +8 -13
- package/lib/commands/repair-changed-files.js +41 -0
- package/lib/commands/repair-datasource-com-v2.js +15 -0
- package/lib/commands/repair-datasource-keys.js +4 -1
- package/lib/commands/repair-datasource-run.js +59 -0
- package/lib/commands/repair-datasource-service-contract.js +206 -0
- package/lib/commands/repair-datasource-test-payload.js +259 -0
- package/lib/commands/repair-datasource.js +49 -64
- package/lib/commands/repair-env-template.js +76 -25
- package/lib/commands/repair-external-run.js +216 -0
- package/lib/commands/repair-openapi-sync.js +49 -4
- package/lib/commands/repair-persist.js +4 -0
- package/lib/commands/repair-rbac.js +52 -1
- package/lib/commands/repair-steps.js +160 -0
- package/lib/commands/repair-system-alignment.js +7 -4
- package/lib/commands/repair.js +29 -296
- package/lib/commands/resource-type.js +371 -0
- 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 +70 -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 +24 -8
- package/lib/commands/test-governance-external.js +3 -24
- package/lib/commands/test-trust-external.js +48 -100
- package/lib/commands/up-common.js +57 -13
- package/lib/commands/up-dataplane.js +56 -15
- package/lib/commands/upload-publish-context.js +75 -0
- package/lib/commands/upload-sidecar-followups.js +89 -0
- package/lib/commands/upload.js +113 -110
- 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 +73 -0
- package/lib/commands/verify-trust-external.js +59 -0
- package/lib/commands/wizard-config-normalizer.js +43 -23
- package/lib/commands/wizard-core.js +2 -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-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 +90 -0
- package/lib/datasource/agent-trust-debug-log.js +1 -1
- package/lib/datasource/agent-trust-run.js +5 -3
- package/lib/datasource/audit-evidence-extract-debug.js +30 -18
- package/lib/datasource/audit-evidence-extract.js +7 -4
- package/lib/datasource/audit-evidence-verify-matrix.js +54 -26
- package/lib/datasource/bulk-loader-run-batches.js +15 -3
- package/lib/datasource/capability/capability-diff-slice.js +2 -2
- package/lib/datasource/capability/capability-manifest.js +254 -0
- package/lib/datasource/capability/capability-resolve.js +3 -4
- package/lib/datasource/capability/copy-operations.js +5 -3
- package/lib/datasource/capability/remove-operations.js +2 -1
- package/lib/datasource/capability/run-capability-copy.js +32 -4
- package/lib/datasource/capability/run-capability-diff.js +3 -3
- package/lib/datasource/capability/run-capability-dimension.js +2 -4
- package/lib/datasource/capability/run-capability-edit.js +4 -7
- package/lib/datasource/capability/run-capability-relate.js +2 -4
- package/lib/datasource/capability/run-capability-remove.js +2 -4
- package/lib/datasource/capability/validate-capability-slice.js +2 -1
- package/lib/datasource/datasource-identifier.js +2 -1
- package/lib/datasource/datasource-load-export-context.js +3 -0
- package/lib/datasource/datasource-validate-display.js +1 -1
- package/lib/datasource/field-reference-validator.js +24 -3
- package/lib/datasource/log-cleaner.js +2 -2
- package/lib/datasource/log-viewer.js +8 -8
- package/lib/datasource/orphan-profiles-validator.js +98 -0
- package/lib/datasource/test-e2e.js +4 -1
- package/lib/datasource/unified-validation-run-body.js +3 -1
- package/lib/datasource/unified-validation-run.js +5 -2
- package/lib/datasource/validate.js +41 -23
- package/lib/deployment/environment-poll-status.js +138 -0
- package/lib/deployment/environment.js +42 -34
- package/lib/external-system/download-helpers.js +87 -0
- package/lib/external-system/download.js +78 -138
- package/lib/external-system/generator.js +1 -55
- package/lib/external-system/integration-auth-context.js +45 -0
- package/lib/external-system/test-auth.js +12 -5
- package/lib/external-system/test.js +16 -21
- package/lib/generator/builders.js +11 -1
- package/lib/generator/external-controller-manifest.js +18 -0
- package/lib/generator/split.js +3 -1
- package/lib/generator/wizard.js +1 -28
- package/lib/governance/governance-cli-errors.js +96 -0
- package/lib/governance/governance-download-display.js +38 -0
- package/lib/governance/governance-run-display.js +215 -25
- package/lib/identity/fk-reference.js +65 -0
- package/lib/identity/identity-apply-core.js +13 -58
- package/lib/identity/identity-apply-phases.js +5 -1
- package/lib/identity/identity-display.js +243 -0
- package/lib/identity/identity-response-helpers.js +52 -0
- package/lib/identity/identity-sync-errors.js +62 -0
- package/lib/identity/resolve-user-id.js +129 -0
- package/lib/identity/username-default.js +31 -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/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-trust.js +70 -0
- package/lib/lifecycle/report-display.js +472 -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 +10 -4
- package/lib/resolvers/datasource-resolver.js +2 -3
- package/lib/resolvers/resource-type-file.js +159 -0
- package/lib/schema/application-schema.json +17 -1
- package/lib/schema/datasource-test-run.schema.json +89 -6
- package/lib/schema/deployment-rules.yaml +2 -1
- package/lib/schema/external-datasource.schema.json +2947 -2619
- package/lib/schema/external-system.schema.json +1418 -761
- package/lib/schema/infra.parameter.yaml +45 -0
- package/lib/schema/type/credential-field-registry.json +103 -97
- package/lib/schema/type/document-storage.json +9 -5
- 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/app-run-containers.js +25 -2
- package/lib/utils/authentication-shape.js +224 -0
- package/lib/utils/build-copy.js +36 -5
- package/lib/utils/build-resolve-image.js +66 -12
- package/lib/utils/celery-companion-apps.js +53 -0
- package/lib/utils/cli-secrets-error-format.js +83 -21
- package/lib/utils/cli-utils.js +20 -4
- package/lib/utils/compose-celery-worker.js +23 -0
- package/lib/utils/compose-generate-docker-compose.js +13 -1
- package/lib/utils/compose-traefik-ingress-base.js +16 -1
- package/lib/utils/config-secrets-path.js +46 -0
- package/lib/utils/configuration-env-resolver.js +36 -0
- package/lib/utils/controller-url.js +88 -1
- package/lib/utils/credential-secrets-env-kv.js +216 -0
- package/lib/utils/credential-secrets-env.js +175 -257
- package/lib/utils/dataplane-resolver.js +18 -2
- package/lib/utils/declarative-url-ports.js +27 -4
- package/lib/utils/deployment-auth-mode.js +41 -0
- package/lib/utils/developer-volume-id.js +33 -0
- package/lib/utils/docker-build.js +1 -4
- 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 +11 -3
- package/lib/utils/external-system-readiness-core.js +10 -0
- package/lib/utils/external-system-readiness-display.js +80 -23
- package/lib/utils/external-system-validators.js +8 -2
- package/lib/utils/health-check.js +4 -1
- package/lib/utils/help-builder.js +13 -8
- 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 +2 -0
- package/lib/utils/paths.js +25 -0
- package/lib/utils/pipeline-upload-auth-retry.js +82 -0
- package/lib/utils/platform-controller-url.js +157 -0
- package/lib/utils/platform-kv-ref.js +57 -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/runtime-configuration-download.js +189 -0
- package/lib/utils/runtime-configuration-values.js +119 -0
- package/lib/utils/schema-loader.js +19 -4
- package/lib/utils/secrets-generator.js +31 -25
- package/lib/utils/secrets-missing-error.js +164 -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 +12 -5
- 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 +8 -0
- package/lib/utils/validation-run-stage.js +45 -0
- package/lib/utils/worker-sidecars.js +142 -0
- package/lib/validation/external-manifest-validator.js +2 -0
- package/lib/validation/runtime-configuration-rules.js +98 -0
- package/lib/validation/validate-display-dimensions.js +154 -0
- package/lib/validation/validate-display-sections.js +276 -0
- package/lib/validation/validate-display-steps.js +67 -0
- package/lib/validation/validate-display.js +24 -402
- package/package.json +1 -1
- package/templates/applications/dataplane/application.yaml +7 -1
- package/templates/applications/dataplane/env.template +50 -14
- package/templates/applications/dataplane/rbac.yaml +108 -1
- package/templates/applications/miso-controller/application.yaml +2 -2
- package/templates/applications/miso-controller/env.template +53 -122
- package/templates/applications/miso-controller/rbac.yaml +28 -13
- package/templates/channels/chatgpt/install.template.md.hbs +49 -0
- package/templates/channels/chatgpt/manifest.template.json.hbs +9 -0
- package/templates/channels/microsoft-copilot/install.template.md.hbs +49 -0
- package/templates/channels/microsoft-copilot/manifest.template.json.hbs +8 -0
- package/templates/channels/slack/install.template.md.hbs +49 -0
- package/templates/channels/slack/manifest.template.json.hbs +66 -0
- package/templates/channels/teams-bot/install.template.md.hbs +49 -0
- package/templates/channels/teams-bot/manifest.template.json.hbs +13 -0
- package/templates/channels/web-embed/install.template.md.hbs +49 -0
- package/templates/channels/web-embed/manifest.template.json.hbs +8 -0
- package/templates/external-system/README.md.hbs +74 -17
- package/templates/python/docker-compose.hbs +75 -18
- package/templates/typescript/docker-compose.hbs +70 -17
- package/lib/commands/test-governance-command-action.js +0 -55
- package/lib/commands/test-trust-command-action.js +0 -118
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
'use strict';
|
|
10
10
|
|
|
11
|
-
const fs = require('fs');
|
|
12
11
|
const path = require('path');
|
|
12
|
+
const { existsSync, readFileSync } = require('../internal/fs-real-sync');
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* @param {object|null} schema - Parsed external-datasource.schema.json
|
|
@@ -33,8 +33,8 @@ function extractStandardOperationOrderFromSchema(schema) {
|
|
|
33
33
|
*/
|
|
34
34
|
function tryReadJsonFile(p) {
|
|
35
35
|
try {
|
|
36
|
-
if (!p || !
|
|
37
|
-
return JSON.parse(
|
|
36
|
+
if (!p || !existsSync(p)) return null;
|
|
37
|
+
return JSON.parse(readFileSync(p, 'utf8'));
|
|
38
38
|
} catch {
|
|
39
39
|
return null;
|
|
40
40
|
}
|
|
@@ -48,25 +48,74 @@ function tryReadJsonFile(p) {
|
|
|
48
48
|
function resolveExternalDatasourceSchemaPath() {
|
|
49
49
|
if (process.env.AIFABRIX_EXTERNAL_DATASOURCE_SCHEMA) {
|
|
50
50
|
const envPath = String(process.env.AIFABRIX_EXTERNAL_DATASOURCE_SCHEMA).trim();
|
|
51
|
-
if (envPath &&
|
|
51
|
+
if (envPath && existsSync(envPath)) return envPath;
|
|
52
52
|
}
|
|
53
53
|
const candidates = [
|
|
54
|
+
path.join(__dirname, '../schema/external-datasource.schema.json'),
|
|
54
55
|
path.join(process.cwd(), 'app/schemas/json/external-datasource.schema.json'),
|
|
55
56
|
path.join(process.cwd(), 'aifabrix-dataplane/app/schemas/json/external-datasource.schema.json'),
|
|
56
57
|
path.join(process.cwd(), '..', 'aifabrix-dataplane', 'app/schemas/json/external-datasource.schema.json'),
|
|
57
58
|
path.join(__dirname, '../../../aifabrix-dataplane/app/schemas/json/external-datasource.schema.json')
|
|
58
59
|
];
|
|
59
60
|
for (const c of candidates) {
|
|
60
|
-
if (c &&
|
|
61
|
+
if (c && existsSync(c)) return c;
|
|
61
62
|
}
|
|
62
63
|
return null;
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
const FALLBACK_PATH = path.join(__dirname, '../schema/cip-capacity-display.fallback.json');
|
|
66
67
|
|
|
68
|
+
/**
|
|
69
|
+
* When schema/fallback JSON cannot be read (missing file, parse error, or Jest fs mocks in a shared worker).
|
|
70
|
+
* Must stay aligned with `cip-capacity-display.fallback.json` and dataplane `cipDefinition.operations`.
|
|
71
|
+
*/
|
|
72
|
+
const HARDCODED_STANDARD_OPERATION_ORDER = ['list', 'get', 'create', 'update', 'delete'];
|
|
73
|
+
const HARDCODED_DISPLAY_ALIASES = { create: 'insert (create)' };
|
|
74
|
+
|
|
67
75
|
/** @type {{ standardOrder: string[], aliases: Record<string, string> }|null} */
|
|
68
76
|
let _cache = null;
|
|
69
77
|
|
|
78
|
+
/**
|
|
79
|
+
* @returns {{ standardOperationOrder: string[], displayAliases: Record<string, string> }}
|
|
80
|
+
*/
|
|
81
|
+
function readFallbackDisplayConfig() {
|
|
82
|
+
const fromFile = tryReadJsonFile(FALLBACK_PATH);
|
|
83
|
+
const order =
|
|
84
|
+
fromFile && Array.isArray(fromFile.standardOperationOrder) && fromFile.standardOperationOrder.length
|
|
85
|
+
? fromFile.standardOperationOrder
|
|
86
|
+
: HARDCODED_STANDARD_OPERATION_ORDER;
|
|
87
|
+
const aliases =
|
|
88
|
+
fromFile && fromFile.displayAliases && typeof fromFile.displayAliases === 'object'
|
|
89
|
+
? fromFile.displayAliases
|
|
90
|
+
: HARDCODED_DISPLAY_ALIASES;
|
|
91
|
+
return { standardOperationOrder: order, displayAliases: aliases };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Prefer schema key order, then append any fallback standard ops missing from the schema
|
|
96
|
+
* (guards partial/stale schema files and cross-test cache pollution in Jest).
|
|
97
|
+
* @param {string[]|null} fromSchema
|
|
98
|
+
* @param {string[]} fbOrder
|
|
99
|
+
* @returns {string[]}
|
|
100
|
+
*/
|
|
101
|
+
function mergeStandardOperationOrders(fromSchema, fbOrder) {
|
|
102
|
+
const schemaOps = Array.isArray(fromSchema)
|
|
103
|
+
? fromSchema.map(op => String(op).trim().toLowerCase()).filter(Boolean)
|
|
104
|
+
: [];
|
|
105
|
+
const fallbackOps = Array.isArray(fbOrder)
|
|
106
|
+
? fbOrder.map(op => String(op).trim().toLowerCase()).filter(Boolean)
|
|
107
|
+
: [];
|
|
108
|
+
if (!schemaOps.length) return fallbackOps;
|
|
109
|
+
const seen = new Set(schemaOps);
|
|
110
|
+
const merged = [...schemaOps];
|
|
111
|
+
for (const op of fallbackOps) {
|
|
112
|
+
if (seen.has(op)) continue;
|
|
113
|
+
seen.add(op);
|
|
114
|
+
merged.push(op);
|
|
115
|
+
}
|
|
116
|
+
return merged;
|
|
117
|
+
}
|
|
118
|
+
|
|
70
119
|
/**
|
|
71
120
|
* @returns {{ standardOrder: string[], aliases: Record<string, string> }}
|
|
72
121
|
*/
|
|
@@ -75,16 +124,12 @@ function getCipCapacityDisplayConfig() {
|
|
|
75
124
|
const schemaPath = resolveExternalDatasourceSchemaPath();
|
|
76
125
|
const schema = schemaPath ? tryReadJsonFile(schemaPath) : null;
|
|
77
126
|
const fromSchema = extractStandardOperationOrderFromSchema(schema);
|
|
78
|
-
const fallback =
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
fallback.displayAliases && typeof fallback.displayAliases === 'object'
|
|
85
|
-
? fallback.displayAliases
|
|
86
|
-
: { create: 'insert (create)' };
|
|
87
|
-
_cache = { standardOrder, aliases };
|
|
127
|
+
const fallback = readFallbackDisplayConfig();
|
|
128
|
+
const standardOrder = mergeStandardOperationOrders(
|
|
129
|
+
fromSchema,
|
|
130
|
+
fallback.standardOperationOrder
|
|
131
|
+
);
|
|
132
|
+
_cache = { standardOrder, aliases: fallback.displayAliases };
|
|
88
133
|
return _cache;
|
|
89
134
|
}
|
|
90
135
|
|
|
@@ -122,6 +167,7 @@ function clearCipCapacityDisplayConfigCacheForTests() {
|
|
|
122
167
|
|
|
123
168
|
module.exports = {
|
|
124
169
|
extractStandardOperationOrderFromSchema,
|
|
170
|
+
mergeStandardOperationOrders,
|
|
125
171
|
resolveExternalDatasourceSchemaPath,
|
|
126
172
|
getCipCapacityDisplayConfig,
|
|
127
173
|
standardOperationRank,
|
|
@@ -104,6 +104,8 @@ function isStdoutTty() {
|
|
|
104
104
|
function emitManifestMetadataLineIfTTY(logger, opts) {
|
|
105
105
|
if (!logger || typeof logger.log !== 'function') return;
|
|
106
106
|
if (!opts || opts.json || opts.envOnly) return;
|
|
107
|
+
const { isSetupQuietOutput } = require('./setup-quiet-output');
|
|
108
|
+
if (isSetupQuietOutput()) return;
|
|
107
109
|
if (!isStdoutTty()) return;
|
|
108
110
|
const appKey = opts.appKey && typeof opts.appKey === 'string' ? opts.appKey.trim() : '';
|
|
109
111
|
const appPath = opts.appPath && typeof opts.appPath === 'string' ? opts.appPath.trim() : '';
|
package/lib/utils/paths.js
CHANGED
|
@@ -315,6 +315,30 @@ function getDevDirectory(appName, developerId) {
|
|
|
315
315
|
return baseDir;
|
|
316
316
|
}
|
|
317
317
|
|
|
318
|
+
/**
|
|
319
|
+
* Target directory for `aifabrix create` — always under {@link process.cwd} (`builder/` or
|
|
320
|
+
* `integration/`) even when those folders do not exist yet. Platform system apps still
|
|
321
|
+
* materialize under {@link getAppsMaterializationParent}.
|
|
322
|
+
*
|
|
323
|
+
* Post-create lookup uses {@link getAppPath} / {@link getBuilderPath} / {@link getIntegrationPath}.
|
|
324
|
+
*
|
|
325
|
+
* @param {string} appName - Application name
|
|
326
|
+
* @param {string} [appType] - Application type (`external` or other)
|
|
327
|
+
* @returns {string} Absolute path to the new application directory
|
|
328
|
+
*/
|
|
329
|
+
function getAppCreatePath(appName, appType) {
|
|
330
|
+
if (!appName || typeof appName !== 'string') {
|
|
331
|
+
throw new Error('App name is required and must be a string');
|
|
332
|
+
}
|
|
333
|
+
if (appType === 'external') {
|
|
334
|
+
return path.join(path.resolve(process.cwd()), 'integration', appName);
|
|
335
|
+
}
|
|
336
|
+
if (isSystemBuilderAppName(appName)) {
|
|
337
|
+
return path.join(getAppsMaterializationParent(), 'builder', appName);
|
|
338
|
+
}
|
|
339
|
+
return path.join(path.resolve(process.cwd()), 'builder', appName);
|
|
340
|
+
}
|
|
341
|
+
|
|
318
342
|
/**
|
|
319
343
|
* Gets the application path (builder or integration folder).
|
|
320
344
|
* Matches {@link getBuilderPath} / {@link getIntegrationPath} (cwd `integration/` / `builder/`, then
|
|
@@ -881,6 +905,7 @@ module.exports = {
|
|
|
881
905
|
getApplicationsBaseDir,
|
|
882
906
|
getDevDirectory,
|
|
883
907
|
getAppPath,
|
|
908
|
+
getAppCreatePath,
|
|
884
909
|
getAppsMaterializationParent,
|
|
885
910
|
getCwdIntegrationRoot,
|
|
886
911
|
getCwdBuilderRoot,
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retry dataplane pipeline upload with application client token when device Bearer fails.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Pipeline publish auth fallback when device Bearer is rejected
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const chalk = require('chalk');
|
|
12
|
+
const logger = require('./logger');
|
|
13
|
+
const { uploadApplicationViaPipeline } = require('../api/pipeline.api');
|
|
14
|
+
const { getDeploymentAuth } = require('./token-manager');
|
|
15
|
+
const { resolveControllerUrl } = require('./controller-url');
|
|
16
|
+
const config = require('../core/config');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {Object} apiResponse
|
|
20
|
+
* @returns {boolean}
|
|
21
|
+
*/
|
|
22
|
+
function isPipelineAuthFailure(apiResponse) {
|
|
23
|
+
if (!apiResponse || apiResponse.success !== false) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
const status = Number(apiResponse.status) || 0;
|
|
27
|
+
if (status === 401 || status === 403) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
const text = String(
|
|
31
|
+
apiResponse.formattedError || apiResponse.error || apiResponse.message || ''
|
|
32
|
+
).toLowerCase();
|
|
33
|
+
return text.includes('invalid token') || text.includes('unauthorized') || text.includes('authentication');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {string} systemKey
|
|
38
|
+
* @returns {Promise<Object|null>}
|
|
39
|
+
*/
|
|
40
|
+
async function resolveClientTokenAuthForPipelineRetry(systemKey) {
|
|
41
|
+
try {
|
|
42
|
+
const controllerUrl = await resolveControllerUrl();
|
|
43
|
+
const environment = await config.resolveEnvironment();
|
|
44
|
+
return await getDeploymentAuth(controllerUrl, environment, systemKey, {
|
|
45
|
+
deploymentAuth: 'client-credentials'
|
|
46
|
+
});
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* POST /api/v1/pipeline/upload with optional client-token retry after device Bearer 401.
|
|
54
|
+
*
|
|
55
|
+
* @async
|
|
56
|
+
* @param {string} dataplaneUrl
|
|
57
|
+
* @param {Object} authConfig
|
|
58
|
+
* @param {Object} payload
|
|
59
|
+
* @param {string} systemKey - External system key for credential lookup on retry
|
|
60
|
+
* @returns {Promise<Object>}
|
|
61
|
+
*/
|
|
62
|
+
async function uploadApplicationViaPipelineWithAuthRetry(dataplaneUrl, authConfig, payload, systemKey) {
|
|
63
|
+
const first = await uploadApplicationViaPipeline(dataplaneUrl, authConfig, payload);
|
|
64
|
+
if (!isPipelineAuthFailure(first) || authConfig.type !== 'bearer') {
|
|
65
|
+
return first;
|
|
66
|
+
}
|
|
67
|
+
const clientAuth = await resolveClientTokenAuthForPipelineRetry(systemKey);
|
|
68
|
+
if (!clientAuth || clientAuth.type === 'bearer') {
|
|
69
|
+
return first;
|
|
70
|
+
}
|
|
71
|
+
logger.log(
|
|
72
|
+
chalk.yellow(
|
|
73
|
+
'Device login token was rejected for pipeline publish; retrying with application client token…'
|
|
74
|
+
)
|
|
75
|
+
);
|
|
76
|
+
return uploadApplicationViaPipeline(dataplaneUrl, clientAuth, payload);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = {
|
|
80
|
+
isPipelineAuthFailure,
|
|
81
|
+
uploadApplicationViaPipelineWithAuthRetry
|
|
82
|
+
};
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Absolute public base URLs for platform apps (Keycloak, Miso Controller, Dataplane).
|
|
3
|
+
* Used by setup (`platform-controller`), guided footers, and declarative routing.
|
|
4
|
+
*
|
|
5
|
+
* @fileoverview Platform app public URL resolution (Traefik / localhost + dev ports)
|
|
6
|
+
* @author AI Fabrix Team
|
|
7
|
+
* @version 1.0.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
const config = require('../core/config');
|
|
16
|
+
const pathsUtil = require('./paths');
|
|
17
|
+
const { loadConfigFile } = require('./config-format');
|
|
18
|
+
const { computePublicUrlBaseString } = require('./url-declarative-public-base');
|
|
19
|
+
const { joinUrlPath, normalizeFrontDoorPatternForHealth } = require('./health-check-url');
|
|
20
|
+
const { getDevPorts } = require('./dev-config');
|
|
21
|
+
const { flagsForSetupPlatformMode } = require('../core/setup-platform-mode');
|
|
22
|
+
|
|
23
|
+
const MISO_CONTROLLER_APP = 'miso-controller';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Bundled `templates/applications/<app>/application.yaml` (fresh install has no builder copy yet).
|
|
27
|
+
*
|
|
28
|
+
* @param {string} appName
|
|
29
|
+
* @returns {Object|null}
|
|
30
|
+
*/
|
|
31
|
+
function loadBundledApplicationVariables(appName) {
|
|
32
|
+
const templateConfigPath = path.join(
|
|
33
|
+
pathsUtil.getProjectRoot(),
|
|
34
|
+
'templates',
|
|
35
|
+
'applications',
|
|
36
|
+
appName,
|
|
37
|
+
'application.yaml'
|
|
38
|
+
);
|
|
39
|
+
if (!fs.existsSync(templateConfigPath)) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const variables = loadConfigFile(templateConfigPath);
|
|
43
|
+
return variables && typeof variables === 'object' ? variables : null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Load platform app variables from materialized builder config, or from bundled template
|
|
48
|
+
* when `aifabrix setup` runs before `up-platform` has copied templates.
|
|
49
|
+
*
|
|
50
|
+
* @param {string} appName
|
|
51
|
+
* @returns {Object}
|
|
52
|
+
*/
|
|
53
|
+
function loadAppVariables(appName) {
|
|
54
|
+
const builderPath = pathsUtil.getBuilderPath(appName);
|
|
55
|
+
if (fs.existsSync(builderPath)) {
|
|
56
|
+
try {
|
|
57
|
+
const configPath = pathsUtil.resolveApplicationConfigPath(builderPath);
|
|
58
|
+
return loadConfigFile(configPath) || {};
|
|
59
|
+
} catch (err) {
|
|
60
|
+
const msg = err && err.message ? String(err.message) : '';
|
|
61
|
+
if (!msg.includes('Application config not found')) {
|
|
62
|
+
throw err;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const fromTemplate = loadBundledApplicationVariables(appName);
|
|
67
|
+
if (fromTemplate) {
|
|
68
|
+
return fromTemplate;
|
|
69
|
+
}
|
|
70
|
+
throw new Error(
|
|
71
|
+
`Application config not found for platform app "${appName}". ` +
|
|
72
|
+
'Run `aifabrix setup` or `aifabrix up-platform` to materialize builder templates.'
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function computeLocalFullPlatformFrontDoorUrl(params) {
|
|
77
|
+
const { setupPlatformMode, remoteServer, developerIdNum, frontDoorRouting } = params;
|
|
78
|
+
if (!flagsForSetupPlatformMode(setupPlatformMode).traefik) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
if (remoteServer && String(remoteServer).trim()) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
const ports = getDevPorts(Number.isFinite(developerIdNum) ? developerIdNum : 0);
|
|
85
|
+
const mount = normalizeFrontDoorPatternForHealth(frontDoorRouting.pattern);
|
|
86
|
+
// Full local platform uses a single front-door port (3000 + devId×100), not the infra Traefik port.
|
|
87
|
+
return joinUrlPath(`http://localhost:${ports.app}`, mount);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Compute the absolute base URL for a platform app (same rules as guided `up-platform` footer).
|
|
92
|
+
*
|
|
93
|
+
* @async
|
|
94
|
+
* @param {string} appName - e.g. `miso-controller`, `keycloak`, `dataplane`
|
|
95
|
+
* @returns {Promise<string>}
|
|
96
|
+
*/
|
|
97
|
+
async function computeAppBaseUrl(appName) {
|
|
98
|
+
const variables = loadAppVariables(appName);
|
|
99
|
+
const basePort = Number(variables.port || 3000);
|
|
100
|
+
const developerIdRaw = await config.getDeveloperId();
|
|
101
|
+
const developerIdNum =
|
|
102
|
+
typeof developerIdRaw === 'string' ? parseInt(developerIdRaw, 10) : developerIdRaw;
|
|
103
|
+
const userCfg = await config.getConfig();
|
|
104
|
+
const remoteServer = await config.getRemoteServer();
|
|
105
|
+
const infraTlsEnabled = Boolean(userCfg && userCfg.tlsEnabled);
|
|
106
|
+
|
|
107
|
+
const fd = variables.frontDoorRouting || null;
|
|
108
|
+
const frontDoorEnabled = Boolean(fd && fd.enabled === true);
|
|
109
|
+
const traefikOn = Boolean(userCfg && userCfg.traefik);
|
|
110
|
+
const pathActive = Boolean(traefikOn && frontDoorEnabled);
|
|
111
|
+
const setupPlatformMode = await config.getSetupPlatformMode();
|
|
112
|
+
|
|
113
|
+
const publicBase = computePublicUrlBaseString({
|
|
114
|
+
traefik: traefikOn,
|
|
115
|
+
pathActive,
|
|
116
|
+
hostTemplate: fd ? fd.host : null,
|
|
117
|
+
tls: fd ? fd.tls : true,
|
|
118
|
+
developerIdRaw,
|
|
119
|
+
remoteServer,
|
|
120
|
+
profile: 'docker',
|
|
121
|
+
listenPort: basePort,
|
|
122
|
+
developerIdNum,
|
|
123
|
+
infraTlsEnabled
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
if (pathActive) {
|
|
127
|
+
const localFullUrl = computeLocalFullPlatformFrontDoorUrl({
|
|
128
|
+
setupPlatformMode,
|
|
129
|
+
remoteServer,
|
|
130
|
+
developerIdNum,
|
|
131
|
+
frontDoorRouting: fd
|
|
132
|
+
});
|
|
133
|
+
if (localFullUrl) return localFullUrl;
|
|
134
|
+
const mount = normalizeFrontDoorPatternForHealth(fd.pattern);
|
|
135
|
+
return joinUrlPath(publicBase, mount);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return String(publicBase).replace(/\/+$/, '');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Absolute Miso Controller URL for local platform install / setup auth.
|
|
143
|
+
*
|
|
144
|
+
* @async
|
|
145
|
+
* @returns {Promise<string>}
|
|
146
|
+
*/
|
|
147
|
+
async function resolvePlatformControllerUrl() {
|
|
148
|
+
return computeAppBaseUrl(MISO_CONTROLLER_APP);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
module.exports = {
|
|
152
|
+
MISO_CONTROLLER_APP,
|
|
153
|
+
loadAppVariables,
|
|
154
|
+
loadBundledApplicationVariables,
|
|
155
|
+
computeAppBaseUrl,
|
|
156
|
+
resolvePlatformControllerUrl
|
|
157
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform installation kv:// refs from infra.parameter.yaml (flat keys, no path segments).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Distinguish platform catalog secrets from per-integration path-style kv refs
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const { getInfraParameterCatalog } = require('../parameters/infra-parameter-catalog');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Returns the single-segment path after kv:// when the ref has no slashes.
|
|
15
|
+
*
|
|
16
|
+
* @param {string} val - kv:// reference
|
|
17
|
+
* @returns {string|null}
|
|
18
|
+
*/
|
|
19
|
+
function kvRefSegment(val) {
|
|
20
|
+
if (typeof val !== 'string' || !val.trim().toLowerCase().startsWith('kv://')) return null;
|
|
21
|
+
const after = val.trim().slice(5);
|
|
22
|
+
if (!after || after.includes('/')) return null;
|
|
23
|
+
return after;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* True when val is kv://<key> and key exists in shipped infra.parameter.yaml.
|
|
28
|
+
*
|
|
29
|
+
* @param {string} val - kv:// reference
|
|
30
|
+
* @returns {boolean}
|
|
31
|
+
*/
|
|
32
|
+
function isPlatformCatalogKvRef(val) {
|
|
33
|
+
const segment = kvRefSegment(val);
|
|
34
|
+
if (!segment) return false;
|
|
35
|
+
try {
|
|
36
|
+
const catalog = getInfraParameterCatalog();
|
|
37
|
+
return Boolean(catalog.findEntryForKey(segment));
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* True when repair should normalize a kv value to path-style kv://systemKey/variable.
|
|
45
|
+
* Platform catalog refs (e.g. kv://secrets-openaiApiKeyVault) are never legacy.
|
|
46
|
+
*
|
|
47
|
+
* @param {string} val - Value from authentication.security or configuration
|
|
48
|
+
* @returns {boolean}
|
|
49
|
+
*/
|
|
50
|
+
function isLegacyKvValue(val) {
|
|
51
|
+
if (typeof val !== 'string' || !val.trim().toLowerCase().startsWith('kv://')) return false;
|
|
52
|
+
if (isPlatformCatalogKvRef(val)) return false;
|
|
53
|
+
const after = val.trim().slice(5);
|
|
54
|
+
return after.includes('KeyVault') || !after.includes('/');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = { kvRefSegment, isPlatformCatalogKvRef, isLegacyKvValue };
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recreate platform Postgres databases and roles after setup "wipe data".
|
|
3
|
+
*
|
|
4
|
+
* Infra init scripts run only on first volume creation; dropping DBs in a
|
|
5
|
+
* running Postgres container requires explicit bootstrap before Keycloak/Miso start.
|
|
6
|
+
*
|
|
7
|
+
* @fileoverview Platform database bootstrap after postgres wipe
|
|
8
|
+
* @author AI Fabrix Team
|
|
9
|
+
* @version 1.0.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
const config = require('../core/config');
|
|
15
|
+
const adminSecrets = require('../core/admin-secrets');
|
|
16
|
+
const logger = require('./logger');
|
|
17
|
+
const chalk = require('chalk');
|
|
18
|
+
const { successGlyph } = require('./cli-test-layout-chalk');
|
|
19
|
+
const { getPostgresContainerName } = require('./postgres-wipe');
|
|
20
|
+
const dockerExec = require('./docker-exec');
|
|
21
|
+
const { isSetupQuietOutput } = require('./setup-quiet-output');
|
|
22
|
+
|
|
23
|
+
const SUPERUSER_ROLE = 'pgadmin';
|
|
24
|
+
|
|
25
|
+
/** @type {{ dbName: string, secretKey: string }[]} */
|
|
26
|
+
const PLATFORM_DATABASE_SLOTS = [
|
|
27
|
+
{ dbName: 'keycloak', secretKey: 'databases-keycloak-0-passwordKeyVault' },
|
|
28
|
+
{ dbName: 'miso', secretKey: 'databases-miso-controller-0-passwordKeyVault' },
|
|
29
|
+
{ dbName: 'miso-logs', secretKey: 'databases-miso-controller-1-passwordKeyVault' },
|
|
30
|
+
{ dbName: 'dataplane', secretKey: 'databases-dataplane-0-passwordKeyVault' },
|
|
31
|
+
{ dbName: 'dataplane-vector', secretKey: 'databases-dataplane-1-passwordKeyVault' },
|
|
32
|
+
{ dbName: 'dataplane-logs', secretKey: 'databases-dataplane-2-passwordKeyVault' },
|
|
33
|
+
{ dbName: 'dataplane-records', secretKey: 'databases-dataplane-3-passwordKeyVault' }
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {string} dbName
|
|
38
|
+
* @returns {string}
|
|
39
|
+
*/
|
|
40
|
+
function pgRoleName(dbName) {
|
|
41
|
+
return `${String(dbName).replace(/-/g, '_')}_user`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @param {string} s
|
|
46
|
+
* @returns {string}
|
|
47
|
+
*/
|
|
48
|
+
function escapePgString(s) {
|
|
49
|
+
if (s === null || s === undefined || typeof s !== 'string') return '';
|
|
50
|
+
return s.replace(/'/g, '\'\'');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param {string} urlOrPassword
|
|
55
|
+
* @returns {string|null}
|
|
56
|
+
*/
|
|
57
|
+
function extractPasswordFromUrlOrValue(urlOrPassword) {
|
|
58
|
+
if (typeof urlOrPassword !== 'string' || urlOrPassword.length === 0) return null;
|
|
59
|
+
if (!urlOrPassword.includes('://')) return urlOrPassword;
|
|
60
|
+
try {
|
|
61
|
+
const u = new URL(
|
|
62
|
+
urlOrPassword.replace(/\$\{DB_HOST\}/g, 'postgres').replace(/\$\{DB_PORT\}/g, '5432')
|
|
63
|
+
);
|
|
64
|
+
return u.password || null;
|
|
65
|
+
} catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Escape SQL for embedding in a double-quoted shell argument (-c "...").
|
|
72
|
+
* Bash expands `$` and `$$` (PID) inside double quotes; backslash-escape every `$`.
|
|
73
|
+
*
|
|
74
|
+
* @param {string} s
|
|
75
|
+
* @returns {string}
|
|
76
|
+
*/
|
|
77
|
+
function escapeShellDoubleQuoted(s) {
|
|
78
|
+
return s.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\$/g, '\\$');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @async
|
|
83
|
+
* @param {string} container
|
|
84
|
+
* @param {string} sql
|
|
85
|
+
* @param {string} adminPassword
|
|
86
|
+
* @returns {Promise<void>}
|
|
87
|
+
*/
|
|
88
|
+
async function runPsqlCommand(container, sql, adminPassword, database = 'postgres') {
|
|
89
|
+
const escapedSql = escapeShellDoubleQuoted(sql);
|
|
90
|
+
const cmd =
|
|
91
|
+
`docker exec -e PGPASSWORD -i ${container} psql -U ${SUPERUSER_ROLE} -d ${database} ` +
|
|
92
|
+
`-v ON_ERROR_STOP=1 -c "${escapedSql}"`;
|
|
93
|
+
await dockerExec.execWithDockerEnv(cmd, { env: { PGPASSWORD: adminPassword } });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @async
|
|
98
|
+
* @param {string} container
|
|
99
|
+
* @param {string} dbName
|
|
100
|
+
* @param {string} role
|
|
101
|
+
* @param {string} password
|
|
102
|
+
* @param {string} adminPassword
|
|
103
|
+
* @returns {Promise<void>}
|
|
104
|
+
*/
|
|
105
|
+
async function ensureRoleAndDatabase(container, dbName, role, password, adminPassword) {
|
|
106
|
+
if (!/^[A-Za-z_][A-Za-z0-9_-]*$/.test(dbName) || !/^[A-Za-z_][A-Za-z0-9_-]*$/.test(role)) {
|
|
107
|
+
throw new Error(`Unsafe database or role name: ${dbName} / ${role}`);
|
|
108
|
+
}
|
|
109
|
+
const pwd = escapePgString(password);
|
|
110
|
+
await runPsqlCommand(
|
|
111
|
+
container,
|
|
112
|
+
`DO $$ BEGIN IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = '${role}') THEN CREATE USER "${role}" WITH PASSWORD '${pwd}'; ELSE ALTER USER "${role}" WITH PASSWORD '${pwd}'; END IF; END $$;`,
|
|
113
|
+
adminPassword
|
|
114
|
+
);
|
|
115
|
+
const exists = await dockerExec.execWithDockerEnv(
|
|
116
|
+
`docker exec -e PGPASSWORD -i ${container} psql -U ${SUPERUSER_ROLE} -d postgres -tAc "SELECT 1 FROM pg_database WHERE datname = '${dbName}'"`,
|
|
117
|
+
{ env: { PGPASSWORD: adminPassword } }
|
|
118
|
+
);
|
|
119
|
+
const hasDb = String((exists && exists.stdout) || '').trim() === '1';
|
|
120
|
+
if (!hasDb) {
|
|
121
|
+
await runPsqlCommand(
|
|
122
|
+
container,
|
|
123
|
+
`CREATE DATABASE "${dbName}" OWNER "${role}";`,
|
|
124
|
+
adminPassword
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
await runPsqlCommand(
|
|
128
|
+
container,
|
|
129
|
+
`GRANT ALL ON SCHEMA public TO "${role}"; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO "${role}";`,
|
|
130
|
+
adminPassword,
|
|
131
|
+
dbName
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* @async
|
|
137
|
+
* @param {string} container
|
|
138
|
+
* @param {{ dbName: string, secretKey: string }} slot
|
|
139
|
+
* @param {Object} secrets
|
|
140
|
+
* @param {string} adminPassword
|
|
141
|
+
* @returns {Promise<string>}
|
|
142
|
+
*/
|
|
143
|
+
async function ensurePlatformDatabaseSlot(container, slot, secrets, adminPassword) {
|
|
144
|
+
const raw = secrets[slot.secretKey];
|
|
145
|
+
const password = extractPasswordFromUrlOrValue(raw);
|
|
146
|
+
if (!password || !password.trim()) {
|
|
147
|
+
throw new Error(
|
|
148
|
+
`Secret ${slot.secretKey} is missing or empty. Run "aifabrix up-infra" to ensure infra secrets.`
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
const role = pgRoleName(slot.dbName);
|
|
152
|
+
await ensureRoleAndDatabase(container, slot.dbName, role, password.trim(), adminPassword);
|
|
153
|
+
if (!isSetupQuietOutput()) {
|
|
154
|
+
logger.log(chalk.gray(` ${successGlyph()} Ensured database ${slot.dbName} (${role})`));
|
|
155
|
+
}
|
|
156
|
+
return slot.dbName;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* @async
|
|
161
|
+
* @returns {Promise<{ container: string, adminPassword: string, secrets: Object }>}
|
|
162
|
+
*/
|
|
163
|
+
async function resolveBootstrapContext() {
|
|
164
|
+
const devId = await config.getDeveloperId();
|
|
165
|
+
const container = getPostgresContainerName(devId);
|
|
166
|
+
const admin = await adminSecrets.readAndDecryptAdminSecrets();
|
|
167
|
+
const adminPassword = admin && admin.POSTGRES_PASSWORD;
|
|
168
|
+
if (!adminPassword) {
|
|
169
|
+
throw new Error('POSTGRES_PASSWORD not found in admin-secrets.env. Run "aifabrix up-infra" first.');
|
|
170
|
+
}
|
|
171
|
+
const secretsEnsure = require('../core/secrets-ensure');
|
|
172
|
+
await secretsEnsure.ensureInfraSecrets();
|
|
173
|
+
const { loadSecrets } = require('../core/secrets');
|
|
174
|
+
const secrets = await loadSecrets(undefined);
|
|
175
|
+
return { container, adminPassword, secrets };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Recreate platform DB users and databases from resolved kv secrets.
|
|
180
|
+
*
|
|
181
|
+
* @async
|
|
182
|
+
* @returns {Promise<string[]>} database names ensured
|
|
183
|
+
*/
|
|
184
|
+
async function bootstrapPlatformPostgresDatabases() {
|
|
185
|
+
const { container, adminPassword, secrets } = await resolveBootstrapContext();
|
|
186
|
+
const ensured = [];
|
|
187
|
+
for (const slot of PLATFORM_DATABASE_SLOTS) {
|
|
188
|
+
ensured.push(await ensurePlatformDatabaseSlot(container, slot, secrets, adminPassword));
|
|
189
|
+
}
|
|
190
|
+
return ensured;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
module.exports = {
|
|
194
|
+
PLATFORM_DATABASE_SLOTS,
|
|
195
|
+
bootstrapPlatformPostgresDatabases,
|
|
196
|
+
pgRoleName,
|
|
197
|
+
extractPasswordFromUrlOrValue,
|
|
198
|
+
escapeShellDoubleQuoted
|
|
199
|
+
};
|