diff --git a/.changeset/eight-poems-take.md b/.changeset/eight-poems-take.md index 2d04d3806f..00f1d09466 100644 --- a/.changeset/eight-poems-take.md +++ b/.changeset/eight-poems-take.md @@ -3,4 +3,4 @@ '@backstage/plugin-catalog-backend': minor --- -Add an `onConflict` option to location creation, that enables it to perform refreshes instead of throwing conflict errors when there's a pre-existing location already +Add an `onConflict` option to location creation that can refresh an existing location instead of throwing a conflict error. diff --git a/plugins/catalog-backend/config.d.ts b/plugins/catalog-backend/config.d.ts index 48dae2d747..433d8c494c 100644 --- a/plugins/catalog-backend/config.d.ts +++ b/plugins/catalog-backend/config.d.ts @@ -191,6 +191,15 @@ export interface Config { stitchTimeout?: HumanDuration | string; }; + /** + * The strategy to use when there is a conflict with a location being registered. + * + * The default value is "reject". + * + * The "refresh" strategy will refresh the existing location instead of throwing a conflict error. + */ + defaultLocationConflictStrategy?: 'refresh' | 'reject'; + /** * The interval at which the catalog should process its entities. * @remarks diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index d0f2442f0c..d3bf47874d 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -594,6 +594,10 @@ export class CatalogBuilder { const locationService = new AuthorizedLocationService( new DefaultLocationService(locationStore, orchestrator, { allowedLocationTypes: this.allowedLocationType, + defaultLocationConflictStrategy: + (config.getOptionalString( + 'catalog.defaultLocationConflictStrategy', + ) as 'refresh' | 'reject') || 'reject', }), permissionsService, ); diff --git a/plugins/catalog-backend/src/service/DefaultLocationService.test.ts b/plugins/catalog-backend/src/service/DefaultLocationService.test.ts index db95b0f19a..3cce0efc3f 100644 --- a/plugins/catalog-backend/src/service/DefaultLocationService.test.ts +++ b/plugins/catalog-backend/src/service/DefaultLocationService.test.ts @@ -282,6 +282,7 @@ describe('DefaultLocationServiceTest', () => { orchestrator, { allowedLocationTypes: ['url', 'unknown'], + defaultLocationConflictStrategy: 'reject', }, ); await expect( diff --git a/plugins/catalog-backend/src/service/DefaultLocationService.ts b/plugins/catalog-backend/src/service/DefaultLocationService.ts index fe3d8c96a7..7effaa2e88 100644 --- a/plugins/catalog-backend/src/service/DefaultLocationService.ts +++ b/plugins/catalog-backend/src/service/DefaultLocationService.ts @@ -33,6 +33,7 @@ import { BackstageCredentials } from '@backstage/backend-plugin-api'; export type DefaultLocationServiceOptions = { allowedLocationTypes: string[]; + defaultLocationConflictStrategy: 'refresh' | 'reject'; }; export class DefaultLocationService implements LocationService { @@ -45,6 +46,7 @@ export class DefaultLocationService implements LocationService { orchestrator: CatalogProcessingOrchestrator, options: DefaultLocationServiceOptions = { allowedLocationTypes: ['url'], + defaultLocationConflictStrategy: 'reject', }, ) { this.store = store; @@ -71,7 +73,8 @@ export class DefaultLocationService implements LocationService { return this.dryRunCreateLocation(input); } const location = await this.store.createLocation(input, { - onConflict: options?.onConflict, + onConflict: + options?.onConflict ?? this.options.defaultLocationConflictStrategy, }); return { location, entities: [] }; } diff --git a/plugins/catalog-backend/src/service/createRouter.test.ts b/plugins/catalog-backend/src/service/createRouter.test.ts index 1984f2f04f..88cd5abde3 100644 --- a/plugins/catalog-backend/src/service/createRouter.test.ts +++ b/plugins/catalog-backend/src/service/createRouter.test.ts @@ -1685,7 +1685,10 @@ describe('POST /locations/by-query works end to end', () => { const locationService = new DefaultLocationService( store, { process: jest.fn() }, - { allowedLocationTypes: ['url'] }, + { + allowedLocationTypes: ['url'], + defaultLocationConflictStrategy: 'reject', + }, ); const router = await createRouter({ diff --git a/plugins/catalog-backend/src/service/types.ts b/plugins/catalog-backend/src/service/types.ts index b9c1214acb..c949da1eda 100644 --- a/plugins/catalog-backend/src/service/types.ts +++ b/plugins/catalog-backend/src/service/types.ts @@ -88,7 +88,7 @@ export interface LocationStore { createLocation( location: LocationInput, options?: { - onConflict?: 'refresh' | 'reject'; + onConflict: 'refresh' | 'reject'; }, ): Promise; listLocations(): Promise;