Files
certd/packages/ui/certd-server/src/plugins/plugin-aliyun/plugin/deploy-to-fc/index.ts
T
xiaojunnuo 1db1ffde99 perf: 添加阿里云 ESA证书部署插件
- 新增 AliyunDeployCertToESA 插件类,实现证书上传和部署到阿里云 ESA 功能
- 优化证书名称生成逻辑,支持通配符域名
- 重构部分代码,提高可复用性和可维护性
- 更新相关依赖版本,确保兼容性
2025-05-22 23:21:50 +08:00

229 lines
7.6 KiB
TypeScript

import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
import { CertApplyPluginNames, CertInfo, CertReader } from "@certd/plugin-cert";
import { AliyunAccess, createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
@IsTaskPlugin({
name: 'AliyunDeployCertToFC',
title: '阿里云-部署至阿里云FC(3.0)',
icon: 'svg:icon-aliyun',
group: pluginGroups.aliyun.key,
desc: '部署证书到阿里云函数计算(FC3.0),【注意】证书的加密算法必须选择【pkcs1旧版】',
needPlus: false,
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
})
export class AliyunDeployCertToFC extends AbstractTaskPlugin {
@TaskInput({
title: '域名证书',
helper: '请选择证书申请任务输出的域名证书',
component: {
name: 'output-selector',
from: [...CertApplyPluginNames],
},
required: true,
})
cert!: CertInfo;
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
certDomains!: string[];
@TaskInput({
title: 'FC大区',
value: 'cn-hangzhou',
component: {
name: 'a-auto-complete',
vModel: 'value',
options: [
{ value: 'cn-qingdao', label: '华北1(青岛)' },
{ value: 'cn-beijing', label: '华北2(北京)' },
{ value: 'cn-zhangjiakou', label: '华北 3(张家口)' },
{ value: 'cn-huhehaote', label: '华北5(呼和浩特)' },
{ value: 'cn-hangzhou', label: '华东1(杭州)' },
{ value: 'cn-shanghai', label: '华东2(上海)' },
{ value: 'cn-shenzhen', label: '华南1(深圳)' },
{ value: 'ap-southeast-2', label: '澳大利亚(悉尼)' },
{ value: 'eu-central-1', label: '德国(法兰克福)' },
{ value: 'ap-southeast-3', label: '马来西亚(吉隆坡)' },
{ value: 'us-east-1', label: '美国(弗吉尼亚)' },
{ value: 'us-west-1', label: '美国(硅谷)' },
{ value: 'ap-northeast-1', label: '日本(东京)' },
{ value: 'ap-southeast-7', label: '泰国(曼谷)' },
{ value: 'cn-chengdu', label: '西南1(成都)' },
{ value: 'ap-southeast-1', label: '新加坡' },
{ value: 'ap-south-1', label: '印度(孟买)' },
{ value: 'ap-southeast-5', label: '印度尼西亚(雅加达)' },
{ value: 'eu-west-1', label: '英国(伦敦)' },
{ value: 'cn-hongkong', label: '中国香港' },
],
},
required: true,
})
regionId!: string;
@TaskInput({
title: '阿里云账号id',
helper: '阿里云主账号ID,右上角头像下方获取',
component: {
name: 'a-input',
vModel:"value"
},
required: true,
})
accountId!: string;
@TaskInput({
title: 'Access授权',
helper: '阿里云授权AccessKeyId、AccessKeySecret',
component: {
name: 'access-selector',
type: 'aliyun',
},
required: true,
})
accessId!: string;
@TaskInput(
createRemoteSelectInputDefine({
title: 'FC域名',
helper: "请选择要部署证书的域名\n【注意】证书的加密算法必须选择【pkcs1旧版】(否则会报'private key' has to be in PEM format错误)",
typeName: 'AliyunDeployCertToFC',
action: AliyunDeployCertToFC.prototype.onGetDomainList.name,
watches: ['accessId', 'regionId'],
})
)
fcDomains!: string[];
@TaskInput({
title: '域名支持的协议类型',
component: {
name: 'a-select',
value: '',
options: [
{ value: '', label: '保持原样(适用于原来已经开启了HTTPS)' },
{ value: 'HTTPS', label: '仅HTTPS' },
{ value: 'HTTP,HTTPS', label: 'HTTP与HTTPS同时支持' },
],
},
})
protocol!: string;
async onInstance() {}
async execute(): Promise<void> {
this.logger.info('开始部署证书到阿里云');
const access = await this.getAccess<AliyunAccess>(this.accessId);
const client = await this.getClient(access);
const $Util = await import('@alicloud/tea-util');
const $OpenApi = await import('@alicloud/openapi-client');
for (const domainName of this.fcDomains) {
const params = new $OpenApi.Params({
// 接口名称
action: 'UpdateCustomDomain',
// 接口版本
version: '2023-03-30',
// 接口协议
protocol: 'HTTPS',
// 接口 HTTP 方法
method: 'PUT',
authType: 'AK',
style: 'FC',
// 接口 PATH
pathname: `/2023-03-30/custom-domains/${domainName}`,
// 接口请求体内容格式
reqBodyType: 'json',
// 接口响应体内容格式
bodyType: 'json',
});
// body params
const certName = this.buildCertName(CertReader.getMainDomain(this.cert.crt))
const body: { [key: string]: any } = {
certConfig: {
certName: certName,
certificate: this.cert.crt,
privateKey: this.cert.key,
},
};
if (this.protocol) {
body.protocol = this.protocol;
}
const runtime = new $Util.RuntimeOptions({});
const request = new $OpenApi.OpenApiRequest({ body });
// 复制代码运行请自行打印 API 的返回值
// 返回值实际为 Map 类型,可从 Map 中获得三类数据:响应体 body、响应头 headers、HTTP 返回的状态码 statusCode。
await client.callApi(params, request, runtime);
this.logger.info(`部署[${domainName}]成功`);
}
}
async getClient(access: AliyunAccess) {
const $OpenApi = await import('@alicloud/openapi-client');
const config = new $OpenApi.Config({
accessKeyId: access.accessKeyId,
accessKeySecret: access.accessKeySecret,
});
// Endpoint 请参考 https://api.aliyun.com/product/FC
config.endpoint = `${this.accountId}.${this.regionId}.fc.aliyuncs.com`;
return new $OpenApi.default.default(config);
}
async onGetDomainList(data: any) {
if (!this.accessId) {
throw new Error('请选择Access授权');
}
const access = await this.getAccess<AliyunAccess>(this.accessId);
const client = await this.getClient(access);
const $OpenApi = await import('@alicloud/openapi-client');
const $Util = await import('@alicloud/tea-util');
const params = new $OpenApi.Params({
// 接口名称
action: 'ListCustomDomains',
// 接口版本
version: '2023-03-30',
// 接口协议
protocol: 'HTTPS',
// 接口 HTTP 方法
method: 'GET',
authType: 'AK',
style: 'FC',
// 接口 PATH
pathname: `/2023-03-30/custom-domains`,
// 接口请求体内容格式
reqBodyType: 'json',
// 接口响应体内容格式
bodyType: 'json',
});
const runtime = new $Util.RuntimeOptions({});
const request = new $OpenApi.OpenApiRequest({});
// 复制代码运行请自行打印 API 的返回值
// 返回值实际为 Map 类型,可从 Map 中获得三类数据:响应体 body、响应头 headers、HTTP 返回的状态码 statusCode。
const res = await client.callApi(params, request, runtime);
const list = res?.body?.customDomains;
if (!list || list.length === 0) {
throw new Error('没有找到FC域名,请先创建FC域名');
}
const options = list.map((item: any) => {
return {
label: item.domainName,
value: item.domainName,
title: item.domainName,
domain: item.domainName,
};
});
return this.ctx.utils.options.buildGroupOptions(options, this.certDomains);
}
}
new AliyunDeployCertToFC();