@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.
Files changed (62) hide show
  1. package/lib/cloudApp/index.js +12 -3
  2. package/lib/deploy/DatabaseDeployer.js +238 -0
  3. package/lib/deploy/DeployOrchestrator.js +645 -0
  4. package/lib/deploy/FunctionDeployer.js +611 -0
  5. package/lib/deploy/GatewayDeployer.js +612 -0
  6. package/lib/deploy/StateStore.js +327 -0
  7. package/lib/deploy/StaticDeployer.js +236 -0
  8. package/lib/deploy/domain.js +41 -0
  9. package/lib/deploy/framework.js +230 -0
  10. package/lib/deploy/function/artifact.js +207 -0
  11. package/lib/deploy/function/builders/cloud.js +654 -0
  12. package/lib/deploy/function/cam-preflight.js +157 -0
  13. package/lib/deploy/function/cloud-build-service.js +191 -0
  14. package/lib/deploy/function/config-guard.js +595 -0
  15. package/lib/deploy/function/docker-preflight.js +87 -0
  16. package/lib/deploy/function/image-preflight.js +164 -0
  17. package/lib/deploy/function/local-builder.js +129 -0
  18. package/lib/deploy/function/planner.js +278 -0
  19. package/lib/deploy/function/preflight.js +329 -0
  20. package/lib/deploy/types.js +103 -0
  21. package/lib/env/index.js +0 -27
  22. package/lib/environment.js +11 -0
  23. package/lib/function/index.js +1 -1
  24. package/lib/hosting/index.js +4 -1
  25. package/lib/index.js +31 -0
  26. package/lib/projectValidator/index.js +452 -0
  27. package/lib/projectValidator/types.js +2 -0
  28. package/lib/storage/index.js +6 -7
  29. package/lib/utils/index.js +62 -5
  30. package/package.json +2 -1
  31. package/types/cloudApp/index.d.ts +4 -0
  32. package/types/cloudApp/types.d.ts +87 -6
  33. package/types/deploy/DatabaseDeployer.d.ts +70 -0
  34. package/types/deploy/DeployOrchestrator.d.ts +170 -0
  35. package/types/deploy/FunctionDeployer.d.ts +159 -0
  36. package/types/deploy/GatewayDeployer.d.ts +194 -0
  37. package/types/deploy/StateStore.d.ts +170 -0
  38. package/types/deploy/StaticDeployer.d.ts +97 -0
  39. package/types/deploy/domain.d.ts +17 -0
  40. package/types/deploy/framework.d.ts +63 -0
  41. package/types/deploy/function/artifact.d.ts +40 -0
  42. package/types/deploy/function/builders/cloud.d.ts +110 -0
  43. package/types/deploy/function/cam-preflight.d.ts +51 -0
  44. package/types/deploy/function/cloud-build-service.d.ts +10 -0
  45. package/types/deploy/function/config-guard.d.ts +41 -0
  46. package/types/deploy/function/docker-preflight.d.ts +17 -0
  47. package/types/deploy/function/image-preflight.d.ts +21 -0
  48. package/types/deploy/function/local-builder.d.ts +26 -0
  49. package/types/deploy/function/planner.d.ts +15 -0
  50. package/types/deploy/function/preflight.d.ts +9 -0
  51. package/types/deploy/types.d.ts +429 -0
  52. package/types/env/index.d.ts +1 -17
  53. package/types/env/type.d.ts +6 -260
  54. package/types/environment.d.ts +7 -0
  55. package/types/function/types.d.ts +15 -0
  56. package/types/hosting/index.d.ts +6 -0
  57. package/types/index.d.ts +18 -0
  58. package/types/interfaces/function.interface.d.ts +1 -1
  59. package/types/projectValidator/index.d.ts +38 -0
  60. package/types/projectValidator/types.d.ts +26 -0
  61. package/types/storage/index.d.ts +6 -0
  62. package/types/utils/index.d.ts +13 -0
@@ -0,0 +1,157 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.IMAGE_PULL_GRANT_URL = exports.REQUIRED_IMAGE_PULL_POLICY = exports.SCF_PULL_IMAGE_ROLE = void 0;
4
+ exports.isCamPermissionError = isCamPermissionError;
5
+ exports.runImagePullAuthPreflight = runImagePullAuthPreflight;
6
+ const types_1 = require("../types");
7
+ /**
8
+ * 镜像拉取授权 Preflight(涉及 CAM,scope: cloud)
9
+ *
10
+ * SCF 拉取镜像由服务角色 SCF_QcsRole 代理,个人版镜像要求该角色绑定
11
+ * QcloudAccessForSCFRoleInPullImage 策略。本模块在 image 策略部署前:
12
+ * 1. 探测该角色与策略是否就绪;
13
+ * 2. 缺失且允许时,自动绑定「唯一白名单策略」补齐(绝不放大到其它策略);
14
+ * 3. 当前身份无 CAM 权限时不硬失败,降级为结构化指引 + 一键授权链接兜底。
15
+ *
16
+ * 安全边界(对应 security_rules 的 AuthZ / 最小权限):
17
+ * - 自动授权只允许绑定 REQUIRED_IMAGE_PULL_POLICY 这一条部署必需的预置策略;
18
+ * - 不代用户创建角色、不绑定任何白名单外策略;
19
+ * - 探测/授权失败一律走降级,不阻断到无法给出可操作指引。
20
+ */
21
+ /** SCF 拉取镜像使用的服务角色 */
22
+ exports.SCF_PULL_IMAGE_ROLE = 'SCF_QcsRole';
23
+ /** 拉取个人版镜像所需的唯一预置策略(自动授权白名单,仅此一条) */
24
+ exports.REQUIRED_IMAGE_PULL_POLICY = 'QcloudAccessForSCFRoleInPullImage';
25
+ /** 官方一键授权链接,作为无 CAM 权限时的兜底指引 */
26
+ exports.IMAGE_PULL_GRANT_URL = 'https://console.cloud.tencent.com/cam/role/grant' +
27
+ `?roleName=${exports.SCF_PULL_IMAGE_ROLE}&policyName=${exports.REQUIRED_IMAGE_PULL_POLICY}` +
28
+ '&principal=eyJzZXJ2aWNlIjoic2NmLnFjbG91ZC5jb20ifQ%3D%3D';
29
+ /** 判断错误是否为 CAM 权限不足 */
30
+ function isCamPermissionError(error) {
31
+ const code = typeof (error === null || error === void 0 ? void 0 : error.code) === 'string' ? error.code : '';
32
+ const message = typeof (error === null || error === void 0 ? void 0 : error.message) === 'string' ? error.message : '';
33
+ const haystack = `${code} ${message}`.toLowerCase();
34
+ return (haystack.includes('unauthorizedoperation') ||
35
+ haystack.includes('no permission') ||
36
+ haystack.includes('not authorized') ||
37
+ haystack.includes('cam_unauthorized'));
38
+ }
39
+ /** 判断错误是否为角色不存在 */
40
+ function isRoleNotFoundError(error) {
41
+ const code = typeof (error === null || error === void 0 ? void 0 : error.code) === 'string' ? error.code : '';
42
+ const message = typeof (error === null || error === void 0 ? void 0 : error.message) === 'string' ? error.message : '';
43
+ const haystack = `${code} ${message}`.toLowerCase();
44
+ return haystack.includes('notfound') || haystack.includes('not exist') || haystack.includes('rolenotexist');
45
+ }
46
+ function pass(id, summary) {
47
+ return { id, status: 'pass', summary };
48
+ }
49
+ function warn(id, summary, remediation) {
50
+ return { id, status: 'warn', summary, remediation };
51
+ }
52
+ function fail(id, summary, options = {}) {
53
+ return { id, status: 'fail', summary, remediation: options.remediation, detail: options.detail };
54
+ }
55
+ function buildReport(checks) {
56
+ return {
57
+ scope: 'cloud',
58
+ strategy: 'image',
59
+ ready: !checks.some(item => item.status === 'fail'),
60
+ checks
61
+ };
62
+ }
63
+ /** 是否需要执行镜像拉取授权检查 */
64
+ function needsImagePullAuth(config) {
65
+ var _a;
66
+ if (config.type !== 'HTTP') {
67
+ return false;
68
+ }
69
+ // image:已有个人版镜像;local:本地构建推送的也是个人版镜像,SCF 拉取同样需要该授权
70
+ if (config.buildStrategy === 'image') {
71
+ // 联合类型已按 buildStrategy 收窄为 INormalizedHttpImageConfig,可直接访问顶层与 imageConfig 字段
72
+ const imageType = ((_a = config.imageConfig) === null || _a === void 0 ? void 0 : _a.imageType) || config.imageType;
73
+ return imageType === 'personal';
74
+ }
75
+ if (config.buildStrategy === 'local' || config.buildStrategy === 'cloud') {
76
+ // local/cloud 个人版先行:构建推送的默认是个人版镜像,SCF 拉取同样需要该授权;
77
+ // 未显式指定 imageType 时视为 personal;企业版另行处理
78
+ const imageType = config.imageType;
79
+ return imageType === undefined || imageType === 'personal';
80
+ }
81
+ return false;
82
+ }
83
+ /** 无 CAM 读权限时的降级检查项:无法自查,转手动指引 */
84
+ function unverifiableCheck() {
85
+ return warn(types_1.FUNCTION_DEPLOY_ERROR.PREFLIGHT_IMAGE_PULL_AUTH_UNVERIFIABLE, `当前身份无 CAM 权限,无法自动校验或补齐 ${exports.SCF_PULL_IMAGE_ROLE} 的拉镜像授权`, `若个人版镜像拉取失败,请用具备 CAM 权限的账号访问一键授权链接完成授权:${exports.IMAGE_PULL_GRANT_URL}`);
86
+ }
87
+ /**
88
+ * 执行镜像拉取授权 Preflight
89
+ *
90
+ * @param service CAM 能力适配(无则视为无法校验,直接降级为提示)
91
+ * @param config 规范化部署配置
92
+ * @param autoGrant 是否允许自动补齐授权(默认 true)
93
+ * @returns scope 为 cloud 的检查报告,聚合全部结论
94
+ */
95
+ async function runImagePullAuthPreflight(service, config, autoGrant = true) {
96
+ if (!needsImagePullAuth(config)) {
97
+ return buildReport([]);
98
+ }
99
+ // 无 CAM 适配能力:不阻断,仅提示手动授权入口
100
+ if (!service) {
101
+ return buildReport([unverifiableCheck()]);
102
+ }
103
+ const checks = [];
104
+ let roleDetail;
105
+ try {
106
+ roleDetail = await service.getRoleWithPolicies(exports.SCF_PULL_IMAGE_ROLE);
107
+ }
108
+ catch (error) {
109
+ if (isCamPermissionError(error)) {
110
+ // 无 CAM 读权限,无法自查,降级为提示(warn,不阻断部署尝试)
111
+ return buildReport([unverifiableCheck()]);
112
+ }
113
+ if (isRoleNotFoundError(error)) {
114
+ // 角色不存在属于真实前置缺失,不自动创建角色,给出可操作指引
115
+ checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.PREFLIGHT_IMAGE_PULL_ROLE_MISSING, `缺少服务角色 ${exports.SCF_PULL_IMAGE_ROLE}`, {
116
+ remediation: `请访问一键授权链接创建角色并授予拉镜像策略:${exports.IMAGE_PULL_GRANT_URL}`
117
+ }));
118
+ return buildReport(checks);
119
+ }
120
+ // 其它未知错误同样降级为提示,避免因 CAM 波动阻断部署
121
+ return buildReport([unverifiableCheck()]);
122
+ }
123
+ const attached = roleDetail.attachedPolicyNames || [];
124
+ if (attached.includes(exports.REQUIRED_IMAGE_PULL_POLICY)) {
125
+ checks.push(pass(types_1.FUNCTION_DEPLOY_ERROR.PREFLIGHT_IMAGE_PULL_POLICY_MISSING, `${exports.SCF_PULL_IMAGE_ROLE} 已具备拉镜像策略 ${exports.REQUIRED_IMAGE_PULL_POLICY}`));
126
+ return buildReport(checks);
127
+ }
128
+ // 未绑定拉镜像策略
129
+ if (!autoGrant) {
130
+ checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.PREFLIGHT_IMAGE_PULL_POLICY_MISSING, `${exports.SCF_PULL_IMAGE_ROLE} 缺少拉镜像策略 ${exports.REQUIRED_IMAGE_PULL_POLICY}`, {
131
+ remediation: `已关闭自动授权(autoGrant=false)。请访问一键授权链接手动授予:${exports.IMAGE_PULL_GRANT_URL}`
132
+ }));
133
+ return buildReport(checks);
134
+ }
135
+ // 自动授权:只绑定唯一白名单策略
136
+ try {
137
+ await service.attachPolicyByName(exports.SCF_PULL_IMAGE_ROLE, exports.REQUIRED_IMAGE_PULL_POLICY);
138
+ checks.push(pass(types_1.FUNCTION_DEPLOY_ERROR.PREFLIGHT_IMAGE_PULL_AUTH_GRANTED, `已自动为 ${exports.SCF_PULL_IMAGE_ROLE} 绑定拉镜像策略 ${exports.REQUIRED_IMAGE_PULL_POLICY}`));
139
+ return buildReport(checks);
140
+ }
141
+ catch (error) {
142
+ if (isCamPermissionError(error)) {
143
+ // 无 CAM 写权限:不硬失败,降级为可操作指引 + 兜底链接
144
+ checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.PREFLIGHT_CAM_PERMISSION_DENIED, `当前身份无 CAM 授权权限,无法自动为 ${exports.SCF_PULL_IMAGE_ROLE} 补齐拉镜像策略`, {
145
+ remediation: `请用具备 CAM 权限的账号(如主账号)访问一键授权链接完成授权:${exports.IMAGE_PULL_GRANT_URL}`,
146
+ detail: error === null || error === void 0 ? void 0 : error.message
147
+ }));
148
+ return buildReport(checks);
149
+ }
150
+ // 其它授权错误同样给出结构化失败与指引
151
+ checks.push(fail(types_1.FUNCTION_DEPLOY_ERROR.PREFLIGHT_IMAGE_PULL_POLICY_MISSING, `自动绑定拉镜像策略 ${exports.REQUIRED_IMAGE_PULL_POLICY} 失败`, {
152
+ remediation: `请访问一键授权链接手动授予:${exports.IMAGE_PULL_GRANT_URL}`,
153
+ detail: error === null || error === void 0 ? void 0 : error.message
154
+ }));
155
+ return buildReport(checks);
156
+ }
157
+ }
@@ -0,0 +1,191 @@
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.createCloudBuildService = createCloudBuildService;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const os_1 = __importDefault(require("os"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const error_1 = require("../../error");
11
+ const utils_1 = require("../../utils");
12
+ const http_request_1 = require("../../utils/http-request");
13
+ const types_1 = require("../types");
14
+ /**
15
+ * 基于 CloudAppService 的云端构建能力适配器
16
+ *
17
+ * 将 CloudApp custom 构建相关的 API/IO 逐一映射为 CloudImageBuilder 所需的
18
+ * ICloudBuildService 接口,使builder 与具体传输/网络实现解耦、便于测试替换。
19
+ *
20
+ * 步骤对应构建方案「阶段 A」(https://iwiki.woa.com/p/4020519533):
21
+ * - packContext:打包源码目录为临时 ZIP(复用 compressToZip)
22
+ * - getCosInfo:DescribeCloudAppCosInfo(DeployType:custom)取上传凭证与UnixTimestamp
23
+ * - uploadZip:PUT 上传到 COS,携带服务端返回的全部 UploadHeaders
24
+ * - createBuild:CreateCloudApp(DeployType:custom)触发容器内 build+push
25
+ * - getVersionStatus:DescribeCloudAppVersion 查询分步状态与产物
26
+ */
27
+ const CUSTOM_DEPLOY_TYPE = 'custom';
28
+ const CUSTOM_BUILD_TYPE = 'zip';
29
+ /** 云端构建打包默认忽略项,避免上传无关文件或本地环境密钥 */
30
+ const DEFAULT_IGNORE = [
31
+ 'node_modules/**',
32
+ '.venv/**',
33
+ '**/.venv/**',
34
+ 'venv/**',
35
+ '**/venv/**',
36
+ '__pycache__/**',
37
+ '**/__pycache__/**',
38
+ '*.pyc',
39
+ '**/*.pyc',
40
+ '.pytest_cache/**',
41
+ '**/.pytest_cache/**',
42
+ '.mypy_cache/**',
43
+ '**/.mypy_cache/**',
44
+ '.ruff_cache/**',
45
+ '**/.ruff_cache/**',
46
+ '.coverage',
47
+ '**/.coverage',
48
+ 'htmlcov/**',
49
+ '**/htmlcov/**',
50
+ '.git/**',
51
+ '.DS_Store',
52
+ '**/.DS_Store',
53
+ '**/.env',
54
+ // 保留 .env.example/.env.sample/.env.template,其余 .env.* 默认视为本地环境密钥
55
+ '**/.env.!(example|sample|template)'
56
+ ];
57
+ /**
58
+ * 由CloudAppService 构造 ICloudBuildService实现
59
+ */
60
+ function createCloudBuildService(cloudApp, options = {}) {
61
+ const ignore = [...DEFAULT_IGNORE, ...(options.ignore || [])];
62
+ return {
63
+ async packContext(cwd, extraEntries) {
64
+ if (!fs_1.default.existsSync(cwd) || !fs_1.default.statSync(cwd).isDirectory()) {
65
+ throw new error_1.CloudBaseError(`构建上下文目录不存在或不是目录:${cwd}`, {
66
+ code: types_1.FUNCTION_DEPLOY_ERROR.BUILD_CONTEXT_INVALID
67
+ });
68
+ }
69
+ const zipPath = path_1.default.join(os_1.default.tmpdir(), `fn-cloud-build-${Date.now()}-${Math.random().toString(36).slice(2)}.zip`);
70
+ const injectedEntries = (extraEntries || []).map(entry => ({
71
+ zipPath: entry.zipPath.replace(/\\/g, '/').replace(/^\.\//, ''),
72
+ content: entry.content,
73
+ mode: entry.mode
74
+ }));
75
+ // 源码中若存在与 SDK 注入文件同名的路径,必须从 glob 中排除,
76
+ // 避免 ZIP 出现重复 entry 后由解压顺序决定最终执行内容。
77
+ const contextIgnore = [
78
+ ...ignore,
79
+ ...injectedEntries.map(entry => entry.zipPath)
80
+ ];
81
+ // 把 SDK 注入的辅助文件(如 push 脚本)随源码一起打进zip,
82
+ // 通过临时文件注入,绝不写入用户源码目录。
83
+ try {
84
+ await (0, utils_1.compressToZip)({
85
+ dirPath: cwd,
86
+ outputPath: zipPath,
87
+ ignore: contextIgnore,
88
+ extraEntries: injectedEntries
89
+ });
90
+ }
91
+ catch (error) {
92
+ try {
93
+ if (fs_1.default.existsSync(zipPath)) {
94
+ fs_1.default.unlinkSync(zipPath);
95
+ }
96
+ }
97
+ catch (_a) {
98
+ // 清理未完成的临时 ZIP 失败不覆盖原始打包错误
99
+ }
100
+ const original = error instanceof Error ? error : new Error(String(error));
101
+ throw new error_1.CloudBaseError(`打包云端构建上下文失败:${original.message}`, {
102
+ code: types_1.FUNCTION_DEPLOY_ERROR.BUILD_CONTEXT_INVALID,
103
+ original
104
+ });
105
+ }
106
+ return zipPath;
107
+ },
108
+ async getCosInfo(serviceName) {
109
+ const res = await cloudApp.describeCosInfo({
110
+ deployType: CUSTOM_DEPLOY_TYPE,
111
+ serviceName,
112
+ suffix: '.zip'
113
+ });
114
+ return {
115
+ uploadUrl: res.UploadUrl,
116
+ uploadHeaders: (res.UploadHeaders || []).map(h => ({
117
+ key: h.Key,
118
+ value: h.Value
119
+ })),
120
+ unixTimestamp: res.UnixTimestamp
121
+ };
122
+ },
123
+ async uploadZip(cosInfo, zipPath) {
124
+ const zipSize = fs_1.default.statSync(zipPath).size;
125
+ // 必须携带服务端返回的全部 UploadHeaders,缺失会导致 COS 鉴权失败
126
+ const headers = {
127
+ 'Content-Type': 'application/zip',
128
+ 'Content-Length': String(zipSize)
129
+ };
130
+ for (const h of cosInfo.uploadHeaders) {
131
+ headers[h.key] = h.value;
132
+ }
133
+ const response = await (0, http_request_1.fetchStream)(cosInfo.uploadUrl, {
134
+ method: 'PUT',
135
+ body: fs_1.default.createReadStream(zipPath),
136
+ headers
137
+ });
138
+ if (!response.ok) {
139
+ let detail = '';
140
+ try {
141
+ const text = await response.text();
142
+ detail = text ? ` - ${text.substring(0, 500)}` : '';
143
+ }
144
+ catch (_a) {
145
+ // 解析响应体失败不影响主错误抛出
146
+ }
147
+ throw new error_1.CloudBaseError(`上传源码到 COS 失败:${response.status} ${response.statusText}${detail}`, { code: types_1.FUNCTION_DEPLOY_ERROR.CLOUD_UPLOAD_FAILED });
148
+ }
149
+ },
150
+ async createBuild(input) {
151
+ const res = await cloudApp.createApp({
152
+ deployType: CUSTOM_DEPLOY_TYPE,
153
+ serviceName: input.serviceName,
154
+ buildType: CUSTOM_BUILD_TYPE,
155
+ source: {
156
+ type: CUSTOM_BUILD_TYPE,
157
+ // 原样回填 DescribeCloudAppCosInfo 返回的时间戳,指向刚上传的源码包
158
+ cosTimestamp: input.cosTimestamp,
159
+ cosSuffix: '.zip'
160
+ },
161
+ env: input.env,
162
+ secrets: input.secrets,
163
+ customSteps: input.customSteps
164
+ });
165
+ return { buildId: res.BuildId, versionName: res.VersionName, requestId: res.RequestId };
166
+ },
167
+ async getVersionStatus(serviceName, versionName) {
168
+ const res = await cloudApp.describeAppVersion({
169
+ deployType: CUSTOM_DEPLOY_TYPE,
170
+ serviceName,
171
+ versionName
172
+ });
173
+ return {
174
+ status: res.Status,
175
+ steps: res.Steps,
176
+ artifacts: res.Artifacts,
177
+ requestId: res.RequestId
178
+ };
179
+ },
180
+ async cleanup(zipPath) {
181
+ if (zipPath && fs_1.default.existsSync(zipPath)) {
182
+ fs_1.default.unlinkSync(zipPath);
183
+ }
184
+ },
185
+ delay(ms) {
186
+ return new Promise(resolve => {
187
+ setTimeout(resolve, ms);
188
+ });
189
+ }
190
+ };
191
+ }