@cloudbase/cli 1.12.7-alpha.3 → 2.0.1
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/bin/tcb.js +6 -4
- package/lib/commands/run/create.js +6 -1
- package/lib/commands/run/delete.js +6 -1
- package/lib/commands/run/image/common.js +1 -1
- package/lib/commands/run/image/delete.js +5 -0
- package/lib/commands/run/image/download.js +5 -0
- package/lib/commands/run/image/list.js +5 -0
- package/lib/commands/run/image/upload.js +5 -0
- package/lib/commands/run/index.js +1 -0
- package/lib/commands/run/list.js +7 -1
- package/lib/commands/run/service/config.js +112 -0
- package/lib/commands/run/service/create.js +140 -0
- package/lib/commands/run/service/deploy.js +145 -0
- package/lib/commands/run/service/index.js +21 -0
- package/lib/commands/run/service/list.js +115 -0
- package/lib/commands/run/service/update.js +132 -0
- package/lib/commands/run/version/common.js +1 -1
- package/lib/commands/run/version/create.js +5 -0
- package/lib/commands/run/version/delete.js +5 -0
- package/lib/commands/run/version/list.js +5 -0
- package/lib/commands/run/version/modify.js +5 -0
- package/lib/commands/run/version/update.js +5 -0
- package/lib/constant.js +20 -1
- package/lib/help.js +40 -38
- package/lib/run/index.js +1 -0
- package/lib/run/service/common.js +163 -0
- package/lib/run/service/config.js +70 -0
- package/lib/run/service/create.js +67 -0
- package/lib/run/service/deployPackage.js +89 -0
- package/lib/run/service/index.js +23 -0
- package/lib/run/service/list.js +31 -0
- package/lib/run/service/showLogs.js +116 -0
- package/lib/run/service/update.js +83 -0
- package/lib/utils/checkTcbrEnv.js +74 -0
- package/lib/utils/commonParamsCheck.js +48 -0
- package/lib/utils/index.js +3 -0
- package/lib/utils/net/http-request.js +4 -4
- package/lib/utils/tcbrApi/callTcbrApi.js +38 -0
- package/lib/utils/tcbrApi/index.js +17 -0
- package/lib/utils/tcbrApi/tcbr-cloud-api/cloud-api-service.js +268 -0
- package/lib/utils/tcbrApi/tcbr-cloud-api/error.js +17 -0
- package/lib/utils/tcbrApi/tcbr-cloud-api/index.js +17 -0
- package/lib/utils/tcbrApi/tcbr-cloud-api/request.js +40 -0
- package/lib/utils/tcbrApi/tcbr-cloud-api-request.js +61 -0
- package/lib/utils/validator.js +32 -1
- package/package.json +88 -88
- package/src/commands/run/create.ts +9 -2
- package/src/commands/run/delete.ts +8 -2
- package/src/commands/run/image/common.ts +1 -1
- package/src/commands/run/image/delete.ts +8 -1
- package/src/commands/run/image/download.ts +7 -1
- package/src/commands/run/image/list.ts +7 -2
- package/src/commands/run/image/upload.ts +8 -1
- package/src/commands/run/index.ts +2 -1
- package/src/commands/run/list.ts +11 -3
- package/src/commands/run/service/config.ts +81 -0
- package/src/commands/run/service/create.ts +118 -0
- package/src/commands/run/service/deploy.ts +121 -0
- package/src/commands/run/service/index.ts +5 -0
- package/src/commands/run/service/list.ts +94 -0
- package/src/commands/run/service/update.ts +104 -0
- package/src/commands/run/version/common.ts +1 -1
- package/src/commands/run/version/create.ts +8 -1
- package/src/commands/run/version/delete.ts +8 -2
- package/src/commands/run/version/list.ts +7 -1
- package/src/commands/run/version/modify.ts +8 -1
- package/src/commands/run/version/update.ts +8 -1
- package/src/constant.ts +35 -1
- package/src/help.ts +50 -48
- package/src/run/index.ts +2 -1
- package/src/run/service/common.ts +206 -0
- package/src/run/service/config.ts +77 -0
- package/src/run/service/create.ts +52 -0
- package/src/run/service/deployPackage.ts +65 -0
- package/src/run/service/index.ts +7 -0
- package/src/run/service/list.ts +29 -0
- package/src/run/service/showLogs.ts +98 -0
- package/src/run/service/update.ts +81 -0
- package/src/types.ts +128 -2
- package/src/utils/checkTcbrEnv.ts +67 -0
- package/src/utils/commonParamsCheck.ts +65 -0
- package/src/utils/index.ts +5 -1
- package/src/utils/net/http-request.ts +1 -1
- package/src/utils/tcbrApi/callTcbrApi.ts +28 -0
- package/src/utils/tcbrApi/index.ts +1 -0
- package/src/utils/tcbrApi/tcbr-cloud-api/cloud-api-service.ts +363 -0
- package/src/utils/tcbrApi/tcbr-cloud-api/error.ts +30 -0
- package/src/utils/tcbrApi/tcbr-cloud-api/index.ts +1 -0
- package/src/utils/tcbrApi/tcbr-cloud-api/request.ts +28 -0
- package/src/utils/tcbrApi/tcbr-cloud-api-request.ts +66 -0
- package/src/utils/validator.ts +64 -32
- package/types/commands/run/index.d.ts +1 -0
- package/types/commands/run/service/config.d.ts +14 -0
- package/types/commands/run/service/create.d.ts +13 -0
- package/types/commands/run/service/deploy.d.ts +13 -0
- package/types/commands/run/service/index.d.ts +5 -0
- package/types/commands/run/service/list.d.ts +13 -0
- package/types/commands/run/service/update.d.ts +13 -0
- package/types/constant.d.ts +18 -0
- package/types/run/index.d.ts +1 -0
- package/types/run/service/common.d.ts +32 -0
- package/types/run/service/config.d.ts +23 -0
- package/types/run/service/create.d.ts +7 -0
- package/types/run/service/deployPackage.d.ts +11 -0
- package/types/run/service/index.d.ts +7 -0
- package/types/run/service/list.d.ts +2 -0
- package/types/run/service/showLogs.d.ts +2 -0
- package/types/run/service/update.d.ts +2 -0
- package/types/types.d.ts +116 -2
- package/types/utils/checkTcbrEnv.d.ts +3 -0
- package/types/utils/commonParamsCheck.d.ts +3 -0
- package/types/utils/index.d.ts +3 -0
- package/types/utils/tcbrApi/callTcbrApi.d.ts +1 -0
- package/types/utils/tcbrApi/index.d.ts +1 -0
- package/types/utils/tcbrApi/tcbr-cloud-api/cloud-api-service.d.ts +51 -0
- package/types/utils/tcbrApi/tcbr-cloud-api/error.d.ts +20 -0
- package/types/utils/tcbrApi/tcbr-cloud-api/index.d.ts +1 -0
- package/types/utils/tcbrApi/tcbr-cloud-api/request.d.ts +4 -0
- package/types/utils/tcbrApi/tcbr-cloud-api-request.d.ts +9 -0
- package/types/utils/validator.d.ts +4 -0
package/src/types.ts
CHANGED
|
@@ -375,8 +375,8 @@ export interface IListBranch {
|
|
|
375
375
|
export interface IListImage {
|
|
376
376
|
envId: string,
|
|
377
377
|
serviceName: string,
|
|
378
|
-
limit
|
|
379
|
-
offset
|
|
378
|
+
limit?: number,
|
|
379
|
+
offset?: number
|
|
380
380
|
}
|
|
381
381
|
|
|
382
382
|
export interface IDeleteImage {
|
|
@@ -530,4 +530,130 @@ export interface IGetFunctionAliasRes {
|
|
|
530
530
|
Description: string
|
|
531
531
|
AddTime: string
|
|
532
532
|
ModTime: string
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
export interface ITcbrServerBaseConfig {
|
|
536
|
+
EnvId: string,
|
|
537
|
+
ServerName: string,
|
|
538
|
+
OpenAccessTypes: string[],
|
|
539
|
+
Cpu: number,
|
|
540
|
+
Mem: number,
|
|
541
|
+
MinNum: number,
|
|
542
|
+
MaxNum: number,
|
|
543
|
+
PolicyDetails: {
|
|
544
|
+
PolicyType: string,
|
|
545
|
+
PolicyThreshold: number
|
|
546
|
+
}[],
|
|
547
|
+
CustomLogs: string,
|
|
548
|
+
EnvParams: string,
|
|
549
|
+
InitialDelaySeconds: number,
|
|
550
|
+
CreateTime: string,
|
|
551
|
+
Port: number,
|
|
552
|
+
HasDockerfile: boolean,
|
|
553
|
+
Dockerfile: string,
|
|
554
|
+
BuildDir: string,
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
export interface IDescribeCloudRunServerDetail {
|
|
558
|
+
BaseInfo: {
|
|
559
|
+
ServerName: string,
|
|
560
|
+
DefaultDomainName: string,
|
|
561
|
+
CustomDomainName: string,
|
|
562
|
+
Status: 'running' | 'deploying' | 'deploy_failed',
|
|
563
|
+
UpdateTime: string,
|
|
564
|
+
},
|
|
565
|
+
ServerConfig: ITcbrServerBaseConfig,
|
|
566
|
+
RequestId: string
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
export interface ITcbrServiceOptions {
|
|
570
|
+
noConfirm: boolean,
|
|
571
|
+
override: boolean,
|
|
572
|
+
defaultOverride: boolean,
|
|
573
|
+
envId: string,
|
|
574
|
+
serviceName: string,
|
|
575
|
+
path: string,
|
|
576
|
+
cpu: number,
|
|
577
|
+
mem: number,
|
|
578
|
+
minNum: number,
|
|
579
|
+
maxNum: number,
|
|
580
|
+
policyDetails: string,
|
|
581
|
+
customLogs: string,
|
|
582
|
+
envParams: string,
|
|
583
|
+
containerPort: number,
|
|
584
|
+
remark: string,
|
|
585
|
+
targetDir: string,
|
|
586
|
+
dockerfile: string,
|
|
587
|
+
image: string,
|
|
588
|
+
library_image: string,
|
|
589
|
+
json: boolean
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
export interface ICloudRunProcessLog {
|
|
593
|
+
EnvId: string,
|
|
594
|
+
RunId: string
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
export interface ICloudRunBuildLog {
|
|
598
|
+
EnvId: string,
|
|
599
|
+
ServerName: string,
|
|
600
|
+
ServerVersion: string,
|
|
601
|
+
BuildId: number,
|
|
602
|
+
Offset: number
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
export interface IDescribeWxCloudBaseRunReleaseOrder {
|
|
606
|
+
envId: string,
|
|
607
|
+
serviceName: string
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
export interface IGetLogs {
|
|
611
|
+
envId: string,
|
|
612
|
+
taskId: number,
|
|
613
|
+
serviceName: string,
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
export interface ITcbrServiceConfigOptions {
|
|
617
|
+
serviceName: string,
|
|
618
|
+
envId: string,
|
|
619
|
+
cpu: number,
|
|
620
|
+
mem: number,
|
|
621
|
+
minNum: number,
|
|
622
|
+
maxNum: number,
|
|
623
|
+
policyDetails: string,
|
|
624
|
+
customLogs: string,
|
|
625
|
+
envParams: string,
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
export interface IServerInfo {
|
|
629
|
+
ServerName: string,
|
|
630
|
+
DefaultDomainName: string,
|
|
631
|
+
CustomDomainName: string,
|
|
632
|
+
Status: string,
|
|
633
|
+
UpdateTime: string,
|
|
634
|
+
CreatedTime: string,
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
export interface ITcbrServiceRequiredOptions {
|
|
638
|
+
envId: string,
|
|
639
|
+
serviceName: string,
|
|
640
|
+
containerPort: number,
|
|
641
|
+
isCreated: boolean,
|
|
642
|
+
path: string,
|
|
643
|
+
library_image: string,
|
|
644
|
+
image: string
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
export interface ITcbrServiceOptionalOptions {
|
|
648
|
+
cpu: number | string,
|
|
649
|
+
mem: number | string,
|
|
650
|
+
maxNum: number | string,
|
|
651
|
+
minNum: number | string
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
export interface ITcbrServiceConvertedOptionalOptions {
|
|
655
|
+
cpuConverted: number,
|
|
656
|
+
memConverted: number,
|
|
657
|
+
maxNumConverted: number,
|
|
658
|
+
minNumConverted: number
|
|
533
659
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
import { CloudBaseError } from '../error'
|
|
3
|
+
import { CloudApiService } from './net'
|
|
4
|
+
import { EnumEnvCheck } from '../constant'
|
|
5
|
+
const tcbService = CloudApiService.getInstance('tcb')
|
|
6
|
+
|
|
7
|
+
const oldCmdSet =
|
|
8
|
+
`
|
|
9
|
+
服务列表:tcb run:deprecated list --envId <envId>
|
|
10
|
+
创建服务:tcb run:deprecated create --envId <envId> --name <name>
|
|
11
|
+
删除服务:tcb run:deprecated delete --envId <envId> --serviceName <serviceName>
|
|
12
|
+
|
|
13
|
+
版本列表:tcb run:deprecated version list --envId <envId> --serviceName <serviceName>
|
|
14
|
+
创建版本:tcb run:deprecated version create --envId <envId> --serviceName <serviceName>
|
|
15
|
+
分配流量:tcb run:deprecated version modify --envId <envId> --serviceName <serviceName>
|
|
16
|
+
滚动更新:tcb run:deprecated version update --envId <envId> --serviceName <serviceName> --versionName <versionName>
|
|
17
|
+
删除版本:tcb run:deprecated version delete --envId <envId> --serviceName <serviceName> --versionName <versionName>
|
|
18
|
+
|
|
19
|
+
查看镜像:tcb run:deprecated image list --envId <envId> --serviceName <serviceName>
|
|
20
|
+
上传镜像:tcb run:deprecated image upload --envId <envId> --serviceName <serviceName> --imageId <imageId> --imageTag <imageTag>
|
|
21
|
+
下载镜像:tcb run:deprecated image download --envId <envId> --serviceName <serviceName> --imageTag <imageTag>
|
|
22
|
+
删除镜像:tcb run:deprecated image delete --envId <envId> --serviceName <serviceName> --imageTag <imageTag>
|
|
23
|
+
`
|
|
24
|
+
const newCmdSet =
|
|
25
|
+
`
|
|
26
|
+
查看环境下服务:tcb run service:list --envId <envId>
|
|
27
|
+
创建云托管服务:tcb run service:create --envId <envId> --serviceName <serviceName> --containerPort <containerPort>
|
|
28
|
+
更新云托管服务:tcb run service:update --envId <envId> --serviceName <serviceName> --containerPort <containerPort>
|
|
29
|
+
部署云托管服务:tcb run deploy --envId <envId> --serviceName <serviceName> --containerPort <containerPort>
|
|
30
|
+
更新服务基础配置:tcb run service:config --envId <envId> --serviceName <serviceName>
|
|
31
|
+
`
|
|
32
|
+
/**
|
|
33
|
+
*
|
|
34
|
+
* @param envId 环境 Id
|
|
35
|
+
* @param isTcbr 使用的命令是否是 tcbr 新操作集
|
|
36
|
+
* @returns
|
|
37
|
+
*/
|
|
38
|
+
export async function checkTcbrEnv(envId: string | undefined, isTcbr: boolean): Promise<EnumEnvCheck> | never {
|
|
39
|
+
if(envId === undefined) {
|
|
40
|
+
throw new CloudBaseError('请使用 -e 或 --envId 指定环境 ID')
|
|
41
|
+
}
|
|
42
|
+
const { EnvList: [envInfo] } = await tcbService.request('DescribeEnvs', {
|
|
43
|
+
EnvId: envId
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
if(envInfo === undefined) {
|
|
47
|
+
throw new CloudBaseError('无法读取到有效的环境信息,请检查环境 ID 是否正确')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if ((envInfo.EnvType === 'tcbr' && isTcbr) || (envInfo.EnvType !== 'tcbr' && !isTcbr)) {
|
|
51
|
+
return EnumEnvCheck.EnvFit
|
|
52
|
+
} else if (envInfo.EnvType === 'tcbr' && !isTcbr) {
|
|
53
|
+
return EnumEnvCheck.EnvNewCmdOld
|
|
54
|
+
} else if (envInfo.EnvType !== 'tcbr' && isTcbr) {
|
|
55
|
+
return EnumEnvCheck.EnvOldCmdNew
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function logEnvCheck(envId: string, warningType: EnumEnvCheck) {
|
|
60
|
+
if(warningType === EnumEnvCheck.EnvNewCmdOld) {
|
|
61
|
+
// 当前环境是 tcbr 环境且使用的不是 tcbr 新操作集
|
|
62
|
+
throw new CloudBaseError(`当前能力不支持 ${envId} 环境,请使用如下操作集:${chalk.grey(newCmdSet)}`)
|
|
63
|
+
} else if (warningType === EnumEnvCheck.EnvOldCmdNew) {
|
|
64
|
+
// 当前环境不是 tcbr 环境但使用 tcbr 操作集
|
|
65
|
+
throw new CloudBaseError(`当前能力不支持 ${envId} 环境,请使用如下操作集:${chalk.grey(oldCmdSet)}`)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { CloudBaseError } from '../error'
|
|
2
|
+
import { convertNumber } from '../run'
|
|
3
|
+
import { ITcbrServiceOptionalOptions, ITcbrServiceConvertedOptionalOptions } from '../types'
|
|
4
|
+
import { validateCpuMem } from './validator'
|
|
5
|
+
|
|
6
|
+
export function parseOptionalParams(options: ITcbrServiceOptionalOptions): ITcbrServiceConvertedOptionalOptions {
|
|
7
|
+
let cpuConverted
|
|
8
|
+
let memConverted
|
|
9
|
+
if (options.cpu || options.mem) {
|
|
10
|
+
let data = validateCpuMem(options.cpu, options.mem)
|
|
11
|
+
;[cpuConverted, memConverted] = [data.cpuOutput, data.memOutput]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let maxNumConverted
|
|
15
|
+
if (options.maxNum) {
|
|
16
|
+
maxNumConverted = convertNumber(options.maxNum)
|
|
17
|
+
if (maxNumConverted < 0 || maxNumConverted > 50) {
|
|
18
|
+
throw new CloudBaseError('最大副本数必须大于等于0且小于等于50')
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let minNumConverted
|
|
23
|
+
if (options.minNum) {
|
|
24
|
+
minNumConverted = convertNumber(options.minNum)
|
|
25
|
+
if (minNumConverted < 0 || minNumConverted > 50) {
|
|
26
|
+
throw new CloudBaseError('最小副本数必须大于等于0且小于等于50')
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (minNumConverted > maxNumConverted) {
|
|
31
|
+
throw new CloudBaseError('最小副本数不能大于最大副本数')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
cpuConverted,
|
|
36
|
+
memConverted,
|
|
37
|
+
maxNumConverted,
|
|
38
|
+
minNumConverted
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
*
|
|
44
|
+
* @description 通用两层三元运算符的参数处理,例如
|
|
45
|
+
* MaxNum: maxNumConverted
|
|
46
|
+
? convertNumber(maxNum)
|
|
47
|
+
: _override
|
|
48
|
+
? (previousServerConfig?.MaxNum)
|
|
49
|
+
: 50
|
|
50
|
+
* @param originalParam 原始参数,如 maxNumConverted
|
|
51
|
+
* @param override 是否覆盖,如 _override
|
|
52
|
+
* @param handler 处理参数的函数,如 convertNumber
|
|
53
|
+
* @param overrideVal 如果覆盖,提供的默认覆盖值
|
|
54
|
+
* @param defaultVal 默认值
|
|
55
|
+
* @param args 传入 handler 中使用的额外参数
|
|
56
|
+
*/
|
|
57
|
+
export function parseInputParam(originalParam, override: boolean, handler: Function | null, overrideVal, defaultVal, ...args) {
|
|
58
|
+
return originalParam
|
|
59
|
+
? (typeof handler === 'function')
|
|
60
|
+
? handler(originalParam, ...args)
|
|
61
|
+
: originalParam
|
|
62
|
+
: override
|
|
63
|
+
? overrideVal
|
|
64
|
+
: defaultVal
|
|
65
|
+
}
|
package/src/utils/index.ts
CHANGED
|
@@ -18,4 +18,8 @@ export * from './config'
|
|
|
18
18
|
export * from './auth'
|
|
19
19
|
export * from './store'
|
|
20
20
|
export * from './notice'
|
|
21
|
-
export * from './parallel'
|
|
21
|
+
export * from './parallel'
|
|
22
|
+
|
|
23
|
+
export * from './tcbrApi'
|
|
24
|
+
export * from './checkTcbrEnv'
|
|
25
|
+
export * from './commonParamsCheck'
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import _fetch, { RequestInit } from 'node-fetch'
|
|
2
|
-
import HttpsProxyAgent from 'https-proxy-agent'
|
|
2
|
+
import {HttpsProxyAgent} from 'https-proxy-agent'
|
|
3
3
|
import { REQUEST_TIMEOUT } from '../../constant'
|
|
4
4
|
import { CloudBaseError } from '../../error'
|
|
5
5
|
import { getProxy } from './proxy'
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { CloudBaseError } from '@cloudbase/toolbox'
|
|
2
|
+
import { CloudApiService } from './tcbr-cloud-api-request'
|
|
3
|
+
|
|
4
|
+
const tcbrService = CloudApiService.getInstance('tcbr')
|
|
5
|
+
|
|
6
|
+
export async function callTcbrApi(action: string, data: Record<string, any>) {
|
|
7
|
+
try {
|
|
8
|
+
const res = await tcbrService.request(action, data)
|
|
9
|
+
// 返回统一格式的 JSON 结果
|
|
10
|
+
return {
|
|
11
|
+
code: 0,
|
|
12
|
+
errmsg: 'success',
|
|
13
|
+
data: {
|
|
14
|
+
...res
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
} catch (e) {
|
|
18
|
+
// 不直接 throw 因为调用 DescribeCloudRunServerDetail 如果传入了当前不存在的服务名会返回报错
|
|
19
|
+
// 对特殊错误特殊处理
|
|
20
|
+
if (e.code === 'AuthFailure.UnauthorizedOperation') {
|
|
21
|
+
console.log('\n', `requestId: ${e.requestId}`)
|
|
22
|
+
throw new CloudBaseError('您没有权限执行此操作,请检查 CAM 策略\n')
|
|
23
|
+
} else if (e.code === 'LimitExceeded') {
|
|
24
|
+
throw new CloudBaseError(`${e.original.Message}\n`)
|
|
25
|
+
}
|
|
26
|
+
return e
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './callTcbrApi'
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import crypto from 'crypto'
|
|
2
|
+
import { URL } from 'url'
|
|
3
|
+
import QueryString from 'query-string'
|
|
4
|
+
|
|
5
|
+
import { fetch as _fetch, fetchStream as _fetchStream, nodeFetch as _nodeFetch } from './request'
|
|
6
|
+
import { CloudBaseError } from './error'
|
|
7
|
+
|
|
8
|
+
function isObject(x) {
|
|
9
|
+
return typeof x === 'object' && !Array.isArray(x) && x !== null
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// 移除对象中的空值,防止调用云 API 失败
|
|
13
|
+
function deepRemoveVoid(obj) {
|
|
14
|
+
if (Array.isArray(obj)) {
|
|
15
|
+
return obj.map(deepRemoveVoid)
|
|
16
|
+
} else if (isObject(obj)) {
|
|
17
|
+
let result = {}
|
|
18
|
+
for (const key in obj) {
|
|
19
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
20
|
+
const value = obj[key]
|
|
21
|
+
if (typeof value !== 'undefined' && value !== null) {
|
|
22
|
+
result[key] = deepRemoveVoid(value)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return result
|
|
27
|
+
} else {
|
|
28
|
+
return obj
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type HexBase64Latin1Encoding = 'latin1' | 'hex' | 'base64';
|
|
33
|
+
|
|
34
|
+
function sha256(message: string, secret: string, encoding?: HexBase64Latin1Encoding) {
|
|
35
|
+
const hmac = crypto.createHmac('sha256', secret)
|
|
36
|
+
return hmac.update(message).digest(encoding)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function getHash(message: string): string {
|
|
40
|
+
const hash = crypto.createHash('sha256')
|
|
41
|
+
return hash.update(message).digest('hex')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function getDate(timestamp: number): string {
|
|
45
|
+
const date = new Date(timestamp * 1000)
|
|
46
|
+
const year = date.getUTCFullYear()
|
|
47
|
+
const month = ('0' + (date.getUTCMonth() + 1)).slice(-2)
|
|
48
|
+
// UTC 日期,非本地时间
|
|
49
|
+
const day = ('0' + date.getUTCDate()).slice(-2)
|
|
50
|
+
return `${year}-${month}-${day}`
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const ServiceVersionMap = {
|
|
54
|
+
tcb: '2018-06-08',
|
|
55
|
+
scf: '2018-04-16',
|
|
56
|
+
flexdb: '2018-11-27',
|
|
57
|
+
cam: '2019-01-16',
|
|
58
|
+
vpc: '2017-03-12',
|
|
59
|
+
ssl: '2019-12-05',
|
|
60
|
+
tcbr: '2022-02-17',
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface ServiceOptions {
|
|
64
|
+
service: string;
|
|
65
|
+
version?: string;
|
|
66
|
+
proxy?: string;
|
|
67
|
+
timeout?: number;
|
|
68
|
+
region?: string;
|
|
69
|
+
baseParams?: Record<string, any>;
|
|
70
|
+
credential?: Credential;
|
|
71
|
+
getCredential?: () => Promise<Credential> | Credential;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface Credential {
|
|
75
|
+
secretId: string;
|
|
76
|
+
secretKey: string;
|
|
77
|
+
token?: string;
|
|
78
|
+
tokenExpired?: number;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface RequestOptions {
|
|
82
|
+
action: string;
|
|
83
|
+
data?: Record<string, any>;
|
|
84
|
+
method?: 'POST' | 'GET';
|
|
85
|
+
region?: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export const fetch = _fetch
|
|
89
|
+
export const fetchStream = _fetchStream
|
|
90
|
+
export const nodeFetch = _nodeFetch
|
|
91
|
+
|
|
92
|
+
// token 将在 n 分钟内过期
|
|
93
|
+
const isTokenExpired = (credential: Credential, gap = 120) =>
|
|
94
|
+
credential.tokenExpired && Number(credential.tokenExpired) < Date.now() + gap * 1000
|
|
95
|
+
|
|
96
|
+
export class CloudApiService {
|
|
97
|
+
// 缓存请求实例
|
|
98
|
+
static serviceCacheMap: Record<string, CloudApiService> = {};
|
|
99
|
+
|
|
100
|
+
static getInstance(options: ServiceOptions) {
|
|
101
|
+
const { service } = options
|
|
102
|
+
if (CloudApiService.serviceCacheMap?.[service]) {
|
|
103
|
+
return CloudApiService.serviceCacheMap[service]
|
|
104
|
+
}
|
|
105
|
+
const apiService = new CloudApiService(options)
|
|
106
|
+
// 预防 serviceCacheMap 被置空导致的错误
|
|
107
|
+
CloudApiService.serviceCacheMap = {
|
|
108
|
+
...CloudApiService.serviceCacheMap
|
|
109
|
+
}
|
|
110
|
+
CloudApiService.serviceCacheMap[service] = apiService
|
|
111
|
+
return apiService
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
service: string;
|
|
115
|
+
version: string;
|
|
116
|
+
proxy: string;
|
|
117
|
+
timeout: number;
|
|
118
|
+
region: string;
|
|
119
|
+
credential: Credential;
|
|
120
|
+
baseParams: Record<string, any>;
|
|
121
|
+
getCredential: () => Promise<Credential> | Credential;
|
|
122
|
+
|
|
123
|
+
url: string;
|
|
124
|
+
host: string;
|
|
125
|
+
action: string;
|
|
126
|
+
method: 'POST' | 'GET';
|
|
127
|
+
data: Record<string, any>;
|
|
128
|
+
payload: Record<string, any>;
|
|
129
|
+
|
|
130
|
+
constructor(options: ServiceOptions) {
|
|
131
|
+
if (!options) {
|
|
132
|
+
throw new CloudBaseError('Options cloud not be empty!')
|
|
133
|
+
}
|
|
134
|
+
const {
|
|
135
|
+
service,
|
|
136
|
+
baseParams,
|
|
137
|
+
version,
|
|
138
|
+
proxy,
|
|
139
|
+
region,
|
|
140
|
+
credential,
|
|
141
|
+
getCredential,
|
|
142
|
+
timeout = 60000
|
|
143
|
+
} = options
|
|
144
|
+
|
|
145
|
+
this.service = service
|
|
146
|
+
this.timeout = timeout
|
|
147
|
+
|
|
148
|
+
if (this.service === 'tcb' && process.env.CLOUDBASE_TCB_CLOUDAPI_PROXY) {
|
|
149
|
+
this.proxy = process.env.CLOUDBASE_TCB_CLOUDAPI_PROXY
|
|
150
|
+
} else {
|
|
151
|
+
this.proxy = proxy
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (this.service === 'tcb' && process.env.CLOUDBASE_TCB_CLOUDAPI_REGION) {
|
|
155
|
+
this.region = process.env.CLOUDBASE_TCB_CLOUDAPI_REGION
|
|
156
|
+
} else {
|
|
157
|
+
this.region = region || process.env.TENCENTCLOUD_REGION || 'ap-shanghai'
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
this.credential = credential
|
|
161
|
+
this.baseParams = baseParams || {}
|
|
162
|
+
this.getCredential = getCredential
|
|
163
|
+
this.version = ServiceVersionMap[service] || version
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
get baseUrl() {
|
|
167
|
+
const urlMap = {
|
|
168
|
+
tcb: 'https://tcb.tencentcloudapi.com',
|
|
169
|
+
flexdb: 'https://flexdb.tencentcloudapi.com',
|
|
170
|
+
lowcode: `${process.env.CLOUDBASE_LOWCODE_ENDPOINT || 'https://lcap.cloud.tencent.com' }/api/v1/cliapi`,
|
|
171
|
+
tcbr: 'https://tcbr.tencentcloudapi.com',
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (this.service === 'tcb' && process.env.CLOUDBASE_TCB_CLOUDAPI_HOST) {
|
|
175
|
+
return `http://${process.env.CLOUDBASE_TCB_CLOUDAPI_HOST}`
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (urlMap[this.service]) {
|
|
179
|
+
return urlMap[this.service]
|
|
180
|
+
} else {
|
|
181
|
+
return `https://${this.service}.tencentcloudapi.com`
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// overload
|
|
186
|
+
async request(options: RequestOptions);
|
|
187
|
+
async request(action: string, data?: Record<string, any>, method?: 'POST' | 'GET');
|
|
188
|
+
|
|
189
|
+
async request(
|
|
190
|
+
actionOrOptions: string | RequestOptions,
|
|
191
|
+
assignData: Record<string, any> = {},
|
|
192
|
+
assignMethod: 'POST' | 'GET' = 'POST'
|
|
193
|
+
) {
|
|
194
|
+
// 增加 region 参数,兼容之前的入参形式
|
|
195
|
+
let action
|
|
196
|
+
let data
|
|
197
|
+
let method
|
|
198
|
+
let region
|
|
199
|
+
if (typeof actionOrOptions === 'string') {
|
|
200
|
+
action = actionOrOptions
|
|
201
|
+
data = assignData
|
|
202
|
+
method = assignMethod
|
|
203
|
+
} else {
|
|
204
|
+
action = actionOrOptions?.action
|
|
205
|
+
data = actionOrOptions?.data || {}
|
|
206
|
+
method = actionOrOptions?.method || 'POST'
|
|
207
|
+
region = actionOrOptions?.region
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
this.action = action
|
|
211
|
+
this.data = deepRemoveVoid({ ...data, ...this.baseParams })
|
|
212
|
+
this.method = method
|
|
213
|
+
|
|
214
|
+
this.url = this.baseUrl
|
|
215
|
+
|
|
216
|
+
// 不存在密钥,或临时密钥过期
|
|
217
|
+
if (!this.credential?.secretId || isTokenExpired(this.credential)) {
|
|
218
|
+
if (!this.getCredential) {
|
|
219
|
+
throw new CloudBaseError('You must provide credential info!')
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (typeof this.getCredential !== 'function') {
|
|
223
|
+
throw new CloudBaseError('The getCredential option must be a function!')
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const credential = await this.getCredential()
|
|
227
|
+
|
|
228
|
+
if (!credential) {
|
|
229
|
+
throw new CloudBaseError('Calling getCredential function get no credential info!')
|
|
230
|
+
}
|
|
231
|
+
this.credential = credential
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
const data: Record<string, any> = await this.requestWithSign(region)
|
|
236
|
+
if (data.Response.Error) {
|
|
237
|
+
const tcError = new CloudBaseError(data.Response.Error.Message, {
|
|
238
|
+
action,
|
|
239
|
+
requestId: data.Response.RequestId,
|
|
240
|
+
code: data.Response.Error.Code,
|
|
241
|
+
original: data.Response.Error
|
|
242
|
+
})
|
|
243
|
+
throw tcError
|
|
244
|
+
} else {
|
|
245
|
+
return data.Response
|
|
246
|
+
}
|
|
247
|
+
} catch (e) {
|
|
248
|
+
// throw e
|
|
249
|
+
if (e.name === 'CloudBaseError') {
|
|
250
|
+
throw e
|
|
251
|
+
} else {
|
|
252
|
+
throw new CloudBaseError(e.message, {
|
|
253
|
+
action,
|
|
254
|
+
code: e.code,
|
|
255
|
+
type: e.type
|
|
256
|
+
})
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async requestWithSign(region) {
|
|
262
|
+
// data 中可能带有 readStream,由于需要计算整个 body 的 hash,
|
|
263
|
+
// 所以这里把 readStream 转为 Buffer
|
|
264
|
+
// await convertReadStreamToBuffer(data)
|
|
265
|
+
const timestamp = Math.floor(Date.now() / 1000)
|
|
266
|
+
|
|
267
|
+
const { method, timeout, data } = this
|
|
268
|
+
|
|
269
|
+
if (method === 'GET') {
|
|
270
|
+
this.url += '?' + QueryString.stringify(data)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (method === 'POST') {
|
|
274
|
+
this.payload = data
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const { CLOUDBASE_TCB_CLOUDAPI_HOST } = process.env
|
|
278
|
+
|
|
279
|
+
if (this.service === 'tcb' && CLOUDBASE_TCB_CLOUDAPI_HOST) {
|
|
280
|
+
this.host = CLOUDBASE_TCB_CLOUDAPI_HOST
|
|
281
|
+
} else {
|
|
282
|
+
this.host = new URL(this.url).host
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const config: any = {
|
|
286
|
+
method,
|
|
287
|
+
timeout,
|
|
288
|
+
headers: {
|
|
289
|
+
Host: this.host,
|
|
290
|
+
'X-TC-Action': this.action,
|
|
291
|
+
'X-TC-Region': region || this.region,
|
|
292
|
+
'X-TC-Timestamp': timestamp,
|
|
293
|
+
'X-TC-Version': this.version
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (this.credential.token) {
|
|
298
|
+
config.headers['X-TC-Token'] = this.credential.token
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (method === 'GET') {
|
|
302
|
+
config.headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
|
303
|
+
}
|
|
304
|
+
if (method === 'POST') {
|
|
305
|
+
config.body = JSON.stringify(data)
|
|
306
|
+
config.headers['Content-Type'] = 'application/json'
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const sign = this.getRequestSign(timestamp)
|
|
310
|
+
|
|
311
|
+
config.headers['Authorization'] = sign
|
|
312
|
+
return fetch(this.url, config, this.proxy)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
getRequestSign(timestamp: number) {
|
|
316
|
+
const { method, url, service } = this
|
|
317
|
+
const { secretId, secretKey } = this.credential
|
|
318
|
+
const urlObj = new URL(url)
|
|
319
|
+
|
|
320
|
+
// 通用头部
|
|
321
|
+
let headers = ''
|
|
322
|
+
const signedHeaders = 'content-type;host'
|
|
323
|
+
if (method === 'GET') {
|
|
324
|
+
headers = 'content-type:application/x-www-form-urlencoded\n'
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (method === 'POST') {
|
|
328
|
+
headers = 'content-type:application/json\n'
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
let path = urlObj.pathname
|
|
332
|
+
if (path === '/api/v1/cliapi' && service === 'lowcode') {
|
|
333
|
+
path = '//lcap.cloud.tencent.com/api/v1/cliapi'
|
|
334
|
+
headers += 'host:lcap.cloud.tencent.com\n'
|
|
335
|
+
} else {
|
|
336
|
+
headers += `host:${this.host}\n`
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
const querystring = urlObj.search.slice(1)
|
|
341
|
+
|
|
342
|
+
const payloadHash = this.payload ? getHash(JSON.stringify(this.payload)) : getHash('')
|
|
343
|
+
|
|
344
|
+
const canonicalRequest = `${method}\n${path}\n${querystring}\n${headers}\n${signedHeaders}\n${payloadHash}`
|
|
345
|
+
|
|
346
|
+
const date = getDate(timestamp)
|
|
347
|
+
|
|
348
|
+
const StringToSign = `TC3-HMAC-SHA256\n${timestamp}\n${date}/${service}/tc3_request\n${getHash(
|
|
349
|
+
canonicalRequest
|
|
350
|
+
)}`
|
|
351
|
+
|
|
352
|
+
const kDate = sha256(date, `TC3${secretKey}`)
|
|
353
|
+
const kService = sha256(service, kDate)
|
|
354
|
+
const kSigning = sha256('tc3_request', kService)
|
|
355
|
+
const signature = sha256(StringToSign, kSigning, 'hex')
|
|
356
|
+
|
|
357
|
+
return `TC3-HMAC-SHA256 Credential=${secretId}/${date}/${service}/tc3_request, SignedHeaders=${signedHeaders}, Signature=${signature}`
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
clearCredentialCache() {
|
|
361
|
+
this.credential = null
|
|
362
|
+
}
|
|
363
|
+
}
|