From a696c946e73b5d0b64696e76bb2d0108606df523 Mon Sep 17 00:00:00 2001 From: Mark Cowlishaw Date: Sun, 10 Aug 2025 16:46:52 -0700 Subject: [PATCH 1/5] Add legacy templates for optional location and etags --- .../leg-temps-aug-2025-7-10-16-45-28.md | 7 + .../legacy/non-standard-properties/main.tsp | 72 +++ .../2021-10-01-preview/openapi.json | 561 ++++++++++++++++++ .../lib/Legacy/arm.legacy.tsp | 1 + .../lib/Legacy/resource.tsp | 47 ++ .../lib/models.tsp | 19 + .../src/private.decorators.ts | 5 +- .../src/resource.ts | 5 +- .../reference/data-types.md | 70 +++ .../reference/index.mdx | 3 + 10 files changed, 788 insertions(+), 2 deletions(-) create mode 100644 .chronus/changes/leg-temps-aug-2025-7-10-16-45-28.md create mode 100644 packages/samples/specs/resource-manager/legacy/non-standard-properties/main.tsp create mode 100644 packages/samples/test/output/azure/resource-manager/legacy/non-standard-properties/@azure-tools/typespec-autorest/2021-10-01-preview/openapi.json create mode 100644 packages/typespec-azure-resource-manager/lib/Legacy/resource.tsp diff --git a/.chronus/changes/leg-temps-aug-2025-7-10-16-45-28.md b/.chronus/changes/leg-temps-aug-2025-7-10-16-45-28.md new file mode 100644 index 0000000000..66ddba39a4 --- /dev/null +++ b/.chronus/changes/leg-temps-aug-2025-7-10-16-45-28.md @@ -0,0 +1,7 @@ +--- +changeKind: fix +packages: + - "@azure-tools/typespec-azure-resource-manager" +--- + +Add templates for optional location and etags \ No newline at end of file diff --git a/packages/samples/specs/resource-manager/legacy/non-standard-properties/main.tsp b/packages/samples/specs/resource-manager/legacy/non-standard-properties/main.tsp new file mode 100644 index 0000000000..f1edb4ad4d --- /dev/null +++ b/packages/samples/specs/resource-manager/legacy/non-standard-properties/main.tsp @@ -0,0 +1,72 @@ +import "@typespec/http"; +import "@typespec/rest"; +import "@typespec/versioning"; +import "@azure-tools/typespec-azure-core"; +import "@azure-tools/typespec-azure-resource-manager"; + +using Http; +using Rest; +using Versioning; +using Azure.ResourceManager; + +@armProviderNamespace +@service(#{ title: "ContosoProviderHubClient" }) +@versioned(Versions) +@doc("Contoso Resource Provider management API.") +namespace Microsoft.ContosoProviderHub; + +/** Contoso API versions */ +enum Versions { + /** 2021-10-01-preview version */ + @useDependency(Azure.ResourceManager.Versions.v1_0_Preview_1) + @armCommonTypesVersion(Azure.ResourceManager.CommonTypes.Versions.v5) + `2021-10-01-preview`, +} + +interface Operations extends Azure.ResourceManager.Operations {} + +#suppress "@azure-tools/typespec-azure-core/no-legacy-usage" "This is a legacy sample" +@doc("A ContosoProviderHub resource") +model Employee is Legacy.TrackedResourceWithOptionalLocation { + ...ResourceNameParameter; + ...ETagProperty; +} + +@doc("The rp-specific properties of the employee") +model EmployeeProperties { + @doc("The employee age in years") + age?: int32; + + @doc("The city of current residence") + city?: string; + + @doc("security profile for the employee") + @encode("base64url") + profile?: bytes; + + @visibility(Lifecycle.Read) + @doc("The status of the last operation.") + provisioningState?: ProvisioningState; +} + +@armResourceOperations +interface Employees extends TrackedResourceOperations {} + +@doc("The provisioning state of a resource.") +@Azure.Core.lroStatus +union ProvisioningState { + string, + ResourceProvisioningState, + + @doc("The resource is being provisioned") + Provisioning: "Provisioning", + + @doc("The resource is updating") + Updating: "Updating", + + @doc("The resource is being deleted") + Deleting: "Deleting", + + @doc("The resource create request has been accepted") + Accepted: "Accepted", +} diff --git a/packages/samples/test/output/azure/resource-manager/legacy/non-standard-properties/@azure-tools/typespec-autorest/2021-10-01-preview/openapi.json b/packages/samples/test/output/azure/resource-manager/legacy/non-standard-properties/@azure-tools/typespec-autorest/2021-10-01-preview/openapi.json new file mode 100644 index 0000000000..98f551d456 --- /dev/null +++ b/packages/samples/test/output/azure/resource-manager/legacy/non-standard-properties/@azure-tools/typespec-autorest/2021-10-01-preview/openapi.json @@ -0,0 +1,561 @@ +{ + "swagger": "2.0", + "info": { + "title": "ContosoProviderHubClient", + "version": "2021-10-01-preview", + "description": "Contoso Resource Provider management API.", + "x-typespec-generated": [ + { + "emitter": "@azure-tools/typespec-autorest" + } + ] + }, + "schemes": [ + "https" + ], + "host": "management.azure.com", + "produces": [ + "application/json" + ], + "consumes": [ + "application/json" + ], + "security": [ + { + "azure_auth": [ + "user_impersonation" + ] + } + ], + "securityDefinitions": { + "azure_auth": { + "type": "oauth2", + "description": "Azure Active Directory OAuth2 Flow.", + "flow": "implicit", + "authorizationUrl": "https://login.microsoftonline.com/common/oauth2/authorize", + "scopes": { + "user_impersonation": "impersonate your user account" + } + } + }, + "tags": [ + { + "name": "Operations" + }, + { + "name": "Employees" + } + ], + "paths": { + "/providers/Microsoft.ContosoProviderHub/operations": { + "get": { + "operationId": "Operations_List", + "tags": [ + "Operations" + ], + "description": "List the operations for the provider", + "parameters": [ + { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/parameters/ApiVersionParameter" + } + ], + "responses": { + "200": { + "description": "Azure operation completed successfully.", + "schema": { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/definitions/OperationListResult" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/definitions/ErrorResponse" + } + } + }, + "x-ms-pageable": { + "nextLinkName": "nextLink" + } + } + }, + "/subscriptions/{subscriptionId}/providers/Microsoft.ContosoProviderHub/employees": { + "get": { + "operationId": "Employees_ListBySubscription", + "tags": [ + "Employees" + ], + "description": "List Employee resources by subscription ID", + "parameters": [ + { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/parameters/ApiVersionParameter" + }, + { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/parameters/SubscriptionIdParameter" + } + ], + "responses": { + "200": { + "description": "Azure operation completed successfully.", + "schema": { + "$ref": "#/definitions/EmployeeListResult" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/definitions/ErrorResponse" + } + } + }, + "x-ms-pageable": { + "nextLinkName": "nextLink" + } + } + }, + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ContosoProviderHub/employees": { + "get": { + "operationId": "Employees_ListByResourceGroup", + "tags": [ + "Employees" + ], + "description": "List Employee resources by resource group", + "parameters": [ + { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/parameters/ApiVersionParameter" + }, + { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/parameters/SubscriptionIdParameter" + }, + { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/parameters/ResourceGroupNameParameter" + } + ], + "responses": { + "200": { + "description": "Azure operation completed successfully.", + "schema": { + "$ref": "#/definitions/EmployeeListResult" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/definitions/ErrorResponse" + } + } + }, + "x-ms-pageable": { + "nextLinkName": "nextLink" + } + } + }, + "/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ContosoProviderHub/employees/{employeeName}": { + "get": { + "operationId": "Employees_Get", + "tags": [ + "Employees" + ], + "description": "Get a Employee", + "parameters": [ + { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/parameters/ApiVersionParameter" + }, + { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/parameters/SubscriptionIdParameter" + }, + { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/parameters/ResourceGroupNameParameter" + }, + { + "name": "employeeName", + "in": "path", + "description": "The name of the Employee", + "required": true, + "type": "string", + "pattern": "^[a-zA-Z0-9-]{3,24}$" + } + ], + "responses": { + "200": { + "description": "Azure operation completed successfully.", + "schema": { + "$ref": "#/definitions/Employee" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/definitions/ErrorResponse" + } + } + } + }, + "put": { + "operationId": "Employees_CreateOrUpdate", + "tags": [ + "Employees" + ], + "description": "Create a Employee", + "parameters": [ + { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/parameters/ApiVersionParameter" + }, + { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/parameters/SubscriptionIdParameter" + }, + { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/parameters/ResourceGroupNameParameter" + }, + { + "name": "employeeName", + "in": "path", + "description": "The name of the Employee", + "required": true, + "type": "string", + "pattern": "^[a-zA-Z0-9-]{3,24}$" + }, + { + "name": "resource", + "in": "body", + "description": "Resource create parameters.", + "required": true, + "schema": { + "$ref": "#/definitions/Employee" + } + } + ], + "responses": { + "200": { + "description": "Resource 'Employee' update operation succeeded", + "schema": { + "$ref": "#/definitions/Employee" + } + }, + "201": { + "description": "Resource 'Employee' create operation succeeded", + "schema": { + "$ref": "#/definitions/Employee" + }, + "headers": { + "Azure-AsyncOperation": { + "type": "string", + "description": "A link to the status monitor" + }, + "Retry-After": { + "type": "integer", + "format": "int32", + "description": "The Retry-After header can indicate how long the client should wait before polling the operation status." + } + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/definitions/ErrorResponse" + } + } + }, + "x-ms-long-running-operation-options": { + "final-state-via": "azure-async-operation" + }, + "x-ms-long-running-operation": true + }, + "patch": { + "operationId": "Employees_Update", + "tags": [ + "Employees" + ], + "description": "Update a Employee", + "parameters": [ + { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/parameters/ApiVersionParameter" + }, + { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/parameters/SubscriptionIdParameter" + }, + { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/parameters/ResourceGroupNameParameter" + }, + { + "name": "employeeName", + "in": "path", + "description": "The name of the Employee", + "required": true, + "type": "string", + "pattern": "^[a-zA-Z0-9-]{3,24}$" + }, + { + "name": "properties", + "in": "body", + "description": "The resource properties to be updated.", + "required": true, + "schema": { + "$ref": "#/definitions/EmployeeUpdate" + } + } + ], + "responses": { + "200": { + "description": "Azure operation completed successfully.", + "schema": { + "$ref": "#/definitions/Employee" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/definitions/ErrorResponse" + } + } + } + }, + "delete": { + "operationId": "Employees_Delete", + "tags": [ + "Employees" + ], + "description": "Delete a Employee", + "parameters": [ + { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/parameters/ApiVersionParameter" + }, + { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/parameters/SubscriptionIdParameter" + }, + { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/parameters/ResourceGroupNameParameter" + }, + { + "name": "employeeName", + "in": "path", + "description": "The name of the Employee", + "required": true, + "type": "string", + "pattern": "^[a-zA-Z0-9-]{3,24}$" + } + ], + "responses": { + "200": { + "description": "Resource deleted successfully." + }, + "202": { + "description": "Resource deletion accepted.", + "headers": { + "Location": { + "type": "string", + "description": "The Location header contains the URL where the status of the long running operation can be checked." + }, + "Retry-After": { + "type": "integer", + "format": "int32", + "description": "The Retry-After header can indicate how long the client should wait before polling the operation status." + } + } + }, + "204": { + "description": "Resource does not exist." + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/definitions/ErrorResponse" + } + } + }, + "x-ms-long-running-operation-options": { + "final-state-via": "location" + }, + "x-ms-long-running-operation": true + } + } + }, + "definitions": { + "Employee": { + "type": "object", + "description": "A ContosoProviderHub resource", + "properties": { + "properties": { + "$ref": "#/definitions/EmployeeProperties", + "description": "The resource-specific properties for this resource.", + "x-ms-client-flatten": true + }, + "etag": { + "type": "string", + "description": "If etag is provided in the response body, it may also be provided as a header per the normal etag convention. Entity tags are used for comparing two or more entities from the same requested resource. HTTP/1.1 uses entity tags in the etag (section 14.19), If-Match (section 14.24), If-None-Match (section 14.26), and If-Range (section 14.27) header fields.", + "readOnly": true + } + }, + "allOf": [ + { + "$ref": "#/definitions/TrackedResource" + } + ] + }, + "EmployeeListResult": { + "type": "object", + "description": "The response of a Employee list operation.", + "properties": { + "value": { + "type": "array", + "description": "The Employee items on this page", + "items": { + "$ref": "#/definitions/Employee" + } + }, + "nextLink": { + "type": "string", + "format": "uri", + "description": "The link to the next page of items" + } + }, + "required": [ + "value" + ] + }, + "EmployeeProperties": { + "type": "object", + "description": "The rp-specific properties of the employee", + "properties": { + "age": { + "type": "integer", + "format": "int32", + "description": "The employee age in years" + }, + "city": { + "type": "string", + "description": "The city of current residence" + }, + "profile": { + "type": "string", + "format": "base64url", + "description": "security profile for the employee" + }, + "provisioningState": { + "$ref": "#/definitions/ProvisioningState", + "description": "The status of the last operation.", + "readOnly": true + } + } + }, + "EmployeeUpdate": { + "type": "object", + "description": "The type used for update operations of the Employee.", + "properties": { + "tags": { + "type": "object", + "description": "Resource tags.", + "additionalProperties": { + "type": "string" + } + }, + "properties": { + "$ref": "#/definitions/EmployeeUpdateProperties", + "description": "The resource-specific properties for this resource.", + "x-ms-client-flatten": true + } + } + }, + "EmployeeUpdateProperties": { + "type": "object", + "description": "The updatable properties of the Employee.", + "properties": { + "age": { + "type": "integer", + "format": "int32", + "description": "The employee age in years" + }, + "city": { + "type": "string", + "description": "The city of current residence" + }, + "profile": { + "type": "string", + "format": "base64url", + "description": "security profile for the employee" + } + } + }, + "ProvisioningState": { + "type": "string", + "description": "The provisioning state of a resource.", + "enum": [ + "Succeeded", + "Failed", + "Canceled", + "Provisioning", + "Updating", + "Deleting", + "Accepted" + ], + "x-ms-enum": { + "name": "ProvisioningState", + "modelAsString": true, + "values": [ + { + "name": "Succeeded", + "value": "Succeeded", + "description": "Resource has been created." + }, + { + "name": "Failed", + "value": "Failed", + "description": "Resource creation failed." + }, + { + "name": "Canceled", + "value": "Canceled", + "description": "Resource creation was canceled." + }, + { + "name": "Provisioning", + "value": "Provisioning", + "description": "The resource is being provisioned" + }, + { + "name": "Updating", + "value": "Updating", + "description": "The resource is updating" + }, + { + "name": "Deleting", + "value": "Deleting", + "description": "The resource is being deleted" + }, + { + "name": "Accepted", + "value": "Accepted", + "description": "The resource create request has been accepted" + } + ] + }, + "readOnly": true + }, + "TrackedResource": { + "type": "object", + "description": "A tracked resource with the 'location' property optional", + "properties": { + "tags": { + "type": "object", + "description": "Resource tags.", + "additionalProperties": { + "type": "string" + } + }, + "location": { + "type": "string", + "description": "The geo-location where the resource lives", + "x-ms-mutability": [ + "read", + "create" + ] + } + }, + "allOf": [ + { + "$ref": "../../../../../../../../../specs/resource-manager/common-types/v5/types.json#/definitions/Resource" + } + ] + } + }, + "parameters": {} +} diff --git a/packages/typespec-azure-resource-manager/lib/Legacy/arm.legacy.tsp b/packages/typespec-azure-resource-manager/lib/Legacy/arm.legacy.tsp index 4ff7d5ac4e..4b862d7a53 100644 --- a/packages/typespec-azure-resource-manager/lib/Legacy/arm.legacy.tsp +++ b/packages/typespec-azure-resource-manager/lib/Legacy/arm.legacy.tsp @@ -5,5 +5,6 @@ import "./interfaces.tsp"; import "./extension.tsp"; import "./extension-operations.tsp"; import "./private-endpoints.tsp"; +import "./resource.tsp"; namespace Azure.ResourceManager.Legacy; diff --git a/packages/typespec-azure-resource-manager/lib/Legacy/resource.tsp b/packages/typespec-azure-resource-manager/lib/Legacy/resource.tsp new file mode 100644 index 0000000000..6d0b1b16b0 --- /dev/null +++ b/packages/typespec-azure-resource-manager/lib/Legacy/resource.tsp @@ -0,0 +1,47 @@ +import "@typespec/rest"; +import "@typespec/http"; + +namespace Azure.ResourceManager.Legacy; + +using Azure.ResourceManager.Private; + +/** + * This type uses an optional location property, only used by legacy APIs. + * Concrete tracked resource types can be created by aliasing this type using a specific property type. + * + * See more details on [different Azure Resource Manager resource type here.](https://azure.github.io/typespec-azure/docs/howtos/ARM/resource-type) + * @template Properties A model containing the provider-specific properties for this resource + * @template PropertiesOptional A boolean flag indicating whether the resource `Properties` field is marked as optional or required. Default true is optional and recommended. + * + * @example + * ```typespec + * model Employee is TrackedResourceWithOptionalLocation { + * ...ResourceNameParameter + * } + * ``` + */ +@doc("Concrete tracked resource types can be created by aliasing this type using a specific property type.") +@armResourceInternal(Properties) +@Http.Private.includeInapplicableMetadataInPayload(false) +model TrackedResourceWithOptionalLocation< + Properties extends {}, + PropertiesOptional extends valueof boolean = true +> extends LegacyTrackedResource { + @doc("The resource-specific properties for this resource.") + @conditionalClientFlatten + @armResourcePropertiesOptionality(PropertiesOptional) + properties?: Properties; +} + +/** + * A tracked resource with the 'location' property optional + */ +@friendlyName("TrackedResource") +model LegacyTrackedResource extends CommonTypes.Resource { + /** Resource tags. */ + tags?: Record; + + /** The geo-location where the resource lives */ + @visibility(Lifecycle.Read, Lifecycle.Create) + location?: string; +} diff --git a/packages/typespec-azure-resource-manager/lib/models.tsp b/packages/typespec-azure-resource-manager/lib/models.tsp index 62892c3ac1..d1553984b9 100644 --- a/packages/typespec-azure-resource-manager/lib/models.tsp +++ b/packages/typespec-azure-resource-manager/lib/models.tsp @@ -302,6 +302,25 @@ model EntityTagProperty { eTag?: string; } +/** + * Model used only to spread in the standard `etag` envelope property for a resource + * + * @example + * + * ```typespec + * model Foo is TrackedResource { + * // Only have standard Succeeded, Failed, Cancelled states + * ...ETagProperty; + * } + * ``` + */ +@doc("The etag property envelope.") +model ETagProperty { + @doc("If etag is provided in the response body, it may also be provided as a header per the normal etag convention. Entity tags are used for comparing two or more entities from the same requested resource. HTTP/1.1 uses entity tags in the etag (section 14.19), If-Match (section 14.24), If-None-Match (section 14.26), and If-Range (section 14.27) header fields.") + @visibility(Lifecycle.Read) + etag?: string; +} + #deprecated "`ResourceKind` will be deprecated. Please use `ResourceKindProperty` instead." alias ResourceKind = ResourceKindProperty; /** diff --git a/packages/typespec-azure-resource-manager/src/private.decorators.ts b/packages/typespec-azure-resource-manager/src/private.decorators.ts index 59fb8051a1..e6ea52a75f 100644 --- a/packages/typespec-azure-resource-manager/src/private.decorators.ts +++ b/packages/typespec-azure-resource-manager/src/private.decorators.ts @@ -409,7 +409,10 @@ export function registerArmResource( nameParameter?: string, ): void { const { program } = context; - if (resourceType.namespace && getTypeName(resourceType.namespace) === "Azure.ResourceManager") { + if ( + resourceType.namespace && + getTypeName(resourceType.namespace).startsWith("Azure.ResourceManager") + ) { // The @armResource decorator will be evaluated on instantiations of // base templated resource types like TrackedResource, // so ignore in that case. diff --git a/packages/typespec-azure-resource-manager/src/resource.ts b/packages/typespec-azure-resource-manager/src/resource.ts index 6dd9b80718..db39050de8 100644 --- a/packages/typespec-azure-resource-manager/src/resource.ts +++ b/packages/typespec-azure-resource-manager/src/resource.ts @@ -731,7 +731,10 @@ export function getArmResourceInfo( export function getArmResourceKind(resourceType: Model): ArmResourceKind | undefined { if (resourceType.baseModel) { const coreType = resourceType.baseModel; - if (coreType.name.startsWith("TrackedResource")) { + if ( + coreType.name.startsWith("TrackedResource") || + coreType.name.startsWith("LegacyTrackedResource") + ) { return "Tracked"; } else if (coreType.name.startsWith("ProxyResource")) { return "Proxy"; diff --git a/website/src/content/docs/docs/libraries/azure-resource-manager/reference/data-types.md b/website/src/content/docs/docs/libraries/azure-resource-manager/reference/data-types.md index f4497c800d..0022f552f2 100644 --- a/website/src/content/docs/docs/libraries/azure-resource-manager/reference/data-types.md +++ b/website/src/content/docs/docs/libraries/azure-resource-manager/reference/data-types.md @@ -606,6 +606,29 @@ model Foo is TrackedResource { | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | eTag? | `string` | If eTag is provided in the response body, it may also be provided as a header per the normal etag convention. Entity tags are used for comparing two or more entities from the same requested resource. HTTP/1.1 uses entity tags in the etag (section 14.19), If-Match (section 14.24), If-None-Match (section 14.26), and If-Range (section 14.27) header fields. | +### `ETagProperty` {#Azure.ResourceManager.ETagProperty} + +Model used only to spread in the standard `etag` envelope property for a resource + +```typespec +model Azure.ResourceManager.ETagProperty +``` + +#### Examples + +```typespec +model Foo is TrackedResource { + // Only have standard Succeeded, Failed, Cancelled states + ...ETagProperty; +} +``` + +#### Properties + +| Name | Type | Description | +| ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| etag? | `string` | If etag is provided in the response body, it may also be provided as a header per the normal etag convention. Entity tags are used for comparing two or more entities from the same requested resource. HTTP/1.1 uses entity tags in the etag (section 14.19), If-Match (section 14.24), If-None-Match (section 14.26), and If-Range (section 14.27) header fields. | + ### `ExtendedLocationProperty` {#Azure.ResourceManager.ExtendedLocationProperty} Model representing the standard `extendedLocation` envelope property for a resource. @@ -3370,6 +3393,21 @@ model Azure.ResourceManager.Legacy.ArmOperationOptions | useStaticRoute? | `boolean` | Should a static route be used | | route? | `string` | The status route for operations to use | +### `LegacyTrackedResource` {#Azure.ResourceManager.Legacy.LegacyTrackedResource} + +A tracked resource with the 'location' property optional + +```typespec +model Azure.ResourceManager.Legacy.LegacyTrackedResource +``` + +#### Properties + +| Name | Type | Description | +| --------- | ---------------- | ----------------------------------------- | +| tags? | `Record` | Resource tags. | +| location? | `string` | The geo-location where the resource lives | + ### `ManagedServiceIdentityV4` {#Azure.ResourceManager.Legacy.ManagedServiceIdentityV4} Managed service identity (system assigned and/or user assigned identities) @@ -3451,6 +3489,38 @@ model Azure.ResourceManager.Legacy.ProviderParameter | -------- | -------------------------------- | ----------- | | provider | `"Microsoft.ThisWillBeReplaced"` | | +### `TrackedResourceWithOptionalLocation` {#Azure.ResourceManager.Legacy.TrackedResourceWithOptionalLocation} + +This type uses an optional location property, only used by legacy APIs. +Concrete tracked resource types can be created by aliasing this type using a specific property type. + +See more details on [different Azure Resource Manager resource type here.](https://azure.github.io/typespec-azure/docs/howtos/ARM/resource-type) + +```typespec +model Azure.ResourceManager.Legacy.TrackedResourceWithOptionalLocation +``` + +#### Template Parameters + +| Name | Description | +| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| Properties | A model containing the provider-specific properties for this resource | +| PropertiesOptional | A boolean flag indicating whether the resource `Properties` field is marked as optional or required. Default true is optional and recommended. | + +#### Examples + +```typespec +model Employee is TrackedResourceWithOptionalLocation { + ...ResourceNameParameter; +} +``` + +#### Properties + +| Name | Type | Description | +| ----------- | ------------ | ----------- | +| properties? | `Properties` | | + ### `ManagedServiceIdentityType` {#Azure.ResourceManager.Legacy.ManagedServiceIdentityType} Type of managed service identity (where both SystemAssigned and UserAssigned types are allowed). diff --git a/website/src/content/docs/docs/libraries/azure-resource-manager/reference/index.mdx b/website/src/content/docs/docs/libraries/azure-resource-manager/reference/index.mdx index 120d02fb24..80123b88cc 100644 --- a/website/src/content/docs/docs/libraries/azure-resource-manager/reference/index.mdx +++ b/website/src/content/docs/docs/libraries/azure-resource-manager/reference/index.mdx @@ -138,6 +138,7 @@ npm install --save-peer @azure-tools/typespec-azure-resource-manager - [`DefaultProvisioningStateProperty`](./data-types.md#Azure.ResourceManager.DefaultProvisioningStateProperty) - [`EncryptionProperty`](./data-types.md#Azure.ResourceManager.EncryptionProperty) - [`EntityTagProperty`](./data-types.md#Azure.ResourceManager.EntityTagProperty) +- [`ETagProperty`](./data-types.md#Azure.ResourceManager.ETagProperty) - [`ExtendedLocationProperty`](./data-types.md#Azure.ResourceManager.ExtendedLocationProperty) - [`ExtensionActionScope`](./data-types.md#Azure.ResourceManager.ExtensionActionScope) - [`ExtensionResource`](./data-types.md#Azure.ResourceManager.ExtensionResource) @@ -338,10 +339,12 @@ npm install --save-peer @azure-tools/typespec-azure-resource-manager ### Models - [`ArmOperationOptions`](./data-types.md#Azure.ResourceManager.Legacy.ArmOperationOptions) +- [`LegacyTrackedResource`](./data-types.md#Azure.ResourceManager.Legacy.LegacyTrackedResource) - [`ManagedServiceIdentityV4`](./data-types.md#Azure.ResourceManager.Legacy.ManagedServiceIdentityV4) - [`ManagedServiceIdentityV4Property`](./data-types.md#Azure.ResourceManager.Legacy.ManagedServiceIdentityV4Property) - [`Provider`](./data-types.md#Azure.ResourceManager.Legacy.Provider) - [`ProviderParameter`](./data-types.md#Azure.ResourceManager.Legacy.ProviderParameter) +- [`TrackedResourceWithOptionalLocation`](./data-types.md#Azure.ResourceManager.Legacy.TrackedResourceWithOptionalLocation) ## Azure.ResourceManager.Legacy.Extension From 79d5040de8cfed41d076b0c3acecf4ca04e9373c Mon Sep 17 00:00:00 2001 From: Mark Cowlishaw Date: Mon, 11 Aug 2025 13:49:42 -0700 Subject: [PATCH 2/5] Fix issues with registration check and respond to feedback --- .../legacy/non-standard-properties/main.tsp | 2 +- .../lib/Legacy/resource.tsp | 18 ++++++++++++++++++ .../src/private.decorators.ts | 6 ++++-- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/packages/samples/specs/resource-manager/legacy/non-standard-properties/main.tsp b/packages/samples/specs/resource-manager/legacy/non-standard-properties/main.tsp index f1edb4ad4d..ca09ab76b3 100644 --- a/packages/samples/specs/resource-manager/legacy/non-standard-properties/main.tsp +++ b/packages/samples/specs/resource-manager/legacy/non-standard-properties/main.tsp @@ -29,7 +29,7 @@ interface Operations extends Azure.ResourceManager.Operations {} @doc("A ContosoProviderHub resource") model Employee is Legacy.TrackedResourceWithOptionalLocation { ...ResourceNameParameter; - ...ETagProperty; + ...Legacy.EntityTagProperty; } @doc("The rp-specific properties of the employee") diff --git a/packages/typespec-azure-resource-manager/lib/Legacy/resource.tsp b/packages/typespec-azure-resource-manager/lib/Legacy/resource.tsp index 6d0b1b16b0..2152a221b2 100644 --- a/packages/typespec-azure-resource-manager/lib/Legacy/resource.tsp +++ b/packages/typespec-azure-resource-manager/lib/Legacy/resource.tsp @@ -45,3 +45,21 @@ model LegacyTrackedResource extends CommonTypes.Resource { @visibility(Lifecycle.Read, Lifecycle.Create) location?: string; } + +/** + * Model used only to spread in the standard `etag` envelope property for a resource + * + * @example + * + * ```typespec + * model Foo is TrackedResource { + * // Only have standard Succeeded, Failed, Cancelled states + * ...ETagProperty; + * } + * ``` + */ +alias EntityTagProperty = { + @doc("If etag is provided in the response body, it may also be provided as a header per the normal etag convention. Entity tags are used for comparing two or more entities from the same requested resource. HTTP/1.1 uses entity tags in the etag (section 14.19), If-Match (section 14.24), If-None-Match (section 14.26), and If-Range (section 14.27) header fields.") + @visibility(Lifecycle.Read) + etag?: string; +}; diff --git a/packages/typespec-azure-resource-manager/src/private.decorators.ts b/packages/typespec-azure-resource-manager/src/private.decorators.ts index e6ea52a75f..3496297acd 100644 --- a/packages/typespec-azure-resource-manager/src/private.decorators.ts +++ b/packages/typespec-azure-resource-manager/src/private.decorators.ts @@ -409,9 +409,11 @@ export function registerArmResource( nameParameter?: string, ): void { const { program } = context; + const namespaceName = resourceType.namespace ? getTypeName(resourceType.namespace) : undefined; if ( - resourceType.namespace && - getTypeName(resourceType.namespace).startsWith("Azure.ResourceManager") + namespaceName === undefined || + namespaceName === "Azure.ResourceManager" || + namespaceName === "Azure.ResourceManager.Legacy" ) { // The @armResource decorator will be evaluated on instantiations of // base templated resource types like TrackedResource, From 860674d4cd0028d9e1e3cf19e2ddde9d859e237e Mon Sep 17 00:00:00 2001 From: Mark Cowlishaw Date: Mon, 11 Aug 2025 13:54:35 -0700 Subject: [PATCH 3/5] Respond to review feedback --- .../lib/models.tsp | 19 --------------- .../reference/data-types.md | 23 ------------------- .../reference/index.mdx | 1 - 3 files changed, 43 deletions(-) diff --git a/packages/typespec-azure-resource-manager/lib/models.tsp b/packages/typespec-azure-resource-manager/lib/models.tsp index d1553984b9..62892c3ac1 100644 --- a/packages/typespec-azure-resource-manager/lib/models.tsp +++ b/packages/typespec-azure-resource-manager/lib/models.tsp @@ -302,25 +302,6 @@ model EntityTagProperty { eTag?: string; } -/** - * Model used only to spread in the standard `etag` envelope property for a resource - * - * @example - * - * ```typespec - * model Foo is TrackedResource { - * // Only have standard Succeeded, Failed, Cancelled states - * ...ETagProperty; - * } - * ``` - */ -@doc("The etag property envelope.") -model ETagProperty { - @doc("If etag is provided in the response body, it may also be provided as a header per the normal etag convention. Entity tags are used for comparing two or more entities from the same requested resource. HTTP/1.1 uses entity tags in the etag (section 14.19), If-Match (section 14.24), If-None-Match (section 14.26), and If-Range (section 14.27) header fields.") - @visibility(Lifecycle.Read) - etag?: string; -} - #deprecated "`ResourceKind` will be deprecated. Please use `ResourceKindProperty` instead." alias ResourceKind = ResourceKindProperty; /** diff --git a/website/src/content/docs/docs/libraries/azure-resource-manager/reference/data-types.md b/website/src/content/docs/docs/libraries/azure-resource-manager/reference/data-types.md index 0022f552f2..f43af2c21f 100644 --- a/website/src/content/docs/docs/libraries/azure-resource-manager/reference/data-types.md +++ b/website/src/content/docs/docs/libraries/azure-resource-manager/reference/data-types.md @@ -606,29 +606,6 @@ model Foo is TrackedResource { | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | eTag? | `string` | If eTag is provided in the response body, it may also be provided as a header per the normal etag convention. Entity tags are used for comparing two or more entities from the same requested resource. HTTP/1.1 uses entity tags in the etag (section 14.19), If-Match (section 14.24), If-None-Match (section 14.26), and If-Range (section 14.27) header fields. | -### `ETagProperty` {#Azure.ResourceManager.ETagProperty} - -Model used only to spread in the standard `etag` envelope property for a resource - -```typespec -model Azure.ResourceManager.ETagProperty -``` - -#### Examples - -```typespec -model Foo is TrackedResource { - // Only have standard Succeeded, Failed, Cancelled states - ...ETagProperty; -} -``` - -#### Properties - -| Name | Type | Description | -| ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| etag? | `string` | If etag is provided in the response body, it may also be provided as a header per the normal etag convention. Entity tags are used for comparing two or more entities from the same requested resource. HTTP/1.1 uses entity tags in the etag (section 14.19), If-Match (section 14.24), If-None-Match (section 14.26), and If-Range (section 14.27) header fields. | - ### `ExtendedLocationProperty` {#Azure.ResourceManager.ExtendedLocationProperty} Model representing the standard `extendedLocation` envelope property for a resource. diff --git a/website/src/content/docs/docs/libraries/azure-resource-manager/reference/index.mdx b/website/src/content/docs/docs/libraries/azure-resource-manager/reference/index.mdx index 80123b88cc..bdecd231fe 100644 --- a/website/src/content/docs/docs/libraries/azure-resource-manager/reference/index.mdx +++ b/website/src/content/docs/docs/libraries/azure-resource-manager/reference/index.mdx @@ -138,7 +138,6 @@ npm install --save-peer @azure-tools/typespec-azure-resource-manager - [`DefaultProvisioningStateProperty`](./data-types.md#Azure.ResourceManager.DefaultProvisioningStateProperty) - [`EncryptionProperty`](./data-types.md#Azure.ResourceManager.EncryptionProperty) - [`EntityTagProperty`](./data-types.md#Azure.ResourceManager.EntityTagProperty) -- [`ETagProperty`](./data-types.md#Azure.ResourceManager.ETagProperty) - [`ExtendedLocationProperty`](./data-types.md#Azure.ResourceManager.ExtendedLocationProperty) - [`ExtensionActionScope`](./data-types.md#Azure.ResourceManager.ExtensionActionScope) - [`ExtensionResource`](./data-types.md#Azure.ResourceManager.ExtensionResource) From b84d47aaf70023b097b7dd297efe49b808b29376 Mon Sep 17 00:00:00 2001 From: Mark Cowlishaw Date: Mon, 11 Aug 2025 14:00:41 -0700 Subject: [PATCH 4/5] Respond to review feedback --- .../typespec-azure-resource-manager/lib/Legacy/resource.tsp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typespec-azure-resource-manager/lib/Legacy/resource.tsp b/packages/typespec-azure-resource-manager/lib/Legacy/resource.tsp index 2152a221b2..01b48b5df1 100644 --- a/packages/typespec-azure-resource-manager/lib/Legacy/resource.tsp +++ b/packages/typespec-azure-resource-manager/lib/Legacy/resource.tsp @@ -59,7 +59,7 @@ model LegacyTrackedResource extends CommonTypes.Resource { * ``` */ alias EntityTagProperty = { - @doc("If etag is provided in the response body, it may also be provided as a header per the normal etag convention. Entity tags are used for comparing two or more entities from the same requested resource. HTTP/1.1 uses entity tags in the etag (section 14.19), If-Match (section 14.24), If-None-Match (section 14.26), and If-Range (section 14.27) header fields.") + /** "If etag is provided in the response body, it may also be provided as a header per the normal etag convention. Entity tags are used for comparing two or more entities from the same requested resource. HTTP/1.1 uses entity tags in the etag (section 14.19), If-Match (section 14.24), If-None-Match (section 14.26), and If-Range (section 14.27) header fields.") */ @visibility(Lifecycle.Read) etag?: string; }; From 1454ff92ca225e415703989c89cb50af18e750f5 Mon Sep 17 00:00:00 2001 From: Mark Cowlishaw Date: Mon, 11 Aug 2025 15:10:41 -0700 Subject: [PATCH 5/5] Regenerate samples for new documentation --- .../typespec-autorest/2021-10-01-preview/openapi.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/samples/test/output/azure/resource-manager/legacy/non-standard-properties/@azure-tools/typespec-autorest/2021-10-01-preview/openapi.json b/packages/samples/test/output/azure/resource-manager/legacy/non-standard-properties/@azure-tools/typespec-autorest/2021-10-01-preview/openapi.json index 98f551d456..d9834a0a05 100644 --- a/packages/samples/test/output/azure/resource-manager/legacy/non-standard-properties/@azure-tools/typespec-autorest/2021-10-01-preview/openapi.json +++ b/packages/samples/test/output/azure/resource-manager/legacy/non-standard-properties/@azure-tools/typespec-autorest/2021-10-01-preview/openapi.json @@ -381,7 +381,7 @@ }, "etag": { "type": "string", - "description": "If etag is provided in the response body, it may also be provided as a header per the normal etag convention. Entity tags are used for comparing two or more entities from the same requested resource. HTTP/1.1 uses entity tags in the etag (section 14.19), If-Match (section 14.24), If-None-Match (section 14.26), and If-Range (section 14.27) header fields.", + "description": "\"If etag is provided in the response body, it may also be provided as a header per the normal etag convention. Entity tags are used for comparing two or more entities from the same requested resource. HTTP/1.1 uses entity tags in the etag (section 14.19), If-Match (section 14.24), If-None-Match (section 14.26), and If-Range (section 14.27) header fields.\")", "readOnly": true } },