refactor: huawei

pull/21/merge
xiaojunnuo 2023-05-09 13:52:25 +08:00
parent 9747d40734
commit d2897cefaa
8 changed files with 128 additions and 77 deletions

View File

@ -116,7 +116,7 @@ module.exports = async function(client, userOpts) {
const keyAuthorization = await client.getChallengeKeyAuthorization(challenge); const keyAuthorization = await client.getChallengeKeyAuthorization(challenge);
try { try {
await opts.challengeCreateFn(authz, challenge, keyAuthorization); const recordItem = await opts.challengeCreateFn(authz, challenge, keyAuthorization);
/* Challenge verification */ /* Challenge verification */
if (opts.skipChallengeVerification === true) { if (opts.skipChallengeVerification === true) {
@ -139,7 +139,7 @@ module.exports = async function(client, userOpts) {
log(`[auto] [${d}] Trigger challengeRemoveFn()`); log(`[auto] [${d}] Trigger challengeRemoveFn()`);
try { try {
await opts.challengeRemoveFn(authz, challenge, keyAuthorization); await opts.challengeRemoveFn(authz, challenge, keyAuthorization, recordItem);
} }
catch (e) { catch (e) {
log(`[auto] [${d}] challengeRemoveFn threw error: ${e.message}`); log(`[auto] [${d}] challengeRemoveFn threw error: ${e.message}`);

View File

@ -49,11 +49,22 @@ export class Executor {
this.runtime.start(runnable); this.runtime.start(runnable);
await this.onChanged(this.runtime); await this.onChanged(this.runtime);
const contextKey = `status.${runnable.id}`; const contextKey = `status.${runnable.id}`;
const inputKey = `input.${runnable.id}`;
if (runnable.strategy?.runStrategy === RunStrategy.SkipWhenSucceed) { if (runnable.strategy?.runStrategy === RunStrategy.SkipWhenSucceed) {
//如果是成功后跳过策略 //如果是成功后跳过策略
const lastResult = await this.pipelineContext.get(contextKey); 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); this.runtime.skip(runnable);
await this.onChanged(this.runtime); await this.onChanged(this.runtime);
return ResultType.skip; return ResultType.skip;

View File

@ -7,7 +7,7 @@ function generateId() {
} }
export const pipeline: Pipeline = { export const pipeline: Pipeline = {
version: 1, version: 1,
id: generateId(), id: "3",
title: "华为管道测试", title: "华为管道测试",
userId: 1, userId: 1,
triggers: [], triggers: [],
@ -27,10 +27,13 @@ export const pipeline: Pipeline = {
title: "申请证书", title: "申请证书",
type: "CertApply", type: "CertApply",
input: { input: {
domains: ["*.powerleader.chat"], domains: ["powerleader.chat", "*.powerleader.chat", "*.test.powerleader.chat", "*.ai.powerleader.chat"],
email: "xiaojunnuo@qq.com", email: "xiaojunnuo@qq.com",
dnsProviderType: "huawei", dnsProviderType: "huawei",
accessId: "111", accessId: "333",
},
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
}, },
}, },
], ],

View File

@ -13,7 +13,7 @@ describe("pipeline-hauwei-test", function () {
} }
const executor = new Executor({ userId: "test", pipeline, onChanged, accessService: new AccessServiceTest(), storage: new FileStorage() }); 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"); // expect(define.name).eq("EchoPlugin");
}); });
}); });

View File

@ -123,12 +123,17 @@ export class AcmeService {
/* Replace this */ /* Replace this */
this.logger.info(`Would remove TXT record "${dnsRecord}" with value "${recordValue}"`); this.logger.info(`Would remove TXT record "${dnsRecord}" with value "${recordValue}"`);
await dnsProvider.removeRecord({ try {
fullRecord: dnsRecord, await dnsProvider.removeRecord({
type: "TXT", fullRecord: dnsRecord,
value: keyAuthorization, type: "TXT",
record: recordItem, value: keyAuthorization,
}); record: recordItem,
});
} catch (e) {
this.logger.error("删除解析记录出错:", e);
throw e;
}
} }
} }

View File

@ -181,6 +181,17 @@ export class CertApplyPlugin implements ITaskPlugin {
if (this.forceUpdate) { if (this.forceUpdate) {
return null; 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; let oldCert;
try { try {
oldCert = await this.readCurrentCert(); oldCert = await this.readCurrentCert();
@ -192,6 +203,11 @@ export class CertApplyPlugin implements ITaskPlugin {
return null; return null;
} }
if (inputChanged) {
this.logger.info("输入参数变更,申请新证书");
return null;
}
const ret = this.isWillExpire(oldCert.expires, this.renewDays); const ret = this.isWillExpire(oldCert.expires, this.renewDays);
if (!ret.isWillExpire) { if (!ret.isWillExpire) {
this.logger.info(`证书还未过期:过期时间${dayjs(oldCert.expires).format("YYYY-MM-DD HH:mm:ss")},剩余${ret.leftDays}`); this.logger.info(`证书还未过期:过期时间${dayjs(oldCert.expires).format("YYYY-MM-DD HH:mm:ss")},剩余${ret.leftDays}`);
@ -261,6 +277,9 @@ export class CertApplyPlugin implements ITaskPlugin {
csr: this.formatCert(cert.csr), csr: this.formatCert(cert.csr),
}; };
await this.pipelineContext.set("cert", newCert); 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() { async readCurrentCert() {

View File

@ -2,7 +2,11 @@ import _ from "lodash";
import { CreateRecordOptions, IDnsProvider, IsDnsProvider, RemoveRecordOptions } from "@certd/plugin-cert"; import { CreateRecordOptions, IDnsProvider, IsDnsProvider, RemoveRecordOptions } from "@certd/plugin-cert";
import { Autowire, ILogger } from "@certd/pipeline"; import { Autowire, ILogger } from "@certd/pipeline";
import { HuaweiAccess } from "../access"; import { HuaweiAccess } from "../access";
import { HuaweiYunClient } from "../lib/client"; import { ApiRequestOptions, HuaweiYunClient } from "../lib/client";
export type SearchRecordOptions = {
zoneId: string;
} & CreateRecordOptions;
@IsDnsProvider({ @IsDnsProvider({
name: "huawei", name: "huawei",
@ -11,85 +15,85 @@ import { HuaweiYunClient } from "../lib/client";
accessType: "huawei", accessType: "huawei",
}) })
export class HuaweiDnsProvider implements IDnsProvider { export class HuaweiDnsProvider implements IDnsProvider {
client: any; client!: HuaweiYunClient;
@Autowire() @Autowire()
access!: HuaweiAccess; access!: HuaweiAccess;
@Autowire() @Autowire()
logger!: ILogger; logger!: ILogger;
endpoint = "https://domains-external.myhuaweicloud.com"; domainEndpoint = "https://domains-external.myhuaweicloud.com";
dnsEndpoint = "https://dns.cn-south-1.myhuaweicloud.com";
async onInstance() { async onInstance() {
const access: any = this.access; const access: any = this.access;
this.client = new HuaweiYunClient(access); this.client = new HuaweiYunClient(access);
} }
async getDomainList() { async getDomainList() {
const url = `${this.endpoint}/v2/domains`; const url = `${this.dnsEndpoint}/v2/zones`;
const ret = await this.client.request({ const ret = await this.client.request({
url, url,
method: "GET", method: "GET",
}); });
return ret.domains; return ret.zones;
} }
async matchDomain(dnsRecord: string) { async matchDomain(dnsRecord: string) {
const list = await this.getDomainList(); const zoneList = await this.getDomainList();
let domain = null; let zoneRecord = null;
for (const item of list) { for (const item of zoneList) {
if (_.endsWith(dnsRecord, item.DomainName)) { if (_.endsWith(dnsRecord + ".", item.name)) {
domain = item.DomainName; zoneRecord = item;
break; break;
} }
} }
if (!domain) { if (!zoneRecord) {
throw new Error("can not find Domain ," + dnsRecord); throw new Error("can not find Domain ," + dnsRecord);
} }
return domain; return zoneRecord;
} }
async getRecords(domain: string, rr: string, value: string) { async searchRecord(options: SearchRecordOptions): Promise<any> {
const params: any = { const req: ApiRequestOptions = {
RegionId: "cn-hangzhou", url: `${this.dnsEndpoint}/v2/zones/${options.zoneId}/recordsets?name=${options.fullRecord}.`,
DomainName: domain, method: "GET",
RRKeyWord: rr,
ValueKeyWord: undefined,
}; };
if (value) { const ret = await this.client.request(req);
params.ValueKeyWord = value; return ret.recordsets;
}
const requestOption = {
method: "POST",
};
const ret = await this.client.request("DescribeDomainRecords", params, requestOption);
return ret.DomainRecords.Record;
} }
async createRecord(options: CreateRecordOptions): Promise<any> { async createRecord(options: CreateRecordOptions): Promise<any> {
const { fullRecord, value, type } = options; const { fullRecord, value, type } = options;
this.logger.info("添加域名解析:", fullRecord, value); this.logger.info("添加域名解析:", fullRecord, value);
const domain = await this.matchDomain(fullRecord); const zoneRecord = await this.matchDomain(fullRecord);
const rr = fullRecord.replace("." + domain, ""); const zoneId = zoneRecord.id;
const params = { const records: any = await this.searchRecord({
RegionId: "cn-hangzhou", zoneId,
DomainName: domain, ...options,
RR: rr, });
Type: type, if (records && records.length > 0) {
Value: value, for (const record of records) {
// Line: 'oversea' // 海外 await this.removeRecord({
}; record: records[0],
...options,
const requestOption = { });
method: "POST", }
}; }
try { try {
const ret = await this.client.request("AddDomainRecord", params, requestOption); const req: ApiRequestOptions = {
this.logger.info("添加域名解析成功:", value, value, ret.RecordId); url: `${this.dnsEndpoint}/v2/zones/${zoneId}/recordsets`,
return ret.RecordId; 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) { } catch (e: any) {
if (e.code === "DomainRecordDuplicate") { if (e.code === "DNS.0312") {
return; return;
} }
this.logger.info("添加域名解析出错", e); this.logger.info("添加域名解析出错", e);
@ -98,16 +102,12 @@ export class HuaweiDnsProvider implements IDnsProvider {
} }
async removeRecord(options: RemoveRecordOptions): Promise<any> { async removeRecord(options: RemoveRecordOptions): Promise<any> {
const { fullRecord, value, record } = options; const { fullRecord, value, record } = options;
const params = { const req: ApiRequestOptions = {
RegionId: "cn-hangzhou", url: `${this.dnsEndpoint}/v2/zones/${record.zone_id}/recordsets/${record.id}`,
RecordId: record, method: "DELETE",
}; };
const requestOption = { const ret = await this.client.request(req);
method: "POST",
};
const ret = await this.client.request("DeleteDomainRecord", params, requestOption);
this.logger.info("删除域名解析成功:", fullRecord, value, ret.RecordId); this.logger.info("删除域名解析成功:", fullRecord, value, ret.RecordId);
return ret.RecordId; return ret.RecordId;
} }

View File

@ -3,12 +3,13 @@ import signer from "./signer";
import https from "https"; import https from "https";
import { HuaweiAccess } from "../access"; import { HuaweiAccess } from "../access";
import { axios } from "@certd/acme-client"; import { axios } from "@certd/acme-client";
import { logger } from "@certd/pipeline";
export type ApiRequestOptions = { export type ApiRequestOptions = {
method: string; method: string;
url: string; url: string;
headers: any; headers?: any;
body: any; data?: any;
}; };
export class HuaweiYunClient { export class HuaweiYunClient {
access: HuaweiAccess; access: HuaweiAccess;
@ -26,17 +27,29 @@ export class HuaweiYunClient {
//Set request host. //Set request host.
//Set request URI. //Set request URI.
//Set parameters for the request URL. //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. //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" }; 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. //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); const opt = sig.Sign(r);
console.log("opt", opt); try {
console.log(opt.headers["X-Sdk-Date"]); const res = await axios.request({
console.log(opt.headers["Authorization"]); url: options.url,
const res = await axios.request(opt); method: options.method,
return res; 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;
}
} }
} }