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[]) {
|
function groupByDomain(options: any[], inDomains: string[]) {
|
||||||
const matched = [];
|
const matched = [];
|
||||||
|
@ -19,16 +19,16 @@ function groupByDomain(options: any[], inDomains: string[]) {
|
||||||
function buildGroupOptions(options: any[], inDomains: string[]) {
|
function buildGroupOptions(options: any[], inDomains: string[]) {
|
||||||
const grouped = groupByDomain(options, inDomains);
|
const grouped = groupByDomain(options, inDomains);
|
||||||
const groupOptions = [];
|
const groupOptions = [];
|
||||||
groupOptions.push({ value: 'matched', disabled: true, label: '----已匹配----' });
|
groupOptions.push({ value: "matched", disabled: true, label: "----已匹配----" });
|
||||||
if (grouped.matched.length === 0) {
|
if (grouped.matched.length === 0) {
|
||||||
options.push({ value: '', disabled: true, label: '没有可以匹配的域名' });
|
options.push({ value: "", disabled: true, label: "没有可以匹配的域名" });
|
||||||
} else {
|
} else {
|
||||||
for (const matched of grouped.matched) {
|
for (const matched of grouped.matched) {
|
||||||
groupOptions.push(matched);
|
groupOptions.push(matched);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (grouped.notMatched.length > 0) {
|
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) {
|
for (const notMatched of grouped.notMatched) {
|
||||||
groupOptions.push(notMatched);
|
groupOptions.push(notMatched);
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@ export function createRemoteSelectInputDefine(opts?: {
|
||||||
multi?: boolean;
|
multi?: boolean;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
rules?: any;
|
rules?: any;
|
||||||
|
mergeScript?: string;
|
||||||
}) {
|
}) {
|
||||||
const title = opts?.title || "请选择";
|
const title = opts?.title || "请选择";
|
||||||
const certDomainsInputKey = opts?.certDomainsInputKey || "certDomains";
|
const certDomainsInputKey = opts?.certDomainsInputKey || "certDomains";
|
||||||
|
@ -66,7 +67,9 @@ export function createRemoteSelectInputDefine(opts?: {
|
||||||
},
|
},
|
||||||
rules: opts?.rules,
|
rules: opts?.rules,
|
||||||
required: opts.required ?? true,
|
required: opts.required ?? true,
|
||||||
mergeScript: `
|
mergeScript:
|
||||||
|
opts.mergeScript ??
|
||||||
|
`
|
||||||
return {
|
return {
|
||||||
component:{
|
component:{
|
||||||
form: ctx.compute(({form})=>{
|
form: ctx.compute(({form})=>{
|
||||||
|
@ -80,3 +83,5 @@ export function createRemoteSelectInputDefine(opts?: {
|
||||||
|
|
||||||
return merge(item, opts?.formItem);
|
return merge(item, opts?.formItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -53,11 +53,15 @@ const getOptions = async () => {
|
||||||
if (!define) {
|
if (!define) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const pluginType = getPluginType();
|
||||||
|
const { form } = getScope();
|
||||||
|
const input = pluginType === "plugin" ? form.input : form;
|
||||||
|
|
||||||
for (let key in define.input) {
|
for (let key in define.input) {
|
||||||
const inWatches = props.watches.includes(key);
|
const inWatches = props.watches.includes(key);
|
||||||
const inputDefine = define.input[key];
|
const inputDefine = define.input[key];
|
||||||
if (inWatches && inputDefine.required) {
|
if (inWatches && inputDefine.required) {
|
||||||
const value = props.form[key];
|
const value = input[key];
|
||||||
if (value == null || value === "") {
|
if (value == null || value === "") {
|
||||||
console.log("remote-select required", key);
|
console.log("remote-select required", key);
|
||||||
return;
|
return;
|
||||||
|
@ -69,8 +73,6 @@ const getOptions = async () => {
|
||||||
hasError.value = false;
|
hasError.value = false;
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
optionsRef.value = [];
|
optionsRef.value = [];
|
||||||
const { form } = getScope();
|
|
||||||
const pluginType = getPluginType();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await doRequest(
|
const res = await doRequest(
|
||||||
|
@ -78,7 +80,7 @@ const getOptions = async () => {
|
||||||
type: pluginType,
|
type: pluginType,
|
||||||
typeName: form.type,
|
typeName: form.type,
|
||||||
action: props.action,
|
action: props.action,
|
||||||
input: pluginType === "plugin" ? form.input : form,
|
input,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onError(err: any) {
|
onError(err: any) {
|
||||||
|
@ -115,11 +117,16 @@ async function refreshOptions() {
|
||||||
watch(
|
watch(
|
||||||
() => {
|
() => {
|
||||||
const values = [];
|
const values = [];
|
||||||
|
|
||||||
|
const pluginType = getPluginType();
|
||||||
|
const { form } = getScope();
|
||||||
|
const input = pluginType === "plugin" ? form.input : form;
|
||||||
|
|
||||||
for (const item of props.watches) {
|
for (const item of props.watches) {
|
||||||
values.push(props.form[item]);
|
values.push(input[item]);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
form: props.form,
|
form: input,
|
||||||
watched: values,
|
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;
|
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() {
|
async getServiceCls() {
|
||||||
if (this.CommonService) {
|
if (this.CommonService) {
|
||||||
return this.CommonService;
|
return this.CommonService;
|
||||||
|
@ -114,11 +127,11 @@ export class VolcengineClient {
|
||||||
defaultVersion: string;
|
defaultVersion: string;
|
||||||
}) {
|
}) {
|
||||||
super(Object.assign({ host: "open.volcengineapi.com" }, options));
|
super(Object.assign({ host: "open.volcengineapi.com" }, options));
|
||||||
this.Generic = async (req: { action: string, body?: any, method?: string, query?: any }) => {
|
this.Generic = async (req: { action: string, body?: any, method?: string, query?: any ,version?:string}) => {
|
||||||
const { action, method, body, query } = req;
|
const { action, method, body, query,version } = req;
|
||||||
return await this.fetchOpenAPI({
|
return await this.fetchOpenAPI({
|
||||||
Action: action,
|
Action: action,
|
||||||
Version: options.defaultVersion,
|
Version: version||options.defaultVersion,
|
||||||
method: method as any,
|
method: method as any,
|
||||||
headers: {
|
headers: {
|
||||||
"content-type": "application/json"
|
"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);
|
const res = await this.Generic(req);
|
||||||
|
if (res ==="Not Found"){
|
||||||
|
throw new Error(`${res} (检查method)`);
|
||||||
|
}
|
||||||
if (res.errorcode) {
|
if (res.errorcode) {
|
||||||
throw new Error(`${res.errorcode}:${res.message}`);
|
throw new Error(`${res.errorcode}:${res.message}`);
|
||||||
}
|
}
|
||||||
|
@ -146,4 +162,5 @@ export class VolcengineClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue