@cloudbase/manager-node 5.7.1-beta.1 → 5.8.0-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/lib/cloudApp/index.js +12 -3
- package/lib/deploy/DatabaseDeployer.js +238 -0
- package/lib/deploy/DeployOrchestrator.js +645 -0
- package/lib/deploy/FunctionDeployer.js +611 -0
- package/lib/deploy/GatewayDeployer.js +612 -0
- package/lib/deploy/StateStore.js +327 -0
- package/lib/deploy/StaticDeployer.js +236 -0
- package/lib/deploy/domain.js +41 -0
- package/lib/deploy/framework.js +230 -0
- package/lib/deploy/function/artifact.js +207 -0
- package/lib/deploy/function/builders/cloud.js +654 -0
- package/lib/deploy/function/cam-preflight.js +157 -0
- package/lib/deploy/function/cloud-build-service.js +191 -0
- package/lib/deploy/function/config-guard.js +595 -0
- package/lib/deploy/function/docker-preflight.js +87 -0
- package/lib/deploy/function/image-preflight.js +164 -0
- package/lib/deploy/function/local-builder.js +129 -0
- package/lib/deploy/function/planner.js +278 -0
- package/lib/deploy/function/preflight.js +329 -0
- package/lib/deploy/types.js +103 -0
- package/lib/env/index.js +0 -27
- package/lib/environment.js +11 -0
- package/lib/function/index.js +1 -1
- package/lib/hosting/index.js +4 -1
- package/lib/index.js +31 -0
- package/lib/projectValidator/index.js +452 -0
- package/lib/projectValidator/types.js +2 -0
- package/lib/storage/index.js +6 -7
- package/lib/utils/index.js +62 -5
- package/package.json +2 -1
- package/types/cloudApp/index.d.ts +4 -0
- package/types/cloudApp/types.d.ts +87 -6
- package/types/deploy/DatabaseDeployer.d.ts +70 -0
- package/types/deploy/DeployOrchestrator.d.ts +170 -0
- package/types/deploy/FunctionDeployer.d.ts +159 -0
- package/types/deploy/GatewayDeployer.d.ts +194 -0
- package/types/deploy/StateStore.d.ts +170 -0
- package/types/deploy/StaticDeployer.d.ts +97 -0
- package/types/deploy/domain.d.ts +17 -0
- package/types/deploy/framework.d.ts +63 -0
- package/types/deploy/function/artifact.d.ts +40 -0
- package/types/deploy/function/builders/cloud.d.ts +110 -0
- package/types/deploy/function/cam-preflight.d.ts +51 -0
- package/types/deploy/function/cloud-build-service.d.ts +10 -0
- package/types/deploy/function/config-guard.d.ts +41 -0
- package/types/deploy/function/docker-preflight.d.ts +17 -0
- package/types/deploy/function/image-preflight.d.ts +21 -0
- package/types/deploy/function/local-builder.d.ts +26 -0
- package/types/deploy/function/planner.d.ts +15 -0
- package/types/deploy/function/preflight.d.ts +9 -0
- package/types/deploy/types.d.ts +429 -0
- package/types/env/index.d.ts +1 -17
- package/types/env/type.d.ts +6 -260
- package/types/environment.d.ts +7 -0
- package/types/function/types.d.ts +15 -0
- package/types/hosting/index.d.ts +6 -0
- package/types/index.d.ts +18 -0
- package/types/interfaces/function.interface.d.ts +1 -1
- package/types/projectValidator/index.d.ts +38 -0
- package/types/projectValidator/types.d.ts +26 -0
- package/types/storage/index.d.ts +6 -0
- package/types/utils/index.d.ts +13 -0
|
@@ -0,0 +1,329 @@
|
|
|
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.runLocalPreflight = runLocalPreflight;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const packer_1 = require("../../function/packer");
|
|
10
|
+
const types_1 = require("../types");
|
|
11
|
+
/**
|
|
12
|
+
* 部署前环境检查(Preflight)
|
|
13
|
+
*
|
|
14
|
+
* 与 config-guard 的区别:config-guard 是纯函数、无 IO 的配置契约校验;
|
|
15
|
+
* 本文件执行有 IO 的本地检查,覆盖 HTTP 代码包条件以及 cloud 镜像构建上下文。
|
|
16
|
+
*
|
|
17
|
+
* 本阶段只读文件系统,不执行命令、不访问网络,不检查 Docker、TCR 或 CAM。
|
|
18
|
+
*/
|
|
19
|
+
/** HTTP 代码包函数要求的启动文件 */
|
|
20
|
+
const SCF_BOOTSTRAP = 'scf_bootstrap';
|
|
21
|
+
/** 可执行位掩码(owner/group/other 任一可执行即可) */
|
|
22
|
+
const EXECUTABLE_MASK = 0o111;
|
|
23
|
+
/** 解压前代码包大小阈值,复用底层上传阈值,超过给出告警 */
|
|
24
|
+
const PACKAGE_SIZE_WARN_THRESHOLD = packer_1.BIG_FILE_SIZE;
|
|
25
|
+
/** 遍历目录项数量上限,超过则停止统计,避免超大目录阻塞检查 */
|
|
26
|
+
const MAX_SCAN_ENTRIES = 20000;
|
|
27
|
+
/** 遍历目录深度上限,避免异常深层结构阻塞检查 */
|
|
28
|
+
const MAX_SCAN_DEPTH = 64;
|
|
29
|
+
function fail(id, summary, options = {}) {
|
|
30
|
+
return { id, status: 'fail', summary, remediation: options.remediation, detail: options.detail };
|
|
31
|
+
}
|
|
32
|
+
function warn(id, summary, remediation) {
|
|
33
|
+
return { id, status: 'warn', summary, remediation };
|
|
34
|
+
}
|
|
35
|
+
/** 构造本地 Preflight 报告 */
|
|
36
|
+
function buildReport(strategy, checks) {
|
|
37
|
+
return {
|
|
38
|
+
scope: 'local',
|
|
39
|
+
strategy,
|
|
40
|
+
ready: !checks.some(item => item.status === 'fail'),
|
|
41
|
+
checks
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 解析 HTTP zip 部署的函数目录
|
|
46
|
+
*
|
|
47
|
+
* functionPath 直接指向函数目录;functionRootPath 是父目录,需拼接函数名;
|
|
48
|
+
* base64Code 已是内存中的 ZIP,无本地目录可检查
|
|
49
|
+
* @returns 函数目录绝对路径;base64Code 或无法定位时返回 null
|
|
50
|
+
*/
|
|
51
|
+
function resolveFunctionDir(config) {
|
|
52
|
+
if (typeof config.functionPath === 'string' && config.functionPath.trim()) {
|
|
53
|
+
return path_1.default.resolve(config.functionPath);
|
|
54
|
+
}
|
|
55
|
+
if (typeof config.functionRootPath === 'string' && config.functionRootPath.trim()) {
|
|
56
|
+
return path_1.default.resolve(config.functionRootPath, config.name);
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
/** 安全获取文件状态,不存在返回 null,其它 IO 错误抛出 */
|
|
61
|
+
function safeStat(target) {
|
|
62
|
+
try {
|
|
63
|
+
return fs_1.default.statSync(target);
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
if (error && error.code === 'ENOENT') {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/** 检查 cloud 构建上下文与 Dockerfile,确保任何云端操作前即可失败 */
|
|
73
|
+
function checkCloudBuildSource(config, checks) {
|
|
74
|
+
const cwd = path_1.default.resolve(config.build.cwd || config.functionPath || process.cwd());
|
|
75
|
+
const contextStat = safeStat(cwd);
|
|
76
|
+
if (contextStat === null) {
|
|
77
|
+
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_CONTEXT_INVALID, '云端镜像构建上下文目录不存在', {
|
|
78
|
+
remediation: '请确认 build.cwd 或函数目录指向真实存在的构建上下文',
|
|
79
|
+
detail: `期望路径:${cwd}`
|
|
80
|
+
}));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (!contextStat.isDirectory()) {
|
|
84
|
+
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.BUILD_CONTEXT_INVALID, '云端镜像构建上下文不是目录', {
|
|
85
|
+
remediation: 'build.cwd 需指向包含 Dockerfile 和函数源码的目录',
|
|
86
|
+
detail: `路径:${cwd}`
|
|
87
|
+
}));
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const dockerfile = config.build.dockerfile || types_1.DEFAULT_DOCKERFILE;
|
|
91
|
+
const dockerfilePath = path_1.default.resolve(cwd, dockerfile);
|
|
92
|
+
const dockerfileStat = safeStat(dockerfilePath);
|
|
93
|
+
if (dockerfileStat === null || !dockerfileStat.isFile()) {
|
|
94
|
+
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.CLOUD_BUILD_DOCKERFILE_MISSING, `云端镜像构建缺少有效的 Dockerfile:${dockerfilePath}`, {
|
|
95
|
+
remediation: '请在构建上下文中创建 Dockerfile,或通过 build.dockerfile 指定实际文件',
|
|
96
|
+
detail: `期望路径:${dockerfilePath}`
|
|
97
|
+
}));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/** 检查函数目录下的 scf_bootstrap 启动文件 */
|
|
101
|
+
function checkBootstrap(functionDir, checks) {
|
|
102
|
+
const bootstrapPath = path_1.default.join(functionDir, SCF_BOOTSTRAP);
|
|
103
|
+
const stat = safeStat(bootstrapPath);
|
|
104
|
+
if (stat === null) {
|
|
105
|
+
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.PREFLIGHT_BOOTSTRAP_MISSING, `HTTP 代码包缺少启动文件 ${SCF_BOOTSTRAP}`, {
|
|
106
|
+
remediation: `请在函数目录下提供可执行的 ${SCF_BOOTSTRAP},用于启动监听端口的 HTTP 服务`,
|
|
107
|
+
detail: `期望路径:${bootstrapPath}`
|
|
108
|
+
}));
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (!stat.isFile()) {
|
|
112
|
+
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.PREFLIGHT_BOOTSTRAP_NOT_FILE, `${SCF_BOOTSTRAP} 不是普通文件`, {
|
|
113
|
+
remediation: `请确保 ${SCF_BOOTSTRAP} 是一个文件而非目录或链接`,
|
|
114
|
+
detail: `路径:${bootstrapPath}`
|
|
115
|
+
}));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
// 仅 POSIX 有可执行位,Windows 无该概念,跳过可执行位判断
|
|
119
|
+
if (process.platform !== 'win32' && (stat.mode & EXECUTABLE_MASK) === 0) {
|
|
120
|
+
checks.push(warn(types_1.FUNCTION_DEPLOY_ERROR.PREFLIGHT_BOOTSTRAP_NOT_EXECUTABLE, `${SCF_BOOTSTRAP} 缺少可执行权限`, `建议执行 chmod +x ${SCF_BOOTSTRAP},否则运行环境可能无法启动服务`));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/** 判断 target 解析后是否逃逸出根目录 */
|
|
124
|
+
function isPathEscaped(root, target) {
|
|
125
|
+
const relative = path_1.default.relative(root, target);
|
|
126
|
+
// 空串表示与根相同;以 .. 开头或为绝对路径表示逃逸
|
|
127
|
+
return relative === '..' || relative.startsWith(`..${path_1.default.sep}`) || path_1.default.isAbsolute(relative);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* 遍历函数目录,统计总大小并检测逃逸软链接
|
|
131
|
+
*
|
|
132
|
+
* 使用 lstat 不跟随链接;对软链接单独解析目标,判断是否指向根目录之外。
|
|
133
|
+
* 设深度与数量上限,避免异常目录结构阻塞检查。
|
|
134
|
+
*/
|
|
135
|
+
function scanPackageContents(functionDir) {
|
|
136
|
+
const result = { totalSize: 0, escapedLinks: [], truncated: false };
|
|
137
|
+
let visited = 0;
|
|
138
|
+
// 根目录也解析为真实路径,避免 /var→/private/var 这类平台软链接导致内部链接被误判逃逸
|
|
139
|
+
let realRoot;
|
|
140
|
+
try {
|
|
141
|
+
realRoot = fs_1.default.realpathSync(functionDir);
|
|
142
|
+
}
|
|
143
|
+
catch (_a) {
|
|
144
|
+
realRoot = functionDir;
|
|
145
|
+
}
|
|
146
|
+
const walk = (dir, depth) => {
|
|
147
|
+
if (result.truncated || depth > MAX_SCAN_DEPTH) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const entries = fs_1.default.readdirSync(dir);
|
|
151
|
+
for (const entry of entries) {
|
|
152
|
+
if (visited >= MAX_SCAN_ENTRIES) {
|
|
153
|
+
result.truncated = true;
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
visited += 1;
|
|
157
|
+
const fullPath = path_1.default.join(dir, entry);
|
|
158
|
+
const stat = fs_1.default.lstatSync(fullPath);
|
|
159
|
+
if (stat.isSymbolicLink()) {
|
|
160
|
+
// 解析链接目标,判断是否逃逸出函数目录
|
|
161
|
+
let realTarget;
|
|
162
|
+
try {
|
|
163
|
+
realTarget = fs_1.default.realpathSync(fullPath);
|
|
164
|
+
}
|
|
165
|
+
catch (_a) {
|
|
166
|
+
// 断链(指向不存在的目标)视为逃逸风险,记录相对路径
|
|
167
|
+
result.escapedLinks.push(path_1.default.relative(functionDir, fullPath));
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (isPathEscaped(realRoot, realTarget)) {
|
|
171
|
+
result.escapedLinks.push(path_1.default.relative(functionDir, fullPath));
|
|
172
|
+
}
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (stat.isDirectory()) {
|
|
176
|
+
walk(fullPath, depth + 1);
|
|
177
|
+
if (result.truncated) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (stat.isFile()) {
|
|
183
|
+
result.totalSize += stat.size;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
walk(functionDir, 0);
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
190
|
+
/** 检查 Node.js 依赖是否随代码包携带 */
|
|
191
|
+
function checkNodeDependencyBundled(functionDir, checks) {
|
|
192
|
+
const pkgPath = path_1.default.join(functionDir, 'package.json');
|
|
193
|
+
const pkgStat = safeStat(pkgPath);
|
|
194
|
+
if (pkgStat === null || !pkgStat.isFile()) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
let hasDependencies = false;
|
|
198
|
+
try {
|
|
199
|
+
const pkg = JSON.parse(fs_1.default.readFileSync(pkgPath, 'utf8'));
|
|
200
|
+
const deps = pkg && typeof pkg === 'object' ? pkg.dependencies : undefined;
|
|
201
|
+
hasDependencies = !!deps && typeof deps === 'object' && Object.keys(deps).length > 0;
|
|
202
|
+
}
|
|
203
|
+
catch (_a) {
|
|
204
|
+
// package.json 无法解析属于用户代码问题,不在本检查职责内
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (!hasDependencies) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const nodeModulesStat = safeStat(path_1.default.join(functionDir, 'node_modules'));
|
|
211
|
+
if (nodeModulesStat === null || !nodeModulesStat.isDirectory()) {
|
|
212
|
+
checks.push(warn(types_1.FUNCTION_DEPLOY_ERROR.PREFLIGHT_DEPENDENCY_NOT_BUNDLED, 'package.json 声明了依赖但未发现 node_modules', '当前配置不会在云端安装依赖,请先安装依赖并随代码包上传'));
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
const PYTHON_DEPENDENCY_FILES = [
|
|
216
|
+
'requirements.txt',
|
|
217
|
+
'pyproject.toml',
|
|
218
|
+
'poetry.lock',
|
|
219
|
+
'Pipfile',
|
|
220
|
+
'Pipfile.lock',
|
|
221
|
+
'uv.lock'
|
|
222
|
+
];
|
|
223
|
+
/**
|
|
224
|
+
* 检查 Python 代码包是否包含依赖清单
|
|
225
|
+
*
|
|
226
|
+
* pip install -t . 不会留下统一可靠的 vendor 标记,且依赖还可能由 Layer 提供,
|
|
227
|
+
* 因此这里只做保守告警,提醒用户确认依赖已经进入最终代码目录。
|
|
228
|
+
*/
|
|
229
|
+
function checkPythonDependencyBundled(functionDir, checks) {
|
|
230
|
+
const manifests = PYTHON_DEPENDENCY_FILES.filter(file => {
|
|
231
|
+
const stat = safeStat(path_1.default.join(functionDir, file));
|
|
232
|
+
return (stat === null || stat === void 0 ? void 0 : stat.isFile()) === true;
|
|
233
|
+
});
|
|
234
|
+
if (manifests.length === 0) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
checks.push(warn(types_1.FUNCTION_DEPLOY_ERROR.PREFLIGHT_DEPENDENCY_NOT_BUNDLED, `发现 Python 依赖清单(${manifests.join('、')}),请确认依赖已随代码包携带`, 'HTTP 代码包部署不会执行 pip;请在兼容的 Linux 环境中使用 pip install -r requirements.txt -t <最终代码目录>,或确认依赖由 Layer 提供'));
|
|
238
|
+
}
|
|
239
|
+
/** 检查包大小与软链接逃逸 */
|
|
240
|
+
function checkPackageContents(functionDir, checks) {
|
|
241
|
+
const scan = scanPackageContents(functionDir);
|
|
242
|
+
for (const link of scan.escapedLinks) {
|
|
243
|
+
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.PREFLIGHT_SYMLINK_ESCAPE, '代码目录包含逃逸到目录之外的软链接', {
|
|
244
|
+
remediation: '请移除指向函数目录之外的符号链接,避免打包引入非预期或敏感文件',
|
|
245
|
+
detail: `软链接:${link}`
|
|
246
|
+
}));
|
|
247
|
+
}
|
|
248
|
+
if (!scan.truncated && scan.totalSize > PACKAGE_SIZE_WARN_THRESHOLD) {
|
|
249
|
+
const sizeMb = (scan.totalSize / 1024 / 1024).toFixed(2);
|
|
250
|
+
const limitMb = (PACKAGE_SIZE_WARN_THRESHOLD / 1024 / 1024).toFixed(0);
|
|
251
|
+
checks.push(warn(types_1.FUNCTION_DEPLOY_ERROR.PREFLIGHT_PACKAGE_TOO_LARGE, `代码包体积约 ${sizeMb} MB,超过 ${limitMb} MB`, '较大的代码包会拖慢上传与冷启动,建议清理无关文件或依赖'));
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* 执行部署前本地检查
|
|
256
|
+
*
|
|
257
|
+
* HTTP zip 策略检查代码包条件;HTTP cloud 策略检查构建上下文和 Dockerfile。
|
|
258
|
+
* base64Code 来源无本地目录,跳过文件检查。
|
|
259
|
+
* @returns 本地检查报告,聚合全部问题
|
|
260
|
+
*/
|
|
261
|
+
function runLocalPreflight(config) {
|
|
262
|
+
const strategy = config.buildStrategy;
|
|
263
|
+
if (config.type !== 'HTTP') {
|
|
264
|
+
return buildReport(strategy, []);
|
|
265
|
+
}
|
|
266
|
+
if (strategy === 'cloud') {
|
|
267
|
+
const checks = [];
|
|
268
|
+
try {
|
|
269
|
+
checkCloudBuildSource(config, checks);
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.PREFLIGHT_IO_ERROR, '读取云端镜像构建上下文时发生 IO 错误', {
|
|
273
|
+
remediation: '请检查目录权限与磁盘状态后重试',
|
|
274
|
+
detail: error instanceof Error ? error.message : String(error)
|
|
275
|
+
}));
|
|
276
|
+
}
|
|
277
|
+
return buildReport(strategy, checks);
|
|
278
|
+
}
|
|
279
|
+
if (strategy !== 'zip') {
|
|
280
|
+
return buildReport(strategy, []);
|
|
281
|
+
}
|
|
282
|
+
// 收窄到 zip 子类型后可直接访问代码来源字段(base64Code/functionPath/functionRootPath)
|
|
283
|
+
const source = config;
|
|
284
|
+
// base64Code 已是内存 ZIP,无本地目录可查
|
|
285
|
+
if (typeof source.base64Code === 'string' && source.base64Code.trim()) {
|
|
286
|
+
return buildReport(strategy, []);
|
|
287
|
+
}
|
|
288
|
+
const checks = [];
|
|
289
|
+
try {
|
|
290
|
+
const functionDir = resolveFunctionDir(source);
|
|
291
|
+
if (functionDir === null) {
|
|
292
|
+
// 代码来源缺失属于配置契约问题,已由 config-guard 覆盖,此处不重复报错
|
|
293
|
+
return buildReport(strategy, checks);
|
|
294
|
+
}
|
|
295
|
+
const dirStat = safeStat(functionDir);
|
|
296
|
+
if (dirStat === null) {
|
|
297
|
+
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.PREFLIGHT_CODE_PATH_MISSING, '代码目录不存在', {
|
|
298
|
+
remediation: '请确认 functionPath 或 functionRootPath 指向真实存在的目录',
|
|
299
|
+
detail: `期望路径:${functionDir}`
|
|
300
|
+
}));
|
|
301
|
+
return buildReport(strategy, checks);
|
|
302
|
+
}
|
|
303
|
+
if (!dirStat.isDirectory()) {
|
|
304
|
+
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.PREFLIGHT_CODE_PATH_NOT_DIRECTORY, '代码路径不是目录', {
|
|
305
|
+
remediation: '代码来源需指向目录,而非文件',
|
|
306
|
+
detail: `路径:${functionDir}`
|
|
307
|
+
}));
|
|
308
|
+
return buildReport(strategy, checks);
|
|
309
|
+
}
|
|
310
|
+
checkBootstrap(functionDir, checks);
|
|
311
|
+
checkPackageContents(functionDir, checks);
|
|
312
|
+
const runtime = source.runtime === undefined ? 'Nodejs' : String(source.runtime);
|
|
313
|
+
if (runtime.startsWith('Nodejs')) {
|
|
314
|
+
if (source.installDependency === false) {
|
|
315
|
+
checkNodeDependencyBundled(functionDir, checks);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
else if (runtime.startsWith('Python')) {
|
|
319
|
+
checkPythonDependencyBundled(functionDir, checks);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
catch (error) {
|
|
323
|
+
checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.PREFLIGHT_IO_ERROR, '读取代码目录时发生 IO 错误', {
|
|
324
|
+
remediation: '请检查目录权限与磁盘状态后重试',
|
|
325
|
+
detail: error instanceof Error ? error.message : String(error)
|
|
326
|
+
}));
|
|
327
|
+
}
|
|
328
|
+
return buildReport(strategy, checks);
|
|
329
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FUNCTION_DEPLOY_ERROR = exports.CUSTOM_IMAGE_RUNTIME = exports.DEFAULT_DOCKERFILE = exports.DEFAULT_IMAGE_PLATFORM = exports.IMAGE_FUNCTION_PORT = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* 镜像 HTTP 函数固定监听端口
|
|
6
|
+
* SCF HTTP 自定义镜像函数要求容器监听 9000
|
|
7
|
+
*/
|
|
8
|
+
exports.IMAGE_FUNCTION_PORT = 9000;
|
|
9
|
+
/**
|
|
10
|
+
* 镜像构建默认目标平台
|
|
11
|
+
* SCF 运行环境为 linux/amd64,Apple Silicon 也必须显式构建该平台
|
|
12
|
+
*/
|
|
13
|
+
exports.DEFAULT_IMAGE_PLATFORM = 'linux/amd64';
|
|
14
|
+
/** 默认 Dockerfile 名称,相对于构建上下文 */
|
|
15
|
+
exports.DEFAULT_DOCKERFILE = 'Dockerfile';
|
|
16
|
+
/** 自定义镜像运行时标识 */
|
|
17
|
+
exports.CUSTOM_IMAGE_RUNTIME = 'CustomImage';
|
|
18
|
+
/** 函数部署错误码 */
|
|
19
|
+
exports.FUNCTION_DEPLOY_ERROR = {
|
|
20
|
+
CONFIG_INVALID: 'CONFIG_INVALID',
|
|
21
|
+
CONFIG_NAME_INVALID: 'CONFIG_NAME_INVALID',
|
|
22
|
+
CONFIG_TYPE_INVALID: 'CONFIG_TYPE_INVALID',
|
|
23
|
+
CONFIG_STRATEGY_INVALID: 'CONFIG_STRATEGY_INVALID',
|
|
24
|
+
CONFIG_TIMEOUT_INVALID: 'CONFIG_TIMEOUT_INVALID',
|
|
25
|
+
CONFIG_MEMORY_SIZE_INVALID: 'CONFIG_MEMORY_SIZE_INVALID',
|
|
26
|
+
CONFIG_ENV_VARIABLE_INVALID: 'CONFIG_ENV_VARIABLE_INVALID',
|
|
27
|
+
EVENT_IMAGE_NOT_SUPPORTED: 'EVENT_IMAGE_NOT_SUPPORTED',
|
|
28
|
+
EVENT_GATEWAY_NOT_SUPPORTED: 'EVENT_GATEWAY_NOT_SUPPORTED',
|
|
29
|
+
EVENT_HTTP_FIELD_NOT_SUPPORTED: 'EVENT_HTTP_FIELD_NOT_SUPPORTED',
|
|
30
|
+
HTTP_ZIP_RUNTIME_INVALID: 'HTTP_ZIP_RUNTIME_INVALID',
|
|
31
|
+
HTTP_ZIP_IMAGE_CONFIG_CONFLICT: 'HTTP_ZIP_IMAGE_CONFIG_CONFLICT',
|
|
32
|
+
HTTP_ZIP_INSTALL_DEPENDENCY_OVERRIDDEN: 'HTTP_ZIP_INSTALL_DEPENDENCY_OVERRIDDEN',
|
|
33
|
+
HTTP_TRIGGER_NOT_SUPPORTED: 'HTTP_TRIGGER_NOT_SUPPORTED',
|
|
34
|
+
HTTP_CONCURRENCY_INVALID: 'HTTP_CONCURRENCY_INVALID',
|
|
35
|
+
HTTP_PROTOCOL_PARAMS_INVALID: 'HTTP_PROTOCOL_PARAMS_INVALID',
|
|
36
|
+
CODE_SOURCE_MISSING: 'CODE_SOURCE_MISSING',
|
|
37
|
+
CODE_SOURCE_CONFLICT: 'CODE_SOURCE_CONFLICT',
|
|
38
|
+
IMAGE_CONFIG_MISSING: 'IMAGE_CONFIG_MISSING',
|
|
39
|
+
IMAGE_CONFIG_NOT_ALLOWED: 'IMAGE_CONFIG_NOT_ALLOWED',
|
|
40
|
+
IMAGE_URI_INVALID: 'IMAGE_URI_INVALID',
|
|
41
|
+
IMAGE_TAG_MISSING: 'IMAGE_TAG_MISSING',
|
|
42
|
+
IMAGE_TAG_LATEST_FORBIDDEN: 'IMAGE_TAG_LATEST_FORBIDDEN',
|
|
43
|
+
IMAGE_TYPE_INVALID: 'IMAGE_TYPE_INVALID',
|
|
44
|
+
IMAGE_REGISTRY_ID_MISSING: 'IMAGE_REGISTRY_ID_MISSING',
|
|
45
|
+
IMAGE_REGISTRY_ID_INVALID: 'IMAGE_REGISTRY_ID_INVALID',
|
|
46
|
+
IMAGE_PORT_INVALID: 'IMAGE_PORT_INVALID',
|
|
47
|
+
IMAGE_RUNTIME_INVALID: 'IMAGE_RUNTIME_INVALID',
|
|
48
|
+
IMAGE_HANDLER_NOT_ALLOWED: 'IMAGE_HANDLER_NOT_ALLOWED',
|
|
49
|
+
BUILD_CONFIG_MISSING: 'BUILD_CONFIG_MISSING',
|
|
50
|
+
BUILD_DOCKERFILE_INVALID: 'BUILD_DOCKERFILE_INVALID',
|
|
51
|
+
BUILD_CONTEXT_INVALID: 'BUILD_CONTEXT_INVALID',
|
|
52
|
+
BUILD_PLATFORM_INVALID: 'BUILD_PLATFORM_INVALID',
|
|
53
|
+
BUILD_TAG_INVALID: 'BUILD_TAG_INVALID',
|
|
54
|
+
BUILD_REPOSITORY_INVALID: 'BUILD_REPOSITORY_INVALID',
|
|
55
|
+
BUILD_ARG_INVALID: 'BUILD_ARG_INVALID',
|
|
56
|
+
CLOUD_REGISTRY_CREDENTIAL_MISSING: 'CLOUD_REGISTRY_CREDENTIAL_MISSING',
|
|
57
|
+
CLOUD_REGISTRY_CREDENTIAL_INVALID: 'CLOUD_REGISTRY_CREDENTIAL_INVALID',
|
|
58
|
+
GATEWAY_PATH_INVALID: 'GATEWAY_PATH_INVALID',
|
|
59
|
+
LOCAL_FALLBACK_INVALID: 'LOCAL_FALLBACK_INVALID',
|
|
60
|
+
// Preflight(有 IO 的本地检查)
|
|
61
|
+
PREFLIGHT_CODE_PATH_MISSING: 'PREFLIGHT_CODE_PATH_MISSING',
|
|
62
|
+
PREFLIGHT_CODE_PATH_NOT_DIRECTORY: 'PREFLIGHT_CODE_PATH_NOT_DIRECTORY',
|
|
63
|
+
PREFLIGHT_BOOTSTRAP_MISSING: 'PREFLIGHT_BOOTSTRAP_MISSING',
|
|
64
|
+
PREFLIGHT_BOOTSTRAP_NOT_FILE: 'PREFLIGHT_BOOTSTRAP_NOT_FILE',
|
|
65
|
+
PREFLIGHT_BOOTSTRAP_NOT_EXECUTABLE: 'PREFLIGHT_BOOTSTRAP_NOT_EXECUTABLE',
|
|
66
|
+
PREFLIGHT_PACKAGE_TOO_LARGE: 'PREFLIGHT_PACKAGE_TOO_LARGE',
|
|
67
|
+
PREFLIGHT_SYMLINK_ESCAPE: 'PREFLIGHT_SYMLINK_ESCAPE',
|
|
68
|
+
PREFLIGHT_DEPENDENCY_NOT_BUNDLED: 'PREFLIGHT_DEPENDENCY_NOT_BUNDLED',
|
|
69
|
+
PREFLIGHT_IO_ERROR: 'PREFLIGHT_IO_ERROR',
|
|
70
|
+
// Docker 本地能力 Preflight(local 策略,scope: local)
|
|
71
|
+
PREFLIGHT_DOCKER_CLI_MISSING: 'PREFLIGHT_DOCKER_CLI_MISSING',
|
|
72
|
+
PREFLIGHT_DOCKER_DAEMON_UNAVAILABLE: 'PREFLIGHT_DOCKER_DAEMON_UNAVAILABLE',
|
|
73
|
+
PREFLIGHT_DOCKER_BUILDX_MISSING: 'PREFLIGHT_DOCKER_BUILDX_MISSING',
|
|
74
|
+
// 本地构建与推送(local 策略)
|
|
75
|
+
LOCAL_BUILD_CONTEXT_MISSING: 'LOCAL_BUILD_CONTEXT_MISSING',
|
|
76
|
+
LOCAL_BUILD_DOCKERFILE_MISSING: 'LOCAL_BUILD_DOCKERFILE_MISSING',
|
|
77
|
+
LOCAL_BUILD_REPOSITORY_MISSING: 'LOCAL_BUILD_REPOSITORY_MISSING',
|
|
78
|
+
LOCAL_BUILD_FAILED: 'LOCAL_BUILD_FAILED',
|
|
79
|
+
LOCAL_PUSH_FAILED: 'LOCAL_PUSH_FAILED',
|
|
80
|
+
LOCAL_DIGEST_QUERY_FAILED: 'LOCAL_DIGEST_QUERY_FAILED',
|
|
81
|
+
// 云端构建(cloud 策略,CloudApp custom)
|
|
82
|
+
CLOUD_BUILD_DOCKERFILE_MISSING: 'CLOUD_BUILD_DOCKERFILE_MISSING',
|
|
83
|
+
CLOUD_UPLOAD_FAILED: 'CLOUD_UPLOAD_FAILED',
|
|
84
|
+
CLOUD_BUILD_TRIGGER_FAILED: 'CLOUD_BUILD_TRIGGER_FAILED',
|
|
85
|
+
CLOUD_BUILD_FAILED: 'CLOUD_BUILD_FAILED',
|
|
86
|
+
CLOUD_REGISTRY_AUTH_FAILED: 'CLOUD_REGISTRY_AUTH_FAILED',
|
|
87
|
+
CLOUD_PERSONAL_REGISTRY_REPOSITORY_QUOTA_EXCEEDED: 'CLOUD_PERSONAL_REGISTRY_REPOSITORY_QUOTA_EXCEEDED',
|
|
88
|
+
CLOUD_BUILD_CANCELED: 'CLOUD_BUILD_CANCELED',
|
|
89
|
+
CLOUD_BUILD_TIMEOUT: 'CLOUD_BUILD_TIMEOUT',
|
|
90
|
+
// 镜像远端校验 Preflight(有 IO,scope: cloud)
|
|
91
|
+
PREFLIGHT_IMAGE_REMOTE_NOT_FOUND: 'PREFLIGHT_IMAGE_REMOTE_NOT_FOUND',
|
|
92
|
+
PREFLIGHT_IMAGE_REMOTE_UNVERIFIABLE: 'PREFLIGHT_IMAGE_REMOTE_UNVERIFIABLE',
|
|
93
|
+
PREFLIGHT_IMAGE_DIGEST_UNRESOLVED: 'PREFLIGHT_IMAGE_DIGEST_UNRESOLVED',
|
|
94
|
+
PREFLIGHT_IMAGE_REGION_MISMATCH: 'PREFLIGHT_IMAGE_REGION_MISMATCH',
|
|
95
|
+
// 镜像拉取授权 Preflight(涉及 CAM,scope: cloud)
|
|
96
|
+
PREFLIGHT_IMAGE_PULL_ROLE_MISSING: 'PREFLIGHT_IMAGE_PULL_ROLE_MISSING',
|
|
97
|
+
PREFLIGHT_IMAGE_PULL_POLICY_MISSING: 'PREFLIGHT_IMAGE_PULL_POLICY_MISSING',
|
|
98
|
+
PREFLIGHT_IMAGE_PULL_AUTH_GRANTED: 'PREFLIGHT_IMAGE_PULL_AUTH_GRANTED',
|
|
99
|
+
PREFLIGHT_IMAGE_PULL_AUTH_UNVERIFIABLE: 'PREFLIGHT_IMAGE_PULL_AUTH_UNVERIFIABLE',
|
|
100
|
+
PREFLIGHT_CAM_PERMISSION_DENIED: 'PREFLIGHT_CAM_PERMISSION_DENIED',
|
|
101
|
+
FUNCTION_RECREATE_REQUIRED: 'FUNCTION_RECREATE_REQUIRED',
|
|
102
|
+
NOT_IMPLEMENTED: 'NOT_IMPLEMENTED'
|
|
103
|
+
};
|
package/lib/env/index.js
CHANGED
|
@@ -28,7 +28,6 @@ class EnvService {
|
|
|
28
28
|
this.tcbService = new utils_1.CloudService(environment.cloudBaseContext, 'tcb', '2018-06-08');
|
|
29
29
|
this.commonService = environment.getCommonService('lowcode', '2021-01-08');
|
|
30
30
|
this.billingService = new billing_1.BillingService(environment.cloudBaseContext);
|
|
31
|
-
this.certificateCloudService = new utils_1.CloudService(environment.cloudBaseContext, 'ssl', '2019-12-05');
|
|
32
31
|
this.tokenCache = new Map();
|
|
33
32
|
}
|
|
34
33
|
/**
|
|
@@ -770,23 +769,6 @@ class EnvService {
|
|
|
770
769
|
async describeCreditsUsageDetail(params) {
|
|
771
770
|
return this.cloudService.request('DescribeCreditsUsageDetail', Object.assign({}, params));
|
|
772
771
|
}
|
|
773
|
-
/**
|
|
774
|
-
* 获取当前凭证有权限访问的 SSL 证书列表。
|
|
775
|
-
*
|
|
776
|
-
* 证书用于自定义域名 HTTPS 配置,本接口不依赖 CloudBase 环境信息。
|
|
777
|
-
* @param {DescribeCertificatesParams} params 查询参数
|
|
778
|
-
* @returns {Promise<DescribeCertificatesRes>}
|
|
779
|
-
*/
|
|
780
|
-
async describeCertificates(params = {}) {
|
|
781
|
-
if (params.Offset !== undefined && (!Number.isInteger(params.Offset) || params.Offset < 0)) {
|
|
782
|
-
throw new error_1.CloudBaseError('Offset 必须是大于等于 0 的整数');
|
|
783
|
-
}
|
|
784
|
-
if (params.Limit !== undefined &&
|
|
785
|
-
(!Number.isInteger(params.Limit) || params.Limit < 1 || params.Limit > 1000)) {
|
|
786
|
-
throw new error_1.CloudBaseError('Limit 必须是 1 至 1000 的整数');
|
|
787
|
-
}
|
|
788
|
-
return this.certificateCloudService.request('DescribeCertificates', params);
|
|
789
|
-
}
|
|
790
772
|
/**
|
|
791
773
|
* 查询HTTP访问服务域名路由信息
|
|
792
774
|
* @param {DescribeHttpServiceRouteParams} params 查询参数
|
|
@@ -795,15 +777,6 @@ class EnvService {
|
|
|
795
777
|
async describeHttpServiceRoute(params) {
|
|
796
778
|
return this.cloudService.request('DescribeHTTPServiceRoute', Object.assign({}, params));
|
|
797
779
|
}
|
|
798
|
-
/**
|
|
799
|
-
* 校验HTTP访问服务域名路由
|
|
800
|
-
* 此操作为只读预检,不会创建任何资源。
|
|
801
|
-
* @param {VerifyHttpServiceRouteParams} params 校验参数
|
|
802
|
-
* @returns {Promise<VerifyHttpServiceRouteRes>}
|
|
803
|
-
*/
|
|
804
|
-
async verifyHttpServiceRoute(params) {
|
|
805
|
-
return this.cloudService.request('VerifyHTTPServiceRoute', Object.assign({}, params));
|
|
806
|
-
}
|
|
807
780
|
/**
|
|
808
781
|
* 创建HTTP访问服务域名路由
|
|
809
782
|
* @param {CreateHttpServiceRouteParams} params 创建参数
|
package/lib/environment.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.Environment = void 0;
|
|
4
4
|
const database_1 = require("./database");
|
|
5
5
|
const function_1 = require("./function");
|
|
6
|
+
const FunctionDeployer_1 = require("./deploy/FunctionDeployer");
|
|
6
7
|
const cloudrun_1 = require("./cloudrun");
|
|
7
8
|
const agent_1 = require("./agent");
|
|
8
9
|
const storage_1 = require("./storage");
|
|
@@ -83,6 +84,16 @@ class Environment {
|
|
|
83
84
|
getFunctionService() {
|
|
84
85
|
return this.functionService;
|
|
85
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* 获取函数部署编排器
|
|
89
|
+
* 采用懒初始化,避免模块循环引用导致的初始化顺序问题
|
|
90
|
+
*/
|
|
91
|
+
getFunctionDeployer() {
|
|
92
|
+
if (!this.functionDeployer) {
|
|
93
|
+
this.functionDeployer = new FunctionDeployer_1.FunctionDeployer(this);
|
|
94
|
+
}
|
|
95
|
+
return this.functionDeployer;
|
|
96
|
+
}
|
|
86
97
|
getCloudRunService() {
|
|
87
98
|
return this.cloudRunService;
|
|
88
99
|
}
|
package/lib/function/index.js
CHANGED
package/lib/hosting/index.js
CHANGED
|
@@ -364,7 +364,7 @@ class HostingService {
|
|
|
364
364
|
* @param options
|
|
365
365
|
*/
|
|
366
366
|
async setWebsiteDocument(options) {
|
|
367
|
-
const { indexDocument, errorDocument, routingRules } = options;
|
|
367
|
+
const { indexDocument, errorDocument, originalHttpStatus, charity404, autoAddressing, routingRules } = options;
|
|
368
368
|
const hosting = await this.checkStatus();
|
|
369
369
|
const { Bucket, Regoin } = hosting;
|
|
370
370
|
const storageService = await this.environment.getStorageService();
|
|
@@ -373,6 +373,9 @@ class HostingService {
|
|
|
373
373
|
region: Regoin,
|
|
374
374
|
indexDocument,
|
|
375
375
|
errorDocument,
|
|
376
|
+
originalHttpStatus,
|
|
377
|
+
charity404,
|
|
378
|
+
autoAddressing,
|
|
376
379
|
routingRules
|
|
377
380
|
});
|
|
378
381
|
return res;
|
package/lib/index.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
const context_1 = require("./context");
|
|
3
3
|
const environmentManager_1 = require("./environmentManager");
|
|
4
4
|
const docs_1 = require("./docs");
|
|
5
|
+
const projectValidator_1 = require("./projectValidator");
|
|
6
|
+
const DeployOrchestrator_1 = require("./deploy/DeployOrchestrator");
|
|
5
7
|
class CloudBase {
|
|
6
8
|
/**
|
|
7
9
|
* init 初始化 为单例
|
|
@@ -49,6 +51,13 @@ class CloudBase {
|
|
|
49
51
|
get functions() {
|
|
50
52
|
return this.currentEnvironment().getFunctionService();
|
|
51
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* 函数部署编排器(FunctionDeployer)
|
|
56
|
+
* 在现有 FunctionService 之上,按 buildStrategy 编排 Event/HTTP 函数的创建、更新与访问收敛
|
|
57
|
+
*/
|
|
58
|
+
get functionDeployer() {
|
|
59
|
+
return this.currentEnvironment().getFunctionDeployer();
|
|
60
|
+
}
|
|
52
61
|
get cloudrun() {
|
|
53
62
|
return this.currentEnvironment().getCloudRunService();
|
|
54
63
|
}
|
|
@@ -146,6 +155,28 @@ class CloudBase {
|
|
|
146
155
|
}
|
|
147
156
|
return this.docsService;
|
|
148
157
|
}
|
|
158
|
+
get projectValidator() {
|
|
159
|
+
if (!this.projectValidatorService) {
|
|
160
|
+
this.projectValidatorService = new projectValidator_1.ProjectValidator();
|
|
161
|
+
}
|
|
162
|
+
return this.projectValidatorService;
|
|
163
|
+
}
|
|
164
|
+
getProjectValidator() {
|
|
165
|
+
return this.projectValidator;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* 声明式部署编排器
|
|
169
|
+
* 顺序:database → functions → app → hosting → gateway;支持 dry-run / only / skip / 幂等重试
|
|
170
|
+
*/
|
|
171
|
+
get deployOrchestrator() {
|
|
172
|
+
if (!this.deployOrchestratorService) {
|
|
173
|
+
this.deployOrchestratorService = new DeployOrchestrator_1.DeployOrchestrator(this.currentEnvironment());
|
|
174
|
+
}
|
|
175
|
+
return this.deployOrchestratorService;
|
|
176
|
+
}
|
|
177
|
+
getDeployOrchestrator() {
|
|
178
|
+
return this.deployOrchestrator;
|
|
179
|
+
}
|
|
149
180
|
getEnvironmentManager() {
|
|
150
181
|
return this.environmentManager;
|
|
151
182
|
}
|