feat: midway注解方式编写插件

pull/9/head^2
xiaojunnuo 2022-12-29 23:52:51 +08:00
parent 717d203622
commit e4ec4e1404
30 changed files with 506 additions and 446 deletions

View File

@ -0,0 +1,6 @@
{
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
"useWorkspaces": true,
"version": "0.0.0",
"npmClient": "yarn"
}

View File

@ -3,7 +3,7 @@
"private": true, "private": true,
"type": "module", "type": "module",
"devDependencies": { "devDependencies": {
"lerna": "^3.18.4" "lerna": "^6.3.0"
}, },
"scripts": { "scripts": {
"start": "lerna bootstrap --hoist", "start": "lerna bootstrap --hoist",
@ -11,6 +11,9 @@
}, },
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"lodash-es": "^4.17.20" "lodash-es": "^4.17.21"
} },
"workspaces": [
"packages/**"
]
} }

@ -1 +1 @@
Subproject commit 1c0e55a5339ef0ac92f96726c4c61e6918908730 Subproject commit 138c736ce6468e208a1055fd6289b8c2c8361306

View File

@ -1,7 +1,5 @@
import { Registrable } from "../registry"; import { Registrable } from "../registry";
import { accessRegistry } from "./registry";
import { FormItemProps } from "../d.ts"; import { FormItemProps } from "../d.ts";
import { AbstractAccess } from "./abstract-access";
export type AccessInputDefine = FormItemProps & { export type AccessInputDefine = FormItemProps & {
title: string; title: string;
@ -13,5 +11,5 @@ export type AccessDefine = Registrable & {
}; };
}; };
export interface IAccessService { export interface IAccessService {
getById(id: any): Promise<AbstractAccess>; getById(id: any): Promise<any>;
} }

View File

@ -9,7 +9,7 @@ import {
saveModule, saveModule,
} from "@midwayjs/decorator"; } from "@midwayjs/decorator";
import { AccessDefine, AccessInputDefine } from "./api"; import { AccessDefine, AccessInputDefine } from "./api";
import _ from "lodash-es"; import _ from "lodash";
import { accessRegistry } from "./registry"; import { accessRegistry } from "./registry";
// 提供一个唯一 key // 提供一个唯一 key

View File

@ -1,14 +1,15 @@
import { ConcurrencyStrategy, Pipeline, ResultType, Runnable, RunStrategy, Stage, Step, Task } from "../d.ts"; import { ConcurrencyStrategy, Pipeline, ResultType, Runnable, RunStrategy, Stage, Step, Task } from "../d.ts";
import _ from "lodash"; import _ from "lodash";
import { RunHistory } from "./run-history"; import { RunHistory } from "./run-history";
import { pluginRegistry, ITaskPlugin, PluginDefine } from "../plugin"; import { PluginDefine, pluginRegistry } from "../plugin";
import { ContextFactory, IContext } from "./context"; import { ContextFactory, IContext } from "./context";
import { IStorage } from "./storage"; import { IStorage } from "./storage";
import { logger } from "../utils/util.log"; import { logger } from "../utils/util.log";
import { Logger } from "log4js"; import { Logger } from "log4js";
import { request } from "../utils/util.request"; import { request } from "../utils/util.request";
import { IAccessService } from "../access"; import { IAccessService } from "../access";
import { Registrable, RegistryItem } from "../registry"; import { RegistryItem } from "../registry";
export class Executor { export class Executor {
userId: any; userId: any;
pipeline: Pipeline; pipeline: Pipeline;
@ -136,7 +137,7 @@ export class Executor {
private async runStep(step: Step) { private async runStep(step: Step) {
//执行任务 //执行任务
const plugin: RegistryItem = pluginRegistry.get(step.type); const plugin: RegistryItem = pluginRegistry.get(step.type);
const context = { const context: any = {
logger: this.runtime.loggers[step.id], logger: this.runtime.loggers[step.id],
accessService: this.accessService, accessService: this.accessService,
pipelineContext: this.pipelineContext, pipelineContext: this.pipelineContext,
@ -159,7 +160,9 @@ export class Executor {
} }
}); });
_.forEach(define.autowire, (item, key: string) => {
instance[key] = context[key];
});
const res = await instance.execute(); const res = await instance.execute();

View File

@ -1,18 +1,5 @@
// src/decorator/memoryCache.decorator.ts // src/decorator/memoryCache.decorator.ts
import { import { getClassMetadata, listModule, Provide, saveClassMetadata, saveModule, Scope, ScopeEnum } from "@midwayjs/decorator";
attachClassMetadata,
attachPropertyDataToClass,
createCustomPropertyDecorator,
getClassMetadata,
listModule,
listPropertyDataFromClass,
Provide,
saveClassMetadata,
saveModule,
Scope,
ScopeEnum,
} from "@midwayjs/decorator";
import _ from "lodash-es";
import { dnsProviderRegistry } from "./registry"; import { dnsProviderRegistry } from "./registry";
import { DnsProviderDefine } from "./api"; import { DnsProviderDefine } from "./api";

View File

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

View File

@ -1,9 +1,7 @@
import { Config, Configuration, getClassMetadata, Inject, listModule, listPropertyDataFromClass, Logger } from "@midwayjs/decorator"; import { Config, Configuration, Inject, Logger } from "@midwayjs/decorator";
import _ from "lodash-es";
// @ts-ignore // @ts-ignore
import { ILogger } from "@midwayjs/logger"; import { ILogger } from "@midwayjs/logger";
import { IMidwayContainer, MidwayDecoratorService } from "@midwayjs/core"; import { IMidwayContainer, MidwayDecoratorService } from "@midwayjs/core";
import { pluginRegistry } from "../plugin";
import { registerPlugins } from "../plugin/decorator"; import { registerPlugins } from "../plugin/decorator";
import { registerAccess } from "../access/decorator"; import { registerAccess } from "../access/decorator";
import { registerDnsProviders } from "../dns-provider"; import { registerDnsProviders } from "../dns-provider";

View File

@ -1,5 +1,4 @@
import { Registrable } from "../registry"; import { Registrable } from "../registry";
import { pluginRegistry } from "./registry";
import { FormItemProps } from "../d.ts"; import { FormItemProps } from "../d.ts";
export enum ContextScope { export enum ContextScope {
@ -33,6 +32,7 @@ export type PluginDefine = Registrable & {
}; };
export interface ITaskPlugin { export interface ITaskPlugin {
onInit(): Promise<void>;
execute(): Promise<void>; execute(): Promise<void>;
} }

View File

@ -1,18 +1,13 @@
// src/decorator/memoryCache.decorator.ts
import { import {
attachClassMetadata, attachClassMetadata,
attachPropertyDataToClass, attachPropertyDataToClass,
createCustomPropertyDecorator,
getClassMetadata, getClassMetadata,
listModule, listModule,
listPropertyDataFromClass, listPropertyDataFromClass,
Provide,
saveClassMetadata, saveClassMetadata,
saveModule, saveModule,
Scope,
ScopeEnum,
} from "@midwayjs/decorator"; } from "@midwayjs/decorator";
import _ from "lodash-es"; import _ from "lodash";
import { pluginRegistry } from "./registry"; import { pluginRegistry } from "./registry";
import { PluginDefine, TaskInputDefine, TaskOutputDefine } from "./api"; import { PluginDefine, TaskInputDefine, TaskOutputDefine } from "./api";
@ -33,11 +28,11 @@ export function IsTaskPlugin(define: PluginDefine): ClassDecorator {
}, },
target target
); );
// 指定 IoC 容器创建实例的作用域,这里注册为请求作用域,这样能取到 ctx // // 指定 IoC 容器创建实例的作用域,这里注册为请求作用域,这样能取到 ctx
Scope(ScopeEnum.Prototype)(target); // Scope(ScopeEnum.Prototype)(target);
// 调用一下 Provide 装饰器,这样用户的 class 可以省略写 @Provide() 装饰器了 // 调用一下 Provide 装饰器,这样用户的 class 可以省略写 @Provide() 装饰器了
Provide()(target); // Provide()(target);
}; };
} }

View File

@ -1,4 +1,4 @@
import log4js, { LoggingEvent } from "log4js"; import log4js, { LoggingEvent, Logger } from "log4js";
const OutputAppender = { const OutputAppender = {
configure: (config: any, layouts: any, findAppender: any, levels: any) => { configure: (config: any, layouts: any, findAppender: any, levels: any) => {
@ -32,3 +32,4 @@ export function buildLogger(write: (text: string) => void) {
}); });
return logger; return logger;
} }
export type LOGGER = Logger;

View File

@ -22,6 +22,7 @@
"@certd/plugin-util": "^0.3.0" "@certd/plugin-util": "^0.3.0"
}, },
"devDependencies": { "devDependencies": {
"log4js": "^6.7.1",
"@types/lodash": "^4.14.186", "@types/lodash": "^4.14.186",
"vue-tsc": "^0.38.9", "vue-tsc": "^0.38.9",
"@alicloud/cs20151215": "^3.0.3", "@alicloud/cs20151215": "^3.0.3",
@ -40,7 +41,6 @@
"eslint-plugin-import": "^2.26.0", "eslint-plugin-import": "^2.26.0",
"eslint-plugin-node": "^11.1.0", "eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
"log4js": "^6.3.0",
"mocha": "^10.1.0", "mocha": "^10.1.0",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"typescript": "^4.8.4", "typescript": "^4.8.4",

View File

@ -1,27 +1,25 @@
import { AbstractAccess, IsAccess } from "@certd/pipeline"; import { IsAccess, IsAccessInput } from "@certd/pipeline";
@IsAccess({ @IsAccess({
name: "aliyun", name: "aliyun",
title: "阿里云授权", title: "阿里云授权",
desc: "", desc: "",
input: {
accessKeyId: {
title: "accessKeyId",
component: {
placeholder: "accessKeyId",
},
required: true,
},
accessKeySecret: {
title: "accessKeySecret",
component: {
placeholder: "accessKeySecret",
},
required: true,
},
},
}) })
export class AliyunAccess extends AbstractAccess { export class AliyunAccess {
@IsAccessInput({
title: "accessKeyId",
component: {
placeholder: "accessKeyId",
},
required: true,
})
accessKeyId = ""; accessKeyId = "";
@IsAccessInput({
title: "accessKeySecret",
component: {
placeholder: "accessKeySecret",
},
required: true,
})
accessKeySecret = ""; accessKeySecret = "";
} }

View File

@ -1,6 +1,7 @@
import Core from "@alicloud/pop-core"; import Core from "@alicloud/pop-core";
import _ from "lodash"; import _ from "lodash";
import { AbstractDnsProvider, CreateRecordOptions, IDnsProvider, IsDnsProvider, RemoveRecordOptions } from "@certd/pipeline"; import { CreateRecordOptions, IDnsProvider, IsDnsProvider, RemoveRecordOptions } from "@certd/pipeline";
import { Logger } from "log4js";
@IsDnsProvider({ @IsDnsProvider({
name: "aliyun", name: "aliyun",
@ -8,11 +9,10 @@ import { AbstractDnsProvider, CreateRecordOptions, IDnsProvider, IsDnsProvider,
desc: "阿里云DNS解析提供商", desc: "阿里云DNS解析提供商",
accessType: "aliyun", accessType: "aliyun",
}) })
export class AliyunDnsProvider extends AbstractDnsProvider implements IDnsProvider { export class AliyunDnsProvider implements IDnsProvider {
client: any; client: any;
constructor() { access: any;
super(); logger!: Logger;
}
async onInit() { async onInit() {
const access: any = this.access; const access: any = this.access;
this.client = new Core({ this.client = new Core({

View File

@ -1,59 +1,69 @@
import { AbstractPlugin, IsTask, RunStrategy, TaskInput, TaskOutput, TaskPlugin } from "@certd/pipeline"; import { Autowire, IAccessService, IsTaskPlugin, ITaskPlugin, LOGGER, RunStrategy, TaskInput } from "@certd/pipeline";
import dayjs from "dayjs"; import dayjs from "dayjs";
import Core from "@alicloud/pop-core"; import Core from "@alicloud/pop-core";
import RPCClient from "@alicloud/pop-core"; import RPCClient from "@alicloud/pop-core";
import { AliyunAccess } from "../../access"; import { AliyunAccess } from "../../access";
import { Inject } from "@midwayjs/core";
@IsTask(() => { @IsTaskPlugin({
return { name: "DeployCertToAliyunCDN",
name: "DeployCertToAliyunCDN", title: "部署证书至阿里云CDN",
title: "部署证书至阿里云CDN", desc: "依赖证书申请前置任务自动部署域名证书至阿里云CDN",
desc: "依赖证书申请前置任务自动部署域名证书至阿里云CDN", default: {
input: { strategy: {
domainName: { runStrategy: RunStrategy.SkipWhenSucceed,
title: "CDN加速域名",
helper: "你在阿里云上配置的CDN加速域名比如certd.docmirror.cn",
required: true,
},
certName: {
title: "证书名称",
helper: "上传后将以此名称作为前缀备注",
},
cert: {
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: "pi-output-selector",
},
required: true,
},
accessId: {
title: "Access授权",
helper: "阿里云授权AccessKeyId、AccessKeySecret",
component: {
name: "pi-access-selector",
type: "aliyun",
},
required: true,
},
}, },
output: {}, },
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
};
}) })
export class DeployCertToAliyunCDN extends AbstractPlugin implements TaskPlugin { export class DeployCertToAliyunCDN implements ITaskPlugin {
async execute(input: TaskInput): Promise<TaskOutput> { @TaskInput({
title: "CDN加速域名",
helper: "你在阿里云上配置的CDN加速域名比如certd.docmirror.cn",
required: true,
})
domainName!: string;
@TaskInput({
title: "证书名称",
helper: "上传后将以此名称作为前缀备注",
})
certName!: string;
@TaskInput({
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: "pi-output-selector",
},
required: true,
})
cert!: string;
@TaskInput({
title: "Access授权",
helper: "阿里云授权AccessKeyId、AccessKeySecret",
component: {
name: "pi-access-selector",
type: "aliyun",
},
required: true,
})
accessId!: string;
@Inject()
accessService!: IAccessService;
@Autowire()
logger!: LOGGER;
// eslint-disable-next-line @typescript-eslint/no-empty-function
async onInit() {}
async execute(): Promise<void> {
console.log("开始部署证书到阿里云cdn"); console.log("开始部署证书到阿里云cdn");
const access = (await this.accessService.getById(input.accessId)) as AliyunAccess; const access = (await this.accessService.getById(this.accessId)) as AliyunAccess;
const client = this.getClient(access); const client = this.getClient(access);
const params = await this.buildParams(input); const params = await this.buildParams();
await this.doRequest(client, params); await this.doRequest(client, params);
console.log("部署完成"); console.log("部署完成");
return {};
} }
getClient(access: AliyunAccess) { getClient(access: AliyunAccess) {
@ -65,13 +75,12 @@ export class DeployCertToAliyunCDN extends AbstractPlugin implements TaskPlugin
}); });
} }
async buildParams(input: TaskInput) { async buildParams() {
const { certName, domainName } = input; const CertName = (this.certName ?? "certd") + "-" + dayjs().format("YYYYMMDDHHmmss");
const CertName = (certName ?? "certd") + "-" + dayjs().format("YYYYMMDDHHmmss"); const cert: any = this.cert;
const cert = input.cert;
return { return {
RegionId: "cn-hangzhou", RegionId: "cn-hangzhou",
DomainName: domainName, DomainName: this.domainName,
ServerCertificateStatus: "on", ServerCertificateStatus: "on",
CertName: CertName, CertName: CertName,
CertType: "upload", CertType: "upload",

View File

@ -1,70 +1,81 @@
import { AbstractPlugin, IsTask, RunStrategy, TaskInput, TaskOutput, TaskPlugin } from "@certd/pipeline"; import { Autowire, IAccessService, IsTaskPlugin, ITaskPlugin, RunStrategy, TaskInput, TaskOutput } from "@certd/pipeline";
import Core from "@alicloud/pop-core"; import Core from "@alicloud/pop-core";
import { AliyunAccess } from "../../access"; import { AliyunAccess } from "../../access";
import { appendTimeSuffix, checkRet, ZoneOptions } from "../../utils"; import { appendTimeSuffix, checkRet, ZoneOptions } from "../../utils";
import { Logger } from "log4js";
@IsTask(() => { @IsTaskPlugin({
return { name: "uploadCertToAliyun",
name: "uploadCertToAliyun", title: "上传证书到阿里云",
title: "上传证书到阿里云", desc: "",
desc: "", default: {
input: { strategy: {
name: { runStrategy: RunStrategy.SkipWhenSucceed,
title: "证书名称",
helper: "证书上传后将以此参数作为名称前缀",
},
regionId: {
title: "大区",
value: "cn-hangzhou",
component: {
name: "a-select",
vModel: "value",
options: ZoneOptions,
},
required: true,
},
cert: {
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: "pi-output-selector",
},
required: true,
},
accessId: {
title: "Access授权",
helper: "阿里云授权AccessKeyId、AccessKeySecret",
component: {
name: "pi-access-selector",
type: "aliyun",
},
required: true,
},
}, },
output: { },
aliyunCertId: {
title: "上传成功后的阿里云CertId",
},
},
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
};
}) })
export class UploadCertToAliyun extends AbstractPlugin implements TaskPlugin { export class UploadCertToAliyun implements ITaskPlugin {
async execute(input: TaskInput): Promise<TaskOutput> { @TaskInput({
title: "证书名称",
helper: "证书上传后将以此参数作为名称前缀",
})
name!: string;
@TaskInput({
title: "大区",
value: "cn-hangzhou",
component: {
name: "a-select",
vModel: "value",
options: ZoneOptions,
},
required: true,
})
regionId!: string;
@TaskInput({
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: "pi-output-selector",
},
required: true,
})
cert!: any;
@TaskInput({
title: "Access授权",
helper: "阿里云授权AccessKeyId、AccessKeySecret",
component: {
name: "pi-access-selector",
type: "aliyun",
},
required: true,
})
accessId!: string;
@TaskOutput({
title: "上传成功后的阿里云CertId",
})
aliyunCertId!: string;
@Autowire()
accessService!: IAccessService;
@Autowire()
logger!: Logger;
async execute(): Promise<void> {
console.log("开始部署证书到阿里云cdn"); console.log("开始部署证书到阿里云cdn");
const access = (await this.accessService.getById(input.accessId)) as AliyunAccess; const access = (await this.accessService.getById(this.accessId)) as AliyunAccess;
const client = this.getClient(access); const client = this.getClient(access);
const { name, cert } = input; const certName = appendTimeSuffix(this.name);
const certName = appendTimeSuffix(name);
const params = { const params = {
RegionId: input.regionId || "cn-hangzhou", RegionId: this.regionId || "cn-hangzhou",
Name: certName, Name: certName,
Cert: cert.crt, Cert: this.cert.crt,
Key: cert.key, Key: this.cert.key,
}; };
const requestOption = { const requestOption = {
@ -74,7 +85,9 @@ export class UploadCertToAliyun extends AbstractPlugin implements TaskPlugin {
const ret = (await client.request("CreateUserCertificate", params, requestOption)) as any; const ret = (await client.request("CreateUserCertificate", params, requestOption)) as any;
checkRet(ret); checkRet(ret);
this.logger.info("证书上传成功aliyunCertId=", ret.CertId); this.logger.info("证书上传成功aliyunCertId=", ret.CertId);
return { aliyunCertId: ret.CertId };
//output
this.aliyunCertId = ret.CertId;
} }
getClient(aliyunProvider: AliyunAccess) { getClient(aliyunProvider: AliyunAccess) {

View File

@ -18,6 +18,7 @@
}, },
"devDependencies": { "devDependencies": {
"log4js": "^6.7.1",
"@types/lodash": "^4.14.186", "@types/lodash": "^4.14.186",
"vue-tsc": "^0.38.9", "vue-tsc": "^0.38.9",
"@alicloud/cs20151215": "^3.0.3", "@alicloud/cs20151215": "^3.0.3",
@ -36,7 +37,6 @@
"eslint-plugin-import": "^2.26.0", "eslint-plugin-import": "^2.26.0",
"eslint-plugin-node": "^11.1.0", "eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
"log4js": "^6.3.0",
"mocha": "^10.1.0", "mocha": "^10.1.0",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"typescript": "^4.8.4", "typescript": "^4.8.4",

View File

@ -1,4 +1,4 @@
export * from "@certd/plugin-aliyun"; // export * from "@certd/plugin-aliyun";
export * from "@certd/plugin-cert"; export * from "@certd/plugin-cert";
export * from "@certd/plugin-tencent"; export * from "@certd/plugin-tencent";
export * from "@certd/plugin-host"; // export * from "@certd/plugin-host";

View File

@ -16,6 +16,7 @@
"@certd/pipeline": "^0.3.0" "@certd/pipeline": "^0.3.0"
}, },
"devDependencies": { "devDependencies": {
"log4js": "^6.7.1",
"dayjs": "^1.11.6", "dayjs": "^1.11.6",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"@types/lodash": "^4.14.186", "@types/lodash": "^4.14.186",
@ -36,7 +37,6 @@
"eslint-plugin-import": "^2.26.0", "eslint-plugin-import": "^2.26.0",
"eslint-plugin-node": "^11.1.0", "eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
"log4js": "^6.3.0",
"mocha": "^10.1.0", "mocha": "^10.1.0",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"typescript": "^4.8.4", "typescript": "^4.8.4",

View File

@ -1,7 +1,7 @@
// @ts-ignore // @ts-ignore
import * as acme from "@certd/acme-client"; import * as acme from "@certd/acme-client";
import _ from "lodash"; import _ from "lodash";
import { AbstractDnsProvider, IDnsProvider } from "@certd/pipeline"; import { IDnsProvider } from "@certd/pipeline";
import { Challenge } from "@certd/acme-client/types/rfc8555"; import { Challenge } from "@certd/acme-client/types/rfc8555";
import { Logger } from "log4js"; import { Logger } from "log4js";
import { IContext } from "@certd/pipeline/src/core/context"; import { IContext } from "@certd/pipeline/src/core/context";
@ -132,7 +132,7 @@ export class AcmeService {
} }
} }
async order(options: { email: string; domains: string | string[]; dnsProvider: AbstractDnsProvider; csrInfo: any; isTest?: boolean }) { async order(options: { email: string; domains: string | string[]; dnsProvider: any; csrInfo: any; isTest?: boolean }) {
const { email, isTest, domains, csrInfo, dnsProvider } = options; const { email, isTest, domains, csrInfo, dnsProvider } = options;
const client: acme.Client = await this.getAcmeClient(email, isTest); const client: acme.Client = await this.getAcmeClient(email, isTest);

View File

@ -1,123 +1,164 @@
import { AbstractDnsProvider, AbstractPlugin, dnsProviderRegistry, IsTask, RunStrategy, TaskInput, TaskOutput, TaskPlugin } from "@certd/pipeline"; import {
Autowire,
dnsProviderRegistry,
HttpClient,
IAccessService,
IContext,
IsTaskPlugin,
ITaskPlugin,
RunStrategy,
TaskInput,
TaskOutput,
} from "@certd/pipeline";
import forge from "node-forge"; import forge from "node-forge";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { AcmeService } from "./acme"; import { AcmeService } from "./acme";
import _ from "lodash"; import _ from "lodash";
import { Logger } from "log4js";
export type CertInfo = { export type CertInfo = {
crt: string; crt: string;
key: string; key: string;
csr: string; csr: string;
}; };
@IsTask(() => { @IsTaskPlugin({
return { name: "CertApply",
name: "CertApply", title: "证书申请",
title: "证书申请", desc: "免费通配符域名证书申请,支持多个域名打到同一个证书上",
desc: "免费通配符域名证书申请,支持多个域名打到同一个证书上", default: {
input: { input: {
domains: { renewDays: 20,
title: "域名", forceUpdate: false,
component: {
name: "a-select",
vModel: "value",
mode: "tags",
open: false,
},
required: true,
col: {
span: 24,
},
helper:
"支持通配符域名,例如: *.foo.com 、 *.test.handsfree.work\n" +
"支持多个域名、多个子域名、多个通配符域名打到一个证书上域名必须是在同一个DNS提供商解析\n" +
"多级子域名要分成多个域名输入(*.foo.com的证书不能用于xxx.yyy.foo.com\n" +
"输入一个回车之后,再输入下一个",
},
email: {
title: "邮箱",
component: {
name: "a-input",
vModel: "value",
},
required: true,
helper: "请输入邮箱",
},
dnsProviderType: {
title: "DNS提供商",
component: {
name: "pi-dns-provider-selector",
},
required: true,
helper: "请选择dns解析提供商",
},
dnsProviderAccess: {
title: "DNS解析授权",
component: {
name: "pi-access-selector",
},
required: true,
helper: "请选择dns解析提供商授权",
},
renewDays: {
title: "更新天数",
component: {
name: "a-input-number",
vModel: "value",
},
required: true,
helper: "到期前多少天后更新证书",
},
forceUpdate: {
title: "强制更新",
component: {
name: "a-switch",
vModel: "checked",
},
helper: "是否强制重新申请证书",
},
}, },
default: { strategy: {
input: { runStrategy: RunStrategy.AlwaysRun,
renewDays: 20,
forceUpdate: false,
},
strategy: {
runStrategy: RunStrategy.AlwaysRun,
},
}, },
output: { },
cert: {
key: "cert",
type: "CertInfo",
title: "域名证书",
},
},
};
}) })
export class CertApplyPlugin extends AbstractPlugin implements TaskPlugin { export class CertApplyPlugin implements ITaskPlugin {
@TaskInput({
title: "域名",
component: {
name: "a-select",
vModel: "value",
mode: "tags",
open: false,
},
required: true,
col: {
span: 24,
},
helper:
"支持通配符域名,例如: *.foo.com 、 *.test.handsfree.work\n" +
"支持多个域名、多个子域名、多个通配符域名打到一个证书上域名必须是在同一个DNS提供商解析\n" +
"多级子域名要分成多个域名输入(*.foo.com的证书不能用于xxx.yyy.foo.com\n" +
"输入一个回车之后,再输入下一个",
})
domains!: string;
@TaskInput({
title: "邮箱",
component: {
name: "a-input",
vModel: "value",
},
required: true,
helper: "请输入邮箱",
})
email!: string;
@TaskInput({
title: "DNS提供商",
component: {
name: "pi-dns-provider-selector",
},
required: true,
helper: "请选择dns解析提供商",
})
dnsProviderType!: string;
@TaskInput({
title: "DNS解析授权",
component: {
name: "pi-access-selector",
},
required: true,
helper: "请选择dns解析提供商授权",
})
dnsProviderAccess!: string;
@TaskInput({
title: "更新天数",
component: {
name: "a-input-number",
vModel: "value",
},
required: true,
helper: "到期前多少天后更新证书",
})
renewDays!: number;
@TaskInput({
title: "强制更新",
component: {
name: "a-switch",
vModel: "checked",
},
helper: "是否强制重新申请证书",
})
forceUpdate!: string;
@TaskInput({
title: "CsrInfo",
})
csrInfo: any;
// @ts-ignore // @ts-ignore
acme: AcmeService; acme: AcmeService;
protected async onInit() {
@Autowire()
logger!: Logger;
@Autowire()
userContext!: IContext;
@Autowire()
accessService!: IAccessService;
@Autowire()
http!: HttpClient;
@Autowire()
pipelineContext!: IContext;
@TaskOutput({
title: "域名证书",
})
cert?: CertInfo;
async onInit() {
this.acme = new AcmeService({ userContext: this.userContext, logger: this.logger }); this.acme = new AcmeService({ userContext: this.userContext, logger: this.logger });
} }
async execute(input: TaskInput): Promise<TaskOutput> { async execute(): Promise<void> {
const oldCert = await this.condition(input); const oldCert = await this.condition();
if (oldCert != null) { if (oldCert != null) {
return { return this.output(oldCert);
cert: oldCert,
};
} }
const cert = await this.doCertApply(input); const cert = await this.doCertApply();
return { cert }; return this.output(cert);
}
output(cert: any) {
this.cert = cert;
} }
/** /**
* *
* @param input * @param input
*/ */
async condition(input: TaskInput) { async condition() {
if (input.forceUpdate) { if (this.forceUpdate) {
return null; return null;
} }
let oldCert; let oldCert;
@ -131,7 +172,7 @@ export class CertApplyPlugin extends AbstractPlugin implements TaskPlugin {
return null; return null;
} }
const ret = this.isWillExpire(oldCert.expires, input.renewDays); const ret = this.isWillExpire(oldCert.expires, this.renewDays);
if (!ret.isWillExpire) { if (!ret.isWillExpire) {
this.logger.info(`证书还未过期:过期时间${dayjs(oldCert.expires).format("YYYY-MM-DD HH:mm:ss")},剩余${ret.leftDays}`); this.logger.info(`证书还未过期:过期时间${dayjs(oldCert.expires).format("YYYY-MM-DD HH:mm:ss")},剩余${ret.leftDays}`);
return oldCert; return oldCert;
@ -140,11 +181,11 @@ export class CertApplyPlugin extends AbstractPlugin implements TaskPlugin {
return null; return null;
} }
async doCertApply(input: TaskInput) { async doCertApply() {
const email = input["email"]; const email = this["email"];
const domains = input["domains"]; const domains = this["domains"];
const dnsProviderType = input["dnsProviderType"]; const dnsProviderType = this["dnsProviderType"];
const dnsProviderAccessId = input["dnsProviderAccess"]; const dnsProviderAccessId = this["dnsProviderAccess"];
const csrInfo = _.merge( const csrInfo = _.merge(
{ {
country: "CN", country: "CN",
@ -154,7 +195,7 @@ export class CertApplyPlugin extends AbstractPlugin implements TaskPlugin {
organizationUnit: "IT Department", organizationUnit: "IT Department",
emailAddress: email, emailAddress: email,
}, },
input["csrInfo"] this.csrInfo
); );
this.logger.info("开始申请证书,", email, domains); this.logger.info("开始申请证书,", email, domains);

View File

@ -15,7 +15,7 @@
"ssh2": "^0.8.9" "ssh2": "^0.8.9"
}, },
"devDependencies": { "devDependencies": {
"log4js": "^6.3.0", "log4js": "^6.7.1",
"dayjs": "^1.9.7", "dayjs": "^1.9.7",
"lodash-es": "^4.17.20", "lodash-es": "^4.17.20",
"@types/lodash": "^4.14.186", "@types/lodash": "^4.14.186",

View File

@ -16,8 +16,8 @@
"tencentcloud-sdk-nodejs": "^4.0.44" "tencentcloud-sdk-nodejs": "^4.0.44"
}, },
"devDependencies": { "devDependencies": {
"log4js": "^6.7.1",
"axios": "^0.21.1", "axios": "^0.21.1",
"log4js": "^6.3.0",
"dayjs": "^1.9.7", "dayjs": "^1.9.7",
"lodash-es": "^4.17.20", "lodash-es": "^4.17.20",
"@types/lodash": "^4.14.186", "@types/lodash": "^4.14.186",

View File

@ -1,8 +1,7 @@
import { IAccessService, IsTaskPlugin, RunStrategy, TaskInput, ITaskPlugin } from "@certd/pipeline"; import { IAccessService, IsTaskPlugin, RunStrategy, TaskInput, ITaskPlugin, LOGGER } from "@certd/pipeline";
import tencentcloud from "tencentcloud-sdk-nodejs/index"; import tencentcloud from "tencentcloud-sdk-nodejs/index";
import { TencentAccess } from "../../access"; import { TencentAccess } from "../../access";
import { Inject } from "@midwayjs/decorator"; import { Inject } from "@midwayjs/decorator";
import { ILogger } from "@midwayjs/core";
@IsTaskPlugin({ @IsTaskPlugin({
name: "DeployCertToTencentCDN", name: "DeployCertToTencentCDN",
@ -52,7 +51,10 @@ export class DeployToCdnPlugin implements ITaskPlugin {
accessService!: IAccessService; accessService!: IAccessService;
@Inject() @Inject()
logger!: ILogger; logger!: LOGGER;
// eslint-disable-next-line @typescript-eslint/no-empty-function
async onInit() {}
async execute(): Promise<void> { async execute(): Promise<void> {
const accessProvider: TencentAccess = (await this.accessService.getById(this.accessId)) as TencentAccess; const accessProvider: TencentAccess = (await this.accessService.getById(this.accessId)) as TencentAccess;

View File

@ -1,9 +1,8 @@
import { IAccessService, IsTaskPlugin, ITaskPlugin, RunStrategy, TaskInput, utils } from "@certd/pipeline"; import { IAccessService, IsTaskPlugin, ITaskPlugin, LOGGER, RunStrategy, TaskInput, utils } from "@certd/pipeline";
import tencentcloud from "tencentcloud-sdk-nodejs/index"; import tencentcloud from "tencentcloud-sdk-nodejs/index";
import { TencentAccess } from "../../access"; import { TencentAccess } from "../../access";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { Inject } from "@midwayjs/decorator"; import { Inject } from "@midwayjs/decorator";
import { ILogger } from "@midwayjs/core";
@IsTaskPlugin({ @IsTaskPlugin({
name: "DeployCertToTencentCLB", name: "DeployCertToTencentCLB",
@ -77,8 +76,10 @@ export class DeployToClbPlugin implements ITaskPlugin {
accessService!: IAccessService; accessService!: IAccessService;
@Inject() @Inject()
logger!: ILogger; logger!: LOGGER;
// eslint-disable-next-line @typescript-eslint/no-empty-function
async onInit() {}
async execute(): Promise<void> { async execute(): Promise<void> {
const accessProvider = (await this.accessService.getById(this.accessId)) as TencentAccess; const accessProvider = (await this.accessService.getById(this.accessId)) as TencentAccess;
const client = this.getClient(accessProvider, this.region); const client = this.getClient(accessProvider, this.region);

View File

@ -1,121 +1,117 @@
import { AbstractPlugin, IsTask, RunStrategy, TaskInput, TaskOutput, TaskPlugin, utils } from "@certd/pipeline"; import { Autowire, IAccessService, IsTaskPlugin, ITaskPlugin, RunStrategy, TaskInput, utils } from "@certd/pipeline";
import tencentcloud from "tencentcloud-sdk-nodejs/index"; import tencentcloud from "tencentcloud-sdk-nodejs/index";
import { K8sClient } from "@certd/plugin-util"; import { K8sClient } from "@certd/plugin-util";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { Logger } from "log4js";
@IsTask(() => { @IsTaskPlugin({
return { name: "DeployCertToTencentTKEIngress",
name: "DeployCertToTencentTKEIngress", title: "部署到腾讯云TKE-ingress",
title: "部署到腾讯云TKE-ingress", desc: "需要【上传到腾讯云】作为前置任务",
desc: "需要【上传到腾讯云】作为前置任务", default: {
input: { strategy: {
region: { runStrategy: RunStrategy.SkipWhenSucceed,
title: "大区",
value: "ap-guangzhou",
required: true,
},
clusterId: {
title: "集群ID",
required: true,
desc: "例如cls-6lbj1vee",
request: true,
},
namespace: {
title: "集群namespace",
value: "default",
required: true,
},
secreteName: {
title: "证书的secret名称",
required: true,
},
ingressName: {
title: "ingress名称",
required: true,
},
ingressClass: {
title: "ingress类型",
component: {
name: "a-select",
options: [{ value: "qcloud" }, { value: "nginx" }],
},
helper: "可选 qcloud / nginx",
},
clusterIp: {
title: "集群内网ip",
helper: "如果开启了外网的话,无需设置",
},
clusterDomain: {
title: "集群域名",
helper: "可不填,默认为:[clusterId].ccs.tencent-cloud.com",
},
tencentCertId: {
title: "腾讯云证书id",
helper: "请选择“上传证书到腾讯云”前置任务的输出",
component: {
name: "pi-output-selector",
from: "UploadCertToTencent",
},
required: true,
},
/**
* AccessProviderkey,access
*/
accessId: {
title: "Access授权",
helper: "access授权",
component: {
name: "pi-access-selector",
type: "tencent",
},
required: true,
},
cert: {
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: "pi-output-selector",
},
required: true,
},
}, },
default: { },
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
output: {},
};
}) })
export class DeployCertToTencentTKEIngressPlugin extends AbstractPlugin implements TaskPlugin { export class DeployCertToTencentTKEIngressPlugin implements ITaskPlugin {
async execute(input: TaskInput): Promise<TaskOutput> { @TaskInput({ title: "大区", value: "ap-guangzhou", required: true })
const { accessId, region, clusterId, clusterIp, ingressClass } = input; region!: string;
let { clusterDomain } = input;
const accessProvider = this.accessService.getById(accessId); @TaskInput({ title: "集群ID", required: true, desc: "例如cls-6lbj1vee", request: true })
const tkeClient = this.getTkeClient(accessProvider, region); clusterId!: string;
const kubeConfigStr = await this.getTkeKubeConfig(tkeClient, clusterId);
@TaskInput({ title: "集群namespace", value: "default", required: true })
namespace!: string;
@TaskInput({ title: "证书的secret名称", required: true })
secretName!: string | string[];
@TaskInput({ title: "ingress名称", required: true })
ingressName!: string | string[];
@TaskInput({
title: "ingress类型",
component: {
name: "a-select",
options: [{ value: "qcloud" }, { value: "nginx" }],
},
helper: "可选 qcloud / nginx",
})
ingressClass!: string;
@TaskInput({ title: "集群内网ip", helper: "如果开启了外网的话,无需设置" })
clusterIp!: string;
@TaskInput({ title: "集群域名", helper: "可不填,默认为:[clusterId].ccs.tencent-cloud.com" })
clusterDomain!: string;
@TaskInput({
title: "腾讯云证书id",
helper: "请选择“上传证书到腾讯云”前置任务的输出",
component: {
name: "pi-output-selector",
from: "UploadCertToTencent",
},
required: true,
})
tencentCertId!: string;
/**
* AccessProviderkey,access
*/
@TaskInput({
title: "Access授权",
helper: "access授权",
component: {
name: "pi-access-selector",
type: "tencent",
},
required: true,
})
accessId!: string;
@TaskInput({
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: "pi-output-selector",
},
required: true,
})
cert!: any;
@Autowire()
logger!: Logger;
@Autowire()
accessService!: IAccessService;
// eslint-disable-next-line @typescript-eslint/no-empty-function
async onInit() {}
async execute(): Promise<void> {
const accessProvider = this.accessService.getById(this.accessId);
const tkeClient = this.getTkeClient(accessProvider, this.region);
const kubeConfigStr = await this.getTkeKubeConfig(tkeClient, this.clusterId);
this.logger.info("kubeconfig已成功获取"); this.logger.info("kubeconfig已成功获取");
const k8sClient = new K8sClient(kubeConfigStr); const k8sClient = new K8sClient(kubeConfigStr);
if (clusterIp != null) { if (this.clusterIp != null) {
if (!clusterDomain) { if (!this.clusterDomain) {
clusterDomain = `${clusterId}.ccs.tencent-cloud.com`; this.clusterDomain = `${this.clusterId}.ccs.tencent-cloud.com`;
} }
// 修改内网解析ip地址 // 修改内网解析ip地址
k8sClient.setLookup({ [clusterDomain]: { ip: clusterIp } }); k8sClient.setLookup({ [this.clusterDomain]: { ip: this.clusterIp } });
} }
const ingressType = ingressClass || "qcloud"; const ingressType = this.ingressClass || "qcloud";
if (ingressType === "qcloud") { if (ingressType === "qcloud") {
await this.patchQcloudCertSecret({ k8sClient, input }); await this.patchQcloudCertSecret({ k8sClient });
} else { } else {
await this.patchNginxCertSecret({ k8sClient, input }); await this.patchNginxCertSecret({ k8sClient });
} }
await utils.sleep(2000); // 停留2秒等待secret部署完成 await utils.sleep(2000); // 停留2秒等待secret部署完成
await this.restartIngress({ k8sClient, input }); await this.restartIngress({ k8sClient });
return {};
} }
getTkeClient(accessProvider: any, region = "ap-guangzhou") { getTkeClient(accessProvider: any, region = "ap-guangzhou") {
@ -154,15 +150,14 @@ export class DeployCertToTencentTKEIngressPlugin extends AbstractPlugin implemen
return name + "-" + dayjs().format("YYYYMMDD-HHmmss"); return name + "-" + dayjs().format("YYYYMMDD-HHmmss");
} }
async patchQcloudCertSecret(options: { k8sClient: any; input: TaskInput }) { async patchQcloudCertSecret(options: { k8sClient: any }) {
const { tencentCertId } = options.input; if (this.tencentCertId == null) {
if (tencentCertId == null) {
throw new Error("请先将【上传证书到腾讯云】作为前置任务"); throw new Error("请先将【上传证书到腾讯云】作为前置任务");
} }
this.logger.info("腾讯云证书ID:", tencentCertId); this.logger.info("腾讯云证书ID:", this.tencentCertId);
const certIdBase64 = Buffer.from(tencentCertId).toString("base64"); const certIdBase64 = Buffer.from(this.tencentCertId).toString("base64");
const { namespace, secretName } = options.input; const { namespace, secretName } = this;
const body = { const body = {
data: { data: {
@ -174,7 +169,7 @@ export class DeployCertToTencentTKEIngressPlugin extends AbstractPlugin implemen
}, },
}, },
}; };
let secretNames = secretName; let secretNames: any = secretName;
if (typeof secretName === "string") { if (typeof secretName === "string") {
secretNames = [secretName]; secretNames = [secretName];
} }
@ -184,15 +179,15 @@ export class DeployCertToTencentTKEIngressPlugin extends AbstractPlugin implemen
} }
} }
async patchNginxCertSecret(options: { k8sClient: any; input: TaskInput }) { async patchNginxCertSecret(options: { k8sClient: any }) {
const { k8sClient, input } = options; const { k8sClient } = options;
const { cert } = input; const { cert } = this;
const crt = cert.crt; const crt = cert.crt;
const key = cert.key; const key = cert.key;
const crtBase64 = Buffer.from(crt).toString("base64"); const crtBase64 = Buffer.from(crt).toString("base64");
const keyBase64 = Buffer.from(key).toString("base64"); const keyBase64 = Buffer.from(key).toString("base64");
const { namespace, secretName } = input; const { namespace, secretName } = this;
const body = { const body = {
data: { data: {
@ -215,9 +210,9 @@ export class DeployCertToTencentTKEIngressPlugin extends AbstractPlugin implemen
} }
} }
async restartIngress(options: { k8sClient: any; input: TaskInput }) { async restartIngress(options: { k8sClient: any }) {
const { k8sClient, input } = options; const { k8sClient } = options;
const { namespace, ingressName } = input; const { namespace, ingressName } = this;
const body = { const body = {
metadata: { metadata: {
@ -226,7 +221,7 @@ export class DeployCertToTencentTKEIngressPlugin extends AbstractPlugin implemen
}, },
}, },
}; };
let ingressNames = ingressName; let ingressNames = this.ingressName;
if (typeof ingressName === "string") { if (typeof ingressName === "string") {
ingressNames = [ingressName]; ingressNames = [ingressName];
} }

View File

@ -1,49 +1,58 @@
import { AbstractPlugin, IsTask, RunStrategy, TaskInput, TaskOutput, TaskPlugin } from "@certd/pipeline"; import { Autowire, IAccessService, IsTaskPlugin, ITaskPlugin, RunStrategy, TaskInput, TaskOutput, LOGGER } from "@certd/pipeline";
import tencentcloud from "tencentcloud-sdk-nodejs/index"; import tencentcloud from "tencentcloud-sdk-nodejs/index";
import dayjs from "dayjs"; import dayjs from "dayjs";
@IsTask(() => { @IsTaskPlugin({
return { name: "UploadCertToTencent",
name: "UploadCertToTencent", title: "上传证书到腾讯云",
title: "上传证书到腾讯云", desc: "上传成功后输出tencentCertId",
desc: "上传成功后输出tencentCertId", default: {
input: { strategy: {
name: { runStrategy: RunStrategy.SkipWhenSucceed,
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,
},
}, },
default: { },
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
output: {
tencentCertId: {
title: "上传成功后的腾讯云CertId",
},
},
};
}) })
export class UploadToTencentPlugin extends AbstractPlugin implements TaskPlugin { export class UploadToTencentPlugin implements ITaskPlugin {
async execute(input: TaskInput): Promise<TaskOutput> { @TaskInput({ title: "证书名称" })
const { accessId, name, cert } = input; name!: string;
@TaskInput({
title: "Access授权",
helper: "access授权",
component: {
name: "pi-access-selector",
type: "tencent",
},
required: true,
})
accessId!: string;
@TaskInput({
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: "pi-output-selector",
},
required: true,
})
cert!: any;
@TaskOutput({
title: "上传成功后的腾讯云CertId",
})
tencentCertId?: string;
@Autowire()
accessService!: IAccessService;
@Autowire()
logger!: LOGGER;
// eslint-disable-next-line @typescript-eslint/no-empty-function
async onInit() {}
async execute(): Promise<void> {
const { accessId, name, cert } = this;
const accessProvider = this.accessService.getById(accessId); const accessProvider = this.accessService.getById(accessId);
const certName = this.appendTimeSuffix(name || cert.domain); const certName = this.appendTimeSuffix(name || cert.domain);
const client = this.getClient(accessProvider); const client = this.getClient(accessProvider);
@ -56,7 +65,8 @@ export class UploadToTencentPlugin extends AbstractPlugin implements TaskPlugin
const ret = await client.UploadCertificate(params); const ret = await client.UploadCertificate(params);
this.checkRet(ret); this.checkRet(ret);
this.logger.info("证书上传成功tencentCertId=", ret.CertificateId); this.logger.info("证书上传成功tencentCertId=", ret.CertificateId);
return { tencentCertId: ret.CertificateId };
this.tencentCertId = ret.CertificateId;
} }
appendTimeSuffix(name: string) { appendTimeSuffix(name: string) {

View File

@ -15,6 +15,7 @@
"kubernetes-client": "^9.0.0" "kubernetes-client": "^9.0.0"
}, },
"devDependencies": { "devDependencies": {
"log4js": "^6.7.1",
"@types/lodash": "^4.14.186", "@types/lodash": "^4.14.186",
"vue-tsc": "^0.38.9", "vue-tsc": "^0.38.9",
"@alicloud/cs20151215": "^3.0.3", "@alicloud/cs20151215": "^3.0.3",

@ -1 +1 @@
Subproject commit f508b6426b5869ae0fd33d8ec8e170e097eed9d4 Subproject commit 08f08022903aed6ba5150a80ea3484a8bc1830b7