Skip to content

Commit

Permalink
feat: 部署函数支持添加云梯默认标签(运营部门,运营产品,负责人)
Browse files Browse the repository at this point in the history
  • Loading branch information
wangpand0508 committed Apr 23, 2024
1 parent b525006 commit 9058356
Show file tree
Hide file tree
Showing 12 changed files with 193 additions and 32 deletions.
25 changes: 16 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "tencent-component-toolkit",
"version": "2.24.2",
"version": "2.24.3",
"description": "Tencent component toolkit",
"main": "lib/index.js",
"types": "lib/index.d.ts",
Expand Down Expand Up @@ -66,12 +66,12 @@
"@semantic-release/git": "^9.0.0",
"@semantic-release/npm": "^7.0.4",
"@semantic-release/release-notes-generator": "^9.0.1",
"@types/axios": "^0.14.0",
"@types/react-grid-layout": "^1.1.2",
"@types/uuid": "^8.3.1",
"@typescript-eslint/eslint-plugin": "^4.14.0",
"@typescript-eslint/parser": "^4.14.0",
"@ygkit/secure": "^0.0.3",
"axios": "^0.21.0",
"dotenv": "^8.2.0",
"eslint": "^7.18.0",
"eslint-config-prettier": "^6.10.0",
Expand All @@ -90,6 +90,7 @@
"@types/jest": "^26.0.20",
"@types/node": "^14.14.31",
"@ygkit/request": "^0.1.8",
"axios": "^0.21.0",
"camelcase": "^6.2.0",
"cos-nodejs-sdk-v5": "^2.9.20",
"dayjs": "^1.10.4",
Expand Down
17 changes: 0 additions & 17 deletions src/modules/apigw/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,23 +151,6 @@ export default class Apigw {
outputs.usagePlan = usagePlan;
}

try {
const { tags } = inputs;
if (tags) {
await this.tagClient.deployResourceTags({
tags: tags.map(({ key, value }) => ({ TagKey: key, TagValue: value })),
resourceId: serviceId,
serviceType: ApiServiceType.apigw,
resourcePrefix: 'service',
});
if (tags.length > 0) {
outputs.tags = tags;
}
}
} catch (e) {
console.log(`[TAG] ${e.message}`);
}

// return this.formatApigwOutputs(outputs);
return outputs;
}
Expand Down
1 change: 1 addition & 0 deletions src/modules/cam/apis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const ACTIONS = [
'CreateRole',
'GetRole',
'DeleteRole',
'GetUserAppId',
] as const;

export type ActionType = typeof ACTIONS[number];
Expand Down
15 changes: 15 additions & 0 deletions src/modules/cam/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,4 +112,19 @@ export default class Cam {
async CheckSCFExcuteRole() {
return this.isRoleExist('QCS_SCFExcuteRole');
}

/** 查询用户AppId */
async GetUserAppId(): Promise<{ OwnerUin: string; AppId: string; Uin: string }> {
try {
return this.request({
Action: 'GetUserAppId',
});
} catch (error) {
return {
OwnerUin: '',
AppId: '',
Uin: '',
};
}
}
}
1 change: 1 addition & 0 deletions src/modules/scf/constants.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export const WebServerImageDefaultPort = 9000;
export const YunTiTagDocHref = 'https://doc.weixin.qq.com/doc/w3_AQ8AWgYkAOEEeD1cr34R7S66r8ONY?scode=AJEAIQdfAAoiSWrYcZAOMAswb5AFM';
22 changes: 20 additions & 2 deletions src/modules/scf/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { ApigwRemoveInputs } from './../apigw/interface';
import { ActionType } from './apis';
import { RegionType, ApiServiceType, CapiCredentials } from './../interface';
import { Capi } from '@tencent-sdk/capi';
import { ApiTypeError } from '../../utils/error';
import { deepClone, strip } from '../../utils';
import { ApiError, ApiTypeError } from '../../utils/error';
import { deepClone, formatInputTags, isAddedYunTiTags, strip } from '../../utils';
import TagsUtils from '../tag/index';
import ApigwUtils from '../apigw';
import CONFIGS from './config';
Expand All @@ -25,6 +25,9 @@ import ScfEntity from './entities/scf';
import AliasEntity from './entities/alias';
import VersionEntity from './entities/version';
import { ConcurrencyEntity } from './entities/concurrency';
import { default as Cam } from '../cam';
import { YunTiTagDocHref } from './constants';
import { checkYunTi } from '../../utils/api';

/** 云函数组件 */
export default class Scf {
Expand Down Expand Up @@ -252,13 +255,15 @@ export default class Scf {
credentials: this.credentials,
region: this.region,
});
const tags: any = trigger?.parameters?.tags ?? trigger?.tags ?? funcInfo.Tags ?? [];
const triggerOutput = await triggerInstance.create({
scf: this,
region: this.region,
inputs: {
namespace: funcInfo.Namespace,
functionName: funcInfo.FunctionName,
...trigger,
tags: formatInputTags(tags),
},
});

Expand All @@ -278,6 +283,19 @@ export default class Scf {
const functionName = inputs.name;
const { ignoreTriggers = false } = inputs;

// 自研账号,需要检查函数标签是否配置云梯标签(运营部门、运营产品、负责人); 非自研账号,不需要检查
const cam = new Cam(this.credentials, this.region);
const userInfo = await cam.GetUserAppId();
const isYunTi = await checkYunTi(userInfo?.OwnerUin);
if (isYunTi) {
if (!isAddedYunTiTags(inputs)) {
throw new ApiError({
type: 'API_SCF_DeployFunction',
message: `部署失败:自研用户请按照运营部门、运营产品、负责人正确配置函数标签,标签配置指南请参考:${YunTiTagDocHref}`,
});
}
}

// 在部署前,检查函数初始状态,如果初始为 CreateFailed,尝试先删除,再重新创建
let funcInfo = await this.scf.getInitialStatus({ namespace, functionName });

Expand Down
2 changes: 2 additions & 0 deletions src/modules/scf/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ export interface TriggerType {
TriggerName?: string;
Qualifier?: string;
compared?: boolean;
tags?: object;
parameters?: any;
}

export type OriginTriggerType = {
Expand Down
3 changes: 2 additions & 1 deletion src/modules/triggers/apigw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ export default class ApigwTrigger extends BaseTrigger<ApigwTriggerInputsParams>
funcInfo?: FunctionInfo;
inputs: TriggerInputs<ApigwTriggerInputsParams>;
}) {
const { parameters, isAutoRelease } = inputs;
const { parameters, isAutoRelease, tags } = inputs;
const {
oldState,
protocols,
Expand Down Expand Up @@ -192,6 +192,7 @@ export default class ApigwTrigger extends BaseTrigger<ApigwTriggerInputsParams>
method: endpoints[0].method ?? 'ANY',
},
created: !!parameters?.created,
tags,
};
const triggerKey = this.getKey(triggerInputs);

Expand Down
4 changes: 4 additions & 0 deletions src/modules/triggers/interface/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ApigwDeployInputs, ApiEndpoint } from '../../apigw/interface';
import { TagInput } from '../../interface';

export interface ApigwTriggerRemoveScfTriggerInputs {
serviceId: string;
Expand Down Expand Up @@ -129,6 +130,9 @@ export interface TriggerInputs<P extends TriggerInputsParams = TriggerInputsPara

// 是否自动发布服务(API 网关特有)
isAutoRelease?: boolean;

// 标签列表
tags?: TagInput[];
}

export interface TriggerDetail {
Expand Down
38 changes: 37 additions & 1 deletion src/utils/api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Capi } from '@tencent-sdk/capi';
import { deepClone } from '.';
import axios from 'axios';
import { deepClone, getYunTiApiUrl } from '.';
import { ApiServiceType } from '../modules/interface';
import { ApiError } from './error';

Expand Down Expand Up @@ -94,3 +95,38 @@ export function ApiFactory<ACTIONS_T extends readonly string[]>({

return APIS;
}

/**
* checkYunTi 查询账号是否是自研账号
* @param uin 客户主账号
* @returns true: 是自研账号; false: 不是自研账号
*/
export const checkYunTi = async (uin: string) => {
let isYunTi = false;
try {
const params = JSON.stringify({
id: '1',
jsonrpc: '2.0',
method: 'checkOwnUin',
params: { ownUin: [uin] },
});
const apiUrl = getYunTiApiUrl();
const res = await axios.post(apiUrl, params, {
headers: { 'content-type': 'application/json' },
});
if (res?.data?.error?.message) {
throw new Error(res.data.error.message);
} else {
isYunTi =
res?.data?.result?.data &&
res.data.result.data?.some(
(item: { ownUin: string; appId: string }) => item?.ownUin === uin,
);
console.log('check yunTi ownUin:', isYunTi);
}
} catch (error) {
isYunTi = false;
console.log('checkYunTiOwnUin error:', error);
}
return isYunTi;
};
92 changes: 92 additions & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import path from 'path';
import camelCase from 'camelcase';
import { PascalCase } from 'type-fest';
import { CamelCasedProps, PascalCasedProps } from '../modules/interface';
import crypto from 'crypto';
import { ScfDeployInputs } from '../modules/scf/interface';

// TODO: 将一些库换成 lodash

Expand Down Expand Up @@ -273,3 +275,93 @@ export const getQcsResourceId = (service: string, region: string, uin: string, s
// 云资源六段式
return `qcs::${service}:${region}:uin/${uin}:${suffix}`;
};

/**
* hmacSha1 加密HmacSHA1
* @param text 加密文本
* @param key 加密密钥
* @returns
*/
export const hmacSha1 = (text: string, key: string) => {
return crypto.createHmac('sha1', key).update(text).digest('hex');
};

/**
* getYunTiApiUrl 查询云梯API地址
* @returns 云梯API地址
*/
export const getYunTiApiUrl = (): string => {
const apiKey = process.env.SLS_YUNTI_API_KEY || '';
const apiSecret = process.env.SLS_YUNTI_API_SECRET || '';
const apiUrl = process.env.SLS_YUNTI_API_URL;
const timeStamp = Math.floor(Date.now() / 1000);
const apiSign = hmacSha1(`${timeStamp}${apiKey}`, apiSecret);
const url = `${apiUrl}?api_key=${apiKey}&api_ts=${timeStamp}&api_sign=${apiSign}`;
return url;
};

/**
* formatInputTags 格式化输入标签
*/
export const formatInputTags = (
input: Array<any> | { [key: string]: string },
): { key: string; value: string }[] => {
let tags: { key: string; value: string }[] = [];
if (Array.isArray(input)) {
tags = input.map((item) => {
return {
key: item?.key ?? item?.Key ?? '',
value: item?.value ?? item?.Value ?? '',
};
});
} else if (typeof input === 'object' && input !== null) {
tags = Object.entries(input).map(([key, value]) => {
return {
key: (key ?? '').toString(),
value: (value ?? '').toString(),
};
});
} else {
tags = [];
}
return tags;
};

/**
* 是否配置云梯标签
* @param inputs ScfDeployInputs
* @returns true: 已配置云梯标签; false: 未配置云梯标签
*/
export const isAddedYunTiTags = (inputs: ScfDeployInputs): boolean => {
let result = false;
let isApiAddedTags = false;
let isApiAddedYunTiTags = false;
inputs.events?.forEach((item) => {
const [[key, value]] = Object.entries(item);
console.log(key, value);
const tags = formatInputTags(value?.parameters?.tags ?? []);
if (key === 'apigw' && tags?.length > 0) {
isApiAddedTags = true;
}
if (
key === 'apigw' &&
tags?.length > 0 &&
['运营部门', '运营产品', '负责人'].every((tagKey) =>
tags.some((tag) => tag.key === tagKey && !!tag.value),
)
) {
isApiAddedYunTiTags = true;
}
});

const functionTags: { TagKey: string; TagValue: string }[] = Object.entries(
inputs.tags ?? {},
).map(([TagKey, TagValue]) => ({ TagKey, TagValue }));
const isFunAddedYunTiTags =
functionTags?.length > 0 &&
['运营部门', '运营产品', '负责人'].every((key) =>
functionTags.some((item) => item.TagKey === key && !!item.TagValue),
);
result = isApiAddedYunTiTags || (!isApiAddedTags && isFunAddedYunTiTags);
return result;
};

0 comments on commit 9058356

Please sign in to comment.