From 10640277b0fa9df4afda6de0010259c6c8ee00b7 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Thu, 31 Oct 2024 21:22:53 +0700 Subject: [PATCH 1/3] fix(dashmate): cleanup zerossl certs command --- packages/dashmate/src/commands/ssl/cleanup.js | 58 +++++++++++ packages/dashmate/src/createDIContainer.js | 5 + .../cleanupZeroSSLCertificatesTaskFactory.js | 98 +++++++++++++++++++ .../src/ssl/zerossl/cancelCertificate.js | 2 +- .../src/ssl/zerossl/listCertificates.js | 5 +- 5 files changed, 165 insertions(+), 3 deletions(-) create mode 100644 packages/dashmate/src/commands/ssl/cleanup.js create mode 100644 packages/dashmate/src/listr/tasks/ssl/zerossl/cleanupZeroSSLCertificatesTaskFactory.js diff --git a/packages/dashmate/src/commands/ssl/cleanup.js b/packages/dashmate/src/commands/ssl/cleanup.js new file mode 100644 index 00000000000..b7f108aa08a --- /dev/null +++ b/packages/dashmate/src/commands/ssl/cleanup.js @@ -0,0 +1,58 @@ +import { Listr } from 'listr2'; +import { Flags } from '@oclif/core'; +import ConfigBaseCommand from '../../oclif/command/ConfigBaseCommand.js'; +import MuteOneLineError from '../../oclif/errors/MuteOneLineError.js'; + +export default class CleanupCommand extends ConfigBaseCommand { + static description = `Cleanup Zero SSL certificate + +Cancel all drafted or pending validation certificates on ZeroSSL +`; + + static flags = { + ...ConfigBaseCommand.flags, + verbose: Flags.boolean({ char: 'v', description: 'use verbose mode for output', default: false }), + }; + + /** + * @param {Object} args + * @param {Object} flags + * @param {boolean} flags.verbose + * @param {Config} config + * @param {cleanupZeroSSLCertificatesTask} cleanupZeroSSLCertificatesTask + * @return {Promise} + */ + async runWithDependencies( + args, + { + verbose: isVerbose, + }, + config, + cleanupZeroSSLCertificatesTask, + ) { + const tasks = new Listr( + [ + { + title: 'Cleanup ZeroSSL certificate', + task: async () => cleanupZeroSSLCertificatesTask(config), + }, + ], + { + renderer: isVerbose ? 'verbose' : 'default', + rendererOptions: { + showTimer: isVerbose, + clearOutput: false, + collapse: false, + showSubtasks: true, + removeEmptyLines: false, + }, + }, + ); + + try { + await tasks.run(); + } catch (e) { + throw new MuteOneLineError(e); + } + } +} diff --git a/packages/dashmate/src/createDIContainer.js b/packages/dashmate/src/createDIContainer.js index 9f3b8616235..3b2e657d2b9 100644 --- a/packages/dashmate/src/createDIContainer.js +++ b/packages/dashmate/src/createDIContainer.js @@ -23,6 +23,9 @@ import analyseSystemResourcesFactory from './doctor/analyse/analyseSystemResourc import analyseSamplesFactory from './doctor/analyseSamplesFactory.js'; import archiveSamples from './doctor/archiveSamples.js'; import unarchiveSamplesFactory from './doctor/unarchiveSamplesFactory.js'; +import cleanupZeroSSLCertificatesTaskFactory + from './listr/tasks/ssl/zerossl/cleanupZeroSSLCertificatesTaskFactory.js'; +import cancelCertificate from './ssl/zerossl/cancelCertificate.js'; import renderTemplateFactory from './templates/renderTemplateFactory.js'; import renderServiceTemplatesFactory from './templates/renderServiceTemplatesFactory.js'; @@ -206,6 +209,7 @@ export default async function createDIContainer(options = {}) { downloadCertificate: asValue(downloadCertificate), getCertificate: asValue(getCertificate), listCertificates: asValue(listCertificates), + cancelCertificate: asValue(cancelCertificate), createSelfSignedCertificate: asValue(createSelfSignedCertificate), verificationServer: asClass(VerificationServer).singleton(), }); @@ -299,6 +303,7 @@ export default async function createDIContainer(options = {}) { enableCoreQuorumsTask: asFunction(enableCoreQuorumsTaskFactory).singleton(), registerMasternodeGuideTask: asFunction(registerMasternodeGuideTaskFactory).singleton(), obtainZeroSSLCertificateTask: asFunction(obtainZeroSSLCertificateTaskFactory).singleton(), + cleanupZeroSSLCertificatesTask: asFunction(cleanupZeroSSLCertificatesTaskFactory).singleton(), obtainSelfSignedCertificateTask: asFunction(obtainSelfSignedCertificateTaskFactory).singleton(), saveCertificateTask: asFunction(saveCertificateTaskFactory), reindexNodeTask: asFunction(reindexNodeTaskFactory).singleton(), diff --git a/packages/dashmate/src/listr/tasks/ssl/zerossl/cleanupZeroSSLCertificatesTaskFactory.js b/packages/dashmate/src/listr/tasks/ssl/zerossl/cleanupZeroSSLCertificatesTaskFactory.js new file mode 100644 index 00000000000..c0d4979cde5 --- /dev/null +++ b/packages/dashmate/src/listr/tasks/ssl/zerossl/cleanupZeroSSLCertificatesTaskFactory.js @@ -0,0 +1,98 @@ +import chalk from 'chalk'; +import { Listr } from 'listr2'; +import { Observable } from 'rxjs'; +import wait from '../../../../util/wait.js'; + +/** + * @param {listCertificates} listCertificates + * @param {cancelCertificate} cancelCertificate + * @return {cleanupZeroSSLCertificatesTask} + */ +export default function cleanupZeroSSLCertificatesTaskFactory( + listCertificates, + cancelCertificate, +) { + /** + * @typedef {obtainZeroSSLCertificateTask} + * @param {Config} config + * @return {Promise} + */ + async function cleanupZeroSSLCertificatesTask(config) { + return new Listr([ + { + title: 'Collect drafted and pending validation certificates', + // Skips the check if force flag is set + task: async (ctx, task) => { + ctx.apiKey = config.get('platform.gateway.ssl.providerConfigs.zerossl.apiKey', true); + + ctx.certificates = []; + + let certificatesPerRequest = []; + let page = 1; + + // Fetch all certificates in draft or pending validation status + // with pagination + do { + certificatesPerRequest = await listCertificates(ctx.apiKey, ['draft', 'pending_validation'], page); + + ctx.certificates = ctx.certificates.concat(certificatesPerRequest); + + page += 1; + + // eslint-disable-next-line no-param-reassign + task.output = `Found ${ctx.certificates.length} certificates`; + } while (certificatesPerRequest.length === 1000); + + ctx.total = ctx.certificates.length; + }, + }, + { + title: 'Cancel certificates', + skip: (ctx) => ctx.certificates.length === 0, + task: async (ctx, task) => { + // eslint-disable-next-line no-param-reassign + task.title = `Cancel ${ctx.certificates.length} certificates`; + ctx.canceled = 0; + ctx.errored = 0; + return new Observable(async (observer) => { + for (const certificate of ctx.certificates) { + try { + await cancelCertificate(ctx.apiKey, certificate.id); + + ctx.canceled += 1; + } catch (e) { + ctx.errored += 1; + + if (process.env.DEBUG) { + // eslint-disable-next-line no-console + console.warn(e); + } + } + + observer.next(chalk`{green ${ctx.canceled}} / {red ${ctx.errored}} / ${ctx.total}`); + + await wait(100); + } + + if (ctx.errored > 0) { + observer.error(new Error('Some certificates were not canceled. Please try again.')); + } else { + observer.complete(); + } + + return this; + }); + }, + options: { + persistentOutput: true, + }, + }, + ], { + rendererOptions: { + showErrorMessage: true, + }, + }); + } + + return cleanupZeroSSLCertificatesTask; +} diff --git a/packages/dashmate/src/ssl/zerossl/cancelCertificate.js b/packages/dashmate/src/ssl/zerossl/cancelCertificate.js index 5a4e1316740..2d8f9dfa9b7 100644 --- a/packages/dashmate/src/ssl/zerossl/cancelCertificate.js +++ b/packages/dashmate/src/ssl/zerossl/cancelCertificate.js @@ -1,4 +1,4 @@ -import requestApi from './requestApi'; +import requestApi from './requestApi.js'; /** * Get ZeroSSL certificate diff --git a/packages/dashmate/src/ssl/zerossl/listCertificates.js b/packages/dashmate/src/ssl/zerossl/listCertificates.js index 63dad29283b..80d385f0233 100644 --- a/packages/dashmate/src/ssl/zerossl/listCertificates.js +++ b/packages/dashmate/src/ssl/zerossl/listCertificates.js @@ -8,12 +8,13 @@ import Certificate from './Certificate.js'; * @param {string} apiKey * @param {String[]} [statuses] - possible values: draft, pending_validation, issued, cancelled, * revoked, expired. + * @param {number} [page] * @param {string} [search] * @return {Promise} */ -export default async function listCertificates(apiKey, statuses = [], search = undefined) { - let url = `https://api.zerossl.com/certificates?access_key=${apiKey}&limit=1000`; +export default async function listCertificates(apiKey, statuses = [], page = 1, search = undefined) { + let url = `https://api.zerossl.com/certificates?access_key=${apiKey}&limit=1000&page=${page}`; if (statuses.length > 0) { url += `&statuses=${statuses.join(',')}`; From 098f8b09101f59d5e65b600ce66fc2b82cf31d0c Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Thu, 31 Oct 2024 21:50:16 +0700 Subject: [PATCH 2/3] style: long line --- packages/dashmate/src/ssl/zerossl/listCertificates.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/dashmate/src/ssl/zerossl/listCertificates.js b/packages/dashmate/src/ssl/zerossl/listCertificates.js index 80d385f0233..f7530cee4c9 100644 --- a/packages/dashmate/src/ssl/zerossl/listCertificates.js +++ b/packages/dashmate/src/ssl/zerossl/listCertificates.js @@ -13,7 +13,12 @@ import Certificate from './Certificate.js'; * @return {Promise} */ -export default async function listCertificates(apiKey, statuses = [], page = 1, search = undefined) { +export default async function listCertificates( + apiKey, + statuses = [], + page = 1, + search = undefined, +) { let url = `https://api.zerossl.com/certificates?access_key=${apiKey}&limit=1000&page=${page}`; if (statuses.length > 0) { From 057ec02987ddcd8829aa1781de7e01cae9222481 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Thu, 31 Oct 2024 22:41:27 +0700 Subject: [PATCH 3/3] refactor: remove unnecessary async --- packages/dashmate/src/commands/ssl/cleanup.js | 2 +- packages/dashmate/src/commands/ssl/obtain.js | 2 +- .../scheduleRenewZeroSslCertificateFactory.js | 2 +- .../cleanupZeroSSLCertificatesTaskFactory.js | 14 +++++++------- .../zerossl/obtainZeroSSLCertificateTaskFactory.js | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/dashmate/src/commands/ssl/cleanup.js b/packages/dashmate/src/commands/ssl/cleanup.js index b7f108aa08a..eaef76cf49f 100644 --- a/packages/dashmate/src/commands/ssl/cleanup.js +++ b/packages/dashmate/src/commands/ssl/cleanup.js @@ -34,7 +34,7 @@ Cancel all drafted or pending validation certificates on ZeroSSL [ { title: 'Cleanup ZeroSSL certificate', - task: async () => cleanupZeroSSLCertificatesTask(config), + task: () => cleanupZeroSSLCertificatesTask(config), }, ], { diff --git a/packages/dashmate/src/commands/ssl/obtain.js b/packages/dashmate/src/commands/ssl/obtain.js index cc39f1a47a9..4dbfcd35612 100644 --- a/packages/dashmate/src/commands/ssl/obtain.js +++ b/packages/dashmate/src/commands/ssl/obtain.js @@ -45,7 +45,7 @@ Certificate will be renewed if it is about to expire (see 'expiration-days' flag [ { title: 'Obtain ZeroSSL certificate', - task: async () => obtainZeroSSLCertificateTask(config), + task: () => obtainZeroSSLCertificateTask(config), }, ], { diff --git a/packages/dashmate/src/helper/scheduleRenewZeroSslCertificateFactory.js b/packages/dashmate/src/helper/scheduleRenewZeroSslCertificateFactory.js index c403c2bef39..8d741511dd8 100644 --- a/packages/dashmate/src/helper/scheduleRenewZeroSslCertificateFactory.js +++ b/packages/dashmate/src/helper/scheduleRenewZeroSslCertificateFactory.js @@ -51,7 +51,7 @@ export default function scheduleRenewZeroSslCertificateFactory( } const job = new CronJob(expiresAt, async () => { - const tasks = await obtainZeroSSLCertificateTask(config); + const tasks = obtainZeroSSLCertificateTask(config); await tasks.run({ expirationDays: Certificate.EXPIRATION_LIMIT_DAYS, diff --git a/packages/dashmate/src/listr/tasks/ssl/zerossl/cleanupZeroSSLCertificatesTaskFactory.js b/packages/dashmate/src/listr/tasks/ssl/zerossl/cleanupZeroSSLCertificatesTaskFactory.js index c0d4979cde5..f38d62562da 100644 --- a/packages/dashmate/src/listr/tasks/ssl/zerossl/cleanupZeroSSLCertificatesTaskFactory.js +++ b/packages/dashmate/src/listr/tasks/ssl/zerossl/cleanupZeroSSLCertificatesTaskFactory.js @@ -13,18 +13,18 @@ export default function cleanupZeroSSLCertificatesTaskFactory( cancelCertificate, ) { /** - * @typedef {obtainZeroSSLCertificateTask} + * @typedef {cleanupZeroSSLCertificatesTask} * @param {Config} config - * @return {Promise} + * @return {Listr} */ - async function cleanupZeroSSLCertificatesTask(config) { + function cleanupZeroSSLCertificatesTask(config) { + const apiKey = config.get('platform.gateway.ssl.providerConfigs.zerossl.apiKey', true); + return new Listr([ { title: 'Collect drafted and pending validation certificates', // Skips the check if force flag is set task: async (ctx, task) => { - ctx.apiKey = config.get('platform.gateway.ssl.providerConfigs.zerossl.apiKey', true); - ctx.certificates = []; let certificatesPerRequest = []; @@ -33,7 +33,7 @@ export default function cleanupZeroSSLCertificatesTaskFactory( // Fetch all certificates in draft or pending validation status // with pagination do { - certificatesPerRequest = await listCertificates(ctx.apiKey, ['draft', 'pending_validation'], page); + certificatesPerRequest = await listCertificates(apiKey, ['draft', 'pending_validation'], page); ctx.certificates = ctx.certificates.concat(certificatesPerRequest); @@ -57,7 +57,7 @@ export default function cleanupZeroSSLCertificatesTaskFactory( return new Observable(async (observer) => { for (const certificate of ctx.certificates) { try { - await cancelCertificate(ctx.apiKey, certificate.id); + await cancelCertificate(apiKey, certificate.id); ctx.canceled += 1; } catch (e) { diff --git a/packages/dashmate/src/listr/tasks/ssl/zerossl/obtainZeroSSLCertificateTaskFactory.js b/packages/dashmate/src/listr/tasks/ssl/zerossl/obtainZeroSSLCertificateTaskFactory.js index ca679233d03..95eb4b5e13a 100644 --- a/packages/dashmate/src/listr/tasks/ssl/zerossl/obtainZeroSSLCertificateTaskFactory.js +++ b/packages/dashmate/src/listr/tasks/ssl/zerossl/obtainZeroSSLCertificateTaskFactory.js @@ -36,9 +36,9 @@ export default function obtainZeroSSLCertificateTaskFactory( /** * @typedef {obtainZeroSSLCertificateTask} * @param {Config} config - * @return {Promise} + * @return {Listr} */ - async function obtainZeroSSLCertificateTask(config) { + function obtainZeroSSLCertificateTask(config) { return new Listr([ { title: 'Check if certificate already exists and not expiring soon',