mirror of https://github.com/certd/certd
perf: 支持webhook部署证书
parent
0af193c505
commit
cbe0b1c5a6
|
@ -45,8 +45,8 @@ export class SshAccess extends BaseAccess {
|
|||
title: "私钥登录",
|
||||
helper: "私钥或密码必填一项",
|
||||
component: {
|
||||
name: "a-textarea",
|
||||
vModel: "value",
|
||||
name: "pem-input",
|
||||
vModel: "modelValue",
|
||||
},
|
||||
encrypt: true,
|
||||
})
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="pem-input">
|
||||
<FileInput v-bind="fileInput" class="mb-5" type="primary" text="选择文件" @change="onChange" />
|
||||
<a-textarea v-bind="textarea" :value="modelValue" @update:value="emitValue"></a-textarea>
|
||||
<a-textarea placeholder="或直接粘贴" v-bind="textarea" :value="modelValue" @update:value="emitValue"></a-textarea>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -27,7 +27,7 @@ function onChange(e: any) {
|
|||
const size = file.size;
|
||||
if (size > 100 * 1024) {
|
||||
notification.error({
|
||||
message: "文件超过100k,请选择正确的证书文件",
|
||||
message: "文件超过100k,请选择正确的文件",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
export * from './plugin-wait.js';
|
||||
export * from './plugin-deploy-to-mail.js';
|
||||
export * from './plugin-webhook.js';
|
||||
|
|
|
@ -0,0 +1,214 @@
|
|||
import qs from 'qs';
|
||||
import {
|
||||
AbstractTaskPlugin,
|
||||
IsTaskPlugin,
|
||||
NotificationBody,
|
||||
pluginGroups,
|
||||
RunStrategy,
|
||||
TaskInput
|
||||
} from '@certd/pipeline';
|
||||
import {CertApplyPluginNames, CertInfo, CertReader} from "@certd/plugin-cert";
|
||||
|
||||
@IsTaskPlugin({
|
||||
name: 'WebhookDeployCert',
|
||||
title: 'webhook方式部署证书',
|
||||
icon: 'ion:send-sharp',
|
||||
desc: '调用webhook部署证书',
|
||||
group: pluginGroups.other.key,
|
||||
showRunStrategy:true,
|
||||
default: {
|
||||
strategy: {
|
||||
runStrategy: RunStrategy.SkipWhenSucceed,
|
||||
},
|
||||
},
|
||||
})
|
||||
export class WebhookDeployCert extends AbstractTaskPlugin {
|
||||
|
||||
@TaskInput({
|
||||
title: "域名证书",
|
||||
helper: "请选择前置任务输出的域名证书",
|
||||
component: {
|
||||
name: "output-selector",
|
||||
from: [...CertApplyPluginNames]
|
||||
},
|
||||
required: true
|
||||
})
|
||||
cert!: CertInfo ;
|
||||
|
||||
@TaskInput({
|
||||
title: 'webhook地址',
|
||||
component: {
|
||||
placeholder: 'https://xxxxx.com/xxxx',
|
||||
},
|
||||
col: {
|
||||
span: 24,
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
webhook = '';
|
||||
|
||||
@TaskInput({
|
||||
title: '请求方式',
|
||||
value: 'POST',
|
||||
component: {
|
||||
name: 'a-select',
|
||||
placeholder: 'post/put/get',
|
||||
options: [
|
||||
{ value: 'POST', label: 'POST' },
|
||||
{ value: 'PUT', label: 'PUT' },
|
||||
{ value: 'GET', label: 'GET' },
|
||||
],
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
method = '';
|
||||
|
||||
@TaskInput({
|
||||
title: 'ContentType',
|
||||
value: 'application/json',
|
||||
component: {
|
||||
name: 'a-auto-complete',
|
||||
options: [
|
||||
{ value: 'application/json', label: 'application/json' },
|
||||
{ value: 'application/x-www-form-urlencoded', label: 'application/x-www-form-urlencoded' },
|
||||
],
|
||||
},
|
||||
helper: '也可以自定义填写',
|
||||
required: true,
|
||||
})
|
||||
contentType = '';
|
||||
|
||||
@TaskInput({
|
||||
title: 'Headers',
|
||||
component: {
|
||||
name: 'a-textarea',
|
||||
vModel: 'value',
|
||||
rows: 2,
|
||||
},
|
||||
col: {
|
||||
span: 24,
|
||||
},
|
||||
helper: '一行一个,格式为key=value',
|
||||
required: false,
|
||||
})
|
||||
headers = '';
|
||||
|
||||
@TaskInput({
|
||||
title: '消息body模版',
|
||||
value: `{
|
||||
"id":"123",
|
||||
"crt":"{crt}",
|
||||
"key":"{key}"
|
||||
}`,
|
||||
component: {
|
||||
name: 'a-textarea',
|
||||
rows: 4,
|
||||
},
|
||||
col: {
|
||||
span: 24,
|
||||
},
|
||||
helper: `根据对应的webhook接口文档,构建一个json对象作为参数(默认值只是一个示例,一般不是正确的参数)
|
||||
变量用\${}包裹\n字符串需要双引号,使用\\n换行
|
||||
如果是get方式,将作为query参数拼接到url上
|
||||
变量列表:\${domain} 主域名、\${domains} 全部域名、\${crt} 证书、\${key} 私钥、\${ic} 中间证书、\${one} 一体证书、\${der} der证书(base64)、\${pfx} pfx证书(base64)、\${jks} jks证书(base64)、`,
|
||||
required: true,
|
||||
})
|
||||
template = '';
|
||||
|
||||
@TaskInput({
|
||||
title: '忽略证书校验',
|
||||
value: false,
|
||||
component: {
|
||||
name: 'a-switch',
|
||||
vModel: 'checked',
|
||||
},
|
||||
required: false,
|
||||
})
|
||||
skipSslVerify: boolean;
|
||||
|
||||
replaceTemplate(target: string, body: any, urlEncode = false) {
|
||||
let bodyStr = target;
|
||||
const keys = Object.keys(body);
|
||||
for (const key of keys) {
|
||||
let value = urlEncode ? encodeURIComponent(body[key]) : body[key];
|
||||
value = value.replaceAll(`\n`, "\\n");
|
||||
bodyStr = bodyStr.replaceAll(`\${${key}}`, value);
|
||||
|
||||
}
|
||||
return bodyStr;
|
||||
}
|
||||
|
||||
async send() {
|
||||
if (!this.template) {
|
||||
throw new Error('模版不能为空');
|
||||
}
|
||||
if (!this.webhook) {
|
||||
throw new Error('webhook不能为空');
|
||||
}
|
||||
|
||||
|
||||
const certReader = new CertReader(this.cert)
|
||||
|
||||
const replaceBody = {
|
||||
domain: certReader.getMainDomain(),
|
||||
domains: certReader.getAllDomains().join(","),
|
||||
...this.cert
|
||||
};
|
||||
const bodyStr = this.replaceTemplate(this.template, replaceBody);
|
||||
let data = JSON.parse(bodyStr);
|
||||
|
||||
let url = this.webhook;
|
||||
if (this.method.toLowerCase() === 'get') {
|
||||
const query = qs.stringify(data);
|
||||
if (url.includes('?')) {
|
||||
url = `${url}&${query}`;
|
||||
} else {
|
||||
url = `${url}?${query}`;
|
||||
}
|
||||
data = null;
|
||||
}
|
||||
|
||||
const headers: any = {};
|
||||
if (this.headers && this.headers.trim()) {
|
||||
this.headers.split('\n').forEach(item => {
|
||||
item = item.trim();
|
||||
if (item) {
|
||||
const eqIndex = item.indexOf('=');
|
||||
if (eqIndex <= 0) {
|
||||
this.logger.warn('header格式错误,请使用=号分割', item);
|
||||
return;
|
||||
}
|
||||
const key = item.substring(0, eqIndex);
|
||||
headers[key] = item.substring(eqIndex + 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await this.http.request({
|
||||
url: url,
|
||||
method: this.method,
|
||||
headers: {
|
||||
'Content-Type': `${this.contentType}; charset=UTF-8`,
|
||||
...headers,
|
||||
},
|
||||
data: data,
|
||||
skipSslVerify: this.skipSslVerify,
|
||||
});
|
||||
return res
|
||||
} catch (e) {
|
||||
if (e.response?.data) {
|
||||
throw new Error(e.message + ',' + JSON.stringify(e.response.data));
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async onInstance() {}
|
||||
async execute(): Promise<void> {
|
||||
this.logger.info(`通过webhook部署开始`);
|
||||
await this.send();
|
||||
this.logger.info('部署成功');
|
||||
}
|
||||
}
|
||||
new WebhookDeployCert();
|
Loading…
Reference in New Issue