refactor: decorator

pull/9/head^2
xiaojunnuo 2022-12-27 12:32:09 +08:00
parent c23f6172b5
commit 717d203622
30 changed files with 639 additions and 309 deletions

View File

@ -12,22 +12,29 @@
},
"dependencies": {
"@certd/acme-client": "^0.3.0",
"axios": "^0.21.1",
"dayjs": "^1.11.6",
"lodash": "^4.17.21",
"node-forge": "^0.10.0",
"log4js": "^6.3.0",
"axios": "^0.21.1",
"node-forge": "^0.10.0",
"qs": "^6.9.4"
},
"devDependencies": {
"@types/lodash": "^4.14.186",
"vue-tsc": "^0.38.9",
"@alicloud/cs20151215": "^3.0.3",
"@alicloud/openapi-client": "^0.4.0",
"@alicloud/pop-core": "^1.7.10",
"@midwayjs/bootstrap": "^3.9.1",
"@midwayjs/cache": "^3.9.0",
"@midwayjs/cli": "^1.3.21",
"@midwayjs/core": "^3.0.0",
"@midwayjs/decorator": "^3.0.0",
"@midwayjs/koa": "^3.9.0",
"@midwayjs/logger": "^2.17.0",
"@midwayjs/typeorm": "^3.9.0",
"@midwayjs/validate": "^3.9.0",
"@types/chai": "^4.3.3",
"@types/lodash": "^4.14.186",
"@types/lodash-es": "^4.17.6",
"@types/mocha": "^10.0.0",
"@types/node-forge": "^1.3.0",
"@typescript-eslint/eslint-plugin": "^5.38.1",
@ -41,6 +48,7 @@
"mocha": "^10.1.0",
"ts-node": "^10.9.1",
"typescript": "^4.8.4",
"vite": "^3.1.0"
"vite": "^3.1.0",
"vue-tsc": "^0.38.9"
}
}

View File

@ -1,4 +0,0 @@
import { AbstractRegistrable } from "../registry";
import { AccessDefine } from "./api";
export abstract class AbstractAccess extends AbstractRegistrable<AccessDefine> {}

View File

@ -3,22 +3,15 @@ import { accessRegistry } from "./registry";
import { FormItemProps } from "../d.ts";
import { AbstractAccess } from "./abstract-access";
export type AccessInput = FormItemProps & {
export type AccessInputDefine = FormItemProps & {
title: string;
required?: boolean;
};
export type AccessDefine = Registrable & {
input: {
[key: string]: AccessInput;
inputs?: {
[key: string]: AccessInputDefine;
};
};
export function IsAccess(define: AccessDefine) {
return function (target: any) {
target.prototype.define = define;
accessRegistry.install(target);
};
}
export interface IAccessService {
getById(id: any): Promise<AbstractAccess>;
}

View File

@ -0,0 +1,71 @@
// src/decorator/memoryCache.decorator.ts
import {
attachClassMetadata,
attachPropertyDataToClass,
getClassMetadata,
listModule,
listPropertyDataFromClass,
saveClassMetadata,
saveModule,
} from "@midwayjs/decorator";
import { AccessDefine, AccessInputDefine } from "./api";
import _ from "lodash-es";
import { accessRegistry } from "./registry";
// 提供一个唯一 key
export const ACCESS_CLASS_KEY = "decorator:access";
export function IsAccess(define: AccessDefine): ClassDecorator {
console.log("is access define:", define);
return (target: any) => {
console.log("is access load:", target);
// 将装饰的类,绑定到该装饰器,用于后续能获取到 class
saveModule(ACCESS_CLASS_KEY, target);
// 保存一些元数据信息,任意你希望存的东西
saveClassMetadata(
ACCESS_CLASS_KEY,
{
define,
},
target
);
};
}
export const ACCESS_INPUT_KEY = "decorator:access:input";
export function IsAccessInput(input?: AccessInputDefine): PropertyDecorator {
return (target, propertyKey) => {
attachPropertyDataToClass(ACCESS_INPUT_KEY, { input }, target, propertyKey, propertyKey as string);
attachClassMetadata(
ACCESS_CLASS_KEY,
{
inputs: {
[propertyKey]: input,
},
},
target
);
};
}
export function registerAccess() {
const modules = listModule(ACCESS_CLASS_KEY);
for (const mod of modules) {
console.log("mod", mod);
const define = getClassMetadata(ACCESS_CLASS_KEY, mod);
console.log("define", define);
const inputs = listPropertyDataFromClass(ACCESS_INPUT_KEY, mod);
console.log("inputs", inputs);
for (const input of inputs) {
define.inputs = {};
_.merge(define.inputs, input.inputs);
}
accessRegistry.register(define.name, {
define,
target: mod,
});
}
}

View File

@ -1,3 +1,3 @@
export * from "./api";
export * from "./abstract-access";
export * from "./registry";
export * from "./decorator";

View File

@ -1,5 +1,4 @@
import { Registry } from "../registry";
import { AbstractAccess } from "./abstract-access";
// @ts-ignore
export const accessRegistry = new Registry<typeof AbstractAccess>();
export const accessRegistry = new Registry();

View File

@ -0,0 +1,6 @@
import { AxiosInstance } from "axios";
import { IContext } from "../core";
export type HttpClient = AxiosInstance;
export type UserContext = IContext;
export type PipelineContext = IContext;

View File

@ -1,13 +1,14 @@
import { ConcurrencyStrategy, Pipeline, ResultType, Runnable, RunStrategy, Stage, Step, Task } from "../d.ts";
import _ from "lodash";
import { RunHistory } from "./run-history";
import { pluginRegistry, TaskPlugin } from "../plugin";
import { pluginRegistry, ITaskPlugin, PluginDefine } from "../plugin";
import { ContextFactory, IContext } from "./context";
import { IStorage } from "./storage";
import { logger } from "../utils/util.log";
import { Logger } from "log4js";
import { request } from "../utils/util.request";
import { IAccessService } from "../access";
import { Registrable, RegistryItem } from "../registry";
export class Executor {
userId: any;
pipeline: Pipeline;
@ -134,40 +135,38 @@ export class Executor {
private async runStep(step: Step) {
//执行任务
const taskPlugin: TaskPlugin = await this.getPlugin(step.type, this.runtime.loggers[step.id]);
const plugin: RegistryItem = pluginRegistry.get(step.type);
const context = {
logger: this.runtime.loggers[step.id],
accessService: this.accessService,
pipelineContext: this.pipelineContext,
userContext: this.contextFactory.getContext("user", this.userId),
http: request,
};
// @ts-ignore
delete taskPlugin.define;
const define = taskPlugin.getDefine();
const instance = new plugin.target();
// @ts-ignore
const define: PluginDefine = plugin.define;
//从outputContext读取输入参数
_.forEach(define.input, (item, key) => {
_.forEach(define.inputs, (item, key) => {
if (item.component?.name === "pi-output-selector") {
const contextKey = step.input[key];
if (contextKey != null) {
step.input[key] = this.runtime.context[contextKey];
}
} else {
instance[key] = step.input[key];
}
});
const res = await taskPlugin.execute(step.input);
const res = await instance.execute();
//输出到output context
_.forEach(define.output, (item, key) => {
_.forEach(define.outputs, (item, key) => {
const contextKey = `step.${step.id}.${key}`;
this.runtime.context[contextKey] = res[key];
});
}
private async getPlugin(type: string, logger: Logger): Promise<TaskPlugin> {
const pluginClass = pluginRegistry.get(type);
// @ts-ignore
const plugin = new pluginClass();
await plugin.doInit({
accessService: this.accessService,
pipelineContext: this.pipelineContext,
userContext: this.contextFactory.getContext("user", this.userId),
logger,
http: request,
});
return plugin;
}
}

View File

@ -1,6 +1,4 @@
import { Registrable } from "../registry";
import { dnsProviderRegistry } from "./registry";
import { AbstractDnsProvider } from "./abstract-dns-provider";
export type DnsProviderDefine = Registrable & {
accessType: string;
@ -16,19 +14,6 @@ export type RemoveRecordOptions = CreateRecordOptions & {
};
export interface IDnsProvider {
getDefine(): DnsProviderDefine;
createRecord(options: CreateRecordOptions): Promise<any>;
removeRecord(options: RemoveRecordOptions): Promise<any>;
}
export function IsDnsProvider(define: (() => DnsProviderDefine) | DnsProviderDefine) {
return function (target: typeof AbstractDnsProvider) {
if (define instanceof Function) {
target.prototype.define = define();
} else {
target.prototype.define = define;
}
dnsProviderRegistry.install(target);
};
}

View File

@ -0,0 +1,55 @@
// src/decorator/memoryCache.decorator.ts
import {
attachClassMetadata,
attachPropertyDataToClass,
createCustomPropertyDecorator,
getClassMetadata,
listModule,
listPropertyDataFromClass,
Provide,
saveClassMetadata,
saveModule,
Scope,
ScopeEnum,
} from "@midwayjs/decorator";
import _ from "lodash-es";
import { dnsProviderRegistry } from "./registry";
import { DnsProviderDefine } from "./api";
// 提供一个唯一 key
export const DNS_PROVIDER_CLASS_KEY = "decorator:dnsProvider";
export function IsDnsProvider(define: DnsProviderDefine): ClassDecorator {
console.log("is task plugin define:", define);
return (target: any) => {
console.log("is task plugin load:", target);
// 将装饰的类,绑定到该装饰器,用于后续能获取到 class
saveModule(DNS_PROVIDER_CLASS_KEY, target);
// 保存一些元数据信息,任意你希望存的东西
saveClassMetadata(
DNS_PROVIDER_CLASS_KEY,
{
define,
},
target
);
// 指定 IoC 容器创建实例的作用域,这里注册为请求作用域,这样能取到 ctx
Scope(ScopeEnum.Prototype)(target);
// 调用一下 Provide 装饰器,这样用户的 class 可以省略写 @Provide() 装饰器了
Provide()(target);
};
}
export function registerDnsProviders() {
const modules = listModule(DNS_PROVIDER_CLASS_KEY);
for (const mod of modules) {
console.log("mod", mod);
const define = getClassMetadata(DNS_PROVIDER_CLASS_KEY, mod);
console.log("define", define);
dnsProviderRegistry.register(define.name, {
define,
target: mod,
});
}
}

View File

@ -1,3 +1,3 @@
export * from "./api";
export * from "./registry";
export * from "./abstract-dns-provider";
export * from "./decorator";

View File

@ -1,5 +1,4 @@
import { Registry } from "../registry";
import { AbstractDnsProvider } from "./abstract-dns-provider";
// @ts-ignore
export const dnsProviderRegistry = new Registry<typeof AbstractDnsProvider>();
export const dnsProviderRegistry = new Registry();

View File

@ -5,3 +5,6 @@ export * from "./registry";
export * from "./dns-provider";
export * from "./plugin";
export * from "./utils";
export * from "./decorator";
export * from "./midway";
export * from "./context";

View File

@ -0,0 +1,60 @@
import { Config, Configuration, getClassMetadata, Inject, listModule, listPropertyDataFromClass, Logger } from "@midwayjs/decorator";
import _ from "lodash-es";
// @ts-ignore
import { ILogger } from "@midwayjs/logger";
import { IMidwayContainer, MidwayDecoratorService } from "@midwayjs/core";
import { pluginRegistry } from "../plugin";
import { registerPlugins } from "../plugin/decorator";
import { registerAccess } from "../access/decorator";
import { registerDnsProviders } from "../dns-provider";
// ... (see below) ...
@Configuration({
namespace: "pipeline",
//importConfigs: [join(__dirname, './config')],
})
export class PipelineConfiguration {
@Config()
// @ts-ignore
config;
@Logger()
// @ts-ignore
logger: ILogger;
@Inject()
// @ts-ignore
decoratorService: MidwayDecoratorService;
async onReady(container: IMidwayContainer) {
this.logger.info("pipeline install");
registerPlugins();
registerAccess();
registerDnsProviders();
//this.implPropertyDecorator(container);
this.logger.info("pipeline installed");
}
// implPropertyDecorator(container: IMidwayContainer) {
// this.logger.info("初始化 property decorator");
// // 实现装饰器
// this.decoratorService.registerPropertyHandler(CLASS_INPUTS_KEY, (propertyName, meta) => {
// return undefined;
// });
//
// const autowireWhiteList: any = {
// logger: true,
// };
// this.decoratorService.registerPropertyHandler(CLASS_AUTOWIRE_KEY, (propertyName, meta) => {
// // eslint-disable-next-line no-debugger
// debugger;
// const className = meta.name;
// if (autowireWhiteList[className]) {
// //在白名单里面,注入
// return container.get(className);
// }
// this.logger.warn(`autowire failed:${className} class is not in white list`);
// return undefined;
// });
// }
}

View File

@ -0,0 +1,5 @@
// src/index.ts
export { PipelineConfiguration } from "./configuration";
// export * from './controller/user';
// export * from './controller/api';
// export * from './service/user';

View File

@ -1,13 +1,6 @@
import { Registrable } from "../registry";
import { pluginRegistry } from "./registry";
import { FormItemProps } from "../d.ts";
export type TaskInput = {
[key: string]: any;
};
export type TaskOutput = {
[key: string]: any;
};
export enum ContextScope {
global,
@ -28,17 +21,19 @@ export type TaskOutputDefine = {
export type TaskInputDefine = FormItemProps;
export type PluginDefine = Registrable & {
input: {
default?: any;
inputs?: {
[key: string]: TaskInputDefine;
};
output: {
outputs?: {
[key: string]: TaskOutputDefine;
};
autowire?: any;
};
export interface TaskPlugin {
getDefine(): PluginDefine;
execute(input: TaskInput): Promise<TaskOutput>;
export interface ITaskPlugin {
execute(): Promise<void>;
}
export type OutputVO = {
@ -46,15 +41,3 @@ export type OutputVO = {
title: string;
value: any;
};
export function IsTask(define: (() => PluginDefine) | PluginDefine) {
return function (target: any) {
if (define instanceof Function) {
target.prototype.define = define();
} else {
target.prototype.define = define;
}
pluginRegistry.install(target);
};
}

View File

@ -0,0 +1,141 @@
// src/decorator/memoryCache.decorator.ts
import {
attachClassMetadata,
attachPropertyDataToClass,
createCustomPropertyDecorator,
getClassMetadata,
listModule,
listPropertyDataFromClass,
Provide,
saveClassMetadata,
saveModule,
Scope,
ScopeEnum,
} from "@midwayjs/decorator";
import _ from "lodash-es";
import { pluginRegistry } from "./registry";
import { PluginDefine, TaskInputDefine, TaskOutputDefine } from "./api";
// 提供一个唯一 key
export const PLUGIN_CLASS_KEY = "decorator:plugin";
export function IsTaskPlugin(define: PluginDefine): ClassDecorator {
console.log("is task plugin define:", define);
return (target: any) => {
console.log("is task plugin load:", target);
// 将装饰的类,绑定到该装饰器,用于后续能获取到 class
saveModule(PLUGIN_CLASS_KEY, target);
// 保存一些元数据信息,任意你希望存的东西
saveClassMetadata(
PLUGIN_CLASS_KEY,
{
define,
},
target
);
// 指定 IoC 容器创建实例的作用域,这里注册为请求作用域,这样能取到 ctx
Scope(ScopeEnum.Prototype)(target);
// 调用一下 Provide 装饰器,这样用户的 class 可以省略写 @Provide() 装饰器了
Provide()(target);
};
}
export const PLUGIN_INPUT_KEY = "decorator:plugin:input";
export function TaskInput(input?: TaskInputDefine): PropertyDecorator {
return (target, propertyKey) => {
attachPropertyDataToClass(PLUGIN_INPUT_KEY, { input }, target, propertyKey, propertyKey as string);
attachClassMetadata(
PLUGIN_CLASS_KEY,
{
inputs: {
[propertyKey]: input,
},
},
target
);
};
//
// return createCustomPropertyDecorator(CLASS_PROPS_KEY, {
// input,
// });
}
// 装饰器内部的唯一 id
export const PLUGIN_OUTPUT_KEY = "decorator:plugin:output";
export function TaskOutput(output?: TaskOutputDefine): PropertyDecorator {
return (target, propertyKey) => {
attachPropertyDataToClass(PLUGIN_OUTPUT_KEY, { output }, target, propertyKey, propertyKey as string);
attachClassMetadata(
PLUGIN_CLASS_KEY,
{
outputs: {
[propertyKey]: output,
},
},
target
);
};
//
// return createCustomPropertyDecorator(CLASS_PROPS_KEY, {
// input,
// });
}
export type AutowireProp = {
name?: string;
};
export const PLUGIN_AUTOWIRE_KEY = "decorator:plugin:autowire";
export function Autowire(props?: AutowireProp): PropertyDecorator {
return (target, propertyKey) => {
attachPropertyDataToClass(
PLUGIN_AUTOWIRE_KEY,
{
autowire: {
[propertyKey]: props,
},
},
target,
propertyKey,
propertyKey as string
);
};
}
export function registerPlugins() {
const modules = listModule(PLUGIN_CLASS_KEY);
for (const mod of modules) {
console.log("mod", mod);
const define: PluginDefine = getClassMetadata(PLUGIN_CLASS_KEY, mod);
console.log("define", define);
const inputs = listPropertyDataFromClass(PLUGIN_INPUT_KEY, mod);
console.log("inputs", inputs);
for (const input of inputs) {
define.inputs = {};
_.merge(define.inputs, input.inputs);
}
const outputs = listPropertyDataFromClass(PLUGIN_OUTPUT_KEY, mod);
console.log("outputs", outputs);
for (const output of outputs) {
define.outputs = {};
_.merge(define.outputs, output.outputs);
}
const autowire = listPropertyDataFromClass(PLUGIN_AUTOWIRE_KEY, mod);
console.log("autowire", autowire);
for (const auto of autowire) {
define.autowire = {};
_.merge(define.autowire, auto.autowire);
}
pluginRegistry.register(define.name, {
define,
target: mod,
});
}
}

View File

@ -1,3 +1,3 @@
export * from "./api";
export * from "./registry";
export * from "./abstract-plugin";
export * from "./decorator";

View File

@ -1,5 +1,4 @@
import { Registry } from "../registry";
import { AbstractPlugin } from "./abstract-plugin";
// @ts-ignore
export const pluginRegistry = new Registry<typeof AbstractPlugin>();
export const pluginRegistry = new Registry();

View File

@ -0,0 +1,26 @@
import { ILogger } from "@midwayjs/logger";
import { ITaskPlugin } from "../api";
import { Autowire, IsTaskPlugin, TaskInput } from "../decorator";
@IsTaskPlugin({
name: "EchoPlugin",
title: "测试插件",
desc: "test",
})
export class EchoPlugin implements ITaskPlugin {
@TaskInput({
title: "测试属性",
component: {
name: "text",
},
})
test?: string;
@Autowire()
// @ts-ignore
logger: ILogger;
async execute(): Promise<void> {
return Promise.resolve(undefined);
}
}

View File

@ -4,36 +4,16 @@ export type Registrable = {
desc?: string;
};
export abstract class AbstractRegistrable<T extends Registrable> {
// @ts-ignore
define: T;
getDefine(): T {
return this.define;
}
}
export class Registry<T extends typeof AbstractRegistrable> {
export type RegistryItem = {
define: Registrable;
target: any;
};
export class Registry {
storage: {
[key: string]: T;
[key: string]: RegistryItem;
} = {};
install(pluginClass: T) {
if (pluginClass == null) {
return;
}
// @ts-ignore
const plugin = new pluginClass();
delete plugin.define;
const define = plugin.getDefine();
let defineName = define.name;
if (defineName == null) {
defineName = plugin.name;
}
this.register(defineName, pluginClass);
}
register(key: string, value: T) {
register(key: string, value: RegistryItem) {
if (!key || value == null) {
return;
}
@ -68,12 +48,10 @@ export class Registry<T extends typeof AbstractRegistrable> {
}
getDefine(key: string) {
const PluginClass = this.storage[key];
if (!PluginClass) {
const item = this.storage[key];
if (!item) {
return;
}
// @ts-ignore
const plugin = new PluginClass();
return plugin.define;
return item.define;
}
}

View File

@ -1,10 +1,10 @@
import { AbstractPlugin, IsTask, RunStrategy, TaskInput, TaskOutput, TaskPlugin, utils } from "@certd/pipeline";
import Core from "@alicloud/pop-core";
// @ts-ignore
import { ROAClient } from "@alicloud/pop-core";
import { AliyunAccess } from "../../access";
import { K8sClient } from "@certd/plugin-util";
import { appendTimeSuffix } from "../../utils";
const ROAClient = Core.ROAClient;
@IsTask(() => {
return {
name: "DeployCertToAliyunAckIngress",

View File

@ -1,7 +1,5 @@
import { AbstractPlugin, IsTask, RunStrategy, TaskInput, TaskOutput, TaskPlugin } from "@certd/pipeline";
import dayjs from "dayjs";
import Core from "@alicloud/pop-core";
import RPCClient from "@alicloud/pop-core";
import { AliyunAccess } from "../../access";
import { appendTimeSuffix, checkRet, ZoneOptions } from "../../utils";

View File

@ -1,27 +1,25 @@
import { AbstractAccess, IsAccess } from "@certd/pipeline";
import { IsAccess, IsAccessInput } from "@certd/pipeline";
@IsAccess({
name: "dnspod",
title: "dnspod",
desc: "腾讯云的域名解析接口已迁移到dnspod",
input: {
id: {
title: "账户id",
component: {
placeholder: "dnspod接口账户id",
},
rules: [{ required: true, message: "该项必填" }],
},
token: {
title: "token",
component: {
placeholder: "开放接口token",
},
rules: [{ required: true, message: "该项必填" }],
},
},
})
export class DnspodAccess extends AbstractAccess {
export class DnspodAccess {
@IsAccessInput({
title: "token",
component: {
placeholder: "开放接口token",
},
rules: [{ required: true, message: "该项必填" }],
})
token = "";
@IsAccessInput({
title: "账户id",
component: {
placeholder: "dnspod接口账户id",
},
rules: [{ required: true, message: "该项必填" }],
})
id = "";
}

View File

@ -1,26 +1,24 @@
import { AbstractAccess, IsAccess } from "@certd/pipeline";
import { IsAccess, IsAccessInput } from "@certd/pipeline";
@IsAccess({
name: "tencent",
title: "腾讯云",
input: {
secretId: {
title: "secretId",
component: {
placeholder: "secretId",
},
rules: [{ required: true, message: "该项必填" }],
},
secretKey: {
title: "secretKey",
component: {
placeholder: "secretKey",
},
rules: [{ required: true, message: "该项必填" }],
},
},
})
export class TencentAccess extends AbstractAccess {
export class TencentAccess {
@IsAccessInput({
title: "secretId",
component: {
placeholder: "secretId",
},
rules: [{ required: true, message: "该项必填" }],
})
secretId = "";
@IsAccessInput({
title: "secretKey",
component: {
placeholder: "secretKey",
},
rules: [{ required: true, message: "该项必填" }],
})
secretKey = "";
}

View File

@ -1,6 +1,8 @@
import { AbstractDnsProvider, CreateRecordOptions, IDnsProvider, IsDnsProvider, RemoveRecordOptions } from "@certd/pipeline";
import { CreateRecordOptions, HttpClient, IDnsProvider, IsDnsProvider, RemoveRecordOptions } from "@certd/pipeline";
import _ from "lodash";
import { DnspodAccess } from "../access";
import { Inject } from "@midwayjs/decorator";
import { ILogger } from "@midwayjs/core";
@IsDnsProvider({
name: "dnspod",
@ -8,11 +10,17 @@ import { DnspodAccess } from "../access";
desc: "腾讯云的域名解析接口已迁移到dnspod",
accessType: "dnspod",
})
export class DnspodDnsProvider extends AbstractDnsProvider implements IDnsProvider {
export class DnspodDnsProvider implements IDnsProvider {
@Inject()
http!: HttpClient;
@Inject()
access!: DnspodAccess;
@Inject()
logger!: ILogger;
loginToken: any;
constructor() {
super();
}
async onInit() {
const access: DnspodAccess = this.access as DnspodAccess;
this.loginToken = access.id + "," + access.token;

View File

@ -1,56 +1,64 @@
import { AbstractPlugin, IsTask, RunStrategy, TaskInput, TaskOutput, TaskPlugin } from "@certd/pipeline";
import { IAccessService, IsTaskPlugin, RunStrategy, TaskInput, ITaskPlugin } from "@certd/pipeline";
import tencentcloud from "tencentcloud-sdk-nodejs/index";
import { TencentAccess } from "../../access";
import { Inject } from "@midwayjs/decorator";
import { ILogger } from "@midwayjs/core";
@IsTask(() => {
return {
name: "DeployCertToTencentCDN",
title: "部署到腾讯云CDN",
input: {
domainName: {
title: "cdn加速域名",
rules: [{ required: true, message: "该项必填" }],
},
certName: {
title: "证书名称",
helper: "证书上传后将以此参数作为名称前缀",
},
cert: {
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: "pi-output-selector",
from: "CertApply",
},
required: true,
},
accessId: {
title: "Access提供者",
helper: "access 授权",
component: {
name: "pi-access-selector",
type: "tencent",
},
required: true,
},
@IsTaskPlugin({
name: "DeployCertToTencentCDN",
title: "部署到腾讯云CDN",
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
},
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
output: {},
};
},
})
export class DeployToCdnPlugin extends AbstractPlugin implements TaskPlugin {
async execute(input: TaskInput): Promise<TaskOutput> {
const { cert, accessId } = input;
const accessProvider: TencentAccess = (await this.accessService.getById(accessId)) as TencentAccess;
const client = this.getClient(accessProvider);
const params = this.buildParams(input, cert);
await this.doRequest(client, params);
export class DeployToCdnPlugin implements ITaskPlugin {
@TaskInput({
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: "pi-output-selector",
from: "CertApply",
},
required: true,
})
cert!: any;
return {};
@TaskInput({
title: "Access提供者",
helper: "access 授权",
component: {
name: "pi-access-selector",
type: "tencent",
},
required: true,
})
accessId!: string;
@TaskInput({
title: "证书名称",
helper: "证书上传后将以此参数作为名称前缀",
})
certName!: string;
@TaskInput({
title: "cdn加速域名",
rules: [{ required: true, message: "该项必填" }],
})
domainName!: string;
@Inject()
accessService!: IAccessService;
@Inject()
logger!: ILogger;
async execute(): Promise<void> {
const accessProvider: TencentAccess = (await this.accessService.getById(this.accessId)) as TencentAccess;
const client = this.getClient(accessProvider);
const params = this.buildParams();
await this.doRequest(client, params);
}
getClient(accessProvider: TencentAccess) {
@ -72,17 +80,16 @@ export class DeployToCdnPlugin extends AbstractPlugin implements TaskPlugin {
return new CdnClient(clientConfig);
}
buildParams(props: TaskInput, cert: any) {
const { domainName } = props;
buildParams() {
return {
Https: {
Switch: "on",
CertInfo: {
Certificate: cert.crt,
PrivateKey: cert.key,
Certificate: this.cert.crt,
PrivateKey: this.cert.key,
},
},
Domain: domainName,
Domain: this.domainName,
};
}

View File

@ -1,99 +1,115 @@
import { AbstractPlugin, IsTask, RunStrategy, TaskInput, TaskOutput, TaskPlugin, utils } from "@certd/pipeline";
import { IAccessService, IsTaskPlugin, ITaskPlugin, RunStrategy, TaskInput, utils } from "@certd/pipeline";
import tencentcloud from "tencentcloud-sdk-nodejs/index";
import { TencentAccess } from "../../access";
import dayjs from "dayjs";
import { Inject } from "@midwayjs/decorator";
import { ILogger } from "@midwayjs/core";
@IsTask(() => {
return {
name: "DeployCertToTencentCLB",
title: "部署到腾讯云CLB",
desc: "暂时只支持单向认证证书,暂时只支持通用负载均衡",
input: {
region: {
title: "大区",
value: "ap-guangzhou",
component: {
name: "a-select",
options: [{ value: "ap-guangzhou" }],
},
required: true,
},
domain: {
title: "域名",
required: true,
helper: "要更新的支持https的负载均衡的域名",
},
loadBalancerId: {
title: "负载均衡ID",
helper: "如果没有配置则根据域名匹配负载均衡下的监听器根据域名匹配时暂时只支持前100个",
required: true,
},
listenerId: {
title: "监听器ID",
helper: "如果没有配置则根据域名或负载均衡id匹配监听器",
},
certName: {
title: "证书名称前缀",
},
accessId: {
title: "Access提供者",
helper: "access授权",
component: {
name: "pi-access-selector",
type: "tencent",
},
required: true,
},
cert: {
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: "pi-output-selector",
},
required: true,
},
@IsTaskPlugin({
name: "DeployCertToTencentCLB",
title: "部署到腾讯云CLB",
desc: "暂时只支持单向认证证书,暂时只支持通用负载均衡",
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
},
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
output: {},
};
},
})
export class DeployToClbPlugin extends AbstractPlugin implements TaskPlugin {
async execute(input: TaskInput): Promise<TaskOutput> {
const { accessId, region, domain } = input;
const accessProvider = (await this.accessService.getById(accessId)) as TencentAccess;
const client = this.getClient(accessProvider, region);
export class DeployToClbPlugin implements ITaskPlugin {
@TaskInput({
title: "大区",
value: "ap-guangzhou",
component: {
name: "a-select",
options: [{ value: "ap-guangzhou" }],
},
required: true,
})
region!: string;
const lastCertId = await this.getCertIdFromProps(client, input);
if (!domain) {
await this.updateListener(client, input);
@TaskInput({
title: "证书名称前缀",
})
certName!: string;
@TaskInput({
title: "负载均衡ID",
helper: "如果没有配置则根据域名匹配负载均衡下的监听器根据域名匹配时暂时只支持前100个",
required: true,
})
loadBalancerId!: string;
@TaskInput({
title: "监听器ID",
helper: "如果没有配置则根据域名或负载均衡id匹配监听器",
})
listenerId!: string;
@TaskInput({
title: "域名",
required: true,
helper: "要更新的支持https的负载均衡的域名",
})
domain!: string;
@TaskInput({
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: "pi-output-selector",
},
required: true,
})
cert!: any;
@TaskInput({
title: "Access提供者",
helper: "access授权",
component: {
name: "pi-access-selector",
type: "tencent",
},
required: true,
})
accessId!: string;
@Inject()
accessService!: IAccessService;
@Inject()
logger!: ILogger;
async execute(): Promise<void> {
const accessProvider = (await this.accessService.getById(this.accessId)) as TencentAccess;
const client = this.getClient(accessProvider, this.region);
const lastCertId = await this.getCertIdFromProps(client);
if (!this.domain) {
await this.updateListener(client);
} else {
await this.updateByDomainAttr(client, input);
await this.updateByDomainAttr(client);
}
try {
await utils.sleep(2000);
let newCertId = await this.getCertIdFromProps(client, input);
let newCertId = await this.getCertIdFromProps(client);
if ((lastCertId && newCertId === lastCertId) || (!lastCertId && !newCertId)) {
await utils.sleep(2000);
newCertId = await this.getCertIdFromProps(client, input);
newCertId = await this.getCertIdFromProps(client);
}
if (newCertId === lastCertId) {
return {};
return;
}
this.logger.info("腾讯云证书ID:", newCertId);
} catch (e) {
this.logger.warn("查询腾讯云证书失败", e);
}
return {};
return;
}
async getCertIdFromProps(client: any, input: TaskInput) {
const listenerRet = await this.getListenerList(client, input.loadBalancerId, [input.listenerId]);
return this.getCertIdFromListener(listenerRet[0], input.domain);
async getCertIdFromProps(client: any) {
const listenerRet = await this.getListenerList(client, this.loadBalancerId, [this.listenerId]);
return this.getCertIdFromListener(listenerRet[0], this.domain);
}
getCertIdFromListener(listener: any, domain: string) {
@ -115,28 +131,28 @@ export class DeployToClbPlugin extends AbstractPlugin implements TaskPlugin {
return certId;
}
async updateListener(client: any, props: TaskInput) {
const params = this.buildProps(props);
async updateListener(client: any) {
const params = this.buildProps();
const ret = await client.ModifyListener(params);
this.checkRet(ret);
this.logger.info("设置腾讯云CLB证书成功:", ret.RequestId, "->loadBalancerId:", props.loadBalancerId, "listenerId", props.listenerId);
this.logger.info("设置腾讯云CLB证书成功:", ret.RequestId, "->loadBalancerId:", this.loadBalancerId, "listenerId", this.listenerId);
return ret;
}
async updateByDomainAttr(client: any, props: TaskInput) {
const params: any = this.buildProps(props);
params.Domain = props.domain;
async updateByDomainAttr(client: any) {
const params: any = this.buildProps();
params.Domain = this.domain;
const ret = await client.ModifyDomainAttributes(params);
this.checkRet(ret);
this.logger.info(
"设置腾讯云CLB证书(sni)成功:",
ret.RequestId,
"->loadBalancerId:",
props.loadBalancerId,
this.loadBalancerId,
"listenerId",
props.listenerId,
this.listenerId,
"domain:",
props.domain
this.domain
);
return ret;
}
@ -146,26 +162,25 @@ export class DeployToClbPlugin extends AbstractPlugin implements TaskPlugin {
}
return name + "-" + dayjs().format("YYYYMMDD-HHmmss");
}
buildProps(props: TaskInput) {
const { certName, cert } = props;
buildProps() {
return {
Certificate: {
SSLMode: "UNIDIRECTIONAL", // 单向认证
CertName: this.appendTimeSuffix(certName || cert.domain),
CertKey: cert.key,
CertContent: cert.crt,
CertName: this.appendTimeSuffix(this.certName || this.cert.domain),
CertKey: this.cert.key,
CertContent: this.cert.crt,
},
LoadBalancerId: props.loadBalancerId,
ListenerId: props.listenerId,
LoadBalancerId: this.loadBalancerId,
ListenerId: this.listenerId,
};
}
async getCLBList(client: any, props: TaskInput) {
async getCLBList(client: any) {
const params = {
Limit: 100, // 最大暂时只支持100个暂时没做翻页
OrderBy: "CreateTime",
OrderType: 0,
...props.DescribeLoadBalancers,
// ...this.DescribeLoadBalancers,
};
const ret = await client.DescribeLoadBalancers(params);
this.checkRet(ret);

@ -1 +1 @@
Subproject commit a59e66a11d667dc99b425b14cd6fde7468a8725b
Subproject commit 70f3fd5359ac27b6dbd452f30fbf6630d4c9fa26