@cloudbase/manager-node 5.7.1-beta.1 → 5.8.0-beta.1

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 (63) 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 +606 -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 +214 -0
  11. package/lib/deploy/function/builders/cloud.js +779 -0
  12. package/lib/deploy/function/cam-preflight.js +173 -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 +107 -0
  21. package/lib/env/index.js +0 -27
  22. package/lib/environment.js +11 -0
  23. package/lib/function/index.js +30 -4
  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 +161 -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 +47 -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 +433 -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/index.d.ts +2 -0
  56. package/types/function/types.d.ts +15 -0
  57. package/types/hosting/index.d.ts +6 -0
  58. package/types/index.d.ts +18 -0
  59. package/types/interfaces/function.interface.d.ts +1 -1
  60. package/types/projectValidator/index.d.ts +38 -0
  61. package/types/projectValidator/types.d.ts +26 -0
  62. package/types/storage/index.d.ts +6 -0
  63. package/types/utils/index.d.ts +13 -0
@@ -0,0 +1,173 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TCR_PUSH_GRANT_URL = exports.IMAGE_PULL_GRANT_URL = exports.REQUIRED_TCR_PUSH_POLICY = exports.TCB_PUSH_ENTERPRISE_IMAGE_ROLE = 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
+ * 镜像部署涉及两个职责不同的服务角色:
11
+ * - SCF_QcsRole:SCF 从个人版或企业版 TCR 拉取镜像;
12
+ * - TCB_QcsRole:CloudApp 云端构建向企业版 TCR 推送镜像。
13
+ *
14
+ * 本模块在部署前探测所需角色与策略;缺失且允许时,只自动绑定各场景明确列入
15
+ * 白名单的预置策略;当前身份无 CAM 权限时,则返回结构化指引和一键授权链接。
16
+ * 模块不会创建角色,也不会绑定白名单之外的策略。
17
+ */
18
+ /** SCF 拉取镜像使用的服务角色 */
19
+ exports.SCF_PULL_IMAGE_ROLE = 'SCF_QcsRole';
20
+ /** SCF 拉取个人版或企业版 TCR 镜像所需的预置策略 */
21
+ exports.REQUIRED_IMAGE_PULL_POLICY = 'QcloudAccessForSCFRoleInPullImage';
22
+ /** CloudApp 云端构建向企业版 TCR 推送镜像使用的服务角色 */
23
+ exports.TCB_PUSH_ENTERPRISE_IMAGE_ROLE = 'TCB_QcsRole';
24
+ /** CloudApp 云端构建向企业版 TCR 推送镜像所需的预置策略 */
25
+ exports.REQUIRED_TCR_PUSH_POLICY = 'QcloudAccessForTCBRoleInTCR';
26
+ /** SCF 拉取镜像的一键授权链接 */
27
+ exports.IMAGE_PULL_GRANT_URL = 'https://console.cloud.tencent.com/cam/role/grant' +
28
+ `?roleName=${exports.SCF_PULL_IMAGE_ROLE}&policyName=${exports.REQUIRED_IMAGE_PULL_POLICY}` +
29
+ '&principal=eyJzZXJ2aWNlIjoic2NmLnFjbG91ZC5jb20ifQ%3D%3D';
30
+ /** 企业版 TCR 云端构建推送的一键授权链接 */
31
+ exports.TCR_PUSH_GRANT_URL = 'https://console.cloud.tencent.com/cam/role/grant' +
32
+ `?roleName=${exports.TCB_PUSH_ENTERPRISE_IMAGE_ROLE}&policyName=${exports.REQUIRED_TCR_PUSH_POLICY}` +
33
+ '&principal=eyJzZXJ2aWNlIjoidGNiLmNsb3VkLnRlbmNlbnQuY29tIn0%3D';
34
+ /** 判断错误是否为 CAM 权限不足 */
35
+ function isCamPermissionError(error) {
36
+ const code = typeof (error === null || error === void 0 ? void 0 : error.code) === 'string' ? error.code : '';
37
+ const message = typeof (error === null || error === void 0 ? void 0 : error.message) === 'string' ? error.message : '';
38
+ const haystack = `${code} ${message}`.toLowerCase();
39
+ return (haystack.includes('unauthorizedoperation') ||
40
+ haystack.includes('no permission') ||
41
+ haystack.includes('not authorized') ||
42
+ haystack.includes('cam_unauthorized'));
43
+ }
44
+ /** 判断错误是否为角色不存在 */
45
+ function isRoleNotFoundError(error) {
46
+ const code = typeof (error === null || error === void 0 ? void 0 : error.code) === 'string' ? error.code : '';
47
+ const message = typeof (error === null || error === void 0 ? void 0 : error.message) === 'string' ? error.message : '';
48
+ const haystack = `${code} ${message}`.toLowerCase();
49
+ return haystack.includes('notfound') || haystack.includes('not exist') || haystack.includes('rolenotexist');
50
+ }
51
+ function pass(id, summary) {
52
+ return { id, status: 'pass', summary };
53
+ }
54
+ function warn(id, summary, remediation) {
55
+ return { id, status: 'warn', summary, remediation };
56
+ }
57
+ function fail(id, summary, options = {}) {
58
+ return { id, status: 'fail', summary, remediation: options.remediation, detail: options.detail };
59
+ }
60
+ function buildReport(checks) {
61
+ return {
62
+ scope: 'cloud',
63
+ strategy: 'image',
64
+ ready: !checks.some(item => item.status === 'fail'),
65
+ checks
66
+ };
67
+ }
68
+ /** image/local/cloud 最终都需要 SCF 从镜像仓库拉取镜像 */
69
+ function needsImagePullAuth(config) {
70
+ return (config.type === 'HTTP' &&
71
+ (config.buildStrategy === 'image' ||
72
+ config.buildStrategy === 'local' ||
73
+ config.buildStrategy === 'cloud'));
74
+ }
75
+ /** 只有企业版 cloud 策略需要 CloudApp 向企业版 TCR 推送镜像 */
76
+ function needsEnterpriseTcrPushAuth(config) {
77
+ return (config.type === 'HTTP' &&
78
+ config.buildStrategy === 'cloud' &&
79
+ config.imageType === 'enterprise');
80
+ }
81
+ function resolveRequirements(config) {
82
+ const requirements = [];
83
+ if (needsImagePullAuth(config)) {
84
+ requirements.push({
85
+ roleName: exports.SCF_PULL_IMAGE_ROLE,
86
+ policyName: exports.REQUIRED_IMAGE_PULL_POLICY,
87
+ purpose: 'SCF 拉取 TCR 镜像',
88
+ grantUrl: exports.IMAGE_PULL_GRANT_URL,
89
+ roleMissingCode: types_1.FUNCTION_DEPLOY_ERROR.PREFLIGHT_IMAGE_PULL_ROLE_MISSING,
90
+ policyMissingCode: types_1.FUNCTION_DEPLOY_ERROR.PREFLIGHT_IMAGE_PULL_POLICY_MISSING,
91
+ grantedCode: types_1.FUNCTION_DEPLOY_ERROR.PREFLIGHT_IMAGE_PULL_AUTH_GRANTED,
92
+ unverifiableCode: types_1.FUNCTION_DEPLOY_ERROR.PREFLIGHT_IMAGE_PULL_AUTH_UNVERIFIABLE
93
+ });
94
+ }
95
+ if (needsEnterpriseTcrPushAuth(config)) {
96
+ requirements.push({
97
+ roleName: exports.TCB_PUSH_ENTERPRISE_IMAGE_ROLE,
98
+ policyName: exports.REQUIRED_TCR_PUSH_POLICY,
99
+ purpose: 'CloudApp 云端构建推送企业版 TCR 镜像',
100
+ grantUrl: exports.TCR_PUSH_GRANT_URL,
101
+ roleMissingCode: types_1.FUNCTION_DEPLOY_ERROR.PREFLIGHT_TCR_PUSH_ROLE_MISSING,
102
+ policyMissingCode: types_1.FUNCTION_DEPLOY_ERROR.PREFLIGHT_TCR_PUSH_POLICY_MISSING,
103
+ grantedCode: types_1.FUNCTION_DEPLOY_ERROR.PREFLIGHT_TCR_PUSH_AUTH_GRANTED,
104
+ unverifiableCode: types_1.FUNCTION_DEPLOY_ERROR.PREFLIGHT_TCR_PUSH_AUTH_UNVERIFIABLE
105
+ });
106
+ }
107
+ return requirements;
108
+ }
109
+ function unverifiableCheck(requirement) {
110
+ return warn(requirement.unverifiableCode, `当前身份无 CAM 权限,无法自动校验或补齐 ${requirement.roleName} 的${requirement.purpose}授权`, `若部署失败,请用具备 CAM 权限的账号访问一键授权链接完成授权:${requirement.grantUrl}`);
111
+ }
112
+ async function checkRequirement(service, requirement, autoGrant) {
113
+ if (!service) {
114
+ return unverifiableCheck(requirement);
115
+ }
116
+ let roleDetail;
117
+ try {
118
+ roleDetail = await service.getRoleWithPolicies(requirement.roleName);
119
+ }
120
+ catch (error) {
121
+ if (isCamPermissionError(error)) {
122
+ return unverifiableCheck(requirement);
123
+ }
124
+ if (isRoleNotFoundError(error)) {
125
+ return fail(requirement.roleMissingCode, `缺少服务角色 ${requirement.roleName}`, {
126
+ remediation: `请访问一键授权链接创建角色并授予 ${requirement.policyName}:${requirement.grantUrl}`
127
+ });
128
+ }
129
+ return unverifiableCheck(requirement);
130
+ }
131
+ const attached = roleDetail.attachedPolicyNames || [];
132
+ if (attached.includes(requirement.policyName)) {
133
+ return pass(requirement.policyMissingCode, `${requirement.roleName} 已具备${requirement.purpose}策略 ${requirement.policyName}`);
134
+ }
135
+ if (!autoGrant) {
136
+ return fail(requirement.policyMissingCode, `${requirement.roleName} 缺少${requirement.purpose}策略 ${requirement.policyName}`, {
137
+ remediation: `已关闭自动授权(autoGrant=false)。请访问一键授权链接手动授予:${requirement.grantUrl}`
138
+ });
139
+ }
140
+ try {
141
+ await service.attachPolicyByName(requirement.roleName, requirement.policyName);
142
+ return pass(requirement.grantedCode, `已自动为 ${requirement.roleName} 绑定${requirement.purpose}策略 ${requirement.policyName}`);
143
+ }
144
+ catch (error) {
145
+ if (isCamPermissionError(error)) {
146
+ return fail(types_1.FUNCTION_DEPLOY_ERROR.PREFLIGHT_CAM_PERMISSION_DENIED, `当前身份无 CAM 授权权限,无法自动为 ${requirement.roleName} 补齐${requirement.purpose}策略`, {
147
+ remediation: `请用具备 CAM 权限的账号(如主账号)访问一键授权链接完成授权:${requirement.grantUrl}`,
148
+ detail: error === null || error === void 0 ? void 0 : error.message
149
+ });
150
+ }
151
+ return fail(requirement.policyMissingCode, `自动绑定策略 ${requirement.policyName} 失败`, {
152
+ remediation: `请访问一键授权链接手动授予:${requirement.grantUrl}`,
153
+ detail: error === null || error === void 0 ? void 0 : error.message
154
+ });
155
+ }
156
+ }
157
+ /**
158
+ * 执行镜像仓库授权 Preflight。
159
+ *
160
+ * 企业版 cloud 策略会同时检查 SCF 拉取权限和 TCB 云端构建推送权限;
161
+ * 其他镜像策略只检查与自身相关的授权,不会额外要求 TCB 推送权限。
162
+ */
163
+ async function runImagePullAuthPreflight(service, config, autoGrant = true) {
164
+ const requirements = resolveRequirements(config);
165
+ if (requirements.length === 0) {
166
+ return buildReport([]);
167
+ }
168
+ const checks = [];
169
+ for (const requirement of requirements) {
170
+ checks.push(await checkRequirement(service, requirement, autoGrant));
171
+ }
172
+ return buildReport(checks);
173
+ }
@@ -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
+ }