Skip to content

Commit 0710afd

Browse files
committed
add handling for existing association links in product and resource publishers
1 parent 91af28a commit 0710afd

3 files changed

Lines changed: 37 additions & 3 deletions

File tree

src/clients/apim-client.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,16 @@ export class HttpError extends Error {
3131
}
3232
}
3333

34+
/**
35+
* Returns true when the error indicates an association link already exists.
36+
* APIM returns HTTP 409 Conflict ("Link already exists between specified ...")
37+
* when an association is re-created; the desired end state is already in place,
38+
* so callers treat it as an idempotent success rather than a failure.
39+
*/
40+
export function isLinkAlreadyExistsError(error: unknown): boolean {
41+
return error instanceof HttpError && error.status === 409;
42+
}
43+
3444
export class ApimClient implements IApimClient {
3545
private credential: DefaultAzureCredential;
3646
private readonly authScope: string;

src/services/product-publisher.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { logger } from '../lib/logger.js';
1515
import { getNamePart } from '../lib/resource-path.js';
1616
import { parseArmUri } from '../lib/resource-uri.js';
1717
import { isWorkspaceScope, buildLinkPayload } from '../lib/workspace-link.js';
18+
import { isLinkAlreadyExistsError } from '../clients/apim-client.js';
1819

1920
/**
2021
* Publish a Product with all its associations (APIs, Groups, Tags).
@@ -206,6 +207,10 @@ async function publishProductAssociations(
206207
await client.putResource(context, assocDescriptor, payload);
207208
logger.debug(`Created ${resourceType} association: ${productName}/${name}`);
208209
} catch (error) {
210+
if (isLinkAlreadyExistsError(error)) {
211+
logger.debug(`${resourceType} association already exists: ${productName}/${name}`);
212+
continue;
213+
}
209214
logger.warn(`Failed to create ${resourceType} association ${productName}/${name}: ${String(error)}`);
210215
}
211216
}
@@ -259,6 +264,10 @@ async function publishProductTags(
259264
await client.putResource(context, tagDescriptor, payload);
260265
logger.debug(`Created ProductTag association: ${productName}/${tagName}`);
261266
} catch (error) {
267+
if (isLinkAlreadyExistsError(error)) {
268+
logger.debug(`ProductTag association already exists: ${productName}/${tagName}`);
269+
continue;
270+
}
262271
logger.warn(`Failed to create ProductTag association ${productName}/${tagName}: ${String(error)}`);
263272
}
264273
}

src/services/resource-publisher.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { isAutoGeneratedId } from '../lib/auto-generated.js';
1919
import { isWorkspaceScope, buildLinkPayload } from '../lib/workspace-link.js';
2020
import { logger } from '../lib/logger.js';
2121
import { REDACTION_MARKER } from './secret-redactor.js';
22+
import { isLinkAlreadyExistsError } from '../clients/apim-client.js';
2223
import type { OverrideConfig } from '../models/config.js';
2324

2425
export interface ResourcePublishResult {
@@ -308,8 +309,15 @@ async function publishAssociation(
308309
type: descriptor.type,
309310
nameParts: [getNamePart(descriptor.nameParts, 0), entry.name],
310311
};
311-
// PUT empty body for association (APIM uses PUT to create association)
312-
await client.putResource(context, assocDescriptor, {});
312+
try {
313+
// PUT empty body for association (APIM uses PUT to create association)
314+
await client.putResource(context, assocDescriptor, {});
315+
} catch (error) {
316+
// 409 means the link already exists — desired state is in place.
317+
if (!isLinkAlreadyExistsError(error)) {
318+
throw error;
319+
}
320+
}
313321
}
314322

315323
return {
@@ -616,7 +624,14 @@ async function publishWorkspaceApiTagLink(
616624
}
617625
const payload = buildLinkPayload(context, meta.workspaceLinkIdProperty, 'apis', apiName, descriptor.workspace);
618626

619-
await client.putResource(context, descriptor, payload);
627+
try {
628+
await client.putResource(context, descriptor, payload);
629+
} catch (error) {
630+
// 409 means the tag/api link already exists — desired state is in place.
631+
if (!isLinkAlreadyExistsError(error)) {
632+
throw error;
633+
}
634+
}
620635

621636
return {
622637
descriptor,

0 commit comments

Comments
 (0)