Command that triggered the bug
apiops publish
Expected behavior
Summary
When publishing a Gateway→API (or Product→API/Group) association, publishAssociation
throws on the first link whose referenced API/group does not exist on the target,
which aborts the whole association loop. As a result, APIs that are present on the
target never get linked to the gateway/product — a single missing reference silently
drops all remaining valid associations.
Affected component
- Repository:
Azure/apiops-cli (main)
- File:
src/services/resource-publisher.ts
- Function:
publishAssociation (used for ProductApi, ProductGroup, GatewayApi)
Root cause
The per-entry catch only tolerates the "link already exists" (HTTP 409) case; any other
error — including APIM's HTTP 400 ValidationError … "API not found" / "Group not found"
— is re-thrown, which exits the for loop and marks the whole association failed:
for (const entry of entries) {
// ...
try {
await client.putResource(context, assocDescriptor, {}); // PUT gateways/{gw}/apis/{api}
} catch (error) {
// 409 means the link already exists — desired state is in place.
if (!isLinkAlreadyExistsError(error)) {
throw error; // ← a single missing API aborts the remaining links
}
}
}
The apis.json for a gateway lists every API the source gateway serves. If the publish
scope does not include all of them (e.g. an extraction filter, or an API that failed to
publish earlier in the same run), the association PUT for the missing API returns
API not found, and the loop aborts before linking the APIs that do exist.
Note the inconsistency: Product associations already log a warning and continue on a
missing API/group, while Gateway associations (and product associations that flow
through publishAssociation) abort. The behavior should be consistent.
Steps to reproduce
- Have a self-hosted (or workspace) gateway on the source with several APIs assigned.
- Extract with a filter that includes the gateway but only a subset of those APIs
(so gateways/{gw}/apis.json lists APIs that are not in the artifact set).
- Publish to a target.
- Observe the gateway association fails entirely.
Expected behavior
- Links for APIs/groups that exist on the target are created.
- A missing referenced API/group is skipped with a warning and does not abort the
remaining links.
Actual behavior
Actual behavior
ERROR NOOP gatewayapi/<gateway>: HTTP 400: {"error":{"code":"ValidationError",
"details":[{"target":"aid","message":"API not found"}]}}
No APIs are linked to the gateway — including the ones that were published successfully.
The API ends up served only by the managed gateway (which is implicit), so it looks like
"only one gateway survived."
Downstream impact
- Self-hosted / workspace gateway assignments silently disappear on publish whenever the
gateway's apis.json references any API outside the current publish scope.
- Cascades: SOAP or other APIs that fail to import earlier in the run also cause every
gateway link to be dropped, not just their own.
Suggested fix
Treat a missing referenced API/group as a per-entry skip (warning) instead of a fatal
error, mirroring the existing "link already exists" handling and the product-association
behavior:
} catch (error) {
if (isLinkAlreadyExistsError(error)) {
continue;
}
const message = error instanceof Error ? error.message : String(error);
if (message.includes('API not found') || message.includes('Group not found')) {
logger.warn(
`Skipping ${associationType} association '${entry.name}' on ` +
`'${getNamePart(descriptor.nameParts, 0)}': referenced resource not found on target`
);
continue;
}
throw error;
}
apiops CLI version
1.0.0
Environment details
- VSCode -
Devcontainer
Azure/apiops-cli main
- Command:
publish
CI/CD environment
None
Is this bug blocking you?
Yes
Command that triggered the bug
apiops publish
Expected behavior
Summary
When publishing a Gateway→API (or Product→API/Group) association,
publishAssociationthrows on the first link whose referenced API/group does not exist on the target,
which aborts the whole association loop. As a result, APIs that are present on the
target never get linked to the gateway/product — a single missing reference silently
drops all remaining valid associations.
Affected component
Azure/apiops-cli(main)src/services/resource-publisher.tspublishAssociation(used forProductApi,ProductGroup,GatewayApi)Root cause
The per-entry catch only tolerates the "link already exists" (HTTP 409) case; any other
error — including APIM's
HTTP 400 ValidationError … "API not found"/"Group not found"— is re-thrown, which exits the
forloop and marks the whole associationfailed:The
apis.jsonfor a gateway lists every API the source gateway serves. If the publishscope does not include all of them (e.g. an extraction filter, or an API that failed to
publish earlier in the same run), the association PUT for the missing API returns
API not found, and the loop aborts before linking the APIs that do exist.Note the inconsistency: Product associations already log a warning and continue on a
missing API/group, while Gateway associations (and product associations that flow
through
publishAssociation) abort. The behavior should be consistent.Steps to reproduce
(so
gateways/{gw}/apis.jsonlists APIs that are not in the artifact set).Expected behavior
remaining links.
Actual behavior
Actual behavior
No APIs are linked to the gateway — including the ones that were published successfully.
The API ends up served only by the managed gateway (which is implicit), so it looks like
"only one gateway survived."
Downstream impact
gateway's
apis.jsonreferences any API outside the current publish scope.gateway link to be dropped, not just their own.
Suggested fix
Treat a missing referenced API/group as a per-entry skip (warning) instead of a fatal
error, mirroring the existing "link already exists" handling and the product-association
behavior:
apiops CLI version
1.0.0
Environment details
DevcontainerAzure/apiops-climainpublishCI/CD environment
None
Is this bug blocking you?
Yes