Skip to content

Commit 4b0b78b

Browse files
wangpand0508brycewwang
andauthored
feat: serverless.yml支持函数url开启cors配置 (#304)
Co-authored-by: brycewwang <[email protected]>
1 parent aa4de34 commit 4b0b78b

File tree

10 files changed

+169
-36
lines changed

10 files changed

+169
-36
lines changed

.npmignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,7 @@ prettier.config.js
1313
release.config.js
1414
commitlint.config.js
1515
.editorconfig
16+
src
17+
*.ts
18+
tsconfig.json
19+
babel.config.js

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
"@semantic-release/npm": "^7.0.4",
6868
"@semantic-release/release-notes-generator": "^9.0.1",
6969
"@types/axios": "^0.14.0",
70+
"@types/lodash": "^4.17.17",
7071
"@types/react-grid-layout": "^1.1.2",
7172
"@types/uuid": "^8.3.1",
7273
"@typescript-eslint/eslint-plugin": "^4.14.0",
@@ -94,6 +95,7 @@
9495
"camelcase": "^6.2.0",
9596
"cos-nodejs-sdk-v5": "^2.9.20",
9697
"dayjs": "^1.10.4",
98+
"lodash": "^4.17.21",
9799
"moment": "^2.29.1",
98100
"tencent-cloud-sdk": "^1.0.5",
99101
"type-fest": "^0.20.2",

src/modules/scf/apis.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const ACTIONS = [
1010
'GetFunctionEventInvokeConfig',
1111
'UpdateFunctionEventInvokeConfig',
1212
'CreateTrigger',
13+
'UpdateTrigger',
1314
'DeleteTrigger',
1415
'PublishVersion',
1516
'ListVersionByFunction',

src/modules/triggers/base.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,4 +116,4 @@ export const TRIGGER_STATUS_MAP = {
116116
0: 'CLOSE',
117117
};
118118

119-
export const CAN_UPDATE_TRIGGER = ['apigw', 'cls', 'mps', 'clb'];
119+
export const CAN_UPDATE_TRIGGER = ['apigw', 'cls', 'mps', 'clb','http','ckafka'];

src/modules/triggers/ckafka.ts

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { CapiCredentials, RegionType } from './../interface';
2-
import { TriggerInputs, CkafkaTriggerInputsParams, CreateTriggerReq } from './interface';
2+
import { TriggerInputs, CkafkaTriggerInputsParams, CreateTriggerReq,TriggerAction } from './interface';
33
import Scf from '../scf';
4-
import { TRIGGER_STATUS_MAP } from './base';
4+
import { TRIGGER_STATUS_MAP } from './base';
55
import { TriggerManager } from './manager';
6+
import { getScfTriggerByName } from './utils';
67

78
export default class CkafkaTrigger {
89
credentials: CapiCredentials;
@@ -29,20 +30,22 @@ export default class CkafkaTrigger {
2930
return `${triggerInputs.Type}-${triggerInputs.TriggerName}-${desc}-${Enable}-${triggerInputs.Qualifier}`;
3031
}
3132

32-
formatInputs({ inputs }: { inputs: TriggerInputs<CkafkaTriggerInputsParams> }) {
33+
formatInputs({ inputs,action = 'CreateTrigger'}: { inputs: TriggerInputs<CkafkaTriggerInputsParams>,action?: TriggerAction }) {
3334
const { parameters } = inputs;
35+
const triggerName = parameters?.name || `${parameters?.instanceId}-${parameters?.topic}`;
3436
const triggerInputs: CreateTriggerReq = {
35-
Action: 'CreateTrigger',
37+
Action: action,
3638
FunctionName: inputs.functionName,
3739
Namespace: inputs.namespace,
3840
Type: 'ckafka',
3941
Qualifier: parameters?.qualifier ?? '$DEFAULT',
40-
TriggerName: `${parameters?.name}-${parameters?.topic}`,
42+
TriggerName: triggerName,
4143
TriggerDesc: JSON.stringify({
4244
maxMsgNum: parameters?.maxMsgNum ?? 100,
4345
offset: parameters?.offset ?? 'latest',
4446
retry: parameters?.retry ?? 10000,
4547
timeOut: parameters?.timeout ?? 60,
48+
consumerGroupName: parameters?.consumerGroupName ?? '',
4649
}),
4750
Enable: parameters?.enable ? 'OPEN' : 'CLOSE',
4851
};
@@ -57,16 +60,35 @@ export default class CkafkaTrigger {
5760
async create({
5861
scf,
5962
inputs,
63+
region
6064
}: {
6165
scf: Scf | TriggerManager;
6266
region: RegionType;
6367
inputs: TriggerInputs<CkafkaTriggerInputsParams>;
6468
}) {
65-
const { triggerInputs } = this.formatInputs({ inputs });
66-
console.log(`Creating ${triggerInputs.Type} trigger ${triggerInputs.TriggerName}`);
67-
const { TriggerInfo } = await scf.request(triggerInputs as any);
68-
TriggerInfo.Qualifier = TriggerInfo.Qualifier || triggerInputs.Qualifier;
69-
return TriggerInfo;
69+
// 查询当前触发器是否已存在
70+
const existTrigger = await getScfTriggerByName({ scf, region, inputs });
71+
// 更新触发器
72+
if (existTrigger) {
73+
const { triggerInputs } = this.formatInputs({ inputs, action: 'UpdateTrigger' });
74+
console.log(`${triggerInputs.Type} trigger ${triggerInputs.TriggerName} is exist`)
75+
console.log(`Updating ${triggerInputs.Type} trigger ${triggerInputs.TriggerName}`);
76+
try {
77+
// 更新触发器
78+
await scf.request(triggerInputs as any);
79+
// 更新成功后,查询最新的触发器信息
80+
const trigger = await getScfTriggerByName({ scf, region, inputs });
81+
return trigger;
82+
} catch (error) {
83+
return {}
84+
}
85+
} else { // 创建触发器
86+
const { triggerInputs } = this.formatInputs({ inputs });
87+
console.log(`Creating ${triggerInputs.Type} trigger ${triggerInputs.TriggerName}`);
88+
const { TriggerInfo } = await scf.request(triggerInputs as any);
89+
TriggerInfo.Qualifier = TriggerInfo.Qualifier || triggerInputs.Qualifier;
90+
return TriggerInfo;
91+
}
7092
}
7193
async delete({ scf, inputs }: { scf: Scf; inputs: TriggerInputs }) {
7294
console.log(`Removing ${inputs.type} trigger ${inputs.triggerName}`);

src/modules/triggers/http.ts

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import Scf from '../scf';
22
import { TriggerManager } from './manager';
33
import { CapiCredentials, RegionType } from './../interface';
44
import BaseTrigger from './base';
5-
import { HttpTriggerInputsParams, TriggerInputs, CreateTriggerReq } from './interface';
5+
import { HttpTriggerInputsParams, TriggerInputs, CreateTriggerReq,TriggerAction } from './interface';
6+
import { caseForObject } from '../../utils';
7+
import { getScfTriggerByName } from './utils';
68

79
export default class HttpTrigger extends BaseTrigger<HttpTriggerInputsParams> {
810
credentials: CapiCredentials;
@@ -15,31 +17,33 @@ export default class HttpTrigger extends BaseTrigger<HttpTriggerInputsParams> {
1517
}
1618

1719
getKey(triggerInputs: CreateTriggerReq) {
18-
const triggerDesc = JSON.parse(triggerInputs.TriggerDesc!);
19-
const tempDest = JSON.stringify({
20-
authType: triggerDesc?.AuthType,
21-
enableIntranet: triggerDesc?.NetConfig?.EnableIntranet,
22-
enableExtranet: triggerDesc?.NetConfig?.EnableExtranet,
23-
});
24-
return `http-${tempDest}-${triggerInputs.Qualifier}`;
20+
return `http-${triggerInputs?.TriggerName}`;
2521
}
2622

27-
formatInputs({ inputs }: { region: RegionType; inputs: TriggerInputs<HttpTriggerInputsParams> }) {
23+
formatInputs({ inputs,action = 'CreateTrigger' }: { region: RegionType; inputs: TriggerInputs<HttpTriggerInputsParams> ,action?: TriggerAction}) {
2824
const { parameters } = inputs;
25+
const triggerName = parameters?.name || 'url-trigger';
26+
const { origins,headers,methods,exposeHeaders } = parameters?.corsConfig || {}
2927
const triggerInputs: CreateTriggerReq = {
30-
Action: 'CreateTrigger',
28+
Action: action,
3129
FunctionName: inputs.functionName,
3230
Namespace: inputs.namespace,
33-
3431
Type: 'http',
3532
Qualifier: parameters?.qualifier || '$DEFAULT',
36-
TriggerName: parameters?.name || 'url-trigger',
33+
TriggerName: triggerName,
3734
TriggerDesc: JSON.stringify({
3835
AuthType: parameters?.authType || 'NONE',
3936
NetConfig: {
4037
EnableIntranet: parameters?.netConfig?.enableIntranet ?? false,
4138
EnableExtranet: parameters?.netConfig?.enableExtranet ?? false,
4239
},
40+
CorsConfig: parameters?.corsConfig ? caseForObject({
41+
...parameters?.corsConfig,
42+
origins: typeof origins === 'string' ? origins?.split(',') : origins,
43+
methods: typeof methods === 'string' ? methods?.split(',') : methods,
44+
headers: typeof headers === 'string' ? headers?.split(',') : headers,
45+
exposeHeaders: typeof exposeHeaders === 'string' ? exposeHeaders?.split(',') : exposeHeaders,
46+
},'upper') : undefined
4347
}),
4448
Enable: 'OPEN',
4549
};
@@ -61,12 +65,29 @@ export default class HttpTrigger extends BaseTrigger<HttpTriggerInputsParams> {
6165
region: RegionType;
6266
inputs: TriggerInputs<HttpTriggerInputsParams>;
6367
}) {
64-
const { triggerInputs } = this.formatInputs({ region, inputs });
65-
console.log(`Creating ${triggerInputs.Type} trigger ${triggerInputs.TriggerName}`);
66-
const { TriggerInfo } = await scf.request(triggerInputs);
67-
TriggerInfo.Qualifier = TriggerInfo.Qualifier || triggerInputs.Qualifier;
68-
69-
return TriggerInfo;
68+
// 查询当前触发器是否已存在
69+
const existTrigger = await getScfTriggerByName({ scf, region, inputs });
70+
// 更新触发器
71+
if (existTrigger) {
72+
const { triggerInputs } = this.formatInputs({ region, inputs, action: 'UpdateTrigger' });
73+
console.log(`${triggerInputs.Type} trigger ${triggerInputs.TriggerName} is exist`)
74+
console.log(`Updating ${triggerInputs.Type} trigger ${triggerInputs.TriggerName}`);
75+
try {
76+
// 更新触发器
77+
await scf.request(triggerInputs);
78+
// 更新成功后,查询最新的触发器信息
79+
const trigger = await getScfTriggerByName({ scf, region, inputs });
80+
return trigger;
81+
} catch (error) {
82+
return {}
83+
}
84+
} else { // 创建触发器
85+
const { triggerInputs } = this.formatInputs({ region, inputs });
86+
console.log(`Creating ${triggerInputs.Type} trigger ${triggerInputs.TriggerName}`);
87+
const { TriggerInfo } = await scf.request(triggerInputs);
88+
TriggerInfo.Qualifier = TriggerInfo.Qualifier || triggerInputs.Qualifier;
89+
return TriggerInfo;
90+
}
7091
}
7192

7293
async delete({

src/modules/triggers/interface/index.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ApigwDeployInputs, ApiEndpoint } from '../../apigw/interface';
22
import { TagInput } from '../../interface';
33

4+
export type TriggerAction = 'CreateTrigger' | 'UpdateTrigger'
45
export interface ApigwTriggerRemoveScfTriggerInputs {
56
serviceId: string;
67
apiId: string;
@@ -42,7 +43,7 @@ export interface ApigwTriggerInputsParams extends ApigwDeployInputs {
4243

4344
export type TriggerType = 'scf' | 'timer' | string;
4445
export interface CreateTriggerReq {
45-
Action?: 'CreateTrigger';
46+
Action?: TriggerAction;
4647
ResourceId?: string;
4748
FunctionName?: string;
4849
Namespace?: string;
@@ -57,11 +58,13 @@ export interface CreateTriggerReq {
5758
export interface CkafkaTriggerInputsParams extends TriggerInputsParams {
5859
qualifier?: string;
5960
name?: string;
60-
topic?: string;
61+
instanceId?: string; //ckafka实例ID
62+
topic?: string; //ckafka主题名称
6163
maxMsgNum?: number;
6264
offset?: number;
6365
retry?: number;
6466
timeout?: number;
67+
consumerGroupName?: string;
6568
enable?: boolean;
6669
}
6770

@@ -100,6 +103,15 @@ export interface HttpTriggerInputsParams {
100103
enableIntranet?: boolean;
101104
enableExtranet?: boolean;
102105
};
106+
corsConfig: {
107+
enable: boolean
108+
origins: Array<string> | string
109+
methods: Array<string> | string
110+
headers: Array<string> | string
111+
exposeHeaders: Array<string> | string
112+
credentials: boolean
113+
maxAge: number
114+
}
103115
}
104116

105117
export interface MpsTriggerInputsParams {
@@ -120,6 +132,7 @@ export interface TimerTriggerInputsParams {
120132

121133
export interface TriggerInputs<P extends TriggerInputsParams = TriggerInputsParams> {
122134
functionName: string;
135+
Type?: string; // 兼容scf组件触发器类型字段
123136
type?: string;
124137
triggerDesc?: string;
125138
triggerName?: string;

src/modules/triggers/manager.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -277,11 +277,17 @@ export class TriggerManager {
277277
// 1. 删除老的无法更新的触发器
278278
for (let i = 0, len = deleteList.length; i < len; i++) {
279279
const trigger = deleteList[i];
280-
await this.removeTrigger({
281-
name,
282-
namespace,
283-
trigger,
284-
});
280+
// 若类型触发器不支持编辑,需要先删除,后重新创建;
281+
if (!CAN_UPDATE_TRIGGER.includes(trigger?.Type)) {
282+
await this.removeTrigger({
283+
name,
284+
namespace,
285+
trigger,
286+
});
287+
} else {
288+
// 若触发器类型支持编辑,直接跳过删除
289+
continue;
290+
}
285291
}
286292

287293
// 2. 创建新的触发器

src/modules/triggers/utils/index.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { RegionType } from "../../interface";
2+
import Scf from "../../scf";
3+
import { CkafkaTriggerInputsParams, HttpTriggerInputsParams, TriggerDetail, TriggerInputs } from "../interface";
4+
import { TriggerManager } from "../manager";
5+
6+
// 获取函数下指定类型以及指定触发器名称的触发器
7+
export async function getScfTriggerByName({
8+
scf,
9+
inputs
10+
}: {
11+
scf: Scf | TriggerManager;
12+
region: RegionType;
13+
inputs: TriggerInputs<HttpTriggerInputsParams | CkafkaTriggerInputsParams>;
14+
}): Promise<TriggerDetail> {
15+
const filters = [
16+
{
17+
Name: 'Type',
18+
Values: [inputs?.type || inputs?.Type]
19+
}
20+
]
21+
if (inputs?.parameters?.name) {
22+
filters.push({
23+
Name: 'TriggerName',
24+
Values: [inputs?.parameters?.name]
25+
})
26+
}
27+
if (inputs?.parameters?.qualifier) {
28+
filters.push({
29+
Name: 'Qualifier',
30+
Values: [inputs?.parameters?.qualifier?.toString()]
31+
})
32+
}
33+
const response = await scf.request({
34+
Action: 'ListTriggers',
35+
FunctionName: inputs?.functionName,
36+
Namespace: inputs?.namespace,
37+
Limit: 1000,
38+
Filters: filters
39+
});
40+
return response?.Triggers?.[0];
41+
}

src/utils/index.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import camelCase from 'camelcase';
44
import { PascalCase } from 'type-fest';
55
import { CamelCasedProps, PascalCasedProps } from '../modules/interface';
66
import crypto from 'crypto';
7+
import _ from 'lodash';
8+
79

810
// TODO: 将一些库换成 lodash
911

@@ -314,3 +316,24 @@ export const getYunTiApiUrl = (): string => {
314316
const url = `${apiUrl}?api_key=${apiKey}&api_ts=${timeStamp}&api_sign=${apiSign}`;
315317
return url;
316318
};
319+
320+
321+
322+
/**
323+
* 首字母转换大小写
324+
* @param {*} obj
325+
* @param {*} type
326+
* @returns
327+
*/
328+
export function caseForObject(obj: object,type : 'upper' | 'lower') {
329+
if (!_.isPlainObject(obj)) return obj;
330+
return _.transform(obj, (result: { [key: string]: any }, value, key) => {
331+
let newKey:string = '';
332+
if (type === 'upper') {
333+
newKey = _.upperFirst(key)
334+
} else {
335+
newKey = _.lowerFirst(key);
336+
}
337+
result[newKey] = _.isPlainObject(value) ? caseForObject(value,type) : value;
338+
}, {});
339+
}

0 commit comments

Comments
 (0)