Azure / Azure/azure-cli

az resource commands use default subscription instead of resource's subscription for API version lookup

Open
#32,454 1 comment 0 reactions 2 assignees Claimed by @yanzhudd View on GitHub
act-identity-squad ARM Auto-Assign Azure CLI Team question
Dominant language
Python
Stars
4.6k
Forks
3.5k
Avg merge
3d 2h
Merged PRs (30d)
60

Description

### Describe the bug

When using `az resource` commands with the `--ids` parameter, the commands incorrectly use the default subscription (or fail if none is set) to determine the API version, instead of using the subscription specified in the resource ID. This affects all `az resource` commands that support the `--ids` parameter.

**Two manifestations of the same bug:**

1. **When default subscription differs from resource's subscription:**
The command queries the wrong subscription's provider information for API version determination. This can cause failures or incorrect behavior when the resource uses a preview API version that's only available in its subscription (due to feature flags or allowlisting), but not in the default subscription.

2. **When no default subscription is set:**
The command fails completely with "No subscription found. Run 'az account set' to select a subscription" even though the resource ID contains all necessary information including the subscription.

**Affected commands:**
- `az resource show`
- `az resource create`
- `az resource delete`
- `az resource update`
- `az resource patch`
- `az resource tag`
- `az resource invoke-action`

### Related command

az resource show
az resource create
az resource delete
az resource update
az resource patch
az resource tag
az resource invoke-action

### Errors

### Scenario 1: No default subscription set

```
No subscription found. Run 'az account set' to select a subscription.
```

### Scenario 2: Feature-flagged API version

When the resource uses a preview API version only available in its subscription:

```
The resource type 'resourceType' could not be found in the namespace 'Microsoft.Provider' for api version '...'
```

Or the command may silently use an incorrect/incompatible API version.

### Issue script & Debug output

```bash
# Scenario 1: No default subscription set
# (Can occur if user hasn't run 'az account set' or manually modified azureProfile.json)
az resource show --ids /subscriptions/sub-B/resourceGroups/myRG/providers/Microsoft.Compute/virtualMachines/myVM
# Error: No subscription found. Run 'az account set' to select a subscription.
# Expected: Should work using sub-B from the resource ID

# Scenario 2: Default subscription differs from resource subscription
az account set --subscription sub-A
az resource show --ids /subscriptions/sub-B/resourceGroups/myRG/providers/Microsoft.Compute/virtualMachines/myVM
# Queries sub-A's provider information instead of sub-B's
# May fail or use wrong API version if sub-B has different available API versions (e.g., feature-flagged APIs)
```

### Expected behavior

When `az resource` commands are called with a resource ID via `--ids`, they should:

1. Parse the subscription ID from the resource ID
2. Create a resource client for that specific subscription
3. Query that subscription's provider information to determine the correct API version
4. Execute the command using the correct subscription context

The commands should work correctly regardless of whether:
- A default subscription is set or not
- The resource's subscription matches the default subscription

The resource ID contains all necessary information, so no default subscription should be required.

### Environment Summary

azure-cli 2.80.0

core 2.80.0
telemetry 1.1.0

Dependencies:
msal 1.34.0b1
azure-mgmt-resource 23.3.0

Python location '/opt/az/bin/python3'
Config directory '/tmp/tmp.DbT0g2h54k'
Extensions directory '/tmp/tmp.DbT0g2h54k/cliextensions'

Python (Linux) 3.13.9 (main, Nov 11 2025, 12:31:34) [GCC 11.4.0]

Legal docs and information: aka.ms/AzureCliLegal

Your CLI is up-to-date.

### Additional context

## Root Cause

**File:** `src/azure-cli/azure/cli/command_modules/resource/custom.py`

The bug occurs in two places:

1. **`_ResourceUtils.__init__` (line ~3776):** Creates the resource client factory without using the subscription from the resource ID:
```python
self.rcf = rcf or _resource_client_factory(cli_ctx) # Uses default subscription
```

2. **`_resolve_api_version_by_id` (line ~4030):** Parses the subscription from the resource ID but doesn't use it to query provider information:
```python
parts = parse_resource_id(resource_id) # Extracts subscription
# ... but then uses rcf created with default subscription:
return _ResourceUtils.resolve_api_version(rcf, namespace, parent, resource_type, ...)
```

3. **`resolve_api_version` (line ~4007):** Queries the provider using whatever subscription the rcf was created with:
```python
provider = rcf.providers.get(resource_provider_namespace) # Wrong subscription!
```

## Suggested Fix

**1. Update `_resource_client_factory` to accept `subscription_id` parameter:**

File: `src/azure-cli/azure/cli/command_modules/resource/_client_factory.py`
```python
def _resource_client_factory(cli_ctx, subscription_id=None, **_):
from azure.cli.core.commands.client_factory import get_mgmt_service_client
from azure.cli.core.profiles import ResourceType
return get_mgmt_service_client(cli_ctx, ResourceType.MGMT_RESOURCE_RESOURCES,
subscription_id=subscription_id)
```

**2. Update `_ResourceUtils.__init__` to use subscription from resource ID:**

File: `src/azure-cli/azure/cli/command_modules/resource/custom.py`
```python
self.rcf = rcf
if not self.rcf:
# If we have a resource_id, extract the subscription and use it for the client factory
subscription_id_for_client = None
if resource_id:
parsed_resource_id = parse_resource_id(resource_id)
subscription_id_for_client = parsed_resource_id.get('subscription')
self.rcf = _resource_client_factory(cli_ctx, subscription_id=subscription_id_for_client)
```

**3. Update `_resolve_api_version_by_id` to handle subscription comparison:**

```python
@staticmethod
def _resolve_api_version_by_id(rcf, resource_id, latest_include_preview=False, cli_ctx=None):
parts = parse_resource_id(resource_id)
# ... existing parsing logic ...

# Use the subscription from the resource ID if different from default
resource_subscription = parts.get('subscription')
if resource_subscription and cli_ctx:
try:
default_subscription = get_subscription_id(cli_ctx)
except Exception: # pylint: disable=broad-except
# No default subscription set
default_subscription = None
if resource_subscription != default_subscription:
rcf = _resource_client_factory(cli_ctx, subscription_id=resource_subscription)

return _ResourceUtils.resolve_api_version(rcf, namespace, parent, resource_type,
latest_include_preview=latest_include_preview)
```

**4. Update call site to pass `cli_ctx`:**

```python
api_version = _ResourceUtils._resolve_api_version_by_id(self.rcf, resource_id,
latest_include_preview=latest_include_preview,
cli_ctx=cli_ctx)
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.