@cloudbase/manager-node 5.8.9-beta.0 → 5.9.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/.codebuddy/rules/error-code-contract.mdc +13 -0
- package/CHANGELOG.md +1 -0
- package/lib/agent/index.js +6 -5
- package/lib/aiModel/index.js +21 -9
- package/lib/cloudApp/index.js +4 -4
- package/lib/cloudrun/index.js +16 -15
- package/lib/common/index.js +1 -1
- package/lib/database/index.js +23 -23
- package/lib/deploy/DatabaseDeployer.js +19 -24
- package/lib/deploy/DeployOrchestrator.js +2 -2
- package/lib/deploy/FunctionDeployer.js +19 -1
- package/lib/deploy/GatewayDeployer.js +5 -5
- package/lib/deploy/StaticDeployer.js +2 -2
- package/lib/deploy/framework.js +2 -1
- package/lib/deploy/function/config-guard.js +23 -3
- package/lib/deploy/function/enterprise-tcr.js +18 -2
- package/lib/docs/index.js +4 -3
- package/lib/env/index.js +16 -16
- package/lib/environment.js +4 -4
- package/lib/environmentManager.js +5 -1
- package/lib/error-docs.js +385 -0
- package/lib/error-types.js +21 -0
- package/lib/error.js +239 -2
- package/lib/function/index.js +135 -41
- package/lib/function/packer.js +1 -1
- package/lib/hosting/index.js +1 -1
- package/lib/index.js +2 -1
- package/lib/integration/crypto.js +4 -3
- package/lib/integration/index.js +1 -1
- package/lib/integration/secretKey.js +4 -4
- package/lib/monitor/index.js +1 -1
- package/lib/mysql/index.js +2 -1
- package/lib/permission/index.js +56 -49
- package/lib/platform/index.js +2 -2
- package/lib/sandbox/index.js +68 -61
- package/lib/storage/index.js +164 -162
- package/lib/user/index.js +32 -31
- package/lib/utils/cloud-api-request.js +13 -5
- package/lib/utils/cloudbase-request.js +21 -0
- package/lib/utils/fs.js +3 -3
- package/lib/utils/http-request.js +2 -2
- package/lib/utils/index.js +4 -3
- package/lib/utils/storage-config.js +3 -3
- package/package.json +2 -2
- package/types/error-docs.d.ts +57 -0
- package/types/error-types.d.ts +32 -0
- package/types/error.d.ts +148 -2
- package/types/function/index.d.ts +14 -1
- package/types/utils/cloudbase-request.d.ts +22 -0
package/lib/function/index.js
CHANGED
|
@@ -10,12 +10,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.FunctionService = exports.ENTERPRISE_IMAGE_REGION_GUIDANCE = void 0;
|
|
13
|
+
exports.formatImageDeploymentErrorMessage = formatImageDeploymentErrorMessage;
|
|
13
14
|
const fs_1 = __importDefault(require("fs"));
|
|
14
15
|
const path_1 = __importDefault(require("path"));
|
|
15
16
|
const lodash_1 = __importDefault(require("lodash"));
|
|
16
17
|
const cos_nodejs_sdk_v5_1 = __importDefault(require("cos-nodejs-sdk-v5"));
|
|
17
18
|
const packer_1 = require("./packer");
|
|
18
19
|
const error_1 = require("../error");
|
|
20
|
+
const error_types_1 = require("../error-types");
|
|
19
21
|
const utils_1 = require("../utils");
|
|
20
22
|
const constant_1 = require("../constant");
|
|
21
23
|
/**
|
|
@@ -115,23 +117,46 @@ function getFieldIgnoreCase(obj, fieldName) {
|
|
|
115
117
|
}
|
|
116
118
|
/** 企业版 TCR 镜像对 SCF 不可见时的排查指引 */
|
|
117
119
|
exports.ENTERPRISE_IMAGE_REGION_GUIDANCE = '请确认 TCR 企业版实例已在当前函数地域提供可访问的实例或复制实例,并检查 RegistryId、镜像地址及拉取权限。';
|
|
120
|
+
/**
|
|
121
|
+
* 幂等缓存:错误对象 → 已增强的文本。
|
|
122
|
+
*
|
|
123
|
+
* 为什么不用「给错误对象挂标记位 / 写回 message」:
|
|
124
|
+
* 1) **不改入参** ⇒ 无副作用,不污染调用方传入的数据源;
|
|
125
|
+
* 2) 冻结 / 只读 / 不可扩展对象同样有效(此前要靠 try/catch 兜底);
|
|
126
|
+
* 3) WeakMap 为弱引用 ⇒ 不阻碍错误对象被回收。
|
|
127
|
+
*/
|
|
128
|
+
const ENHANCED_MESSAGES = new WeakMap();
|
|
129
|
+
/** 取出可安全取属性的错误对象(null / 原始值 ⇒ undefined) */
|
|
130
|
+
function errorCarrierOf(error) {
|
|
131
|
+
return typeof error === 'object' && error !== null ? error : undefined;
|
|
132
|
+
}
|
|
118
133
|
/**
|
|
119
134
|
* 为企业版镜像的典型不可见错误补充排查指引,同时保留原始错误和 requestId。
|
|
120
135
|
* 仅匹配 SCF 已进入 enterprise 镜像查询阶段的错误,避免给普通网络或鉴权错误误导提示。
|
|
136
|
+
*
|
|
137
|
+
* 纯函数:只读入参,**不修改**传进来的错误对象;幂等由 WeakMap 缓存保证。
|
|
121
138
|
*/
|
|
122
139
|
function formatImageDeploymentErrorMessage(error, imageConfig) {
|
|
123
|
-
const
|
|
124
|
-
const
|
|
140
|
+
const carrier = errorCarrierOf(error);
|
|
141
|
+
const message = typeof (carrier === null || carrier === void 0 ? void 0 : carrier.message) === 'string' ? carrier.message : String(error);
|
|
142
|
+
const code = typeof (carrier === null || carrier === void 0 ? void 0 : carrier.code) === 'string' ? carrier.code : '';
|
|
125
143
|
const haystack = `${code} ${message}`.toLowerCase();
|
|
126
144
|
const isEnterprise = (imageConfig === null || imageConfig === void 0 ? void 0 : imageConfig.imageType) === 'enterprise';
|
|
127
145
|
const isEnterpriseImageNotFound = haystack.includes('resourcenotfound.image') ||
|
|
128
146
|
haystack.includes('describe images enterprise resource not found');
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
147
|
+
// 幂等只看缓存,不看文本(文本被中间层改动也不影响判定)
|
|
148
|
+
const applied = carrier ? ENHANCED_MESSAGES.get(carrier) : undefined;
|
|
149
|
+
if (applied !== undefined) {
|
|
150
|
+
return applied;
|
|
151
|
+
}
|
|
152
|
+
if (!isEnterprise || !isEnterpriseImageNotFound) {
|
|
132
153
|
return message;
|
|
133
154
|
}
|
|
134
|
-
|
|
155
|
+
const enhanced = `${message}\n提示:${exports.ENTERPRISE_IMAGE_REGION_GUIDANCE}`;
|
|
156
|
+
if (carrier) {
|
|
157
|
+
ENHANCED_MESSAGES.set(carrier, enhanced);
|
|
158
|
+
}
|
|
159
|
+
return enhanced;
|
|
135
160
|
}
|
|
136
161
|
// 解析函数配置,换成请求参数(用于 CreateFunction)
|
|
137
162
|
function configToParams(options) {
|
|
@@ -267,6 +292,63 @@ function configToParams(options) {
|
|
|
267
292
|
const runtime = params.Runtime || 'Nodejs20.19';
|
|
268
293
|
return Object.assign({ Handler: func.handler || 'index.main', Runtime: 'Nodejs20.19', MemorySize: 256, InstallDependency: isNodeFunction(runtime) ? 'TRUE' : 'FALSE' }, params);
|
|
269
294
|
}
|
|
295
|
+
/**
|
|
296
|
+
* 判断错误是否**可重试**(AIP-194:只重试 `UNAVAILABLE` 类瞬时错误)。
|
|
297
|
+
*
|
|
298
|
+
* ⚠️ 默认**不可重试**(default-deny):权限不足、参数非法、资源冲突等**确定性失败**立即抛出 ——
|
|
299
|
+
* 对它们重试只会浪费时间,更会把「必然失败」伪装成「偶发失败」,掩盖真实原因。
|
|
300
|
+
*/
|
|
301
|
+
/** 安全取字符串字段(不依赖 `as any` 断言 —— 代码评审:类型安全) */
|
|
302
|
+
function pickStringField(value, key) {
|
|
303
|
+
if (typeof value !== 'object' || value === null) {
|
|
304
|
+
return '';
|
|
305
|
+
}
|
|
306
|
+
const field = value[key];
|
|
307
|
+
return typeof field === 'string' ? field : '';
|
|
308
|
+
}
|
|
309
|
+
/** 可重试的**精确错误码**(小写比较;AIP-194 的 UNAVAILABLE 家族 + 本层网络 errno) */
|
|
310
|
+
const RETRYABLE_ERROR_CODES = new Set([
|
|
311
|
+
'resourceunavailable',
|
|
312
|
+
'serviceunavailable',
|
|
313
|
+
'internalerror',
|
|
314
|
+
'requestlimitexceeded',
|
|
315
|
+
'etimedout',
|
|
316
|
+
'econnreset',
|
|
317
|
+
'econnrefused'
|
|
318
|
+
]);
|
|
319
|
+
/** 仅在 ① 未命中时用于**文案**回退的瞬时信号(不含 `not found` 等语义模糊项) */
|
|
320
|
+
const RETRYABLE_ERROR_TEXT_HINTS = [
|
|
321
|
+
'unavailable',
|
|
322
|
+
'internal error',
|
|
323
|
+
'timeout',
|
|
324
|
+
'connection reset',
|
|
325
|
+
'network error'
|
|
326
|
+
];
|
|
327
|
+
function isRetryableError(e) {
|
|
328
|
+
const code = pickStringField(e, 'code').toLowerCase();
|
|
329
|
+
const message = pickStringField(e, 'message').toLowerCase();
|
|
330
|
+
// ① 精确码优先(含 `ResourceUnavailable.xxx` 这类带子码的形态)
|
|
331
|
+
if (RETRYABLE_ERROR_CODES.has(code) || RETRYABLE_ERROR_CODES.has(code.split('.').pop() || '')) {
|
|
332
|
+
return true;
|
|
333
|
+
}
|
|
334
|
+
// ② 退一步只看瞬时信号文案
|
|
335
|
+
return RETRYABLE_ERROR_TEXT_HINTS.some(hint => message.includes(hint));
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* 判断错误是否为**创建/更新期的「资源尚不可见」**(NOT_FOUND 家族)。
|
|
339
|
+
*
|
|
340
|
+
* 场景:`CreateFunction` 已提交成功,但 `GetFunction` 短时间内仍返回 NOT_FOUND。
|
|
341
|
+
* 这类错误应**容忍并继续轮询**,而不是把正常的「创建中」误报为失败。
|
|
342
|
+
*
|
|
343
|
+
* ⚠️ 与 `isRetryableError` 分工不同:NOT_FOUND 的**竞态容忍**只在这里(轮询场景),
|
|
344
|
+
* 不进「可重试」白名单 —— 避免把「资源 ID 写错」这类确定性失败也当成可重试。
|
|
345
|
+
*/
|
|
346
|
+
function isNotYetVisibleError(e) {
|
|
347
|
+
const code = pickStringField(e, 'code').toLowerCase();
|
|
348
|
+
const message = pickStringField(e, 'message').toLowerCase();
|
|
349
|
+
const haystack = `${code} ${message}`;
|
|
350
|
+
return haystack.includes('notfound') || haystack.includes('not_found') || haystack.includes('not found');
|
|
351
|
+
}
|
|
270
352
|
class FunctionService {
|
|
271
353
|
constructor(environment) {
|
|
272
354
|
this.environment = environment;
|
|
@@ -307,7 +389,7 @@ class FunctionService {
|
|
|
307
389
|
await packer.build();
|
|
308
390
|
base64 = await packer.getBase64Code();
|
|
309
391
|
if (!base64) {
|
|
310
|
-
|
|
392
|
+
(0, error_1.throwNotFound)('函数不存在!');
|
|
311
393
|
}
|
|
312
394
|
params.AddFiles = base64;
|
|
313
395
|
}
|
|
@@ -357,7 +439,7 @@ class FunctionService {
|
|
|
357
439
|
params.Code = customCode;
|
|
358
440
|
}
|
|
359
441
|
if (!((_c = (_b = params.Code) === null || _b === void 0 ? void 0 : _b.ImageConfig) === null || _c === void 0 ? void 0 : _c.ImageUri)) {
|
|
360
|
-
|
|
442
|
+
(0, error_1.throwInvalidParam)('镜像部署需要配置 imageConfig.imageUri');
|
|
361
443
|
}
|
|
362
444
|
// 镜像函数不需要 Handler / InstallDependency
|
|
363
445
|
delete params.Handler;
|
|
@@ -544,7 +626,7 @@ class FunctionService {
|
|
|
544
626
|
currentOffset += pageSize;
|
|
545
627
|
}
|
|
546
628
|
catch (error) {
|
|
547
|
-
|
|
629
|
+
(0, error_1.throwApiError)(`获取函数列表失败: ${error.message}`);
|
|
548
630
|
}
|
|
549
631
|
}
|
|
550
632
|
// 格式化数据
|
|
@@ -601,7 +683,7 @@ class FunctionService {
|
|
|
601
683
|
(0, utils_1.successLog)(`[${name}] 函数删除成功!`);
|
|
602
684
|
}
|
|
603
685
|
catch (e) {
|
|
604
|
-
|
|
686
|
+
(0, error_1.throwApiError)(e.message);
|
|
605
687
|
}
|
|
606
688
|
})());
|
|
607
689
|
await Promise.all(promises);
|
|
@@ -664,7 +746,7 @@ class FunctionService {
|
|
|
664
746
|
data.push(info);
|
|
665
747
|
}
|
|
666
748
|
catch (e) {
|
|
667
|
-
|
|
749
|
+
(0, error_1.throwApiError)(`${name} 获取信息失败:${e.message}`);
|
|
668
750
|
}
|
|
669
751
|
})());
|
|
670
752
|
await Promise.all(promises);
|
|
@@ -686,7 +768,7 @@ class FunctionService {
|
|
|
686
768
|
* @returns {Promise<IFunctionLogRes>}
|
|
687
769
|
*/
|
|
688
770
|
async getFunctionLogs(_options) {
|
|
689
|
-
|
|
771
|
+
(0, error_1.throwApiError)('getFunctionLogs 已废弃:底层 GetFunctionLogs 接口已下线,请使用 env.getLogService().searchClsLog() 查询云函数日志');
|
|
690
772
|
}
|
|
691
773
|
/**
|
|
692
774
|
* 获取函数日志 ID 列表
|
|
@@ -703,7 +785,7 @@ class FunctionService {
|
|
|
703
785
|
* @returns {Promise<IFunctionLogResV2>}
|
|
704
786
|
*/
|
|
705
787
|
async getFunctionLogsV2(_options) {
|
|
706
|
-
|
|
788
|
+
(0, error_1.throwApiError)('getFunctionLogsV2 已废弃:底层 GetFunctionLogs 接口已下线,请使用 env.getLogService().searchClsLog() 查询云函数日志');
|
|
707
789
|
}
|
|
708
790
|
/**
|
|
709
791
|
* 根据函数日志 ID 查询日志详情
|
|
@@ -716,7 +798,7 @@ class FunctionService {
|
|
|
716
798
|
* @returns {Promise<IFunctionLogDetailRes>}
|
|
717
799
|
*/
|
|
718
800
|
async getFunctionLogDetail(_options) {
|
|
719
|
-
|
|
801
|
+
(0, error_1.throwApiError)('getFunctionLogDetail 已废弃:底层 GetFunctionLogDetail 接口已下线,请使用 env.getLogService().searchClsLog() 查询云函数日志');
|
|
720
802
|
}
|
|
721
803
|
/**
|
|
722
804
|
* 获取函数的完整调用日志
|
|
@@ -726,7 +808,7 @@ class FunctionService {
|
|
|
726
808
|
* @returns {Promise<IFunctionLogDetailRes[]>} 返回包含完整日志详情的数组
|
|
727
809
|
*/
|
|
728
810
|
async getCompleteFunctionLogs(_options) {
|
|
729
|
-
|
|
811
|
+
(0, error_1.throwApiError)('getCompleteFunctionLogs 已废弃:底层 GetFunctionLogs/GetFunctionLogDetail 接口已下线,请使用 env.getLogService().searchClsLog() 查询云函数日志');
|
|
730
812
|
}
|
|
731
813
|
/**
|
|
732
814
|
* 更新云函数配置
|
|
@@ -885,7 +967,7 @@ class FunctionService {
|
|
|
885
967
|
}
|
|
886
968
|
};
|
|
887
969
|
if (!params.Code.ImageConfig.ImageUri) {
|
|
888
|
-
|
|
970
|
+
(0, error_1.throwInvalidParam)('镜像部署需要配置 imageConfig.imageUri');
|
|
889
971
|
}
|
|
890
972
|
try {
|
|
891
973
|
// 等待函数状态正常
|
|
@@ -1023,7 +1105,7 @@ class FunctionService {
|
|
|
1023
1105
|
return Object.assign({ RequestId }, Result);
|
|
1024
1106
|
}
|
|
1025
1107
|
catch (e) {
|
|
1026
|
-
throw new error_1.CloudBaseError(`[${name}] 调用失败:${e.message}`, {
|
|
1108
|
+
throw new error_1.CloudBaseError(`[${name}] 调用失败:${e.message}`, { code: 'API_ERROR', source: 'manager-node',
|
|
1027
1109
|
requestId: e.requestId
|
|
1028
1110
|
});
|
|
1029
1111
|
}
|
|
@@ -1045,7 +1127,7 @@ class FunctionService {
|
|
|
1045
1127
|
return result;
|
|
1046
1128
|
}
|
|
1047
1129
|
catch (e) {
|
|
1048
|
-
|
|
1130
|
+
(0, error_1.throwApiError)(`${func.name} 函数调用失败:${e.message}`);
|
|
1049
1131
|
}
|
|
1050
1132
|
})());
|
|
1051
1133
|
return Promise.all(promises);
|
|
@@ -1062,7 +1144,7 @@ class FunctionService {
|
|
|
1062
1144
|
async copyFunction(name, newFunctionName, targetEnvId, force = false) {
|
|
1063
1145
|
const { namespace } = this.getFunctionConfig();
|
|
1064
1146
|
if (!namespace || !name || !newFunctionName) {
|
|
1065
|
-
|
|
1147
|
+
(0, error_1.throwInvalidParam)('参数缺失');
|
|
1066
1148
|
}
|
|
1067
1149
|
return this.scfService.request('CopyFunction', {
|
|
1068
1150
|
FunctionName: name,
|
|
@@ -1084,7 +1166,7 @@ class FunctionService {
|
|
|
1084
1166
|
const { namespace } = this.getFunctionConfig();
|
|
1085
1167
|
const parsedTriggers = triggers.map(item => {
|
|
1086
1168
|
if (item.type !== 'timer') {
|
|
1087
|
-
|
|
1169
|
+
(0, error_1.throwInvalidParam)(`不支持的触发器类型 [${item.type}],目前仅支持定时触发器(timer)!`);
|
|
1088
1170
|
}
|
|
1089
1171
|
return {
|
|
1090
1172
|
TriggerName: item.name,
|
|
@@ -1117,7 +1199,7 @@ class FunctionService {
|
|
|
1117
1199
|
(0, utils_1.successLog)(`[${func.name}] 创建云函数触发器成功!`);
|
|
1118
1200
|
}
|
|
1119
1201
|
catch (e) {
|
|
1120
|
-
|
|
1202
|
+
(0, error_1.throwApiError)(e.message);
|
|
1121
1203
|
}
|
|
1122
1204
|
})());
|
|
1123
1205
|
await Promise.all(promises);
|
|
@@ -1139,7 +1221,7 @@ class FunctionService {
|
|
|
1139
1221
|
});
|
|
1140
1222
|
}
|
|
1141
1223
|
catch (e) {
|
|
1142
|
-
|
|
1224
|
+
(0, error_1.throwApiError)(`[${name}] 删除触发器失败:${e.message}`);
|
|
1143
1225
|
}
|
|
1144
1226
|
}
|
|
1145
1227
|
async batchDeleteTriggers(options) {
|
|
@@ -1150,7 +1232,7 @@ class FunctionService {
|
|
|
1150
1232
|
(0, utils_1.successLog)(`[${func.name}] 删除云函数触发器成功!`);
|
|
1151
1233
|
}
|
|
1152
1234
|
catch (e) {
|
|
1153
|
-
|
|
1235
|
+
(0, error_1.throwApiError)(e.message);
|
|
1154
1236
|
}
|
|
1155
1237
|
})());
|
|
1156
1238
|
await Promise.all(promises);
|
|
@@ -1202,7 +1284,7 @@ class FunctionService {
|
|
|
1202
1284
|
return { Url, RequestId, CodeSha256 };
|
|
1203
1285
|
}
|
|
1204
1286
|
catch (e) {
|
|
1205
|
-
throw new error_1.CloudBaseError(`[${functionName}] 获取函数代码下载链接失败:${e.message}`, {
|
|
1287
|
+
throw new error_1.CloudBaseError(`[${functionName}] 获取函数代码下载链接失败:${e.message}`, { code: 'API_ERROR', source: 'manager-node',
|
|
1206
1288
|
requestId: e.requestId
|
|
1207
1289
|
});
|
|
1208
1290
|
}
|
|
@@ -1231,7 +1313,7 @@ class FunctionService {
|
|
|
1231
1313
|
Layers = Layers.map(item => lodash_1.default.pick(item, ['LayerName', 'LayerVersion']));
|
|
1232
1314
|
const index = Layers.findIndex(item => item.LayerName === layerName && item.LayerVersion === layerVersion);
|
|
1233
1315
|
if (index === -1) {
|
|
1234
|
-
|
|
1316
|
+
(0, error_1.throwNotFound)('层不存在');
|
|
1235
1317
|
}
|
|
1236
1318
|
// 删除指定的层
|
|
1237
1319
|
Layers.splice(index, 1);
|
|
@@ -1268,7 +1350,7 @@ class FunctionService {
|
|
|
1268
1350
|
const url = res === null || res === void 0 ? void 0 : res.Location;
|
|
1269
1351
|
const zipPath = path_1.default.join(destPath, `${name}-${version}.zip`);
|
|
1270
1352
|
if ((0, utils_1.checkFullAccess)(zipPath)) {
|
|
1271
|
-
|
|
1353
|
+
(0, error_1.throwInvalidParam)(`文件已存在:${zipPath}`);
|
|
1272
1354
|
}
|
|
1273
1355
|
// 下载文件
|
|
1274
1356
|
return (0, utils_1.downloadAndExtractRemoteZip)(url, destPath);
|
|
@@ -1301,7 +1383,7 @@ class FunctionService {
|
|
|
1301
1383
|
else {
|
|
1302
1384
|
const fileType = path_1.default.extname(contentPath);
|
|
1303
1385
|
if (fileType !== '.zip') {
|
|
1304
|
-
|
|
1386
|
+
(0, error_1.throwInvalidParam)('文件类型不正确,目前只支持 ZIP 文件!');
|
|
1305
1387
|
}
|
|
1306
1388
|
const fileBuffer = await fs_1.default.promises.readFile(contentPath);
|
|
1307
1389
|
base64 = fileBuffer.toString('base64');
|
|
@@ -1370,7 +1452,9 @@ class FunctionService {
|
|
|
1370
1452
|
timer = setTimeout(() => {
|
|
1371
1453
|
clearInterval(ticker);
|
|
1372
1454
|
if (!resolved) {
|
|
1373
|
-
reject(
|
|
1455
|
+
// ⚠️ 不能写 reject(throwNetError(...)):该 helper 必抛 ⇒ 异常抛在定时器回调里、
|
|
1456
|
+
// Promise 永不 settle ⇒ 调用方挂死。**值位置**必须构造错误对象。
|
|
1457
|
+
reject(new error_1.CloudBaseError('函数状态异常,检查超时', { type: error_types_1.ERROR_TYPES.netError }));
|
|
1374
1458
|
}
|
|
1375
1459
|
}, 300000);
|
|
1376
1460
|
ticker = setInterval(async () => {
|
|
@@ -1384,7 +1468,7 @@ class FunctionService {
|
|
|
1384
1468
|
const errorDetails = (StatusReasons === null || StatusReasons === void 0 ? void 0 : StatusReasons.map(item => `[${item.ErrorCode}] ${item.ErrorMessage}`).join('\n')) || '';
|
|
1385
1469
|
const errorMsg = `云函数创建失败${StatusDesc ? `\n状态描述: ${StatusDesc}` : ''}${errorDetails ? `\n失败信息: ${errorDetails}` : ''}`;
|
|
1386
1470
|
// 注意:这里不传递 RequestId,因为这是 GetFunction 的 RequestId,不是导致失败的 CreateFunction/UpdateFunctionCode 的 RequestId
|
|
1387
|
-
|
|
1471
|
+
(0, error_1.throwApiError)(errorMsg);
|
|
1388
1472
|
}
|
|
1389
1473
|
// 函数状态正常
|
|
1390
1474
|
clearInterval(ticker);
|
|
@@ -1392,6 +1476,10 @@ class FunctionService {
|
|
|
1392
1476
|
resolve();
|
|
1393
1477
|
}
|
|
1394
1478
|
catch (e) {
|
|
1479
|
+
// 容忍创建/更新期的「资源尚不可见」:`GetFunction` 在函数刚提交后会短暂返回 NOT_FOUND,
|
|
1480
|
+
// 此时继续轮询即可(超时仍由 5 分钟定时器兜底)—— 否则会把正常的「创建中」误报为失败。
|
|
1481
|
+
if (isNotYetVisibleError(e))
|
|
1482
|
+
return;
|
|
1395
1483
|
clearInterval(ticker);
|
|
1396
1484
|
clearTimeout(timer);
|
|
1397
1485
|
reject(e);
|
|
@@ -1523,7 +1611,7 @@ class FunctionService {
|
|
|
1523
1611
|
const { func, functionPath, functionRootPath } = options;
|
|
1524
1612
|
const { env, appId } = this.getFunctionConfig();
|
|
1525
1613
|
if (!appId) {
|
|
1526
|
-
|
|
1614
|
+
(0, error_1.throwApiError)('无法获取 AppId,请确认环境初始化完成或已开通存储服务');
|
|
1527
1615
|
}
|
|
1528
1616
|
const objectPath = `${appId}/${env}/${func.name}.zip`;
|
|
1529
1617
|
// 1. 生成存放函数包的临时 Cos 目录
|
|
@@ -1565,7 +1653,8 @@ class FunctionService {
|
|
|
1565
1653
|
// 添加超时保护
|
|
1566
1654
|
const timeout = setTimeout(() => {
|
|
1567
1655
|
packer.clean();
|
|
1568
|
-
|
|
1656
|
+
// 同上:值位置不能调用必抛 helper
|
|
1657
|
+
reject(new error_1.CloudBaseError(`[${func.name}] COS 上传超时(60秒)`, { type: error_types_1.ERROR_TYPES.netError }));
|
|
1569
1658
|
}, 60000);
|
|
1570
1659
|
cos.sliceUploadFile(uploadParams, async (err, data) => {
|
|
1571
1660
|
clearTimeout(timeout);
|
|
@@ -1640,7 +1729,7 @@ class FunctionService {
|
|
|
1640
1729
|
catch (e) {
|
|
1641
1730
|
// 忽略响应体解析错误
|
|
1642
1731
|
}
|
|
1643
|
-
|
|
1732
|
+
(0, error_1.throwApiError)(`上传失败: ${response.status} ${response.statusText}${errorDetail}`);
|
|
1644
1733
|
}
|
|
1645
1734
|
// 清理临时文件
|
|
1646
1735
|
await packer.clean();
|
|
@@ -1655,7 +1744,7 @@ class FunctionService {
|
|
|
1655
1744
|
const MAX_ZIP_SIZE = 1.5 * 1024 * 1024; // 1.5MB
|
|
1656
1745
|
const MAX_BASE64_LENGTH = Math.floor((MAX_ZIP_SIZE * 4) / 3); // ≈ 2097152
|
|
1657
1746
|
if ((base64Code === null || base64Code === void 0 ? void 0 : base64Code.length) > MAX_BASE64_LENGTH) {
|
|
1658
|
-
|
|
1747
|
+
(0, error_1.throwInvalidParam)('ZipFile 上传不能大于 1.5MB,请使用 COS 上传方式');
|
|
1659
1748
|
}
|
|
1660
1749
|
if (base64Code === null || base64Code === void 0 ? void 0 : base64Code.length) {
|
|
1661
1750
|
return {
|
|
@@ -1680,11 +1769,11 @@ class FunctionService {
|
|
|
1680
1769
|
// 判断是否超过 ZipFile 上传大小限制(1.5MB)
|
|
1681
1770
|
const reachMax = await packer.isReachMaxSize();
|
|
1682
1771
|
if (reachMax) {
|
|
1683
|
-
|
|
1772
|
+
(0, error_1.throwInvalidParam)('ZipFile 上传不能大于 1.5MB,请使用 COS 上传方式(deployMode: "cos")');
|
|
1684
1773
|
}
|
|
1685
1774
|
const base64 = await packer.getBase64Code();
|
|
1686
1775
|
if (!(base64 === null || base64 === void 0 ? void 0 : base64.length)) {
|
|
1687
|
-
|
|
1776
|
+
(0, error_1.throwInvalidParam)('文件不能为空');
|
|
1688
1777
|
}
|
|
1689
1778
|
console.log(`[${func.name}] 部署方式: ZIP base64 上传`);
|
|
1690
1779
|
return {
|
|
@@ -1736,20 +1825,25 @@ class FunctionService {
|
|
|
1736
1825
|
ObjectPath: `${appId}/${env}/${name}.zip"`
|
|
1737
1826
|
});
|
|
1738
1827
|
}
|
|
1828
|
+
/**
|
|
1829
|
+
* 创建触发器(带重试)。
|
|
1830
|
+
*
|
|
1831
|
+
* ⚠️ AIP-194:**只重试可重试错误**。旧实现是 catch-all 盲重试 —— 权限不足 / 参数非法这类
|
|
1832
|
+
* **必然失败**的错误也会被重试 3 次,既浪费时间,又把确定性失败伪装成偶发失败、掩盖真实原因。
|
|
1833
|
+
*/
|
|
1739
1834
|
async retryCreateTrigger(name, triggers, count = 0) {
|
|
1740
1835
|
try {
|
|
1741
1836
|
const res = await this.createFunctionTriggers(name, triggers);
|
|
1742
1837
|
return res;
|
|
1743
1838
|
}
|
|
1744
1839
|
catch (e) {
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
const res = await this.retryCreateTrigger(name, triggers, count + 1);
|
|
1748
|
-
return res;
|
|
1749
|
-
}
|
|
1750
|
-
else {
|
|
1840
|
+
// 不可重试(权限 / 参数 / 资源冲突等)或已达上限 → 立即抛出真实错误
|
|
1841
|
+
if (!isRetryableError(e) || count >= 3) {
|
|
1751
1842
|
throw e;
|
|
1752
1843
|
}
|
|
1844
|
+
console.warn(`[${name}] 创建触发器遇到可重试错误(第 ${count + 1}/3 次),500ms 后重试:${pickStringField(e, 'message') || String(e)}`);
|
|
1845
|
+
await (0, utils_1.sleep)(500);
|
|
1846
|
+
return this.retryCreateTrigger(name, triggers, count + 1);
|
|
1753
1847
|
}
|
|
1754
1848
|
}
|
|
1755
1849
|
/**
|
package/lib/function/packer.js
CHANGED
|
@@ -100,7 +100,7 @@ class FunctionPacker {
|
|
|
100
100
|
const jarExist = (0, utils_1.checkFullAccess)(`${filePath}.jar`);
|
|
101
101
|
const zipExist = (0, utils_1.checkFullAccess)(`${filePath}.zip`);
|
|
102
102
|
if (!jarExist && !zipExist) {
|
|
103
|
-
|
|
103
|
+
(0, error_1.throwNotFound)('未找到部署函数的 Jar 或者 ZIP 格式文件!');
|
|
104
104
|
}
|
|
105
105
|
this.zipFilePath = jarExist ? `${filePath}.jar` : `${filePath}.zip`;
|
|
106
106
|
}
|
package/lib/hosting/index.js
CHANGED
|
@@ -54,7 +54,7 @@ class HostingService {
|
|
|
54
54
|
const website = hostings[0];
|
|
55
55
|
// offline 状态的服务可重新开启
|
|
56
56
|
if (website.Status !== 'offline') {
|
|
57
|
-
|
|
57
|
+
(0, error_1.throwApiError)('静态网站服务已开启,请勿重复操作!');
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
const res = await this.tcbService.request('CreateStaticStore', {
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
const error_1 = require("./error");
|
|
2
3
|
const context_1 = require("./context");
|
|
3
4
|
const environmentManager_1 = require("./environmentManager");
|
|
4
5
|
const docs_1 = require("./docs");
|
|
@@ -25,7 +26,7 @@ class CloudBase {
|
|
|
25
26
|
let { secretId, secretKey, token, envId, proxy, region, envType, useInternalEndpoint, internalEndpointRegion, requestFn } = config;
|
|
26
27
|
// config 中传入的 secretId secretkey 必须同时存在(有 requestFn 时可以不传)
|
|
27
28
|
if (!requestFn && ((secretId && !secretKey) || (!secretId && secretKey))) {
|
|
28
|
-
|
|
29
|
+
(0, error_1.throwInvalidParam)('secretId and secretKey must be a pair');
|
|
29
30
|
}
|
|
30
31
|
this.cloudBaseConfig = {
|
|
31
32
|
secretId: secretId ? secretId.trim() : secretId,
|
|
@@ -49,6 +49,7 @@ exports.aesEncryptWithIv = aesEncryptWithIv;
|
|
|
49
49
|
* 使用 Node 内置 crypto 复刻,无需引入额外依赖。
|
|
50
50
|
*/
|
|
51
51
|
const crypto = __importStar(require("crypto"));
|
|
52
|
+
const error_1 = require("../error");
|
|
52
53
|
const ALGORITHM = 'aes-256-cbc';
|
|
53
54
|
const KEY_BYTE_LENGTH = 32;
|
|
54
55
|
const IV_BYTE_LENGTH = 16;
|
|
@@ -56,7 +57,7 @@ const IV_BYTE_LENGTH = 16;
|
|
|
56
57
|
const IV_BASE64_LENGTH = 24;
|
|
57
58
|
function assertKey(key) {
|
|
58
59
|
if (Buffer.byteLength(key, 'utf8') !== KEY_BYTE_LENGTH) {
|
|
59
|
-
|
|
60
|
+
(0, error_1.throwInvalidParam)('AES key 必须是 32 字节长度');
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
63
|
/**
|
|
@@ -65,7 +66,7 @@ function assertKey(key) {
|
|
|
65
66
|
function aesEncryptWithIv(plaintext, key, iv) {
|
|
66
67
|
assertKey(key);
|
|
67
68
|
if (iv.length !== IV_BYTE_LENGTH) {
|
|
68
|
-
|
|
69
|
+
(0, error_1.throwInvalidParam)('IV 必须是 16 字节长度');
|
|
69
70
|
}
|
|
70
71
|
const cipher = crypto.createCipheriv(ALGORITHM, Buffer.from(key, 'utf8'), iv);
|
|
71
72
|
const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
|
|
@@ -89,7 +90,7 @@ class AESCryptoImpl {
|
|
|
89
90
|
const ciphertextBase64 = encryptedData.slice(IV_BASE64_LENGTH);
|
|
90
91
|
const iv = Buffer.from(ivBase64, 'base64');
|
|
91
92
|
if (iv.length !== IV_BYTE_LENGTH) {
|
|
92
|
-
|
|
93
|
+
(0, error_1.throwInvalidParam)('密文格式错误:IV 长度非法');
|
|
93
94
|
}
|
|
94
95
|
const decipher = crypto.createDecipheriv(ALGORITHM, Buffer.from(key, 'utf8'), iv);
|
|
95
96
|
const decrypted = Buffer.concat([
|
package/lib/integration/index.js
CHANGED
|
@@ -170,7 +170,7 @@ class IntegrationService {
|
|
|
170
170
|
validateRequiredParams(params, fields) {
|
|
171
171
|
for (const field of fields) {
|
|
172
172
|
if (!params[field]) {
|
|
173
|
-
|
|
173
|
+
(0, error_1.throwInvalidParam)(`${field} 是必填参数`);
|
|
174
174
|
}
|
|
175
175
|
}
|
|
176
176
|
}
|
|
@@ -60,24 +60,24 @@ async function getAesKey(params = {}) {
|
|
|
60
60
|
});
|
|
61
61
|
}
|
|
62
62
|
catch (e) {
|
|
63
|
-
|
|
63
|
+
(0, error_1.throwApiError)('获取集成加解密密钥失败:七彩石配置拉取请求异常');
|
|
64
64
|
}
|
|
65
65
|
const data = (_a = resJson === null || resJson === void 0 ? void 0 : resJson.Response) === null || _a === void 0 ? void 0 : _a.Data;
|
|
66
66
|
if (!data || data.ErrorCode !== '0') {
|
|
67
|
-
|
|
67
|
+
(0, error_1.throwApiError)(`获取集成加解密密钥失败:七彩石返回异常(ErrorCode=${data === null || data === void 0 ? void 0 : data.ErrorCode})`);
|
|
68
68
|
}
|
|
69
69
|
let config;
|
|
70
70
|
try {
|
|
71
71
|
config = JSON.parse(data.ConfigValue)[0];
|
|
72
72
|
}
|
|
73
73
|
catch (e) {
|
|
74
|
-
|
|
74
|
+
(0, error_1.throwApiError)('获取集成加解密密钥失败:七彩石配置内容解析失败');
|
|
75
75
|
}
|
|
76
76
|
const kvs = ((_b = config === null || config === void 0 ? void 0 : config.kvs) === null || _b === void 0 ? void 0 : _b.kvs) || [];
|
|
77
77
|
const target = kvs.find(kv => kv.key === RAINBOW_CONFIG.CRYPTO_KEY_FIELD);
|
|
78
78
|
const key = target === null || target === void 0 ? void 0 : target.value;
|
|
79
79
|
if (!key) {
|
|
80
|
-
|
|
80
|
+
(0, error_1.throwApiError)(`获取集成加解密密钥失败:七彩石配置中缺少 ${RAINBOW_CONFIG.CRYPTO_KEY_FIELD} 字段`);
|
|
81
81
|
}
|
|
82
82
|
keyCache[group] = key;
|
|
83
83
|
return key;
|
package/lib/monitor/index.js
CHANGED
|
@@ -37,7 +37,7 @@ function validatePeriod(startStr, endStr, period) {
|
|
|
37
37
|
const rangeMs = endMs - startMs;
|
|
38
38
|
const rule = PERIOD_RULES.find(r => rangeMs <= r.maxRangeMs);
|
|
39
39
|
if (rule && !rule.allowed.includes(period)) {
|
|
40
|
-
|
|
40
|
+
(0, error_1.throwInvalidParam)(`Period=${period} 不适用于当前时间范围。` +
|
|
41
41
|
`时间范围 ${startStr} ~ ${endStr}(约 ${(rangeMs / ONE_DAY_MS).toFixed(1)} 天)` +
|
|
42
42
|
`仅支持 Period: [${rule.allowed.join(', ')}]`);
|
|
43
43
|
}
|
package/lib/mysql/index.js
CHANGED
|
@@ -22,6 +22,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
22
22
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
23
|
exports.MysqlService = void 0;
|
|
24
24
|
const utils_1 = require("../utils");
|
|
25
|
+
const error_1 = require("../error");
|
|
25
26
|
__exportStar(require("./types/index"), exports);
|
|
26
27
|
class MysqlService {
|
|
27
28
|
constructor(environment) {
|
|
@@ -416,7 +417,7 @@ class MysqlService {
|
|
|
416
417
|
const res = await this.describeClusterInstanceGroups();
|
|
417
418
|
const groups = res.InstanceGroupInfoList;
|
|
418
419
|
if (!groups || groups.length === 0) {
|
|
419
|
-
|
|
420
|
+
(0, error_1.throwApiError)('当前集群没有实例组,无法执行外网操作');
|
|
420
421
|
}
|
|
421
422
|
this._instanceGroupId = groups[0].InstanceGroupId;
|
|
422
423
|
}
|