From d2897cefaa52e29f3883987db4e6fc6253a33478 Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Tue, 9 May 2023 13:52:25 +0800 Subject: [PATCH] refactor: huawei --- packages/core/acme-client/src/auto.js | 4 +- packages/core/pipeline/src/core/executor.ts | 13 ++- .../plugin-all/test/huawei/pipeline.huawei.ts | 9 +- .../plugin-all/test/huawei/pipeline.test.ts | 2 +- .../src/plugin/cert-plugin/acme.ts | 17 ++- .../src/plugin/cert-plugin/index.ts | 19 +++ .../src/dns-provider/huawei-dns-provider.ts | 108 +++++++++--------- .../plugins/plugin-huawei/src/lib/client.ts | 33 ++++-- 8 files changed, 128 insertions(+), 77 deletions(-) diff --git a/packages/core/acme-client/src/auto.js b/packages/core/acme-client/src/auto.js index e36eade89..4779c979d 100644 --- a/packages/core/acme-client/src/auto.js +++ b/packages/core/acme-client/src/auto.js @@ -116,7 +116,7 @@ module.exports = async function(client, userOpts) { const keyAuthorization = await client.getChallengeKeyAuthorization(challenge); try { - await opts.challengeCreateFn(authz, challenge, keyAuthorization); + const recordItem = await opts.challengeCreateFn(authz, challenge, keyAuthorization); /* Challenge verification */ if (opts.skipChallengeVerification === true) { @@ -139,7 +139,7 @@ module.exports = async function(client, userOpts) { log(`[auto] [${d}] Trigger challengeRemoveFn()`); try { - await opts.challengeRemoveFn(authz, challenge, keyAuthorization); + await opts.challengeRemoveFn(authz, challenge, keyAuthorization, recordItem); } catch (e) { log(`[auto] [${d}] challengeRemoveFn threw error: ${e.message}`); diff --git a/packages/core/pipeline/src/core/executor.ts b/packages/core/pipeline/src/core/executor.ts index 353c1d60d..436e81632 100644 --- a/packages/core/pipeline/src/core/executor.ts +++ b/packages/core/pipeline/src/core/executor.ts @@ -49,11 +49,22 @@ export class Executor { this.runtime.start(runnable); await this.onChanged(this.runtime); const contextKey = `status.${runnable.id}`; + const inputKey = `input.${runnable.id}`; if (runnable.strategy?.runStrategy === RunStrategy.SkipWhenSucceed) { //如果是成功后跳过策略 const lastResult = await this.pipelineContext.get(contextKey); - if (lastResult != null && lastResult === ResultType.success) { + const lastInput = await this.pipelineContext.get(inputKey); + let inputChanged = false; + //TODO 参数不变 + if (runnableType === "step") { + const step = runnable as Step; + const input = JSON.stringify(step.input); + if (input != null && lastInput !== input) { + inputChanged = true; + } + } + if (lastResult != null && lastResult === ResultType.success && !inputChanged) { this.runtime.skip(runnable); await this.onChanged(this.runtime); return ResultType.skip; diff --git a/packages/plugins/plugin-all/test/huawei/pipeline.huawei.ts b/packages/plugins/plugin-all/test/huawei/pipeline.huawei.ts index 4b836463b..35476fc7c 100644 --- a/packages/plugins/plugin-all/test/huawei/pipeline.huawei.ts +++ b/packages/plugins/plugin-all/test/huawei/pipeline.huawei.ts @@ -7,7 +7,7 @@ function generateId() { } export const pipeline: Pipeline = { version: 1, - id: generateId(), + id: "3", title: "华为管道测试", userId: 1, triggers: [], @@ -27,10 +27,13 @@ export const pipeline: Pipeline = { title: "申请证书", type: "CertApply", input: { - domains: ["*.powerleader.chat"], + domains: ["powerleader.chat", "*.powerleader.chat", "*.test.powerleader.chat", "*.ai.powerleader.chat"], email: "xiaojunnuo@qq.com", dnsProviderType: "huawei", - accessId: "111", + accessId: "333", + }, + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed, }, }, ], diff --git a/packages/plugins/plugin-all/test/huawei/pipeline.test.ts b/packages/plugins/plugin-all/test/huawei/pipeline.test.ts index 8726f9f02..5137ef5b7 100644 --- a/packages/plugins/plugin-all/test/huawei/pipeline.test.ts +++ b/packages/plugins/plugin-all/test/huawei/pipeline.test.ts @@ -13,7 +13,7 @@ describe("pipeline-hauwei-test", function () { } const executor = new Executor({ userId: "test", pipeline, onChanged, accessService: new AccessServiceTest(), storage: new FileStorage() }); - await executor.run(1, "user"); + await executor.run(2, "user"); // expect(define.name).eq("EchoPlugin"); }); }); diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts index 07a654047..42b6a3fb9 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts @@ -123,12 +123,17 @@ export class AcmeService { /* Replace this */ this.logger.info(`Would remove TXT record "${dnsRecord}" with value "${recordValue}"`); - await dnsProvider.removeRecord({ - fullRecord: dnsRecord, - type: "TXT", - value: keyAuthorization, - record: recordItem, - }); + try { + await dnsProvider.removeRecord({ + fullRecord: dnsRecord, + type: "TXT", + value: keyAuthorization, + record: recordItem, + }); + } catch (e) { + this.logger.error("删除解析记录出错:", e); + throw e; + } } } diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/index.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/index.ts index 8394905f2..d587faf63 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/index.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/index.ts @@ -181,6 +181,17 @@ export class CertApplyPlugin implements ITaskPlugin { if (this.forceUpdate) { return null; } + + let inputChanged = false; + const inputCacheKey = "input.cert"; + const oldInputStr = await this.pipelineContext.get(inputCacheKey); + await this.pipelineContext.set(inputCacheKey, this.cert); + const oldInput = JSON.stringify(oldInputStr); + const thisInput = JSON.stringify(this.cert); + if (oldInput !== thisInput) { + inputChanged = true; + } + let oldCert; try { oldCert = await this.readCurrentCert(); @@ -192,6 +203,11 @@ export class CertApplyPlugin implements ITaskPlugin { return null; } + if (inputChanged) { + this.logger.info("输入参数变更,申请新证书"); + return null; + } + const ret = this.isWillExpire(oldCert.expires, this.renewDays); if (!ret.isWillExpire) { this.logger.info(`证书还未过期:过期时间${dayjs(oldCert.expires).format("YYYY-MM-DD HH:mm:ss")},剩余${ret.leftDays}天`); @@ -261,6 +277,9 @@ export class CertApplyPlugin implements ITaskPlugin { csr: this.formatCert(cert.csr), }; await this.pipelineContext.set("cert", newCert); + await this.pipelineContext.set("cert.crt", newCert.crt); + await this.pipelineContext.set("cert.key", newCert.key); + await this.pipelineContext.set("cert.csr", newCert.csr); } async readCurrentCert() { diff --git a/packages/plugins/plugin-huawei/src/dns-provider/huawei-dns-provider.ts b/packages/plugins/plugin-huawei/src/dns-provider/huawei-dns-provider.ts index 35492bd6a..d343b6e61 100644 --- a/packages/plugins/plugin-huawei/src/dns-provider/huawei-dns-provider.ts +++ b/packages/plugins/plugin-huawei/src/dns-provider/huawei-dns-provider.ts @@ -2,7 +2,11 @@ import _ from "lodash"; import { CreateRecordOptions, IDnsProvider, IsDnsProvider, RemoveRecordOptions } from "@certd/plugin-cert"; import { Autowire, ILogger } from "@certd/pipeline"; import { HuaweiAccess } from "../access"; -import { HuaweiYunClient } from "../lib/client"; +import { ApiRequestOptions, HuaweiYunClient } from "../lib/client"; + +export type SearchRecordOptions = { + zoneId: string; +} & CreateRecordOptions; @IsDnsProvider({ name: "huawei", @@ -11,85 +15,85 @@ import { HuaweiYunClient } from "../lib/client"; accessType: "huawei", }) export class HuaweiDnsProvider implements IDnsProvider { - client: any; + client!: HuaweiYunClient; @Autowire() access!: HuaweiAccess; @Autowire() logger!: ILogger; - endpoint = "https://domains-external.myhuaweicloud.com"; + domainEndpoint = "https://domains-external.myhuaweicloud.com"; + dnsEndpoint = "https://dns.cn-south-1.myhuaweicloud.com"; async onInstance() { const access: any = this.access; this.client = new HuaweiYunClient(access); } async getDomainList() { - const url = `${this.endpoint}/v2/domains`; + const url = `${this.dnsEndpoint}/v2/zones`; const ret = await this.client.request({ url, method: "GET", }); - return ret.domains; + return ret.zones; } 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; + const zoneList = await this.getDomainList(); + let zoneRecord = null; + for (const item of zoneList) { + if (_.endsWith(dnsRecord + ".", item.name)) { + zoneRecord = item; break; } } - if (!domain) { + if (!zoneRecord) { throw new Error("can not find Domain ," + dnsRecord); } - return domain; + return zoneRecord; } - async getRecords(domain: string, rr: string, value: string) { - const params: any = { - RegionId: "cn-hangzhou", - DomainName: domain, - RRKeyWord: rr, - ValueKeyWord: undefined, + async searchRecord(options: SearchRecordOptions): Promise { + const req: ApiRequestOptions = { + url: `${this.dnsEndpoint}/v2/zones/${options.zoneId}/recordsets?name=${options.fullRecord}.`, + method: "GET", }; - if (value) { - params.ValueKeyWord = value; - } - - const requestOption = { - method: "POST", - }; - - const ret = await this.client.request("DescribeDomainRecords", params, requestOption); - return ret.DomainRecords.Record; + const ret = await this.client.request(req); + return ret.recordsets; } 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 zoneRecord = await this.matchDomain(fullRecord); + const zoneId = zoneRecord.id; - const params = { - RegionId: "cn-hangzhou", - DomainName: domain, - RR: rr, - Type: type, - Value: value, - // Line: 'oversea' // 海外 - }; - - const requestOption = { - method: "POST", - }; + const records: any = await this.searchRecord({ + zoneId, + ...options, + }); + if (records && records.length > 0) { + for (const record of records) { + await this.removeRecord({ + record: records[0], + ...options, + }); + } + } try { - const ret = await this.client.request("AddDomainRecord", params, requestOption); - this.logger.info("添加域名解析成功:", value, value, ret.RecordId); - return ret.RecordId; + const req: ApiRequestOptions = { + url: `${this.dnsEndpoint}/v2/zones/${zoneId}/recordsets`, + method: "POST", + data: { + name: fullRecord + ".", + type, + records: [`"${value}"`], + }, + }; + const ret = await this.client.request(req); + this.logger.info("添加域名解析成功:", value, ret); + return ret; } catch (e: any) { - if (e.code === "DomainRecordDuplicate") { + if (e.code === "DNS.0312") { return; } this.logger.info("添加域名解析出错", e); @@ -98,16 +102,12 @@ export class HuaweiDnsProvider implements IDnsProvider { } async removeRecord(options: RemoveRecordOptions): Promise { const { fullRecord, value, record } = options; - const params = { - RegionId: "cn-hangzhou", - RecordId: record, + const req: ApiRequestOptions = { + url: `${this.dnsEndpoint}/v2/zones/${record.zone_id}/recordsets/${record.id}`, + method: "DELETE", }; - const requestOption = { - method: "POST", - }; - - const ret = await this.client.request("DeleteDomainRecord", params, requestOption); + const ret = await this.client.request(req); this.logger.info("删除域名解析成功:", fullRecord, value, ret.RecordId); return ret.RecordId; } diff --git a/packages/plugins/plugin-huawei/src/lib/client.ts b/packages/plugins/plugin-huawei/src/lib/client.ts index 7ba6d7670..ba40b13b5 100644 --- a/packages/plugins/plugin-huawei/src/lib/client.ts +++ b/packages/plugins/plugin-huawei/src/lib/client.ts @@ -3,12 +3,13 @@ import signer from "./signer"; import https from "https"; import { HuaweiAccess } from "../access"; import { axios } from "@certd/acme-client"; +import { logger } from "@certd/pipeline"; export type ApiRequestOptions = { method: string; url: string; - headers: any; - body: any; + headers?: any; + data?: any; }; export class HuaweiYunClient { access: HuaweiAccess; @@ -26,17 +27,29 @@ export class HuaweiYunClient { //Set request host. //Set request URI. //Set parameters for the request URL. - const r = new signer.HttpRequest(options.method, options.url, options.headers, options.body); + let body = undefined; + if (options.data) { + body = JSON.stringify(options.data); + } + const r = new signer.HttpRequest(options.method, options.url, options.headers, body); //Add header parameters, for example, x-domain-id for invoking a global service and x-project-id for invoking a project-level service. r.headers = { "Content-Type": "application/json" }; //Add a body if you have specified the PUT or POST method. Special characters, such as the double quotation mark ("), contained in the body must be escaped. - r.body = ""; - + // r.body = option; const opt = sig.Sign(r); - console.log("opt", opt); - console.log(opt.headers["X-Sdk-Date"]); - console.log(opt.headers["Authorization"]); - const res = await axios.request(opt); - return res; + try { + const res = await axios.request({ + url: options.url, + method: options.method, + headers: opt.headers, + data: body, + }); + return res.data; + } catch (e: any) { + logger.error("华为云接口请求出错:", e?.response?.data); + const error: any = new Error(e?.response?.data.message); + error.code = e?.response?.code; + throw error; + } } }