Skip to content

[ACR] Fix #33660: az acr network-rule list: Fix null virtualNetworkResourceId and add virtualNetworkSubnetResourceId#33685

Open
johnsonshi wants to merge 1 commit into
Azure:devfrom
johnsonshi:johsh/fix-33660-acr-vnet-subnet-id
Open

[ACR] Fix #33660: az acr network-rule list: Fix null virtualNetworkResourceId and add virtualNetworkSubnetResourceId#33685
johnsonshi wants to merge 1 commit into
Azure:devfrom
johnsonshi:johsh/fix-33660-acr-vnet-subnet-id

Conversation

@johnsonshi

@johnsonshi johnsonshi commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Fixes #33660

Related command

az acr network-rule list (root cause also affects az acr network-rule add and az acr network-rule remove)

Description

az acr network-rule list returned every virtual network rule with virtualNetworkResourceId: null, and the expected virtualNetworkSubnetResourceId field was missing (reported on 2.85.0 / 2.86.0 / 2.87.0).

Root cause: network_rule.py talks to the registry REST API directly (the networkRuleSet.virtualNetworkRules property is not modeled in the current azure-mgmt-containerregistry SDK, so the module is pinned to 2021-08-01-preview). The code read the subnet resource ID from rule.get('id'), but the API returns it under the key virtualNetworkSubnetResourceId. As a result:

  • list / add emitted virtualNetworkResourceId: null (the id key does not exist in the response).
  • remove filtered on x.get('id'), which was always None, so a matching rule was never actually removed.

I verified the real field name directly against the ARM REST API (GET/PATCH on a Premium registry). The virtualNetworkRules array is exposed by 2021-08-01-preview (the current pin) and, after being absent from the intervening API versions, is present again in the latest 2026-03-01-preview; in both versions the subnet id is returned under virtualNetworkSubnetResourceId:

API version virtualNetworkRules subnet id key
2021-08-01-preview (CLI pin) present virtualNetworkSubnetResourceId
2021-09-01 … 2026-01-01-preview absent
2026-03-01-preview (latest) present virtualNetworkSubnetResourceId

Changes (src/azure-cli/azure/cli/command_modules/acr/network_rule.py):

  • _format_registry_response now reads virtualNetworkSubnetResourceId (falling back to id for older response shapes) and surfaces both virtualNetworkResourceId (kept for backward compatibility with existing scripts) and virtualNetworkSubnetResourceId in the output.
  • acr_network_rule_add sends the subnet under virtualNetworkSubnetResourceId (the API accepts it and echoes it back).
  • acr_network_rule_remove matches on virtualNetworkSubnetResourceId (fallback id) so rules are removed correctly.

Resulting behavior:

// Before
"virtualNetworkRules": [{ "action": "Allow", "virtualNetworkResourceId": null }]

// After
"virtualNetworkRules": [{
  "action": "Allow",
  "virtualNetworkResourceId": "/subscriptions/.../subnets/acrsubnet",
  "virtualNetworkSubnetResourceId": "/subscriptions/.../subnets/acrsubnet"
}]

The pinned API version is intentionally left unchanged — this is a surgical fix for the field mapping. Reading virtualNetworkSubnetResourceId is forward-compatible with the newer API version above.

Out of scope / future work (service endpoint GA): The virtual-network (service endpoint) rules capability is being re-introduced in newer preview APIs — it is present again in 2026-03-01-preview — and is progressing toward a future GA. Bumping this module's pinned API version (or, once azure-mgmt-containerregistry models networkRuleSet.virtualNetworkRules again, migrating these calls back to the SDK) is best done as part of that GA work and is intentionally out of scope for this bug fix. The fix is version-independent: virtualNetworkSubnetResourceId is identical on the pinned 2021-08-01-preview and on 2026-03-01-preview, so it holds across a future bump.

Field naming and compatibility (virtualNetworkResourceId vs virtualNetworkSubnetResourceId)

The output intentionally carries both keys, both populated with the same subnet resource ID. This is deliberate for forward and backward compatibility, and it lines up with how the property is encoded in the API:

  • In the 2021-08-01-preview swagger, the property is the wire field id with x-ms-client-name: VirtualNetworkResourceId, and its description is literally "Resource ID of a subnet, for example: /subscriptions/{…}/virtualNetworks/{vnetName}/subnets/{subnetName}." In other words, virtualNetworkResourceId is a documented misnomer — the name says "virtual network", but the value has always been a subnet id:

    "id": {
      "description": "Resource ID of a subnet, for example: /subscriptions/{…}/virtualNetworks/{vnetName}/subnets/{subnetName}.",
      "type": "string",
      "x-ms-client-name": "VirtualNetworkResourceId"
    }
  • The deployed service — and the re-GA 2026-03-01-preview — returns that same subnet id under the accurately-named virtualNetworkSubnetResourceId. So the CLI surfaces both: virtualNetworkSubnetResourceId (correct, matches the current/re-GA API — forward compatible) and virtualNetworkResourceId (the name the CLI has always emitted — backward compatible).

  • Why not just replace it? virtualNetworkResourceId is the existing output contract: user scripts, --query expressions, and the module's own scenario-test assertions read it. Dropping it or renaming it would be a breaking change for anyone parsing az acr network-rule list output today, for no functional gain. Emitting both keeps existing consumers working while giving new consumers the correctly-named field.

How the commands read/write the property (code walkthrough)

All three subcommands go through two thin REST helpers in network_rule.py, both targeting .../registries/{name}?api-version=2021-08-01-preview:

  • _get_registry(...)send_raw_request(cli_ctx, "GET", url) — reads the registry JSON.
  • _update_registry(...)send_raw_request(cli_ctx, "PATCH", url, body=...) — writes back a {"properties": {"networkRuleSet": ...}} payload.

add and remove are read-modify-write: GET the current networkRuleSet, mutate the virtualNetworkRules list in memory, PATCH the whole set back, then format the PATCH response with the same read path as list.

  1. az acr network-rule list — read path

    • _get_registry GETs the registry, then _format_registry_response runs _format_virtual_network_rule on each entry.
    • Each response entry looks like {"action": "Allow", "virtualNetworkSubnetResourceId": "<subnet-id>"}. The helper does subnet_id = rule.get('virtualNetworkSubnetResourceId') or rule.get('id') and emits both virtualNetworkResourceId and virtualNetworkSubnetResourceId.
    • Before the fix: it read rule.get('id') — a key the response does not contain — so virtualNetworkResourceId came back null.
  2. az acr network-rule add — write, then read

    • GET current rules, then append using the request key the service expects: virtual_network_rules.append({'virtualNetworkSubnetResourceId': subnet_id, 'action': 'Allow'}).
    • PATCH the whole networkRuleSet, then format the PATCH response (same read path as list).
    • The service accepts either id or virtualNetworkSubnetResourceId on the request but always echoes virtualNetworkSubnetResourceId; sending the same key we read keeps write/read symmetric.
  3. az acr network-rule remove — read, filter, write

    • GET current rules, drop the entry whose subnet id matches — (x.get('virtualNetworkSubnetResourceId') or x.get('id') or '').lower() != subnet_id — then PATCH the result.
    • Before the fix: it filtered on x.get('id') (always None), so nothing ever matched and the rule was never removed.

Request/response shape (2021-08-01-preview):

// PATCH request body sent by `add` (service also accepts "id" here)
{ "properties": { "networkRuleSet": {
    "defaultAction": "Deny",
    "virtualNetworkRules": [
      { "action": "Allow", "virtualNetworkSubnetResourceId": "/subscriptions/.../subnets/acrsubnet" }
    ],
    "ipRules": []
}}}

// GET/PATCH response body the service returns (what list/add/remove parse)
{ "properties": { "networkRuleSet": {
    "defaultAction": "Deny",
    "virtualNetworkRules": [
      { "action": "Allow", "virtualNetworkSubnetResourceId": "/subscriptions/.../subnets/acrsubnet" }
    ],
    "ipRules": []
}}}

Testing Guide

Automated: test_acr_network_rule (AcrNetworkRuleCommandsTests).

  • Passed live against real Azure resources: azdev test test_acr_network_rule --live1 passed in 68.27s. The scenario creates a VNet + subnet with a Microsoft.ContainerRegistry service endpoint, a Premium registry with --default-action Deny, then adds/lists/removes VNet and IP rules and asserts both virtualNetworkResourceId and the new virtualNetworkSubnetResourceId equal the subnet id. The HTTP recording was regenerated from this live run so CI playback exercises the current API field name.
  • Passed in playback: azdev test test_acr_network_rule1 passed in 6.27s.
  • azdev style acr and azdev linter acr both PASSED.

Live validation transcript (actual runs; registry/VNet/subnet names genericized and subscription id scrubbed to 00000000-…).

(1) Raw ARM ground truth — why the old code failed. Send the rule under id (exactly what the pre-fix CLI sent) and read back the raw response:

az rest --method patch \
  --url "https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myrg/providers/Microsoft.ContainerRegistry/registries/myacr?api-version=2021-08-01-preview" \
  --headers "Content-Type=application/json" \
  --body '{"properties":{"networkRuleSet":{"defaultAction":"Deny","virtualNetworkRules":[{"id":"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myrg/providers/Microsoft.Network/virtualNetworks/myvnet/subnets/mysubnet","action":"Allow"}],"ipRules":[]}}}' \
  --query "properties.networkRuleSet"
{
  "defaultAction": "Deny",
  "ipRules": [],
  "virtualNetworkRules": [
    {
      "action": "Allow",
      "virtualNetworkSubnetResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myrg/providers/Microsoft.Network/virtualNetworks/myvnet/subnets/mysubnet"
    }
  ]
}

The request used id, but the service stored and returned virtualNetworkSubnetResourceId — never id. The pre-fix CLI read rule.get('id'), hence the null.

(2) End-to-end with the fixed CLI against a Premium registry (--default-action Deny) whose subnet has a Microsoft.ContainerRegistry service endpoint:

$ az acr network-rule add -g myrg -n myacr --vnet-name myvnet --subnet mysubnet
WARNING: Argument 'vnet_name' has been deprecated and will be removed in a future release.
WARNING: Argument 'subnet' has been deprecated and will be removed in a future release.
// networkRuleSet.virtualNetworkRules from the add response
[
  {
    "action": "Allow",
    "virtualNetworkResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myrg/providers/Microsoft.Network/virtualNetworks/myvnet/subnets/mysubnet",
    "virtualNetworkSubnetResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myrg/providers/Microsoft.Network/virtualNetworks/myvnet/subnets/mysubnet"
  }
]
$ az acr network-rule list -g myrg -n myacr
{
  "ipRules": [],
  "virtualNetworkRules": [
    {
      "action": "Allow",
      "virtualNetworkResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myrg/providers/Microsoft.Network/virtualNetworks/myvnet/subnets/mysubnet",
      "virtualNetworkSubnetResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myrg/providers/Microsoft.Network/virtualNetworks/myvnet/subnets/mysubnet"
    }
  ]
}

Both keys are populated with the subnet id (pre-fix: virtualNetworkResourceId: null, no virtualNetworkSubnetResourceId). Then remove actually deletes the rule (pre-fix: silent no-op):

$ az acr network-rule remove -g myrg -n myacr --vnet-name myvnet --subnet mysubnet
$ az acr network-rule list -g myrg -n myacr
{ "ipRules": [], "virtualNetworkRules": [] }

History Notes

[ACR] az acr network-rule list/add/remove: Fix virtualNetworkRules entries returning virtualNetworkResourceId as null and surface the virtualNetworkSubnetResourceId field


This checklist is used to make sure that common guidelines for a pull request are followed.

…Rules showing null subnet id

The ACR registry REST API returns the virtual network rule's subnet
resource ID under the `virtualNetworkSubnetResourceId` key (confirmed
against the live 2021-08-01-preview pin and the 2026-03-01-preview
re-GA API). The CLI read the stale `id` key, so `az acr network-rule
list/add` emitted `virtualNetworkResourceId: null`, and
`az acr network-rule remove` never matched a rule to delete.

- _format_registry_response: read `virtualNetworkSubnetResourceId`
  (fallback `id`) and surface both `virtualNetworkResourceId` (kept for
  backward compat) and `virtualNetworkSubnetResourceId`.
- acr_network_rule_add: send `virtualNetworkSubnetResourceId`.
- acr_network_rule_remove: match on `virtualNetworkSubnetResourceId`
  (fallback `id`) so rules are actually removed.
- Update scenario test assertions and regenerate the recording to
  reflect the current API field name.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 1, 2026 02:24
@johnsonshi johnsonshi requested review from a team as code owners July 1, 2026 02:24
@azure-client-tools-bot-prd

azure-client-tools-bot-prd Bot commented Jul 1, 2026

Copy link
Copy Markdown
️✔️AzureCLI-FullTest
️✔️acr
️✔️latest
️✔️3.12
️✔️3.14
️✔️acs
️✔️latest
️✔️3.12
️✔️3.14
️✔️advisor
️✔️latest
️✔️3.12
️✔️3.14
️✔️ams
️✔️latest
️✔️3.12
️✔️3.14
️✔️apim
️✔️latest
️✔️3.12
️✔️3.14
️✔️appconfig
️✔️latest
️✔️3.12
️✔️3.14
️✔️appservice
️✔️latest
️✔️3.12
️✔️3.14
️✔️aro
️✔️latest
️✔️3.12
️✔️3.14
️✔️backup
️✔️latest
️✔️3.12
️✔️3.14
️✔️batch
️✔️latest
️✔️3.12
️✔️3.14
️✔️batchai
️✔️latest
️✔️3.12
️✔️3.14
️✔️billing
️✔️latest
️✔️3.12
️✔️3.14
️✔️botservice
️✔️latest
️✔️3.12
️✔️3.14
️✔️cloud
️✔️latest
️✔️3.12
️✔️3.14
️✔️cognitiveservices
️✔️latest
️✔️3.12
️✔️3.14
️✔️compute_recommender
️✔️latest
️✔️3.12
️✔️3.14
️✔️computefleet
️✔️latest
️✔️3.12
️✔️3.14
️✔️config
️✔️latest
️✔️3.12
️✔️3.14
️✔️configure
️✔️latest
️✔️3.12
️✔️3.14
️✔️consumption
️✔️latest
️✔️3.12
️✔️3.14
️✔️container
️✔️latest
️✔️3.12
️✔️3.14
️✔️containerapp
️✔️latest
️✔️3.12
️✔️3.14
️✔️core
️✔️latest
️✔️3.12
️✔️3.14
️✔️cosmosdb
️✔️latest
️✔️3.12
️✔️3.14
️✔️databoxedge
️✔️latest
️✔️3.12
️✔️3.14
️✔️dls
️✔️latest
️✔️3.12
️✔️3.14
️✔️dms
️✔️latest
️✔️3.12
️✔️3.14
️✔️eventgrid
️✔️latest
️✔️3.12
️✔️3.14
️✔️eventhubs
️✔️latest
️✔️3.12
️✔️3.14
️✔️feedback
️✔️latest
️✔️3.12
️✔️3.14
️✔️find
️✔️latest
️✔️3.12
️✔️3.14
️✔️hdinsight
️✔️latest
️✔️3.12
️✔️3.14
️✔️identity
️✔️latest
️✔️3.12
️✔️3.14
️✔️iot
️✔️latest
️✔️3.12
️✔️3.14
️✔️keyvault
️✔️latest
️✔️3.12
️✔️3.14
️✔️lab
️✔️latest
️✔️3.12
️✔️3.14
️✔️managedservices
️✔️latest
️✔️3.12
️✔️3.14
️✔️maps
️✔️latest
️✔️3.12
️✔️3.14
️✔️marketplaceordering
️✔️latest
️✔️3.12
️✔️3.14
️✔️monitor
️✔️latest
️✔️3.12
️✔️3.14
️✔️mysql
️✔️latest
️✔️3.12
️✔️3.14
️✔️netappfiles
️✔️latest
️✔️3.12
️✔️3.14
️✔️network
️✔️latest
️✔️3.12
️✔️3.14
️✔️policyinsights
️✔️latest
️✔️3.12
️✔️3.14
️✔️postgresql
️✔️latest
️✔️3.12
️✔️3.14
️✔️privatedns
️✔️latest
️✔️3.12
️✔️3.14
️✔️profile
️✔️latest
️✔️3.12
️✔️3.14
️✔️rdbms
️✔️latest
️✔️3.12
️✔️3.14
️✔️redis
️✔️latest
️✔️3.12
️✔️3.14
️✔️relay
️✔️latest
️✔️3.12
️✔️3.14
️✔️resource
️✔️latest
️✔️3.12
️✔️3.14
️✔️role
️✔️latest
️✔️3.12
️✔️3.14
️✔️search
️✔️latest
️✔️3.12
️✔️3.14
️✔️security
️✔️latest
️✔️3.12
️✔️3.14
️✔️servicebus
️✔️latest
️✔️3.12
️✔️3.14
️✔️serviceconnector
️✔️latest
️✔️3.12
️✔️3.14
️✔️servicefabric
️✔️latest
️✔️3.12
️✔️3.14
️✔️signalr
️✔️latest
️✔️3.12
️✔️3.14
️✔️sql
️✔️latest
️✔️3.12
️✔️3.14
️✔️sqlvm
️✔️latest
️✔️3.12
️✔️3.14
️✔️storage
️✔️latest
️✔️3.12
️✔️3.14
️✔️synapse
️✔️latest
️✔️3.12
️✔️3.14
️✔️telemetry
️✔️latest
️✔️3.12
️✔️3.14
️✔️util
️✔️latest
️✔️3.12
️✔️3.14
️✔️vm
️✔️latest
️✔️3.12
️✔️3.14

@azure-client-tools-bot-prd

azure-client-tools-bot-prd Bot commented Jul 1, 2026

Copy link
Copy Markdown
️✔️AzureCLI-BreakingChangeTest
️✔️Non Breaking Changes

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes ACR az acr network-rule list/add/remove to correctly read/write virtual network rule subnet IDs when using direct REST calls (since networkRuleSet.virtualNetworkRules isn’t modeled in the current SDK for the pinned API version).

Changes:

  • Normalize virtual network rule parsing to read virtualNetworkSubnetResourceId (fallback to legacy id) and surface both virtualNetworkResourceId (back-compat) and virtualNetworkSubnetResourceId in CLI output.
  • Update acr network-rule add to send the subnet under virtualNetworkSubnetResourceId, and acr network-rule remove to match/remove rules using that key (fallback id).
  • Extend the live/playback scenario test to assert virtualNetworkSubnetResourceId is present and correct.

Reviewed changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated no comments.

File Description
src/azure-cli/azure/cli/command_modules/acr/network_rule.py Fixes field mapping for VNet rules in REST payloads/responses and ensures remove matches the correct key (with legacy fallback).
src/azure-cli/azure/cli/command_modules/acr/tests/latest/test_acr_network_rule_commands.py Adds assertions to cover the new/expected virtualNetworkSubnetResourceId output in both registry and list command responses.
src/azure-cli/HISTORY.rst Documents the behavior fix in the release history.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@yonzhan

yonzhan commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

ACR

@johnsonshi

Copy link
Copy Markdown
Contributor Author

This PR will solve #33660 and will close #33663

@yonzhan yonzhan assigned yanzhudd and unassigned necusjz Jul 1, 2026
@yonzhan yonzhan removed Network az network vnet/lb/nic/dns/etc... Auto-Assign Auto assign by bot act-quality-productivity-squad labels Jul 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

az acr network-rule list missing virtualNetworkSubnetResourceId

5 participants