perf: 支持部署到阿里云GA

This commit is contained in:
xiaojunnuo
2026-01-31 19:30:20 +08:00
parent c27636529d
commit 1a0d3eeb1b
14 changed files with 408 additions and 18 deletions

View File

@@ -1,8 +1,17 @@
import dayjs from "dayjs";
export const stringUtils = {
maxLength(str?: string, length = 100) {
if (str) {
return str.length > length ? str.slice(0, length) + '...' : str;
return str.length > length ? str.slice(0, length) + "..." : str;
}
return '';
return "";
},
appendTimeSuffix(str?: string) {
if (str) {
return `${str}-${dayjs().format("YYYYMMDDHHmmssSSS")}`;
}
return "";
},
};

View File

@@ -249,10 +249,7 @@ export abstract class AbstractTaskPlugin implements ITaskPlugin {
abstract execute(): Promise<void | string>;
appendTimeSuffix(name?: string) {
if (name == null) {
name = "certd";
}
return name + "_" + dayjs().format("YYYYMMDDHHmmssSSS");
return utils.string.appendTimeSuffix(name);
}
buildCertName(domain: string, prefix = "") {
@@ -297,6 +294,10 @@ export abstract class AbstractTaskPlugin implements ITaskPlugin {
getStepIdFromRefInput(ref = ".") {
return ref.split(".")[1];
}
buildDomainGroupOptions(options: any[], domains: string[]) {
return utils.options.buildGroupOptions(options, domains);
}
}
export type OutputVO = {

View File

@@ -305,11 +305,13 @@ export class AliyunDeployCertToALB extends AbstractTaskPlugin {
});
const certName = this.buildCertName(CertReader.getMainDomain(this.cert.crt));
certId = await sslClient.uploadCert({
const certIdRes = await sslClient.uploadCert({
name: certName,
cert: this.cert
});
certId = certIdRes.certId;
}
return certId;
}

View File

@@ -155,10 +155,11 @@ export class AliyunDeployCertToAll extends AbstractTaskPlugin {
//
let certId: any = this.cert;
if (typeof this.cert === "object") {
certId = await sslClient.uploadCert({
const certIdRes = await sslClient.uploadCert({
name: this.appendTimeSuffix("certd"),
cert: this.cert,
});
certId = certIdRes.certId;
}
const jobId = await this.createDeployJob(sslClient, certId);

View File

@@ -115,10 +115,11 @@ export class DeployCertToAliyunCDN extends AbstractTaskPlugin {
if (typeof this.cert === 'object') {
// @ts-ignore
const certName = this.buildCertName(CertReader.getMainDomain(this.cert.crt))
certId = await sslClient.uploadCert({
const certIdRes = await sslClient.uploadCert({
name:certName,
cert: this.cert,
});
certId = certIdRes.certId;
}
const client = await this.getClient(access);

View File

@@ -119,10 +119,11 @@ export class AliyunDeployCertToESA extends AbstractTaskPlugin {
certName = this.buildCertName(CertReader.getMainDomain(this.cert.crt));
certId = await sslClient.uploadCert({
const certIdRes = await sslClient.uploadCert({
name: certName,
cert: this.cert
});
certId = certIdRes.certId;
this.logger.info("上传证书成功", certId, certName);
}
return {

View File

@@ -0,0 +1,330 @@
import { AbstractTaskPlugin, IsTaskPlugin, Pager, PageSearch, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert";
import {
createCertDomainGetterInputDefine,
createRemoteSelectInputDefine
} from "@certd/plugin-lib";
import { AliyunAccess } from "../../../plugin-lib/aliyun/access/index.js";
import { AliyunSslClient } from "../../../plugin-lib/aliyun/lib/ssl-client.js";
@IsTaskPlugin({
name: "AliyunDeployCertToGA",
title: "阿里云-部署至GA",
icon: "svg:icon-aliyun",
group: pluginGroups.aliyun.key,
desc: "部署证书到阿里云GA(全球加速),支持更新默认证书和扩展证书",
needPlus: false,
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed
}
}
})
export class AliyunDeployCertToGA extends AbstractTaskPlugin {
@TaskInput({
title: "域名证书",
helper: "请选择证书申请任务输出的域名证书",
component: {
name: "output-selector",
from: [...CertApplyPluginNames, 'uploadCertToAliyun']
},
required: true
})
cert!: CertInfo|number;
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
certDomains!: string[];
@TaskInput({
title: "证书接入点",
helper: "不会选就保持默认即可",
value: "cas.aliyuncs.com",
component: {
name: "a-select",
options: [
{ value: "cas.aliyuncs.com", label: "中国大陆" },
{ value: "cas.ap-southeast-1.aliyuncs.com", label: "新加坡" }
]
},
required: true
})
casEndpoint!: string;
@TaskInput({
title: "Access授权",
helper: "阿里云授权AccessKeyId、AccessKeySecret",
component: {
name: "access-selector",
type: "aliyun"
},
required: true
})
accessId!: string;
@TaskInput(
createRemoteSelectInputDefine({
title: "全球加速实例",
helper: "请选择要部署证书的全球加速实例",
action: AliyunDeployCertToGA.prototype.onGetAcceleratorList.name,
watches: ["accessId"],
multi: false,
})
)
acceleratorId!: string;
@TaskInput(
createRemoteSelectInputDefine({
title: "监听",
helper: "请选择要部署证书的监听",
action: AliyunDeployCertToGA.prototype.onGetListenerList.name,
watches: ["accessId", "acceleratorId"]
})
)
listenerIds!: string[];
@TaskInput({
title: "证书类型",
helper: "选择更新默认证书还是扩展证书",
value: "default",
component: {
name: "a-select",
options: [
{ value: "default", label: "默认证书" },
{ value: "additional", label: "扩展证书" }
]
},
required: true,
})
certType!: string;
@TaskInput(
createRemoteSelectInputDefine({
title: "扩展证书域名",
helper: "将证书里的域名扩展绑定到监听器中",
action: AliyunDeployCertToGA.prototype.onGetAdditionalDomainList.name,
watches: ["accessId", "acceleratorId", "listenerIds", "certType"],
mergeScript: `
return {
show: ctx.compute(({form})=>{
return form.certType === "additional";
})
}
`
})
)
additionalDomains!: string[];
async onInstance() {
}
async getAliyunCertId(access: AliyunAccess) {
const sslClient = new AliyunSslClient({
access,
logger: this.logger,
endpoint: this.casEndpoint
});
return await sslClient.uploadCertOrGet(this.cert)
}
async execute(): Promise<void> {
this.logger.info("开始部署证书到阿里云GA");
const access = await this.getAccess<AliyunAccess>(this.accessId);
const client = await this.getClient(access);
const { certIdentifier } = await this.getAliyunCertId(access);
for (const listenerId of this.listenerIds) {
if (this.certType === "default") {
// 更新默认证书
this.logger.info(`开始更新默认证书到实例[${this.acceleratorId}]监听[${listenerId}]`);
const res = await client.doRequest({
action: "UpdateListener",
version: "2019-11-20",
data: {
query: {
RegionId: "cn-hangzhou",
AcceleratorId: this.acceleratorId,
ListenerId: listenerId,
Certificates: [
{ Id: certIdentifier},
]
}
}
});
this.logger.info(`部署默认证书到实例[${this.acceleratorId}]监听[${listenerId}]成功:${JSON.stringify(res)}`);
} else if (this.certType === "additional") {
// 处理扩展证书
for (const domain of this.additionalDomains) {
// 先检查域名是否已存在
this.logger.info(`开始检查域名[${domain}]是否已存在于实例[${this.acceleratorId}]监听[${listenerId}]`);
const existingCerts = await client.doRequest({
action: "ListListenerCertificates",
version: "2019-11-20",
method: "GET",
data: {
query: {
RegionId: "cn-hangzhou",
AcceleratorId: this.acceleratorId,
ListenerId: listenerId
}
}
});
const domainExists = existingCerts.Certificates?.some((cert: any) =>
cert.Domain === domain
);
if (domainExists) {
// 更新扩展证书
this.logger.info(`域名[${domain}]已存在,开始更新扩展证书到实例[${this.acceleratorId}]监听[${listenerId}]`);
const res = await client.doRequest({
action: "UpdateAdditionalCertificateWithListener",
version: "2019-11-20",
data: {
query: {
RegionId: "cn-hangzhou",
AcceleratorId: this.acceleratorId,
ListenerId: listenerId,
Domain: domain,
CertificateId: certIdentifier
}
}
});
this.logger.info(`更新扩展证书到实例[${this.acceleratorId}]监听[${listenerId}]域名[${domain}]成功:${JSON.stringify(res)}`);
} else {
// 新增扩展证书绑定
this.logger.info(`域名[${domain}]不存在,开始新增扩展证书绑定到实例[${this.acceleratorId}]监听[${listenerId}]`);
const res = await client.doRequest({
action: "AssociateAdditionalCertificatesWithListener",
version: "2019-11-20",
data: {
query: {
RegionId: "cn-hangzhou",
AcceleratorId: this.acceleratorId,
ListenerId: listenerId,
Certificates: [{
Id: certIdentifier,
Domain: domain
}]
}
}
});
this.logger.info(`新增扩展证书绑定到实例[${this.acceleratorId}]监听[${listenerId}]域名[${domain}]成功:${JSON.stringify(res)}`);
}
}
}
await this.ctx.utils.sleep(3000);
}
}
async getClient(access: AliyunAccess) {
const endpoint = `ga.cn-hangzhou.aliyuncs.com`;
return access.getClient(endpoint);
}
async onGetAcceleratorList(data: PageSearch) {
if (!this.accessId) {
throw new Error("请选择Access授权");
}
const pager = new Pager(data)
pager.pageSize = 50
const access = await this.getAccess<AliyunAccess>(this.accessId);
const client = await this.getClient(access);
const res = await client.doRequest({
action: "ListAccelerators",
version: "2019-11-20",
method: "GET",
data: {
query: {
RegionId: "cn-hangzhou",
PageNumber: pager.pageNo,
PageSize: pager.pageSize,
State: "active"
}
}
});
const list = res?.Accelerators;
if (!list || list.length === 0) {
throw new Error("没有找到全球加速实例,请先创建实例");
}
const options = list.map((item: any) => {
const label = `${item.Name} (${item.AcceleratorId})`
return {
label: label,
value: item.AcceleratorId,
};
});
return options;
}
async onGetListenerList(data: any) {
if (!this.accessId) {
throw new Error("请选择Access授权");
}
if (!this.acceleratorId) {
throw new Error("请先选择全球加速实例");
}
const access = await this.getAccess<AliyunAccess>(this.accessId);
const client = await this.getClient(access);
const res = await client.doRequest({
action: "ListListeners",
version: "2019-11-20",
method: "GET",
data: {
query: {
RegionId: "cn-hangzhou",
AcceleratorId: this.acceleratorId
}
}
});
const listeners = res?.Listeners;
if (!listeners || listeners.length === 0) {
throw new Error("没有找到监听,请先创建监听");
}
const options = listeners.map((item: any) => {
return {
label: `${item.ListenerId} (${item.Protocol}})`,
value: item.ListenerId,
};
});
return options;
}
async onGetAdditionalDomainList(data: any) {
if (!this.accessId) {
throw new Error("请选择Access授权");
}
if (!this.acceleratorId) {
throw new Error("请先选择全球加速实例");
}
if (!this.listenerIds || this.listenerIds.length === 0) {
throw new Error("请先选择监听");
}
if (this.certType !== "additional") {
throw new Error("请选择扩展证书类型");
}
// 获取当前监听已绑定的证书域名
const list = this.certDomains || [];
const options = list.map((item: any) => {
return {
label: item,
value: item,
domain: item,
};
});
return options;
}
}
new AliyunDeployCertToGA();

View File

@@ -294,10 +294,11 @@ export class AliyunDeployCertToNLB extends AbstractTaskPlugin {
const certName = this.buildCertName(CertReader.getMainDomain(this.cert.crt))
certId = await sslClient.uploadCert({
const certIdRes = await sslClient.uploadCert({
name: certName,
cert: this.cert,
});
certId = certIdRes.certId;
}
return certId;
}

View File

@@ -212,10 +212,11 @@ export class DeployCertToAliyunOSS extends AbstractTaskPlugin {
certName = this.buildCertName(CertReader.getMainDomain(this.cert.crt));
certId = await sslClient.uploadCert({
const certIdRes = await sslClient.uploadCert({
name: certName,
cert: this.cert
});
certId = certIdRes.certId;
this.logger.info("上传证书成功", certId, certName);
}
return {

View File

@@ -251,10 +251,11 @@ export class AliyunDeployCertToSLB extends AbstractTaskPlugin {
if (typeof this.cert === 'object') {
const name = this.appendTimeSuffix('certd');
certId = await sslClient.uploadCert({
const certIdRes = await sslClient.uploadCert({
name: name,
cert: this.cert,
});
certId = certIdRes.certId;
}
return await sslClient.getCertInfo(certId);

View File

@@ -152,10 +152,11 @@ export class AliyunDeployCertToWaf extends AbstractTaskPlugin {
endpoint: this.casEndpoint,
});
certId = await sslClient.uploadCert({
const certIdRes = await sslClient.uploadCert({
name: this.buildCertName(CertReader.getMainDomain(this.cert.crt)),
cert: this.cert,
});
certId = certIdRes.certId;
}
const client = await this.getWafClient(access);

View File

@@ -8,6 +8,7 @@ export * from './deploy-to-nlb/index.js';
export * from './deploy-to-slb/index.js';
export * from './deploy-to-fc/index.js';
export * from './deploy-to-esa/index.js';
export * from './deploy-to-ga/index.js';
export * from './deploy-to-vod/index.js';
export * from './deploy-to-apigateway/index.js';
export * from './deploy-to-apig/index.js';

View File

@@ -104,10 +104,11 @@ export class UploadCertToAliyun extends AbstractTaskPlugin {
}else{
certName = this.buildCertName(CertReader.getMainDomain(this.cert.crt))
}
this.aliyunCertId = await client.uploadCert({
const certIdRes = await client.uploadCert({
name: certName,
cert: this.cert,
});
this.aliyunCertId = certIdRes.certId as any;
}
}
//注册插件

View File

@@ -1,6 +1,7 @@
import { ILogger } from "@certd/basic";
import { ILogger, utils } from "@certd/basic";
import { AliyunAccess } from "../access/index.js";
import { AliyunClient } from "./index.js";
import { CertInfo, CertReader } from "@certd/plugin-lib";
export type AliyunCertInfo = {
crt: string; //fullchain证书
@@ -32,6 +33,11 @@ export type AliyunSslUploadCertReq = {
export type CasCertInfo = { certId: number; certName: string; certIdentifier: string; notAfter: number; casRegion: string };
export type CasCertId = {
certId: number;
certIdentifier: string;
certName: string;
}
export class AliyunSslClient {
opts: AliyunSslClientOpts;
logger: ILogger;
@@ -85,7 +91,14 @@ export class AliyunSslClient {
};
}
async uploadCert(req: AliyunSslUploadCertReq) {
getCertIdentifier(certId: number | string) {
if (typeof certId === "string" && certId.indexOf("-") > 0) {
return certId;
}
return `${certId}-${this.getCasRegionFromEndpoint(this.opts.endpoint)}`;
}
async uploadCert(req: AliyunSslUploadCertReq) : Promise<CasCertId> {
const client = await this.getClient();
const params = {
Name: req.name,
@@ -102,7 +115,33 @@ export class AliyunSslClient {
this.checkRet(ret);
this.opts.logger.info("证书上传成功aliyunCertId=", ret.CertId);
//output
return ret.CertId;
return {
certId: ret.CertId,
certName: req.name,
certIdentifier: this.getCertIdentifier(ret.CertId),
}
}
async uploadCertOrGet(cert: CertInfo | number ) :Promise<CasCertId>{
if (typeof cert === "object") {
// 上传证书到阿里云
this.logger.info(`开始上传证书`);
const certName = CertReader.buildCertName(cert);
const res = await this.uploadCert({
name: certName,
cert: cert
});
this.logger.info("上传证书成功", JSON.stringify(res));
return res
}
const certId = cert as any;
let certName: any = utils.string.appendTimeSuffix(certId);
const certIdentifier = this.getCertIdentifier(certId);
return {
certId,
certIdentifier,
certName
}
}
async getResourceList(req: AliyunSslGetResourceListReq) {