Merge pull request #33747 from backstage/freben/migrate-techdocs-from-alpha-catalog-service-ref
Migrate techdocs plugins from alpha to stable catalogServiceRef
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/plugin-search-backend-module-techdocs': patch
|
||||
'@backstage/plugin-techdocs-backend': patch
|
||||
---
|
||||
|
||||
Migrated internal usage of the deprecated `catalogServiceRef` from `@backstage/plugin-catalog-node/alpha` to the stable `catalogServiceRef` from `@backstage/plugin-catalog-node`.
|
||||
+19
-34
@@ -21,6 +21,7 @@ import {
|
||||
mockServices,
|
||||
registerMswTestHooks,
|
||||
} from '@backstage/backend-test-utils';
|
||||
import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils';
|
||||
import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import { Readable } from 'node:stream';
|
||||
@@ -85,10 +86,12 @@ describe('DefaultTechDocsCollatorFactory', () => {
|
||||
const mockDiscoveryApi = mockServices.discovery.mock({
|
||||
getBaseUrl: async () => 'http://test-backend',
|
||||
});
|
||||
const mockCatalog = catalogServiceMock({ entities: expectedEntities });
|
||||
const options = {
|
||||
logger,
|
||||
discovery: mockDiscoveryApi,
|
||||
auth: mockServices.auth(),
|
||||
catalog: mockCatalog,
|
||||
};
|
||||
|
||||
it('has expected type', () => {
|
||||
@@ -112,31 +115,6 @@ describe('DefaultTechDocsCollatorFactory', () => {
|
||||
'http://test-backend/static/docs/default/Component/test-entity-with-docs/search/search_index.json',
|
||||
(_, res, ctx) => res(ctx.status(200), ctx.json(mockSearchDocIndex)),
|
||||
),
|
||||
rest.get('http://test-backend/entities', (req, res, ctx) => {
|
||||
// Imitate offset/limit pagination.
|
||||
const offset = parseInt(
|
||||
req.url.searchParams.get('offset') || '0',
|
||||
10,
|
||||
);
|
||||
const limit = parseInt(
|
||||
req.url.searchParams.get('limit') || '500',
|
||||
10,
|
||||
);
|
||||
|
||||
// Limit 50 corresponds to a case testing pagination.
|
||||
if (limit === 50) {
|
||||
// Return 50 copies of invalid entities on the first request.
|
||||
if (offset === 0) {
|
||||
return res(ctx.status(200), ctx.json(Array(50).fill({})));
|
||||
}
|
||||
// Then just the regular 2 on the second.
|
||||
return res(ctx.status(200), ctx.json(expectedEntities));
|
||||
}
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.json(expectedEntities.slice(offset, limit + offset)),
|
||||
);
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -147,7 +125,6 @@ describe('DefaultTechDocsCollatorFactory', () => {
|
||||
it('fetches from the configured catalog and tech docs services', async () => {
|
||||
const pipeline = TestPipeline.fromCollator(collator);
|
||||
const { documents } = await pipeline.execute();
|
||||
expect(mockDiscoveryApi.getBaseUrl).toHaveBeenCalledWith('catalog');
|
||||
expect(mockDiscoveryApi.getBaseUrl).toHaveBeenCalledWith('techdocs');
|
||||
expect(documents).toHaveLength(mockSearchDocIndex.docs.length);
|
||||
});
|
||||
@@ -184,6 +161,7 @@ describe('DefaultTechDocsCollatorFactory', () => {
|
||||
discovery: mockDiscoveryApi,
|
||||
logger,
|
||||
auth: mockServices.auth(),
|
||||
catalog: mockCatalog,
|
||||
});
|
||||
collator = await factory.getCollator();
|
||||
|
||||
@@ -208,9 +186,9 @@ describe('DefaultTechDocsCollatorFactory', () => {
|
||||
});
|
||||
|
||||
it('paginates through catalog entities using batchSize', async () => {
|
||||
// A parallelismLimit of 1 is a catalog limit of 50 per request. Code
|
||||
// above in the /entities handler ensures valid entities are only
|
||||
// returned on the second page.
|
||||
// parallelismLimit of 1 → batchSize of 50 per request.
|
||||
// First page returns exactly 50 (no techdocs annotation) triggering a
|
||||
// second request; second page returns the real entity with annotation.
|
||||
const _config = new ConfigReader({
|
||||
...config.get(),
|
||||
search: {
|
||||
@@ -221,17 +199,24 @@ describe('DefaultTechDocsCollatorFactory', () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
factory = DefaultTechDocsCollatorFactory.fromConfig(_config, options);
|
||||
const paginationCatalog = catalogServiceMock({ entities: [] });
|
||||
jest
|
||||
.spyOn(paginationCatalog, 'getEntities')
|
||||
.mockResolvedValueOnce({ items: Array(50).fill({}) })
|
||||
.mockResolvedValueOnce({ items: expectedEntities });
|
||||
factory = DefaultTechDocsCollatorFactory.fromConfig(_config, {
|
||||
...options,
|
||||
catalog: paginationCatalog,
|
||||
});
|
||||
collator = await factory.getCollator();
|
||||
|
||||
const pipeline = TestPipeline.fromCollator(collator);
|
||||
const { documents } = await pipeline.execute();
|
||||
|
||||
// Only 1 entity with TechDocs configured multiplied by 3 pages.
|
||||
expect(paginationCatalog.getEntities).toHaveBeenCalledTimes(2);
|
||||
// First page: 50 entities with no techdocs annotation → 0 docs
|
||||
// Second page: 1 entity × 3 search index docs → 3 docs
|
||||
expect(documents).toHaveLength(3);
|
||||
expect(_config.get('search.collators.techdocs.parallelismLimit')).toEqual(
|
||||
1,
|
||||
);
|
||||
});
|
||||
|
||||
describe('with legacyPathCasing configuration', () => {
|
||||
|
||||
+7
-14
@@ -16,8 +16,6 @@
|
||||
|
||||
import {
|
||||
CATALOG_FILTER_EXISTS,
|
||||
CatalogApi,
|
||||
CatalogClient,
|
||||
EntityFilterQuery,
|
||||
} from '@backstage/catalog-client';
|
||||
import {
|
||||
@@ -45,6 +43,7 @@ import {
|
||||
DiscoveryService,
|
||||
LoggerService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { CatalogService } from '@backstage/plugin-catalog-node';
|
||||
|
||||
/**
|
||||
* Options to configure the TechDocs collator factory
|
||||
@@ -56,7 +55,7 @@ export type TechDocsCollatorFactoryOptions = {
|
||||
logger: LoggerService;
|
||||
auth: AuthService;
|
||||
locationTemplate?: string;
|
||||
catalogClient?: CatalogApi;
|
||||
catalog: CatalogService;
|
||||
parallelismLimit?: number;
|
||||
legacyPathCasing?: boolean;
|
||||
entityTransformer?: TechDocsCollatorEntityTransformer;
|
||||
@@ -86,7 +85,7 @@ export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory {
|
||||
private locationTemplate: string;
|
||||
private readonly logger: LoggerService;
|
||||
private readonly auth: AuthService;
|
||||
private readonly catalogClient: CatalogApi;
|
||||
private readonly catalog: CatalogService;
|
||||
private readonly parallelismLimit: number;
|
||||
private readonly legacyPathCasing: boolean;
|
||||
private entityTransformer: TechDocsCollatorEntityTransformer;
|
||||
@@ -99,9 +98,7 @@ export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory {
|
||||
this.locationTemplate =
|
||||
options.locationTemplate || '/docs/:namespace/:kind/:name/:path';
|
||||
this.logger = options.logger.child({ documentType: this.type });
|
||||
this.catalogClient =
|
||||
options.catalogClient ||
|
||||
new CatalogClient({ discoveryApi: options.discovery });
|
||||
this.catalog = options.catalog;
|
||||
this.parallelismLimit = options.parallelismLimit ?? 10;
|
||||
this.legacyPathCasing = options.legacyPathCasing ?? false;
|
||||
this.entityTransformer = options.entityTransformer ?? (() => ({}));
|
||||
@@ -147,13 +144,9 @@ export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory {
|
||||
// parallelism limit to simplify configuration.
|
||||
const batchSize = this.parallelismLimit * 50;
|
||||
while (moreEntitiesToGet) {
|
||||
const { token: catalogToken } = await this.auth.getPluginRequestToken({
|
||||
onBehalfOf: await this.auth.getOwnServiceCredentials(),
|
||||
targetPluginId: 'catalog',
|
||||
});
|
||||
|
||||
const credentials = await this.auth.getOwnServiceCredentials();
|
||||
const entities = (
|
||||
await this.catalogClient.getEntities(
|
||||
await this.catalog.getEntities(
|
||||
{
|
||||
filter: {
|
||||
'metadata.annotations.backstage.io/techdocs-ref':
|
||||
@@ -163,7 +156,7 @@ export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory {
|
||||
limit: batchSize,
|
||||
offset: entitiesRetrieved,
|
||||
},
|
||||
{ token: catalogToken },
|
||||
{ credentials },
|
||||
)
|
||||
).items;
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { EntityFilterQuery } from '@backstage/catalog-client';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha';
|
||||
import { catalogServiceRef } from '@backstage/plugin-catalog-node';
|
||||
import { searchIndexRegistryExtensionPoint } from '@backstage/plugin-search-backend-node/alpha';
|
||||
import { DefaultTechDocsCollatorFactory } from './collators/DefaultTechDocsCollatorFactory';
|
||||
import {
|
||||
@@ -162,7 +162,7 @@ export default createBackendModule({
|
||||
discovery,
|
||||
auth,
|
||||
logger,
|
||||
catalogClient: catalog,
|
||||
catalog,
|
||||
entityTransformer,
|
||||
documentTransformer,
|
||||
customCatalogApiFilters,
|
||||
|
||||
@@ -34,7 +34,7 @@ import {
|
||||
techdocsPreparerExtensionPoint,
|
||||
techdocsPublisherExtensionPoint,
|
||||
} from '@backstage/plugin-techdocs-node';
|
||||
import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha';
|
||||
import { catalogServiceRef } from '@backstage/plugin-catalog-node';
|
||||
import * as winston from 'winston';
|
||||
import { createRouter } from './service/router';
|
||||
|
||||
@@ -166,7 +166,7 @@ export const techdocsPlugin = createBackendPlugin({
|
||||
discovery,
|
||||
httpAuth,
|
||||
auth,
|
||||
catalogClient: catalog,
|
||||
catalog,
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -16,13 +16,11 @@
|
||||
|
||||
import { CachedEntityLoader } from './CachedEntityLoader';
|
||||
import { CompoundEntityRef } from '@backstage/catalog-model';
|
||||
import { mockServices } from '@backstage/backend-test-utils';
|
||||
import { mockCredentials, mockServices } from '@backstage/backend-test-utils';
|
||||
import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils';
|
||||
import { BackstageCredentials } from '@backstage/backend-plugin-api';
|
||||
|
||||
describe('CachedEntityLoader', () => {
|
||||
const cache = mockServices.cache.mock();
|
||||
const auth = mockServices.auth.mock();
|
||||
|
||||
const entityName: CompoundEntityRef = {
|
||||
kind: 'component',
|
||||
@@ -39,23 +37,8 @@ describe('CachedEntityLoader', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const token = 'test-token';
|
||||
|
||||
const userCredentials: BackstageCredentials = {
|
||||
$$type: '@backstage/BackstageCredentials',
|
||||
principal: {
|
||||
type: 'user',
|
||||
userEntityRef: 'user:default/test-user',
|
||||
},
|
||||
};
|
||||
|
||||
const pluginCredentials: BackstageCredentials = {
|
||||
$$type: '@backstage/BackstageCredentials',
|
||||
principal: {
|
||||
type: 'plugin',
|
||||
subject: 'plugin:test-plugin',
|
||||
},
|
||||
};
|
||||
const userCredentials = mockCredentials.user('user:default/test-user');
|
||||
const serviceCredentials = mockCredentials.service('plugin:test-plugin');
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
@@ -64,14 +47,17 @@ describe('CachedEntityLoader', () => {
|
||||
it('writes entities to cache for user credentials', async () => {
|
||||
cache.get.mockResolvedValue(undefined);
|
||||
const catalog = catalogServiceMock({ entities: [entity] });
|
||||
auth.isPrincipal.mockReturnValue(true);
|
||||
jest.spyOn(catalog, 'getEntityByRef');
|
||||
|
||||
const loader = new CachedEntityLoader({ auth, catalog, cache });
|
||||
const result = await loader.load(userCredentials, entityName, token);
|
||||
const loader = new CachedEntityLoader({ catalog, cache });
|
||||
const result = await loader.load(userCredentials, entityName);
|
||||
|
||||
expect(result).toEqual(entity);
|
||||
expect(catalog.getEntityByRef).toHaveBeenCalledWith(entityName, {
|
||||
credentials: userCredentials,
|
||||
});
|
||||
expect(cache.set).toHaveBeenCalledWith(
|
||||
'catalog:component:default/test:user:default/test-user',
|
||||
`catalog:component:default/test:${userCredentials}`,
|
||||
entity,
|
||||
{ ttl: 5000 },
|
||||
);
|
||||
@@ -81,10 +67,9 @@ describe('CachedEntityLoader', () => {
|
||||
const catalog = catalogServiceMock();
|
||||
jest.spyOn(catalog, 'getEntityByRef');
|
||||
cache.get.mockResolvedValue(entity);
|
||||
auth.isPrincipal.mockReturnValue(true);
|
||||
|
||||
const loader = new CachedEntityLoader({ auth, catalog, cache });
|
||||
const result = await loader.load(userCredentials, entityName, token);
|
||||
const loader = new CachedEntityLoader({ catalog, cache });
|
||||
const result = await loader.load(userCredentials, entityName);
|
||||
|
||||
expect(result).toEqual(entity);
|
||||
expect(catalog.getEntityByRef).not.toHaveBeenCalled();
|
||||
@@ -93,33 +78,14 @@ describe('CachedEntityLoader', () => {
|
||||
it('does not cache missing entities', async () => {
|
||||
const catalog = catalogServiceMock({ entities: [] });
|
||||
cache.get.mockResolvedValue(undefined);
|
||||
auth.isPrincipal.mockReturnValue(true);
|
||||
|
||||
const loader = new CachedEntityLoader({ auth, catalog, cache });
|
||||
const result = await loader.load(userCredentials, entityName, token);
|
||||
const loader = new CachedEntityLoader({ catalog, cache });
|
||||
const result = await loader.load(userCredentials, entityName);
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
expect(cache.set).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('uses entity ref as cache key for service credentials', async () => {
|
||||
const catalog = catalogServiceMock({ entities: [entity] });
|
||||
cache.get.mockResolvedValue(undefined);
|
||||
auth.isPrincipal.mockReturnValueOnce(false).mockReturnValueOnce(true);
|
||||
|
||||
const loader = new CachedEntityLoader({ auth, catalog, cache });
|
||||
const result = await loader.load(pluginCredentials, entityName, undefined);
|
||||
|
||||
expect(result).toEqual(entity);
|
||||
expect(cache.set).toHaveBeenCalledWith(
|
||||
'catalog:component:default/test:plugin:test-plugin',
|
||||
entity,
|
||||
{
|
||||
ttl: 5000,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('calls the catalog if the cache read takes too long', async () => {
|
||||
cache.get.mockImplementation(
|
||||
() =>
|
||||
@@ -128,80 +94,48 @@ describe('CachedEntityLoader', () => {
|
||||
}),
|
||||
);
|
||||
const catalog = catalogServiceMock({ entities: [entity] });
|
||||
auth.isPrincipal.mockReturnValue(true);
|
||||
|
||||
const loader = new CachedEntityLoader({ auth, catalog, cache });
|
||||
const result = await loader.load(userCredentials, entityName, token);
|
||||
const loader = new CachedEntityLoader({ catalog, cache });
|
||||
const result = await loader.load(userCredentials, entityName);
|
||||
|
||||
expect(result).toEqual(entity);
|
||||
});
|
||||
|
||||
it('creates different cache keys for different users', async () => {
|
||||
it('creates different cache keys for different credentials', async () => {
|
||||
const catalog = catalogServiceMock({ entities: [entity] });
|
||||
cache.get.mockResolvedValue(undefined);
|
||||
auth.isPrincipal.mockReturnValue(true);
|
||||
|
||||
const loader = new CachedEntityLoader({ auth, catalog, cache });
|
||||
const loader = new CachedEntityLoader({ catalog, cache });
|
||||
|
||||
const anotherUserCredentials: BackstageCredentials = {
|
||||
$$type: '@backstage/BackstageCredentials',
|
||||
principal: {
|
||||
type: 'user',
|
||||
userEntityRef: 'user:default/another-user',
|
||||
},
|
||||
};
|
||||
const anotherUserCredentials = mockCredentials.user(
|
||||
'user:default/another-user',
|
||||
);
|
||||
|
||||
await loader.load(userCredentials, entityName, token);
|
||||
await loader.load(anotherUserCredentials, entityName, token);
|
||||
await loader.load(userCredentials, entityName);
|
||||
await loader.load(anotherUserCredentials, entityName);
|
||||
|
||||
expect(cache.set).toHaveBeenCalledWith(
|
||||
'catalog:component:default/test:user:default/test-user',
|
||||
`catalog:component:default/test:${userCredentials}`,
|
||||
entity,
|
||||
{ ttl: 5000 },
|
||||
);
|
||||
expect(cache.set).toHaveBeenCalledWith(
|
||||
'catalog:component:default/test:user:default/another-user',
|
||||
`catalog:component:default/test:${anotherUserCredentials}`,
|
||||
entity,
|
||||
{ ttl: 5000 },
|
||||
);
|
||||
});
|
||||
|
||||
it('creates cache key with service subject for service credentials', async () => {
|
||||
it('uses service credentials as cache key for service credentials', async () => {
|
||||
const catalog = catalogServiceMock({ entities: [entity] });
|
||||
cache.get.mockResolvedValue(undefined);
|
||||
auth.isPrincipal.mockReturnValueOnce(false).mockReturnValueOnce(true);
|
||||
|
||||
const loader = new CachedEntityLoader({ auth, catalog, cache });
|
||||
const result = await loader.load(pluginCredentials, entityName, token);
|
||||
const loader = new CachedEntityLoader({ catalog, cache });
|
||||
const result = await loader.load(serviceCredentials, entityName);
|
||||
|
||||
expect(result).toEqual(entity);
|
||||
expect(cache.set).toHaveBeenCalledWith(
|
||||
'catalog:component:default/test:plugin:test-plugin',
|
||||
entity,
|
||||
{ ttl: 5000 },
|
||||
);
|
||||
expect(auth.isPrincipal).toHaveBeenCalledWith(pluginCredentials, 'user');
|
||||
expect(auth.isPrincipal).toHaveBeenCalledWith(pluginCredentials, 'service');
|
||||
});
|
||||
|
||||
it('handles credentials that are neither user nor service', async () => {
|
||||
const catalog = catalogServiceMock({ entities: [entity] });
|
||||
cache.get.mockResolvedValue(undefined);
|
||||
auth.isPrincipal.mockReturnValue(false);
|
||||
|
||||
const unknownCredentials: BackstageCredentials = {
|
||||
$$type: '@backstage/BackstageCredentials',
|
||||
principal: {
|
||||
type: 'unknown' as any,
|
||||
},
|
||||
};
|
||||
|
||||
const loader = new CachedEntityLoader({ auth, catalog, cache });
|
||||
const result = await loader.load(unknownCredentials, entityName, token);
|
||||
|
||||
expect(result).toEqual(entity);
|
||||
expect(cache.set).toHaveBeenCalledWith(
|
||||
'catalog:component:default/test',
|
||||
`catalog:component:default/test:${serviceCredentials}`,
|
||||
entity,
|
||||
{ ttl: 5000 },
|
||||
);
|
||||
|
||||
@@ -15,31 +15,27 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
AuthService,
|
||||
BackstageCredentials,
|
||||
CacheService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { CatalogApi } from '@backstage/catalog-client';
|
||||
import {
|
||||
Entity,
|
||||
CompoundEntityRef,
|
||||
stringifyEntityRef,
|
||||
} from '@backstage/catalog-model';
|
||||
import { CatalogService } from '@backstage/plugin-catalog-node';
|
||||
|
||||
export type CachedEntityLoaderOptions = {
|
||||
auth: AuthService;
|
||||
catalog: CatalogApi;
|
||||
catalog: CatalogService;
|
||||
cache: CacheService;
|
||||
};
|
||||
|
||||
export class CachedEntityLoader {
|
||||
private readonly auth: AuthService;
|
||||
private readonly catalog: CatalogApi;
|
||||
private readonly catalog: CatalogService;
|
||||
private readonly cache: CacheService;
|
||||
private readonly readTimeout = 1000;
|
||||
|
||||
constructor({ auth, catalog, cache }: CachedEntityLoaderOptions) {
|
||||
this.auth = auth;
|
||||
constructor({ catalog, cache }: CachedEntityLoaderOptions) {
|
||||
this.catalog = catalog;
|
||||
this.cache = cache;
|
||||
}
|
||||
@@ -47,7 +43,6 @@ export class CachedEntityLoader {
|
||||
async load(
|
||||
credentials: BackstageCredentials,
|
||||
entityRef: CompoundEntityRef,
|
||||
token: string | undefined,
|
||||
): Promise<Entity | undefined> {
|
||||
const cacheKey = this.getCacheKey(entityRef, credentials);
|
||||
let result = await this.getFromCache(cacheKey);
|
||||
@@ -56,7 +51,7 @@ export class CachedEntityLoader {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = await this.catalog.getEntityByRef(entityRef, { token });
|
||||
result = await this.catalog.getEntityByRef(entityRef, { credentials });
|
||||
|
||||
if (result) {
|
||||
this.cache.set(cacheKey, result, { ttl: 5000 });
|
||||
@@ -78,14 +73,10 @@ export class CachedEntityLoader {
|
||||
entityName: CompoundEntityRef,
|
||||
credentials: BackstageCredentials,
|
||||
): string {
|
||||
const key = ['catalog', stringifyEntityRef(entityName)];
|
||||
|
||||
if (this.auth.isPrincipal(credentials, 'user')) {
|
||||
key.push(credentials.principal.userEntityRef);
|
||||
} else if (this.auth.isPrincipal(credentials, 'service')) {
|
||||
key.push(credentials.principal.subject);
|
||||
}
|
||||
|
||||
return key.join(':');
|
||||
return [
|
||||
'catalog',
|
||||
stringifyEntityRef(entityName),
|
||||
String(credentials), // these have a well defined toString method
|
||||
].join(':');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ import { CachedEntityLoader } from './CachedEntityLoader';
|
||||
import { createEventStream, createRouter, RouterOptions } from './router';
|
||||
import { TechDocsCache } from '../cache';
|
||||
import { mockErrorHandler, mockServices } from '@backstage/backend-test-utils';
|
||||
import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils';
|
||||
|
||||
jest.mock('@backstage/catalog-client');
|
||||
jest.mock('./CachedEntityLoader');
|
||||
jest.mock('./DocsSynchronizer');
|
||||
jest.mock('../cache/TechDocsCache');
|
||||
@@ -107,6 +107,7 @@ describe('createRouter', () => {
|
||||
const docsBuildStrategy: jest.Mocked<DocsBuildStrategy> = {
|
||||
shouldBuild: jest.fn(),
|
||||
};
|
||||
const mockCatalogService = catalogServiceMock();
|
||||
const outOfTheBoxOptions = {
|
||||
preparers,
|
||||
generators,
|
||||
@@ -124,6 +125,7 @@ describe('createRouter', () => {
|
||||
docsBuildStrategy,
|
||||
auth: mockServices.auth(),
|
||||
httpAuth: mockServices.httpAuth(),
|
||||
catalog: mockCatalogService,
|
||||
};
|
||||
const recommendedOptions = {
|
||||
publisher,
|
||||
@@ -134,6 +136,7 @@ describe('createRouter', () => {
|
||||
docsBuildStrategy,
|
||||
auth: mockServices.auth(),
|
||||
httpAuth: mockServices.httpAuth(),
|
||||
catalog: mockCatalogService,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { CatalogApi, CatalogClient } from '@backstage/catalog-client';
|
||||
import { stringifyEntityRef } from '@backstage/catalog-model';
|
||||
import { Config, readDurationFromConfig } from '@backstage/config';
|
||||
import { NotFoundError } from '@backstage/errors';
|
||||
@@ -41,6 +40,7 @@ import {
|
||||
HttpAuthService,
|
||||
LoggerService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { CatalogService } from '@backstage/plugin-catalog-node';
|
||||
import { durationToMilliseconds } from '@backstage/types';
|
||||
|
||||
/**
|
||||
@@ -60,7 +60,7 @@ export type OutOfTheBoxDeploymentOptions = {
|
||||
cache: CacheService;
|
||||
docsBuildStrategy?: DocsBuildStrategy;
|
||||
buildLogTransport?: winston.transport;
|
||||
catalogClient?: CatalogApi;
|
||||
catalog: CatalogService;
|
||||
httpAuth: HttpAuthService;
|
||||
auth: AuthService;
|
||||
};
|
||||
@@ -79,7 +79,7 @@ export type RecommendedDeploymentOptions = {
|
||||
cache: CacheService;
|
||||
docsBuildStrategy?: DocsBuildStrategy;
|
||||
buildLogTransport?: winston.transport;
|
||||
catalogClient?: CatalogApi;
|
||||
catalog: CatalogService;
|
||||
httpAuth: HttpAuthService;
|
||||
auth: AuthService;
|
||||
};
|
||||
@@ -114,10 +114,9 @@ export async function createRouter(
|
||||
options: RouterOptions,
|
||||
): Promise<express.Router> {
|
||||
const router = Router();
|
||||
const { publisher, config, logger, discovery, httpAuth, auth } = options;
|
||||
const { publisher, config, logger, discovery, httpAuth, auth, catalog } =
|
||||
options;
|
||||
|
||||
const catalogClient =
|
||||
options.catalogClient ?? new CatalogClient({ discoveryApi: discovery });
|
||||
const docsBuildStrategy =
|
||||
options.docsBuildStrategy ?? DefaultDocsBuildStrategy.fromConfig(config);
|
||||
const buildLogTransport = options.buildLogTransport;
|
||||
@@ -125,8 +124,7 @@ export async function createRouter(
|
||||
// Entities are cached to optimize the /static/docs request path, which can be called many times
|
||||
// when loading a single techdocs page.
|
||||
const entityLoader = new CachedEntityLoader({
|
||||
auth,
|
||||
catalog: catalogClient,
|
||||
catalog,
|
||||
cache: options.cache,
|
||||
});
|
||||
|
||||
@@ -163,13 +161,8 @@ export async function createRouter(
|
||||
|
||||
const credentials = await httpAuth.credentials(req);
|
||||
|
||||
const { token } = await auth.getPluginRequestToken({
|
||||
onBehalfOf: credentials,
|
||||
targetPluginId: 'catalog',
|
||||
});
|
||||
|
||||
// Verify that the related entity exists and the current user has permission to view it.
|
||||
const entity = await entityLoader.load(credentials, entityName, token);
|
||||
const entity = await entityLoader.load(credentials, entityName);
|
||||
|
||||
if (!entity) {
|
||||
throw new NotFoundError(
|
||||
@@ -202,12 +195,7 @@ export async function createRouter(
|
||||
|
||||
const credentials = await httpAuth.credentials(req);
|
||||
|
||||
const { token } = await auth.getPluginRequestToken({
|
||||
onBehalfOf: credentials,
|
||||
targetPluginId: 'catalog',
|
||||
});
|
||||
|
||||
const entity = await entityLoader.load(credentials, entityName, token);
|
||||
const entity = await entityLoader.load(credentials, entityName);
|
||||
|
||||
if (!entity) {
|
||||
throw new NotFoundError(
|
||||
@@ -240,17 +228,12 @@ export async function createRouter(
|
||||
|
||||
const credentials = await httpAuth.credentials(req);
|
||||
|
||||
const { token } = await auth.getPluginRequestToken({
|
||||
onBehalfOf: credentials,
|
||||
targetPluginId: 'catalog',
|
||||
const entity = await entityLoader.load(credentials, {
|
||||
kind,
|
||||
namespace,
|
||||
name,
|
||||
});
|
||||
|
||||
const entity = await entityLoader.load(
|
||||
credentials,
|
||||
{ kind, namespace, name },
|
||||
token,
|
||||
);
|
||||
|
||||
if (!entity?.metadata?.uid) {
|
||||
throw new NotFoundError('Entity metadata UID missing');
|
||||
}
|
||||
@@ -315,12 +298,7 @@ export async function createRouter(
|
||||
allowLimitedAccess: true,
|
||||
});
|
||||
|
||||
const { token } = await auth.getPluginRequestToken({
|
||||
onBehalfOf: credentials,
|
||||
targetPluginId: 'catalog',
|
||||
});
|
||||
|
||||
const entity = await entityLoader.load(credentials, entityName, token);
|
||||
const entity = await entityLoader.load(credentials, entityName);
|
||||
|
||||
if (!entity) {
|
||||
throw new NotFoundError(
|
||||
|
||||
Reference in New Issue
Block a user