@cloudbase/manager-node 5.8.0-beta.1 → 5.8.0-beta.2
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/deploy/DeployOrchestrator.js +8 -4
- package/lib/deploy/StateStore.js +1 -0
- package/lib/deploy/function/artifact.js +26 -8
- package/lib/deploy/function/config-guard.js +138 -70
- package/lib/function/index.js +5 -1
- package/lib/projectValidator/index.js +4 -2
- package/package.json +2 -2
- package/types/deploy/StateStore.d.ts +4 -0
- package/types/deploy/types.d.ts +34 -5
- package/types/function/index.d.ts +1 -0
|
@@ -158,7 +158,7 @@ class DeployOrchestrator {
|
|
|
158
158
|
// (失败资源保留旧快照,下次 diff 仍会检测到差异);快照写入失败不影响部署结果
|
|
159
159
|
if (!dryRun) {
|
|
160
160
|
try {
|
|
161
|
-
await this.persistState({ config, plan, results, cwd });
|
|
161
|
+
await this.persistState({ config, plan, results, cwd, envId: options.envId });
|
|
162
162
|
}
|
|
163
163
|
catch (e) {
|
|
164
164
|
(_j = log === null || log === void 0 ? void 0 : log.warn) === null || _j === void 0 ? void 0 : _j.call(log, `状态快照写入失败:${(e === null || e === void 0 ? void 0 : e.message) || String(e)}`);
|
|
@@ -203,7 +203,7 @@ class DeployOrchestrator {
|
|
|
203
203
|
*/
|
|
204
204
|
async persistState(options) {
|
|
205
205
|
var _a, _b, _c, _d, _e, _f;
|
|
206
|
-
const { config, plan, results, cwd } = options;
|
|
206
|
+
const { config, plan, results, cwd, envId } = options;
|
|
207
207
|
const stateStore = new StateStore_1.StateStore({ cwd });
|
|
208
208
|
const prev = stateStore.read();
|
|
209
209
|
// hosting
|
|
@@ -263,7 +263,7 @@ class DeployOrchestrator {
|
|
|
263
263
|
resources.gateway = prev.resources.gateway;
|
|
264
264
|
}
|
|
265
265
|
if (Object.keys(resources).length) {
|
|
266
|
-
stateStore.write({ resources });
|
|
266
|
+
stateStore.write({ resources, envId });
|
|
267
267
|
}
|
|
268
268
|
}
|
|
269
269
|
/**
|
|
@@ -291,7 +291,11 @@ class DeployOrchestrator {
|
|
|
291
291
|
};
|
|
292
292
|
// 本地 state 快照(diff 基准):--refresh 时忽略本地 skip 判定
|
|
293
293
|
const stateStore = new StateStore_1.StateStore({ cwd });
|
|
294
|
-
const
|
|
294
|
+
const rawState = refresh ? null : stateStore.read();
|
|
295
|
+
// 跨环境保护:快照记录的 envId 与目标环境不一致(或旧格式快照无 envId)时,
|
|
296
|
+
// 快照是其他环境部署后的指纹,不可作为 skip 依据 → 视为无快照,强制走全量部署,
|
|
297
|
+
// 避免切换环境后误判「产物未变更」导致新环境 COS 404
|
|
298
|
+
const state = rawState && rawState.envId === envId ? rawState : null;
|
|
295
299
|
const planCtx = { config, envId, stateStore, state, refresh, cwd };
|
|
296
300
|
for (const type of DEPLOY_ORDER) {
|
|
297
301
|
if (!isEnabled(type))
|
package/lib/deploy/StateStore.js
CHANGED
|
@@ -78,6 +78,7 @@ class StateStore {
|
|
|
78
78
|
version: 1,
|
|
79
79
|
updatedAt: new Date().toISOString(),
|
|
80
80
|
gitCommit: (_a = state.gitCommit) !== null && _a !== void 0 ? _a : this.resolveGitCommit(),
|
|
81
|
+
envId: state.envId,
|
|
81
82
|
resources: state.resources
|
|
82
83
|
};
|
|
83
84
|
fs_1.default.writeFileSync(filePath, JSON.stringify(full, null, 2), 'utf8');
|
|
@@ -33,10 +33,9 @@ function createImageArtifactFromConfig(config) {
|
|
|
33
33
|
// imageType/registryId 的契约位置在配置顶层(IHttpImageRuntimeCommon),
|
|
34
34
|
// 与 local/cloud 策略一致;imageConfig 内的同名字段仅作向后兼容兜底。
|
|
35
35
|
// 个人版镜像(personal,如个人版 CCR)不应携带 registryId。
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
: undefined;
|
|
36
|
+
const configuredRegistryId = (_a = config.registryId) !== null && _a !== void 0 ? _a : imageConfig.registryId;
|
|
37
|
+
const imageType = (_c = (_b = config.imageType) !== null && _b !== void 0 ? _b : imageConfig.imageType) !== null && _c !== void 0 ? _c : (configuredRegistryId ? 'enterprise' : 'personal');
|
|
38
|
+
const registryId = imageType === 'enterprise' ? configuredRegistryId : undefined;
|
|
40
39
|
return {
|
|
41
40
|
kind: 'image',
|
|
42
41
|
deployMode: 'image',
|
|
@@ -60,14 +59,18 @@ function createImageArtifactFromConfig(config) {
|
|
|
60
59
|
* 此处仅承载最终 imageUri/digest 与容器启动配置,之后复用镜像部署链。
|
|
61
60
|
*/
|
|
62
61
|
function createImageArtifactFromBuild(config, build) {
|
|
63
|
-
var _a;
|
|
62
|
+
var _a, _b, _c;
|
|
63
|
+
const imageType = (_a = config.imageType) !== null && _a !== void 0 ? _a : (((_b = config.registryId) !== null && _b !== void 0 ? _b : config.build.registryId) ? 'enterprise' : 'personal');
|
|
64
|
+
const registryId = imageType === 'enterprise'
|
|
65
|
+
? (_c = config.registryId) !== null && _c !== void 0 ? _c : config.build.registryId
|
|
66
|
+
: undefined;
|
|
64
67
|
return {
|
|
65
68
|
kind: 'image',
|
|
66
69
|
deployMode: 'image',
|
|
67
70
|
imageUri: build.imageUri,
|
|
68
71
|
imageDigest: build.imageDigest,
|
|
69
|
-
|
|
70
|
-
|
|
72
|
+
imageType,
|
|
73
|
+
registryId,
|
|
71
74
|
imagePort: types_1.IMAGE_FUNCTION_PORT,
|
|
72
75
|
effectiveStrategy: 'local',
|
|
73
76
|
entryPoint: config.entryPoint,
|
|
@@ -172,7 +175,22 @@ function toCloudFunction(config, artifact) {
|
|
|
172
175
|
'role',
|
|
173
176
|
'codeSecret',
|
|
174
177
|
'l5',
|
|
175
|
-
'ignore'
|
|
178
|
+
'ignore',
|
|
179
|
+
// —— 高级配置:与 fn deploy 传统通道(configToParams 白名单)对齐,
|
|
180
|
+
// 字段名需与其大小写不敏感匹配规则一致,否则会被下游静默丢弃 ——
|
|
181
|
+
'tags',
|
|
182
|
+
'clsLogsetId',
|
|
183
|
+
'clsTopicId',
|
|
184
|
+
'deadLetterConfig',
|
|
185
|
+
'publicNetConfig',
|
|
186
|
+
'cfsConfig',
|
|
187
|
+
'intranetConfig',
|
|
188
|
+
'initTimeout',
|
|
189
|
+
'asyncRunEnable',
|
|
190
|
+
'traceEnable',
|
|
191
|
+
'autoDeployClsTopicIndex',
|
|
192
|
+
'autoCreateClsTopic',
|
|
193
|
+
'dnsCache'
|
|
176
194
|
];
|
|
177
195
|
passthroughFields.forEach(field => {
|
|
178
196
|
if (source[field] !== undefined) {
|
|
@@ -182,7 +182,7 @@ function inferHttpBuildStrategy(config) {
|
|
|
182
182
|
* 以便 checkDeployConfig 汇总全部问题
|
|
183
183
|
*/
|
|
184
184
|
function normalizeDeployConfig(config) {
|
|
185
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
185
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
|
|
186
186
|
if (!config || typeof config !== 'object') {
|
|
187
187
|
throw new error_1.CloudBaseError('部署配置必须是对象', {
|
|
188
188
|
code: types_1.FUNCTION_DEPLOY_ERROR.CONFIG_INVALID
|
|
@@ -211,19 +211,19 @@ function normalizeDeployConfig(config) {
|
|
|
211
211
|
ignore: ignoreList.filter((item) => !/node_modules/.test(item)) });
|
|
212
212
|
}
|
|
213
213
|
const normalizedImage = Object.assign(Object.assign({}, cloned), { type: 'HTTP', buildStrategy, runtime: types_1.CUSTOM_IMAGE_RUNTIME, imagePort: (_a = cloned.imagePort) !== null && _a !== void 0 ? _a : types_1.IMAGE_FUNCTION_PORT });
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
214
|
+
normalizedImage.imageType =
|
|
215
|
+
(_d = (_b = cloned.imageType) !== null && _b !== void 0 ? _b : (_c = cloned.imageConfig) === null || _c === void 0 ? void 0 : _c.imageType) !== null && _d !== void 0 ? _d : (((_g = (_e = cloned.registryId) !== null && _e !== void 0 ? _e : (_f = cloned.imageConfig) === null || _f === void 0 ? void 0 : _f.registryId) !== null && _g !== void 0 ? _g : (_h = cloned.build) === null || _h === void 0 ? void 0 : _h.registryId)
|
|
216
|
+
? 'enterprise'
|
|
217
|
+
: 'personal');
|
|
218
218
|
// 镜像函数由容器启动命令决定入口,不使用 handler 与云端装依赖
|
|
219
219
|
delete normalizedImage.handler;
|
|
220
220
|
delete normalizedImage.installDependency;
|
|
221
221
|
if (buildStrategy === 'cloud' || buildStrategy === 'local') {
|
|
222
|
-
normalizedImage.build = Object.assign(Object.assign({}, (cloned.build || {})), { dockerfile: (
|
|
222
|
+
normalizedImage.build = Object.assign(Object.assign({}, (cloned.build || {})), { dockerfile: (_k = (_j = cloned.build) === null || _j === void 0 ? void 0 : _j.dockerfile) !== null && _k !== void 0 ? _k : types_1.DEFAULT_DOCKERFILE, platform: (_m = (_l = cloned.build) === null || _l === void 0 ? void 0 : _l.platform) !== null && _m !== void 0 ? _m : types_1.DEFAULT_IMAGE_PLATFORM, repository: (_p = (_o = cloned.build) === null || _o === void 0 ? void 0 : _o.repository) !== null && _p !== void 0 ? _p : cloned.name });
|
|
223
223
|
}
|
|
224
224
|
if (buildStrategy === 'local') {
|
|
225
225
|
// 默认不静默改变构建环境
|
|
226
|
-
normalizedImage.localFallback = (
|
|
226
|
+
normalizedImage.localFallback = (_q = cloned.localFallback) !== null && _q !== void 0 ? _q : 'error';
|
|
227
227
|
}
|
|
228
228
|
return normalizedImage;
|
|
229
229
|
}
|
|
@@ -377,13 +377,9 @@ function checkImageIdentity(raw, checks) {
|
|
|
377
377
|
const imageConfig = raw === null || raw === void 0 ? void 0 : raw.imageConfig;
|
|
378
378
|
const buildRegistryId = (_a = raw === null || raw === void 0 ? void 0 : raw.build) === null || _a === void 0 ? void 0 : _a.registryId;
|
|
379
379
|
const buildStrategy = raw === null || raw === void 0 ? void 0 : raw.buildStrategy;
|
|
380
|
-
const
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
: 'personal'
|
|
384
|
-
: 'enterprise';
|
|
385
|
-
const imageType = (_c = (_b = raw === null || raw === void 0 ? void 0 : raw.imageType) !== null && _b !== void 0 ? _b : imageConfig === null || imageConfig === void 0 ? void 0 : imageConfig.imageType) !== null && _c !== void 0 ? _c : defaultImageType;
|
|
386
|
-
const registryId = (_e = (_d = raw === null || raw === void 0 ? void 0 : raw.registryId) !== null && _d !== void 0 ? _d : imageConfig === null || imageConfig === void 0 ? void 0 : imageConfig.registryId) !== null && _e !== void 0 ? _e : buildRegistryId;
|
|
380
|
+
const registryId = (_c = (_b = raw === null || raw === void 0 ? void 0 : raw.registryId) !== null && _b !== void 0 ? _b : imageConfig === null || imageConfig === void 0 ? void 0 : imageConfig.registryId) !== null && _c !== void 0 ? _c : buildRegistryId;
|
|
381
|
+
const defaultImageType = registryId ? 'enterprise' : 'personal';
|
|
382
|
+
const imageType = (_e = (_d = raw === null || raw === void 0 ? void 0 : raw.imageType) !== null && _d !== void 0 ? _d : imageConfig === null || imageConfig === void 0 ? void 0 : imageConfig.imageType) !== null && _e !== void 0 ? _e : defaultImageType;
|
|
387
383
|
if (imageType !== 'enterprise' && imageType !== 'personal') {
|
|
388
384
|
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.IMAGE_TYPE_INVALID, '镜像类型不合法', 'imageType 只能是 enterprise 或 personal'));
|
|
389
385
|
return;
|
|
@@ -403,17 +399,34 @@ function checkImageIdentity(raw, checks) {
|
|
|
403
399
|
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.IMAGE_TYPE_INVALID, '个人版镜像不能配置企业版仓库实例 ID', '请移除 build.registryId,或将 imageType 设置为 enterprise'));
|
|
404
400
|
}
|
|
405
401
|
}
|
|
406
|
-
/**
|
|
407
|
-
function
|
|
408
|
-
var _a, _b, _c;
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
402
|
+
/** 解析构建策略使用的镜像类型 */
|
|
403
|
+
function resolveBuildImageType(raw, build) {
|
|
404
|
+
var _a, _b, _c, _d, _e, _f;
|
|
405
|
+
return (_c = (_a = raw.imageType) !== null && _a !== void 0 ? _a : (_b = raw.imageConfig) === null || _b === void 0 ? void 0 : _b.imageType) !== null && _c !== void 0 ? _c : (((_f = (_d = raw.registryId) !== null && _d !== void 0 ? _d : (_e = raw.imageConfig) === null || _e === void 0 ? void 0 : _e.registryId) !== null && _f !== void 0 ? _f : build.registryId) ? 'enterprise' : 'personal');
|
|
406
|
+
}
|
|
407
|
+
/** 校验构建 tag */
|
|
408
|
+
function checkBuildTag(raw, build) {
|
|
409
|
+
if (build.tag === undefined) {
|
|
410
|
+
return [];
|
|
413
411
|
}
|
|
414
|
-
if (raw.buildStrategy === 'cloud'
|
|
415
|
-
|
|
412
|
+
if (raw.buildStrategy === 'cloud') {
|
|
413
|
+
return [
|
|
414
|
+
fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_TAG_INVALID, 'cloud 策略不支持自定义镜像 tag', '请移除 build.tag,云端构建会统一使用平台生成的 CLOUDBASE_VERSION_NAME')
|
|
415
|
+
];
|
|
416
416
|
}
|
|
417
|
+
if (typeof build.tag !== 'string' || !IMAGE_TAG_PATTERN.test(build.tag)) {
|
|
418
|
+
return [fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_TAG_INVALID, '镜像 tag 不合法')];
|
|
419
|
+
}
|
|
420
|
+
if (build.tag === 'latest') {
|
|
421
|
+
return [
|
|
422
|
+
fail(types_1.FUNCTION_DEPLOY_ERROR.IMAGE_TAG_LATEST_FORBIDDEN, '镜像 tag 禁止使用 latest', '请使用不可变 tag,或不指定 tag 由构建器生成时间戳 tag')
|
|
423
|
+
];
|
|
424
|
+
}
|
|
425
|
+
return [];
|
|
426
|
+
}
|
|
427
|
+
/** 校验构建上下文、Dockerfile 与目标平台 */
|
|
428
|
+
function checkBuildSource(build, checks) {
|
|
429
|
+
var _a, _b;
|
|
417
430
|
if (build.cwd !== undefined && (typeof build.cwd !== 'string' || !build.cwd.trim())) {
|
|
418
431
|
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_CONTEXT_INVALID, '构建上下文目录不合法', 'build.cwd 需为非空字符串'));
|
|
419
432
|
}
|
|
@@ -425,68 +438,123 @@ function checkBuildTarget(raw, checks) {
|
|
|
425
438
|
if (platform !== types_1.DEFAULT_IMAGE_PLATFORM) {
|
|
426
439
|
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_PLATFORM_INVALID, `当前仅支持构建 ${types_1.DEFAULT_IMAGE_PLATFORM} 镜像`, `请移除 build.platform 或设置为 ${types_1.DEFAULT_IMAGE_PLATFORM}`));
|
|
427
440
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
441
|
+
}
|
|
442
|
+
/** 判断当前构建策略是否要求完整仓库地址 */
|
|
443
|
+
function requiresFullBuildRepository(raw, imageType) {
|
|
444
|
+
return raw.buildStrategy === 'local' ||
|
|
445
|
+
(raw.buildStrategy === 'cloud' && imageType === 'enterprise');
|
|
446
|
+
}
|
|
447
|
+
/** 添加仓库地址缺失或不完整的错误 */
|
|
448
|
+
function addFullRepositoryError(raw, checks, missing) {
|
|
449
|
+
const isLocal = raw.buildStrategy === 'local';
|
|
450
|
+
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_REPOSITORY_INVALID, missing
|
|
451
|
+
? isLocal
|
|
452
|
+
? 'local 策略缺少目标镜像仓库地址'
|
|
453
|
+
: 'enterprise cloud 策略缺少目标镜像仓库地址'
|
|
454
|
+
: isLocal
|
|
455
|
+
? 'local 策略需要完整镜像仓库地址'
|
|
456
|
+
: 'enterprise cloud 策略需要完整镜像仓库地址', missing
|
|
457
|
+
? '请配置 build.repository 为 registry/namespace/repository'
|
|
458
|
+
: isLocal
|
|
459
|
+
? '请配置 registry/namespace/repository;当前 local 构建使用本机 Docker 登录态直接 push'
|
|
460
|
+
: 'P1 自动发现 TCR 实例域名完成前,请配置 registry/namespace/repository'));
|
|
461
|
+
}
|
|
462
|
+
/** 校验目标镜像仓库 */
|
|
463
|
+
function checkBuildRepository(raw, build, checks) {
|
|
464
|
+
const imageType = resolveBuildImageType(raw, build);
|
|
465
|
+
const requiresFullRepository = requiresFullBuildRepository(raw, imageType);
|
|
466
|
+
if (build.repository === undefined) {
|
|
467
|
+
if (requiresFullRepository) {
|
|
468
|
+
addFullRepositoryError(raw, checks, true);
|
|
434
469
|
}
|
|
470
|
+
return;
|
|
435
471
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
472
|
+
const repository = typeof build.repository === 'string' ? build.repository.trim() : build.repository;
|
|
473
|
+
const parsedRepository = typeof repository === 'string' ? parseImageReference(repository) : null;
|
|
474
|
+
if (!parsedRepository || parsedRepository.tag || parsedRepository.digest) {
|
|
475
|
+
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_REPOSITORY_INVALID, '镜像仓库地址不合法', 'repository 需为不含 tag 或 digest 的镜像仓库路径'));
|
|
476
|
+
return;
|
|
441
477
|
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
478
|
+
const repositoryPath = parsedRepository.repository;
|
|
479
|
+
if (requiresFullRepository &&
|
|
480
|
+
(!parsedRepository.registry || !repositoryPath.includes('/'))) {
|
|
481
|
+
addFullRepositoryError(raw, checks, false);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
/** 校验单个 Docker build arg */
|
|
485
|
+
function checkBuildArg(raw, buildArg, checks) {
|
|
486
|
+
const [key, value] = buildArg;
|
|
487
|
+
if (!ENV_KEY_PATTERN.test(key)) {
|
|
488
|
+
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_ARG_INVALID, `构建参数名不合法:${key}`));
|
|
489
|
+
}
|
|
490
|
+
if (typeof value !== 'string') {
|
|
491
|
+
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_ARG_INVALID, `构建参数 ${key} 的值必须是字符串`));
|
|
492
|
+
}
|
|
493
|
+
if (SENSITIVE_KEY_PATTERN.test(key)) {
|
|
494
|
+
const message = `构建参数 ${key} 可能包含敏感信息`;
|
|
495
|
+
const remediation = '构建参数会写入镜像构建记录,请改用运行时环境变量或密钥管理';
|
|
496
|
+
checks.push(raw.buildStrategy === 'cloud'
|
|
497
|
+
? fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_ARG_INVALID, message, remediation)
|
|
498
|
+
: warn(types_1.FUNCTION_DEPLOY_ERROR.BUILD_ARG_INVALID, message, remediation));
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
/** 校验 Docker build args */
|
|
502
|
+
function checkBuildArgs(raw, build, checks) {
|
|
503
|
+
if (build.buildArgs === undefined) {
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
if (!lodash_1.default.isPlainObject(build.buildArgs)) {
|
|
507
|
+
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_ARG_INVALID, '构建参数必须是对象'));
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
Object.entries(build.buildArgs).forEach(buildArg => {
|
|
511
|
+
checkBuildArg(raw, buildArg, checks);
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
/** 校验个人版 TCR 云端推送凭证 */
|
|
515
|
+
function checkPersonalRegistryCredential(registryCredential, checks) {
|
|
516
|
+
if (!lodash_1.default.isPlainObject(registryCredential)) {
|
|
517
|
+
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.CLOUD_REGISTRY_CREDENTIAL_MISSING, '个人版 TCR 云端构建缺少推送凭证', '请在 build.registryCredential 中显式声明 username/password,' +
|
|
518
|
+
'推荐使用 {{env.TCB_TCR_USERNAME}} 与 {{env.TCB_TCR_PASSWORD}}'));
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
const username = registryCredential.username;
|
|
522
|
+
const password = registryCredential.password;
|
|
523
|
+
if (typeof username !== 'string' || !/^\d{5,20}$/.test(username.trim())) {
|
|
524
|
+
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.CLOUD_REGISTRY_CREDENTIAL_INVALID, '个人版 TCR 登录用户名不合法', 'username 需为腾讯云账号 ID(UIN),推荐使用 {{env.TCB_TCR_USERNAME}}'));
|
|
525
|
+
}
|
|
526
|
+
if (typeof password !== 'string' || !password || password.length > 16 * 1024) {
|
|
527
|
+
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.CLOUD_REGISTRY_CREDENTIAL_INVALID, '个人版 TCR 固定密码未配置或长度不合法', 'password 必须显式声明且不能为空,推荐使用 {{env.TCB_TCR_PASSWORD}}'));
|
|
463
528
|
}
|
|
529
|
+
}
|
|
530
|
+
/** 校验镜像仓库命名空间与推送凭证 */
|
|
531
|
+
function checkBuildRegistry(raw, build, checks) {
|
|
532
|
+
const imageType = resolveBuildImageType(raw, build);
|
|
464
533
|
const registryCredential = build.registryCredential;
|
|
465
|
-
const imageType = (_c = raw.imageType) !== null && _c !== void 0 ? _c : (build.registryId ? 'enterprise' : 'personal');
|
|
466
534
|
if (raw.buildStrategy === 'cloud' && imageType === 'personal') {
|
|
467
535
|
if (typeof build.namespace !== 'string' ||
|
|
468
536
|
!build.namespace.trim() ||
|
|
469
537
|
!IMAGE_REPOSITORY_PATTERN.test(build.namespace.trim())) {
|
|
470
538
|
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_REPOSITORY_INVALID, '个人版 TCR 命名空间未配置或不合法', '请在 build.namespace 中显式声明,推荐使用 {{env.TCB_TCR_NAMESPACE}}'));
|
|
471
539
|
}
|
|
472
|
-
|
|
473
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.CLOUD_REGISTRY_CREDENTIAL_MISSING, '个人版 TCR 云端构建缺少推送凭证', '请在 build.registryCredential 中显式声明 username/password,' +
|
|
474
|
-
'推荐使用 {{env.TCB_TCR_USERNAME}} 与 {{env.TCB_TCR_PASSWORD}}'));
|
|
475
|
-
}
|
|
476
|
-
else {
|
|
477
|
-
const username = registryCredential.username;
|
|
478
|
-
const password = registryCredential.password;
|
|
479
|
-
if (typeof username !== 'string' || !/^\d{5,20}$/.test(username.trim())) {
|
|
480
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.CLOUD_REGISTRY_CREDENTIAL_INVALID, '个人版 TCR 登录用户名不合法', 'username 需为腾讯云账号 ID(UIN),推荐使用 {{env.TCB_TCR_USERNAME}}'));
|
|
481
|
-
}
|
|
482
|
-
if (typeof password !== 'string' || !password || password.length > 16 * 1024) {
|
|
483
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.CLOUD_REGISTRY_CREDENTIAL_INVALID, '个人版 TCR 固定密码未配置或长度不合法', 'password 必须显式声明且不能为空,推荐使用 {{env.TCB_TCR_PASSWORD}}'));
|
|
484
|
-
}
|
|
485
|
-
}
|
|
540
|
+
checkPersonalRegistryCredential(registryCredential, checks);
|
|
486
541
|
}
|
|
487
542
|
else if (registryCredential !== undefined) {
|
|
488
543
|
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.CLOUD_REGISTRY_CREDENTIAL_INVALID, 'registryCredential 仅支持 cloud + personal 构建', '企业版使用 STS 临时凭证换取 token;local 构建使用本机 Docker 登录态'));
|
|
489
544
|
}
|
|
545
|
+
}
|
|
546
|
+
/** 校验镜像构建目标配置 */
|
|
547
|
+
function checkBuildTarget(raw, checks) {
|
|
548
|
+
const build = raw.build;
|
|
549
|
+
if (!lodash_1.default.isPlainObject(build)) {
|
|
550
|
+
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_CONFIG_MISSING, '缺少镜像构建配置', '请配置 build,至少包含构建上下文与 Dockerfile 信息'));
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
checks.push(...checkBuildTag(raw, build));
|
|
554
|
+
checkBuildSource(build, checks);
|
|
555
|
+
checkBuildRepository(raw, build, checks);
|
|
556
|
+
checkBuildArgs(raw, build, checks);
|
|
557
|
+
checkBuildRegistry(raw, build, checks);
|
|
490
558
|
if (raw.imageConfig !== undefined) {
|
|
491
559
|
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.IMAGE_CONFIG_NOT_ALLOWED, `buildStrategy 为 ${raw.buildStrategy} 时不能指定 imageConfig`, '镜像地址将由构建结果决定,如需直接部署已有镜像请使用 buildStrategy: image'));
|
|
492
560
|
}
|
package/lib/function/index.js
CHANGED
|
@@ -1167,7 +1167,7 @@ class FunctionService {
|
|
|
1167
1167
|
* @returns {Promise<void>}
|
|
1168
1168
|
*/
|
|
1169
1169
|
async downloadFunctionCode(options) {
|
|
1170
|
-
const { destPath, envId, functionName, codeSecret } = options;
|
|
1170
|
+
const { destPath, envId, functionName, codeSecret, qualifier } = options;
|
|
1171
1171
|
// 检验路径是否存在
|
|
1172
1172
|
(0, utils_1.checkFullAccess)(destPath, true);
|
|
1173
1173
|
// 获取下载链接:ENABLE_TCBF 开启走 tcb 的 DownloadFunction,否则走 scf 的 GetFunctionAddress
|
|
@@ -1176,6 +1176,10 @@ class FunctionService {
|
|
|
1176
1176
|
FunctionName: functionName,
|
|
1177
1177
|
[this.namespaceParamKey]: envId
|
|
1178
1178
|
};
|
|
1179
|
+
// 指定版本时透传 Qualifier;默认 $LATEST 与现状一致
|
|
1180
|
+
if (qualifier) {
|
|
1181
|
+
params.Qualifier = qualifier;
|
|
1182
|
+
}
|
|
1179
1183
|
if (codeSecret) {
|
|
1180
1184
|
params.CodeSecret = codeSecret;
|
|
1181
1185
|
}
|
|
@@ -190,10 +190,12 @@ class ProjectValidator {
|
|
|
190
190
|
return (config.functions || []).some((func) => this.isPersonalCloudFunction(func));
|
|
191
191
|
}
|
|
192
192
|
isPersonalCloudFunction(func) {
|
|
193
|
-
var _a;
|
|
193
|
+
var _a, _b, _c, _d, _e, _f;
|
|
194
|
+
const registryId = (_b = (_a = func.imageConfig) === null || _a === void 0 ? void 0 : _a.registryId) !== null && _b !== void 0 ? _b : (_d = (_c = func.imageConfig) === null || _c === void 0 ? void 0 : _c.build) === null || _d === void 0 ? void 0 : _d.registryId;
|
|
195
|
+
const imageType = (_f = (_e = func.imageConfig) === null || _e === void 0 ? void 0 : _e.imageType) !== null && _f !== void 0 ? _f : (registryId ? 'enterprise' : 'personal');
|
|
194
196
|
return (func.type === 'HTTP' &&
|
|
195
197
|
func.buildStrategy === 'cloud' &&
|
|
196
|
-
|
|
198
|
+
imageType === 'personal');
|
|
197
199
|
}
|
|
198
200
|
validateResources(cwd, config) {
|
|
199
201
|
const errors = [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudbase/manager-node",
|
|
3
|
-
"version": "5.8.0-beta.
|
|
3
|
+
"version": "5.8.0-beta.2",
|
|
4
4
|
"description": "The node manage service api for cloudbase.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@cloudbase/signature-nodejs": "^1.0.0",
|
|
45
|
-
"@cloudbase/toolbox": "^0.8.3-beta.
|
|
45
|
+
"@cloudbase/toolbox": "^0.8.3-beta.2",
|
|
46
46
|
"@cloudbase/database": "^0.6.2",
|
|
47
47
|
"archiver": "^3.1.1",
|
|
48
48
|
"cos-nodejs-sdk-v5": "^2.14.0",
|
|
@@ -58,6 +58,8 @@ export interface IDeployState {
|
|
|
58
58
|
version: number;
|
|
59
59
|
updatedAt: string;
|
|
60
60
|
gitCommit?: string;
|
|
61
|
+
/** 写入快照时的目标环境 ID:跨环境切换(如上海 → 新加坡)后旧快照不可信,skip 判定强制失效 */
|
|
62
|
+
envId?: string;
|
|
61
63
|
resources: {
|
|
62
64
|
hosting?: IHostingStateEntry[];
|
|
63
65
|
gateway?: unknown;
|
|
@@ -102,6 +104,8 @@ export declare class StateStore {
|
|
|
102
104
|
write(state: {
|
|
103
105
|
resources: IDeployState['resources'];
|
|
104
106
|
gitCommit?: string;
|
|
107
|
+
/** 本次部署的目标环境 ID(用于跨环境切换时使旧快照 skip 判定失效) */
|
|
108
|
+
envId?: string;
|
|
105
109
|
}): void;
|
|
106
110
|
/**
|
|
107
111
|
* 计算产物目录逐文件 sha256 指纹(仅 hash,兼容旧调用)
|
package/types/deploy/types.d.ts
CHANGED
|
@@ -156,6 +156,35 @@ export interface IFunctionDeployCommon {
|
|
|
156
156
|
/** 声明式配置中的函数代码目录,由 DeployOrchestrator 解析为绝对 functionPath */
|
|
157
157
|
dir?: string;
|
|
158
158
|
l5?: boolean | 'TRUE' | 'FALSE';
|
|
159
|
+
/** 标签列表(透传为 SCF Tags) */
|
|
160
|
+
tags?: {
|
|
161
|
+
Key: string;
|
|
162
|
+
Value: string;
|
|
163
|
+
}[];
|
|
164
|
+
/** 日志投递 CLS 日志集 ID */
|
|
165
|
+
clsLogsetId?: string;
|
|
166
|
+
/** 日志投递 CLS 主题 ID */
|
|
167
|
+
clsTopicId?: string;
|
|
168
|
+
/** 死信队列配置(透传为 SCF DeadLetterConfig) */
|
|
169
|
+
deadLetterConfig?: Record<string, unknown>;
|
|
170
|
+
/** 公网访问配置(透传为 SCF PublicNetConfig) */
|
|
171
|
+
publicNetConfig?: Record<string, unknown>;
|
|
172
|
+
/** 文件系统 CFS 配置(透传为 SCF CfsConfig) */
|
|
173
|
+
cfsConfig?: Record<string, unknown>;
|
|
174
|
+
/** 内网固定 IP 配置(透传为 SCF IntranetConfig) */
|
|
175
|
+
intranetConfig?: Record<string, unknown>;
|
|
176
|
+
/** 初始化超时时间(秒),范围 1-60 */
|
|
177
|
+
initTimeout?: number;
|
|
178
|
+
/** 是否开启异步属性(仅 CreateFunction 支持) */
|
|
179
|
+
asyncRunEnable?: boolean | 'TRUE' | 'FALSE';
|
|
180
|
+
/** 是否开启事件追踪(仅 CreateFunction 支持) */
|
|
181
|
+
traceEnable?: boolean | 'TRUE' | 'FALSE';
|
|
182
|
+
/** 是否自动创建 CLS 索引(仅 CreateFunction 支持) */
|
|
183
|
+
autoDeployClsTopicIndex?: boolean | 'TRUE' | 'FALSE';
|
|
184
|
+
/** 是否自动创建 CLS 主题(仅 CreateFunction 支持) */
|
|
185
|
+
autoCreateClsTopic?: boolean | 'TRUE' | 'FALSE';
|
|
186
|
+
/** 是否开启 DNS 缓存(仅 Event 函数支持) */
|
|
187
|
+
dnsCache?: boolean | 'TRUE' | 'FALSE';
|
|
159
188
|
}
|
|
160
189
|
/** 本地代码来源,三者互斥 */
|
|
161
190
|
export interface ILocalCodeSource {
|
|
@@ -233,11 +262,11 @@ export interface IImageBuildTarget {
|
|
|
233
262
|
namespace?: string;
|
|
234
263
|
/**
|
|
235
264
|
* 目标镜像仓库路径。
|
|
236
|
-
* cloud
|
|
237
|
-
* registry/namespace/repository
|
|
265
|
+
* personal cloud 可省略并默认使用函数名;enterprise cloud 在自动实例发现完成前需提供完整地址;
|
|
266
|
+
* local 策略当前需提供可直接 push 的完整 registry/namespace/repository 地址。
|
|
238
267
|
*/
|
|
239
268
|
repository?: string;
|
|
240
|
-
/**
|
|
269
|
+
/** local 策略的目标镜像 tag;cloud 策略由平台生成,不接受自定义 tag */
|
|
241
270
|
tag?: string;
|
|
242
271
|
/** 目标平台,默认 linux/amd64 */
|
|
243
272
|
platform?: string;
|
|
@@ -253,9 +282,9 @@ export interface IHttpImageRuntimeCommon extends IHttpFunctionDeployCommon {
|
|
|
253
282
|
runtime?: typeof CUSTOM_IMAGE_RUNTIME;
|
|
254
283
|
/** 容器监听端口,仅允许 9000 */
|
|
255
284
|
imagePort?: number;
|
|
256
|
-
/**
|
|
285
|
+
/** 镜像类型;省略时有 registryId 推断为 enterprise,否则推断为 personal */
|
|
257
286
|
imageType?: 'enterprise' | 'personal';
|
|
258
|
-
/** 企业版 TCR 实例 ID
|
|
287
|
+
/** 企业版 TCR 实例 ID 的运行时规范位置;个人版镜像不传 */
|
|
259
288
|
registryId?: string;
|
|
260
289
|
entryPoint?: string;
|
|
261
290
|
command?: string;
|