diff --git a/packages/core/acme-client/src/auto.js b/packages/core/acme-client/src/auto.js index e780d30c9..ed59fba1b 100644 --- a/packages/core/acme-client/src/auto.js +++ b/packages/core/acme-client/src/auto.js @@ -144,7 +144,6 @@ module.exports = async (client, userOpts) => { log(`[auto] [${d}] challenge verification threw error: ${e.message}`); } } - /* Complete challenge and wait for valid status */ log(`[auto] [${d}] Completing challenge with ACME provider and waiting for valid status`); await client.completeChallenge(challenge); @@ -236,6 +235,9 @@ module.exports = async (client, userOpts) => { for (const challengePromises of allChallengePromises) { i += 1; log(`开始第${i}组`); + if (opts.signal && opts.signal.aborted) { + throw new Error('用户取消'); + } // eslint-disable-next-line no-await-in-loop await runPromisePa(challengePromises); } diff --git a/packages/core/acme-client/src/client.js b/packages/core/acme-client/src/client.js index 055696b31..f3cf2a70a 100644 --- a/packages/core/acme-client/src/client.js +++ b/packages/core/acme-client/src/client.js @@ -490,6 +490,9 @@ class AcmeClient { const keyAuthorization = await this.getChallengeKeyAuthorization(challenge); const verifyFn = async () => { + if (this.opts.signal && this.opts.signal.aborted) { + throw new Error('用户取消'); + } await verify[challenge.type](authz, challenge, keyAuthorization); }; @@ -513,6 +516,9 @@ class AcmeClient { */ async completeChallenge(challenge) { + if (this.opts.signal && this.opts.signal.aborted) { + throw new Error('用户取消'); + } const resp = await this.api.completeChallenge(challenge.url, {}); return resp.data; } @@ -550,6 +556,10 @@ class AcmeClient { } 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]); /* Verify status */ diff --git a/packages/core/acme-client/types/index.d.ts b/packages/core/acme-client/types/index.d.ts index 0969d8855..406b546a5 100644 --- a/packages/core/acme-client/types/index.d.ts +++ b/packages/core/acme-client/types/index.d.ts @@ -45,6 +45,7 @@ export interface ClientOptions { backoffMin?: number; backoffMax?: number; urlMapping?: UrlMapping; + signal?: AbortSignal; } export interface ClientExternalAccountBindingOptions { @@ -61,6 +62,7 @@ export interface ClientAutoOptions { skipChallengeVerification?: boolean; challengePriority?: string[]; preferredChain?: string; + signal?: AbortSignal; } export class Client { diff --git a/packages/core/pipeline/src/core/executor.ts b/packages/core/pipeline/src/core/executor.ts index 79b4b7d0e..bae5bd019 100644 --- a/packages/core/pipeline/src/core/executor.ts +++ b/packages/core/pipeline/src/core/executor.ts @@ -23,6 +23,7 @@ export type ExecutorOptions = { emailService: IEmailService; fileRootDir?: string; }; + export class Executor { pipeline: Pipeline; runtime!: RunHistory; @@ -32,7 +33,8 @@ export class Executor { lastStatusMap!: RunnableCollection; lastRuntime!: RunHistory; options: ExecutorOptions; - canceled = false; + abort: AbortController = new AbortController(); + onChanged: (history: RunHistory) => Promise; constructor(options: ExecutorOptions) { this.options = options; @@ -53,7 +55,7 @@ export class Executor { } async cancel() { - this.canceled = true; + this.abort.abort(); this.runtime?.cancel(this.pipeline); await this.onChanged(this.runtime); } @@ -102,6 +104,8 @@ export class Executor { } } if (lastResult != null && lastResult === ResultType.success && !inputChanged) { + runnable.status!.output = lastNode?.status?.output; + runnable.status!.files = lastNode?.status?.files; this.runtime.skip(runnable); await this.onChanged(this.runtime); return ResultType.skip; @@ -113,10 +117,15 @@ export class Executor { // const timeout = runnable.timeout ?? 20 * 60 * 1000; try { - if (this.canceled) { + if (this.abort.signal.aborted) { + this.runtime.cancel(runnable); return ResultType.canceled; } await run(); + if (this.abort.signal.aborted) { + this.runtime.cancel(runnable); + return ResultType.canceled; + } this.runtime.success(runnable); return ResultType.success; } catch (e: any) { @@ -211,16 +220,11 @@ export class Executor { if (item.component?.name === "pi-output-selector") { const contextKey = step.input[key]; if (contextKey != null) { - const value = this.runtime.context[contextKey]; - if (value == null) { - currentLogger.warn(`[step init] input ${define.title} is null,前置任务步骤输出值为空,请按如下步骤排查:`); - currentLogger.log(`1、检查前置任务(证书申请任务)是否是配置了成功后跳过,如果是,请改为正常执行`); - currentLogger.log( - `2、是否曾经删除过前置任务(证书申请任务),然后又重新添加了,如果是,请重新编辑当前任务,重新选择一下前置任务输出的参数(域名证书那一栏)` - ); - currentLogger.log(`3、以上都不是,联系作者吧`); - } - step.input[key] = value; + // "cert": "step.-BNFVPMKPu2O-i9NiOQxP.cert", + const arr = contextKey.split("."); + const id = arr[1]; + const outputKey = arr[2]; + step.input[key] = this.lastStatusMap.get(id)?.status?.output[outputKey]; } } }); @@ -241,6 +245,7 @@ export class Executor { parent: this.runtime.id, rootDir: this.options.fileRootDir, }), + signal: this.abort.signal, }; instance.setCtx(taskCtx); @@ -254,8 +259,8 @@ export class Executor { //输出上下文变量到output context _.forEach(define.output, (item: any, key: any) => { step.status!.output[key] = instance[key]; - const stepOutputKey = `step.${step.id}.${key}`; - this.runtime.context[stepOutputKey] = instance[key]; + // const stepOutputKey = `step.${step.id}.${key}`; + // this.runtime.context[stepOutputKey] = instance[key]; }); step.status!.files = instance.getFiles(); diff --git a/packages/core/pipeline/src/core/run-history.ts b/packages/core/pipeline/src/core/run-history.ts index bfdfc6076..ad27c5c3b 100644 --- a/packages/core/pipeline/src/core/run-history.ts +++ b/packages/core/pipeline/src/core/run-history.ts @@ -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 { buildLogger } from "../utils/util.log.js"; import { Logger } from "log4js"; @@ -14,15 +14,12 @@ export type RunTrigger = { export function NewRunHistory(obj: any) { const history = new RunHistory(obj.id, obj.trigger, obj.pipeline); - history.context = obj.context; history.logs = obj.logs; history._loggers = obj.loggers; return history; } export class RunHistory { id!: string; - //运行时上下文变量 - context: Context = {}; pipeline!: Pipeline; logs: { [runnableId: string]: string[]; diff --git a/packages/core/pipeline/src/plugin/api.ts b/packages/core/pipeline/src/plugin/api.ts index 4da1c0d4f..edc5b3b29 100644 --- a/packages/core/pipeline/src/plugin/api.ts +++ b/packages/core/pipeline/src/plugin/api.ts @@ -64,6 +64,7 @@ export type TaskInstanceContext = { http: AxiosInstance; fileStore: FileStore; lastStatus?: Runnable; + signal: AbortSignal; }; export abstract class AbstractTaskPlugin implements ITaskPlugin { 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 f191b8a92..8ff096673 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts @@ -23,6 +23,7 @@ type AcmeServiceOptions = { skipLocalVerify?: boolean; useMappingProxy?: boolean; privateKeyType?: PrivateKeyType; + signal?: AbortSignal; }; export class AcmeService { @@ -99,6 +100,7 @@ export class AcmeService { backoffMin: 5000, backoffMax: 10000, urlMapping, + signal: this.options.signal, }); if (conf.accountUrl == null) { @@ -253,6 +255,7 @@ export class AcmeService { challengeRemoveFn: async (authz: acme.Authorization, challenge: Challenge, keyAuthorization: string, recordItem: any): Promise => { return await this.challengeRemoveFn(authz, challenge, keyAuthorization, recordItem, dnsProvider); }, + signal: this.options.signal, }); const cert: CertInfo = { diff --git a/packages/ui/certd-server/package.json b/packages/ui/certd-server/package.json index 1648fe658..40185f979 100644 --- a/packages/ui/certd-server/package.json +++ b/packages/ui/certd-server/package.json @@ -64,6 +64,7 @@ "svg-captcha": "^1.4.0", "tencentcloud-sdk-nodejs": "^4.0.44", "tencentcloud-sdk-nodejs-dnspod": "^4.0.866", + "tencentcloud-sdk-nodejs-teo": "^4.0.919", "typeorm": "^0.3.11" }, "devDependencies": { diff --git a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-cdn/index.ts b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-cdn/index.ts index 252b2dc32..851d493e3 100644 --- a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-cdn/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-cdn/index.ts @@ -4,9 +4,8 @@ import { TencentAccess } from '../../access/index.js'; import { CertInfo } from '@certd/plugin-cert'; @IsTaskPlugin({ - name: 'DeployCertToTencentEO', - title: '部署到腾讯云EO', - desc: '腾讯云边缘安全加速平台 EO', + name: 'DeployCertToTencentCDN', + title: '部署到腾讯云CDN', group: pluginGroups.tencent.key, default: { strategy: { diff --git a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-eocdn/index.ts b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-eo/index.ts similarity index 61% rename from packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-eocdn/index.ts rename to packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-eo/index.ts index 851d493e3..0f1b27af1 100644 --- a/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-eocdn/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-tencent/plugin/deploy-to-eo/index.ts @@ -1,11 +1,11 @@ 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 { CertInfo } from '@certd/plugin-cert'; @IsTaskPlugin({ - name: 'DeployCertToTencentCDN', - title: '部署到腾讯云CDN', + name: 'DeployCertToTencentEO', + title: '部署到腾讯云EO', + desc: '腾讯云边缘安全加速平台EO,必须配置上传证书到腾讯云任务', group: pluginGroups.tencent.key, default: { strategy: { @@ -13,17 +13,17 @@ import { CertInfo } from '@certd/plugin-cert'; }, }, }) -export class DeployToCdnPlugin extends AbstractTaskPlugin { +export class DeployToEOPlugin extends AbstractTaskPlugin { @TaskInput({ - title: '域名证书', - helper: '请选择前置任务输出的域名证书', + title: '已上传证书ID', + helper: '请选择前置任务上传到腾讯云的证书', component: { name: 'pi-output-selector', - from: 'CertApply', + from: 'UploadCertToTencent', }, required: true, }) - cert!: CertInfo; + certId!: string; @TaskInput({ title: 'Access提供者', @@ -36,6 +36,12 @@ export class DeployToCdnPlugin extends AbstractTaskPlugin { }) accessId!: string; + @TaskInput({ + title: '站点ID', + helper: '类似于zone-xxxx的字符串,在站点概览页面左上角,或者,站点列表页面站点名称下方', + }) + zoneId!: string; + @TaskInput({ title: '证书名称', helper: '证书上传后将以此参数作为名称前缀', @@ -44,9 +50,16 @@ export class DeployToCdnPlugin extends AbstractTaskPlugin { @TaskInput({ title: 'cdn加速域名', + component: { + name: 'a-select', + vModel: 'value', + mode: 'tags', + open: false, + }, + helper: '支持多个域名', rules: [{ required: true, message: '该项必填' }], }) - domainName!: string; + domainNames!: string[]; // @TaskInput({ // title: "CDN接口", @@ -69,7 +82,7 @@ export class DeployToCdnPlugin extends AbstractTaskPlugin { } getClient(accessProvider: TencentAccess) { - const CdnClient = tencentcloud.cdn.v20180606.Client; + const TeoClient = tencentcloud.teo.v20220901.Client; const clientConfig = { credential: { @@ -79,31 +92,31 @@ export class DeployToCdnPlugin extends AbstractTaskPlugin { region: '', profile: { httpProfile: { - endpoint: 'cdn.tencentcloudapi.com', + endpoint: 'teo.tencentcloudapi.com', }, }, }; - return new CdnClient(clientConfig); + return new TeoClient(clientConfig); } buildParams() { return { - Https: { - Switch: 'on', - CertInfo: { - Certificate: this.cert.crt, - PrivateKey: this.cert.key, + ZoneId: this.zoneId, + Hosts: this.domainNames, + Mode: 'sslcert', + ServerCertInfo: [ + { + CertId: this.certId, }, - }, - Domain: this.domainName, + ], }; } async doRequest(client: any, params: any) { - const ret = await client.UpdateDomainConfig(params); + const ret = await client.ModifyHostsCertificate(params); this.checkRet(ret); - this.logger.info('设置腾讯云CDN证书成功:', ret.RequestId); + this.logger.info('设置腾讯云EO证书成功:', ret.RequestId); return ret.RequestId; }