feat: 腾讯云证书tke ingress

This commit is contained in:
xiaojunnuo
2021-01-02 02:47:58 +08:00
parent 43e90503ca
commit 67bff28255
20 changed files with 1852 additions and 173 deletions
+1
View File
@@ -12,6 +12,7 @@
"@certd/certd": "^0.0.1",
"@types/node": "^14.14.13",
"dayjs": "^1.9.7",
"kubernetes-client": "^9.0.0",
"lodash": "^4.17.20",
"log4js": "^6.3.0",
"tencentcloud-sdk-nodejs": "^4.0.39"
@@ -16,6 +16,10 @@ export class UploadCertToAliyun extends AbstractAliyunPlugin {
name: {
label: '证书名称'
},
regionId: {
label: '大区',
value: 'cn-hangzhou'
},
accessProvider: {
label: 'Access提供者',
type: [String, Object],
@@ -41,11 +45,11 @@ export class UploadCertToAliyun extends AbstractAliyunPlugin {
})
}
async execute ({ accessProviders, cert, props, context, logger }) {
async execute ({ accessProviders, cert, props, context }) {
const { name, accessProvider } = props
const certName = name + '-' + dayjs().format('YYYYMMDD-HHmmss')
const certName = this.appendTimeSuffix(name || cert.domain)
const params = {
RegionId: 'cn-hangzhou',
RegionId: props.regionId || 'cn-hangzhou',
Name: certName,
Cert: this.format(cert.crt.toString()),
Key: this.format(cert.key.toString())
@@ -62,4 +66,33 @@ export class UploadCertToAliyun extends AbstractAliyunPlugin {
this.logger.info('证书上传成功:aliyunCertId=', ret.CertId)
context.aliyunCertId = ret.CertId
}
/**
* 没用,现在阿里云证书不允许删除
* @param accessProviders
* @param cert
* @param props
* @param context
* @returns {Promise<void>}
*/
async rollback ({ accessProviders, cert, props, context }) {
const { accessProvider } = props
const { aliyunCertId } = context
this.logger.info('准备删除阿里云证书:', aliyunCertId)
const params = {
RegionId: props.regionId || 'cn-hangzhou',
CertId: aliyunCertId
}
const requestOption = {
method: 'POST'
}
const provider = super.getAccessProvider(accessProvider, accessProviders)
const client = this.getClient(provider)
const ret = await client.request('DeleteUserCertificate', params, requestOption)
this.checkRet(ret)
this.logger.info('证书删除成功:', aliyunCertId)
delete context.aliyunCertId
}
}
+17 -3
View File
@@ -1,5 +1,19 @@
import { UploadCertToAliyun } from './aliyun/upload-to-aliyun/index.js'
import { DeployCertToAliyunCDN } from './aliyun/deploy-to-cdn/index.js'
export default {
UploadCertToAliyun, DeployCertToAliyunCDN
}
import { UploadCertToTencent } from './tencent/upload-to-tencent/index.js'
import { DeployCertToTencentCDN } from './tencent/deploy-to-cdn/index.js'
import { DeployCertToTencentCLB } from './tencent/deploy-to-clb/index.js'
import { DeployCertToTencentTKEIngress } from './tencent/deploy-to-tke-ingress/index.js'
export default [
UploadCertToAliyun,
DeployCertToAliyunCDN,
UploadCertToTencent,
DeployCertToTencentTKEIngress,
DeployCertToTencentCDN,
DeployCertToTencentCLB
]
@@ -13,4 +13,8 @@ export class AbstractTencentPlugin extends AbstractPlugin {
throw new Error('执行失败:' + ret.Error.Code + ',' + ret.Error.Message)
}
}
getSafetyDomain (domain) {
return domain.replace(/\*/g, '_')
}
}
@@ -0,0 +1,148 @@
import { AbstractTencentPlugin } from '../../tencent/abstract-tencent.js'
import tencentcloud from 'tencentcloud-sdk-nodejs'
import { K8sClient } from '../../utils/util.k8s.client.js'
export class DeployCertToTencentTKEIngress extends AbstractTencentPlugin {
/**
* 插件定义
* 名称
* 入参
* 出参
*/
static define () {
return {
name: 'deployCertToTencentTKEIngress',
label: '部署到腾讯云TKE-ingress',
input: {
clusterId: {
label: '集群ID',
required: true,
desc: '例如:cls-6lbj1vee'
},
region: {
label: '大区',
value: 'ap-guangzhou'
},
secreteName: {
label: '证书的secret名称'
},
ingressName: {
label: 'ingress名称'
},
accessProvider: {
label: 'Access提供者',
type: [String, Object],
desc: 'AccessProviders的key 或 一个包含accessKeyId与accessKeySecret的对象',
options: 'accessProviders[type=tencent]',
required: true
}
},
output: {
}
}
}
async execute ({ accessProviders, cert, props, context }) {
const accessProvider = this.getAccessProvider(props.accessProvider, accessProviders)
const tkeClient = this.getTkeClient(accessProvider, props.region)
const kubeConfigStr = await this.getTkeKubeConfig(tkeClient, props.clusterId)
const k8sClient = new K8sClient(kubeConfigStr)
await this.patchCertSecret({ k8sClient, props, context })
await this.sleep(2000) // 停留2秒,等待secret部署完成
await this.restartIngress({ k8sClient, props })
return true
}
getTkeClient (accessProvider, region = 'ap-guangzhou') {
const TkeClient = tencentcloud.tke.v20180525.Client
const clientConfig = {
credential: {
secretId: accessProvider.secretId,
secretKey: accessProvider.secretKey
},
region,
profile: {
httpProfile: {
endpoint: 'tke.tencentcloudapi.com'
}
}
}
return new TkeClient(clientConfig)
}
async getTkeKubeConfig (client, clusterId) {
// Depends on tencentcloud-sdk-nodejs version 4.0.3 or higher
const params = {
ClusterId: clusterId
}
const ret = await client.DescribeClusterKubeconfig(params)
this.checkRet(ret)
return ret.Kubeconfig
}
async createCertSecret ({ k8sClient, props, cert, context }) {
const { tencentCertId } = context
if (tencentCertId == null) {
throw new Error('请先将【上传证书到腾讯云】作为前置任务')
}
const certIdBase64 = Buffer.from(tencentCertId).toString('base64')
let name = 'cert-' + cert.domain.replace(/\./g, '-')
name = name.replace(/\*/g, '-')
// name = this.appendTimeSuffix(name)
const body = {
kind: 'Secret',
data: {
qcloud_cert_id: certIdBase64
},
metadata: {
name,
labels: {
certd: 'certd-' + this.getSafetyDomain(cert.domain)
}
},
type: 'Opaque'
}
return await k8sClient.createSecret({ namespace: props.namespace, body: body })
}
async patchCertSecret ({ k8sClient, props, context }) {
const { tencentCertId } = context
if (tencentCertId == null) {
throw new Error('请先将【上传证书到腾讯云】作为前置任务')
}
const certIdBase64 = Buffer.from(tencentCertId).toString('base64')
const { namespace, secretName } = props
const body = {
data: {
qcloud_cert_id: certIdBase64
},
metadata: {
labels: {
certd: this.appendTimeSuffix('certd')
}
}
}
return await k8sClient.patchSecret({ namespace, secretName, body })
}
async restartIngress ({ k8sClient, props }) {
const { namespace, ingressName } = props
const body = {
metadata: {
labels: {
certd: this.appendTimeSuffix('certd')
}
}
}
return await k8sClient.patchIngress({ namespace, ingressName, body })
}
}
@@ -0,0 +1,80 @@
import kubernetesClient from 'kubernetes-client'
import logger from './util.log.js'
import Request from 'kubernetes-client/backends/request/index.js'
const { KubeConfig, Client } = kubernetesClient
export class K8sClient {
constructor (kubeConfigStr) {
const kubeconfig = new KubeConfig()
kubeconfig.loadFromString(kubeConfigStr)
const backend = new Request({ kubeconfig })
this.client = new Client({ backend, version: '1.13' })
}
/**
* 查询 secret列表
* @param opts = {namespace:default}
* @returns secretsList
*/
async getSecret (opts) {
const namespace = opts?.namespace || 'default'
const secrets = await this.client.api.v1.namespaces(namespace).secrets.get()
return secrets
}
/**
* 创建Secret
* @param opts {namespace:default, body:yamlStr}
* @returns {Promise<*>}
*/
async createSecret (opts) {
const namespace = opts?.namespace || 'default'
const created = await this.client.api.v1.namespaces(namespace).secrets.post({
body: opts.body
})
logger.info('new secrets:', created)
return created
}
async updateSecret (opts) {
const namespace = opts?.namespace || 'default'
const secretName = opts?.secretName
if (secretName == null) {
throw new Error('secretName 不能为空')
}
return await this.client.api.v1.namespaces(namespace).secrets(secretName).put({
body: opts.body
})
}
async patchSecret (opts) {
const namespace = opts?.namespace || 'default'
const secretName = opts?.secretName
if (secretName == null) {
throw new Error('secretName 不能为空')
}
return await this.client.api.v1.namespaces(namespace).secrets(secretName).patch({
body: opts.body
})
}
async getIngress (opts) {
const namespace = opts?.namespace || 'default'
const ingressName = opts?.ingressName
if (!ingressName) {
throw new Error('ingressName 不能为空')
}
return await this.client.apis.extensions.v1beta1.namespaces(namespace).ingresses(ingressName).get()
}
async patchIngress (opts) {
const namespace = opts?.namespace || 'default'
const ingressName = opts?.ingressName
if (!ingressName) {
throw new Error('ingressName 不能为空')
}
return await this.client.apis.extensions.v1beta1.namespaces(namespace).ingresses(ingressName).patch({
body: opts.body
})
}
}
@@ -1,13 +1,16 @@
import pkg from 'chai'
import { DeployCertToAliyunCDN } from '../../src/aliyun/deploy-to-cdn/index.js'
import options from '../options.js'
import { Certd } from '@certd/certd'
import createOptions from '../../../../test/options.js'
const { expect } = pkg
describe('DeployToAliyunCDN', function () {
it('#execute', async function () {
this.timeout(5000)
const options = createOptions()
const plugin = new DeployCertToAliyunCDN()
const certd = new Certd()
const cert = certd.readCurrentCert('xiaojunnuo@qq.com', ['*.docmirror.cn'])
options.cert.domains = ['*.docmirror.cn', 'docmirror.cn']
const certd = new Certd(options)
const cert = await certd.readCurrentCert()
const ret = await plugin.doExecute({
accessProviders: options.accessProviders,
cert,
@@ -1,21 +1,28 @@
import pkg from 'chai'
import { UploadCertToAliyun } from '../../src/aliyun/upload-to-aliyun/index.js'
import options from '../options.js'
import { Certd } from '@certd/certd'
import { createOptions } from '../../../../test/options.js'
const { expect } = pkg
describe('PluginUploadToAliyun', function () {
it('#execute', async function () {
this.timeout(5000)
const options = createOptions()
options.cert.email = 'xiaojunnuo@qq.com'
options.cert.domains = ['_.docmirror.cn']
const plugin = new UploadCertToAliyun()
const certd = new Certd()
const cert = certd.readCurrentCert('xiaojunnuo@qq.com', ['_.docmirror.cn'])
const certd = new Certd(options)
const cert = await certd.readCurrentCert()
const context = {}
await plugin.doExecute({
const deployOpts = {
accessProviders: options.accessProviders,
cert,
props: { name: 'certd部署测试', accessProvider: 'aliyun' },
props: { accessProvider: 'aliyun' },
context
})
}
await plugin.doExecute(deployOpts)
console.log('context:', context)
// await plugin.sleep(1000)
// await plugin.rollback(deployOpts)
})
})
@@ -0,0 +1,111 @@
import pkg from 'chai'
import { DeployCertToTencentTKEIngress } from '../../src/tencent/deploy-to-tke-ingress/index.js'
import { Certd } from '@certd/certd'
import { createOptions } from '../../../../test/options.js'
import { K8sClient } from '../../src/utils/util.k8s.client.js'
const { expect } = pkg
async function getOptions () {
const options = createOptions()
options.args.test = false
options.cert.email = 'xiaojunnuo@qq.com'
options.cert.domains = ['*.docmirror.cn']
const certd = new Certd(options)
const cert = await certd.readCurrentCert()
const context = {}
const deployOpts = {
accessProviders: options.accessProviders,
cert,
props: {
accessProvider: 'tencent-yonsz',
region: 'ap-guangzhou',
clusterId: 'cls-6lbj1vee'
},
context
}
return { options, deployOpts }
}
describe('DeployCertToTencentTKEIngress', function () {
// it('#getTkeKubeConfig', async function () {
// const { options, deployOpts } = await getOptions()
// const plugin = new DeployCertToTencentTKEIngress()
// const tkeClient = plugin.getTkeClient(options.accessProviders[deployOpts.props.accessProvider], deployOpts.props.region)
// const kubeConfig = await plugin.getTkeKubeConfig(tkeClient, deployOpts.props)
// console.log('kubeConfig:', kubeConfig)
// })
//
// it('#getTKESecrets', async function () {
// this.timeout(5000)
// const { options, deployOpts } = await getOptions()
// const plugin = new DeployCertToTencentTKEIngress()
// const tkeClient = plugin.getTkeClient(options.accessProviders[deployOpts.props.accessProvider], deployOpts.props.region)
// const kubeConfig = await plugin.getTkeKubeConfig(tkeClient, deployOpts.props)
//
// const k8sClient = new K8sClient(kubeConfig)
// const secrets = await k8sClient.getSecret()
//
// console.log('secrets:', secrets)
// })
//
// it('#patchTKECertSecrets', async function () {
// this.timeout(5000)
//
// const { options, deployOpts } = await getOptions()
// const plugin = new DeployCertToTencentTKEIngress()
// const tkeClient = plugin.getTkeClient(options.accessProviders[deployOpts.props.accessProvider], deployOpts.props.region)
// const kubeConfig = await plugin.getTkeKubeConfig(tkeClient, deployOpts.props)
// const k8sClient = new K8sClient(kubeConfig)
//
// deployOpts.k8sClient = k8sClient
// deployOpts.context.tencentCertId = 'hNVD3Z45'
// const newCecret = await plugin.patchCertSecret(deployOpts)
// console.log('newCecret', newCecret)
// })
// it('#GetTkeIngress', async function () {
// this.timeout(5000)
//
// const { options, deployOpts } = await getOptions()
// deployOpts.props.ingressName = 'ingress-base'
// deployOpts.props.secretName = 'cert---docmirror-cn'
// const plugin = new DeployCertToTencentTKEIngress()
// const tkeClient = plugin.getTkeClient(options.accessProviders[deployOpts.props.accessProvider], deployOpts.props.region)
// const kubeConfig = await plugin.getTkeKubeConfig(tkeClient, deployOpts.props)
//
// const k8sClient = new K8sClient(kubeConfig)
// const ingress = await k8sClient.getIngress({
// ingressName: 'ingress-base'
// })
// console.log('ingress:', ingress)
// })
// it('#RestartTKEIngress', async function () {
// this.timeout(5000)
//
// const { options, deployOpts } = await getOptions()
// deployOpts.props.ingressName = 'ingress-base'
// deployOpts.props.secretName = 'cert---docmirror-cn'
// const plugin = new DeployCertToTencentTKEIngress()
// const tkeClient = plugin.getTkeClient(options.accessProviders[deployOpts.props.accessProvider], deployOpts.props.region)
// const kubeConfig = await plugin.getTkeKubeConfig(tkeClient, deployOpts.props)
//
// const k8sClient = new K8sClient(kubeConfig)
//
// deployOpts.k8sClient = k8sClient
// deployOpts.context.tencentCertId = 'hNVD3Z45'
// const newCecret = await plugin.restartIngress(deployOpts)
// console.log('newCecret', newCecret)
// })
it('#execute', async function () {
this.timeout(5000)
const { deployOpts } = await getOptions()
deployOpts.props.ingressName = 'ingress-base'
deployOpts.props.secretName = 'cert---docmirror-cn'
deployOpts.context.tencentCertId = 'hNUZJrZf'
const plugin = new DeployCertToTencentTKEIngress()
const ret = await plugin.doExecute(deployOpts)
console.log('sucess', ret)
})
})
File diff suppressed because it is too large Load Diff