@geek-fun/serverlessinsight 0.6.6 → 0.6.8

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/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geek-fun/serverlessinsight",
3
- "version": "0.6.6",
3
+ "version": "0.6.8",
4
4
  "description": "Full life cycle cross providers serverless application management for your fast-growing business.",
5
5
  "homepage": "https://serverlessinsight.geekfun.club",
6
6
  "main": "dist/src/index.js",
@@ -23,7 +23,10 @@ const plan = async (options) => {
23
23
  const iac = (0, parser_1.revalYaml)(iacLocation, context);
24
24
  // Store IAC in context for access by all functions
25
25
  (0, common_1.setIac)(iac);
26
- common_1.logger.info(lang_1.lang.__('GENERATING_PLAN_FOR_SCF'));
26
+ const providerDisplayName = iac.provider.name === common_1.ProviderEnum.ALIYUN
27
+ ? lang_1.lang.__('PROVIDER_ALIYUN')
28
+ : lang_1.lang.__('PROVIDER_TENCENT');
29
+ common_1.logger.info(lang_1.lang.__('GENERATING_PLAN_FOR_PROVIDER', { provider: providerDisplayName }));
27
30
  const backend = (0, stateBackend_1.createStateBackend)(iac.backend, context);
28
31
  let planResult;
29
32
  if (iac.provider.name === common_1.ProviderEnum.TENCENT) {
@@ -78,115 +78,211 @@ const isDuplicateSecurityGroupRuleError = (error) => {
78
78
  ]);
79
79
  return duplicateCodes.has(error.code);
80
80
  };
81
- const createEcsOperations = (ecsClient, context) => ({
82
- createSecurityGroup: async (securityGroupName, vpcId, ingressRules, egressRules, description) => {
83
- const createRequest = new ecs.CreateSecurityGroupRequest({
84
- regionId: context.region,
85
- securityGroupName,
86
- vpcId,
87
- description: description ?? `ServerlessInsight security group for ${securityGroupName}`,
88
- });
89
- const response = await ecsClient.createSecurityGroup(createRequest);
90
- const securityGroupId = response.body?.securityGroupId;
91
- if (!securityGroupId) {
92
- throw new Error('Failed to create security group');
93
- }
94
- // Add ingress rules
95
- for (const rule of ingressRules) {
96
- let parsedRule;
97
- try {
98
- parsedRule = (0, exports.parseSecurityGroupRule)(rule);
99
- }
100
- catch (error) {
101
- logger_1.logger.warn(`Skipping invalid ingress rule: ${rule}. ${String(error)}`);
102
- continue;
103
- }
104
- const ingressRequest = new ecs.AuthorizeSecurityGroupRequest({
81
+ const createEcsOperations = (ecsClient, context) => {
82
+ const operations = {
83
+ createSecurityGroup: async (securityGroupName, vpcId, ingressRules, egressRules, description) => {
84
+ const createRequest = new ecs.CreateSecurityGroupRequest({
105
85
  regionId: context.region,
106
- securityGroupId,
107
- ipProtocol: parsedRule.protocol.toLowerCase(),
108
- sourceCidrIp: parsedRule.cidr,
109
- portRange: transformPortRange(parsedRule.protocol, parsedRule.portRange),
86
+ securityGroupName,
87
+ vpcId,
88
+ description: description ?? `ServerlessInsight security group for ${securityGroupName}`,
110
89
  });
111
- try {
112
- await ecsClient.authorizeSecurityGroup(ingressRequest);
90
+ const response = await ecsClient.createSecurityGroup(createRequest);
91
+ const securityGroupId = response.body?.securityGroupId;
92
+ if (!securityGroupId) {
93
+ throw new Error('Failed to create security group');
113
94
  }
114
- catch (error) {
115
- if (isDuplicateSecurityGroupRuleError(error)) {
116
- logger_1.logger.debug(`Ingress rule already exists, skipping: ${rule}`);
95
+ // Add ingress rules
96
+ for (const rule of ingressRules) {
97
+ let parsedRule;
98
+ try {
99
+ parsedRule = (0, exports.parseSecurityGroupRule)(rule);
100
+ }
101
+ catch (error) {
102
+ logger_1.logger.warn(`Skipping invalid ingress rule: ${rule}. ${String(error)}`);
117
103
  continue;
118
104
  }
119
- logger_1.logger.warn(`Failed to add ingress rule: ${rule}. ${String(error)}`);
105
+ const ingressRequest = new ecs.AuthorizeSecurityGroupRequest({
106
+ regionId: context.region,
107
+ securityGroupId,
108
+ ipProtocol: parsedRule.protocol.toLowerCase(),
109
+ sourceCidrIp: parsedRule.cidr,
110
+ portRange: transformPortRange(parsedRule.protocol, parsedRule.portRange),
111
+ });
112
+ try {
113
+ await ecsClient.authorizeSecurityGroup(ingressRequest);
114
+ }
115
+ catch (error) {
116
+ if (isDuplicateSecurityGroupRuleError(error)) {
117
+ logger_1.logger.debug(`Ingress rule already exists, skipping: ${rule}`);
118
+ continue;
119
+ }
120
+ logger_1.logger.warn(`Failed to add ingress rule: ${rule}. ${String(error)}`);
121
+ }
120
122
  }
121
- }
122
- // Add egress rules
123
- for (const rule of egressRules) {
124
- let parsedRule;
123
+ // Add egress rules
124
+ for (const rule of egressRules) {
125
+ let parsedRule;
126
+ try {
127
+ parsedRule = (0, exports.parseSecurityGroupRule)(rule);
128
+ }
129
+ catch (error) {
130
+ logger_1.logger.warn(`Skipping invalid egress rule: ${rule}. ${String(error)}`);
131
+ continue;
132
+ }
133
+ const egressRequest = new ecs.AuthorizeSecurityGroupEgressRequest({
134
+ regionId: context.region,
135
+ securityGroupId,
136
+ ipProtocol: parsedRule.protocol.toLowerCase(),
137
+ destCidrIp: parsedRule.cidr,
138
+ portRange: transformPortRange(parsedRule.protocol, parsedRule.portRange),
139
+ });
140
+ try {
141
+ await ecsClient.authorizeSecurityGroupEgress(egressRequest);
142
+ }
143
+ catch (error) {
144
+ if (isDuplicateSecurityGroupRuleError(error)) {
145
+ logger_1.logger.debug(`Egress rule already exists, skipping: ${rule}`);
146
+ continue;
147
+ }
148
+ logger_1.logger.warn(`Failed to add egress rule: ${rule}. ${String(error)}`);
149
+ }
150
+ }
151
+ const sg = await operations.getSecurityGroup(securityGroupId);
152
+ if (!sg) {
153
+ return {
154
+ securityGroupId,
155
+ securityGroupName,
156
+ vpcId,
157
+ description,
158
+ };
159
+ }
160
+ return sg;
161
+ },
162
+ getSecurityGroupRules: async (securityGroupId) => {
163
+ const ingressRules = [];
164
+ const egressRules = [];
125
165
  try {
126
- parsedRule = (0, exports.parseSecurityGroupRule)(rule);
166
+ const request = new ecs.DescribeSecurityGroupAttributeRequest({
167
+ regionId: context.region,
168
+ securityGroupId,
169
+ direction: 'ingress',
170
+ });
171
+ const response = await ecsClient.describeSecurityGroupAttribute(request);
172
+ if (response?.body?.permissions?.permission) {
173
+ for (const perm of response.body.permissions.permission) {
174
+ ingressRules.push({
175
+ direction: 'ingress',
176
+ ipProtocol: perm.ipProtocol ?? '',
177
+ portRange: perm.portRange ?? '',
178
+ sourceCidrIp: perm.sourceCidrIp,
179
+ priority: perm.priority,
180
+ policy: perm.policy,
181
+ description: perm.description,
182
+ ruleId: perm.ruleId,
183
+ });
184
+ }
185
+ }
127
186
  }
128
187
  catch (error) {
129
- logger_1.logger.warn(`Skipping invalid egress rule: ${rule}. ${String(error)}`);
130
- continue;
188
+ logger_1.logger.debug(`Failed to get ingress rules: ${String(error)}`);
131
189
  }
132
- const egressRequest = new ecs.AuthorizeSecurityGroupEgressRequest({
133
- regionId: context.region,
134
- securityGroupId,
135
- ipProtocol: parsedRule.protocol.toLowerCase(),
136
- destCidrIp: parsedRule.cidr,
137
- portRange: transformPortRange(parsedRule.protocol, parsedRule.portRange),
138
- });
139
190
  try {
140
- await ecsClient.authorizeSecurityGroupEgress(egressRequest);
191
+ const request = new ecs.DescribeSecurityGroupAttributeRequest({
192
+ regionId: context.region,
193
+ securityGroupId,
194
+ direction: 'egress',
195
+ });
196
+ const response = await ecsClient.describeSecurityGroupAttribute(request);
197
+ if (response?.body?.permissions?.permission) {
198
+ for (const perm of response.body.permissions.permission) {
199
+ egressRules.push({
200
+ direction: 'egress',
201
+ ipProtocol: perm.ipProtocol ?? '',
202
+ portRange: perm.portRange ?? '',
203
+ destCidrIp: perm.destCidrIp,
204
+ priority: perm.priority,
205
+ policy: perm.policy,
206
+ description: perm.description,
207
+ ruleId: perm.ruleId,
208
+ });
209
+ }
210
+ }
141
211
  }
142
212
  catch (error) {
143
- if (isDuplicateSecurityGroupRuleError(error)) {
144
- logger_1.logger.debug(`Egress rule already exists, skipping: ${rule}`);
145
- continue;
213
+ logger_1.logger.debug(`Failed to get egress rules: ${String(error)}`);
214
+ }
215
+ return { ingressRules, egressRules };
216
+ },
217
+ getSecurityGroup: async (securityGroupId) => {
218
+ try {
219
+ const request = new ecs.DescribeSecurityGroupsRequest({
220
+ regionId: context.region,
221
+ securityGroupId,
222
+ });
223
+ const response = await ecsClient.describeSecurityGroups(request);
224
+ if (!response ||
225
+ !response.body ||
226
+ !response.body.securityGroups ||
227
+ !response.body.securityGroups.securityGroup ||
228
+ response.body.securityGroups.securityGroup.length === 0) {
229
+ return null;
146
230
  }
147
- logger_1.logger.warn(`Failed to add egress rule: ${rule}. ${String(error)}`);
231
+ const sg = response.body.securityGroups.securityGroup[0];
232
+ const rules = await operations.getSecurityGroupRules(securityGroupId);
233
+ return {
234
+ securityGroupId: sg.securityGroupId ?? securityGroupId,
235
+ securityGroupName: sg.securityGroupName,
236
+ vpcId: sg.vpcId,
237
+ description: sg.description,
238
+ createTime: sg.creationTime,
239
+ ingressRules: rules.ingressRules,
240
+ egressRules: rules.egressRules,
241
+ };
148
242
  }
149
- }
150
- return {
151
- securityGroupId,
152
- securityGroupName,
153
- vpcId,
154
- description,
155
- };
156
- },
157
- getSecurityGroup: async (securityGroupId) => {
158
- try {
159
- const request = new ecs.DescribeSecurityGroupsRequest({
243
+ catch {
244
+ return null;
245
+ }
246
+ },
247
+ getSecurityGroupByName: async (securityGroupName, vpcId) => {
248
+ try {
249
+ const request = new ecs.DescribeSecurityGroupsRequest({
250
+ regionId: context.region,
251
+ securityGroupName,
252
+ vpcId,
253
+ });
254
+ const response = await ecsClient.describeSecurityGroups(request);
255
+ if (!response ||
256
+ !response.body ||
257
+ !response.body.securityGroups ||
258
+ !response.body.securityGroups.securityGroup ||
259
+ response.body.securityGroups.securityGroup.length === 0) {
260
+ return null;
261
+ }
262
+ const sg = response.body.securityGroups.securityGroup[0];
263
+ const rules = await operations.getSecurityGroupRules(sg.securityGroupId);
264
+ return {
265
+ securityGroupId: sg.securityGroupId,
266
+ securityGroupName: sg.securityGroupName,
267
+ vpcId: sg.vpcId,
268
+ description: sg.description,
269
+ createTime: sg.creationTime,
270
+ ingressRules: rules.ingressRules,
271
+ egressRules: rules.egressRules,
272
+ };
273
+ }
274
+ catch {
275
+ return null;
276
+ }
277
+ },
278
+ deleteSecurityGroup: async (securityGroupId) => {
279
+ const request = new ecs.DeleteSecurityGroupRequest({
160
280
  regionId: context.region,
161
281
  securityGroupId,
162
282
  });
163
- const response = await ecsClient.describeSecurityGroups(request);
164
- if (!response ||
165
- !response.body ||
166
- !response.body.securityGroups ||
167
- !response.body.securityGroups.securityGroup ||
168
- response.body.securityGroups.securityGroup.length === 0) {
169
- return null;
170
- }
171
- const sg = response.body.securityGroups.securityGroup[0];
172
- return {
173
- securityGroupId: sg.securityGroupId ?? securityGroupId,
174
- securityGroupName: sg.securityGroupName,
175
- vpcId: sg.vpcId,
176
- description: sg.description,
177
- createTime: sg.creationTime,
178
- };
179
- }
180
- catch {
181
- return null;
182
- }
183
- },
184
- deleteSecurityGroup: async (securityGroupId) => {
185
- const request = new ecs.DeleteSecurityGroupRequest({
186
- regionId: context.region,
187
- securityGroupId,
188
- });
189
- await ecsClient.deleteSecurityGroup(request);
190
- },
191
- });
283
+ await ecsClient.deleteSecurityGroup(request);
284
+ },
285
+ };
286
+ return operations;
287
+ };
192
288
  exports.createEcsOperations = createEcsOperations;
@@ -97,6 +97,13 @@ const createNasOperations = (nasClient) => {
97
97
  priority: 1,
98
98
  });
99
99
  await nasClient.createAccessRule(request);
100
+ return {
101
+ accessGroupName,
102
+ sourceCidrIp,
103
+ rwAccessType: 'RDWR',
104
+ userAccessType: 'no_squash',
105
+ priority: 1,
106
+ };
100
107
  },
101
108
  createFileSystem: async (storageClass, functionName) => {
102
109
  const { fileSystemType, storageType } = storageClassMap[storageClass];
@@ -149,6 +156,11 @@ const createNasOperations = (nasClient) => {
149
156
  protocolType: fs.protocolType,
150
157
  status: fs.status,
151
158
  createTime: fs.createTime,
159
+ description: fs.description,
160
+ zoneId: fs.zoneId,
161
+ capacity: fs.capacity,
162
+ encrypted: fs.encrypted,
163
+ mountTargetCount: fs.mountTargetCount,
152
164
  };
153
165
  }
154
166
  catch {
@@ -131,6 +131,7 @@ const createRamOperations = (ramClient) => {
131
131
  throw error;
132
132
  }
133
133
  }
134
+ return policyName;
134
135
  };
135
136
  return {
136
137
  createRole: async (roleName, trustedServices, description) => {
@@ -148,9 +149,12 @@ const createRamOperations = (ramClient) => {
148
149
  arn: response.body?.role?.arn,
149
150
  description: response.body?.role?.description,
150
151
  createDate: response.body?.role?.createDate,
152
+ updateDate: response.body?.role?.updateDate,
153
+ maxSessionDuration: response.body?.role?.maxSessionDuration,
154
+ assumeRolePolicyDocument: assumeRolePolicy,
151
155
  };
152
- await attachRolePolicyForFc(roleName);
153
- return roleInfo;
156
+ const policyName = await attachRolePolicyForFc(roleName);
157
+ return { ...roleInfo, policyName };
154
158
  }
155
159
  catch (error) {
156
160
  if (error &&
@@ -166,13 +170,17 @@ const createRamOperations = (ramClient) => {
166
170
  newAssumeRolePolicyDocument: assumeRolePolicy,
167
171
  });
168
172
  await ramClient.updateRole(updateRequest);
169
- await attachRolePolicyForFc(roleName);
173
+ const policyName = await attachRolePolicyForFc(roleName);
170
174
  return {
171
175
  roleName,
172
176
  roleId: getResponse.body?.role?.roleId,
173
177
  arn: getResponse.body?.role?.arn,
174
178
  description: getResponse.body?.role?.description,
175
179
  createDate: getResponse.body?.role?.createDate,
180
+ updateDate: getResponse.body?.role?.updateDate,
181
+ maxSessionDuration: getResponse.body?.role?.maxSessionDuration,
182
+ assumeRolePolicyDocument: assumeRolePolicy,
183
+ policyName,
176
184
  };
177
185
  }
178
186
  catch (recoveryError) {
@@ -197,6 +205,10 @@ const createRamOperations = (ramClient) => {
197
205
  arn: getResponse.body?.role?.arn,
198
206
  description: getResponse.body?.role?.description,
199
207
  createDate: getResponse.body?.role?.createDate,
208
+ updateDate: getResponse.body?.role?.updateDate,
209
+ maxSessionDuration: getResponse.body?.role?.maxSessionDuration,
210
+ assumeRolePolicyDocument: assumeRolePolicy,
211
+ policyName: `${roleName}-policy`,
200
212
  };
201
213
  const updateRequest = new ram.UpdateRoleRequest({
202
214
  roleName,
@@ -231,6 +243,10 @@ const createRamOperations = (ramClient) => {
231
243
  arn: response.body.role.arn,
232
244
  description: response.body.role.description,
233
245
  createDate: response.body.role.createDate,
246
+ updateDate: response.body.role.updateDate,
247
+ maxSessionDuration: response.body.role.maxSessionDuration,
248
+ assumeRolePolicyDocument: response.body.role.assumeRolePolicyDocument,
249
+ policyName: `${roleName}-policy`,
234
250
  };
235
251
  }
236
252
  catch (error) {
@@ -37,14 +37,18 @@ exports.getIamInfo = void 0;
37
37
  const ims20190815_1 = __importStar(require("@alicloud/ims20190815")), ims20190815 = ims20190815_1;
38
38
  const openApi = __importStar(require("@alicloud/openapi-client"));
39
39
  const providerEnum_1 = require("./providerEnum");
40
+ const constants_1 = require("./constants");
40
41
  const getIamInfo = async (context) => {
41
42
  if (context.provider !== providerEnum_1.ProviderEnum.ALIYUN) {
42
43
  return undefined;
43
44
  }
44
- const imsClient = new ims20190815_1.default(new openApi.Config({
45
+ const imsConfig = new openApi.Config({
45
46
  accessKeyId: context.accessKeyId,
46
47
  accessKeySecret: context.accessKeySecret,
47
- }));
48
+ });
49
+ imsConfig.connectTimeout = constants_1.ALIYUN_FC3_CONNECT_TIMEOUT_MS;
50
+ imsConfig.readTimeout = constants_1.ALIYUN_FC3_READ_TIMEOUT_MS;
51
+ const imsClient = new ims20190815_1.default(imsConfig);
48
52
  const { body } = await imsClient.getUser(new ims20190815.GetUserRequest({ userAccessKeyId: context.accessKeyId }));
49
53
  return body?.user
50
54
  ? {
@@ -79,6 +79,9 @@ exports.en = {
79
79
  // Plan command messages
80
80
  PLAN_COMMAND_TENCENT_ONLY: 'Plan command is currently only supported for Tencent provider',
81
81
  GENERATING_PLAN_FOR_SCF: 'Generating plan for Tencent SCF resources...',
82
+ GENERATING_PLAN_FOR_PROVIDER: 'Generating plan for {{provider}} resources...',
83
+ PROVIDER_ALIYUN: 'Alibaba Cloud',
84
+ PROVIDER_TENCENT: 'Tencent Cloud',
82
85
  DEPLOYMENT_PLAN: 'DEPLOYMENT PLAN',
83
86
  NO_CHANGES_INFRASTRUCTURE_UP_TO_DATE: 'No changes. Infrastructure is up to date.',
84
87
  CREATE: 'CREATE',
@@ -365,6 +368,7 @@ exports.en = {
365
368
  CREATING_SLS_INDEX: 'Creating SLS index for: {{logstoreName}}',
366
369
  CREATING_RAM_ROLE: 'Creating RAM role: {{roleName}}',
367
370
  CREATING_SECURITY_GROUP: 'Creating security group: {{sgName}}',
371
+ SECURITY_GROUP_NOT_FOUND: 'Security group "{{sgName}}" not found in VPC "{{vpcId}}"',
368
372
  CREATING_NAS_ACCESS_GROUP: 'Creating NAS access group: {{accessGroupName}}',
369
373
  CREATING_NAS_ACCESS_RULE: 'Creating NAS access rule for: {{accessGroupName}}',
370
374
  CREATING_NAS_FILE_SYSTEM: 'Creating NAS file system for: {{name}}',
@@ -79,6 +79,9 @@ exports.zhCN = {
79
79
  // Plan command messages
80
80
  PLAN_COMMAND_TENCENT_ONLY: 'Plan 命令目前仅支持腾讯云提供商',
81
81
  GENERATING_PLAN_FOR_SCF: '正在为腾讯云 SCF 资源生成计划...',
82
+ GENERATING_PLAN_FOR_PROVIDER: '正在为{{provider}}资源生成计划...',
83
+ PROVIDER_ALIYUN: '阿里云',
84
+ PROVIDER_TENCENT: '腾讯云',
82
85
  DEPLOYMENT_PLAN: '部署计划',
83
86
  NO_CHANGES_INFRASTRUCTURE_UP_TO_DATE: '无变更。基础设施已是最新状态。',
84
87
  CREATE: '创建',
@@ -362,6 +365,7 @@ exports.zhCN = {
362
365
  CREATING_SLS_INDEX: '正在为 SLS 日志库创建索引: {{logstoreName}}',
363
366
  CREATING_RAM_ROLE: '正在创建 RAM 角色: {{roleName}}',
364
367
  CREATING_SECURITY_GROUP: '正在创建安全组: {{sgName}}',
368
+ SECURITY_GROUP_NOT_FOUND: '在 VPC "{{vpcId}}" 中未找到安全组 "{{sgName}}"',
365
369
  CREATING_NAS_ACCESS_GROUP: '正在创建 NAS 权限组: {{accessGroupName}}',
366
370
  CREATING_NAS_ACCESS_RULE: '正在为 NAS 权限组创建访问规则: {{accessGroupName}}',
367
371
  CREATING_NAS_FILE_SYSTEM: '正在为 {{name}} 创建 NAS 文件系统',
@@ -43,7 +43,9 @@ const generateApigwPlan = async (context, state, events, serviceName) => {
43
43
  domain: event.domain
44
44
  ? {
45
45
  domainName: event.domain.domain_name,
46
- hasCertificate: !!(event.domain.certificate_body || event.domain.certificate_id),
46
+ certificateId: event.domain.certificate_id ?? null,
47
+ certificateBody: event.domain.certificate_body ?? null,
48
+ certificatePrivateKey: event.domain.certificate_private_key ? '(managed)' : null,
47
49
  protocol: event.domain.protocol ?? null,
48
50
  }
49
51
  : null,
@@ -133,7 +133,9 @@ const createApigwResource = async (context, event, serviceName, roleArn, state)
133
133
  domain: event.domain
134
134
  ? {
135
135
  domainName: event.domain.domain_name,
136
- hasCertificate: !!(event.domain.certificate_body || event.domain.certificate_id),
136
+ certificateId: event.domain.certificate_id ?? null,
137
+ certificateBody: event.domain.certificate_body ?? null,
138
+ certificatePrivateKey: event.domain.certificate_private_key ? '(managed)' : null,
137
139
  protocol: event.domain.protocol ?? null,
138
140
  }
139
141
  : null,
@@ -173,7 +175,9 @@ const createApigwResource = async (context, event, serviceName, roleArn, state)
173
175
  domain: event.domain
174
176
  ? {
175
177
  domainName: event.domain.domain_name,
176
- hasCertificate: !!(event.domain.certificate_body || event.domain.certificate_id),
178
+ certificateId: event.domain.certificate_id ?? null,
179
+ certificateBody: event.domain.certificate_body ?? null,
180
+ certificatePrivateKey: event.domain.certificate_private_key ? '(managed)' : null,
177
181
  protocol: event.domain.protocol ?? null,
178
182
  }
179
183
  : null,
@@ -209,7 +213,9 @@ const createApigwResource = async (context, event, serviceName, roleArn, state)
209
213
  domain: event.domain
210
214
  ? {
211
215
  domainName: event.domain.domain_name,
212
- hasCertificate: !!(event.domain.certificate_body || event.domain.certificate_id),
216
+ certificateId: event.domain.certificate_id ?? null,
217
+ certificateBody: event.domain.certificate_body ?? null,
218
+ certificatePrivateKey: event.domain.certificate_private_key ? '(managed)' : null,
213
219
  protocol: event.domain.protocol ?? null,
214
220
  }
215
221
  : null,
@@ -318,7 +324,9 @@ const updateApigwResource = async (context, event, serviceName, roleArn, state)
318
324
  domain: event.domain
319
325
  ? {
320
326
  domainName: event.domain.domain_name,
321
- hasCertificate: !!(event.domain.certificate_body || event.domain.certificate_id),
327
+ certificateId: event.domain.certificate_id ?? null,
328
+ certificateBody: event.domain.certificate_body ?? null,
329
+ certificatePrivateKey: event.domain.certificate_private_key ? '(managed)' : null,
322
330
  protocol: event.domain.protocol ?? null,
323
331
  }
324
332
  : null,
@@ -4,6 +4,32 @@ exports.generateFunctionPlan = void 0;
4
4
  const common_1 = require("../../common");
5
5
  const aliyunClient_1 = require("../../common/aliyunClient");
6
6
  const fc3Types_1 = require("./fc3Types");
7
+ const lang_1 = require("../../lang");
8
+ const isSecurityGroupId = (value) => value.startsWith('sg-');
9
+ const resolveSecurityGroupId = async (context, securityGroupName, vpcId) => {
10
+ if (isSecurityGroupId(securityGroupName)) {
11
+ return securityGroupName;
12
+ }
13
+ const client = (0, aliyunClient_1.createAliyunClient)(context);
14
+ const sg = await client.ecs.getSecurityGroupByName(securityGroupName, vpcId);
15
+ if (!sg) {
16
+ throw new Error(lang_1.lang.__('SECURITY_GROUP_NOT_FOUND', { sgName: securityGroupName, vpcId: vpcId ?? 'default' }));
17
+ }
18
+ return sg.securityGroupId;
19
+ };
20
+ const resolveVpcConfigSecurityGroup = async (context, config) => {
21
+ if (!config.vpcConfig?.securityGroupId) {
22
+ return config;
23
+ }
24
+ const securityGroupId = await resolveSecurityGroupId(context, config.vpcConfig.securityGroupId, config.vpcConfig.vpcId);
25
+ return {
26
+ ...config,
27
+ vpcConfig: {
28
+ ...config.vpcConfig,
29
+ securityGroupId,
30
+ },
31
+ };
32
+ };
7
33
  const planFunctionDeletion = (logicalId, definition) => ({
8
34
  logicalId,
9
35
  action: 'delete',
@@ -22,7 +48,8 @@ const generateFunctionPlan = async (context, state, functions) => {
22
48
  const functionItems = await Promise.all(functions.map(async (fn) => {
23
49
  const logicalId = `functions.${fn.key}`;
24
50
  const currentState = (0, common_1.getResource)(state, logicalId);
25
- const config = (0, fc3Types_1.functionToFc3Config)(fn);
51
+ const rawConfig = (0, fc3Types_1.functionToFc3Config)(fn);
52
+ const config = await resolveVpcConfigSecurityGroup(context, rawConfig);
26
53
  const codePath = fn.code.path;
27
54
  const desiredCodeHash = (0, common_1.computeFileHash)(codePath);
28
55
  const desiredDefinition = (0, fc3Types_1.extractFc3Definition)(config, desiredCodeHash);
@@ -247,13 +247,13 @@ const createDependentResources = async (context, fn, serviceName, existingInstan
247
247
  const accessGroupName = `${fn.name}-${context.stage}-nas-access-${mountPath}`;
248
248
  logger_1.logger.info(lang_1.lang.__('CREATING_NAS_ACCESS_GROUP', { accessGroupName }));
249
249
  const accessGroup = await client.nas.createAccessGroup(accessGroupName);
250
+ logger_1.logger.info(lang_1.lang.__('CREATING_NAS_ACCESS_RULE', { accessGroupName }));
251
+ const accessRule = await client.nas.createAccessRule(accessGroupName, '10.0.0.0/8');
250
252
  instances.push({
251
253
  type: 'ALIYUN_NAS_ACCESS_GROUP',
252
254
  id: accessGroupName,
253
- attributes: { ...accessGroup },
255
+ attributes: { ...accessGroup, accessRules: [accessRule] },
254
256
  });
255
- logger_1.logger.info(lang_1.lang.__('CREATING_NAS_ACCESS_RULE', { accessGroupName }));
256
- await client.nas.createAccessRule(accessGroupName, '10.0.0.0/8');
257
257
  logger_1.logger.info(lang_1.lang.__('CREATING_NAS_FILE_SYSTEM', { name: fn.name }));
258
258
  const fileSystem = await client.nas.createFileSystem(nasItem.storage_class, fn.name);
259
259
  instances.push({
@@ -50,6 +50,23 @@ const functionToFc3Config = (fn) => {
50
50
  securityGroupId: fn.network.security_group.name,
51
51
  };
52
52
  }
53
+ if (fn.storage?.nas && fn.storage.nas.length > 0) {
54
+ config.nasConfig = {
55
+ userId: 10003,
56
+ groupId: 10003,
57
+ mountPoints: fn.storage.nas.map((nas) => ({
58
+ serverAddr: nas.storage_class,
59
+ mountDir: nas.mount_path,
60
+ enableTls: false,
61
+ })),
62
+ };
63
+ }
64
+ if (fn.log !== undefined) {
65
+ config.logConfig = {
66
+ enableRequestMetrics: fn.log,
67
+ enableInstanceMetrics: fn.log,
68
+ };
69
+ }
53
70
  return config;
54
71
  };
55
72
  exports.functionToFc3Config = functionToFc3Config;
@@ -65,6 +82,8 @@ const extractFc3Definition = (config, codeHash) => {
65
82
  vpcConfig: config.vpcConfig ?? null,
66
83
  gpuConfig: config.gpuConfig ?? null,
67
84
  customContainerConfig: config.customContainerConfig ?? null,
85
+ nasConfig: config.nasConfig ?? null,
86
+ logConfig: config.logConfig ?? null,
68
87
  codeHash,
69
88
  };
70
89
  };
@@ -46,14 +46,16 @@ const generateBucketPlan = async (context, state, buckets) => {
46
46
  };
47
47
  }
48
48
  const currentDefinition = currentState.definition || {};
49
- const definitionChanged = !(0, hashUtils_1.attributesEqual)(currentDefinition, desiredDefinition);
50
- if (definitionChanged) {
49
+ const { domainBound, ...comparableDefinition } = currentDefinition;
50
+ const definitionChanged = !(0, hashUtils_1.attributesEqual)(comparableDefinition, desiredDefinition);
51
+ const domainBindingPending = domainBound === false;
52
+ if (definitionChanged || domainBindingPending) {
51
53
  return {
52
54
  logicalId,
53
55
  action: 'update',
54
56
  resourceType: 'ALIYUN_OSS_BUCKET',
55
57
  changes: { before: currentDefinition, after: desiredDefinition },
56
- drifted: true,
58
+ ...(definitionChanged ? { drifted: true } : {}),
57
59
  };
58
60
  }
59
61
  return { logicalId, action: 'noop', resourceType: 'ALIYUN_OSS_BUCKET' };
@@ -132,14 +132,16 @@ const createBucketResource = async (context, bucket, state) => {
132
132
  if (!bucketInfo) {
133
133
  throw new Error(`Failed to refresh state for bucket: ${config.bucketName}`);
134
134
  }
135
- const definition = (0, ossTypes_1.extractOssBucketDefinition)(config);
136
135
  const sid = (0, common_1.buildSid)('aliyun', 'oss', context.stage, config.bucketName);
137
136
  const logicalId = `buckets.${bucket.key}`;
138
137
  const instances = [buildOssInstanceFromProvider(bucketInfo, sid)];
139
138
  const partialResourceState = {
140
139
  mode: 'managed',
141
140
  region: context.region,
142
- definition,
141
+ definition: {
142
+ ...(0, ossTypes_1.extractOssBucketDefinition)(config),
143
+ ...(bucket.website?.domain != null ? { domainBound: null } : {}),
144
+ },
143
145
  instances,
144
146
  lastUpdated: new Date().toISOString(),
145
147
  };
@@ -196,7 +198,12 @@ const createBucketResource = async (context, bucket, state) => {
196
198
  const finalResourceState = {
197
199
  mode: 'managed',
198
200
  region: context.region,
199
- definition,
201
+ definition: {
202
+ ...(0, ossTypes_1.extractOssBucketDefinition)(config),
203
+ ...(bucket.website?.domain != null
204
+ ? { domainBound: cnameInfo?.bucketCnameBound ?? null }
205
+ : {}),
206
+ },
200
207
  instances,
201
208
  lastUpdated: new Date().toISOString(),
202
209
  };
@@ -225,12 +232,12 @@ const updateBucketResource = async (context, bucket, state) => {
225
232
  if (!bucketInfo) {
226
233
  throw new Error(`Failed to refresh state for bucket: ${config.bucketName}`);
227
234
  }
228
- const definition = (0, ossTypes_1.extractOssBucketDefinition)(config);
229
235
  const sid = (0, common_1.buildSid)('aliyun', 'oss', context.stage, config.bucketName);
230
236
  const logicalId = `buckets.${bucket.key}`;
231
237
  const instances = [buildOssInstanceFromProvider(bucketInfo, sid)];
232
238
  const existingState = state.resources[logicalId];
233
239
  const existingDnsInstance = existingState?.instances?.find((i) => i.type === types_1.ResourceTypeEnum.ALIYUN_OSS_DNS_CNAME);
240
+ let cnameInfo;
234
241
  if (bucket.website?.domain) {
235
242
  const domainChanged = existingDnsInstance?.domain !== bucket.website.domain;
236
243
  if (domainChanged && existingDnsInstance) {
@@ -247,7 +254,7 @@ const updateBucketResource = async (context, bucket, state) => {
247
254
  bucketName: config.bucketName,
248
255
  }));
249
256
  }
250
- const cnameInfo = await client.oss.bindCustomDomain(config.bucketName, bucket.website.domain, certificate);
257
+ cnameInfo = await client.oss.bindCustomDomain(config.bucketName, bucket.website.domain, certificate);
251
258
  if (cnameInfo) {
252
259
  const instanceId = cnameInfo.dnsRecordId ?? bucket.website.domain;
253
260
  const dnsInstance = {
@@ -273,7 +280,12 @@ const updateBucketResource = async (context, bucket, state) => {
273
280
  const resourceState = {
274
281
  mode: 'managed',
275
282
  region: context.region,
276
- definition,
283
+ definition: {
284
+ ...(0, ossTypes_1.extractOssBucketDefinition)(config),
285
+ ...(bucket.website?.domain != null
286
+ ? { domainBound: cnameInfo?.bucketCnameBound ?? null }
287
+ : {}),
288
+ },
277
289
  instances,
278
290
  lastUpdated: new Date().toISOString(),
279
291
  };
@@ -30,6 +30,27 @@ const bucketToOssBucketConfig = (bucket) => {
30
30
  if (bucket.website?.domain) {
31
31
  config.domain = bucket.website.domain;
32
32
  }
33
+ if (bucket.website?.domain_certificate_id) {
34
+ config.domainCertificateId = bucket.website.domain_certificate_id;
35
+ }
36
+ if (bucket.website?.domain_certificate_body) {
37
+ config.domainCertificateBody = bucket.website.domain_certificate_body;
38
+ }
39
+ if (bucket.website?.domain_certificate_private_key) {
40
+ config.domainCertificatePrivateKey = bucket.website.domain_certificate_private_key;
41
+ }
42
+ if (bucket.website?.domain_protocol) {
43
+ config.domainProtocol = bucket.website.domain_protocol;
44
+ }
45
+ if (bucket.versioning?.status) {
46
+ config.versioningStatus = bucket.versioning.status;
47
+ }
48
+ if (bucket.security?.sse_algorithm) {
49
+ config.sseAlgorithm = bucket.security.sse_algorithm;
50
+ }
51
+ if (bucket.security?.sse_kms_master_key_id) {
52
+ config.sseKmsMasterKeyId = bucket.security.sse_kms_master_key_id;
53
+ }
33
54
  return config;
34
55
  };
35
56
  exports.bucketToOssBucketConfig = bucketToOssBucketConfig;
@@ -45,6 +66,13 @@ const extractOssBucketDefinition = (config) => {
45
66
  : {},
46
67
  storageClass: config.storageClass ?? null,
47
68
  domain: config.domain ?? null,
69
+ domainCertificateId: config.domainCertificateId ?? null,
70
+ domainCertificateBody: config.domainCertificateBody ?? null,
71
+ domainCertificatePrivateKey: config.domainCertificatePrivateKey ? '(managed)' : null,
72
+ domainProtocol: config.domainProtocol ?? null,
73
+ versioningStatus: config.versioningStatus ?? null,
74
+ sseAlgorithm: config.sseAlgorithm ?? null,
75
+ sseKmsMasterKeyId: config.sseKmsMasterKeyId ?? null,
48
76
  };
49
77
  };
50
78
  exports.extractOssBucketDefinition = extractOssBucketDefinition;
@@ -43,7 +43,12 @@ const tableToTableStoreConfig = (table) => {
43
43
  instanceName: table.collection,
44
44
  tableName: table.name,
45
45
  clusterType,
46
+ description: table.desc,
46
47
  primaryKey,
48
+ attributes: table.attributes.map((attr) => ({
49
+ name: attr.name,
50
+ type: attr.type,
51
+ })),
47
52
  network: table.network,
48
53
  };
49
54
  // Add reserved throughput if specified
@@ -55,6 +60,12 @@ const tableToTableStoreConfig = (table) => {
55
60
  },
56
61
  };
57
62
  }
63
+ if (table.throughput?.onDemand) {
64
+ config.onDemandThroughput = {
65
+ read: table.throughput.onDemand.read,
66
+ write: table.throughput.onDemand.write,
67
+ };
68
+ }
58
69
  // Default table options
59
70
  config.tableOptions = {
60
71
  timeToLive: -1, // -1 means never expire
@@ -68,8 +79,11 @@ const extractTableStoreDefinition = (config) => {
68
79
  instanceName: config.instanceName,
69
80
  tableName: config.tableName,
70
81
  clusterType: config.clusterType,
82
+ description: config.description ?? null,
71
83
  primaryKey: config.primaryKey,
84
+ attributes: config.attributes,
72
85
  reservedThroughput: config.reservedThroughput ?? null,
86
+ onDemandThroughput: config.onDemandThroughput ?? null,
73
87
  tableOptions: config.tableOptions ?? null,
74
88
  network: config.network ?? null,
75
89
  };
@@ -26,6 +26,30 @@ const bucketToCosBucketConfig = (bucket, region) => {
26
26
  Key: bucket.website.error_page,
27
27
  };
28
28
  }
29
+ if (bucket.website.domain) {
30
+ config.Domain = bucket.website.domain;
31
+ }
32
+ if (bucket.website.domain_certificate_id) {
33
+ config.DomainCertificateId = bucket.website.domain_certificate_id;
34
+ }
35
+ if (bucket.website.domain_certificate_body) {
36
+ config.DomainCertificateBody = bucket.website.domain_certificate_body;
37
+ }
38
+ if (bucket.website.domain_certificate_private_key) {
39
+ config.DomainCertificatePrivateKey = bucket.website.domain_certificate_private_key;
40
+ }
41
+ if (bucket.website.domain_protocol) {
42
+ config.DomainProtocol = bucket.website.domain_protocol;
43
+ }
44
+ }
45
+ if (bucket.versioning?.status) {
46
+ config.VersioningStatus = bucket.versioning.status;
47
+ }
48
+ if (bucket.security?.sse_algorithm) {
49
+ config.SseAlgorithm = bucket.security.sse_algorithm;
50
+ }
51
+ if (bucket.security?.sse_kms_master_key_id) {
52
+ config.SseKmsMasterKeyId = bucket.security.sse_kms_master_key_id;
29
53
  }
30
54
  return config;
31
55
  };
@@ -41,6 +65,14 @@ const extractCosBucketDefinition = (config) => {
41
65
  errorDocument: config.WebsiteConfiguration.ErrorDocument?.Key ?? null,
42
66
  }
43
67
  : {},
68
+ domain: config.Domain ?? null,
69
+ domainCertificateId: config.DomainCertificateId ?? null,
70
+ domainCertificateBody: config.DomainCertificateBody ?? null,
71
+ domainCertificatePrivateKey: config.DomainCertificatePrivateKey ? '(managed)' : null,
72
+ domainProtocol: config.DomainProtocol ?? null,
73
+ versioningStatus: config.VersioningStatus ?? null,
74
+ sseAlgorithm: config.SseAlgorithm ?? null,
75
+ sseKmsMasterKeyId: config.SseKmsMasterKeyId ?? null,
44
76
  };
45
77
  };
46
78
  exports.extractCosBucketDefinition = extractCosBucketDefinition;
@@ -18,6 +18,31 @@ const functionToScfConfig = (fn) => {
18
18
  })),
19
19
  };
20
20
  }
21
+ if (fn.network) {
22
+ config.VpcConfig = {
23
+ VpcId: fn.network.vpc_id,
24
+ SubnetId: fn.network.subnet_ids[0],
25
+ };
26
+ }
27
+ if (fn.storage?.disk) {
28
+ config.DiskSize = fn.storage.disk;
29
+ }
30
+ if (fn.storage?.nas && fn.storage.nas.length > 0) {
31
+ config.CfsConfig = {
32
+ CfsInsList: fn.storage.nas.map((nas) => ({
33
+ LocalMountDir: nas.mount_path,
34
+ RemoteMountDir: '/',
35
+ })),
36
+ };
37
+ }
38
+ if (fn.gpu) {
39
+ config.UseGpu = 'TRUE';
40
+ }
41
+ if (fn.container) {
42
+ config.ImageConfig = {
43
+ ImageUri: fn.container.image,
44
+ };
45
+ }
21
46
  return config;
22
47
  };
23
48
  exports.functionToScfConfig = functionToScfConfig;
@@ -31,6 +56,11 @@ const extractScfDefinition = (config, codeHash) => {
31
56
  timeout: config.Timeout,
32
57
  environment: envMap,
33
58
  codeHash,
59
+ vpcConfig: config.VpcConfig ?? null,
60
+ diskSize: config.DiskSize ?? null,
61
+ cfsConfig: config.CfsConfig ?? null,
62
+ useGpu: config.UseGpu ?? null,
63
+ imageConfig: config.ImageConfig ?? null,
34
64
  };
35
65
  };
36
66
  exports.extractScfDefinition = extractScfDefinition;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geek-fun/serverlessinsight",
3
- "version": "0.6.6",
3
+ "version": "0.6.8",
4
4
  "description": "Full life cycle cross providers serverless application management for your fast-growing business.",
5
5
  "homepage": "https://serverlessinsight.geekfun.club",
6
6
  "main": "dist/src/index.js",