Skip to content

Commit a2e8c3c

Browse files
committed
feat: add postgresql
1 parent 3839a7a commit a2e8c3c

File tree

5 files changed

+380
-0
lines changed

5 files changed

+380
-0
lines changed

package-lock.json

Whitespace-only changes.

src/baas/postgresql/apis.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
function HttpError(code, message) {
2+
this.code = code || 0
3+
this.message = message || ''
4+
}
5+
6+
HttpError.prototype = Error.prototype
7+
8+
function apiFactory(actions) {
9+
const apis = {}
10+
actions.forEach((action) => {
11+
apis[action] = async (apig, inputs) => {
12+
const data = {
13+
Version: '2017-03-12',
14+
Action: action,
15+
RequestClient: 'ServerlessComponent',
16+
...inputs
17+
}
18+
if (apig.options.Token) {
19+
data.Token = apig.options.Token
20+
}
21+
try {
22+
const { Response } = await apig.request(
23+
data,
24+
// this is preset options for apigateway
25+
{
26+
debug: false,
27+
ServiceType: 'postgres',
28+
// baseHost: 'tencentcloudapi.com'
29+
host: 'postgres.tencentcloudapi.com'
30+
},
31+
false
32+
)
33+
if (Response && Response.Error && Response.Error.Code) {
34+
throw new HttpError(Response.Error.Code, Response.Error.Message)
35+
}
36+
return Response
37+
} catch (e) {
38+
throw new HttpError(500, e.message)
39+
}
40+
}
41+
})
42+
43+
return apis
44+
}
45+
46+
const ACTIONS = [
47+
'CreateServerlessDBInstance',
48+
'DescribeServerlessDBInstances',
49+
'DeleteServerlessDBInstance',
50+
'OpenServerlessDBExtranetAccess',
51+
'CloseServerlessDBExtranetAccess',
52+
'UpdateCdnConfig'
53+
]
54+
const APIS = apiFactory(ACTIONS)
55+
56+
module.exports = APIS

src/baas/postgresql/index.js

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
const { Capi } = require('@tencent-sdk/capi')
2+
const {
3+
createDbInstance,
4+
getDbInstanceDetail,
5+
getDbExtranetAccess,
6+
toggleDbInstanceAccess,
7+
deleteDbInstance,
8+
formatPgUrl
9+
} = require('./utils')
10+
11+
class Postgresql {
12+
constructor(credentials = {}, region) {
13+
this.region = region || 'ap-guangzhou'
14+
this.credentials = credentials
15+
this.capi = new Capi({
16+
Region: region,
17+
AppId: this.credentials.AppId,
18+
SecretId: this.credentials.SecretId,
19+
SecretKey: this.credentials.SecretKey,
20+
Token: this.credentials.Token
21+
})
22+
}
23+
24+
async deploy(inputs = {}) {
25+
const {
26+
region,
27+
zone,
28+
projectId,
29+
dBInstanceName,
30+
dBVersion,
31+
dBCharset,
32+
extranetAccess,
33+
vpcConfig
34+
} = inputs
35+
36+
const outputs = {
37+
region: region,
38+
zone: zone,
39+
vpcConfig: vpcConfig,
40+
dBInstanceName: dBInstanceName
41+
}
42+
43+
let dbDetail = await getDbInstanceDetail(this.capi, dBInstanceName)
44+
45+
if (dbDetail && dbDetail.DBInstanceName) {
46+
const publicAccess = getDbExtranetAccess(dbDetail.DBInstanceNetInfo)
47+
// exist and public access config different, update db instance
48+
if (publicAccess !== extranetAccess) {
49+
console.log(`DB instance ${dBInstanceName} existed, updating...`)
50+
dbDetail = await toggleDbInstanceAccess(this.capi, dBInstanceName, extranetAccess)
51+
} else {
52+
console.log(`DB instance ${dBInstanceName} existed.`)
53+
}
54+
} else {
55+
// not exist, create
56+
const postgresInputs = {
57+
Zone: zone,
58+
ProjectId: projectId,
59+
DBInstanceName: dBInstanceName,
60+
DBVersion: dBVersion,
61+
DBCharset: dBCharset
62+
}
63+
64+
if (vpcConfig.vpcId) {
65+
postgresInputs.VpcId = vpcConfig.vpcId
66+
}
67+
68+
if (vpcConfig.subnetId) {
69+
postgresInputs.SubnetId = vpcConfig.subnetId
70+
}
71+
dbDetail = await createDbInstance(this.capi, postgresInputs)
72+
outputs.dBInstanceId = dbDetail.DBInstanceId
73+
if (extranetAccess) {
74+
dbDetail = await toggleDbInstanceAccess(this.capi, dBInstanceName, extranetAccess)
75+
}
76+
}
77+
78+
const {
79+
DBInstanceNetInfo,
80+
DBAccountSet: [accountInfo],
81+
DBDatabaseList: [dbName]
82+
} = dbDetail
83+
let internetInfo = null
84+
let extranetInfo = null
85+
86+
DBInstanceNetInfo.forEach((item) => {
87+
if (item.NetType === 'private') {
88+
internetInfo = item
89+
}
90+
if (item.NetType === 'public') {
91+
extranetInfo = item
92+
}
93+
})
94+
if (vpcConfig.vpcId) {
95+
outputs.private = formatPgUrl(internetInfo, accountInfo, dbName)
96+
}
97+
if (extranetAccess && extranetInfo) {
98+
outputs.public = formatPgUrl(extranetInfo, accountInfo, dbName)
99+
}
100+
101+
return outputs
102+
}
103+
104+
async remove(inputs = {}) {
105+
const { dBInstanceName } = inputs
106+
// need circle for deleting, after host status is 6, then we can delete it
107+
await deleteDbInstance(this.capi, dBInstanceName)
108+
return {}
109+
}
110+
}
111+
112+
module.exports = Postgresql

src/baas/postgresql/index.test.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
const Postgresql = require('./index')
2+
3+
async function runTest() {
4+
const credentials = {
5+
SecretId: '',
6+
SecretKey: ''
7+
}
8+
9+
// support region: ap-guangzhou-2, ap-beijing-3, ap-shanghai-2
10+
const inputs = {
11+
region: 'ap-guangzhou',
12+
zone: 'ap-guangzhou-2',
13+
dBInstanceName: 'serverless',
14+
projectId: 0,
15+
dBVersion: '10.4',
16+
dBCharset: 'UTF8',
17+
vpcConfig: {
18+
vpcId: 'vpc-id3zoj6r',
19+
subnetId: 'subnet-kwc49rti'
20+
},
21+
extranetAccess: true
22+
}
23+
const pg = new Postgresql(credentials, inputs.region)
24+
// deploy
25+
const outputs = await pg.deploy(inputs)
26+
console.log(outputs)
27+
// remove
28+
await pg.remove(outputs)
29+
}
30+
31+
runTest()
32+
33+
process.on('unhandledRejection', (e) => {
34+
console.log(e);
35+
36+
})

src/baas/postgresql/utils.js

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
const { sleep, waitResponse } = require('@ygkit/request')
2+
const {
3+
CreateServerlessDBInstance,
4+
DescribeServerlessDBInstances,
5+
OpenServerlessDBExtranetAccess,
6+
CloseServerlessDBExtranetAccess,
7+
DeleteServerlessDBInstance
8+
} = require('./apis')
9+
10+
// timeout 5 minutes
11+
const TIMEOUT = 5 * 60 * 1000
12+
13+
/**
14+
*
15+
* @param {object} capi capi instance
16+
* @param {*} dBInstanceName
17+
*/
18+
async function getDbInstanceDetail(capi, dBInstanceName) {
19+
// get instance detail
20+
try {
21+
const res = await DescribeServerlessDBInstances(capi, {
22+
Filter: [
23+
{
24+
Name: 'db-instance-name',
25+
Values: [dBInstanceName]
26+
}
27+
]
28+
})
29+
if (res.DBInstanceSet) {
30+
const {
31+
DBInstanceSet: [dbDetail]
32+
} = res
33+
return dbDetail
34+
}
35+
return null
36+
} catch (e) {
37+
console.log(e)
38+
return null
39+
}
40+
}
41+
42+
/**
43+
* get db public access status
44+
* @param {array} netInfos network infos
45+
*/
46+
function getDbExtranetAccess(netInfos) {
47+
let result = false
48+
netInfos.forEach((item) => {
49+
if (item.NetType === 'public') {
50+
result = item.Status === '1'
51+
}
52+
})
53+
return result
54+
}
55+
56+
/**
57+
INSTANCE_STATUS_APPLYING: "applying", 申请中
58+
INSTANCE_STATUS_INIT: "init", 待初始化
59+
INSTANCE_STATUS_INITING: "initing", 初始化中
60+
INSTANCE_STATUS_OK: "running", 运行中
61+
INSTANCE_STATUS_LIMITED: "limited run", 受限运行
62+
INSTANCE_STATUS_ISOLATED: "isolated", 已隔离
63+
INSTANCE_STATUS_RECYCLING: "recycling", 回收中
64+
INSTANCE_STATUS_RECYCLED: "recycled", 已回收
65+
INSTANCE_STATUS_JOB_RUNNING: "job running", 任务执行中
66+
INSTANCE_STATUS_OFFLINE: "offline", 下线
67+
INSTANCE_STATUS_MIGRATE: "migrating", 迁移中
68+
INSTANCE_STATUS_EXPANDING: "expanding", 扩容中
69+
INSTANCE_STATUS_READONLY: "readonly", 只读
70+
INSTANCE_STATUS_RESTARTING: "restarting", 重启中
71+
*/
72+
73+
/**
74+
* toggle db instance extranet access
75+
* @param {object} capi capi client
76+
* @param {string} dBInstanceName db instance name
77+
* @param {boolean} extranetAccess whether open extranet accesss
78+
*/
79+
async function toggleDbInstanceAccess(capi, dBInstanceName, extranetAccess) {
80+
if (extranetAccess) {
81+
console.log(`Start open db extranet access...`)
82+
await OpenServerlessDBExtranetAccess(capi, {
83+
DBInstanceName: dBInstanceName
84+
})
85+
const detail = await waitResponse({
86+
callback: async () => getDbInstanceDetail(capi, dBInstanceName),
87+
targetResponse: 'running',
88+
targetProp: 'DBInstanceStatus',
89+
timeout: TIMEOUT
90+
})
91+
console.log(`Open db extranet access success`)
92+
return detail
93+
}
94+
console.log(`Start close db extranet access...`)
95+
await CloseServerlessDBExtranetAccess(capi, {
96+
DBInstanceName: dBInstanceName
97+
})
98+
const detail = await waitResponse({
99+
callback: async () => getDbInstanceDetail(capi, dBInstanceName),
100+
targetResponse: 'running',
101+
targetProp: 'DBInstanceStatus',
102+
timeout: TIMEOUT
103+
})
104+
console.log(`Close db extranet access success`)
105+
return detail
106+
}
107+
108+
/**
109+
* create db instance
110+
* @param {object} capi capi client
111+
* @param {object} postgresInputs create db instance inputs
112+
*/
113+
async function createDbInstance(capi, postgresInputs) {
114+
console.log(`Start create DB instance ${postgresInputs.DBInstanceName}...`)
115+
const { DBInstanceId } = await CreateServerlessDBInstance(capi, postgresInputs)
116+
console.log(`Creating DB instance ID: ${DBInstanceId}`)
117+
118+
const detail = await waitResponse({
119+
callback: async () => getDbInstanceDetail(capi, postgresInputs.DBInstanceName),
120+
targetResponse: 'running',
121+
targetProp: 'DBInstanceStatus',
122+
timeout: TIMEOUT
123+
})
124+
console.log(`Created DB instance name ${postgresInputs.DBInstanceName} successfully`)
125+
return detail
126+
}
127+
128+
/**
129+
* delete db instance
130+
* @param {object} capi capi client
131+
* @param {string} db instance name
132+
*/
133+
async function deleteDbInstance(capi, dBInstanceName) {
134+
console.log(`Start removing postgres instance ${dBInstanceName}`)
135+
await DeleteServerlessDBInstance(capi, {
136+
DBInstanceName: dBInstanceName
137+
})
138+
const detail = await waitResponse({
139+
callback: async () => getDbInstanceDetail(capi, dBInstanceName),
140+
targetResponse: undefined,
141+
targetProp: 'DBInstanceStatus',
142+
timeout: TIMEOUT
143+
})
144+
console.log(`Removed postgres instance ${dBInstanceName}.`)
145+
return detail
146+
}
147+
148+
/**
149+
* format postgresql connect string
150+
* @param {object} netInfo network info
151+
* @param {object} accountInfo account info
152+
* @param {string} dbName db name
153+
*/
154+
function formatPgUrl(netInfo, accountInfo, dbName) {
155+
return {
156+
connectionString: `postgresql://${accountInfo.DBUser}:${encodeURIComponent(
157+
accountInfo.DBPassword
158+
)}@${netInfo.Address || netInfo.Ip}:${netInfo.Port}/${dbName}`,
159+
host: netInfo.Address || netInfo.Ip,
160+
port: netInfo.Port,
161+
user: accountInfo.DBUser,
162+
password: accountInfo.DBPassword,
163+
dbname: dbName
164+
}
165+
}
166+
167+
module.exports = {
168+
TIMEOUT,
169+
createDbInstance,
170+
getDbInstanceDetail,
171+
getDbExtranetAccess,
172+
deleteDbInstance,
173+
toggleDbInstanceAccess,
174+
formatPgUrl,
175+
sleep
176+
}

0 commit comments

Comments
 (0)