@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.
Files changed (120) hide show
  1. package/bin/tcb.js +6 -4
  2. package/lib/commands/run/create.js +6 -1
  3. package/lib/commands/run/delete.js +6 -1
  4. package/lib/commands/run/image/common.js +1 -1
  5. package/lib/commands/run/image/delete.js +5 -0
  6. package/lib/commands/run/image/download.js +5 -0
  7. package/lib/commands/run/image/list.js +5 -0
  8. package/lib/commands/run/image/upload.js +5 -0
  9. package/lib/commands/run/index.js +1 -0
  10. package/lib/commands/run/list.js +7 -1
  11. package/lib/commands/run/service/config.js +112 -0
  12. package/lib/commands/run/service/create.js +140 -0
  13. package/lib/commands/run/service/deploy.js +145 -0
  14. package/lib/commands/run/service/index.js +21 -0
  15. package/lib/commands/run/service/list.js +115 -0
  16. package/lib/commands/run/service/update.js +132 -0
  17. package/lib/commands/run/version/common.js +1 -1
  18. package/lib/commands/run/version/create.js +5 -0
  19. package/lib/commands/run/version/delete.js +5 -0
  20. package/lib/commands/run/version/list.js +5 -0
  21. package/lib/commands/run/version/modify.js +5 -0
  22. package/lib/commands/run/version/update.js +5 -0
  23. package/lib/constant.js +20 -1
  24. package/lib/help.js +40 -38
  25. package/lib/run/index.js +1 -0
  26. package/lib/run/service/common.js +163 -0
  27. package/lib/run/service/config.js +70 -0
  28. package/lib/run/service/create.js +67 -0
  29. package/lib/run/service/deployPackage.js +89 -0
  30. package/lib/run/service/index.js +23 -0
  31. package/lib/run/service/list.js +31 -0
  32. package/lib/run/service/showLogs.js +116 -0
  33. package/lib/run/service/update.js +83 -0
  34. package/lib/utils/checkTcbrEnv.js +74 -0
  35. package/lib/utils/commonParamsCheck.js +48 -0
  36. package/lib/utils/index.js +3 -0
  37. package/lib/utils/net/http-request.js +4 -4
  38. package/lib/utils/tcbrApi/callTcbrApi.js +38 -0
  39. package/lib/utils/tcbrApi/index.js +17 -0
  40. package/lib/utils/tcbrApi/tcbr-cloud-api/cloud-api-service.js +268 -0
  41. package/lib/utils/tcbrApi/tcbr-cloud-api/error.js +17 -0
  42. package/lib/utils/tcbrApi/tcbr-cloud-api/index.js +17 -0
  43. package/lib/utils/tcbrApi/tcbr-cloud-api/request.js +40 -0
  44. package/lib/utils/tcbrApi/tcbr-cloud-api-request.js +61 -0
  45. package/lib/utils/validator.js +32 -1
  46. package/package.json +88 -88
  47. package/src/commands/run/create.ts +9 -2
  48. package/src/commands/run/delete.ts +8 -2
  49. package/src/commands/run/image/common.ts +1 -1
  50. package/src/commands/run/image/delete.ts +8 -1
  51. package/src/commands/run/image/download.ts +7 -1
  52. package/src/commands/run/image/list.ts +7 -2
  53. package/src/commands/run/image/upload.ts +8 -1
  54. package/src/commands/run/index.ts +2 -1
  55. package/src/commands/run/list.ts +11 -3
  56. package/src/commands/run/service/config.ts +81 -0
  57. package/src/commands/run/service/create.ts +118 -0
  58. package/src/commands/run/service/deploy.ts +121 -0
  59. package/src/commands/run/service/index.ts +5 -0
  60. package/src/commands/run/service/list.ts +94 -0
  61. package/src/commands/run/service/update.ts +104 -0
  62. package/src/commands/run/version/common.ts +1 -1
  63. package/src/commands/run/version/create.ts +8 -1
  64. package/src/commands/run/version/delete.ts +8 -2
  65. package/src/commands/run/version/list.ts +7 -1
  66. package/src/commands/run/version/modify.ts +8 -1
  67. package/src/commands/run/version/update.ts +8 -1
  68. package/src/constant.ts +35 -1
  69. package/src/help.ts +50 -48
  70. package/src/run/index.ts +2 -1
  71. package/src/run/service/common.ts +206 -0
  72. package/src/run/service/config.ts +77 -0
  73. package/src/run/service/create.ts +52 -0
  74. package/src/run/service/deployPackage.ts +65 -0
  75. package/src/run/service/index.ts +7 -0
  76. package/src/run/service/list.ts +29 -0
  77. package/src/run/service/showLogs.ts +98 -0
  78. package/src/run/service/update.ts +81 -0
  79. package/src/types.ts +128 -2
  80. package/src/utils/checkTcbrEnv.ts +67 -0
  81. package/src/utils/commonParamsCheck.ts +65 -0
  82. package/src/utils/index.ts +5 -1
  83. package/src/utils/net/http-request.ts +1 -1
  84. package/src/utils/tcbrApi/callTcbrApi.ts +28 -0
  85. package/src/utils/tcbrApi/index.ts +1 -0
  86. package/src/utils/tcbrApi/tcbr-cloud-api/cloud-api-service.ts +363 -0
  87. package/src/utils/tcbrApi/tcbr-cloud-api/error.ts +30 -0
  88. package/src/utils/tcbrApi/tcbr-cloud-api/index.ts +1 -0
  89. package/src/utils/tcbrApi/tcbr-cloud-api/request.ts +28 -0
  90. package/src/utils/tcbrApi/tcbr-cloud-api-request.ts +66 -0
  91. package/src/utils/validator.ts +64 -32
  92. package/types/commands/run/index.d.ts +1 -0
  93. package/types/commands/run/service/config.d.ts +14 -0
  94. package/types/commands/run/service/create.d.ts +13 -0
  95. package/types/commands/run/service/deploy.d.ts +13 -0
  96. package/types/commands/run/service/index.d.ts +5 -0
  97. package/types/commands/run/service/list.d.ts +13 -0
  98. package/types/commands/run/service/update.d.ts +13 -0
  99. package/types/constant.d.ts +18 -0
  100. package/types/run/index.d.ts +1 -0
  101. package/types/run/service/common.d.ts +32 -0
  102. package/types/run/service/config.d.ts +23 -0
  103. package/types/run/service/create.d.ts +7 -0
  104. package/types/run/service/deployPackage.d.ts +11 -0
  105. package/types/run/service/index.d.ts +7 -0
  106. package/types/run/service/list.d.ts +2 -0
  107. package/types/run/service/showLogs.d.ts +2 -0
  108. package/types/run/service/update.d.ts +2 -0
  109. package/types/types.d.ts +116 -2
  110. package/types/utils/checkTcbrEnv.d.ts +3 -0
  111. package/types/utils/commonParamsCheck.d.ts +3 -0
  112. package/types/utils/index.d.ts +3 -0
  113. package/types/utils/tcbrApi/callTcbrApi.d.ts +1 -0
  114. package/types/utils/tcbrApi/index.d.ts +1 -0
  115. package/types/utils/tcbrApi/tcbr-cloud-api/cloud-api-service.d.ts +51 -0
  116. package/types/utils/tcbrApi/tcbr-cloud-api/error.d.ts +20 -0
  117. package/types/utils/tcbrApi/tcbr-cloud-api/index.d.ts +1 -0
  118. package/types/utils/tcbrApi/tcbr-cloud-api/request.d.ts +4 -0
  119. package/types/utils/tcbrApi/tcbr-cloud-api-request.d.ts +9 -0
  120. 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: number,
379
- offset: number
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
+ }
@@ -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
+ }