-
Notifications
You must be signed in to change notification settings - Fork 72
Description
Description
resolveArmResources in @azure-tools/typespec-azure-resource-manager incorrectly merges cross-scope LegacyOperations that share the same model into a single resource, instead of separating them into distinct resources per scope.
This affects real-world patterns like the Support SDK, where SupportTicketDetails (decorated with @subscriptionResource) is used by both subscription-scoped (SupportTickets) and tenant-scoped (SupportTicketsNoSubscription) interfaces via LegacyOperations.
Reproduction
@subscriptionResource
model SupportTicketDetails is ProxyResource<SupportTicketProperties> {
...ResourceNameParameter<
Resource = SupportTicketDetails,
KeyName = "supportTicketName",
SegmentName = "supportTickets",
NamePattern = ""
>;
}
model SupportTicketProperties {
description?: string;
}
// Subscription-scoped operations (includes SubscriptionIdParameter)
alias SubTicketOps = Azure.ResourceManager.Legacy.LegacyOperations<
{ ...ApiVersionParameter; ...SubscriptionIdParameter; ...Azure.ResourceManager.Legacy.Provider; },
{ @segment("supportTickets") @key @TypeSpec.Http.path supportTicketName: string; }
>;
// Tenant-scoped operations (no SubscriptionIdParameter)
alias TenantTicketOps = Azure.ResourceManager.Legacy.LegacyOperations<
{ ...ApiVersionParameter; ...Azure.ResourceManager.Legacy.Provider; },
{ @segment("supportTickets") @key @TypeSpec.Http.path supportTicketName: string; }
>;
@armResourceOperations
interface SupportTickets {
get is SubTicketOps.Read<SupportTicketDetails>;
list is SubTicketOps.List<SupportTicketDetails>;
}
@armResourceOperations
interface SupportTicketsNoSubscription {
get is TenantTicketOps.Read<SupportTicketDetails>;
list is TenantTicketOps.List<SupportTicketDetails>;
}Expected behavior
resolveArmResources should produce 2 separate resources (one per scope):
- Subscription-scoped resource with
resourceInstancePath: "/subscriptions/{subscriptionId}/providers/.../supportTickets/{supportTicketName}",scope: "Subscription", and its own lifecycle read + list operations - Tenant-scoped resource with
resourceInstancePath: "/providers/.../supportTickets/{supportTicketName}",scope: "Tenant", and its own lifecycle read + list operations
Actual behavior
resolveArmResources returns 1 merged resource:
resources.length: 1
Resource:
typeName: SupportTicketDetails
resourceInstancePath: /subscriptions/{subscriptionId}/providers/Microsoft.ContosoProviderHub/supportTickets/{supportTicketName}
resourceName: SupportTickets
scope: Subscription
lifecycle.read[0]:
kind=read, operationGroup=SupportTickets
path=/subscriptions/{subscriptionId}/providers/.../supportTickets/{supportTicketName}
lifecycle.read[1]:
kind=read, operationGroup=SupportTicketsNoSubscription
path=/providers/.../supportTickets/{supportTicketName}
lists[0]:
kind=list, operationGroup=SupportTickets
path=/subscriptions/{subscriptionId}/providers/.../supportTickets
lists[1]:
kind=list, operationGroup=SupportTicketsNoSubscription
path=/providers/.../supportTickets
The operations from both scopes are merged into a single resource. The individual operations do retain their correct paths (e.g., lifecycle.read[1] has the tenant path /providers/.../supportTickets/{name}), but the resource-level scope is "Subscription" and only the subscription resourceInstancePath is used.
Affected package
packages/typespec-azure-resource-manager/src/resource.ts — resolveArmResources function