perf: 支持部署到火山云tos自定义域名证书

https://github.com/certd/certd/issues/693
This commit is contained in:
xiaojunnuo
2026-03-26 00:05:30 +08:00
parent c5d285f755
commit af6deb99cd
7 changed files with 377 additions and 48 deletions

View File

@@ -87,6 +87,7 @@
"@simplewebauthn/server": "^13.2.3",
"@ucloud-sdks/ucloud-sdk-js": "^0.2.4",
"@volcengine/openapi": "^1.28.1",
"@volcengine/tos-sdk": "^2.9.1",
"ali-oss": "^6.21.0",
"alipay-sdk": "^4.13.0",
"axios": "^1.9.0",

View File

@@ -5,3 +5,4 @@ export * from './plugin-deploy-to-alb.js'
export * from './plugin-deploy-to-live.js'
export * from './plugin-deploy-to-dcdn.js'
export * from './plugin-deploy-to-vod.js'
export * from './plugin-deploy-to-tos.js'

View File

@@ -100,7 +100,8 @@ export class VolcengineDeployToCDN extends AbstractTaskPlugin {
await service.UpdateCdnConfig({
Domain: domain,
HTTPS: {
CertInfo: { CertId: certId },
CertInfo: { CertId: certId as string },
Switch: true,
}
})
this.logger.info(`部署域名${domain}证书成功`);

View File

@@ -0,0 +1,199 @@
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert";
import { optionsUtils } from "@certd/basic";
import { VolcengineAccess } from "../access.js";
import { VolcengineClient } from "../ve-client.js";
@IsTaskPlugin({
name: 'VolcengineDeployToTOS',
title: '火山引擎-部署证书至TOS自定义域名',
icon: 'svg:icon-volcengine',
group: pluginGroups.volcengine.key,
desc: '支持TOS自定义域名的HTTPS证书部署',
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
})
export class VolcengineDeployToTOS extends AbstractTaskPlugin {
@TaskInput({
title: '域名证书',
helper: '请选择前置任务输出的域名证书',
component: {
name: 'output-selector',
from: [...CertApplyPluginNames, 'VolcengineUploadToCertCenter'],
},
required: true,
})
cert!: CertInfo | string;
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
certDomains!: string[];
@TaskInput({
title: 'Access授权',
helper: '火山引擎AccessKeyId、AccessKeySecret',
component: {
name: 'access-selector',
type: 'volcengine',
},
required: true,
})
accessId!: string;
@TaskInput({
title: '地域',
helper: 'TOS服务所在地域',
component: {
name: 'a-select',
options:[
{ label: "华北2北京", value: "cn-beijing" },
{ label: "华东2上海", value: "cn-shanghai" },
{ label: "华南1广州", value: "cn-guangzhou" },
{ label: "中国香港", value: "cn-hongkong" },
{ label: "亚太东南(柔佛)", value: "ap-southeast-1" },
{ label: "亚太东南(雅加达)", value: "ap-southeast-3" },
]
},
value: 'cn-beijing',
required: true,
})
region:string = "cn-beijing"
@TaskInput({
title: 'Bucket',
helper: '存储桶名称',
component: {
name: 'remote-auto-complete',
vModel: 'value',
type: 'plugin',
action: 'onGetBucketList',
search: false,
pager: false,
watches: ['accessId', 'region']
},
required: true,
})
bucket!: string;
@TaskInput(
createRemoteSelectInputDefine({
title: 'TOS自定义域名',
helper: '你在火山引擎上配置的TOS自定义域名比如:example.com',
action: VolcengineDeployToTOS.prototype.onGetDomainList.name,
watches: ['certDomains', 'accessId', 'region', 'bucket'],
required: true,
})
)
domainName!: string | string[];
async onInstance() {}
async execute(): Promise<void> {
this.logger.info('开始部署证书到火山引擎TOS自定义域名');
const access = await this.getAccess<VolcengineAccess>(this.accessId);
this.logger.info(`bucket: ${this.bucket}, region: ${this.region}, domainName: ${this.domainName}`);
const client = new VolcengineClient({
logger: this.logger,
access,
http: this.http
});
const tosService = await client.getTOSService({ region: this.region });
if (!this.cert) {
throw new Error('你还未选择证书');
}
let certId = this.cert
if (typeof certId !== 'string') {
const certInfo = this.cert as CertInfo
this.logger.info(`开始上传证书`)
const certService = await client.getCertCenterService();
certId = await certService.ImportCertificate({
certName: this.appendTimeSuffix('certd'),
cert: certInfo
})
this.logger.info(`上传证书成功:${certId}`);
}else{
this.logger.info(`使用已有证书ID${certId}`);
}
for (const domain of this.domainName) {
this.logger.info(`开始部署域名${domain}证书`)
await tosService.putBucketCustomDomain({
bucket: this.bucket,
customDomainRule: {
Domain: domain,
CertId: certId as string
}
})
this.logger.info(`部署域名${domain}证书成功`);
await this.ctx.utils.sleep(1000)
}
this.logger.info('部署完成');
}
async onGetDomainList(data: any) {
if (!this.accessId || !this.bucket) {
throw new Error('请选择Access授权和Bucket');
}
const access = await this.getAccess<VolcengineAccess>(this.accessId);
const client = new VolcengineClient({
logger: this.logger,
access,
http: this.http
});
const tosService = await client.getTOSService({ region: this.region });
const res = await tosService.getBucketCustomDomain({
bucket: this.bucket
});
const list = res?.data?.CustomDomainRules || [];
if (!list || list.length === 0) {
throw new Error('找不到TOS自定义域名您可以手动输入');
}
const options = list.map((item: any) => {
return {
value: item.Domain,
label: item.Domain,
domain: item.Domain,
};
});
return optionsUtils.buildGroupOptions(options, this.certDomains);
}
async onGetBucketList(data: any) {
if (!this.accessId) {
throw new Error('请选择Access授权');
}
const access = await this.getAccess<VolcengineAccess>(this.accessId);
const client = new VolcengineClient({
logger: this.logger,
access,
http: this.http
});
const tosService = await client.getTOSService({ region: this.region });
const res = await tosService.listBuckets();
const buckets = res?.data?.Buckets || [];
return buckets.map((bucket: any) => ({
label: `${bucket.Name}<${bucket.Location}>`,
value: bucket.Name
}));
}
}
new VolcengineDeployToTOS();

View File

@@ -124,6 +124,19 @@ export class VolcengineClient {
return service;
}
async getTOSService(opts: { region?: string }) {
const { TosClient } = await import("@volcengine/tos-sdk");
const client = new TosClient({
accessKeyId: this.opts.access.accessKeyId,
accessKeySecret: this.opts.access.secretAccessKey,
region: opts.region,
endpoint: `tos-${opts.region}.volces.com`
});
return client;
}
async getStsService() {
const CommonService = await this.getServiceCls();