2025-12-31 17:01:37 +08:00
|
|
|
|
import { AbstractTaskPlugin, IsTaskPlugin, 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: "AliyunDeployCertToAll",
|
|
|
|
|
|
title: "阿里云-部署至任意云资源",
|
|
|
|
|
|
icon: "svg:icon-aliyun",
|
|
|
|
|
|
group: pluginGroups.aliyun.key,
|
|
|
|
|
|
desc: "【不建议使用】需要消耗阿里云自动部署次数,支持SLB、LIVE、webHosting、VOD、CR、DCDN、DDoS、CDN、ALB、APIGateway、FC、GA、MSE、NLB、OSS、SAE、WAF等云产品",
|
|
|
|
|
|
needPlus: false,
|
|
|
|
|
|
default: {
|
|
|
|
|
|
strategy: {
|
|
|
|
|
|
runStrategy: RunStrategy.SkipWhenSucceed,
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
})
|
|
|
|
|
|
export class AliyunDeployCertToAll extends AbstractTaskPlugin {
|
|
|
|
|
|
@TaskInput({
|
|
|
|
|
|
title: "域名证书",
|
|
|
|
|
|
helper: "请选择证书申请任务输出的域名证书\n或者选择前置任务“上传证书到阿里云”任务的证书ID,可以减少上传到阿里云的证书数量",
|
|
|
|
|
|
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: "新加坡" },
|
|
|
|
|
|
{ value: "cas.eu-central-1.aliyuncs.com", label: "德国(法兰克福)" },
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
required: true,
|
|
|
|
|
|
})
|
|
|
|
|
|
endpoint!: string;
|
|
|
|
|
|
|
|
|
|
|
|
@TaskInput({
|
|
|
|
|
|
title: "Access授权",
|
|
|
|
|
|
helper: "阿里云授权AccessKeyId、AccessKeySecret",
|
|
|
|
|
|
component: {
|
|
|
|
|
|
name: "access-selector",
|
|
|
|
|
|
type: "aliyun",
|
|
|
|
|
|
},
|
|
|
|
|
|
required: true,
|
|
|
|
|
|
})
|
|
|
|
|
|
accessId!: string;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* SLB:传统型负载均衡 CLB(仅中国站)
|
|
|
|
|
|
* LIVE:视频直播(仅中国站)
|
|
|
|
|
|
* webHosting:云虚拟主机(仅中国站)
|
|
|
|
|
|
* VOD:视频点播(仅中国站)
|
|
|
|
|
|
* CR:容器镜像服务(仅中国站)
|
|
|
|
|
|
* DCDN:全站加速
|
|
|
|
|
|
* DDoS:DDos 防护
|
|
|
|
|
|
* CDN:内容分发网络
|
|
|
|
|
|
* ALB:应用负载均衡
|
|
|
|
|
|
* APIGateway:API 网关
|
|
|
|
|
|
* FC:函数计算
|
|
|
|
|
|
* GA:全球加速
|
|
|
|
|
|
* MSE:微服务引擎
|
|
|
|
|
|
* NLB:网络型负载均衡
|
|
|
|
|
|
* OSS:对象存储
|
|
|
|
|
|
* SAE:Serverless 应用引擎
|
|
|
|
|
|
* WAF:Web 应用防火墙
|
|
|
|
|
|
*/
|
|
|
|
|
|
@TaskInput({
|
|
|
|
|
|
title: "云产品类型",
|
|
|
|
|
|
helper: "请选择云产品类型",
|
|
|
|
|
|
component: {
|
|
|
|
|
|
name: "a-select",
|
|
|
|
|
|
vModel: "value",
|
|
|
|
|
|
options: [
|
|
|
|
|
|
{ value: "SLB", label: "SLB-传统型负载均衡 CLB(仅中国站)" },
|
|
|
|
|
|
{ value: "LIVE", label: "LIVE-视频直播(仅中国站)" },
|
|
|
|
|
|
{ value: "webHosting", label: "webHosting-云虚拟主机(仅中国站)" },
|
|
|
|
|
|
{ value: "VOD", label: "VOD-视频点播(仅中国站)" },
|
|
|
|
|
|
{ value: "CR", label: "CR-容器镜像服务(仅中国站)" },
|
|
|
|
|
|
{ value: "DCDN", label: "DCDN-全站加速" },
|
|
|
|
|
|
{ value: "DDoS", label: "DDos 防护" },
|
|
|
|
|
|
{ value: "CDN", label: "CDN-内容分发网络" },
|
|
|
|
|
|
{ value: "ALB", label: "ALB-应用负载均衡" },
|
|
|
|
|
|
{ value: "APIGateway", label: "APIGateway-API 网关" },
|
|
|
|
|
|
{ value: "FC", label: "FC-函数计算" },
|
|
|
|
|
|
{ value: "GA", label: "GA-全球加速" },
|
|
|
|
|
|
{ value: "MSE", label: "MSE-微服务引擎" },
|
|
|
|
|
|
{ value: "NLB", label: "NLB-网络型负载均衡" },
|
|
|
|
|
|
{ value: "OSS", label: "OSS-对象存储" },
|
|
|
|
|
|
{ value: "SAE", label: "SAE-Serverless应用引擎" },
|
|
|
|
|
|
{ value: "WAF", label: "WAF-Web应用防火墙" },
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
required: true,
|
|
|
|
|
|
})
|
|
|
|
|
|
cloudProduct!: string;
|
|
|
|
|
|
|
|
|
|
|
|
@TaskInput(
|
|
|
|
|
|
createRemoteSelectInputDefine({
|
|
|
|
|
|
title: "要部署证书的云产品",
|
|
|
|
|
|
helper: "请选择要部署证书的云产品,注意:新创建的云产品资源可能需要过1-2小时才会在此处显示",
|
|
|
|
|
|
typeName: "AliyunDeployCertToAll",
|
|
|
|
|
|
action: AliyunDeployCertToAll.prototype.onGetProductList.name,
|
|
|
|
|
|
watches: ["cloudProduct", "accessId"],
|
|
|
|
|
|
})
|
|
|
|
|
|
)
|
|
|
|
|
|
productIds!: string[];
|
|
|
|
|
|
|
|
|
|
|
|
@TaskInput(
|
|
|
|
|
|
createRemoteSelectInputDefine({
|
|
|
|
|
|
title: "联系人",
|
|
|
|
|
|
helper: "请选择联系人,如果没有,需要先到[阿里云控制台创建联系人](https://yundun.console.aliyun.com/?p=cas#/informationManagement/person/)",
|
|
|
|
|
|
typeName: "AliyunDeployCertToAll",
|
|
|
|
|
|
action: AliyunDeployCertToAll.prototype.onGetContactList.name,
|
|
|
|
|
|
})
|
|
|
|
|
|
)
|
|
|
|
|
|
contactIds!: string[];
|
|
|
|
|
|
|
|
|
|
|
|
@TaskInput({
|
|
|
|
|
|
title: "检查超时时间",
|
|
|
|
|
|
helper: "检查部署任务超时时间,单位分钟",
|
|
|
|
|
|
value: 10,
|
|
|
|
|
|
component: {
|
|
|
|
|
|
name: "a-input-number",
|
|
|
|
|
|
vModel: "value",
|
|
|
|
|
|
},
|
|
|
|
|
|
required: true,
|
|
|
|
|
|
})
|
|
|
|
|
|
checkTimeout!: number;
|
|
|
|
|
|
|
|
|
|
|
|
async onInstance() {}
|
|
|
|
|
|
|
|
|
|
|
|
async execute(): Promise<void> {
|
|
|
|
|
|
this.logger.info("开始部署证书到阿里云");
|
|
|
|
|
|
const access = await this.getAccess<AliyunAccess>(this.accessId);
|
|
|
|
|
|
const sslClient = new AliyunSslClient({
|
|
|
|
|
|
access,
|
|
|
|
|
|
logger: this.logger,
|
|
|
|
|
|
endpoint: this.endpoint,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
let certId: any = this.cert;
|
|
|
|
|
|
if (typeof this.cert === "object") {
|
2026-01-31 19:30:20 +08:00
|
|
|
|
const certIdRes = await sslClient.uploadCert({
|
2025-12-31 17:01:37 +08:00
|
|
|
|
name: this.appendTimeSuffix("certd"),
|
|
|
|
|
|
cert: this.cert,
|
|
|
|
|
|
});
|
2026-01-31 19:30:20 +08:00
|
|
|
|
certId = certIdRes.certId;
|
2025-12-31 17:01:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const jobId = await this.createDeployJob(sslClient, certId);
|
|
|
|
|
|
|
|
|
|
|
|
await this.updateJobStatus(sslClient, jobId, "scheduling");
|
|
|
|
|
|
|
|
|
|
|
|
this.logger.info("开始检查部署任务执行结果");
|
|
|
|
|
|
const startTime = Date.now();
|
|
|
|
|
|
while (Date.now() < startTime + this.checkTimeout * 60 * 1000) {
|
|
|
|
|
|
this.checkSignal();
|
|
|
|
|
|
await this.ctx.utils.sleep(10000);
|
|
|
|
|
|
let res: any = {};
|
|
|
|
|
|
try {
|
|
|
|
|
|
res = await this.getJobDetail(sslClient, jobId);
|
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
|
this.logger.error(e);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
const status = res.Status;
|
|
|
|
|
|
if (status == "success") {
|
|
|
|
|
|
this.logger.info("部署任务执行成功:", status);
|
|
|
|
|
|
return;
|
|
|
|
|
|
} else if (status == "error") {
|
|
|
|
|
|
this.logger.error(`部署任务执行失败,请前往 https://yundun.console.aliyun.com/?p=cas#/deployDetail/user/${jobId} 查看失败原因: `, res);
|
|
|
|
|
|
|
|
|
|
|
|
throw new Error("部署任务执行失败,");
|
|
|
|
|
|
} else {
|
|
|
|
|
|
/**
|
|
|
|
|
|
* pending:待执行
|
|
|
|
|
|
* editing:编辑中
|
|
|
|
|
|
* scheduling:调度中
|
|
|
|
|
|
* processing:部署中
|
|
|
|
|
|
* error:部署失败
|
|
|
|
|
|
* success:部署成功
|
|
|
|
|
|
*/
|
|
|
|
|
|
this.logger.info("部署任务正在执行中: ", status);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
throw new Error("部署任务执行超时,请手动检查任务状态");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async updateJobStatus(sslClient: AliyunSslClient, jobId: string, status: string) {
|
|
|
|
|
|
const params = {
|
|
|
|
|
|
JobId: jobId,
|
|
|
|
|
|
Status: status,
|
|
|
|
|
|
};
|
|
|
|
|
|
const requestOption = {
|
|
|
|
|
|
method: "POST",
|
|
|
|
|
|
formatParams: false,
|
|
|
|
|
|
};
|
|
|
|
|
|
const res = await sslClient.doRequest("UpdateDeploymentJobStatus", params, requestOption);
|
|
|
|
|
|
this.logger.info("部署任务开始执行,部署需要时间,RequestId=", res.RequestId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async onGetProductList(data: any) {
|
|
|
|
|
|
if (!this.accessId) {
|
|
|
|
|
|
throw new Error("请选择Access授权");
|
|
|
|
|
|
}
|
|
|
|
|
|
const access = await this.getAccess<AliyunAccess>(this.accessId);
|
|
|
|
|
|
const sslClient = new AliyunSslClient({
|
|
|
|
|
|
access,
|
|
|
|
|
|
logger: this.logger,
|
|
|
|
|
|
endpoint: this.endpoint,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (!this.cloudProduct) {
|
|
|
|
|
|
throw new Error("请选择云产品类型");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const res = await sslClient.getResourceList({
|
|
|
|
|
|
cloudProduct: this.cloudProduct,
|
|
|
|
|
|
});
|
|
|
|
|
|
if (!res?.Data || res?.Data.length === 0) {
|
|
|
|
|
|
throw new Error("没有找到对应类型的云资源");
|
|
|
|
|
|
}
|
|
|
|
|
|
const options = res.Data.map((item: any) => {
|
|
|
|
|
|
return {
|
|
|
|
|
|
label: `${item.Domain}<${item.Id}>`,
|
|
|
|
|
|
value: item.Id,
|
|
|
|
|
|
title: `${item.CloudProduct}:${item.CertName || "证书未命名"}`,
|
|
|
|
|
|
domain: item.Domain,
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
|
|
|
|
|
return this.ctx.utils.options.buildGroupOptions(options, this.certDomains);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async onGetContactList(data: any) {
|
|
|
|
|
|
if (!this.accessId) {
|
|
|
|
|
|
throw new Error("请选择Access授权");
|
|
|
|
|
|
}
|
|
|
|
|
|
const access = await this.getAccess<AliyunAccess>(this.accessId);
|
|
|
|
|
|
const sslClient = new AliyunSslClient({
|
|
|
|
|
|
access,
|
|
|
|
|
|
logger: this.logger,
|
|
|
|
|
|
endpoint: this.endpoint,
|
|
|
|
|
|
});
|
|
|
|
|
|
const res = await sslClient.getContactList();
|
|
|
|
|
|
/*
|
|
|
|
|
|
"Email": "@qq.com",
|
|
|
|
|
|
"EmailStatus": 0,
|
|
|
|
|
|
"MobileStatus": 0,
|
|
|
|
|
|
"ContactId": 378992,
|
|
|
|
|
|
"Mobile": "",
|
|
|
|
|
|
"Name": ""
|
|
|
|
|
|
*/
|
|
|
|
|
|
if (!res?.ContactList || res?.ContactList.length === 0) {
|
|
|
|
|
|
throw new Error("没有找到联系人");
|
|
|
|
|
|
}
|
|
|
|
|
|
return res.ContactList.map((item: any) => {
|
|
|
|
|
|
return {
|
|
|
|
|
|
label: `${item.Name}<${item.Email}:${item.ContactId}>`,
|
|
|
|
|
|
value: item.ContactId,
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async getJobDetail(sslClient: AliyunSslClient, jobId: number) {
|
|
|
|
|
|
const params = {
|
|
|
|
|
|
JobId: jobId,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const requestOption = {
|
|
|
|
|
|
method: "POST",
|
|
|
|
|
|
formatParams: false,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return await sslClient.doRequest("DescribeDeploymentJob", params, requestOption);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async createDeployJob(sslClient: AliyunSslClient, certId: any) {
|
|
|
|
|
|
const res = await sslClient.createDeploymentJob({
|
|
|
|
|
|
name: "自动部署证书(By Certd)",
|
|
|
|
|
|
jobType: "user",
|
|
|
|
|
|
contactIds: this.contactIds,
|
|
|
|
|
|
resourceIds: this.productIds,
|
|
|
|
|
|
certIds: [certId],
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const jobId = res.JobId;
|
|
|
|
|
|
this.logger.info("部署任务创建成功: jobId=", jobId);
|
|
|
|
|
|
return jobId;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
new AliyunDeployCertToAll();
|