mirror of https://github.com/certd/certd
feat: midway注解方式编写插件
parent
52522f27e9
commit
dcd1023a39
|
@ -6,7 +6,7 @@ export type AccessInputDefine = FormItemProps & {
|
|||
required?: boolean;
|
||||
};
|
||||
export type AccessDefine = Registrable & {
|
||||
inputs?: {
|
||||
input?: {
|
||||
[key: string]: AccessInputDefine;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -20,7 +20,7 @@ export function IsAccess(define: AccessDefine): ClassDecorator {
|
|||
inputs[property] = input;
|
||||
}
|
||||
}
|
||||
_.merge(define, { inputs });
|
||||
_.merge(define, { input: inputs });
|
||||
Reflect.defineMetadata(ACCESS_CLASS_KEY, define, target);
|
||||
target.define = define;
|
||||
accessRegistry.register(define.name, {
|
||||
|
@ -30,9 +30,9 @@ export function IsAccess(define: AccessDefine): ClassDecorator {
|
|||
};
|
||||
}
|
||||
|
||||
export function IsAccessInput(input?: AccessInputDefine): PropertyDecorator {
|
||||
export function AccessInput(input?: AccessInputDefine): PropertyDecorator {
|
||||
return (target, propertyKey) => {
|
||||
target = Decorator.target(target);
|
||||
target = Decorator.target(target, propertyKey);
|
||||
// const _type = Reflect.getMetadata("design:type", target, propertyKey);
|
||||
Reflect.defineMetadata(ACCESS_INPUT_KEY, input, target, propertyKey);
|
||||
};
|
||||
|
|
|
@ -9,6 +9,7 @@ import { Logger } from "log4js";
|
|||
import { request } from "../utils/util.request";
|
||||
import { IAccessService } from "../access";
|
||||
import { RegistryItem } from "../registry";
|
||||
import { Decorator } from "../decorator";
|
||||
|
||||
export class Executor {
|
||||
userId: any;
|
||||
|
@ -149,21 +150,17 @@ export class Executor {
|
|||
// @ts-ignore
|
||||
const define: PluginDefine = plugin.define;
|
||||
//从outputContext读取输入参数
|
||||
_.forEach(define.input, (item, key) => {
|
||||
Decorator.inject(define.input, instance, step.input, (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];
|
||||
}
|
||||
});
|
||||
|
||||
_.forEach(define.autowire, (item, key: string) => {
|
||||
instance[key] = context[key];
|
||||
});
|
||||
|
||||
Decorator.inject(define.autowire, instance, context);
|
||||
await instance.onInit();
|
||||
await instance.execute();
|
||||
|
||||
//输出到output context
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import { Decorator } from "./index";
|
||||
|
||||
export type AutowireProp = {
|
||||
name?: string;
|
||||
type?: any;
|
||||
};
|
||||
export const AUTOWIRE_KEY = "pipeline:autowire";
|
||||
|
||||
export function Autowire(props?: AutowireProp): PropertyDecorator {
|
||||
return (target, propertyKey) => {
|
||||
const _type = Reflect.getMetadata("design:type", target, propertyKey);
|
||||
target = Decorator.target(target, propertyKey);
|
||||
props = props || {};
|
||||
props.type = _type;
|
||||
Reflect.defineMetadata(AUTOWIRE_KEY, props || {}, target, propertyKey);
|
||||
};
|
||||
}
|
|
@ -1,28 +1,2 @@
|
|||
const propertyMap: any = {};
|
||||
function attachProperty(target: any, propertyKey: string | symbol) {
|
||||
let props = propertyMap[target];
|
||||
if (props == null) {
|
||||
props = {};
|
||||
propertyMap[target] = props;
|
||||
}
|
||||
props[propertyKey] = true;
|
||||
}
|
||||
|
||||
function getClassProperties(target: any) {
|
||||
return propertyMap[target] || {};
|
||||
}
|
||||
|
||||
function target(target: any, propertyKey?: string | symbol) {
|
||||
if (typeof target === "object" && target.constructor) {
|
||||
target = target.constructor;
|
||||
}
|
||||
if (propertyKey != null) {
|
||||
attachProperty(target, propertyKey);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
export const Decorator = {
|
||||
target,
|
||||
attachProperty,
|
||||
getClassProperties,
|
||||
};
|
||||
export * from "./utils";
|
||||
export * from "./common";
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
import _ from "lodash";
|
||||
|
||||
const propertyMap: any = {};
|
||||
function attachProperty(target: any, propertyKey: string | symbol) {
|
||||
let props = propertyMap[target];
|
||||
if (props == null) {
|
||||
props = {};
|
||||
propertyMap[target] = props;
|
||||
}
|
||||
props[propertyKey] = true;
|
||||
}
|
||||
|
||||
function getClassProperties(target: any) {
|
||||
return propertyMap[target] || {};
|
||||
}
|
||||
|
||||
function target(target: any, propertyKey?: string | symbol) {
|
||||
if (typeof target === "object" && target.constructor) {
|
||||
target = target.constructor;
|
||||
}
|
||||
if (propertyKey != null) {
|
||||
attachProperty(target, propertyKey);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
function inject(define: any, instance: any, context: any, preHandler?: (item: any, key: string, instance: any, context: any) => void) {
|
||||
_.forEach(define, (item, key) => {
|
||||
if (preHandler) {
|
||||
preHandler(item, key, instance, context);
|
||||
}
|
||||
if (context[key] != undefined) {
|
||||
instance[key] = context[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
export const Decorator = {
|
||||
target,
|
||||
attachProperty,
|
||||
getClassProperties,
|
||||
inject,
|
||||
};
|
|
@ -1,22 +0,0 @@
|
|||
import { AbstractRegistrable } from "../registry";
|
||||
import { CreateRecordOptions, IDnsProvider, DnsProviderDefine, RemoveRecordOptions } from "./api";
|
||||
import { AbstractAccess } from "../access";
|
||||
import { Logger } from "log4js";
|
||||
import { AxiosInstance } from "axios";
|
||||
export abstract class AbstractDnsProvider extends AbstractRegistrable<DnsProviderDefine> implements IDnsProvider {
|
||||
access!: AbstractAccess;
|
||||
logger!: Logger;
|
||||
http!: AxiosInstance;
|
||||
doInit(options: { access: AbstractAccess; logger: Logger; http: AxiosInstance }) {
|
||||
this.access = options.access;
|
||||
this.logger = options.logger;
|
||||
this.http = options.http;
|
||||
this.onInit();
|
||||
}
|
||||
|
||||
protected abstract onInit(): void;
|
||||
|
||||
abstract createRecord(options: CreateRecordOptions): Promise<any>;
|
||||
|
||||
abstract removeRecord(options: RemoveRecordOptions): Promise<any>;
|
||||
}
|
|
@ -2,8 +2,8 @@ export * from "./core";
|
|||
export * from "./d.ts";
|
||||
export * from "./access";
|
||||
export * from "./registry";
|
||||
export * from "./dns-provider";
|
||||
export * from "./plugin";
|
||||
export * from "./utils";
|
||||
export * from "./midway";
|
||||
export * from "./context";
|
||||
export * from "./decorator";
|
||||
|
|
|
@ -2,6 +2,7 @@ import _ from "lodash";
|
|||
import { pluginRegistry } from "./registry";
|
||||
import { PluginDefine, TaskInputDefine, TaskOutputDefine } from "./api";
|
||||
import { Decorator } from "../decorator";
|
||||
import { AUTOWIRE_KEY } from "../decorator";
|
||||
|
||||
// 提供一个唯一 key
|
||||
export const PLUGIN_CLASS_KEY = "pipeline:plugin";
|
||||
|
@ -20,7 +21,7 @@ export function IsTaskPlugin(define: PluginDefine): ClassDecorator {
|
|||
inputs[property] = input;
|
||||
}
|
||||
|
||||
const autowire = Reflect.getMetadata(PLUGIN_AUTOWIRE_KEY, target, property);
|
||||
const autowire = Reflect.getMetadata(AUTOWIRE_KEY, target, property);
|
||||
if (autowire) {
|
||||
autowires[property] = autowire;
|
||||
}
|
||||
|
@ -59,19 +60,3 @@ export function TaskOutput(output?: TaskOutputDefine): PropertyDecorator {
|
|||
Reflect.defineMetadata(PLUGIN_OUTPUT_KEY, output, target, propertyKey);
|
||||
};
|
||||
}
|
||||
|
||||
export type AutowireProp = {
|
||||
name?: string;
|
||||
type?: any;
|
||||
};
|
||||
export const PLUGIN_AUTOWIRE_KEY = "pipeline:plugin:autowire";
|
||||
|
||||
export function Autowire(props?: AutowireProp): PropertyDecorator {
|
||||
return (target, propertyKey) => {
|
||||
const _type = Reflect.getMetadata("design:type", target, propertyKey);
|
||||
target = Decorator.target(target, propertyKey);
|
||||
props = props || {};
|
||||
props.type = _type;
|
||||
Reflect.defineMetadata(PLUGIN_AUTOWIRE_KEY, props || {}, target, propertyKey);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ export class Registry {
|
|||
this.storage[key] = value;
|
||||
}
|
||||
|
||||
get(name: string) {
|
||||
get(name: string): RegistryItem {
|
||||
if (!name) {
|
||||
throw new Error("插件名称不能为空");
|
||||
}
|
||||
|
|
|
@ -32,4 +32,4 @@ export function buildLogger(write: (text: string) => void) {
|
|||
});
|
||||
return logger;
|
||||
}
|
||||
export type LOGGER = Logger;
|
||||
export type ILogger = Logger;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { IsTaskPlugin, TaskInput, ITaskPlugin, LOGGER, Autowire, TaskOutput } from "../src";
|
||||
import { IsTaskPlugin, TaskInput, ITaskPlugin, ILogger, Autowire, TaskOutput } from "../src";
|
||||
|
||||
@IsTaskPlugin({
|
||||
name: "EchoPlugin",
|
||||
|
@ -15,7 +15,7 @@ export class EchoPlugin implements ITaskPlugin {
|
|||
cert!: any;
|
||||
|
||||
@Autowire()
|
||||
logger!: LOGGER;
|
||||
logger!: ILogger;
|
||||
|
||||
@TaskOutput({
|
||||
title: "cert info",
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/ban-ts-ignore": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
// "no-unused-expressions": "off",
|
||||
"max-len": [0, 160, 2, { "ignoreUrls": true }]
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { IsAccess, IsAccessInput } from "@certd/pipeline";
|
||||
import { IsAccess, AccessInput } from "@certd/pipeline";
|
||||
|
||||
@IsAccess({
|
||||
name: "aliyun",
|
||||
|
@ -6,7 +6,7 @@ import { IsAccess, IsAccessInput } from "@certd/pipeline";
|
|||
desc: "",
|
||||
})
|
||||
export class AliyunAccess {
|
||||
@IsAccessInput({
|
||||
@AccessInput({
|
||||
title: "accessKeyId",
|
||||
component: {
|
||||
placeholder: "accessKeyId",
|
||||
|
@ -14,7 +14,7 @@ export class AliyunAccess {
|
|||
required: true,
|
||||
})
|
||||
accessKeyId = "";
|
||||
@IsAccessInput({
|
||||
@AccessInput({
|
||||
title: "accessKeySecret",
|
||||
component: {
|
||||
placeholder: "accessKeySecret",
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import Core from "@alicloud/pop-core";
|
||||
import _ from "lodash";
|
||||
import { CreateRecordOptions, IDnsProvider, IsDnsProvider, RemoveRecordOptions } from "@certd/pipeline";
|
||||
import { Logger } from "log4js";
|
||||
import { CreateRecordOptions, IDnsProvider, IsDnsProvider, RemoveRecordOptions } from "@certd/plugin-cert";
|
||||
import { Autowire, ILogger } from "@certd/pipeline";
|
||||
import { AliyunAccess } from "../access";
|
||||
|
||||
@IsDnsProvider({
|
||||
name: "aliyun",
|
||||
|
@ -11,8 +12,10 @@ import { Logger } from "log4js";
|
|||
})
|
||||
export class AliyunDnsProvider implements IDnsProvider {
|
||||
client: any;
|
||||
access: any;
|
||||
logger!: Logger;
|
||||
@Autowire()
|
||||
access!: AliyunAccess;
|
||||
@Autowire()
|
||||
logger!: ILogger;
|
||||
async onInit() {
|
||||
const access: any = this.access;
|
||||
this.client = new Core({
|
||||
|
|
|
@ -1,101 +1,119 @@
|
|||
import { AbstractPlugin, IsTask, RunStrategy, TaskInput, TaskOutput, TaskPlugin, utils } from "@certd/pipeline";
|
||||
import { Autowire, IAccessService, IsTaskPlugin, ITaskPlugin, ILogger, RunStrategy, TaskInput, utils } from "@certd/pipeline";
|
||||
// @ts-ignore
|
||||
import { ROAClient } from "@alicloud/pop-core";
|
||||
import { AliyunAccess } from "../../access";
|
||||
import { K8sClient } from "@certd/plugin-util";
|
||||
import { appendTimeSuffix } from "../../utils";
|
||||
import { CertInfo } from "@certd/plugin-cert";
|
||||
|
||||
@IsTask(() => {
|
||||
return {
|
||||
name: "DeployCertToAliyunAckIngress",
|
||||
title: "部署到阿里云AckIngress",
|
||||
input: {
|
||||
clusterId: {
|
||||
title: "集群id",
|
||||
component: {
|
||||
placeholder: "集群id",
|
||||
},
|
||||
},
|
||||
secretName: {
|
||||
title: "保密字典Id",
|
||||
component: {
|
||||
placeholder: "保密字典Id",
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
regionId: {
|
||||
title: "大区",
|
||||
value: "cn-shanghai",
|
||||
component: {
|
||||
placeholder: "集群所属大区",
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
namespace: {
|
||||
title: "命名空间",
|
||||
value: "default",
|
||||
component: {
|
||||
placeholder: "命名空间",
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
ingressName: {
|
||||
title: "ingress名称",
|
||||
value: "",
|
||||
component: {
|
||||
placeholder: "ingress名称",
|
||||
},
|
||||
required: true,
|
||||
helper: "可以传入一个数组",
|
||||
},
|
||||
ingressClass: {
|
||||
title: "ingress类型",
|
||||
value: "nginx",
|
||||
component: {
|
||||
placeholder: "暂时只支持nginx类型",
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
isPrivateIpAddress: {
|
||||
title: "是否私网ip",
|
||||
value: false,
|
||||
component: {
|
||||
placeholder: "集群连接端点是否是私网ip",
|
||||
},
|
||||
helper: "如果您当前certd运行在同一个私网下,可以选择是。",
|
||||
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,
|
||||
},
|
||||
@IsTaskPlugin({
|
||||
name: "DeployCertToAliyunAckIngress",
|
||||
title: "部署到阿里云AckIngress",
|
||||
input: {},
|
||||
output: {},
|
||||
default: {
|
||||
strategy: {
|
||||
runStrategy: RunStrategy.SkipWhenSucceed,
|
||||
},
|
||||
output: {},
|
||||
default: {
|
||||
strategy: {
|
||||
runStrategy: RunStrategy.SkipWhenSucceed,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
})
|
||||
export class DeployCertToAliyunAckIngressPlugin extends AbstractPlugin implements TaskPlugin {
|
||||
async execute(input: TaskInput): Promise<TaskOutput> {
|
||||
export class DeployCertToAliyunAckIngressPlugin implements ITaskPlugin {
|
||||
@TaskInput({
|
||||
title: "集群id",
|
||||
component: {
|
||||
placeholder: "集群id",
|
||||
},
|
||||
})
|
||||
clusterId!: string;
|
||||
|
||||
@TaskInput({
|
||||
title: "保密字典Id",
|
||||
component: {
|
||||
placeholder: "保密字典Id",
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
secretName!: string | string[];
|
||||
|
||||
@TaskInput({
|
||||
title: "大区",
|
||||
value: "cn-shanghai",
|
||||
component: {
|
||||
placeholder: "集群所属大区",
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
regionId!: string;
|
||||
|
||||
@TaskInput({
|
||||
title: "命名空间",
|
||||
value: "default",
|
||||
component: {
|
||||
placeholder: "命名空间",
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
namespace!: string;
|
||||
@TaskInput({
|
||||
title: "ingress名称",
|
||||
value: "",
|
||||
component: {
|
||||
placeholder: "ingress名称",
|
||||
},
|
||||
required: true,
|
||||
helper: "可以传入一个数组",
|
||||
})
|
||||
ingressName!: string;
|
||||
@TaskInput({
|
||||
title: "ingress类型",
|
||||
value: "nginx",
|
||||
component: {
|
||||
placeholder: "暂时只支持nginx类型",
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
ingressClass!: string;
|
||||
@TaskInput({
|
||||
title: "是否私网ip",
|
||||
value: false,
|
||||
component: {
|
||||
placeholder: "集群连接端点是否是私网ip",
|
||||
},
|
||||
helper: "如果您当前certd运行在同一个私网下,可以选择是。",
|
||||
required: true,
|
||||
})
|
||||
isPrivateIpAddress!: boolean;
|
||||
@TaskInput({
|
||||
title: "域名证书",
|
||||
helper: "请选择前置任务输出的域名证书",
|
||||
component: {
|
||||
name: "pi-output-selector",
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
cert!: CertInfo;
|
||||
@TaskInput({
|
||||
title: "Access授权",
|
||||
helper: "阿里云授权AccessKeyId、AccessKeySecret",
|
||||
component: {
|
||||
name: "pi-access-selector",
|
||||
type: "aliyun",
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
accessId!: string;
|
||||
|
||||
@Autowire()
|
||||
accessService!: IAccessService;
|
||||
@Autowire()
|
||||
logger!: ILogger;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
async onInit(): Promise<void> {}
|
||||
async execute(): Promise<void> {
|
||||
console.log("开始部署证书到阿里云cdn");
|
||||
const { regionId, ingressClass, clusterId, isPrivateIpAddress, cert } = input;
|
||||
const access = (await this.accessService.getById(input.accessId)) as AliyunAccess;
|
||||
const { regionId, ingressClass, clusterId, isPrivateIpAddress, cert } = this;
|
||||
const access = (await this.accessService.getById(this.accessId)) as AliyunAccess;
|
||||
const client = this.getClient(access, regionId);
|
||||
const kubeConfigStr = await this.getKubeConfig(client, clusterId, isPrivateIpAddress);
|
||||
|
||||
|
@ -106,17 +124,16 @@ export class DeployCertToAliyunAckIngressPlugin extends AbstractPlugin implement
|
|||
throw new Error("暂未实现");
|
||||
// await this.patchQcloudCertSecret({ k8sClient, props, context })
|
||||
} else {
|
||||
await this.patchNginxCertSecret({ cert, k8sClient, input });
|
||||
await this.patchNginxCertSecret({ cert, k8sClient });
|
||||
}
|
||||
|
||||
await utils.sleep(3000); // 停留2秒,等待secret部署完成
|
||||
// await this.restartIngress({ k8sClient, props })
|
||||
return {};
|
||||
}
|
||||
|
||||
async restartIngress(options: { k8sClient: any; input: TaskInput }) {
|
||||
const { k8sClient, input } = options;
|
||||
const { namespace } = input;
|
||||
async restartIngress(options: { k8sClient: any }) {
|
||||
const { k8sClient } = options;
|
||||
const { namespace } = this;
|
||||
|
||||
const body = {
|
||||
metadata: {
|
||||
|
@ -136,7 +153,7 @@ export class DeployCertToAliyunAckIngressPlugin extends AbstractPlugin implement
|
|||
return false;
|
||||
}
|
||||
for (const tls of item.spec.tls) {
|
||||
if (tls.secretName === input.secretName) {
|
||||
if (tls.secretName === this.secretName) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -151,14 +168,14 @@ export class DeployCertToAliyunAckIngressPlugin extends AbstractPlugin implement
|
|||
}
|
||||
}
|
||||
|
||||
async patchNginxCertSecret(options: { cert: any; k8sClient: any; input: TaskInput }) {
|
||||
const { cert, k8sClient, input } = options;
|
||||
async patchNginxCertSecret(options: { cert: any; k8sClient: any }) {
|
||||
const { cert, k8sClient } = options;
|
||||
const crt = cert.crt;
|
||||
const key = cert.key;
|
||||
const crtBase64 = Buffer.from(crt).toString("base64");
|
||||
const keyBase64 = Buffer.from(key).toString("base64");
|
||||
|
||||
const { namespace, secretName } = input;
|
||||
const { namespace, secretName } = this;
|
||||
|
||||
const body = {
|
||||
data: {
|
||||
|
@ -171,7 +188,7 @@ export class DeployCertToAliyunAckIngressPlugin extends AbstractPlugin implement
|
|||
},
|
||||
},
|
||||
};
|
||||
let secretNames = secretName;
|
||||
let secretNames: any = secretName;
|
||||
if (typeof secretName === "string") {
|
||||
secretNames = [secretName];
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import { Autowire, IAccessService, IsTaskPlugin, ITaskPlugin, LOGGER, RunStrategy, TaskInput } from "@certd/pipeline";
|
||||
import { Autowire, IAccessService, ILogger, IsTaskPlugin, ITaskPlugin, RunStrategy, TaskInput } from "@certd/pipeline";
|
||||
import dayjs from "dayjs";
|
||||
import Core from "@alicloud/pop-core";
|
||||
import RPCClient from "@alicloud/pop-core";
|
||||
import { AliyunAccess } from "../../access";
|
||||
import { Inject } from "@midwayjs/core";
|
||||
|
||||
@IsTaskPlugin({
|
||||
name: "DeployCertToAliyunCDN",
|
||||
|
@ -50,11 +49,11 @@ export class DeployCertToAliyunCDN implements ITaskPlugin {
|
|||
})
|
||||
accessId!: string;
|
||||
|
||||
@Inject()
|
||||
@Autowire()
|
||||
accessService!: IAccessService;
|
||||
|
||||
@Autowire()
|
||||
logger!: LOGGER;
|
||||
logger!: ILogger;
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
async onInit() {}
|
||||
async execute(): Promise<void> {
|
||||
|
|
|
@ -17,7 +17,6 @@ import { Logger } from "log4js";
|
|||
export class UploadCertToAliyun implements ITaskPlugin {
|
||||
@TaskInput({
|
||||
title: "证书名称",
|
||||
|
||||
helper: "证书上传后将以此参数作为名称前缀",
|
||||
})
|
||||
name!: string;
|
||||
|
@ -66,6 +65,9 @@ export class UploadCertToAliyun implements ITaskPlugin {
|
|||
@Autowire()
|
||||
logger!: Logger;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
async onInit() {}
|
||||
|
||||
async execute(): Promise<void> {
|
||||
console.log("开始部署证书到阿里云cdn");
|
||||
const access = (await this.accessService.getById(this.accessId)) as AliyunAccess;
|
||||
|
|
|
@ -1,199 +0,0 @@
|
|||
import { AbstractAliyunPlugin } from '../abstract-aliyun.js'
|
||||
import Core from '@alicloud/pop-core'
|
||||
import { K8sClient } from '@certd/plugin-common'
|
||||
const ROAClient = Core.ROAClient
|
||||
|
||||
const define = {
|
||||
name: 'deployCertToAliyunAckIngress',
|
||||
title: '部署到阿里云AckIngress',
|
||||
input: {
|
||||
clusterId: {
|
||||
title: '集群id',
|
||||
component: {
|
||||
placeholder: '集群id'
|
||||
}
|
||||
},
|
||||
secretName: {
|
||||
title: '保密字典Id',
|
||||
component: {
|
||||
placeholder: '保密字典Id'
|
||||
},
|
||||
required: true
|
||||
},
|
||||
regionId: {
|
||||
title: '大区',
|
||||
value: 'cn-shanghai',
|
||||
component: {
|
||||
placeholder: '集群所属大区'
|
||||
},
|
||||
required: true
|
||||
},
|
||||
namespace: {
|
||||
title: '命名空间',
|
||||
value: 'default',
|
||||
component: {
|
||||
placeholder: '命名空间'
|
||||
},
|
||||
required: true
|
||||
},
|
||||
ingressName: {
|
||||
title: 'ingress名称',
|
||||
value: '',
|
||||
component: {
|
||||
placeholder: 'ingress名称'
|
||||
},
|
||||
required: true,
|
||||
helper: '可以传入一个数组'
|
||||
},
|
||||
ingressClass: {
|
||||
title: 'ingress类型',
|
||||
value: 'nginx',
|
||||
component: {
|
||||
placeholder: '暂时只支持nginx类型'
|
||||
},
|
||||
required: true
|
||||
},
|
||||
isPrivateIpAddress: {
|
||||
title: '是否私网ip',
|
||||
value: false,
|
||||
component: {
|
||||
placeholder: '集群连接端点是否是私网ip'
|
||||
},
|
||||
helper: '如果您当前certd运行在同一个私网下,可以选择是。',
|
||||
required: true
|
||||
},
|
||||
accessProvider: {
|
||||
title: 'Access授权',
|
||||
type: [String, Object],
|
||||
helper: 'AccessKey、AccessSecret',
|
||||
component: {
|
||||
name: 'access-selector',
|
||||
type: 'aliyun'
|
||||
},
|
||||
required: true
|
||||
}
|
||||
},
|
||||
output: {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export class DeployCertToAliyunAckIngress extends AbstractAliyunPlugin {
|
||||
static define () {
|
||||
return define
|
||||
}
|
||||
|
||||
async execute ({ cert, props, context }) {
|
||||
const accessProvider = this.getAccessProvider(props.accessProvider)
|
||||
const client = this.getClient(accessProvider, props.regionId)
|
||||
|
||||
const kubeConfigStr = await this.getKubeConfig(client, props.clusterId, props.isPrivateIpAddress)
|
||||
|
||||
this.logger.info('kubeconfig已成功获取')
|
||||
const k8sClient = new K8sClient(kubeConfigStr)
|
||||
const ingressType = props.ingressClass || 'qcloud'
|
||||
if (ingressType === 'qcloud') {
|
||||
throw new Error('暂未实现')
|
||||
// await this.patchQcloudCertSecret({ k8sClient, props, context })
|
||||
} else {
|
||||
await this.patchNginxCertSecret({ cert, k8sClient, props, context })
|
||||
}
|
||||
|
||||
await this.sleep(3000) // 停留2秒,等待secret部署完成
|
||||
// await this.restartIngress({ k8sClient, props })
|
||||
return true
|
||||
}
|
||||
|
||||
async restartIngress ({ k8sClient, props }) {
|
||||
const { namespace } = props
|
||||
|
||||
const body = {
|
||||
metadata: {
|
||||
labels: {
|
||||
certd: this.appendTimeSuffix('certd')
|
||||
}
|
||||
}
|
||||
}
|
||||
const ingressList = await k8sClient.getIngressList({ namespace })
|
||||
console.log('ingressList:', ingressList)
|
||||
if (!ingressList || !ingressList.body || !ingressList.body.items) {
|
||||
return
|
||||
}
|
||||
const ingressNames = ingressList.body.items.filter(item => {
|
||||
if (!item.spec.tls) {
|
||||
return false
|
||||
}
|
||||
for (const tls of item.spec.tls) {
|
||||
if (tls.secretName === props.secretName) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}).map(item => {
|
||||
return item.metadata.name
|
||||
})
|
||||
for (const ingress of ingressNames) {
|
||||
await k8sClient.patchIngress({ namespace, ingressName: ingress, body })
|
||||
this.logger.info(`ingress已重启:${ingress}`)
|
||||
}
|
||||
}
|
||||
|
||||
async patchNginxCertSecret ({ cert, k8sClient, props, context }) {
|
||||
const crt = cert.crt
|
||||
const key = cert.key
|
||||
const crtBase64 = Buffer.from(crt).toString('base64')
|
||||
const keyBase64 = Buffer.from(key).toString('base64')
|
||||
|
||||
const { namespace, secretName } = props
|
||||
|
||||
const body = {
|
||||
data: {
|
||||
'tls.crt': crtBase64,
|
||||
'tls.key': keyBase64
|
||||
},
|
||||
metadata: {
|
||||
labels: {
|
||||
certd: this.appendTimeSuffix('certd')
|
||||
}
|
||||
}
|
||||
}
|
||||
let secretNames = secretName
|
||||
if (typeof secretName === 'string') {
|
||||
secretNames = [secretName]
|
||||
}
|
||||
for (const secret of secretNames) {
|
||||
await k8sClient.patchSecret({ namespace, secretName: secret, body })
|
||||
this.logger.info(`CertSecret已更新:${secret}`)
|
||||
}
|
||||
}
|
||||
|
||||
getClient (aliyunProvider, regionId) {
|
||||
return new ROAClient({
|
||||
accessKeyId: aliyunProvider.accessKeyId,
|
||||
accessKeySecret: aliyunProvider.accessKeySecret,
|
||||
endpoint: `https://cs.${regionId}.aliyuncs.com`,
|
||||
apiVersion: '2015-12-15'
|
||||
})
|
||||
}
|
||||
|
||||
async getKubeConfig (client, clusterId, isPrivateIpAddress = false) {
|
||||
const httpMethod = 'GET'
|
||||
const uriPath = `/k8s/${clusterId}/user_config`
|
||||
const queries = {
|
||||
PrivateIpAddress: isPrivateIpAddress
|
||||
}
|
||||
const body = '{}'
|
||||
const headers = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
const requestOption = {}
|
||||
|
||||
try {
|
||||
const res = await client.request(httpMethod, uriPath, queries, body, headers, requestOption)
|
||||
return res.config
|
||||
} catch (e) {
|
||||
console.error('请求出错:', e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,106 +0,0 @@
|
|||
import Core from '@alicloud/pop-core'
|
||||
import { AbstractAliyunPlugin } from '../abstract-aliyun.js'
|
||||
import { ZoneOptions } from '../../utils/index.js'
|
||||
|
||||
const define = {
|
||||
name: 'uploadCertToAliyun',
|
||||
title: '上传证书到阿里云',
|
||||
desc: '',
|
||||
input: {
|
||||
name: {
|
||||
title: '证书名称',
|
||||
helper: '证书上传后将以此参数作为名称前缀'
|
||||
},
|
||||
regionId: {
|
||||
title: '大区',
|
||||
value: 'cn-hangzhou',
|
||||
component: {
|
||||
name: 'a-select',
|
||||
vModel: 'value',
|
||||
options: ZoneOptions
|
||||
},
|
||||
required: true
|
||||
},
|
||||
accessProvider: {
|
||||
title: 'Access授权',
|
||||
helper: 'Access授权',
|
||||
component: {
|
||||
name: 'access-selector',
|
||||
type: 'aliyun'
|
||||
},
|
||||
required: true
|
||||
}
|
||||
},
|
||||
output: {
|
||||
aliyunCertId: {
|
||||
type: String,
|
||||
desc: '上传成功后的阿里云CertId'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class UploadCertToAliyun extends AbstractAliyunPlugin {
|
||||
static define () {
|
||||
return define
|
||||
}
|
||||
|
||||
getClient (aliyunProvider) {
|
||||
return new Core({
|
||||
accessKeyId: aliyunProvider.accessKeyId,
|
||||
accessKeySecret: aliyunProvider.accessKeySecret,
|
||||
endpoint: 'https://cas.aliyuncs.com',
|
||||
apiVersion: '2018-07-13'
|
||||
})
|
||||
}
|
||||
|
||||
async execute ({ cert, props, context }) {
|
||||
const { name, accessProvider } = props
|
||||
const certName = this.appendTimeSuffix(name || cert.domain)
|
||||
const params = {
|
||||
RegionId: props.regionId || 'cn-hangzhou',
|
||||
Name: certName,
|
||||
Cert: cert.crt,
|
||||
Key: cert.key
|
||||
}
|
||||
|
||||
const requestOption = {
|
||||
method: 'POST'
|
||||
}
|
||||
|
||||
const provider = this.getAccessProvider(accessProvider)
|
||||
const client = this.getClient(provider)
|
||||
const ret = await client.request('CreateUserCertificate', params, requestOption)
|
||||
this.checkRet(ret)
|
||||
this.logger.info('证书上传成功:aliyunCertId=', ret.CertId)
|
||||
context.aliyunCertId = ret.CertId
|
||||
}
|
||||
|
||||
/**
|
||||
* 没用,现在阿里云证书不允许删除
|
||||
* @param accessProviders
|
||||
* @param cert
|
||||
* @param props
|
||||
* @param context
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async rollback ({ cert, props, context }) {
|
||||
const { accessProvider } = props
|
||||
const { aliyunCertId } = context
|
||||
this.logger.info('准备删除阿里云证书:', aliyunCertId)
|
||||
const params = {
|
||||
RegionId: props.regionId || 'cn-hangzhou',
|
||||
CertId: aliyunCertId
|
||||
}
|
||||
|
||||
const requestOption = {
|
||||
method: 'POST'
|
||||
}
|
||||
|
||||
const provider = this.getAccessProvider(accessProvider)
|
||||
const client = this.getClient(provider)
|
||||
const ret = await client.request('DeleteUserCertificate', params, requestOption)
|
||||
this.checkRet(ret)
|
||||
this.logger.info('证书删除成功:', aliyunCertId)
|
||||
delete context.aliyunCertId
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@
|
|||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/ban-ts-ignore": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
// "no-unused-expressions": "off",
|
||||
"max-len": [0, 160, 2, { "ignoreUrls": true }]
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// export * from "@certd/plugin-aliyun";
|
||||
export * from "@certd/plugin-cert";
|
||||
export * from "@certd/plugin-aliyun";
|
||||
export * from "@certd/plugin-tencent";
|
||||
// export * from "@certd/plugin-host";
|
||||
export * from "@certd/plugin-host";
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/ban-ts-ignore": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
// "no-unused-expressions": "off",
|
||||
"max-len": [0, 160, 2, { "ignoreUrls": true }]
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import { Registrable } from "../registry";
|
||||
import { Registrable } from "@certd/pipeline";
|
||||
|
||||
export type DnsProviderDefine = Registrable & {
|
||||
accessType: string;
|
||||
autowire?: {
|
||||
[key: string]: any;
|
||||
};
|
||||
};
|
||||
|
||||
export type CreateRecordOptions = {
|
||||
|
@ -14,6 +17,7 @@ export type RemoveRecordOptions = CreateRecordOptions & {
|
|||
};
|
||||
|
||||
export interface IDnsProvider {
|
||||
onInit(): Promise<void>;
|
||||
createRecord(options: CreateRecordOptions): Promise<any>;
|
||||
removeRecord(options: RemoveRecordOptions): Promise<any>;
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
// src/decorator/memoryCache.decorator.ts
|
||||
import { dnsProviderRegistry } from "./registry";
|
||||
import { DnsProviderDefine } from "./api";
|
||||
import { Decorator } from "../decorator";
|
||||
import { Decorator, AUTOWIRE_KEY } from "@certd/pipeline";
|
||||
import _ from "lodash";
|
||||
|
||||
// 提供一个唯一 key
|
||||
export const DNS_PROVIDER_CLASS_KEY = "pipeline:dns-provider";
|
||||
|
@ -9,6 +10,16 @@ export const DNS_PROVIDER_CLASS_KEY = "pipeline:dns-provider";
|
|||
export function IsDnsProvider(define: DnsProviderDefine): ClassDecorator {
|
||||
return (target: any) => {
|
||||
target = Decorator.target(target);
|
||||
const autowires: any = {};
|
||||
const properties = Decorator.getClassProperties(target);
|
||||
for (const property in properties) {
|
||||
const autowire = Reflect.getMetadata(AUTOWIRE_KEY, target, property);
|
||||
if (autowire) {
|
||||
autowires[property] = autowire;
|
||||
}
|
||||
}
|
||||
_.merge(define, { autowire: autowires });
|
||||
|
||||
Reflect.defineMetadata(DNS_PROVIDER_CLASS_KEY, define, target);
|
||||
|
||||
target.define = define;
|
|
@ -1,4 +1,4 @@
|
|||
import { Registry } from "../registry";
|
||||
import { Registry } from "@certd/pipeline";
|
||||
|
||||
// @ts-ignore
|
||||
export const dnsProviderRegistry = new Registry();
|
|
@ -1 +1,2 @@
|
|||
export * from "./plugin";
|
||||
export * from "./dns-provider";
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
// @ts-ignore
|
||||
import * as acme from "@certd/acme-client";
|
||||
import _ from "lodash";
|
||||
import { IDnsProvider } from "@certd/pipeline";
|
||||
import { Challenge } from "@certd/acme-client/types/rfc8555";
|
||||
import { Logger } from "log4js";
|
||||
import { IContext } from "@certd/pipeline/src/core/context";
|
||||
import { IDnsProvider } from "../../dns-provider";
|
||||
|
||||
export class AcmeService {
|
||||
userContext: IContext;
|
||||
|
|
|
@ -1,26 +1,46 @@
|
|||
import {
|
||||
Autowire,
|
||||
dnsProviderRegistry,
|
||||
HttpClient,
|
||||
IAccessService,
|
||||
IContext,
|
||||
IsTaskPlugin,
|
||||
ITaskPlugin,
|
||||
RunStrategy,
|
||||
TaskInput,
|
||||
TaskOutput,
|
||||
} from "@certd/pipeline";
|
||||
import { Autowire, HttpClient, IAccessService, IContext, IsTaskPlugin, ITaskPlugin, RunStrategy, TaskInput, TaskOutput } from "@certd/pipeline";
|
||||
import forge from "node-forge";
|
||||
import dayjs from "dayjs";
|
||||
import { AcmeService } from "./acme";
|
||||
import _ from "lodash";
|
||||
import { Logger } from "log4js";
|
||||
|
||||
export type CertInfo = {
|
||||
import { Decorator } from "@certd/pipeline/src/decorator";
|
||||
import { DnsProviderDefine, dnsProviderRegistry } from "../../dns-provider";
|
||||
import fs from "fs";
|
||||
import os from "os";
|
||||
export class CertInfo {
|
||||
crt: string;
|
||||
key: string;
|
||||
csr: string;
|
||||
};
|
||||
|
||||
detail: any;
|
||||
expires: number;
|
||||
constructor(opts: { crt: string; key: string; csr: string }) {
|
||||
this.crt = opts.crt;
|
||||
this.key = opts.key;
|
||||
this.csr = opts.csr;
|
||||
|
||||
const { detail, expires } = this.getCrtDetail(this.crt);
|
||||
this.detail = detail;
|
||||
this.expires = expires.getTime();
|
||||
}
|
||||
|
||||
getCrtDetail(crt: string) {
|
||||
const pki = forge.pki;
|
||||
const detail = pki.certificateFromPem(crt.toString());
|
||||
const expires = detail.validity.notAfter;
|
||||
return { detail, expires };
|
||||
}
|
||||
|
||||
saveToFile(type: "crt" | "key", path?: string) {
|
||||
if (path == null) {
|
||||
//写入临时目录
|
||||
path = `${os.tmpdir()}/certd/tmp/${Math.floor(Math.random() * 1000000)}/cert.${type}`;
|
||||
}
|
||||
fs.writeFileSync(path, this[type]);
|
||||
return path;
|
||||
}
|
||||
}
|
||||
@IsTaskPlugin({
|
||||
name: "CertApply",
|
||||
title: "证书申请",
|
||||
|
@ -199,11 +219,16 @@ export class CertApplyPlugin implements ITaskPlugin {
|
|||
);
|
||||
this.logger.info("开始申请证书,", email, domains);
|
||||
|
||||
const dnsProviderClass = dnsProviderRegistry.get(dnsProviderType);
|
||||
const dnsProviderPlugin = dnsProviderRegistry.get(dnsProviderType);
|
||||
const DnsProviderClass = dnsProviderPlugin.target;
|
||||
const dnsProviderDefine = dnsProviderPlugin.define as DnsProviderDefine;
|
||||
const access = await this.accessService.getById(dnsProviderAccessId);
|
||||
|
||||
// @ts-ignore
|
||||
const dnsProvider: AbstractDnsProvider = new dnsProviderClass();
|
||||
dnsProvider.doInit({ access, logger: this.logger, http: this.http });
|
||||
const dnsProvider: IDnsProvider = new DnsProviderClass();
|
||||
const context = { access, logger: this.logger, http: this.http };
|
||||
Decorator.inject(dnsProviderDefine.autowire, dnsProvider, context);
|
||||
await dnsProvider.onInit();
|
||||
|
||||
const cert = await this.acme.order({
|
||||
email,
|
||||
|
@ -239,23 +264,11 @@ export class CertApplyPlugin implements ITaskPlugin {
|
|||
}
|
||||
|
||||
async readCurrentCert() {
|
||||
const cert: CertInfo = await this.pipelineContext.get("cert");
|
||||
const cert: any = await this.pipelineContext.get("cert");
|
||||
if (cert == null) {
|
||||
return undefined;
|
||||
}
|
||||
const { detail, expires } = this.getCrtDetail(cert.crt);
|
||||
return {
|
||||
...cert,
|
||||
detail,
|
||||
expires: expires.getTime(),
|
||||
};
|
||||
}
|
||||
|
||||
getCrtDetail(crt: string) {
|
||||
const pki = forge.pki;
|
||||
const detail = pki.certificateFromPem(crt.toString());
|
||||
const expires = detail.validity.notAfter;
|
||||
return { detail, expires };
|
||||
return new CertInfo(cert);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/ban-ts-ignore": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
// "no-unused-expressions": "off",
|
||||
"max-len": [0, 160, 2, { "ignoreUrls": true }]
|
||||
}
|
||||
|
|
|
@ -15,31 +15,31 @@
|
|||
"ssh2": "^0.8.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"log4js": "^6.7.1",
|
||||
"dayjs": "^1.9.7",
|
||||
"lodash-es": "^4.17.20",
|
||||
"@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/core": "^3.0.0",
|
||||
"@midwayjs/decorator": "^3.0.0",
|
||||
"@types/chai": "^4.3.3",
|
||||
"@types/lodash": "^4.14.186",
|
||||
"@types/mocha": "^10.0.0",
|
||||
"@types/node-forge": "^1.3.0",
|
||||
"@types/ssh2": "^1.11.6",
|
||||
"@typescript-eslint/eslint-plugin": "^5.38.1",
|
||||
"@typescript-eslint/parser": "^5.38.1",
|
||||
"chai": "^4.3.6",
|
||||
"dayjs": "^1.9.7",
|
||||
"eslint": "^8.24.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"lodash-es": "^4.17.20",
|
||||
"log4js": "^6.3.0",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,48 +1,47 @@
|
|||
import { AbstractAccess, IsAccess } from "@certd/pipeline";
|
||||
import { AccessInput, IAccess, IsAccess } from "@certd/pipeline";
|
||||
|
||||
@IsAccess({
|
||||
name: "ssh",
|
||||
title: "主机登录授权",
|
||||
desc: "",
|
||||
input: {
|
||||
host: {
|
||||
title: "主机地址",
|
||||
component: {
|
||||
placeholder: "主机域名或IP地址",
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
port: {
|
||||
title: "端口",
|
||||
value: "22",
|
||||
component: {
|
||||
placeholder: "22",
|
||||
},
|
||||
rules: [{ required: true, message: "此项必填" }],
|
||||
},
|
||||
username: {
|
||||
title: "用户名",
|
||||
value: "root",
|
||||
rules: [{ required: true, message: "此项必填" }],
|
||||
},
|
||||
password: {
|
||||
title: "密码",
|
||||
component: {
|
||||
name: "a-input-password",
|
||||
vModel: "value",
|
||||
},
|
||||
helper: "登录密码或密钥必填一项",
|
||||
},
|
||||
privateKey: {
|
||||
title: "密钥",
|
||||
helper: "密钥或密码必填一项",
|
||||
},
|
||||
},
|
||||
input: {},
|
||||
})
|
||||
export class SshAccess extends AbstractAccess {
|
||||
host = "";
|
||||
port = 22;
|
||||
username = "root";
|
||||
password?: string;
|
||||
privateKey?: string;
|
||||
export class SshAccess implements IAccess {
|
||||
@AccessInput({
|
||||
title: "主机地址",
|
||||
component: {
|
||||
placeholder: "主机域名或IP地址",
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
host!: string;
|
||||
@AccessInput({
|
||||
title: "端口",
|
||||
value: "22",
|
||||
component: {
|
||||
placeholder: "22",
|
||||
},
|
||||
rules: [{ required: true, message: "此项必填" }],
|
||||
})
|
||||
port!: string;
|
||||
@AccessInput({
|
||||
title: "用户名",
|
||||
value: "root",
|
||||
rules: [{ required: true, message: "此项必填" }],
|
||||
})
|
||||
username!: string;
|
||||
@AccessInput({
|
||||
title: "密码",
|
||||
component: {
|
||||
name: "a-input-password",
|
||||
vModel: "value",
|
||||
},
|
||||
helper: "登录密码或密钥必填一项",
|
||||
})
|
||||
password!: string;
|
||||
@AccessInput({
|
||||
title: "密钥",
|
||||
helper: "密钥或密码必填一项",
|
||||
})
|
||||
privateKey!: string;
|
||||
}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
// @ts-ignore
|
||||
import ssh2 from "ssh2";
|
||||
import path from "path";
|
||||
import _ from "lodash";
|
||||
import { Logger } from "log4js";
|
||||
import { ILogger } from "@certd/pipeline";
|
||||
export class SshClient {
|
||||
logger: Logger;
|
||||
constructor(logger: Logger) {
|
||||
logger: ILogger;
|
||||
constructor(logger: ILogger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
/**
|
||||
|
|
|
@ -1,47 +1,56 @@
|
|||
import { IsTask, TaskInput, TaskOutput, TaskPlugin, AbstractPlugin, RunStrategy } from "@certd/pipeline";
|
||||
import { Autowire, IAccessService, IsTaskPlugin, ILogger, RunStrategy, TaskInput, ITaskPlugin } from "@certd/pipeline";
|
||||
import { SshClient } from "../../lib/ssh";
|
||||
import { CertInfo } from "@certd/plugin-cert";
|
||||
|
||||
@IsTask(() => {
|
||||
return {
|
||||
name: "hostShellExecute",
|
||||
title: "执行远程主机脚本命令",
|
||||
input: {
|
||||
accessId: {
|
||||
title: "主机登录配置",
|
||||
helper: "登录",
|
||||
component: {
|
||||
name: "pi-access-selector",
|
||||
type: "ssh",
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
cert: {
|
||||
title: "域名证书",
|
||||
helper: "请选择前置任务输出的域名证书",
|
||||
component: {
|
||||
name: "pi-output-selector",
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
script: {
|
||||
title: "shell脚本命令",
|
||||
component: {
|
||||
name: "a-textarea",
|
||||
vModel: "value",
|
||||
},
|
||||
},
|
||||
@IsTaskPlugin({
|
||||
name: "hostShellExecute",
|
||||
title: "执行远程主机脚本命令",
|
||||
input: {},
|
||||
default: {
|
||||
strategy: {
|
||||
runStrategy: RunStrategy.SkipWhenSucceed,
|
||||
},
|
||||
default: {
|
||||
strategy: {
|
||||
runStrategy: RunStrategy.SkipWhenSucceed,
|
||||
},
|
||||
},
|
||||
output: {},
|
||||
};
|
||||
},
|
||||
output: {},
|
||||
})
|
||||
export class HostShellExecutePlugin extends AbstractPlugin implements TaskPlugin {
|
||||
async execute(input: TaskInput): Promise<TaskOutput> {
|
||||
const { script, accessId } = input;
|
||||
export class HostShellExecutePlugin implements ITaskPlugin {
|
||||
@TaskInput({
|
||||
title: "主机登录配置",
|
||||
helper: "登录",
|
||||
component: {
|
||||
name: "pi-access-selector",
|
||||
type: "ssh",
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
accessId!: string;
|
||||
@TaskInput({
|
||||
title: "域名证书",
|
||||
helper: "请选择前置任务输出的域名证书",
|
||||
component: {
|
||||
name: "pi-output-selector",
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
cert!: CertInfo;
|
||||
@TaskInput({
|
||||
title: "shell脚本命令",
|
||||
component: {
|
||||
name: "a-textarea",
|
||||
vModel: "value",
|
||||
},
|
||||
})
|
||||
script!: string;
|
||||
|
||||
@Autowire()
|
||||
accessService!: IAccessService;
|
||||
@Autowire()
|
||||
logger!: ILogger;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
async onInit() {}
|
||||
async execute(): Promise<void> {
|
||||
const { script, accessId } = this;
|
||||
const connectConf = this.accessService.getById(accessId);
|
||||
const sshClient = new SshClient(this.logger);
|
||||
const ret = await sshClient.exec({
|
||||
|
@ -49,6 +58,5 @@ export class HostShellExecutePlugin extends AbstractPlugin implements TaskPlugin
|
|||
script,
|
||||
});
|
||||
this.logger.info("exec res:", ret);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,71 +1,87 @@
|
|||
import { IsTask, TaskInput, TaskOutput, TaskPlugin, AbstractPlugin, RunStrategy } from "@certd/pipeline";
|
||||
import { Autowire, IAccessService, IsTaskPlugin, ITaskPlugin, ILogger, RunStrategy, TaskInput, TaskOutput } from "@certd/pipeline";
|
||||
import { SshClient } from "../../lib/ssh";
|
||||
import { CertInfo } from "@certd/plugin-cert";
|
||||
import * as fs from "fs";
|
||||
|
||||
@IsTask(() => {
|
||||
return {
|
||||
name: "uploadCertToHost",
|
||||
title: "上传证书到主机",
|
||||
input: {
|
||||
crtPath: {
|
||||
title: "证书保存路径",
|
||||
},
|
||||
keyPath: {
|
||||
title: "私钥保存路径",
|
||||
},
|
||||
cert: {
|
||||
title: "域名证书",
|
||||
helper: "请选择前置任务输出的域名证书",
|
||||
component: {
|
||||
name: "pi-output-selector",
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
accessId: {
|
||||
title: "主机登录配置",
|
||||
helper: "access授权",
|
||||
component: {
|
||||
name: "pi-access-selector",
|
||||
type: "ssh",
|
||||
},
|
||||
rules: [{ required: true, message: "此项必填" }],
|
||||
},
|
||||
sudo: {
|
||||
title: "是否sudo",
|
||||
component: {
|
||||
name: "a-checkbox",
|
||||
vModel: "checked",
|
||||
},
|
||||
},
|
||||
@IsTaskPlugin({
|
||||
name: "uploadCertToHost",
|
||||
title: "上传证书到主机",
|
||||
default: {
|
||||
strategy: {
|
||||
runStrategy: RunStrategy.SkipWhenSucceed,
|
||||
},
|
||||
default: {
|
||||
strategy: {
|
||||
runStrategy: RunStrategy.SkipWhenSucceed,
|
||||
},
|
||||
},
|
||||
output: {
|
||||
hostCrtPath: {
|
||||
title: "上传成功后的证书路径",
|
||||
},
|
||||
hostKeyPath: {
|
||||
title: "上传成功后的私钥路径",
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
})
|
||||
export class UploadCertToHostPlugin extends AbstractPlugin implements TaskPlugin {
|
||||
async execute(input: TaskInput): Promise<TaskOutput> {
|
||||
const { crtPath, keyPath, cert, accessId, sudo } = input;
|
||||
export class UploadCertToHostPlugin implements ITaskPlugin {
|
||||
@TaskInput({
|
||||
title: "证书保存路径",
|
||||
})
|
||||
crtPath!: string;
|
||||
@TaskInput({
|
||||
title: "私钥保存路径",
|
||||
})
|
||||
keyPath!: string;
|
||||
@TaskInput({
|
||||
title: "域名证书",
|
||||
helper: "请选择前置任务输出的域名证书",
|
||||
component: {
|
||||
name: "pi-output-selector",
|
||||
},
|
||||
required: true,
|
||||
})
|
||||
cert!: CertInfo;
|
||||
@TaskInput({
|
||||
title: "主机登录配置",
|
||||
helper: "access授权",
|
||||
component: {
|
||||
name: "pi-access-selector",
|
||||
type: "ssh",
|
||||
},
|
||||
rules: [{ required: true, message: "此项必填" }],
|
||||
})
|
||||
accessId!: string;
|
||||
@TaskInput({
|
||||
title: "是否sudo",
|
||||
component: {
|
||||
name: "a-checkbox",
|
||||
vModel: "checked",
|
||||
},
|
||||
})
|
||||
sudo!: boolean;
|
||||
|
||||
@Autowire()
|
||||
accessService!: IAccessService;
|
||||
@Autowire()
|
||||
logger!: ILogger;
|
||||
|
||||
@TaskOutput({
|
||||
title: "证书保存路径",
|
||||
})
|
||||
hostCrtPath!: string;
|
||||
|
||||
@TaskOutput({
|
||||
title: "私钥保存路径",
|
||||
})
|
||||
hostKeyPath!: string;
|
||||
|
||||
async onInit() {}
|
||||
async execute(): Promise<void> {
|
||||
const { crtPath, keyPath, cert, accessId, sudo } = this;
|
||||
const connectConf = this.accessService.getById(accessId);
|
||||
const sshClient = new SshClient(this.logger);
|
||||
|
||||
const saveCrtPath = cert.saveToFile("crt");
|
||||
const saveKeyPath = cert.saveToFile("key");
|
||||
|
||||
await sshClient.uploadFiles({
|
||||
connectConf,
|
||||
transports: [
|
||||
{
|
||||
localPath: cert.crtPath,
|
||||
localPath: saveCrtPath,
|
||||
remotePath: crtPath,
|
||||
},
|
||||
{
|
||||
localPath: cert.keyPath,
|
||||
localPath: saveKeyPath,
|
||||
remotePath: keyPath,
|
||||
},
|
||||
],
|
||||
|
@ -73,9 +89,12 @@ export class UploadCertToHostPlugin extends AbstractPlugin implements TaskPlugin
|
|||
});
|
||||
this.logger.info("证书上传成功:crtPath=", crtPath, ",keyPath=", keyPath);
|
||||
|
||||
return {
|
||||
hostCrtPath: crtPath,
|
||||
hostKeyPath: keyPath,
|
||||
};
|
||||
//删除临时文件
|
||||
fs.unlinkSync(saveCrtPath);
|
||||
fs.unlinkSync(saveKeyPath);
|
||||
|
||||
//输出
|
||||
this.hostCrtPath = crtPath;
|
||||
this.hostKeyPath = keyPath;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/ban-ts-ignore": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
// "no-unused-expressions": "off",
|
||||
"max-len": [0, 160, 2, { "ignoreUrls": true }]
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"dependencies": {
|
||||
"@certd/pipeline": "^0.3.0",
|
||||
"@certd/plugin-util": "^0.3.0",
|
||||
"@certd/plugin-cert": "^0.3.0",
|
||||
"tencentcloud-sdk-nodejs": "^4.0.44"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { IsAccess, IsAccessInput } from "@certd/pipeline";
|
||||
import { IsAccess, AccessInput } from "@certd/pipeline";
|
||||
|
||||
@IsAccess({
|
||||
name: "dnspod",
|
||||
|
@ -6,7 +6,7 @@ import { IsAccess, IsAccessInput } from "@certd/pipeline";
|
|||
desc: "腾讯云的域名解析接口已迁移到dnspod",
|
||||
})
|
||||
export class DnspodAccess {
|
||||
@IsAccessInput({
|
||||
@AccessInput({
|
||||
title: "token",
|
||||
component: {
|
||||
placeholder: "开放接口token",
|
||||
|
@ -14,7 +14,7 @@ export class DnspodAccess {
|
|||
rules: [{ required: true, message: "该项必填" }],
|
||||
})
|
||||
token = "";
|
||||
@IsAccessInput({
|
||||
@AccessInput({
|
||||
title: "账户id",
|
||||
component: {
|
||||
placeholder: "dnspod接口账户id",
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { IsAccess, IsAccessInput } from "@certd/pipeline";
|
||||
import { IsAccess, AccessInput } from "@certd/pipeline";
|
||||
|
||||
@IsAccess({
|
||||
name: "tencent",
|
||||
title: "腾讯云",
|
||||
})
|
||||
export class TencentAccess {
|
||||
@IsAccessInput({
|
||||
@AccessInput({
|
||||
title: "secretId",
|
||||
component: {
|
||||
placeholder: "secretId",
|
||||
|
@ -13,7 +13,7 @@ export class TencentAccess {
|
|||
rules: [{ required: true, message: "该项必填" }],
|
||||
})
|
||||
secretId = "";
|
||||
@IsAccessInput({
|
||||
@AccessInput({
|
||||
title: "secretKey",
|
||||
component: {
|
||||
placeholder: "secretKey",
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import { CreateRecordOptions, HttpClient, IDnsProvider, IsDnsProvider, RemoveRecordOptions } from "@certd/pipeline";
|
||||
import { Autowire, HttpClient, ILogger } from "@certd/pipeline";
|
||||
import { CreateRecordOptions, IDnsProvider, IsDnsProvider, RemoveRecordOptions } from "@certd/plugin-cert";
|
||||
import _ from "lodash";
|
||||
import { DnspodAccess } from "../access";
|
||||
import { Inject } from "@midwayjs/decorator";
|
||||
import { ILogger } from "@midwayjs/core";
|
||||
|
||||
@IsDnsProvider({
|
||||
name: "dnspod",
|
||||
|
@ -11,12 +10,12 @@ import { ILogger } from "@midwayjs/core";
|
|||
accessType: "dnspod",
|
||||
})
|
||||
export class DnspodDnsProvider implements IDnsProvider {
|
||||
@Inject()
|
||||
@Autowire()
|
||||
http!: HttpClient;
|
||||
|
||||
@Inject()
|
||||
@Autowire()
|
||||
access!: DnspodAccess;
|
||||
@Inject()
|
||||
@Autowire()
|
||||
logger!: ILogger;
|
||||
|
||||
loginToken: any;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { IAccessService, IsTaskPlugin, RunStrategy, TaskInput, ITaskPlugin, LOGGER } from "@certd/pipeline";
|
||||
import { Autowire, IAccessService, IsTaskPlugin, ITaskPlugin, ILogger, RunStrategy, TaskInput } from "@certd/pipeline";
|
||||
import tencentcloud from "tencentcloud-sdk-nodejs/index";
|
||||
import { TencentAccess } from "../../access";
|
||||
import { Inject } from "@midwayjs/decorator";
|
||||
import { CertInfo } from "@certd/plugin-cert";
|
||||
|
||||
@IsTaskPlugin({
|
||||
name: "DeployCertToTencentCDN",
|
||||
|
@ -22,7 +22,7 @@ export class DeployToCdnPlugin implements ITaskPlugin {
|
|||
},
|
||||
required: true,
|
||||
})
|
||||
cert!: any;
|
||||
cert!: CertInfo;
|
||||
|
||||
@TaskInput({
|
||||
title: "Access提供者",
|
||||
|
@ -47,11 +47,11 @@ export class DeployToCdnPlugin implements ITaskPlugin {
|
|||
})
|
||||
domainName!: string;
|
||||
|
||||
@Inject()
|
||||
@Autowire()
|
||||
accessService!: IAccessService;
|
||||
|
||||
@Inject()
|
||||
logger!: LOGGER;
|
||||
@Autowire()
|
||||
logger!: ILogger;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
async onInit() {}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import { IAccessService, IsTaskPlugin, ITaskPlugin, LOGGER, RunStrategy, TaskInput, utils } from "@certd/pipeline";
|
||||
import { Autowire, IAccessService, IsTaskPlugin, ITaskPlugin, ILogger, 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";
|
||||
|
||||
@IsTaskPlugin({
|
||||
name: "DeployCertToTencentCLB",
|
||||
|
@ -72,11 +71,11 @@ export class DeployToClbPlugin implements ITaskPlugin {
|
|||
})
|
||||
accessId!: string;
|
||||
|
||||
@Inject()
|
||||
@Autowire()
|
||||
accessService!: IAccessService;
|
||||
|
||||
@Inject()
|
||||
logger!: LOGGER;
|
||||
@Autowire()
|
||||
logger!: ILogger;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
async onInit() {}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Autowire, IAccessService, IsTaskPlugin, ITaskPlugin, RunStrategy, TaskInput, TaskOutput, LOGGER } from "@certd/pipeline";
|
||||
import { Autowire, IAccessService, IsTaskPlugin, ITaskPlugin, RunStrategy, TaskInput, TaskOutput, ILogger } from "@certd/pipeline";
|
||||
import tencentcloud from "tencentcloud-sdk-nodejs/index";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
|
@ -46,7 +46,7 @@ export class UploadToTencentPlugin implements ITaskPlugin {
|
|||
accessService!: IAccessService;
|
||||
|
||||
@Autowire()
|
||||
logger!: LOGGER;
|
||||
logger!: ILogger;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
async onInit() {}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/ban-ts-ignore": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
// "no-unused-expressions": "off",
|
||||
"max-len": [0, 160, 2, { "ignoreUrls": true }]
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit b7422ab48ef81f5c98129bacb69d0eefdfe50645
|
||||
Subproject commit 8c7b3853be27df392dff765d39c6c53de09418bd
|
|
@ -1 +1 @@
|
|||
Subproject commit 5901fb5a440a7cebe3a2b6dfaec1b014e30b3a0c
|
||||
Subproject commit a5681f154468fc5c4aac7e4a0ce090cfffc00e9e
|
Loading…
Reference in New Issue