Command that triggered the bug
extract / publish
Expected behavior
Summary
APIOps captures and publishes API↔gateway assignments only for self-hosted / custom
gateways (the ones returned by GET /service/gateways). The built-in managed gateway
(id managed) is never extracted, so its per-API membership is not represented in
artifacts and is never reconciled on publish. As a result the source and target diverge
whenever an API's managed-gateway membership differs from the target default.
Expected behavior
The target API's gateway set matches the source of truth. Given the source (dev) API is
assigned to a self-hosted gateway only (explicitly removed from the managed gateway):
Gateways: my-selfhosted-gateway
publishing the extracted artifacts should reproduce exactly that set on the target —
self-hosted only, not on the managed gateway.
Actual behavior
Actual behavior
After extract (self-hosted only) + publish to a fresh target, the API ends up on both
gateways, because APIM auto-adds a newly created API to the managed gateway and APIOps
never removes it:
Gateways: Managed my-selfhosted-gateway
The self-hosted assignment is correct; the extra Managed assignment is silent drift
that APIOps cannot currently express or reconcile.
Root cause
extract iterates gateways via GET /service/gateways, which returns only self-hosted
gateways. The managed gateway is not in that list, so no gateways/managed/apis.json
artifact is produced.
publish only adds associations from artifacts; it has no desired-state record for
the managed gateway, so it cannot remove an API from managed to match the source.
Impact
- dev→prod config drift for any API whose managed-gateway membership is not the default
(e.g. APIs intentionally served only by a self-hosted/workspace gateway).
- The drift is silent — publish reports success while the gateway set does not match the
source of truth.
Possible approaches (for discussion before implementation)
This is a design decision because reconciling managed membership is opinionated and
involves DELETE behavior:
- Extract managed membership explicitly. Query
GET /service/gateways/managed/apis
directly (works even though managed is absent from the gateways list) and write
gateways/managed/apis.json. Publish then reconciles managed assignments like any
other gateway (add missing, and — under --delete-unmatched — remove extras).
- Caveat: on classic APIM most APIs are on managed by default, so the managed list can
be large; publishing it is mostly redundant except for the intentional removals.
- Record only the negative state (APIs explicitly removed from managed) and apply the
removal on publish. Smaller footprint but a new artifact concept.
- Document as a known limitation and require a manual step for the removal case:
az rest --method delete --url ".../service/{svc}/gateways/managed/apis/{api}?api-version=2024-05-01"
Current workaround
Remove the API from the managed gateway on the target manually:
az rest --method delete \
--url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.ApiManagement/service/{svc}/gateways/managed/apis/{api}?api-version=2024-05-01"
apiops CLI version
1.0.0
Environment details
Azure/apiops-cli main
- Commands:
extract, publish
CI/CD environment
None
Is this bug blocking you?
None
Command that triggered the bug
extract / publish
Expected behavior
Summary
APIOps captures and publishes API↔gateway assignments only for self-hosted / custom
gateways (the ones returned by
GET /service/gateways). The built-in managed gateway(id
managed) is never extracted, so its per-API membership is not represented inartifacts and is never reconciled on publish. As a result the source and target diverge
whenever an API's managed-gateway membership differs from the target default.
Expected behavior
The target API's gateway set matches the source of truth. Given the source (dev) API is
assigned to a self-hosted gateway only (explicitly removed from the managed gateway):
publishing the extracted artifacts should reproduce exactly that set on the target —
self-hosted only, not on the managed gateway.
Actual behavior
Actual behavior
After extract (self-hosted only) + publish to a fresh target, the API ends up on both
gateways, because APIM auto-adds a newly created API to the managed gateway and APIOps
never removes it:
The self-hosted assignment is correct; the extra Managed assignment is silent drift
that APIOps cannot currently express or reconcile.
Root cause
extractiterates gateways viaGET /service/gateways, which returns only self-hostedgateways. The managed gateway is not in that list, so no
gateways/managed/apis.jsonartifact is produced.
publishonly adds associations from artifacts; it has no desired-state record forthe managed gateway, so it cannot remove an API from managed to match the source.
Impact
(e.g. APIs intentionally served only by a self-hosted/workspace gateway).
source of truth.
Possible approaches (for discussion before implementation)
This is a design decision because reconciling managed membership is opinionated and
involves DELETE behavior:
GET /service/gateways/managed/apisdirectly (works even though
managedis absent from the gateways list) and writegateways/managed/apis.json. Publish then reconciles managed assignments like anyother gateway (add missing, and — under
--delete-unmatched— remove extras).be large; publishing it is mostly redundant except for the intentional removals.
removal on publish. Smaller footprint but a new artifact concept.
Current workaround
Remove the API from the managed gateway on the target manually:
apiops CLI version
1.0.0
Environment details
Azure/apiops-climainextract,publishCI/CD environment
None
Is this bug blocking you?
None