perf: 添加部署证书至火山 Live

- 新增 VolcengineDeployToLive 插件,用于将证书部署到火山引擎视频直播
- 新增 VolcengineDeployToVOD 插件,用于将证书部署到火山引擎视频点播
- 更新 ve-client.ts,增加对 Live 和 VOD 服务的支持
pull/409/head
xiaojunnuo 2025-04-21 23:39:33 +08:00
parent 42dfe936b7
commit abea80e3ab
4 changed files with 373 additions and 2 deletions

View File

@ -2,3 +2,4 @@ export * from './plugin-deploy-to-cdn.js'
export * from './plugin-deploy-to-clb.js'
export * from './plugin-upload-to-cert-center.js'
export * from './plugin-deploy-to-alb.js'
export * from './plugin-deploy-to-live.js'

View File

@ -0,0 +1,144 @@
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert";
import { VolcengineAccess } from "../access.js";
import { VolcengineClient } from "../ve-client.js";
@IsTaskPlugin({
name: "VolcengineDeployToLive",
title: "火山引擎-部署证书至Live",
icon: "svg:icon-volcengine",
group: pluginGroups.volcengine.key,
desc: "部署至火山引擎视频直播",
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed
}
}
})
export class VolcengineDeployToLive extends AbstractTaskPlugin {
@TaskInput({
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: "output-selector",
from: [...CertApplyPluginNames]
},
required: true
})
cert!: CertInfo;
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
certDomains!: string[];
@TaskInput({
title: "Access授权",
helper: "火山引擎AccessKeyId、AccessKeySecret",
component: {
name: "access-selector",
type: "volcengine"
},
required: true
})
accessId!: string;
@TaskInput(
createRemoteSelectInputDefine({
title: "直播域名",
helper: "选择要部署证书的直播域名",
action: VolcengineDeployToLive.prototype.onGetDomainList.name,
watches: ["certDomains", "accessId"],
required: true
})
)
domainList!: string | string[];
async onInstance() {
}
async execute(): Promise<void> {
this.logger.info("开始部署证书到火山引擎视频直播");
const service = await this.getLiveService();
let certId = await this.uploadCert(service);
for (const item of this.domainList) {
this.logger.info(`开始部署直播域名${item}证书`);
await service.request({
action: "BindCert",
body: {
Domain: item,
HTTPS: true,
ChainID: certId
}
});
this.logger.info(`部署直播域名${item}证书成功`);
}
this.logger.info("部署完成");
}
private async uploadCert(liveService: any) {
const res = await liveService.request({
action: "CreateCert",
body: {
Rsa: {
Pubkey: this.cert.crt,
Prikey: this.cert.key
},
UseWay: "https"
}
});
const certId = res.Result.ChainID;
this.logger.info("证书上传成功", certId);
return certId;
}
private async getLiveService() {
const access = await this.getAccess<VolcengineAccess>(this.accessId);
const client = new VolcengineClient({
logger: this.logger,
access,
http: this.http
});
return await client.getLiveService();
}
async onGetDomainList(data: any) {
if (!this.accessId) {
throw new Error("请选择Access授权");
}
const service = await this.getLiveService();
const res = await service.request({
action: "ListDomainDetail",
body: {
"PageNum": 1,
"PageSize": 100
}
});
const list = res.Result?.DomainList;
if (!list || list.length === 0) {
throw new Error("找不到直播域名,您也可以手动输入域名");
}
const options = list.map((item: any) => {
return {
value: item.Domain,
label: `${item.Domain}<${item.Type}>`,
domain: item.Domain
};
});
return this.ctx.utils.options.buildGroupOptions(options, this.certDomains);
}
}
new VolcengineDeployToLive();

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 { VolcengineAccess } from "../access.js";
import { VolcengineClient } from "../ve-client.js";
@IsTaskPlugin({
name: "VolcengineDeployToVOD",
title: "火山引擎-部署证书至VOD",
icon: "svg:icon-volcengine",
group: pluginGroups.volcengine.key,
desc: "部署至火山引擎视频点播(暂不可用)",
deprecated:"暂时缺少部署ssl接口",
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed
}
}
})
export class VolcengineDeployToVOD 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(
// createRemoteSelectInputDefine({
// title: "空间名称",
// helper: "选择要部署证书的监听器\n需要在监听器中选择证书中心进行跨服务访问授权",
// action: VolcengineDeployToVOD.prototype.onGetSpaceList.name,
// watches: ["certDomains", "accessId", "regionId"],
// required: true
// })
{
title: "空间名称",
required: true
}
)
spaceName!: string;
@TaskInput(
createRemoteSelectInputDefine({
title: "点播域名",
helper: "选择要部署证书的点播域名\n需要先在域名管理页面进行证书中心访问授权即点击去配置SSL证书",
action: VolcengineDeployToVOD.prototype.onGetDomainList.name,
watches: ["certDomains", "accessId", "spaceName"],
required: true
})
)
domainList!: string | string[];
async onInstance() {
}
async execute(): Promise<void> {
this.logger.info("开始部署证书到火山引擎VOD");
const access = await this.getAccess<VolcengineAccess>(this.accessId);
let certId = await this.uploadOrGetCertId(access);
const service = await this.getVodService();
for (const item of this.domainList) {
this.logger.info(`开始部署点播域名${item}证书`);
await service.request({
action: "ModifyListenerAttributes",
query: {
ListenerId: item,
CertificateSource: "cert_center",
CertCenterCertificateId: certId
}
});
this.logger.info(`部署点播域名${item}证书成功`);
}
this.logger.info("部署完成");
}
private async uploadOrGetCertId(access: VolcengineAccess) {
const certService = await this.getCertService(access);
let certId = this.cert;
if (typeof certId !== "string") {
const certInfo = this.cert as CertInfo;
this.logger.info(`开始上传证书`);
certId = await certService.ImportCertificate({
certName: this.appendTimeSuffix("certd"),
cert: certInfo
});
this.logger.info(`上传证书成功:${certId}`);
} else {
this.logger.info(`使用已有证书ID:${certId}`);
}
return certId;
}
private async getCertService(access: VolcengineAccess) {
const client = new VolcengineClient({
logger: this.logger,
access,
http: this.http
});
return await client.getCertCenterService();
}
private async getVodService(req?: { version?: string }) {
const access = await this.getAccess<VolcengineAccess>(this.accessId);
const client = new VolcengineClient({
logger: this.logger,
access,
http: this.http
});
return await client.getVodService(req);
}
// async onGetSpaceList(data: any) {
// if (!this.accessId) {
// throw new Error("请选择Access授权");
// }
// const service = await this.getVodService();
//
// const res = await service.request({
// action: "ListSpace",
// method: "GET",
// query: {
// PageSize: 100,
// },
// });
//
// const list = res.Result;
// if (!list || list.length === 0) {
// throw new Error("找不到空间,您可以手动填写");
// }
// return list.map((item: any) => {
// return {
// value: item.SpaceName,
// label: `${item.SpaceName}`
// };
// });
// }
async onGetDomainList(data: any) {
if (!this.accessId) {
throw new Error("请选择Access授权");
}
const service = await this.getVodService();
const res = await service.request({
action: "ListDomain",
body: {
SpaceName: this.spaceName,
Offset: 100
}
});
const instances = res.Result?.PlayInstanceInfo?.ByteInstances;
if (!instances || instances.length === 0) {
throw new Error("找不到点播域名,您也可以手动输入点播域名");
}
const list = []
for (const item of instances) {
for (const domain of item.Domains) {
list.push({
value: item.Domain,
label: item.Domain,
domain: domain.Domain
});
}
}
return this.ctx.utils.options.buildGroupOptions(list, this.certDomains);
}
}
new VolcengineDeployToVOD();

View File

@ -28,7 +28,7 @@ export class VolcengineClient {
service.ImportCertificate = async (body: { certName: string, cert: any }) => {
const { certName, cert } = body;
const res = await service.request({
const res = await service.request({
action: "ImportCertificate",
method: "POST",
body: {
@ -40,7 +40,7 @@ export class VolcengineClient {
}
}
});
return res.Result.InstanceId || res.Result.RepeatId
return res.Result.InstanceId || res.Result.RepeatId;
};
return service;
}
@ -59,6 +59,33 @@ export class VolcengineClient {
return service;
}
async getLiveService() {
const CommonService = await this.getServiceCls();
const service = new CommonService({
serviceName: "live",
defaultVersion: "2023-01-01"
});
service.setAccessKeyId(this.opts.access.accessKeyId);
service.setSecretKey(this.opts.access.secretAccessKey);
service.setRegion("cn-north-1");
return service;
}
async getVodService(opts?: { version?: string }) {
const CommonService = await this.getServiceCls();
const service = new CommonService({
serviceName: "vod",
defaultVersion: opts?.version || "2021-01-01"
});
service.setAccessKeyId(this.opts.access.accessKeyId);
service.setSecretKey(this.opts.access.secretAccessKey);
return service;
}
async getAlbService(opts: { region?: string }) {
const CommonService = await this.getServiceCls();