perf: 创建证书任务可以选择lege插件

pull/101/head
xiaojunnuo 2024-07-21 02:26:03 +08:00
parent 4afbf20c1a
commit affef13037
40 changed files with 434 additions and 378 deletions

View File

@ -23,6 +23,7 @@ export type TaskInputDefine = FormItemProps;
export type PluginDefine = Registrable & { export type PluginDefine = Registrable & {
default?: any; default?: any;
group?: string;
input?: { input?: {
[key: string]: TaskInputDefine; [key: string]: TaskInputDefine;
}; };

View File

@ -0,0 +1,25 @@
import { PluginDefine } from "./api";
export class PluginGroup {
key: string;
title: string;
desc?: string;
order: number;
plugins: PluginDefine[];
constructor(key: string, title: string, order = 0, desc = "") {
this.key = key;
this.title = title;
this.order = order;
this.desc = desc;
this.plugins = [];
}
}
export const pluginGroups = {
cert: new PluginGroup("cert", "证书申请", 1),
aliyun: new PluginGroup("aliyun", "阿里云", 2),
huawei: new PluginGroup("huawei", "华为云", 3),
tencent: new PluginGroup("tencent", "腾讯云", 4),
host: new PluginGroup("host", "主机", 5),
other: new PluginGroup("other", "其他", 7),
};

View File

@ -1,3 +1,4 @@
export * from "./api.js"; export * from "./api.js";
export * from "./registry.js"; export * from "./registry.js";
export * from "./decorator.js"; export * from "./decorator.js";
export * from "./group.js";

View File

@ -1,4 +1,16 @@
import { Registry } from "../registry/index.js"; import { OnRegisterContext, Registry } from "../registry/index.js";
import { AbstractTaskPlugin } from "./api.js"; import { AbstractTaskPlugin } from "./api.js";
import { pluginGroups } from "./group.js";
export const pluginRegistry = new Registry<AbstractTaskPlugin>("plugin"); const onRegister = ({ key, value }: OnRegisterContext<AbstractTaskPlugin>) => {
const group = value?.define?.group as string;
if (group) {
if (pluginGroups.hasOwnProperty(group)) {
// @ts-ignore
pluginGroups[group].plugins.push(value.define);
} else {
pluginGroups.other.plugins.push(value.define);
}
}
};
export const pluginRegistry = new Registry<AbstractTaskPlugin>("plugin", onRegister);

View File

@ -4,20 +4,31 @@ export type Registrable = {
name: string; name: string;
title: string; title: string;
desc?: string; desc?: string;
group?: string;
}; };
export type RegistryItem<T> = { export type RegistryItem<T> = {
define: Registrable; define: Registrable;
target: T; target: T;
}; };
export type OnRegisterContext<T> = {
registry: Registry<T>;
key: string;
value: RegistryItem<T>;
};
export type OnRegister<T> = (ctx: OnRegisterContext<T>) => void;
export class Registry<T> { export class Registry<T> {
type = ""; type = "";
storage: { storage: {
[key: string]: RegistryItem<T>; [key: string]: RegistryItem<T>;
} = {}; } = {};
constructor(type: string) { onRegister?: OnRegister<T>;
constructor(type: string, onRegister?: OnRegister<T>) {
this.type = type; this.type = type;
this.onRegister = onRegister;
} }
register(key: string, value: RegistryItem<T>) { register(key: string, value: RegistryItem<T>) {
@ -25,6 +36,13 @@ export class Registry<T> {
return; return;
} }
this.storage[key] = value; this.storage[key] = value;
if (this.onRegister) {
this.onRegister({
registry: this,
key,
value,
});
}
logger.info(`注册插件:${this.type}:${key}`); logger.info(`注册插件:${this.type}:${key}`);
} }

View File

@ -21,6 +21,7 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
col: { col: {
span: 24, span: 24,
}, },
order: -1,
helper: helper:
"1、支持通配符域名例如 *.foo.com、foo.com、*.test.handsfree.work\n" + "1、支持通配符域名例如 *.foo.com、foo.com、*.test.handsfree.work\n" +
"2、支持多个域名、多个子域名、多个通配符域名打到一个证书上域名必须是在同一个DNS提供商解析\n" + "2、支持多个域名、多个子域名、多个通配符域名打到一个证书上域名必须是在同一个DNS提供商解析\n" +
@ -36,12 +37,14 @@ export abstract class CertApplyBasePlugin extends AbstractTaskPlugin {
vModel: "value", vModel: "value",
}, },
required: true, required: true,
order: -1,
helper: "请输入邮箱", helper: "请输入邮箱",
}) })
email!: string; email!: string;
@TaskInput({ @TaskInput({
title: "更新天数", title: "更新天数",
value: 20,
component: { component: {
name: "a-input-number", name: "a-input-number",
vModel: "value", vModel: "value",

View File

@ -1,4 +1,4 @@
import { Decorator, IsTaskPlugin, RunStrategy, TaskInput } from "@certd/pipeline"; import { Decorator, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
import type { CertInfo, SSLProvider } from "./acme.js"; import type { CertInfo, SSLProvider } from "./acme.js";
import { AcmeService } from "./acme.js"; import { AcmeService } from "./acme.js";
import _ from "lodash-es"; import _ from "lodash-es";
@ -11,7 +11,8 @@ export type { CertInfo };
@IsTaskPlugin({ @IsTaskPlugin({
name: "CertApply", name: "CertApply",
title: "证书申请", title: "证书申请JS版",
group: pluginGroups.cert.key,
desc: "免费通配符域名证书申请,支持多个域名打到同一个证书上", desc: "免费通配符域名证书申请,支持多个域名打到同一个证书上",
default: { default: {
input: { input: {
@ -46,6 +47,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
name: "pi-access-selector", name: "pi-access-selector",
type: "eab", type: "eab",
}, },
maybeNeed: true,
helper: "如果使用ZeroSSL证书需要提供EAB授权 请前往 https://app.zerossl.com/developer 生成 'EAB Credentials for ACME Clients' ", helper: "如果使用ZeroSSL证书需要提供EAB授权 请前往 https://app.zerossl.com/developer 生成 'EAB Credentials for ACME Clients' ",
}) })
eabAccessId!: number; eabAccessId!: number;

View File

@ -1,4 +1,4 @@
import { IsTaskPlugin, RunStrategy, sp, Step, TaskInput } from "@certd/pipeline"; import { IsTaskPlugin, pluginGroups, RunStrategy, sp, Step, TaskInput } from "@certd/pipeline";
import type { CertInfo } from "./acme.js"; import type { CertInfo } from "./acme.js";
import { CertReader } from "./cert-reader.js"; import { CertReader } from "./cert-reader.js";
import { CertApplyBasePlugin } from "./base.js"; import { CertApplyBasePlugin } from "./base.js";
@ -12,7 +12,8 @@ export type { CertInfo };
@IsTaskPlugin({ @IsTaskPlugin({
name: "CertApplyLego", name: "CertApplyLego",
title: "证书申请Lego", title: "证书申请Lego",
desc: "支持海量DNS解析提供商推荐使用", group: pluginGroups.cert.key,
desc: "支持海量DNS解析提供商推荐使用一样的免费通配符域名证书申请支持多个域名打到同一个证书上",
default: { default: {
input: { input: {
renewDays: 20, renewDays: 20,
@ -29,7 +30,9 @@ export class CertApplyLegoPlugin extends CertApplyBasePlugin {
component: { component: {
name: "a-input", name: "a-input",
vModel: "value", vModel: "value",
placeholder: "alidns",
}, },
helper: "你的域名是通过哪家提供商进行解析的具体应该配置什么请参考lego文档https://go-acme.github.io/lego/dns/",
required: true, required: true,
}) })
dnsType!: string; dnsType!: string;
@ -39,7 +42,8 @@ export class CertApplyLegoPlugin extends CertApplyBasePlugin {
component: { component: {
name: "a-textarea", name: "a-textarea",
vModel: "value", vModel: "value",
rows: 6, rows: 4,
placeholder: "ALICLOUD_ACCESS_KEY=abcdefghijklmnopqrstuvwx\nALICLOUD_SECRET_KEY=your-secret-key",
}, },
required: true, required: true,
helper: "一行一条,例如 appKeyId=xxxxx具体配置请参考lego文档https://go-acme.github.io/lego/dns/", helper: "一行一条,例如 appKeyId=xxxxx具体配置请参考lego文档https://go-acme.github.io/lego/dns/",
@ -52,16 +56,20 @@ export class CertApplyLegoPlugin extends CertApplyBasePlugin {
name: "pi-access-selector", name: "pi-access-selector",
type: "eab", type: "eab",
}, },
maybeNeed: true,
helper: "如果需要提供EAB授权", helper: "如果需要提供EAB授权",
}) })
eabAccessId!: number; legoEabAccessId!: number;
@TaskInput({ @TaskInput({
title: "自定义LEGO参数", title: "自定义LEGO参数",
component: { component: {
name: "a-input", name: "a-input",
vModel: "value", vModel: "value",
placeholder: "--dns-timeout 30",
}, },
helper: "额外的lego命令行参数参考文档https://go-acme.github.io/lego/usage/cli/options/",
maybeNeed: true,
}) })
customArgs = ""; customArgs = "";
@ -73,8 +81,8 @@ export class CertApplyLegoPlugin extends CertApplyBasePlugin {
this.userContext = this.ctx.userContext; this.userContext = this.ctx.userContext;
this.http = this.ctx.http; this.http = this.ctx.http;
this.lastStatus = this.ctx.lastStatus as Step; this.lastStatus = this.ctx.lastStatus as Step;
if (this.eabAccessId) { if (this.legoEabAccessId) {
this.eab = await this.ctx.accessService.getById(this.eabAccessId); this.eab = await this.ctx.accessService.getById(this.legoEabAccessId);
} }
} }
async onInit(): Promise<void> {} async onInit(): Promise<void> {}

View File

@ -5,7 +5,7 @@ RUN npm install -g pnpm@8.15.7 --registry=https://registry.npmmirror.com
RUN pnpm config set registry https://registry.npmmirror.com/ RUN pnpm config set registry https://registry.npmmirror.com/
RUN cd /workspace/certd-client && pnpm install && npm run build RUN cd /workspace/certd-client && pnpm install && npm run build
RUN cd /workspace/certd-server && pnpm install && npm run build RUN cd /workspace/certd-server && pnpm install && npm run build-on-docker
RUN cp /workspace/certd-client/dist/* /workspace/certd-server/public/ -rf RUN cp /workspace/certd-client/dist/* /workspace/certd-server/public/ -rf

View File

@ -9,12 +9,7 @@ const defaultInputDefine = {
} }
}; };
export async function GetList(query: any) { function initPlugins(plugins: any) {
const plugins = await request({
url: apiPrefix + "/list",
method: "post",
params: query
});
for (const plugin of plugins) { for (const plugin of plugins) {
for (const key in plugin.input) { for (const key in plugin.input) {
const field = _.merge({}, defaultInputDefine, plugin.input[key]); const field = _.merge({}, defaultInputDefine, plugin.input[key]);
@ -24,7 +19,7 @@ export async function GetList(query: any) {
//嵌套对象 //嵌套对象
field.key = ["input", key]; field.key = ["input", key];
if (field.required) { if (field.required) {
delete field.required; // delete field.required;
if (field.rules == null) { if (field.rules == null) {
field.rules = []; field.rules = [];
} }
@ -34,5 +29,28 @@ export async function GetList(query: any) {
} }
} }
console.log("plugins", plugins); console.log("plugins", plugins);
}
export async function GetList(query: any) {
const plugins = await request({
url: apiPrefix + "/list",
method: "post",
params: query
});
initPlugins(plugins);
return plugins; return plugins;
} }
export async function GetGroups(query: any) {
const groups = await request({
url: apiPrefix + "/groups",
method: "post",
params: query
});
const plugins: any = [];
for (const groupKey in groups) {
plugins.push(...groups[groupKey].plugins);
}
initPlugins(plugins);
return groups;
}

View File

@ -1,7 +1,39 @@
import { compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from "@fast-crud/fast-crud"; import { compute, CreateCrudOptionsRet, dict } from "@fast-crud/fast-crud";
import { Dicts } from "./dicts"; import { PluginGroup } from "@certd/pipeline";
export default function (certPluginGroup: PluginGroup, formWrapperRef: any): CreateCrudOptionsRet {
const inputs: any = {};
for (const plugin of certPluginGroup.plugins) {
for (const inputKey in plugin.input) {
if (inputs[inputKey]) {
inputs[inputKey].form.show = true;
continue;
}
const inputDefine = plugin.input[inputKey];
if (!inputDefine.required && !inputDefine.maybeNeed) {
continue;
}
inputs[inputKey] = {
title: inputDefine.title,
form: {
...inputDefine,
show: compute((ctx) => {
console.log(formWrapperRef);
const form = formWrapperRef.value.getFormData();
if (!form) {
return false;
}
return form?.certApplyPlugin === plugin.name;
})
}
};
}
}
console.log(inputs);
debugger;
export default function (): CreateCrudOptionsRet {
return { return {
crudOptions: { crudOptions: {
form: { form: {
@ -10,143 +42,31 @@ export default function (): CreateCrudOptionsRet {
} }
}, },
columns: { columns: {
domains: { certApplyPlugin: {
title: "域名", title: "证书申请插件",
type: "dict-select", type: "dict-select",
search: { dict: dict({
show: true, data: [
component: { { value: "CertApply", label: "JS-ACME" },
name: "a-input" { value: "CertApplyLego", label: "Lego-ACME" }
} ]
}, }),
form: { form: {
col: { order: 0,
span: 24 value: "CertApply",
},
wrapperCol: {
span: null
},
component: {
mode: "tags",
open: false
},
helper: { helper: {
render: () => { render: () => {
return ( return (
<div> <ul>
<div> *.foo.com *.test.handsfree.work</div> <li>Lego-ACMELegoDNS</li>
<div>DNS</div> <li>JS-ACMEDNSCloudflare</li>
<div>*.foo.comxxx.yyy.foo.com</div> </ul>
<div></div>
</div>
); );
} }
},
valueResolve({ form }) {
if (form.domains instanceof String) {
form.domains = form.domains?.join(",");
}
},
rules: [{ required: true, message: "请填写域名" }]
}
},
email: {
title: "邮箱",
type: "text",
search: { show: false },
form: {
rules: [{ required: true, type: "email", message: "请填写邮箱" }]
}
},
blank: {
title: "占位",
type: "text",
form: {
blank: true
}
},
sslProvider: {
title: "证书提供商",
type: "dict-select",
dict: Dicts.sslProviderDict
},
eabAccess: {
title: "EAB授权",
type: "dict-select",
form: {
component: {
name: "PiAccessSelector",
type: "eab",
vModel: "modelValue"
},
helper: "如果是ZeroSSL需要配置EAB授权https://app.zerossl.com/developer 生成 'EAB' "
}
},
dnsProviderType: {
title: "DNS提供商",
type: "dict-select",
dict: Dicts.dnsProviderTypeDict,
form: {
value: "aliyun",
rules: [{ required: true, message: "请选择DNS提供商" }],
valueChange({ form }) {
form.dnsProviderAccess = null;
} }
} }
}, },
dnsProviderAccess: { ...inputs
title: "DNS授权",
type: "text",
form: {
component: {
name: "PiAccessSelector",
type: compute(({ form }) => {
return form.dnsProviderType;
}),
vModel: "modelValue"
},
rules: [{ required: true, message: "请选择DNS授权" }]
}
}
// country: {
// title: "国家",
// type: "text",
// form: {
// value: "China"
// }
// },
// state: {
// title: "省份",
// type: "text",
// form: {
// value: "GuangDong"
// }
// },
// locality: {
// title: "市区",
// type: "text",
// form: {
// value: "NanShan"
// }
// },
// organization: {
// title: "单位",
// type: "text",
// form: {
// value: "CertD"
// }
// },
// organizationUnit: {
// title: "部门",
// type: "text",
// form: {
// value: "IT Dept"
// }
// },
// remark: {
// title: "备注",
// type: "text"
// }
} }
} }
}; };

View File

@ -1,5 +1,5 @@
<template> <template>
<fs-form-wrapper ref="formWrapperRef" /> <fs-form-wrapper v-if="formWrapperOptions" ref="formWrapperRef" />
</template> </template>
<script lang="ts"> <script lang="ts">
@ -7,14 +7,23 @@ import { useColumns, useExpose } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud.jsx"; import createCrudOptions from "./crud.jsx";
import { ref } from "vue"; import { ref } from "vue";
import _ from "lodash-es"; import _ from "lodash-es";
import * as api from "../api.plugin";
import { PluginGroup, PluginGroups } from "/@/views/certd/pipeline/pipeline/type";
export default { export default {
name: "PiCertdForm", name: "PiCertdForm",
setup(props: any, ctx: any) { setup(props: any, ctx: any) {
const formWrapperRef = ref();
const formWrapperOptions = ref();
const doSubmitRef = ref();
async function buildFormOptions() {
const pluginGroups: { [key: string]: PluginGroup } = await api.GetGroups({});
const certPluginGroup = pluginGroups.cert;
// //
const { buildFormOptions } = useColumns(); const { buildFormOptions } = useColumns();
//使crudOptions //使crudOptions
let { crudOptions } = createCrudOptions(); let { crudOptions } = createCrudOptions(certPluginGroup, formWrapperRef);
const doSubmitRef = ref();
const formOptions = buildFormOptions( const formOptions = buildFormOptions(
_.merge(crudOptions, { _.merge(crudOptions, {
form: { form: {
@ -26,9 +35,9 @@ export default {
}) as any }) as any
); );
const formWrapperRef = ref();
const formWrapperOptions = ref();
formWrapperOptions.value = formOptions; formWrapperOptions.value = formOptions;
}
buildFormOptions();
function open(doSubmit: any) { function open(doSubmit: any) {
doSubmitRef.value = doSubmit; doSubmitRef.value = doSubmit;
formWrapperRef.value.open(formWrapperOptions.value); formWrapperRef.value.open(formWrapperOptions.value);

View File

@ -58,7 +58,7 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
strategy: { strategy: {
runStrategy: 0 // 正常执行 runStrategy: 0 // 正常执行
}, },
type: "CertApply" type: form.certApplyPlugin
} }
] ]
} }

View File

@ -11,7 +11,7 @@ import * as pluginApi from "./api.plugin";
import * as historyApi from "./api.history"; import * as historyApi from "./api.history";
import * as api from "./api"; import * as api from "./api";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import { PipelineDetail, PipelineOptions, RunHistory } from "./pipeline/type"; import { PipelineDetail, PipelineOptions, PluginGroups, RunHistory } from "./pipeline/type";
export default defineComponent({ export default defineComponent({
name: "PipelineDetail", name: "PipelineDetail",
@ -43,9 +43,9 @@ export default defineComponent({
return detail; return detail;
}, },
async getPlugins() { async getPluginGroups() {
const plugins = await pluginApi.GetList({}); const groups = await pluginApi.GetGroups({});
return plugins as any[]; return new PluginGroups(groups);
}, },
async doSave(pipelineConfig: any) { async doSave(pipelineConfig: any) {

View File

@ -4,13 +4,9 @@
<div class="title">我的流水线</div> <div class="title">我的流水线</div>
</template> </template>
<fs-crud ref="crudRef" v-bind="crudBinding"> <fs-crud ref="crudRef" v-bind="crudBinding">
<template #actionbar-right> <template #actionbar-right> </template>
<!-- <span style="margin-left: 10px">出现<a-tag>Promise rejected attempt #18,retrying in 10000ms:No TXT recordsfound for name</a-tag></span>-->
</template>
<template #form-bottom> <template #form-bottom>
<div> <div>申请证书</div>
申请证书
</div>
</template> </template>
<pi-certd-form ref="certdFormRef"></pi-certd-form> <pi-certd-form ref="certdFormRef"></pi-certd-form>
</fs-crud> </fs-crud>

View File

@ -23,8 +23,10 @@ export default {
const currentStepIndex = inject("currentStepIndex") as Ref<number>; const currentStepIndex = inject("currentStepIndex") as Ref<number>;
const currentTask = inject("currentTask") as Ref<any>; const currentTask = inject("currentTask") as Ref<any>;
const getPluginGroups = inject("getPluginGroups") as Ref<any>;
const pluginGroups = getPluginGroups();
function onCreate() { function onCreate() {
options.value = pluginManager.getPreStepOutputOptions({ options.value = pluginGroups.getPreStepOutputOptions({
pipeline: pipeline.value, pipeline: pipeline.value,
currentStageIndex: currentStageIndex.value, currentStageIndex: currentStageIndex.value,
currentStepIndex: currentStepIndex.value, currentStepIndex: currentStepIndex.value,
@ -40,7 +42,7 @@ export default {
watch( watch(
() => { () => {
return pluginManager.map; return pluginGroups.value.map;
}, },
() => { () => {
onCreate(); onCreate();

View File

@ -1,5 +1,5 @@
<template> <template>
<a-drawer v-model:open="stepDrawerVisible" placement="right" :closable="true" width="600px" @after-open-change="stepDrawerOnAfterVisibleChange"> <a-drawer v-model:open="stepDrawerVisible" placement="right" :closable="true" width="700px" @after-open-change="stepDrawerOnAfterVisibleChange">
<template #title> <template #title>
编辑步骤 编辑步骤
<a-button v-if="editMode" @click="stepDelete()"> <a-button v-if="editMode" @click="stepDelete()">
@ -8,8 +8,10 @@
</template> </template>
<template v-if="currentStep"> <template v-if="currentStep">
<pi-container v-if="currentStep._isAdd" class="pi-step-form"> <pi-container v-if="currentStep._isAdd" class="pi-step-form">
<a-tabs tab-position="left">
<a-tab-pane v-for="group of pluginGroups.groups" :key="group.key" :tab="group.title">
<a-row :gutter="10"> <a-row :gutter="10">
<a-col v-for="(item, index) of stepPluginDefineList" :key="index" class="step-plugin" :span="12"> <a-col v-for="item of group.plugins" :key="item.key" class="step-plugin" :span="12">
<a-card <a-card
hoverable hoverable
:class="{ current: item.name === currentStep.type }" :class="{ current: item.name === currentStep.type }"
@ -31,7 +33,11 @@
</a-card> </a-card>
</a-col> </a-col>
</a-row> </a-row>
</a-tab-pane>
</a-tabs>
<div style="padding: 20px; margin-left: 100px">
<a-button v-if="editMode" type="primary" @click="stepTypeSave"> </a-button> <a-button v-if="editMode" type="primary" @click="stepTypeSave"> </a-button>
</div>
</pi-container> </pi-container>
<pi-container v-else class="pi-step-form"> <pi-container v-else class="pi-step-form">
<a-form ref="stepFormRef" class="step-form" :model="currentStep" :label-col="labelCol" :wrapper-col="wrapperCol"> <a-form ref="stepFormRef" class="step-form" :model="currentStep" :label-col="labelCol" :wrapper-col="wrapperCol">
@ -91,6 +97,8 @@ import { computed, inject, Ref, ref } from "vue";
import _ from "lodash-es"; import _ from "lodash-es";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { CopyOutlined } from "@ant-design/icons-vue"; import { CopyOutlined } from "@ant-design/icons-vue";
import { PluginGroups } from "/@/views/certd/pipeline/pipeline/type";
export default { export default {
name: "PiStepForm", name: "PiStepForm",
components: { CopyOutlined }, components: { CopyOutlined },
@ -107,8 +115,8 @@ export default {
* @returns * @returns
*/ */
function useStepForm() { function useStepForm() {
const stepPluginDefineList: any = inject("plugins"); const getPluginGroups: any = inject("getPluginGroups");
const pluginGroups: PluginGroups = getPluginGroups();
const mode: Ref = ref("add"); const mode: Ref = ref("add");
const callback: Ref = ref(); const callback: Ref = ref();
const currentStep: Ref = ref({ title: undefined, input: {} }); const currentStep: Ref = ref({ title: undefined, input: {} });
@ -199,9 +207,8 @@ export default {
const changeCurrentPlugin = (step: any) => { const changeCurrentPlugin = (step: any) => {
const stepType = step.type; const stepType = step.type;
const pluginDefine = stepPluginDefineList.value.find((p: any) => { const pluginDefine = pluginGroups.get(stepType);
return p.name === stepType; debugger;
});
if (pluginDefine) { if (pluginDefine) {
step.type = stepType; step.type = stepType;
step._isAdd = false; step._isAdd = false;
@ -271,7 +278,7 @@ export default {
return { return {
stepTypeSelected, stepTypeSelected,
stepTypeSave, stepTypeSave,
stepPluginDefineList, pluginGroups,
stepFormRef, stepFormRef,
mode, mode,
stepAdd, stepAdd,

View File

@ -1,5 +1,12 @@
<template> <template>
<a-drawer v-model:open="taskDrawerVisible" placement="right" :closable="true" width="600px" class="pi-task-form" @after-open-change="taskDrawerOnAfterVisibleChange"> <a-drawer
v-model:open="taskDrawerVisible"
placement="right"
:closable="true"
width="700px"
class="pi-task-form"
@after-open-change="taskDrawerOnAfterVisibleChange"
>
<template #title> <template #title>
编辑任务 编辑任务
<a-button v-if="editMode" @click="taskDelete()"> <a-button v-if="editMode" @click="taskDelete()">
@ -24,7 +31,13 @@
/> />
<div class="steps"> <div class="steps">
<a-form-item :value="currentTask.steps" name="steps" label="" :wrapper-col="{ span: 24 }" :rules="[{ required: true, message: '至少需要一个步骤,或者你可以点击标题右边删除按钮删除此任务' }]"> <a-form-item
:value="currentTask.steps"
name="steps"
label=""
:wrapper-col="{ span: 24 }"
:rules="[{ required: true, message: '至少需要一个步骤,或者你可以点击标题右边删除按钮删除此任务' }]"
>
<a-descriptions title="任务步骤" size="small"> <a-descriptions title="任务步骤" size="small">
<template #extra> <template #extra>
<a-button type="primary" @click="stepAdd(currentTask)"></a-button> <a-button type="primary" @click="stepAdd(currentTask)"></a-button>
@ -70,7 +83,7 @@ import { provide, Ref, ref } from "vue";
import _ from "lodash-es"; import _ from "lodash-es";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import PiStepForm from "../step-form/index.vue"; import PiStepForm from "../step-form/index.vue";
import { message, Modal } from "ant-design-vue"; import { Modal } from "ant-design-vue";
import { CopyOutlined } from "@ant-design/icons-vue"; import { CopyOutlined } from "@ant-design/icons-vue";
export default { export default {
@ -88,7 +101,7 @@ export default {
const stepFormRef: Ref<any> = ref(null); const stepFormRef: Ref<any> = ref(null);
const currentStepIndex = ref(0); const currentStepIndex = ref(0);
provide("currentStepIndex", currentStepIndex); provide("currentStepIndex", currentStepIndex);
const stepAdd = (task: any, stepDef: any) => { const stepAdd = (task: any, stepDef?: any) => {
currentStepIndex.value = task.steps.length; currentStepIndex.value = task.steps.length;
stepFormRef.value.stepAdd((type: any, value: any) => { stepFormRef.value.stepAdd((type: any, value: any) => {
if (type === "save") { if (type === "save") {
@ -179,7 +192,7 @@ export default {
const taskAdd = (emit: any, taskMerge: any) => { const taskAdd = (emit: any, taskMerge: any) => {
mode.value = "add"; mode.value = "add";
const blankTask = { id: nanoid(), title: "新任务", steps: [], status: null }; const blankTask: any = { id: nanoid(), title: "新任务", steps: [], status: null };
const task: any = _.merge(blankTask, taskMerge); const task: any = _.merge(blankTask, taskMerge);
taskOpen(task, emit); taskOpen(task, emit);
}; };

View File

@ -223,7 +223,7 @@ import _ from "lodash-es";
import { message, Modal, notification } from "ant-design-vue"; import { message, Modal, notification } from "ant-design-vue";
import { pluginManager } from "/@/views/certd/pipeline/pipeline/plugin"; import { pluginManager } from "/@/views/certd/pipeline/pipeline/plugin";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { PipelineDetail, PipelineOptions, RunHistory } from "./type"; import { PipelineDetail, PipelineOptions, PluginGroups, RunHistory } from "./type";
import type { Runnable } from "@certd/pipeline"; import type { Runnable } from "@certd/pipeline";
import PiHistoryTimelineItem from "/@/views/certd/pipeline/pipeline/component/history-timeline-item.vue"; import PiHistoryTimelineItem from "/@/views/certd/pipeline/pipeline/component/history-timeline-item.vue";
export default defineComponent({ export default defineComponent({
@ -348,17 +348,17 @@ export default defineComponent({
} }
); );
const plugins: Ref<any> = ref([]); const pluginGroupsRef: Ref<PluginGroups> = ref();
const fetchPlugins = async () => { const fetchPlugins = async () => {
const list = await props.options.getPlugins(); pluginGroupsRef.value = await props.options.getPluginGroups();
plugins.value = list;
pluginManager.init(list);
}; };
fetchPlugins(); fetchPlugins();
provide("pipeline", pipeline); provide("pipeline", pipeline);
provide("plugins", plugins); provide("getPluginGroups", () => {
return pluginGroupsRef.value;
});
provide("currentHistory", currentHistory); provide("currentHistory", currentHistory);
function useTask() { function useTask() {

View File

@ -17,49 +17,7 @@ export class PluginManager {
this.map = map; this.map = map;
} }
get(name: string) {
return this.map[name];
}
getPreStepOutputOptions({ pipeline, currentStageIndex, currentStepIndex, currentTask }: any) {
const steps = this.collectionPreStepOutputs({
pipeline,
currentStageIndex,
currentStepIndex,
currentTask
});
const options: any[] = [];
for (const step of steps) {
const stepDefine = this.get(step.type);
for (const key in stepDefine?.output) {
options.push({
value: `step.${step.id}.${key}`,
label: `${stepDefine.output[key].title}【from${step.title}`,
type: step.type
});
}
}
return options;
}
collectionPreStepOutputs({ pipeline, currentStageIndex, currentStepIndex, currentTask }: any) {
const steps: any[] = [];
// 开始放step
for (let i = 0; i < currentStageIndex; i++) {
const stage = pipeline.stages[i];
for (const task of stage.tasks) {
for (const step of task.steps) {
steps.push(step);
}
}
}
//放当前任务下的step
for (let i = 0; i < currentStepIndex; i++) {
const step = currentTask.steps[i];
steps.push(step);
}
return steps;
}
} }
export const pluginManager = new PluginManager(); export const pluginManager = new PluginManager();

View File

@ -1,4 +1,5 @@
import type { Pipeline } from "@certd/pipeline"; import type { Pipeline } from "@certd/pipeline";
import { FormItemProps } from "@fast-crud/fast-crud";
export type PipelineDetail = { export type PipelineDetail = {
pipeline: Pipeline; pipeline: Pipeline;
}; };
@ -10,6 +11,110 @@ export type RunHistory = {
[id: string]: string[]; [id: string]: string[];
}; };
}; };
export type PluginGroup = {
key: string;
title: string;
desc?: string;
order: number;
plugins: any[];
};
export type PluginDefine = {
key: string;
title: string;
desc?: string;
input: {
[key: string]: FormItemProps;
};
output: {
[key: string]: any;
};
};
export class PluginGroups {
groups: { [key: string]: PluginGroup };
map: { [key: string]: PluginDefine };
constructor(groups: { [key: string]: PluginGroup }) {
this.groups = groups;
this.initGroup(groups);
this.initMap();
}
private initGroup(groups: { [p: string]: PluginGroup }) {
const all: PluginGroup = {
key: "all",
title: "全部",
order: 0,
plugins: []
};
for (const key in groups) {
all.plugins.push(...groups[key].plugins);
}
this.groups = {
all,
...groups
};
}
initMap() {
const map: { [key: string]: PluginDefine } = {};
for (const key in this.groups) {
const group = this.groups[key];
for (const plugin of group.plugins) {
map[plugin.name] = plugin;
}
}
this.map = map;
}
getGroups() {
return this.groups;
}
get(name: string) {
return this.map[name];
}
getPreStepOutputOptions({ pipeline, currentStageIndex, currentStepIndex, currentTask }: any) {
const steps = this.collectionPreStepOutputs({
pipeline,
currentStageIndex,
currentStepIndex,
currentTask
});
const options: any[] = [];
for (const step of steps) {
const stepDefine = this.get(step.type);
for (const key in stepDefine?.output) {
options.push({
value: `step.${step.id}.${key}`,
label: `${stepDefine.output[key].title}【from${step.title}`,
type: step.type
});
}
}
return options;
}
collectionPreStepOutputs({ pipeline, currentStageIndex, currentStepIndex, currentTask }: any) {
const steps: any[] = [];
// 开始放step
for (let i = 0; i < currentStageIndex; i++) {
const stage = pipeline.stages[i];
for (const task of stage.tasks) {
for (const step of task.steps) {
steps.push(step);
}
}
}
//放当前任务下的step
for (let i = 0; i < currentStepIndex; i++) {
const step = currentTask.steps[i];
steps.push(step);
}
return steps;
}
}
export type PipelineOptions = { export type PipelineOptions = {
doTrigger(options: { pipelineId: number }): Promise<void>; doTrigger(options: { pipelineId: number }): Promise<void>;
@ -17,5 +122,5 @@ export type PipelineOptions = {
getPipelineDetail(query: { pipelineId: number }): Promise<PipelineDetail>; getPipelineDetail(query: { pipelineId: number }): Promise<PipelineDetail>;
getHistoryList(query: { pipelineId: number }): Promise<RunHistory[]>; getHistoryList(query: { pipelineId: number }): Promise<RunHistory[]>;
getHistoryDetail(query: { historyId: number }): Promise<RunHistory>; getHistoryDetail(query: { historyId: number }): Promise<RunHistory>;
getPlugins(): Promise<Pipeline[]>; getPluginGroups(): Promise<PluginGroups>;
}; };

View File

@ -1,2 +1,3 @@
koa: koa:
port: 7001 port: 7001

View File

@ -14,6 +14,7 @@
"lint:fix": "mwts fix", "lint:fix": "mwts fix",
"ci": "npm run cov", "ci": "npm run cov",
"build": "mwtsc --cleanOutDir --skipLibCheck", "build": "mwtsc --cleanOutDir --skipLibCheck",
"build-on-docker": "node ./before-build.js && npm run build",
"up-mw-deps": "npx midway-version -u -w" "up-mw-deps": "npx midway-version -u -w"
}, },
"dependencies": { "dependencies": {

View File

@ -37,7 +37,9 @@ const development = {
}, },
}, },
}, },
cron: {}, cron: {
immediateTriggerOnce: false,
},
/** /**
* *
*/ */
@ -84,6 +86,8 @@ const development = {
resetAdminPasswd: false, resetAdminPasswd: false,
}, },
} as MidwayConfig; } as MidwayConfig;
mergeConfig(development, 'development');
mergeConfig(development, env); mergeConfig(development, env);
export default development; export default development;

View File

@ -8,6 +8,9 @@ const production = {
preview: { preview: {
enabled: false, enabled: false,
}, },
cron: {
immediateTriggerOnce: true,
},
} as MidwayConfig; } as MidwayConfig;
mergeConfig(production, 'production'); mergeConfig(production, 'production');

View File

@ -1,4 +1,4 @@
import { Autoload, Init, Inject, Scope, ScopeEnum } from '@midwayjs/core'; import { Autoload, Config, Init, Inject, Scope, ScopeEnum } from '@midwayjs/core';
import { PipelineService } from '../service/pipeline-service.js'; import { PipelineService } from '../service/pipeline-service.js';
import { logger } from '../../../utils/logger.js'; import { logger } from '../../../utils/logger.js';
@ -10,11 +10,13 @@ export class AutoRegisterCron {
// @Inject() // @Inject()
// echoPlugin: EchoPlugin; // echoPlugin: EchoPlugin;
@Config('cron.immediateTriggerOnce')
private immediateTriggerOnce = false;
@Init() @Init()
async init() { async init() {
logger.info('加载定时trigger开始'); logger.info('加载定时trigger开始');
await this.pipelineService.onStartup(); await this.pipelineService.onStartup(this.immediateTriggerOnce);
// logger.info(this.echoPlugin, this.echoPlugin.test); // logger.info(this.echoPlugin, this.echoPlugin.test);
// logger.info('加载定时trigger完成'); // logger.info('加载定时trigger完成');
// //

View File

@ -1,11 +1,4 @@
import { import { ALL, Controller, Inject, Post, Provide, Query } from '@midwayjs/core';
ALL,
Controller,
Inject,
Post,
Provide,
Query,
} from '@midwayjs/core';
import { BaseController } from '../../../basic/base-controller.js'; import { BaseController } from '../../../basic/base-controller.js';
import { PluginService } from '../service/plugin-service.js'; import { PluginService } from '../service/plugin-service.js';
import { Constants } from '../../../basic/constants.js'; import { Constants } from '../../../basic/constants.js';
@ -25,4 +18,11 @@ export class PluginController extends BaseController {
const list = this.service.getList(); const list = this.service.getList();
return this.ok(list); return this.ok(list);
} }
@Post('/groups', { summary: Constants.per.authOnly })
async groups(@Query(ALL) query) {
query.userId = this.ctx.user.id;
const group = this.service.getGroups();
return this.ok(group);
}
} }

View File

@ -1,4 +1,4 @@
import { Config, Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core'; import { Config, Inject, Provide, Scope, ScopeEnum, sleep } from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/typeorm'; import { InjectEntityModel } from '@midwayjs/typeorm';
import { In, Repository } from 'typeorm'; import { In, Repository } from 'typeorm';
import { BaseService } from '../../../basic/base-service.js'; import { BaseService } from '../../../basic/base-service.js';
@ -80,7 +80,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
/** /**
* *
*/ */
async onStartup() { async onStartup(immediateTriggerOnce: boolean) {
logger.info('加载定时trigger开始'); logger.info('加载定时trigger开始');
const idEntityList = await this.repository.find({ const idEntityList = await this.repository.find({
select: { select: {
@ -114,7 +114,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
for (const entity of list) { for (const entity of list) {
const pipeline = JSON.parse(entity.content ?? '{}'); const pipeline = JSON.parse(entity.content ?? '{}');
try { try {
this.registerTriggers(pipeline); await this.registerTriggers(pipeline, immediateTriggerOnce);
} catch (e) { } catch (e) {
logger.error('加载定时trigger失败', e); logger.error('加载定时trigger失败', e);
} }
@ -123,13 +123,18 @@ export class PipelineService extends BaseService<PipelineEntity> {
logger.info('定时器数量:', this.cron.getListSize()); logger.info('定时器数量:', this.cron.getListSize());
} }
registerTriggers(pipeline?: Pipeline) { async registerTriggers(pipeline?: Pipeline, immediateTriggerOnce = false) {
if (pipeline?.triggers == null) { if (pipeline?.triggers == null) {
return; return;
} }
for (const trigger of pipeline.triggers) { for (const trigger of pipeline.triggers) {
this.registerCron(pipeline.id, trigger); this.registerCron(pipeline.id, trigger);
} }
if (immediateTriggerOnce) {
await this.trigger(pipeline.id);
await sleep(1000);
}
} }
async trigger(id) { async trigger(id) {

View File

@ -1,5 +1,5 @@
import { Provide, Scope, ScopeEnum } from '@midwayjs/core'; import { Provide, Scope, ScopeEnum } from '@midwayjs/core';
import { pluginRegistry } from '@certd/pipeline'; import { pluginGroups, pluginRegistry } from '@certd/pipeline';
@Provide() @Provide()
@Scope(ScopeEnum.Singleton) @Scope(ScopeEnum.Singleton)
export class PluginService { export class PluginService {
@ -12,4 +12,8 @@ export class PluginService {
} }
return list; return list;
} }
getGroups() {
return pluginGroups;
}
} }

View File

@ -1,4 +1,4 @@
import { AbstractTaskPlugin, IAccessService, ILogger, IsTaskPlugin, RunStrategy, TaskInput, utils } from '@certd/pipeline'; import { AbstractTaskPlugin, IAccessService, ILogger, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, utils } from '@certd/pipeline';
// @ts-ignore // @ts-ignore
import { ROAClient } from '@alicloud/pop-core'; import { ROAClient } from '@alicloud/pop-core';
import { AliyunAccess } from '../../access/index.js'; import { AliyunAccess } from '../../access/index.js';
@ -9,6 +9,7 @@ import { CertInfo } from '@certd/plugin-cert';
@IsTaskPlugin({ @IsTaskPlugin({
name: 'DeployCertToAliyunAckIngress', name: 'DeployCertToAliyunAckIngress',
title: '部署到阿里云AckIngress', title: '部署到阿里云AckIngress',
group: pluginGroups.aliyun.key,
input: {}, input: {},
output: {}, output: {},
default: { default: {

View File

@ -2,7 +2,7 @@ import {
AbstractTaskPlugin, AbstractTaskPlugin,
IAccessService, IAccessService,
ILogger, ILogger,
IsTaskPlugin, IsTaskPlugin, pluginGroups,
RunStrategy, RunStrategy,
TaskInput, TaskInput,
} from '@certd/pipeline'; } from '@certd/pipeline';
@ -14,6 +14,7 @@ import { AliyunAccess } from '../../access/index.js';
@IsTaskPlugin({ @IsTaskPlugin({
name: 'DeployCertToAliyunCDN', name: 'DeployCertToAliyunCDN',
title: '部署证书至阿里云CDN', title: '部署证书至阿里云CDN',
group: pluginGroups.aliyun.key,
desc: '依赖证书申请前置任务自动部署域名证书至阿里云CDN', desc: '依赖证书申请前置任务自动部署域名证书至阿里云CDN',
default: { default: {
strategy: { strategy: {

View File

@ -1,11 +1,4 @@
import { import { AbstractTaskPlugin, IAccessService, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, TaskOutput } from '@certd/pipeline';
AbstractTaskPlugin,
IAccessService,
IsTaskPlugin,
RunStrategy,
TaskInput,
TaskOutput,
} from '@certd/pipeline';
import Core from '@alicloud/pop-core'; import Core from '@alicloud/pop-core';
import { AliyunAccess } from '../../access/index.js'; import { AliyunAccess } from '../../access/index.js';
import { appendTimeSuffix, checkRet, ZoneOptions } from '../../utils/index.js'; import { appendTimeSuffix, checkRet, ZoneOptions } from '../../utils/index.js';
@ -14,6 +7,7 @@ import { Logger } from 'log4js';
@IsTaskPlugin({ @IsTaskPlugin({
name: 'uploadCertToAliyun', name: 'uploadCertToAliyun',
title: '上传证书到阿里云', title: '上传证书到阿里云',
group: pluginGroups.aliyun.key,
desc: '', desc: '',
default: { default: {
strategy: { strategy: {
@ -76,9 +70,7 @@ export class UploadCertToAliyun extends AbstractTaskPlugin {
async execute(): Promise<void> { async execute(): Promise<void> {
console.log('开始部署证书到阿里云cdn'); console.log('开始部署证书到阿里云cdn');
const access = (await this.accessService.getById( const access = (await this.accessService.getById(this.accessId)) as AliyunAccess;
this.accessId
)) as AliyunAccess;
const client = this.getClient(access); const client = this.getClient(access);
const certName = appendTimeSuffix(this.name); const certName = appendTimeSuffix(this.name);
const params = { const params = {
@ -92,11 +84,7 @@ export class UploadCertToAliyun extends AbstractTaskPlugin {
method: 'POST', method: 'POST',
}; };
const ret = (await client.request( const ret = (await client.request('CreateUserCertificate', params, requestOption)) as any;
'CreateUserCertificate',
params,
requestOption
)) as any;
checkRet(ret); checkRet(ret);
this.logger.info('证书上传成功aliyunCertId=', ret.CertId); this.logger.info('证书上传成功aliyunCertId=', ret.CertId);

View File

@ -1,16 +1,10 @@
import { import { AbstractTaskPlugin, IAccessService, ILogger, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
AbstractTaskPlugin,
IAccessService,
ILogger,
IsTaskPlugin,
RunStrategy,
TaskInput,
} from '@certd/pipeline';
import { CertInfo, CertReader } from '@certd/plugin-cert'; import { CertInfo, CertReader } from '@certd/plugin-cert';
@IsTaskPlugin({ @IsTaskPlugin({
name: 'CloudflareDeployToCDN', name: 'CloudflareDeployToCDN',
title: '部署证书到CF CDN', title: '部署证书到CF CDN',
group: pluginGroups.other.key,
desc: '暂未实现,不可用', desc: '暂未实现,不可用',
default: { default: {
strategy: { strategy: {

View File

@ -1,10 +1,11 @@
import { AbstractTaskPlugin, IAccessService, ILogger, IsTaskPlugin, RunStrategy, TaskInput } from '@certd/pipeline'; import { AbstractTaskPlugin, IAccessService, ILogger, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
import { CertInfo, CertReader } from '@certd/plugin-cert'; import { CertInfo, CertReader } from '@certd/plugin-cert';
import { K8sClient } from '@certd/lib-k8s'; import { K8sClient } from '@certd/lib-k8s';
@IsTaskPlugin({ @IsTaskPlugin({
name: 'demoTest', name: 'demoTest',
title: 'Demo测试插件', title: 'Demo测试插件',
group: pluginGroups.other.key,
default: { default: {
strategy: { strategy: {
runStrategy: RunStrategy.SkipWhenSucceed, runStrategy: RunStrategy.SkipWhenSucceed,

View File

@ -1,16 +1,10 @@
import { import { AbstractTaskPlugin, IAccessService, ILogger, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
AbstractTaskPlugin,
IAccessService,
ILogger,
IsTaskPlugin,
RunStrategy,
TaskInput,
} from '@certd/pipeline';
import { SshClient } from '../../lib/ssh.js'; import { SshClient } from '../../lib/ssh.js';
@IsTaskPlugin({ @IsTaskPlugin({
name: 'hostShellExecute', name: 'hostShellExecute',
title: '执行远程主机脚本命令', title: '执行远程主机脚本命令',
group: pluginGroups.host.key,
input: {}, input: {},
default: { default: {
strategy: { strategy: {

View File

@ -1,4 +1,4 @@
import { AbstractTaskPlugin, IAccessService, ILogger, IsTaskPlugin, RunStrategy, TaskInput, TaskOutput } from '@certd/pipeline'; import { AbstractTaskPlugin, IAccessService, ILogger, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, TaskOutput } from '@certd/pipeline';
import { SshClient } from '../../lib/ssh.js'; import { SshClient } from '../../lib/ssh.js';
import { CertInfo, CertReader } from '@certd/plugin-cert'; import { CertInfo, CertReader } from '@certd/plugin-cert';
import * as fs from 'fs'; import * as fs from 'fs';
@ -7,6 +7,7 @@ import { SshAccess } from '../../access/index.js';
@IsTaskPlugin({ @IsTaskPlugin({
name: 'uploadCertToHost', name: 'uploadCertToHost',
title: '上传证书到主机', title: '上传证书到主机',
group: pluginGroups.host.key,
desc: '也支持复制证书到本机', desc: '也支持复制证书到本机',
default: { default: {
strategy: { strategy: {

View File

@ -1,11 +1,4 @@
import { import { AbstractTaskPlugin, IAccessService, ILogger, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
AbstractTaskPlugin,
IAccessService,
ILogger,
IsTaskPlugin,
RunStrategy,
TaskInput,
} from '@certd/pipeline';
import tencentcloud from 'tencentcloud-sdk-nodejs'; import tencentcloud from 'tencentcloud-sdk-nodejs';
import { TencentAccess } from '../../access/index.js'; import { TencentAccess } from '../../access/index.js';
import { CertInfo } from '@certd/plugin-cert'; import { CertInfo } from '@certd/plugin-cert';
@ -13,6 +6,7 @@ import { CertInfo } from '@certd/plugin-cert';
@IsTaskPlugin({ @IsTaskPlugin({
name: 'DeployCertToTencentCDN', name: 'DeployCertToTencentCDN',
title: '部署到腾讯云CDN', title: '部署到腾讯云CDN',
group: pluginGroups.tencent.key,
default: { default: {
strategy: { strategy: {
runStrategy: RunStrategy.SkipWhenSucceed, runStrategy: RunStrategy.SkipWhenSucceed,
@ -75,9 +69,7 @@ export class DeployToCdnPlugin extends AbstractTaskPlugin {
} }
async execute(): Promise<void> { async execute(): Promise<void> {
const accessProvider: TencentAccess = (await this.accessService.getById( const accessProvider: TencentAccess = (await this.accessService.getById(this.accessId)) as TencentAccess;
this.accessId
)) as TencentAccess;
const client = this.getClient(accessProvider); const client = this.getClient(accessProvider);
const params = this.buildParams(); const params = this.buildParams();
await this.doRequest(client, params); await this.doRequest(client, params);

View File

@ -1,12 +1,4 @@
import { import { AbstractTaskPlugin, IAccessService, ILogger, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, utils } from '@certd/pipeline';
AbstractTaskPlugin,
IAccessService,
ILogger,
IsTaskPlugin,
RunStrategy,
TaskInput,
utils,
} from '@certd/pipeline';
import tencentcloud from 'tencentcloud-sdk-nodejs'; import tencentcloud from 'tencentcloud-sdk-nodejs';
import { TencentAccess } from '../../access/index.js'; import { TencentAccess } from '../../access/index.js';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
@ -14,6 +6,7 @@ import dayjs from 'dayjs';
@IsTaskPlugin({ @IsTaskPlugin({
name: 'DeployCertToTencentCLB', name: 'DeployCertToTencentCLB',
title: '部署到腾讯云CLB', title: '部署到腾讯云CLB',
group: pluginGroups.tencent.key,
desc: '暂时只支持单向认证证书,暂时只支持通用负载均衡', desc: '暂时只支持单向认证证书,暂时只支持通用负载均衡',
default: { default: {
strategy: { strategy: {
@ -40,8 +33,7 @@ export class DeployToClbPlugin extends AbstractTaskPlugin {
@TaskInput({ @TaskInput({
title: '负载均衡ID', title: '负载均衡ID',
helper: helper: '如果没有配置则根据域名匹配负载均衡下的监听器根据域名匹配时暂时只支持前100个',
'如果没有配置则根据域名匹配负载均衡下的监听器根据域名匹配时暂时只支持前100个',
required: true, required: true,
}) })
loadBalancerId!: string; loadBalancerId!: string;
@ -88,9 +80,7 @@ export class DeployToClbPlugin extends AbstractTaskPlugin {
this.logger = this.ctx.logger; this.logger = this.ctx.logger;
} }
async execute(): Promise<void> { async execute(): Promise<void> {
const accessProvider = (await this.accessService.getById( const accessProvider = (await this.accessService.getById(this.accessId)) as TencentAccess;
this.accessId
)) as TencentAccess;
const client = this.getClient(accessProvider, this.region); const client = this.getClient(accessProvider, this.region);
const lastCertId = await this.getCertIdFromProps(client); const lastCertId = await this.getCertIdFromProps(client);
@ -103,10 +93,7 @@ export class DeployToClbPlugin extends AbstractTaskPlugin {
try { try {
await utils.sleep(2000); await utils.sleep(2000);
let newCertId = await this.getCertIdFromProps(client); let newCertId = await this.getCertIdFromProps(client);
if ( if ((lastCertId && newCertId === lastCertId) || (!lastCertId && !newCertId)) {
(lastCertId && newCertId === lastCertId) ||
(!lastCertId && !newCertId)
) {
await utils.sleep(2000); await utils.sleep(2000);
newCertId = await this.getCertIdFromProps(client); newCertId = await this.getCertIdFromProps(client);
} }
@ -121,11 +108,7 @@ export class DeployToClbPlugin extends AbstractTaskPlugin {
} }
async getCertIdFromProps(client: any) { async getCertIdFromProps(client: any) {
const listenerRet = await this.getListenerList( const listenerRet = await this.getListenerList(client, this.loadBalancerId, [this.listenerId]);
client,
this.loadBalancerId,
[this.listenerId]
);
return this.getCertIdFromListener(listenerRet[0], this.domain); return this.getCertIdFromListener(listenerRet[0], this.domain);
} }
@ -152,14 +135,7 @@ export class DeployToClbPlugin extends AbstractTaskPlugin {
const params = this.buildProps(); const params = this.buildProps();
const ret = await client.ModifyListener(params); const ret = await client.ModifyListener(params);
this.checkRet(ret); this.checkRet(ret);
this.logger.info( this.logger.info('设置腾讯云CLB证书成功:', ret.RequestId, '->loadBalancerId:', this.loadBalancerId, 'listenerId', this.listenerId);
'设置腾讯云CLB证书成功:',
ret.RequestId,
'->loadBalancerId:',
this.loadBalancerId,
'listenerId',
this.listenerId
);
return ret; return ret;
} }

View File

@ -1,11 +1,4 @@
import { import { AbstractTaskPlugin, IAccessService, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, utils } from '@certd/pipeline';
AbstractTaskPlugin,
IAccessService,
IsTaskPlugin,
RunStrategy,
TaskInput,
utils,
} from '@certd/pipeline';
import tencentcloud from 'tencentcloud-sdk-nodejs'; import tencentcloud from 'tencentcloud-sdk-nodejs';
import { K8sClient } from '@certd/lib-k8s'; import { K8sClient } from '@certd/lib-k8s';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
@ -14,6 +7,7 @@ import { Logger } from 'log4js';
@IsTaskPlugin({ @IsTaskPlugin({
name: 'DeployCertToTencentTKEIngress', name: 'DeployCertToTencentTKEIngress',
title: '部署到腾讯云TKE-ingress', title: '部署到腾讯云TKE-ingress',
group: pluginGroups.tencent.key,
desc: '需要【上传到腾讯云】作为前置任务', desc: '需要【上传到腾讯云】作为前置任务',
default: { default: {
strategy: { strategy: {
@ -104,10 +98,7 @@ export class DeployCertToTencentTKEIngressPlugin extends AbstractTaskPlugin {
async execute(): Promise<void> { async execute(): Promise<void> {
const accessProvider = await this.accessService.getById(this.accessId); const accessProvider = await this.accessService.getById(this.accessId);
const tkeClient = this.getTkeClient(accessProvider, this.region); const tkeClient = this.getTkeClient(accessProvider, this.region);
const kubeConfigStr = await this.getTkeKubeConfig( const kubeConfigStr = await this.getTkeKubeConfig(tkeClient, this.clusterId);
tkeClient,
this.clusterId
);
this.logger.info('kubeconfig已成功获取'); this.logger.info('kubeconfig已成功获取');
const k8sClient = new K8sClient(kubeConfigStr); const k8sClient = new K8sClient(kubeConfigStr);
@ -154,9 +145,7 @@ export class DeployCertToTencentTKEIngressPlugin extends AbstractTaskPlugin {
}; };
const ret = await client.DescribeClusterKubeconfig(params); const ret = await client.DescribeClusterKubeconfig(params);
this.checkRet(ret); this.checkRet(ret);
this.logger.info( this.logger.info('注意:后续操作需要在【集群->基本信息】中开启外网或内网访问,https://console.cloud.tencent.com/tke2/cluster');
'注意:后续操作需要在【集群->基本信息】中开启外网或内网访问,https://console.cloud.tencent.com/tke2/cluster'
);
return ret.Kubeconfig; return ret.Kubeconfig;
} }

View File

@ -1,4 +1,4 @@
import { AbstractTaskPlugin, IAccessService, ILogger, IsTaskPlugin, RunStrategy, TaskInput, TaskOutput } from '@certd/pipeline'; import { AbstractTaskPlugin, IAccessService, ILogger, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput, TaskOutput } from '@certd/pipeline';
import tencentcloud from 'tencentcloud-sdk-nodejs'; import tencentcloud from 'tencentcloud-sdk-nodejs';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
@ -6,6 +6,7 @@ import dayjs from 'dayjs';
name: 'UploadCertToTencent', name: 'UploadCertToTencent',
title: '上传证书到腾讯云', title: '上传证书到腾讯云',
desc: '上传成功后输出tencentCertId', desc: '上传成功后输出tencentCertId',
group: pluginGroups.tencent.key,
default: { default: {
strategy: { strategy: {
runStrategy: RunStrategy.SkipWhenSucceed, runStrategy: RunStrategy.SkipWhenSucceed,