@backstage/plugin-techdocs-backend 2.0.5-next.0 → 2.0.5-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @backstage/plugin-techdocs-backend
|
|
2
2
|
|
|
3
|
+
## 2.0.5-next.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 484e500: Updated CachedEntityLoader to use BackstageCredentials instead of raw tokens for cache key generation. It now uses principal-based identification (user entity ref for users, subject for services) instead of token-based keys, providing more consistent caching behavior.
|
|
8
|
+
- Updated dependencies
|
|
9
|
+
- @backstage/backend-defaults@0.11.2-next.0
|
|
10
|
+
- @backstage/backend-plugin-api@1.4.2-next.0
|
|
11
|
+
- @backstage/catalog-client@1.11.0-next.0
|
|
12
|
+
- @backstage/catalog-model@1.7.5
|
|
13
|
+
- @backstage/config@1.3.3
|
|
14
|
+
- @backstage/errors@1.2.7
|
|
15
|
+
- @backstage/integration@1.17.1
|
|
16
|
+
- @backstage/plugin-catalog-common@1.1.5
|
|
17
|
+
- @backstage/plugin-catalog-node@1.18.0-next.0
|
|
18
|
+
- @backstage/plugin-permission-common@0.9.1
|
|
19
|
+
- @backstage/plugin-search-backend-module-techdocs@0.4.5-next.0
|
|
20
|
+
- @backstage/plugin-techdocs-common@0.1.1
|
|
21
|
+
- @backstage/plugin-techdocs-node@1.13.6-next.0
|
|
22
|
+
|
|
3
23
|
## 2.0.5-next.0
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
|
@@ -3,15 +3,17 @@
|
|
|
3
3
|
var catalogModel = require('@backstage/catalog-model');
|
|
4
4
|
|
|
5
5
|
class CachedEntityLoader {
|
|
6
|
+
auth;
|
|
6
7
|
catalog;
|
|
7
8
|
cache;
|
|
8
9
|
readTimeout = 1e3;
|
|
9
|
-
constructor({ catalog, cache }) {
|
|
10
|
+
constructor({ auth, catalog, cache }) {
|
|
11
|
+
this.auth = auth;
|
|
10
12
|
this.catalog = catalog;
|
|
11
13
|
this.cache = cache;
|
|
12
14
|
}
|
|
13
|
-
async load(entityRef, token) {
|
|
14
|
-
const cacheKey = this.getCacheKey(entityRef,
|
|
15
|
+
async load(credentials, entityRef, token) {
|
|
16
|
+
const cacheKey = this.getCacheKey(entityRef, credentials);
|
|
15
17
|
let result = await this.getFromCache(cacheKey);
|
|
16
18
|
if (result) {
|
|
17
19
|
return result;
|
|
@@ -28,10 +30,12 @@ class CachedEntityLoader {
|
|
|
28
30
|
new Promise((cancelAfter) => setTimeout(cancelAfter, this.readTimeout))
|
|
29
31
|
]);
|
|
30
32
|
}
|
|
31
|
-
getCacheKey(entityName,
|
|
33
|
+
getCacheKey(entityName, credentials) {
|
|
32
34
|
const key = ["catalog", catalogModel.stringifyEntityRef(entityName)];
|
|
33
|
-
if (
|
|
34
|
-
key.push(
|
|
35
|
+
if (this.auth.isPrincipal(credentials, "user")) {
|
|
36
|
+
key.push(credentials.principal.userEntityRef);
|
|
37
|
+
} else if (this.auth.isPrincipal(credentials, "service")) {
|
|
38
|
+
key.push(credentials.principal.subject);
|
|
35
39
|
}
|
|
36
40
|
return key.join(":");
|
|
37
41
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CachedEntityLoader.cjs.js","sources":["../../src/service/CachedEntityLoader.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {
|
|
1
|
+
{"version":3,"file":"CachedEntityLoader.cjs.js","sources":["../../src/service/CachedEntityLoader.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AuthService,\n BackstageCredentials,\n CacheService,\n} from '@backstage/backend-plugin-api';\nimport { CatalogApi } from '@backstage/catalog-client';\nimport {\n Entity,\n CompoundEntityRef,\n stringifyEntityRef,\n} from '@backstage/catalog-model';\n\nexport type CachedEntityLoaderOptions = {\n auth: AuthService;\n catalog: CatalogApi;\n cache: CacheService;\n};\n\nexport class CachedEntityLoader {\n private readonly auth: AuthService;\n private readonly catalog: CatalogApi;\n private readonly cache: CacheService;\n private readonly readTimeout = 1000;\n\n constructor({ auth, catalog, cache }: CachedEntityLoaderOptions) {\n this.auth = auth;\n this.catalog = catalog;\n this.cache = cache;\n }\n\n async load(\n credentials: BackstageCredentials,\n entityRef: CompoundEntityRef,\n token: string | undefined,\n ): Promise<Entity | undefined> {\n const cacheKey = this.getCacheKey(entityRef, credentials);\n let result = await this.getFromCache(cacheKey);\n\n if (result) {\n return result;\n }\n\n result = await this.catalog.getEntityByRef(entityRef, { token });\n\n if (result) {\n this.cache.set(cacheKey, result, { ttl: 5000 });\n }\n\n return result;\n }\n\n private async getFromCache(key: string): Promise<Entity | undefined> {\n // Promise.race ensures we don't hang the client for long if the cache is\n // temporarily unreachable.\n return (await Promise.race([\n this.cache.get(key),\n new Promise(cancelAfter => setTimeout(cancelAfter, this.readTimeout)),\n ])) as Entity | undefined;\n }\n\n private getCacheKey(\n entityName: CompoundEntityRef,\n credentials: BackstageCredentials,\n ): string {\n const key = ['catalog', stringifyEntityRef(entityName)];\n\n if (this.auth.isPrincipal(credentials, 'user')) {\n key.push(credentials.principal.userEntityRef);\n } else if (this.auth.isPrincipal(credentials, 'service')) {\n key.push(credentials.principal.subject);\n }\n\n return key.join(':');\n }\n}\n"],"names":["stringifyEntityRef"],"mappings":";;;;AAkCO,MAAM,kBAAmB,CAAA;AAAA,EACb,IAAA;AAAA,EACA,OAAA;AAAA,EACA,KAAA;AAAA,EACA,WAAc,GAAA,GAAA;AAAA,EAE/B,WAAY,CAAA,EAAE,IAAM,EAAA,OAAA,EAAS,OAAoC,EAAA;AAC/D,IAAA,IAAA,CAAK,IAAO,GAAA,IAAA;AACZ,IAAA,IAAA,CAAK,OAAU,GAAA,OAAA;AACf,IAAA,IAAA,CAAK,KAAQ,GAAA,KAAA;AAAA;AACf,EAEA,MAAM,IAAA,CACJ,WACA,EAAA,SAAA,EACA,KAC6B,EAAA;AAC7B,IAAA,MAAM,QAAW,GAAA,IAAA,CAAK,WAAY,CAAA,SAAA,EAAW,WAAW,CAAA;AACxD,IAAA,IAAI,MAAS,GAAA,MAAM,IAAK,CAAA,YAAA,CAAa,QAAQ,CAAA;AAE7C,IAAA,IAAI,MAAQ,EAAA;AACV,MAAO,OAAA,MAAA;AAAA;AAGT,IAAA,MAAA,GAAS,MAAM,IAAK,CAAA,OAAA,CAAQ,eAAe,SAAW,EAAA,EAAE,OAAO,CAAA;AAE/D,IAAA,IAAI,MAAQ,EAAA;AACV,MAAA,IAAA,CAAK,MAAM,GAAI,CAAA,QAAA,EAAU,QAAQ,EAAE,GAAA,EAAK,KAAM,CAAA;AAAA;AAGhD,IAAO,OAAA,MAAA;AAAA;AACT,EAEA,MAAc,aAAa,GAA0C,EAAA;AAGnE,IAAQ,OAAA,MAAM,QAAQ,IAAK,CAAA;AAAA,MACzB,IAAA,CAAK,KAAM,CAAA,GAAA,CAAI,GAAG,CAAA;AAAA,MAClB,IAAI,OAAQ,CAAA,CAAA,WAAA,KAAe,WAAW,WAAa,EAAA,IAAA,CAAK,WAAW,CAAC;AAAA,KACrE,CAAA;AAAA;AACH,EAEQ,WAAA,CACN,YACA,WACQ,EAAA;AACR,IAAA,MAAM,GAAM,GAAA,CAAC,SAAW,EAAAA,+BAAA,CAAmB,UAAU,CAAC,CAAA;AAEtD,IAAA,IAAI,IAAK,CAAA,IAAA,CAAK,WAAY,CAAA,WAAA,EAAa,MAAM,CAAG,EAAA;AAC9C,MAAI,GAAA,CAAA,IAAA,CAAK,WAAY,CAAA,SAAA,CAAU,aAAa,CAAA;AAAA,eACnC,IAAK,CAAA,IAAA,CAAK,WAAY,CAAA,WAAA,EAAa,SAAS,CAAG,EAAA;AACxD,MAAI,GAAA,CAAA,IAAA,CAAK,WAAY,CAAA,SAAA,CAAU,OAAO,CAAA;AAAA;AAGxC,IAAO,OAAA,GAAA,CAAI,KAAK,GAAG,CAAA;AAAA;AAEvB;;;;"}
|
|
@@ -26,6 +26,7 @@ async function createRouter(options) {
|
|
|
26
26
|
const docsBuildStrategy = options.docsBuildStrategy ?? DefaultDocsBuildStrategy.DefaultDocsBuildStrategy.fromConfig(config);
|
|
27
27
|
const buildLogTransport = options.buildLogTransport;
|
|
28
28
|
const entityLoader = new CachedEntityLoader.CachedEntityLoader({
|
|
29
|
+
auth,
|
|
29
30
|
catalog: catalogClient$1,
|
|
30
31
|
cache: options.cache
|
|
31
32
|
});
|
|
@@ -52,7 +53,7 @@ async function createRouter(options) {
|
|
|
52
53
|
onBehalfOf: credentials,
|
|
53
54
|
targetPluginId: "catalog"
|
|
54
55
|
});
|
|
55
|
-
const entity = await entityLoader.load(entityName, token);
|
|
56
|
+
const entity = await entityLoader.load(credentials, entityName, token);
|
|
56
57
|
if (!entity) {
|
|
57
58
|
throw new errors.NotFoundError(
|
|
58
59
|
`Unable to get metadata for '${catalogModel.stringifyEntityRef(entityName)}'`
|
|
@@ -83,7 +84,7 @@ async function createRouter(options) {
|
|
|
83
84
|
onBehalfOf: credentials,
|
|
84
85
|
targetPluginId: "catalog"
|
|
85
86
|
});
|
|
86
|
-
const entity = await entityLoader.load(entityName, token);
|
|
87
|
+
const entity = await entityLoader.load(credentials, entityName, token);
|
|
87
88
|
if (!entity) {
|
|
88
89
|
throw new errors.NotFoundError(
|
|
89
90
|
`Unable to get metadata for '${catalogModel.stringifyEntityRef(entityName)}'`
|
|
@@ -111,7 +112,11 @@ async function createRouter(options) {
|
|
|
111
112
|
onBehalfOf: credentials,
|
|
112
113
|
targetPluginId: "catalog"
|
|
113
114
|
});
|
|
114
|
-
const entity = await entityLoader.load(
|
|
115
|
+
const entity = await entityLoader.load(
|
|
116
|
+
credentials,
|
|
117
|
+
{ kind, namespace, name },
|
|
118
|
+
token
|
|
119
|
+
);
|
|
115
120
|
if (!entity?.metadata?.uid) {
|
|
116
121
|
throw new errors.NotFoundError("Entity metadata UID missing");
|
|
117
122
|
}
|
|
@@ -163,7 +168,7 @@ async function createRouter(options) {
|
|
|
163
168
|
onBehalfOf: credentials,
|
|
164
169
|
targetPluginId: "catalog"
|
|
165
170
|
});
|
|
166
|
-
const entity = await entityLoader.load(entityName, token);
|
|
171
|
+
const entity = await entityLoader.load(credentials, entityName, token);
|
|
167
172
|
if (!entity) {
|
|
168
173
|
throw new errors.NotFoundError(
|
|
169
174
|
`Entity not found for ${catalogModel.stringifyEntityRef(entityName)}`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.cjs.js","sources":["../../src/service/router.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { CatalogApi, CatalogClient } from '@backstage/catalog-client';\nimport { stringifyEntityRef } from '@backstage/catalog-model';\nimport { Config } from '@backstage/config';\nimport { NotFoundError } from '@backstage/errors';\nimport {\n DocsBuildStrategy,\n GeneratorBuilder,\n getLocationForEntity,\n PreparerBuilder,\n PublisherBase,\n} from '@backstage/plugin-techdocs-node';\nimport express, { Response } from 'express';\nimport Router from 'express-promise-router';\nimport { Knex } from 'knex';\nimport { ScmIntegrations } from '@backstage/integration';\nimport { DocsSynchronizer, DocsSynchronizerSyncOpts } from './DocsSynchronizer';\nimport { createCacheMiddleware, TechDocsCache } from '../cache';\nimport { CachedEntityLoader } from './CachedEntityLoader';\nimport { DefaultDocsBuildStrategy } from './DefaultDocsBuildStrategy';\nimport * as winston from 'winston';\nimport {\n AuthService,\n CacheService,\n DiscoveryService,\n HttpAuthService,\n LoggerService,\n} from '@backstage/backend-plugin-api';\n\n/**\n * Required dependencies for running TechDocs in the \"out-of-the-box\"\n * deployment configuration (prepare/generate/publish all in the Backend).\n *\n * @internal\n */\nexport type OutOfTheBoxDeploymentOptions = {\n preparers: PreparerBuilder;\n generators: GeneratorBuilder;\n publisher: PublisherBase;\n logger: LoggerService;\n discovery: DiscoveryService;\n database?: Knex; // TODO: Make database required when we're implementing database stuff.\n config: Config;\n cache: CacheService;\n docsBuildStrategy?: DocsBuildStrategy;\n buildLogTransport?: winston.transport;\n catalogClient?: CatalogApi;\n httpAuth: HttpAuthService;\n auth: AuthService;\n};\n\n/**\n * Required dependencies for running TechDocs in the \"recommended\" deployment\n * configuration (prepare/generate handled externally in CI/CD).\n *\n * @internal\n */\nexport type RecommendedDeploymentOptions = {\n publisher: PublisherBase;\n logger: LoggerService;\n discovery: DiscoveryService;\n config: Config;\n cache: CacheService;\n docsBuildStrategy?: DocsBuildStrategy;\n buildLogTransport?: winston.transport;\n catalogClient?: CatalogApi;\n httpAuth: HttpAuthService;\n auth: AuthService;\n};\n\n/**\n * One of the two deployment configurations must be provided.\n *\n * @internal\n */\nexport type RouterOptions =\n | RecommendedDeploymentOptions\n | OutOfTheBoxDeploymentOptions;\n\n/**\n * Typeguard to help createRouter() understand when we are in a \"recommended\"\n * deployment vs. when we are in an out-of-the-box deployment configuration.\n *\n * @internal\n */\nfunction isOutOfTheBoxOption(\n opt: RouterOptions,\n): opt is OutOfTheBoxDeploymentOptions {\n return (opt as OutOfTheBoxDeploymentOptions).preparers !== undefined;\n}\n\n/**\n * Creates a techdocs router.\n *\n * @internal\n */\nexport async function createRouter(\n options: RouterOptions,\n): Promise<express.Router> {\n const router = Router();\n const { publisher, config, logger, discovery, httpAuth, auth } = options;\n\n const catalogClient =\n options.catalogClient ?? new CatalogClient({ discoveryApi: discovery });\n const docsBuildStrategy =\n options.docsBuildStrategy ?? DefaultDocsBuildStrategy.fromConfig(config);\n const buildLogTransport = options.buildLogTransport;\n\n // Entities are cached to optimize the /static/docs request path, which can be called many times\n // when loading a single techdocs page.\n const entityLoader = new CachedEntityLoader({\n catalog: catalogClient,\n cache: options.cache,\n });\n\n // Set up a cache client if configured.\n let cache: TechDocsCache | undefined;\n const defaultTtl = config.getOptionalNumber('techdocs.cache.ttl');\n if (defaultTtl) {\n const cacheClient = options.cache.withOptions({ defaultTtl });\n cache = TechDocsCache.fromConfig(config, { cache: cacheClient, logger });\n }\n\n const scmIntegrations = ScmIntegrations.fromConfig(config);\n const docsSynchronizer = new DocsSynchronizer({\n publisher,\n logger,\n buildLogTransport,\n config,\n scmIntegrations,\n cache,\n });\n\n router.get('/metadata/techdocs/:namespace/:kind/:name', async (req, res) => {\n const { kind, namespace, name } = req.params;\n const entityName = { kind, namespace, name };\n\n const credentials = await httpAuth.credentials(req);\n\n const { token } = await auth.getPluginRequestToken({\n onBehalfOf: credentials,\n targetPluginId: 'catalog',\n });\n\n // Verify that the related entity exists and the current user has permission to view it.\n const entity = await entityLoader.load(entityName, token);\n\n if (!entity) {\n throw new NotFoundError(\n `Unable to get metadata for '${stringifyEntityRef(entityName)}'`,\n );\n }\n\n try {\n const techdocsMetadata = await publisher.fetchTechDocsMetadata(\n entityName,\n );\n\n res.json(techdocsMetadata);\n } catch (err) {\n logger.info(\n `Unable to get metadata for '${stringifyEntityRef(\n entityName,\n )}' with error ${err}`,\n );\n throw new NotFoundError(\n `Unable to get metadata for '${stringifyEntityRef(entityName)}'`,\n err,\n );\n }\n });\n\n router.get('/metadata/entity/:namespace/:kind/:name', async (req, res) => {\n const { kind, namespace, name } = req.params;\n const entityName = { kind, namespace, name };\n\n const credentials = await httpAuth.credentials(req);\n\n const { token } = await auth.getPluginRequestToken({\n onBehalfOf: credentials,\n targetPluginId: 'catalog',\n });\n\n const entity = await entityLoader.load(entityName, token);\n\n if (!entity) {\n throw new NotFoundError(\n `Unable to get metadata for '${stringifyEntityRef(entityName)}'`,\n );\n }\n\n try {\n const locationMetadata = getLocationForEntity(entity, scmIntegrations);\n res.json({ ...entity, locationMetadata });\n } catch (err) {\n logger.info(\n `Unable to get metadata for '${stringifyEntityRef(\n entityName,\n )}' with error ${err}`,\n );\n throw new NotFoundError(\n `Unable to get metadata for '${stringifyEntityRef(entityName)}'`,\n err,\n );\n }\n });\n\n // Check if docs are the latest version and trigger rebuilds if not\n // Responds with an event-stream that closes after the build finished\n // Responds with an immediate success if rebuild not needed\n // If a build is required, responds with a success when finished\n router.get('/sync/:namespace/:kind/:name', async (req, res) => {\n const { kind, namespace, name } = req.params;\n\n const credentials = await httpAuth.credentials(req);\n\n const { token } = await auth.getPluginRequestToken({\n onBehalfOf: credentials,\n targetPluginId: 'catalog',\n });\n\n const entity = await entityLoader.load({ kind, namespace, name }, token);\n\n if (!entity?.metadata?.uid) {\n throw new NotFoundError('Entity metadata UID missing');\n }\n\n const responseHandler: DocsSynchronizerSyncOpts = createEventStream(res);\n\n // By default, techdocs-backend will only try to build documentation for an entity if techdocs.builder is set to\n // 'local'. If set to 'external', it will assume that an external process (e.g. CI/CD pipeline\n // of the repository) is responsible for building and publishing documentation to the storage provider.\n // Altering the implementation of the injected docsBuildStrategy allows for more complex behaviours, based on\n // either config or the properties of the entity (e.g. annotations, labels, spec fields etc.).\n const shouldBuild = await docsBuildStrategy.shouldBuild({ entity });\n if (!shouldBuild) {\n // However, if caching is enabled, take the opportunity to check and\n // invalidate stale cache entries.\n if (cache) {\n const { token: techDocsToken } = await auth.getPluginRequestToken({\n onBehalfOf: await auth.getOwnServiceCredentials(),\n targetPluginId: 'techdocs',\n });\n await docsSynchronizer.doCacheSync({\n responseHandler,\n discovery,\n token: techDocsToken,\n entity,\n });\n return;\n }\n responseHandler.finish({ updated: false });\n return;\n }\n\n // Set the synchronization and build process if \"out-of-the-box\" configuration is provided.\n if (isOutOfTheBoxOption(options)) {\n const { preparers, generators } = options;\n\n await docsSynchronizer.doSync({\n responseHandler,\n entity,\n preparers,\n generators,\n });\n return;\n }\n\n responseHandler.error(\n new Error(\n \"Invalid configuration. docsBuildStrategy.shouldBuild returned 'true', but no 'preparer' was provided to the router initialization.\",\n ),\n );\n });\n\n // Ensures that the related entity exists and the current user has permission to view it.\n if (config.getOptionalBoolean('permission.enabled')) {\n router.use(\n '/static/docs/:namespace/:kind/:name',\n async (req, _res, next) => {\n const { kind, namespace, name } = req.params;\n const entityName = { kind, namespace, name };\n\n const credentials = await httpAuth.credentials(req, {\n allowLimitedAccess: true,\n });\n\n const { token } = await auth.getPluginRequestToken({\n onBehalfOf: credentials,\n targetPluginId: 'catalog',\n });\n\n const entity = await entityLoader.load(entityName, token);\n\n if (!entity) {\n throw new NotFoundError(\n `Entity not found for ${stringifyEntityRef(entityName)}`,\n );\n }\n\n next();\n },\n );\n }\n\n // If a cache manager was provided, attach the cache middleware.\n if (cache) {\n router.use(createCacheMiddleware({ logger, cache }));\n }\n\n // Route middleware which serves files from the storage set in the publisher.\n router.use('/static/docs', publisher.docsRouter());\n\n return router;\n}\n\n/**\n * Create an event-stream response that emits the events 'log', 'error', and 'finish'.\n *\n * @param res - the response to write the event-stream to\n * @returns A tuple of <log, error, finish> callbacks to emit messages. A call to 'error' or 'finish'\n * will close the event-stream.\n */\nexport function createEventStream(\n res: Response<any, any>,\n): DocsSynchronizerSyncOpts {\n // Mandatory headers and http status to keep connection open\n res.writeHead(200, {\n Connection: 'keep-alive',\n 'Cache-Control': 'no-cache',\n 'Content-Type': 'text/event-stream',\n });\n\n // client closes connection\n res.socket?.on('close', () => {\n res.end();\n });\n\n // write the event to the stream\n const send = (type: 'error' | 'finish' | 'log', data: any) => {\n res.write(`event: ${type}\\ndata: ${JSON.stringify(data)}\\n\\n`);\n\n // res.flush() is only available with the compression middleware\n if (res.flush) {\n res.flush();\n }\n };\n\n return {\n log: data => {\n send('log', data);\n },\n\n error: e => {\n send('error', e.message);\n res.end();\n },\n\n finish: result => {\n send('finish', result);\n res.end();\n },\n };\n}\n"],"names":["Router","catalogClient","CatalogClient","DefaultDocsBuildStrategy","CachedEntityLoader","TechDocsCache","ScmIntegrations","DocsSynchronizer","NotFoundError","stringifyEntityRef","getLocationForEntity","createCacheMiddleware"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"router.cjs.js","sources":["../../src/service/router.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { CatalogApi, CatalogClient } from '@backstage/catalog-client';\nimport { stringifyEntityRef } from '@backstage/catalog-model';\nimport { Config } from '@backstage/config';\nimport { NotFoundError } from '@backstage/errors';\nimport {\n DocsBuildStrategy,\n GeneratorBuilder,\n getLocationForEntity,\n PreparerBuilder,\n PublisherBase,\n} from '@backstage/plugin-techdocs-node';\nimport express, { Response } from 'express';\nimport Router from 'express-promise-router';\nimport { Knex } from 'knex';\nimport { ScmIntegrations } from '@backstage/integration';\nimport { DocsSynchronizer, DocsSynchronizerSyncOpts } from './DocsSynchronizer';\nimport { createCacheMiddleware, TechDocsCache } from '../cache';\nimport { CachedEntityLoader } from './CachedEntityLoader';\nimport { DefaultDocsBuildStrategy } from './DefaultDocsBuildStrategy';\nimport * as winston from 'winston';\nimport {\n AuthService,\n CacheService,\n DiscoveryService,\n HttpAuthService,\n LoggerService,\n} from '@backstage/backend-plugin-api';\n\n/**\n * Required dependencies for running TechDocs in the \"out-of-the-box\"\n * deployment configuration (prepare/generate/publish all in the Backend).\n *\n * @internal\n */\nexport type OutOfTheBoxDeploymentOptions = {\n preparers: PreparerBuilder;\n generators: GeneratorBuilder;\n publisher: PublisherBase;\n logger: LoggerService;\n discovery: DiscoveryService;\n database?: Knex; // TODO: Make database required when we're implementing database stuff.\n config: Config;\n cache: CacheService;\n docsBuildStrategy?: DocsBuildStrategy;\n buildLogTransport?: winston.transport;\n catalogClient?: CatalogApi;\n httpAuth: HttpAuthService;\n auth: AuthService;\n};\n\n/**\n * Required dependencies for running TechDocs in the \"recommended\" deployment\n * configuration (prepare/generate handled externally in CI/CD).\n *\n * @internal\n */\nexport type RecommendedDeploymentOptions = {\n publisher: PublisherBase;\n logger: LoggerService;\n discovery: DiscoveryService;\n config: Config;\n cache: CacheService;\n docsBuildStrategy?: DocsBuildStrategy;\n buildLogTransport?: winston.transport;\n catalogClient?: CatalogApi;\n httpAuth: HttpAuthService;\n auth: AuthService;\n};\n\n/**\n * One of the two deployment configurations must be provided.\n *\n * @internal\n */\nexport type RouterOptions =\n | RecommendedDeploymentOptions\n | OutOfTheBoxDeploymentOptions;\n\n/**\n * Typeguard to help createRouter() understand when we are in a \"recommended\"\n * deployment vs. when we are in an out-of-the-box deployment configuration.\n *\n * @internal\n */\nfunction isOutOfTheBoxOption(\n opt: RouterOptions,\n): opt is OutOfTheBoxDeploymentOptions {\n return (opt as OutOfTheBoxDeploymentOptions).preparers !== undefined;\n}\n\n/**\n * Creates a techdocs router.\n *\n * @internal\n */\nexport async function createRouter(\n options: RouterOptions,\n): Promise<express.Router> {\n const router = Router();\n const { publisher, config, logger, discovery, httpAuth, auth } = options;\n\n const catalogClient =\n options.catalogClient ?? new CatalogClient({ discoveryApi: discovery });\n const docsBuildStrategy =\n options.docsBuildStrategy ?? DefaultDocsBuildStrategy.fromConfig(config);\n const buildLogTransport = options.buildLogTransport;\n\n // Entities are cached to optimize the /static/docs request path, which can be called many times\n // when loading a single techdocs page.\n const entityLoader = new CachedEntityLoader({\n auth,\n catalog: catalogClient,\n cache: options.cache,\n });\n\n // Set up a cache client if configured.\n let cache: TechDocsCache | undefined;\n const defaultTtl = config.getOptionalNumber('techdocs.cache.ttl');\n if (defaultTtl) {\n const cacheClient = options.cache.withOptions({ defaultTtl });\n cache = TechDocsCache.fromConfig(config, { cache: cacheClient, logger });\n }\n\n const scmIntegrations = ScmIntegrations.fromConfig(config);\n const docsSynchronizer = new DocsSynchronizer({\n publisher,\n logger,\n buildLogTransport,\n config,\n scmIntegrations,\n cache,\n });\n\n router.get('/metadata/techdocs/:namespace/:kind/:name', async (req, res) => {\n const { kind, namespace, name } = req.params;\n const entityName = { kind, namespace, name };\n\n const credentials = await httpAuth.credentials(req);\n\n const { token } = await auth.getPluginRequestToken({\n onBehalfOf: credentials,\n targetPluginId: 'catalog',\n });\n\n // Verify that the related entity exists and the current user has permission to view it.\n const entity = await entityLoader.load(credentials, entityName, token);\n\n if (!entity) {\n throw new NotFoundError(\n `Unable to get metadata for '${stringifyEntityRef(entityName)}'`,\n );\n }\n\n try {\n const techdocsMetadata = await publisher.fetchTechDocsMetadata(\n entityName,\n );\n\n res.json(techdocsMetadata);\n } catch (err) {\n logger.info(\n `Unable to get metadata for '${stringifyEntityRef(\n entityName,\n )}' with error ${err}`,\n );\n throw new NotFoundError(\n `Unable to get metadata for '${stringifyEntityRef(entityName)}'`,\n err,\n );\n }\n });\n\n router.get('/metadata/entity/:namespace/:kind/:name', async (req, res) => {\n const { kind, namespace, name } = req.params;\n const entityName = { kind, namespace, name };\n\n const credentials = await httpAuth.credentials(req);\n\n const { token } = await auth.getPluginRequestToken({\n onBehalfOf: credentials,\n targetPluginId: 'catalog',\n });\n\n const entity = await entityLoader.load(credentials, entityName, token);\n\n if (!entity) {\n throw new NotFoundError(\n `Unable to get metadata for '${stringifyEntityRef(entityName)}'`,\n );\n }\n\n try {\n const locationMetadata = getLocationForEntity(entity, scmIntegrations);\n res.json({ ...entity, locationMetadata });\n } catch (err) {\n logger.info(\n `Unable to get metadata for '${stringifyEntityRef(\n entityName,\n )}' with error ${err}`,\n );\n throw new NotFoundError(\n `Unable to get metadata for '${stringifyEntityRef(entityName)}'`,\n err,\n );\n }\n });\n\n // Check if docs are the latest version and trigger rebuilds if not\n // Responds with an event-stream that closes after the build finished\n // Responds with an immediate success if rebuild not needed\n // If a build is required, responds with a success when finished\n router.get('/sync/:namespace/:kind/:name', async (req, res) => {\n const { kind, namespace, name } = req.params;\n\n const credentials = await httpAuth.credentials(req);\n\n const { token } = await auth.getPluginRequestToken({\n onBehalfOf: credentials,\n targetPluginId: 'catalog',\n });\n\n const entity = await entityLoader.load(\n credentials,\n { kind, namespace, name },\n token,\n );\n\n if (!entity?.metadata?.uid) {\n throw new NotFoundError('Entity metadata UID missing');\n }\n\n const responseHandler: DocsSynchronizerSyncOpts = createEventStream(res);\n\n // By default, techdocs-backend will only try to build documentation for an entity if techdocs.builder is set to\n // 'local'. If set to 'external', it will assume that an external process (e.g. CI/CD pipeline\n // of the repository) is responsible for building and publishing documentation to the storage provider.\n // Altering the implementation of the injected docsBuildStrategy allows for more complex behaviours, based on\n // either config or the properties of the entity (e.g. annotations, labels, spec fields etc.).\n const shouldBuild = await docsBuildStrategy.shouldBuild({ entity });\n if (!shouldBuild) {\n // However, if caching is enabled, take the opportunity to check and\n // invalidate stale cache entries.\n if (cache) {\n const { token: techDocsToken } = await auth.getPluginRequestToken({\n onBehalfOf: await auth.getOwnServiceCredentials(),\n targetPluginId: 'techdocs',\n });\n await docsSynchronizer.doCacheSync({\n responseHandler,\n discovery,\n token: techDocsToken,\n entity,\n });\n return;\n }\n responseHandler.finish({ updated: false });\n return;\n }\n\n // Set the synchronization and build process if \"out-of-the-box\" configuration is provided.\n if (isOutOfTheBoxOption(options)) {\n const { preparers, generators } = options;\n\n await docsSynchronizer.doSync({\n responseHandler,\n entity,\n preparers,\n generators,\n });\n return;\n }\n\n responseHandler.error(\n new Error(\n \"Invalid configuration. docsBuildStrategy.shouldBuild returned 'true', but no 'preparer' was provided to the router initialization.\",\n ),\n );\n });\n\n // Ensures that the related entity exists and the current user has permission to view it.\n if (config.getOptionalBoolean('permission.enabled')) {\n router.use(\n '/static/docs/:namespace/:kind/:name',\n async (req, _res, next) => {\n const { kind, namespace, name } = req.params;\n const entityName = { kind, namespace, name };\n\n const credentials = await httpAuth.credentials(req, {\n allowLimitedAccess: true,\n });\n\n const { token } = await auth.getPluginRequestToken({\n onBehalfOf: credentials,\n targetPluginId: 'catalog',\n });\n\n const entity = await entityLoader.load(credentials, entityName, token);\n\n if (!entity) {\n throw new NotFoundError(\n `Entity not found for ${stringifyEntityRef(entityName)}`,\n );\n }\n\n next();\n },\n );\n }\n\n // If a cache manager was provided, attach the cache middleware.\n if (cache) {\n router.use(createCacheMiddleware({ logger, cache }));\n }\n\n // Route middleware which serves files from the storage set in the publisher.\n router.use('/static/docs', publisher.docsRouter());\n\n return router;\n}\n\n/**\n * Create an event-stream response that emits the events 'log', 'error', and 'finish'.\n *\n * @param res - the response to write the event-stream to\n * @returns A tuple of <log, error, finish> callbacks to emit messages. A call to 'error' or 'finish'\n * will close the event-stream.\n */\nexport function createEventStream(\n res: Response<any, any>,\n): DocsSynchronizerSyncOpts {\n // Mandatory headers and http status to keep connection open\n res.writeHead(200, {\n Connection: 'keep-alive',\n 'Cache-Control': 'no-cache',\n 'Content-Type': 'text/event-stream',\n });\n\n // client closes connection\n res.socket?.on('close', () => {\n res.end();\n });\n\n // write the event to the stream\n const send = (type: 'error' | 'finish' | 'log', data: any) => {\n res.write(`event: ${type}\\ndata: ${JSON.stringify(data)}\\n\\n`);\n\n // res.flush() is only available with the compression middleware\n if (res.flush) {\n res.flush();\n }\n };\n\n return {\n log: data => {\n send('log', data);\n },\n\n error: e => {\n send('error', e.message);\n res.end();\n },\n\n finish: result => {\n send('finish', result);\n res.end();\n },\n };\n}\n"],"names":["Router","catalogClient","CatalogClient","DefaultDocsBuildStrategy","CachedEntityLoader","TechDocsCache","ScmIntegrations","DocsSynchronizer","NotFoundError","stringifyEntityRef","getLocationForEntity","createCacheMiddleware"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backstage/plugin-techdocs-backend",
|
|
3
|
-
"version": "2.0.5-next.
|
|
3
|
+
"version": "2.0.5-next.1",
|
|
4
4
|
"description": "The Backstage backend plugin that renders technical documentation for your components",
|
|
5
5
|
"backstage": {
|
|
6
6
|
"role": "backend-plugin",
|
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
"devDependencies": {
|
|
97
97
|
"@backstage/backend-defaults": "0.11.2-next.0",
|
|
98
98
|
"@backstage/backend-test-utils": "1.7.1-next.0",
|
|
99
|
-
"@backstage/cli": "0.
|
|
99
|
+
"@backstage/cli": "0.34.0-next.1",
|
|
100
100
|
"@types/express": "^4.17.6",
|
|
101
101
|
"msw": "^2.0.0",
|
|
102
102
|
"supertest": "^7.0.0"
|