Azure / Azure/typespec-azure

resolveArmResources default legacy resource names can collide for nested resources

Open
#5,251 1 comment 0 reactions 3 assignees Claimed by @catalinaperalta View on GitHub
design:needed lib:azure-resource-manager triaged:core
Dominant language
TypeScript
Stars
27
Forks
90
Avg merge
1d 22h
Merged PRs (30d)
156

Description

## Summary

`resolveArmResources` can assign the same default `resourceName` to two distinct legacy ARM resources when their resource type paths share the same last two segments.

This preserves both resource identities, but downstream SDK emitters that use `resourceName` as a generated type name can collide and effectively drop one generated resource type.

## Observed case

Found while validating `@azure-tools/typespec-azure-resource-manager` `0.72.0-dev.1` in Azure/azure-sdk-for-net provisioning generation.

Compute has the same resource model, `VirtualMachineRunCommand`, reused under two different parent paths:

| ARM resource type | Expected distinct generated resource | Current default `resourceName` |
| --- | --- | --- |
| `Microsoft.Compute/virtualMachines/runCommands` | VM run command resource | `VirtualMachinesRunCommands` |
| `Microsoft.Compute/virtualMachineScaleSets/virtualMachines/runCommands` | VMSS VM run command resource | `VirtualMachinesRunCommands` |

Both resource projections are present in the provisioning emitter input (`tspCodeModel.json`), but both carry `resourceName = VirtualMachinesRunCommands`. The provisioning C# generator uses `resourceName` as the type/file name, so the two projections collide and only one `VirtualMachinesRunCommands.cs` remains.

## Root cause

The default legacy resource-name fallback uses only the final two resource type segments:

```ts
function getDefaultLegacyResourceName(operation: ArmResourceOperationData, httpOp: string): string {
const pathInfo = parseArmResourceInstancePath(httpOp);
if (pathInfo !== undefined) {
let types: string[] = pathInfo.resourceType.types;
if (types.length > 1) {
types = types.slice(types.length - 2);
}
return types.flatMap((t) => pascalCase(t)).join("");
} else {
return operation.resourceModelName;
}
}
```

For Compute:

- `virtualMachines/runCommands` -> `VirtualMachinesRunCommands`
- `virtualMachineScaleSets/virtualMachines/runCommands` -> last two segments are also `virtualMachines/runCommands` -> `VirtualMachinesRunCommands`

## Impact

Downstream emitters receive distinct resource identities but non-distinct names. Any emitter using `ResolvedResource.resourceName` as a type name or key can generate duplicate/colliding declarations.

## Expected behavior

Default resource names should be unique for distinct resource identities, or the API should expose enough disambiguation metadata for emitters to safely generate unique names.

Possible directions:

- Include more parent path context when the last-two-segment fallback collides.
- Preserve a collision-safe default name for legacy resources based on the full nested type path.
- Expose both the raw model/resource name and a collision-safe suggested name.

## Related validation

A focused resource identity test was added in Azure/typespec-azure#5247. That test verifies both resource paths are preserved; this issue is specifically about the duplicate `resourceName` assigned to those preserved identities.

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.