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
@@ -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
})
}
}