From 5857fdd6abb2e2dd4bc3eb7ae8c51693cbfde387 Mon Sep 17 00:00:00 2001 From: Derrick Mehaffy Date: Mon, 22 Jul 2024 09:45:26 -0700 Subject: [PATCH 1/5] init v5 changes --- .npmrc | 2 +- package.json | 9 ++++++--- server/register.js | 29 +++++++++++++++-------------- server/services/connection.js | 4 ++-- yarn.lock | 21 ++++++++++++++------- 5 files changed, 38 insertions(+), 27 deletions(-) diff --git a/.npmrc b/.npmrc index cffe8cd..43c97e7 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1 @@ -save-exact=true +package-lock=false diff --git a/package.json b/package.json index cba4563..db86ddb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "strapi-plugin-redis", - "version": "1.1.0", + "version": "2.0.0", "description": "Plugin used to centralize management of Redis connections in Strapi", "strapi": { "displayName": "Redis", @@ -11,8 +11,11 @@ }, "dependencies": { "chalk": "4.1.2", - "debug": "4.3.4", - "ioredis": "5.2.4" + "debug": "4.3.5", + "ioredis": "5.4.1" + }, + "peerDependencies": { + "@strapi/strapi": "^5.0.0-rc.4" }, "scripts": {}, "author": { diff --git a/server/register.js b/server/register.js index e00dd74..ed3b633 100644 --- a/server/register.js +++ b/server/register.js @@ -1,6 +1,6 @@ -'use strict' +'use strict'; -const debug = require('debug') +const debug = require('debug'); module.exports = async ({ strapi }) => { // Load plugin Config @@ -8,12 +8,12 @@ module.exports = async ({ strapi }) => { // Configure plugin debug if (coreConfig.settings.debug === true) { - debug.enable('strapi:strapi-plugin-redis') + debug.enable('strapi:strapi-plugin-redis'); } // Allow plugin + ioredis debug if (coreConfig.settings.debug === true && coreConfig.settings.debugIORedis === true) { - debug.enable('strapi:strapi-plugin-redis,ioredis:*') + debug.enable('strapi:strapi-plugin-redis,ioredis:*'); } // Construct Redis API @@ -26,14 +26,15 @@ module.exports = async ({ strapi }) => { await strapi.plugin('redis').service('connection').buildAll(coreConfig); // Construct Admin Permissions - const actions = [ - { - section: 'settings', - category: 'redis', - displayName: 'Access the Redis Overview page', - uid: 'settings.read', - pluginName: 'redis', - }, - ]; - await strapi.admin.services.permission.actionProvider.registerMany(actions); + // Commenting this since it's not needed for now, there is no admin panel for this plugin + // const actions = [ + // { + // section: 'settings', + // category: 'redis', + // displayName: 'Access the Redis Overview page', + // uid: 'settings.read', + // pluginName: 'redis', + // }, + // ]; + // await strapi.admin.services.permission.actionProvider.registerMany(actions); }; diff --git a/server/services/connection.js b/server/services/connection.js index 1ec416b..728e3ca 100644 --- a/server/services/connection.js +++ b/server/services/connection.js @@ -24,7 +24,7 @@ module.exports = ({ strapi }) => ({ debug(`${chalk.red('Failed to build')} ${name} connection - ${chalk.blue('cluster')}`); } - // Check for sentinel config + // Check for sentinel config } else if (nameConfig.connection.sentinels) { delete nameConfig.connection.host; delete nameConfig.connection.port; @@ -37,7 +37,7 @@ module.exports = ({ strapi }) => ({ debug(`${chalk.red('Failed to build')} ${name} connection - ${chalk.yellow('sentinel')}`); } - // Check for regular single connection + // Check for regular single connection } else { try { strapi.redis.connections[name] = { diff --git a/yarn.lock b/yarn.lock index f696346..c9bda95 100644 --- a/yarn.lock +++ b/yarn.lock @@ -39,14 +39,21 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -debug@4.3.4, debug@^4.3.4: +debug@4.3.5: + version "4.3.5" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" + integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== + dependencies: + ms "2.1.2" + +debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" -denque@^2.0.1: +denque@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== @@ -56,15 +63,15 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -ioredis@5.2.4: - version "5.2.4" - resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-5.2.4.tgz#9e262a668bc29bae98f2054c1e0d7efd86996b96" - integrity sha512-qIpuAEt32lZJQ0XyrloCRdlEdUUNGG9i0UOk6zgzK6igyudNWqEBxfH6OlbnOOoBBvr1WB02mm8fR55CnikRng== +ioredis@5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-5.4.1.tgz#1c56b70b759f01465913887375ed809134296f40" + integrity sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA== dependencies: "@ioredis/commands" "^1.1.1" cluster-key-slot "^1.1.0" debug "^4.3.4" - denque "^2.0.1" + denque "^2.1.0" lodash.defaults "^4.2.0" lodash.isarguments "^3.1.0" redis-errors "^1.2.0" From c284b1d4d529811ea9fb0e9f333e3f3ddf08d345 Mon Sep 17 00:00:00 2001 From: Derrick Mehaffy Date: Mon, 22 Jul 2024 10:09:28 -0700 Subject: [PATCH 2/5] migrate redcron/redlock code from v4 plugin --- package.json | 6 +++- server/config/index.js | 9 ++++++ server/register.js | 72 +++++++++++++++++++++++++++++++++++------- yarn.lock | 12 +++++++ 4 files changed, 86 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index db86ddb..35dceb7 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "dependencies": { "chalk": "4.1.2", "debug": "4.3.5", - "ioredis": "5.4.1" + "ioredis": "5.4.1", + "redlock": "5.0.0-beta.2" }, "peerDependencies": { "@strapi/strapi": "^5.0.0-rc.4" @@ -33,6 +34,9 @@ "email": "derrickmehaffy@gmail.com", "url": "https://github.com/derrickmehaffy", "lead": true + }, + { + "name": "Excl Networks Inc." } ], "bugs": { diff --git a/server/config/index.js b/server/config/index.js index 071e218..c76932c 100644 --- a/server/config/index.js +++ b/server/config/index.js @@ -5,6 +5,15 @@ module.exports = { settings: { debug: false, debugIORedis: false, + redlockConfig: { + driftFactor: 0.01, + retryCount: 10, + retryDelay: 200, + retryJitter: 200, + }, + enableRedlock: false, + lockDelay: null, + lockTTL: 5000, }, connections: { default: { diff --git a/server/register.js b/server/register.js index ed3b633..34c0a03 100644 --- a/server/register.js +++ b/server/register.js @@ -1,6 +1,7 @@ 'use strict'; const debug = require('debug'); +const { default: Redlock } = require('redlock'); module.exports = async ({ strapi }) => { // Load plugin Config @@ -25,16 +26,63 @@ module.exports = async ({ strapi }) => { // Build Redis database connections await strapi.plugin('redis').service('connection').buildAll(coreConfig); - // Construct Admin Permissions - // Commenting this since it's not needed for now, there is no admin panel for this plugin - // const actions = [ - // { - // section: 'settings', - // category: 'redis', - // displayName: 'Access the Redis Overview page', - // uid: 'settings.read', - // pluginName: 'redis', - // }, - // ]; - // await strapi.admin.services.permission.actionProvider.registerMany(actions); + // Configure Redlock + if (coreConfig.settings.enableRedlock === true) { + const originalAdd = strapi.cron.add; + const redlockConfig = coreConfig.settings.redlockConfig; + + strapi.cron.add = (tasks) => { + const generateRedlockFunction = (originalFunction, name) => { + return async (...args) => { + const connections = Object.keys(strapi.redis.connections).map((key) => { + return strapi.redis.connections[key].client; + }); + const redlock = new Redlock(connections, redlockConfig); + + let lock; + try { + lock = await redlock.acquire([name], coreConfig.settings.lockTTL); + debug(`Job ${name} acquired lock`); + await originalFunction(...args); + } catch (e) { + debug(`Job ${name} failed to acquire lock`); + } finally { + // wait some time so other processes will lose the lock + let lockDelay = coreConfig.settings.lockDelay + ? coreConfig.settings.lockDelay + : coreConfig.settings.redlockConfig.retryCount * + (coreConfig.settings.redlockConfig.retryDelay + + coreConfig.settings.redlockConfig.retryJitter); + debug(`Job ${name} waiting ${lockDelay}ms before releasing lock`); + await new Promise((resolve) => setTimeout(resolve, lockDelay)); + if (lock) { + debug(`Job ${name} releasing lock`); + try { + await lock.release(); + } catch (e) { + debug(`Job ${name} failed to release lock ${e}`); + } + } + } + }; + }; + Object.keys(tasks).forEach((key) => { + const taskValue = tasks[key]; + if (typeof taskValue === 'function') { + strapi.log.info('redlock requires tasks to use the object format'); + return; + } else if ( + typeof taskValue === 'object' && + taskValue && + typeof taskValue.task === 'function' && + taskValue.bypassRedlock !== true + ) { + // fallback to key if no name is provided + const taskName = taskValue.name || key; + taskValue.task = generateRedlockFunction(taskValue.task, 'redlock:' + taskName); + } + }); + originalAdd(tasks); + }; + } }; diff --git a/yarn.lock b/yarn.lock index c9bda95..a994484 100644 --- a/yarn.lock +++ b/yarn.lock @@ -93,6 +93,11 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +node-abort-controller@^3.0.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz#a94377e964a9a37ac3976d848cb5c765833b8548" + integrity sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ== + redis-errors@^1.0.0, redis-errors@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" @@ -105,6 +110,13 @@ redis-parser@^3.0.0: dependencies: redis-errors "^1.0.0" +redlock@5.0.0-beta.2: + version "5.0.0-beta.2" + resolved "https://registry.yarnpkg.com/redlock/-/redlock-5.0.0-beta.2.tgz#a629c07e07d001c0fdd9f2efa614144c4416fe44" + integrity sha512-2RDWXg5jgRptDrB1w9O/JgSZC0j7y4SlaXnor93H/UJm/QyDiFgBKNtrh0TI6oCXqYSaSoXxFh6Sd3VtYfhRXw== + dependencies: + node-abort-controller "^3.0.1" + standard-as-callback@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45" From 6c7f1c349ae78447e1669b7c02b78b29b19bba0f Mon Sep 17 00:00:00 2001 From: Derrick Mehaffy Date: Mon, 22 Jul 2024 10:56:20 -0700 Subject: [PATCH 3/5] fix dep warning in 5 --- server/register.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/register.js b/server/register.js index 34c0a03..d4f56ac 100644 --- a/server/register.js +++ b/server/register.js @@ -5,7 +5,7 @@ const { default: Redlock } = require('redlock'); module.exports = async ({ strapi }) => { // Load plugin Config - const coreConfig = strapi.config.get('plugin.redis'); + const coreConfig = strapi.config.get('plugin::redis'); // Configure plugin debug if (coreConfig.settings.debug === true) { From 101fe599f47c7303b89f3a9ca4c16762a0e98730 Mon Sep 17 00:00:00 2001 From: Derrick Mehaffy Date: Fri, 20 Sep 2024 20:31:05 -0700 Subject: [PATCH 4/5] Getting ready for Strapi 5 Stable --- README.md | 49 ++++++++++++++++++------------------------------- package.json | 2 +- 2 files changed, 19 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index e534129..c19aef1 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,6 @@ Monthly download on NPM - - Vulnerabilities on Snyk -

@@ -40,7 +37,7 @@ ## 🚦 Current Status -This package is currently under development and should be consider **ALPHA** in terms of state. I/We are currently accepting contributions and/or dedicated contributors to help develop and maintain this package. +This package is currently maintained and should be considered **Stable/GA** in terms of state. I/We are currently accepting contributions and/or dedicated contributors to help develop and maintain this package. For more information on contributing please see [the contrib message below](#contributing). @@ -48,13 +45,14 @@ For more information on contributing please see [the contrib message below](#con This package's lead maintainer is an employee of Strapi however this package is not officially maintained by Strapi Solutions SAS nor Strapi, Inc. and is currently maintained in the free time of the lead maintainer. -**Absolutely no part of this code should be considered covered under any agreement you have with Strapi proper** including but not limited to any Enterprise Agreement you have with Strapi. +**Absolutely no part of this code should be considered covered under any agreement you have with Strapi proper** including but not limited to any Enterprise and/or Cloud Agreement you have with Strapi. ## ✨ Features -This plugin utilizes 1 core package: +This plugin utilizes 2 core packages: -- [ioredis](https://github.com/luin/ioredis) - for all connection management +- [ioredis](https://github.com/luin/ioredis) - for all connection management to any Redis or Redis-compatible database +- [redlock](https://github.com/mike-marcacci/node-redlock) - for distributed locks related to Strapi's built in cron-tasks system These are the primary features that are finished or currently being worked on: @@ -62,6 +60,7 @@ These are the primary features that are finished or currently being worked on: - [x] Redis Replica + Sentinel Support - [ ] Redis Sharding Support (assumed working, no config samples) - [x] Multiple connections/databases +- [x] Redlock capabilities with Strapi's built-in cron tasks ## 🤔 Motivation @@ -72,42 +71,28 @@ A few examples of where Redis could be used within a Strapi application: - LRU-based response cache for REST - Apollo server GraphQL cache - IP Rate-limiting using something like [koa2-ratelimit](https://www.npmjs.com/package/koa2-ratelimit) -- Distributed Redis locks for Strapi clusters (useful for clustered usage of cron tasks) +- Server-side user session storage - So much more If you are currently using this package in your plugin and would like to be featured, please feel free to submit an issue to have your plugin added to the list below: - [strapi-plugin-rest-cache](https://www.npmjs.com/package/strapi-plugin-rest-cache) - via: [strapi-provider-rest-cache-redis](https://www.npmjs.com/package/strapi-provider-rest-cache-redis) -- [strapi-plugin-redcron](https://www.npmjs.com/package/strapi-plugin-redcron) - More plugins coming soon! +Note the following packages used to use this package with Strapi v4 but have since been merged into this package: + +- [strapi-plugin-redcron](https://www.npmjs.com/package/strapi-plugin-redcron) + ## 🖐 Requirements Supported Strapi Versions: -| Strapi Version | Supported | Tested On | -| -------------- | --------- | ------------- | -| v3 | ❌ | N/A | -| v4.0.x | ✅ | July 2022 | -| v4.1.x | ✅ | July 2022 | -| v4.2.x | ✅ | July 2022 | -| v4.3.x | ✅ | December 2022 | -| v4.4.x | ✅ | December 2022 | -| v4.5.x | ✅ | December 2022 | -| v4.6.x | ✅ | January 2024 | -| v4.7.x | ✅ | January 2024 | -| v4.8.x | ✅ | January 2024 | -| v4.9.x | ✅ | January 2024 | -| v4.10.x | ✅ | January 2024 | -| v4.11.x | ✅ | January 2024 | -| v4.12.x | ✅ | January 2024 | -| v4.13.x | ✅ | January 2024 | -| v4.14.x | ✅ | January 2024 | -| v4.15.x | ✅ | January 2024 | -| v4.16.x | ✅ | January 2024 | -| v4.17.x | ✅ | January 2024 | -| v4.19.x | ✅ | January 2024 | +| Strapi Version | Plugin Version | Supported | Tested On | +|----------------|----------------|-----------|-----------| +| v3.x.x | N/A | ❌ | N/A | +| v4.x.x | 1.1.0 | ✅ | Sept 2024 | +| v5.x.x | 2.0.0 | ✅ | Sept 2024 | **This plugin will not work with Strapi v3 projects as it utilizes APIs that don't exist in the v3!** @@ -115,6 +100,8 @@ Supported Strapi Versions: Install the plugin in your Strapi project or your Strapi plugin. +[!WARNING] For Strapi 4 projects you should use the `1.x.x` version of this plugin, for Strapi 5 projects you should use the `2.x.x` version of this plugin. + ```bash # Using Yarn (Recommended) yarn add strapi-plugin-redis diff --git a/package.json b/package.json index 35dceb7..ca29cbd 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "redlock": "5.0.0-beta.2" }, "peerDependencies": { - "@strapi/strapi": "^5.0.0-rc.4" + "@strapi/strapi": "^5.0.0" }, "scripts": {}, "author": { From 0593789b8eb12d84eac4bd7d79740888e8ffadea Mon Sep 17 00:00:00 2001 From: Derrick Mehaffy Date: Fri, 20 Sep 2024 20:32:46 -0700 Subject: [PATCH 5/5] testing warning block --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c19aef1..9e98c10 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,8 @@ Supported Strapi Versions: Install the plugin in your Strapi project or your Strapi plugin. -[!WARNING] For Strapi 4 projects you should use the `1.x.x` version of this plugin, for Strapi 5 projects you should use the `2.x.x` version of this plugin. +**Warning** +For Strapi 4 projects you should use the `1.x.x` version of this plugin, for Strapi 5 projects you should use the `2.x.x` version of this plugin. ```bash # Using Yarn (Recommended)