diff --git a/CHANGELOG.md b/CHANGELOG.md index 64d629db..e795e01f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +# [2.25.0](https://github.com/serverless-tencent/tencent-component-toolkit/compare/v2.24.2...v2.25.0) (2024-04-24) + + +### Features + +* yml支持job镜像配置&修复cfs找不到挂载点问题 ([#296](https://github.com/serverless-tencent/tencent-component-toolkit/issues/296)) ([0bff41b](https://github.com/serverless-tencent/tencent-component-toolkit/commit/0bff41bf8776a0115f2cf8c473d024bb4dce31e9)) +* 部署函数支持添加云梯默认标签(运营部门,运营产品,负责人) ([#298](https://github.com/serverless-tencent/tencent-component-toolkit/issues/298)) ([fa1d037](https://github.com/serverless-tencent/tencent-component-toolkit/commit/fa1d037153e12b9ad2a3e2ddfb7e04c692375d25)) + + +### Reverts + +* Revert "feat: yml支持job镜像配置&修复cfs找不到挂载点问题 (#296)" (#297) ([6034e25](https://github.com/serverless-tencent/tencent-component-toolkit/commit/6034e255533b1f3c4aa7771bd3b9b3b0786e34a4)), closes [#296](https://github.com/serverless-tencent/tencent-component-toolkit/issues/296) [#297](https://github.com/serverless-tencent/tencent-component-toolkit/issues/297) + ## [2.24.2](https://github.com/serverless-tencent/tencent-component-toolkit/compare/v2.24.1...v2.24.2) (2022-11-02) diff --git a/package-lock.json b/package-lock.json index b905b769..356cc673 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3887,6 +3887,15 @@ "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", "dev": true }, + "@types/axios": { + "version": "0.14.0", + "resolved": "https://mirrors.tencent.com/npm/@types/axios/-/axios-0.14.0.tgz", + "integrity": "sha512-KqQnQbdYE54D7oa/UmYVMZKq7CO4l8DEENzOKc4aBRwxCXSlJXGz83flFx5L7AWrOQnmuN3kVsRdt+GZPPjiVQ==", + "dev": true, + "requires": { + "axios": "*" + } + }, "@types/babel__core": { "version": "7.1.15", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.15.tgz", @@ -4673,12 +4682,11 @@ "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" }, "axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", - "dev": true, + "version": "0.21.0", + "resolved": "https://mirrors.tencent.com/npm/axios/-/axios-0.21.0.tgz", + "integrity": "sha512-fmkJBknJKoZwem3/IKSSLpkdNXZeBu5Q7GA/aRsr2btgrptmSCxi2oFjZHqGdK9DoTil9PIHlPIZw2EcRJXRvw==", "requires": { - "follow-redirects": "^1.14.0" + "follow-redirects": "^1.10.0" } }, "babel-jest": { @@ -7374,10 +7382,9 @@ "dev": true }, "follow-redirects": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.3.tgz", - "integrity": "sha512-3MkHxknWMUtb23apkgz/83fDoe+y+qr0TdgacGIA7bew+QLBo3vdgEN2xEsuXNivpFy4CyDhBBZnNZOtalmenw==", - "dev": true + "version": "1.15.6", + "resolved": "https://mirrors.tencent.com/npm/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==" }, "for-in": { "version": "1.0.2", diff --git a/package.json b/package.json index 2faee77f..2e9e9717 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tencent-component-toolkit", - "version": "2.24.2", + "version": "2.25.0", "description": "Tencent component toolkit", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -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", @@ -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", diff --git a/src/modules/cam/apis.ts b/src/modules/cam/apis.ts index 99543d63..e3e5ee24 100644 --- a/src/modules/cam/apis.ts +++ b/src/modules/cam/apis.ts @@ -8,6 +8,7 @@ const ACTIONS = [ 'CreateRole', 'GetRole', 'DeleteRole', + 'GetUserAppId', ] as const; export type ActionType = typeof ACTIONS[number]; diff --git a/src/modules/cam/index.ts b/src/modules/cam/index.ts index f98e5d08..db2b87e5 100644 --- a/src/modules/cam/index.ts +++ b/src/modules/cam/index.ts @@ -2,6 +2,8 @@ import { ActionType } from './apis'; import { CapiCredentials, RegionType, ApiServiceType } from './../interface'; import { Capi } from '@tencent-sdk/capi'; import APIS from './apis'; +import { getYunTiApiUrl } from '../../utils'; +import axios from 'axios'; /** CAM (访问管理)for serverless */ export default class Cam { @@ -112,4 +114,54 @@ 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: '', + }; + } + } + + /** + * checkYunTi 检查是否是云梯账号 + * @returns {boolean} true: 是云梯账号; false: 非云梯账号 + */ + async checkYunTi(): Promise { + let isYunTi = false; + const { OwnerUin: uin } = await this.GetUserAppId(); + 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; + } } diff --git a/src/modules/scf/constants.ts b/src/modules/scf/constants.ts new file mode 100644 index 00000000..bc6249fa --- /dev/null +++ b/src/modules/scf/constants.ts @@ -0,0 +1 @@ +export const WebServerImageDefaultPort = 9000; diff --git a/src/modules/scf/index.ts b/src/modules/scf/index.ts index e8ebd768..2813f2ab 100644 --- a/src/modules/scf/index.ts +++ b/src/modules/scf/index.ts @@ -3,7 +3,7 @@ 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 { deepClone, formatInputTags, strip } from '../../utils'; import TagsUtils from '../tag/index'; import ApigwUtils from '../apigw'; import CONFIGS from './config'; @@ -252,6 +252,7 @@ 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, @@ -259,6 +260,7 @@ export default class Scf { namespace: funcInfo.Namespace, functionName: funcInfo.FunctionName, ...trigger, + tags: formatInputTags(tags), }, }); @@ -457,4 +459,14 @@ export default class Scf { const logs = await this.scf.getLogs(inputs); return logs; } + + checkAddedYunTiTags(tags: Array<{ [key: string]: string }>): boolean { + const formatTags = formatInputTags(tags); + const result = + formatTags?.length > 0 && + ['运营部门', '运营产品', '负责人'].every((tagKey) => + formatTags.some((item) => item.key === tagKey && !!item.value), + ); + return result; + } } diff --git a/src/modules/scf/interface.ts b/src/modules/scf/interface.ts index d7f3d6ba..f714a7b7 100644 --- a/src/modules/scf/interface.ts +++ b/src/modules/scf/interface.ts @@ -12,6 +12,8 @@ export interface FunctionCode { RegistryId?: string; Command?: string; Args?: string; + ContainerImageAccelerate?: boolean; + ImagePort?: number; }; } @@ -76,6 +78,8 @@ export interface TriggerType { TriggerName?: string; Qualifier?: string; compared?: boolean; + tags?: object; + parameters?: any; } export type OriginTriggerType = { @@ -203,6 +207,7 @@ export interface ScfCreateFunctionInputs { cfs?: { cfsId: string; + mountInsId?: string; MountInsId?: string; localMountDir: string; remoteMountDir: string; @@ -228,6 +233,10 @@ export interface ScfCreateFunctionInputs { command?: string; // 启动命令参数 args?: string; + // 是否开启镜像加速 + containerImageAccelerate?: boolean; + // 监听端口: -1 表示job镜像,0~65535 表示Web Server镜像 + imagePort?: number; }; // 异步调用重试配置 @@ -391,25 +400,25 @@ export interface GetRequestStatusOptions { /** * 函数名称 */ - functionName: string + functionName: string; /** * 需要查询状态的请求id */ - functionRequestId: string + functionRequestId: string; /** * 函数的所在的命名空间 */ - namespace?: string + namespace?: string; /** * 查询的开始时间,例如:2017-05-16 20:00:00,不填默认为当前时间 - 15min */ - startTime?: string + startTime?: string; /** * 查询的结束时间,例如:2017-05-16 20:59:59,不填默认为当前时间。EndTime 需要晚于 StartTime。 */ - endTime?: string + endTime?: string; } diff --git a/src/modules/scf/utils.ts b/src/modules/scf/utils.ts index 0c905d1e..a7da9fa3 100644 --- a/src/modules/scf/utils.ts +++ b/src/modules/scf/utils.ts @@ -1,3 +1,4 @@ +import { WebServerImageDefaultPort } from './constants'; import { ScfCreateFunctionInputs, BaseFunctionConfig, ProtocolParams } from './interface'; const CONFIGS = require('./config').default; @@ -52,6 +53,18 @@ export const formatInputs = (inputs: ScfCreateFunctionInputs) => { if (imageConfig.args) { functionInputs.Code!.ImageConfig!.Args = imageConfig.args; } + // 镜像加速 + if (imageConfig.containerImageAccelerate !== undefined) { + functionInputs.Code!.ImageConfig!.ContainerImageAccelerate = + imageConfig.containerImageAccelerate; + } + // 监听端口: -1 表示 job镜像,0 ~ 65526 表示webServer镜像 + if (imageConfig.imagePort) { + functionInputs.Code!.ImageConfig!.ImagePort = + Number.isInteger(imageConfig?.imagePort) && imageConfig?.imagePort === -1 + ? -1 + : WebServerImageDefaultPort; + } } else { // 基于 COS 代码部署 functionInputs.Code = { @@ -149,7 +162,7 @@ export const formatInputs = (inputs: ScfCreateFunctionInputs) => { inputs.cfs.forEach((item) => { functionInputs.CfsConfig?.CfsInsList.push({ CfsId: item.cfsId, - MountInsId: item.MountInsId || item.cfsId, + MountInsId: item.mountInsId || item.MountInsId || item.cfsId, LocalMountDir: item.localMountDir, RemoteMountDir: item.remoteMountDir, UserGroupId: String(item.userGroupId || 10000), diff --git a/src/modules/triggers/apigw.ts b/src/modules/triggers/apigw.ts index 5438d621..cfaba5f4 100644 --- a/src/modules/triggers/apigw.ts +++ b/src/modules/triggers/apigw.ts @@ -147,7 +147,7 @@ export default class ApigwTrigger extends BaseTrigger funcInfo?: FunctionInfo; inputs: TriggerInputs; }) { - const { parameters, isAutoRelease } = inputs; + const { parameters, isAutoRelease, tags } = inputs; const { oldState, protocols, @@ -192,6 +192,7 @@ export default class ApigwTrigger extends BaseTrigger method: endpoints[0].method ?? 'ANY', }, created: !!parameters?.created, + tags, }; const triggerKey = this.getKey(triggerInputs); diff --git a/src/modules/triggers/interface/index.ts b/src/modules/triggers/interface/index.ts index ca07f7dc..0b7c65d8 100644 --- a/src/modules/triggers/interface/index.ts +++ b/src/modules/triggers/interface/index.ts @@ -1,4 +1,5 @@ import { ApigwDeployInputs, ApiEndpoint } from '../../apigw/interface'; +import { TagInput } from '../../interface'; export interface ApigwTriggerRemoveScfTriggerInputs { serviceId: string; @@ -129,6 +130,9 @@ export interface TriggerInputs

{ + 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 | { [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) { + tags = Object.entries(input).map(([key, value]) => { + return { + key: (key ?? '').toString(), + value: (value ?? '').toString(), + }; + }); + } else { + tags = undefined as any; + } + return tags; +};