Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/many-baboons-lick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@axis-backstage/plugin-readme-backend': patch
---

Added config to set the cache ttl.
14 changes: 14 additions & 0 deletions plugins/readme-backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,20 @@ readme:
- README.markdown
```

To override the default TTL of the internal cache a duration can be set using the `cacheTtl` key. This can be either a string
or an object. The following examples should be equal.

```yaml
readme:
cacheTtl:
hours: 2
```

```yaml
readme:
cacheTtl: '2h'
```

### Troubleshooting

If the backend fails to provide README content for an entity, it could be due to several reasons.
Expand Down
6 changes: 6 additions & 0 deletions plugins/readme-backend/config.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { HumanDuration } from '@backstage/types';

export interface Config {
/**
* Configuration options for the Readme backend plugin
Expand All @@ -8,5 +10,9 @@ export interface Config {
* when looking for a README file and which order to use.
*/
fileNames?: string[];
/**
* Optional cache TTL, defaults to 1 hour.
*/
cacheTtl?: HumanDuration | string;
};
}
3 changes: 2 additions & 1 deletion plugins/readme-backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,17 @@
"@backstage/backend-plugin-api": "^1.2.0",
"@backstage/catalog-client": "^1.9.1",
"@backstage/catalog-model": "^1.7.3",
"@backstage/config": "^1.3.2",
"@backstage/errors": "^1.2.7",
"@backstage/integration": "^1.16.1",
"@backstage/types": "^1.2.1",
"@types/express": "*",
"express": "^4.17.1",
"express-promise-router": "^4.1.0"
},
"devDependencies": {
"@backstage/backend-test-utils": "^1.3.0",
"@backstage/cli": "^0.30.0",
"@backstage/config": "^1.3.2",
"@types/supertest": "^2.0.12",
"msw": "^2.7.3",
"supertest": "^6.2.4"
Expand Down
101 changes: 101 additions & 0 deletions plugins/readme-backend/src/service/config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { mockServices } from '@backstage/backend-test-utils';

import { getCacheTtl } from './config';

describe('getCacheTtl', () => {
it('should return default TTL when cache.ttl is not configured', () => {
const mockConfig = mockServices.rootConfig({
data: {},
});

const result = getCacheTtl(mockConfig);

expect(result).toBe(3_600_000); // 1 hour in milliseconds
});

it('should return default TTL when readme config section exists but no ttl key', () => {
const mockConfig = mockServices.rootConfig({
data: {
readme: {},
},
});

const result = getCacheTtl(mockConfig);

expect(result).toBe(3_600_000); // 1 hour in milliseconds
});

it('should return configured TTL in seconds', () => {
const mockConfig = mockServices.rootConfig({
data: {
readme: {
cacheTtl: { seconds: 1800 }, // 30 minutes
},
},
});

const result = getCacheTtl(mockConfig);

expect(result).toBe(1_800_000); // 30 minutes in milliseconds
});

it('should return configured TTL in minutes', () => {
const mockConfig = mockServices.rootConfig({
data: {
readme: {
cacheTtl: { minutes: 10 },
},
},
});

const result = getCacheTtl(mockConfig);

expect(result).toBe(600_000); // 10 minutes in milliseconds
});

it('should return configured TTL in hours', () => {
const mockConfig = mockServices.rootConfig({
data: {
readme: {
cacheTtl: { hours: 2 },
},
},
});

const result = getCacheTtl(mockConfig);

expect(result).toBe(7_200_000); // 2 hours in milliseconds
});

it('should return configured TTL as a string value (interpreted as milliseconds)', () => {
const mockConfig = mockServices.rootConfig({
data: {
readme: {
cacheTtl: '4w',
},
},
});

const result = getCacheTtl(mockConfig);

expect(result).toBe(2419200000); // 4 weeks in milliseconds
});

it('should handle complex duration configuration', () => {
const mockConfig = mockServices.rootConfig({
data: {
readme: {
cacheTtl: {
hours: 1,
minutes: 30,
seconds: 45,
},
},
},
});

const result = getCacheTtl(mockConfig);

expect(result).toBe(5_445_000); // 1h 30m 45s in milliseconds
});
});
11 changes: 11 additions & 0 deletions plugins/readme-backend/src/service/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Config, readDurationFromConfig } from '@backstage/config';
import { durationToMilliseconds } from '@backstage/types';

const CACHE_CONFIG_KEY = 'readme.cacheTtl';

export const getCacheTtl = (config: Config): number =>
config.has(CACHE_CONFIG_KEY)
? durationToMilliseconds(
readDurationFromConfig(config, { key: CACHE_CONFIG_KEY }),
)
: 3_600_000;
14 changes: 9 additions & 5 deletions plugins/readme-backend/src/service/constants.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ConfigReader } from '@backstage/config';
import { mockServices } from '@backstage/backend-test-utils';
import { NOT_FOUND_PLACEHOLDER, getReadmeTypes } from './constants';

describe('constants', () => {
Expand All @@ -11,7 +11,9 @@ describe('constants', () => {

describe('getReadmeTypes', () => {
it('should return default readme types when config is empty', () => {
const config = new ConfigReader({});
const config = mockServices.rootConfig({
data: {},
});
const readmeTypes = getReadmeTypes(config);
expect(readmeTypes).toEqual([
{ name: 'README.md', type: 'text/markdown' },
Expand All @@ -23,9 +25,11 @@ describe('constants', () => {
});

it('should return custom readme types from config', () => {
const config = new ConfigReader({
readme: {
fileNames: ['CUSTOM.md', 'CUSTOM.txt'],
const config = mockServices.rootConfig({
data: {
readme: {
fileNames: ['CUSTOM.md', 'CUSTOM.txt'],
},
},
});
const readmeTypes = getReadmeTypes(config);
Expand Down
32 changes: 21 additions & 11 deletions plugins/readme-backend/src/service/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { isError, NotFoundError } from '@backstage/errors';
import express from 'express';
import Router from 'express-promise-router';
import { isSymLink } from '../lib';
import { getCacheTtl } from './config';
import { NOT_FOUND_PLACEHOLDER, getReadmeTypes } from './constants';
import { ReadmeFile } from './types';

Expand All @@ -42,8 +43,9 @@ export async function createRouter(
): Promise<express.Router> {
const { auth, logger, config, reader, discovery, cache } = options;
const catalogClient = new CatalogClient({ discoveryApi: discovery });
const cacheTtl = getCacheTtl(config);

logger.info('Initializing readme backend');
logger.info(`Initializing readme backend. Cache TTL: ${cacheTtl}ms`);
const integrations = ScmIntegrations.fromConfig(config);
const router = Router();
router.use(express.json());
Expand Down Expand Up @@ -121,11 +123,15 @@ export async function createRouter(
content = (await symLinkUrlResponse.buffer()).toString('utf-8');
}

cache.set(entityRef, {
name: fileType.name,
type: fileType.type,
content: content,
});
cache.set(
entityRef,
{
name: fileType.name,
type: fileType.type,
content: content,
},
{ ttl: cacheTtl },
);
logger.info(
`Found README for ${entityRef}: ${url} type ${fileType.type}`,
);
Expand All @@ -143,11 +149,15 @@ export async function createRouter(
}
}
logger.info(`Readme not found for ${entityRef}`);
cache.set(entityRef, {
name: NOT_FOUND_PLACEHOLDER,
type: '',
content: '',
});
cache.set(
entityRef,
{
name: NOT_FOUND_PLACEHOLDER,
type: '',
content: '',
},
{ ttl: cacheTtl },
);
throw new NotFoundError('Readme could not be found');
});

Expand Down
1 change: 1 addition & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1551,6 +1551,7 @@ __metadata:
"@backstage/config": "npm:^1.3.2"
"@backstage/errors": "npm:^1.2.7"
"@backstage/integration": "npm:^1.16.1"
"@backstage/types": "npm:^1.2.1"
"@types/express": "npm:*"
"@types/supertest": "npm:^2.0.12"
express: "npm:^4.17.1"
Expand Down