fix: 修复成功后跳过之后丢失腾讯云证书id的bug

pull/148/head
xiaojunnuo 2024-08-23 23:26:31 +08:00
parent 3345c145b8
commit 37eb762afe
10 changed files with 78 additions and 45 deletions

View File

@ -144,7 +144,6 @@ module.exports = async (client, userOpts) => {
log(`[auto] [${d}] challenge verification threw error: ${e.message}`); log(`[auto] [${d}] challenge verification threw error: ${e.message}`);
} }
} }
/* Complete challenge and wait for valid status */ /* Complete challenge and wait for valid status */
log(`[auto] [${d}] Completing challenge with ACME provider and waiting for valid status`); log(`[auto] [${d}] Completing challenge with ACME provider and waiting for valid status`);
await client.completeChallenge(challenge); await client.completeChallenge(challenge);
@ -236,6 +235,9 @@ module.exports = async (client, userOpts) => {
for (const challengePromises of allChallengePromises) { for (const challengePromises of allChallengePromises) {
i += 1; i += 1;
log(`开始第${i}`); log(`开始第${i}`);
if (opts.signal && opts.signal.aborted) {
throw new Error('用户取消');
}
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
await runPromisePa(challengePromises); await runPromisePa(challengePromises);
} }

View File

@ -490,6 +490,9 @@ class AcmeClient {
const keyAuthorization = await this.getChallengeKeyAuthorization(challenge); const keyAuthorization = await this.getChallengeKeyAuthorization(challenge);
const verifyFn = async () => { const verifyFn = async () => {
if (this.opts.signal && this.opts.signal.aborted) {
throw new Error('用户取消');
}
await verify[challenge.type](authz, challenge, keyAuthorization); await verify[challenge.type](authz, challenge, keyAuthorization);
}; };
@ -513,6 +516,9 @@ class AcmeClient {
*/ */
async completeChallenge(challenge) { async completeChallenge(challenge) {
if (this.opts.signal && this.opts.signal.aborted) {
throw new Error('用户取消');
}
const resp = await this.api.completeChallenge(challenge.url, {}); const resp = await this.api.completeChallenge(challenge.url, {});
return resp.data; return resp.data;
} }
@ -550,6 +556,10 @@ class AcmeClient {
} }
const verifyFn = async (abort) => { const verifyFn = async (abort) => {
if (this.opts.signal && this.opts.signal.aborted) {
throw new Error('用户取消');
}
const resp = await this.api.apiRequest(item.url, null, [200]); const resp = await this.api.apiRequest(item.url, null, [200]);
/* Verify status */ /* Verify status */

View File

@ -45,6 +45,7 @@ export interface ClientOptions {
backoffMin?: number; backoffMin?: number;
backoffMax?: number; backoffMax?: number;
urlMapping?: UrlMapping; urlMapping?: UrlMapping;
signal?: AbortSignal;
} }
export interface ClientExternalAccountBindingOptions { export interface ClientExternalAccountBindingOptions {
@ -61,6 +62,7 @@ export interface ClientAutoOptions {
skipChallengeVerification?: boolean; skipChallengeVerification?: boolean;
challengePriority?: string[]; challengePriority?: string[];
preferredChain?: string; preferredChain?: string;
signal?: AbortSignal;
} }
export class Client { export class Client {

View File

@ -23,6 +23,7 @@ export type ExecutorOptions = {
emailService: IEmailService; emailService: IEmailService;
fileRootDir?: string; fileRootDir?: string;
}; };
export class Executor { export class Executor {
pipeline: Pipeline; pipeline: Pipeline;
runtime!: RunHistory; runtime!: RunHistory;
@ -32,7 +33,8 @@ export class Executor {
lastStatusMap!: RunnableCollection; lastStatusMap!: RunnableCollection;
lastRuntime!: RunHistory; lastRuntime!: RunHistory;
options: ExecutorOptions; options: ExecutorOptions;
canceled = false; abort: AbortController = new AbortController();
onChanged: (history: RunHistory) => Promise<void>; onChanged: (history: RunHistory) => Promise<void>;
constructor(options: ExecutorOptions) { constructor(options: ExecutorOptions) {
this.options = options; this.options = options;
@ -53,7 +55,7 @@ export class Executor {
} }
async cancel() { async cancel() {
this.canceled = true; this.abort.abort();
this.runtime?.cancel(this.pipeline); this.runtime?.cancel(this.pipeline);
await this.onChanged(this.runtime); await this.onChanged(this.runtime);
} }
@ -102,6 +104,8 @@ export class Executor {
} }
} }
if (lastResult != null && lastResult === ResultType.success && !inputChanged) { if (lastResult != null && lastResult === ResultType.success && !inputChanged) {
runnable.status!.output = lastNode?.status?.output;
runnable.status!.files = lastNode?.status?.files;
this.runtime.skip(runnable); this.runtime.skip(runnable);
await this.onChanged(this.runtime); await this.onChanged(this.runtime);
return ResultType.skip; return ResultType.skip;
@ -113,10 +117,15 @@ export class Executor {
// const timeout = runnable.timeout ?? 20 * 60 * 1000; // const timeout = runnable.timeout ?? 20 * 60 * 1000;
try { try {
if (this.canceled) { if (this.abort.signal.aborted) {
this.runtime.cancel(runnable);
return ResultType.canceled; return ResultType.canceled;
} }
await run(); await run();
if (this.abort.signal.aborted) {
this.runtime.cancel(runnable);
return ResultType.canceled;
}
this.runtime.success(runnable); this.runtime.success(runnable);
return ResultType.success; return ResultType.success;
} catch (e: any) { } catch (e: any) {
@ -211,16 +220,11 @@ export class Executor {
if (item.component?.name === "pi-output-selector") { if (item.component?.name === "pi-output-selector") {
const contextKey = step.input[key]; const contextKey = step.input[key];
if (contextKey != null) { if (contextKey != null) {
const value = this.runtime.context[contextKey]; // "cert": "step.-BNFVPMKPu2O-i9NiOQxP.cert",
if (value == null) { const arr = contextKey.split(".");
currentLogger.warn(`[step init] input ${define.title} is null前置任务步骤输出值为空请按如下步骤排查`); const id = arr[1];
currentLogger.log(`1、检查前置任务证书申请任务是否是配置了成功后跳过如果是请改为正常执行`); const outputKey = arr[2];
currentLogger.log( step.input[key] = this.lastStatusMap.get(id)?.status?.output[outputKey];
`2、是否曾经删除过前置任务证书申请任务然后又重新添加了如果是请重新编辑当前任务重新选择一下前置任务输出的参数域名证书那一栏`
);
currentLogger.log(`3、以上都不是联系作者吧`);
}
step.input[key] = value;
} }
} }
}); });
@ -241,6 +245,7 @@ export class Executor {
parent: this.runtime.id, parent: this.runtime.id,
rootDir: this.options.fileRootDir, rootDir: this.options.fileRootDir,
}), }),
signal: this.abort.signal,
}; };
instance.setCtx(taskCtx); instance.setCtx(taskCtx);
@ -254,8 +259,8 @@ export class Executor {
//输出上下文变量到output context //输出上下文变量到output context
_.forEach(define.output, (item: any, key: any) => { _.forEach(define.output, (item: any, key: any) => {
step.status!.output[key] = instance[key]; step.status!.output[key] = instance[key];
const stepOutputKey = `step.${step.id}.${key}`; // const stepOutputKey = `step.${step.id}.${key}`;
this.runtime.context[stepOutputKey] = instance[key]; // this.runtime.context[stepOutputKey] = instance[key];
}); });
step.status!.files = instance.getFiles(); step.status!.files = instance.getFiles();

View File

@ -1,4 +1,4 @@
import { Context, HistoryResult, Pipeline, ResultType, Runnable, RunnableMap, Stage, Step, Task } from "../dt/index.js"; import { HistoryResult, Pipeline, ResultType, Runnable, RunnableMap, Stage, Step, Task } from "../dt/index.js";
import _ from "lodash-es"; import _ from "lodash-es";
import { buildLogger } from "../utils/util.log.js"; import { buildLogger } from "../utils/util.log.js";
import { Logger } from "log4js"; import { Logger } from "log4js";
@ -14,15 +14,12 @@ export type RunTrigger = {
export function NewRunHistory(obj: any) { export function NewRunHistory(obj: any) {
const history = new RunHistory(obj.id, obj.trigger, obj.pipeline); const history = new RunHistory(obj.id, obj.trigger, obj.pipeline);
history.context = obj.context;
history.logs = obj.logs; history.logs = obj.logs;
history._loggers = obj.loggers; history._loggers = obj.loggers;
return history; return history;
} }
export class RunHistory { export class RunHistory {
id!: string; id!: string;
//运行时上下文变量
context: Context = {};
pipeline!: Pipeline; pipeline!: Pipeline;
logs: { logs: {
[runnableId: string]: string[]; [runnableId: string]: string[];

View File

@ -64,6 +64,7 @@ export type TaskInstanceContext = {
http: AxiosInstance; http: AxiosInstance;
fileStore: FileStore; fileStore: FileStore;
lastStatus?: Runnable; lastStatus?: Runnable;
signal: AbortSignal;
}; };
export abstract class AbstractTaskPlugin implements ITaskPlugin { export abstract class AbstractTaskPlugin implements ITaskPlugin {

View File

@ -23,6 +23,7 @@ type AcmeServiceOptions = {
skipLocalVerify?: boolean; skipLocalVerify?: boolean;
useMappingProxy?: boolean; useMappingProxy?: boolean;
privateKeyType?: PrivateKeyType; privateKeyType?: PrivateKeyType;
signal?: AbortSignal;
}; };
export class AcmeService { export class AcmeService {
@ -99,6 +100,7 @@ export class AcmeService {
backoffMin: 5000, backoffMin: 5000,
backoffMax: 10000, backoffMax: 10000,
urlMapping, urlMapping,
signal: this.options.signal,
}); });
if (conf.accountUrl == null) { if (conf.accountUrl == null) {
@ -253,6 +255,7 @@ export class AcmeService {
challengeRemoveFn: async (authz: acme.Authorization, challenge: Challenge, keyAuthorization: string, recordItem: any): Promise<any> => { challengeRemoveFn: async (authz: acme.Authorization, challenge: Challenge, keyAuthorization: string, recordItem: any): Promise<any> => {
return await this.challengeRemoveFn(authz, challenge, keyAuthorization, recordItem, dnsProvider); return await this.challengeRemoveFn(authz, challenge, keyAuthorization, recordItem, dnsProvider);
}, },
signal: this.options.signal,
}); });
const cert: CertInfo = { const cert: CertInfo = {

View File

@ -64,6 +64,7 @@
"svg-captcha": "^1.4.0", "svg-captcha": "^1.4.0",
"tencentcloud-sdk-nodejs": "^4.0.44", "tencentcloud-sdk-nodejs": "^4.0.44",
"tencentcloud-sdk-nodejs-dnspod": "^4.0.866", "tencentcloud-sdk-nodejs-dnspod": "^4.0.866",
"tencentcloud-sdk-nodejs-teo": "^4.0.919",
"typeorm": "^0.3.11" "typeorm": "^0.3.11"
}, },
"devDependencies": { "devDependencies": {

View File

@ -4,9 +4,8 @@ import { TencentAccess } from '../../access/index.js';
import { CertInfo } from '@certd/plugin-cert'; import { CertInfo } from '@certd/plugin-cert';
@IsTaskPlugin({ @IsTaskPlugin({
name: 'DeployCertToTencentEO', name: 'DeployCertToTencentCDN',
title: '部署到腾讯云EO', title: '部署到腾讯云CDN',
desc: '腾讯云边缘安全加速平台 EO',
group: pluginGroups.tencent.key, group: pluginGroups.tencent.key,
default: { default: {
strategy: { strategy: {

View File

@ -1,11 +1,11 @@
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline'; import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
import tencentcloud from 'tencentcloud-sdk-nodejs'; import tencentcloud from 'tencentcloud-sdk-nodejs-teo';
import { TencentAccess } from '../../access/index.js'; import { TencentAccess } from '../../access/index.js';
import { CertInfo } from '@certd/plugin-cert';
@IsTaskPlugin({ @IsTaskPlugin({
name: 'DeployCertToTencentCDN', name: 'DeployCertToTencentEO',
title: '部署到腾讯云CDN', title: '部署到腾讯云EO',
desc: '腾讯云边缘安全加速平台EO必须配置上传证书到腾讯云任务',
group: pluginGroups.tencent.key, group: pluginGroups.tencent.key,
default: { default: {
strategy: { strategy: {
@ -13,17 +13,17 @@ import { CertInfo } from '@certd/plugin-cert';
}, },
}, },
}) })
export class DeployToCdnPlugin extends AbstractTaskPlugin { export class DeployToEOPlugin extends AbstractTaskPlugin {
@TaskInput({ @TaskInput({
title: '域名证书', title: '已上传证书ID',
helper: '请选择前置任务输出的域名证书', helper: '请选择前置任务上传到腾讯云的证书',
component: { component: {
name: 'pi-output-selector', name: 'pi-output-selector',
from: 'CertApply', from: 'UploadCertToTencent',
}, },
required: true, required: true,
}) })
cert!: CertInfo; certId!: string;
@TaskInput({ @TaskInput({
title: 'Access提供者', title: 'Access提供者',
@ -36,6 +36,12 @@ export class DeployToCdnPlugin extends AbstractTaskPlugin {
}) })
accessId!: string; accessId!: string;
@TaskInput({
title: '站点ID',
helper: '类似于zone-xxxx的字符串在站点概览页面左上角或者站点列表页面站点名称下方',
})
zoneId!: string;
@TaskInput({ @TaskInput({
title: '证书名称', title: '证书名称',
helper: '证书上传后将以此参数作为名称前缀', helper: '证书上传后将以此参数作为名称前缀',
@ -44,9 +50,16 @@ export class DeployToCdnPlugin extends AbstractTaskPlugin {
@TaskInput({ @TaskInput({
title: 'cdn加速域名', title: 'cdn加速域名',
component: {
name: 'a-select',
vModel: 'value',
mode: 'tags',
open: false,
},
helper: '支持多个域名',
rules: [{ required: true, message: '该项必填' }], rules: [{ required: true, message: '该项必填' }],
}) })
domainName!: string; domainNames!: string[];
// @TaskInput({ // @TaskInput({
// title: "CDN接口", // title: "CDN接口",
@ -69,7 +82,7 @@ export class DeployToCdnPlugin extends AbstractTaskPlugin {
} }
getClient(accessProvider: TencentAccess) { getClient(accessProvider: TencentAccess) {
const CdnClient = tencentcloud.cdn.v20180606.Client; const TeoClient = tencentcloud.teo.v20220901.Client;
const clientConfig = { const clientConfig = {
credential: { credential: {
@ -79,31 +92,31 @@ export class DeployToCdnPlugin extends AbstractTaskPlugin {
region: '', region: '',
profile: { profile: {
httpProfile: { httpProfile: {
endpoint: 'cdn.tencentcloudapi.com', endpoint: 'teo.tencentcloudapi.com',
}, },
}, },
}; };
return new CdnClient(clientConfig); return new TeoClient(clientConfig);
} }
buildParams() { buildParams() {
return { return {
Https: { ZoneId: this.zoneId,
Switch: 'on', Hosts: this.domainNames,
CertInfo: { Mode: 'sslcert',
Certificate: this.cert.crt, ServerCertInfo: [
PrivateKey: this.cert.key, {
CertId: this.certId,
}, },
}, ],
Domain: this.domainName,
}; };
} }
async doRequest(client: any, params: any) { async doRequest(client: any, params: any) {
const ret = await client.UpdateDomainConfig(params); const ret = await client.ModifyHostsCertificate(params);
this.checkRet(ret); this.checkRet(ret);
this.logger.info('设置腾讯云CDN证书成功:', ret.RequestId); this.logger.info('设置腾讯云EO证书成功:', ret.RequestId);
return ret.RequestId; return ret.RequestId;
} }