mirror of
https://github.com/certd/certd.git
synced 2026-04-24 12:27:25 +08:00
feat: 腾讯云证书tke ingress
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
+699
-9
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user