From 78004bdfb552a3b83298fa09d518ca282a529d90 Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Tue, 27 Jan 2026 19:25:50 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20ucloud=E6=94=AF=E6=8C=81=E9=83=A8?= =?UTF-8?q?=E7=BD=B2=E5=88=B0alb?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin-lib/src/cert/cert-reader.ts | 2 +- .../src/plugins/plugin-ucloud/access.ts | 8 +- .../plugin-ucloud/plugins/constants.ts | 70 ++++++ .../plugins/plugin-ucloud/plugins/index.ts | 1 + .../plugins/plugin-deploy-to-alb.ts | 226 ++++++++++++------ .../plugins/plugin-deploy-to-us3.ts | 200 ++++++++++++++++ 6 files changed, 434 insertions(+), 73 deletions(-) create mode 100644 packages/ui/certd-server/src/plugins/plugin-ucloud/plugins/constants.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-ucloud/plugins/plugin-deploy-to-us3.ts diff --git a/packages/plugins/plugin-lib/src/cert/cert-reader.ts b/packages/plugins/plugin-lib/src/cert/cert-reader.ts index ba8c10262..df2d86edb 100644 --- a/packages/plugins/plugin-lib/src/cert/cert-reader.ts +++ b/packages/plugins/plugin-lib/src/cert/cert-reader.ts @@ -250,7 +250,7 @@ export class CertReader { return name + "_" + dayjs().format("YYYYMMDDHHmmssSSS"); } - static buildCertName(cert: any) { + static buildCertName(cert: CertInfo) { return new CertReader(cert).buildCertName(); } } diff --git a/packages/ui/certd-server/src/plugins/plugin-ucloud/access.ts b/packages/ui/certd-server/src/plugins/plugin-ucloud/access.ts index 1440d7f1e..09e761ab2 100644 --- a/packages/ui/certd-server/src/plugins/plugin-ucloud/access.ts +++ b/packages/ui/certd-server/src/plugins/plugin-ucloud/access.ts @@ -74,14 +74,14 @@ export class UCloudAccess extends BaseAccess { } - async getClient() { + async getClient(region?:string) { if (this.client) { return this.client; } const { Client } = await import('@ucloud-sdks/ucloud-sdk-js'); const client = new Client({ config: { - region: 'cn-bj2', + region: region || 'cn-bj2', projectId: this.projectId || "", baseUrl: "https://api.ucloud.cn" }, @@ -108,7 +108,7 @@ export class UCloudAccess extends BaseAccess { const res = await client.uaccount().getRegion({ "Action": "GetRegion" }); - this.ctx.logger.info(`获取到区域列表:${JSON.stringify(res)}`); + // this.ctx.logger.info(`获取到区域列表:${JSON.stringify(res)}`); return res; } @@ -212,7 +212,7 @@ export class UCloudAccess extends BaseAccess { const resp = await client.invoke(new Request({ ...req })); - this.ctx.logger.info(`请求UCloud API:${JSON.stringify(resp)}`); + // this.ctx.logger.info(`请求UCloud API:${JSON.stringify(resp)}`); const res = resp.data || {} if (res.RetCode !== 0) { throw new Error(res.Message) diff --git a/packages/ui/certd-server/src/plugins/plugin-ucloud/plugins/constants.ts b/packages/ui/certd-server/src/plugins/plugin-ucloud/plugins/constants.ts new file mode 100644 index 000000000..6ca79f232 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-ucloud/plugins/constants.ts @@ -0,0 +1,70 @@ +/** + * cn-bj1 华北(北京) +cn-bj2 华北(北京2) +cn-wlcb 华北(乌兰察布) +cn-wlcb2 华北(乌兰察布2) +cn-sh2 华东(上海2) +cn-jx 华东(嘉兴) +cn-sh 金融云-华东(上海) +cn-gd2 华南(广州2) +cn-gd 华南(广州) +cn-guiyang1 西南(贵阳) +hk 香港 +tw-tp 台湾(台北) +sg 新加坡 +jpn-tky 日本(东京) +kr-seoul 韩国(首尔) +th-bkk 泰国(曼谷) +idn-jakarta 印度尼西亚(雅加达) +vn-sng 越南(胡志明) +ph-mnl 菲律宾(马尼拉) +ind-mumbai 印度(孟买) +pk-khi 巴基斯坦(卡拉奇) +us-den 美国(丹佛) +us-ca 美国(洛杉矶) +us-ws 美国(华盛顿) +bra-saopaulo 巴西(圣保罗) +rus-mosc 俄罗斯(莫斯科) +ge-fra 德国(法兰克福) +uk-london 英国(伦敦) +uae-dubai 阿联酋(迪拜) +afr-nigeria 尼日利亚(拉各斯) +uz-tas 乌兹别克斯坦(塔什干) +kz-ala 哈萨克斯坦(阿拉木图) +mx-qro 墨西哥(克雷塔罗) + */ +export const UCloudRegions = [ + {label: "华北(北京)",value: "cn-bj1" }, + {label: "华北(北京2)",value: "cn-bj2" }, + {label: "华北(乌兰察布)",value: "cn-wlcb" }, + {label: "华北(乌兰察布2)",value: "cn-wlcb2" }, + {label: "华东(上海2)",value: "cn-sh2" }, + {label: "华东(嘉兴)",value: "cn-jx" }, + {label: "金融云-华东(上海)",value: "cn-sh" }, + {label: "华南(广州2)",value: "cn-gd2" }, + {label: "华南(广州)",value: "cn-gd" }, + {label: "西南(贵阳)",value: "cn-guiyang1" }, + {label: "香港",value: "hk" }, + {label: "台湾(台北)",value: "tw-tp" }, + {label: "新加坡",value: "sg" }, + {label: "日本(东京)",value: "jpn-tky" }, + {label: "韩国(首尔)",value: "kr-seoul" }, + {label: "泰国(曼谷)",value: "th-bkk" }, + {label: "印度尼西亚(雅加达)",value: "idn-jakarta" }, + {label: "越南(胡志明)",value: "vn-sng" }, + {label: "菲律宾(马尼拉)",value: "ph-mnl" }, + {label: "印度(孟买)",value: "ind-mumbai" }, + {label: "巴基斯坦(卡拉奇)",value: "pk-khi" }, + {label: "美国(丹佛)",value: "us-den" }, + {label: "美国(洛杉矶)",value: "us-ca" }, + {label: "美国(华盛顿)",value: "us-ws" }, + {label: "巴西(圣保罗)",value: "bra-saopaulo" }, + {label: "俄罗斯(莫斯科)",value: "rus-mosc" }, + {label: "德国(法兰克福)",value: "ge-fra" }, + {label: "英国(伦敦)",value: "uk-london" }, + {label: "阿联酋(迪拜)",value: "uae-dubai" }, + {label: "尼日利亚(拉各斯)",value: "afr-nigeria" }, + {label: "乌兹别克斯坦(塔什干)",value: "uz-tas" }, + {label: "哈萨克斯坦(阿拉木图)",value: "kz-ala" }, + {label: "墨西哥(克雷塔罗)",value: "mx-qro" }, +] \ No newline at end of file diff --git a/packages/ui/certd-server/src/plugins/plugin-ucloud/plugins/index.ts b/packages/ui/certd-server/src/plugins/plugin-ucloud/plugins/index.ts index b42432d30..6d7717aea 100644 --- a/packages/ui/certd-server/src/plugins/plugin-ucloud/plugins/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-ucloud/plugins/index.ts @@ -2,3 +2,4 @@ export * from './plugin-deploy-to-cdn.js'; export * from './plugin-upload-to-ussl.js'; export * from './plugin-deploy-to-waf.js'; export * from './plugin-deploy-to-alb.js'; +export * from './plugin-deploy-to-us3.js'; diff --git a/packages/ui/certd-server/src/plugins/plugin-ucloud/plugins/plugin-deploy-to-alb.ts b/packages/ui/certd-server/src/plugins/plugin-ucloud/plugins/plugin-deploy-to-alb.ts index b37bfda95..056fb4b95 100644 --- a/packages/ui/certd-server/src/plugins/plugin-ucloud/plugins/plugin-deploy-to-alb.ts +++ b/packages/ui/certd-server/src/plugins/plugin-ucloud/plugins/plugin-deploy-to-alb.ts @@ -1,7 +1,8 @@ import { AbstractTaskPlugin, IsTaskPlugin, PageSearch, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert"; -import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; +import { CertReader, createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; import { UCloudAccess } from "../access.js"; +import { UCloudRegions } from "./constants.js"; @IsTaskPlugin({ name: "UCloudDeployToALB", @@ -22,10 +23,10 @@ export class UCloudDeployToALB extends AbstractTaskPlugin { helper: "请选择前置任务输出的域名证书", component: { name: "output-selector", - from: [...CertApplyPluginNames, ":UCloudCertId:"] + from: [...CertApplyPluginNames] } }) - cert!: CertInfo | { type: string, id: number, name: string }; + cert!: CertInfo; @TaskInput(createCertDomainGetterInputDefine({ props: { required: false } })) certDomains!: string[]; @@ -42,107 +43,194 @@ export class UCloudDeployToALB extends AbstractTaskPlugin { @TaskInput( createRemoteSelectInputDefine({ - title: "负载均衡实例", - helper: "选择ULB负载均衡实例", - action: UCloudDeployToALB.prototype.onGetULBList.name + title: "地域", + helper: "选择UCloud地域", + action: UCloudDeployToALB.prototype.onGetRegionList.name, + multi:false }) ) - ulbId!: string; + region!: string; + + @TaskInput( + createRemoteSelectInputDefine({ + title: "负载均衡实例", + helper: "选择ALB负载均衡实例", + action: UCloudDeployToALB.prototype.onGetALBList.name, + multi:false + }) + ) + loadBalancerId!: string; @TaskInput( createRemoteSelectInputDefine({ title: "监听器列表", helper: "要更新的ALB监听器列表", - action: UCloudDeployToALB.prototype.onGetVServerList.name + action: UCloudDeployToALB.prototype.onGetListenerList.name }) ) - vServerList!: string[]; + listenerList!: string[]; + + @TaskInput({ + title: "上传证书模式", + helper: "选择是更新默认证书还是添加扩展证书", + component: { + name: "a-select", + options: [ + { label: "更新默认证书", value: "update_default" }, + { label: "添加扩展证书", value: "add_extension" } + ] + }, + required: true, + default: "update_default" + }) + deployMode!: string; async onInstance() { } - async execute(): Promise { + async onGetRegionList(req: PageSearch = {}) { const access = await this.getAccess(this.accessId); - let certType = "ussl" - let certId = 0 - if (this.cert && typeof this.cert === 'object' && 'id' in this.cert) { - certId = this.cert.id - } else { - const cert = await access.SslUploadCert({ - cert: this.cert as CertInfo - }); - certId = cert.id + const res = await access.GetRegion(); + let list = res.Regions || []; + + if (!list || list.length === 0) { + throw new Error("没有获取到UCloud地域列表"); } - for (const item of this.vServerList) { - this.logger.info(`----------- 开始更新监听器:${item}`); - await this.deployToAlb({ - access: access, - ulbId: this.ulbId, - vServerId: item, - certId: certId, - certType: certType + const haveSet = {} + list = list.filter((item: any) => { + const region = item.Region; + if (haveSet[region]) { + return false; + } + haveSet[region] = true; + return true; + }) + let options = list.map((item: any) => { + const region = item.Region; + const name = UCloudRegions.find((r) => r.value === region)?.label || item.RegionName; + return { + label: `${name}(${item.Region})`, + value: item.Region + }; + }); + + return { + list: options, + total: options.length, + pageNo: 1, + pageSize: options.length + }; + } + + async uploadCertToULB(){ + const access = await this.getAccess(this.accessId); + const certInfo = this.cert as CertInfo; + const sslName = CertReader.buildCertName(certInfo); + const sslContent = certInfo.crt + '\n' + certInfo.key; + + const createRes = await access.invoke({ + "Action": "CreateSSL", + "Region": this.region, + "ProjectId": access.projectId, + "SSLName": sslName, + "SSLContent": sslContent, }); - this.logger.info(`----------- 更新监听器证书${item}成功`); + + if (createRes.RetCode !== 0) { + throw new Error(`创建SSL证书失败: ${createRes.Message || '未知错误'}`); + } + + return createRes.SSLId; + } + + async execute(): Promise { + const access = await this.getAccess(this.accessId); + let certId = await this.uploadCertToULB(); + + + + for (const item of this.listenerList) { + this.logger.info(`----------- 开始处理监听器:${item}`); + if (this.deployMode === "update_default") { + await this.updateDefaultCert({ + access: access, + loadBalancerId: this.loadBalancerId, + listenerId: item, + certId: certId + }); + this.logger.info(`----------- 更新监听器默认证书${item}成功`); + } else if (this.deployMode === "add_extension") { + await this.addExtensionCert({ + access: access, + loadBalancerId: this.loadBalancerId, + listenerId: item, + certId: certId + }); + this.logger.info(`----------- 添加监听器扩展证书${item}成功`); + } } this.logger.info("部署完成"); } - async deployToAlb(req: { access: any, ulbId: string, vServerId: string, certId: number, certType: string }) { - const { access, ulbId, vServerId, certId, certType } = req - - this.logger.info(`----------- 获取监听器${vServerId}配置`); - const vServerRes = await access.invoke({ - "Action": "DescribeVServer", - "ProjectId": access.projectId, - "ULBId": ulbId, - "VServerId": vServerId - }); - - const vServer = vServerRes.VServerSet?.[0]; - if (!vServer) { - throw new Error(`没有找到监听器${vServerId}`); - } - - this.logger.info(`----------- 更新ALB监听器HTTPS配置${vServerId}`); + async updateDefaultCert(req: { access: any, loadBalancerId: string, listenerId: string, certId: string }) { + const { access, loadBalancerId, listenerId, certId } = req + + this.logger.info(`----------- 更新ALB监听器默认证书${listenerId}`); const resp = await access.invoke({ - "Action": "UpdateVServerAttribute", + "Action": "UpdateListenerAttribute", + "Region": this.region, "ProjectId": access.projectId, - "ULBId": ulbId, - "VServerId": vServerId, - "SSLMode": "port", - "CertificateId": certId, - "CertificateType": certType + "LoadBalancerId": loadBalancerId, + "ListenerId": listenerId, + "Certificates": [certId] }); - this.logger.info(`----------- 部署ALB证书${vServerId}成功,${JSON.stringify(resp)}`); + this.logger.info(`----------- 更新监听器默认证书${listenerId}成功,${JSON.stringify(resp)}`); } - async onGetULBList(req: PageSearch = {}) { + async addExtensionCert(req: { access: any, loadBalancerId: string, listenerId: string, certId: string }) { + const { access, loadBalancerId, listenerId, certId } = req + + this.logger.info(`----------- 添加ALB监听器扩展证书${listenerId}`); + const resp = await access.invoke({ + "Action": "AddSSLBinding", + "Region": this.region, + "ProjectId": access.projectId, + "LoadBalancerId": loadBalancerId, + "ListenerId": listenerId, + "SSLIds": [certId] + }); + this.logger.info(`----------- 添加监听器扩展证书${listenerId}成功,${JSON.stringify(resp)}`); + } + + async onGetALBList(req: PageSearch = {}) { const access = await this.getAccess(this.accessId); const pageNo = req.pageNo ?? 1; const pageSize = req.pageSize ?? 100; const res = await access.invoke({ - "Action": "DescribeULB", + "Action": "DescribeLoadBalancers", + "Region": this.region, "ProjectId": access.projectId, + "Type": "Application", "Offset": (pageNo - 1) * pageSize, "Limit": pageSize }); - const total = res.TotalCount || 0; - const list = res.Dataset || []; + const total = res.LoadBalancers?.length || 0; + const list = res.LoadBalancers || []; if (!list || list.length === 0) { - throw new Error("没有找到ULB实例,请先在控制台创建ULB实例"); + throw new Error("没有找到ALB实例,请先在控制台创建ALB实例"); } const options = list.map((item: any) => { return { - label: `${item.Name || item.ULBId}<${item.ULBId}>`, - value: `${item.ULBId}` + label: `${item.Name || item.LoadBalancerId}<${item.LoadBalancerId}>`, + value: `${item.LoadBalancerId}` }; }); @@ -154,26 +242,28 @@ export class UCloudDeployToALB extends AbstractTaskPlugin { }; } - async onGetVServerList(req: PageSearch = {}) { + + async onGetListenerList(req: PageSearch = {}) { const access = await this.getAccess(this.accessId); - if (!this.ulbId) { - throw new Error("请先选择ULB负载均衡实例"); + if (!this.loadBalancerId) { + throw new Error("请先选择ALB负载均衡实例"); } const pageNo = req.pageNo ?? 1; const pageSize = req.pageSize ?? 100; const res = await access.invoke({ - "Action": "DescribeVServer", + "Action": "DescribeListeners", + "Region": this.region, "ProjectId": access.projectId, - "ULBId": this.ulbId, + "LoadBalancerId": this.loadBalancerId, "Offset": (pageNo - 1) * pageSize, "Limit": pageSize }); const total = res.TotalCount || 0; - const list = res.VServerSet || []; + const list = res.Listeners || []; if (!list || list.length === 0) { throw new Error("没有找到ALB监听器,请先在控制台创建ALB实例和监听器"); @@ -181,9 +271,9 @@ export class UCloudDeployToALB extends AbstractTaskPlugin { const options = list.map((item: any) => { return { - label: `${item.VServerName || item.VServerId}<${item.VServerId}>`, - value: `${item.VServerId}`, - domain: item.VServerName || item.VServerId + label: `${item.Name || item.ListenerId}<${item.ListenerId}>`, + value: `${item.ListenerId}`, + domain: item.Name || item.ListenerId }; }); diff --git a/packages/ui/certd-server/src/plugins/plugin-ucloud/plugins/plugin-deploy-to-us3.ts b/packages/ui/certd-server/src/plugins/plugin-ucloud/plugins/plugin-deploy-to-us3.ts new file mode 100644 index 000000000..72e16a508 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-ucloud/plugins/plugin-deploy-to-us3.ts @@ -0,0 +1,200 @@ +import { AbstractTaskPlugin, IsTaskPlugin, PageSearch, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; +import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert"; +import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; +import { UCloudAccess } from "../access.js"; + +@IsTaskPlugin({ + name: "UCloudDeployToUS3", + title: "UCloud-部署到对象存储(US3)", + desc: "将证书部署到UCloud对象存储(US3)", + icon: "svg:icon-ucloud", + group: pluginGroups.ucloud.key, + needPlus: false, + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed + } + } +}) +export class UCloudDeployToUS3 extends AbstractTaskPlugin { + @TaskInput({ + title: "域名证书", + helper: "请选择前置任务输出的域名证书", + component: { + name: "output-selector", + from: [...CertApplyPluginNames] + } + }) + cert!: CertInfo ; + + @TaskInput(createCertDomainGetterInputDefine({ props: { required: false } })) + certDomains!: string[]; + + @TaskInput({ + title: "UCloud授权", + component: { + name: "access-selector", + type: "ucloud" + }, + required: true + }) + accessId!: string; + + @TaskInput( + createRemoteSelectInputDefine({ + title: "存储桶", + helper: "要更新的UCloud存储桶", + action: UCloudDeployToUS3.prototype.onGetBucketList.name + }) + ) + bucket!: string; + + @TaskInput( + createRemoteSelectInputDefine({ + title: "域名列表", + helper: "要更新的UCloud域名列表", + action: UCloudDeployToUS3.prototype.onGetDomainList.name + }) + ) + domainList!: string[]; + + async onInstance() { + } + + async execute(): Promise { + const access = await this.getAccess(this.accessId); + let certName = this.appendTimeSuffix("certd") + let cert: CertInfo; + + for (const domain of this.domainList) { + this.logger.info(`----------- 开始更新存储桶${this.bucket}的域名${domain}证书`); + await this.deployToUS3({ + access: access, + bucket: this.bucket, + domain: domain, + cert: cert, + certName: certName + }); + this.logger.info(`----------- 更新存储桶${this.bucket}的域名${domain}证书成功`); + } + + this.logger.info("部署完成"); + } + + async deployToUS3(req: { access: any, bucket: string, domain: string, cert: CertInfo, certName: string }) { + const { access, bucket, domain, cert, certName } = req + + const body: any = { + "Action": "UpdateUFileSSLCert", + "BucketName": bucket, + "Domain": domain, + "CertificateName": certName, + "Certificate": cert.crt, + "CertificateKey": cert.key + } + + this.logger.info(`----------- 更新对象存储SSL证书${bucket}:${domain},${JSON.stringify(body)}`); + const resp = await access.invoke(body); + this.logger.info(`----------- 部署对象存储证书${bucket}:${domain}成功,${JSON.stringify(resp)}`); + } + + async onGetBucketList(req: PageSearch = {}) { + const access = await this.getAccess(this.accessId); + + const pageNo = req.pageNo ?? 1; + const pageSize = req.pageSize ?? 100; + + try { + const resp = await access.invoke({ + "Action": "DescribeBucket", + "ProjectId": access.projectId, + "Offset": (pageNo - 1) * pageSize, + "Limit": pageSize + }); + + this.logger.info(`获取到存储桶列表:${JSON.stringify(resp)}`); + + const buckets = resp.DataSet || []; + const total = buckets.length; + + if (!buckets || buckets.length === 0) { + throw new Error("没有找到存储桶,请先在控制台创建存储桶"); + } + + const options = buckets.map((item: any) => { + return { + label: `${item.BucketName}<${item.Region}>`, + value: `${item.BucketName}`, + bucket: item.BucketName + }; + }); + + return { + list: this.ctx.utils.options.buildGroupOptions(options, this.certDomains), + total: total, + pageNo: pageNo, + pageSize: pageSize + }; + } catch (err) { + this.logger.error(`获取存储桶列表失败:${err}`); + throw err; + } + } + + async onGetDomainList(req: PageSearch = {}) { + const access = await this.getAccess(this.accessId); + + if (!this.bucket) { + throw new Error("请先选择存储桶"); + } + + try { + const resp = await access.invoke({ + "Action": "DescribeBucket", + "ProjectId": access.projectId, + "BucketName": this.bucket + }); + + this.logger.info(`获取到存储桶域名列表:${JSON.stringify(resp)}`); + + const buckets = resp.DataSet || []; + if (!buckets || buckets.length === 0) { + throw new Error(`没有找到存储桶${this.bucket}`); + } + + const bucketInfo = buckets[0]; + const domainSet = bucketInfo.Domain || {}; + + const allDomains = [ + ...(domainSet.Src || []), + ...(domainSet.Cdn || []), + ...(domainSet.CustomSrc || []), + ...(domainSet.CustomCdn || []) + ]; + + if (!allDomains || allDomains.length === 0) { + throw new Error(`没有找到存储桶${this.bucket}的域名,请先在控制台为存储桶添加域名`); + } + + const options = allDomains.map((domain: string) => { + return { + label: domain, + value: domain, + domain: domain + }; + }); + + return { + list: this.ctx.utils.options.buildGroupOptions(options, this.certDomains), + total: allDomains.length, + pageNo: 1, + pageSize: allDomains.length + }; + } catch (err) { + this.logger.error(`获取存储桶域名列表失败:${err}`); + throw err; + } + } +} + +new UCloudDeployToUS3();