@aifabrix/builder 2.47.3 → 2.48.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/jest.isolated-projects.js +4 -0
- package/jest.projects.js +4 -0
- package/lib/api/types/dev.types.js +1 -1
- package/lib/app/down.js +3 -20
- package/lib/app/register.js +20 -18
- package/lib/app/rotate-secret.js +39 -16
- package/lib/build/index.js +12 -49
- package/lib/build/integration-docker-staging.js +35 -0
- package/lib/build/standard-docker-build.js +113 -0
- package/lib/cli/setup-app.js +4 -0
- package/lib/commands/dev-init.js +18 -0
- package/lib/commands/setup-prompts-platform-mode.js +11 -1
- package/lib/commands/setup.js +1 -0
- package/lib/commands/up-dataplane-credentials.js +88 -5
- package/lib/core/setup-platform-mode.js +16 -9
- package/lib/parameters/infra-kv-discovery.js +4 -3
- package/lib/utils/aifabrix-runtime-config-dir.js +2 -1
- package/lib/utils/compose-generate-docker-compose.js +2 -1
- package/lib/utils/config-paths.js +2 -2
- package/lib/utils/dev-ca-docker-augment.js +54 -0
- package/lib/utils/docker-daemon-tls-ca.js +326 -0
- package/lib/utils/ensure-dev-certs-for-remote-docker.js +5 -0
- package/lib/utils/promote-dev-docker-image.js +170 -0
- package/lib/utils/remote-docker-env.js +22 -7
- package/lib/utils/secrets-helpers.js +9 -14
- package/lib/utils/secrets-kv-refs.js +47 -3
- package/lib/utils/secrets-missing-error.js +1 -9
- package/lib/utils/token-manager-client-credentials.js +11 -1
- package/lib/utils/worker-sidecars.js +28 -1
- package/package.json +1 -1
- package/.temp/jest-protection-convert/run-IFN2nv/.protection/hubspot-companies.json +0 -1
- package/.temp/jest-protection-convert/run-IvmKLO/.protection/hubspot-companies.yaml +0 -17
- package/.temp/jest-protection-convert/run-SdvblF/.protection/hubspot-companies.json +0 -1
- package/.temp/jest-protection-convert/run-b51TsW/.protection/hubspot-companies.json +0 -1
- package/.temp/jest-protection-convert/run-fiLmb1/.protection/hubspot-companies.json +0 -1
- package/.temp/jest-protection-convert/run-qYGHMU/.protection/hubspot-companies.yaml +0 -17
- package/.temp/jest-protection-fixtures/hubspot-Mq5re8/hubspot-companies.yaml +0 -17
- package/.temp/jest-protection-fixtures/hubspot-f3u4YL/hubspot-companies.yaml +0 -17
- package/.temp/jest-protection-fixtures/hubspot-iQtd1O/hubspot-companies.yaml +0 -17
- package/.temp/jest-protection-fixtures/hubspot-mdmz5M/hubspot-companies.yaml +0 -17
- package/.temp/jest-protection-fixtures/hubspot-qbKYxQ/hubspot-companies.yaml +0 -17
- package/.temp/jest-protection-fixtures/hubspot-yzlJy6/hubspot-companies.yaml +0 -17
- package/.temp/validation/summary.txt +0 -5
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Promote a developer-scoped local Docker image to the manifest base repository (tag only).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Used by `aifabrix build --base --from-dev` and dev-promote workflows
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const config = require('../core/config');
|
|
12
|
+
const { loadAndValidateConfig } = require('./build-helpers');
|
|
13
|
+
const { resolveBuildDockerTagNames } = require('./build-resolve-image');
|
|
14
|
+
const { execWithDockerEnv } = require('./docker-exec');
|
|
15
|
+
const logger = require('./logger');
|
|
16
|
+
const { formatSuccessLine } = require('./cli-test-layout-chalk');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {string} value
|
|
20
|
+
* @returns {string}
|
|
21
|
+
*/
|
|
22
|
+
function shellQuote(value) {
|
|
23
|
+
return `'${String(value).replace(/'/g, '\'\\\'\'')}'`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {string} imageRef - repository:tag
|
|
28
|
+
* @returns {Promise<boolean>}
|
|
29
|
+
*/
|
|
30
|
+
async function dockerImageRefExists(imageRef) {
|
|
31
|
+
try {
|
|
32
|
+
await execWithDockerEnv(`docker image inspect ${shellQuote(imageRef)}`);
|
|
33
|
+
return true;
|
|
34
|
+
} catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @param {string} imageRef
|
|
41
|
+
* @returns {Promise<string|null>}
|
|
42
|
+
*/
|
|
43
|
+
async function readDockerImageId(imageRef) {
|
|
44
|
+
try {
|
|
45
|
+
const { stdout } = await execWithDockerEnv(
|
|
46
|
+
`docker image inspect --format '{{.Id}}' ${shellQuote(imageRef)}`
|
|
47
|
+
);
|
|
48
|
+
const id = stdout.trim();
|
|
49
|
+
return id || null;
|
|
50
|
+
} catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param {string} sourceRef
|
|
57
|
+
* @param {string} targetRef
|
|
58
|
+
* @returns {Promise<void>}
|
|
59
|
+
*/
|
|
60
|
+
async function dockerTagImageRef(sourceRef, targetRef) {
|
|
61
|
+
await execWithDockerEnv(`docker tag ${shellQuote(sourceRef)} ${shellQuote(targetRef)}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @param {string} appName
|
|
66
|
+
* @param {Object} variables
|
|
67
|
+
* @param {string|number} developerId
|
|
68
|
+
* @param {Object} options - `tag` (base target), `fromDevTag` (dev source; default `latest`)
|
|
69
|
+
* @returns {Promise<{ devRef: string, baseRef: string, devTag: string, baseTag: string }>}
|
|
70
|
+
*/
|
|
71
|
+
async function resolveDevAndBaseImageRefs(appName, variables, developerId, options) {
|
|
72
|
+
const baseTag = options.tag || 'latest';
|
|
73
|
+
const devTag = options.fromDevTag || 'latest';
|
|
74
|
+
const { buildImageName: baseName } = await resolveBuildDockerTagNames(appName, variables, developerId, {
|
|
75
|
+
...options,
|
|
76
|
+
base: true
|
|
77
|
+
});
|
|
78
|
+
const { buildImageName: devName } = await resolveBuildDockerTagNames(appName, variables, developerId, {
|
|
79
|
+
...options,
|
|
80
|
+
base: false
|
|
81
|
+
});
|
|
82
|
+
return {
|
|
83
|
+
devRef: `${devName}:${devTag}`,
|
|
84
|
+
baseRef: `${baseName}:${baseTag}`,
|
|
85
|
+
devTag,
|
|
86
|
+
baseTag
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Find a local developer-scoped image to promote (dev-build usually tags `latest`).
|
|
92
|
+
* @param {string} appName
|
|
93
|
+
* @param {Object} appConfig
|
|
94
|
+
* @param {string|number} developerId
|
|
95
|
+
* @param {Object} options
|
|
96
|
+
* @returns {Promise<{ devRef: string, baseRef: string }|null>}
|
|
97
|
+
*/
|
|
98
|
+
async function resolvePromoteSourceAndTargetRefs(appName, appConfig, developerId, options) {
|
|
99
|
+
const baseTag = options.tag || 'latest';
|
|
100
|
+
const devTagCandidates = ['latest'];
|
|
101
|
+
if (baseTag !== 'latest' && !devTagCandidates.includes(baseTag)) {
|
|
102
|
+
devTagCandidates.push(baseTag);
|
|
103
|
+
}
|
|
104
|
+
for (const fromDevTag of devTagCandidates) {
|
|
105
|
+
const refs = await resolveDevAndBaseImageRefs(appName, appConfig, developerId, {
|
|
106
|
+
...options,
|
|
107
|
+
fromDevTag
|
|
108
|
+
});
|
|
109
|
+
if (await dockerImageRefExists(refs.devRef)) {
|
|
110
|
+
return { devRef: refs.devRef, baseRef: refs.baseRef };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Tag developer-scoped image as manifest base when present locally.
|
|
118
|
+
* @param {string} appName
|
|
119
|
+
* @param {Object} options - build options (`base`, `tag`, `registry`, `fromDev`)
|
|
120
|
+
* @returns {Promise<string|null>} base image ref when promoted/skipped; null when dev image missing
|
|
121
|
+
*/
|
|
122
|
+
async function tryPromoteDevImageToBase(appName, options = {}) {
|
|
123
|
+
if (options.base !== true) {
|
|
124
|
+
throw new Error('Promote-from-dev requires --base.');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const developerId = await config.getDeveloperId();
|
|
128
|
+
const idNum = typeof developerId === 'string' ? parseInt(developerId, 10) : Number(developerId);
|
|
129
|
+
if (!Number.isFinite(idNum) || idNum <= 0) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const { config: appConfig } = await loadAndValidateConfig(appName);
|
|
134
|
+
const resolved = await resolvePromoteSourceAndTargetRefs(appName, appConfig, developerId, options);
|
|
135
|
+
if (!resolved) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
const { devRef, baseRef } = resolved;
|
|
139
|
+
|
|
140
|
+
const devId = await readDockerImageId(devRef);
|
|
141
|
+
if (await dockerImageRefExists(baseRef)) {
|
|
142
|
+
const baseId = await readDockerImageId(baseRef);
|
|
143
|
+
if (devId && baseId && devId === baseId) {
|
|
144
|
+
logger.log(formatSuccessLine(`Base image already matches dev build: ${baseRef}`));
|
|
145
|
+
return baseRef;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
await dockerTagImageRef(devRef, baseRef);
|
|
150
|
+
logger.log(formatSuccessLine(`Promoted dev image to base (tag only): ${baseRef}`));
|
|
151
|
+
return baseRef;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* @param {Object} options
|
|
156
|
+
* @returns {boolean}
|
|
157
|
+
*/
|
|
158
|
+
function shouldAttemptDevPromote(options) {
|
|
159
|
+
return options.fromDev === true && options.base === true;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
module.exports = {
|
|
163
|
+
tryPromoteDevImageToBase,
|
|
164
|
+
shouldAttemptDevPromote,
|
|
165
|
+
resolveDevAndBaseImageRefs,
|
|
166
|
+
resolvePromoteSourceAndTargetRefs,
|
|
167
|
+
dockerImageRefExists,
|
|
168
|
+
readDockerImageId,
|
|
169
|
+
dockerTagImageRef
|
|
170
|
+
};
|
|
@@ -6,10 +6,12 @@
|
|
|
6
6
|
* @version 2.0.0
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
const fs = require('fs');
|
|
9
10
|
const path = require('path');
|
|
10
11
|
const config = require('../core/config');
|
|
11
|
-
const { getCertDir } = require('./dev-cert-helper');
|
|
12
|
+
const { getCertDir, readServerCaPem } = require('./dev-cert-helper');
|
|
12
13
|
const { getConfigDirForPaths } = require('./paths');
|
|
14
|
+
const { resolveRemoteDockerDaemonVerify } = require('./docker-daemon-tls-ca');
|
|
13
15
|
|
|
14
16
|
function devTlsCertPaths(certDir) {
|
|
15
17
|
return {
|
|
@@ -43,10 +45,13 @@ function missingCaError(trimmed, certDir) {
|
|
|
43
45
|
* When docker-endpoint is set, we do not fall back to the local daemon without that endpoint
|
|
44
46
|
* (avoids accidentally using Docker Desktop while the dev profile targets a remote engine).
|
|
45
47
|
*
|
|
46
|
-
* When **TLS skip-verify** is enabled (config or
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
48
|
+
* When **TLS skip-verify** is enabled (config or `AIFABRIX_DOCKER_TLS_SKIP_VERIFY`), Docker uses
|
|
49
|
+
* DOCKER_TLS_VERIFY=0 (client cert/key still sent when present). Skip-verify applies even when ca.pem
|
|
50
|
+
* exists — ca.pem may hold the internal issue-cert CA while the Docker daemon uses a public/wildcard cert.
|
|
51
|
+
*
|
|
52
|
+
* When skip-verify is off and ca.pem exists, the CLI verifies the daemon only if ca.pem includes the
|
|
53
|
+
* Docker daemon trust chain (merged at dev init/refresh or lazily on first Docker use). Otherwise it
|
|
54
|
+
* falls back to DOCKER_TLS_VERIFY=0 with client mTLS (public CA daemon on a trusted builder host).
|
|
50
55
|
*
|
|
51
56
|
* Without skip-verify, cert.pem, key.pem, and ca.pem are required in the dev cert directory.
|
|
52
57
|
*
|
|
@@ -61,7 +66,6 @@ async function getRemoteDockerEnv() {
|
|
|
61
66
|
const trimmed = endpoint.trim();
|
|
62
67
|
const certDir = getCertDir(getConfigDirForPaths(), await config.getDeveloperId());
|
|
63
68
|
const { certPath, keyPath, caPath } = devTlsCertPaths(certDir);
|
|
64
|
-
const fs = require('fs');
|
|
65
69
|
const skipVerify = await config.getDockerTlsSkipVerify();
|
|
66
70
|
const hasClient = fs.existsSync(certPath) && fs.existsSync(keyPath);
|
|
67
71
|
const hasCa = fs.existsSync(caPath);
|
|
@@ -71,7 +75,18 @@ async function getRemoteDockerEnv() {
|
|
|
71
75
|
return { DOCKER_HOST: trimmed, DOCKER_TLS_VERIFY: '0' };
|
|
72
76
|
}
|
|
73
77
|
if (!hasCa && !skipVerify) throw missingCaError(trimmed, certDir);
|
|
74
|
-
|
|
78
|
+
|
|
79
|
+
let verifyDaemon = false;
|
|
80
|
+
if (!skipVerify && hasCa) {
|
|
81
|
+
verifyDaemon = await resolveRemoteDockerDaemonVerify({
|
|
82
|
+
certDir,
|
|
83
|
+
existingCaPem: readServerCaPem(certDir),
|
|
84
|
+
dockerEndpoint: trimmed,
|
|
85
|
+
remoteServerUrl: await config.getRemoteServer(),
|
|
86
|
+
skipVerify: false
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
75
90
|
return {
|
|
76
91
|
DOCKER_HOST: trimmed,
|
|
77
92
|
DOCKER_TLS_VERIFY: verifyDaemon ? '1' : '0',
|
|
@@ -19,7 +19,7 @@ const { updateContainerPortInEnvFile } = require('./env-ports');
|
|
|
19
19
|
const { buildEnvVarMap } = require('./env-map');
|
|
20
20
|
const { getLocalPortFromPath } = require('./port-resolver');
|
|
21
21
|
const { readYamlAtPath, applyCanonicalSecretsOverride } = require('./secrets-canonical');
|
|
22
|
-
const { collectUniqueKvPathStrings } = require('./secrets-kv-refs');
|
|
22
|
+
const { collectUniqueKvPathStrings, isCommentOrEmptyLine, activeEnvLineForKvScan, envLineWithoutInlineComment } = require('./secrets-kv-refs');
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Interpolate ${VAR} occurrences with values from envVars map
|
|
@@ -34,16 +34,6 @@ function interpolateEnvVars(content, envVars) {
|
|
|
34
34
|
});
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
/**
|
|
38
|
-
* Returns true if the line is a comment or empty (should be skipped for kv:// resolution)
|
|
39
|
-
* @param {string} line - Single line
|
|
40
|
-
* @returns {boolean}
|
|
41
|
-
*/
|
|
42
|
-
function isCommentOrEmptyLine(line) {
|
|
43
|
-
const t = line.trim();
|
|
44
|
-
return t === '' || t.startsWith('#');
|
|
45
|
-
}
|
|
46
|
-
|
|
47
37
|
/** Regex for kv:// path (allows slashes, e.g. kv://hubspot/clientId) */
|
|
48
38
|
const KV_REF_PATTERN = /kv:\/\/([a-zA-Z0-9_\-/]+)/g;
|
|
49
39
|
|
|
@@ -162,10 +152,11 @@ function collectMissingSecrets(content, secrets, scopedKv = null) {
|
|
|
162
152
|
const seen = new Set();
|
|
163
153
|
const missing = [];
|
|
164
154
|
for (const line of content.split('\n')) {
|
|
165
|
-
|
|
155
|
+
const scanLine = activeEnvLineForKvScan(line);
|
|
156
|
+
if (!scanLine) continue;
|
|
166
157
|
let match;
|
|
167
158
|
KV_REF_PATTERN.lastIndex = 0;
|
|
168
|
-
while ((match = KV_REF_PATTERN.exec(
|
|
159
|
+
while ((match = KV_REF_PATTERN.exec(scanLine)) !== null) {
|
|
169
160
|
const pathStr = match[1];
|
|
170
161
|
if (seen.has(pathStr)) continue;
|
|
171
162
|
seen.add(pathStr);
|
|
@@ -222,7 +213,10 @@ function replaceKvInContent(content, secrets, envVars, scopedKv = null) {
|
|
|
222
213
|
const lines = content.split('\n');
|
|
223
214
|
const result = lines.map(line => {
|
|
224
215
|
if (isCommentOrEmptyLine(line)) return line;
|
|
225
|
-
|
|
216
|
+
const scanLine = envLineWithoutInlineComment(line);
|
|
217
|
+
if (!scanLine.trim()) return line;
|
|
218
|
+
const suffix = line.slice(scanLine.length);
|
|
219
|
+
const replaced = scanLine.replace(KV_REF_PATTERN, (match, pathStr) => {
|
|
226
220
|
let value = resolveKvRefValue(secretsMap, pathStr, scopedKv?.envKey, effective);
|
|
227
221
|
if ((value === undefined || value === null) && isKvKeyAllowedEmptyWhenAbsent(pathStr)) {
|
|
228
222
|
value = '';
|
|
@@ -234,6 +228,7 @@ function replaceKvInContent(content, secrets, envVars, scopedKv = null) {
|
|
|
234
228
|
}
|
|
235
229
|
return value !== null && value !== undefined ? String(value) : match;
|
|
236
230
|
});
|
|
231
|
+
return replaced + suffix;
|
|
237
232
|
});
|
|
238
233
|
return result.join('\n');
|
|
239
234
|
}
|
|
@@ -7,11 +7,51 @@
|
|
|
7
7
|
|
|
8
8
|
const KV_REF_PATTERN = /kv:\/\/([a-zA-Z0-9_\-/]+)/g;
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* True when the line is empty or a full-line comment (# after trim).
|
|
12
|
+
*
|
|
13
|
+
* @param {string} line
|
|
14
|
+
* @returns {boolean}
|
|
15
|
+
*/
|
|
10
16
|
function isCommentOrEmptyLine(line) {
|
|
11
|
-
const t = line.trim();
|
|
17
|
+
const t = String(line).trim();
|
|
12
18
|
return t === '' || t.startsWith('#');
|
|
13
19
|
}
|
|
14
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Active env line for kv:// scanning: drop inline `#` comments (not inside quotes).
|
|
23
|
+
*
|
|
24
|
+
* @param {string} line
|
|
25
|
+
* @returns {string}
|
|
26
|
+
*/
|
|
27
|
+
function envLineWithoutInlineComment(line) {
|
|
28
|
+
if (isCommentOrEmptyLine(line)) {
|
|
29
|
+
return '';
|
|
30
|
+
}
|
|
31
|
+
let inSingle = false;
|
|
32
|
+
let inDouble = false;
|
|
33
|
+
const raw = String(line);
|
|
34
|
+
for (let i = 0; i < raw.length; i++) {
|
|
35
|
+
const c = raw[i];
|
|
36
|
+
if (c === '\'' && !inDouble) {
|
|
37
|
+
inSingle = !inSingle;
|
|
38
|
+
} else if (c === '"' && !inSingle) {
|
|
39
|
+
inDouble = !inDouble;
|
|
40
|
+
} else if (c === '#' && !inSingle && !inDouble) {
|
|
41
|
+
return raw.slice(0, i);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return raw;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {string} line
|
|
49
|
+
* @returns {string}
|
|
50
|
+
*/
|
|
51
|
+
function activeEnvLineForKvScan(line) {
|
|
52
|
+
return envLineWithoutInlineComment(line).trim();
|
|
53
|
+
}
|
|
54
|
+
|
|
15
55
|
/**
|
|
16
56
|
* @param {string} content
|
|
17
57
|
* @returns {string[]}
|
|
@@ -23,10 +63,11 @@ function collectUniqueKvPathStrings(content) {
|
|
|
23
63
|
return out;
|
|
24
64
|
}
|
|
25
65
|
for (const line of content.split('\n')) {
|
|
26
|
-
|
|
66
|
+
const scanLine = activeEnvLineForKvScan(line);
|
|
67
|
+
if (!scanLine) continue;
|
|
27
68
|
let match;
|
|
28
69
|
KV_REF_PATTERN.lastIndex = 0;
|
|
29
|
-
while ((match = KV_REF_PATTERN.exec(
|
|
70
|
+
while ((match = KV_REF_PATTERN.exec(scanLine)) !== null) {
|
|
30
71
|
const pathStr = match[1];
|
|
31
72
|
if (!seen.has(pathStr)) {
|
|
32
73
|
seen.add(pathStr);
|
|
@@ -38,5 +79,8 @@ function collectUniqueKvPathStrings(content) {
|
|
|
38
79
|
}
|
|
39
80
|
|
|
40
81
|
module.exports = {
|
|
82
|
+
isCommentOrEmptyLine,
|
|
83
|
+
envLineWithoutInlineComment,
|
|
84
|
+
activeEnvLineForKvScan,
|
|
41
85
|
collectUniqueKvPathStrings
|
|
42
86
|
};
|
|
@@ -9,15 +9,7 @@
|
|
|
9
9
|
'use strict';
|
|
10
10
|
|
|
11
11
|
const { formatMissingSecretsFileInfo } = require('./secrets-helpers');
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* @param {string} line
|
|
15
|
-
* @returns {boolean}
|
|
16
|
-
*/
|
|
17
|
-
function isCommentOrEmptyLine(line) {
|
|
18
|
-
const t = String(line).trim();
|
|
19
|
-
return t === '' || t.startsWith('#');
|
|
20
|
-
}
|
|
12
|
+
const { isCommentOrEmptyLine } = require('./secrets-kv-refs');
|
|
21
13
|
|
|
22
14
|
/**
|
|
23
15
|
* @param {string} kvRef - e.g. kv://hubspot-demo/clientSecret
|
|
@@ -183,12 +183,22 @@ async function resolveCredentialAppForDeployment(appName) {
|
|
|
183
183
|
|
|
184
184
|
/**
|
|
185
185
|
* @param {string} appName
|
|
186
|
+
* @param {{ secretsFileOnly?: boolean }} [options]
|
|
186
187
|
* @returns {Promise<{ clientId: string, clientSecret: string }|null>}
|
|
187
188
|
*/
|
|
188
|
-
async function loadClientCredentials(appName) {
|
|
189
|
+
async function loadClientCredentials(appName, options = {}) {
|
|
189
190
|
if (!appName || typeof appName !== 'string') {
|
|
190
191
|
throw new Error('App name is required and must be a string');
|
|
191
192
|
}
|
|
193
|
+
if (options.secretsFileOnly === true) {
|
|
194
|
+
for (const key of credentialAppKeysToTry(appName)) {
|
|
195
|
+
const fromFile = await readClientCredentialsFromSecretsFileDecrypted(key);
|
|
196
|
+
if (fromFile) {
|
|
197
|
+
return fromFile;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
192
202
|
const resolved = await resolveCredentialAppForDeployment(appName);
|
|
193
203
|
return resolved.credentials;
|
|
194
204
|
}
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
'use strict';
|
|
8
8
|
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
|
|
9
11
|
const { buildDefaultLocalContainerName } = require('./environment-scoped-resources');
|
|
10
12
|
|
|
11
13
|
/**
|
|
@@ -88,10 +90,31 @@ function getWorkerCompanionAppKeys(appKey, appConfig) {
|
|
|
88
90
|
return resolveWorkerSidecars(appKey, appConfig).companions.map((entry) => entry.key);
|
|
89
91
|
}
|
|
90
92
|
|
|
93
|
+
/**
|
|
94
|
+
* @param {string|undefined|null} envFilePath
|
|
95
|
+
* @returns {boolean}
|
|
96
|
+
*/
|
|
97
|
+
function isLocalModeFromEnvFile(envFilePath) {
|
|
98
|
+
if (!envFilePath || typeof envFilePath !== 'string') {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
const content = fs.readFileSync(envFilePath, 'utf8');
|
|
103
|
+
const match = content.match(/^LOCAL_MODE=(.+)$/m);
|
|
104
|
+
if (!match) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
const value = match[1].trim().replace(/^["']|["']$/g, '').toLowerCase();
|
|
108
|
+
return value === 'true' || value === '1' || value === 'yes';
|
|
109
|
+
} catch {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
91
114
|
/**
|
|
92
115
|
* @param {string} appKey
|
|
93
116
|
* @param {object} appConfig
|
|
94
|
-
* @param {{ devId: string|number, idNum: number }} ids
|
|
117
|
+
* @param {{ devId: string|number, idNum: number, envFilePath?: string }} ids
|
|
95
118
|
* @returns {{
|
|
96
119
|
* enabled: boolean,
|
|
97
120
|
* worker?: { key: string, containerName: string, command: string },
|
|
@@ -99,6 +122,9 @@ function getWorkerCompanionAppKeys(appKey, appConfig) {
|
|
|
99
122
|
* }}
|
|
100
123
|
*/
|
|
101
124
|
function buildWorkerSidecarComposeContext(appKey, appConfig, ids) {
|
|
125
|
+
if (isLocalModeFromEnvFile(ids.envFilePath)) {
|
|
126
|
+
return { enabled: false };
|
|
127
|
+
}
|
|
102
128
|
const sidecars = resolveWorkerSidecars(appKey, appConfig);
|
|
103
129
|
if (!sidecars.enabled) {
|
|
104
130
|
return { enabled: false };
|
|
@@ -133,6 +159,7 @@ function imageRunToStartupCommand(imageRun) {
|
|
|
133
159
|
}
|
|
134
160
|
|
|
135
161
|
module.exports = {
|
|
162
|
+
isLocalModeFromEnvFile,
|
|
136
163
|
readImageRunCommand,
|
|
137
164
|
isWorkerStackEnabled,
|
|
138
165
|
resolveWorkerSidecars,
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"apiVersion":"dataplane.aifabrix.ai/v1","kind":"Protection","metadata":{"key":"hubspot-country-sales","displayName":"HubSpot Country Sales Access"},"spec":{"enabled":true,"datasourceKey":"hubspot-companies","mode":"replaceForSource","rules":[{"key":"sales-country-users","principal":{"type":"group","expression":"Sales {{fk.country.metadata.iso3}} Users"},"grants":[{"dimensionKey":"country","valueExpression":"{{fk.country.metadata.iso2}}"}]}]}}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
apiVersion: dataplane.aifabrix.ai/v1
|
|
2
|
-
kind: Protection
|
|
3
|
-
metadata:
|
|
4
|
-
key: hubspot-country-sales
|
|
5
|
-
displayName: HubSpot Country Sales Access
|
|
6
|
-
spec:
|
|
7
|
-
enabled: true
|
|
8
|
-
datasourceKey: hubspot-companies
|
|
9
|
-
mode: replaceForSource
|
|
10
|
-
rules:
|
|
11
|
-
- key: sales-country-users
|
|
12
|
-
principal:
|
|
13
|
-
type: group
|
|
14
|
-
expression: "Sales {{fk.country.metadata.iso3}} Users"
|
|
15
|
-
grants:
|
|
16
|
-
- dimensionKey: country
|
|
17
|
-
valueExpression: "{{fk.country.metadata.iso2}}"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"kind":"Protection"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"apiVersion":"dataplane.aifabrix.ai/v1","kind":"Protection","metadata":{"key":"hubspot-country-sales","displayName":"HubSpot Country Sales Access"},"spec":{"enabled":true,"datasourceKey":"hubspot-companies","mode":"replaceForSource","rules":[{"key":"sales-country-users","principal":{"type":"group","expression":"Sales {{fk.country.metadata.iso3}} Users"},"grants":[{"dimensionKey":"country","valueExpression":"{{fk.country.metadata.iso2}}"}]}]}}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"kind":"Protection"}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
apiVersion: dataplane.aifabrix.ai/v1
|
|
2
|
-
kind: Protection
|
|
3
|
-
metadata:
|
|
4
|
-
key: hubspot-country-sales
|
|
5
|
-
displayName: HubSpot Country Sales Access
|
|
6
|
-
spec:
|
|
7
|
-
enabled: true
|
|
8
|
-
datasourceKey: hubspot-companies
|
|
9
|
-
mode: replaceForSource
|
|
10
|
-
rules:
|
|
11
|
-
- key: sales-country-users
|
|
12
|
-
principal:
|
|
13
|
-
type: group
|
|
14
|
-
expression: "Sales {{fk.country.metadata.iso3}} Users"
|
|
15
|
-
grants:
|
|
16
|
-
- dimensionKey: country
|
|
17
|
-
valueExpression: "{{fk.country.metadata.iso2}}"
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
apiVersion: dataplane.aifabrix.ai/v1
|
|
2
|
-
kind: Protection
|
|
3
|
-
metadata:
|
|
4
|
-
key: hubspot-country-sales
|
|
5
|
-
displayName: HubSpot Country Sales Access
|
|
6
|
-
spec:
|
|
7
|
-
enabled: true
|
|
8
|
-
datasourceKey: hubspot-companies
|
|
9
|
-
mode: replaceForSource
|
|
10
|
-
rules:
|
|
11
|
-
- key: sales-country-users
|
|
12
|
-
principal:
|
|
13
|
-
type: group
|
|
14
|
-
expression: "Sales {{fk.country.metadata.iso3}} Users"
|
|
15
|
-
grants:
|
|
16
|
-
- dimensionKey: country
|
|
17
|
-
valueExpression: "{{fk.country.metadata.iso2}}"
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
apiVersion: dataplane.aifabrix.ai/v1
|
|
2
|
-
kind: Protection
|
|
3
|
-
metadata:
|
|
4
|
-
key: hubspot-country-sales
|
|
5
|
-
displayName: HubSpot Country Sales Access
|
|
6
|
-
spec:
|
|
7
|
-
enabled: true
|
|
8
|
-
datasourceKey: hubspot-companies
|
|
9
|
-
mode: replaceForSource
|
|
10
|
-
rules:
|
|
11
|
-
- key: sales-country-users
|
|
12
|
-
principal:
|
|
13
|
-
type: group
|
|
14
|
-
expression: "Sales {{fk.country.metadata.iso3}} Users"
|
|
15
|
-
grants:
|
|
16
|
-
- dimensionKey: country
|
|
17
|
-
valueExpression: "{{fk.country.metadata.iso2}}"
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
apiVersion: dataplane.aifabrix.ai/v1
|
|
2
|
-
kind: Protection
|
|
3
|
-
metadata:
|
|
4
|
-
key: hubspot-country-sales
|
|
5
|
-
displayName: HubSpot Country Sales Access
|
|
6
|
-
spec:
|
|
7
|
-
enabled: true
|
|
8
|
-
datasourceKey: hubspot-companies
|
|
9
|
-
mode: replaceForSource
|
|
10
|
-
rules:
|
|
11
|
-
- key: sales-country-users
|
|
12
|
-
principal:
|
|
13
|
-
type: group
|
|
14
|
-
expression: "Sales {{fk.country.metadata.iso3}} Users"
|
|
15
|
-
grants:
|
|
16
|
-
- dimensionKey: country
|
|
17
|
-
valueExpression: "{{fk.country.metadata.iso2}}"
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
apiVersion: dataplane.aifabrix.ai/v1
|
|
2
|
-
kind: Protection
|
|
3
|
-
metadata:
|
|
4
|
-
key: hubspot-country-sales
|
|
5
|
-
displayName: HubSpot Country Sales Access
|
|
6
|
-
spec:
|
|
7
|
-
enabled: true
|
|
8
|
-
datasourceKey: hubspot-companies
|
|
9
|
-
mode: replaceForSource
|
|
10
|
-
rules:
|
|
11
|
-
- key: sales-country-users
|
|
12
|
-
principal:
|
|
13
|
-
type: group
|
|
14
|
-
expression: "Sales {{fk.country.metadata.iso3}} Users"
|
|
15
|
-
grants:
|
|
16
|
-
- dimensionKey: country
|
|
17
|
-
valueExpression: "{{fk.country.metadata.iso2}}"
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
apiVersion: dataplane.aifabrix.ai/v1
|
|
2
|
-
kind: Protection
|
|
3
|
-
metadata:
|
|
4
|
-
key: hubspot-country-sales
|
|
5
|
-
displayName: HubSpot Country Sales Access
|
|
6
|
-
spec:
|
|
7
|
-
enabled: true
|
|
8
|
-
datasourceKey: hubspot-companies
|
|
9
|
-
mode: replaceForSource
|
|
10
|
-
rules:
|
|
11
|
-
- key: sales-country-users
|
|
12
|
-
principal:
|
|
13
|
-
type: group
|
|
14
|
-
expression: "Sales {{fk.country.metadata.iso3}} Users"
|
|
15
|
-
grants:
|
|
16
|
-
- dimensionKey: country
|
|
17
|
-
valueExpression: "{{fk.country.metadata.iso2}}"
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
apiVersion: dataplane.aifabrix.ai/v1
|
|
2
|
-
kind: Protection
|
|
3
|
-
metadata:
|
|
4
|
-
key: hubspot-country-sales
|
|
5
|
-
displayName: HubSpot Country Sales Access
|
|
6
|
-
spec:
|
|
7
|
-
enabled: true
|
|
8
|
-
datasourceKey: hubspot-companies
|
|
9
|
-
mode: replaceForSource
|
|
10
|
-
rules:
|
|
11
|
-
- key: sales-country-users
|
|
12
|
-
principal:
|
|
13
|
-
type: group
|
|
14
|
-
expression: "Sales {{fk.country.metadata.iso3}} Users"
|
|
15
|
-
grants:
|
|
16
|
-
- dimensionKey: country
|
|
17
|
-
valueExpression: "{{fk.country.metadata.iso2}}"
|