From 063cdc5ed5f9c7c152bd6574465fde945fd4a0c8 Mon Sep 17 00:00:00 2001 From: Gabriel Dugny Date: Fri, 11 Jul 2025 21:27:19 +0200 Subject: [PATCH] Techdocs: support HumanDuration for cache config Signed-off-by: Gabriel Dugny --- .changeset/flat-colts-know.md | 5 +++ plugins/techdocs-backend/config.d.ts | 10 +++-- plugins/techdocs-backend/package.json | 1 + .../src/cache/TechDocsCache.ts | 19 +++++++-- .../src/service/router.test.ts | 40 +++++++++++-------- .../techdocs-backend/src/service/router.ts | 18 +++++++-- yarn.lock | 1 + 7 files changed, 66 insertions(+), 28 deletions(-) create mode 100644 .changeset/flat-colts-know.md diff --git a/.changeset/flat-colts-know.md b/.changeset/flat-colts-know.md new file mode 100644 index 0000000000..e79ba52ae8 --- /dev/null +++ b/.changeset/flat-colts-know.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs-backend': minor +--- + +Techdocs: support HumanDuration for cache TTL and timeout configuration diff --git a/plugins/techdocs-backend/config.d.ts b/plugins/techdocs-backend/config.d.ts index c220717ddb..4954392828 100644 --- a/plugins/techdocs-backend/config.d.ts +++ b/plugins/techdocs-backend/config.d.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import { HumanDuration } from '@backstage/types'; + export interface Config { /** * Configuration options for the techdocs-backend plugin @@ -277,7 +279,7 @@ export interface Config { */ cache?: { /** - * The cache time-to-live for TechDocs sites (in milliseconds). Set this + * The cache time-to-live for TechDocs sites, in milliseconds for a number or a human duration. Set this * to a non-zero value to cache TechDocs sites and assets as they are * read from storage. * @@ -285,16 +287,16 @@ export interface Config { * and to pass a PluginCacheManager instance to TechDocs Backend's * createRouter method in your backend. */ - ttl: number; + ttl: number | HumanDuration | string; /** - * The time (in milliseconds) that the TechDocs backend will wait for + * The time (in milliseconds for a number or a human duration) that the TechDocs backend will wait for * a cache service to respond before continuing on as though the cached * object was not found (e.g. when the cache sercice is unavailable). * * Defaults to 1000 milliseconds. */ - readTimeout?: number; + readTimeout?: number | HumanDuration | string; }; /** diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index 62e150d519..93fbbdcf6d 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -71,6 +71,7 @@ "@backstage/plugin-search-backend-module-techdocs": "workspace:^", "@backstage/plugin-techdocs-common": "workspace:^", "@backstage/plugin-techdocs-node": "workspace:^", + "@backstage/types": "workspace:^", "express": "^4.17.1", "express-promise-router": "^4.1.0", "fs-extra": "^11.2.0", diff --git a/plugins/techdocs-backend/src/cache/TechDocsCache.ts b/plugins/techdocs-backend/src/cache/TechDocsCache.ts index a5eb4d37be..d5cbdaae36 100644 --- a/plugins/techdocs-backend/src/cache/TechDocsCache.ts +++ b/plugins/techdocs-backend/src/cache/TechDocsCache.ts @@ -14,8 +14,9 @@ * limitations under the License. */ import { assertError, CustomErrorBase } from '@backstage/errors'; -import { Config } from '@backstage/config'; +import { Config, readDurationFromConfig } from '@backstage/config'; import { CacheService, LoggerService } from '@backstage/backend-plugin-api'; +import { durationToMilliseconds } from '@backstage/types'; export class CacheInvalidationError extends CustomErrorBase {} @@ -42,8 +43,20 @@ export class TechDocsCache { config: Config, { cache, logger }: { cache: CacheService; logger: LoggerService }, ) { - const timeout = config.getOptionalNumber('techdocs.cache.readTimeout'); - const readTimeout = timeout === undefined ? 1000 : timeout; + let readTimeout: number; + if (config.has('techdocs.cache.readTimeout')) { + if (typeof config.get('techdocs.cache.readTimeout') === 'number') { + readTimeout = config.getNumber('techdocs.cache.readTimeout'); + } else { + readTimeout = durationToMilliseconds( + readDurationFromConfig(config, { + key: 'techdocs.cache.readTimeout', + }), + ); + } + } else { + readTimeout = 1000; + } return new TechDocsCache({ cache, logger, readTimeout }); } diff --git a/plugins/techdocs-backend/src/service/router.test.ts b/plugins/techdocs-backend/src/service/router.test.ts index 233f679fac..89c1009c3f 100644 --- a/plugins/techdocs-backend/src/service/router.test.ts +++ b/plugins/techdocs-backend/src/service/router.test.ts @@ -30,14 +30,10 @@ import { TechDocsCache } from '../cache'; import { mockErrorHandler, mockServices } from '@backstage/backend-test-utils'; jest.mock('@backstage/catalog-client'); -jest.mock('@backstage/config'); jest.mock('./CachedEntityLoader'); jest.mock('./DocsSynchronizer'); jest.mock('../cache/TechDocsCache'); -const MockedConfigReader = ConfigReader as jest.MockedClass< - typeof ConfigReader ->; const MockDocsSynchronizer = DocsSynchronizer as jest.MockedClass< typeof DocsSynchronizer >; @@ -115,7 +111,13 @@ describe('createRouter', () => { preparers, generators, publisher, - config: new ConfigReader({}), + config: new ConfigReader({ + techdocs: { + cache: { + ttl: 1, + }, + }, + }), logger: mockServices.logger.mock(), discovery, cache: mockServices.cache.mock(), @@ -143,9 +145,6 @@ describe('createRouter', () => { discovery.getBaseUrl.mockImplementation(async type => { return `http://backstage.local/api/${type}`; }); - MockedConfigReader.prototype.getOptionalNumber.mockImplementation(key => - key === 'techdocs.cache.ttl' ? 1 : undefined, - ); MockTechDocsCache.get.mockResolvedValue(undefined); MockTechDocsCache.set.mockResolvedValue(); }); @@ -326,13 +325,17 @@ data: {"updated":true} }); it('should check entity access when permissions are enabled', async () => { - MockedConfigReader.prototype.getOptionalBoolean.mockImplementation(key => - key === 'permission.enabled' ? true : undefined, - ); const docsRouter = jest.fn((_req, res) => res.sendStatus(200)); publisher.docsRouter.mockReturnValue(docsRouter); - const app = await createApp(outOfTheBoxOptions); + const app = await createApp({ + ...outOfTheBoxOptions, + config: new ConfigReader({ + permission: { + enabled: true, + }, + }), + }); MockCachedEntityLoader.prototype.load.mockResolvedValue(entity); @@ -345,11 +348,14 @@ data: {"updated":true} }); it('should not return assets without corresponding entity access', async () => { - MockedConfigReader.prototype.getOptionalBoolean.mockImplementation(key => - key === 'permission.enabled' ? true : undefined, - ); - - const app = await createApp(outOfTheBoxOptions); + const app = await createApp({ + ...outOfTheBoxOptions, + config: new ConfigReader({ + permission: { + enabled: true, + }, + }), + }); MockCachedEntityLoader.prototype.load.mockResolvedValue(undefined); diff --git a/plugins/techdocs-backend/src/service/router.ts b/plugins/techdocs-backend/src/service/router.ts index bfaf072717..475d822b21 100644 --- a/plugins/techdocs-backend/src/service/router.ts +++ b/plugins/techdocs-backend/src/service/router.ts @@ -16,7 +16,7 @@ import { CatalogApi, CatalogClient } from '@backstage/catalog-client'; import { stringifyEntityRef } from '@backstage/catalog-model'; -import { Config } from '@backstage/config'; +import { Config, readDurationFromConfig } from '@backstage/config'; import { NotFoundError } from '@backstage/errors'; import { DocsBuildStrategy, @@ -41,6 +41,7 @@ import { HttpAuthService, LoggerService, } from '@backstage/backend-plugin-api'; +import { durationToMilliseconds } from '@backstage/types'; /** * Required dependencies for running TechDocs in the "out-of-the-box" @@ -130,9 +131,18 @@ export async function createRouter( // Set up a cache client if configured. let cache: TechDocsCache | undefined; - const defaultTtl = config.getOptionalNumber('techdocs.cache.ttl'); - if (defaultTtl) { - const cacheClient = options.cache.withOptions({ defaultTtl }); + if (config.has('techdocs.cache.ttl')) { + let ttlMs: number; + if (typeof config.get('techdocs.cache.ttl') === 'number') { + ttlMs = config.getNumber('techdocs.cache.ttl'); + } else { + ttlMs = durationToMilliseconds( + readDurationFromConfig(config, { + key: 'techdocs.cache.ttl', + }), + ); + } + const cacheClient = options.cache.withOptions({ defaultTtl: ttlMs }); cache = TechDocsCache.fromConfig(config, { cache: cacheClient, logger }); } diff --git a/yarn.lock b/yarn.lock index 53b3bfcacf..5ccb5ad0ea 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8720,6 +8720,7 @@ __metadata: "@backstage/plugin-search-backend-module-techdocs": "workspace:^" "@backstage/plugin-techdocs-common": "workspace:^" "@backstage/plugin-techdocs-node": "workspace:^" + "@backstage/types": "workspace:^" "@types/express": "npm:^4.17.6" express: "npm:^4.17.1" express-promise-router: "npm:^4.1.0"