From dfdb8e3a730e6d9fe6ba45d65a10d7a09fa50e5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 10 Mar 2026 14:12:20 +0100 Subject: [PATCH] Add defaultLocationConflictStrategy config option for catalog locations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a catalog config option to set the default conflict strategy when registering locations, so adopters can default to 'refresh' instead of 'reject' without requiring each caller to specify it explicitly. Co-Authored-By: Claude Opus 4.6 Signed-off-by: Fredrik Adelöw --- .changeset/eight-poems-take.md | 2 +- plugins/catalog-backend/config.d.ts | 9 +++++++++ plugins/catalog-backend/src/service/CatalogBuilder.ts | 4 ++++ .../src/service/DefaultLocationService.test.ts | 1 + .../src/service/DefaultLocationService.ts | 5 ++++- plugins/catalog-backend/src/service/createRouter.test.ts | 5 ++++- plugins/catalog-backend/src/service/types.ts | 2 +- 7 files changed, 24 insertions(+), 4 deletions(-) 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;