@aifabrix/builder 2.57.0 → 2.59.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/lib/agent-kit/codex-app-server.js +44 -0
- package/lib/agent-kit/init.js +4 -17
- package/lib/agent-kit/session-report.js +15 -10
- package/lib/agent-kit/setup-auth.js +131 -0
- package/lib/agent-kit/setup-context.js +118 -0
- package/lib/agent-kit/setup-github.js +70 -0
- package/lib/agent-kit/setup-managed.js +79 -0
- package/lib/agent-kit/setup-process.js +42 -0
- package/lib/agent-kit/setup-session.js +53 -0
- package/lib/agent-kit/setup-state.js +52 -0
- package/lib/agent-kit/setup-workspace.js +113 -0
- package/lib/agent-kit/setup.js +84 -0
- package/lib/agent-kit/start.js +4 -3
- package/lib/agent-kit/tmux-sessions.js +20 -3
- package/lib/api/dev.api.js +1 -1
- package/lib/api/miso-health.api.js +26 -0
- package/lib/api/types/dev.types.js +3 -3
- package/lib/app/deploy.js +0 -102
- package/lib/app/push.js +98 -41
- package/lib/build/standard-docker-build.js +13 -11
- package/lib/cli/index.js +4 -0
- package/lib/cli/setup-app.js +16 -5
- package/lib/cli/setup-dev-path-commands.js +53 -2
- package/lib/cli/setup-dev.js +7 -4
- package/lib/cli/setup-environment.js +17 -4
- package/lib/cli/setup-help.js +86 -0
- package/lib/cli/setup-onboarding.js +37 -0
- package/lib/cli/setup-utility-repair.js +7 -5
- package/lib/commands/agent-kit-help.js +21 -7
- package/lib/commands/agent-kit-sessions.js +126 -0
- package/lib/commands/agent-kit-setup.js +44 -0
- package/lib/commands/agent-kit.js +5 -79
- package/lib/commands/dev-init.js +21 -23
- package/lib/commands/dev-refresh.js +2 -2
- package/lib/commands/dev-registry.js +155 -0
- package/lib/commands/dev-show-display.js +10 -2
- package/lib/commands/governance-verify-external.js +12 -5
- package/lib/commands/repair-datasource.js +9 -9
- package/lib/commands/repair.js +45 -8
- package/lib/commands/setup-image-refresh.js +26 -0
- package/lib/commands/upload-qualification-client.js +56 -11
- package/lib/commands/upload-qualification-poll.js +51 -22
- package/lib/commands/wizard-dataplane.js +16 -52
- package/lib/core/config-attach-extensions.js +4 -1
- package/lib/datasource/datasource-validate-summary.js +8 -5
- package/lib/datasource/sync-defaults.js +53 -0
- package/lib/deployment/installation/azure-infra-stage.js +50 -9
- package/lib/deployment/installation/azure-preflight.js +63 -7
- package/lib/deployment/installation/azure-readiness.js +59 -7
- package/lib/deployment/installation/environment-auth.js +60 -20
- package/lib/deployment/installation/environment-stage.js +9 -5
- package/lib/deployment/installation/infra-catalog.js +57 -5
- package/lib/deployment/installation/infra-manifest.js +22 -2
- package/lib/deployment/installation/infra-projection.js +14 -3
- package/lib/deployment/installation/registry-auth-mode.js +121 -0
- package/lib/deployment/installation/stage-input.js +21 -6
- package/lib/deployment/marketplace-environment.js +26 -1
- package/lib/generator/builders.js +8 -9
- package/lib/generator/index.js +23 -12
- package/lib/integration-definition/apply.js +2 -2
- package/lib/internal/fs-real-sync.js +4 -1
- package/lib/programmatic/bearer-auth.js +3 -62
- package/lib/programmatic/builder-api-startup.js +51 -0
- package/lib/programmatic/builder-miso-runtime.js +167 -0
- package/lib/programmatic/openapi-descriptions.js +3 -3
- package/lib/programmatic/openapi-schema-descriptions-core.js +3 -3
- package/lib/programmatic/openapi-spec.js +2 -1
- package/lib/programmatic/route-handler-map.js +3 -2
- package/lib/schema/environment-deploy-request.schema.json +1 -1
- package/lib/schema/external-datasource.schema.json +20 -5
- package/lib/schema/infra-parameter.schema.json +1 -1
- package/lib/schema/infra.parameter.yaml +2 -1
- package/lib/schema/infrastructure-schema.json +200 -3
- package/lib/schema/installation-environment.schema.json +1 -1
- package/lib/schema/wizard-config.schema.json +1 -1
- package/lib/utils/app-register-config.js +2 -4
- package/lib/utils/build-resolve-image.js +2 -21
- package/lib/utils/cli-utils.js +8 -1
- package/lib/utils/config-paths.js +24 -29
- package/lib/utils/config-registry-preference.js +113 -0
- package/lib/utils/dev-init-ssh-merge.js +5 -4
- package/lib/utils/dev-user-groups.js +2 -1
- package/lib/utils/docker-build.js +53 -19
- package/lib/utils/docker-daemon-tls-ca.js +2 -2
- package/lib/utils/env-map.js +23 -6
- package/lib/utils/error-formatters/validation-errors.js +25 -5
- package/lib/utils/help-builder.js +4 -3
- package/lib/utils/image-tags.js +88 -0
- package/lib/utils/promote-dev-docker-image.js +6 -0
- package/lib/utils/registry-credentials.js +236 -0
- package/lib/utils/resolve-docker-image-ref.js +78 -9
- package/lib/utils/secrets-helpers.js +9 -1
- package/lib/utils/token-manager.js +29 -36
- package/lib/utils/variable-transformer.js +2 -4
- package/package.json +4 -2
- package/templates/agent-kit/instructions/AGENTKIT.md +39 -2
- package/templates/agent-kit/instructions/root.AGENTS.md +40 -1
- package/templates/agent-kit/skills/shared/hosts.md +23 -5
- package/templates/agent-kit/workspace/README.md +14 -1
- package/templates/applications/builder-api/application.yaml +1 -1
- package/templates/applications/dataplane/application.yaml +1 -1
- package/templates/applications/dataplane/env.template +1 -1
- package/templates/applications/miso-controller/application.yaml +1 -1
- package/templates/applications/miso-controller/env.template +1 -1
- package/templates/external-system/external-datasource.yaml.hbs +7 -6
- package/templates/marketplace/main.json +440 -82
|
@@ -20,6 +20,9 @@ const LICENSE_EDITIONS = Object.freeze(['COMMUNITY', 'STANDARD', 'ENTERPRISE']);
|
|
|
20
20
|
const DEFAULT_LICENSE_EDITION = 'COMMUNITY';
|
|
21
21
|
const ONBOARDING_MODES = Object.freeze(['saasDirect', 'customerOwned']);
|
|
22
22
|
const DEFAULT_ONBOARDING_MODE = 'saasDirect';
|
|
23
|
+
const ACR_AUTH_MODES = Object.freeze(['auto', 'mi', 'credentials']);
|
|
24
|
+
const DEFAULT_ACR_AUTH = 'auto';
|
|
25
|
+
const AZURE_LOCATION = /^[a-z0-9]{2,40}$/;
|
|
23
26
|
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
24
27
|
|
|
25
28
|
/**
|
|
@@ -36,6 +39,45 @@ function normalizeLicenseEdition(value) {
|
|
|
36
39
|
throw new Error(`License edition must be one of ${LICENSE_EDITIONS.join(', ')} or an Azure Marketplace subscription ID`);
|
|
37
40
|
}
|
|
38
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Normalizes requested ACR image-pull auth: auto (default), mi, or credentials.
|
|
44
|
+
* `admin` (and its spellings) is a deprecated alias for `credentials`.
|
|
45
|
+
* @param {string} value - Raw value
|
|
46
|
+
* @returns {string} Canonical mode
|
|
47
|
+
*/
|
|
48
|
+
function normalizeAcrAuth(value) {
|
|
49
|
+
const raw = String(value ?? '').trim();
|
|
50
|
+
const aliases = {
|
|
51
|
+
auto: 'auto',
|
|
52
|
+
mi: 'mi',
|
|
53
|
+
'managed-identity': 'mi',
|
|
54
|
+
managedidentity: 'mi',
|
|
55
|
+
credentials: 'credentials',
|
|
56
|
+
admin: 'credentials',
|
|
57
|
+
adminuser: 'credentials',
|
|
58
|
+
'admin-user': 'credentials',
|
|
59
|
+
password: 'credentials'
|
|
60
|
+
};
|
|
61
|
+
const mode = aliases[raw.toLowerCase()];
|
|
62
|
+
if (!mode) {
|
|
63
|
+
throw new Error(`ACR auth must be one of ${ACR_AUTH_MODES.join(', ')} (deprecated alias: admin = credentials)`);
|
|
64
|
+
}
|
|
65
|
+
return mode;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Validates an Azure region name (for example swedencentral).
|
|
70
|
+
* @param {string} value - Raw value
|
|
71
|
+
* @returns {string} Lower-case region
|
|
72
|
+
*/
|
|
73
|
+
function normalizeAzureLocation(value) {
|
|
74
|
+
const location = String(value ?? '').trim().toLowerCase();
|
|
75
|
+
if (!AZURE_LOCATION.test(location)) {
|
|
76
|
+
throw new Error(`--location must be an Azure region name such as swedencentral (letters and digits only); got "${value}"`);
|
|
77
|
+
}
|
|
78
|
+
return location;
|
|
79
|
+
}
|
|
80
|
+
|
|
39
81
|
const PARAMETER_CATALOG = Object.freeze({
|
|
40
82
|
infrastructureNameOverride: { kind: 'file', source: 'user', description: 'Optional infrastructure-name override; empty uses generated naming.' },
|
|
41
83
|
location: { kind: 'file', source: 'user', required: true, description: 'Azure region for shared infrastructure.' },
|
|
@@ -50,11 +92,14 @@ const PARAMETER_CATALOG = Object.freeze({
|
|
|
50
92
|
keycloakVerifyAudience: { kind: 'file', source: 'templateDefault', description: 'Enable Keycloak audience verification.' },
|
|
51
93
|
encryptionKey: { kind: 'secretEnv', env: 'ENCRYPTION_KEY', required: true, description: 'Application/data encryption key.' },
|
|
52
94
|
acrLoginServer: { kind: 'acrLookup', field: 'loginServer', description: 'Resolved from acrName; not entered separately.' },
|
|
53
|
-
acrAdminUsername: { kind: 'fixed', value: '', source: 'managedIdentityMode', description: '
|
|
54
|
-
acrAdminPassword: { kind: 'fixed', value: '', source: 'managedIdentityMode', description: '
|
|
55
|
-
acrResourceId: { kind: 'acrLookup', field: 'id', description: 'Resolved from acrName;
|
|
56
|
-
deployRoleAssignments: { kind: 'file', source: 'templateDefault', description: 'Deploy
|
|
95
|
+
acrAdminUsername: { kind: 'fixed', value: '', source: 'managedIdentityMode', description: 'Empty in the workspace; credential mode fills it only in the temporary ARM parameters file (never committed).' },
|
|
96
|
+
acrAdminPassword: { kind: 'fixed', value: '', source: 'managedIdentityMode', description: 'Empty in the workspace; credential mode fills it only in the temporary ARM parameters file (never committed).' },
|
|
97
|
+
acrResourceId: { kind: 'acrLookup', field: 'id', description: 'Resolved from acrName; mi mode grants AcrPull to both web app identities.' },
|
|
98
|
+
deployRoleAssignments: { kind: 'file', source: 'templateDefault', description: 'Deploy Contributor role assignments in the installation resource group. Needed in every mode: without it the controller cannot create the environment resources its second stage deploys.' },
|
|
99
|
+
deployRegistryRoleAssignment: { kind: 'parameterDefault', defaultFrom: 'deployRoleAssignments', source: 'templateDefault', description: 'Deploy the AcrPull grant on the container registry. False while credential image pull is in effect, because that grant lives in the registry resource group, which a customer installation cannot reach; restored for managed identity.' },
|
|
57
100
|
moriBaseUrl: { kind: 'file', source: 'registration', required: true, description: 'Mori service base URL.' },
|
|
101
|
+
moriLicensePublicKey: { kind: 'file', source: 'registration', required: false, description: 'Mori licence issuer public key (SPKI PEM); empty leaves the installation unpinned and it then refuses every licence.' },
|
|
102
|
+
moriLicenseIssuer: { kind: 'file', source: 'registration', required: false, description: 'Expected iss claim on a Mori licence.' },
|
|
58
103
|
moriCallbackApiKey: { kind: 'secretEnv', env: 'MORI_CALLBACK_API_KEY', required: true, description: 'Mori callback authentication key.' },
|
|
59
104
|
entraCallbackStateSecret: { kind: 'deferred', description: 'Not collected or sent by infra; issued/configured during later Entra provider onboarding.' },
|
|
60
105
|
entraIdClientId: { kind: 'deferred', description: 'Not collected or sent by infra; issued/configured during later Entra provider onboarding.' },
|
|
@@ -62,6 +107,7 @@ const PARAMETER_CATALOG = Object.freeze({
|
|
|
62
107
|
entraIdTenantId: { kind: 'deferred', description: 'Customer Entra provider tenant; distinct from the Azure subscription directory tenant and configured later.' },
|
|
63
108
|
licenseEdition: { kind: 'license', source: 'cliOrFile', description: 'License requested from Mori during tenant activation: COMMUNITY, STANDARD or ENTERPRISE, or an Azure Marketplace subscription ID. Standard/Enterprise by name is a 30-day evaluation.' },
|
|
64
109
|
onboardingMode: { kind: 'onboarding', source: 'workspacePolicy', description: 'Miso onboarding mode (ONBOARDING_MODE); Builder installations use saasDirect, Marketplace customer installations use customerOwned.' },
|
|
110
|
+
capacityProfile: { kind: 'capacity', source: 'workspacePreset', description: 'Infrastructure sizing profile (s, m, l, xl) selecting compute and PostgreSQL SKUs. Derived from the workspace preset so the two cannot drift; set it with --preset.' },
|
|
65
111
|
misoImageTag: { kind: 'file', source: 'templateDefault', description: 'Miso container image tag.' },
|
|
66
112
|
keycloakImageTag: { kind: 'file', source: 'templateDefault', description: 'Keycloak container image tag.' }
|
|
67
113
|
});
|
|
@@ -85,7 +131,9 @@ const OUTPUT_CATALOG = Object.freeze({
|
|
|
85
131
|
keycloakWebAppUrl: 'Keycloak web application URL.',
|
|
86
132
|
keycloakRealmUrl: 'Keycloak realm URL.',
|
|
87
133
|
keycloakAdminUser: 'Keycloak administrator user name.',
|
|
88
|
-
portalUrl: 'Azure portal link for the installation resource group.'
|
|
134
|
+
portalUrl: 'Azure portal link for the installation resource group.',
|
|
135
|
+
azureOpenAiCreated: 'Whether Phase-1 created the Azure OpenAI account in the installation region.',
|
|
136
|
+
azureOpenAiSkipReason: 'Why Azure OpenAI was not created; empty when it was. An unsupported region skips the optional account instead of relocating it.'
|
|
89
137
|
});
|
|
90
138
|
|
|
91
139
|
/** Mori service key name written by DOCKER registration. */
|
|
@@ -159,7 +207,11 @@ module.exports = {
|
|
|
159
207
|
DEFAULT_LICENSE_EDITION,
|
|
160
208
|
ONBOARDING_MODES,
|
|
161
209
|
DEFAULT_ONBOARDING_MODE,
|
|
210
|
+
ACR_AUTH_MODES,
|
|
211
|
+
DEFAULT_ACR_AUTH,
|
|
162
212
|
normalizeLicenseEdition,
|
|
213
|
+
normalizeAcrAuth,
|
|
214
|
+
normalizeAzureLocation,
|
|
163
215
|
buildEnvTemplate,
|
|
164
216
|
assertTemplateCoverage
|
|
165
217
|
};
|
|
@@ -8,9 +8,10 @@
|
|
|
8
8
|
|
|
9
9
|
const crypto = require('crypto');
|
|
10
10
|
const {
|
|
11
|
-
PARAMETER_CATALOG, OUTPUT_CATALOG, DEFAULT_LICENSE_EDITION, DEFAULT_ONBOARDING_MODE
|
|
11
|
+
PARAMETER_CATALOG, OUTPUT_CATALOG, DEFAULT_LICENSE_EDITION, DEFAULT_ONBOARDING_MODE, DEFAULT_ACR_AUTH
|
|
12
12
|
} = require('./infra-catalog');
|
|
13
13
|
|
|
14
|
+
const ACR_AUTH_DESCRIPTION = 'Image pull auth: auto (default; local dev set-registry pair for the ACR login server, else managed identity), mi (AcrPull), or credentials (local pair, then authorized ACR admin lookup). Credentials are never committed.';
|
|
14
15
|
const INFRA_SCHEMA_VERSION = '2.0.0';
|
|
15
16
|
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
16
17
|
|
|
@@ -41,17 +42,34 @@ function parameterEntry(name, template) {
|
|
|
41
42
|
return entry(DEFAULT_LICENSE_EDITION, spec.source, spec.description);
|
|
42
43
|
case 'onboarding':
|
|
43
44
|
return entry(DEFAULT_ONBOARDING_MODE, spec.source, spec.description);
|
|
45
|
+
case 'capacity':
|
|
46
|
+
// Mirrors the workspace preset; azure-infra-stage keeps them in step on every run.
|
|
47
|
+
return entry('s', spec.source, spec.description);
|
|
48
|
+
case 'parameterDefault':
|
|
49
|
+
return entry(templateDefault(template, spec.defaultFrom), spec.source, spec.description);
|
|
44
50
|
default:
|
|
45
51
|
return entry(templateDefault(template, name), spec.source, spec.description, spec.required ? { required: true } : {});
|
|
46
52
|
}
|
|
47
53
|
}
|
|
48
54
|
|
|
55
|
+
/** Sources written by Builder workflows that must survive regeneration. */
|
|
56
|
+
const STICKY_SOURCES = Object.freeze(['cli', 'acrAuthCredentials', 'acrAuthAdmin']);
|
|
57
|
+
|
|
58
|
+
function preservedState(previous) {
|
|
59
|
+
const kept = { value: previous.value };
|
|
60
|
+
if (STICKY_SOURCES.includes(previous.source)) kept.source = previous.source;
|
|
61
|
+
for (const key of ['restoreValue', 'restoreSource']) {
|
|
62
|
+
if (previous[key] !== undefined) kept[key] = previous[key];
|
|
63
|
+
}
|
|
64
|
+
return kept;
|
|
65
|
+
}
|
|
66
|
+
|
|
49
67
|
function buildParameters(template, existing = {}) {
|
|
50
68
|
const parameters = {};
|
|
51
69
|
for (const name of Object.keys(PARAMETER_CATALOG)) {
|
|
52
70
|
const fresh = parameterEntry(name, template);
|
|
53
71
|
const workflowOwned = ['fixed', 'deferred', 'secretEnv'].includes(PARAMETER_CATALOG[name].kind);
|
|
54
|
-
parameters[name] = existing[name] && !workflowOwned ? { ...fresh,
|
|
72
|
+
parameters[name] = existing[name] && !workflowOwned ? { ...fresh, ...preservedState(existing[name]) } : fresh;
|
|
55
73
|
}
|
|
56
74
|
return parameters;
|
|
57
75
|
}
|
|
@@ -64,6 +82,7 @@ function buildOutputs(existing = {}) {
|
|
|
64
82
|
function azureSections(template, existing) {
|
|
65
83
|
return {
|
|
66
84
|
acrName: existing.acrName || entry('', 'user', 'Required private ACR name. Builder resolves and persists its login server and resource ID using the authenticated Azure account.', { required: true }),
|
|
85
|
+
acrAuth: existing.acrAuth || entry(DEFAULT_ACR_AUTH, 'cliOrFile', ACR_AUTH_DESCRIPTION),
|
|
67
86
|
allowMyIp: existing.allowMyIp || entry(false, 'cliOrFile', 'Default-off internal test access. When enabled, Builder resolves and audits one public IPv4 /32.', { resolvedCidr: null, resolvedAt: null, resolver: null }),
|
|
68
87
|
preset: existing.preset || entry('s', 'cliOrFile', 'Shared infrastructure sizing preset.'),
|
|
69
88
|
parameters: buildParameters(template, existing.parameters),
|
|
@@ -156,6 +175,7 @@ function missingRequiredFields(doc) {
|
|
|
156
175
|
|
|
157
176
|
module.exports = {
|
|
158
177
|
INFRA_SCHEMA_VERSION,
|
|
178
|
+
ACR_AUTH_DESCRIPTION,
|
|
159
179
|
buildInfraDocument,
|
|
160
180
|
missingRequiredFields
|
|
161
181
|
};
|
|
@@ -3,12 +3,15 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* @fileoverview Projects `environment-infra.json` plus resolved workspace secrets into standard
|
|
5
5
|
* ARM deploymentParameters, and applies the default-off current-IP access option.
|
|
6
|
-
* Deferred tenant-activation parameters are never projected.
|
|
6
|
+
* Deferred tenant-activation parameters are never projected. ACR admin credentials (password mode)
|
|
7
|
+
* are injected only into the temporary parameters file and never enter the input hash.
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
10
|
const { PARAMETER_CATALOG } = require('./infra-catalog');
|
|
10
11
|
const { sha256 } = require('./workspace-files');
|
|
11
12
|
|
|
13
|
+
const ARM_CREDENTIAL_PARAMS = Object.freeze(new Set(['acrAdminUsername', 'acrAdminPassword']));
|
|
14
|
+
|
|
12
15
|
function projectParameter(name, doc, context) {
|
|
13
16
|
const spec = PARAMETER_CATALOG[name];
|
|
14
17
|
switch (spec.kind) {
|
|
@@ -27,6 +30,12 @@ function projectParameter(name, doc, context) {
|
|
|
27
30
|
}
|
|
28
31
|
return context.acr[spec.field];
|
|
29
32
|
case 'fixed':
|
|
33
|
+
if (name === 'acrAdminUsername' && context.registryCredentials) {
|
|
34
|
+
return context.registryCredentials.username;
|
|
35
|
+
}
|
|
36
|
+
if (name === 'acrAdminPassword' && context.registryCredentials) {
|
|
37
|
+
return context.registryCredentials.password;
|
|
38
|
+
}
|
|
30
39
|
return spec.value;
|
|
31
40
|
default:
|
|
32
41
|
return doc.parameters[name].value;
|
|
@@ -39,6 +48,7 @@ function projectParameter(name, doc, context) {
|
|
|
39
48
|
* @param {Object} context - Resolution context
|
|
40
49
|
* @param {Object.<string,string>} context.env - Resolved workspace .env values
|
|
41
50
|
* @param {{ id: string, loginServer: string }} context.acr - Resolved registry
|
|
51
|
+
* @param {{ username: string, password: string }|null|undefined} context.registryCredentials - Credential-mode pull creds (never hashed)
|
|
42
52
|
* @param {string} context.templateHash - SHA-256 of the deployed template
|
|
43
53
|
* @returns {{ document: Object, inputHash: string }}
|
|
44
54
|
*/
|
|
@@ -49,9 +59,10 @@ function buildArmParameters(doc, context) {
|
|
|
49
59
|
const value = projectParameter(name, doc, context);
|
|
50
60
|
if (value === undefined) continue;
|
|
51
61
|
parameters[name] = { value };
|
|
52
|
-
|
|
53
|
-
if (
|
|
62
|
+
const catalog = PARAMETER_CATALOG[name];
|
|
63
|
+
if (catalog.kind !== 'secretEnv' && !ARM_CREDENTIAL_PARAMS.has(name)) nonSecret[name] = value;
|
|
54
64
|
}
|
|
65
|
+
nonSecret.acrAuth = doc.acrAuth?.effective || doc.acrAuth?.value || 'auto';
|
|
55
66
|
return {
|
|
56
67
|
document: {
|
|
57
68
|
$schema: 'https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#',
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Image-pull authentication for CLI-managed AZURE infra. The requested mode
|
|
5
|
+
* (`auto|mi|credentials`, deprecated `admin` = credentials) resolves to an effective `mi` or
|
|
6
|
+
* `credentials` mode. Credential values come from the primary user's `dev set-registry` pair for the
|
|
7
|
+
* resolved ACR login server, or (explicit credentials only) from an authorized ACR admin lookup.
|
|
8
|
+
* They live only in memory and in the temporary ARM parameters file.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const { normalizeAcrAuth } = require('./infra-catalog');
|
|
12
|
+
const { ACR_AUTH_DESCRIPTION } = require('./infra-manifest');
|
|
13
|
+
|
|
14
|
+
const FORCED_ROLE_SOURCES = Object.freeze(['acrAuthCredentials', 'acrAuthAdmin']);
|
|
15
|
+
|
|
16
|
+
function restoreForcedRole(roles) {
|
|
17
|
+
const { restoreValue, restoreSource, ...rest } = roles;
|
|
18
|
+
return {
|
|
19
|
+
...rest,
|
|
20
|
+
value: restoreValue === undefined ? true : restoreValue,
|
|
21
|
+
source: restoreSource || 'templateDefault'
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function restoreLegacyContributorRole(doc) {
|
|
26
|
+
const roles = doc.parameters.deployRoleAssignments;
|
|
27
|
+
if (roles && FORCED_ROLE_SOURCES.includes(roles.source)) {
|
|
28
|
+
doc.parameters.deployRoleAssignments = restoreForcedRole(roles);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Normalizes and records the requested mode (CLI wins over the workspace value).
|
|
34
|
+
* @param {Object} doc - Infra manifest (mutated)
|
|
35
|
+
* @param {Object} input - Stage input (`acrAuth` from CLI)
|
|
36
|
+
* @returns {string} Requested mode
|
|
37
|
+
*/
|
|
38
|
+
function applyRequestedAcrAuth(doc, input) {
|
|
39
|
+
const fromCli = input.acrAuth !== undefined;
|
|
40
|
+
const mode = normalizeAcrAuth(fromCli ? input.acrAuth : (doc.acrAuth?.value || 'auto'));
|
|
41
|
+
doc.acrAuth = {
|
|
42
|
+
...(doc.acrAuth || {}),
|
|
43
|
+
value: mode,
|
|
44
|
+
source: fromCli ? 'cli' : (doc.acrAuth?.source || 'cliOrFile'),
|
|
45
|
+
description: ACR_AUTH_DESCRIPTION
|
|
46
|
+
};
|
|
47
|
+
return mode;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function localSetupHint(host) {
|
|
51
|
+
return `Run "aifabrix dev set-registry ${host} --username <username> --password-stdin", or use --acr-auth mi`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function resolveAzureFallback(doc, acr, az, fetchAdmin) {
|
|
55
|
+
try {
|
|
56
|
+
return fetchAdmin(doc, acr, az);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
throw new Error(`No registry credentials are available for ${acr.loginServer}: ${error.message}. ${localSetupHint(acr.loginServer)}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Resolves the effective pull mode and ephemeral credentials. A partial local pair always fails.
|
|
64
|
+
* @param {Object} doc - Infra manifest with a normalized `acrAuth`
|
|
65
|
+
* @param {{ loginServer: string, adminUserEnabled?: boolean }} acr - Resolved registry
|
|
66
|
+
* @param {Function} az - Azure CLI runner
|
|
67
|
+
* @param {Object} deps - `loadRegistryCredentials(host)` and `resolveAcrAdminCredentials(doc, acr, az)`
|
|
68
|
+
* @returns {Promise<{ mode: 'mi'|'credentials', credentials: ({username: string, password: string}|null), source: string }>}
|
|
69
|
+
*/
|
|
70
|
+
async function resolveRegistryAuth(doc, acr, az, deps) {
|
|
71
|
+
const requested = normalizeAcrAuth(doc.acrAuth?.value || 'auto');
|
|
72
|
+
if (requested === 'mi') return { mode: 'mi', credentials: null, source: 'managedIdentity' };
|
|
73
|
+
const local = await deps.loadRegistryCredentials(acr.loginServer);
|
|
74
|
+
if (local) return { mode: 'credentials', credentials: local, source: 'localRegistryCredentials' };
|
|
75
|
+
if (requested === 'auto') return { mode: 'mi', credentials: null, source: 'managedIdentity' };
|
|
76
|
+
const azure = await resolveAzureFallback(doc, acr, az, deps.resolveAcrAdminCredentials);
|
|
77
|
+
return { mode: 'credentials', credentials: azure, source: 'acrAdminLookup' };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Records the effective mode and keeps the registry role assignment consistent with it.
|
|
82
|
+
*
|
|
83
|
+
* Credential mode turns off only `deployRegistryRoleAssignment` -- the AcrPull grant, which lives
|
|
84
|
+
* in the registry's own resource group and, for a customer installation, in the vendor's tenant
|
|
85
|
+
* where Azure RBAC cannot reach. It deliberately leaves `deployRoleAssignments` alone: those are
|
|
86
|
+
* Contributor grants in the resource group the installation is creating, which the deployer owns.
|
|
87
|
+
*
|
|
88
|
+
* Turning both off is what ce-12 and ce-13 did, and it broke the installation in a way that took
|
|
89
|
+
* two attempts to see. Without Contributor on its own resource group the controller cannot create
|
|
90
|
+
* anything, so its second stage failed every module, no resource bindings were recorded, and both
|
|
91
|
+
* application deployments refused with "Environment 'dev' has no recorded placement".
|
|
92
|
+
*
|
|
93
|
+
* @param {Object} doc - Infra manifest (mutated)
|
|
94
|
+
* @param {'mi'|'credentials'} mode - Effective mode
|
|
95
|
+
*/
|
|
96
|
+
function applyEffectiveAcrAuth(doc, mode) {
|
|
97
|
+
doc.acrAuth = { ...doc.acrAuth, effective: mode };
|
|
98
|
+
restoreLegacyContributorRole(doc);
|
|
99
|
+
const roles = doc.parameters.deployRegistryRoleAssignment
|
|
100
|
+
|| doc.parameters.deployRoleAssignments;
|
|
101
|
+
const forced = FORCED_ROLE_SOURCES.includes(roles.source);
|
|
102
|
+
if (mode === 'credentials') {
|
|
103
|
+
if (forced) return;
|
|
104
|
+
doc.parameters.deployRegistryRoleAssignment = {
|
|
105
|
+
...roles, value: false, source: 'acrAuthCredentials', restoreValue: roles.value, restoreSource: roles.source
|
|
106
|
+
};
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (!forced) return;
|
|
110
|
+
doc.parameters.deployRegistryRoleAssignment = restoreForcedRole(roles);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* @param {Object} doc - Infra manifest
|
|
115
|
+
* @returns {boolean} True when the last resolved pull mode uses registry credentials
|
|
116
|
+
*/
|
|
117
|
+
function usesRegistryCredentials(doc) {
|
|
118
|
+
return doc.acrAuth?.effective === 'credentials';
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
module.exports = { applyRequestedAcrAuth, resolveRegistryAuth, applyEffectiveAcrAuth, usesRegistryCredentials };
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
const path = require('path');
|
|
10
|
-
const { normalizeLicenseEdition } = require('./infra-catalog');
|
|
10
|
+
const { normalizeLicenseEdition, normalizeAcrAuth, normalizeAzureLocation } = require('./infra-catalog');
|
|
11
11
|
|
|
12
12
|
const WORKSPACE_STAGES = Object.freeze(['register', 'infra', 'dev', 'tst', 'pro']);
|
|
13
13
|
const ENVIRONMENT_STAGES = Object.freeze(['dev', 'tst', 'pro']);
|
|
@@ -102,11 +102,24 @@ function assertInfraOnlyOptions(stage, options) {
|
|
|
102
102
|
if (options.allowMyIp && stage !== 'infra') {
|
|
103
103
|
throw new Error('--allow-my-ip is supported only with --stage infra for AZURE installations');
|
|
104
104
|
}
|
|
105
|
-
if (options.licenseEdition
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
if (options.licenseEdition !== undefined) {
|
|
106
|
+
if (stage !== 'infra') {
|
|
107
|
+
throw new Error('--license-edition is supported only with --stage infra for AZURE installations');
|
|
108
|
+
}
|
|
109
|
+
normalizeLicenseEdition(options.licenseEdition);
|
|
110
|
+
}
|
|
111
|
+
if (options.acrAuth !== undefined) {
|
|
112
|
+
if (stage !== 'infra') {
|
|
113
|
+
throw new Error('--acr-auth is supported only with --stage infra for AZURE installations');
|
|
114
|
+
}
|
|
115
|
+
normalizeAcrAuth(options.acrAuth);
|
|
116
|
+
}
|
|
117
|
+
if (options.location !== undefined) {
|
|
118
|
+
if (stage !== 'infra') {
|
|
119
|
+
throw new Error('--location is supported only with --stage infra (later environments inherit the saved region)');
|
|
120
|
+
}
|
|
121
|
+
normalizeAzureLocation(options.location);
|
|
108
122
|
}
|
|
109
|
-
normalizeLicenseEdition(options.licenseEdition);
|
|
110
123
|
}
|
|
111
124
|
|
|
112
125
|
function assertStageScopedOptions(stage, options) {
|
|
@@ -137,7 +150,7 @@ function assertStageScopedOptions(stage, options) {
|
|
|
137
150
|
* @param {Object} options - Commander options
|
|
138
151
|
* @param {string|undefined} persistedVendor - Vendor recorded in an existing workspace
|
|
139
152
|
* @returns {{ resourceGroup: string, stage: string, vendor: string, allowMyIp: boolean, licenseEdition: string|undefined,
|
|
140
|
-
* preset: string|undefined, controllerUrl: string|undefined, misoApiKeyEnv: string|undefined }}
|
|
153
|
+
* acrAuth: string|undefined, location: string|undefined, preset: string|undefined, controllerUrl: string|undefined, misoApiKeyEnv: string|undefined }}
|
|
141
154
|
*/
|
|
142
155
|
function resolveStageInput(resourceGroupKey, options, persistedVendor) {
|
|
143
156
|
const resourceGroup = validateResourceGroupKey(resourceGroupKey);
|
|
@@ -150,6 +163,8 @@ function resolveStageInput(resourceGroupKey, options, persistedVendor) {
|
|
|
150
163
|
vendor,
|
|
151
164
|
allowMyIp: options.allowMyIp === true,
|
|
152
165
|
licenseEdition: options.licenseEdition,
|
|
166
|
+
acrAuth: options.acrAuth,
|
|
167
|
+
location: options.location,
|
|
153
168
|
preset: options.preset,
|
|
154
169
|
controllerUrl: options.controllerUrl,
|
|
155
170
|
misoApiKeyEnv: options.misoApiKeyEnv
|
|
@@ -9,6 +9,7 @@ const crypto = require('crypto');
|
|
|
9
9
|
const { spawnSync } = require('child_process');
|
|
10
10
|
const config = require('../core/config');
|
|
11
11
|
const { getRuntimeConfigDir } = require('../core/config-runtime-paths');
|
|
12
|
+
const logger = require('../utils/logger');
|
|
12
13
|
|
|
13
14
|
const SHIPPED_TEMPLATE = path.resolve(__dirname, '../../templates/marketplace/main.json');
|
|
14
15
|
// Resolved per call (not a module-load constant) so it honors AIFABRIX_HOME/AIFABRIX_CONFIG
|
|
@@ -273,8 +274,28 @@ async function deployMarketplaceEnvironment(envKey, options, dependencies = {})
|
|
|
273
274
|
return deployPlatform({ ...input, controllerUrl: options.controllerUrl }, dependencies);
|
|
274
275
|
}
|
|
275
276
|
|
|
277
|
+
/**
|
|
278
|
+
* Attempts allowed for the controller, at 10-second intervals.
|
|
279
|
+
*
|
|
280
|
+
* The controller runs first-time onboarding inside startup, and on Azure that onboarding is
|
|
281
|
+
* normally interrupted once — the template applies post-Key-Vault app settings and restarts the
|
|
282
|
+
* site — so the retry runs too. A clean installation was measured taking 720 seconds to pass its
|
|
283
|
+
* warm-up probe. The old 30 attempts gave 5 minutes and reported `readiness did not pass` for
|
|
284
|
+
* installations that were healthy, which is worse than a slow wait: it leaves `controllerUrl`
|
|
285
|
+
* unrecorded, so `--stage dev` then refuses with "Shared infrastructure is not deployed".
|
|
286
|
+
*
|
|
287
|
+
* 180 attempts is 30 minutes, matching WEBSITES_CONTAINER_START_TIME_LIMIT. Waiting longer than
|
|
288
|
+
* App Service itself would is pointless; waiting less than it declares is what caused this.
|
|
289
|
+
*/
|
|
290
|
+
const CONTROLLER_READINESS_ATTEMPTS = 180;
|
|
291
|
+
|
|
276
292
|
async function probeController(controllerUrl) {
|
|
277
|
-
return probeHttps(
|
|
293
|
+
return probeHttps(
|
|
294
|
+
controllerUrl,
|
|
295
|
+
'/api/v1/health/readiness',
|
|
296
|
+
'Controller',
|
|
297
|
+
CONTROLLER_READINESS_ATTEMPTS
|
|
298
|
+
);
|
|
278
299
|
}
|
|
279
300
|
|
|
280
301
|
/**
|
|
@@ -297,6 +318,10 @@ async function probeHttps(baseUrl, pathname, label, attempts = 30) {
|
|
|
297
318
|
} catch (_) {
|
|
298
319
|
// App Service startup can lag successful ARM provisioning.
|
|
299
320
|
}
|
|
321
|
+
// A wait measured in minutes should say so, or it is indistinguishable from a hang.
|
|
322
|
+
if (attempt > 0 && attempt % 6 === 0) {
|
|
323
|
+
logger.log(` waiting for ${label} readiness… ${attempt * 10}s elapsed`);
|
|
324
|
+
}
|
|
300
325
|
await new Promise(resolve => setTimeout(resolve, 10000));
|
|
301
326
|
}
|
|
302
327
|
throw new Error(`${label} readiness did not pass at ${url.origin}; inspect App Service before resuming`);
|
|
@@ -41,16 +41,15 @@ function filterConfigurationByRegistryMode(configuration, _registryMode) {
|
|
|
41
41
|
return Array.isArray(configuration) ? configuration : [];
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Deployment image reference using the shared registry precedence
|
|
46
|
+
* (installation `registry` in config.yaml, then manifest `image.registry`).
|
|
47
|
+
* @param {Object} variables - Application variables
|
|
48
|
+
* @returns {string}
|
|
49
|
+
*/
|
|
44
50
|
function buildImageReference(variables) {
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
const tag = variables.image?.tag || 'latest';
|
|
48
|
-
|
|
49
|
-
if (registry) {
|
|
50
|
-
return `${registry}/${imageName}:${tag}`;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return `${imageName}:${tag}`;
|
|
51
|
+
const { buildManifestImageReference } = require('../utils/resolve-docker-image-ref');
|
|
52
|
+
return buildManifestImageReference(variables, 'app');
|
|
54
53
|
}
|
|
55
54
|
|
|
56
55
|
/**
|
package/lib/generator/index.js
CHANGED
|
@@ -117,35 +117,46 @@ function substitutePortInDeployment(obj, portNumber) {
|
|
|
117
117
|
/** Regex to find ${VAR} placeholders for env substitution */
|
|
118
118
|
const ENV_VAR_PLACEHOLDER_REGEX = /\$\{([^}]+)\}/g;
|
|
119
119
|
|
|
120
|
+
function replaceDeploymentEnvString(value, envVarMap, fieldPath) {
|
|
121
|
+
if (fieldPath === 'frontDoorRouting.host') return value;
|
|
122
|
+
return value.replace(ENV_VAR_PLACEHOLDER_REGEX, (match, varName) =>
|
|
123
|
+
Object.prototype.hasOwnProperty.call(envVarMap, varName) ? String(envVarMap[varName]) : match);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function substituteEnvVarsInArray(items, envVarMap, fieldPath) {
|
|
127
|
+
for (let i = 0; i < items.length; i++) {
|
|
128
|
+
const itemPath = `${fieldPath}[${i}]`;
|
|
129
|
+
if (typeof items[i] === 'string') {
|
|
130
|
+
items[i] = replaceDeploymentEnvString(items[i], envVarMap, itemPath);
|
|
131
|
+
} else if (typeof items[i] === 'object' && items[i] !== null) {
|
|
132
|
+
substituteEnvVarsInDeployment(items[i], envVarMap, itemPath);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
120
137
|
/**
|
|
121
138
|
* Recursively replaces ${VAR} with envVarMap[VAR] in all string values of obj (in-place).
|
|
122
139
|
* Only substitutes when VAR is a key in envVarMap (from env-config.yaml / config).
|
|
123
140
|
*
|
|
124
141
|
* @param {Object} obj - Deployment manifest or any nested object
|
|
125
142
|
* @param {Object} envVarMap - Flat map of variable names to values (e.g. from buildEnvVarMap('docker'))
|
|
143
|
+
* @param {string} [fieldPath=''] - Internal traversal path used to preserve the portable front door host template
|
|
126
144
|
*/
|
|
127
|
-
function substituteEnvVarsInDeployment(obj, envVarMap) {
|
|
145
|
+
function substituteEnvVarsInDeployment(obj, envVarMap, fieldPath = '') {
|
|
128
146
|
if (!envVarMap || typeof envVarMap !== 'object') return;
|
|
129
147
|
if (obj === null || obj === undefined) return;
|
|
130
148
|
if (Array.isArray(obj)) {
|
|
131
|
-
|
|
132
|
-
if (typeof obj[i] === 'string') {
|
|
133
|
-
obj[i] = obj[i].replace(ENV_VAR_PLACEHOLDER_REGEX, (match, varName) =>
|
|
134
|
-
Object.prototype.hasOwnProperty.call(envVarMap, varName) ? String(envVarMap[varName]) : match);
|
|
135
|
-
} else if (typeof obj[i] === 'object' && obj[i] !== null) {
|
|
136
|
-
substituteEnvVarsInDeployment(obj[i], envVarMap);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
149
|
+
substituteEnvVarsInArray(obj, envVarMap, fieldPath);
|
|
139
150
|
return;
|
|
140
151
|
}
|
|
141
152
|
if (typeof obj === 'object') {
|
|
142
153
|
for (const key of Object.keys(obj)) {
|
|
143
154
|
const value = obj[key];
|
|
155
|
+
const childPath = fieldPath ? `${fieldPath}.${key}` : key;
|
|
144
156
|
if (typeof value === 'string') {
|
|
145
|
-
obj[key] = value
|
|
146
|
-
Object.prototype.hasOwnProperty.call(envVarMap, varName) ? String(envVarMap[varName]) : match);
|
|
157
|
+
obj[key] = replaceDeploymentEnvString(value, envVarMap, childPath);
|
|
147
158
|
} else if (typeof value === 'object' && value !== null) {
|
|
148
|
-
substituteEnvVarsInDeployment(value, envVarMap);
|
|
159
|
+
substituteEnvVarsInDeployment(value, envVarMap, childPath);
|
|
149
160
|
}
|
|
150
161
|
}
|
|
151
162
|
}
|
|
@@ -18,6 +18,7 @@ const { applyStaticAndEnterprise } = require('./apply-static-enterprise');
|
|
|
18
18
|
const { applyViewpointDefaults } = require('./apply-viewpoint-defaults');
|
|
19
19
|
const { stampCredentialTestEndpoint } = require('./apply-test-endpoint');
|
|
20
20
|
const { buildSystemFile } = require('./apply-system-file');
|
|
21
|
+
const { resolveAuthoredDatasourceSync } = require('../datasource/sync-defaults');
|
|
21
22
|
|
|
22
23
|
const STAMP_FILE = '.aifabrix-integration-definition.json';
|
|
23
24
|
|
|
@@ -160,8 +161,7 @@ function buildDatasourceFile(ds, systemKey, capabilities, sync, enrichOptions =
|
|
|
160
161
|
...(ds.storageType ? { storageType: ds.storageType } : {}),
|
|
161
162
|
...(ds.dimensions && typeof ds.dimensions === 'object' ? { dimensions: ds.dimensions } : {}),
|
|
162
163
|
capabilities: buildDatasourceCapabilities(caps, key),
|
|
163
|
-
|
|
164
|
-
sync: { mode: 'pull', schedule: /** @type {any} */ (sync).schedule || '0 */6 * * *', batchSize: 100 },
|
|
164
|
+
sync: resolveAuthoredDatasourceSync(ds, sync),
|
|
165
165
|
primaryKey: ['id'],
|
|
166
166
|
labelKey: [labelField],
|
|
167
167
|
...buildDatasourceSchema(labelField)
|
|
@@ -178,5 +178,8 @@ module.exports = {
|
|
|
178
178
|
rmSync,
|
|
179
179
|
unlinkSync,
|
|
180
180
|
copyFileSync,
|
|
181
|
-
appendFileSync
|
|
181
|
+
appendFileSync,
|
|
182
|
+
openSync: (...args) => delegate('openSync', args),
|
|
183
|
+
closeSync: (...args) => delegate('closeSync', args),
|
|
184
|
+
realpathSync: (...args) => delegate('realpathSync', args)
|
|
182
185
|
};
|
|
@@ -8,8 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
'use strict';
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
let misoClientSingleton = null;
|
|
11
|
+
const { getBuilderMisoRuntime } = require('./builder-miso-runtime');
|
|
13
12
|
|
|
14
13
|
/**
|
|
15
14
|
* @param {import('http').IncomingMessage} req
|
|
@@ -36,65 +35,10 @@ function assertNoForbiddenAuthHeaders(req) {
|
|
|
36
35
|
}
|
|
37
36
|
|
|
38
37
|
/**
|
|
39
|
-
* @returns {
|
|
40
|
-
*/
|
|
41
|
-
function resolveControllerUrlFromEnv() {
|
|
42
|
-
const raw =
|
|
43
|
-
process.env.MISO_CONTROLLER_URL ||
|
|
44
|
-
process.env.CONTROLLER_URL ||
|
|
45
|
-
process.env.MISO_WEB_SERVER_URL ||
|
|
46
|
-
'';
|
|
47
|
-
const url = String(raw).trim().replace(/\/$/, '');
|
|
48
|
-
if (!url) {
|
|
49
|
-
throw new Error('MISO_CONTROLLER_URL (or CONTROLLER_URL) is required for token validation');
|
|
50
|
-
}
|
|
51
|
-
return url;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* @returns {import('@aifabrix/miso-client/dist/types/config.types').MisoClientConfig}
|
|
56
|
-
*/
|
|
57
|
-
function buildMisoClientConfigFromEnv() {
|
|
58
|
-
const clientId = (process.env.MISO_CLIENTID || '').trim();
|
|
59
|
-
const clientSecret = (process.env.MISO_CLIENTSECRET || '').trim();
|
|
60
|
-
if (!clientId || !clientSecret) {
|
|
61
|
-
throw new Error('MISO_CLIENTID and MISO_CLIENTSECRET are required for builder-api auth');
|
|
62
|
-
}
|
|
63
|
-
return {
|
|
64
|
-
clientId,
|
|
65
|
-
clientSecret,
|
|
66
|
-
controllerUrl: resolveControllerUrlFromEnv(),
|
|
67
|
-
logLevel: (process.env.LOG_LEVEL || 'info').toLowerCase()
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Test hook — reset cached MisoClient between Jest cases.
|
|
73
|
-
*/
|
|
74
|
-
function resetMisoClientForTests() {
|
|
75
|
-
misoClientSingleton = null;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* @returns {Promise<import('@aifabrix/miso-client').MisoClient>}
|
|
38
|
+
* @returns {Promise<import('@aifabrix/miso-client').MisoClient>} Initialized shared client
|
|
80
39
|
*/
|
|
81
40
|
async function getMisoClientForAuth() {
|
|
82
|
-
|
|
83
|
-
try {
|
|
84
|
-
({ MisoClient } = require('@aifabrix/miso-client'));
|
|
85
|
-
} catch {
|
|
86
|
-
const err = new Error('@aifabrix/miso-client is required for builder-api auth');
|
|
87
|
-
err.statusCode = 503;
|
|
88
|
-
throw err;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (!misoClientSingleton) {
|
|
92
|
-
misoClientSingleton = new MisoClient(buildMisoClientConfigFromEnv());
|
|
93
|
-
}
|
|
94
|
-
if (!misoClientSingleton.isInitialized()) {
|
|
95
|
-
await misoClientSingleton.initialize();
|
|
96
|
-
}
|
|
97
|
-
return misoClientSingleton;
|
|
41
|
+
return getBuilderMisoRuntime().getClient();
|
|
98
42
|
}
|
|
99
43
|
|
|
100
44
|
/**
|
|
@@ -167,9 +111,6 @@ async function authenticateRequest(req, opts = {}) {
|
|
|
167
111
|
module.exports = {
|
|
168
112
|
extractBearerToken,
|
|
169
113
|
assertNoForbiddenAuthHeaders,
|
|
170
|
-
resolveControllerUrlFromEnv,
|
|
171
|
-
buildMisoClientConfigFromEnv,
|
|
172
|
-
resetMisoClientForTests,
|
|
173
114
|
getMisoClientForAuth,
|
|
174
115
|
validateBearerToken,
|
|
175
116
|
authenticateRequest
|