diff --git a/packages/certd/src/index.js b/packages/certd/src/index.js index 84189fe9..395c75e3 100644 --- a/packages/certd/src/index.js +++ b/packages/certd/src/index.js @@ -85,15 +85,9 @@ export class Certd { isTest: options.args.test }) - const certDir = this.writeCert(options.cert.email, options.cert.domains, cert) - const { detail, expires } = this.getCrtDetail(cert.crt) - return { - ...cert, - detail, - expires, - certDir, - isNew: true - } + this.writeCert(options.cert.email, options.cert.domains, cert) + const certRet = this.readCurrentCert(options.cert.email, options.cert.domains) + certRet.isNew = true } async createDnsProvider (options) { @@ -126,6 +120,10 @@ export class Certd { return linkPath } + readCurrentCertByOptions (options) { + return this.readCurrentCert(options.cert.email, options.cert.domains) + } + readCurrentCert (email, domains) { const certFilesRootDir = this.buildCertDir(email, domains) const currentPath = path.join(certFilesRootDir, 'current') @@ -137,12 +135,12 @@ export class Certd { } const key = this.store.get(currentPath + `/${domainFileName}.key`) const csr = this.store.get(currentPath + `/${domainFileName}.csr`) - const { detail, expires } = this.getCrtDetail(crt) - const certDir = this.store.getActualKey(currentPath) + + const domain = this.getMainDomain(domains) return { - crt, key, csr, detail, expires, certDir + crt, key, csr, detail, expires, certDir, domain, domains, email } } diff --git a/packages/certd/test/index.test.js b/packages/certd/test/index.test.js index 499d853f..fa5164dc 100644 --- a/packages/certd/test/index.test.js +++ b/packages/certd/test/index.test.js @@ -3,35 +3,37 @@ import { Certd } from '../src/index.js' import { createOptions } from '../../../test/options.js' const { expect } = chai const fakeCrt = `-----BEGIN CERTIFICATE----- -MIIFNTCCBB2gAwIBAgITAPpZZ0r22X9e/yCu3/DPCqYZUTANBgkqhkiG9w0BAQsF -ADAiMSAwHgYDVQQDDBdGYWtlIExFIEludGVybWVkaWF0ZSBYMTAeFw0yMDEyMTMw -OTI5NTFaFw0yMTAzMTMwOTI5NTFaMBkxFzAVBgNVBAMMDiouZG9jbWlycm9yLmNu -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7ejWwrYC0Z7dbYJgT87I -cZ9ymvxS3OI5OymhE4IscZIWPJTJ/AklZhzUDY61kC2CM+ixWNF8/w17Z2yRdhkV -PmAuNzn47kCHi3voaq5Gb16jYHB160ak6Reds/o4V8Kypwgifx4GjMGDYCRHD9Rg -YD/WaTN68Ir337WPdhRBJYBKOVhjmiAOuIvyHz+oOhbB+9kOb+Fg9LYGHlxeE2lC -ADuU3YstHbSfHS+U11BDcOXXlhpnX2EkNQntzr6QgEvWOahB8h9nKDeF2lQCe019 -UyNyZtiKgWajGzHdWT6e2XppWljtEDQsyMCncDQUOcFU3isCp0+YgcpIZi0r98m7 -fQIDAQABo4ICazCCAmcwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUF -BwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBSlRx7ZafevdHLp -XoM14DoCiNtdRTAfBgNVHSMEGDAWgBTAzANGuVggzFxycPPhLssgpvVoOjB3Bggr -BgEFBQcBAQRrMGkwMgYIKwYBBQUHMAGGJmh0dHA6Ly9vY3NwLnN0Zy1pbnQteDEu -bGV0c2VuY3J5cHQub3JnMDMGCCsGAQUFBzAChidodHRwOi8vY2VydC5zdGctaW50 -LXgxLmxldHNlbmNyeXB0Lm9yZy8wGQYDVR0RBBIwEIIOKi5kb2NtaXJyb3IuY24w -TAYDVR0gBEUwQzAIBgZngQwBAgEwNwYLKwYBBAGC3xMBAQEwKDAmBggrBgEFBQcC -ARYaaHR0cDovL2Nwcy5sZXRzZW5jcnlwdC5vcmcwggEEBgorBgEEAdZ5AgQCBIH1 -BIHyAPAAdgAD7fHal3a284w0HjntnXB6dXA2nPmETzJ/6eFBODYbYAAAAXZbpkHw -AAAEAwBHMEUCIQDAsD0yStiWkCh0pop21/o+3nc56Mz7XIvBMvKXgGR6BgIgKDUN -+auptABhgABECoGpHdv5jVcs1MwJrySV81O0w5MAdgCwzIPlpfl9a698CcwoSQSH -KsfoixMsY1C3xv0m4WxsdwAAAXZbpkH6AAAEAwBHMEUCIQDenPpUWTbnxO45ISpC -6kZo9xfqS5yEYM4VfOtf46iI7gIgNLT8bZnf6jcfwiS0AC9mfrV5nZgfCF4fgu5J -z+7GRV8wDQYJKoZIhvcNAQELBQADggEBAOuOY+C3ugzfAHc8PX3a+6MrWJi3s4bg -DNg1ViY5R6n+F1nonUaHsThHZglDfwWgxOstxzzLU9f/jLOnVmeMz96vnxefW4+f -jgXeYRupja94VufAjIdFOJIEjCLwH/Zs8PpMUcxSf58J6dScMVmptenrsQWyRkEA -CEsZKQnjL00F5QlIls+yczYoToD6Pt9hGhJ9NG2rWMwcbflXW8l7E8eQPegYFYw3 -EB1qffDdoJsGId4BwoRNt1N1uHl63ZFFMLmVV+SWXUrwf3Zpj5nxzk3d3bvh9k43 -GbIyf5y6M6DOiUymEY8izoozC/aVb32Z860d5pRGjkRNLD8jKuupd7I= +MIIFSTCCBDGgAwIBAgITAPoZZk/LhVIyXoic2NnJyxubezANBgkqhkiG9w0BAQsF +ADAiMSAwHgYDVQQDDBdGYWtlIExFIEludGVybWVkaWF0ZSBYMTAeFw0yMDEyMTQx +NjA1NTFaFw0yMTAzMTQxNjA1NTFaMBsxGTAXBgNVBAMMECouZG9jbWlycm9yLmNs +dWIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC75tGrYjly+RpcZehQ +my1EpaXElT4L60pINKV2YDKnBrcSSo1c6rO7nFh12eC/ju4WwYUep0RVmBDF8xD0 +I1Sd1uuDTQWP0UT1X9yqdXtjvxpUqoCHAzG633f3sJRFul7mDLuC9tRCuae9o7qP +EZ827XOmjBR35dso9I2GEE4828J3YE3tSKtobZlM+30jozLEcsO0PTyM5mq5PPjP +VI3fGLcEaBmLZf5ixz4XkcY9IAhyAMYf03cT2wRoYPBaDdXblgCYL6sFtIMbzl3M +Di94PB8NyoNSsC2nmBdWi54wFOgBvY/4ljsX/q7X3EqlSvcA0/M6/c/J9kJ3eupv +jV8nAgMBAAGjggJ9MIICeTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYB +BQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFAkdTjSCV3KD +x28sf98MrwVfyFYgMB8GA1UdIwQYMBaAFMDMA0a5WCDMXHJw8+EuyyCm9Wg6MHcG +CCsGAQUFBwEBBGswaTAyBggrBgEFBQcwAYYmaHR0cDovL29jc3Auc3RnLWludC14 +MS5sZXRzZW5jcnlwdC5vcmcwMwYIKwYBBQUHMAKGJ2h0dHA6Ly9jZXJ0LnN0Zy1p +bnQteDEubGV0c2VuY3J5cHQub3JnLzArBgNVHREEJDAighAqLmRvY21pcnJvci5j +bHVigg5kb2NtaXJyb3IuY2x1YjBMBgNVHSAERTBDMAgGBmeBDAECATA3BgsrBgEE +AYLfEwEBATAoMCYGCCsGAQUFBwIBFhpodHRwOi8vY3BzLmxldHNlbmNyeXB0Lm9y +ZzCCAQQGCisGAQQB1nkCBAIEgfUEgfIA8AB1ABboacHRlerXw/iXGuPwdgH3jOG2 +nTGoUhi2g38xqBUIAAABdmI3LM4AAAQDAEYwRAIgaiNqXSEq+sxp8eqlJXp/KFdO +so5mT50MoRsLF8Inu0ACIDP46+ekng7I0BlmyIPmbqFcZgnZFVWLLCdLYijhVyOL +AHcA3Zk0/KXnJIDJVmh9gTSZCEmySfe1adjHvKs/XMHzbmQAAAF2YjcuxwAABAMA +SDBGAiEAxpeB8/w4YkHZ62nH20h128VtuTSmYDCnF7EK2fQyeZYCIQDbJlF2wehZ +sF1BeE7qnYYqCTP0dYIrQ9HWtBa/MbGOKTANBgkqhkiG9w0BAQsFAAOCAQEAL2di +HKh6XcZtGk0BFxJa51sCZ3MLu9+Zy90kCRD4ooP5x932WxVM25+LBRd+xSzx+TRL +UVrlKp9GdMYX1JXL4Vf2NwzuFO3snPDe/qizD/3+D6yo8eKJ/LD82t5kLWAD2rto +YfVSTKwfNIBBJwHUnjviBPJmheHHCKmz8Ct6/6QxFAeta9TAMn0sFeVCQnmAq7HL +jrunq0tNHR/EKG0ITPLf+6P7MxbmpYNnq918766l0tKsW8oo8ZSGEwKU2LMaSiAa +hasyl/2gMnYXjtKOjDcnR8oLpbrOg0qpVbynmJin1HP835oHPPAZ1gLsqYTTizNz +AHxTaXliTVvS83dogw== -----END CERTIFICATE----- + -----BEGIN CERTIFICATE----- MIIEqzCCApOgAwIBAgIRAIvhKg5ZRO08VGQx8JdhT+UwDQYJKoZIhvcNAQELBQAw GjEYMBYGA1UEAwwPRmFrZSBMRSBSb290IFgxMB4XDTE2MDUyMzIyMDc1OVoXDTM2 diff --git a/packages/plugins/src/abstract-plugin/index.js b/packages/plugins/src/abstract-plugin/index.js index 1f9a5af1..e61b7fd7 100644 --- a/packages/plugins/src/abstract-plugin/index.js +++ b/packages/plugins/src/abstract-plugin/index.js @@ -1,17 +1,26 @@ import fs from 'fs' import logger from '../utils/util.log.js' +import dayjs from 'dayjs' +import Sleep from '../utils/util.sleep.js' export class AbstractPlugin { constructor () { this.logger = logger } + appendTimeSuffix (name) { + if (name == null) { + name = 'certd' + } + return name + '-' + dayjs().format('YYYYMMDD-HHmmss') + } + async executeFromContextFile (options = {}) { const { contextPath } = options const contextJson = fs.readFileSync(contextPath) const context = JSON.parse(contextJson) options.context = context - const newContext = await this.execute(options) - fs.writeFileSync(JSON.stringify(newContext || context)) + await this.doExecute(options) + fs.writeFileSync(JSON.stringify(context)) } async doExecute (options) { @@ -55,4 +64,8 @@ export class AbstractPlugin { } return accessProvider } + + async sleep (time) { + await Sleep(time) + } } diff --git a/packages/plugins/src/tencent/abstract-tencent.js b/packages/plugins/src/tencent/abstract-tencent.js index ee4d2a60..76103bda 100644 --- a/packages/plugins/src/tencent/abstract-tencent.js +++ b/packages/plugins/src/tencent/abstract-tencent.js @@ -9,7 +9,7 @@ export class AbstractTencentPlugin extends AbstractPlugin { } checkRet (ret) { - if (ret && ret.Error) { + if (!ret || ret.Error) { throw new Error('执行失败:' + ret.Error.Code + ',' + ret.Error.Message) } } diff --git a/packages/plugins/src/tencent/deploy-to-cdn/index.js b/packages/plugins/src/tencent/deploy-to-cdn/index.js index d25ac7af..69419811 100644 --- a/packages/plugins/src/tencent/deploy-to-cdn/index.js +++ b/packages/plugins/src/tencent/deploy-to-cdn/index.js @@ -57,7 +57,6 @@ export class DeployCertToTencentCDN extends AbstractTencentPlugin { const client = this.getClient(accessProvider) const params = this.buildParams(props, context, cert) await this.doRequest(client, params) - return context } async rollback ({ accessProviders, cert, props, context }) { @@ -98,7 +97,7 @@ export class DeployCertToTencentCDN extends AbstractTencentPlugin { }, Domain: domainName } - if (from === 'upload') { + if (from === 'upload' || tencentCertId == null) { params.Https.CertInfo = { Certificate: this.format(cert.crt.toString()), PrivateKey: this.format(cert.key.toString()) diff --git a/packages/plugins/src/tencent/deploy-to-clb/index.js b/packages/plugins/src/tencent/deploy-to-clb/index.js new file mode 100644 index 00000000..b2e31c10 --- /dev/null +++ b/packages/plugins/src/tencent/deploy-to-clb/index.js @@ -0,0 +1,191 @@ +import { AbstractTencentPlugin } from '../../tencent/abstract-tencent.js' +import tencentcloud from 'tencentcloud-sdk-nodejs' +export class DeployCertToTencentCLB extends AbstractTencentPlugin { + /** + * 插件定义 + * 名称 + * 入参 + * 出参 + */ + static define () { + return { + name: 'deployCertToTencentCLB', + label: '部署到腾讯云CLB', + desc: '暂时只支持单向认证证书,暂时只支持通用负载均衡', + input: { + region: { + label: '大区', + value: 'ap-guangzhou' + }, + domain: { + label: '域名', + type: [String, Array], + desc: '要更新的支持https的负载均衡的域名' + }, + loadBalancerId: { + label: '负载均衡ID', + desc: '如果没有配置,则根据域名匹配负载均衡下的监听器(根据域名匹配时暂时只支持前100个)' + }, + listenerId: { + label: '监听器ID', + desc: '如果没有配置,则根据域名或负载均衡id匹配监听器' + }, + certName: { + label: '证书名称', + desc: '如无uploadCertToTencent作为前置,则此项需要设置,默认为域名' + }, + 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 { region } = props + const client = this.getClient(accessProvider, region) + + const lastCertId = await this.getCertIdFromProps(client, props) + if (!props.domain) { + await this.updateListener(client, cert, props, context) + } else { + await this.updateByDomainAttr(client, cert, props, context) + } + + try { + await this.sleep(2000) + let newCertId = await this.getCertIdFromProps(client, props) + if ((lastCertId && newCertId === lastCertId) || (!lastCertId && !newCertId)) { + await this.sleep(2000) + newCertId = await this.getCertIdFromProps(client, props) + } + if (newCertId === lastCertId) { + return {} + } + this.logger.info('腾讯云证书ID:', newCertId) + if (!context.tencentCertId) { + context.tencentCertId = newCertId + } + return { tencentCertId: newCertId } + } catch (e) { + this.logger.warn('查询腾讯云证书失败', e) + } + } + + async getCertIdFromProps (client, props) { + const listenerRet = await this.getListenerList(client, props.loadBalancerId, [props.listenerId]) + return this.getCertIdFromListener(listenerRet[0], props.domain) + } + + getCertIdFromListener (listener, domain) { + let certId + if (!domain) { + certId = listener.Certificate.CertId + } else { + if (listener.Rules && listener.Rules.length > 0) { + for (const rule of listener.Rules) { + if (rule.Domain === domain) { + if (rule.Certificate != null) { + certId = rule.Certificate.CertId + } + break + } + } + } + } + return certId + } + + async rollback ({ accessProviders, cert, props, context }) { + this.logger.warn('未实现rollback') + } + + async updateListener (client, cert, props, context) { + const params = this.buildProps(props, context, cert) + const ret = await client.ModifyListener(params) + this.checkRet(ret) + this.logger.info('设置腾讯云CLB证书成功:', ret.RequestId, '->loadBalancerId:', props.loadBalancerId, 'listenerId', props.listenerId) + return ret + } + + async updateByDomainAttr (client, cert, props, context) { + const params = this.buildProps(props, context, cert) + params.Domain = props.domain + const ret = await client.ModifyDomainAttributes(params) + this.checkRet(ret) + this.logger.info('设置腾讯云CLB证书(sni)成功:', ret.RequestId, '->loadBalancerId:', props.loadBalancerId, 'listenerId', props.listenerId, 'domain:', props.domain) + return ret + } + + buildProps (props, context, cert) { + const { certName } = props + const { tencentCertId } = context + + const params = { + Certificate: { + SSLMode: 'UNIDIRECTIONAL', // 单向认证 + CertId: tencentCertId + }, + LoadBalancerId: props.loadBalancerId, + ListenerId: props.listenerId + } + + if (tencentCertId == null) { + params.Certificate.CertName = this.appendTimeSuffix(certName || cert.domain) + params.Certificate.CertKey = this.format(cert.key.toString()) + params.Certificate.CertContent = this.format(cert.crt.toString()) + } + return params + } + + async getCLBList (client, props) { + const params = { + Limit: 100, // 最大暂时只支持100个,暂时没做翻页 + OrderBy: 'CreateTime', + OrderType: 0, + ...props.DescribeLoadBalancers + } + const ret = await client.DescribeLoadBalancers(params) + this.checkRet(ret) + return ret.LoadBalancerSet + } + + async getListenerList (client, balancerId, listenerIds) { + // HTTPS + const params = { + LoadBalancerId: balancerId, + Protocol: 'HTTPS', + ListenerIds: listenerIds + } + const ret = await client.DescribeListeners(params) + this.checkRet(ret) + return ret.Listeners + } + + getClient (accessProvider, region) { + const ClbClient = tencentcloud.clb.v20180317.Client + + const clientConfig = { + credential: { + secretId: accessProvider.secretId, + secretKey: accessProvider.secretKey + }, + region: region, + profile: { + httpProfile: { + endpoint: 'clb.tencentcloudapi.com' + } + } + } + + return new ClbClient(clientConfig) + } +} diff --git a/packages/plugins/src/tencent/upload-to-tencent/index.js b/packages/plugins/src/tencent/upload-to-tencent/index.js index df23fb87..a456e2b3 100644 --- a/packages/plugins/src/tencent/upload-to-tencent/index.js +++ b/packages/plugins/src/tencent/upload-to-tencent/index.js @@ -54,7 +54,7 @@ export class UploadCertToTencent extends AbstractTencentPlugin { async execute ({ accessProviders, cert, props, context, logger }) { const { name, accessProvider } = props - const certName = name + '-' + dayjs().format('YYYYMMDD-HHmmss') + const certName = this.appendTimeSuffix(name) const provider = super.getAccessProvider(accessProvider, accessProviders) const client = this.getClient(provider) @@ -70,7 +70,7 @@ export class UploadCertToTencent extends AbstractTencentPlugin { context.tencentCertId = ret.CertificateId } - async rollback ({ accessProviders, cert, props, context, logger }) { + async rollback ({ accessProviders, cert, props, context }) { const { accessProvider } = props const provider = super.getAccessProvider(accessProvider, accessProviders) const client = this.getClient(provider) diff --git a/packages/plugins/src/utils/util.sleep.js b/packages/plugins/src/utils/util.sleep.js new file mode 100644 index 00000000..298d90c2 --- /dev/null +++ b/packages/plugins/src/utils/util.sleep.js @@ -0,0 +1,7 @@ +export default function (timeout) { + return new Promise(resolve => { + setTimeout(() => { + resolve() + }, timeout) + }) +} diff --git a/packages/plugins/test/tencent/deploy-to-cdn.test.js b/packages/plugins/test/tencent/deploy-to-cdn.test.js index 4cd5ba87..cc919c0d 100644 --- a/packages/plugins/test/tencent/deploy-to-cdn.test.js +++ b/packages/plugins/test/tencent/deploy-to-cdn.test.js @@ -37,13 +37,13 @@ describe('DeployToTencentCDN', function () { const options = createOptions() options.args.test = false const plugin = new DeployCertToTencentCDN() - const certd = new Certd() + const certd = new Certd(options) const cert = certd.readCurrentCert('xiaojunnuo@qq.com', ['*.docmirror.cn']) const context = {} const deployOpts = { accessProviders: options.accessProviders, cert, - props: { domainName: 'tentcent-certd.docmirror.cn', from: 'upload', accessProvider: 'tencent' }, + props: { domainName: 'tentcent-certd.docmirror.cn', accessProvider: 'tencent' }, context } const ret = await plugin.doExecute(deployOpts) diff --git a/packages/plugins/test/tencent/deploy-to-clb.test.js b/packages/plugins/test/tencent/deploy-to-clb.test.js new file mode 100644 index 00000000..2a2aff13 --- /dev/null +++ b/packages/plugins/test/tencent/deploy-to-clb.test.js @@ -0,0 +1,106 @@ +import pkg from 'chai' +import { DeployCertToTencentCLB } from '../../src/tencent/deploy-to-clb/index.js' +import { Certd } from '@certd/certd' +// eslint-disable-next-line no-unused-vars +import { createOptions } from '../../../../test/options.js' +import { UploadCertToTencent } from '../../src/tencent/upload-to-tencent/index.js' +const { expect } = pkg +describe('DeployToTencentCLB', function () { + it('#execute-getClbList', async function () { + const options = createOptions() + options.args.test = false + options.cert.dnsProvider = 'tencent-yonsz' + const deployPlugin = new DeployCertToTencentCLB() + const props = { + region: 'ap-guangzhou', + domain: 'certd-test-no-sni.base.yonsz.net', + accessProvider: 'tencent-yonsz' + } + const accessProviders = options.accessProviders + const accessProvider = deployPlugin.getAccessProvider(props.accessProvider, accessProviders) + const { region } = props + const client = deployPlugin.getClient(accessProvider, region) + + const ret = await deployPlugin.getCLBList(client, props) + expect(ret.length > 0).ok + console.log('clb count:', ret.length) + }) + it('#execute-getListenerList', async function () { + const options = createOptions() + options.args.test = false + options.cert.dnsProvider = 'tencent-yonsz' + const deployPlugin = new DeployCertToTencentCLB() + const props = { + region: 'ap-guangzhou', + domain: 'certd-test-no-sni.base.yonsz.net', + accessProvider: 'tencent-yonsz', + loadBalancerId: 'lb-59yhe5xo' + } + const accessProviders = options.accessProviders + const accessProvider = deployPlugin.getAccessProvider(props.accessProvider, accessProviders) + const { region } = props + const client = deployPlugin.getClient(accessProvider, region) + + const ret = await deployPlugin.getListenerList(client, props.loadBalancerId, props) + expect(ret.length > 0).ok + console.log('clb count:', ret.length, ret) + }) + + it('#execute-no-sni-listenerId', async function () { + this.timeout(10000) + const options = createOptions() + options.args.test = false + options.cert.dnsProvider = 'tencent-yonsz' + const certd = new Certd(options) + const cert = certd.readCurrentCert('xiaojunnuo@qq.com', ['*.docmirror.cn']) + const deployPlugin = new DeployCertToTencentCLB() + const context = {} + const deployOpts = { + accessProviders: options.accessProviders, + cert, + props: { + region: 'ap-guangzhou', + loadBalancerId: 'lb-59yhe5xo', + listenerId: 'lbl-1vfwx8dq', + accessProvider: 'tencent-yonsz' + }, + context + } + const ret = await deployPlugin.doExecute(deployOpts) + expect(ret).ok + console.log('ret:', ret) + + // 删除测试证书 + const uploadPlugin = new UploadCertToTencent() + await uploadPlugin.doRollback(deployOpts) + }) + + it('#execute-sni-listenerId', async function () { + this.timeout(10000) + const options = createOptions() + options.args.test = false + options.cert.dnsProvider = 'tencent-yonsz' + const certd = new Certd(options) + const cert = certd.readCurrentCert('xiaojunnuo@qq.com', ['*.docmirror.cn']) + const deployPlugin = new DeployCertToTencentCLB() + const context = {} + const deployOpts = { + accessProviders: options.accessProviders, + cert, + props: { + region: 'ap-guangzhou', + loadBalancerId: 'lb-59yhe5xo', + listenerId: 'lbl-akbyf5ac', + domain: 'certd-test-sni.base.yonsz.net', + accessProvider: 'tencent-yonsz' + }, + context + } + const ret = await deployPlugin.doExecute(deployOpts) + expect(ret).ok + console.log('ret:', ret) + // 删除测试证书 + const uploadPlugin = new UploadCertToTencent() + await uploadPlugin.doRollback(deployOpts) + }) +})