mirror of https://github.com/certd/certd
perf: 支持部署证书到火山dcdn
parent
a63d687f1c
commit
5f85219495
|
@ -1,4 +1,4 @@
|
|||
import { domainUtils } from './util.domain.js';
|
||||
import { domainUtils } from "./util.domain.js";
|
||||
|
||||
function groupByDomain(options: any[], inDomains: string[]) {
|
||||
const matched = [];
|
||||
|
@ -19,16 +19,16 @@ function groupByDomain(options: any[], inDomains: string[]) {
|
|||
function buildGroupOptions(options: any[], inDomains: string[]) {
|
||||
const grouped = groupByDomain(options, inDomains);
|
||||
const groupOptions = [];
|
||||
groupOptions.push({ value: 'matched', disabled: true, label: '----已匹配----' });
|
||||
groupOptions.push({ value: "matched", disabled: true, label: "----已匹配----" });
|
||||
if (grouped.matched.length === 0) {
|
||||
options.push({ value: '', disabled: true, label: '没有可以匹配的域名' });
|
||||
options.push({ value: "", disabled: true, label: "没有可以匹配的域名" });
|
||||
} else {
|
||||
for (const matched of grouped.matched) {
|
||||
groupOptions.push(matched);
|
||||
}
|
||||
}
|
||||
if (grouped.notMatched.length > 0) {
|
||||
groupOptions.push({ value: 'unmatched', disabled: true, label: '----未匹配----' });
|
||||
groupOptions.push({ value: "unmatched", disabled: true, label: "----未匹配----" });
|
||||
for (const notMatched of grouped.notMatched) {
|
||||
groupOptions.push(notMatched);
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ export function createRemoteSelectInputDefine(opts?: {
|
|||
multi?: boolean;
|
||||
required?: boolean;
|
||||
rules?: any;
|
||||
mergeScript?: string;
|
||||
}) {
|
||||
const title = opts?.title || "请选择";
|
||||
const certDomainsInputKey = opts?.certDomainsInputKey || "certDomains";
|
||||
|
@ -66,7 +67,9 @@ export function createRemoteSelectInputDefine(opts?: {
|
|||
},
|
||||
rules: opts?.rules,
|
||||
required: opts.required ?? true,
|
||||
mergeScript: `
|
||||
mergeScript:
|
||||
opts.mergeScript ??
|
||||
`
|
||||
return {
|
||||
component:{
|
||||
form: ctx.compute(({form})=>{
|
||||
|
@ -80,3 +83,5 @@ export function createRemoteSelectInputDefine(opts?: {
|
|||
|
||||
return merge(item, opts?.formItem);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -53,11 +53,15 @@ const getOptions = async () => {
|
|||
if (!define) {
|
||||
return;
|
||||
}
|
||||
const pluginType = getPluginType();
|
||||
const { form } = getScope();
|
||||
const input = pluginType === "plugin" ? form.input : form;
|
||||
|
||||
for (let key in define.input) {
|
||||
const inWatches = props.watches.includes(key);
|
||||
const inputDefine = define.input[key];
|
||||
if (inWatches && inputDefine.required) {
|
||||
const value = props.form[key];
|
||||
const value = input[key];
|
||||
if (value == null || value === "") {
|
||||
console.log("remote-select required", key);
|
||||
return;
|
||||
|
@ -69,8 +73,6 @@ const getOptions = async () => {
|
|||
hasError.value = false;
|
||||
loading.value = true;
|
||||
optionsRef.value = [];
|
||||
const { form } = getScope();
|
||||
const pluginType = getPluginType();
|
||||
|
||||
try {
|
||||
const res = await doRequest(
|
||||
|
@ -78,7 +80,7 @@ const getOptions = async () => {
|
|||
type: pluginType,
|
||||
typeName: form.type,
|
||||
action: props.action,
|
||||
input: pluginType === "plugin" ? form.input : form,
|
||||
input,
|
||||
},
|
||||
{
|
||||
onError(err: any) {
|
||||
|
@ -115,11 +117,16 @@ async function refreshOptions() {
|
|||
watch(
|
||||
() => {
|
||||
const values = [];
|
||||
|
||||
const pluginType = getPluginType();
|
||||
const { form } = getScope();
|
||||
const input = pluginType === "plugin" ? form.input : form;
|
||||
|
||||
for (const item of props.watches) {
|
||||
values.push(props.form[item]);
|
||||
values.push(input[item]);
|
||||
}
|
||||
return {
|
||||
form: props.form,
|
||||
form: input,
|
||||
watched: values,
|
||||
};
|
||||
},
|
||||
|
|
|
@ -0,0 +1,204 @@
|
|||
import {AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput} from "@certd/pipeline";
|
||||
import {createCertDomainGetterInputDefine, createRemoteSelectInputDefine} from "@certd/plugin-lib";
|
||||
import {CertApplyPluginNames, CertInfo, CertReader} from "@certd/plugin-cert";
|
||||
import {VolcengineAccess} from "../access.js";
|
||||
import {VolcengineClient} from "../ve-client.js";
|
||||
|
||||
@IsTaskPlugin({
|
||||
name: "VolcengineDeployToDCDN",
|
||||
title: "火山引擎-部署证书至DCDN",
|
||||
icon: "svg:icon-volcengine",
|
||||
group: pluginGroups.volcengine.key,
|
||||
desc: "部署至火山引擎全站加速",
|
||||
// showRunStrategy: true,
|
||||
default: {
|
||||
strategy: {
|
||||
runStrategy: RunStrategy.SkipWhenSucceed
|
||||
}
|
||||
}
|
||||
})
|
||||
export class VolcengineDeployToDCDN extends AbstractTaskPlugin {
|
||||
@TaskInput({
|
||||
title: "域名证书",
|
||||
helper: "请选择前置任务输出的域名证书",
|
||||
component: {
|
||||
name: "output-selector",
|
||||
from: [...CertApplyPluginNames]
|
||||
},
|
||||
required: true
|
||||
})
|
||||
cert!: CertInfo;
|
||||
|
||||
@TaskInput(createCertDomainGetterInputDefine({props: {required: false}}))
|
||||
certDomains!: string[];
|
||||
|
||||
@TaskInput({
|
||||
title: "自动匹配",
|
||||
helper: "是否根据证书自动匹配合适的DCDN域名进行部署",
|
||||
value: false,
|
||||
component: {
|
||||
name: "a-switch",
|
||||
type: "checked"
|
||||
},
|
||||
required: true
|
||||
})
|
||||
autoMatch!: boolean;
|
||||
|
||||
@TaskInput({
|
||||
title: "Access授权",
|
||||
helper: "火山引擎AccessKeyId、AccessKeySecret",
|
||||
component: {
|
||||
name: "access-selector",
|
||||
type: "volcengine"
|
||||
},
|
||||
required: true
|
||||
})
|
||||
accessId!: string;
|
||||
|
||||
|
||||
@TaskInput(
|
||||
createRemoteSelectInputDefine({
|
||||
title: "DCDN域名",
|
||||
helper: "选择要部署证书的DCDN域名",
|
||||
action: VolcengineDeployToDCDN.prototype.onGetDomainList.name,
|
||||
watches: ["certDomains", "accessId"],
|
||||
required: true,
|
||||
mergeScript: `
|
||||
return {
|
||||
show: ctx.compute(({form})=>{
|
||||
return !form.autoMatch
|
||||
})
|
||||
}
|
||||
`
|
||||
})
|
||||
)
|
||||
domainList!: string | string[];
|
||||
|
||||
|
||||
async onInstance() {
|
||||
}
|
||||
|
||||
async uploadCert(client: VolcengineClient) {
|
||||
const certService = await client.getCertCenterService();
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
async execute(): Promise<void> {
|
||||
this.logger.info("开始部署证书到火山引擎DCDN");
|
||||
|
||||
const client = await this.getClient();
|
||||
let certId = await this.uploadCert(client);
|
||||
|
||||
const service = await client.getDCDNService();
|
||||
|
||||
this.certDomains = new CertReader(this.cert).getAllDomains()
|
||||
|
||||
|
||||
let domainList = this.domainList
|
||||
if (!this.autoMatch) {
|
||||
//手动根据域名部署
|
||||
if (!this.domainList || this.domainList.length === 0) {
|
||||
throw new Error("域名列表不能为空");
|
||||
}
|
||||
} else {
|
||||
//自动匹配
|
||||
const options = await this.getDomainOptions(service);
|
||||
const grouped = this.ctx.utils.options.groupByDomain(options, this.certDomains);
|
||||
|
||||
const matched = grouped.matched
|
||||
|
||||
domainList = matched.map(item => item.domain)
|
||||
|
||||
if (domainList.length === 0) {
|
||||
this.logger.warn("没有匹配到域名,跳过部署")
|
||||
this.logger.info("当前证书域名:", this.certDomains)
|
||||
this.logger.info("当前DCDN域名:", grouped.notMatched.map(item => item.domain))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//域名十个十个的分割
|
||||
for (let i = 0; i < domainList.length; i += 10) {
|
||||
const batch = domainList.slice(i, i + 10);
|
||||
this.logger.info(`开始部署证书到域名:${batch}`);
|
||||
const res = await service.request({
|
||||
action: "CreateCertBind",
|
||||
method: "POST",
|
||||
body: {
|
||||
"DomainNames": batch,
|
||||
"CertSource": "volc",
|
||||
"CertId": certId
|
||||
},
|
||||
version: "2021-04-01"
|
||||
});
|
||||
this.logger.info(`部署证书到域名成功:`,JSON.stringify(res));
|
||||
}
|
||||
|
||||
this.logger.info("部署完成");
|
||||
|
||||
}
|
||||
|
||||
|
||||
async getClient() {
|
||||
const access = await this.getAccess<VolcengineAccess>(this.accessId);
|
||||
|
||||
return new VolcengineClient({
|
||||
logger: this.logger,
|
||||
access,
|
||||
http: this.http
|
||||
})
|
||||
}
|
||||
|
||||
async onGetDomainList(data: any) {
|
||||
if (!this.accessId) {
|
||||
throw new Error("请选择Access授权");
|
||||
}
|
||||
|
||||
const client = await this.getClient();
|
||||
const service = await client.getDCDNService();
|
||||
const options = await this.getDomainOptions(service);
|
||||
return this.ctx.utils.options.buildGroupOptions(options, this.certDomains);
|
||||
}
|
||||
|
||||
private async getDomainOptions(service: any) {
|
||||
const res = await service.request({
|
||||
method: "POST",
|
||||
action: "DescribeUserDomains",
|
||||
body: {
|
||||
"PageSize": 1000
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const list = res.Result?.Domains;
|
||||
if (!list || list.length === 0) {
|
||||
throw new Error("找不到DCDN域名,您也可以手动输入域名");
|
||||
}
|
||||
const options = list.map((item: any) => {
|
||||
return {
|
||||
value: item.Domain,
|
||||
label: `${item.Domain}<${item.Scope}>`,
|
||||
domain: item.Domain
|
||||
};
|
||||
});
|
||||
return options;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
new VolcengineDeployToDCDN();
|
|
@ -100,6 +100,19 @@ export class VolcengineClient {
|
|||
return service;
|
||||
}
|
||||
|
||||
async getDCDNService( opts?: { }) {
|
||||
const CommonService = await this.getServiceCls();
|
||||
|
||||
const service = new CommonService({
|
||||
serviceName: "dcdn",
|
||||
defaultVersion: "2023-01-01"
|
||||
});
|
||||
service.setAccessKeyId(this.opts.access.accessKeyId);
|
||||
service.setSecretKey(this.opts.access.secretAccessKey);
|
||||
service.setRegion("cn-north-1");
|
||||
return service;
|
||||
}
|
||||
|
||||
async getServiceCls() {
|
||||
if (this.CommonService) {
|
||||
return this.CommonService;
|
||||
|
@ -114,11 +127,11 @@ export class VolcengineClient {
|
|||
defaultVersion: string;
|
||||
}) {
|
||||
super(Object.assign({ host: "open.volcengineapi.com" }, options));
|
||||
this.Generic = async (req: { action: string, body?: any, method?: string, query?: any }) => {
|
||||
const { action, method, body, query } = req;
|
||||
this.Generic = async (req: { action: string, body?: any, method?: string, query?: any ,version?:string}) => {
|
||||
const { action, method, body, query,version } = req;
|
||||
return await this.fetchOpenAPI({
|
||||
Action: action,
|
||||
Version: options.defaultVersion,
|
||||
Version: version||options.defaultVersion,
|
||||
method: method as any,
|
||||
headers: {
|
||||
"content-type": "application/json"
|
||||
|
@ -129,8 +142,11 @@ export class VolcengineClient {
|
|||
};
|
||||
}
|
||||
|
||||
async request(req: { action: string, body?: any, method?: string, query?: any }) {
|
||||
async request(req: { action: string, body?: any, method?: string, query?: any,version?:string }) {
|
||||
const res = await this.Generic(req);
|
||||
if (res ==="Not Found"){
|
||||
throw new Error(`${res} (检查method)`);
|
||||
}
|
||||
if (res.errorcode) {
|
||||
throw new Error(`${res.errorcode}:${res.message}`);
|
||||
}
|
||||
|
@ -146,4 +162,5 @@ export class VolcengineClient {
|
|||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue