Skip to content

Commit 20e2cc6

Browse files
authored
feat(scf): support http type (#227)
* feat(scf): support http type * chore: fix test config
1 parent 4af7afa commit 20e2cc6

File tree

11 files changed

+248
-17
lines changed

11 files changed

+248
-17
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,5 @@ lib
2323
env.js
2424
package-lock.json
2525
yarn.lock
26+
27+
__tests__/apigw.list.test.ts

__tests__/scf.http.test.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { sleep } from '@ygkit/request';
2+
import { ScfDeployInputs } from '../src/modules/scf/interface';
3+
import { Scf } from '../src';
4+
5+
describe('Scf - http', () => {
6+
const credentials = {
7+
SecretId: process.env.TENCENT_SECRET_ID,
8+
SecretKey: process.env.TENCENT_SECRET_KEY,
9+
};
10+
const scf = new Scf(credentials, 'ap-chongqing');
11+
12+
const triggers = {
13+
apigw: {
14+
apigw: {
15+
parameters: {
16+
serviceName: 'serverless_test',
17+
protocols: ['http', 'https'],
18+
endpoints: [
19+
{
20+
path: '/',
21+
method: 'ANY',
22+
function: {
23+
type: 'web',
24+
},
25+
},
26+
],
27+
},
28+
},
29+
},
30+
};
31+
32+
const events = Object.entries(triggers).map(([, value]) => value);
33+
34+
const inputs: ScfDeployInputs = {
35+
// name: `serverless-test-http-${Date.now()}`,
36+
name: `serverless-test-http`,
37+
code: {
38+
bucket: 'test-chongqing',
39+
object: 'express_http.zip',
40+
},
41+
type: 'web',
42+
namespace: 'default',
43+
runtime: 'Nodejs12.16',
44+
region: 'ap-chongqing',
45+
description: 'Created by Serverless',
46+
memorySize: 256,
47+
timeout: 20,
48+
tags: {
49+
test: 'test',
50+
},
51+
environment: {
52+
variables: {
53+
TEST: 'value',
54+
},
55+
},
56+
events,
57+
};
58+
59+
let outputs;
60+
61+
test('deploy', async () => {
62+
outputs = await scf.deploy(inputs);
63+
expect(outputs.FunctionName).toBe(inputs.name);
64+
expect(outputs.Type).toBe('HTTP');
65+
expect(outputs.Qualifier).toBe('$LATEST');
66+
expect(outputs.Description).toBe('Created by Serverless');
67+
expect(outputs.Timeout).toBe(inputs.timeout);
68+
expect(outputs.MemorySize).toBe(inputs.memorySize);
69+
expect(outputs.Runtime).toBe(inputs.runtime);
70+
});
71+
test('update', async () => {
72+
await sleep(3000);
73+
outputs = await scf.deploy(inputs);
74+
expect(outputs.FunctionName).toBe(inputs.name);
75+
expect(outputs.Type).toBe('HTTP');
76+
expect(outputs.Qualifier).toBe('$LATEST');
77+
expect(outputs.Description).toBe('Created by Serverless');
78+
expect(outputs.Timeout).toBe(inputs.timeout);
79+
expect(outputs.MemorySize).toBe(inputs.memorySize);
80+
expect(outputs.Runtime).toBe(inputs.runtime);
81+
});
82+
test('remove', async () => {
83+
const res = await scf.remove({
84+
functionName: inputs.name,
85+
...outputs,
86+
});
87+
expect(res).toEqual(true);
88+
});
89+
});

jest.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const config = {
1515
'/__tests__/cdn.test.ts',
1616
'/__tests__/apigw.custom-domains.test.ts',
1717
'/__tests__/scf.sp.test.ts', // 专门用来验证测试小地域功能发布测试
18+
'/__tests__/scf.http.test.ts', // 专门用来验证测试 HTTP 直通
1819
'/__tests__/triggers/mps.test.ts',
1920
'/__tests__/trigger.manager.test.ts',
2021
],

src/modules/apigw/apis.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ const ACTIONS = [
3434
'DescribeServiceSubDomainMappings',
3535
'BindSubDomain',
3636
'UnBindSubDomain',
37+
'DescribeServicesStatus',
38+
'DescribeServiceEnvironmentList',
3739
] as const;
3840

3941
export type ActionType = typeof ACTIONS[number];

src/modules/apigw/entities/api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,7 @@ export default class ApiEntity {
461461
);
462462
}
463463
apiInputs.serviceScfFunctionName = endpoint.function.functionName;
464+
apiInputs.serviceScfFunctionType = endpoint.function.functionType;
464465
apiInputs.serviceScfFunctionNamespace = endpoint.function.functionNamespace || 'default';
465466
apiInputs.serviceScfIsIntegratedResponse = endpoint.function.isIntegratedResponse
466467
? true

src/modules/apigw/entities/service.ts

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,28 @@ export default class ServiceEntity {
3939
return result as never;
4040
}
4141

42+
/**
43+
* 获取 API 网关列表
44+
* @param options 参数
45+
* @returns 网关列表
46+
*/
47+
async list(options?: { offset?: number; limit?: number }) {
48+
options = {
49+
...{ limit: 10, offset: 0 },
50+
...(options || {}),
51+
};
52+
try {
53+
const res: { TotalCount: number; ServiceSet: any[] } = await this.request({
54+
Action: 'DescribeServicesStatus',
55+
Offset: options.offset,
56+
Limit: options.limit,
57+
});
58+
return res.ServiceSet || [];
59+
} catch (e) {
60+
return [];
61+
}
62+
}
63+
4264
async getById(serviceId: string) {
4365
try {
4466
const detail: Detail = await this.request({
@@ -52,6 +74,112 @@ export default class ServiceEntity {
5274
}
5375
}
5476

77+
async removeApiUsagePlan(ServiceId: string) {
78+
const { ApiUsagePlanList = [] } = await this.request({
79+
Action: 'DescribeApiUsagePlan',
80+
ServiceId,
81+
});
82+
83+
for (let i = 0; i < ApiUsagePlanList.length; i++) {
84+
const { UsagePlanId, Environment, ApiId } = ApiUsagePlanList[i];
85+
console.log(`APIGW - Removing api usage plan: ${UsagePlanId}`);
86+
const { AccessKeyList = [] } = await this.request({
87+
Action: 'DescribeUsagePlanSecretIds',
88+
UsagePlanId: UsagePlanId,
89+
Limit: 100,
90+
});
91+
92+
const AccessKeyIds = AccessKeyList.map((item: { SecretId: string }) => item.SecretId);
93+
94+
if (AccessKeyIds && AccessKeyIds.length > 0) {
95+
await this.request({
96+
Action: 'UnBindSecretIds',
97+
UsagePlanId: UsagePlanId,
98+
AccessKeyIds: AccessKeyIds,
99+
});
100+
// delelet all created api key
101+
for (let sIdx = 0; sIdx < AccessKeyIds.length; sIdx++) {
102+
await this.request({
103+
Action: 'DisableApiKey',
104+
AccessKeyId: AccessKeyIds[sIdx],
105+
});
106+
}
107+
}
108+
109+
// unbind environment
110+
await this.request({
111+
Action: 'UnBindEnvironment',
112+
ServiceId,
113+
UsagePlanIds: [UsagePlanId],
114+
Environment: Environment,
115+
BindType: 'API',
116+
ApiIds: [ApiId],
117+
});
118+
119+
await this.request({
120+
Action: 'DeleteUsagePlan',
121+
UsagePlanId: UsagePlanId,
122+
});
123+
}
124+
}
125+
126+
async removeById(serviceId: string) {
127+
try {
128+
const { ApiIdStatusSet = [] } = await this.request({
129+
Action: 'DescribeApisStatus',
130+
ServiceId: serviceId,
131+
Limit: 100,
132+
});
133+
134+
// remove all apis
135+
for (let i = 0; i < ApiIdStatusSet.length; i++) {
136+
const { ApiId } = ApiIdStatusSet[i];
137+
138+
await this.removeApiUsagePlan(serviceId);
139+
140+
console.log(`APIGW - Removing api: ${ApiId}`);
141+
142+
await this.request({
143+
Action: 'DeleteApi',
144+
ServiceId: serviceId,
145+
ApiId,
146+
});
147+
}
148+
149+
// unrelease service
150+
// get environment list
151+
const { EnvironmentList = [] } = await this.request({
152+
Action: 'DescribeServiceEnvironmentList',
153+
ServiceId: serviceId,
154+
});
155+
156+
for (let i = 0; i < EnvironmentList.length; i++) {
157+
const { EnvironmentName, Status } = EnvironmentList[i];
158+
if (Status === 1) {
159+
try {
160+
console.log(
161+
`APIGW - Unreleasing service: ${serviceId}, environment: ${EnvironmentName}`,
162+
);
163+
await this.request({
164+
Action: 'UnReleaseService',
165+
ServiceId: serviceId,
166+
EnvironmentName,
167+
});
168+
} catch (e) {}
169+
}
170+
}
171+
172+
// delete service
173+
console.log(`APIGW - Removing service: ${serviceId}`);
174+
await this.request({
175+
Action: 'DeleteService',
176+
ServiceId: serviceId,
177+
});
178+
} catch (e) {
179+
console.error(e);
180+
}
181+
}
182+
55183
/** 创建 API 网关服务 */
56184
async create(serviceConf: ApigwCreateServiceInputs): Promise<ApigwCreateOrUpdateServiceOutputs> {
57185
const {

src/modules/scf/entities/scf.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,12 +184,14 @@ export default class ScfEntity extends BaseEntity {
184184

185185
const reqInputs: Partial<typeof reqParams> = reqParams;
186186

187-
// 更新函数接口不能传递一下参数
187+
// 更新函数接口不能传递以下参数
188+
delete reqInputs.Type;
188189
delete reqInputs.Handler;
189190
delete reqInputs.Code;
190191
delete reqInputs.CodeSource;
191192
delete reqInputs.AsyncRunEnable;
192193
delete reqInputs.InstallDependency;
194+
delete reqInputs.DeployMode;
193195

194196
// +++++++++++++++++++++++
195197
// FIXME: 以下是函数绑定层逻辑,当函数有一个层,更新的时候想删除,需要传递参数 Layers 不能为空,必须包含特殊元素:{ LayerName: '', LayerVersion: 0 }

src/modules/scf/interface.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ export interface ScfCreateFunctionInputs {
8383
Namespace?: string;
8484

8585
name: string;
86+
type?: string;
87+
deployMode?: string;
8688
code?: {
8789
bucket: string;
8890
object: string;

src/modules/scf/utils.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ export const formatInputs = (region: RegionType, inputs: ScfCreateFunctionInputs
1818
Timeout?: number;
1919
InitTimeout?: number;
2020
MemorySize?: number;
21+
Type?: 'HTTP' | 'Event';
22+
DeployMode?: 'code' | 'image';
2123
PublicNetConfig?: {
2224
PublicNetStatus: 'ENABLE' | 'DISABLE';
2325
EipConfig: {
@@ -53,7 +55,8 @@ export const formatInputs = (region: RegionType, inputs: ScfCreateFunctionInputs
5355
CosBucketName: inputs.code?.bucket,
5456
CosObjectName: inputs.code?.object,
5557
},
56-
Handler: inputs.handler,
58+
Type: inputs.type === 'web' ? 'HTTP' : 'Event',
59+
DeployMode: inputs.deployMode === 'image' ? 'image' : 'code',
5760
Runtime: inputs.runtime,
5861
Namespace: inputs.namespace || CONFIGS.defaultNamespace,
5962
Timeout: +(inputs.timeout || CONFIGS.defaultTimeout),
@@ -69,6 +72,19 @@ export const formatInputs = (region: RegionType, inputs: ScfCreateFunctionInputs
6972
InstallDependency: inputs.installDependency === true ? 'TRUE' : 'FALSE',
7073
};
7174

75+
// 只有 Event 函数才支持
76+
if (inputs.type !== 'web') {
77+
functionInputs.Handler = inputs.handler;
78+
79+
if (inputs.asyncRunEnable !== undefined) {
80+
functionInputs.AsyncRunEnable = inputs.asyncRunEnable === true ? 'TRUE' : 'FALSE';
81+
}
82+
83+
if (inputs.traceEnable !== undefined) {
84+
functionInputs.TraceEnable = inputs.traceEnable === true ? 'TRUE' : 'FALSE';
85+
}
86+
}
87+
7288
// 非必须参数
7389
if (inputs.role) {
7490
functionInputs.Role = inputs.role;
@@ -143,13 +159,5 @@ export const formatInputs = (region: RegionType, inputs: ScfCreateFunctionInputs
143159
});
144160
}
145161

146-
if (inputs.asyncRunEnable !== undefined) {
147-
functionInputs.AsyncRunEnable = inputs.asyncRunEnable === true ? 'TRUE' : 'FALSE';
148-
}
149-
150-
if (inputs.traceEnable !== undefined) {
151-
functionInputs.TraceEnable = inputs.traceEnable === true ? 'TRUE' : 'FALSE';
152-
}
153-
154162
return functionInputs;
155163
};

src/modules/triggers/apigw.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,8 @@ export default class ApigwTrigger extends BaseTrigger<ApigwTriggerInputsParams>
176176
ep.function.functionName = inputs.functionName;
177177
ep.function.functionNamespace = inputs.namespace || namespace || 'default';
178178
ep.function.functionQualifier = ep.function.functionQualifier ?? '$DEFAULT';
179+
// HTTP - Web 类型,EVENT - 时间类型
180+
ep.function.functionType = ep.function.type === 'web' ? 'HTTP' : 'EVENT';
179181
return ep;
180182
}),
181183
netTypes: parameters?.netTypes,

src/modules/triggers/base.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,7 @@ export default abstract class BaseTrigger<P = TriggerInputsParams> {
3535

3636
abstract getKey(triggerType: CreateTriggerReq): Promise<string> | string;
3737

38-
abstract formatInputs({
39-
region,
40-
inputs,
41-
}: {
42-
region: RegionType;
43-
inputs: TriggerInputs<P>;
44-
}):
38+
abstract formatInputs({ region, inputs }: { region: RegionType; inputs: TriggerInputs<P> }):
4539
| {
4640
triggerKey: string;
4741
triggerInputs: P;

0 commit comments

Comments
 (0)