@cloudbase/manager-node 5.7.0-beta.3 → 5.7.1-beta.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.config.unit.js +14 -0
- package/lib/cloudApp/index.js +3 -12
- package/lib/env/index.js +9 -0
- package/lib/environment.js +0 -11
- package/lib/function/index.js +67 -26
- package/lib/hosting/index.js +1 -4
- package/lib/index.js +0 -31
- package/lib/storage/index.js +7 -6
- package/lib/utils/index.js +5 -62
- package/package.json +1 -2
- package/types/cloudApp/index.d.ts +0 -4
- package/types/cloudApp/types.d.ts +6 -87
- package/types/env/index.d.ts +8 -1
- package/types/env/type.d.ts +97 -6
- package/types/environment.d.ts +0 -7
- package/types/function/index.d.ts +3 -0
- package/types/function/types.d.ts +0 -15
- package/types/hosting/index.d.ts +0 -6
- package/types/index.d.ts +0 -18
- package/types/interfaces/function.interface.d.ts +1 -1
- package/types/storage/index.d.ts +0 -6
- package/types/utils/index.d.ts +0 -13
- package/lib/deploy/DatabaseDeployer.js +0 -238
- package/lib/deploy/DeployOrchestrator.js +0 -632
- package/lib/deploy/FunctionDeployer.js +0 -611
- package/lib/deploy/GatewayDeployer.js +0 -612
- package/lib/deploy/StateStore.js +0 -327
- package/lib/deploy/StaticDeployer.js +0 -239
- package/lib/deploy/domain.js +0 -41
- package/lib/deploy/framework.js +0 -105
- package/lib/deploy/function/artifact.js +0 -207
- package/lib/deploy/function/builders/cloud.js +0 -536
- package/lib/deploy/function/cam-preflight.js +0 -157
- package/lib/deploy/function/cloud-build-service.js +0 -170
- package/lib/deploy/function/config-guard.js +0 -592
- package/lib/deploy/function/docker-preflight.js +0 -87
- package/lib/deploy/function/image-preflight.js +0 -164
- package/lib/deploy/function/local-builder.js +0 -129
- package/lib/deploy/function/planner.js +0 -282
- package/lib/deploy/function/preflight.js +0 -264
- package/lib/deploy/types.js +0 -99
- package/lib/projectValidator/index.js +0 -440
- package/lib/projectValidator/types.js +0 -2
- package/types/deploy/DatabaseDeployer.d.ts +0 -70
- package/types/deploy/DeployOrchestrator.d.ts +0 -168
- package/types/deploy/FunctionDeployer.d.ts +0 -158
- package/types/deploy/GatewayDeployer.d.ts +0 -194
- package/types/deploy/StateStore.d.ts +0 -170
- package/types/deploy/StaticDeployer.d.ts +0 -97
- package/types/deploy/domain.d.ts +0 -17
- package/types/deploy/framework.d.ts +0 -23
- package/types/deploy/function/artifact.d.ts +0 -40
- package/types/deploy/function/builders/cloud.d.ts +0 -110
- package/types/deploy/function/cam-preflight.d.ts +0 -51
- package/types/deploy/function/cloud-build-service.d.ts +0 -10
- package/types/deploy/function/config-guard.d.ts +0 -41
- package/types/deploy/function/docker-preflight.d.ts +0 -17
- package/types/deploy/function/image-preflight.d.ts +0 -21
- package/types/deploy/function/local-builder.d.ts +0 -26
- package/types/deploy/function/planner.d.ts +0 -15
- package/types/deploy/function/preflight.d.ts +0 -9
- package/types/deploy/types.d.ts +0 -429
- package/types/projectValidator/index.d.ts +0 -38
- package/types/projectValidator/types.d.ts +0 -26
|
@@ -1,592 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.parseImageReference = parseImageReference;
|
|
7
|
-
exports.normalizeGatewayPath = normalizeGatewayPath;
|
|
8
|
-
exports.isCustomImageRuntime = isCustomImageRuntime;
|
|
9
|
-
exports.normalizeDeployConfig = normalizeDeployConfig;
|
|
10
|
-
exports.checkDeployConfig = checkDeployConfig;
|
|
11
|
-
exports.assertDeployConfig = assertDeployConfig;
|
|
12
|
-
const lodash_1 = __importDefault(require("lodash"));
|
|
13
|
-
const error_1 = require("../../error");
|
|
14
|
-
const types_1 = require("../types");
|
|
15
|
-
/**
|
|
16
|
-
* 配置契约守卫
|
|
17
|
-
*
|
|
18
|
-
* 该文件只做纯函数、无 IO 的校验,职责是保证进入部署编排的配置组合本身合法。
|
|
19
|
-
* 不负责以下检查,它们属于 Preflight:
|
|
20
|
-
* - 目录、Dockerfile、scf_bootstrap 是否真实存在
|
|
21
|
-
* - Docker 是否安装并可用
|
|
22
|
-
* - TCR 实例、地域、角色和权限
|
|
23
|
-
* - 网络连通性
|
|
24
|
-
*/
|
|
25
|
-
/** 云函数名称规则:字母开头,允许字母数字下划线中划线,长度 2-60 */
|
|
26
|
-
const FUNCTION_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9_-]{1,59}$/;
|
|
27
|
-
/** 镜像 tag 规则 */
|
|
28
|
-
const IMAGE_TAG_PATTERN = /^[A-Za-z0-9_][A-Za-z0-9._-]{0,127}$/;
|
|
29
|
-
/** 镜像 digest 规则 */
|
|
30
|
-
const IMAGE_DIGEST_PATTERN = /^[a-z0-9]+(?:[.+_-][a-z0-9]+)*:[a-fA-F0-9]{32,}$/;
|
|
31
|
-
/** 镜像仓库路径规则 */
|
|
32
|
-
const IMAGE_REPOSITORY_PATTERN = /^[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*(?:\/[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*)*$/;
|
|
33
|
-
/** 网关路径字符白名单,避免注入与歧义字符 */
|
|
34
|
-
const GATEWAY_PATH_PATTERN = /^[A-Za-z0-9\-._~%/:@]+$/;
|
|
35
|
-
/** 环境变量与构建参数名规则 */
|
|
36
|
-
const ENV_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
37
|
-
/** 疑似敏感字段名,用于提示不要通过构建参数传递密钥 */
|
|
38
|
-
const SENSITIVE_KEY_PATTERN = /(secret|token|password|passwd|credential|private[_-]?key|access[_-]?key)/i;
|
|
39
|
-
const HTTP_BUILD_STRATEGIES = ['zip', 'cloud', 'local', 'image'];
|
|
40
|
-
const MAX_GATEWAY_PATH_LENGTH = 512;
|
|
41
|
-
/**
|
|
42
|
-
* 解析 OCI 镜像引用
|
|
43
|
-
*
|
|
44
|
-
* 支持 [registry[:port]/]repository[:tag][@digest]
|
|
45
|
-
* 无法仅通过是否包含冒号判断 tag,因为 registry 可能带端口
|
|
46
|
-
* @returns 解析结果,非法时返回 null
|
|
47
|
-
*/
|
|
48
|
-
function parseImageReference(reference) {
|
|
49
|
-
if (typeof reference !== 'string') {
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
const ref = reference.trim();
|
|
53
|
-
if (!ref || /\s/.test(ref) || ref.length > 512) {
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
let remain = ref;
|
|
57
|
-
let digest;
|
|
58
|
-
const atIndex = remain.lastIndexOf('@');
|
|
59
|
-
if (atIndex > -1) {
|
|
60
|
-
digest = remain.slice(atIndex + 1);
|
|
61
|
-
remain = remain.slice(0, atIndex);
|
|
62
|
-
if (!IMAGE_DIGEST_PATTERN.test(digest)) {
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
let tag;
|
|
67
|
-
const lastSlashIndex = remain.lastIndexOf('/');
|
|
68
|
-
const colonIndex = remain.indexOf(':', lastSlashIndex + 1);
|
|
69
|
-
if (colonIndex > -1) {
|
|
70
|
-
tag = remain.slice(colonIndex + 1);
|
|
71
|
-
remain = remain.slice(0, colonIndex);
|
|
72
|
-
if (!IMAGE_TAG_PATTERN.test(tag)) {
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
if (!remain) {
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
79
|
-
let registry;
|
|
80
|
-
let repository = remain;
|
|
81
|
-
const firstSlashIndex = remain.indexOf('/');
|
|
82
|
-
if (firstSlashIndex > -1) {
|
|
83
|
-
const maybeRegistry = remain.slice(0, firstSlashIndex);
|
|
84
|
-
const isRegistry = maybeRegistry === 'localhost' ||
|
|
85
|
-
maybeRegistry.includes('.') ||
|
|
86
|
-
maybeRegistry.includes(':');
|
|
87
|
-
if (isRegistry) {
|
|
88
|
-
registry = maybeRegistry;
|
|
89
|
-
repository = remain.slice(firstSlashIndex + 1);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
if (!repository || !IMAGE_REPOSITORY_PATTERN.test(repository)) {
|
|
93
|
-
return null;
|
|
94
|
-
}
|
|
95
|
-
return { registry, repository, tag, digest };
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* 规范化网关路径
|
|
99
|
-
*
|
|
100
|
-
* 合并连续斜杠并去除尾部斜杠,根路径保持为 /
|
|
101
|
-
* @returns 规范化结果,非法时返回 null
|
|
102
|
-
*/
|
|
103
|
-
function normalizeGatewayPath(input) {
|
|
104
|
-
if (typeof input !== 'string') {
|
|
105
|
-
return null;
|
|
106
|
-
}
|
|
107
|
-
const raw = input.trim();
|
|
108
|
-
if (!raw || raw.length > MAX_GATEWAY_PATH_LENGTH) {
|
|
109
|
-
return null;
|
|
110
|
-
}
|
|
111
|
-
if (!raw.startsWith('/')) {
|
|
112
|
-
return null;
|
|
113
|
-
}
|
|
114
|
-
// 拒绝 query、fragment、反斜杠与控制字符
|
|
115
|
-
if (/[?#\\]/.test(raw) || /[\u0000-\u001f\u007f]/.test(raw)) {
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
if (!GATEWAY_PATH_PATTERN.test(raw)) {
|
|
119
|
-
return null;
|
|
120
|
-
}
|
|
121
|
-
const merged = raw.replace(/\/{2,}/g, '/');
|
|
122
|
-
const segments = merged.split('/');
|
|
123
|
-
// 拒绝相对路径段,避免路径穿越歧义
|
|
124
|
-
if (segments.some(segment => segment === '.' || segment === '..')) {
|
|
125
|
-
return null;
|
|
126
|
-
}
|
|
127
|
-
if (merged === '/') {
|
|
128
|
-
return '/';
|
|
129
|
-
}
|
|
130
|
-
return merged.replace(/\/+$/, '');
|
|
131
|
-
}
|
|
132
|
-
/** 判断是否为自定义镜像运行时 */
|
|
133
|
-
function isCustomImageRuntime(runtime) {
|
|
134
|
-
return typeof runtime === 'string' && runtime.toLowerCase() === types_1.CUSTOM_IMAGE_RUNTIME.toLowerCase();
|
|
135
|
-
}
|
|
136
|
-
/** 判断相对路径是否安全,禁止绝对路径与向上穿越 */
|
|
137
|
-
function isSafeRelativePath(input) {
|
|
138
|
-
if (typeof input !== 'string') {
|
|
139
|
-
return false;
|
|
140
|
-
}
|
|
141
|
-
const value = input.trim();
|
|
142
|
-
if (!value || value.length > 512) {
|
|
143
|
-
return false;
|
|
144
|
-
}
|
|
145
|
-
if (/[\u0000-\u001f\u007f]/.test(value)) {
|
|
146
|
-
return false;
|
|
147
|
-
}
|
|
148
|
-
// 绝对路径与 Windows 盘符
|
|
149
|
-
if (value.startsWith('/') || value.startsWith('\\') || /^[A-Za-z]:[\\/]/.test(value)) {
|
|
150
|
-
return false;
|
|
151
|
-
}
|
|
152
|
-
const segments = value.split(/[\\/]+/);
|
|
153
|
-
if (segments.some(segment => segment === '..')) {
|
|
154
|
-
return false;
|
|
155
|
-
}
|
|
156
|
-
return true;
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* 推断 HTTP 函数的构建策略
|
|
160
|
-
* 未显式指定时,按已有配置做保守推断,便于兼容旧配置
|
|
161
|
-
*/
|
|
162
|
-
function inferHttpBuildStrategy(config) {
|
|
163
|
-
if (config === null || config === void 0 ? void 0 : config.buildStrategy) {
|
|
164
|
-
return config.buildStrategy;
|
|
165
|
-
}
|
|
166
|
-
if (config === null || config === void 0 ? void 0 : config.imageConfig) {
|
|
167
|
-
return 'image';
|
|
168
|
-
}
|
|
169
|
-
if (config === null || config === void 0 ? void 0 : config.build) {
|
|
170
|
-
// 有构建上下文但未声明策略时,默认使用无需本地 Docker 的云端构建
|
|
171
|
-
return 'cloud';
|
|
172
|
-
}
|
|
173
|
-
if (isCustomImageRuntime(config === null || config === void 0 ? void 0 : config.runtime)) {
|
|
174
|
-
return 'image';
|
|
175
|
-
}
|
|
176
|
-
return 'zip';
|
|
177
|
-
}
|
|
178
|
-
/**
|
|
179
|
-
* 规范化部署配置
|
|
180
|
-
*
|
|
181
|
-
* 仅填充默认值与收窄类型,不做合法性判断,保证在非法输入下也能安全返回,
|
|
182
|
-
* 以便 checkDeployConfig 汇总全部问题
|
|
183
|
-
*/
|
|
184
|
-
function normalizeDeployConfig(config) {
|
|
185
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
186
|
-
if (!config || typeof config !== 'object') {
|
|
187
|
-
throw new error_1.CloudBaseError('部署配置必须是对象', {
|
|
188
|
-
code: types_1.FUNCTION_DEPLOY_ERROR.CONFIG_INVALID
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
const cloned = lodash_1.default.cloneDeep(config);
|
|
192
|
-
const type = cloned.type === 'HTTP' ? 'HTTP' : 'Event';
|
|
193
|
-
if (type === 'Event') {
|
|
194
|
-
return Object.assign(Object.assign({}, cloned), { type: 'Event', buildStrategy: 'zip' });
|
|
195
|
-
}
|
|
196
|
-
const buildStrategy = inferHttpBuildStrategy(cloned);
|
|
197
|
-
if (buildStrategy === 'zip') {
|
|
198
|
-
const isNodejsRuntime = cloned.runtime === undefined || String(cloned.runtime).startsWith('Nodejs');
|
|
199
|
-
if (isNodejsRuntime) {
|
|
200
|
-
return Object.assign(Object.assign({}, cloned), { type: 'HTTP', buildStrategy: 'zip' });
|
|
201
|
-
}
|
|
202
|
-
const ignoreList = Array.isArray(cloned.ignore)
|
|
203
|
-
? cloned.ignore
|
|
204
|
-
: cloned.ignore
|
|
205
|
-
? [cloned.ignore]
|
|
206
|
-
: [];
|
|
207
|
-
return Object.assign(Object.assign({}, cloned), { type: 'HTTP', buildStrategy: 'zip',
|
|
208
|
-
// SCF Web 函数的非 Node.js 运行时不支持云端安装依赖,需随代码包携带
|
|
209
|
-
installDependency: false,
|
|
210
|
-
// node_modules 必须随代码包上传,不能被 ignore 排除
|
|
211
|
-
ignore: ignoreList.filter((item) => !/node_modules/.test(item)) });
|
|
212
|
-
}
|
|
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
|
-
if (buildStrategy === 'cloud' || buildStrategy === 'local') {
|
|
215
|
-
normalizedImage.imageType =
|
|
216
|
-
(_b = cloned.imageType) !== null && _b !== void 0 ? _b : (((_c = cloned.build) === null || _c === void 0 ? void 0 : _c.registryId) ? 'enterprise' : 'personal');
|
|
217
|
-
}
|
|
218
|
-
// 镜像函数由容器启动命令决定入口,不使用 handler 与云端装依赖
|
|
219
|
-
delete normalizedImage.handler;
|
|
220
|
-
delete normalizedImage.installDependency;
|
|
221
|
-
if (buildStrategy === 'cloud' || buildStrategy === 'local') {
|
|
222
|
-
normalizedImage.build = Object.assign(Object.assign({}, (cloned.build || {})), { dockerfile: (_e = (_d = cloned.build) === null || _d === void 0 ? void 0 : _d.dockerfile) !== null && _e !== void 0 ? _e : types_1.DEFAULT_DOCKERFILE, platform: (_g = (_f = cloned.build) === null || _f === void 0 ? void 0 : _f.platform) !== null && _g !== void 0 ? _g : types_1.DEFAULT_IMAGE_PLATFORM, repository: (_j = (_h = cloned.build) === null || _h === void 0 ? void 0 : _h.repository) !== null && _j !== void 0 ? _j : cloned.name });
|
|
223
|
-
}
|
|
224
|
-
if (buildStrategy === 'local') {
|
|
225
|
-
// 默认不静默改变构建环境
|
|
226
|
-
normalizedImage.localFallback = (_k = cloned.localFallback) !== null && _k !== void 0 ? _k : 'error';
|
|
227
|
-
}
|
|
228
|
-
return normalizedImage;
|
|
229
|
-
}
|
|
230
|
-
function fail(id, summary, remediation) {
|
|
231
|
-
return { id, status: 'fail', summary, remediation };
|
|
232
|
-
}
|
|
233
|
-
function warn(id, summary, remediation) {
|
|
234
|
-
return { id, status: 'warn', summary, remediation };
|
|
235
|
-
}
|
|
236
|
-
/** 校验通用函数属性 */
|
|
237
|
-
function checkCommonFields(config, checks) {
|
|
238
|
-
if (typeof config.name !== 'string' || !FUNCTION_NAME_PATTERN.test(config.name)) {
|
|
239
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.CONFIG_NAME_INVALID, '函数名称不合法', '名称需以字母开头,只能包含字母、数字、下划线和中划线,长度 2-60'));
|
|
240
|
-
}
|
|
241
|
-
if (config.type !== undefined && config.type !== 'Event' && config.type !== 'HTTP') {
|
|
242
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.CONFIG_TYPE_INVALID, '函数类型不合法', 'type 只能是 Event 或 HTTP'));
|
|
243
|
-
}
|
|
244
|
-
if (config.timeout !== undefined) {
|
|
245
|
-
const timeout = config.timeout;
|
|
246
|
-
if (!Number.isInteger(timeout) || timeout < 1 || timeout > 900) {
|
|
247
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.CONFIG_TIMEOUT_INVALID, '超时时间不合法', 'timeout 需为 1-900 之间的整数,单位秒'));
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
if (config.memorySize !== undefined) {
|
|
251
|
-
const memorySize = config.memorySize;
|
|
252
|
-
if (!Number.isInteger(memorySize) || memorySize < 64 || memorySize > 3072) {
|
|
253
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.CONFIG_MEMORY_SIZE_INVALID, '内存规格不合法', 'memorySize 需为 64-3072 之间的整数,单位 MB'));
|
|
254
|
-
}
|
|
255
|
-
else if (memorySize % 64 !== 0) {
|
|
256
|
-
checks.push(warn(types_1.FUNCTION_DEPLOY_ERROR.CONFIG_MEMORY_SIZE_INVALID, '内存规格可能不被支持', '建议使用 64 的整数倍,例如 128、256、512'));
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
if (config.envVariables !== undefined) {
|
|
260
|
-
if (!lodash_1.default.isPlainObject(config.envVariables)) {
|
|
261
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.CONFIG_ENV_VARIABLE_INVALID, '环境变量必须是对象', 'envVariables 需为 key-value 形式'));
|
|
262
|
-
}
|
|
263
|
-
else {
|
|
264
|
-
Object.entries(config.envVariables).forEach(([key, value]) => {
|
|
265
|
-
if (!ENV_KEY_PATTERN.test(key)) {
|
|
266
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.CONFIG_ENV_VARIABLE_INVALID, `环境变量名不合法:${key}`, '变量名需以字母或下划线开头,仅包含字母、数字和下划线'));
|
|
267
|
-
}
|
|
268
|
-
const validValue = typeof value === 'string' ||
|
|
269
|
-
typeof value === 'number' ||
|
|
270
|
-
typeof value === 'boolean';
|
|
271
|
-
if (!validValue) {
|
|
272
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.CONFIG_ENV_VARIABLE_INVALID, `环境变量 ${key} 的值类型不合法`, '值只能是字符串、数字或布尔值'));
|
|
273
|
-
}
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
/** 校验本地代码来源 */
|
|
279
|
-
function checkCodeSource(config, checks) {
|
|
280
|
-
const sources = ['functionRootPath', 'functionPath', 'base64Code'].filter(key => config[key] !== undefined && config[key] !== null && config[key] !== '');
|
|
281
|
-
if (sources.length === 0) {
|
|
282
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.CODE_SOURCE_MISSING, '缺少代码来源', '请提供 functionRootPath、functionPath 或 base64Code 之一'));
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
if (sources.length > 1) {
|
|
286
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.CODE_SOURCE_CONFLICT, '代码来源存在冲突', `functionRootPath、functionPath 和 base64Code 只能指定一个,当前指定了 ${sources.join('、')}`));
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
/** 校验 Event 函数专属约束 */
|
|
290
|
-
function checkEventConfig(raw, checks) {
|
|
291
|
-
if (raw.buildStrategy !== undefined && raw.buildStrategy !== 'zip') {
|
|
292
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.CONFIG_STRATEGY_INVALID, `Event 函数不支持 buildStrategy: ${raw.buildStrategy}`, '镜像构建策略仅支持 HTTP 函数,Event 函数请使用代码包部署'));
|
|
293
|
-
}
|
|
294
|
-
if (raw.imageConfig || isCustomImageRuntime(raw.runtime)) {
|
|
295
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.EVENT_IMAGE_NOT_SUPPORTED, 'Event 函数不支持自定义镜像', '请将 type 设置为 HTTP,或改用托管运行时'));
|
|
296
|
-
}
|
|
297
|
-
if (raw.gatewayPath !== undefined || raw.public !== undefined) {
|
|
298
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.EVENT_GATEWAY_NOT_SUPPORTED, 'Event 函数不支持网关路径与匿名访问配置', '如需 HTTP 访问,请将 type 设置为 HTTP'));
|
|
299
|
-
}
|
|
300
|
-
if (raw.protocolType !== undefined || raw.instanceConcurrencyConfig !== undefined) {
|
|
301
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.EVENT_HTTP_FIELD_NOT_SUPPORTED, 'Event 函数不支持 HTTP 协议与实例并发配置', '请移除 protocolType 与 instanceConcurrencyConfig'));
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
/** 校验 HTTP 函数公共约束 */
|
|
305
|
-
function checkHttpCommon(raw, checks) {
|
|
306
|
-
var _a, _b;
|
|
307
|
-
if (raw.triggers !== undefined) {
|
|
308
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.HTTP_TRIGGER_NOT_SUPPORTED, 'HTTP 函数不支持事件触发器', 'HTTP 函数通过网关路径访问,如需定时任务请使用 Event 函数'));
|
|
309
|
-
}
|
|
310
|
-
if (raw.gatewayPath !== undefined && normalizeGatewayPath(raw.gatewayPath) === null) {
|
|
311
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.GATEWAY_PATH_INVALID, '网关路径不合法', '需以 / 开头,不能包含查询串、片段、反斜杠或相对路径段'));
|
|
312
|
-
}
|
|
313
|
-
const concurrency = raw.instanceConcurrencyConfig;
|
|
314
|
-
if (concurrency !== undefined) {
|
|
315
|
-
if (!lodash_1.default.isPlainObject(concurrency)) {
|
|
316
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.HTTP_CONCURRENCY_INVALID, '实例并发配置必须是对象'));
|
|
317
|
-
}
|
|
318
|
-
else if (concurrency.maxConcurrency !== undefined) {
|
|
319
|
-
const max = concurrency.maxConcurrency;
|
|
320
|
-
if (!Number.isInteger(max) || max < 1 || max > 100) {
|
|
321
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.HTTP_CONCURRENCY_INVALID, '单实例并发数不合法', 'maxConcurrency 需为 1-100 之间的整数'));
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
const idleTimeOut = (_b = (_a = raw.protocolParams) === null || _a === void 0 ? void 0 : _a.wsParams) === null || _b === void 0 ? void 0 : _b.idleTimeOut;
|
|
326
|
-
if (idleTimeOut !== undefined) {
|
|
327
|
-
if (!Number.isInteger(idleTimeOut) || idleTimeOut < 10 || idleTimeOut > 7200) {
|
|
328
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.HTTP_PROTOCOL_PARAMS_INVALID, 'WebSocket 空闲超时不合法', 'idleTimeOut 需为 10-7200 之间的整数,单位秒'));
|
|
329
|
-
}
|
|
330
|
-
else if (raw.protocolType !== 'WS') {
|
|
331
|
-
checks.push(warn(types_1.FUNCTION_DEPLOY_ERROR.HTTP_PROTOCOL_PARAMS_INVALID, 'WebSocket 参数在当前协议下不生效', '如需使用 WebSocket,请设置 protocolType 为 WS'));
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
/** 校验镜像函数公共约束 */
|
|
336
|
-
function checkImageRuntimeCommon(raw, checks) {
|
|
337
|
-
if (raw.runtime !== undefined && !isCustomImageRuntime(raw.runtime)) {
|
|
338
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.IMAGE_RUNTIME_INVALID, `镜像部署要求 runtime 为 ${types_1.CUSTOM_IMAGE_RUNTIME}`, `当前为 ${raw.runtime},请移除 runtime 或设置为 ${types_1.CUSTOM_IMAGE_RUNTIME}`));
|
|
339
|
-
}
|
|
340
|
-
if (raw.imagePort !== undefined && raw.imagePort !== types_1.IMAGE_FUNCTION_PORT) {
|
|
341
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.IMAGE_PORT_INVALID, `镜像监听端口必须为 ${types_1.IMAGE_FUNCTION_PORT}`, `请让容器监听 ${types_1.IMAGE_FUNCTION_PORT} 端口`));
|
|
342
|
-
}
|
|
343
|
-
if (raw.handler !== undefined) {
|
|
344
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.IMAGE_HANDLER_NOT_ALLOWED, '镜像函数不支持 handler', '镜像函数由容器启动命令决定入口,请移除 handler'));
|
|
345
|
-
}
|
|
346
|
-
if (raw.installDependency !== undefined) {
|
|
347
|
-
checks.push(warn(types_1.FUNCTION_DEPLOY_ERROR.HTTP_ZIP_INSTALL_DEPENDENCY_OVERRIDDEN, '镜像函数忽略 installDependency', '依赖应在镜像构建阶段安装,该字段已被忽略'));
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
/** 校验镜像引用 */
|
|
351
|
-
function checkImageReference(imageConfig, checks) {
|
|
352
|
-
const imageUri = imageConfig === null || imageConfig === void 0 ? void 0 : imageConfig.imageUri;
|
|
353
|
-
if (typeof imageUri !== 'string' || !imageUri.trim()) {
|
|
354
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.IMAGE_URI_INVALID, '缺少镜像地址', '请配置 imageConfig.imageUri'));
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
const parsed = parseImageReference(imageUri);
|
|
358
|
-
if (!parsed) {
|
|
359
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.IMAGE_URI_INVALID, '镜像地址不合法', '格式应为 registry/namespace/repository:tag 或 ...@sha256:digest'));
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
if (!parsed.tag && !parsed.digest) {
|
|
363
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.IMAGE_TAG_MISSING, '镜像地址缺少版本标识', '请使用固定 tag 或 digest,保证部署可重试与可回滚'));
|
|
364
|
-
return;
|
|
365
|
-
}
|
|
366
|
-
if (parsed.tag === 'latest' && !parsed.digest) {
|
|
367
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.IMAGE_TAG_LATEST_FORBIDDEN, '镜像地址禁止使用 latest', '请使用内容可追溯的不可变 tag 或 digest'));
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
/**
|
|
371
|
-
* 校验镜像类型与仓库 ID
|
|
372
|
-
* image 策略从 imageConfig.registryId(兼容顶层 registryId)读取;
|
|
373
|
-
* local/cloud 策略从 build.registryId 读取,imageType 仍位于镜像运行时公共配置顶层。
|
|
374
|
-
*/
|
|
375
|
-
function checkImageIdentity(raw, checks) {
|
|
376
|
-
var _a, _b, _c, _d, _e;
|
|
377
|
-
const imageConfig = raw === null || raw === void 0 ? void 0 : raw.imageConfig;
|
|
378
|
-
const buildRegistryId = (_a = raw === null || raw === void 0 ? void 0 : raw.build) === null || _a === void 0 ? void 0 : _a.registryId;
|
|
379
|
-
const buildStrategy = raw === null || raw === void 0 ? void 0 : raw.buildStrategy;
|
|
380
|
-
const defaultImageType = buildStrategy === 'cloud' || buildStrategy === 'local'
|
|
381
|
-
? buildRegistryId
|
|
382
|
-
? 'enterprise'
|
|
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;
|
|
387
|
-
if (imageType !== 'enterprise' && imageType !== 'personal') {
|
|
388
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.IMAGE_TYPE_INVALID, '镜像类型不合法', 'imageType 只能是 enterprise 或 personal'));
|
|
389
|
-
return;
|
|
390
|
-
}
|
|
391
|
-
if (imageType === 'enterprise') {
|
|
392
|
-
if (typeof registryId !== 'string' || !registryId.trim()) {
|
|
393
|
-
const remediation = buildStrategy === 'cloud' || buildStrategy === 'local'
|
|
394
|
-
? '请配置 build.registryId'
|
|
395
|
-
: '请配置 registryId';
|
|
396
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.IMAGE_REGISTRY_ID_MISSING, '企业版镜像缺少仓库实例 ID', remediation));
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
else if (registryId !== undefined) {
|
|
400
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.IMAGE_TYPE_INVALID, '个人版镜像不能配置企业版仓库实例 ID', '请移除 build.registryId,或将 imageType 设置为 enterprise'));
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
/** 校验镜像构建目标配置 */
|
|
404
|
-
function checkBuildTarget(raw, checks) {
|
|
405
|
-
var _a, _b, _c;
|
|
406
|
-
const build = raw.build;
|
|
407
|
-
if (!lodash_1.default.isPlainObject(build)) {
|
|
408
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_CONFIG_MISSING, '缺少镜像构建配置', '请配置 build,至少包含构建上下文与 Dockerfile 信息'));
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
if (raw.buildStrategy === 'cloud' && build.tag !== undefined) {
|
|
412
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_TAG_INVALID, 'cloud 策略不支持自定义镜像 tag', '请移除 build.tag,云端构建会统一使用平台生成的 CLOUDBASE_VERSION_NAME'));
|
|
413
|
-
}
|
|
414
|
-
if (build.cwd !== undefined && (typeof build.cwd !== 'string' || !build.cwd.trim())) {
|
|
415
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_CONTEXT_INVALID, '构建上下文目录不合法', 'build.cwd 需为非空字符串'));
|
|
416
|
-
}
|
|
417
|
-
const dockerfile = (_a = build.dockerfile) !== null && _a !== void 0 ? _a : types_1.DEFAULT_DOCKERFILE;
|
|
418
|
-
if (!isSafeRelativePath(dockerfile)) {
|
|
419
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_DOCKERFILE_INVALID, 'Dockerfile 路径不合法', '需为构建上下文内的相对路径,且不能包含 .. 或绝对路径'));
|
|
420
|
-
}
|
|
421
|
-
const platform = (_b = build.platform) !== null && _b !== void 0 ? _b : types_1.DEFAULT_IMAGE_PLATFORM;
|
|
422
|
-
if (platform !== types_1.DEFAULT_IMAGE_PLATFORM) {
|
|
423
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_PLATFORM_INVALID, `当前仅支持构建 ${types_1.DEFAULT_IMAGE_PLATFORM} 镜像`, `请移除 build.platform 或设置为 ${types_1.DEFAULT_IMAGE_PLATFORM}`));
|
|
424
|
-
}
|
|
425
|
-
if (build.tag !== undefined && raw.buildStrategy !== 'cloud') {
|
|
426
|
-
if (typeof build.tag !== 'string' || !IMAGE_TAG_PATTERN.test(build.tag)) {
|
|
427
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_TAG_INVALID, '镜像 tag 不合法'));
|
|
428
|
-
}
|
|
429
|
-
else if (build.tag === 'latest') {
|
|
430
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.IMAGE_TAG_LATEST_FORBIDDEN, '镜像 tag 禁止使用 latest', '请使用不可变 tag,或不指定 tag 由构建器按内容摘要生成'));
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
if (build.repository !== undefined) {
|
|
434
|
-
if (typeof build.repository !== 'string' ||
|
|
435
|
-
!IMAGE_REPOSITORY_PATTERN.test(build.repository)) {
|
|
436
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_REPOSITORY_INVALID, '镜像仓库名不合法', '只能包含小写字母、数字与 . _ - 分隔符'));
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
if (build.buildArgs !== undefined) {
|
|
440
|
-
if (!lodash_1.default.isPlainObject(build.buildArgs)) {
|
|
441
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_ARG_INVALID, '构建参数必须是对象'));
|
|
442
|
-
}
|
|
443
|
-
else {
|
|
444
|
-
Object.entries(build.buildArgs).forEach(([key, value]) => {
|
|
445
|
-
if (!ENV_KEY_PATTERN.test(key)) {
|
|
446
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_ARG_INVALID, `构建参数名不合法:${key}`));
|
|
447
|
-
}
|
|
448
|
-
if (typeof value !== 'string') {
|
|
449
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_ARG_INVALID, `构建参数 ${key} 的值必须是字符串`));
|
|
450
|
-
}
|
|
451
|
-
if (SENSITIVE_KEY_PATTERN.test(key)) {
|
|
452
|
-
const message = `构建参数 ${key} 可能包含敏感信息`;
|
|
453
|
-
const remediation = '构建参数会写入镜像构建记录,请改用运行时环境变量或密钥管理';
|
|
454
|
-
checks.push(raw.buildStrategy === 'cloud'
|
|
455
|
-
? fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_ARG_INVALID, message, remediation)
|
|
456
|
-
: warn(types_1.FUNCTION_DEPLOY_ERROR.BUILD_ARG_INVALID, message, remediation));
|
|
457
|
-
}
|
|
458
|
-
});
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
const registryCredential = build.registryCredential;
|
|
462
|
-
const imageType = (_c = raw.imageType) !== null && _c !== void 0 ? _c : (build.registryId ? 'enterprise' : 'personal');
|
|
463
|
-
if (raw.buildStrategy === 'cloud' && imageType === 'personal') {
|
|
464
|
-
if (typeof build.namespace !== 'string' ||
|
|
465
|
-
!build.namespace.trim() ||
|
|
466
|
-
!IMAGE_REPOSITORY_PATTERN.test(build.namespace.trim())) {
|
|
467
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_REPOSITORY_INVALID, '个人版 TCR 命名空间未配置或不合法', '请在 build.namespace 中显式声明,推荐使用 {{env.TCB_TCR_NAMESPACE}}'));
|
|
468
|
-
}
|
|
469
|
-
if (!lodash_1.default.isPlainObject(registryCredential)) {
|
|
470
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.CLOUD_REGISTRY_CREDENTIAL_MISSING, '个人版 TCR 云端构建缺少推送凭证', '请在 build.registryCredential 中显式声明 username/password,' +
|
|
471
|
-
'推荐使用 {{env.TCB_TCR_USERNAME}} 与 {{env.TCB_TCR_PASSWORD}}'));
|
|
472
|
-
}
|
|
473
|
-
else {
|
|
474
|
-
const username = registryCredential.username;
|
|
475
|
-
const password = registryCredential.password;
|
|
476
|
-
if (typeof username !== 'string' || !/^\d{5,20}$/.test(username.trim())) {
|
|
477
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.CLOUD_REGISTRY_CREDENTIAL_INVALID, '个人版 TCR 登录用户名不合法', 'username 需为腾讯云账号 ID(UIN),推荐使用 {{env.TCB_TCR_USERNAME}}'));
|
|
478
|
-
}
|
|
479
|
-
if (typeof password !== 'string' || !password || password.length > 16 * 1024) {
|
|
480
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.CLOUD_REGISTRY_CREDENTIAL_INVALID, '个人版 TCR 固定密码未配置或长度不合法', 'password 必须显式声明且不能为空,推荐使用 {{env.TCB_TCR_PASSWORD}}'));
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
else if (registryCredential !== undefined) {
|
|
485
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.CLOUD_REGISTRY_CREDENTIAL_INVALID, 'registryCredential 仅支持 cloud + personal 构建', '企业版使用 STS 临时凭证换取 token;local 构建使用本机 Docker 登录态'));
|
|
486
|
-
}
|
|
487
|
-
if (raw.imageConfig !== undefined) {
|
|
488
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.IMAGE_CONFIG_NOT_ALLOWED, `buildStrategy 为 ${raw.buildStrategy} 时不能指定 imageConfig`, '镜像地址将由构建结果决定,如需直接部署已有镜像请使用 buildStrategy: image'));
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
/**
|
|
492
|
-
* 校验部署配置
|
|
493
|
-
* @returns 检查报告,包含全部问题而非仅第一个
|
|
494
|
-
*/
|
|
495
|
-
function checkDeployConfig(config) {
|
|
496
|
-
const checks = [];
|
|
497
|
-
if (!config || typeof config !== 'object') {
|
|
498
|
-
return {
|
|
499
|
-
scope: 'config',
|
|
500
|
-
ready: false,
|
|
501
|
-
checks: [
|
|
502
|
-
fail(types_1.FUNCTION_DEPLOY_ERROR.CONFIG_INVALID, '部署配置必须是对象', '请传入 IFunctionDeployConfig 对象')
|
|
503
|
-
]
|
|
504
|
-
};
|
|
505
|
-
}
|
|
506
|
-
const raw = config;
|
|
507
|
-
checkCommonFields(raw, checks);
|
|
508
|
-
const isHttp = raw.type === 'HTTP';
|
|
509
|
-
let strategy = 'zip';
|
|
510
|
-
if (!isHttp) {
|
|
511
|
-
checkEventConfig(raw, checks);
|
|
512
|
-
checkCodeSource(raw, checks);
|
|
513
|
-
}
|
|
514
|
-
else {
|
|
515
|
-
strategy = inferHttpBuildStrategy(raw);
|
|
516
|
-
if (!HTTP_BUILD_STRATEGIES.includes(strategy)) {
|
|
517
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.CONFIG_STRATEGY_INVALID, `不支持的 buildStrategy: ${strategy}`, `可选值为 ${HTTP_BUILD_STRATEGIES.join('、')}`));
|
|
518
|
-
return buildReport(checks, strategy);
|
|
519
|
-
}
|
|
520
|
-
checkHttpCommon(raw, checks);
|
|
521
|
-
if (strategy === 'zip') {
|
|
522
|
-
if (isCustomImageRuntime(raw.runtime)) {
|
|
523
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.HTTP_ZIP_RUNTIME_INVALID, `代码包部署不支持 ${types_1.CUSTOM_IMAGE_RUNTIME} 运行时`, '请改用 buildStrategy 为 cloud、local 或 image'));
|
|
524
|
-
}
|
|
525
|
-
if (raw.imageConfig !== undefined) {
|
|
526
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.HTTP_ZIP_IMAGE_CONFIG_CONFLICT, '代码包部署不能指定 imageConfig', '如需部署镜像,请设置 buildStrategy 为 image'));
|
|
527
|
-
}
|
|
528
|
-
const isNodejsRuntime = raw.runtime === undefined || String(raw.runtime).startsWith('Nodejs');
|
|
529
|
-
if (!isNodejsRuntime &&
|
|
530
|
-
raw.installDependency !== undefined &&
|
|
531
|
-
raw.installDependency !== false &&
|
|
532
|
-
raw.installDependency !== 'FALSE') {
|
|
533
|
-
checks.push(warn(types_1.FUNCTION_DEPLOY_ERROR.HTTP_ZIP_INSTALL_DEPENDENCY_OVERRIDDEN, 'HTTP 非 Node.js 代码包部署已强制关闭云端安装依赖', '请在本地安装运行依赖并将其一并打入代码包'));
|
|
534
|
-
}
|
|
535
|
-
checkCodeSource(raw, checks);
|
|
536
|
-
}
|
|
537
|
-
else {
|
|
538
|
-
checkImageRuntimeCommon(raw, checks);
|
|
539
|
-
if (strategy === 'image') {
|
|
540
|
-
if (!lodash_1.default.isPlainObject(raw.imageConfig)) {
|
|
541
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.IMAGE_CONFIG_MISSING, '缺少镜像配置', '请配置 imageConfig.imageUri 与 registryId'));
|
|
542
|
-
}
|
|
543
|
-
else {
|
|
544
|
-
checkImageReference(raw.imageConfig, checks);
|
|
545
|
-
// imageType 在顶层,registryId 允许顶层或 imageConfig,统一传 raw
|
|
546
|
-
checkImageIdentity(raw, checks);
|
|
547
|
-
// 注:imagePort 定义在顶层,已由 checkImageRuntimeCommon 校验,此处不重复
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
else {
|
|
551
|
-
checkImageIdentity(raw, checks);
|
|
552
|
-
checkBuildTarget(raw, checks);
|
|
553
|
-
if (strategy === 'local') {
|
|
554
|
-
const fallback = raw.localFallback;
|
|
555
|
-
if (fallback !== undefined && fallback !== 'cloud' && fallback !== 'error') {
|
|
556
|
-
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.LOCAL_FALLBACK_INVALID, 'localFallback 取值不合法', "只能是 'cloud' 或 'error'"));
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
return buildReport(checks, isHttp ? strategy : 'zip');
|
|
563
|
-
}
|
|
564
|
-
function buildReport(checks, strategy) {
|
|
565
|
-
const hasFail = checks.some(item => item.status === 'fail');
|
|
566
|
-
return {
|
|
567
|
-
scope: 'config',
|
|
568
|
-
strategy,
|
|
569
|
-
ready: !hasFail,
|
|
570
|
-
checks
|
|
571
|
-
};
|
|
572
|
-
}
|
|
573
|
-
/**
|
|
574
|
-
* 校验并规范化部署配置
|
|
575
|
-
* @throws CloudBaseError 存在任一 fail 项时抛出,错误信息包含全部失败原因
|
|
576
|
-
*/
|
|
577
|
-
function assertDeployConfig(config) {
|
|
578
|
-
const report = checkDeployConfig(config);
|
|
579
|
-
if (!report.ready) {
|
|
580
|
-
const failures = report.checks.filter(item => item.status === 'fail');
|
|
581
|
-
const message = failures
|
|
582
|
-
.map(item => {
|
|
583
|
-
const remediation = item.remediation ? `,${item.remediation}` : '';
|
|
584
|
-
return `- [${item.id}] ${item.summary}${remediation}`;
|
|
585
|
-
})
|
|
586
|
-
.join('\n');
|
|
587
|
-
throw new error_1.CloudBaseError(`部署配置校验未通过:\n${message}`, {
|
|
588
|
-
code: failures[0].id
|
|
589
|
-
});
|
|
590
|
-
}
|
|
591
|
-
return normalizeDeployConfig(config);
|
|
592
|
-
}
|