From d66bc337614681c8a7bc93308ff71978755bacf3 Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Mon, 7 Nov 2022 23:31:20 +0800 Subject: [PATCH] refactor: 1 --- packages/core/pipeline/index.ts | 1 + packages/core/pipeline/package.json | 8 +- .../pipeline/src/access/access-service.ts | 5 - packages/core/pipeline/src/access/api.ts | 5 + .../pipeline/src/access/impl/aliyun-access.ts | 28 -- .../core/pipeline/src/access/impl/index.ts | 1 - packages/core/pipeline/src/access/index.ts | 2 +- packages/core/pipeline/src/core/executor.ts | 5 +- packages/core/pipeline/src/core/index.ts | 2 + .../src/dns-provider/abstract-dns-provider.ts | 5 +- .../core/pipeline/src/dns-provider/index.ts | 2 +- .../providers/aliyun-dns-provider.ts | 120 --------- .../src/dns-provider/providers/index.ts | 1 - packages/core/pipeline/src/index.ts | 1 + .../pipeline/src/plugin/abstract-plugin.ts | 7 +- packages/core/pipeline/src/plugin/index.ts | 2 +- .../src/plugin/plugins/cert-plugin/acme.ts | 199 -------------- .../src/plugin/plugins/cert-plugin/index.ts | 236 ----------------- .../src/plugin/plugins/deploy-to-cdn/index.ts | 100 ------- .../core/pipeline/src/plugin/plugins/index.ts | 3 - packages/core/pipeline/src/utils/index.ts | 7 + .../core/pipeline/src/utils/util.request.ts | 58 +++++ .../core/pipeline/src/utils/util.sleep.ts | 7 + .../plugin/plugins => test}/echo-plugin.ts | 3 +- packages/core/pipeline/test/index.test.ts | 7 +- .../test/pipeline/access-service-test.ts | 5 +- .../test/pipeline/plugins/cert-apply.test.ts | 23 -- .../pipeline/plugins/deploy-to-cdn.test.ts | 23 -- packages/plugins/plugin-aliyun/.eslintrc | 14 - packages/plugins/plugin-aliyun/.gitignore | 7 - packages/plugins/plugin-aliyun/package.json | 34 --- .../plugins/plugin-aliyun/rollup.config.js | 13 - .../src/access-providers/aliyun.js | 27 -- .../plugin-aliyun/src/dns-providers/aliyun.js | 130 ---------- packages/plugins/plugin-aliyun/src/index.js | 25 -- .../src/plugins/abstract-aliyun.js | 9 - .../plugins/deploy-to-ack-ingress/index.js | 199 -------------- .../src/plugins/deploy-to-cdn/index.js | 95 ------- .../src/plugins/upload-to-aliyun/index.js | 106 -------- .../plugins/plugin-aliyun/src/utils/index.js | 3 - .../test/dns-providers/aliyun.cert.test.js | 22 -- .../test/dns-providers/aliyun.test.js | 39 --- .../plugins/plugin-aliyun/test/options.js | 42 --- .../deploy-to-ack-ingress-nginx.test.js | 67 ----- .../test/plugins/deploy-to-cdn.test.js | 21 -- .../test/plugins/upload-to-aliyun.test.js | 27 -- .../test/pipeline/access-service-test.ts | 9 + packages/plugins/plugin-common/.eslintrc | 14 - packages/plugins/plugin-common/.gitignore | 7 - packages/plugins/plugin-common/package.json | 29 --- .../plugins/plugin-common/rollup.config.js | 13 - packages/plugins/plugin-common/src/index.js | 1 - .../plugin-common/src/lib/k8s.client.js | 114 -------- packages/plugins/plugin-host/.eslintrc | 25 +- packages/plugins/plugin-host/.gitignore | 33 ++- packages/plugins/plugin-host/package.json | 72 +++--- packages/plugins/plugin-host/rollup.config.js | 13 - .../plugin-host/src/access-providers/ssh.js | 26 -- packages/plugins/plugin-host/src/index.js | 22 -- packages/plugins/plugin-host/src/lib/ssh.ts | 148 +++++++++++ .../src/plugin/host-shell-execute/index.ts | 54 ++++ .../src/plugin/upload-to-host/index.ts | 81 ++++++ .../plugin-host/src/plugins/abstract-host.js | 9 - .../src/plugins/host-shell-execute/index.js | 57 ---- .../plugins/plugin-host/src/plugins/ssh.js | 130 ---------- .../src/plugins/upload-to-host/index.js | 85 ------ packages/plugins/plugin-host/test/options.js | 42 --- .../test/plugins/host-shell-execute.test.js | 52 ---- .../test/plugins/upload-to-host.test.js | 48 ---- packages/plugins/plugin-tencent/.eslintrc | 25 +- packages/plugins/plugin-tencent/.gitignore | 33 ++- packages/plugins/plugin-tencent/package.json | 76 +++--- .../plugins/plugin-tencent/rollup.config.js | 13 - .../src/access-providers/dnspod.js | 24 -- .../src/access-providers/tencent.js | 22 -- .../src/access/tencent-access.ts | 26 ++ .../src/dns-provider/dnspod-dns-provider.ts | 107 ++++++++ .../plugin-tencent/src/dns-provider/index.ts | 1 + .../src/dns-providers/dnspod.js | 96 ------- packages/plugins/plugin-tencent/src/index.js | 34 --- .../src/plugin/deploy-to-clb/index.ts | 211 +++++++++++++++ .../src/plugin/deploy-to-tke-ingress/index.ts | 243 ++++++++++++++++++ .../src/plugin/upload-to-tencent/index.ts | 107 ++++++++ .../src/plugins/abstract-tencent.js | 13 - .../src/plugins/deploy-to-cdn/index.js | 104 -------- .../src/plugins/deploy-to-clb/index.js | 198 -------------- .../plugins/deploy-to-tke-ingress/index.js | 213 --------------- .../src/plugins/upload-to-tencent/index.js | 90 ------- .../test/dns-providers/dnspod.cert.test.js | 27 -- .../test/dns-providers/dnspod.test.js | 35 --- .../test/plugins/deploy-to-cdn.test.js | 52 ---- .../test/plugins/deploy-to-clb.test.js | 105 -------- .../deploy-to-tke-ingress-nginx.test.js | 59 ----- .../plugins/deploy-to-tke-ingress.test.js | 57 ---- .../test/plugins/upload-to-tencent.test.js | 27 -- .../plugins/plugin-util/src/lib/k8s.client.ts | 116 +++++++++ packages/server/certd-server | 2 +- 97 files changed, 1384 insertions(+), 3562 deletions(-) create mode 100644 packages/core/pipeline/index.ts delete mode 100644 packages/core/pipeline/src/access/access-service.ts delete mode 100644 packages/core/pipeline/src/access/impl/aliyun-access.ts delete mode 100644 packages/core/pipeline/src/access/impl/index.ts delete mode 100644 packages/core/pipeline/src/dns-provider/providers/aliyun-dns-provider.ts delete mode 100644 packages/core/pipeline/src/dns-provider/providers/index.ts delete mode 100644 packages/core/pipeline/src/plugin/plugins/cert-plugin/acme.ts delete mode 100644 packages/core/pipeline/src/plugin/plugins/cert-plugin/index.ts delete mode 100644 packages/core/pipeline/src/plugin/plugins/deploy-to-cdn/index.ts delete mode 100644 packages/core/pipeline/src/plugin/plugins/index.ts create mode 100644 packages/core/pipeline/src/utils/index.ts create mode 100644 packages/core/pipeline/src/utils/util.request.ts create mode 100644 packages/core/pipeline/src/utils/util.sleep.ts rename packages/core/pipeline/{src/plugin/plugins => test}/echo-plugin.ts (81%) delete mode 100644 packages/core/pipeline/test/pipeline/plugins/cert-apply.test.ts delete mode 100644 packages/core/pipeline/test/pipeline/plugins/deploy-to-cdn.test.ts delete mode 100644 packages/plugins/plugin-aliyun/.eslintrc delete mode 100644 packages/plugins/plugin-aliyun/.gitignore delete mode 100644 packages/plugins/plugin-aliyun/package.json delete mode 100644 packages/plugins/plugin-aliyun/rollup.config.js delete mode 100644 packages/plugins/plugin-aliyun/src/access-providers/aliyun.js delete mode 100644 packages/plugins/plugin-aliyun/src/dns-providers/aliyun.js delete mode 100644 packages/plugins/plugin-aliyun/src/index.js delete mode 100644 packages/plugins/plugin-aliyun/src/plugins/abstract-aliyun.js delete mode 100644 packages/plugins/plugin-aliyun/src/plugins/deploy-to-ack-ingress/index.js delete mode 100644 packages/plugins/plugin-aliyun/src/plugins/deploy-to-cdn/index.js delete mode 100644 packages/plugins/plugin-aliyun/src/plugins/upload-to-aliyun/index.js delete mode 100644 packages/plugins/plugin-aliyun/src/utils/index.js delete mode 100644 packages/plugins/plugin-aliyun/test/dns-providers/aliyun.cert.test.js delete mode 100644 packages/plugins/plugin-aliyun/test/dns-providers/aliyun.test.js delete mode 100644 packages/plugins/plugin-aliyun/test/options.js delete mode 100644 packages/plugins/plugin-aliyun/test/plugins/deploy-to-ack-ingress-nginx.test.js delete mode 100644 packages/plugins/plugin-aliyun/test/plugins/deploy-to-cdn.test.js delete mode 100644 packages/plugins/plugin-aliyun/test/plugins/upload-to-aliyun.test.js create mode 100644 packages/plugins/plugin-cert/test/pipeline/access-service-test.ts delete mode 100644 packages/plugins/plugin-common/.eslintrc delete mode 100644 packages/plugins/plugin-common/.gitignore delete mode 100644 packages/plugins/plugin-common/package.json delete mode 100644 packages/plugins/plugin-common/rollup.config.js delete mode 100644 packages/plugins/plugin-common/src/index.js delete mode 100644 packages/plugins/plugin-common/src/lib/k8s.client.js delete mode 100644 packages/plugins/plugin-host/rollup.config.js delete mode 100644 packages/plugins/plugin-host/src/access-providers/ssh.js delete mode 100644 packages/plugins/plugin-host/src/index.js create mode 100644 packages/plugins/plugin-host/src/lib/ssh.ts create mode 100644 packages/plugins/plugin-host/src/plugin/host-shell-execute/index.ts create mode 100644 packages/plugins/plugin-host/src/plugin/upload-to-host/index.ts delete mode 100644 packages/plugins/plugin-host/src/plugins/abstract-host.js delete mode 100644 packages/plugins/plugin-host/src/plugins/host-shell-execute/index.js delete mode 100644 packages/plugins/plugin-host/src/plugins/ssh.js delete mode 100644 packages/plugins/plugin-host/src/plugins/upload-to-host/index.js delete mode 100644 packages/plugins/plugin-host/test/options.js delete mode 100644 packages/plugins/plugin-host/test/plugins/host-shell-execute.test.js delete mode 100644 packages/plugins/plugin-host/test/plugins/upload-to-host.test.js delete mode 100644 packages/plugins/plugin-tencent/rollup.config.js delete mode 100644 packages/plugins/plugin-tencent/src/access-providers/dnspod.js delete mode 100644 packages/plugins/plugin-tencent/src/access-providers/tencent.js create mode 100644 packages/plugins/plugin-tencent/src/access/tencent-access.ts create mode 100644 packages/plugins/plugin-tencent/src/dns-provider/dnspod-dns-provider.ts create mode 100644 packages/plugins/plugin-tencent/src/dns-provider/index.ts delete mode 100644 packages/plugins/plugin-tencent/src/dns-providers/dnspod.js delete mode 100644 packages/plugins/plugin-tencent/src/index.js create mode 100644 packages/plugins/plugin-tencent/src/plugin/deploy-to-clb/index.ts create mode 100644 packages/plugins/plugin-tencent/src/plugin/deploy-to-tke-ingress/index.ts create mode 100644 packages/plugins/plugin-tencent/src/plugin/upload-to-tencent/index.ts delete mode 100644 packages/plugins/plugin-tencent/src/plugins/abstract-tencent.js delete mode 100644 packages/plugins/plugin-tencent/src/plugins/deploy-to-cdn/index.js delete mode 100644 packages/plugins/plugin-tencent/src/plugins/deploy-to-clb/index.js delete mode 100644 packages/plugins/plugin-tencent/src/plugins/deploy-to-tke-ingress/index.js delete mode 100644 packages/plugins/plugin-tencent/src/plugins/upload-to-tencent/index.js delete mode 100644 packages/plugins/plugin-tencent/test/dns-providers/dnspod.cert.test.js delete mode 100644 packages/plugins/plugin-tencent/test/dns-providers/dnspod.test.js delete mode 100644 packages/plugins/plugin-tencent/test/plugins/deploy-to-cdn.test.js delete mode 100644 packages/plugins/plugin-tencent/test/plugins/deploy-to-clb.test.js delete mode 100644 packages/plugins/plugin-tencent/test/plugins/deploy-to-tke-ingress-nginx.test.js delete mode 100644 packages/plugins/plugin-tencent/test/plugins/deploy-to-tke-ingress.test.js delete mode 100644 packages/plugins/plugin-tencent/test/plugins/upload-to-tencent.test.js create mode 100644 packages/plugins/plugin-util/src/lib/k8s.client.ts diff --git a/packages/core/pipeline/index.ts b/packages/core/pipeline/index.ts new file mode 100644 index 00000000..3bd16e17 --- /dev/null +++ b/packages/core/pipeline/index.ts @@ -0,0 +1 @@ +export * from "./src"; diff --git a/packages/core/pipeline/package.json b/packages/core/pipeline/package.json index 1fb81fc1..7aa06a3f 100644 --- a/packages/core/pipeline/package.json +++ b/packages/core/pipeline/package.json @@ -2,7 +2,7 @@ "name": "@certd/pipeline", "private": true, "version": "0.3.0", - "main": "./dist/pipeline.umd.js", + "main": "./src/index.ts", "module": "./dist/pipeline.mjs", "types": "./dist/es/index.d.ts", "scripts": { @@ -14,7 +14,10 @@ "@certd/acme-client": "^0.3.0", "dayjs": "^1.11.6", "lodash": "^4.17.21", - "node-forge": "^0.10.0" + "node-forge": "^0.10.0", + "log4js": "^6.3.0", + "axios": "^0.21.1", + "qs": "^6.9.4" }, "devDependencies": { "@types/lodash": "^4.14.186", @@ -35,7 +38,6 @@ "eslint-plugin-import": "^2.26.0", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^4.2.1", - "log4js": "^6.3.0", "mocha": "^10.1.0", "ts-node": "^10.9.1", "typescript": "^4.8.4", diff --git a/packages/core/pipeline/src/access/access-service.ts b/packages/core/pipeline/src/access/access-service.ts deleted file mode 100644 index b200c0e7..00000000 --- a/packages/core/pipeline/src/access/access-service.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { AbstractAccess } from "./abstract-access"; - -export interface IAccessService { - getById(id: any): Promise; -} diff --git a/packages/core/pipeline/src/access/api.ts b/packages/core/pipeline/src/access/api.ts index 0964ab29..48356c0b 100644 --- a/packages/core/pipeline/src/access/api.ts +++ b/packages/core/pipeline/src/access/api.ts @@ -1,6 +1,7 @@ import { Registrable } from "../registry"; import { accessRegistry } from "./registry"; import { FormItemProps } from "../d.ts"; +import { AbstractAccess } from "./abstract-access"; export type AccessInput = FormItemProps & { title: string; @@ -17,3 +18,7 @@ export function IsAccess(define: AccessDefine) { accessRegistry.install(target); }; } + +export interface IAccessService { + getById(id: any): Promise; +} diff --git a/packages/core/pipeline/src/access/impl/aliyun-access.ts b/packages/core/pipeline/src/access/impl/aliyun-access.ts deleted file mode 100644 index c770c778..00000000 --- a/packages/core/pipeline/src/access/impl/aliyun-access.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { IsAccess } from "../api"; -import { AbstractAccess } from "../abstract-access"; - -@IsAccess({ - name: "aliyun", - title: "阿里云授权", - desc: "", - input: { - accessKeyId: { - title: "accessKeyId", - component: { - placeholder: "accessKeyId", - }, - required: true, - }, - accessKeySecret: { - title: "accessKeySecret", - component: { - placeholder: "accessKeySecret", - }, - required: true, - }, - }, -}) -export class AliyunAccess extends AbstractAccess { - accessKeyId = ""; - accessKeySecret = ""; -} diff --git a/packages/core/pipeline/src/access/impl/index.ts b/packages/core/pipeline/src/access/impl/index.ts deleted file mode 100644 index c90ec152..00000000 --- a/packages/core/pipeline/src/access/impl/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./aliyun-access"; diff --git a/packages/core/pipeline/src/access/index.ts b/packages/core/pipeline/src/access/index.ts index 0da3dcdd..b51a95f1 100644 --- a/packages/core/pipeline/src/access/index.ts +++ b/packages/core/pipeline/src/access/index.ts @@ -1,3 +1,3 @@ export * from "./api"; -export * from "./impl"; export * from "./abstract-access"; +export * from "./registry"; diff --git a/packages/core/pipeline/src/core/executor.ts b/packages/core/pipeline/src/core/executor.ts index 776a56f7..cf86fa1a 100644 --- a/packages/core/pipeline/src/core/executor.ts +++ b/packages/core/pipeline/src/core/executor.ts @@ -2,12 +2,12 @@ import { ConcurrencyStrategy, Pipeline, ResultType, Runnable, RunStrategy, Stage import _ from "lodash"; import { RunHistory } from "./run-history"; import { pluginRegistry, TaskPlugin } from "../plugin"; -import { IAccessService } from "../access/access-service"; import { ContextFactory, IContext } from "./context"; import { IStorage } from "./storage"; import { logger } from "../utils/util.log"; import { Logger } from "log4js"; - +import { request } from "../utils/util.request"; +import { IAccessService } from "../access"; export class Executor { userId: any; pipeline: Pipeline; @@ -166,6 +166,7 @@ export class Executor { pipelineContext: this.pipelineContext, userContext: this.contextFactory.getContext("user", this.userId), logger, + http: request, }); return plugin; } diff --git a/packages/core/pipeline/src/core/index.ts b/packages/core/pipeline/src/core/index.ts index be89b480..537c1c67 100644 --- a/packages/core/pipeline/src/core/index.ts +++ b/packages/core/pipeline/src/core/index.ts @@ -1,2 +1,4 @@ export * from "./executor"; export * from "./run-history"; +export * from "./context"; +export * from "./storage"; diff --git a/packages/core/pipeline/src/dns-provider/abstract-dns-provider.ts b/packages/core/pipeline/src/dns-provider/abstract-dns-provider.ts index 84a4d272..92454c0f 100644 --- a/packages/core/pipeline/src/dns-provider/abstract-dns-provider.ts +++ b/packages/core/pipeline/src/dns-provider/abstract-dns-provider.ts @@ -2,12 +2,15 @@ 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 implements IDnsProvider { access!: AbstractAccess; logger!: Logger; - doInit(options: { 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(); } diff --git a/packages/core/pipeline/src/dns-provider/index.ts b/packages/core/pipeline/src/dns-provider/index.ts index 43384b02..e89e5292 100644 --- a/packages/core/pipeline/src/dns-provider/index.ts +++ b/packages/core/pipeline/src/dns-provider/index.ts @@ -1,3 +1,3 @@ -import "./providers"; export * from "./api"; export * from "./registry"; +export * from "./abstract-dns-provider"; diff --git a/packages/core/pipeline/src/dns-provider/providers/aliyun-dns-provider.ts b/packages/core/pipeline/src/dns-provider/providers/aliyun-dns-provider.ts deleted file mode 100644 index 0dafb087..00000000 --- a/packages/core/pipeline/src/dns-provider/providers/aliyun-dns-provider.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { AbstractDnsProvider } from "../abstract-dns-provider"; -import Core from "@alicloud/pop-core"; -import _ from "lodash"; -import { CreateRecordOptions, IDnsProvider, IsDnsProvider, RemoveRecordOptions } from "../api"; - -@IsDnsProvider({ - name: "aliyun", - title: "阿里云", - desc: "阿里云DNS解析提供商", - accessType: "aliyun", -}) -export class AliyunDnsProvider extends AbstractDnsProvider implements IDnsProvider { - client: any; - constructor() { - super(); - } - async onInit() { - const access: any = this.access; - this.client = new Core({ - accessKeyId: access.accessKeyId, - accessKeySecret: access.accessKeySecret, - endpoint: "https://alidns.aliyuncs.com", - apiVersion: "2015-01-09", - }); - } - - async getDomainList() { - const params = { - RegionId: "cn-hangzhou", - }; - - const requestOption = { - method: "POST", - }; - - const ret = await this.client.request("DescribeDomains", params, requestOption); - return ret.Domains.Domain; - } - - async matchDomain(dnsRecord: string) { - const list = await this.getDomainList(); - let domain = null; - for (const item of list) { - if (_.endsWith(dnsRecord, item.DomainName)) { - domain = item.DomainName; - break; - } - } - if (!domain) { - throw new Error("can not find Domain ," + dnsRecord); - } - return domain; - } - - async getRecords(domain: string, rr: string, value: string) { - const params: any = { - RegionId: "cn-hangzhou", - DomainName: domain, - RRKeyWord: rr, - ValueKeyWord: undefined, - }; - if (value) { - params.ValueKeyWord = value; - } - - const requestOption = { - method: "POST", - }; - - const ret = await this.client.request("DescribeDomainRecords", params, requestOption); - return ret.DomainRecords.Record; - } - - async createRecord(options: CreateRecordOptions): Promise { - const { fullRecord, value, type } = options; - this.logger.info("添加域名解析:", fullRecord, value); - const domain = await this.matchDomain(fullRecord); - const rr = fullRecord.replace("." + domain, ""); - - const params = { - RegionId: "cn-hangzhou", - DomainName: domain, - RR: rr, - Type: type, - Value: value, - // Line: 'oversea' // 海外 - }; - - const requestOption = { - method: "POST", - }; - - try { - const ret = await this.client.request("AddDomainRecord", params, requestOption); - this.logger.info("添加域名解析成功:", value, value, ret.RecordId); - return ret.RecordId; - } catch (e: any) { - if (e.code === "DomainRecordDuplicate") { - return; - } - this.logger.info("添加域名解析出错", e); - throw e; - } - } - async removeRecord(options: RemoveRecordOptions): Promise { - const { fullRecord, value, record } = options; - const params = { - RegionId: "cn-hangzhou", - RecordId: record, - }; - - const requestOption = { - method: "POST", - }; - - const ret = await this.client.request("DeleteDomainRecord", params, requestOption); - this.logger.info("删除域名解析成功:", fullRecord, value, ret.RecordId); - return ret.RecordId; - } -} diff --git a/packages/core/pipeline/src/dns-provider/providers/index.ts b/packages/core/pipeline/src/dns-provider/providers/index.ts deleted file mode 100644 index b4c8f020..00000000 --- a/packages/core/pipeline/src/dns-provider/providers/index.ts +++ /dev/null @@ -1 +0,0 @@ -import "./aliyun-dns-provider"; diff --git a/packages/core/pipeline/src/index.ts b/packages/core/pipeline/src/index.ts index a8c132da..d6d2e459 100644 --- a/packages/core/pipeline/src/index.ts +++ b/packages/core/pipeline/src/index.ts @@ -4,3 +4,4 @@ export * from "./access"; export * from "./registry"; export * from "./dns-provider"; export * from "./plugin"; +export * from "./utils"; diff --git a/packages/core/pipeline/src/plugin/abstract-plugin.ts b/packages/core/pipeline/src/plugin/abstract-plugin.ts index db6ed9de..2f2fdbfe 100644 --- a/packages/core/pipeline/src/plugin/abstract-plugin.ts +++ b/packages/core/pipeline/src/plugin/abstract-plugin.ts @@ -1,8 +1,9 @@ import { AbstractRegistrable } from "../registry"; import { Logger } from "log4js"; -import { IAccessService } from "../access/access-service"; import { IContext } from "../core/context"; import { PluginDefine, TaskInput, TaskOutput, TaskPlugin } from "./api"; +import { IAccessService } from "../access"; +import { AxiosInstance } from "axios"; export abstract class AbstractPlugin extends AbstractRegistrable implements TaskPlugin { logger!: Logger; @@ -12,12 +13,14 @@ export abstract class AbstractPlugin extends AbstractRegistrable i pipelineContext: IContext; // @ts-ignore userContext: IContext; + http!: AxiosInstance; - async doInit(options: { accessService: IAccessService; pipelineContext: IContext; userContext: IContext; logger: Logger }) { + async doInit(options: { accessService: IAccessService; pipelineContext: IContext; userContext: IContext; logger: Logger; http: AxiosInstance }) { this.accessService = options.accessService; this.pipelineContext = options.pipelineContext; this.userContext = options.userContext; this.logger = options.logger; + this.http = options.http; await this.onInit(); } diff --git a/packages/core/pipeline/src/plugin/index.ts b/packages/core/pipeline/src/plugin/index.ts index 4a4ed8f2..2d9b5e5a 100644 --- a/packages/core/pipeline/src/plugin/index.ts +++ b/packages/core/pipeline/src/plugin/index.ts @@ -1,3 +1,3 @@ -import "./plugins"; export * from "./api"; export * from "./registry"; +export * from "./abstract-plugin"; diff --git a/packages/core/pipeline/src/plugin/plugins/cert-plugin/acme.ts b/packages/core/pipeline/src/plugin/plugins/cert-plugin/acme.ts deleted file mode 100644 index 9fb95685..00000000 --- a/packages/core/pipeline/src/plugin/plugins/cert-plugin/acme.ts +++ /dev/null @@ -1,199 +0,0 @@ -// @ts-ignore -import * as acme from "@certd/acme-client"; -import _ from "lodash"; -import { AbstractDnsProvider } from "../../../dns-provider/abstract-dns-provider"; -import { IContext } from "../../../core/context"; -import { IDnsProvider } from "../../../dns-provider"; -import { Challenge } from "@certd/acme-client/types/rfc8555"; -import { Logger } from "log4js"; -export class AcmeService { - userContext: IContext; - logger: Logger; - constructor(options: { userContext: IContext; logger: Logger }) { - this.userContext = options.userContext; - this.logger = options.logger; - acme.setLogger((text: string) => { - this.logger.info(text); - }); - } - - async getAccountConfig(email: string) { - return (await this.userContext.get(this.buildAccountKey(email))) || {}; - } - - buildAccountKey(email: string) { - return "acme.config." + email; - } - - async saveAccountConfig(email: string, conf: any) { - await this.userContext.set(this.buildAccountKey(email), conf); - } - - async getAcmeClient(email: string, isTest = false): Promise { - const conf = await this.getAccountConfig(email); - if (conf.key == null) { - conf.key = await this.createNewKey(); - await this.saveAccountConfig(email, conf); - } - if (isTest == null) { - isTest = process.env.CERTD_MODE === "test"; - } - const client = new acme.Client({ - directoryUrl: isTest ? acme.directory.letsencrypt.staging : acme.directory.letsencrypt.production, - accountKey: conf.key, - accountUrl: conf.accountUrl, - backoffAttempts: 20, - backoffMin: 5000, - backoffMax: 10000, - }); - - if (conf.accountUrl == null) { - const accountPayload = { - termsOfServiceAgreed: true, - contact: [`mailto:${email}`], - }; - await client.createAccount(accountPayload); - conf.accountUrl = client.getAccountUrl(); - await this.saveAccountConfig(email, conf); - } - return client; - } - - async createNewKey() { - const key = await acme.forge.createPrivateKey(); - return key.toString(); - } - - async challengeCreateFn(authz: any, challenge: any, keyAuthorization: string, dnsProvider: IDnsProvider) { - this.logger.info("Triggered challengeCreateFn()"); - - /* http-01 */ - if (challenge.type === "http-01") { - const filePath = `/var/www/html/.well-known/acme-challenge/${challenge.token}`; - const fileContents = keyAuthorization; - - this.logger.info(`Creating challenge response for ${authz.identifier.value} at path: ${filePath}`); - - /* Replace this */ - this.logger.info(`Would write "${fileContents}" to path "${filePath}"`); - // await fs.writeFileAsync(filePath, fileContents); - } else if (challenge.type === "dns-01") { - /* dns-01 */ - const dnsRecord = `_acme-challenge.${authz.identifier.value}`; - const recordValue = keyAuthorization; - - this.logger.info(`Creating TXT record for ${authz.identifier.value}: ${dnsRecord}`); - - /* Replace this */ - this.logger.info(`Would create TXT record "${dnsRecord}" with value "${recordValue}"`); - - return await dnsProvider.createRecord({ - fullRecord: dnsRecord, - type: "TXT", - value: recordValue, - }); - } - } - - /** - * Function used to remove an ACME challenge response - * - * @param {object} authz Authorization object - * @param {object} challenge Selected challenge - * @param {string} keyAuthorization Authorization key - * @param recordItem challengeCreateFn create record item - * @param dnsProvider dnsProvider - * @returns {Promise} - */ - - async challengeRemoveFn(authz: any, challenge: any, keyAuthorization: string, recordItem: any, dnsProvider: IDnsProvider) { - this.logger.info("Triggered challengeRemoveFn()"); - - /* http-01 */ - if (challenge.type === "http-01") { - const filePath = `/var/www/html/.well-known/acme-challenge/${challenge.token}`; - - this.logger.info(`Removing challenge response for ${authz.identifier.value} at path: ${filePath}`); - - /* Replace this */ - this.logger.info(`Would remove file on path "${filePath}"`); - // await fs.unlinkAsync(filePath); - } else if (challenge.type === "dns-01") { - const dnsRecord = `_acme-challenge.${authz.identifier.value}`; - const recordValue = keyAuthorization; - - this.logger.info(`Removing TXT record for ${authz.identifier.value}: ${dnsRecord}`); - - /* Replace this */ - this.logger.info(`Would remove TXT record "${dnsRecord}" with value "${recordValue}"`); - await dnsProvider.removeRecord({ - fullRecord: dnsRecord, - type: "TXT", - value: keyAuthorization, - record: recordItem, - }); - } - } - - async order(options: { email: string; domains: string | string[]; dnsProvider: AbstractDnsProvider; csrInfo: any; isTest?: boolean }) { - const { email, isTest, domains, csrInfo, dnsProvider } = options; - const client: acme.Client = await this.getAcmeClient(email, isTest); - - /* Create CSR */ - const { commonName, altNames } = this.buildCommonNameByDomains(domains); - - const [key, csr] = await acme.forge.createCsr({ - commonName, - ...csrInfo, - altNames, - }); - if (dnsProvider == null) { - throw new Error("dnsProvider 不能为空"); - } - /* 自动申请证书 */ - const crt = await client.auto({ - csr, - email: email, - termsOfServiceAgreed: true, - challengePriority: ["dns-01"], - challengeCreateFn: async (authz: acme.Authorization, challenge: Challenge, keyAuthorization: string): Promise => { - return await this.challengeCreateFn(authz, challenge, keyAuthorization, dnsProvider); - }, - challengeRemoveFn: async (authz: acme.Authorization, challenge: Challenge, keyAuthorization: string, recordItem: any): Promise => { - return await this.challengeRemoveFn(authz, challenge, keyAuthorization, recordItem, dnsProvider); - }, - }); - - const cert = { - crt: crt.toString(), - key: key.toString(), - csr: csr.toString(), - }; - /* Done */ - this.logger.debug(`CSR:\n${cert.csr}`); - this.logger.debug(`Certificate:\n${cert.crt}`); - this.logger.info("证书申请成功"); - return cert; - } - - buildCommonNameByDomains(domains: string | string[]): { - commonName: string; - altNames: string[] | undefined; - } { - if (typeof domains === "string") { - domains = domains.split(","); - } - if (domains.length === 0) { - throw new Error("domain can not be empty"); - } - const commonName = domains[0]; - let altNames: undefined | string[] = undefined; - if (domains.length > 1) { - altNames = _.slice(domains, 1); - } - return { - commonName, - altNames, - }; - } -} diff --git a/packages/core/pipeline/src/plugin/plugins/cert-plugin/index.ts b/packages/core/pipeline/src/plugin/plugins/cert-plugin/index.ts deleted file mode 100644 index 16d8038a..00000000 --- a/packages/core/pipeline/src/plugin/plugins/cert-plugin/index.ts +++ /dev/null @@ -1,236 +0,0 @@ -import { AbstractPlugin } from "../../abstract-plugin"; -import forge from "node-forge"; -import { IsTask, TaskInput, TaskOutput, TaskPlugin } from "../../api"; -import dayjs from "dayjs"; -import { dnsProviderRegistry } from "../../../dns-provider"; -import { AbstractDnsProvider } from "../../../dns-provider/abstract-dns-provider"; -import { AcmeService } from "./acme"; -import _ from "lodash"; -export type CertInfo = { - crt: string; - key: string; - csr: string; -}; -@IsTask(() => { - return { - name: "CertApply", - title: "证书申请", - desc: "免费通配符域名证书申请,支持多个域名打到同一个证书上", - input: { - domains: { - 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" + - "输入一个回车之后,再输入下一个", - }, - 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: { - input: { - renewDays: 20, - forceUpdate: false, - }, - }, - output: { - cert: { - key: "cert", - type: "CertInfo", - title: "域名证书", - }, - }, - }; -}) -export class CertApplyPlugin extends AbstractPlugin implements TaskPlugin { - // @ts-ignore - acme: AcmeService; - protected async onInit() { - this.acme = new AcmeService({ userContext: this.userContext, logger: this.logger }); - } - - async execute(input: TaskInput): Promise { - const oldCert = await this.condition(input); - if (oldCert != null) { - return { - cert: oldCert, - }; - } - const cert = await this.doCertApply(input); - return { cert }; - } - - /** - * 是否更新证书 - * @param input - */ - async condition(input: TaskInput) { - if (input.forceUpdate) { - return null; - } - let oldCert; - try { - oldCert = await this.readCurrentCert(); - } catch (e) { - this.logger.warn("读取cert失败:", e); - } - if (oldCert == null) { - this.logger.info("还未申请过,准备申请新证书"); - return null; - } - - const ret = this.isWillExpire(oldCert.expires, input.renewDays); - if (!ret.isWillExpire) { - this.logger.info(`证书还未过期:过期时间${dayjs(oldCert.expires).format("YYYY-MM-DD HH:mm:ss")},剩余${ret.leftDays}天`); - return oldCert; - } - this.logger.info("即将过期,开始更新证书"); - return null; - } - - async doCertApply(input: TaskInput) { - const email = input["email"]; - const domains = input["domains"]; - const dnsProviderType = input["dnsProviderType"]; - const dnsProviderAccessId = input["dnsProviderAccess"]; - const csrInfo = _.merge( - { - country: "CN", - state: "GuangDong", - locality: "ShengZhen", - organization: "CertD Org.", - organizationUnit: "IT Department", - emailAddress: email, - }, - input["csrInfo"] - ); - this.logger.info("开始申请证书,", email, domains); - - const dnsProviderClass = dnsProviderRegistry.get(dnsProviderType); - const access = await this.accessService.getById(dnsProviderAccessId); - // @ts-ignore - const dnsProvider: AbstractDnsProvider = new dnsProviderClass(); - dnsProvider.doInit({ access, logger: this.logger }); - - const cert = await this.acme.order({ - email, - domains, - dnsProvider, - csrInfo, - isTest: false, - }); - - await this.writeCert(cert); - const ret = await this.readCurrentCert(); - - return { - ...ret, - isNew: true, - }; - } - - formatCert(pem: string) { - pem = pem.replace(/\r/g, ""); - pem = pem.replace(/\n\n/g, "\n"); - pem = pem.replace(/\n$/g, ""); - return pem; - } - - async writeCert(cert: { crt: string; key: string; csr: string }) { - const newCert = { - crt: this.formatCert(cert.crt), - key: this.formatCert(cert.key), - csr: this.formatCert(cert.csr), - }; - await this.pipelineContext.set("cert", newCert); - } - - async readCurrentCert() { - const cert: CertInfo = 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 }; - } - - /** - * 检查是否过期,默认提前20天 - * @param expires - * @param maxDays - * @returns {boolean} - */ - isWillExpire(expires: number, maxDays = 20) { - if (expires == null) { - throw new Error("过期时间不能为空"); - } - // 检查有效期 - const leftDays = dayjs(expires).diff(dayjs(), "day"); - return { - isWillExpire: leftDays < maxDays, - leftDays, - }; - } -} diff --git a/packages/core/pipeline/src/plugin/plugins/deploy-to-cdn/index.ts b/packages/core/pipeline/src/plugin/plugins/deploy-to-cdn/index.ts deleted file mode 100644 index d75a7699..00000000 --- a/packages/core/pipeline/src/plugin/plugins/deploy-to-cdn/index.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { AbstractPlugin } from "../../abstract-plugin"; -import { IsTask, TaskInput, TaskOutput, TaskPlugin } from "../../api"; -import dayjs from "dayjs"; -import Core from "@alicloud/pop-core"; -import RPCClient from "@alicloud/pop-core"; -import { AliyunAccess } from "../../../access"; -import { CertInfo } from "../cert-plugin"; -import { RunStrategy } from "../../../d.ts"; - -@IsTask(() => { - return { - name: "DeployCertToAliyunCDN", - title: "部署证书至阿里云CDN", - desc: "依赖证书申请前置任务,自动部署域名证书至阿里云CDN", - input: { - domainName: { - 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 { - async execute(input: TaskInput): Promise { - console.log("开始部署证书到阿里云cdn"); - const access = (await this.accessService.getById(input.accessId)) as AliyunAccess; - const client = this.getClient(access); - const params = await this.buildParams(input); - await this.doRequest(client, params); - console.log("部署完成"); - return {}; - } - - getClient(access: AliyunAccess) { - return new Core({ - accessKeyId: access.accessKeyId, - accessKeySecret: access.accessKeySecret, - endpoint: "https://cdn.aliyuncs.com", - apiVersion: "2018-05-10", - }); - } - - async buildParams(input: TaskInput) { - const { certName, domainName } = input; - const CertName = (certName ?? "certd") + "-" + dayjs().format("YYYYMMDDHHmmss"); - const cert = input.cert as CertInfo; - return { - RegionId: "cn-hangzhou", - DomainName: domainName, - ServerCertificateStatus: "on", - CertName: CertName, - CertType: "upload", - ServerCertificate: cert.crt, - PrivateKey: cert.key, - }; - } - - async doRequest(client: RPCClient, params: any) { - const requestOption = { - method: "POST", - }; - const ret: any = await client.request("SetDomainServerCertificate", params, requestOption); - this.checkRet(ret); - this.logger.info("设置cdn证书成功:", ret.RequestId); - } - - checkRet(ret: any) { - if (ret.code != null) { - throw new Error("执行失败:" + ret.Message); - } - } -} diff --git a/packages/core/pipeline/src/plugin/plugins/index.ts b/packages/core/pipeline/src/plugin/plugins/index.ts deleted file mode 100644 index e27fce77..00000000 --- a/packages/core/pipeline/src/plugin/plugins/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./cert-plugin/index"; -export * from "./echo-plugin"; -export * from "./deploy-to-cdn/index"; diff --git a/packages/core/pipeline/src/utils/index.ts b/packages/core/pipeline/src/utils/index.ts new file mode 100644 index 00000000..b17e682c --- /dev/null +++ b/packages/core/pipeline/src/utils/index.ts @@ -0,0 +1,7 @@ +import sleep from "./util.sleep"; +import { request } from "./util.request"; +export * from "./util.log"; +export const utils = { + sleep, + http: request, +}; diff --git a/packages/core/pipeline/src/utils/util.request.ts b/packages/core/pipeline/src/utils/util.request.ts new file mode 100644 index 00000000..cca9f296 --- /dev/null +++ b/packages/core/pipeline/src/utils/util.request.ts @@ -0,0 +1,58 @@ +import axios from "axios"; +// @ts-ignore +import qs from "qs"; +import { logger } from "./util.log"; +/** + * @description 创建请求实例 + */ +function createService() { + // 创建一个 axios 实例 + const service = axios.create(); + // 请求拦截 + service.interceptors.request.use( + (config: any) => { + if (config.formData) { + config.data = qs.stringify(config.formData, { + arrayFormat: "indices", + allowDots: true, + }); // 序列化请求参数 + delete config.formData; + } + return config; + }, + (error: Error) => { + // 发送失败 + logger.error(error); + return Promise.reject(error); + } + ); + // 响应拦截 + service.interceptors.response.use( + (response: any) => { + logger.info("http response:", JSON.stringify(response.data)); + return response.data; + }, + (error: any) => { + // const status = _.get(error, 'response.status') + // switch (status) { + // case 400: error.message = '请求错误'; break + // case 401: error.message = '未授权,请登录'; break + // case 403: error.message = '拒绝访问'; break + // case 404: error.message = `请求地址出错: ${error.response.config.url}`; break + // case 408: error.message = '请求超时'; break + // case 500: error.message = '服务器内部错误'; break + // case 501: error.message = '服务未实现'; break + // case 502: error.message = '网关错误'; break + // case 503: error.message = '服务不可用'; break + // case 504: error.message = '网关超时'; break + // case 505: error.message = 'HTTP版本不受支持'; break + // default: break + // } + logger.error("请求出错:", error.response.config.url, error); + return Promise.reject(error); + } + ); + return service; +} + +export const request = createService(); diff --git a/packages/core/pipeline/src/utils/util.sleep.ts b/packages/core/pipeline/src/utils/util.sleep.ts new file mode 100644 index 00000000..5dcca1d8 --- /dev/null +++ b/packages/core/pipeline/src/utils/util.sleep.ts @@ -0,0 +1,7 @@ +export default function (timeout: number) { + return new Promise((resolve) => { + setTimeout(() => { + resolve({}); + }, timeout); + }); +} diff --git a/packages/core/pipeline/src/plugin/plugins/echo-plugin.ts b/packages/core/pipeline/test/echo-plugin.ts similarity index 81% rename from packages/core/pipeline/src/plugin/plugins/echo-plugin.ts rename to packages/core/pipeline/test/echo-plugin.ts index a6b92ee6..f24b82b9 100644 --- a/packages/core/pipeline/src/plugin/plugins/echo-plugin.ts +++ b/packages/core/pipeline/test/echo-plugin.ts @@ -1,5 +1,4 @@ -import { AbstractPlugin } from "../abstract-plugin"; -import { IsTask, TaskInput, TaskOutput, TaskPlugin } from "../api"; +import { AbstractPlugin, IsTask, TaskInput, TaskOutput, TaskPlugin } from "../src"; @IsTask(() => { return { diff --git a/packages/core/pipeline/test/index.test.ts b/packages/core/pipeline/test/index.test.ts index 9d5eadcb..e44a6e60 100644 --- a/packages/core/pipeline/test/index.test.ts +++ b/packages/core/pipeline/test/index.test.ts @@ -1,11 +1,12 @@ import { expect } from "chai"; import "mocha"; -import { EchoPlugin } from "../src/plugin/plugins"; +import { EchoPlugin } from "./echo-plugin"; describe("task_plugin", function () { it("#taskplugin", function () { const echoPlugin = new EchoPlugin(); - const define = echoPlugin.define; - echoPlugin.execute({ context: {}, props: { test: 111 } }); + // @ts-ignore + const define = echoPlugin.getDefine(); + echoPlugin.execute({ context: {}, input: { test: 111 } }); expect(define.name).eq("EchoPlugin"); }); }); diff --git a/packages/core/pipeline/test/pipeline/access-service-test.ts b/packages/core/pipeline/test/pipeline/access-service-test.ts index 2b8c2915..bb55a964 100644 --- a/packages/core/pipeline/test/pipeline/access-service-test.ts +++ b/packages/core/pipeline/test/pipeline/access-service-test.ts @@ -1,10 +1,9 @@ -import { IAccessService } from "../../src/access/access-service"; -import { AbstractAccess, AliyunAccess } from "../../src"; +import { AbstractAccess, IAccessService } from "../../src"; import { aliyunSecret } from "../user.secret"; export class AccessServiceTest implements IAccessService { async getById(id: any): Promise { return { ...aliyunSecret, - } as AliyunAccess; + } as any; } } diff --git a/packages/core/pipeline/test/pipeline/plugins/cert-apply.test.ts b/packages/core/pipeline/test/pipeline/plugins/cert-apply.test.ts deleted file mode 100644 index 5304eb89..00000000 --- a/packages/core/pipeline/test/pipeline/plugins/cert-apply.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { expect } from "chai"; -import "mocha"; -import { CertApplyPlugin } from "../../../src/plugin/plugins"; -import { pluginInitProps } from "../init.test"; -describe("CertApply", function () { - it("#execute", async function () { - this.timeout(120000); - const plugin = new CertApplyPlugin(); - // @ts-ignore - delete plugin.define; - await plugin.doInit(pluginInitProps); - const output = await plugin.execute({ - domains: ["*.docmirror.cn", "docmirror.cn"], - email: "xiaojunnuo@qq.com", - dnsProviderType: "aliyun", - accessId: "111", - forceUpdate: true, - }); - const cert = output.cert; - expect(plugin.getDefine().name).eq("CertApply"); - expect(cert.crt != null).eq(true); - }); -}); diff --git a/packages/core/pipeline/test/pipeline/plugins/deploy-to-cdn.test.ts b/packages/core/pipeline/test/pipeline/plugins/deploy-to-cdn.test.ts deleted file mode 100644 index 7d9706ba..00000000 --- a/packages/core/pipeline/test/pipeline/plugins/deploy-to-cdn.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { expect } from "chai"; -import "mocha"; -import { DeployCertToAliyunCDN } from "../../../src/plugin/plugins"; -import { pluginInitProps } from "../init.test"; - -describe("DeployToAliyunCDN", function () { - it("#execute", async function () { - this.timeout(120000); - const plugin = new DeployCertToAliyunCDN(); - // @ts-ignore - delete plugin.define; - - await plugin.doInit(pluginInitProps); - - const cert = await pluginInitProps.pipelineContext.get("cert"); - - await plugin.execute({ - cert, - domainName: "certd-cdn-upload.docmirror.cn", - }); - expect(plugin.getDefine().name).eq("DeployCertToAliyunCDN"); - }); -}); diff --git a/packages/plugins/plugin-aliyun/.eslintrc b/packages/plugins/plugin-aliyun/.eslintrc deleted file mode 100644 index c6ce67f2..00000000 --- a/packages/plugins/plugin-aliyun/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "extends": "standard", - "env": { - "mocha": true - }, - "overrides": [ - { - "files": ["*.test.js", "*.spec.js"], - "rules": { - "no-unused-expressions": "off" - } - } - ] -} diff --git a/packages/plugins/plugin-aliyun/.gitignore b/packages/plugins/plugin-aliyun/.gitignore deleted file mode 100644 index cbb386fa..00000000 --- a/packages/plugins/plugin-aliyun/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -.vscode/ -node_modules/ -npm-debug.log -yarn-error.log -yarn.lock -package-lock.json -/.idea/ diff --git a/packages/plugins/plugin-aliyun/package.json b/packages/plugins/plugin-aliyun/package.json deleted file mode 100644 index 5dc585f0..00000000 --- a/packages/plugins/plugin-aliyun/package.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "@certd/plugin-aliyun", - "version": "0.3.0", - "description": "", - "type": "module", - "main": "./dist/index.cjs", - "module": "./dist/fast-crud.es.js", - "scripts": { - "build": "rollup -c" - }, - "dependencies": { - "@alicloud/cs20151215": "^3.0.3", - "@alicloud/openapi-client": "^0.4.0", - "@alicloud/pop-core": "^1.7.10", - "@certd/api": "^0.3.0", - "dayjs": "^1.9.7", - "lodash-es": "^4.17.20" - }, - "devDependencies": { - "@certd/certd": "^0.3.0", - "@certd/plugin-common": "^0.3.0", - "chai": "^4.2.0", - "eslint": "^7.15.0", - "eslint-config-standard": "^16.0.2", - "eslint-plugin-import": "^2.22.1", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^4.2.1", - "mocha": "^8.2.1", - "rollup": "^3.2.3" - }, - "author": "Greper", - "license": "MIT", - "gitHead": "5fbd7742665c0a949333d805153e9b6af91c0a71" -} diff --git a/packages/plugins/plugin-aliyun/rollup.config.js b/packages/plugins/plugin-aliyun/rollup.config.js deleted file mode 100644 index c67735ae..00000000 --- a/packages/plugins/plugin-aliyun/rollup.config.js +++ /dev/null @@ -1,13 +0,0 @@ -export default { - input: 'src/index.js', - output: [ - { - file: 'dist/index.cjs', - format: 'cjs' - }, - { - file: 'dist/index.es.js', - format: 'es' - } - ] -} diff --git a/packages/plugins/plugin-aliyun/src/access-providers/aliyun.js b/packages/plugins/plugin-aliyun/src/access-providers/aliyun.js deleted file mode 100644 index 73c12170..00000000 --- a/packages/plugins/plugin-aliyun/src/access-providers/aliyun.js +++ /dev/null @@ -1,27 +0,0 @@ -export class AliyunAccessProvider { - static define () { - return { - name: 'aliyun', - title: '阿里云', - desc: '', - input: { - accessKeyId: { - component: { - placeholder: 'accessKeyId' - }, - rules: [{ required: true, message: '必填项' }] - }, - accessKeySecret: { - component: { - placeholder: 'accessKeySecret' - }, - rules: [{ required: true, message: '必填项' }] - - } - }, - output: { - - } - } - } -} diff --git a/packages/plugins/plugin-aliyun/src/dns-providers/aliyun.js b/packages/plugins/plugin-aliyun/src/dns-providers/aliyun.js deleted file mode 100644 index e7526f9f..00000000 --- a/packages/plugins/plugin-aliyun/src/dns-providers/aliyun.js +++ /dev/null @@ -1,130 +0,0 @@ -import { AbstractDnsProvider } from '@certd/api' -import Core from '@alicloud/pop-core' -import _ from 'lodash' -export class AliyunDnsProvider extends AbstractDnsProvider { - static define () { - return { - name: 'aliyun', - title: '阿里云', - desc: '', - input: { - accessProvider: { - title: '授权', - helper: '需要aliyun类型的授权', - component: { - name: 'access-selector', - type: 'aliyun' - }, - rules: [{ required: true, message: '必填项' }] - } - }, - output: { - - } - } - } - - constructor (args) { - super(args) - const { props } = args - const accessProvider = this.getAccessProvider(props.accessProvider) - this.client = new Core({ - accessKeyId: accessProvider.accessKeyId, - accessKeySecret: accessProvider.accessKeySecret, - endpoint: 'https://alidns.aliyuncs.com', - apiVersion: '2015-01-09' - }) - } - - async getDomainList () { - const params = { - RegionId: 'cn-hangzhou' - } - - const requestOption = { - method: 'POST' - } - - const ret = await this.client.request('DescribeDomains', params, requestOption) - return ret.Domains.Domain - } - - async matchDomain (dnsRecord) { - const list = await this.getDomainList() - let domain = null - for (const item of list) { - if (_.endsWith(dnsRecord, item.DomainName)) { - domain = item.DomainName - break - } - } - if (!domain) { - throw new Error('can not find Domain ,' + dnsRecord) - } - return domain - } - - async getRecords (domain, rr, value) { - const params = { - RegionId: 'cn-hangzhou', - DomainName: domain, - RRKeyWord: rr - } - if (value) { - params.ValueKeyWord = value - } - - const requestOption = { - method: 'POST' - } - - const ret = await this.client.request('DescribeDomainRecords', params, requestOption) - return ret.DomainRecords.Record - } - - async createRecord ({ fullRecord, type, value }) { - this.logger.info('添加域名解析:', fullRecord, value) - const domain = await this.matchDomain(fullRecord) - const rr = fullRecord.replace('.' + domain, '') - - const params = { - RegionId: 'cn-hangzhou', - DomainName: domain, - RR: rr, - Type: type, - Value: value - // Line: 'oversea' // 海外 - } - - const requestOption = { - method: 'POST' - } - - try { - const ret = await this.client.request('AddDomainRecord', params, requestOption) - this.logger.info('添加域名解析成功:', value, value, ret.RecordId) - return ret.RecordId - } catch (e) { - if (e.code === 'DomainRecordDuplicate') { - return - } - this.logger.info('添加域名解析出错', e) - throw e - } - } - - async removeRecord ({ fullRecord, type, value, record }) { - const params = { - RegionId: 'cn-hangzhou', - RecordId: record - } - - const requestOption = { - method: 'POST' - } - - const ret = await this.client.request('DeleteDomainRecord', params, requestOption) - this.logger.info('删除域名解析成功:', fullRecord, value, ret.RecordId) - return ret.RecordId - } -} diff --git a/packages/plugins/plugin-aliyun/src/index.js b/packages/plugins/plugin-aliyun/src/index.js deleted file mode 100644 index a7e9941c..00000000 --- a/packages/plugins/plugin-aliyun/src/index.js +++ /dev/null @@ -1,25 +0,0 @@ -import _ from 'lodash' - -import { AliyunDnsProvider } from './dns-providers/aliyun.js' -import { AliyunAccessProvider } from './access-providers/aliyun.js' -import { UploadCertToAliyun } from './plugins/upload-to-aliyun/index.js' -import { DeployCertToAliyunCDN } from './plugins/deploy-to-cdn/index.js' -import { DeployCertToAliyunAckIngress } from './plugins/deploy-to-ack-ingress/index.js' - -import { pluginRegistry, accessProviderRegistry, dnsProviderRegistry } from '@certd/api' - -export const Plugins = { - UploadCertToAliyun, - DeployCertToAliyunCDN, - DeployCertToAliyunAckIngress -} -export default { - install () { - _.forEach(Plugins, item => { - pluginRegistry.install(item) - }) - - accessProviderRegistry.install(AliyunAccessProvider) - dnsProviderRegistry.install(AliyunDnsProvider) - } -} diff --git a/packages/plugins/plugin-aliyun/src/plugins/abstract-aliyun.js b/packages/plugins/plugin-aliyun/src/plugins/abstract-aliyun.js deleted file mode 100644 index b5f1baff..00000000 --- a/packages/plugins/plugin-aliyun/src/plugins/abstract-aliyun.js +++ /dev/null @@ -1,9 +0,0 @@ -import { AbstractPlugin } from '@certd/api' - -export class AbstractAliyunPlugin extends AbstractPlugin { - checkRet (ret) { - if (ret.code != null) { - throw new Error('执行失败:', ret.Message) - } - } -} diff --git a/packages/plugins/plugin-aliyun/src/plugins/deploy-to-ack-ingress/index.js b/packages/plugins/plugin-aliyun/src/plugins/deploy-to-ack-ingress/index.js deleted file mode 100644 index fe5738fe..00000000 --- a/packages/plugins/plugin-aliyun/src/plugins/deploy-to-ack-ingress/index.js +++ /dev/null @@ -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 - } - } -} diff --git a/packages/plugins/plugin-aliyun/src/plugins/deploy-to-cdn/index.js b/packages/plugins/plugin-aliyun/src/plugins/deploy-to-cdn/index.js deleted file mode 100644 index f7f53c1e..00000000 --- a/packages/plugins/plugin-aliyun/src/plugins/deploy-to-cdn/index.js +++ /dev/null @@ -1,95 +0,0 @@ -import { AbstractAliyunPlugin } from '../abstract-aliyun.js' -import Core from '@alicloud/pop-core' -import dayjs from 'dayjs' - -const define = { - name: 'deployCertToAliyunCDN', - title: '部署到阿里云CDN', - input: { - domainName: { - title: 'cdn加速域名', - component: { - placeholder: 'cdn加速域名' - }, - required: true - }, - certName: { - title: '证书名称', - component: { - placeholder: '上传后将以此名称作为前缀' - } - }, - from: { - value: 'upload', - title: '证书来源', - required: true, - component: { - placeholder: '证书来源', - name: 'a-select', - options: [ - { value: 'upload', label: '直接上传' } - ] - } - }, - accessProvider: { - label: 'Access提供者', - type: [String, Object], - desc: 'access授权', - component: { - name: 'access-selector', - type: 'aliyun' - }, - required: true - } - }, - output: { - - } -} - -export class DeployCertToAliyunCDN extends AbstractAliyunPlugin { - static define () { - return define - } - - async execute ({ cert, props, context }) { - const accessProvider = this.getAccessProvider(props.accessProvider) - const client = this.getClient(accessProvider) - const params = this.buildParams(props, context, cert) - await this.doRequest(client, params) - } - - getClient (aliyunProvider) { - return new Core({ - accessKeyId: aliyunProvider.accessKeyId, - accessKeySecret: aliyunProvider.accessKeySecret, - endpoint: 'https://cdn.aliyuncs.com', - apiVersion: '2018-05-10' - }) - } - - buildParams (args, context, cert) { - const { certName, from, domainName } = args - const CertName = certName + '-' + dayjs().format('YYYYMMDDHHmmss') - - const params = { - RegionId: 'cn-hangzhou', - DomainName: domainName, - ServerCertificateStatus: 'on', - CertName: CertName, - CertType: from, - ServerCertificate: cert.crt, - PrivateKey: cert.key - } - return params - } - - async doRequest (client, params) { - const requestOption = { - method: 'POST' - } - const ret = await client.request('SetDomainServerCertificate', params, requestOption) - this.checkRet(ret) - this.logger.info('设置cdn证书成功:', ret.RequestId) - } -} diff --git a/packages/plugins/plugin-aliyun/src/plugins/upload-to-aliyun/index.js b/packages/plugins/plugin-aliyun/src/plugins/upload-to-aliyun/index.js deleted file mode 100644 index e0128000..00000000 --- a/packages/plugins/plugin-aliyun/src/plugins/upload-to-aliyun/index.js +++ /dev/null @@ -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} - */ - 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 - } -} diff --git a/packages/plugins/plugin-aliyun/src/utils/index.js b/packages/plugins/plugin-aliyun/src/utils/index.js deleted file mode 100644 index 0c7dd220..00000000 --- a/packages/plugins/plugin-aliyun/src/utils/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export const ZoneOptions = [ - { value: 'cn-hangzhou' } -] diff --git a/packages/plugins/plugin-aliyun/test/dns-providers/aliyun.cert.test.js b/packages/plugins/plugin-aliyun/test/dns-providers/aliyun.cert.test.js deleted file mode 100644 index cb67cef4..00000000 --- a/packages/plugins/plugin-aliyun/test/dns-providers/aliyun.cert.test.js +++ /dev/null @@ -1,22 +0,0 @@ -import pkg from 'chai' -import { createOptions } from '../../../../../test/options.js' -import { Certd } from '@certd/certd' -import PluginAliyun from '../../src/index.js' - -// 安装默认插件和授权提供者 -PluginAliyun.install() -const { expect } = pkg -describe('AliyunDnsProvider', function () { - it('#申请证书-aliyun', async function () { - this.timeout(300000) - const options = createOptions() - options.args = { forceCert: true, test: false } - const certd = new Certd(options) - const cert = await certd.certApply() - expect(cert).ok - expect(cert.crt).ok - expect(cert.key).ok - expect(cert.detail).ok - expect(cert.expires).ok - }) -}) diff --git a/packages/plugins/plugin-aliyun/test/dns-providers/aliyun.test.js b/packages/plugins/plugin-aliyun/test/dns-providers/aliyun.test.js deleted file mode 100644 index 559ec960..00000000 --- a/packages/plugins/plugin-aliyun/test/dns-providers/aliyun.test.js +++ /dev/null @@ -1,39 +0,0 @@ -import pkg from 'chai' -import { AliyunDnsProvider } from '../../src/dns-providers/aliyun.js' -import { createOptions } from '../../../../../test/options.js' -const { expect } = pkg - -export function getPluginOptions () { - const options = createOptions() - return { accessProviders: options.accessProviders, props: options.cert.dnsProvider } -} - -describe('AliyunDnsProvider', function () { - it('#getDomainList', async function () { - const options = getPluginOptions() - const aliyunDnsProvider = new AliyunDnsProvider(options) - const domainList = await aliyunDnsProvider.getDomainList() - console.log('domainList', domainList) - expect(domainList.length).gt(0) - }) - - it('#getRecords', async function () { - const options = getPluginOptions() - const aliyunDnsProvider = new AliyunDnsProvider(options) - const recordList = await aliyunDnsProvider.getRecords('docmirror.cn', '*') - console.log('recordList', recordList) - expect(recordList.length).gt(0) - }) - - it('#createAndRemoveRecord', async function () { - const options = getPluginOptions() - const aliyunDnsProvider = new AliyunDnsProvider(options) - const record = await aliyunDnsProvider.createRecord({ fullRecord: '___certd___.__test__.docmirror.cn', type: 'TXT', value: 'aaaa' }) - console.log('recordId', record) - expect(record != null).ok - - const recordId = await aliyunDnsProvider.removeRecord({ fullRecord: '___certd___.__test__.docmirror.cn', type: 'TXT', value: 'aaaa', record }) - console.log('recordId', recordId) - expect(recordId != null).ok - }) -}) diff --git a/packages/plugins/plugin-aliyun/test/options.js b/packages/plugins/plugin-aliyun/test/options.js deleted file mode 100644 index ae03e286..00000000 --- a/packages/plugins/plugin-aliyun/test/options.js +++ /dev/null @@ -1,42 +0,0 @@ -import _ from 'lodash-es' -import optionsPrivate from '../../../test/options.private.mjs' -const defaultOptions = { - version: '1.0.0', - args: { - directory: 'test', - dry: false - }, - accessProviders: { - aliyun: { - providerType: 'aliyun', - accessKeyId: '', - accessKeySecret: '' - }, - myLinux: { - providerType: 'SSH', - username: 'xxx', - password: 'xxx', - host: '1111.com', - port: 22, - publicKey: '' - } - }, - cert: { - domains: ['*.docmirror.club', 'docmirror.club'], - email: 'xiaojunnuo@qq.com', - dnsProvider: { type: 'aliyun', accessProvider: 'aliyun' }, - certProvider: 'letsencrypt', - csrInfo: { - country: 'CN', - state: 'GuangDong', - locality: 'ShengZhen', - organization: 'CertD Org.', - organizationUnit: 'IT Department', - emailAddress: 'xiaojunnuo@qq.com' - } - } -} - -_.merge(defaultOptions, optionsPrivate) - -export default defaultOptions diff --git a/packages/plugins/plugin-aliyun/test/plugins/deploy-to-ack-ingress-nginx.test.js b/packages/plugins/plugin-aliyun/test/plugins/deploy-to-ack-ingress-nginx.test.js deleted file mode 100644 index 300632a4..00000000 --- a/packages/plugins/plugin-aliyun/test/plugins/deploy-to-ack-ingress-nginx.test.js +++ /dev/null @@ -1,67 +0,0 @@ -import pkg from 'chai' -import { DeployCertToAliyunAckIngress } from '../../src/plugins/deploy-to-ack-ingress/index.js' -import { Certd } from '@certd/certd' -import { createOptions } from '../../../../../test/options.js' -import { K8sClient } from '@certd/plugin-common' - -const { expect } = pkg - -async function getOptions () { - const options = createOptions() - options.args.test = false - options.cert.email = 'xiaojunnuo@qq.com' - options.cert.domains = ['*.docmirror.cn'] - const certd = new Certd(options) - const cert = await certd.readCurrentCert() - const context = {} - const deployOpts = { - accessProviders: options.accessProviders, - cert, - props: { - accessProvider: 'aliyun-yonsz-prod', - regionId: 'cn-shanghai', - clusterId: 'c9e107ca518314f70973636965037fc00', - secretName: 'default-ingress-secret1638601684896', - namespace: 'default', - ingressClass: 'nginx' - }, - context - } - return { options, deployOpts } -} - -describe('DeployCertToAliyunAckIngressNginx', function () { - it('#getAliyunSecrets', async function () { - this.timeout(50000) - const { options, deployOpts } = await getOptions() - const plugin = new DeployCertToAliyunAckIngress(options) - const ackClient = plugin.getClient(options.accessProviders[deployOpts.props.accessProvider], deployOpts.props.regionId) - const kubeConfig = await plugin.getKubeConfig(ackClient, deployOpts.props.clusterId, false) - - const k8sClient = new K8sClient(kubeConfig) - const secrets = await k8sClient.getSecret({ namespace: 'default' }) - - console.log('secrets:', secrets) - }) - it('#getAliyunIngreses', async function () { - this.timeout(50000) - const { options, deployOpts } = await getOptions() - const plugin = new DeployCertToAliyunAckIngress(options) - const ackClient = plugin.getClient(options.accessProviders[deployOpts.props.accessProvider], deployOpts.props.regionId) - const kubeConfig = await plugin.getKubeConfig(ackClient, deployOpts.props.clusterId, false) - - const k8sClient = new K8sClient(kubeConfig) - const list = await k8sClient.getIngressList({ namespace: 'default' }) - - console.log('list:', list) - }) - it('#execute', async function () { - this.timeout(5000) - - const { options, deployOpts } = await getOptions() - const plugin = new DeployCertToAliyunAckIngress(options) - - const ret = await plugin.doExecute(deployOpts) - console.log('success', ret) - }) -}) diff --git a/packages/plugins/plugin-aliyun/test/plugins/deploy-to-cdn.test.js b/packages/plugins/plugin-aliyun/test/plugins/deploy-to-cdn.test.js deleted file mode 100644 index ab11e4fb..00000000 --- a/packages/plugins/plugin-aliyun/test/plugins/deploy-to-cdn.test.js +++ /dev/null @@ -1,21 +0,0 @@ -import pkg from 'chai' -import { DeployCertToAliyunCDN } from '../../src/plugins/deploy-to-cdn/index.js' -import { Certd } from '@certd/certd' -import { createOptions } from '../../../../../test/options.js' -const { expect } = pkg - -describe('DeployToAliyunCDN', function () { - it('#execute', async function () { - this.timeout(5000) - const options = createOptions() - const plugin = new DeployCertToAliyunCDN(options) - options.cert.domains = ['*.docmirror.cn', 'docmirror.cn'] - const certd = new Certd(options) - const cert = await certd.readCurrentCert() - const ret = await plugin.doExecute({ - cert, - props: { domainName: 'certd-cdn-upload.docmirror.cn', certName: 'certd部署测试', from: 'cas', accessProvider: 'aliyun' } - }) - console.log('context:', context, ret) - }) -}) diff --git a/packages/plugins/plugin-aliyun/test/plugins/upload-to-aliyun.test.js b/packages/plugins/plugin-aliyun/test/plugins/upload-to-aliyun.test.js deleted file mode 100644 index 9fb59f3a..00000000 --- a/packages/plugins/plugin-aliyun/test/plugins/upload-to-aliyun.test.js +++ /dev/null @@ -1,27 +0,0 @@ -import pkg from 'chai' -import { UploadCertToAliyun } from '../../src/plugins/upload-to-aliyun/index.js' -import { Certd } from '@certd/certd' -import { createOptions } from '../../../../../test/options.js' -const { expect } = pkg -describe('PluginUploadToAliyun', function () { - it('#execute', async function () { - this.timeout(5000) - const options = createOptions() - options.cert.email = 'xiaojunnuo@qq.com' - options.cert.domains = ['_.docmirror.cn'] - const plugin = new UploadCertToAliyun(options) - const certd = new Certd(options) - const cert = await certd.readCurrentCert() - const context = {} - const deployOpts = { - cert, - props: { accessProvider: 'aliyun' }, - context - } - await plugin.doExecute(deployOpts) - console.log('context:', context) - - // await plugin.sleep(1000) - // await plugin.rollback(deployOpts) - }) -}) diff --git a/packages/plugins/plugin-cert/test/pipeline/access-service-test.ts b/packages/plugins/plugin-cert/test/pipeline/access-service-test.ts new file mode 100644 index 00000000..86f0daad --- /dev/null +++ b/packages/plugins/plugin-cert/test/pipeline/access-service-test.ts @@ -0,0 +1,9 @@ +import { AbstractAccess, IAccessService } from "@certd/pipeline"; +import { aliyunSecret } from "../user.secret"; +export class AccessServiceTest implements IAccessService { + async getById(id: any): Promise { + return { + ...aliyunSecret, + } as any; + } +} diff --git a/packages/plugins/plugin-common/.eslintrc b/packages/plugins/plugin-common/.eslintrc deleted file mode 100644 index c6ce67f2..00000000 --- a/packages/plugins/plugin-common/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "extends": "standard", - "env": { - "mocha": true - }, - "overrides": [ - { - "files": ["*.test.js", "*.spec.js"], - "rules": { - "no-unused-expressions": "off" - } - } - ] -} diff --git a/packages/plugins/plugin-common/.gitignore b/packages/plugins/plugin-common/.gitignore deleted file mode 100644 index cbb386fa..00000000 --- a/packages/plugins/plugin-common/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -.vscode/ -node_modules/ -npm-debug.log -yarn-error.log -yarn.lock -package-lock.json -/.idea/ diff --git a/packages/plugins/plugin-common/package.json b/packages/plugins/plugin-common/package.json deleted file mode 100644 index 9ef21fa2..00000000 --- a/packages/plugins/plugin-common/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "@certd/plugin-common", - "version": "0.3.0", - "description": "", - "type": "module", - "main": "./dist/index.cjs", - "module": "./dist/fast-crud.es.js", - "scripts": { - "build": "rollup -c" - }, - "dependencies": { - "@certd/api": "^0.3.0", - "kubernetes-client": "^9.0.0" - }, - "devDependencies": { - "@certd/certd": "^0.3.0", - "chai": "^4.2.0", - "eslint": "^7.15.0", - "eslint-config-standard": "^16.0.2", - "eslint-plugin-import": "^2.22.1", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^4.2.1", - "mocha": "^8.2.1", - "rollup": "^3.2.3" - }, - "author": "Greper", - "license": "MIT", - "gitHead": "5fbd7742665c0a949333d805153e9b6af91c0a71" -} diff --git a/packages/plugins/plugin-common/rollup.config.js b/packages/plugins/plugin-common/rollup.config.js deleted file mode 100644 index c67735ae..00000000 --- a/packages/plugins/plugin-common/rollup.config.js +++ /dev/null @@ -1,13 +0,0 @@ -export default { - input: 'src/index.js', - output: [ - { - file: 'dist/index.cjs', - format: 'cjs' - }, - { - file: 'dist/index.es.js', - format: 'es' - } - ] -} diff --git a/packages/plugins/plugin-common/src/index.js b/packages/plugins/plugin-common/src/index.js deleted file mode 100644 index 49dfa5bb..00000000 --- a/packages/plugins/plugin-common/src/index.js +++ /dev/null @@ -1 +0,0 @@ -export { K8sClient } from './lib/k8s.client.js' diff --git a/packages/plugins/plugin-common/src/lib/k8s.client.js b/packages/plugins/plugin-common/src/lib/k8s.client.js deleted file mode 100644 index 919c172e..00000000 --- a/packages/plugins/plugin-common/src/lib/k8s.client.js +++ /dev/null @@ -1,114 +0,0 @@ -import kubernetesClient from 'kubernetes-client' -import { util } from '@certd/api' -import Request from 'kubernetes-client/backends/request/index.js' -import dns from 'dns' -const { KubeConfig, Client } = kubernetesClient -const logger = util.logger - -export class K8sClient { - constructor (kubeConfigStr) { - this.kubeConfigStr = kubeConfigStr - this.init() - } - - init () { - const kubeconfig = new KubeConfig() - kubeconfig.loadFromString(this.kubeConfigStr) - const reqOpts = { kubeconfig, request: {} } - if (this.lookup) { - reqOpts.request.lookup = this.lookup - } - - const backend = new Request(reqOpts) - this.client = new Client({ backend, version: '1.13' }) - } - - /** - * - * @param localRecords { [domain]:{ip:'xxx.xx.xxx'} } - */ - setLookup (localRecords) { - this.lookup = (hostnameReq, options, callback) => { - logger.info('custom lookup', hostnameReq, localRecords) - if (localRecords[hostnameReq]) { - logger.info('local record', hostnameReq, localRecords[hostnameReq]) - callback(null, localRecords[hostnameReq].ip, 4) - } else { - dns.lookup(hostnameReq, options, callback) - } - } - this.init() - } - - /** - * 查询 secret列表 - * @param opts = {namespace:default} - * @returns secretsList - */ - async getSecret (opts = {}) { - const namespace = opts.namespace || 'default' - const secrets = await this.client.api.v1.namespaces(namespace).secrets.get() - return secrets - } - - /** - * 创建Secret - * @param opts {namespace:default, body:yamlStr} - * @returns {Promise<*>} - */ - async createSecret (opts) { - const namespace = opts.namespace || 'default' - const created = await this.client.api.v1.namespaces(namespace).secrets.post({ - body: opts.body - }) - logger.info('new secrets:', created) - return created - } - - async updateSecret (opts) { - const namespace = opts.namespace || 'default' - const secretName = opts.secretName - if (secretName == null) { - throw new Error('secretName 不能为空') - } - return await this.client.api.v1.namespaces(namespace).secrets(secretName).put({ - body: opts.body - }) - } - - async patchSecret (opts) { - const namespace = opts.namespace || 'default' - const secretName = opts.secretName - if (secretName == null) { - throw new Error('secretName 不能为空') - } - return await this.client.api.v1.namespaces(namespace).secrets(secretName).patch({ - body: opts.body - }) - } - - async getIngressList (opts) { - const namespace = opts.namespace || 'default' - return await this.client.apis.extensions.v1beta1.namespaces(namespace).ingresses.get() - } - - async getIngress (opts) { - const namespace = opts.namespace || 'default' - const ingressName = opts.ingressName - if (!ingressName) { - throw new Error('ingressName 不能为空') - } - return await this.client.apis.extensions.v1beta1.namespaces(namespace).ingresses(ingressName).get() - } - - async patchIngress (opts) { - const namespace = opts.namespace || 'default' - const ingressName = opts.ingressName - if (!ingressName) { - throw new Error('ingressName 不能为空') - } - return await this.client.apis.extensions.v1beta1.namespaces(namespace).ingresses(ingressName).patch({ - body: opts.body - }) - } -} diff --git a/packages/plugins/plugin-host/.eslintrc b/packages/plugins/plugin-host/.eslintrc index c6ce67f2..218e910f 100644 --- a/packages/plugins/plugin-host/.eslintrc +++ b/packages/plugins/plugin-host/.eslintrc @@ -1,14 +1,21 @@ { - "extends": "standard", + "parser": "@typescript-eslint/parser", + "plugins": [ + "@typescript-eslint" + ], + "extends": [ + "plugin:@typescript-eslint/recommended", + "plugin:prettier/recommended", + "prettier" + ], "env": { "mocha": true }, - "overrides": [ - { - "files": ["*.test.js", "*.spec.js"], - "rules": { - "no-unused-expressions": "off" - } - } - ] + "rules": { + "@typescript-eslint/ban-ts-comment": "off", + "@typescript-eslint/ban-ts-ignore": "off", + "@typescript-eslint/no-explicit-any": "off", +// "no-unused-expressions": "off", + "max-len": [0, 160, 2, { "ignoreUrls": true }] + } } diff --git a/packages/plugins/plugin-host/.gitignore b/packages/plugins/plugin-host/.gitignore index cbb386fa..5ee37c64 100644 --- a/packages/plugins/plugin-host/.gitignore +++ b/packages/plugins/plugin-host/.gitignore @@ -1,7 +1,26 @@ -.vscode/ -node_modules/ -npm-debug.log -yarn-error.log -yarn.lock -package-lock.json -/.idea/ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +test/user.secret.ts \ No newline at end of file diff --git a/packages/plugins/plugin-host/package.json b/packages/plugins/plugin-host/package.json index 6d1ceb86..989e04db 100644 --- a/packages/plugins/plugin-host/package.json +++ b/packages/plugins/plugin-host/package.json @@ -1,31 +1,45 @@ { - "name": "@certd/plugin-host", - "version": "0.3.0", - "description": "", - "type": "module", - "main": "./dist/index.cjs", - "module": "./dist/fast-crud.es.js", - "scripts": { - "build": "rollup -c" - }, - "dependencies": { - "@certd/api": "^0.3.0", - "dayjs": "^1.9.7", - "lodash-es": "^4.17.20", - "ssh2": "^0.8.9" - }, - "devDependencies": { - "@certd/certd": "^0.3.0", - "chai": "^4.2.0", - "eslint": "^7.15.0", - "eslint-config-standard": "^16.0.2", - "eslint-plugin-import": "^2.22.1", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^4.2.1", - "mocha": "^8.2.1", - "rollup": "^3.2.3" - }, - "author": "Greper", - "license": "MIT", - "gitHead": "5fbd7742665c0a949333d805153e9b6af91c0a71" + "name": "@certd/plugin-host", + "private": true, + "version": "0.3.0", + "main": "./src/index.ts", + "module": "./dist/plugin-aliyun.mjs", + "types": "./dist/es/plugin-aliyun.d.ts", + "scripts": { + "dev": "vite", + "build": "vue-tsc --noEmit && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@certd/pipeline": "^0.3.0", + "ssh2": "^0.8.9" + }, + "devDependencies": { + "log4js": "^6.3.0", + "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/mocha": "^10.0.0", + "@types/node-forge": "^1.3.0", + "@typescript-eslint/eslint-plugin": "^5.38.1", + "@typescript-eslint/parser": "^5.38.1", + "chai": "^4.3.6", + "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", + "log4js": "^6.3.0", + "mocha": "^10.1.0", + "ts-node": "^10.9.1", + "typescript": "^4.8.4", + "vite": "^3.1.0" + } } diff --git a/packages/plugins/plugin-host/rollup.config.js b/packages/plugins/plugin-host/rollup.config.js deleted file mode 100644 index c67735ae..00000000 --- a/packages/plugins/plugin-host/rollup.config.js +++ /dev/null @@ -1,13 +0,0 @@ -export default { - input: 'src/index.js', - output: [ - { - file: 'dist/index.cjs', - format: 'cjs' - }, - { - file: 'dist/index.es.js', - format: 'es' - } - ] -} diff --git a/packages/plugins/plugin-host/src/access-providers/ssh.js b/packages/plugins/plugin-host/src/access-providers/ssh.js deleted file mode 100644 index 6fe9c3f5..00000000 --- a/packages/plugins/plugin-host/src/access-providers/ssh.js +++ /dev/null @@ -1,26 +0,0 @@ -export class SSHAccessProvider { - static define () { - return { - name: 'ssh', - title: '主机', - desc: '', - input: { - host: { rules: [{ required: true, message: '此项必填' }] }, - port: { - title: '端口', - value: '22', - rules: [{ required: true, message: '此项必填' }] - }, - username: { - value: 'root', - rules: [{ required: true, message: '此项必填' }] - }, - password: { helper: '登录密码' }, - privateKey: { - title: '密钥', - helper: '密钥或密码必填一项' - } - } - } - } -} diff --git a/packages/plugins/plugin-host/src/index.js b/packages/plugins/plugin-host/src/index.js deleted file mode 100644 index 1c975a04..00000000 --- a/packages/plugins/plugin-host/src/index.js +++ /dev/null @@ -1,22 +0,0 @@ -import _ from 'lodash' - -import { SSHAccessProvider } from './access-providers/ssh.js' - -import { UploadCertToHost } from './plugins/upload-to-host/index.js' -import { HostShellExecute } from './plugins/host-shell-execute/index.js' - -import { pluginRegistry, accessProviderRegistry } from '@certd/api' - -export const DefaultPlugins = { - UploadCertToHost, - HostShellExecute -} -export default { - install () { - _.forEach(DefaultPlugins, item => { - pluginRegistry.install(item) - }) - - accessProviderRegistry.install(SSHAccessProvider) - } -} diff --git a/packages/plugins/plugin-host/src/lib/ssh.ts b/packages/plugins/plugin-host/src/lib/ssh.ts new file mode 100644 index 00000000..191b11c5 --- /dev/null +++ b/packages/plugins/plugin-host/src/lib/ssh.ts @@ -0,0 +1,148 @@ +import ssh2 from "ssh2"; +import path from "path"; +import _ from "lodash"; +import { Logger } from "log4js"; +export class SshClient { + logger: Logger; + constructor(logger: Logger) { + this.logger = logger; + } + /** + * + * @param connectConf + { + host: '192.168.100.100', + port: 22, + username: 'frylock', + password: 'nodejsrules' + } + * @param options + */ + uploadFiles(options: { connectConf: any; transports: any; sudo: boolean }) { + const { connectConf, transports, sudo } = options; + const conn = new ssh2.Client(); + + return new Promise((resolve, reject) => { + conn + .on("ready", () => { + this.logger.info("连接服务器成功"); + conn.sftp(async (err: Error, sftp: any) => { + if (err) { + throw err; + } + + try { + for (const transport of transports) { + this.logger.info("上传文件:", JSON.stringify(transport)); + const sudoCmd = sudo ? "sudo" : ""; + await this.exec({ connectConf, script: `${sudoCmd} mkdir -p ${path.dirname(transport.remotePath)} ` }); + await this.fastPut({ sftp, ...transport }); + } + resolve({}); + } catch (e) { + reject(e); + } finally { + conn.end(); + } + }); + }) + .connect(connectConf); + }); + } + + exec(options: { connectConf: any; script: string | Array }) { + let { script } = options; + const { connectConf } = options; + if (_.isArray(script)) { + script = script.join("\n"); + } + this.logger.info("执行命令:", script); + return new Promise((resolve, reject) => { + this.connect({ + connectConf, + onReady: (conn: any) => { + conn.exec(script, (err: Error, stream: any) => { + if (err) { + reject(err); + return; + } + let data: any = null; + stream + .on("close", (code: any, signal: any) => { + this.logger.info(`[${connectConf.host}][close]:code:${code}`); + data = data ? data.toString() : null; + if (code === 0) { + resolve(data); + } else { + reject(new Error(data)); + } + conn.end(); + }) + .on("data", (ret: any) => { + this.logger.info(`[${connectConf.host}][info]: ` + ret); + data = ret; + }) + .stderr.on("data", (err: Error) => { + this.logger.info(`[${connectConf.host}][error]: ` + err); + data = err; + }); + }); + }, + }); + }); + } + + shell(options: { connectConf: any; script: string }) { + const { connectConf, script } = options; + return new Promise((resolve, reject) => { + this.connect({ + connectConf, + onReady: (conn: any) => { + conn.shell((err: Error, stream: any) => { + if (err) { + reject(err); + return; + } + const output: any = []; + stream + .on("close", () => { + this.logger.info("Stream :: close"); + conn.end(); + resolve(output); + }) + .on("data", (data: any) => { + this.logger.info("" + data); + output.push("" + data); + }); + stream.end(script + "\nexit\n"); + }); + }, + }); + }); + } + + connect(options: { connectConf: any; onReady: any }) { + const { connectConf, onReady } = options; + const conn = new ssh2.Client(); + conn + .on("ready", () => { + this.logger.info("Client :: ready"); + onReady(conn); + }) + .connect(connectConf); + return conn; + } + + fastPut(options: { sftp: any; localPath: string; remotePath: string }) { + const { sftp, localPath, remotePath } = options; + return new Promise((resolve, reject) => { + sftp.fastPut(localPath, remotePath, (err: Error) => { + if (err) { + reject(err); + return; + } + resolve({}); + }); + }); + } +} diff --git a/packages/plugins/plugin-host/src/plugin/host-shell-execute/index.ts b/packages/plugins/plugin-host/src/plugin/host-shell-execute/index.ts new file mode 100644 index 00000000..f9c31008 --- /dev/null +++ b/packages/plugins/plugin-host/src/plugin/host-shell-execute/index.ts @@ -0,0 +1,54 @@ +import { IsTask, TaskInput, TaskOutput, TaskPlugin, AbstractPlugin, RunStrategy } from "@certd/pipeline"; +import { SshClient } from "../../lib/ssh"; + +@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", + }, + }, + }, + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed, + }, + }, + output: {}, + }; +}) +export class HostShellExecutePlugin extends AbstractPlugin implements TaskPlugin { + async execute(input: TaskInput): Promise { + const { script, accessId } = input; + const connectConf = this.accessService.getById(accessId); + const sshClient = new SshClient(this.logger); + const ret = await sshClient.exec({ + connectConf, + script, + }); + this.logger.info("exec res:", ret); + return {}; + } +} diff --git a/packages/plugins/plugin-host/src/plugin/upload-to-host/index.ts b/packages/plugins/plugin-host/src/plugin/upload-to-host/index.ts new file mode 100644 index 00000000..d99d94c9 --- /dev/null +++ b/packages/plugins/plugin-host/src/plugin/upload-to-host/index.ts @@ -0,0 +1,81 @@ +import { IsTask, TaskInput, TaskOutput, TaskPlugin, AbstractPlugin, RunStrategy } from "@certd/pipeline"; +import { SshClient } from "../../lib/ssh"; + +@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", + }, + }, + }, + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed, + }, + }, + output: { + hostCrtPath: { + title: "上传成功后的证书路径", + }, + hostKeyPath: { + title: "上传成功后的私钥路径", + }, + }, + }; +}) +export class UploadCertToHostPlugin extends AbstractPlugin implements TaskPlugin { + async execute(input: TaskInput): Promise { + const { crtPath, keyPath, cert, accessId, sudo } = input; + const connectConf = this.accessService.getById(accessId); + const sshClient = new SshClient(this.logger); + await sshClient.uploadFiles({ + connectConf, + transports: [ + { + localPath: cert.crtPath, + remotePath: crtPath, + }, + { + localPath: cert.keyPath, + remotePath: keyPath, + }, + ], + sudo, + }); + this.logger.info("证书上传成功:crtPath=", crtPath, ",keyPath=", keyPath); + + return { + hostCrtPath: crtPath, + hostKeyPath: keyPath, + }; + } +} diff --git a/packages/plugins/plugin-host/src/plugins/abstract-host.js b/packages/plugins/plugin-host/src/plugins/abstract-host.js deleted file mode 100644 index 58ca547e..00000000 --- a/packages/plugins/plugin-host/src/plugins/abstract-host.js +++ /dev/null @@ -1,9 +0,0 @@ -import { AbstractPlugin } from '@certd/api' - -export class AbstractHostPlugin extends AbstractPlugin { - checkRet (ret) { - if (ret.code != null) { - throw new Error('执行失败:', ret.Message) - } - } -} diff --git a/packages/plugins/plugin-host/src/plugins/host-shell-execute/index.js b/packages/plugins/plugin-host/src/plugins/host-shell-execute/index.js deleted file mode 100644 index 15fc1c3d..00000000 --- a/packages/plugins/plugin-host/src/plugins/host-shell-execute/index.js +++ /dev/null @@ -1,57 +0,0 @@ -import { AbstractHostPlugin } from '../abstract-host.js' -import { SshClient } from '../ssh.js' -export class HostShellExecute extends AbstractHostPlugin { - /** - * 插件定义 - * 名称 - * 入参 - * 出参 - */ - static define () { - return { - name: 'hostShellExecute', - title: '执行远程主机脚本命令', - input: { - accessProvider: { - title: '主机登录配置', - helper: '登录', - component: { - name: 'access-selector', - type: 'ssh' - }, - required: true - }, - script: { - title: 'shell脚本命令', - component: { - name: 'a-textarea' - } - } - }, - output: { - - } - } - } - - async execute ({ cert, props, context }) { - const { script, accessProvider } = props - const connectConf = this.getAccessProvider(accessProvider) - const sshClient = new SshClient() - const ret = await sshClient.exec({ - connectConf, - script - }) - return ret - } - - /** - * @param cert - * @param props - * @param context - * @returns {Promise} - */ - async rollback ({ cert, props, context }) { - - } -} diff --git a/packages/plugins/plugin-host/src/plugins/ssh.js b/packages/plugins/plugin-host/src/plugins/ssh.js deleted file mode 100644 index 8dc40351..00000000 --- a/packages/plugins/plugin-host/src/plugins/ssh.js +++ /dev/null @@ -1,130 +0,0 @@ -import ssh2 from 'ssh2' -import path from 'path' -import { util } from '@certd/api' -import _ from 'lodash' -const logger = util.logger -export class SshClient { - /** - * - * @param connectConf - { - host: '192.168.100.100', - port: 22, - username: 'frylock', - password: 'nodejsrules' - } - * @param transports - */ - uploadFiles ({ connectConf, transports, sudo = false }) { - const conn = new ssh2.Client() - - return new Promise((resolve, reject) => { - conn.on('ready', () => { - logger.info('连接服务器成功') - conn.sftp(async (err, sftp) => { - if (err) { - throw err - } - - try { - for (const transport of transports) { - logger.info('上传文件:', JSON.stringify(transport)) - sudo = sudo ? 'sudo' : '' - await this.exec({ connectConf, script: `${sudo} mkdir -p ${path.dirname(transport.remotePath)} ` }) - await this.fastPut({ sftp, ...transport }) - } - resolve() - } catch (e) { - reject(e) - } finally { - conn.end() - } - }) - }).connect(connectConf) - }) - } - - exec ({ connectConf, script }) { - if (_.isArray(script)) { - script = script.join('\n') - } - console.log('执行命令:', script) - return new Promise((resolve, reject) => { - this.connect({ - connectConf, - onReady: (conn) => { - conn.exec(script, (err, stream) => { - if (err) { - reject(err) - return - } - let data = null - stream.on('close', (code, signal) => { - console.log(`[${connectConf.host}][close]:code:${code}`) - data = data ? data.toString() : null - if (code === 0) { - resolve(data) - } else { - reject(new Error(data)) - } - conn.end() - }).on('data', (ret) => { - console.log(`[${connectConf.host}][info]: ` + ret) - data = ret - }).stderr.on('data', (err) => { - console.log(`[${connectConf.host}][error]: ` + err) - data = err - }) - }) - } - }) - }) - } - - shell ({ connectConf, script }) { - return new Promise((resolve, reject) => { - this.connect({ - connectConf, - onReady: (conn) => { - conn.shell((err, stream) => { - if (err) { - reject(err) - return - } - const output = [] - stream.on('close', () => { - logger.info('Stream :: close') - conn.end() - resolve(output) - }).on('data', (data) => { - logger.info('' + data) - output.push('' + data) - }) - stream.end(script + '\nexit\n') - }) - } - }) - }) - } - - connect ({ connectConf, onReady }) { - const conn = new ssh2.Client() - conn.on('ready', () => { - console.log('Client :: ready') - onReady(conn) - }).connect(connectConf) - return conn - } - - fastPut ({ sftp, localPath, remotePath }) { - return new Promise((resolve, reject) => { - sftp.fastPut(localPath, remotePath, (err) => { - if (err) { - reject(err) - return - } - resolve() - }) - }) - } -} diff --git a/packages/plugins/plugin-host/src/plugins/upload-to-host/index.js b/packages/plugins/plugin-host/src/plugins/upload-to-host/index.js deleted file mode 100644 index 2b39ca72..00000000 --- a/packages/plugins/plugin-host/src/plugins/upload-to-host/index.js +++ /dev/null @@ -1,85 +0,0 @@ -import { AbstractHostPlugin } from '../abstract-host.js' -import { SshClient } from '../ssh.js' -export class UploadCertToHost extends AbstractHostPlugin { - /** - * 插件定义 - * 名称 - * 入参 - * 出参 - */ - static define () { - return { - name: 'uploadCertToHost', - title: '上传证书到主机', - input: { - crtPath: { - title: '证书保存路径' - }, - keyPath: { - title: '私钥保存路径' - }, - accessProvider: { - title: '主机登录配置', - helper: 'access授权', - component: { - name: 'access-selector', - type: 'ssh' - }, - rules: [{ required: true, message: '此项必填' }] - }, - sudo: { - title: '是否sudo', - component: { - name: 'a-checkbox', - vModel: 'checked' - } - } - }, - output: { - hostCrtPath: { - helper: '上传成功后的证书路径' - }, - hostKeyPath: { - helper: '上传成功后的私钥路径' - } - } - } - } - - async execute ({ cert, props, context }) { - const { crtPath, keyPath, accessProvider } = props - const connectConf = this.getAccessProvider(accessProvider) - const sshClient = new SshClient() - await sshClient.uploadFiles({ - connectConf, - transports: [ - { - localPath: cert.crtPath, - remotePath: crtPath - }, - { - localPath: cert.keyPath, - remotePath: keyPath - } - ] - }) - this.logger.info('证书上传成功:crtPath=', crtPath, ',keyPath=', keyPath) - - context.hostCrtPath = crtPath - context.hostKeyPath = keyPath - return { - hostCrtPath: crtPath, - hostKeyPath: keyPath - } - } - - /** - * @param cert - * @param props - * @param context - * @returns {Promise} - */ - async rollback ({ cert, props, context }) { - - } -} diff --git a/packages/plugins/plugin-host/test/options.js b/packages/plugins/plugin-host/test/options.js deleted file mode 100644 index 3391e99f..00000000 --- a/packages/plugins/plugin-host/test/options.js +++ /dev/null @@ -1,42 +0,0 @@ -import _ from 'lodash-es' -import optionsPrivate from '../../../test/options.private.mjs' -const defaultOptions = { - version: '1.0.0', - args: { - directory: 'test', - dry: false - }, - accessProviders: { - aliyun: { - providerType: 'aliyun', - accessKeyId: '', - accessKeySecret: '' - }, - myLinux: { - providerType: 'SSH', - username: 'xxx', - password: 'xxx', - host: '1111.com', - port: 22, - publicKey: '' - } - }, - cert: { - domains: ['*.docmirror.club', 'docmirror.club'], - email: 'xiaojunnuo@qq.com', - dnsProvider: 'aliyun', - certProvider: 'letsencrypt', - csrInfo: { - country: 'CN', - state: 'GuangDong', - locality: 'ShengZhen', - organization: 'CertD Org.', - organizationUnit: 'IT Department', - emailAddress: 'xiaojunnuo@qq.com' - } - } -} - -_.merge(defaultOptions, optionsPrivate) - -export default defaultOptions diff --git a/packages/plugins/plugin-host/test/plugins/host-shell-execute.test.js b/packages/plugins/plugin-host/test/plugins/host-shell-execute.test.js deleted file mode 100644 index 6c88ef34..00000000 --- a/packages/plugins/plugin-host/test/plugins/host-shell-execute.test.js +++ /dev/null @@ -1,52 +0,0 @@ -import pkg from 'chai' -import { HostShellExecute } from '../../src/plugins/host-shell-execute/index.js' -import { Certd } from '@certd/certd' -import { createOptions } from '../../../../../test/options.js' -const { expect } = pkg -describe('HostShellExecute', function () { - it('#execute', async function () { - this.timeout(10000) - const options = createOptions() - options.args = { test: false } - options.cert.email = 'xiaojunnuo@qq.com' - options.cert.domains = ['*.docmirror.cn'] - const plugin = new HostShellExecute(options) - const certd = new Certd(options) - const cert = await certd.readCurrentCert() - const context = {} - const uploadOpts = { - cert, - props: { script: ['ls ', 'ls '], accessProvider: 'aliyun-ssh' }, - context - } - const ret = await plugin.doExecute(uploadOpts) - expect(ret).ok - console.log('-----' + JSON.stringify(ret)) - }) - - it('#execute-hk-restart-docker', async function () { - this.timeout(10000) - const options = createOptions() - const plugin = new HostShellExecute(options) - const uploadOpts = { - props: { script: ['cd /home/ubuntu/deloy/nginx-proxy\nsudo docker-compose build\nsudo docker-compose up -d\n'], accessProvider: 'aliyun-ssh-hk' }, - context: {} - } - const ret = await plugin.doExecute(uploadOpts) - expect(ret).ok - console.log('-----' + JSON.stringify(ret)) - }) - - it('#execute-publicKey-login', async function () { - this.timeout(10000) - const options = createOptions() - const plugin = new HostShellExecute(options) - const shellOpts = { - props: { script: ['ls'], accessProvider: 'tencent-ssh-base01' }, - context: {} - } - const ret = await plugin.doExecute(shellOpts) - expect(ret).ok - console.log('-----' + JSON.stringify(ret)) - }) -}) diff --git a/packages/plugins/plugin-host/test/plugins/upload-to-host.test.js b/packages/plugins/plugin-host/test/plugins/upload-to-host.test.js deleted file mode 100644 index dde5c95c..00000000 --- a/packages/plugins/plugin-host/test/plugins/upload-to-host.test.js +++ /dev/null @@ -1,48 +0,0 @@ -import pkg from 'chai' -import { UploadCertToHost } from '../../src/plugins/upload-to-host/index.js' -import { Certd } from '@certd/certd' -import { createOptions } from '../../../../../test/options.js' -const { expect } = pkg -describe('PluginUploadToHost', function () { - it('#execute', async function () { - this.timeout(10000) - const options = createOptions() - options.args = { test: false } - options.cert.email = 'xiaojunnuo@qq.com' - options.cert.domains = ['*.docmirror.cn'] - const plugin = new UploadCertToHost(options) - const certd = new Certd(options) - const cert = await certd.readCurrentCert() - const context = {} - const uploadOpts = { - cert, - props: { crtPath: '/root/certd/test/test.crt', keyPath: '/root/certd/test/test.key', accessProvider: 'aliyun-ssh' }, - context - } - await plugin.doExecute(uploadOpts) - console.log('context:', context) - - await plugin.doRollback(uploadOpts) - }) - - it('#execute-to-ubantu', async function () { - this.timeout(10000) - const options = createOptions() - options.args = { test: false } - options.cert.email = 'xiaojunnuo@qq.com' - options.cert.domains = ['*.docmirror.cn'] - const plugin = new UploadCertToHost(options) - const certd = new Certd(options) - const cert = await certd.readCurrentCert() - const context = {} - const uploadOpts = { - cert, - props: { crtPath: '/home/ubuntu/deloy/nginx-proxy/ssl/test.crt', keyPath: '/home/ubuntu/deloy/nginx-proxy/ssl/test.key', accessProvider: 'aliyun-ssh-hk' }, - context - } - await plugin.doExecute(uploadOpts) - console.log('context:', context) - - await plugin.doRollback(uploadOpts) - }) -}) diff --git a/packages/plugins/plugin-tencent/.eslintrc b/packages/plugins/plugin-tencent/.eslintrc index c6ce67f2..218e910f 100644 --- a/packages/plugins/plugin-tencent/.eslintrc +++ b/packages/plugins/plugin-tencent/.eslintrc @@ -1,14 +1,21 @@ { - "extends": "standard", + "parser": "@typescript-eslint/parser", + "plugins": [ + "@typescript-eslint" + ], + "extends": [ + "plugin:@typescript-eslint/recommended", + "plugin:prettier/recommended", + "prettier" + ], "env": { "mocha": true }, - "overrides": [ - { - "files": ["*.test.js", "*.spec.js"], - "rules": { - "no-unused-expressions": "off" - } - } - ] + "rules": { + "@typescript-eslint/ban-ts-comment": "off", + "@typescript-eslint/ban-ts-ignore": "off", + "@typescript-eslint/no-explicit-any": "off", +// "no-unused-expressions": "off", + "max-len": [0, 160, 2, { "ignoreUrls": true }] + } } diff --git a/packages/plugins/plugin-tencent/.gitignore b/packages/plugins/plugin-tencent/.gitignore index cbb386fa..5ee37c64 100644 --- a/packages/plugins/plugin-tencent/.gitignore +++ b/packages/plugins/plugin-tencent/.gitignore @@ -1,7 +1,26 @@ -.vscode/ -node_modules/ -npm-debug.log -yarn-error.log -yarn.lock -package-lock.json -/.idea/ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +test/user.secret.ts \ No newline at end of file diff --git a/packages/plugins/plugin-tencent/package.json b/packages/plugins/plugin-tencent/package.json index 9623ee48..54301c78 100644 --- a/packages/plugins/plugin-tencent/package.json +++ b/packages/plugins/plugin-tencent/package.json @@ -1,33 +1,47 @@ { - "name": "@certd/plugin-tencent", - "version": "0.3.0", - "description": "", - "type": "module", - "main": "./dist/index.cjs", - "module": "./dist/fast-crud.es.js", - "scripts": { - "build": "rollup -c" - }, - "dependencies": { - "@certd/api": "^0.3.0", - "@certd/plugin-common": "^0.3.0", - "dayjs": "^1.9.7", - "kubernetes-client": "^9.0.0", - "lodash-es": "^4.17.20", - "tencentcloud-sdk-nodejs": "^4.0.44" - }, - "devDependencies": { - "@certd/certd": "^0.3.0", - "chai": "^4.2.0", - "eslint": "^7.15.0", - "eslint-config-standard": "^16.0.2", - "eslint-plugin-import": "^2.22.1", - "eslint-plugin-node": "^11.1.0", - "eslint-plugin-promise": "^4.2.1", - "mocha": "^8.2.1", - "rollup": "^3.2.3" - }, - "author": "Greper", - "license": "MIT", - "gitHead": "5fbd7742665c0a949333d805153e9b6af91c0a71" + "name": "@certd/plugin-tencent", + "private": true, + "version": "0.3.0", + "main": "./src/index.ts", + "module": "./dist/plugin-aliyun.mjs", + "types": "./dist/es/plugin-aliyun.d.ts", + "scripts": { + "dev": "vite", + "build": "vue-tsc --noEmit && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@certd/pipeline": "^0.3.0", + "@certd/plugin-util": "^0.3.0", + "tencentcloud-sdk-nodejs": "^4.0.44" + }, + "devDependencies": { + "axios": "^0.21.1", + "log4js": "^6.3.0", + "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/mocha": "^10.0.0", + "@types/node-forge": "^1.3.0", + "@typescript-eslint/eslint-plugin": "^5.38.1", + "@typescript-eslint/parser": "^5.38.1", + "chai": "^4.3.6", + "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", + "log4js": "^6.3.0", + "mocha": "^10.1.0", + "ts-node": "^10.9.1", + "typescript": "^4.8.4", + "vite": "^3.1.0" + } } diff --git a/packages/plugins/plugin-tencent/rollup.config.js b/packages/plugins/plugin-tencent/rollup.config.js deleted file mode 100644 index c67735ae..00000000 --- a/packages/plugins/plugin-tencent/rollup.config.js +++ /dev/null @@ -1,13 +0,0 @@ -export default { - input: 'src/index.js', - output: [ - { - file: 'dist/index.cjs', - format: 'cjs' - }, - { - file: 'dist/index.es.js', - format: 'es' - } - ] -} diff --git a/packages/plugins/plugin-tencent/src/access-providers/dnspod.js b/packages/plugins/plugin-tencent/src/access-providers/dnspod.js deleted file mode 100644 index 6e297c5f..00000000 --- a/packages/plugins/plugin-tencent/src/access-providers/dnspod.js +++ /dev/null @@ -1,24 +0,0 @@ -export class DnspodAccessProvider { - static define () { - return { - name: 'dnspod', - title: 'dnspod', - desc: '腾讯云的域名解析接口已迁移到dnspod', - input: { - id: { - component: { - placeholder: 'dnspod接口账户id' - }, - rules: [{ required: true, message: '该项必填' }] - }, - token: { - title: 'token', - component: { - placeholder: '开放接口token' - }, - rules: [{ required: true, message: '该项必填' }] - } - } - } - } -} diff --git a/packages/plugins/plugin-tencent/src/access-providers/tencent.js b/packages/plugins/plugin-tencent/src/access-providers/tencent.js deleted file mode 100644 index 94e280c0..00000000 --- a/packages/plugins/plugin-tencent/src/access-providers/tencent.js +++ /dev/null @@ -1,22 +0,0 @@ -export class TencentAccessProvider { - static define () { - return { - name: 'tencent', - title: '腾讯云', - input: { - secretId: { - component: { - placeholder: 'secretId' - }, - rules: [{ required: true, message: '该项必填' }] - }, - secretKey: { - component: { - placeholder: 'secretKey' - }, - rules: [{ required: true, message: '该项必填' }] - } - } - } - } -} diff --git a/packages/plugins/plugin-tencent/src/access/tencent-access.ts b/packages/plugins/plugin-tencent/src/access/tencent-access.ts new file mode 100644 index 00000000..a4f4757f --- /dev/null +++ b/packages/plugins/plugin-tencent/src/access/tencent-access.ts @@ -0,0 +1,26 @@ +import { AbstractAccess, IsAccess } from "@certd/pipeline"; + +@IsAccess({ + name: "tencent", + title: "腾讯云", + input: { + secretId: { + title: "secretId", + component: { + placeholder: "secretId", + }, + rules: [{ required: true, message: "该项必填" }], + }, + secretKey: { + title: "secretKey", + component: { + placeholder: "secretKey", + }, + rules: [{ required: true, message: "该项必填" }], + }, + }, +}) +export class TencentAccess extends AbstractAccess { + secretId = ""; + secretKey = ""; +} diff --git a/packages/plugins/plugin-tencent/src/dns-provider/dnspod-dns-provider.ts b/packages/plugins/plugin-tencent/src/dns-provider/dnspod-dns-provider.ts new file mode 100644 index 00000000..1495e912 --- /dev/null +++ b/packages/plugins/plugin-tencent/src/dns-provider/dnspod-dns-provider.ts @@ -0,0 +1,107 @@ +import { AbstractDnsProvider, CreateRecordOptions, IDnsProvider, IsDnsProvider, RemoveRecordOptions } from "@certd/pipeline"; +import _ from "lodash"; +import { DnspodAccess } from "../access"; + +@IsDnsProvider({ + name: "dnspod", + title: "dnspod(腾讯云)", + desc: "腾讯云的域名解析接口已迁移到dnspod", + accessType: "dnspod", +}) +export class DnspodDnsProvider extends AbstractDnsProvider implements IDnsProvider { + loginToken: any; + constructor() { + super(); + } + async onInit() { + const access: DnspodAccess = this.access as DnspodAccess; + this.loginToken = access.id + "," + access.token; + } + + async doRequest(options: any, successCodes: string[] = []) { + const config: any = { + // @ts-ignore + method: "post", + formData: { + login_token: this.loginToken, + format: "json", + lang: "cn", + error_on_empty: "no", + }, + timeout: 5000, + }; + _.merge(config, options); + + const ret: any = await this.http.request(config); + if (!ret || !ret.status) { + const code = ret.status.code; + if (code !== "1" || !successCodes.includes(code)) { + throw new Error("请求失败:" + ret.status.message + ",api=" + config.url); + } + } + return ret; + } + + async getDomainList() { + const ret = await this.doRequest({ + url: "https://dnsapi.cn/Domain.List", + }); + this.logger.debug("dnspod 域名列表:", ret.domains); + return ret.domains; + } + + async createRecord(options: CreateRecordOptions): Promise { + const { fullRecord, value, type } = options; + this.logger.info("添加域名解析:", fullRecord, value); + const domainItem = await this.matchDomain(fullRecord); + const domain = domainItem.name; + const rr = fullRecord.replace("." + domain, ""); + + const ret = await this.doRequest( + { + url: "https://dnsapi.cn/Record.Create", + formData: { + domain, + sub_domain: rr, + record_type: type, + record_line: "默认", + value: value, + mx: 1, + }, + }, + ["104"] + ); // 104错误码为记录已存在,无需再次添加 + this.logger.info("添加域名解析成功:", fullRecord, value, JSON.stringify(ret.record)); + return ret.record; + } + + async removeRecord(options: RemoveRecordOptions) { + const { fullRecord, value, record } = options; + const domain = await this.matchDomain(fullRecord); + + const ret = await this.doRequest({ + url: "https://dnsapi.cn/Record.Remove", + formData: { + domain, + record_id: record.id, + }, + }); + this.logger.info("删除域名解析成功:", fullRecord, value); + return ret.RecordId; + } + + async matchDomain(dnsRecord: any) { + const list = await this.getDomainList(); + let domain = null; + for (const item of list) { + if (_.endsWith(dnsRecord, item.name)) { + domain = item; + break; + } + } + if (!domain) { + throw new Error("找不到域名,请检查域名是否正确:" + dnsRecord); + } + return domain; + } +} diff --git a/packages/plugins/plugin-tencent/src/dns-provider/index.ts b/packages/plugins/plugin-tencent/src/dns-provider/index.ts new file mode 100644 index 00000000..9d13dc18 --- /dev/null +++ b/packages/plugins/plugin-tencent/src/dns-provider/index.ts @@ -0,0 +1 @@ +import "./dnspod-dns-provider"; diff --git a/packages/plugins/plugin-tencent/src/dns-providers/dnspod.js b/packages/plugins/plugin-tencent/src/dns-providers/dnspod.js deleted file mode 100644 index dcf23398..00000000 --- a/packages/plugins/plugin-tencent/src/dns-providers/dnspod.js +++ /dev/null @@ -1,96 +0,0 @@ -import { AbstractDnsProvider, util } from '@certd/api' -import _ from 'lodash' -const request = util.request -export class DnspodDnsProvider extends AbstractDnsProvider { - static define () { - return { - name: 'dnspod', - title: 'dnspod(腾讯云)', - desc: '腾讯云的域名解析接口已迁移到dnspod', - input: { - accessProvider: { - title: '授权', - helper: '需要dnspod类型的授权', - component: { - name: 'access-selector', - type: 'dnspod' - }, - required: true - } - } - } - } - - constructor (args) { - super(args) - const { props } = args - const accessProvider = this.getAccessProvider(props.accessProvider) - this.loginToken = accessProvider.id + ',' + accessProvider.token - } - - async doRequest (options, successCodes = []) { - const config = { - method: 'post', - formData: { - login_token: this.loginToken, - format: 'json', - lang: 'cn', - error_on_empty: 'no' - }, - timeout: 5000 - } - _.merge(config, options) - - const ret = await request(config) - if (!ret || !ret.status) { - const code = ret.status.code - if (code !== '1' || !successCodes.includes(code)) { - throw new Error('请求失败:' + ret.status.message + ',api=' + config.url) - } - } - return ret - } - - async getDomainList () { - const ret = await this.doRequest({ - url: 'https://dnsapi.cn/Domain.List' - }) - this.logger.debug('dnspod 域名列表:', ret.domains) - return ret.domains - } - - async createRecord ({ fullRecord, type, value }) { - this.logger.info('添加域名解析:', fullRecord, value) - const domainItem = await this.matchDomain(fullRecord, 'name') - const domain = domainItem.name - const rr = fullRecord.replace('.' + domain, '') - - const ret = await this.doRequest({ - url: 'https://dnsapi.cn/Record.Create', - formData: { - domain, - sub_domain: rr, - record_type: type, - record_line: '默认', - value: value, - mx: 1 - } - }, ['104'])// 104错误码为记录已存在,无需再次添加 - this.logger.info('添加域名解析成功:', fullRecord, value, JSON.stringify(ret.record)) - return ret.record - } - - async removeRecord ({ fullRecord, type, value, record }) { - const domain = await this.matchDomain(fullRecord, 'name') - - const ret = await this.doRequest({ - url: 'https://dnsapi.cn/Record.Remove', - formData: { - domain, - record_id: record.id - } - }) - this.logger.info('删除域名解析成功:', fullRecord, value) - return ret.RecordId - } -} diff --git a/packages/plugins/plugin-tencent/src/index.js b/packages/plugins/plugin-tencent/src/index.js deleted file mode 100644 index f9dc0b2c..00000000 --- a/packages/plugins/plugin-tencent/src/index.js +++ /dev/null @@ -1,34 +0,0 @@ -import _ from 'lodash' - -import { TencentAccessProvider } from './access-providers/tencent.js' -import { DnspodAccessProvider } from './access-providers/dnspod.js' -import { DnspodDnsProvider } from './dns-providers/dnspod.js' - -import { UploadCertToTencent } from './plugins/upload-to-tencent/index.js' - -import { DeployCertToTencentCDN } from './plugins/deploy-to-cdn/index.js' - -import { DeployCertToTencentCLB } from './plugins/deploy-to-clb/index.js' - -import { DeployCertToTencentTKEIngress } from './plugins/deploy-to-tke-ingress/index.js' - -import { pluginRegistry, accessProviderRegistry, dnsProviderRegistry } from '@certd/api' - -export const DefaultPlugins = { - UploadCertToTencent, - DeployCertToTencentTKEIngress, - DeployCertToTencentCDN, - DeployCertToTencentCLB -} -export default { - install () { - _.forEach(DefaultPlugins, item => { - pluginRegistry.install(item) - }) - - accessProviderRegistry.install(TencentAccessProvider) - accessProviderRegistry.install(DnspodAccessProvider) - - dnsProviderRegistry.install(DnspodDnsProvider) - } -} diff --git a/packages/plugins/plugin-tencent/src/plugin/deploy-to-clb/index.ts b/packages/plugins/plugin-tencent/src/plugin/deploy-to-clb/index.ts new file mode 100644 index 00000000..12527dd9 --- /dev/null +++ b/packages/plugins/plugin-tencent/src/plugin/deploy-to-clb/index.ts @@ -0,0 +1,211 @@ +import { AbstractPlugin, IsTask, RunStrategy, TaskInput, TaskOutput, TaskPlugin, utils } from "@certd/pipeline"; +import tencentcloud from "tencentcloud-sdk-nodejs/index"; +import { TencentAccess } from "../../access"; +import dayjs from "dayjs"; + +@IsTask(() => { + return { + name: "DeployCertToTencentCLB", + title: "部署到腾讯云CLB", + desc: "暂时只支持单向认证证书,暂时只支持通用负载均衡", + input: { + region: { + title: "大区", + value: "ap-guangzhou", + component: { + name: "a-select", + options: [{ value: "ap-guangzhou" }], + }, + required: true, + }, + domain: { + title: "域名", + required: true, + helper: "要更新的支持https的负载均衡的域名", + }, + loadBalancerId: { + title: "负载均衡ID", + helper: "如果没有配置,则根据域名匹配负载均衡下的监听器(根据域名匹配时暂时只支持前100个)", + required: true, + }, + listenerId: { + title: "监听器ID", + helper: "如果没有配置,则根据域名或负载均衡id匹配监听器", + }, + certName: { + title: "证书名称前缀", + }, + accessId: { + title: "Access提供者", + helper: "access授权", + component: { + name: "pi-access-selector", + type: "tencent", + }, + required: true, + }, + cert: { + title: "域名证书", + helper: "请选择前置任务输出的域名证书", + component: { + name: "pi-output-selector", + }, + required: true, + }, + }, + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed, + }, + }, + output: {}, + }; +}) +export class DeployToClbPlugin extends AbstractPlugin implements TaskPlugin { + async execute(input: TaskInput): Promise { + const { accessId, region, domain } = input; + const accessProvider = (await this.accessService.getById(accessId)) as TencentAccess; + const client = this.getClient(accessProvider, region); + + const lastCertId = await this.getCertIdFromProps(client, input); + if (!domain) { + await this.updateListener(client, input); + } else { + await this.updateByDomainAttr(client, input); + } + + try { + await utils.sleep(2000); + let newCertId = await this.getCertIdFromProps(client, input); + if ((lastCertId && newCertId === lastCertId) || (!lastCertId && !newCertId)) { + await utils.sleep(2000); + newCertId = await this.getCertIdFromProps(client, input); + } + if (newCertId === lastCertId) { + return {}; + } + this.logger.info("腾讯云证书ID:", newCertId); + } catch (e) { + this.logger.warn("查询腾讯云证书失败", e); + } + return {}; + } + + async getCertIdFromProps(client: any, input: TaskInput) { + const listenerRet = await this.getListenerList(client, input.loadBalancerId, [input.listenerId]); + return this.getCertIdFromListener(listenerRet[0], input.domain); + } + + getCertIdFromListener(listener: any, domain: string) { + let certId; + if (!domain) { + certId = listener.Certificate.CertId; + } else { + if (listener.Rules && listener.Rules.length > 0) { + for (const rule of listener.Rules) { + if (rule.Domain === domain) { + if (rule.Certificate != null) { + certId = rule.Certificate.CertId; + } + break; + } + } + } + } + return certId; + } + + async updateListener(client: any, props: TaskInput) { + const params = this.buildProps(props); + const ret = await client.ModifyListener(params); + this.checkRet(ret); + this.logger.info("设置腾讯云CLB证书成功:", ret.RequestId, "->loadBalancerId:", props.loadBalancerId, "listenerId", props.listenerId); + return ret; + } + + async updateByDomainAttr(client: any, props: TaskInput) { + const params: any = this.buildProps(props); + params.Domain = props.domain; + const ret = await client.ModifyDomainAttributes(params); + this.checkRet(ret); + this.logger.info( + "设置腾讯云CLB证书(sni)成功:", + ret.RequestId, + "->loadBalancerId:", + props.loadBalancerId, + "listenerId", + props.listenerId, + "domain:", + props.domain + ); + return ret; + } + appendTimeSuffix(name: string) { + if (name == null) { + name = "certd"; + } + return name + "-" + dayjs().format("YYYYMMDD-HHmmss"); + } + buildProps(props: TaskInput) { + const { certName, cert } = props; + return { + Certificate: { + SSLMode: "UNIDIRECTIONAL", // 单向认证 + CertName: this.appendTimeSuffix(certName || cert.domain), + CertKey: cert.key, + CertContent: cert.crt, + }, + LoadBalancerId: props.loadBalancerId, + ListenerId: props.listenerId, + }; + } + + async getCLBList(client: any, props: TaskInput) { + const params = { + Limit: 100, // 最大暂时只支持100个,暂时没做翻页 + OrderBy: "CreateTime", + OrderType: 0, + ...props.DescribeLoadBalancers, + }; + const ret = await client.DescribeLoadBalancers(params); + this.checkRet(ret); + return ret.LoadBalancerSet; + } + + async getListenerList(client: any, balancerId: any, listenerIds: any) { + // HTTPS + const params = { + LoadBalancerId: balancerId, + Protocol: "HTTPS", + ListenerIds: listenerIds, + }; + const ret = await client.DescribeListeners(params); + this.checkRet(ret); + return ret.Listeners; + } + + getClient(accessProvider: TencentAccess, region: string) { + const ClbClient = tencentcloud.clb.v20180317.Client; + + const clientConfig = { + credential: { + secretId: accessProvider.secretId, + secretKey: accessProvider.secretKey, + }, + region: region, + profile: { + httpProfile: { + endpoint: "clb.tencentcloudapi.com", + }, + }, + }; + + return new ClbClient(clientConfig); + } + + checkRet(ret: any) { + if (!ret || ret.Error) { + throw new Error("执行失败:" + ret.Error.Code + "," + ret.Error.Message); + } + } +} diff --git a/packages/plugins/plugin-tencent/src/plugin/deploy-to-tke-ingress/index.ts b/packages/plugins/plugin-tencent/src/plugin/deploy-to-tke-ingress/index.ts new file mode 100644 index 00000000..1ed99c85 --- /dev/null +++ b/packages/plugins/plugin-tencent/src/plugin/deploy-to-tke-ingress/index.ts @@ -0,0 +1,243 @@ +import { AbstractPlugin, IsTask, RunStrategy, TaskInput, TaskOutput, TaskPlugin, utils } from "@certd/pipeline"; +import tencentcloud from "tencentcloud-sdk-nodejs/index"; +import { K8sClient } from "@certd/plugin-util"; +import dayjs from "dayjs"; + +@IsTask(() => { + return { + name: "DeployCertToTencentTKEIngress", + title: "部署到腾讯云TKE-ingress", + desc: "需要【上传到腾讯云】作为前置任务", + input: { + region: { + 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, + }, + + /** + * AccessProvider的key,或者一个包含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 { + async execute(input: TaskInput): Promise { + const { accessId, region, clusterId, clusterIp, ingressClass } = input; + let { clusterDomain } = input; + const accessProvider = this.accessService.getById(accessId); + const tkeClient = this.getTkeClient(accessProvider, region); + const kubeConfigStr = await this.getTkeKubeConfig(tkeClient, clusterId); + + this.logger.info("kubeconfig已成功获取"); + const k8sClient = new K8sClient(kubeConfigStr); + if (clusterIp != null) { + if (!clusterDomain) { + clusterDomain = `${clusterId}.ccs.tencent-cloud.com`; + } + // 修改内网解析ip地址 + k8sClient.setLookup({ [clusterDomain]: { ip: clusterIp } }); + } + const ingressType = ingressClass || "qcloud"; + if (ingressType === "qcloud") { + await this.patchQcloudCertSecret({ k8sClient, input }); + } else { + await this.patchNginxCertSecret({ k8sClient, input }); + } + + await utils.sleep(2000); // 停留2秒,等待secret部署完成 + await this.restartIngress({ k8sClient, input }); + return {}; + } + + getTkeClient(accessProvider: any, region = "ap-guangzhou") { + const TkeClient = tencentcloud.tke.v20180525.Client; + const clientConfig = { + credential: { + secretId: accessProvider.secretId, + secretKey: accessProvider.secretKey, + }, + region, + profile: { + httpProfile: { + endpoint: "tke.tencentcloudapi.com", + }, + }, + }; + + return new TkeClient(clientConfig); + } + + async getTkeKubeConfig(client: any, clusterId: string) { + // Depends on tencentcloud-sdk-nodejs version 4.0.3 or higher + const params = { + ClusterId: clusterId, + }; + const ret = await client.DescribeClusterKubeconfig(params); + this.checkRet(ret); + this.logger.info("注意:后续操作需要在【集群->基本信息】中开启外网或内网访问,https://console.cloud.tencent.com/tke2/cluster"); + return ret.Kubeconfig; + } + + appendTimeSuffix(name: string) { + if (name == null) { + name = "certd"; + } + return name + "-" + dayjs().format("YYYYMMDD-HHmmss"); + } + + async patchQcloudCertSecret(options: { k8sClient: any; input: TaskInput }) { + const { tencentCertId } = options.input; + if (tencentCertId == null) { + throw new Error("请先将【上传证书到腾讯云】作为前置任务"); + } + this.logger.info("腾讯云证书ID:", tencentCertId); + const certIdBase64 = Buffer.from(tencentCertId).toString("base64"); + + const { namespace, secretName } = options.input; + + const body = { + data: { + qcloud_cert_id: certIdBase64, + }, + metadata: { + labels: { + certd: this.appendTimeSuffix("certd"), + }, + }, + }; + let secretNames = secretName; + if (typeof secretName === "string") { + secretNames = [secretName]; + } + for (const secret of secretNames) { + await options.k8sClient.patchSecret({ namespace, secretName: secret, body }); + this.logger.info(`CertSecret已更新:${secret}`); + } + } + + async patchNginxCertSecret(options: { k8sClient: any; input: TaskInput }) { + const { k8sClient, input } = options; + const { cert } = input; + 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 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}`); + } + } + + async restartIngress(options: { k8sClient: any; input: TaskInput }) { + const { k8sClient, input } = options; + const { namespace, ingressName } = input; + + const body = { + metadata: { + labels: { + certd: this.appendTimeSuffix("certd"), + }, + }, + }; + let ingressNames = ingressName; + if (typeof ingressName === "string") { + ingressNames = [ingressName]; + } + for (const ingress of ingressNames) { + await k8sClient.patchIngress({ namespace, ingressName: ingress, body }); + this.logger.info(`ingress已重启:${ingress}`); + } + } + checkRet(ret: any) { + if (!ret || ret.Error) { + throw new Error("执行失败:" + ret.Error.Code + "," + ret.Error.Message); + } + } +} diff --git a/packages/plugins/plugin-tencent/src/plugin/upload-to-tencent/index.ts b/packages/plugins/plugin-tencent/src/plugin/upload-to-tencent/index.ts new file mode 100644 index 00000000..ed19df2e --- /dev/null +++ b/packages/plugins/plugin-tencent/src/plugin/upload-to-tencent/index.ts @@ -0,0 +1,107 @@ +import { AbstractPlugin, IsTask, RunStrategy, TaskInput, TaskOutput, TaskPlugin } from "@certd/pipeline"; +import tencentcloud from "tencentcloud-sdk-nodejs/index"; +import dayjs from "dayjs"; + +@IsTask(() => { + return { + name: "UploadCertToTencent", + title: "上传证书到腾讯云", + desc: "上传成功后输出:tencentCertId", + input: { + name: { + 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 { + async execute(input: TaskInput): Promise { + const { accessId, name, cert } = input; + const accessProvider = this.accessService.getById(accessId); + const certName = this.appendTimeSuffix(name || cert.domain); + const client = this.getClient(accessProvider); + + const params = { + CertificatePublicKey: cert.crt, + CertificatePrivateKey: cert.key, + Alias: certName, + }; + const ret = await client.UploadCertificate(params); + this.checkRet(ret); + this.logger.info("证书上传成功:tencentCertId=", ret.CertificateId); + return { tencentCertId: ret.CertificateId }; + } + + appendTimeSuffix(name: string) { + if (name == null) { + name = "certd"; + } + return name + "-" + dayjs().format("YYYYMMDD-HHmmss"); + } + + getClient(accessProvider: any) { + const SslClient = tencentcloud.ssl.v20191205.Client; + + const clientConfig = { + credential: { + secretId: accessProvider.secretId, + secretKey: accessProvider.secretKey, + }, + region: "", + profile: { + httpProfile: { + endpoint: "ssl.tencentcloudapi.com", + }, + }, + }; + + return new SslClient(clientConfig); + } + + // async rollback({ input }) { + // const { accessId } = input; + // const accessProvider = this.accessService.getById(accessId); + // const client = this.getClient(accessProvider); + // + // const { tencentCertId } = context; + // const params = { + // CertificateId: tencentCertId, + // }; + // const ret = await client.DeleteCertificate(params); + // this.checkRet(ret); + // this.logger.info("证书删除成功:DeleteResult=", ret.DeleteResult); + // delete context.tencentCertId; + // } + checkRet(ret: any) { + if (!ret || ret.Error) { + throw new Error("执行失败:" + ret.Error.Code + "," + ret.Error.Message); + } + } +} diff --git a/packages/plugins/plugin-tencent/src/plugins/abstract-tencent.js b/packages/plugins/plugin-tencent/src/plugins/abstract-tencent.js deleted file mode 100644 index bb66a9fb..00000000 --- a/packages/plugins/plugin-tencent/src/plugins/abstract-tencent.js +++ /dev/null @@ -1,13 +0,0 @@ -import { AbstractPlugin } from '@certd/api' - -export class AbstractTencentPlugin extends AbstractPlugin { - checkRet (ret) { - if (!ret || ret.Error) { - throw new Error('执行失败:' + ret.Error.Code + ',' + ret.Error.Message) - } - } - - getSafetyDomain (domain) { - return domain.replace(/\*/g, '_') - } -} diff --git a/packages/plugins/plugin-tencent/src/plugins/deploy-to-cdn/index.js b/packages/plugins/plugin-tencent/src/plugins/deploy-to-cdn/index.js deleted file mode 100644 index f11387c2..00000000 --- a/packages/plugins/plugin-tencent/src/plugins/deploy-to-cdn/index.js +++ /dev/null @@ -1,104 +0,0 @@ -import { AbstractTencentPlugin } from '../abstract-tencent.js' -import dayjs from 'dayjs' -import tencentcloud from 'tencentcloud-sdk-nodejs' - -export class DeployCertToTencentCDN extends AbstractTencentPlugin { - /** - * 插件定义 - * 名称 - * 入参 - * 出参 - */ - static define () { - return { - name: 'deployCertToTencentCDN', - title: '部署到腾讯云CDN', - input: { - domainName: { - title: 'cdn加速域名', - rules: [{ required: true, message: '该项必填' }] - }, - certName: { - title: '证书名称', - helper: '证书上传后将以此参数作为名称前缀' - }, - accessProvider: { - title: 'Access提供者', - helper: 'access 授权', - component: { - name: 'access-selector', - type: 'tencent' - }, - required: true - } - }, - output: { - tencentCertId: { - type: String, - desc: '证书来源选择上传时,将返回此id' - } - } - } - } - - async execute ({ cert, props, context }) { - const accessProvider = this.getAccessProvider(props.accessProvider) - const client = this.getClient(accessProvider) - const params = this.buildParams(props, context, cert) - await this.doRequest(client, params) - } - - async rollback ({ cert, props, context }) { - - } - - getClient (accessProvider) { - const CdnClient = tencentcloud.cdn.v20180606.Client - - const clientConfig = { - credential: { - secretId: accessProvider.secretId, - secretKey: accessProvider.secretKey - }, - region: '', - profile: { - httpProfile: { - endpoint: 'cdn.tencentcloudapi.com' - } - } - } - - return new CdnClient(clientConfig) - } - - buildParams (props, context, cert) { - const { domainName, from } = props - const { tencentCertId } = context - this.logger.info('部署腾讯云证书ID:', tencentCertId) - const params = { - Https: { - Switch: 'on', - CertInfo: { - CertId: tencentCertId - // Certificate: '1231', - // PrivateKey: '1231' - } - }, - Domain: domainName - } - if (from === 'upload' || tencentCertId == null) { - params.Https.CertInfo = { - Certificate: cert.crt, - PrivateKey: cert.key - } - } - return params - } - - async doRequest (client, params) { - const ret = await client.UpdateDomainConfig(params) - this.checkRet(ret) - this.logger.info('设置腾讯云CDN证书成功:', ret.RequestId) - return ret.RequestId - } -} diff --git a/packages/plugins/plugin-tencent/src/plugins/deploy-to-clb/index.js b/packages/plugins/plugin-tencent/src/plugins/deploy-to-clb/index.js deleted file mode 100644 index 31ec9194..00000000 --- a/packages/plugins/plugin-tencent/src/plugins/deploy-to-clb/index.js +++ /dev/null @@ -1,198 +0,0 @@ -import { AbstractTencentPlugin } from '../abstract-tencent.js' -import tencentcloud from 'tencentcloud-sdk-nodejs' -export class DeployCertToTencentCLB extends AbstractTencentPlugin { - /** - * 插件定义 - * 名称 - * 入参 - * 出参 - */ - static define () { - return { - name: 'deployCertToTencentCLB', - title: '部署到腾讯云CLB', - desc: '暂时只支持单向认证证书,暂时只支持通用负载均衡', - input: { - region: { - title: '大区', - value: 'ap-guangzhou', - component: { - name: 'a-select', - options: [{ value: 'ap-guangzhou' }] - }, - required: true - }, - domain: { - title: '域名', - required: true, - helper: '要更新的支持https的负载均衡的域名' - }, - loadBalancerId: { - title: '负载均衡ID', - helper: '如果没有配置,则根据域名匹配负载均衡下的监听器(根据域名匹配时暂时只支持前100个)', - required: true - }, - listenerId: { - title: '监听器ID', - helper: '如果没有配置,则根据域名或负载均衡id匹配监听器' - }, - certName: { - title: '证书名称前缀' - }, - accessProvider: { - title: 'Access提供者', - helper: 'access授权', - component: { - name: 'access-selector', - type: 'tencent' - }, - required: true - } - }, - output: { - - } - } - } - - async execute ({ cert, props, context }) { - const accessProvider = this.getAccessProvider(props.accessProvider) - const { region } = props - const client = this.getClient(accessProvider, region) - - const lastCertId = await this.getCertIdFromProps(client, props) - if (!props.domain) { - await this.updateListener(client, cert, props, context) - } else { - await this.updateByDomainAttr(client, cert, props, context) - } - - try { - await this.sleep(2000) - let newCertId = await this.getCertIdFromProps(client, props) - if ((lastCertId && newCertId === lastCertId) || (!lastCertId && !newCertId)) { - await this.sleep(2000) - newCertId = await this.getCertIdFromProps(client, props) - } - if (newCertId === lastCertId) { - return {} - } - this.logger.info('腾讯云证书ID:', newCertId) - if (!context.tencentCertId) { - context.tencentCertId = newCertId - } - return { tencentCertId: newCertId } - } catch (e) { - this.logger.warn('查询腾讯云证书失败', e) - } - } - - async getCertIdFromProps (client, props) { - const listenerRet = await this.getListenerList(client, props.loadBalancerId, [props.listenerId]) - return this.getCertIdFromListener(listenerRet[0], props.domain) - } - - getCertIdFromListener (listener, domain) { - let certId - if (!domain) { - certId = listener.Certificate.CertId - } else { - if (listener.Rules && listener.Rules.length > 0) { - for (const rule of listener.Rules) { - if (rule.Domain === domain) { - if (rule.Certificate != null) { - certId = rule.Certificate.CertId - } - break - } - } - } - } - return certId - } - - async rollback ({ cert, props, context }) { - this.logger.warn('未实现rollback') - } - - async updateListener (client, cert, props, context) { - const params = this.buildProps(props, context, cert) - const ret = await client.ModifyListener(params) - this.checkRet(ret) - this.logger.info('设置腾讯云CLB证书成功:', ret.RequestId, '->loadBalancerId:', props.loadBalancerId, 'listenerId', props.listenerId) - return ret - } - - async updateByDomainAttr (client, cert, props, context) { - const params = this.buildProps(props, context, cert) - params.Domain = props.domain - const ret = await client.ModifyDomainAttributes(params) - this.checkRet(ret) - this.logger.info('设置腾讯云CLB证书(sni)成功:', ret.RequestId, '->loadBalancerId:', props.loadBalancerId, 'listenerId', props.listenerId, 'domain:', props.domain) - return ret - } - - buildProps (props, context, cert) { - const { certName } = props - const { tencentCertId } = context - this.logger.info('部署腾讯云证书ID:', tencentCertId) - const params = { - Certificate: { - SSLMode: 'UNIDIRECTIONAL', // 单向认证 - CertId: tencentCertId - }, - LoadBalancerId: props.loadBalancerId, - ListenerId: props.listenerId - } - - if (tencentCertId == null) { - params.Certificate.CertName = this.appendTimeSuffix(certName || cert.domain) - params.Certificate.CertKey = cert.key - params.Certificate.CertContent = cert.crt - } - return params - } - - async getCLBList (client, props) { - const params = { - Limit: 100, // 最大暂时只支持100个,暂时没做翻页 - OrderBy: 'CreateTime', - OrderType: 0, - ...props.DescribeLoadBalancers - } - const ret = await client.DescribeLoadBalancers(params) - this.checkRet(ret) - return ret.LoadBalancerSet - } - - async getListenerList (client, balancerId, listenerIds) { - // HTTPS - const params = { - LoadBalancerId: balancerId, - Protocol: 'HTTPS', - ListenerIds: listenerIds - } - const ret = await client.DescribeListeners(params) - this.checkRet(ret) - return ret.Listeners - } - - getClient (accessProvider, region) { - const ClbClient = tencentcloud.clb.v20180317.Client - - const clientConfig = { - credential: { - secretId: accessProvider.secretId, - secretKey: accessProvider.secretKey - }, - region: region, - profile: { - httpProfile: { - endpoint: 'clb.tencentcloudapi.com' - } - } - } - - return new ClbClient(clientConfig) - } -} diff --git a/packages/plugins/plugin-tencent/src/plugins/deploy-to-tke-ingress/index.js b/packages/plugins/plugin-tencent/src/plugins/deploy-to-tke-ingress/index.js deleted file mode 100644 index b3e5fbcb..00000000 --- a/packages/plugins/plugin-tencent/src/plugins/deploy-to-tke-ingress/index.js +++ /dev/null @@ -1,213 +0,0 @@ -import { AbstractTencentPlugin } from '../abstract-tencent.js' -import tencentcloud from 'tencentcloud-sdk-nodejs' -import { K8sClient } from '@certd/plugin-common' -export class DeployCertToTencentTKEIngress extends AbstractTencentPlugin { - /** - * 插件定义 - * 名称 - * 入参 - * 出参 - */ - static define () { - return { - name: 'deployCertToTencentTKEIngress', - title: '部署到腾讯云TKE-ingress', - desc: '需要【上传到腾讯云】作为前置任务', - input: { - region: { - 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' - }, - /** - * AccessProvider的key,或者一个包含access的具体的对象 - */ - accessProvider: { - title: 'Access授权', - helper: 'access授权', - component: { - name: 'access-selector', - type: 'tencent' - }, - required: true - } - }, - output: { - - } - } - } - - async execute ({ cert, props, context }) { - const accessProvider = this.getAccessProvider(props.accessProvider) - const tkeClient = this.getTkeClient(accessProvider, props.region) - const kubeConfigStr = await this.getTkeKubeConfig(tkeClient, props.clusterId) - - this.logger.info('kubeconfig已成功获取') - const k8sClient = new K8sClient(kubeConfigStr) - if (props.clusterIp != null) { - let clusterDomain = props.clusterDomain - if (!clusterDomain) { - clusterDomain = `${props.clusterId}.ccs.tencent-cloud.com` - } - // 修改内网解析ip地址 - k8sClient.setLookup({ [clusterDomain]: { ip: props.clusterIp } }) - } - const ingressType = props.ingressClass || 'qcloud' - if (ingressType === 'qcloud') { - await this.patchQcloudCertSecret({ k8sClient, props, context }) - } else { - await this.patchNginxCertSecret({ cert, k8sClient, props, context }) - } - - await this.sleep(2000) // 停留2秒,等待secret部署完成 - await this.restartIngress({ k8sClient, props }) - return true - } - - getTkeClient (accessProvider, region = 'ap-guangzhou') { - const TkeClient = tencentcloud.tke.v20180525.Client - const clientConfig = { - credential: { - secretId: accessProvider.secretId, - secretKey: accessProvider.secretKey - }, - region, - profile: { - httpProfile: { - endpoint: 'tke.tencentcloudapi.com' - } - } - } - - return new TkeClient(clientConfig) - } - - async getTkeKubeConfig (client, clusterId) { - // Depends on tencentcloud-sdk-nodejs version 4.0.3 or higher - const params = { - ClusterId: clusterId - } - const ret = await client.DescribeClusterKubeconfig(params) - this.checkRet(ret) - this.logger.info('注意:后续操作需要在【集群->基本信息】中开启外网或内网访问,https://console.cloud.tencent.com/tke2/cluster') - return ret.Kubeconfig - } - - async patchQcloudCertSecret ({ k8sClient, props, context }) { - const { tencentCertId } = context - if (tencentCertId == null) { - throw new Error('请先将【上传证书到腾讯云】作为前置任务') - } - this.logger.info('腾讯云证书ID:', tencentCertId) - const certIdBase64 = Buffer.from(tencentCertId).toString('base64') - - const { namespace, secretName } = props - - const body = { - data: { - qcloud_cert_id: certIdBase64 - }, - 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}`) - } - } - - 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}`) - } - } - - async restartIngress ({ k8sClient, props }) { - const { namespace, ingressName } = props - - const body = { - metadata: { - labels: { - certd: this.appendTimeSuffix('certd') - } - } - } - let ingressNames = ingressName - if (typeof ingressName === 'string') { - ingressNames = [ingressName] - } - for (const ingress of ingressNames) { - await k8sClient.patchIngress({ namespace, ingressName: ingress, body }) - this.logger.info(`ingress已重启:${ingress}`) - } - } -} diff --git a/packages/plugins/plugin-tencent/src/plugins/upload-to-tencent/index.js b/packages/plugins/plugin-tencent/src/plugins/upload-to-tencent/index.js deleted file mode 100644 index f25eb5a8..00000000 --- a/packages/plugins/plugin-tencent/src/plugins/upload-to-tencent/index.js +++ /dev/null @@ -1,90 +0,0 @@ -import tencentcloud from 'tencentcloud-sdk-nodejs' -import { AbstractTencentPlugin } from '../abstract-tencent.js' - -export class UploadCertToTencent extends AbstractTencentPlugin { - /** - * 插件定义 - * 名称 - * 入参 - * 出参 - */ - static define () { - return { - name: 'uploadCertToTencent', - title: '上传证书到腾讯云', - desc: '成功后获取,tencentCertId', - input: { - name: { - title: '证书名称' - }, - accessProvider: { - title: 'Access授权', - helper: 'access授权', - component: { - name: 'access-selector', - type: 'tencent' - }, - required: true - } - }, - output: { - tencentCertId: { - type: String, - desc: '上传成功后的腾讯云CertId' - } - } - } - } - - getClient (accessProvider) { - const SslClient = tencentcloud.ssl.v20191205.Client - - const clientConfig = { - credential: { - secretId: accessProvider.secretId, - secretKey: accessProvider.secretKey - }, - region: '', - profile: { - httpProfile: { - endpoint: 'ssl.tencentcloudapi.com' - } - } - } - - return new SslClient(clientConfig) - } - - async execute ({ cert, props, context, logger }) { - const { name, accessProvider } = props - const certName = this.appendTimeSuffix(name || cert.domain) - - const provider = this.getAccessProvider(accessProvider) - const client = this.getClient(provider) - - const params = { - CertificatePublicKey: cert.crt, - CertificatePrivateKey: cert.key, - Alias: certName - } - const ret = await client.UploadCertificate(params) - this.checkRet(ret) - this.logger.info('证书上传成功:tencentCertId=', ret.CertificateId) - context.tencentCertId = ret.CertificateId - } - - async rollback ({ cert, props, context }) { - const { accessProvider } = props - const provider = super.getAccessProvider(accessProvider) - const client = this.getClient(provider) - - const { tencentCertId } = context - const params = { - CertificateId: tencentCertId - } - const ret = await client.DeleteCertificate(params) - this.checkRet(ret) - this.logger.info('证书删除成功:DeleteResult=', ret.DeleteResult) - delete context.tencentCertId - } -} diff --git a/packages/plugins/plugin-tencent/test/dns-providers/dnspod.cert.test.js b/packages/plugins/plugin-tencent/test/dns-providers/dnspod.cert.test.js deleted file mode 100644 index 675276e2..00000000 --- a/packages/plugins/plugin-tencent/test/dns-providers/dnspod.cert.test.js +++ /dev/null @@ -1,27 +0,0 @@ -import pkg from 'chai' -import PluginTencent from '../../src/index.js' -import { createOptions } from '../../../../../test/options.js' -import { Certd } from '@certd/certd' -const { expect } = pkg - -// 安装默认插件和授权提供者 -PluginTencent.install() -describe('DnspodDnsProvider', function () { - it('#申请证书', async function () { - this.timeout(300000) - const options = createOptions() - options.cert.domains = ['*.certd.xyz', '*.test.certd.xyz', '*.base.certd.xyz', 'certd.xyz'] - options.cert.dnsProvider = { - type: 'dnspod', - accessProvider: 'dnspod' - } - options.args = { forceCert: true } - const certd = new Certd(options) - const cert = await certd.certApply() - expect(cert).ok - expect(cert.crt).ok - expect(cert.key).ok - expect(cert.detail).ok - expect(cert.expires).ok - }) -}) diff --git a/packages/plugins/plugin-tencent/test/dns-providers/dnspod.test.js b/packages/plugins/plugin-tencent/test/dns-providers/dnspod.test.js deleted file mode 100644 index 750a550a..00000000 --- a/packages/plugins/plugin-tencent/test/dns-providers/dnspod.test.js +++ /dev/null @@ -1,35 +0,0 @@ -import pkg from 'chai' -import { DnspodDnsProvider } from '../../src/dns-providers/dnspod.js' -import { createOptions, getDnsProviderOptions } from '../../../../../test/options.js' -const { expect } = pkg -describe('DnspodDnsProvider', function () { - it('#getDomainList', async function () { - let options = createOptions() - options.cert.dnsProvider = { - type: 'dnspod', - accessProvider: 'dnspod' - } - options = getDnsProviderOptions(options) - - const dnsProvider = new DnspodDnsProvider(options) - const domainList = await dnsProvider.getDomainList() - console.log('domainList', domainList) - expect(domainList.length).gt(0) - }) - - it('#createRecord&removeRecord', async function () { - let options = createOptions() - options.cert.dnsProvider = { - type: 'dnspod', - accessProvider: 'dnspod' - } - options = getDnsProviderOptions(options) - - const dnsProvider = new DnspodDnsProvider(options) - const record = await dnsProvider.createRecord({ fullRecord: '___certd___.__test__.certd.xyz', type: 'TXT', value: 'aaaa' }) - console.log('recordId', record.id) - expect(record.id != null).ok - - await dnsProvider.removeRecord({ fullRecord: '___certd___.__test__.certd.xyz', type: 'TXT', value: 'aaaa', record }) - }) -}) diff --git a/packages/plugins/plugin-tencent/test/plugins/deploy-to-cdn.test.js b/packages/plugins/plugin-tencent/test/plugins/deploy-to-cdn.test.js deleted file mode 100644 index 410d72fe..00000000 --- a/packages/plugins/plugin-tencent/test/plugins/deploy-to-cdn.test.js +++ /dev/null @@ -1,52 +0,0 @@ -import pkg from 'chai' -import { DeployCertToTencentCDN } from '../../src/plugins/deploy-to-cdn/index.js' -import { Certd } from '@certd/certd' -import { UploadCertToTencent } from '../../src/plugins/upload-to-tencent/index.js' -import { createOptions } from '../../../../../test/options.js' -const { expect } = pkg -describe('DeployToTencentCDN', function () { - it('#execute-from-store', async function () { - const options = createOptions() - options.args.test = false - const certd = new Certd(options) - const cert = await certd.readCurrentCert('xiaojunnuo@qq.com', ['*.docmirror.cn']) - const context = {} - const uploadPlugin = new UploadCertToTencent(options) - const uploadOptions = { - cert, - props: { name: 'certd部署测试', accessProvider: 'tencent' }, - context - } - await uploadPlugin.doExecute(uploadOptions) - - const deployPlugin = new DeployCertToTencentCDN(options) - const deployOpts = { - cert, - props: { domainName: 'tentcent-certd.docmirror.cn', certName: 'certd部署测试', accessProvider: 'tencent' }, - context - } - await deployPlugin.doExecute(deployOpts) - console.log('context:', context) - expect(context.tencentCertId).ok - - await uploadPlugin.doRollback(uploadOptions) - }) - it('#execute-upload', async function () { - const options = createOptions() - options.args.test = false - options.cert.email = 'xiaojunnuo@qq.com' - options.cert.domains = ['*.docmirror.cn'] - const plugin = new DeployCertToTencentCDN(options) - const certd = new Certd(options) - const cert = await certd.readCurrentCert() - const context = {} - const deployOpts = { - cert, - props: { domainName: 'tentcent-certd.docmirror.cn', accessProvider: 'tencent' }, - context - } - const ret = await plugin.doExecute(deployOpts) - console.log('context:', context, ret) - expect(context).be.empty - }) -}) diff --git a/packages/plugins/plugin-tencent/test/plugins/deploy-to-clb.test.js b/packages/plugins/plugin-tencent/test/plugins/deploy-to-clb.test.js deleted file mode 100644 index ac3dbd68..00000000 --- a/packages/plugins/plugin-tencent/test/plugins/deploy-to-clb.test.js +++ /dev/null @@ -1,105 +0,0 @@ -import pkg from 'chai' -import { DeployCertToTencentCLB } from '../../src/plugins/deploy-to-clb/index.js' -import { Certd } from '@certd/certd' -// eslint-disable-next-line no-unused-vars -import { createOptions } from '../../../../../test/options.js' -import { UploadCertToTencent } from '../../src/plugins/upload-to-tencent/index.js' -const { expect } = pkg -describe('DeployToTencentCLB', function () { - it('#execute-getClbList', async function () { - const options = createOptions() - options.args.test = false - options.cert.dnsProvider = 'tencent-yonsz' - const deployPlugin = new DeployCertToTencentCLB(options) - const props = { - region: 'ap-guangzhou', - domain: 'certd-test-no-sni.base.yonsz.net', - accessProvider: 'tencent-yonsz' - } - const accessProvider = deployPlugin.getAccessProvider(props.accessProvider) - const { region } = props - const client = deployPlugin.getClient(accessProvider, region) - - const ret = await deployPlugin.getCLBList(client, props) - expect(ret.length > 0).ok - console.log('clb count:', ret.length) - }) - it('#execute-getListenerList', async function () { - const options = createOptions() - options.args.test = false - options.cert.dnsProvider = 'tencent-yonsz' - const deployPlugin = new DeployCertToTencentCLB(options) - const props = { - region: 'ap-guangzhou', - domain: 'certd-test-no-sni.base.yonsz.net', - accessProvider: 'tencent-yonsz', - loadBalancerId: 'lb-59yhe5xo', - listenerId: 'lbl-1vfwx8dq' - } - const accessProvider = deployPlugin.getAccessProvider(props.accessProvider) - const { region } = props - const client = deployPlugin.getClient(accessProvider, region) - - const ret = await deployPlugin.getListenerList(client, props.loadBalancerId, [props.listenerId]) - expect(ret.length > 0).ok - console.log('clb count:', ret.length, ret) - }) - - it('#execute-no-sni-listenerId', async function () { - this.timeout(10000) - const options = createOptions() - options.args.test = false - options.cert.dnsProvider = 'tencent-yonsz' - options.cert.email = 'xiaojunnuo@qq.com' - options.cert.domains = ['*.docmirror.cn'] - const certd = new Certd(options) - const cert = await certd.readCurrentCert() - const deployPlugin = new DeployCertToTencentCLB(options) - const context = {} - const deployOpts = { - cert, - props: { - region: 'ap-guangzhou', - loadBalancerId: 'lb-59yhe5xo', - listenerId: 'lbl-1vfwx8dq', - accessProvider: 'tencent-yonsz' - }, - context - } - const ret = await deployPlugin.doExecute(deployOpts) - expect(ret).ok - console.log('ret:', ret) - - // 删除测试证书 - const uploadPlugin = new UploadCertToTencent(options) - await uploadPlugin.doRollback(deployOpts) - }) - - it('#execute-sni-listenerId', async function () { - this.timeout(10000) - const options = createOptions() - options.args.test = false - options.cert.dnsProvider = 'tencent-yonsz' - const certd = new Certd(options) - const cert = certd.readCurrentCert('xiaojunnuo@qq.com', ['*.docmirror.cn']) - const deployPlugin = new DeployCertToTencentCLB(options) - const context = {} - const deployOpts = { - cert, - props: { - region: 'ap-guangzhou', - loadBalancerId: 'lb-59yhe5xo', - listenerId: 'lbl-akbyf5ac', - domain: 'certd-test-sni.base.yonsz.net', - accessProvider: 'tencent-yonsz' - }, - context - } - const ret = await deployPlugin.doExecute(deployOpts) - console.log('ret:', ret) - expect(ret).ok - // 删除测试证书 - const uploadPlugin = new UploadCertToTencent(options) - await uploadPlugin.doRollback(deployOpts) - }) -}) diff --git a/packages/plugins/plugin-tencent/test/plugins/deploy-to-tke-ingress-nginx.test.js b/packages/plugins/plugin-tencent/test/plugins/deploy-to-tke-ingress-nginx.test.js deleted file mode 100644 index f08991e0..00000000 --- a/packages/plugins/plugin-tencent/test/plugins/deploy-to-tke-ingress-nginx.test.js +++ /dev/null @@ -1,59 +0,0 @@ -import pkg from 'chai' -import { DeployCertToTencentTKEIngress } from '../../src/plugins/deploy-to-tke-ingress/index.js' -import { Certd } from '@certd/certd' -import { createOptions } from '../../../../../test/options.js' -import { K8sClient } from '../../src/utils/util.k8s.client.js' - -const { expect } = pkg - -async function getOptions () { - const options = createOptions() - options.args.test = false - options.cert.email = 'xiaojunnuo@qq.com' - options.cert.domains = ['*.docmirror.cn'] - const certd = new Certd(options) - const cert = await certd.readCurrentCert() - const context = {} - const deployOpts = { - accessProviders: options.accessProviders, - cert, - props: { - accessProvider: 'tencent-yonsz', - region: 'ap-guangzhou', - clusterId: 'cls-6lbj1vee' - }, - context - } - return { options, deployOpts } -} - -describe('DeployCertToTencentTKEIngressNginx', function () { - it('#getTKESecrets', async function () { - this.timeout(50000) - const { options, deployOpts } = await getOptions() - const plugin = new DeployCertToTencentTKEIngress(options) - const tkeClient = plugin.getTkeClient(options.accessProviders[deployOpts.props.accessProvider], deployOpts.props.region) - const kubeConfig = await plugin.getTkeKubeConfig(tkeClient, deployOpts.props.clusterId) - - const k8sClient = new K8sClient(kubeConfig) - k8sClient.setLookup({ - 'cls-6lbj1vee.ccs.tencent-cloud.com': { ip: '13.123.123.123' } - }) - const secrets = await k8sClient.getSecret({ namespace: 'stress' }) - - console.log('secrets:', secrets) - }) - it('#execute', async function () { - this.timeout(5000) - - const { options, deployOpts } = await getOptions() - deployOpts.props.ingressName = 'stress-ingress-nginx' - deployOpts.props.ingressClass = 'nginx' - deployOpts.props.secretName = 'stress-all' - deployOpts.props.namespace = 'stress' - const plugin = new DeployCertToTencentTKEIngress(options) - - const ret = await plugin.doExecute(deployOpts) - console.log('sucess', ret) - }) -}) diff --git a/packages/plugins/plugin-tencent/test/plugins/deploy-to-tke-ingress.test.js b/packages/plugins/plugin-tencent/test/plugins/deploy-to-tke-ingress.test.js deleted file mode 100644 index 7cb795e0..00000000 --- a/packages/plugins/plugin-tencent/test/plugins/deploy-to-tke-ingress.test.js +++ /dev/null @@ -1,57 +0,0 @@ -import pkg from 'chai' -import { DeployCertToTencentTKEIngress } from '../../src/plugins/deploy-to-tke-ingress/index.js' -import { Certd } from '@certd/certd' -import { createOptions } from '../../../../../test/options.js' -import { K8sClient } from '../../src/utils/util.k8s.client.js' - -const { expect } = pkg - -async function getOptions () { - const options = createOptions() - options.args.test = false - options.cert.email = 'xiaojunnuo@qq.com' - options.cert.domains = ['*.docmirror.cn'] - const certd = new Certd(options) - const cert = await certd.readCurrentCert() - const context = {} - const deployOpts = { - accessProviders: options.accessProviders, - cert, - props: { - accessProvider: 'tencent-yonsz', - region: 'ap-guangzhou', - clusterId: 'cls-6lbj1vee' - }, - context - } - return { options, deployOpts } -} - -describe('DeployCertToTencentTKEIngress', function () { - it('#getTKESecrets', async function () { - this.timeout(50000) - const { options, deployOpts } = await getOptions() - const plugin = new DeployCertToTencentTKEIngress(options) - const tkeClient = plugin.getTkeClient(options.accessProviders[deployOpts.props.accessProvider], deployOpts.props.region) - const kubeConfig = await plugin.getTkeKubeConfig(tkeClient, deployOpts.props.clusterId) - - const k8sClient = new K8sClient(kubeConfig) - k8sClient.setLookup({ - 'cls-6lbj1vee.ccs.tencent-cloud.com': { ip: '13.123.123.123' } - }) - const secrets = await k8sClient.getSecret({ namespace: 'default' }) - - console.log('secrets:', secrets) - }) - it('#execute', async function () { - this.timeout(5000) - const { options, deployOpts } = await getOptions() - deployOpts.props.ingressName = 'ingress-base' - deployOpts.props.secretName = 'cert---docmirror-cn' - deployOpts.context.tencentCertId = 'hNUZJrZf' - const plugin = new DeployCertToTencentTKEIngress(options) - - const ret = await plugin.doExecute(deployOpts) - console.log('sucess', ret) - }) -}) diff --git a/packages/plugins/plugin-tencent/test/plugins/upload-to-tencent.test.js b/packages/plugins/plugin-tencent/test/plugins/upload-to-tencent.test.js deleted file mode 100644 index b79f8203..00000000 --- a/packages/plugins/plugin-tencent/test/plugins/upload-to-tencent.test.js +++ /dev/null @@ -1,27 +0,0 @@ -import pkg from 'chai' -import { UploadCertToTencent } from '../../src/plugins/upload-to-tencent/index.js' -import { Certd } from '@certd/certd' -import { createOptions } from '../../../../../test/options.js' -const { expect } = pkg -describe('PluginUploadToTencent', function () { - it('#execute', async function () { - const options = createOptions() - const plugin = new UploadCertToTencent(options) - options.args = { test: false } - options.cert.email = 'xiaojunnuo@qq.com' - options.cert.domains = ['*.docmirror.cn'] - const certd = new Certd(options) - const cert = await certd.readCurrentCert() - const context = {} - const uploadOpts = { - accessProviders: options.accessProviders, - cert, - props: { name: 'certd部署测试', accessProvider: 'tencent' }, - context - } - await plugin.doExecute(uploadOpts) - console.log('context:', context) - - await plugin.doRollback(uploadOpts) - }) -}) diff --git a/packages/plugins/plugin-util/src/lib/k8s.client.ts b/packages/plugins/plugin-util/src/lib/k8s.client.ts new file mode 100644 index 00000000..d8c6d70b --- /dev/null +++ b/packages/plugins/plugin-util/src/lib/k8s.client.ts @@ -0,0 +1,116 @@ +import kubernetesClient from "kubernetes-client"; +import dns from "dns"; +import { logger } from "@certd/pipeline"; + +// @ts-ignore +const { KubeConfig, Client, Request } = kubernetesClient; + +export class K8sClient { + kubeConfigStr: string; + lookup!: any; + client!: any; + constructor(kubeConfigStr: string) { + this.kubeConfigStr = kubeConfigStr; + this.init(); + } + + init() { + const kubeconfig = new KubeConfig(); + kubeconfig.loadFromString(this.kubeConfigStr); + const reqOpts = { kubeconfig, request: {} } as any; + if (this.lookup) { + reqOpts.request.lookup = this.lookup; + } + + const backend = new Request(reqOpts); + this.client = new Client({ backend, version: "1.13" }); + } + + /** + * + * @param localRecords { [domain]:{ip:'xxx.xx.xxx'} } + */ + setLookup(localRecords: { [key: string]: { ip: string } }) { + this.lookup = (hostnameReq: any, options: any, callback: any) => { + logger.info("custom lookup", hostnameReq, localRecords); + if (localRecords[hostnameReq]) { + logger.info("local record", hostnameReq, localRecords[hostnameReq]); + callback(null, localRecords[hostnameReq].ip, 4); + } else { + dns.lookup(hostnameReq, options, callback); + } + }; + this.init(); + } + + /** + * 查询 secret列表 + * @param opts = {namespace:default} + * @returns secretsList + */ + async getSecret(opts: { namespace: string }) { + const namespace = opts.namespace || "default"; + return await this.client.api.v1.namespaces(namespace).secrets.get(); + } + + /** + * 创建Secret + * @param opts {namespace:default, body:yamlStr} + * @returns {Promise<*>} + */ + async createSecret(opts: any) { + const namespace = opts.namespace || "default"; + const created = await this.client.api.v1.namespaces(namespace).secrets.post({ + body: opts.body, + }); + logger.info("new secrets:", created); + return created; + } + + async updateSecret(opts: any) { + const namespace = opts.namespace || "default"; + const secretName = opts.secretName; + if (secretName == null) { + throw new Error("secretName 不能为空"); + } + return await this.client.api.v1.namespaces(namespace).secrets(secretName).put({ + body: opts.body, + }); + } + + async patchSecret(opts: any) { + const namespace = opts.namespace || "default"; + const secretName = opts.secretName; + if (secretName == null) { + throw new Error("secretName 不能为空"); + } + return await this.client.api.v1.namespaces(namespace).secrets(secretName).patch({ + body: opts.body, + }); + } + + async getIngressList(opts: any) { + const namespace = opts.namespace || "default"; + return await this.client.apis.extensions.v1beta1.namespaces(namespace).ingresses.get(); + } + + async getIngress(opts: any) { + const namespace = opts.namespace || "default"; + const ingressName = opts.ingressName; + if (!ingressName) { + throw new Error("ingressName 不能为空"); + } + return await this.client.apis.extensions.v1beta1.namespaces(namespace).ingresses(ingressName).get(); + } + + async patchIngress(opts: any) { + const namespace = opts.namespace || "default"; + const ingressName = opts.ingressName; + if (!ingressName) { + throw new Error("ingressName 不能为空"); + } + return await this.client.apis.extensions.v1beta1.namespaces(namespace).ingresses(ingressName).patch({ + body: opts.body, + }); + } +} diff --git a/packages/server/certd-server b/packages/server/certd-server index 7782a7de..77fb1fa8 160000 --- a/packages/server/certd-server +++ b/packages/server/certd-server @@ -1 +1 @@ -Subproject commit 7782a7de65f60e2b4611bca1e29b4ab19deec82b +Subproject commit 77fb1fa8492d95c7f6698aaaadac34cb2de4de2b